diff --git a/.coveragerc b/.coveragerc index 3785240a387b06..8d3aeb916d1164 100644 --- a/.coveragerc +++ b/.coveragerc @@ -33,7 +33,11 @@ omit = homeassistant/components/airvisual/air_quality.py homeassistant/components/airvisual/sensor.py homeassistant/components/aladdin_connect/cover.py - homeassistant/components/alarmdecoder/* + homeassistant/components/alarmdecoder/__init__.py + homeassistant/components/alarmdecoder/alarm_control_panel.py + homeassistant/components/alarmdecoder/binary_sensor.py + homeassistant/components/alarmdecoder/const.py + homeassistant/components/alarmdecoder/sensor.py homeassistant/components/alpha_vantage/sensor.py homeassistant/components/amazon_polly/tts.py homeassistant/components/ambiclimate/climate.py @@ -117,7 +121,6 @@ omit = homeassistant/components/buienradar/util.py homeassistant/components/buienradar/weather.py homeassistant/components/caldav/calendar.py - homeassistant/components/canary/alarm_control_panel.py homeassistant/components/canary/camera.py homeassistant/components/cast/* homeassistant/components/cert_expiry/helper.py @@ -266,7 +269,9 @@ omit = homeassistant/components/firmata/board.py homeassistant/components/firmata/const.py homeassistant/components/firmata/entity.py + homeassistant/components/firmata/light.py homeassistant/components/firmata/pin.py + homeassistant/components/firmata/sensor.py homeassistant/components/firmata/switch.py homeassistant/components/fitbit/sensor.py homeassistant/components/fixer/sensor.py @@ -315,6 +320,8 @@ omit = homeassistant/components/glances/sensor.py homeassistant/components/gntp/notify.py homeassistant/components/goalfeed/* + homeassistant/components/goalzero/__init__.py + homeassistant/components/goalzero/binary_sensor.py homeassistant/components/google/* homeassistant/components/google_cloud/tts.py homeassistant/components/google_maps/device_tracker.py @@ -369,6 +376,7 @@ omit = homeassistant/components/hunterdouglas_powerview/sensor.py homeassistant/components/hunterdouglas_powerview/cover.py homeassistant/components/hunterdouglas_powerview/entity.py + homeassistant/components/hvv_departures/binary_sensor.py homeassistant/components/hvv_departures/sensor.py homeassistant/components/hvv_departures/__init__.py homeassistant/components/hydrawise/* @@ -478,7 +486,8 @@ omit = homeassistant/components/london_underground/sensor.py homeassistant/components/loopenergy/sensor.py homeassistant/components/luci/device_tracker.py - homeassistant/components/luftdaten/* + homeassistant/components/luftdaten/__init__.py + homeassistant/components/luftdaten/sensor.py homeassistant/components/lupusec/* homeassistant/components/lutron/* homeassistant/components/lutron_caseta/__init__.py @@ -530,7 +539,9 @@ omit = homeassistant/components/mjpeg/camera.py homeassistant/components/mobile_app/* homeassistant/components/mochad/* - homeassistant/components/modbus/* + homeassistant/components/modbus/climate.py + homeassistant/components/modbus/cover.py + homeassistant/components/modbus/switch.py homeassistant/components/modem_callerid/sensor.py homeassistant/components/mpchc/media_player.py homeassistant/components/mpd/media_player.py @@ -595,6 +606,10 @@ omit = homeassistant/components/oasa_telematics/sensor.py homeassistant/components/ohmconnect/sensor.py homeassistant/components/ombi/* + homeassistant/components/omnilogic/__init__.py + homeassistant/components/omnilogic/common.py + homeassistant/components/omnilogic/sensor.py + homeassistant/components/onewire/const.py homeassistant/components/onewire/sensor.py homeassistant/components/onkyo/media_player.py homeassistant/components/onvif/__init__.py @@ -636,6 +651,9 @@ omit = homeassistant/components/ovo_energy/__init__.py homeassistant/components/ovo_energy/const.py homeassistant/components/ovo_energy/sensor.py + homeassistant/components/ozw/__init__.py + homeassistant/components/ozw/entity.py + homeassistant/components/ozw/services.py homeassistant/components/panasonic_bluray/media_player.py homeassistant/components/panasonic_viera/media_player.py homeassistant/components/pandora/media_player.py @@ -803,6 +821,7 @@ omit = homeassistant/components/spc/* homeassistant/components/speedtestdotnet/* homeassistant/components/spider/* + homeassistant/components/splunk/* homeassistant/components/spotcrime/sensor.py homeassistant/components/spotify/__init__.py homeassistant/components/spotify/media_player.py @@ -829,9 +848,10 @@ omit = homeassistant/components/synology/camera.py homeassistant/components/synology_chat/notify.py homeassistant/components/synology_dsm/__init__.py - homeassistant/components/synology_dsm/camera.py homeassistant/components/synology_dsm/binary_sensor.py + homeassistant/components/synology_dsm/camera.py homeassistant/components/synology_dsm/sensor.py + homeassistant/components/synology_dsm/switch.py homeassistant/components/synology_srm/device_tracker.py homeassistant/components/syslog/notify.py homeassistant/components/systemmonitor/sensor.py @@ -845,7 +865,13 @@ omit = homeassistant/components/ted5000/sensor.py homeassistant/components/telegram/notify.py homeassistant/components/telegram_bot/* - homeassistant/components/tellduslive/* + homeassistant/components/tellduslive/__init__.py + homeassistant/components/tellduslive/binary_sensor.py + homeassistant/components/tellduslive/cover.py + homeassistant/components/tellduslive/entry.py + homeassistant/components/tellduslive/light.py + homeassistant/components/tellduslive/sensor.py + homeassistant/components/tellduslive/switch.py homeassistant/components/tellstick/* homeassistant/components/telnet/switch.py homeassistant/components/temper/sensor.py @@ -864,7 +890,9 @@ omit = homeassistant/components/thingspeak/* homeassistant/components/thinkingcleaner/* homeassistant/components/thomson/device_tracker.py - homeassistant/components/tibber/* + homeassistant/components/tibber/__init__.py + homeassistant/components/tibber/notify.py + homeassistant/components/tibber/sensor.py homeassistant/components/tikteck/light.py homeassistant/components/tile/__init__.py homeassistant/components/tile/device_tracker.py @@ -1025,12 +1053,8 @@ omit = homeassistant/components/zhong_hong/climate.py homeassistant/components/xbee/* homeassistant/components/ziggo_mediabox_xl/media_player.py - homeassistant/components/zoneminder/* homeassistant/components/supla/* homeassistant/components/zwave/util.py - homeassistant/components/ozw/__init__.py - homeassistant/components/ozw/entity.py - homeassistant/components/ozw/services.py [report] # Regexes for lines to exclude from consideration diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3d65df477e7605..600a94cec66605 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -25,7 +25,7 @@ jobs: uses: actions/checkout@v2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} id: python - uses: actions/setup-python@v2.1.2 + uses: actions/setup-python@v2.1.3 with: python-version: ${{ env.DEFAULT_PYTHON }} - name: Restore base Python virtual environment @@ -73,7 +73,7 @@ jobs: - name: Check out code from GitHub uses: actions/checkout@v2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v2.1.2 + uses: actions/setup-python@v2.1.3 id: python with: python-version: ${{ env.DEFAULT_PYTHON }} @@ -118,7 +118,7 @@ jobs: - name: Check out code from GitHub uses: actions/checkout@v2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v2.1.2 + uses: actions/setup-python@v2.1.3 id: python with: python-version: ${{ env.DEFAULT_PYTHON }} @@ -163,7 +163,7 @@ jobs: - name: Check out code from GitHub uses: actions/checkout@v2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v2.1.2 + uses: actions/setup-python@v2.1.3 id: python with: python-version: ${{ env.DEFAULT_PYTHON }} @@ -230,7 +230,7 @@ jobs: - name: Check out code from GitHub uses: actions/checkout@v2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v2.1.2 + uses: actions/setup-python@v2.1.3 id: python with: python-version: ${{ env.DEFAULT_PYTHON }} @@ -278,7 +278,7 @@ jobs: - name: Check out code from GitHub uses: actions/checkout@v2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v2.1.2 + uses: actions/setup-python@v2.1.3 id: python with: python-version: ${{ env.DEFAULT_PYTHON }} @@ -326,7 +326,7 @@ jobs: - name: Check out code from GitHub uses: actions/checkout@v2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v2.1.2 + uses: actions/setup-python@v2.1.3 id: python with: python-version: ${{ env.DEFAULT_PYTHON }} @@ -371,7 +371,7 @@ jobs: - name: Check out code from GitHub uses: actions/checkout@v2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v2.1.2 + uses: actions/setup-python@v2.1.3 id: python with: python-version: ${{ env.DEFAULT_PYTHON }} @@ -419,7 +419,7 @@ jobs: - name: Check out code from GitHub uses: actions/checkout@v2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v2.1.2 + uses: actions/setup-python@v2.1.3 id: python with: python-version: ${{ env.DEFAULT_PYTHON }} @@ -475,7 +475,7 @@ jobs: - name: Check out code from GitHub uses: actions/checkout@v2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v2.1.2 + uses: actions/setup-python@v2.1.3 id: python with: python-version: ${{ env.DEFAULT_PYTHON }} @@ -555,7 +555,7 @@ jobs: - name: Check out code from GitHub uses: actions/checkout@v2 - name: Set up Python ${{ env.DEFAULT_PYTHON }} - uses: actions/setup-python@v2.1.2 + uses: actions/setup-python@v2.1.3 id: python with: python-version: ${{ env.DEFAULT_PYTHON }} @@ -736,7 +736,7 @@ jobs: -p no:sugar \ tests - name: Upload coverage artifact - uses: actions/upload-artifact@v2.1.4 + uses: actions/upload-artifact@v2.2.0 with: name: coverage-${{ matrix.python-version }}-group${{ matrix.group }} path: .coverage diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e24b6095b4a011..121cc1eab8e900 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: - --quiet-level=2 exclude_types: [csv, json] - repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.3 + rev: 3.8.4 hooks: - id: flake8 additional_dependencies: @@ -39,7 +39,7 @@ repos: - --configfile=tests/bandit.yaml files: ^(homeassistant|script|tests)/.+\.py$ - repo: https://github.com/PyCQA/isort - rev: 5.5.2 + rev: 5.5.3 hooks: - id: isort - repo: https://github.com/pre-commit/pre-commit-hooks diff --git a/CODEOWNERS b/CODEOWNERS index d6bb7042c41280..a335dc0bb19a46 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -157,6 +157,7 @@ homeassistant/components/geonetnz_volcano/* @exxamalte homeassistant/components/gios/* @bieniu homeassistant/components/gitter/* @fabaff homeassistant/components/glances/* @fabaff @engrbm87 +homeassistant/components/goalzero/* @tkdrob homeassistant/components/gogogate2/* @vangorra homeassistant/components/google_assistant/* @home-assistant/cloud homeassistant/components/google_cloud/* @lufton @@ -169,6 +170,7 @@ homeassistant/components/growatt_server/* @indykoning homeassistant/components/guardian/* @bachya homeassistant/components/harmony/* @ehendrix23 @bramkragten @bdraco homeassistant/components/hassio/* @home-assistant/hass-io +homeassistant/components/hdmi_cec/* @newAM homeassistant/components/heatmiser/* @andylockran homeassistant/components/heos/* @andrewsayre homeassistant/components/here_travel_time/* @eifinger @@ -193,6 +195,7 @@ homeassistant/components/humidifier/* @home-assistant/core @Shulyaka homeassistant/components/hunterdouglas_powerview/* @bdraco homeassistant/components/hvv_departures/* @vigonotion homeassistant/components/hydrawise/* @ptcryan +homeassistant/components/hyperion/* @dermotduffy homeassistant/components/iammeter/* @lewei50 homeassistant/components/iaqualink/* @flz homeassistant/components/icloud/* @Quentame @@ -226,7 +229,7 @@ homeassistant/components/keenetic_ndms2/* @foxel homeassistant/components/kef/* @basnijholt homeassistant/components/keyboard_remote/* @bendavid homeassistant/components/knx/* @Julius2342 @farmio @marvin-w -homeassistant/components/kodi/* @OnFreund +homeassistant/components/kodi/* @OnFreund @cgtobi homeassistant/components/konnected/* @heythisisnate @kit-klein homeassistant/components/lametric/* @robbiet480 homeassistant/components/launch_library/* @ludeeus @@ -238,7 +241,7 @@ homeassistant/components/logger/* @home-assistant/core homeassistant/components/logi_circle/* @evanjd homeassistant/components/loopenergy/* @pavoni homeassistant/components/lovelace/* @home-assistant/frontend -homeassistant/components/luci/* @fbradyirl @mzdrale +homeassistant/components/luci/* @mzdrale homeassistant/components/luftdaten/* @fabaff homeassistant/components/lupusec/* @majuss homeassistant/components/lutron/* @JonGilmore @@ -261,7 +264,7 @@ homeassistant/components/min_max/* @fabaff homeassistant/components/minecraft_server/* @elmurato homeassistant/components/minio/* @tkislan homeassistant/components/mobile_app/* @robbiet480 -homeassistant/components/modbus/* @adamchengtkc @janiversen +homeassistant/components/modbus/* @adamchengtkc @janiversen @vzahradnik homeassistant/components/monoprice/* @etsinko @OnFreund homeassistant/components/moon/* @fabaff homeassistant/components/mpd/* @fabaff @@ -300,6 +303,7 @@ homeassistant/components/nzbget/* @chriscla homeassistant/components/obihai/* @dshokouhi homeassistant/components/ohmconnect/* @robbiet480 homeassistant/components/ombi/* @larssont +homeassistant/components/omnilogic/* @oliver84 @djtimca @gentoosu homeassistant/components/onboarding/* @home-assistant/core homeassistant/components/onewire/* @garbled1 homeassistant/components/onvif/* @hunterjm @@ -330,6 +334,7 @@ homeassistant/components/plum_lightpad/* @ColinHarrington @prystupa homeassistant/components/point/* @fredrike homeassistant/components/poolsense/* @haemishkyd homeassistant/components/powerwall/* @bdraco @jrester +homeassistant/components/profiler/* @bdraco homeassistant/components/progettihwsw/* @ardaseremet homeassistant/components/prometheus/* @knyar homeassistant/components/proxmoxve/* @k4ds3 @jhollowe @@ -349,14 +354,16 @@ homeassistant/components/raincloud/* @vanstinator homeassistant/components/rainforest_eagle/* @gtdiehl @jcalbert homeassistant/components/rainmachine/* @bachya homeassistant/components/random/* @fabaff +homeassistant/components/rejseplanen/* @DarkFox homeassistant/components/repetier/* @MTrab -homeassistant/components/rfxtrx/* @danielhiversen @elupus +homeassistant/components/rfxtrx/* @danielhiversen @elupus @RobBie1221 homeassistant/components/ring/* @balloob homeassistant/components/risco/* @OnFreund homeassistant/components/rmvtransport/* @cgtobi homeassistant/components/roku/* @ctalkington homeassistant/components/roomba/* @pschmitt @cyr-ius @shenxn homeassistant/components/roon/* @pavoni +homeassistant/components/rpi_power/* @shenxn @swetoast homeassistant/components/safe_mode/* @home-assistant/core homeassistant/components/saj/* @fredericvl homeassistant/components/salt/* @bjornorri @@ -399,9 +406,11 @@ homeassistant/components/soma/* @ratsept homeassistant/components/somfy/* @tetienne homeassistant/components/sonarr/* @ctalkington homeassistant/components/songpal/* @rytilahti @shenxn +homeassistant/components/sonos/* @cgtobi homeassistant/components/spaceapi/* @fabaff homeassistant/components/speedtestdotnet/* @rohankapoorcom @engrbm87 homeassistant/components/spider/* @peternijssen +homeassistant/components/splunk/* @Bre77 homeassistant/components/spotify/* @frenck homeassistant/components/sql/* @dgomes homeassistant/components/squeezebox/* @rajlaud @@ -428,6 +437,7 @@ homeassistant/components/tado/* @michaelarnauts @bdraco homeassistant/components/tag/* @balloob @dmulcahey homeassistant/components/tahoma/* @philklei homeassistant/components/tankerkoenig/* @guillempages +homeassistant/components/tasmota/* @emontnemery homeassistant/components/tautulli/* @ludeeus homeassistant/components/tellduslive/* @fredrike homeassistant/components/template/* @PhracturedBlue @tetienne @@ -466,7 +476,7 @@ homeassistant/components/velbus/* @Cereal2nd @brefra homeassistant/components/velux/* @Julius2342 homeassistant/components/vera/* @vangorra homeassistant/components/versasense/* @flamm3blemuff1n -homeassistant/components/version/* @fabaff +homeassistant/components/version/* @fabaff @ludeeus homeassistant/components/vesync/* @markperdue @webdjoe @thegardenmonkey homeassistant/components/vicare/* @oischinger homeassistant/components/vilfo/* @ManneW @@ -502,8 +512,9 @@ homeassistant/components/yi/* @bachya homeassistant/components/zeroconf/* @Kane610 homeassistant/components/zerproc/* @emlove homeassistant/components/zha/* @dmulcahey @adminiuga +homeassistant/components/zodiac/* @JulienTant homeassistant/components/zone/* @home-assistant/core -homeassistant/components/zoneminder/* @rohankapoorcom +homeassistant/components/zoneminder/* @rohankapoorcom @vangorra homeassistant/components/zwave/* @home-assistant/z-wave # Individual files diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1921e5d38dd146..8f8a79ab901676 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,14 +5,14 @@ Everybody is invited and welcome to contribute to Home Assistant. There is a lot The process is straight-forward. - Read [How to get faster PR reviews](https://github.com/kubernetes/community/blob/master/contributors/guide/pull-requests.md#best-practices-for-faster-reviews) by Kubernetes (but skip step 0 and 1) - - Fork the Home Assistant [git repository](https://github.com/home-assistant/home-assistant). + - Fork the Home Assistant [git repository](https://github.com/home-assistant/core). - Write the code for your device, notification service, sensor, or IoT thing. - Ensure tests work. - - Create a Pull Request against the [**dev**](https://github.com/home-assistant/home-assistant/tree/dev) branch of Home Assistant. + - Create a Pull Request against the [**dev**](https://github.com/home-assistant/core/tree/dev) branch of Home Assistant. Still interested? Then you should take a peek at the [developer documentation](https://developers.home-assistant.io/) to get more details. ## Feature suggestions If you want to suggest a new feature for Home Assistant (e.g., new integrations), please open a thread in our [Community Forum: Feature Requests](https://community.home-assistant.io/c/feature-requests). -We use [GitHub for tracking issues](https://github.com/home-assistant/home-assistant/issues), not for tracking feature requests. \ No newline at end of file +We use [GitHub for tracking issues](https://github.com/home-assistant/core/issues), not for tracking feature requests. diff --git a/azure-pipelines-wheels.yml b/azure-pipelines-wheels.yml index 77d90ac94f107e..bcf16e8dee764b 100644 --- a/azure-pipelines-wheels.yml +++ b/azure-pipelines-wheels.yml @@ -47,7 +47,7 @@ jobs: - template: templates/azp-job-wheels.yaml@azure parameters: builderVersion: '$(versionWheels)' - builderApk: 'build-base;cmake;git;linux-headers;bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;autoconf;automake;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev' + builderApk: 'build-base;cmake;git;linux-headers;libexecinfo-dev;bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;autoconf;automake;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev' builderPip: 'Cython;numpy;scikit-build' builderEnvFile: true skipBinary: 'aiohttp' diff --git a/build.json b/build.json index 6d8763a019f196..d63a245793b480 100644 --- a/build.json +++ b/build.json @@ -1,11 +1,11 @@ { "image": "homeassistant/{arch}-homeassistant", "build_from": { - "aarch64": "homeassistant/aarch64-homeassistant-base:8.3.0", - "armhf": "homeassistant/armhf-homeassistant-base:8.3.0", - "armv7": "homeassistant/armv7-homeassistant-base:8.3.0", - "amd64": "homeassistant/amd64-homeassistant-base:8.3.0", - "i386": "homeassistant/i386-homeassistant-base:8.3.0" + "aarch64": "homeassistant/aarch64-homeassistant-base:8.4.0", + "armhf": "homeassistant/armhf-homeassistant-base:8.4.0", + "armv7": "homeassistant/armv7-homeassistant-base:8.4.0", + "amd64": "homeassistant/amd64-homeassistant-base:8.4.0", + "i386": "homeassistant/i386-homeassistant-base:8.4.0" }, "labels": { "io.hass.type": "core" diff --git a/docs/source/_templates/links.html b/docs/source/_templates/links.html index 53a8d1e425d702..7982649f72ed8b 100644 --- a/docs/source/_templates/links.html +++ b/docs/source/_templates/links.html @@ -1,6 +1,6 @@ diff --git a/homeassistant/components/abode/config_flow.py b/homeassistant/components/abode/config_flow.py index 18146551a56934..72e7ec1d9ebafd 100644 --- a/homeassistant/components/abode/config_flow.py +++ b/homeassistant/components/abode/config_flow.py @@ -47,8 +47,8 @@ async def async_step_user(self, user_input=None): except (AbodeException, ConnectTimeout, HTTPError) as ex: LOGGER.error("Unable to connect to Abode: %s", str(ex)) if ex.errcode == HTTP_BAD_REQUEST: - return self._show_form({"base": "invalid_credentials"}) - return self._show_form({"base": "connection_error"}) + return self._show_form({"base": "invalid_auth"}) + return self._show_form({"base": "cannot_connect"}) return self.async_create_entry( title=user_input[CONF_USERNAME], diff --git a/homeassistant/components/abode/strings.json b/homeassistant/components/abode/strings.json index 14d570e76e2cb3..63b62fefceccf2 100644 --- a/homeassistant/components/abode/strings.json +++ b/homeassistant/components/abode/strings.json @@ -10,12 +10,11 @@ } }, "error": { - "identifier_exists": "Account already registered.", - "invalid_credentials": "Invalid credentials.", - "connection_error": "Unable to connect to Abode." + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, "abort": { - "single_instance_allowed": "Only a single configuration of Abode is allowed." + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" } } } \ No newline at end of file diff --git a/homeassistant/components/abode/translations/ca.json b/homeassistant/components/abode/translations/ca.json index 5a1552700d9900..f33b51f6d6bd82 100644 --- a/homeassistant/components/abode/translations/ca.json +++ b/homeassistant/components/abode/translations/ca.json @@ -1,11 +1,13 @@ { "config": { "abort": { - "single_instance_allowed": "Nom\u00e9s es permet una \u00fanica configuraci\u00f3 d'Abode." + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", "connection_error": "No es pot connectar amb Abode.", "identifier_exists": "Compte ja registrat.", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "invalid_credentials": "Credencials inv\u00e0lides." }, "step": { diff --git a/homeassistant/components/abode/translations/el.json b/homeassistant/components/abode/translations/el.json new file mode 100644 index 00000000000000..b30be708065e42 --- /dev/null +++ b/homeassistant/components/abode/translations/el.json @@ -0,0 +1,8 @@ +{ + "config": { + "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\u03b7 \u03b1\u03c5\u03b8\u03b5\u03bd\u03c4\u03b9\u03ba\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/abode/translations/en.json b/homeassistant/components/abode/translations/en.json index ae33c6bed04c7b..34a1ad27484216 100644 --- a/homeassistant/components/abode/translations/en.json +++ b/homeassistant/components/abode/translations/en.json @@ -1,11 +1,13 @@ { "config": { "abort": { - "single_instance_allowed": "Only a single configuration of Abode is allowed." + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "error": { + "cannot_connect": "Failed to connect", "connection_error": "Unable to connect to Abode.", "identifier_exists": "Account already registered.", + "invalid_auth": "Invalid authentication", "invalid_credentials": "Invalid credentials." }, "step": { diff --git a/homeassistant/components/abode/translations/es.json b/homeassistant/components/abode/translations/es.json index 76f06de9b85483..9999856883c9e0 100644 --- a/homeassistant/components/abode/translations/es.json +++ b/homeassistant/components/abode/translations/es.json @@ -4,8 +4,10 @@ "single_instance_allowed": "Solo se permite una \u00fanica configuraci\u00f3n de Abode." }, "error": { + "cannot_connect": "No se pudo conectar", "connection_error": "No se puede conectar a Abode.", "identifier_exists": "Cuenta ya registrada.", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", "invalid_credentials": "Credenciales inv\u00e1lidas." }, "step": { diff --git a/homeassistant/components/abode/translations/et.json b/homeassistant/components/abode/translations/et.json new file mode 100644 index 00000000000000..de5254de79674c --- /dev/null +++ b/homeassistant/components/abode/translations/et.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Tuvastamise viga" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/abode/translations/fr.json b/homeassistant/components/abode/translations/fr.json index 1c4cfe0087256f..81056f44fa81c6 100644 --- a/homeassistant/components/abode/translations/fr.json +++ b/homeassistant/components/abode/translations/fr.json @@ -4,8 +4,10 @@ "single_instance_allowed": "Une seule configuration d'Abode est autoris\u00e9e." }, "error": { + "cannot_connect": "\u00c9chec de connexion", "connection_error": "Impossible de se connecter \u00e0 Abode.", "identifier_exists": "Compte d\u00e9j\u00e0 enregistr\u00e9.", + "invalid_auth": "Authentification invalide", "invalid_credentials": "Informations d'identification invalides." }, "step": { diff --git a/homeassistant/components/abode/translations/it.json b/homeassistant/components/abode/translations/it.json index 97b5d56228389d..e6a43a31d0d352 100644 --- a/homeassistant/components/abode/translations/it.json +++ b/homeassistant/components/abode/translations/it.json @@ -1,11 +1,13 @@ { "config": { "abort": { - "single_instance_allowed": "\u00c8 consentita una sola configurazione di Abode." + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "error": { + "cannot_connect": "Impossibile connettersi", "connection_error": "Impossibile connettersi ad Abode.", "identifier_exists": "Account gi\u00e0 registrato", + "invalid_auth": "Autenticazione non valida", "invalid_credentials": "Credenziali non valide" }, "step": { diff --git a/homeassistant/components/abode/translations/no.json b/homeassistant/components/abode/translations/no.json index 60c1ad897f14b5..af6f3ed27493b2 100644 --- a/homeassistant/components/abode/translations/no.json +++ b/homeassistant/components/abode/translations/no.json @@ -1,11 +1,13 @@ { "config": { "abort": { - "single_instance_allowed": "Bare en enkelt konfigurasjon av Abode er tillatt." + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "error": { + "cannot_connect": "Tilkobling mislyktes.", "connection_error": "Kan ikke koble til Abode.", "identifier_exists": "Kontoen er allerede registrert.", + "invalid_auth": "Ugyldig godkjenning", "invalid_credentials": "Ugyldig legitimasjon" }, "step": { diff --git a/homeassistant/components/abode/translations/pl.json b/homeassistant/components/abode/translations/pl.json index d7a25bb20b7621..7ff6317559cd03 100644 --- a/homeassistant/components/abode/translations/pl.json +++ b/homeassistant/components/abode/translations/pl.json @@ -4,6 +4,7 @@ "single_instance_allowed": "Dozwolona jest tylko jedna konfiguracja Abode." }, "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "connection_error": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z Abode.", "identifier_exists": "Konto jest ju\u017c zarejestrowane.", "invalid_credentials": "Nieprawid\u0142owe dane uwierzytelniaj\u0105ce" diff --git a/homeassistant/components/abode/translations/ru.json b/homeassistant/components/abode/translations/ru.json index e0e6e131289da7..bec110bb70623a 100644 --- a/homeassistant/components/abode/translations/ru.json +++ b/homeassistant/components/abode/translations/ru.json @@ -1,11 +1,13 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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.", "connection_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a Abode.", "identifier_exists": "\u0423\u0447\u0451\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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "invalid_credentials": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0435 \u0443\u0447\u0451\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435." }, "step": { diff --git a/homeassistant/components/abode/translations/zh-Hant.json b/homeassistant/components/abode/translations/zh-Hant.json index 7a97cbda3c59f6..760935accd74aa 100644 --- a/homeassistant/components/abode/translations/zh-Hant.json +++ b/homeassistant/components/abode/translations/zh-Hant.json @@ -1,11 +1,13 @@ { "config": { "abort": { - "single_instance_allowed": "\u50c5\u5141\u8a31\u8a2d\u5b9a\u4e00\u7d44 Abode\u3002" + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002" }, "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", "connection_error": "\u7121\u6cd5\u9023\u7dda\u81f3 Abode\u3002", "identifier_exists": "\u5e33\u865f\u5df2\u8a3b\u518a\u3002", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "invalid_credentials": "\u6191\u8b49\u7121\u6548\u3002" }, "step": { diff --git a/homeassistant/components/accuweather/const.py b/homeassistant/components/accuweather/const.py index e572feafcf81af..5696a35ea2f542 100644 --- a/homeassistant/components/accuweather/const.py +++ b/homeassistant/components/accuweather/const.py @@ -5,6 +5,7 @@ LENGTH_FEET, LENGTH_INCHES, LENGTH_METERS, + LENGTH_MILLIMETERS, PERCENTAGE, SPEED_KILOMETERS_PER_HOUR, SPEED_MILES_PER_HOUR, @@ -24,7 +25,6 @@ CONCENTRATION_PARTS_PER_CUBIC_METER = f"p/{VOLUME_CUBIC_METERS}" COORDINATOR = "coordinator" DOMAIN = "accuweather" -LENGTH_MILIMETERS = "mm" MANUFACTURER = "AccuWeather, Inc." NAME = "AccuWeather" UNDO_UPDATE_LISTENER = "undo_update_listener" @@ -238,7 +238,7 @@ ATTR_DEVICE_CLASS: None, ATTR_ICON: "mdi:weather-rainy", ATTR_LABEL: "Precipitation", - ATTR_UNIT_METRIC: LENGTH_MILIMETERS, + ATTR_UNIT_METRIC: LENGTH_MILLIMETERS, ATTR_UNIT_IMPERIAL: LENGTH_INCHES, }, "PressureTendency": { diff --git a/homeassistant/components/accuweather/manifest.json b/homeassistant/components/accuweather/manifest.json index a383c49f34893d..6ccd6a4f10b1f5 100644 --- a/homeassistant/components/accuweather/manifest.json +++ b/homeassistant/components/accuweather/manifest.json @@ -2,7 +2,7 @@ "domain": "accuweather", "name": "AccuWeather", "documentation": "https://www.home-assistant.io/integrations/accuweather/", - "requirements": ["accuweather==0.0.10"], + "requirements": ["accuweather==0.0.11"], "codeowners": ["@bieniu"], "config_flow": true, "quality_scale": "platinum" diff --git a/homeassistant/components/accuweather/strings.json b/homeassistant/components/accuweather/strings.json index 89228cd069240e..7bf61de8476c08 100644 --- a/homeassistant/components/accuweather/strings.json +++ b/homeassistant/components/accuweather/strings.json @@ -5,10 +5,10 @@ "title": "AccuWeather", "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.", "data": { - "name": "Name of the integration", + "name": "[%key:common::config_flow::data::name%]", "api_key": "[%key:common::config_flow::data::api_key%]", - "latitude": "Latitude", - "longitude": "Longitude" + "latitude": "[%key:common::config_flow::data::latitude%]", + "longitude": "[%key:common::config_flow::data::longitude%]" } } }, diff --git a/homeassistant/components/accuweather/translations/de.json b/homeassistant/components/accuweather/translations/de.json new file mode 100644 index 00000000000000..320c834e920cbb --- /dev/null +++ b/homeassistant/components/accuweather/translations/de.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "user": { + "data": { + "latitude": "Breitengrad", + "longitude": "L\u00e4ngengrad" + }, + "title": "AccuWeather" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/et.json b/homeassistant/components/accuweather/translations/et.json new file mode 100644 index 00000000000000..f669c36ad39273 --- /dev/null +++ b/homeassistant/components/accuweather/translations/et.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Sidumine juba tehtud. V\u00f5imalik on ainult 1 sidumine." + }, + "error": { + "cannot_connect": "\u00dchendus eba\u00f5nnestus", + "invalid_api_key": "API v\u00f5ti on vale", + "requests_exceeded": "Accuweatheri API-le esitatud p\u00e4ringute piirarv on \u00fcletatud. Peate ootama (v\u00f5i muutma API v\u00f5tit)." + }, + "step": { + "user": { + "data": { + "api_key": "API v\u00f5ti", + "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" + } + } + }, + "options": { + "step": { + "user": { + "data": { + "forecast": "Ilmateade" + }, + "description": "AccuWeather API tasuta versioonis toimub ilmaennustuse lubamisel andmete v\u00e4rskendamine iga 32 minuti asemel iga 64 minuti j\u00e4rel.", + "title": "AccuWeatheri valikud" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/fr.json b/homeassistant/components/accuweather/translations/fr.json index 33001cf5b84463..40cf1ccc0b9f9f 100644 --- a/homeassistant/components/accuweather/translations/fr.json +++ b/homeassistant/components/accuweather/translations/fr.json @@ -5,11 +5,30 @@ }, "error": { "cannot_connect": "\u00c9chec de connexion", - "invalid_api_key": "Cl\u00e9 API invalide" + "invalid_api_key": "Cl\u00e9 API invalide", + "requests_exceeded": "Le nombre autoris\u00e9 de requ\u00eates adress\u00e9es \u00e0 l'API AccuWeather a \u00e9t\u00e9 d\u00e9pass\u00e9. Vous devez attendre ou modifier la cl\u00e9 API." }, "step": { "user": { - "description": "Si vous avez besoin d'aide pour la configuration, consultez le site suivant : https://www.home-assistant.io/integrations/accuweather/\n\nCertains capteurs ne sont pas activ\u00e9s par d\u00e9faut. Vous pouvez les activer dans le registre des entit\u00e9s apr\u00e8s la configuration de l'int\u00e9gration.\nLes pr\u00e9visions m\u00e9t\u00e9orologiques ne sont pas activ\u00e9es par d\u00e9faut. Vous pouvez l'activer dans les options d'int\u00e9gration." + "data": { + "api_key": "Cl\u00e9 d'API", + "latitude": "Latitude", + "longitude": "Longitude", + "name": "Nom de l'int\u00e9gration" + }, + "description": "Si vous avez besoin d'aide pour la configuration, consultez le site suivant : https://www.home-assistant.io/integrations/accuweather/\n\nCertains capteurs ne sont pas activ\u00e9s par d\u00e9faut. Vous pouvez les activer dans le registre des entit\u00e9s apr\u00e8s la configuration de l'int\u00e9gration.\nLes pr\u00e9visions m\u00e9t\u00e9orologiques ne sont pas activ\u00e9es par d\u00e9faut. Vous pouvez l'activer dans les options d'int\u00e9gration.", + "title": "AccuWeather" + } + } + }, + "options": { + "step": { + "user": { + "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" } } } diff --git a/homeassistant/components/accuweather/translations/pl.json b/homeassistant/components/accuweather/translations/pl.json index a518c287b1103f..052ed5b6236133 100644 --- a/homeassistant/components/accuweather/translations/pl.json +++ b/homeassistant/components/accuweather/translations/pl.json @@ -4,7 +4,7 @@ "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." }, "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "invalid_api_key": "Nieprawid\u0142owy klucz API.", "requests_exceeded": "Dozwolona liczba zapyta\u0144 do interfejsu API AccuWeather zosta\u0142a przekroczona. Musisz poczeka\u0107 lub zmieni\u0107 klucz API." }, diff --git a/homeassistant/components/accuweather/translations/sensor.et.json b/homeassistant/components/accuweather/translations/sensor.et.json new file mode 100644 index 00000000000000..ca58cd9ab6bcb3 --- /dev/null +++ b/homeassistant/components/accuweather/translations/sensor.et.json @@ -0,0 +1,9 @@ +{ + "state": { + "accuweather__pressure_tendency": { + "falling": "Langev", + "rising": "T\u00f5usev", + "steady": "\u00dchtlane" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/accuweather/translations/uk.json b/homeassistant/components/accuweather/translations/uk.json index a399c8f0694058..8c3f282b35070d 100644 --- a/homeassistant/components/accuweather/translations/uk.json +++ b/homeassistant/components/accuweather/translations/uk.json @@ -6,7 +6,6 @@ "step": { "user": { "data": { - "api_key": "", "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430", "longitude": "\u0414\u043e\u0432\u0433\u043e\u0442\u0430", "name": "\u041d\u0430\u0437\u0432\u0430 \u0456\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0456\u0457" diff --git a/homeassistant/components/acmeda/config_flow.py b/homeassistant/components/acmeda/config_flow.py index 816768b0800c35..f421fa9ca25329 100644 --- a/homeassistant/components/acmeda/config_flow.py +++ b/homeassistant/components/acmeda/config_flow.py @@ -45,7 +45,7 @@ async def async_step_user(self, user_input=None): pass if len(hubs) == 0: - return self.async_abort(reason="all_configured") + return self.async_abort(reason="no_devices_found") if len(hubs) == 1: return await self.async_create(hubs[0]) diff --git a/homeassistant/components/acmeda/strings.json b/homeassistant/components/acmeda/strings.json index eb7ed44999b2db..a2209fe1a88ab1 100644 --- a/homeassistant/components/acmeda/strings.json +++ b/homeassistant/components/acmeda/strings.json @@ -10,7 +10,7 @@ } }, "abort": { - "all_configured": "No new Pulse hubs discovered." + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/acmeda/translations/ca.json b/homeassistant/components/acmeda/translations/ca.json index 0812387ab7f33c..2481794f8f2782 100644 --- a/homeassistant/components/acmeda/translations/ca.json +++ b/homeassistant/components/acmeda/translations/ca.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "all_configured": "No s'han descobert nous hubs de Pulse." + "all_configured": "No s'han descobert nous hubs de Pulse.", + "no_devices_found": "No s'han trobat dispositius a la xarxa" }, "step": { "user": { diff --git a/homeassistant/components/acmeda/translations/en.json b/homeassistant/components/acmeda/translations/en.json index ab20d0b1939df6..77768f20d69b43 100644 --- a/homeassistant/components/acmeda/translations/en.json +++ b/homeassistant/components/acmeda/translations/en.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "all_configured": "No new Pulse hubs discovered." + "all_configured": "No new Pulse hubs discovered.", + "no_devices_found": "No devices found on the network" }, "step": { "user": { diff --git a/homeassistant/components/acmeda/translations/es.json b/homeassistant/components/acmeda/translations/es.json index 0ca3dbf6e2ff39..dcdd885fe26e3c 100644 --- a/homeassistant/components/acmeda/translations/es.json +++ b/homeassistant/components/acmeda/translations/es.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "all_configured": "No se han descubierto nuevos hubs Pulse." + "all_configured": "No se han descubierto nuevos hubs Pulse.", + "no_devices_found": "No se encontraron dispositivos en la red" }, "step": { "user": { diff --git a/homeassistant/components/acmeda/translations/et.json b/homeassistant/components/acmeda/translations/et.json new file mode 100644 index 00000000000000..b6075037dc465c --- /dev/null +++ b/homeassistant/components/acmeda/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "no_devices_found": "V\u00f5rgus ei tuvastatud \u00fchtegi seadet" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/acmeda/translations/fr.json b/homeassistant/components/acmeda/translations/fr.json index a60d52ac6f44c0..8270b13d76e7e5 100644 --- a/homeassistant/components/acmeda/translations/fr.json +++ b/homeassistant/components/acmeda/translations/fr.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "all_configured": "Aucun nouveau hub Pulse n'a \u00e9t\u00e9 d\u00e9couvert." + "all_configured": "Aucun nouveau hub Pulse n'a \u00e9t\u00e9 d\u00e9couvert.", + "no_devices_found": "Aucun appareil trouv\u00e9 sur le r\u00e9seau" }, "step": { "user": { diff --git a/homeassistant/components/acmeda/translations/it.json b/homeassistant/components/acmeda/translations/it.json index a9349d0692384b..dbf92d54db92b2 100644 --- a/homeassistant/components/acmeda/translations/it.json +++ b/homeassistant/components/acmeda/translations/it.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "all_configured": "Non sono stati scoperti nuovi hub Pulse." + "all_configured": "Non sono stati scoperti nuovi hub Pulse.", + "no_devices_found": "Nessun dispositivo trovato sulla rete" }, "step": { "user": { diff --git a/homeassistant/components/acmeda/translations/no.json b/homeassistant/components/acmeda/translations/no.json index 66335077cfbaff..8ecb4ef889dd51 100644 --- a/homeassistant/components/acmeda/translations/no.json +++ b/homeassistant/components/acmeda/translations/no.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "all_configured": "Ingen nye Pulse-hub oppdaget." + "all_configured": "Ingen nye Pulse-hub oppdaget.", + "no_devices_found": "Ingen enheter funnet p\u00e5 nettverket" }, "step": { "user": { diff --git a/homeassistant/components/acmeda/translations/ru.json b/homeassistant/components/acmeda/translations/ru.json index 92922fdbb5d99b..b11fd876975153 100644 --- a/homeassistant/components/acmeda/translations/ru.json +++ b/homeassistant/components/acmeda/translations/ru.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "all_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u044b." + "all_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u044b.", + "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." }, "step": { "user": { diff --git a/homeassistant/components/acmeda/translations/zh-Hant.json b/homeassistant/components/acmeda/translations/zh-Hant.json index 5d4263ea5ae098..e5f29fbb4bdf04 100644 --- a/homeassistant/components/acmeda/translations/zh-Hant.json +++ b/homeassistant/components/acmeda/translations/zh-Hant.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "all_configured": "\u672a\u641c\u5c0b\u5230 Pulse hub" + "all_configured": "\u672a\u641c\u5c0b\u5230 Pulse hub", + "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u8a2d\u5099" }, "step": { "user": { diff --git a/homeassistant/components/adguard/config_flow.py b/homeassistant/components/adguard/config_flow.py index a0ace62386291d..c799c4a71355c8 100644 --- a/homeassistant/components/adguard/config_flow.py +++ b/homeassistant/components/adguard/config_flow.py @@ -80,7 +80,7 @@ async def async_step_user(self, user_input=None): try: await adguard.version() except AdGuardHomeConnectionError: - errors["base"] = "connection_error" + errors["base"] = "cannot_connect" return await self._show_setup_form(errors) return self.async_create_entry( @@ -152,7 +152,7 @@ async def async_step_hassio_confirm(self, user_input=None): try: await adguard.version() except AdGuardHomeConnectionError: - errors["base"] = "connection_error" + errors["base"] = "cannot_connect" return await self._show_hassio_form(errors) return self.async_create_entry( diff --git a/homeassistant/components/adguard/strings.json b/homeassistant/components/adguard/strings.json index f010f9e2ade25a..2d4bb49304fbe5 100644 --- a/homeassistant/components/adguard/strings.json +++ b/homeassistant/components/adguard/strings.json @@ -8,8 +8,8 @@ "password": "[%key:common::config_flow::data::password%]", "port": "[%key:common::config_flow::data::port%]", "username": "[%key:common::config_flow::data::username%]", - "ssl": "AdGuard Home uses a SSL certificate", - "verify_ssl": "AdGuard Home uses a proper certificate" + "ssl": "[%key:common::config_flow::data::ssl%]", + "verify_ssl": "[%key:common::config_flow::data::verify_ssl%]" } }, "hassio_confirm": { @@ -17,10 +17,12 @@ "description": "Do you want to configure Home Assistant to connect to the AdGuard Home provided by the Hass.io add-on: {addon}?" } }, - "error": { "connection_error": "Failed to connect." }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" + }, "abort": { "existing_instance_updated": "Updated existing configuration.", - "single_instance_allowed": "Only a single configuration of AdGuard Home is allowed." + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" } } } diff --git a/homeassistant/components/adguard/translations/ca.json b/homeassistant/components/adguard/translations/ca.json index b263e433cfb508..aec7f6fc2dab18 100644 --- a/homeassistant/components/adguard/translations/ca.json +++ b/homeassistant/components/adguard/translations/ca.json @@ -2,9 +2,10 @@ "config": { "abort": { "existing_instance_updated": "S'ha actualitzat la configuraci\u00f3 existent.", - "single_instance_allowed": "Nom\u00e9s es permet una \u00fanica configuraci\u00f3 d'AdGuard Home." + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", "connection_error": "No s'ha pogut connectar." }, "step": { @@ -17,9 +18,9 @@ "host": "[%key::common::config_flow::data::host%]", "password": "[%key::common::config_flow::data::password%]", "port": "[%key::common::config_flow::data::port%]", - "ssl": "AdGuard Home utilitza un certificat SSL", + "ssl": "Utilitza un certificat SSL", "username": "[%key::common::config_flow::data::username%]", - "verify_ssl": "AdGuard Home utilitza un certificat adequat" + "verify_ssl": "Verifica el certificat SSL" }, "description": "Configuraci\u00f3 de la inst\u00e0ncia d'AdGuard Home, permet el control i la monitoritzaci\u00f3." } diff --git a/homeassistant/components/adguard/translations/el.json b/homeassistant/components/adguard/translations/el.json new file mode 100644 index 00000000000000..04b238a916d221 --- /dev/null +++ b/homeassistant/components/adguard/translations/el.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/adguard/translations/en.json b/homeassistant/components/adguard/translations/en.json index b52d0e5afd8582..e0c1f6d7a0b7f1 100644 --- a/homeassistant/components/adguard/translations/en.json +++ b/homeassistant/components/adguard/translations/en.json @@ -2,9 +2,10 @@ "config": { "abort": { "existing_instance_updated": "Updated existing configuration.", - "single_instance_allowed": "Only a single configuration of AdGuard Home is allowed." + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "error": { + "cannot_connect": "Failed to connect", "connection_error": "Failed to connect." }, "step": { @@ -17,9 +18,9 @@ "host": "Host", "password": "Password", "port": "Port", - "ssl": "AdGuard Home uses a SSL certificate", + "ssl": "Uses an SSL certificate", "username": "Username", - "verify_ssl": "AdGuard Home uses a proper certificate" + "verify_ssl": "Verify SSL certificate" }, "description": "Set up your AdGuard Home instance to allow monitoring and control." } diff --git a/homeassistant/components/adguard/translations/es.json b/homeassistant/components/adguard/translations/es.json index 82ba749dcc8bb5..b6c275a401ccbb 100644 --- a/homeassistant/components/adguard/translations/es.json +++ b/homeassistant/components/adguard/translations/es.json @@ -5,6 +5,7 @@ "single_instance_allowed": "S\u00f3lo se permite una \u00fanica configuraci\u00f3n de AdGuard Home." }, "error": { + "cannot_connect": "No se pudo conectar", "connection_error": "No se conect\u00f3." }, "step": { diff --git a/homeassistant/components/adguard/translations/et.json b/homeassistant/components/adguard/translations/et.json new file mode 100644 index 00000000000000..4acca09cd8e7e8 --- /dev/null +++ b/homeassistant/components/adguard/translations/et.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00dchendus eba\u00f5nnestus", + "connection_error": "\u00dchenduse loomine nurjus" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/adguard/translations/fr.json b/homeassistant/components/adguard/translations/fr.json index bf275f922f1ec9..43cee03560c971 100644 --- a/homeassistant/components/adguard/translations/fr.json +++ b/homeassistant/components/adguard/translations/fr.json @@ -5,6 +5,7 @@ "single_instance_allowed": "Une seule configuration d'AdGuard Home est autoris\u00e9e." }, "error": { + "cannot_connect": "\u00c9chec de connexion", "connection_error": "\u00c9chec de connexion." }, "step": { diff --git a/homeassistant/components/adguard/translations/it.json b/homeassistant/components/adguard/translations/it.json index ac2a903c5e5aca..ff37f64aefcc0a 100644 --- a/homeassistant/components/adguard/translations/it.json +++ b/homeassistant/components/adguard/translations/it.json @@ -2,9 +2,10 @@ "config": { "abort": { "existing_instance_updated": "Configurazione esistente aggiornata.", - "single_instance_allowed": "\u00c8 consentita solo una singola configurazione di AdGuard Home." + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "error": { + "cannot_connect": "Impossibile connettersi", "connection_error": "Impossibile connettersi." }, "step": { @@ -17,9 +18,9 @@ "host": "Host", "password": "Password", "port": "Porta", - "ssl": "AdGuard Home utilizza un certificato SSL", + "ssl": "Utilizza un certificato SSL", "username": "Nome utente", - "verify_ssl": "AdGuard Home utilizza un certificato appropriato" + "verify_ssl": "Verificare il certificato SSL" }, "description": "Configura l'istanza di AdGuard Home per consentire il monitoraggio e il controllo." } diff --git a/homeassistant/components/adguard/translations/lb.json b/homeassistant/components/adguard/translations/lb.json index abf6df83a6a63e..860abfeb27f077 100644 --- a/homeassistant/components/adguard/translations/lb.json +++ b/homeassistant/components/adguard/translations/lb.json @@ -5,6 +5,7 @@ "single_instance_allowed": "N\u00ebmmen eng eenzeg Konfiguratioun vun AdGuard Home ass erlaabt." }, "error": { + "cannot_connect": "Feeler beim verbannen", "connection_error": "Feeler beim verbannen." }, "step": { diff --git a/homeassistant/components/adguard/translations/no.json b/homeassistant/components/adguard/translations/no.json index a772988c042869..747af5d5610461 100644 --- a/homeassistant/components/adguard/translations/no.json +++ b/homeassistant/components/adguard/translations/no.json @@ -2,9 +2,10 @@ "config": { "abort": { "existing_instance_updated": "Oppdatert eksisterende konfigurasjon.", - "single_instance_allowed": "Kun en konfigurasjon av AdGuard Hjemer tillatt." + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "error": { + "cannot_connect": "Tilkobling mislyktes.", "connection_error": "Tilkobling mislyktes." }, "step": { @@ -17,9 +18,9 @@ "host": "Vert", "password": "Passord", "port": "", - "ssl": "AdGuard Hjem bruker et SSL-sertifikat", + "ssl": "Bruker et SSL-sertifikat", "username": "Brukernavn", - "verify_ssl": "AdGuard Home bruker et riktig sertifikat" + "verify_ssl": "Verifisere SSL-sertifikat" }, "description": "Sett opp din AdGuard Hjem instans for \u00e5 tillate overv\u00e5king og kontroll." } diff --git a/homeassistant/components/adguard/translations/pl.json b/homeassistant/components/adguard/translations/pl.json index ed034fcd1db658..daa700925f6ec3 100644 --- a/homeassistant/components/adguard/translations/pl.json +++ b/homeassistant/components/adguard/translations/pl.json @@ -5,7 +5,8 @@ "single_instance_allowed": "Dozwolona jest tylko jedna konfiguracja AdGuard Home." }, "error": { - "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia." + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, "step": { "hassio_confirm": { diff --git a/homeassistant/components/adguard/translations/ru.json b/homeassistant/components/adguard/translations/ru.json index ed65b1423bdcac..175d0bc1d5a410 100644 --- a/homeassistant/components/adguard/translations/ru.json +++ b/homeassistant/components/adguard/translations/ru.json @@ -2,9 +2,10 @@ "config": { "abort": { "existing_instance_updated": "\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0430.", - "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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.", "connection_error": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f." }, "step": { @@ -17,9 +18,9 @@ "host": "\u0425\u043e\u0441\u0442", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", - "ssl": "AdGuard Home \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL", + "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": "\u041b\u043e\u0433\u0438\u043d", - "verify_ssl": "AdGuard Home \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442" + "verify_ssl": "\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL" }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430 \u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f AdGuard Home." } diff --git a/homeassistant/components/adguard/translations/zh-Hant.json b/homeassistant/components/adguard/translations/zh-Hant.json index 5fbfdbbc59c56b..b883b0a01ba6bb 100644 --- a/homeassistant/components/adguard/translations/zh-Hant.json +++ b/homeassistant/components/adguard/translations/zh-Hant.json @@ -2,9 +2,10 @@ "config": { "abort": { "existing_instance_updated": "\u5df2\u66f4\u65b0\u73fe\u6709\u8a2d\u5b9a\u3002", - "single_instance_allowed": "\u50c5\u5141\u8a31\u8a2d\u5b9a\u4e00\u7d44 AdGuard Home\u3002" + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002" }, "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", "connection_error": "\u9023\u7dda\u5931\u6557\u3002" }, "step": { @@ -17,9 +18,9 @@ "host": "\u4e3b\u6a5f\u7aef", "password": "\u5bc6\u78bc", "port": "\u901a\u8a0a\u57e0", - "ssl": "AdGuard Home \u4f7f\u7528 SSL \u8a8d\u8b49", + "ssl": "\u4f7f\u7528 SSL \u8a8d\u8b49", "username": "\u4f7f\u7528\u8005\u540d\u7a31", - "verify_ssl": "AdGuard Home \u4f7f\u7528\u5c0d\u61c9\u8a8d\u8b49" + "verify_ssl": "\u78ba\u8a8d SSL \u8a8d\u8b49" }, "description": "\u8a2d\u5b9a AdGuard Home \u4ee5\u9032\u884c\u76e3\u63a7\u3002" } diff --git a/homeassistant/components/ads/binary_sensor.py b/homeassistant/components/ads/binary_sensor.py index df8a74dc1d5584..d481420b4d4edd 100644 --- a/homeassistant/components/ads/binary_sensor.py +++ b/homeassistant/components/ads/binary_sensor.py @@ -4,6 +4,7 @@ import voluptuous as vol from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_MOVING, DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, BinarySensorEntity, @@ -43,7 +44,7 @@ class AdsBinarySensor(AdsEntity, BinarySensorEntity): def __init__(self, ads_hub, name, ads_var, device_class): """Initialize ADS binary sensor.""" super().__init__(ads_hub, name, ads_var) - self._device_class = device_class or "moving" + self._device_class = device_class or DEVICE_CLASS_MOVING async def async_added_to_hass(self): """Register device notification.""" diff --git a/homeassistant/components/agent_dvr/strings.json b/homeassistant/components/agent_dvr/strings.json index 95f992310836f0..476d9956222e9c 100644 --- a/homeassistant/components/agent_dvr/strings.json +++ b/homeassistant/components/agent_dvr/strings.json @@ -11,11 +11,11 @@ } }, "abort": { - "already_configured": "Device is already configured" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" }, "error": { - "already_in_progress": "Config flow for device is already in progress.", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", "device_unavailable": "Device is not available" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/agent_dvr/translations/ca.json b/homeassistant/components/agent_dvr/translations/ca.json index 401f2166bd1f2d..737692adf75c94 100644 --- a/homeassistant/components/agent_dvr/translations/ca.json +++ b/homeassistant/components/agent_dvr/translations/ca.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Dispositiu ja est\u00e0 configurat" + "already_configured": "El dispositiu ja est\u00e0 configurat" }, "error": { - "already_in_progress": "El flux de dades de configuraci\u00f3 pel dispositiu ja est\u00e0 en curs.", + "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", "device_unavailable": "Dispositiu no est\u00e0 disponible" }, "step": { diff --git a/homeassistant/components/agent_dvr/translations/en.json b/homeassistant/components/agent_dvr/translations/en.json index 0f110a06863401..595dd44522aada 100644 --- a/homeassistant/components/agent_dvr/translations/en.json +++ b/homeassistant/components/agent_dvr/translations/en.json @@ -4,7 +4,7 @@ "already_configured": "Device is already configured" }, "error": { - "already_in_progress": "Config flow for device is already in progress.", + "already_in_progress": "Configuration flow is already in progress", "device_unavailable": "Device is not available" }, "step": { diff --git a/homeassistant/components/agent_dvr/translations/it.json b/homeassistant/components/agent_dvr/translations/it.json index 1db719893aa889..1c1e92af95d109 100644 --- a/homeassistant/components/agent_dvr/translations/it.json +++ b/homeassistant/components/agent_dvr/translations/it.json @@ -4,7 +4,7 @@ "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" }, "error": { - "already_in_progress": "Il flusso di configurazione per il dispositivo \u00e8 gi\u00e0 in corso.", + "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "device_unavailable": "Il dispositivo non \u00e8 disponibile" }, "step": { diff --git a/homeassistant/components/agent_dvr/translations/no.json b/homeassistant/components/agent_dvr/translations/no.json index 3fcbb8f161798d..8df278350ca10f 100644 --- a/homeassistant/components/agent_dvr/translations/no.json +++ b/homeassistant/components/agent_dvr/translations/no.json @@ -4,7 +4,7 @@ "already_configured": "Enheten er allerede konfigurert" }, "error": { - "already_in_progress": "Konfigurasjonsflyt for enhet p\u00e5g\u00e5r allerede.", + "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", "device_unavailable": "Enheten er ikke tilgjengelig" }, "step": { diff --git a/homeassistant/components/agent_dvr/translations/pl.json b/homeassistant/components/agent_dvr/translations/pl.json index 5045015087f03a..9c101555f78e89 100644 --- a/homeassistant/components/agent_dvr/translations/pl.json +++ b/homeassistant/components/agent_dvr/translations/pl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" }, "error": { "already_in_progress": "Konfiguracja urz\u0105dzenia jest ju\u017c w toku.", diff --git a/homeassistant/components/agent_dvr/translations/ru.json b/homeassistant/components/agent_dvr/translations/ru.json index 69b192fc710ae9..6bda3e2e5912c1 100644 --- a/homeassistant/components/agent_dvr/translations/ru.json +++ b/homeassistant/components/agent_dvr/translations/ru.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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": { - "already_in_progress": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.", + "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.", "device_unavailable": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e." }, "step": { diff --git a/homeassistant/components/agent_dvr/translations/zh-Hant.json b/homeassistant/components/agent_dvr/translations/zh-Hant.json index dba3e0937b7bbd..6c257a415bed68 100644 --- a/homeassistant/components/agent_dvr/translations/zh-Hant.json +++ b/homeassistant/components/agent_dvr/translations/zh-Hant.json @@ -4,7 +4,7 @@ "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { - "already_in_progress": "\u8a2d\u5099\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d\u3002", + "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "device_unavailable": "\u8a2d\u5099\u7121\u6cd5\u4f7f\u7528" }, "step": { diff --git a/homeassistant/components/air_quality/group.py b/homeassistant/components/air_quality/group.py new file mode 100644 index 00000000000000..4741f8a3b548cd --- /dev/null +++ b/homeassistant/components/air_quality/group.py @@ -0,0 +1,14 @@ +"""Describe group states.""" + + +from homeassistant.components.group import GroupIntegrationRegistry +from homeassistant.core import callback +from homeassistant.helpers.typing import HomeAssistantType + + +@callback +def async_describe_on_off_states( + hass: HomeAssistantType, registry: GroupIntegrationRegistry +) -> None: + """Describe group on off states.""" + registry.exclude_domain() diff --git a/homeassistant/components/airly/config_flow.py b/homeassistant/components/airly/config_flow.py index 84bad2d3719f3f..8b3b1949ec39f8 100644 --- a/homeassistant/components/airly/config_flow.py +++ b/homeassistant/components/airly/config_flow.py @@ -39,7 +39,7 @@ async def async_step_user(self, user_input=None): self._abort_if_unique_id_configured() api_key_valid = await self._test_api_key(websession, user_input["api_key"]) if not api_key_valid: - self._errors["base"] = "auth" + self._errors["base"] = "invalid_api_key" else: location_valid = await self._test_location( websession, diff --git a/homeassistant/components/airly/manifest.json b/homeassistant/components/airly/manifest.json index 8140bc91c5fc60..77de843ffce631 100644 --- a/homeassistant/components/airly/manifest.json +++ b/homeassistant/components/airly/manifest.json @@ -3,7 +3,7 @@ "name": "Airly", "documentation": "https://www.home-assistant.io/integrations/airly", "codeowners": ["@bieniu"], - "requirements": ["airly==0.0.2"], + "requirements": ["airly==1.0.0"], "config_flow": true, "quality_scale": "platinum" } diff --git a/homeassistant/components/airly/strings.json b/homeassistant/components/airly/strings.json index 8bf7782e910505..9d3352322868cb 100644 --- a/homeassistant/components/airly/strings.json +++ b/homeassistant/components/airly/strings.json @@ -7,17 +7,17 @@ "data": { "name": "Name of the integration", "api_key": "[%key:common::config_flow::data::api_key%]", - "latitude": "Latitude", - "longitude": "Longitude" + "latitude": "[%key:common::config_flow::data::latitude%]", + "longitude": "[%key:common::config_flow::data::longitude%]" } } }, "error": { "wrong_location": "No Airly measuring stations in this area.", - "auth": "API key is not correct." + "invalid_api_key": "[%key:common::config_flow::error::invalid_api_key%]" }, "abort": { "already_configured": "Airly integration for these coordinates is already configured." } } -} \ No newline at end of file +} diff --git a/homeassistant/components/airly/translations/ca.json b/homeassistant/components/airly/translations/ca.json index 9d0854811201de..087b38afb69d22 100644 --- a/homeassistant/components/airly/translations/ca.json +++ b/homeassistant/components/airly/translations/ca.json @@ -5,6 +5,7 @@ }, "error": { "auth": "La clau API no \u00e9s correcta.", + "invalid_api_key": "Clau API inv\u00e0lida", "wrong_location": "No hi ha estacions de mesura Airly en aquesta zona." }, "step": { diff --git a/homeassistant/components/airly/translations/el.json b/homeassistant/components/airly/translations/el.json new file mode 100644 index 00000000000000..e39b0aef88d863 --- /dev/null +++ b/homeassistant/components/airly/translations/el.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_api_key": "\u0386\u03ba\u03c5\u03c1\u03bf API \u03ba\u03bb\u03b5\u03b9\u03b4\u03af" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airly/translations/en.json b/homeassistant/components/airly/translations/en.json index 2dd164823ccfc7..d1faa18aff2c8b 100644 --- a/homeassistant/components/airly/translations/en.json +++ b/homeassistant/components/airly/translations/en.json @@ -5,6 +5,7 @@ }, "error": { "auth": "API key is not correct.", + "invalid_api_key": "Invalid API key", "wrong_location": "No Airly measuring stations in this area." }, "step": { diff --git a/homeassistant/components/airly/translations/es.json b/homeassistant/components/airly/translations/es.json index dececf29a6989f..1baf28d12a0867 100644 --- a/homeassistant/components/airly/translations/es.json +++ b/homeassistant/components/airly/translations/es.json @@ -5,6 +5,7 @@ }, "error": { "auth": "La clave de la API no es correcta.", + "invalid_api_key": "Clave API no v\u00e1lida", "wrong_location": "No hay estaciones de medici\u00f3n Airly en esta zona." }, "step": { diff --git a/homeassistant/components/airly/translations/et.json b/homeassistant/components/airly/translations/et.json new file mode 100644 index 00000000000000..d1a9b20ddb17b9 --- /dev/null +++ b/homeassistant/components/airly/translations/et.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "invalid_api_key": "Vigane API v\u00f5ti" + }, + "step": { + "user": { + "data": { + "latitude": "Laiuskraad", + "longitude": "Pikkuskraad" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airly/translations/fr.json b/homeassistant/components/airly/translations/fr.json index ac821ab226cfb8..4259112dbc9d79 100644 --- a/homeassistant/components/airly/translations/fr.json +++ b/homeassistant/components/airly/translations/fr.json @@ -5,6 +5,7 @@ }, "error": { "auth": "La cl\u00e9 API n'est pas correcte.", + "invalid_api_key": "Cl\u00e9 API invalide", "wrong_location": "Aucune station de mesure Airly dans cette zone." }, "step": { diff --git a/homeassistant/components/airly/translations/it.json b/homeassistant/components/airly/translations/it.json index e394b7af8d35d6..18a64633d4936a 100644 --- a/homeassistant/components/airly/translations/it.json +++ b/homeassistant/components/airly/translations/it.json @@ -5,6 +5,7 @@ }, "error": { "auth": "La chiave API non \u00e8 corretta.", + "invalid_api_key": "Chiave API non valida", "wrong_location": "Nessuna stazione di misurazione Airly in quest'area." }, "step": { diff --git a/homeassistant/components/airly/translations/no.json b/homeassistant/components/airly/translations/no.json index 09e77a311ebb45..47d865e24a61a6 100644 --- a/homeassistant/components/airly/translations/no.json +++ b/homeassistant/components/airly/translations/no.json @@ -5,6 +5,7 @@ }, "error": { "auth": "API-n\u00f8kkelen er ikke korrekt.", + "invalid_api_key": "Ugyldig API-n\u00f8kkel", "wrong_location": "Ingen Airly m\u00e5lestasjoner i dette omr\u00e5det." }, "step": { diff --git a/homeassistant/components/airly/translations/ru.json b/homeassistant/components/airly/translations/ru.json index 9b3a62331db45b..bda79183980365 100644 --- a/homeassistant/components/airly/translations/ru.json +++ b/homeassistant/components/airly/translations/ru.json @@ -5,6 +5,7 @@ }, "error": { "auth": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API.", + "invalid_api_key": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API.", "wrong_location": "\u0412 \u044d\u0442\u043e\u0439 \u043e\u0431\u043b\u0430\u0441\u0442\u0438 \u043d\u0435\u0442 \u0438\u0437\u043c\u0435\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0445 \u0441\u0442\u0430\u043d\u0446\u0438\u0439 Airly." }, "step": { diff --git a/homeassistant/components/airly/translations/zh-Hant.json b/homeassistant/components/airly/translations/zh-Hant.json index fc6c58cf97879b..d0b813b84dc661 100644 --- a/homeassistant/components/airly/translations/zh-Hant.json +++ b/homeassistant/components/airly/translations/zh-Hant.json @@ -5,6 +5,7 @@ }, "error": { "auth": "API \u5bc6\u9470\u4e0d\u6b63\u78ba\u3002", + "invalid_api_key": "API \u5bc6\u9470\u7121\u6548", "wrong_location": "\u8a72\u5340\u57df\u6c92\u6709 Arily \u76e3\u6e2c\u7ad9\u3002" }, "step": { diff --git a/homeassistant/components/airvisual/__init__.py b/homeassistant/components/airvisual/__init__.py index f06e4fe70b73de..d6d7a93a3662e1 100644 --- a/homeassistant/components/airvisual/__init__.py +++ b/homeassistant/components/airvisual/__init__.py @@ -3,7 +3,7 @@ from datetime import timedelta from math import ceil -from pyairvisual import Client +from pyairvisual import CloudAPI, NodeSamba from pyairvisual.errors import ( AirVisualError, InvalidKeyError, @@ -211,23 +211,22 @@ def _standardize_node_pro_config_entry(hass, config_entry): async def async_setup_entry(hass, config_entry): """Set up AirVisual as config entry.""" - websession = aiohttp_client.async_get_clientsession(hass) - if CONF_API_KEY in config_entry.data: _standardize_geography_config_entry(hass, config_entry) - client = Client(api_key=config_entry.data[CONF_API_KEY], session=websession) + websession = aiohttp_client.async_get_clientsession(hass) + cloud_api = CloudAPI(config_entry.data[CONF_API_KEY], session=websession) async def async_update_data(): """Get new data from the API.""" if CONF_CITY in config_entry.data: - api_coro = client.api.city( + api_coro = cloud_api.air_quality.city( config_entry.data[CONF_CITY], config_entry.data[CONF_STATE], config_entry.data[CONF_COUNTRY], ) else: - api_coro = client.api.nearest_city( + api_coro = cloud_api.air_quality.nearest_city( config_entry.data[CONF_LATITUDE], config_entry.data[CONF_LONGITUDE], ) @@ -267,17 +266,13 @@ async def async_update_data(): else: _standardize_node_pro_config_entry(hass, config_entry) - client = Client(session=websession) - async def async_update_data(): """Get new data from the API.""" try: - return await client.node.from_samba( - config_entry.data[CONF_IP_ADDRESS], - config_entry.data[CONF_PASSWORD], - include_history=False, - include_trends=False, - ) + async with NodeSamba( + config_entry.data[CONF_IP_ADDRESS], config_entry.data[CONF_PASSWORD] + ) as node: + return await node.async_get_latest_measurements() except NodeProError as err: raise UpdateFailed(f"Error while retrieving data: {err}") from err diff --git a/homeassistant/components/airvisual/air_quality.py b/homeassistant/components/airvisual/air_quality.py index bb2d64a23db97d..047367fa67c68c 100644 --- a/homeassistant/components/airvisual/air_quality.py +++ b/homeassistant/components/airvisual/air_quality.py @@ -40,9 +40,9 @@ def __init__(self, airvisual): @property def air_quality_index(self): """Return the Air Quality Index (AQI).""" - if self.coordinator.data["current"]["settings"]["is_aqi_usa"]: - return self.coordinator.data["current"]["measurements"]["aqi_us"] - return self.coordinator.data["current"]["measurements"]["aqi_cn"] + if self.coordinator.data["settings"]["is_aqi_usa"]: + return self.coordinator.data["measurements"]["aqi_us"] + return self.coordinator.data["measurements"]["aqi_cn"] @property def available(self): @@ -52,61 +52,59 @@ def available(self): @property def carbon_dioxide(self): """Return the CO2 (carbon dioxide) level.""" - return self.coordinator.data["current"]["measurements"].get("co2") + return self.coordinator.data["measurements"].get("co2") @property def device_info(self): """Return device registry information for this entity.""" return { - "identifiers": { - (DOMAIN, self.coordinator.data["current"]["serial_number"]) - }, - "name": self.coordinator.data["current"]["settings"]["node_name"], + "identifiers": {(DOMAIN, self.coordinator.data["serial_number"])}, + "name": self.coordinator.data["settings"]["node_name"], "manufacturer": "AirVisual", - "model": f'{self.coordinator.data["current"]["status"]["model"]}', + "model": f'{self.coordinator.data["status"]["model"]}', "sw_version": ( - f'Version {self.coordinator.data["current"]["status"]["system_version"]}' - f'{self.coordinator.data["current"]["status"]["app_version"]}' + f'Version {self.coordinator.data["status"]["system_version"]}' + f'{self.coordinator.data["status"]["app_version"]}' ), } @property def name(self): """Return the name.""" - node_name = self.coordinator.data["current"]["settings"]["node_name"] + node_name = self.coordinator.data["settings"]["node_name"] return f"{node_name} Node/Pro: Air Quality" @property def particulate_matter_2_5(self): """Return the particulate matter 2.5 level.""" - return self.coordinator.data["current"]["measurements"].get("pm2_5") + return self.coordinator.data["measurements"].get("pm2_5") @property def particulate_matter_10(self): """Return the particulate matter 10 level.""" - return self.coordinator.data["current"]["measurements"].get("pm1_0") + return self.coordinator.data["measurements"].get("pm1_0") @property def particulate_matter_0_1(self): """Return the particulate matter 0.1 level.""" - return self.coordinator.data["current"]["measurements"].get("pm0_1") + return self.coordinator.data["measurements"].get("pm0_1") @property def unique_id(self): """Return a unique, Home Assistant friendly identifier for this entity.""" - return self.coordinator.data["current"]["serial_number"] + return self.coordinator.data["serial_number"] @callback def update_from_latest_data(self): """Update the entity from the latest data.""" self._attrs.update( { - ATTR_VOC: self.coordinator.data["current"]["measurements"].get("voc"), + ATTR_VOC: self.coordinator.data["measurements"].get("voc"), **{ ATTR_SENSOR_LIFE.format(pollutant): lifespan - for pollutant, lifespan in self.coordinator.data["current"][ - "status" - ]["sensor_life"].items() + for pollutant, lifespan in self.coordinator.data["status"][ + "sensor_life" + ].items() }, } ) diff --git a/homeassistant/components/airvisual/config_flow.py b/homeassistant/components/airvisual/config_flow.py index bb1c262eba76cc..7f8022d31f6aef 100644 --- a/homeassistant/components/airvisual/config_flow.py +++ b/homeassistant/components/airvisual/config_flow.py @@ -1,7 +1,7 @@ """Define a config flow manager for AirVisual.""" import asyncio -from pyairvisual import Client +from pyairvisual import CloudAPI, NodeSamba from pyairvisual.errors import InvalidKeyError, NodeProError import voluptuous as vol @@ -108,7 +108,7 @@ async def async_step_geography(self, user_input=None): return self.async_abort(reason="already_configured") websession = aiohttp_client.async_get_clientsession(self.hass) - client = Client(session=websession, api_key=user_input[CONF_API_KEY]) + cloud_api = CloudAPI(user_input[CONF_API_KEY], session=websession) # If this is the first (and only the first) time we've seen this API key, check # that it's valid: @@ -120,7 +120,7 @@ async def async_step_geography(self, user_input=None): async with check_keys_lock: if user_input[CONF_API_KEY] not in checked_keys: try: - await client.api.nearest_city() + await cloud_api.air_quality.nearest_city() except InvalidKeyError: return self.async_show_form( step_id="geography", @@ -157,24 +157,20 @@ async def async_step_node_pro(self, user_input=None): await self._async_set_unique_id(user_input[CONF_IP_ADDRESS]) - websession = aiohttp_client.async_get_clientsession(self.hass) - client = Client(session=websession) + node = NodeSamba(user_input[CONF_IP_ADDRESS], user_input[CONF_PASSWORD]) try: - await client.node.from_samba( - user_input[CONF_IP_ADDRESS], - user_input[CONF_PASSWORD], - include_history=False, - include_trends=False, - ) + await node.async_connect() except NodeProError as err: LOGGER.error("Error connecting to Node/Pro unit: %s", err) return self.async_show_form( step_id="node_pro", data_schema=self.node_pro_schema, - errors={CONF_IP_ADDRESS: "unable_to_connect"}, + errors={CONF_IP_ADDRESS: "cannot_connect"}, ) + await node.async_disconnect() + return self.async_create_entry( title=f"Node/Pro ({user_input[CONF_IP_ADDRESS]})", data={**user_input, CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_NODE_PRO}, diff --git a/homeassistant/components/airvisual/manifest.json b/homeassistant/components/airvisual/manifest.json index 93b57a4804ee22..d78245512754c8 100644 --- a/homeassistant/components/airvisual/manifest.json +++ b/homeassistant/components/airvisual/manifest.json @@ -3,6 +3,6 @@ "name": "AirVisual", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/airvisual", - "requirements": ["pyairvisual==4.4.0"], + "requirements": ["pyairvisual==5.0.2"], "codeowners": ["@bachya"] } diff --git a/homeassistant/components/airvisual/sensor.py b/homeassistant/components/airvisual/sensor.py index 895ffa494a44ad..a81c118ecc9480 100644 --- a/homeassistant/components/airvisual/sensor.py +++ b/homeassistant/components/airvisual/sensor.py @@ -38,10 +38,6 @@ ATTR_POLLUTANT_UNIT = "pollutant_unit" ATTR_REGION = "region" -MASS_PARTS_PER_MILLION = "ppm" -MASS_PARTS_PER_BILLION = "ppb" -VOLUME_MICROGRAMS_PER_CUBIC_METER = "µg/m3" - SENSOR_KIND_LEVEL = "air_pollution_level" SENSOR_KIND_AQI = "air_quality_index" SENSOR_KIND_POLLUTANT = "main_pollutant" @@ -229,22 +225,20 @@ def device_class(self): def device_info(self): """Return device registry information for this entity.""" return { - "identifiers": { - (DOMAIN, self.coordinator.data["current"]["serial_number"]) - }, - "name": self.coordinator.data["current"]["settings"]["node_name"], + "identifiers": {(DOMAIN, self.coordinator.data["serial_number"])}, + "name": self.coordinator.data["settings"]["node_name"], "manufacturer": "AirVisual", - "model": f'{self.coordinator.data["current"]["status"]["model"]}', + "model": f'{self.coordinator.data["status"]["model"]}', "sw_version": ( - f'Version {self.coordinator.data["current"]["status"]["system_version"]}' - f'{self.coordinator.data["current"]["status"]["app_version"]}' + f'Version {self.coordinator.data["status"]["system_version"]}' + f'{self.coordinator.data["status"]["app_version"]}' ), } @property def name(self): """Return the name.""" - node_name = self.coordinator.data["current"]["settings"]["node_name"] + node_name = self.coordinator.data["settings"]["node_name"] return f"{node_name} Node/Pro: {self._name}" @property @@ -255,18 +249,14 @@ def state(self): @property def unique_id(self): """Return a unique, Home Assistant friendly identifier for this entity.""" - return f"{self.coordinator.data['current']['serial_number']}_{self._kind}" + return f"{self.coordinator.data['serial_number']}_{self._kind}" @callback def update_from_latest_data(self): """Update the entity from the latest data.""" if self._kind == SENSOR_KIND_BATTERY_LEVEL: - self._state = self.coordinator.data["current"]["status"]["battery"] + self._state = self.coordinator.data["status"]["battery"] elif self._kind == SENSOR_KIND_HUMIDITY: - self._state = self.coordinator.data["current"]["measurements"].get( - "humidity" - ) + self._state = self.coordinator.data["measurements"].get("humidity") elif self._kind == SENSOR_KIND_TEMPERATURE: - self._state = self.coordinator.data["current"]["measurements"].get( - "temperature_C" - ) + self._state = self.coordinator.data["measurements"].get("temperature_C") diff --git a/homeassistant/components/airvisual/strings.json b/homeassistant/components/airvisual/strings.json index c7e2bc347016a2..fd8e10f105e9fd 100644 --- a/homeassistant/components/airvisual/strings.json +++ b/homeassistant/components/airvisual/strings.json @@ -6,8 +6,8 @@ "description": "Use the AirVisual cloud API to monitor a geographical location.", "data": { "api_key": "[%key:common::config_flow::data::api_key%]", - "latitude": "Latitude", - "longitude": "Longitude" + "latitude": "[%key:common::config_flow::data::latitude%]", + "longitude": "[%key:common::config_flow::data::longitude%]" } }, "node_pro": { @@ -29,9 +29,9 @@ } }, "error": { - "general_error": "There was an unknown error.", - "invalid_api_key": "Invalid API key provided.", - "unable_to_connect": "Unable to connect to Node/Pro unit." + "general_error": "[%key:common::config_flow::error::unknown%]", + "invalid_api_key": "[%key:common::config_flow::error::invalid_api_key%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, "abort": { "already_configured": "These coordinates or Node/Pro ID are already registered." @@ -47,4 +47,4 @@ } } } -} \ No newline at end of file +} diff --git a/homeassistant/components/airvisual/translations/ca.json b/homeassistant/components/airvisual/translations/ca.json index 045be812dc2031..98e09971ae341c 100644 --- a/homeassistant/components/airvisual/translations/ca.json +++ b/homeassistant/components/airvisual/translations/ca.json @@ -4,8 +4,9 @@ "already_configured": "Aquestes coordenades o Node/Pro ID ja estan registrades." }, "error": { - "general_error": "S'ha produ\u00eft un error desconegut.", - "invalid_api_key": "Clau API proporiconada no v\u00e0lida.", + "cannot_connect": "Ha fallat la connexi\u00f3", + "general_error": "Error inesperat", + "invalid_api_key": "Clau API inv\u00e0lida", "unable_to_connect": "No s'ha pogut connectar a la unitat Node/Pro." }, "step": { diff --git a/homeassistant/components/airvisual/translations/el.json b/homeassistant/components/airvisual/translations/el.json new file mode 100644 index 00000000000000..04b238a916d221 --- /dev/null +++ b/homeassistant/components/airvisual/translations/el.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airvisual/translations/en.json b/homeassistant/components/airvisual/translations/en.json index a85c2ba75cef39..beb7ebe6bf3b02 100644 --- a/homeassistant/components/airvisual/translations/en.json +++ b/homeassistant/components/airvisual/translations/en.json @@ -4,8 +4,9 @@ "already_configured": "These coordinates or Node/Pro ID are already registered." }, "error": { - "general_error": "There was an unknown error.", - "invalid_api_key": "Invalid API key provided.", + "cannot_connect": "Failed to connect", + "general_error": "Unexpected error", + "invalid_api_key": "Invalid API key", "unable_to_connect": "Unable to connect to Node/Pro unit." }, "step": { diff --git a/homeassistant/components/airvisual/translations/es.json b/homeassistant/components/airvisual/translations/es.json index dbb44ed4abe473..2ef588f3accf04 100644 --- a/homeassistant/components/airvisual/translations/es.json +++ b/homeassistant/components/airvisual/translations/es.json @@ -4,6 +4,7 @@ "already_configured": "Estas coordenadas o Nodo/Pro ID ya est\u00e1n registradas." }, "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.", "unable_to_connect": "No se puede conectar a la unidad Node/Pro." diff --git a/homeassistant/components/airvisual/translations/et.json b/homeassistant/components/airvisual/translations/et.json new file mode 100644 index 00000000000000..eb795276597901 --- /dev/null +++ b/homeassistant/components/airvisual/translations/et.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00dchendamine nurjus" + }, + "step": { + "geography": { + "data": { + "latitude": "Laiuskraad", + "longitude": "Pikkuskraad" + } + }, + "user": { + "data": { + "cloud_api": "Geograafiline asukoht" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/airvisual/translations/fr.json b/homeassistant/components/airvisual/translations/fr.json index 7bd42083e6dcf0..55247360306fe5 100644 --- a/homeassistant/components/airvisual/translations/fr.json +++ b/homeassistant/components/airvisual/translations/fr.json @@ -4,6 +4,7 @@ "already_configured": "Cette cl\u00e9 API est d\u00e9j\u00e0 utilis\u00e9e." }, "error": { + "cannot_connect": "\u00c9chec de connexion", "general_error": "Une erreur inconnue est survenue.", "invalid_api_key": "La cl\u00e9 API fournie n'est pas valide.", "unable_to_connect": "Impossible de se connecter \u00e0 l'unit\u00e9 Node / Pro." diff --git a/homeassistant/components/airvisual/translations/it.json b/homeassistant/components/airvisual/translations/it.json index c22481918a0eb3..1edb269d37427a 100644 --- a/homeassistant/components/airvisual/translations/it.json +++ b/homeassistant/components/airvisual/translations/it.json @@ -4,8 +4,9 @@ "already_configured": "Queste coordinate o Node/Pro ID sono gi\u00e0 registrate." }, "error": { - "general_error": "Si \u00e8 verificato un errore sconosciuto.", - "invalid_api_key": "Chiave API non valida fornita.", + "cannot_connect": "Impossibile connettersi", + "general_error": "Errore imprevisto", + "invalid_api_key": "Chiave API non valida", "unable_to_connect": "Impossibile connettersi all'unit\u00e0 Node/Pro." }, "step": { diff --git a/homeassistant/components/airvisual/translations/lb.json b/homeassistant/components/airvisual/translations/lb.json index d0cffc8ec2d309..cca720da82c030 100644 --- a/homeassistant/components/airvisual/translations/lb.json +++ b/homeassistant/components/airvisual/translations/lb.json @@ -4,6 +4,7 @@ "already_configured": "D\u00ebs Koordinate oder ode/Pro ID si schon registr\u00e9iert." }, "error": { + "cannot_connect": "Feeler beim verbannen", "general_error": "Onbekannten Feeler", "invalid_api_key": "Ong\u00ebltegen API Schl\u00ebssel uginn", "unable_to_connect": "Kann sech net mat der Node/Pri verbannen." diff --git a/homeassistant/components/airvisual/translations/no.json b/homeassistant/components/airvisual/translations/no.json index 8fcf00a6714b84..80e0f6631c6c96 100644 --- a/homeassistant/components/airvisual/translations/no.json +++ b/homeassistant/components/airvisual/translations/no.json @@ -4,8 +4,9 @@ "already_configured": "Disse koordinatene eller Node / Pro ID er allerede registrert." }, "error": { - "general_error": "Det oppstod en ukjent feil.", - "invalid_api_key": "Ugyldig API-n\u00f8kkel angitt.", + "cannot_connect": "Tilkobling mislyktes.", + "general_error": "Uventet feil", + "invalid_api_key": "Ugyldig API-n\u00f8kkel", "unable_to_connect": "Kan ikke koble til Node / Pro-enheten." }, "step": { diff --git a/homeassistant/components/airvisual/translations/pl.json b/homeassistant/components/airvisual/translations/pl.json index dea77a233aabb8..b00a29a8a5da01 100644 --- a/homeassistant/components/airvisual/translations/pl.json +++ b/homeassistant/components/airvisual/translations/pl.json @@ -4,7 +4,8 @@ "already_configured": "Ten klucz API jest ju\u017c w u\u017cyciu." }, "error": { - "general_error": "Nieoczekiwany b\u0142\u0105d.", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "general_error": "Nieoczekiwany b\u0142\u0105d", "invalid_api_key": "Nieprawid\u0142owy klucz API.", "unable_to_connect": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z jednostk\u0105 Node/Pro." }, diff --git a/homeassistant/components/airvisual/translations/ru.json b/homeassistant/components/airvisual/translations/ru.json index 67af449c9b0e46..c35aefa3b9375c 100644 --- a/homeassistant/components/airvisual/translations/ru.json +++ b/homeassistant/components/airvisual/translations/ru.json @@ -4,7 +4,8 @@ "already_configured": "\u041c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043e." }, "error": { - "general_error": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\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.", + "general_error": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430.", "invalid_api_key": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API.", "unable_to_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." }, diff --git a/homeassistant/components/airvisual/translations/zh-Hant.json b/homeassistant/components/airvisual/translations/zh-Hant.json index 80e8372f3f6d51..689b95027fa93c 100644 --- a/homeassistant/components/airvisual/translations/zh-Hant.json +++ b/homeassistant/components/airvisual/translations/zh-Hant.json @@ -4,8 +4,9 @@ "already_configured": "\u6b64\u5ea7\u6a19\u6216 Node/Pro ID \u5df2\u8a3b\u518a\u3002" }, "error": { - "general_error": "\u767c\u751f\u672a\u77e5\u932f\u8aa4\u3002", - "invalid_api_key": "API \u5bc6\u9470\u7121\u6548\u3002", + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "general_error": "\u672a\u9810\u671f\u932f\u8aa4", + "invalid_api_key": "API \u5bc6\u9470\u7121\u6548", "unable_to_connect": "\u7121\u6cd5\u9023\u7dda\u81f3 Node/Pro \u8a2d\u5099\u3002" }, "step": { diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py index 50b8adb4c032de..114abfa9cd6e4c 100644 --- a/homeassistant/components/alarm_control_panel/__init__.py +++ b/homeassistant/components/alarm_control_panel/__init__.py @@ -175,12 +175,11 @@ def supported_features(self) -> int: @property def state_attributes(self): """Return the state attributes.""" - state_attr = { + return { ATTR_CODE_FORMAT: self.code_format, ATTR_CHANGED_BY: self.changed_by, ATTR_CODE_ARM_REQUIRED: self.code_arm_required, } - return state_attr class AlarmControlPanel(AlarmControlPanelEntity): diff --git a/homeassistant/components/alarm_control_panel/device_action.py b/homeassistant/components/alarm_control_panel/device_action.py index 81e444ae16f0cb..0dc16fdcf42275 100644 --- a/homeassistant/components/alarm_control_panel/device_action.py +++ b/homeassistant/components/alarm_control_panel/device_action.py @@ -6,6 +6,7 @@ from homeassistant.const import ( ATTR_CODE, ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, CONF_CODE, CONF_DEVICE_ID, CONF_DOMAIN, @@ -56,7 +57,7 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: if state is None: continue - supported_features = state.attributes["supported_features"] + supported_features = state.attributes[ATTR_SUPPORTED_FEATURES] # Add actions for each entity that belongs to this integration if supported_features & SUPPORT_ALARM_ARM_AWAY: diff --git a/homeassistant/components/alarm_control_panel/device_condition.py b/homeassistant/components/alarm_control_panel/device_condition.py index c4d43d1b051472..e5b3ec6aeee960 100644 --- a/homeassistant/components/alarm_control_panel/device_condition.py +++ b/homeassistant/components/alarm_control_panel/device_condition.py @@ -11,6 +11,7 @@ ) from homeassistant.const import ( ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, CONF_CONDITION, CONF_DEVICE_ID, CONF_DOMAIN, @@ -73,7 +74,7 @@ async def async_get_conditions( if state is None: continue - supported_features = state.attributes["supported_features"] + supported_features = state.attributes[ATTR_SUPPORTED_FEATURES] # Add conditions for each entity that belongs to this integration conditions += [ diff --git a/homeassistant/components/alarm_control_panel/device_trigger.py b/homeassistant/components/alarm_control_panel/device_trigger.py index eeea1dbbf33a31..cb07ff35e962c2 100644 --- a/homeassistant/components/alarm_control_panel/device_trigger.py +++ b/homeassistant/components/alarm_control_panel/device_trigger.py @@ -12,6 +12,7 @@ from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA from homeassistant.components.homeassistant.triggers import state as state_trigger from homeassistant.const import ( + ATTR_SUPPORTED_FEATURES, CONF_DEVICE_ID, CONF_DOMAIN, CONF_ENTITY_ID, @@ -64,7 +65,7 @@ async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: if entity_state is None: continue - supported_features = entity_state.attributes["supported_features"] + supported_features = entity_state.attributes[ATTR_SUPPORTED_FEATURES] # Add triggers for each entity that belongs to this integration triggers += [ diff --git a/homeassistant/components/alarm_control_panel/group.py b/homeassistant/components/alarm_control_panel/group.py new file mode 100644 index 00000000000000..6645f12245d257 --- /dev/null +++ b/homeassistant/components/alarm_control_panel/group.py @@ -0,0 +1,31 @@ +"""Describe group states.""" + + +from homeassistant.components.group import GroupIntegrationRegistry +from homeassistant.const import ( + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_CUSTOM_BYPASS, + STATE_ALARM_ARMED_HOME, + STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_TRIGGERED, + STATE_OFF, +) +from homeassistant.core import callback +from homeassistant.helpers.typing import HomeAssistantType + + +@callback +def async_describe_on_off_states( + hass: HomeAssistantType, registry: GroupIntegrationRegistry +) -> None: + """Describe group on off states.""" + registry.on_off_states( + { + STATE_ALARM_ARMED_AWAY, + STATE_ALARM_ARMED_CUSTOM_BYPASS, + STATE_ALARM_ARMED_HOME, + STATE_ALARM_ARMED_NIGHT, + STATE_ALARM_TRIGGERED, + }, + STATE_OFF, + ) diff --git a/homeassistant/components/alarm_control_panel/translations/et.json b/homeassistant/components/alarm_control_panel/translations/et.json index 28c47b5a06d973..76b0c845d01247 100644 --- a/homeassistant/components/alarm_control_panel/translations/et.json +++ b/homeassistant/components/alarm_control_panel/translations/et.json @@ -1,4 +1,27 @@ { + "device_automation": { + "action_type": { + "arm_away": "Valvesta {entity_name}", + "arm_home": "Valvesta {entity_name} kodus re\u017eiimis", + "arm_night": "Valvesta {entity_name} \u00f6\u00f6re\u017eiimis", + "disarm": "V\u00f5ta {entity_name} valvest maha", + "trigger": "K\u00e4ivita {entity_name}" + }, + "condition_type": { + "is_armed_away": "{entity_name} on valvestatud", + "is_armed_home": "{entity_name} on valvestatud kodure\u017eiimis", + "is_armed_night": "{entity_name} on valvestatud \u00f6\u00f6re\u017eiimis", + "is_disarmed": "{entity_name} on valve alt maas", + "is_triggered": "{entity_name} on h\u00e4iret andnud" + }, + "trigger_type": { + "armed_away": "{entity_name} valvestatus", + "armed_home": "{entity_name} valvestatus kodure\u017eiimis", + "armed_night": "{entity_name} valvestatus \u00f6\u00f6re\u017eiimis", + "disarmed": "{entity_name} v\u00f5eti valvest maha", + "triggered": "{entity_name} andis h\u00e4iret" + } + }, "state": { "_": { "armed": "Valves", diff --git a/homeassistant/components/alarm_control_panel/translations/nl.json b/homeassistant/components/alarm_control_panel/translations/nl.json index 15b5fd8457c2ef..0a0f33d6181f6d 100644 --- a/homeassistant/components/alarm_control_panel/translations/nl.json +++ b/homeassistant/components/alarm_control_panel/translations/nl.json @@ -25,7 +25,7 @@ "state": { "_": { "armed": "Ingeschakeld", - "armed_away": "Afwezig Ingeschakeld", + "armed_away": "Ingeschakeld afwezig", "armed_custom_bypass": "Ingeschakeld met overbrugging(en)", "armed_home": "Ingeschakeld thuis", "armed_night": "Ingeschakeld nacht", diff --git a/homeassistant/components/alarmdecoder/__init__.py b/homeassistant/components/alarmdecoder/__init__.py index 0aa9fcc29eca49..8dd704f133345a 100644 --- a/homeassistant/components/alarmdecoder/__init__.py +++ b/homeassistant/components/alarmdecoder/__init__.py @@ -1,167 +1,82 @@ """Support for AlarmDecoder devices.""" +import asyncio from datetime import timedelta import logging from adext import AdExt -from alarmdecoder.devices import SerialDevice, SocketDevice, USBDevice +from alarmdecoder.devices import SerialDevice, SocketDevice from alarmdecoder.util import NoDeviceError -import voluptuous as vol -from homeassistant.components.binary_sensor import DEVICE_CLASSES_SCHEMA -from homeassistant.const import CONF_HOST, EVENT_HOMEASSISTANT_STOP -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.discovery import load_platform -from homeassistant.util import dt as dt_util - -_LOGGER = logging.getLogger(__name__) - -DOMAIN = "alarmdecoder" - -DATA_AD = "alarmdecoder" - -CONF_DEVICE = "device" -CONF_DEVICE_BAUD = "baudrate" -CONF_DEVICE_PATH = "path" -CONF_DEVICE_PORT = "port" -CONF_DEVICE_TYPE = "type" -CONF_AUTO_BYPASS = "autobypass" -CONF_PANEL_DISPLAY = "panel_display" -CONF_ZONE_NAME = "name" -CONF_ZONE_TYPE = "type" -CONF_ZONE_LOOP = "loop" -CONF_ZONE_RFID = "rfid" -CONF_ZONES = "zones" -CONF_RELAY_ADDR = "relayaddr" -CONF_RELAY_CHAN = "relaychan" -CONF_CODE_ARM_REQUIRED = "code_arm_required" - -DEFAULT_DEVICE_TYPE = "socket" -DEFAULT_DEVICE_HOST = "localhost" -DEFAULT_DEVICE_PORT = 10000 -DEFAULT_DEVICE_PATH = "/dev/ttyUSB0" -DEFAULT_DEVICE_BAUD = 115200 - -DEFAULT_AUTO_BYPASS = False -DEFAULT_PANEL_DISPLAY = False -DEFAULT_CODE_ARM_REQUIRED = True - -DEFAULT_ZONE_TYPE = "opening" - -SIGNAL_PANEL_MESSAGE = "alarmdecoder.panel_message" -SIGNAL_PANEL_ARM_AWAY = "alarmdecoder.panel_arm_away" -SIGNAL_PANEL_ARM_HOME = "alarmdecoder.panel_arm_home" -SIGNAL_PANEL_DISARM = "alarmdecoder.panel_disarm" - -SIGNAL_ZONE_FAULT = "alarmdecoder.zone_fault" -SIGNAL_ZONE_RESTORE = "alarmdecoder.zone_restore" -SIGNAL_RFX_MESSAGE = "alarmdecoder.rfx_message" -SIGNAL_REL_MESSAGE = "alarmdecoder.rel_message" - -DEVICE_SOCKET_SCHEMA = vol.Schema( - { - vol.Required(CONF_DEVICE_TYPE): "socket", - vol.Optional(CONF_HOST, default=DEFAULT_DEVICE_HOST): cv.string, - vol.Optional(CONF_DEVICE_PORT, default=DEFAULT_DEVICE_PORT): cv.port, - } +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + CONF_HOST, + CONF_PORT, + CONF_PROTOCOL, + EVENT_HOMEASSISTANT_STOP, ) +from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.util import dt as dt_util -DEVICE_SERIAL_SCHEMA = vol.Schema( - { - vol.Required(CONF_DEVICE_TYPE): "serial", - vol.Optional(CONF_DEVICE_PATH, default=DEFAULT_DEVICE_PATH): cv.string, - vol.Optional(CONF_DEVICE_BAUD, default=DEFAULT_DEVICE_BAUD): cv.string, - } +from .const import ( + CONF_DEVICE_BAUD, + CONF_DEVICE_PATH, + DATA_AD, + DATA_REMOVE_STOP_LISTENER, + DATA_REMOVE_UPDATE_LISTENER, + DATA_RESTART, + DOMAIN, + PROTOCOL_SERIAL, + PROTOCOL_SOCKET, + SIGNAL_PANEL_MESSAGE, + SIGNAL_REL_MESSAGE, + SIGNAL_RFX_MESSAGE, + SIGNAL_ZONE_FAULT, + SIGNAL_ZONE_RESTORE, ) -DEVICE_USB_SCHEMA = vol.Schema({vol.Required(CONF_DEVICE_TYPE): "usb"}) - -ZONE_SCHEMA = vol.Schema( - { - vol.Required(CONF_ZONE_NAME): cv.string, - vol.Optional(CONF_ZONE_TYPE, default=DEFAULT_ZONE_TYPE): vol.Any( - DEVICE_CLASSES_SCHEMA - ), - vol.Optional(CONF_ZONE_RFID): cv.string, - vol.Optional(CONF_ZONE_LOOP): vol.All(vol.Coerce(int), vol.Range(min=1, max=4)), - vol.Inclusive( - CONF_RELAY_ADDR, - "relaylocation", - "Relay address and channel must exist together", - ): cv.byte, - vol.Inclusive( - CONF_RELAY_CHAN, - "relaylocation", - "Relay address and channel must exist together", - ): cv.byte, - } -) +_LOGGER = logging.getLogger(__name__) -CONFIG_SCHEMA = vol.Schema( - { - DOMAIN: vol.Schema( - { - vol.Required(CONF_DEVICE): vol.Any( - DEVICE_SOCKET_SCHEMA, DEVICE_SERIAL_SCHEMA, DEVICE_USB_SCHEMA - ), - vol.Optional( - CONF_PANEL_DISPLAY, default=DEFAULT_PANEL_DISPLAY - ): cv.boolean, - vol.Optional(CONF_AUTO_BYPASS, default=DEFAULT_AUTO_BYPASS): cv.boolean, - vol.Optional( - CONF_CODE_ARM_REQUIRED, default=DEFAULT_CODE_ARM_REQUIRED - ): cv.boolean, - vol.Optional(CONF_ZONES): {vol.Coerce(int): ZONE_SCHEMA}, - } - ) - }, - extra=vol.ALLOW_EXTRA, -) +PLATFORMS = ["alarm_control_panel", "sensor", "binary_sensor"] -def setup(hass, config): +async def async_setup(hass, config): """Set up for the AlarmDecoder devices.""" - conf = config.get(DOMAIN) + return True + - restart = False - device = conf[CONF_DEVICE] - display = conf[CONF_PANEL_DISPLAY] - auto_bypass = conf[CONF_AUTO_BYPASS] - code_arm_required = conf[CONF_CODE_ARM_REQUIRED] - zones = conf.get(CONF_ZONES) +async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: + """Set up AlarmDecoder config flow.""" + undo_listener = entry.add_update_listener(_update_listener) - device_type = device[CONF_DEVICE_TYPE] - host = DEFAULT_DEVICE_HOST - port = DEFAULT_DEVICE_PORT - path = DEFAULT_DEVICE_PATH - baud = DEFAULT_DEVICE_BAUD + ad_connection = entry.data + protocol = ad_connection[CONF_PROTOCOL] def stop_alarmdecoder(event): """Handle the shutdown of AlarmDecoder.""" + if not hass.data.get(DOMAIN): + return _LOGGER.debug("Shutting down alarmdecoder") - nonlocal restart - restart = False + hass.data[DOMAIN][entry.entry_id][DATA_RESTART] = False controller.close() - def open_connection(now=None): + async def open_connection(now=None): """Open a connection to AlarmDecoder.""" - nonlocal restart try: - controller.open(baud) + await hass.async_add_executor_job(controller.open, baud) except NoDeviceError: - _LOGGER.debug("Failed to connect. Retrying in 5 seconds") - hass.helpers.event.track_point_in_time( + _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) ) return _LOGGER.debug("Established a connection with the alarmdecoder") - restart = True + hass.data[DOMAIN][entry.entry_id][DATA_RESTART] = True def handle_closed_connection(event): """Restart after unexpected loss of connection.""" - nonlocal restart - if not restart: + if not hass.data[DOMAIN][entry.entry_id][DATA_RESTART]: return - restart = False + hass.data[DOMAIN][entry.entry_id][DATA_RESTART] = False _LOGGER.warning("AlarmDecoder unexpectedly lost connection") hass.add_job(open_connection) @@ -185,18 +100,14 @@ def handle_rel_message(sender, message): """Handle relay or zone expander message from AlarmDecoder.""" hass.helpers.dispatcher.dispatcher_send(SIGNAL_REL_MESSAGE, message) - controller = False - if device_type == "socket": - host = device[CONF_HOST] - port = device[CONF_DEVICE_PORT] + baud = ad_connection.get(CONF_DEVICE_BAUD) + if protocol == PROTOCOL_SOCKET: + host = ad_connection[CONF_HOST] + port = ad_connection[CONF_PORT] controller = AdExt(SocketDevice(interface=(host, port))) - elif device_type == "serial": - path = device[CONF_DEVICE_PATH] - baud = device[CONF_DEVICE_BAUD] + if protocol == PROTOCOL_SERIAL: + path = ad_connection[CONF_DEVICE_PATH] controller = AdExt(SerialDevice(interface=path)) - elif device_type == "usb": - AdExt(USBDevice.find()) - return False controller.on_message += handle_message controller.on_rfx_message += handle_rfx_message @@ -205,24 +116,56 @@ def handle_rel_message(sender, message): controller.on_close += handle_closed_connection controller.on_expander_message += handle_rel_message - hass.data[DATA_AD] = controller + remove_stop_listener = hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STOP, stop_alarmdecoder + ) + + hass.data.setdefault(DOMAIN, {}) + hass.data[DOMAIN][entry.entry_id] = { + DATA_AD: controller, + DATA_REMOVE_UPDATE_LISTENER: undo_listener, + DATA_REMOVE_STOP_LISTENER: remove_stop_listener, + DATA_RESTART: False, + } + + await open_connection() + + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, component) + ) + return True - open_connection() - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_alarmdecoder) +async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry): + """Unload a AlarmDecoder entry.""" + hass.data[DOMAIN][entry.entry_id][DATA_RESTART] = False - load_platform( - hass, - "alarm_control_panel", - DOMAIN, - {CONF_AUTO_BYPASS: auto_bypass, CONF_CODE_ARM_REQUIRED: code_arm_required}, - config, + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, component) + for component in PLATFORMS + ] + ) ) - if zones: - load_platform(hass, "binary_sensor", DOMAIN, {CONF_ZONES: zones}, config) + if not unload_ok: + return False - if display: - load_platform(hass, "sensor", DOMAIN, conf, config) + hass.data[DOMAIN][entry.entry_id][DATA_REMOVE_UPDATE_LISTENER]() + hass.data[DOMAIN][entry.entry_id][DATA_REMOVE_STOP_LISTENER]() + await hass.async_add_executor_job(hass.data[DOMAIN][entry.entry_id][DATA_AD].close) + + if hass.data[DOMAIN][entry.entry_id]: + hass.data[DOMAIN].pop(entry.entry_id) + if not hass.data[DOMAIN]: + hass.data.pop(DOMAIN) return True + + +async def _update_listener(hass: HomeAssistantType, entry: ConfigEntry): + """Handle options update.""" + _LOGGER.debug("AlarmDecoder options updated: %s", entry.as_dict()["options"]) + await hass.config_entries.async_reload(entry.entry_id) diff --git a/homeassistant/components/alarmdecoder/alarm_control_panel.py b/homeassistant/components/alarmdecoder/alarm_control_panel.py index 117374552f3b66..bc2d74a5042619 100644 --- a/homeassistant/components/alarmdecoder/alarm_control_panel.py +++ b/homeassistant/components/alarmdecoder/alarm_control_panel.py @@ -12,6 +12,7 @@ SUPPORT_ALARM_ARM_HOME, SUPPORT_ALARM_ARM_NIGHT, ) +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_CODE, STATE_ALARM_ARMED_AWAY, @@ -20,66 +21,70 @@ STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED, ) +from homeassistant.helpers import entity_platform import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.typing import HomeAssistantType -from . import ( +from .const import ( + CONF_ALT_NIGHT_MODE, CONF_AUTO_BYPASS, CONF_CODE_ARM_REQUIRED, DATA_AD, + DEFAULT_ARM_OPTIONS, DOMAIN, + OPTIONS_ARM, SIGNAL_PANEL_MESSAGE, ) _LOGGER = logging.getLogger(__name__) SERVICE_ALARM_TOGGLE_CHIME = "alarm_toggle_chime" -ALARM_TOGGLE_CHIME_SCHEMA = vol.Schema({vol.Required(ATTR_CODE): cv.string}) SERVICE_ALARM_KEYPRESS = "alarm_keypress" ATTR_KEYPRESS = "keypress" -ALARM_KEYPRESS_SCHEMA = vol.Schema({vol.Required(ATTR_KEYPRESS): cv.string}) -def setup_platform(hass, config, add_entities, discovery_info=None): +async def async_setup_entry( + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities +): """Set up for AlarmDecoder alarm panels.""" - if discovery_info is None: - return - - auto_bypass = discovery_info[CONF_AUTO_BYPASS] - code_arm_required = discovery_info[CONF_CODE_ARM_REQUIRED] - entity = AlarmDecoderAlarmPanel(auto_bypass, code_arm_required) - add_entities([entity]) + options = entry.options + arm_options = options.get(OPTIONS_ARM, DEFAULT_ARM_OPTIONS) + client = hass.data[DOMAIN][entry.entry_id][DATA_AD] + + entity = AlarmDecoderAlarmPanel( + client=client, + auto_bypass=arm_options[CONF_AUTO_BYPASS], + code_arm_required=arm_options[CONF_CODE_ARM_REQUIRED], + alt_night_mode=arm_options[CONF_ALT_NIGHT_MODE], + ) + async_add_entities([entity]) - def alarm_toggle_chime_handler(service): - """Register toggle chime handler.""" - code = service.data.get(ATTR_CODE) - entity.alarm_toggle_chime(code) + platform = entity_platform.current_platform.get() - hass.services.register( - DOMAIN, + platform.async_register_entity_service( SERVICE_ALARM_TOGGLE_CHIME, - alarm_toggle_chime_handler, - schema=ALARM_TOGGLE_CHIME_SCHEMA, + { + vol.Required(ATTR_CODE): cv.string, + }, + "alarm_toggle_chime", ) - def alarm_keypress_handler(service): - """Register keypress handler.""" - keypress = service.data[ATTR_KEYPRESS] - entity.alarm_keypress(keypress) - - hass.services.register( - DOMAIN, + platform.async_register_entity_service( SERVICE_ALARM_KEYPRESS, - alarm_keypress_handler, - schema=ALARM_KEYPRESS_SCHEMA, + { + vol.Required(ATTR_KEYPRESS): cv.string, + }, + "alarm_keypress", ) class AlarmDecoderAlarmPanel(AlarmControlPanelEntity): """Representation of an AlarmDecoder-based alarm panel.""" - def __init__(self, auto_bypass, code_arm_required): + def __init__(self, client, auto_bypass, code_arm_required, alt_night_mode): """Initialize the alarm panel.""" + self._client = client self._display = "" self._name = "Alarm Panel" self._state = None @@ -95,6 +100,7 @@ def __init__(self, auto_bypass, code_arm_required): self._zone_bypassed = None self._auto_bypass = auto_bypass self._code_arm_required = code_arm_required + self._alt_night_mode = alt_night_mode async def async_added_to_hass(self): """Register callbacks.""" @@ -180,11 +186,11 @@ def device_state_attributes(self): def alarm_disarm(self, code=None): """Send disarm command.""" if code: - self.hass.data[DATA_AD].send(f"{code!s}1") + self._client.send(f"{code!s}1") def alarm_arm_away(self, code=None): """Send arm away command.""" - self.hass.data[DATA_AD].arm_away( + self._client.arm_away( code=code, code_arm_required=self._code_arm_required, auto_bypass=self._auto_bypass, @@ -192,7 +198,7 @@ def alarm_arm_away(self, code=None): def alarm_arm_home(self, code=None): """Send arm home command.""" - self.hass.data[DATA_AD].arm_home( + self._client.arm_home( code=code, code_arm_required=self._code_arm_required, auto_bypass=self._auto_bypass, @@ -200,18 +206,19 @@ def alarm_arm_home(self, code=None): def alarm_arm_night(self, code=None): """Send arm night command.""" - self.hass.data[DATA_AD].arm_night( + self._client.arm_night( code=code, code_arm_required=self._code_arm_required, + alt_night_mode=self._alt_night_mode, auto_bypass=self._auto_bypass, ) def alarm_toggle_chime(self, code=None): """Send toggle chime command.""" if code: - self.hass.data[DATA_AD].send(f"{code!s}9") + self._client.send(f"{code!s}9") def alarm_keypress(self, keypress): """Send custom keypresses.""" if keypress: - self.hass.data[DATA_AD].send(keypress) + self._client.send(keypress) diff --git a/homeassistant/components/alarmdecoder/binary_sensor.py b/homeassistant/components/alarmdecoder/binary_sensor.py index cec1b8356b0234..55bf13d7fef0b5 100644 --- a/homeassistant/components/alarmdecoder/binary_sensor.py +++ b/homeassistant/components/alarmdecoder/binary_sensor.py @@ -2,20 +2,23 @@ import logging from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.helpers.typing import HomeAssistantType -from . import ( +from .const import ( CONF_RELAY_ADDR, CONF_RELAY_CHAN, CONF_ZONE_LOOP, CONF_ZONE_NAME, + CONF_ZONE_NUMBER, CONF_ZONE_RFID, CONF_ZONE_TYPE, - CONF_ZONES, + DEFAULT_ZONE_OPTIONS, + OPTIONS_ZONES, SIGNAL_REL_MESSAGE, SIGNAL_RFX_MESSAGE, SIGNAL_ZONE_FAULT, SIGNAL_ZONE_RESTORE, - ZONE_SCHEMA, ) _LOGGER = logging.getLogger(__name__) @@ -30,27 +33,28 @@ ATTR_RF_LOOP1 = "rf_loop1" -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the AlarmDecoder binary sensor devices.""" - configured_zones = discovery_info[CONF_ZONES] - - devices = [] - for zone_num in configured_zones: - device_config_data = ZONE_SCHEMA(configured_zones[zone_num]) - zone_type = device_config_data[CONF_ZONE_TYPE] - zone_name = device_config_data[CONF_ZONE_NAME] - zone_rfid = device_config_data.get(CONF_ZONE_RFID) - zone_loop = device_config_data.get(CONF_ZONE_LOOP) - relay_addr = device_config_data.get(CONF_RELAY_ADDR) - relay_chan = device_config_data.get(CONF_RELAY_CHAN) - device = AlarmDecoderBinarySensor( +async def async_setup_entry( + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities +): + """Set up for AlarmDecoder sensor.""" + + zones = entry.options.get(OPTIONS_ZONES, DEFAULT_ZONE_OPTIONS) + + entities = [] + for zone_num in zones: + zone_info = zones[zone_num] + zone_type = zone_info[CONF_ZONE_TYPE] + zone_name = zone_info[CONF_ZONE_NAME] + zone_rfid = zone_info.get(CONF_ZONE_RFID) + zone_loop = zone_info.get(CONF_ZONE_LOOP) + relay_addr = zone_info.get(CONF_RELAY_ADDR) + relay_chan = zone_info.get(CONF_RELAY_CHAN) + entity = AlarmDecoderBinarySensor( zone_num, zone_name, zone_type, zone_rfid, zone_loop, relay_addr, relay_chan ) - devices.append(device) - - add_entities(devices) + entities.append(entity) - return True + async_add_entities(entities) class AlarmDecoderBinarySensor(BinarySensorEntity): @@ -67,7 +71,7 @@ def __init__( relay_chan, ): """Initialize the binary_sensor.""" - self._zone_number = zone_number + self._zone_number = int(zone_number) self._zone_type = zone_type self._state = None self._name = zone_name @@ -116,7 +120,7 @@ def should_poll(self): @property def device_state_attributes(self): """Return the state attributes.""" - attr = {} + attr = {CONF_ZONE_NUMBER: self._zone_number} if self._rfid and self._rfstate is not None: attr[ATTR_RF_BIT0] = bool(self._rfstate & 0x01) attr[ATTR_RF_LOW_BAT] = bool(self._rfstate & 0x02) diff --git a/homeassistant/components/alarmdecoder/config_flow.py b/homeassistant/components/alarmdecoder/config_flow.py new file mode 100644 index 00000000000000..74b23f049a710b --- /dev/null +++ b/homeassistant/components/alarmdecoder/config_flow.py @@ -0,0 +1,360 @@ +"""Config flow for AlarmDecoder.""" +import logging + +from adext import AdExt +from alarmdecoder.devices import SerialDevice, SocketDevice +from alarmdecoder.util import NoDeviceError +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.components.binary_sensor import DEVICE_CLASSES +from homeassistant.const import CONF_HOST, CONF_PORT, CONF_PROTOCOL +from homeassistant.core import callback + +from .const import ( # pylint: disable=unused-import + CONF_ALT_NIGHT_MODE, + CONF_AUTO_BYPASS, + CONF_CODE_ARM_REQUIRED, + CONF_DEVICE_BAUD, + CONF_DEVICE_PATH, + CONF_RELAY_ADDR, + CONF_RELAY_CHAN, + CONF_ZONE_LOOP, + CONF_ZONE_NAME, + CONF_ZONE_NUMBER, + CONF_ZONE_RFID, + CONF_ZONE_TYPE, + DEFAULT_ARM_OPTIONS, + DEFAULT_DEVICE_BAUD, + DEFAULT_DEVICE_HOST, + DEFAULT_DEVICE_PATH, + DEFAULT_DEVICE_PORT, + DEFAULT_ZONE_OPTIONS, + DEFAULT_ZONE_TYPE, + DOMAIN, + OPTIONS_ARM, + OPTIONS_ZONES, + PROTOCOL_SERIAL, + PROTOCOL_SOCKET, +) + +EDIT_KEY = "edit_selection" +EDIT_ZONES = "Zones" +EDIT_SETTINGS = "Arming Settings" + +_LOGGER = logging.getLogger(__name__) + + +class AlarmDecoderFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a AlarmDecoder config flow.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH + + def __init__(self): + """Initialize AlarmDecoder ConfigFlow.""" + self.protocol = None + + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Get the options flow for AlarmDecoder.""" + return AlarmDecoderOptionsFlowHandler(config_entry) + + async def async_step_user(self, user_input=None): + """Handle a flow initialized by the user.""" + if user_input is not None: + self.protocol = user_input[CONF_PROTOCOL] + return await self.async_step_protocol() + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required(CONF_PROTOCOL): vol.In( + [PROTOCOL_SOCKET, PROTOCOL_SERIAL] + ), + } + ), + ) + + async def async_step_protocol(self, user_input=None): + """Handle AlarmDecoder protocol setup.""" + errors = {} + if user_input is not None: + if _device_already_added( + self._async_current_entries(), user_input, self.protocol + ): + return self.async_abort(reason="already_configured") + connection = {} + baud = None + if self.protocol == PROTOCOL_SOCKET: + host = connection[CONF_HOST] = user_input[CONF_HOST] + port = connection[CONF_PORT] = user_input[CONF_PORT] + title = f"{host}:{port}" + device = SocketDevice(interface=(host, port)) + if self.protocol == PROTOCOL_SERIAL: + path = connection[CONF_DEVICE_PATH] = user_input[CONF_DEVICE_PATH] + baud = connection[CONF_DEVICE_BAUD] = user_input[CONF_DEVICE_BAUD] + title = path + device = SerialDevice(interface=path) + + controller = AdExt(device) + + def test_connection(): + controller.open(baud) + controller.close() + + try: + await self.hass.async_add_executor_job(test_connection) + return self.async_create_entry( + title=title, data={CONF_PROTOCOL: self.protocol, **connection} + ) + except NoDeviceError: + errors["base"] = "service_unavailable" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception during AlarmDecoder setup") + errors["base"] = "unknown" + + if self.protocol == PROTOCOL_SOCKET: + schema = vol.Schema( + { + vol.Required(CONF_HOST, default=DEFAULT_DEVICE_HOST): str, + vol.Required(CONF_PORT, default=DEFAULT_DEVICE_PORT): int, + } + ) + if self.protocol == PROTOCOL_SERIAL: + schema = vol.Schema( + { + vol.Required(CONF_DEVICE_PATH, default=DEFAULT_DEVICE_PATH): str, + vol.Required(CONF_DEVICE_BAUD, default=DEFAULT_DEVICE_BAUD): int, + } + ) + + return self.async_show_form( + step_id="protocol", + data_schema=schema, + errors=errors, + ) + + +class AlarmDecoderOptionsFlowHandler(config_entries.OptionsFlow): + """Handle AlarmDecoder options.""" + + def __init__(self, config_entry: config_entries.ConfigEntry): + """Initialize AlarmDecoder options flow.""" + self.arm_options = config_entry.options.get(OPTIONS_ARM, DEFAULT_ARM_OPTIONS) + self.zone_options = config_entry.options.get( + OPTIONS_ZONES, DEFAULT_ZONE_OPTIONS + ) + self.selected_zone = None + + async def async_step_init(self, user_input=None): + """Manage the options.""" + if user_input is not None: + if user_input[EDIT_KEY] == EDIT_SETTINGS: + return await self.async_step_arm_settings() + if user_input[EDIT_KEY] == EDIT_ZONES: + return await self.async_step_zone_select() + + return self.async_show_form( + step_id="init", + data_schema=vol.Schema( + { + vol.Required(EDIT_KEY, default=EDIT_SETTINGS): vol.In( + [EDIT_SETTINGS, EDIT_ZONES] + ) + }, + ), + ) + + async def async_step_arm_settings(self, user_input=None): + """Arming options form.""" + if user_input is not None: + return self.async_create_entry( + title="", + data={OPTIONS_ARM: user_input, OPTIONS_ZONES: self.zone_options}, + ) + + return self.async_show_form( + step_id="arm_settings", + data_schema=vol.Schema( + { + vol.Optional( + CONF_ALT_NIGHT_MODE, + default=self.arm_options[CONF_ALT_NIGHT_MODE], + ): bool, + vol.Optional( + CONF_AUTO_BYPASS, default=self.arm_options[CONF_AUTO_BYPASS] + ): bool, + vol.Optional( + CONF_CODE_ARM_REQUIRED, + default=self.arm_options[CONF_CODE_ARM_REQUIRED], + ): bool, + }, + ), + ) + + async def async_step_zone_select(self, user_input=None): + """Zone selection form.""" + errors = _validate_zone_input(user_input) + + if user_input is not None and not errors: + self.selected_zone = str( + int(user_input[CONF_ZONE_NUMBER]) + ) # remove leading zeros + return await self.async_step_zone_details() + + return self.async_show_form( + step_id="zone_select", + data_schema=vol.Schema({vol.Required(CONF_ZONE_NUMBER): str}), + errors=errors, + ) + + async def async_step_zone_details(self, user_input=None): + """Zone details form.""" + errors = _validate_zone_input(user_input) + + if user_input is not None and not errors: + zone_options = self.zone_options.copy() + zone_id = self.selected_zone + zone_options[zone_id] = _fix_input_types(user_input) + + # Delete zone entry if zone_name is omitted + if CONF_ZONE_NAME not in zone_options[zone_id]: + zone_options.pop(zone_id) + + return self.async_create_entry( + title="", + data={OPTIONS_ARM: self.arm_options, OPTIONS_ZONES: zone_options}, + ) + + existing_zone_settings = self.zone_options.get(self.selected_zone, {}) + + return self.async_show_form( + step_id="zone_details", + description_placeholders={CONF_ZONE_NUMBER: self.selected_zone}, + data_schema=vol.Schema( + { + vol.Optional( + CONF_ZONE_NAME, + description={ + "suggested_value": existing_zone_settings.get( + CONF_ZONE_NAME + ) + }, + ): str, + vol.Optional( + CONF_ZONE_TYPE, + default=existing_zone_settings.get( + CONF_ZONE_TYPE, DEFAULT_ZONE_TYPE + ), + ): vol.In(DEVICE_CLASSES), + vol.Optional( + CONF_ZONE_RFID, + description={ + "suggested_value": existing_zone_settings.get( + CONF_ZONE_RFID + ) + }, + ): str, + vol.Optional( + CONF_ZONE_LOOP, + description={ + "suggested_value": existing_zone_settings.get( + CONF_ZONE_LOOP + ) + }, + ): str, + vol.Optional( + CONF_RELAY_ADDR, + description={ + "suggested_value": existing_zone_settings.get( + CONF_RELAY_ADDR + ) + }, + ): str, + vol.Optional( + CONF_RELAY_CHAN, + description={ + "suggested_value": existing_zone_settings.get( + CONF_RELAY_CHAN + ) + }, + ): str, + } + ), + errors=errors, + ) + + +def _validate_zone_input(zone_input): + if not zone_input: + return {} + errors = {} + + # CONF_RELAY_ADDR & CONF_RELAY_CHAN are inclusive + if (CONF_RELAY_ADDR in zone_input and CONF_RELAY_CHAN not in zone_input) or ( + CONF_RELAY_ADDR not in zone_input and CONF_RELAY_CHAN in zone_input + ): + errors["base"] = "relay_inclusive" + + # The following keys must be int + for key in [CONF_ZONE_NUMBER, CONF_ZONE_LOOP, CONF_RELAY_ADDR, CONF_RELAY_CHAN]: + if key in zone_input: + try: + int(zone_input[key]) + except ValueError: + errors[key] = "int" + + # CONF_ZONE_LOOP depends on CONF_ZONE_RFID + if CONF_ZONE_LOOP in zone_input and CONF_ZONE_RFID not in zone_input: + errors[CONF_ZONE_LOOP] = "loop_rfid" + + # CONF_ZONE_LOOP must be 1-4 + if ( + CONF_ZONE_LOOP in zone_input + and zone_input[CONF_ZONE_LOOP].isdigit() + and int(zone_input[CONF_ZONE_LOOP]) not in list(range(1, 5)) + ): + errors[CONF_ZONE_LOOP] = "loop_range" + + return errors + + +def _fix_input_types(zone_input): + """Convert necessary keys to int. + + Since ConfigFlow inputs of type int cannot default to an empty string, we collect the values below as + strings and then convert them to ints. + """ + + for key in [CONF_ZONE_LOOP, CONF_RELAY_ADDR, CONF_RELAY_CHAN]: + if key in zone_input: + zone_input[key] = int(zone_input[key]) + + return zone_input + + +def _device_already_added(current_entries, user_input, protocol): + """Determine if entry has already been added to HA.""" + user_host = user_input.get(CONF_HOST) + user_port = user_input.get(CONF_PORT) + user_path = user_input.get(CONF_DEVICE_PATH) + user_baud = user_input.get(CONF_DEVICE_BAUD) + + for entry in current_entries: + entry_host = entry.data.get(CONF_HOST) + entry_port = entry.data.get(CONF_PORT) + entry_path = entry.data.get(CONF_DEVICE_PATH) + entry_baud = entry.data.get(CONF_DEVICE_BAUD) + + if protocol == PROTOCOL_SOCKET: + if user_host == entry_host and user_port == entry_port: + return True + + if protocol == PROTOCOL_SERIAL: + if user_baud == entry_baud and user_path == entry_path: + return True + + return False diff --git a/homeassistant/components/alarmdecoder/const.py b/homeassistant/components/alarmdecoder/const.py new file mode 100644 index 00000000000000..f1bfb66f0d468f --- /dev/null +++ b/homeassistant/components/alarmdecoder/const.py @@ -0,0 +1,49 @@ +"""Constants for the AlarmDecoder component.""" + +CONF_ALT_NIGHT_MODE = "alt_night_mode" +CONF_AUTO_BYPASS = "auto_bypass" +CONF_CODE_ARM_REQUIRED = "code_arm_required" +CONF_DEVICE_BAUD = "device_baudrate" +CONF_DEVICE_PATH = "device_path" +CONF_RELAY_ADDR = "zone_relayaddr" +CONF_RELAY_CHAN = "zone_relaychan" +CONF_ZONE_LOOP = "zone_loop" +CONF_ZONE_NAME = "zone_name" +CONF_ZONE_NUMBER = "zone_number" +CONF_ZONE_RFID = "zone_rfid" +CONF_ZONE_TYPE = "zone_type" + +DATA_AD = "alarmdecoder" +DATA_REMOVE_STOP_LISTENER = "rm_stop_listener" +DATA_REMOVE_UPDATE_LISTENER = "rm_update_listener" +DATA_RESTART = "restart" + +DEFAULT_ALT_NIGHT_MODE = False +DEFAULT_AUTO_BYPASS = False +DEFAULT_CODE_ARM_REQUIRED = True +DEFAULT_DEVICE_BAUD = 115200 +DEFAULT_DEVICE_HOST = "alarmdecoder" +DEFAULT_DEVICE_PATH = "/dev/ttyUSB0" +DEFAULT_DEVICE_PORT = 10000 +DEFAULT_ZONE_TYPE = "window" + +DEFAULT_ARM_OPTIONS = { + CONF_ALT_NIGHT_MODE: DEFAULT_ALT_NIGHT_MODE, + CONF_AUTO_BYPASS: DEFAULT_AUTO_BYPASS, + CONF_CODE_ARM_REQUIRED: DEFAULT_CODE_ARM_REQUIRED, +} +DEFAULT_ZONE_OPTIONS = {} + +DOMAIN = "alarmdecoder" + +OPTIONS_ARM = "arm_options" +OPTIONS_ZONES = "zone_options" + +PROTOCOL_SERIAL = "serial" +PROTOCOL_SOCKET = "socket" + +SIGNAL_PANEL_MESSAGE = "alarmdecoder.panel_message" +SIGNAL_REL_MESSAGE = "alarmdecoder.rel_message" +SIGNAL_RFX_MESSAGE = "alarmdecoder.rfx_message" +SIGNAL_ZONE_FAULT = "alarmdecoder.zone_fault" +SIGNAL_ZONE_RESTORE = "alarmdecoder.zone_restore" diff --git a/homeassistant/components/alarmdecoder/manifest.json b/homeassistant/components/alarmdecoder/manifest.json index ea2c3fb01c83f5..1697858718d273 100644 --- a/homeassistant/components/alarmdecoder/manifest.json +++ b/homeassistant/components/alarmdecoder/manifest.json @@ -3,5 +3,6 @@ "name": "AlarmDecoder", "documentation": "https://www.home-assistant.io/integrations/alarmdecoder", "requirements": ["adext==0.3"], - "codeowners": ["@ajschmidt8"] + "codeowners": ["@ajschmidt8"], + "config_flow": true } diff --git a/homeassistant/components/alarmdecoder/sensor.py b/homeassistant/components/alarmdecoder/sensor.py index 96e5feb532d313..4ce953af1d4fd6 100644 --- a/homeassistant/components/alarmdecoder/sensor.py +++ b/homeassistant/components/alarmdecoder/sensor.py @@ -1,26 +1,29 @@ """Support for AlarmDecoder sensors (Shows Panel Display).""" import logging +from homeassistant.config_entries import ConfigEntry from homeassistant.helpers.entity import Entity +from homeassistant.helpers.typing import HomeAssistantType -from . import SIGNAL_PANEL_MESSAGE +from .const import SIGNAL_PANEL_MESSAGE _LOGGER = logging.getLogger(__name__) -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up for AlarmDecoder sensor devices.""" - _LOGGER.debug("AlarmDecoderSensor: setup_platform") +async def async_setup_entry( + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities +): + """Set up for AlarmDecoder sensor.""" - device = AlarmDecoderSensor(hass) - - add_entities([device]) + entity = AlarmDecoderSensor() + async_add_entities([entity]) + return True class AlarmDecoderSensor(Entity): """Representation of an AlarmDecoder keypad.""" - def __init__(self, hass): + def __init__(self): """Initialize the alarm panel.""" self._display = "" self._state = None diff --git a/homeassistant/components/alarmdecoder/services.yaml b/homeassistant/components/alarmdecoder/services.yaml index bcf5a927713aac..37c7ddf210c1c2 100644 --- a/homeassistant/components/alarmdecoder/services.yaml +++ b/homeassistant/components/alarmdecoder/services.yaml @@ -1,6 +1,9 @@ alarm_keypress: description: Send custom keypresses to the alarm. fields: + entity_id: + description: Name of alarm control panel to deliver keypress. + example: "alarm_control_panel.main" keypress: description: "String to send to the alarm panel." example: "*71" @@ -8,6 +11,9 @@ alarm_keypress: alarm_toggle_chime: description: Send the alarm the toggle chime command. fields: + entity_id: + description: Name of alarm control panel to toggle chime. + example: "alarm_control_panel.main" code: description: A required code to toggle the alarm control panel chime with. example: 1234 diff --git a/homeassistant/components/alarmdecoder/strings.json b/homeassistant/components/alarmdecoder/strings.json new file mode 100644 index 00000000000000..ed250b92b98840 --- /dev/null +++ b/homeassistant/components/alarmdecoder/strings.json @@ -0,0 +1,72 @@ +{ + "config": { + "step": { + "user": { + "title": "Choose AlarmDecoder Protocol", + "data": { + "protocol": "Protocol" + } + }, + "protocol": { + "title": "Configure connection settings", + "data": { + "host": "[%key:common::config_flow::data::host%]", + "port": "[%key:common::config_flow::data::port%]", + "device_baudrate": "Device Baud Rate", + "device_path": "Device Path" + } + } + }, + "error": { + "service_unavailable": "[%key:common::config_flow::error::cannot_connect%]" + }, + "create_entry": { "default": "Successfully connected to AlarmDecoder." }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + } + }, + "options": { + "step": { + "init": { + "title": "Configure AlarmDecoder", + "description": "What would you like to edit?", + "data": { + "edit_select": "Edit" + } + }, + "arm_settings": { + "title": "Configure AlarmDecoder", + "data": { + "auto_bypass": "Auto Bypass on Arm", + "code_arm_required": "Code Required for Arming", + "alt_night_mode": "Alternative Night Mode" + } + }, + "zone_select": { + "title": "Configure AlarmDecoder", + "description": "Enter the zone number you'd like to to add, edit, or remove.", + "data": { + "zone_number": "Zone Number" + } + }, + "zone_details": { + "title": "Configure AlarmDecoder", + "description": "Enter details for zone {zone_number}. To delete zone {zone_number}, leave Zone Name blank.", + "data": { + "zone_name": "Zone Name", + "zone_type": "Zone Type", + "zone_rfid": "RF Serial", + "zone_loop": "RF Loop", + "zone_relayaddr": "Relay Address", + "zone_relaychan": "Relay Channel" + } + } + }, + "error": { + "relay_inclusive": "Relay Address and Relay Channel are codependent and must be included together.", + "int": "The field below must be an integer.", + "loop_rfid": "RF Loop cannot be used without RF Serial.", + "loop_range": "RF Loop must be an integer between 1 and 4." + } + } +} diff --git a/homeassistant/components/alarmdecoder/translations/ca.json b/homeassistant/components/alarmdecoder/translations/ca.json new file mode 100644 index 00000000000000..3042a991c5f330 --- /dev/null +++ b/homeassistant/components/alarmdecoder/translations/ca.json @@ -0,0 +1,74 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat" + }, + "create_entry": { + "default": "S'ha connectat correctament amb AlarmDecoder." + }, + "error": { + "service_unavailable": "Ha fallat la connexi\u00f3" + }, + "step": { + "protocol": { + "data": { + "device_baudrate": "Velocitat, en baudis, del dispositiu", + "device_path": "Ruta del dispositiu", + "host": "Amfitri\u00f3", + "port": "Port" + }, + "title": "Configuraci\u00f3 dels par\u00e0metres de connexi\u00f3" + }, + "user": { + "data": { + "protocol": "Protocol" + }, + "title": "Selecciona el protocol d'AlarmDecoder" + } + } + }, + "options": { + "error": { + "int": "El camp seg\u00fcent ha de ser un nombre enter.", + "loop_range": "El bucle RF ha de ser un nombre enter entre 1 i 4.", + "loop_rfid": "El bucle RF no es pot utilitzar sense RF s\u00e8rie.", + "relay_inclusive": "L'adre\u00e7a i el canal de rel\u00e9 s\u00f3n codependents i s'han d'incloure junts." + }, + "step": { + "arm_settings": { + "data": { + "alt_night_mode": "Mode nocturn alternatiu", + "auto_bypass": "Bypass autom\u00e0tic en l'activaci\u00f3", + "code_arm_required": "Codi necessari per a l'activaci\u00f3" + }, + "title": "Configuraci\u00f3 d'AlarmDecoder" + }, + "init": { + "data": { + "edit_select": "Edita" + }, + "description": "Qu\u00e8 voldries editar?", + "title": "Configuraci\u00f3 d'AlarmDecoder" + }, + "zone_details": { + "data": { + "zone_loop": "Bucle RF", + "zone_name": "Nom de la zona", + "zone_relayaddr": "Adre\u00e7a del rel\u00e9", + "zone_relaychan": "Canal del rel\u00e9", + "zone_rfid": "RF s\u00e8rie", + "zone_type": "Tipus de zona" + }, + "description": "Introdueix els detalls de la zona {zone_number}. Per suprimir la zona {zone_number}, deixa el nom de la zona en blanc.", + "title": "Configuraci\u00f3 d'AlarmDecoder" + }, + "zone_select": { + "data": { + "zone_number": "N\u00famero de zona" + }, + "description": "Introdueix el n\u00famero de zona que vulguis afegir, editar o eliminar.", + "title": "Configuraci\u00f3 d'AlarmDecoder" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/alarmdecoder/translations/cs.json b/homeassistant/components/alarmdecoder/translations/cs.json new file mode 100644 index 00000000000000..b42e092bb47804 --- /dev/null +++ b/homeassistant/components/alarmdecoder/translations/cs.json @@ -0,0 +1,35 @@ +{ + "options": { + "step": { + "arm_settings": { + "title": "Konfigurovat AlarmDecoder" + }, + "init": { + "data": { + "edit_select": "Upravit" + }, + "description": "Co chcete upravit?", + "title": "Konfigurovat AlarmDecoder" + }, + "zone_details": { + "data": { + "zone_loop": "RF Loop", + "zone_name": "N\u00e1zev z\u00f3ny", + "zone_relayaddr": "Relay adresa", + "zone_relaychan": "Relay kan\u00e1l", + "zone_rfid": "RF Serial", + "zone_type": "Typ z\u00f3ny" + }, + "description": "Zadejte podrobnosti pro z\u00f3nu {zone_number}. Chcete-li odstranit z\u00f3nu {zone_number}, ponechejte n\u00e1zev z\u00f3ny pr\u00e1zdn\u00fd.", + "title": "Konfigurovat AlarmDecoder" + }, + "zone_select": { + "data": { + "zone_number": "\u010c\u00edslo z\u00f3ny" + }, + "description": "Zadejte \u010d\u00edslo z\u00f3ny, kterou chcete p\u0159idat, upravit nebo odstranit.", + "title": "Konfigurovat AlarmDecoder" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/alarmdecoder/translations/de.json b/homeassistant/components/alarmdecoder/translations/de.json new file mode 100644 index 00000000000000..69318f87b11106 --- /dev/null +++ b/homeassistant/components/alarmdecoder/translations/de.json @@ -0,0 +1,48 @@ +{ + "config": { + "error": { + "service_unavailable": "Verbindung konnte nicht hergestellt werden" + }, + "step": { + "protocol": { + "data": { + "host": "Host", + "port": "Port" + } + }, + "user": { + "data": { + "protocol": "Protokoll" + } + } + } + }, + "options": { + "step": { + "arm_settings": { + "data": { + "alt_night_mode": "Alternativer Nachtmodus" + } + }, + "init": { + "data": { + "edit_select": "Bearbeiten" + }, + "description": "Was m\u00f6chtest du bearbeiten?" + }, + "zone_details": { + "data": { + "zone_name": "Zonenname", + "zone_relayaddr": "Relais-Adresse", + "zone_type": "Zonentyp" + } + }, + "zone_select": { + "data": { + "zone_number": "Zonennummer" + }, + "description": "Geben Sie die Zonennummer ein, die Sie hinzuf\u00fcgen, bearbeiten oder entfernen m\u00f6chten." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/alarmdecoder/translations/el.json b/homeassistant/components/alarmdecoder/translations/el.json new file mode 100644 index 00000000000000..44197dbe5fb767 --- /dev/null +++ b/homeassistant/components/alarmdecoder/translations/el.json @@ -0,0 +1,74 @@ +{ + "config": { + "abort": { + "already_configured": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03b9\u03bd\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03bc\u03ad\u03bd\u03b7" + }, + "create_entry": { + "default": "\u0395\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf AlarmDecoder." + }, + "error": { + "service_unavailable": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, + "step": { + "protocol": { + "data": { + "device_baudrate": "\u03a1\u03c5\u03b8\u03bc\u03cc\u03c2 Baud \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2", + "device_path": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2", + "host": "\u0394\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1" + }, + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, + "user": { + "data": { + "protocol": "\u03a0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf" + }, + "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c0\u03c1\u03c9\u03c4\u03cc\u03ba\u03bf\u03bb\u03bb\u03bf AlarmDecoder" + } + } + }, + "options": { + "error": { + "int": "\u03a4\u03bf \u03c0\u03b1\u03c1\u03b1\u03ba\u03ac\u03c4\u03c9 \u03c0\u03b5\u03b4\u03af\u03bf \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03ba\u03ad\u03c1\u03b1\u03b9\u03bf\u03c2.", + "loop_range": "\u039f \u03b2\u03c1\u03cc\u03c7\u03bf\u03c2 RF \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03ba\u03ad\u03c1\u03b1\u03b9\u03bf\u03c2 \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03bc\u03b5\u03c4\u03b1\u03be\u03cd 1 \u03ba\u03b1\u03b9 4.", + "loop_rfid": "\u039f \u03b2\u03c1\u03cc\u03c7\u03bf\u03c2 RF \u03b4\u03b5\u03bd \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03c7\u03c9\u03c1\u03af\u03c2 \u03c4\u03bf \u03c3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc RF.", + "relay_inclusive": "\u0397 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03c1\u03b5\u03bb\u03ad \u03ba\u03b1\u03b9 \u03c4\u03bf \u03ba\u03b1\u03bd\u03ac\u03bb\u03b9 \u03b1\u03bd\u03b1\u03bc\u03b5\u03c4\u03ac\u03b4\u03bf\u03c3\u03b7\u03c2 \u03b5\u03be\u03b1\u03c1\u03c4\u03ce\u03bd\u03c4\u03b1\u03b9 \u03b1\u03c0\u03cc \u03c4\u03bf\u03bd \u03ba\u03ce\u03b4\u03b9\u03ba\u03b1 \u03ba\u03b1\u03b9 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c0\u03b5\u03c1\u03b9\u03bb\u03b1\u03bc\u03b2\u03ac\u03bd\u03bf\u03bd\u03c4\u03b1\u03b9 \u03bc\u03b1\u03b6\u03af." + }, + "step": { + "arm_settings": { + "data": { + "alt_night_mode": "\u0395\u03bd\u03b1\u03bb\u03bb\u03b1\u03ba\u03c4\u03b9\u03ba\u03ae \u03bd\u03c5\u03c7\u03c4\u03b5\u03c1\u03b9\u03bd\u03ae \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1", + "auto_bypass": "\u0391\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03c0\u03b1\u03c1\u03ac\u03ba\u03b1\u03bc\u03c8\u03b7 \u03c3\u03c4\u03bf\u03bd \u039f\u03c0\u03bb\u03b9\u03c3\u03bc\u03cc", + "code_arm_required": "\u0391\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03b3\u03b9\u03b1 \u03bf\u03c0\u03bb\u03b9\u03c3\u03bc\u03cc" + }, + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 AlarmDecoder" + }, + "init": { + "data": { + "edit_select": "\u0395\u03c0\u03b5\u03be\u03b5\u03c1\u03b3\u03b1\u03c3\u03af\u03b1" + }, + "description": "\u03a4\u03b9 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03c0\u03b5\u03be\u03b5\u03c1\u03b3\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5;", + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 AlarmDecoder" + }, + "zone_details": { + "data": { + "zone_loop": "\u0392\u03c1\u03cc\u03c7\u03bf\u03c2 RF", + "zone_name": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03b6\u03ce\u03bd\u03b7\u03c2", + "zone_relayaddr": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03c1\u03b5\u03bb\u03ad", + "zone_relaychan": "\u039a\u03b1\u03bd\u03ac\u03bb\u03b9 \u03c1\u03b5\u03bb\u03ad", + "zone_rfid": "\u03a3\u03b5\u03b9\u03c1\u03b9\u03b1\u03ba\u03cc RF", + "zone_type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03b6\u03ce\u03bd\u03b7\u03c2" + }, + "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03bb\u03b5\u03c0\u03c4\u03bf\u03bc\u03ad\u03c1\u03b5\u03b9\u03b5\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03b6\u03ce\u03bd\u03b7 {zone_number}. \u0393\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03b3\u03c1\u03ac\u03c8\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03b6\u03ce\u03bd\u03b7 {zone_number}, \u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03ba\u03b5\u03bd\u03cc \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03b6\u03ce\u03bd\u03b7\u03c2.", + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 AlarmDecoder" + }, + "zone_select": { + "data": { + "zone_number": "\u0391\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03b6\u03ce\u03bd\u03b7\u03c2" + }, + "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03b1\u03c1\u03b9\u03b8\u03bc\u03cc \u03b6\u03ce\u03bd\u03b7\u03c2 \u03c0\u03bf\u03c5 \u03b8\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5, \u03bd\u03b1 \u03b5\u03c0\u03b5\u03be\u03b5\u03c1\u03b3\u03b1\u03c3\u03c4\u03b5\u03af\u03c4\u03b5 \u03ae \u03bd\u03b1 \u03ba\u03b1\u03c4\u03b1\u03c1\u03b3\u03ae\u03c3\u03b5\u03c4\u03b5.", + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 AlarmDecoder" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/alarmdecoder/translations/en.json b/homeassistant/components/alarmdecoder/translations/en.json new file mode 100644 index 00000000000000..66301414cc8c0a --- /dev/null +++ b/homeassistant/components/alarmdecoder/translations/en.json @@ -0,0 +1,74 @@ +{ + "config": { + "abort": { + "already_configured": "Device is already configured" + }, + "create_entry": { + "default": "Successfully connected to AlarmDecoder." + }, + "error": { + "service_unavailable": "Failed to connect" + }, + "step": { + "protocol": { + "data": { + "device_baudrate": "Device Baud Rate", + "device_path": "Device Path", + "host": "Host", + "port": "Port" + }, + "title": "Configure connection settings" + }, + "user": { + "data": { + "protocol": "Protocol" + }, + "title": "Choose AlarmDecoder Protocol" + } + } + }, + "options": { + "error": { + "int": "The field below must be an integer.", + "loop_range": "RF Loop must be an integer between 1 and 4.", + "loop_rfid": "RF Loop cannot be used without RF Serial.", + "relay_inclusive": "Relay Address and Relay Channel are codependent and must be included together." + }, + "step": { + "arm_settings": { + "data": { + "alt_night_mode": "Alternative Night Mode", + "auto_bypass": "Auto Bypass on Arm", + "code_arm_required": "Code Required for Arming" + }, + "title": "Configure AlarmDecoder" + }, + "init": { + "data": { + "edit_select": "Edit" + }, + "description": "What would you like to edit?", + "title": "Configure AlarmDecoder" + }, + "zone_details": { + "data": { + "zone_loop": "RF Loop", + "zone_name": "Zone Name", + "zone_relayaddr": "Relay Address", + "zone_relaychan": "Relay Channel", + "zone_rfid": "RF Serial", + "zone_type": "Zone Type" + }, + "description": "Enter details for zone {zone_number}. To delete zone {zone_number}, leave Zone Name blank.", + "title": "Configure AlarmDecoder" + }, + "zone_select": { + "data": { + "zone_number": "Zone Number" + }, + "description": "Enter the zone number you'd like to to add, edit, or remove.", + "title": "Configure AlarmDecoder" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/alarmdecoder/translations/es.json b/homeassistant/components/alarmdecoder/translations/es.json new file mode 100644 index 00000000000000..5b4670306daddc --- /dev/null +++ b/homeassistant/components/alarmdecoder/translations/es.json @@ -0,0 +1,74 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo AlarmDecoder ya est\u00e1 configurado." + }, + "create_entry": { + "default": "Conectado con \u00e9xito a AlarmDecoder." + }, + "error": { + "service_unavailable": "No se pudo conectar" + }, + "step": { + "protocol": { + "data": { + "device_baudrate": "Velocidad en baudios del dispositivo", + "device_path": "Ruta del dispositivo", + "host": "Host", + "port": "Puerto" + }, + "title": "Configurar los ajustes de conexi\u00f3n" + }, + "user": { + "data": { + "protocol": "Protocolo" + }, + "title": "Elige el protocolo del AlarmDecoder" + } + } + }, + "options": { + "error": { + "int": "El campo siguiente debe ser un n\u00famero entero.", + "loop_range": "El bucle RF debe ser un n\u00famero entero entre 1 y 4.", + "loop_rfid": "El bucle de RF no puede utilizarse sin el serie RF.", + "relay_inclusive": "La direcci\u00f3n de retransmisi\u00f3n y el canal de retransmisi\u00f3n son codependientes y deben incluirse a la vez." + }, + "step": { + "arm_settings": { + "data": { + "alt_night_mode": "Modo noche alternativo", + "auto_bypass": "Desv\u00edo autom\u00e1tico al armar", + "code_arm_required": "C\u00f3digo requerido para el armado" + }, + "title": "Configurar AlarmDecoder" + }, + "init": { + "data": { + "edit_select": "Editar" + }, + "description": "\u00bfQu\u00e9 te gustar\u00eda editar?", + "title": "Configurar AlarmDecoder" + }, + "zone_details": { + "data": { + "zone_loop": "Bucle RF", + "zone_name": "Nombre de zona", + "zone_relayaddr": "Direcci\u00f3n de retransmisi\u00f3n", + "zone_relaychan": "Canal de retransmisi\u00f3n", + "zone_rfid": "Serie RF", + "zone_type": "Tipo de zona" + }, + "description": "Introduce los detalles para la zona {zona_number}. Para borrar la zona {zone_number}, deja el nombre de la zona en blanco.", + "title": "Configurar AlarmDecoder" + }, + "zone_select": { + "data": { + "zone_number": "N\u00famero de zona" + }, + "description": "Introduce el n\u00famero de zona que deseas a\u00f1adir, editar o eliminar.", + "title": "Configurar AlarmDecoder" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/alarmdecoder/translations/et.json b/homeassistant/components/alarmdecoder/translations/et.json new file mode 100644 index 00000000000000..9fb7fd7fb269c8 --- /dev/null +++ b/homeassistant/components/alarmdecoder/translations/et.json @@ -0,0 +1,52 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud" + }, + "step": { + "user": { + "data": { + "protocol": "Protokoll" + } + } + } + }, + "options": { + "step": { + "arm_settings": { + "data": { + "alt_night_mode": "Alternatiivne \u00f6\u00f6re\u017eiim", + "auto_bypass": "Automaatne m\u00f6\u00f6daviik valvestamisel", + "code_arm_required": "Valvestamise kood" + }, + "title": "Seadista AlarmDecoder" + }, + "init": { + "data": { + "edit_select": "Muuda" + }, + "description": "Mida Te soovite muuta?", + "title": "Seadista AlarmDecoder" + }, + "zone_details": { + "data": { + "zone_loop": "RF silmus", + "zone_name": "Ala nimi", + "zone_relayaddr": "Relee aadress", + "zone_relaychan": "Relee kanalinumber", + "zone_rfid": "RF jada\u00fchendus", + "zone_type": "Ala t\u00fc\u00fcp" + }, + "description": "Sisestage ala {zone_number} \u00fcksikasjad. Ala {zone_number} kustutamiseks j\u00e4tke ala nimi t\u00fchjaks.", + "title": "Seadista AlarmDecoder" + }, + "zone_select": { + "data": { + "zone_number": "Ala number" + }, + "description": "Sisestage ala number mida soovite lisada, muuta v\u00f5i eemaldada.", + "title": "Seadista AlarmDecoder" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/alarmdecoder/translations/fr.json b/homeassistant/components/alarmdecoder/translations/fr.json new file mode 100644 index 00000000000000..c48cf00cdede4d --- /dev/null +++ b/homeassistant/components/alarmdecoder/translations/fr.json @@ -0,0 +1,74 @@ +{ + "config": { + "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + }, + "create_entry": { + "default": "Connexion r\u00e9ussie \u00e0 AlarmDecoder." + }, + "error": { + "service_unavailable": "\u00c9chec de connexion" + }, + "step": { + "protocol": { + "data": { + "device_baudrate": "D\u00e9bit en bauds de l'appareil", + "device_path": "Chemin du p\u00e9riph\u00e9rique", + "host": "H\u00f4te", + "port": "Port" + }, + "title": "Configurer les param\u00e8tres de connexion" + }, + "user": { + "data": { + "protocol": "Protocole" + }, + "title": "Choisissez le protocole AlarmDecoder" + } + } + }, + "options": { + "error": { + "int": "Le champ ci-dessous doit \u00eatre un entier.", + "loop_range": "La boucle RF doit \u00eatre un entier compris entre 1 et 4.", + "loop_rfid": "La boucle RF ne peut pas \u00eatre utilis\u00e9e sans s\u00e9rie RF.", + "relay_inclusive": "L'adresse de relais et le canal de relais d\u00e9pendent du codage et doivent \u00eatre inclus ensemble." + }, + "step": { + "arm_settings": { + "data": { + "alt_night_mode": "Mode nuit alternatif", + "auto_bypass": "Bypass automatique \u00e0 l'armement", + "code_arm_required": "Code requis pour l'armement" + }, + "title": "Configurer AlarmDecoder" + }, + "init": { + "data": { + "edit_select": "Modifier" + }, + "description": "Que voulez-vous modifier?", + "title": "Configurer AlarmDecoder" + }, + "zone_details": { + "data": { + "zone_loop": "Boucle RF", + "zone_name": "Nom de zone", + "zone_relayaddr": "Adresse de relais", + "zone_relaychan": "Canal de relais", + "zone_rfid": "RF S\u00e9rie", + "zone_type": "Type de zone" + }, + "description": "Entrez les d\u00e9tails de la zone {zone_number} . Pour supprimer la zone {zone_number} , laissez le nom de zone vide.", + "title": "Configurer AlarmDecoder" + }, + "zone_select": { + "data": { + "zone_number": "Num\u00e9ro de zone" + }, + "description": "Saisissez le num\u00e9ro de zone que vous souhaitez ajouter, modifier ou supprimer.", + "title": "Configurer AlarmDecoder" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/alarmdecoder/translations/it.json b/homeassistant/components/alarmdecoder/translations/it.json new file mode 100644 index 00000000000000..ca5bf39cefde9b --- /dev/null +++ b/homeassistant/components/alarmdecoder/translations/it.json @@ -0,0 +1,74 @@ +{ + "config": { + "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + }, + "create_entry": { + "default": "Collegato con successo ad AlarmDecoder." + }, + "error": { + "service_unavailable": "Impossibile connettersi" + }, + "step": { + "protocol": { + "data": { + "device_baudrate": "Velocit\u00e0 di trasmissione del dispositivo", + "device_path": "Percorso del dispositivo", + "host": "Host", + "port": "Porta" + }, + "title": "Configurare le impostazioni di connessione" + }, + "user": { + "data": { + "protocol": "Protocollo" + }, + "title": "Scegliere il protocollo AlarmDecoder" + } + } + }, + "options": { + "error": { + "int": "Il campo sottostante deve essere un numero intero.", + "loop_range": "Il Ciclo RF deve essere un numero intero compreso tra 1 e 4.", + "loop_rfid": "Il Ciclo RF non pu\u00f2 essere utilizzato senza il Seriale RF ", + "relay_inclusive": "L'indirizzo del rel\u00e8 e il canale del rel\u00e8 sono codipendenti e devono essere inclusi insieme." + }, + "step": { + "arm_settings": { + "data": { + "alt_night_mode": "Modalit\u00e0 notturna alternativa", + "auto_bypass": "Bypass automatico all'attivazione", + "code_arm_required": "Codice richiesto per l'attivazione" + }, + "title": "Configurare AlarmDecoder" + }, + "init": { + "data": { + "edit_select": "Modifica" + }, + "description": "Cosa vorresti modificare?", + "title": "Configurare AlarmDecoder" + }, + "zone_details": { + "data": { + "zone_loop": "Ciclo RF", + "zone_name": "Nome zona", + "zone_relayaddr": "Indirizzo rel\u00e8", + "zone_relaychan": "Canale rel\u00e8", + "zone_rfid": "Seriale RF", + "zone_type": "Tipo di zona" + }, + "description": "Immettere i dettagli per la zona {zone_number}. Per eliminare la zona {zone_number}, lasciare vuoto il campo Nome zona.", + "title": "Configurare AlarmDecoder" + }, + "zone_select": { + "data": { + "zone_number": "Numero di zona" + }, + "description": "Immettere il numero di zona che si desidera aggiungere, modificare o rimuovere.", + "title": "Configurare AlarmDecoder" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/alarmdecoder/translations/ko.json b/homeassistant/components/alarmdecoder/translations/ko.json new file mode 100644 index 00000000000000..c4038572ece733 --- /dev/null +++ b/homeassistant/components/alarmdecoder/translations/ko.json @@ -0,0 +1,74 @@ +{ + "config": { + "abort": { + "already_configured": "\uc7a5\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4." + }, + "create_entry": { + "default": "AlarmDecoder\uc5d0 \uc131\uacf5\uc801\uc73c\ub85c \uc5f0\uacb0\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + }, + "error": { + "service_unavailable": "\uc5f0\uacb0 \uc2e4\ud328" + }, + "step": { + "protocol": { + "data": { + "device_baudrate": "\uc7a5\uce58 \uc804\uc1a1 \uc18d\ub3c4", + "device_path": "\uc7a5\uce58 \uacbd\ub85c", + "host": "\ud638\uc2a4\ud2b8", + "port": "\ud3ec\ud2b8" + }, + "title": "\uc5f0\uacb0 \uc124\uc815 \uad6c\uc131" + }, + "user": { + "data": { + "protocol": "\ud504\ub85c\ud1a0\ucf5c" + }, + "title": "AlarmDecoder \ud504\ub85c\ud1a0\ucf5c \uc120\ud0dd" + } + } + }, + "options": { + "error": { + "int": "\uc544\ub798 \ud544\ub4dc\ub294 \uc815\uc218\uc5ec\uc57c \ud569\ub2c8\ub2e4.", + "loop_range": "RF \ub8e8\ud504\ub294 1\uc5d0\uc11c 4 \uc0ac\uc774\uc758 \uc815\uc218\uc5ec\uc57c \ud569\ub2c8\ub2e4.", + "loop_rfid": "RF \ub8e8\ud504\ub294 RF \uc2dc\ub9ac\uc5bc\uc5c6\uc774 \uc0ac\uc6a9\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", + "relay_inclusive": "\ub9b4\ub808\uc774 \uc8fc\uc18c\uc640 \ub9b4\ub808\uc774 \ucc44\ub110\uc740 \uc11c\ub85c \uc758\uc874\uc801\uc774\uba70 \ud568\uaed8 \ud3ec\ud568\ub418\uc5b4\uc57c\ud569\ub2c8\ub2e4." + }, + "step": { + "arm_settings": { + "data": { + "alt_night_mode": "\ub300\uccb4 \uc57c\uac04 \ubaa8\ub4dc", + "auto_bypass": "\uacbd\ube44\uc911 \uc790\ub3d9 \uc6b0\ud68c", + "code_arm_required": "\uacbd\ube44\uc5d0 \ud544\uc694\ud55c \ucf54\ub4dc" + }, + "title": "AlarmDecoder \uad6c\uc131" + }, + "init": { + "data": { + "edit_select": "\ud3b8\uc9d1" + }, + "description": "\ubb34\uc5c7\uc744 \ud3b8\uc9d1 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "AlarmDecoder \uad6c\uc131" + }, + "zone_details": { + "data": { + "zone_loop": "RF \ub8e8\ud504", + "zone_name": "\uc601\uc5ed \uc774\ub984", + "zone_relayaddr": "\ub9b4\ub808\uc774 \uc8fc\uc18c", + "zone_relaychan": "\ub9b4\ub808\uc774 \ucc44\ub110", + "zone_rfid": "RF \uc2dc\ub9ac\uc5bc", + "zone_type": "\uc601\uc5ed \uc720\ud615" + }, + "description": "{zone_number} \uc601\uc5ed\uc5d0 \ub300\ud55c \uc138\ubd80 \uc815\ubcf4\ub97c \uc785\ub825\ud569\ub2c8\ub2e4. {zone_number} \uc601\uc5ed\uc744 \uc0ad\uc81c\ud558\ub824\uba74 \uc601\uc5ed \uc774\ub984\uc744 \ube44\uc6cc \ub461\ub2c8\ub2e4.", + "title": "AlarmDecoder \uad6c\uc131" + }, + "zone_select": { + "data": { + "zone_number": "\uad6c\uc5ed \ubc88\ud638" + }, + "description": "\ucd94\uac00, \ud3b8\uc9d1 \ub610\ub294 \uc81c\uac70\ud560 \uc601\uc5ed \ubc88\ud638\ub97c \uc785\ub825\ud569\ub2c8\ub2e4.", + "title": "AlarmDecoder \uad6c\uc131" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/alarmdecoder/translations/lb.json b/homeassistant/components/alarmdecoder/translations/lb.json new file mode 100644 index 00000000000000..082e279c764d6e --- /dev/null +++ b/homeassistant/components/alarmdecoder/translations/lb.json @@ -0,0 +1,72 @@ +{ + "config": { + "abort": { + "already_configured": "Apparat ass scho konfigur\u00e9iert" + }, + "create_entry": { + "default": "Erfollegr\u00e4ich mat Alarmdecoder verbonnen." + }, + "error": { + "service_unavailable": "Feeler beim verbannen" + }, + "step": { + "protocol": { + "data": { + "device_baudrate": "Apparat Baudrate", + "device_path": "Pad vum Apparat", + "host": "Host", + "port": "Port" + }, + "title": "Verbindung's Optioune konfigur\u00e9ieren" + }, + "user": { + "data": { + "protocol": "Protokoll" + }, + "title": "Alarmdecoder Protokoll auswielen" + } + } + }, + "options": { + "error": { + "int": "D'Feld hei \u00ebnnen muss eng ganz Zuel sinn.", + "relay_inclusive": "Relais Adress a Relais Kanal sin vuneneen ofh\u00e4ngeg a musse mat abegraff sinn." + }, + "step": { + "arm_settings": { + "data": { + "alt_night_mode": "Alternative Nuecht Modus", + "auto_bypass": "Auto Bypass beim aktiv\u00e9ieren", + "code_arm_required": "Code erfuerderlech fir d'Aktiv\u00e9ierung" + }, + "title": "AlarmDecoder konfigur\u00e9ieren" + }, + "init": { + "data": { + "edit_select": "\u00c4nneren" + }, + "description": "Wat w\u00eblls du \u00e4nneren?", + "title": "AlarmDecoder konfigur\u00e9ieren" + }, + "zone_details": { + "data": { + "zone_loop": "RF Schleef", + "zone_name": "Numm vun der Zone", + "zone_relayaddr": "Relais Adresse", + "zone_relaychan": "Relais Kanal", + "zone_rfid": "RF Serielle", + "zone_type": "Type vun der Zone" + }, + "description": "G\u00ebff Detailer fir Zone {zone_number} an. Fir Zone {zone_number} ze l\u00e4schen, loss den Numm vun der Zone eidel.", + "title": "AlarmDecoder konfigur\u00e9ieren" + }, + "zone_select": { + "data": { + "zone_number": "Zone Nummer" + }, + "description": "G\u00ebff d'Zonennummer an d\u00e9is Du w\u00eblls b\u00e4isetzen, \u00e4nneren oder l\u00e4schen.", + "title": "AlarmDecoder konfigur\u00e9ieren" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/alarmdecoder/translations/nl.json b/homeassistant/components/alarmdecoder/translations/nl.json new file mode 100644 index 00000000000000..6091d4c4bd717f --- /dev/null +++ b/homeassistant/components/alarmdecoder/translations/nl.json @@ -0,0 +1,73 @@ +{ + "config": { + "abort": { + "already_configured": "AlarmDecoder-apparaat is al geconfigureerd." + }, + "create_entry": { + "default": "Succesvol verbonden met AlarmDecoder." + }, + "error": { + "service_unavailable": "Kon niet verbinden" + }, + "step": { + "protocol": { + "data": { + "device_baudrate": "Baudrate van apparaat", + "device_path": "Apparaatpad", + "host": "Host", + "port": "Poort" + }, + "title": "Configureer de verbindingsinstellingen" + }, + "user": { + "data": { + "protocol": "Protocol" + }, + "title": "Kies AlarmDecoder Protocol" + } + } + }, + "options": { + "error": { + "int": "Het onderstaande veld moet een geheel getal zijn.", + "loop_range": "RF Lus moet een geheel getal zijn tussen 1 en 4.", + "loop_rfid": "RF Lus kan niet worden gebruikt zonder RF Serieel.", + "relay_inclusive": "Het relais-adres en het relais-kanaal zijn codeafhankelijk en moeten samen worden opgenomen." + }, + "step": { + "arm_settings": { + "data": { + "alt_night_mode": "Alternatieve nachtmodus", + "auto_bypass": "Automatische bypass bij inschakelen", + "code_arm_required": "Code vereist voor inschakelen" + }, + "title": "Configureer AlarmDecoder" + }, + "init": { + "data": { + "edit_select": "Bewerk" + }, + "description": "Wat wilt u bewerken?", + "title": "Configureer AlarmDecoder" + }, + "zone_details": { + "data": { + "zone_loop": "RF Lus", + "zone_name": "Zone naam", + "zone_relayaddr": "Relais Adres", + "zone_relaychan": "Relais Kanaal", + "zone_rfid": "RF Serieel", + "zone_type": "Zone Type" + }, + "title": "Configureer AlarmDecoder" + }, + "zone_select": { + "data": { + "zone_number": "Zone nummer" + }, + "description": "Voer het zone nummer in dat u wilt toevoegen, bewerken of verwijderen.", + "title": "Configureer AlarmDecoder" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/alarmdecoder/translations/no.json b/homeassistant/components/alarmdecoder/translations/no.json new file mode 100644 index 00000000000000..36c5f21c60c7df --- /dev/null +++ b/homeassistant/components/alarmdecoder/translations/no.json @@ -0,0 +1,74 @@ +{ + "config": { + "abort": { + "already_configured": "Enheten er allerede konfigurert" + }, + "create_entry": { + "default": "Vellykket koblet til AlarmDecoder." + }, + "error": { + "service_unavailable": "Tilkobling mislyktes." + }, + "step": { + "protocol": { + "data": { + "device_baudrate": "Baud-hastighet for enhet", + "device_path": "Bane til enheten", + "host": "Vert", + "port": "Port" + }, + "title": "Konfigurer tilkoblingsinnstillinger" + }, + "user": { + "data": { + "protocol": "Protokoll" + }, + "title": "Velg AlarmDecoder Protokoll" + } + } + }, + "options": { + "error": { + "int": "Feltet nedenfor m\u00e5 v\u00e6re et helt tall.", + "loop_range": "RF Loop m\u00e5 v\u00e6re et heltall mellom 1 og 4.", + "loop_rfid": "RF Loop kan ikke brukes uten RF Serial.", + "relay_inclusive": "Rel\u00e9adresse og rel\u00e9kanal er kodeavhengige og m\u00e5 inkluderes sammen." + }, + "step": { + "arm_settings": { + "data": { + "alt_night_mode": "Alternativ nattmodus", + "auto_bypass": "Auto bypass p\u00e5 Arm", + "code_arm_required": "Kode kreves for tilkobling" + }, + "title": "Konfigurer AlarmDecoder" + }, + "init": { + "data": { + "edit_select": "Rediger" + }, + "description": "Hva \u00f8nsker du \u00e5 redigere?", + "title": "Konfigurer AlarmDecoder" + }, + "zone_details": { + "data": { + "zone_loop": "RF Loop", + "zone_name": "Sonenavn", + "zone_relayaddr": "Rel\u00e9 adresse", + "zone_relaychan": "Rel\u00e9 kanal", + "zone_rfid": "RF seriell", + "zone_type": "Sone type" + }, + "description": "Angi detaljer for sonen {zone_number}. Hvis du vil slette sonen {zone_number}, lar du Sonenavn st\u00e5 tomt.", + "title": "Konfigurer AlarmDecoder" + }, + "zone_select": { + "data": { + "zone_number": "Sone nummer" + }, + "description": "Angi sonenummeret du vil legge til, redigere eller fjerne.", + "title": "Konfigurer AlarmDecoder" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/alarmdecoder/translations/pl.json b/homeassistant/components/alarmdecoder/translations/pl.json new file mode 100644 index 00000000000000..38438b57de6f90 --- /dev/null +++ b/homeassistant/components/alarmdecoder/translations/pl.json @@ -0,0 +1,72 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" + }, + "create_entry": { + "default": "Pomy\u015blnie po\u0142\u0105czono z AlarmDecoder." + }, + "error": { + "service_unavailable": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" + }, + "step": { + "protocol": { + "data": { + "device_baudrate": "Szybko\u015b\u0107 transmisji urz\u0105dzenia (Baud Rate)", + "device_path": "\u015acie\u017cka urz\u0105dzenia", + "host": "Nazwa hosta lub adres IP", + "port": "Port" + }, + "title": "Konfiguracja ustawie\u0144 po\u0142\u0105czenia" + }, + "user": { + "data": { + "protocol": "Protok\u00f3\u0142" + }, + "title": "Wybierz protok\u00f3\u0142 AlarmDecodera" + } + } + }, + "options": { + "error": { + "int": "Poni\u017csze pole musi by\u0107 liczb\u0105 ca\u0142kowit\u0105.", + "loop_range": "P\u0119tla RF (RF Loop) musi by\u0107 liczb\u0105 ca\u0142kowit\u0105 od 1 do 4.", + "relay_inclusive": "Adres przeka\u017anika i kana\u0142 przeka\u017anika s\u0105 wsp\u00f3\u0142zale\u017cne i musz\u0105 by\u0107 zawarte razem." + }, + "step": { + "arm_settings": { + "data": { + "alt_night_mode": "Alternatywny tryb nocny", + "auto_bypass": "Automatyczne obej\u015bcie przy uzbrajaniu", + "code_arm_required": "Wymagaj kodu do uzbrojenia" + }, + "title": "Konfiguracja AlarmDecoder" + }, + "init": { + "data": { + "edit_select": "Edytuj" + }, + "description": "Co chcia\u0142by\u015b edytowa\u0107?", + "title": "Konfiguracja AlarmDecoder" + }, + "zone_details": { + "data": { + "zone_loop": "P\u0119tla RF (RF Loop)", + "zone_name": "Nazwa strefy", + "zone_relayaddr": "Adres przeka\u017anika", + "zone_relaychan": "Kana\u0142 przeka\u017anika", + "zone_type": "Rodzaj strefy" + }, + "description": "Wprowad\u017a szczeg\u00f3\u0142y dla strefy {zone_number} . Aby usun\u0105\u0107 stref\u0119 {zone_number}, pozostaw nazw\u0119 strefy pust\u0105.", + "title": "Konfiguracja AlarmDecoder" + }, + "zone_select": { + "data": { + "zone_number": "Numer strefy" + }, + "description": "Wprowad\u017a numer strefy, kt\u00f3r\u0105 chcesz doda\u0107, edytowa\u0107 lub usun\u0105\u0107.", + "title": "Konfiguracja AlarmDecoder" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/alarmdecoder/translations/ru.json b/homeassistant/components/alarmdecoder/translations/ru.json new file mode 100644 index 00000000000000..3a6e56686fd6bc --- /dev/null +++ b/homeassistant/components/alarmdecoder/translations/ru.json @@ -0,0 +1,74 @@ +{ + "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." + }, + "create_entry": { + "default": "\u0423\u0441\u043f\u0435\u0448\u043d\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u043a AlarmDecoder." + }, + "error": { + "service_unavailable": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." + }, + "step": { + "protocol": { + "data": { + "device_baudrate": "\u0421\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u043f\u0435\u0440\u0435\u0434\u0430\u0447\u0438 \u0434\u0430\u043d\u043d\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430", + "device_path": "\u041f\u0443\u0442\u044c \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443", + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" + }, + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f" + }, + "user": { + "data": { + "protocol": "\u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b" + }, + "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b AlarmDecoder" + } + } + }, + "options": { + "error": { + "int": "\u041f\u043e\u043b\u0435 \u043d\u0438\u0436\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u0446\u0435\u043b\u044b\u043c \u0447\u0438\u0441\u043b\u043e\u043c.", + "loop_range": "RF Loop \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0446\u0435\u043b\u044b\u043c \u0447\u0438\u0441\u043b\u043e\u043c \u043e\u0442 1 \u0434\u043e 4.", + "loop_rfid": "RF Loop \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0431\u0435\u0437 RF Serial.", + "relay_inclusive": "\u0410\u0434\u0440\u0435\u0441 \u0440\u0435\u043b\u0435 \u0438 \u043a\u0430\u043d\u0430\u043b \u0440\u0435\u043b\u0435 \u0432\u0437\u0430\u0438\u043c\u043e\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u044b \u0438 \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u044b \u0432\u043c\u0435\u0441\u0442\u0435." + }, + "step": { + "arm_settings": { + "data": { + "alt_night_mode": "\u0410\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0439 \u043d\u043e\u0447\u043d\u043e\u0439 \u0440\u0435\u0436\u0438\u043c", + "auto_bypass": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u0432\u043a\u043b\u044e\u0447\u0430\u0442\u044c \u0438\u0441\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043f\u0440\u0438 \u043f\u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0435 \u043d\u0430 \u043e\u0445\u0440\u0430\u043d\u0443", + "code_arm_required": "\u041a\u043e\u0434, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0439 \u0434\u043b\u044f \u043f\u043e\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438 \u043d\u0430 \u043e\u0445\u0440\u0430\u043d\u0443" + }, + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 AlarmDecoder" + }, + "init": { + "data": { + "edit_select": "\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c" + }, + "description": "\u0427\u0442\u043e \u0431\u044b \u0412\u044b \u0445\u043e\u0442\u0435\u043b\u0438 \u0440\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c?", + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 AlarmDecoder" + }, + "zone_details": { + "data": { + "zone_loop": "RF Loop", + "zone_name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0437\u043e\u043d\u044b", + "zone_relayaddr": "\u0410\u0434\u0440\u0435\u0441 \u0440\u0435\u043b\u0435", + "zone_relaychan": "\u041a\u0430\u043d\u0430\u043b \u0440\u0435\u043b\u0435", + "zone_rfid": "RF Serial", + "zone_type": "\u0422\u0438\u043f \u0437\u043e\u043d\u044b" + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u0437\u043e\u043d\u044b {zone_number}. \u0427\u0442\u043e\u0431\u044b \u0443\u0434\u0430\u043b\u0438\u0442\u044c \u0437\u043e\u043d\u0443 {zone_number}, \u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u043e\u043b\u0435 \"\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 \u0437\u043e\u043d\u044b\" \u043f\u0443\u0441\u0442\u044b\u043c.", + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 AlarmDecoder" + }, + "zone_select": { + "data": { + "zone_number": "\u041d\u043e\u043c\u0435\u0440 \u0437\u043e\u043d\u044b" + }, + "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043d\u043e\u043c\u0435\u0440 \u0437\u043e\u043d\u044b, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c, \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u0438\u043b\u0438 \u0443\u0434\u0430\u043b\u0438\u0442\u044c.", + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 AlarmDecoder" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/alarmdecoder/translations/sv.json b/homeassistant/components/alarmdecoder/translations/sv.json new file mode 100644 index 00000000000000..6c9f0dbcb43f2b --- /dev/null +++ b/homeassistant/components/alarmdecoder/translations/sv.json @@ -0,0 +1,27 @@ +{ + "config": { + "step": { + "protocol": { + "data": { + "device_path": "Enhetsv\u00e4g" + }, + "title": "Konfigurera anslutningsinst\u00e4llningar" + }, + "user": { + "data": { + "protocol": "Protokoll" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "edit_select": "Redigera" + }, + "description": "Vad vill du redigera?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/alarmdecoder/translations/zh-Hant.json b/homeassistant/components/alarmdecoder/translations/zh-Hant.json new file mode 100644 index 00000000000000..4caf58203c8028 --- /dev/null +++ b/homeassistant/components/alarmdecoder/translations/zh-Hant.json @@ -0,0 +1,74 @@ +{ + "config": { + "abort": { + "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "create_entry": { + "default": "\u6210\u529f\u9023\u7dda\u81f3 AlarmDecoder\u3002" + }, + "error": { + "service_unavailable": "\u9023\u7dda\u5931\u6557" + }, + "step": { + "protocol": { + "data": { + "device_baudrate": "\u8a2d\u5099\u901a\u8a0a\u7387", + "device_path": "\u8a2d\u5099\u8def\u5f91", + "host": "\u4e3b\u6a5f\u7aef", + "port": "\u901a\u8a0a\u57e0" + }, + "title": "\u8a2d\u5b9a\u9023\u7dda\u8a2d\u5b9a" + }, + "user": { + "data": { + "protocol": "\u901a\u8a0a\u5354\u5b9a" + }, + "title": "\u9078\u64c7 AlarmDecoder \u901a\u8a0a\u5354\u5b9a" + } + } + }, + "options": { + "error": { + "int": "\u4e0b\u65b9\u6b04\u4f4d\u5fc5\u9808\u70ba\u6574\u6578\u3002", + "loop_range": "RF \u8ff4\u8def\u5fc5\u9808\u70ba\u4ecb\u65bc 1 \u81f3 4 \u9593\u7684\u6574\u6578\u3002", + "loop_rfid": "\u5982\u679c\u6c92\u6709 RF \u5e8f\u5217\u5247\u7121\u6cd5\u4f7f\u7528 RF \u8ff4\u8def\u3002", + "relay_inclusive": "\u4e2d\u7e7c\u5730\u5740\u8207\u4e2d\u7e7c\u983b\u9053\u70ba\u76f8\u4e92\u4f9d\u8cf4\uff0c\u4e26\u5fc5\u9808\u4e00\u8d77\u5305\u542b\u3002" + }, + "step": { + "arm_settings": { + "data": { + "alt_night_mode": "\u66ff\u4ee3\u591c\u9593\u6a21\u5f0f", + "auto_bypass": "\u81ea\u52d5\u5ffd\u7565\u8b66\u6212", + "code_arm_required": "\u8b66\u6212\u9700\u8981\u4ee3\u78bc" + }, + "title": "\u8a2d\u5b9a AlarmDecoder" + }, + "init": { + "data": { + "edit_select": "\u7de8\u8f2f" + }, + "description": "\u662f\u5426\u8981\u9032\u884c\u7de8\u8f2f\uff1f", + "title": "\u8a2d\u5b9a AlarmDecoder" + }, + "zone_details": { + "data": { + "zone_loop": "RF \u8ff4\u8def", + "zone_name": "\u5340\u57df\u540d\u7a31", + "zone_relayaddr": "\u4e2d\u7e7c\u4f4d\u5740", + "zone_relaychan": "\u4e2d\u7e7c\u983b\u9053", + "zone_rfid": "RF \u5e8f\u5217", + "zone_type": "\u5340\u57df\u985e\u578b" + }, + "description": "\u8f38\u5165\u5340\u57df {zone_number} \u8a73\u7d30\u8cc7\u6599\u3002\u6b32\u522a\u9664\u5340\u57df {zone_number}\uff0c\u4fdd\u6301\u5340\u57df\u540d\u7a31\u7a7a\u767d\u3002", + "title": "\u8a2d\u5b9a AlarmDecoder" + }, + "zone_select": { + "data": { + "zone_number": "\u5340\u57df\u78bc" + }, + "description": "\u8f38\u5165\u6240\u8981\u65b0\u589e\u3001\u7de8\u8f2f\u6216\u79fb\u9664\u7684\u5340\u57df\u78bc\u3002", + "title": "\u8a2d\u5b9a AlarmDecoder" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/alexa/handlers.py b/homeassistant/components/alexa/handlers.py index 6eeb3235a64714..783c7a36949a99 100644 --- a/homeassistant/components/alexa/handlers.py +++ b/homeassistant/components/alexa/handlers.py @@ -17,6 +17,7 @@ from homeassistant.components.climate import const as climate from homeassistant.const import ( ATTR_ENTITY_ID, + ATTR_ENTITY_PICTURE, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, SERVICE_ALARM_ARM_AWAY, @@ -1532,7 +1533,7 @@ async def async_api_initialize_camera_stream(hass, config, directive, context): """Process a InitializeCameraStreams request.""" entity = directive.entity stream_source = await camera.async_request_stream(hass, entity.entity_id, fmt="hls") - camera_image = hass.states.get(entity.entity_id).attributes["entity_picture"] + camera_image = hass.states.get(entity.entity_id).attributes[ATTR_ENTITY_PICTURE] try: external_url = network.get_url( diff --git a/homeassistant/components/alexa/state_report.py b/homeassistant/components/alexa/state_report.py index a61dfc02d1093e..1d06422056d932 100644 --- a/homeassistant/components/alexa/state_report.py +++ b/homeassistant/components/alexa/state_report.py @@ -6,7 +6,7 @@ import aiohttp import async_timeout -from homeassistant.const import MATCH_ALL, STATE_ON +from homeassistant.const import HTTP_ACCEPTED, MATCH_ALL, STATE_ON import homeassistant.util.dt as dt_util from .const import API_CHANGE, Cause @@ -109,7 +109,7 @@ async def async_send_changereport_message( _LOGGER.debug("Sent: %s", json.dumps(message_serialized)) _LOGGER.debug("Received (%s): %s", response.status, response_text) - if response.status == 202: + if response.status == HTTP_ACCEPTED: return response_json = json.loads(response_text) @@ -240,7 +240,7 @@ async def async_send_doorbell_event_message(hass, config, alexa_entity): _LOGGER.debug("Sent: %s", json.dumps(message_serialized)) _LOGGER.debug("Received (%s): %s", response.status, response_text) - if response.status == 202: + if response.status == HTTP_ACCEPTED: return response_json = json.loads(response_text) diff --git a/homeassistant/components/almond/config_flow.py b/homeassistant/components/almond/config_flow.py index 0421612b7020ab..8a4c5417700160 100644 --- a/homeassistant/components/almond/config_flow.py +++ b/homeassistant/components/almond/config_flow.py @@ -51,7 +51,7 @@ async def async_step_user(self, user_input=None): """Handle a flow start.""" # Only allow 1 instance. if self._async_current_entries(): - return self.async_abort(reason="already_setup") + return self.async_abort(reason="single_instance_allowed") return await super().async_step_user(user_input) @@ -79,7 +79,7 @@ async def async_step_import(self, user_input: dict = None) -> dict: """Import data.""" # Only allow 1 instance. if self._async_current_entries(): - return self.async_abort(reason="already_setup") + return self.async_abort(reason="single_instance_allowed") if not await async_verify_local_connection(self.hass, user_input["host"]): self.logger.warning( @@ -97,7 +97,7 @@ async def async_step_import(self, user_input: dict = None) -> dict: async def async_step_hassio(self, discovery_info): """Receive a Hass.io discovery.""" if self._async_current_entries(): - return self.async_abort(reason="already_setup") + return self.async_abort(reason="single_instance_allowed") self.hassio_discovery = discovery_info diff --git a/homeassistant/components/almond/strings.json b/homeassistant/components/almond/strings.json index e8244798e8174e..b9074ebe4e3888 100644 --- a/homeassistant/components/almond/strings.json +++ b/homeassistant/components/almond/strings.json @@ -1,15 +1,17 @@ { "config": { "step": { - "pick_implementation": { "title": "Pick Authentication Method" }, + "pick_implementation": { + "title": "[%key:common::config_flow::title::oauth2_pick_implementation%]" + }, "hassio_confirm": { "title": "Almond via Hass.io add-on", "description": "Do you want to configure Home Assistant to connect to Almond provided by the Hass.io add-on: {addon}?" } }, "abort": { - "already_setup": "You can only configure one Almond account.", - "cannot_connect": "Unable to connect to the Almond server.", + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "missing_configuration": "Please check the documentation on how to set up Almond.", "no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]" } diff --git a/homeassistant/components/almond/translations/es.json b/homeassistant/components/almond/translations/es.json index de9fb58eabdd0c..94b1f4ea6baf2d 100644 --- a/homeassistant/components/almond/translations/es.json +++ b/homeassistant/components/almond/translations/es.json @@ -3,7 +3,8 @@ "abort": { "already_setup": "S\u00f3lo puede configurar una cuenta de Almond.", "cannot_connect": "No se puede conectar al servidor Almond.", - "missing_configuration": "Consulte la documentaci\u00f3n sobre c\u00f3mo configurar Almond." + "missing_configuration": "Consulte la documentaci\u00f3n sobre c\u00f3mo configurar Almond.", + "no_url_available": "No hay URL disponible. Para obtener informaci\u00f3n sobre este error, [consulta la secci\u00f3n de ayuda]({docs_url})" }, "step": { "hassio_confirm": { diff --git a/homeassistant/components/almond/translations/fr.json b/homeassistant/components/almond/translations/fr.json index f39a1660bb9003..7b7f4bff1e4be6 100644 --- a/homeassistant/components/almond/translations/fr.json +++ b/homeassistant/components/almond/translations/fr.json @@ -3,7 +3,8 @@ "abort": { "already_setup": "Vous ne pouvez configurer qu'un seul compte Almond", "cannot_connect": "Impossible de se connecter au serveur Almond", - "missing_configuration": "Veuillez consulter la documentation pour savoir comment configurer Almond." + "missing_configuration": "Veuillez consulter la documentation pour savoir comment configurer Almond.", + "no_url_available": "Aucune URL disponible. Pour plus d'informations sur cette erreur, [consultez la section d'aide] ( {docs_url} )" }, "step": { "hassio_confirm": { diff --git a/homeassistant/components/almond/translations/it.json b/homeassistant/components/almond/translations/it.json index aa3711446307ca..6bf280230a44f0 100644 --- a/homeassistant/components/almond/translations/it.json +++ b/homeassistant/components/almond/translations/it.json @@ -12,7 +12,7 @@ "title": "Almond tramite il componente aggiuntivo di Hass.io" }, "pick_implementation": { - "title": "Seleziona metodo di autenticazione" + "title": "Scegli il metodo di autenticazione" } } } diff --git a/homeassistant/components/almond/translations/ko.json b/homeassistant/components/almond/translations/ko.json index 645eaafab08e26..08cb120bf9de60 100644 --- a/homeassistant/components/almond/translations/ko.json +++ b/homeassistant/components/almond/translations/ko.json @@ -3,7 +3,8 @@ "abort": { "already_setup": "\ud558\ub098\uc758 Almond \uacc4\uc815\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "cannot_connect": "Almond \uc11c\ubc84\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4.", - "missing_configuration": "Almond \uc124\uc815 \ubc29\ubc95\uc5d0 \ub300\ud55c \uc124\uba85\uc11c\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694." + "missing_configuration": "Almond \uc124\uc815 \ubc29\ubc95\uc5d0 \ub300\ud55c \uc124\uba85\uc11c\ub97c \ud655\uc778\ud574\uc8fc\uc138\uc694.", + "no_url_available": "\uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc5d0\ub7ec\uc5d0 \ub300\ud55c \uc815\ubcf4\ub294 \ub3c4\uc6c0\ub9d0 \uc139\uc158\uc744 \ud655\uc778\ud558\uc138\uc694({docs_url})" }, "step": { "hassio_confirm": { diff --git a/homeassistant/components/almond/translations/lb.json b/homeassistant/components/almond/translations/lb.json index 3b866a326bea77..bfcdad87e52f7c 100644 --- a/homeassistant/components/almond/translations/lb.json +++ b/homeassistant/components/almond/translations/lb.json @@ -3,7 +3,8 @@ "abort": { "already_setup": "Dir k\u00ebnnt n\u00ebmmen een eenzegen Almond Kont konfigur\u00e9ieren.", "cannot_connect": "Kann sech net mam Almond Server verbannen.", - "missing_configuration": "Kuckt w.e.g. Dokumentatioun iwwert d'ariichten vun Almond." + "missing_configuration": "Kuckt w.e.g. Dokumentatioun iwwert d'ariichten vun Almond.", + "no_url_available": "Keng URL disponibel. Fir Informatiounen iwwert d\u00ebse Feeler, [kuck H\u00ebllef Sektioun]({docs_url})" }, "step": { "hassio_confirm": { diff --git a/homeassistant/components/almond/translations/nl.json b/homeassistant/components/almond/translations/nl.json index 7a2a60b1a695e3..da4671f85916c8 100644 --- a/homeassistant/components/almond/translations/nl.json +++ b/homeassistant/components/almond/translations/nl.json @@ -3,7 +3,8 @@ "abort": { "already_setup": "U kunt slechts \u00e9\u00e9n Almond-account configureren.", "cannot_connect": "Kan geen verbinding maken met de Almond-server.", - "missing_configuration": "Raadpleeg de documentatie over het instellen van Almond." + "missing_configuration": "Raadpleeg de documentatie over het instellen van Almond.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})" }, "step": { "hassio_confirm": { diff --git a/homeassistant/components/almond/translations/no.json b/homeassistant/components/almond/translations/no.json index 3a6a89a8340236..c9da3b2303c41c 100644 --- a/homeassistant/components/almond/translations/no.json +++ b/homeassistant/components/almond/translations/no.json @@ -3,7 +3,8 @@ "abort": { "already_setup": "Du kan bare konfigurere en Almond konto.", "cannot_connect": "Kan ikke koble til Almond-serveren.", - "missing_configuration": "Vennligst sjekk dokumentasjonen om hvordan du setter opp Almond." + "missing_configuration": "Vennligst sjekk dokumentasjonen om hvordan du setter opp Almond.", + "no_url_available": "Ingen URL tilgjengelig. For informasjon om denne feilen, [sjekk {docs_url} ] ( {docs_url} )" }, "step": { "hassio_confirm": { diff --git a/homeassistant/components/almond/translations/zh-Hant.json b/homeassistant/components/almond/translations/zh-Hant.json index 96e3d92e060724..d5ea73a873d313 100644 --- a/homeassistant/components/almond/translations/zh-Hant.json +++ b/homeassistant/components/almond/translations/zh-Hant.json @@ -3,7 +3,8 @@ "abort": { "already_setup": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44 Almond \u5e33\u865f\u3002", "cannot_connect": "\u7121\u6cd5\u9023\u7dda\u81f3 Almond \u4f3a\u670d\u5668\u3002", - "missing_configuration": "\u8acb\u53c3\u8003\u76f8\u95dc\u6587\u4ef6\u4ee5\u4e86\u89e3\u5982\u4f55\u8a2d\u5b9a Almond\u3002" + "missing_configuration": "\u8acb\u53c3\u8003\u76f8\u95dc\u6587\u4ef6\u4ee5\u4e86\u89e3\u5982\u4f55\u8a2d\u5b9a Almond\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})" }, "step": { "hassio_confirm": { diff --git a/homeassistant/components/ambiclimate/config_flow.py b/homeassistant/components/ambiclimate/config_flow.py index 2b88e7ab91eb16..3d1111136521f6 100644 --- a/homeassistant/components/ambiclimate/config_flow.py +++ b/homeassistant/components/ambiclimate/config_flow.py @@ -53,20 +53,20 @@ def __init__(self): async def async_step_user(self, user_input=None): """Handle external yaml configuration.""" if self.hass.config_entries.async_entries(DOMAIN): - return self.async_abort(reason="already_setup") + return self.async_abort(reason="already_configured_account") config = self.hass.data.get(DATA_AMBICLIMATE_IMPL, {}) if not config: _LOGGER.debug("No config") - return self.async_abort(reason="no_config") + return self.async_abort(reason="oauth2_missing_configuration") return await self.async_step_auth() async def async_step_auth(self, user_input=None): """Handle a flow start.""" if self.hass.config_entries.async_entries(DOMAIN): - return self.async_abort(reason="already_setup") + return self.async_abort(reason="already_configured_account") errors = {} @@ -88,7 +88,7 @@ async def async_step_auth(self, user_input=None): async def async_step_code(self, code=None): """Received code for authentication.""" if self.hass.config_entries.async_entries(DOMAIN): - return self.async_abort(reason="already_setup") + return self.async_abort(reason="already_configured_account") token_info = await self._get_token_info(code) diff --git a/homeassistant/components/ambiclimate/strings.json b/homeassistant/components/ambiclimate/strings.json index 26af78f891560e..9e01281ad307ee 100644 --- a/homeassistant/components/ambiclimate/strings.json +++ b/homeassistant/components/ambiclimate/strings.json @@ -14,8 +14,8 @@ "follow_link": "Please follow the link and authenticate before pressing Submit" }, "abort": { - "already_setup": "The Ambiclimate account is configured.", - "no_config": "You need to configure Ambiclimate before being able to authenticate with it. [Please read the instructions](https://www.home-assistant.io/components/ambiclimate/).", + "already_configured_account": "[%key:common::config_flow::abort::already_configured_account%]", + "oauth2_missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]", "access_token": "Unknown error generating an access token." } } diff --git a/homeassistant/components/ambiclimate/translations/ca.json b/homeassistant/components/ambiclimate/translations/ca.json index 64ea45410d35a1..04722aebb05efa 100644 --- a/homeassistant/components/ambiclimate/translations/ca.json +++ b/homeassistant/components/ambiclimate/translations/ca.json @@ -2,8 +2,10 @@ "config": { "abort": { "access_token": "S'ha produ\u00eft un error desconegut al generat un token d'acc\u00e9s.", + "already_configured_account": "El compte ja ha estat configurat", "already_setup": "El compte d'Ambi Climate est\u00e0 configurat.", - "no_config": "Necessites configurar Ambiclimate abans de poder autenticar-t'hi. Llegeix les [instruccions](https://www.home-assistant.io/components/ambiclimate/)." + "no_config": "Necessites configurar Ambiclimate abans de poder autenticar-t'hi. Llegeix les [instruccions](https://www.home-assistant.io/components/ambiclimate/).", + "oauth2_missing_configuration": "El component no est\u00e0 configurat. Mira'n la documentaci\u00f3." }, "create_entry": { "default": "Autenticaci\u00f3 exitosa amb Ambi Climate." diff --git a/homeassistant/components/ambiclimate/translations/en.json b/homeassistant/components/ambiclimate/translations/en.json index 177ecd2907f9de..efcc96d85dd37c 100644 --- a/homeassistant/components/ambiclimate/translations/en.json +++ b/homeassistant/components/ambiclimate/translations/en.json @@ -2,8 +2,10 @@ "config": { "abort": { "access_token": "Unknown error generating an access token.", + "already_configured_account": "Account is already configured", "already_setup": "The Ambiclimate account is configured.", - "no_config": "You need to configure Ambiclimate before being able to authenticate with it. [Please read the instructions](https://www.home-assistant.io/components/ambiclimate/)." + "no_config": "You need to configure Ambiclimate before being able to authenticate with it. [Please read the instructions](https://www.home-assistant.io/components/ambiclimate/).", + "oauth2_missing_configuration": "The component is not configured. Please follow the documentation." }, "create_entry": { "default": "Successfully authenticated with Ambiclimate" diff --git a/homeassistant/components/ambiclimate/translations/es.json b/homeassistant/components/ambiclimate/translations/es.json index 01d6643b6349ff..ba5a53de2ae8c0 100644 --- a/homeassistant/components/ambiclimate/translations/es.json +++ b/homeassistant/components/ambiclimate/translations/es.json @@ -2,8 +2,10 @@ "config": { "abort": { "access_token": "Error desconocido al generar un token de acceso.", + "already_configured_account": "La cuenta ya ha sido configurada", "already_setup": "La cuenta de Ambiclimate est\u00e1 configurada.", - "no_config": "Es necesario configurar Ambiclimate antes de poder autenticarse con \u00e9l. [Por favor, lee las instrucciones](https://www.home-assistant.io/components/ambiclimate/)." + "no_config": "Es necesario configurar Ambiclimate antes de poder autenticarse con \u00e9l. [Por favor, lee las instrucciones](https://www.home-assistant.io/components/ambiclimate/).", + "oauth2_missing_configuration": "El componente no est\u00e1 configurado. Consulta la documentaci\u00f3n." }, "create_entry": { "default": "Autenticado correctamente con Ambiclimate" diff --git a/homeassistant/components/ambiclimate/translations/et.json b/homeassistant/components/ambiclimate/translations/et.json new file mode 100644 index 00000000000000..4bca8b8fa4ed8c --- /dev/null +++ b/homeassistant/components/ambiclimate/translations/et.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "already_configured_account": "Kasutaja on juba lisatud", + "oauth2_missing_configuration": "Osis pole h\u00e4\u00e4lestatud. Lisainfot saad dokumentatsioonist." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ambiclimate/translations/fr.json b/homeassistant/components/ambiclimate/translations/fr.json index 9c5864fcb0f6bd..879a02d38d00c7 100644 --- a/homeassistant/components/ambiclimate/translations/fr.json +++ b/homeassistant/components/ambiclimate/translations/fr.json @@ -2,8 +2,10 @@ "config": { "abort": { "access_token": "Erreur inconnue lors de la g\u00e9n\u00e9ration d'un jeton d'acc\u00e8s.", + "already_configured_account": "Le compte a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9", "already_setup": "Le compte Ambiclimate est configur\u00e9.", - "no_config": "Vous devez configurer Ambiclimate avant de pouvoir vous authentifier aupr\u00e8s de celui-ci. [Veuillez lire les instructions] (https://www.home-assistant.io/components/ambiclimate/)." + "no_config": "Vous devez configurer Ambiclimate avant de pouvoir vous authentifier aupr\u00e8s de celui-ci. [Veuillez lire les instructions] (https://www.home-assistant.io/components/ambiclimate/).", + "oauth2_missing_configuration": "Le composant n'est pas configur\u00e9. Veuillez suivre la documentation." }, "create_entry": { "default": "Authentifi\u00e9 avec succ\u00e8s avec Ambiclimate" diff --git a/homeassistant/components/ambiclimate/translations/it.json b/homeassistant/components/ambiclimate/translations/it.json index 427aa0ab445f75..b53ebaab64870d 100644 --- a/homeassistant/components/ambiclimate/translations/it.json +++ b/homeassistant/components/ambiclimate/translations/it.json @@ -2,8 +2,10 @@ "config": { "abort": { "access_token": "Errore sconosciuto durante la generazione di un token di accesso.", + "already_configured_account": "L'account \u00e8 gi\u00e0 configurato", "already_setup": "L'account Ambiclimate \u00e8 configurato.", - "no_config": "\u00c8 necessario configurare Ambiclimate prima di poter eseguire l'autenticazione con esso. [Leggere le istruzioni](https://www.home-assistant.io/components/ambiclimate/)." + "no_config": "\u00c8 necessario configurare Ambiclimate prima di poter eseguire l'autenticazione con esso. [Leggere le istruzioni](https://www.home-assistant.io/components/ambiclimate/).", + "oauth2_missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione." }, "create_entry": { "default": "Autenticato con successo con Ambiclimate" diff --git a/homeassistant/components/ambiclimate/translations/no.json b/homeassistant/components/ambiclimate/translations/no.json index 3cc3f4617e797f..493ad755df1576 100644 --- a/homeassistant/components/ambiclimate/translations/no.json +++ b/homeassistant/components/ambiclimate/translations/no.json @@ -2,8 +2,10 @@ "config": { "abort": { "access_token": "Ukjent feil ved oppretting av tilgangstoken.", + "already_configured_account": "Kontoen er allerede konfigurert", "already_setup": "Ambiclimate-kontoen er konfigurert.", - "no_config": "Du m\u00e5 konfigurere Ambiclimate f\u00f8r du kan godkjenne den. [Vennligst les instruksjonene](https://www.home-assistant.io/components/ambiclimate/)." + "no_config": "Du m\u00e5 konfigurere Ambiclimate f\u00f8r du kan godkjenne den. [Vennligst les instruksjonene](https://www.home-assistant.io/components/ambiclimate/).", + "oauth2_missing_configuration": "Komponenten er ikke konfigurert. Vennligst f\u00f8lg dokumentasjonen." }, "create_entry": { "default": "Vellykket godkjenning med Ambiclimate" diff --git a/homeassistant/components/ambiclimate/translations/ru.json b/homeassistant/components/ambiclimate/translations/ru.json index 7a440f28bed54f..e172063d383b61 100644 --- a/homeassistant/components/ambiclimate/translations/ru.json +++ b/homeassistant/components/ambiclimate/translations/ru.json @@ -2,8 +2,10 @@ "config": { "abort": { "access_token": "\u041f\u0440\u0438 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u0442\u043e\u043a\u0435\u043d\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430.", + "already_configured_account": "\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_setup": "\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.", - "no_config": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 Ambiclimate \u043f\u0435\u0440\u0435\u0434 \u043f\u0440\u043e\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u0435\u043c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438](https://www.home-assistant.io/components/ambiclimate/)." + "no_config": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 Ambiclimate \u043f\u0435\u0440\u0435\u0434 \u043f\u0440\u043e\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u0435\u043c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438](https://www.home-assistant.io/components/ambiclimate/).", + "oauth2_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 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438." }, "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." diff --git a/homeassistant/components/ambiclimate/translations/zh-Hant.json b/homeassistant/components/ambiclimate/translations/zh-Hant.json index 7b995d099442b6..9227f72a306481 100644 --- a/homeassistant/components/ambiclimate/translations/zh-Hant.json +++ b/homeassistant/components/ambiclimate/translations/zh-Hant.json @@ -2,8 +2,10 @@ "config": { "abort": { "access_token": "\u7522\u751f\u5b58\u53d6\u8a8d\u8b49\u78bc\u672a\u77e5\u932f\u8aa4\u3002", + "already_configured_account": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "already_setup": "Ambiclimate \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", - "no_config": "\u5fc5\u9808\u5148\u8a2d\u5b9a Ambiclimate \u65b9\u80fd\u9032\u884c\u8a8d\u8b49\u3002[\u8acb\u53c3\u95b1\u6559\u5b78\u6307\u5f15]\uff08https://www.home-assistant.io/components/ambiclimate/\uff09\u3002" + "no_config": "\u5fc5\u9808\u5148\u8a2d\u5b9a Ambiclimate \u65b9\u80fd\u9032\u884c\u8a8d\u8b49\u3002[\u8acb\u53c3\u95b1\u6559\u5b78\u6307\u5f15]\uff08https://www.home-assistant.io/components/ambiclimate/\uff09\u3002", + "oauth2_missing_configuration": "\u5143\u4ef6\u5c1a\u672a\u8a2d\u7f6e\uff0c\u8acb\u53c3\u95b1\u6587\u4ef6\u8aaa\u660e\u3002" }, "create_entry": { "default": "\u5df2\u6210\u529f\u8a8d\u8b49 Ambiclimate \u8a2d\u5099\u3002" diff --git a/homeassistant/components/ambient_station/__init__.py b/homeassistant/components/ambient_station/__init__.py index 9428449dc75a20..68bfb85cf62c7c 100644 --- a/homeassistant/components/ambient_station/__init__.py +++ b/homeassistant/components/ambient_station/__init__.py @@ -6,8 +6,10 @@ from aioambient.errors import WebsocketError import voluptuous as vol +from homeassistant.components.binary_sensor import DEVICE_CLASS_CONNECTIVITY from homeassistant.config_entries import SOURCE_IMPORT from homeassistant.const import ( + AREA_SQUARE_METERS, ATTR_LOCATION, ATTR_NAME, CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, @@ -15,8 +17,10 @@ CONF_API_KEY, DEGREE, EVENT_HOMEASSISTANT_STOP, + LIGHT_LUX, PERCENTAGE, POWER_WATT, + PRESSURE_INHG, SPEED_MILES_PER_HOUR, TEMP_FAHRENHEIT, ) @@ -141,8 +145,8 @@ TYPE_YEARLYRAININ = "yearlyrainin" SENSOR_TYPES = { TYPE_24HOURRAININ: ("24 Hr Rain", "in", TYPE_SENSOR, None), - TYPE_BAROMABSIN: ("Abs Pressure", "inHg", TYPE_SENSOR, "pressure"), - TYPE_BAROMRELIN: ("Rel Pressure", "inHg", TYPE_SENSOR, "pressure"), + TYPE_BAROMABSIN: ("Abs Pressure", PRESSURE_INHG, TYPE_SENSOR, "pressure"), + TYPE_BAROMRELIN: ("Rel Pressure", PRESSURE_INHG, TYPE_SENSOR, "pressure"), TYPE_BATT10: ("Battery 10", None, TYPE_BINARY_SENSOR, "battery"), TYPE_BATT1: ("Battery 1", None, TYPE_BINARY_SENSOR, "battery"), TYPE_BATT2: ("Battery 2", None, TYPE_BINARY_SENSOR, "battery"), @@ -175,16 +179,16 @@ TYPE_LASTRAIN: ("Last Rain", None, TYPE_SENSOR, "timestamp"), TYPE_MAXDAILYGUST: ("Max Gust", SPEED_MILES_PER_HOUR, TYPE_SENSOR, None), TYPE_MONTHLYRAININ: ("Monthly Rain", "in", TYPE_SENSOR, None), - TYPE_RELAY10: ("Relay 10", None, TYPE_BINARY_SENSOR, "connectivity"), - TYPE_RELAY1: ("Relay 1", None, TYPE_BINARY_SENSOR, "connectivity"), - TYPE_RELAY2: ("Relay 2", None, TYPE_BINARY_SENSOR, "connectivity"), - TYPE_RELAY3: ("Relay 3", None, TYPE_BINARY_SENSOR, "connectivity"), - TYPE_RELAY4: ("Relay 4", None, TYPE_BINARY_SENSOR, "connectivity"), - TYPE_RELAY5: ("Relay 5", None, TYPE_BINARY_SENSOR, "connectivity"), - TYPE_RELAY6: ("Relay 6", None, TYPE_BINARY_SENSOR, "connectivity"), - TYPE_RELAY7: ("Relay 7", None, TYPE_BINARY_SENSOR, "connectivity"), - TYPE_RELAY8: ("Relay 8", None, TYPE_BINARY_SENSOR, "connectivity"), - TYPE_RELAY9: ("Relay 9", None, TYPE_BINARY_SENSOR, "connectivity"), + TYPE_RELAY10: ("Relay 10", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), + TYPE_RELAY1: ("Relay 1", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), + TYPE_RELAY2: ("Relay 2", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), + TYPE_RELAY3: ("Relay 3", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), + TYPE_RELAY4: ("Relay 4", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), + TYPE_RELAY5: ("Relay 5", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), + TYPE_RELAY6: ("Relay 6", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), + TYPE_RELAY7: ("Relay 7", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), + TYPE_RELAY8: ("Relay 8", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), + TYPE_RELAY9: ("Relay 9", None, TYPE_BINARY_SENSOR, DEVICE_CLASS_CONNECTIVITY), TYPE_SOILHUM10: ("Soil Humidity 10", PERCENTAGE, TYPE_SENSOR, "humidity"), TYPE_SOILHUM1: ("Soil Humidity 1", PERCENTAGE, TYPE_SENSOR, "humidity"), TYPE_SOILHUM2: ("Soil Humidity 2", PERCENTAGE, TYPE_SENSOR, "humidity"), @@ -205,8 +209,13 @@ TYPE_SOILTEMP7F: ("Soil Temp 7", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), TYPE_SOILTEMP8F: ("Soil Temp 8", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), TYPE_SOILTEMP9F: ("Soil Temp 9", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), - TYPE_SOLARRADIATION: ("Solar Rad", f"{POWER_WATT}/m^2", TYPE_SENSOR, None), - TYPE_SOLARRADIATION_LX: ("Solar Rad (lx)", "lx", TYPE_SENSOR, "illuminance"), + TYPE_SOLARRADIATION: ( + "Solar Rad", + f"{POWER_WATT}/{AREA_SQUARE_METERS}", + TYPE_SENSOR, + None, + ), + TYPE_SOLARRADIATION_LX: ("Solar Rad (lx)", LIGHT_LUX, TYPE_SENSOR, "illuminance"), TYPE_TEMP10F: ("Temp 10", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), TYPE_TEMP1F: ("Temp 1", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), TYPE_TEMP2F: ("Temp 2", TEMP_FAHRENHEIT, TYPE_SENSOR, "temperature"), diff --git a/homeassistant/components/ambient_station/strings.json b/homeassistant/components/ambient_station/strings.json index 54b3dd055115ac..a9bce82e10b19e 100644 --- a/homeassistant/components/ambient_station/strings.json +++ b/homeassistant/components/ambient_station/strings.json @@ -10,11 +10,11 @@ } }, "error": { - "invalid_key": "Invalid API Key and/or Application Key", + "invalid_key": "[%key:common::config_flow::error::invalid_api_key%]", "no_devices": "No devices found in account" }, "abort": { - "already_configured": "This app key is already in use." + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/android_ip_webcam/binary_sensor.py b/homeassistant/components/android_ip_webcam/binary_sensor.py index 14384565718480..377ecfec667592 100644 --- a/homeassistant/components/android_ip_webcam/binary_sensor.py +++ b/homeassistant/components/android_ip_webcam/binary_sensor.py @@ -1,5 +1,8 @@ """Support for Android IP Webcam binary sensors.""" -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_MOTION, + BinarySensorEntity, +) from . import CONF_HOST, CONF_NAME, DATA_IP_WEBCAM, KEY_MAP, AndroidIPCamEntity @@ -47,4 +50,4 @@ async def async_update(self): @property def device_class(self): """Return the class of this device, from component DEVICE_CLASSES.""" - return "motion" + return DEVICE_CLASS_MOTION diff --git a/homeassistant/components/androidtv/manifest.json b/homeassistant/components/androidtv/manifest.json index 8e62813714eaec..f6c773941f166b 100644 --- a/homeassistant/components/androidtv/manifest.json +++ b/homeassistant/components/androidtv/manifest.json @@ -4,7 +4,7 @@ "documentation": "https://www.home-assistant.io/integrations/androidtv", "requirements": [ "adb-shell[async]==0.2.1", - "androidtv[async]==0.0.49", + "androidtv[async]==0.0.50", "pure-python-adb[async]==0.3.0.dev0" ], "codeowners": ["@JeffLIrion"] diff --git a/homeassistant/components/androidtv/media_player.py b/homeassistant/components/androidtv/media_player.py index 959c85abd77799..b13a2000e5ba51 100644 --- a/homeassistant/components/androidtv/media_player.py +++ b/homeassistant/components/androidtv/media_player.py @@ -380,7 +380,7 @@ async def _adb_exception_catcher(self, *args, **kwargs): # An unforeseen exception occurred. Close the ADB connection so that # it doesn't happen over and over again, then raise the exception. await self.aftv.adb_close() - self._available = False # pylint: disable=protected-access + self._available = False raise return _adb_exception_catcher @@ -476,11 +476,6 @@ def name(self): """Return the device name.""" return self._name - @property - def should_poll(self): - """Device should be polled.""" - return True - @property def source(self): """Return the current app.""" @@ -502,14 +497,23 @@ def unique_id(self): return self._unique_id @adb_decorator() + async def _adb_screencap(self): + """Take a screen capture from the device.""" + return await self.aftv.adb_screencap() + async def async_get_media_image(self): """Fetch current playing image.""" if not self._screencap or self.state in [STATE_OFF, None] or not self.available: return None, None - media_data = await self.aftv.adb_screencap() + media_data = await self._adb_screencap() if media_data: return media_data, "image/png" + + # If an exception occurred and the device is no longer available, write the state + if not self.available: + self.async_write_ha_state() + return None, None @adb_decorator() diff --git a/homeassistant/components/anel_pwrctrl/switch.py b/homeassistant/components/anel_pwrctrl/switch.py index c769f51d5b6b6f..0669a3bb6c6a4f 100644 --- a/homeassistant/components/anel_pwrctrl/switch.py +++ b/homeassistant/components/anel_pwrctrl/switch.py @@ -66,11 +66,6 @@ def __init__(self, port, parent_device): self._port = port self._parent_device = parent_device - @property - def should_poll(self): - """Return the polling state.""" - return True - @property def unique_id(self): """Return the unique ID of the device.""" diff --git a/homeassistant/components/api/__init__.py b/homeassistant/components/api/__init__.py index c1f692a76ad370..34d899e996cd1f 100644 --- a/homeassistant/components/api/__init__.py +++ b/homeassistant/components/api/__init__.py @@ -38,6 +38,7 @@ from homeassistant.helpers.network import NoURLAvailableError, get_url from homeassistant.helpers.service import async_get_all_descriptions from homeassistant.helpers.state import AsyncTrackStates +from homeassistant.helpers.system_info import async_get_system_info _LOGGER = logging.getLogger(__name__) @@ -45,6 +46,7 @@ ATTR_EXTERNAL_URL = "external_url" ATTR_INTERNAL_URL = "internal_url" ATTR_LOCATION_NAME = "location_name" +ATTR_INSTALLATION_TYPE = "installation_type" ATTR_REQUIRES_API_PASSWORD = "requires_api_password" ATTR_UUID = "uuid" ATTR_VERSION = "version" @@ -181,6 +183,7 @@ async def get(self, request): """Get discovery information.""" hass = request.app["hass"] uuid = await hass.helpers.instance_id.async_get() + system_info = await async_get_system_info(hass) data = { ATTR_UUID: uuid, @@ -188,6 +191,7 @@ async def get(self, request): ATTR_EXTERNAL_URL: None, ATTR_INTERNAL_URL: None, ATTR_LOCATION_NAME: hass.config.location_name, + ATTR_INSTALLATION_TYPE: system_info[ATTR_INSTALLATION_TYPE], # always needs authentication ATTR_REQUIRES_API_PASSWORD: True, ATTR_VERSION: __version__, diff --git a/homeassistant/components/apprise/manifest.json b/homeassistant/components/apprise/manifest.json index beb3a80ceebc81..34061120322db4 100644 --- a/homeassistant/components/apprise/manifest.json +++ b/homeassistant/components/apprise/manifest.json @@ -2,6 +2,6 @@ "domain": "apprise", "name": "Apprise", "documentation": "https://www.home-assistant.io/integrations/apprise", - "requirements": ["apprise==0.8.8"], + "requirements": ["apprise==0.8.9"], "codeowners": ["@caronc"] } diff --git a/homeassistant/components/apprise/notify.py b/homeassistant/components/apprise/notify.py index 0c8c5b26eeca4b..95bf11ddc097ae 100644 --- a/homeassistant/components/apprise/notify.py +++ b/homeassistant/components/apprise/notify.py @@ -28,8 +28,7 @@ def get_service(hass, config, discovery_info=None): """Get the Apprise notification service.""" - - # Create our object + # Create our Apprise Instance (reference our asset) a_obj = apprise.Apprise() if config.get(CONF_FILE): diff --git a/homeassistant/components/arcam_fmj/config_flow.py b/homeassistant/components/arcam_fmj/config_flow.py index 2a9f1946cb4d98..c04757e75d3cac 100644 --- a/homeassistant/components/arcam_fmj/config_flow.py +++ b/homeassistant/components/arcam_fmj/config_flow.py @@ -37,7 +37,7 @@ async def _async_check_and_create(self, host, port): try: await client.start() except ConnectionFailed: - return self.async_abort(reason="unable_to_connect") + return self.async_abort(reason="cannot_connect") finally: await client.stop() diff --git a/homeassistant/components/arcam_fmj/strings.json b/homeassistant/components/arcam_fmj/strings.json index 67aaf7a11cbdc8..b6a7d6ee559253 100644 --- a/homeassistant/components/arcam_fmj/strings.json +++ b/homeassistant/components/arcam_fmj/strings.json @@ -1,28 +1,28 @@ { - "config": { - "abort": { - "already_configured": "Device was already setup.", - "already_in_progress": "Config flow for device is already in progress.", - "unable_to_connect": "Unable to connect to device." - }, - "error": {}, - "flow_title": "Arcam FMJ on {host}", - "step": { - "confirm": { - "description": "Do you want to add Arcam FMJ on `{host}` to Home Assistant?" - }, - "user": { - "data": { - "host": "[%key:common::config_flow::data::host%]", - "port": "[%key:common::config_flow::data::port%]" - }, - "description": "Please enter the host name or IP address of device." - } - } + "config": { + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, - "device_automation": { - "trigger_type": { - "turn_on": "{entity_name} was requested to turn on" - } + "error": {}, + "flow_title": "Arcam FMJ on {host}", + "step": { + "confirm": { + "description": "Do you want to add Arcam FMJ on `{host}` to Home Assistant?" + }, + "user": { + "data": { + "host": "[%key:common::config_flow::data::host%]", + "port": "[%key:common::config_flow::data::port%]" + }, + "description": "Please enter the host name or IP address of device." + } + } + }, + "device_automation": { + "trigger_type": { + "turn_on": "{entity_name} was requested to turn on" } + } } diff --git a/homeassistant/components/arcam_fmj/translations/ca.json b/homeassistant/components/arcam_fmj/translations/ca.json index 28149d5e06edd9..98cedd0771e2f1 100644 --- a/homeassistant/components/arcam_fmj/translations/ca.json +++ b/homeassistant/components/arcam_fmj/translations/ca.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "El dispositiu ja s'ha configurat.", - "already_in_progress": "El flux de dades de configuraci\u00f3 pel dispositiu ja est\u00e0 en curs.", + "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", "unable_to_connect": "No es pot connectar amb el dispositiu." }, "flow_title": "Arcam FMJ a {host}", diff --git a/homeassistant/components/arcam_fmj/translations/en.json b/homeassistant/components/arcam_fmj/translations/en.json index b22ed592f69084..4858cb993707cd 100644 --- a/homeassistant/components/arcam_fmj/translations/en.json +++ b/homeassistant/components/arcam_fmj/translations/en.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Device was already setup.", - "already_in_progress": "Config flow for device is already in progress.", + "already_in_progress": "Configuration flow is already in progress", "unable_to_connect": "Unable to connect to device." }, "flow_title": "Arcam FMJ on {host}", diff --git a/homeassistant/components/arcam_fmj/translations/it.json b/homeassistant/components/arcam_fmj/translations/it.json index 61f3dd6fc47be8..1328ba1cd349c0 100644 --- a/homeassistant/components/arcam_fmj/translations/it.json +++ b/homeassistant/components/arcam_fmj/translations/it.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Il dispositivo era gi\u00e0 configurato.", - "already_in_progress": "Il flusso di configurazione per il dispositivo \u00e8 gi\u00e0 in corso.", + "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "unable_to_connect": "Impossibile connettersi al dispositivo." }, "error": { diff --git a/homeassistant/components/arcam_fmj/translations/no.json b/homeassistant/components/arcam_fmj/translations/no.json index 14d55224119cfa..4d5933acd72877 100644 --- a/homeassistant/components/arcam_fmj/translations/no.json +++ b/homeassistant/components/arcam_fmj/translations/no.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Enheten var allerede konfigurert.", - "already_in_progress": "Konfigurasjonsflyt for enhet p\u00e5g\u00e5r allerede.", + "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", "unable_to_connect": "Kan ikke koble til enheten." }, "flow_title": "Arcam FMJ p\u00e5 {host}", diff --git a/homeassistant/components/arcam_fmj/translations/pl.json b/homeassistant/components/arcam_fmj/translations/pl.json index 7b2d5da76e52fe..7cc66881cdfda9 100644 --- a/homeassistant/components/arcam_fmj/translations/pl.json +++ b/homeassistant/components/arcam_fmj/translations/pl.json @@ -1,14 +1,20 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "unable_to_connect": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z urz\u0105dzeniem." }, + "flow_title": "Arcam FMJ na {host}", "step": { + "confirm": { + "description": "Czy chcesz doda\u0107 Arcam FMJ na \"{host}\" do Home Assistant?" + }, "user": { "data": { "host": "Nazwa hosta lub adres IP", "port": "Port" - } + }, + "description": "Wpisz nazw\u0119 hosta lub adres IP urz\u0105dzenia." } } }, diff --git a/homeassistant/components/arcam_fmj/translations/ru.json b/homeassistant/components/arcam_fmj/translations/ru.json index 807bfb4e4c577c..f3dac175812bc2 100644 --- a/homeassistant/components/arcam_fmj/translations/ru.json +++ b/homeassistant/components/arcam_fmj/translations/ru.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", - "already_in_progress": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.", + "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.", "unable_to_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." }, "flow_title": "Arcam FMJ {host}", diff --git a/homeassistant/components/arcam_fmj/translations/zh-Hant.json b/homeassistant/components/arcam_fmj/translations/zh-Hant.json index f25906f0d815bb..8e9bfad987e455 100644 --- a/homeassistant/components/arcam_fmj/translations/zh-Hant.json +++ b/homeassistant/components/arcam_fmj/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\u8a2d\u5099\u5df2\u8a2d\u5b9a\u3002", - "already_in_progress": "\u8a2d\u5099\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d\u3002", + "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "unable_to_connect": "\u7121\u6cd5\u9023\u7dda\u81f3\u8a2d\u5099\u3002" }, "flow_title": "Arcam FMJ \uff08{host}\uff09", diff --git a/homeassistant/components/arduino/__init__.py b/homeassistant/components/arduino/__init__.py index e87a625522e11f..2890fd4abdaf38 100644 --- a/homeassistant/components/arduino/__init__.py +++ b/homeassistant/components/arduino/__init__.py @@ -23,6 +23,12 @@ def setup(hass, config): """Set up the Arduino component.""" + _LOGGER.warning( + "The %s integration has been deprecated. Please move your " + "configuration to the firmata integration. " + "https://www.home-assistant.io/integrations/firmata", + DOMAIN, + ) port = config[DOMAIN][CONF_PORT] diff --git a/homeassistant/components/arwn/sensor.py b/homeassistant/components/arwn/sensor.py index 7c947be61bf686..3db1283279e684 100644 --- a/homeassistant/components/arwn/sensor.py +++ b/homeassistant/components/arwn/sensor.py @@ -96,7 +96,7 @@ def async_sensor_event_received(msg): store[sensor.name] = sensor _LOGGER.debug( "Registering new sensor %(name)s => %(event)s", - dict(name=sensor.name, event=event), + {"name": sensor.name, "event": event}, ) async_add_entities((sensor,), True) else: diff --git a/homeassistant/components/atag/manifest.json b/homeassistant/components/atag/manifest.json index 9f8a5d2c6ebe5a..5e94afb06d31cb 100644 --- a/homeassistant/components/atag/manifest.json +++ b/homeassistant/components/atag/manifest.json @@ -3,6 +3,6 @@ "name": "Atag", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/atag/", - "requirements": ["pyatag==0.3.3.4"], + "requirements": ["pyatag==0.3.4.4"], "codeowners": ["@MatsNL"] } diff --git a/homeassistant/components/atag/strings.json b/homeassistant/components/atag/strings.json index 85e22c10c1be3d..22d7092142007f 100644 --- a/homeassistant/components/atag/strings.json +++ b/homeassistant/components/atag/strings.json @@ -6,14 +6,14 @@ "title": "Connect to the device", "data": { "host": "[%key:common::config_flow::data::host%]", - "email": "Email (Optional)", + "email": "[%key:common::config_flow::data::email%]", "port": "[%key:common::config_flow::data::port%]" } } }, "error": { "unauthorized": "Pairing denied, check device for auth request", - "connection_error": "Failed to connect, please try again" + "connection_error": "[%key:common::config_flow::error::cannot_connect%]" }, "abort": { "already_configured": "This device has already been added to HomeAssistant" diff --git a/homeassistant/components/atag/translations/ca.json b/homeassistant/components/atag/translations/ca.json index 6677e7eaa4d4e6..d15cf197ffefd9 100644 --- a/homeassistant/components/atag/translations/ca.json +++ b/homeassistant/components/atag/translations/ca.json @@ -4,13 +4,13 @@ "already_configured": "Aquest dispositiu ja ha estat afegit a Home Assistant" }, "error": { - "connection_error": "No s'ha pogut connectar, torna-ho a provar", + "connection_error": "Ha fallat la connexi\u00f3", "unauthorized": "La vinculaci\u00f3 s'ha denegat, comprova si hi ha una sol\u00b7licitud d'autenticaci\u00f3 al dispositiu" }, "step": { "user": { "data": { - "email": "Correu electr\u00f2nic (opcional)", + "email": "Correu electr\u00f2nic", "host": "Amfitri\u00f3", "port": "Port" }, diff --git a/homeassistant/components/atag/translations/en.json b/homeassistant/components/atag/translations/en.json index 8901a417065b6a..3bd41082395def 100644 --- a/homeassistant/components/atag/translations/en.json +++ b/homeassistant/components/atag/translations/en.json @@ -4,13 +4,13 @@ "already_configured": "This device has already been added to HomeAssistant" }, "error": { - "connection_error": "Failed to connect, please try again", + "connection_error": "Failed to connect", "unauthorized": "Pairing denied, check device for auth request" }, "step": { "user": { "data": { - "email": "Email (Optional)", + "email": "Email", "host": "Host", "port": "Port" }, diff --git a/homeassistant/components/atag/translations/it.json b/homeassistant/components/atag/translations/it.json index cbef4fab268cd6..aee7a323ef5875 100644 --- a/homeassistant/components/atag/translations/it.json +++ b/homeassistant/components/atag/translations/it.json @@ -4,13 +4,13 @@ "already_configured": "Questo dispositivo \u00e8 gi\u00e0 stato aggiunto a HomeAssistant" }, "error": { - "connection_error": "Impossibile connettersi, si prega di riprovare", + "connection_error": "Impossibile connettersi", "unauthorized": "Associazione negata, controllare il dispositivo per la richiesta di autenticazione" }, "step": { "user": { "data": { - "email": "E-mail (Opzionale)", + "email": "E-mail", "host": "Host", "port": "Porta" }, diff --git a/homeassistant/components/atag/translations/no.json b/homeassistant/components/atag/translations/no.json index a0e428f286a274..690a8ce661ae67 100644 --- a/homeassistant/components/atag/translations/no.json +++ b/homeassistant/components/atag/translations/no.json @@ -4,13 +4,13 @@ "already_configured": "Denne enheten er allerede lagt til i HomeAssistant" }, "error": { - "connection_error": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen", + "connection_error": "Tilkobling mislyktes.", "unauthorized": "Parring nektet, sjekk enheten for autorisasjonsforesp\u00f8rsel" }, "step": { "user": { "data": { - "email": "E-post (valgfritt)", + "email": "E-post", "host": "Vert", "port": "" }, diff --git a/homeassistant/components/atag/translations/pl.json b/homeassistant/components/atag/translations/pl.json index d7d633aeda5cb0..37291614bee92c 100644 --- a/homeassistant/components/atag/translations/pl.json +++ b/homeassistant/components/atag/translations/pl.json @@ -4,7 +4,8 @@ "already_configured": "To urz\u0105dzenie zosta\u0142o ju\u017c dodane do Home Assistant" }, "error": { - "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie." + "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", + "unauthorized": "Odmowa parowania, sprawd\u017a urz\u0105dzenie pod k\u0105tem \u017c\u0105dania autoryzacji." }, "step": { "user": { diff --git a/homeassistant/components/atag/translations/ru.json b/homeassistant/components/atag/translations/ru.json index caf80b415e625d..c7bdcead280c17 100644 --- a/homeassistant/components/atag/translations/ru.json +++ b/homeassistant/components/atag/translations/ru.json @@ -4,13 +4,13 @@ "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." }, "error": { - "connection_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", + "connection_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "unauthorized": "\u0421\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435 \u0437\u0430\u043f\u0440\u0435\u0449\u0435\u043d\u043e, \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0430 \u0437\u0430\u043f\u0440\u043e\u0441 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438." }, "step": { "user": { "data": { - "email": "\u0410\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)", + "email": "\u0410\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b", "host": "\u0425\u043e\u0441\u0442", "port": "\u041f\u043e\u0440\u0442" }, diff --git a/homeassistant/components/atag/translations/zh-Hant.json b/homeassistant/components/atag/translations/zh-Hant.json index 6fc2e520d17dbd..5e3590f7a5dfd1 100644 --- a/homeassistant/components/atag/translations/zh-Hant.json +++ b/homeassistant/components/atag/translations/zh-Hant.json @@ -4,13 +4,13 @@ "already_configured": "\u6b64\u8a2d\u5099\u5df2\u7d93\u65b0\u589e\u81f3 Home Assistant" }, "error": { - "connection_error": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21", + "connection_error": "\u9023\u7dda\u5931\u6557", "unauthorized": "\u914d\u5c0d\u906d\u62d2\uff0c\u8acb\u6aa2\u67e5\u8a2d\u5099\u8a8d\u8b49\u8acb\u6c42" }, "step": { "user": { "data": { - "email": "\u90f5\u4ef6\uff08\u9078\u9805\uff09", + "email": "\u96fb\u5b50\u90f5\u4ef6", "host": "\u4e3b\u6a5f\u7aef", "port": "\u901a\u8a0a\u57e0" }, diff --git a/homeassistant/components/august/__init__.py b/homeassistant/components/august/__init__.py index e0d7749dcbb42f..feaf61450e8b34 100644 --- a/homeassistant/components/august/__init__.py +++ b/homeassistant/components/august/__init__.py @@ -3,13 +3,18 @@ import itertools import logging -from aiohttp import ClientError +from aiohttp import ClientError, ClientResponseError from august.authenticator import ValidationResult from august.exceptions import AugustApiAIOHTTPError import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME +from homeassistant.const import ( + CONF_PASSWORD, + CONF_TIMEOUT, + CONF_USERNAME, + HTTP_UNAUTHORIZED, +) from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError import homeassistant.helpers.config_validation as cv @@ -29,7 +34,7 @@ MIN_TIME_BETWEEN_DETAIL_UPDATES, VERIFICATION_CODE_KEY, ) -from .exceptions import InvalidAuth, RequireValidation +from .exceptions import CannotConnect, InvalidAuth, RequireValidation from .gateway import AugustGateway from .subscriber import AugustSubscriberMixin @@ -113,10 +118,7 @@ async def async_setup_august(hass, config_entry, august_gateway): await august_gateway.async_authenticate() except RequireValidation: await async_request_validation(hass, config_entry, august_gateway) - return False - except InvalidAuth: - _LOGGER.error("Password is no longer valid. Please set up August again") - return False + raise # We still use the configurator to get a new 2fa code # when needed since config_flow doesn't have a way @@ -171,8 +173,30 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): try: await august_gateway.async_setup(entry.data) return await async_setup_august(hass, entry, august_gateway) - except asyncio.TimeoutError as err: + except ClientResponseError as err: + if err.status == HTTP_UNAUTHORIZED: + _async_start_reauth(hass, entry) + return False + raise ConfigEntryNotReady from err + except InvalidAuth: + _async_start_reauth(hass, entry) + return False + except RequireValidation: + return False + except (CannotConnect, asyncio.TimeoutError) as err: + raise ConfigEntryNotReady from err + + +def _async_start_reauth(hass: HomeAssistant, entry: ConfigEntry): + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": "reauth"}, + data=entry.data, + ) + ) + _LOGGER.error("Password is no longer valid. Please reauthenticate") async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): diff --git a/homeassistant/components/august/config_flow.py b/homeassistant/components/august/config_flow.py index bf6f1d9cd8142d..f595479c0cfd65 100644 --- a/homeassistant/components/august/config_flow.py +++ b/homeassistant/components/august/config_flow.py @@ -4,7 +4,7 @@ from august.authenticator import ValidationResult import voluptuous as vol -from homeassistant import config_entries, core +from homeassistant import config_entries from homeassistant.const import CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME from .const import ( @@ -19,18 +19,8 @@ _LOGGER = logging.getLogger(__name__) -DATA_SCHEMA = vol.Schema( - { - vol.Required(CONF_LOGIN_METHOD, default="phone"): vol.In(LOGIN_METHODS), - vol.Required(CONF_USERNAME): str, - vol.Required(CONF_PASSWORD): str, - vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): vol.Coerce(int), - } -) - async def async_validate_input( - hass: core.HomeAssistant, data, august_gateway, ): @@ -79,6 +69,7 @@ def __init__(self): """Store an AugustGateway().""" self._august_gateway = None self.user_auth_details = {} + self._needs_reset = False super().__init__() async def async_step_user(self, user_input=None): @@ -87,30 +78,45 @@ async def async_step_user(self, user_input=None): self._august_gateway = AugustGateway(self.hass) errors = {} if user_input is not None: - await self._august_gateway.async_setup(user_input) + combined_inputs = {**self.user_auth_details, **user_input} + await self._august_gateway.async_setup(combined_inputs) + if self._needs_reset: + self._needs_reset = False + await self._august_gateway.async_reset_authentication() try: info = await async_validate_input( - self.hass, - user_input, + combined_inputs, self._august_gateway, ) - await self.async_set_unique_id(user_input[CONF_USERNAME]) - return self.async_create_entry(title=info["title"], data=info["data"]) except CannotConnect: errors["base"] = "cannot_connect" except InvalidAuth: errors["base"] = "invalid_auth" except RequireValidation: - self.user_auth_details = user_input + self.user_auth_details.update(user_input) return await self.async_step_validation() except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" + if not errors: + self.user_auth_details.update(user_input) + + existing_entry = await self.async_set_unique_id( + combined_inputs[CONF_USERNAME] + ) + if existing_entry: + self.hass.config_entries.async_update_entry( + existing_entry, data=info["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=info["title"], data=info["data"]) + return self.async_show_form( - step_id="user", data_schema=DATA_SCHEMA, errors=errors + step_id="user", data_schema=self._async_build_schema(), errors=errors ) async def async_step_validation(self, user_input=None): @@ -135,3 +141,23 @@ async def async_step_import(self, user_input): self._abort_if_unique_id_configured() return await self.async_step_user(user_input) + + async def async_step_reauth(self, data): + """Handle configuration by re-auth.""" + self.user_auth_details = dict(data) + self._needs_reset = True + return await self.async_step_user() + + def _async_build_schema(self): + """Generate the config flow schema.""" + base_schema = { + vol.Required(CONF_LOGIN_METHOD, default="phone"): vol.In(LOGIN_METHODS), + vol.Required(CONF_USERNAME): str, + vol.Required(CONF_PASSWORD): str, + vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): vol.Coerce(int), + } + for key in self.user_auth_details: + if key == CONF_PASSWORD or key not in base_schema: + continue + del base_schema[key] + return vol.Schema(base_schema) diff --git a/homeassistant/components/august/gateway.py b/homeassistant/components/august/gateway.py index 6918907611ffda..b72bb52e710925 100644 --- a/homeassistant/components/august/gateway.py +++ b/homeassistant/components/august/gateway.py @@ -2,12 +2,18 @@ import asyncio import logging +import os -from aiohttp import ClientError +from aiohttp import ClientError, ClientResponseError from august.api_async import ApiAsync from august.authenticator_async import AuthenticationState, AuthenticatorAsync -from homeassistant.const import CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME +from homeassistant.const import ( + CONF_PASSWORD, + CONF_TIMEOUT, + CONF_USERNAME, + HTTP_UNAUTHORIZED, +) from homeassistant.helpers import aiohttp_client from .const import ( @@ -32,29 +38,14 @@ def __init__(self, hass): self._access_token_cache_file = None self._hass = hass self._config = None - self._api = None - self._authenticator = None - self._authentication = None - - @property - def authenticator(self): - """August authentication object from py-august.""" - return self._authenticator - - @property - def authentication(self): - """August authentication object from py-august.""" - return self._authentication + self.api = None + self.authenticator = None + self.authentication = None @property def access_token(self): """Access token for the api.""" - return self._authentication.access_token - - @property - def api(self): - """August api object from py-august.""" - return self._api + return self.authentication.access_token def config_entry(self): """Config entry.""" @@ -78,12 +69,12 @@ async def async_setup(self, conf): ) self._config = conf - self._api = ApiAsync( + self.api = ApiAsync( self._aiohttp_session, timeout=self._config.get(CONF_TIMEOUT) ) - self._authenticator = AuthenticatorAsync( - self._api, + self.authenticator = AuthenticatorAsync( + self.api, self._config[CONF_LOGIN_METHOD], self._config[CONF_USERNAME], self._config[CONF_PASSWORD], @@ -93,30 +84,47 @@ async def async_setup(self, conf): ), ) - await self._authenticator.async_setup_authentication() + await self.authenticator.async_setup_authentication() async def async_authenticate(self): """Authenticate with the details provided to setup.""" - self._authentication = None + self.authentication = None try: - self._authentication = await self.authenticator.async_authenticate() + self.authentication = await self.authenticator.async_authenticate() + if self.authentication.state == AuthenticationState.AUTHENTICATED: + # Call the locks api to verify we are actually + # authenticated because we can be authenticated + # by have no access + await self.api.async_get_operable_locks(self.access_token) + except ClientResponseError as ex: + if ex.status == HTTP_UNAUTHORIZED: + raise InvalidAuth from ex + + raise CannotConnect from ex except ClientError as ex: _LOGGER.error("Unable to connect to August service: %s", str(ex)) raise CannotConnect from ex - if self._authentication.state == AuthenticationState.BAD_PASSWORD: + if self.authentication.state == AuthenticationState.BAD_PASSWORD: raise InvalidAuth - if self._authentication.state == AuthenticationState.REQUIRES_VALIDATION: + if self.authentication.state == AuthenticationState.REQUIRES_VALIDATION: raise RequireValidation - if self._authentication.state != AuthenticationState.AUTHENTICATED: - _LOGGER.error( - "Unknown authentication state: %s", self._authentication.state - ) + if self.authentication.state != AuthenticationState.AUTHENTICATED: + _LOGGER.error("Unknown authentication state: %s", self.authentication.state) raise InvalidAuth - return self._authentication + return self.authentication + + async def async_reset_authentication(self): + """Remove the cache file.""" + await self._hass.async_add_executor_job(self._reset_authentication) + + def _reset_authentication(self): + """Remove the cache file.""" + if os.path.exists(self._access_token_cache_file): + os.unlink(self._access_token_cache_file) async def async_refresh_access_token_if_needed(self): """Refresh the august access token if needed.""" @@ -130,4 +138,4 @@ async def async_refresh_access_token_if_needed(self): self.authentication.access_token_expires, refreshed_authentication.access_token_expires, ) - self._authentication = refreshed_authentication + self.authentication = refreshed_authentication diff --git a/homeassistant/components/august/strings.json b/homeassistant/components/august/strings.json index 880c13c7fe278c..998d870e629e03 100644 --- a/homeassistant/components/august/strings.json +++ b/homeassistant/components/august/strings.json @@ -1,12 +1,13 @@ { "config": { "error": { - "unknown": "Unexpected error", - "cannot_connect": "Failed to connect, please try again", - "invalid_auth": "Invalid authentication" + "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%]" }, "abort": { - "already_configured": "Account is already configured" + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" }, "step": { "validation": { @@ -28,4 +29,4 @@ } } } -} \ No newline at end of file +} diff --git a/homeassistant/components/august/translations/ca.json b/homeassistant/components/august/translations/ca.json index 4f8f9cebe63ec6..9d3e3535e46c6d 100644 --- a/homeassistant/components/august/translations/ca.json +++ b/homeassistant/components/august/translations/ca.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "El compte ja ha estat configurat" + "already_configured": "El compte ja ha estat configurat", + "reauth_successful": "Re-autenticaci\u00f3 realitzada correctament" }, "error": { - "cannot_connect": "No s'ha pogut connectar, torna-ho a provar", + "cannot_connect": "Ha fallat la connexi\u00f3", "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "unknown": "Error inesperat" }, diff --git a/homeassistant/components/august/translations/el.json b/homeassistant/components/august/translations/el.json new file mode 100644 index 00000000000000..08f214f7386300 --- /dev/null +++ b/homeassistant/components/august/translations/el.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "reauth_successful": "\u0397 \u03b5\u03c0\u03b1\u03bd\u03b1\u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03ae\u03c4\u03b1\u03bd \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03ae\u03c2" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/august/translations/en.json b/homeassistant/components/august/translations/en.json index b8bf1b1bc03c7e..c6c19321d8a5a0 100644 --- a/homeassistant/components/august/translations/en.json +++ b/homeassistant/components/august/translations/en.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "Account is already configured" + "already_configured": "Account is already configured", + "reauth_successful": "Re-authentication was successful" }, "error": { - "cannot_connect": "Failed to connect, please try again", + "cannot_connect": "Failed to connect", "invalid_auth": "Invalid authentication", "unknown": "Unexpected error" }, diff --git a/homeassistant/components/august/translations/es.json b/homeassistant/components/august/translations/es.json index 28d9743c073a8c..2ec72c9e4eb8bf 100644 --- a/homeassistant/components/august/translations/es.json +++ b/homeassistant/components/august/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": "La reautenticaci\u00f3n se realiz\u00f3 correctamente" }, "error": { "cannot_connect": "No se ha podido conectar, por favor, int\u00e9ntalo de nuevo.", diff --git a/homeassistant/components/august/translations/et.json b/homeassistant/components/august/translations/et.json new file mode 100644 index 00000000000000..af21b9d8204a6d --- /dev/null +++ b/homeassistant/components/august/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "reauth_successful": "Taasautentimine \u00f5nnestus" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/august/translations/fr.json b/homeassistant/components/august/translations/fr.json index 752b7dc3712656..82568b681fdf48 100644 --- a/homeassistant/components/august/translations/fr.json +++ b/homeassistant/components/august/translations/fr.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9" + "already_configured": "Le compte est d\u00e9j\u00e0 configur\u00e9", + "reauth_successful": "La r\u00e9-authentification a r\u00e9ussi" }, "error": { "cannot_connect": "Impossible de se connecter, veuillez r\u00e9essayer", diff --git a/homeassistant/components/august/translations/it.json b/homeassistant/components/august/translations/it.json index 3a5f2676acdb39..08332c29d7ecdf 100644 --- a/homeassistant/components/august/translations/it.json +++ b/homeassistant/components/august/translations/it.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "L'account \u00e8 gi\u00e0 configurato" + "already_configured": "L'account \u00e8 gi\u00e0 configurato", + "reauth_successful": "La riautenticazione ha avuto successo" }, "error": { - "cannot_connect": "Impossibile connettersi, si prega di riprovare.", + "cannot_connect": "Impossibile connettersi", "invalid_auth": "Autenticazione non valida", "unknown": "Errore imprevisto" }, diff --git a/homeassistant/components/august/translations/ko.json b/homeassistant/components/august/translations/ko.json index 52f939c45a0a76..c11bc55ec40dde 100644 --- a/homeassistant/components/august/translations/ko.json +++ b/homeassistant/components/august/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\uc774 \uc131\uacf5\ud588\uc2b5\ub2c8\ub2e4." }, "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", diff --git a/homeassistant/components/august/translations/lb.json b/homeassistant/components/august/translations/lb.json index 501af05c2dff7d..dbc71325a816c5 100644 --- a/homeassistant/components/august/translations/lb.json +++ b/homeassistant/components/august/translations/lb.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Kont ass scho konfigur\u00e9iert" + "already_configured": "Kont ass scho konfigur\u00e9iert", + "reauth_successful": "Re-Authentifikatioun erfollegr\u00e4ich" }, "error": { "cannot_connect": "Feeler beim verbannen, prob\u00e9iert w.e.g. nach emol.", diff --git a/homeassistant/components/august/translations/nl.json b/homeassistant/components/august/translations/nl.json index 1697f634d9a82e..e48d27801ccdc7 100644 --- a/homeassistant/components/august/translations/nl.json +++ b/homeassistant/components/august/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Account al geconfigureerd" + "already_configured": "Account al geconfigureerd", + "reauth_successful": "Herauthenticatie was succesvol" }, "error": { "cannot_connect": "Verbinding mislukt, probeer het opnieuw", diff --git a/homeassistant/components/august/translations/no.json b/homeassistant/components/august/translations/no.json index 838508f132dc30..764aa5624a63bf 100644 --- a/homeassistant/components/august/translations/no.json +++ b/homeassistant/components/august/translations/no.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "Kontoen er allerede konfigurert" + "already_configured": "Kontoen er allerede konfigurert", + "reauth_successful": "Reautentisering var vellykket" }, "error": { - "cannot_connect": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen", + "cannot_connect": "Tilkobling mislyktes.", "invalid_auth": "Ugyldig godkjenning", "unknown": "Uventet feil" }, diff --git a/homeassistant/components/august/translations/pl.json b/homeassistant/components/august/translations/pl.json index eeaa5269da45c3..1e046c286f1d6e 100644 --- a/homeassistant/components/august/translations/pl.json +++ b/homeassistant/components/august/translations/pl.json @@ -1,12 +1,13 @@ { "config": { "abort": { - "already_configured": "Konto jest ju\u017c skonfigurowane." + "already_configured": "Konto jest ju\u017c skonfigurowane", + "reauth_successful": "Ponowne uwierzytelnienie powiod\u0142o si\u0119" }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "invalid_auth": "Niepoprawne uwierzytelnienie.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "user": { diff --git a/homeassistant/components/august/translations/ru.json b/homeassistant/components/august/translations/ru.json index 9a49caed547bee..9ea0b531bf8b0c 100644 --- a/homeassistant/components/august/translations/ru.json +++ b/homeassistant/components/august/translations/ru.json @@ -1,10 +1,11 @@ { "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_configured": "\u042d\u0442\u0430 \u0443\u0447\u0451\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant.", + "reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430 \u0443\u0441\u043f\u0435\u0448\u043d\u043e." }, "error": { - "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", + "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "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/august/translations/zh-Hant.json b/homeassistant/components/august/translations/zh-Hant.json index 6b7e206d4c4f8c..667d881465962f 100644 --- a/homeassistant/components/august/translations/zh-Hant.json +++ b/homeassistant/components/august/translations/zh-Hant.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "reauth_successful": "\u91cd\u65b0\u8a8d\u8b49\u6210\u529f" }, "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21", + "cannot_connect": "\u9023\u7dda\u5931\u6557", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, diff --git a/homeassistant/components/auth/login_flow.py b/homeassistant/components/auth/login_flow.py index 31e3b7ea648d9d..725450a0a1208d 100644 --- a/homeassistant/components/auth/login_flow.py +++ b/homeassistant/components/auth/login_flow.py @@ -80,7 +80,11 @@ ) from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.components.http.view import HomeAssistantView -from homeassistant.const import HTTP_BAD_REQUEST, HTTP_NOT_FOUND +from homeassistant.const import ( + HTTP_BAD_REQUEST, + HTTP_METHOD_NOT_ALLOWED, + HTTP_NOT_FOUND, +) from . import indieauth @@ -153,7 +157,7 @@ def __init__(self, flow_mgr, store_result): async def get(self, request): """Do not allow index of flows in progress.""" - return web.Response(status=405) + return web.Response(status=HTTP_METHOD_NOT_ALLOWED) @RequestDataValidator( vol.Schema( diff --git a/homeassistant/components/automation/config.py b/homeassistant/components/automation/config.py index 2ac2b8d9354128..3a296178aeb9c0 100644 --- a/homeassistant/components/automation/config.py +++ b/homeassistant/components/automation/config.py @@ -10,7 +10,7 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_per_platform from homeassistant.helpers.condition import async_validate_condition_config -from homeassistant.helpers.script import async_validate_action_config +from homeassistant.helpers.script import async_validate_actions_config from homeassistant.helpers.trigger import async_validate_trigger_config from homeassistant.loader import IntegrationNotFound @@ -36,9 +36,7 @@ async def async_validate_config_item(hass, config, full_config=None): ] ) - config[CONF_ACTION] = await asyncio.gather( - *[async_validate_action_config(hass, action) for action in config[CONF_ACTION]] - ) + config[CONF_ACTION] = await async_validate_actions_config(hass, config[CONF_ACTION]) return config diff --git a/homeassistant/components/avri/translations/pl.json b/homeassistant/components/avri/translations/pl.json index 0881b1ec26a4af..088edbe79371ec 100644 --- a/homeassistant/components/avri/translations/pl.json +++ b/homeassistant/components/avri/translations/pl.json @@ -1,6 +1,10 @@ { "config": { + "abort": { + "already_configured": "Ten adres jest ju\u017c skonfigurowany." + }, "error": { + "invalid_country_code": "Nieznany dwuliterowy kod kraju.", "invalid_house_number": "Nieprawid\u0142owy numer domu" }, "step": { @@ -8,9 +12,13 @@ "data": { "country_code": "Dwuliterowy kod kraju", "house_number": "Numer domu", + "house_number_extension": "Numer mieszkania", "zip_code": "Kod pocztowy" - } + }, + "description": "Wpisz sw\u00f3j adres", + "title": "Avri" } } - } + }, + "title": "Avri" } \ No newline at end of file diff --git a/homeassistant/components/awair/const.py b/homeassistant/components/awair/const.py index e3c2a1761196ea..b262fdec572086 100644 --- a/homeassistant/components/awair/const.py +++ b/homeassistant/components/awair/const.py @@ -14,6 +14,7 @@ DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, + LIGHT_LUX, PERCENTAGE, TEMP_CELSIUS, ) @@ -63,7 +64,7 @@ API_LUX: { ATTR_DEVICE_CLASS: DEVICE_CLASS_ILLUMINANCE, ATTR_ICON: None, - ATTR_UNIT: "lx", + ATTR_UNIT: LIGHT_LUX, ATTR_LABEL: "Illuminance", ATTR_UNIQUE_ID: "illuminance", }, diff --git a/homeassistant/components/awair/strings.json b/homeassistant/components/awair/strings.json index 1351cbd2db0611..10041e19e56942 100644 --- a/homeassistant/components/awair/strings.json +++ b/homeassistant/components/awair/strings.json @@ -23,7 +23,7 @@ "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", "no_devices": "[%key:common::config_flow::abort::no_devices_found%]", - "reauth_successful": "[%key:common::config_flow::data::access_token%] updated successfully" + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" } } } diff --git a/homeassistant/components/awair/translations/de.json b/homeassistant/components/awair/translations/de.json index d0eb8e91b3e9ef..fcdcd0190e3ba2 100644 --- a/homeassistant/components/awair/translations/de.json +++ b/homeassistant/components/awair/translations/de.json @@ -5,7 +5,15 @@ }, "step": { "reauth": { + "data": { + "email": "E-Mail" + }, "description": "Bitte geben Sie Ihr Awair-Entwicklerzugriffstoken erneut ein." + }, + "user": { + "data": { + "email": "E-Mail" + } } } } diff --git a/homeassistant/components/awair/translations/hu.json b/homeassistant/components/awair/translations/hu.json new file mode 100644 index 00000000000000..436e8b1fb7dd75 --- /dev/null +++ b/homeassistant/components/awair/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/awair/translations/pl.json b/homeassistant/components/awair/translations/pl.json index 07983402c42d66..76fe5a91cd92e0 100644 --- a/homeassistant/components/awair/translations/pl.json +++ b/homeassistant/components/awair/translations/pl.json @@ -1,9 +1,9 @@ { "config": { "abort": { - "already_configured": "Konto jest ju\u017c skonfigurowane.", + "already_configured": "Konto jest ju\u017c skonfigurowane", "no_devices": "Nie znaleziono urz\u0105dze\u0144 w sieci.", - "reauth_successful": "Token dost\u0119pu pomy\u015blnie zaktualizowano." + "reauth_successful": "Token dost\u0119pu pomy\u015blnie zaktualizowano" }, "error": { "auth": "Token dost\u0119pu" diff --git a/homeassistant/components/axis/config_flow.py b/homeassistant/components/axis/config_flow.py index 5b300fe323bfad..ea1db54855b1ae 100644 --- a/homeassistant/components/axis/config_flow.py +++ b/homeassistant/components/axis/config_flow.py @@ -94,10 +94,10 @@ async def async_step_user(self, user_input=None): return await self._create_entry() except AuthenticationRequired: - errors["base"] = "faulty_credentials" + errors["base"] = "invalid_auth" except CannotConnect: - errors["base"] = "device_unavailable" + errors["base"] = "cannot_connect" data = self.discovery_schema or { vol.Required(CONF_HOST): str, diff --git a/homeassistant/components/axis/manifest.json b/homeassistant/components/axis/manifest.json index ceb926f326eb11..959d53a01ae35e 100644 --- a/homeassistant/components/axis/manifest.json +++ b/homeassistant/components/axis/manifest.json @@ -3,11 +3,11 @@ "name": "Axis", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/axis", - "requirements": ["axis==35"], + "requirements": ["axis==37"], "zeroconf": [ - {"type":"_axis-video._tcp.local.","macaddress":"00408C*"}, - {"type":"_axis-video._tcp.local.","macaddress":"ACCC8E*"}, - {"type":"_axis-video._tcp.local.","macaddress":"B8A44F*"} + { "type": "_axis-video._tcp.local.", "macaddress": "00408C*" }, + { "type": "_axis-video._tcp.local.", "macaddress": "ACCC8E*" }, + { "type": "_axis-video._tcp.local.", "macaddress": "B8A44F*" } ], "after_dependencies": ["mqtt"], "codeowners": ["@Kane610"] diff --git a/homeassistant/components/axis/strings.json b/homeassistant/components/axis/strings.json index 672bfe141b9d62..046b7fee475f6e 100644 --- a/homeassistant/components/axis/strings.json +++ b/homeassistant/components/axis/strings.json @@ -13,14 +13,13 @@ } }, "error": { - "already_configured": "Device is already configured", - "already_in_progress": "Config flow for device is already in progress.", - "device_unavailable": "Device is not available", - "faulty_credentials": "Bad user credentials" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" }, "abort": { - "already_configured": "Device is already configured", - "bad_config_file": "Bad data from configuration file", + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "link_local_address": "Link local addresses are not supported", "not_axis_device": "Discovered device not an Axis device" } diff --git a/homeassistant/components/axis/translations/ca.json b/homeassistant/components/axis/translations/ca.json index 8cbbe638d24bde..695a462d4168a4 100644 --- a/homeassistant/components/axis/translations/ca.json +++ b/homeassistant/components/axis/translations/ca.json @@ -8,7 +8,7 @@ }, "error": { "already_configured": "El dispositiu ja est\u00e0 configurat", - "already_in_progress": "El flux de dades de configuraci\u00f3 pel dispositiu ja est\u00e0 en curs.", + "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", "device_unavailable": "El dispositiu no est\u00e0 disponible", "faulty_credentials": "Credencials d'usuari incorrectes" }, diff --git a/homeassistant/components/axis/translations/en.json b/homeassistant/components/axis/translations/en.json index 29461ee0612551..bf1cdd7fda1686 100644 --- a/homeassistant/components/axis/translations/en.json +++ b/homeassistant/components/axis/translations/en.json @@ -8,7 +8,7 @@ }, "error": { "already_configured": "Device is already configured", - "already_in_progress": "Config flow for device is already in progress.", + "already_in_progress": "Configuration flow is already in progress", "device_unavailable": "Device is not available", "faulty_credentials": "Bad user credentials" }, diff --git a/homeassistant/components/axis/translations/it.json b/homeassistant/components/axis/translations/it.json index 1ad2baddb85cb6..fee94009b15168 100644 --- a/homeassistant/components/axis/translations/it.json +++ b/homeassistant/components/axis/translations/it.json @@ -8,7 +8,7 @@ }, "error": { "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", - "already_in_progress": "Il flusso di configurazione per il dispositivo \u00e8 gi\u00e0 in corso.", + "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "device_unavailable": "Il dispositivo non \u00e8 disponibile", "faulty_credentials": "Credenziali utente non valide" }, diff --git a/homeassistant/components/axis/translations/no.json b/homeassistant/components/axis/translations/no.json index 039e6138753838..c0b68c93d20c98 100644 --- a/homeassistant/components/axis/translations/no.json +++ b/homeassistant/components/axis/translations/no.json @@ -8,7 +8,7 @@ }, "error": { "already_configured": "Enheten er allerede konfigurert", - "already_in_progress": "Konfigurasjonsflyt for enhet p\u00e5g\u00e5r allerede.", + "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", "device_unavailable": "Enheten er ikke tilgjengelig", "faulty_credentials": "Ugyldig brukerlegitimasjon" }, diff --git a/homeassistant/components/axis/translations/pl.json b/homeassistant/components/axis/translations/pl.json index e6d55f5a5796a3..154577573bbe8b 100644 --- a/homeassistant/components/axis/translations/pl.json +++ b/homeassistant/components/axis/translations/pl.json @@ -1,13 +1,13 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", "bad_config_file": "B\u0142\u0119dne dane z pliku konfiguracyjnego", "link_local_address": "Po\u0142\u0105czenie lokalnego adresu nie jest obs\u0142ugiwane", "not_axis_device": "Wykryte urz\u0105dzenie nie jest urz\u0105dzeniem Axis" }, "error": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", "already_in_progress": "Konfiguracja urz\u0105dzenia jest ju\u017c w toku.", "device_unavailable": "Urz\u0105dzenie jest niedost\u0119pne", "faulty_credentials": "B\u0142\u0119dne dane uwierzytelniaj\u0105ce" diff --git a/homeassistant/components/axis/translations/ru.json b/homeassistant/components/axis/translations/ru.json index 4f8aea64e78be7..d8be3a1723cd75 100644 --- a/homeassistant/components/axis/translations/ru.json +++ b/homeassistant/components/axis/translations/ru.json @@ -8,7 +8,7 @@ }, "error": { "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", - "already_in_progress": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.", + "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.", "device_unavailable": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e.", "faulty_credentials": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0435 \u0443\u0447\u0451\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435." }, diff --git a/homeassistant/components/axis/translations/zh-Hant.json b/homeassistant/components/axis/translations/zh-Hant.json index 8e90d449f11f80..8fb6154719627a 100644 --- a/homeassistant/components/axis/translations/zh-Hant.json +++ b/homeassistant/components/axis/translations/zh-Hant.json @@ -8,7 +8,7 @@ }, "error": { "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", - "already_in_progress": "\u8a2d\u5099\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d\u3002", + "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "device_unavailable": "\u8a2d\u5099\u7121\u6cd5\u4f7f\u7528", "faulty_credentials": "\u4f7f\u7528\u8005\u6191\u8b49\u7121\u6548" }, diff --git a/homeassistant/components/azure_devops/__init__.py b/homeassistant/components/azure_devops/__init__.py index e08dd4d8559e60..f72a4c44918d48 100644 --- a/homeassistant/components/azure_devops/__init__.py +++ b/homeassistant/components/azure_devops/__init__.py @@ -126,4 +126,5 @@ def device_info(self) -> Dict[str, Any]: }, "manufacturer": self.organization, "name": self.project, + "entry_type": "service", } diff --git a/homeassistant/components/azure_devops/strings.json b/homeassistant/components/azure_devops/strings.json index 2bb53010153ad7..64ad79c7698e7e 100644 --- a/homeassistant/components/azure_devops/strings.json +++ b/homeassistant/components/azure_devops/strings.json @@ -26,7 +26,7 @@ }, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", - "reauth_successful": "[%key:common::config_flow::data::access_token%] updated successfully" + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" } }, "title": "Azure DevOps" diff --git a/homeassistant/components/azure_devops/translations/fr.json b/homeassistant/components/azure_devops/translations/fr.json index 7d50110a24e438..528c76767ea89d 100644 --- a/homeassistant/components/azure_devops/translations/fr.json +++ b/homeassistant/components/azure_devops/translations/fr.json @@ -3,6 +3,31 @@ "abort": { "already_configured": "Le compte a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9", "reauth_successful": "Jeton d'acc\u00e8s mis \u00e0 jour avec succ\u00e8s" + }, + "error": { + "authorization_error": "Erreur d'autorisation. V\u00e9rifiez que vous avez acc\u00e8s au projet et que vous disposez des informations d'identification correctes.", + "connection_error": "Impossible de se connecter \u00e0 Azure DevOps.", + "project_error": "Impossible d'obtenir les informations sur le projet." + }, + "flow_title": "Azure DevOps: {project_url}", + "step": { + "reauth": { + "data": { + "personal_access_token": "Jeton d'acc\u00e8s personnel (PAT)" + }, + "description": "L'authentification a \u00e9chou\u00e9 pour {project_url} . Veuillez saisir vos informations d'identification actuelles.", + "title": "R\u00e9authentification" + }, + "user": { + "data": { + "organization": "Organisation", + "personal_access_token": "Jeton d'acc\u00e8s personnel (PAT)", + "project": "Projet" + }, + "description": "Configurez une instance Azure DevOps pour acc\u00e9der \u00e0 votre projet. Un jeton d'acc\u00e8s personnel n'est requis que pour un projet priv\u00e9.", + "title": "Ajouter un projet Azure DevOps" + } } - } + }, + "title": "Azure DevOps" } \ No newline at end of file diff --git a/homeassistant/components/azure_devops/translations/hu.json b/homeassistant/components/azure_devops/translations/hu.json new file mode 100644 index 00000000000000..436e8b1fb7dd75 --- /dev/null +++ b/homeassistant/components/azure_devops/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/azure_devops/translations/pl.json b/homeassistant/components/azure_devops/translations/pl.json new file mode 100644 index 00000000000000..21afd65c49aab0 --- /dev/null +++ b/homeassistant/components/azure_devops/translations/pl.json @@ -0,0 +1,33 @@ +{ + "config": { + "abort": { + "already_configured": "Konto jest ju\u017c skonfigurowane", + "reauth_successful": "Token dost\u0119pu pomy\u015blnie zaktualizowany" + }, + "error": { + "authorization_error": "B\u0142\u0105d autoryzacji. Sprawd\u017a, czy masz dost\u0119p do projektu i masz prawid\u0142owe po\u015bwiadczenia.", + "connection_error": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z us\u0142ug\u0105 Azure DevOps.", + "project_error": "Nie mo\u017cna uzyska\u0107 informacji o projekcie." + }, + "flow_title": "Azure DevOps: {project_url}", + "step": { + "reauth": { + "data": { + "personal_access_token": "Osobisty token dost\u0119pu (PAT)" + }, + "description": "Uwierzytelnianie dla {project_url} nie powiod\u0142o si\u0119. Wprowad\u017a aktualne dane uwierzytelniaj\u0105ce.", + "title": "Ponowne uwierzytelnianie" + }, + "user": { + "data": { + "organization": "Organizacja", + "personal_access_token": "Osobisty token dost\u0119pu (PAT)", + "project": "Projekt" + }, + "description": "Skonfiguruj instancj\u0119 Azure DevOps, aby uzyska\u0107 dost\u0119p do swojego projektu. Osobisty token dost\u0119pu (PAT) jest wymagany tylko dla prywatnego projektu.", + "title": "Dodaj projekt Azure DevOps" + } + } + }, + "title": "Azure DevOps" +} \ No newline at end of file diff --git a/homeassistant/components/azure_service_bus/manifest.json b/homeassistant/components/azure_service_bus/manifest.json index 9e3f0e956e5866..d7a232d8d1ac8b 100644 --- a/homeassistant/components/azure_service_bus/manifest.json +++ b/homeassistant/components/azure_service_bus/manifest.json @@ -2,6 +2,6 @@ "domain": "azure_service_bus", "name": "Azure Service Bus", "documentation": "https://www.home-assistant.io/integrations/azure_service_bus", - "requirements": ["azure-servicebus==0.50.1"], + "requirements": ["azure-servicebus==0.50.3"], "codeowners": ["@hfurubotten"] } diff --git a/homeassistant/components/bayesian/binary_sensor.py b/homeassistant/components/bayesian/binary_sensor.py index 90540e456c56ef..4768b3f4fe6261 100644 --- a/homeassistant/components/bayesian/binary_sensor.py +++ b/homeassistant/components/bayesian/binary_sensor.py @@ -182,6 +182,7 @@ def async_threshold_sensor_state_listener(event): entity = event.data.get("entity_id") self.current_observations.update(self._record_entity_observations(entity)) + self.async_set_context(event.context) self._recalculate_and_write_state() self.async_on_remove( @@ -220,6 +221,8 @@ def _async_template_result_changed(event, updates): obs_entry = None self.current_observations[obs["id"]] = obs_entry + if event: + self.async_set_context(event.context) self._recalculate_and_write_state() for template in self.observations_by_template: diff --git a/homeassistant/components/bh1750/sensor.py b/homeassistant/components/bh1750/sensor.py index df8e87f751d179..4d4067eb77deca 100644 --- a/homeassistant/components/bh1750/sensor.py +++ b/homeassistant/components/bh1750/sensor.py @@ -7,7 +7,7 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_NAME, DEVICE_CLASS_ILLUMINANCE +from homeassistant.const import CONF_NAME, DEVICE_CLASS_ILLUMINANCE, LIGHT_LUX import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -37,7 +37,6 @@ ONE_TIME_HIGH_RES_MODE_2: (0x21, False), # 0.5lx resolution. } -SENSOR_UNIT = "lx" DEFAULT_NAME = "BH1750 Light Sensor" DEFAULT_I2C_ADDRESS = "0x23" DEFAULT_I2C_BUS = 1 @@ -85,7 +84,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= _LOGGER.error("BH1750 sensor not detected at %s", i2c_address) return False - dev = [BH1750Sensor(sensor, name, SENSOR_UNIT, config[CONF_MULTIPLIER])] + dev = [BH1750Sensor(sensor, name, LIGHT_LUX, config[CONF_MULTIPLIER])] _LOGGER.info( "Setup of BH1750 light sensor at %s in mode %s is complete", i2c_address, diff --git a/homeassistant/components/binary_sensor/group.py b/homeassistant/components/binary_sensor/group.py new file mode 100644 index 00000000000000..1636054663dc69 --- /dev/null +++ b/homeassistant/components/binary_sensor/group.py @@ -0,0 +1,15 @@ +"""Describe group states.""" + + +from homeassistant.components.group import GroupIntegrationRegistry +from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.core import callback +from homeassistant.helpers.typing import HomeAssistantType + + +@callback +def async_describe_on_off_states( + hass: HomeAssistantType, registry: GroupIntegrationRegistry +) -> None: + """Describe group on off states.""" + registry.on_off_states({STATE_ON}, STATE_OFF) diff --git a/homeassistant/components/binary_sensor/translations/et.json b/homeassistant/components/binary_sensor/translations/et.json index a9da1be9ee2888..c074c56675ac2e 100644 --- a/homeassistant/components/binary_sensor/translations/et.json +++ b/homeassistant/components/binary_sensor/translations/et.json @@ -1,4 +1,94 @@ { + "device_automation": { + "condition_type": { + "is_bat_low": "{entity_name} aku on t\u00fchjenemas", + "is_cold": "{entity_name} on k\u00fclm", + "is_connected": "{entity_name} on \u00fchendatud", + "is_gas": "{entity_name} tuvastab gaasi(leket)", + "is_hot": "{entity_name} on kuum", + "is_light": "{entity_name} tuvastab valgust", + "is_locked": "{entity_name} on lukustatud", + "is_moist": "{entity_name} on niiske", + "is_motion": "{entity_name} tuvastab liikumist", + "is_moving": "{entity_name} liigub", + "is_no_gas": "{entity_name} ei tuvasta gaasi(leket)", + "is_no_light": "{entity_name} ei tuvasta valgust", + "is_no_motion": "{entity_name} ei tuvasta liikumist", + "is_no_problem": "{entity_name} ei leia probleemi", + "is_no_smoke": "{entity_name} ei tuvasta suitsu", + "is_no_sound": "{entity_name} ei tuvasta heli", + "is_no_vibration": "{entity_name} ei tuvasta vibratsiooni", + "is_not_bat_low": "{entity_name} aku on laetud", + "is_not_cold": "{entity_name} ei ole k\u00fclm", + "is_not_connected": "{entity_name} pole \u00fchendatud", + "is_not_hot": "{entity_name} ei ole kuum", + "is_not_locked": "{entity_name} on lukustamata", + "is_not_moist": "{entity_name} on kuiv", + "is_not_moving": "{entity_name} liikumist ei tuvastatud", + "is_not_occupied": "{entity_name} pole h\u00f5ivatud", + "is_not_open": "{entity_name} on suletud", + "is_not_plugged_in": "{entity_name} on lahti \u00fchendatud", + "is_not_powered": "{entity_name} ei ole voolu all", + "is_not_present": "{entity_name} puudub", + "is_not_unsafe": "{entity_name} on turvaline", + "is_occupied": "{entity_name} on h\u00f5ivatud", + "is_off": "{entity_name} on v\u00e4lja l\u00fclitatud", + "is_on": "{entity_name} on sisse l\u00fclitatud", + "is_open": "{entity_name} on avatud", + "is_plugged_in": "{entity_name} on \u00fchendatud", + "is_powered": "{entity_name} on voolu all", + "is_present": "{entity_name} on saadaval", + "is_problem": "Olemil {entity_name} on probleem", + "is_smoke": "{entity_name} tuvastab suitsu", + "is_sound": "{entity_name} tuvastab heli", + "is_unsafe": "{entity_name} on ebaturvaline", + "is_vibration": "{entity_name} tuvastab vibratsiooni" + }, + "trigger_type": { + "bat_low": "{entity_name} aku hakkab t\u00fchjaks saama", + "cold": "{entity_name} muutus k\u00fclmaks", + "connected": "{entity_name} on \u00fchendatud", + "gas": "{entity_name} tuvastas gaasi(leket)", + "hot": "{entity_name} muutus kuumaks", + "light": "{entity_name} tuvastas valgust", + "locked": "{entity_name} on lukus", + "moist": "{entity_name} muutus niiskeks", + "motion": "{entity_name} tuvastas liikumist", + "moving": "{entity_name} hakkas liikuma", + "no_gas": "{entity_name} l\u00f5petas gaasi(lekke) tuvastamise", + "no_light": "{entity_name} l\u00f5petas valguse tuvastamise", + "no_motion": "{entity_name} l\u00f5petas liikumise tuvastamise", + "no_problem": "{entity_name} l\u00f5petas probleemi tuvastamise", + "no_smoke": "{entity_name} l\u00f5petas suitsu tuvastamise", + "no_sound": "{entity_name} l\u00f5petas heli tuvastamise", + "no_vibration": "{entity_name} l\u00f5petas vibratsiooni tuvastamise", + "not_bat_low": "{entity_name} aku on laetud", + "not_cold": "{entity_name} ei ole enam k\u00fclm", + "not_connected": "{entity_name} on lahti \u00fchendatud", + "not_hot": "{entity_name} ei ole enam kuum", + "not_locked": "{entity_name} on lukustamata", + "not_moist": "{entity_name} muutus kuivaks", + "not_moving": "{entity_name} liikumine peatus", + "not_occupied": "{entity_name} vabanes h\u00f5ivest", + "not_opened": "{entity_name} sulgus", + "not_plugged_in": "{entity_name} \u00fchendati vooluv\u00f5rgust v\u00e4lja", + "not_powered": "{entity_name} pole toidet", + "not_present": "{entity_name} puudub", + "not_unsafe": "{entity_name} muutus turvaliseks", + "occupied": "{entity_name} h\u00f5ivati", + "opened": "{entity_name} avanes", + "plugged_in": "{entity_name} \u00fchendati", + "powered": "{entity_name} l\u00fcltus voolu alla", + "present": "{entity_name} on saadaval", + "problem": "{entity_name} avastas probleemi", + "smoke": "{entity_name} tuvastas suitsu", + "sound": "{entity_name} tuvastas heli", + "turned_off": "{entity_name} l\u00fclitus v\u00e4lja", + "turned_on": "{entity_name} l\u00fclitus sisse", + "unsafe": "{entity_name} on ebaturvaline", + "vibration": "{entity_name} registreeris vibratsiooni" + } + }, "state": { "_": { "off": "V\u00e4ljas", @@ -41,8 +131,8 @@ "on": "M\u00e4rg" }, "motion": { - "off": "Puudub", - "on": "Tuvastatud" + "off": "Liikumine puudub", + "on": "Liikumine tuvastatud" }, "occupancy": { "off": "Puudub", diff --git a/homeassistant/components/binary_sensor/translations/uk.json b/homeassistant/components/binary_sensor/translations/uk.json index 7b01acae4fb55b..29767f6d6d62e5 100644 --- a/homeassistant/components/binary_sensor/translations/uk.json +++ b/homeassistant/components/binary_sensor/translations/uk.json @@ -1,4 +1,18 @@ { + "device_automation": { + "condition_type": { + "is_bat_low": "{entity_name} \u043d\u0438\u0437\u044c\u043a\u0438\u0439 \u0440\u0456\u0432\u0435\u043d\u044c \u0430\u043a\u0443\u043c\u0443\u043b\u044f\u0442\u043e\u0440\u0430", + "is_not_bat_low": "{entity_name} \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u0438\u043c \u0437\u0430\u0440\u044f\u0434 \u0430\u043a\u0443\u043c\u0443\u043b\u044f\u0442\u043e\u0440\u0430" + }, + "trigger_type": { + "bat_low": "{entity_name} \u043d\u0438\u0437\u044c\u043a\u0438\u0439 \u0437\u0430\u0440\u044f\u0434 \u0430\u043a\u0443\u043c\u0443\u043b\u044f\u0442\u043e\u0440\u0430", + "not_bat_low": "{entity_name} \u043d\u043e\u0440\u043c\u0430\u043b\u044c\u043d\u0438\u0439 \u0430\u043a\u0443\u043c\u0443\u043b\u044f\u0442\u043e\u0440", + "not_opened": "{entity_name} \u0437\u0430\u043a\u0440\u0438\u0442\u043e", + "opened": "{entity_name} \u0432\u0456\u0434\u043a\u0440\u0438\u0442\u043e", + "turned_off": "{entity_name} \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e", + "turned_on": "{entity_name} \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e" + } + }, "state": { "_": { "off": "\u0412\u0438\u043c\u043a\u043d\u0435\u043d\u043e", diff --git a/homeassistant/components/blebox/strings.json b/homeassistant/components/blebox/strings.json index f929d62d8d90c0..9a3c261a34a1b8 100644 --- a/homeassistant/components/blebox/strings.json +++ b/homeassistant/components/blebox/strings.json @@ -1,13 +1,13 @@ { "config": { "abort": { - "already_configured": "This BleBox device is already configured.", + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "address_already_configured": "A BleBox device is already configured at {address}." }, "error": { - "cannot_connect": "Unable to connect to the BleBox device. (Check the logs for errors.)", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "unsupported_version": "BleBox device has outdated firmware. Please upgrade it first.", - "unknown": "Unknown error while connecting to the BleBox device. (Check the logs for errors.)" + "unknown": "[%key:common::config_flow::error::unknown%]" }, "flow_title": "BleBox device: {name} ({host})", "step": { diff --git a/homeassistant/components/blebox/translations/ca.json b/homeassistant/components/blebox/translations/ca.json index 39bf371fac1dd4..aba528fafb57d0 100644 --- a/homeassistant/components/blebox/translations/ca.json +++ b/homeassistant/components/blebox/translations/ca.json @@ -2,11 +2,11 @@ "config": { "abort": { "address_already_configured": "Ja hi ha un dispositiu BleBox configurat a {address}.", - "already_configured": "Aquest dispositiu BleBox ja est\u00e0 configurat" + "already_configured": "El dispositiu ja est\u00e0 configurat" }, "error": { - "cannot_connect": "No s'ha pogut connectar al dispositiu BleBox. (Consulta els registres per veure-hi els errors).", - "unknown": "S'ha produ\u00eft un error desconegut en connectar-se al dispositiu BleBox. (Consulta els registres per veure-hi els errors).", + "cannot_connect": "Ha fallat la connexi\u00f3", + "unknown": "Error inesperat", "unsupported_version": "El dispositiu BleBox t\u00e9 un firmware obsolet. Primer actualitza'l." }, "flow_title": "Dispositiu BleBox: {name} ({host})", diff --git a/homeassistant/components/blebox/translations/en.json b/homeassistant/components/blebox/translations/en.json index 29f7a03bb3145d..7ff38e25343279 100644 --- a/homeassistant/components/blebox/translations/en.json +++ b/homeassistant/components/blebox/translations/en.json @@ -2,11 +2,11 @@ "config": { "abort": { "address_already_configured": "A BleBox device is already configured at {address}.", - "already_configured": "This BleBox device is already configured." + "already_configured": "Device is already configured" }, "error": { - "cannot_connect": "Unable to connect to the BleBox device. (Check the logs for errors.)", - "unknown": "Unknown error while connecting to the BleBox device. (Check the logs for errors.)", + "cannot_connect": "Failed to connect", + "unknown": "Unexpected error", "unsupported_version": "BleBox device has outdated firmware. Please upgrade it first." }, "flow_title": "BleBox device: {name} ({host})", diff --git a/homeassistant/components/blebox/translations/fr.json b/homeassistant/components/blebox/translations/fr.json index 75d506a82128e4..d30d026d177dca 100644 --- a/homeassistant/components/blebox/translations/fr.json +++ b/homeassistant/components/blebox/translations/fr.json @@ -6,7 +6,8 @@ }, "error": { "cannot_connect": "Impossible de connecter le p\u00e9riph\u00e9rique BleBox. (V\u00e9rifiez les journaux pour les erreurs.)", - "unknown": "Erreur inconnue lors de la connexion au p\u00e9riph\u00e9rique BleBox. (V\u00e9rifiez les journaux pour les erreurs.)" + "unknown": "Erreur inconnue lors de la connexion au p\u00e9riph\u00e9rique BleBox. (V\u00e9rifiez les journaux pour les erreurs.)", + "unsupported_version": "L'appareil BleBox a un micrologiciel obsol\u00e8te. Veuillez d'abord le mettre \u00e0 jour." }, "flow_title": "P\u00e9riph\u00e9rique Blebox: {name} ({host)}", "step": { diff --git a/homeassistant/components/blebox/translations/it.json b/homeassistant/components/blebox/translations/it.json index 73f7b9276e92bc..265a158e22d628 100644 --- a/homeassistant/components/blebox/translations/it.json +++ b/homeassistant/components/blebox/translations/it.json @@ -2,11 +2,11 @@ "config": { "abort": { "address_already_configured": "Un dispositivo BleBox \u00e8 gi\u00e0 configurato in {address}.", - "already_configured": "Questo dispositivo BleBox \u00e8 gi\u00e0 configurato." + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" }, "error": { - "cannot_connect": "Impossibile connettersi al dispositivo BleBox. (Controllare i registri per errori).", - "unknown": "Errore sconosciuto durante la connessione al dispositivo BleBox. (Controllare i registri per errori).", + "cannot_connect": "Impossibile connettersi", + "unknown": "Errore imprevisto", "unsupported_version": "Il dispositivo BleBox ha un firmware obsoleto. Si prega di aggiornarlo prima." }, "flow_title": "Dispositivo BleBox: {name} ({host})", diff --git a/homeassistant/components/blebox/translations/no.json b/homeassistant/components/blebox/translations/no.json index 239d1fb03c6af8..7bede9535e79af 100644 --- a/homeassistant/components/blebox/translations/no.json +++ b/homeassistant/components/blebox/translations/no.json @@ -2,11 +2,11 @@ "config": { "abort": { "address_already_configured": "En BleBox-enhet er allerede konfigurert p\u00e5 {address} .", - "already_configured": "Denne BleBox-enheten er allerede konfigurert." + "already_configured": "Enheten er allerede konfigurert" }, "error": { - "cannot_connect": "Kan ikke koble til BleBox-enheten. (Kontroller loggene for feil.)", - "unknown": "Ukjent feil under tilkobling til BleBox-enheten. (Kontroller loggene for feil.)", + "cannot_connect": "Tilkobling mislyktes.", + "unknown": "Uventet feil", "unsupported_version": "BleBox-enheten har utdatert fastvare. Vennligst oppgrader den f\u00f8rst." }, "flow_title": "BleBox-enhet: {name} ({host})", diff --git a/homeassistant/components/blebox/translations/ru.json b/homeassistant/components/blebox/translations/ru.json index b82261be7f768b..4fd361021ebd57 100644 --- a/homeassistant/components/blebox/translations/ru.json +++ b/homeassistant/components/blebox/translations/ru.json @@ -2,11 +2,11 @@ "config": { "abort": { "address_already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0441 \u0430\u0434\u0440\u0435\u0441\u043e\u043c {address } \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043b\u043e\u0433\u0438 \u043d\u0430 \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u043e\u0448\u0438\u0431\u043e\u043a.", - "unknown": "\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. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043b\u043e\u0433\u0438 \u043d\u0430 \u043d\u0430\u043b\u0438\u0447\u0438\u0435 \u043e\u0448\u0438\u0431\u043e\u043a.", + "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.", "unsupported_version": "\u041f\u0440\u043e\u0448\u0438\u0432\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0441\u0442\u0430\u0440\u0435\u043b\u0430. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u0435 \u0435\u0451." }, "flow_title": "BleBox device: {name} ({host})", diff --git a/homeassistant/components/blink/sensor.py b/homeassistant/components/blink/sensor.py index 35cc2d0d5a5d0e..3c3adf6d990e6d 100644 --- a/homeassistant/components/blink/sensor.py +++ b/homeassistant/components/blink/sensor.py @@ -4,6 +4,7 @@ from homeassistant.const import ( DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_TEMPERATURE, + SIGNAL_STRENGTH_DECIBELS_MILLIWATT, TEMP_FAHRENHEIT, ) from homeassistant.helpers.entity import Entity @@ -14,7 +15,11 @@ SENSORS = { TYPE_TEMPERATURE: ["Temperature", TEMP_FAHRENHEIT, DEVICE_CLASS_TEMPERATURE], - TYPE_WIFI_STRENGTH: ["Wifi Signal", "dBm", DEVICE_CLASS_SIGNAL_STRENGTH], + TYPE_WIFI_STRENGTH: [ + "Wifi Signal", + SIGNAL_STRENGTH_DECIBELS_MILLIWATT, + DEVICE_CLASS_SIGNAL_STRENGTH, + ], } diff --git a/homeassistant/components/blink/translations/ko.json b/homeassistant/components/blink/translations/ko.json index ef3ffc108e57b3..ac8c96e4f2d7a5 100644 --- a/homeassistant/components/blink/translations/ko.json +++ b/homeassistant/components/blink/translations/ko.json @@ -4,6 +4,8 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { + "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", + "invalid_access_token": "\uc798\ubabb\ub41c \uc778\uc99d", "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" }, diff --git a/homeassistant/components/blink/translations/pl.json b/homeassistant/components/blink/translations/pl.json index 7d6d01266d97a6..50436c77e824ca 100644 --- a/homeassistant/components/blink/translations/pl.json +++ b/homeassistant/components/blink/translations/pl.json @@ -1,11 +1,13 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" }, "error": { - "invalid_auth": "Niepoprawne uwierzytelnienie.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_access_token": "Niepoprawny token dost\u0119pu", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "2fa": { @@ -23,5 +25,16 @@ "title": "Zaloguj si\u0119 za pomoc\u0105 konta Blink" } } + }, + "options": { + "step": { + "simple_options": { + "data": { + "scan_interval": "Cz\u0119stotliwo\u015b\u0107 skanowania (w sekundach)" + }, + "description": "Konfigurowanie integracji Blink", + "title": "Opcje Blink" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/blinksticklight/light.py b/homeassistant/components/blinksticklight/light.py index 56009c90da3731..71e6a6b2c71786 100644 --- a/homeassistant/components/blinksticklight/light.py +++ b/homeassistant/components/blinksticklight/light.py @@ -54,11 +54,6 @@ def __init__(self, stick, name): self._hs_color = None self._brightness = None - @property - def should_poll(self): - """Set up polling.""" - return True - @property def name(self): """Return the name of the light.""" diff --git a/homeassistant/components/bloomsky/__init__.py b/homeassistant/components/bloomsky/__init__.py index 929f8218144ae9..cd993e0332a366 100644 --- a/homeassistant/components/bloomsky/__init__.py +++ b/homeassistant/components/bloomsky/__init__.py @@ -6,7 +6,12 @@ import requests import voluptuous as vol -from homeassistant.const import CONF_API_KEY, HTTP_OK +from homeassistant.const import ( + CONF_API_KEY, + HTTP_METHOD_NOT_ALLOWED, + HTTP_OK, + HTTP_UNAUTHORIZED, +) from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle @@ -67,9 +72,9 @@ def refresh_devices(self): headers={AUTHORIZATION: self._api_key}, timeout=10, ) - if response.status_code == 401: + if response.status_code == HTTP_UNAUTHORIZED: raise RuntimeError("Invalid API_KEY") - if response.status_code == 405: + if response.status_code == HTTP_METHOD_NOT_ALLOWED: _LOGGER.error("You have no bloomsky devices configured") return if response.status_code != HTTP_OK: diff --git a/homeassistant/components/bloomsky/binary_sensor.py b/homeassistant/components/bloomsky/binary_sensor.py index 077171006bf807..43c5614679c324 100644 --- a/homeassistant/components/bloomsky/binary_sensor.py +++ b/homeassistant/components/bloomsky/binary_sensor.py @@ -3,7 +3,11 @@ import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_MOISTURE, + PLATFORM_SCHEMA, + BinarySensorEntity, +) from homeassistant.const import CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv @@ -11,7 +15,7 @@ _LOGGER = logging.getLogger(__name__) -SENSOR_TYPES = {"Rain": "moisture", "Night": None} +SENSOR_TYPES = {"Rain": DEVICE_CLASS_MOISTURE, "Night": None} PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { diff --git a/homeassistant/components/bloomsky/sensor.py b/homeassistant/components/bloomsky/sensor.py index 0ddeec6a5773a3..eaa03ef2f3b0eb 100644 --- a/homeassistant/components/bloomsky/sensor.py +++ b/homeassistant/components/bloomsky/sensor.py @@ -5,8 +5,11 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA from homeassistant.const import ( + AREA_SQUARE_METERS, CONF_MONITORED_CONDITIONS, PERCENTAGE, + PRESSURE_INHG, + PRESSURE_MBAR, TEMP_CELSIUS, TEMP_FAHRENHEIT, ) @@ -31,8 +34,8 @@ SENSOR_UNITS_IMPERIAL = { "Temperature": TEMP_FAHRENHEIT, "Humidity": PERCENTAGE, - "Pressure": "inHg", - "Luminance": "cd/m²", + "Pressure": PRESSURE_INHG, + "Luminance": f"cd/{AREA_SQUARE_METERS}", "Voltage": "mV", } @@ -40,8 +43,8 @@ SENSOR_UNITS_METRIC = { "Temperature": TEMP_CELSIUS, "Humidity": PERCENTAGE, - "Pressure": "mbar", - "Luminance": "cd/m²", + "Pressure": PRESSURE_MBAR, + "Luminance": f"cd/{AREA_SQUARE_METERS}", "Voltage": "mV", } diff --git a/homeassistant/components/bmw_connected_drive/binary_sensor.py b/homeassistant/components/bmw_connected_drive/binary_sensor.py index ee89873e8feff0..31ef2dacf3aa21 100644 --- a/homeassistant/components/bmw_connected_drive/binary_sensor.py +++ b/homeassistant/components/bmw_connected_drive/binary_sensor.py @@ -3,7 +3,12 @@ from bimmer_connected.state import ChargingState, LockState -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_OPENING, + DEVICE_CLASS_PLUG, + DEVICE_CLASS_PROBLEM, + BinarySensorEntity, +) from homeassistant.const import ATTR_ATTRIBUTION, LENGTH_KILOMETERS from . import DOMAIN as BMW_DOMAIN @@ -12,17 +17,25 @@ _LOGGER = logging.getLogger(__name__) SENSOR_TYPES = { - "lids": ["Doors", "opening", "mdi:car-door-lock"], - "windows": ["Windows", "opening", "mdi:car-door"], + "lids": ["Doors", DEVICE_CLASS_OPENING, "mdi:car-door-lock"], + "windows": ["Windows", DEVICE_CLASS_OPENING, "mdi:car-door"], "door_lock_state": ["Door lock state", "lock", "mdi:car-key"], "lights_parking": ["Parking lights", "light", "mdi:car-parking-lights"], - "condition_based_services": ["Condition based services", "problem", "mdi:wrench"], - "check_control_messages": ["Control messages", "problem", "mdi:car-tire-alert"], + "condition_based_services": [ + "Condition based services", + DEVICE_CLASS_PROBLEM, + "mdi:wrench", + ], + "check_control_messages": [ + "Control messages", + DEVICE_CLASS_PROBLEM, + "mdi:car-tire-alert", + ], } SENSOR_TYPES_ELEC = { "charging_status": ["Charging status", "power", "mdi:ev-station"], - "connection_status": ["Connection status", "plug", "mdi:car-electric"], + "connection_status": ["Connection status", DEVICE_CLASS_PLUG, "mdi:car-electric"], } SENSOR_TYPES_ELEC.update(SENSOR_TYPES) diff --git a/homeassistant/components/bom/sensor.py b/homeassistant/components/bom/sensor.py index 56406b29e82b20..0470ea6e9704f0 100644 --- a/homeassistant/components/bom/sensor.py +++ b/homeassistant/components/bom/sensor.py @@ -21,7 +21,9 @@ CONF_NAME, LENGTH_KILOMETERS, LENGTH_METERS, + LENGTH_MILLIMETERS, PERCENTAGE, + PRESSURE_MBAR, SPEED_KILOMETERS_PER_HOUR, TEMP_CELSIUS, ) @@ -66,11 +68,11 @@ "gust_kt": ["Wind Gust kt", "kt"], "air_temp": ["Air Temp C", TEMP_CELSIUS], "dewpt": ["Dew Point C", TEMP_CELSIUS], - "press": ["Pressure mb", "mbar"], + "press": ["Pressure mb", PRESSURE_MBAR], "press_qnh": ["Pressure qnh", "qnh"], "press_msl": ["Pressure msl", "msl"], "press_tend": ["Pressure Tend", None], - "rain_trace": ["Rain Today", "mm"], + "rain_trace": ["Rain Today", LENGTH_MILLIMETERS], "rel_hum": ["Relative Humidity", PERCENTAGE], "sea_state": ["Sea State", None], "swell_dir_worded": ["Swell Direction", None], @@ -173,7 +175,7 @@ def state(self): @property def device_state_attributes(self): """Return the state attributes of the device.""" - attr = { + return { ATTR_ATTRIBUTION: ATTRIBUTION, ATTR_LAST_UPDATE: self.bom_data.last_updated, ATTR_SENSOR_ID: self._condition, @@ -182,8 +184,6 @@ def device_state_attributes(self): ATTR_ZONE_ID: self.bom_data.latest_data["history_product"], } - return attr - @property def unit_of_measurement(self): """Return the units of measurement.""" diff --git a/homeassistant/components/bom/weather.py b/homeassistant/components/bom/weather.py index 94b9960c851bce..9229d0c11d4356 100644 --- a/homeassistant/components/bom/weather.py +++ b/homeassistant/components/bom/weather.py @@ -54,7 +54,9 @@ def name(self): @property def condition(self): """Return the current condition.""" - return self.bom_data.get_reading("weather") + return self.bom_data.get_reading("weather") or self.bom_data.get_reading( + "cloud" + ) # Now implement the WeatherEntity interface diff --git a/homeassistant/components/bond/config_flow.py b/homeassistant/components/bond/config_flow.py index 49ca559685c141..6666cd57ca357e 100644 --- a/homeassistant/components/bond/config_flow.py +++ b/homeassistant/components/bond/config_flow.py @@ -7,7 +7,12 @@ import voluptuous as vol from homeassistant import config_entries, exceptions -from homeassistant.const import CONF_ACCESS_TOKEN, CONF_HOST, CONF_NAME +from homeassistant.const import ( + CONF_ACCESS_TOKEN, + CONF_HOST, + CONF_NAME, + HTTP_UNAUTHORIZED, +) from .const import CONF_BOND_ID from .const import DOMAIN # pylint:disable=unused-import @@ -31,7 +36,7 @@ async def _validate_input(data: Dict[str, Any]) -> str: except ClientConnectionError as error: raise InputValidationError("cannot_connect") from error except ClientResponseError as error: - if error.status == 401: + if error.status == HTTP_UNAUTHORIZED: raise InputValidationError("invalid_auth") from error raise InputValidationError("unknown") from error except Exception as error: diff --git a/homeassistant/components/bond/fan.py b/homeassistant/components/bond/fan.py index 19e345b7e23092..e59d0234beb8ed 100644 --- a/homeassistant/components/bond/fan.py +++ b/homeassistant/components/bond/fan.py @@ -120,7 +120,7 @@ async def async_set_speed(self, speed: str) -> None: async def async_turn_on(self, speed: Optional[str] = None, **kwargs) -> None: """Turn on the fan.""" - _LOGGER.debug("async_turn_on called with speed %s", speed) + _LOGGER.debug("Fan async_turn_on called with speed %s", speed) if speed is not None: if speed == SPEED_OFF: diff --git a/homeassistant/components/bond/light.py b/homeassistant/components/bond/light.py index 7dec44dbb38a7a..5e66019579dce8 100644 --- a/homeassistant/components/bond/light.py +++ b/homeassistant/components/bond/light.py @@ -1,4 +1,5 @@ """Support for Bond lights.""" +import logging from typing import Any, Callable, List, Optional from bond_api import Action, DeviceType @@ -17,6 +18,8 @@ from .entity import BondEntity from .utils import BondDevice +_LOGGER = logging.getLogger(__name__) + async def async_setup_entry( hass: HomeAssistant, @@ -96,7 +99,7 @@ class BondFireplace(BondEntity, LightEntity): """Representation of a Bond-controlled fireplace.""" def __init__(self, hub: BondHub, device: BondDevice): - """Create HA entity representing Bond fan.""" + """Create HA entity representing Bond fireplace.""" super().__init__(hub, device) self._power: Optional[bool] = None @@ -119,6 +122,8 @@ def is_on(self) -> bool: async def async_turn_on(self, **kwargs: Any) -> None: """Turn the fireplace on.""" + _LOGGER.debug("Fireplace async_turn_on called with: %s", kwargs) + brightness = kwargs.get(ATTR_BRIGHTNESS) if brightness: flame = round((brightness * 100) / 255) @@ -128,6 +133,8 @@ async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None: """Turn the fireplace off.""" + _LOGGER.debug("Fireplace async_turn_off called with: %s", kwargs) + await self._hub.bond.action(self._device.device_id, Action.turn_off()) @property diff --git a/homeassistant/components/bond/translations/de.json b/homeassistant/components/bond/translations/de.json index d10ea1e71b00ca..393232025ddc1f 100644 --- a/homeassistant/components/bond/translations/de.json +++ b/homeassistant/components/bond/translations/de.json @@ -7,7 +7,8 @@ "step": { "user": { "data": { - "access_token": "Zugriffstoken" + "access_token": "Zugriffstoken", + "host": "Host" } } } diff --git a/homeassistant/components/bond/translations/fr.json b/homeassistant/components/bond/translations/fr.json index cabbb73a370440..496a21339cbf24 100644 --- a/homeassistant/components/bond/translations/fr.json +++ b/homeassistant/components/bond/translations/fr.json @@ -6,6 +6,7 @@ "error": { "cannot_connect": "Echec de connexion", "invalid_auth": "Authentification invalide", + "old_firmware": "Ancien micrologiciel non pris en charge sur l'appareil Bond - veuillez mettre \u00e0 niveau avant de continuer", "unknown": "Erreur inattendue" }, "flow_title": "Bond : {bond_id} ({h\u00f4te})", diff --git a/homeassistant/components/bond/translations/hu.json b/homeassistant/components/bond/translations/hu.json new file mode 100644 index 00000000000000..3b2d79a34a77e2 --- /dev/null +++ b/homeassistant/components/bond/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bond/translations/ko.json b/homeassistant/components/bond/translations/ko.json index d50380c81ebef5..61576d7043166b 100644 --- a/homeassistant/components/bond/translations/ko.json +++ b/homeassistant/components/bond/translations/ko.json @@ -5,7 +5,11 @@ "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": "\ubcf8\ub4dc : {bond_id} ( {host} )", "step": { + "confirm": { + "description": "{bond_id} \ub97c \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + }, "user": { "data": { "access_token": "\uc561\uc138\uc2a4 \ud1a0\ud070", diff --git a/homeassistant/components/bond/translations/pl.json b/homeassistant/components/bond/translations/pl.json index 10b6433daee865..c50c270b74cd50 100644 --- a/homeassistant/components/bond/translations/pl.json +++ b/homeassistant/components/bond/translations/pl.json @@ -1,11 +1,22 @@ { "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" + }, "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", - "invalid_auth": "Niepoprawne uwierzytelnienie.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "old_firmware": "Stare, nieobs\u0142ugiwane oprogramowanie na urz\u0105dzeniu Bond - zaktualizuj przed kontynuowaniem", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, + "flow_title": "Bond: {bond_id} ({host})", "step": { + "confirm": { + "data": { + "access_token": "Token dost\u0119pu" + }, + "description": "Czy chcesz skonfigurowa\u0107 {bond_id}?" + }, "user": { "data": { "access_token": "Token dost\u0119pu", diff --git a/homeassistant/components/braviatv/strings.json b/homeassistant/components/braviatv/strings.json index c066f91d395b00..7414b8008c15f3 100644 --- a/homeassistant/components/braviatv/strings.json +++ b/homeassistant/components/braviatv/strings.json @@ -12,17 +12,17 @@ "title": "Authorize Sony Bravia TV", "description": "Enter the PIN code shown on the Sony Bravia TV. \n\nIf the PIN code is not shown, you have to unregister Home Assistant on your TV, go to: Settings -> Network -> Remote device settings -> Unregister remote device.", "data": { - "pin": "PIN code" + "pin": "[%key:common::config_flow::data::pin%]" } } }, "error": { "invalid_host": "Invalid hostname or IP address.", - "cannot_connect": "Failed to connect, invalid host or PIN code.", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "unsupported_model": "Your TV model is not supported." }, "abort": { - "already_configured": "This TV is already configured.", + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "no_ip_control": "IP Control is disabled on your TV or the TV is not supported." } }, @@ -36,4 +36,4 @@ } } } -} \ No newline at end of file +} diff --git a/homeassistant/components/braviatv/translations/ca.json b/homeassistant/components/braviatv/translations/ca.json index c35dc3ce8576cd..c46036e428b09b 100644 --- a/homeassistant/components/braviatv/translations/ca.json +++ b/homeassistant/components/braviatv/translations/ca.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Aquest televisor ja est\u00e0 configurat.", + "already_configured": "El dispositiu ja est\u00e0 configurat", "no_ip_control": "El control IP del teu televisor est\u00e0 desactivat o aquest no \u00e9s compatible." }, "error": { - "cannot_connect": "No s'ha pogut connectar, amfitri\u00f3 o codi PIN inv\u00e0lids.", + "cannot_connect": "Ha fallat la connexi\u00f3", "invalid_host": "Nom de l'amfitri\u00f3 o adre\u00e7a IP inv\u00e0lids.", "unsupported_model": "Aquest model de televisor no \u00e9s compatible." }, diff --git a/homeassistant/components/braviatv/translations/en.json b/homeassistant/components/braviatv/translations/en.json index 98a569b3ab1290..d32b674a7a1c3c 100644 --- a/homeassistant/components/braviatv/translations/en.json +++ b/homeassistant/components/braviatv/translations/en.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "This TV is already configured.", + "already_configured": "Device is already configured", "no_ip_control": "IP Control is disabled on your TV or the TV is not supported." }, "error": { - "cannot_connect": "Failed to connect, invalid host or PIN code.", + "cannot_connect": "Failed to connect", "invalid_host": "Invalid hostname or IP address.", "unsupported_model": "Your TV model is not supported." }, diff --git a/homeassistant/components/braviatv/translations/it.json b/homeassistant/components/braviatv/translations/it.json index 46cb5fca7a4c4e..5df455386a3c22 100644 --- a/homeassistant/components/braviatv/translations/it.json +++ b/homeassistant/components/braviatv/translations/it.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Questo televisore \u00e8 gi\u00e0 configurato.", + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", "no_ip_control": "Il controllo IP \u00e8 disabilitato sulla TV o la TV non \u00e8 supportata." }, "error": { - "cannot_connect": "Connessione non riuscita, host o codice PIN non valido.", + "cannot_connect": "Impossibile connettersi", "invalid_host": "Nome host o indirizzo IP non valido.", "unsupported_model": "Il tuo modello TV non \u00e8 supportato." }, diff --git a/homeassistant/components/braviatv/translations/no.json b/homeassistant/components/braviatv/translations/no.json index cd687d8f2d0cc9..a890890c072b43 100644 --- a/homeassistant/components/braviatv/translations/no.json +++ b/homeassistant/components/braviatv/translations/no.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Denne TV-en er allerede konfigurert.", + "already_configured": "Enheten er allerede konfigurert", "no_ip_control": "IP-kontrollen er deaktivert p\u00e5 TVen eller TV-en st\u00f8ttes ikke." }, "error": { - "cannot_connect": "Kunne ikke koble til, ugyldig vert eller PIN-kode.", + "cannot_connect": "Tilkobling mislyktes.", "invalid_host": "Ugyldig vertsnavn eller IP-adresse.", "unsupported_model": "TV-modellen din st\u00f8ttes ikke." }, diff --git a/homeassistant/components/braviatv/translations/ru.json b/homeassistant/components/braviatv/translations/ru.json index a799a29831fe58..add32dd0d794a5 100644 --- a/homeassistant/components/braviatv/translations/ru.json +++ b/homeassistant/components/braviatv/translations/ru.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "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_ip_control": "\u041d\u0430 \u0442\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440\u0435 \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u043e IP, \u043b\u0438\u0431\u043e \u044d\u0442\u0430 \u043c\u043e\u0434\u0435\u043b\u044c \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, \u043d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0445\u043e\u0441\u0442 \u0438\u043b\u0438 PIN-\u043a\u043e\u0434.", + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "invalid_host": "\u041d\u0435\u0432\u0435\u0440\u043d\u043e\u0435 \u0434\u043e\u043c\u0435\u043d\u043d\u043e\u0435 \u0438\u043c\u044f \u0438\u043b\u0438 IP-\u0430\u0434\u0440\u0435\u0441.", "unsupported_model": "\u042d\u0442\u0430 \u043c\u043e\u0434\u0435\u043b\u044c \u0442\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440\u0430 \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f." }, diff --git a/homeassistant/components/braviatv/translations/zh-Hant.json b/homeassistant/components/braviatv/translations/zh-Hant.json index 027e44abaa4754..d45b1b778626ba 100644 --- a/homeassistant/components/braviatv/translations/zh-Hant.json +++ b/homeassistant/components/braviatv/translations/zh-Hant.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "\u6b64\u96fb\u8996\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3002", + "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "no_ip_control": "\u96fb\u8996\u4e0a\u7684 IP \u5df2\u95dc\u9589\u6216\u4e0d\u652f\u63f4\u6b64\u6b3e\u96fb\u8996\u3002" }, "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557\uff0c\u7121\u6548\u7684\u4e3b\u6a5f\u540d\u7a31\u6216 PIN \u78bc\u3002", + "cannot_connect": "\u9023\u7dda\u5931\u6557", "invalid_host": "\u7121\u6548\u4e3b\u6a5f\u540d\u6216 IP \u4f4d\u5740", "unsupported_model": "\u4e0d\u652f\u63f4\u6b64\u6b3e\u96fb\u8996\u578b\u865f\u3002" }, diff --git a/homeassistant/components/broadlink/config_flow.py b/homeassistant/components/broadlink/config_flow.py index 284a9bffe19c1d..9fe83e350cfbd5 100644 --- a/homeassistant/components/broadlink/config_flow.py +++ b/homeassistant/components/broadlink/config_flow.py @@ -7,11 +7,11 @@ from broadlink.exceptions import ( AuthenticationError, BroadlinkException, - DeviceOfflineError, + NetworkTimeoutError, ) import voluptuous as vol -from homeassistant import config_entries +from homeassistant import config_entries, data_entry_flow from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_TIMEOUT, CONF_TYPE from homeassistant.helpers import config_validation as cv @@ -20,6 +20,7 @@ DEFAULT_PORT, DEFAULT_TIMEOUT, DOMAIN, + DOMAINS_AND_TYPES, ) from .helpers import format_mac @@ -36,6 +37,19 @@ def __init__(self): async def async_set_device(self, device, raise_on_progress=True): """Define a device for the config flow.""" + supported_types = { + device_type + for _, device_types in DOMAINS_AND_TYPES + for device_type in device_types + } + if device.type not in supported_types: + LOGGER.error( + "Unsupported device: %s. If it worked before, please open " + "an issue at https://github.com/home-assistant/core/issues", + hex(device.devtype), + ) + raise data_entry_flow.AbortFlow("not_supported") + await self.async_set_unique_id( device.mac.hex(), raise_on_progress=raise_on_progress ) @@ -125,7 +139,7 @@ async def async_step_auth(self): await self.async_set_unique_id(device.mac.hex()) return await self.async_step_reset(errors=errors) - except DeviceOfflineError as err: + except NetworkTimeoutError as err: errors["base"] = "cannot_connect" err_msg = str(err) @@ -193,7 +207,7 @@ async def async_step_unlock(self, user_input=None): try: await self.hass.async_add_executor_job(device.set_lock, False) - except DeviceOfflineError as err: + except NetworkTimeoutError as err: errors["base"] = "cannot_connect" err_msg = str(err) diff --git a/homeassistant/components/broadlink/device.py b/homeassistant/components/broadlink/device.py index d05fdfd4df6406..51d9b0a497ffa2 100644 --- a/homeassistant/components/broadlink/device.py +++ b/homeassistant/components/broadlink/device.py @@ -9,7 +9,7 @@ AuthorizationError, BroadlinkException, ConnectionClosedError, - DeviceOfflineError, + NetworkTimeoutError, ) from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_TIMEOUT, CONF_TYPE @@ -82,7 +82,7 @@ async def async_setup(self): await self._async_handle_auth_error() return False - except (DeviceOfflineError, OSError) as err: + except (NetworkTimeoutError, OSError) as err: raise ConfigEntryNotReady from err except BroadlinkException as err: diff --git a/homeassistant/components/broadlink/manifest.json b/homeassistant/components/broadlink/manifest.json index 6a6300449044cd..9c6e571ec8685e 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.14.1"], + "requirements": ["broadlink==0.15.0"], "codeowners": ["@danielhiversen", "@felipediel"], "config_flow": true } diff --git a/homeassistant/components/broadlink/remote.py b/homeassistant/components/broadlink/remote.py index 7c1ae7349da85b..7c7a05ac1676b7 100644 --- a/homeassistant/components/broadlink/remote.py +++ b/homeassistant/components/broadlink/remote.py @@ -9,7 +9,7 @@ from broadlink.exceptions import ( AuthorizationError, BroadlinkException, - DeviceOfflineError, + NetworkTimeoutError, ReadError, StorageError, ) @@ -243,6 +243,9 @@ async def async_send_command(self, command, **kwargs): delay = kwargs[ATTR_DELAY_SECS] if not self._state: + _LOGGER.warning( + "remote.send_command canceled: %s entity is turned off", self.entity_id + ) return should_delay = False @@ -262,7 +265,7 @@ async def async_send_command(self, command, **kwargs): try: await self._device.async_request(self._device.api.send_data, code) - except (AuthorizationError, DeviceOfflineError, OSError) as err: + except (AuthorizationError, NetworkTimeoutError, OSError) as err: _LOGGER.error("Failed to send '%s': %s", command, err) break @@ -285,6 +288,9 @@ async def async_learn_command(self, **kwargs): toggle = kwargs[ATTR_ALTERNATIVE] if not self._state: + _LOGGER.warning( + "remote.learn_command canceled: %s entity is turned off", self.entity_id + ) return should_store = False @@ -295,7 +301,7 @@ async def async_learn_command(self, **kwargs): if toggle: code = [code, await self._async_learn_command(command)] - except (AuthorizationError, DeviceOfflineError, OSError) as err: + except (AuthorizationError, NetworkTimeoutError, OSError) as err: _LOGGER.error("Failed to learn '%s': %s", command, err) break diff --git a/homeassistant/components/broadlink/strings.json b/homeassistant/components/broadlink/strings.json index 44cb1801eded1d..efa9d3c35f072a 100644 --- a/homeassistant/components/broadlink/strings.json +++ b/homeassistant/components/broadlink/strings.json @@ -26,15 +26,16 @@ "finish": { "title": "Choose a name for the device", "data": { - "name": "Name" + "name": "[%key:common::config_flow::data::name%]" } } }, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", - "already_in_progress": "There is already a configuration flow in progress for this device", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "invalid_host": "Invalid hostname or IP address", + "not_supported": "Device not supported", "unknown": "[%key:common::config_flow::error::unknown%]" }, "error": { diff --git a/homeassistant/components/broadlink/translations/ca.json b/homeassistant/components/broadlink/translations/ca.json index 3e642b0f6b5e75..edde3bbdfcf8a5 100644 --- a/homeassistant/components/broadlink/translations/ca.json +++ b/homeassistant/components/broadlink/translations/ca.json @@ -2,9 +2,10 @@ "config": { "abort": { "already_configured": "El dispositiu ja est\u00e0 configurat", - "already_in_progress": "Ja hi ha un flux de configuraci\u00f3 en curs per a aquest dispositiu", + "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", "cannot_connect": "Ha fallat la connexi\u00f3", "invalid_host": "Nom de l'amfitri\u00f3 o l'adre\u00e7a IP inv\u00e0lids", + "not_supported": "Dispositiu no compatible", "unknown": "Error inesperat" }, "error": { diff --git a/homeassistant/components/broadlink/translations/de.json b/homeassistant/components/broadlink/translations/de.json new file mode 100644 index 00000000000000..b0d7a55c78774e --- /dev/null +++ b/homeassistant/components/broadlink/translations/de.json @@ -0,0 +1,16 @@ +{ + "config": { + "step": { + "finish": { + "data": { + "name": "Name" + } + }, + "user": { + "data": { + "host": "Host" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/broadlink/translations/el.json b/homeassistant/components/broadlink/translations/el.json new file mode 100644 index 00000000000000..af16604830e394 --- /dev/null +++ b/homeassistant/components/broadlink/translations/el.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_in_progress": "\u03a5\u03c0\u03ac\u03c1\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03bc\u03b9\u03b1 \u03c1\u03bf\u03ae \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7\u03c2 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd \u03c3\u03b5 \u03b5\u03be\u03ad\u03bb\u03b9\u03be\u03b7 \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03ae\u03bd \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae", + "invalid_host": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\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 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", + "not_supported": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9" + }, + "error": { + "invalid_host": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\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 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" + }, + "flow_title": "{name} ( {model} \u03c3\u03c4\u03bf {host} )", + "step": { + "auth": { + "title": "\u0388\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" + }, + "finish": { + "data": { + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + }, + "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03ad\u03bd\u03b1 \u03cc\u03bd\u03bf\u03bc\u03b1 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" + }, + "reset": { + "description": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c3\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03ba\u03bb\u03b5\u03b9\u03b4\u03c9\u03bc\u03ad\u03bd\u03b7 \u03b3\u03b9\u03b1 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2. \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 \u03c4\u03bf \u03be\u03b5\u03ba\u03bb\u03b5\u03b9\u03b4\u03ce\u03c3\u03b5\u03c4\u03b5:\n1. \u0395\u03c1\u03b3\u03bf\u03c3\u03c4\u03b1\u03c3\u03b9\u03b1\u03ba\u03ae \u03b5\u03c0\u03b1\u03bd\u03b1\u03c6\u03bf\u03c1\u03ac \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2.\n2. \u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03c0\u03af\u03c3\u03b7\u03bc\u03b7 \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c3\u03c4\u03bf \u03c4\u03bf\u03c0\u03b9\u03ba\u03cc \u03c3\u03b1\u03c2 \u03b4\u03af\u03ba\u03c4\u03c5\u03bf.\n3. \u03a3\u03c4\u03b1\u03bc\u03b1\u03c4\u03ae\u03c3\u03c4\u03b5. \u039c\u03b7\u03bd \u03bf\u03bb\u03bf\u03ba\u03bb\u03b7\u03c1\u03ce\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7. \u039a\u03bb\u03b5\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03c6\u03b1\u03c1\u03bc\u03bf\u03b3\u03ae.\n4. \u039a\u03ac\u03bd\u03c4\u03b5 \u03ba\u03bb\u03b9\u03ba \u03c3\u03c4\u03b7\u03bd \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03a5\u03c0\u03bf\u03b2\u03bf\u03bb\u03ae.", + "title": "\u039e\u03b5\u03ba\u03bb\u03b5\u03af\u03b4\u03c9\u03bc\u03b1 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2" + }, + "unlock": { + "data": { + "unlock": "\u039d\u03b1\u03b9, \u03ba\u03ac\u03bd\u03c4\u03bf." + }, + "description": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c3\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03ba\u03bb\u03b5\u03b9\u03b4\u03c9\u03bc\u03ad\u03bd\u03b7. \u0391\u03c5\u03c4\u03cc \u03bc\u03c0\u03bf\u03c1\u03b5\u03af \u03bd\u03b1 \u03bf\u03b4\u03b7\u03b3\u03ae\u03c3\u03b5\u03b9 \u03c3\u03b5 \u03c0\u03c1\u03bf\u03b2\u03bb\u03ae\u03bc\u03b1\u03c4\u03b1 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03c3\u03c4\u03bf Home Assistant. \u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c4\u03bf \u03be\u03b5\u03ba\u03bb\u03b5\u03b9\u03b4\u03ce\u03c3\u03b5\u03c4\u03b5;", + "title": "\u039e\u03b5\u03ba\u03bb\u03b5\u03af\u03b4\u03c9\u03bc\u03b1 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae\u03c2 (\u03c0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)" + }, + "user": { + "data": { + "timeout": "\u03a7\u03c1\u03bf\u03bd\u03b9\u03ba\u03cc \u03cc\u03c1\u03b9\u03bf" + }, + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/broadlink/translations/en.json b/homeassistant/components/broadlink/translations/en.json index fa3feb880080b7..0b1029b8a0a3c2 100644 --- a/homeassistant/components/broadlink/translations/en.json +++ b/homeassistant/components/broadlink/translations/en.json @@ -2,9 +2,10 @@ "config": { "abort": { "already_configured": "Device is already configured", - "already_in_progress": "There is already a configuration flow in progress for this device", + "already_in_progress": "Configuration flow is already in progress", "cannot_connect": "Failed to connect", "invalid_host": "Invalid hostname or IP address", + "not_supported": "Device not supported", "unknown": "Unexpected error" }, "error": { diff --git a/homeassistant/components/broadlink/translations/es.json b/homeassistant/components/broadlink/translations/es.json index fdceeabd2cd414..98c4cbdfb30486 100644 --- a/homeassistant/components/broadlink/translations/es.json +++ b/homeassistant/components/broadlink/translations/es.json @@ -5,6 +5,7 @@ "already_in_progress": "Ya hay un flujo de configuraci\u00f3n en curso para este dispositivo", "cannot_connect": "No se pudo conectar", "invalid_host": "Nombre de host o direcci\u00f3n IP no v\u00e1lidos", + "not_supported": "Dispositivo no compatible", "unknown": "Error inesperado" }, "error": { diff --git a/homeassistant/components/broadlink/translations/et.json b/homeassistant/components/broadlink/translations/et.json new file mode 100644 index 00000000000000..fc7f3424b6286a --- /dev/null +++ b/homeassistant/components/broadlink/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "not_supported": "Seadet ei toetata" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/broadlink/translations/fr.json b/homeassistant/components/broadlink/translations/fr.json index 2bf2477f61522d..1d80059fb7a142 100644 --- a/homeassistant/components/broadlink/translations/fr.json +++ b/homeassistant/components/broadlink/translations/fr.json @@ -5,6 +5,7 @@ "already_in_progress": "Il y a d\u00e9j\u00e0 un processus de configuration en cours pour cet appareil", "cannot_connect": "\u00c9chec de connexion", "invalid_host": "Nom d'h\u00f4te ou adresse IP non valide", + "not_supported": "Dispositif non pris en charge", "unknown": "Erreur inattendue" }, "error": { diff --git a/homeassistant/components/broadlink/translations/hu.json b/homeassistant/components/broadlink/translations/hu.json new file mode 100644 index 00000000000000..3b2d79a34a77e2 --- /dev/null +++ b/homeassistant/components/broadlink/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/broadlink/translations/it.json b/homeassistant/components/broadlink/translations/it.json index 939925104fd080..d8e64b4bea33e5 100644 --- a/homeassistant/components/broadlink/translations/it.json +++ b/homeassistant/components/broadlink/translations/it.json @@ -2,9 +2,10 @@ "config": { "abort": { "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", - "already_in_progress": "\u00c8 gi\u00e0 in corso un flusso di configurazione per questo dispositivo", + "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "cannot_connect": "Impossibile connettersi", "invalid_host": "Nome host o indirizzo IP non valido", + "not_supported": "Dispositivo non supportato", "unknown": "Errore imprevisto" }, "error": { diff --git a/homeassistant/components/broadlink/translations/ko.json b/homeassistant/components/broadlink/translations/ko.json new file mode 100644 index 00000000000000..47ebf3db64a08a --- /dev/null +++ b/homeassistant/components/broadlink/translations/ko.json @@ -0,0 +1,42 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "already_in_progress": "\uc774 \uae30\uae30\uc5d0 \ub300\ud574 \uc774\ubbf8 \uc9c4\ud589\uc911\uc778 \uad6c\uc131\uc774 \uc788\uc2b5\ub2c8\ub2e4.", + "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", + "invalid_host": "\uc798\ubabb\ub41c \ud638\uc2a4\ud2b8\uba85 \ub610\ub294 IP \uc8fc\uc18c", + "not_supported": "\uc9c0\uc6d0\ub418\uc9c0 \uc54a\ub294 \uc7a5\uce58", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", + "invalid_host": "\uc798\ubabb\ub41c \ud638\uc2a4\ud2b8\uba85 \ub610\ub294 IP \uc8fc\uc18c", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec" + }, + "flow_title": "{name} ({host} \uc758 {model})", + "step": { + "auth": { + "title": "\uc7a5\uce58\uc5d0 \uc778\uc99d" + }, + "finish": { + "title": "\uc7a5\uce58 \uc774\ub984\uc744 \uc120\ud0dd\ud558\uc2ed\uc2dc\uc624" + }, + "reset": { + "title": "\uc7a5\uce58 \uc7a0\uae08 \ud574\uc81c" + }, + "unlock": { + "data": { + "unlock": "\uc608" + }, + "description": "\uc7a5\uce58\uac00 \uc7a0\uaca8 \uc788\uc2b5\ub2c8\ub2e4. \uc774\ub85c \uc778\ud574 Home Assistant\uc5d0\uc11c \uc778\uc99d \ubb38\uc81c\uac00 \ubc1c\uc0dd\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4. \uc7a0\uae08\uc744 \ud574\uc81c \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "\uc7a5\uce58 \uc7a0\uae08 \ud574\uc81c (\uc635\uc158)" + }, + "user": { + "data": { + "timeout": "\uc81c\ud55c \uc2dc\uac04" + }, + "title": "\uc7a5\uce58\uc5d0 \uc5f0\uacb0" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/broadlink/translations/lb.json b/homeassistant/components/broadlink/translations/lb.json index 0872c01b608845..e4f60a3eac9457 100644 --- a/homeassistant/components/broadlink/translations/lb.json +++ b/homeassistant/components/broadlink/translations/lb.json @@ -5,6 +5,7 @@ "already_in_progress": "Et ass schonn ee Konfiguratioun's Oflaf fir d\u00ebsen Apparat am gaang.", "cannot_connect": "Feeler beim verbannen", "invalid_host": "Ong\u00ebltege Numm oder IP Adresse.", + "not_supported": "Apparat net \u00ebnnerst\u00ebtzt.", "unknown": "Onerwaarte Feeler" }, "error": { diff --git a/homeassistant/components/broadlink/translations/nl.json b/homeassistant/components/broadlink/translations/nl.json new file mode 100644 index 00000000000000..d6185150e499f6 --- /dev/null +++ b/homeassistant/components/broadlink/translations/nl.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "not_supported": "Apparaat wordt niet ondersteund" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/broadlink/translations/no.json b/homeassistant/components/broadlink/translations/no.json index beeae80745d527..ad07b2b6d95841 100644 --- a/homeassistant/components/broadlink/translations/no.json +++ b/homeassistant/components/broadlink/translations/no.json @@ -2,9 +2,10 @@ "config": { "abort": { "already_configured": "Enheten er allerede konfigurert", - "already_in_progress": "Det p\u00e5g\u00e5r allerede en konfigurasjonsflyt for denne enheten", + "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", "cannot_connect": "Tilkobling mislyktes.", "invalid_host": "Ugyldig vertsnavn eller IP-adresse", + "not_supported": "Enheten st\u00f8ttes ikke", "unknown": "Uventet feil" }, "error": { diff --git a/homeassistant/components/broadlink/translations/pl.json b/homeassistant/components/broadlink/translations/pl.json index a168d020d02983..8c4704475917db 100644 --- a/homeassistant/components/broadlink/translations/pl.json +++ b/homeassistant/components/broadlink/translations/pl.json @@ -5,6 +5,7 @@ "already_in_progress": "Konfiguracja integracji Broadlink jest ju\u017c w toku.", "cannot_connect": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z urz\u0105dzeniem", "invalid_host": "B\u0142\u0119dna nazwa hosta b\u0105d\u017a adres IP", + "not_supported": "Urz\u0105dzenie nie jest obs\u0142ugiwane", "unknown": "Wyst\u0105pi\u0142 nieoczekiwany b\u0142\u0105d" }, "error": { @@ -24,7 +25,7 @@ "title": "Wprowad\u017a nazw\u0119 dla urz\u0105dzenia" }, "reset": { - "description": "Twoje urz\u0105dzenie jest zablokowane. Post\u0119puj zgodnie z instrukcjami, aby je odblokowa\u0107:\\n1. Zresetuj urz\u0105dzenie do ustawie\u0144 fabrycznych.\\n2. Dodaj urz\u0105dzenie w oficjalnej aplikacji do swojej sieci.\\n3. Stop! Nie ko\u0144cz konfiguracji w aplikacji tylko j\u0105 zamknij.\\n4. Potwierd\u017a odblokowanie (przycisk Odblokuj).", + "description": "Twoje urz\u0105dzenie jest zablokowane. Post\u0119puj zgodnie z instrukcjami, aby je odblokowa\u0107: \nOpcja 1 (preferowana):\n1. W aplikacji Broadlink wejd\u017a w swoje urz\u0105dzenie\n2. Kliknij \"\u2022 \u2022 \u2022\" \n3. Wy\u0142\u0105cz opcje \"Lock Device\"\n\nOpcja 2:\n1. Zresetuj urz\u0105dzenie do ustawie\u0144 fabrycznych. \n2. Dodaj urz\u0105dzenie w oficjalnej aplikacji do swojej sieci. \n3. Stop! Nie ko\u0144cz konfiguracji w aplikacji tylko j\u0105 zamknij. \n4. Potwierd\u017a odblokowanie (przycisk Odblokuj).", "title": "Odblokuj urz\u0105dzeniem" }, "unlock": { diff --git a/homeassistant/components/broadlink/translations/ru.json b/homeassistant/components/broadlink/translations/ru.json index f7d0cfab3d1215..617c508b8c2e7c 100644 --- a/homeassistant/components/broadlink/translations/ru.json +++ b/homeassistant/components/broadlink/translations/ru.json @@ -2,9 +2,10 @@ "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": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.", + "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.", "invalid_host": "\u041d\u0435\u0432\u0435\u0440\u043d\u043e\u0435 \u0434\u043e\u043c\u0435\u043d\u043d\u043e\u0435 \u0438\u043c\u044f \u0438\u043b\u0438 IP-\u0430\u0434\u0440\u0435\u0441.", + "not_supported": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f.", "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "error": { diff --git a/homeassistant/components/broadlink/translations/sv.json b/homeassistant/components/broadlink/translations/sv.json new file mode 100644 index 00000000000000..38d02e42d90829 --- /dev/null +++ b/homeassistant/components/broadlink/translations/sv.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "not_supported": "Enheten st\u00f6ds inte" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/broadlink/translations/zh-Hant.json b/homeassistant/components/broadlink/translations/zh-Hant.json index 741e8beb2f7542..01db5167bb2150 100644 --- a/homeassistant/components/broadlink/translations/zh-Hant.json +++ b/homeassistant/components/broadlink/translations/zh-Hant.json @@ -2,9 +2,10 @@ "config": { "abort": { "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", - "already_in_progress": "\u6b64\u8a2d\u5099\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d\u3002", + "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "cannot_connect": "\u9023\u7dda\u5931\u6557", "invalid_host": "\u7121\u6548\u4e3b\u6a5f\u540d\u7a31\u6216 IP \u4f4d\u5740", + "not_supported": "\u8a2d\u5099\u4e0d\u652f\u63f4", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "error": { diff --git a/homeassistant/components/broadlink/updater.py b/homeassistant/components/broadlink/updater.py index bd124d1e1acfb6..6d3bbc5b6c1f01 100644 --- a/homeassistant/components/broadlink/updater.py +++ b/homeassistant/components/broadlink/updater.py @@ -1,12 +1,15 @@ """Support for fetching data from Broadlink devices.""" from abc import ABC, abstractmethod from datetime import timedelta +from functools import partial import logging +import broadlink as blk from broadlink.exceptions import ( AuthorizationError, BroadlinkException, CommandNotSupportedError, + NetworkTimeoutError, StorageError, ) @@ -18,6 +21,9 @@ def get_update_manager(device): """Return an update manager for a given Broadlink device.""" + if device.api.model.startswith("RM mini"): + return BroadlinkRMMini3UpdateManager(device) + update_managers = { "A1": BroadlinkA1UpdateManager, "MP1": BroadlinkMP1UpdateManager, @@ -42,7 +48,7 @@ def __init__(self, device): self.coordinator = DataUpdateCoordinator( device.hass, _LOGGER, - name="device", + name=f"{device.name} ({device.api.model} at {device.api.host[0]})", update_method=self.async_update, update_interval=timedelta(minutes=1), ) @@ -61,14 +67,20 @@ async def async_update(self): ): self.available = False _LOGGER.warning( - "Disconnected from the device at %s", self.device.api.host[0] + "Disconnected from %s (%s at %s)", + self.device.name, + self.device.api.model, + self.device.api.host[0], ) raise UpdateFailed(err) from err else: if self.available is False: _LOGGER.warning( - "Connected to the device at %s", self.device.api.host[0] + "Connected to %s (%s at %s)", + self.device.name, + self.device.api.model, + self.device.api.host[0], ) self.available = True self.last_update = dt.utcnow() @@ -95,6 +107,22 @@ async def async_fetch_data(self): return await self.device.async_request(self.device.api.check_power) +class BroadlinkRMMini3UpdateManager(BroadlinkUpdateManager): + """Manages updates for Broadlink RM mini 3 devices.""" + + async def async_fetch_data(self): + """Fetch data from the device.""" + hello = partial( + blk.discover, + discover_ip_address=self.device.api.host[0], + timeout=self.device.api.timeout, + ) + devices = await self.device.hass.async_add_executor_job(hello) + if not devices: + raise NetworkTimeoutError("The device is offline") + return {} + + class BroadlinkRMUpdateManager(BroadlinkUpdateManager): """Manages updates for Broadlink RM2 and RM4 devices.""" diff --git a/homeassistant/components/brother/config_flow.py b/homeassistant/components/brother/config_flow.py index 8b3a9539cc35ea..306945b0424026 100644 --- a/homeassistant/components/brother/config_flow.py +++ b/homeassistant/components/brother/config_flow.py @@ -59,7 +59,7 @@ async def async_step_user(self, user_input=None): except InvalidHost: errors[CONF_HOST] = "wrong_host" except ConnectionError: - errors["base"] = "connection_error" + errors["base"] = "cannot_connect" except SnmpError: errors["base"] = "snmp_error" except UnsupportedModel: @@ -72,7 +72,7 @@ async def async_step_user(self, user_input=None): async def async_step_zeroconf(self, discovery_info): """Handle zeroconf discovery.""" if discovery_info is None: - return self.async_abort(reason="connection_error") + return self.async_abort(reason="cannot_connect") if not discovery_info.get("name") or not discovery_info["name"].startswith( "Brother" @@ -86,7 +86,7 @@ async def async_step_zeroconf(self, discovery_info): try: await self.brother.async_update() except (ConnectionError, SnmpError, UnsupportedModel): - return self.async_abort(reason="connection_error") + return self.async_abort(reason="cannot_connect") # Check if already configured await self.async_set_unique_id(self.brother.serial.lower()) diff --git a/homeassistant/components/brother/strings.json b/homeassistant/components/brother/strings.json index 264992a7eae3e4..358f76e77b88bf 100644 --- a/homeassistant/components/brother/strings.json +++ b/homeassistant/components/brother/strings.json @@ -19,12 +19,12 @@ }, "error": { "wrong_host": "Invalid hostname or IP address.", - "connection_error": "Connection error.", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "snmp_error": "SNMP server turned off or printer not supported." }, "abort": { "unsupported_model": "This printer model is not supported.", - "already_configured": "This printer is already configured." + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } } } \ No newline at end of file diff --git a/homeassistant/components/brother/translations/ca.json b/homeassistant/components/brother/translations/ca.json index 6f8c1ca912f25d..e32d26ff79aaca 100644 --- a/homeassistant/components/brother/translations/ca.json +++ b/homeassistant/components/brother/translations/ca.json @@ -1,11 +1,12 @@ { "config": { "abort": { - "already_configured": "Aquesta impressora ja est\u00e0 configurada.", + "already_configured": "El dispositiu ja est\u00e0 configurat", "unsupported_model": "Aquest model d'impressora no \u00e9s compatible." }, "error": { - "connection_error": "Error de connexi\u00f3.", + "cannot_connect": "Ha fallat la connexi\u00f3", + "connection_error": "Ha fallat la connexi\u00f3", "snmp_error": "El servidor SNMP s'ha tancat o la impressora no \u00e9s compatible.", "wrong_host": "Nom de l'amfitri\u00f3 o adre\u00e7a IP inv\u00e0lids." }, diff --git a/homeassistant/components/brother/translations/el.json b/homeassistant/components/brother/translations/el.json new file mode 100644 index 00000000000000..04b238a916d221 --- /dev/null +++ b/homeassistant/components/brother/translations/el.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/brother/translations/en.json b/homeassistant/components/brother/translations/en.json index 8cd1ed46919578..af5cc681d51ce2 100644 --- a/homeassistant/components/brother/translations/en.json +++ b/homeassistant/components/brother/translations/en.json @@ -1,11 +1,12 @@ { "config": { "abort": { - "already_configured": "This printer is already configured.", + "already_configured": "Device is already configured", "unsupported_model": "This printer model is not supported." }, "error": { - "connection_error": "Connection error.", + "cannot_connect": "Failed to connect", + "connection_error": "Failed to connect", "snmp_error": "SNMP server turned off or printer not supported.", "wrong_host": "Invalid hostname or IP address." }, diff --git a/homeassistant/components/brother/translations/es.json b/homeassistant/components/brother/translations/es.json index 51e1492be13cbb..565b71fe7d2c0e 100644 --- a/homeassistant/components/brother/translations/es.json +++ b/homeassistant/components/brother/translations/es.json @@ -5,6 +5,7 @@ "unsupported_model": "Este modelo de impresora no es compatible." }, "error": { + "cannot_connect": "No se pudo conectar", "connection_error": "Error de conexi\u00f3n.", "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." diff --git a/homeassistant/components/brother/translations/et.json b/homeassistant/components/brother/translations/et.json new file mode 100644 index 00000000000000..1aac71fc60d899 --- /dev/null +++ b/homeassistant/components/brother/translations/et.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "unsupported_model": "Seda printeri mudelit ei toetata." + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "connection_error": "\u00dchenduse t\u00f5rge.", + "snmp_error": "SNMP-server on v\u00e4lja l\u00fclitatud v\u00f5i printerit ei toetata.", + "wrong_host": "Sobimatu hostinimi v\u00f5i IP-aadress." + }, + "flow_title": "Brotheri printer: {model} {serial_number}", + "step": { + "user": { + "data": { + "host": "Host", + "type": "Printeri t\u00fc\u00fcp" + }, + "description": "Seadistage Brotheri printeri sidumine. Kui teil on seadistamisega probleeme minge aadressile https://www.home-assistant.io/integrations/brother" + }, + "zeroconf_confirm": { + "data": { + "type": "Printeri t\u00fc\u00fcp" + }, + "description": "Kas soovite lisada Home Assistanti Brotheri printeri {model} seerianumbriga \" {serial_number} \"?", + "title": "Avastatud Brotheri printer" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/brother/translations/fr.json b/homeassistant/components/brother/translations/fr.json index e47b046e59e5da..83b360b1706738 100644 --- a/homeassistant/components/brother/translations/fr.json +++ b/homeassistant/components/brother/translations/fr.json @@ -5,6 +5,7 @@ "unsupported_model": "Ce mod\u00e8le d'imprimante n'est pas pris en charge." }, "error": { + "cannot_connect": "\u00c9chec de connexion", "connection_error": "Erreur de connexion.", "snmp_error": "Serveur SNMP d\u00e9sactiv\u00e9 ou imprimante non prise en charge.", "wrong_host": "Nom d'h\u00f4te ou adresse IP invalide." diff --git a/homeassistant/components/brother/translations/it.json b/homeassistant/components/brother/translations/it.json index 47d378606f1a1b..47191618bc8cad 100644 --- a/homeassistant/components/brother/translations/it.json +++ b/homeassistant/components/brother/translations/it.json @@ -1,11 +1,12 @@ { "config": { "abort": { - "already_configured": "Questa stampante \u00e8 gi\u00e0 configurata.", + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", "unsupported_model": "Questo modello di stampante non \u00e8 supportato." }, "error": { - "connection_error": "Errore di connessione.", + "cannot_connect": "Impossibile connettersi", + "connection_error": "Impossibile connettersi", "snmp_error": "Server SNMP spento o stampante non supportata.", "wrong_host": "Nome host o indirizzo IP non valido." }, diff --git a/homeassistant/components/brother/translations/no.json b/homeassistant/components/brother/translations/no.json index bfc5d811f42ada..6fafe9b1d3a0a2 100644 --- a/homeassistant/components/brother/translations/no.json +++ b/homeassistant/components/brother/translations/no.json @@ -1,11 +1,12 @@ { "config": { "abort": { - "already_configured": "Denne skriveren er allerede konfigurert.", + "already_configured": "Enheten er allerede konfigurert", "unsupported_model": "Denne skrivermodellen er ikke st\u00f8ttet." }, "error": { - "connection_error": "Tilkoblingen mislyktes.", + "cannot_connect": "Tilkobling mislyktes.", + "connection_error": "Tilkobling mislyktes.", "snmp_error": "SNMP verten er skrudd av eller printeren er ikke st\u00f8ttet.", "wrong_host": "Ugyldig vertsnavn eller IP-adresse." }, diff --git a/homeassistant/components/brother/translations/pl.json b/homeassistant/components/brother/translations/pl.json index cb7c3365628105..250e7d81f31446 100644 --- a/homeassistant/components/brother/translations/pl.json +++ b/homeassistant/components/brother/translations/pl.json @@ -5,6 +5,7 @@ "unsupported_model": "Ten model drukarki nie jest obs\u0142ugiwany." }, "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "connection_error": "B\u0142\u0105d po\u0142\u0105czenia.", "snmp_error": "Serwer SNMP wy\u0142\u0105czony lub drukarka nie jest obs\u0142ugiwana.", "wrong_host": "Niepoprawna nazwa hosta lub adres IP drukarki." diff --git a/homeassistant/components/brother/translations/ru.json b/homeassistant/components/brother/translations/ru.json index f880f754590dcf..635fbf87f48a59 100644 --- a/homeassistant/components/brother/translations/ru.json +++ b/homeassistant/components/brother/translations/ru.json @@ -1,11 +1,12 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "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.", "unsupported_model": "\u042d\u0442\u0430 \u043c\u043e\u0434\u0435\u043b\u044c \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u0430 \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f." }, "error": { - "connection_error": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\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.", "snmp_error": "\u0421\u0435\u0440\u0432\u0435\u0440 SNMP \u0432\u044b\u043a\u043b\u044e\u0447\u0435\u043d \u0438\u043b\u0438 \u043f\u0440\u0438\u043d\u0442\u0435\u0440 \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f.", "wrong_host": "\u041d\u0435\u0432\u0435\u0440\u043d\u043e\u0435 \u0434\u043e\u043c\u0435\u043d\u043d\u043e\u0435 \u0438\u043c\u044f \u0438\u043b\u0438 IP-\u0430\u0434\u0440\u0435\u0441." }, diff --git a/homeassistant/components/brother/translations/zh-Hant.json b/homeassistant/components/brother/translations/zh-Hant.json index a1d35a44984418..36455eaf085096 100644 --- a/homeassistant/components/brother/translations/zh-Hant.json +++ b/homeassistant/components/brother/translations/zh-Hant.json @@ -1,11 +1,12 @@ { "config": { "abort": { - "already_configured": "\u6b64\u5370\u8868\u6a5f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "unsupported_model": "\u4e0d\u652f\u63f4\u6b64\u6b3e\u5370\u8868\u6a5f\u3002" }, "error": { - "connection_error": "\u9023\u7dda\u932f\u8aa4\u3002", + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "connection_error": "\u9023\u7dda\u5931\u6557", "snmp_error": "SNMP \u4f3a\u670d\u5668\u70ba\u95dc\u9589\u72c0\u614b\u6216\u5370\u8868\u6a5f\u4e0d\u652f\u63f4\u3002", "wrong_host": "\u7121\u6548\u4e3b\u6a5f\u540d\u6216 IP \u4f4d\u5740" }, diff --git a/homeassistant/components/brunt/cover.py b/homeassistant/components/brunt/cover.py index 7f36874c40ef2e..ceb56ba03fa750 100644 --- a/homeassistant/components/brunt/cover.py +++ b/homeassistant/components/brunt/cover.py @@ -7,6 +7,7 @@ from homeassistant.components.cover import ( ATTR_POSITION, + DEVICE_CLASS_WINDOW, PLATFORM_SCHEMA, SUPPORT_CLOSE, SUPPORT_OPEN, @@ -19,7 +20,6 @@ _LOGGER = logging.getLogger(__name__) COVER_FEATURES = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION -DEVICE_CLASS = "window" ATTR_REQUEST_POSITION = "request_position" NOTIFICATION_ID = "brunt_notification" @@ -141,7 +141,7 @@ def device_state_attributes(self): @property def device_class(self): """Return the class of this device, from component DEVICE_CLASSES.""" - return DEVICE_CLASS + return DEVICE_CLASS_WINDOW @property def supported_features(self): diff --git a/homeassistant/components/bsblan/config_flow.py b/homeassistant/components/bsblan/config_flow.py index 1ecfc239af6fd4..e0e806ae205a54 100644 --- a/homeassistant/components/bsblan/config_flow.py +++ b/homeassistant/components/bsblan/config_flow.py @@ -39,7 +39,7 @@ async def async_step_user( passkey=user_input.get(CONF_PASSKEY), ) except BSBLanError: - return self._show_setup_form({"base": "connection_error"}) + return self._show_setup_form({"base": "cannot_connect"}) # Check if already configured await self.async_set_unique_id(info.device_identification) diff --git a/homeassistant/components/bsblan/strings.json b/homeassistant/components/bsblan/strings.json index 2e7c63f4d3a2a5..1003f75a4a3339 100644 --- a/homeassistant/components/bsblan/strings.json +++ b/homeassistant/components/bsblan/strings.json @@ -14,10 +14,10 @@ } }, "error": { - "connection_error": "Failed to connect to BSB-Lan device." + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, "abort": { - "already_configured": "Device is already configured" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/bsblan/translations/ca.json b/homeassistant/components/bsblan/translations/ca.json index 2fcab5683de5b9..6096784c879908 100644 --- a/homeassistant/components/bsblan/translations/ca.json +++ b/homeassistant/components/bsblan/translations/ca.json @@ -4,6 +4,7 @@ "already_configured": "El dispositiu ja est\u00e0 configurat" }, "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", "connection_error": "No s'ha pogut connectar amb el dispositiu BSB-Lan." }, "flow_title": "BSB-Lan: {name}", diff --git a/homeassistant/components/bsblan/translations/el.json b/homeassistant/components/bsblan/translations/el.json new file mode 100644 index 00000000000000..04b238a916d221 --- /dev/null +++ b/homeassistant/components/bsblan/translations/el.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bsblan/translations/en.json b/homeassistant/components/bsblan/translations/en.json index ce745b351b13a4..b8e3d0b5495116 100644 --- a/homeassistant/components/bsblan/translations/en.json +++ b/homeassistant/components/bsblan/translations/en.json @@ -4,6 +4,7 @@ "already_configured": "Device is already configured" }, "error": { + "cannot_connect": "Failed to connect", "connection_error": "Failed to connect to BSB-Lan device." }, "flow_title": "BSB-Lan: {name}", diff --git a/homeassistant/components/bsblan/translations/es.json b/homeassistant/components/bsblan/translations/es.json index e22dbfa75ec516..319948ff5d19cf 100644 --- a/homeassistant/components/bsblan/translations/es.json +++ b/homeassistant/components/bsblan/translations/es.json @@ -4,6 +4,7 @@ "already_configured": "El dispositivo ya est\u00e1 configurado" }, "error": { + "cannot_connect": "No se pudo conectar", "connection_error": "No se ha podido conectar con el dispositivo BSB-Lan." }, "flow_title": "BSB-Lan: {name}", diff --git a/homeassistant/components/bsblan/translations/et.json b/homeassistant/components/bsblan/translations/et.json new file mode 100644 index 00000000000000..81308dd3b50651 --- /dev/null +++ b/homeassistant/components/bsblan/translations/et.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "connection_error": "BSB-Lan seadmega \u00fchenduse loomine nurjus." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/bsblan/translations/fr.json b/homeassistant/components/bsblan/translations/fr.json index 48b235aa1770a5..1b912eaa93b476 100644 --- a/homeassistant/components/bsblan/translations/fr.json +++ b/homeassistant/components/bsblan/translations/fr.json @@ -4,6 +4,7 @@ "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" }, "error": { + "cannot_connect": "\u00c9chec de connexion", "connection_error": "Impossible de se connecter \u00e0 l'appareil BSB-Lan." }, "flow_title": "BSB-Lan: {name}", diff --git a/homeassistant/components/bsblan/translations/it.json b/homeassistant/components/bsblan/translations/it.json index 421a5ad975a34f..2ff7f173d17962 100644 --- a/homeassistant/components/bsblan/translations/it.json +++ b/homeassistant/components/bsblan/translations/it.json @@ -4,6 +4,7 @@ "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" }, "error": { + "cannot_connect": "Impossibile connettersi", "connection_error": "Impossibile connettersi al dispositivo BSB-Lan." }, "flow_title": "BSB-Lan: {name}", diff --git a/homeassistant/components/bsblan/translations/nl.json b/homeassistant/components/bsblan/translations/nl.json index c1909b19508901..c92ccbebb62b73 100644 --- a/homeassistant/components/bsblan/translations/nl.json +++ b/homeassistant/components/bsblan/translations/nl.json @@ -3,6 +3,7 @@ "step": { "user": { "data": { + "host": "Host", "port": "Poort" } } diff --git a/homeassistant/components/bsblan/translations/no.json b/homeassistant/components/bsblan/translations/no.json index 040349997f444e..b631b28b10edda 100644 --- a/homeassistant/components/bsblan/translations/no.json +++ b/homeassistant/components/bsblan/translations/no.json @@ -4,6 +4,7 @@ "already_configured": "Enheten er allerede konfigurert" }, "error": { + "cannot_connect": "Tilkobling mislyktes.", "connection_error": "Kunne ikke koble til BSB-Lan-enheten." }, "flow_title": "BSB-Lan: {name}", diff --git a/homeassistant/components/bsblan/translations/pl.json b/homeassistant/components/bsblan/translations/pl.json index 21b4208576162a..6288eab9604e62 100644 --- a/homeassistant/components/bsblan/translations/pl.json +++ b/homeassistant/components/bsblan/translations/pl.json @@ -1,9 +1,10 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" }, "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia z urz\u0105dzeniem BSB_LAN." }, "flow_title": "BSB-Lan: {name}", diff --git a/homeassistant/components/bsblan/translations/ru.json b/homeassistant/components/bsblan/translations/ru.json index 60d0ad4b6536e9..3d4009abb7bb31 100644 --- a/homeassistant/components/bsblan/translations/ru.json +++ b/homeassistant/components/bsblan/translations/ru.json @@ -1,9 +1,10 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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.", "connection_error": "\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." }, "flow_title": "BSB-Lan: {name}", diff --git a/homeassistant/components/bsblan/translations/zh-Hant.json b/homeassistant/components/bsblan/translations/zh-Hant.json index 09f15ff84f89e0..81de5bf53658de 100644 --- a/homeassistant/components/bsblan/translations/zh-Hant.json +++ b/homeassistant/components/bsblan/translations/zh-Hant.json @@ -4,6 +4,7 @@ "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", "connection_error": "BSB-Lan \u8a2d\u5099\u9023\u7dda\u5931\u6557\u3002" }, "flow_title": "BSB-Lan\uff1a{name}", diff --git a/homeassistant/components/buienradar/sensor.py b/homeassistant/components/buienradar/sensor.py index ba0063ebb85864..b1e41122dc002d 100644 --- a/homeassistant/components/buienradar/sensor.py +++ b/homeassistant/components/buienradar/sensor.py @@ -30,7 +30,9 @@ DEGREE, IRRADIATION_WATTS_PER_SQUARE_METER, LENGTH_KILOMETERS, + LENGTH_MILLIMETERS, PERCENTAGE, + PRESSURE_HPA, SPEED_KILOMETERS_PER_HOUR, TEMP_CELSIUS, TIME_HOURS, @@ -78,25 +80,29 @@ "windforce": ["Wind force", "Bft", "mdi:weather-windy"], "winddirection": ["Wind direction", None, "mdi:compass-outline"], "windazimuth": ["Wind direction azimuth", DEGREE, "mdi:compass-outline"], - "pressure": ["Pressure", "hPa", "mdi:gauge"], + "pressure": ["Pressure", PRESSURE_HPA, "mdi:gauge"], "visibility": ["Visibility", LENGTH_KILOMETERS, None], "windgust": ["Wind gust", SPEED_KILOMETERS_PER_HOUR, "mdi:weather-windy"], - "precipitation": ["Precipitation", f"mm/{TIME_HOURS}", "mdi:weather-pouring"], + "precipitation": [ + "Precipitation", + f"{LENGTH_MILLIMETERS}/{TIME_HOURS}", + "mdi:weather-pouring", + ], "irradiance": ["Irradiance", IRRADIATION_WATTS_PER_SQUARE_METER, "mdi:sunglasses"], "precipitation_forecast_average": [ "Precipitation forecast average", - f"mm/{TIME_HOURS}", + f"{LENGTH_MILLIMETERS}/{TIME_HOURS}", "mdi:weather-pouring", ], "precipitation_forecast_total": [ "Precipitation forecast total", - "mm", + LENGTH_MILLIMETERS, "mdi:weather-pouring", ], # new in json api (>1.0.0): - "rainlast24hour": ["Rain last 24h", "mm", "mdi:weather-pouring"], + "rainlast24hour": ["Rain last 24h", LENGTH_MILLIMETERS, "mdi:weather-pouring"], # new in json api (>1.0.0): - "rainlasthour": ["Rain last hour", "mm", "mdi:weather-pouring"], + "rainlasthour": ["Rain last hour", LENGTH_MILLIMETERS, "mdi:weather-pouring"], "temperature_1d": ["Temperature 1d", TEMP_CELSIUS, "mdi:thermometer"], "temperature_2d": ["Temperature 2d", TEMP_CELSIUS, "mdi:thermometer"], "temperature_3d": ["Temperature 3d", TEMP_CELSIUS, "mdi:thermometer"], @@ -107,23 +113,23 @@ "mintemp_3d": ["Minimum temperature 3d", TEMP_CELSIUS, "mdi:thermometer"], "mintemp_4d": ["Minimum temperature 4d", TEMP_CELSIUS, "mdi:thermometer"], "mintemp_5d": ["Minimum temperature 5d", TEMP_CELSIUS, "mdi:thermometer"], - "rain_1d": ["Rain 1d", "mm", "mdi:weather-pouring"], - "rain_2d": ["Rain 2d", "mm", "mdi:weather-pouring"], - "rain_3d": ["Rain 3d", "mm", "mdi:weather-pouring"], - "rain_4d": ["Rain 4d", "mm", "mdi:weather-pouring"], - "rain_5d": ["Rain 5d", "mm", "mdi:weather-pouring"], + "rain_1d": ["Rain 1d", LENGTH_MILLIMETERS, "mdi:weather-pouring"], + "rain_2d": ["Rain 2d", LENGTH_MILLIMETERS, "mdi:weather-pouring"], + "rain_3d": ["Rain 3d", LENGTH_MILLIMETERS, "mdi:weather-pouring"], + "rain_4d": ["Rain 4d", LENGTH_MILLIMETERS, "mdi:weather-pouring"], + "rain_5d": ["Rain 5d", LENGTH_MILLIMETERS, "mdi:weather-pouring"], # new in json api (>1.0.0): - "minrain_1d": ["Minimum rain 1d", "mm", "mdi:weather-pouring"], - "minrain_2d": ["Minimum rain 2d", "mm", "mdi:weather-pouring"], - "minrain_3d": ["Minimum rain 3d", "mm", "mdi:weather-pouring"], - "minrain_4d": ["Minimum rain 4d", "mm", "mdi:weather-pouring"], - "minrain_5d": ["Minimum rain 5d", "mm", "mdi:weather-pouring"], + "minrain_1d": ["Minimum rain 1d", LENGTH_MILLIMETERS, "mdi:weather-pouring"], + "minrain_2d": ["Minimum rain 2d", LENGTH_MILLIMETERS, "mdi:weather-pouring"], + "minrain_3d": ["Minimum rain 3d", LENGTH_MILLIMETERS, "mdi:weather-pouring"], + "minrain_4d": ["Minimum rain 4d", LENGTH_MILLIMETERS, "mdi:weather-pouring"], + "minrain_5d": ["Minimum rain 5d", LENGTH_MILLIMETERS, "mdi:weather-pouring"], # new in json api (>1.0.0): - "maxrain_1d": ["Maximum rain 1d", "mm", "mdi:weather-pouring"], - "maxrain_2d": ["Maximum rain 2d", "mm", "mdi:weather-pouring"], - "maxrain_3d": ["Maximum rain 3d", "mm", "mdi:weather-pouring"], - "maxrain_4d": ["Maximum rain 4d", "mm", "mdi:weather-pouring"], - "maxrain_5d": ["Maximum rain 5d", "mm", "mdi:weather-pouring"], + "maxrain_1d": ["Maximum rain 1d", LENGTH_MILLIMETERS, "mdi:weather-pouring"], + "maxrain_2d": ["Maximum rain 2d", LENGTH_MILLIMETERS, "mdi:weather-pouring"], + "maxrain_3d": ["Maximum rain 3d", LENGTH_MILLIMETERS, "mdi:weather-pouring"], + "maxrain_4d": ["Maximum rain 4d", LENGTH_MILLIMETERS, "mdi:weather-pouring"], + "maxrain_5d": ["Maximum rain 5d", LENGTH_MILLIMETERS, "mdi:weather-pouring"], "rainchance_1d": ["Rainchance 1d", PERCENTAGE, "mdi:weather-pouring"], "rainchance_2d": ["Rainchance 2d", PERCENTAGE, "mdi:weather-pouring"], "rainchance_3d": ["Rainchance 3d", PERCENTAGE, "mdi:weather-pouring"], diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index f6b909231cab09..25505800709284 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -19,6 +19,7 @@ from homeassistant.components.media_player.const import ( ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, + ATTR_MEDIA_EXTRA, DOMAIN as DOMAIN_MP, SERVICE_PLAY_MEDIA, ) @@ -35,6 +36,7 @@ from homeassistant.const import ( ATTR_ENTITY_ID, CONF_FILENAME, + CONTENT_TYPE_MULTIPART, EVENT_HOMEASSISTANT_START, SERVICE_TURN_OFF, SERVICE_TURN_ON, @@ -46,7 +48,7 @@ PLATFORM_SCHEMA, PLATFORM_SCHEMA_BASE, ) -from homeassistant.helpers.entity import Entity +from homeassistant.helpers.entity import Entity, entity_sources from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.network import get_url from homeassistant.loader import bind_hass @@ -184,7 +186,7 @@ async def async_get_still_stream(request, image_cb, content_type, interval): This method must be run in the event loop. """ response = web.StreamResponse() - response.content_type = "multipart/x-mixed-replace; boundary=--frameboundary" + response.content_type = CONTENT_TYPE_MULTIPART.format("--frameboundary") await response.prepare(request) async def write_to_mjpeg_stream(img_bytes): @@ -695,14 +697,47 @@ async def async_handle_play_stream_service(camera, service_call): options=camera.stream_options, ) data = { - ATTR_ENTITY_ID: entity_ids, ATTR_MEDIA_CONTENT_ID: f"{get_url(hass)}{url}", ATTR_MEDIA_CONTENT_TYPE: FORMAT_CONTENT_TYPE[fmt], } - await hass.services.async_call( - DOMAIN_MP, SERVICE_PLAY_MEDIA, data, blocking=True, context=service_call.context - ) + # It is required to send a different payload for cast media players + cast_entity_ids = [ + entity + for entity, source in entity_sources(hass).items() + if entity in entity_ids and source["domain"] == "cast" + ] + other_entity_ids = list(set(entity_ids) - set(cast_entity_ids)) + + if cast_entity_ids: + await hass.services.async_call( + DOMAIN_MP, + SERVICE_PLAY_MEDIA, + { + ATTR_ENTITY_ID: cast_entity_ids, + **data, + ATTR_MEDIA_EXTRA: { + "stream_type": "LIVE", + "media_info": { + "hlsVideoSegmentFormat": "fmp4", + }, + }, + }, + blocking=True, + context=service_call.context, + ) + + if other_entity_ids: + await hass.services.async_call( + DOMAIN_MP, + SERVICE_PLAY_MEDIA, + { + ATTR_ENTITY_ID: other_entity_ids, + **data, + }, + blocking=True, + context=service_call.context, + ) async def async_handle_record_service(camera, call): diff --git a/homeassistant/components/canary/__init__.py b/homeassistant/components/canary/__init__.py index 165787a7f46a1b..64f2d00735eabe 100644 --- a/homeassistant/components/canary/__init__.py +++ b/homeassistant/components/canary/__init__.py @@ -1,4 +1,5 @@ """Support for Canary devices.""" +import asyncio from datetime import timedelta import logging @@ -6,20 +7,26 @@ from requests import ConnectTimeout, HTTPError import voluptuous as vol +from homeassistant.components.camera.const import DOMAIN as CAMERA_DOMAIN +from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME -from homeassistant.helpers import discovery +from homeassistant.exceptions import ConfigEntryNotReady import homeassistant.helpers.config_validation as cv -from homeassistant.util import Throttle +from homeassistant.helpers.typing import HomeAssistantType + +from .const import ( + CONF_FFMPEG_ARGUMENTS, + DATA_COORDINATOR, + DATA_UNDO_UPDATE_LISTENER, + DEFAULT_FFMPEG_ARGUMENTS, + DEFAULT_TIMEOUT, + DOMAIN, +) +from .coordinator import CanaryDataUpdateCoordinator _LOGGER = logging.getLogger(__name__) -NOTIFICATION_ID = "canary_notification" -NOTIFICATION_TITLE = "Canary Setup" - -DOMAIN = "canary" -DATA_CANARY = "canary" MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30) -DEFAULT_TIMEOUT = 10 CONFIG_SCHEMA = vol.Schema( { @@ -34,90 +41,109 @@ extra=vol.ALLOW_EXTRA, ) -CANARY_COMPONENTS = ["alarm_control_panel", "camera", "sensor"] +PLATFORMS = ["alarm_control_panel", "camera", "sensor"] -def setup(hass, config): - """Set up the Canary component.""" - conf = config[DOMAIN] - username = conf[CONF_USERNAME] - password = conf[CONF_PASSWORD] - timeout = conf[CONF_TIMEOUT] +async def async_setup(hass: HomeAssistantType, config: dict) -> bool: + """Set up the Canary integration.""" + hass.data.setdefault(DOMAIN, {}) - try: - hass.data[DATA_CANARY] = CanaryData(username, password, timeout) - except (ConnectTimeout, HTTPError) as ex: - _LOGGER.error("Unable to connect to Canary service: %s", str(ex)) - hass.components.persistent_notification.create( - f"Error: {ex}
You will need to restart hass after fixing.", - title=NOTIFICATION_TITLE, - notification_id=NOTIFICATION_ID, - ) - return False + if hass.config_entries.async_entries(DOMAIN): + return True - for component in CANARY_COMPONENTS: - discovery.load_platform(hass, component, DOMAIN, {}, config) + ffmpeg_arguments = DEFAULT_FFMPEG_ARGUMENTS + if CAMERA_DOMAIN in config: + camera_config = next( + (item for item in config[CAMERA_DOMAIN] if item["platform"] == DOMAIN), + None, + ) + if camera_config: + ffmpeg_arguments = camera_config.get( + CONF_FFMPEG_ARGUMENTS, DEFAULT_FFMPEG_ARGUMENTS + ) + + if DOMAIN in config: + if ffmpeg_arguments != DEFAULT_FFMPEG_ARGUMENTS: + config[DOMAIN][CONF_FFMPEG_ARGUMENTS] = ffmpeg_arguments + + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=config[DOMAIN], + ) + ) return True -class CanaryData: - """Get the latest data and update the states.""" +async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: + """Set up Canary from a config entry.""" + if not entry.options: + options = { + CONF_FFMPEG_ARGUMENTS: entry.data.get( + CONF_FFMPEG_ARGUMENTS, DEFAULT_FFMPEG_ARGUMENTS + ), + CONF_TIMEOUT: entry.data.get(CONF_TIMEOUT, DEFAULT_TIMEOUT), + } + hass.config_entries.async_update_entry(entry, options=options) + + try: + canary_api = await hass.async_add_executor_job(_get_canary_api_instance, entry) + except (ConnectTimeout, HTTPError) as error: + _LOGGER.error("Unable to connect to Canary service: %s", str(error)) + raise ConfigEntryNotReady from error + + coordinator = CanaryDataUpdateCoordinator(hass, api=canary_api) + await coordinator.async_refresh() - def __init__(self, username, password, timeout): - """Init the Canary data object.""" + if not coordinator.last_update_success: + raise ConfigEntryNotReady - self._api = Api(username, password, timeout) + undo_listener = entry.add_update_listener(_async_update_listener) - self._locations_by_id = {} - self._readings_by_device_id = {} + hass.data[DOMAIN][entry.entry_id] = { + DATA_COORDINATOR: coordinator, + DATA_UNDO_UPDATE_LISTENER: undo_listener, + } - self.update() + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, component) + ) - @Throttle(MIN_TIME_BETWEEN_UPDATES) - def update(self, **kwargs): - """Get the latest data from py-canary.""" - for location in self._api.get_locations(): - location_id = location.location_id + return True - self._locations_by_id[location_id] = location - for device in location.devices: - if device.is_online: - self._readings_by_device_id[ - device.device_id - ] = self._api.get_latest_readings(device.device_id) +async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, component) + for component in PLATFORMS + ] + ) + ) - @property - def locations(self): - """Return a list of locations.""" - return self._locations_by_id.values() + if unload_ok: + hass.data[DOMAIN][entry.entry_id][DATA_UNDO_UPDATE_LISTENER]() + hass.data[DOMAIN].pop(entry.entry_id) - def get_location(self, location_id): - """Return a location based on location_id.""" - return self._locations_by_id.get(location_id, []) + return unload_ok - def get_readings(self, device_id): - """Return a list of readings based on device_id.""" - return self._readings_by_device_id.get(device_id, []) - def get_reading(self, device_id, sensor_type): - """Return reading for device_id and sensor type.""" - readings = self._readings_by_device_id.get(device_id, []) - return next( - ( - reading.value - for reading in readings - if reading.sensor_type == sensor_type - ), - None, - ) +async def _async_update_listener(hass: HomeAssistantType, entry: ConfigEntry) -> None: + """Handle options update.""" + await hass.config_entries.async_reload(entry.entry_id) + - def set_location_mode(self, location_id, mode_name, is_private=False): - """Set location mode.""" - self._api.set_location_mode(location_id, mode_name, is_private) - self.update(no_throttle=True) +def _get_canary_api_instance(entry: ConfigEntry) -> Api: + """Initialize a new instance of CanaryApi.""" + canary = Api( + entry.data[CONF_USERNAME], + entry.data[CONF_PASSWORD], + entry.options.get(CONF_TIMEOUT, DEFAULT_TIMEOUT), + ) - def get_live_stream_session(self, device): - """Return live stream session.""" - return self._api.get_live_stream_session(device) + return canary diff --git a/homeassistant/components/canary/alarm_control_panel.py b/homeassistant/components/canary/alarm_control_panel.py index ea0e3078b0c269..957b659eb79c89 100644 --- a/homeassistant/components/canary/alarm_control_panel.py +++ b/homeassistant/components/canary/alarm_control_panel.py @@ -1,5 +1,6 @@ """Support for Canary alarm.""" import logging +from typing import Callable, List from canary.api import LOCATION_MODE_AWAY, LOCATION_MODE_HOME, LOCATION_MODE_NIGHT @@ -9,55 +10,78 @@ SUPPORT_ALARM_ARM_HOME, SUPPORT_ALARM_ARM_NIGHT, ) +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.helpers.entity import Entity +from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import DATA_CANARY +from .const import DATA_COORDINATOR, DOMAIN +from .coordinator import CanaryDataUpdateCoordinator _LOGGER = logging.getLogger(__name__) -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the Canary alarms.""" - data = hass.data[DATA_CANARY] - devices = [CanaryAlarm(data, location.location_id) for location in data.locations] +async def async_setup_entry( + hass: HomeAssistantType, + entry: ConfigEntry, + async_add_entities: Callable[[List[Entity], bool], None], +) -> None: + """Set up Canary alarm control panels based on a config entry.""" + coordinator: CanaryDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][ + DATA_COORDINATOR + ] + alarms = [ + CanaryAlarm(coordinator, location) + for location_id, location in coordinator.data["locations"].items() + ] - add_entities(devices, True) + async_add_entities(alarms, True) -class CanaryAlarm(AlarmControlPanelEntity): +class CanaryAlarm(CoordinatorEntity, AlarmControlPanelEntity): """Representation of a Canary alarm control panel.""" - def __init__(self, data, location_id): + def __init__(self, coordinator, location): """Initialize a Canary security camera.""" - self._data = data - self._location_id = location_id + super().__init__(coordinator) + self._location_id = location.location_id + self._location_name = location.name + + @property + def location(self): + """Return information about the location.""" + return self.coordinator.data["locations"][self._location_id] @property def name(self): """Return the name of the alarm.""" - location = self._data.get_location(self._location_id) - return location.name + return self._location_name + + @property + def unique_id(self): + """Return the unique ID of the alarm.""" + return str(self._location_id) @property def state(self): """Return the state of the device.""" - location = self._data.get_location(self._location_id) - - if location.is_private: + if self.location.is_private: return STATE_ALARM_DISARMED - mode = location.mode + mode = self.location.mode if mode.name == LOCATION_MODE_AWAY: return STATE_ALARM_ARMED_AWAY if mode.name == LOCATION_MODE_HOME: return STATE_ALARM_ARMED_HOME if mode.name == LOCATION_MODE_NIGHT: return STATE_ALARM_ARMED_NIGHT + return None @property @@ -68,26 +92,24 @@ def supported_features(self) -> int: @property def device_state_attributes(self): """Return the state attributes.""" - location = self._data.get_location(self._location_id) - return {"private": location.is_private} + return {"private": self.location.is_private} def alarm_disarm(self, code=None): """Send disarm command.""" - location = self._data.get_location(self._location_id) - self._data.set_location_mode(self._location_id, location.mode.name, True) + self.coordinator.canary.set_location_mode( + self._location_id, self.location.mode.name, True + ) def alarm_arm_home(self, code=None): """Send arm home command.""" - self._data.set_location_mode(self._location_id, LOCATION_MODE_HOME) + self.coordinator.canary.set_location_mode(self._location_id, LOCATION_MODE_HOME) def alarm_arm_away(self, code=None): """Send arm away command.""" - self._data.set_location_mode(self._location_id, LOCATION_MODE_AWAY) + self.coordinator.canary.set_location_mode(self._location_id, LOCATION_MODE_AWAY) def alarm_arm_night(self, code=None): """Send arm night command.""" - self._data.set_location_mode(self._location_id, LOCATION_MODE_NIGHT) - - def update(self): - """Get the latest state of the sensor.""" - self._data.update() + self.coordinator.canary.set_location_mode( + self._location_id, LOCATION_MODE_NIGHT + ) diff --git a/homeassistant/components/canary/camera.py b/homeassistant/components/canary/camera.py index 3ba7f094da145c..4d0a4a0d169a4d 100644 --- a/homeassistant/components/canary/camera.py +++ b/homeassistant/components/canary/camera.py @@ -2,6 +2,7 @@ import asyncio from datetime import timedelta import logging +from typing import Callable, List from haffmpeg.camera import CameraMjpeg from haffmpeg.tools import IMAGE_JPEG, ImageFrame @@ -9,78 +10,121 @@ from homeassistant.components.camera import PLATFORM_SCHEMA, Camera from homeassistant.components.ffmpeg import DATA_FFMPEG +from homeassistant.config_entries import ConfigEntry from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.util import Throttle -from . import DATA_CANARY, DEFAULT_TIMEOUT +from .const import ( + CONF_FFMPEG_ARGUMENTS, + DATA_COORDINATOR, + DEFAULT_FFMPEG_ARGUMENTS, + DEFAULT_TIMEOUT, + DOMAIN, + MANUFACTURER, +) +from .coordinator import CanaryDataUpdateCoordinator _LOGGER = logging.getLogger(__name__) -CONF_FFMPEG_ARGUMENTS = "ffmpeg_arguments" -DEFAULT_ARGUMENTS = "-pred 1" - MIN_TIME_BETWEEN_SESSION_RENEW = timedelta(seconds=90) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - {vol.Optional(CONF_FFMPEG_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string} +PLATFORM_SCHEMA = vol.All( + cv.deprecated(CONF_FFMPEG_ARGUMENTS, invalidation_version="0.118"), + PLATFORM_SCHEMA.extend( + { + vol.Optional( + CONF_FFMPEG_ARGUMENTS, default=DEFAULT_FFMPEG_ARGUMENTS + ): cv.string + } + ), ) -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the Canary sensors.""" - if discovery_info is not None: - return - - data = hass.data[DATA_CANARY] - devices = [] - - for location in data.locations: +async def async_setup_entry( + hass: HomeAssistantType, + entry: ConfigEntry, + async_add_entities: Callable[[List[Entity], bool], None], +) -> None: + """Set up Canary sensors based on a config entry.""" + coordinator: CanaryDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][ + DATA_COORDINATOR + ] + ffmpeg_arguments = entry.options.get( + CONF_FFMPEG_ARGUMENTS, DEFAULT_FFMPEG_ARGUMENTS + ) + cameras = [] + + for location_id, location in coordinator.data["locations"].items(): for device in location.devices: if device.is_online: - devices.append( + cameras.append( CanaryCamera( hass, - data, - location, + coordinator, + location_id, device, DEFAULT_TIMEOUT, - config[CONF_FFMPEG_ARGUMENTS], + ffmpeg_arguments, ) ) - add_entities(devices, True) + async_add_entities(cameras, True) -class CanaryCamera(Camera): +class CanaryCamera(CoordinatorEntity, Camera): """An implementation of a Canary security camera.""" - def __init__(self, hass, data, location, device, timeout, ffmpeg_args): + def __init__(self, hass, coordinator, location_id, device, timeout, ffmpeg_args): """Initialize a Canary security camera.""" - super().__init__() - + super().__init__(coordinator) self._ffmpeg = hass.data[DATA_FFMPEG] self._ffmpeg_arguments = ffmpeg_args - self._data = data - self._location = location + self._location_id = location_id self._device = device + self._device_id = device.device_id + self._device_name = device.name + self._device_type_name = device.device_type["name"] self._timeout = timeout self._live_stream_session = None + @property + def location(self): + """Return information about the location.""" + return self.coordinator.data["locations"][self._location_id] + @property def name(self): """Return the name of this device.""" - return self._device.name + return self._device_name + + @property + def unique_id(self): + """Return the unique ID of this camera.""" + return str(self._device_id) + + @property + def device_info(self): + """Return the device_info of the device.""" + return { + "identifiers": {(DOMAIN, str(self._device_id))}, + "name": self._device_name, + "model": self._device_type_name, + "manufacturer": MANUFACTURER, + } @property def is_recording(self): """Return true if the device is recording.""" - return self._location.is_recording + return self.location.is_recording @property def motion_detection_enabled(self): """Return the camera motion detection status.""" - return not self._location.is_recording + return not self.location.is_recording async def async_camera_image(self): """Return a still image response from the camera.""" @@ -120,4 +164,6 @@ async def handle_async_mjpeg_stream(self, request): @Throttle(MIN_TIME_BETWEEN_SESSION_RENEW) def renew_live_stream_session(self): """Renew live stream session.""" - self._live_stream_session = self._data.get_live_stream_session(self._device) + self._live_stream_session = self.coordinator.canary.get_live_stream_session( + self._device + ) diff --git a/homeassistant/components/canary/config_flow.py b/homeassistant/components/canary/config_flow.py new file mode 100644 index 00000000000000..dc2822d836a2ae --- /dev/null +++ b/homeassistant/components/canary/config_flow.py @@ -0,0 +1,121 @@ +"""Config flow for Canary.""" +import logging +from typing import Any, Dict, Optional + +from canary.api import Api +from requests import ConnectTimeout, HTTPError +import voluptuous as vol + +from homeassistant.config_entries import CONN_CLASS_CLOUD_POLL, ConfigFlow, OptionsFlow +from homeassistant.const import CONF_PASSWORD, CONF_TIMEOUT, CONF_USERNAME +from homeassistant.core import callback +from homeassistant.helpers.typing import ConfigType, HomeAssistantType + +from .const import CONF_FFMPEG_ARGUMENTS, DEFAULT_FFMPEG_ARGUMENTS, DEFAULT_TIMEOUT +from .const import DOMAIN # pylint: disable=unused-import + +_LOGGER = logging.getLogger(__name__) + + +def validate_input(hass: HomeAssistantType, data: dict) -> Dict[str, Any]: + """Validate the user input allows us to connect. + + Data has the keys from DATA_SCHEMA with values provided by the user. + """ + # constructor does login call + Api( + data[CONF_USERNAME], + data[CONF_PASSWORD], + data.get(CONF_TIMEOUT, DEFAULT_TIMEOUT), + ) + + return True + + +class CanaryConfigFlow(ConfigFlow, domain=DOMAIN): + """Handle a config flow for Canary.""" + + VERSION = 1 + CONNECTION_CLASS = CONN_CLASS_CLOUD_POLL + + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Get the options flow for this handler.""" + return CanaryOptionsFlowHandler(config_entry) + + async def async_step_import( + self, user_input: Optional[ConfigType] = None + ) -> Dict[str, Any]: + """Handle a flow initiated by configuration file.""" + return await self.async_step_user(user_input) + + async def async_step_user( + self, user_input: Optional[ConfigType] = None + ) -> Dict[str, Any]: + """Handle a flow initiated by the user.""" + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + + errors = {} + default_username = "" + + if user_input is not None: + if CONF_TIMEOUT not in user_input: + user_input[CONF_TIMEOUT] = DEFAULT_TIMEOUT + + default_username = user_input[CONF_USERNAME] + + try: + await self.hass.async_add_executor_job( + validate_input, self.hass, user_input + ) + except (ConnectTimeout, HTTPError): + errors["base"] = "cannot_connect" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + return self.async_abort(reason="unknown") + else: + return self.async_create_entry( + title=user_input[CONF_USERNAME], + data=user_input, + ) + + data_schema = { + vol.Required(CONF_USERNAME, default=default_username): str, + vol.Required(CONF_PASSWORD): str, + } + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema(data_schema), + errors=errors or {}, + ) + + +class CanaryOptionsFlowHandler(OptionsFlow): + """Handle Canary client options.""" + + def __init__(self, config_entry): + """Initialize options flow.""" + self.config_entry = config_entry + + async def async_step_init(self, user_input: Optional[ConfigType] = None): + """Manage Canary options.""" + if user_input is not None: + return self.async_create_entry(title="", data=user_input) + + options = { + vol.Optional( + CONF_FFMPEG_ARGUMENTS, + default=self.config_entry.options.get( + CONF_FFMPEG_ARGUMENTS, DEFAULT_FFMPEG_ARGUMENTS + ), + ): str, + vol.Optional( + CONF_TIMEOUT, + default=self.config_entry.options.get(CONF_TIMEOUT, DEFAULT_TIMEOUT), + ): int, + } + + return self.async_show_form(step_id="init", data_schema=vol.Schema(options)) diff --git a/homeassistant/components/canary/const.py b/homeassistant/components/canary/const.py new file mode 100644 index 00000000000000..8219a485ef972c --- /dev/null +++ b/homeassistant/components/canary/const.py @@ -0,0 +1,16 @@ +"""Constants for the Canary integration.""" + +DOMAIN = "canary" + +MANUFACTURER = "Canary Connect, Inc" + +# Configuration +CONF_FFMPEG_ARGUMENTS = "ffmpeg_arguments" + +# Data +DATA_COORDINATOR = "coordinator" +DATA_UNDO_UPDATE_LISTENER = "undo_update_listener" + +# Defaults +DEFAULT_FFMPEG_ARGUMENTS = "-pred 1" +DEFAULT_TIMEOUT = 10 diff --git a/homeassistant/components/canary/coordinator.py b/homeassistant/components/canary/coordinator.py new file mode 100644 index 00000000000000..650bc3d70eab05 --- /dev/null +++ b/homeassistant/components/canary/coordinator.py @@ -0,0 +1,59 @@ +"""Provides the Canary DataUpdateCoordinator.""" +from datetime import timedelta +import logging + +from async_timeout import timeout +from canary.api import Api +from requests import ConnectTimeout, HTTPError + +from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed + +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + + +class CanaryDataUpdateCoordinator(DataUpdateCoordinator): + """Class to manage fetching Canary data.""" + + def __init__(self, hass: HomeAssistantType, *, api: Api): + """Initialize global Canary data updater.""" + self.canary = api + update_interval = timedelta(seconds=30) + + super().__init__( + hass, + _LOGGER, + name=DOMAIN, + update_interval=update_interval, + ) + + def _update_data(self) -> dict: + """Fetch data from Canary via sync functions.""" + locations_by_id = {} + readings_by_device_id = {} + + for location in self.canary.get_locations(): + location_id = location.location_id + locations_by_id[location_id] = location + + for device in location.devices: + if device.is_online: + readings_by_device_id[ + device.device_id + ] = self.canary.get_latest_readings(device.device_id) + + return { + "locations": locations_by_id, + "readings": readings_by_device_id, + } + + async def _async_update_data(self) -> dict: + """Fetch data from Canary.""" + + try: + async with timeout(15): + return await self.hass.async_add_executor_job(self._update_data) + except (ConnectTimeout, HTTPError) as error: + raise UpdateFailed(f"Invalid response from API: {error}") from error diff --git a/homeassistant/components/canary/manifest.json b/homeassistant/components/canary/manifest.json index e383cb7514b9fa..b4598d64087f75 100644 --- a/homeassistant/components/canary/manifest.json +++ b/homeassistant/components/canary/manifest.json @@ -4,5 +4,6 @@ "documentation": "https://www.home-assistant.io/integrations/canary", "requirements": ["py-canary==0.5.0"], "dependencies": ["ffmpeg"], - "codeowners": [] + "codeowners": [], + "config_flow": true } diff --git a/homeassistant/components/canary/sensor.py b/homeassistant/components/canary/sensor.py index 5f1b1fe906b48f..99dcdf48fceddb 100644 --- a/homeassistant/components/canary/sensor.py +++ b/homeassistant/components/canary/sensor.py @@ -1,11 +1,24 @@ """Support for Canary sensors.""" +from typing import Callable, List + from canary.api import SensorType -from homeassistant.const import PERCENTAGE, TEMP_CELSIUS +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_HUMIDITY, + DEVICE_CLASS_SIGNAL_STRENGTH, + DEVICE_CLASS_TEMPERATURE, + PERCENTAGE, + SIGNAL_STRENGTH_DECIBELS_MILLIWATT, + TEMP_CELSIUS, +) from homeassistant.helpers.entity import Entity -from homeassistant.helpers.icon import icon_for_battery_level +from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.helpers.update_coordinator import CoordinatorEntity -from . import DATA_CANARY +from .const import DATA_COORDINATOR, DOMAIN, MANUFACTURER +from .coordinator import CanaryDataUpdateCoordinator SENSOR_VALUE_PRECISION = 2 ATTR_AIR_QUALITY = "air_quality" @@ -18,13 +31,19 @@ CANARY_FLEX = "Canary Flex" # Sensor types are defined like so: -# sensor type name, unit_of_measurement, icon +# sensor type name, unit_of_measurement, icon, device class, products supported SENSOR_TYPES = [ - ["temperature", TEMP_CELSIUS, "mdi:thermometer", [CANARY_PRO]], - ["humidity", PERCENTAGE, "mdi:water-percent", [CANARY_PRO]], - ["air_quality", None, "mdi:weather-windy", [CANARY_PRO]], - ["wifi", "dBm", "mdi:wifi", [CANARY_FLEX]], - ["battery", PERCENTAGE, "mdi:battery-50", [CANARY_FLEX]], + ["temperature", TEMP_CELSIUS, None, DEVICE_CLASS_TEMPERATURE, [CANARY_PRO]], + ["humidity", PERCENTAGE, None, DEVICE_CLASS_HUMIDITY, [CANARY_PRO]], + ["air_quality", None, "mdi:weather-windy", None, [CANARY_PRO]], + [ + "wifi", + SIGNAL_STRENGTH_DECIBELS_MILLIWATT, + None, + DEVICE_CLASS_SIGNAL_STRENGTH, + [CANARY_FLEX], + ], + ["battery", PERCENTAGE, None, DEVICE_CLASS_BATTERY, [CANARY_FLEX]], ] STATE_AIR_QUALITY_NORMAL = "normal" @@ -32,37 +51,77 @@ STATE_AIR_QUALITY_VERY_ABNORMAL = "very_abnormal" -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the Canary sensors.""" - data = hass.data[DATA_CANARY] - devices = [] +async def async_setup_entry( + hass: HomeAssistantType, + entry: ConfigEntry, + async_add_entities: Callable[[List[Entity], bool], None], +) -> None: + """Set up Canary sensors based on a config entry.""" + coordinator: CanaryDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][ + DATA_COORDINATOR + ] + sensors = [] - for location in data.locations: + for location in coordinator.data["locations"].values(): for device in location.devices: if device.is_online: device_type = device.device_type for sensor_type in SENSOR_TYPES: - if device_type.get("name") in sensor_type[3]: - devices.append( - CanarySensor(data, sensor_type, location, device) + if device_type.get("name") in sensor_type[4]: + sensors.append( + CanarySensor(coordinator, sensor_type, location, device) ) - add_entities(devices, True) + async_add_entities(sensors, True) -class CanarySensor(Entity): +class CanarySensor(CoordinatorEntity, Entity): """Representation of a Canary sensor.""" - def __init__(self, data, sensor_type, location, device): + def __init__(self, coordinator, sensor_type, location, device): """Initialize the sensor.""" - self._data = data + super().__init__(coordinator) self._sensor_type = sensor_type self._device_id = device.device_id - self._sensor_value = None + self._device_name = device.name + self._device_type_name = device.device_type["name"] sensor_type_name = sensor_type[0].replace("_", " ").title() self._name = f"{location.name} {device.name} {sensor_type_name}" + canary_sensor_type = None + if self._sensor_type[0] == "air_quality": + canary_sensor_type = SensorType.AIR_QUALITY + elif self._sensor_type[0] == "temperature": + canary_sensor_type = SensorType.TEMPERATURE + elif self._sensor_type[0] == "humidity": + canary_sensor_type = SensorType.HUMIDITY + elif self._sensor_type[0] == "wifi": + canary_sensor_type = SensorType.WIFI + elif self._sensor_type[0] == "battery": + canary_sensor_type = SensorType.BATTERY + + self._canary_type = canary_sensor_type + + @property + def reading(self): + """Return the device sensor reading.""" + readings = self.coordinator.data["readings"][self._device_id] + + value = next( + ( + reading.value + for reading in readings + if reading.sensor_type == self._canary_type + ), + None, + ) + + if value is not None: + return round(float(value), SENSOR_VALUE_PRECISION) + + return None + @property def name(self): """Return the name of the Canary sensor.""" @@ -71,59 +130,52 @@ def name(self): @property def state(self): """Return the state of the sensor.""" - return self._sensor_value + return self.reading @property def unique_id(self): """Return the unique ID of this sensor.""" return f"{self._device_id}_{self._sensor_type[0]}" + @property + def device_info(self): + """Return the device_info of the device.""" + return { + "identifiers": {(DOMAIN, str(self._device_id))}, + "name": self._device_name, + "model": self._device_type_name, + "manufacturer": MANUFACTURER, + } + @property def unit_of_measurement(self): """Return the unit of measurement.""" return self._sensor_type[1] + @property + def device_class(self): + """Device class for the sensor.""" + return self._sensor_type[3] + @property def icon(self): """Icon for the sensor.""" - if self.state is not None and self._sensor_type[0] == "battery": - return icon_for_battery_level(battery_level=self.state) - return self._sensor_type[2] @property def device_state_attributes(self): """Return the state attributes.""" - if self._sensor_type[0] == "air_quality" and self._sensor_value is not None: + reading = self.reading + + if self._sensor_type[0] == "air_quality" and reading is not None: air_quality = None - if self._sensor_value <= 0.4: + if reading <= 0.4: air_quality = STATE_AIR_QUALITY_VERY_ABNORMAL - elif self._sensor_value <= 0.59: + elif reading <= 0.59: air_quality = STATE_AIR_QUALITY_ABNORMAL - elif self._sensor_value <= 1.0: + elif reading <= 1.0: air_quality = STATE_AIR_QUALITY_NORMAL return {ATTR_AIR_QUALITY: air_quality} return None - - def update(self): - """Get the latest state of the sensor.""" - self._data.update() - - canary_sensor_type = None - if self._sensor_type[0] == "air_quality": - canary_sensor_type = SensorType.AIR_QUALITY - elif self._sensor_type[0] == "temperature": - canary_sensor_type = SensorType.TEMPERATURE - elif self._sensor_type[0] == "humidity": - canary_sensor_type = SensorType.HUMIDITY - elif self._sensor_type[0] == "wifi": - canary_sensor_type = SensorType.WIFI - elif self._sensor_type[0] == "battery": - canary_sensor_type = SensorType.BATTERY - - value = self._data.get_reading(self._device_id, canary_sensor_type) - - if value is not None: - self._sensor_value = round(float(value), SENSOR_VALUE_PRECISION) diff --git a/homeassistant/components/canary/strings.json b/homeassistant/components/canary/strings.json new file mode 100644 index 00000000000000..504a5dc2ac1ddf --- /dev/null +++ b/homeassistant/components/canary/strings.json @@ -0,0 +1,31 @@ +{ + "config": { + "flow_title": "Canary: {name}", + "step": { + "user": { + "title": "Connect to Canary", + "data": { + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]" + } + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" + }, + "abort": { + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + } + }, + "options": { + "step": { + "init": { + "data": { + "ffmpeg_arguments": "Arguments passed to ffmpeg for cameras", + "timeout": "Request Timeout (seconds)" + } + } + } + } +} diff --git a/homeassistant/components/canary/translations/ca.json b/homeassistant/components/canary/translations/ca.json new file mode 100644 index 00000000000000..c4b80d7537aa7e --- /dev/null +++ b/homeassistant/components/canary/translations/ca.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3.", + "unknown": "Error inesperat" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3" + }, + "flow_title": "Canary: {name}", + "step": { + "user": { + "data": { + "password": "Contrasenya", + "username": "Nom d'usuari" + }, + "title": "Connexi\u00f3 amb Canary" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ffmpeg_arguments": "Par\u00e0metres enviats a ffmpeg per c\u00e0meres", + "timeout": "Temps d'espera de sol\u00b7licitud (segons)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/canary/translations/de.json b/homeassistant/components/canary/translations/de.json new file mode 100644 index 00000000000000..159f961c3a6fa9 --- /dev/null +++ b/homeassistant/components/canary/translations/de.json @@ -0,0 +1,22 @@ +{ + "config": { + "flow_title": "Canary: {name}", + "step": { + "user": { + "data": { + "password": "Passwort", + "username": "Benutzername" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "Anfrage-Timeout (Sekunden)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/canary/translations/el.json b/homeassistant/components/canary/translations/el.json new file mode 100644 index 00000000000000..6b11d64282950e --- /dev/null +++ b/homeassistant/components/canary/translations/el.json @@ -0,0 +1,31 @@ +{ + "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.", + "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": "Canary: {name}", + "step": { + "user": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + }, + "title": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf Canary" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ffmpeg_arguments": "\u03a4\u03b1 \u03c7\u03b1\u03c1\u03b1\u03ba\u03c4\u03b7\u03c1\u03b9\u03c3\u03c4\u03b9\u03ba\u03ac \u03c0\u03bf\u03c5 \u03b4\u03b9\u03b1\u03b2\u03b9\u03b2\u03ac\u03c3\u03c4\u03b7\u03ba\u03b1\u03bd \u03c3\u03c4\u03bf ffmpeg \u03b3\u03b9\u03b1 \u03ba\u03ac\u03bc\u03b5\u03c1\u03b5\u03c2", + "timeout": "\u0391\u03af\u03c4\u03b7\u03bc\u03b1 \u03c7\u03c1\u03bf\u03bd\u03b9\u03ba\u03bf\u03cd \u03bf\u03c1\u03af\u03bf\u03c5 (\u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/canary/translations/en.json b/homeassistant/components/canary/translations/en.json new file mode 100644 index 00000000000000..1e04d1825f3487 --- /dev/null +++ b/homeassistant/components/canary/translations/en.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Already configured. Only a single configuration possible.", + "unknown": "Unexpected error" + }, + "error": { + "cannot_connect": "Failed to connect" + }, + "flow_title": "Canary: {name}", + "step": { + "user": { + "data": { + "password": "Password", + "username": "Username" + }, + "title": "Connect to Canary" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ffmpeg_arguments": "Arguments passed to ffmpeg for cameras", + "timeout": "Request Timeout (seconds)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/canary/translations/es.json b/homeassistant/components/canary/translations/es.json new file mode 100644 index 00000000000000..1b881d4dcd22a6 --- /dev/null +++ b/homeassistant/components/canary/translations/es.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ya est\u00e1 configurado. S\u00f3lo es posible una \u00fanica configuraci\u00f3n.", + "unknown": "Error inesperado" + }, + "error": { + "cannot_connect": "No se pudo conectar" + }, + "flow_title": "Canary: {name}", + "step": { + "user": { + "data": { + "password": "Contrase\u00f1a", + "username": "Usuario" + }, + "title": "Conectar a Canary" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ffmpeg_arguments": "Par\u00e1metros pasados a ffmpeg para c\u00e1maras", + "timeout": "Tiempo de espera de la solicitud (segundos)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/canary/translations/et.json b/homeassistant/components/canary/translations/et.json new file mode 100644 index 00000000000000..eb0f01ea4d9ae8 --- /dev/null +++ b/homeassistant/components/canary/translations/et.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine.", + "unknown": "Tundmatu viga" + }, + "error": { + "cannot_connect": "\u00dchendus nurjus" + }, + "flow_title": "Canary {name}", + "step": { + "user": { + "data": { + "password": "Salas\u00f5na", + "username": "Kasutajanimi" + }, + "title": "Loo \u00fchendus Canary-iga" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "P\u00e4ringu ajal\u00f5pp (sekundites)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/canary/translations/fr.json b/homeassistant/components/canary/translations/fr.json new file mode 100644 index 00000000000000..9bb1761f9fba98 --- /dev/null +++ b/homeassistant/components/canary/translations/fr.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible.", + "unknown": "Erreur inattendue" + }, + "error": { + "cannot_connect": "\u00c9chec de connexion" + }, + "flow_title": "Canary : {name}", + "step": { + "user": { + "data": { + "password": "Mot de passe", + "username": "Nom d'utilisateur" + }, + "title": "Se connecter \u00e0 Canary" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ffmpeg_arguments": "Arguments transmis \u00e0 ffmpeg pour les cam\u00e9ras", + "timeout": "D\u00e9lai d'expiration de la demande (secondes)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/canary/translations/it.json b/homeassistant/components/canary/translations/it.json new file mode 100644 index 00000000000000..b29a758acaafd5 --- /dev/null +++ b/homeassistant/components/canary/translations/it.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione.", + "unknown": "Errore imprevisto" + }, + "error": { + "cannot_connect": "Impossibile connettersi" + }, + "flow_title": "Canary: {name}", + "step": { + "user": { + "data": { + "password": "Password", + "username": "Nome utente" + }, + "title": "Connettiti a Canary" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ffmpeg_arguments": "Argomenti passati a ffmpeg per le fotocamere", + "timeout": "Richiesta Timeout (secondi)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/canary/translations/ko.json b/homeassistant/components/canary/translations/ko.json new file mode 100644 index 00000000000000..0b1d82bb20a418 --- /dev/null +++ b/homeassistant/components/canary/translations/ko.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\uc774\ubbf8 \uc124\uc815\ub418\uc5b4 \uc788\uc74c. \ud558\ub098\uc758 \uc124\uc815\ub9cc \uac00\ub2a5\ud568.", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec \ubc1c\uc0dd" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328" + }, + "flow_title": "Canary: {name}", + "step": { + "user": { + "data": { + "password": "\uc554\ud638", + "username": "\uc0ac\uc6a9\uc790\uba85" + }, + "title": "Canary\uc5d0 \uc5f0\uacb0" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ffmpeg_arguments": "\uce74\uba54\ub77c ffmpeg\uc5d0 \uc804\ub2ec \ub41c \uc778\uc218", + "timeout": "\uc694\uccad \uc81c\ud55c \uc2dc\uac04 (\ucd08)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/canary/translations/lb.json b/homeassistant/components/canary/translations/lb.json new file mode 100644 index 00000000000000..0ad059e1fcc2e7 --- /dev/null +++ b/homeassistant/components/canary/translations/lb.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun m\u00e9iglech.", + "unknown": "Onerwaarte Feeler" + }, + "error": { + "cannot_connect": "Feeler beim verbannen" + }, + "flow_title": "Canary: {name}", + "step": { + "user": { + "data": { + "password": "Passwuert", + "username": "Benotzernumm" + }, + "title": "Mat Canary verbannen" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ffmpeg_arguments": "Argumenter fir ffmpeg fir Kamera", + "timeout": "Ufro Z\u00e4itiwwerscheidung (sekonnen)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/canary/translations/nl.json b/homeassistant/components/canary/translations/nl.json new file mode 100644 index 00000000000000..9681bcd7c371fe --- /dev/null +++ b/homeassistant/components/canary/translations/nl.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Al geconfigureerd. Slechts \u00e9\u00e9n enkele configuratie mogelijk.", + "unknown": "Onverwachte fout" + }, + "error": { + "cannot_connect": "Kon niet verbinden" + }, + "flow_title": "Canary: {name}", + "step": { + "user": { + "data": { + "password": "Wachtwoord", + "username": "Gebruikersnaam" + }, + "title": "Maak verbinding met Canary" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "Time-out verzoek (seconden)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/canary/translations/no.json b/homeassistant/components/canary/translations/no.json new file mode 100644 index 00000000000000..1de0a59b206d7a --- /dev/null +++ b/homeassistant/components/canary/translations/no.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig.", + "unknown": "Uventet feil" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes." + }, + "flow_title": "Canary: {name}", + "step": { + "user": { + "data": { + "password": "Passord", + "username": "Brukernavn" + }, + "title": "Koble til Canary" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ffmpeg_arguments": "Argumenter sendt til ffmpeg for kameraer", + "timeout": "Be om tidsavbrudd (sekunder)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/canary/translations/pl.json b/homeassistant/components/canary/translations/pl.json new file mode 100644 index 00000000000000..f9c346da393d58 --- /dev/null +++ b/homeassistant/components/canary/translations/pl.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja.", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" + }, + "flow_title": "Canary: {name}", + "step": { + "user": { + "data": { + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + }, + "title": "Po\u0142\u0105czenie z Canary" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "Limit czasu \u017c\u0105dania (w sekundach)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/canary/translations/ru.json b/homeassistant/components/canary/translations/ru.json new file mode 100644 index 00000000000000..146863cf768ba2 --- /dev/null +++ b/homeassistant/components/canary/translations/ru.json @@ -0,0 +1,31 @@ +{ + "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.", + "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": "Canary: {name}", + "step": { + "user": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u041b\u043e\u0433\u0438\u043d" + }, + "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a Canary" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ffmpeg_arguments": "\u0410\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u044b, \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u043d\u044b\u0435 \u0432 ffmpeg \u0434\u043b\u044f \u043a\u0430\u043c\u0435\u0440", + "timeout": "\u0422\u0430\u0439\u043c-\u0430\u0443\u0442 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/canary/translations/zh-Hant.json b/homeassistant/components/canary/translations/zh-Hant.json new file mode 100644 index 00000000000000..07463bc8a154fc --- /dev/null +++ b/homeassistant/components/canary/translations/zh-Hant.json @@ -0,0 +1,31 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557" + }, + "flow_title": "Canary\uff1a{name}", + "step": { + "user": { + "data": { + "password": "\u5bc6\u78bc", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + }, + "title": "\u9023\u7dda\u81f3 Canary" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "ffmpeg_arguments": "\u50b3\u905e\u81f3 ffmpeg \u4e4b\u651d\u5f71\u6a5f\u53c3\u6578", + "timeout": "\u8acb\u6c42\u903e\u6642\uff08\u79d2\uff09" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cast/manifest.json b/homeassistant/components/cast/manifest.json index 49d26431f5bbee..03412d3b6df4ea 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==7.2.1"], + "requirements": ["pychromecast==7.5.0"], "after_dependencies": ["cloud", "http", "media_source", "tts", "zeroconf"], "zeroconf": ["_googlecast._tcp.local."], "codeowners": ["@emontnemery"] diff --git a/homeassistant/components/cast/media_player.py b/homeassistant/components/cast/media_player.py index 177babdb476fa2..314236d9f56541 100644 --- a/homeassistant/components/cast/media_player.py +++ b/homeassistant/components/cast/media_player.py @@ -21,6 +21,7 @@ from homeassistant.components.http.auth import async_sign_path from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity from homeassistant.components.media_player.const import ( + ATTR_MEDIA_EXTRA, MEDIA_TYPE_MOVIE, MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, @@ -85,7 +86,7 @@ ENTITY_SCHEMA = vol.All( - cv.deprecated(CONF_HOST, invalidation_version="0.116"), + cv.deprecated(CONF_HOST), vol.Schema( { vol.Exclusive(CONF_HOST, "device_identifier"): cv.string, @@ -96,7 +97,7 @@ ) PLATFORM_SCHEMA = vol.All( - cv.deprecated(CONF_HOST, invalidation_version="0.116"), + cv.deprecated(CONF_HOST), PLATFORM_SCHEMA.extend( { vol.Exclusive(CONF_HOST, "device_identifier"): cv.string, @@ -171,7 +172,7 @@ async def _async_setup_platform( # Import CEC IGNORE attributes pychromecast.IGNORE_CEC += config.get(CONF_IGNORE_CEC, []) hass.data.setdefault(ADDED_CAST_DEVICES_KEY, set()) - hass.data.setdefault(KNOWN_CHROMECAST_INFO_KEY, dict()) + hass.data.setdefault(KNOWN_CHROMECAST_INFO_KEY, {}) info = None if discovery_info is not None: @@ -375,9 +376,9 @@ def new_media_status(self, media_status): if tts_base_url and media_status.content_id.startswith(tts_base_url): url_description = f" from tts.base_url ({tts_base_url})" if external_url and media_status.content_id.startswith(external_url): - url_description = " from external_url ({external_url})" + url_description = f" from external_url ({external_url})" if internal_url and media_status.content_id.startswith(internal_url): - url_description = " from internal_url ({internal_url})" + url_description = f" from internal_url ({internal_url})" _LOGGER.error( "Failed to cast media %s%s. Please make sure the URL is: " @@ -574,7 +575,9 @@ def play_media(self, media_type, media_id, **kwargs): except NotImplementedError: _LOGGER.error("App %s not supported", app_name) else: - self._chromecast.media_controller.play_media(media_id, media_type) + self._chromecast.media_controller.play_media( + media_id, media_type, **kwargs.get(ATTR_MEDIA_EXTRA, {}) + ) # ========== Properties ========== @property diff --git a/homeassistant/components/cast/strings.json b/homeassistant/components/cast/strings.json index aed62243f31e30..ad8f0f41ae7b29 100644 --- a/homeassistant/components/cast/strings.json +++ b/homeassistant/components/cast/strings.json @@ -2,12 +2,12 @@ "config": { "step": { "confirm": { - "description": "Do you want to set up Google Cast?" + "description": "[%key:common::config_flow::description::confirm_setup%]" } }, "abort": { - "single_instance_allowed": "Only a single configuration of Google Cast is necessary.", - "no_devices_found": "No Google Cast devices found on the network." + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" } } } diff --git a/homeassistant/components/cast/translations/ca.json b/homeassistant/components/cast/translations/ca.json index 02a2459fdc0b5e..dc21c371e60fa0 100644 --- a/homeassistant/components/cast/translations/ca.json +++ b/homeassistant/components/cast/translations/ca.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "no_devices_found": "No s'han trobat dispositius de Google Cast a la xarxa.", - "single_instance_allowed": "Nom\u00e9s cal una sola configuraci\u00f3 de Google Cast." + "no_devices_found": "No s'han trobat dispositius a la xarxa", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "step": { "confirm": { - "description": "Vols configurar Google Cast?" + "description": "Vols comen\u00e7ar la configuraci\u00f3?" } } } diff --git a/homeassistant/components/cast/translations/en.json b/homeassistant/components/cast/translations/en.json index e06a94f700b661..f05becffed3131 100644 --- a/homeassistant/components/cast/translations/en.json +++ b/homeassistant/components/cast/translations/en.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "no_devices_found": "No Google Cast devices found on the network.", - "single_instance_allowed": "Only a single configuration of Google Cast is necessary." + "no_devices_found": "No devices found on the network", + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "step": { "confirm": { - "description": "Do you want to set up Google Cast?" + "description": "Do you want to start set up?" } } } diff --git a/homeassistant/components/cast/translations/et.json b/homeassistant/components/cast/translations/et.json new file mode 100644 index 00000000000000..05287b5a52b9a2 --- /dev/null +++ b/homeassistant/components/cast/translations/et.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "no_devices_found": "V\u00f5rgust ei leitud \u00fchtegi Google Casti seadet.", + "single_instance_allowed": "Vajalik on ainult \u00fcks Google Casti konfiguratsioon." + }, + "step": { + "confirm": { + "description": "Kas soovid seadistada Google Casti?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/cast/translations/it.json b/homeassistant/components/cast/translations/it.json index 6e56218c99210e..0278fe07bfeff9 100644 --- a/homeassistant/components/cast/translations/it.json +++ b/homeassistant/components/cast/translations/it.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "no_devices_found": "Nessun dispositivo Google Cast trovato in rete.", - "single_instance_allowed": "\u00c8 necessaria una sola configurazione di Google Cast." + "no_devices_found": "Nessun dispositivo trovato sulla rete", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "step": { "confirm": { - "description": "Vuoi configurare Google Cast?" + "description": "Vuoi iniziare la configurazione?" } } } diff --git a/homeassistant/components/cast/translations/no.json b/homeassistant/components/cast/translations/no.json index b96be399cc8580..b3d6b5d782e97a 100644 --- a/homeassistant/components/cast/translations/no.json +++ b/homeassistant/components/cast/translations/no.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "no_devices_found": "Ingen Google Cast enheter funnet p\u00e5 nettverket.", - "single_instance_allowed": "Kun en konfigurasjon av Google Cast er n\u00f8dvendig." + "no_devices_found": "Ingen enheter funnet p\u00e5 nettverket", + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "step": { "confirm": { - "description": "\u00d8nsker du \u00e5 sette opp Google Cast?" + "description": "Vil du starte oppsettet?" } } } diff --git a/homeassistant/components/cast/translations/ru.json b/homeassistant/components/cast/translations/ru.json index a62f33832e0b73..85a42bf1be546c 100644 --- a/homeassistant/components/cast/translations/ru.json +++ b/homeassistant/components/cast/translations/ru.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 Google Cast \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b.", - "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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." }, "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 Google Cast?" + "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0447\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443?" } } } diff --git a/homeassistant/components/cast/translations/zh-Hant.json b/homeassistant/components/cast/translations/zh-Hant.json index 3bb3e70d68865c..91a0dc60be7fc4 100644 --- a/homeassistant/components/cast/translations/zh-Hant.json +++ b/homeassistant/components/cast/translations/zh-Hant.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "no_devices_found": "\u5728\u7db2\u8def\u4e0a\u627e\u4e0d\u5230 Google Cast \u8a2d\u5099\u3002", - "single_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u6b21 Google Cast \u5373\u53ef\u3002" + "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u8a2d\u5099", + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002" }, "step": { "confirm": { - "description": "\u662f\u5426\u8981\u8a2d\u5b9a Google Cast\uff1f" + "description": "\u662f\u5426\u8981\u958b\u59cb\u8a2d\u5b9a\uff1f" } } } diff --git a/homeassistant/components/cert_expiry/strings.json b/homeassistant/components/cert_expiry/strings.json index fb14e90d586863..bb50f1ee88b447 100644 --- a/homeassistant/components/cert_expiry/strings.json +++ b/homeassistant/components/cert_expiry/strings.json @@ -17,8 +17,8 @@ "connection_refused": "Connection refused when connecting to host" }, "abort": { - "already_configured": "This host and port combination is already configured", + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]", "import_failed": "Import from config failed" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/cert_expiry/translations/ca.json b/homeassistant/components/cert_expiry/translations/ca.json index 50d761f03c23f3..42da690550b986 100644 --- a/homeassistant/components/cert_expiry/translations/ca.json +++ b/homeassistant/components/cert_expiry/translations/ca.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Aquesta combinaci\u00f3 d'amfitri\u00f3 i port ja est\u00e0 configurada", + "already_configured": "El servei ja est\u00e0 configurat", "import_failed": "La importaci\u00f3 des de configuraci\u00f3 ha fallat" }, "error": { diff --git a/homeassistant/components/cert_expiry/translations/en.json b/homeassistant/components/cert_expiry/translations/en.json index 8d551ec2e03922..ff9eb2582f187a 100644 --- a/homeassistant/components/cert_expiry/translations/en.json +++ b/homeassistant/components/cert_expiry/translations/en.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "This host and port combination is already configured", + "already_configured": "Service is already configured", "import_failed": "Import from config failed" }, "error": { diff --git a/homeassistant/components/cert_expiry/translations/it.json b/homeassistant/components/cert_expiry/translations/it.json index 29b26710b626db..232ee01022154e 100644 --- a/homeassistant/components/cert_expiry/translations/it.json +++ b/homeassistant/components/cert_expiry/translations/it.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Questa combinazione di host e porta \u00e8 gi\u00e0 configurata", + "already_configured": "Il servizio \u00e8 gi\u00e0 configurato", "import_failed": "Importazione dalla configurazione non riuscita" }, "error": { diff --git a/homeassistant/components/cert_expiry/translations/no.json b/homeassistant/components/cert_expiry/translations/no.json index a7aa3d1ab133b3..50a4125a0ff572 100644 --- a/homeassistant/components/cert_expiry/translations/no.json +++ b/homeassistant/components/cert_expiry/translations/no.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Denne verts- og portkombinasjonen er allerede konfigurert", + "already_configured": "Tjenesten er allerede konfigurert", "import_failed": "Import fra config mislyktes" }, "error": { diff --git a/homeassistant/components/cert_expiry/translations/ru.json b/homeassistant/components/cert_expiry/translations/ru.json index a924398f90e73e..6aec708243303c 100644 --- a/homeassistant/components/cert_expiry/translations/ru.json +++ b/homeassistant/components/cert_expiry/translations/ru.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u042d\u0442\u0430 \u043a\u043e\u043c\u0431\u0438\u043d\u0430\u0446\u0438\u044f \u0445\u043e\u0441\u0442\u0430 \u0438 \u043f\u043e\u0440\u0442\u0430 \u0443\u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u0430.", + "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.", "import_failed": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0438\u043c\u043f\u043e\u0440\u0442\u0430 \u0438\u0437 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438." }, "error": { diff --git a/homeassistant/components/cert_expiry/translations/zh-Hant.json b/homeassistant/components/cert_expiry/translations/zh-Hant.json index 6cfe05f6d1a252..f21603709ac9f4 100644 --- a/homeassistant/components/cert_expiry/translations/zh-Hant.json +++ b/homeassistant/components/cert_expiry/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u6b64\u4e3b\u6a5f\u7aef\u8207\u901a\u8a0a\u57e0\u7d44\u5408\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "already_configured": "\u670d\u52d9\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "import_failed": "\u532f\u5165\u8a2d\u5b9a\u5931\u6557" }, "error": { diff --git a/homeassistant/components/clickatell/notify.py b/homeassistant/components/clickatell/notify.py index 966dbdee6e2938..09096f44b7403e 100644 --- a/homeassistant/components/clickatell/notify.py +++ b/homeassistant/components/clickatell/notify.py @@ -5,7 +5,7 @@ import voluptuous as vol from homeassistant.components.notify import PLATFORM_SCHEMA, BaseNotificationService -from homeassistant.const import CONF_API_KEY, CONF_RECIPIENT, HTTP_OK +from homeassistant.const import CONF_API_KEY, CONF_RECIPIENT, HTTP_ACCEPTED, HTTP_OK import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) @@ -37,5 +37,5 @@ def send_message(self, message="", **kwargs): data = {"apiKey": self.api_key, "to": self.recipient, "content": message} resp = requests.get(BASE_API_URL, params=data, timeout=5) - if (resp.status_code != HTTP_OK) or (resp.status_code != 202): + if (resp.status_code != HTTP_OK) or (resp.status_code != HTTP_ACCEPTED): _LOGGER.error("Error %s : %s", resp.status_code, resp.text) diff --git a/homeassistant/components/climate/device_action.py b/homeassistant/components/climate/device_action.py index 6f7725ac83577a..3f2b8dc23f2c89 100644 --- a/homeassistant/components/climate/device_action.py +++ b/homeassistant/components/climate/device_action.py @@ -5,6 +5,7 @@ from homeassistant.const import ( ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, CONF_DEVICE_ID, CONF_DOMAIN, CONF_ENTITY_ID, @@ -61,7 +62,7 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: CONF_TYPE: "set_hvac_mode", } ) - if state.attributes["supported_features"] & const.SUPPORT_PRESET_MODE: + if state.attributes[ATTR_SUPPORTED_FEATURES] & const.SUPPORT_PRESET_MODE: actions.append( { CONF_DEVICE_ID: device_id, diff --git a/homeassistant/components/climate/device_condition.py b/homeassistant/components/climate/device_condition.py index 8a5b9ceede8f9d..423efdf8196c4d 100644 --- a/homeassistant/components/climate/device_condition.py +++ b/homeassistant/components/climate/device_condition.py @@ -5,6 +5,7 @@ from homeassistant.const import ( ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, CONF_CONDITION, CONF_DEVICE_ID, CONF_DOMAIN, @@ -63,7 +64,10 @@ async def async_get_conditions( } ) - if state and state.attributes["supported_features"] & const.SUPPORT_PRESET_MODE: + if ( + state + and state.attributes[ATTR_SUPPORTED_FEATURES] & const.SUPPORT_PRESET_MODE + ): conditions.append( { CONF_CONDITION: "device", diff --git a/homeassistant/components/climate/group.py b/homeassistant/components/climate/group.py new file mode 100644 index 00000000000000..87674da414bee3 --- /dev/null +++ b/homeassistant/components/climate/group.py @@ -0,0 +1,20 @@ +"""Describe group states.""" + + +from homeassistant.components.group import GroupIntegrationRegistry +from homeassistant.const import STATE_OFF +from homeassistant.core import callback +from homeassistant.helpers.typing import HomeAssistantType + +from .const import HVAC_MODE_OFF, HVAC_MODES + + +@callback +def async_describe_on_off_states( + hass: HomeAssistantType, registry: GroupIntegrationRegistry +) -> None: + """Describe group on off states.""" + registry.on_off_states( + set(HVAC_MODES) - {HVAC_MODE_OFF}, + STATE_OFF, + ) diff --git a/homeassistant/components/climate/translations/et.json b/homeassistant/components/climate/translations/et.json index 1c4a6a5ff11218..7be57f4cdaa013 100644 --- a/homeassistant/components/climate/translations/et.json +++ b/homeassistant/components/climate/translations/et.json @@ -1,4 +1,19 @@ { + "device_automation": { + "action_type": { + "set_hvac_mode": "Kliimaseadme {entity_name} re\u017eiimi muutmine", + "set_preset_mode": "Olemi {entity_name} eelseadistuse muutmine" + }, + "condition_type": { + "is_hvac_mode": "{entity_name} on seatud kindlale kliimaseadme re\u017eiimile", + "is_preset_mode": "{entity_name} on seatud kindlale eelseadistatud re\u017eiimile" + }, + "trigger_type": { + "current_humidity_changed": "{entity_name} m\u00f5\u00f5detud niiskus muutus", + "current_temperature_changed": "{entity_name} m\u00f5\u00f5detud temperatuur muutus", + "hvac_mode_changed": "{entity_name} kliimasedame re\u017eiim on muudetud" + } + }, "state": { "_": { "auto": "Automaatne", @@ -10,5 +25,5 @@ "off": "V\u00e4ljas" } }, - "title": "Kliima" + "title": "Kliimaseade" } \ No newline at end of file diff --git a/homeassistant/components/climate/translations/uk.json b/homeassistant/components/climate/translations/uk.json index 227e0e1f4ef98a..8d636c386e5479 100644 --- a/homeassistant/components/climate/translations/uk.json +++ b/homeassistant/components/climate/translations/uk.json @@ -1,12 +1,27 @@ { + "device_automation": { + "action_type": { + "set_hvac_mode": "\u0417\u043c\u0456\u043d\u0438\u0442\u0438 \u0440\u0435\u0436\u0438\u043c HVAC \u043d\u0430 {entity_name}", + "set_preset_mode": "\u0417\u043c\u0456\u043d\u0438\u0442\u0438 \u043f\u043e\u043f\u0435\u0440\u0435\u0434\u043d\u044c\u043e \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u043d\u044f \u043d\u0430 {entity_name}" + }, + "condition_type": { + "is_hvac_mode": "{entity_name} \u0432\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e \u0432 \u043f\u0435\u0432\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c HVAC", + "is_preset_mode": "{entity_name} \u043d\u0430\u0441\u0442\u0440\u043e\u0454\u043d\u043e \u043d\u0430 \u043f\u0435\u0432\u043d\u0438\u0439 \u0440\u0435\u0436\u0438\u043c" + }, + "trigger_type": { + "current_humidity_changed": "{entity_name} \u0432\u0438\u043c\u0456\u0440\u044f\u043d\u0430 \u0432\u043e\u043b\u043e\u0433\u0456\u0441\u0442\u044c \u0437\u043c\u0456\u043d\u0435\u043d\u0430", + "current_temperature_changed": "{entity_name} \u0432\u0438\u043c\u0456\u0440\u044f\u043d\u0443 \u0442\u0435\u043c\u043f\u0435\u0440\u0430\u0442\u0443\u0440\u0443 \u0437\u043c\u0456\u043d\u0435\u043d\u043e", + "hvac_mode_changed": "{entity_name} \u0420\u0435\u0436\u0438\u043c HVAC \u0437\u043c\u0456\u043d\u0435\u043d\u043e" + } + }, "state": { "_": { "auto": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u0438\u0439", "cool": "\u041e\u0445\u043e\u043b\u043e\u0434\u0436\u0435\u043d\u043d\u044f", "dry": "\u041e\u0441\u0443\u0448\u0435\u043d\u043d\u044f", "fan_only": "\u041b\u0438\u0448\u0435 \u0432\u0435\u043d\u0442\u0438\u043b\u044f\u0442\u043e\u0440", - "heat": "\u041e\u0431\u0456\u0433\u0440\u0456\u0432\u0430\u043d\u043d\u044f", - "heat_cool": "\u041e\u043f\u0430\u043b\u0435\u043d\u043d\u044f/\u041e\u0445\u043e\u043b\u043e\u0434\u0436\u0435\u043d\u043d\u044f", + "heat": "\u041d\u0430\u0433\u0440\u0456\u0432\u0430\u043d\u043d\u044f", + "heat_cool": "\u041d\u0430\u0433\u0440\u0456\u0432\u0430\u043d\u043d\u044f/\u041e\u0445\u043e\u043b\u043e\u0434\u0436\u0435\u043d\u043d\u044f", "off": "\u0412\u0438\u043c\u043a\u043d\u0435\u043d\u043e" } }, diff --git a/homeassistant/components/cloud/binary_sensor.py b/homeassistant/components/cloud/binary_sensor.py index baa63679d42c1b..0e3b20fa011592 100644 --- a/homeassistant/components/cloud/binary_sensor.py +++ b/homeassistant/components/cloud/binary_sensor.py @@ -1,7 +1,10 @@ """Support for Home Assistant Cloud binary sensors.""" import asyncio -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_CONNECTIVITY, + BinarySensorEntity, +) from homeassistant.helpers.dispatcher import async_dispatcher_connect from .const import DISPATCHER_REMOTE_UPDATE, DOMAIN @@ -44,7 +47,7 @@ def is_on(self) -> bool: @property def device_class(self) -> str: """Return the class of this device, from component DEVICE_CLASSES.""" - return "connectivity" + return DEVICE_CLASS_CONNECTIVITY @property def available(self) -> bool: diff --git a/homeassistant/components/cloud/http_api.py b/homeassistant/components/cloud/http_api.py index 00a2ddb46630ae..3075f6a3f9d93c 100644 --- a/homeassistant/components/cloud/http_api.py +++ b/homeassistant/components/cloud/http_api.py @@ -19,7 +19,13 @@ from homeassistant.components.http import HomeAssistantView from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.components.websocket_api import const as ws_const -from homeassistant.const import HTTP_BAD_REQUEST, HTTP_INTERNAL_SERVER_ERROR, HTTP_OK +from homeassistant.const import ( + HTTP_BAD_GATEWAY, + HTTP_BAD_REQUEST, + HTTP_INTERNAL_SERVER_ERROR, + HTTP_OK, + HTTP_UNAUTHORIZED, +) from homeassistant.core import callback from .const import ( @@ -73,7 +79,10 @@ HTTP_INTERNAL_SERVER_ERROR, "Remote UI not compatible with 127.0.0.1/::1 as trusted proxies.", ), - asyncio.TimeoutError: (502, "Unable to reach the Home Assistant cloud."), + asyncio.TimeoutError: ( + HTTP_BAD_GATEWAY, + "Unable to reach the Home Assistant cloud.", + ), aiohttp.ClientError: ( HTTP_INTERNAL_SERVER_ERROR, "Error making internal request", @@ -122,7 +131,7 @@ async def async_setup(hass): HTTP_BAD_REQUEST, "An account with the given email already exists.", ), - auth.Unauthenticated: (401, "Authentication failed."), + auth.Unauthenticated: (HTTP_UNAUTHORIZED, "Authentication failed."), auth.PasswordChangeRequired: ( HTTP_BAD_REQUEST, "Password change required.", @@ -177,7 +186,7 @@ def _process_cloud_exception(exc, where): if err_info is None: _LOGGER.exception("Unexpected error processing request for %s", where) - err_info = (502, f"Unexpected error: {exc}") + err_info = (HTTP_BAD_GATEWAY, f"Unexpected error: {exc}") return err_info diff --git a/homeassistant/components/comfoconnect/sensor.py b/homeassistant/components/comfoconnect/sensor.py index 6ec92477555586..5f04ba134a8bc3 100644 --- a/homeassistant/components/comfoconnect/sensor.py +++ b/homeassistant/components/comfoconnect/sensor.py @@ -34,6 +34,7 @@ TEMP_CELSIUS, TIME_DAYS, TIME_HOURS, + VOLUME_CUBIC_METERS, ) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -159,14 +160,14 @@ ATTR_AIR_FLOW_SUPPLY: { ATTR_DEVICE_CLASS: None, ATTR_LABEL: "Supply airflow", - ATTR_UNIT: f"m³/{TIME_HOURS}", + ATTR_UNIT: f"{VOLUME_CUBIC_METERS}/{TIME_HOURS}", ATTR_ICON: "mdi:fan", ATTR_ID: SENSOR_FAN_SUPPLY_FLOW, }, ATTR_AIR_FLOW_EXHAUST: { ATTR_DEVICE_CLASS: None, ATTR_LABEL: "Exhaust airflow", - ATTR_UNIT: f"m³/{TIME_HOURS}", + ATTR_UNIT: f"{VOLUME_CUBIC_METERS}/{TIME_HOURS}", ATTR_ICON: "mdi:fan", ATTR_ID: SENSOR_FAN_EXHAUST_FLOW, }, diff --git a/homeassistant/components/concord232/alarm_control_panel.py b/homeassistant/components/concord232/alarm_control_panel.py index 94880dcccf7493..f502e805c853d2 100644 --- a/homeassistant/components/concord232/alarm_control_panel.py +++ b/homeassistant/components/concord232/alarm_control_panel.py @@ -101,7 +101,7 @@ def update(self): except requests.exceptions.ConnectionError as ex: _LOGGER.error( "Unable to connect to %(host)s: %(reason)s", - dict(host=self._url, reason=ex), + {"host": self._url, "reason": ex}, ) return except IndexError: diff --git a/homeassistant/components/concord232/binary_sensor.py b/homeassistant/components/concord232/binary_sensor.py index 3077056c397df0..7fd1d9748b337d 100644 --- a/homeassistant/components/concord232/binary_sensor.py +++ b/homeassistant/components/concord232/binary_sensor.py @@ -7,6 +7,10 @@ import voluptuous as vol from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_MOTION, + DEVICE_CLASS_OPENING, + DEVICE_CLASS_SAFETY, + DEVICE_CLASS_SMOKE, DEVICE_CLASSES, PLATFORM_SCHEMA, BinarySensorEntity, @@ -85,14 +89,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None): def get_opening_type(zone): """Return the result of the type guessing from name.""" if "MOTION" in zone["name"]: - return "motion" + return DEVICE_CLASS_MOTION if "KEY" in zone["name"]: - return "safety" + return DEVICE_CLASS_SAFETY if "SMOKE" in zone["name"]: - return "smoke" + return DEVICE_CLASS_SMOKE if "WATER" in zone["name"]: return "water" - return "opening" + return DEVICE_CLASS_OPENING class Concord232ZoneSensor(BinarySensorEntity): @@ -111,11 +115,6 @@ def device_class(self): """Return the class of this sensor, from DEVICE_CLASSES.""" return self._zone_type - @property - def should_poll(self): - """No polling needed.""" - return True - @property def name(self): """Return the name of the binary sensor.""" diff --git a/homeassistant/components/config/group.py b/homeassistant/components/config/group.py index e26b2b80bc13f1..22a9bf4f02ab8c 100644 --- a/homeassistant/components/config/group.py +++ b/homeassistant/components/config/group.py @@ -1,8 +1,14 @@ """Provide configuration end points for Groups.""" -from homeassistant.components.group import DOMAIN, GROUP_SCHEMA +from homeassistant.components.group import ( + DOMAIN, + GROUP_SCHEMA, + GroupIntegrationRegistry, +) from homeassistant.config import GROUP_CONFIG_PATH from homeassistant.const import SERVICE_RELOAD +from homeassistant.core import callback import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.typing import HomeAssistantType from . import EditKeyBasedConfigView @@ -25,3 +31,11 @@ async def hook(action, config_key): ) ) return True + + +@callback +def async_describe_on_off_states( + hass: HomeAssistantType, registry: GroupIntegrationRegistry +) -> None: + """Describe group on off states.""" + return diff --git a/homeassistant/components/config/zwave.py b/homeassistant/components/config/zwave.py index edc2e9af42c98a..7a99d29c774a6a 100644 --- a/homeassistant/components/config/zwave.py +++ b/homeassistant/components/config/zwave.py @@ -6,7 +6,7 @@ from homeassistant.components.http import HomeAssistantView from homeassistant.components.zwave import DEVICE_CONFIG_SCHEMA_ENTRY, const -from homeassistant.const import HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_OK +from homeassistant.const import HTTP_ACCEPTED, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_OK import homeassistant.core as ha import homeassistant.helpers.config_validation as cv @@ -254,7 +254,9 @@ def _set_protection(): ) state = node.set_protection(value_id, selection) if not state: - return self.json_message("Protection setting did not complete", 202) + return self.json_message( + "Protection setting did not complete", HTTP_ACCEPTED + ) return self.json_message("Protection setting succsessfully set", HTTP_OK) return await hass.async_add_executor_job(_set_protection) diff --git a/homeassistant/components/control4/translations/de.json b/homeassistant/components/control4/translations/de.json new file mode 100644 index 00000000000000..1653a11c3ed996 --- /dev/null +++ b/homeassistant/components/control4/translations/de.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "IP-Addresse", + "password": "Passwort", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/control4/translations/fr.json b/homeassistant/components/control4/translations/fr.json index 1b3803499de549..7d9bd88a81076c 100644 --- a/homeassistant/components/control4/translations/fr.json +++ b/homeassistant/components/control4/translations/fr.json @@ -14,6 +14,16 @@ "host": "Adresse IP", "password": "Mot de passe", "username": "Nom d'utilisateur" + }, + "description": "Veuillez saisir les d\u00e9tails de votre compte Control4 et l'adresse IP de votre contr\u00f4leur local." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Secondes entre les mises \u00e0 jour" } } } diff --git a/homeassistant/components/control4/translations/hu.json b/homeassistant/components/control4/translations/hu.json new file mode 100644 index 00000000000000..3b2d79a34a77e2 --- /dev/null +++ b/homeassistant/components/control4/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/control4/translations/nl.json b/homeassistant/components/control4/translations/nl.json new file mode 100644 index 00000000000000..4d00f0bfc74883 --- /dev/null +++ b/homeassistant/components/control4/translations/nl.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Gebruikersnaam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/control4/translations/pl.json b/homeassistant/components/control4/translations/pl.json index 3064a0044b1835..0076cfb69dd479 100644 --- a/homeassistant/components/control4/translations/pl.json +++ b/homeassistant/components/control4/translations/pl.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" }, "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", - "invalid_auth": "Niepoprawne uwierzytelnienie.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "user": { diff --git a/homeassistant/components/control4/translations/uk.json b/homeassistant/components/control4/translations/uk.json index c771883714a12b..6c0426eba8fb3c 100644 --- a/homeassistant/components/control4/translations/uk.json +++ b/homeassistant/components/control4/translations/uk.json @@ -3,7 +3,6 @@ "step": { "user": { "data": { - "password": "", "username": "\u0406\u043c'\u044f \u043a\u043e\u0440\u0438\u0441\u0442\u0443\u0432\u0430\u0447\u0430" } } diff --git a/homeassistant/components/coolmaster/climate.py b/homeassistant/components/coolmaster/climate.py index 77afd85395abea..7077854a768e2d 100644 --- a/homeassistant/components/coolmaster/climate.py +++ b/homeassistant/components/coolmaster/climate.py @@ -14,6 +14,7 @@ SUPPORT_TARGET_TEMPERATURE, ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.core import callback from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import CONF_SUPPORTED_MODES, DATA_COORDINATOR, DATA_INFO, DOMAIN @@ -66,15 +67,10 @@ def __init__(self, coordinator, unit_id, unit, supported_modes, info): self._hvac_modes = supported_modes self._info = info - def _refresh_from_coordinator(self): + @callback + def _handle_coordinator_update(self): self._unit = self.coordinator.data[self._unit_id] - self.async_write_ha_state() - - async def async_added_to_hass(self): - """When entity is added to hass.""" - self.async_on_remove( - self.coordinator.async_add_listener(self._refresh_from_coordinator) - ) + super()._handle_coordinator_update() @property def device_info(self): diff --git a/homeassistant/components/coolmaster/config_flow.py b/homeassistant/components/coolmaster/config_flow.py index c674146fd15697..7c9b4d5d065b6b 100644 --- a/homeassistant/components/coolmaster/config_flow.py +++ b/homeassistant/components/coolmaster/config_flow.py @@ -54,7 +54,7 @@ async def async_step_user(self, user_input=None): if not result: errors["base"] = "no_units" except (OSError, ConnectionRefusedError, TimeoutError): - errors["base"] = "connection_error" + errors["base"] = "cannot_connect" if errors: return self.async_show_form( diff --git a/homeassistant/components/coolmaster/manifest.json b/homeassistant/components/coolmaster/manifest.json index 58bd51fca4da17..85bd3b1893febc 100644 --- a/homeassistant/components/coolmaster/manifest.json +++ b/homeassistant/components/coolmaster/manifest.json @@ -3,6 +3,6 @@ "name": "CoolMasterNet", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/coolmaster", - "requirements": ["pycoolmasternet-async==0.1.1"], + "requirements": ["pycoolmasternet-async==0.1.2"], "codeowners": ["@OnFreund"] } diff --git a/homeassistant/components/coolmaster/strings.json b/homeassistant/components/coolmaster/strings.json index fd91e5576d2dca..7afc012a1917b5 100644 --- a/homeassistant/components/coolmaster/strings.json +++ b/homeassistant/components/coolmaster/strings.json @@ -15,8 +15,8 @@ } }, "error": { - "connection_error": "Failed to connect to CoolMasterNet instance. Please check your host.", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "no_units": "Could not find any HVAC units in CoolMasterNet host." } } -} \ No newline at end of file +} diff --git a/homeassistant/components/coolmaster/translations/ca.json b/homeassistant/components/coolmaster/translations/ca.json index fb256e52f8824c..40186987031ce5 100644 --- a/homeassistant/components/coolmaster/translations/ca.json +++ b/homeassistant/components/coolmaster/translations/ca.json @@ -1,6 +1,7 @@ { "config": { "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", "connection_error": "No s'ha pogut connectar amb la inst\u00e0ncia de CoolMasterNet. Comprova l'amfitri\u00f3.", "no_units": "No s'ha pogut trobar cap unitat d'HVAC a l'amfitri\u00f3 de CoolMasterNet." }, diff --git a/homeassistant/components/coolmaster/translations/el.json b/homeassistant/components/coolmaster/translations/el.json new file mode 100644 index 00000000000000..04b238a916d221 --- /dev/null +++ b/homeassistant/components/coolmaster/translations/el.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/coolmaster/translations/en.json b/homeassistant/components/coolmaster/translations/en.json index 6c09ceb725b94c..3e69c5c82fd61a 100644 --- a/homeassistant/components/coolmaster/translations/en.json +++ b/homeassistant/components/coolmaster/translations/en.json @@ -1,6 +1,7 @@ { "config": { "error": { + "cannot_connect": "Failed to connect", "connection_error": "Failed to connect to CoolMasterNet instance. Please check your host.", "no_units": "Could not find any HVAC units in CoolMasterNet host." }, diff --git a/homeassistant/components/coolmaster/translations/es.json b/homeassistant/components/coolmaster/translations/es.json index 6835914c5137e9..3d5524d1f135e9 100644 --- a/homeassistant/components/coolmaster/translations/es.json +++ b/homeassistant/components/coolmaster/translations/es.json @@ -1,6 +1,7 @@ { "config": { "error": { + "cannot_connect": "No se pudo conectar", "connection_error": "Error al conectarse a la instancia de CoolMasterNet. Por favor revise su anfitri\u00f3n.", "no_units": "No se ha encontrado ninguna unidad HVAC en el host CoolMasterNet." }, diff --git a/homeassistant/components/coolmaster/translations/et.json b/homeassistant/components/coolmaster/translations/et.json new file mode 100644 index 00000000000000..6342c5d2aa2cf3 --- /dev/null +++ b/homeassistant/components/coolmaster/translations/et.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "connection_error": "CoolMasterNet'iga \u00fchenduse loomine nurjus. Palun kontrolli hosti andmeid." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/coolmaster/translations/fr.json b/homeassistant/components/coolmaster/translations/fr.json index f790e0187186bb..2028043dc3e7c0 100644 --- a/homeassistant/components/coolmaster/translations/fr.json +++ b/homeassistant/components/coolmaster/translations/fr.json @@ -1,6 +1,7 @@ { "config": { "error": { + "cannot_connect": "\u00c9chec de connexion", "connection_error": "\u00c9chec de la connexion \u00e0 l'instance CoolMasterNet. S'il vous pla\u00eet v\u00e9rifier votre h\u00f4te.", "no_units": "Impossible de trouver des unit\u00e9s HVAC dans l'h\u00f4te CoolMasterNet." }, diff --git a/homeassistant/components/coolmaster/translations/it.json b/homeassistant/components/coolmaster/translations/it.json index 33ac306ce1a04d..0e7814581e30df 100644 --- a/homeassistant/components/coolmaster/translations/it.json +++ b/homeassistant/components/coolmaster/translations/it.json @@ -1,6 +1,7 @@ { "config": { "error": { + "cannot_connect": "Impossibile connettersi", "connection_error": "Impossibile connettersi all'istanza CoolMasterNet. Controlla il tuo host.", "no_units": "Impossibile trovare alcuna unit\u00e0 HVAC nell'host CoolMasterNet." }, diff --git a/homeassistant/components/coolmaster/translations/no.json b/homeassistant/components/coolmaster/translations/no.json index 328b113a1824f4..c77773fe5c3b84 100644 --- a/homeassistant/components/coolmaster/translations/no.json +++ b/homeassistant/components/coolmaster/translations/no.json @@ -1,6 +1,7 @@ { "config": { "error": { + "cannot_connect": "Tilkobling mislyktes.", "connection_error": "Kunne ikke koble til CoolMasterNet-forekomsten. Sjekk verten din.", "no_units": "Kunne ikke finne noen HVAC-enheter i CoolMasterNet vert." }, diff --git a/homeassistant/components/coolmaster/translations/pl.json b/homeassistant/components/coolmaster/translations/pl.json index 9b0e4bc5846a26..2a15839a86281f 100644 --- a/homeassistant/components/coolmaster/translations/pl.json +++ b/homeassistant/components/coolmaster/translations/pl.json @@ -1,6 +1,7 @@ { "config": { "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "connection_error": "Nie mo\u017cna po\u0142\u0105czy\u0107 z CoolMasterNet. Sprawd\u017a adres hosta.", "no_units": "Nie mo\u017cna znale\u017a\u0107 urz\u0105dze\u0144 HVAC na ho\u015bcie CoolMasterNet." }, diff --git a/homeassistant/components/coolmaster/translations/ru.json b/homeassistant/components/coolmaster/translations/ru.json index 993a66539b2db3..3c508e80ab3790 100644 --- a/homeassistant/components/coolmaster/translations/ru.json +++ b/homeassistant/components/coolmaster/translations/ru.json @@ -1,6 +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.", "connection_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f, \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0430\u0434\u0440\u0435\u0441 \u0445\u043e\u0441\u0442\u0430.", "no_units": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043d\u0430\u0439\u0442\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043e\u0442\u043e\u043f\u043b\u0435\u043d\u0438\u044f, \u0432\u0435\u043d\u0442\u0438\u043b\u044f\u0446\u0438\u0438 \u0438 \u043a\u043e\u043d\u0434\u0438\u0446\u0438\u043e\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f." }, diff --git a/homeassistant/components/coolmaster/translations/zh-Hant.json b/homeassistant/components/coolmaster/translations/zh-Hant.json index a96bf8bd432084..e3d6fc83db7cc4 100644 --- a/homeassistant/components/coolmaster/translations/zh-Hant.json +++ b/homeassistant/components/coolmaster/translations/zh-Hant.json @@ -1,6 +1,7 @@ { "config": { "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", "connection_error": "\u9023\u7dda\u81f3 CoolMasterNet \u5931\u6557\uff0c\u8acb\u6aa2\u67e5\u4e3b\u6a5f\u7aef\u3002", "no_units": "\u7121\u6cd5\u65bc CoolMasterNet \u4e3b\u6a5f\u627e\u5230\u4efb\u4f55 HVAC \u8a2d\u5099\u3002" }, diff --git a/homeassistant/components/coronavirus/strings.json b/homeassistant/components/coronavirus/strings.json index 949034e6bc7f41..6a5b2626003850 100644 --- a/homeassistant/components/coronavirus/strings.json +++ b/homeassistant/components/coronavirus/strings.json @@ -6,6 +6,8 @@ "data": { "country": "Country" } } }, - "abort": { "already_configured": "This country is already configured." } + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]" + } } } diff --git a/homeassistant/components/coronavirus/translations/ca.json b/homeassistant/components/coronavirus/translations/ca.json index c44da0ab21ad6e..82e46a209d02a0 100644 --- a/homeassistant/components/coronavirus/translations/ca.json +++ b/homeassistant/components/coronavirus/translations/ca.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Aquest pa\u00eds ja est\u00e0 configurat." + "already_configured": "El servei ja est\u00e0 configurat" }, "step": { "user": { diff --git a/homeassistant/components/coronavirus/translations/en.json b/homeassistant/components/coronavirus/translations/en.json index f388c734351344..cbd057bfce109f 100644 --- a/homeassistant/components/coronavirus/translations/en.json +++ b/homeassistant/components/coronavirus/translations/en.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "This country is already configured." + "already_configured": "Service is already configured" }, "step": { "user": { diff --git a/homeassistant/components/coronavirus/translations/et.json b/homeassistant/components/coronavirus/translations/et.json new file mode 100644 index 00000000000000..a69b845e623c76 --- /dev/null +++ b/homeassistant/components/coronavirus/translations/et.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "already_configured": "See riik on juba seadistatud." + }, + "step": { + "user": { + "data": { + "country": "Riik" + }, + "title": "Vali j\u00e4lgiv riik" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/coronavirus/translations/it.json b/homeassistant/components/coronavirus/translations/it.json index 26b40e06ebd704..8cc2065b94a641 100644 --- a/homeassistant/components/coronavirus/translations/it.json +++ b/homeassistant/components/coronavirus/translations/it.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Questa Nazione \u00e8 gi\u00e0 configurata." + "already_configured": "Il servizio \u00e8 gi\u00e0 configurato" }, "step": { "user": { diff --git a/homeassistant/components/coronavirus/translations/no.json b/homeassistant/components/coronavirus/translations/no.json index 359f15b3323450..bf111868e4bf18 100644 --- a/homeassistant/components/coronavirus/translations/no.json +++ b/homeassistant/components/coronavirus/translations/no.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Dette landet er allerede konfigurert." + "already_configured": "Tjenesten er allerede konfigurert" }, "step": { "user": { diff --git a/homeassistant/components/coronavirus/translations/ru.json b/homeassistant/components/coronavirus/translations/ru.json index 7a39c547c82ba5..e7e6798f6a4f68 100644 --- a/homeassistant/components/coronavirus/translations/ru.json +++ b/homeassistant/components/coronavirus/translations/ru.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "already_configured": "\u042d\u0442\u0430 \u0441\u043b\u0443\u0436\u0431\u0430 \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant." }, "step": { "user": { diff --git a/homeassistant/components/cover/group.py b/homeassistant/components/cover/group.py new file mode 100644 index 00000000000000..d031b7cf693c29 --- /dev/null +++ b/homeassistant/components/cover/group.py @@ -0,0 +1,16 @@ +"""Describe group states.""" + + +from homeassistant.components.group import GroupIntegrationRegistry +from homeassistant.const import STATE_CLOSED, STATE_OPEN +from homeassistant.core import callback +from homeassistant.helpers.typing import HomeAssistantType + + +@callback +def async_describe_on_off_states( + hass: HomeAssistantType, registry: GroupIntegrationRegistry +) -> None: + """Describe group on off states.""" + # On means open, Off means closed + registry.on_off_states({STATE_OPEN}, STATE_CLOSED) diff --git a/homeassistant/components/cover/translations/et.json b/homeassistant/components/cover/translations/et.json index 96d81b3a7b61a2..baca6feeca5044 100644 --- a/homeassistant/components/cover/translations/et.json +++ b/homeassistant/components/cover/translations/et.json @@ -1,12 +1,39 @@ { + "device_automation": { + "action_type": { + "close": "Sule aknakate {entity_name}", + "close_tilt": "Sule aknakatte {entity_name} kaldribid", + "open": "Ava aknakate {entity_name}", + "open_tilt": "Ava aknakatte {entity_name} kaldribid", + "set_position": "M\u00e4\u00e4ra aknakatte {entity_name} asend", + "set_tilt_position": "M\u00e4\u00e4ra aknakatte {entity_name} kaldribide asend", + "stop": "Peata aknakatte {entity_name} liikumine" + }, + "condition_type": { + "is_closed": "Aknakate {entity_name} on suletud", + "is_closing": "Aknakate {entity_name} sulgub", + "is_open": "Aknakate {entity_name} on avatud", + "is_opening": "Aknakate {entity_name} avaneb", + "is_position": "Aknakatte {entity_name} praegune asend on", + "is_tilt_position": "Aknakatte {entity_name} praegune kalle on" + }, + "trigger_type": { + "closed": "Aknakate {entity_name} sulgus", + "closing": "Aknakate {entity_name} sulgub", + "opened": "Aknakate {entity_name} avanes", + "opening": "Aknakate {entity_name} avaneb", + "position": "Aknakatte {entity_name} asend muutub", + "tilt_position": "Aknakatte {entity_name} kalle muutub" + } + }, "state": { "_": { "closed": "Suletud", - "closing": "Sulgub", + "closing": "Aknakate sulgub", "open": "Avatud", "opening": "Avaneb", - "stopped": "Peatatud" + "stopped": "Aknakate peatatus" } }, - "title": "Kate" + "title": "Kardin" } \ No newline at end of file diff --git a/homeassistant/components/cover/translations/uk.json b/homeassistant/components/cover/translations/uk.json index 0485a9bb37198b..66cd0c77c73c97 100644 --- a/homeassistant/components/cover/translations/uk.json +++ b/homeassistant/components/cover/translations/uk.json @@ -2,6 +2,9 @@ "device_automation": { "action_type": { "stop": "\u0417\u0443\u043f\u0438\u043d\u0438\u0442\u0438 {entity_name}" + }, + "trigger_type": { + "opened": "{entity_name} \u0432\u0456\u0434\u043a\u0440\u0438\u0442\u043e" } }, "state": { diff --git a/homeassistant/components/daikin/translations/pl.json b/homeassistant/components/daikin/translations/pl.json index fc39f78c2d0f81..7b40fa55a1d673 100644 --- a/homeassistant/components/daikin/translations/pl.json +++ b/homeassistant/components/daikin/translations/pl.json @@ -1,12 +1,13 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, "error": { - "device_fail": "Nieoczekiwany b\u0142\u0105d.", - "device_timeout": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", - "forbidden": "Niepoprawne uwierzytelnienie." + "device_fail": "Nieoczekiwany b\u0142\u0105d", + "device_timeout": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "forbidden": "Niepoprawne uwierzytelnienie" }, "step": { "user": { diff --git a/homeassistant/components/danfoss_air/binary_sensor.py b/homeassistant/components/danfoss_air/binary_sensor.py index 7f6876a709be48..9d3123185c4f9d 100644 --- a/homeassistant/components/danfoss_air/binary_sensor.py +++ b/homeassistant/components/danfoss_air/binary_sensor.py @@ -1,7 +1,10 @@ """Support for the for Danfoss Air HRV binary sensors.""" from pydanfossair.commands import ReadCommand -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_OPENING, + BinarySensorEntity, +) from . import DOMAIN as DANFOSS_AIR_DOMAIN @@ -11,7 +14,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): data = hass.data[DANFOSS_AIR_DOMAIN] sensors = [ - ["Danfoss Air Bypass Active", ReadCommand.bypass, "opening"], + ["Danfoss Air Bypass Active", ReadCommand.bypass, DEVICE_CLASS_OPENING], ["Danfoss Air Away Mode Active", ReadCommand.away_mode, None], ] diff --git a/homeassistant/components/darksky/sensor.py b/homeassistant/components/darksky/sensor.py index e167f16b4e44b1..49b5c6c69f6ec8 100644 --- a/homeassistant/components/darksky/sensor.py +++ b/homeassistant/components/darksky/sensor.py @@ -18,7 +18,9 @@ DEGREE, LENGTH_CENTIMETERS, LENGTH_KILOMETERS, + LENGTH_MILLIMETERS, PERCENTAGE, + PRESSURE_MBAR, SPEED_KILOMETERS_PER_HOUR, SPEED_METERS_PER_SECOND, SPEED_MILES_PER_HOUR, @@ -109,11 +111,11 @@ ], "precip_intensity": [ "Precip Intensity", - f"mm/{TIME_HOURS}", + f"{LENGTH_MILLIMETERS}/{TIME_HOURS}", "in", - f"mm/{TIME_HOURS}", - f"mm/{TIME_HOURS}", - f"mm/{TIME_HOURS}", + f"{LENGTH_MILLIMETERS}/{TIME_HOURS}", + f"{LENGTH_MILLIMETERS}/{TIME_HOURS}", + f"{LENGTH_MILLIMETERS}/{TIME_HOURS}", "mdi:weather-rainy", ["currently", "minutely", "hourly", "daily"], ], @@ -219,11 +221,11 @@ ], "pressure": [ "Pressure", - "mbar", - "mbar", - "mbar", - "mbar", - "mbar", + PRESSURE_MBAR, + PRESSURE_MBAR, + PRESSURE_MBAR, + PRESSURE_MBAR, + PRESSURE_MBAR, "mdi:gauge", ["currently", "hourly", "daily"], ], @@ -329,11 +331,11 @@ ], "precip_intensity_max": [ "Daily Max Precip Intensity", - f"mm/{TIME_HOURS}", + f"{LENGTH_MILLIMETERS}/{TIME_HOURS}", "in", - f"mm/{TIME_HOURS}", - f"mm/{TIME_HOURS}", - f"mm/{TIME_HOURS}", + f"{LENGTH_MILLIMETERS}/{TIME_HOURS}", + f"{LENGTH_MILLIMETERS}/{TIME_HOURS}", + f"{LENGTH_MILLIMETERS}/{TIME_HOURS}", "mdi:thermometer", ["daily"], ], diff --git a/homeassistant/components/ddwrt/device_tracker.py b/homeassistant/components/ddwrt/device_tracker.py index 9b6fd1bdb64300..c324d6a5b6469d 100644 --- a/homeassistant/components/ddwrt/device_tracker.py +++ b/homeassistant/components/ddwrt/device_tracker.py @@ -17,6 +17,7 @@ CONF_USERNAME, CONF_VERIFY_SSL, HTTP_OK, + HTTP_UNAUTHORIZED, ) import homeassistant.helpers.config_validation as cv @@ -155,7 +156,7 @@ def get_ddwrt_data(self, url): return if response.status_code == HTTP_OK: return _parse_ddwrt_response(response.text) - if response.status_code == 401: + if response.status_code == HTTP_UNAUTHORIZED: # Authentication error _LOGGER.exception( "Failed to authenticate, check your username and password" diff --git a/homeassistant/components/deconz/binary_sensor.py b/homeassistant/components/deconz/binary_sensor.py index c8917934dd7b20..a9f49586134583 100644 --- a/homeassistant/components/deconz/binary_sensor.py +++ b/homeassistant/components/deconz/binary_sensor.py @@ -8,6 +8,7 @@ DEVICE_CLASS_OPENING, DEVICE_CLASS_SMOKE, DEVICE_CLASS_VIBRATION, + DOMAIN, BinarySensorEntity, ) from homeassistant.const import ATTR_TEMPERATURE @@ -32,24 +33,21 @@ } -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Old way of setting up deCONZ platforms.""" - - async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the deCONZ binary sensor.""" gateway = get_gateway_from_config_entry(hass, config_entry) + gateway.entities[DOMAIN] = set() @callback - def async_add_sensor(sensors, new=True): + def async_add_sensor(sensors): """Add binary sensor from deCONZ.""" entities = [] for sensor in sensors: if ( - new - and sensor.BINARY + sensor.BINARY + and sensor.uniqueid not in gateway.entities[DOMAIN] and ( gateway.option_allow_clip_sensor or not sensor.type.startswith("CLIP") @@ -57,7 +55,8 @@ def async_add_sensor(sensors, new=True): ): entities.append(DeconzBinarySensor(sensor, gateway)) - async_add_entities(entities, True) + if entities: + async_add_entities(entities, True) gateway.listeners.append( async_dispatcher_connect( @@ -73,15 +72,14 @@ def async_add_sensor(sensors, new=True): class DeconzBinarySensor(DeconzDevice, BinarySensorEntity): """Representation of a deCONZ binary sensor.""" + TYPE = DOMAIN + @callback - def async_update_callback(self, force_update=False, ignore_update=False): + def async_update_callback(self, force_update=False): """Update the sensor's state.""" - if ignore_update: - return - keys = {"on", "reachable", "state"} if force_update or self._device.changed_keys.intersection(keys): - self.async_write_ha_state() + super().async_update_callback(force_update=force_update) @property def is_on(self): diff --git a/homeassistant/components/deconz/climate.py b/homeassistant/components/deconz/climate.py index 424693505ca47f..e4de5badb61e39 100644 --- a/homeassistant/components/deconz/climate.py +++ b/homeassistant/components/deconz/climate.py @@ -1,7 +1,7 @@ """Support for deCONZ climate devices.""" from pydeconz.sensor import Thermostat -from homeassistant.components.climate import ClimateEntity +from homeassistant.components.climate import DOMAIN, ClimateEntity from homeassistant.components.climate.const import ( HVAC_MODE_AUTO, HVAC_MODE_HEAT, @@ -19,27 +19,24 @@ SUPPORT_HVAC = [HVAC_MODE_AUTO, HVAC_MODE_HEAT, HVAC_MODE_OFF] -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Old way of setting up deCONZ platforms.""" - - async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the deCONZ climate devices. Thermostats are based on the same device class as sensors in deCONZ. """ gateway = get_gateway_from_config_entry(hass, config_entry) + gateway.entities[DOMAIN] = set() @callback - def async_add_climate(sensors, new=True): + def async_add_climate(sensors): """Add climate devices from deCONZ.""" entities = [] for sensor in sensors: if ( - new - and sensor.type in Thermostat.ZHATYPE + sensor.type in Thermostat.ZHATYPE + and sensor.uniqueid not in gateway.entities[DOMAIN] and ( gateway.option_allow_clip_sensor or not sensor.type.startswith("CLIP") @@ -47,7 +44,8 @@ def async_add_climate(sensors, new=True): ): entities.append(DeconzThermostat(sensor, gateway)) - async_add_entities(entities, True) + if entities: + async_add_entities(entities, True) gateway.listeners.append( async_dispatcher_connect( @@ -61,6 +59,8 @@ def async_add_climate(sensors, new=True): class DeconzThermostat(DeconzDevice, ClimateEntity): """Representation of a deCONZ thermostat.""" + TYPE = DOMAIN + @property def supported_features(self): """Return the list of supported features.""" diff --git a/homeassistant/components/deconz/config_flow.py b/homeassistant/components/deconz/config_flow.py index f3ae5682131890..c43c1c955048e1 100644 --- a/homeassistant/components/deconz/config_flow.py +++ b/homeassistant/components/deconz/config_flow.py @@ -22,13 +22,13 @@ from .const import ( CONF_ALLOW_CLIP_SENSOR, CONF_ALLOW_DECONZ_GROUPS, + CONF_ALLOW_NEW_DEVICES, CONF_BRIDGE_ID, - DEFAULT_ALLOW_CLIP_SENSOR, - DEFAULT_ALLOW_DECONZ_GROUPS, DEFAULT_PORT, DOMAIN, LOGGER, ) +from .gateway import get_gateway_from_config_entry DECONZ_MANUFACTURERURL = "http://www.dresden-elektronik.de" CONF_SERIAL = "serial" @@ -172,6 +172,9 @@ async def _create_entry(self): except asyncio.TimeoutError: return self.async_abort(reason="no_bridges") + if self.bridge_id == "0000000000000000": + return self.async_abort(reason="no_hardware_available") + return self.async_create_entry(title=self.bridge_id, data=self.deconz_config) async def async_step_ssdp(self, discovery_info): @@ -251,18 +254,17 @@ def __init__(self, config_entry): """Initialize deCONZ options flow.""" self.config_entry = config_entry self.options = dict(config_entry.options) + self.gateway = None async def async_step_init(self, user_input=None): """Manage the deCONZ options.""" + self.gateway = get_gateway_from_config_entry(self.hass, self.config_entry) return await self.async_step_deconz_devices() async def async_step_deconz_devices(self, user_input=None): """Manage the deconz devices options.""" if user_input is not None: - self.options[CONF_ALLOW_CLIP_SENSOR] = user_input[CONF_ALLOW_CLIP_SENSOR] - self.options[CONF_ALLOW_DECONZ_GROUPS] = user_input[ - CONF_ALLOW_DECONZ_GROUPS - ] + self.options.update(user_input) return self.async_create_entry(title="", data=self.options) return self.async_show_form( @@ -271,15 +273,15 @@ async def async_step_deconz_devices(self, user_input=None): { vol.Optional( CONF_ALLOW_CLIP_SENSOR, - default=self.config_entry.options.get( - CONF_ALLOW_CLIP_SENSOR, DEFAULT_ALLOW_CLIP_SENSOR - ), + default=self.gateway.option_allow_clip_sensor, ): bool, vol.Optional( CONF_ALLOW_DECONZ_GROUPS, - default=self.config_entry.options.get( - CONF_ALLOW_DECONZ_GROUPS, DEFAULT_ALLOW_DECONZ_GROUPS - ), + default=self.gateway.option_allow_deconz_groups, + ): bool, + vol.Optional( + CONF_ALLOW_NEW_DEVICES, + default=self.gateway.option_allow_new_devices, ): bool, } ), diff --git a/homeassistant/components/deconz/const.py b/homeassistant/components/deconz/const.py index c2190321fdf837..d965b6485f8a81 100644 --- a/homeassistant/components/deconz/const.py +++ b/homeassistant/components/deconz/const.py @@ -1,6 +1,15 @@ """Constants for the deCONZ component.""" import logging +from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN +from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN +from homeassistant.components.cover import DOMAIN as COVER_DOMAIN +from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN +from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN +from homeassistant.components.scene import DOMAIN as SCENE_DOMAIN +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN + LOGGER = logging.getLogger(__package__) DOMAIN = "deconz" @@ -11,19 +20,22 @@ DEFAULT_PORT = 80 DEFAULT_ALLOW_CLIP_SENSOR = False DEFAULT_ALLOW_DECONZ_GROUPS = True +DEFAULT_ALLOW_NEW_DEVICES = True CONF_ALLOW_CLIP_SENSOR = "allow_clip_sensor" CONF_ALLOW_DECONZ_GROUPS = "allow_deconz_groups" +CONF_ALLOW_NEW_DEVICES = "allow_new_devices" CONF_MASTER_GATEWAY = "master" SUPPORTED_PLATFORMS = [ - "binary_sensor", - "climate", - "cover", - "light", - "scene", - "sensor", - "switch", + BINARY_SENSOR_DOMAIN, + CLIMATE_DOMAIN, + COVER_DOMAIN, + LIGHT_DOMAIN, + LOCK_DOMAIN, + SCENE_DOMAIN, + SENSOR_DOMAIN, + SWITCH_DOMAIN, ] NEW_GROUP = "groups" @@ -36,12 +48,20 @@ ATTR_ON = "on" ATTR_VALVE = "valve" +# Covers DAMPERS = ["Level controllable output"] WINDOW_COVERS = ["Window covering device", "Window covering controller"] COVER_TYPES = DAMPERS + WINDOW_COVERS +# Locks +LOCKS = ["Door Lock"] +LOCK_TYPES = LOCKS + +# Switches POWER_PLUGS = ["On/Off light", "On/Off plug-in unit", "Smart plug"] SIRENS = ["Warning device"] SWITCH_TYPES = POWER_PLUGS + SIRENS +CONF_ANGLE = "angle" CONF_GESTURE = "gesture" +CONF_XY = "xy" diff --git a/homeassistant/components/deconz/cover.py b/homeassistant/components/deconz/cover.py index e01cfdbe5f8964..82f471305e9f5b 100644 --- a/homeassistant/components/deconz/cover.py +++ b/homeassistant/components/deconz/cover.py @@ -1,6 +1,8 @@ """Support for deCONZ covers.""" from homeassistant.components.cover import ( ATTR_POSITION, + DEVICE_CLASS_WINDOW, + DOMAIN, SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_SET_POSITION, @@ -15,16 +17,13 @@ from .gateway import get_gateway_from_config_entry -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Old way of setting up deCONZ platforms.""" - - async def async_setup_entry(hass, config_entry, async_add_entities): """Set up covers for deCONZ component. - Covers are based on same device class as lights in deCONZ. + Covers are based on the same device class as lights in deCONZ. """ gateway = get_gateway_from_config_entry(hass, config_entry) + gateway.entities[DOMAIN] = set() @callback def async_add_cover(lights): @@ -32,10 +31,14 @@ def async_add_cover(lights): entities = [] for light in lights: - if light.type in COVER_TYPES: + if ( + light.type in COVER_TYPES + and light.uniqueid not in gateway.entities[DOMAIN] + ): entities.append(DeconzCover(light, gateway)) - async_add_entities(entities, True) + if entities: + async_add_entities(entities, True) gateway.listeners.append( async_dispatcher_connect( @@ -49,6 +52,8 @@ def async_add_cover(lights): class DeconzCover(DeconzDevice, CoverEntity): """Representation of a deCONZ cover.""" + TYPE = DOMAIN + def __init__(self, device, gateway): """Set up cover device.""" super().__init__(device, gateway) @@ -74,7 +79,7 @@ def device_class(self): if self._device.type in DAMPERS: return "damper" if self._device.type in WINDOW_COVERS: - return "window" + return DEVICE_CLASS_WINDOW @property def supported_features(self): diff --git a/homeassistant/components/deconz/deconz_device.py b/homeassistant/components/deconz/deconz_device.py index b77014cc34bb4c..4bcd63c8fa227f 100644 --- a/homeassistant/components/deconz/deconz_device.py +++ b/homeassistant/components/deconz/deconz_device.py @@ -14,7 +14,6 @@ def __init__(self, device, gateway): """Set up device and add update callback to get data from websocket.""" self._device = device self.gateway = gateway - self.listeners = [] @property def unique_id(self): @@ -51,11 +50,12 @@ def device_info(self): class DeconzDevice(DeconzBase, Entity): """Representation of a deCONZ device.""" + TYPE = "" + def __init__(self, device, gateway): """Set up device and add update callback to get data from websocket.""" super().__init__(device, gateway) - - self.unsub_dispatcher = None + self.gateway.entities[self.TYPE].add(self.unique_id) @property def entity_registry_enabled_default(self): @@ -72,7 +72,7 @@ async def async_added_to_hass(self): """Subscribe to device events.""" self._device.register_callback(self.async_update_callback) self.gateway.deconz_ids[self.entity_id] = self._device.deconz_id - self.listeners.append( + self.async_on_remove( async_dispatcher_connect( self.hass, self.gateway.signal_reachable, self.async_update_callback ) @@ -83,13 +83,12 @@ async def async_will_remove_from_hass(self) -> None: self._device.remove_callback(self.async_update_callback) if self.entity_id in self.gateway.deconz_ids: del self.gateway.deconz_ids[self.entity_id] - for unsub_dispatcher in self.listeners: - unsub_dispatcher() + self.gateway.entities[self.TYPE].remove(self.unique_id) @callback - def async_update_callback(self, force_update=False, ignore_update=False): + def async_update_callback(self, force_update=False): """Update the device's state.""" - if ignore_update: + if not force_update and self.gateway.ignore_state_updates: return self.async_write_ha_state() diff --git a/homeassistant/components/deconz/deconz_event.py b/homeassistant/components/deconz/deconz_event.py index 9ad4a7f3162f6d..968ab3cee391b5 100644 --- a/homeassistant/components/deconz/deconz_event.py +++ b/homeassistant/components/deconz/deconz_event.py @@ -1,14 +1,57 @@ """Representation of a deCONZ remote.""" +from pydeconz.sensor import Switch + from homeassistant.const import CONF_EVENT, CONF_ID, CONF_UNIQUE_ID from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.util import slugify -from .const import CONF_GESTURE, LOGGER +from .const import CONF_ANGLE, CONF_GESTURE, CONF_XY, LOGGER, NEW_SENSOR from .deconz_device import DeconzBase CONF_DECONZ_EVENT = "deconz_event" +async def async_setup_events(gateway) -> None: + """Set up the deCONZ events.""" + + @callback + def async_add_sensor(sensors): + """Create DeconzEvent.""" + for sensor in sensors: + + if not gateway.option_allow_clip_sensor and sensor.type.startswith("CLIP"): + continue + + if sensor.type not in Switch.ZHATYPE or sensor.uniqueid in { + event.unique_id for event in gateway.events + }: + continue + + new_event = DeconzEvent(sensor, gateway) + gateway.hass.async_create_task(new_event.async_update_device_registry()) + gateway.events.append(new_event) + + gateway.listeners.append( + async_dispatcher_connect( + gateway.hass, gateway.async_signal_new_device(NEW_SENSOR), async_add_sensor + ) + ) + + async_add_sensor( + [gateway.api.sensors[key] for key in sorted(gateway.api.sensors, key=int)] + ) + + +@callback +def async_unload_events(gateway) -> None: + """Unload all deCONZ events.""" + for event in gateway.events: + event.async_will_remove_from_hass() + + gateway.events.clear() + + class DeconzEvent(DeconzBase): """When you want signals instead of entities. @@ -35,12 +78,14 @@ def device(self): def async_will_remove_from_hass(self) -> None: """Disconnect event object when removed.""" self._device.remove_callback(self.async_update_callback) - self._device = None @callback - def async_update_callback(self, force_update=False, ignore_update=False): + def async_update_callback(self, force_update=False): """Fire the event if reason is that state is updated.""" - if ignore_update or "state" not in self._device.changed_keys: + if ( + self.gateway.ignore_state_updates + or "state" not in self._device.changed_keys + ): return data = { @@ -52,6 +97,12 @@ def async_update_callback(self, force_update=False, ignore_update=False): if self._device.gesture is not None: data[CONF_GESTURE] = self._device.gesture + if self._device.angle is not None: + data[CONF_ANGLE] = self._device.angle + + if self._device.xy is not None: + data[CONF_XY] = self._device.xy + self.gateway.hass.bus.async_fire(CONF_DECONZ_EVENT, data) async def async_update_device_registry(self): diff --git a/homeassistant/components/deconz/device_trigger.py b/homeassistant/components/deconz/device_trigger.py index bddde34a7ded0c..e400afd9b9f3d9 100644 --- a/homeassistant/components/deconz/device_trigger.py +++ b/homeassistant/components/deconz/device_trigger.py @@ -16,6 +16,7 @@ ) from . import DOMAIN +from .const import LOGGER from .deconz_event import CONF_DECONZ_EVENT, CONF_GESTURE CONF_SUBTYPE = "subtype" @@ -267,7 +268,8 @@ } AQARA_SINGLE_WALL_SWITCH_WXKG03LM_MODEL = "lumi.remote.b186acn01" -AQARA_SINGLE_WALL_SWITCH_WXKG03LM = { +AQARA_SINGLE_WALL_SWITCH_WXKG06LM_MODEL = "lumi.remote.b186acn02" +AQARA_SINGLE_WALL_SWITCH = { (CONF_SHORT_PRESS, CONF_TURN_ON): {CONF_EVENT: 1002}, (CONF_LONG_PRESS, CONF_TURN_ON): {CONF_EVENT: 1001}, (CONF_DOUBLE_PRESS, CONF_TURN_ON): {CONF_EVENT: 1004}, @@ -370,7 +372,8 @@ AQARA_DOUBLE_WALL_SWITCH_MODEL: AQARA_DOUBLE_WALL_SWITCH, AQARA_DOUBLE_WALL_SWITCH_MODEL_2020: AQARA_DOUBLE_WALL_SWITCH, AQARA_DOUBLE_WALL_SWITCH_WXKG02LM_MODEL: AQARA_DOUBLE_WALL_SWITCH_WXKG02LM, - AQARA_SINGLE_WALL_SWITCH_WXKG03LM_MODEL: AQARA_SINGLE_WALL_SWITCH_WXKG03LM, + AQARA_SINGLE_WALL_SWITCH_WXKG03LM_MODEL: AQARA_SINGLE_WALL_SWITCH, + AQARA_SINGLE_WALL_SWITCH_WXKG06LM_MODEL: AQARA_SINGLE_WALL_SWITCH, AQARA_MINI_SWITCH_MODEL: AQARA_MINI_SWITCH, AQARA_ROUND_SWITCH_MODEL: AQARA_ROUND_SWITCH, AQARA_SQUARE_SWITCH_MODEL: AQARA_SQUARE_SWITCH, @@ -427,6 +430,7 @@ async def async_attach_trigger(hass, config, action, automation_info): deconz_event = _get_deconz_event_from_device_id(hass, device.id) if deconz_event is None: + LOGGER.error("No deconz_event tied to device %s found", device.name) raise InvalidDeviceAutomationConfig event_id = deconz_event.serial diff --git a/homeassistant/components/deconz/gateway.py b/homeassistant/components/deconz/gateway.py index 828f65c9811154..5ec07c40754772 100644 --- a/homeassistant/components/deconz/gateway.py +++ b/homeassistant/components/deconz/gateway.py @@ -14,9 +14,11 @@ from .const import ( CONF_ALLOW_CLIP_SENSOR, CONF_ALLOW_DECONZ_GROUPS, + CONF_ALLOW_NEW_DEVICES, CONF_MASTER_GATEWAY, DEFAULT_ALLOW_CLIP_SENSOR, DEFAULT_ALLOW_DECONZ_GROUPS, + DEFAULT_ALLOW_NEW_DEVICES, DOMAIN, LOGGER, NEW_GROUP, @@ -25,6 +27,7 @@ NEW_SENSOR, SUPPORTED_PLATFORMS, ) +from .deconz_event import async_setup_events, async_unload_events from .errors import AuthenticationRequired, CannotConnect @@ -42,9 +45,14 @@ def __init__(self, hass, config_entry) -> None: self.hass = hass self.config_entry = config_entry - self.available = True self.api = None + + self.available = True + self.ignore_state_updates = False + self.deconz_ids = {} + self.device_id = None + self.entities = {} self.events = [] self.listeners = [] @@ -56,11 +64,18 @@ def bridgeid(self) -> str: """Return the unique identifier of the gateway.""" return self.config_entry.unique_id + @property + def host(self) -> str: + """Return the host of the gateway.""" + return self.config_entry.data[CONF_HOST] + @property def master(self) -> bool: """Gateway which is used with deCONZ services without defining id.""" return self.config_entry.options[CONF_MASTER_GATEWAY] + # Options + @property def option_allow_clip_sensor(self) -> bool: """Allow loading clip sensor from gateway.""" @@ -75,10 +90,57 @@ def option_allow_deconz_groups(self) -> bool: 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 + ) + + # Signals + + @property + def signal_reachable(self) -> str: + """Gateway specific event to signal a change in connection status.""" + return f"deconz-reachable-{self.bridgeid}" + + @callback + def async_signal_new_device(self, device_type) -> str: + """Gateway specific event to signal new device.""" + new_device = { + NEW_GROUP: f"deconz_new_group_{self.bridgeid}", + NEW_LIGHT: f"deconz_new_light_{self.bridgeid}", + NEW_SCENE: f"deconz_new_scene_{self.bridgeid}", + NEW_SENSOR: f"deconz_new_sensor_{self.bridgeid}", + } + return new_device[device_type] + + # Callbacks + + @callback + def async_connection_status_callback(self, available) -> None: + """Handle signals of gateway connection status.""" + self.available = available + self.ignore_state_updates = False + async_dispatcher_send(self.hass, self.signal_reachable, True) + + @callback + def async_add_device_callback(self, device_type, device) -> None: + """Handle event of new device creation in deCONZ.""" + if not self.option_allow_new_devices: + return + + if not isinstance(device, list): + device = [device] + + async_dispatcher_send( + self.hass, self.async_signal_new_device(device_type), device + ) + async def async_update_device_registry(self) -> None: """Update device registry.""" device_registry = await self.hass.helpers.device_registry.async_get_registry() - device_registry.async_get_or_create( + entry = device_registry.async_get_or_create( config_entry_id=self.config_entry.entry_id, connections={(CONNECTION_NETWORK_MAC, self.api.config.mac)}, identifiers={(DOMAIN, self.api.config.bridgeid)}, @@ -87,6 +149,7 @@ async def async_update_device_registry(self) -> None: name=self.api.config.name, sw_version=self.api.config.swversion, ) + self.device_id = entry.id async def async_setup(self) -> bool: """Set up a deCONZ gateway.""" @@ -112,6 +175,8 @@ async def async_setup(self) -> bool: ) ) + self.hass.async_create_task(async_setup_events(self)) + self.api.start() self.config_entry.add_update_listener(self.async_config_entry_updated) @@ -126,11 +191,13 @@ async def async_config_entry_updated(hass, entry) -> None: Causes for this is either discovery updating host address or config entry options changing. """ gateway = get_gateway_from_config_entry(hass, entry) + if not gateway: return - if gateway.api.host != entry.data[CONF_HOST]: + + if gateway.api.host != gateway.host: gateway.api.close() - gateway.api.host = entry.data[CONF_HOST] + gateway.api.host = gateway.host gateway.api.start() return @@ -174,37 +241,6 @@ async def options_updated(self): # from Home Assistant entity_registry.async_remove(entity_id) - @property - def signal_reachable(self) -> str: - """Gateway specific event to signal a change in connection status.""" - return f"deconz-reachable-{self.bridgeid}" - - @callback - def async_connection_status_callback(self, available) -> None: - """Handle signals of gateway connection status.""" - self.available = available - async_dispatcher_send(self.hass, self.signal_reachable, True) - - @callback - def async_signal_new_device(self, device_type) -> str: - """Gateway specific event to signal new device.""" - new_device = { - NEW_GROUP: f"deconz_new_group_{self.bridgeid}", - NEW_LIGHT: f"deconz_new_light_{self.bridgeid}", - NEW_SCENE: f"deconz_new_scene_{self.bridgeid}", - NEW_SENSOR: f"deconz_new_sensor_{self.bridgeid}", - } - return new_device[device_type] - - @callback - def async_add_device_callback(self, device_type, device) -> None: - """Handle event of new device creation in deCONZ.""" - if not isinstance(device, list): - device = [device] - async_dispatcher_send( - self.hass, self.async_signal_new_device(device_type), device - ) - @callback def shutdown(self, event) -> None: """Wrap the call to deconz.close. @@ -227,9 +263,7 @@ async def async_reset(self): unsub_dispatcher() self.listeners = [] - for event in self.events: - event.async_will_remove_from_hass() - self.events.clear() + async_unload_events(self) self.deconz_ids = {} return True diff --git a/homeassistant/components/deconz/light.py b/homeassistant/components/deconz/light.py index fc0c01b30df142..50fb3a8300b83c 100644 --- a/homeassistant/components/deconz/light.py +++ b/homeassistant/components/deconz/light.py @@ -6,6 +6,7 @@ ATTR_FLASH, ATTR_HS_COLOR, ATTR_TRANSITION, + DOMAIN, EFFECT_COLORLOOP, FLASH_LONG, FLASH_SHORT, @@ -25,6 +26,7 @@ CONF_GROUP_ID_BASE, COVER_TYPES, DOMAIN as DECONZ_DOMAIN, + LOCK_TYPES, NEW_GROUP, NEW_LIGHT, SWITCH_TYPES, @@ -33,13 +35,10 @@ from .gateway import get_gateway_from_config_entry -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Old way of setting up deCONZ platforms.""" - - async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the deCONZ lights and groups from a config entry.""" gateway = get_gateway_from_config_entry(hass, config_entry) + gateway.entities[DOMAIN] = set() @callback def async_add_light(lights): @@ -47,10 +46,14 @@ def async_add_light(lights): entities = [] for light in lights: - if light.type not in COVER_TYPES + SWITCH_TYPES: + if ( + light.type not in COVER_TYPES + LOCK_TYPES + SWITCH_TYPES + and light.uniqueid not in gateway.entities[DOMAIN] + ): entities.append(DeconzLight(light, gateway)) - async_add_entities(entities, True) + if entities: + async_add_entities(entities, True) gateway.listeners.append( async_dispatcher_connect( @@ -67,10 +70,16 @@ def async_add_group(groups): entities = [] for group in groups: - if group.lights: - entities.append(DeconzGroup(group, gateway)) + if not group.lights: + continue + + known_groups = set(gateway.entities[DOMAIN]) + new_group = DeconzGroup(group, gateway) + if new_group.unique_id not in known_groups: + entities.append(new_group) - async_add_entities(entities, True) + if entities: + async_add_entities(entities, True) gateway.listeners.append( async_dispatcher_connect( @@ -85,6 +94,8 @@ def async_add_group(groups): class DeconzBaseLight(DeconzDevice, LightEntity): """Representation of a deCONZ light.""" + TYPE = DOMAIN + def __init__(self, device, gateway): """Set up light.""" super().__init__(device, gateway) @@ -198,10 +209,7 @@ async def async_turn_off(self, **kwargs): @property def device_state_attributes(self): """Return the device state attributes.""" - attributes = {} - attributes["is_deconz_group"] = self._device.type == "LightGroup" - - return attributes + return {"is_deconz_group": self._device.type == "LightGroup"} class DeconzLight(DeconzBaseLight): @@ -223,13 +231,12 @@ class DeconzGroup(DeconzBaseLight): def __init__(self, device, gateway): """Set up group and create an unique id.""" - super().__init__(device, gateway) + group_id_base = gateway.config_entry.unique_id + if CONF_GROUP_ID_BASE in gateway.config_entry.data: + group_id_base = gateway.config_entry.data[CONF_GROUP_ID_BASE] + self._unique_id = f"{group_id_base}-{device.deconz_id}" - group_id_base = self.gateway.config_entry.unique_id - if CONF_GROUP_ID_BASE in self.gateway.config_entry.data: - group_id_base = self.gateway.config_entry.data[CONF_GROUP_ID_BASE] - - self._unique_id = f"{group_id_base}-{self._device.deconz_id}" + super().__init__(device, gateway) @property def unique_id(self): diff --git a/homeassistant/components/deconz/lock.py b/homeassistant/components/deconz/lock.py new file mode 100644 index 00000000000000..1f4fbe570698b5 --- /dev/null +++ b/homeassistant/components/deconz/lock.py @@ -0,0 +1,59 @@ +"""Support for deCONZ locks.""" +from homeassistant.components.lock import DOMAIN, LockEntity +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from .const import LOCKS, NEW_LIGHT +from .deconz_device import DeconzDevice +from .gateway import get_gateway_from_config_entry + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up locks for deCONZ component. + + Locks are based on the same device class as lights in deCONZ. + """ + gateway = get_gateway_from_config_entry(hass, config_entry) + gateway.entities[DOMAIN] = set() + + @callback + def async_add_lock(lights): + """Add lock from deCONZ.""" + entities = [] + + for light in lights: + + if light.type in LOCKS and light.uniqueid not in gateway.entities[DOMAIN]: + entities.append(DeconzLock(light, gateway)) + + if entities: + async_add_entities(entities, True) + + gateway.listeners.append( + async_dispatcher_connect( + hass, gateway.async_signal_new_device(NEW_LIGHT), async_add_lock + ) + ) + + async_add_lock(gateway.api.lights.values()) + + +class DeconzLock(DeconzDevice, LockEntity): + """Representation of a deCONZ lock.""" + + TYPE = DOMAIN + + @property + def is_locked(self): + """Return true if lock is on.""" + return self._device.state + + async def async_lock(self, **kwargs): + """Lock the lock.""" + data = {"on": True} + await self._device.async_set_state(data) + + async def async_unlock(self, **kwargs): + """Unlock the lock.""" + data = {"on": False} + await self._device.async_set_state(data) diff --git a/homeassistant/components/deconz/manifest.json b/homeassistant/components/deconz/manifest.json index 2cba87f74d6e0f..6a47864375e919 100644 --- a/homeassistant/components/deconz/manifest.json +++ b/homeassistant/components/deconz/manifest.json @@ -3,7 +3,7 @@ "name": "deCONZ", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/deconz", - "requirements": ["pydeconz==72"], + "requirements": ["pydeconz==73"], "ssdp": [ { "manufacturer": "Royal Philips Electronics" diff --git a/homeassistant/components/deconz/scene.py b/homeassistant/components/deconz/scene.py index fdeb1d43accad1..9ca7f39f034c9b 100644 --- a/homeassistant/components/deconz/scene.py +++ b/homeassistant/components/deconz/scene.py @@ -9,10 +9,6 @@ from .gateway import get_gateway_from_config_entry -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Old way of setting up deCONZ platforms.""" - - async def async_setup_entry(hass, config_entry, async_add_entities): """Set up scenes for deCONZ component.""" gateway = get_gateway_from_config_entry(hass, config_entry) @@ -22,7 +18,8 @@ def async_add_scene(scenes): """Add scene from deCONZ.""" entities = [DeconzScene(scene, gateway) for scene in scenes] - async_add_entities(entities) + if entities: + async_add_entities(entities) gateway.listeners.append( async_dispatcher_connect( diff --git a/homeassistant/components/deconz/sensor.py b/homeassistant/components/deconz/sensor.py index 4ebed981e2d759..ebc62aee57f5eb 100644 --- a/homeassistant/components/deconz/sensor.py +++ b/homeassistant/components/deconz/sensor.py @@ -12,6 +12,7 @@ Thermostat, ) +from homeassistant.components.sensor import DOMAIN from homeassistant.const import ( ATTR_TEMPERATURE, ATTR_VOLTAGE, @@ -22,6 +23,7 @@ DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, ENERGY_KILO_WATT_HOUR, + LIGHT_LUX, PERCENTAGE, POWER_WATT, PRESSURE_HPA, @@ -35,7 +37,6 @@ from .const import ATTR_DARK, ATTR_ON, NEW_SENSOR from .deconz_device import DeconzDevice -from .deconz_event import DeconzEvent from .gateway import get_gateway_from_config_entry ATTR_CURRENT = "current" @@ -60,67 +61,55 @@ UNIT_OF_MEASUREMENT = { Consumption: ENERGY_KILO_WATT_HOUR, Humidity: PERCENTAGE, - LightLevel: "lx", + LightLevel: LIGHT_LUX, Power: POWER_WATT, Pressure: PRESSURE_HPA, Temperature: TEMP_CELSIUS, } -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Old way of setting up deCONZ platforms.""" - - async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the deCONZ sensors.""" gateway = get_gateway_from_config_entry(hass, config_entry) + gateway.entities[DOMAIN] = set() - batteries = set() battery_handler = DeconzBatteryHandler(gateway) @callback - def async_add_sensor(sensors, new=True): + def async_add_sensor(sensors): """Add sensors from deCONZ. - Create DeconzEvent if part of ZHAType list. - Create DeconzSensor if not a ZHAType and not a binary sensor. Create DeconzBattery if sensor has a battery attribute. - If new is false it means an existing sensor has got a battery state reported. + Create DeconzSensor if not a battery, switch or thermostat and not a binary sensor. """ entities = [] for sensor in sensors: - if new and sensor.type in Switch.ZHATYPE: - - if gateway.option_allow_clip_sensor or not sensor.type.startswith( - "CLIP" - ): - new_event = DeconzEvent(sensor, gateway) - hass.async_create_task(new_event.async_update_device_registry()) - gateway.events.append(new_event) - - elif ( - new - and sensor.BINARY is False - and sensor.type not in Battery.ZHATYPE + Thermostat.ZHATYPE - and ( - gateway.option_allow_clip_sensor - or not sensor.type.startswith("CLIP") - ) - ): - entities.append(DeconzSensor(sensor, gateway)) + if not gateway.option_allow_clip_sensor and sensor.type.startswith("CLIP"): + continue if sensor.battery is not None: + battery_handler.remove_tracker(sensor) + + known_batteries = set(gateway.entities[DOMAIN]) new_battery = DeconzBattery(sensor, gateway) - if new_battery.unique_id not in batteries: - batteries.add(new_battery.unique_id) + if new_battery.unique_id not in known_batteries: entities.append(new_battery) - battery_handler.remove_tracker(sensor) + else: battery_handler.create_tracker(sensor) - async_add_entities(entities, True) + if ( + not sensor.BINARY + and sensor.type + not in Battery.ZHATYPE + Switch.ZHATYPE + Thermostat.ZHATYPE + and sensor.uniqueid not in gateway.entities[DOMAIN] + ): + entities.append(DeconzSensor(sensor, gateway)) + + if entities: + async_add_entities(entities, True) gateway.listeners.append( async_dispatcher_connect( @@ -136,15 +125,14 @@ def async_add_sensor(sensors, new=True): class DeconzSensor(DeconzDevice): """Representation of a deCONZ sensor.""" + TYPE = DOMAIN + @callback - def async_update_callback(self, force_update=False, ignore_update=False): + def async_update_callback(self, force_update=False): """Update the sensor's state.""" - if ignore_update: - return - keys = {"on", "reachable", "state"} if force_update or self._device.changed_keys.intersection(keys): - self.async_write_ha_state() + super().async_update_callback(force_update=force_update) @property def state(self): @@ -201,15 +189,14 @@ def device_state_attributes(self): class DeconzBattery(DeconzDevice): """Battery class for when a device is only represented as an event.""" + TYPE = DOMAIN + @callback - def async_update_callback(self, force_update=False, ignore_update=False): + def async_update_callback(self, force_update=False): """Update the battery's state, if needed.""" - if ignore_update: - return - keys = {"battery", "reachable"} if force_update or self._device.changed_keys.intersection(keys): - self.async_write_ha_state() + super().async_update_callback(force_update=force_update) @property def unique_id(self): @@ -273,7 +260,6 @@ def async_update_callback(self, ignore_update=False): self.gateway.hass, self.gateway.async_signal_new_device(NEW_SENSOR), [self.sensor], - False, ) diff --git a/homeassistant/components/deconz/services.py b/homeassistant/components/deconz/services.py index c85fa8073a3351..bf503376321c1a 100644 --- a/homeassistant/components/deconz/services.py +++ b/homeassistant/components/deconz/services.py @@ -3,6 +3,10 @@ import voluptuous as vol from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.entity_registry import ( + async_entries_for_config_entry, + async_entries_for_device, +) from .config_flow import get_master_gateway from .const import ( @@ -35,7 +39,8 @@ ) SERVICE_DEVICE_REFRESH = "device_refresh" -SERVICE_DEVICE_REFRESH_SCHEMA = vol.All(vol.Schema({vol.Optional(CONF_BRIDGE_ID): str})) +SERVICE_REMOVE_ORPHANED_ENTRIES = "remove_orphaned_entries" +SELECT_GATEWAY_SCHEMA = vol.All(vol.Schema({vol.Optional(CONF_BRIDGE_ID): str})) async def async_setup_services(hass): @@ -56,6 +61,9 @@ async def async_call_deconz_service(service_call): elif service == SERVICE_DEVICE_REFRESH: await async_refresh_devices_service(hass, service_data) + elif service == SERVICE_REMOVE_ORPHANED_ENTRIES: + await async_remove_orphaned_entries_service(hass, service_data) + hass.services.async_register( DOMAIN, SERVICE_CONFIGURE_DEVICE, @@ -67,7 +75,14 @@ async def async_call_deconz_service(service_call): DOMAIN, SERVICE_DEVICE_REFRESH, async_call_deconz_service, - schema=SERVICE_DEVICE_REFRESH_SCHEMA, + schema=SELECT_GATEWAY_SCHEMA, + ) + + hass.services.async_register( + DOMAIN, + SERVICE_REMOVE_ORPHANED_ENTRIES, + async_call_deconz_service, + schema=SELECT_GATEWAY_SCHEMA, ) @@ -80,6 +95,7 @@ async def async_unload_services(hass): hass.services.async_remove(DOMAIN, SERVICE_CONFIGURE_DEVICE) hass.services.async_remove(DOMAIN, SERVICE_DEVICE_REFRESH) + hass.services.async_remove(DOMAIN, SERVICE_REMOVE_ORPHANED_ENTRIES) async def async_configure_service(hass, data): @@ -127,7 +143,9 @@ async def async_refresh_devices_service(hass, data): scenes = set(gateway.api.scenes.keys()) sensors = set(gateway.api.sensors.keys()) - await gateway.api.refresh_state(ignore_update=True) + gateway.ignore_state_updates = True + await gateway.api.refresh_state() + gateway.ignore_state_updates = False gateway.async_add_device_callback( NEW_GROUP, @@ -164,3 +182,54 @@ async def async_refresh_devices_service(hass, data): if sensor_id not in sensors ], ) + + +async def async_remove_orphaned_entries_service(hass, data): + """Remove orphaned deCONZ entries from device and entity registries.""" + gateway = get_master_gateway(hass) + if CONF_BRIDGE_ID in data: + gateway = hass.data[DOMAIN][normalize_bridge_id(data[CONF_BRIDGE_ID])] + + entity_registry = await hass.helpers.entity_registry.async_get_registry() + device_registry = await hass.helpers.device_registry.async_get_registry() + + entity_entries = async_entries_for_config_entry( + entity_registry, gateway.config_entry.entry_id + ) + + entities_to_be_removed = [] + devices_to_be_removed = [ + entry.id + for entry in device_registry.devices.values() + if gateway.config_entry.entry_id in entry.config_entries + ] + + # Don't remove the Gateway device + if gateway.device_id in devices_to_be_removed: + devices_to_be_removed.remove(gateway.device_id) + + # Don't remove devices belonging to available events + for event in gateway.events: + if event.device_id in devices_to_be_removed: + devices_to_be_removed.remove(event.device_id) + + for entry in entity_entries: + + # Don't remove available entities + if entry.unique_id in gateway.entities[entry.domain]: + + # Don't remove devices with available entities + if entry.device_id in devices_to_be_removed: + devices_to_be_removed.remove(entry.device_id) + continue + # Remove entities that are not available + entities_to_be_removed.append(entry.entity_id) + + # Remove unavailable entities + for entity_id in entities_to_be_removed: + entity_registry.async_remove(entity_id) + + # Remove devices that don't belong to any entity + for device_id in devices_to_be_removed: + if len(async_entries_for_device(entity_registry, device_id)) == 0: + device_registry.async_remove_device(device_id) diff --git a/homeassistant/components/deconz/services.yaml b/homeassistant/components/deconz/services.yaml index d8bf3e4d9949f7..9d85e76d8d3a53 100644 --- a/homeassistant/components/deconz/services.yaml +++ b/homeassistant/components/deconz/services.yaml @@ -23,3 +23,10 @@ device_refresh: bridgeid: description: (Optional) Bridgeid is a string unique for each deCONZ hardware. It can be found as part of the integration name. example: "00212EFFFF012345" + +remove_orphaned_entries: + description: Clean up device and entity registry entries orphaned by deCONZ. + fields: + bridgeid: + description: (Optional) Bridgeid is a string unique for each deCONZ hardware. It can be found as part of the integration name. + example: "00212EFFFF012345" diff --git a/homeassistant/components/deconz/strings.json b/homeassistant/components/deconz/strings.json index cf5acb5cff709f..ed854623ef8060 100644 --- a/homeassistant/components/deconz/strings.json +++ b/homeassistant/components/deconz/strings.json @@ -27,8 +27,9 @@ }, "abort": { "already_configured": "Bridge is already configured", - "already_in_progress": "Config flow for bridge is already in progress.", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", "no_bridges": "No deCONZ bridges discovered", + "no_hardware_available": "No radio hardware connected to deCONZ", "not_deconz_bridge": "Not a deCONZ bridge", "one_instance_only": "Component only supports one deCONZ instance", "updated_instance": "Updated deCONZ instance with new host address" @@ -39,7 +40,8 @@ "deconz_devices": { "data": { "allow_clip_sensor": "Allow deCONZ CLIP sensors", - "allow_deconz_groups": "Allow deCONZ light groups" + "allow_deconz_groups": "Allow deCONZ light groups", + "allow_new_devices": "Allow automatic addition of new devices" }, "description": "Configure visibility of deCONZ device types", "title": "deCONZ options" @@ -100,4 +102,4 @@ "side_6": "Side 6" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/deconz/switch.py b/homeassistant/components/deconz/switch.py index d7b6b55fbb884b..02fa66fc35a336 100644 --- a/homeassistant/components/deconz/switch.py +++ b/homeassistant/components/deconz/switch.py @@ -1,5 +1,5 @@ """Support for deCONZ switches.""" -from homeassistant.components.switch import SwitchEntity +from homeassistant.components.switch import DOMAIN, SwitchEntity from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -8,16 +8,13 @@ from .gateway import get_gateway_from_config_entry -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Old way of setting up deCONZ platforms.""" - - async def async_setup_entry(hass, config_entry, async_add_entities): """Set up switches for deCONZ component. - Switches are based same device class as lights in deCONZ. + Switches are based on the same device class as lights in deCONZ. """ gateway = get_gateway_from_config_entry(hass, config_entry) + gateway.entities[DOMAIN] = set() @callback def async_add_switch(lights): @@ -26,13 +23,19 @@ def async_add_switch(lights): for light in lights: - if light.type in POWER_PLUGS: + if ( + light.type in POWER_PLUGS + and light.uniqueid not in gateway.entities[DOMAIN] + ): entities.append(DeconzPowerPlug(light, gateway)) - elif light.type in SIRENS: + elif ( + light.type in SIRENS and light.uniqueid not in gateway.entities[DOMAIN] + ): entities.append(DeconzSiren(light, gateway)) - async_add_entities(entities, True) + if entities: + async_add_entities(entities, True) gateway.listeners.append( async_dispatcher_connect( @@ -46,6 +49,8 @@ def async_add_switch(lights): class DeconzPowerPlug(DeconzDevice, SwitchEntity): """Representation of a deCONZ power plug.""" + TYPE = DOMAIN + @property def is_on(self): """Return true if switch is on.""" @@ -65,6 +70,8 @@ async def async_turn_off(self, **kwargs): class DeconzSiren(DeconzDevice, SwitchEntity): """Representation of a deCONZ siren.""" + TYPE = DOMAIN + @property def is_on(self): """Return true if switch is on.""" diff --git a/homeassistant/components/deconz/translations/ca.json b/homeassistant/components/deconz/translations/ca.json index 1c8500b3cc1f64..77d948e8f4d49f 100644 --- a/homeassistant/components/deconz/translations/ca.json +++ b/homeassistant/components/deconz/translations/ca.json @@ -2,8 +2,9 @@ "config": { "abort": { "already_configured": "L'enlla\u00e7 ja est\u00e0 configurat", - "already_in_progress": "El flux de dades de configuraci\u00f3 per l'enlla\u00e7 ja est\u00e0 en curs.", + "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", "one_instance_only": "El component nom\u00e9s admet una inst\u00e0ncia deCONZ", "updated_instance": "S'ha actualitzat la inst\u00e0ncia de deCONZ amb una nova adre\u00e7a" @@ -93,7 +94,8 @@ "deconz_devices": { "data": { "allow_clip_sensor": "Permet sensors deCONZ CLIP", - "allow_deconz_groups": "Permet grups de llums deCONZ" + "allow_deconz_groups": "Permet grups de llums deCONZ", + "allow_new_devices": "Permet l'addici\u00f3 autom\u00e0tica de nous dispositius" }, "description": "Configura la visibilitat dels tipus dels dispositius deCONZ", "title": "Opcions de deCONZ" diff --git a/homeassistant/components/deconz/translations/el.json b/homeassistant/components/deconz/translations/el.json new file mode 100644 index 00000000000000..bb5489843b8608 --- /dev/null +++ b/homeassistant/components/deconz/translations/el.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "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" + } + }, + "options": { + "step": { + "deconz_devices": { + "data": { + "allow_new_devices": "\u039d\u03b1 \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c0\u03b5\u03c4\u03b1\u03b9 \u03b7 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7 \u03bd\u03ad\u03c9\u03bd \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/deconz/translations/en.json b/homeassistant/components/deconz/translations/en.json index fa329d2e1b3040..fc13160ce66c0b 100644 --- a/homeassistant/components/deconz/translations/en.json +++ b/homeassistant/components/deconz/translations/en.json @@ -2,8 +2,9 @@ "config": { "abort": { "already_configured": "Bridge is already configured", - "already_in_progress": "Config flow for bridge is already in progress.", + "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", "one_instance_only": "Component only supports one deCONZ instance", "updated_instance": "Updated deCONZ instance with new host address" @@ -93,7 +94,8 @@ "deconz_devices": { "data": { "allow_clip_sensor": "Allow deCONZ CLIP sensors", - "allow_deconz_groups": "Allow deCONZ light groups" + "allow_deconz_groups": "Allow deCONZ light groups", + "allow_new_devices": "Allow automatic addition of new devices" }, "description": "Configure visibility of deCONZ device types", "title": "deCONZ options" diff --git a/homeassistant/components/deconz/translations/es.json b/homeassistant/components/deconz/translations/es.json index 877623188bbac0..b00949b98c7a49 100644 --- a/homeassistant/components/deconz/translations/es.json +++ b/homeassistant/components/deconz/translations/es.json @@ -4,6 +4,7 @@ "already_configured": "La pasarela ya est\u00e1 configurada", "already_in_progress": "El flujo de configuraci\u00f3n para la pasarela ya est\u00e1 en marcha.", "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", "one_instance_only": "El componente solo admite una instancia de deCONZ", "updated_instance": "Instancia deCONZ actualizada con nueva direcci\u00f3n de host" @@ -93,7 +94,8 @@ "deconz_devices": { "data": { "allow_clip_sensor": "Permitir sensores deCONZ CLIP", - "allow_deconz_groups": "Permitir grupos de luz deCONZ" + "allow_deconz_groups": "Permitir grupos de luz deCONZ", + "allow_new_devices": "Permitir a\u00f1adir autom\u00e1ticamente nuevos dispositivos" }, "description": "Configurar la visibilidad de los tipos de dispositivos deCONZ", "title": "Opciones deCONZ" diff --git a/homeassistant/components/deconz/translations/et.json b/homeassistant/components/deconz/translations/et.json new file mode 100644 index 00000000000000..ff449b13e75b1f --- /dev/null +++ b/homeassistant/components/deconz/translations/et.json @@ -0,0 +1,45 @@ +{ + "config": { + "abort": { + "no_hardware_available": "DeCONZi raadio riistvara puudub" + } + }, + "device_automation": { + "trigger_subtype": { + "both_buttons": "M\u00f5lemad nupud", + "bottom_buttons": "Alumised nupud", + "button_1": "Esimene nupp", + "button_2": "Teine nupp", + "button_3": "Kolmas nupp", + "button_4": "Neljas nupp", + "close": "Sulge", + "dim_down": "H\u00e4marda", + "dim_up": "Tee heledamaks", + "left": "Vasakpoolne", + "open": "Ava", + "right": "Parempoolne", + "side_1": "1. k\u00fclg", + "side_2": "2. k\u00fclg", + "side_3": "3. k\u00fclg", + "side_4": "4. k\u00fclg", + "side_5": "5. k\u00fclg", + "side_6": "6. k\u00fclg", + "top_buttons": "\u00dclemised nupud", + "turn_off": "L\u00fclita v\u00e4lja", + "turn_on": "L\u00fclita sisse" + }, + "trigger_type": { + "remote_awakened": "Seade \u00e4rkas", + "remote_button_rotation_stopped": "Nupu \" {subtype} \" p\u00f6\u00f6ramine peatus" + } + }, + "options": { + "step": { + "deconz_devices": { + "data": { + "allow_new_devices": "Luba uute seadmete automaatne lisamine" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/deconz/translations/fr.json b/homeassistant/components/deconz/translations/fr.json index 4fad292f27443f..2704951db5cd95 100644 --- a/homeassistant/components/deconz/translations/fr.json +++ b/homeassistant/components/deconz/translations/fr.json @@ -4,6 +4,7 @@ "already_configured": "Ce pont est d\u00e9j\u00e0 configur\u00e9", "already_in_progress": "Le flux de configuration pour le pont 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", "one_instance_only": "Le composant prend uniquement en charge une instance deCONZ", "updated_instance": "Instance deCONZ mise \u00e0 jour avec la nouvelle adresse d'h\u00f4te" @@ -26,6 +27,11 @@ "host": "Nom d'h\u00f4te ou adresse IP", "port": "Port" } + }, + "user": { + "data": { + "host": "S\u00e9lectionnez la passerelle deCONZ d\u00e9couverte" + } } } }, @@ -88,7 +94,8 @@ "deconz_devices": { "data": { "allow_clip_sensor": "Autoriser les capteurs deCONZ CLIP", - "allow_deconz_groups": "Autoriser les groupes de lumi\u00e8res deCONZ" + "allow_deconz_groups": "Autoriser les groupes de lumi\u00e8res deCONZ", + "allow_new_devices": "Autoriser l'ajout automatique de nouveaux appareils" }, "description": "Configurer la visibilit\u00e9 des appareils de type deCONZ", "title": "Options deCONZ" diff --git a/homeassistant/components/deconz/translations/it.json b/homeassistant/components/deconz/translations/it.json index 26a1a14e32a5c2..2029e11613fe79 100644 --- a/homeassistant/components/deconz/translations/it.json +++ b/homeassistant/components/deconz/translations/it.json @@ -2,8 +2,9 @@ "config": { "abort": { "already_configured": "Il Bridge \u00e8 gi\u00e0 configurato", - "already_in_progress": "Il flusso di configurazione per bridge \u00e8 gi\u00e0 in corso.", + "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", "one_instance_only": "Il componente supporto solo un'istanza di deCONZ", "updated_instance": "Istanza deCONZ aggiornata con nuovo indirizzo host" @@ -93,7 +94,8 @@ "deconz_devices": { "data": { "allow_clip_sensor": "Consentire sensori CLIP deCONZ", - "allow_deconz_groups": "Consentire gruppi luce deCONZ" + "allow_deconz_groups": "Consentire gruppi luce deCONZ", + "allow_new_devices": "Consentire l'aggiunta automatica di nuovi dispositivi" }, "description": "Configurare la visibilit\u00e0 dei tipi di dispositivi deCONZ", "title": "Opzioni deCONZ" diff --git a/homeassistant/components/deconz/translations/lb.json b/homeassistant/components/deconz/translations/lb.json index 6e80c2393aca73..d2c36eb30bd4b1 100644 --- a/homeassistant/components/deconz/translations/lb.json +++ b/homeassistant/components/deconz/translations/lb.json @@ -4,6 +4,7 @@ "already_configured": "Bridge ass schon konfigur\u00e9iert", "already_in_progress": "Konfiguratioun fir d\u00ebsen Apparat 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", "one_instance_only": "Komponent \u00ebnnerst\u00ebtzt n\u00ebmmen eng deCONZ Instanz", "updated_instance": "deCONZ Instanz gouf mat der neier Adress vum Apparat ge\u00e4nnert" @@ -93,7 +94,8 @@ "deconz_devices": { "data": { "allow_clip_sensor": "deCONZ Clip Sensoren erlaben", - "allow_deconz_groups": "deCONZ Luucht Gruppen erlaben" + "allow_deconz_groups": "deCONZ Luucht Gruppen erlaben", + "allow_new_devices": "Erlaabt automatesch dob\u00e4isetze vu neien Apparater" }, "description": "Visibilit\u00e9it vun deCONZ Apparater konfigur\u00e9ieren", "title": "deCONZ Optiounen" diff --git a/homeassistant/components/deconz/translations/no.json b/homeassistant/components/deconz/translations/no.json index 231901c4cd404b..932b5d82da0167 100644 --- a/homeassistant/components/deconz/translations/no.json +++ b/homeassistant/components/deconz/translations/no.json @@ -2,8 +2,9 @@ "config": { "abort": { "already_configured": "Broen er allerede konfigurert", - "already_in_progress": "Konfigurasjonsflyt for bro p\u00e5g\u00e5r allerede.", + "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", "one_instance_only": "Komponenten st\u00f8tter bare \u00e9n deCONZ forekomst", "updated_instance": "Oppdatert deCONZ forekomst med ny vertsadresse" @@ -93,7 +94,8 @@ "deconz_devices": { "data": { "allow_clip_sensor": "Tillat deCONZ CLIP-sensorer", - "allow_deconz_groups": "Tillat deCONZ lys grupper" + "allow_deconz_groups": "Tillat deCONZ lys grupper", + "allow_new_devices": "Tillat automatisk tilsetning av nye enheter" }, "description": "Konfigurere synlighet av deCONZ enhetstyper", "title": "deCONZ alternativer" diff --git a/homeassistant/components/deconz/translations/pl.json b/homeassistant/components/deconz/translations/pl.json index 27dbc0535cebd1..2fa9549585a251 100644 --- a/homeassistant/components/deconz/translations/pl.json +++ b/homeassistant/components/deconz/translations/pl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Mostek jest ju\u017c skonfigurowany.", + "already_configured": "Mostek jest ju\u017c skonfigurowany", "already_in_progress": "Konfiguracja mostka jest ju\u017c w toku.", "no_bridges": "Nie odkryto mostk\u00f3w deCONZ", "not_deconz_bridge": "To nie jest mostek deCONZ", diff --git a/homeassistant/components/deconz/translations/ru.json b/homeassistant/components/deconz/translations/ru.json index 32bf2001f44b23..794c2e52bd2243 100644 --- a/homeassistant/components/deconz/translations/ru.json +++ b/homeassistant/components/deconz/translations/ru.json @@ -2,8 +2,9 @@ "config": { "abort": { "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", - "already_in_progress": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.", + "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.", "one_instance_only": "\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 deCONZ.", "updated_instance": "\u0410\u0434\u0440\u0435\u0441 \u0445\u043e\u0441\u0442\u0430 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d." @@ -93,7 +94,8 @@ "deconz_devices": { "data": { "allow_clip_sensor": "\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c \u0441\u0435\u043d\u0441\u043e\u0440\u044b deCONZ CLIP", - "allow_deconz_groups": "\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c \u0433\u0440\u0443\u043f\u043f\u044b \u043e\u0441\u0432\u0435\u0449\u0435\u043d\u0438\u044f deCONZ" + "allow_deconz_groups": "\u041e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0442\u044c \u0433\u0440\u0443\u043f\u043f\u044b \u043e\u0441\u0432\u0435\u0449\u0435\u043d\u0438\u044f deCONZ", + "allow_new_devices": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043d\u043e\u0432\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432" }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0432\u0438\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0442\u0438\u043f\u043e\u0432 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 deCONZ", "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 deCONZ" diff --git a/homeassistant/components/deconz/translations/zh-Hant.json b/homeassistant/components/deconz/translations/zh-Hant.json index 2b941f5ae64b8c..088ee458abec34 100644 --- a/homeassistant/components/deconz/translations/zh-Hant.json +++ b/homeassistant/components/deconz/translations/zh-Hant.json @@ -2,8 +2,9 @@ "config": { "abort": { "already_configured": "Bridge \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", - "already_in_progress": "Bridge \u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d\u3002", + "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "no_bridges": "\u672a\u641c\u5c0b\u5230 deCONZ Bridfe", + "no_hardware_available": "deCONZ \u6c92\u6709\u4efb\u4f55\u7121\u7dda\u96fb\u8a2d\u5099\u9023\u7dda", "not_deconz_bridge": "\u975e deCONZ Bridge \u8a2d\u5099", "one_instance_only": "\u7d44\u4ef6\u50c5\u652f\u63f4\u4e00\u7d44 deCONZ \u7269\u4ef6", "updated_instance": "\u4f7f\u7528\u65b0\u4e3b\u6a5f\u7aef\u4f4d\u5740\u66f4\u65b0 deCONZ \u8a2d\u5099" @@ -93,7 +94,8 @@ "deconz_devices": { "data": { "allow_clip_sensor": "\u5141\u8a31 deCONZ CLIP \u611f\u61c9\u5668", - "allow_deconz_groups": "\u5141\u8a31 deCONZ \u71c8\u5149\u7fa4\u7d44" + "allow_deconz_groups": "\u5141\u8a31 deCONZ \u71c8\u5149\u7fa4\u7d44", + "allow_new_devices": "\u5141\u8a31\u81ea\u52d5\u5316\u65b0\u589e\u8a2d\u5099" }, "description": "\u8a2d\u5b9a deCONZ \u53ef\u8996\u8a2d\u5099\u985e\u578b", "title": "deCONZ \u9078\u9805" diff --git a/homeassistant/components/decora/light.py b/homeassistant/components/decora/light.py index 4335c99076a84d..2fc436eda20e1e 100644 --- a/homeassistant/components/decora/light.py +++ b/homeassistant/components/decora/light.py @@ -125,11 +125,6 @@ def supported_features(self): """Flag supported features.""" return SUPPORT_DECORA_LED - @property - def should_poll(self): - """We can read the device state, so poll.""" - return True - @property def assumed_state(self): """We can read the actual state.""" diff --git a/homeassistant/components/demo/binary_sensor.py b/homeassistant/components/demo/binary_sensor.py index 04d8e72f9a8005..c4186eae5056b6 100644 --- a/homeassistant/components/demo/binary_sensor.py +++ b/homeassistant/components/demo/binary_sensor.py @@ -1,5 +1,9 @@ """Demo platform that has two fake binary sensors.""" -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_MOISTURE, + DEVICE_CLASS_MOTION, + BinarySensorEntity, +) from . import DOMAIN @@ -8,8 +12,12 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= """Set up the Demo binary sensor platform.""" async_add_entities( [ - DemoBinarySensor("binary_1", "Basement Floor Wet", False, "moisture"), - DemoBinarySensor("binary_2", "Movement Backyard", True, "motion"), + DemoBinarySensor( + "binary_1", "Basement Floor Wet", False, DEVICE_CLASS_MOISTURE + ), + DemoBinarySensor( + "binary_2", "Movement Backyard", True, DEVICE_CLASS_MOTION + ), ] ) diff --git a/homeassistant/components/demo/camera.py b/homeassistant/components/demo/camera.py index ce211a47594df5..2059f258972f78 100644 --- a/homeassistant/components/demo/camera.py +++ b/homeassistant/components/demo/camera.py @@ -40,14 +40,6 @@ def name(self): """Return the name of this camera.""" return self._name - @property - def should_poll(self): - """Demo camera doesn't need poll. - - Need explicitly call async_write_ha_state() after state changed. - """ - return False - @property def supported_features(self): """Camera support turn on/off features.""" diff --git a/homeassistant/components/demo/translations/no.json b/homeassistant/components/demo/translations/no.json index 5003b9da5684bb..48da80fd62999b 100644 --- a/homeassistant/components/demo/translations/no.json +++ b/homeassistant/components/demo/translations/no.json @@ -5,7 +5,7 @@ "data": { "bool": "Valgfri boolean", "constant": "Konstant", - "int": "Numerisk inndata" + "int": "Numerisk innputt" } }, "options_2": { diff --git a/homeassistant/components/denon/media_player.py b/homeassistant/components/denon/media_player.py index 9f451ab3025712..ed90c2ddcb00db 100644 --- a/homeassistant/components/denon/media_player.py +++ b/homeassistant/components/denon/media_player.py @@ -111,10 +111,18 @@ def _setup_sources(self, telnet): if nsfrn: self._name = nsfrn - # SSFUN - Configured sources with names + # SSFUN - Configured sources with (optional) names self._source_list = {} for line in self.telnet_request(telnet, "SSFUN ?", all_lines=True): - source, configured_name = line[len("SSFUN") :].split(" ", 1) + ssfun = line[len("SSFUN") :].split(" ", 1) + + source = ssfun[0] + if len(ssfun) == 2 and ssfun[1]: + configured_name = ssfun[1] + else: + # No name configured, reusing the source name + configured_name = source + self._source_list[configured_name] = source # SSSOD - Deleted sources diff --git a/homeassistant/components/denonavr/manifest.json b/homeassistant/components/denonavr/manifest.json index afc5a7ef72f825..86bee686764f88 100644 --- a/homeassistant/components/denonavr/manifest.json +++ b/homeassistant/components/denonavr/manifest.json @@ -3,7 +3,7 @@ "name": "Denon AVR Network Receivers", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/denonavr", - "requirements": ["denonavr==0.9.4", "getmac==0.8.2"], + "requirements": ["denonavr==0.9.5", "getmac==0.8.2"], "codeowners": ["@scarface-4711", "@starkillerOG"], "ssdp": [ { diff --git a/homeassistant/components/denonavr/media_player.py b/homeassistant/components/denonavr/media_player.py index c28b1a4cab5f57..b5990dede21310 100644 --- a/homeassistant/components/denonavr/media_player.py +++ b/homeassistant/components/denonavr/media_player.py @@ -308,14 +308,13 @@ def media_episode(self): @property def device_state_attributes(self): """Return device specific state attributes.""" - attributes = {} if ( self._sound_mode_raw is not None and self._sound_mode_support and self._power == "ON" ): - attributes[ATTR_SOUND_MODE_RAW] = self._sound_mode_raw - return attributes + return {ATTR_SOUND_MODE_RAW: self._sound_mode_raw} + return {} def media_play_pause(self): """Play or pause the media player.""" diff --git a/homeassistant/components/denonavr/strings.json b/homeassistant/components/denonavr/strings.json index 25648d974abcf7..6ac96f2c17c7f5 100644 --- a/homeassistant/components/denonavr/strings.json +++ b/homeassistant/components/denonavr/strings.json @@ -6,7 +6,7 @@ "title": "Denon AVR Network Receivers", "description": "Connect to your receiver, if the IP address is not set, auto-discovery is used", "data": { - "host": "IP address" + "host": "[%key:common::config_flow::data::ip%]" } }, "confirm": { @@ -26,7 +26,7 @@ }, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", - "already_in_progress": "Config flow for this Denon AVR is already in progress", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", "connection_error": "Failed to connect, please try again, disconnecting mains power and ethernet cables and reconnecting them may help", "not_denonavr_manufacturer": "Not a Denon AVR Network Receiver, discovered manafucturer did not match", "not_denonavr_missing": "Not a Denon AVR Network Receiver, discovery information not complete" diff --git a/homeassistant/components/denonavr/translations/ca.json b/homeassistant/components/denonavr/translations/ca.json index 34e73124056b40..bf67ba257ca16e 100644 --- a/homeassistant/components/denonavr/translations/ca.json +++ b/homeassistant/components/denonavr/translations/ca.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "El dispositiu ja est\u00e0 configurat", - "already_in_progress": "El flux de dades de configuraci\u00f3 per aquest Denon AVR ja est\u00e0 en curs", + "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", "connection_error": "No s'ha pogut connectar, torna-ho a provar. \u00c9s possible que s'arregli si desconnectes i tornes a connectar els cables d'Ethernet i d'alimentaci\u00f3.", "not_denonavr_manufacturer": "No \u00e9s un receptor de xarxa Denon AVR, no coincideix el fabricant descobert", "not_denonavr_missing": "No \u00e9s un receptor de xarxa Denon AVR, informaci\u00f3 de descobriment no completa" diff --git a/homeassistant/components/denonavr/translations/de.json b/homeassistant/components/denonavr/translations/de.json new file mode 100644 index 00000000000000..4fd05d6c578b35 --- /dev/null +++ b/homeassistant/components/denonavr/translations/de.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "host": "IP-Adresse" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/denonavr/translations/en.json b/homeassistant/components/denonavr/translations/en.json index 91ec0d62a45327..4d2b16352e2ab2 100644 --- a/homeassistant/components/denonavr/translations/en.json +++ b/homeassistant/components/denonavr/translations/en.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Device is already configured", - "already_in_progress": "Config flow for this Denon AVR is already in progress", + "already_in_progress": "Configuration flow is already in progress", "connection_error": "Failed to connect, please try again, disconnecting mains power and ethernet cables and reconnecting them may help", "not_denonavr_manufacturer": "Not a Denon AVR Network Receiver, discovered manafucturer did not match", "not_denonavr_missing": "Not a Denon AVR Network Receiver, discovery information not complete" @@ -25,7 +25,7 @@ }, "user": { "data": { - "host": "IP address" + "host": "IP Address" }, "description": "Connect to your receiver, if the IP address is not set, auto-discovery is used", "title": "Denon AVR Network Receivers" diff --git a/homeassistant/components/denonavr/translations/et.json b/homeassistant/components/denonavr/translations/et.json new file mode 100644 index 00000000000000..77c31e46107a35 --- /dev/null +++ b/homeassistant/components/denonavr/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "connection_error": "\u00dchenduse loomine nurjus. Vooluv\u00f5rgust ja LAN v\u00f5rgust eemaldamine ja taas\u00fchendamine v\u00f5ib aidata" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/denonavr/translations/hu.json b/homeassistant/components/denonavr/translations/hu.json new file mode 100644 index 00000000000000..3b2d79a34a77e2 --- /dev/null +++ b/homeassistant/components/denonavr/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/denonavr/translations/it.json b/homeassistant/components/denonavr/translations/it.json index 4b6359cfce626b..9fc4e3cf90f3c8 100644 --- a/homeassistant/components/denonavr/translations/it.json +++ b/homeassistant/components/denonavr/translations/it.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", - "already_in_progress": "Il flusso di configurazione per questo Denon AVR \u00e8 gi\u00e0 in corso", + "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "connection_error": "Impossibile connettersi, si prega di riprovare, pu\u00f2 essere utile scollegare i cavi di alimentazione ed i cavi Ethernet e ricollegarli", "not_denonavr_manufacturer": "Non \u00e8 un ricevitore di rete Denon AVR, il produttore rilevato non corrisponde", "not_denonavr_missing": "Non \u00e8 un ricevitore di rete Denon AVR, le informazioni di rilevamento non sono complete" diff --git a/homeassistant/components/denonavr/translations/no.json b/homeassistant/components/denonavr/translations/no.json index e156101c378da5..2ae9a550c3d592 100644 --- a/homeassistant/components/denonavr/translations/no.json +++ b/homeassistant/components/denonavr/translations/no.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Enheten er allerede konfigurert", - "already_in_progress": "Konfigurasjonsflyt for denne Denon AVR p\u00e5g\u00e5r allerede", + "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", "connection_error": "Kunne ikke koble til, vennligst pr\u00f8v igjen. Koble fra str\u00f8m- og nettverkskablene og koble dem til igjen kan hjelpe", "not_denonavr_manufacturer": "Ikke en Denon AVR Network Receiver, oppdaget manafucturer stemte ikke overens", "not_denonavr_missing": "Ikke en Denon AVR Network Receiver, oppdagelsesinformasjon ikke fullf\u00f8rt" diff --git a/homeassistant/components/denonavr/translations/pl.json b/homeassistant/components/denonavr/translations/pl.json index f025f6e2dd4d79..eb396842bf0817 100644 --- a/homeassistant/components/denonavr/translations/pl.json +++ b/homeassistant/components/denonavr/translations/pl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", "already_in_progress": "Konfiguracja urz\u0105dzenia jest ju\u017c w toku.", "connection_error": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z urz\u0105dzeniem, spr\u00f3buj ponownie, od\u0142\u0105czenie zasilania sieciowego i kabla Ethernet i ponowne pod\u0142\u0105czenie mo\u017ce pom\u00f3c", "not_denonavr_manufacturer": "Nie jest to urz\u0105dzenie AVR firmy Denon, producent wykrytego urz\u0105dzenia nie pasuje.", diff --git a/homeassistant/components/denonavr/translations/ru.json b/homeassistant/components/denonavr/translations/ru.json index 79e8c213e5f472..6dddf729fe358e 100644 --- a/homeassistant/components/denonavr/translations/ru.json +++ b/homeassistant/components/denonavr/translations/ru.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant.", - "already_in_progress": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.", + "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, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437. \u0415\u0441\u043b\u0438 \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u0435\u0442\u0441\u044f, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u043f\u0435\u0440\u0435\u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043a\u0430\u0431\u0435\u043b\u044c Ethernet \u0438 \u0441\u0435\u0442\u0435\u0432\u043e\u0439 \u043a\u0430\u0431\u0435\u043b\u044c.", "not_denonavr_manufacturer": "\u042d\u0442\u043e \u043d\u0435 \u0440\u0435\u0441\u0438\u0432\u0435\u0440 Denon. \u041f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c \u043d\u0435 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442.", "not_denonavr_missing": "\u041d\u0435\u043f\u043e\u043b\u043d\u0430\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u0434\u043b\u044f \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430." diff --git a/homeassistant/components/denonavr/translations/zh-Hant.json b/homeassistant/components/denonavr/translations/zh-Hant.json index 6127d1703054c5..5dfe1df53b24ca 100644 --- a/homeassistant/components/denonavr/translations/zh-Hant.json +++ b/homeassistant/components/denonavr/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", - "already_in_progress": "Denon AVR \u8a2d\u5099\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d\u3002", + "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "connection_error": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002\u95dc\u9589\u4e3b\u96fb\u6e90\u3001\u5c07\u4e59\u592a\u7db2\u8def\u65b7\u7dda\u5f8c\u91cd\u65b0\u9023\u7dda\uff0c\u53ef\u80fd\u6703\u6709\u6240\u5e6b\u52a9", "not_denonavr_manufacturer": "\u4e26\u975e Denon AVR \u7db2\u8def\u63a5\u6536\u5668\uff0c\u6240\u63a2\u7d22\u4e4b\u88fd\u9020\u5ee0\u5546\u4e0d\u7b26\u5408", "not_denonavr_missing": "\u4e26\u975e Denon AVR \u7db2\u8def\u63a5\u6536\u5668\uff0c\u63a2\u7d22\u8cc7\u8a0a\u4e0d\u5b8c\u6574" diff --git a/homeassistant/components/derivative/sensor.py b/homeassistant/components/derivative/sensor.py index c1369dd0f5b0b2..7c2cc444a5a25f 100644 --- a/homeassistant/components/derivative/sensor.py +++ b/homeassistant/components/derivative/sensor.py @@ -213,8 +213,7 @@ def should_poll(self): @property def device_state_attributes(self): """Return the state attributes of the sensor.""" - state_attr = {ATTR_SOURCE_ID: self._sensor_source_id} - return state_attr + return {ATTR_SOURCE_ID: self._sensor_source_id} @property def icon(self): diff --git a/homeassistant/components/device_tracker/group.py b/homeassistant/components/device_tracker/group.py new file mode 100644 index 00000000000000..07ec2cfe985ed7 --- /dev/null +++ b/homeassistant/components/device_tracker/group.py @@ -0,0 +1,15 @@ +"""Describe group states.""" + + +from homeassistant.components.group import GroupIntegrationRegistry +from homeassistant.const import STATE_HOME, STATE_NOT_HOME +from homeassistant.core import callback +from homeassistant.helpers.typing import HomeAssistantType + + +@callback +def async_describe_on_off_states( + hass: HomeAssistantType, registry: GroupIntegrationRegistry +) -> None: + """Describe group on off states.""" + registry.on_off_states({STATE_HOME}, STATE_NOT_HOME) diff --git a/homeassistant/components/device_tracker/translations/et.json b/homeassistant/components/device_tracker/translations/et.json index 340c03665ff230..c4f2b6f277d9da 100644 --- a/homeassistant/components/device_tracker/translations/et.json +++ b/homeassistant/components/device_tracker/translations/et.json @@ -1,4 +1,10 @@ { + "device_automation": { + "condition_type": { + "is_home": "{entity_name} on kodus", + "is_not_home": "{entity_name} on eemal" + } + }, "state": { "_": { "home": "Kodus", diff --git a/homeassistant/components/devolo_home_control/__init__.py b/homeassistant/components/devolo_home_control/__init__.py index c955bf770966da..ba180fd6ea49a0 100644 --- a/homeassistant/components/devolo_home_control/__init__.py +++ b/homeassistant/components/devolo_home_control/__init__.py @@ -4,7 +4,7 @@ from devolo_home_control_api.homecontrol import HomeControl from devolo_home_control_api.mydevolo import Mydevolo -from homeassistant.components import switch as ha_switch +from homeassistant.components import zeroconf from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, EVENT_HOMEASSISTANT_STOP from homeassistant.exceptions import ConfigEntryNotReady @@ -12,8 +12,6 @@ from .const import CONF_HOMECONTROL, CONF_MYDEVOLO, DOMAIN, PLATFORMS -SUPPORTED_PLATFORMS = [ha_switch.DOMAIN] - async def async_setup(hass, config): """Get all devices and add them to hass.""" @@ -32,7 +30,6 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool mydevolo.user = conf[CONF_USERNAME] mydevolo.password = conf[CONF_PASSWORD] mydevolo.url = conf[CONF_MYDEVOLO] - mydevolo.mprm = conf[CONF_HOMECONTROL] credentials_valid = await hass.async_add_executor_job(mydevolo.credentials_valid) @@ -44,11 +41,16 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool gateway_ids = await hass.async_add_executor_job(mydevolo.get_gateway_ids) gateway_id = gateway_ids[0] - mprm_url = mydevolo.mprm try: + zeroconf_instance = await zeroconf.async_get_instance(hass) hass.data[DOMAIN]["homecontrol"] = await hass.async_add_executor_job( - partial(HomeControl, gateway_id=gateway_id, url=mprm_url) + partial( + HomeControl, + gateway_id=gateway_id, + zeroconf_instance=zeroconf_instance, + url=conf[CONF_HOMECONTROL], + ) ) except ConnectionError as err: raise ConfigEntryNotReady from err diff --git a/homeassistant/components/devolo_home_control/binary_sensor.py b/homeassistant/components/devolo_home_control/binary_sensor.py index e05350dbbacd67..f884ce0219d944 100644 --- a/homeassistant/components/devolo_home_control/binary_sensor.py +++ b/homeassistant/components/devolo_home_control/binary_sensor.py @@ -67,50 +67,35 @@ def __init__(self, homecontrol, device_instance, element_uid): element_uid ) + super().__init__( + homecontrol=homecontrol, + device_instance=device_instance, + element_uid=element_uid, + ) + self._device_class = DEVICE_CLASS_MAPPING.get( self._binary_sensor_property.sub_type or self._binary_sensor_property.sensor_type ) - name = device_instance.item_name if self._device_class is None: if device_instance.binary_sensor_property.get(element_uid).sub_type != "": - name += f" {device_instance.binary_sensor_property.get(element_uid).sub_type}" + self._name += f" {device_instance.binary_sensor_property.get(element_uid).sub_type}" else: - name += f" {device_instance.binary_sensor_property.get(element_uid).sensor_type}" + self._name += f" {device_instance.binary_sensor_property.get(element_uid).sensor_type}" - super().__init__( - homecontrol=homecontrol, - device_instance=device_instance, - element_uid=element_uid, - name=name, - sync=self._sync, - ) - - self._state = self._binary_sensor_property.state - - self._subscriber = None + self._value = self._binary_sensor_property.state @property def is_on(self): """Return the state.""" - return self._state + return self._value @property def device_class(self): """Return device class.""" return self._device_class - def _sync(self, message=None): - """Update the binary sensor state.""" - if message[0].startswith("devolo.BinarySensor"): - self._state = self._device_instance.binary_sensor_property[message[0]].state - elif message[0].startswith("hdm"): - self._available = self._device_instance.is_online() - else: - _LOGGER.debug("No valid message received: %s", message) - self.schedule_update_ha_state() - class DevoloRemoteControl(DevoloDeviceEntity, BinarySensorEntity): """Representation of a remote control within devolo Home Control.""" @@ -120,26 +105,22 @@ def __init__(self, homecontrol, device_instance, element_uid, key): self._remote_control_property = device_instance.remote_control_property.get( element_uid ) + super().__init__( homecontrol=homecontrol, device_instance=device_instance, element_uid=f"{element_uid}_{key}", - name=device_instance.item_name, - sync=self._sync, ) self._key = key - self._state = False - self._subscriber = None - @property def is_on(self): """Return the state.""" return self._state - def _sync(self, message=None): + def _sync(self, message): """Update the binary sensor state.""" if ( message[0] == self._remote_control_property.element_uid @@ -150,8 +131,6 @@ def _sync(self, message=None): message[0] == self._remote_control_property.element_uid and message[1] == 0 ): self._state = False - elif message[0].startswith("hdm"): - self._available = self._device_instance.is_online() else: - _LOGGER.debug("No valid message received: %s", message) + self._generic_message(message) self.schedule_update_ha_state() diff --git a/homeassistant/components/devolo_home_control/climate.py b/homeassistant/components/devolo_home_control/climate.py index d44a0c981f1175..297e431e63a765 100644 --- a/homeassistant/components/devolo_home_control/climate.py +++ b/homeassistant/components/devolo_home_control/climate.py @@ -14,7 +14,7 @@ from homeassistant.helpers.typing import HomeAssistantType from .const import DOMAIN -from .devolo_device import DevoloDeviceEntity +from .devolo_multi_level_switch import DevoloMultiLevelSwitchDeviceEntity _LOGGER = logging.getLogger(__name__) @@ -27,7 +27,7 @@ async def async_setup_entry( for device in hass.data[DOMAIN]["homecontrol"].multi_level_switch_devices: for multi_level_switch in device.multi_level_switch_property: - if device.deviceModelUID in [ + if device.device_model_uid in [ "devolo.model.Thermostat:Valve", "devolo.model.Room:Thermostat", ]: @@ -42,29 +42,13 @@ async def async_setup_entry( async_add_entities(entities, False) -class DevoloClimateDeviceEntity(DevoloDeviceEntity, ClimateEntity): +class DevoloClimateDeviceEntity(DevoloMultiLevelSwitchDeviceEntity, ClimateEntity): """Representation of a climate/thermostat device within devolo Home Control.""" - def __init__(self, homecontrol, device_instance, element_uid): - """Initialize a devolo climate/thermostat device.""" - super().__init__( - homecontrol=homecontrol, - device_instance=device_instance, - element_uid=element_uid, - name=device_instance.item_name, - sync=self._sync, - ) - - self._multi_level_switch_property = ( - device_instance.multi_level_switch_property.get(element_uid) - ) - - self._temperature = self._multi_level_switch_property.value - @property def current_temperature(self) -> Optional[float]: """Return the current temperature.""" - return self._temperature + return self._value @property def hvac_mode(self) -> str: @@ -104,13 +88,3 @@ def temperature_unit(self) -> str: def set_temperature(self, **kwargs): """Set new target temperature.""" self._multi_level_switch_property.set(kwargs[ATTR_TEMPERATURE]) - - def _sync(self, message=None): - """Update the climate entity triggered by web socket connection.""" - if message[0] == self._unique_id: - self._temperature = message[1] - elif message[0].startswith("hdm"): - self._available = self._device_instance.is_online() - else: - _LOGGER.debug("Not valid message received: %s", message) - self.schedule_update_ha_state() diff --git a/homeassistant/components/devolo_home_control/config_flow.py b/homeassistant/components/devolo_home_control/config_flow.py index 93b2cfc11e5f70..cde55ebb4bfd41 100644 --- a/homeassistant/components/devolo_home_control/config_flow.py +++ b/homeassistant/components/devolo_home_control/config_flow.py @@ -38,8 +38,8 @@ async def async_step_user(self, user_input=None): self.data_schema = { vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str, - vol.Required(CONF_MYDEVOLO): str, - vol.Required(CONF_HOMECONTROL): str, + vol.Required(CONF_MYDEVOLO, default=DEFAULT_MYDEVOLO): str, + vol.Required(CONF_HOMECONTROL, default=DEFAULT_MPRM): str, } if user_input is None: return self._show_form(user_input) @@ -53,15 +53,15 @@ async def async_step_user(self, user_input=None): mydevolo.password = password if self.show_advanced_options: mydevolo.url = user_input[CONF_MYDEVOLO] - mydevolo.mprm = user_input[CONF_HOMECONTROL] + mprm = user_input[CONF_HOMECONTROL] else: mydevolo.url = DEFAULT_MYDEVOLO - mydevolo.mprm = DEFAULT_MPRM + mprm = DEFAULT_MPRM credentials_valid = await self.hass.async_add_executor_job( mydevolo.credentials_valid ) if not credentials_valid: - return self._show_form({"base": "invalid_credentials"}) + return self._show_form({"base": "invalid_auth"}) _LOGGER.debug("Credentials valid") gateway_ids = await self.hass.async_add_executor_job(mydevolo.get_gateway_ids) await self.async_set_unique_id(gateway_ids[0]) @@ -73,7 +73,7 @@ async def async_step_user(self, user_input=None): CONF_PASSWORD: password, CONF_USERNAME: user, CONF_MYDEVOLO: mydevolo.url, - CONF_HOMECONTROL: mydevolo.mprm, + CONF_HOMECONTROL: mprm, }, ) diff --git a/homeassistant/components/devolo_home_control/cover.py b/homeassistant/components/devolo_home_control/cover.py index b93713cc7005b4..21e555e3122891 100644 --- a/homeassistant/components/devolo_home_control/cover.py +++ b/homeassistant/components/devolo_home_control/cover.py @@ -12,7 +12,7 @@ from homeassistant.helpers.typing import HomeAssistantType from .const import DOMAIN -from .devolo_device import DevoloDeviceEntity +from .devolo_multi_level_switch import DevoloMultiLevelSwitchDeviceEntity _LOGGER = logging.getLogger(__name__) @@ -37,29 +37,13 @@ async def async_setup_entry( async_add_entities(entities, False) -class DevoloCoverDeviceEntity(DevoloDeviceEntity, CoverEntity): +class DevoloCoverDeviceEntity(DevoloMultiLevelSwitchDeviceEntity, CoverEntity): """Representation of a cover device within devolo Home Control.""" - def __init__(self, homecontrol, device_instance, element_uid): - """Initialize a devolo blinds device.""" - super().__init__( - homecontrol=homecontrol, - device_instance=device_instance, - element_uid=element_uid, - name=device_instance.item_name, - sync=self._sync, - ) - - self._multi_level_switch_property = ( - device_instance.multi_level_switch_property.get(element_uid) - ) - - self._position = self._multi_level_switch_property.value - @property def current_cover_position(self): """Return the current position. 0 is closed. 100 is open.""" - return self._position + return self._value @property def device_class(self): @@ -69,7 +53,7 @@ def device_class(self): @property def is_closed(self): """Return if the blind is closed or not.""" - return not bool(self._position) + return not bool(self._value) @property def supported_features(self): @@ -87,13 +71,3 @@ def close_cover(self, **kwargs): def set_cover_position(self, **kwargs): """Set the blind to the given position.""" self._multi_level_switch_property.set(kwargs["position"]) - - def _sync(self, message=None): - """Update the binary sensor state.""" - if message[0] == self._unique_id: - self._position = message[1] - elif message[0].startswith("hdm"): - self._available = self._device_instance.is_online() - else: - _LOGGER.debug("Not valid message received: %s", message) - self.schedule_update_ha_state() diff --git a/homeassistant/components/devolo_home_control/devolo_device.py b/homeassistant/components/devolo_home_control/devolo_device.py index 06ddf2175f733b..48f9b59af4a2a4 100644 --- a/homeassistant/components/devolo_home_control/devolo_device.py +++ b/homeassistant/components/devolo_home_control/devolo_device.py @@ -10,14 +10,17 @@ class DevoloDeviceEntity(Entity): - """Representation of a sensor within devolo Home Control.""" + """Abstract representation of a device within devolo Home Control.""" - def __init__(self, homecontrol, device_instance, element_uid, name, sync): + def __init__(self, homecontrol, device_instance, element_uid): """Initialize a devolo device entity.""" self._device_instance = device_instance - self._name = name self._unique_id = element_uid self._homecontrol = homecontrol + self._name = device_instance.settings_property["general_device_settings"].name + self._device_class = None + self._value = None + self._unit = None # This is not doing I/O. It fetches an internal state of the API self._available = device_instance.is_online() @@ -27,13 +30,11 @@ def __init__(self, homecontrol, device_instance, element_uid, name, sync): self._model = device_instance.name self.subscriber = None - self.sync_callback = sync + self.sync_callback = self._sync async def async_added_to_hass(self) -> None: """Call when entity is added to hass.""" - self.subscriber = Subscriber( - self._device_instance.item_name, callback=self.sync_callback - ) + self.subscriber = Subscriber(self._name, callback=self.sync_callback) self._homecontrol.publisher.register( self._device_instance.uid, self.subscriber, self.sync_callback ) @@ -54,7 +55,7 @@ def device_info(self): """Return the device info.""" return { "identifiers": {(DOMAIN, self._device_instance.uid)}, - "name": self._device_instance.item_name, + "name": self._name, "manufacturer": self._brand, "model": self._model, } @@ -73,3 +74,21 @@ def name(self): def available(self) -> bool: """Return the online state.""" return self._available + + def _sync(self, message): + """Update the state.""" + if message[0] == self._unique_id: + self._value = message[1] + else: + self._generic_message(message) + self.schedule_update_ha_state() + + def _generic_message(self, message): + """Handle generic messages.""" + if len(message) == 3 and message[2] == "battery_level": + self._value = message[1] + elif len(message) == 3 and message[2] == "status": + # Maybe the API wants to tell us, that the device went on- or offline. + self._available = self._device_instance.is_online() + else: + _LOGGER.debug("No valid message received: %s", message) diff --git a/homeassistant/components/devolo_home_control/devolo_multi_level_switch.py b/homeassistant/components/devolo_home_control/devolo_multi_level_switch.py index 70629854deac75..8056192340c934 100644 --- a/homeassistant/components/devolo_home_control/devolo_multi_level_switch.py +++ b/homeassistant/components/devolo_home_control/devolo_multi_level_switch.py @@ -15,21 +15,9 @@ def __init__(self, homecontrol, device_instance, element_uid): homecontrol=homecontrol, device_instance=device_instance, element_uid=element_uid, - name=device_instance.item_name, - sync=self._sync, ) self._multi_level_switch_property = device_instance.multi_level_switch_property[ element_uid ] self._value = self._multi_level_switch_property.value - - def _sync(self, message): - """Update the multi level switch state.""" - if message[0] == self._multi_level_switch_property.element_uid: - self._value = message[1] - elif message[0].startswith("hdm"): - self._available = self._device_instance.is_online() - else: - _LOGGER.debug("No valid message received: %s", message) - self.schedule_update_ha_state() diff --git a/homeassistant/components/devolo_home_control/manifest.json b/homeassistant/components/devolo_home_control/manifest.json index bdb3a80fff69e6..359fc76b4f81cc 100644 --- a/homeassistant/components/devolo_home_control/manifest.json +++ b/homeassistant/components/devolo_home_control/manifest.json @@ -2,7 +2,8 @@ "domain": "devolo_home_control", "name": "devolo Home Control", "documentation": "https://www.home-assistant.io/integrations/devolo_home_control", - "requirements": ["devolo-home-control-api==0.13.0"], + "requirements": ["devolo-home-control-api==0.15.0"], + "after_dependencies": ["zeroconf"], "config_flow": true, "codeowners": ["@2Fake", "@Shutgun"], "quality_scale": "silver" diff --git a/homeassistant/components/devolo_home_control/sensor.py b/homeassistant/components/devolo_home_control/sensor.py index 4bb2536dcc2997..d32a2887842830 100644 --- a/homeassistant/components/devolo_home_control/sensor.py +++ b/homeassistant/components/devolo_home_control/sensor.py @@ -2,12 +2,14 @@ import logging from homeassistant.components.sensor import ( + DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_POWER, DEVICE_CLASS_TEMPERATURE, ) from homeassistant.config_entries import ConfigEntry +from homeassistant.const import PERCENTAGE from homeassistant.helpers.typing import HomeAssistantType from .const import DOMAIN @@ -16,6 +18,7 @@ _LOGGER = logging.getLogger(__name__) DEVICE_CLASS_MAPPING = { + "battery": DEVICE_CLASS_BATTERY, "temperature": DEVICE_CLASS_TEMPERATURE, "light": DEVICE_CLASS_ILLUMINANCE, "humidity": DEVICE_CLASS_HUMIDITY, @@ -33,7 +36,7 @@ async def async_setup_entry( for device in hass.data[DOMAIN]["homecontrol"].multi_level_sensor_devices: for multi_level_sensor in device.multi_level_sensor_property: entities.append( - DevoloMultiLevelDeviceEntity( + DevoloGenericMultiLevelDeviceEntity( homecontrol=hass.data[DOMAIN]["homecontrol"], device_instance=device, element_uid=multi_level_sensor, @@ -51,75 +54,83 @@ async def async_setup_entry( consumption=consumption_type, ) ) + if hasattr(device, "battery_level"): + entities.append( + DevoloBatteryEntity( + homecontrol=hass.data[DOMAIN]["homecontrol"], + device_instance=device, + element_uid=f"devolo.BatterySensor:{device.uid}", + ) + ) async_add_entities(entities, False) class DevoloMultiLevelDeviceEntity(DevoloDeviceEntity): - """Representation of a multi level sensor within devolo Home Control.""" + """Abstract representation of a multi level sensor within devolo Home Control.""" + + @property + def device_class(self) -> str: + """Return device class.""" + return self._device_class + + @property + def state(self): + """Return the state of the sensor.""" + return self._value + + @property + def unit_of_measurement(self): + """Return the unit of measurement of this entity.""" + return self._unit + + +class DevoloGenericMultiLevelDeviceEntity(DevoloMultiLevelDeviceEntity): + """Representation of a generic multi level sensor within devolo Home Control.""" def __init__( self, homecontrol, device_instance, element_uid, - multi_level_sensor_property=None, - sync=None, ): """Initialize a devolo multi level sensor.""" - if multi_level_sensor_property is None: - self._multi_level_sensor_property = ( - device_instance.multi_level_sensor_property[element_uid] - ) - else: - self._multi_level_sensor_property = multi_level_sensor_property + self._multi_level_sensor_property = device_instance.multi_level_sensor_property[ + element_uid + ] - self._state = self._multi_level_sensor_property.value + super().__init__( + homecontrol=homecontrol, + device_instance=device_instance, + element_uid=element_uid, + ) self._device_class = DEVICE_CLASS_MAPPING.get( self._multi_level_sensor_property.sensor_type ) - name = device_instance.item_name + self._value = self._multi_level_sensor_property.value + self._unit = self._multi_level_sensor_property.unit if self._device_class is None: - name += f" {self._multi_level_sensor_property.sensor_type}" + self._name += f" {self._multi_level_sensor_property.sensor_type}" - self._unit = self._multi_level_sensor_property.unit + +class DevoloBatteryEntity(DevoloMultiLevelDeviceEntity): + """Representation of a battery entity within devolo Home Control.""" + + def __init__(self, homecontrol, device_instance, element_uid): + """Initialize a battery sensor.""" super().__init__( homecontrol=homecontrol, device_instance=device_instance, element_uid=element_uid, - name=name, - sync=self._sync if sync is None else sync, ) - @property - def device_class(self) -> str: - """Return device class.""" - return self._device_class - - @property - def state(self): - """Return the state of the sensor.""" - return self._state - - @property - def unit_of_measurement(self): - """Return the unit of measurement of this entity.""" - return self._unit + self._device_class = DEVICE_CLASS_MAPPING.get("battery") - def _sync(self, message=None): - """Update the multi level sensor state.""" - if message[0] == self._multi_level_sensor_property.element_uid: - self._state = self._device_instance.multi_level_sensor_property[ - message[0] - ].value - elif message[0].startswith("hdm"): - self._available = self._device_instance.is_online() - else: - _LOGGER.debug("No valid message received: %s", message) - self.schedule_update_ha_state() + self._value = device_instance.battery_level + self._unit = PERCENTAGE class DevoloConsumptionEntity(DevoloMultiLevelDeviceEntity): @@ -127,39 +138,37 @@ class DevoloConsumptionEntity(DevoloMultiLevelDeviceEntity): def __init__(self, homecontrol, device_instance, element_uid, consumption): """Initialize a devolo consumption sensor.""" - self._device_instance = device_instance - self.value = getattr( + super().__init__( + homecontrol=homecontrol, + device_instance=device_instance, + element_uid=element_uid, + ) + + self._sensor_type = consumption + self._device_class = DEVICE_CLASS_MAPPING.get(consumption) + + self._value = getattr( device_instance.consumption_property[element_uid], consumption ) - self.sensor_type = consumption - self.unit = getattr( + self._unit = getattr( device_instance.consumption_property[element_uid], f"{consumption}_unit" ) - self.element_uid = element_uid - super().__init__( - homecontrol, - device_instance, - element_uid, - multi_level_sensor_property=self, - sync=self._sync, - ) + self._name += f" {consumption}" @property def unique_id(self): """Return the unique ID of the entity.""" - return f"{self._unique_id}_{self.sensor_type}" + return f"{self._unique_id}_{self._sensor_type}" - def _sync(self, message=None): + def _sync(self, message): """Update the consumption sensor state.""" - if message[0] == self.element_uid: - self._state = getattr( - self._device_instance.consumption_property[self.element_uid], - self.sensor_type, + if message[0] == self._unique_id: + self._value = getattr( + self._device_instance.consumption_property[self._unique_id], + self._sensor_type, ) - elif message[0].startswith("hdm"): - self._available = self._device_instance.is_online() else: - _LOGGER.debug("No valid message received: %s", message) + self._generic_message(message) self.schedule_update_ha_state() diff --git a/homeassistant/components/devolo_home_control/strings.json b/homeassistant/components/devolo_home_control/strings.json index 620ca4218546bf..8c73831a229bfb 100644 --- a/homeassistant/components/devolo_home_control/strings.json +++ b/homeassistant/components/devolo_home_control/strings.json @@ -2,21 +2,21 @@ "title": "devolo Home Control", "config": { "abort": { - "already_configured": "This Home Control Central Unit is already in use." + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" }, "error": { - "invalid_credentials": "Incorrect user name and/or password." + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" }, "step": { "user": { "data": { - "username": "E-Mail-Address / devolo ID", + "username": "[%key:common::config_flow::data::email%] / devolo ID", "password": "[%key:common::config_flow::data::password%]", - "mydevolo_url": "mydevolo URL", - "home_control_url": "Home Control URL" + "mydevolo_url": "mydevolo [%key:common::config_flow::data::url%]", + "home_control_url": "Home Control [%key:common::config_flow::data::url%]" }, "title": "devolo Home Control" } } } -} \ No newline at end of file +} diff --git a/homeassistant/components/devolo_home_control/switch.py b/homeassistant/components/devolo_home_control/switch.py index 9a7812af7bd80f..bc62dbfe2b9eb6 100644 --- a/homeassistant/components/devolo_home_control/switch.py +++ b/homeassistant/components/devolo_home_control/switch.py @@ -6,6 +6,7 @@ from homeassistant.helpers.typing import HomeAssistantType from .const import DOMAIN +from .devolo_device import DevoloDeviceEntity _LOGGER = logging.getLogger(__name__) @@ -32,26 +33,16 @@ async def async_setup_entry( async_add_entities(entities) -class DevoloSwitch(SwitchEntity): +class DevoloSwitch(DevoloDeviceEntity, SwitchEntity): """Representation of a switch.""" def __init__(self, homecontrol, device_instance, element_uid): """Initialize an devolo Switch.""" - self._device_instance = device_instance - - # Create the unique ID - self._unique_id = element_uid - - self._homecontrol = homecontrol - self._name = self._device_instance.item_name - - # This is not doing I/O. It fetches an internal state of the API - self._available = self._device_instance.is_online() - - # Get the brand and model information - self._brand = self._device_instance.brand - self._model = self._device_instance.name - + super().__init__( + homecontrol=homecontrol, + device_instance=device_instance, + element_uid=element_uid, + ) self._binary_switch_property = self._device_instance.binary_switch_property.get( self._unique_id ) @@ -64,47 +55,6 @@ def __init__(self, homecontrol, device_instance, element_uid): else: self._consumption = None - self.subscriber = None - - async def async_added_to_hass(self): - """Call when entity is added to hass.""" - self.subscriber = Subscriber( - self._device_instance.item_name, callback=self.sync - ) - self._homecontrol.publisher.register( - self._device_instance.uid, self.subscriber, self.sync - ) - - @property - def unique_id(self): - """Return the unique ID of the switch.""" - return self._unique_id - - @property - def device_info(self): - """Return the device info.""" - return { - "identifiers": {(DOMAIN, self._device_instance.uid)}, - "name": self.name, - "manufacturer": self._brand, - "model": self._model, - } - - @property - def device_id(self): - """Return the ID of this switch.""" - return self._unique_id - - @property - def name(self): - """Return the display name of this switch.""" - return self._name - - @property - def should_poll(self): - """Return the polling state.""" - return False - @property def is_on(self): """Return the state.""" @@ -115,11 +65,6 @@ def current_power_w(self): """Return the current consumption.""" return self._consumption - @property - def available(self): - """Return the online state.""" - return self._available - def turn_on(self, **kwargs): """Switch on the device.""" self._is_on = True @@ -130,7 +75,7 @@ def turn_off(self, **kwargs): self._is_on = False self._binary_switch_property.set(state=False) - def sync(self, message=None): + def _sync(self, message): """Update the binary switch state and consumption.""" if message[0].startswith("devolo.BinarySwitch"): self._is_on = self._device_instance.binary_switch_property[message[0]].state @@ -138,22 +83,6 @@ def sync(self, message=None): self._consumption = self._device_instance.consumption_property[ message[0] ].current - elif message[0].startswith("hdm"): - self._available = self._device_instance.is_online() else: - _LOGGER.debug("No valid message received: %s", message) + self._generic_message(message) self.schedule_update_ha_state() - - -class Subscriber: - """Subscriber class for the publisher in mprm websocket class.""" - - def __init__(self, name, callback): - """Initiate the device.""" - self.name = name - self.callback = callback - - def update(self, message): - """Trigger hass to update the device.""" - _LOGGER.debug('%s got message "%s"', self.name, message) - self.callback(message) diff --git a/homeassistant/components/devolo_home_control/translations/ca.json b/homeassistant/components/devolo_home_control/translations/ca.json index af0b4eb105ac49..593c50980144ab 100644 --- a/homeassistant/components/devolo_home_control/translations/ca.json +++ b/homeassistant/components/devolo_home_control/translations/ca.json @@ -1,9 +1,10 @@ { "config": { "abort": { - "already_configured": "Aquesta Central Unit de Home Control ja est\u00e0 en \u00fas." + "already_configured": "El compte ja ha estat configurat" }, "error": { + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "invalid_credentials": "Nom d'usuari i/o contrasenya incorrectes." }, "step": { diff --git a/homeassistant/components/devolo_home_control/translations/en.json b/homeassistant/components/devolo_home_control/translations/en.json index ae888d37f4a7b8..e5c9f84cab0274 100644 --- a/homeassistant/components/devolo_home_control/translations/en.json +++ b/homeassistant/components/devolo_home_control/translations/en.json @@ -1,9 +1,10 @@ { "config": { "abort": { - "already_configured": "This Home Control Central Unit is already in use." + "already_configured": "Account is already configured" }, "error": { + "invalid_auth": "Invalid authentication", "invalid_credentials": "Incorrect user name and/or password." }, "step": { @@ -12,7 +13,7 @@ "home_control_url": "Home Control URL", "mydevolo_url": "mydevolo URL", "password": "Password", - "username": "E-Mail-Address / devolo ID" + "username": "Email / devolo ID" }, "title": "devolo Home Control" } diff --git a/homeassistant/components/devolo_home_control/translations/es.json b/homeassistant/components/devolo_home_control/translations/es.json index 9eb7f04f923ac2..d511b5d28036e3 100644 --- a/homeassistant/components/devolo_home_control/translations/es.json +++ b/homeassistant/components/devolo_home_control/translations/es.json @@ -4,6 +4,7 @@ "already_configured": "Esta Unidad Central de Home Control ya est\u00e1 en uso." }, "error": { + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", "invalid_credentials": "Nombre de usuario y/o contrase\u00f1a incorrectos." }, "step": { diff --git a/homeassistant/components/devolo_home_control/translations/et.json b/homeassistant/components/devolo_home_control/translations/et.json new file mode 100644 index 00000000000000..2227b7442a79c6 --- /dev/null +++ b/homeassistant/components/devolo_home_control/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Tuvastamise viga" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/devolo_home_control/translations/fr.json b/homeassistant/components/devolo_home_control/translations/fr.json index 1f9b6f4e28f34d..acc2b171b638cf 100644 --- a/homeassistant/components/devolo_home_control/translations/fr.json +++ b/homeassistant/components/devolo_home_control/translations/fr.json @@ -4,6 +4,7 @@ "already_configured": "Cette unit\u00e9 centrale Home Control est d\u00e9j\u00e0 utilis\u00e9e." }, "error": { + "invalid_auth": "Authentification invalide", "invalid_credentials": "Nom d''utilisateur et/ou mot de passe incorrect." }, "step": { diff --git a/homeassistant/components/devolo_home_control/translations/it.json b/homeassistant/components/devolo_home_control/translations/it.json index 89bc030a7c84c9..8788e3e307afdc 100644 --- a/homeassistant/components/devolo_home_control/translations/it.json +++ b/homeassistant/components/devolo_home_control/translations/it.json @@ -1,9 +1,10 @@ { "config": { "abort": { - "already_configured": "Questo Home Control Central \u00e8 gi\u00e0 in uso." + "already_configured": "L'account \u00e8 gi\u00e0 configurato" }, "error": { + "invalid_auth": "Autenticazione non valida", "invalid_credentials": "Nome utente e/o password non corretti." }, "step": { @@ -12,7 +13,7 @@ "home_control_url": "URL di Home Control", "mydevolo_url": "URL di mydevolo", "password": "Password", - "username": "Indirizzo e-mail / devolo ID" + "username": "E-mail / devolo ID" }, "title": "devolo Home Control" } diff --git a/homeassistant/components/devolo_home_control/translations/no.json b/homeassistant/components/devolo_home_control/translations/no.json index 19c8d2653c1f55..2ec2b0744f24b5 100644 --- a/homeassistant/components/devolo_home_control/translations/no.json +++ b/homeassistant/components/devolo_home_control/translations/no.json @@ -1,9 +1,10 @@ { "config": { "abort": { - "already_configured": "Denne hjemmekontrollsentralenheten er allerede i bruk." + "already_configured": "Kontoen er allerede konfigurert" }, "error": { + "invalid_auth": "Ugyldig godkjenning", "invalid_credentials": "Ugyldig brukernavn og/eller passord" }, "step": { @@ -12,7 +13,7 @@ "home_control_url": "Home Control URL", "mydevolo_url": "mydevolo URL", "password": "Passord", - "username": "E-postadresse / devolo-ID" + "username": "E-post / devolo ID" }, "title": "" } diff --git a/homeassistant/components/devolo_home_control/translations/ru.json b/homeassistant/components/devolo_home_control/translations/ru.json index b65a45d72275ad..71cff94add67c7 100644 --- a/homeassistant/components/devolo_home_control/translations/ru.json +++ b/homeassistant/components/devolo_home_control/translations/ru.json @@ -1,16 +1,17 @@ { "config": { "abort": { - "already_configured": "\u042d\u0442\u043e\u0442 Home Control Central Unit \u0443\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f." + "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": { + "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "invalid_credentials": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043b\u043e\u0433\u0438\u043d \u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c." }, "step": { "user": { "data": { - "home_control_url": "Home Control URL", - "mydevolo_url": "mydevolo URL", + "home_control_url": "Home Control URL-\u0430\u0434\u0440\u0435\u0441", + "mydevolo_url": "mydevolo URL-\u0430\u0434\u0440\u0435\u0441", "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 / devolo ID" }, diff --git a/homeassistant/components/devolo_home_control/translations/zh-Hant.json b/homeassistant/components/devolo_home_control/translations/zh-Hant.json index ef2407fe5bd9c5..3336d67464a16a 100644 --- a/homeassistant/components/devolo_home_control/translations/zh-Hant.json +++ b/homeassistant/components/devolo_home_control/translations/zh-Hant.json @@ -1,18 +1,19 @@ { "config": { "abort": { - "already_configured": "\u6b64 Home Control Central \u5df2\u7d93\u65bc\u4f7f\u7528\u4e2d\u3002" + "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "invalid_credentials": "\u4f7f\u7528\u8005\u540d\u7a31\u53ca/\u6216\u5bc6\u78bc\u932f\u8aa4\u3002" }, "step": { "user": { "data": { - "home_control_url": "Home Control URL", - "mydevolo_url": "mydevolo URL", + "home_control_url": "Home Control \u7db2\u5740", + "mydevolo_url": "mydevolo \u7db2\u5740", "password": "\u5bc6\u78bc", - "username": "E-Mail \u4f4d\u5740 / devolo ID" + "username": "\u96fb\u5b50\u90f5\u4ef6 / devolo ID" }, "title": "devolo Home Control" } diff --git a/homeassistant/components/dexcom/config_flow.py b/homeassistant/components/dexcom/config_flow.py index e2ce9e491863d2..26c0c92ca78061 100644 --- a/homeassistant/components/dexcom/config_flow.py +++ b/homeassistant/components/dexcom/config_flow.py @@ -46,9 +46,9 @@ async def async_step_user(self, user_input=None): user_input[CONF_SERVER] == SERVER_OUS, ) except SessionError: - errors["base"] = "session_error" + errors["base"] = "cannot_connect" except AccountError: - errors["base"] = "account_error" + errors["base"] = "invalid_auth" except Exception: # pylint: disable=broad-except errors["base"] = "unknown" diff --git a/homeassistant/components/dexcom/strings.json b/homeassistant/components/dexcom/strings.json index 7b9932ec4de6af..bb29f814ee8619 100644 --- a/homeassistant/components/dexcom/strings.json +++ b/homeassistant/components/dexcom/strings.json @@ -12,11 +12,13 @@ } }, "error": { - "session_error": "[%key:common::config_flow::error::cannot_connect%]", - "account_error": "[%key:common::config_flow::error::invalid_auth%]", + "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_account": "[%key:common::config_flow::abort::already_configured_account%]" } + "abort": { + "already_configured_account": "[%key:common::config_flow::abort::already_configured_account%]" + } }, "options": { "step": { diff --git a/homeassistant/components/dexcom/translations/ca.json b/homeassistant/components/dexcom/translations/ca.json index 7be3b94993f8bb..b92d6b7ab06e55 100644 --- a/homeassistant/components/dexcom/translations/ca.json +++ b/homeassistant/components/dexcom/translations/ca.json @@ -5,6 +5,8 @@ }, "error": { "account_error": "Autenticaci\u00f3 inv\u00e0lida", + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "session_error": "Ha fallat la connexi\u00f3", "unknown": "Error inesperat" }, diff --git a/homeassistant/components/dexcom/translations/de.json b/homeassistant/components/dexcom/translations/de.json index af843097539ac9..31ded6b7f9e018 100644 --- a/homeassistant/components/dexcom/translations/de.json +++ b/homeassistant/components/dexcom/translations/de.json @@ -10,7 +10,8 @@ "step": { "user": { "data": { - "password": "Passwort" + "password": "Passwort", + "username": "Benutzername" } } } diff --git a/homeassistant/components/dexcom/translations/el.json b/homeassistant/components/dexcom/translations/el.json new file mode 100644 index 00000000000000..b30be708065e42 --- /dev/null +++ b/homeassistant/components/dexcom/translations/el.json @@ -0,0 +1,8 @@ +{ + "config": { + "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\u03b7 \u03b1\u03c5\u03b8\u03b5\u03bd\u03c4\u03b9\u03ba\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dexcom/translations/en.json b/homeassistant/components/dexcom/translations/en.json index 2d2f6aa0834b88..92ca2d11859450 100644 --- a/homeassistant/components/dexcom/translations/en.json +++ b/homeassistant/components/dexcom/translations/en.json @@ -5,6 +5,8 @@ }, "error": { "account_error": "Invalid authentication", + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", "session_error": "Failed to connect", "unknown": "Unexpected error" }, diff --git a/homeassistant/components/dexcom/translations/es.json b/homeassistant/components/dexcom/translations/es.json index 146e77632514f3..95f80f558b3d28 100644 --- a/homeassistant/components/dexcom/translations/es.json +++ b/homeassistant/components/dexcom/translations/es.json @@ -5,6 +5,8 @@ }, "error": { "account_error": "Autenticaci\u00f3n no v\u00e1lida", + "cannot_connect": "No se pudo conectar", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", "session_error": "No se pudo conectar", "unknown": "Error inesperado" }, diff --git a/homeassistant/components/dexcom/translations/et.json b/homeassistant/components/dexcom/translations/et.json new file mode 100644 index 00000000000000..1632ade0fe242c --- /dev/null +++ b/homeassistant/components/dexcom/translations/et.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Tuvastamise viga" + } + }, + "options": { + "step": { + "init": { + "data": { + "unit_of_measurement": "M\u00f5\u00f5t\u00fchik" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dexcom/translations/fr.json b/homeassistant/components/dexcom/translations/fr.json index 3825a88d322c64..5ad342c22ffc94 100644 --- a/homeassistant/components/dexcom/translations/fr.json +++ b/homeassistant/components/dexcom/translations/fr.json @@ -5,6 +5,8 @@ }, "error": { "account_error": "L'authentification ne'st pas valide", + "cannot_connect": "\u00c9chec de connexion", + "invalid_auth": "Authentification invalide", "session_error": "\u00c9chec de connexion", "unknown": "Erreur inattendue" }, diff --git a/homeassistant/components/dexcom/translations/hu.json b/homeassistant/components/dexcom/translations/hu.json new file mode 100644 index 00000000000000..9f2fd5d72f4336 --- /dev/null +++ b/homeassistant/components/dexcom/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured_account": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dexcom/translations/it.json b/homeassistant/components/dexcom/translations/it.json index e9695304c92322..0042c585cd1a1e 100644 --- a/homeassistant/components/dexcom/translations/it.json +++ b/homeassistant/components/dexcom/translations/it.json @@ -5,6 +5,8 @@ }, "error": { "account_error": "Autenticazione non valida", + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", "session_error": "Impossibile connettersi", "unknown": "Errore imprevisto" }, diff --git a/homeassistant/components/dexcom/translations/nl.json b/homeassistant/components/dexcom/translations/nl.json new file mode 100644 index 00000000000000..4d00f0bfc74883 --- /dev/null +++ b/homeassistant/components/dexcom/translations/nl.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Gebruikersnaam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dexcom/translations/no.json b/homeassistant/components/dexcom/translations/no.json index 61ad015b5a49c5..4df58b25f1c899 100644 --- a/homeassistant/components/dexcom/translations/no.json +++ b/homeassistant/components/dexcom/translations/no.json @@ -5,6 +5,8 @@ }, "error": { "account_error": "Ugyldig godkjenning", + "cannot_connect": "Tilkobling mislyktes.", + "invalid_auth": "Ugyldig godkjenning", "session_error": "Tilkobling mislyktes.", "unknown": "Uventet feil" }, diff --git a/homeassistant/components/dexcom/translations/pl.json b/homeassistant/components/dexcom/translations/pl.json index 24ae7a17370fbc..be45bb9f6136fb 100644 --- a/homeassistant/components/dexcom/translations/pl.json +++ b/homeassistant/components/dexcom/translations/pl.json @@ -1,12 +1,13 @@ { "config": { "abort": { - "already_configured_account": "Konto jest ju\u017c skonfigurowane." + "already_configured_account": "Konto jest ju\u017c skonfigurowane" }, "error": { - "account_error": "Niepoprawne uwierzytelnienie.", - "session_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "account_error": "Niepoprawne uwierzytelnienie", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "session_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "user": { diff --git a/homeassistant/components/dexcom/translations/ru.json b/homeassistant/components/dexcom/translations/ru.json index 01bd9a3f0b3376..69b79638100ef7 100644 --- a/homeassistant/components/dexcom/translations/ru.json +++ b/homeassistant/components/dexcom/translations/ru.json @@ -5,6 +5,8 @@ }, "error": { "account_error": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "session_error": "\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." }, diff --git a/homeassistant/components/dexcom/translations/zh-Hant.json b/homeassistant/components/dexcom/translations/zh-Hant.json index f056b92b2de1b9..656e082f7c4a08 100644 --- a/homeassistant/components/dexcom/translations/zh-Hant.json +++ b/homeassistant/components/dexcom/translations/zh-Hant.json @@ -5,6 +5,8 @@ }, "error": { "account_error": "\u9a57\u8b49\u78bc\u7121\u6548", + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "session_error": "\u9023\u7dda\u5931\u6557", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, diff --git a/homeassistant/components/dialogflow/strings.json b/homeassistant/components/dialogflow/strings.json index d1a691dc92bf6b..f17491a75285bd 100644 --- a/homeassistant/components/dialogflow/strings.json +++ b/homeassistant/components/dialogflow/strings.json @@ -7,7 +7,7 @@ } }, "abort": { - "one_instance_allowed": "Only a single instance is necessary.", + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive Dialogflow messages." }, "create_entry": { diff --git a/homeassistant/components/dialogflow/translations/ca.json b/homeassistant/components/dialogflow/translations/ca.json index 17dd38ffb203f8..bc62fe1f555c14 100644 --- a/homeassistant/components/dialogflow/translations/ca.json +++ b/homeassistant/components/dialogflow/translations/ca.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "La inst\u00e0ncia de Home Assistant ha de ser accessible des d'Internet per rebre missatges de Dialogflow.", - "one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia." + "one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia.", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "create_entry": { "default": "Per enviar esdeveniments a Home Assistant, haur\u00e0s de configurar la [integraci\u00f3 webhook de Dialogflow]({dialogflow_url}). \n\n Completa la seg\u00fcent informaci\u00f3: \n\n- URL: `{webhook_url}` \n- M\u00e8tode: POST \n- Tipus de contingut: application/json\n\nConsulta la [documentaci\u00f3]({docs_url}) per a m\u00e9s detalls." diff --git a/homeassistant/components/dialogflow/translations/el.json b/homeassistant/components/dialogflow/translations/el.json new file mode 100644 index 00000000000000..aecb2ee553fa42 --- /dev/null +++ b/homeassistant/components/dialogflow/translations/el.json @@ -0,0 +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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dialogflow/translations/en.json b/homeassistant/components/dialogflow/translations/en.json index cc9eda6a96803e..f78843fd5034ea 100644 --- a/homeassistant/components/dialogflow/translations/en.json +++ b/homeassistant/components/dialogflow/translations/en.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive Dialogflow messages.", - "one_instance_allowed": "Only a single instance is necessary." + "one_instance_allowed": "Only a single instance is necessary.", + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "create_entry": { "default": "To send events to Home Assistant, you will need to setup [webhook integration of Dialogflow]({dialogflow_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nSee [the documentation]({docs_url}) for further details." diff --git a/homeassistant/components/dialogflow/translations/es.json b/homeassistant/components/dialogflow/translations/es.json index f71313484994e1..3d6f9c440e8f81 100644 --- a/homeassistant/components/dialogflow/translations/es.json +++ b/homeassistant/components/dialogflow/translations/es.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Tu instancia de Home Assistant debe ser accesible desde Internet para recibir mensajes de Dialogflow.", - "one_instance_allowed": "S\u00f3lo se necesita una instancia." + "one_instance_allowed": "S\u00f3lo se necesita una instancia.", + "single_instance_allowed": "Ya est\u00e1 configurado. S\u00f3lo es posible una \u00fanica configuraci\u00f3n." }, "create_entry": { "default": "Para enviar eventos a Home Assistant, necesitas configurar [Integraci\u00f3n de flujos de dialogo de webhook]({dialogflow_url}).\n\nRellena la siguiente informaci\u00f3n:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nVer [Documentaci\u00f3n]({docs_url}) para m\u00e1s detalles." diff --git a/homeassistant/components/dialogflow/translations/et.json b/homeassistant/components/dialogflow/translations/et.json new file mode 100644 index 00000000000000..fafda5352ec1b4 --- /dev/null +++ b/homeassistant/components/dialogflow/translations/et.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "not_internet_accessible": "Dialogflow teavituste vastuv\u00f5tmiseks peab teie Home Assistant olema Interneti kaudu ligip\u00e4\u00e4setav.", + "one_instance_allowed": "Vaja on ainult \u00fchte \u00fcksust.", + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + }, + "create_entry": { + "default": "S\u00fcndmuste saatmiseks Home Assistantile peate seadistama [Dialogflow'i veebihaagii integreerimine] ( {dialogflow_url} ). \n\n Sisestage j\u00e4rgmine teave: \n\n - URL: \" {webhook_url} \" \n - Meetod: POST \n - Sisu t\u00fc\u00fcp: rakendus / json \n\n Lisateavet leiate [dokumentatsioonist] ( {docs_url} )." + }, + "step": { + "user": { + "description": "Kas oled kindel, et soovid seadistada Dialogflow?", + "title": "Seadistage Dialogflow veebihaak" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dialogflow/translations/fr.json b/homeassistant/components/dialogflow/translations/fr.json index 81de11edbd5bb6..85ace2c378e643 100644 --- a/homeassistant/components/dialogflow/translations/fr.json +++ b/homeassistant/components/dialogflow/translations/fr.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Votre instance de Home Assistant doit \u00eatre accessible depuis Internet pour recevoir les messages Dialogflow.", - "one_instance_allowed": "Une seule instance est n\u00e9cessaire." + "one_instance_allowed": "Une seule instance est n\u00e9cessaire.", + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." }, "create_entry": { "default": "Pour envoyer des \u00e9v\u00e9nements \u00e0 Home Assistant, vous devez configurer [Webhooks avec Dialogflow] ( {dialogflow_url} ). \n\n Remplissez les informations suivantes: \n\n - URL: ` {webhook_url} ` \n - M\u00e9thode: POST \n - Type de contenu: application / json \n\n Voir [la documentation] ( {docs_url} ) pour savoir comment configurer les automatisations pour g\u00e9rer les donn\u00e9es entrantes." diff --git a/homeassistant/components/dialogflow/translations/it.json b/homeassistant/components/dialogflow/translations/it.json index 2c933d09a523bd..a318633d4e4eaa 100644 --- a/homeassistant/components/dialogflow/translations/it.json +++ b/homeassistant/components/dialogflow/translations/it.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "La tua istanza di Home Assistant deve essere accessibile da Internet per ricevere messaggi da Dialogflow.", - "one_instance_allowed": "\u00c8 necessaria una sola istanza." + "one_instance_allowed": "\u00c8 necessaria una sola istanza.", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "create_entry": { "default": "Per inviare eventi a Home Assistant, dovrai configurare [l'integrazione webhook di Dialogflow]({dialogflow_url})\n\n Compila le seguenti informazioni: \n\n - URL: `{webhook_url}` \n - Method: POST \n - Content Type: application/json \n\n Vedi [la documentazione]({docs_url}) for ulteriori dettagli." diff --git a/homeassistant/components/dialogflow/translations/lb.json b/homeassistant/components/dialogflow/translations/lb.json index a10adda6702526..d64fa8bd2e7ff0 100644 --- a/homeassistant/components/dialogflow/translations/lb.json +++ b/homeassistant/components/dialogflow/translations/lb.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "\u00c4r Home Assistant Instanz muss iwwert Internet accessibel si fir Dialogflow Noriichten z'empf\u00e4nken.", - "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg." + "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg.", + "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech." }, "create_entry": { "default": "Fir Evenementer un Home Assistant ze sch\u00e9cken, muss [Webhook Integratioun mat Dialogflow]({dialogflow_url}) ageriicht ginn.\n\nF\u00ebllt folgend Informatiounen aus:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\nLiest [Dokumentatioun]({docs_url}) fir w\u00e9ider D\u00e9tailer." diff --git a/homeassistant/components/dialogflow/translations/no.json b/homeassistant/components/dialogflow/translations/no.json index 8abbc221b7cc19..91d4d53f13e05a 100644 --- a/homeassistant/components/dialogflow/translations/no.json +++ b/homeassistant/components/dialogflow/translations/no.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Din Home Assistant forekomst m\u00e5 v\u00e6re tilgjengelig fra internett for \u00e5 kunne motta Dialogflow meldinger.", - "one_instance_allowed": "Kun en forekomst er n\u00f8dvendig." + "one_instance_allowed": "Kun en forekomst er n\u00f8dvendig.", + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "create_entry": { "default": "For \u00e5 sende hendelser til Home Assistant, m\u00e5 du sette opp [webhook integrasjon av Dialogflow]({dialogflow_url}). \n\nFyll ut f\u00f8lgende informasjon: \n\n- URL: `{webhook_url}` \n- Metode: POST\n- Innholdstype: application/json\n\nSe [dokumentasjonen]({docs_url}) for ytterligere detaljer." diff --git a/homeassistant/components/dialogflow/translations/ru.json b/homeassistant/components/dialogflow/translations/ru.json index 15ecb63392e0a3..7e0d0087ec76a4 100644 --- a/homeassistant/components/dialogflow/translations/ru.json +++ b/homeassistant/components/dialogflow/translations/ru.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 Dialogflow.", - "one_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "one_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e." }, "create_entry": { "default": "\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 Home Assistant \u0412\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Webhook \u0434\u043b\u044f [Dialogflow]({dialogflow_url}).\n\n\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\n\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]({docs_url}) \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." diff --git a/homeassistant/components/dialogflow/translations/zh-Hant.json b/homeassistant/components/dialogflow/translations/zh-Hant.json index bf850322480550..c03cfbfd72d69f 100644 --- a/homeassistant/components/dialogflow/translations/zh-Hant.json +++ b/homeassistant/components/dialogflow/translations/zh-Hant.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Home Assistant \u8a2d\u5099\u5fc5\u9808\u80fd\u5920\u7531\u7db2\u969b\u7db2\u8def\u5b58\u53d6\uff0c\u65b9\u80fd\u63a5\u53d7 Dialogflow \u8a0a\u606f\u3002", - "one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u7269\u4ef6\u5373\u53ef\u3002" + "one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u7269\u4ef6\u5373\u53ef\u3002", + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002" }, "create_entry": { "default": "\u6b32\u50b3\u9001\u4e8b\u4ef6\u81f3 Home Assistant\uff0c\u5c07\u9700\u8a2d\u5b9a [webhook integration of Dialogflow]({dialogflow_url})\u3002\n\n\u8acb\u586b\u5beb\u4e0b\u5217\u8cc7\u8a0a\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\n\u8acb\u53c3\u95b1 [\u6587\u4ef6]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u8a73\u7d30\u8cc7\u6599\u3002" diff --git a/homeassistant/components/digital_ocean/binary_sensor.py b/homeassistant/components/digital_ocean/binary_sensor.py index d076dae9210f2d..eb1345df45c011 100644 --- a/homeassistant/components/digital_ocean/binary_sensor.py +++ b/homeassistant/components/digital_ocean/binary_sensor.py @@ -3,7 +3,11 @@ import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_MOVING, + PLATFORM_SCHEMA, + BinarySensorEntity, +) from homeassistant.const import ATTR_ATTRIBUTION import homeassistant.helpers.config_validation as cv @@ -25,7 +29,6 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = "Droplet" -DEFAULT_DEVICE_CLASS = "moving" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( {vol.Required(CONF_DROPLETS): vol.All(cv.ensure_list, [cv.string])} ) @@ -73,7 +76,7 @@ def is_on(self): @property def device_class(self): """Return the class of this sensor.""" - return DEFAULT_DEVICE_CLASS + return DEVICE_CLASS_MOVING @property def device_state_attributes(self): diff --git a/homeassistant/components/digitalloggers/switch.py b/homeassistant/components/digitalloggers/switch.py index 7448b9fbcf3847..f86f6d7f1aa553 100644 --- a/homeassistant/components/digitalloggers/switch.py +++ b/homeassistant/components/digitalloggers/switch.py @@ -95,11 +95,6 @@ def is_on(self): """Return true if relay is on.""" return self._state - @property - def should_poll(self): - """Return the polling state.""" - return True - def turn_on(self, **kwargs): """Instruct the relay to turn on.""" self._outlet.on() diff --git a/homeassistant/components/directv/media_player.py b/homeassistant/components/directv/media_player.py index 127fb5c04b26b7..dfd88ca885b5ff 100644 --- a/homeassistant/components/directv/media_player.py +++ b/homeassistant/components/directv/media_player.py @@ -126,14 +126,14 @@ async def async_update(self): @property def device_state_attributes(self): """Return device specific state attributes.""" - attributes = {} - if not self._is_standby: - attributes[ATTR_MEDIA_CURRENTLY_RECORDING] = self.media_currently_recording - attributes[ATTR_MEDIA_RATING] = self.media_rating - attributes[ATTR_MEDIA_RECORDED] = self.media_recorded - attributes[ATTR_MEDIA_START_TIME] = self.media_start_time - - return attributes + if self._is_standby: + return {} + return { + ATTR_MEDIA_CURRENTLY_RECORDING: self.media_currently_recording, + ATTR_MEDIA_RATING: self.media_rating, + ATTR_MEDIA_RECORDED: self.media_recorded, + ATTR_MEDIA_START_TIME: self.media_start_time, + } @property def name(self): diff --git a/homeassistant/components/directv/strings.json b/homeassistant/components/directv/strings.json index 7a07185978beaf..9e30a366cc07f9 100644 --- a/homeassistant/components/directv/strings.json +++ b/homeassistant/components/directv/strings.json @@ -17,7 +17,7 @@ }, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", - "unknown": "Unexpected error" + "unknown": "[%key:common::config_flow::error::unknown%]" } } } diff --git a/homeassistant/components/directv/translations/pl.json b/homeassistant/components/directv/translations/pl.json index bec0198ca704b0..db0dc7ea0a42d8 100644 --- a/homeassistant/components/directv/translations/pl.json +++ b/homeassistant/components/directv/translations/pl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia." + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, "flow_title": "DirecTV: {name}", "step": { diff --git a/homeassistant/components/dlink/switch.py b/homeassistant/components/dlink/switch.py index 4fd21f200dd062..c173c879ad1808 100644 --- a/homeassistant/components/dlink/switch.py +++ b/homeassistant/components/dlink/switch.py @@ -13,7 +13,6 @@ CONF_NAME, CONF_PASSWORD, CONF_USERNAME, - STATE_UNKNOWN, TEMP_CELSIUS, ) import homeassistant.helpers.config_validation as cv @@ -146,14 +145,14 @@ def update(self): _LOGGER.warning("Waiting %s s to retry", retry_seconds) return - _state = STATE_UNKNOWN + _state = "unknown" try: self._last_tried = dt_util.now() _state = self.smartplug.state except urllib.error.HTTPError: _LOGGER.error("D-Link connection problem") - if _state == STATE_UNKNOWN: + if _state == "unknown": self._n_tried += 1 self.available = False _LOGGER.warning("Failed to connect to D-Link switch") diff --git a/homeassistant/components/doods/image_processing.py b/homeassistant/components/doods/image_processing.py index 4130f67ec138d2..f4180ffcffa5d1 100644 --- a/homeassistant/components/doods/image_processing.py +++ b/homeassistant/components/doods/image_processing.py @@ -1,6 +1,7 @@ """Support for the DOODS service.""" import io import logging +import os import time from PIL import Image, ImageDraw, UnidentifiedImageError @@ -26,6 +27,7 @@ ATTR_MATCHES = "matches" ATTR_SUMMARY = "summary" ATTR_TOTAL_MATCHES = "total_matches" +ATTR_PROCESS_TIME = "process_time" CONF_URL = "url" CONF_AUTH_KEY = "auth_key" @@ -203,6 +205,7 @@ def __init__(self, hass, camera_entity, name, doods, detector, config): self._matches = {} self._total_matches = 0 self._last_image = None + self._process_time = 0 @property def camera_entity(self): @@ -228,6 +231,7 @@ def device_state_attributes(self): label: len(values) for label, values in self._matches.items() }, ATTR_TOTAL_MATCHES: self._total_matches, + ATTR_PROCESS_TIME: self._process_time, } def _save_image(self, image, matches, paths): @@ -270,6 +274,8 @@ def _save_image(self, image, matches, paths): for path in paths: _LOGGER.info("Saving results image to %s", path) + if not os.path.exists(os.path.dirname(path)): + os.makedirs(os.path.dirname(path), exist_ok=True) img.save(path) def process_image(self, image): @@ -308,6 +314,7 @@ def process_image(self, image): _LOGGER.error(response["error"]) self._matches = matches self._total_matches = total_matches + self._process_time = time.monotonic() - start return for detection in response["detections"]: @@ -380,3 +387,4 @@ def process_image(self, image): self._matches = matches self._total_matches = total_matches + self._process_time = time.monotonic() - start diff --git a/homeassistant/components/doorbird/__init__.py b/homeassistant/components/doorbird/__init__.py index 43ab0c96153e05..1f7e02e8569b25 100644 --- a/homeassistant/components/doorbird/__init__.py +++ b/homeassistant/components/doorbird/__init__.py @@ -19,6 +19,7 @@ CONF_TOKEN, CONF_USERNAME, HTTP_OK, + HTTP_UNAUTHORIZED, ) from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady @@ -127,7 +128,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): status = await hass.async_add_executor_job(device.ready) info = await hass.async_add_executor_job(device.info) except urllib.error.HTTPError as err: - if err.code == 401: + if err.code == HTTP_UNAUTHORIZED: _LOGGER.error( "Authorization rejected by DoorBird for %s@%s", username, device_ip ) @@ -357,7 +358,9 @@ async def get(self, request, event): device = get_doorstation_by_token(hass, token) if device is None: - return web.Response(status=401, text="Invalid token provided.") + return web.Response( + status=HTTP_UNAUTHORIZED, text="Invalid token provided." + ) if device: event_data = device.get_event_data() diff --git a/homeassistant/components/doorbird/config_flow.py b/homeassistant/components/doorbird/config_flow.py index 07b753da6ee8a3..8e3f661254df15 100644 --- a/homeassistant/components/doorbird/config_flow.py +++ b/homeassistant/components/doorbird/config_flow.py @@ -7,7 +7,13 @@ import voluptuous as vol from homeassistant import config_entries, core, exceptions -from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import ( + CONF_HOST, + CONF_NAME, + CONF_PASSWORD, + CONF_USERNAME, + HTTP_UNAUTHORIZED, +) from homeassistant.core import callback from homeassistant.util.network import is_link_local @@ -39,7 +45,7 @@ async def validate_input(hass: core.HomeAssistant, data): status = await hass.async_add_executor_job(device.ready) info = await hass.async_add_executor_job(device.info) except urllib.error.HTTPError as err: - if err.code == 401: + if err.code == HTTP_UNAUTHORIZED: raise InvalidAuth from err raise CannotConnect from err except OSError as err: diff --git a/homeassistant/components/doorbird/strings.json b/homeassistant/components/doorbird/strings.json index 6f1f866053e231..076124cf0954e3 100644 --- a/homeassistant/components/doorbird/strings.json +++ b/homeassistant/components/doorbird/strings.json @@ -22,7 +22,7 @@ } }, "abort": { - "already_configured": "This DoorBird is already configured", + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "link_local_address": "Link local addresses are not supported", "not_doorbird_device": "This device is not a DoorBird" }, @@ -33,4 +33,4 @@ "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/doorbird/translations/ca.json b/homeassistant/components/doorbird/translations/ca.json index 1639b471d4d864..2adc6227ab4a93 100644 --- a/homeassistant/components/doorbird/translations/ca.json +++ b/homeassistant/components/doorbird/translations/ca.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Aquest dispositiu DoorBird ja est\u00e0 configurat", + "already_configured": "El dispositiu ja est\u00e0 configurat", "link_local_address": "L'enlla\u00e7 amb adreces locals no est\u00e0 perm\u00e8s", "not_doorbird_device": "Aquest dispositiu no \u00e9s DoorBird" }, diff --git a/homeassistant/components/doorbird/translations/en.json b/homeassistant/components/doorbird/translations/en.json index adf3127ffa0669..8ddf95f377380f 100644 --- a/homeassistant/components/doorbird/translations/en.json +++ b/homeassistant/components/doorbird/translations/en.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "This DoorBird is already configured", + "already_configured": "Device is already configured", "link_local_address": "Link local addresses are not supported", "not_doorbird_device": "This device is not a DoorBird" }, diff --git a/homeassistant/components/doorbird/translations/fr.json b/homeassistant/components/doorbird/translations/fr.json index 304760dbf58e45..fd8bf04d29e32c 100644 --- a/homeassistant/components/doorbird/translations/fr.json +++ b/homeassistant/components/doorbird/translations/fr.json @@ -28,7 +28,8 @@ "init": { "data": { "events": "Liste d'\u00e9v\u00e9nements s\u00e9par\u00e9s par des virgules." - } + }, + "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/it.json b/homeassistant/components/doorbird/translations/it.json index ee9c603fb13ec6..51b45cb79bb273 100644 --- a/homeassistant/components/doorbird/translations/it.json +++ b/homeassistant/components/doorbird/translations/it.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Questo DoorBird \u00e8 gi\u00e0 configurato", + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", "link_local_address": "Gli indirizzi locali di collegamento non sono supportati", "not_doorbird_device": "Questo dispositivo non \u00e8 un DoorBird" }, diff --git a/homeassistant/components/doorbird/translations/nl.json b/homeassistant/components/doorbird/translations/nl.json index 85180df8b4a1d0..2bf97d687ab851 100644 --- a/homeassistant/components/doorbird/translations/nl.json +++ b/homeassistant/components/doorbird/translations/nl.json @@ -13,7 +13,8 @@ "user": { "data": { "host": "Host (IP-adres)", - "name": "Apparaatnaam" + "name": "Apparaatnaam", + "username": "Gebruikersnaam" }, "title": "Maak verbinding met de DoorBird" } diff --git a/homeassistant/components/doorbird/translations/no.json b/homeassistant/components/doorbird/translations/no.json index 4929e58c61fe52..8b48beef43e068 100644 --- a/homeassistant/components/doorbird/translations/no.json +++ b/homeassistant/components/doorbird/translations/no.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Denne DoorBird er allerede konfigurert", + "already_configured": "Enheten er allerede konfigurert", "link_local_address": "Linking av lokale adresser st\u00f8ttes ikke", "not_doorbird_device": "Denne enheten er ikke en DoorBird" }, diff --git a/homeassistant/components/doorbird/translations/pl.json b/homeassistant/components/doorbird/translations/pl.json index a24febcd94afed..446fd21626a6ad 100644 --- a/homeassistant/components/doorbird/translations/pl.json +++ b/homeassistant/components/doorbird/translations/pl.json @@ -6,9 +6,9 @@ "not_doorbird_device": "To urz\u0105dzenie nie jest urz\u0105dzeniem DoorBird." }, "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", - "invalid_auth": "Niepoprawne uwierzytelnienie.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "flow_title": "DoorBird {name} ({host})", "step": { diff --git a/homeassistant/components/doorbird/translations/ru.json b/homeassistant/components/doorbird/translations/ru.json index 9ca74bd778059f..274b88a8b473f1 100644 --- a/homeassistant/components/doorbird/translations/ru.json +++ b/homeassistant/components/doorbird/translations/ru.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "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.", "link_local_address": "\u0421\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0435 \u0430\u0434\u0440\u0435\u0441\u0430 \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f.", "not_doorbird_device": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435 DoorBird." }, diff --git a/homeassistant/components/doorbird/translations/zh-Hant.json b/homeassistant/components/doorbird/translations/zh-Hant.json index 281a6e54ac2361..a4b3bd2fd8686a 100644 --- a/homeassistant/components/doorbird/translations/zh-Hant.json +++ b/homeassistant/components/doorbird/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u6b64 DoorBird \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "link_local_address": "\u4e0d\u652f\u63f4\u9023\u7d50\u672c\u5730\u7aef\u4f4d\u5740", "not_doorbird_device": "\u6b64\u8a2d\u5099\u4e26\u975e DoorBird" }, diff --git a/homeassistant/components/dsmr/config_flow.py b/homeassistant/components/dsmr/config_flow.py index d0d0304a02a562..724f9393fbfaa1 100644 --- a/homeassistant/components/dsmr/config_flow.py +++ b/homeassistant/components/dsmr/config_flow.py @@ -48,9 +48,9 @@ async def validate_connect(self, hass: core.HomeAssistant) -> bool: """Test if we can validate connection with the device.""" def update_telegram(telegram): - self._telegram = telegram - - transport.close() + if obis_ref.EQUIPMENT_IDENTIFIER in telegram: + self._telegram = telegram + transport.close() if self._host is None: reader_factory = partial( diff --git a/homeassistant/components/dsmr/translations/es.json b/homeassistant/components/dsmr/translations/es.json new file mode 100644 index 00000000000000..e8e23bf8343ee3 --- /dev/null +++ b/homeassistant/components/dsmr/translations/es.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dsmr/translations/fr.json b/homeassistant/components/dsmr/translations/fr.json index c4bc0d48b1a08b..ea382532a71327 100644 --- a/homeassistant/components/dsmr/translations/fr.json +++ b/homeassistant/components/dsmr/translations/fr.json @@ -2,6 +2,10 @@ "config": { "abort": { "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + }, + "step": { + "one": "", + "other": "Autre" } } } \ No newline at end of file diff --git a/homeassistant/components/dsmr/translations/hu.json b/homeassistant/components/dsmr/translations/hu.json new file mode 100644 index 00000000000000..3b2d79a34a77e2 --- /dev/null +++ b/homeassistant/components/dsmr/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dsmr/translations/ko.json b/homeassistant/components/dsmr/translations/ko.json new file mode 100644 index 00000000000000..9c8fbbe80a99cb --- /dev/null +++ b/homeassistant/components/dsmr/translations/ko.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "\uc7a5\uce58\ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dsmr/translations/lb.json b/homeassistant/components/dsmr/translations/lb.json new file mode 100644 index 00000000000000..6469543442e1ff --- /dev/null +++ b/homeassistant/components/dsmr/translations/lb.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Apparat ass scho konfigur\u00e9iert" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dsmr/translations/pl.json b/homeassistant/components/dsmr/translations/pl.json index 815a6f19706e95..637a81a3f87144 100644 --- a/homeassistant/components/dsmr/translations/pl.json +++ b/homeassistant/components/dsmr/translations/pl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" } } } \ No newline at end of file diff --git a/homeassistant/components/dsmr_reader/definitions.py b/homeassistant/components/dsmr_reader/definitions.py index 0ec67bc97fd8f2..5fda67e65a342c 100644 --- a/homeassistant/components/dsmr_reader/definitions.py +++ b/homeassistant/components/dsmr_reader/definitions.py @@ -1,6 +1,7 @@ """Definitions for DSMR Reader sensors added to MQTT.""" from homeassistant.const import ( + CURRENCY_EURO, ELECTRICAL_CURRENT_AMPERE, ENERGY_KILO_WATT_HOUR, VOLT, @@ -166,17 +167,17 @@ def tariff_transform(value): "dsmr/day-consumption/electricity1_cost": { "name": "Low tariff cost", "icon": "mdi:currency-eur", - "unit": "€", + "unit": CURRENCY_EURO, }, "dsmr/day-consumption/electricity2_cost": { "name": "High tariff cost", "icon": "mdi:currency-eur", - "unit": "€", + "unit": CURRENCY_EURO, }, "dsmr/day-consumption/electricity_cost_merged": { "name": "Power total cost", "icon": "mdi:currency-eur", - "unit": "€", + "unit": CURRENCY_EURO, }, "dsmr/day-consumption/gas": { "name": "Gas usage", @@ -186,37 +187,37 @@ def tariff_transform(value): "dsmr/day-consumption/gas_cost": { "name": "Gas cost", "icon": "mdi:currency-eur", - "unit": "€", + "unit": CURRENCY_EURO, }, "dsmr/day-consumption/total_cost": { "name": "Total cost", "icon": "mdi:currency-eur", - "unit": "€", + "unit": CURRENCY_EURO, }, "dsmr/day-consumption/energy_supplier_price_electricity_delivered_1": { "name": "Low tariff delivered price", "icon": "mdi:currency-eur", - "unit": "€", + "unit": CURRENCY_EURO, }, "dsmr/day-consumption/energy_supplier_price_electricity_delivered_2": { "name": "High tariff delivered price", "icon": "mdi:currency-eur", - "unit": "€", + "unit": CURRENCY_EURO, }, "dsmr/day-consumption/energy_supplier_price_electricity_returned_1": { "name": "Low tariff returned price", "icon": "mdi:currency-eur", - "unit": "€", + "unit": CURRENCY_EURO, }, "dsmr/day-consumption/energy_supplier_price_electricity_returned_2": { "name": "High tariff returned price", "icon": "mdi:currency-eur", - "unit": "€", + "unit": CURRENCY_EURO, }, "dsmr/day-consumption/energy_supplier_price_gas": { "name": "Gas price", "icon": "mdi:currency-eur", - "unit": "€", + "unit": CURRENCY_EURO, }, "dsmr/meter-stats/dsmr_version": { "name": "DSMR version", diff --git a/homeassistant/components/dunehd/translations/hu.json b/homeassistant/components/dunehd/translations/hu.json new file mode 100644 index 00000000000000..44b4442dc31c9e --- /dev/null +++ b/homeassistant/components/dunehd/translations/hu.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + }, + "error": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/dunehd/translations/pl.json b/homeassistant/components/dunehd/translations/pl.json index 2e0e2b352ca830..893a71d4b701a6 100644 --- a/homeassistant/components/dunehd/translations/pl.json +++ b/homeassistant/components/dunehd/translations/pl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" }, "error": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "invalid_host": "Nieprawid\u0142owa nazwa hosta lub adres IP." }, "step": { diff --git a/homeassistant/components/dwd_weather_warnings/manifest.json b/homeassistant/components/dwd_weather_warnings/manifest.json index e67fbb08e29227..df4c412cc62842 100644 --- a/homeassistant/components/dwd_weather_warnings/manifest.json +++ b/homeassistant/components/dwd_weather_warnings/manifest.json @@ -1,7 +1,7 @@ { "domain": "dwd_weather_warnings", - "name": "Deutsche Wetter Dienst (DWD) Weather Warnings", + "name": "Deutscher Wetterdienst (DWD) Weather Warnings", "documentation": "https://www.home-assistant.io/integrations/dwd_weather_warnings", "codeowners": ["@runningman84", "@stephan192", "@Hummel95"], - "requirements": ["dwdwfsapi==1.0.2"] + "requirements": ["dwdwfsapi==1.0.3"] } diff --git a/homeassistant/components/dweet/__init__.py b/homeassistant/components/dweet/__init__.py index db985e57a41bdb..c076fc81628bb6 100644 --- a/homeassistant/components/dweet/__init__.py +++ b/homeassistant/components/dweet/__init__.py @@ -6,6 +6,7 @@ import voluptuous as vol from homeassistant.const import ( + ATTR_FRIENDLY_NAME, CONF_NAME, CONF_WHITELIST, EVENT_STATE_CHANGED, @@ -58,7 +59,7 @@ def dweet_event_listener(event): except ValueError: _state = state.state - json_body[state.attributes.get("friendly_name")] = _state + json_body[state.attributes.get(ATTR_FRIENDLY_NAME)] = _state send_data(name, json_body) diff --git a/homeassistant/components/eafm/translations/hu.json b/homeassistant/components/eafm/translations/hu.json new file mode 100644 index 00000000000000..3b2d79a34a77e2 --- /dev/null +++ b/homeassistant/components/eafm/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/eafm/translations/ko.json b/homeassistant/components/eafm/translations/ko.json new file mode 100644 index 00000000000000..4e7bfc9dc9363d --- /dev/null +++ b/homeassistant/components/eafm/translations/ko.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "no_stations": "\ud64d\uc218 \ubaa8\ub2c8\ud130\ub9c1 \uc2a4\ud14c\uc774\uc158\uc774 \uc5c6\uc2b5\ub2c8\ub2e4." + }, + "step": { + "user": { + "data": { + "station": "\uc2a4\ud14c\uc774\uc158" + }, + "description": "\ubaa8\ub2c8\ud130\ub9c1\ud560 \uc2a4\ud14c\uc774\uc158 \uc120\ud0dd", + "title": "\ud64d\uc218 \ubaa8\ub2c8\ud130\ub9c1 \uc2a4\ud14c\uc774\uc158 \ucd94\uc801" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/eafm/translations/pl.json b/homeassistant/components/eafm/translations/pl.json new file mode 100644 index 00000000000000..54f53bf5e32050 --- /dev/null +++ b/homeassistant/components/eafm/translations/pl.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "no_stations": "Nie znaleziono stacji monitorowania powodzi." + }, + "step": { + "user": { + "data": { + "station": "Stacja" + }, + "description": "Wybierz stacj\u0119, kt\u00f3r\u0105 chcesz monitorowa\u0107", + "title": "\u015aled\u017a stacj\u0119 monitorowania powodzi" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ebusd/__init__.py b/homeassistant/components/ebusd/__init__.py index 855e62727b5683..00c40344d6ec28 100644 --- a/homeassistant/components/ebusd/__init__.py +++ b/homeassistant/components/ebusd/__init__.py @@ -1,5 +1,4 @@ """Support for Ebusd daemon for communication with eBUS heating systems.""" -from datetime import timedelta import logging import socket @@ -14,7 +13,6 @@ ) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import load_platform -from homeassistant.util import Throttle from .const import DOMAIN, SENSOR_TYPES @@ -26,8 +24,6 @@ CACHE_TTL = 900 SERVICE_EBUSD_WRITE = "ebusd_write" -MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=15) - def verify_ebusd_config(config): """Verify eBusd config.""" @@ -59,6 +55,7 @@ def verify_ebusd_config(config): def setup(hass, config): """Set up the eBusd component.""" + _LOGGER.debug("Integration setup started") conf = config[DOMAIN] name = conf[CONF_NAME] circuit = conf[CONF_CIRCUIT] @@ -66,7 +63,6 @@ def setup(hass, config): server_address = (conf.get(CONF_HOST), conf.get(CONF_PORT)) try: - _LOGGER.debug("Ebusd integration setup started") ebusdpy.init(server_address) hass.data[DOMAIN] = EbusdData(server_address, circuit) @@ -95,7 +91,6 @@ def __init__(self, address, circuit): self._address = address self.value = {} - @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self, name, stype): """Call the Ebusd API to update the data.""" try: diff --git a/homeassistant/components/ebusd/sensor.py b/homeassistant/components/ebusd/sensor.py index 63f72a89ccd8aa..badb94a6f8541f 100644 --- a/homeassistant/components/ebusd/sensor.py +++ b/homeassistant/components/ebusd/sensor.py @@ -3,6 +3,7 @@ import logging from homeassistant.helpers.entity import Entity +from homeassistant.util import Throttle import homeassistant.util.dt as dt_util from .const import DOMAIN @@ -13,6 +14,7 @@ TIME_FRAME2_END = "time_frame2_end" TIME_FRAME3_BEGIN = "time_frame3_begin" TIME_FRAME3_END = "time_frame3_end" +MIN_TIME_BETWEEN_UPDATES = datetime.timedelta(seconds=15) _LOGGER = logging.getLogger(__name__) @@ -85,6 +87,7 @@ def unit_of_measurement(self): """Return the unit of measurement.""" return self._unit_of_measurement + @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): """Fetch new state data for the sensor.""" try: diff --git a/homeassistant/components/ecobee/binary_sensor.py b/homeassistant/components/ecobee/binary_sensor.py index 64c4b07ed1f0a1..9593fc0e497c2e 100644 --- a/homeassistant/components/ecobee/binary_sensor.py +++ b/homeassistant/components/ecobee/binary_sensor.py @@ -70,7 +70,7 @@ def device_info(self): _LOGGER.error( "Model number for ecobee thermostat %s not recognized. " "Please visit this link and provide the following information: " - "https://github.com/home-assistant/home-assistant/issues/27172 " + "https://github.com/home-assistant/core/issues/27172 " "Unrecognized model number: %s", thermostat["name"], thermostat["modelNumber"], diff --git a/homeassistant/components/ecobee/climate.py b/homeassistant/components/ecobee/climate.py index dcb21ac41b187c..ccfddca4b03015 100644 --- a/homeassistant/components/ecobee/climate.py +++ b/homeassistant/components/ecobee/climate.py @@ -351,7 +351,7 @@ def device_info(self): _LOGGER.error( "Model number for ecobee thermostat %s not recognized. " "Please visit this link and provide the following information: " - "https://github.com/home-assistant/home-assistant/issues/27172 " + "https://github.com/home-assistant/core/issues/27172 " "Unrecognized model number: %s", self.name, self.thermostat["modelNumber"], diff --git a/homeassistant/components/ecobee/config_flow.py b/homeassistant/components/ecobee/config_flow.py index cbe16832a34781..6641181b7e4cd1 100644 --- a/homeassistant/components/ecobee/config_flow.py +++ b/homeassistant/components/ecobee/config_flow.py @@ -29,7 +29,7 @@ async def async_step_user(self, user_input=None): """Handle a flow initiated by the user.""" if self._async_current_entries(): # Config entry already exists, only one allowed. - return self.async_abort(reason="one_instance_only") + return self.async_abort(reason="single_instance_allowed") errors = {} stored_api_key = ( diff --git a/homeassistant/components/ecobee/sensor.py b/homeassistant/components/ecobee/sensor.py index 4351b230538612..cfbaa7a45161b0 100644 --- a/homeassistant/components/ecobee/sensor.py +++ b/homeassistant/components/ecobee/sensor.py @@ -5,7 +5,6 @@ DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, PERCENTAGE, - STATE_UNKNOWN, TEMP_FAHRENHEIT, ) from homeassistant.helpers.entity import Entity @@ -83,7 +82,7 @@ def device_info(self): _LOGGER.error( "Model number for ecobee thermostat %s not recognized. " "Please visit this link and provide the following information: " - "https://github.com/home-assistant/home-assistant/issues/27172 " + "https://github.com/home-assistant/core/issues/27172 " "Unrecognized model number: %s", thermostat["name"], thermostat["modelNumber"], @@ -112,7 +111,7 @@ def state(self): if self._state in [ ECOBEE_STATE_CALIBRATING, ECOBEE_STATE_UNKNOWN, - STATE_UNKNOWN, + "unknown", ]: return None diff --git a/homeassistant/components/ecobee/strings.json b/homeassistant/components/ecobee/strings.json index b80996cb2a31a3..78f0708134c8f6 100644 --- a/homeassistant/components/ecobee/strings.json +++ b/homeassistant/components/ecobee/strings.json @@ -18,7 +18,7 @@ "token_request_failed": "Error requesting tokens from ecobee; please try again." }, "abort": { - "one_instance_only": "This integration currently supports only one ecobee instance." + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/ecobee/translations/ca.json b/homeassistant/components/ecobee/translations/ca.json index b75006483c7e7d..8b20669c76ba9d 100644 --- a/homeassistant/components/ecobee/translations/ca.json +++ b/homeassistant/components/ecobee/translations/ca.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "one_instance_only": "Aquesta integraci\u00f3 nom\u00e9s admet una sola inst\u00e0ncia ecobee." + "one_instance_only": "Aquesta integraci\u00f3 nom\u00e9s admet una sola inst\u00e0ncia ecobee.", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "error": { "pin_request_failed": "Error al sol\u00b7licitar els PIN d'ecobee; verifica que la clau API \u00e9s correcta.", diff --git a/homeassistant/components/ecobee/translations/en.json b/homeassistant/components/ecobee/translations/en.json index a105296f813e0e..024ac774133af7 100644 --- a/homeassistant/components/ecobee/translations/en.json +++ b/homeassistant/components/ecobee/translations/en.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "one_instance_only": "This integration currently supports only one ecobee instance." + "one_instance_only": "This integration currently supports only one ecobee instance.", + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "error": { "pin_request_failed": "Error requesting PIN from ecobee; please verify API key is correct.", diff --git a/homeassistant/components/ecobee/translations/es.json b/homeassistant/components/ecobee/translations/es.json index 26260e38ca7fea..dd6b9526f9b5c2 100644 --- a/homeassistant/components/ecobee/translations/es.json +++ b/homeassistant/components/ecobee/translations/es.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "one_instance_only": "Esta integraci\u00f3n actualmente solo admite una instancia de ecobee." + "one_instance_only": "Esta integraci\u00f3n actualmente solo admite una instancia de ecobee.", + "single_instance_allowed": "Ya est\u00e1 configurado. S\u00f3lo es posible una \u00fanica configuraci\u00f3n." }, "error": { "pin_request_failed": "Error al solicitar el PIN de ecobee; verifique que la clave API sea correcta.", diff --git a/homeassistant/components/ecobee/translations/et.json b/homeassistant/components/ecobee/translations/et.json new file mode 100644 index 00000000000000..e8b5eae41495c6 --- /dev/null +++ b/homeassistant/components/ecobee/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ecobee/translations/it.json b/homeassistant/components/ecobee/translations/it.json index dce66271b9ac85..b243662201082c 100644 --- a/homeassistant/components/ecobee/translations/it.json +++ b/homeassistant/components/ecobee/translations/it.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "one_instance_only": "Questa integrazione supporta attualmente una sola istanza ecobee." + "one_instance_only": "Questa integrazione supporta attualmente una sola istanza ecobee.", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "error": { "pin_request_failed": "Errore durante la richiesta del PIN da ecobee; verificare che la chiave API sia corretta.", diff --git a/homeassistant/components/ecobee/translations/no.json b/homeassistant/components/ecobee/translations/no.json index 048659ac83d190..0c634b39bd9ce6 100644 --- a/homeassistant/components/ecobee/translations/no.json +++ b/homeassistant/components/ecobee/translations/no.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "one_instance_only": "Denne integrasjonen st\u00f8tter forel\u00f8pig bare \u00e9n ecobee-forekomst." + "one_instance_only": "Denne integrasjonen st\u00f8tter forel\u00f8pig bare \u00e9n ecobee-forekomst.", + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "error": { "pin_request_failed": "Feil under foresp\u00f8rsel om PIN-kode fra ecobee. Kontroller at API-n\u00f8kkelen er riktig.", diff --git a/homeassistant/components/ecobee/translations/ru.json b/homeassistant/components/ecobee/translations/ru.json index 37c1f63822c22b..f9c12b5b3a3a70 100644 --- a/homeassistant/components/ecobee/translations/ru.json +++ b/homeassistant/components/ecobee/translations/ru.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "one_instance_only": "\u042d\u0442\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0432 \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0435\u0435 \u0432\u0440\u0435\u043c\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 ecobee." + "one_instance_only": "\u042d\u0442\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u0432 \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0435\u0435 \u0432\u0440\u0435\u043c\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 ecobee.", + "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": { "pin_request_failed": "\u041f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430 \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u0430 PIN-\u043a\u043e\u0434\u0430 \u0443 ecobee; \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u043a\u043b\u044e\u0447\u0430 API.", diff --git a/homeassistant/components/ecobee/weather.py b/homeassistant/components/ecobee/weather.py index 4ea90d27106344..7774a6648a5c19 100644 --- a/homeassistant/components/ecobee/weather.py +++ b/homeassistant/components/ecobee/weather.py @@ -1,5 +1,5 @@ """Support for displaying weather info from Ecobee API.""" -from datetime import datetime +from datetime import timedelta from pyecobee.const import ECOBEE_STATE_UNKNOWN @@ -13,6 +13,7 @@ WeatherEntity, ) from homeassistant.const import TEMP_FAHRENHEIT +from homeassistant.util import dt as dt_util from .const import ( _LOGGER, @@ -73,7 +74,7 @@ def device_info(self): _LOGGER.error( "Model number for ecobee thermostat %s not recognized. " "Please visit this link and provide the following information: " - "https://github.com/home-assistant/home-assistant/issues/27172 " + "https://github.com/home-assistant/core/issues/27172 " "Unrecognized model number: %s", thermostat["name"], thermostat["modelNumber"], @@ -165,10 +166,13 @@ def forecast(self): return None forecasts = [] - for day in range(1, 5): + date = dt_util.utcnow() + for day in range(0, 5): forecast = _process_forecast(self.weather["forecasts"][day]) if forecast is None: continue + forecast[ATTR_FORECAST_TIME] = date.isoformat() + date += timedelta(days=1) forecasts.append(forecast) if forecasts: @@ -186,9 +190,6 @@ def _process_forecast(json): """Process a single ecobee API forecast to return expected values.""" forecast = {} try: - forecast[ATTR_FORECAST_TIME] = datetime.strptime( - json["dateTime"], "%Y-%m-%d %H:%M:%S" - ).isoformat() forecast[ATTR_FORECAST_CONDITION] = ECOBEE_WEATHER_SYMBOL_TO_HASS[ json["weatherSymbol"] ] diff --git a/homeassistant/components/edl21/sensor.py b/homeassistant/components/edl21/sensor.py index 0bc4c21fc2f14c..5eb7542fed9c7d 100644 --- a/homeassistant/components/edl21/sensor.py +++ b/homeassistant/components/edl21/sensor.py @@ -145,7 +145,7 @@ def event(self, message_body) -> None: elif obis not in self._OBIS_BLACKLIST: _LOGGER.warning( "Unhandled sensor %s detected. Please report at " - 'https://github.com/home-assistant/home-assistant/issues?q=is%%3Aissue+label%%3A"integration%%3A+edl21"+', + 'https://github.com/home-assistant/core/issues?q=is%%3Aissue+label%%3A"integration%%3A+edl21"+', obis, ) self._OBIS_BLACKLIST.add(obis) diff --git a/homeassistant/components/egardia/binary_sensor.py b/homeassistant/components/egardia/binary_sensor.py index 4be443a36f44bf..6882171b67f56a 100644 --- a/homeassistant/components/egardia/binary_sensor.py +++ b/homeassistant/components/egardia/binary_sensor.py @@ -1,7 +1,11 @@ """Interfaces with Egardia/Woonveilig alarm control panel.""" import logging -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_MOTION, + DEVICE_CLASS_OPENING, + BinarySensorEntity, +) from homeassistant.const import STATE_OFF, STATE_ON from . import ATTR_DISCOVER_DEVICES, EGARDIA_DEVICE @@ -9,9 +13,9 @@ _LOGGER = logging.getLogger(__name__) EGARDIA_TYPE_TO_DEVICE_CLASS = { - "IR Sensor": "motion", - "Door Contact": "opening", - "IR": "motion", + "IR Sensor": DEVICE_CLASS_MOTION, + "Door Contact": DEVICE_CLASS_OPENING, + "IR": DEVICE_CLASS_MOTION, } diff --git a/homeassistant/components/eight_sleep/sensor.py b/homeassistant/components/eight_sleep/sensor.py index 824ea210d69eaf..43bcb4c2f0930f 100644 --- a/homeassistant/components/eight_sleep/sensor.py +++ b/homeassistant/components/eight_sleep/sensor.py @@ -112,11 +112,11 @@ async def async_update(self): @property def device_state_attributes(self): """Return device state attributes.""" - state_attr = {ATTR_TARGET_HEAT: self._usrobj.target_heating_level} - state_attr[ATTR_ACTIVE_HEAT] = self._usrobj.now_heating - state_attr[ATTR_DURATION_HEAT] = self._usrobj.heating_remaining - - return state_attr + return { + ATTR_TARGET_HEAT: self._usrobj.target_heating_level, + ATTR_ACTIVE_HEAT: self._usrobj.now_heating, + ATTR_DURATION_HEAT: self._usrobj.heating_remaining, + } class EightUserSensor(EightSleepUserEntity): diff --git a/homeassistant/components/elgato/config_flow.py b/homeassistant/components/elgato/config_flow.py index 687310c2c3eb73..624774b9bf42f3 100644 --- a/homeassistant/components/elgato/config_flow.py +++ b/homeassistant/components/elgato/config_flow.py @@ -33,7 +33,7 @@ async def async_step_user( user_input[CONF_HOST], user_input[CONF_PORT] ) except ElgatoError: - return self._show_setup_form({"base": "connection_error"}) + return self._show_setup_form({"base": "cannot_connect"}) # Check if already configured await self.async_set_unique_id(info.serial_number) @@ -53,14 +53,14 @@ async def async_step_zeroconf( ) -> Dict[str, Any]: """Handle zeroconf discovery.""" if user_input is None: - return self.async_abort(reason="connection_error") + return self.async_abort(reason="cannot_connect") try: info = await self._get_elgato_info( user_input[CONF_HOST], user_input[CONF_PORT] ) except ElgatoError: - return self.async_abort(reason="connection_error") + return self.async_abort(reason="cannot_connect") # Check if already configured await self.async_set_unique_id(info.serial_number) @@ -92,7 +92,7 @@ async def async_step_zeroconf_confirm( self.context.get(CONF_HOST), self.context.get(CONF_PORT) ) except ElgatoError: - return self.async_abort(reason="connection_error") + return self.async_abort(reason="cannot_connect") # Check if already configured await self.async_set_unique_id(info.serial_number) diff --git a/homeassistant/components/elgato/strings.json b/homeassistant/components/elgato/strings.json index a00bf027451726..54c5f43a5dafbc 100644 --- a/homeassistant/components/elgato/strings.json +++ b/homeassistant/components/elgato/strings.json @@ -15,11 +15,11 @@ } }, "error": { - "connection_error": "Failed to connect to Elgato Key Light device." + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, "abort": { - "already_configured": "This Elgato Key Light device is already configured.", - "connection_error": "Failed to connect to Elgato Key Light device." + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/elgato/translations/ca.json b/homeassistant/components/elgato/translations/ca.json index 1f02dc1f79fadf..04cf0488dc684c 100644 --- a/homeassistant/components/elgato/translations/ca.json +++ b/homeassistant/components/elgato/translations/ca.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Aquest dispositiu Elgato Key Light ja est\u00e0 configurat.", + "already_configured": "El dispositiu ja est\u00e0 configurat", + "cannot_connect": "Ha fallat la connexi\u00f3", "connection_error": "No s'ha pogut connectar amb el dispositiu Elgato Key Light." }, "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", "connection_error": "No s'ha pogut connectar amb el dispositiu Elgato Key Light." }, "flow_title": "Elgato Key Light: {serial_number}", diff --git a/homeassistant/components/elgato/translations/el.json b/homeassistant/components/elgato/translations/el.json new file mode 100644 index 00000000000000..58012c1e4e38de --- /dev/null +++ b/homeassistant/components/elgato/translations/el.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elgato/translations/en.json b/homeassistant/components/elgato/translations/en.json index fea7945ecf4f2f..607ab996406c5c 100644 --- a/homeassistant/components/elgato/translations/en.json +++ b/homeassistant/components/elgato/translations/en.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "This Elgato Key Light device is already configured.", + "already_configured": "Device is already configured", + "cannot_connect": "Failed to connect", "connection_error": "Failed to connect to Elgato Key Light device." }, "error": { + "cannot_connect": "Failed to connect", "connection_error": "Failed to connect to Elgato Key Light device." }, "flow_title": "Elgato Key Light: {serial_number}", diff --git a/homeassistant/components/elgato/translations/es.json b/homeassistant/components/elgato/translations/es.json index 46ced57ad2d4c0..60d872ef1c116e 100644 --- a/homeassistant/components/elgato/translations/es.json +++ b/homeassistant/components/elgato/translations/es.json @@ -2,9 +2,11 @@ "config": { "abort": { "already_configured": "Este dispositivo Elgato Key Light ya est\u00e1 configurado.", + "cannot_connect": "No se pudo conectar", "connection_error": "No se pudo conectar al dispositivo Elgato Key Light." }, "error": { + "cannot_connect": "No se pudo conectar", "connection_error": "No se pudo conectar al dispositivo Elgato Key Light." }, "flow_title": "Elgato Key Light: {serial_number}", diff --git a/homeassistant/components/elgato/translations/et.json b/homeassistant/components/elgato/translations/et.json new file mode 100644 index 00000000000000..10864fe4d5d758 --- /dev/null +++ b/homeassistant/components/elgato/translations/et.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "cannot_connect": "\u00dchendamine nurjus", + "connection_error": "Elgato Key Light seadmega \u00fchenduse loomine nurjus." + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "connection_error": "Elgato Key Light seadmega \u00fchenduse loomine nurjus." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/elgato/translations/fr.json b/homeassistant/components/elgato/translations/fr.json index 5a8e2bf46ca01b..7e0547166250d2 100644 --- a/homeassistant/components/elgato/translations/fr.json +++ b/homeassistant/components/elgato/translations/fr.json @@ -2,9 +2,11 @@ "config": { "abort": { "already_configured": "Cet appareil Elgato Key Light est d\u00e9j\u00e0 configur\u00e9.", + "cannot_connect": "\u00c9chec de connexion", "connection_error": "Impossible de se connecter au p\u00e9riph\u00e9rique Elgato Key Light." }, "error": { + "cannot_connect": "\u00c9chec de connexion", "connection_error": "Impossible de se connecter au p\u00e9riph\u00e9rique Elgato Key Light." }, "flow_title": "Elgato Key Light: {serial_number}", diff --git a/homeassistant/components/elgato/translations/it.json b/homeassistant/components/elgato/translations/it.json index 3985bcf6f591b1..2f1f2130a0bca6 100644 --- a/homeassistant/components/elgato/translations/it.json +++ b/homeassistant/components/elgato/translations/it.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Questo dispositivo Elgato Key Light \u00e8 gi\u00e0 configurato.", + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "cannot_connect": "Impossibile connettersi", "connection_error": "Impossibile connettersi al dispositivo Elgato Key Light." }, "error": { + "cannot_connect": "Impossibile connettersi", "connection_error": "Impossibile connettersi al dispositivo Elgato Key Light." }, "flow_title": "Elgato Key Light: {serial_number}", diff --git a/homeassistant/components/elgato/translations/no.json b/homeassistant/components/elgato/translations/no.json index bb7e56211de98b..c17b1dfffee6b3 100644 --- a/homeassistant/components/elgato/translations/no.json +++ b/homeassistant/components/elgato/translations/no.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Denne Elgato Key Light-enheten er allerede konfigurert.", + "already_configured": "Enheten er allerede konfigurert", + "cannot_connect": "Tilkobling mislyktes.", "connection_error": "Kunne ikke koble til Elgato Key Light-enheten." }, "error": { + "cannot_connect": "Tilkobling mislyktes.", "connection_error": "Kunne ikke koble til Elgato Key Light-enheten." }, "flow_title": "Elgato Key Light: {serial_number}", diff --git a/homeassistant/components/elgato/translations/pl.json b/homeassistant/components/elgato/translations/pl.json index 263c67a67caee1..ab4cdc4438ee6f 100644 --- a/homeassistant/components/elgato/translations/pl.json +++ b/homeassistant/components/elgato/translations/pl.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia z urz\u0105dzeniem Elgato Key Light." }, "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia z urz\u0105dzeniem Elgato Key Light." }, "flow_title": "Elgato Key Light: {serial_number}", diff --git a/homeassistant/components/elgato/translations/ru.json b/homeassistant/components/elgato/translations/ru.json index 7cd7036b861f47..b790d2fa66ba6e 100644 --- a/homeassistant/components/elgato/translations/ru.json +++ b/homeassistant/components/elgato/translations/ru.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "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.", + "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 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443 Elgato Key Light." }, "error": { + "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 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443 Elgato Key Light." }, "flow_title": "Elgato Key Light: {serial_number}", diff --git a/homeassistant/components/elgato/translations/zh-Hant.json b/homeassistant/components/elgato/translations/zh-Hant.json index 596dc530a2fdf6..2ddd8e6b23e183 100644 --- a/homeassistant/components/elgato/translations/zh-Hant.json +++ b/homeassistant/components/elgato/translations/zh-Hant.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Elgato Key \u7167\u660e\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3002", + "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "cannot_connect": "\u9023\u7dda\u5931\u6557", "connection_error": "Elgato Key \u7167\u660e\u8a2d\u5099\u9023\u7dda\u5931\u6557\u3002" }, "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", "connection_error": "Elgato Key \u7167\u660e\u8a2d\u5099\u9023\u7dda\u5931\u6557\u3002" }, "flow_title": "Elgato Key \u7167\u660e\uff1a{serial_number}", diff --git a/homeassistant/components/elkm1/strings.json b/homeassistant/components/elkm1/strings.json index 223e2ca3fffd0c..bf0da956d445c1 100644 --- a/homeassistant/components/elkm1/strings.json +++ b/homeassistant/components/elkm1/strings.json @@ -15,9 +15,9 @@ } }, "error": { - "cannot_connect": "Failed to connect, please try again", - "invalid_auth": "Invalid authentication", - "unknown": "Unexpected 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": "An ElkM1 with this prefix is already configured", diff --git a/homeassistant/components/elkm1/translations/ca.json b/homeassistant/components/elkm1/translations/ca.json index fd69a52d16da8c..ce766c314ed9aa 100644 --- a/homeassistant/components/elkm1/translations/ca.json +++ b/homeassistant/components/elkm1/translations/ca.json @@ -5,7 +5,7 @@ "already_configured": "Ja hi ha un Elk-M1 configurat amb aquest prefix" }, "error": { - "cannot_connect": "No s'ha pogut connectar, torna-ho a provar", + "cannot_connect": "Ha fallat la connexi\u00f3", "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "unknown": "Error inesperat" }, diff --git a/homeassistant/components/elkm1/translations/en.json b/homeassistant/components/elkm1/translations/en.json index 7fef25d79a6f36..04fd3c189b562f 100644 --- a/homeassistant/components/elkm1/translations/en.json +++ b/homeassistant/components/elkm1/translations/en.json @@ -5,7 +5,7 @@ "already_configured": "An ElkM1 with this prefix is already configured" }, "error": { - "cannot_connect": "Failed to connect, please try again", + "cannot_connect": "Failed to connect", "invalid_auth": "Invalid authentication", "unknown": "Unexpected error" }, diff --git a/homeassistant/components/elkm1/translations/fr.json b/homeassistant/components/elkm1/translations/fr.json index 81265e587b2e41..618299def29208 100644 --- a/homeassistant/components/elkm1/translations/fr.json +++ b/homeassistant/components/elkm1/translations/fr.json @@ -16,8 +16,10 @@ "password": "Mot de passe", "prefix": "Un pr\u00e9fixe unique (laissez vide si vous n'avez qu'un seul ElkM1).", "protocol": "Protocole", + "temperature_unit": "L'unit\u00e9 de temp\u00e9rature utilis\u00e9e par ElkM1.", "username": "Nom d'utilisateur" }, + "description": "La cha\u00eene d'adresse doit \u00eatre au format \u00abadresse [: port]\u00bb pour \u00abs\u00e9curis\u00e9\u00bb et \u00abnon s\u00e9curis\u00e9\u00bb. Exemple: '192.168.1.1'. Le port est facultatif et vaut par d\u00e9faut 2101 pour \u00abnon s\u00e9curis\u00e9\u00bb et 2601 pour \u00abs\u00e9curis\u00e9\u00bb. Pour le protocole s\u00e9rie, l'adresse doit \u00eatre au format \u00abtty [: baud]\u00bb. Exemple: '/ dev / ttyS1'. Le baud est facultatif et par d\u00e9faut \u00e0 115200.", "title": "Se connecter a Elk-M1 Control" } } diff --git a/homeassistant/components/elkm1/translations/it.json b/homeassistant/components/elkm1/translations/it.json index e6eeacaf661b1d..18e53997937f59 100644 --- a/homeassistant/components/elkm1/translations/it.json +++ b/homeassistant/components/elkm1/translations/it.json @@ -5,7 +5,7 @@ "already_configured": "Un ElkM1 con questo prefisso \u00e8 gi\u00e0 configurato" }, "error": { - "cannot_connect": "Impossibile connettersi, si prega di riprovare", + "cannot_connect": "Impossibile connettersi", "invalid_auth": "Autenticazione non valida", "unknown": "Errore imprevisto" }, diff --git a/homeassistant/components/elkm1/translations/no.json b/homeassistant/components/elkm1/translations/no.json index d00c4ab2eef03e..e4b16aa773c1df 100644 --- a/homeassistant/components/elkm1/translations/no.json +++ b/homeassistant/components/elkm1/translations/no.json @@ -5,7 +5,7 @@ "already_configured": "En ElkM1 med dette prefikset er allerede konfigurert" }, "error": { - "cannot_connect": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen", + "cannot_connect": "Tilkobling mislyktes.", "invalid_auth": "Ugyldig godkjenning", "unknown": "Uventet feil" }, diff --git a/homeassistant/components/elkm1/translations/pl.json b/homeassistant/components/elkm1/translations/pl.json index b38c9aaa6d2cf8..c7f21554dee2e7 100644 --- a/homeassistant/components/elkm1/translations/pl.json +++ b/homeassistant/components/elkm1/translations/pl.json @@ -6,8 +6,8 @@ }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "invalid_auth": "Niepoprawne uwierzytelnienie.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "user": { diff --git a/homeassistant/components/elkm1/translations/ru.json b/homeassistant/components/elkm1/translations/ru.json index 94d3a47a8e1aa3..3c84b98b0cade9 100644 --- a/homeassistant/components/elkm1/translations/ru.json +++ b/homeassistant/components/elkm1/translations/ru.json @@ -5,7 +5,7 @@ "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0441 \u044d\u0442\u0438\u043c \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u043e\u043c \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." }, "error": { - "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", + "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "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/elkm1/translations/zh-Hant.json b/homeassistant/components/elkm1/translations/zh-Hant.json index 01c1197f66a973..0a2f1f60faac0b 100644 --- a/homeassistant/components/elkm1/translations/zh-Hant.json +++ b/homeassistant/components/elkm1/translations/zh-Hant.json @@ -5,7 +5,7 @@ "already_configured": "\u4f7f\u7528\u6b64 Prefix \u7684\u4e00\u7d44 ElkM1 \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21", + "cannot_connect": "\u9023\u7dda\u5931\u6557", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, diff --git a/homeassistant/components/emulated_roku/strings.json b/homeassistant/components/emulated_roku/strings.json index a47c1c4799b71d..a47f3f9a2e75a9 100644 --- a/homeassistant/components/emulated_roku/strings.json +++ b/homeassistant/components/emulated_roku/strings.json @@ -9,7 +9,7 @@ "advertise_port": "Advertise port", "host_ip": "Host IP", "listen_port": "Listen port", - "name": "Name", + "name": "[%key:common::config_flow::data::name%]", "upnp_bind_multicast": "Bind multicast (True/False)" }, "title": "Define server configuration" diff --git a/homeassistant/components/emulated_roku/translations/et.json b/homeassistant/components/emulated_roku/translations/et.json index b94548b44af47e..cc4dd04a9ea42c 100644 --- a/homeassistant/components/emulated_roku/translations/et.json +++ b/homeassistant/components/emulated_roku/translations/et.json @@ -4,7 +4,8 @@ "user": { "data": { "host_ip": "", - "name": "Nimi" + "name": "Nimi", + "upnp_bind_multicast": "Seo multicast (jah/ei)" } } } diff --git a/homeassistant/components/enigma2/media_player.py b/homeassistant/components/enigma2/media_player.py index 563b2c5195d76f..a086f5ba27c14b 100644 --- a/homeassistant/components/enigma2/media_player.py +++ b/homeassistant/components/enigma2/media_player.py @@ -253,17 +253,13 @@ def device_state_attributes(self): currservice_begin: is in the format '21:00'. currservice_end: is in the format '21:00'. """ - attributes = {} - if not self.e2_box.in_standby: - attributes[ATTR_MEDIA_CURRENTLY_RECORDING] = self.e2_box.status_info[ - "isRecording" - ] - attributes[ATTR_MEDIA_DESCRIPTION] = self.e2_box.status_info[ + if self.e2_box.in_standby: + return {} + return { + ATTR_MEDIA_CURRENTLY_RECORDING: self.e2_box.status_info["isRecording"], + ATTR_MEDIA_DESCRIPTION: self.e2_box.status_info[ "currservice_fulldescription" - ] - attributes[ATTR_MEDIA_START_TIME] = self.e2_box.status_info[ - "currservice_begin" - ] - attributes[ATTR_MEDIA_END_TIME] = self.e2_box.status_info["currservice_end"] - - return attributes + ], + ATTR_MEDIA_START_TIME: self.e2_box.status_info["currservice_begin"], + ATTR_MEDIA_END_TIME: self.e2_box.status_info["currservice_end"], + } diff --git a/homeassistant/components/enocean/translations/de.json b/homeassistant/components/enocean/translations/de.json index 9664031c000559..eb98e1fb2b95f2 100644 --- a/homeassistant/components/enocean/translations/de.json +++ b/homeassistant/components/enocean/translations/de.json @@ -4,5 +4,6 @@ "single_instance_allowed": "Schon konfiguriert. Nur eine einzige Konfiguration m\u00f6glich." }, "flow_title": "ENOcean-Einrichtung" - } + }, + "title": "EnOcean" } \ No newline at end of file diff --git a/homeassistant/components/environment_canada/camera.py b/homeassistant/components/environment_canada/camera.py index 706308830f6c57..bf35da16fa5f53 100644 --- a/homeassistant/components/environment_canada/camera.py +++ b/homeassistant/components/environment_canada/camera.py @@ -86,9 +86,7 @@ def name(self): @property def device_state_attributes(self): """Return the state attributes of the device.""" - attr = {ATTR_ATTRIBUTION: CONF_ATTRIBUTION, ATTR_UPDATED: self.timestamp} - - return attr + return {ATTR_ATTRIBUTION: CONF_ATTRIBUTION, ATTR_UPDATED: self.timestamp} @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): diff --git a/homeassistant/components/envirophat/sensor.py b/homeassistant/components/envirophat/sensor.py index 4813fd47a9246a..873d9935ee8443 100644 --- a/homeassistant/components/envirophat/sensor.py +++ b/homeassistant/components/envirophat/sensor.py @@ -6,7 +6,13 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_DISPLAY_OPTIONS, CONF_NAME, TEMP_CELSIUS, VOLT +from homeassistant.const import ( + CONF_DISPLAY_OPTIONS, + CONF_NAME, + PRESSURE_HPA, + TEMP_CELSIUS, + VOLT, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle @@ -30,7 +36,7 @@ "magnetometer_y": ["magnetometer_y", " ", "mdi:magnet"], "magnetometer_z": ["magnetometer_z", " ", "mdi:magnet"], "temperature": ["temperature", TEMP_CELSIUS, "mdi:thermometer"], - "pressure": ["pressure", "hPa", "mdi:gauge"], + "pressure": ["pressure", PRESSURE_HPA, "mdi:gauge"], "voltage_0": ["voltage_0", VOLT, "mdi:flash"], "voltage_1": ["voltage_1", VOLT, "mdi:flash"], "voltage_2": ["voltage_2", VOLT, "mdi:flash"], diff --git a/homeassistant/components/epson/media_player.py b/homeassistant/components/epson/media_player.py index df0dcc536b5b35..235caa8d1a393e 100644 --- a/homeassistant/components/epson/media_player.py +++ b/homeassistant/components/epson/media_player.py @@ -235,7 +235,6 @@ async def async_media_previous_track(self): @property def device_state_attributes(self): """Return device specific state attributes.""" - attributes = {} - if self._cmode is not None: - attributes[ATTR_CMODE] = self._cmode - return attributes + if self._cmode is None: + return {} + return {ATTR_CMODE: self._cmode} diff --git a/homeassistant/components/esphome/__init__.py b/homeassistant/components/esphome/__init__.py index f1b22c13bf13ce..c9d07a22ec6c83 100644 --- a/homeassistant/components/esphome/__init__.py +++ b/homeassistant/components/esphome/__init__.py @@ -66,6 +66,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool host = entry.data[CONF_HOST] port = entry.data[CONF_PORT] password = entry.data[CONF_PASSWORD] + device_id = None zeroconf_instance = await zeroconf.async_get_instance(hass) @@ -129,6 +130,15 @@ def async_on_service_call(service: HomeassistantServiceCall) -> None: "Can only generate events under esphome domain! (%s)", host ) return + + # Call native tag scan + if service_name == "tag_scanned": + tag_id = service_data["tag_id"] + hass.async_create_task( + hass.components.tag.async_scan_tag(tag_id, device_id) + ) + return + hass.bus.async_fire(service.service, service_data) else: hass.async_create_task( @@ -166,10 +176,13 @@ def async_on_state_subscription(entity_id: str) -> None: async def on_login() -> None: """Subscribe to states and list entities on successful API login.""" + nonlocal device_id try: entry_data.device_info = await cli.device_info() entry_data.available = True - await _async_setup_device_registry(hass, entry, entry_data.device_info) + device_id = await _async_setup_device_registry( + hass, entry, entry_data.device_info + ) entry_data.async_update_device_state(hass) entity_infos, services = await cli.list_entities_services() @@ -265,7 +278,7 @@ async def _async_setup_device_registry( if device_info.compilation_time: sw_version += f" ({device_info.compilation_time})" device_registry = await dr.async_get_registry(hass) - device_registry.async_get_or_create( + entry = device_registry.async_get_or_create( config_entry_id=entry.entry_id, connections={(dr.CONNECTION_NETWORK_MAC, device_info.mac_address)}, name=device_info.name, @@ -273,6 +286,7 @@ async def _async_setup_device_registry( model=device_info.model, sw_version=sw_version, ) + return entry.id async def _register_service( diff --git a/homeassistant/components/esphome/manifest.json b/homeassistant/components/esphome/manifest.json index c57ff4a55207cf..123c7931e41d34 100644 --- a/homeassistant/components/esphome/manifest.json +++ b/homeassistant/components/esphome/manifest.json @@ -6,7 +6,5 @@ "requirements": ["aioesphomeapi==2.6.3"], "zeroconf": ["_esphomelib._tcp.local."], "codeowners": ["@OttoWinter"], - "after_dependencies": [ - "zeroconf" - ] + "after_dependencies": ["zeroconf", "tag"] } diff --git a/homeassistant/components/esphome/strings.json b/homeassistant/components/esphome/strings.json index af7515245b58e0..c0f6aafa1b2e38 100644 --- a/homeassistant/components/esphome/strings.json +++ b/homeassistant/components/esphome/strings.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "ESP is already configured", - "already_in_progress": "ESP configuration is already in progress" + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]" }, "error": { "resolve_error": "Can't resolve address of the ESP. If this error persists, please set a static IP address: https://esphomelib.com/esphomeyaml/components/wifi.html#manual-ips", @@ -30,4 +30,4 @@ }, "flow_title": "ESPHome: {name}" } -} \ No newline at end of file +} diff --git a/homeassistant/components/esphome/translations/ca.json b/homeassistant/components/esphome/translations/ca.json index 8833749d071db3..ff16b40bc25bc9 100644 --- a/homeassistant/components/esphome/translations/ca.json +++ b/homeassistant/components/esphome/translations/ca.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "ESP ja est\u00e0 configurat", - "already_in_progress": "La configuraci\u00f3 de l'ESP ja est\u00e0 en curs" + "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs" }, "error": { "connection_error": "No s'ha pogut connectar amb ESP. Verifica que l'arxiu YAML cont\u00e9 la l\u00ednia 'api:'.", diff --git a/homeassistant/components/esphome/translations/cs.json b/homeassistant/components/esphome/translations/cs.json index b437d6d79fd281..4d3691e0cbc5d5 100644 --- a/homeassistant/components/esphome/translations/cs.json +++ b/homeassistant/components/esphome/translations/cs.json @@ -1,8 +1,7 @@ { "config": { "abort": { - "already_configured": "Tento ESP uzel je ji\u017e nakonfigurov\u00e1n", - "already_in_progress": "Konfigurace uzlu ESP ji\u017e prob\u00edh\u00e1" + "already_configured": "Tento ESP uzel je ji\u017e nakonfigurov\u00e1n" }, "error": { "connection_error": "Nelze se p\u0159ipojit k ESP. Zkontrolujte, zda va\u0161e YAML konfigurace obsahuje \u0159\u00e1dek 'api:'.", diff --git a/homeassistant/components/esphome/translations/en.json b/homeassistant/components/esphome/translations/en.json index 6386e1f70930bc..b31664cc60401c 100644 --- a/homeassistant/components/esphome/translations/en.json +++ b/homeassistant/components/esphome/translations/en.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "ESP is already configured", - "already_in_progress": "ESP configuration is already in progress" + "already_in_progress": "Configuration flow is already in progress" }, "error": { "connection_error": "Can't connect to ESP. Please make sure your YAML file contains an 'api:' line.", diff --git a/homeassistant/components/esphome/translations/it.json b/homeassistant/components/esphome/translations/it.json index 19ff26493a8a21..feef1bda549684 100644 --- a/homeassistant/components/esphome/translations/it.json +++ b/homeassistant/components/esphome/translations/it.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "ESP \u00e8 gi\u00e0 configurato", - "already_in_progress": "La configurazione ESP \u00e8 gi\u00e0 in corso" + "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso" }, "error": { "connection_error": "Impossibile connettersi ad ESP. Assicurati che il tuo file YAML contenga una riga \"api:\".", diff --git a/homeassistant/components/esphome/translations/no.json b/homeassistant/components/esphome/translations/no.json index 3c2dafff34d48f..cc775d05a979a8 100644 --- a/homeassistant/components/esphome/translations/no.json +++ b/homeassistant/components/esphome/translations/no.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "ESP er allerede konfigurert", - "already_in_progress": "ESP-konfigurasjon p\u00e5g\u00e5r allerede" + "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede" }, "error": { "connection_error": "Kan ikke koble til ESP. Kontroller at YAML filen din inneholder en \"api:\" linje.", diff --git a/homeassistant/components/esphome/translations/ru.json b/homeassistant/components/esphome/translations/ru.json index c03e4619ec92c2..332b4176f48716 100644 --- a/homeassistant/components/esphome/translations/ru.json +++ b/homeassistant/components/esphome/translations/ru.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", - "already_in_progress": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f." + "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." }, "error": { "connection_error": "\u041d\u0435 \u0443\u0434\u0430\u0435\u0442\u0441\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a ESP. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0443\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0412\u0430\u0448 YAML-\u0444\u0430\u0439\u043b \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0441\u0442\u0440\u043e\u043a\u0443 'api:'.", diff --git a/homeassistant/components/esphome/translations/zh-Hant.json b/homeassistant/components/esphome/translations/zh-Hant.json index be25ddd6366364..ce4686508754e0 100644 --- a/homeassistant/components/esphome/translations/zh-Hant.json +++ b/homeassistant/components/esphome/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "ESP \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", - "already_in_progress": "ESP \u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d\u3002" + "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d" }, "error": { "connection_error": "\u7121\u6cd5\u9023\u7dda\u81f3 ESP\uff0c\u8acb\u78ba\u5b9a\u60a8\u7684 YAML \u6a94\u6848\u5305\u542b\u300capi:\u300d\u8a2d\u5b9a\u5217\u3002", diff --git a/homeassistant/components/fan/group.py b/homeassistant/components/fan/group.py new file mode 100644 index 00000000000000..1636054663dc69 --- /dev/null +++ b/homeassistant/components/fan/group.py @@ -0,0 +1,15 @@ +"""Describe group states.""" + + +from homeassistant.components.group import GroupIntegrationRegistry +from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.core import callback +from homeassistant.helpers.typing import HomeAssistantType + + +@callback +def async_describe_on_off_states( + hass: HomeAssistantType, registry: GroupIntegrationRegistry +) -> None: + """Describe group on off states.""" + registry.on_off_states({STATE_ON}, STATE_OFF) diff --git a/homeassistant/components/fan/translations/et.json b/homeassistant/components/fan/translations/et.json index 6652568a0a7b24..2b141351e1ded6 100644 --- a/homeassistant/components/fan/translations/et.json +++ b/homeassistant/components/fan/translations/et.json @@ -1,4 +1,18 @@ { + "device_automation": { + "action_type": { + "turn_off": "L\u00fclita {entity_name} v\u00e4lja", + "turn_on": "L\u00fclita {entity_name} sisse" + }, + "condition_type": { + "is_off": "{entity_name} on v\u00e4lja l\u00fclitatud", + "is_on": "{entity_name} on sisse l\u00fclitatud" + }, + "trigger_type": { + "turned_off": "{entity_name} l\u00fclitus v\u00e4lja", + "turned_on": "{entity_name} l\u00fclitus sisse" + } + }, "state": { "_": { "off": "V\u00e4ljas", diff --git a/homeassistant/components/fan/translations/uk.json b/homeassistant/components/fan/translations/uk.json index 80b64c28c2f9ce..3fd103cd244c56 100644 --- a/homeassistant/components/fan/translations/uk.json +++ b/homeassistant/components/fan/translations/uk.json @@ -1,4 +1,10 @@ { + "device_automation": { + "trigger_type": { + "turned_off": "{entity_name} \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e", + "turned_on": "{entity_name} \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e" + } + }, "state": { "_": { "off": "\u0412\u0438\u043c\u043a\u043d\u0435\u043d\u043e", diff --git a/homeassistant/components/ffmpeg/__init__.py b/homeassistant/components/ffmpeg/__init__.py index f109103a99c9b3..ca752208f1258f 100644 --- a/homeassistant/components/ffmpeg/__init__.py +++ b/homeassistant/components/ffmpeg/__init__.py @@ -7,6 +7,7 @@ from homeassistant.const import ( ATTR_ENTITY_ID, + CONTENT_TYPE_MULTIPART, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, ) @@ -122,9 +123,9 @@ async def async_get_version(self): def ffmpeg_stream_content_type(self): """Return HTTP content type for ffmpeg stream.""" if self._major_version is not None and self._major_version > 3: - return "multipart/x-mixed-replace;boundary=ffmpeg" + return CONTENT_TYPE_MULTIPART.format("ffmpeg") - return "multipart/x-mixed-replace;boundary=ffserver" + return CONTENT_TYPE_MULTIPART.format("ffserver") class FFmpegBase(Entity): diff --git a/homeassistant/components/ffmpeg_motion/binary_sensor.py b/homeassistant/components/ffmpeg_motion/binary_sensor.py index a8842f9c40183c..9b4218c011cffb 100644 --- a/homeassistant/components/ffmpeg_motion/binary_sensor.py +++ b/homeassistant/components/ffmpeg_motion/binary_sensor.py @@ -4,7 +4,11 @@ import haffmpeg.sensor as ffmpeg_sensor import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_MOTION, + PLATFORM_SCHEMA, + BinarySensorEntity, +) from homeassistant.components.ffmpeg import ( CONF_EXTRA_ARGUMENTS, CONF_INITIAL_STATE, @@ -119,4 +123,4 @@ async def _async_start_ffmpeg(self, entity_ids): @property def device_class(self): """Return the class of this sensor, from DEVICE_CLASSES.""" - return "motion" + return DEVICE_CLASS_MOTION diff --git a/homeassistant/components/ffmpeg_noise/binary_sensor.py b/homeassistant/components/ffmpeg_noise/binary_sensor.py index 6ada2bb274896d..387f25afe6e1db 100644 --- a/homeassistant/components/ffmpeg_noise/binary_sensor.py +++ b/homeassistant/components/ffmpeg_noise/binary_sensor.py @@ -4,7 +4,7 @@ import haffmpeg.sensor as ffmpeg_sensor import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA +from homeassistant.components.binary_sensor import DEVICE_CLASS_SOUND, PLATFORM_SCHEMA from homeassistant.components.ffmpeg import ( CONF_EXTRA_ARGUMENTS, CONF_INITIAL_STATE, @@ -84,4 +84,4 @@ async def _async_start_ffmpeg(self, entity_ids): @property def device_class(self): """Return the class of this sensor, from DEVICE_CLASSES.""" - return "sound" + return DEVICE_CLASS_SOUND diff --git a/homeassistant/components/fibaro/binary_sensor.py b/homeassistant/components/fibaro/binary_sensor.py index 251bd1df6a3ed6..21ce22ea8e31c1 100644 --- a/homeassistant/components/fibaro/binary_sensor.py +++ b/homeassistant/components/fibaro/binary_sensor.py @@ -1,7 +1,14 @@ """Support for Fibaro binary sensors.""" import logging -from homeassistant.components.binary_sensor import DOMAIN, BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_DOOR, + DEVICE_CLASS_MOTION, + DEVICE_CLASS_SMOKE, + DEVICE_CLASS_WINDOW, + DOMAIN, + BinarySensorEntity, +) from homeassistant.const import CONF_DEVICE_CLASS, CONF_ICON from . import FIBARO_DEVICES, FibaroDevice @@ -10,11 +17,11 @@ SENSOR_TYPES = { "com.fibaro.floodSensor": ["Flood", "mdi:water", "flood"], - "com.fibaro.motionSensor": ["Motion", "mdi:run", "motion"], - "com.fibaro.doorSensor": ["Door", "mdi:window-open", "door"], - "com.fibaro.windowSensor": ["Window", "mdi:window-open", "window"], - "com.fibaro.smokeSensor": ["Smoke", "mdi:smoking", "smoke"], - "com.fibaro.FGMS001": ["Motion", "mdi:run", "motion"], + "com.fibaro.motionSensor": ["Motion", "mdi:run", DEVICE_CLASS_MOTION], + "com.fibaro.doorSensor": ["Door", "mdi:window-open", DEVICE_CLASS_DOOR], + "com.fibaro.windowSensor": ["Window", "mdi:window-open", DEVICE_CLASS_WINDOW], + "com.fibaro.smokeSensor": ["Smoke", "mdi:smoking", DEVICE_CLASS_SMOKE], + "com.fibaro.FGMS001": ["Motion", "mdi:run", DEVICE_CLASS_MOTION], "com.fibaro.heatDetector": ["Heat", "mdi:fire", "heat"], } diff --git a/homeassistant/components/fibaro/sensor.py b/homeassistant/components/fibaro/sensor.py index e9e7265f917bfe..3c8129700227e4 100644 --- a/homeassistant/components/fibaro/sensor.py +++ b/homeassistant/components/fibaro/sensor.py @@ -7,6 +7,7 @@ DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, + LIGHT_LUX, PERCENTAGE, TEMP_CELSIUS, TEMP_FAHRENHEIT, @@ -35,7 +36,7 @@ None, DEVICE_CLASS_HUMIDITY, ], - "com.fibaro.lightSensor": ["Light", "lx", None, DEVICE_CLASS_ILLUMINANCE], + "com.fibaro.lightSensor": ["Light", LIGHT_LUX, None, DEVICE_CLASS_ILLUMINANCE], } _LOGGER = logging.getLogger(__name__) @@ -71,7 +72,7 @@ def __init__(self, fibaro_device): try: if not self._unit: if self.fibaro_device.properties.unit == "lux": - self._unit = "lx" + self._unit = LIGHT_LUX elif self.fibaro_device.properties.unit == "C": self._unit = TEMP_CELSIUS elif self.fibaro_device.properties.unit == "F": diff --git a/homeassistant/components/filesize/sensor.py b/homeassistant/components/filesize/sensor.py index 63042ec629228c..27122a3cb9c568 100644 --- a/homeassistant/components/filesize/sensor.py +++ b/homeassistant/components/filesize/sensor.py @@ -78,12 +78,11 @@ def icon(self): @property def device_state_attributes(self): """Return other details about the sensor state.""" - attr = { + return { "path": self._path, "last_updated": self._last_updated, "bytes": self._size, } - return attr @property def unit_of_measurement(self): diff --git a/homeassistant/components/filter/sensor.py b/homeassistant/components/filter/sensor.py index b0cc08bc94502c..72f4b00c4e098e 100644 --- a/homeassistant/components/filter/sensor.py +++ b/homeassistant/components/filter/sensor.py @@ -319,8 +319,7 @@ def should_poll(self): @property def device_state_attributes(self): """Return the state attributes of the sensor.""" - state_attr = {ATTR_ENTITY_ID: self._entity} - return state_attr + return {ATTR_ENTITY_ID: self._entity} class FilterState: diff --git a/homeassistant/components/fints/sensor.py b/homeassistant/components/fints/sensor.py index d81f353c222f8c..6cd62333a8714b 100644 --- a/homeassistant/components/fints/sensor.py +++ b/homeassistant/components/fints/sensor.py @@ -169,14 +169,6 @@ def __init__(self, client: FinTsClient, account, name: str) -> None: self._balance: float = None self._currency: str = None - @property - def should_poll(self) -> bool: - """Return True. - - Data needs to be polled from the bank servers. - """ - return True - def update(self) -> None: """Get the current balance and currency for the account.""" bank = self._client.client @@ -229,14 +221,6 @@ def __init__(self, client: FinTsClient, account, name: str) -> None: self._holdings = [] self._total: float = None - @property - def should_poll(self) -> bool: - """Return True. - - Data needs to be polled from the bank servers. - """ - return True - def update(self) -> None: """Get the current holdings for the account.""" bank = self._client.client diff --git a/homeassistant/components/firmata/__init__.py b/homeassistant/components/firmata/__init__.py index b64a88cbf5730a..c0394a95a49ac0 100644 --- a/homeassistant/components/firmata/__init__.py +++ b/homeassistant/components/firmata/__init__.py @@ -6,7 +6,17 @@ import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_NAME, EVENT_HOMEASSISTANT_STOP +from homeassistant.const import ( + CONF_BINARY_SENSORS, + CONF_LIGHTS, + CONF_MAXIMUM, + CONF_MINIMUM, + CONF_NAME, + CONF_PIN, + CONF_SENSORS, + CONF_SWITCHES, + EVENT_HOMEASSISTANT_STOP, +) from homeassistant.core import HomeAssistant from homeassistant.helpers import config_validation as cv, device_registry as dr @@ -14,21 +24,22 @@ from .const import ( CONF_ARDUINO_INSTANCE_ID, CONF_ARDUINO_WAIT, - CONF_BINARY_SENSORS, + CONF_DIFFERENTIAL, CONF_INITIAL_STATE, CONF_NEGATE_STATE, - CONF_PIN, CONF_PIN_MODE, + CONF_PLATFORM_MAP, CONF_SAMPLING_INTERVAL, CONF_SERIAL_BAUD_RATE, CONF_SERIAL_PORT, CONF_SLEEP_TUNE, - CONF_SWITCHES, DOMAIN, FIRMATA_MANUFACTURER, + PIN_MODE_ANALOG, PIN_MODE_INPUT, PIN_MODE_OUTPUT, PIN_MODE_PULLUP, + PIN_MODE_PWM, ) _LOGGER = logging.getLogger(__name__) @@ -40,8 +51,8 @@ SWITCH_SCHEMA = vol.Schema( { vol.Required(CONF_NAME): cv.string, + # Both digital and analog pins may be used as digital output vol.Required(CONF_PIN): vol.Any(cv.positive_int, ANALOG_PIN_SCHEMA), - # will be analog mode in future too vol.Required(CONF_PIN_MODE): PIN_MODE_OUTPUT, vol.Optional(CONF_INITIAL_STATE, default=False): cv.boolean, vol.Optional(CONF_NEGATE_STATE, default=False): cv.boolean, @@ -49,17 +60,45 @@ required=True, ) +LIGHT_SCHEMA = vol.Schema( + { + vol.Required(CONF_NAME): cv.string, + # Both digital and analog pins may be used as PWM/analog output + vol.Required(CONF_PIN): vol.Any(cv.positive_int, ANALOG_PIN_SCHEMA), + vol.Required(CONF_PIN_MODE): PIN_MODE_PWM, + vol.Optional(CONF_INITIAL_STATE, default=0): cv.positive_int, + vol.Optional(CONF_MINIMUM, default=0): cv.positive_int, + vol.Optional(CONF_MAXIMUM, default=255): cv.positive_int, + }, + required=True, +) + BINARY_SENSOR_SCHEMA = vol.Schema( { vol.Required(CONF_NAME): cv.string, + # Both digital and analog pins may be used as digital input vol.Required(CONF_PIN): vol.Any(cv.positive_int, ANALOG_PIN_SCHEMA), - # will be analog mode in future too vol.Required(CONF_PIN_MODE): vol.Any(PIN_MODE_INPUT, PIN_MODE_PULLUP), vol.Optional(CONF_NEGATE_STATE, default=False): cv.boolean, }, required=True, ) +SENSOR_SCHEMA = vol.Schema( + { + vol.Required(CONF_NAME): cv.string, + # Currently only analog input sensor is implemented + vol.Required(CONF_PIN): ANALOG_PIN_SCHEMA, + vol.Required(CONF_PIN_MODE): PIN_MODE_ANALOG, + # Default differential is 40 to avoid a flood of messages on initial setup + # in case pin is unplugged. Firmata responds really really fast + vol.Optional(CONF_DIFFERENTIAL, default=40): vol.All( + cv.positive_int, vol.Range(min=1) + ), + }, + required=True, +) + BOARD_CONFIG_SCHEMA = vol.Schema( { vol.Required(CONF_SERIAL_PORT): cv.string, @@ -71,7 +110,9 @@ ), vol.Optional(CONF_SAMPLING_INTERVAL): cv.positive_int, vol.Optional(CONF_SWITCHES): [SWITCH_SCHEMA], + vol.Optional(CONF_LIGHTS): [LIGHT_SCHEMA], vol.Optional(CONF_BINARY_SENSORS): [BINARY_SENSOR_SCHEMA], + vol.Optional(CONF_SENSORS): [SENSOR_SCHEMA], }, required=True, ) @@ -155,14 +196,11 @@ async def handle_shutdown(event) -> None: sw_version=board.firmware_version, ) - if CONF_BINARY_SENSORS in config_entry.data: - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, "binary_sensor") - ) - if CONF_SWITCHES in config_entry.data: - hass.async_create_task( - hass.config_entries.async_forward_entry_setup(config_entry, "switch") - ) + for (conf, platform) in CONF_PLATFORM_MAP.items(): + if conf in config_entry.data: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, platform) + ) return True @@ -173,16 +211,11 @@ async def async_unload_entry( _LOGGER.debug("Closing Firmata board %s", config_entry.data[CONF_NAME]) unload_entries = [] - if CONF_BINARY_SENSORS in config_entry.data: - unload_entries.append( - hass.config_entries.async_forward_entry_unload( - config_entry, "binary_sensor" + for (conf, platform) in CONF_PLATFORM_MAP.items(): + if conf in config_entry.data: + unload_entries.append( + hass.config_entries.async_forward_entry_unload(config_entry, platform) ) - ) - if CONF_SWITCHES in config_entry.data: - unload_entries.append( - hass.config_entries.async_forward_entry_unload(config_entry, "switch") - ) results = [] if unload_entries: results = await asyncio.gather(*unload_entries) diff --git a/homeassistant/components/firmata/binary_sensor.py b/homeassistant/components/firmata/binary_sensor.py index 4576b8dc69eff7..c2708fc27533da 100644 --- a/homeassistant/components/firmata/binary_sensor.py +++ b/homeassistant/components/firmata/binary_sensor.py @@ -4,10 +4,10 @@ from homeassistant.components.binary_sensor import BinarySensorEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_NAME +from homeassistant.const import CONF_NAME, CONF_PIN from homeassistant.core import HomeAssistant -from .const import CONF_NEGATE_STATE, CONF_PIN, CONF_PIN_MODE, DOMAIN +from .const import CONF_NEGATE_STATE, CONF_PIN_MODE, DOMAIN from .entity import FirmataPinEntity from .pin import FirmataBinaryDigitalInput, FirmataPinUsedException @@ -30,7 +30,7 @@ async def async_setup_entry( api.setup() except FirmataPinUsedException: _LOGGER.error( - "Could not setup binary sensor on pin %s since pin already in use.", + "Could not setup binary sensor on pin %s since pin already in use", binary_sensor[CONF_PIN], ) continue diff --git a/homeassistant/components/firmata/board.py b/homeassistant/components/firmata/board.py index bae30014d63236..73e3c004cb91ef 100644 --- a/homeassistant/components/firmata/board.py +++ b/homeassistant/components/firmata/board.py @@ -5,17 +5,23 @@ from pymata_express.pymata_express import PymataExpress from pymata_express.pymata_express_serial import serial -from homeassistant.const import CONF_NAME +from homeassistant.const import ( + CONF_BINARY_SENSORS, + CONF_LIGHTS, + CONF_NAME, + CONF_SENSORS, + CONF_SWITCHES, +) from .const import ( CONF_ARDUINO_INSTANCE_ID, CONF_ARDUINO_WAIT, - CONF_BINARY_SENSORS, CONF_SAMPLING_INTERVAL, CONF_SERIAL_BAUD_RATE, CONF_SERIAL_PORT, CONF_SLEEP_TUNE, - CONF_SWITCHES, + PIN_TYPE_ANALOG, + PIN_TYPE_DIGITAL, ) _LOGGER = logging.getLogger(__name__) @@ -34,13 +40,19 @@ def __init__(self, config: dict): self.protocol_version = None self.name = self.config[CONF_NAME] self.switches = [] + self.lights = [] self.binary_sensors = [] + self.sensors = [] self.used_pins = [] if CONF_SWITCHES in self.config: self.switches = self.config[CONF_SWITCHES] + if CONF_LIGHTS in self.config: + self.lights = self.config[CONF_LIGHTS] if CONF_BINARY_SENSORS in self.config: self.binary_sensors = self.config[CONF_BINARY_SENSORS] + if CONF_SENSORS in self.config: + self.sensors = self.config[CONF_SENSORS] async def async_setup(self, tries=0) -> bool: """Set up a Firmata instance.""" @@ -109,11 +121,11 @@ def mark_pin_used(self, pin: FirmataPinType) -> bool: def get_pin_type(self, pin: FirmataPinType) -> tuple: """Return the type and Firmata location of a pin on the board.""" if isinstance(pin, str): - pin_type = "analog" + pin_type = PIN_TYPE_ANALOG firmata_pin = int(pin[1:]) firmata_pin += self.api.first_analog_pin else: - pin_type = "digital" + pin_type = PIN_TYPE_DIGITAL firmata_pin = pin return (pin_type, firmata_pin) diff --git a/homeassistant/components/firmata/const.py b/homeassistant/components/firmata/const.py index 1ad3cbb8423afa..6259582b5f790b 100644 --- a/homeassistant/components/firmata/const.py +++ b/homeassistant/components/firmata/const.py @@ -1,24 +1,35 @@ """Constants for the Firmata component.""" -import logging - -LOGGER = logging.getLogger(__package__) +from homeassistant.const import ( + CONF_BINARY_SENSORS, + CONF_LIGHTS, + CONF_SENSORS, + CONF_SWITCHES, +) CONF_ARDUINO_INSTANCE_ID = "arduino_instance_id" CONF_ARDUINO_WAIT = "arduino_wait" -CONF_BINARY_SENSORS = "binary_sensors" +CONF_DIFFERENTIAL = "differential" CONF_INITIAL_STATE = "initial" CONF_NAME = "name" CONF_NEGATE_STATE = "negate" -CONF_PIN = "pin" CONF_PINS = "pins" CONF_PIN_MODE = "pin_mode" +PIN_MODE_ANALOG = "ANALOG" PIN_MODE_OUTPUT = "OUTPUT" +PIN_MODE_PWM = "PWM" PIN_MODE_INPUT = "INPUT" PIN_MODE_PULLUP = "PULLUP" +PIN_TYPE_ANALOG = 1 +PIN_TYPE_DIGITAL = 0 CONF_SAMPLING_INTERVAL = "sampling_interval" CONF_SERIAL_BAUD_RATE = "serial_baud_rate" CONF_SERIAL_PORT = "serial_port" CONF_SLEEP_TUNE = "sleep_tune" -CONF_SWITCHES = "switches" DOMAIN = "firmata" FIRMATA_MANUFACTURER = "Firmata" +CONF_PLATFORM_MAP = { + CONF_BINARY_SENSORS: "binary_sensor", + CONF_LIGHTS: "light", + CONF_SENSORS: "sensor", + CONF_SWITCHES: "switch", +} diff --git a/homeassistant/components/firmata/light.py b/homeassistant/components/firmata/light.py new file mode 100644 index 00000000000000..e95b51014136b0 --- /dev/null +++ b/homeassistant/components/firmata/light.py @@ -0,0 +1,98 @@ +"""Support for Firmata light output.""" + +import logging +from typing import Type + +from homeassistant.components.light import ( + ATTR_BRIGHTNESS, + SUPPORT_BRIGHTNESS, + LightEntity, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_MAXIMUM, CONF_MINIMUM, CONF_NAME, CONF_PIN +from homeassistant.core import HomeAssistant + +from .board import FirmataPinType +from .const import CONF_INITIAL_STATE, CONF_PIN_MODE, DOMAIN +from .entity import FirmataPinEntity +from .pin import FirmataBoardPin, FirmataPinUsedException, FirmataPWMOutput + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry( + hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities +) -> None: + """Set up the Firmata lights.""" + new_entities = [] + + board = hass.data[DOMAIN][config_entry.entry_id] + for light in board.lights: + pin = light[CONF_PIN] + pin_mode = light[CONF_PIN_MODE] + initial = light[CONF_INITIAL_STATE] + minimum = light[CONF_MINIMUM] + maximum = light[CONF_MAXIMUM] + api = FirmataPWMOutput(board, pin, pin_mode, initial, minimum, maximum) + try: + api.setup() + except FirmataPinUsedException: + _LOGGER.error( + "Could not setup light on pin %s since pin already in use", + light[CONF_PIN], + ) + continue + name = light[CONF_NAME] + light_entity = FirmataLight(api, config_entry, name, pin) + new_entities.append(light_entity) + + if new_entities: + async_add_entities(new_entities) + + +class FirmataLight(FirmataPinEntity, LightEntity): + """Representation of a light on a Firmata board.""" + + def __init__( + self, + api: Type[FirmataBoardPin], + config_entry: ConfigEntry, + name: str, + pin: FirmataPinType, + ): + """Initialize the light pin entity.""" + super().__init__(api, config_entry, name, pin) + + # Default first turn on to max + self._last_on_level = 255 + + async def async_added_to_hass(self) -> None: + """Set up a light.""" + await self._api.start_pin() + + @property + def is_on(self) -> bool: + """Return true if light is on.""" + return self._api.state > 0 + + @property + def brightness(self) -> int: + """Return the brightness of the light.""" + return self._api.state + + @property + def supported_features(self) -> int: + """Flag supported features.""" + return SUPPORT_BRIGHTNESS + + async def async_turn_on(self, **kwargs) -> None: + """Turn on light.""" + level = kwargs.get(ATTR_BRIGHTNESS, self._last_on_level) + await self._api.set_level(level) + self.async_write_ha_state() + self._last_on_level = level + + async def async_turn_off(self, **kwargs) -> None: + """Turn off light.""" + await self._api.set_level(0) + self.async_write_ha_state() diff --git a/homeassistant/components/firmata/manifest.json b/homeassistant/components/firmata/manifest.json index d894c0a440b8fd..8b283c4f81df99 100644 --- a/homeassistant/components/firmata/manifest.json +++ b/homeassistant/components/firmata/manifest.json @@ -4,7 +4,7 @@ "config_flow": false, "documentation": "https://www.home-assistant.io/integrations/firmata", "requirements": [ - "pymata-express==1.13" + "pymata-express==1.19" ], "codeowners": [ "@DaAwesomeP" diff --git a/homeassistant/components/firmata/pin.py b/homeassistant/components/firmata/pin.py index 644986fb66cea3..3259d76cbb3ffb 100644 --- a/homeassistant/components/firmata/pin.py +++ b/homeassistant/components/firmata/pin.py @@ -2,10 +2,8 @@ import logging from typing import Callable -from homeassistant.core import callback - from .board import FirmataBoard, FirmataPinType -from .const import PIN_MODE_INPUT, PIN_MODE_PULLUP +from .const import PIN_MODE_INPUT, PIN_MODE_PULLUP, PIN_TYPE_ANALOG _LOGGER = logging.getLogger(__name__) @@ -25,6 +23,10 @@ def __init__(self, board: FirmataBoard, pin: FirmataPinType, pin_mode: str): self._pin_type, self._firmata_pin = self.board.get_pin_type(self._pin) self._state = None + if self._pin_type == PIN_TYPE_ANALOG: + # Pymata wants the analog pin formatted as the # from "A#" + self._analog_pin = int(self._pin[1:]) + def setup(self): """Set up a pin and make sure it is valid.""" if not self.board.mark_pin_used(self._pin): @@ -85,6 +87,53 @@ async def turn_off(self) -> None: self._state = False +class FirmataPWMOutput(FirmataBoardPin): + """Representation of a Firmata PWM/analog Output Pin.""" + + def __init__( + self, + board: FirmataBoard, + pin: FirmataPinType, + pin_mode: str, + initial: bool, + minimum: int, + maximum: int, + ): + """Initialize the PWM/analog output pin.""" + self._initial = initial + self._min = minimum + self._max = maximum + self._range = self._max - self._min + super().__init__(board, pin, pin_mode) + + async def start_pin(self) -> None: + """Set initial state on a pin.""" + _LOGGER.debug( + "Setting initial state for PWM/analog output pin %s on board %s to %d", + self._pin, + self.board.name, + self._initial, + ) + api = self.board.api + await api.set_pin_mode_pwm_output(self._firmata_pin) + + new_pin_state = round((self._initial * self._range) / 255) + self._min + await api.pwm_write(self._firmata_pin, new_pin_state) + self._state = self._initial + + @property + def state(self) -> int: + """Return PWM/analog state.""" + return self._state + + async def set_level(self, level: int) -> None: + """Set PWM/analog output.""" + _LOGGER.debug("Setting PWM/analog output on pin %s to %d", self._pin, level) + new_pin_state = round((level * self._range) / 255) + self._min + await self.board.api.pwm_write(self._firmata_pin, new_pin_state) + self._state = level + + class FirmataBinaryDigitalInput(FirmataBoardPin): """Representation of a Firmata Digital Input Pin.""" @@ -99,7 +148,7 @@ def __init__( async def start_pin(self, forward_callback: Callable[[], None]) -> None: """Get initial state and start reporting a pin.""" _LOGGER.debug( - "Starting reporting updates for input pin %s on board %s", + "Starting reporting updates for digital input pin %s on board %s", self._pin, self.board.name, ) @@ -133,7 +182,6 @@ def is_on(self) -> bool: """Return true if digital input is on.""" return self._state - @callback async def latch_callback(self, data: list) -> None: """Update pin state on callback.""" if data[1] != self._firmata_pin: @@ -151,3 +199,65 @@ async def latch_callback(self, data: list) -> None: return self._state = new_state self._forward_callback() + + +class FirmataAnalogInput(FirmataBoardPin): + """Representation of a Firmata Analog Input Pin.""" + + def __init__( + self, board: FirmataBoard, pin: FirmataPinType, pin_mode: str, differential: int + ): + """Initialize the analog input pin.""" + self._differential = differential + self._forward_callback = None + super().__init__(board, pin, pin_mode) + + async def start_pin(self, forward_callback: Callable[[], None]) -> None: + """Get initial state and start reporting a pin.""" + _LOGGER.debug( + "Starting reporting updates for analog input pin %s on board %s", + self._pin, + self.board.name, + ) + self._forward_callback = forward_callback + api = self.board.api + # Only PIN_MODE_ANALOG_INPUT mode is supported as sensor input + await api.set_pin_mode_analog_input( + self._analog_pin, self.latch_callback, self._differential + ) + + self._state = (await self.board.api.analog_read(self._analog_pin))[0] + + self._forward_callback() + + async def stop_pin(self) -> None: + """Stop reporting analog input pin.""" + _LOGGER.debug( + "Stopping reporting updates for analog input pin %s on board %s", + self._pin, + self.board.name, + ) + api = self.board.api + await api.disable_analog_reporting(self._analog_pin) + + @property + def state(self) -> int: + """Return sensor state.""" + return self._state + + async def latch_callback(self, data: list) -> None: + """Update pin state on callback.""" + if data[1] != self._analog_pin: + return + _LOGGER.debug( + "Received latch %d for analog input pin %s on board %s", + data[2], + self._pin, + self.board.name, + ) + new_state = data[2] + if self._state == new_state: + _LOGGER.debug("stopping") + return + self._state = new_state + self._forward_callback() diff --git a/homeassistant/components/firmata/sensor.py b/homeassistant/components/firmata/sensor.py new file mode 100644 index 00000000000000..cb9db1f11e5f55 --- /dev/null +++ b/homeassistant/components/firmata/sensor.py @@ -0,0 +1,59 @@ +"""Support for Firmata sensor input.""" + +import logging + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_NAME, CONF_PIN +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity + +from .const import CONF_DIFFERENTIAL, CONF_PIN_MODE, DOMAIN +from .entity import FirmataPinEntity +from .pin import FirmataAnalogInput, FirmataPinUsedException + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry( + hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities +) -> None: + """Set up the Firmata sensors.""" + new_entities = [] + + board = hass.data[DOMAIN][config_entry.entry_id] + for sensor in board.sensors: + pin = sensor[CONF_PIN] + pin_mode = sensor[CONF_PIN_MODE] + differential = sensor[CONF_DIFFERENTIAL] + api = FirmataAnalogInput(board, pin, pin_mode, differential) + try: + api.setup() + except FirmataPinUsedException: + _LOGGER.error( + "Could not setup sensor on pin %s since pin already in use", + sensor[CONF_PIN], + ) + continue + name = sensor[CONF_NAME] + sensor_entity = FirmataSensor(api, config_entry, name, pin) + new_entities.append(sensor_entity) + + if new_entities: + async_add_entities(new_entities) + + +class FirmataSensor(FirmataPinEntity, Entity): + """Representation of a sensor on a Firmata board.""" + + async def async_added_to_hass(self) -> None: + """Set up a sensor.""" + await self._api.start_pin(self.async_write_ha_state) + + async def async_will_remove_from_hass(self) -> None: + """Stop reporting a sensor.""" + await self._api.stop_pin() + + @property + def state(self) -> int: + """Return sensor state.""" + return self._api.state diff --git a/homeassistant/components/firmata/strings.json b/homeassistant/components/firmata/strings.json index 68d7ae8c041a97..90e62325c2ab13 100644 --- a/homeassistant/components/firmata/strings.json +++ b/homeassistant/components/firmata/strings.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "cannot_connect": "Cannot connect to Firmata board during setup" + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, "step": {} } diff --git a/homeassistant/components/firmata/switch.py b/homeassistant/components/firmata/switch.py index ab67a6d6840101..f1aaf3357c06d9 100644 --- a/homeassistant/components/firmata/switch.py +++ b/homeassistant/components/firmata/switch.py @@ -4,16 +4,10 @@ from homeassistant.components.switch import SwitchEntity from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_NAME +from homeassistant.const import CONF_NAME, CONF_PIN from homeassistant.core import HomeAssistant -from .const import ( - CONF_INITIAL_STATE, - CONF_NEGATE_STATE, - CONF_PIN, - CONF_PIN_MODE, - DOMAIN, -) +from .const import CONF_INITIAL_STATE, CONF_NEGATE_STATE, CONF_PIN_MODE, DOMAIN from .entity import FirmataPinEntity from .pin import FirmataBinaryDigitalOutput, FirmataPinUsedException @@ -37,7 +31,7 @@ async def async_setup_entry( api.setup() except FirmataPinUsedException: _LOGGER.error( - "Could not setup switch on pin %s since pin already in use.", + "Could not setup switch on pin %s since pin already in use", switch[CONF_PIN], ) continue @@ -55,7 +49,6 @@ class FirmataSwitch(FirmataPinEntity, SwitchEntity): async def async_added_to_hass(self) -> None: """Set up a switch.""" await self._api.start_pin() - self.async_write_ha_state() @property def is_on(self) -> bool: @@ -64,12 +57,10 @@ def is_on(self) -> bool: async def async_turn_on(self, **kwargs) -> None: """Turn on switch.""" - _LOGGER.debug("Turning switch %s on", self._name) await self._api.turn_on() self.async_write_ha_state() async def async_turn_off(self, **kwargs) -> None: """Turn off switch.""" - _LOGGER.debug("Turning switch %s off", self._name) await self._api.turn_off() self.async_write_ha_state() diff --git a/homeassistant/components/firmata/translations/ca.json b/homeassistant/components/firmata/translations/ca.json index 29a04d50b3e5fb..cf210681e533f0 100644 --- a/homeassistant/components/firmata/translations/ca.json +++ b/homeassistant/components/firmata/translations/ca.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "cannot_connect": "No s'ha pogut connectar a la placa Frimata durant la configuraci\u00f3" + "cannot_connect": "Ha fallat la connexi\u00f3" } } } \ No newline at end of file diff --git a/homeassistant/components/firmata/translations/en.json b/homeassistant/components/firmata/translations/en.json index 39ea716d9755a9..03668c055d8010 100644 --- a/homeassistant/components/firmata/translations/en.json +++ b/homeassistant/components/firmata/translations/en.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "cannot_connect": "Cannot connect to Firmata board during setup" + "cannot_connect": "Failed to connect" } } } \ No newline at end of file diff --git a/homeassistant/components/firmata/translations/fr.json b/homeassistant/components/firmata/translations/fr.json new file mode 100644 index 00000000000000..a66d58dce876d5 --- /dev/null +++ b/homeassistant/components/firmata/translations/fr.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "cannot_connect": "Impossible de se connecter \u00e0 la carte Firmata pendant la configuration" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/firmata/translations/it.json b/homeassistant/components/firmata/translations/it.json index 79c4a093140a9e..6c2460ab0b2ad8 100644 --- a/homeassistant/components/firmata/translations/it.json +++ b/homeassistant/components/firmata/translations/it.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "cannot_connect": "Impossibile connettersi alla scheda Firmata durante la configurazione" + "cannot_connect": "Impossibile connettersi" }, "step": { "one": "uno", diff --git a/homeassistant/components/firmata/translations/no.json b/homeassistant/components/firmata/translations/no.json index e1e5c8f1ea4d95..9edce0bbb15dda 100644 --- a/homeassistant/components/firmata/translations/no.json +++ b/homeassistant/components/firmata/translations/no.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "cannot_connect": "Kan ikke koble til Firmata Board under installasjonen" + "cannot_connect": "Tilkobling mislyktes." } } } \ No newline at end of file diff --git a/homeassistant/components/firmata/translations/ru.json b/homeassistant/components/firmata/translations/ru.json index 64737774a2d735..6bea2885a8e84e 100644 --- a/homeassistant/components/firmata/translations/ru.json +++ b/homeassistant/components/firmata/translations/ru.json @@ -1,7 +1,7 @@ { "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 \u043a \u043f\u043b\u0430\u0442\u0435 Firmata \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0438." + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." } } } \ No newline at end of file diff --git a/homeassistant/components/firmata/translations/zh-Hant.json b/homeassistant/components/firmata/translations/zh-Hant.json index d86ad56653cda7..d587eaf5ab2a50 100644 --- a/homeassistant/components/firmata/translations/zh-Hant.json +++ b/homeassistant/components/firmata/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "cannot_connect": "\u65bc\u8a2d\u5b9a\u671f\u9593\uff0c\u7121\u6cd5\u9023\u7dda\u81f3 Firmata \u677f" + "cannot_connect": "\u9023\u7dda\u5931\u6557" } } } \ No newline at end of file diff --git a/homeassistant/components/fitbit/sensor.py b/homeassistant/components/fitbit/sensor.py index f0914ab35f0003..f6e3fd90fe5c35 100644 --- a/homeassistant/components/fitbit/sensor.py +++ b/homeassistant/components/fitbit/sensor.py @@ -185,9 +185,7 @@ def fitbit_configuration_callback(callback_data): else: setup_platform(hass, config, add_entities, discovery_info) - start_url = ( - f"{get_url(hass, require_current_request=True)}{FITBIT_AUTH_CALLBACK_PATH}" - ) + start_url = f"{get_url(hass)}{FITBIT_AUTH_CALLBACK_PATH}" description = f"""Please create a Fitbit developer app at https://dev.fitbit.com/apps/new. @@ -222,7 +220,7 @@ def request_oauth_completion(hass): def fitbit_configuration_callback(callback_data): """Handle configuration updates.""" - start_url = f"{get_url(hass, require_current_request=True)}{FITBIT_AUTH_START}" + start_url = f"{get_url(hass)}{FITBIT_AUTH_START}" description = f"Please authorize Fitbit by visiting {start_url}" @@ -314,9 +312,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): config_file.get(CONF_CLIENT_ID), config_file.get(CONF_CLIENT_SECRET) ) - redirect_uri = ( - f"{get_url(hass, require_current_request=True)}{FITBIT_AUTH_CALLBACK_PATH}" - ) + redirect_uri = f"{get_url(hass)}{FITBIT_AUTH_CALLBACK_PATH}" fitbit_auth_start_url, _ = oauth.authorize_token_url( redirect_uri=redirect_uri, diff --git a/homeassistant/components/flick_electric/strings.json b/homeassistant/components/flick_electric/strings.json index d3d1a9d3bdfc70..a37a803fb6a7c1 100644 --- a/homeassistant/components/flick_electric/strings.json +++ b/homeassistant/components/flick_electric/strings.json @@ -13,12 +13,12 @@ } }, "error": { - "cannot_connect": "Failed to connect, please try again", - "invalid_auth": "Invalid authentication", - "unknown": "Unexpected 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": "That account is already configured" + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/flick_electric/translations/ca.json b/homeassistant/components/flick_electric/translations/ca.json index f4eb1bffd459c2..5bc9684d3b2862 100644 --- a/homeassistant/components/flick_electric/translations/ca.json +++ b/homeassistant/components/flick_electric/translations/ca.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Aquest compte ja est\u00e0 configurat" + "already_configured": "El compte ja ha estat configurat" }, "error": { - "cannot_connect": "No s'ha pogut connectar, torna-ho a provar", + "cannot_connect": "Ha fallat la connexi\u00f3", "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "unknown": "Error inesperat" }, diff --git a/homeassistant/components/flick_electric/translations/de.json b/homeassistant/components/flick_electric/translations/de.json index b69e8de8f7c240..d63283fe36b076 100644 --- a/homeassistant/components/flick_electric/translations/de.json +++ b/homeassistant/components/flick_electric/translations/de.json @@ -16,5 +16,6 @@ } } } - } + }, + "title": "Flick Electric" } \ No newline at end of file diff --git a/homeassistant/components/flick_electric/translations/en.json b/homeassistant/components/flick_electric/translations/en.json index eb1ce7f296d902..4a3e4c8fbe6536 100644 --- a/homeassistant/components/flick_electric/translations/en.json +++ b/homeassistant/components/flick_electric/translations/en.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "That account is already configured" + "already_configured": "Account is already configured" }, "error": { - "cannot_connect": "Failed to connect, please try again", + "cannot_connect": "Failed to connect", "invalid_auth": "Invalid authentication", "unknown": "Unexpected error" }, diff --git a/homeassistant/components/flick_electric/translations/it.json b/homeassistant/components/flick_electric/translations/it.json index 4adec6d5f6922f..a955849183a12b 100644 --- a/homeassistant/components/flick_electric/translations/it.json +++ b/homeassistant/components/flick_electric/translations/it.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Questo account \u00e8 gi\u00e0 configurato." + "already_configured": "L'account \u00e8 gi\u00e0 configurato" }, "error": { - "cannot_connect": "Impossibile connettersi, si prega di riprovare", + "cannot_connect": "Impossibile connettersi", "invalid_auth": "Autenticazione non valida", "unknown": "Errore imprevisto" }, diff --git a/homeassistant/components/flick_electric/translations/nl.json b/homeassistant/components/flick_electric/translations/nl.json index 5f7433d97dbb4d..c4901d328c3c6d 100644 --- a/homeassistant/components/flick_electric/translations/nl.json +++ b/homeassistant/components/flick_electric/translations/nl.json @@ -3,7 +3,8 @@ "step": { "user": { "data": { - "password": "Wachtwoord" + "password": "Wachtwoord", + "username": "Gebruikersnaam" } } } diff --git a/homeassistant/components/flick_electric/translations/no.json b/homeassistant/components/flick_electric/translations/no.json index 706ab084901ad8..a0101588e23e20 100644 --- a/homeassistant/components/flick_electric/translations/no.json +++ b/homeassistant/components/flick_electric/translations/no.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Denne kontoen er allerede konfigurert" + "already_configured": "Kontoen er allerede konfigurert" }, "error": { - "cannot_connect": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen", + "cannot_connect": "Tilkobling mislyktes.", "invalid_auth": "Ugyldig godkjenning", "unknown": "Uventet feil" }, diff --git a/homeassistant/components/flick_electric/translations/pl.json b/homeassistant/components/flick_electric/translations/pl.json index fb6554d00d8d0d..19c319a366bed1 100644 --- a/homeassistant/components/flick_electric/translations/pl.json +++ b/homeassistant/components/flick_electric/translations/pl.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "Konto jest ju\u017c skonfigurowane." + "already_configured": "Konto jest ju\u017c skonfigurowane" }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "invalid_auth": "Niepoprawne uwierzytelnienie.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "user": { diff --git a/homeassistant/components/flick_electric/translations/ru.json b/homeassistant/components/flick_electric/translations/ru.json index 1f88596bfc006e..5883c3866968ea 100644 --- a/homeassistant/components/flick_electric/translations/ru.json +++ b/homeassistant/components/flick_electric/translations/ru.json @@ -4,7 +4,7 @@ "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": { - "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", + "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "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/flick_electric/translations/zh-Hant.json b/homeassistant/components/flick_electric/translations/zh-Hant.json index 0f88c07cdaa6f7..9e7b29b489315f 100644 --- a/homeassistant/components/flick_electric/translations/zh-Hant.json +++ b/homeassistant/components/flick_electric/translations/zh-Hant.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "\u6b64\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21", + "cannot_connect": "\u9023\u7dda\u5931\u6557", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, diff --git a/homeassistant/components/flo/binary_sensor.py b/homeassistant/components/flo/binary_sensor.py index 4af91a8ef77237..a8bac49867471b 100644 --- a/homeassistant/components/flo/binary_sensor.py +++ b/homeassistant/components/flo/binary_sensor.py @@ -36,12 +36,13 @@ def is_on(self): @property def device_state_attributes(self): """Return the state attributes.""" - attr = {} - if self._device.has_alerts: - attr["info"] = self._device.pending_info_alerts_count - attr["warning"] = self._device.pending_warning_alerts_count - attr["critical"] = self._device.pending_critical_alerts_count - return attr + if not self._device.has_alerts: + return {} + return { + "info": self._device.pending_info_alerts_count, + "warning": self._device.pending_warning_alerts_count, + "critical": self._device.pending_critical_alerts_count, + } @property def device_class(self): diff --git a/homeassistant/components/flo/translations/de.json b/homeassistant/components/flo/translations/de.json new file mode 100644 index 00000000000000..6f39806287630f --- /dev/null +++ b/homeassistant/components/flo/translations/de.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Passwort", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flo/translations/hu.json b/homeassistant/components/flo/translations/hu.json new file mode 100644 index 00000000000000..3b2d79a34a77e2 --- /dev/null +++ b/homeassistant/components/flo/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flo/translations/ko.json b/homeassistant/components/flo/translations/ko.json new file mode 100644 index 00000000000000..7235d67c278011 --- /dev/null +++ b/homeassistant/components/flo/translations/ko.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + }, + "error": { + "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", + "invalid_auth": "\uc798\ubabb\ub41c \uc778\uc99d", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec" + }, + "step": { + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8" + } + } + } + }, + "title": "flo" +} \ No newline at end of file diff --git a/homeassistant/components/flo/translations/nl.json b/homeassistant/components/flo/translations/nl.json new file mode 100644 index 00000000000000..4d00f0bfc74883 --- /dev/null +++ b/homeassistant/components/flo/translations/nl.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Gebruikersnaam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flo/translations/pl.json b/homeassistant/components/flo/translations/pl.json new file mode 100644 index 00000000000000..561b45d65b1893 --- /dev/null +++ b/homeassistant/components/flo/translations/pl.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "host": "Nazwa hosta lub adres IP", + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + } + } + } + }, + "title": "Flo" +} \ No newline at end of file diff --git a/homeassistant/components/flume/strings.json b/homeassistant/components/flume/strings.json index 039b0d11d724c9..67b4a95d0699fc 100644 --- a/homeassistant/components/flume/strings.json +++ b/homeassistant/components/flume/strings.json @@ -1,9 +1,9 @@ { "config": { "error": { - "unknown": "Unexpected error", - "invalid_auth": "Invalid authentication", - "cannot_connect": "Failed to connect, please try again" + "unknown": "[%key:common::config_flow::error::unknown%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, "step": { "user": { @@ -18,7 +18,7 @@ } }, "abort": { - "already_configured": "This account is already configured" + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/flume/translations/ca.json b/homeassistant/components/flume/translations/ca.json index 71ee4fd2345e1d..e612b29db674aa 100644 --- a/homeassistant/components/flume/translations/ca.json +++ b/homeassistant/components/flume/translations/ca.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Aquest compte ja est\u00e0 configurat" + "already_configured": "El compte ja ha estat configurat" }, "error": { - "cannot_connect": "No s'ha pogut connectar, torna-ho a provar", + "cannot_connect": "Ha fallat la connexi\u00f3", "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "unknown": "Error inesperat" }, diff --git a/homeassistant/components/flume/translations/en.json b/homeassistant/components/flume/translations/en.json index ed24c552d8ffb6..ac7d4335903f6d 100644 --- a/homeassistant/components/flume/translations/en.json +++ b/homeassistant/components/flume/translations/en.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "This account is already configured" + "already_configured": "Account is already configured" }, "error": { - "cannot_connect": "Failed to connect, please try again", + "cannot_connect": "Failed to connect", "invalid_auth": "Invalid authentication", "unknown": "Unexpected error" }, diff --git a/homeassistant/components/flume/translations/fr.json b/homeassistant/components/flume/translations/fr.json index a746d793bc4fd6..fdb7ab8ed9a0c0 100644 --- a/homeassistant/components/flume/translations/fr.json +++ b/homeassistant/components/flume/translations/fr.json @@ -12,6 +12,7 @@ "user": { "data": { "client_id": "ID du client", + "client_secret": "Secret client", "password": "Mot de passe", "username": "Nom d'utilisateur" }, diff --git a/homeassistant/components/flume/translations/it.json b/homeassistant/components/flume/translations/it.json index 6d9974f9481f94..43b82331840b2c 100644 --- a/homeassistant/components/flume/translations/it.json +++ b/homeassistant/components/flume/translations/it.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Questo account \u00e8 gi\u00e0 configurato." + "already_configured": "L'account \u00e8 gi\u00e0 configurato" }, "error": { - "cannot_connect": "Impossibile connettersi, si prega di riprovare", + "cannot_connect": "Impossibile connettersi", "invalid_auth": "Autenticazione non valida", "unknown": "Errore imprevisto" }, diff --git a/homeassistant/components/flume/translations/no.json b/homeassistant/components/flume/translations/no.json index 785f392a255e4d..16f842eb371a4d 100644 --- a/homeassistant/components/flume/translations/no.json +++ b/homeassistant/components/flume/translations/no.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Denne kontoen er allerede konfigurert" + "already_configured": "Kontoen er allerede konfigurert" }, "error": { - "cannot_connect": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen", + "cannot_connect": "Tilkobling mislyktes.", "invalid_auth": "Ugyldig godkjenning", "unknown": "Uventet feil" }, diff --git a/homeassistant/components/flume/translations/pl.json b/homeassistant/components/flume/translations/pl.json index ff1d73fe0ced77..f899b1446a6a95 100644 --- a/homeassistant/components/flume/translations/pl.json +++ b/homeassistant/components/flume/translations/pl.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "Konto jest ju\u017c skonfigurowane." + "already_configured": "Konto jest ju\u017c skonfigurowane" }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "invalid_auth": "Niepoprawne uwierzytelnienie.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "user": { diff --git a/homeassistant/components/flume/translations/ru.json b/homeassistant/components/flume/translations/ru.json index 0ed6e839227231..f35579c2dee5b7 100644 --- a/homeassistant/components/flume/translations/ru.json +++ b/homeassistant/components/flume/translations/ru.json @@ -4,7 +4,7 @@ "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": { - "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", + "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "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/flume/translations/zh-Hant.json b/homeassistant/components/flume/translations/zh-Hant.json index cc7dea80e52248..7a585b1b618b2e 100644 --- a/homeassistant/components/flume/translations/zh-Hant.json +++ b/homeassistant/components/flume/translations/zh-Hant.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "\u6b64\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21", + "cannot_connect": "\u9023\u7dda\u5931\u6557", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, diff --git a/homeassistant/components/flunearyou/config_flow.py b/homeassistant/components/flunearyou/config_flow.py index 0a55a21f6edaca..9918858fd93285 100644 --- a/homeassistant/components/flunearyou/config_flow.py +++ b/homeassistant/components/flunearyou/config_flow.py @@ -53,8 +53,6 @@ async def async_step_user(self, user_input=None): ) except FluNearYouError as err: LOGGER.error("Error while configuring integration: %s", err) - return self.async_show_form( - step_id="user", errors={"base": "general_error"} - ) + return self.async_show_form(step_id="user", errors={"base": "unknown"}) return self.async_create_entry(title=unique_id, data=user_input) diff --git a/homeassistant/components/flunearyou/strings.json b/homeassistant/components/flunearyou/strings.json index 2a7e59989b0bfa..780f28fcc6f86f 100644 --- a/homeassistant/components/flunearyou/strings.json +++ b/homeassistant/components/flunearyou/strings.json @@ -4,12 +4,17 @@ "user": { "title": "Configure Flu Near You", "description": "Monitor user-based and CDC repots for a pair of coordinates.", - "data": { "latitude": "Latitude", "longitude": "Longitude" } + "data": { + "latitude": "[%key:common::config_flow::data::latitude%]", + "longitude": "[%key:common::config_flow::data::longitude%]" + } } }, - "error": { "general_error": "There was an unknown error." }, + "error": { + "unknown": "[%key:common::config_flow::error::unknown%]" + }, "abort": { "already_configured": "These coordinates are already registered." } } -} +} \ No newline at end of file diff --git a/homeassistant/components/flunearyou/translations/ca.json b/homeassistant/components/flunearyou/translations/ca.json index c26c1f55b2cc18..1cb1408cbfbb05 100644 --- a/homeassistant/components/flunearyou/translations/ca.json +++ b/homeassistant/components/flunearyou/translations/ca.json @@ -4,7 +4,8 @@ "already_configured": "Les coordenades ja estan registrades" }, "error": { - "general_error": "S'ha produ\u00eft un error desconegut." + "general_error": "S'ha produ\u00eft un error desconegut.", + "unknown": "Error inesperat" }, "step": { "user": { diff --git a/homeassistant/components/flunearyou/translations/en.json b/homeassistant/components/flunearyou/translations/en.json index 88997a89c90c7d..289c950ce0c40d 100644 --- a/homeassistant/components/flunearyou/translations/en.json +++ b/homeassistant/components/flunearyou/translations/en.json @@ -4,7 +4,8 @@ "already_configured": "These coordinates are already registered." }, "error": { - "general_error": "There was an unknown error." + "general_error": "There was an unknown error.", + "unknown": "Unexpected error" }, "step": { "user": { diff --git a/homeassistant/components/flunearyou/translations/es.json b/homeassistant/components/flunearyou/translations/es.json index cdaa475037d524..c7306db4ec7d9e 100644 --- a/homeassistant/components/flunearyou/translations/es.json +++ b/homeassistant/components/flunearyou/translations/es.json @@ -4,7 +4,8 @@ "already_configured": "Estas coordenadas ya est\u00e1n registradas." }, "error": { - "general_error": "Se ha producido un error desconocido." + "general_error": "Se ha producido un error desconocido.", + "unknown": "Error inesperado" }, "step": { "user": { diff --git a/homeassistant/components/flunearyou/translations/et.json b/homeassistant/components/flunearyou/translations/et.json new file mode 100644 index 00000000000000..2ed64a235440ae --- /dev/null +++ b/homeassistant/components/flunearyou/translations/et.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "unknown": "Tundmatu viga" + }, + "step": { + "user": { + "data": { + "latitude": "Laiuskraad", + "longitude": "Pikkuskraad" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/flunearyou/translations/fr.json b/homeassistant/components/flunearyou/translations/fr.json index 27789e1b4cfc7f..50300be55e12e8 100644 --- a/homeassistant/components/flunearyou/translations/fr.json +++ b/homeassistant/components/flunearyou/translations/fr.json @@ -12,6 +12,7 @@ "latitude": "Latitude", "longitude": "Longitude" }, + "description": "Surveillez les rapports des utilisateurs et du CDC pour des coordonn\u00e9es.", "title": "Configurer Flu Near You" } } diff --git a/homeassistant/components/flunearyou/translations/it.json b/homeassistant/components/flunearyou/translations/it.json index fc90199664eca6..f0dcf878649cbf 100644 --- a/homeassistant/components/flunearyou/translations/it.json +++ b/homeassistant/components/flunearyou/translations/it.json @@ -4,7 +4,8 @@ "already_configured": "Queste coordinate sono gi\u00e0 registrate." }, "error": { - "general_error": "Si \u00e8 verificato un errore sconosciuto." + "general_error": "Si \u00e8 verificato un errore sconosciuto.", + "unknown": "Errore imprevisto" }, "step": { "user": { diff --git a/homeassistant/components/flunearyou/translations/pl.json b/homeassistant/components/flunearyou/translations/pl.json index de344b82d00046..cde9f39c3f94a7 100644 --- a/homeassistant/components/flunearyou/translations/pl.json +++ b/homeassistant/components/flunearyou/translations/pl.json @@ -4,7 +4,7 @@ "already_configured": "Wsp\u00f3\u0142rz\u0119dne s\u0105 ju\u017c zarejestrowane." }, "error": { - "general_error": "Nieoczekiwany b\u0142\u0105d." + "general_error": "Nieoczekiwany b\u0142\u0105d" }, "step": { "user": { diff --git a/homeassistant/components/flunearyou/translations/ru.json b/homeassistant/components/flunearyou/translations/ru.json index b4ff15d4044854..61d208ce9581ea 100644 --- a/homeassistant/components/flunearyou/translations/ru.json +++ b/homeassistant/components/flunearyou/translations/ru.json @@ -4,7 +4,8 @@ "already_configured": "\u041a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u044b \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u044b." }, "error": { - "general_error": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + "general_error": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\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": { "user": { diff --git a/homeassistant/components/folder/sensor.py b/homeassistant/components/folder/sensor.py index 19a5791d7cb5be..9a062133718579 100644 --- a/homeassistant/components/folder/sensor.py +++ b/homeassistant/components/folder/sensor.py @@ -94,14 +94,13 @@ def icon(self): @property def device_state_attributes(self): """Return other details about the sensor state.""" - attr = { + return { "path": self._folder_path, "filter": self._filter_term, "number_of_files": self._number_of_files, "bytes": self._size, "file_list": self._file_list, } - return attr @property def unit_of_measurement(self): diff --git a/homeassistant/components/forked_daapd/strings.json b/homeassistant/components/forked_daapd/strings.json index 33f9c7b91aa3f3..e3be0b6795d383 100644 --- a/homeassistant/components/forked_daapd/strings.json +++ b/homeassistant/components/forked_daapd/strings.json @@ -6,7 +6,7 @@ "title": "Set up forked-daapd device", "data": { "name": "Friendly name", - "host": "Host", + "host": "[%key:common::config_flow::data::host%]", "port": "API port", "password": "API password (leave blank if no password)" } @@ -17,10 +17,10 @@ "wrong_host_or_port": "Unable to connect. Please check host and port.", "wrong_password": "Incorrect password.", "wrong_server_type": "The forked-daapd integration requires a forked-daapd server with version >= 27.0.", - "unknown_error": "Unknown error." + "unknown_error": "[%key:common::config_flow::error::unknown%]" }, "abort": { - "already_configured": "Device is already configured.", + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "not_forked_daapd": "Device is not a forked-daapd server." } }, diff --git a/homeassistant/components/forked_daapd/translations/ca.json b/homeassistant/components/forked_daapd/translations/ca.json index cf58af459f68e7..1b3792eeb1bd0a 100644 --- a/homeassistant/components/forked_daapd/translations/ca.json +++ b/homeassistant/components/forked_daapd/translations/ca.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "El dispositiu ja est\u00e0 configurat.", + "already_configured": "El dispositiu ja est\u00e0 configurat", "not_forked_daapd": "El dispositiu no \u00e9s un servidor de forked-daapd." }, "error": { - "unknown_error": "Error desconegut.", + "unknown_error": "Error inesperat", "websocket_not_enabled": "El websocket de forked-daapd no est\u00e0 activat.", "wrong_host_or_port": "No s'ha pogut connectar, verifica l'amfitri\u00f3 i el port.", "wrong_password": "Contrasenya incorrecta.", diff --git a/homeassistant/components/forked_daapd/translations/en.json b/homeassistant/components/forked_daapd/translations/en.json index 0c87c6624ffc36..397836809da8c2 100644 --- a/homeassistant/components/forked_daapd/translations/en.json +++ b/homeassistant/components/forked_daapd/translations/en.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Device is already configured.", + "already_configured": "Device is already configured", "not_forked_daapd": "Device is not a forked-daapd server." }, "error": { - "unknown_error": "Unknown error.", + "unknown_error": "Unexpected error", "websocket_not_enabled": "forked-daapd server websocket not enabled.", "wrong_host_or_port": "Unable to connect. Please check host and port.", "wrong_password": "Incorrect password.", diff --git a/homeassistant/components/forked_daapd/translations/it.json b/homeassistant/components/forked_daapd/translations/it.json index f6d4517c7e7014..3f400e0edc50c5 100644 --- a/homeassistant/components/forked_daapd/translations/it.json +++ b/homeassistant/components/forked_daapd/translations/it.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato.", + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", "not_forked_daapd": "Il dispositivo non \u00e8 un server forked-daapd." }, "error": { - "unknown_error": "Errore sconosciuto.", + "unknown_error": "Errore imprevisto", "websocket_not_enabled": "websocket del server forked-daapd non abilitato.", "wrong_host_or_port": "Impossibile connettersi. Si prega di controllare host e porta.", "wrong_password": "Password errata", diff --git a/homeassistant/components/forked_daapd/translations/no.json b/homeassistant/components/forked_daapd/translations/no.json index ac58ddf96399a9..32ae6fe73ec373 100644 --- a/homeassistant/components/forked_daapd/translations/no.json +++ b/homeassistant/components/forked_daapd/translations/no.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Enheten er allerede konfigurert.", + "already_configured": "Enheten er allerede konfigurert", "not_forked_daapd": "Enheten er ikke en forked-daapd-server." }, "error": { - "unknown_error": "Ukjent feil.", + "unknown_error": "Uventet feil", "websocket_not_enabled": "websocket for forked-daapd server ikke aktivert.", "wrong_host_or_port": "Kan ikke koble til. Vennligst sjekk vert og port.", "wrong_password": "Feil passord.", diff --git a/homeassistant/components/forked_daapd/translations/pl.json b/homeassistant/components/forked_daapd/translations/pl.json index d40e9b282aad2e..d246307e6d006c 100644 --- a/homeassistant/components/forked_daapd/translations/pl.json +++ b/homeassistant/components/forked_daapd/translations/pl.json @@ -1,12 +1,17 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", + "not_forked_daapd": "Urz\u0105dzenie nie jest serwerem forked-daapd." }, "error": { "unknown_error": "Nieznany b\u0142\u0105d.", - "wrong_password": "Nieprawid\u0142owe has\u0142o" + "websocket_not_enabled": "Websocket serwera forked-daapd nie jest w\u0142\u0105czony.", + "wrong_host_or_port": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia. Sprawd\u017a adres hosta i port.", + "wrong_password": "Nieprawid\u0142owe has\u0142o", + "wrong_server_type": "Integracja forked-daapd wymaga serwera forked-daapd w wersji >= 27.0." }, + "flow_title": "Serwer forked-daapd: {name} ( {host})", "step": { "user": { "data": { @@ -14,7 +19,8 @@ "name": "Przyjazna nazwa", "password": "Has\u0142o API (pozostaw puste, je\u015bli nie ma has\u0142a)", "port": "Port API" - } + }, + "title": "Skonfiguruj urz\u0105dzenie forked-daapd" } } } diff --git a/homeassistant/components/forked_daapd/translations/ru.json b/homeassistant/components/forked_daapd/translations/ru.json index 89bd71cb0417a9..d5111b8f286ae9 100644 --- a/homeassistant/components/forked_daapd/translations/ru.json +++ b/homeassistant/components/forked_daapd/translations/ru.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant.", "not_forked_daapd": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435 \u0441\u0435\u0440\u0432\u0435\u0440 forked-daapd." }, "error": { - "unknown_error": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430.", + "unknown_error": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430.", "websocket_not_enabled": "\u0412\u0435\u0431-\u0441\u043e\u043a\u0435\u0442 forked-daapd \u043d\u0435 \u0432\u043a\u043b\u044e\u0447\u0435\u043d.", "wrong_host_or_port": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f, \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0430\u0434\u0440\u0435\u0441 \u0445\u043e\u0441\u0442\u0430.", "wrong_password": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043f\u0430\u0440\u043e\u043b\u044c.", diff --git a/homeassistant/components/forked_daapd/translations/zh-Hant.json b/homeassistant/components/forked_daapd/translations/zh-Hant.json index ec51bbacea3cf1..2dcced078ed806 100644 --- a/homeassistant/components/forked_daapd/translations/zh-Hant.json +++ b/homeassistant/components/forked_daapd/translations/zh-Hant.json @@ -5,7 +5,7 @@ "not_forked_daapd": "\u8a2d\u5099\u4e26\u975e forked-daapd \u4f3a\u670d\u5668\u3002" }, "error": { - "unknown_error": "\u672a\u77e5\u932f\u8aa4\u3002", + "unknown_error": "\u672a\u9810\u671f\u932f\u8aa4", "websocket_not_enabled": "forked-daapd \u4f3a\u670d\u5668 websocket \u672a\u958b\u555f\u3002", "wrong_host_or_port": "\u7121\u6cd5\u9023\u7dda\uff0c\u8acb\u78ba\u8a8d\u4e3b\u6a5f\u8207\u901a\u8a0a\u57e0\u3002", "wrong_password": "\u5bc6\u78bc\u932f\u8aa4\u3002", diff --git a/homeassistant/components/foursquare/__init__.py b/homeassistant/components/foursquare/__init__.py index bae0336a63e254..6f33c9ff5912fe 100644 --- a/homeassistant/components/foursquare/__init__.py +++ b/homeassistant/components/foursquare/__init__.py @@ -5,7 +5,12 @@ import voluptuous as vol from homeassistant.components.http import HomeAssistantView -from homeassistant.const import CONF_ACCESS_TOKEN, HTTP_BAD_REQUEST, HTTP_OK +from homeassistant.const import ( + CONF_ACCESS_TOKEN, + HTTP_BAD_REQUEST, + HTTP_CREATED, + HTTP_OK, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) @@ -55,7 +60,7 @@ def checkin_user(call): url = f"https://api.foursquare.com/v2/checkins/add?oauth_token={config[CONF_ACCESS_TOKEN]}&v=20160802&m=swarm" response = requests.post(url, data=call.data, timeout=10) - if response.status_code not in (HTTP_OK, 201): + if response.status_code not in (HTTP_OK, HTTP_CREATED): _LOGGER.exception( "Error checking in user. Response %d: %s:", response.status_code, diff --git a/homeassistant/components/freebox/config_flow.py b/homeassistant/components/freebox/config_flow.py index 0589dfb2ef10ff..d776c34c4f9bdf 100644 --- a/homeassistant/components/freebox/config_flow.py +++ b/homeassistant/components/freebox/config_flow.py @@ -92,7 +92,7 @@ async def async_step_link(self, user_input=None): except HttpRequestError: _LOGGER.error("Error connecting to the Freebox router at %s", self._host) - errors["base"] = "connection_failed" + errors["base"] = "cannot_connect" except Exception: # pylint: disable=broad-except _LOGGER.exception( diff --git a/homeassistant/components/freebox/sensor.py b/homeassistant/components/freebox/sensor.py index dc0d808c438011..aeeaba438ffc14 100644 --- a/homeassistant/components/freebox/sensor.py +++ b/homeassistant/components/freebox/sensor.py @@ -146,11 +146,12 @@ def __init__( def async_update_state(self) -> None: """Update the Freebox call sensor.""" self._call_list_for_type = [] - for call in self._router.call_list: - if not call["new"]: - continue - if call["type"] == self._sensor_type: - self._call_list_for_type.append(call) + if self._router.call_list: + for call in self._router.call_list: + if not call["new"]: + continue + if call["type"] == self._sensor_type: + self._call_list_for_type.append(call) self._state = len(self._call_list_for_type) diff --git a/homeassistant/components/freebox/strings.json b/homeassistant/components/freebox/strings.json index 0fdc4571a1d8dc..cb48e5322de944 100644 --- a/homeassistant/components/freebox/strings.json +++ b/homeassistant/components/freebox/strings.json @@ -15,11 +15,11 @@ }, "error": { "register_failed": "Failed to register, please try again", - "connection_failed": "Failed to connect, please try again", - "unknown": "Unknown error: please retry later" + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { - "already_configured": "Host already configured" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/freebox/translations/ca.json b/homeassistant/components/freebox/translations/ca.json index 264e0ed3038693..2568c3db0b4644 100644 --- a/homeassistant/components/freebox/translations/ca.json +++ b/homeassistant/components/freebox/translations/ca.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "L'amfitri\u00f3 ja est\u00e0 configurat" + "already_configured": "El dispositiu ja est\u00e0 configurat" }, "error": { - "connection_failed": "No s'ha pogut connectar, torna-ho a provar", + "cannot_connect": "Ha fallat la connexi\u00f3", "register_failed": "No s'ha pogut registrar, torna-ho a provar", - "unknown": "Error desconegut: torna-ho a provar m\u00e9s tard" + "unknown": "Error inesperat" }, "step": { "link": { diff --git a/homeassistant/components/freebox/translations/de.json b/homeassistant/components/freebox/translations/de.json index cf18dce0870849..c21e3c6b67fe70 100644 --- a/homeassistant/components/freebox/translations/de.json +++ b/homeassistant/components/freebox/translations/de.json @@ -4,7 +4,7 @@ "already_configured": "Host bereits konfiguriert" }, "error": { - "connection_failed": "Verbindung fehlgeschlagen, versuchen Sie es erneut", + "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", "register_failed": "Registrieren fehlgeschlagen, bitte versuche es erneut", "unknown": "Unbekannter Fehler: Bitte versuchen Sie es sp\u00e4ter erneut" }, diff --git a/homeassistant/components/freebox/translations/en.json b/homeassistant/components/freebox/translations/en.json index 15e18a8982b3ef..539cfbcfe1ee09 100644 --- a/homeassistant/components/freebox/translations/en.json +++ b/homeassistant/components/freebox/translations/en.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "Host already configured" + "already_configured": "Device is already configured" }, "error": { - "connection_failed": "Failed to connect, please try again", + "cannot_connect": "Failed to connect", "register_failed": "Failed to register, please try again", - "unknown": "Unknown error: please retry later" + "unknown": "Unexpected error" }, "step": { "link": { diff --git a/homeassistant/components/freebox/translations/es-419.json b/homeassistant/components/freebox/translations/es-419.json index 015835514536d3..1c99bc8b472c71 100644 --- a/homeassistant/components/freebox/translations/es-419.json +++ b/homeassistant/components/freebox/translations/es-419.json @@ -4,7 +4,7 @@ "already_configured": "Host ya configurado" }, "error": { - "connection_failed": "No se pudo conectar, intente nuevamente", + "cannot_connect": "No se pudo conectar, intente nuevamente", "register_failed": "No se pudo registrar, intente de nuevo", "unknown": "Error desconocido: vuelva a intentarlo m\u00e1s tarde" }, diff --git a/homeassistant/components/freebox/translations/es.json b/homeassistant/components/freebox/translations/es.json index 3c62f33c3be27b..45926ebc274a99 100644 --- a/homeassistant/components/freebox/translations/es.json +++ b/homeassistant/components/freebox/translations/es.json @@ -4,7 +4,7 @@ "already_configured": "El host ya est\u00e1 configurado." }, "error": { - "connection_failed": "No se ha podido conectar, por favor, int\u00e9ntalo de nuevo.", + "cannot_connect": "No se ha podido conectar, por favor, int\u00e9ntalo de nuevo.", "register_failed": "No se pudo registrar, int\u00e9ntalo de nuevo", "unknown": "Error desconocido: por favor, int\u00e9ntalo de nuevo m\u00e1s" }, diff --git a/homeassistant/components/freebox/translations/et.json b/homeassistant/components/freebox/translations/et.json new file mode 100644 index 00000000000000..96e7dc6fd75eca --- /dev/null +++ b/homeassistant/components/freebox/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "register_failed": "\u00dchenduse loomine nurjus. Proovi uuesti" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/freebox/translations/fr.json b/homeassistant/components/freebox/translations/fr.json index 135d8f4d465fd5..f06cfed6cd7695 100644 --- a/homeassistant/components/freebox/translations/fr.json +++ b/homeassistant/components/freebox/translations/fr.json @@ -4,7 +4,7 @@ "already_configured": "H\u00f4te d\u00e9j\u00e0 configur\u00e9" }, "error": { - "connection_failed": "Impossible de se connecter, veuillez r\u00e9essayer", + "cannot_connect": "Impossible de se connecter, veuillez r\u00e9essayer", "register_failed": "\u00c9chec de l'inscription, veuillez r\u00e9essayer", "unknown": "Erreur inconnue: veuillez r\u00e9essayer plus tard" }, diff --git a/homeassistant/components/freebox/translations/it.json b/homeassistant/components/freebox/translations/it.json index 11d27eebd693ca..7dd5e279a87277 100644 --- a/homeassistant/components/freebox/translations/it.json +++ b/homeassistant/components/freebox/translations/it.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "Host gi\u00e0 configurato" + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" }, "error": { - "connection_failed": "Impossibile connettersi, si prega di riprovare", + "cannot_connect": "Impossibile connettersi", "register_failed": "Errore in fase di registrazione, si prega di riprovare", - "unknown": "Errore sconosciuto: riprovare pi\u00f9 tardi" + "unknown": "Errore imprevisto" }, "step": { "link": { diff --git a/homeassistant/components/freebox/translations/ko.json b/homeassistant/components/freebox/translations/ko.json index ce0bc09989e48f..986f345b3ecc29 100644 --- a/homeassistant/components/freebox/translations/ko.json +++ b/homeassistant/components/freebox/translations/ko.json @@ -4,7 +4,7 @@ "already_configured": "\ud638\uc2a4\ud2b8\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, "error": { - "connection_failed": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", + "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", "register_failed": "\ub4f1\ub85d\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", "unknown": "\uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uc785\ub2c8\ub2e4. \ub098\uc911\uc5d0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694" }, diff --git a/homeassistant/components/freebox/translations/lb.json b/homeassistant/components/freebox/translations/lb.json index eccc419c79b808..e7c1b1148dce35 100644 --- a/homeassistant/components/freebox/translations/lb.json +++ b/homeassistant/components/freebox/translations/lb.json @@ -4,7 +4,7 @@ "already_configured": "Apparat ass scho konfigur\u00e9iert" }, "error": { - "connection_failed": "Feeler beim verbannen, prob\u00e9ier w.e.g. nach emol.", + "cannot_connect": "Feeler beim verbannen, prob\u00e9ier w.e.g. nach emol.", "register_failed": "Feeler beim registr\u00e9ieren, prob\u00e9ier w.e.g. nach emol", "unknown": "Onbekannte Feeler: prob\u00e9iertsp\u00e9ider nach emol" }, diff --git a/homeassistant/components/freebox/translations/nl.json b/homeassistant/components/freebox/translations/nl.json index 62c69997e17626..ea41fcfcd6a8e9 100644 --- a/homeassistant/components/freebox/translations/nl.json +++ b/homeassistant/components/freebox/translations/nl.json @@ -4,7 +4,7 @@ "already_configured": "Host is al geconfigureerd." }, "error": { - "connection_failed": "Verbinding mislukt, probeer het opnieuw", + "cannot_connect": "Verbinding mislukt, probeer het opnieuw", "register_failed": "Registratie is mislukt, probeer het opnieuw", "unknown": "Onbekende fout: probeer het later nog eens" }, diff --git a/homeassistant/components/freebox/translations/no.json b/homeassistant/components/freebox/translations/no.json index 0ec9bf70ecdaf6..f66078583c1dfe 100644 --- a/homeassistant/components/freebox/translations/no.json +++ b/homeassistant/components/freebox/translations/no.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "Verten er allerede konfigurert" + "already_configured": "Enheten er allerede konfigurert" }, "error": { - "connection_failed": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen", + "cannot_connect": "Tilkobling mislyktes.", "register_failed": "Registrering feilet, vennligst pr\u00f8v igjen", - "unknown": "Ukjent feil: pr\u00f8v p\u00e5 nytt senere" + "unknown": "Uventet feil" }, "step": { "link": { diff --git a/homeassistant/components/freebox/translations/pl.json b/homeassistant/components/freebox/translations/pl.json index c465e16fcb6de9..73c65a7227b105 100644 --- a/homeassistant/components/freebox/translations/pl.json +++ b/homeassistant/components/freebox/translations/pl.json @@ -4,7 +4,7 @@ "already_configured": "Nazwa hosta lub adres IP" }, "error": { - "connection_failed": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", "register_failed": "Nie uda\u0142o si\u0119 zarejestrowa\u0107. Spr\u00f3buj ponownie.", "unknown": "Nieznany b\u0142\u0105d, spr\u00f3buj ponownie p\u00f3\u017aniej." }, diff --git a/homeassistant/components/freebox/translations/ru.json b/homeassistant/components/freebox/translations/ru.json index 1bef863e15f612..4ec5b68516c07d 100644 --- a/homeassistant/components/freebox/translations/ru.json +++ b/homeassistant/components/freebox/translations/ru.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0445\u043e\u0441\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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": { - "connection_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "register_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0441\u043d\u043e\u0432\u0430.", - "unknown": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430: \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": { "link": { diff --git a/homeassistant/components/freebox/translations/sl.json b/homeassistant/components/freebox/translations/sl.json index 0a36450501b4cb..a161bfa494e138 100644 --- a/homeassistant/components/freebox/translations/sl.json +++ b/homeassistant/components/freebox/translations/sl.json @@ -4,7 +4,7 @@ "already_configured": "Gostitelj je \u017ee konfiguriran" }, "error": { - "connection_failed": "Povezava ni uspela, poskusite znova", + "cannot_connect": "Povezava ni uspela, poskusite znova", "register_failed": "Registracija ni uspela, poskusite znova", "unknown": "Neznana napaka: poskusite pozneje" }, diff --git a/homeassistant/components/freebox/translations/sv.json b/homeassistant/components/freebox/translations/sv.json index 6c6cc5c64ecee3..aa43ee660322f7 100644 --- a/homeassistant/components/freebox/translations/sv.json +++ b/homeassistant/components/freebox/translations/sv.json @@ -4,7 +4,7 @@ "already_configured": "V\u00e4rden \u00e4r redan konfigurerad." }, "error": { - "connection_failed": "Det gick inte att ansluta, f\u00f6rs\u00f6k igen", + "cannot_connect": "Det gick inte att ansluta, f\u00f6rs\u00f6k igen", "register_failed": "Misslyckades med att registrera, v\u00e4nligen f\u00f6rs\u00f6k igen", "unknown": "Ok\u00e4nt fel: f\u00f6rs\u00f6k igen senare" }, diff --git a/homeassistant/components/freebox/translations/zh-Hant.json b/homeassistant/components/freebox/translations/zh-Hant.json index be643ab9fd9ae4..608c5bbcba7ed3 100644 --- a/homeassistant/components/freebox/translations/zh-Hant.json +++ b/homeassistant/components/freebox/translations/zh-Hant.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "\u4e3b\u6a5f\u7aef\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { - "connection_failed": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21", + "cannot_connect": "\u9023\u7dda\u5931\u6557", "register_failed": "\u8a3b\u518a\u5931\u6557\uff0c\u8acb\u7a0d\u5f8c\u518d\u8a66", - "unknown": "\u672a\u77e5\u932f\u8aa4\uff1a\u8acb\u7a0d\u5f8c\u518d\u8a66" + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { "link": { diff --git a/homeassistant/components/fritzbox/binary_sensor.py b/homeassistant/components/fritzbox/binary_sensor.py index 7db216c32e107e..1246eb4afaf2f5 100644 --- a/homeassistant/components/fritzbox/binary_sensor.py +++ b/homeassistant/components/fritzbox/binary_sensor.py @@ -1,7 +1,10 @@ """Support for Fritzbox binary sensors.""" import requests -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_WINDOW, + BinarySensorEntity, +) from homeassistant.const import CONF_DEVICES from .const import CONF_CONNECTIONS, DOMAIN as FRITZBOX_DOMAIN, LOGGER @@ -53,7 +56,7 @@ def name(self): @property def device_class(self): """Return the class of this sensor.""" - return "window" + return DEVICE_CLASS_WINDOW @property def is_on(self): diff --git a/homeassistant/components/fritzbox/strings.json b/homeassistant/components/fritzbox/strings.json index 3b287fa38a5fa5..5a537b33e7b8d7 100644 --- a/homeassistant/components/fritzbox/strings.json +++ b/homeassistant/components/fritzbox/strings.json @@ -19,7 +19,7 @@ } }, "abort": { - "already_in_progress": "AVM FRITZ!Box configuration is already in progress.", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", "already_configured": "This AVM FRITZ!Box is already configured.", "not_found": "No supported AVM FRITZ!Box found on the network.", "not_supported": "Connected to AVM FRITZ!Box but it's unable to control Smart Home devices." @@ -28,4 +28,4 @@ "auth_failed": "Username and/or password are incorrect." } } -} \ No newline at end of file +} diff --git a/homeassistant/components/fritzbox/translations/ca.json b/homeassistant/components/fritzbox/translations/ca.json index af82f1fc582ed8..cc8035d4dde871 100644 --- a/homeassistant/components/fritzbox/translations/ca.json +++ b/homeassistant/components/fritzbox/translations/ca.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Aquest AVM FRITZ!Box ja est\u00e0 configurat.", - "already_in_progress": "La configuraci\u00f3 de l'AVM FRITZ!Box ja est\u00e0 en curs.", + "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", "not_found": "No s'ha trobat cap AVM FRITZ!Box compatible a la xarxa.", "not_supported": "Connectat a AVM FRITZ!Box per\u00f2 no es poden controlar dispositius Smart Home." }, diff --git a/homeassistant/components/fritzbox/translations/en.json b/homeassistant/components/fritzbox/translations/en.json index cc9b13619a7d8f..90f433a9059a91 100644 --- a/homeassistant/components/fritzbox/translations/en.json +++ b/homeassistant/components/fritzbox/translations/en.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "This AVM FRITZ!Box is already configured.", - "already_in_progress": "AVM FRITZ!Box configuration is already in progress.", + "already_in_progress": "Configuration flow is already in progress", "not_found": "No supported AVM FRITZ!Box found on the network.", "not_supported": "Connected to AVM FRITZ!Box but it's unable to control Smart Home devices." }, diff --git a/homeassistant/components/fritzbox/translations/it.json b/homeassistant/components/fritzbox/translations/it.json index 3b99e98587139c..26c0f2a35a83c7 100644 --- a/homeassistant/components/fritzbox/translations/it.json +++ b/homeassistant/components/fritzbox/translations/it.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Questo AVM FRITZ!Box \u00e8 gi\u00e0 configurato.", - "already_in_progress": "La configurazione di AVM FRITZ!Box \u00e8 gi\u00e0 in corso.", + "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "not_found": "Nessun AVM FRITZ!Box supportato trovato sulla rete.", "not_supported": "Collegato a AVM FRITZ!Box ma non \u00e8 in grado di controllare i dispositivi Smart Home." }, diff --git a/homeassistant/components/fritzbox/translations/no.json b/homeassistant/components/fritzbox/translations/no.json index 44d8c28418bb9a..8b9b5a5c911586 100644 --- a/homeassistant/components/fritzbox/translations/no.json +++ b/homeassistant/components/fritzbox/translations/no.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Denne AVM FRITZ!Box er allerede konfigurert.", - "already_in_progress": "AVM FRITZ!Box-konfigurasjon p\u00e5g\u00e5r allerede.", + "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", "not_found": "Ingen st\u00f8ttet AVM FRITZ!Box funnet p\u00e5 nettverket.", "not_supported": "Tilkoblet AVM FRITZ! Box, men den klarer ikke \u00e5 kontrollere Smart Home-enheter." }, diff --git a/homeassistant/components/fritzbox/translations/ru.json b/homeassistant/components/fritzbox/translations/ru.json index 635e0a2b891fbe..5c6ecc076b9bed 100644 --- a/homeassistant/components/fritzbox/translations/ru.json +++ b/homeassistant/components/fritzbox/translations/ru.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", - "already_in_progress": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.", + "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_found": "\u0412 \u0441\u0435\u0442\u0438 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u044b\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432.", "not_supported": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a AVM FRITZ! Box \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043e, \u043d\u043e \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430\u043c\u0438 Smart Home \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e." }, diff --git a/homeassistant/components/fritzbox/translations/zh-Hant.json b/homeassistant/components/fritzbox/translations/zh-Hant.json index 415ad9c71bd810..882e2d0b6a66df 100644 --- a/homeassistant/components/fritzbox/translations/zh-Hant.json +++ b/homeassistant/components/fritzbox/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\u6b64 AVM FRITZ!Box \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", - "already_in_progress": "AVM FRITZ!Box \u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d\u3002", + "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "not_found": "\u5728\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u652f\u63f4\u7684 AVM FRITZ!Box\u3002", "not_supported": "\u5df2\u9023\u7dda\u81f3 AVM FRITZ!Box \u4f46\u7121\u6cd5\u63a7\u5236\u667a\u80fd\u5bb6\u5ead\u8a2d\u5099\u3002" }, diff --git a/homeassistant/components/fritzbox_netmonitor/sensor.py b/homeassistant/components/fritzbox_netmonitor/sensor.py index c0d010cf37e967..5601ad5d74f0ca 100644 --- a/homeassistant/components/fritzbox_netmonitor/sensor.py +++ b/homeassistant/components/fritzbox_netmonitor/sensor.py @@ -98,7 +98,7 @@ def state_attributes(self): # Don't return attributes if FritzBox is unreachable if self._state == STATE_UNAVAILABLE: return {} - attr = { + return { ATTR_IS_LINKED: self._is_linked, ATTR_IS_CONNECTED: self._is_connected, ATTR_EXTERNAL_IP: self._external_ip, @@ -110,7 +110,6 @@ def state_attributes(self): ATTR_MAX_BYTE_RATE_UP: self._max_byte_rate_up, ATTR_MAX_BYTE_RATE_DOWN: self._max_byte_rate_down, } - return attr @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 73eb24d08cd40b..66c1b6d997fbe7 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -559,7 +559,7 @@ def websocket_get_themes(hass, connection, msg): "themes": { "safe_mode": { "primary-color": "#db4437", - "accent-color": "#eeee02", + "accent-color": "#ffca28", } }, "default_theme": "safe_mode", diff --git a/homeassistant/components/frontend/manifest.json b/homeassistant/components/frontend/manifest.json index 7ccb606894a51e..6732f6e99c37fc 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==20200909.0"], + "requirements": ["home-assistant-frontend==20201001.1"], "dependencies": [ "api", "auth", diff --git a/homeassistant/components/frontend/services.yaml b/homeassistant/components/frontend/services.yaml index 31eb4d5d1ca1d8..cc0d6bde21645b 100644 --- a/homeassistant/components/frontend/services.yaml +++ b/homeassistant/components/frontend/services.yaml @@ -5,7 +5,7 @@ set_theme: fields: name: description: Name of a predefined theme, 'default' or 'none'. - example: "light" + example: "default" mode: description: The mode the theme is for, either 'dark' or 'light' (default). example: "dark" diff --git a/homeassistant/components/frontier_silicon/media_player.py b/homeassistant/components/frontier_silicon/media_player.py index 852528fb3a5207..bc9f0f35b5778e 100644 --- a/homeassistant/components/frontier_silicon/media_player.py +++ b/homeassistant/components/frontier_silicon/media_player.py @@ -126,11 +126,6 @@ def fs_device(self): """ return AFSAPI(self._device_url, self._password) - @property - def should_poll(self): - """Device should be polled.""" - return True - @property def name(self): """Return the device name.""" diff --git a/homeassistant/components/garadget/cover.py b/homeassistant/components/garadget/cover.py index 34a9a13b8d9883..9c089e85811768 100644 --- a/homeassistant/components/garadget/cover.py +++ b/homeassistant/components/garadget/cover.py @@ -105,14 +105,14 @@ def __init__(self, hass, args): self._name = doorconfig["nme"] self.update() except requests.exceptions.ConnectionError as ex: - _LOGGER.error("Unable to connect to server: %(reason)s", dict(reason=ex)) + _LOGGER.error("Unable to connect to server: %(reason)s", {"reason": ex}) self._state = STATE_OFFLINE self._available = False self._name = DEFAULT_NAME except KeyError: _LOGGER.warning( "Garadget device %(device)s seems to be offline", - dict(device=self.device_id), + {"device": self.device_id}, ) self._name = DEFAULT_NAME self._state = STATE_OFFLINE @@ -129,11 +129,6 @@ def name(self): """Return the name of the cover.""" return self._name - @property - def should_poll(self): - """No polling needed for a demo cover.""" - return True - @property def available(self): """Return True if entity is available.""" @@ -235,12 +230,12 @@ def update(self): self.sensor = status["sensor"] self._available = True except requests.exceptions.ConnectionError as ex: - _LOGGER.error("Unable to connect to server: %(reason)s", dict(reason=ex)) + _LOGGER.error("Unable to connect to server: %(reason)s", {"reason": ex}) self._state = STATE_OFFLINE except KeyError: _LOGGER.warning( "Garadget device %(device)s seems to be offline", - dict(device=self.device_id), + {"device": self.device_id}, ) self._state = STATE_OFFLINE diff --git a/homeassistant/components/garmin_connect/sensor.py b/homeassistant/components/garmin_connect/sensor.py index 48713789c6eb21..9b67801105351d 100644 --- a/homeassistant/components/garmin_connect/sensor.py +++ b/homeassistant/components/garmin_connect/sensor.py @@ -121,14 +121,13 @@ def unit_of_measurement(self): @property def device_state_attributes(self): """Return attributes for sensor.""" - attributes = {} - if self._data.data: - attributes = { - "source": self._data.data["source"], - "last_synced": self._data.data["lastSyncTimestampGMT"], - ATTR_ATTRIBUTION: ATTRIBUTION, - } - return attributes + if not self._data.data: + return {} + return { + "source": self._data.data["source"], + "last_synced": self._data.data["lastSyncTimestampGMT"], + ATTR_ATTRIBUTION: ATTRIBUTION, + } @property def device_info(self) -> Dict[str, Any]: diff --git a/homeassistant/components/garmin_connect/strings.json b/homeassistant/components/garmin_connect/strings.json index 6aac2686a5c86b..0ec7a3ce04c39e 100644 --- a/homeassistant/components/garmin_connect/strings.json +++ b/homeassistant/components/garmin_connect/strings.json @@ -4,10 +4,10 @@ "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" }, "error": { - "cannot_connect": "Failed to connect, please try again.", - "invalid_auth": "Invalid authentication.", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", "too_many_requests": "Too many requests, retry later.", - "unknown": "Unexpected error." + "unknown": "[%key:common::config_flow::error::unknown%]" }, "step": { "user": { @@ -20,4 +20,4 @@ } } } -} \ No newline at end of file +} diff --git a/homeassistant/components/garmin_connect/translations/ca.json b/homeassistant/components/garmin_connect/translations/ca.json index 34d7273ef9626d..73b12090fcf0fa 100644 --- a/homeassistant/components/garmin_connect/translations/ca.json +++ b/homeassistant/components/garmin_connect/translations/ca.json @@ -4,10 +4,10 @@ "already_configured": "[%key::common::config_flow::abort::already_configured_account%]" }, "error": { - "cannot_connect": "No s'ha pogut connectar, torna-ho a provar.", - "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida.", + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "too_many_requests": "Massa sol\u00b7licituds, torna-ho a intentar m\u00e9s tard.", - "unknown": "Error inesperat." + "unknown": "Error inesperat" }, "step": { "user": { diff --git a/homeassistant/components/garmin_connect/translations/en.json b/homeassistant/components/garmin_connect/translations/en.json index 3b3b2fcb86585e..c1b563d38f3e2f 100644 --- a/homeassistant/components/garmin_connect/translations/en.json +++ b/homeassistant/components/garmin_connect/translations/en.json @@ -4,10 +4,10 @@ "already_configured": "Account is already configured" }, "error": { - "cannot_connect": "Failed to connect, please try again.", - "invalid_auth": "Invalid authentication.", + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", "too_many_requests": "Too many requests, retry later.", - "unknown": "Unexpected error." + "unknown": "Unexpected error" }, "step": { "user": { diff --git a/homeassistant/components/garmin_connect/translations/it.json b/homeassistant/components/garmin_connect/translations/it.json index 62937ed2ab1b88..791de295a80a5c 100644 --- a/homeassistant/components/garmin_connect/translations/it.json +++ b/homeassistant/components/garmin_connect/translations/it.json @@ -4,10 +4,10 @@ "already_configured": "L'account \u00e8 gi\u00e0 configurato" }, "error": { - "cannot_connect": "Impossibile connettersi, si prega di riprovare.", - "invalid_auth": "Autenticazione non valida.", + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", "too_many_requests": "Troppe richieste, riprovare pi\u00f9 tardi.", - "unknown": "Errore imprevisto." + "unknown": "Errore imprevisto" }, "step": { "user": { diff --git a/homeassistant/components/garmin_connect/translations/no.json b/homeassistant/components/garmin_connect/translations/no.json index 9058d46d02a0bc..aa970261f115b0 100644 --- a/homeassistant/components/garmin_connect/translations/no.json +++ b/homeassistant/components/garmin_connect/translations/no.json @@ -4,10 +4,10 @@ "already_configured": "Denne kontoen er allerede konfigurert." }, "error": { - "cannot_connect": "Kunne ikke koble til, pr\u00f8v igjen.", - "invalid_auth": "Ugyldig godkjenning.", + "cannot_connect": "Tilkobling mislyktes.", + "invalid_auth": "Ugyldig godkjenning", "too_many_requests": "For mange foresp\u00f8rsler, pr\u00f8v p\u00e5 nytt senere.", - "unknown": "Uventet feil." + "unknown": "Uventet feil" }, "step": { "user": { diff --git a/homeassistant/components/garmin_connect/translations/pl.json b/homeassistant/components/garmin_connect/translations/pl.json index 982c7b2c50bc1f..5aaa67a913ea4e 100644 --- a/homeassistant/components/garmin_connect/translations/pl.json +++ b/homeassistant/components/garmin_connect/translations/pl.json @@ -1,13 +1,13 @@ { "config": { "abort": { - "already_configured": "Konto jest ju\u017c skonfigurowane." + "already_configured": "Konto jest ju\u017c skonfigurowane" }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "invalid_auth": "Niepoprawne uwierzytelnienie.", + "invalid_auth": "Niepoprawne uwierzytelnienie", "too_many_requests": "Zbyt wiele \u017c\u0105da\u0144, spr\u00f3buj ponownie p\u00f3\u017aniej.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "user": { diff --git a/homeassistant/components/garmin_connect/translations/ru.json b/homeassistant/components/garmin_connect/translations/ru.json index c9448e1a3fcd1f..69fa96c2a5e9af 100644 --- a/homeassistant/components/garmin_connect/translations/ru.json +++ b/homeassistant/components/garmin_connect/translations/ru.json @@ -4,7 +4,7 @@ "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": { - "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", + "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "too_many_requests": "\u0421\u043b\u0438\u0448\u043a\u043e\u043c \u043c\u043d\u043e\u0433\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432, \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u043f\u043e\u0437\u0436\u0435.", "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." diff --git a/homeassistant/components/garmin_connect/translations/zh-Hant.json b/homeassistant/components/garmin_connect/translations/zh-Hant.json index c7351932734020..cbf928152aa701 100644 --- a/homeassistant/components/garmin_connect/translations/zh-Hant.json +++ b/homeassistant/components/garmin_connect/translations/zh-Hant.json @@ -4,10 +4,10 @@ "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002", - "invalid_auth": "\u9a57\u8b49\u7121\u6548\u3002", + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "too_many_requests": "\u8acb\u6c42\u6b21\u6578\u904e\u591a\uff0c\u8acb\u7a0d\u5f8c\u91cd\u8a66\u3002", - "unknown": "\u672a\u9810\u671f\u932f\u8aa4\u3002" + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { "user": { diff --git a/homeassistant/components/gdacs/strings.json b/homeassistant/components/gdacs/strings.json index 496b996823a996..955936cf986b96 100644 --- a/homeassistant/components/gdacs/strings.json +++ b/homeassistant/components/gdacs/strings.json @@ -3,9 +3,13 @@ "step": { "user": { "title": "Fill in your filter details.", - "data": { "radius": "Radius" } + "data": { + "radius": "Radius" + } } }, - "abort": { "already_configured": "Location is already configured." } + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]" + } } -} +} \ No newline at end of file diff --git a/homeassistant/components/gdacs/translations/ca.json b/homeassistant/components/gdacs/translations/ca.json index db6359c881b716..b5c13c0c3675f2 100644 --- a/homeassistant/components/gdacs/translations/ca.json +++ b/homeassistant/components/gdacs/translations/ca.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La ubicaci\u00f3 ja est\u00e0 configurada." + "already_configured": "El servei ja est\u00e0 configurat" }, "step": { "user": { diff --git a/homeassistant/components/gdacs/translations/en.json b/homeassistant/components/gdacs/translations/en.json index 8b4d3522ce204a..4e68f486e4d086 100644 --- a/homeassistant/components/gdacs/translations/en.json +++ b/homeassistant/components/gdacs/translations/en.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Location is already configured." + "already_configured": "Service is already configured" }, "step": { "user": { diff --git a/homeassistant/components/gdacs/translations/it.json b/homeassistant/components/gdacs/translations/it.json index 3fad0146c138e2..ac5c8ece1297de 100644 --- a/homeassistant/components/gdacs/translations/it.json +++ b/homeassistant/components/gdacs/translations/it.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La posizione \u00e8 gi\u00e0 configurata." + "already_configured": "Il servizio \u00e8 gi\u00e0 configurato" }, "step": { "user": { diff --git a/homeassistant/components/gdacs/translations/no.json b/homeassistant/components/gdacs/translations/no.json index 372a24c0b385be..036c660af90ac1 100644 --- a/homeassistant/components/gdacs/translations/no.json +++ b/homeassistant/components/gdacs/translations/no.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Plasseringen er allerede konfigurert." + "already_configured": "Tjenesten er allerede konfigurert" }, "step": { "user": { diff --git a/homeassistant/components/gdacs/translations/ru.json b/homeassistant/components/gdacs/translations/ru.json index b946694403784d..efad056d304f6e 100644 --- a/homeassistant/components/gdacs/translations/ru.json +++ b/homeassistant/components/gdacs/translations/ru.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u041c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043e." + "already_configured": "\u042d\u0442\u0430 \u0441\u043b\u0443\u0436\u0431\u0430 \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant." }, "step": { "user": { diff --git a/homeassistant/components/geo_json_events/geo_location.py b/homeassistant/components/geo_json_events/geo_location.py index 2fd8466cc198f4..bb2d86539e98a2 100644 --- a/homeassistant/components/geo_json_events/geo_location.py +++ b/homeassistant/components/geo_json_events/geo_location.py @@ -203,7 +203,6 @@ def unit_of_measurement(self): @property def device_state_attributes(self): """Return the device state attributes.""" - attributes = {} - if self._external_id: - attributes[ATTR_EXTERNAL_ID] = self._external_id - return attributes + if not self._external_id: + return {} + return {ATTR_EXTERNAL_ID: self._external_id} diff --git a/homeassistant/components/geo_location/trigger.py b/homeassistant/components/geo_location/trigger.py index dc8879b51e594e..415f5f48de583b 100644 --- a/homeassistant/components/geo_location/trigger.py +++ b/homeassistant/components/geo_location/trigger.py @@ -2,16 +2,11 @@ import voluptuous as vol from homeassistant.components.geo_location import DOMAIN -from homeassistant.const import ( - CONF_EVENT, - CONF_PLATFORM, - CONF_SOURCE, - CONF_ZONE, - EVENT_STATE_CHANGED, -) +from homeassistant.const import CONF_EVENT, CONF_PLATFORM, CONF_SOURCE, CONF_ZONE from homeassistant.core import 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 # mypy: allow-untyped-defs, no-check-untyped-defs @@ -45,9 +40,6 @@ async def async_attach_trigger(hass, config, action, automation_info): @callback def state_change_listener(event): """Handle specific state changes.""" - # Skip if the event is not a geo_location entity. - if not event.data.get("entity_id").startswith(DOMAIN): - return # Skip if the event's source does not match the trigger's source. from_state = event.data.get("old_state") to_state = event.data.get("new_state") @@ -83,4 +75,6 @@ def state_change_listener(event): event.context, ) - return hass.bus.async_listen(EVENT_STATE_CHANGED, state_change_listener) + return async_track_state_change_filtered( + hass, TrackStates(False, set(), {DOMAIN}), state_change_listener + ).async_remove diff --git a/homeassistant/components/geofency/strings.json b/homeassistant/components/geofency/strings.json index 1c6a72f27c8383..a7b6649fb6e6db 100644 --- a/homeassistant/components/geofency/strings.json +++ b/homeassistant/components/geofency/strings.json @@ -7,7 +7,7 @@ } }, "abort": { - "one_instance_allowed": "Only a single instance is necessary.", + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive messages from Geofency." }, "create_entry": { diff --git a/homeassistant/components/geofency/translations/ca.json b/homeassistant/components/geofency/translations/ca.json index 315b1d3be8d314..59eab112331ca7 100644 --- a/homeassistant/components/geofency/translations/ca.json +++ b/homeassistant/components/geofency/translations/ca.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "La inst\u00e0ncia de Home Assistant ha de ser accessible des d'Internet per rebre missatges de Geofency.", - "one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia." + "one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia.", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "create_entry": { "default": "Per enviar esdeveniments a Home Assistant, haur\u00e0s de configurar l'opci\u00f3 webhook de Geofency.\n\nCompleta la seg\u00fcent informaci\u00f3:\n\n- URL: `{webhook_url}` \n- M\u00e8tode: POST \n\nConsulta la [documentaci\u00f3]({docs_url}) per a m\u00e9s detalls." diff --git a/homeassistant/components/geofency/translations/el.json b/homeassistant/components/geofency/translations/el.json new file mode 100644 index 00000000000000..aecb2ee553fa42 --- /dev/null +++ b/homeassistant/components/geofency/translations/el.json @@ -0,0 +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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geofency/translations/en.json b/homeassistant/components/geofency/translations/en.json index dad5e9c77d4926..5fc9983e68ce58 100644 --- a/homeassistant/components/geofency/translations/en.json +++ b/homeassistant/components/geofency/translations/en.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive messages from Geofency.", - "one_instance_allowed": "Only a single instance is necessary." + "one_instance_allowed": "Only a single instance is necessary.", + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "create_entry": { "default": "To send events to Home Assistant, you will need to setup the webhook feature in Geofency.\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nSee [the documentation]({docs_url}) for further details." diff --git a/homeassistant/components/geofency/translations/es.json b/homeassistant/components/geofency/translations/es.json index 9149d6699bebae..0eb67f304bdf15 100644 --- a/homeassistant/components/geofency/translations/es.json +++ b/homeassistant/components/geofency/translations/es.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Tu Home Assistant debe ser accesible desde Internet para recibir mensajes de GPSLogger.", - "one_instance_allowed": "S\u00f3lo se necesita una instancia." + "one_instance_allowed": "S\u00f3lo se necesita una instancia.", + "single_instance_allowed": "Ya est\u00e1 configurado. S\u00f3lo es posible una \u00fanica configuraci\u00f3n." }, "create_entry": { "default": "Para enviar eventos a Home Assistant, necesitar\u00e1s configurar la funci\u00f3n de webhook en Geofency.\n\nRellene la siguiente informaci\u00f3n:\n\n- URL: ``{webhook_url}``\n- M\u00e9todo: POST\n\nVer[la documentaci\u00f3n]({docs_url}) para m\u00e1s detalles." diff --git a/homeassistant/components/geofency/translations/et.json b/homeassistant/components/geofency/translations/et.json new file mode 100644 index 00000000000000..e8b5eae41495c6 --- /dev/null +++ b/homeassistant/components/geofency/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/geofency/translations/fr.json b/homeassistant/components/geofency/translations/fr.json index 142f40754b9ddd..b7940cbe58cab9 100644 --- a/homeassistant/components/geofency/translations/fr.json +++ b/homeassistant/components/geofency/translations/fr.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Votre instance Home Assistant doit \u00eatre accessible \u00e0 partir d'Internet pour recevoir les messages Geofency.", - "one_instance_allowed": "Une seule instance est n\u00e9cessaire." + "one_instance_allowed": "Une seule instance est n\u00e9cessaire.", + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." }, "create_entry": { "default": "Pour envoyer des \u00e9v\u00e9nements \u00e0 Home Assistant, vous devez configurer la fonctionnalit\u00e9 Webhook dans Geofency. \n\n Remplissez les informations suivantes: \n\n - URL: ` {webhook_url} ` \n - M\u00e9thode: POST \n\n Voir [la documentation] ( {docs_url} ) pour plus de d\u00e9tails." diff --git a/homeassistant/components/geofency/translations/it.json b/homeassistant/components/geofency/translations/it.json index 4a74e58e5ca998..e061e803e287be 100644 --- a/homeassistant/components/geofency/translations/it.json +++ b/homeassistant/components/geofency/translations/it.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "La tua istanza di Home Assistant deve essere accessibile da Internet per ricevere messaggi da Geofency.", - "one_instance_allowed": "\u00c8 necessaria una sola istanza." + "one_instance_allowed": "\u00c8 necessaria una sola istanza.", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "create_entry": { "default": "Per inviare eventi a Home Assistant, dovrai configurare la funzionalit\u00e0 webhook in Geofency.\n\n Compila le seguenti informazioni: \n\n - URL: `{webhook_url}` \n - Method: POST \n\n Vedi [la documentazione]({docs_url}) for ulteriori dettagli." diff --git a/homeassistant/components/geofency/translations/lb.json b/homeassistant/components/geofency/translations/lb.json index 16f973e52603b7..fcf26deb3ada68 100644 --- a/homeassistant/components/geofency/translations/lb.json +++ b/homeassistant/components/geofency/translations/lb.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "\u00c4r Home Assistant Instanz muss iwwert Internet accessibel si fir Geofency Noriichten z'empf\u00e4nken.", - "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg." + "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg.", + "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech." }, "create_entry": { "default": "Fir Evenementer un Home Assistant ze sch\u00e9cken, muss den Webhook Feature am Geofency ageriicht ginn.\n\nF\u00ebllt folgend Informatiounen aus:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nLiest [Dokumentatioun]({docs_url}) fir w\u00e9ider D\u00e9tailer." diff --git a/homeassistant/components/geofency/translations/no.json b/homeassistant/components/geofency/translations/no.json index 8e66cab4c9c8b5..8bfedb5c0ec27b 100644 --- a/homeassistant/components/geofency/translations/no.json +++ b/homeassistant/components/geofency/translations/no.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Home Assistant m\u00e5 v\u00e6re tilgjengelig fra internett for \u00e5 kunne motta meldinger fra Geofency.", - "one_instance_allowed": "Kun en forekomst er n\u00f8dvendig." + "one_instance_allowed": "Kun en forekomst er n\u00f8dvendig.", + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "create_entry": { "default": "For \u00e5 kunne sende hendelser til Home Assistant, m\u00e5 du sette opp webhook-funksjonen i Geofency. \n\n Fyll ut f\u00f8lgende informasjon: \n\n - URL: `{webhook_url}` \n - Metode: POST \n\n Se [dokumentasjonen]({docs_url}) for ytterligere detaljer." diff --git a/homeassistant/components/geofency/translations/ru.json b/homeassistant/components/geofency/translations/ru.json index dd2a20ad61c1ec..80304f3eb26f73 100644 --- a/homeassistant/components/geofency/translations/ru.json +++ b/homeassistant/components/geofency/translations/ru.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 Geofency.", - "one_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "one_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e." }, "create_entry": { "default": "\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 Home Assistant \u0412\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Webhook \u0434\u043b\u044f Geofency.\n\n\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e:\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\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]({docs_url}) \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." diff --git a/homeassistant/components/geofency/translations/zh-Hant.json b/homeassistant/components/geofency/translations/zh-Hant.json index 95fec4ae15ef5a..494dc8ddf23240 100644 --- a/homeassistant/components/geofency/translations/zh-Hant.json +++ b/homeassistant/components/geofency/translations/zh-Hant.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Home Assistant \u8a2d\u5099\u5fc5\u9808\u80fd\u5920\u7531\u7db2\u969b\u7db2\u8def\u5b58\u53d6\uff0c\u65b9\u80fd\u63a5\u53d7 Geofency \u8a0a\u606f\u3002", - "one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u5373\u53ef\u3002" + "one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u5373\u53ef\u3002", + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002" }, "create_entry": { "default": "\u6b32\u50b3\u9001\u4e8b\u4ef6\u81f3 Home Assistant\uff0c\u5c07\u9700\u65bc Geofency \u5167\u8a2d\u5b9a webhook \u529f\u80fd\u3002\n\n\u8acb\u586b\u5beb\u4e0b\u5217\u8cc7\u8a0a\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\u8acb\u53c3\u95b1 [\u6587\u4ef6]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u8a73\u7d30\u8cc7\u6599\u3002" diff --git a/homeassistant/components/geonetnz_quakes/strings.json b/homeassistant/components/geonetnz_quakes/strings.json index fe328c05603be1..3f2d702492f981 100644 --- a/homeassistant/components/geonetnz_quakes/strings.json +++ b/homeassistant/components/geonetnz_quakes/strings.json @@ -6,6 +6,6 @@ "data": { "radius": "Radius", "mmi": "MMI" } } }, - "abort": { "already_configured": "Location is already configured." } + "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_service%]" } } } diff --git a/homeassistant/components/geonetnz_quakes/translations/ca.json b/homeassistant/components/geonetnz_quakes/translations/ca.json index e97142a6e3fe9d..2a0aff80dfd737 100644 --- a/homeassistant/components/geonetnz_quakes/translations/ca.json +++ b/homeassistant/components/geonetnz_quakes/translations/ca.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La ubicaci\u00f3 ja est\u00e0 configurada." + "already_configured": "El servei ja est\u00e0 configurat" }, "step": { "user": { diff --git a/homeassistant/components/geonetnz_quakes/translations/en.json b/homeassistant/components/geonetnz_quakes/translations/en.json index 68f73bcf0898c2..18ba6c06f6d145 100644 --- a/homeassistant/components/geonetnz_quakes/translations/en.json +++ b/homeassistant/components/geonetnz_quakes/translations/en.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Location is already configured." + "already_configured": "Service is already configured" }, "step": { "user": { diff --git a/homeassistant/components/geonetnz_quakes/translations/it.json b/homeassistant/components/geonetnz_quakes/translations/it.json index c07f04cdb644e3..a78f8f9fa2ddd9 100644 --- a/homeassistant/components/geonetnz_quakes/translations/it.json +++ b/homeassistant/components/geonetnz_quakes/translations/it.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La posizione \u00e8 gi\u00e0 configurata." + "already_configured": "Il servizio \u00e8 gi\u00e0 configurato" }, "step": { "user": { diff --git a/homeassistant/components/geonetnz_quakes/translations/no.json b/homeassistant/components/geonetnz_quakes/translations/no.json index fc3b339d807e07..24b3fb5d70c4b6 100644 --- a/homeassistant/components/geonetnz_quakes/translations/no.json +++ b/homeassistant/components/geonetnz_quakes/translations/no.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Plasseringen er allerede konfigurert." + "already_configured": "Tjenesten er allerede konfigurert" }, "step": { "user": { diff --git a/homeassistant/components/geonetnz_quakes/translations/ru.json b/homeassistant/components/geonetnz_quakes/translations/ru.json index 7ee4f64431e331..6e6fb5fe8ef142 100644 --- a/homeassistant/components/geonetnz_quakes/translations/ru.json +++ b/homeassistant/components/geonetnz_quakes/translations/ru.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "already_configured": "\u042d\u0442\u0430 \u0441\u043b\u0443\u0436\u0431\u0430 \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant." }, "step": { "user": { diff --git a/homeassistant/components/gios/strings.json b/homeassistant/components/gios/strings.json index 2187bcbc9980ca..34e99570dd041e 100644 --- a/homeassistant/components/gios/strings.json +++ b/homeassistant/components/gios/strings.json @@ -5,7 +5,7 @@ "title": "GIO\u015a (Polish Chief Inspectorate Of Environmental Protection)", "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", "data": { - "name": "Name of the integration", + "name": "[%key:common::config_flow::data::name%]", "station_id": "ID of the measuring station" } } @@ -13,10 +13,10 @@ "error": { "wrong_station_id": "ID of the measuring station is not correct.", "invalid_sensors_data": "Invalid sensors' data for this measuring station.", - "cannot_connect": "Cannot connect to the GIO\u015a server." + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, "abort": { - "already_configured": "GIO\u015a integration for this measuring station is already configured." + "already_configured": "[%key:common::config_flow::abort::already_configured_service%] for this measuring station" } } } diff --git a/homeassistant/components/gios/translations/ca.json b/homeassistant/components/gios/translations/ca.json index 29703281b087ce..06e405d78c6003 100644 --- a/homeassistant/components/gios/translations/ca.json +++ b/homeassistant/components/gios/translations/ca.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "La integraci\u00f3 GIO\u015a per a aquesta estaci\u00f3 ja est\u00e0 configurada." + "already_configured": "El servei ja est\u00e0 configurat" }, "error": { - "cannot_connect": "No s'ha pogut connectar al servidor de GIO\u015a.", + "cannot_connect": "Ha fallat la connexi\u00f3", "invalid_sensors_data": "Les dades dels sensors d'aquesta estaci\u00f3 de mesura s\u00f3n inv\u00e0lides.", "wrong_station_id": "L'ID de l'estaci\u00f3 de mesura \u00e9s incorrecte." }, diff --git a/homeassistant/components/gios/translations/en.json b/homeassistant/components/gios/translations/en.json index 3d07ad843bd1aa..b3a3842db83c5a 100644 --- a/homeassistant/components/gios/translations/en.json +++ b/homeassistant/components/gios/translations/en.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "GIO\u015a integration for this measuring station is already configured." + "already_configured": "Service is already configured" }, "error": { - "cannot_connect": "Cannot connect to the GIO\u015a server.", + "cannot_connect": "Failed to connect", "invalid_sensors_data": "Invalid sensors' data for this measuring station.", "wrong_station_id": "ID of the measuring station is not correct." }, diff --git a/homeassistant/components/gios/translations/it.json b/homeassistant/components/gios/translations/it.json index e49fe2ebfe8d46..72df1b3d01d1fb 100644 --- a/homeassistant/components/gios/translations/it.json +++ b/homeassistant/components/gios/translations/it.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "L'integrazione GIO\u015a per questa stazione di misurazione \u00e8 gi\u00e0 configurata." + "already_configured": "Il servizio \u00e8 gi\u00e0 configurato" }, "error": { - "cannot_connect": "Impossibile connettersi al server GIO\u015a.", + "cannot_connect": "Impossibile connettersi", "invalid_sensors_data": "Dati dei sensori non validi per questa stazione di misura.", "wrong_station_id": "L'ID della stazione di misura non \u00e8 corretto." }, diff --git a/homeassistant/components/gios/translations/no.json b/homeassistant/components/gios/translations/no.json index 784b75c9ee5478..de3e038eaaf73c 100644 --- a/homeassistant/components/gios/translations/no.json +++ b/homeassistant/components/gios/translations/no.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "GIO\u015a-integrasjon for denne m\u00e5lestasjonen er allerede konfigurert." + "already_configured": "Tjenesten er allerede konfigurert" }, "error": { - "cannot_connect": "Kan ikke koble til GIO\u015a-tjeneren", + "cannot_connect": "Tilkobling mislyktes.", "invalid_sensors_data": "Ugyldig sensordata for denne m\u00e5lestasjonen", "wrong_station_id": "ID for m\u00e5lestasjon er ikke korrekt" }, diff --git a/homeassistant/components/gios/translations/ru.json b/homeassistant/components/gios/translations/ru.json index ca94b617c9374e..50a8560b00cc1c 100644 --- a/homeassistant/components/gios/translations/ru.json +++ b/homeassistant/components/gios/translations/ru.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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 \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 GIO\u015a.", + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "invalid_sensors_data": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0441\u0435\u043d\u0441\u043e\u0440\u043e\u0432 \u0434\u043b\u044f \u044d\u0442\u043e\u0439 \u0438\u0437\u043c\u0435\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0441\u0442\u0430\u043d\u0446\u0438\u0438.", "wrong_station_id": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 ID \u0438\u0437\u043c\u0435\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0441\u0442\u0430\u043d\u0446\u0438\u0438." }, diff --git a/homeassistant/components/gios/translations/zh-Hant.json b/homeassistant/components/gios/translations/zh-Hant.json index 0d75f83f9e529c..5642c1250ba97f 100644 --- a/homeassistant/components/gios/translations/zh-Hant.json +++ b/homeassistant/components/gios/translations/zh-Hant.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "\u6b64 GIO\u015a \u76e3\u6e2c\u7ad9\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3002" + "already_configured": "\u670d\u52d9\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { - "cannot_connect": "\u7121\u6cd5\u9023\u7dda\u81f3 GIO\u015a \u4f3a\u670d\u5668\u3002", + "cannot_connect": "\u9023\u7dda\u5931\u6557", "invalid_sensors_data": "\u6b64\u76e3\u6e2c\u7ad9\u50b3\u611f\u5668\u8cc7\u6599\u7121\u6548\u3002", "wrong_station_id": "\u76e3\u6e2c\u7ad9 ID \u4e0d\u6b63\u78ba\u3002" }, diff --git a/homeassistant/components/glances/strings.json b/homeassistant/components/glances/strings.json index 68f59c50f985ef..5d96b1ae57e172 100644 --- a/homeassistant/components/glances/strings.json +++ b/homeassistant/components/glances/strings.json @@ -4,23 +4,23 @@ "user": { "title": "Setup Glances", "data": { - "name": "Name", + "name": "[%key:common::config_flow::data::name%]", "host": "[%key:common::config_flow::data::host%]", "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]", "port": "[%key:common::config_flow::data::port%]", "version": "Glances API Version (2 or 3)", - "ssl": "Use SSL/TLS to connect to the Glances system", - "verify_ssl": "Verify the certification of the system" + "ssl": "[%key:common::config_flow::data::ssl%]", + "verify_ssl": "[%key:common::config_flow::data::verify_ssl%]" } } }, "error": { - "cannot_connect": "Unable to connect to host", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "wrong_version": "Version not supported (2 or 3 only)" }, "abort": { - "already_configured": "Host is already configured." + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } }, "options": { @@ -33,4 +33,4 @@ } } } -} \ No newline at end of file +} diff --git a/homeassistant/components/glances/translations/ca.json b/homeassistant/components/glances/translations/ca.json index 7da63024f8b39c..1ef17e201a4abf 100644 --- a/homeassistant/components/glances/translations/ca.json +++ b/homeassistant/components/glances/translations/ca.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "L'amfitri\u00f3 ja est\u00e0 configurat." + "already_configured": "El dispositiu ja est\u00e0 configurat" }, "error": { - "cannot_connect": "No s'ha pogut connectar amb l'amfitri\u00f3", + "cannot_connect": "Ha fallat la connexi\u00f3", "wrong_version": "Versi\u00f3 no compatible (2 o 3 necess\u00e0ria)" }, "step": { @@ -14,9 +14,9 @@ "name": "Nom", "password": "Contrasenya", "port": "Port", - "ssl": "Utilitza SSL/TLS per connectar-te al sistema Glances", + "ssl": "Utilitza un certificat SSL", "username": "Nom d'usuari", - "verify_ssl": "Verifica la certificaci\u00f3 del sistema", + "verify_ssl": "Verifica el certificat SSL", "version": "Versi\u00f3 de l'API de Glances (2 o 3)" }, "title": "Configuraci\u00f3 de Glances" diff --git a/homeassistant/components/glances/translations/en.json b/homeassistant/components/glances/translations/en.json index 0330e8cef653b2..87c53c3cf48a2f 100644 --- a/homeassistant/components/glances/translations/en.json +++ b/homeassistant/components/glances/translations/en.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Host is already configured." + "already_configured": "Device is already configured" }, "error": { - "cannot_connect": "Unable to connect to host", + "cannot_connect": "Failed to connect", "wrong_version": "Version not supported (2 or 3 only)" }, "step": { @@ -14,9 +14,9 @@ "name": "Name", "password": "Password", "port": "Port", - "ssl": "Use SSL/TLS to connect to the Glances system", + "ssl": "Uses an SSL certificate", "username": "Username", - "verify_ssl": "Verify the certification of the system", + "verify_ssl": "Verify SSL certificate", "version": "Glances API Version (2 or 3)" }, "title": "Setup Glances" diff --git a/homeassistant/components/glances/translations/it.json b/homeassistant/components/glances/translations/it.json index 7e8d5af6d8fd71..f4806c5d95266c 100644 --- a/homeassistant/components/glances/translations/it.json +++ b/homeassistant/components/glances/translations/it.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "L'host \u00e8 gi\u00e0 configurato." + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" }, "error": { - "cannot_connect": "Impossibile connettersi all'host", + "cannot_connect": "Impossibile connettersi", "wrong_version": "Versione non supportata (solo 2 o 3)" }, "step": { @@ -14,9 +14,9 @@ "name": "Nome", "password": "Password", "port": "Porta", - "ssl": "Utilizzare SSL/TLS per connettersi al sistema Glances", + "ssl": "Utilizza un certificato SSL", "username": "Nome utente", - "verify_ssl": "Verificare la certificazione del sistema", + "verify_ssl": "Verificare il certificato SSL", "version": "Glances API Version (2 o 3)" }, "title": "Impostare Glances" diff --git a/homeassistant/components/glances/translations/no.json b/homeassistant/components/glances/translations/no.json index dd593c4add6f03..bac0387549a43d 100644 --- a/homeassistant/components/glances/translations/no.json +++ b/homeassistant/components/glances/translations/no.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Verten er allerede konfigurert." + "already_configured": "Enheten er allerede konfigurert" }, "error": { - "cannot_connect": "Kan ikke koble til vert", + "cannot_connect": "Tilkobling mislyktes.", "wrong_version": "Versjonen st\u00f8ttes ikke (bare 2 eller 3)" }, "step": { @@ -14,9 +14,9 @@ "name": "Navn", "password": "Passord", "port": "", - "ssl": "Bruk SSL / TLS for \u00e5 koble til Glances-systemet", + "ssl": "Bruker et SSL-sertifikat", "username": "Brukernavn", - "verify_ssl": "Bekreft sertifiseringen av systemet", + "verify_ssl": "Verifisere SSL-sertifikat", "version": "Glances API-versjon (2 eller 3)" }, "title": "Oppsett av Glances" diff --git a/homeassistant/components/glances/translations/ru.json b/homeassistant/components/glances/translations/ru.json index d87bcb536cf7cd..0dc8c72dc9f55e 100644 --- a/homeassistant/components/glances/translations/ru.json +++ b/homeassistant/components/glances/translations/ru.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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 \u043a \u0445\u043e\u0441\u0442\u0443.", + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "wrong_version": "\u041f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0432\u0435\u0440\u0441\u0438\u0438 2 \u0438 3." }, "step": { @@ -14,9 +14,9 @@ "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", - "ssl": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c SSL / TLS \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f", + "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": "\u041b\u043e\u0433\u0438\u043d", - "verify_ssl": "\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e \u0441\u0438\u0441\u0442\u0435\u043c\u044b", + "verify_ssl": "\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL", "version": "\u0412\u0435\u0440\u0441\u0438\u044f API Glances (2 \u0438\u043b\u0438 3)" }, "title": "Glances" diff --git a/homeassistant/components/glances/translations/zh-Hant.json b/homeassistant/components/glances/translations/zh-Hant.json index dd7c3711e3733b..0054edbdb0d9e6 100644 --- a/homeassistant/components/glances/translations/zh-Hant.json +++ b/homeassistant/components/glances/translations/zh-Hant.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "\u4e3b\u6a5f\u7aef\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3002" + "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { - "cannot_connect": "\u7121\u6cd5\u9023\u7dda\u81f3\u4e3b\u6a5f\u7aef", + "cannot_connect": "\u9023\u7dda\u5931\u6557", "wrong_version": "\u7248\u672c\u4e0d\u652f\u63f4\uff08\u50c5 2 \u6216 3\uff09" }, "step": { @@ -14,9 +14,9 @@ "name": "\u540d\u7a31", "password": "\u5bc6\u78bc", "port": "\u901a\u8a0a\u57e0", - "ssl": "\u4f7f\u7528 SSL/TLS \u9023\u7dda\u81f3 Glances \u7cfb\u7d71", + "ssl": "\u4f7f\u7528 SSL \u8a8d\u8b49", "username": "\u4f7f\u7528\u8005\u540d\u7a31", - "verify_ssl": "\u9a57\u8b49\u7cfb\u7d71\u8a8d\u8b49", + "verify_ssl": "\u78ba\u8a8d SSL \u8a8d\u8b49", "version": "Glances API \u7248\u672c\uff082 \u6216 3\uff09" }, "title": "\u8a2d\u5b9a Glances" diff --git a/homeassistant/components/goalzero/__init__.py b/homeassistant/components/goalzero/__init__.py new file mode 100644 index 00000000000000..ff60a9ac0435bf --- /dev/null +++ b/homeassistant/components/goalzero/__init__.py @@ -0,0 +1,112 @@ +"""The Goal Zero Yeti integration.""" +import asyncio +import logging + +from goalzero import Yeti, exceptions + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_HOST, CONF_NAME +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, + UpdateFailed, +) + +from .const import DATA_KEY_API, DATA_KEY_COORDINATOR, DOMAIN, MIN_TIME_BETWEEN_UPDATES + +_LOGGER = logging.getLogger(__name__) + + +PLATFORMS = ["binary_sensor"] + + +async def async_setup(hass: HomeAssistant, config): + """Set up the Goal Zero Yeti component.""" + + hass.data[DOMAIN] = {} + + return True + + +async def async_setup_entry(hass, entry): + """Set up Goal Zero Yeti from a config entry.""" + name = entry.data[CONF_NAME] + host = entry.data[CONF_HOST] + + session = async_get_clientsession(hass) + api = Yeti(host, hass.loop, session) + try: + await api.get_state() + except exceptions.ConnectError as ex: + _LOGGER.warning("Failed to connect: %s", ex) + raise ConfigEntryNotReady from ex + + async def async_update_data(): + """Fetch data from API endpoint.""" + try: + await api.get_state() + except exceptions.ConnectError as err: + raise UpdateFailed(f"Failed to communicating with API: {err}") from err + + coordinator = DataUpdateCoordinator( + hass, + _LOGGER, + name=name, + update_method=async_update_data, + update_interval=MIN_TIME_BETWEEN_UPDATES, + ) + hass.data[DOMAIN][entry.entry_id] = { + DATA_KEY_API: api, + DATA_KEY_COORDINATOR: coordinator, + } + + for platform in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, platform) + ) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Unload a config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, component) + for component in PLATFORMS + ] + ) + ) + if unload_ok: + hass.data[DOMAIN].pop(entry.entry_id) + return unload_ok + + +class YetiEntity(CoordinatorEntity): + """Representation of a Goal Zero Yeti entity.""" + + def __init__(self, api, coordinator, name, server_unique_id): + """Initialize a Goal Zero Yeti entity.""" + super().__init__(coordinator) + self.api = api + self._name = name + self._server_unique_id = server_unique_id + self._device_class = None + + @property + def device_info(self): + """Return the device information of the entity.""" + return { + "identifiers": {(DOMAIN, self._server_unique_id)}, + "name": self._name, + "manufacturer": "Goal Zero", + } + + @property + def device_class(self): + """Return the class of this device.""" + return self._device_class diff --git a/homeassistant/components/goalzero/binary_sensor.py b/homeassistant/components/goalzero/binary_sensor.py new file mode 100644 index 00000000000000..a2af8a18546bba --- /dev/null +++ b/homeassistant/components/goalzero/binary_sensor.py @@ -0,0 +1,62 @@ +"""Support for Goal Zero Yeti Sensors.""" +from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.const import CONF_NAME + +from . import YetiEntity +from .const import BINARY_SENSOR_DICT, DATA_KEY_API, DATA_KEY_COORDINATOR, DOMAIN + +PARALLEL_UPDATES = 0 + + +async def async_setup_entry(hass, entry, async_add_entities): + """Set up the Goal Zero Yeti sensor.""" + name = entry.data[CONF_NAME] + goalzero_data = hass.data[DOMAIN][entry.entry_id] + sensors = [ + YetiBinarySensor( + goalzero_data[DATA_KEY_API], + goalzero_data[DATA_KEY_COORDINATOR], + name, + sensor_name, + entry.entry_id, + ) + for sensor_name in BINARY_SENSOR_DICT + ] + async_add_entities(sensors, True) + + +class YetiBinarySensor(YetiEntity, BinarySensorEntity): + """Representation of a Goal Zero Yeti sensor.""" + + def __init__(self, api, coordinator, name, sensor_name, server_unique_id): + """Initialize a Goal Zero Yeti sensor.""" + super().__init__(api, coordinator, name, server_unique_id) + + self._condition = sensor_name + + variable_info = BINARY_SENSOR_DICT[sensor_name] + self._condition_name = variable_info[0] + self._icon = variable_info[2] + self._device_class = variable_info[1] + + @property + def name(self): + """Return the name of the sensor.""" + return f"{self._name} {self._condition_name}" + + @property + def unique_id(self): + """Return the unique id of the sensor.""" + return f"{self._server_unique_id}/{self._condition_name}" + + @property + def is_on(self): + """Return if the service is on.""" + if self.api.data: + return self.api.data[self._condition] == 1 + return False + + @property + def icon(self): + """Icon to use in the frontend, if any.""" + return self._icon diff --git a/homeassistant/components/goalzero/config_flow.py b/homeassistant/components/goalzero/config_flow.py new file mode 100644 index 00000000000000..35b6953865c35b --- /dev/null +++ b/homeassistant/components/goalzero/config_flow.py @@ -0,0 +1,77 @@ +"""Config flow for Goal Zero Yeti integration.""" +import logging + +from goalzero import Yeti, exceptions +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_HOST, CONF_NAME +from homeassistant.helpers.aiohttp_client import async_get_clientsession + +from .const import DEFAULT_NAME, DOMAIN # pylint:disable=unused-import + +_LOGGER = logging.getLogger(__name__) + +DATA_SCHEMA = vol.Schema({"host": str, "name": str}) + + +class GoalZeroFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Goal Zero Yeti.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL + + async def async_step_user(self, user_input=None): + """Handle a flow initiated by the user.""" + errors = {} + + if user_input is not None: + host = user_input[CONF_HOST] + name = user_input[CONF_NAME] + + if await self._async_endpoint_existed(host): + return self.async_abort(reason="already_configured") + + try: + await self._async_try_connect(host) + except exceptions.ConnectError: + errors["base"] = "cannot_connect" + _LOGGER.error("Error connecting to device at %s", host) + except exceptions.InvalidHost: + errors["base"] = "invalid_host" + _LOGGER.error("Invalid host at %s", host) + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + else: + return self.async_create_entry( + title=name, + data={CONF_HOST: host, CONF_NAME: name}, + ) + + user_input = user_input or {} + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required( + CONF_HOST, default=user_input.get(CONF_HOST) or "" + ): str, + vol.Optional( + CONF_NAME, default=user_input.get(CONF_NAME) or DEFAULT_NAME + ): str, + } + ), + errors=errors, + ) + + async def _async_endpoint_existed(self, endpoint): + for entry in self._async_current_entries(): + if endpoint == entry.data.get(CONF_HOST): + return True + return False + + async def _async_try_connect(self, host): + session = async_get_clientsession(self.hass) + api = Yeti(host, self.hass.loop, session) + await api.get_state() diff --git a/homeassistant/components/goalzero/const.py b/homeassistant/components/goalzero/const.py new file mode 100644 index 00000000000000..3afa1e537c173d --- /dev/null +++ b/homeassistant/components/goalzero/const.py @@ -0,0 +1,28 @@ +"""Constants for the Goal Zero Yeti integration.""" +from datetime import timedelta + +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_BATTERY_CHARGING, + DEVICE_CLASS_CONNECTIVITY, + DEVICE_CLASS_POWER, +) + +DATA_KEY_COORDINATOR = "coordinator" +DOMAIN = "goalzero" +DEFAULT_NAME = "Yeti" +DATA_KEY_API = "api" + +MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30) + +BINARY_SENSOR_DICT = { + "v12PortStatus": ["12V Port Status", DEVICE_CLASS_POWER, None], + "usbPortStatus": ["USB Port Status", DEVICE_CLASS_POWER, None], + "acPortStatus": ["AC Port Status", DEVICE_CLASS_POWER, None], + "backlight": ["Backlight", None, "mdi:clock-digital"], + "app_online": [ + "App Online", + DEVICE_CLASS_CONNECTIVITY, + None, + ], + "isCharging": ["Charging", DEVICE_CLASS_BATTERY_CHARGING, None], +} diff --git a/homeassistant/components/goalzero/manifest.json b/homeassistant/components/goalzero/manifest.json new file mode 100644 index 00000000000000..803b8f7eaae01d --- /dev/null +++ b/homeassistant/components/goalzero/manifest.json @@ -0,0 +1,8 @@ +{ + "domain": "goalzero", + "name": "Goal Zero Yeti", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/goalzero", + "requirements": ["goalzero==0.1.4"], + "codeowners": ["@tkdrob"] +} diff --git a/homeassistant/components/goalzero/strings.json b/homeassistant/components/goalzero/strings.json new file mode 100644 index 00000000000000..c16b3b283b5ab3 --- /dev/null +++ b/homeassistant/components/goalzero/strings.json @@ -0,0 +1,22 @@ +{ + "config": { + "step": { + "user": { + "title": "Goal Zero Yeti", + "description": "First, you need to download the Goal Zero app: https://www.goalzero.com/product-features/yeti-app/\n\nFollow the instructions to connect your Yeti to your Wifi network. Then get the host ip from your router. DHCP must be set up in your router settings for the device to ensure the host ip does not change. Refer to your router's user manual.", + "data": { + "host": "[%key:common::config_flow::data::host%]", + "name": "[%key:common::config_flow::data::name%]" + } + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_host": "Invalid host provided", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" + } + } +} diff --git a/homeassistant/components/goalzero/translations/ca.json b/homeassistant/components/goalzero/translations/ca.json new file mode 100644 index 00000000000000..00999a0b54eacd --- /dev/null +++ b/homeassistant/components/goalzero/translations/ca.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "El compte ja ha estat configurat" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_host": "L'amfitri\u00f3 proporcionat no \u00e9s v\u00e0lid", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "host": "Amfitri\u00f3", + "name": "Nom" + }, + "description": "En primer lloc, has de baixar-te l'aplicaci\u00f3 Goal Zero: https://www.goalzero.com/product-features/yeti-app/ \n\nSegueix les instruccions per connectar el teu Yeti a la teva xarxa Wifi. A continuaci\u00f3, has d'obtenir la IP d'amfitri\u00f3 del teu encaminador (router). Cal que aquest tingui la configuraci\u00f3 DHCP activada per al teu dispositiu per aix\u00ed garantir que la IP no canvi\u00ef. Si cal, consulta el manual del teu encaminador.", + "title": "Goal Zero Yeti" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/goalzero/translations/el.json b/homeassistant/components/goalzero/translations/el.json new file mode 100644 index 00000000000000..61936c6ff56080 --- /dev/null +++ b/homeassistant/components/goalzero/translations/el.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03ae\u03b4\u03b7 \u03c0\u03b1\u03c1\u03b1\u03bc\u03b5\u03c4\u03c1\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf\u03c2" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2", + "invalid_host": "\u0391\u03c5\u03c4\u03cc \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c4\u03bf Yeti \u03c0\u03bf\u03c5 \u03c8\u03ac\u03c7\u03bd\u03b5\u03c4\u03b5", + "unknown": "\u0386\u03b3\u03bd\u03c9\u03c3\u03c4\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "step": { + "user": { + "data": { + "host": "\u0394\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2", + "name": "\u038c\u03bd\u03bf\u03bc\u03b1" + }, + "description": "\u0391\u03c1\u03c7\u03b9\u03ba\u03ac, \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\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03bb\u03ac\u03b2\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd ip \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae \u03b1\u03c0\u03cc \u03c4\u03bf \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae \u03c3\u03b1\u03c2. \u03a4\u03bf DHCP \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af \u03c3\u03c4\u03b9\u03c2 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 \u03c4\u03bf\u03c5 \u03b4\u03c1\u03bf\u03bc\u03bf\u03bb\u03bf\u03b3\u03b7\u03c4\u03ae \u03c3\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b3\u03b9\u03b1 \u03bd\u03b1 \u03b4\u03b9\u03b1\u03c3\u03c6\u03b1\u03bb\u03b9\u03c3\u03c4\u03b5\u03af \u03cc\u03c4\u03b9 \u03b7 ip \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae \u03b4\u03b5\u03bd \u03b8\u03b1 \u03b1\u03bb\u03bb\u03ac\u03be\u03b5\u03b9. \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" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/goalzero/translations/en.json b/homeassistant/components/goalzero/translations/en.json new file mode 100644 index 00000000000000..0bba36a58c6ffc --- /dev/null +++ b/homeassistant/components/goalzero/translations/en.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Account is already configured" + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_host": "Invalid host provided", + "unknown": "Unexpected error" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Name" + }, + "description": "First, you need to download the Goal Zero app: https://www.goalzero.com/product-features/yeti-app/\n\nFollow the instructions to connect your Yeti to your Wifi network. Then get the host ip from your router. DHCP must be set up in your router settings for the device to ensure the host ip does not change. Refer to your router's user manual.", + "title": "Goal Zero Yeti" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/goalzero/translations/es.json b/homeassistant/components/goalzero/translations/es.json new file mode 100644 index 00000000000000..4897899d8c36ba --- /dev/null +++ b/homeassistant/components/goalzero/translations/es.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "La cuenta ya ha sido configurada" + }, + "error": { + "cannot_connect": "No se pudo conectar", + "invalid_host": "Este no es el Yeti que est\u00e1s buscando", + "unknown": "Error desconocido" + }, + "step": { + "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" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/goalzero/translations/et.json b/homeassistant/components/goalzero/translations/et.json new file mode 100644 index 00000000000000..7c995db2529d25 --- /dev/null +++ b/homeassistant/components/goalzero/translations/et.json @@ -0,0 +1,15 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00dchendus nurjus", + "unknown": "Tundmatu viga" + }, + "step": { + "user": { + "data": { + "name": "Nimi" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/goalzero/translations/fr.json b/homeassistant/components/goalzero/translations/fr.json new file mode 100644 index 00000000000000..a155e8370d1037 --- /dev/null +++ b/homeassistant/components/goalzero/translations/fr.json @@ -0,0 +1,19 @@ +{ + "config": { + "abort": { + "already_configured": "Le compte a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9" + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "unknown": "Erreur inconnue" + }, + "step": { + "user": { + "data": { + "host": "H\u00f4te", + "name": "Nom" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/goalzero/translations/it.json b/homeassistant/components/goalzero/translations/it.json new file mode 100644 index 00000000000000..22d081e9c4f77f --- /dev/null +++ b/homeassistant/components/goalzero/translations/it.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "L'account \u00e8 gi\u00e0 configurato" + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_host": "Host fornito non valido", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "host": "Host", + "name": "Nome" + }, + "description": "Innanzitutto, devi scaricare l'app Goal Zero: https://www.goalzero.com/product-features/yeti-app/\n\nSegui le istruzioni per connettere il tuo Yeti alla tua rete Wifi. Quindi ottieni l'ip host dal tuo router. Il DHCP deve essere configurato nelle impostazioni del router affinch\u00e9 il dispositivo assicuri che l'ip host non cambi. Fare riferimento al manuale utente del router.", + "title": "Goal Zero Yeti" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/goalzero/translations/lb.json b/homeassistant/components/goalzero/translations/lb.json new file mode 100644 index 00000000000000..5972c69873b72f --- /dev/null +++ b/homeassistant/components/goalzero/translations/lb.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Kont ass scho konfigur\u00e9iert" + }, + "error": { + "cannot_connect": "Feeler beim verbannen", + "invalid_host": "D\u00ebst ass net de gesichte Yeti", + "unknown": "Onbekannte Feeler" + }, + "step": { + "user": { + "data": { + "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" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/goalzero/translations/no.json b/homeassistant/components/goalzero/translations/no.json new file mode 100644 index 00000000000000..86d873ef5f7b52 --- /dev/null +++ b/homeassistant/components/goalzero/translations/no.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Kontoen er allerede konfigurert" + }, + "error": { + "cannot_connect": "Tilkobling mislyktes.", + "invalid_host": "Ugyldig vert gitt", + "unknown": "Ukjent feil" + }, + "step": { + "user": { + "data": { + "host": "Vert", + "name": "Navn" + }, + "description": "F\u00f8rst m\u00e5 du laste ned appen Goal Zero: https://www.goalzero.com/product-features/yeti-app/ \n\n F\u00f8lg instruksjonene for \u00e5 koble Yeti til Wifi-nettverket. S\u00e5 f\u00e5 verts-ip fra ruteren din. DHCP m\u00e5 v\u00e6re satt opp i ruteren innstillinger for enheten for \u00e5 sikre at verts-IP ikke endres. Se brukerh\u00e5ndboken til ruteren.", + "title": "Goal Zero Yeti" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/goalzero/translations/pl.json b/homeassistant/components/goalzero/translations/pl.json new file mode 100644 index 00000000000000..a5f2edec12e434 --- /dev/null +++ b/homeassistant/components/goalzero/translations/pl.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Konto jest ju\u017c skonfigurowane" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_host": "Podano nieprawid\u0142owy host", + "unknown": "Nieznany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "host": "Nazwa hosta lub adres IP" + }, + "description": "Najpierw musisz pobra\u0107 aplikacj\u0119 Goal Zero: https://www.goalzero.com/product-features/yeti-app/\n\nPost\u0119puj zgodnie z instrukcjami, aby pod\u0142\u0105czy\u0107 Yeti do sieci Wi-Fi. Nast\u0119pnie uzyskaj adres IP hosta z routera. W ustawieniach routera nale\u017cy skonfigurowa\u0107 DHCP, aby upewni\u0107 si\u0119, \u017ce adres IP hosta nie ulegnie zmianie. Post\u0119puj wg instrukcji obs\u0142ugi routera.", + "title": "Goal Zero Yeti" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/goalzero/translations/ru.json b/homeassistant/components/goalzero/translations/ru.json new file mode 100644 index 00000000000000..80c349f4c0f1d5 --- /dev/null +++ b/homeassistant/components/goalzero/translations/ru.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\u042d\u0442\u0430 \u0443\u0447\u0451\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant." + }, + "error": { + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "invalid_host": "\u0423\u043a\u0430\u0437\u0430\u043d \u043d\u0435\u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u044b\u0439 \u0445\u043e\u0441\u0442.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + }, + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435" + }, + "description": "\u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u0412\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0441\u043a\u0430\u0447\u0430\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 Goal Zero: https://www.goalzero.com/product-features/yeti-app/.\n\n\u0421\u043b\u0435\u0434\u0443\u0439\u0442\u0435 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c \u043f\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044e Yeti \u043a \u0441\u0435\u0442\u0438 WiFi. \u0417\u0430\u0442\u0435\u043c \u0443\u0437\u043d\u0430\u0439\u0442\u0435 IP \u0430\u0434\u0440\u0435\u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0438\u0437 \u0412\u0430\u0448\u0435\u0433\u043e \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430. \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. \u041e \u0442\u043e\u043c, \u043a\u0430\u043a \u044d\u0442\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c, \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u0437\u043d\u0430\u0442\u044c \u0432 \u0440\u0443\u043a\u043e\u0432\u043e\u0434\u0441\u0442\u0432\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0412\u0430\u0448\u0435\u0433\u043e \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0442\u043e\u0440\u0430.", + "title": "Goal Zero Yeti" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/goalzero/translations/zh-Hant.json b/homeassistant/components/goalzero/translations/zh-Hant.json new file mode 100644 index 00000000000000..8eb51501659690 --- /dev/null +++ b/homeassistant/components/goalzero/translations/zh-Hant.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_host": "\u6240\u63d0\u4f9b\u7684\u4e3b\u6a5f\u7aef\u7121\u6548", + "unknown": "\u672a\u77e5\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "host": "\u4e3b\u6a5f\u7aef", + "name": "\u540d\u7a31" + }, + "description": "\u60a8\u9996\u5148\u5fc5\u9808\u5148\u4e0b\u8f09 Goal Zero app\uff1ahttps://www.goalzero.com/product-features/yeti-app/\n\n\u8ddf\u96a8\u6307\u793a\u5c07 Yeti \u9023\u7dda\u81f3\u7121\u7dda\u7db2\u8def\u3002\u63a5\u8005\u7531\u8def\u7531\u5668\u53d6\u5f97\u4e3b\u6a5f\u7aef IP\uff0c \u5fc5\u9808\u65bc\u8def\u7531\u5668\u5167\u8a2d\u5b9a\u8a2d\u5099\u7684 DHCP \u4ee5\u78ba\u4fdd\u4e3b\u6a5f\u7aef IP \u4e0d\u81f3\u65bc\u6539\u8b8a\u3002\u8acb\u53c3\u8003\u60a8\u7684\u8def\u7531\u5668\u624b\u518a\u4e86\u89e3\u5982\u4f55\u64cd\u4f5c\u3002", + "title": "Goal Zero Yeti" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gogogate2/const.py b/homeassistant/components/gogogate2/const.py index 5c0ef55ff3fc0b..2f6ac76122fcd6 100644 --- a/homeassistant/components/gogogate2/const.py +++ b/homeassistant/components/gogogate2/const.py @@ -4,3 +4,4 @@ DATA_UPDATE_COORDINATOR = "data_update_coordinator" DEVICE_TYPE_GOGOGATE2 = "gogogate2" DEVICE_TYPE_ISMARTGATE = "ismartgate" +MANUFACTURER = "Remsol" diff --git a/homeassistant/components/gogogate2/cover.py b/homeassistant/components/gogogate2/cover.py index 8e753eb6ae5eb0..e748b420edbe8a 100644 --- a/homeassistant/components/gogogate2/cover.py +++ b/homeassistant/components/gogogate2/cover.py @@ -34,7 +34,7 @@ cover_unique_id, get_data_update_coordinator, ) -from .const import DEVICE_TYPE_GOGOGATE2, DEVICE_TYPE_ISMARTGATE, DOMAIN +from .const import DEVICE_TYPE_GOGOGATE2, DEVICE_TYPE_ISMARTGATE, DOMAIN, MANUFACTURER _LOGGER = logging.getLogger(__name__) @@ -154,3 +154,15 @@ def _get_door(self) -> AbstractDoor: door = get_door_by_id(self._door.door_id, self.coordinator.data) self._door = door or self._door return self._door + + @property + def device_info(self): + """Device info for the controller.""" + data = self.coordinator.data + return { + "identifiers": {(DOMAIN, self._config_entry.unique_id)}, + "name": self._config_entry.title, + "manufacturer": MANUFACTURER, + "model": data.model, + "sw_version": data.firmwareversion, + } diff --git a/homeassistant/components/gogogate2/manifest.json b/homeassistant/components/gogogate2/manifest.json index edf69144f624c8..893294da25e699 100644 --- a/homeassistant/components/gogogate2/manifest.json +++ b/homeassistant/components/gogogate2/manifest.json @@ -3,7 +3,7 @@ "name": "Gogogate2 and iSmartGate", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/gogogate2", - "requirements": ["gogogate2-api==2.0.1"], + "requirements": ["gogogate2-api==2.0.3"], "codeowners": ["@vangorra"], "homekit": { "models": [ diff --git a/homeassistant/components/gogogate2/strings.json b/homeassistant/components/gogogate2/strings.json index 47a53d0d320988..f5385ff5d54249 100644 --- a/homeassistant/components/gogogate2/strings.json +++ b/homeassistant/components/gogogate2/strings.json @@ -12,7 +12,7 @@ "title": "Setup GogoGate2 or iSmartGate", "description": "Provide requisite information below.", "data": { - "ip_address": "IP Address", + "ip_address": "[%key:common::config_flow::data::ip%]", "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]" } diff --git a/homeassistant/components/gogogate2/translations/fr.json b/homeassistant/components/gogogate2/translations/fr.json index 478e7e8ccf8004..79f216738c4cc9 100644 --- a/homeassistant/components/gogogate2/translations/fr.json +++ b/homeassistant/components/gogogate2/translations/fr.json @@ -15,7 +15,7 @@ "username": "Nom d'utilisateur" }, "description": "Fournissez les informations requises ci-dessous.", - "title": "Configurer GogoGate2" + "title": "Configurer GogoGate2 ou iSmartGate" } } } diff --git a/homeassistant/components/gogogate2/translations/pl.json b/homeassistant/components/gogogate2/translations/pl.json index 7a6c33be781ae4..2ea10491255858 100644 --- a/homeassistant/components/gogogate2/translations/pl.json +++ b/homeassistant/components/gogogate2/translations/pl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia." + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, "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", + "invalid_auth": "Niepoprawne uwierzytelnienie" }, "step": { "user": { diff --git a/homeassistant/components/google_assistant/http.py b/homeassistant/components/google_assistant/http.py index 7b75a36f8bb928..4bf0df8b933022 100644 --- a/homeassistant/components/google_assistant/http.py +++ b/homeassistant/components/google_assistant/http.py @@ -10,7 +10,11 @@ # Typing imports from homeassistant.components.http import HomeAssistantView -from homeassistant.const import CLOUD_NEVER_EXPOSED_ENTITIES, HTTP_INTERNAL_SERVER_ERROR +from homeassistant.const import ( + CLOUD_NEVER_EXPOSED_ENTITIES, + HTTP_INTERNAL_SERVER_ERROR, + HTTP_UNAUTHORIZED, +) from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.util import dt as dt_util @@ -200,7 +204,7 @@ async def _call(): try: return await _call() except ClientResponseError as error: - if error.status == 401: + if error.status == HTTP_UNAUTHORIZED: _LOGGER.warning( "Request for %s unauthorized, renewing token and retrying", url ) diff --git a/homeassistant/components/google_assistant/trait.py b/homeassistant/components/google_assistant/trait.py index 5d2d59f21449de..653324758e0fc8 100644 --- a/homeassistant/components/google_assistant/trait.py +++ b/homeassistant/components/google_assistant/trait.py @@ -29,6 +29,7 @@ ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, + CAST_APP_ID_HOMEASSISTANT, SERVICE_ALARM_ARM_AWAY, SERVICE_ALARM_ARM_CUSTOM_BYPASS, SERVICE_ALARM_ARM_HOME, @@ -287,7 +288,10 @@ async def execute(self, command, data, params, challenge): url = await self.hass.components.camera.async_request_stream( self.state.entity_id, "hls" ) - self.stream_info = {"cameraStreamAccessUrl": f"{get_url(self.hass)}{url}"} + self.stream_info = { + "cameraStreamAccessUrl": f"{get_url(self.hass)}{url}", + "cameraStreamReceiverAppId": CAST_APP_ID_HOMEASSISTANT, + } @register_trait diff --git a/homeassistant/components/gpslogger/strings.json b/homeassistant/components/gpslogger/strings.json index f3d4344cd49c85..b757e5c46cb735 100644 --- a/homeassistant/components/gpslogger/strings.json +++ b/homeassistant/components/gpslogger/strings.json @@ -7,7 +7,7 @@ } }, "abort": { - "one_instance_allowed": "Only a single instance is necessary.", + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive messages from GPSLogger." }, "create_entry": { diff --git a/homeassistant/components/gpslogger/translations/ca.json b/homeassistant/components/gpslogger/translations/ca.json index aebd839a83710c..96edeea6ccb0fd 100644 --- a/homeassistant/components/gpslogger/translations/ca.json +++ b/homeassistant/components/gpslogger/translations/ca.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "La inst\u00e0ncia de Home Assistant ha de ser accessible des d'Internet per rebre missatges de GPSLogger.", - "one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia." + "one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia.", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "create_entry": { "default": "Per enviar esdeveniments a Home Assistant, haur\u00e0s de configurar l'opci\u00f3 webhook de GPSLogger.\n\nCompleta la seg\u00fcent informaci\u00f3:\n\n- URL: `{webhook_url}` \n- M\u00e8tode: POST \n\nConsulta la [documentaci\u00f3]({docs_url}) per a m\u00e9s detalls." diff --git a/homeassistant/components/gpslogger/translations/el.json b/homeassistant/components/gpslogger/translations/el.json new file mode 100644 index 00000000000000..aecb2ee553fa42 --- /dev/null +++ b/homeassistant/components/gpslogger/translations/el.json @@ -0,0 +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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gpslogger/translations/en.json b/homeassistant/components/gpslogger/translations/en.json index 46bbb23148324c..197c012ed4099a 100644 --- a/homeassistant/components/gpslogger/translations/en.json +++ b/homeassistant/components/gpslogger/translations/en.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive messages from GPSLogger.", - "one_instance_allowed": "Only a single instance is necessary." + "one_instance_allowed": "Only a single instance is necessary.", + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "create_entry": { "default": "To send events to Home Assistant, you will need to setup the webhook feature in GPSLogger.\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nSee [the documentation]({docs_url}) for further details." diff --git a/homeassistant/components/gpslogger/translations/es.json b/homeassistant/components/gpslogger/translations/es.json index 8ac817cacbfe95..41199925e1c455 100644 --- a/homeassistant/components/gpslogger/translations/es.json +++ b/homeassistant/components/gpslogger/translations/es.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Tu Home Assistant debe ser accesible desde Internet para recibir mensajes de GPSLogger.", - "one_instance_allowed": "S\u00f3lo se necesita una instancia." + "one_instance_allowed": "S\u00f3lo se necesita una instancia.", + "single_instance_allowed": "Ya est\u00e1 configurado. S\u00f3lo es posible una \u00fanica configuraci\u00f3n." }, "create_entry": { "default": "Para enviar eventos a Home Assistant, necesitar\u00e1s configurar la funci\u00f3n de webhook en GPSLogger.\n\nRellena la siguiente informaci\u00f3n:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nEcha un vistazo a [la documentaci\u00f3n]({docs_url}) para m\u00e1s detalles." diff --git a/homeassistant/components/gpslogger/translations/et.json b/homeassistant/components/gpslogger/translations/et.json new file mode 100644 index 00000000000000..e8b5eae41495c6 --- /dev/null +++ b/homeassistant/components/gpslogger/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/gpslogger/translations/fr.json b/homeassistant/components/gpslogger/translations/fr.json index 65db2a5a300089..bb9321c5fcca07 100644 --- a/homeassistant/components/gpslogger/translations/fr.json +++ b/homeassistant/components/gpslogger/translations/fr.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Votre instance Home Assistant doit \u00eatre accessible \u00e0 partir d'Internet pour recevoir les messages GPSLogger.", - "one_instance_allowed": "Une seule instance est n\u00e9cessaire." + "one_instance_allowed": "Une seule instance est n\u00e9cessaire.", + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." }, "create_entry": { "default": "Pour envoyer des \u00e9v\u00e9nements \u00e0 Home Assistant, vous devez configurer la fonction Webhook dans GPSLogger. \n\n Remplissez les informations suivantes: \n\n - URL: ` {webhook_url} ` \n - M\u00e9thode: POST \n\n Voir [la documentation] ( {docs_url} ) pour plus de d\u00e9tails." diff --git a/homeassistant/components/gpslogger/translations/it.json b/homeassistant/components/gpslogger/translations/it.json index 32e6b574096aaa..dac92f90262a21 100644 --- a/homeassistant/components/gpslogger/translations/it.json +++ b/homeassistant/components/gpslogger/translations/it.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "La tua istanza di Home Assistant deve essere accessibile da Internet per ricevere messaggi da GPSLogger.", - "one_instance_allowed": "\u00c8 necessaria una sola istanza." + "one_instance_allowed": "\u00c8 necessaria una sola istanza.", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "create_entry": { "default": "Per inviare eventi a Home Assistant, dovrai configurare la funzionalit\u00e0 webhook in GPSLogger.\n\n Compila le seguenti informazioni: \n\n - URL: `{webhook_url}` \n - Method: POST \n\n Vedi [la documentazione]({docs_url}) for ulteriori dettagli." diff --git a/homeassistant/components/gpslogger/translations/lb.json b/homeassistant/components/gpslogger/translations/lb.json index a69ac61bc988eb..2e4362b18e2ebe 100644 --- a/homeassistant/components/gpslogger/translations/lb.json +++ b/homeassistant/components/gpslogger/translations/lb.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "\u00c4r Home Assistant Instanz muss iwwert Internet accessibel si fir GPSLogger Noriichten z'empf\u00e4nken.", - "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg." + "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg.", + "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech." }, "create_entry": { "default": "Fir Evenementer un Home Assistant ze sch\u00e9cken, muss den Webhook Feature am GPSLogger ageriicht ginn.\n\nF\u00ebllt folgend Informatiounen aus:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nLiest [Dokumentatioun]({docs_url}) fir w\u00e9ider D\u00e9tailer." diff --git a/homeassistant/components/gpslogger/translations/no.json b/homeassistant/components/gpslogger/translations/no.json index 2b3dc0f67fc2f5..bfd40a5d0f884c 100644 --- a/homeassistant/components/gpslogger/translations/no.json +++ b/homeassistant/components/gpslogger/translations/no.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Home Assistant m\u00e5 v\u00e6re tilgjengelig fra internett for \u00e5 kunne motta meldinger fra GPSLogger.", - "one_instance_allowed": "Kun en forekomst er n\u00f8dvendig." + "one_instance_allowed": "Kun en forekomst er n\u00f8dvendig.", + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "create_entry": { "default": "For \u00e5 sende hendelser til Home Assistant, m\u00e5 du sette opp webhook-funksjonen i GPSLogger. \n\n Fyll ut f\u00f8lgende informasjon: \n\n - URL: `{webhook_url}` \n - Metode: POST \n\n Se [dokumentasjonen]({docs_url}) for ytterligere detaljer." diff --git a/homeassistant/components/gpslogger/translations/ru.json b/homeassistant/components/gpslogger/translations/ru.json index fabf2477590137..bfa6a4850b39ad 100644 --- a/homeassistant/components/gpslogger/translations/ru.json +++ b/homeassistant/components/gpslogger/translations/ru.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 GPSLogger.", - "one_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "one_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e." }, "create_entry": { "default": "\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 Home Assistant \u0412\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Webhook \u0434\u043b\u044f GPSLogger.\n\n\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e:\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\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]({docs_url}) \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." diff --git a/homeassistant/components/gpslogger/translations/zh-Hant.json b/homeassistant/components/gpslogger/translations/zh-Hant.json index f648d20df9fb82..59aa99d1689ac4 100644 --- a/homeassistant/components/gpslogger/translations/zh-Hant.json +++ b/homeassistant/components/gpslogger/translations/zh-Hant.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Home Assistant \u8a2d\u5099\u5fc5\u9808\u80fd\u5920\u7531\u7db2\u969b\u7db2\u8def\u5b58\u53d6\uff0c\u65b9\u80fd\u63a5\u53d7 GPSLogger \u8a0a\u606f\u3002", - "one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u5373\u53ef\u3002" + "one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u5373\u53ef\u3002", + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002" }, "create_entry": { "default": "\u6b32\u50b3\u9001\u4e8b\u4ef6\u81f3 Home Assistant\uff0c\u5c07\u9700\u65bc GPSLogger \u5167\u8a2d\u5b9a webhook \u529f\u80fd\u3002\n\n\u8acb\u586b\u5beb\u4e0b\u5217\u8cc7\u8a0a\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\u8acb\u53c3\u95b1 [\u6587\u4ef6]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u8a73\u7d30\u8cc7\u6599\u3002" diff --git a/homeassistant/components/griddy/sensor.py b/homeassistant/components/griddy/sensor.py index acdcefee52784b..7a155586fac6c1 100644 --- a/homeassistant/components/griddy/sensor.py +++ b/homeassistant/components/griddy/sensor.py @@ -1,7 +1,7 @@ """Support for August sensors.""" import logging -from homeassistant.const import ENERGY_KILO_WATT_HOUR +from homeassistant.const import CURRENCY_CENT, ENERGY_KILO_WATT_HOUR from homeassistant.helpers.update_coordinator import CoordinatorEntity from .const import CONF_LOADZONE, DOMAIN @@ -29,7 +29,7 @@ def __init__(self, settlement_point, coordinator): @property def unit_of_measurement(self): """Return the unit of measurement.""" - return f"¢/{ENERGY_KILO_WATT_HOUR}" + return f"{CURRENCY_CENT}/{ENERGY_KILO_WATT_HOUR}" @property def name(self): diff --git a/homeassistant/components/griddy/strings.json b/homeassistant/components/griddy/strings.json index d8ccb94fae7294..4bc06aade1d418 100644 --- a/homeassistant/components/griddy/strings.json +++ b/homeassistant/components/griddy/strings.json @@ -1,8 +1,8 @@ { "config": { "error": { - "cannot_connect": "Failed to connect, please try again", - "unknown": "Unexpected error" + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "unknown": "[%key:common::config_flow::error::unknown%]" }, "step": { "user": { diff --git a/homeassistant/components/griddy/translations/ca.json b/homeassistant/components/griddy/translations/ca.json index cb363a7dfabe58..8ec47b83bb2158 100644 --- a/homeassistant/components/griddy/translations/ca.json +++ b/homeassistant/components/griddy/translations/ca.json @@ -4,7 +4,7 @@ "already_configured": "Aquesta zona de c\u00e0rrega ja est\u00e0 configurada" }, "error": { - "cannot_connect": "No s'ha pogut connectar, torna-ho a provar", + "cannot_connect": "Ha fallat la connexi\u00f3", "unknown": "Error inesperat" }, "step": { diff --git a/homeassistant/components/griddy/translations/en.json b/homeassistant/components/griddy/translations/en.json index bb1e217133a69f..e9cc3e7bb6ba28 100644 --- a/homeassistant/components/griddy/translations/en.json +++ b/homeassistant/components/griddy/translations/en.json @@ -4,7 +4,7 @@ "already_configured": "This Load Zone is already configured" }, "error": { - "cannot_connect": "Failed to connect, please try again", + "cannot_connect": "Failed to connect", "unknown": "Unexpected error" }, "step": { diff --git a/homeassistant/components/griddy/translations/it.json b/homeassistant/components/griddy/translations/it.json index 2b573170e69087..044cd66e1430f4 100644 --- a/homeassistant/components/griddy/translations/it.json +++ b/homeassistant/components/griddy/translations/it.json @@ -4,7 +4,7 @@ "already_configured": "Questa Zona di Carico \u00e8 gi\u00e0 configurata" }, "error": { - "cannot_connect": "Impossibile connettersi, si prega di riprovare", + "cannot_connect": "Impossibile connettersi", "unknown": "Errore imprevisto" }, "step": { diff --git a/homeassistant/components/griddy/translations/no.json b/homeassistant/components/griddy/translations/no.json index 000b5dae3067a6..b8d98aae0fd600 100644 --- a/homeassistant/components/griddy/translations/no.json +++ b/homeassistant/components/griddy/translations/no.json @@ -4,7 +4,7 @@ "already_configured": "Denne Load Zone er allerede konfigurert" }, "error": { - "cannot_connect": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen", + "cannot_connect": "Tilkobling mislyktes.", "unknown": "Uventet feil" }, "step": { diff --git a/homeassistant/components/griddy/translations/pl.json b/homeassistant/components/griddy/translations/pl.json index e28b4b6f2e6ead..4fe11d213e82e9 100644 --- a/homeassistant/components/griddy/translations/pl.json +++ b/homeassistant/components/griddy/translations/pl.json @@ -5,7 +5,7 @@ }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "user": { diff --git a/homeassistant/components/griddy/translations/ru.json b/homeassistant/components/griddy/translations/ru.json index 9fe98107510336..ee253e43f56bbe 100644 --- a/homeassistant/components/griddy/translations/ru.json +++ b/homeassistant/components/griddy/translations/ru.json @@ -4,7 +4,7 @@ "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0439 \u0437\u043e\u043d\u044b \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\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, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", + "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": { diff --git a/homeassistant/components/griddy/translations/zh-Hant.json b/homeassistant/components/griddy/translations/zh-Hant.json index e112a2c682f8e2..0b8b5130772473 100644 --- a/homeassistant/components/griddy/translations/zh-Hant.json +++ b/homeassistant/components/griddy/translations/zh-Hant.json @@ -4,7 +4,7 @@ "already_configured": "\u6b64\u8ca0\u8f09\u5340\u57df\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21", + "cannot_connect": "\u9023\u7dda\u5931\u6557", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { diff --git a/homeassistant/components/group/__init__.py b/homeassistant/components/group/__init__.py index 87eb2cd615b0fa..4a0050868d92ae 100644 --- a/homeassistant/components/group/__init__.py +++ b/homeassistant/components/group/__init__.py @@ -1,7 +1,8 @@ """Provide the functionality to group entities.""" import asyncio +from contextvars import ContextVar import logging -from typing import Any, Iterable, List, Optional, cast +from typing import Any, Dict, Iterable, List, Optional, Set, cast import voluptuous as vol @@ -17,23 +18,17 @@ ENTITY_MATCH_NONE, EVENT_HOMEASSISTANT_START, SERVICE_RELOAD, - STATE_CLOSED, - STATE_HOME, - STATE_LOCKED, - STATE_NOT_HOME, STATE_OFF, - STATE_OK, STATE_ON, - STATE_OPEN, - STATE_PROBLEM, - STATE_UNKNOWN, - STATE_UNLOCKED, ) -from homeassistant.core import CoreState, callback +from homeassistant.core import CoreState, callback, split_entity_id import homeassistant.helpers.config_validation as cv 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_platforms, +) from homeassistant.helpers.reload import async_reload_integration_platforms from homeassistant.helpers.typing import HomeAssistantType from homeassistant.loader import bind_hass @@ -60,8 +55,12 @@ PLATFORMS = ["light", "cover", "notify"] +REG_KEY = f"{DOMAIN}_registry" + _LOGGER = logging.getLogger(__name__) +current_domain: ContextVar[str] = ContextVar("current_domain") + def _conf_preprocess(value): """Preprocess alternative configuration formats.""" @@ -87,35 +86,42 @@ def _conf_preprocess(value): extra=vol.ALLOW_EXTRA, ) -# List of ON/OFF state tuples for groupable states -_GROUP_TYPES = [ - (STATE_ON, STATE_OFF), - (STATE_HOME, STATE_NOT_HOME), - (STATE_OPEN, STATE_CLOSED), - (STATE_LOCKED, STATE_UNLOCKED), - (STATE_PROBLEM, STATE_OK), -] +class GroupIntegrationRegistry: + """Class to hold a registry of integrations.""" + + on_off_mapping: Dict[str, str] = {STATE_ON: STATE_OFF} + off_on_mapping: Dict[str, str] = {STATE_OFF: STATE_ON} + on_states_by_domain: Dict[str, Set] = {} + exclude_domains: Set = set() -def _get_group_on_off(state): - """Determine the group on/off states based on a state.""" - for states in _GROUP_TYPES: - if state in states: - return states + def exclude_domain(self) -> None: + """Exclude the current domain.""" + self.exclude_domains.add(current_domain.get()) - return None, None + def on_off_states(self, on_states: Set, off_state: str) -> None: + """Register on and off states for the current domain.""" + for on_state in on_states: + if on_state not in self.on_off_mapping: + self.on_off_mapping[on_state] = off_state + + if len(on_states) == 1 and off_state not in self.off_on_mapping: + self.off_on_mapping[off_state] = list(on_states)[0] + + self.on_states_by_domain[current_domain.get()] = set(on_states) @bind_hass def is_on(hass, entity_id): """Test if the group state is in its ON-state.""" - state = hass.states.get(entity_id) + if REG_KEY not in hass.data: + # Integration not setup yet, it cannot be on + return False - if state: - group_on, _ = _get_group_on_off(state.state) + state = hass.states.get(entity_id) - # If we found a group_type, compare to ON-state - return group_on is not None and state.state == group_on + if state is not None: + return state.state in hass.data[REG_KEY].on_off_mapping return False @@ -209,6 +215,10 @@ async def async_setup(hass, config): if component is None: component = hass.data[DOMAIN] = EntityComponent(_LOGGER, DOMAIN, hass) + hass.data[REG_KEY] = GroupIntegrationRegistry() + + await async_process_integration_platforms(hass, DOMAIN, _process_group_platform) + await _async_process_config(hass, config, component) async def reload_service_handler(service): @@ -332,6 +342,13 @@ async def groups_service_handler(service): return True +async def _process_group_platform(hass, domain, platform): + """Process a group platform.""" + + current_domain.set(domain) + platform.async_describe_on_off_states(hass, hass.data[REG_KEY]) + + async def _async_process_config(hass, config, component): """Process group configuration.""" hass.data.setdefault(GROUP_ORDER, 0) @@ -414,14 +431,12 @@ def __init__( """ self.hass = hass self._name = name - self._state = STATE_UNKNOWN + self._state = None self._icon = icon - if entity_ids: - self.tracking = tuple(ent_id.lower() for ent_id in entity_ids) - else: - self.tracking = () - self.group_on = None - self.group_off = None + self._set_tracked(entity_ids) + self._on_off = None + self._assumed = None + self._on_states = None self.user_defined = user_defined self.mode = any if mode: @@ -492,7 +507,7 @@ async def async_create_group( if component is None: component = hass.data[DOMAIN] = EntityComponent(_LOGGER, DOMAIN, hass) - await component.async_add_entities([group], True) + await component.async_add_entities([group]) return group @@ -532,6 +547,7 @@ def state_attributes(self): data = {ATTR_ENTITY_ID: self.tracking, ATTR_ORDER: self._order} if not self.user_defined: data[ATTR_AUTO] = True + return data @property @@ -550,25 +566,57 @@ async def async_update_tracked_entity_ids(self, entity_ids): This method must be run in the event loop. """ - await self.async_stop() - self.tracking = tuple(ent_id.lower() for ent_id in entity_ids) - self.group_on, self.group_off = None, None + self._async_stop() + self._set_tracked(entity_ids) + self._reset_tracked_state() + self._async_start() - await self.async_update_ha_state(True) - self.async_start() + def _set_tracked(self, entity_ids): + """Tuple of entities to be tracked.""" + # tracking are the entities we want to track + # trackable are the entities we actually watch + + if not entity_ids: + self.tracking = () + self.trackable = () + return + + excluded_domains = self.hass.data[REG_KEY].exclude_domains + + tracking = [] + trackable = [] + for ent_id in entity_ids: + ent_id_lower = ent_id.lower() + domain = split_entity_id(ent_id_lower)[0] + tracking.append(ent_id_lower) + if domain not in excluded_domains: + trackable.append(ent_id_lower) + + self.trackable = tuple(trackable) + self.tracking = tuple(tracking) @callback - def async_start(self): + def _async_start(self, *_): + """Start tracking members and write state.""" + self._reset_tracked_state() + self._async_start_tracking() + self.async_write_ha_state() + + @callback + def _async_start_tracking(self): """Start tracking members. This method must be run in the event loop. """ - if self._async_unsub_state_changed is None: + if self.trackable and self._async_unsub_state_changed is None: self._async_unsub_state_changed = async_track_state_change_event( - self.hass, self.tracking, self._async_state_changed_listener + self.hass, self.trackable, self._async_state_changed_listener ) - async def async_stop(self): + self._async_update_group_state() + + @callback + def _async_stop(self): """Unregister the group from Home Assistant. This method must be run in the event loop. @@ -579,19 +627,24 @@ async def async_stop(self): async def async_update(self): """Query all members and determine current group state.""" - self._state = STATE_UNKNOWN + self._state = None self._async_update_group_state() async def async_added_to_hass(self): """Handle addition to Home Assistant.""" + if self.hass.state != CoreState.running: + self.hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_START, self._async_start + ) + return + if self.tracking: - self.async_start() + self._reset_tracked_state() + self._async_start_tracking() async def async_will_remove_from_hass(self): """Handle removal from Home Assistant.""" - if self._async_unsub_state_changed: - self._async_unsub_state_changed() - self._async_unsub_state_changed = None + self._async_stop() async def _async_state_changed_listener(self, event): """Respond to a member state changing. @@ -603,21 +656,47 @@ async def _async_state_changed_listener(self, event): return self.async_set_context(event.context) - self._async_update_group_state(event.data.get("new_state")) + new_state = event.data.get("new_state") + + if new_state is None: + # The state was removed from the state machine + self._reset_tracked_state() + + self._async_update_group_state(new_state) self.async_write_ha_state() - @property - def _tracking_states(self): - """Return the states that the group is tracking.""" - states = [] + def _reset_tracked_state(self): + """Reset tracked state.""" + self._on_off = {} + self._assumed = {} + self._on_states = set() - for entity_id in self.tracking: + for entity_id in self.trackable: state = self.hass.states.get(entity_id) if state is not None: - states.append(state) - - return states + self._see_state(state) + + def _see_state(self, new_state): + """Keep track of the the state.""" + entity_id = new_state.entity_id + domain = new_state.domain + state = new_state.state + registry = self.hass.data[REG_KEY] + self._assumed[entity_id] = new_state.attributes.get(ATTR_ASSUMED_STATE) + + if domain not in registry.on_states_by_domain: + # Handle the group of a group case + if state in registry.on_off_mapping: + self._on_states.add(state) + elif state in registry.off_on_mapping: + self._on_states.add(registry.off_on_mapping[state]) + self._on_off[entity_id] = state in registry.on_off_mapping + else: + entity_on_state = registry.on_states_by_domain[domain] + if domain in self.hass.data[REG_KEY].on_states_by_domain: + self._on_states.update(entity_on_state) + self._on_off[entity_id] = state in entity_on_state @callback def _async_update_group_state(self, tr_state=None): @@ -629,57 +708,39 @@ def _async_update_group_state(self, tr_state=None): This method must be run in the event loop. """ # To store current states of group entities. Might not be needed. - states = None - gr_state = self._state - gr_on = self.group_on - gr_off = self.group_off - - # We have not determined type of group yet - if gr_on is None: - if tr_state is None: - states = self._tracking_states - - for state in states: - gr_on, gr_off = _get_group_on_off(state.state) - if gr_on is not None: - break - else: - gr_on, gr_off = _get_group_on_off(tr_state.state) + if tr_state: + self._see_state(tr_state) - if gr_on is not None: - self.group_on, self.group_off = gr_on, gr_off - - # We cannot determine state of the group - if gr_on is None: + if not self._on_off: return - if tr_state is None or ( - (gr_state == gr_on and tr_state.state == gr_off) - or (gr_state == gr_off and tr_state.state == gr_on) - or tr_state.state not in (gr_on, gr_off) - ): - if states is None: - states = self._tracking_states - - if self.mode(state.state == gr_on for state in states): - self._state = gr_on - else: - self._state = gr_off - - elif tr_state.state in (gr_on, gr_off): - self._state = tr_state.state - if ( tr_state is None or self._assumed_state and not tr_state.attributes.get(ATTR_ASSUMED_STATE) ): - if states is None: - states = self._tracking_states - - self._assumed_state = self.mode( - state.attributes.get(ATTR_ASSUMED_STATE) for state in states - ) + self._assumed_state = self.mode(self._assumed.values()) elif tr_state.attributes.get(ATTR_ASSUMED_STATE): self._assumed_state = True + + num_on_states = len(self._on_states) + # If all the entity domains we are tracking + # have the same on state we use this state + # and its hass.data[REG_KEY].on_off_mapping to off + if num_on_states == 1: + on_state = list(self._on_states)[0] + # If we do not have an on state for any domains + # we use None (which will be STATE_UNKNOWN) + elif num_on_states == 0: + self._state = None + return + # If the entity domains have more than one + # on state, we use STATE_ON/STATE_OFF + else: + on_state = STATE_ON + group_is_on = self.mode(self._on_off.values()) + if group_is_on: + self._state = on_state + else: + self._state = self.hass.data[REG_KEY].on_off_mapping[on_state] diff --git a/homeassistant/components/group/cover.py b/homeassistant/components/group/cover.py index b0d0b8b7bd2480..ab2ad65713de9e 100644 --- a/homeassistant/components/group/cover.py +++ b/homeassistant/components/group/cover.py @@ -39,7 +39,7 @@ STATE_OPEN, STATE_OPENING, ) -from homeassistant.core import State +from homeassistant.core import CoreState, State import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_state_change_event @@ -162,6 +162,10 @@ async def async_added_to_hass(self): self.hass, self._entities, self._update_supported_features_event ) ) + + if self.hass.state == CoreState.running: + await self.async_update() + return await super().async_added_to_hass() @property diff --git a/homeassistant/components/group/light.py b/homeassistant/components/group/light.py index 289bb8df3f0962..007e05edfbb415 100644 --- a/homeassistant/components/group/light.py +++ b/homeassistant/components/group/light.py @@ -36,7 +36,7 @@ STATE_ON, STATE_UNAVAILABLE, ) -from homeassistant.core import State +from homeassistant.core import CoreState, State import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_state_change_event from homeassistant.helpers.typing import ConfigType, HomeAssistantType @@ -111,6 +111,11 @@ async def async_state_changed_listener(event): self.hass, self._entity_ids, async_state_changed_listener ) ) + + if self.hass.state == CoreState.running: + await self.async_update() + return + await super().async_added_to_hass() @property diff --git a/homeassistant/components/growatt_server/sensor.py b/homeassistant/components/growatt_server/sensor.py index 366596beb0bb90..e6ed422db0f33e 100644 --- a/homeassistant/components/growatt_server/sensor.py +++ b/homeassistant/components/growatt_server/sensor.py @@ -12,6 +12,7 @@ CONF_NAME, CONF_PASSWORD, CONF_USERNAME, + CURRENCY_EURO, DEVICE_CLASS_BATTERY, DEVICE_CLASS_CURRENT, DEVICE_CLASS_ENERGY, @@ -21,6 +22,7 @@ ELECTRICAL_CURRENT_AMPERE, ENERGY_KILO_WATT_HOUR, FREQUENCY_HERTZ, + PERCENTAGE, POWER_WATT, TEMP_CELSIUS, VOLT, @@ -39,8 +41,8 @@ # Sensor type order is: Sensor name, Unit of measurement, api data name, additional options TOTAL_SENSOR_TYPES = { - "total_money_today": ("Total money today", "€", "plantMoneyText", {}), - "total_money_total": ("Money lifetime", "€", "totalMoneyText", {}), + "total_money_today": ("Total money today", CURRENCY_EURO, "plantMoneyText", {}), + "total_money_total": ("Money lifetime", CURRENCY_EURO, "totalMoneyText", {}), "total_energy_today": ( "Energy Today", ENERGY_KILO_WATT_HOUR, @@ -230,7 +232,7 @@ ), "storage_battery_percentage": ( "Battery percentage", - "%", + PERCENTAGE, "capacity", {"device_class": DEVICE_CLASS_BATTERY}, ), @@ -338,7 +340,7 @@ ), "storage_load_percentage": ( "Load percentage", - "%", + PERCENTAGE, "loadPercent", {"device_class": DEVICE_CLASS_BATTERY, "round": 2}, ), diff --git a/homeassistant/components/guardian/binary_sensor.py b/homeassistant/components/guardian/binary_sensor.py index c63d80163bc11e..7942dba361e69c 100644 --- a/homeassistant/components/guardian/binary_sensor.py +++ b/homeassistant/components/guardian/binary_sensor.py @@ -3,7 +3,11 @@ from aioguardian import Client -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_CONNECTIVITY, + DEVICE_CLASS_MOISTURE, + BinarySensorEntity, +) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.update_coordinator import DataUpdateCoordinator @@ -22,8 +26,8 @@ SENSOR_KIND_AP_INFO = "ap_enabled" SENSOR_KIND_LEAK_DETECTED = "leak_detected" SENSORS = [ - (SENSOR_KIND_AP_INFO, "Onboard AP Enabled", "connectivity"), - (SENSOR_KIND_LEAK_DETECTED, "Leak Detected", "moisture"), + (SENSOR_KIND_AP_INFO, "Onboard AP Enabled", DEVICE_CLASS_CONNECTIVITY), + (SENSOR_KIND_LEAK_DETECTED, "Leak Detected", DEVICE_CLASS_MOISTURE), ] diff --git a/homeassistant/components/guardian/config_flow.py b/homeassistant/components/guardian/config_flow.py index 71ec271753ecb0..760cf960e4389a 100644 --- a/homeassistant/components/guardian/config_flow.py +++ b/homeassistant/components/guardian/config_flow.py @@ -83,7 +83,7 @@ async def async_step_user(self, user_input=None): async def async_step_zeroconf(self, discovery_info): """Handle the configuration via zeroconf.""" if discovery_info is None: - return self.async_abort(reason="connection_error") + return self.async_abort(reason="cannot_connect") pin = async_get_pin_from_discovery_hostname(discovery_info["hostname"]) await self._async_set_unique_id(pin) diff --git a/homeassistant/components/guardian/strings.json b/homeassistant/components/guardian/strings.json index 3f87d3260f4a62..47abe35037d1e7 100644 --- a/homeassistant/components/guardian/strings.json +++ b/homeassistant/components/guardian/strings.json @@ -5,8 +5,8 @@ "user": { "description": "Configure a local Elexa Guardian device.", "data": { - "ip_address": "IP Address", - "port": "Port" + "ip_address": "[%key:common::config_flow::data::ip%]", + "port": "[%key:common::config_flow::data::port%]" } }, "zeroconf_confirm": { @@ -14,9 +14,9 @@ } }, "abort": { - "already_configured": "This Guardian device has already been configured.", - "already_in_progress": "Guardian device configuration is already in process.", - "connection_error": "Failed to connect to the Guardian device." + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" } } } diff --git a/homeassistant/components/guardian/translations/ca.json b/homeassistant/components/guardian/translations/ca.json index a94126753be454..207242e32c3133 100644 --- a/homeassistant/components/guardian/translations/ca.json +++ b/homeassistant/components/guardian/translations/ca.json @@ -1,8 +1,9 @@ { "config": { "abort": { - "already_configured": "Aquest dispositiu Guardian ja est\u00e0 configurat.", - "already_in_progress": "La configuraci\u00f3 del dispositiu Guardian ja est\u00e0 en curs.", + "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": "No s'ha pogut connectar amb el dispositiu Guardian." }, "step": { diff --git a/homeassistant/components/guardian/translations/el.json b/homeassistant/components/guardian/translations/el.json new file mode 100644 index 00000000000000..eb707f48797b1e --- /dev/null +++ b/homeassistant/components/guardian/translations/el.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/guardian/translations/en.json b/homeassistant/components/guardian/translations/en.json index 40a8a003c12422..2e47f4d28aa909 100644 --- a/homeassistant/components/guardian/translations/en.json +++ b/homeassistant/components/guardian/translations/en.json @@ -1,8 +1,9 @@ { "config": { "abort": { - "already_configured": "This Guardian device has already been configured.", - "already_in_progress": "Guardian device configuration is already in process.", + "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 to the Guardian device." }, "step": { diff --git a/homeassistant/components/guardian/translations/es.json b/homeassistant/components/guardian/translations/es.json index ec2a724e94432e..657261f213ec61 100644 --- a/homeassistant/components/guardian/translations/es.json +++ b/homeassistant/components/guardian/translations/es.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Este dispositivo Guardian ya ha sido configurado", "already_in_progress": "La configuraci\u00f3n del dispositivo Guardian ya est\u00e1 en proceso.", + "cannot_connect": "No se pudo conectar", "connection_error": "No se ha podido conectar con el dispositivo Guardian." }, "step": { diff --git a/homeassistant/components/guardian/translations/et.json b/homeassistant/components/guardian/translations/et.json new file mode 100644 index 00000000000000..fbbf9b86ceb5c8 --- /dev/null +++ b/homeassistant/components/guardian/translations/et.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "cannot_connect": "\u00dchendamine nurjus", + "connection_error": "Guardian seadmega \u00fchenduse loomine nurjus." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/guardian/translations/fr.json b/homeassistant/components/guardian/translations/fr.json index 52742b4e816f18..427c34e6ca13c4 100644 --- a/homeassistant/components/guardian/translations/fr.json +++ b/homeassistant/components/guardian/translations/fr.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "Ce p\u00e9riph\u00e9rique Guardian a d\u00e9j\u00e0 \u00e9t\u00e9 configur\u00e9.", "already_in_progress": "La configuration de l'appareil Guardian est d\u00e9j\u00e0 en cours.", + "cannot_connect": "\u00c9chec de connexion", "connection_error": "Impossible de se connecter au p\u00e9riph\u00e9rique Guardian." }, "step": { diff --git a/homeassistant/components/guardian/translations/it.json b/homeassistant/components/guardian/translations/it.json index 3f1999be761ad8..24647fff40e57d 100644 --- a/homeassistant/components/guardian/translations/it.json +++ b/homeassistant/components/guardian/translations/it.json @@ -1,8 +1,9 @@ { "config": { "abort": { - "already_configured": "Questo dispositivo Guardian \u00e8 gi\u00e0 stato configurato", - "already_in_progress": "La configurazione del dispositivo Guardian \u00e8 gi\u00e0 in corso.", + "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 al dispositivo Guardian." }, "step": { diff --git a/homeassistant/components/guardian/translations/lb.json b/homeassistant/components/guardian/translations/lb.json index 1de8bb9baea92f..e76dd195c14668 100644 --- a/homeassistant/components/guardian/translations/lb.json +++ b/homeassistant/components/guardian/translations/lb.json @@ -3,6 +3,7 @@ "abort": { "already_configured": "D\u00ebse Guardian Apparat ass scho konfigur\u00e9iert.", "already_in_progress": "Guardian Apparat Konfiguratioun ass schonn am gaang.", + "cannot_connect": "Feeler beim verbannen", "connection_error": "Feeler beim verbannen mam Guardian Apparat." }, "step": { diff --git a/homeassistant/components/guardian/translations/no.json b/homeassistant/components/guardian/translations/no.json index fbe5f881124d4b..7a6db75afee40e 100644 --- a/homeassistant/components/guardian/translations/no.json +++ b/homeassistant/components/guardian/translations/no.json @@ -1,15 +1,16 @@ { "config": { "abort": { - "already_configured": "Denne Guardian-enheten er allerede konfigurert.", - "already_in_progress": "Konfigurasjon av Guardian-enheter er allerede i gang.", + "already_configured": "Enheten er allerede konfigurert", + "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", + "cannot_connect": "Tilkobling mislyktes.", "connection_error": "Kan ikke koble til Guardian-enheten." }, "step": { "user": { "data": { "ip_address": "IP adresse", - "port": "" + "port": "Port" }, "description": "Konfigurer en lokal Elexa Guardian-enhet." }, diff --git a/homeassistant/components/guardian/translations/pl.json b/homeassistant/components/guardian/translations/pl.json index 22706a1babcb60..d3770d8fada7be 100644 --- a/homeassistant/components/guardian/translations/pl.json +++ b/homeassistant/components/guardian/translations/pl.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia z urz\u0105dzeniem Guardian, spr\u00f3buj ponownie." }, "step": { @@ -9,8 +9,13 @@ "data": { "ip_address": "Adres IP", "port": "Port" - } + }, + "description": "Skonfiguruj lokalne urz\u0105dzenie Elexa Guardian." + }, + "zeroconf_confirm": { + "description": "Czy chcesz skonfigurowa\u0107 to urz\u0105dzenie Guardian?" } } - } + }, + "title": "Elexa Guardian" } \ No newline at end of file diff --git a/homeassistant/components/guardian/translations/ru.json b/homeassistant/components/guardian/translations/ru.json index c9fe3b07ff7a6f..36eefd1a4cd2ea 100644 --- a/homeassistant/components/guardian/translations/ru.json +++ b/homeassistant/components/guardian/translations/ru.json @@ -1,8 +1,9 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", - "already_in_progress": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.", + "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 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443." }, "step": { diff --git a/homeassistant/components/guardian/translations/zh-Hant.json b/homeassistant/components/guardian/translations/zh-Hant.json index d91c0c3ba8cfb8..1b0ca0090d9343 100644 --- a/homeassistant/components/guardian/translations/zh-Hant.json +++ b/homeassistant/components/guardian/translations/zh-Hant.json @@ -1,8 +1,9 @@ { "config": { "abort": { - "already_configured": "Guardian \u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", - "already_in_progress": "Guardian \u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d\u3002", + "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", + "cannot_connect": "\u9023\u7dda\u5931\u6557", "connection_error": "Guardian \u8a2d\u5099\u9023\u7dda\u5931\u6557\u3002" }, "step": { diff --git a/homeassistant/components/hangouts/manifest.json b/homeassistant/components/hangouts/manifest.json index 80eed48cde9f49..a2605124dc40fa 100644 --- a/homeassistant/components/hangouts/manifest.json +++ b/homeassistant/components/hangouts/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/hangouts", "requirements": [ - "hangups==0.4.10" + "hangups==0.4.11" ], "codeowners": [] } diff --git a/homeassistant/components/hangouts/strings.json b/homeassistant/components/hangouts/strings.json index 0133bf421ff041..bed46e823d93f7 100644 --- a/homeassistant/components/hangouts/strings.json +++ b/homeassistant/components/hangouts/strings.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Google Hangouts is already configured", - "unknown": "Unknown error occurred." + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]", + "unknown": "[%key:common::config_flow::error::unknown%]" }, "error": { "invalid_login": "Invalid Login, please try again.", @@ -26,4 +26,4 @@ } } } -} \ No newline at end of file +} diff --git a/homeassistant/components/hangouts/translations/ca.json b/homeassistant/components/hangouts/translations/ca.json index 7daa1b4f671ea1..2d1d81bb08db66 100644 --- a/homeassistant/components/hangouts/translations/ca.json +++ b/homeassistant/components/hangouts/translations/ca.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Google Hangouts ja est\u00e0 configurat", - "unknown": "S'ha produ\u00eft un error desconegut." + "already_configured": "El servei ja est\u00e0 configurat", + "unknown": "Error inesperat" }, "error": { "invalid_2fa": "La verificaci\u00f3 en dos passos no \u00e9s v\u00e0lida, torna-ho a provar.", diff --git a/homeassistant/components/hangouts/translations/en.json b/homeassistant/components/hangouts/translations/en.json index 0c2c91d1ac8c15..5de8ac249706f6 100644 --- a/homeassistant/components/hangouts/translations/en.json +++ b/homeassistant/components/hangouts/translations/en.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Google Hangouts is already configured", - "unknown": "Unknown error occurred." + "already_configured": "Service is already configured", + "unknown": "Unexpected error" }, "error": { "invalid_2fa": "Invalid 2 Factor Authentication, please try again.", diff --git a/homeassistant/components/hangouts/translations/et.json b/homeassistant/components/hangouts/translations/et.json index b1c29f3577b26d..e8293aff79f7f4 100644 --- a/homeassistant/components/hangouts/translations/et.json +++ b/homeassistant/components/hangouts/translations/et.json @@ -1,6 +1,8 @@ { "config": { "error": { + "invalid_2fa": "Vale 2-teguriline autentimine, proovige uuesti.", + "invalid_2fa_method": "Kehtetu 2FA meetod (kontrollige telefoni teel).", "invalid_login": "Vale Kasutajanimi, palun proovige uuesti." }, "step": { diff --git a/homeassistant/components/hangouts/translations/it.json b/homeassistant/components/hangouts/translations/it.json index 29ddab2491318d..4831d51ef12c2b 100644 --- a/homeassistant/components/hangouts/translations/it.json +++ b/homeassistant/components/hangouts/translations/it.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Google Hangouts \u00e8 gi\u00e0 configurato", - "unknown": "Si \u00e8 verificato un errore sconosciuto." + "already_configured": "Il servizio \u00e8 gi\u00e0 configurato", + "unknown": "Errore imprevisto" }, "error": { "invalid_2fa": "Autenticazione a 2 fattori non valida, riprovare.", diff --git a/homeassistant/components/hangouts/translations/no.json b/homeassistant/components/hangouts/translations/no.json index 8818898c0b4c17..ffb0d91a2f1f7f 100644 --- a/homeassistant/components/hangouts/translations/no.json +++ b/homeassistant/components/hangouts/translations/no.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Google Hangouts er allerede konfigurert", - "unknown": "Ukjent feil oppstod." + "already_configured": "Tjenesten er allerede konfigurert", + "unknown": "Uventet feil" }, "error": { "invalid_2fa": "Ugyldig totrinnsbekreftelse, vennligst pr\u00f8v igjen.", diff --git a/homeassistant/components/hangouts/translations/pl.json b/homeassistant/components/hangouts/translations/pl.json index 69c3020bbfb0d7..2dd3364bd53ae5 100644 --- a/homeassistant/components/hangouts/translations/pl.json +++ b/homeassistant/components/hangouts/translations/pl.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Google Hangouts jest ju\u017c skonfigurowany.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "error": { "invalid_2fa": "Nieprawid\u0142owe uwierzytelnienie dwusk\u0142adnikowe, spr\u00f3buj ponownie.", diff --git a/homeassistant/components/hangouts/translations/ru.json b/homeassistant/components/hangouts/translations/ru.json index 580c858d15f175..d352258ba33e4e 100644 --- a/homeassistant/components/hangouts/translations/ru.json +++ b/homeassistant/components/hangouts/translations/ru.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", - "unknown": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + "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.", + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "error": { "invalid_2fa": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f, \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0441\u043d\u043e\u0432\u0430.", diff --git a/homeassistant/components/hangouts/translations/zh-Hant.json b/homeassistant/components/hangouts/translations/zh-Hant.json index 1619eaddb63810..62a220eaa9462d 100644 --- a/homeassistant/components/hangouts/translations/zh-Hant.json +++ b/homeassistant/components/hangouts/translations/zh-Hant.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Google Hangouts \u5df2\u7d93\u8a2d\u5b9a", - "unknown": "\u767c\u751f\u672a\u77e5\u932f\u8aa4\u3002" + "already_configured": "\u670d\u52d9\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "error": { "invalid_2fa": "\u96d9\u91cd\u9a57\u8b49\u7121\u6548\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002", diff --git a/homeassistant/components/harmony/strings.json b/homeassistant/components/harmony/strings.json index 86de34672be0e1..f953639e22df3e 100644 --- a/homeassistant/components/harmony/strings.json +++ b/homeassistant/components/harmony/strings.json @@ -15,11 +15,11 @@ } }, "error": { - "cannot_connect": "Failed to connect, please try again", - "unknown": "Unexpected error" + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { - "already_configured": "Device is already configured" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } }, "options": { @@ -33,4 +33,4 @@ } } } -} \ No newline at end of file +} diff --git a/homeassistant/components/harmony/translations/ca.json b/homeassistant/components/harmony/translations/ca.json index 5bb279c0482d28..8111fe3b3446be 100644 --- a/homeassistant/components/harmony/translations/ca.json +++ b/homeassistant/components/harmony/translations/ca.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "El dispositiu ja est\u00e0 configurat" + "already_configured": "El dispositiu ja est\u00e0 configurat", + "already_configured_device": "El dispositiu ja est\u00e0 configurat" }, "error": { - "cannot_connect": "No s'ha pogut connectar, torna-ho a provar", + "cannot_connect": "Ha fallat la connexi\u00f3", "unknown": "Error inesperat" }, "flow_title": "Logitech Harmony Hub {name}", diff --git a/homeassistant/components/harmony/translations/en.json b/homeassistant/components/harmony/translations/en.json index ce13e79e2799a3..8843d741154f5f 100644 --- a/homeassistant/components/harmony/translations/en.json +++ b/homeassistant/components/harmony/translations/en.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "Device is already configured" + "already_configured": "Device is already configured", + "already_configured_device": "Device is already configured" }, "error": { - "cannot_connect": "Failed to connect, please try again", + "cannot_connect": "Failed to connect", "unknown": "Unexpected error" }, "flow_title": "Logitech Harmony Hub {name}", diff --git a/homeassistant/components/harmony/translations/es.json b/homeassistant/components/harmony/translations/es.json index 39305d30680bb0..ffd165e5d7ade5 100644 --- a/homeassistant/components/harmony/translations/es.json +++ b/homeassistant/components/harmony/translations/es.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "El dispositivo ya est\u00e1 configurado" + "already_configured": "El dispositivo ya est\u00e1 configurado", + "already_configured_device": "El dispositivo ya est\u00e1 configurado" }, "error": { "cannot_connect": "No se ha podido conectar, por favor, int\u00e9ntalo de nuevo", diff --git a/homeassistant/components/harmony/translations/et.json b/homeassistant/components/harmony/translations/et.json new file mode 100644 index 00000000000000..3a91142ca7a407 --- /dev/null +++ b/homeassistant/components/harmony/translations/et.json @@ -0,0 +1,11 @@ +{ + "config": { + "abort": { + "already_configured_device": "Seade on juba h\u00e4\u00e4letatud" + }, + "error": { + "cannot_connect": "\u00dchenduse loomine nurjus. Proovi uuesti", + "unknown": "Ootamatu t\u00f5rge" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/harmony/translations/fr.json b/homeassistant/components/harmony/translations/fr.json index 4343ec3139dd16..ed838ed0115d48 100644 --- a/homeassistant/components/harmony/translations/fr.json +++ b/homeassistant/components/harmony/translations/fr.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", + "already_configured_device": "L'appareil est d\u00e9j\u00e0 configur\u00e9" }, "error": { "cannot_connect": "Impossible de se connecter, veuillez r\u00e9essayer", diff --git a/homeassistant/components/harmony/translations/it.json b/homeassistant/components/harmony/translations/it.json index c658e69e0c05c1..ea28a617ff3a7f 100644 --- a/homeassistant/components/harmony/translations/it.json +++ b/homeassistant/components/harmony/translations/it.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "already_configured_device": "Il dispositivo \u00e8 gi\u00e0 configurato" }, "error": { - "cannot_connect": "Impossibile connettersi, si prega di riprovare", + "cannot_connect": "Impossibile connettersi", "unknown": "Errore imprevisto" }, "flow_title": "Logitech Harmony Hub {name}", diff --git a/homeassistant/components/harmony/translations/no.json b/homeassistant/components/harmony/translations/no.json index c3518792851546..d93948dab0a12f 100644 --- a/homeassistant/components/harmony/translations/no.json +++ b/homeassistant/components/harmony/translations/no.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "Enheten er allerede konfigurert" + "already_configured": "Enheten er allerede konfigurert", + "already_configured_device": "Enheten er allerede konfigurert" }, "error": { - "cannot_connect": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen", + "cannot_connect": "Tilkobling mislyktes.", "unknown": "Uventet feil" }, "flow_title": "Logitech Harmony Hub {name}", diff --git a/homeassistant/components/harmony/translations/pl.json b/homeassistant/components/harmony/translations/pl.json index 12bbcfaca1825e..664a849061ae00 100644 --- a/homeassistant/components/harmony/translations/pl.json +++ b/homeassistant/components/harmony/translations/pl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "flow_title": "Logitech Harmony Hub {name}", "step": { diff --git a/homeassistant/components/harmony/translations/ru.json b/homeassistant/components/harmony/translations/ru.json index 4e995a26c48ff5..e7ff5241a1252c 100644 --- a/homeassistant/components/harmony/translations/ru.json +++ b/homeassistant/components/harmony/translations/ru.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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." }, "error": { - "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", + "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": "Logitech Harmony Hub {name}", diff --git a/homeassistant/components/harmony/translations/zh-Hant.json b/homeassistant/components/harmony/translations/zh-Hant.json index dfd1249d629b41..01af1b23af7253 100644 --- a/homeassistant/components/harmony/translations/zh-Hant.json +++ b/homeassistant/components/harmony/translations/zh-Hant.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "already_configured_device": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21", + "cannot_connect": "\u9023\u7dda\u5931\u6557", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "flow_title": "\u7f85\u6280 Harmony Hub {name}", diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py index 69c53225d49bce..9604507fcf07c0 100644 --- a/homeassistant/components/hassio/__init__.py +++ b/homeassistant/components/hassio/__init__.py @@ -127,18 +127,57 @@ @bind_hass -async def async_get_addon_info(hass: HomeAssistantType, addon_id: str) -> dict: +async def async_get_addon_info(hass: HomeAssistantType, slug: str) -> dict: """Return add-on info. - The addon_id is a snakecased concatenation of the 'repository' value - found in the add-on info and the 'slug' value found in the add-on config.json. - In the add-on info the addon_id is called 'slug'. + The caller of the function should handle HassioAPIError. + """ + hassio = hass.data[DOMAIN] + return await hassio.get_addon_info(slug) + + +@bind_hass +async def async_install_addon(hass: HomeAssistantType, slug: str) -> None: + """Install add-on. + + The caller of the function should handle HassioAPIError. + """ + hassio = hass.data[DOMAIN] + command = f"/addons/{slug}/install" + await hassio.send_command(command) + + +@bind_hass +async def async_uninstall_addon(hass: HomeAssistantType, slug: str) -> None: + """Uninstall add-on. + + The caller of the function should handle HassioAPIError. + """ + hassio = hass.data[DOMAIN] + command = f"/addons/{slug}/uninstall" + await hassio.send_command(command) + + +@bind_hass +async def async_start_addon(hass: HomeAssistantType, slug: str) -> None: + """Start add-on. + + The caller of the function should handle HassioAPIError. + """ + hassio = hass.data[DOMAIN] + command = f"/addons/{slug}/start" + await hassio.send_command(command) + + +@bind_hass +async def async_stop_addon(hass: HomeAssistantType, slug: str) -> None: + """Stop add-on. The caller of the function should handle HassioAPIError. """ hassio = hass.data[DOMAIN] - result = await hassio.get_addon_info(addon_id) - return result["data"] + command = f"/addons/{slug}/stop" + await hassio.send_command(command) @callback diff --git a/homeassistant/components/hassio/http.py b/homeassistant/components/hassio/http.py index be2cec5ae9c645..95f861e609766c 100644 --- a/homeassistant/components/hassio/http.py +++ b/homeassistant/components/hassio/http.py @@ -12,11 +12,14 @@ import async_timeout from homeassistant.components.http import KEY_AUTHENTICATED, HomeAssistantView +from homeassistant.components.onboarding import async_is_onboarded +from homeassistant.const import HTTP_UNAUTHORIZED from .const import X_HASS_IS_ADMIN, X_HASS_USER_ID, X_HASSIO _LOGGER = logging.getLogger(__name__) +MAX_UPLOAD_SIZE = 1024 * 1024 * 1024 NO_TIMEOUT = re.compile( r"^(?:" @@ -31,6 +34,10 @@ r")$" ) +NO_AUTH_ONBOARDING = re.compile( + r"^(?:" r"|supervisor/logs" r"|snapshots/[^/]+/.+" r")$" +) + NO_AUTH = re.compile( r"^(?:" r"|app/.*" r"|addons/[^/]+/logo" r"|addons/[^/]+/icon" r")$" ) @@ -52,8 +59,9 @@ async def _handle( self, request: web.Request, path: str ) -> Union[web.Response, web.StreamResponse]: """Route data to Hass.io.""" - if _need_auth(path) and not request[KEY_AUTHENTICATED]: - return web.Response(status=401) + hass = request.app["hass"] + if _need_auth(hass, path) and not request[KEY_AUTHENTICATED]: + return web.Response(status=HTTP_UNAUTHORIZED) return await self._command_proxy(path, request) @@ -70,6 +78,16 @@ async def _command_proxy( read_timeout = _get_timeout(path) data = None headers = _init_header(request) + if path == "snapshots/new/upload": + # We need to reuse the full content type that includes the boundary + headers[ + "Content-Type" + ] = request._stored_content_type # pylint: disable=protected-access + + # Snapshots are big, so we need to adjust the allowed size + request._client_max_size = ( # pylint: disable=protected-access + MAX_UPLOAD_SIZE + ) try: with async_timeout.timeout(10): @@ -133,8 +151,10 @@ def _get_timeout(path: str) -> int: return 300 -def _need_auth(path: str) -> bool: +def _need_auth(hass, path: str) -> bool: """Return if a path need authentication.""" + if not async_is_onboarded(hass) and NO_AUTH_ONBOARDING.match(path): + return False if NO_AUTH.match(path): return False return True diff --git a/homeassistant/components/hdmi_cec/manifest.json b/homeassistant/components/hdmi_cec/manifest.json index c3817d25776dae..4c307582281157 100644 --- a/homeassistant/components/hdmi_cec/manifest.json +++ b/homeassistant/components/hdmi_cec/manifest.json @@ -1,8 +1,7 @@ { - "disabled": "Dependency contains code that breaks Home Assistant.", "domain": "hdmi_cec", "name": "HDMI-CEC", "documentation": "https://www.home-assistant.io/integrations/hdmi_cec", - "requirements": ["pyCEC==0.4.13"], - "codeowners": [] + "requirements": ["pyCEC==0.4.14"], + "codeowners": ["@newAM"] } diff --git a/homeassistant/components/heos/config_flow.py b/homeassistant/components/heos/config_flow.py index 138d1c4462c7b5..efffb93e97859e 100644 --- a/homeassistant/components/heos/config_flow.py +++ b/homeassistant/components/heos/config_flow.py @@ -32,7 +32,7 @@ async def async_step_ssdp(self, discovery_info): self.hass.data[DATA_DISCOVERED_HOSTS][friendly_name] = hostname # Abort if other flows in progress or an entry already exists if self._async_in_progress() or self._async_current_entries(): - return self.async_abort(reason="already_setup") + return self.async_abort(reason="single_instance_allowed") await self.async_set_unique_id(DOMAIN) # Show selection form return self.async_show_form(step_id="user") @@ -50,7 +50,7 @@ async def async_step_user(self, user_input=None): self.hass.data.setdefault(DATA_DISCOVERED_HOSTS, {}) # Only a single entry is needed for all devices if self._async_current_entries(): - return self.async_abort(reason="already_setup") + return self.async_abort(reason="single_instance_allowed") # Try connecting to host if provided errors = {} host = None @@ -64,7 +64,7 @@ async def async_step_user(self, user_input=None): self.hass.data.pop(DATA_DISCOVERED_HOSTS) return await self.async_step_import({CONF_HOST: host}) except HeosError: - errors[CONF_HOST] = "connection_failure" + errors[CONF_HOST] = "cannot_connect" finally: await heos.disconnect() diff --git a/homeassistant/components/heos/strings.json b/homeassistant/components/heos/strings.json index b633b3c82b6117..09ada292afde62 100644 --- a/homeassistant/components/heos/strings.json +++ b/homeassistant/components/heos/strings.json @@ -10,10 +10,10 @@ } }, "error": { - "connection_failure": "Unable to connect to the specified host." + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, "abort": { - "already_setup": "You can only configure a single Heos connection as it will support all devices on the network." + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" } } } diff --git a/homeassistant/components/heos/translations/ca.json b/homeassistant/components/heos/translations/ca.json index d8bcf494615bdd..f12e18ac99909b 100644 --- a/homeassistant/components/heos/translations/ca.json +++ b/homeassistant/components/heos/translations/ca.json @@ -1,9 +1,11 @@ { "config": { "abort": { - "already_setup": "Nom\u00e9s pots configurar una \u00fanica connexi\u00f3 de Heos tot i que aquesta ja pot controlar tots els dispositius de la xarxa." + "already_setup": "Nom\u00e9s pots configurar una \u00fanica connexi\u00f3 de Heos tot i que aquesta ja pot controlar tots els dispositius de la xarxa.", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", "connection_failure": "No s'ha pogut connectar amb l'amfitri\u00f3 especificat." }, "step": { diff --git a/homeassistant/components/heos/translations/en.json b/homeassistant/components/heos/translations/en.json index be84c08fa5ef51..ed82022c969b5f 100644 --- a/homeassistant/components/heos/translations/en.json +++ b/homeassistant/components/heos/translations/en.json @@ -1,9 +1,11 @@ { "config": { "abort": { - "already_setup": "You can only configure a single Heos connection as it will support all devices on the network." + "already_setup": "You can only configure a single Heos connection as it will support all devices on the network.", + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "error": { + "cannot_connect": "Failed to connect", "connection_failure": "Unable to connect to the specified host." }, "step": { diff --git a/homeassistant/components/heos/translations/it.json b/homeassistant/components/heos/translations/it.json index 81350edf9adf50..353d0a7e3193a8 100644 --- a/homeassistant/components/heos/translations/it.json +++ b/homeassistant/components/heos/translations/it.json @@ -1,9 +1,11 @@ { "config": { "abort": { - "already_setup": "\u00c8 possibile configurare una singola connessione Heos poich\u00e9 supporta tutti i dispositivi sulla rete." + "already_setup": "\u00c8 possibile configurare una singola connessione Heos poich\u00e9 supporta tutti i dispositivi sulla rete.", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "error": { + "cannot_connect": "Impossibile connettersi", "connection_failure": "Impossibile connettersi all'host specificato." }, "step": { diff --git a/homeassistant/components/heos/translations/pl.json b/homeassistant/components/heos/translations/pl.json index 5394d57bb74098..55a05356e9da22 100644 --- a/homeassistant/components/heos/translations/pl.json +++ b/homeassistant/components/heos/translations/pl.json @@ -4,6 +4,7 @@ "already_setup": "Mo\u017cesz skonfigurowa\u0107 tylko jedno po\u0142\u0105czenie Heos, poniewa\u017c b\u0119dzie ono obs\u0142ugiwa\u0107 wszystkie urz\u0105dzenia w sieci." }, "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "connection_failure": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z okre\u015blonym hostem." }, "step": { diff --git a/homeassistant/components/heos/translations/ru.json b/homeassistant/components/heos/translations/ru.json index b92ea253ebee20..2ee40afbe0c1d3 100644 --- a/homeassistant/components/heos/translations/ru.json +++ b/homeassistant/components/heos/translations/ru.json @@ -1,9 +1,11 @@ { "config": { "abort": { - "already_setup": "\u041d\u0443\u0436\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u043e \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435, \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u043e\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c \u0432\u0441\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 HEOS \u0432 \u0441\u0435\u0442\u0438." + "already_setup": "\u041d\u0443\u0436\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u043e \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435, \u043f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 \u043e\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c \u0432\u0441\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 HEOS \u0432 \u0441\u0435\u0442\u0438.", + "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e." }, "error": { + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "connection_failure": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u043e\u043c\u0443 \u0445\u043e\u0441\u0442\u0443." }, "step": { diff --git a/homeassistant/components/hikvision/binary_sensor.py b/homeassistant/components/hikvision/binary_sensor.py index 779afa10cca643..359f966d119174 100644 --- a/homeassistant/components/hikvision/binary_sensor.py +++ b/homeassistant/components/hikvision/binary_sensor.py @@ -5,7 +5,12 @@ from pyhik.hikvision import HikCamera import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_CONNECTIVITY, + DEVICE_CLASS_MOTION, + PLATFORM_SCHEMA, + BinarySensorEntity, +) from homeassistant.const import ( ATTR_LAST_TRIP_TIME, CONF_CUSTOMIZE, @@ -34,28 +39,28 @@ ATTR_DELAY = "delay" DEVICE_CLASS_MAP = { - "Motion": "motion", - "Line Crossing": "motion", - "Field Detection": "motion", + "Motion": DEVICE_CLASS_MOTION, + "Line Crossing": DEVICE_CLASS_MOTION, + "Field Detection": DEVICE_CLASS_MOTION, "Video Loss": None, - "Tamper Detection": "motion", + "Tamper Detection": DEVICE_CLASS_MOTION, "Shelter Alarm": None, "Disk Full": None, "Disk Error": None, - "Net Interface Broken": "connectivity", - "IP Conflict": "connectivity", + "Net Interface Broken": DEVICE_CLASS_CONNECTIVITY, + "IP Conflict": DEVICE_CLASS_CONNECTIVITY, "Illegal Access": None, "Video Mismatch": None, "Bad Video": None, - "PIR Alarm": "motion", - "Face Detection": "motion", - "Scene Change Detection": "motion", + "PIR Alarm": DEVICE_CLASS_MOTION, + "Face Detection": DEVICE_CLASS_MOTION, + "Scene Change Detection": DEVICE_CLASS_MOTION, "I/O": None, - "Unattended Baggage": "motion", - "Attended Baggage": "motion", + "Unattended Baggage": DEVICE_CLASS_MOTION, + "Attended Baggage": DEVICE_CLASS_MOTION, "Recording Failure": None, - "Exiting Region": "motion", - "Entering Region": "motion", + "Exiting Region": DEVICE_CLASS_MOTION, + "Entering Region": DEVICE_CLASS_MOTION, } CUSTOMIZE_SCHEMA = vol.Schema( @@ -250,8 +255,7 @@ def should_poll(self): @property def device_state_attributes(self): """Return the state attributes.""" - attr = {} - attr[ATTR_LAST_TRIP_TIME] = self._sensor_last_update() + attr = {ATTR_LAST_TRIP_TIME: self._sensor_last_update()} if self._delay != 0: attr[ATTR_DELAY] = self._delay diff --git a/homeassistant/components/hikvisioncam/switch.py b/homeassistant/components/hikvisioncam/switch.py index 2e924135bd402d..2f1f89cd26159c 100644 --- a/homeassistant/components/hikvisioncam/switch.py +++ b/homeassistant/components/hikvisioncam/switch.py @@ -68,11 +68,6 @@ def __init__(self, name, hikvision_cam): self._hikvision_cam = hikvision_cam self._state = STATE_OFF - @property - def should_poll(self): - """Poll for status regularly.""" - return True - @property def name(self): """Return the name of the device if any.""" diff --git a/homeassistant/components/hisense_aehw4a1/strings.json b/homeassistant/components/hisense_aehw4a1/strings.json index 5d9b6f1ef96136..58e6b057c12a5b 100644 --- a/homeassistant/components/hisense_aehw4a1/strings.json +++ b/homeassistant/components/hisense_aehw4a1/strings.json @@ -6,8 +6,8 @@ } }, "abort": { - "single_instance_allowed": "Only a single configuration of Hisense AEH-W4A1 is possible.", - "no_devices_found": "No Hisense AEH-W4A1 devices found on the network." + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" } } } diff --git a/homeassistant/components/hisense_aehw4a1/translations/ca.json b/homeassistant/components/hisense_aehw4a1/translations/ca.json index 3a05c1d6123482..4ead6779df66b4 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/ca.json +++ b/homeassistant/components/hisense_aehw4a1/translations/ca.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "No s'ha trobat cap dispositiu AEH-W4A1 a la xarxa.", - "single_instance_allowed": "Nom\u00e9s \u00e9s possible una \u00fanica configuraci\u00f3 del AEH-W4A1 de Hisense." + "no_devices_found": "No s'han trobat dispositius a la xarxa", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "step": { "confirm": { diff --git a/homeassistant/components/hisense_aehw4a1/translations/en.json b/homeassistant/components/hisense_aehw4a1/translations/en.json index 4b2926002495cc..abdf245c8549f1 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/en.json +++ b/homeassistant/components/hisense_aehw4a1/translations/en.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "No Hisense AEH-W4A1 devices found on the network.", - "single_instance_allowed": "Only a single configuration of Hisense AEH-W4A1 is possible." + "no_devices_found": "No devices found on the network", + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "step": { "confirm": { diff --git a/homeassistant/components/hisense_aehw4a1/translations/it.json b/homeassistant/components/hisense_aehw4a1/translations/it.json index 0da43b27a82646..b6951f4d6478b1 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/it.json +++ b/homeassistant/components/hisense_aehw4a1/translations/it.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Nessun dispositivo Hisense AEH-W4A1 trovato sulla rete.", - "single_instance_allowed": "\u00c8 consentita solo una configurazione di Hisense AEH-W4A1" + "no_devices_found": "Nessun dispositivo trovato sulla rete", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "step": { "confirm": { diff --git a/homeassistant/components/hisense_aehw4a1/translations/no.json b/homeassistant/components/hisense_aehw4a1/translations/no.json index 6a8d6cbe443323..9be1a735d51c33 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/no.json +++ b/homeassistant/components/hisense_aehw4a1/translations/no.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Ingen Hisense AEH-W4A1-enheter funnet p\u00e5 nettverket.", - "single_instance_allowed": "Bare en enkelt konfigurasjon av Hisense AEH-W4A1 er mulig." + "no_devices_found": "Ingen enheter funnet p\u00e5 nettverket", + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "step": { "confirm": { diff --git a/homeassistant/components/hisense_aehw4a1/translations/ru.json b/homeassistant/components/hisense_aehw4a1/translations/ru.json index 0d982c13eb0862..e26dcfb42c0ad7 100644 --- a/homeassistant/components/hisense_aehw4a1/translations/ru.json +++ b/homeassistant/components/hisense_aehw4a1/translations/ru.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 Hisense AEH-W4A1e \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 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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." }, "step": { "confirm": { diff --git a/homeassistant/components/history/__init__.py b/homeassistant/components/history/__init__.py index 0e6fabb66aa462..79c60572ba5a23 100644 --- a/homeassistant/components/history/__init__.py +++ b/homeassistant/components/history/__init__.py @@ -8,7 +8,7 @@ from typing import Optional, cast from aiohttp import web -from sqlalchemy import and_, bindparam, func +from sqlalchemy import and_, bindparam, func, not_, or_ from sqlalchemy.ext import baked import voluptuous as vol @@ -29,6 +29,10 @@ ) from homeassistant.core import Context, State, split_entity_id import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.entityfilter import ( + CONF_ENTITY_GLOBS, + INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA, +) import homeassistant.util.dt as dt_util # mypy: allow-untyped-defs, no-check-untyped-defs @@ -41,27 +45,20 @@ STATE_KEY = "state" LAST_CHANGED_KEY = "last_changed" -# Not reusing from entityfilter because history does not support glob filtering -_FILTER_SCHEMA_INNER = vol.Schema( - { - vol.Optional(CONF_DOMAINS, default=[]): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_ENTITIES, default=[]): cv.entity_ids, - } -) -_FILTER_SCHEMA = vol.Schema( +GLOB_TO_SQL_CHARS = { + 42: "%", # * + 46: "_", # . +} + +CONFIG_SCHEMA = vol.Schema( { - vol.Optional( - CONF_INCLUDE, default=_FILTER_SCHEMA_INNER({}) - ): _FILTER_SCHEMA_INNER, - vol.Optional( - CONF_EXCLUDE, default=_FILTER_SCHEMA_INNER({}) - ): _FILTER_SCHEMA_INNER, - vol.Optional(CONF_ORDER, default=False): cv.boolean, - } + DOMAIN: INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA.extend( + {vol.Optional(CONF_ORDER, default=False): cv.boolean} + ) + }, + extra=vol.ALLOW_EXTRA, ) -CONFIG_SCHEMA = vol.Schema({DOMAIN: _FILTER_SCHEMA}, extra=vol.ALLOW_EXTRA) - SIGNIFICANT_DOMAINS = ( "climate", "device_tracker", @@ -130,8 +127,14 @@ def _get_significant_states( else: baked_query += lambda q: q.filter(States.last_updated > bindparam("start_time")) - if filters: - filters.bake(baked_query, entity_ids) + if entity_ids is not None: + baked_query += lambda q: q.filter( + States.entity_id.in_(bindparam("entity_ids", expanding=True)) + ) + else: + baked_query += lambda q: q.filter(~States.domain.in_(IGNORE_DOMAINS)) + if filters: + filters.bake(baked_query) if end_time is not None: baked_query += lambda q: q.filter(States.last_updated < bindparam("end_time")) @@ -299,10 +302,14 @@ def _get_states_with_session( query = query.join( most_recent_state_ids, States.state_id == most_recent_state_ids.c.max_state_id, - ).filter(~States.domain.in_(IGNORE_DOMAINS)) + ) - if filters: - query = filters.apply(query, entity_ids) + if entity_ids is not None: + query = query.filter(States.entity_id.in_(entity_ids)) + else: + query = query.filter(~States.domain.in_(IGNORE_DOMAINS)) + if filters: + query = filters.apply(query) return [LazyState(row) for row in execute(query)] @@ -542,7 +549,7 @@ def _sorted_significant_states_json( # Optionally reorder the result to respect the ordering given # by any entities explicitly included in the configuration. - if self.use_include_order: + if self.filters and self.use_include_order: sorted_result = [] for order_entity in self.filters.included_entities: for state_list in result: @@ -563,11 +570,14 @@ def sqlalchemy_filter_from_include_exclude_conf(conf): if exclude: filters.excluded_entities = exclude.get(CONF_ENTITIES, []) filters.excluded_domains = exclude.get(CONF_DOMAINS, []) + filters.excluded_entity_globs = exclude.get(CONF_ENTITY_GLOBS, []) include = conf.get(CONF_INCLUDE) if include: filters.included_entities = include.get(CONF_ENTITIES, []) filters.included_domains = include.get(CONF_DOMAINS, []) - return filters + filters.included_entity_globs = include.get(CONF_ENTITY_GLOBS, []) + + return filters if filters.has_config else None class Filters: @@ -577,94 +587,77 @@ def __init__(self): """Initialise the include and exclude filters.""" self.excluded_entities = [] self.excluded_domains = [] + self.excluded_entity_globs = [] + self.included_entities = [] self.included_domains = [] + self.included_entity_globs = [] - def apply(self, query, entity_ids=None): - """Apply the include/exclude filter on domains and entities on query. + def apply(self, query): + """Apply the entity filter.""" + if not self.has_config: + return query - Following rules apply: - * only the include section is configured - just query the specified - entities or domains. - * only the exclude section is configured - filter the specified - entities and domains from all the entities in the system. - * if include and exclude is defined - select the entities specified in - the include and filter out the ones from the exclude list. - """ - # specific entities requested - do not in/exclude anything - if entity_ids is not None: - return query.filter(States.entity_id.in_(entity_ids)) - - query = query.filter(~States.domain.in_(IGNORE_DOMAINS)) + return query.filter(self.entity_filter()) - entity_filter = self.entity_filter() - if entity_filter is not None: - query = query.filter(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 query + return False - def bake(self, baked_query, entity_ids=None): + def bake(self, baked_query): """Update a baked query. Works the same as apply on a baked_query. """ - if entity_ids is not None: - baked_query += lambda q: q.filter( - States.entity_id.in_(bindparam("entity_ids", expanding=True)) - ) + if not self.has_config: return - baked_query += lambda q: q.filter(~States.domain.in_(IGNORE_DOMAINS)) - - if ( - self.excluded_entities - or self.excluded_domains - or self.included_entities - or self.included_domains - ): - baked_query += lambda q: q.filter(self.entity_filter()) + baked_query += lambda q: q.filter(self.entity_filter()) def entity_filter(self): """Generate the entity filter query.""" - entity_filter = None - # filter if only excluded domain is configured - if self.excluded_domains and not self.included_domains: - entity_filter = ~States.domain.in_(self.excluded_domains) - if self.included_entities: - entity_filter &= States.entity_id.in_(self.included_entities) - # filter if only included domain is configured - elif not self.excluded_domains and self.included_domains: - entity_filter = States.domain.in_(self.included_domains) - if self.included_entities: - entity_filter |= States.entity_id.in_(self.included_entities) - # filter if included and excluded domain is configured - elif self.excluded_domains and self.included_domains: - entity_filter = ~States.domain.in_(self.excluded_domains) - if self.included_entities: - entity_filter &= States.domain.in_( - self.included_domains - ) | States.entity_id.in_(self.included_entities) - else: - entity_filter &= States.domain.in_( - self.included_domains - ) & ~States.domain.in_(self.excluded_domains) - # no domain filter just included entities - elif ( - not self.excluded_domains - and not self.included_domains - and self.included_entities - ): - entity_filter = States.entity_id.in_(self.included_entities) - # finally apply excluded entities filter if configured + includes = [] + if self.included_domains: + includes.append(States.domain.in_(self.included_domains)) + if self.included_entities: + includes.append(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(States.domain.in_(self.excluded_domains)) if self.excluded_entities: - if entity_filter is not None: - entity_filter = (entity_filter) & ~States.entity_id.in_( - self.excluded_entities - ) - else: - entity_filter = ~States.entity_id.in_(self.excluded_entities) + excludes.append(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 excludes and includes: + return not_(or_(*excludes)) + + return or_(*includes) & not_(or_(*excludes)) + - return entity_filter +def _glob_to_like(glob_str): + """Translate glob to sql.""" + return States.entity_id.like(glob_str.translate(GLOB_TO_SQL_CHARS)) class LazyState(State): diff --git a/homeassistant/components/history_stats/sensor.py b/homeassistant/components/history_stats/sensor.py index 8b993b0e837ba8..40c0a76bfba884 100644 --- a/homeassistant/components/history_stats/sensor.py +++ b/homeassistant/components/history_stats/sensor.py @@ -174,11 +174,6 @@ def unit_of_measurement(self): """Return the unit the value is expressed in.""" return self._unit_of_measurement - @property - def should_poll(self): - """Return the polling state.""" - return True - @property def device_state_attributes(self): """Return the state attributes of the sensor.""" diff --git a/homeassistant/components/hive/binary_sensor.py b/homeassistant/components/hive/binary_sensor.py index 27c648f554bdd4..120148a8f813f4 100644 --- a/homeassistant/components/hive/binary_sensor.py +++ b/homeassistant/components/hive/binary_sensor.py @@ -1,9 +1,16 @@ """Support for the Hive binary sensors.""" -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_MOTION, + DEVICE_CLASS_OPENING, + BinarySensorEntity, +) from . import DATA_HIVE, DOMAIN, HiveEntity -DEVICETYPE_DEVICE_CLASS = {"motionsensor": "motion", "contactsensor": "opening"} +DEVICETYPE_DEVICE_CLASS = { + "motionsensor": DEVICE_CLASS_MOTION, + "contactsensor": DEVICE_CLASS_OPENING, +} def setup_platform(hass, config, add_entities, discovery_info=None): diff --git a/homeassistant/components/hive/manifest.json b/homeassistant/components/hive/manifest.json index 060a1a0a200d6b..f8fb9bc8c2a181 100644 --- a/homeassistant/components/hive/manifest.json +++ b/homeassistant/components/hive/manifest.json @@ -2,6 +2,6 @@ "domain": "hive", "name": "Hive", "documentation": "https://www.home-assistant.io/integrations/hive", - "requirements": ["pyhiveapi==0.2.20.1"], + "requirements": ["pyhiveapi==0.2.20.2"], "codeowners": ["@Rendili", "@KJonline"] } diff --git a/homeassistant/components/hlk_sw16/translations/de.json b/homeassistant/components/hlk_sw16/translations/de.json new file mode 100644 index 00000000000000..6f39806287630f --- /dev/null +++ b/homeassistant/components/hlk_sw16/translations/de.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Passwort", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hlk_sw16/translations/hu.json b/homeassistant/components/hlk_sw16/translations/hu.json new file mode 100644 index 00000000000000..3b2d79a34a77e2 --- /dev/null +++ b/homeassistant/components/hlk_sw16/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hlk_sw16/translations/nl.json b/homeassistant/components/hlk_sw16/translations/nl.json new file mode 100644 index 00000000000000..4d00f0bfc74883 --- /dev/null +++ b/homeassistant/components/hlk_sw16/translations/nl.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Gebruikersnaam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hlk_sw16/translations/pl.json b/homeassistant/components/hlk_sw16/translations/pl.json new file mode 100644 index 00000000000000..25dab56796cc15 --- /dev/null +++ b/homeassistant/components/hlk_sw16/translations/pl.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "host": "Nazwa hosta lub adres IP", + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/home_connect/strings.json b/homeassistant/components/home_connect/strings.json index 798fe2930a03e1..79455783edf7e9 100644 --- a/homeassistant/components/home_connect/strings.json +++ b/homeassistant/components/home_connect/strings.json @@ -2,15 +2,15 @@ "config": { "step": { "pick_implementation": { - "title": "Pick Authentication Method" + "title": "[%key:common::config_flow::title::oauth2_pick_implementation%]" } }, "abort": { - "missing_configuration": "The Home Connect component is not configured. Please follow the documentation.", + "missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]", "no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]" }, "create_entry": { - "default": "Successfully authenticated with Home Connect." + "default": "[%key:common::config_flow::create_entry::authenticated%]" } } } diff --git a/homeassistant/components/home_connect/translations/ca.json b/homeassistant/components/home_connect/translations/ca.json index 6553ce7e24db2b..12f761904fcc9d 100644 --- a/homeassistant/components/home_connect/translations/ca.json +++ b/homeassistant/components/home_connect/translations/ca.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "missing_configuration": "El component Home Connect no est\u00e0 configurat. Mira'n la documentaci\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})" }, "create_entry": { - "default": "Autenticaci\u00f3 exitosa amb Home Connect." + "default": "Autenticaci\u00f3 exitosa" }, "step": { "pick_implementation": { diff --git a/homeassistant/components/home_connect/translations/en.json b/homeassistant/components/home_connect/translations/en.json index 24190814216b1d..d44b51a0b9787e 100644 --- a/homeassistant/components/home_connect/translations/en.json +++ b/homeassistant/components/home_connect/translations/en.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "missing_configuration": "The Home Connect component is not configured. Please follow the documentation.", + "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})" }, "create_entry": { - "default": "Successfully authenticated with Home Connect." + "default": "Successfully authenticated" }, "step": { "pick_implementation": { diff --git a/homeassistant/components/home_connect/translations/es.json b/homeassistant/components/home_connect/translations/es.json index 7457f7487d4ffd..8c60c994df0c74 100644 --- a/homeassistant/components/home_connect/translations/es.json +++ b/homeassistant/components/home_connect/translations/es.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "missing_configuration": "El componente Home Connect no est\u00e1 configurado. Por favor, sigue la documentaci\u00f3n." + "missing_configuration": "El componente Home Connect no est\u00e1 configurado. Por favor, sigue 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})" }, "create_entry": { "default": "Autenticado correctamente con Home Assistant." diff --git a/homeassistant/components/home_connect/translations/fr.json b/homeassistant/components/home_connect/translations/fr.json index 630960b1c916ee..42a0c34fe8182b 100644 --- a/homeassistant/components/home_connect/translations/fr.json +++ b/homeassistant/components/home_connect/translations/fr.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "missing_configuration": "Le composant Home Connect n'est pas configur\u00e9. Veuillez suivre la documentation." + "missing_configuration": "Le composant Home Connect 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} )" }, "create_entry": { "default": "Authentification r\u00e9ussie avec Home Connect." diff --git a/homeassistant/components/home_connect/translations/it.json b/homeassistant/components/home_connect/translations/it.json index 98aa955b0204ac..3dc834bfd85c74 100644 --- a/homeassistant/components/home_connect/translations/it.json +++ b/homeassistant/components/home_connect/translations/it.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "missing_configuration": "Il componente Home Connect non \u00e8 configurato. Si prega di seguire la documentazione.", + "missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione.", "no_url_available": "Nessun URL disponibile. Per informazioni su questo errore, [controlla la sezione della guida]({docs_url})" }, "create_entry": { - "default": "Autenticazione riuscita con Home Connect." + "default": "Autenticazione riuscita" }, "step": { "pick_implementation": { diff --git a/homeassistant/components/home_connect/translations/ko.json b/homeassistant/components/home_connect/translations/ko.json index 973e1a0ec8803f..8d1f5554e7f880 100644 --- a/homeassistant/components/home_connect/translations/ko.json +++ b/homeassistant/components/home_connect/translations/ko.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "missing_configuration": "Home Connect \uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694." + "missing_configuration": "Home Connect \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": "\uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc5d0\ub7ec\uc5d0 \ub300\ud55c \uc815\ubcf4\ub294 \ub3c4\uc6c0\ub9d0 \uc139\uc158\uc744 \ud655\uc778\ud558\uc138\uc694({docs_url})" }, "create_entry": { "default": "Home Connect \ub85c \uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4." diff --git a/homeassistant/components/home_connect/translations/lb.json b/homeassistant/components/home_connect/translations/lb.json index 1820e1e2788ab3..210c871a1b8e73 100644 --- a/homeassistant/components/home_connect/translations/lb.json +++ b/homeassistant/components/home_connect/translations/lb.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "missing_configuration": "Home Connecz Komponent ass nach net konfigur\u00e9iert. Folleg w.e.g der Dokumentatioun." + "missing_configuration": "Home Connecz Komponent ass nach net konfigur\u00e9iert. Folleg w.e.g der Dokumentatioun.", + "no_url_available": "Keng URL disponibel. Fir Informatiounen iwwert d\u00ebse Feeler, [kuck H\u00ebllef Sektioun]({docs_url})" }, "create_entry": { "default": "Erfollegr\u00e4ich mat Home Connect authentifiz\u00e9iert." diff --git a/homeassistant/components/home_connect/translations/no.json b/homeassistant/components/home_connect/translations/no.json index 908f62efbc9399..69d84fdf653a32 100644 --- a/homeassistant/components/home_connect/translations/no.json +++ b/homeassistant/components/home_connect/translations/no.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "missing_configuration": "Home Connect-komponenten er ikke konfigurert. Vennligst f\u00f8lg dokumentasjonen." + "missing_configuration": "Komponenten er ikke konfigurert. Vennligst f\u00f8lg dokumentasjonen.", + "no_url_available": "Ingen URL tilgjengelig. For informasjon om denne feilen, [sjekk {docs_url} ] ( {docs_url} )" }, "create_entry": { - "default": "Vellykket godkjenning med Home Connect" + "default": "Vellykket godkjenning" }, "step": { "pick_implementation": { diff --git a/homeassistant/components/home_connect/translations/zh-Hant.json b/homeassistant/components/home_connect/translations/zh-Hant.json index 5132dedd515c35..56f8fd16874bf0 100644 --- a/homeassistant/components/home_connect/translations/zh-Hant.json +++ b/homeassistant/components/home_connect/translations/zh-Hant.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "missing_configuration": "Home Connect \u5143\u4ef6\u5c1a\u672a\u8a2d\u7f6e\uff0c\u8acb\u53c3\u95b1\u6587\u4ef6\u8aaa\u660e\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})" }, "create_entry": { - "default": "\u5df2\u6210\u529f\u8a8d\u8b49 Home Connect \u8a2d\u5099\u3002" + "default": "\u5df2\u6210\u529f\u8a8d\u8b49" }, "step": { "pick_implementation": { diff --git a/homeassistant/components/homeassistant/scene.py b/homeassistant/components/homeassistant/scene.py index 7e4a04333449a5..1ff3915f1215c9 100644 --- a/homeassistant/components/homeassistant/scene.py +++ b/homeassistant/components/homeassistant/scene.py @@ -35,15 +35,15 @@ def _convert_states(states): """Convert state definitions to State objects.""" result = {} - for entity_id in states: + for entity_id, info in states.items(): entity_id = cv.entity_id(entity_id) - if isinstance(states[entity_id], dict): - entity_attrs = states[entity_id].copy() + if isinstance(info, dict): + entity_attrs = info.copy() state = entity_attrs.pop(ATTR_STATE, None) attributes = entity_attrs else: - state = states[entity_id] + state = info attributes = {} # YAML translates 'on' to a boolean diff --git a/homeassistant/components/homeassistant/triggers/event.py b/homeassistant/components/homeassistant/triggers/event.py index 800d6bb5f7740a..4498702bac4a14 100644 --- a/homeassistant/components/homeassistant/triggers/event.py +++ b/homeassistant/components/homeassistant/triggers/event.py @@ -11,6 +11,7 @@ CONF_EVENT_TYPE = "event_type" CONF_EVENT_DATA = "event_data" +CONF_EVENT_CONTEXT = "context" _LOGGER = logging.getLogger(__name__) @@ -19,36 +20,42 @@ vol.Required(CONF_PLATFORM): "event", vol.Required(CONF_EVENT_TYPE): cv.string, vol.Optional(CONF_EVENT_DATA): dict, + vol.Optional(CONF_EVENT_CONTEXT): dict, } ) +def _populate_schema(config, config_parameter): + if config_parameter not in config: + return None + + return vol.Schema( + {vol.Required(key): value for key, value in config[config_parameter].items()}, + extra=vol.ALLOW_EXTRA, + ) + + async def async_attach_trigger( hass, config, action, automation_info, *, platform_type="event" ): """Listen for events based on configuration.""" event_type = config.get(CONF_EVENT_TYPE) - event_data_schema = None - if config.get(CONF_EVENT_DATA): - event_data_schema = vol.Schema( - { - vol.Required(key): value - for key, value in config.get(CONF_EVENT_DATA).items() - }, - extra=vol.ALLOW_EXTRA, - ) + event_data_schema = _populate_schema(config, CONF_EVENT_DATA) + event_context_schema = _populate_schema(config, CONF_EVENT_CONTEXT) @callback def handle_event(event): """Listen for events and calls the action when data matches.""" - if event_data_schema: - # Check that the event data matches the configured + try: + # Check that the event data and context match the configured # schema if one was provided - try: + if event_data_schema: event_data_schema(event.data) - except vol.Invalid: - # If event data doesn't match requested schema, skip event - return + if event_context_schema: + event_context_schema(event.context.as_dict()) + except vol.Invalid: + # If event doesn't match, skip event + return hass.async_run_job( action, diff --git a/homeassistant/components/homeassistant/triggers/state.py b/homeassistant/components/homeassistant/triggers/state.py index f57db0ed56a9cc..915856951d2122 100644 --- a/homeassistant/components/homeassistant/triggers/state.py +++ b/homeassistant/components/homeassistant/triggers/state.py @@ -1,7 +1,7 @@ """Offer state listening automation rules.""" from datetime import timedelta import logging -from typing import Dict, Optional +from typing import Any, Dict, Optional import voluptuous as vol @@ -25,18 +25,43 @@ CONF_FROM = "from" CONF_TO = "to" -TRIGGER_SCHEMA = vol.Schema( +BASE_SCHEMA = { + vol.Required(CONF_PLATFORM): "state", + vol.Required(CONF_ENTITY_ID): cv.entity_ids, + vol.Optional(CONF_FOR): cv.positive_time_period_template, + vol.Optional(CONF_ATTRIBUTE): cv.match_all, +} + +TRIGGER_STATE_SCHEMA = vol.Schema( { - vol.Required(CONF_PLATFORM): "state", - vol.Required(CONF_ENTITY_ID): cv.entity_ids, + **BASE_SCHEMA, # These are str on purpose. Want to catch YAML conversions vol.Optional(CONF_FROM): vol.Any(str, [str]), vol.Optional(CONF_TO): vol.Any(str, [str]), - vol.Optional(CONF_FOR): cv.positive_time_period_template, - vol.Optional(CONF_ATTRIBUTE): cv.match_all, } ) +TRIGGER_ATTRIBUTE_SCHEMA = vol.Schema( + { + **BASE_SCHEMA, + vol.Optional(CONF_FROM): cv.match_all, + vol.Optional(CONF_TO): cv.match_all, + } +) + + +def TRIGGER_SCHEMA(value: Any) -> dict: # pylint: disable=invalid-name + """Validate trigger.""" + if not isinstance(value, dict): + raise vol.Invalid("Expected a dictionary") + + # We use this approach instead of vol.Any because + # this gives better error messages. + if CONF_ATTRIBUTE in value: + return TRIGGER_ATTRIBUTE_SCHEMA(value) + + return TRIGGER_STATE_SCHEMA(value) + async def async_attach_trigger( hass: HomeAssistant, @@ -145,7 +170,7 @@ def _check_same_state(_, _2, new_st: State): else: cur_value = new_st.attributes.get(attribute) - if CONF_TO not in config: + if CONF_FROM in config and CONF_TO not in config: return cur_value != old_value return cur_value == new_value diff --git a/homeassistant/components/homeassistant/triggers/time_pattern.py b/homeassistant/components/homeassistant/triggers/time_pattern.py index adacc939870aa1..5f03fb593d623a 100644 --- a/homeassistant/components/homeassistant/triggers/time_pattern.py +++ b/homeassistant/components/homeassistant/triggers/time_pattern.py @@ -36,7 +36,7 @@ def __call__(self, value): if isinstance(value, str) and value.startswith("/"): number = int(value[1:]) else: - number = int(value) + value = number = int(value) if not (0 <= number <= self.maximum): raise vol.Invalid(f"must be a value between 0 and {self.maximum}") diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index 2fb61a61fedf3d..6dc2e2364b63e3 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -9,7 +9,11 @@ from pyhap.const import CATEGORY_OTHER from homeassistant.components import cover, vacuum -from homeassistant.components.cover import DEVICE_CLASS_GARAGE, DEVICE_CLASS_GATE +from homeassistant.components.cover import ( + DEVICE_CLASS_GARAGE, + DEVICE_CLASS_GATE, + DEVICE_CLASS_WINDOW, +) from homeassistant.components.media_player import DEVICE_CLASS_TV from homeassistant.const import ( ATTR_BATTERY_CHARGING, @@ -24,6 +28,7 @@ DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, + LIGHT_LUX, PERCENTAGE, STATE_ON, STATE_UNAVAILABLE, @@ -154,6 +159,11 @@ def get_accessory(hass, driver, state, aid, config): cover.SUPPORT_OPEN | cover.SUPPORT_CLOSE ): a_type = "GarageDoorOpener" + elif ( + device_class == DEVICE_CLASS_WINDOW + and features & cover.SUPPORT_SET_POSITION + ): + a_type = "Window" elif features & cover.SUPPORT_SET_POSITION: a_type = "WindowCovering" elif features & (cover.SUPPORT_OPEN | cover.SUPPORT_CLOSE): @@ -197,7 +207,7 @@ def get_accessory(hass, driver, state, aid, config): a_type = "CarbonMonoxideSensor" elif device_class == DEVICE_CLASS_CO2 or DEVICE_CLASS_CO2 in state.entity_id: a_type = "CarbonDioxideSensor" - elif device_class == DEVICE_CLASS_ILLUMINANCE or unit in ("lm", "lx"): + elif device_class == DEVICE_CLASS_ILLUMINANCE or unit in ("lm", LIGHT_LUX): a_type = "LightSensor" elif state.domain == "switch": diff --git a/homeassistant/components/homekit/config_flow.py b/homeassistant/components/homekit/config_flow.py index 6a3206ac41bd4a..3d35b685271f76 100644 --- a/homeassistant/components/homekit/config_flow.py +++ b/homeassistant/components/homekit/config_flow.py @@ -118,7 +118,7 @@ async def async_step_user(self, user_input=None): self.entry_title = title return await self.async_step_pairing() - default_domains = [] if self._async_current_entries() else DEFAULT_DOMAINS + default_domains = [] if self._async_current_names() else DEFAULT_DOMAINS setup_schema = vol.Schema( { vol.Optional(CONF_AUTO_START, default=DEFAULT_AUTO_START): bool, @@ -146,17 +146,27 @@ async def _async_available_port(self): find_next_available_port, DEFAULT_CONFIG_FLOW_PORT ) + @callback + def _async_current_names(self): + """Return a set of bridge names.""" + current_entries = self._async_current_entries() + + return { + entry.data[CONF_NAME] + for entry in current_entries + if CONF_NAME in entry.data + } + @callback def _async_available_name(self): """Return an available for the bridge.""" - current_entries = self._async_current_entries() # We always pick a RANDOM name to avoid Zeroconf # name collisions. If the name has been seen before # pairing will probably fail. acceptable_chars = string.ascii_uppercase + string.digits trailer = "".join(random.choices(acceptable_chars, k=4)) - all_names = {entry.data[CONF_NAME] for entry in current_entries} + all_names = self._async_current_names() suggested_name = f"{SHORT_BRIDGE_NAME} {trailer}" while suggested_name in all_names: trailer = "".join(random.choices(acceptable_chars, k=4)) diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py index d8eec057191e2d..9a2bc37a5a91b4 100644 --- a/homeassistant/components/homekit/const.py +++ b/homeassistant/components/homekit/const.py @@ -136,6 +136,7 @@ SERV_TEMPERATURE_SENSOR = "TemperatureSensor" SERV_THERMOSTAT = "Thermostat" SERV_VALVE = "Valve" +SERV_WINDOW = "Window" SERV_WINDOW_COVERING = "WindowCovering" # #### Characteristics #### diff --git a/homeassistant/components/homekit/translations/fr.json b/homeassistant/components/homekit/translations/fr.json index 3c6162e378eaaa..4bac36a846728f 100644 --- a/homeassistant/components/homekit/translations/fr.json +++ b/homeassistant/components/homekit/translations/fr.json @@ -32,6 +32,7 @@ "data": { "camera_copy": "Cam\u00e9ras prenant en charge les flux H.264 natifs" }, + "description": "V\u00e9rifiez toutes les cam\u00e9ras prenant en charge les flux H.264 natifs. Si la cam\u00e9ra ne produit pas de flux H.264, le syst\u00e8me transcodera la vid\u00e9o en H.264 pour HomeKit. Le transcodage n\u00e9cessite un processeur performant et il est peu probable qu'il fonctionne sur des ordinateurs \u00e0 carte unique.", "title": "S\u00e9lectionnez le codec vid\u00e9o de la cam\u00e9ra." }, "exclude": { diff --git a/homeassistant/components/homekit/type_covers.py b/homeassistant/components/homekit/type_covers.py index 1e18ad82b94ddb..d8d8da5a974bea 100644 --- a/homeassistant/components/homekit/type_covers.py +++ b/homeassistant/components/homekit/type_covers.py @@ -1,7 +1,11 @@ """Class to hold all cover accessories.""" import logging -from pyhap.const import CATEGORY_GARAGE_DOOR_OPENER, CATEGORY_WINDOW_COVERING +from pyhap.const import ( + CATEGORY_GARAGE_DOOR_OPENER, + CATEGORY_WINDOW, + CATEGORY_WINDOW_COVERING, +) from homeassistant.components.cover import ( ATTR_CURRENT_POSITION, @@ -46,6 +50,7 @@ HK_POSITION_GOING_TO_MIN, HK_POSITION_STOPPED, SERV_GARAGE_DOOR_OPENER, + SERV_WINDOW, SERV_WINDOW_COVERING, ) @@ -128,16 +133,16 @@ def async_update_state(self, new_state): self.char_current_state.set_value(current_door_state) -class WindowCoveringBase(HomeAccessory): +class OpeningDeviceBase(HomeAccessory): """Generate a base Window accessory for a cover entity. This class is used for WindowCoveringBasic and WindowCovering """ - def __init__(self, *args, category): - """Initialize a WindowCoveringBase accessory object.""" - super().__init__(*args, category=CATEGORY_WINDOW_COVERING) + def __init__(self, *args, category, service): + """Initialize a OpeningDeviceBase accessory object.""" + super().__init__(*args, category=category) state = self.hass.states.get(self.entity_id) self.features = state.attributes.get(ATTR_SUPPORTED_FEATURES, 0) @@ -151,7 +156,7 @@ def __init__(self, *args, category): if self._supports_tilt: self.chars.extend([CHAR_TARGET_TILT_ANGLE, CHAR_CURRENT_TILT_ANGLE]) - self.serv_cover = self.add_preload_service(SERV_WINDOW_COVERING, self.chars) + self.serv_cover = self.add_preload_service(service, self.chars) if self._supports_stop: self.char_hold_position = self.serv_cover.configure_char( @@ -211,16 +216,15 @@ def async_update_state(self, new_state): self._homekit_target_tilt = None -@TYPES.register("WindowCovering") -class WindowCovering(WindowCoveringBase, HomeAccessory): - """Generate a Window accessory for a cover entity. +class OpeningDevice(OpeningDeviceBase, HomeAccessory): + """Generate a Window/WindowOpening accessory for a cover entity. The cover entity must support: set_cover_position. """ - def __init__(self, *args): + def __init__(self, *args, category, service): """Initialize a WindowCovering accessory object.""" - super().__init__(*args, category=CATEGORY_WINDOW_COVERING) + super().__init__(*args, category=category, service=service) state = self.hass.states.get(self.entity_id) self._homekit_target = None @@ -278,8 +282,34 @@ def async_update_state(self, new_state): super().async_update_state(new_state) +@TYPES.register("Window") +class Window(OpeningDevice): + """Generate a Window accessory for a cover entity with DEVICE_CLASS_WINDOW. + + The entity must support: set_cover_position. + """ + + def __init__(self, *args): + """Initialize a Window accessory object.""" + super().__init__(*args, category=CATEGORY_WINDOW, service=SERV_WINDOW) + + +@TYPES.register("WindowCovering") +class WindowCovering(OpeningDevice): + """Generate a WindowCovering accessory for a cover entity. + + The entity must support: set_cover_position. + """ + + def __init__(self, *args): + """Initialize a WindowCovering accessory object.""" + super().__init__( + *args, category=CATEGORY_WINDOW_COVERING, service=SERV_WINDOW_COVERING + ) + + @TYPES.register("WindowCoveringBasic") -class WindowCoveringBasic(WindowCoveringBase, HomeAccessory): +class WindowCoveringBasic(OpeningDeviceBase, HomeAccessory): """Generate a Window accessory for a cover entity. The cover entity must support: open_cover, close_cover, @@ -287,8 +317,10 @@ class WindowCoveringBasic(WindowCoveringBase, HomeAccessory): """ def __init__(self, *args): - """Initialize a WindowCovering accessory object.""" - super().__init__(*args, category=CATEGORY_WINDOW_COVERING) + """Initialize a WindowCoveringBasic accessory object.""" + super().__init__( + *args, category=CATEGORY_WINDOW_COVERING, service=SERV_WINDOW_COVERING + ) state = self.hass.states.get(self.entity_id) self.char_current_position = self.serv_cover.configure_char( CHAR_CURRENT_POSITION, value=0 diff --git a/homeassistant/components/homekit/type_lights.py b/homeassistant/components/homekit/type_lights.py index 612d8e53a02238..086934ea6f756f 100644 --- a/homeassistant/components/homekit/type_lights.py +++ b/homeassistant/components/homekit/type_lights.py @@ -24,6 +24,10 @@ STATE_ON, ) from homeassistant.core import callback +from homeassistant.util.color import ( + color_temperature_mired_to_kelvin, + color_temperature_to_hs, +) from .accessories import TYPES, HomeAccessory from .const import ( @@ -64,8 +68,6 @@ def __init__(self, *args): if self._features & SUPPORT_COLOR: self.chars.append(CHAR_HUE) self.chars.append(CHAR_SATURATION) - self._hue = None - self._saturation = None elif self._features & SUPPORT_COLOR_TEMP: # ColorTemperature and Hue characteristic should not be # exposed both. Both states are tracked separately in HomeKit, @@ -179,7 +181,16 @@ def async_update_state(self, new_state): # Handle Color if CHAR_SATURATION in self.chars and CHAR_HUE in self.chars: - hue, saturation = new_state.attributes.get(ATTR_HS_COLOR, (None, None)) + if ATTR_HS_COLOR in new_state.attributes: + hue, saturation = new_state.attributes[ATTR_HS_COLOR] + elif ATTR_COLOR_TEMP in new_state.attributes: + hue, saturation = color_temperature_to_hs( + color_temperature_mired_to_kelvin( + new_state.attributes[ATTR_COLOR_TEMP] + ) + ) + else: + hue, saturation = None, None if isinstance(hue, (int, float)) and isinstance(saturation, (int, float)): hue = round(hue, 0) saturation = round(saturation, 0) diff --git a/homeassistant/components/homekit/type_security_systems.py b/homeassistant/components/homekit/type_security_systems.py index 7d8dcac046d70d..feae1b5cd0676b 100644 --- a/homeassistant/components/homekit/type_security_systems.py +++ b/homeassistant/components/homekit/type_security_systems.py @@ -2,11 +2,19 @@ import logging from pyhap.const import CATEGORY_ALARM_SYSTEM +from pyhap.loader import get_loader from homeassistant.components.alarm_control_panel import DOMAIN +from homeassistant.components.alarm_control_panel.const import ( + SUPPORT_ALARM_ARM_AWAY, + SUPPORT_ALARM_ARM_HOME, + SUPPORT_ALARM_ARM_NIGHT, + SUPPORT_ALARM_TRIGGER, +) from homeassistant.const import ( ATTR_CODE, ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, SERVICE_ALARM_ARM_AWAY, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_NIGHT, @@ -36,6 +44,13 @@ STATE_ALARM_TRIGGERED: 4, } +HASS_TO_HOMEKIT_SERVICES = { + SERVICE_ALARM_ARM_HOME: 0, + SERVICE_ALARM_ARM_AWAY: 1, + SERVICE_ALARM_ARM_NIGHT: 2, + SERVICE_ALARM_DISARM: 3, +} + HOMEKIT_TO_HASS = {c: s for s, c in HASS_TO_HOMEKIT.items()} STATE_TO_SERVICE = { @@ -56,13 +71,72 @@ def __init__(self, *args): state = self.hass.states.get(self.entity_id) self._alarm_code = self.config.get(ATTR_CODE) + supported_states = state.attributes.get( + ATTR_SUPPORTED_FEATURES, + ( + SUPPORT_ALARM_ARM_HOME + | SUPPORT_ALARM_ARM_AWAY + | SUPPORT_ALARM_ARM_NIGHT + | SUPPORT_ALARM_TRIGGER + ), + ) + + loader = get_loader() + default_current_states = loader.get_char( + "SecuritySystemCurrentState" + ).properties.get("ValidValues") + default_target_services = loader.get_char( + "SecuritySystemTargetState" + ).properties.get("ValidValues") + + current_supported_states = [ + HASS_TO_HOMEKIT[STATE_ALARM_DISARMED], + HASS_TO_HOMEKIT[STATE_ALARM_TRIGGERED], + ] + target_supported_services = [HASS_TO_HOMEKIT_SERVICES[SERVICE_ALARM_DISARM]] + + if supported_states & SUPPORT_ALARM_ARM_HOME: + current_supported_states.append(HASS_TO_HOMEKIT[STATE_ALARM_ARMED_HOME]) + target_supported_services.append( + HASS_TO_HOMEKIT_SERVICES[SERVICE_ALARM_ARM_HOME] + ) + + if supported_states & SUPPORT_ALARM_ARM_AWAY: + current_supported_states.append(HASS_TO_HOMEKIT[STATE_ALARM_ARMED_AWAY]) + target_supported_services.append( + HASS_TO_HOMEKIT_SERVICES[SERVICE_ALARM_ARM_AWAY] + ) + + if supported_states & SUPPORT_ALARM_ARM_NIGHT: + current_supported_states.append(HASS_TO_HOMEKIT[STATE_ALARM_ARMED_NIGHT]) + target_supported_services.append( + HASS_TO_HOMEKIT_SERVICES[SERVICE_ALARM_ARM_NIGHT] + ) + + new_current_states = { + key: val + for key, val in default_current_states.items() + if val in current_supported_states + } + new_target_services = { + key: val + for key, val in default_target_services.items() + if val in target_supported_services + } + serv_alarm = self.add_preload_service(SERV_SECURITY_SYSTEM) self.char_current_state = serv_alarm.configure_char( - CHAR_CURRENT_SECURITY_STATE, value=3 + CHAR_CURRENT_SECURITY_STATE, + value=HASS_TO_HOMEKIT[STATE_ALARM_DISARMED], + valid_values=new_current_states, ) self.char_target_state = serv_alarm.configure_char( - CHAR_TARGET_SECURITY_STATE, value=3, setter_callback=self.set_security_state + CHAR_TARGET_SECURITY_STATE, + value=HASS_TO_HOMEKIT_SERVICES[SERVICE_ALARM_DISARM], + valid_values=new_target_services, + setter_callback=self.set_security_state, ) + # Set the state so it is in sync on initial # GET to avoid an event storm after homekit startup self.async_update_state(state) diff --git a/homeassistant/components/homekit_controller/alarm_control_panel.py b/homeassistant/components/homekit_controller/alarm_control_panel.py index 0b8f0b3b2f84f2..333eb462439b54 100644 --- a/homeassistant/components/homekit_controller/alarm_control_panel.py +++ b/homeassistant/components/homekit_controller/alarm_control_panel.py @@ -110,10 +110,8 @@ async def set_alarm_state(self, state, code=None): @property def device_state_attributes(self): """Return the optional state attributes.""" - attributes = {} - battery_level = self.service.value(CharacteristicsTypes.BATTERY_LEVEL) - if battery_level: - attributes[ATTR_BATTERY_LEVEL] = battery_level - return attributes + if not battery_level: + return {} + return {ATTR_BATTERY_LEVEL: battery_level} diff --git a/homeassistant/components/homekit_controller/config_flow.py b/homeassistant/components/homekit_controller/config_flow.py index 2c69512db9d681..9881ef15dcb02b 100644 --- a/homeassistant/components/homekit_controller/config_flow.py +++ b/homeassistant/components/homekit_controller/config_flow.py @@ -8,19 +8,44 @@ from homeassistant import config_entries from homeassistant.components import zeroconf from homeassistant.core import callback +from homeassistant.helpers.device_registry import ( + CONNECTION_NETWORK_MAC, + async_get_registry as async_get_device_registry, +) from .connection import get_accessory_name, get_bridge_information from .const import DOMAIN, KNOWN_DEVICES -HOMEKIT_IGNORE = ["Home Assistant Bridge"] HOMEKIT_DIR = ".homekit" +HOMEKIT_BRIDGE_DOMAIN = "homekit" +HOMEKIT_BRIDGE_SERIAL_NUMBER = "homekit.bridge" +HOMEKIT_BRIDGE_MODEL = "Home Assistant HomeKit Bridge" + PAIRING_FILE = "pairing.json" +MDNS_SUFFIX = "._hap._tcp.local." + PIN_FORMAT = re.compile(r"^(\d{3})-{0,1}(\d{2})-{0,1}(\d{3})$") _LOGGER = logging.getLogger(__name__) +DISALLOWED_CODES = { + "00000000", + "11111111", + "22222222", + "33333333", + "44444444", + "55555555", + "66666666", + "77777777", + "88888888", + "99999999", + "12345678", + "87654321", +} + + def normalize_hkid(hkid): """Normalize a hkid so that it is safe to compare with other normalized hkids.""" return hkid.lower() @@ -42,9 +67,12 @@ def ensure_pin_format(pin): If incorrect code is entered, an exception is raised. """ - match = PIN_FORMAT.search(pin) + match = PIN_FORMAT.search(pin.strip()) if not match: raise aiohomekit.exceptions.MalformedPinError(f"Invalid PIN code f{pin}") + pin_without_dashes = "".join(match.groups()) + if pin_without_dashes in DISALLOWED_CODES: + raise aiohomekit.exceptions.MalformedPinError(f"Invalid PIN code f{pin}") return "-".join(match.groups()) @@ -59,6 +87,7 @@ def __init__(self): """Initialize the homekit_controller flow.""" self.model = None self.hkid = None + self.name = None self.devices = {} self.controller = None self.finish_pairing = None @@ -76,9 +105,11 @@ async def async_step_user(self, user_input=None): key = user_input["device"] self.hkid = self.devices[key].device_id self.model = self.devices[key].info["md"] + self.name = key[: -len(MDNS_SUFFIX)] if key.endswith(MDNS_SUFFIX) else key await self.async_set_unique_id( normalize_hkid(self.hkid), raise_on_progress=False ) + return await self.async_step_pair() if self.controller is None: @@ -141,6 +172,17 @@ async def async_step_unignore(self, user_input): return self.async_abort(reason="no_devices") + async def _hkid_is_homekit_bridge(self, hkid): + """Determine if the device is a homekit bridge.""" + dev_reg = await async_get_device_registry(self.hass) + device = dev_reg.async_get_device( + identifiers=set(), connections={(CONNECTION_NETWORK_MAC, hkid)} + ) + + if device is None: + return False + return device.model == HOMEKIT_BRIDGE_MODEL + async def async_step_zeroconf(self, discovery_info): """Handle a discovered HomeKit accessory. @@ -153,6 +195,12 @@ async def async_step_zeroconf(self, discovery_info): key.lower(): value for (key, value) in discovery_info["properties"].items() } + if "id" not in properties: + _LOGGER.warning( + "HomeKit device %s: id not exposed, in violation of spec", properties + ) + return self.async_abort(reason="invalid_properties") + # The hkid is a unique random number that looks like a pairing code. # It changes if a device is factory reset. hkid = properties["id"] @@ -198,7 +246,6 @@ async def async_step_zeroconf(self, discovery_info): # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["hkid"] = hkid - self.context["title_placeholders"] = {"name": name} if paired: # Device is paired but not to us - ignore it @@ -208,9 +255,10 @@ async def async_step_zeroconf(self, discovery_info): # Devices in HOMEKIT_IGNORE have native local integrations - users # should be encouraged to use native integration and not confused # by alternative HK API. - if model in HOMEKIT_IGNORE: + if await self._hkid_is_homekit_bridge(hkid): return self.async_abort(reason="ignored_model") + self.name = name self.model = model self.hkid = hkid @@ -280,9 +328,8 @@ async def async_step_pair(self, pair_info=None): # Its possible that the first try may have been busy so # we always check to see if self.finish_paring has been # set. - discovery = await self.controller.find_ip_by_device_id(self.hkid) - try: + discovery = await self.controller.find_ip_by_device_id(self.hkid) self.finish_pairing = await discovery.start_pairing(self.hkid) except aiohomekit.BusyError: @@ -332,9 +379,14 @@ async def async_step_protocol_error(self, user_input=None): @callback def _async_step_pair_show_form(self, errors=None): + # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 + placeholders = {"name": self.name} + self.context["title_placeholders"] = {"name": self.name} + return self.async_show_form( step_id="pair", errors=errors or {}, + description_placeholders=placeholders, data_schema=vol.Schema( {vol.Required("pairing_code"): vol.All(str, vol.Strip)} ), diff --git a/homeassistant/components/homekit_controller/cover.py b/homeassistant/components/homekit_controller/cover.py index 086f780b8162b7..5f43b3e8564e68 100644 --- a/homeassistant/components/homekit_controller/cover.py +++ b/homeassistant/components/homekit_controller/cover.py @@ -117,15 +117,12 @@ async def set_door_state(self, state): @property def device_state_attributes(self): """Return the optional state attributes.""" - attributes = {} - obstruction_detected = self.service.value( CharacteristicsTypes.OBSTRUCTION_DETECTED ) - if obstruction_detected: - attributes["obstruction-detected"] = obstruction_detected - - return attributes + if not obstruction_detected: + return {} + return {"obstruction-detected": obstruction_detected} class HomeKitWindowCover(HomeKitEntity, CoverEntity): @@ -249,12 +246,9 @@ async def async_set_cover_tilt_position(self, **kwargs): @property def device_state_attributes(self): """Return the optional state attributes.""" - attributes = {} - obstruction_detected = self.service.value( CharacteristicsTypes.OBSTRUCTION_DETECTED ) - if obstruction_detected: - attributes["obstruction-detected"] = obstruction_detected - - return attributes + if not obstruction_detected: + return {} + return {"obstruction-detected": obstruction_detected} diff --git a/homeassistant/components/homekit_controller/manifest.json b/homeassistant/components/homekit_controller/manifest.json index 06199e9f2102a8..1fb4c05c595d86 100644 --- a/homeassistant/components/homekit_controller/manifest.json +++ b/homeassistant/components/homekit_controller/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/homekit_controller", "requirements": [ - "aiohomekit==0.2.49" + "aiohomekit==0.2.53" ], "zeroconf": [ "_hap._tcp.local." diff --git a/homeassistant/components/homekit_controller/sensor.py b/homeassistant/components/homekit_controller/sensor.py index 944729d2e5cfd7..2075eb9dcc38a8 100644 --- a/homeassistant/components/homekit_controller/sensor.py +++ b/homeassistant/components/homekit_controller/sensor.py @@ -7,6 +7,7 @@ DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, + LIGHT_LUX, PERCENTAGE, TEMP_CELSIUS, ) @@ -19,8 +20,6 @@ BRIGHTNESS_ICON = "mdi:brightness-6" CO2_ICON = "mdi:molecule-co2" -UNIT_LUX = "lux" - class HomeKitHumiditySensor(HomeKitEntity): """Representation of a Homekit humidity sensor.""" @@ -113,7 +112,7 @@ def icon(self): @property def unit_of_measurement(self): """Return units for the sensor.""" - return UNIT_LUX + return LIGHT_LUX @property def state(self): diff --git a/homeassistant/components/homekit_controller/strings.json b/homeassistant/components/homekit_controller/strings.json index babfac05718138..3c6b8405e31e67 100644 --- a/homeassistant/components/homekit_controller/strings.json +++ b/homeassistant/components/homekit_controller/strings.json @@ -1,18 +1,18 @@ { "title": "HomeKit Controller", "config": { - "flow_title": "HomeKit Accessory: {name}", + "flow_title": "{name} via HomeKit Accessory Protocol", "step": { "user": { - "title": "Pair with HomeKit Accessory", - "description": "Select the device you want to pair with", + "title": "Device selection", + "description": "HomeKit Controller communicates over the local area network using a secure encrypted connection without a separate HomeKit controller or iCloud. Select the device you want to pair with:", "data": { "device": "Device" } }, "pair": { - "title": "Pair with HomeKit Accessory", - "description": "Enter your HomeKit pairing code (in the format XXX-XX-XXX) to use this accessory", + "title": "Pair with a device via HomeKit Accessory Protocol", + "description": "HomeKit Controller communicates with {name} over the local area network using a secure encrypted connection without a separate HomeKit controller or iCloud. Enter your HomeKit pairing code (in the format XXX-XX-XXX) to use this accessory. This code is usually found on the device itself or in the packaging.", "data": { "pairing_code": "Pairing Code" } @@ -44,27 +44,28 @@ "already_configured": "Accessory is already configured with this controller.", "invalid_config_entry": "This device is showing as ready to pair but there is already a conflicting configuration entry for it in Home Assistant that must first be removed.", "accessory_not_found_error": "Cannot add pairing as device can no longer be found.", - "already_in_progress": "Config flow for device is already in progress." + "invalid_properties": "Invalid properties announced by device.", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]" } }, "device_automation": { "trigger_type": { - "single_press": "\"{subtype}\" pressed", - "double_press": "\"{subtype}\" pressed twice", - "long_press": "\"{subtype}\" pressed and held" + "single_press": "\"{subtype}\" pressed", + "double_press": "\"{subtype}\" pressed twice", + "long_press": "\"{subtype}\" pressed and held" }, "trigger_subtype": { - "doorbell": "Doorbell", - "button1": "Button 1", - "button2": "Button 2", - "button3": "Button 3", - "button4": "Button 4", - "button5": "Button 5", - "button6": "Button 6", - "button7": "Button 7", - "button8": "Button 8", - "button9": "Button 9", - "button10": "Button 10" + "doorbell": "Doorbell", + "button1": "Button 1", + "button2": "Button 2", + "button3": "Button 3", + "button4": "Button 4", + "button5": "Button 5", + "button6": "Button 6", + "button7": "Button 7", + "button8": "Button 8", + "button9": "Button 9", + "button10": "Button 10" } } } diff --git a/homeassistant/components/homekit_controller/translations/ca.json b/homeassistant/components/homekit_controller/translations/ca.json index bf46d58491701c..8b1dd8eca18699 100644 --- a/homeassistant/components/homekit_controller/translations/ca.json +++ b/homeassistant/components/homekit_controller/translations/ca.json @@ -3,10 +3,11 @@ "abort": { "accessory_not_found_error": "No s'ha pogut vincular, no s'ha trobat el dispositiu.", "already_configured": "Accessori ja configurat amb aquest controlador.", - "already_in_progress": "El flux de dades de configuraci\u00f3 pel dispositiu ja est\u00e0 en curs.", + "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", "already_paired": "Aquest accessori ja est\u00e0 vinculat amb un altre dispositiu. Reinicia l'accessori i torna-ho a provar.", "ignored_model": "La disponibilitat de HomeKit per aquest model est\u00e0 bloquejada ja que, de moment, no hi ha una integraci\u00f3 nativa completa.", "invalid_config_entry": "Aquest dispositiu s'est\u00e0 mostrant com a llest per a ser vinculat per\u00f2 ja hi ha una entrada de configuraci\u00f3 conflictiva a Home Assistant que s'ha d'eliminar primer.", + "invalid_properties": "Propietats anunciades pel dispositiu no v\u00e0lides.", "no_devices": "No s'han trobat dispositius desvinculats." }, "error": { @@ -19,7 +20,7 @@ "unable_to_pair": "No s'ha pogut vincular, torna-ho a provar.", "unknown_error": "El dispositiu ha em\u00e8s un error desconegut. Vinculaci\u00f3 fallida." }, - "flow_title": "Accessori HomeKit: {name}", + "flow_title": "{name} a trav\u00e9s de HomeKit Accessory Protocol", "step": { "busy_error": { "description": "Atura la vinculaci\u00f3 a tots els controladors o prova de reiniciar el dispositiu, despr\u00e9s, segueix amb la vinculaci\u00f3.", @@ -33,8 +34,8 @@ "data": { "pairing_code": "Codi de vinculaci\u00f3" }, - "description": "Introdueix el codi de vinculaci\u00f3 de HomeKit per utilitzar aquest accessori (format XXX-XX-XXX)", - "title": "Vinculaci\u00f3 amb" + "description": "El controlador HomeKit es comunica amb {name} a trav\u00e9s de la xarxa d'\u00e0rea local utilitzant una connexi\u00f3 segura encriptada sense un HomeKit o iCloud separats. Introdueix el codi de vinculaci\u00f3 de HomeKit (en format XXX-XX-XXX) per utilitzar aquest accessori. Aquest codi es troba normalment en el propi dispositiu o en la seva caixa.", + "title": "Vinculaci\u00f3 amb un dispositiu a trav\u00e9s de HomeKit Accessory Protocol" }, "protocol_error": { "description": "\u00c9s possible que el dispositiu no estigui en mode de vinculaci\u00f3, potser cal pr\u00e9mer un bot\u00f3 f\u00edsic o virtual. Assegura't que el dispositiu est\u00e0 en mode vinculaci\u00f3 o prova de reiniciar-lo, despr\u00e9s, segueix amb la vinculaci\u00f3.", @@ -48,10 +49,30 @@ "data": { "device": "Dispositiu" }, - "description": "Selecciona el dispositiu amb el qual et vols vincular", - "title": "Vinculaci\u00f3 amb un accessori HomeKit" + "description": "El controlador HomeKit es comunica a trav\u00e9s de la xarxa d'\u00e0rea local utilitzant una connexi\u00f3 segura encriptada sense un HomeKit o iCloud separats. Selecciona el dispositiu amb el qual et vols vincular:", + "title": "Selecci\u00f3 de dispositiu" } } }, + "device_automation": { + "trigger_subtype": { + "button1": "Bot\u00f3 1", + "button10": "Bot\u00f3 10", + "button2": "Bot\u00f3 2", + "button3": "Bot\u00f3 3", + "button4": "Bot\u00f3 4", + "button5": "Bot\u00f3 5", + "button6": "Bot\u00f3 6", + "button7": "Bot\u00f3 7", + "button8": "Bot\u00f3 8", + "button9": "Bot\u00f3 9", + "doorbell": "Timbre" + }, + "trigger_type": { + "double_press": "\"{subtype}\" premut dues vegades", + "long_press": "\"{subtype}\" premut i mantingut", + "single_press": "\"{subtype}\" premut" + } + }, "title": "Controlador HomeKit" } \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/de.json b/homeassistant/components/homekit_controller/translations/de.json index b10fb6efe45fc6..6ee03a37f88fb5 100644 --- a/homeassistant/components/homekit_controller/translations/de.json +++ b/homeassistant/components/homekit_controller/translations/de.json @@ -36,5 +36,25 @@ } } }, + "device_automation": { + "trigger_subtype": { + "button1": "Knopf 1", + "button10": "Knopf 10", + "button2": "Knopf 2", + "button3": "Knopf 3", + "button4": "Knopf 4", + "button5": "Knopf 5", + "button6": "Knopf 6", + "button7": "Knopf 7", + "button8": "Knopf 8", + "button9": "Knopf 9", + "doorbell": "T\u00fcrklingel" + }, + "trigger_type": { + "double_press": "\"{subtype}\" zweimal gedr\u00fcckt", + "long_press": "\"{subtype}\" gedr\u00fcckt und gehalten", + "single_press": "\"{subtype}\" gedr\u00fcckt" + } + }, "title": "HomeKit-Controller" } \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/el.json b/homeassistant/components/homekit_controller/translations/el.json new file mode 100644 index 00000000000000..41ab3bf67042b9 --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/el.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "invalid_properties": "\u0391\u03bd\u03b1\u03ba\u03bf\u03b9\u03bd\u03ce\u03b8\u03b7\u03ba\u03b1\u03bd \u03bc\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b5\u03c2 \u03b9\u03b4\u03b9\u03cc\u03c4\u03b7\u03c4\u03b5\u03c2 \u03b1\u03c0\u03cc \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae." + }, + "step": { + "busy_error": { + "description": "\u039c\u03b1\u03c4\u03b1\u03b9\u03ce\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03b6\u03b5\u03cd\u03be\u03b7 \u03c3\u03b5 \u03cc\u03bb\u03bf\u03c5\u03c2 \u03c4\u03bf\u03c5\u03c2 \u03b5\u03bb\u03b5\u03b3\u03ba\u03c4\u03ad\u03c2 \u03ae \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ba\u03b1\u03b9, \u03c3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7." + }, + "max_tries_error": { + "description": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ad\u03c7\u03b5\u03b9 \u03bb\u03ac\u03b2\u03b5\u03b9 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03b5\u03c2 \u03b1\u03c0\u03cc 100 \u03b1\u03c0\u03bf\u03c4\u03c5\u03c7\u03b7\u03bc\u03ad\u03bd\u03b5\u03c2 \u03c0\u03c1\u03bf\u03c3\u03c0\u03ac\u03b8\u03b5\u03b9\u03b5\u03c2 \u03b5\u03bb\u03ad\u03b3\u03c7\u03bf\u03c5 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2. \u0394\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bd\u03b5\u03ba\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03ba\u03b1\u03b9, \u03c3\u03c4\u03b7 \u03c3\u03c5\u03bd\u03ad\u03c7\u03b5\u03b9\u03b1, \u03c3\u03c5\u03bd\u03b5\u03c7\u03af\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03c3\u03cd\u03b6\u03b5\u03c5\u03be\u03b7." + } + } + }, + "device_automation": { + "trigger_subtype": { + "button1": "\u039a\u03bf\u03c5\u03bc\u03c0\u03af 1", + "button10": "\u039a\u03bf\u03c5\u03bc\u03c0\u03af 10", + "button2": "\u039a\u03bf\u03c5\u03bc\u03c0\u03af 2", + "button3": "\u039a\u03bf\u03c5\u03bc\u03c0\u03af 3", + "button4": "\u039a\u03bf\u03c5\u03bc\u03c0\u03af 4", + "button5": "\u039a\u03bf\u03c5\u03bc\u03c0\u03af 5", + "button6": "\u039a\u03bf\u03c5\u03bc\u03c0\u03af 6", + "button7": "\u039a\u03bf\u03c5\u03bc\u03c0\u03af 7", + "button8": "\u039a\u03bf\u03c5\u03bc\u03c0\u03af 8", + "button9": "\u039a\u03bf\u03c5\u03bc\u03c0\u03af 9", + "doorbell": "\u039a\u03bf\u03c5\u03b4\u03bf\u03cd\u03bd\u03b9" + }, + "trigger_type": { + "double_press": "\u03a0\u03b9\u03ad\u03c3\u03c4\u03b7\u03ba\u03b5 \u03b4\u03cd\u03bf \u03c6\u03bf\u03c1\u03ad\u03c2 \u03c4\u03bf \" {subtype} \"", + "long_press": "\u03a0\u03b1\u03c4\u03ae\u03b8\u03b7\u03ba\u03b5 \u03ba\u03b1\u03b9 \u03ba\u03c1\u03b1\u03c4\u03ae\u03b8\u03b7\u03ba\u03b5 \u03c4\u03bf \" {subtype} \"", + "single_press": "\u03a0\u03b1\u03c4\u03ae\u03b8\u03b7\u03ba\u03b5 \u03c4\u03bf \" {subtype} \"" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/en.json b/homeassistant/components/homekit_controller/translations/en.json index afa790dd222ddd..3d7e237c73311b 100644 --- a/homeassistant/components/homekit_controller/translations/en.json +++ b/homeassistant/components/homekit_controller/translations/en.json @@ -3,10 +3,11 @@ "abort": { "accessory_not_found_error": "Cannot add pairing as device can no longer be found.", "already_configured": "Accessory is already configured with this controller.", - "already_in_progress": "Config flow for device is already in progress.", + "already_in_progress": "Configuration flow is already in progress", "already_paired": "This accessory is already paired to another device. Please reset the accessory and try again.", "ignored_model": "HomeKit support for this model is blocked as a more feature complete native integration is available.", "invalid_config_entry": "This device is showing as ready to pair but there is already a conflicting configuration entry for it in Home Assistant that must first be removed.", + "invalid_properties": "Invalid properties announced by device.", "no_devices": "No unpaired devices could be found" }, "error": { @@ -19,7 +20,7 @@ "unable_to_pair": "Unable to pair, please try again.", "unknown_error": "Device reported an unknown error. Pairing failed." }, - "flow_title": "HomeKit Accessory: {name}", + "flow_title": "{name} via HomeKit Accessory Protocol", "step": { "busy_error": { "description": "Abort pairing on all controllers, or try restarting the device, then continue to resume pairing.", @@ -33,8 +34,8 @@ "data": { "pairing_code": "Pairing Code" }, - "description": "Enter your HomeKit pairing code (in the format XXX-XX-XXX) to use this accessory", - "title": "Pair with HomeKit Accessory" + "description": "HomeKit Controller communicates with {name} over the local area network using a secure encrypted connection without a separate HomeKit controller or iCloud. Enter your HomeKit pairing code (in the format XXX-XX-XXX) to use this accessory. This code is usually found on the device itself or in the packaging.", + "title": "Pair with a device via HomeKit Accessory Protocol" }, "protocol_error": { "description": "The device may not be in pairing mode and may require a physical or virtual button press. Ensure the device is in pairing mode or try restarting the device, then continue to resume pairing.", @@ -48,31 +49,30 @@ "data": { "device": "Device" }, - "description": "Select the device you want to pair with", - "title": "Pair with HomeKit Accessory" + "description": "HomeKit Controller communicates over the local area network using a secure encrypted connection without a separate HomeKit controller or iCloud. Select the device you want to pair with:", + "title": "Device selection" } } }, - "title": "HomeKit Controller", "device_automation": { - "trigger_type": { - "single_press": "\"{subtype}\" pressed", - "double_press": "\"{subtype}\" pressed twice", - "long_press": "\"{subtype}\" pressed and held" - }, - "trigger_subtype": { - "doorbell": "Doorbell", - "button1": "Button 1", - "button2": "Button 2", - "button3": "Button 3", - "button4": "Button 4", - "button5": "Button 5", - "button6": "Button 6", - "button7": "Button 7", - "button8": "Button 8", - "button9": "Button 9", - "button10": "Button 10" - } - } -} - + "trigger_subtype": { + "button1": "Button 1", + "button10": "Button 10", + "button2": "Button 2", + "button3": "Button 3", + "button4": "Button 4", + "button5": "Button 5", + "button6": "Button 6", + "button7": "Button 7", + "button8": "Button 8", + "button9": "Button 9", + "doorbell": "Doorbell" + }, + "trigger_type": { + "double_press": "\"{subtype}\" pressed twice", + "long_press": "\"{subtype}\" pressed and held", + "single_press": "\"{subtype}\" pressed" + } + }, + "title": "HomeKit Controller" +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/es.json b/homeassistant/components/homekit_controller/translations/es.json index 8eb450e65581f4..b1daa4e50cc8af 100644 --- a/homeassistant/components/homekit_controller/translations/es.json +++ b/homeassistant/components/homekit_controller/translations/es.json @@ -7,6 +7,7 @@ "already_paired": "Este accesorio ya est\u00e1 emparejado con otro dispositivo. Por favor, reinicia el accesorio e int\u00e9ntalo de nuevo.", "ignored_model": "El soporte de HomeKit para este modelo est\u00e1 bloqueado ya que est\u00e1 disponible una integraci\u00f3n nativa m\u00e1s completa.", "invalid_config_entry": "Este dispositivo se muestra como listo para vincular, pero ya existe una entrada que causa conflicto en Home Assistant y se debe eliminar primero.", + "invalid_properties": "Propiedades no v\u00e1lidas anunciadas por dispositivo.", "no_devices": "No se encontraron dispositivos no emparejados" }, "error": { @@ -53,5 +54,25 @@ } } }, + "device_automation": { + "trigger_subtype": { + "button1": "Bot\u00f3n 1", + "button10": "Bot\u00f3n 10", + "button2": "Bot\u00f3n 2", + "button3": "Bot\u00f3n 3", + "button4": "Bot\u00f3n 4", + "button5": "Bot\u00f3n 5", + "button6": "Bot\u00f3n 6", + "button7": "Bot\u00f3n 7", + "button8": "Bot\u00f3n 8", + "button9": "Bot\u00f3n 9", + "doorbell": "Timbre de la puerta" + }, + "trigger_type": { + "double_press": "\"{subtype}\" pulsado dos veces", + "long_press": "\"{subtype}\" pulsado y mantenido", + "single_press": "\"{subtype}\" pulsado" + } + }, "title": "Accesorio HomeKit" } \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/et.json b/homeassistant/components/homekit_controller/translations/et.json new file mode 100644 index 00000000000000..31788215005fc1 --- /dev/null +++ b/homeassistant/components/homekit_controller/translations/et.json @@ -0,0 +1,22 @@ +{ + "device_automation": { + "trigger_subtype": { + "button1": "Nupp 1", + "button10": "Nupp 10", + "button2": "Nupp 2", + "button3": "Nupp 3", + "button4": "Nupp 4", + "button5": "Nupp 5", + "button6": "Nupp 6", + "button7": "Nupp 7", + "button8": "Nupp 8", + "button9": "Nupp 9", + "doorbell": "Uksekell" + }, + "trigger_type": { + "double_press": "\" {subtype} \" tehtud topeltkl\u00f5ps", + "long_press": "\" {subtype} \" on pikalt alla vajutatud", + "single_press": "\" {subtype} \" on vajutatud" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/fr.json b/homeassistant/components/homekit_controller/translations/fr.json index 1e5671a67afa6d..9634fb784f761a 100644 --- a/homeassistant/components/homekit_controller/translations/fr.json +++ b/homeassistant/components/homekit_controller/translations/fr.json @@ -7,6 +7,7 @@ "already_paired": "Cet accessoire est d\u00e9j\u00e0 associ\u00e9 \u00e0 un autre appareil. R\u00e9initialisez l\u2019accessoire et r\u00e9essayez.", "ignored_model": "La prise en charge de HomeKit pour ce mod\u00e8le est bloqu\u00e9e car une int\u00e9gration native plus compl\u00e8te est disponible.", "invalid_config_entry": "Cet appareil est pr\u00eat \u00e0 \u00eatre coupl\u00e9, mais il existe d\u00e9j\u00e0 une entr\u00e9e de configuration en conflit dans Home Assistant \u00e0 supprimer.", + "invalid_properties": "Propri\u00e9t\u00e9s invalides annonc\u00e9es par l'appareil.", "no_devices": "Aucun appareil non appair\u00e9 n'a pu \u00eatre trouv\u00e9" }, "error": { @@ -15,10 +16,11 @@ "max_peers_error": "L'appareil a refus\u00e9 d'ajouter le couplage car il ne dispose pas de stockage de couplage libre.", "max_tries_error": "Le p\u00e9riph\u00e9rique a refus\u00e9 d'ajouter le couplage car il a re\u00e7u plus de 100 tentatives d'authentification infructueuses.", "pairing_failed": "Une erreur non g\u00e9r\u00e9e s'est produite lors de la tentative d'appairage avec cet appareil. Il se peut qu'il s'agisse d'une panne temporaire ou que votre appareil ne soit pas pris en charge actuellement.", + "protocol_error": "Erreur de communication avec l'accessoire. L'appareil peut ne pas \u00eatre en mode d'appairage et peut n\u00e9cessiter une pression sur un bouton physique ou virtuel.", "unable_to_pair": "Impossible d'appairer, veuillez r\u00e9essayer.", "unknown_error": "L'appareil a signal\u00e9 une erreur inconnue. L'appairage a \u00e9chou\u00e9." }, - "flow_title": "Accessoire HomeKit: {name}", + "flow_title": "{name} via le protocole accessoire HomeKit", "step": { "busy_error": { "description": "Annulez l'association sur tous les contr\u00f4leurs ou essayez de red\u00e9marrer l'appareil, puis continuez \u00e0 reprendre l'association.", @@ -32,21 +34,45 @@ "data": { "pairing_code": "Code d\u2019appairage" }, - "description": "Entrez votre code de jumelage HomeKit (au format XXX-XX-XXX) pour utiliser cet accessoire.", - "title": "Appairer avec l'accessoire HomeKit" + "description": "Le contr\u00f4leur HomeKit communique avec {name} sur le r\u00e9seau local en utilisant une connexion crypt\u00e9e s\u00e9curis\u00e9e sans contr\u00f4leur HomeKit s\u00e9par\u00e9 ou iCloud. Entrez votre code d'appariement HomeKit (au format XXX-XX-XXX) pour utiliser cet accessoire. Ce code se trouve g\u00e9n\u00e9ralement sur l'appareil lui-m\u00eame ou dans l'emballage.", + "title": "Couplage avec un appareil via le protocole accessoire HomeKit" }, "protocol_error": { "description": "L\u2019appareil peut ne pas \u00eatre en mode appairement et peut n\u00e9cessiter une pression sur un bouton physique ou virtuel. Assurez-vous que l\u2019appareil est en mode appariement ou essayez de red\u00e9marrer l\u2019appareil, puis continuez \u00e0 reprendre l\u2019appariement.", "title": "Erreur de communication avec l\u2019accessoire" }, + "try_pair_later": { + "description": "Assurez-vous que l'appareil est en mode de couplage ou essayez de red\u00e9marrer l'appareil, puis continuez \u00e0 red\u00e9marrer le couplage.", + "title": "Couplage indisponible" + }, "user": { "data": { "device": "Appareil" }, - "description": "S\u00e9lectionnez l'appareil avec lequel vous voulez appairer", - "title": "Appairer avec l'accessoire HomeKit" + "description": "Le contr\u00f4leur HomeKit communique sur le r\u00e9seau local \u00e0 l'aide d'une connexion crypt\u00e9e s\u00e9curis\u00e9e sans contr\u00f4leur HomeKit s\u00e9par\u00e9 ou iCloud. S\u00e9lectionnez l'appareil avec lequel vous souhaitez vous associer:", + "title": "S\u00e9lection de l'appareil" } } }, + "device_automation": { + "trigger_subtype": { + "button1": "Bouton 1", + "button10": "Bouton 10", + "button2": "Bouton 2", + "button3": "Bouton 3", + "button4": "Bouton 4", + "button5": "Bouton 5", + "button6": "Bouton 6", + "button7": "Bouton 7", + "button8": "Bouton 8", + "button9": "Bouton 9", + "doorbell": "Sonnette" + }, + "trigger_type": { + "double_press": "\" {subtype} \" appuy\u00e9 deux fois", + "long_press": "\" {subtype} \" enfonc\u00e9 et maintenu", + "single_press": "\" {subtype} \" press\u00e9" + } + }, "title": "Accessoire HomeKit" } \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/it.json b/homeassistant/components/homekit_controller/translations/it.json index f07fd6df8b0008..e3f0a5c820eca5 100644 --- a/homeassistant/components/homekit_controller/translations/it.json +++ b/homeassistant/components/homekit_controller/translations/it.json @@ -3,10 +3,11 @@ "abort": { "accessory_not_found_error": "Impossibile aggiungere l'abbinamento in quanto non \u00e8 pi\u00f9 possibile trovare il dispositivo.", "already_configured": "L'accessorio \u00e8 gi\u00e0 configurato con questo controller.", - "already_in_progress": "Il flusso di configurazione per il dispositivo \u00e8 gi\u00e0 in corso.", + "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "already_paired": "Questo accessorio \u00e8 gi\u00e0 associato a un altro dispositivo. Si prega di resettare l'accessorio e riprovare.", "ignored_model": "Il supporto di HomeKit per questo modello \u00e8 bloccato poich\u00e9 \u00e8 disponibile un'integrazione nativa con pi\u00f9 funzionalit\u00e0.", "invalid_config_entry": "Questo dispositivo viene visualizzato come pronto per l'associazione, ma c'\u00e8 gi\u00e0 una voce di configurazione in conflitto in Home Assistant che prima deve essere rimossa.", + "invalid_properties": "Propriet\u00e0 non valide annunciate dal dispositivo.", "no_devices": "Non \u00e8 stato possibile trovare dispositivi non associati" }, "error": { @@ -19,7 +20,7 @@ "unable_to_pair": "Impossibile abbinare, per favore riprova.", "unknown_error": "Il dispositivo ha riportato un errore sconosciuto. L'abbinamento non \u00e8 riuscito." }, - "flow_title": "Accessorio HomeKit: {name}", + "flow_title": "{name} tramite il Protocollo degli Accessori HomeKit", "step": { "busy_error": { "description": "Interrompere l'associazione su tutti i controller o provare a riavviare il dispositivo, quindi continuare a riprendere l'associazione.", @@ -33,8 +34,8 @@ "data": { "pairing_code": "Codice di abbinamento" }, - "description": "Immettere il codice di abbinamento HomeKit (nel formato XXX-XX-XXX) per utilizzare questo accessorio", - "title": "Abbina con accessorio HomeKit" + "description": "Il controller HomeKit comunica con {name} sulla rete locale utilizzando una connessione crittografata sicura senza un controller HomeKit separato o iCloud. Inserisci il tuo codice di associazione HomeKit (nel formato XXX-XX-XXX) per utilizzare questo accessorio. Questo codice si trova solitamente sul dispositivo stesso o nella confezione.", + "title": "Associazione con un dispositivo tramite il Protocollo degli Accessori HomeKit" }, "protocol_error": { "description": "Il dispositivo potrebbe non essere in modalit\u00e0 di associazione e potrebbe richiedere una pressione di un pulsante fisico o virtuale. Assicurati che il dispositivo sia in modalit\u00e0 di associazione o prova a riavviarlo, quindi continua a riprendere l'associazione.", @@ -48,10 +49,30 @@ "data": { "device": "Dispositivo" }, - "description": "Selezionare il dispositivo che si desidera abbinare", - "title": "Abbina con accessorio HomeKit" + "description": "Il controller HomeKit comunica sulla rete locale utilizzando una connessione crittografata sicura senza un controller HomeKit separato o iCloud. Seleziona il dispositivo che desideri associare:", + "title": "Selezione del dispositivo" } } }, + "device_automation": { + "trigger_subtype": { + "button1": "Pulsante 1", + "button10": "Pulsante 10", + "button2": "Pulsante 2", + "button3": "Pulsante 3", + "button4": "Pulsante 4", + "button5": "Pulsante 5", + "button6": "Pulsante 6", + "button7": "Pulsante 7", + "button8": "Pulsante 8", + "button9": "Pulsante 9", + "doorbell": "Campanello" + }, + "trigger_type": { + "double_press": "\"{subtype}\" premuto due volte", + "long_press": "\"{subtype}\" premuto e tenuto premuto", + "single_press": "\"{subtype}\" premuto" + } + }, "title": "Controller 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 55c5ee0053dc3f..a70a6269bb6bf4 100644 --- a/homeassistant/components/homekit_controller/translations/ko.json +++ b/homeassistant/components/homekit_controller/translations/ko.json @@ -7,6 +7,7 @@ "already_paired": "\uc774 \uc561\uc138\uc11c\ub9ac\ub294 \uc774\ubbf8 \ub2e4\ub978 \uae30\uae30\uc640 \ud398\uc5b4\ub9c1\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4. \uc561\uc138\uc11c\ub9ac\ub97c \uc7ac\uc124\uc815\ud558\uace0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", "ignored_model": "\uc774 \ubaa8\ub378\uc5d0 \ub300\ud55c HomeKit \uc9c0\uc6d0\uc740 \ub354 \ub9ce\uc740 \uae30\ub2a5\uc744 \uc81c\uacf5\ud558\ub294 \uae30\ubcf8 \uad6c\uc131\uc694\uc18c\ub85c \uc778\ud574 \ucc28\ub2e8\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "invalid_config_entry": "\uc774 \uae30\uae30\ub294 \ud398\uc5b4\ub9c1 \ud560 \uc900\ube44\uac00 \ub418\uc5c8\uc9c0\ub9cc Home Assistant \uc5d0 \uc774\ubbf8 \uad6c\uc131\ub418\uc5b4 \ucda9\ub3cc\ud558\ub294 \uad6c\uc131\uc694\uc18c\uac00 \uc788\uc2b5\ub2c8\ub2e4. \uba3c\uc800 \ud574\ub2f9 \uad6c\uc131\uc694\uc18c\ub97c \uc81c\uac70\ud574\uc8fc\uc138\uc694.", + "invalid_properties": "\uc7a5\uce58\uc5d0\uc11c\uc120\uc5b8\ud55c \uc798\ubabb\ub41c \uc18d\uc131\uc785\ub2c8\ub2e4.", "no_devices": "\ud398\uc5b4\ub9c1\uc774 \ud544\uc694\ud55c \uae30\uae30\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4" }, "error": { @@ -48,5 +49,25 @@ } } }, + "device_automation": { + "trigger_subtype": { + "button1": "\ubc84\ud2bc 1", + "button10": "\ubc84\ud2bc 10", + "button2": "\ubc84\ud2bc 2", + "button3": "\ubc84\ud2bc 3", + "button4": "\ubc84\ud2bc 4", + "button5": "\ubc84\ud2bc 5", + "button6": "\ubc84\ud2bc 6", + "button7": "\ubc84\ud2bc 7", + "button8": "\ubc84\ud2bc 8", + "button9": "\ubc84\ud2bc 9", + "doorbell": "\ucd08\uc778\uc885" + }, + "trigger_type": { + "double_press": "\" {subtype} \"\uc744 \ub450\ubc88 \ub204\ub984", + "long_press": "\" {subtype} \"\uc744 \uae38\uac8c \ub204\ub984", + "single_press": "\"{subtype}\" \uc744 \ud55c\ubc88 \ub204\ub984" + } + }, "title": "HomeKit \ucee8\ud2b8\ub864\ub7ec" } \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/lb.json b/homeassistant/components/homekit_controller/translations/lb.json index 5431b3b30d1628..7625d0c561ea41 100644 --- a/homeassistant/components/homekit_controller/translations/lb.json +++ b/homeassistant/components/homekit_controller/translations/lb.json @@ -7,6 +7,7 @@ "already_paired": "D\u00ebsen Accessoire ass schonn mat engem aneren Apparat verbonnen. S\u00ebtzt den Apparat op Wierksastellungen zer\u00e9ck an prob\u00e9iert nach emol w.e.g.", "ignored_model": "HomeKit Support fir d\u00ebse Modell ass block\u00e9iert well eng m\u00e9i komplett nativ Integratioun disponibel ass.", "invalid_config_entry": "D\u00ebsen Apparat mellt sech prett fir ze verbanne mee et g\u00ebtt schonn eng Entr\u00e9e am Home Assistant d\u00e9i ee Konflikt duerstellt welch fir d'\u00e9ischt muss erausgeholl ginn.", + "invalid_properties": "Ong\u00eblteg Eegeschafte vum Apparat annonc\u00e9iert", "no_devices": "Keng net verbonnen Apparater fonnt" }, "error": { @@ -53,5 +54,25 @@ } } }, + "device_automation": { + "trigger_subtype": { + "button1": "Kn\u00e4ppchen 1", + "button10": "Kn\u00e4ppchen 10", + "button2": "Kn\u00e4ppchen 2", + "button3": "Kn\u00e4ppchen 3", + "button4": "Kn\u00e4ppchen 4", + "button5": "Kn\u00e4ppchen 5", + "button6": "Kn\u00e4ppchen 6", + "button7": "Kn\u00e4ppchen 7", + "button8": "Kn\u00e4ppchen 8", + "button9": "Kn\u00e4ppchen 9", + "doorbell": "Schell" + }, + "trigger_type": { + "double_press": "\"{subtype}\" zwee mol gedr\u00e9ckt", + "long_press": "\"{subtype}\" gedr\u00e9ckt an ugehal", + "single_press": "\"{subtype}\" gedr\u00e9ckt" + } + }, "title": "HomeKit Kontroller" } \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/nl.json b/homeassistant/components/homekit_controller/translations/nl.json index 20013168c81503..84f5495cca7f7f 100644 --- a/homeassistant/components/homekit_controller/translations/nl.json +++ b/homeassistant/components/homekit_controller/translations/nl.json @@ -7,6 +7,7 @@ "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.", + "invalid_properties": "Ongeldige eigenschappen aangekondigd door apparaat.", "no_devices": "Er zijn geen gekoppelde apparaten gevonden" }, "error": { @@ -36,5 +37,25 @@ } } }, + "device_automation": { + "trigger_subtype": { + "button1": "Knop 1", + "button10": "Knop 10", + "button2": "Knop 2", + "button3": "Knop 3", + "button4": "Knop 4", + "button5": "Knop 5", + "button6": "Knop 6", + "button7": "Knop 7", + "button8": "Knop 8", + "button9": "Knop 9", + "doorbell": "Deurbel" + }, + "trigger_type": { + "double_press": "\" {subtype} \" tweemaal ingedrukt", + "long_press": "\"{subtype}\" ingedrukt en vastgehouden", + "single_press": "\" {subtype} \" ingedrukt" + } + }, "title": "HomeKit Accessoires" } \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/no.json b/homeassistant/components/homekit_controller/translations/no.json index 45444d3d9d053d..5b37ba68e24c11 100644 --- a/homeassistant/components/homekit_controller/translations/no.json +++ b/homeassistant/components/homekit_controller/translations/no.json @@ -3,10 +3,11 @@ "abort": { "accessory_not_found_error": "Kan ikke legge til sammenkobling da enheten ikke lenger kan bli funnet.", "already_configured": "Tilbeh\u00f8r er allerede konfigurert med denne kontrolleren.", - "already_in_progress": "Konfigurasjonsflyt for enhet p\u00e5g\u00e5r allerede.", + "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", "already_paired": "Dette tilbeh\u00f8ret er allerede sammenkoblet med en annen enhet. Vennligst tilbakestill tilbeh\u00f8ret og pr\u00f8v igjen.", "ignored_model": "HomeKit st\u00f8tte for denne modellen er blokkert da en mer funksjonsrik standard integrasjon er tilgjengelig.", "invalid_config_entry": "Denne enheten vises som klar til sammenkobling, men det er allerede en motstridende konfigurasjonsoppf\u00f8ring for den i Hjelpeassistenten som f\u00f8rst m\u00e5 fjernes.", + "invalid_properties": "Ugyldige egenskaper kunngjort av enheten.", "no_devices": "Ingen ukoblede enheter ble funnet" }, "error": { @@ -19,7 +20,7 @@ "unable_to_pair": "Kunne ikke koble til, vennligst pr\u00f8v igjen.", "unknown_error": "Enheten rapporterte en ukjent feil. Sammenkobling mislyktes." }, - "flow_title": "HomeKit Tilbeh\u00f8r: {name}", + "flow_title": "{name} via HomeKit Accessory Protocol", "step": { "busy_error": { "description": "Avbryt sammenkobling p\u00e5 alle kontrollere, eller pr\u00f8v \u00e5 starte enheten p\u00e5 nytt, og fortsett deretter med \u00e5 fortsette sammenkoblingen.", @@ -33,8 +34,8 @@ "data": { "pairing_code": "Sammenkoblingskode" }, - "description": "Angi din HomeKit-sammenkoblingskode (i formatet XXX-XX-XXX) for \u00e5 bruke dette tilbeh\u00f8ret", - "title": "Koble til HomeKit tilbeh\u00f8r" + "description": "HomeKit Controller kommuniserer med {name} over lokalnettverket ved hjelp av en sikker kryptert tilkobling uten en separat HomeKit-kontroller eller iCloud. Skriv inn HomeKit-paringskoden (i formatet XXX-XX-XXX) for \u00e5 bruke dette tilbeh\u00f8ret. Denne koden finnes vanligvis p\u00e5 selve enheten eller i emballasjen.", + "title": "Par med en enhet via HomeKit Accessory Protocol" }, "protocol_error": { "description": "Enheten er kanskje ikke i paringsmodus og kan kreve et fysisk eller virtuelt knappetrykk. Kontroller at enheten er i paringsmodus eller pr\u00f8v \u00e5 starte enheten p\u00e5 nytt, og fortsett deretter \u00e5 fortsette paringen.", @@ -48,10 +49,30 @@ "data": { "device": "Enhet" }, - "description": "Velg enheten du vil koble til", - "title": "Koble til HomeKit tilbeh\u00f8r" + "description": "HomeKit Controller kommuniserer over lokalnettverket ved hjelp av en sikker kryptert tilkobling uten en separat HomeKit-kontroller eller iCloud. Velg enheten du vil pare med:", + "title": "Valg av enhet" } } }, + "device_automation": { + "trigger_subtype": { + "button1": "Knapp 1", + "button10": "Knapp 10", + "button2": "Knapp 2", + "button3": "Knapp 3", + "button4": "Knapp 4", + "button5": "Knapp 5", + "button6": "Knapp 6", + "button7": "Knapp 7", + "button8": "Knapp 8", + "button9": "Knapp 9", + "doorbell": "D\u00f8r-klokke" + }, + "trigger_type": { + "double_press": "{subtype} trykket to ganger", + "long_press": "{subtype} trykket og holdt", + "single_press": "{subtype}\u00bb trykket" + } + }, "title": "HomeKit-kontroller" } \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/pl.json b/homeassistant/components/homekit_controller/translations/pl.json index bed897ea2424d7..a819cea588145b 100644 --- a/homeassistant/components/homekit_controller/translations/pl.json +++ b/homeassistant/components/homekit_controller/translations/pl.json @@ -7,6 +7,7 @@ "already_paired": "To akcesorium jest ju\u017c sparowane z innym urz\u0105dzeniem. Zresetuj akcesorium i spr\u00f3buj ponownie.", "ignored_model": "Obs\u0142uga HomeKit dla tego modelu jest zablokowana, poniewa\u017c dost\u0119pna jest pe\u0142niejsza integracja natywna.", "invalid_config_entry": "To urz\u0105dzenie jest wy\u015bwietlane jako gotowe do sparowania, ale istnieje ju\u017c konfliktowy wpis konfiguracyjny dla niego w Home Assistant, kt\u00f3ry musi zosta\u0107 najpierw usuni\u0119ty.", + "invalid_properties": "Urz\u0105dzenie zg\u0142osi\u0142o nieprawid\u0142owe w\u0142a\u015bciwo\u015bci.", "no_devices": "Nie znaleziono niesparowanych urz\u0105dze\u0144." }, "error": { @@ -15,11 +16,20 @@ "max_peers_error": "Urz\u0105dzenie odm\u00f3wi\u0142o parowania, poniewa\u017c nie ma wolnej pami\u0119ci parowania.", "max_tries_error": "Urz\u0105dzenie odm\u00f3wi\u0142o dodania parowania, poniewa\u017c otrzyma\u0142o ponad 100 nieudanych pr\u00f3b uwierzytelnienia.", "pairing_failed": "Wyst\u0105pi\u0142 nieobs\u0142ugiwany b\u0142\u0105d podczas pr\u00f3by sparowania z tym urz\u0105dzeniem. Mo\u017ce to by\u0107 tymczasowa awaria lub urz\u0105dzenie mo\u017ce nie by\u0107 obecnie obs\u0142ugiwane.", + "protocol_error": "B\u0142\u0105d w komunikacji z urz\u0105dzeniem. Urz\u0105dzenie mo\u017ce nie by\u0107 w trybie parowania i mo\u017ce wymaga\u0107 fizycznego lub wirtualnego naci\u015bni\u0119cia przycisku.", "unable_to_pair": "Nie mo\u017cna sparowa\u0107, spr\u00f3buj ponownie.", "unknown_error": "Urz\u0105dzenie zg\u0142osi\u0142o nieznany b\u0142\u0105d. Parowanie nie powiod\u0142o si\u0119." }, "flow_title": "Akcesoria HomeKit: {name}", "step": { + "busy_error": { + "description": "Przerwij parowanie we wszystkich kontrolerach lub spr\u00f3buj ponownie uruchomi\u0107 urz\u0105dzenie, a nast\u0119pnie wzn\u00f3w parowanie.", + "title": "Urz\u0105dzenie jest ju\u017c sparowane z innym kontrolerem" + }, + "max_tries_error": { + "description": "Urz\u0105dzenie otrzyma\u0142o ponad 100 nieudanych pr\u00f3b uwierzytelnienia. Spr\u00f3buj ponownie uruchomi\u0107 urz\u0105dzenie, a nast\u0119pnie wzn\u00f3w parowanie.", + "title": "Przekroczono maksymaln\u0105 liczb\u0119 pr\u00f3b uwierzytelnienia" + }, "pair": { "data": { "pairing_code": "Kod parowania" @@ -28,7 +38,12 @@ "title": "Sparuj z akcesorium HomeKit" }, "protocol_error": { - "description": "Urz\u0105dzenie mo\u017ce nie by\u0107 w trybie parowania i wymaga\u0107 " + "description": "Urz\u0105dzenie mo\u017ce nie by\u0107 w trybie parowania i mo\u017ce wymaga\u0107 fizycznego lub wirtualnego naci\u015bni\u0119cia przycisku. Upewnij si\u0119, \u017ce urz\u0105dzenie jest w trybie parowania lub spr\u00f3buj je ponownie uruchomi\u0107, a nast\u0119pnie wzn\u00f3w parowanie.", + "title": "B\u0142\u0105d komunikacji z akcesorium" + }, + "try_pair_later": { + "description": "Upewnij si\u0119, \u017ce urz\u0105dzenie jest w trybie parowania lub spr\u00f3buj ponownie uruchomi\u0107 urz\u0105dzenie, a nast\u0119pnie spr\u00f3buj ponownego parowania.", + "title": "Parowanie niedost\u0119pne" }, "user": { "data": { @@ -39,5 +54,25 @@ } } }, + "device_automation": { + "trigger_subtype": { + "button1": "Przycisk 1", + "button10": "Przycisk 10", + "button2": "Przycisk 2", + "button3": "Przycisk 3", + "button4": "Przycisk 4", + "button5": "Przycisk 5", + "button6": "Przycisk 6", + "button7": "Przycisk 7", + "button8": "Przycisk 8", + "button9": "Przycisk 9", + "doorbell": "Dzwonek do drzwi" + }, + "trigger_type": { + "double_press": "\"{subtype}\" naci\u015bni\u0119ty dwukrotnie", + "long_press": "\"{subtype}\" naci\u015bni\u0119ty i przytrzymany", + "single_press": "\"{subtype}\" naci\u015bni\u0119ty" + } + }, "title": "Akcesorium HomeKit" } \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/ru.json b/homeassistant/components/homekit_controller/translations/ru.json index fb9612ef98130b..791dfc36cb43b6 100644 --- a/homeassistant/components/homekit_controller/translations/ru.json +++ b/homeassistant/components/homekit_controller/translations/ru.json @@ -3,10 +3,11 @@ "abort": { "accessory_not_found_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435, \u0442\u0430\u043a \u043a\u0430\u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0431\u043e\u043b\u044c\u0448\u0435 \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043d\u0430\u0439\u0434\u0435\u043d\u043e.", "already_configured": "\u0410\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440 \u0443\u0436\u0435 \u0441\u0432\u044f\u0437\u0430\u043d \u0441 \u044d\u0442\u0438\u043c \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u043e\u043c.", - "already_in_progress": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.", + "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.", "already_paired": "\u042d\u0442\u043e\u0442 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440 \u0443\u0436\u0435 \u0441\u0432\u044f\u0437\u0430\u043d \u0441 \u0434\u0440\u0443\u0433\u0438\u043c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u043c. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u0435 \u0441\u0431\u0440\u043e\u0441 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u0430 \u0438 \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0441\u043d\u043e\u0432\u0430.", "ignored_model": "\u041f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 HomeKit \u0434\u043b\u044f \u044d\u0442\u043e\u0439 \u043c\u043e\u0434\u0435\u043b\u0438 \u0437\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u0430, \u0442\u0430\u043a \u043a\u0430\u043a \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430 \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u043b\u043d\u0430\u044f \u043d\u0430\u0442\u0438\u0432\u043d\u0430\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f.", "invalid_config_entry": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u0433\u043e\u0442\u043e\u0432\u043e\u0435 \u043a \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u044e, \u043d\u043e \u0432 Home Assistant \u0443\u0436\u0435 \u0435\u0441\u0442\u044c \u043a\u043e\u043d\u0444\u043b\u0438\u043a\u0442\u0443\u044e\u0449\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0434\u043b\u044f \u043d\u0435\u0433\u043e, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0443\u0434\u0430\u043b\u0438\u0442\u044c.", + "invalid_properties": "\u041d\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u0430, \u043e\u0431\u044a\u044f\u0432\u043b\u0435\u043d\u043d\u044b\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u043c.", "no_devices": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430, \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0435 \u0434\u043b\u044f \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u044f, \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b." }, "error": { @@ -19,7 +20,7 @@ "unable_to_pair": "\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", "unknown_error": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0441\u043e\u043e\u0431\u0449\u0438\u043b\u043e \u043e \u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u043e\u0439 \u043e\u0448\u0438\u0431\u043a\u0435. \u0421\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435 \u043d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c." }, - "flow_title": "\u0410\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440 HomeKit: {name}", + "flow_title": "{name} \u0447\u0435\u0440\u0435\u0437 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u043e\u0432 HomeKit", "step": { "busy_error": { "description": "\u041e\u0442\u043c\u0435\u043d\u0438\u0442\u0435 \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435 \u043d\u0430 \u0432\u0441\u0435\u0445 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0430\u0445 \u0438\u043b\u0438 \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e, \u0437\u0430\u0442\u0435\u043c \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u0435 \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435.", @@ -33,8 +34,8 @@ "data": { "pairing_code": "\u041a\u043e\u0434 \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u044f" }, - "description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043a\u043e\u0434 \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u044f HomeKit (\u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 XXX-XX-XXX), \u0447\u0442\u043e\u0431\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u044d\u0442\u043e\u0442 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440.", - "title": "\u0421\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435 \u0441 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u043e\u043c HomeKit" + "description": "HomeKit Controller \u043e\u0431\u043c\u0435\u043d\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u0441 {name} \u043f\u043e \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0439 \u0441\u0435\u0442\u0438, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0435 \u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0431\u0435\u0437 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0430 HomeKit \u0438\u043b\u0438 iCloud. \u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u043a\u043e\u0434 \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u044f HomeKit (\u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 XXX-XX-XXX), \u0447\u0442\u043e\u0431\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u044d\u0442\u043e\u0442 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440. \u042d\u0442\u043e\u0442 \u043a\u043e\u0434 \u043e\u0431\u044b\u0447\u043d\u043e \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u043d\u0430 \u0441\u0430\u043c\u043e\u043c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435 \u0438\u043b\u0438 \u043d\u0430 \u0443\u043f\u0430\u043a\u043e\u0432\u043a\u0435.", + "title": "\u0421\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435 \u0441 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u043c \u0447\u0435\u0440\u0435\u0437 \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u043e\u0432 HomeKit" }, "protocol_error": { "description": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e, \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e, \u043d\u0435 \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u044f \u0438 \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u0442\u0440\u0435\u0431\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043d\u0430\u0436\u0430\u0442\u0438\u0435 \u0444\u0438\u0437\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u0438\u043b\u0438 \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0439 \u043a\u043d\u043e\u043f\u043a\u0438. \u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0430\u0445\u043e\u0434\u0438\u0442\u0441\u044f \u0432 \u0440\u0435\u0436\u0438\u043c\u0435 \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u044f, \u0438\u043b\u0438 \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u043f\u0435\u0440\u0435\u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e, \u0438 \u0437\u0430\u0442\u0435\u043c \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0438\u0442\u0435 \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435.", @@ -48,10 +49,30 @@ "data": { "device": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e" }, - "description": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e, \u0441 \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u043d\u0443\u0436\u043d\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435.", - "title": "\u0421\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435 \u0441 \u0430\u043a\u0441\u0435\u0441\u0441\u0443\u0430\u0440\u043e\u043c HomeKit" + "description": "HomeKit Controller \u043e\u0431\u043c\u0435\u043d\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0434\u0430\u043d\u043d\u044b\u043c\u0438 \u043f\u043e \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0439 \u0441\u0435\u0442\u0438 \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0433\u043e \u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0433\u043e \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f \u0431\u0435\u0437 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u0430 HomeKit \u0438\u043b\u0438 iCloud. \u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e, \u0441 \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u0445\u043e\u0442\u0438\u0442\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0441\u043e\u043f\u0440\u044f\u0436\u0435\u043d\u0438\u0435:", + "title": "\u0412\u044b\u0431\u043e\u0440 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430" } } }, + "device_automation": { + "trigger_subtype": { + "button1": "\u041a\u043d\u043e\u043f\u043a\u0430 1", + "button10": "\u041a\u043d\u043e\u043f\u043a\u0430 10", + "button2": "\u041a\u043d\u043e\u043f\u043a\u0430 2", + "button3": "\u041a\u043d\u043e\u043f\u043a\u0430 3", + "button4": "\u041a\u043d\u043e\u043f\u043a\u0430 4", + "button5": "\u041a\u043d\u043e\u043f\u043a\u0430 5", + "button6": "\u041a\u043d\u043e\u043f\u043a\u0430 6", + "button7": "\u041a\u043d\u043e\u043f\u043a\u0430 7", + "button8": "\u041a\u043d\u043e\u043f\u043a\u0430 8", + "button9": "\u041a\u043d\u043e\u043f\u043a\u0430 9", + "doorbell": "\u0414\u0432\u0435\u0440\u043d\u043e\u0439 \u0437\u0432\u043e\u043d\u043e\u043a" + }, + "trigger_type": { + "double_press": "\"{subtype}\" \u043d\u0430\u0436\u0430\u0442\u0430 \u0434\u0432\u0430\u0436\u0434\u044b", + "long_press": "\"{subtype}\" \u043d\u0430\u0436\u0430\u0442\u0430 \u0438 \u0443\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f", + "single_press": "\"{subtype}\" \u043d\u0430\u0436\u0430\u0442\u0430" + } + }, "title": "HomeKit Controller" } \ 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 0c57e09b09b709..e57d61dcdb6e2b 100644 --- a/homeassistant/components/homekit_controller/translations/sv.json +++ b/homeassistant/components/homekit_controller/translations/sv.json @@ -36,5 +36,20 @@ } } }, + "device_automation": { + "trigger_subtype": { + "button1": "Knapp 1", + "button10": "Knapp 10", + "button2": "Knapp 2", + "button3": "Knapp 3", + "button4": "Knapp 4", + "button5": "Knapp 5", + "button6": "Knapp 6", + "button7": "Knapp 7", + "button8": "Knapp 8", + "button9": "Knapp 9", + "doorbell": "D\u00f6rrklocka" + } + }, "title": "HomeKit-tillbeh\u00f6r" } \ No newline at end of file diff --git a/homeassistant/components/homekit_controller/translations/zh-Hant.json b/homeassistant/components/homekit_controller/translations/zh-Hant.json index 51f521d5c358f7..ded672bd83846e 100644 --- a/homeassistant/components/homekit_controller/translations/zh-Hant.json +++ b/homeassistant/components/homekit_controller/translations/zh-Hant.json @@ -3,10 +3,11 @@ "abort": { "accessory_not_found_error": "\u627e\u4e0d\u5230\u8a2d\u5099\uff0c\u7121\u6cd5\u65b0\u589e\u914d\u5c0d\u3002", "already_configured": "\u914d\u4ef6\u5df2\u7d93\u7531\u6b64\u63a7\u5236\u5668\u8a2d\u5b9a\u5b8c\u6210", - "already_in_progress": "\u8a2d\u5099\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d\u3002", + "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "already_paired": "\u914d\u4ef6\u5df2\u7d93\u8207\u5176\u4ed6\u8a2d\u5099\u914d\u5c0d\uff0c\u8acb\u91cd\u7f6e\u914d\u4ef6\u5f8c\u518d\u8a66\u4e00\u6b21\u3002", "ignored_model": "\u7531\u65bc\u6b64\u578b\u865f\u53ef\u539f\u751f\u652f\u63f4\u66f4\u5b8c\u6574\u529f\u80fd\uff0c\u56e0\u6b64 Homekit \u652f\u63f4\u5df2\u88ab\u7981\u6b62\u3002", "invalid_config_entry": "\u6b64\u8a2d\u5099\u986f\u793a\u7b49\u5f85\u9032\u884c\u914d\u5c0d\uff0c\u4f46 Home Assistant \u986f\u793a\u6709\u76f8\u885d\u7a81\u8a2d\u5b9a\u5be6\u9ad4\u5fc5\u9808\u5148\u884c\u79fb\u9664\u3002", + "invalid_properties": "\u8a2d\u5099\u5ba3\u544a\u5c6c\u6027\u7121\u6548\u3002", "no_devices": "\u627e\u4e0d\u5230\u4efb\u4f55\u672a\u914d\u5c0d\u8a2d\u5099" }, "error": { @@ -19,7 +20,7 @@ "unable_to_pair": "\u7121\u6cd5\u914d\u5c0d\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002", "unknown_error": "\u8a2d\u5099\u56de\u5831\u672a\u77e5\u932f\u8aa4\u3002\u914d\u5c0d\u5931\u6557\u3002" }, - "flow_title": "HomeKit \u914d\u4ef6\uff1a{name}", + "flow_title": "{name} \u4f7f\u7528 HomeKit \u914d\u4ef6\u901a\u8a0a\u5354\u5b9a", "step": { "busy_error": { "description": "\u53d6\u6d88\u6240\u6709\u63a7\u5236\u5668\u914d\u5c0d\uff0c\u6216\u8005\u91cd\u555f\u8a2d\u5099\u3001\u7136\u5f8c\u518d\u7e7c\u7e8c\u914d\u5c0d\u3002", @@ -33,8 +34,8 @@ "data": { "pairing_code": "\u8a2d\u5b9a\u4ee3\u78bc" }, - "description": "\u8f38\u5165\u914d\u4ef6 Homekit \u8a2d\u5b9a\u4ee3\u78bc\uff08\u683c\u5f0f\uff1aXXX-XX-XXX\uff09\u4ee5\u4f7f\u7528\u6b64\u914d\u4ef6", - "title": "HomeKit \u914d\u4ef6\u914d\u5c0d" + "description": "\u4f7f\u7528 {name} \u4e4b HomeKit \u63a7\u5236\u5668\u901a\u8a0a\u4f7f\u7528\u52a0\u5bc6\u9023\u7dda\uff0c\u4e26\u4e0d\u9700\u8981\u984d\u5916\u7684 HomeKit \u63a7\u5236\u5668\u6216 iCloud \u9023\u7dda\u3002\u8f38\u5165\u914d\u4ef6 Homekit \u8a2d\u5b9a\u914d\u5c0d\u4ee3\u78bc\uff08\u683c\u5f0f\uff1aXXX-XX-XXX\uff09\u4ee5\u4f7f\u7528\u6b64\u914d\u4ef6\u3002\u4ee3\u78bc\u901a\u5e38\u53ef\u4ee5\u65bc\u8a2d\u5099\u6216\u8005\u5305\u88dd\u4e0a\u627e\u5230\u3002", + "title": "\u900f\u904e HomeKit \u914d\u4ef6\u901a\u8a0a\u5354\u5b9a\u6240\u914d\u5c0d\u8a2d\u5099" }, "protocol_error": { "description": "\u8a2d\u5099\u4e26\u672a\u8655\u65bc\u914d\u5c0d\u6a21\u5f0f\uff0c\u53ef\u80fd\u9700\u8981\u6309\u4e0b\u5be6\u9ad4\u6216\u865b\u64ec\u6309\u9215\u3002\u8acb\u78ba\u5b9a\u8a2d\u5099\u5df2\u7d93\u8655\u65bc\u914d\u5c0d\u6a21\u5f0f\u3001\u6216\u91cd\u555f\u8a2d\u5099\uff0c\u7136\u5f8c\u518d\u7e7c\u7e8c\u914d\u5c0d\u3002", @@ -48,10 +49,30 @@ "data": { "device": "\u8a2d\u5099" }, - "description": "\u9078\u64c7\u6240\u8981\u65b0\u589e\u7684\u8a2d\u5099", - "title": "HomeKit \u914d\u4ef6\u914d\u5c0d" + "description": "\u4f7f\u7528\u5340\u57df\u7db2\u8def\u4e4b HomeKit \u63a7\u5236\u5668\u901a\u8a0a\u4f7f\u7528\u52a0\u5bc6\u9023\u7dda\uff0c\u4e26\u4e0d\u9700\u8981\u984d\u5916\u7684 HomeKit \u63a7\u5236\u5668\u6216 iCloud \u9023\u7dda\u3002\u9078\u64c7\u6240\u8981\u65b0\u589e\u914d\u5c0d\u7684\u8a2d\u5099\uff1a", + "title": "\u8a2d\u5099\u9078\u64c7" } } }, + "device_automation": { + "trigger_subtype": { + "button1": "\u6309\u9215 1", + "button10": "\u6309\u9215 10", + "button2": "\u6309\u9215 2", + "button3": "\u6309\u9215 3", + "button4": "\u6309\u9215 4", + "button5": "\u6309\u9215 5", + "button6": "\u6309\u9215 6", + "button7": "\u6309\u9215 7", + "button8": "\u6309\u9215 8", + "button9": "\u6309\u9215 9", + "doorbell": "\u9580\u9234" + }, + "trigger_type": { + "double_press": "\"{subtype}\" \u6309\u4e0b\u5169\u6b21", + "long_press": "\"{subtype}\" \u6309\u4e0b\u4e26\u6309\u4f4f", + "single_press": "\"{subtype}\" \u6309\u4e0b" + } + }, "title": "HomeKit \u63a7\u5236\u5668" } \ No newline at end of file diff --git a/homeassistant/components/homematic/const.py b/homeassistant/components/homematic/const.py index 1ce18a8e7593b0..22ac7972c4da01 100644 --- a/homeassistant/components/homematic/const.py +++ b/homeassistant/components/homematic/const.py @@ -57,6 +57,8 @@ "IPKeySwitchLevel", "IPMultiIO", "IPWSwitch", + "IOSwitchWireless", + "IPWIODevice", ], DISCOVER_LIGHTS: [ "Dimmer", @@ -111,7 +113,7 @@ "IPThermostatWall2", "IPRemoteMotionV2", "HBUNISenWEA", - "IPWMotionDection", + "PresenceIPW", ], DISCOVER_CLIMATE: [ "Thermostat", @@ -154,6 +156,7 @@ "IPRemoteMotionV2", "IPWInputDevice", "IPWMotionDection", + "IPAlarmSensor", ], DISCOVER_COVER: [ "Blind", diff --git a/homeassistant/components/homematic/entity.py b/homeassistant/components/homematic/entity.py index 49d3ee1f1704d3..8578be93555194 100644 --- a/homeassistant/components/homematic/entity.py +++ b/homeassistant/components/homematic/entity.py @@ -233,8 +233,7 @@ def state(self): @property def state_attributes(self): """Return the state attributes.""" - attr = self._variables.copy() - return attr + return self._variables.copy() @property def icon(self): diff --git a/homeassistant/components/homematic/manifest.json b/homeassistant/components/homematic/manifest.json index d8c60c0f9765f4..63e33a60c536ea 100644 --- a/homeassistant/components/homematic/manifest.json +++ b/homeassistant/components/homematic/manifest.json @@ -2,6 +2,6 @@ "domain": "homematic", "name": "Homematic", "documentation": "https://www.home-assistant.io/integrations/homematic", - "requirements": ["pyhomematic==0.1.68"], + "requirements": ["pyhomematic==0.1.70"], "codeowners": ["@pvizeli", "@danielperna84"] } diff --git a/homeassistant/components/homematic/sensor.py b/homeassistant/components/homematic/sensor.py index 51a88bd120778e..e6439c451c1670 100644 --- a/homeassistant/components/homematic/sensor.py +++ b/homeassistant/components/homematic/sensor.py @@ -9,8 +9,11 @@ DEVICE_CLASS_TEMPERATURE, ENERGY_WATT_HOUR, FREQUENCY_HERTZ, + LENGTH_MILLIMETERS, + LIGHT_LUX, PERCENTAGE, POWER_WATT, + PRESSURE_HPA, SPEED_KILOMETERS_PER_HOUR, TEMP_CELSIUS, VOLT, @@ -48,18 +51,18 @@ "ENERGY_COUNTER": ENERGY_WATT_HOUR, "GAS_POWER": VOLUME_CUBIC_METERS, "GAS_ENERGY_COUNTER": VOLUME_CUBIC_METERS, - "LUX": "lx", - "ILLUMINATION": "lx", - "CURRENT_ILLUMINATION": "lx", - "AVERAGE_ILLUMINATION": "lx", - "LOWEST_ILLUMINATION": "lx", - "HIGHEST_ILLUMINATION": "lx", - "RAIN_COUNTER": "mm", + "LUX": LIGHT_LUX, + "ILLUMINATION": LIGHT_LUX, + "CURRENT_ILLUMINATION": LIGHT_LUX, + "AVERAGE_ILLUMINATION": LIGHT_LUX, + "LOWEST_ILLUMINATION": LIGHT_LUX, + "HIGHEST_ILLUMINATION": LIGHT_LUX, + "RAIN_COUNTER": LENGTH_MILLIMETERS, "WIND_SPEED": SPEED_KILOMETERS_PER_HOUR, "WIND_DIRECTION": DEGREE, "WIND_DIRECTION_RANGE": DEGREE, "SUNSHINEDURATION": "#", - "AIR_PRESSURE": "hPa", + "AIR_PRESSURE": PRESSURE_HPA, "FREQUENCY": FREQUENCY_HERTZ, "VALUE": "#", "VALVE_STATE": PERCENTAGE, diff --git a/homeassistant/components/homematicip_cloud/__init__.py b/homeassistant/components/homematicip_cloud/__init__.py index 47da33e86dab8c..e53307c533bf9d 100644 --- a/homeassistant/components/homematicip_cloud/__init__.py +++ b/homeassistant/components/homematicip_cloud/__init__.py @@ -99,7 +99,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool # Add the HAP name from configuration if set. hapname = home.label if not home.name else f"{home.name} {home.label}" device_registry.async_get_or_create( - config_entry_id=home.id, + config_entry_id=entry.entry_id, identifiers={(DOMAIN, home.id)}, manufacturer="eQ-3", name=hapname, diff --git a/homeassistant/components/homematicip_cloud/binary_sensor.py b/homeassistant/components/homematicip_cloud/binary_sensor.py index 50e8360675b2fc..3c04fd7410a7c4 100644 --- a/homeassistant/components/homematicip_cloud/binary_sensor.py +++ b/homeassistant/components/homematicip_cloud/binary_sensor.py @@ -82,7 +82,7 @@ async def async_setup_entry( ) -> None: """Set up the HomematicIP Cloud binary sensor from a config entry.""" hap = hass.data[HMIPC_DOMAIN][config_entry.unique_id] - entities = [] + entities = [HomematicipCloudConnectionSensor(hap)] for device in hap.home.devices: if isinstance(device, AsyncAccelerationSensor): entities.append(HomematicipAccelerationSensor(hap, device)) @@ -128,14 +128,52 @@ async def async_setup_entry( for group in hap.home.groups: if isinstance(group, AsyncSecurityGroup): - entities.append(HomematicipSecuritySensorGroup(hap, group)) + entities.append(HomematicipSecuritySensorGroup(hap, device=group)) elif isinstance(group, AsyncSecurityZoneGroup): - entities.append(HomematicipSecurityZoneSensorGroup(hap, group)) + entities.append(HomematicipSecurityZoneSensorGroup(hap, device=group)) if entities: async_add_entities(entities) +class HomematicipCloudConnectionSensor(HomematicipGenericEntity, BinarySensorEntity): + """Representation of the HomematicIP cloud connection sensor.""" + + def __init__(self, hap: HomematicipHAP) -> None: + """Initialize the cloud connection sensor.""" + super().__init__(hap, hap.home, "Cloud Connection") + + @property + def device_info(self) -> Dict[str, Any]: + """Return device specific attributes.""" + # Adds a sensor to the existing HAP device + return { + "identifiers": { + # Serial numbers of Homematic IP device + (HMIPC_DOMAIN, self._home.id) + } + } + + @property + def icon(self) -> str: + """Return the icon of the access point entity.""" + return ( + "mdi:access-point-network" + if self._home.connected + else "mdi:access-point-network-off" + ) + + @property + def is_on(self) -> bool: + """Return true if hap is connected to cloud.""" + return self._home.connected + + @property + def available(self) -> bool: + """Sensor is always available.""" + return True + + class HomematicipBaseActionSensor(HomematicipGenericEntity, BinarySensorEntity): """Representation of the HomematicIP base action sensor.""" @@ -323,7 +361,7 @@ class HomematicipSunshineSensor(HomematicipGenericEntity, BinarySensorEntity): def __init__(self, hap: HomematicipHAP, device) -> None: """Initialize sunshine sensor.""" - super().__init__(hap, device, "Sunshine") + super().__init__(hap, device, post="Sunshine") @property def device_class(self) -> str: @@ -352,7 +390,7 @@ class HomematicipBatterySensor(HomematicipGenericEntity, BinarySensorEntity): def __init__(self, hap: HomematicipHAP, device) -> None: """Initialize battery sensor.""" - super().__init__(hap, device, "Battery") + super().__init__(hap, device, post="Battery") @property def device_class(self) -> str: @@ -391,7 +429,7 @@ class HomematicipSecurityZoneSensorGroup(HomematicipGenericEntity, BinarySensorE def __init__(self, hap: HomematicipHAP, device, post: str = "SecurityZone") -> None: """Initialize security zone group.""" device.modelType = f"HmIP-{post}" - super().__init__(hap, device, post) + super().__init__(hap, device, post=post) @property def device_class(self) -> str: @@ -447,7 +485,7 @@ class HomematicipSecuritySensorGroup( def __init__(self, hap: HomematicipHAP, device) -> None: """Initialize security group.""" - super().__init__(hap, device, "Sensors") + super().__init__(hap, device, post="Sensors") @property def device_state_attributes(self) -> Dict[str, Any]: diff --git a/homeassistant/components/homematicip_cloud/generic_entity.py b/homeassistant/components/homematicip_cloud/generic_entity.py index 3a19c1b2afe921..ce8b44f5702c09 100644 --- a/homeassistant/components/homematicip_cloud/generic_entity.py +++ b/homeassistant/components/homematicip_cloud/generic_entity.py @@ -70,12 +70,19 @@ class HomematicipGenericEntity(Entity): """Representation of the HomematicIP generic entity.""" - def __init__(self, hap: HomematicipHAP, device, post: Optional[str] = None) -> None: + def __init__( + self, + hap: HomematicipHAP, + device, + post: Optional[str] = None, + channel: Optional[int] = None, + ) -> None: """Initialize the generic entity.""" self._hap = hap self._home = hap.home self._device = device - self.post = post + self._post = post + self._channel = channel # Marker showing that the HmIP device hase been removed. self.hmip_device_removed = False _LOGGER.info("Setting up %s (%s)", self.name, self._device.modelType) @@ -94,6 +101,7 @@ def device_info(self) -> Dict[str, Any]: "manufacturer": self._device.oem, "model": self._device.modelType, "sw_version": self._device.firmwareVersion, + # Link to the homematic ip access point. "via_device": (HMIPC_DOMAIN, self._device.homeId), } return None @@ -167,18 +175,28 @@ def _async_device_removed(self, *args, **kwargs) -> None: @property def name(self) -> str: """Return the name of the generic entity.""" - name = self._device.label - if name and self._home.name: - name = f"{self._home.name} {name}" - if name and self.post: - name = f"{name} {self.post}" - return name - def _get_label_by_channel(self, channel: int) -> str: - """Return the name of the channel.""" - name = self._device.functionalChannels[channel].label + name = None + # Try to get a label from a channel. + if hasattr(self._device, "functionalChannels"): + if self._channel: + name = self._device.functionalChannels[self._channel].label + else: + if len(self._device.functionalChannels) > 1: + name = self._device.functionalChannels[1].label + + # Use device label, if name is not defined by channel label. + if not name: + name = self._device.label + if self._post: + name = f"{name} {self._post}" + elif self._channel: + name = f"{name} Channel{self._channel}" + + # Add a prefix to the name if the homematic ip home has a name. if name and self._home.name: name = f"{self._home.name} {name}" + return name @property @@ -194,7 +212,13 @@ def available(self) -> bool: @property def unique_id(self) -> str: """Return a unique ID.""" - return f"{self.__class__.__name__}_{self._device.id}" + unique_id = f"{self.__class__.__name__}_{self._device.id}" + if self._channel: + unique_id = ( + f"{self.__class__.__name__}_Channel{self._channel}_{self._device.id}" + ) + + return unique_id @property def icon(self) -> Optional[str]: diff --git a/homeassistant/components/homematicip_cloud/light.py b/homeassistant/components/homematicip_cloud/light.py index 727371223727b9..e4d85c00b19667 100644 --- a/homeassistant/components/homematicip_cloud/light.py +++ b/homeassistant/components/homematicip_cloud/light.py @@ -71,14 +71,6 @@ def __init__(self, hap: HomematicipHAP, device) -> None: """Initialize the light entity.""" super().__init__(hap, device) - @property - def name(self) -> str: - """Return the name of the multi switch channel.""" - label = self._get_label_by_channel(1) - if label: - return label - return super().name - @property def is_on(self) -> bool: """Return true if light is on.""" @@ -149,11 +141,10 @@ class HomematicipNotificationLight(HomematicipGenericEntity, LightEntity): def __init__(self, hap: HomematicipHAP, device, channel: int) -> None: """Initialize the notification light entity.""" - self.channel = channel - if self.channel == 2: - super().__init__(hap, device, "Top") + if channel == 2: + super().__init__(hap, device, post="Top", channel=channel) else: - super().__init__(hap, device, "Bottom") + super().__init__(hap, device, post="Bottom", channel=channel) self._color_switcher = { RGBColorState.WHITE: [0.0, 0.0], @@ -167,7 +158,7 @@ def __init__(self, hap: HomematicipHAP, device, channel: int) -> None: @property def _func_channel(self) -> NotificationLightChannel: - return self._device.functionalChannels[self.channel] + return self._device.functionalChannels[self._channel] @property def is_on(self) -> bool: @@ -198,14 +189,6 @@ def device_state_attributes(self) -> Dict[str, Any]: return state_attr - @property - def name(self) -> str: - """Return the name of the notification light sensor.""" - label = self._get_label_by_channel(self.channel) - if label: - return label - return f"{super().name} Notification" - @property def supported_features(self) -> int: """Flag supported features.""" @@ -214,7 +197,7 @@ def supported_features(self) -> int: @property def unique_id(self) -> str: """Return a unique ID.""" - return f"{self.__class__.__name__}_{self.post}_{self._device.id}" + return f"{self.__class__.__name__}_{self._post}_{self._device.id}" async def async_turn_on(self, **kwargs) -> None: """Turn the light on.""" @@ -237,7 +220,7 @@ async def async_turn_on(self, **kwargs) -> None: transition = kwargs.get(ATTR_TRANSITION, 0.5) await self._device.set_rgb_dim_level_with_time( - channelIndex=self.channel, + channelIndex=self._channel, rgb=simple_rgb_color, dimLevel=dim_level, onTime=0, @@ -250,7 +233,7 @@ async def async_turn_off(self, **kwargs) -> None: transition = kwargs.get(ATTR_TRANSITION, 0.5) await self._device.set_rgb_dim_level_with_time( - channelIndex=self.channel, + channelIndex=self._channel, rgb=simple_rgb_color, dimLevel=0.0, onTime=0, diff --git a/homeassistant/components/homematicip_cloud/sensor.py b/homeassistant/components/homematicip_cloud/sensor.py index 32191cde20e486..77d7560e622f98 100644 --- a/homeassistant/components/homematicip_cloud/sensor.py +++ b/homeassistant/components/homematicip_cloud/sensor.py @@ -30,6 +30,8 @@ DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_POWER, DEVICE_CLASS_TEMPERATURE, + LENGTH_MILLIMETERS, + LIGHT_LUX, PERCENTAGE, POWER_WATT, SPEED_KILOMETERS_PER_HOUR, @@ -125,7 +127,7 @@ class HomematicipAccesspointStatus(HomematicipGenericEntity): def __init__(self, hap: HomematicipHAP) -> None: """Initialize access point status entity.""" - super().__init__(hap, hap.home) + super().__init__(hap, device=hap.home, post="Duty Cycle") @property def device_info(self) -> Dict[str, Any]: @@ -134,7 +136,7 @@ def device_info(self) -> Dict[str, Any]: return { "identifiers": { # Serial numbers of Homematic IP device - (HMIPC_DOMAIN, self._device.id) + (HMIPC_DOMAIN, self._home.id) } } @@ -174,7 +176,7 @@ class HomematicipHeatingThermostat(HomematicipGenericEntity): def __init__(self, hap: HomematicipHAP, device) -> None: """Initialize heating thermostat device.""" - super().__init__(hap, device, "Heating") + super().__init__(hap, device, post="Heating") @property def icon(self) -> str: @@ -203,7 +205,7 @@ class HomematicipHumiditySensor(HomematicipGenericEntity): def __init__(self, hap: HomematicipHAP, device) -> None: """Initialize the thermometer device.""" - super().__init__(hap, device, "Humidity") + super().__init__(hap, device, post="Humidity") @property def device_class(self) -> str: @@ -226,7 +228,7 @@ class HomematicipTemperatureSensor(HomematicipGenericEntity): def __init__(self, hap: HomematicipHAP, device) -> None: """Initialize the thermometer device.""" - super().__init__(hap, device, "Temperature") + super().__init__(hap, device, post="Temperature") @property def device_class(self) -> str: @@ -263,7 +265,7 @@ class HomematicipIlluminanceSensor(HomematicipGenericEntity): def __init__(self, hap: HomematicipHAP, device) -> None: """Initialize the device.""" - super().__init__(hap, device, "Illuminance") + super().__init__(hap, device, post="Illuminance") @property def device_class(self) -> str: @@ -281,7 +283,7 @@ def state(self) -> float: @property def unit_of_measurement(self) -> str: """Return the unit this state is expressed in.""" - return "lx" + return LIGHT_LUX @property def device_state_attributes(self) -> Dict[str, Any]: @@ -301,7 +303,7 @@ class HomematicipPowerSensor(HomematicipGenericEntity): def __init__(self, hap: HomematicipHAP, device) -> None: """Initialize the device.""" - super().__init__(hap, device, "Power") + super().__init__(hap, device, post="Power") @property def device_class(self) -> str: @@ -324,7 +326,7 @@ class HomematicipWindspeedSensor(HomematicipGenericEntity): def __init__(self, hap: HomematicipHAP, device) -> None: """Initialize the windspeed sensor.""" - super().__init__(hap, device, "Windspeed") + super().__init__(hap, device, post="Windspeed") @property def state(self) -> float: @@ -357,7 +359,7 @@ class HomematicipTodayRainSensor(HomematicipGenericEntity): def __init__(self, hap: HomematicipHAP, device) -> None: """Initialize the device.""" - super().__init__(hap, device, "Today Rain") + super().__init__(hap, device, post="Today Rain") @property def state(self) -> float: @@ -367,7 +369,7 @@ def state(self) -> float: @property def unit_of_measurement(self) -> str: """Return the unit this state is expressed in.""" - return "mm" + return LENGTH_MILLIMETERS class HomematicipPassageDetectorDeltaCounter(HomematicipGenericEntity): diff --git a/homeassistant/components/homematicip_cloud/strings.json b/homeassistant/components/homematicip_cloud/strings.json index fddb2b85df69dc..1efafdc6e29337 100644 --- a/homeassistant/components/homematicip_cloud/strings.json +++ b/homeassistant/components/homematicip_cloud/strings.json @@ -5,8 +5,8 @@ "title": "Pick HomematicIP Access point", "data": { "hapid": "Access point ID (SGTIN)", - "pin": "Pin Code (optional)", - "name": "Name (optional, used as name prefix for all devices)" + "pin": "[%key:common::config_flow::data::pin%]", + "name": "[%key:common::config_flow::data::name%] (optional, used as name prefix for all devices)" } }, "link": { @@ -21,9 +21,9 @@ "timeout_button": "Blue button press timeout, please try again." }, "abort": { - "unknown": "Unknown error occurred.", - "connection_aborted": "Could not connect to HMIP server", - "already_configured": "Access point is already configured" + "unknown": "[%key:common::config_flow::error::unknown%]", + "connection_aborted": "[%key:common::config_flow::error::cannot_connect%]", + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } } } diff --git a/homeassistant/components/homematicip_cloud/switch.py b/homeassistant/components/homematicip_cloud/switch.py index 64ee862b2d2345..b9fbdad651a26a 100644 --- a/homeassistant/components/homematicip_cloud/switch.py +++ b/homeassistant/components/homematicip_cloud/switch.py @@ -54,16 +54,16 @@ async def async_setup_entry( entities.append(HomematicipSwitch(hap, device)) elif isinstance(device, AsyncOpenCollector8Module): for channel in range(1, 9): - entities.append(HomematicipMultiSwitch(hap, device, channel)) + entities.append(HomematicipMultiSwitch(hap, device, channel=channel)) elif isinstance(device, AsyncHeatingSwitch2): for channel in range(1, 3): - entities.append(HomematicipMultiSwitch(hap, device, channel)) + entities.append(HomematicipMultiSwitch(hap, device, channel=channel)) elif isinstance(device, AsyncMultiIOBox): for channel in range(1, 3): - entities.append(HomematicipMultiSwitch(hap, device, channel)) + entities.append(HomematicipMultiSwitch(hap, device, channel=channel)) elif isinstance(device, AsyncPrintedCircuitBoardSwitch2): for channel in range(1, 3): - entities.append(HomematicipMultiSwitch(hap, device, channel)) + entities.append(HomematicipMultiSwitch(hap, device, channel=channel)) for group in hap.home.groups: if isinstance(group, (AsyncExtendedLinkedSwitchingGroup, AsyncSwitchingGroup)): @@ -156,31 +156,17 @@ class HomematicipMultiSwitch(HomematicipGenericEntity, SwitchEntity): def __init__(self, hap: HomematicipHAP, device, channel: int) -> None: """Initialize the multi switch device.""" - self.channel = channel - super().__init__(hap, device, f"Channel{channel}") - - @property - def name(self) -> str: - """Return the name of the multi switch channel.""" - label = self._get_label_by_channel(self.channel) - if label: - return label - return super().name - - @property - def unique_id(self) -> str: - """Return a unique ID.""" - return f"{self.__class__.__name__}_{self.post}_{self._device.id}" + super().__init__(hap, device, channel=channel) @property def is_on(self) -> bool: """Return true if switch is on.""" - return self._device.functionalChannels[self.channel].on + return self._device.functionalChannels[self._channel].on async def async_turn_on(self, **kwargs) -> None: """Turn the switch on.""" - await self._device.turn_on(self.channel) + await self._device.turn_on(self._channel) async def async_turn_off(self, **kwargs) -> None: """Turn the switch off.""" - await self._device.turn_off(self.channel) + await self._device.turn_off(self._channel) diff --git a/homeassistant/components/homematicip_cloud/translations/ca.json b/homeassistant/components/homematicip_cloud/translations/ca.json index 871af68e360b57..26892977185841 100644 --- a/homeassistant/components/homematicip_cloud/translations/ca.json +++ b/homeassistant/components/homematicip_cloud/translations/ca.json @@ -1,9 +1,9 @@ { "config": { "abort": { - "already_configured": "El punt d'acc\u00e9s ja est\u00e0 configurat", - "connection_aborted": "No s'ha pogut connectar al servidor HMIP", - "unknown": "S'ha produ\u00eft un error desconegut." + "already_configured": "El dispositiu ja est\u00e0 configurat", + "connection_aborted": "Ha fallat la connexi\u00f3", + "unknown": "Error inesperat" }, "error": { "invalid_pin": "Codi PIN inv\u00e0lid, torna-ho a provar.", @@ -16,7 +16,7 @@ "init": { "data": { "hapid": "Identificador del punt d'acc\u00e9s (SGTIN)", - "name": "Nom (opcional, s'utilitza com a nom prefix per a tots els dispositius)", + "name": "Nom (opcional, s'utilitza com a prefix de nom per a tots els dispositius)", "pin": "Codi PIN (opcional)" }, "title": "Tria el punt d'acc\u00e9s HomematicIP" diff --git a/homeassistant/components/homematicip_cloud/translations/el.json b/homeassistant/components/homematicip_cloud/translations/el.json new file mode 100644 index 00000000000000..843d590e7e0ae1 --- /dev/null +++ b/homeassistant/components/homematicip_cloud/translations/el.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_pin": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf PIN, \u03b4\u03bf\u03ba\u03b9\u03bc\u03ac\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/homematicip_cloud/translations/en.json b/homeassistant/components/homematicip_cloud/translations/en.json index 97cfd1e3f4e46c..acddc0766c4d37 100644 --- a/homeassistant/components/homematicip_cloud/translations/en.json +++ b/homeassistant/components/homematicip_cloud/translations/en.json @@ -1,9 +1,9 @@ { "config": { "abort": { - "already_configured": "Access point is already configured", - "connection_aborted": "Could not connect to HMIP server", - "unknown": "Unknown error occurred." + "already_configured": "Device is already configured", + "connection_aborted": "Failed to connect", + "unknown": "Unexpected error" }, "error": { "invalid_pin": "Invalid PIN, please try again.", diff --git a/homeassistant/components/homematicip_cloud/translations/es.json b/homeassistant/components/homematicip_cloud/translations/es.json index b5877fe09ce270..cd300d4e4b3bd7 100644 --- a/homeassistant/components/homematicip_cloud/translations/es.json +++ b/homeassistant/components/homematicip_cloud/translations/es.json @@ -6,6 +6,7 @@ "unknown": "Se ha producido un error desconocido." }, "error": { + "invalid_pin": "PIN no v\u00e1lido, por favor int\u00e9ntalo de nuevo.", "invalid_sgtin_or_pin": "PIN no v\u00e1lido, por favor int\u00e9ntalo de nuevo.", "press_the_button": "Por favor, pulsa el bot\u00f3n azul", "register_failed": "No se pudo registrar, por favor intentelo de nuevo.", diff --git a/homeassistant/components/homematicip_cloud/translations/et.json b/homeassistant/components/homematicip_cloud/translations/et.json index 92f07d401e633b..4f97508c045661 100644 --- a/homeassistant/components/homematicip_cloud/translations/et.json +++ b/homeassistant/components/homematicip_cloud/translations/et.json @@ -1,6 +1,7 @@ { "config": { "error": { + "invalid_pin": "Vale PIN-kood. Palun proovige uuesti.", "invalid_sgtin_or_pin": "Vale PIN, palun proovige uuesti" }, "step": { diff --git a/homeassistant/components/homematicip_cloud/translations/fr.json b/homeassistant/components/homematicip_cloud/translations/fr.json index 585334b31183ff..0c5f54d588ab04 100644 --- a/homeassistant/components/homematicip_cloud/translations/fr.json +++ b/homeassistant/components/homematicip_cloud/translations/fr.json @@ -7,7 +7,7 @@ }, "error": { "invalid_pin": "Code PIN invalide, veuillez r\u00e9essayer.", - "invalid_sgtin_or_pin": "Code PIN invalide, veuillez r\u00e9essayer.", + "invalid_sgtin_or_pin": "Code SGTIN ou PIN invalide, veuillez r\u00e9essayer.", "press_the_button": "Veuillez appuyer sur le bouton bleu.", "register_failed": "\u00c9chec d'enregistrement. Veuillez r\u00e9essayer.", "timeout_button": "D\u00e9lai d'attente expir\u00e9, veuillez r\u00e9\u00e9ssayer." diff --git a/homeassistant/components/homematicip_cloud/translations/it.json b/homeassistant/components/homematicip_cloud/translations/it.json index 4e7bfd1108ca99..fc6dc5aa55ad0c 100644 --- a/homeassistant/components/homematicip_cloud/translations/it.json +++ b/homeassistant/components/homematicip_cloud/translations/it.json @@ -1,9 +1,9 @@ { "config": { "abort": { - "already_configured": "Il punto di accesso \u00e8 gi\u00e0 configurato", - "connection_aborted": "Impossibile connettersi al server HMIP", - "unknown": "Si \u00e8 verificato un errore sconosciuto." + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "connection_aborted": "Impossibile connettersi", + "unknown": "Errore imprevisto" }, "error": { "invalid_pin": "PIN non valido, riprova.", diff --git a/homeassistant/components/homematicip_cloud/translations/ko.json b/homeassistant/components/homematicip_cloud/translations/ko.json index b85b8ac00b1153..962faa680664a9 100644 --- a/homeassistant/components/homematicip_cloud/translations/ko.json +++ b/homeassistant/components/homematicip_cloud/translations/ko.json @@ -6,6 +6,7 @@ "unknown": "\uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4" }, "error": { + "invalid_pin": "\uc798\ubabb\ub41c PIN\uc785\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud558\uc2ed\uc2dc\uc624.", "invalid_sgtin_or_pin": "PIN\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", "press_the_button": "\ud30c\ub780\uc0c9 \ubc84\ud2bc\uc744 \ub20c\ub7ec\uc8fc\uc138\uc694.", "register_failed": "\ub4f1\ub85d\uc5d0 \uc2e4\ud328\ud558\uc600\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.", diff --git a/homeassistant/components/homematicip_cloud/translations/lb.json b/homeassistant/components/homematicip_cloud/translations/lb.json index 80892a3e282419..92487c12ea6b09 100644 --- a/homeassistant/components/homematicip_cloud/translations/lb.json +++ b/homeassistant/components/homematicip_cloud/translations/lb.json @@ -6,6 +6,7 @@ "unknown": "Onbekannten Feeler opgetrueden" }, "error": { + "invalid_pin": "Ong\u00ebltege Pin, prob\u00e9ier w.e.g. nach emol.", "invalid_sgtin_or_pin": "Ong\u00ebltege Pin, prob\u00e9iert w.e.g. nach emol.", "press_the_button": "Dr\u00e9ckt w.e.g. de bloe Kn\u00e4ppchen.", "register_failed": "Feeler beim registr\u00e9ieren, prob\u00e9iert w.e.g. nach emol.", diff --git a/homeassistant/components/homematicip_cloud/translations/nl.json b/homeassistant/components/homematicip_cloud/translations/nl.json index 7127b5c5aae98e..f16e385c3a0908 100644 --- a/homeassistant/components/homematicip_cloud/translations/nl.json +++ b/homeassistant/components/homematicip_cloud/translations/nl.json @@ -6,6 +6,7 @@ "unknown": "Er is een onbekende fout opgetreden." }, "error": { + "invalid_pin": "Ongeldige pincode, probeer het opnieuw.", "invalid_sgtin_or_pin": "Ongeldige PIN-code, probeer het nogmaals.", "press_the_button": "Druk op de blauwe knop.", "register_failed": "Kan niet registreren, gelieve opnieuw te proberen.", diff --git a/homeassistant/components/homematicip_cloud/translations/pl.json b/homeassistant/components/homematicip_cloud/translations/pl.json index cfd8e96c2a2fa5..c317bbddd26c0a 100644 --- a/homeassistant/components/homematicip_cloud/translations/pl.json +++ b/homeassistant/components/homematicip_cloud/translations/pl.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "Punkt dost\u0119pu jest ju\u017c skonfigurowany.", "connection_aborted": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z serwerem HMIP", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "error": { "invalid_pin": "Nieprawid\u0142owy kod PIN, spr\u00f3buj ponownie.", diff --git a/homeassistant/components/homematicip_cloud/translations/ru.json b/homeassistant/components/homematicip_cloud/translations/ru.json index b307140ef02a21..a7b3ad1506c19f 100644 --- a/homeassistant/components/homematicip_cloud/translations/ru.json +++ b/homeassistant/components/homematicip_cloud/translations/ru.json @@ -1,9 +1,9 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", - "connection_aborted": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 HMIP.", - "unknown": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + "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.", + "connection_aborted": "\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." }, "error": { "invalid_pin": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 PIN-\u043a\u043e\u0434, \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0441\u043d\u043e\u0432\u0430.", diff --git a/homeassistant/components/huawei_lte/__init__.py b/homeassistant/components/huawei_lte/__init__.py index 29db6026cc4c1c..8deed67e62bea5 100644 --- a/homeassistant/components/huawei_lte/__init__.py +++ b/homeassistant/components/huawei_lte/__init__.py @@ -63,6 +63,7 @@ KEY_DEVICE_INFORMATION, KEY_DEVICE_SIGNAL, KEY_DIALUP_MOBILE_DATASWITCH, + KEY_MONITORING_CHECK_NOTIFICATIONS, KEY_MONITORING_MONTH_STATISTICS, KEY_MONITORING_STATUS, KEY_MONITORING_TRAFFIC_STATISTICS, @@ -145,7 +146,7 @@ class Router: suspended = attr.ib(init=False, default=False) notify_last_attempt: float = attr.ib(init=False, default=-1) - def __attrs_post_init__(self): + def __attrs_post_init__(self) -> None: """Set up internal state on init.""" self.client = Client(self.connection) @@ -175,7 +176,7 @@ def device_connections(self) -> Set[Tuple[str, str]]: """Get router connections for device registry.""" return {(dr.CONNECTION_NETWORK_MAC, self.mac)} if self.mac else set() - def _get_data(self, key: str, func: Callable[[None], Any]) -> None: + def _get_data(self, key: str, func: Callable[[], Any]) -> None: if not self.subscriptions.get(key): return if key in self.inflight_gets: @@ -243,6 +244,10 @@ def update(self) -> None: self._get_data( KEY_MONITORING_MONTH_STATISTICS, self.client.monitoring.month_statistics ) + self._get_data( + KEY_MONITORING_CHECK_NOTIFICATIONS, + self.client.monitoring.check_notifications, + ) self._get_data(KEY_MONITORING_STATUS, self.client.monitoring.status) self._get_data( KEY_MONITORING_TRAFFIC_STATISTICS, self.client.monitoring.traffic_statistics @@ -270,7 +275,7 @@ def logout(self) -> None: except Exception: # pylint: disable=broad-except _LOGGER.warning("Logout error", exc_info=True) - def cleanup(self, *_) -> None: + def cleanup(self, *_: Any) -> None: """Clean up resources.""" self.subscriptions.clear() @@ -354,7 +359,7 @@ def get_connection() -> Connection: username = config_entry.data.get(CONF_USERNAME) password = config_entry.data.get(CONF_PASSWORD) if username or password: - connection = AuthorizedConnection( + connection: Connection = AuthorizedConnection( url, username=username, password=password, timeout=CONNECTION_TIMEOUT ) else: diff --git a/homeassistant/components/huawei_lte/binary_sensor.py b/homeassistant/components/huawei_lte/binary_sensor.py index 575cc9789ca50d..f5c60963aa7d3c 100644 --- a/homeassistant/components/huawei_lte/binary_sensor.py +++ b/homeassistant/components/huawei_lte/binary_sensor.py @@ -1,7 +1,7 @@ """Support for Huawei LTE binary sensors.""" import logging -from typing import Optional +from typing import List, Optional import attr from huawei_lte_api.enums.cradle import ConnectionStatusEnum @@ -11,9 +11,15 @@ BinarySensorEntity, ) from homeassistant.const import CONF_URL +from homeassistant.helpers.entity import Entity from . import HuaweiLteBaseEntity -from .const import DOMAIN, KEY_MONITORING_STATUS, KEY_WLAN_WIFI_FEATURE_SWITCH +from .const import ( + DOMAIN, + KEY_MONITORING_CHECK_NOTIFICATIONS, + KEY_MONITORING_STATUS, + KEY_WLAN_WIFI_FEATURE_SWITCH, +) _LOGGER = logging.getLogger(__name__) @@ -21,7 +27,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up from config entry.""" router = hass.data[DOMAIN].routers[config_entry.data[CONF_URL]] - entities = [] + entities: List[Entity] = [] if router.data.get(KEY_MONITORING_STATUS): entities.append(HuaweiLteMobileConnectionBinarySensor(router)) @@ -29,6 +35,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entities.append(HuaweiLteWifi24ghzStatusBinarySensor(router)) entities.append(HuaweiLteWifi5ghzStatusBinarySensor(router)) + if router.data.get(KEY_MONITORING_CHECK_NOTIFICATIONS): + entities.append(HuaweiLteSmsStorageFullBinarySensor(router)) + async_add_entities(entities, True) @@ -49,19 +58,19 @@ def entity_registry_enabled_default(self) -> bool: def _device_unique_id(self) -> str: return f"{self.key}.{self.item}" - async def async_added_to_hass(self): + async def async_added_to_hass(self) -> None: """Subscribe to needed data on add.""" await super().async_added_to_hass() self.router.subscriptions[self.key].add(f"{BINARY_SENSOR_DOMAIN}/{self.item}") - async def async_will_remove_from_hass(self): + async def async_will_remove_from_hass(self) -> None: """Unsubscribe from needed data on remove.""" await super().async_will_remove_from_hass() self.router.subscriptions[self.key].remove( f"{BINARY_SENSOR_DOMAIN}/{self.item}" ) - async def async_update(self): + async def async_update(self) -> None: """Update state.""" try: value = self.router.data[self.key][self.item] @@ -86,7 +95,7 @@ async def async_update(self): class HuaweiLteMobileConnectionBinarySensor(HuaweiLteBaseBinarySensor): """Huawei LTE mobile connection binary sensor.""" - def __attrs_post_init__(self): + def __attrs_post_init__(self) -> None: """Initialize identifiers.""" self.key = KEY_MONITORING_STATUS self.item = "ConnectionStatus" @@ -113,7 +122,7 @@ def assumed_state(self) -> bool: ) @property - def icon(self): + def icon(self) -> str: """Return mobile connectivity sensor icon.""" return "mdi:signal" if self.is_on else "mdi:signal-off" @@ -149,7 +158,7 @@ def assumed_state(self) -> bool: return self._raw_state is None @property - def icon(self): + def icon(self) -> str: """Return WiFi status sensor icon.""" return "mdi:wifi" if self.is_on else "mdi:wifi-off" @@ -158,7 +167,7 @@ def icon(self): class HuaweiLteWifiStatusBinarySensor(HuaweiLteBaseWifiStatusBinarySensor): """Huawei LTE WiFi status binary sensor.""" - def __attrs_post_init__(self): + def __attrs_post_init__(self) -> None: """Initialize identifiers.""" self.key = KEY_MONITORING_STATUS self.item = "WifiStatus" @@ -172,7 +181,7 @@ def _entity_name(self) -> str: class HuaweiLteWifi24ghzStatusBinarySensor(HuaweiLteBaseWifiStatusBinarySensor): """Huawei LTE 2.4GHz WiFi status binary sensor.""" - def __attrs_post_init__(self): + def __attrs_post_init__(self) -> None: """Initialize identifiers.""" self.key = KEY_WLAN_WIFI_FEATURE_SWITCH self.item = "wifi24g_switch_enable" @@ -186,7 +195,7 @@ def _entity_name(self) -> str: class HuaweiLteWifi5ghzStatusBinarySensor(HuaweiLteBaseWifiStatusBinarySensor): """Huawei LTE 5GHz WiFi status binary sensor.""" - def __attrs_post_init__(self): + def __attrs_post_init__(self) -> None: """Initialize identifiers.""" self.key = KEY_WLAN_WIFI_FEATURE_SWITCH self.item = "wifi5g_enabled" @@ -194,3 +203,32 @@ def __attrs_post_init__(self): @property def _entity_name(self) -> str: return "5GHz WiFi status" + + +@attr.s +class HuaweiLteSmsStorageFullBinarySensor(HuaweiLteBaseBinarySensor): + """Huawei LTE SMS storage full binary sensor.""" + + def __attrs_post_init__(self) -> None: + """Initialize identifiers.""" + self.key = KEY_MONITORING_CHECK_NOTIFICATIONS + self.item = "SmsStorageFull" + + @property + def _entity_name(self) -> str: + return "SMS storage full" + + @property + def is_on(self) -> bool: + """Return whether the binary sensor is on.""" + return self._raw_state is not None and int(self._raw_state) != 0 + + @property + def assumed_state(self) -> bool: + """Return True if real state is assumed, not known.""" + return self._raw_state is None + + @property + def icon(self) -> str: + """Return WiFi status sensor icon.""" + return "mdi:email-alert" if self.is_on else "mdi:email-off" diff --git a/homeassistant/components/huawei_lte/config_flow.py b/homeassistant/components/huawei_lte/config_flow.py index 9d5ce40074d3ba..af45f7e59bd1ef 100644 --- a/homeassistant/components/huawei_lte/config_flow.py +++ b/homeassistant/components/huawei_lte/config_flow.py @@ -121,7 +121,7 @@ async def async_step_user(self, user_input=None): if self._already_configured(user_input): return self.async_abort(reason="already_configured") - conn = None + conn: Optional[Connection] = None def logout(): if hasattr(conn, "user"): @@ -189,7 +189,7 @@ def get_router_title(conn: Connection) -> str: except LoginErrorPasswordWrongException: errors[CONF_PASSWORD] = "incorrect_password" except LoginErrorUsernamePasswordWrongException: - errors[CONF_USERNAME] = "incorrect_username_or_password" + errors[CONF_USERNAME] = "invalid_auth" except LoginErrorUsernamePasswordOverrunException: errors["base"] = "login_attempts_exceeded" except ResponseErrorException: @@ -200,7 +200,7 @@ def get_router_title(conn: Connection) -> str: errors[CONF_URL] = "connection_timeout" except Exception: # pylint: disable=broad-except _LOGGER.warning("Unknown error connecting to device", exc_info=True) - errors[CONF_URL] = "unknown_connection_error" + errors[CONF_URL] = "unknown" if errors: await self.hass.async_add_executor_job(logout) return await self._async_show_user_form( diff --git a/homeassistant/components/huawei_lte/const.py b/homeassistant/components/huawei_lte/const.py index 2c5a3f8a9f66a8..039bab10fb95e6 100644 --- a/homeassistant/components/huawei_lte/const.py +++ b/homeassistant/components/huawei_lte/const.py @@ -27,6 +27,7 @@ KEY_DEVICE_INFORMATION = "device_information" KEY_DEVICE_SIGNAL = "device_signal" KEY_DIALUP_MOBILE_DATASWITCH = "dialup_mobile_dataswitch" +KEY_MONITORING_CHECK_NOTIFICATIONS = "monitoring_check_notifications" KEY_MONITORING_MONTH_STATISTICS = "monitoring_month_statistics" KEY_MONITORING_STATUS = "monitoring_status" KEY_MONITORING_TRAFFIC_STATISTICS = "monitoring_traffic_statistics" @@ -36,13 +37,18 @@ KEY_WLAN_HOST_LIST = "wlan_host_list" KEY_WLAN_WIFI_FEATURE_SWITCH = "wlan_wifi_feature_switch" -BINARY_SENSOR_KEYS = {KEY_MONITORING_STATUS, KEY_WLAN_WIFI_FEATURE_SWITCH} +BINARY_SENSOR_KEYS = { + KEY_MONITORING_CHECK_NOTIFICATIONS, + KEY_MONITORING_STATUS, + KEY_WLAN_WIFI_FEATURE_SWITCH, +} DEVICE_TRACKER_KEYS = {KEY_WLAN_HOST_LIST} SENSOR_KEYS = { KEY_DEVICE_INFORMATION, KEY_DEVICE_SIGNAL, + KEY_MONITORING_CHECK_NOTIFICATIONS, KEY_MONITORING_MONTH_STATISTICS, KEY_MONITORING_STATUS, KEY_MONITORING_TRAFFIC_STATISTICS, diff --git a/homeassistant/components/huawei_lte/device_tracker.py b/homeassistant/components/huawei_lte/device_tracker.py index 54e8f318cf68b9..781f2dfcf11cbc 100644 --- a/homeassistant/components/huawei_lte/device_tracker.py +++ b/homeassistant/components/huawei_lte/device_tracker.py @@ -16,6 +16,7 @@ from homeassistant.core import callback from homeassistant.helpers import entity_registry from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity import Entity from . import HuaweiLteBaseEntity from .const import DOMAIN, KEY_WLAN_HOST_LIST, UPDATE_SIGNAL @@ -81,7 +82,7 @@ def async_add_new_entities(hass, router_url, async_add_entities, tracked): _LOGGER.debug("%s[%s][%s] not in data", KEY_WLAN_HOST_LIST, "Hosts", "Host") return - new_entities = [] + new_entities: List[Entity] = [] for host in (x for x in hosts if x.get("MacAddress")): entity = HuaweiLteScannerEntity(router, host["MacAddress"]) if entity.unique_id in tracked: @@ -116,7 +117,7 @@ class HuaweiLteScannerEntity(HuaweiLteBaseEntity, ScannerEntity): _hostname: Optional[str] = attr.ib(init=False, default=None) _device_state_attributes: Dict[str, Any] = attr.ib(init=False, factory=dict) - def __attrs_post_init__(self): + def __attrs_post_init__(self) -> None: """Initialize internal state.""" self._device_state_attributes["mac_address"] = self.mac @@ -148,17 +149,8 @@ async def async_update(self) -> None: hosts = self.router.data[KEY_WLAN_HOST_LIST]["Hosts"]["Host"] host = next((x for x in hosts if x.get("MacAddress") == self.mac), None) self._is_connected = host is not None - if self._is_connected: + if host is not None: self._hostname = host.get("HostName") self._device_state_attributes = { _better_snakecase(k): v for k, v in host.items() if k != "HostName" } - - -def get_scanner(*args, **kwargs): # pylint: disable=useless-return - """Old no longer used way to set up Huawei LTE device tracker.""" - _LOGGER.warning( - "Loading and configuring as a platform is no longer supported or " - "required, convert to enabling/disabling available entities" - ) - return None diff --git a/homeassistant/components/huawei_lte/notify.py b/homeassistant/components/huawei_lte/notify.py index 91cc8864eb0c9f..375ced911c8d2d 100644 --- a/homeassistant/components/huawei_lte/notify.py +++ b/homeassistant/components/huawei_lte/notify.py @@ -19,10 +19,6 @@ async def async_get_service(hass, config, discovery_info=None): """Get the notification service.""" if discovery_info is None: - _LOGGER.warning( - "Loading as a platform is no longer supported, convert to use " - "config entries or the huawei_lte component" - ) return None router = hass.data[DOMAIN].routers[discovery_info[CONF_URL]] diff --git a/homeassistant/components/huawei_lte/sensor.py b/homeassistant/components/huawei_lte/sensor.py index f547dbd2eb6a61..64f5f8176a9b63 100644 --- a/homeassistant/components/huawei_lte/sensor.py +++ b/homeassistant/components/huawei_lte/sensor.py @@ -2,7 +2,7 @@ import logging import re -from typing import Optional +from typing import Callable, Dict, List, NamedTuple, Optional, Pattern, Tuple, Union import attr @@ -10,13 +10,22 @@ DEVICE_CLASS_SIGNAL_STRENGTH, DOMAIN as SENSOR_DOMAIN, ) -from homeassistant.const import CONF_URL, DATA_BYTES, STATE_UNKNOWN, TIME_SECONDS +from homeassistant.const import ( + CONF_URL, + DATA_BYTES, + DATA_RATE_BYTES_PER_SECOND, + STATE_UNKNOWN, + TIME_SECONDS, +) +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.typing import StateType from . import HuaweiLteBaseEntity from .const import ( DOMAIN, KEY_DEVICE_INFORMATION, KEY_DEVICE_SIGNAL, + KEY_MONITORING_CHECK_NOTIFICATIONS, KEY_MONITORING_MONTH_STATISTICS, KEY_MONITORING_STATUS, KEY_MONITORING_TRAFFIC_STATISTICS, @@ -29,25 +38,66 @@ _LOGGER = logging.getLogger(__name__) -SENSOR_META = { - KEY_DEVICE_INFORMATION: dict( +class SensorMeta(NamedTuple): + """Metadata for defining sensors.""" + + name: Optional[str] = None + device_class: Optional[str] = None + icon: Union[str, Callable[[StateType], str], None] = None + unit: Optional[str] = None + enabled_default: bool = False + include: Optional[Pattern[str]] = None + exclude: Optional[Pattern[str]] = None + formatter: Optional[Callable[[str], Tuple[StateType, Optional[str]]]] = None + + +SENSOR_META: Dict[Union[str, Tuple[str, str]], SensorMeta] = { + KEY_DEVICE_INFORMATION: SensorMeta( include=re.compile(r"^WanIP.*Address$", re.IGNORECASE) ), - (KEY_DEVICE_INFORMATION, "WanIPAddress"): dict( + (KEY_DEVICE_INFORMATION, "WanIPAddress"): SensorMeta( name="WAN IP address", icon="mdi:ip", enabled_default=True ), - (KEY_DEVICE_INFORMATION, "WanIPv6Address"): dict( + (KEY_DEVICE_INFORMATION, "WanIPv6Address"): SensorMeta( name="WAN IPv6 address", icon="mdi:ip" ), - (KEY_DEVICE_SIGNAL, "band"): dict(name="Band"), - (KEY_DEVICE_SIGNAL, "cell_id"): dict(name="Cell ID"), - (KEY_DEVICE_SIGNAL, "lac"): dict(name="LAC", icon="mdi:map-marker"), - (KEY_DEVICE_SIGNAL, "mode"): dict( + (KEY_DEVICE_SIGNAL, "band"): SensorMeta(name="Band"), + (KEY_DEVICE_SIGNAL, "cell_id"): SensorMeta(name="Cell ID"), + (KEY_DEVICE_SIGNAL, "dl_mcs"): SensorMeta(name="Downlink MCS"), + (KEY_DEVICE_SIGNAL, "dlbandwidth"): SensorMeta( + name="Downlink bandwidth", + icon=lambda x: (x is None or x < 8) + and "mdi:speedometer-slow" + or x < 15 + and "mdi:speedometer-medium" + or "mdi:speedometer", + ), + (KEY_DEVICE_SIGNAL, "earfcn"): SensorMeta(name="EARFCN"), + (KEY_DEVICE_SIGNAL, "lac"): SensorMeta(name="LAC", icon="mdi:map-marker"), + (KEY_DEVICE_SIGNAL, "plmn"): SensorMeta(name="PLMN"), + (KEY_DEVICE_SIGNAL, "rac"): SensorMeta(name="RAC", icon="mdi:map-marker"), + (KEY_DEVICE_SIGNAL, "rrc_status"): SensorMeta(name="RRC status"), + (KEY_DEVICE_SIGNAL, "tac"): SensorMeta(name="TAC", icon="mdi:map-marker"), + (KEY_DEVICE_SIGNAL, "tdd"): SensorMeta(name="TDD"), + (KEY_DEVICE_SIGNAL, "txpower"): SensorMeta( + name="Transmit power", + device_class=DEVICE_CLASS_SIGNAL_STRENGTH, + ), + (KEY_DEVICE_SIGNAL, "ul_mcs"): SensorMeta(name="Uplink MCS"), + (KEY_DEVICE_SIGNAL, "ulbandwidth"): SensorMeta( + name="Uplink bandwidth", + icon=lambda x: (x is None or x < 8) + and "mdi:speedometer-slow" + or x < 15 + and "mdi:speedometer-medium" + or "mdi:speedometer", + ), + (KEY_DEVICE_SIGNAL, "mode"): SensorMeta( name="Mode", formatter=lambda x: ({"0": "2G", "2": "3G", "7": "4G"}.get(x, "Unknown"), None), ), - (KEY_DEVICE_SIGNAL, "pci"): dict(name="PCI"), - (KEY_DEVICE_SIGNAL, "rsrq"): dict( + (KEY_DEVICE_SIGNAL, "pci"): SensorMeta(name="PCI"), + (KEY_DEVICE_SIGNAL, "rsrq"): SensorMeta( name="RSRQ", device_class=DEVICE_CLASS_SIGNAL_STRENGTH, # http://www.lte-anbieter.info/technik/rsrq.php @@ -60,7 +110,7 @@ or "mdi:signal-cellular-3", enabled_default=True, ), - (KEY_DEVICE_SIGNAL, "rsrp"): dict( + (KEY_DEVICE_SIGNAL, "rsrp"): SensorMeta( name="RSRP", device_class=DEVICE_CLASS_SIGNAL_STRENGTH, # http://www.lte-anbieter.info/technik/rsrp.php @@ -73,7 +123,7 @@ or "mdi:signal-cellular-3", enabled_default=True, ), - (KEY_DEVICE_SIGNAL, "rssi"): dict( + (KEY_DEVICE_SIGNAL, "rssi"): SensorMeta( name="RSSI", device_class=DEVICE_CLASS_SIGNAL_STRENGTH, # https://eyesaas.com/wi-fi-signal-strength/ @@ -86,7 +136,7 @@ or "mdi:signal-cellular-3", enabled_default=True, ), - (KEY_DEVICE_SIGNAL, "sinr"): dict( + (KEY_DEVICE_SIGNAL, "sinr"): SensorMeta( name="SINR", device_class=DEVICE_CLASS_SIGNAL_STRENGTH, # http://www.lte-anbieter.info/technik/sinr.php @@ -99,7 +149,7 @@ or "mdi:signal-cellular-3", enabled_default=True, ), - (KEY_DEVICE_SIGNAL, "rscp"): dict( + (KEY_DEVICE_SIGNAL, "rscp"): SensorMeta( name="RSCP", device_class=DEVICE_CLASS_SIGNAL_STRENGTH, # https://wiki.teltonika.lt/view/RSCP @@ -111,7 +161,7 @@ and "mdi:signal-cellular-2" or "mdi:signal-cellular-3", ), - (KEY_DEVICE_SIGNAL, "ecio"): dict( + (KEY_DEVICE_SIGNAL, "ecio"): SensorMeta( name="EC/IO", device_class=DEVICE_CLASS_SIGNAL_STRENGTH, # https://wiki.teltonika.lt/view/EC/IO @@ -123,69 +173,90 @@ and "mdi:signal-cellular-2" or "mdi:signal-cellular-3", ), - KEY_MONITORING_MONTH_STATISTICS: dict( + KEY_MONITORING_CHECK_NOTIFICATIONS: SensorMeta( + exclude=re.compile( + r"^(onlineupdatestatus|smsstoragefull)$", + re.IGNORECASE, + ) + ), + (KEY_MONITORING_CHECK_NOTIFICATIONS, "UnreadMessage"): SensorMeta( + name="SMS unread", icon="mdi:email-receive" + ), + KEY_MONITORING_MONTH_STATISTICS: SensorMeta( exclude=re.compile(r"^month(duration|lastcleartime)$", re.IGNORECASE) ), - (KEY_MONITORING_MONTH_STATISTICS, "CurrentMonthDownload"): dict( + (KEY_MONITORING_MONTH_STATISTICS, "CurrentMonthDownload"): SensorMeta( name="Current month download", unit=DATA_BYTES, icon="mdi:download" ), - (KEY_MONITORING_MONTH_STATISTICS, "CurrentMonthUpload"): dict( + (KEY_MONITORING_MONTH_STATISTICS, "CurrentMonthUpload"): SensorMeta( name="Current month upload", unit=DATA_BYTES, icon="mdi:upload" ), - KEY_MONITORING_STATUS: dict( + KEY_MONITORING_STATUS: SensorMeta( include=re.compile( r"^(currentwifiuser|(primary|secondary).*dns)$", re.IGNORECASE ) ), - (KEY_MONITORING_STATUS, "CurrentWifiUser"): dict( + (KEY_MONITORING_STATUS, "CurrentWifiUser"): SensorMeta( name="WiFi clients connected", icon="mdi:wifi" ), - (KEY_MONITORING_STATUS, "PrimaryDns"): dict( + (KEY_MONITORING_STATUS, "PrimaryDns"): SensorMeta( name="Primary DNS server", icon="mdi:ip" ), - (KEY_MONITORING_STATUS, "SecondaryDns"): dict( + (KEY_MONITORING_STATUS, "SecondaryDns"): SensorMeta( name="Secondary DNS server", icon="mdi:ip" ), - (KEY_MONITORING_STATUS, "PrimaryIPv6Dns"): dict( + (KEY_MONITORING_STATUS, "PrimaryIPv6Dns"): SensorMeta( name="Primary IPv6 DNS server", icon="mdi:ip" ), - (KEY_MONITORING_STATUS, "SecondaryIPv6Dns"): dict( + (KEY_MONITORING_STATUS, "SecondaryIPv6Dns"): SensorMeta( name="Secondary IPv6 DNS server", icon="mdi:ip" ), - KEY_MONITORING_TRAFFIC_STATISTICS: dict( + KEY_MONITORING_TRAFFIC_STATISTICS: SensorMeta( exclude=re.compile(r"^showtraffic$", re.IGNORECASE) ), - (KEY_MONITORING_TRAFFIC_STATISTICS, "CurrentConnectTime"): dict( + (KEY_MONITORING_TRAFFIC_STATISTICS, "CurrentConnectTime"): SensorMeta( name="Current connection duration", unit=TIME_SECONDS, icon="mdi:timer-outline" ), - (KEY_MONITORING_TRAFFIC_STATISTICS, "CurrentDownload"): dict( + (KEY_MONITORING_TRAFFIC_STATISTICS, "CurrentDownload"): SensorMeta( name="Current connection download", unit=DATA_BYTES, icon="mdi:download" ), - (KEY_MONITORING_TRAFFIC_STATISTICS, "CurrentUpload"): dict( + (KEY_MONITORING_TRAFFIC_STATISTICS, "CurrentDownloadRate"): SensorMeta( + name="Current download rate", + unit=DATA_RATE_BYTES_PER_SECOND, + icon="mdi:download", + ), + (KEY_MONITORING_TRAFFIC_STATISTICS, "CurrentUpload"): SensorMeta( name="Current connection upload", unit=DATA_BYTES, icon="mdi:upload" ), - (KEY_MONITORING_TRAFFIC_STATISTICS, "TotalConnectTime"): dict( + (KEY_MONITORING_TRAFFIC_STATISTICS, "CurrentUploadRate"): SensorMeta( + name="Current upload rate", + unit=DATA_RATE_BYTES_PER_SECOND, + icon="mdi:upload", + ), + (KEY_MONITORING_TRAFFIC_STATISTICS, "TotalConnectTime"): SensorMeta( name="Total connected duration", unit=TIME_SECONDS, icon="mdi:timer-outline" ), - (KEY_MONITORING_TRAFFIC_STATISTICS, "TotalDownload"): dict( + (KEY_MONITORING_TRAFFIC_STATISTICS, "TotalDownload"): SensorMeta( name="Total download", unit=DATA_BYTES, icon="mdi:download" ), - (KEY_MONITORING_TRAFFIC_STATISTICS, "TotalUpload"): dict( + (KEY_MONITORING_TRAFFIC_STATISTICS, "TotalUpload"): SensorMeta( name="Total upload", unit=DATA_BYTES, icon="mdi:upload" ), - KEY_NET_CURRENT_PLMN: dict(exclude=re.compile(r"^(Rat|ShortName)$", re.IGNORECASE)), - (KEY_NET_CURRENT_PLMN, "State"): dict( + KEY_NET_CURRENT_PLMN: SensorMeta( + exclude=re.compile(r"^(Rat|ShortName|Spn)$", re.IGNORECASE) + ), + (KEY_NET_CURRENT_PLMN, "State"): SensorMeta( name="Operator search mode", formatter=lambda x: ({"0": "Auto", "1": "Manual"}.get(x, "Unknown"), None), ), - (KEY_NET_CURRENT_PLMN, "FullName"): dict( + (KEY_NET_CURRENT_PLMN, "FullName"): SensorMeta( name="Operator name", ), - (KEY_NET_CURRENT_PLMN, "Numeric"): dict( + (KEY_NET_CURRENT_PLMN, "Numeric"): SensorMeta( name="Operator code", ), - KEY_NET_NET_MODE: dict(include=re.compile(r"^NetworkMode$", re.IGNORECASE)), - (KEY_NET_NET_MODE, "NetworkMode"): dict( + KEY_NET_NET_MODE: SensorMeta(include=re.compile(r"^NetworkMode$", re.IGNORECASE)), + (KEY_NET_NET_MODE, "NetworkMode"): SensorMeta( name="Preferred mode", formatter=lambda x: ( { @@ -200,8 +271,52 @@ None, ), ), - (KEY_SMS_SMS_COUNT, "LocalUnread"): dict( - name="SMS unread", + (KEY_SMS_SMS_COUNT, "LocalDeleted"): SensorMeta( + name="SMS deleted (device)", + icon="mdi:email-minus", + ), + (KEY_SMS_SMS_COUNT, "LocalDraft"): SensorMeta( + name="SMS drafts (device)", + icon="mdi:email-send-outline", + ), + (KEY_SMS_SMS_COUNT, "LocalInbox"): SensorMeta( + name="SMS inbox (device)", + icon="mdi:email", + ), + (KEY_SMS_SMS_COUNT, "LocalMax"): SensorMeta( + name="SMS capacity (device)", + icon="mdi:email", + ), + (KEY_SMS_SMS_COUNT, "LocalOutbox"): SensorMeta( + name="SMS outbox (device)", + icon="mdi:email-send", + ), + (KEY_SMS_SMS_COUNT, "LocalUnread"): SensorMeta( + name="SMS unread (device)", + icon="mdi:email-receive", + ), + (KEY_SMS_SMS_COUNT, "SimDraft"): SensorMeta( + name="SMS drafts (SIM)", + icon="mdi:email-send-outline", + ), + (KEY_SMS_SMS_COUNT, "SimInbox"): SensorMeta( + name="SMS inbox (SIM)", + icon="mdi:email", + ), + (KEY_SMS_SMS_COUNT, "SimMax"): SensorMeta( + name="SMS capacity (SIM)", + icon="mdi:email", + ), + (KEY_SMS_SMS_COUNT, "SimOutbox"): SensorMeta( + name="SMS outbox (SIM)", + icon="mdi:email-send", + ), + (KEY_SMS_SMS_COUNT, "SimUnread"): SensorMeta( + name="SMS unread (SIM)", + icon="mdi:email-receive", + ), + (KEY_SMS_SMS_COUNT, "SimUsed"): SensorMeta( + name="SMS messages (SIM)", icon="mdi:email-receive", ), } @@ -210,22 +325,22 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up from config entry.""" router = hass.data[DOMAIN].routers[config_entry.data[CONF_URL]] - sensors = [] + sensors: List[Entity] = [] for key in SENSOR_KEYS: items = router.data.get(key) if not items: continue key_meta = SENSOR_META.get(key) if key_meta: - include = key_meta.get("include") - if include: - items = filter(include.search, items) - exclude = key_meta.get("exclude") - if exclude: - items = [x for x in items if not exclude.search(x)] + if key_meta.include: + items = filter(key_meta.include.search, items) + if key_meta.exclude: + items = [x for x in items if not key_meta.exclude.search(x)] for item in items: sensors.append( - HuaweiLteSensor(router, key, item, SENSOR_META.get((key, item), {})) + HuaweiLteSensor( + router, key, item, SENSOR_META.get((key, item), SensorMeta()) + ) ) async_add_entities(sensors, True) @@ -254,48 +369,48 @@ class HuaweiLteSensor(HuaweiLteBaseEntity): key: str = attr.ib() item: str = attr.ib() - meta: dict = attr.ib() + meta: SensorMeta = attr.ib() - _state = attr.ib(init=False, default=STATE_UNKNOWN) - _unit: str = attr.ib(init=False) + _state: StateType = attr.ib(init=False, default=STATE_UNKNOWN) + _unit: Optional[str] = attr.ib(init=False) - async def async_added_to_hass(self): + async def async_added_to_hass(self) -> None: """Subscribe to needed data on add.""" await super().async_added_to_hass() self.router.subscriptions[self.key].add(f"{SENSOR_DOMAIN}/{self.item}") - async def async_will_remove_from_hass(self): + async def async_will_remove_from_hass(self) -> None: """Unsubscribe from needed data on remove.""" await super().async_will_remove_from_hass() self.router.subscriptions[self.key].remove(f"{SENSOR_DOMAIN}/{self.item}") @property def _entity_name(self) -> str: - return self.meta.get("name", self.item) + return self.meta.name or self.item @property def _device_unique_id(self) -> str: return f"{self.key}.{self.item}" @property - def state(self): + def state(self) -> StateType: """Return sensor state.""" return self._state @property def device_class(self) -> Optional[str]: """Return sensor device class.""" - return self.meta.get("device_class") + return self.meta.device_class @property - def unit_of_measurement(self): + def unit_of_measurement(self) -> Optional[str]: """Return sensor's unit of measurement.""" - return self.meta.get("unit", self._unit) + return self.meta.unit or self._unit @property - def icon(self): + def icon(self) -> Optional[str]: """Return icon for sensor.""" - icon = self.meta.get("icon") + icon = self.meta.icon if callable(icon): return icon(self.state) return icon @@ -303,9 +418,9 @@ def icon(self): @property def entity_registry_enabled_default(self) -> bool: """Return if the entity should be enabled when first added to the entity registry.""" - return bool(self.meta.get("enabled_default")) + return self.meta.enabled_default - async def async_update(self): + async def async_update(self) -> None: """Update state.""" try: value = self.router.data[self.key][self.item] @@ -315,16 +430,8 @@ async def async_update(self): return self._available = True - formatter = self.meta.get("formatter") + formatter = self.meta.formatter if not callable(formatter): formatter = format_default self._state, self._unit = formatter(value) - - -async def async_setup_platform(*args, **kwargs): - """Old no longer used way to set up Huawei LTE sensors.""" - _LOGGER.warning( - "Loading and configuring as a platform is no longer supported or " - "required, convert to enabling/disabling available entities" - ) diff --git a/homeassistant/components/huawei_lte/strings.json b/homeassistant/components/huawei_lte/strings.json index 8435d0a5347b91..00994f8b0a0ab1 100644 --- a/homeassistant/components/huawei_lte/strings.json +++ b/homeassistant/components/huawei_lte/strings.json @@ -1,20 +1,19 @@ { "config": { "abort": { - "already_configured": "This device has already been configured", - "already_in_progress": "This device is already being configured", + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", "not_huawei_lte": "Not a Huawei LTE device" }, "error": { - "connection_failed": "Connection failed", "connection_timeout": "Connection timeout", "incorrect_password": "Incorrect password", "incorrect_username": "Incorrect username", - "incorrect_username_or_password": "Incorrect username or password", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", "invalid_url": "Invalid URL", "login_attempts_exceeded": "Maximum login attempts exceeded, please try again later", "response_error": "Unknown error from device", - "unknown_connection_error": "Unknown error connecting to device" + "unknown": "[%key:common::config_flow::error::unknown%]" }, "flow_title": "Huawei LTE: {name}", "step": { diff --git a/homeassistant/components/huawei_lte/switch.py b/homeassistant/components/huawei_lte/switch.py index 45b179f470fc41..853fe3f40e7f8f 100644 --- a/homeassistant/components/huawei_lte/switch.py +++ b/homeassistant/components/huawei_lte/switch.py @@ -1,7 +1,7 @@ """Support for Huawei LTE switches.""" import logging -from typing import Optional +from typing import Any, List, Optional import attr @@ -11,6 +11,7 @@ SwitchEntity, ) from homeassistant.const import CONF_URL +from homeassistant.helpers.entity import Entity from . import HuaweiLteBaseEntity from .const import DOMAIN, KEY_DIALUP_MOBILE_DATASWITCH @@ -21,7 +22,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities): """Set up from config entry.""" router = hass.data[DOMAIN].routers[config_entry.data[CONF_URL]] - switches = [] + switches: List[Entity] = [] if router.data.get(KEY_DIALUP_MOBILE_DATASWITCH): switches.append(HuaweiLteMobileDataSwitch(router)) @@ -40,30 +41,30 @@ class HuaweiLteBaseSwitch(HuaweiLteBaseEntity, SwitchEntity): def _turn(self, state: bool) -> None: raise NotImplementedError - def turn_on(self, **kwargs): + def turn_on(self, **kwargs: Any) -> None: """Turn switch on.""" self._turn(state=True) - def turn_off(self, **kwargs): + def turn_off(self, **kwargs: Any) -> None: """Turn switch off.""" self._turn(state=False) @property - def device_class(self): + def device_class(self) -> str: """Return device class.""" return DEVICE_CLASS_SWITCH - async def async_added_to_hass(self): + async def async_added_to_hass(self) -> None: """Subscribe to needed data on add.""" await super().async_added_to_hass() self.router.subscriptions[self.key].add(f"{SWITCH_DOMAIN}/{self.item}") - async def async_will_remove_from_hass(self): + async def async_will_remove_from_hass(self) -> None: """Unsubscribe from needed data on remove.""" await super().async_will_remove_from_hass() self.router.subscriptions[self.key].remove(f"{SWITCH_DOMAIN}/{self.item}") - async def async_update(self): + async def async_update(self) -> None: """Update state.""" try: value = self.router.data[self.key][self.item] @@ -79,7 +80,7 @@ async def async_update(self): class HuaweiLteMobileDataSwitch(HuaweiLteBaseSwitch): """Huawei LTE mobile data switch device.""" - def __attrs_post_init__(self): + def __attrs_post_init__(self) -> None: """Initialize identifiers.""" self.key = KEY_DIALUP_MOBILE_DATASWITCH self.item = "dataswitch" @@ -104,6 +105,6 @@ def _turn(self, state: bool) -> None: self.schedule_update_ha_state() @property - def icon(self): + def icon(self) -> str: """Return switch icon.""" return "mdi:signal" if self.is_on else "mdi:signal-off" diff --git a/homeassistant/components/huawei_lte/translations/ca.json b/homeassistant/components/huawei_lte/translations/ca.json index 1a7b245c9fefb2..856c3986ed0119 100644 --- a/homeassistant/components/huawei_lte/translations/ca.json +++ b/homeassistant/components/huawei_lte/translations/ca.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Aquest dispositiu ja ha estat configurat", - "already_in_progress": "Aquest dispositiu ja s'est\u00e0 configurant", + "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": { @@ -11,9 +11,11 @@ "incorrect_password": "Contrasenya incorrecta", "incorrect_username": "Nom d'usuari incorrecte", "incorrect_username_or_password": "Nom d'usuari o contrasenya incorrectes", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "invalid_url": "URL inv\u00e0lid", "login_attempts_exceeded": "Nombre m\u00e0xim d'intents d'inici de sessi\u00f3 superat, torna-ho a provar m\u00e9s tard", "response_error": "S'ha produ\u00eft un error desconegut del dispositiu", + "unknown": "Error inesperat", "unknown_connection_error": "S'ha produ\u00eft un error desconegut en connectar-se al dispositiu" }, "flow_title": "Huawei LTE: {name}", diff --git a/homeassistant/components/huawei_lte/translations/de.json b/homeassistant/components/huawei_lte/translations/de.json index 15fd57a3d33603..a0fa9914c47435 100644 --- a/homeassistant/components/huawei_lte/translations/de.json +++ b/homeassistant/components/huawei_lte/translations/de.json @@ -16,10 +16,12 @@ "response_error": "Unbekannter Fehler vom Ger\u00e4t", "unknown_connection_error": "Unbekannter Fehler beim Herstellen der Verbindung zum Ger\u00e4t" }, + "flow_title": "Huawei LTE: {name}", "step": { "user": { "data": { "password": "Passwort", + "url": "URL", "username": "Benutzername" }, "description": "Gib die Zugangsdaten zum Ger\u00e4t ein. Die Angabe von Benutzername und Passwort ist optional, erm\u00f6glicht aber die Unterst\u00fctzung weiterer Integrationsfunktionen. Andererseits kann die Verwendung einer autorisierten Verbindung zu Problemen beim Zugriff auf die Web-Schnittstelle des Ger\u00e4ts von au\u00dferhalb des Home Assistant f\u00fchren, w\u00e4hrend die Integration aktiv ist, und umgekehrt.", diff --git a/homeassistant/components/huawei_lte/translations/el.json b/homeassistant/components/huawei_lte/translations/el.json new file mode 100644 index 00000000000000..0957ca085d79e9 --- /dev/null +++ b/homeassistant/components/huawei_lte/translations/el.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b1\u03c5\u03b8\u03b5\u03bd\u03c4\u03b9\u03ba\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", + "unknown": "\u039c\u03b7 \u03b1\u03bd\u03b1\u03bc\u03b5\u03bd\u03cc\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huawei_lte/translations/en.json b/homeassistant/components/huawei_lte/translations/en.json index 022328ea2eaf6b..fa66aebeba80f6 100644 --- a/homeassistant/components/huawei_lte/translations/en.json +++ b/homeassistant/components/huawei_lte/translations/en.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "This device has already been configured", - "already_in_progress": "This device is already being configured", + "already_configured": "Device is already configured", + "already_in_progress": "Configuration flow is already in progress", "not_huawei_lte": "Not a Huawei LTE device" }, "error": { @@ -11,9 +11,11 @@ "incorrect_password": "Incorrect password", "incorrect_username": "Incorrect username", "incorrect_username_or_password": "Incorrect username or password", + "invalid_auth": "Invalid authentication", "invalid_url": "Invalid URL", "login_attempts_exceeded": "Maximum login attempts exceeded, please try again later", "response_error": "Unknown error from device", + "unknown": "Unexpected error", "unknown_connection_error": "Unknown error connecting to device" }, "flow_title": "Huawei LTE: {name}", diff --git a/homeassistant/components/huawei_lte/translations/es.json b/homeassistant/components/huawei_lte/translations/es.json index 268fdf8eff57fc..289bd7c534c564 100644 --- a/homeassistant/components/huawei_lte/translations/es.json +++ b/homeassistant/components/huawei_lte/translations/es.json @@ -11,9 +11,11 @@ "incorrect_password": "Contrase\u00f1a incorrecta", "incorrect_username": "Nombre de usuario incorrecto", "incorrect_username_or_password": "Nombre de usuario o contrase\u00f1a incorrectos", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", "invalid_url": "URL no v\u00e1lida", "login_attempts_exceeded": "Se han superado los intentos de inicio de sesi\u00f3n m\u00e1ximos, int\u00e9ntelo de nuevo m\u00e1s tarde.", "response_error": "Error desconocido del dispositivo", + "unknown": "Error inesperado", "unknown_connection_error": "Error desconocido al conectarse al dispositivo" }, "flow_title": "Huawei LTE: {name}", diff --git a/homeassistant/components/huawei_lte/translations/et.json b/homeassistant/components/huawei_lte/translations/et.json new file mode 100644 index 00000000000000..8c8a4497b99edf --- /dev/null +++ b/homeassistant/components/huawei_lte/translations/et.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "invalid_auth": "Tuvastamise viga", + "unknown": "Ootamatu t\u00f5rge" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/huawei_lte/translations/fr.json b/homeassistant/components/huawei_lte/translations/fr.json index 2c7437d9a144c1..0662843a918ba3 100644 --- a/homeassistant/components/huawei_lte/translations/fr.json +++ b/homeassistant/components/huawei_lte/translations/fr.json @@ -11,9 +11,11 @@ "incorrect_password": "Mot de passe incorrect", "incorrect_username": "Nom d'utilisateur incorrect", "incorrect_username_or_password": "identifiant ou mot de passe incorrect", + "invalid_auth": "Authentification invalide", "invalid_url": "URL invalide", "login_attempts_exceeded": "Nombre maximal de tentatives de connexion d\u00e9pass\u00e9, veuillez r\u00e9essayer ult\u00e9rieurement", "response_error": "Erreur inconnue de l'appareil", + "unknown": "Erreur inattendue", "unknown_connection_error": "Erreur inconnue lors de la connexion \u00e0 l'appareil" }, "flow_title": "Huawei LTE: {nom}", diff --git a/homeassistant/components/huawei_lte/translations/it.json b/homeassistant/components/huawei_lte/translations/it.json index 53a91afab06c14..cf6e97846d388b 100644 --- a/homeassistant/components/huawei_lte/translations/it.json +++ b/homeassistant/components/huawei_lte/translations/it.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Questo dispositivo \u00e8 gi\u00e0 stato configurato", - "already_in_progress": "Questo dispositivo \u00e8 gi\u00e0 in fase di configurazione", + "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": { @@ -11,9 +11,11 @@ "incorrect_password": "Password errata", "incorrect_username": "Nome utente errato", "incorrect_username_or_password": "Nome utente o password errati", + "invalid_auth": "Autenticazione non valida", "invalid_url": "URL non valido", "login_attempts_exceeded": "Superati i tentativi di accesso massimi, riprovare pi\u00f9 tardi", "response_error": "Errore sconosciuto dal dispositivo", + "unknown": "Errore imprevisto", "unknown_connection_error": "Errore sconosciuto durante la connessione al dispositivo" }, "flow_title": "Huawei LTE: {name}", diff --git a/homeassistant/components/huawei_lte/translations/no.json b/homeassistant/components/huawei_lte/translations/no.json index 710a318d9667f6..f33b88622786ac 100644 --- a/homeassistant/components/huawei_lte/translations/no.json +++ b/homeassistant/components/huawei_lte/translations/no.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Denne enheten er allerede konfigurert", - "already_in_progress": "Denne enheten blir allerede konfigurert", + "already_configured": "Enheten er allerede konfigurert", + "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", "not_huawei_lte": "Ikke en Huawei LTE-enhet" }, "error": { @@ -11,9 +11,11 @@ "incorrect_password": "Feil passord", "incorrect_username": "Feil brukernavn", "incorrect_username_or_password": "Feil brukernavn eller passord", + "invalid_auth": "Ugyldig godkjenning", "invalid_url": "Ugyldig URL-adresse", "login_attempts_exceeded": "Maksimalt antall p\u00e5loggingsfors\u00f8k er overskredet, vennligst pr\u00f8v igjen senere", "response_error": "Ukjent feil fra enheten", + "unknown": "Uventet feil", "unknown_connection_error": "Ukjent feil under tilkobling til enhet" }, "flow_title": "", diff --git a/homeassistant/components/huawei_lte/translations/pl.json b/homeassistant/components/huawei_lte/translations/pl.json index e38188d134f2f7..405ffdf0343d13 100644 --- a/homeassistant/components/huawei_lte/translations/pl.json +++ b/homeassistant/components/huawei_lte/translations/pl.json @@ -21,6 +21,7 @@ "user": { "data": { "password": "Has\u0142o", + "url": "URL", "username": "Nazwa u\u017cytkownika" }, "description": "Wprowad\u017a szczeg\u00f3\u0142y dost\u0119pu do urz\u0105dzenia. Okre\u015blenie nazwy u\u017cytkownika i has\u0142a jest opcjonalne, ale umo\u017cliwia obs\u0142ug\u0119 wi\u0119kszej liczby funkcji integracji. Z drugiej strony u\u017cycie autoryzowanego po\u0142\u0105czenia mo\u017ce powodowa\u0107 problemy z dost\u0119pem do interfejsu internetowego urz\u0105dzenia z zewn\u0105trz Home Assistanta gdy integracja jest aktywna.", diff --git a/homeassistant/components/huawei_lte/translations/ru.json b/homeassistant/components/huawei_lte/translations/ru.json index 5830cb8ccb3ac0..68c025894cd0c5 100644 --- a/homeassistant/components/huawei_lte/translations/ru.json +++ b/homeassistant/components/huawei_lte/translations/ru.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", - "already_in_progress": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "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": { @@ -11,9 +11,11 @@ "incorrect_password": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043f\u0430\u0440\u043e\u043b\u044c.", "incorrect_username": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043b\u043e\u0433\u0438\u043d.", "incorrect_username_or_password": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043b\u043e\u0433\u0438\u043d \u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c.", + "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "invalid_url": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 URL-\u0430\u0434\u0440\u0435\u0441.", "login_attempts_exceeded": "\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 \u0432\u0445\u043e\u0434\u0430, \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443 \u043f\u043e\u0437\u0436\u0435.", "response_error": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\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.", "unknown_connection_error": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443." }, "flow_title": "Huawei LTE: {name}", diff --git a/homeassistant/components/huawei_lte/translations/zh-Hant.json b/homeassistant/components/huawei_lte/translations/zh-Hant.json index 55cc0b6acf6ccd..7dcc0f8851dcc6 100644 --- a/homeassistant/components/huawei_lte/translations/zh-Hant.json +++ b/homeassistant/components/huawei_lte/translations/zh-Hant.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "\u6b64\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", - "already_in_progress": "\u6b64\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "not_huawei_lte": "\u4e26\u975e\u83ef\u70ba LTE \u8a2d\u5099" }, "error": { @@ -11,9 +11,11 @@ "incorrect_password": "\u5bc6\u78bc\u932f\u8aa4", "incorrect_username": "\u4f7f\u7528\u8005\u540d\u7a31\u932f\u8aa4", "incorrect_username_or_password": "\u4f7f\u7528\u8005\u540d\u7a31\u6216\u5bc6\u78bc\u932f\u8aa4", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "invalid_url": "\u7db2\u5740\u7121\u6548", "login_attempts_exceeded": "\u5df2\u9054\u5617\u8a66\u767b\u5165\u6700\u5927\u6b21\u6578\uff0c\u8acb\u7a0d\u5f8c\u518d\u8a66", "response_error": "\u4f86\u81ea\u8a2d\u5099\u672a\u77e5\u932f\u8aa4", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4", "unknown_connection_error": "\u9023\u7dda\u81f3\u8a2d\u5099\u672a\u77e5\u932f\u8aa4" }, "flow_title": "\u83ef\u70ba LTE\uff1a{name}", diff --git a/homeassistant/components/huawei_router/device_tracker.py b/homeassistant/components/huawei_router/device_tracker.py index be34b26be0d4b3..69278ed6574a88 100644 --- a/homeassistant/components/huawei_router/device_tracker.py +++ b/homeassistant/components/huawei_router/device_tracker.py @@ -50,7 +50,7 @@ class HuaweiDeviceScanner(DeviceScanner): '"(?P.*?)","(?P.*?)",' '"(?P.*?)"' ) - LOGIN_COOKIE = dict(Cookie="body:Language:portuguese:id=-1") + LOGIN_COOKIE = {"Cookie": "body:Language:portuguese:id=-1"} def __init__(self, config): """Initialize the scanner.""" diff --git a/homeassistant/components/hue/light.py b/homeassistant/components/hue/light.py index 20066bd1be2e2f..821d482ec25dd9 100644 --- a/homeassistant/components/hue/light.py +++ b/homeassistant/components/hue/light.py @@ -458,7 +458,6 @@ async def async_turn_off(self, **kwargs): @property def device_state_attributes(self): """Return the device state attributes.""" - attributes = {} - if self.is_group: - attributes[ATTR_IS_HUE_GROUP] = self.is_group - return attributes + if not self.is_group: + return {} + return {ATTR_IS_HUE_GROUP: self.is_group} diff --git a/homeassistant/components/hue/sensor.py b/homeassistant/components/hue/sensor.py index e96f844a5e10c9..f5911bbb50c7d3 100644 --- a/homeassistant/components/hue/sensor.py +++ b/homeassistant/components/hue/sensor.py @@ -10,6 +10,7 @@ DEVICE_CLASS_BATTERY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, + LIGHT_LUX, PERCENTAGE, TEMP_CELSIUS, ) @@ -41,7 +42,7 @@ class HueLightLevel(GenericHueGaugeSensorEntity): """The light level sensor entity for a Hue motion sensor device.""" device_class = DEVICE_CLASS_ILLUMINANCE - unit_of_measurement = "lx" + unit_of_measurement = LIGHT_LUX @property def state(self): diff --git a/homeassistant/components/hue/strings.json b/homeassistant/components/hue/strings.json index a16d5e3a632719..678b7c2cad24a8 100644 --- a/homeassistant/components/hue/strings.json +++ b/homeassistant/components/hue/strings.json @@ -20,16 +20,16 @@ }, "error": { "register_failed": "Failed to register, please try again", - "linking": "Unknown linking error occurred." + "linking": "[%key:common::config_flow::error::unknown%]" }, "abort": { "discover_timeout": "Unable to discover Hue bridges", "no_bridges": "No Philips Hue bridges discovered", "all_configured": "All Philips Hue bridges are already configured", - "unknown": "Unknown error occurred", - "cannot_connect": "Unable to connect to the bridge", - "already_configured": "Bridge is already configured", - "already_in_progress": "Config flow for bridge is already in progress.", + "unknown": "[%key:common::config_flow::error::unknown%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", "not_hue_bridge": "Not a Hue bridge" } }, diff --git a/homeassistant/components/hue/translations/ca.json b/homeassistant/components/hue/translations/ca.json index 97098b21db7768..47bb10b2abbdd5 100644 --- a/homeassistant/components/hue/translations/ca.json +++ b/homeassistant/components/hue/translations/ca.json @@ -2,16 +2,16 @@ "config": { "abort": { "all_configured": "Tots els enlla\u00e7os Philips Hue ja estan configurats", - "already_configured": "L'enlla\u00e7 ja est\u00e0 configurat", - "already_in_progress": "El flux de dades de configuraci\u00f3 per l'enlla\u00e7 ja est\u00e0 en curs.", - "cannot_connect": "No s'ha pogut connectar amb l'enlla\u00e7", + "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", "discover_timeout": "No s'han pogut descobrir enlla\u00e7os Hue", "no_bridges": "No s'han trobat enlla\u00e7os Philips Hue", "not_hue_bridge": "No \u00e9s un enlla\u00e7 Hue", - "unknown": "S'ha produ\u00eft un error desconegut" + "unknown": "Error inesperat" }, "error": { - "linking": "S'ha produ\u00eft un error desconegut al vincular.", + "linking": "Error inesperat", "register_failed": "No s'ha pogut registrar, torna-ho a provar" }, "step": { diff --git a/homeassistant/components/hue/translations/de.json b/homeassistant/components/hue/translations/de.json index c9c8c96f4d559d..0defb33ae5e509 100644 --- a/homeassistant/components/hue/translations/de.json +++ b/homeassistant/components/hue/translations/de.json @@ -26,6 +26,9 @@ "title": "Hub verbinden" }, "manual": { + "data": { + "host": "Host" + }, "title": "Manuelles Konfigurieren einer Hue Bridge" } } diff --git a/homeassistant/components/hue/translations/en.json b/homeassistant/components/hue/translations/en.json index 9f3a96f2da3580..e03eabd3d2361c 100644 --- a/homeassistant/components/hue/translations/en.json +++ b/homeassistant/components/hue/translations/en.json @@ -2,16 +2,16 @@ "config": { "abort": { "all_configured": "All Philips Hue bridges are already configured", - "already_configured": "Bridge is already configured", - "already_in_progress": "Config flow for bridge is already in progress.", - "cannot_connect": "Unable to connect to the bridge", + "already_configured": "Device is already configured", + "already_in_progress": "Configuration flow is already in progress", + "cannot_connect": "Failed to connect", "discover_timeout": "Unable to discover Hue bridges", "no_bridges": "No Philips Hue bridges discovered", "not_hue_bridge": "Not a Hue bridge", - "unknown": "Unknown error occurred" + "unknown": "Unexpected error" }, "error": { - "linking": "Unknown linking error occurred.", + "linking": "Unexpected error", "register_failed": "Failed to register, please try again" }, "step": { diff --git a/homeassistant/components/hue/translations/et.json b/homeassistant/components/hue/translations/et.json index 92553c84cfe22d..0aeea7286d9ea0 100644 --- a/homeassistant/components/hue/translations/et.json +++ b/homeassistant/components/hue/translations/et.json @@ -1,14 +1,46 @@ { "config": { "abort": { + "all_configured": "K\u00f5ik Philips Hue sillad on juba konfigureeritud", + "discover_timeout": "Ei leia Philips Hue sildu", + "no_bridges": "Philips Hue sildu ei avastatud", "unknown": "Ilmnes tundmatu viga" }, + "error": { + "linking": "Ilmnes tundmatu linkimist\u00f5rge.", + "register_failed": "Registreerimine nurjus. Proovige uuesti" + }, "step": { "init": { "data": { "host": "" - } + }, + "title": "Valige Hue sild" + }, + "link": { + "description": "Vajutage silla nuppu, et registreerida Philips Hue Home Assistant abil. \n\n ! [Nupu asukoht sillal] (/ static / images / config_philips_hue.jpg)" } } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "Esimene nupp", + "button_2": "Teine nupp", + "button_3": "Kolmas nupp", + "button_4": "Neljas nupp", + "dim_down": "H\u00e4marda", + "dim_up": "Tee heledamaks", + "double_buttons_1_3": "Esimene ja kolmas nupp", + "double_buttons_2_4": "Teine ja neljas nupp", + "turn_off": "L\u00fclita v\u00e4lja", + "turn_on": "L\u00fclita sisse" + }, + "trigger_type": { + "remote_button_long_release": "\"{subtype}\" nupp vabastatati p\u00e4rast pikka vajutust", + "remote_button_short_press": "\"{subtype}\" nupp on vajutatud", + "remote_button_short_release": "\"{subtype}\" nupp vabastati", + "remote_double_button_long_press": "M\u00f5lemad \"{subtype}\" nupud vabastatati p\u00e4rast pikka vajutust", + "remote_double_button_short_press": "M\u00f5lemad \"{subtype}\" nupud vabastatati" + } } } \ No newline at end of file diff --git a/homeassistant/components/hue/translations/fr.json b/homeassistant/components/hue/translations/fr.json index 99e82f1a89b506..f19c5ec7a34b71 100644 --- a/homeassistant/components/hue/translations/fr.json +++ b/homeassistant/components/hue/translations/fr.json @@ -49,7 +49,9 @@ "trigger_type": { "remote_button_long_release": "Bouton \" {subtype} \" rel\u00e2ch\u00e9 apr\u00e8s un appui long", "remote_button_short_press": "bouton \"{subtype}\" est press\u00e9", - "remote_button_short_release": "Bouton \" {subtype} \" est rel\u00e2ch\u00e9" + "remote_button_short_release": "Bouton \" {subtype} \" est rel\u00e2ch\u00e9", + "remote_double_button_long_press": "Les deux \"{sous-type}\" ont \u00e9t\u00e9 rel\u00e2ch\u00e9s apr\u00e8s un appui long", + "remote_double_button_short_press": "Les deux \" {subtype} \" ont \u00e9t\u00e9 rel\u00e2ch\u00e9s" } }, "options": { diff --git a/homeassistant/components/hue/translations/it.json b/homeassistant/components/hue/translations/it.json index 6fc4554b384270..f49e64b9b685ef 100644 --- a/homeassistant/components/hue/translations/it.json +++ b/homeassistant/components/hue/translations/it.json @@ -2,16 +2,16 @@ "config": { "abort": { "all_configured": "Tutti i bridge di Philips Hue sono gi\u00e0 configurati", - "already_configured": "Il bridge \u00e8 gi\u00e0 configurato", - "already_in_progress": "Il flusso di configurazione per bridge \u00e8 gi\u00e0 in corso.", - "cannot_connect": "Impossibile connettersi al bridge", + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", + "cannot_connect": "Impossibile connettersi", "discover_timeout": "Impossibile trovare i bridge Hue", "no_bridges": "Nessun bridge di Philips Hue trovato", "not_hue_bridge": "Non \u00e8 un bridge Hue", - "unknown": "Si \u00e8 verificato un errore" + "unknown": "Errore imprevisto" }, "error": { - "linking": "Si \u00e8 verificato un errore sconosciuto in fase di collegamento.", + "linking": "Errore imprevisto", "register_failed": "Errore in fase di registrazione, riprova" }, "step": { diff --git a/homeassistant/components/hue/translations/no.json b/homeassistant/components/hue/translations/no.json index 2d51ee26452df7..1b699c6e8850df 100644 --- a/homeassistant/components/hue/translations/no.json +++ b/homeassistant/components/hue/translations/no.json @@ -3,7 +3,7 @@ "abort": { "all_configured": "Alle Philips Hue Bridger er allerede konfigurert", "already_configured": "Bridge er allerede konfigurert", - "already_in_progress": "Konfigurasjonsflyt for bro p\u00e5g\u00e5r allerede.", + "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", "cannot_connect": "Kan ikke koble til Bridge", "discover_timeout": "Kunne ikke oppdage Hue Bridger", "no_bridges": "Ingen Philips Hue Bridger oppdaget", diff --git a/homeassistant/components/hue/translations/pl.json b/homeassistant/components/hue/translations/pl.json index 02dad0c3e5276b..6e2623d23ac380 100644 --- a/homeassistant/components/hue/translations/pl.json +++ b/homeassistant/components/hue/translations/pl.json @@ -1,18 +1,18 @@ { "config": { "abort": { - "all_configured": "Wszystkie mostki Hue s\u0105 ju\u017c skonfigurowane.", - "already_configured": "Mostek jest ju\u017c skonfigurowany.", + "all_configured": "Wszystkie mostki Hue s\u0105 ju\u017c skonfigurowane", + "already_configured": "Mostek jest ju\u017c skonfigurowany", "already_in_progress": "Konfiguracja mostka jest ju\u017c w toku.", "cannot_connect": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z mostkiem", "discover_timeout": "Nie mo\u017cna wykry\u0107 \u017cadnych mostk\u00f3w Hue", - "no_bridges": "Nie wykryto mostk\u00f3w Hue.", + "no_bridges": "Nie wykryto mostk\u00f3w Hue", "not_hue_bridge": "To nie jest mostek Hue", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "error": { "linking": "Wyst\u0105pi\u0142 nieznany b\u0142\u0105d w trakcie \u0142\u0105czenia.", - "register_failed": "Nie uda\u0142o si\u0119 zarejestrowa\u0107. Spr\u00f3buj ponownie." + "register_failed": "Nie uda\u0142o si\u0119 zarejestrowa\u0107. Spr\u00f3buj ponownie" }, "step": { "init": { diff --git a/homeassistant/components/hue/translations/ru.json b/homeassistant/components/hue/translations/ru.json index f302eeaa4732c5..d33a23e6179f3a 100644 --- a/homeassistant/components/hue/translations/ru.json +++ b/homeassistant/components/hue/translations/ru.json @@ -3,7 +3,7 @@ "abort": { "all_configured": "\u0412\u0441\u0435 Philips Hue \u0448\u043b\u044e\u0437\u044b \u0443\u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u044b.", "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", - "already_in_progress": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.", + "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 \u043a \u0448\u043b\u044e\u0437\u0443.", "discover_timeout": "\u0428\u043b\u044e\u0437 Philips Hue \u043d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d.", "no_bridges": "\u0428\u043b\u044e\u0437\u044b Philips Hue \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b.", diff --git a/homeassistant/components/hue/translations/zh-Hant.json b/homeassistant/components/hue/translations/zh-Hant.json index 2442ae30d10dd6..be69584951f8c8 100644 --- a/homeassistant/components/hue/translations/zh-Hant.json +++ b/homeassistant/components/hue/translations/zh-Hant.json @@ -3,7 +3,7 @@ "abort": { "all_configured": "\u6240\u6709 Philips Hue Bridge \u7686\u5df2\u8a2d\u5b9a\u5b8c\u6210", "already_configured": "Bridge \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", - "already_in_progress": "Bridge \u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d\u3002", + "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "cannot_connect": "\u7121\u6cd5\u9023\u7dda\u81f3 Bridge", "discover_timeout": "\u7121\u6cd5\u641c\u5c0b\u5230 Hue Bridge", "no_bridges": "\u672a\u641c\u5c0b\u5230 Philips Hue Bridge", diff --git a/homeassistant/components/humidifier/device_action.py b/homeassistant/components/humidifier/device_action.py index a6194994a9c03e..6bccd375207cc2 100644 --- a/homeassistant/components/humidifier/device_action.py +++ b/homeassistant/components/humidifier/device_action.py @@ -6,6 +6,7 @@ from homeassistant.components.device_automation import toggle_entity from homeassistant.const import ( ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, CONF_DEVICE_ID, CONF_DOMAIN, CONF_ENTITY_ID, @@ -63,7 +64,7 @@ async def async_get_actions(hass: HomeAssistant, device_id: str) -> List[dict]: if state is None: continue - if state.attributes["supported_features"] & const.SUPPORT_MODES: + if state.attributes[ATTR_SUPPORTED_FEATURES] & const.SUPPORT_MODES: actions.append( { CONF_DEVICE_ID: device_id, diff --git a/homeassistant/components/humidifier/device_condition.py b/homeassistant/components/humidifier/device_condition.py index 7f37fc3b1fa533..714a51ab0161dc 100644 --- a/homeassistant/components/humidifier/device_condition.py +++ b/homeassistant/components/humidifier/device_condition.py @@ -6,6 +6,7 @@ from homeassistant.components.device_automation import toggle_entity from homeassistant.const import ( ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, CONF_CONDITION, CONF_DEVICE_ID, CONF_DOMAIN, @@ -48,7 +49,7 @@ async def async_get_conditions( state = hass.states.get(entry.entity_id) - if state and state.attributes["supported_features"] & const.SUPPORT_MODES: + if state and state.attributes[ATTR_SUPPORTED_FEATURES] & const.SUPPORT_MODES: conditions.append( { CONF_CONDITION: "device", diff --git a/homeassistant/components/humidifier/group.py b/homeassistant/components/humidifier/group.py new file mode 100644 index 00000000000000..1636054663dc69 --- /dev/null +++ b/homeassistant/components/humidifier/group.py @@ -0,0 +1,15 @@ +"""Describe group states.""" + + +from homeassistant.components.group import GroupIntegrationRegistry +from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.core import callback +from homeassistant.helpers.typing import HomeAssistantType + + +@callback +def async_describe_on_off_states( + hass: HomeAssistantType, registry: GroupIntegrationRegistry +) -> None: + """Describe group on off states.""" + registry.on_off_states({STATE_ON}, STATE_OFF) diff --git a/homeassistant/components/humidifier/translations/et.json b/homeassistant/components/humidifier/translations/et.json new file mode 100644 index 00000000000000..303edb781b68a1 --- /dev/null +++ b/homeassistant/components/humidifier/translations/et.json @@ -0,0 +1,21 @@ +{ + "device_automation": { + "action_type": { + "set_humidity": "M\u00e4\u00e4ra {entity_name} niiskus", + "set_mode": "Muuda {entity_name} t\u00f6\u00f6re\u017eiimi", + "toggle": "Muuda {entity_name} olekut", + "turn_off": "L\u00fclita {entity_name} v\u00e4lja", + "turn_on": "L\u00fclita {entity_name} sisse" + }, + "condition_type": { + "is_mode": "{entity_name} on seatud kindlale t\u00f6\u00f6re\u017eiimile", + "is_off": "{entity_name} on v\u00e4lja l\u00fclitatud", + "is_on": "{entity_name} on sisse l\u00fclitatud" + }, + "trigger_type": { + "target_humidity_changed": "{entity_name} eelseatud niiskus muutus", + "turned_off": "{entity_name} l\u00fclitus v\u00e4lja", + "turned_on": "{entity_name} l\u00fclitus sisse" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/humidifier/translations/fr.json b/homeassistant/components/humidifier/translations/fr.json index 4b680bdba7fdbe..236c3b93343e68 100644 --- a/homeassistant/components/humidifier/translations/fr.json +++ b/homeassistant/components/humidifier/translations/fr.json @@ -8,10 +8,12 @@ "turn_on": "Allumer {entity_name}" }, "condition_type": { + "is_mode": "{entity_name} est d\u00e9fini sur un mode sp\u00e9cifique", "is_off": "{entity_name} est d\u00e9sactiv\u00e9", "is_on": "{entity_name} est activ\u00e9" }, "trigger_type": { + "target_humidity_changed": "{nom_de_l'entit\u00e9} changement de l'humidit\u00e9 cible", "turned_off": "{entity_name} s'est \u00e9teint", "turned_on": "{entity_name} s'est allum\u00e9" } diff --git a/homeassistant/components/humidifier/translations/uk.json b/homeassistant/components/humidifier/translations/uk.json new file mode 100644 index 00000000000000..4081c4e13fc281 --- /dev/null +++ b/homeassistant/components/humidifier/translations/uk.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "trigger_type": { + "turned_off": "{entity_name} \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e", + "turned_on": "{entity_name} \u0443\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u043e" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hunterdouglas_powerview/strings.json b/homeassistant/components/hunterdouglas_powerview/strings.json index 4cba22b60fb4b2..5d0b042454d7d9 100644 --- a/homeassistant/components/hunterdouglas_powerview/strings.json +++ b/homeassistant/components/hunterdouglas_powerview/strings.json @@ -14,11 +14,11 @@ } }, "error": { - "cannot_connect": "Failed to connect, please try again", - "unknown": "Unexpected error" + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { - "already_configured": "Device is already configured" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } } } diff --git a/homeassistant/components/hunterdouglas_powerview/translations/et.json b/homeassistant/components/hunterdouglas_powerview/translations/et.json new file mode 100644 index 00000000000000..0d70cd06fcaa70 --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/translations/et.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00dchenduse loomine nurjus. Proovi uuesti", + "unknown": "Ootamatu t\u00f5rge" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hunterdouglas_powerview/translations/fr.json b/homeassistant/components/hunterdouglas_powerview/translations/fr.json index a1bd06078c6e15..e5208ebdd689f5 100644 --- a/homeassistant/components/hunterdouglas_powerview/translations/fr.json +++ b/homeassistant/components/hunterdouglas_powerview/translations/fr.json @@ -19,5 +19,6 @@ "title": "Connectez-vous au concentrateur PowerView" } } - } + }, + "title": "Hunter Douglas PowerView" } \ No newline at end of file diff --git a/homeassistant/components/hunterdouglas_powerview/translations/pl.json b/homeassistant/components/hunterdouglas_powerview/translations/pl.json index cad41869ced4e9..87d7b8a915e5ed 100644 --- a/homeassistant/components/hunterdouglas_powerview/translations/pl.json +++ b/homeassistant/components/hunterdouglas_powerview/translations/pl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "link": { diff --git a/homeassistant/components/hvv_departures/__init__.py b/homeassistant/components/hvv_departures/__init__.py index 853ed9460c8c20..e003b25ea85a50 100644 --- a/homeassistant/components/hvv_departures/__init__.py +++ b/homeassistant/components/hvv_departures/__init__.py @@ -1,6 +1,7 @@ """The HVV integration.""" import asyncio +from homeassistant.components.binary_sensor import DOMAIN as DOMAIN_BINARY_SENSOR from homeassistant.components.sensor import DOMAIN as DOMAIN_SENSOR from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME @@ -10,7 +11,7 @@ from .const import DOMAIN from .hub import GTIHub -PLATFORMS = [DOMAIN_SENSOR] +PLATFORMS = [DOMAIN_SENSOR, DOMAIN_BINARY_SENSOR] async def async_setup(hass: HomeAssistant, config: dict): diff --git a/homeassistant/components/hvv_departures/binary_sensor.py b/homeassistant/components/hvv_departures/binary_sensor.py new file mode 100644 index 00000000000000..7d19fcc8fdfcc1 --- /dev/null +++ b/homeassistant/components/hvv_departures/binary_sensor.py @@ -0,0 +1,201 @@ +"""Binary sensor platform for hvv_departures.""" +from datetime import timedelta +import logging + +from aiohttp import ClientConnectorError +import async_timeout +from pygti.exceptions import InvalidAuth + +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_PROBLEM, + BinarySensorEntity, +) +from homeassistant.const import ATTR_ATTRIBUTION +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, + UpdateFailed, +) + +from .const import ATTRIBUTION, CONF_STATION, DOMAIN, MANUFACTURER + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, entry, async_add_entities): + """Set up the binary_sensor platform.""" + hub = hass.data[DOMAIN][entry.entry_id] + station_name = entry.data[CONF_STATION]["name"] + station = entry.data[CONF_STATION] + + def get_elevator_entities_from_station_information( + station_name, station_information + ): + """Convert station information into a list of elevators.""" + elevators = {} + + if station_information is None: + return {} + + for partial_station in station_information.get("partialStations", []): + for elevator in partial_station.get("elevators", []): + + state = elevator.get("state") != "READY" + available = elevator.get("state") != "UNKNOWN" + label = elevator.get("label") + description = elevator.get("description") + + if label is not None: + name = f"Elevator {label} at {station_name}" + else: + name = f"Unknown elevator at {station_name}" + + if description is not None: + name += f" ({description})" + + lines = elevator.get("lines") + + idx = f"{station_name}-{label}-{lines}" + + elevators[idx] = { + "state": state, + "name": name, + "available": available, + "attributes": { + "cabin_width": elevator.get("cabinWidth"), + "cabin_length": elevator.get("cabinLength"), + "door_width": elevator.get("doorWidth"), + "elevator_type": elevator.get("elevatorType"), + "button_type": elevator.get("buttonType"), + "cause": elevator.get("cause"), + "lines": lines, + ATTR_ATTRIBUTION: ATTRIBUTION, + }, + } + return elevators + + async def async_update_data(): + """Fetch data from API endpoint. + + This is the place to pre-process the data to lookup tables + so entities can quickly look up their data. + """ + + payload = {"station": station} + + try: + async with async_timeout.timeout(10): + return get_elevator_entities_from_station_information( + station_name, await hub.gti.stationInformation(payload) + ) + except InvalidAuth as err: + raise UpdateFailed(f"Authentication failed: {err}") from err + except ClientConnectorError as err: + raise UpdateFailed(f"Network not available: {err}") from err + except Exception as err: # pylint: disable=broad-except + raise UpdateFailed(f"Error occurred while fetching data: {err}") from err + + coordinator = DataUpdateCoordinator( + hass, + _LOGGER, + # Name of the data. For logging purposes. + name="hvv_departures.binary_sensor", + update_method=async_update_data, + # Polling interval. Will only be polled if there are subscribers. + update_interval=timedelta(hours=1), + ) + + # Fetch initial data so we have data when entities subscribe + await coordinator.async_refresh() + + async_add_entities( + HvvDepartureBinarySensor(coordinator, idx, entry) + for (idx, ent) in coordinator.data.items() + ) + + +class HvvDepartureBinarySensor(CoordinatorEntity, BinarySensorEntity): + """HVVDepartureBinarySensor class.""" + + def __init__(self, coordinator, idx, config_entry): + """Initialize.""" + super().__init__(coordinator) + self.coordinator = coordinator + self.idx = idx + self.config_entry = config_entry + + @property + def is_on(self): + """Return entity state.""" + return self.coordinator.data[self.idx]["state"] + + @property + def should_poll(self): + """No need to poll. Coordinator notifies entity of updates.""" + return False + + @property + def available(self): + """Return if entity is available.""" + return ( + self.coordinator.last_update_success + and self.coordinator.data[self.idx]["available"] + ) + + @property + def device_info(self): + """Return the device info for this sensor.""" + return { + "identifiers": { + ( + DOMAIN, + self.config_entry.entry_id, + self.config_entry.data[CONF_STATION]["id"], + self.config_entry.data[CONF_STATION]["type"], + ) + }, + "name": f"Departures at {self.config_entry.data[CONF_STATION]['name']}", + "manufacturer": MANUFACTURER, + } + + @property + def name(self): + """Return the name of the sensor.""" + return self.coordinator.data[self.idx]["name"] + + @property + def unique_id(self): + """Return a unique ID to use for this sensor.""" + return self.idx + + @property + def device_class(self): + """Return the class of this device, from component DEVICE_CLASSES.""" + return DEVICE_CLASS_PROBLEM + + @property + def device_state_attributes(self): + """Return the state attributes.""" + if not ( + self.coordinator.last_update_success + and self.coordinator.data[self.idx]["available"] + ): + return None + return { + k: v + for k, v in self.coordinator.data[self.idx]["attributes"].items() + if v is not None + } + + async def async_added_to_hass(self): + """When entity is added to hass.""" + self.async_on_remove( + self.coordinator.async_add_listener(self.async_write_ha_state) + ) + + async def async_update(self): + """Update the entity. + + Only used by the generic entity update service. + """ + await self.coordinator.async_request_refresh() diff --git a/homeassistant/components/hvv_departures/strings.json b/homeassistant/components/hvv_departures/strings.json index dfd6484f7d8933..c29ad6cc69411c 100644 --- a/homeassistant/components/hvv_departures/strings.json +++ b/homeassistant/components/hvv_departures/strings.json @@ -5,9 +5,9 @@ "user": { "title": "Connect to the HVV API", "data": { - "host": "Host", - "username": "Username", - "password": "Password" + "host": "[%key:common::config_flow::data::host%]", + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]" } }, "station": { @@ -24,12 +24,12 @@ } }, "error": { - "cannot_connect": "Failed to connect, please try again", - "invalid_auth": "Invalid authentication", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", "no_results": "No results. Try with a different station/address" }, "abort": { - "already_configured": "Device is already configured" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } }, "options": { diff --git a/homeassistant/components/hvv_departures/translations/ca.json b/homeassistant/components/hvv_departures/translations/ca.json index 4b295512febdbb..7a6025459c95ca 100644 --- a/homeassistant/components/hvv_departures/translations/ca.json +++ b/homeassistant/components/hvv_departures/translations/ca.json @@ -4,7 +4,7 @@ "already_configured": "El dispositiu ja est\u00e0 configurat" }, "error": { - "cannot_connect": "No s'ha pogut connectar, torna-ho a provar", + "cannot_connect": "Ha fallat la connexi\u00f3", "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "no_results": "Sense resultats. Prova-ho amb una altra estaci\u00f3/adre\u00e7a" }, diff --git a/homeassistant/components/hvv_departures/translations/de.json b/homeassistant/components/hvv_departures/translations/de.json new file mode 100644 index 00000000000000..b383e57bd93fa3 --- /dev/null +++ b/homeassistant/components/hvv_departures/translations/de.json @@ -0,0 +1,48 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + }, + "error": { + "cannot_connect": "Verbindung fehlgeschlagen, bitte erneut versuchen", + "invalid_auth": "Ung\u00fcltige Authentifizierung", + "no_results": "Keine Ergebnisse. Versuch es mit einer anderen Station/Adresse" + }, + "step": { + "station": { + "data": { + "station": "Station/Adresse" + }, + "title": "Station/Adresse eingeben" + }, + "station_select": { + "data": { + "station": "Station/Adresse" + }, + "title": "Station/Adresse ausw\u00e4hlen" + }, + "user": { + "data": { + "host": "Host", + "password": "Passwort", + "username": "Benutzername" + }, + "title": "Mit der HVV-API verbinden" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "filter": "Linien ausw\u00e4hlen", + "offset": "Versatz (Minuten)", + "real_time": "Echtzeitdaten verwenden" + }, + "description": "Optionen f\u00fcr diesen Abfahrtssensor \u00e4ndern", + "title": "Optionen" + } + } + }, + "title": "HVV Abfahrten" +} \ No newline at end of file diff --git a/homeassistant/components/hvv_departures/translations/en.json b/homeassistant/components/hvv_departures/translations/en.json index ede3ece2f4a575..846457196e5a54 100644 --- a/homeassistant/components/hvv_departures/translations/en.json +++ b/homeassistant/components/hvv_departures/translations/en.json @@ -4,7 +4,7 @@ "already_configured": "Device is already configured" }, "error": { - "cannot_connect": "Failed to connect, please try again", + "cannot_connect": "Failed to connect", "invalid_auth": "Invalid authentication", "no_results": "No results. Try with a different station/address" }, diff --git a/homeassistant/components/hvv_departures/translations/fr.json b/homeassistant/components/hvv_departures/translations/fr.json index afc67b1087d3e2..e6560da40471e7 100644 --- a/homeassistant/components/hvv_departures/translations/fr.json +++ b/homeassistant/components/hvv_departures/translations/fr.json @@ -35,11 +35,14 @@ "step": { "init": { "data": { + "filter": "S\u00e9lectionnez des lignes", "offset": "D\u00e9calage (minutes)", "real_time": "Utiliser des donn\u00e9es en temps r\u00e9el" }, + "description": "Modifier les options de ce capteur de d\u00e9part", "title": "Options" } } - } + }, + "title": "D\u00e9parts HVV" } \ No newline at end of file diff --git a/homeassistant/components/hvv_departures/translations/it.json b/homeassistant/components/hvv_departures/translations/it.json index f1dd507a0d5c44..2b9419f70cc18c 100644 --- a/homeassistant/components/hvv_departures/translations/it.json +++ b/homeassistant/components/hvv_departures/translations/it.json @@ -4,7 +4,7 @@ "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" }, "error": { - "cannot_connect": "Impossibile connettersi, si prega di riprovare", + "cannot_connect": "Impossibile connettersi", "invalid_auth": "Autenticazione non valida", "no_results": "Nessun risultato. Prova con un'altra stazione/indirizzo" }, diff --git a/homeassistant/components/hvv_departures/translations/nl.json b/homeassistant/components/hvv_departures/translations/nl.json new file mode 100644 index 00000000000000..4d00f0bfc74883 --- /dev/null +++ b/homeassistant/components/hvv_departures/translations/nl.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Gebruikersnaam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/hvv_departures/translations/no.json b/homeassistant/components/hvv_departures/translations/no.json index 52b91ef31d40dd..7d2ca8c5879650 100644 --- a/homeassistant/components/hvv_departures/translations/no.json +++ b/homeassistant/components/hvv_departures/translations/no.json @@ -4,7 +4,7 @@ "already_configured": "Enheten er allerede konfigurert" }, "error": { - "cannot_connect": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen", + "cannot_connect": "Tilkobling mislyktes.", "invalid_auth": "Ugyldig godkjenning", "no_results": "Ingen resultater. Pr\u00f8v med en annen stasjon/adresse" }, diff --git a/homeassistant/components/hvv_departures/translations/pl.json b/homeassistant/components/hvv_departures/translations/pl.json index 5bf87fc08a8327..7ea22e48d5490f 100644 --- a/homeassistant/components/hvv_departures/translations/pl.json +++ b/homeassistant/components/hvv_departures/translations/pl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" }, "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", + "invalid_auth": "Niepoprawne uwierzytelnienie", "no_results": "Brak wynik\u00f3w. Spr\u00f3buj z inn\u0105 stacj\u0105/adresem." }, "step": { diff --git a/homeassistant/components/hvv_departures/translations/ru.json b/homeassistant/components/hvv_departures/translations/ru.json index b83981ae76ffa7..354faf167a72de 100644 --- a/homeassistant/components/hvv_departures/translations/ru.json +++ b/homeassistant/components/hvv_departures/translations/ru.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", + "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "no_results": "\u041d\u0435\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432. \u041f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0441 \u0434\u0440\u0443\u0433\u043e\u0439 \u0441\u0442\u0430\u043d\u0446\u0438\u0435\u0439 / \u0430\u0434\u0440\u0435\u0441\u043e\u043c." }, diff --git a/homeassistant/components/hvv_departures/translations/zh-Hant.json b/homeassistant/components/hvv_departures/translations/zh-Hant.json index ee22830c030400..5859493eeab6e8 100644 --- a/homeassistant/components/hvv_departures/translations/zh-Hant.json +++ b/homeassistant/components/hvv_departures/translations/zh-Hant.json @@ -4,7 +4,7 @@ "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21", + "cannot_connect": "\u9023\u7dda\u5931\u6557", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "no_results": "\u6c92\u6709\u7d50\u679c\u3002\u8acb\u5617\u8a66\u5176\u4ed6\u8eca\u7ad9/\u5730\u5740" }, diff --git a/homeassistant/components/hyperion/light.py b/homeassistant/components/hyperion/light.py index d1baec315bf326..db34a21dadab22 100644 --- a/homeassistant/components/hyperion/light.py +++ b/homeassistant/components/hyperion/light.py @@ -1,8 +1,7 @@ -"""Support for Hyperion remotes.""" -import json +"""Support for Hyperion-NG remotes.""" import logging -import socket +from hyperion import client, const import voluptuous as vol from homeassistant.components.light import ( @@ -16,6 +15,7 @@ LightEntity, ) from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT +from homeassistant.exceptions import PlatformNotReady import homeassistant.helpers.config_validation as cv import homeassistant.util.color as color_util @@ -26,103 +26,91 @@ CONF_HDMI_PRIORITY = "hdmi_priority" CONF_EFFECT_LIST = "effect_list" +# As we want to preserve brightness control for effects (e.g. to reduce the +# brightness for V4L), we need to persist the effect that is in flight, so +# subsequent calls to turn_on will know the keep the effect enabled. +# Unfortunately the Home Assistant UI does not easily expose a way to remove a +# selected effect (there is no 'No Effect' option by default). Instead, we +# create a new fake effect ("Solid") that is always selected by default for +# showing a solid color. This is the same method used by WLED. +KEY_EFFECT_SOLID = "Solid" + DEFAULT_COLOR = [255, 255, 255] +DEFAULT_BRIGHTNESS = 255 +DEFAULT_EFFECT = KEY_EFFECT_SOLID DEFAULT_NAME = "Hyperion" +DEFAULT_ORIGIN = "Home Assistant" DEFAULT_PORT = 19444 DEFAULT_PRIORITY = 128 DEFAULT_HDMI_PRIORITY = 880 -DEFAULT_EFFECT_LIST = [ - "HDMI", - "Cinema brighten lights", - "Cinema dim lights", - "Knight rider", - "Blue mood blobs", - "Cold mood blobs", - "Full color mood blobs", - "Green mood blobs", - "Red mood blobs", - "Warm mood blobs", - "Police Lights Single", - "Police Lights Solid", - "Rainbow mood", - "Rainbow swirl fast", - "Rainbow swirl", - "Random", - "Running dots", - "System Shutdown", - "Snake", - "Sparks Color", - "Sparks", - "Strobe blue", - "Strobe Raspbmc", - "Strobe white", - "Color traces", - "UDP multicast listener", - "UDP listener", - "X-Mas", -] +DEFAULT_EFFECT_LIST = [] SUPPORT_HYPERION = SUPPORT_COLOR | SUPPORT_BRIGHTNESS | SUPPORT_EFFECT -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_DEFAULT_COLOR, default=DEFAULT_COLOR): vol.All( - list, - vol.Length(min=3, max=3), - [vol.All(vol.Coerce(int), vol.Range(min=0, max=255))], - ), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PRIORITY, default=DEFAULT_PRIORITY): cv.positive_int, - vol.Optional( - CONF_HDMI_PRIORITY, default=DEFAULT_HDMI_PRIORITY - ): cv.positive_int, - vol.Optional(CONF_EFFECT_LIST, default=DEFAULT_EFFECT_LIST): vol.All( - cv.ensure_list, [cv.string] - ), - } +PLATFORM_SCHEMA = vol.All( + cv.deprecated(CONF_HDMI_PRIORITY, invalidation_version="0.118"), + cv.deprecated(CONF_DEFAULT_COLOR, invalidation_version="0.118"), + cv.deprecated(CONF_EFFECT_LIST, invalidation_version="0.118"), + PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_DEFAULT_COLOR, default=DEFAULT_COLOR): vol.All( + list, + vol.Length(min=3, max=3), + [vol.All(vol.Coerce(int), vol.Range(min=0, max=255))], + ), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PRIORITY, default=DEFAULT_PRIORITY): cv.positive_int, + vol.Optional( + CONF_HDMI_PRIORITY, default=DEFAULT_HDMI_PRIORITY + ): cv.positive_int, + vol.Optional(CONF_EFFECT_LIST, default=DEFAULT_EFFECT_LIST): vol.All( + cv.ensure_list, [cv.string] + ), + } + ), ) +ICON_LIGHTBULB = "mdi:lightbulb" +ICON_EFFECT = "mdi:lava-lamp" +ICON_EXTERNAL_SOURCE = "mdi:video-input-hdmi" + -def setup_platform(hass, config, add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up a Hyperion server remote.""" name = config[CONF_NAME] host = config[CONF_HOST] port = config[CONF_PORT] priority = config[CONF_PRIORITY] - hdmi_priority = config[CONF_HDMI_PRIORITY] - default_color = config[CONF_DEFAULT_COLOR] - effect_list = config[CONF_EFFECT_LIST] - device = Hyperion( - name, host, port, priority, default_color, hdmi_priority, effect_list - ) + hyperion_client = client.HyperionClient(host, port) + + if not await hyperion_client.async_client_connect(): + raise PlatformNotReady - if device.setup(): - add_entities([device]) + async_add_entities([Hyperion(name, priority, hyperion_client)]) class Hyperion(LightEntity): """Representation of a Hyperion remote.""" - def __init__( - self, name, host, port, priority, default_color, hdmi_priority, effect_list - ): + def __init__(self, name, priority, hyperion_client): """Initialize the light.""" - self._host = host - self._port = port self._name = name self._priority = priority - self._hdmi_priority = hdmi_priority - self._default_color = default_color - self._rgb_color = [0, 0, 0] - self._rgb_mem = [0, 0, 0] - self._brightness = 255 - self._icon = "mdi:lightbulb" - self._effect_list = effect_list - self._effect = None - self._skip_update = False + self._client = hyperion_client + + # Active state representing the Hyperion instance. + self._set_internal_state( + brightness=255, rgb_color=DEFAULT_COLOR, effect=KEY_EFFECT_SOLID + ) + self._effect_list = [] + + @property + def should_poll(self): + """Return whether or not this entity should be polled.""" + return False @property def name(self): @@ -142,7 +130,7 @@ def hs_color(self): @property def is_on(self): """Return true if not black.""" - return self._rgb_color != [0, 0, 0] + return self._client.is_on() @property def icon(self): @@ -157,158 +145,233 @@ def effect(self): @property def effect_list(self): """Return the list of supported effects.""" - return self._effect_list + return ( + self._effect_list + + const.KEY_COMPONENTID_EXTERNAL_SOURCES + + [KEY_EFFECT_SOLID] + ) @property def supported_features(self): """Flag supported features.""" return SUPPORT_HYPERION - def turn_on(self, **kwargs): + @property + def available(self): + """Return server availability.""" + return self._client.has_loaded_state + + @property + def unique_id(self): + """Return a unique id for this instance.""" + return self._client.id + + async def async_turn_on(self, **kwargs): """Turn the lights on.""" + # == Turn device on == + # Turn on both ALL (Hyperion itself) and LEDDEVICE. It would be + # preferable to enable LEDDEVICE after the settings (e.g. brightness, + # color, effect), but this is not possible due to: + # https://github.com/hyperion-project/hyperion.ng/issues/967 + if not self.is_on: + if not await self._client.async_send_set_component( + **{ + const.KEY_COMPONENTSTATE: { + const.KEY_COMPONENT: const.KEY_COMPONENTID_ALL, + const.KEY_STATE: True, + } + } + ): + return + + if not await self._client.async_send_set_component( + **{ + const.KEY_COMPONENTSTATE: { + const.KEY_COMPONENT: const.KEY_COMPONENTID_LEDDEVICE, + const.KEY_STATE: True, + } + } + ): + return + + # == Get key parameters == + brightness = kwargs.get(ATTR_BRIGHTNESS, self._brightness) + effect = kwargs.get(ATTR_EFFECT, self._effect) if ATTR_HS_COLOR in kwargs: rgb_color = color_util.color_hs_to_RGB(*kwargs[ATTR_HS_COLOR]) - elif self._rgb_mem == [0, 0, 0]: - rgb_color = self._default_color else: - rgb_color = self._rgb_mem + rgb_color = self._rgb_color + + # == Set brightness == + if self._brightness != brightness: + if not await self._client.async_send_set_adjustment( + **{ + const.KEY_ADJUSTMENT: { + const.KEY_BRIGHTNESS: int( + round((float(brightness) * 100) / 255) + ) + } + } + ): + return - brightness = kwargs.get(ATTR_BRIGHTNESS, self._brightness) + # == Set an external source + if effect and effect in const.KEY_COMPONENTID_EXTERNAL_SOURCES: - if ATTR_EFFECT in kwargs: - self._skip_update = True - self._effect = kwargs[ATTR_EFFECT] - if self._effect == "HDMI": - self.json_request({"command": "clearall"}) - self._icon = "mdi:video-input-hdmi" - self._brightness = 255 - self._rgb_color = [125, 125, 125] - else: - self.json_request( - { - "command": "effect", - "priority": self._priority, - "effect": {"name": self._effect}, + # Clear any color/effect. + if not await self._client.async_send_clear( + **{const.KEY_PRIORITY: self._priority} + ): + return + + # Turn off all external sources, except the intended. + for key in const.KEY_COMPONENTID_EXTERNAL_SOURCES: + if not await self._client.async_send_set_component( + **{ + const.KEY_COMPONENTSTATE: { + const.KEY_COMPONENT: key, + const.KEY_STATE: effect == key, + } } - ) - self._icon = "mdi:lava-lamp" - self._rgb_color = [175, 0, 255] - return + ): + return - cal_color = [int(round(x * float(brightness) / 255)) for x in rgb_color] - self.json_request( - {"command": "color", "priority": self._priority, "color": cal_color} - ) + # == Set an effect + elif effect and effect != KEY_EFFECT_SOLID: + # This call should not be necessary, but without it there is no priorities-update issued: + # https://github.com/hyperion-project/hyperion.ng/issues/992 + if not await self._client.async_send_clear( + **{const.KEY_PRIORITY: self._priority} + ): + return - def turn_off(self, **kwargs): - """Disconnect all remotes.""" - self.json_request({"command": "clearall"}) - self.json_request( - {"command": "color", "priority": self._priority, "color": [0, 0, 0]} - ) + if not await self._client.async_send_set_effect( + **{ + const.KEY_PRIORITY: self._priority, + const.KEY_EFFECT: {const.KEY_NAME: effect}, + const.KEY_ORIGIN: DEFAULT_ORIGIN, + } + ): + return + # == Set a color + else: + if not await self._client.async_send_set_color( + **{ + const.KEY_PRIORITY: self._priority, + const.KEY_COLOR: rgb_color, + const.KEY_ORIGIN: DEFAULT_ORIGIN, + } + ): + return - def update(self): - """Get the lights status.""" - # postpone the immediate state check for changes that take time - if self._skip_update: - self._skip_update = False + async def async_turn_off(self, **kwargs): + """Disable the LED output component.""" + if not await self._client.async_send_set_component( + **{ + const.KEY_COMPONENTSTATE: { + const.KEY_COMPONENT: const.KEY_COMPONENTID_LEDDEVICE, + const.KEY_STATE: False, + } + } + ): return - response = self.json_request({"command": "serverinfo"}) - if response: - # workaround for outdated Hyperion - if "activeLedColor" not in response["info"]: - self._rgb_color = self._default_color - self._rgb_mem = self._default_color - self._brightness = 255 - self._icon = "mdi:lightbulb" - self._effect = None - return - # Check if Hyperion is in ambilight mode trough an HDMI grabber - try: - active_priority = response["info"]["priorities"][0]["priority"] - if active_priority == self._hdmi_priority: - self._brightness = 255 - self._rgb_color = [125, 125, 125] - self._icon = "mdi:video-input-hdmi" - self._effect = "HDMI" - return - except (KeyError, IndexError): - pass - - led_color = response["info"]["activeLedColor"] - if not led_color or led_color[0]["RGB Value"] == [0, 0, 0]: - # Get the active effect - if response["info"].get("activeEffects"): - self._rgb_color = [175, 0, 255] - self._icon = "mdi:lava-lamp" - try: - s_name = response["info"]["activeEffects"][0]["script"] - s_name = s_name.split("/")[-1][:-3].split("-")[0] - self._effect = [ - x for x in self._effect_list if s_name.lower() in x.lower() - ][0] - except (KeyError, IndexError): - self._effect = None - # Bulb off state - else: - self._rgb_color = [0, 0, 0] - self._icon = "mdi:lightbulb" - self._effect = None - else: - # Get the RGB color - self._rgb_color = led_color[0]["RGB Value"] - self._brightness = max(self._rgb_color) - self._rgb_mem = [ - int(round(float(x) * 255 / self._brightness)) - for x in self._rgb_color - ] - self._icon = "mdi:lightbulb" - self._effect = None - - def setup(self): - """Get the hostname of the remote.""" - response = self.json_request({"command": "serverinfo"}) - if response: - if self._name == self._host: - self._name = response["info"]["hostname"] - return True - return False - def json_request(self, request, wait_for_response=False): - """Communicate with the JSON server.""" - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.settimeout(5) - - try: - sock.connect((self._host, self._port)) - except OSError: - sock.close() - return False - - sock.send(bytearray(f"{json.dumps(request)}\n", "utf-8")) - try: - buf = sock.recv(4096) - except socket.timeout: - # Something is wrong, assume it's offline - sock.close() - return False - - # Read until a newline or timeout - buffering = True - while buffering: - if "\n" in str(buf, "utf-8"): - response = str(buf, "utf-8").split("\n")[0] - buffering = False + def _set_internal_state(self, brightness=None, rgb_color=None, effect=None): + """Set the internal state.""" + if brightness is not None: + self._brightness = brightness + if rgb_color is not None: + self._rgb_color = rgb_color + if effect is not None: + self._effect = effect + if effect == KEY_EFFECT_SOLID: + self._icon = ICON_LIGHTBULB + elif effect in const.KEY_COMPONENTID_EXTERNAL_SOURCES: + self._icon = ICON_EXTERNAL_SOURCE else: - try: - more = sock.recv(4096) - except socket.timeout: - more = None - if not more: - buffering = False - response = str(buf, "utf-8") - else: - buf += more - - sock.close() - return json.loads(response) + self._icon = ICON_EFFECT + + def _update_components(self, _=None): + """Update Hyperion components.""" + self.async_write_ha_state() + + def _update_adjustment(self, _=None): + """Update Hyperion adjustments.""" + if self._client.adjustment: + brightness_pct = self._client.adjustment[0].get( + const.KEY_BRIGHTNESS, DEFAULT_BRIGHTNESS + ) + if brightness_pct < 0 or brightness_pct > 100: + return + self._set_internal_state( + brightness=int(round((brightness_pct * 255) / float(100))) + ) + self.async_write_ha_state() + + def _update_priorities(self, _=None): + """Update Hyperion priorities.""" + visible_priority = self._client.visible_priority + if visible_priority: + componentid = visible_priority.get(const.KEY_COMPONENTID) + if componentid in const.KEY_COMPONENTID_EXTERNAL_SOURCES: + self._set_internal_state(rgb_color=DEFAULT_COLOR, effect=componentid) + elif componentid == const.KEY_COMPONENTID_EFFECT: + # Owner is the effect name. + # See: https://docs.hyperion-project.org/en/json/ServerInfo.html#priorities + self._set_internal_state( + rgb_color=DEFAULT_COLOR, effect=visible_priority[const.KEY_OWNER] + ) + elif componentid == const.KEY_COMPONENTID_COLOR: + self._set_internal_state( + rgb_color=visible_priority[const.KEY_VALUE][const.KEY_RGB], + effect=KEY_EFFECT_SOLID, + ) + self.async_write_ha_state() + + def _update_effect_list(self, _=None): + """Update Hyperion effects.""" + if not self._client.effects: + return + effect_list = [] + for effect in self._client.effects or []: + if const.KEY_NAME in effect: + effect_list.append(effect[const.KEY_NAME]) + if effect_list: + self._effect_list = effect_list + self.async_write_ha_state() + + def _update_full_state(self): + """Update full Hyperion state.""" + self._update_adjustment() + self._update_priorities() + self._update_effect_list() + + _LOGGER.debug( + "Hyperion full state update: On=%s,Brightness=%i,Effect=%s " + "(%i effects total),Color=%s", + self.is_on, + self._brightness, + self._effect, + len(self._effect_list), + self._rgb_color, + ) + + def _update_client(self, json): + """Update client connection state.""" + self.async_write_ha_state() + + async def async_added_to_hass(self): + """Register callbacks when entity added to hass.""" + self._client.set_callbacks( + { + f"{const.KEY_ADJUSTMENT}-{const.KEY_UPDATE}": self._update_adjustment, + f"{const.KEY_COMPONENTS}-{const.KEY_UPDATE}": self._update_components, + f"{const.KEY_EFFECTS}-{const.KEY_UPDATE}": self._update_effect_list, + f"{const.KEY_PRIORITIES}-{const.KEY_UPDATE}": self._update_priorities, + f"{const.KEY_CLIENT}-{const.KEY_UPDATE}": self._update_client, + } + ) + + # Load initial state. + self._update_full_state() + return True diff --git a/homeassistant/components/hyperion/manifest.json b/homeassistant/components/hyperion/manifest.json index 6d9d0ae4d9def4..4a9bf2ada8c930 100644 --- a/homeassistant/components/hyperion/manifest.json +++ b/homeassistant/components/hyperion/manifest.json @@ -2,5 +2,6 @@ "domain": "hyperion", "name": "Hyperion", "documentation": "https://www.home-assistant.io/integrations/hyperion", - "codeowners": [] + "requirements": ["hyperion-py==0.3.0"], + "codeowners": ["@dermotduffy"] } diff --git a/homeassistant/components/iaqualink/config_flow.py b/homeassistant/components/iaqualink/config_flow.py index d64ec711198c68..c083aee7c1c076 100644 --- a/homeassistant/components/iaqualink/config_flow.py +++ b/homeassistant/components/iaqualink/config_flow.py @@ -23,7 +23,7 @@ async def async_step_user(self, user_input: Optional[ConfigType] = None): # Supporting a single account. entries = self.hass.config_entries.async_entries(DOMAIN) if entries: - return self.async_abort(reason="already_setup") + return self.async_abort(reason="single_instance_allowed") errors = {} @@ -36,7 +36,7 @@ async def async_step_user(self, user_input: Optional[ConfigType] = None): await aqualink.login() return self.async_create_entry(title=username, data=user_input) except AqualinkLoginException: - errors["base"] = "connection_failure" + errors["base"] = "cannot_connect" return self.async_show_form( step_id="user", diff --git a/homeassistant/components/iaqualink/strings.json b/homeassistant/components/iaqualink/strings.json index a861fd35420855..5e7fcf6aa7a743 100644 --- a/homeassistant/components/iaqualink/strings.json +++ b/homeassistant/components/iaqualink/strings.json @@ -11,10 +11,10 @@ } }, "error": { - "connection_failure": "Unable to connect to iAqualink. Check your username and password." + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, "abort": { - "already_setup": "You can only configure a single iAqualink connection." + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/iaqualink/translations/ca.json b/homeassistant/components/iaqualink/translations/ca.json index 196f4970f93133..f0ca2ef59e0f96 100644 --- a/homeassistant/components/iaqualink/translations/ca.json +++ b/homeassistant/components/iaqualink/translations/ca.json @@ -1,9 +1,11 @@ { "config": { "abort": { - "already_setup": "Nom\u00e9s pots configurar una \u00fanica connexi\u00f3 d'iAqualink." + "already_setup": "Nom\u00e9s pots configurar una \u00fanica connexi\u00f3 d'iAqualink.", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", "connection_failure": "No s'ha pogut connectar amb iAqualink. Comprova el nom d'usuari i la contrasenya." }, "step": { diff --git a/homeassistant/components/iaqualink/translations/en.json b/homeassistant/components/iaqualink/translations/en.json index 8c66ad88e6b770..3ddc3a139245fe 100644 --- a/homeassistant/components/iaqualink/translations/en.json +++ b/homeassistant/components/iaqualink/translations/en.json @@ -1,9 +1,11 @@ { "config": { "abort": { - "already_setup": "You can only configure a single iAqualink connection." + "already_setup": "You can only configure a single iAqualink connection.", + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "error": { + "cannot_connect": "Failed to connect", "connection_failure": "Unable to connect to iAqualink. Check your username and password." }, "step": { diff --git a/homeassistant/components/iaqualink/translations/it.json b/homeassistant/components/iaqualink/translations/it.json index 568f91961f2f3d..f1abb66006f7cf 100644 --- a/homeassistant/components/iaqualink/translations/it.json +++ b/homeassistant/components/iaqualink/translations/it.json @@ -1,9 +1,11 @@ { "config": { "abort": { - "already_setup": "\u00c8 possibile configurare una sola connessione iAqualink." + "already_setup": "\u00c8 possibile configurare una sola connessione iAqualink.", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "error": { + "cannot_connect": "Impossibile connettersi", "connection_failure": "Impossibile connettersi a iAqualink. Controllare il nome utente e la password." }, "step": { diff --git a/homeassistant/components/iaqualink/translations/pl.json b/homeassistant/components/iaqualink/translations/pl.json index f0582b633f1ea8..ae13f8dfcfdba0 100644 --- a/homeassistant/components/iaqualink/translations/pl.json +++ b/homeassistant/components/iaqualink/translations/pl.json @@ -4,6 +4,7 @@ "already_setup": "Mo\u017cesz skonfigurowa\u0107 tylko jedno po\u0142\u0105czenie iAqualink." }, "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "connection_failure": "Nie mo\u017cna po\u0142\u0105czy\u0107 z iAqualink. Sprawd\u017a nazw\u0119 u\u017cytkownika i has\u0142o." }, "step": { diff --git a/homeassistant/components/icloud/config_flow.py b/homeassistant/components/icloud/config_flow.py index 052e5b98379cb5..a26226a9e182bd 100644 --- a/homeassistant/components/icloud/config_flow.py +++ b/homeassistant/components/icloud/config_flow.py @@ -113,7 +113,7 @@ async def async_step_user(self, user_input=None): except PyiCloudFailedLoginException as error: _LOGGER.error("Error logging into iCloud service: %s", error) self.api = None - errors[CONF_USERNAME] = "login" + errors[CONF_USERNAME] = "invalid_auth" return await self._show_setup_form(user_input, errors) if self.api.requires_2sa: diff --git a/homeassistant/components/icloud/strings.json b/homeassistant/components/icloud/strings.json index 7153bb9340e80b..43bc204f451e55 100644 --- a/homeassistant/components/icloud/strings.json +++ b/homeassistant/components/icloud/strings.json @@ -26,13 +26,13 @@ } }, "error": { - "login": "Login error: please check your email & password", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", "send_verification_code": "Failed to send verification code", "validate_verification_code": "Failed to verify your verification code, choose a trust device and start the verification again" }, "abort": { - "already_configured": "Account already configured", + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", "no_device": "None of your devices have \"Find my iPhone\" activated" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/icloud/translations/ca.json b/homeassistant/components/icloud/translations/ca.json index 5fa4ee6626df3e..9b3d0ea5ce46f6 100644 --- a/homeassistant/components/icloud/translations/ca.json +++ b/homeassistant/components/icloud/translations/ca.json @@ -5,6 +5,7 @@ "no_device": "Cap dels teus dispositius t\u00e9 activada la opci\u00f3 \"Troba el meu iPhone\"" }, "error": { + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "login": "Error d'inici de sessi\u00f3: comprova el correu electr\u00f2nic i la contrasenya", "send_verification_code": "No s'ha pogut enviar el codi de verificaci\u00f3", "validate_verification_code": "No s'ha pogut verificar el codi de verificaci\u00f3, tria un dispositiu de confian\u00e7a i torna a iniciar el proc\u00e9s" diff --git a/homeassistant/components/icloud/translations/en.json b/homeassistant/components/icloud/translations/en.json index 0e94418822284a..b76f98b21dadb3 100644 --- a/homeassistant/components/icloud/translations/en.json +++ b/homeassistant/components/icloud/translations/en.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "Account already configured", + "already_configured": "Account is already configured", "no_device": "None of your devices have \"Find my iPhone\" activated" }, "error": { + "invalid_auth": "Invalid authentication", "login": "Login error: please check your email & password", "send_verification_code": "Failed to send verification code", "validate_verification_code": "Failed to verify your verification code, choose a trust device and start the verification again" diff --git a/homeassistant/components/icloud/translations/es.json b/homeassistant/components/icloud/translations/es.json index 49bd9d612eb6ba..9322716f73d6af 100644 --- a/homeassistant/components/icloud/translations/es.json +++ b/homeassistant/components/icloud/translations/es.json @@ -5,6 +5,7 @@ "no_device": "Ninguno de tus dispositivos tiene activado \"Buscar mi iPhone\"" }, "error": { + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", "login": "Error de inicio de sesi\u00f3n: comprueba tu direcci\u00f3n de correo electr\u00f3nico y contrase\u00f1a", "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" diff --git a/homeassistant/components/icloud/translations/et.json b/homeassistant/components/icloud/translations/et.json new file mode 100644 index 00000000000000..2227b7442a79c6 --- /dev/null +++ b/homeassistant/components/icloud/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Tuvastamise viga" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/icloud/translations/fr.json b/homeassistant/components/icloud/translations/fr.json index 0ede270fd79856..000696d5b7d2f2 100644 --- a/homeassistant/components/icloud/translations/fr.json +++ b/homeassistant/components/icloud/translations/fr.json @@ -5,6 +5,7 @@ "no_device": "Aucun de vos appareils n'a activ\u00e9 \"Find my iPhone\"" }, "error": { + "invalid_auth": "Authentification invalide", "login": "Erreur de connexion: veuillez v\u00e9rifier votre e-mail et votre mot de passe", "send_verification_code": "\u00c9chec de l'envoi du code de v\u00e9rification", "validate_verification_code": "Impossible de v\u00e9rifier votre code de v\u00e9rification, choisissez un appareil de confiance et recommencez la v\u00e9rification" diff --git a/homeassistant/components/icloud/translations/it.json b/homeassistant/components/icloud/translations/it.json index 27429081e1e765..0d91335c41a5c1 100644 --- a/homeassistant/components/icloud/translations/it.json +++ b/homeassistant/components/icloud/translations/it.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "Account gi\u00e0 configurato", + "already_configured": "L'account \u00e8 gi\u00e0 configurato", "no_device": "Nessuno dei tuoi dispositivi ha attivato \"Trova il mio iPhone\"" }, "error": { + "invalid_auth": "Autenticazione non valida", "login": "Errore di accesso: si prega di controllare la tua e-mail e la password", "send_verification_code": "Impossibile inviare il codice di verifica", "validate_verification_code": "Impossibile verificare il codice di verifica, scegliere un dispositivo attendibile e riavviare la verifica" diff --git a/homeassistant/components/icloud/translations/no.json b/homeassistant/components/icloud/translations/no.json index 9805c769a67680..0068468d44f42e 100644 --- a/homeassistant/components/icloud/translations/no.json +++ b/homeassistant/components/icloud/translations/no.json @@ -5,6 +5,7 @@ "no_device": "Ingen av enhetene dine har \"Finn min iPhone\" aktivert" }, "error": { + "invalid_auth": "Ugyldig godkjenning", "login": "Innloggingsfeil: vennligst sjekk e-postadressen og passordet ditt", "send_verification_code": "Kunne ikke sende bekreftelseskode", "validate_verification_code": "Kunne ikke bekrefte bekreftelseskoden din, velg en tillitsenhet og start bekreftelsen p\u00e5 nytt" diff --git a/homeassistant/components/icloud/translations/ru.json b/homeassistant/components/icloud/translations/ru.json index 9066961ce29577..55725302617ff1 100644 --- a/homeassistant/components/icloud/translations/ru.json +++ b/homeassistant/components/icloud/translations/ru.json @@ -5,6 +5,7 @@ "no_device": "\u041d\u0438 \u043d\u0430 \u043e\u0434\u043d\u043e\u043c \u0438\u0437 \u0412\u0430\u0448\u0438\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 \u043d\u0435 \u0430\u043a\u0442\u0438\u0432\u0438\u0440\u043e\u0432\u0430\u043d\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \"\u041d\u0430\u0439\u0442\u0438 iPhone\"." }, "error": { + "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "login": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0432\u0445\u043e\u0434\u0430: \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0430\u0434\u0440\u0435\u0441 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b \u0438 \u043f\u0430\u0440\u043e\u043b\u044c.", "send_verification_code": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u043a\u043e\u0434 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f.", "validate_verification_code": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c \u043a\u043e\u0434 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f, \u0432\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0434\u043e\u0432\u0435\u0440\u0435\u043d\u043d\u043e\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0438 \u043d\u0430\u0447\u043d\u0438\u0442\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u0441\u043d\u043e\u0432\u0430." diff --git a/homeassistant/components/ifttt/strings.json b/homeassistant/components/ifttt/strings.json index b637e0de13da8b..9002cf307563c3 100644 --- a/homeassistant/components/ifttt/strings.json +++ b/homeassistant/components/ifttt/strings.json @@ -7,7 +7,7 @@ } }, "abort": { - "one_instance_allowed": "Only a single instance is necessary.", + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive IFTTT messages." }, "create_entry": { diff --git a/homeassistant/components/ifttt/translations/ca.json b/homeassistant/components/ifttt/translations/ca.json index 29f9fa29fe99dc..8706b5655dc98a 100644 --- a/homeassistant/components/ifttt/translations/ca.json +++ b/homeassistant/components/ifttt/translations/ca.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "La inst\u00e0ncia de Home Assistant ha de ser accessible des d'Internet per rebre missatges de IFTTT.", - "one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia." + "one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia.", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "create_entry": { "default": "Per enviar esdeveniments a Home Assistant, necessitar\u00e0s utilitzar l'acci\u00f3 \"Make a web resquest\" de [IFTTT Webhook applet]({applet_url}). \n\nCompleta la seg\u00fcent informaci\u00f3: \n\n- URL: `{webhook_url}` \n- Method: POST \n- Content Type: application/json \n\nConsulta la [documentaci\u00f3]({docs_url}) sobre com configurar les automatitzacions per gestionar dades entrants." diff --git a/homeassistant/components/ifttt/translations/el.json b/homeassistant/components/ifttt/translations/el.json new file mode 100644 index 00000000000000..aecb2ee553fa42 --- /dev/null +++ b/homeassistant/components/ifttt/translations/el.json @@ -0,0 +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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ifttt/translations/en.json b/homeassistant/components/ifttt/translations/en.json index 5a54f5a9aa5eaf..006680f940262a 100644 --- a/homeassistant/components/ifttt/translations/en.json +++ b/homeassistant/components/ifttt/translations/en.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive IFTTT messages.", - "one_instance_allowed": "Only a single instance is necessary." + "one_instance_allowed": "Only a single instance is necessary.", + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "create_entry": { "default": "To send events to Home Assistant, you will need to use the \"Make a web request\" action from the [IFTTT Webhook applet]({applet_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data." diff --git a/homeassistant/components/ifttt/translations/es.json b/homeassistant/components/ifttt/translations/es.json index 713cff93847bb1..7af87b11f35d64 100644 --- a/homeassistant/components/ifttt/translations/es.json +++ b/homeassistant/components/ifttt/translations/es.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Su instancia de Home Assistant debe estar accesible desde Internet para recibir mensajes IFTTT.", - "one_instance_allowed": "S\u00f3lo se necesita una instancia." + "one_instance_allowed": "S\u00f3lo se necesita una instancia.", + "single_instance_allowed": "Ya est\u00e1 configurado. S\u00f3lo es posible una \u00fanica configuraci\u00f3n." }, "create_entry": { "default": "Para enviar eventos a Home Assistant debes usar la acci\u00f3n \"Make a web request\" del [applet IFTTT Webhook]({applet_url}).\n\nCompleta la siguiente informaci\u00f3n: \n\n- URL: `{webhook_url}`\n- M\u00e9todo: POST\n- Tipo de contenido: application/json\n\nConsulta [la documentaci\u00f3n]({docs_url}) sobre c\u00f3mo configurar las automatizaciones para manejar los datos entrantes." diff --git a/homeassistant/components/ifttt/translations/et.json b/homeassistant/components/ifttt/translations/et.json new file mode 100644 index 00000000000000..c378c953b499b7 --- /dev/null +++ b/homeassistant/components/ifttt/translations/et.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + }, + "create_entry": { + "default": "S\u00fcndmuste saatmiseks Home Assistantile peate kasutama toimingut \"Make a web request\" [IFTTT Webhooki apletilt] ({applet_url}).\n\nSisestage j\u00e4rgmine teave:\n\n- URL: {webhook_url}.\n- Method: POST\n- Content Type: application/json\n\nVaadake [dokumentatsiooni]({docs_url}) kuidas seadistada sissetulevate andmete t\u00f6\u00f6tlemiseks automatiseerimisi." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ifttt/translations/fr.json b/homeassistant/components/ifttt/translations/fr.json index 823801e9743463..b8782f1452e10d 100644 --- a/homeassistant/components/ifttt/translations/fr.json +++ b/homeassistant/components/ifttt/translations/fr.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Votre instance Home Assistant doit \u00eatre accessible \u00e0 partir d'Internet pour recevoir les messages IFTTT.", - "one_instance_allowed": "Une seule instance est n\u00e9cessaire." + "one_instance_allowed": "Une seule instance est n\u00e9cessaire.", + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." }, "create_entry": { "default": "Pour envoyer des \u00e9v\u00e9nements \u00e0 Home Assistant, vous devez utiliser l'action \"Effectuer une demande Web\" \u00e0 partir de [l'applet IFTTT Webhook]({applet_url}). \n\n Remplissez les informations suivantes: \n\n - URL: ` {webhook_url} ` \n - M\u00e9thode: POST \n - Type de contenu: application / json \n\n Voir [la documentation]({docs_url}) pour savoir comment configurer les automatisations pour g\u00e9rer les donn\u00e9es entrantes." diff --git a/homeassistant/components/ifttt/translations/it.json b/homeassistant/components/ifttt/translations/it.json index 1989efd733ce4e..101e91e7f98448 100644 --- a/homeassistant/components/ifttt/translations/it.json +++ b/homeassistant/components/ifttt/translations/it.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "La tua istanza di Home Assistant deve essere accessibile da Internet per ricevere messaggi IFTTT.", - "one_instance_allowed": "\u00c8 necessaria una sola istanza." + "one_instance_allowed": "\u00c8 necessaria una sola istanza.", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "create_entry": { "default": "Per inviare eventi a Home Assistant, \u00e8 necessario utilizzare l'azione \"Crea una richiesta web\" dall'[applet IFTTT Webhook]({applet_url}). \n\n Compilare le seguenti informazioni: \n\n - URL: `{webhook_url}` \n - Metodo: POST \n - Tipo di contenuto: application/json \n\nVedere [la documentazione]({docs_url}) su come configurare le automazioni per gestire i dati in arrivo." diff --git a/homeassistant/components/ifttt/translations/lb.json b/homeassistant/components/ifttt/translations/lb.json index a13dd946a3e5a7..7ff324b45a259f 100644 --- a/homeassistant/components/ifttt/translations/lb.json +++ b/homeassistant/components/ifttt/translations/lb.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "\u00c4r Home Assistant Instanz muss iwwert Internet accessibel si fir IFTTT Noriichten z'empf\u00e4nken.", - "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg." + "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg.", + "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech." }, "create_entry": { "default": "Fir Evenementer un Home Assistant ze sch\u00e9ckemusst dir d'Aktioun \"Make a web request\" vum [IFTTT Webhook applet] ({applet_url}) benotzen.\n\nGitt folgend Informatiounen un:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nKuckt iech [Dokumentatioun]({docs_url}) w\u00e9i een Automatisatioune mat empfaangene Donn\u00e9e konfigur\u00e9iert." diff --git a/homeassistant/components/ifttt/translations/no.json b/homeassistant/components/ifttt/translations/no.json index 54c2f71f40bac7..bb1605c7499c68 100644 --- a/homeassistant/components/ifttt/translations/no.json +++ b/homeassistant/components/ifttt/translations/no.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Din Home Assistant enhet m\u00e5 v\u00e6re tilgjengelig fra internett for \u00e5 kunne motta IFTTT-meldinger.", - "one_instance_allowed": "Kun en forekomst er n\u00f8dvendig." + "one_instance_allowed": "Kun en forekomst er n\u00f8dvendig.", + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "create_entry": { "default": "For \u00e5 sende hendelser til Home Assistant, m\u00e5 du bruke \"Make a web request\" handlingen fra [IFTTT Webhook applet]({applet_url}).\n\nFyll ut f\u00f8lgende informasjon:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nSe [dokumentasjonen]({docs_url}) om hvordan du konfigurerer automatiseringer for \u00e5 h\u00e5ndtere innkommende data." diff --git a/homeassistant/components/ifttt/translations/ru.json b/homeassistant/components/ifttt/translations/ru.json index c78fd1090ad026..4f3efe9e267f9a 100644 --- a/homeassistant/components/ifttt/translations/ru.json +++ b/homeassistant/components/ifttt/translations/ru.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 IFTTT.", - "one_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "one_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e." }, "create_entry": { "default": "\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 Home Assistant \u0412\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \"Make a web request\" \u0438\u0437 [IFTTT Webhook applet]({applet_url}).\n\n\u0417\u0430\u043f\u043e\u043b\u043d\u0438\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\n\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]({docs_url}) \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0439 \u043f\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u043f\u043e\u0441\u0442\u0443\u043f\u0430\u044e\u0449\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445." diff --git a/homeassistant/components/ifttt/translations/zh-Hant.json b/homeassistant/components/ifttt/translations/zh-Hant.json index 8337aa82479d4b..cb57097e4f207b 100644 --- a/homeassistant/components/ifttt/translations/zh-Hant.json +++ b/homeassistant/components/ifttt/translations/zh-Hant.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Home Assistant \u7269\u4ef6\u5fc5\u9808\u80fd\u5920\u7531\u7db2\u969b\u7db2\u8def\u5b58\u53d6\uff0c\u65b9\u80fd\u63a5\u53d7 IFTTT \u8a0a\u606f\u3002", - "one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u7269\u4ef6\u5373\u53ef\u3002" + "one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u7269\u4ef6\u5373\u53ef\u3002", + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002" }, "create_entry": { "default": "\u6b32\u50b3\u9001\u4e8b\u4ef6\u81f3 Home Assistant\uff0c\u5c07\u9700\u8981\u7531 [IFTTT Webhook applet]({applet_url}) \u547c\u53eb\u300c\u9032\u884c Web \u8acb\u6c42\u300d\u52d5\u4f5c\u3002\n\n\u8acb\u586b\u5beb\u4e0b\u5217\u8cc7\u8a0a\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\n\u95dc\u65bc\u5982\u4f55\u50b3\u5165\u8cc7\u6599\u81ea\u52d5\u5316\u8a2d\u5b9a\uff0c\u8acb\u53c3\u95b1[\u6587\u4ef6]({docs_url})\u4ee5\u9032\u884c\u4e86\u89e3\u3002" diff --git a/homeassistant/components/image/manifest.json b/homeassistant/components/image/manifest.json index 4fc9c2d1d05ea0..246ea3871400ec 100644 --- a/homeassistant/components/image/manifest.json +++ b/homeassistant/components/image/manifest.json @@ -4,9 +4,7 @@ "config_flow": false, "documentation": "https://www.home-assistant.io/integrations/image", "requirements": ["pillow==7.2.0"], - "ssdp": [], - "zeroconf": [], - "homekit": {}, "dependencies": ["http"], - "codeowners": ["@home-assistant/core"] + "codeowners": ["@home-assistant/core"], + "quality_scale": "internal" } diff --git a/homeassistant/components/image_processing/__init__.py b/homeassistant/components/image_processing/__init__.py index 84ba5b45fc4d17..4617e78a6ece4c 100644 --- a/homeassistant/components/image_processing/__init__.py +++ b/homeassistant/components/image_processing/__init__.py @@ -173,9 +173,7 @@ def device_class(self): @property def state_attributes(self): """Return device specific state attributes.""" - attr = {ATTR_FACES: self.faces, ATTR_TOTAL_FACES: self.total_faces} - - return attr + return {ATTR_FACES: self.faces, ATTR_TOTAL_FACES: self.total_faces} def process_faces(self, faces, total): """Send event with detected faces and store data.""" diff --git a/homeassistant/components/influxdb/__init__.py b/homeassistant/components/influxdb/__init__.py index db49e119235697..45d3a4f5a25d0c 100644 --- a/homeassistant/components/influxdb/__init__.py +++ b/homeassistant/components/influxdb/__init__.py @@ -57,6 +57,7 @@ CONF_PASSWORD, CONF_PATH, CONF_PORT, + CONF_PRECISION, CONF_RETRY_COUNT, CONF_SSL, CONF_TAGS, @@ -307,13 +308,13 @@ def get_influx_connection(conf, test_write=False, test_read=False): kwargs = { CONF_TIMEOUT: TIMEOUT, } + precision = conf.get(CONF_PRECISION) if conf[CONF_API_VERSION] == API_VERSION_2: kwargs[CONF_URL] = conf[CONF_URL] kwargs[CONF_TOKEN] = conf[CONF_TOKEN] kwargs[INFLUX_CONF_ORG] = conf[CONF_ORG] bucket = conf.get(CONF_BUCKET) - influx = InfluxDBClientV2(**kwargs) query_api = influx.query_api() initial_write_mode = SYNCHRONOUS if test_write else ASYNCHRONOUS @@ -322,7 +323,7 @@ def get_influx_connection(conf, test_write=False, test_read=False): def write_v2(json): """Write data to V2 influx.""" try: - write_api.write(bucket=bucket, record=json) + write_api.write(bucket=bucket, record=json, write_precision=precision) except (urllib3.exceptions.HTTPError, OSError) as exc: raise ConnectionError(CONNECTION_ERROR % exc) from exc except ApiException as exc: @@ -393,7 +394,7 @@ def close_v2(): def write_v1(json): """Write data to V1 influx.""" try: - influx.write_points(json) + influx.write_points(json, time_precision=precision) except ( requests.exceptions.RequestException, exceptions.InfluxDBServerError, diff --git a/homeassistant/components/influxdb/const.py b/homeassistant/components/influxdb/const.py index c1b5ce3a59144a..029e4d482e8095 100644 --- a/homeassistant/components/influxdb/const.py +++ b/homeassistant/components/influxdb/const.py @@ -29,6 +29,7 @@ CONF_COMPONENT_CONFIG_DOMAIN = "component_config_domain" CONF_RETRY_COUNT = "max_retries" CONF_IGNORE_ATTRIBUTES = "ignore_attributes" +CONF_PRECISION = "precision" CONF_LANGUAGE = "language" CONF_QUERIES = "queries" @@ -136,6 +137,7 @@ vol.Optional(CONF_PATH): cv.string, vol.Optional(CONF_PORT): cv.port, vol.Optional(CONF_SSL): cv.boolean, + vol.Optional(CONF_PRECISION): vol.In(["ms", "s", "us", "ns"]), # Connection config for V1 API only. vol.Inclusive(CONF_USERNAME, "authentication"): cv.string, vol.Inclusive(CONF_PASSWORD, "authentication"): cv.string, diff --git a/homeassistant/components/influxdb/sensor.py b/homeassistant/components/influxdb/sensor.py index eb5b0ce609101f..2fcf4baaba8f13 100644 --- a/homeassistant/components/influxdb/sensor.py +++ b/homeassistant/components/influxdb/sensor.py @@ -224,11 +224,6 @@ def unit_of_measurement(self): """Return the unit of measurement of this entity, if any.""" return self._unit_of_measurement - @property - def should_poll(self): - """Return the polling state.""" - return True - def update(self): """Get the latest data from Influxdb and updates the states.""" self.data.update() diff --git a/homeassistant/components/input_boolean/translations/et.json b/homeassistant/components/input_boolean/translations/et.json index 3edfbf3cb5da5a..e0b2d46b168e00 100644 --- a/homeassistant/components/input_boolean/translations/et.json +++ b/homeassistant/components/input_boolean/translations/et.json @@ -5,5 +5,5 @@ "on": "Sees" } }, - "title": "Sisesta t\u00f5ev\u00e4\u00e4rtus" + "title": "T\u00f5ev\u00e4\u00e4rtuse abiline" } \ No newline at end of file diff --git a/homeassistant/components/input_boolean/translations/no.json b/homeassistant/components/input_boolean/translations/no.json index b0a608a1754293..f08c1e111deee9 100644 --- a/homeassistant/components/input_boolean/translations/no.json +++ b/homeassistant/components/input_boolean/translations/no.json @@ -5,5 +5,5 @@ "on": "P\u00e5" } }, - "title": "Inndata boolsk" + "title": "Innputt boolsk" } \ No newline at end of file diff --git a/homeassistant/components/input_datetime/translations/et.json b/homeassistant/components/input_datetime/translations/et.json index e72e7b102880b7..83acbf892621ce 100644 --- a/homeassistant/components/input_datetime/translations/et.json +++ b/homeassistant/components/input_datetime/translations/et.json @@ -1,3 +1,3 @@ { - "title": "Sisesta kuup\u00e4ev ja kellaaeg" + "title": "Kuup\u00e4eva ja kellaaja abiline" } \ No newline at end of file diff --git a/homeassistant/components/input_datetime/translations/no.json b/homeassistant/components/input_datetime/translations/no.json index e9a36c0fc88d18..716ca6fbbc01e6 100644 --- a/homeassistant/components/input_datetime/translations/no.json +++ b/homeassistant/components/input_datetime/translations/no.json @@ -1,3 +1,3 @@ { - "title": "Inndata datotid" + "title": "Innputt datotid" } \ No newline at end of file diff --git a/homeassistant/components/input_number/translations/et.json b/homeassistant/components/input_number/translations/et.json index f4182fbb3d5b9d..a241a89ff5362b 100644 --- a/homeassistant/components/input_number/translations/et.json +++ b/homeassistant/components/input_number/translations/et.json @@ -1,3 +1,3 @@ { - "title": "Sisendi number" + "title": "Arvv\u00e4\u00e4rtuse abiline" } \ No newline at end of file diff --git a/homeassistant/components/input_number/translations/no.json b/homeassistant/components/input_number/translations/no.json index cc918fabb2f99b..3988fe3eace615 100644 --- a/homeassistant/components/input_number/translations/no.json +++ b/homeassistant/components/input_number/translations/no.json @@ -1,3 +1,3 @@ { - "title": "Inndata nummer" + "title": "Innputt nummer" } \ No newline at end of file diff --git a/homeassistant/components/input_select/translations/et.json b/homeassistant/components/input_select/translations/et.json index 22378cd35a0dd0..5cce5f5ce0b402 100644 --- a/homeassistant/components/input_select/translations/et.json +++ b/homeassistant/components/input_select/translations/et.json @@ -1,3 +1,3 @@ { - "title": "Vali sisend" + "title": "Valikmen\u00fc\u00fc abiline" } \ No newline at end of file diff --git a/homeassistant/components/input_select/translations/no.json b/homeassistant/components/input_select/translations/no.json index c5802730c428d3..a87771349a8e1a 100644 --- a/homeassistant/components/input_select/translations/no.json +++ b/homeassistant/components/input_select/translations/no.json @@ -1,3 +1,3 @@ { - "title": "Inndata valg" + "title": "Innputt valg" } \ No newline at end of file diff --git a/homeassistant/components/input_text/translations/et.json b/homeassistant/components/input_text/translations/et.json index 047874d6328446..42d7d57f419cb3 100644 --- a/homeassistant/components/input_text/translations/et.json +++ b/homeassistant/components/input_text/translations/et.json @@ -1,3 +1,3 @@ { - "title": "Teksti sisestamine" + "title": "Tekstisisestuse abiline" } \ No newline at end of file diff --git a/homeassistant/components/input_text/translations/no.json b/homeassistant/components/input_text/translations/no.json index bf41f9dc43cc72..9c1141de543fac 100644 --- a/homeassistant/components/input_text/translations/no.json +++ b/homeassistant/components/input_text/translations/no.json @@ -1,3 +1,3 @@ { - "title": "Inndata tekst" + "title": "Innputt tekst" } \ No newline at end of file diff --git a/homeassistant/components/insteon/manifest.json b/homeassistant/components/insteon/manifest.json index 871629b68775c9..d20f56054b3a59 100644 --- a/homeassistant/components/insteon/manifest.json +++ b/homeassistant/components/insteon/manifest.json @@ -2,7 +2,7 @@ "domain": "insteon", "name": "Insteon", "documentation": "https://www.home-assistant.io/integrations/insteon", - "requirements": ["pyinsteon==1.0.7"], + "requirements": ["pyinsteon==1.0.8"], "codeowners": ["@teharris1"], "config_flow": true } \ No newline at end of file diff --git a/homeassistant/components/insteon/translations/de.json b/homeassistant/components/insteon/translations/de.json new file mode 100644 index 00000000000000..dfefff8c559eba --- /dev/null +++ b/homeassistant/components/insteon/translations/de.json @@ -0,0 +1,66 @@ +{ + "config": { + "step": { + "hub2": { + "data": { + "password": "Passwort", + "username": "Benutzername" + } + }, + "hubv1": { + "data": { + "host": "IP-Adresse", + "port": "Port" + } + }, + "hubv2": { + "data": { + "host": "IP-Adresse", + "password": "Passwort", + "port": "Port", + "username": "Benutzername" + } + }, + "init": { + "title": "Insteon" + }, + "plm": { + "title": "Insteon PLM" + }, + "user": { + "title": "Insteon" + } + } + }, + "options": { + "step": { + "add_override": { + "title": "Insteon" + }, + "add_x10": { + "data": { + "platform": "Plattform" + }, + "title": "Insteon" + }, + "change_hub_config": { + "data": { + "host": "IP-Adresse", + "password": "Passwort", + "port": "Port", + "username": "Benutzername" + }, + "title": "Insteon" + }, + "init": { + "title": "Insteon" + }, + "remove_override": { + "title": "Insteon" + }, + "remove_x10": { + "title": "Insteon" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/insteon/translations/el.json b/homeassistant/components/insteon/translations/el.json new file mode 100644 index 00000000000000..f35d5bad3434fd --- /dev/null +++ b/homeassistant/components/insteon/translations/el.json @@ -0,0 +1,44 @@ +{ + "config": { + "step": { + "hubv1": { + "data": { + "port": "\u0398\u03cd\u03c1\u03b1" + }, + "description": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd \u03c4\u03bf\u03c5 Insteon Hub Version 1 (\u03c0\u03c1\u03b9\u03bd \u03b1\u03c0\u03cc \u03c4\u03bf 2014).", + "title": "Insteon Hub Version 1" + }, + "hubv2": { + "data": { + "host": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "port": "\u0398\u03cd\u03c1\u03b1" + }, + "description": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c0\u03b1\u03c1\u03b1\u03bc\u03ad\u03c4\u03c1\u03c9\u03bd \u03c4\u03bf\u03c5 Insteon Hub Version 2.", + "title": "Insteon Hub Version 2" + }, + "user": { + "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" + } + } + }, + "options": { + "step": { + "remove_override": { + "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" + }, + "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" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/insteon/translations/fr.json b/homeassistant/components/insteon/translations/fr.json index 45b85201a3c7c2..f18df0110489ce 100644 --- a/homeassistant/components/insteon/translations/fr.json +++ b/homeassistant/components/insteon/translations/fr.json @@ -48,14 +48,19 @@ }, "init": { "data": { - "hubv1": "Hub Version 1 (avant 2014)" + "hubv1": "Hub Version 1 (avant 2014)", + "hubv2": "Hub version 2", + "plm": "Modem PowerLink (PLM)" }, + "description": "S\u00e9lectionnez le type de modem Insteon.", "title": "Insteon" }, "plm": { "data": { "device": "Chemin du p\u00e9riph\u00e9rique USB" - } + }, + "description": "Configurez le modem Insteon PowerLink (PLM).", + "title": "Insteon PLM" }, "user": { "data": { @@ -68,21 +73,27 @@ }, "options": { "abort": { + "already_configured": "Une connexion Insteon par modem est d\u00e9j\u00e0 configur\u00e9e", "cannot_connect": "Impossible de se connecter au modem Insteon" }, "error": { "cannot_connect": "\u00c9chec de connexion", + "input_error": "Entr\u00e9es non valides, veuillez v\u00e9rifier vos valeurs.", "select_single": "S\u00e9lectionnez une option" }, "step": { "add_override": { "data": { - "cat": "Cat\u00e9gorie d'appareil (c.-\u00e0-d. 0x10)" + "address": "Adresse de l'appareil (par exemple 1a2b3c)", + "cat": "Cat\u00e9gorie d'appareil (c.-\u00e0-d. 0x10)", + "subcat": "Sous-cat\u00e9gorie de p\u00e9riph\u00e9rique (par exemple 0x0a)" }, + "description": "Ajoutez un remplacement de p\u00e9riph\u00e9rique.", "title": "Insteon" }, "add_x10": { "data": { + "housecode": "Code maison (a - p)", "platform": "Plate-forme", "steps": "Pas de gradateur (pour les appareils d'\u00e9clairage uniquement, par d\u00e9faut 22)", "unitcode": "Code de l'unit\u00e9 (1-16)" @@ -97,6 +108,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" }, "init": { diff --git a/homeassistant/components/insteon/translations/ko.json b/homeassistant/components/insteon/translations/ko.json new file mode 100644 index 00000000000000..7c77bd49e27f38 --- /dev/null +++ b/homeassistant/components/insteon/translations/ko.json @@ -0,0 +1,111 @@ +{ + "config": { + "abort": { + "already_configured": "Insteon \ubaa8\ub380 \uc5f0\uacb0\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub428. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4." + }, + "error": { + "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", + "select_single": "\ud558\ub098\uc758 \uc635\uc158\uc744 \uc120\ud0dd\ud558\uc2ed\uc2dc\uc624." + }, + "step": { + "hub1": { + "data": { + "host": "\ud5c8\ube0c IP \uc8fc\uc18c", + "port": "IP \ud3ec\ud2b8" + }, + "description": "Insteon Hub \ubc84\uc804 1 (2014 \ub144 \uc774\uc804)\uc744 \uad6c\uc131\ud569\ub2c8\ub2e4.", + "title": "Insteon Hub \ubc84\uc804 1" + }, + "hub2": { + "data": { + "host": "\ud5c8\ube0c IP \uc8fc\uc18c", + "port": "IP \ud3ec\ud2b8" + }, + "description": "Insteon Hub \ubc84\uc804 2\ub97c \uad6c\uc131\ud569\ub2c8\ub2e4.", + "title": "Insteon Hub \ubc84\uc804 2" + }, + "hubv1": { + "data": { + "host": "IP \uc8fc\uc18c", + "port": "\ud3ec\ud2b8" + }, + "description": "Insteon Hub \ubc84\uc804 1 (2014 \ub144 \uc774\uc804)\uc744 \uad6c\uc131\ud569\ub2c8\ub2e4.", + "title": "Insteon Hub \ubc84\uc804 1" + }, + "hubv2": { + "data": { + "host": "IP \uc8fc\uc18c", + "password": "\uc554\ud638", + "port": "\ud3ec\ud2b8", + "username": "\uc0ac\uc6a9\uc790\uba85" + }, + "description": "Insteon Hub \ubc84\uc804 2\ub97c \uad6c\uc131\ud569\ub2c8\ub2e4.", + "title": "Insteon Hub \ubc84\uc804 2" + }, + "init": { + "data": { + "hubv1": "\ud5c8\ube0c \ubc84\uc804 1 (2014 \ub144 \uc774\uc804)" + } + }, + "plm": { + "data": { + "device": "USB \uc7a5\uce58 \uacbd\ub85c" + } + }, + "user": { + "data": { + "modem_type": "\ubaa8\ub380 \uc720\ud615." + }, + "description": "Insteon \ubaa8\ub380 \uc720\ud615\uc744 \uc120\ud0dd\ud558\uc2ed\uc2dc\uc624.", + "title": "Insteon" + } + } + }, + "options": { + "abort": { + "cannot_connect": "Insteon \ubaa8\ub380\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." + }, + "error": { + "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", + "select_single": "\uc635\uc158 \uc120\ud0dd" + }, + "step": { + "add_override": { + "data": { + "cat": "\uc7a5\uce58 \ubc94\uc8fc(\uc608: 0x10)" + } + }, + "add_x10": { + "data": { + "steps": "\ub514\uba38 \ub2e8\uacc4(\ub77c\uc774\ud2b8 \uc7a5\uce58\uc5d0\ub9cc, \uae30\ubcf8\uac12 22)", + "unitcode": "\ub2e8\uc704 \ucf54\ub4dc (1-16)" + }, + "description": "Insteon Hub \ube44\ubc00\ubc88\ud638\ub97c \ubcc0\uacbd\ud569\ub2c8\ub2e4." + }, + "init": { + "data": { + "add_override": "\uc7a5\uce58 Override \ucd94\uac00", + "add_x10": "X10 \uc7a5\uce58\ub97c \ucd94\uac00\ud569\ub2c8\ub2e4.", + "change_hub_config": "\ud5c8\ube0c \uad6c\uc131\uc744 \ubcc0\uacbd\ud569\ub2c8\ub2e4.", + "remove_override": "\uc7a5\uce58 Override \uc81c\uac70", + "remove_x10": "X10 \uc7a5\uce58\ub97c \uc81c\uac70\ud569\ub2c8\ub2e4." + }, + "description": "\uad6c\uc131 \ud560 \uc635\uc158\uc744 \uc120\ud0dd\ud558\uc2ed\uc2dc\uc624." + }, + "remove_override": { + "data": { + "address": "\uc81c\uac70 \ud560 \uc7a5\uce58 \uc8fc\uc18c\ub97c \uc120\ud0dd\ud558\uc2ed\uc2dc\uc624." + }, + "description": "\uc7a5\uce58 Override \uc81c\uac70" + }, + "remove_x10": { + "data": { + "address": "\uc81c\uac70 \ud560 \uc7a5\uce58 \uc8fc\uc18c\ub97c \uc120\ud0dd\ud558\uc2ed\uc2dc\uc624." + }, + "description": "X10 \uc7a5\uce58 \uc81c\uac70" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/insteon/translations/lb.json b/homeassistant/components/insteon/translations/lb.json index bff6ff757c1bec..a0b327a655fc16 100644 --- a/homeassistant/components/insteon/translations/lb.json +++ b/homeassistant/components/insteon/translations/lb.json @@ -27,6 +27,24 @@ "description": "Insteon Hub Versioun 2 konfigur\u00e9ieren.", "title": "Insteon Hub Versioun 2" }, + "hubv1": { + "data": { + "host": "IP Adresse", + "port": "Port" + }, + "description": "Insteon Hub Versioun 1 (pre-2014) konfigur\u00e9ieren.", + "title": "Insteon Hub Versioun 1" + }, + "hubv2": { + "data": { + "host": "IP Adresse", + "password": "Passwuert", + "port": "Port", + "username": "Benotzernumm" + }, + "description": "Insteon Hub Versioun 2 konfigur\u00e9ieren.", + "title": "Insteon Hub Versioun 2" + }, "init": { "data": { "hubv1": "Hub Versioun 1 (Pre-2014)", @@ -42,6 +60,13 @@ }, "description": "Insteon PowerLink Modem (PLM) konfigur\u00e9ieren.", "title": "Insteon PLM" + }, + "user": { + "data": { + "modem_type": "Typ vu Modem." + }, + "description": "Insteon Modem Typ auswielen.", + "title": "Insteon" } } }, diff --git a/homeassistant/components/insteon/translations/nl.json b/homeassistant/components/insteon/translations/nl.json new file mode 100644 index 00000000000000..538755dd013fe3 --- /dev/null +++ b/homeassistant/components/insteon/translations/nl.json @@ -0,0 +1,35 @@ +{ + "config": { + "step": { + "hub2": { + "data": { + "username": "Gebruikersnaam" + } + }, + "hubv2": { + "data": { + "username": "Gebruikersnaam" + } + }, + "user": { + "data": { + "modem_type": "Modemtype." + }, + "description": "Selecteer het Insteon-modemtype.", + "title": "Insteon" + } + } + }, + "options": { + "step": { + "change_hub_config": { + "data": { + "host": "IP-adres", + "password": "Wachtwoord", + "port": "Poort", + "username": "Gebruikersnaam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/insteon/translations/pl.json b/homeassistant/components/insteon/translations/pl.json index cb697462ca9bc9..2b506fb05d0c1a 100644 --- a/homeassistant/components/insteon/translations/pl.json +++ b/homeassistant/components/insteon/translations/pl.json @@ -1,18 +1,40 @@ { "config": { "abort": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", + "already_configured": "Po\u0142\u0105czenie z modemem Insteon jest ju\u017c skonfigurowane", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." }, "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia." + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "select_single": "Wybierz jedn\u0105 z opcji." }, "step": { + "hub1": { + "data": { + "host": "Adres IP Huba", + "port": "Numer portu IP" + }, + "description": "Konfiguracja Huba Insteon \u2014 wersja 1 (sprzed 2014r.)", + "title": "Hub Insteon \u2014 wersja 1" + }, + "hub2": { + "data": { + "host": "Adres IP Huba", + "password": "[%key_id:common::config_flow::data::password%]", + "port": "Numer portu IP", + "username": "[%key_id:common::config_flow::data::username%]" + }, + "description": "Konfiguracja Huba Insteon \u2014 wersja 2.", + "title": "Hub Insteon \u2014 wersja 2" + }, "hubv1": { "data": { "host": "Adres IP", "port": "Port" - } + }, + "description": "Konfiguracja Huba Insteon \u2014 wersja 1 (sprzed 2014r.)", + "title": "Hub Insteon \u2014 wersja 1" }, "hubv2": { "data": { @@ -20,30 +42,99 @@ "password": "Has\u0142o", "port": "Port", "username": "Nazwa u\u017cytkownika" - } + }, + "description": "Konfiguracja Huba Insteon \u2014 wersja 2.", + "title": "Hub Insteon \u2014 wersja 2" + }, + "init": { + "data": { + "hubv1": "Hub \u2014 wersja 1 (sprzed 2014r.)", + "hubv2": "Hub \u2014 wersja 2", + "plm": "Modem PowerLink (PLM)" + }, + "description": "Wybierz typ modemu Insteon.", + "title": "Insteon" }, "plm": { "data": { "device": "\u015acie\u017cka urz\u0105dzenia USB" - } + }, + "description": "Konfiguracja modemu Insteon PowerLink (PLM).", + "title": "Insteon PLM" }, "user": { + "data": { + "modem_type": "Typ modemu." + }, + "description": "Wybierz typ modemu Insteon.", "title": "Insteon" } } }, "options": { + "abort": { + "already_configured": "Po\u0142\u0105czenie z modemem Insteon jest ju\u017c skonfigurowane", + "cannot_connect": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z modemem Insteon" + }, "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia." + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "input_error": "Nieprawid\u0142owe wpisy, sprawd\u017a wpisane warto\u015bci.", + "select_single": "Wybierz jedn\u0105 z opcji." }, "step": { + "add_override": { + "data": { + "address": "Adres urz\u0105dzenia (np. 1a2b3c)", + "cat": "Kategoria urz\u0105dzenia (np. 0x10)", + "subcat": "Podkategoria urz\u0105dzenia (np. 0x0a)" + }, + "description": "Dodawanie nadpisanie urz\u0105dzenia.", + "title": "Insteon" + }, + "add_x10": { + "data": { + "housecode": "Housecode (a - p)", + "platform": "Platforma", + "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" + }, "change_hub_config": { "data": { "host": "Adres IP", "password": "Has\u0142o", "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 Assistant. Nie zmienia to konfiguracji samego Huba. Aby zmieni\u0107 jego konfiguracj\u0119, u\u017cyj aplikacji Hub.", + "title": "Insteon" + }, + "init": { + "data": { + "add_override": "Dodawanie nadpisanie urz\u0105dzenia.", + "add_x10": "Dodaj urz\u0105dzenie X10.", + "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" + }, + "remove_x10": { + "data": { + "address": "Wybierz adres urz\u0105dzenia do usuni\u0119cia" + }, + "description": "Usu\u0144 urz\u0105dzenie X10", + "title": "Insteon" } } } diff --git a/homeassistant/components/integration/sensor.py b/homeassistant/components/integration/sensor.py index 6169e084a02cf8..a776920b8e644c 100644 --- a/homeassistant/components/integration/sensor.py +++ b/homeassistant/components/integration/sensor.py @@ -203,8 +203,7 @@ def should_poll(self): @property def device_state_attributes(self): """Return the state attributes of the sensor.""" - state_attr = {ATTR_SOURCE_ID: self._sensor_source_id} - return state_attr + return {ATTR_SOURCE_ID: self._sensor_source_id} @property def icon(self): diff --git a/homeassistant/components/ios/notify.py b/homeassistant/components/ios/notify.py index 62dd72973da081..f9c682bf52743c 100644 --- a/homeassistant/components/ios/notify.py +++ b/homeassistant/components/ios/notify.py @@ -12,6 +12,7 @@ ATTR_TITLE_DEFAULT, BaseNotificationService, ) +from homeassistant.const import HTTP_CREATED, HTTP_TOO_MANY_REQUESTS import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) @@ -90,13 +91,13 @@ def send_message(self, message="", **kwargs): req = requests.post(PUSH_URL, json=data, timeout=10) - if req.status_code != 201: + if req.status_code != HTTP_CREATED: fallback_error = req.json().get("errorMessage", "Unknown error") fallback_message = ( f"Internal server error, please try again later: {fallback_error}" ) message = req.json().get("message", fallback_message) - if req.status_code == 429: + if req.status_code == HTTP_TOO_MANY_REQUESTS: _LOGGER.warning(message) log_rate_limits(self.hass, target, req.json(), 30) else: diff --git a/homeassistant/components/iota/__init__.py b/homeassistant/components/iota/__init__.py index 497e94a08d6bf8..dbf5fc15007259 100644 --- a/homeassistant/components/iota/__init__.py +++ b/homeassistant/components/iota/__init__.py @@ -72,8 +72,7 @@ def name(self): @property def device_state_attributes(self): """Return the state attributes of the device.""" - attr = {CONF_WALLET_NAME: self._name} - return attr + return {CONF_WALLET_NAME: self._name} @property def api(self): diff --git a/homeassistant/components/ipma/strings.json b/homeassistant/components/ipma/strings.json index 5b325938411fe1..e3825bd4b2729e 100644 --- a/homeassistant/components/ipma/strings.json +++ b/homeassistant/components/ipma/strings.json @@ -5,9 +5,9 @@ "title": "Location", "description": "Instituto Portugu\u00eas do Mar e Atmosfera", "data": { - "name": "Name", - "latitude": "Latitude", - "longitude": "Longitude", + "name": "[%key:common::config_flow::data::name%]", + "latitude": "[%key:common::config_flow::data::latitude%]", + "longitude": "[%key:common::config_flow::data::longitude%]", "mode": "Mode" } } diff --git a/homeassistant/components/ipma/translations/et.json b/homeassistant/components/ipma/translations/et.json new file mode 100644 index 00000000000000..32fab1be8dfcd8 --- /dev/null +++ b/homeassistant/components/ipma/translations/et.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "user": { + "data": { + "latitude": "Laiuskraad", + "longitude": "Pikkuskraad" + }, + "title": "Asukoht" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ipma/translations/it.json b/homeassistant/components/ipma/translations/it.json index 5ab6af8d2183df..ceee3c9091fed9 100644 --- a/homeassistant/components/ipma/translations/it.json +++ b/homeassistant/components/ipma/translations/it.json @@ -7,7 +7,7 @@ "user": { "data": { "latitude": "Latitudine", - "longitude": "Longitudine", + "longitude": "Logitudine", "mode": "Modalit\u00e0", "name": "Nome" }, diff --git a/homeassistant/components/ipp/strings.json b/homeassistant/components/ipp/strings.json index 09c2424151f74f..d13d281cef31f4 100644 --- a/homeassistant/components/ipp/strings.json +++ b/homeassistant/components/ipp/strings.json @@ -9,8 +9,8 @@ "host": "[%key:common::config_flow::data::host%]", "port": "[%key:common::config_flow::data::port%]", "base_path": "Relative path to the printer", - "ssl": "Printer supports communication over SSL/TLS", - "verify_ssl": "Printer uses a proper SSL certificate" + "ssl": "[%key:common::config_flow::data::ssl%]", + "verify_ssl": "[%key:common::config_flow::data::verify_ssl%]" } }, "zeroconf_confirm": { diff --git a/homeassistant/components/ipp/translations/ca.json b/homeassistant/components/ipp/translations/ca.json index 6e3185767d5b36..32a04679eee127 100644 --- a/homeassistant/components/ipp/translations/ca.json +++ b/homeassistant/components/ipp/translations/ca.json @@ -20,8 +20,8 @@ "base_path": "Ruta relativa a la impressora", "host": "Amfitri\u00f3", "port": "Port", - "ssl": "La impressora \u00e9s compatible amb comunicaci\u00f3 SSL/TLS", - "verify_ssl": "La impressora utilitza un certificat SSL adequat" + "ssl": "Utilitza un certificat SSL", + "verify_ssl": "Verifica el certificat SSL" }, "description": "Configura la impressora amb el protocol d'impressi\u00f3 per Internet (IPP) per integrar-la amb Home Assistant.", "title": "Enlla\u00e7 d'impressora" diff --git a/homeassistant/components/ipp/translations/en.json b/homeassistant/components/ipp/translations/en.json index 0267c5b5091433..7c8eccc389bbb2 100644 --- a/homeassistant/components/ipp/translations/en.json +++ b/homeassistant/components/ipp/translations/en.json @@ -20,8 +20,8 @@ "base_path": "Relative path to the printer", "host": "Host", "port": "Port", - "ssl": "Printer supports communication over SSL/TLS", - "verify_ssl": "Printer uses a proper SSL certificate" + "ssl": "Uses an SSL certificate", + "verify_ssl": "Verify SSL certificate" }, "description": "Set up your printer via Internet Printing Protocol (IPP) to integrate with Home Assistant.", "title": "Link your printer" diff --git a/homeassistant/components/ipp/translations/et.json b/homeassistant/components/ipp/translations/et.json new file mode 100644 index 00000000000000..ac554145a1701d --- /dev/null +++ b/homeassistant/components/ipp/translations/et.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba seadistatud", + "connection_error": "\u00dchendumine eba\u00f5nnestus", + "connection_upgrade": "Printeriga \u00fchenduse loomine nurjus kuna vajalik on \u00fchenduse uuendamine.", + "ipp_error": "Ilmnes IPP viga.", + "ipp_version_error": "Printer ei toeta seda IPP versiooni.", + "parse_error": "Printeri vastuse s\u00f5elumine nurjus.", + "unique_id_required": "Seadmel puudub avastamiseks vajalik kordumatu ID." + }, + "error": { + "connection_error": "\u00dchendumine eba\u00f5nnestus", + "connection_upgrade": "Printeriga \u00fchenduse loomine nurjus. Proovige uuesti kui SSL/TLS-i suvand on m\u00e4rgitud." + }, + "flow_title": "Printer: {name}", + "step": { + "user": { + "data": { + "base_path": "Printeri suhteline rada", + "host": "Host", + "port": "Port", + "ssl": "Printer toetab SSL/TLS \u00fchendust", + "verify_ssl": "Printer kasutab \u00f5iget SSL-serti" + }, + "description": "Seadistage oma printer Interneti-printimisprotokolli (IPP) kaudu, et see integreeruks Home Assistantiga.", + "title": "Linkige oma printer" + }, + "zeroconf_confirm": { + "description": "Kas soovite seadistada {name}?", + "title": "Avastatud printer" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ipp/translations/it.json b/homeassistant/components/ipp/translations/it.json index c1eacf00c28da6..de8bf9bbe4f084 100644 --- a/homeassistant/components/ipp/translations/it.json +++ b/homeassistant/components/ipp/translations/it.json @@ -20,8 +20,8 @@ "base_path": "Percorso relativo alla stampante", "host": "Host", "port": "Porta", - "ssl": "La stampante supporta la comunicazione su SSL/TLS", - "verify_ssl": "La stampante utilizza un certificato SSL adeguato" + "ssl": "Utilizza un certificato SSL", + "verify_ssl": "Verificare il certificato SSL" }, "description": "Configurare la stampante tramite Internet Printing Protocol (IPP) per l'integrazione con Home Assistant.", "title": "Collegare la stampante" diff --git a/homeassistant/components/ipp/translations/no.json b/homeassistant/components/ipp/translations/no.json index 4e94efe71c16af..3e25dcb0c4c467 100644 --- a/homeassistant/components/ipp/translations/no.json +++ b/homeassistant/components/ipp/translations/no.json @@ -20,8 +20,8 @@ "base_path": "Relativ bane til skriveren", "host": "Vert", "port": "", - "ssl": "Skriveren st\u00f8tter kommunikasjon over SSL/TLS", - "verify_ssl": "Skriveren bruker et riktig SSL-sertifikat" + "ssl": "Bruker et SSL-sertifikat", + "verify_ssl": "Verifisere SSL-sertifikat" }, "description": "Sett opp skriveren din via Internet Printing Protocol (IPP) for \u00e5 integrere med Home Assistant.", "title": "Koble til skriveren din" diff --git a/homeassistant/components/ipp/translations/pl.json b/homeassistant/components/ipp/translations/pl.json index 4e5af33041c400..c5096f6af8e456 100644 --- a/homeassistant/components/ipp/translations/pl.json +++ b/homeassistant/components/ipp/translations/pl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", - "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "connection_upgrade": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z drukark\u0105 z powodu konieczno\u015bci uaktualnienia po\u0142\u0105czenia.", "ipp_error": "Wyst\u0105pi\u0142 b\u0142\u0105d IPP.", "ipp_version_error": "Wersja IPP nieobs\u0142ugiwana przez drukark\u0119.", @@ -10,7 +10,7 @@ "unique_id_required": "Urz\u0105dzenie nie posiada unikalnej identyfikacji wymaganej do wykrycia." }, "error": { - "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", + "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "connection_upgrade": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z drukark\u0105. Spr\u00f3buj ponownie z zaznaczon\u0105 opcj\u0105 SSL/TLS." }, "flow_title": "Drukarka: {name}", diff --git a/homeassistant/components/ipp/translations/ru.json b/homeassistant/components/ipp/translations/ru.json index 4953c9dae5c260..9fdde4c62fec76 100644 --- a/homeassistant/components/ipp/translations/ru.json +++ b/homeassistant/components/ipp/translations/ru.json @@ -20,8 +20,8 @@ "base_path": "\u041e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u043f\u0443\u0442\u044c \u043a \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u0443", "host": "\u0425\u043e\u0441\u0442", "port": "\u041f\u043e\u0440\u0442", - "ssl": "\u041f\u0440\u0438\u043d\u0442\u0435\u0440 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0441\u0432\u044f\u0437\u044c \u043f\u043e SSL/TLS", - "verify_ssl": "\u041f\u0440\u0438\u043d\u0442\u0435\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL" + "ssl": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL", + "verify_ssl": "\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL" }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u043f\u0440\u0438\u043d\u0442\u0435\u0440\u0430 \u043f\u043e \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u0443 IPP.", "title": "Internet Printing Protocol (IPP)" diff --git a/homeassistant/components/ipp/translations/zh-Hant.json b/homeassistant/components/ipp/translations/zh-Hant.json index d1c9c00bdb8fe2..3ddd1250f1538b 100644 --- a/homeassistant/components/ipp/translations/zh-Hant.json +++ b/homeassistant/components/ipp/translations/zh-Hant.json @@ -20,8 +20,8 @@ "base_path": "\u5370\u8868\u6a5f\u76f8\u5c0d\u8def\u5f91", "host": "\u4e3b\u6a5f\u7aef", "port": "\u901a\u8a0a\u57e0", - "ssl": "\u5370\u8868\u6a5f\u652f\u63f4 SSL/TLS \u901a\u8a0a", - "verify_ssl": "\u5370\u8868\u6a5f\u4f7f\u7528\u5c0d\u61c9\u8a8d\u8b49" + "ssl": "\u4f7f\u7528 SSL \u8a8d\u8b49", + "verify_ssl": "\u78ba\u8a8d SSL \u8a8d\u8b49" }, "description": "\u900f\u904e\u7db2\u969b\u7db2\u8def\u5217\u5370\u5354\u5b9a\uff08IPP\uff09\u8a2d\u5b9a\u5370\u8868\u6a5f\u4ee5\u6574\u5408\u81f3 Home Assistant\u3002", "title": "\u9023\u7d50\u5370\u8868\u6a5f" diff --git a/homeassistant/components/iqvia/strings.json b/homeassistant/components/iqvia/strings.json index b0d82430ef7978..7e16d3ae6e38e2 100644 --- a/homeassistant/components/iqvia/strings.json +++ b/homeassistant/components/iqvia/strings.json @@ -13,7 +13,7 @@ "invalid_zip_code": "ZIP code is invalid" }, "abort": { - "already_configured": "This ZIP code has already been configured." + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]" } } } diff --git a/homeassistant/components/iqvia/translations/ko.json b/homeassistant/components/iqvia/translations/ko.json index f3dd4f82b6266b..f6a914bd07d9f0 100644 --- a/homeassistant/components/iqvia/translations/ko.json +++ b/homeassistant/components/iqvia/translations/ko.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "\uc774 \uc6b0\ud3b8 \ubc88\ud638\ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + }, "error": { "invalid_zip_code": "\uc6b0\ud3b8\ubc88\ud638\uac00 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, diff --git a/homeassistant/components/iqvia/translations/pl.json b/homeassistant/components/iqvia/translations/pl.json index f33a5257a609ba..e18f195d9b7b97 100644 --- a/homeassistant/components/iqvia/translations/pl.json +++ b/homeassistant/components/iqvia/translations/pl.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Ten kod pocztowy jest ju\u017c skonfigurowany." + }, "error": { "invalid_zip_code": "Kod pocztowy jest nieprawid\u0142owy" }, diff --git a/homeassistant/components/islamic_prayer_times/config_flow.py b/homeassistant/components/islamic_prayer_times/config_flow.py index d45997af76f62e..065af0bd6110f3 100644 --- a/homeassistant/components/islamic_prayer_times/config_flow.py +++ b/homeassistant/components/islamic_prayer_times/config_flow.py @@ -23,7 +23,7 @@ def async_get_options_flow(config_entry): async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" if self._async_current_entries(): - return self.async_abort(reason="one_instance_allowed") + return self.async_abort(reason="single_instance_allowed") if user_input is None: return self.async_show_form(step_id="user") diff --git a/homeassistant/components/islamic_prayer_times/strings.json b/homeassistant/components/islamic_prayer_times/strings.json index 857ce4c2dffb52..73998913f41fee 100644 --- a/homeassistant/components/islamic_prayer_times/strings.json +++ b/homeassistant/components/islamic_prayer_times/strings.json @@ -8,7 +8,7 @@ } }, "abort": { - "one_instance_allowed": "Only a single instance is necessary." + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" } }, "options": { diff --git a/homeassistant/components/islamic_prayer_times/translations/ca.json b/homeassistant/components/islamic_prayer_times/translations/ca.json index 6b01d442df8359..436534f1a3b339 100644 --- a/homeassistant/components/islamic_prayer_times/translations/ca.json +++ b/homeassistant/components/islamic_prayer_times/translations/ca.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia." + "one_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3.", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "step": { "user": { diff --git a/homeassistant/components/islamic_prayer_times/translations/el.json b/homeassistant/components/islamic_prayer_times/translations/el.json new file mode 100644 index 00000000000000..aecb2ee553fa42 --- /dev/null +++ b/homeassistant/components/islamic_prayer_times/translations/el.json @@ -0,0 +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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/islamic_prayer_times/translations/en.json b/homeassistant/components/islamic_prayer_times/translations/en.json index e9781c17fb1466..e028ee8fdf65fd 100644 --- a/homeassistant/components/islamic_prayer_times/translations/en.json +++ b/homeassistant/components/islamic_prayer_times/translations/en.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "one_instance_allowed": "Only a single instance is necessary." + "one_instance_allowed": "Already configured. Only a single configuration possible.", + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "step": { "user": { diff --git a/homeassistant/components/islamic_prayer_times/translations/es.json b/homeassistant/components/islamic_prayer_times/translations/es.json index 8dc6c5e5cf82ee..951ba94a7c1c4b 100644 --- a/homeassistant/components/islamic_prayer_times/translations/es.json +++ b/homeassistant/components/islamic_prayer_times/translations/es.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "one_instance_allowed": "S\u00f3lo se necesita una instancia." + "one_instance_allowed": "S\u00f3lo se necesita una instancia.", + "single_instance_allowed": "Ya est\u00e1 configurado. S\u00f3lo es posible una \u00fanica configuraci\u00f3n." }, "step": { "user": { diff --git a/homeassistant/components/islamic_prayer_times/translations/et.json b/homeassistant/components/islamic_prayer_times/translations/et.json new file mode 100644 index 00000000000000..e8b5eae41495c6 --- /dev/null +++ b/homeassistant/components/islamic_prayer_times/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/islamic_prayer_times/translations/fr.json b/homeassistant/components/islamic_prayer_times/translations/fr.json index 6499df244ab17c..6270620a412591 100644 --- a/homeassistant/components/islamic_prayer_times/translations/fr.json +++ b/homeassistant/components/islamic_prayer_times/translations/fr.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "one_instance_allowed": "Une seule instance est n\u00e9cessaire." + "one_instance_allowed": "Une seule instance est n\u00e9cessaire.", + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." }, "step": { "user": { diff --git a/homeassistant/components/islamic_prayer_times/translations/it.json b/homeassistant/components/islamic_prayer_times/translations/it.json index ff1d085a58dad0..a8a5f389f90d12 100644 --- a/homeassistant/components/islamic_prayer_times/translations/it.json +++ b/homeassistant/components/islamic_prayer_times/translations/it.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "one_instance_allowed": "\u00c8 necessaria solo una singola istanza." + "one_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione.", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "step": { "user": { diff --git a/homeassistant/components/islamic_prayer_times/translations/lb.json b/homeassistant/components/islamic_prayer_times/translations/lb.json index 7ba934174365fd..906cb0e3c20885 100644 --- a/homeassistant/components/islamic_prayer_times/translations/lb.json +++ b/homeassistant/components/islamic_prayer_times/translations/lb.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg." + "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg.", + "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech." }, "step": { "user": { diff --git a/homeassistant/components/islamic_prayer_times/translations/no.json b/homeassistant/components/islamic_prayer_times/translations/no.json index 59e601648ff176..5fc3230390ed25 100644 --- a/homeassistant/components/islamic_prayer_times/translations/no.json +++ b/homeassistant/components/islamic_prayer_times/translations/no.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "one_instance_allowed": "Kun en enkelt forekomst er n\u00f8dvendig." + "one_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig.", + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "step": { "user": { diff --git a/homeassistant/components/islamic_prayer_times/translations/ru.json b/homeassistant/components/islamic_prayer_times/translations/ru.json index 66f2e918f65719..696671eeb7e729 100644 --- a/homeassistant/components/islamic_prayer_times/translations/ru.json +++ b/homeassistant/components/islamic_prayer_times/translations/ru.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "one_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "one_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.", + "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/islamic_prayer_times/translations/zh-Hant.json b/homeassistant/components/islamic_prayer_times/translations/zh-Hant.json index b9dc6928e019af..2a2325bfc35df9 100644 --- a/homeassistant/components/islamic_prayer_times/translations/zh-Hant.json +++ b/homeassistant/components/islamic_prayer_times/translations/zh-Hant.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u5373\u53ef\u3002" + "one_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002", + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002" }, "step": { "user": { diff --git a/homeassistant/components/isy994/const.py b/homeassistant/components/isy994/const.py index d911fae2c82fc7..e003a52c91fbed 100644 --- a/homeassistant/components/isy994/const.py +++ b/homeassistant/components/isy994/const.py @@ -44,7 +44,10 @@ from homeassistant.components.sensor import DOMAIN as SENSOR from homeassistant.components.switch import DOMAIN as SWITCH from homeassistant.const import ( + AREA_SQUARE_METERS, CONCENTRATION_PARTS_PER_MILLION, + CURRENCY_CENT, + CURRENCY_DOLLAR, DEGREE, ENERGY_KILO_WATT_HOUR, FREQUENCY_HERTZ, @@ -54,11 +57,15 @@ LENGTH_KILOMETERS, LENGTH_METERS, LENGTH_MILES, + LENGTH_MILLIMETERS, + LIGHT_LUX, MASS_KILOGRAMS, MASS_POUNDS, PERCENTAGE, POWER_WATT, + PRESSURE_HPA, PRESSURE_INHG, + PRESSURE_MBAR, SERVICE_LOCK, SERVICE_UNLOCK, SPEED_KILOMETERS_PER_HOUR, @@ -86,6 +93,8 @@ TIME_YEARS, UV_INDEX, VOLT, + VOLUME_CUBIC_FEET, + VOLUME_CUBIC_METERS, VOLUME_GALLONS, VOLUME_LITERS, ) @@ -316,9 +325,9 @@ "3": f"btu/{TIME_HOURS}", "4": TEMP_CELSIUS, "5": LENGTH_CENTIMETERS, - "6": f"{LENGTH_FEET}³", - "7": f"{LENGTH_FEET}³/{TIME_MINUTES}", - "8": "m³", + "6": VOLUME_CUBIC_FEET, + "7": f"{VOLUME_CUBIC_FEET}/{TIME_MINUTES}", + "8": f"{VOLUME_CUBIC_METERS}", "9": TIME_DAYS, "10": TIME_DAYS, "12": "dB", @@ -344,17 +353,17 @@ "33": ENERGY_KILO_WATT_HOUR, "34": "liedu", "35": VOLUME_LITERS, - "36": "lx", + "36": LIGHT_LUX, "37": "mercalli", "38": LENGTH_METERS, - "39": f"{LENGTH_METERS}³/{TIME_HOURS}", + "39": f"{VOLUME_CUBIC_METERS}/{TIME_HOURS}", "40": SPEED_METERS_PER_SECOND, "41": "mA", "42": TIME_MILLISECONDS, "43": "mV", "44": TIME_MINUTES, "45": TIME_MINUTES, - "46": f"mm/{TIME_HOURS}", + "46": f"{LENGTH_MILLIMETERS}/{TIME_HOURS}", "47": TIME_MONTHS, "48": SPEED_MILES_PER_HOUR, "49": SPEED_METERS_PER_SECOND, @@ -377,15 +386,15 @@ "71": UV_INDEX, "72": VOLT, "73": POWER_WATT, - "74": f"{POWER_WATT}/{LENGTH_METERS}²", + "74": f"{POWER_WATT}/{AREA_SQUARE_METERS}", "75": "weekday", "76": DEGREE, "77": TIME_YEARS, - "82": "mm", + "82": LENGTH_MILLIMETERS, "83": LENGTH_KILOMETERS, "85": "Ω", "86": "kΩ", - "87": f"{LENGTH_METERS}³/{LENGTH_METERS}³", + "87": f"{VOLUME_CUBIC_METERS}/{VOLUME_CUBIC_METERS}", "88": "Water activity", "89": "RPM", "90": FREQUENCY_HERTZ, @@ -394,10 +403,10 @@ UOM_8_BIT_RANGE: "", # Range 0-255, no unit. UOM_DOUBLE_TEMP: UOM_DOUBLE_TEMP, "102": "kWs", - "103": "$", - "104": "¢", + "103": CURRENCY_DOLLAR, + "104": CURRENCY_CENT, "105": LENGTH_INCHES, - "106": f"mm/{TIME_DAYS}", + "106": f"{LENGTH_MILLIMETERS}/{TIME_DAYS}", "107": "", # raw 1-byte unsigned value "108": "", # raw 2-byte unsigned value "109": "", # raw 3-byte unsigned value @@ -407,8 +416,8 @@ "113": "", # raw 3-byte signed value "114": "", # raw 4-byte signed value "116": LENGTH_MILES, - "117": "mbar", - "118": "hPa", + "117": PRESSURE_MBAR, + "118": PRESSURE_HPA, "119": f"{POWER_WATT}{TIME_HOURS}", "120": f"{LENGTH_INCHES}/{TIME_DAYS}", } diff --git a/homeassistant/components/isy994/strings.json b/homeassistant/components/isy994/strings.json index ce9818bc0c8616..18e08b65009417 100644 --- a/homeassistant/components/isy994/strings.json +++ b/homeassistant/components/isy994/strings.json @@ -6,7 +6,7 @@ "user": { "data": { "username": "[%key:common::config_flow::data::username%]", - "host": "URL", + "host": "[%key:common::config_flow::data::url%]", "password": "[%key:common::config_flow::data::password%]", "tls": "The TLS version of the ISY controller." }, diff --git a/homeassistant/components/isy994/translations/de.json b/homeassistant/components/isy994/translations/de.json index b9c3362c488cb3..d14dfa6c65a16a 100644 --- a/homeassistant/components/isy994/translations/de.json +++ b/homeassistant/components/isy994/translations/de.json @@ -10,9 +10,20 @@ "step": { "user": { "data": { + "host": "URL", + "password": "Passwort", "username": "Benutzername" } } } + }, + "options": { + "step": { + "init": { + "data": { + "ignore_string": "Zeichenfolge ignorieren" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/isy994/translations/fr.json b/homeassistant/components/isy994/translations/fr.json index 102d1fabf052ad..5afaf2ad830e03 100644 --- a/homeassistant/components/isy994/translations/fr.json +++ b/homeassistant/components/isy994/translations/fr.json @@ -29,7 +29,8 @@ "data": { "ignore_string": "Ignorer la cha\u00eene", "restore_light_state": "Restaurer la luminosit\u00e9", - "sensor_string": "Node Sensor String" + "sensor_string": "Node Sensor String", + "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" diff --git a/homeassistant/components/isy994/translations/nl.json b/homeassistant/components/isy994/translations/nl.json index a39fc58dc25040..b252dde3257cab 100644 --- a/homeassistant/components/isy994/translations/nl.json +++ b/homeassistant/components/isy994/translations/nl.json @@ -1,5 +1,12 @@ { "config": { - "flow_title": "Universele apparaten ISY994 {name} ({host})" + "flow_title": "Universele apparaten ISY994 {name} ({host})", + "step": { + "user": { + "data": { + "username": "Gebruikersnaam" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/isy994/translations/pl.json b/homeassistant/components/isy994/translations/pl.json index 27f79ef2801b8f..d35b9e91eb6411 100644 --- a/homeassistant/components/isy994/translations/pl.json +++ b/homeassistant/components/isy994/translations/pl.json @@ -1,13 +1,13 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" }, "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", + "invalid_auth": "Niepoprawne uwierzytelnienie", "invalid_host": "Wpis hosta nie by\u0142 w pe\u0142nym formacie URL, np. http://192.168.10.100:80.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "flow_title": "Urz\u0105dzenia uniwersalne ISY994 {name} ({host})", "step": { diff --git a/homeassistant/components/izone/strings.json b/homeassistant/components/izone/strings.json index 9772bd4c93c182..7d1e8f1d4768ab 100644 --- a/homeassistant/components/izone/strings.json +++ b/homeassistant/components/izone/strings.json @@ -6,8 +6,8 @@ } }, "abort": { - "single_instance_allowed": "Only a single configuration of iZone is necessary.", - "no_devices_found": "No iZone devices found on the network." + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" } } } diff --git a/homeassistant/components/izone/translations/ca.json b/homeassistant/components/izone/translations/ca.json index 811b3cc8c29b2c..6c05ec8ce8bd89 100644 --- a/homeassistant/components/izone/translations/ca.json +++ b/homeassistant/components/izone/translations/ca.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "No s'han trobat dispositius iZone a la xarxa.", - "single_instance_allowed": "Nom\u00e9s cal una \u00fanica configuraci\u00f3 de iZone." + "no_devices_found": "No s'han trobat dispositius a la xarxa", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "step": { "confirm": { diff --git a/homeassistant/components/izone/translations/en.json b/homeassistant/components/izone/translations/en.json index 03cc003752b38e..f0ab3077adfc70 100644 --- a/homeassistant/components/izone/translations/en.json +++ b/homeassistant/components/izone/translations/en.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "No iZone devices found on the network.", - "single_instance_allowed": "Only a single configuration of iZone is necessary." + "no_devices_found": "No devices found on the network", + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "step": { "confirm": { diff --git a/homeassistant/components/izone/translations/it.json b/homeassistant/components/izone/translations/it.json index 79151ca44a4cd9..c141fadc27023f 100644 --- a/homeassistant/components/izone/translations/it.json +++ b/homeassistant/components/izone/translations/it.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Nessun dispositivo iZone trovato in rete.", - "single_instance_allowed": "\u00c8 necessaria una sola configurazione di iZone." + "no_devices_found": "Nessun dispositivo trovato sulla rete", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "step": { "confirm": { diff --git a/homeassistant/components/izone/translations/no.json b/homeassistant/components/izone/translations/no.json index 5c3e424833929b..84c50c19f77655 100644 --- a/homeassistant/components/izone/translations/no.json +++ b/homeassistant/components/izone/translations/no.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Finner ingen iZone-enheter p\u00e5 nettverket.", - "single_instance_allowed": "Bare en konfigurasjon av iZone er n\u00f8dvendig." + "no_devices_found": "Ingen enheter funnet p\u00e5 nettverket", + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "step": { "confirm": { diff --git a/homeassistant/components/izone/translations/ru.json b/homeassistant/components/izone/translations/ru.json index 23b0a85370caa4..d57ba2d0fca497 100644 --- a/homeassistant/components/izone/translations/ru.json +++ b/homeassistant/components/izone/translations/ru.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 iZone \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 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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." }, "step": { "confirm": { diff --git a/homeassistant/components/izone/translations/zh-Hant.json b/homeassistant/components/izone/translations/zh-Hant.json index 283136b5c8657b..f49de8669d10c6 100644 --- a/homeassistant/components/izone/translations/zh-Hant.json +++ b/homeassistant/components/izone/translations/zh-Hant.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "\u5728\u7db2\u8def\u4e0a\u627e\u4e0d\u5230 iZone \u8a2d\u5099\u3002", - "single_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u6b21 iZone \u5373\u53ef\u3002" + "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u8a2d\u5099", + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002" }, "step": { "confirm": { diff --git a/homeassistant/components/jewish_calendar/sensor.py b/homeassistant/components/jewish_calendar/sensor.py index 606f4fffab12f8..7b3a3af9c1185a 100644 --- a/homeassistant/components/jewish_calendar/sensor.py +++ b/homeassistant/components/jewish_calendar/sensor.py @@ -113,10 +113,9 @@ def make_zmanim(self, date): @property def device_state_attributes(self): """Return the state attributes.""" - if self._type == "holiday": - return self._holiday_attrs - - return {} + if self._type != "holiday": + return {} + return self._holiday_attrs def get_state(self, daytime_date, after_shkia_date, after_tzais_date): """For a given type of sensor, return the state.""" diff --git a/homeassistant/components/juicenet/strings.json b/homeassistant/components/juicenet/strings.json index 4c8ffb8c62f931..bc4a66e72d4361 100644 --- a/homeassistant/components/juicenet/strings.json +++ b/homeassistant/components/juicenet/strings.json @@ -1,17 +1,17 @@ { "config": { "abort": { - "already_configured": "This JuiceNet account is already configured" + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" }, "error": { - "cannot_connect": "Failed to connect, please try again", - "invalid_auth": "Invalid authentication", - "unknown": "Unexpected 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%]" }, "step": { "user": { "data": { - "api_token": "JuiceNet API Token" + "api_token": "[%key:common::config_flow::data::api_token%]" }, "description": "You will need the API Token from https://home.juice.net/Manage.", "title": "Connect to JuiceNet" diff --git a/homeassistant/components/juicenet/translations/ca.json b/homeassistant/components/juicenet/translations/ca.json index 1d7cad2111be77..f5df69210620e3 100644 --- a/homeassistant/components/juicenet/translations/ca.json +++ b/homeassistant/components/juicenet/translations/ca.json @@ -1,17 +1,17 @@ { "config": { "abort": { - "already_configured": "Aquest compte de JuiceNet ja est\u00e0 configurat" + "already_configured": "El compte ja ha estat configurat" }, "error": { - "cannot_connect": "No s'ha pogut connectar, torna-ho a provar", + "cannot_connect": "Ha fallat la connexi\u00f3", "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "unknown": "Error inesperat" }, "step": { "user": { "data": { - "api_token": "Token de l'API de JuiceNet" + "api_token": "Token d'API" }, "description": "Necessitar\u00e0s la clau API de https://home.juice.net/Manage.", "title": "Connexi\u00f3 amb JuiceNet" diff --git a/homeassistant/components/juicenet/translations/en.json b/homeassistant/components/juicenet/translations/en.json index faf21a8d6174bb..3d11f5de63a682 100644 --- a/homeassistant/components/juicenet/translations/en.json +++ b/homeassistant/components/juicenet/translations/en.json @@ -1,17 +1,17 @@ { "config": { "abort": { - "already_configured": "This JuiceNet account is already configured" + "already_configured": "Account is already configured" }, "error": { - "cannot_connect": "Failed to connect, please try again", + "cannot_connect": "Failed to connect", "invalid_auth": "Invalid authentication", "unknown": "Unexpected error" }, "step": { "user": { "data": { - "api_token": "JuiceNet API Token" + "api_token": "API Token" }, "description": "You will need the API Token from https://home.juice.net/Manage.", "title": "Connect to JuiceNet" diff --git a/homeassistant/components/juicenet/translations/it.json b/homeassistant/components/juicenet/translations/it.json index be8eee9745d88a..90e3ccd3c17f5b 100644 --- a/homeassistant/components/juicenet/translations/it.json +++ b/homeassistant/components/juicenet/translations/it.json @@ -1,17 +1,17 @@ { "config": { "abort": { - "already_configured": "Questo account JuiceNet \u00e8 gi\u00e0 configurato" + "already_configured": "L'account \u00e8 gi\u00e0 configurato" }, "error": { - "cannot_connect": "Impossibile connettersi, si prega di riprovare", + "cannot_connect": "Impossibile connettersi", "invalid_auth": "Autenticazione non valida", "unknown": "Errore imprevisto" }, "step": { "user": { "data": { - "api_token": "Token API JuiceNet" + "api_token": "Token API" }, "description": "Avrete bisogno del Token API da https://home.juice.net/Manage.", "title": "Connettersi a JuiceNet" diff --git a/homeassistant/components/juicenet/translations/no.json b/homeassistant/components/juicenet/translations/no.json index 1d0e3a15f5bc90..790dea03a02a1b 100644 --- a/homeassistant/components/juicenet/translations/no.json +++ b/homeassistant/components/juicenet/translations/no.json @@ -1,17 +1,17 @@ { "config": { "abort": { - "already_configured": "Denne JuiceNet-kontoen er allerede konfigurert" + "already_configured": "Kontoen er allerede konfigurert" }, "error": { - "cannot_connect": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen", + "cannot_connect": "Tilkobling mislyktes.", "invalid_auth": "Ugyldig godkjenning", "unknown": "Uventet feil" }, "step": { "user": { "data": { - "api_token": "JuiceNet API-token" + "api_token": "API-token" }, "description": "Du trenger API-tokenet fra https://home.juice.net/Manage.", "title": "Koble til JuiceNet" diff --git a/homeassistant/components/juicenet/translations/pl.json b/homeassistant/components/juicenet/translations/pl.json index 601ce0c91284ec..c308ad2524a81f 100644 --- a/homeassistant/components/juicenet/translations/pl.json +++ b/homeassistant/components/juicenet/translations/pl.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "Konto jest ju\u017c skonfigurowane." + "already_configured": "Konto jest ju\u017c skonfigurowane" }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "invalid_auth": "Niepoprawne uwierzytelnienie.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "user": { diff --git a/homeassistant/components/juicenet/translations/ru.json b/homeassistant/components/juicenet/translations/ru.json index 69b68f82990b47..2fec7d485c477c 100644 --- a/homeassistant/components/juicenet/translations/ru.json +++ b/homeassistant/components/juicenet/translations/ru.json @@ -4,7 +4,7 @@ "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": { - "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", + "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "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/juicenet/translations/zh-Hant.json b/homeassistant/components/juicenet/translations/zh-Hant.json index b54bb3d4676fbc..815edb1fb2710b 100644 --- a/homeassistant/components/juicenet/translations/zh-Hant.json +++ b/homeassistant/components/juicenet/translations/zh-Hant.json @@ -1,17 +1,17 @@ { "config": { "abort": { - "already_configured": "JuiceNet \u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21", + "cannot_connect": "\u9023\u7dda\u5931\u6557", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { "user": { "data": { - "api_token": "JuiceNet API \u5bc6\u9470" + "api_token": "API \u5bc6\u9470" }, "description": "\u5c07\u9700\u8981\u7531 https://home.juice.net/Manage \u53d6\u5f97 API \u5bc6\u9470\u3002", "title": "\u9023\u7dda\u81f3 JuiceNet" diff --git a/homeassistant/components/kankun/switch.py b/homeassistant/components/kankun/switch.py index ce179515e880f2..d1a616f86fa83f 100644 --- a/homeassistant/components/kankun/switch.py +++ b/homeassistant/components/kankun/switch.py @@ -94,11 +94,6 @@ def _query_state(self): except requests.RequestException: _LOGGER.error("State query failed") - @property - def should_poll(self): - """Return the polling state.""" - return True - @property def name(self): """Return the name of the switch.""" diff --git a/homeassistant/components/knx/__init__.py b/homeassistant/components/knx/__init__.py index 5a2f29e6247fd1..87ade4955a010e 100644 --- a/homeassistant/components/knx/__init__.py +++ b/homeassistant/components/knx/__init__.py @@ -6,7 +6,12 @@ from xknx.devices import DateTime, ExposeSensor from xknx.dpt import DPTArray, DPTBase, DPTBinary from xknx.exceptions import XKNXException -from xknx.io import DEFAULT_MCAST_PORT, ConnectionConfig, ConnectionType +from xknx.io import ( + DEFAULT_MCAST_GRP, + DEFAULT_MCAST_PORT, + ConnectionConfig, + ConnectionType, +) from xknx.telegram import AddressFilter, GroupAddress, Telegram from homeassistant.const import ( @@ -24,7 +29,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_state_change_event -from .const import DATA_KNX, DOMAIN, SupportedPlatforms +from .const import DOMAIN, SupportedPlatforms from .factory import create_knx_device from .schema import ( BinarySensorSchema, @@ -48,6 +53,9 @@ CONF_KNX_TUNNELING = "tunneling" CONF_KNX_FIRE_EVENT = "fire_event" CONF_KNX_FIRE_EVENT_FILTER = "fire_event_filter" +CONF_KNX_INDIVIDUAL_ADDRESS = "individual_address" +CONF_KNX_MCAST_GRP = "multicast_group" +CONF_KNX_MCAST_PORT = "multicast_port" CONF_KNX_STATE_UPDATER = "state_updater" CONF_KNX_RATE_LIMIT = "rate_limit" CONF_KNX_EXPOSE = "expose" @@ -72,6 +80,11 @@ vol.Inclusive(CONF_KNX_FIRE_EVENT_FILTER, "fire_ev"): vol.All( cv.ensure_list, [cv.string] ), + vol.Optional( + CONF_KNX_INDIVIDUAL_ADDRESS, default=XKNX.DEFAULT_ADDRESS + ): cv.string, + vol.Optional(CONF_KNX_MCAST_GRP, default=DEFAULT_MCAST_GRP): cv.string, + vol.Optional(CONF_KNX_MCAST_PORT, default=DEFAULT_MCAST_PORT): cv.port, vol.Optional(CONF_KNX_STATE_UPDATER, default=True): cv.boolean, vol.Optional(CONF_KNX_RATE_LIMIT, default=20): vol.All( vol.Coerce(int), vol.Range(min=1, max=100) @@ -126,21 +139,19 @@ async def async_setup(hass, config): """Set up the KNX component.""" try: - hass.data[DATA_KNX] = KNXModule(hass, config) - hass.data[DATA_KNX].async_create_exposures() - await hass.data[DATA_KNX].start() + hass.data[DOMAIN] = KNXModule(hass, config) + hass.data[DOMAIN].async_create_exposures() + await hass.data[DOMAIN].start() except XKNXException as ex: - _LOGGER.warning("Can't connect to KNX interface: %s", ex) + _LOGGER.warning("Could not connect to KNX interface: %s", ex) hass.components.persistent_notification.async_create( - f"Can't connect to KNX interface:
{ex}", title="KNX" + f"Could not connect to KNX interface:
{ex}", title="KNX" ) for platform in SupportedPlatforms: if platform.value in config[DOMAIN]: for device_config in config[DOMAIN][platform.value]: - create_knx_device( - hass, platform, hass.data[DATA_KNX].xknx, device_config - ) + create_knx_device(platform, hass.data[DOMAIN].xknx, device_config) # We need to wait until all entities are loaded into the device list since they could also be created from other platforms for platform in SupportedPlatforms: @@ -148,7 +159,7 @@ async def async_setup(hass, config): discovery.async_load_platform(hass, platform.value, DOMAIN, {}, config) ) - if not hass.data[DATA_KNX].xknx.devices: + if not hass.data[DOMAIN].xknx.devices: _LOGGER.warning( "No KNX devices are configured. Please read " "https://www.home-assistant.io/blog/2020/09/17/release-115/#breaking-changes" @@ -157,7 +168,7 @@ async def async_setup(hass, config): hass.services.async_register( DOMAIN, SERVICE_KNX_SEND, - hass.data[DATA_KNX].service_send_to_knx_bus, + hass.data[DOMAIN].service_send_to_knx_bus, schema=SERVICE_KNX_SEND_SCHEMA, ) @@ -180,17 +191,17 @@ def init_xknx(self): """Initialize of KNX object.""" self.xknx = XKNX( config=self.config_file(), - loop=self.hass.loop, + own_address=self.config[DOMAIN][CONF_KNX_INDIVIDUAL_ADDRESS], rate_limit=self.config[DOMAIN][CONF_KNX_RATE_LIMIT], + multicast_group=self.config[DOMAIN][CONF_KNX_MCAST_GRP], + multicast_port=self.config[DOMAIN][CONF_KNX_MCAST_PORT], + connection_config=self.connection_config(), + state_updater=self.config[DOMAIN][CONF_KNX_STATE_UPDATER], ) async def start(self): """Start KNX object. Connect to tunneling or Routing device.""" - connection_config = self.connection_config() - await self.xknx.start( - state_updater=self.config[DOMAIN][CONF_KNX_STATE_UPDATER], - connection_config=connection_config, - ) + await self.xknx.start() self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.stop) self.connected = True @@ -213,9 +224,8 @@ def connection_config(self): return self.connection_config_tunneling() if CONF_KNX_ROUTING in self.config[DOMAIN]: return self.connection_config_routing() - # return None to let xknx use config from xknx.yaml connection block if given - # otherwise it will use default ConnectionConfig (Automatic) - return None + # config from xknx.yaml always has priority later on + return ConnectionConfig() def connection_config_routing(self): """Return the connection_config if routing is configured.""" @@ -229,12 +239,10 @@ def connection_config_routing(self): def connection_config_tunneling(self): """Return the connection_config if tunneling is configured.""" gateway_ip = self.config[DOMAIN][CONF_KNX_TUNNELING][CONF_HOST] - gateway_port = self.config[DOMAIN][CONF_KNX_TUNNELING].get(CONF_PORT) + gateway_port = self.config[DOMAIN][CONF_KNX_TUNNELING][CONF_PORT] local_ip = self.config[DOMAIN][CONF_KNX_TUNNELING].get( ConnectionSchema.CONF_KNX_LOCAL_IP ) - if gateway_port is None: - gateway_port = DEFAULT_MCAST_PORT return ConnectionConfig( connection_type=ConnectionType.TUNNELING, gateway_ip=gateway_ip, @@ -267,7 +275,7 @@ def async_create_exposures(self): attribute = to_expose.get(ExposeSchema.CONF_KNX_EXPOSE_ATTRIBUTE) default = to_expose.get(ExposeSchema.CONF_KNX_EXPOSE_DEFAULT) address = to_expose.get(ExposeSchema.CONF_KNX_EXPOSE_ADDRESS) - if expose_type in ["time", "date", "datetime"]: + if expose_type.lower() in ["time", "date", "datetime"]: exposure = KNXExposeTime(self.xknx, expose_type, address) exposure.async_register() self.exposures.append(exposure) @@ -313,29 +321,29 @@ def calculate_payload(attr_payload): payload = calculate_payload(attr_payload) address = GroupAddress(attr_address) - telegram = Telegram() - telegram.payload = payload - telegram.group_address = address + telegram = Telegram(group_address=address, payload=payload) await self.xknx.telegrams.put(telegram) class KNXExposeTime: """Object to Expose Time/Date object to KNX bus.""" - def __init__(self, xknx, expose_type, address): + def __init__(self, xknx: XKNX, expose_type: str, address: str): """Initialize of Expose class.""" self.xknx = xknx - self.type = expose_type + self.expose_type = expose_type self.address = address self.device = None @callback def async_register(self): """Register listener.""" - broadcast_type_string = self.type.upper() - broadcast_type = broadcast_type_string self.device = DateTime( - self.xknx, "Time", broadcast_type=broadcast_type, group_address=self.address + self.xknx, + name=self.expose_type.capitalize(), + broadcast_type=self.expose_type.upper(), + localtime=True, + group_address=self.address, ) diff --git a/homeassistant/components/knx/binary_sensor.py b/homeassistant/components/knx/binary_sensor.py index f3b7e881134252..a62e95f1defa19 100644 --- a/homeassistant/components/knx/binary_sensor.py +++ b/homeassistant/components/knx/binary_sensor.py @@ -1,67 +1,43 @@ """Support for KNX/IP binary sensors.""" +from typing import Any, Dict, Optional + from xknx.devices import BinarySensor as XknxBinarySensor -from homeassistant.components.binary_sensor import BinarySensorEntity -from homeassistant.core import callback +from homeassistant.components.binary_sensor import DEVICE_CLASSES, BinarySensorEntity -from . import DATA_KNX +from .const import ATTR_COUNTER, DOMAIN +from .knx_entity import KnxEntity async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up binary sensor(s) for KNX platform.""" entities = [] - for device in hass.data[DATA_KNX].xknx.devices: + for device in hass.data[DOMAIN].xknx.devices: if isinstance(device, XknxBinarySensor): entities.append(KNXBinarySensor(device)) async_add_entities(entities) -class KNXBinarySensor(BinarySensorEntity): +class KNXBinarySensor(KnxEntity, BinarySensorEntity): """Representation of a KNX binary sensor.""" def __init__(self, device: XknxBinarySensor): """Initialize of KNX binary sensor.""" - self.device = device - - @callback - def async_register_callbacks(self): - """Register callbacks to update hass after device was changed.""" - - async def after_update_callback(device): - """Call after device was updated.""" - self.async_write_ha_state() - - self.device.register_device_updated_cb(after_update_callback) - - async def async_added_to_hass(self): - """Store register state change callback.""" - self.async_register_callbacks() - - async def async_update(self): - """Request a state update from KNX bus.""" - await self.device.sync() - - @property - def name(self): - """Return the name of the KNX device.""" - return self.device.name - - @property - def available(self): - """Return True if entity is available.""" - return self.hass.data[DATA_KNX].connected - - @property - def should_poll(self): - """No polling needed within KNX.""" - return False + super().__init__(device) @property def device_class(self): """Return the class of this sensor.""" - return self.device.device_class + if self._device.device_class in DEVICE_CLASSES: + return self._device.device_class + return None @property def is_on(self): """Return true if the binary sensor is on.""" - return self.device.is_on() + return self._device.is_on() + + @property + def device_state_attributes(self) -> Optional[Dict[str, Any]]: + """Return device specific state attributes.""" + return {ATTR_COUNTER: self._device.counter} diff --git a/homeassistant/components/knx/climate.py b/homeassistant/components/knx/climate.py index b5aaeb679073a8..1960627a8d6bb5 100644 --- a/homeassistant/components/knx/climate.py +++ b/homeassistant/components/knx/climate.py @@ -14,8 +14,8 @@ ) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS -from . import DATA_KNX -from .const import OPERATION_MODES, PRESET_MODES +from .const import DOMAIN, OPERATION_MODES, PRESET_MODES +from .knx_entity import KnxEntity OPERATION_MODES_INV = dict(reversed(item) for item in OPERATION_MODES.items()) PRESET_MODES_INV = dict(reversed(item) for item in PRESET_MODES.items()) @@ -24,18 +24,19 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up climate(s) for KNX platform.""" entities = [] - for device in hass.data[DATA_KNX].xknx.devices: + for device in hass.data[DOMAIN].xknx.devices: if isinstance(device, XknxClimate): entities.append(KNXClimate(device)) async_add_entities(entities) -class KNXClimate(ClimateEntity): +class KNXClimate(KnxEntity, ClimateEntity): """Representation of a KNX climate device.""" def __init__(self, device: XknxClimate): """Initialize of a KNX climate device.""" - self.device = device + super().__init__(device) + self._unit_of_measurement = TEMP_CELSIUS @property @@ -43,35 +44,10 @@ def supported_features(self) -> int: """Return the list of supported features.""" return SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE - async def async_added_to_hass(self) -> None: - """Register callbacks to update hass after device was changed.""" - - async def after_update_callback(device): - """Call after device was updated.""" - self.async_write_ha_state() - - self.device.register_device_updated_cb(after_update_callback) - self.device.mode.register_device_updated_cb(after_update_callback) - async def async_update(self): """Request a state update from KNX bus.""" - await self.device.sync() - await self.device.mode.sync() - - @property - def name(self) -> str: - """Return the name of the KNX device.""" - return self.device.name - - @property - def available(self) -> bool: - """Return True if entity is available.""" - return self.hass.data[DATA_KNX].connected - - @property - def should_poll(self) -> bool: - """No polling needed within KNX.""" - return False + await self._device.sync() + await self._device.mode.sync() @property def temperature_unit(self): @@ -81,44 +57,44 @@ def temperature_unit(self): @property def current_temperature(self): """Return the current temperature.""" - return self.device.temperature.value + return self._device.temperature.value @property def target_temperature_step(self): """Return the supported step of target temperature.""" - return self.device.temperature_step + return self._device.temperature_step @property def target_temperature(self): """Return the temperature we try to reach.""" - return self.device.target_temperature.value + return self._device.target_temperature.value @property def min_temp(self): """Return the minimum temperature.""" - return self.device.target_temperature_min + return self._device.target_temperature_min @property def max_temp(self): """Return the maximum temperature.""" - return self.device.target_temperature_max + return self._device.target_temperature_max async def async_set_temperature(self, **kwargs) -> None: """Set new target temperature.""" temperature = kwargs.get(ATTR_TEMPERATURE) if temperature is None: return - await self.device.set_target_temperature(temperature) + await self._device.set_target_temperature(temperature) self.async_write_ha_state() @property def hvac_mode(self) -> Optional[str]: """Return current operation ie. heat, cool, idle.""" - if self.device.supports_on_off and not self.device.is_on: + if self._device.supports_on_off and not self._device.is_on: return HVAC_MODE_OFF - if self.device.mode.supports_operation_mode: + if self._device.mode.supports_operation_mode: return OPERATION_MODES.get( - self.device.mode.operation_mode.value, HVAC_MODE_HEAT + self._device.mode.operation_mode.value, HVAC_MODE_HEAT ) # default to "heat" return HVAC_MODE_HEAT @@ -128,10 +104,10 @@ def hvac_modes(self) -> Optional[List[str]]: """Return the list of available operation modes.""" _operations = [ OPERATION_MODES.get(operation_mode.value) - for operation_mode in self.device.mode.operation_modes + for operation_mode in self._device.mode.operation_modes ] - if self.device.supports_on_off: + if self._device.supports_on_off: if not _operations: _operations.append(HVAC_MODE_HEAT) _operations.append(HVAC_MODE_OFF) @@ -142,16 +118,16 @@ def hvac_modes(self) -> Optional[List[str]]: async def async_set_hvac_mode(self, hvac_mode: str) -> None: """Set operation mode.""" - if self.device.supports_on_off and hvac_mode == HVAC_MODE_OFF: - await self.device.turn_off() + if self._device.supports_on_off and hvac_mode == HVAC_MODE_OFF: + await self._device.turn_off() else: - if self.device.supports_on_off and not self.device.is_on: - await self.device.turn_on() - if self.device.mode.supports_operation_mode: + if self._device.supports_on_off and not self._device.is_on: + await self._device.turn_on() + if self._device.mode.supports_operation_mode: knx_operation_mode = HVACOperationMode( OPERATION_MODES_INV.get(hvac_mode) ) - await self.device.mode.set_operation_mode(knx_operation_mode) + await self._device.mode.set_operation_mode(knx_operation_mode) self.async_write_ha_state() @property @@ -160,8 +136,8 @@ def preset_mode(self) -> Optional[str]: Requires SUPPORT_PRESET_MODE. """ - if self.device.mode.supports_operation_mode: - return PRESET_MODES.get(self.device.mode.operation_mode.value, PRESET_AWAY) + if self._device.mode.supports_operation_mode: + return PRESET_MODES.get(self._device.mode.operation_mode.value, PRESET_AWAY) return None @property @@ -172,14 +148,14 @@ def preset_modes(self) -> Optional[List[str]]: """ _presets = [ PRESET_MODES.get(operation_mode.value) - for operation_mode in self.device.mode.operation_modes + for operation_mode in self._device.mode.operation_modes ] return list(filter(None, _presets)) async def async_set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" - if self.device.mode.supports_operation_mode: + if self._device.mode.supports_operation_mode: knx_operation_mode = HVACOperationMode(PRESET_MODES_INV.get(preset_mode)) - await self.device.mode.set_operation_mode(knx_operation_mode) + await self._device.mode.set_operation_mode(knx_operation_mode) self.async_write_ha_state() diff --git a/homeassistant/components/knx/const.py b/homeassistant/components/knx/const.py index a81fc526415e4f..8b0dd90393b91a 100644 --- a/homeassistant/components/knx/const.py +++ b/homeassistant/components/knx/const.py @@ -15,7 +15,6 @@ ) DOMAIN = "knx" -DATA_KNX = "data_knx" CONF_STATE_ADDRESS = "state_address" CONF_SYNC_STATE = "sync_state" @@ -60,3 +59,5 @@ class SupportedPlatforms(Enum): "Standby": PRESET_AWAY, "Comfort": PRESET_COMFORT, } + +ATTR_COUNTER = "counter" diff --git a/homeassistant/components/knx/cover.py b/homeassistant/components/knx/cover.py index 8c50bb2afe9cc8..c677b12c0ee986 100644 --- a/homeassistant/components/knx/cover.py +++ b/homeassistant/components/knx/cover.py @@ -15,65 +15,39 @@ from homeassistant.core import callback from homeassistant.helpers.event import async_track_utc_time_change -from . import DATA_KNX +from .const import DOMAIN +from .knx_entity import KnxEntity async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up cover(s) for KNX platform.""" entities = [] - for device in hass.data[DATA_KNX].xknx.devices: + for device in hass.data[DOMAIN].xknx.devices: if isinstance(device, XknxCover): entities.append(KNXCover(device)) async_add_entities(entities) -class KNXCover(CoverEntity): +class KNXCover(KnxEntity, CoverEntity): """Representation of a KNX cover.""" def __init__(self, device: XknxCover): """Initialize the cover.""" - self.device = device + super().__init__(device) + self._unsubscribe_auto_updater = None @callback - def async_register_callbacks(self): - """Register callbacks to update hass after device was changed.""" - - async def after_update_callback(device): - """Call after device was updated.""" - self.async_write_ha_state() - if self.device.is_traveling(): - self.start_auto_updater() - - self.device.register_device_updated_cb(after_update_callback) - - async def async_added_to_hass(self): - """Store register state change callback.""" - self.async_register_callbacks() - - async def async_update(self): - """Request a state update from KNX bus.""" - await self.device.sync() - - @property - def name(self): - """Return the name of the KNX device.""" - return self.device.name - - @property - def available(self): - """Return True if entity is available.""" - return self.hass.data[DATA_KNX].connected - - @property - def should_poll(self): - """No polling needed within KNX.""" - return False + async def after_update_callback(self, device): + """Call after device was updated.""" + self.async_write_ha_state() + if self._device.is_traveling(): + self.start_auto_updater() @property def device_class(self): """Return the class of this device, from component DEVICE_CLASSES.""" - if self.device.supports_angle: + if self._device.supports_angle: return DEVICE_CLASS_BLIND return None @@ -81,9 +55,9 @@ def device_class(self): def supported_features(self): """Flag supported features.""" supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION - if self.device.supports_stop: + if self._device.supports_stop: supported_features |= SUPPORT_STOP - if self.device.supports_angle: + if self._device.supports_angle: supported_features |= SUPPORT_SET_TILT_POSITION return supported_features @@ -95,57 +69,57 @@ def current_cover_position(self): """ # In KNX 0 is open, 100 is closed. try: - return 100 - self.device.current_position() + return 100 - self._device.current_position() except TypeError: return None @property def is_closed(self): """Return if the cover is closed.""" - return self.device.is_closed() + return self._device.is_closed() @property def is_opening(self): """Return if the cover is opening or not.""" - return self.device.is_opening() + return self._device.is_opening() @property def is_closing(self): """Return if the cover is closing or not.""" - return self.device.is_closing() + return self._device.is_closing() async def async_close_cover(self, **kwargs): """Close the cover.""" - await self.device.set_down() + await self._device.set_down() async def async_open_cover(self, **kwargs): """Open the cover.""" - await self.device.set_up() + await self._device.set_up() async def async_set_cover_position(self, **kwargs): """Move the cover to a specific position.""" knx_position = 100 - kwargs[ATTR_POSITION] - await self.device.set_position(knx_position) + await self._device.set_position(knx_position) async def async_stop_cover(self, **kwargs): """Stop the cover.""" - await self.device.stop() + await self._device.stop() self.stop_auto_updater() @property def current_cover_tilt_position(self): """Return current tilt position of cover.""" - if not self.device.supports_angle: + if not self._device.supports_angle: return None try: - return 100 - self.device.current_angle() + return 100 - self._device.current_angle() except TypeError: return None async def async_set_cover_tilt_position(self, **kwargs): """Move the cover tilt to a specific position.""" knx_tilt_position = 100 - kwargs[ATTR_TILT_POSITION] - await self.device.set_angle(knx_tilt_position) + await self._device.set_angle(knx_tilt_position) def start_auto_updater(self): """Start the autoupdater to update Home Assistant while cover is moving.""" @@ -164,7 +138,7 @@ def stop_auto_updater(self): def auto_updater_hook(self, now): """Call for the autoupdater.""" self.async_write_ha_state() - if self.device.position_reached(): + if self._device.position_reached(): self.stop_auto_updater() - self.hass.add_job(self.device.auto_stop_if_necessary()) + self.hass.add_job(self._device.auto_stop_if_necessary()) diff --git a/homeassistant/components/knx/factory.py b/homeassistant/components/knx/factory.py index 42c4dd675f5c76..3334e49ce38cae 100644 --- a/homeassistant/components/knx/factory.py +++ b/homeassistant/components/knx/factory.py @@ -1,7 +1,6 @@ """Factory function to initialize KNX devices from config.""" from xknx import XKNX from xknx.devices import ( - ActionCallback as XknxActionCallback, BinarySensor as XknxBinarySensor, Climate as XknxClimate, ClimateMode as XknxClimateMode, @@ -16,11 +15,9 @@ ) from homeassistant.const import CONF_ADDRESS, CONF_DEVICE_CLASS, CONF_NAME, CONF_TYPE -from homeassistant.core import HomeAssistant -from homeassistant.helpers.script import Script from homeassistant.helpers.typing import ConfigType -from .const import DOMAIN, ColorTempModes, SupportedPlatforms +from .const import ColorTempModes, SupportedPlatforms from .schema import ( BinarySensorSchema, ClimateSchema, @@ -34,7 +31,6 @@ def create_knx_device( - hass: HomeAssistant, platform: SupportedPlatforms, knx_module: XKNX, config: ConfigType, @@ -62,7 +58,7 @@ def create_knx_device( return _create_scene(knx_module, config) if platform is SupportedPlatforms.binary_sensor: - return _create_binary_sensor(hass, knx_module, config) + return _create_binary_sensor(knx_module, config) if platform is SupportedPlatforms.weather: return _create_weather(knx_module, config) @@ -239,24 +235,9 @@ def _create_scene(knx_module: XKNX, config: ConfigType) -> XknxScene: ) -def _create_binary_sensor( - hass: HomeAssistant, knx_module: XKNX, config: ConfigType -) -> XknxBinarySensor: +def _create_binary_sensor(knx_module: XKNX, config: ConfigType) -> XknxBinarySensor: """Return a KNX binary sensor to be used within XKNX.""" device_name = config[CONF_NAME] - actions = [] - automations = config.get(BinarySensorSchema.CONF_AUTOMATION) - if automations is not None: - for automation in automations: - counter = automation[BinarySensorSchema.CONF_COUNTER] - hook = automation[BinarySensorSchema.CONF_HOOK] - action = automation[BinarySensorSchema.CONF_ACTION] - script_name = f"{device_name} turn ON script" - script = Script(hass, action, script_name, DOMAIN) - action = XknxActionCallback( - knx_module, script.async_run, hook=hook, counter=counter - ) - actions.append(action) return XknxBinarySensor( knx_module, @@ -265,8 +246,8 @@ def _create_binary_sensor( sync_state=config[BinarySensorSchema.CONF_SYNC_STATE], device_class=config.get(CONF_DEVICE_CLASS), ignore_internal_state=config[BinarySensorSchema.CONF_IGNORE_INTERNAL_STATE], + context_timeout=config[BinarySensorSchema.CONF_CONTEXT_TIMEOUT], reset_after=config.get(BinarySensorSchema.CONF_RESET_AFTER), - actions=actions, ) @@ -287,6 +268,9 @@ def _create_weather(knx_module: XKNX, config: ConfigType) -> XknxWeather: group_address_brightness_west=config.get( WeatherSchema.CONF_KNX_BRIGHTNESS_WEST_ADDRESS ), + group_address_brightness_north=config.get( + WeatherSchema.CONF_KNX_BRIGHTNESS_NORTH_ADDRESS + ), group_address_wind_speed=config.get(WeatherSchema.CONF_KNX_WIND_SPEED_ADDRESS), group_address_rain_alarm=config.get(WeatherSchema.CONF_KNX_RAIN_ALARM_ADDRESS), group_address_frost_alarm=config.get( diff --git a/homeassistant/components/knx/knx_entity.py b/homeassistant/components/knx/knx_entity.py new file mode 100644 index 00000000000000..296bcb2f5405fb --- /dev/null +++ b/homeassistant/components/knx/knx_entity.py @@ -0,0 +1,51 @@ +"""Base class for KNX devices.""" +from xknx.devices import Climate as XknxClimate, Device as XknxDevice + +from homeassistant.helpers.entity import Entity + +from .const import DOMAIN + + +class KnxEntity(Entity): + """Representation of a KNX entity.""" + + def __init__(self, device: XknxDevice): + """Set up device.""" + self._device = device + + @property + def name(self): + """Return the name of the KNX device.""" + return self._device.name + + @property + def available(self): + """Return True if entity is available.""" + return self.hass.data[DOMAIN].connected + + @property + def should_poll(self): + """No polling needed within KNX.""" + return False + + async def async_update(self): + """Request a state update from KNX bus.""" + await self._device.sync() + + async def after_update_callback(self, device: XknxDevice): + """Call after device was updated.""" + self.async_write_ha_state() + + async def async_added_to_hass(self) -> None: + """Store register state change callback.""" + self._device.register_device_updated_cb(self.after_update_callback) + + if isinstance(self._device, XknxClimate): + self._device.mode.register_device_updated_cb(self.after_update_callback) + + async def async_will_remove_from_hass(self) -> None: + """Disconnect device object when removed.""" + self._device.unregister_device_updated_cb(self.after_update_callback) + + if isinstance(self._device, XknxClimate): + self._device.mode.unregister_device_updated_cb(self.after_update_callback) diff --git a/homeassistant/components/knx/light.py b/homeassistant/components/knx/light.py index 6d8438df0f92f9..d9f0f9c0d3afe6 100644 --- a/homeassistant/components/knx/light.py +++ b/homeassistant/components/knx/light.py @@ -12,10 +12,10 @@ SUPPORT_WHITE_VALUE, LightEntity, ) -from homeassistant.core import callback import homeassistant.util.color as color_util -from . import DATA_KNX +from .const import DOMAIN +from .knx_entity import KnxEntity DEFAULT_COLOR = (0.0, 0.0) DEFAULT_BRIGHTNESS = 255 @@ -25,18 +25,18 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up lights for KNX platform.""" entities = [] - for device in hass.data[DATA_KNX].xknx.devices: + for device in hass.data[DOMAIN].xknx.devices: if isinstance(device, XknxLight): entities.append(KNXLight(device)) async_add_entities(entities) -class KNXLight(LightEntity): +class KNXLight(KnxEntity, LightEntity): """Representation of a KNX light.""" def __init__(self, device: XknxLight): """Initialize of KNX light.""" - self.device = device + super().__init__(device) self._min_kelvin = device.min_kelvin self._max_kelvin = device.max_kelvin @@ -47,46 +47,13 @@ def __init__(self, device: XknxLight): self._min_kelvin ) - @callback - def async_register_callbacks(self): - """Register callbacks to update hass after device was changed.""" - - async def after_update_callback(device): - """Call after device was updated.""" - self.async_write_ha_state() - - self.device.register_device_updated_cb(after_update_callback) - - async def async_added_to_hass(self): - """Store register state change callback.""" - self.async_register_callbacks() - - async def async_update(self): - """Request a state update from KNX bus.""" - await self.device.sync() - - @property - def name(self): - """Return the name of the KNX device.""" - return self.device.name - - @property - def available(self): - """Return True if entity is available.""" - return self.hass.data[DATA_KNX].connected - - @property - def should_poll(self): - """No polling needed within KNX.""" - return False - @property def brightness(self): """Return the brightness of this light between 0..255.""" - if self.device.supports_brightness: - return self.device.current_brightness + if self._device.supports_brightness: + return self._device.current_brightness hsv_color = self._hsv_color - if self.device.supports_color and hsv_color: + if self._device.supports_color and hsv_color: return round(hsv_color[-1] / 100 * 255) return None @@ -94,35 +61,35 @@ def brightness(self): def hs_color(self): """Return the HS color value.""" rgb = None - if self.device.supports_rgbw or self.device.supports_color: - rgb, _ = self.device.current_color + if self._device.supports_rgbw or self._device.supports_color: + rgb, _ = self._device.current_color return color_util.color_RGB_to_hs(*rgb) if rgb else None @property def _hsv_color(self): """Return the HSV color value.""" rgb = None - if self.device.supports_rgbw or self.device.supports_color: - rgb, _ = self.device.current_color + if self._device.supports_rgbw or self._device.supports_color: + rgb, _ = self._device.current_color return color_util.color_RGB_to_hsv(*rgb) if rgb else None @property def white_value(self): """Return the white value.""" white = None - if self.device.supports_rgbw: - _, white = self.device.current_color + if self._device.supports_rgbw: + _, white = self._device.current_color return white @property def color_temp(self): """Return the color temperature in mireds.""" - if self.device.supports_color_temperature: - kelvin = self.device.current_color_temperature + if self._device.supports_color_temperature: + kelvin = self._device.current_color_temperature if kelvin is not None: return color_util.color_temperature_kelvin_to_mired(kelvin) - if self.device.supports_tunable_white: - relative_ct = self.device.current_tunable_white + if self._device.supports_tunable_white: + relative_ct = self._device.current_tunable_white if relative_ct is not None: # as KNX devices typically use Kelvin we use it as base for # calculating ct from percent @@ -155,19 +122,22 @@ def effect(self): @property def is_on(self): """Return true if light is on.""" - return self.device.state + return self._device.state @property def supported_features(self): """Flag supported features.""" flags = 0 - if self.device.supports_brightness: + if self._device.supports_brightness: flags |= SUPPORT_BRIGHTNESS - if self.device.supports_color: + if self._device.supports_color: flags |= SUPPORT_COLOR | SUPPORT_BRIGHTNESS - if self.device.supports_rgbw: + if self._device.supports_rgbw: flags |= SUPPORT_COLOR | SUPPORT_WHITE_VALUE - if self.device.supports_color_temperature or self.device.supports_tunable_white: + if ( + self._device.supports_color_temperature + or self._device.supports_tunable_white + ): flags |= SUPPORT_COLOR_TEMP return flags @@ -191,14 +161,16 @@ async def async_turn_on(self, **kwargs): or update_white_value or update_color_temp ): - await self.device.set_on() + await self._device.set_on() - if self.device.supports_brightness and (update_brightness and not update_color): + if self._device.supports_brightness and ( + update_brightness and not update_color + ): # if we don't need to update the color, try updating brightness # directly if supported; don't do it if color also has to be # changed, as RGB color implicitly sets the brightness as well - await self.device.set_brightness(brightness) - elif (self.device.supports_rgbw or self.device.supports_color) and ( + await self._device.set_brightness(brightness) + elif (self._device.supports_rgbw or self._device.supports_color) and ( update_brightness or update_color or update_white_value ): # change RGB color, white value (if supported), and brightness @@ -208,25 +180,25 @@ async def async_turn_on(self, **kwargs): brightness = DEFAULT_BRIGHTNESS if hs_color is None: hs_color = DEFAULT_COLOR - if white_value is None and self.device.supports_rgbw: + if white_value is None and self._device.supports_rgbw: white_value = DEFAULT_WHITE_VALUE rgb = color_util.color_hsv_to_RGB(*hs_color, brightness * 100 / 255) - await self.device.set_color(rgb, white_value) + await self._device.set_color(rgb, white_value) if update_color_temp: kelvin = int(color_util.color_temperature_mired_to_kelvin(mireds)) kelvin = min(self._max_kelvin, max(self._min_kelvin, kelvin)) - if self.device.supports_color_temperature: - await self.device.set_color_temperature(kelvin) - elif self.device.supports_tunable_white: + if self._device.supports_color_temperature: + await self._device.set_color_temperature(kelvin) + elif self._device.supports_tunable_white: relative_ct = int( 255 * (kelvin - self._min_kelvin) / (self._max_kelvin - self._min_kelvin) ) - await self.device.set_tunable_white(relative_ct) + await self._device.set_tunable_white(relative_ct) async def async_turn_off(self, **kwargs): """Turn the light off.""" - await self.device.set_off() + await self._device.set_off() diff --git a/homeassistant/components/knx/manifest.json b/homeassistant/components/knx/manifest.json index 8986d85b8b6f5a..2d387f0653de14 100644 --- a/homeassistant/components/knx/manifest.json +++ b/homeassistant/components/knx/manifest.json @@ -2,6 +2,7 @@ "domain": "knx", "name": "KNX", "documentation": "https://www.home-assistant.io/integrations/knx", - "requirements": ["xknx==0.13.0"], - "codeowners": ["@Julius2342", "@farmio", "@marvin-w"] + "requirements": ["xknx==0.15.0"], + "codeowners": ["@Julius2342", "@farmio", "@marvin-w"], + "quality_scale": "silver" } diff --git a/homeassistant/components/knx/notify.py b/homeassistant/components/knx/notify.py index e47cfca2794e77..7210795bd71ab8 100644 --- a/homeassistant/components/knx/notify.py +++ b/homeassistant/components/knx/notify.py @@ -5,13 +5,13 @@ from homeassistant.components.notify import BaseNotificationService -from . import DATA_KNX +from .const import DOMAIN async def async_get_service(hass, config, discovery_info=None): """Get the KNX notification service.""" notification_devices = [] - for device in hass.data[DATA_KNX].xknx.devices: + for device in hass.data[DOMAIN].xknx.devices: if isinstance(device, XknxNotification): notification_devices.append(device) return ( diff --git a/homeassistant/components/knx/scene.py b/homeassistant/components/knx/scene.py index b4df94a0fd4d34..6c76fdbd199e2b 100644 --- a/homeassistant/components/knx/scene.py +++ b/homeassistant/components/knx/scene.py @@ -5,30 +5,26 @@ from homeassistant.components.scene import Scene -from . import DATA_KNX +from .const import DOMAIN +from .knx_entity import KnxEntity async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the scenes for KNX platform.""" entities = [] - for device in hass.data[DATA_KNX].xknx.devices: + for device in hass.data[DOMAIN].xknx.devices: if isinstance(device, XknxScene): entities.append(KNXScene(device)) async_add_entities(entities) -class KNXScene(Scene): +class KNXScene(KnxEntity, Scene): """Representation of a KNX scene.""" - def __init__(self, scene: XknxScene): + def __init__(self, device: XknxScene): """Init KNX scene.""" - self.scene = scene - - @property - def name(self): - """Return the name of the scene.""" - return self.scene.name + super().__init__(device) async def async_activate(self, **kwargs: Any) -> None: """Activate the scene.""" - await self.scene.run() + await self._device.run() diff --git a/homeassistant/components/knx/schema.py b/homeassistant/components/knx/schema.py index a436f2dcdc8e7a..84a54536db5ddb 100644 --- a/homeassistant/components/knx/schema.py +++ b/homeassistant/components/knx/schema.py @@ -1,6 +1,7 @@ """Voluptuous schemas for the KNX integration.""" import voluptuous as vol from xknx.devices.climate import SetpointShiftMode +from xknx.io import DEFAULT_MCAST_PORT from homeassistant.const import ( CONF_ADDRESS, @@ -29,9 +30,9 @@ class ConnectionSchema: TUNNELING_SCHEMA = vol.Schema( { + vol.Optional(CONF_PORT, default=DEFAULT_MCAST_PORT): cv.port, vol.Required(CONF_HOST): cv.string, vol.Optional(CONF_KNX_LOCAL_IP): cv.string, - vol.Optional(CONF_PORT): cv.port, } ) @@ -84,27 +85,14 @@ class BinarySensorSchema: CONF_STATE_ADDRESS = CONF_STATE_ADDRESS CONF_SYNC_STATE = CONF_SYNC_STATE CONF_IGNORE_INTERNAL_STATE = "ignore_internal_state" - CONF_AUTOMATION = "automation" - CONF_HOOK = "hook" - CONF_DEFAULT_HOOK = "on" - CONF_COUNTER = "counter" - CONF_DEFAULT_COUNTER = 1 - CONF_ACTION = "action" + CONF_CONTEXT_TIMEOUT = "context_timeout" CONF_RESET_AFTER = "reset_after" DEFAULT_NAME = "KNX Binary Sensor" - AUTOMATION_SCHEMA = vol.Schema( - { - vol.Optional(CONF_HOOK, default=CONF_DEFAULT_HOOK): cv.string, - vol.Optional(CONF_COUNTER, default=CONF_DEFAULT_COUNTER): cv.port, - vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA, - } - ) - - AUTOMATIONS_SCHEMA = vol.All(cv.ensure_list, [AUTOMATION_SCHEMA]) SCHEMA = vol.All( cv.deprecated("significant_bit"), + cv.deprecated("automation"), vol.Schema( { vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, @@ -113,11 +101,13 @@ class BinarySensorSchema: cv.boolean, cv.string, ), - vol.Optional(CONF_IGNORE_INTERNAL_STATE, default=False): cv.boolean, + vol.Optional(CONF_IGNORE_INTERNAL_STATE, default=True): cv.boolean, + vol.Optional(CONF_CONTEXT_TIMEOUT, default=1.0): vol.All( + vol.Coerce(float), vol.Range(min=0, max=10) + ), vol.Required(CONF_STATE_ADDRESS): cv.string, vol.Optional(CONF_DEVICE_CLASS): cv.string, vol.Optional(CONF_RESET_AFTER): cv.positive_int, - vol.Optional(CONF_AUTOMATION): AUTOMATIONS_SCHEMA, } ), ) @@ -350,6 +340,7 @@ class WeatherSchema: CONF_KNX_BRIGHTNESS_SOUTH_ADDRESS = "address_brightness_south" CONF_KNX_BRIGHTNESS_EAST_ADDRESS = "address_brightness_east" CONF_KNX_BRIGHTNESS_WEST_ADDRESS = "address_brightness_west" + CONF_KNX_BRIGHTNESS_NORTH_ADDRESS = "address_brightness_north" CONF_KNX_WIND_SPEED_ADDRESS = "address_wind_speed" CONF_KNX_RAIN_ALARM_ADDRESS = "address_rain_alarm" CONF_KNX_FROST_ALARM_ADDRESS = "address_frost_alarm" @@ -374,6 +365,7 @@ class WeatherSchema: vol.Optional(CONF_KNX_BRIGHTNESS_SOUTH_ADDRESS): cv.string, vol.Optional(CONF_KNX_BRIGHTNESS_EAST_ADDRESS): cv.string, vol.Optional(CONF_KNX_BRIGHTNESS_WEST_ADDRESS): cv.string, + vol.Optional(CONF_KNX_BRIGHTNESS_NORTH_ADDRESS): cv.string, vol.Optional(CONF_KNX_WIND_SPEED_ADDRESS): cv.string, vol.Optional(CONF_KNX_RAIN_ALARM_ADDRESS): cv.string, vol.Optional(CONF_KNX_FROST_ALARM_ADDRESS): cv.string, diff --git a/homeassistant/components/knx/sensor.py b/homeassistant/components/knx/sensor.py index d87119239cf598..fc2cbced8bb6ab 100644 --- a/homeassistant/components/knx/sensor.py +++ b/homeassistant/components/knx/sensor.py @@ -1,77 +1,43 @@ """Support for KNX/IP sensors.""" from xknx.devices import Sensor as XknxSensor -from homeassistant.core import callback +from homeassistant.components.sensor import DEVICE_CLASSES from homeassistant.helpers.entity import Entity -from . import DATA_KNX +from .const import DOMAIN +from .knx_entity import KnxEntity async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up sensor(s) for KNX platform.""" entities = [] - for device in hass.data[DATA_KNX].xknx.devices: + for device in hass.data[DOMAIN].xknx.devices: if isinstance(device, XknxSensor): entities.append(KNXSensor(device)) async_add_entities(entities) -class KNXSensor(Entity): +class KNXSensor(KnxEntity, Entity): """Representation of a KNX sensor.""" def __init__(self, device: XknxSensor): """Initialize of a KNX sensor.""" - self.device = device - - @callback - def async_register_callbacks(self): - """Register callbacks to update hass after device was changed.""" - - async def after_update_callback(device): - """Call after device was updated.""" - self.async_write_ha_state() - - self.device.register_device_updated_cb(after_update_callback) - - async def async_added_to_hass(self): - """Store register state change callback.""" - self.async_register_callbacks() - - async def async_update(self): - """Update the state from KNX.""" - await self.device.sync() - - @property - def name(self): - """Return the name of the KNX device.""" - return self.device.name - - @property - def available(self): - """Return True if entity is available.""" - return self.hass.data[DATA_KNX].connected - - @property - def should_poll(self): - """No polling needed within KNX.""" - return False + super().__init__(device) @property def state(self): """Return the state of the sensor.""" - return self.device.resolve_state() + return self._device.resolve_state() @property def unit_of_measurement(self): """Return the unit this state is expressed in.""" - return self.device.unit_of_measurement() + return self._device.unit_of_measurement() @property def device_class(self): """Return the device class of the sensor.""" - return self.device.ha_device_class() - - @property - def device_state_attributes(self): - """Return the state attributes.""" + device_class = self._device.ha_device_class() + if device_class in DEVICE_CLASSES: + return device_class return None diff --git a/homeassistant/components/knx/switch.py b/homeassistant/components/knx/switch.py index c378d1b0ca470b..ae3048e2d23adc 100644 --- a/homeassistant/components/knx/switch.py +++ b/homeassistant/components/knx/switch.py @@ -2,69 +2,36 @@ from xknx.devices import Switch as XknxSwitch from homeassistant.components.switch import SwitchEntity -from homeassistant.core import callback -from . import DATA_KNX +from . import DOMAIN +from .knx_entity import KnxEntity async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up switch(es) for KNX platform.""" entities = [] - for device in hass.data[DATA_KNX].xknx.devices: + for device in hass.data[DOMAIN].xknx.devices: if isinstance(device, XknxSwitch): entities.append(KNXSwitch(device)) async_add_entities(entities) -class KNXSwitch(SwitchEntity): +class KNXSwitch(KnxEntity, SwitchEntity): """Representation of a KNX switch.""" def __init__(self, device: XknxSwitch): """Initialize of KNX switch.""" - self.device = device - - @callback - def async_register_callbacks(self): - """Register callbacks to update hass after device was changed.""" - - async def after_update_callback(device): - """Call after device was updated.""" - self.async_write_ha_state() - - self.device.register_device_updated_cb(after_update_callback) - - async def async_added_to_hass(self): - """Store register state change callback.""" - self.async_register_callbacks() - - async def async_update(self): - """Request a state update from KNX bus.""" - await self.device.sync() - - @property - def name(self): - """Return the name of the KNX device.""" - return self.device.name - - @property - def available(self): - """Return true if entity is available.""" - return self.hass.data[DATA_KNX].connected - - @property - def should_poll(self): - """Return the polling state. Not needed within KNX.""" - return False + super().__init__(device) @property def is_on(self): """Return true if device is on.""" - return self.device.state + return self._device.state async def async_turn_on(self, **kwargs): """Turn the device on.""" - await self.device.set_on() + await self._device.set_on() async def async_turn_off(self, **kwargs): """Turn the device off.""" - await self.device.set_off() + await self._device.set_off() diff --git a/homeassistant/components/knx/weather.py b/homeassistant/components/knx/weather.py index 97500ef81947b1..097fa661f4ad0a 100644 --- a/homeassistant/components/knx/weather.py +++ b/homeassistant/components/knx/weather.py @@ -1,37 +1,34 @@ """Support for KNX/IP weather station.""" + from xknx.devices import Weather as XknxWeather from homeassistant.components.weather import WeatherEntity from homeassistant.const import TEMP_CELSIUS -from .const import DATA_KNX +from .const import DOMAIN +from .knx_entity import KnxEntity async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the scenes for KNX platform.""" entities = [] - for device in hass.data[DATA_KNX].xknx.devices: + for device in hass.data[DOMAIN].xknx.devices: if isinstance(device, XknxWeather): entities.append(KNXWeather(device)) async_add_entities(entities) -class KNXWeather(WeatherEntity): +class KNXWeather(KnxEntity, WeatherEntity): """Representation of a KNX weather device.""" def __init__(self, device: XknxWeather): """Initialize of a KNX sensor.""" - self.device = device - - @property - def name(self): - """Return the name of the weather device.""" - return self.device.name + super().__init__(device) @property def temperature(self): """Return current temperature.""" - return self.device.temperature + return self._device.temperature @property def temperature_unit(self): @@ -43,25 +40,27 @@ def pressure(self): """Return current air pressure.""" # KNX returns pA - HA requires hPa return ( - self.device.air_pressure / 100 - if self.device.air_pressure is not None + self._device.air_pressure / 100 + if self._device.air_pressure is not None else None ) @property def condition(self): """Return current weather condition.""" - return self.device.ha_current_state().value + return self._device.ha_current_state().value @property def humidity(self): """Return current humidity.""" - return self.device.humidity if self.device.humidity is not None else None + return self._device.humidity if self._device.humidity is not None else None @property def wind_speed(self): """Return current wind speed in km/h.""" # KNX only supports wind speed in m/s return ( - self.device.wind_speed * 3.6 if self.device.wind_speed is not None else None + self._device.wind_speed * 3.6 + if self._device.wind_speed is not None + else None ) diff --git a/homeassistant/components/kodi/browse_media.py b/homeassistant/components/kodi/browse_media.py index c7df170b5c9743..0c4522b5fbd7d1 100644 --- a/homeassistant/components/kodi/browse_media.py +++ b/homeassistant/components/kodi/browse_media.py @@ -5,6 +5,7 @@ from homeassistant.components.media_player.const import ( MEDIA_CLASS_ALBUM, MEDIA_CLASS_ARTIST, + MEDIA_CLASS_CHANNEL, MEDIA_CLASS_DIRECTORY, MEDIA_CLASS_EPISODE, MEDIA_CLASS_MOVIE, @@ -15,6 +16,7 @@ MEDIA_CLASS_TV_SHOW, MEDIA_TYPE_ALBUM, MEDIA_TYPE_ARTIST, + MEDIA_TYPE_CHANNEL, MEDIA_TYPE_EPISODE, MEDIA_TYPE_MOVIE, MEDIA_TYPE_PLAYLIST, @@ -45,6 +47,7 @@ MEDIA_TYPE_PLAYLIST: MEDIA_CLASS_PLAYLIST, MEDIA_TYPE_TRACK: MEDIA_CLASS_TRACK, MEDIA_TYPE_TVSHOW: MEDIA_CLASS_TV_SHOW, + MEDIA_TYPE_CHANNEL: MEDIA_CLASS_CHANNEL, MEDIA_TYPE_EPISODE: MEDIA_CLASS_EPISODE, } @@ -64,90 +67,105 @@ async def build_item_response(media_library, payload): title = None media = None - query = {"properties": ["thumbnail"]} - # pylint: disable=protected-access + properties = ["thumbnail"] if search_type == MEDIA_TYPE_ALBUM: if search_id: - query.update({"filter": {"albumid": int(search_id)}}) - query["properties"].extend( - ["albumid", "artist", "duration", "album", "track"] - ) - album = await media_library._server.AudioLibrary.GetAlbumDetails( - {"albumid": int(search_id), "properties": ["thumbnail"]} + album = await media_library.get_album_details( + album_id=int(search_id), properties=properties ) thumbnail = media_library.thumbnail_url( album["albumdetails"].get("thumbnail") ) title = album["albumdetails"]["label"] - media = await media_library._server.AudioLibrary.GetSongs(query) + media = await media_library.get_songs( + album_id=int(search_id), + properties=[ + "albumid", + "artist", + "duration", + "album", + "thumbnail", + "track", + ], + ) media = media.get("songs") else: - media = await media_library._server.AudioLibrary.GetAlbums(query) + media = await media_library.get_albums(properties=properties) media = media.get("albums") title = "Albums" + elif search_type == MEDIA_TYPE_ARTIST: if search_id: - query.update({"filter": {"artistid": int(search_id)}}) - media = await media_library._server.AudioLibrary.GetAlbums(query) + media = await media_library.get_albums( + artist_id=int(search_id), properties=properties + ) media = media.get("albums") - artist = await media_library._server.AudioLibrary.GetArtistDetails( - {"artistid": int(search_id), "properties": ["thumbnail"]} + artist = await media_library.get_artist_details( + artist_id=int(search_id), properties=properties ) thumbnail = media_library.thumbnail_url( artist["artistdetails"].get("thumbnail") ) title = artist["artistdetails"]["label"] else: - media = await media_library._server.AudioLibrary.GetArtists(query) + media = await media_library.get_artists(properties) media = media.get("artists") title = "Artists" + elif search_type == "library_music": library = {MEDIA_TYPE_ALBUM: "Albums", MEDIA_TYPE_ARTIST: "Artists"} media = [{"label": name, "type": type_} for type_, name in library.items()] title = "Music Library" + elif search_type == MEDIA_TYPE_MOVIE: - media = await media_library._server.VideoLibrary.GetMovies(query) + media = await media_library.get_movies(properties) media = media.get("movies") title = "Movies" + elif search_type == MEDIA_TYPE_TVSHOW: if search_id: - media = await media_library._server.VideoLibrary.GetSeasons( - { - "tvshowid": int(search_id), - "properties": ["thumbnail", "season", "tvshowid"], - } + media = await media_library.get_seasons( + tv_show_id=int(search_id), + properties=["thumbnail", "season", "tvshowid"], ) media = media.get("seasons") - tvshow = await media_library._server.VideoLibrary.GetTVShowDetails( - {"tvshowid": int(search_id), "properties": ["thumbnail"]} + tvshow = await media_library.get_tv_show_details( + tv_show_id=int(search_id), properties=properties ) thumbnail = media_library.thumbnail_url( tvshow["tvshowdetails"].get("thumbnail") ) title = tvshow["tvshowdetails"]["label"] else: - media = await media_library._server.VideoLibrary.GetTVShows(query) + media = await media_library.get_tv_shows(properties) media = media.get("tvshows") title = "TV Shows" + elif search_type == MEDIA_TYPE_SEASON: tv_show_id, season_id = search_id.split("/", 1) - media = await media_library._server.VideoLibrary.GetEpisodes( - { - "tvshowid": int(tv_show_id), - "season": int(season_id), - "properties": ["thumbnail", "tvshowid", "seasonid"], - } + media = await media_library.get_episodes( + tv_show_id=int(tv_show_id), + season_id=int(season_id), + properties=["thumbnail", "tvshowid", "seasonid"], ) media = media.get("episodes") if media: - season = await media_library._server.VideoLibrary.GetSeasonDetails( - {"seasonid": int(media[0]["seasonid"]), "properties": ["thumbnail"]} + season = await media_library.get_season_details( + season_id=int(media[0]["seasonid"]), properties=properties ) thumbnail = media_library.thumbnail_url( season["seasondetails"].get("thumbnail") ) title = season["seasondetails"]["label"] + elif search_type == MEDIA_TYPE_CHANNEL: + media = await media_library.get_channels( + channel_group_id="alltv", + properties=["thumbnail", "channeltype", "channel", "broadcastnow"], + ) + media = media.get("channels") + title = "Channels" + if media is None: return None @@ -227,9 +245,18 @@ def item_payload(item, media_library): media_content_id = f"{item['tvshowid']}" can_play = False can_expand = True + elif "channelid" in item: + media_content_type = MEDIA_TYPE_CHANNEL + media_content_id = f"{item['channelid']}" + broadcasting = item.get("broadcastnow") + if broadcasting: + show = broadcasting.get("title") + title = f"{title} - {show}" + can_play = True + can_expand = False else: # this case is for the top folder of each type - # possible content types: album, artist, movie, library_music, tvshow + # possible content types: album, artist, movie, library_music, tvshow, channel media_class = MEDIA_CLASS_DIRECTORY media_content_type = item["type"] media_content_id = "" @@ -274,6 +301,7 @@ def library_payload(media_library): "library_music": "Music", MEDIA_TYPE_MOVIE: "Movies", MEDIA_TYPE_TVSHOW: "TV shows", + MEDIA_TYPE_CHANNEL: "Channels", } for item in [{"label": name, "type": type_} for type_, name in library.items()]: library_info.children.append( diff --git a/homeassistant/components/kodi/config_flow.py b/homeassistant/components/kodi/config_flow.py index f10dcbb2d2806f..c11255aba874e2 100644 --- a/homeassistant/components/kodi/config_flow.py +++ b/homeassistant/components/kodi/config_flow.py @@ -116,6 +116,9 @@ async def async_step_zeroconf(self, discovery_info: DiscoveryInfoType): } ) + # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 + self.context.update({"title_placeholders": {CONF_NAME: self._name}}) + try: await validate_http(self.hass, self._get_data()) await validate_ws(self.hass, self._get_data()) @@ -129,8 +132,6 @@ async def async_step_zeroconf(self, discovery_info: DiscoveryInfoType): _LOGGER.exception("Unexpected exception") return self.async_abort(reason="unknown") - # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 - self.context.update({"title_placeholders": {CONF_NAME: self._name}}) return await self.async_step_discovery_confirm() async def async_step_discovery_confirm(self, user_input=None): @@ -201,6 +202,10 @@ async def async_step_ws_port(self, user_input=None): if user_input is not None: self._ws_port = user_input.get(CONF_WS_PORT) + # optional ints return 0 rather than None when empty + if self._ws_port == 0: + self._ws_port = None + try: await validate_ws(self.hass, self._get_data()) except WSCannotConnect: diff --git a/homeassistant/components/kodi/manifest.json b/homeassistant/components/kodi/manifest.json index b3794d5dfa2218..24d3393d7c37ba 100644 --- a/homeassistant/components/kodi/manifest.json +++ b/homeassistant/components/kodi/manifest.json @@ -2,10 +2,15 @@ "domain": "kodi", "name": "Kodi", "documentation": "https://www.home-assistant.io/integrations/kodi", - "requirements": ["pykodi==0.1.2"], + "requirements": [ + "pykodi==0.2.1" + ], "codeowners": [ - "@OnFreund" + "@OnFreund", + "@cgtobi" + ], + "zeroconf": [ + "_xbmc-jsonrpc-h._tcp.local." ], - "zeroconf": ["_xbmc-jsonrpc-h._tcp.local."], "config_flow": true -} +} \ No newline at end of file diff --git a/homeassistant/components/kodi/media_player.py b/homeassistant/components/kodi/media_player.py index ce05f3fc732620..68809559cbf40a 100644 --- a/homeassistant/components/kodi/media_player.py +++ b/homeassistant/components/kodi/media_player.py @@ -5,6 +5,7 @@ import re import jsonrpc_base +from pykodi import CannotConnectError import voluptuous as vol from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerEntity @@ -324,11 +325,15 @@ def async_on_volume_changed(self, sender, data): self._app_properties["muted"] = data["muted"] self.async_write_ha_state() - @callback - def async_on_quit(self, sender, data): + async def async_on_quit(self, sender, data): """Reset the player state on quit action.""" + await self._clear_connection() + + async def _clear_connection(self, close=True): self._reset_state() - self.hass.async_create_task(self._connection.close()) + self.async_write_ha_state() + if close: + await self._connection.close() @property def unique_id(self): @@ -386,14 +391,23 @@ async def _async_ws_connect(self): try: await self._connection.connect() self._on_ws_connected() - except jsonrpc_base.jsonrpc.TransportError: - _LOGGER.info("Unable to connect to Kodi via websocket") + except (jsonrpc_base.jsonrpc.TransportError, CannotConnectError): _LOGGER.debug("Unable to connect to Kodi via websocket", exc_info=True) + await self._clear_connection(False) + + async def _ping(self): + try: + await self._kodi.ping() + except (jsonrpc_base.jsonrpc.TransportError, CannotConnectError): + _LOGGER.debug("Unable to ping Kodi via websocket", exc_info=True) + await self._clear_connection() async def _async_connect_websocket_if_disconnected(self, *_): """Reconnect the websocket if it fails.""" if not self._connection.connected: await self._async_ws_connect() + else: + await self._ping() @callback def _register_ws_callbacks(self): @@ -464,7 +478,7 @@ def name(self): @property def should_poll(self): """Return True if entity has to be polled for state.""" - return (not self._connection.can_subscribe) or (not self._connection.connected) + return not self._connection.can_subscribe @property def volume_level(self): @@ -663,17 +677,10 @@ async def async_play_media(self, media_type, media_id, **kwargs): elif media_type_lower in [ MEDIA_TYPE_ARTIST, MEDIA_TYPE_ALBUM, + MEDIA_TYPE_TRACK, ]: await self.async_clear_playlist() - params = {"playlistid": 0, "item": {f"{media_type}id": int(media_id)}} - # pylint: disable=protected-access - await self._kodi._server.Playlist.Add(params) - await self._kodi.play_playlist(0) - elif media_type_lower == MEDIA_TYPE_TRACK: - await self._kodi.clear_playlist() - params = {"playlistid": 0, "item": {"songid": int(media_id)}} - # pylint: disable=protected-access - await self._kodi._server.Playlist.Add(params) + await self.async_add_to_playlist(media_type_lower, media_id) await self._kodi.play_playlist(0) elif media_type_lower in [ MEDIA_TYPE_MOVIE, @@ -681,8 +688,7 @@ async def async_play_media(self, media_type, media_id, **kwargs): MEDIA_TYPE_SEASON, MEDIA_TYPE_TVSHOW, ]: - # pylint: disable=protected-access - await self._kodi._play_item( + await self._kodi.play_item( {MAP_KODI_MEDIA_TYPES[media_type_lower]: int(media_id)} ) else: @@ -700,7 +706,7 @@ async def async_call_method(self, method, **kwargs): _LOGGER.debug("Run API method %s, kwargs=%s", method, kwargs) result_ok = False try: - result = self._kodi.call_method(method, **kwargs) + result = await self._kodi.call_method(method, **kwargs) result_ok = True except jsonrpc_base.jsonrpc.ProtocolError as exc: result = exc.args[2]["error"] @@ -737,6 +743,15 @@ async def async_clear_playlist(self): """Clear default playlist (i.e. playlistid=0).""" await self._kodi.clear_playlist() + async def async_add_to_playlist(self, media_type, media_id): + """Add media item to default playlist (i.e. playlistid=0).""" + if media_type == MEDIA_TYPE_ARTIST: + await self._kodi.add_artist_to_playlist(int(media_id)) + elif media_type == MEDIA_TYPE_ALBUM: + await self._kodi.add_album_to_playlist(int(media_id)) + elif media_type == MEDIA_TYPE_TRACK: + await self._kodi.add_song_to_playlist(int(media_id)) + async def async_add_media_to_playlist( self, media_type, media_id=None, media_name="ALL", artist_name="" ): @@ -752,7 +767,7 @@ async def async_add_media_to_playlist( if media_id is None: media_id = await self._async_find_song(media_name, artist_name) if media_id: - self._kodi.add_song_to_playlist(int(media_id)) + await self._kodi.add_song_to_playlist(int(media_id)) elif media_type == "ALBUM": if media_id is None: @@ -762,7 +777,7 @@ async def async_add_media_to_playlist( media_id = await self._async_find_album(media_name, artist_name) if media_id: - self._kodi.add_album_to_playlist(int(media_id)) + await self._kodi.add_album_to_playlist(int(media_id)) else: raise RuntimeError("Unrecognized media type.") diff --git a/homeassistant/components/kodi/strings.json b/homeassistant/components/kodi/strings.json index 56a637e54ceabd..cf2f265f577783 100644 --- a/homeassistant/components/kodi/strings.json +++ b/homeassistant/components/kodi/strings.json @@ -7,7 +7,7 @@ "data": { "host": "[%key:common::config_flow::data::host%]", "port": "[%key:common::config_flow::data::port%]", - "ssl": "Connect over SSL" + "ssl": "[%key:common::config_flow::data::ssl%]" } }, "discovery_confirm": { diff --git a/homeassistant/components/kodi/translations/ca.json b/homeassistant/components/kodi/translations/ca.json index d9993e01f54e62..09b16682083eac 100644 --- a/homeassistant/components/kodi/translations/ca.json +++ b/homeassistant/components/kodi/translations/ca.json @@ -36,7 +36,7 @@ "data": { "host": "Amfitri\u00f3", "port": "Port", - "ssl": "Connexi\u00f3 mitjan\u00e7ant SSL" + "ssl": "Utilitza un certificat SSL" }, "description": "Informaci\u00f3 de connexi\u00f3 de Kodi. Assegura't d'activar \"Permet el control de Kodi via HTTP\" a Sistema/Configuraci\u00f3/Xarxa/Serveis." }, diff --git a/homeassistant/components/kodi/translations/de.json b/homeassistant/components/kodi/translations/de.json new file mode 100644 index 00000000000000..ac98f2016b462c --- /dev/null +++ b/homeassistant/components/kodi/translations/de.json @@ -0,0 +1,30 @@ +{ + "config": { + "flow_title": "Kodi: {name}", + "step": { + "credentials": { + "data": { + "password": "Passwort", + "username": "Benutzername" + } + }, + "host": { + "data": { + "host": "Host", + "port": "Port" + } + }, + "user": { + "data": { + "host": "Host", + "port": "Port" + } + }, + "ws_port": { + "data": { + "ws_port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/el.json b/homeassistant/components/kodi/translations/el.json new file mode 100644 index 00000000000000..93b077fbe1d980 --- /dev/null +++ b/homeassistant/components/kodi/translations/el.json @@ -0,0 +1,39 @@ +{ + "config": { + "flow_title": "Kodi: {\u03cc\u03bd\u03bf\u03bc\u03b1}", + "step": { + "credentials": { + "description": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03ba\u03b1\u03b9 \u03c4\u03bf\u03bd \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03c4\u03bf\u03c5 Kodi. \u0391\u03c5\u03c4\u03ac \u03bc\u03c0\u03bf\u03c1\u03b5\u03af\u03c4\u03b5 \u03bd\u03b1 \u03b2\u03c1\u03b5\u03af\u03c4\u03b5 \u03c3\u03c4\u03bf \u03a3\u03cd\u03c3\u03c4\u03b7\u03bc\u03b1 / \u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 / \u0394\u03af\u03ba\u03c4\u03c5\u03bf / \u03a5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b5\u03c2." + }, + "discovery_confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ad\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf Kodi ('{name}') \u03c3\u03c4\u03bf Home Assistant;", + "title": "\u0391\u03bd\u03b1\u03ba\u03b1\u03bb\u03cd\u03c6\u03b8\u03b7\u03ba\u03b5 Kodi" + }, + "host": { + "data": { + "port": "\u0398\u03cd\u03c1\u03b1", + "ssl": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03ad\u03c3\u03c9 SSL" + }, + "description": "\u03a0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 Kodi. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b2\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03ad\u03c7\u03b5\u03c4\u03b5 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9 \u03c4\u03bf \"\u039d\u03b1 \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c0\u03b5\u03c4\u03b1\u03b9 \u03bf \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03bf\u03c5 Kodi \u03bc\u03ad\u03c3\u03c9 HTTP\" \u03c3\u03c4\u03bf \u03a3\u03cd\u03c3\u03c4\u03b7\u03bc\u03b1/\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2/\u0394\u03af\u03ba\u03c4\u03c5\u03bf/\u03a5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b5\u03c2." + }, + "user": { + "data": { + "ssl": "\u03a3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03bc\u03ad\u03c3\u03c9 SSL" + }, + "description": "\u03a0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 Kodi. \u03a0\u03b1\u03c1\u03b1\u03ba\u03b1\u03bb\u03bf\u03cd\u03bc\u03b5 \u03b2\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03ad\u03c7\u03b5\u03c4\u03b5 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03b9 \u03c4\u03bf \"\u039d\u03b1 \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c0\u03b5\u03c4\u03b1\u03b9 \u03bf \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03bf\u03c5 Kodi \u03bc\u03ad\u03c3\u03c9 HTTP\" \u03c3\u03c4\u03bf \u03a3\u03cd\u03c3\u03c4\u03b7\u03bc\u03b1/\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2/\u0394\u03af\u03ba\u03c4\u03c5\u03bf/\u03a5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b5\u03c2." + }, + "ws_port": { + "data": { + "ws_port": "\u0398\u03cd\u03c1\u03b1" + }, + "description": "\u0397 \u03b8\u03cd\u03c1\u03b1 WebSocket (\u03bc\u03b5\u03c1\u03b9\u03ba\u03ad\u03c2 \u03c6\u03bf\u03c1\u03ad\u03c2 \u03bf\u03bd\u03bf\u03bc\u03ac\u03b6\u03b5\u03c4\u03b1\u03b9 \u03b8\u03cd\u03c1\u03b1 TCP \u03c3\u03c4\u03bf Kodi). \u0393\u03b9\u03b1 \u03bd\u03b1 \u03c3\u03c5\u03bd\u03b4\u03b5\u03b8\u03b5\u03af\u03c4\u03b5 \u03bc\u03ad\u03c3\u03c9 \u03c4\u03bf\u03c5 WebSocket, \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \"\u039d\u03b1 \u03b5\u03c0\u03b9\u03c4\u03c1\u03ad\u03c0\u03bf\u03bd\u03c4\u03b1\u03b9 \u03c0\u03c1\u03bf\u03b3\u03c1\u03ac\u03bc\u03bc\u03b1\u03c4\u03b1 ... \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03bf\u03c5 Kodi\" \u03c3\u03c4\u03bf \u03a3\u03cd\u03c3\u03c4\u03b7\u03bc\u03b1 / \u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2 / \u0394\u03af\u03ba\u03c4\u03c5\u03bf / \u03a5\u03c0\u03b7\u03c1\u03b5\u03c3\u03af\u03b5\u03c2. \u0395\u03ac\u03bd \u03c4\u03bf WebSocket \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf, \u03b1\u03c6\u03b1\u03b9\u03c1\u03ad\u03c3\u03c4\u03b5 \u03c4\u03b7 \u03b8\u03cd\u03c1\u03b1 \u03ba\u03b1\u03b9 \u03b1\u03c6\u03ae\u03c3\u03c4\u03b5 \u03ba\u03b5\u03bd\u03cc." + } + } + }, + "device_automation": { + "trigger_type": { + "turn_off": "\u0396\u03b7\u03c4\u03ae\u03b8\u03b7\u03ba\u03b5 \u03b1\u03c0\u03cc \u03c4\u03bf {entity_name} \u03bd\u03b1 \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af", + "turn_on": "\u0396\u03b7\u03c4\u03ae\u03b8\u03b7\u03ba\u03b5 \u03b1\u03c0\u03cc \u03c4\u03bf {entity_name} \u03bd\u03b1 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/en.json b/homeassistant/components/kodi/translations/en.json index 4a9a48ea9ca003..0f703057dcfaa5 100644 --- a/homeassistant/components/kodi/translations/en.json +++ b/homeassistant/components/kodi/translations/en.json @@ -36,7 +36,7 @@ "data": { "host": "Host", "port": "Port", - "ssl": "Connect over SSL" + "ssl": "Uses an SSL certificate" }, "description": "Kodi connection information. Please make sure to enable \"Allow control of Kodi via HTTP\" in System/Settings/Network/Services." }, diff --git a/homeassistant/components/kodi/translations/et.json b/homeassistant/components/kodi/translations/et.json new file mode 100644 index 00000000000000..9d7c8e2a02895f --- /dev/null +++ b/homeassistant/components/kodi/translations/et.json @@ -0,0 +1,8 @@ +{ + "device_automation": { + "trigger_type": { + "turn_off": "{entity_name} paluti v\u00e4lja l\u00fclitada", + "turn_on": "{entity_name} paluti sisse l\u00fclitada" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/hu.json b/homeassistant/components/kodi/translations/hu.json new file mode 100644 index 00000000000000..3b2d79a34a77e2 --- /dev/null +++ b/homeassistant/components/kodi/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/it.json b/homeassistant/components/kodi/translations/it.json index a48d8ca9bb0041..565aaa9d39ac51 100644 --- a/homeassistant/components/kodi/translations/it.json +++ b/homeassistant/components/kodi/translations/it.json @@ -36,7 +36,7 @@ "data": { "host": "Host", "port": "Porta", - "ssl": "Connettiti tramite SSL" + "ssl": "Utilizza un certificato SSL" }, "description": "Informazioni sulla connessione Kodi. Assicurati di abilitare \"Consenti il controllo di Kodi tramite HTTP\" in Sistema/Impostazioni/Rete/Servizi." }, diff --git a/homeassistant/components/kodi/translations/ko.json b/homeassistant/components/kodi/translations/ko.json new file mode 100644 index 00000000000000..6dc6b8bf87ad0e --- /dev/null +++ b/homeassistant/components/kodi/translations/ko.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", + "invalid_auth": "\uc798\ubabb\ub41c \uc778\uc99d" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", + "invalid_auth": "\uc798\ubabb\ub41c \uc778\uc99d", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec" + }, + "step": { + "credentials": { + "description": "Kodi \uc0ac\uc6a9\uc790\uba85\uacfc \uc554\ud638\ub97c \uc785\ub825\ud558\uc2ed\uc2dc\uc624. \uc774\ub7ec\ud55c \ub0b4\uc6a9\uc740 \uc2dc\uc2a4\ud15c/\uc124\uc815/\ub124\ud2b8\uc6cc\ud06c/\uc11c\ube44\uc2a4\uc5d0\uc11c \ucc3e\uc744 \uc218 \uc788\uc2b5\ub2c8\ub2e4." + }, + "discovery_confirm": { + "description": "Kodi (` {name} `)\ub97c Home Assistant\uc5d0 \ucd94\uac00 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?", + "title": "Kodi \ubc1c\uacac" + }, + "host": { + "data": { + "ssl": "SSL\uc744 \ud1b5\ud574 \uc5f0\uacb0" + }, + "description": "Kodi \uc5f0\uacb0 \uc815\ubcf4. \uc2dc\uc2a4\ud15c / \uc124\uc815 / \ub124\ud2b8\uc6cc\ud06c / \uc11c\ube44\uc2a4\uc5d0\uc11c \"HTTP\ub97c \ud1b5\ud55c Kodi \uc81c\uc5b4 \ud5c8\uc6a9\"\uc744 \ud65c\uc131\ud654\ud588\ub294\uc9c0 \ud655\uc778\ud558\uc2ed\uc2dc\uc624." + }, + "user": { + "description": "Kodi \uc5f0\uacb0 \uc815\ubcf4. \uc2dc\uc2a4\ud15c / \uc124\uc815 / \ub124\ud2b8\uc6cc\ud06c / \uc11c\ube44\uc2a4\uc5d0\uc11c \"HTTP\ub97c \ud1b5\ud55c Kodi \uc81c\uc5b4 \ud5c8\uc6a9\"\uc744 \ud65c\uc131\ud654\ud588\ub294\uc9c0 \ud655\uc778\ud558\uc2ed\uc2dc\uc624." + }, + "ws_port": { + "description": "WebSocket \ud3ec\ud2b8 (Kodi\uc5d0\uc11c TCP \ud3ec\ud2b8\ub77c\uace0\ub3c4 \ud568). WebSocket\uc744 \ud1b5\ud574 \uc5f0\uacb0\ud558\ub824\uba74 \uc2dc\uc2a4\ud15c / \uc124\uc815 / \ub124\ud2b8\uc6cc\ud06c / \uc11c\ube44\uc2a4\uc5d0\uc11c \"\ud504\ub85c\uadf8\ub7a8\uc774 Kodi\ub97c \uc81c\uc5b4\ud558\ub3c4\ub85d \ud5c8\uc6a9\"\uc744 \ud65c\uc131\ud654\ud574\uc57c\ud569\ub2c8\ub2e4. WebSocket\uc774 \ud65c\uc131\ud654\ub418\uc9c0 \uc54a\uc740 \uacbd\uc6b0 \ud3ec\ud2b8\ub97c \uc81c\uac70\ud558\uace0 \ube44\uc6cc \ub461\ub2c8\ub2e4." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/lb.json b/homeassistant/components/kodi/translations/lb.json index 872615f5bea24a..0fde1e6ffdf819 100644 --- a/homeassistant/components/kodi/translations/lb.json +++ b/homeassistant/components/kodi/translations/lb.json @@ -43,8 +43,15 @@ "ws_port": { "data": { "ws_port": "Port" - } + }, + "description": "De Websocket Port (heiansdo TCP port am Kodi genannt). Fir sech k\u00ebnnen iwwer Websocket ze verbannen muss du \"Allow programs ... to control Kodi\" an de System/Settings/Network/Services aktiv\u00e9ieren. Falls Websocket net aktiv\u00e9iert ass, l\u00e4sch de Port a loss eidel." } } + }, + "device_automation": { + "trigger_type": { + "turn_off": "{entity_name} soll ugeschalt ginn", + "turn_on": "{entity_name} soll ugeschalt ginn" + } } } \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/nl.json b/homeassistant/components/kodi/translations/nl.json new file mode 100644 index 00000000000000..235d5a50be6365 --- /dev/null +++ b/homeassistant/components/kodi/translations/nl.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "unknown": "Onverwachte fout" + }, + "step": { + "credentials": { + "data": { + "username": "Gebruikersnaam" + } + }, + "user": { + "data": { + "host": "Host", + "port": "Poort", + "ssl": "Maak verbinding via SSL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/no.json b/homeassistant/components/kodi/translations/no.json index 1c8bc3d6a5310b..fc4ae7ff736e95 100644 --- a/homeassistant/components/kodi/translations/no.json +++ b/homeassistant/components/kodi/translations/no.json @@ -36,7 +36,7 @@ "data": { "host": "Vert", "port": "Port", - "ssl": "Koble til via SSL" + "ssl": "Bruker et SSL-sertifikat" }, "description": "Kodi-tilkoblingsinformasjon. Vennligst s\u00f8rg for \u00e5 aktivere \"Tillat kontroll av Kodi via HTTP\" i System / Innstillinger / Nettverk / Tjenester." }, diff --git a/homeassistant/components/kodi/translations/pl.json b/homeassistant/components/kodi/translations/pl.json index 3e71fd0df8b62c..ac84350cf93988 100644 --- a/homeassistant/components/kodi/translations/pl.json +++ b/homeassistant/components/kodi/translations/pl.json @@ -1,40 +1,56 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", - "invalid_auth": "Niepoprawne uwierzytelnienie.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", - "invalid_auth": "Niepoprawne uwierzytelnienie.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, + "flow_title": "Kodi: {name}", "step": { "credentials": { "data": { "password": "Has\u0142o", "username": "Nazwa u\u017cytkownika" - } + }, + "description": "Wprowad\u017a nazw\u0119 u\u017cytkownika i has\u0142o Kodi. Mo\u017cna je znale\u017a\u0107 w System/Ustawienia/Sie\u0107/Us\u0142ugi." + }, + "discovery_confirm": { + "description": "Czy chcesz doda\u0107 Kodi (\"{name}\") do Home Assistant?", + "title": "Wykryto urz\u0105dzenia Kodi" }, "host": { "data": { "host": "Nazwa hosta lub adres IP", - "port": "Port" - } + "port": "Port", + "ssl": "Po\u0142\u0105cz przez SSL" + }, + "description": "Informacje o po\u0142\u0105czeniu Kodi. Upewnij si\u0119, \u017ce w\u0142\u0105czy\u0142e\u015b \"Zezwalaj na zdalne sterowanie przez HTTP\" w System/Ustawienia/Sieci/Us\u0142ugi." }, "user": { "data": { "host": "Nazwa hosta lub adres IP", "port": "Port" - } + }, + "description": "Informacje o po\u0142\u0105czeniu Kodi. Upewnij si\u0119, \u017ce w\u0142\u0105czy\u0142e\u015b \"Zezwalaj na zdalne sterowanie przez HTTP\" w System/Ustawienia/Sieci/Us\u0142ugi." }, "ws_port": { "data": { "ws_port": "Port" - } + }, + "description": "Port WebSocket (czasami nazywany portem TCP w Kodi). Aby po\u0142\u0105czy\u0107 si\u0119 przez WebSocket, musisz w\u0142\u0105czy\u0107 opcj\u0119 \u201eZezwalaj ... programom na kontrolowanie Kodi\u201d w System / Ustawienia / Sie\u0107 / Us\u0142ugi. Je\u015bli us\u0142uga WebSocket nie jest w\u0142\u0105czona, usu\u0144 port i pozostaw pusty." } } + }, + "device_automation": { + "trigger_type": { + "turn_off": "{entity_name} zosta\u0142 poproszony o wy\u0142\u0105czenie", + "turn_on": "{entity_name} zosta\u0142 poproszony o w\u0142\u0105czenie" + } } } \ No newline at end of file diff --git a/homeassistant/components/kodi/translations/ru.json b/homeassistant/components/kodi/translations/ru.json index 8702c016871ff1..3e6a898bf43cb4 100644 --- a/homeassistant/components/kodi/translations/ru.json +++ b/homeassistant/components/kodi/translations/ru.json @@ -36,7 +36,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442", "port": "\u041f\u043e\u0440\u0442", - "ssl": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043f\u043e SSL" + "ssl": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL" }, "description": "\u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0432\u043a\u043b\u044e\u0447\u0435\u043d \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 \"\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u0443\u0434\u0430\u043b\u0451\u043d\u043d\u043e\u0435 \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043f\u043e HTTP\" \u0432 \u0440\u0430\u0437\u0434\u0435\u043b\u0435 \"\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\" - \"\u0421\u043b\u0443\u0436\u0431\u044b\" - \"\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\"." }, diff --git a/homeassistant/components/kodi/translations/zh-Hant.json b/homeassistant/components/kodi/translations/zh-Hant.json index b00f6245366375..c36fe96740b9d8 100644 --- a/homeassistant/components/kodi/translations/zh-Hant.json +++ b/homeassistant/components/kodi/translations/zh-Hant.json @@ -36,7 +36,7 @@ "data": { "host": "\u4e3b\u6a5f\u7aef", "port": "\u901a\u8a0a\u57e0", - "ssl": "\u901a\u904e SSL \u9023\u7dda" + "ssl": "\u4f7f\u7528 SSL \u8a8d\u8b49" }, "description": "Kodi \u9023\u7dda\u8cc7\u8a0a\uff0c\u8acb\u78ba\u5b9a\u5df2\u65bc\u300c\u7cfb\u7d71/\u8a2d\u5b9a/\u7db2\u8def/\u670d\u52d9\u300d\u4e2d\u958b\u555f \"\u5141\u8a31\u900f\u904e HTTP \u63a7\u5236 Kodi\"\u3002" }, diff --git a/homeassistant/components/konnected/strings.json b/homeassistant/components/konnected/strings.json index 281de6032eeb9f..4fd62dee8e7c9d 100644 --- a/homeassistant/components/konnected/strings.json +++ b/homeassistant/components/konnected/strings.json @@ -18,12 +18,12 @@ } }, "error": { - "cannot_connect": "Unable to connect to a Konnected Panel at {host}:{port}" + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, "abort": { - "unknown": "Unknown error occurred", - "already_configured": "Device is already configured", - "already_in_progress": "Config flow for device is already in progress.", + "unknown": "[%key:common::config_flow::error::unknown%]", + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", "not_konn_panel": "Not a recognized Konnected.io device" } }, @@ -62,7 +62,7 @@ "description": "{zone} options", "data": { "type": "Binary Sensor Type", - "name": "Name (optional)", + "name": "[%key:common::config_flow::data::name%] (optional)", "inverse": "Invert the open/close state" } }, @@ -71,7 +71,7 @@ "description": "{zone} options", "data": { "type": "Sensor Type", - "name": "Name (optional)", + "name": "[%key:common::config_flow::data::name%] (optional)", "poll_interval": "Poll Interval (minutes) (optional)" } }, @@ -79,7 +79,7 @@ "title": "Configure Switchable Output", "description": "{zone} options: state {state}", "data": { - "name": "Name (optional)", + "name": "[%key:common::config_flow::data::name%] (optional)", "activation": "Output when on", "momentary": "Pulse duration (ms) (optional)", "pause": "Pause between pulses (ms) (optional)", diff --git a/homeassistant/components/konnected/translations/ca.json b/homeassistant/components/konnected/translations/ca.json index 8938db2621b42e..68a62bd7d32673 100644 --- a/homeassistant/components/konnected/translations/ca.json +++ b/homeassistant/components/konnected/translations/ca.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "El dispositiu ja est\u00e0 configurat", - "already_in_progress": "El flux de dades de configuraci\u00f3 pel dispositiu ja est\u00e0 en curs.", + "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", "not_konn_panel": "No s'ha reconegut com a un dispositiu Konnected.io", "unknown": "S'ha produ\u00eft un error desconegut" }, diff --git a/homeassistant/components/konnected/translations/en.json b/homeassistant/components/konnected/translations/en.json index 3b9b422c903ae4..88a109e13011a7 100644 --- a/homeassistant/components/konnected/translations/en.json +++ b/homeassistant/components/konnected/translations/en.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Device is already configured", - "already_in_progress": "Config flow for device is already in progress.", + "already_in_progress": "Configuration flow is already in progress", "not_konn_panel": "Not a recognized Konnected.io device", "unknown": "Unknown error occurred" }, diff --git a/homeassistant/components/konnected/translations/fr.json b/homeassistant/components/konnected/translations/fr.json index be48b5e15c2807..3c020967c8d5d3 100644 --- a/homeassistant/components/konnected/translations/fr.json +++ b/homeassistant/components/konnected/translations/fr.json @@ -32,6 +32,7 @@ "not_konn_panel": "Non reconnu comme appareil Konnected.io" }, "error": { + "bad_host": "URL de substitution de l'h\u00f4te de l'API non valide", "one": "Vide", "other": "Vide" }, @@ -65,6 +66,7 @@ "7": "Zone 7", "out": "OUT" }, + "description": "D\u00e9couverte d\u2019un {model} \u00e0 {host}. S\u00e9lectionnez la configuration de base de chaque E/S ci-dessous - en fonction de l\u2019E/S, il peut permettre des capteurs binaires (contacts ouverts/proches), des capteurs num\u00e9riques (dht et ds18b20) ou des sorties commutables. Vous pourrez configurer des options d\u00e9taill\u00e9es dans les \u00e9tapes suivantes.", "title": "Configurer les E/S" }, "options_io_ext": { @@ -83,8 +85,10 @@ }, "options_misc": { "data": { + "api_host": "Remplacer l'URL de l'h\u00f4te de l'API (facultatif)", "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" + "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" }, "description": "Veuillez s\u00e9lectionner le comportement souhat\u00e9 de votre panneau", "title": "Configurer divers" diff --git a/homeassistant/components/konnected/translations/it.json b/homeassistant/components/konnected/translations/it.json index ff8b6da9e3daab..f0820198e7e1f4 100644 --- a/homeassistant/components/konnected/translations/it.json +++ b/homeassistant/components/konnected/translations/it.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", - "already_in_progress": "Il flusso di configurazione per il dispositivo \u00e8 gi\u00e0 in corso.", + "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "not_konn_panel": "Non \u00e8 un dispositivo Konnected.io riconosciuto", "unknown": "Si \u00e8 verificato un errore sconosciuto" }, diff --git a/homeassistant/components/konnected/translations/nl.json b/homeassistant/components/konnected/translations/nl.json index dcb5f1ed6c4182..8e9eba0d134c19 100644 --- a/homeassistant/components/konnected/translations/nl.json +++ b/homeassistant/components/konnected/translations/nl.json @@ -2,8 +2,13 @@ "config": { "abort": { "already_configured": "Apparaat is al geconfigureerd", + "already_in_progress": "De configuratiestroom voor het apparaat wordt al uitgevoerd.", + "not_konn_panel": "Geen herkend Konnected.io apparaat", "unknown": "Onbekende fout opgetreden" }, + "error": { + "cannot_connect": "Kan geen verbinding maken met een Konnected Panel op {host} : {port}" + }, "step": { "confirm": { "title": "Konnected Apparaat Klaar" diff --git a/homeassistant/components/konnected/translations/no.json b/homeassistant/components/konnected/translations/no.json index af39b9750a421a..c1d18939741a1d 100644 --- a/homeassistant/components/konnected/translations/no.json +++ b/homeassistant/components/konnected/translations/no.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Enheten er allerede konfigurert", - "already_in_progress": "Konfigurasjonsflyt for enhet p\u00e5g\u00e5r allerede.", + "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", "not_konn_panel": "Ikke en anerkjent Konnected.io-enhet", "unknown": "Ukjent feil oppstod" }, diff --git a/homeassistant/components/konnected/translations/pl.json b/homeassistant/components/konnected/translations/pl.json index 1f9b60bbae3d78..95b3a9f5d5e50e 100644 --- a/homeassistant/components/konnected/translations/pl.json +++ b/homeassistant/components/konnected/translations/pl.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", "already_in_progress": "Konfiguracja urz\u0105dzenia jest ju\u017c w toku.", "not_konn_panel": "Nie rozpoznano urz\u0105dzenia Konnected.io.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "error": { "cannot_connect": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z panelem Konnected na {host}:{port}" diff --git a/homeassistant/components/konnected/translations/ru.json b/homeassistant/components/konnected/translations/ru.json index 433d6bad4c991b..9963fb50f02ef0 100644 --- a/homeassistant/components/konnected/translations/ru.json +++ b/homeassistant/components/konnected/translations/ru.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", - "already_in_progress": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.", + "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_konn_panel": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e Konnected.io \u043d\u0435 \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u043d\u043e.", "unknown": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, diff --git a/homeassistant/components/konnected/translations/zh-Hant.json b/homeassistant/components/konnected/translations/zh-Hant.json index c2c63e20c3857a..8ee25150c9c3cc 100644 --- a/homeassistant/components/konnected/translations/zh-Hant.json +++ b/homeassistant/components/konnected/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", - "already_in_progress": "\u8a2d\u5099\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d\u3002", + "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "not_konn_panel": "\u4e26\u975e\u53ef\u8b58\u5225 Konnected.io \u8a2d\u5099", "unknown": "\u767c\u751f\u672a\u77e5\u932f\u8aa4\u3002" }, diff --git a/homeassistant/components/lcn/manifest.json b/homeassistant/components/lcn/manifest.json index 74bfe10555551d..6eeb4a69b26b90 100644 --- a/homeassistant/components/lcn/manifest.json +++ b/homeassistant/components/lcn/manifest.json @@ -2,6 +2,6 @@ "domain": "lcn", "name": "LCN", "documentation": "https://www.home-assistant.io/integrations/lcn", - "requirements": ["pypck==0.6.4"], + "requirements": ["pypck==0.7.2"], "codeowners": ["@alengwenus"] } diff --git a/homeassistant/components/life360/translations/et.json b/homeassistant/components/life360/translations/et.json new file mode 100644 index 00000000000000..6ea3ab1c89e3fe --- /dev/null +++ b/homeassistant/components/life360/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "unexpected": "Ootamatu t\u00f5rge Life360 serveriga suhtlemisel" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/life360/translations/pl.json b/homeassistant/components/life360/translations/pl.json index 19a6c6d8828a9a..b0a5785a320a73 100644 --- a/homeassistant/components/life360/translations/pl.json +++ b/homeassistant/components/life360/translations/pl.json @@ -2,7 +2,7 @@ "config": { "abort": { "invalid_credentials": "Nieprawid\u0142owe dane uwierzytelniaj\u0105ce", - "user_already_configured": "Konto jest ju\u017c skonfigurowane." + "user_already_configured": "Konto jest ju\u017c skonfigurowane" }, "create_entry": { "default": "Aby skonfigurowa\u0107 zaawansowane ustawienia, zapoznaj si\u0119 z [dokumentacj\u0105 Life360]({docs_url})." @@ -11,7 +11,7 @@ "invalid_credentials": "Nieprawid\u0142owe dane uwierzytelniaj\u0105ce", "invalid_username": "Nieprawid\u0142owa nazwa u\u017cytkownika", "unexpected": "Nieoczekiwany b\u0142\u0105d komunikacji z serwerem Life360", - "user_already_configured": "Konto jest ju\u017c skonfigurowane." + "user_already_configured": "Konto jest ju\u017c skonfigurowane" }, "step": { "user": { diff --git a/homeassistant/components/lifx/strings.json b/homeassistant/components/lifx/strings.json index c35dcf3eb26e0a..ebb8b39a8bc68b 100644 --- a/homeassistant/components/lifx/strings.json +++ b/homeassistant/components/lifx/strings.json @@ -6,8 +6,8 @@ } }, "abort": { - "single_instance_allowed": "Only a single configuration of LIFX is possible.", - "no_devices_found": "No LIFX devices found on the network." + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" } } } diff --git a/homeassistant/components/lifx/translations/ca.json b/homeassistant/components/lifx/translations/ca.json index edc525a92cb911..28c25cde70ac5c 100644 --- a/homeassistant/components/lifx/translations/ca.json +++ b/homeassistant/components/lifx/translations/ca.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "No s'han trobat dispositius LIFX a la xarxa.", - "single_instance_allowed": "Nom\u00e9s \u00e9s possible una \u00fanica configuraci\u00f3 de LIFX." + "no_devices_found": "No s'han trobat dispositius a la xarxa", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "step": { "confirm": { diff --git a/homeassistant/components/lifx/translations/en.json b/homeassistant/components/lifx/translations/en.json index ab4d5458d824d3..154101995ac9b4 100644 --- a/homeassistant/components/lifx/translations/en.json +++ b/homeassistant/components/lifx/translations/en.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "No LIFX devices found on the network.", - "single_instance_allowed": "Only a single configuration of LIFX is possible." + "no_devices_found": "No devices found on the network", + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "step": { "confirm": { diff --git a/homeassistant/components/lifx/translations/it.json b/homeassistant/components/lifx/translations/it.json index 40b4c98f4907ea..be167ec99948a6 100644 --- a/homeassistant/components/lifx/translations/it.json +++ b/homeassistant/components/lifx/translations/it.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Nessun dispositivo LIFX trovato in rete.", - "single_instance_allowed": "\u00c8 consentita solo una singola configurazione di LIFX." + "no_devices_found": "Nessun dispositivo trovato sulla rete", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "step": { "confirm": { diff --git a/homeassistant/components/lifx/translations/no.json b/homeassistant/components/lifx/translations/no.json index 708efba9cc71a8..4771b4c05d7c6c 100644 --- a/homeassistant/components/lifx/translations/no.json +++ b/homeassistant/components/lifx/translations/no.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Ingen LIFX-enheter funnet p\u00e5 nettverket.", - "single_instance_allowed": "Kun en konfigurasjon av LIFX er mulig." + "no_devices_found": "Ingen enheter funnet p\u00e5 nettverket", + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "step": { "confirm": { diff --git a/homeassistant/components/lifx/translations/ru.json b/homeassistant/components/lifx/translations/ru.json index 9ffd9a95270769..0d50dec498b70a 100644 --- a/homeassistant/components/lifx/translations/ru.json +++ b/homeassistant/components/lifx/translations/ru.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 LIFX \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 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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." }, "step": { "confirm": { diff --git a/homeassistant/components/lifx/translations/zh-Hant.json b/homeassistant/components/lifx/translations/zh-Hant.json index bc20e93c596628..ed704711a661e1 100644 --- a/homeassistant/components/lifx/translations/zh-Hant.json +++ b/homeassistant/components/lifx/translations/zh-Hant.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "\u5728\u7db2\u8def\u4e0a\u627e\u4e0d\u5230 LIFX \u8a2d\u5099\u3002", - "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44 LIFX\u3002" + "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u8a2d\u5099", + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002" }, "step": { "confirm": { diff --git a/homeassistant/components/lifx_cloud/scene.py b/homeassistant/components/lifx_cloud/scene.py index 75ab46567943de..cf39d70d89abfa 100644 --- a/homeassistant/components/lifx_cloud/scene.py +++ b/homeassistant/components/lifx_cloud/scene.py @@ -9,7 +9,13 @@ import voluptuous as vol from homeassistant.components.scene import Scene -from homeassistant.const import CONF_PLATFORM, CONF_TIMEOUT, CONF_TOKEN, HTTP_OK +from homeassistant.const import ( + CONF_PLATFORM, + CONF_TIMEOUT, + CONF_TOKEN, + HTTP_OK, + HTTP_UNAUTHORIZED, +) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv @@ -50,7 +56,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= devices = [LifxCloudScene(hass, headers, timeout, scene) for scene in data] async_add_entities(devices) return True - if status == 401: + if status == HTTP_UNAUTHORIZED: _LOGGER.error("Unauthorized (bad token?) on %s", url) return False diff --git a/homeassistant/components/light/group.py b/homeassistant/components/light/group.py new file mode 100644 index 00000000000000..1636054663dc69 --- /dev/null +++ b/homeassistant/components/light/group.py @@ -0,0 +1,15 @@ +"""Describe group states.""" + + +from homeassistant.components.group import GroupIntegrationRegistry +from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.core import callback +from homeassistant.helpers.typing import HomeAssistantType + + +@callback +def async_describe_on_off_states( + hass: HomeAssistantType, registry: GroupIntegrationRegistry +) -> None: + """Describe group on off states.""" + registry.on_off_states({STATE_ON}, STATE_OFF) diff --git a/homeassistant/components/light/translations/et.json b/homeassistant/components/light/translations/et.json index 4eef7a8526746a..f137faa0bf7757 100644 --- a/homeassistant/components/light/translations/et.json +++ b/homeassistant/components/light/translations/et.json @@ -1,4 +1,22 @@ { + "device_automation": { + "action_type": { + "brightness_decrease": "V\u00e4henda {entity_name} heledust", + "brightness_increase": "Suurenda{entity_name} heledust", + "flash": "Vilguta {entity_name}", + "toggle": "Muuda {entity_name} olekut", + "turn_off": "L\u00fclita {entity_name} v\u00e4lja", + "turn_on": "L\u00fclita {entity_name} sisse" + }, + "condition_type": { + "is_off": "{entity_name} on v\u00e4lja l\u00fclitatud", + "is_on": "{entity_name} on sisse l\u00fclitatud" + }, + "trigger_type": { + "turned_off": "{entity_name} l\u00fclitus v\u00e4lja", + "turned_on": "{entity_name} l\u00fclitus sisse" + } + }, "state": { "_": { "off": "V\u00e4ljas", diff --git a/homeassistant/components/light/translations/uk.json b/homeassistant/components/light/translations/uk.json index 06c880fff77add..67685889c5489d 100644 --- a/homeassistant/components/light/translations/uk.json +++ b/homeassistant/components/light/translations/uk.json @@ -1,4 +1,10 @@ { + "device_automation": { + "trigger_type": { + "turned_off": "{entity_name} \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e", + "turned_on": "{entity_name} \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e" + } + }, "state": { "_": { "off": "\u0412\u0438\u043c\u043a\u043d\u0435\u043d\u043e", diff --git a/homeassistant/components/linode/binary_sensor.py b/homeassistant/components/linode/binary_sensor.py index c4a14210d32b2e..bb81a022891139 100644 --- a/homeassistant/components/linode/binary_sensor.py +++ b/homeassistant/components/linode/binary_sensor.py @@ -3,7 +3,11 @@ import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_MOVING, + PLATFORM_SCHEMA, + BinarySensorEntity, +) import homeassistant.helpers.config_validation as cv from . import ( @@ -22,7 +26,6 @@ _LOGGER = logging.getLogger(__name__) DEFAULT_NAME = "Node" -DEFAULT_DEVICE_CLASS = "moving" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( {vol.Required(CONF_NODES): vol.All(cv.ensure_list, [cv.string])} ) @@ -69,7 +72,7 @@ def is_on(self): @property def device_class(self): """Return the class of this sensor.""" - return DEFAULT_DEVICE_CLASS + return DEVICE_CLASS_MOVING @property def device_state_attributes(self): diff --git a/homeassistant/components/local_ip/strings.json b/homeassistant/components/local_ip/strings.json index 2c571be1a0ae96..1859223e6570ae 100644 --- a/homeassistant/components/local_ip/strings.json +++ b/homeassistant/components/local_ip/strings.json @@ -10,7 +10,7 @@ } }, "abort": { - "single_instance_allowed": "Only a single configuration of Local IP is allowed." + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" } } } diff --git a/homeassistant/components/local_ip/translations/ca.json b/homeassistant/components/local_ip/translations/ca.json index e0df6934361508..7be2a2d70d6cef 100644 --- a/homeassistant/components/local_ip/translations/ca.json +++ b/homeassistant/components/local_ip/translations/ca.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Nom\u00e9s \u00e9s possible configurar una sola IP local." + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "step": { "user": { diff --git a/homeassistant/components/local_ip/translations/en.json b/homeassistant/components/local_ip/translations/en.json index 5a4077352e701d..7f823968f9c0af 100644 --- a/homeassistant/components/local_ip/translations/en.json +++ b/homeassistant/components/local_ip/translations/en.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "Only a single configuration of Local IP is allowed." + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "step": { "user": { diff --git a/homeassistant/components/local_ip/translations/et.json b/homeassistant/components/local_ip/translations/et.json new file mode 100644 index 00000000000000..70493aee468f05 --- /dev/null +++ b/homeassistant/components/local_ip/translations/et.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "Anduri nimi" + }, + "title": "Kohalik IP-aadress" + } + } + }, + "title": "Kohalik IP-aadress" +} \ No newline at end of file diff --git a/homeassistant/components/local_ip/translations/it.json b/homeassistant/components/local_ip/translations/it.json index 3e1592546003b7..9173584c9f8469 100644 --- a/homeassistant/components/local_ip/translations/it.json +++ b/homeassistant/components/local_ip/translations/it.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "single_instance_allowed": "\u00c8 consentita una sola configurazione dell'IP locale." + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "step": { "user": { diff --git a/homeassistant/components/locative/strings.json b/homeassistant/components/locative/strings.json index 53a0c160e99a29..3a5821f40b1174 100644 --- a/homeassistant/components/locative/strings.json +++ b/homeassistant/components/locative/strings.json @@ -7,7 +7,7 @@ } }, "abort": { - "one_instance_allowed": "Only a single instance is necessary.", + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive messages from Geofency." }, "create_entry": { diff --git a/homeassistant/components/locative/translations/ca.json b/homeassistant/components/locative/translations/ca.json index 61c6a6c48afa18..89dcafd65d5bfd 100644 --- a/homeassistant/components/locative/translations/ca.json +++ b/homeassistant/components/locative/translations/ca.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "La inst\u00e0ncia de Home Assistant ha de ser accessible des d'Internet per rebre missatges de Geofency.", - "one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia." + "one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia.", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "create_entry": { "default": "Per enviar ubicacions a Home Assistant, haur\u00e0s de configurar l'opci\u00f3 webhook de l'aplicaci\u00f3 Locative.\n\nCompleta la seg\u00fcent informaci\u00f3:\n\n- URL: `{webhook_url}` \n- M\u00e8tode: POST \n\nConsulta la [documentaci\u00f3]({docs_url}) per a m\u00e9s detalls." diff --git a/homeassistant/components/locative/translations/el.json b/homeassistant/components/locative/translations/el.json new file mode 100644 index 00000000000000..aecb2ee553fa42 --- /dev/null +++ b/homeassistant/components/locative/translations/el.json @@ -0,0 +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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/locative/translations/en.json b/homeassistant/components/locative/translations/en.json index 750797e535bf19..59ff777603e3d7 100644 --- a/homeassistant/components/locative/translations/en.json +++ b/homeassistant/components/locative/translations/en.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive messages from Geofency.", - "one_instance_allowed": "Only a single instance is necessary." + "one_instance_allowed": "Only a single instance is necessary.", + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "create_entry": { "default": "To send locations to Home Assistant, you will need to setup the webhook feature in the Locative app.\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nSee [the documentation]({docs_url}) for further details." diff --git a/homeassistant/components/locative/translations/es.json b/homeassistant/components/locative/translations/es.json index 53188cd7be2325..d095c46744d6da 100644 --- a/homeassistant/components/locative/translations/es.json +++ b/homeassistant/components/locative/translations/es.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Tu Home Assistant debe ser accesible desde Internet para recibir mensajes de Geofency.", - "one_instance_allowed": "S\u00f3lo se necesita una instancia." + "one_instance_allowed": "S\u00f3lo se necesita una instancia.", + "single_instance_allowed": "Ya est\u00e1 configurado. S\u00f3lo es posible una \u00fanica configuraci\u00f3n." }, "create_entry": { "default": "Para enviar ubicaciones a Home Assistant, es necesario configurar la caracter\u00edstica webhook en la app de Locative.\n\nRellena la siguiente informaci\u00f3n:\n\n- URL: `{webhook_url}`\n- M\u00e9todo: POST\n\nRevisa [la documentaci\u00f3n]({docs_url}) para m\u00e1s detalles." diff --git a/homeassistant/components/locative/translations/et.json b/homeassistant/components/locative/translations/et.json new file mode 100644 index 00000000000000..e8b5eae41495c6 --- /dev/null +++ b/homeassistant/components/locative/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/locative/translations/fr.json b/homeassistant/components/locative/translations/fr.json index 4e65415b1b3ef3..1327b2d0f8bff1 100644 --- a/homeassistant/components/locative/translations/fr.json +++ b/homeassistant/components/locative/translations/fr.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Votre instance Home Assistant doit \u00eatre accessible \u00e0 partir d'Internet pour recevoir les messages Geofency.", - "one_instance_allowed": "Une seule instance est n\u00e9cessaire." + "one_instance_allowed": "Une seule instance est n\u00e9cessaire.", + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." }, "create_entry": { "default": "Pour envoyer des localisations \u00e0 Home Assistant, vous devez configurer la fonctionnalit\u00e9 Webhook dans l'application Locative. \n\n Remplissez les informations suivantes: \n\n - URL: ` {webhook_url} ` \n - M\u00e9thode: POST \n\n Voir [la documentation] ( {docs_url} ) pour plus de d\u00e9tails." diff --git a/homeassistant/components/locative/translations/it.json b/homeassistant/components/locative/translations/it.json index 210fbfe4c2848f..37e47c11aefa44 100644 --- a/homeassistant/components/locative/translations/it.json +++ b/homeassistant/components/locative/translations/it.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "La tua istanza di Home Assistant deve essere accessibile da Internet per ricevere messaggi da Geofency.", - "one_instance_allowed": "\u00c8 necessaria una sola istanza." + "one_instance_allowed": "\u00c8 necessaria una sola istanza.", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "create_entry": { "default": "Per inviare localit\u00e0 a Home Assistant, dovrai configurare la funzionalit\u00e0 Webhook nell'app Locative.\n\n Compila le seguenti informazioni: \n\n - URL: `{webhook_url}` \n - Method: POST \n\n Vedi [la documentazione]({docs_url}) for ulteriori dettagli." diff --git a/homeassistant/components/locative/translations/lb.json b/homeassistant/components/locative/translations/lb.json index 42086f5cfe5822..06d303ff0b1a8d 100644 --- a/homeassistant/components/locative/translations/lb.json +++ b/homeassistant/components/locative/translations/lb.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "\u00c4r Home Assistant Instanz muss iwwert Internet accessibel si fir Geofency Noriichten z'empf\u00e4nken.", - "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg." + "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg.", + "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech." }, "create_entry": { "default": "Fir Plazen un Home Assistant ze sch\u00e9cken, muss den Webhook Feature an der Locative App ageriicht ginn.\n\nF\u00ebllt folgend Informatiounen aus:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nLiest [Dokumentatioun]({docs_url}) fir w\u00e9ider D\u00e9tailer." diff --git a/homeassistant/components/locative/translations/no.json b/homeassistant/components/locative/translations/no.json index 46f9f413236075..fdb42ff7e12528 100644 --- a/homeassistant/components/locative/translations/no.json +++ b/homeassistant/components/locative/translations/no.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Home Assistant m\u00e5 v\u00e6re tilgjengelig fra internett for \u00e5 kunne motta meldinger fra Geofency.", - "one_instance_allowed": "Kun en forekomst er n\u00f8dvendig." + "one_instance_allowed": "Kun en forekomst er n\u00f8dvendig.", + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "create_entry": { "default": "For \u00e5 kunne sende steder til Home Assistant, m\u00e5 du sette opp webhook-funksjonen i Locative. \n\n Fyll ut f\u00f8lgende informasjon: \n\n - URL: `{webhook_url}` \n - Metode: POST \n\n Se [dokumentasjonen]({docs_url}) for ytterligere detaljer." diff --git a/homeassistant/components/locative/translations/ru.json b/homeassistant/components/locative/translations/ru.json index 591f2f423024d2..0981c9f2f1c683 100644 --- a/homeassistant/components/locative/translations/ru.json +++ b/homeassistant/components/locative/translations/ru.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 Locative.", - "one_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "one_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e." }, "create_entry": { "default": "\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 Home Assistant \u0412\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Webhook \u0434\u043b\u044f Locative.\n\n\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e:\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\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]({docs_url}) \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." diff --git a/homeassistant/components/locative/translations/zh-Hant.json b/homeassistant/components/locative/translations/zh-Hant.json index 3b0089ed220bcd..f84a40c1152e14 100644 --- a/homeassistant/components/locative/translations/zh-Hant.json +++ b/homeassistant/components/locative/translations/zh-Hant.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Home Assistant \u8a2d\u5099\u5fc5\u9808\u80fd\u5920\u7531\u7db2\u969b\u7db2\u8def\u5b58\u53d6\uff0c\u65b9\u80fd\u63a5\u53d7 Locative \u8a0a\u606f\u3002", - "one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u5373\u53ef\u3002" + "one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u5373\u53ef\u3002", + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002" }, "create_entry": { "default": "\u6b32\u50b3\u9001\u5ea7\u6a19\u81f3 Home Assistant\uff0c\u5c07\u9700\u65bc Locative App \u5167\u8a2d\u5b9a webhook \u529f\u80fd\u3002\n\n\u8acb\u586b\u5beb\u4e0b\u5217\u8cc7\u8a0a\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\u8acb\u53c3\u95b1 [\u6587\u4ef6]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u8a73\u7d30\u8cc7\u6599\u3002" diff --git a/homeassistant/components/lock/group.py b/homeassistant/components/lock/group.py new file mode 100644 index 00000000000000..d64b2172750638 --- /dev/null +++ b/homeassistant/components/lock/group.py @@ -0,0 +1,15 @@ +"""Describe group states.""" + + +from homeassistant.components.group import GroupIntegrationRegistry +from homeassistant.const import STATE_LOCKED, STATE_UNLOCKED +from homeassistant.core import callback +from homeassistant.helpers.typing import HomeAssistantType + + +@callback +def async_describe_on_off_states( + hass: HomeAssistantType, registry: GroupIntegrationRegistry +) -> None: + """Describe group on off states.""" + registry.on_off_states({STATE_LOCKED}, STATE_UNLOCKED) diff --git a/homeassistant/components/lock/translations/et.json b/homeassistant/components/lock/translations/et.json index 448d1d4531ad46..1ebf7e1e6dd751 100644 --- a/homeassistant/components/lock/translations/et.json +++ b/homeassistant/components/lock/translations/et.json @@ -1,4 +1,19 @@ { + "device_automation": { + "action_type": { + "lock": "Lukusta {entity_name}", + "open": "Ava {entity_name}", + "unlock": "Tee {entity_name} lahti" + }, + "condition_type": { + "is_locked": "{entity_name} on lukus", + "is_unlocked": "{entity_name} on lukustamata" + }, + "trigger_type": { + "locked": "{entity_name} on lukus", + "unlocked": "{entity_name} on lukustamata" + } + }, "state": { "_": { "locked": "Lukus", diff --git a/homeassistant/components/logbook/__init__.py b/homeassistant/components/logbook/__init__.py index 0c7786de90bf2a..9eb3c80435cb29 100644 --- a/homeassistant/components/logbook/__init__.py +++ b/homeassistant/components/logbook/__init__.py @@ -7,25 +7,24 @@ import sqlalchemy from sqlalchemy.orm import aliased +from sqlalchemy.sql.expression import literal import voluptuous as vol -from homeassistant.components import sun 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.models import ( Events, States, - process_timestamp, process_timestamp_to_utc_isoformat, ) from homeassistant.components.recorder.util import session_scope from homeassistant.components.script import EVENT_SCRIPT_STARTED from homeassistant.const import ( - ATTR_DEVICE_CLASS, ATTR_DOMAIN, ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, + ATTR_ICON, ATTR_NAME, ATTR_SERVICE, EVENT_CALL_SERVICE, @@ -34,16 +33,8 @@ EVENT_LOGBOOK_ENTRY, EVENT_STATE_CHANGED, HTTP_BAD_REQUEST, - STATE_NOT_HOME, - STATE_OFF, - STATE_ON, -) -from homeassistant.core import ( - DOMAIN as HA_DOMAIN, - callback, - split_entity_id, - valid_entity_id, ) +from homeassistant.core import DOMAIN as HA_DOMAIN, callback, split_entity_id from homeassistant.exceptions import InvalidEntityFormatError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entityfilter import ( @@ -60,6 +51,7 @@ 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": "([^"]+)"') _LOGGER = logging.getLogger(__name__) @@ -76,6 +68,8 @@ EMPTY_JSON_OBJECT = "{}" UNIT_OF_MEASUREMENT_JSON = '"unit_of_measurement":' +HA_DOMAIN_ENTITY_ID = f"{HA_DOMAIN}." + CONFIG_SCHEMA = vol.Schema( {DOMAIN: INCLUDE_EXCLUDE_BASE_FILTER_SCHEMA}, extra=vol.ALLOW_EXTRA ) @@ -85,13 +79,25 @@ EVENT_HOMEASSISTANT_STOP, ] -ALL_EVENT_TYPES = [ - EVENT_STATE_CHANGED, +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, +] + SCRIPT_AUTOMATION_EVENTS = [EVENT_AUTOMATION_TRIGGERED, EVENT_SCRIPT_STARTED] LOG_MESSAGE_SCHEMA = vol.Schema( @@ -206,7 +212,15 @@ async def get(self, request, datetime=None): else: period = int(period) - entity_id = request.query.get("entity") + entity_ids = request.query.get("entity") + if entity_ids: + 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 end_time = request.query.get("end_time") if end_time is None: @@ -229,7 +243,7 @@ def json_events(): hass, start_day, end_day, - entity_id, + entity_ids, self.filters, self.entities_filter, entity_matches_only, @@ -246,6 +260,7 @@ def humanify(hass, events, entity_attr_cache, context_lookup): - 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( @@ -280,27 +295,7 @@ def humanify(hass, events, entity_attr_cache, context_lookup): start_stop_events[event.time_fired_minute] = 2 # Yield entries - external_events = hass.data.get(DOMAIN, {}) for event in events_batch: - if 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 - context_event = context_lookup.get(event.context_id) - if context_event: - _augment_data_with_context( - data, - data.get(ATTR_ENTITY_ID), - event, - context_event, - entity_attr_cache, - external_events, - ) - yield data - if event.event_type == EVENT_STATE_CHANGED: entity_id = event.entity_id domain = event.domain @@ -317,13 +312,14 @@ def humanify(hass, events, entity_attr_cache, context_lookup): "name": _entity_name_from_event( entity_id, event, entity_attr_cache ), - "message": _entry_message_from_event( - entity_id, domain, event, entity_attr_cache - ), - "domain": domain, + "state": event.state, "entity_id": entity_id, } + icon = event.attributes_icon + if icon: + data["icon"] = icon + if event.context_user_id: data["context_user_id"] = event.context_user_id @@ -340,6 +336,25 @@ def humanify(hass, events, entity_attr_cache, context_lookup): 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 + context_event = context_lookup.get(event.context_id) + if context_event: + _augment_data_with_context( + data, + data.get(ATTR_ENTITY_ID), + event, + context_event, + 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 @@ -381,6 +396,7 @@ def humanify(hass, events, entity_attr_cache, context_lookup): "domain": domain, "entity_id": entity_id, } + if event.context_user_id: data["context_user_id"] = event.context_user_id @@ -402,231 +418,185 @@ def _get_events( hass, start_day, end_day, - entity_id=None, + entity_ids=None, filters=None, entities_filter=None, entity_matches_only=False, ): """Get events for a period of time.""" + entity_attr_cache = EntityAttributeCache(hass) context_lookup = {None: None} - entity_id_lower = None - apply_sql_entities_filter = True 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 _keep_event(hass, event, entities_filter): + 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_id is not None: - entity_id_lower = entity_id.lower() - if not valid_entity_id(entity_id_lower): - raise InvalidEntityFormatError( - f"Invalid entity id encountered: {entity_id_lower}. " - "Format should be ." - ) - entities_filter = generate_filter([], [entity_id_lower], [], []) - apply_sql_entities_filter = False + 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") - query = ( - session.query( - Events.event_type, - Events.event_data, - Events.time_fired, - Events.context_id, - Events.context_user_id, - States.state, - States.entity_id, - States.domain, - States.attributes, - ) - .order_by(Events.time_fired) - .outerjoin(States, (Events.event_id == States.event_id)) - .outerjoin(old_state, (States.old_state_id == old_state.state_id)) - # The below filter, removes state change events that do not have - # and old_state, new_state, or the old and - # new state. - # - .filter( - (Events.event_type != EVENT_STATE_CHANGED) - | ( - (States.state_id.isnot(None)) - & (old_state.state_id.isnot(None)) - & (States.state.isnot(None)) - & (States.state != old_state.state) - ) - ) - # - # Prefilter out continuous domains that have - # ATTR_UNIT_OF_MEASUREMENT as its much faster in sql. - # - .filter( - (Events.event_type != EVENT_STATE_CHANGED) - | sqlalchemy.not_(States.domain.in_(CONTINUOUS_DOMAINS)) - | sqlalchemy.not_(States.attributes.contains(UNIT_OF_MEASUREMENT_JSON)) + 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 ) - .filter( - Events.event_type.in_(ALL_EVENT_TYPES + list(hass.data.get(DOMAIN, {}))) - ) - .filter((Events.time_fired > start_day) & (Events.time_fired < end_day)) - ) - - if entity_id_lower is not None: if entity_matches_only: # When entity_matches_only is provided, contexts and events that do not - # contain the entity_id are not included in the logbook response. - entity_id_json = ENTITY_ID_JSON_TEMPLATE.format(entity_id_lower) - query = query.filter( - ( - (States.last_updated == States.last_changed) - & (States.entity_id == entity_id_lower) - ) - | ( - States.state_id.is_(None) - & Events.event_data.contains(entity_id_json) - ) - ) - else: - query = query.filter( - ( - (States.last_updated == States.last_changed) - & (States.entity_id == entity_id_lower) - ) - | (States.state_id.is_(None)) + # 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 = query.filter( + 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) - | (States.state_id.is_(None)) + | (Events.event_type != EVENT_STATE_CHANGED) ) - - if apply_sql_entities_filter and filters: - entity_filter = filters.entity_filter() - if entity_filter is not None: + if filters: query = query.filter( - entity_filter | (Events.event_type != EVENT_STATE_CHANGED) + filters.entity_filter() | (Events.event_type != EVENT_STATE_CHANGED) ) + query = query.order_by(Events.time_fired) + return list( humanify(hass, yield_events(query), entity_attr_cache, context_lookup) ) +def _generate_events_query(session): + return session.query( + *EVENT_COLUMNS, + States.state, + States.entity_id, + States.domain, + States.attributes, + ) + + +def _generate_events_query_without_states(session): + return session.query( + *EVENT_COLUMNS, + literal(None).label("state"), + literal(None).label("entity_id"), + literal(None).label("domain"), + literal(None).label("attributes"), + ) + + +def _generate_states_query(session, start_day, end_day, old_state, entity_ids): + 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(_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) + ) + ) + + +def _apply_events_types_and_states_filter(hass, query, old_state): + 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) | _continuous_entity_matcher() + ) + ) + return _apply_event_types_filter(hass, events_query, ALL_EVENT_TYPES) + + +def _missing_state_matcher(old_state): + # 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 _continuous_entity_matcher(): + # + # Prefilter out continuous domains that have + # ATTR_UNIT_OF_MEASUREMENT as its much faster in sql. + # + return sqlalchemy.or_( + sqlalchemy.not_(States.domain.in_(CONTINUOUS_DOMAINS)), + sqlalchemy.not_(States.attributes.contains(UNIT_OF_MEASUREMENT_JSON)), + ) + + +def _apply_event_time_filter(events_query, start_day, end_day): + return events_query.filter( + (Events.time_fired > start_day) & (Events.time_fired < end_day) + ) + + +def _apply_event_types_filter(hass, query, event_types): + return query.filter( + Events.event_type.in_(event_types + list(hass.data.get(DOMAIN, {}))) + ) + + +def _apply_event_entity_id_matchers(events_query, entity_ids): + return events_query.filter( + sqlalchemy.or_( + *[ + Events.event_data.contains(ENTITY_ID_JSON_TEMPLATE.format(entity_id)) + for entity_id in entity_ids + ] + ) + ) + + def _keep_event(hass, event, entities_filter): - if event.event_type == EVENT_STATE_CHANGED: - entity_id = event.entity_id - elif event.event_type in HOMEASSISTANT_EVENTS: - entity_id = f"{HA_DOMAIN}." - elif event.event_type == EVENT_CALL_SERVICE: - return False + if event.event_type in HOMEASSISTANT_EVENTS: + return entities_filter is None or entities_filter(HA_DOMAIN_ENTITY_ID) + + entity_id = event.data_entity_id + if 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: - entity_id = event.data_entity_id - if not 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 - entity_id = f"{domain}." - - return entities_filter is None or entities_filter(entity_id) - - -def _entry_message_from_event(entity_id, domain, event, entity_attr_cache): - """Convert a state to a message for the logbook.""" - # We pass domain in so we don't have to split entity_id again - state_state = event.state - - if domain in ["device_tracker", "person"]: - if state_state == STATE_NOT_HOME: - return "is away" - return f"is at {state_state}" - - if domain == "sun": - if state_state == sun.STATE_ABOVE_HORIZON: - return "has risen" - return "has set" - - if domain == "binary_sensor": - device_class = entity_attr_cache.get(entity_id, ATTR_DEVICE_CLASS, event) - if device_class == "battery": - if state_state == STATE_ON: - return "is low" - if state_state == STATE_OFF: - return "is normal" - - if device_class == "connectivity": - if state_state == STATE_ON: - return "is connected" - if state_state == STATE_OFF: - return "is disconnected" - - if device_class in ["door", "garage_door", "opening", "window"]: - if state_state == STATE_ON: - return "is opened" - if state_state == STATE_OFF: - return "is closed" - - if device_class == "lock": - if state_state == STATE_ON: - return "is unlocked" - if state_state == STATE_OFF: - return "is locked" - - if device_class == "plug": - if state_state == STATE_ON: - return "is plugged in" - if state_state == STATE_OFF: - return "is unplugged" - - if device_class == "presence": - if state_state == STATE_ON: - return "is at home" - if state_state == STATE_OFF: - return "is away" - - if device_class == "safety": - if state_state == STATE_ON: - return "is unsafe" - if state_state == STATE_OFF: - return "is safe" - - if device_class in [ - "cold", - "gas", - "heat", - "light", - "moisture", - "motion", - "occupancy", - "power", - "problem", - "smoke", - "sound", - "vibration", - ]: - if state_state == STATE_ON: - return f"detected {device_class}" - if state_state == STATE_OFF: - return f"cleared (no {device_class} detected)" - - if state_state == STATE_ON: - # Future: combine groups and its entity entries ? - return "turned on" - - if state_state == STATE_OFF: - return "turned off" - - return f"changed to {state_state}" + 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( @@ -697,7 +667,6 @@ class LazyEventPartialState: __slots__ = [ "_row", "_event_data", - "_time_fired", "_time_fired_isoformat", "_attributes", "event_type", @@ -713,7 +682,6 @@ def __init__(self, row): """Init the lazy event.""" self._row = row self._event_data = None - self._time_fired = None self._time_fired_isoformat = None self._attributes = None self.event_type = self._row.event_type @@ -724,6 +692,15 @@ def __init__(self, row): self.context_user_id = self._row.context_user_id self.time_fired_minute = self._row.time_fired.minute + @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.attributes) + return result and result.group(1) + @property def data_entity_id(self): """Extract the entity id from the decoded data or json.""" @@ -765,25 +742,14 @@ def data(self): self._event_data = json.loads(self._row.event_data) return self._event_data - @property - def time_fired(self): - """Time event was fired in utc.""" - if not self._time_fired: - self._time_fired = ( - process_timestamp(self._row.time_fired) or dt_util.utcnow() - ) - return self._time_fired - @property def time_fired_isoformat(self): """Time event was fired in utc isoformat.""" if not self._time_fired_isoformat: - if self._time_fired: - self._time_fired_isoformat = self._time_fired.isoformat() - else: - self._time_fired_isoformat = process_timestamp_to_utc_isoformat( - self._row.time_fired or dt_util.utcnow() - ) + self._time_fired_isoformat = process_timestamp_to_utc_isoformat( + self._row.time_fired or dt_util.utcnow() + ) + return self._time_fired_isoformat diff --git a/homeassistant/components/luci/device_tracker.py b/homeassistant/components/luci/device_tracker.py index 9d71b3d263a743..d4fb1d5f7bc388 100644 --- a/homeassistant/components/luci/device_tracker.py +++ b/homeassistant/components/luci/device_tracker.py @@ -94,6 +94,12 @@ def _update_info(self): last_results = [] for device in result: - last_results.append(device) + if ( + not hasattr(self.router.router.owrt_version, "release") + or not self.router.router.owrt_version.release + or self.router.router.owrt_version.release[0] < 19 + or device.reachable + ): + last_results.append(device) self.last_results = last_results diff --git a/homeassistant/components/luci/manifest.json b/homeassistant/components/luci/manifest.json index d18cbae51035d6..3b51aab6e4acbb 100644 --- a/homeassistant/components/luci/manifest.json +++ b/homeassistant/components/luci/manifest.json @@ -3,5 +3,5 @@ "name": "OpenWRT (luci)", "documentation": "https://www.home-assistant.io/integrations/luci", "requirements": ["openwrt-luci-rpc==1.1.6"], - "codeowners": ["@fbradyirl", "@mzdrale"] + "codeowners": ["@mzdrale"] } diff --git a/homeassistant/components/luftdaten/__init__.py b/homeassistant/components/luftdaten/__init__.py index 9d18496913911b..91e9c96d42936b 100644 --- a/homeassistant/components/luftdaten/__init__.py +++ b/homeassistant/components/luftdaten/__init__.py @@ -13,6 +13,7 @@ CONF_SENSORS, CONF_SHOW_ON_MAP, PERCENTAGE, + PRESSURE_PA, TEMP_CELSIUS, ) from homeassistant.core import callback @@ -44,8 +45,8 @@ SENSORS = { SENSOR_TEMPERATURE: ["Temperature", "mdi:thermometer", TEMP_CELSIUS], SENSOR_HUMIDITY: ["Humidity", "mdi:water-percent", PERCENTAGE], - SENSOR_PRESSURE: ["Pressure", "mdi:arrow-down-bold", "Pa"], - SENSOR_PRESSURE_AT_SEALEVEL: ["Pressure at sealevel", "mdi:download", "Pa"], + SENSOR_PRESSURE: ["Pressure", "mdi:arrow-down-bold", PRESSURE_PA], + SENSOR_PRESSURE_AT_SEALEVEL: ["Pressure at sealevel", "mdi:download", PRESSURE_PA], SENSOR_PM10: [ "PM10", "mdi:thought-bubble", diff --git a/homeassistant/components/lutron/binary_sensor.py b/homeassistant/components/lutron/binary_sensor.py index e2e143da4352ee..f77a2b120daced 100644 --- a/homeassistant/components/lutron/binary_sensor.py +++ b/homeassistant/components/lutron/binary_sensor.py @@ -51,6 +51,4 @@ def name(self): @property def device_state_attributes(self): """Return the state attributes.""" - attr = {} - attr["lutron_integration_id"] = self._lutron_device.id - return attr + return {"lutron_integration_id": self._lutron_device.id} diff --git a/homeassistant/components/lutron/cover.py b/homeassistant/components/lutron/cover.py index 438a433fb0fe31..1ec7c07aac04ed 100644 --- a/homeassistant/components/lutron/cover.py +++ b/homeassistant/components/lutron/cover.py @@ -66,6 +66,4 @@ def update(self): @property def device_state_attributes(self): """Return the state attributes.""" - attr = {} - attr["Lutron Integration ID"] = self._lutron_device.id - return attr + return {"Lutron Integration ID": self._lutron_device.id} diff --git a/homeassistant/components/lutron/light.py b/homeassistant/components/lutron/light.py index 2b5bff7d848291..411a1d494df470 100644 --- a/homeassistant/components/lutron/light.py +++ b/homeassistant/components/lutron/light.py @@ -71,8 +71,7 @@ def turn_off(self, **kwargs): @property def device_state_attributes(self): """Return the state attributes.""" - attr = {"lutron_integration_id": self._lutron_device.id} - return attr + return {"lutron_integration_id": self._lutron_device.id} @property def is_on(self): diff --git a/homeassistant/components/lutron/switch.py b/homeassistant/components/lutron/switch.py index d03cb4a19530ee..ac00faaa9bcc19 100644 --- a/homeassistant/components/lutron/switch.py +++ b/homeassistant/components/lutron/switch.py @@ -48,9 +48,7 @@ def turn_off(self, **kwargs): @property def device_state_attributes(self): """Return the state attributes.""" - attr = {} - attr["lutron_integration_id"] = self._lutron_device.id - return attr + return {"lutron_integration_id": self._lutron_device.id} @property def is_on(self): @@ -83,12 +81,11 @@ def turn_off(self, **kwargs): @property def device_state_attributes(self): """Return the state attributes.""" - attr = { + return { "keypad": self._keypad_name, "scene": self._scene_name, "led": self._lutron_device.name, } - return attr @property def is_on(self): diff --git a/homeassistant/components/lutron_caseta/__init__.py b/homeassistant/components/lutron_caseta/__init__.py index 40b65293f1d266..37a9d12af957f5 100644 --- a/homeassistant/components/lutron_caseta/__init__.py +++ b/homeassistant/components/lutron_caseta/__init__.py @@ -64,9 +64,9 @@ async def async_setup_entry(hass, config_entry): """Set up a bridge from a config entry.""" host = config_entry.data[CONF_HOST] - keyfile = config_entry.data[CONF_KEYFILE] - certfile = config_entry.data[CONF_CERTFILE] - ca_certs = config_entry.data[CONF_CA_CERTS] + keyfile = hass.config.path(config_entry.data[CONF_KEYFILE]) + certfile = hass.config.path(config_entry.data[CONF_CERTFILE]) + ca_certs = hass.config.path(config_entry.data[CONF_CA_CERTS]) bridge = Smartbridge.create_tls( hostname=host, keyfile=keyfile, certfile=certfile, ca_certs=ca_certs diff --git a/homeassistant/components/lutron_caseta/config_flow.py b/homeassistant/components/lutron_caseta/config_flow.py index 3a5a4a151a1450..1290d88b09c590 100644 --- a/homeassistant/components/lutron_caseta/config_flow.py +++ b/homeassistant/components/lutron_caseta/config_flow.py @@ -83,9 +83,9 @@ async def async_validate_connectable_bridge_config(self): try: bridge = Smartbridge.create_tls( hostname=self.data[CONF_HOST], - keyfile=self.data[CONF_KEYFILE], - certfile=self.data[CONF_CERTFILE], - ca_certs=self.data[CONF_CA_CERTS], + keyfile=self.hass.config.path(self.data[CONF_KEYFILE]), + certfile=self.hass.config.path(self.data[CONF_CERTFILE]), + ca_certs=self.hass.config.path(self.data[CONF_CA_CERTS]), ) await bridge.connect() diff --git a/homeassistant/components/lutron_caseta/cover.py b/homeassistant/components/lutron_caseta/cover.py index 81a65786900be7..5eabfcd16fe19f 100644 --- a/homeassistant/components/lutron_caseta/cover.py +++ b/homeassistant/components/lutron_caseta/cover.py @@ -7,6 +7,7 @@ SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_SET_POSITION, + SUPPORT_STOP, CoverEntity, ) @@ -39,7 +40,7 @@ class LutronCasetaCover(LutronCasetaDevice, CoverEntity): @property def supported_features(self): """Flag supported features.""" - return SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION + return SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP | SUPPORT_SET_POSITION @property def is_closed(self): @@ -51,19 +52,27 @@ def current_cover_position(self): """Return the current position of cover.""" return self._device["current_state"] + async def async_stop_cover(self, **kwargs): + """Top the cover.""" + await self._smartbridge.stop_cover(self.device_id) + async def async_close_cover(self, **kwargs): """Close the cover.""" - self._smartbridge.set_value(self.device_id, 0) + await self._smartbridge.lower_cover(self.device_id) + self.async_update() + self.async_write_ha_state() async def async_open_cover(self, **kwargs): """Open the cover.""" - self._smartbridge.set_value(self.device_id, 100) + await self._smartbridge.raise_cover(self.device_id) + self.async_update() + self.async_write_ha_state() async def async_set_cover_position(self, **kwargs): """Move the shade to a specific position.""" if ATTR_POSITION in kwargs: position = kwargs[ATTR_POSITION] - self._smartbridge.set_value(self.device_id, position) + await self._smartbridge.set_value(self.device_id, position) async def async_update(self): """Call when forcing a refresh of the device.""" diff --git a/homeassistant/components/lutron_caseta/fan.py b/homeassistant/components/lutron_caseta/fan.py index aa6ab1c714490f..90728c5f2fe55c 100644 --- a/homeassistant/components/lutron_caseta/fan.py +++ b/homeassistant/components/lutron_caseta/fan.py @@ -84,7 +84,7 @@ async def async_turn_off(self, **kwargs): async def async_set_speed(self, speed: str) -> None: """Set the speed of the fan.""" - self._smartbridge.set_fan(self.device_id, SPEED_TO_VALUE[speed]) + await self._smartbridge.set_fan(self.device_id, SPEED_TO_VALUE[speed]) @property def is_on(self): diff --git a/homeassistant/components/lutron_caseta/light.py b/homeassistant/components/lutron_caseta/light.py index 471be51219bb18..c46e8931390b32 100644 --- a/homeassistant/components/lutron_caseta/light.py +++ b/homeassistant/components/lutron_caseta/light.py @@ -1,10 +1,13 @@ """Support for Lutron Caseta lights.""" +from datetime import timedelta import logging from homeassistant.components.light import ( ATTR_BRIGHTNESS, + ATTR_TRANSITION, DOMAIN, SUPPORT_BRIGHTNESS, + SUPPORT_TRANSITION, LightEntity, ) @@ -47,21 +50,31 @@ class LutronCasetaLight(LutronCasetaDevice, LightEntity): @property def supported_features(self): """Flag supported features.""" - return SUPPORT_BRIGHTNESS + return SUPPORT_BRIGHTNESS | SUPPORT_TRANSITION @property def brightness(self): """Return the brightness of the light.""" return to_hass_level(self._device["current_state"]) + async def _set_brightness(self, brightness, **kwargs): + args = {} + if ATTR_TRANSITION in kwargs: + args["fade_time"] = timedelta(seconds=kwargs[ATTR_TRANSITION]) + + await self._smartbridge.set_value( + self.device_id, to_lutron_level(brightness), **args + ) + async def async_turn_on(self, **kwargs): """Turn the light on.""" - brightness = kwargs.get(ATTR_BRIGHTNESS, 255) - self._smartbridge.set_value(self.device_id, to_lutron_level(brightness)) + brightness = kwargs.pop(ATTR_BRIGHTNESS, 255) + + await self._set_brightness(brightness, **kwargs) async def async_turn_off(self, **kwargs): """Turn the light off.""" - self._smartbridge.set_value(self.device_id, 0) + await self._set_brightness(0, **kwargs) @property def is_on(self): diff --git a/homeassistant/components/lutron_caseta/manifest.json b/homeassistant/components/lutron_caseta/manifest.json index 34fc326425cc3c..f80e34a6b5c158 100644 --- a/homeassistant/components/lutron_caseta/manifest.json +++ b/homeassistant/components/lutron_caseta/manifest.json @@ -3,9 +3,9 @@ "name": "Lutron Caséta", "documentation": "https://www.home-assistant.io/integrations/lutron_caseta", "requirements": [ - "pylutron-caseta==0.6.1" + "pylutron-caseta==0.7.0" ], "codeowners": [ "@swails" ] -} +} \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/scene.py b/homeassistant/components/lutron_caseta/scene.py index c74f60bc88ce97..a69f4594d14760 100644 --- a/homeassistant/components/lutron_caseta/scene.py +++ b/homeassistant/components/lutron_caseta/scene.py @@ -43,4 +43,4 @@ def name(self): async def async_activate(self, **kwargs: Any) -> None: """Activate the scene.""" - self._bridge.activate_scene(self._scene_id) + await self._bridge.activate_scene(self._scene_id) diff --git a/homeassistant/components/lutron_caseta/strings.json b/homeassistant/components/lutron_caseta/strings.json index 082497b1bf26a4..a03cdd8c9d66ae 100644 --- a/homeassistant/components/lutron_caseta/strings.json +++ b/homeassistant/components/lutron_caseta/strings.json @@ -7,11 +7,11 @@ } }, "error": { - "cannot_connect": "Failed to connect to Caséta bridge; check your host and certificate configuration." + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, "abort": { - "already_configured": "Caséta bridge already configured.", - "cannot_connect": "Cancelled setup of Caséta bridge due to connection failure." + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/lutron_caseta/switch.py b/homeassistant/components/lutron_caseta/switch.py index d7f9246feeb0f0..1cccd485524bad 100644 --- a/homeassistant/components/lutron_caseta/switch.py +++ b/homeassistant/components/lutron_caseta/switch.py @@ -32,11 +32,11 @@ class LutronCasetaLight(LutronCasetaDevice, SwitchEntity): async def async_turn_on(self, **kwargs): """Turn the switch on.""" - self._smartbridge.turn_on(self.device_id) + await self._smartbridge.turn_on(self.device_id) async def async_turn_off(self, **kwargs): """Turn the switch off.""" - self._smartbridge.turn_off(self.device_id) + await self._smartbridge.turn_off(self.device_id) @property def is_on(self): diff --git a/homeassistant/components/lutron_caseta/translations/ca.json b/homeassistant/components/lutron_caseta/translations/ca.json index 4b6be6e3a6ed96..c3b0e686cc4616 100644 --- a/homeassistant/components/lutron_caseta/translations/ca.json +++ b/homeassistant/components/lutron_caseta/translations/ca.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "L'enlla\u00e7 de Cas\u00e9ta ja configurat.", - "cannot_connect": "S'ha cancel\u00b7lat la configuraci\u00f3 de l'enlla\u00e7 de Cas\u00e9ta per un error en la connexi\u00f3." + "already_configured": "El dispositiu ja est\u00e0 configurat", + "cannot_connect": "Ha fallat la connexi\u00f3" }, "error": { - "cannot_connect": "No s'ha pogut connectar a l'enlla\u00e7 de Cas\u00e9ta; comprova la configuraci\u00f3 de l'amfitri\u00f3 i del certificat." + "cannot_connect": "Ha fallat la connexi\u00f3" }, "step": { "import_failed": { diff --git a/homeassistant/components/lutron_caseta/translations/en.json b/homeassistant/components/lutron_caseta/translations/en.json index 469bcae37c7957..3797d476db3495 100644 --- a/homeassistant/components/lutron_caseta/translations/en.json +++ b/homeassistant/components/lutron_caseta/translations/en.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Cas\u00e9ta bridge already configured.", - "cannot_connect": "Cancelled setup of Cas\u00e9ta bridge due to connection failure." + "already_configured": "Device is already configured", + "cannot_connect": "Failed to connect" }, "error": { - "cannot_connect": "Failed to connect to Cas\u00e9ta bridge; check your host and certificate configuration." + "cannot_connect": "Failed to connect" }, "step": { "import_failed": { diff --git a/homeassistant/components/lutron_caseta/translations/et.json b/homeassistant/components/lutron_caseta/translations/et.json new file mode 100644 index 00000000000000..72a54b6b97742a --- /dev/null +++ b/homeassistant/components/lutron_caseta/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "cannot_connect": "Cas\u00e9ta Bridge seadmega \u00fchenduse loomine nurjus. Kontrolli hosti ja serdi s\u00e4tteid." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/lutron_caseta/translations/fr.json b/homeassistant/components/lutron_caseta/translations/fr.json index 02c48b586f206b..0674172e97581f 100644 --- a/homeassistant/components/lutron_caseta/translations/fr.json +++ b/homeassistant/components/lutron_caseta/translations/fr.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "already_configured": "Pont Cas\u00e9ta d\u00e9j\u00e0 configur\u00e9.", + "cannot_connect": "Installation annul\u00e9e du pont Cas\u00e9ta en raison d'un \u00e9chec de connexion." + }, "error": { "cannot_connect": "\u00c9chec de la connexion \u00e0 la passerelle Cas\u00e9ta; v\u00e9rifiez la configuration de votre h\u00f4te et de votre certificat." }, diff --git a/homeassistant/components/lutron_caseta/translations/it.json b/homeassistant/components/lutron_caseta/translations/it.json index 9e4cb9ec02cce7..5bdcf87607dccc 100644 --- a/homeassistant/components/lutron_caseta/translations/it.json +++ b/homeassistant/components/lutron_caseta/translations/it.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Il bridge Cas\u00e9ta \u00e8 gi\u00e0 configurato.", - "cannot_connect": "Configurazione annullata del bridge Cas\u00e9ta a causa di un errore di connessione." + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "cannot_connect": "Impossibile connettersi" }, "error": { - "cannot_connect": "Impossibile connettersi al bridge Cas\u00e9ta; controllare la configurazione dell'host e del certificato." + "cannot_connect": "Impossibile connettersi" }, "step": { "import_failed": { diff --git a/homeassistant/components/lutron_caseta/translations/no.json b/homeassistant/components/lutron_caseta/translations/no.json index b63e8fb4111d26..b4cdadd2d2fef4 100644 --- a/homeassistant/components/lutron_caseta/translations/no.json +++ b/homeassistant/components/lutron_caseta/translations/no.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Cas\u00e9ta bridge allerede konfigurert.", - "cannot_connect": "Avbrutt oppsett av Cas\u00e9ta bridge p\u00e5 grunn av tilkoblingssvikt." + "already_configured": "Enheten er allerede konfigurert", + "cannot_connect": "Tilkobling mislyktes." }, "error": { - "cannot_connect": "Kunne ikke koble til Cas\u00e9ta bridge; sjekk verts- og sertifikatkonfigurasjonen." + "cannot_connect": "Tilkobling mislyktes." }, "step": { "import_failed": { diff --git a/homeassistant/components/lutron_caseta/translations/ru.json b/homeassistant/components/lutron_caseta/translations/ru.json index f7f566b1f543b2..05bd4f51c70bf1 100644 --- a/homeassistant/components/lutron_caseta/translations/ru.json +++ b/homeassistant/components/lutron_caseta/translations/ru.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", - "cannot_connect": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0448\u043b\u044e\u0437\u0430 \u043e\u0442\u043c\u0435\u043d\u0435\u043d\u0430 \u0438\u0437-\u0437\u0430 \u0441\u0431\u043e\u044f \u0432 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\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.", + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\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 \u043a \u0448\u043b\u044e\u0437\u0443, \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0430\u0434\u0440\u0435\u0441 \u0445\u043e\u0441\u0442\u0430 \u0438 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442." + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." }, "step": { "import_failed": { diff --git a/homeassistant/components/mailgun/strings.json b/homeassistant/components/mailgun/strings.json index 29ea3c0b952a6f..a948c6165e7977 100644 --- a/homeassistant/components/mailgun/strings.json +++ b/homeassistant/components/mailgun/strings.json @@ -7,7 +7,7 @@ } }, "abort": { - "one_instance_allowed": "Only a single instance is necessary.", + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive Mailgun messages." }, "create_entry": { diff --git a/homeassistant/components/mailgun/translations/ca.json b/homeassistant/components/mailgun/translations/ca.json index 629205b276d0c2..496894090a7ab2 100644 --- a/homeassistant/components/mailgun/translations/ca.json +++ b/homeassistant/components/mailgun/translations/ca.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "La inst\u00e0ncia de Home Assistant ha de ser accessible des d'Internet per rebre missatges de Mailgun.", - "one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia." + "one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia.", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "create_entry": { "default": "Per enviar esdeveniments a Home Assistant, haur\u00e0s de configurar [Webhooks amb Mailgun]({mailgun_url}). \n\nCompleta la seg\u00fcent informaci\u00f3: \n\n- URL: `{webhook_url}` \n- M\u00e8tode: POST \n- Tipus de contingut: application/json\n\nConsulta la [documentaci\u00f3]({docs_url}) sobre com configurar les automatitzacions per gestionar dades entrants." diff --git a/homeassistant/components/mailgun/translations/el.json b/homeassistant/components/mailgun/translations/el.json new file mode 100644 index 00000000000000..aecb2ee553fa42 --- /dev/null +++ b/homeassistant/components/mailgun/translations/el.json @@ -0,0 +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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/translations/en.json b/homeassistant/components/mailgun/translations/en.json index 85044505c84f09..bcb3556087dd7d 100644 --- a/homeassistant/components/mailgun/translations/en.json +++ b/homeassistant/components/mailgun/translations/en.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive Mailgun messages.", - "one_instance_allowed": "Only a single instance is necessary." + "one_instance_allowed": "Only a single instance is necessary.", + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "create_entry": { "default": "To send events to Home Assistant, you will need to setup [Webhooks with Mailgun]({mailgun_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data." diff --git a/homeassistant/components/mailgun/translations/es.json b/homeassistant/components/mailgun/translations/es.json index f2e5638dcc338e..4b9762c8b92514 100644 --- a/homeassistant/components/mailgun/translations/es.json +++ b/homeassistant/components/mailgun/translations/es.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Tu instancia de Home Assistant debe ser accesible desde Internet para recibir mensajes de Mailgun.", - "one_instance_allowed": "S\u00f3lo se necesita una instancia." + "one_instance_allowed": "S\u00f3lo se necesita una instancia.", + "single_instance_allowed": "Ya est\u00e1 configurado. S\u00f3lo es posible una \u00fanica configuraci\u00f3n." }, "create_entry": { "default": "Para enviar eventos a Home Assistant debes configurar los [Webhooks en Mailgun]({mailgun_url}). \n\n Completa la siguiente informaci\u00f3n: \n\n - URL: `{webhook_url}` \n - M\u00e9todo: POST \n - Tipo de contenido: application/json \n\n Consulta [la documentaci\u00f3n]({docs_url}) sobre c\u00f3mo configurar las automatizaciones para manejar los datos entrantes." diff --git a/homeassistant/components/mailgun/translations/et.json b/homeassistant/components/mailgun/translations/et.json new file mode 100644 index 00000000000000..e8b5eae41495c6 --- /dev/null +++ b/homeassistant/components/mailgun/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mailgun/translations/fr.json b/homeassistant/components/mailgun/translations/fr.json index 266c48f91a5e57..edb9f01be3deb3 100644 --- a/homeassistant/components/mailgun/translations/fr.json +++ b/homeassistant/components/mailgun/translations/fr.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Votre instance Home Assistant doit \u00eatre accessible \u00e0 partir d'Internet pour recevoir les messages Mailgun.", - "one_instance_allowed": "Une seule instance est n\u00e9cessaire." + "one_instance_allowed": "Une seule instance est n\u00e9cessaire.", + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." }, "create_entry": { "default": "Pour envoyer des \u00e9v\u00e9nements \u00e0 Home Assistant, vous devez configurer [Webhooks avec Mailgun]({mailgun_url}). \n\n Remplissez les informations suivantes: \n\n - URL: `{webhook_url}` \n - M\u00e9thode: POST \n - Type de contenu: application/json \n\n Voir [la documentation]({docs_url}) pour savoir comment configurer les automatisations pour g\u00e9rer les donn\u00e9es entrantes." diff --git a/homeassistant/components/mailgun/translations/it.json b/homeassistant/components/mailgun/translations/it.json index 624301a14d19a3..0373d686744c72 100644 --- a/homeassistant/components/mailgun/translations/it.json +++ b/homeassistant/components/mailgun/translations/it.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "La tua istanza di Home Assistant deve essere accessibile da Internet per ricevere messaggi da Mailgun.", - "one_instance_allowed": "\u00c8 necessaria una sola istanza." + "one_instance_allowed": "\u00c8 necessaria una sola istanza.", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "create_entry": { "default": "Per inviare eventi a Home Assistant, dovrai configurare [Webhooks con Mailgun]({mailgun_url})\n\n Compila le seguenti informazioni: \n\n - URL: `{webhook_url}` \n - Method: POST \n - Content Type: application/json\n\n Vedi [la documentazione]({docs_url}) su come configurare le automazioni per gestire i dati in arrivo." diff --git a/homeassistant/components/mailgun/translations/lb.json b/homeassistant/components/mailgun/translations/lb.json index 10504b88214ced..42e3c98d8378d3 100644 --- a/homeassistant/components/mailgun/translations/lb.json +++ b/homeassistant/components/mailgun/translations/lb.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "\u00c4r Home Assistant Instanz muss iwwert Internet accessibel si fir Mailgun Noriichten z'empf\u00e4nken.", - "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg." + "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg.", + "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech." }, "create_entry": { "default": "Fir Evenementer un Home Assistant ze sch\u00e9cken, mussen [Webhooks mat Mailgun]({mailgun_url}) ageriicht ginn.\n\nF\u00ebllt folgend Informatiounen aus:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nLiest [Dokumentatioun]({docs_url}) w\u00e9i een Automatiounen ariicht welch eingehend Donn\u00e9\u00eb trait\u00e9ieren." diff --git a/homeassistant/components/mailgun/translations/no.json b/homeassistant/components/mailgun/translations/no.json index 9f26892e703ba0..a649df5a968bc8 100644 --- a/homeassistant/components/mailgun/translations/no.json +++ b/homeassistant/components/mailgun/translations/no.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Din Home Assistant forekomst m\u00e5 v\u00e6re tilgjengelig fra internett for \u00e5 motta Mailgun-meldinger.", - "one_instance_allowed": "Kun en forekomst er n\u00f8dvendig." + "one_instance_allowed": "Kun en forekomst er n\u00f8dvendig.", + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "create_entry": { "default": "For \u00e5 sende hendelser til Home Assistant, m\u00e5 du sette opp [Webhooks with Mailgun]({mailgun_url}).\n\nFyll ut f\u00f8lgende informasjon:\n\n- URL: `{webhook_url}`\n- Metode: POST\n- Innholdstype: application/json\n\nSe [dokumentasjonen]({docs_url}) om hvordan du konfigurerer automatiseringer for \u00e5 h\u00e5ndtere innkommende data." diff --git a/homeassistant/components/mailgun/translations/ru.json b/homeassistant/components/mailgun/translations/ru.json index 1d469c4692defa..f8a7c4518191c7 100644 --- a/homeassistant/components/mailgun/translations/ru.json +++ b/homeassistant/components/mailgun/translations/ru.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 Mailgun.", - "one_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "one_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e." }, "create_entry": { "default": "\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 Home Assistant \u0412\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Webhook \u0434\u043b\u044f [Mailgun]({mailgun_url}).\n\n\u0417\u0430\u043f\u043e\u043b\u043d\u0438\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\n\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]({docs_url}) \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0439 \u043f\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u043f\u043e\u0441\u0442\u0443\u043f\u0430\u044e\u0449\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445." diff --git a/homeassistant/components/mailgun/translations/zh-Hant.json b/homeassistant/components/mailgun/translations/zh-Hant.json index cd39d7a4d83fd5..51b859cb73fead 100644 --- a/homeassistant/components/mailgun/translations/zh-Hant.json +++ b/homeassistant/components/mailgun/translations/zh-Hant.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Home Assistant \u8a2d\u5099\u5fc5\u9808\u80fd\u5920\u7531\u7db2\u969b\u7db2\u8def\u5b58\u53d6\uff0c\u65b9\u80fd\u63a5\u53d7 Mailgun \u8a0a\u606f\u3002", - "one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u7269\u4ef6\u5373\u53ef\u3002" + "one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u7269\u4ef6\u5373\u53ef\u3002", + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002" }, "create_entry": { "default": "\u6b32\u50b3\u9001\u4e8b\u4ef6\u81f3 Home Assistant\uff0c\u5c07\u9700\u8a2d\u5b9a [Webhooks with Mailgun]({mailgun_url}) \u3002\n\n\u8acb\u586b\u5beb\u4e0b\u5217\u8cc7\u8a0a\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\n\u95dc\u65bc\u5982\u4f55\u50b3\u5165\u8cc7\u6599\u81ea\u52d5\u5316\u8a2d\u5b9a\uff0c\u8acb\u53c3\u95b1[\u6587\u4ef6]({docs_url})\u4ee5\u9032\u884c\u4e86\u89e3\u3002" diff --git a/homeassistant/components/manual/alarm_control_panel.py b/homeassistant/components/manual/alarm_control_panel.py index 4e361b5086c7ba..168d5b637e920e 100644 --- a/homeassistant/components/manual/alarm_control_panel.py +++ b/homeassistant/components/manual/alarm_control_panel.py @@ -394,13 +394,12 @@ def _validate_code(self, code, state): @property def device_state_attributes(self): """Return the state attributes.""" - state_attr = {} - if self.state == STATE_ALARM_PENDING or self.state == STATE_ALARM_ARMING: - state_attr[ATTR_PREVIOUS_STATE] = self._previous_state - state_attr[ATTR_NEXT_STATE] = self._state - - return state_attr + return { + ATTR_PREVIOUS_STATE: self._previous_state, + ATTR_NEXT_STATE: self._state, + } + return {} @callback def async_scheduled_update(self, now): diff --git a/homeassistant/components/manual_mqtt/alarm_control_panel.py b/homeassistant/components/manual_mqtt/alarm_control_panel.py index 4c760d3dab029b..10c2a473a29562 100644 --- a/homeassistant/components/manual_mqtt/alarm_control_panel.py +++ b/homeassistant/components/manual_mqtt/alarm_control_panel.py @@ -415,13 +415,12 @@ def _validate_code(self, code, state): @property def device_state_attributes(self): """Return the state attributes.""" - state_attr = {} - - if self.state == STATE_ALARM_PENDING: - state_attr[ATTR_PRE_PENDING_STATE] = self._previous_state - state_attr[ATTR_POST_PENDING_STATE] = self._state - - return state_attr + if self.state != STATE_ALARM_PENDING: + return {} + return { + ATTR_PRE_PENDING_STATE: self._previous_state, + ATTR_POST_PENDING_STATE: self._state, + } async def async_added_to_hass(self): """Subscribe to MQTT events.""" diff --git a/homeassistant/components/maxcube/binary_sensor.py b/homeassistant/components/maxcube/binary_sensor.py index b42c96f99c2fec..312f55ce0d8aff 100644 --- a/homeassistant/components/maxcube/binary_sensor.py +++ b/homeassistant/components/maxcube/binary_sensor.py @@ -1,7 +1,10 @@ """Support for MAX! binary sensors via MAX! Cube.""" import logging -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_WINDOW, + BinarySensorEntity, +) from . import DATA_KEY @@ -30,16 +33,11 @@ class MaxCubeShutter(BinarySensorEntity): def __init__(self, handler, name, rf_address): """Initialize MAX! Cube BinarySensorEntity.""" self._name = name - self._sensor_type = "window" + self._sensor_type = DEVICE_CLASS_WINDOW self._rf_address = rf_address self._cubehandle = handler self._state = None - @property - def should_poll(self): - """Return the polling state.""" - return True - @property def name(self): """Return the name of the BinarySensorEntity.""" diff --git a/homeassistant/components/maxcube/climate.py b/homeassistant/components/maxcube/climate.py index 69d9177da5dcfd..e222784ca57151 100644 --- a/homeassistant/components/maxcube/climate.py +++ b/homeassistant/components/maxcube/climate.py @@ -40,6 +40,11 @@ # On (valve fully open) ON_TEMPERATURE = 30.5 +# Lowest Value without turning off +MIN_TEMPERATURE = 5.0 +# Largest Value without fully opening +MAX_TEMPERATURE = 30.0 + SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_PRESET_MODE HASS_PRESET_TO_MAX_MODE = { @@ -100,13 +105,17 @@ def name(self): def min_temp(self): """Return the minimum temperature.""" device = self._cubehandle.cube.device_by_rf(self._rf_address) - return self.map_temperature_max_hass(device.min_temperature) + if device.min_temperature is None: + return MIN_TEMPERATURE + return device.min_temperature @property def max_temp(self): """Return the maximum temperature.""" device = self._cubehandle.cube.device_by_rf(self._rf_address) - return self.map_temperature_max_hass(device.max_temperature) + if device.max_temperature is None: + return MAX_TEMPERATURE + return device.max_temperature @property def temperature_unit(self): @@ -117,9 +126,7 @@ def temperature_unit(self): def current_temperature(self): """Return the current temperature.""" device = self._cubehandle.cube.device_by_rf(self._rf_address) - - # Map and return current temperature - return self.map_temperature_max_hass(device.actual_temperature) + return device.actual_temperature @property def hvac_mode(self): @@ -195,7 +202,13 @@ def hvac_action(self): def target_temperature(self): """Return the temperature we try to reach.""" device = self._cubehandle.cube.device_by_rf(self._rf_address) - return self.map_temperature_max_hass(device.target_temperature) + if ( + device.target_temperature is None + or device.target_temperature < self.min_temp + or device.target_temperature > self.max_temp + ): + return None + return device.target_temperature def set_temperature(self, **kwargs): """Set new target temperatures.""" @@ -273,21 +286,11 @@ def device_state_attributes(self): """Return the optional state attributes.""" cube = self._cubehandle.cube device = cube.device_by_rf(self._rf_address) - attributes = {} - if cube.is_thermostat(device): - attributes[ATTR_VALVE_POSITION] = device.valve_position - - return attributes + if not cube.is_thermostat(device): + return {} + return {ATTR_VALVE_POSITION: device.valve_position} def update(self): """Get latest data from MAX! Cube.""" self._cubehandle.update() - - @staticmethod - def map_temperature_max_hass(temperature): - """Map Temperature from MAX! to Home Assistant.""" - if temperature is None: - return 0.0 - - return temperature diff --git a/homeassistant/components/media_extractor/manifest.json b/homeassistant/components/media_extractor/manifest.json index 62d53b17c47980..1dbb642aee9d2b 100644 --- a/homeassistant/components/media_extractor/manifest.json +++ b/homeassistant/components/media_extractor/manifest.json @@ -2,7 +2,7 @@ "domain": "media_extractor", "name": "Media Extractor", "documentation": "https://www.home-assistant.io/integrations/media_extractor", - "requirements": ["youtube_dl==2020.07.28"], + "requirements": ["youtube_dl==2020.09.20"], "dependencies": ["media_player"], "codeowners": [], "quality_scale": "internal" diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py index 348bc521a5a8c7..1bf0e213a25246 100644 --- a/homeassistant/components/media_player/__init__.py +++ b/homeassistant/components/media_player/__init__.py @@ -27,6 +27,7 @@ HTTP_INTERNAL_SERVER_ERROR, HTTP_NOT_FOUND, HTTP_OK, + HTTP_UNAUTHORIZED, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, @@ -71,6 +72,7 @@ ATTR_MEDIA_DURATION, ATTR_MEDIA_ENQUEUE, ATTR_MEDIA_EPISODE, + ATTR_MEDIA_EXTRA, ATTR_MEDIA_PLAYLIST, ATTR_MEDIA_POSITION, ATTR_MEDIA_POSITION_UPDATED_AT, @@ -139,6 +141,7 @@ vol.Required(ATTR_MEDIA_CONTENT_TYPE): cv.string, vol.Required(ATTR_MEDIA_CONTENT_ID): cv.string, vol.Optional(ATTR_MEDIA_ENQUEUE): cv.boolean, + vol.Optional(ATTR_MEDIA_EXTRA, default={}): dict, } ATTR_TO_PROPERTY = [ @@ -880,7 +883,7 @@ async def get(self, request: web.Request, entity_id: str) -> web.Response: """Start a get request.""" player = self.component.get_entity(entity_id) if player is None: - status = HTTP_NOT_FOUND if request[KEY_AUTHENTICATED] else 401 + status = HTTP_NOT_FOUND if request[KEY_AUTHENTICATED] else HTTP_UNAUTHORIZED return web.Response(status=status) authenticated = ( @@ -889,7 +892,7 @@ async def get(self, request: web.Request, entity_id: str) -> web.Response: ) if not authenticated: - return web.Response(status=401) + return web.Response(status=HTTP_UNAUTHORIZED) data, content_type = await player.async_get_media_image() diff --git a/homeassistant/components/media_player/const.py b/homeassistant/components/media_player/const.py index 0035fc9f4d27ac..3db3100634133b 100644 --- a/homeassistant/components/media_player/const.py +++ b/homeassistant/components/media_player/const.py @@ -12,6 +12,7 @@ ATTR_MEDIA_CONTENT_TYPE = "media_content_type" ATTR_MEDIA_DURATION = "media_duration" ATTR_MEDIA_ENQUEUE = "enqueue" +ATTR_MEDIA_EXTRA = "extra" ATTR_MEDIA_EPISODE = "media_episode" ATTR_MEDIA_PLAYLIST = "media_playlist" ATTR_MEDIA_POSITION = "media_position" diff --git a/homeassistant/components/media_player/group.py b/homeassistant/components/media_player/group.py new file mode 100644 index 00000000000000..b612165fa19d88 --- /dev/null +++ b/homeassistant/components/media_player/group.py @@ -0,0 +1,17 @@ +"""Describe group states.""" + + +from homeassistant.components.group import GroupIntegrationRegistry +from homeassistant.const import STATE_OFF +from homeassistant.core import callback +from homeassistant.helpers.typing import HomeAssistantType + +from . import STATE_IDLE, STATE_PLAYING + + +@callback +def async_describe_on_off_states( + hass: HomeAssistantType, registry: GroupIntegrationRegistry +) -> None: + """Describe group on off states.""" + registry.on_off_states({STATE_PLAYING, STATE_IDLE}, STATE_OFF) diff --git a/homeassistant/components/media_player/reproduce_state.py b/homeassistant/components/media_player/reproduce_state.py index a90e4fffdc1fb0..64955d1913ba30 100644 --- a/homeassistant/components/media_player/reproduce_state.py +++ b/homeassistant/components/media_player/reproduce_state.py @@ -5,7 +5,6 @@ from homeassistant.const import ( SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, - SERVICE_MEDIA_SEEK, SERVICE_MEDIA_STOP, SERVICE_TURN_OFF, SERVICE_TURN_ON, @@ -25,7 +24,6 @@ ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_ENQUEUE, - ATTR_MEDIA_SEEK_POSITION, ATTR_MEDIA_VOLUME_LEVEL, ATTR_MEDIA_VOLUME_MUTED, ATTR_SOUND_MODE, @@ -58,16 +56,18 @@ async def call_service(service: str, keys: Iterable) -> None: DOMAIN, service, data, blocking=True, context=context ) - if state.state == STATE_ON: - await call_service(SERVICE_TURN_ON, []) - elif state.state == STATE_OFF: + if state.state == STATE_OFF: await call_service(SERVICE_TURN_OFF, []) - elif state.state == STATE_PLAYING: - await call_service(SERVICE_MEDIA_PLAY, []) - elif state.state == STATE_IDLE: - await call_service(SERVICE_MEDIA_STOP, []) - elif state.state == STATE_PAUSED: - await call_service(SERVICE_MEDIA_PAUSE, []) + # entities that are off have no other attributes to restore + return + + if state.state in [ + STATE_ON, + STATE_PLAYING, + STATE_IDLE, + STATE_PAUSED, + ]: + await call_service(SERVICE_TURN_ON, []) if ATTR_MEDIA_VOLUME_LEVEL in state.attributes: await call_service(SERVICE_VOLUME_SET, [ATTR_MEDIA_VOLUME_LEVEL]) @@ -75,15 +75,14 @@ async def call_service(service: str, keys: Iterable) -> None: if ATTR_MEDIA_VOLUME_MUTED in state.attributes: await call_service(SERVICE_VOLUME_MUTE, [ATTR_MEDIA_VOLUME_MUTED]) - if ATTR_MEDIA_SEEK_POSITION in state.attributes: - await call_service(SERVICE_MEDIA_SEEK, [ATTR_MEDIA_SEEK_POSITION]) - if ATTR_INPUT_SOURCE in state.attributes: await call_service(SERVICE_SELECT_SOURCE, [ATTR_INPUT_SOURCE]) if ATTR_SOUND_MODE in state.attributes: await call_service(SERVICE_SELECT_SOUND_MODE, [ATTR_SOUND_MODE]) + already_playing = False + if (ATTR_MEDIA_CONTENT_TYPE in state.attributes) and ( ATTR_MEDIA_CONTENT_ID in state.attributes ): @@ -91,6 +90,14 @@ async def call_service(service: str, keys: Iterable) -> None: SERVICE_PLAY_MEDIA, [ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_ENQUEUE], ) + already_playing = True + + if state.state == STATE_PLAYING and not already_playing: + await call_service(SERVICE_MEDIA_PLAY, []) + elif state.state == STATE_IDLE: + await call_service(SERVICE_MEDIA_STOP, []) + elif state.state == STATE_PAUSED: + await call_service(SERVICE_MEDIA_PAUSE, []) async def async_reproduce_states( diff --git a/homeassistant/components/media_player/translations/et.json b/homeassistant/components/media_player/translations/et.json index 2800870e9ccf38..4d71a30a8ac2f8 100644 --- a/homeassistant/components/media_player/translations/et.json +++ b/homeassistant/components/media_player/translations/et.json @@ -1,4 +1,13 @@ { + "device_automation": { + "condition_type": { + "is_idle": "{entity_name} on j\u00f5udeolekus", + "is_off": "{entity_name} on v\u00e4lja l\u00fclitatud", + "is_on": "{entity_name} on sisse l\u00fclitatud", + "is_paused": "{entity_name} on peatatud", + "is_playing": "{entity_name} m\u00e4ngib" + } + }, "state": { "_": { "idle": "Ootel", diff --git a/homeassistant/components/media_source/const.py b/homeassistant/components/media_source/const.py index 68a8244c3cedd1..739af47e653bbf 100644 --- a/homeassistant/components/media_source/const.py +++ b/homeassistant/components/media_source/const.py @@ -15,4 +15,6 @@ "image": MEDIA_CLASS_IMAGE, } URI_SCHEME = "media-source://" -URI_SCHEME_REGEX = re.compile(r"^media-source://(?P[^/]+)?(?P.+)?") +URI_SCHEME_REGEX = re.compile( + r"^media-source:\/\/(?:(?P(?!.+__)(?!_)[\da-z_]+(?(?!\/).+))?)?$" +) diff --git a/homeassistant/components/media_source/local_source.py b/homeassistant/components/media_source/local_source.py index a558de775f8aed..6c60da562e08de 100644 --- a/homeassistant/components/media_source/local_source.py +++ b/homeassistant/components/media_source/local_source.py @@ -21,26 +21,7 @@ def async_setup(hass: HomeAssistant): """Set up local media source.""" source = LocalSource(hass) hass.data[DOMAIN][DOMAIN] = source - hass.http.register_view(LocalMediaView(hass)) - - -@callback -def async_parse_identifier(item: MediaSourceItem) -> Tuple[str, str]: - """Parse identifier.""" - if not item.identifier: - source_dir_id = "media" - location = "" - - else: - source_dir_id, location = item.identifier.lstrip("/").split("/", 1) - - if source_dir_id != "media": - raise Unresolvable("Unknown source directory.") - - if location != sanitize_path(location): - raise Unresolvable("Invalid path.") - - return source_dir_id, location + hass.http.register_view(LocalMediaView(hass, source)) class LocalSource(MediaSource): @@ -56,22 +37,41 @@ def __init__(self, hass: HomeAssistant): @callback def async_full_path(self, source_dir_id, location) -> Path: """Return full path.""" - return self.hass.config.path("media", location) + return Path(self.hass.config.media_dirs[source_dir_id], location) + + @callback + def async_parse_identifier(self, item: MediaSourceItem) -> Tuple[str, str]: + """Parse identifier.""" + if not item.identifier: + # Empty source_dir_id and location + return "", "" + + source_dir_id, location = item.identifier.split("/", 1) + if source_dir_id not in self.hass.config.media_dirs: + raise Unresolvable("Unknown source directory.") + + if location != sanitize_path(location): + raise Unresolvable("Invalid path.") + + return source_dir_id, location async def async_resolve_media(self, item: MediaSourceItem) -> str: """Resolve media to a url.""" - source_dir_id, location = async_parse_identifier(item) + source_dir_id, location = self.async_parse_identifier(item) + if source_dir_id == "" or source_dir_id not in self.hass.config.media_dirs: + raise Unresolvable("Unknown source directory.") + mime_type, _ = mimetypes.guess_type( - self.async_full_path(source_dir_id, location) + str(self.async_full_path(source_dir_id, location)) ) - return PlayMedia(item.identifier, mime_type) + return PlayMedia(f"/media/{item.identifier}", mime_type) async def async_browse_media( self, item: MediaSourceItem, media_types: Tuple[str] = MEDIA_MIME_TYPES ) -> BrowseMediaSource: """Return media.""" try: - source_dir_id, location = async_parse_identifier(item) + source_dir_id, location = self.async_parse_identifier(item) except Unresolvable as err: raise BrowseError(str(err)) from err @@ -79,9 +79,37 @@ async def async_browse_media( self._browse_media, source_dir_id, location ) - def _browse_media(self, source_dir_id, location): + def _browse_media(self, source_dir_id: str, location: Path): """Browse media.""" - full_path = Path(self.hass.config.path("media", location)) + + # If only one media dir is configured, use that as the local media root + if source_dir_id == "" and len(self.hass.config.media_dirs) == 1: + source_dir_id = list(self.hass.config.media_dirs)[0] + + # Multiple folder, root is requested + if source_dir_id == "": + if location: + raise BrowseError("Folder not found.") + + base = BrowseMediaSource( + domain=DOMAIN, + identifier="", + media_class=MEDIA_CLASS_DIRECTORY, + media_content_type=None, + title=self.name, + can_play=False, + can_expand=True, + children_media_class=MEDIA_CLASS_DIRECTORY, + ) + + base.children = [ + self._browse_media(source_dir_id, "") + for source_dir_id in self.hass.config.media_dirs + ] + + return base + + full_path = Path(self.hass.config.media_dirs[source_dir_id], location) if not full_path.exists(): if location == "": @@ -118,7 +146,7 @@ def _build_item_response(self, source_dir_id: str, path: Path, is_child=False): media = BrowseMediaSource( domain=DOMAIN, - identifier=f"{source_dir_id}/{path.relative_to(self.hass.config.path('media'))}", + identifier=f"{source_dir_id}/{path.relative_to(self.hass.config.media_dirs[source_dir_id])}", media_class=media_class, media_content_type=mime_type or "", title=title, @@ -149,19 +177,25 @@ class LocalMediaView(HomeAssistantView): Returns media files in config/media. """ - url = "/media/{location:.*}" + url = "/media/{source_dir_id}/{location:.*}" name = "media" - def __init__(self, hass: HomeAssistant): + def __init__(self, hass: HomeAssistant, source: LocalSource): """Initialize the media view.""" self.hass = hass + self.source = source - async def get(self, request: web.Request, location: str) -> web.FileResponse: + async def get( + self, request: web.Request, source_dir_id: str, location: str + ) -> web.FileResponse: """Start a GET request.""" if location != sanitize_path(location): - return web.HTTPNotFound() + raise web.HTTPNotFound() + + if source_dir_id not in self.hass.config.media_dirs: + raise web.HTTPNotFound() - media_path = Path(self.hass.config.path("media", location)) + media_path = self.source.async_full_path(source_dir_id, location) # Check that the file exists if not media_path.is_file(): diff --git a/homeassistant/components/melcloud/config_flow.py b/homeassistant/components/melcloud/config_flow.py index ed6fc31c41482d..8813883c151462 100644 --- a/homeassistant/components/melcloud/config_flow.py +++ b/homeassistant/components/melcloud/config_flow.py @@ -9,7 +9,13 @@ import voluptuous as vol from homeassistant import config_entries -from homeassistant.const import CONF_PASSWORD, CONF_TOKEN, CONF_USERNAME, HTTP_FORBIDDEN +from homeassistant.const import ( + CONF_PASSWORD, + CONF_TOKEN, + CONF_USERNAME, + HTTP_FORBIDDEN, + HTTP_UNAUTHORIZED, +) from .const import DOMAIN # pylint: disable=unused-import @@ -57,7 +63,7 @@ async def _create_client( self.hass.helpers.aiohttp_client.async_get_clientsession(), ) except ClientResponseError as err: - if err.status == 401 or err.status == HTTP_FORBIDDEN: + if err.status == HTTP_UNAUTHORIZED or err.status == HTTP_FORBIDDEN: return self.async_abort(reason="invalid_auth") return self.async_abort(reason="cannot_connect") except (asyncio.TimeoutError, ClientError): diff --git a/homeassistant/components/melcloud/strings.json b/homeassistant/components/melcloud/strings.json index c4161a87ff80f5..a1bce80d7ad425 100644 --- a/homeassistant/components/melcloud/strings.json +++ b/homeassistant/components/melcloud/strings.json @@ -11,12 +11,12 @@ } }, "error": { - "cannot_connect": "Failed to connect, please try again", - "invalid_auth": "Invalid authentication", - "unknown": "Unexpected 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": "MELCloud integration already configured for this email. Access token has been refreshed." } } -} \ No newline at end of file +} diff --git a/homeassistant/components/melcloud/translations/ca.json b/homeassistant/components/melcloud/translations/ca.json index 2da4f4b57f34c9..b31698d28ce9df 100644 --- a/homeassistant/components/melcloud/translations/ca.json +++ b/homeassistant/components/melcloud/translations/ca.json @@ -4,7 +4,7 @@ "already_configured": "La integraci\u00f3 MELCloud ja est\u00e0 configurada amb aquest correu electr\u00f2nic. El token d'acc\u00e9s s'ha actualitzat." }, "error": { - "cannot_connect": "No s'ha pogut connectar, torna-ho a provar", + "cannot_connect": "Ha fallat la connexi\u00f3", "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "unknown": "Error inesperat" }, diff --git a/homeassistant/components/melcloud/translations/en.json b/homeassistant/components/melcloud/translations/en.json index 6e701fecf5527e..e21fe15f304421 100644 --- a/homeassistant/components/melcloud/translations/en.json +++ b/homeassistant/components/melcloud/translations/en.json @@ -4,7 +4,7 @@ "already_configured": "MELCloud integration already configured for this email. Access token has been refreshed." }, "error": { - "cannot_connect": "Failed to connect, please try again", + "cannot_connect": "Failed to connect", "invalid_auth": "Invalid authentication", "unknown": "Unexpected error" }, diff --git a/homeassistant/components/melcloud/translations/et.json b/homeassistant/components/melcloud/translations/et.json new file mode 100644 index 00000000000000..0d70cd06fcaa70 --- /dev/null +++ b/homeassistant/components/melcloud/translations/et.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00dchenduse loomine nurjus. Proovi uuesti", + "unknown": "Ootamatu t\u00f5rge" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/melcloud/translations/it.json b/homeassistant/components/melcloud/translations/it.json index 2a3125619324d0..15d9f0d06fc26b 100644 --- a/homeassistant/components/melcloud/translations/it.json +++ b/homeassistant/components/melcloud/translations/it.json @@ -4,7 +4,7 @@ "already_configured": "Integrazione MELCloud gi\u00e0 configurata per questa e-mail. Il token di accesso \u00e8 stato aggiornato." }, "error": { - "cannot_connect": "Impossibile connettersi, si prega di riprovare.", + "cannot_connect": "Impossibile connettersi", "invalid_auth": "Autenticazione non valida", "unknown": "Errore imprevisto" }, diff --git a/homeassistant/components/melcloud/translations/no.json b/homeassistant/components/melcloud/translations/no.json index e96fdb171e7d25..6608b8c5c712c9 100644 --- a/homeassistant/components/melcloud/translations/no.json +++ b/homeassistant/components/melcloud/translations/no.json @@ -4,7 +4,7 @@ "already_configured": "MELCloud integrasjon er allerede konfigurert p\u00e5 denne e-posten. Access token har blitt oppdatert." }, "error": { - "cannot_connect": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen", + "cannot_connect": "Tilkobling mislyktes.", "invalid_auth": "Ugyldig godkjenning", "unknown": "Uventet feil" }, diff --git a/homeassistant/components/melcloud/translations/pl.json b/homeassistant/components/melcloud/translations/pl.json index 44467601826546..cd0c961089e9e9 100644 --- a/homeassistant/components/melcloud/translations/pl.json +++ b/homeassistant/components/melcloud/translations/pl.json @@ -5,8 +5,8 @@ }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "invalid_auth": "Niepoprawne uwierzytelnienie.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "user": { diff --git a/homeassistant/components/melcloud/translations/ru.json b/homeassistant/components/melcloud/translations/ru.json index 7fd6d31a7539dc..e904ea4e8b7206 100644 --- a/homeassistant/components/melcloud/translations/ru.json +++ b/homeassistant/components/melcloud/translations/ru.json @@ -4,7 +4,7 @@ "already_configured": "\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f MELCloud \u0443\u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u0430 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0430\u0434\u0440\u0435\u0441\u0430 \u044d\u043b\u0435\u043a\u0442\u0440\u043e\u043d\u043d\u043e\u0439 \u043f\u043e\u0447\u0442\u044b. \u0422\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d." }, "error": { - "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", + "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "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/melcloud/translations/zh-Hant.json b/homeassistant/components/melcloud/translations/zh-Hant.json index 1e6e1d880c72f9..9947b5ac990869 100644 --- a/homeassistant/components/melcloud/translations/zh-Hant.json +++ b/homeassistant/components/melcloud/translations/zh-Hant.json @@ -4,7 +4,7 @@ "already_configured": "\u5df2\u4f7f\u7528\u6b64\u90f5\u4ef6\u8a2d\u5b9a MELCloud \u6574\u5408\u3002\u5b58\u53d6\u5bc6\u9470\u5df2\u66f4\u65b0\u3002" }, "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21\u3002", + "cannot_connect": "\u9023\u7dda\u5931\u6557", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, diff --git a/homeassistant/components/met/config_flow.py b/homeassistant/components/met/config_flow.py index b9a992bb823e6f..6b3d6735f46604 100644 --- a/homeassistant/components/met/config_flow.py +++ b/homeassistant/components/met/config_flow.py @@ -47,7 +47,7 @@ async def async_step_user(self, user_input=None): return self.async_create_entry( title=user_input[CONF_NAME], data=user_input ) - self._errors[CONF_NAME] = "name_exists" + self._errors[CONF_NAME] = "already_configured" return await self._show_config_form( name=HOME_LOCATION_NAME, diff --git a/homeassistant/components/met/strings.json b/homeassistant/components/met/strings.json index 814df01b49ec00..c3e28d98efffab 100644 --- a/homeassistant/components/met/strings.json +++ b/homeassistant/components/met/strings.json @@ -5,13 +5,13 @@ "title": "Location", "description": "Meteorologisk institutt", "data": { - "name": "Name", - "latitude": "Latitude", - "longitude": "Longitude", + "name": "[%key:common::config_flow::data::name%]", + "latitude": "[%key:common::config_flow::data::latitude%]", + "longitude": "[%key:common::config_flow::data::longitude%]", "elevation": "Elevation" } } }, - "error": { "name_exists": "Location already exists" } + "error": { "already_configured": "[%key:common::config_flow::abort::already_configured_service%]" } } } diff --git a/homeassistant/components/met/translations/ca.json b/homeassistant/components/met/translations/ca.json index 242232663f0f04..d33e905e721dfa 100644 --- a/homeassistant/components/met/translations/ca.json +++ b/homeassistant/components/met/translations/ca.json @@ -1,6 +1,7 @@ { "config": { "error": { + "already_configured": "El servei ja est\u00e0 configurat", "name_exists": "El nom ja existeix" }, "step": { diff --git a/homeassistant/components/met/translations/en.json b/homeassistant/components/met/translations/en.json index 8c57e4226a054a..012557d917a978 100644 --- a/homeassistant/components/met/translations/en.json +++ b/homeassistant/components/met/translations/en.json @@ -1,6 +1,7 @@ { "config": { "error": { + "already_configured": "Service is already configured", "name_exists": "Location already exists" }, "step": { diff --git a/homeassistant/components/met/translations/es.json b/homeassistant/components/met/translations/es.json index e9cfbce5291832..9306bd03022910 100644 --- a/homeassistant/components/met/translations/es.json +++ b/homeassistant/components/met/translations/es.json @@ -1,6 +1,7 @@ { "config": { "error": { + "already_configured": "El servicio ya est\u00e1 configurado", "name_exists": "La ubicaci\u00f3n ya existe" }, "step": { diff --git a/homeassistant/components/met/translations/et.json b/homeassistant/components/met/translations/et.json new file mode 100644 index 00000000000000..df6a380a5e76fb --- /dev/null +++ b/homeassistant/components/met/translations/et.json @@ -0,0 +1,20 @@ +{ + "config": { + "error": { + "already_configured": "Teenus on juba h\u00e4\u00e4lestatud", + "name_exists": "See asukoht on juba m\u00e4\u00e4ratud" + }, + "step": { + "user": { + "data": { + "elevation": "K\u00f5rgus merepinnast", + "latitude": "Laiuskraad", + "longitude": "Pikkuskraad", + "name": "Nimi" + }, + "description": "Norra ilmateenistus", + "title": "Asukoht" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/met/translations/fr.json b/homeassistant/components/met/translations/fr.json index 9f43be5d93b0b6..fc712cb69128c2 100644 --- a/homeassistant/components/met/translations/fr.json +++ b/homeassistant/components/met/translations/fr.json @@ -1,6 +1,7 @@ { "config": { "error": { + "already_configured": "Le service est d\u00e9j\u00e0 configur\u00e9", "name_exists": "Emplacement d\u00e9j\u00e0 existant" }, "step": { diff --git a/homeassistant/components/met/translations/it.json b/homeassistant/components/met/translations/it.json index 18433ab1697043..16d68553ab4830 100644 --- a/homeassistant/components/met/translations/it.json +++ b/homeassistant/components/met/translations/it.json @@ -1,6 +1,7 @@ { "config": { "error": { + "already_configured": "Il servizio \u00e8 gi\u00e0 configurato", "name_exists": "La posizione esiste gi\u00e0" }, "step": { @@ -8,7 +9,7 @@ "data": { "elevation": "Altitudine", "latitude": "Latitudine", - "longitude": "Longitudine", + "longitude": "Logitudine", "name": "Nome" }, "description": "Meteorologisk institutt", diff --git a/homeassistant/components/met/translations/no.json b/homeassistant/components/met/translations/no.json index 90489288b62fe3..937f626e6c2ebf 100644 --- a/homeassistant/components/met/translations/no.json +++ b/homeassistant/components/met/translations/no.json @@ -1,6 +1,7 @@ { "config": { "error": { + "already_configured": "Tjenesten er allerede konfigurert", "name_exists": "Lokasjonen finnes allerede" }, "step": { diff --git a/homeassistant/components/met/translations/ru.json b/homeassistant/components/met/translations/ru.json index 85333490f3ccda..149e085057b8bb 100644 --- a/homeassistant/components/met/translations/ru.json +++ b/homeassistant/components/met/translations/ru.json @@ -1,6 +1,7 @@ { "config": { "error": { + "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.", "name_exists": "\u041c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0443\u0436\u0435 \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u043e." }, "step": { diff --git a/homeassistant/components/met/translations/zh-Hant.json b/homeassistant/components/met/translations/zh-Hant.json index 0128f0ad448931..bb5d2df412a9b5 100644 --- a/homeassistant/components/met/translations/zh-Hant.json +++ b/homeassistant/components/met/translations/zh-Hant.json @@ -1,6 +1,7 @@ { "config": { "error": { + "already_configured": "\u670d\u52d9\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "name_exists": "\u8a72\u5ea7\u6a19\u5df2\u5b58\u5728" }, "step": { diff --git a/homeassistant/components/met/weather.py b/homeassistant/components/met/weather.py index 3355c497aab4af..e4c64f9aedab21 100644 --- a/homeassistant/components/met/weather.py +++ b/homeassistant/components/met/weather.py @@ -5,6 +5,8 @@ from homeassistant.components.weather import ( ATTR_FORECAST_CONDITION, + ATTR_FORECAST_TEMP, + ATTR_FORECAST_TIME, ATTR_WEATHER_HUMIDITY, ATTR_WEATHER_PRESSURE, ATTR_WEATHER_TEMPERATURE, @@ -209,13 +211,17 @@ def forecast(self): met_forecast = self.coordinator.data.hourly_forecast else: met_forecast = self.coordinator.data.daily_forecast + required_keys = {ATTR_FORECAST_TEMP, ATTR_FORECAST_TIME} ha_forecast = [] for met_item in met_forecast: + if not set(met_item).issuperset(required_keys): + continue ha_item = { k: met_item[v] for k, v in FORECAST_MAP.items() if met_item.get(v) } - ha_item[ATTR_FORECAST_CONDITION] = format_condition( - ha_item[ATTR_FORECAST_CONDITION] - ) + if ha_item.get(ATTR_FORECAST_CONDITION): + ha_item[ATTR_FORECAST_CONDITION] = format_condition( + ha_item[ATTR_FORECAST_CONDITION] + ) ha_forecast.append(ha_item) return ha_forecast diff --git a/homeassistant/components/meteo_france/const.py b/homeassistant/components/meteo_france/const.py index 8b4d3a33501085..59524ed1a80b54 100644 --- a/homeassistant/components/meteo_france/const.py +++ b/homeassistant/components/meteo_france/const.py @@ -4,6 +4,7 @@ DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TIMESTAMP, + LENGTH_MILLIMETERS, PERCENTAGE, PRESSURE_HPA, SPEED_KILOMETERS_PER_HOUR, @@ -108,7 +109,7 @@ }, "precipitation": { ENTITY_NAME: "Daily precipitation", - ENTITY_UNIT: "mm", + ENTITY_UNIT: LENGTH_MILLIMETERS, ENTITY_ICON: "mdi:cup-water", ENTITY_DEVICE_CLASS: None, ENTITY_ENABLE: True, diff --git a/homeassistant/components/meteo_france/strings.json b/homeassistant/components/meteo_france/strings.json index 611d1ca054cb93..4deb17d01e686b 100644 --- a/homeassistant/components/meteo_france/strings.json +++ b/homeassistant/components/meteo_france/strings.json @@ -21,7 +21,7 @@ }, "abort": { "already_configured": "City already configured", - "unknown": "Unknown error: please retry later" + "unknown": "[%key:common::config_flow::error::unknown%]" } }, "options": { @@ -33,4 +33,4 @@ } } } -} \ No newline at end of file +} diff --git a/homeassistant/components/meteo_france/translations/ca.json b/homeassistant/components/meteo_france/translations/ca.json index 81f2e6f2d204ac..f38111707407c0 100644 --- a/homeassistant/components/meteo_france/translations/ca.json +++ b/homeassistant/components/meteo_france/translations/ca.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Ciutat ja configurada", - "unknown": "Error desconegut: torna-ho a provar m\u00e9s tard" + "unknown": "Error inesperat" }, "error": { "empty": "No s'ha trobat cap resultat en la cerca de la ciutat: comprova el camp ciutat" diff --git a/homeassistant/components/meteo_france/translations/en.json b/homeassistant/components/meteo_france/translations/en.json index 979f705cc5bb8e..02a08504ec4f48 100644 --- a/homeassistant/components/meteo_france/translations/en.json +++ b/homeassistant/components/meteo_france/translations/en.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "City already configured", - "unknown": "Unknown error: please retry later" + "unknown": "Unexpected error" }, "error": { "empty": "No result in city search: please check the city field" diff --git a/homeassistant/components/meteo_france/translations/it.json b/homeassistant/components/meteo_france/translations/it.json index df5cf4a637524f..d9ffd866b1cc9e 100644 --- a/homeassistant/components/meteo_france/translations/it.json +++ b/homeassistant/components/meteo_france/translations/it.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Citt\u00e0 gi\u00e0 configurata", - "unknown": "Errore sconosciuto: riprovare pi\u00f9 tardi" + "unknown": "Errore imprevisto" }, "error": { "empty": "Nessun risultato nella ricerca della citt\u00e0: si prega di controllare il campo citt\u00e0" diff --git a/homeassistant/components/meteo_france/translations/ko.json b/homeassistant/components/meteo_france/translations/ko.json index 166ddaa68ab992..4b8dc3204dd57a 100644 --- a/homeassistant/components/meteo_france/translations/ko.json +++ b/homeassistant/components/meteo_france/translations/ko.json @@ -4,7 +4,13 @@ "already_configured": "\ub3c4\uc2dc\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "unknown": "\uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uc785\ub2c8\ub2e4. \ub098\uc911\uc5d0 \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694" }, + "error": { + "empty": "\ub3c4\uc2dc \uac80\uc0c9 \uacb0\uacfc \uc5c6\uc74c: \ub3c4\uc2dc \ud544\ub4dc\ub97c \ud655\uc778\ud558\uc2ed\uc2dc\uc624." + }, "step": { + "cities": { + "title": "\ud504\ub791\uc2a4 \uae30\uc0c1\uccad (M\u00e9t\u00e9o-France)" + }, "user": { "data": { "city": "\ub3c4\uc2dc" @@ -13,5 +19,14 @@ "title": "\ud504\ub791\uc2a4 \uae30\uc0c1\uccad (M\u00e9t\u00e9o-France)" } } + }, + "options": { + "step": { + "init": { + "data": { + "mode": "\uc608\uce21 \ubaa8\ub4dc" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/meteo_france/translations/no.json b/homeassistant/components/meteo_france/translations/no.json index 91eea1fcec7a32..f0aadd799d69d8 100644 --- a/homeassistant/components/meteo_france/translations/no.json +++ b/homeassistant/components/meteo_france/translations/no.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Byen er allerede konfigurert", - "unknown": "Ukjent feil: pr\u00f8v p\u00e5 nytt senere" + "unknown": "Uventet feil" }, "error": { "empty": "Ingen resultater i bys\u00f8k: vennligst sjekk byfeltet" diff --git a/homeassistant/components/meteo_france/translations/pl.json b/homeassistant/components/meteo_france/translations/pl.json index 46f3c1fdc27558..ea9e0b1312e8c4 100644 --- a/homeassistant/components/meteo_france/translations/pl.json +++ b/homeassistant/components/meteo_france/translations/pl.json @@ -4,7 +4,17 @@ "already_configured": "Miasto jest ju\u017c skonfigurowane.", "unknown": "Nieznany b\u0142\u0105d: spr\u00f3buj ponownie p\u00f3\u017aniej." }, + "error": { + "empty": "Brak wynik\u00f3w: sprawd\u017a nazw\u0119 w polu miasta" + }, "step": { + "cities": { + "data": { + "city": "Miasto" + }, + "description": "Wybierz swoje miasto z listy", + "title": "M\u00e9t\u00e9o-France" + }, "user": { "data": { "city": "Miasto" @@ -13,5 +23,14 @@ "title": "M\u00e9t\u00e9o-France" } } + }, + "options": { + "step": { + "init": { + "data": { + "mode": "Tryb prognozy" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/meteo_france/translations/ru.json b/homeassistant/components/meteo_france/translations/ru.json index ba0bf1df3c2534..7b0bb4e88a2c6b 100644 --- a/homeassistant/components/meteo_france/translations/ru.json +++ b/homeassistant/components/meteo_france/translations/ru.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0441 \u0442\u0430\u043a\u0438\u043c\u0438 \u0436\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", - "unknown": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430: \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." }, "error": { "empty": "\u041d\u0435\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432 \u043f\u043e\u0438\u0441\u043a\u0430. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043f\u043e\u043b\u0435 \"\u0413\u043e\u0440\u043e\u0434\"." diff --git a/homeassistant/components/meteo_france/translations/zh-Hant.json b/homeassistant/components/meteo_france/translations/zh-Hant.json index 0179f5ad7d1856..ac2b11dbab1358 100644 --- a/homeassistant/components/meteo_france/translations/zh-Hant.json +++ b/homeassistant/components/meteo_france/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\u57ce\u5e02\u5df2\u8a2d\u5b9a\u5b8c\u6210", - "unknown": "\u672a\u77e5\u932f\u8aa4\uff1a\u8acb\u7a0d\u5f8c\u518d\u8a66" + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "error": { "empty": "\u627e\u4e0d\u5230\u76f8\u7b26\u7684\u57ce\u5e02\uff1a\u8acb\u78ba\u8a8d\u57ce\u5e02\u6b04\u4f4d" diff --git a/homeassistant/components/meteoalarm/binary_sensor.py b/homeassistant/components/meteoalarm/binary_sensor.py index b481b417b9ea50..6b13d03ebbad44 100644 --- a/homeassistant/components/meteoalarm/binary_sensor.py +++ b/homeassistant/components/meteoalarm/binary_sensor.py @@ -5,7 +5,11 @@ from meteoalertapi import Meteoalert import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_SAFETY, + PLATFORM_SCHEMA, + BinarySensorEntity, +) from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME import homeassistant.helpers.config_validation as cv @@ -17,7 +21,6 @@ CONF_LANGUAGE = "language" CONF_PROVINCE = "province" -DEFAULT_DEVICE_CLASS = "safety" DEFAULT_NAME = "meteoalarm" SCAN_INTERVAL = timedelta(minutes=30) @@ -78,7 +81,7 @@ def device_state_attributes(self): @property def device_class(self): """Return the device class of this binary sensor.""" - return DEFAULT_DEVICE_CLASS + return DEVICE_CLASS_SAFETY def update(self): """Update device state.""" diff --git a/homeassistant/components/metoffice/strings.json b/homeassistant/components/metoffice/strings.json index 74d8b16542a048..5a1c59bcfb72fb 100644 --- a/homeassistant/components/metoffice/strings.json +++ b/homeassistant/components/metoffice/strings.json @@ -5,9 +5,9 @@ "description": "The latitude and longitude will be used to find the closest weather station.", "title": "Connect to the UK Met Office", "data": { - "api_key": "Met Office DataPoint API key", - "latitude": "Latitude", - "longitude": "Longitude" + "api_key": "[%key:common::config_flow::data::api_key%]", + "latitude": "[%key:common::config_flow::data::latitude%]", + "longitude": "[%key:common::config_flow::data::longitude%]" } } }, @@ -16,7 +16,7 @@ "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/metoffice/translations/de.json b/homeassistant/components/metoffice/translations/de.json new file mode 100644 index 00000000000000..55896dc4901fce --- /dev/null +++ b/homeassistant/components/metoffice/translations/de.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "latitude": "Breitengrad", + "longitude": "L\u00e4ngengrad" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/metoffice/translations/et.json b/homeassistant/components/metoffice/translations/et.json new file mode 100644 index 00000000000000..c6ad082c40e2df --- /dev/null +++ b/homeassistant/components/metoffice/translations/et.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "user": { + "data": { + "latitude": "Laiuskraad", + "longitude": "Pikkuskraad" + }, + "description": "Laius- ja pikkuskraadi kasutatakse l\u00e4hima ilmajaama leidmiseks." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/metoffice/translations/hu.json b/homeassistant/components/metoffice/translations/hu.json new file mode 100644 index 00000000000000..3b2d79a34a77e2 --- /dev/null +++ b/homeassistant/components/metoffice/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/metoffice/translations/pl.json b/homeassistant/components/metoffice/translations/pl.json index 7167faf5494f34..6b129f2965c779 100644 --- a/homeassistant/components/metoffice/translations/pl.json +++ b/homeassistant/components/metoffice/translations/pl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "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." + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "user": { diff --git a/homeassistant/components/mfi/switch.py b/homeassistant/components/mfi/switch.py index d2ba2371303bba..21963140547f14 100644 --- a/homeassistant/components/mfi/switch.py +++ b/homeassistant/components/mfi/switch.py @@ -69,11 +69,6 @@ def __init__(self, port): self._port = port self._target_state = None - @property - def should_poll(self): - """Return the polling state.""" - return True - @property def unique_id(self): """Return the unique ID of the device.""" @@ -114,7 +109,7 @@ def current_power_w(self): @property def device_state_attributes(self): """Return the state attributes for the device.""" - attr = {} - attr["volts"] = round(self._port.data.get("v_rms", 0), 1) - attr["amps"] = round(self._port.data.get("i_rms", 0), 1) - return attr + return { + "volts": round(self._port.data.get("v_rms", 0), 1), + "amps": round(self._port.data.get("i_rms", 0), 1), + } diff --git a/homeassistant/components/microsoft_face/__init__.py b/homeassistant/components/microsoft_face/__init__.py index 208cecf6d3b945..69a738724c3225 100644 --- a/homeassistant/components/microsoft_face/__init__.py +++ b/homeassistant/components/microsoft_face/__init__.py @@ -8,7 +8,7 @@ import async_timeout import voluptuous as vol -from homeassistant.const import ATTR_NAME, CONF_API_KEY, CONF_TIMEOUT +from homeassistant.const import ATTR_NAME, CONF_API_KEY, CONF_TIMEOUT, CONTENT_TYPE_JSON from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv @@ -290,7 +290,7 @@ async def call_api(self, method, function, data=None, binary=False, params=None) headers[CONTENT_TYPE] = "application/octet-stream" payload = data else: - headers[CONTENT_TYPE] = "application/json" + headers[CONTENT_TYPE] = CONTENT_TYPE_JSON if data is not None: payload = json.dumps(data).encode() else: diff --git a/homeassistant/components/miflora/sensor.py b/homeassistant/components/miflora/sensor.py index 6206c67dc03504..5c5257d4181c28 100644 --- a/homeassistant/components/miflora/sensor.py +++ b/homeassistant/components/miflora/sensor.py @@ -17,6 +17,7 @@ CONF_NAME, CONF_SCAN_INTERVAL, EVENT_HOMEASSISTANT_START, + LIGHT_LUX, PERCENTAGE, TEMP_CELSIUS, TEMP_FAHRENHEIT, @@ -53,7 +54,7 @@ # Sensor types are defined like: Name, units, icon SENSOR_TYPES = { "temperature": ["Temperature", TEMP_CELSIUS, "mdi:thermometer"], - "light": ["Light intensity", "lx", "mdi:white-balance-sunny"], + "light": ["Light intensity", LIGHT_LUX, "mdi:white-balance-sunny"], "moisture": ["Moisture", PERCENTAGE, "mdi:water-percent"], "conductivity": ["Conductivity", CONDUCTIVITY, "mdi:flash-circle"], "battery": ["Battery", PERCENTAGE, "mdi:battery-charging"], @@ -183,8 +184,7 @@ def available(self): @property def device_state_attributes(self): """Return the state attributes of the device.""" - attr = {ATTR_LAST_SUCCESSFUL_UPDATE: self.last_successful_update} - return attr + return {ATTR_LAST_SUCCESSFUL_UPDATE: self.last_successful_update} @property def unit_of_measurement(self): diff --git a/homeassistant/components/mikrotik/config_flow.py b/homeassistant/components/mikrotik/config_flow.py index c1a41abf0d07b8..91e0f366b4dc17 100644 --- a/homeassistant/components/mikrotik/config_flow.py +++ b/homeassistant/components/mikrotik/config_flow.py @@ -53,8 +53,8 @@ async def async_step_user(self, user_input=None): except CannotConnect: errors["base"] = "cannot_connect" except LoginError: - errors[CONF_USERNAME] = "wrong_credentials" - errors[CONF_PASSWORD] = "wrong_credentials" + errors[CONF_USERNAME] = "invalid_auth" + errors[CONF_PASSWORD] = "invalid_auth" if not errors: return self.async_create_entry( diff --git a/homeassistant/components/mikrotik/strings.json b/homeassistant/components/mikrotik/strings.json index 9fa665add80fc3..6d421cb183827f 100644 --- a/homeassistant/components/mikrotik/strings.json +++ b/homeassistant/components/mikrotik/strings.json @@ -4,7 +4,7 @@ "user": { "title": "Set up Mikrotik Router", "data": { - "name": "Name", + "name": "[%key:common::config_flow::data::name%]", "host": "[%key:common::config_flow::data::host%]", "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]", @@ -15,11 +15,11 @@ }, "error": { "name_exists": "Name exists", - "cannot_connect": "Connection Unsuccessful", - "wrong_credentials": "Wrong Credentials" + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" }, "abort": { - "already_configured": "Mikrotik is already configured" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } }, "options": { @@ -33,4 +33,4 @@ } } } -} \ No newline at end of file +} diff --git a/homeassistant/components/mikrotik/translations/ca.json b/homeassistant/components/mikrotik/translations/ca.json index 10589dac474fd4..8556a3c7765f2e 100644 --- a/homeassistant/components/mikrotik/translations/ca.json +++ b/homeassistant/components/mikrotik/translations/ca.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Mikrotik ja est\u00e0 configurat" + "already_configured": "El dispositiu ja est\u00e0 configurat", + "already_configured_device": "El dispositiu ja est\u00e0 configurat" }, "error": { - "cannot_connect": "La connexi\u00f3 no ha tingut \u00e8xit", + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "name_exists": "El nom existeix", "wrong_credentials": "Credencials incorrectes" }, diff --git a/homeassistant/components/mikrotik/translations/en.json b/homeassistant/components/mikrotik/translations/en.json index 692d1247fcbf25..cad279a7afadc1 100644 --- a/homeassistant/components/mikrotik/translations/en.json +++ b/homeassistant/components/mikrotik/translations/en.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Mikrotik is already configured" + "already_configured": "Device is already configured", + "already_configured_device": "Device is already configured" }, "error": { - "cannot_connect": "Connection Unsuccessful", + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", "name_exists": "Name exists", "wrong_credentials": "Wrong Credentials" }, diff --git a/homeassistant/components/mikrotik/translations/es.json b/homeassistant/components/mikrotik/translations/es.json index b575db70f1156f..32897593c98da1 100644 --- a/homeassistant/components/mikrotik/translations/es.json +++ b/homeassistant/components/mikrotik/translations/es.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Mikrotik ya est\u00e1 configurado" + "already_configured": "Mikrotik ya est\u00e1 configurado", + "already_configured_device": "El dispositivo ya est\u00e1 configurado" }, "error": { "cannot_connect": "Conexi\u00f3n fallida", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", "name_exists": "El nombre ya existe", "wrong_credentials": "Credenciales incorrectas" }, diff --git a/homeassistant/components/mikrotik/translations/et.json b/homeassistant/components/mikrotik/translations/et.json new file mode 100644 index 00000000000000..51c20225df5f6c --- /dev/null +++ b/homeassistant/components/mikrotik/translations/et.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "already_configured_device": "Seade on juba h\u00e4\u00e4lestatud" + }, + "error": { + "invalid_auth": "Tuvastamise viga" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mikrotik/translations/fr.json b/homeassistant/components/mikrotik/translations/fr.json index 0049c69632f28c..9b773d3f33600b 100644 --- a/homeassistant/components/mikrotik/translations/fr.json +++ b/homeassistant/components/mikrotik/translations/fr.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Mikrotik est d\u00e9j\u00e0 configur\u00e9" + "already_configured": "Mikrotik est d\u00e9j\u00e0 configur\u00e9", + "already_configured_device": "L'appareil est d\u00e9j\u00e0 configur\u00e9" }, "error": { "cannot_connect": "\u00c9chec de la connexion", + "invalid_auth": "Authentification invalide", "name_exists": "Le nom existe", "wrong_credentials": "Identifiants erron\u00e9s" }, @@ -27,6 +29,7 @@ "device_tracker": { "data": { "arp_ping": "Activer le ping ARP", + "detection_time": "Intervalle de consid\u00e9ration de pr\u00e9sence", "force_dhcp": "Forcer l'analyse \u00e0 l'aide de DHCP" } } diff --git a/homeassistant/components/mikrotik/translations/it.json b/homeassistant/components/mikrotik/translations/it.json index 104392236b22bd..a1bf16d643dae4 100644 --- a/homeassistant/components/mikrotik/translations/it.json +++ b/homeassistant/components/mikrotik/translations/it.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Mikrotik \u00e8 gi\u00e0 configurato" + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "already_configured_device": "Il dispositivo \u00e8 gi\u00e0 configurato" }, "error": { - "cannot_connect": "Connessione Non Riuscita", + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", "name_exists": "Il Nome esiste gi\u00e0", "wrong_credentials": "Credenziali Errate" }, diff --git a/homeassistant/components/mikrotik/translations/no.json b/homeassistant/components/mikrotik/translations/no.json index 1e528fa4986a25..c5d21d5626b15c 100644 --- a/homeassistant/components/mikrotik/translations/no.json +++ b/homeassistant/components/mikrotik/translations/no.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Mikrotik er allerede konfigurert" + "already_configured": "Enheten er allerede konfigurert", + "already_configured_device": "Enheten er allerede konfigurert" }, "error": { - "cannot_connect": "Tilkobling mislykket", + "cannot_connect": "Tilkobling mislyktes.", + "invalid_auth": "Ugyldig godkjenning", "name_exists": "Navnet eksisterer", "wrong_credentials": "Feil legitimasjon" }, diff --git a/homeassistant/components/mikrotik/translations/ru.json b/homeassistant/components/mikrotik/translations/ru.json index e21472d4da73f8..a4596cc70d98e0 100644 --- a/homeassistant/components/mikrotik/translations/ru.json +++ b/homeassistant/components/mikrotik/translations/ru.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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." }, "error": { - "cannot_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.", + "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "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_credentials": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0435 \u0443\u0447\u0451\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435." }, diff --git a/homeassistant/components/mikrotik/translations/zh-Hant.json b/homeassistant/components/mikrotik/translations/zh-Hant.json index caca14b79ed129..5285d089fc61b5 100644 --- a/homeassistant/components/mikrotik/translations/zh-Hant.json +++ b/homeassistant/components/mikrotik/translations/zh-Hant.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Mikrotik \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "already_configured_device": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { - "cannot_connect": "\u9023\u7dda\u672a\u6210\u529f", + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "name_exists": "\u8a72\u540d\u7a31\u5df2\u5b58\u5728", "wrong_credentials": "\u6191\u8b49\u932f\u8aa4" }, diff --git a/homeassistant/components/mill/translations/nl.json b/homeassistant/components/mill/translations/nl.json new file mode 100644 index 00000000000000..4d00f0bfc74883 --- /dev/null +++ b/homeassistant/components/mill/translations/nl.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Gebruikersnaam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mill/translations/pl.json b/homeassistant/components/mill/translations/pl.json index c9bef09227cebe..eaf1da95e9e183 100644 --- a/homeassistant/components/mill/translations/pl.json +++ b/homeassistant/components/mill/translations/pl.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Konto jest ju\u017c skonfigurowane." + "already_configured": "Konto jest ju\u017c skonfigurowane" }, "error": { - "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia." + "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, "step": { "user": { diff --git a/homeassistant/components/min_max/sensor.py b/homeassistant/components/min_max/sensor.py index a54fa3e2d46ba5..c9cd27d16ddbd4 100644 --- a/homeassistant/components/min_max/sensor.py +++ b/homeassistant/components/min_max/sensor.py @@ -228,12 +228,11 @@ def should_poll(self): @property def device_state_attributes(self): """Return the state attributes of the sensor.""" - state_attr = { + return { attr: getattr(self, attr) for attr in ATTR_TO_PROPERTY if getattr(self, attr) is not None } - return state_attr @property def icon(self): diff --git a/homeassistant/components/minecraft_server/strings.json b/homeassistant/components/minecraft_server/strings.json index c0a0c78d5d99f2..9e546a3cdfa171 100644 --- a/homeassistant/components/minecraft_server/strings.json +++ b/homeassistant/components/minecraft_server/strings.json @@ -5,7 +5,7 @@ "title": "Link your Minecraft Server", "description": "Set up your Minecraft Server instance to allow monitoring.", "data": { - "name": "Name", + "name": "[%key:common::config_flow::data::name%]", "host": "[%key:common::config_flow::data::host%]" } } @@ -16,7 +16,7 @@ "invalid_ip": "IP address is invalid (MAC address could not be determined). Please correct it and try again." }, "abort": { - "already_configured": "Host is already configured." + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/minecraft_server/translations/ca.json b/homeassistant/components/minecraft_server/translations/ca.json index ce395b757135b3..b06229a1b1af26 100644 --- a/homeassistant/components/minecraft_server/translations/ca.json +++ b/homeassistant/components/minecraft_server/translations/ca.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "L'amfitri\u00f3 ja est\u00e0 configurat." + "already_configured": "El servei ja est\u00e0 configurat" }, "error": { "cannot_connect": "Ha fallat la connexi\u00f3 amb el servidor. Comprova l'amfitri\u00f3 i el port i torna-ho a provar. Assegurat que estas utilitzant la versi\u00f3 del servidor 1.7 o superior.", diff --git a/homeassistant/components/minecraft_server/translations/en.json b/homeassistant/components/minecraft_server/translations/en.json index fc736c667bec0a..471118773a1f2b 100644 --- a/homeassistant/components/minecraft_server/translations/en.json +++ b/homeassistant/components/minecraft_server/translations/en.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Host is already configured." + "already_configured": "Service is already configured" }, "error": { "cannot_connect": "Failed to connect to server. Please check the host and port and try again. Also ensure that you are running at least Minecraft version 1.7 on your server.", diff --git a/homeassistant/components/minecraft_server/translations/it.json b/homeassistant/components/minecraft_server/translations/it.json index 7f214c4774330b..8aaee8d8b319dc 100644 --- a/homeassistant/components/minecraft_server/translations/it.json +++ b/homeassistant/components/minecraft_server/translations/it.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "L'host \u00e8 gi\u00e0 configurato." + "already_configured": "Il servizio \u00e8 gi\u00e0 configurato" }, "error": { "cannot_connect": "Impossibile connettersi al server. Controllare l'host e la porta e riprovare. Assicurarsi inoltre che si esegue almeno Minecraft versione 1.7 sul server.", diff --git a/homeassistant/components/minecraft_server/translations/no.json b/homeassistant/components/minecraft_server/translations/no.json index 4d2ecc6dbaabc2..5230401a3c3e0e 100644 --- a/homeassistant/components/minecraft_server/translations/no.json +++ b/homeassistant/components/minecraft_server/translations/no.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Verten er allerede konfigurert." + "already_configured": "Tjenesten er allerede konfigurert" }, "error": { "cannot_connect": "Kan ikke koble til serveren. Kontroller verten og porten, og pr\u00f8v p\u00e5 nytt. S\u00f8rg ogs\u00e5 for at du kj\u00f8rer minst Minecraft versjon 1.7 p\u00e5 serveren din.", diff --git a/homeassistant/components/minecraft_server/translations/ru.json b/homeassistant/components/minecraft_server/translations/ru.json index b95c12a1d8023e..93e182a4dfa02c 100644 --- a/homeassistant/components/minecraft_server/translations/ru.json +++ b/homeassistant/components/minecraft_server/translations/ru.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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 \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u0438 \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0441\u043d\u043e\u0432\u0430. \u0422\u0430\u043a\u0436\u0435 \u0443\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u043d\u0430 \u0412\u0430\u0448\u0435\u043c \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d Minecraft \u0432\u0435\u0440\u0441\u0438\u0438 1.7, \u0438\u043b\u0438 \u0432\u044b\u0448\u0435.", diff --git a/homeassistant/components/minecraft_server/translations/zh-Hant.json b/homeassistant/components/minecraft_server/translations/zh-Hant.json index 6dc996fa67dc41..b8bd401d13e355 100644 --- a/homeassistant/components/minecraft_server/translations/zh-Hant.json +++ b/homeassistant/components/minecraft_server/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u4e3b\u6a5f\u7aef\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3002" + "already_configured": "\u670d\u52d9\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "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\u65bc\u4f3a\u670d\u5668\u4e0a\u57f7\u884c\u6700\u65b0\u7248\u672c Minecraft 1.7 \u7248\u3002", diff --git a/homeassistant/components/mobile_app/helpers.py b/homeassistant/components/mobile_app/helpers.py index b2cb3d22e4b7f3..7c5cbd135ed454 100644 --- a/homeassistant/components/mobile_app/helpers.py +++ b/homeassistant/components/mobile_app/helpers.py @@ -7,7 +7,7 @@ from nacl.encoding import Base64Encoder from nacl.secret import SecretBox -from homeassistant.const import HTTP_BAD_REQUEST, HTTP_OK +from homeassistant.const import CONTENT_TYPE_JSON, HTTP_BAD_REQUEST, HTTP_OK from homeassistant.core import Context from homeassistant.helpers.json import JSONEncoder from homeassistant.helpers.typing import HomeAssistantType @@ -94,7 +94,7 @@ def registration_context(registration: Dict) -> Context: def empty_okay_response(headers: Dict = None, status: int = HTTP_OK) -> Response: """Return a Response with empty JSON object and a 200.""" return Response( - text="{}", status=status, content_type="application/json", headers=headers + text="{}", status=status, content_type=CONTENT_TYPE_JSON, headers=headers ) @@ -161,7 +161,7 @@ def webhook_response( data = json.dumps({"encrypted": True, "encrypted_data": enc_data}) return Response( - text=data, status=status, content_type="application/json", headers=headers + text=data, status=status, content_type=CONTENT_TYPE_JSON, headers=headers ) diff --git a/homeassistant/components/mobile_app/notify.py b/homeassistant/components/mobile_app/notify.py index 62bb5fdf08da0b..04d308a5a05a82 100644 --- a/homeassistant/components/mobile_app/notify.py +++ b/homeassistant/components/mobile_app/notify.py @@ -12,7 +12,12 @@ ATTR_TITLE_DEFAULT, BaseNotificationService, ) -from homeassistant.const import HTTP_OK +from homeassistant.const import ( + HTTP_ACCEPTED, + HTTP_CREATED, + HTTP_OK, + HTTP_TOO_MANY_REQUESTS, +) from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.util.dt as dt_util @@ -135,7 +140,7 @@ async def async_send_message(self, message="", **kwargs): response = await self._session.post(push_url, json=data) result = await response.json() - if response.status in [HTTP_OK, 201, 202]: + if response.status in [HTTP_OK, HTTP_CREATED, HTTP_ACCEPTED]: log_rate_limits(self.hass, entry_data[ATTR_DEVICE_NAME], result) continue @@ -152,7 +157,7 @@ async def async_send_message(self, message="", **kwargs): " This message is generated externally to Home Assistant." ) - if response.status == 429: + if response.status == HTTP_TOO_MANY_REQUESTS: _LOGGER.warning(message) log_rate_limits( self.hass, entry_data[ATTR_DEVICE_NAME], result, logging.WARNING diff --git a/homeassistant/components/mobile_app/translations/et.json b/homeassistant/components/mobile_app/translations/et.json new file mode 100644 index 00000000000000..e5c01546976167 --- /dev/null +++ b/homeassistant/components/mobile_app/translations/et.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "install_app": "Home Assistantiga sidumiseks avage mobiilirakendus. \u00dchilduvate rakenduste loendi leiate jaotisest [dokumendid] ( {apps_url} )." + }, + "step": { + "confirm": { + "description": "Kas soovid seadistada mobiilirakenduse sidumist?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mobile_app/webhook.py b/homeassistant/components/mobile_app/webhook.py index 2f5e69fd02b446..bbeea2e9521231 100644 --- a/homeassistant/components/mobile_app/webhook.py +++ b/homeassistant/components/mobile_app/webhook.py @@ -26,6 +26,7 @@ ATTR_DOMAIN, ATTR_SERVICE, ATTR_SERVICE_DATA, + ATTR_SUPPORTED_FEATURES, CONF_WEBHOOK_ID, HTTP_BAD_REQUEST, HTTP_CREATED, @@ -267,7 +268,7 @@ async def webhook_stream_camera(hass, config_entry, data): resp = {"mjpeg_path": "/api/camera_proxy_stream/%s" % (camera.entity_id)} - if camera.attributes["supported_features"] & CAMERA_SUPPORT_STREAM: + if camera.attributes[ATTR_SUPPORTED_FEATURES] & CAMERA_SUPPORT_STREAM: try: resp["hls_path"] = await hass.components.camera.async_request_stream( camera.entity_id, "hls" diff --git a/homeassistant/components/modbus/__init__.py b/homeassistant/components/modbus/__init__.py index 0a7ea08543ab77..822000cb56a023 100644 --- a/homeassistant/components/modbus/__init__.py +++ b/homeassistant/components/modbus/__init__.py @@ -6,29 +6,50 @@ from pymodbus.transaction import ModbusRtuFramer import voluptuous as vol +from homeassistant.components.cover import ( + DEVICE_CLASSES_SCHEMA as COVER_DEVICE_CLASSES_SCHEMA, +) from homeassistant.const import ( ATTR_STATE, + CONF_COVERS, CONF_DELAY, + CONF_DEVICE_CLASS, CONF_HOST, CONF_METHOD, CONF_NAME, CONF_PORT, + CONF_SCAN_INTERVAL, + CONF_SLAVE, CONF_TIMEOUT, CONF_TYPE, EVENT_HOMEASSISTANT_STOP, ) import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.discovery import load_platform from .const import ( ATTR_ADDRESS, ATTR_HUB, ATTR_UNIT, ATTR_VALUE, + CALL_TYPE_COIL, + CALL_TYPE_REGISTER_HOLDING, + CALL_TYPE_REGISTER_INPUT, CONF_BAUDRATE, CONF_BYTESIZE, + CONF_INPUT_TYPE, CONF_PARITY, + CONF_REGISTER, + CONF_STATE_CLOSED, + CONF_STATE_CLOSING, + CONF_STATE_OPEN, + CONF_STATE_OPENING, + CONF_STATUS_REGISTER, + CONF_STATUS_REGISTER_TYPE, CONF_STOPBITS, DEFAULT_HUB, + DEFAULT_SCAN_INTERVAL, + DEFAULT_SLAVE, MODBUS_DOMAIN as DOMAIN, SERVICE_WRITE_COIL, SERVICE_WRITE_REGISTER, @@ -36,9 +57,33 @@ _LOGGER = logging.getLogger(__name__) - BASE_SCHEMA = vol.Schema({vol.Optional(CONF_NAME, default=DEFAULT_HUB): cv.string}) +COVERS_SCHEMA = vol.All( + cv.has_at_least_one_key(CALL_TYPE_COIL, CONF_REGISTER), + vol.Schema( + { + vol.Required(CONF_NAME): cv.string, + vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): vol.All( + cv.time_period, lambda value: value.total_seconds() + ), + vol.Optional(CONF_DEVICE_CLASS): COVER_DEVICE_CLASSES_SCHEMA, + vol.Optional(CONF_SLAVE, default=DEFAULT_SLAVE): cv.positive_int, + vol.Optional(CONF_STATE_CLOSED, default=0): cv.positive_int, + vol.Optional(CONF_STATE_CLOSING, default=3): cv.positive_int, + vol.Optional(CONF_STATE_OPEN, default=1): cv.positive_int, + vol.Optional(CONF_STATE_OPENING, default=2): cv.positive_int, + vol.Optional(CONF_STATUS_REGISTER): cv.positive_int, + vol.Optional( + CONF_STATUS_REGISTER_TYPE, + default=CALL_TYPE_REGISTER_HOLDING, + ): vol.In([CALL_TYPE_REGISTER_HOLDING, CALL_TYPE_REGISTER_INPUT]), + vol.Exclusive(CALL_TYPE_COIL, CONF_INPUT_TYPE): cv.positive_int, + vol.Exclusive(CONF_REGISTER, CONF_INPUT_TYPE): cv.positive_int, + } + ), +) + SERIAL_SCHEMA = BASE_SCHEMA.extend( { vol.Required(CONF_BAUDRATE): cv.positive_int, @@ -49,6 +94,7 @@ vol.Required(CONF_STOPBITS): vol.Any(1, 2), vol.Required(CONF_TYPE): "serial", vol.Optional(CONF_TIMEOUT, default=3): cv.socket_timeout, + vol.Optional(CONF_COVERS): vol.All(cv.ensure_list, [COVERS_SCHEMA]), } ) @@ -59,14 +105,10 @@ vol.Required(CONF_TYPE): vol.Any("tcp", "udp", "rtuovertcp"), vol.Optional(CONF_TIMEOUT, default=3): cv.socket_timeout, vol.Optional(CONF_DELAY, default=0): cv.positive_int, + vol.Optional(CONF_COVERS): vol.All(cv.ensure_list, [COVERS_SCHEMA]), } ) -CONFIG_SCHEMA = vol.Schema( - {DOMAIN: vol.All(cv.ensure_list, [vol.Any(SERIAL_SCHEMA, ETHERNET_SCHEMA)])}, - extra=vol.ALLOW_EXTRA, -) - SERVICE_WRITE_REGISTER_SCHEMA = vol.Schema( { vol.Optional(ATTR_HUB, default=DEFAULT_HUB): cv.string, @@ -87,13 +129,30 @@ } ) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.All( + cv.ensure_list, + [ + vol.Any(SERIAL_SCHEMA, ETHERNET_SCHEMA), + ], + ), + }, + extra=vol.ALLOW_EXTRA, +) + def setup(hass, config): """Set up Modbus component.""" hass.data[DOMAIN] = hub_collect = {} - for client_config in config[DOMAIN]: - hub_collect[client_config[CONF_NAME]] = ModbusHub(client_config) + for conf_hub in config[DOMAIN]: + hub_collect[conf_hub[CONF_NAME]] = ModbusHub(conf_hub) + + # load platforms + for component, conf_key in (("cover", CONF_COVERS),): + if conf_key in conf_hub: + load_platform(hass, component, DOMAIN, conf_hub, config) def stop_modbus(event): """Stop Modbus service.""" diff --git a/homeassistant/components/modbus/const.py b/homeassistant/components/modbus/const.py index c12c50cdc075be..dc29dd626ae966 100644 --- a/homeassistant/components/modbus/const.py +++ b/homeassistant/components/modbus/const.py @@ -46,6 +46,7 @@ ATTR_VALUE = "value" SERVICE_WRITE_COIL = "write_coil" SERVICE_WRITE_REGISTER = "write_register" +DEFAULT_SCAN_INTERVAL = 15 # seconds # binary_sensor.py CONF_INPUTS = "inputs" @@ -71,3 +72,12 @@ CONF_MAX_TEMP = "max_temp" CONF_MIN_TEMP = "min_temp" CONF_STEP = "temp_step" + +# cover.py +CONF_STATE_OPEN = "state_open" +CONF_STATE_CLOSED = "state_closed" +CONF_STATE_OPENING = "state_opening" +CONF_STATE_CLOSING = "state_closing" +CONF_STATUS_REGISTER = "status_register" +CONF_STATUS_REGISTER_TYPE = "status_register_type" +DEFAULT_SLAVE = 1 diff --git a/homeassistant/components/modbus/cover.py b/homeassistant/components/modbus/cover.py new file mode 100644 index 00000000000000..a7c9c301ac55a9 --- /dev/null +++ b/homeassistant/components/modbus/cover.py @@ -0,0 +1,244 @@ +"""Support for Modbus covers.""" +from datetime import timedelta +import logging +from typing import Any, Dict, Optional + +from pymodbus.exceptions import ConnectionException, ModbusException +from pymodbus.pdu import ExceptionResponse + +from homeassistant.components.cover import SUPPORT_CLOSE, SUPPORT_OPEN, CoverEntity +from homeassistant.const import ( + CONF_COVERS, + CONF_DEVICE_CLASS, + CONF_NAME, + CONF_SCAN_INTERVAL, + CONF_SLAVE, +) +from homeassistant.helpers.event import async_track_time_interval +from homeassistant.helpers.restore_state import RestoreEntity +from homeassistant.helpers.typing import ( + ConfigType, + DiscoveryInfoType, + HomeAssistantType, +) + +from . import ModbusHub +from .const import ( + CALL_TYPE_COIL, + CALL_TYPE_REGISTER_HOLDING, + CALL_TYPE_REGISTER_INPUT, + CONF_REGISTER, + CONF_STATE_CLOSED, + CONF_STATE_CLOSING, + CONF_STATE_OPEN, + CONF_STATE_OPENING, + CONF_STATUS_REGISTER, + CONF_STATUS_REGISTER_TYPE, + MODBUS_DOMAIN, +) + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_platform( + hass: HomeAssistantType, + config: ConfigType, + async_add_entities, + discovery_info: Optional[DiscoveryInfoType] = None, +): + """Read configuration and create Modbus cover.""" + if discovery_info is None: + return + + covers = [] + for cover in discovery_info[CONF_COVERS]: + hub: ModbusHub = hass.data[MODBUS_DOMAIN][discovery_info[CONF_NAME]] + covers.append(ModbusCover(hub, cover)) + + async_add_entities(covers) + + +class ModbusCover(CoverEntity, RestoreEntity): + """Representation of a Modbus cover.""" + + def __init__( + self, + hub: ModbusHub, + config: Dict[str, Any], + ): + """Initialize the modbus cover.""" + self._hub: ModbusHub = hub + self._coil = config.get(CALL_TYPE_COIL) + self._device_class = config.get(CONF_DEVICE_CLASS) + self._name = config[CONF_NAME] + self._register = config.get(CONF_REGISTER) + self._slave = config[CONF_SLAVE] + self._state_closed = config[CONF_STATE_CLOSED] + self._state_closing = config[CONF_STATE_CLOSING] + self._state_open = config[CONF_STATE_OPEN] + self._state_opening = config[CONF_STATE_OPENING] + self._status_register = config.get(CONF_STATUS_REGISTER) + self._status_register_type = config[CONF_STATUS_REGISTER_TYPE] + self._scan_interval = timedelta(seconds=config[CONF_SCAN_INTERVAL]) + self._value = None + self._available = True + + # If we read cover status from coil, and not from optional status register, + # we interpret boolean value False as closed cover, and value True as open cover. + # Intermediate states are not supported in such a setup. + if self._coil is not None and self._status_register is None: + self._state_closed = False + self._state_open = True + self._state_closing = None + self._state_opening = None + + # If we read cover status from the main register (i.e., an optional + # status register is not specified), we need to make sure the register_type + # is set to "holding". + if self._register is not None and self._status_register is None: + self._status_register = self._register + self._status_register_type = CALL_TYPE_REGISTER_HOLDING + + async def async_added_to_hass(self): + """Handle entity which will be added.""" + state = await self.async_get_last_state() + if not state: + return + self._value = state.state + + async_track_time_interval( + self.hass, lambda arg: self._update(), self._scan_interval + ) + + @property + def device_class(self) -> Optional[str]: + """Return the device class of the sensor.""" + return self._device_class + + @property + def name(self): + """Return the name of the switch.""" + return self._name + + @property + def supported_features(self): + """Flag supported features.""" + return SUPPORT_OPEN | SUPPORT_CLOSE + + @property + def available(self) -> bool: + """Return True if entity is available.""" + return self._available + + @property + def is_opening(self): + """Return if the cover is opening or not.""" + return self._value == self._state_opening + + @property + def is_closing(self): + """Return if the cover is closing or not.""" + return self._value == self._state_closing + + @property + def is_closed(self): + """Return if the cover is closed or not.""" + return self._value == self._state_closed + + @property + def should_poll(self): + """Return True if entity has to be polled for state. + + False if entity pushes its state to HA. + """ + + # Handle polling directly in this entity + return False + + def open_cover(self, **kwargs: Any) -> None: + """Open cover.""" + if self._coil is not None: + self._write_coil(True) + else: + self._write_register(self._state_open) + + self._update() + + def close_cover(self, **kwargs: Any) -> None: + """Close cover.""" + if self._coil is not None: + self._write_coil(False) + else: + self._write_register(self._state_closed) + + self._update() + + def _update(self): + """Update the state of the cover.""" + if self._coil is not None and self._status_register is None: + self._value = self._read_coil() + else: + self._value = self._read_status_register() + + self.schedule_update_ha_state() + + def _read_status_register(self) -> Optional[int]: + """Read status register using the Modbus hub slave.""" + try: + if self._status_register_type == CALL_TYPE_REGISTER_INPUT: + result = self._hub.read_input_registers( + self._slave, self._status_register, 1 + ) + else: + result = self._hub.read_holding_registers( + self._slave, self._status_register, 1 + ) + except ConnectionException: + self._available = False + return + + if isinstance(result, (ModbusException, ExceptionResponse)): + self._available = False + return + + value = int(result.registers[0]) + self._available = True + + return value + + def _write_register(self, value): + """Write holding register using the Modbus hub slave.""" + try: + self._hub.write_register(self._slave, self._register, value) + except ConnectionException: + self._available = False + return + + self._available = True + + def _read_coil(self) -> Optional[bool]: + """Read coil using the Modbus hub slave.""" + try: + result = self._hub.read_coils(self._slave, self._coil, 1) + except ConnectionException: + self._available = False + return + + if isinstance(result, (ModbusException, ExceptionResponse)): + self._available = False + return + + value = bool(result.bits[0]) + self._available = True + + return value + + def _write_coil(self, value): + """Write coil using the Modbus hub slave.""" + try: + self._hub.write_coil(self._slave, self._coil, value) + except ConnectionException: + self._available = False + return + + self._available = True diff --git a/homeassistant/components/modbus/manifest.json b/homeassistant/components/modbus/manifest.json index a9155c7b628d3f..05e9c39c4b5b80 100644 --- a/homeassistant/components/modbus/manifest.json +++ b/homeassistant/components/modbus/manifest.json @@ -3,5 +3,5 @@ "name": "Modbus", "documentation": "https://www.home-assistant.io/integrations/modbus", "requirements": ["pymodbus==2.3.0"], - "codeowners": ["@adamchengtkc", "@janiversen"] + "codeowners": ["@adamchengtkc", "@janiversen", "@vzahradnik"] } diff --git a/homeassistant/components/modbus/switch.py b/homeassistant/components/modbus/switch.py index 8037d926ef1f1f..fa5b42807b0a8f 100644 --- a/homeassistant/components/modbus/switch.py +++ b/homeassistant/components/modbus/switch.py @@ -158,19 +158,22 @@ def update(self): """Update the state of the switch.""" self._is_on = self._read_coil(self._coil) - def _read_coil(self, coil) -> Optional[bool]: + def _read_coil(self, coil) -> bool: """Read coil using the Modbus hub slave.""" try: result = self._hub.read_coils(self._slave, coil, 1) except ConnectionException: self._available = False - return + return False if isinstance(result, (ModbusException, ExceptionResponse)): self._available = False - return + return False self._available = True + # bits[0] select the lowest bit in result, + # is_on for a binary_sensor is true if the bit are 1 + # The other bits are not considered. return bool(result.bits[0]) def _write_coil(self, coil, value): diff --git a/homeassistant/components/monoprice/strings.json b/homeassistant/components/monoprice/strings.json index c25fb901d7658c..008c182f41b063 100644 --- a/homeassistant/components/monoprice/strings.json +++ b/homeassistant/components/monoprice/strings.json @@ -15,11 +15,11 @@ } }, "error": { - "cannot_connect": "Failed to connect, please try again", - "unknown": "Unexpected error" + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { - "already_configured": "Device is already configured" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } }, "options": { @@ -37,4 +37,4 @@ } } } -} \ No newline at end of file +} diff --git a/homeassistant/components/monoprice/translations/ca.json b/homeassistant/components/monoprice/translations/ca.json index 6af5204b91e15e..1e4c623c215649 100644 --- a/homeassistant/components/monoprice/translations/ca.json +++ b/homeassistant/components/monoprice/translations/ca.json @@ -4,7 +4,7 @@ "already_configured": "El dispositiu ja est\u00e0 configurat" }, "error": { - "cannot_connect": "No s'ha pogut connectar, torna-ho a provar", + "cannot_connect": "Ha fallat la connexi\u00f3", "unknown": "Error inesperat" }, "step": { diff --git a/homeassistant/components/monoprice/translations/en.json b/homeassistant/components/monoprice/translations/en.json index 9e9f3a4d2cfb27..08438f8a985ee5 100644 --- a/homeassistant/components/monoprice/translations/en.json +++ b/homeassistant/components/monoprice/translations/en.json @@ -4,7 +4,7 @@ "already_configured": "Device is already configured" }, "error": { - "cannot_connect": "Failed to connect, please try again", + "cannot_connect": "Failed to connect", "unknown": "Unexpected error" }, "step": { diff --git a/homeassistant/components/monoprice/translations/it.json b/homeassistant/components/monoprice/translations/it.json index b89758a9da3598..d084929e320321 100644 --- a/homeassistant/components/monoprice/translations/it.json +++ b/homeassistant/components/monoprice/translations/it.json @@ -4,7 +4,7 @@ "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" }, "error": { - "cannot_connect": "Impossibile connettersi, si prega di riprovare", + "cannot_connect": "Impossibile connettersi", "unknown": "Errore imprevisto" }, "step": { diff --git a/homeassistant/components/monoprice/translations/no.json b/homeassistant/components/monoprice/translations/no.json index acd4bde8774102..93efecbf54aea4 100644 --- a/homeassistant/components/monoprice/translations/no.json +++ b/homeassistant/components/monoprice/translations/no.json @@ -4,7 +4,7 @@ "already_configured": "Enheten er allerede konfigurert" }, "error": { - "cannot_connect": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen", + "cannot_connect": "Tilkobling mislyktes.", "unknown": "Uventet feil" }, "step": { diff --git a/homeassistant/components/monoprice/translations/pl.json b/homeassistant/components/monoprice/translations/pl.json index b5af0e8851f9de..020e0f2c554a76 100644 --- a/homeassistant/components/monoprice/translations/pl.json +++ b/homeassistant/components/monoprice/translations/pl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "user": { diff --git a/homeassistant/components/monoprice/translations/ru.json b/homeassistant/components/monoprice/translations/ru.json index 5b891db80a38c3..4fb5eb892a1de9 100644 --- a/homeassistant/components/monoprice/translations/ru.json +++ b/homeassistant/components/monoprice/translations/ru.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", + "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": { diff --git a/homeassistant/components/monoprice/translations/zh-Hant.json b/homeassistant/components/monoprice/translations/zh-Hant.json index ca1923d62d6cab..e653bda9205709 100644 --- a/homeassistant/components/monoprice/translations/zh-Hant.json +++ b/homeassistant/components/monoprice/translations/zh-Hant.json @@ -4,7 +4,7 @@ "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21", + "cannot_connect": "\u9023\u7dda\u5931\u6557", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { diff --git a/homeassistant/components/moon/translations/sensor.cs.json b/homeassistant/components/moon/translations/sensor.cs.json index ff430618ff96eb..368fa833ea023f 100644 --- a/homeassistant/components/moon/translations/sensor.cs.json +++ b/homeassistant/components/moon/translations/sensor.cs.json @@ -3,8 +3,12 @@ "moon__phase": { "first_quarter": "Prvn\u00ed \u010dtvr\u0165", "full_moon": "\u00dapln\u011bk", - "waxing_crescent": "Dor\u016fstaj\u00edc\u00ed srpek", - "waxing_gibbous": "Prvn\u00ed \u010dtvr\u0165" + "last_quarter": "Posledn\u00ed \u010dtvrt", + "new_moon": "Nov", + "waning_crescent": "Ub\u00fdvaj\u00edc\u00ed p\u016flm\u011bs\u00edc", + "waning_gibbous": "Ub\u00fdvaj\u00edc\u00ed m\u011bs\u00edc", + "waxing_crescent": "Dor\u016fstaj\u00edc\u00ed p\u016flm\u011bs\u00edc", + "waxing_gibbous": "Dor\u016fstaj\u00edc\u00ed m\u011bs\u00edc" } } } \ No newline at end of file diff --git a/homeassistant/components/moon/translations/sensor.et.json b/homeassistant/components/moon/translations/sensor.et.json new file mode 100644 index 00000000000000..e147772786848c --- /dev/null +++ b/homeassistant/components/moon/translations/sensor.et.json @@ -0,0 +1,14 @@ +{ + "state": { + "moon__phase": { + "first_quarter": "Kasvav poolkuu", + "full_moon": "T\u00e4iskuu", + "last_quarter": "Kahanev poolkuu", + "new_moon": "Kuu loomine", + "waning_crescent": "Vanakuu", + "waning_gibbous": "Kahanev kuu", + "waxing_crescent": "Noorkuu", + "waxing_gibbous": "Kasvav kuu" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mpd/media_player.py b/homeassistant/components/mpd/media_player.py index 8a46cef6eb35d7..845b0ae506ba71 100644 --- a/homeassistant/components/mpd/media_player.py +++ b/homeassistant/components/mpd/media_player.py @@ -138,12 +138,12 @@ def _fetch_status(self): if position is None: position = self._status.get("time") - if position is not None and ":" in position: + if isinstance(position, str) and ":" in position: position = position.split(":")[0] if position is not None and self._media_position != position: self._media_position_updated_at = dt_util.utcnow() - self._media_position = int(position) + self._media_position = int(float(position)) self._update_playlists() @@ -159,8 +159,9 @@ def update(self): self._connect() self._fetch_status() - except (mpd.ConnectionError, OSError, BrokenPipeError, ValueError): + except (mpd.ConnectionError, OSError, BrokenPipeError, ValueError) as error: # Cleanly disconnect in case connection is not in valid state + _LOGGER.debug("Error updating status: %s", error) self._disconnect() @property diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 2b5dca6474f812..b42032c6a1c8a2 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -60,6 +60,7 @@ CONF_RETAIN, CONF_STATE_TOPIC, CONF_WILL_MESSAGE, + DATA_MQTT_CONFIG, DEFAULT_BIRTH, DEFAULT_DISCOVERY, DEFAULT_PAYLOAD_AVAILABLE, @@ -88,7 +89,6 @@ DOMAIN = "mqtt" DATA_MQTT = "mqtt" -DATA_MQTT_CONFIG = "mqtt_config" SERVICE_PUBLISH = "publish" SERVICE_DUMP = "dump" @@ -134,7 +134,7 @@ CONNECTION_FAILED_RECOVERABLE = "connection_failed_recoverable" DISCOVERY_COOLDOWN = 2 -TIMEOUT_ACK = 1 +TIMEOUT_ACK = 10 PLATFORMS = [ "alarm_control_panel", @@ -630,6 +630,7 @@ class Subscription: """Class to hold data about an active subscription.""" topic: str = attr.ib() + matcher: Any = attr.ib() callback: MessageCallbackType = attr.ib() qos: int = attr.ib(default=0) encoding: str = attr.ib(default="utf-8") @@ -838,7 +839,9 @@ async def async_subscribe( if not isinstance(topic, str): raise HomeAssistantError("Topic needs to be a string!") - subscription = Subscription(topic, msg_callback, qos, encoding) + subscription = Subscription( + topic, _matcher_for_topic(topic), msg_callback, qos, encoding + ) self.subscriptions.append(subscription) # Only subscribe if currently connected. @@ -953,7 +956,7 @@ def _mqtt_handle_message(self, msg) -> None: timestamp = dt_util.utcnow() for subscription in self.subscriptions: - if not _match_topic(subscription.topic, msg.topic): + if not subscription.matcher(msg.topic): continue payload: SubscribePayloadType = msg.payload @@ -1050,18 +1053,14 @@ def _raise_on_error(result_code: int) -> None: ) -def _match_topic(subscription: str, topic: str) -> bool: - """Test if topic matches subscription.""" +def _matcher_for_topic(subscription: str) -> Any: # pylint: disable=import-outside-toplevel from paho.mqtt.matcher import MQTTMatcher matcher = MQTTMatcher() matcher[subscription] = True - try: - next(matcher.iter_match(topic)) - return True - except StopIteration: - return False + + return lambda topic: next(matcher.iter_match(topic), False) class MqttAttributes(Entity): @@ -1229,7 +1228,7 @@ async def cleanup_device_registry(hass, device_id): """Remove device registry entry if there are no remaining entities or triggers.""" # Local import to avoid circular dependencies # pylint: disable=import-outside-toplevel - from . import device_trigger + from . import device_trigger, tag device_registry = await hass.helpers.device_registry.async_get_registry() entity_registry = await hass.helpers.entity_registry.async_get_registry() @@ -1239,6 +1238,7 @@ async def cleanup_device_registry(hass, device_id): entity_registry, device_id ) and not await device_trigger.async_get_triggers(hass, device_id) + and not tag.async_has_tags(hass, device_id) ): device_registry.async_remove_device(device_id) @@ -1465,3 +1465,33 @@ async def forward_messages(mqttmsg: Message): ) connection.send_message(websocket_api.result_message(msg["id"])) + + +@callback +def async_subscribe_connection_status(hass, connection_status_callback): + """Subscribe to MQTT connection changes.""" + + @callback + def connected(): + hass.async_add_job(connection_status_callback, True) + + @callback + def disconnected(): + _LOGGER.error("Calling connection_status_callback, False") + hass.async_add_job(connection_status_callback, False) + + subscriptions = { + "connect": async_dispatcher_connect(hass, MQTT_CONNECTED, connected), + "disconnect": async_dispatcher_connect(hass, MQTT_DISCONNECTED, disconnected), + } + + def unsubscribe(): + subscriptions["connect"]() + subscriptions["disconnect"]() + + return unsubscribe + + +def is_connected(hass): + """Return if MQTT client is connected.""" + return hass.data[DATA_MQTT].connected diff --git a/homeassistant/components/mqtt/config_flow.py b/homeassistant/components/mqtt/config_flow.py index 8b1c350323c05b..5c4016437a62d2 100644 --- a/homeassistant/components/mqtt/config_flow.py +++ b/homeassistant/components/mqtt/config_flow.py @@ -24,6 +24,7 @@ CONF_BROKER, CONF_DISCOVERY, CONF_WILL_MESSAGE, + DATA_MQTT_CONFIG, DEFAULT_BIRTH, DEFAULT_DISCOVERY, DEFAULT_WILL, @@ -162,6 +163,7 @@ async def async_step_broker(self, user_input=None): """Manage the MQTT options.""" errors = {} current_config = self.config_entry.data + yaml_config = self.hass.data.get(DATA_MQTT_CONFIG, {}) if user_input is not None: can_connect = await self.hass.async_add_executor_job( try_connection, @@ -178,20 +180,22 @@ async def async_step_broker(self, user_input=None): errors["base"] = "cannot_connect" fields = OrderedDict() - fields[vol.Required(CONF_BROKER, default=current_config[CONF_BROKER])] = str - fields[vol.Required(CONF_PORT, default=current_config[CONF_PORT])] = vol.Coerce( - int - ) + current_broker = current_config.get(CONF_BROKER, yaml_config.get(CONF_BROKER)) + current_port = current_config.get(CONF_PORT, yaml_config.get(CONF_PORT)) + current_user = current_config.get(CONF_USERNAME, yaml_config.get(CONF_USERNAME)) + current_pass = current_config.get(CONF_PASSWORD, yaml_config.get(CONF_PASSWORD)) + fields[vol.Required(CONF_BROKER, default=current_broker)] = str + fields[vol.Required(CONF_PORT, default=current_port)] = vol.Coerce(int) fields[ vol.Optional( CONF_USERNAME, - description={"suggested_value": current_config.get(CONF_USERNAME)}, + description={"suggested_value": current_user}, ) ] = str fields[ vol.Optional( CONF_PASSWORD, - description={"suggested_value": current_config.get(CONF_PASSWORD)}, + description={"suggested_value": current_pass}, ) ] = str @@ -205,6 +209,7 @@ async def async_step_options(self, user_input=None): """Manage the MQTT options.""" errors = {} current_config = self.config_entry.data + yaml_config = self.hass.data.get(DATA_MQTT_CONFIG, {}) options_config = {} if user_input is not None: bad_birth = False @@ -253,16 +258,24 @@ async def async_step_options(self, user_input=None): ) return self.async_create_entry(title="", data=None) - birth = {**DEFAULT_BIRTH, **current_config.get(CONF_BIRTH_MESSAGE, {})} - will = {**DEFAULT_WILL, **current_config.get(CONF_WILL_MESSAGE, {})} + birth = { + **DEFAULT_BIRTH, + **current_config.get( + CONF_BIRTH_MESSAGE, yaml_config.get(CONF_BIRTH_MESSAGE, {}) + ), + } + will = { + **DEFAULT_WILL, + **current_config.get( + CONF_WILL_MESSAGE, yaml_config.get(CONF_WILL_MESSAGE, {}) + ), + } + discovery = current_config.get( + CONF_DISCOVERY, yaml_config.get(CONF_DISCOVERY, DEFAULT_DISCOVERY) + ) fields = OrderedDict() - fields[ - vol.Optional( - CONF_DISCOVERY, - default=current_config.get(CONF_DISCOVERY, DEFAULT_DISCOVERY), - ) - ] = bool + fields[vol.Optional(CONF_DISCOVERY, default=discovery)] = bool # Birth message is disabled if CONF_BIRTH_MESSAGE = {} fields[ diff --git a/homeassistant/components/mqtt/const.py b/homeassistant/components/mqtt/const.py index 7ea6d9d348bc4e..5ab3f756311e0e 100644 --- a/homeassistant/components/mqtt/const.py +++ b/homeassistant/components/mqtt/const.py @@ -17,6 +17,8 @@ CONF_STATE_TOPIC = "state_topic" CONF_WILL_MESSAGE = "will_message" +DATA_MQTT_CONFIG = "mqtt_config" + DEFAULT_PREFIX = "homeassistant" DEFAULT_BIRTH_WILL_TOPIC = DEFAULT_PREFIX + "/status" DEFAULT_DISCOVERY = False diff --git a/homeassistant/components/mqtt/discovery.py b/homeassistant/components/mqtt/discovery.py index a7d5236148b997..6c4cbfd212fefd 100644 --- a/homeassistant/components/mqtt/discovery.py +++ b/homeassistant/components/mqtt/discovery.py @@ -32,6 +32,7 @@ "lock", "sensor", "switch", + "tag", "vacuum", ] @@ -154,6 +155,12 @@ async def async_device_message_received(msg): from . import device_automation await device_automation.async_setup_entry(hass, config_entry) + elif component == "tag": + # Local import to avoid circular dependencies + # pylint: disable=import-outside-toplevel + from . import tag + + await tag.async_setup_entry(hass, config_entry) else: await hass.config_entries.async_forward_entry_setup( config_entry, component diff --git a/homeassistant/components/mqtt/manifest.json b/homeassistant/components/mqtt/manifest.json index 8b293eb06f6585..4d44090a4e3559 100644 --- a/homeassistant/components/mqtt/manifest.json +++ b/homeassistant/components/mqtt/manifest.json @@ -3,7 +3,7 @@ "name": "MQTT", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/mqtt", - "requirements": ["paho-mqtt==1.5.0"], + "requirements": ["paho-mqtt==1.5.1"], "dependencies": ["http"], "codeowners": ["@home-assistant/core", "@emontnemery"] } diff --git a/homeassistant/components/mqtt/strings.json b/homeassistant/components/mqtt/strings.json index 75c3fdec2607ce..8c3db8e5b61eb0 100644 --- a/homeassistant/components/mqtt/strings.json +++ b/homeassistant/components/mqtt/strings.json @@ -20,10 +20,10 @@ } }, "abort": { - "single_instance_allowed": "Only a single configuration of MQTT is allowed." + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" }, "error": { - "cannot_connect": "Unable to connect to the broker." + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" } }, "device_automation": { @@ -68,7 +68,7 @@ "birth_payload": "Birth message payload", "birth_qos": "Birth message QoS", "birth_retain": "Birth message retain", - "will_enable": "Enable birth message", + "will_enable": "Enable will message", "will_topic": "Will message topic", "will_payload": "Will message payload", "will_qos": "Will message QoS", @@ -77,9 +77,9 @@ } }, "error": { - "cannot_connect": "Unable to connect to the broker.", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "bad_birth": "Invalid birth topic.", "bad_will": "Invalid will topic." } } -} \ No newline at end of file +} diff --git a/homeassistant/components/mqtt/tag.py b/homeassistant/components/mqtt/tag.py new file mode 100644 index 00000000000000..94356ccf778c31 --- /dev/null +++ b/homeassistant/components/mqtt/tag.py @@ -0,0 +1,224 @@ +"""Provides tag scanning for MQTT.""" +import logging + +import voluptuous as vol + +from homeassistant.components import mqtt +from homeassistant.const import CONF_PLATFORM, CONF_VALUE_TEMPLATE +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from . import ( + ATTR_DISCOVERY_HASH, + ATTR_DISCOVERY_TOPIC, + CONF_CONNECTIONS, + CONF_DEVICE, + CONF_IDENTIFIERS, + CONF_QOS, + CONF_TOPIC, + DOMAIN, + cleanup_device_registry, + subscription, +) +from .discovery import MQTT_DISCOVERY_NEW, MQTT_DISCOVERY_UPDATED, clear_discovery_hash +from .util import valid_subscribe_topic + +_LOGGER = logging.getLogger(__name__) + +TAG = "tag" +TAGS = "mqtt_tags" + +PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_DEVICE): mqtt.MQTT_ENTITY_DEVICE_INFO_SCHEMA, + vol.Optional(CONF_PLATFORM): "mqtt", + vol.Required(CONF_TOPIC): valid_subscribe_topic, + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + }, + mqtt.validate_device_has_at_least_one_identifier, +) + + +async def async_setup_entry(hass, config_entry): + """Set up MQTT tag scan dynamically through MQTT discovery.""" + + async def async_discover(discovery_payload): + """Discover and add MQTT tag scan.""" + discovery_data = discovery_payload.discovery_data + try: + config = PLATFORM_SCHEMA(discovery_payload) + await async_setup_tag(hass, config, config_entry, discovery_data) + except Exception: + clear_discovery_hash(hass, discovery_data[ATTR_DISCOVERY_HASH]) + raise + + async_dispatcher_connect( + hass, MQTT_DISCOVERY_NEW.format("tag", "mqtt"), async_discover + ) + + +async def async_setup_tag(hass, config, config_entry, discovery_data): + """Set up the MQTT tag scanner.""" + discovery_hash = discovery_data[ATTR_DISCOVERY_HASH] + discovery_id = discovery_hash[1] + + device_id = None + if CONF_DEVICE in config: + await _update_device(hass, config_entry, config) + + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get_device( + {(DOMAIN, id_) for id_ in config[CONF_DEVICE][CONF_IDENTIFIERS]}, + {tuple(x) for x in config[CONF_DEVICE][CONF_CONNECTIONS]}, + ) + + if device is None: + return + device_id = device.id + + if TAGS not in hass.data: + hass.data[TAGS] = {} + if device_id not in hass.data[TAGS]: + hass.data[TAGS][device_id] = {} + + tag_scanner = MQTTTagScanner( + hass, + config, + device_id, + discovery_data, + config_entry, + ) + + await tag_scanner.setup() + + if device_id: + hass.data[TAGS][device_id][discovery_id] = tag_scanner + + +def async_has_tags(hass, device_id): + """Device has tag scanners.""" + if TAGS not in hass.data or device_id not in hass.data[TAGS]: + return False + return hass.data[TAGS][device_id] != {} + + +class MQTTTagScanner: + """MQTT Tag scanner.""" + + def __init__(self, hass, config, device_id, discovery_data, config_entry): + """Initialize.""" + self._config = config + self._config_entry = config_entry + self.device_id = device_id + self.discovery_data = discovery_data + self.hass = hass + self._remove_discovery = None + self._remove_device_updated = None + self._sub_state = None + self._value_template = None + + self._setup_from_config(config) + + async def discovery_update(self, payload): + """Handle discovery update.""" + discovery_hash = self.discovery_data[ATTR_DISCOVERY_HASH] + _LOGGER.info( + "Got update for tag scanner with hash: %s '%s'", discovery_hash, payload + ) + if not payload: + # Empty payload: Remove tag scanner + _LOGGER.info("Removing tag scanner: %s", discovery_hash) + await self.tear_down() + if self.device_id: + await cleanup_device_registry(self.hass, self.device_id) + else: + # Non-empty payload: Update tag scanner + _LOGGER.info("Updating tag scanner: %s", discovery_hash) + config = PLATFORM_SCHEMA(payload) + self._config = config + if self.device_id: + await _update_device(self.hass, self._config_entry, config) + self._setup_from_config(config) + await self.subscribe_topics() + + def _setup_from_config(self, config): + self._value_template = lambda value, error_value: value + if CONF_VALUE_TEMPLATE in config: + value_template = config.get(CONF_VALUE_TEMPLATE) + value_template.hass = self.hass + + self._value_template = value_template.async_render_with_possible_json_value + + async def setup(self): + """Set up the MQTT tag scanner.""" + discovery_hash = self.discovery_data[ATTR_DISCOVERY_HASH] + await self.subscribe_topics() + if self.device_id: + self._remove_device_updated = self.hass.bus.async_listen( + EVENT_DEVICE_REGISTRY_UPDATED, self.device_removed + ) + self._remove_discovery = async_dispatcher_connect( + self.hass, + MQTT_DISCOVERY_UPDATED.format(discovery_hash), + self.discovery_update, + ) + + async def subscribe_topics(self): + """Subscribe to MQTT topics.""" + + async def tag_scanned(msg): + tag_id = self._value_template(msg.payload, error_value="").strip() + if not tag_id: # No output from template, ignore + return + + await self.hass.components.tag.async_scan_tag(tag_id, self.device_id) + + self._sub_state = await subscription.async_subscribe_topics( + self.hass, + self._sub_state, + { + "state_topic": { + "topic": self._config[CONF_TOPIC], + "msg_callback": tag_scanned, + "qos": self._config[CONF_QOS], + } + }, + ) + + async def device_removed(self, event): + """Handle the removal of a device.""" + device_id = event.data["device_id"] + if event.data["action"] != "remove" or device_id != self.device_id: + return + + await self.tear_down() + + async def tear_down(self): + """Cleanup tag scanner.""" + discovery_hash = self.discovery_data[ATTR_DISCOVERY_HASH] + discovery_id = discovery_hash[1] + discovery_topic = self.discovery_data[ATTR_DISCOVERY_TOPIC] + + clear_discovery_hash(self.hass, discovery_hash) + if self.device_id: + self._remove_device_updated() + self._remove_discovery() + + mqtt.publish(self.hass, discovery_topic, "", retain=True) + self._sub_state = await subscription.async_unsubscribe_topics( + self.hass, self._sub_state + ) + if self.device_id: + self.hass.data[TAGS][self.device_id].pop(discovery_id) + + +async def _update_device(hass, config_entry, config): + """Update device registry.""" + device_registry = await hass.helpers.device_registry.async_get_registry() + config_entry_id = config_entry.entry_id + device_info = mqtt.device_info_from_config(config[CONF_DEVICE]) + + if config_entry_id is not None and device_info is not None: + device_info["config_entry_id"] = config_entry_id + device_registry.async_get_or_create(**device_info) diff --git a/homeassistant/components/mqtt/translations/ca.json b/homeassistant/components/mqtt/translations/ca.json index e94be2d1a6d568..f72ee30cdcfc3a 100644 --- a/homeassistant/components/mqtt/translations/ca.json +++ b/homeassistant/components/mqtt/translations/ca.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "single_instance_allowed": "Nom\u00e9s es permet una \u00fanica configuraci\u00f3 de MQTT." + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "error": { - "cannot_connect": "No s'ha pogut connectar amb el broker." + "cannot_connect": "Ha fallat la connexi\u00f3" }, "step": { "broker": { @@ -52,7 +52,7 @@ "error": { "bad_birth": "Topic missatge de naixement inv\u00e0lid.", "bad_will": "Topic missatge d'\u00faltima voluntat inv\u00e0lid.", - "cannot_connect": "No es pot connectar amb el broker." + "cannot_connect": "Ha fallat la connexi\u00f3" }, "step": { "broker": { @@ -72,7 +72,7 @@ "birth_retain": "Retenci\u00f3 missatge de naixement", "birth_topic": "Topic missatge de naixement", "discovery": "Activar descobriment", - "will_enable": "Activa el missatge de naixement", + "will_enable": "Activa el missatge d'\u00faltima voluntat", "will_payload": "Dades (payload) missatge d'\u00faltima voluntat", "will_qos": "QoS missatge d'\u00faltima voluntat", "will_retain": "Retenci\u00f3 missatge d'\u00faltima voluntat", diff --git a/homeassistant/components/mqtt/translations/de.json b/homeassistant/components/mqtt/translations/de.json index 7256fe2f956cd5..7f34e10fa896c4 100644 --- a/homeassistant/components/mqtt/translations/de.json +++ b/homeassistant/components/mqtt/translations/de.json @@ -52,7 +52,10 @@ "step": { "broker": { "data": { - "password": "Passwort" + "broker": "Broker", + "password": "Passwort", + "port": "Port", + "username": "Benutzername" } } } diff --git a/homeassistant/components/mqtt/translations/en.json b/homeassistant/components/mqtt/translations/en.json index 8ece91cb85d796..362e51b440537d 100644 --- a/homeassistant/components/mqtt/translations/en.json +++ b/homeassistant/components/mqtt/translations/en.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "single_instance_allowed": "Only a single configuration of MQTT is allowed." + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "error": { - "cannot_connect": "Unable to connect to the broker." + "cannot_connect": "Failed to connect" }, "step": { "broker": { @@ -52,7 +52,7 @@ "error": { "bad_birth": "Invalid birth topic.", "bad_will": "Invalid will topic.", - "cannot_connect": "Unable to connect to the broker." + "cannot_connect": "Failed to connect" }, "step": { "broker": { @@ -72,7 +72,7 @@ "birth_retain": "Birth message retain", "birth_topic": "Birth message topic", "discovery": "Enable discovery", - "will_enable": "Enable birth message", + "will_enable": "Enable will message", "will_payload": "Will message payload", "will_qos": "Will message QoS", "will_retain": "Will message retain", diff --git a/homeassistant/components/mqtt/translations/et.json b/homeassistant/components/mqtt/translations/et.json new file mode 100644 index 00000000000000..e8a9fac81d7416 --- /dev/null +++ b/homeassistant/components/mqtt/translations/et.json @@ -0,0 +1,85 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Lubatud on ainult \u00fcks MQTT konfiguratsioon." + }, + "error": { + "cannot_connect": "Vahendajaga ei saa \u00fchendust luua." + }, + "step": { + "broker": { + "data": { + "broker": "Vahendaja", + "discovery": "Luba automaatne avastamine", + "password": "Salas\u00f5na", + "port": "Port", + "username": "Kasutajanimi" + }, + "description": "Sisestage oma MQTT vahendaja andmed." + }, + "hassio_confirm": { + "data": { + "discovery": "Luba automaatne avastamine" + }, + "description": "Kas soovite seadistada Home Assistanti \u00fchenduse loomiseks Hass.io lisandmooduli {addon} pakutava MQTT vahendajaga?", + "title": "MQTT vahendaja Hass.io pistikprogrammi kaudu" + } + } + }, + "device_automation": { + "trigger_subtype": { + "button_1": "Esimene nupp", + "button_2": "Teine nupp", + "button_3": "Kolmas nupp", + "button_4": "Neljas nupp", + "button_5": "Viies nupp", + "button_6": "Kuues nupp", + "turn_off": "L\u00fclita v\u00e4lja", + "turn_on": "L\u00fclita sisse" + }, + "trigger_type": { + "button_double_press": "\" {subtype} \" on topeltkl\u00f5psatud", + "button_long_press": "\" {subtype} \" on pikalt alla vajutatud", + "button_long_release": "\"{subtype}\" vabastatati p\u00e4rast pikka vajutust", + "button_quadruple_press": "\"{subtype}\" on neljakordselt kl\u00f5psatud", + "button_quintuple_press": "\"{subtype}\" on viiekordselt kl\u00f5psatud", + "button_short_press": "\u201e {subtype} \u201d on vajutatud", + "button_short_release": "\" {subtype} \" vabastati", + "button_triple_press": "\"{subtype}\" on kolmekordselt kl\u00f5psatud" + } + }, + "options": { + "error": { + "bad_birth": "Kehtetu loomise teavitus.", + "bad_will": "Kehtetu l\u00f5petamise teavitus.", + "cannot_connect": "Vahendajaga ei saa \u00fchendust luua." + }, + "step": { + "broker": { + "data": { + "broker": "Vahendaja", + "password": "Salas\u00f5na", + "port": "Port", + "username": "Kasutajanimi" + }, + "description": "Sisestage oma MQTT vahendaja \u00fchenduse teave." + }, + "options": { + "data": { + "birth_enable": "Luba loomisteavitus", + "birth_payload": "S\u00fcnniteate v\u00e4\u00e4rtus", + "birth_qos": "S\u00fcnniteate QoS", + "birth_retain": "S\u00fcnniteate j\u00e4\u00e4dvustamine", + "birth_topic": "S\u00fcnniteate teema", + "discovery": "Luba avastamine", + "will_enable": "Luba loomisteavitus", + "will_payload": "L\u00f5petamisteate v\u00e4\u00e4rtus", + "will_qos": "L\u00f5petamisteate QoS", + "will_retain": "L\u00f5petamisteate j\u00e4\u00e4dvustamine", + "will_topic": "L\u00f5petamisteade" + }, + "description": "Valige MQTT s\u00e4tted." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/mqtt/translations/it.json b/homeassistant/components/mqtt/translations/it.json index 3488c32391149e..845d0efabc787a 100644 --- a/homeassistant/components/mqtt/translations/it.json +++ b/homeassistant/components/mqtt/translations/it.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "single_instance_allowed": "\u00c8 consentita solo una singola configurazione di MQTT." + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "error": { - "cannot_connect": "Impossibile connettersi al broker." + "cannot_connect": "Impossibile connettersi" }, "step": { "broker": { @@ -52,7 +52,7 @@ "error": { "bad_birth": "Argomento birth non valido.", "bad_will": "Argomento will non valido.", - "cannot_connect": "Impossibile connettersi al broker." + "cannot_connect": "Impossibile connettersi" }, "step": { "broker": { @@ -72,7 +72,7 @@ "birth_retain": "Persistenza del messaggio birth", "birth_topic": "Argomento del messaggio birth", "discovery": "Attiva l'individuazione", - "will_enable": "Abilita messaggio di nascita", + "will_enable": "Abilita messaggio di ultima volont\u00e0 e testamento", "will_payload": "Payload del messaggio will", "will_qos": "QoS del messaggio will", "will_retain": "Persistenza del messaggio will", diff --git a/homeassistant/components/mqtt/translations/nl.json b/homeassistant/components/mqtt/translations/nl.json index 7953c744f2713b..2b8119aca4d341 100644 --- a/homeassistant/components/mqtt/translations/nl.json +++ b/homeassistant/components/mqtt/translations/nl.json @@ -34,7 +34,17 @@ "button_4": "Vierde knop", "button_5": "Vijfde knop", "button_6": "Zesde knop", - "turn_off": "Uitschakelen" + "turn_off": "Uitschakelen", + "turn_on": "Inschakelen" + } + }, + "options": { + "step": { + "broker": { + "data": { + "username": "Gebruikersnaam" + } + } } } } \ No newline at end of file diff --git a/homeassistant/components/mqtt/translations/no.json b/homeassistant/components/mqtt/translations/no.json index b1863b90d1c9fd..4f958bab3e14c0 100644 --- a/homeassistant/components/mqtt/translations/no.json +++ b/homeassistant/components/mqtt/translations/no.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "single_instance_allowed": "Kun en konfigurasjon av MQTT er tillatt." + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "error": { - "cannot_connect": "Kan ikke koble til megleren." + "cannot_connect": "Tilkobling mislyktes." }, "step": { "broker": { @@ -52,7 +52,7 @@ "error": { "bad_birth": "Ugyldig f\u00f8dselsemne.", "bad_will": "Ugyldig emne.", - "cannot_connect": "Kan ikke koble til megleren." + "cannot_connect": "Tilkobling mislyktes." }, "step": { "broker": { @@ -72,7 +72,7 @@ "birth_retain": "F\u00f8dselsmelding behold", "birth_topic": "F\u00f8dselsmelding emne", "discovery": "Aktiver oppdagelse", - "will_enable": "Aktiver f\u00f8dselsmelding", + "will_enable": "Aktiver will melding", "will_payload": "Testament melding nyttelast", "will_qos": "Testament melding QoS", "will_retain": "Testament melding behold", diff --git a/homeassistant/components/mqtt/translations/pl.json b/homeassistant/components/mqtt/translations/pl.json index 92c6d49e6773f3..13984b2bd5976d 100644 --- a/homeassistant/components/mqtt/translations/pl.json +++ b/homeassistant/components/mqtt/translations/pl.json @@ -66,11 +66,13 @@ }, "options": { "data": { + "birth_enable": "W\u0142\u0105cz wiadomo\u015b\u0107 \"birth\"", "birth_payload": "Warto\u015b\u0107 wiadomo\u015bci \"birth\"", "birth_qos": "QoS wiadomo\u015bci \"birth\"", "birth_retain": "Flaga \"retain\" wiadomo\u015bci \"birth\"", "birth_topic": "Temat wiadomo\u015bci \"birth\"", "discovery": "W\u0142\u0105cz wykrywanie", + "will_enable": "W\u0142\u0105cz wiadomo\u015b\u0107 \"will\"", "will_payload": "Warto\u015b\u0107 wiadomo\u015bci \"will\"", "will_qos": "QoS wiadomo\u015bci \"will\"", "will_retain": "Flaga \"retain\" wiadomo\u015bci \"will\"", diff --git a/homeassistant/components/mqtt/translations/ru.json b/homeassistant/components/mqtt/translations/ru.json index 4ff21126cfaad6..bfac4cadecc9ff 100644 --- a/homeassistant/components/mqtt/translations/ru.json +++ b/homeassistant/components/mqtt/translations/ru.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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 \u043a \u0431\u0440\u043e\u043a\u0435\u0440\u0443." + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." }, "step": { "broker": { @@ -52,7 +52,7 @@ "error": { "bad_birth": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0442\u043e\u043f\u0438\u043a \u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438.", "bad_will": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0442\u043e\u043f\u0438\u043a \u043e\u0431 \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\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 \u043a \u0431\u0440\u043e\u043a\u0435\u0440\u0443" + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." }, "step": { "broker": { @@ -72,7 +72,7 @@ "birth_retain": "\u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u0442\u043e\u043f\u0438\u043a \u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438", "birth_topic": "\u0422\u043e\u043f\u0438\u043a \u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 (LWT)", "discovery": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u0435", - "will_enable": "\u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0442\u043e\u043f\u0438\u043a \u043e\u0431 \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438", + "will_enable": "\u041e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0442\u044c \u0442\u043e\u043f\u0438\u043a \u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438", "will_payload": "\u0417\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0442\u043e\u043f\u0438\u043a\u0430 \u043e\u0431 \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438", "will_qos": "QoS \u0442\u043e\u043f\u0438\u043a\u0430 \u043e\u0431 \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438", "will_retain": "\u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u0442\u043e\u043f\u0438\u043a \u043e\u0431 \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438", diff --git a/homeassistant/components/mqtt/translations/zh-Hant.json b/homeassistant/components/mqtt/translations/zh-Hant.json index 02978a22327122..de92aee31717ed 100644 --- a/homeassistant/components/mqtt/translations/zh-Hant.json +++ b/homeassistant/components/mqtt/translations/zh-Hant.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "single_instance_allowed": "\u50c5\u5141\u8a31\u8a2d\u5b9a\u4e00\u7d44 MQTT\u3002" + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002" }, "error": { - "cannot_connect": "\u7121\u6cd5\u9023\u7dda\u81f3 Broker\u3002" + "cannot_connect": "\u9023\u7dda\u5931\u6557" }, "step": { "broker": { @@ -52,7 +52,7 @@ "error": { "bad_birth": "Birth \u4e3b\u984c\u7121\u6548\u3002", "bad_will": "Will \u4e3b\u984c\u7121\u6548\u3002", - "cannot_connect": "\u7121\u6cd5\u9023\u7dda\u81f3 Broker\u3002" + "cannot_connect": "\u9023\u7dda\u5931\u6557" }, "step": { "broker": { @@ -72,7 +72,7 @@ "birth_retain": "Birth \u8a0a\u606f Retain", "birth_topic": "Birth \u8a0a\u606f\u4e3b\u984c", "discovery": "\u958b\u555f\u63a2\u7d22", - "will_enable": "\u958b\u555f Birth \u8a0a\u606f", + "will_enable": "\u958b\u555f Will \u8a0a\u606f", "will_payload": "Will \u8a0a\u606f payload", "will_qos": "Will \u8a0a\u606f QoS", "will_retain": "Will \u8a0a\u606f Retain", diff --git a/homeassistant/components/myq/strings.json b/homeassistant/components/myq/strings.json index 566b6a0fdacc7c..19717907b0ffa2 100644 --- a/homeassistant/components/myq/strings.json +++ b/homeassistant/components/myq/strings.json @@ -10,12 +10,12 @@ } }, "error": { - "cannot_connect": "Failed to connect, please try again", - "invalid_auth": "Invalid authentication", - "unknown": "Unexpected 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": "MyQ is already configured" + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/myq/translations/ca.json b/homeassistant/components/myq/translations/ca.json index d22f3a39085e25..2b6549586a40be 100644 --- a/homeassistant/components/myq/translations/ca.json +++ b/homeassistant/components/myq/translations/ca.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "MyQ ja est\u00e0 configurat" + "already_configured": "El servei ja est\u00e0 configurat" }, "error": { - "cannot_connect": "No s'ha pogut connectar, torna-ho a provar", + "cannot_connect": "Ha fallat la connexi\u00f3", "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "unknown": "Error inesperat" }, diff --git a/homeassistant/components/myq/translations/en.json b/homeassistant/components/myq/translations/en.json index 54392f3e2935de..9dad2d10cad481 100644 --- a/homeassistant/components/myq/translations/en.json +++ b/homeassistant/components/myq/translations/en.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "MyQ is already configured" + "already_configured": "Service is already configured" }, "error": { - "cannot_connect": "Failed to connect, please try again", + "cannot_connect": "Failed to connect", "invalid_auth": "Invalid authentication", "unknown": "Unexpected error" }, diff --git a/homeassistant/components/myq/translations/et.json b/homeassistant/components/myq/translations/et.json new file mode 100644 index 00000000000000..0d70cd06fcaa70 --- /dev/null +++ b/homeassistant/components/myq/translations/et.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00dchenduse loomine nurjus. Proovi uuesti", + "unknown": "Ootamatu t\u00f5rge" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/myq/translations/it.json b/homeassistant/components/myq/translations/it.json index 9e0484dd6bb640..ac793e62c6d2b9 100644 --- a/homeassistant/components/myq/translations/it.json +++ b/homeassistant/components/myq/translations/it.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "MyQ \u00e8 gi\u00e0 configurato" + "already_configured": "Il servizio \u00e8 gi\u00e0 configurato" }, "error": { - "cannot_connect": "Impossibile connettersi, si prega di riprovare", + "cannot_connect": "Impossibile connettersi", "invalid_auth": "Autenticazione non valida", "unknown": "Errore imprevisto" }, diff --git a/homeassistant/components/myq/translations/no.json b/homeassistant/components/myq/translations/no.json index 4db38e2b91fa89..1df4d0b60008f6 100644 --- a/homeassistant/components/myq/translations/no.json +++ b/homeassistant/components/myq/translations/no.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "MyQ er allerede konfigurert" + "already_configured": "Tjenesten er allerede konfigurert" }, "error": { - "cannot_connect": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen", + "cannot_connect": "Tilkobling mislyktes.", "invalid_auth": "Ugyldig godkjenning", "unknown": "Uventet feil" }, diff --git a/homeassistant/components/myq/translations/pl.json b/homeassistant/components/myq/translations/pl.json index aefc8336903b36..d01f9e588fa8ce 100644 --- a/homeassistant/components/myq/translations/pl.json +++ b/homeassistant/components/myq/translations/pl.json @@ -5,8 +5,8 @@ }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "invalid_auth": "Niepoprawne uwierzytelnienie.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "user": { diff --git a/homeassistant/components/myq/translations/ru.json b/homeassistant/components/myq/translations/ru.json index 81ec65f0d63864..daa3148beef481 100644 --- a/homeassistant/components/myq/translations/ru.json +++ b/homeassistant/components/myq/translations/ru.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", + "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "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/mysensors/binary_sensor.py b/homeassistant/components/mysensors/binary_sensor.py index 0bab6ea6eeae0f..4ec3c6e0abd65c 100644 --- a/homeassistant/components/mysensors/binary_sensor.py +++ b/homeassistant/components/mysensors/binary_sensor.py @@ -1,6 +1,11 @@ """Support for MySensors binary sensors.""" from homeassistant.components import mysensors from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_MOISTURE, + DEVICE_CLASS_MOTION, + DEVICE_CLASS_SAFETY, + DEVICE_CLASS_SOUND, + DEVICE_CLASS_VIBRATION, DEVICE_CLASSES, DOMAIN, BinarySensorEntity, @@ -9,13 +14,13 @@ SENSORS = { "S_DOOR": "door", - "S_MOTION": "motion", + "S_MOTION": DEVICE_CLASS_MOTION, "S_SMOKE": "smoke", - "S_SPRINKLER": "safety", - "S_WATER_LEAK": "safety", - "S_SOUND": "sound", - "S_VIBRATION": "vibration", - "S_MOISTURE": "moisture", + "S_SPRINKLER": DEVICE_CLASS_SAFETY, + "S_WATER_LEAK": DEVICE_CLASS_SAFETY, + "S_SOUND": DEVICE_CLASS_SOUND, + "S_VIBRATION": DEVICE_CLASS_VIBRATION, + "S_MOISTURE": DEVICE_CLASS_MOISTURE, } diff --git a/homeassistant/components/mysensors/sensor.py b/homeassistant/components/mysensors/sensor.py index 8ff2139a7b42c4..6a6e95ddd0106b 100644 --- a/homeassistant/components/mysensors/sensor.py +++ b/homeassistant/components/mysensors/sensor.py @@ -9,12 +9,14 @@ ENERGY_KILO_WATT_HOUR, FREQUENCY_HERTZ, LENGTH_METERS, + LIGHT_LUX, MASS_KILOGRAMS, PERCENTAGE, POWER_WATT, TEMP_CELSIUS, TEMP_FAHRENHEIT, VOLT, + VOLUME_CUBIC_METERS, ) SENSORS = { @@ -36,11 +38,11 @@ "V_KWH": [ENERGY_KILO_WATT_HOUR, None], "V_LIGHT_LEVEL": [PERCENTAGE, "mdi:white-balance-sunny"], "V_FLOW": [LENGTH_METERS, "mdi:gauge"], - "V_VOLUME": ["m³", None], + "V_VOLUME": [f"{VOLUME_CUBIC_METERS}", None], "V_LEVEL": { "S_SOUND": ["dB", "mdi:volume-high"], "S_VIBRATION": [FREQUENCY_HERTZ, None], - "S_LIGHT_LEVEL": ["lx", "mdi:white-balance-sunny"], + "S_LIGHT_LEVEL": [LIGHT_LUX, "mdi:white-balance-sunny"], }, "V_VOLTAGE": [VOLT, "mdi:flash"], "V_CURRENT": [ELECTRICAL_CURRENT_AMPERE, "mdi:flash-auto"], diff --git a/homeassistant/components/neato/config_flow.py b/homeassistant/components/neato/config_flow.py index 88a2085b33968f..f74364bc8bc38d 100644 --- a/homeassistant/components/neato/config_flow.py +++ b/homeassistant/components/neato/config_flow.py @@ -105,8 +105,8 @@ def try_login(username, password, vendor): try: Account(username, password, this_vendor) except NeatoLoginException: - return "invalid_credentials" + return "invalid_auth" except NeatoRobotException: - return "unexpected_error" + return "unknown" return None diff --git a/homeassistant/components/neato/strings.json b/homeassistant/components/neato/strings.json index 024a30a8e2d97a..5d71d4889acc04 100644 --- a/homeassistant/components/neato/strings.json +++ b/homeassistant/components/neato/strings.json @@ -12,15 +12,15 @@ } }, "error": { - "invalid_credentials": "Invalid credentials", - "unexpected_error": "Unexpected error" + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "unknown": "[%key:common::config_flow::error::unknown%]" }, "create_entry": { "default": "See [Neato documentation]({docs_url})." }, "abort": { - "already_configured": "Already configured", - "invalid_credentials": "Invalid credentials" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" } } } \ No newline at end of file diff --git a/homeassistant/components/neato/translations/ca.json b/homeassistant/components/neato/translations/ca.json index d1cfe9f7a911a4..c3d3426eb99a30 100644 --- a/homeassistant/components/neato/translations/ca.json +++ b/homeassistant/components/neato/translations/ca.json @@ -1,15 +1,18 @@ { "config": { "abort": { - "already_configured": "Ja configurat", + "already_configured": "El dispositiu ja est\u00e0 configurat", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "invalid_credentials": "Credencials inv\u00e0lides" }, "create_entry": { "default": "Consulta la [documentaci\u00f3 de Neato]({docs_url})." }, "error": { + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "invalid_credentials": "Credencials inv\u00e0lides", - "unexpected_error": "Error inesperat" + "unexpected_error": "Error inesperat", + "unknown": "Error inesperat" }, "step": { "user": { diff --git a/homeassistant/components/neato/translations/el.json b/homeassistant/components/neato/translations/el.json new file mode 100644 index 00000000000000..36bd6653da6d43 --- /dev/null +++ b/homeassistant/components/neato/translations/el.json @@ -0,0 +1,11 @@ +{ + "config": { + "abort": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b1\u03c5\u03b8\u03b5\u03bd\u03c4\u03b9\u03ba\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7" + }, + "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b1\u03c5\u03b8\u03b5\u03bd\u03c4\u03b9\u03ba\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", + "unknown": "\u039c\u03b7 \u03b1\u03bd\u03b1\u03bc\u03b5\u03bd\u03cc\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/neato/translations/en.json b/homeassistant/components/neato/translations/en.json index d41faddcc665ef..5cc832f39b759b 100644 --- a/homeassistant/components/neato/translations/en.json +++ b/homeassistant/components/neato/translations/en.json @@ -1,15 +1,18 @@ { "config": { "abort": { - "already_configured": "Already configured", + "already_configured": "Device is already configured", + "invalid_auth": "Invalid authentication", "invalid_credentials": "Invalid credentials" }, "create_entry": { "default": "See [Neato documentation]({docs_url})." }, "error": { + "invalid_auth": "Invalid authentication", "invalid_credentials": "Invalid credentials", - "unexpected_error": "Unexpected error" + "unexpected_error": "Unexpected error", + "unknown": "Unexpected error" }, "step": { "user": { diff --git a/homeassistant/components/neato/translations/es.json b/homeassistant/components/neato/translations/es.json index 766f05d31bf76d..13dbe19844c0ee 100644 --- a/homeassistant/components/neato/translations/es.json +++ b/homeassistant/components/neato/translations/es.json @@ -2,14 +2,17 @@ "config": { "abort": { "already_configured": "Ya est\u00e1 configurado", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", "invalid_credentials": "Credenciales no v\u00e1lidas" }, "create_entry": { "default": "Ver [documentaci\u00f3n Neato]({docs_url})." }, "error": { + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", "invalid_credentials": "Credenciales no v\u00e1lidas", - "unexpected_error": "Error inesperado" + "unexpected_error": "Error inesperado", + "unknown": "Error inesperado" }, "step": { "user": { diff --git a/homeassistant/components/neato/translations/et.json b/homeassistant/components/neato/translations/et.json new file mode 100644 index 00000000000000..4026f703b7ba9e --- /dev/null +++ b/homeassistant/components/neato/translations/et.json @@ -0,0 +1,21 @@ +{ + "config": { + "abort": { + "invalid_auth": "Tuvastamise viga" + }, + "error": { + "invalid_auth": "Tuvastamise viga", + "unexpected_error": "Ootamatu t\u00f5rge", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "password": "Salas\u00f5na", + "username": "Kasutajanimi", + "vendor": "Tootja" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/neato/translations/fr.json b/homeassistant/components/neato/translations/fr.json index 66f1bb52804300..11c06cd1c97b8b 100644 --- a/homeassistant/components/neato/translations/fr.json +++ b/homeassistant/components/neato/translations/fr.json @@ -2,14 +2,17 @@ "config": { "abort": { "already_configured": "D\u00e9j\u00e0 configur\u00e9", + "invalid_auth": "Authentification invalide", "invalid_credentials": "Informations d'identification invalides" }, "create_entry": { "default": "Voir [Documentation Neato]({docs_url})." }, "error": { + "invalid_auth": "Authentification invalide", "invalid_credentials": "Informations d'identification invalides", - "unexpected_error": "Erreur inattendue" + "unexpected_error": "Erreur inattendue", + "unknown": "Erreur inattendue" }, "step": { "user": { diff --git a/homeassistant/components/neato/translations/it.json b/homeassistant/components/neato/translations/it.json index a456a2688e0225..654862f02594a8 100644 --- a/homeassistant/components/neato/translations/it.json +++ b/homeassistant/components/neato/translations/it.json @@ -1,15 +1,18 @@ { "config": { "abort": { - "already_configured": "Gi\u00e0 configurato", + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "invalid_auth": "Autenticazione non valida", "invalid_credentials": "Credenziali non valide" }, "create_entry": { "default": "Vedere la [Documentazione di Neato]({docs_url})." }, "error": { + "invalid_auth": "Autenticazione non valida", "invalid_credentials": "Credenziali non valide", - "unexpected_error": "Errore inaspettato" + "unexpected_error": "Errore inaspettato", + "unknown": "Errore imprevisto" }, "step": { "user": { diff --git a/homeassistant/components/neato/translations/no.json b/homeassistant/components/neato/translations/no.json index de8932cd570c93..57116666454ab3 100644 --- a/homeassistant/components/neato/translations/no.json +++ b/homeassistant/components/neato/translations/no.json @@ -1,15 +1,18 @@ { "config": { "abort": { - "already_configured": "Allerede konfigurert", + "already_configured": "Enheten er allerede konfigurert", + "invalid_auth": "Ugyldig godkjenning", "invalid_credentials": "Ugyldig legitimasjon" }, "create_entry": { "default": "Se [Neato dokumentasjon]({docs_url})." }, "error": { + "invalid_auth": "Ugyldig godkjenning", "invalid_credentials": "Ugyldig legitimasjon", - "unexpected_error": "Uventet feil" + "unexpected_error": "Uventet feil", + "unknown": "Uventet feil" }, "step": { "user": { diff --git a/homeassistant/components/neato/translations/pl.json b/homeassistant/components/neato/translations/pl.json index 80e0a1df48ef11..821cf79c971596 100644 --- a/homeassistant/components/neato/translations/pl.json +++ b/homeassistant/components/neato/translations/pl.json @@ -9,7 +9,7 @@ }, "error": { "invalid_credentials": "Nieprawid\u0142owe dane uwierzytelniaj\u0105ce", - "unexpected_error": "Nieoczekiwany b\u0142\u0105d." + "unexpected_error": "Nieoczekiwany b\u0142\u0105d" }, "step": { "user": { diff --git a/homeassistant/components/neato/translations/ru.json b/homeassistant/components/neato/translations/ru.json index 92935906a865fe..cb899e4614a925 100644 --- a/homeassistant/components/neato/translations/ru.json +++ b/homeassistant/components/neato/translations/ru.json @@ -1,15 +1,18 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "invalid_credentials": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0435 \u0443\u0447\u0451\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435." }, "create_entry": { "default": "\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]({docs_url}) \u0434\u043b\u044f \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u043e\u0439 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438." }, "error": { + "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "invalid_credentials": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0435 \u0443\u0447\u0451\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435.", - "unexpected_error": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." + "unexpected_error": "\u041d\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": { "user": { diff --git a/homeassistant/components/neato/translations/zh-Hant.json b/homeassistant/components/neato/translations/zh-Hant.json index 8d8fe629030216..84d31b35e6d97e 100644 --- a/homeassistant/components/neato/translations/zh-Hant.json +++ b/homeassistant/components/neato/translations/zh-Hant.json @@ -1,15 +1,18 @@ { "config": { "abort": { - "already_configured": "\u5df2\u8a2d\u5b9a\u5b8c\u6210", + "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "invalid_credentials": "\u6191\u8b49\u7121\u6548" }, "create_entry": { "default": "\u8acb\u53c3\u95b1 [Neato \u6587\u4ef6]({docs_url})\u3002" }, "error": { + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "invalid_credentials": "\u6191\u8b49\u7121\u6548", - "unexpected_error": "\u672a\u9810\u671f\u932f\u8aa4" + "unexpected_error": "\u672a\u9810\u671f\u932f\u8aa4", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { "user": { diff --git a/homeassistant/components/nest/binary_sensor.py b/homeassistant/components/nest/binary_sensor.py index dd52e1d665fc2b..0e9198a0220d04 100644 --- a/homeassistant/components/nest/binary_sensor.py +++ b/homeassistant/components/nest/binary_sensor.py @@ -2,14 +2,20 @@ from itertools import chain import logging -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_CONNECTIVITY, + DEVICE_CLASS_MOTION, + DEVICE_CLASS_OCCUPANCY, + DEVICE_CLASS_SOUND, + BinarySensorEntity, +) from homeassistant.const import CONF_MONITORED_CONDITIONS from . import CONF_BINARY_SENSORS, DATA_NEST, DATA_NEST_CONFIG, NestSensorDevice _LOGGER = logging.getLogger(__name__) -BINARY_TYPES = {"online": "connectivity"} +BINARY_TYPES = {"online": DEVICE_CLASS_CONNECTIVITY} CLIMATE_BINARY_TYPES = { "fan": None, @@ -19,9 +25,9 @@ } CAMERA_BINARY_TYPES = { - "motion_detected": "motion", - "sound_detected": "sound", - "person_detected": "occupancy", + "motion_detected": DEVICE_CLASS_MOTION, + "sound_detected": DEVICE_CLASS_SOUND, + "person_detected": DEVICE_CLASS_OCCUPANCY, } STRUCTURE_BINARY_TYPES = {"away": None} @@ -153,7 +159,7 @@ def unique_id(self): @property def device_class(self): """Return the device class of the binary sensor.""" - return "motion" + return DEVICE_CLASS_MOTION def update(self): """Retrieve latest state.""" diff --git a/homeassistant/components/nest/local_auth.py b/homeassistant/components/nest/local_auth.py index 8b0af5011ec02b..df03c05439862b 100644 --- a/homeassistant/components/nest/local_auth.py +++ b/homeassistant/components/nest/local_auth.py @@ -4,6 +4,7 @@ from nest.nest import AUTHORIZE_URL, AuthorizationError, NestAuth +from homeassistant.const import HTTP_UNAUTHORIZED from homeassistant.core import callback from . import config_flow @@ -42,7 +43,7 @@ async def resolve_auth_code(hass, client_id, client_secret, code): await hass.async_add_job(auth.login) return await result except AuthorizationError as err: - if err.response.status_code == 401: + if err.response.status_code == HTTP_UNAUTHORIZED: raise config_flow.CodeInvalid() raise config_flow.NestAuthError( f"Unknown error: {err} ({err.response.status_code})" diff --git a/homeassistant/components/netatmo/camera.py b/homeassistant/components/netatmo/camera.py index 3f9720f3adb2b1..dff6013c7c6989 100644 --- a/homeassistant/components/netatmo/camera.py +++ b/homeassistant/components/netatmo/camera.py @@ -284,9 +284,9 @@ def async_update_callback(self): self._data.events.get(self._id, {}) ) elif self._model == "NOC": # Smart Outdoor Camera - self.hass.data[DOMAIN][DATA_EVENTS][ - self._id - ] = self._data.outdoor_events.get(self._id, {}) + self.hass.data[DOMAIN][DATA_EVENTS][self._id] = self.process_events( + self._data.outdoor_events.get(self._id, {}) + ) def process_events(self, events): """Add meta data to events.""" diff --git a/homeassistant/components/netatmo/climate.py b/homeassistant/components/netatmo/climate.py index f24591fe95467d..30ce38753c6d5e 100644 --- a/homeassistant/components/netatmo/climate.py +++ b/homeassistant/components/netatmo/climate.py @@ -315,7 +315,7 @@ def hvac_modes(self): @property def hvac_action(self) -> Optional[str]: """Return the current running hvac operation if supported.""" - if self._model == NA_THERM: + if self._model == NA_THERM and self._boilerstatus is not None: return CURRENT_HVAC_MAP_NETATMO[self._boilerstatus] # Maybe it is a valve if self._room_status and self._room_status.get("heating_power_request", 0) > 0: diff --git a/homeassistant/components/netatmo/media_source.py b/homeassistant/components/netatmo/media_source.py index 765276772242f7..6375c46d394f51 100644 --- a/homeassistant/components/netatmo/media_source.py +++ b/homeassistant/components/netatmo/media_source.py @@ -80,8 +80,20 @@ def _build_item_response( ) -> BrowseMediaSource: if event_id and event_id in self.events[camera_id]: created = dt.datetime.fromtimestamp(event_id) - thumbnail = self.events[camera_id][event_id].get("snapshot", {}).get("url") - message = remove_html_tags(self.events[camera_id][event_id]["message"]) + if self.events[camera_id][event_id]["type"] == "outdoor": + thumbnail = ( + self.events[camera_id][event_id]["event_list"][0] + .get("snapshot", {}) + .get("url") + ) + message = remove_html_tags( + self.events[camera_id][event_id]["event_list"][0]["message"] + ) + else: + thumbnail = ( + self.events[camera_id][event_id].get("snapshot", {}).get("url") + ) + message = remove_html_tags(self.events[camera_id][event_id]["message"]) title = f"{created} - {message}" else: title = self.hass.data[DOMAIN][DATA_CAMERAS].get(camera_id, MANUFACTURER) diff --git a/homeassistant/components/netatmo/sensor.py b/homeassistant/components/netatmo/sensor.py index 2368d54efdf560..dc61f3d68cf5cc 100644 --- a/homeassistant/components/netatmo/sensor.py +++ b/homeassistant/components/netatmo/sensor.py @@ -12,6 +12,7 @@ DEVICE_CLASS_PRESSURE, DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_TEMPERATURE, + LENGTH_MILLIMETERS, PERCENTAGE, PRESSURE_MBAR, SPEED_KILOMETERS_PER_HOUR, @@ -48,13 +49,15 @@ SENSOR_TYPES = { "temperature": ["Temperature", TEMP_CELSIUS, None, DEVICE_CLASS_TEMPERATURE], + "temp_trend": ["Temperature trend", None, "mdi:trending-up", None], "co2": ["CO2", CONCENTRATION_PARTS_PER_MILLION, "mdi:molecule-co2", None], "pressure": ["Pressure", PRESSURE_MBAR, None, DEVICE_CLASS_PRESSURE], + "pressure_trend": ["Pressure trend", None, "mdi:trending-up", None], "noise": ["Noise", "dB", "mdi:volume-high", None], "humidity": ["Humidity", PERCENTAGE, None, DEVICE_CLASS_HUMIDITY], - "rain": ["Rain", "mm", "mdi:weather-rainy", None], - "sum_rain_1": ["Rain last hour", "mm", "mdi:weather-rainy", None], - "sum_rain_24": ["Rain last 24h", "mm", "mdi:weather-rainy", None], + "rain": ["Rain", LENGTH_MILLIMETERS, "mdi:weather-rainy", None], + "sum_rain_1": ["Rain last hour", LENGTH_MILLIMETERS, "mdi:weather-rainy", None], + "sum_rain_24": ["Rain today", LENGTH_MILLIMETERS, "mdi:weather-rainy", None], "battery_vp": ["Battery", "", "mdi:battery", None], "battery_lvl": ["Battery Level", "", "mdi:battery", None], "battery_percent": ["Battery Percent", PERCENTAGE, None, DEVICE_CLASS_BATTERY], @@ -311,6 +314,8 @@ def async_update_callback(self): try: if self.type == "temperature": self._state = round(data["Temperature"], 1) + elif self.type == "temp_trend": + self._state = data["temp_trend"] elif self.type == "humidity": self._state = data["Humidity"] elif self.type == "rain": @@ -325,6 +330,8 @@ def async_update_callback(self): self._state = data["CO2"] elif self.type == "pressure": self._state = round(data["Pressure"], 1) + elif self.type == "pressure_trend": + self._state = data["pressure_trend"] elif self.type == "battery_percent": self._state = data["battery_percent"] elif self.type == "battery_lvl": @@ -336,15 +343,15 @@ def async_update_callback(self): elif self.type == "max_temp": self._state = data["max_temp"] elif self.type == "windangle_value": - self._state = data["WindAngle"] + self._state = fix_angle(data["WindAngle"]) elif self.type == "windangle": - self._state = process_angle(data["WindAngle"]) + self._state = process_angle(fix_angle(data["WindAngle"])) elif self.type == "windstrength": self._state = data["WindStrength"] elif self.type == "gustangle_value": - self._state = data["GustAngle"] + self._state = fix_angle(data["GustAngle"]) elif self.type == "gustangle": - self._state = process_angle(data["GustAngle"]) + self._state = process_angle(fix_angle(data["GustAngle"])) elif self.type == "guststrength": self._state = data["GustStrength"] elif self.type == "reachable": @@ -366,6 +373,13 @@ def async_update_callback(self): return +def fix_angle(angle: int) -> int: + """Fix angle when value is negative.""" + if angle < 0: + return 360 + angle + return angle + + def process_angle(angle: int) -> str: """Process angle and return string for display.""" if angle >= 330: diff --git a/homeassistant/components/netatmo/translations/es.json b/homeassistant/components/netatmo/translations/es.json index a72728e8438660..1ad29ab080ddd4 100644 --- a/homeassistant/components/netatmo/translations/es.json +++ b/homeassistant/components/netatmo/translations/es.json @@ -3,6 +3,7 @@ "abort": { "authorize_url_timeout": "Tiempo excedido generando la url de autorizaci\u00f3n.", "missing_configuration": "El componente no est\u00e1 configurado. Por favor, 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. S\u00f3lo es posible una \u00fanica configuraci\u00f3n." }, "create_entry": { diff --git a/homeassistant/components/netatmo/translations/et.json b/homeassistant/components/netatmo/translations/et.json new file mode 100644 index 00000000000000..cf0944dbe0e1c6 --- /dev/null +++ b/homeassistant/components/netatmo/translations/et.json @@ -0,0 +1,27 @@ +{ + "options": { + "step": { + "public_weather": { + "data": { + "area_name": "Ala nimi", + "lat_ne": "Kirdenurga laiuskraad", + "lat_sw": "Edelanurga laiuskraad", + "lon_ne": "Kirdenurga pikkuskraad", + "lon_sw": "Edelanurga pikkuskraad", + "mode": "Arvutamine", + "show_on_map": "Kuva kaardil" + }, + "description": "Seadista selle ala avalik ilmaandur.", + "title": "Netatmo avalik ilmaandur" + }, + "public_weather_areas": { + "data": { + "new_area": "Ala nimi", + "weather_areas": "Ilmaandmete alad" + }, + "description": "Seadista avalikke ilmastikuandureid.", + "title": "Netatmo avalik ilmaandur" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/netatmo/translations/fr.json b/homeassistant/components/netatmo/translations/fr.json index fd4072dc54ab46..fe8fc74d273473 100644 --- a/homeassistant/components/netatmo/translations/fr.json +++ b/homeassistant/components/netatmo/translations/fr.json @@ -3,6 +3,7 @@ "abort": { "authorize_url_timeout": "D\u00e9lai de g\u00e9n\u00e9ration de l'URL d'authentification d\u00e9pass\u00e9.", "missing_configuration": "Ce 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} )", "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." }, "create_entry": { diff --git a/homeassistant/components/netatmo/translations/ko.json b/homeassistant/components/netatmo/translations/ko.json index f8c052bd5f8aff..8165941f0d8fa1 100644 --- a/homeassistant/components/netatmo/translations/ko.json +++ b/homeassistant/components/netatmo/translations/ko.json @@ -2,7 +2,8 @@ "config": { "abort": { "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." + "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": "\uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc5d0\ub7ec\uc5d0 \ub300\ud55c \uc815\ubcf4\ub294 \ub3c4\uc6c0\ub9d0 \uc139\uc158\uc744 \ud655\uc778\ud558\uc138\uc694({docs_url})" }, "create_entry": { "default": "\uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4" diff --git a/homeassistant/components/netatmo/translations/lb.json b/homeassistant/components/netatmo/translations/lb.json index f46b8627e0c16f..6f71b98fd936cd 100644 --- a/homeassistant/components/netatmo/translations/lb.json +++ b/homeassistant/components/netatmo/translations/lb.json @@ -3,6 +3,7 @@ "abort": { "authorize_url_timeout": "Z\u00e4itiwwerschreidung beim erstellen vun der Authorisatiouns URL", "missing_configuration": "D\u00ebs Komponent ass net konfigur\u00e9iert. Folleg w.e.g der Dokumentatioun.", + "no_url_available": "Keng URL disponibel. Fir Informatiounen iwwert d\u00ebse Feeler, [kuck H\u00ebllef Sektioun]({docs_url})", "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun m\u00e9iglech." }, "create_entry": { @@ -28,7 +29,8 @@ "data": { "new_area": "Numm vum Ber\u00e4ich", "weather_areas": "Wieder Ber\u00e4icher" - } + }, + "title": "Netatmo public weather sensor" } } } diff --git a/homeassistant/components/netatmo/translations/no.json b/homeassistant/components/netatmo/translations/no.json index 5cc1d400719afc..98e3206f46a830 100644 --- a/homeassistant/components/netatmo/translations/no.json +++ b/homeassistant/components/netatmo/translations/no.json @@ -3,6 +3,7 @@ "abort": { "authorize_url_timeout": "Tidsavbrutt ved oppretting av godkjennings url.", "missing_configuration": "Komponeneten er ikke konfigurert. Vennligst f\u00f8lg dokumentasjonen.", + "no_url_available": "Ingen URL tilgjengelig. For informasjon om denne feilen, [sjekk {docs_url} ] ( {docs_url} )", "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "create_entry": { diff --git a/homeassistant/components/netatmo/translations/pl.json b/homeassistant/components/netatmo/translations/pl.json index 8a549e4cd3070b..721c3a2a946a2b 100644 --- a/homeassistant/components/netatmo/translations/pl.json +++ b/homeassistant/components/netatmo/translations/pl.json @@ -2,7 +2,8 @@ "config": { "abort": { "authorize_url_timeout": "Przekroczono limit czasu generowania URL autoryzacji.", - "missing_configuration": "Komponent nie jest skonfigurowany. Post\u0119puj zgodnie z dokumentacj\u0105." + "missing_configuration": "Komponent nie jest skonfigurowany. Post\u0119puj zgodnie z dokumentacj\u0105.", + "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." }, "create_entry": { "default": "Pomy\u015blnie uwierzytelniono" diff --git a/homeassistant/components/netatmo/translations/zh-Hant.json b/homeassistant/components/netatmo/translations/zh-Hant.json index fd528ed4cfcf44..588675c670e734 100644 --- a/homeassistant/components/netatmo/translations/zh-Hant.json +++ b/homeassistant/components/netatmo/translations/zh-Hant.json @@ -3,6 +3,7 @@ "abort": { "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})", "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002" }, "create_entry": { diff --git a/homeassistant/components/netgear_lte/sensor_types.py b/homeassistant/components/netgear_lte/sensor_types.py index e354c84e715dfc..644fe35c8c3eb3 100644 --- a/homeassistant/components/netgear_lte/sensor_types.py +++ b/homeassistant/components/netgear_lte/sensor_types.py @@ -1,7 +1,11 @@ """Define possible sensor types.""" from homeassistant.components.binary_sensor import DEVICE_CLASS_CONNECTIVITY -from homeassistant.const import DATA_MEBIBYTES, PERCENTAGE +from homeassistant.const import ( + DATA_MEBIBYTES, + PERCENTAGE, + SIGNAL_STRENGTH_DECIBELS_MILLIWATT, +) SENSOR_SMS = "sms" SENSOR_SMS_TOTAL = "sms_total" @@ -12,8 +16,8 @@ SENSOR_SMS_TOTAL: "messages", SENSOR_USAGE: DATA_MEBIBYTES, "radio_quality": PERCENTAGE, - "rx_level": "dBm", - "tx_level": "dBm", + "rx_level": SIGNAL_STRENGTH_DECIBELS_MILLIWATT, + "tx_level": SIGNAL_STRENGTH_DECIBELS_MILLIWATT, "upstream": None, "connection_text": None, "connection_type": None, diff --git a/homeassistant/components/nexia/strings.json b/homeassistant/components/nexia/strings.json index dcfb40b898ac65..876ea2d656f11b 100644 --- a/homeassistant/components/nexia/strings.json +++ b/homeassistant/components/nexia/strings.json @@ -10,12 +10,12 @@ } }, "error": { - "cannot_connect": "Failed to connect, please try again", - "invalid_auth": "Invalid authentication", - "unknown": "Unexpected 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": "This nexia home is already configured" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/nexia/translations/ca.json b/homeassistant/components/nexia/translations/ca.json index fcbb879548cb4f..beb5b1a1d9dcfe 100644 --- a/homeassistant/components/nexia/translations/ca.json +++ b/homeassistant/components/nexia/translations/ca.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Aquest dispositiu nexia home ja est\u00e0 configurat" + "already_configured": "El dispositiu ja est\u00e0 configurat" }, "error": { - "cannot_connect": "No s'ha pogut connectar, torna-ho a provar", + "cannot_connect": "Ha fallat la connexi\u00f3", "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "unknown": "Error inesperat" }, diff --git a/homeassistant/components/nexia/translations/en.json b/homeassistant/components/nexia/translations/en.json index 016518495dcdde..fad0b8e542a568 100644 --- a/homeassistant/components/nexia/translations/en.json +++ b/homeassistant/components/nexia/translations/en.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "This nexia home is already configured" + "already_configured": "Device is already configured" }, "error": { - "cannot_connect": "Failed to connect, please try again", + "cannot_connect": "Failed to connect", "invalid_auth": "Invalid authentication", "unknown": "Unexpected error" }, diff --git a/homeassistant/components/nexia/translations/et.json b/homeassistant/components/nexia/translations/et.json new file mode 100644 index 00000000000000..0d70cd06fcaa70 --- /dev/null +++ b/homeassistant/components/nexia/translations/et.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00dchenduse loomine nurjus. Proovi uuesti", + "unknown": "Ootamatu t\u00f5rge" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nexia/translations/it.json b/homeassistant/components/nexia/translations/it.json index 034aff83ecdab1..254617d718be90 100644 --- a/homeassistant/components/nexia/translations/it.json +++ b/homeassistant/components/nexia/translations/it.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Questo Nexia Home \u00e8 gi\u00e0 configurato" + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" }, "error": { - "cannot_connect": "Impossibile connettersi, si prega di riprovare", + "cannot_connect": "Impossibile connettersi", "invalid_auth": "Autenticazione non valida", "unknown": "Errore imprevisto" }, diff --git a/homeassistant/components/nexia/translations/no.json b/homeassistant/components/nexia/translations/no.json index 335d8834ec388a..dc4ed03f46e451 100644 --- a/homeassistant/components/nexia/translations/no.json +++ b/homeassistant/components/nexia/translations/no.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Dette nexia hjem er allerede konfigurert" + "already_configured": "Enheten er allerede konfigurert" }, "error": { - "cannot_connect": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen", + "cannot_connect": "Tilkobling mislyktes.", "invalid_auth": "Ugyldig godkjenning", "unknown": "Uventet feil" }, diff --git a/homeassistant/components/nexia/translations/pl.json b/homeassistant/components/nexia/translations/pl.json index 84844ddbd9483b..7f09569418d373 100644 --- a/homeassistant/components/nexia/translations/pl.json +++ b/homeassistant/components/nexia/translations/pl.json @@ -5,8 +5,8 @@ }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "invalid_auth": "Niepoprawne uwierzytelnienie.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "user": { diff --git a/homeassistant/components/nexia/translations/ru.json b/homeassistant/components/nexia/translations/ru.json index 1dd3be26fd13f7..a19d16e3a7e696 100644 --- a/homeassistant/components/nexia/translations/ru.json +++ b/homeassistant/components/nexia/translations/ru.json @@ -1,10 +1,10 @@ { "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_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, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", + "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "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/nextcloud/__init__.py b/homeassistant/components/nextcloud/__init__.py index 12c17e6081d5bf..1a773040980781 100644 --- a/homeassistant/components/nextcloud/__init__.py +++ b/homeassistant/components/nextcloud/__init__.py @@ -111,6 +111,7 @@ def nextcloud_update(event_time): return False hass.data[DOMAIN] = get_data_points(ncm.data) + hass.data[DOMAIN]["instance"] = conf[CONF_URL] # Update sensors on time interval track_time_interval(hass, nextcloud_update, conf[CONF_SCAN_INTERVAL]) diff --git a/homeassistant/components/nfandroidtv/notify.py b/homeassistant/components/nfandroidtv/notify.py index 369a5c875accef..e71d81f2b79048 100644 --- a/homeassistant/components/nfandroidtv/notify.py +++ b/homeassistant/components/nfandroidtv/notify.py @@ -158,26 +158,26 @@ def send_message(self, message="", **kwargs): """Send a message to a Android TV device.""" _LOGGER.debug("Sending notification to: %s", self._target) - payload = dict( - filename=( + payload = { + "filename": ( "icon.png", self._icon_file, "application/octet-stream", {"Expires": "0"}, ), - type="0", - title=kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT), - msg=message, - duration="%i" % self._default_duration, - fontsize="%i" % FONTSIZES.get(self._default_fontsize), - position="%i" % POSITIONS.get(self._default_position), - bkgcolor="%s" % COLORS.get(self._default_color), - transparency="%i" % TRANSPARENCIES.get(self._default_transparency), - offset="0", - app=ATTR_TITLE_DEFAULT, - force="true", - interrupt="%i" % self._default_interrupt, - ) + "type": "0", + "title": kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT), + "msg": message, + "duration": "%i" % self._default_duration, + "fontsize": "%i" % FONTSIZES.get(self._default_fontsize), + "position": "%i" % POSITIONS.get(self._default_position), + "bkgcolor": "%s" % COLORS.get(self._default_color), + "transparency": "%i" % TRANSPARENCIES.get(self._default_transparency), + "offset": "0", + "app": ATTR_TITLE_DEFAULT, + "force": "true", + "interrupt": "%i" % self._default_interrupt, + } data = kwargs.get(ATTR_DATA) if data: diff --git a/homeassistant/components/nightscout/__init__.py b/homeassistant/components/nightscout/__init__.py index 88939cbe7906a0..1b67963bcc3985 100644 --- a/homeassistant/components/nightscout/__init__.py +++ b/homeassistant/components/nightscout/__init__.py @@ -7,7 +7,7 @@ from py_nightscout import Api as NightscoutAPI from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_URL +from homeassistant.const import CONF_API_KEY, CONF_URL from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry as dr @@ -30,8 +30,9 @@ async def async_setup(hass: HomeAssistant, config: dict): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up Nightscout from a config entry.""" server_url = entry.data[CONF_URL] + api_key = entry.data.get(CONF_API_KEY) session = async_get_clientsession(hass) - api = NightscoutAPI(server_url, session=session) + api = NightscoutAPI(server_url, session=session, api_secret=api_key) try: status = await api.get_server_status() except (ClientError, AsyncIOTimeoutError, OSError) as error: diff --git a/homeassistant/components/nightscout/config_flow.py b/homeassistant/components/nightscout/config_flow.py index bd33bc8dcb4d24..3000d652e46f87 100644 --- a/homeassistant/components/nightscout/config_flow.py +++ b/homeassistant/components/nightscout/config_flow.py @@ -2,27 +2,32 @@ from asyncio import TimeoutError as AsyncIOTimeoutError import logging -from aiohttp import ClientError +from aiohttp import ClientError, ClientResponseError from py_nightscout import Api as NightscoutAPI import voluptuous as vol from homeassistant import config_entries, exceptions -from homeassistant.const import CONF_URL +from homeassistant.const import CONF_API_KEY, CONF_URL from .const import DOMAIN # pylint:disable=unused-import from .utils import hash_from_url _LOGGER = logging.getLogger(__name__) -DATA_SCHEMA = vol.Schema({vol.Required(CONF_URL): str}) +DATA_SCHEMA = vol.Schema({vol.Required(CONF_URL): str, vol.Optional(CONF_API_KEY): str}) async def _validate_input(data): """Validate the user input allows us to connect.""" url = data[CONF_URL] + api_key = data.get(CONF_API_KEY) try: - api = NightscoutAPI(url) + api = NightscoutAPI(url, api_secret=api_key) status = await api.get_server_status() + if status.settings.get("authDefaultRoles") == "status-only": + await api.get_sgvs() + except ClientResponseError as error: + raise InputValidationError("invalid_auth") from error except (ClientError, AsyncIOTimeoutError, OSError) as error: raise InputValidationError("cannot_connect") from error diff --git a/homeassistant/components/nightscout/manifest.json b/homeassistant/components/nightscout/manifest.json index b3e9b3a0d555d4..ecc44258e907ff 100644 --- a/homeassistant/components/nightscout/manifest.json +++ b/homeassistant/components/nightscout/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/nightscout", "requirements": [ - "py-nightscout==1.2.1" + "py-nightscout==1.2.2" ], "codeowners": [ "@marciogranzotto" diff --git a/homeassistant/components/nightscout/strings.json b/homeassistant/components/nightscout/strings.json index a6e100ae8f29fc..2240bcec02b571 100644 --- a/homeassistant/components/nightscout/strings.json +++ b/homeassistant/components/nightscout/strings.json @@ -3,12 +3,16 @@ "flow_title": "Nightscout", "step": { "user": { + "title": "Enter your Nightscout server information.", + "description": "- URL: the address of your nightscout instance. I.e.: https://myhomeassistant.duckdns.org:5423\n- API Key (Optional): Only use if your instance is protected (auth_default_roles != readable).", "data": { - "url": "URL" + "url": "[%key:common::config_flow::data::url%]", + "api_key": "[%key:common::config_flow::data::api_key%]" } } }, "error": { + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "unknown": "[%key:common::config_flow::error::unknown%]" }, @@ -16,4 +20,4 @@ "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/nightscout/translations/ca.json b/homeassistant/components/nightscout/translations/ca.json index eb06d94de3b396..21a472680b4aa9 100644 --- a/homeassistant/components/nightscout/translations/ca.json +++ b/homeassistant/components/nightscout/translations/ca.json @@ -5,14 +5,18 @@ }, "error": { "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "unknown": "Error inesperat" }, "flow_title": "Nightscout", "step": { "user": { "data": { + "api_key": "Clau API", "url": "URL" - } + }, + "description": "- URL: l'adre\u00e7a de la teva inst\u00e0ncia de Nightscout. Per exemple: https://myhomeassistant.duckdns.org:5423 \n- Clau API (opcional): utilitza-la nom\u00e9s si la teva inst\u00e0ncia est\u00e0 protegida (auth_default_roles != readable).", + "title": "Introdueix la informaci\u00f3 del teu servidor Nightscout." } } } diff --git a/homeassistant/components/nightscout/translations/de.json b/homeassistant/components/nightscout/translations/de.json new file mode 100644 index 00000000000000..a7ad0fe1d27c43 --- /dev/null +++ b/homeassistant/components/nightscout/translations/de.json @@ -0,0 +1,12 @@ +{ + "config": { + "flow_title": "Nightscout", + "step": { + "user": { + "data": { + "url": "URL" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nightscout/translations/el.json b/homeassistant/components/nightscout/translations/el.json new file mode 100644 index 00000000000000..42762bfddce8c9 --- /dev/null +++ b/homeassistant/components/nightscout/translations/el.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "user": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API" + }, + "description": "- \u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 URL: \u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03c0\u03b1\u03c1\u03bf\u03c5\u03c3\u03af\u03b1\u03c2 \u03c4\u03bf\u03c5 nightcout. \u0394\u03b7\u03bb\u03b1\u03b4\u03ae: https://myhomeassistant.duckdns.org:5423\n - \u039a\u03bb\u03b5\u03b9\u03b4\u03af API (\u03a0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc): \u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 \u03bc\u03cc\u03bd\u03bf \u03b5\u03ac\u03bd \u03b7 \u03c0\u03b1\u03c1\u03bf\u03c5\u03c3\u03af\u03b1 \u03c3\u03b1\u03c2 \u03c0\u03c1\u03bf\u03c3\u03c4\u03b1\u03c4\u03b5\u03cd\u03b5\u03c4\u03b1\u03b9 (auth_default_roles! = readable).", + "title": "\u0395\u03b9\u03c3\u03b1\u03b3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03b9\u03c2 \u03c0\u03bb\u03b7\u03c1\u03bf\u03c6\u03bf\u03c1\u03af\u03b5\u03c2 \u03c4\u03bf\u03c5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae Nightscout." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nightscout/translations/en.json b/homeassistant/components/nightscout/translations/en.json index b7947c84997a63..d8b4c441283ec7 100644 --- a/homeassistant/components/nightscout/translations/en.json +++ b/homeassistant/components/nightscout/translations/en.json @@ -5,14 +5,18 @@ }, "error": { "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", "unknown": "Unexpected error" }, "flow_title": "Nightscout", "step": { "user": { "data": { + "api_key": "API Key", "url": "URL" - } + }, + "description": "- URL: the address of your nightscout instance. I.e.: https://myhomeassistant.duckdns.org:5423\n- API Key (Optional): Only use if your instance is protected (auth_default_roles != readable).", + "title": "Enter your Nightscout server information." } } } diff --git a/homeassistant/components/nightscout/translations/es.json b/homeassistant/components/nightscout/translations/es.json index 9a03055bac4495..307b8ede5aab57 100644 --- a/homeassistant/components/nightscout/translations/es.json +++ b/homeassistant/components/nightscout/translations/es.json @@ -5,14 +5,18 @@ }, "error": { "cannot_connect": "No se pudo conectar", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", "unknown": "Error inesperado" }, "flow_title": "Nightscout", "step": { "user": { "data": { + "api_key": "Clave API", "url": "URL" - } + }, + "description": "- URL: la direcci\u00f3n de tu instancia de nightscout. Por ejemplo: https://myhomeassistant.duckdns.org:5423 \n - Clave API (opcional): util\u00edzala s\u00f3lo si tu instancia est\u00e1 protegida (auth_default_roles! = readable).", + "title": "Introduce la informaci\u00f3n del servidor de Nightscout." } } } diff --git a/homeassistant/components/nightscout/translations/et.json b/homeassistant/components/nightscout/translations/et.json new file mode 100644 index 00000000000000..cc02e4ab9642ca --- /dev/null +++ b/homeassistant/components/nightscout/translations/et.json @@ -0,0 +1,14 @@ +{ + "config": { + "error": { + "invalid_auth": "Tuvastamise viga" + }, + "step": { + "user": { + "data": { + "api_key": "API v\u00f5ti" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nightscout/translations/fr.json b/homeassistant/components/nightscout/translations/fr.json index 9ed1ea1bfd1004..1bcd530d29b52f 100644 --- a/homeassistant/components/nightscout/translations/fr.json +++ b/homeassistant/components/nightscout/translations/fr.json @@ -5,11 +5,14 @@ }, "error": { "cannot_connect": "\u00c9chec de connexion", + "invalid_auth": "Authentification invalide", "unknown": "Erreur inattendue" }, + "flow_title": "Nightscout", "step": { "user": { "data": { + "api_key": "Cl\u00e9 d'API", "url": "URL" } } diff --git a/homeassistant/components/nightscout/translations/hu.json b/homeassistant/components/nightscout/translations/hu.json new file mode 100644 index 00000000000000..3b2d79a34a77e2 --- /dev/null +++ b/homeassistant/components/nightscout/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nightscout/translations/it.json b/homeassistant/components/nightscout/translations/it.json index e0305d1b5e2546..8a1bfc826e2301 100644 --- a/homeassistant/components/nightscout/translations/it.json +++ b/homeassistant/components/nightscout/translations/it.json @@ -5,14 +5,18 @@ }, "error": { "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", "unknown": "Errore imprevisto" }, "flow_title": "Nightscout", "step": { "user": { "data": { + "api_key": "Chiave API", "url": "URL" - } + }, + "description": "- URL: l'indirizzo della tua istanza nightscout. Ad es.: https://myhomeassistant.duckdns.org:5423\n- Chiave API (opzionale): utilizzare solo se l'istanza \u00e8 protetta (auth_default_roles != readable).", + "title": "Inserisci le informazioni del tuo server Nightscout." } } } diff --git a/homeassistant/components/nightscout/translations/ko.json b/homeassistant/components/nightscout/translations/ko.json index 17dee71d640a46..0235c446e75454 100644 --- a/homeassistant/components/nightscout/translations/ko.json +++ b/homeassistant/components/nightscout/translations/ko.json @@ -2,6 +2,9 @@ "config": { "abort": { "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" + }, + "error": { + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec" } } } \ No newline at end of file diff --git a/homeassistant/components/nightscout/translations/lb.json b/homeassistant/components/nightscout/translations/lb.json index 76f6a7f233c5b5..cc1f048e84e95e 100644 --- a/homeassistant/components/nightscout/translations/lb.json +++ b/homeassistant/components/nightscout/translations/lb.json @@ -5,14 +5,18 @@ }, "error": { "cannot_connect": "Feeler beim verbannen", + "invalid_auth": "Ong\u00eblteg Authentifikatioun", "unknown": "Onerwaarte Feeler" }, "flow_title": "Nightscout", "step": { "user": { "data": { + "api_key": "API Schl\u00ebssel", "url": "URL" - } + }, + "description": "- URL. Adresse vun denger Nightscout Instanz: beispill: \nhttps://myhomeassistant.duckdns.org:5423\n- API Schl\u00ebssel (optionell): N\u00ebmmen benotzen wann deng Instanz proteg\u00e9iert ass. \n(auth_default_roles != readable)", + "title": "F\u00ebll d\u00e9ng Nightscout Server Informatiounen aus." } } } diff --git a/homeassistant/components/nightscout/translations/no.json b/homeassistant/components/nightscout/translations/no.json index a586083f5694ed..7f600268e88845 100644 --- a/homeassistant/components/nightscout/translations/no.json +++ b/homeassistant/components/nightscout/translations/no.json @@ -5,14 +5,18 @@ }, "error": { "cannot_connect": "Tilkobling mislyktes.", + "invalid_auth": "Ugyldig godkjenning", "unknown": "Uventet feil" }, "flow_title": "Nightscout", "step": { "user": { "data": { + "api_key": "API-n\u00f8kkel", "url": "URL" - } + }, + "description": "- URL: adressen til din nattscout-forekomst. Dvs: https://myhomeassistant.duckdns.org:5423 \n - API-n\u00f8kkel (valgfritt): Bruk bare hvis forekomsten din er beskyttet (auth_default_roles! = Lesbar).", + "title": "Skriv inn informasjon om Nightscout-serveren." } } } diff --git a/homeassistant/components/nightscout/translations/pl.json b/homeassistant/components/nightscout/translations/pl.json new file mode 100644 index 00000000000000..2883286c78fd97 --- /dev/null +++ b/homeassistant/components/nightscout/translations/pl.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "flow_title": "Nightscout", + "step": { + "user": { + "data": { + "api_key": "Klucz API" + }, + "description": "- URL: adres url Twojej instancji nightcout. Np: https://myhomeassistant.duckdns.org:5423\n- Klucz API (opcjonalny): u\u017cywaj tylko wtedy, gdy Twoja instancja jest chroniona (auth_default_roles! = readable).", + "title": "Wprowad\u017a informacje o serwerze Nightscout." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nightscout/translations/ru.json b/homeassistant/components/nightscout/translations/ru.json index cf904f0134c6dd..738c4dfa9a33d0 100644 --- a/homeassistant/components/nightscout/translations/ru.json +++ b/homeassistant/components/nightscout/translations/ru.json @@ -5,14 +5,18 @@ }, "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "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": { + "api_key": "\u041a\u043b\u044e\u0447 API", "url": "URL-\u0430\u0434\u0440\u0435\u0441" - } + }, + "description": "- URL: \u0430\u0434\u0440\u0435\u0441 \u0412\u0430\u0448\u0435\u0433\u043e Nightscout. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440: https://myhomeassistant.duckdns.org:5423\n- \u041a\u043b\u044e\u0447 API (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e): \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435, \u0442\u043e\u043b\u044c\u043a\u043e \u0435\u0441\u043b\u0438 \u0412\u0430\u0448 Nightcout \u0437\u0430\u0449\u0438\u0449\u0435\u043d (auth_default_roles != readable).", + "title": "Nightscout" } } } diff --git a/homeassistant/components/nightscout/translations/zh-Hant.json b/homeassistant/components/nightscout/translations/zh-Hant.json index fa9f3d12427a31..5066f5a2edb7eb 100644 --- a/homeassistant/components/nightscout/translations/zh-Hant.json +++ b/homeassistant/components/nightscout/translations/zh-Hant.json @@ -5,14 +5,18 @@ }, "error": { "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "flow_title": "Nightscout", "step": { "user": { "data": { + "api_key": "API \u5bc6\u9470", "url": "\u7db2\u5740" - } + }, + "description": "- URL\uff1aNightscout \u8a2d\u5099\u4f4d\u5740\u3002\u4f8b\u5982\uff1ahttps://myhomeassistant.duckdns.org:5423\n- API \u5bc6\u9470\uff08\u9078\u9805\uff09\uff1a\u50c5\u65bc\u8a2d\u5099\u70ba\u4fdd\u8b77\u72c0\u614b\uff08(auth_default_roles != readable\uff09\u4e0b\u4f7f\u7528\u3002", + "title": "\u8f38\u5165 Nightscout \u4f3a\u670d\u5668\u8cc7\u8a0a\u3002" } } } diff --git a/homeassistant/components/notify/translations/et.json b/homeassistant/components/notify/translations/et.json index d2c08643c0683c..798972fe384345 100644 --- a/homeassistant/components/notify/translations/et.json +++ b/homeassistant/components/notify/translations/et.json @@ -1,3 +1,3 @@ { - "title": "Teata" + "title": "Teavitused" } \ No newline at end of file diff --git a/homeassistant/components/notion/binary_sensor.py b/homeassistant/components/notion/binary_sensor.py index e798b53856585f..3702688eb94fdf 100644 --- a/homeassistant/components/notion/binary_sensor.py +++ b/homeassistant/components/notion/binary_sensor.py @@ -2,7 +2,14 @@ import logging from typing import Callable -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_CONNECTIVITY, + DEVICE_CLASS_DOOR, + DEVICE_CLASS_MOISTURE, + DEVICE_CLASS_SMOKE, + DEVICE_CLASS_WINDOW, + BinarySensorEntity, +) from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant, callback @@ -26,15 +33,15 @@ BINARY_SENSOR_TYPES = { SENSOR_BATTERY: ("Low Battery", "battery"), - SENSOR_DOOR: ("Door", "door"), + SENSOR_DOOR: ("Door", DEVICE_CLASS_DOOR), SENSOR_GARAGE_DOOR: ("Garage Door", "garage_door"), - SENSOR_LEAK: ("Leak Detector", "moisture"), - SENSOR_MISSING: ("Missing", "connectivity"), - SENSOR_SAFE: ("Safe", "door"), - SENSOR_SLIDING: ("Sliding Door/Window", "door"), - SENSOR_SMOKE_CO: ("Smoke/Carbon Monoxide Detector", "smoke"), - SENSOR_WINDOW_HINGED_HORIZONTAL: ("Hinged Window", "window"), - SENSOR_WINDOW_HINGED_VERTICAL: ("Hinged Window", "window"), + SENSOR_LEAK: ("Leak Detector", DEVICE_CLASS_MOISTURE), + SENSOR_MISSING: ("Missing", DEVICE_CLASS_CONNECTIVITY), + SENSOR_SAFE: ("Safe", DEVICE_CLASS_DOOR), + SENSOR_SLIDING: ("Sliding Door/Window", DEVICE_CLASS_DOOR), + SENSOR_SMOKE_CO: ("Smoke/Carbon Monoxide Detector", DEVICE_CLASS_SMOKE), + SENSOR_WINDOW_HINGED_HORIZONTAL: ("Hinged Window", DEVICE_CLASS_WINDOW), + SENSOR_WINDOW_HINGED_VERTICAL: ("Hinged Window", DEVICE_CLASS_WINDOW), } diff --git a/homeassistant/components/notion/config_flow.py b/homeassistant/components/notion/config_flow.py index 58c5c0d44eea62..bcbd22bccc6bde 100644 --- a/homeassistant/components/notion/config_flow.py +++ b/homeassistant/components/notion/config_flow.py @@ -47,6 +47,6 @@ async def async_step_user(self, user_input=None): user_input[CONF_USERNAME], user_input[CONF_PASSWORD], session ) except NotionError: - return await self._show_form({"base": "invalid_credentials"}) + return await self._show_form({"base": "invalid_auth"}) return self.async_create_entry(title=user_input[CONF_USERNAME], data=user_input) diff --git a/homeassistant/components/notion/strings.json b/homeassistant/components/notion/strings.json index 1fbe4837f7e7d7..401f0095e30837 100644 --- a/homeassistant/components/notion/strings.json +++ b/homeassistant/components/notion/strings.json @@ -10,11 +10,11 @@ } }, "error": { - "invalid_credentials": "Invalid username or password", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", "no_devices": "No devices found in account" }, "abort": { - "already_configured": "This username is already in use." + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/notion/translations/ca.json b/homeassistant/components/notion/translations/ca.json index 05626cbd483cb2..7abc95e5990edc 100644 --- a/homeassistant/components/notion/translations/ca.json +++ b/homeassistant/components/notion/translations/ca.json @@ -1,9 +1,10 @@ { "config": { "abort": { - "already_configured": "Aquest nom d'usuari ja est\u00e0 en \u00fas." + "already_configured": "El compte ja ha estat configurat" }, "error": { + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "invalid_credentials": "Nom d'usuari o contrasenya incorrectes", "no_devices": "No s'han trobat dispositius al compte" }, diff --git a/homeassistant/components/notion/translations/en.json b/homeassistant/components/notion/translations/en.json index d70aa73824abd4..b092cc000d9d6d 100644 --- a/homeassistant/components/notion/translations/en.json +++ b/homeassistant/components/notion/translations/en.json @@ -1,9 +1,10 @@ { "config": { "abort": { - "already_configured": "This username is already in use." + "already_configured": "Account is already configured" }, "error": { + "invalid_auth": "Invalid authentication", "invalid_credentials": "Invalid username or password", "no_devices": "No devices found in account" }, diff --git a/homeassistant/components/notion/translations/es.json b/homeassistant/components/notion/translations/es.json index 2ea7fbb0db79c4..40f2f1af14fd96 100644 --- a/homeassistant/components/notion/translations/es.json +++ b/homeassistant/components/notion/translations/es.json @@ -4,6 +4,7 @@ "already_configured": "Este nombre de usuario ya est\u00e1 en uso." }, "error": { + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", "invalid_credentials": "Usuario o contrase\u00f1a no v\u00e1lido", "no_devices": "No se han encontrado dispositivos en la cuenta" }, diff --git a/homeassistant/components/notion/translations/et.json b/homeassistant/components/notion/translations/et.json new file mode 100644 index 00000000000000..2227b7442a79c6 --- /dev/null +++ b/homeassistant/components/notion/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Tuvastamise viga" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/notion/translations/it.json b/homeassistant/components/notion/translations/it.json index c7626f592029e5..732982278680c1 100644 --- a/homeassistant/components/notion/translations/it.json +++ b/homeassistant/components/notion/translations/it.json @@ -1,9 +1,10 @@ { "config": { "abort": { - "already_configured": "Questo nome utente \u00e8 gi\u00e0 in uso." + "already_configured": "L'account \u00e8 gi\u00e0 configurato" }, "error": { + "invalid_auth": "Autenticazione non valida", "invalid_credentials": "Nome utente o password non validi", "no_devices": "Nessun dispositivo trovato nell'account" }, diff --git a/homeassistant/components/notion/translations/ru.json b/homeassistant/components/notion/translations/ru.json index 907ee235c965ce..0ac19623c1aa64 100644 --- a/homeassistant/components/notion/translations/ru.json +++ b/homeassistant/components/notion/translations/ru.json @@ -1,9 +1,10 @@ { "config": { "abort": { - "already_configured": "\u042d\u0442\u0430 \u0443\u0447\u0451\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u0443\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f." + "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": { + "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "invalid_credentials": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043b\u043e\u0433\u0438\u043d \u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c.", "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." }, diff --git a/homeassistant/components/nsw_fuel_station/sensor.py b/homeassistant/components/nsw_fuel_station/sensor.py index 5e9a9835bf494b..b6c0d1a5d9bd0c 100644 --- a/homeassistant/components/nsw_fuel_station/sensor.py +++ b/homeassistant/components/nsw_fuel_station/sensor.py @@ -7,7 +7,7 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ATTR_ATTRIBUTION +from homeassistant.const import ATTR_ATTRIBUTION, CURRENCY_CENT, VOLUME_LITERS import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import Throttle @@ -179,7 +179,7 @@ def device_state_attributes(self) -> dict: @property def unit_of_measurement(self) -> str: """Return the units of measurement.""" - return "¢/L" + return f"{CURRENCY_CENT}/{VOLUME_LITERS}" def update(self): """Update current conditions.""" diff --git a/homeassistant/components/nuheat/strings.json b/homeassistant/components/nuheat/strings.json index 367420178e630d..aebad40e6a998c 100644 --- a/homeassistant/components/nuheat/strings.json +++ b/homeassistant/components/nuheat/strings.json @@ -1,13 +1,13 @@ { "config": { "error": { - "unknown": "Unexpected error", - "cannot_connect": "Failed to connect, please try again", - "invalid_auth": "Invalid authentication", + "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%]", "invalid_thermostat": "The thermostat serial number is invalid." }, "abort": { - "already_configured": "The thermostat is already configured" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" }, "step": { "user": { @@ -21,4 +21,4 @@ } } } -} \ No newline at end of file +} diff --git a/homeassistant/components/nuheat/translations/ca.json b/homeassistant/components/nuheat/translations/ca.json index c7821838f4f71f..cba3a76ad8fd0e 100644 --- a/homeassistant/components/nuheat/translations/ca.json +++ b/homeassistant/components/nuheat/translations/ca.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "El term\u00f2stat ja est\u00e0 configurat" + "already_configured": "El dispositiu ja est\u00e0 configurat" }, "error": { - "cannot_connect": "No s'ha pogut connectar, torna-ho a provar", + "cannot_connect": "Ha fallat la connexi\u00f3", "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "invalid_thermostat": "El n\u00famero de s\u00e8rie del term\u00f2stat no \u00e9s v\u00e0lid.", "unknown": "Error inesperat" diff --git a/homeassistant/components/nuheat/translations/en.json b/homeassistant/components/nuheat/translations/en.json index d7b2697d5e057a..73d6caa94e3c23 100644 --- a/homeassistant/components/nuheat/translations/en.json +++ b/homeassistant/components/nuheat/translations/en.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "The thermostat is already configured" + "already_configured": "Device is already configured" }, "error": { - "cannot_connect": "Failed to connect, please try again", + "cannot_connect": "Failed to connect", "invalid_auth": "Invalid authentication", "invalid_thermostat": "The thermostat serial number is invalid.", "unknown": "Unexpected error" diff --git a/homeassistant/components/nuheat/translations/et.json b/homeassistant/components/nuheat/translations/et.json new file mode 100644 index 00000000000000..0d70cd06fcaa70 --- /dev/null +++ b/homeassistant/components/nuheat/translations/et.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00dchenduse loomine nurjus. Proovi uuesti", + "unknown": "Ootamatu t\u00f5rge" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nuheat/translations/fr.json b/homeassistant/components/nuheat/translations/fr.json index da5c3260f2a43d..f0e912805edf7d 100644 --- a/homeassistant/components/nuheat/translations/fr.json +++ b/homeassistant/components/nuheat/translations/fr.json @@ -16,6 +16,7 @@ "serial_number": "Num\u00e9ro de s\u00e9rie du thermostat.", "username": "Nom d'utilisateur" }, + "description": "Vous devrez obtenir le num\u00e9ro de s\u00e9rie ou l'identifiant num\u00e9rique de votre thermostat en vous connectant \u00e0 https://MyNuHeat.com et en s\u00e9lectionnant votre (vos) thermostat (s).", "title": "Connectez-vous au NuHeat" } } diff --git a/homeassistant/components/nuheat/translations/it.json b/homeassistant/components/nuheat/translations/it.json index 973ff695acbf19..1e17ce8ecf8cbd 100644 --- a/homeassistant/components/nuheat/translations/it.json +++ b/homeassistant/components/nuheat/translations/it.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Il termostato \u00e8 gi\u00e0 configurato" + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" }, "error": { - "cannot_connect": "Impossibile connettersi, si prega di riprovare", + "cannot_connect": "Impossibile connettersi", "invalid_auth": "Autenticazione non valida", "invalid_thermostat": "Il numero di serie del termostato non \u00e8 valido.", "unknown": "Errore imprevisto" diff --git a/homeassistant/components/nuheat/translations/no.json b/homeassistant/components/nuheat/translations/no.json index 7ec631197cc60a..199a3be91ce62f 100644 --- a/homeassistant/components/nuheat/translations/no.json +++ b/homeassistant/components/nuheat/translations/no.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Termostaten er allerede konfigurert" + "already_configured": "Enheten er allerede konfigurert" }, "error": { - "cannot_connect": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen", + "cannot_connect": "Tilkobling mislyktes.", "invalid_auth": "Ugyldig godkjenning", "invalid_thermostat": "Termostatens serienummer er ugyldig.", "unknown": "Uventet feil" diff --git a/homeassistant/components/nuheat/translations/pl.json b/homeassistant/components/nuheat/translations/pl.json index d55b545d040dda..d992afe9cc0cb3 100644 --- a/homeassistant/components/nuheat/translations/pl.json +++ b/homeassistant/components/nuheat/translations/pl.json @@ -5,9 +5,9 @@ }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "invalid_auth": "Niepoprawne uwierzytelnienie.", + "invalid_auth": "Niepoprawne uwierzytelnienie", "invalid_thermostat": "Numer seryjny termostatu jest nieprawid\u0142owy.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "user": { diff --git a/homeassistant/components/nuheat/translations/ru.json b/homeassistant/components/nuheat/translations/ru.json index f4d06e8eca64ab..09e74c0e4cbf8a 100644 --- a/homeassistant/components/nuheat/translations/ru.json +++ b/homeassistant/components/nuheat/translations/ru.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", + "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "invalid_thermostat": "\u0421\u0435\u0440\u0438\u0439\u043d\u044b\u0439 \u043d\u043e\u043c\u0435\u0440 \u0442\u0435\u0440\u043c\u043e\u0441\u0442\u0430\u0442\u0430 \u043d\u0435\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u0435\u043d.", "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/numato/__init__.py b/homeassistant/components/numato/__init__.py index 8efc56e12fef7c..1730ff829566a1 100644 --- a/homeassistant/components/numato/__init__.py +++ b/homeassistant/components/numato/__init__.py @@ -12,6 +12,7 @@ CONF_SWITCHES, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, + PERCENTAGE, ) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import load_platform @@ -31,7 +32,6 @@ DEFAULT_INVERT_LOGIC = False DEFAULT_SRC_RANGE = [0, 1024] DEFAULT_DST_RANGE = [0.0, 100.0] -DEFAULT_UNIT = "%" DEFAULT_DEV = [f"/dev/ttyACM{i}" for i in range(10)] PORT_RANGE = range(1, 8) # ports 0-7 are ADC capable @@ -82,7 +82,7 @@ def adc_port_number(num): vol.Required(CONF_NAME): cv.string, vol.Optional(CONF_SRC_RANGE, default=DEFAULT_SRC_RANGE): int_range, vol.Optional(CONF_DST_RANGE, default=DEFAULT_DST_RANGE): float_range, - vol.Optional(CONF_DST_UNIT, default=DEFAULT_UNIT): cv.string, + vol.Optional(CONF_DST_UNIT, default=PERCENTAGE): cv.string, } ) @@ -171,7 +171,7 @@ class NumatoAPI: def __init__(self): """Initialize API state.""" - self.ports_registered = dict() + self.ports_registered = {} def check_port_free(self, device_id, port, direction): """Check whether a port is still free set up. diff --git a/homeassistant/components/nut/strings.json b/homeassistant/components/nut/strings.json index 2203a501ff286e..1b71280b6a95c8 100644 --- a/homeassistant/components/nut/strings.json +++ b/homeassistant/components/nut/strings.json @@ -25,11 +25,11 @@ } }, "error": { - "cannot_connect": "Failed to connect, please try again", - "unknown": "Unexpected error" + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { - "already_configured": "Device is already configured" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } }, "options": { @@ -43,4 +43,4 @@ } } } -} \ No newline at end of file +} diff --git a/homeassistant/components/nut/translations/ca.json b/homeassistant/components/nut/translations/ca.json index 13c1bde317363e..fa7b728e115272 100644 --- a/homeassistant/components/nut/translations/ca.json +++ b/homeassistant/components/nut/translations/ca.json @@ -4,7 +4,7 @@ "already_configured": "El dispositiu ja est\u00e0 configurat" }, "error": { - "cannot_connect": "No s'ha pogut connectar, torna-ho a provar", + "cannot_connect": "Ha fallat la connexi\u00f3", "unknown": "Error inesperat" }, "step": { diff --git a/homeassistant/components/nut/translations/en.json b/homeassistant/components/nut/translations/en.json index f698ad9287aad6..2e5db79d81c328 100644 --- a/homeassistant/components/nut/translations/en.json +++ b/homeassistant/components/nut/translations/en.json @@ -4,7 +4,7 @@ "already_configured": "Device is already configured" }, "error": { - "cannot_connect": "Failed to connect, please try again", + "cannot_connect": "Failed to connect", "unknown": "Unexpected error" }, "step": { diff --git a/homeassistant/components/nut/translations/it.json b/homeassistant/components/nut/translations/it.json index 177fd72067e102..440cb421504a47 100644 --- a/homeassistant/components/nut/translations/it.json +++ b/homeassistant/components/nut/translations/it.json @@ -4,7 +4,7 @@ "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" }, "error": { - "cannot_connect": "Impossibile connettersi, si prega di riprovare", + "cannot_connect": "Impossibile connettersi", "unknown": "Errore imprevisto" }, "step": { diff --git a/homeassistant/components/nut/translations/no.json b/homeassistant/components/nut/translations/no.json index 6fd749442c3bdc..8b1704fe04ae95 100644 --- a/homeassistant/components/nut/translations/no.json +++ b/homeassistant/components/nut/translations/no.json @@ -4,7 +4,7 @@ "already_configured": "Enheten er allerede konfigurert" }, "error": { - "cannot_connect": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen", + "cannot_connect": "Tilkobling mislyktes.", "unknown": "Uventet feil" }, "step": { diff --git a/homeassistant/components/nut/translations/pl.json b/homeassistant/components/nut/translations/pl.json index 5fb9082d6764ad..d24638341d27bb 100644 --- a/homeassistant/components/nut/translations/pl.json +++ b/homeassistant/components/nut/translations/pl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "resources": { diff --git a/homeassistant/components/nut/translations/ru.json b/homeassistant/components/nut/translations/ru.json index 012ac2ae568f8d..7a3f1c9b47e0fb 100644 --- a/homeassistant/components/nut/translations/ru.json +++ b/homeassistant/components/nut/translations/ru.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", + "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": { diff --git a/homeassistant/components/nut/translations/zh-Hant.json b/homeassistant/components/nut/translations/zh-Hant.json index 35cd266854f07f..b75bd37958f276 100644 --- a/homeassistant/components/nut/translations/zh-Hant.json +++ b/homeassistant/components/nut/translations/zh-Hant.json @@ -4,7 +4,7 @@ "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21", + "cannot_connect": "\u9023\u7dda\u5931\u6557", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { diff --git a/homeassistant/components/nws/manifest.json b/homeassistant/components/nws/manifest.json index 23643699f3a892..ef0a35b846a68d 100644 --- a/homeassistant/components/nws/manifest.json +++ b/homeassistant/components/nws/manifest.json @@ -3,7 +3,7 @@ "name": "National Weather Service (NWS)", "documentation": "https://www.home-assistant.io/integrations/nws", "codeowners": ["@MatthewFlamm"], - "requirements": ["pynws==1.2.1"], + "requirements": ["pynws==1.3.0"], "quality_scale": "platinum", "config_flow": true } diff --git a/homeassistant/components/nws/strings.json b/homeassistant/components/nws/strings.json index 83e8a3a694bcab..0f0bdcf4a1cb62 100644 --- a/homeassistant/components/nws/strings.json +++ b/homeassistant/components/nws/strings.json @@ -6,18 +6,18 @@ "title": "Connect to the National Weather Service", "data": { "api_key": "[%key:common::config_flow::data::api_key%]", - "latitude": "Latitude", - "longitude": "Longitude", + "latitude": "[%key:common::config_flow::data::latitude%]", + "longitude": "[%key:common::config_flow::data::longitude%]", "station": "METAR station code" } } }, "error": { - "cannot_connect": "Failed to connect, please try again", - "unknown": "Unexpected error" + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { - "already_configured": "Device is already configured" + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/nws/translations/ca.json b/homeassistant/components/nws/translations/ca.json index a2239f5f97dbd7..e012d68a10e2d1 100644 --- a/homeassistant/components/nws/translations/ca.json +++ b/homeassistant/components/nws/translations/ca.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "El dispositiu ja est\u00e0 configurat" + "already_configured": "El servei ja est\u00e0 configurat" }, "error": { - "cannot_connect": "No s'ha pogut connectar, torna-ho a provar", + "cannot_connect": "Ha fallat la connexi\u00f3", "unknown": "Error inesperat" }, "step": { diff --git a/homeassistant/components/nws/translations/en.json b/homeassistant/components/nws/translations/en.json index 19eec6a20e3920..04cb13bf5e80d3 100644 --- a/homeassistant/components/nws/translations/en.json +++ b/homeassistant/components/nws/translations/en.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Device is already configured" + "already_configured": "Service is already configured" }, "error": { - "cannot_connect": "Failed to connect, please try again", + "cannot_connect": "Failed to connect", "unknown": "Unexpected error" }, "step": { diff --git a/homeassistant/components/nws/translations/et.json b/homeassistant/components/nws/translations/et.json new file mode 100644 index 00000000000000..a9607835b439f6 --- /dev/null +++ b/homeassistant/components/nws/translations/et.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "user": { + "data": { + "latitude": "Laiuskraad", + "longitude": "Pikkuskraad" + }, + "title": "\u00dchendu riikliku ilmateenistusega (USA)" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nws/translations/fr.json b/homeassistant/components/nws/translations/fr.json index 86db25ebd11ebf..568179cf9fae20 100644 --- a/homeassistant/components/nws/translations/fr.json +++ b/homeassistant/components/nws/translations/fr.json @@ -15,6 +15,7 @@ "longitude": "Longitude", "station": "Code de la station METAR" }, + "description": "Si aucun code de station METAR n'est sp\u00e9cifi\u00e9, la latitude et la longitude seront utilis\u00e9es pour trouver la station la plus proche.", "title": "Se connecter au National Weather Service" } } diff --git a/homeassistant/components/nws/translations/it.json b/homeassistant/components/nws/translations/it.json index 92c519513b4ce8..827d8078b55aad 100644 --- a/homeassistant/components/nws/translations/it.json +++ b/homeassistant/components/nws/translations/it.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + "already_configured": "Il servizio \u00e8 gi\u00e0 configurato" }, "error": { - "cannot_connect": "Impossibile connettersi, si prega di riprovare", + "cannot_connect": "Impossibile connettersi", "unknown": "Errore imprevisto" }, "step": { diff --git a/homeassistant/components/nws/translations/no.json b/homeassistant/components/nws/translations/no.json index f26abdeaa2e443..ba7f4c7a26d031 100644 --- a/homeassistant/components/nws/translations/no.json +++ b/homeassistant/components/nws/translations/no.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Enheten er allerede konfigurert" + "already_configured": "Tjenesten er allerede konfigurert" }, "error": { - "cannot_connect": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen", + "cannot_connect": "Tilkobling mislyktes.", "unknown": "Uventet feil" }, "step": { diff --git a/homeassistant/components/nws/translations/pl.json b/homeassistant/components/nws/translations/pl.json index ab1011d9d56617..2671f0408c9499 100644 --- a/homeassistant/components/nws/translations/pl.json +++ b/homeassistant/components/nws/translations/pl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "user": { diff --git a/homeassistant/components/nws/translations/ru.json b/homeassistant/components/nws/translations/ru.json index 96ab63cd2d435a..926e936a594389 100644 --- a/homeassistant/components/nws/translations/ru.json +++ b/homeassistant/components/nws/translations/ru.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", + "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": { diff --git a/homeassistant/components/nws/translations/zh-Hant.json b/homeassistant/components/nws/translations/zh-Hant.json index 703dbd240b8681..067234c7b54e13 100644 --- a/homeassistant/components/nws/translations/zh-Hant.json +++ b/homeassistant/components/nws/translations/zh-Hant.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + "already_configured": "\u670d\u52d9\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21", + "cannot_connect": "\u9023\u7dda\u5931\u6557", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { diff --git a/homeassistant/components/nws/weather.py b/homeassistant/components/nws/weather.py index 3c641447e84f97..69dac297b1b7cc 100644 --- a/homeassistant/components/nws/weather.py +++ b/homeassistant/components/nws/weather.py @@ -318,3 +318,8 @@ async def async_update(self): """ await self.coordinator_observation.async_request_refresh() await self.coordinator_forecast.async_request_refresh() + + @property + def entity_registry_enabled_default(self) -> bool: + """Return if the entity should be enabled when first added to the entity registry.""" + return self.mode == DAYNIGHT diff --git a/homeassistant/components/nx584/alarm_control_panel.py b/homeassistant/components/nx584/alarm_control_panel.py index 2df7cd1bb4b04c..6366831b598c16 100644 --- a/homeassistant/components/nx584/alarm_control_panel.py +++ b/homeassistant/components/nx584/alarm_control_panel.py @@ -58,7 +58,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= except requests.exceptions.ConnectionError as ex: _LOGGER.error( "Unable to connect to %(host)s: %(reason)s", - dict(host=url, reason=ex), + {"host": url, "reason": ex}, ) raise PlatformNotReady from ex @@ -118,7 +118,7 @@ def update(self): except requests.exceptions.ConnectionError as ex: _LOGGER.error( "Unable to connect to %(host)s: %(reason)s", - dict(host=self._url, reason=ex), + {"host": self._url, "reason": ex}, ) self._state = None zones = [] @@ -132,7 +132,7 @@ def update(self): if zone["bypassed"]: _LOGGER.debug( "Zone %(zone)s is bypassed, assuming HOME", - dict(zone=zone["number"]), + {"zone": zone["number"]}, ) bypassed = True break diff --git a/homeassistant/components/nx584/binary_sensor.py b/homeassistant/components/nx584/binary_sensor.py index 127ce02b371700..2db3531f879555 100644 --- a/homeassistant/components/nx584/binary_sensor.py +++ b/homeassistant/components/nx584/binary_sensor.py @@ -8,6 +8,7 @@ import voluptuous as vol from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_OPENING, DEVICE_CLASSES, PLATFORM_SCHEMA, BinarySensorEntity, @@ -59,7 +60,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return False zone_sensors = { - zone["number"]: NX584ZoneSensor(zone, zone_types.get(zone["number"], "opening")) + zone["number"]: NX584ZoneSensor( + zone, zone_types.get(zone["number"], DEVICE_CLASS_OPENING) + ) for zone in zones if zone["number"] not in exclude } diff --git a/homeassistant/components/nzbget/__init__.py b/homeassistant/components/nzbget/__init__.py index 130fa0d55b8528..467cd8c06dbc18 100644 --- a/homeassistant/components/nzbget/__init__.py +++ b/homeassistant/components/nzbget/__init__.py @@ -37,7 +37,7 @@ _LOGGER = logging.getLogger(__name__) -PLATFORMS = ["sensor"] +PLATFORMS = ["sensor", "switch"] CONFIG_SCHEMA = vol.Schema( { diff --git a/homeassistant/components/nzbget/config_flow.py b/homeassistant/components/nzbget/config_flow.py index dfed7a9bfeece8..f593eeb0729c4c 100644 --- a/homeassistant/components/nzbget/config_flow.py +++ b/homeassistant/components/nzbget/config_flow.py @@ -38,8 +38,8 @@ def validate_input(hass: HomeAssistantType, data: dict) -> Dict[str, Any]: """ nzbget_api = NZBGetAPI( data[CONF_HOST], - data[CONF_USERNAME] if data[CONF_USERNAME] != "" else None, - data[CONF_PASSWORD] if data[CONF_PASSWORD] != "" else None, + data.get(CONF_USERNAME), + data.get(CONF_PASSWORD), data[CONF_SSL], data[CONF_VERIFY_SSL], data[CONF_PORT], diff --git a/homeassistant/components/nzbget/coordinator.py b/homeassistant/components/nzbget/coordinator.py index 8892475bc098b9..9a76d802bdd3a6 100644 --- a/homeassistant/components/nzbget/coordinator.py +++ b/homeassistant/components/nzbget/coordinator.py @@ -29,8 +29,8 @@ def __init__(self, hass: HomeAssistantType, *, config: dict, options: dict): """Initialize global NZBGet data updater.""" self.nzbget = NZBGetAPI( config[CONF_HOST], - config[CONF_USERNAME] if config[CONF_USERNAME] != "" else None, - config[CONF_PASSWORD] if config[CONF_PASSWORD] != "" else None, + config.get(CONF_USERNAME), + config.get(CONF_PASSWORD), config[CONF_SSL], config[CONF_VERIFY_SSL], config[CONF_PORT], diff --git a/homeassistant/components/nzbget/sensor.py b/homeassistant/components/nzbget/sensor.py index ddbc73ca10a63e..b4133e7550d985 100644 --- a/homeassistant/components/nzbget/sensor.py +++ b/homeassistant/components/nzbget/sensor.py @@ -64,7 +64,7 @@ async def async_setup_entry( async_add_entities(sensors) -class NZBGetSensor(NZBGetEntity, Entity): +class NZBGetSensor(NZBGetEntity): """Representation of a NZBGet sensor.""" def __init__( diff --git a/homeassistant/components/nzbget/strings.json b/homeassistant/components/nzbget/strings.json index 5a0c31054a9a71..96049ea936986c 100644 --- a/homeassistant/components/nzbget/strings.json +++ b/homeassistant/components/nzbget/strings.json @@ -5,13 +5,13 @@ "user": { "title": "Connect to NZBGet", "data": { - "name": "Name", + "name": "[%key:common::config_flow::data::name%]", "host": "[%key:common::config_flow::data::host%]", "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]", "port": "[%key:common::config_flow::data::port%]", - "ssl": "NZBGet uses a SSL certificate", - "verify_ssl": "NZBGet uses a proper certificate" + "ssl": "[%key:common::config_flow::data::ssl%]", + "verify_ssl": "[%key:common::config_flow::data::verify_ssl%]" } } }, diff --git a/homeassistant/components/nzbget/switch.py b/homeassistant/components/nzbget/switch.py new file mode 100644 index 00000000000000..c4ceaab5ded353 --- /dev/null +++ b/homeassistant/components/nzbget/switch.py @@ -0,0 +1,72 @@ +"""Support for NZBGet switches.""" +from typing import Callable, List + +from homeassistant.components.switch import SwitchEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_NAME +from homeassistant.helpers.entity import Entity +from homeassistant.helpers.typing import HomeAssistantType + +from . import NZBGetEntity +from .const import DATA_COORDINATOR, DOMAIN +from .coordinator import NZBGetDataUpdateCoordinator + + +async def async_setup_entry( + hass: HomeAssistantType, + entry: ConfigEntry, + async_add_entities: Callable[[List[Entity], bool], None], +) -> None: + """Set up NZBGet sensor based on a config entry.""" + coordinator: NZBGetDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id][ + DATA_COORDINATOR + ] + + switches = [ + NZBGetDownloadSwitch( + coordinator, + entry.entry_id, + entry.data[CONF_NAME], + ), + ] + + async_add_entities(switches) + + +class NZBGetDownloadSwitch(NZBGetEntity, SwitchEntity): + """Representation of a NZBGet download switch.""" + + def __init__( + self, + coordinator: NZBGetDataUpdateCoordinator, + entry_id: str, + entry_name: str, + ): + """Initialize a new NZBGet switch.""" + self._unique_id = f"{entry_id}_download" + + super().__init__( + coordinator=coordinator, + entry_id=entry_id, + name=f"{entry_name} Download", + ) + + @property + def unique_id(self) -> str: + """Return the unique ID of the switch.""" + return self._unique_id + + @property + def is_on(self): + """Return the state of the switch.""" + return not self.coordinator.data["status"].get("DownloadPaused", False) + + async def async_turn_on(self, **kwargs) -> None: + """Set downloads to enabled.""" + await self.hass.async_add_executor_job(self.coordinator.nzbget.resumedownload) + await self.coordinator.async_request_refresh() + + async def async_turn_off(self, **kwargs) -> None: + """Set downloads to paused.""" + await self.hass.async_add_executor_job(self.coordinator.nzbget.pausedownload) + await self.coordinator.async_request_refresh() diff --git a/homeassistant/components/nzbget/translations/ca.json b/homeassistant/components/nzbget/translations/ca.json index 535b56a3768de7..b567d77bcb3a92 100644 --- a/homeassistant/components/nzbget/translations/ca.json +++ b/homeassistant/components/nzbget/translations/ca.json @@ -16,9 +16,9 @@ "name": "Nom", "password": "Contrasenya", "port": "Port", - "ssl": "NZBGet utilitza un certificat SSL", + "ssl": "Utilitza un certificat SSL", "username": "Nom d'usuari", - "verify_ssl": "NZBGet utilitza un certificat adequat" + "verify_ssl": "Verifica el certificat SSL" }, "title": "Connexi\u00f3 amb NZBGet" } diff --git a/homeassistant/components/nzbget/translations/de.json b/homeassistant/components/nzbget/translations/de.json new file mode 100644 index 00000000000000..6aa89d2e62ec3e --- /dev/null +++ b/homeassistant/components/nzbget/translations/de.json @@ -0,0 +1,25 @@ +{ + "config": { + "flow_title": "NZBGet: {name}", + "step": { + "user": { + "data": { + "host": "Host", + "name": "Name", + "password": "Passwort", + "port": "Port", + "username": "Benutzername" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Aktualisierungsh\u00e4ufigkeit (Sekunden)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nzbget/translations/el.json b/homeassistant/components/nzbget/translations/el.json new file mode 100644 index 00000000000000..25febfd7c8a34c --- /dev/null +++ b/homeassistant/components/nzbget/translations/el.json @@ -0,0 +1,11 @@ +{ + "config": { + "abort": { + "unknown": "\u039c\u03b7 \u03b1\u03bd\u03b1\u03bc\u03b5\u03bd\u03cc\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": "NZBGet: {\u03cc\u03bd\u03bf\u03bc\u03b1}" + } +} \ No newline at end of file diff --git a/homeassistant/components/nzbget/translations/en.json b/homeassistant/components/nzbget/translations/en.json index c410d4e487493c..d9a6273c27cffb 100644 --- a/homeassistant/components/nzbget/translations/en.json +++ b/homeassistant/components/nzbget/translations/en.json @@ -16,9 +16,9 @@ "name": "Name", "password": "Password", "port": "Port", - "ssl": "NZBGet uses a SSL certificate", + "ssl": "Uses an SSL certificate", "username": "Username", - "verify_ssl": "NZBGet uses a proper certificate" + "verify_ssl": "Verify SSL certificate" }, "title": "Connect to NZBGet" } diff --git a/homeassistant/components/nzbget/translations/it.json b/homeassistant/components/nzbget/translations/it.json index 31438f9d55b689..88bea29987b3f4 100644 --- a/homeassistant/components/nzbget/translations/it.json +++ b/homeassistant/components/nzbget/translations/it.json @@ -16,9 +16,9 @@ "name": "Nome", "password": "Password", "port": "Porta", - "ssl": "NZBGet utilizza un certificato SSL", + "ssl": "Utilizza un certificato SSL", "username": "Nome utente", - "verify_ssl": "NZBGet utilizza un certificato proprio" + "verify_ssl": "Verificare il certificato SSL" }, "title": "Connettiti a NZBGet" } diff --git a/homeassistant/components/nzbget/translations/ko.json b/homeassistant/components/nzbget/translations/ko.json new file mode 100644 index 00000000000000..ea9108b936710b --- /dev/null +++ b/homeassistant/components/nzbget/translations/ko.json @@ -0,0 +1,36 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\uc774\ubbf8 \uad6c\uc131\ub428. \ud558\ub098\uc758 \uad6c\uc131\ub9cc \uac00\ub2a5\ud569\ub2c8\ub2e4.", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", + "invalid_auth": "\uc798\ubabb\ub41c \uc778\uc99d" + }, + "flow_title": "NZBGet : {name}", + "step": { + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8", + "name": "\uc774\ub984", + "password": "\uc554\ud638", + "port": "\ud3ec\ud2b8", + "ssl": "NZBGet\uc740 SSL \uc778\uc99d\uc11c\ub97c \uc0ac\uc6a9\ud569\ub2c8\ub2e4.", + "username": "\uc0ac\uc6a9\uc790\uba85", + "verify_ssl": "NZBGet\uc740 \uc801\uc808\ud55c \uc778\uc99d\uc11c\ub97c \uc0ac\uc6a9\ud569\ub2c8\ub2e4." + }, + "title": "NZBGet\uc5d0 \uc5f0\uacb0" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\uc5c5\ub370\uc774\ud2b8 \uac04\uaca9 (\ucd08)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nzbget/translations/lb.json b/homeassistant/components/nzbget/translations/lb.json new file mode 100644 index 00000000000000..5da36a5d859583 --- /dev/null +++ b/homeassistant/components/nzbget/translations/lb.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "unknown": "Onerwaarte Feeler" + }, + "error": { + "cannot_connect": "Feeler beim verbannen", + "invalid_auth": "Ong\u00eblteg Authentifikatioun" + }, + "flow_title": "NZBGet: {name}", + "step": { + "user": { + "data": { + "host": "Host", + "name": "Numm", + "password": "Passwuert", + "port": "Port", + "ssl": "NZBGet benotzt een SSL Zertifikat", + "username": "Benotzernumm", + "verify_ssl": "NZBGet benotzt ee g\u00ebltegen SSL Zertifikat" + }, + "title": "Mat NZBGet verbannen" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Intervalle vun de Mise \u00e0 jour (sekonnen)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nzbget/translations/nl.json b/homeassistant/components/nzbget/translations/nl.json new file mode 100644 index 00000000000000..472952ad8b14e5 --- /dev/null +++ b/homeassistant/components/nzbget/translations/nl.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "Naam", + "username": "Gebruikersnaam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/nzbget/translations/no.json b/homeassistant/components/nzbget/translations/no.json index e8230da6f2db8c..3afef3773836a4 100644 --- a/homeassistant/components/nzbget/translations/no.json +++ b/homeassistant/components/nzbget/translations/no.json @@ -16,9 +16,9 @@ "name": "Navn", "password": "Passord", "port": "Port", - "ssl": "NZBGet bruker et SSL-sertifikat", + "ssl": "Bruker et SSL-sertifikat", "username": "Brukernavn", - "verify_ssl": "NZBGet bruker et riktig sertifikat" + "verify_ssl": "Verifisere SSL-sertifikat" }, "title": "Koble til NZBGet" } diff --git a/homeassistant/components/nzbget/translations/pl.json b/homeassistant/components/nzbget/translations/pl.json index a5bd1b5cdcb0ec..e01c59fe2a7927 100644 --- a/homeassistant/components/nzbget/translations/pl.json +++ b/homeassistant/components/nzbget/translations/pl.json @@ -1,7 +1,12 @@ { "config": { + "abort": { + "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja.", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, "error": { - "invalid_auth": "Niepoprawne uwierzytelnienie." + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie" }, "flow_title": "NZBGet: {name}", "step": { @@ -12,6 +17,16 @@ "password": "Has\u0142o", "port": "Port", "username": "Nazwa u\u017cytkownika" + }, + "title": "Po\u0142\u0105czenie z NZBGet" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Cz\u0119stotliwo\u015b\u0107 aktualizacji (w sekundach)" } } } diff --git a/homeassistant/components/nzbget/translations/ru.json b/homeassistant/components/nzbget/translations/ru.json index 93e44307ab8543..daab5c87ef72a8 100644 --- a/homeassistant/components/nzbget/translations/ru.json +++ b/homeassistant/components/nzbget/translations/ru.json @@ -16,9 +16,9 @@ "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", - "ssl": "NZBGet \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL", + "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": "\u041b\u043e\u0433\u0438\u043d", - "verify_ssl": "NZBGet \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442" + "verify_ssl": "\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL" }, "title": "NZBGet" } diff --git a/homeassistant/components/nzbget/translations/zh-Hant.json b/homeassistant/components/nzbget/translations/zh-Hant.json index d2fc50ac26acc7..e2c3c8ea8207d4 100644 --- a/homeassistant/components/nzbget/translations/zh-Hant.json +++ b/homeassistant/components/nzbget/translations/zh-Hant.json @@ -16,9 +16,9 @@ "name": "\u540d\u7a31", "password": "\u5bc6\u78bc", "port": "\u901a\u8a0a\u57e0", - "ssl": "NZBGet \u4f7f\u7528 SSL \u8a8d\u8b49", + "ssl": "\u4f7f\u7528 SSL \u8a8d\u8b49", "username": "\u4f7f\u7528\u8005\u540d\u7a31", - "verify_ssl": "NZBGet \u4f7f\u7528\u5c0d\u61c9\u8a8d\u8b49" + "verify_ssl": "\u78ba\u8a8d SSL \u8a8d\u8b49" }, "title": "\u9023\u7dda\u81f3 NZBGet" } diff --git a/homeassistant/components/omnilogic/__init__.py b/homeassistant/components/omnilogic/__init__.py new file mode 100644 index 00000000000000..ff4dd93a0e1537 --- /dev/null +++ b/homeassistant/components/omnilogic/__init__.py @@ -0,0 +1,90 @@ +"""The Omnilogic integration.""" +import asyncio +import logging + +from omnilogic import LoginException, OmniLogic, OmniLogicException + +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 import aiohttp_client + +from .common import OmniLogicUpdateCoordinator +from .const import CONF_SCAN_INTERVAL, COORDINATOR, DOMAIN, OMNI_API + +_LOGGER = logging.getLogger(__name__) + +PLATFORMS = ["sensor"] + + +async def async_setup(hass: HomeAssistant, config: dict): + """Set up the Omnilogic component.""" + hass.data.setdefault(DOMAIN, {}) + + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): + """Set up Omnilogic from a config entry.""" + + conf = entry.data + username = conf[CONF_USERNAME] + password = conf[CONF_PASSWORD] + + polling_interval = 6 + if CONF_SCAN_INTERVAL in conf: + polling_interval = conf[CONF_SCAN_INTERVAL] + + session = aiohttp_client.async_get_clientsession(hass) + + api = OmniLogic(username, password, session) + + try: + await api.connect() + await api.get_telemetry_data() + except LoginException as error: + _LOGGER.error("Login Failed: %s", error) + return False + except OmniLogicException as error: + _LOGGER.debug("OmniLogic API error: %s", error) + raise ConfigEntryNotReady from error + + coordinator = OmniLogicUpdateCoordinator( + hass=hass, + api=api, + name="Omnilogic", + polling_interval=polling_interval, + ) + await coordinator.async_refresh() + + if not coordinator.last_update_success: + raise ConfigEntryNotReady + + hass.data[DOMAIN][entry.entry_id] = { + COORDINATOR: coordinator, + OMNI_API: api, + } + + for component in PLATFORMS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, component) + ) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Unload a config entry.""" + unload_ok = all( + await asyncio.gather( + *[ + hass.config_entries.async_forward_entry_unload(entry, component) + for component in PLATFORMS + ] + ) + ) + if unload_ok: + hass.data[DOMAIN].pop(entry.entry_id) + + return unload_ok diff --git a/homeassistant/components/omnilogic/common.py b/homeassistant/components/omnilogic/common.py new file mode 100644 index 00000000000000..791d81b6757a53 --- /dev/null +++ b/homeassistant/components/omnilogic/common.py @@ -0,0 +1,157 @@ +"""Common classes and elements for Omnilogic Integration.""" + +from datetime import timedelta +import logging + +from omnilogic import OmniLogicException + +from homeassistant.const import ATTR_NAME +from homeassistant.core import HomeAssistant +from homeassistant.helpers.update_coordinator import ( + CoordinatorEntity, + DataUpdateCoordinator, + UpdateFailed, +) + +from .const import ( + ALL_ITEM_KINDS, + ATTR_IDENTIFIERS, + ATTR_MANUFACTURER, + ATTR_MODEL, + DOMAIN, +) + +_LOGGER = logging.getLogger(__name__) + + +class OmniLogicUpdateCoordinator(DataUpdateCoordinator): + """Class to manage fetching update data from single endpoint.""" + + def __init__( + self, + hass: HomeAssistant, + api: str, + name: str, + polling_interval: int, + ): + """Initialize the global Omnilogic data updater.""" + self.api = api + + super().__init__( + hass=hass, + logger=_LOGGER, + name=name, + update_interval=timedelta(seconds=polling_interval), + ) + + async def _async_update_data(self): + """Fetch data from OmniLogic.""" + try: + data = await self.api.get_telemetry_data() + + except OmniLogicException as error: + raise UpdateFailed(f"Error updating from OmniLogic: {error}") from error + + parsed_data = {} + + def get_item_data(item, item_kind, current_id, data): + """Get data per kind of Omnilogic API item.""" + if isinstance(item, list): + for single_item in item: + data = get_item_data(single_item, item_kind, current_id, data) + + if "systemId" in item: + system_id = item["systemId"] + current_id = current_id + (item_kind, system_id) + data[current_id] = item + + for kind in ALL_ITEM_KINDS: + if kind in item: + data = get_item_data(item[kind], kind, current_id, data) + + return data + + parsed_data = get_item_data(data, "Backyard", (), parsed_data) + + return parsed_data + + +class OmniLogicEntity(CoordinatorEntity): + """Defines the base OmniLogic entity.""" + + def __init__( + self, + coordinator: OmniLogicUpdateCoordinator, + kind: str, + name: str, + item_id: tuple, + icon: str, + ): + """Initialize the OmniLogic Entity.""" + super().__init__(coordinator) + + bow_id = None + entity_data = coordinator.data[item_id] + + backyard_id = item_id[:2] + if len(item_id) == 6: + bow_id = item_id[:4] + + msp_system_id = coordinator.data[backyard_id]["systemId"] + entity_friendly_name = f"{coordinator.data[backyard_id]['BackyardName']} " + unique_id = f"{msp_system_id}" + + if bow_id is not None: + unique_id = f"{unique_id}_{coordinator.data[bow_id]['systemId']}" + entity_friendly_name = ( + f"{entity_friendly_name}{coordinator.data[bow_id]['Name']} " + ) + + unique_id = f"{unique_id}_{coordinator.data[item_id]['systemId']}_{kind}" + + if entity_data.get("Name") is not None: + entity_friendly_name = f"{entity_friendly_name} {entity_data['Name']}" + + entity_friendly_name = f"{entity_friendly_name} {name}" + + unique_id = unique_id.replace(" ", "_") + + self._kind = kind + self._name = entity_friendly_name + self._unique_id = unique_id + self._item_id = item_id + self._icon = icon + self._attrs = {} + self._msp_system_id = msp_system_id + self._backyard_name = coordinator.data[backyard_id]["BackyardName"] + + @property + def unique_id(self) -> str: + """Return a unique, Home Assistant friendly identifier for this entity.""" + return self._unique_id + + @property + def name(self) -> str: + """Return the name of the entity.""" + return self._name + + @property + def icon(self): + """Return the icon for the entity.""" + return self._icon + + @property + def device_state_attributes(self): + """Return the attributes.""" + return self._attrs + + @property + def device_info(self): + """Define the device as back yard/MSP System.""" + + return { + ATTR_IDENTIFIERS: {(DOMAIN, self._msp_system_id)}, + ATTR_NAME: self._backyard_name, + ATTR_MANUFACTURER: "Hayward", + ATTR_MODEL: "OmniLogic", + } diff --git a/homeassistant/components/omnilogic/config_flow.py b/homeassistant/components/omnilogic/config_flow.py new file mode 100644 index 00000000000000..641ec5a8d94bfd --- /dev/null +++ b/homeassistant/components/omnilogic/config_flow.py @@ -0,0 +1,95 @@ +"""Config flow for Omnilogic integration.""" +import logging + +from omnilogic import LoginException, OmniLogic, OmniLogicException +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.core import callback +from homeassistant.helpers import aiohttp_client + +from .const import CONF_SCAN_INTERVAL, DOMAIN # pylint:disable=unused-import + +_LOGGER = logging.getLogger(__name__) + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Omnilogic.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + + @staticmethod + @callback + def async_get_options_flow(config_entry): + """Get the options flow for this handler.""" + return OptionsFlowHandler(config_entry) + + async def async_step_user(self, user_input=None): + """Handle the initial step.""" + errors = {} + + config_entry = self.hass.config_entries.async_entries(DOMAIN) + if config_entry: + return self.async_abort(reason="single_instance_allowed") + + errors = {} + + if user_input is not None: + username = user_input[CONF_USERNAME] + password = user_input[CONF_PASSWORD] + + session = aiohttp_client.async_get_clientsession(self.hass) + omni = OmniLogic(username, password, session) + + try: + await omni.connect() + except LoginException: + errors["base"] = "invalid_auth" + except OmniLogicException: + errors["base"] = "cannot_connect" + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + else: + await self.async_set_unique_id(user_input["username"]) + self._abort_if_unique_id_configured() + return self.async_create_entry(title="Omnilogic", data=user_input) + + return self.async_show_form( + step_id="user", + data_schema=vol.Schema( + { + vol.Required(CONF_USERNAME): str, + vol.Required(CONF_PASSWORD): str, + } + ), + errors=errors, + ) + + +class OptionsFlowHandler(config_entries.OptionsFlow): + """Handle Omnilogic client options.""" + + def __init__(self, config_entry): + """Initialize options flow.""" + self.config_entry = config_entry + + async def async_step_init(self, user_input=None): + """Manage 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.Optional( + CONF_SCAN_INTERVAL, + default=6, + ): int, + } + ), + ) diff --git a/homeassistant/components/omnilogic/const.py b/homeassistant/components/omnilogic/const.py new file mode 100644 index 00000000000000..a57ef2b062a928 --- /dev/null +++ b/homeassistant/components/omnilogic/const.py @@ -0,0 +1,29 @@ +"""Constants for the Omnilogic integration.""" + +DOMAIN = "omnilogic" +CONF_SCAN_INTERVAL = "polling_interval" +COORDINATOR = "coordinator" +OMNI_API = "omni_api" +ATTR_IDENTIFIERS = "identifiers" +ATTR_MANUFACTURER = "manufacturer" +ATTR_MODEL = "model" + +PUMP_TYPES = { + "FMT_VARIABLE_SPEED_PUMP": "VARIABLE", + "FMT_SINGLE_SPEED": "SINGLE", + "FMT_DUAL_SPEED": "DUAL", + "PMP_VARIABLE_SPEED_PUMP": "VARIABLE", + "PMP_SINGLE_SPEED": "SINGLE", + "PMP_DUAL_SPEED": "DUAL", +} + +ALL_ITEM_KINDS = { + "BOWS", + "Filter", + "Heater", + "Chlorinator", + "CSAD", + "Lights", + "Relays", + "Pumps", +} diff --git a/homeassistant/components/omnilogic/manifest.json b/homeassistant/components/omnilogic/manifest.json new file mode 100644 index 00000000000000..468b48d620a9d6 --- /dev/null +++ b/homeassistant/components/omnilogic/manifest.json @@ -0,0 +1,8 @@ +{ + "domain": "omnilogic", + "name": "Hayward Omnilogic", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/omnilogic", + "requirements": ["omnilogic==0.4.0"], + "codeowners": ["@oliver84","@djtimca","@gentoosu"] +} diff --git a/homeassistant/components/omnilogic/sensor.py b/homeassistant/components/omnilogic/sensor.py new file mode 100644 index 00000000000000..f4bb0f45d5ed8c --- /dev/null +++ b/homeassistant/components/omnilogic/sensor.py @@ -0,0 +1,356 @@ +"""Definition and setup of the Omnilogic Sensors for Home Assistant.""" + +import logging + +from homeassistant.components.sensor import DEVICE_CLASS_TEMPERATURE +from homeassistant.const import ( + CONCENTRATION_PARTS_PER_MILLION, + MASS_GRAMS, + PERCENTAGE, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, + VOLUME_LITERS, +) + +from .common import OmniLogicEntity, OmniLogicUpdateCoordinator +from .const import COORDINATOR, DOMAIN, PUMP_TYPES + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, entry, async_add_entities): + """Set up the sensor platform.""" + + coordinator = hass.data[DOMAIN][entry.entry_id][COORDINATOR] + entities = [] + + for item_id, item in coordinator.data.items(): + id_len = len(item_id) + item_kind = item_id[-2] + entity_settings = SENSOR_TYPES.get((id_len, item_kind)) + + if not entity_settings: + continue + + for entity_setting in entity_settings: + for state_key, entity_class in entity_setting["entity_classes"].items(): + if state_key not in item: + continue + + guard = False + for guard_condition in entity_setting["guard_condition"]: + if guard_condition and all( + item.get(guard_key) == guard_value + for guard_key, guard_value in guard_condition.items() + ): + guard = True + + if guard: + continue + + entity = entity_class( + coordinator=coordinator, + state_key=state_key, + name=entity_setting["name"], + kind=entity_setting["kind"], + item_id=item_id, + device_class=entity_setting["device_class"], + icon=entity_setting["icon"], + unit=entity_setting["unit"], + ) + + entities.append(entity) + + async_add_entities(entities) + + +class OmnilogicSensor(OmniLogicEntity): + """Defines an Omnilogic sensor entity.""" + + def __init__( + self, + coordinator: OmniLogicUpdateCoordinator, + kind: str, + name: str, + device_class: str, + icon: str, + unit: str, + item_id: tuple, + state_key: str, + ): + """Initialize Entities.""" + super().__init__( + coordinator=coordinator, + kind=kind, + name=name, + item_id=item_id, + icon=icon, + ) + + backyard_id = item_id[:2] + unit_type = coordinator.data[backyard_id].get("Unit-of-Measurement") + + self._unit_type = unit_type + self._device_class = device_class + self._unit = unit + self._state_key = state_key + + @property + def device_class(self): + """Return the device class of the entity.""" + return self._device_class + + @property + def unit_of_measurement(self): + """Return the right unit of measure.""" + return self._unit + + +class OmniLogicTemperatureSensor(OmnilogicSensor): + """Define an OmniLogic Temperature (Air/Water) Sensor.""" + + @property + def state(self): + """Return the state for the temperature sensor.""" + sensor_data = self.coordinator.data[self._item_id][self._state_key] + + hayward_state = sensor_data + hayward_unit_of_measure = TEMP_FAHRENHEIT + state = sensor_data + + if self._unit_type == "Metric": + hayward_state = round((hayward_state - 32) * 5 / 9, 1) + hayward_unit_of_measure = TEMP_CELSIUS + + if int(sensor_data) == -1: + hayward_state = None + state = None + + self._attrs["hayward_temperature"] = hayward_state + self._attrs["hayward_unit_of_measure"] = hayward_unit_of_measure + + self._unit = TEMP_FAHRENHEIT + + return state + + +class OmniLogicPumpSpeedSensor(OmnilogicSensor): + """Define an OmniLogic Pump Speed Sensor.""" + + @property + def state(self): + """Return the state for the pump speed sensor.""" + + pump_type = PUMP_TYPES[self.coordinator.data[self._item_id]["Filter-Type"]] + pump_speed = self.coordinator.data[self._item_id][self._state_key] + + if pump_type == "VARIABLE": + self._unit = PERCENTAGE + state = pump_speed + elif pump_type == "DUAL": + if pump_speed == 0: + state = "off" + elif pump_speed == self.coordinator.data[self._item_id].get( + "Min-Pump-Speed" + ): + state = "low" + elif pump_speed == self.coordinator.data[self._item_id].get( + "Max-Pump-Speed" + ): + state = "high" + + self._attrs["pump_type"] = pump_type + + return state + + +class OmniLogicSaltLevelSensor(OmnilogicSensor): + """Define an OmniLogic Salt Level Sensor.""" + + @property + def state(self): + """Return the state for the salt level sensor.""" + + salt_return = self.coordinator.data[self._item_id][self._state_key] + unit_of_measurement = self._unit + + if self._unit_type == "Metric": + salt_return = round(salt_return / 1000, 2) + unit_of_measurement = f"{MASS_GRAMS}/{VOLUME_LITERS}" + + self._unit = unit_of_measurement + + return salt_return + + +class OmniLogicChlorinatorSensor(OmnilogicSensor): + """Define an OmniLogic Chlorinator Sensor.""" + + @property + def state(self): + """Return the state for the chlorinator sensor.""" + state = self.coordinator.data[self._item_id][self._state_key] + + return state + + +class OmniLogicPHSensor(OmnilogicSensor): + """Define an OmniLogic pH Sensor.""" + + @property + def state(self): + """Return the state for the pH sensor.""" + + ph_state = self.coordinator.data[self._item_id][self._state_key] + + if ph_state == 0: + ph_state = None + + return ph_state + + +class OmniLogicORPSensor(OmnilogicSensor): + """Define an OmniLogic ORP Sensor.""" + + def __init__( + self, + coordinator: OmniLogicUpdateCoordinator, + state_key: str, + name: str, + kind: str, + item_id: tuple, + device_class: str, + icon: str, + unit: str, + ): + """Initialize the sensor.""" + super().__init__( + coordinator=coordinator, + kind=kind, + name=name, + device_class=device_class, + icon=icon, + unit=unit, + item_id=item_id, + state_key=state_key, + ) + + @property + def state(self): + """Return the state for the ORP sensor.""" + + orp_state = self.coordinator.data[self._item_id][self._state_key] + + if orp_state == -1: + orp_state = None + + return orp_state + + +SENSOR_TYPES = { + (2, "Backyard"): [ + { + "entity_classes": {"airTemp": OmniLogicTemperatureSensor}, + "name": "Air Temperature", + "kind": "air_temperature", + "device_class": DEVICE_CLASS_TEMPERATURE, + "icon": None, + "unit": TEMP_FAHRENHEIT, + "guard_condition": [{}], + }, + ], + (4, "BOWS"): [ + { + "entity_classes": {"waterTemp": OmniLogicTemperatureSensor}, + "name": "Water Temperature", + "kind": "water_temperature", + "device_class": DEVICE_CLASS_TEMPERATURE, + "icon": None, + "unit": TEMP_FAHRENHEIT, + "guard_condition": [{}], + }, + ], + (6, "Filter"): [ + { + "entity_classes": {"filterSpeed": OmniLogicPumpSpeedSensor}, + "name": "Speed", + "kind": "filter_pump_speed", + "device_class": None, + "icon": "mdi:speedometer", + "unit": PERCENTAGE, + "guard_condition": [ + {"Type": "FMT_SINGLE_SPEED"}, + ], + }, + ], + (6, "Pumps"): [ + { + "entity_classes": {"pumpSpeed": OmniLogicPumpSpeedSensor}, + "name": "Pump Speed", + "kind": "pump_speed", + "device_class": None, + "icon": "mdi:speedometer", + "unit": PERCENTAGE, + "guard_condition": [ + {"Type": "PMP_SINGLE_SPEED"}, + ], + }, + ], + (6, "Chlorinator"): [ + { + "entity_classes": {"Timed-Percent": OmniLogicChlorinatorSensor}, + "name": "Setting", + "kind": "chlorinator", + "device_class": None, + "icon": "mdi:gauge", + "unit": PERCENTAGE, + "guard_condition": [ + { + "Shared-Type": "BOW_SHARED_EQUIPMENT", + "status": "0", + }, + { + "operatingMode": "2", + }, + ], + }, + { + "entity_classes": {"avgSaltLevel": OmniLogicSaltLevelSensor}, + "name": "Salt Level", + "kind": "salt_level", + "device_class": None, + "icon": "mdi:gauge", + "unit": CONCENTRATION_PARTS_PER_MILLION, + "guard_condition": [ + { + "Shared-Type": "BOW_SHARED_EQUIPMENT", + "status": "0", + }, + ], + }, + ], + (6, "CSAD"): [ + { + "entity_classes": {"ph": OmniLogicPHSensor}, + "name": "pH", + "kind": "csad_ph", + "device_class": None, + "icon": "mdi:gauge", + "unit": "pH", + "guard_condition": [ + {"ph": ""}, + ], + }, + { + "entity_classes": {"orp": OmniLogicORPSensor}, + "name": "ORP", + "kind": "csad_orp", + "device_class": None, + "icon": "mdi:gauge", + "unit": "mV", + "guard_condition": [ + {"orp": ""}, + ], + }, + ], +} diff --git a/homeassistant/components/omnilogic/strings.json b/homeassistant/components/omnilogic/strings.json new file mode 100644 index 00000000000000..285bc29b802c9e --- /dev/null +++ b/homeassistant/components/omnilogic/strings.json @@ -0,0 +1,30 @@ +{ + "title": "Omnilogic", + "config": { + "step": { + "user": { + "data": { + "username": "[%key:common::config_flow::data::username%]", + "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%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + }, + "abort": { + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" + } + }, + "options": { + "step": { + "init": { + "data": { + "polling_interval": "Polling interval (in seconds)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/omnilogic/translations/ca.json b/homeassistant/components/omnilogic/translations/ca.json new file mode 100644 index 00000000000000..53c8755dd36934 --- /dev/null +++ b/homeassistant/components/omnilogic/translations/ca.json @@ -0,0 +1,30 @@ +{ + "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", + "unknown": "Error inesperat" + }, + "step": { + "user": { + "data": { + "password": "Contrasenya", + "username": "Nom d'usuari" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "polling_interval": "Interval d'escaneig (segons)" + } + } + } + }, + "title": "Omnilogic" +} \ No newline at end of file diff --git a/homeassistant/components/omnilogic/translations/de.json b/homeassistant/components/omnilogic/translations/de.json new file mode 100644 index 00000000000000..c400283458932d --- /dev/null +++ b/homeassistant/components/omnilogic/translations/de.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Passwort", + "username": "Benutzername" + } + } + } + }, + "title": "Omnilogic" +} \ No newline at end of file diff --git a/homeassistant/components/omnilogic/translations/el.json b/homeassistant/components/omnilogic/translations/el.json new file mode 100644 index 00000000000000..fbe77ec16c0ed4 --- /dev/null +++ b/homeassistant/components/omnilogic/translations/el.json @@ -0,0 +1,30 @@ +{ + "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." + }, + "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\u03b7 \u03b1\u03c5\u03b8\u03b5\u03bd\u03c4\u03b9\u03ba\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", + "unknown": "\u039c\u03b7 \u03b1\u03bd\u03b1\u03bc\u03b5\u03bd\u03cc\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "step": { + "user": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "polling_interval": "\u0394\u03b9\u03ac\u03c3\u03c4\u03b7\u03bc\u03b1 \u03bb\u03ae\u03c8\u03b7\u03c2 (\u03c3\u03b5 \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1)" + } + } + } + }, + "title": "Omnilogic" +} \ No newline at end of file diff --git a/homeassistant/components/omnilogic/translations/en.json b/homeassistant/components/omnilogic/translations/en.json new file mode 100644 index 00000000000000..858cfe31323d3d --- /dev/null +++ b/homeassistant/components/omnilogic/translations/en.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Already configured. Only a single configuration possible." + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "step": { + "user": { + "data": { + "password": "Password", + "username": "Username" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "polling_interval": "Polling interval (in seconds)" + } + } + } + }, + "title": "Omnilogic" +} \ No newline at end of file diff --git a/homeassistant/components/omnilogic/translations/es.json b/homeassistant/components/omnilogic/translations/es.json new file mode 100644 index 00000000000000..849cd73b40f73d --- /dev/null +++ b/homeassistant/components/omnilogic/translations/es.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ya est\u00e1 configurado. S\u00f3lo es posible una \u00fanica configuraci\u00f3n." + }, + "error": { + "cannot_connect": "No se pudo conectar", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", + "unknown": "Error inesperado" + }, + "step": { + "user": { + "data": { + "password": "Contrase\u00f1a", + "username": "Usuario" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "polling_interval": "Intervalo de sondeo (en segundos)" + } + } + } + }, + "title": "Omnilogic" +} \ No newline at end of file diff --git a/homeassistant/components/omnilogic/translations/et.json b/homeassistant/components/omnilogic/translations/et.json new file mode 100644 index 00000000000000..41abe3283eb172 --- /dev/null +++ b/homeassistant/components/omnilogic/translations/et.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + }, + "error": { + "cannot_connect": "\u00dchendus nurjus", + "invalid_auth": "Tuvastamise viga", + "unknown": "Tundmatu viga" + }, + "step": { + "user": { + "data": { + "password": "Salas\u00f5na", + "username": "Kasutajanimi" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "polling_interval": "P\u00e4ringute intervall (sekundites)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/omnilogic/translations/fr.json b/homeassistant/components/omnilogic/translations/fr.json new file mode 100644 index 00000000000000..167beb756a8f1d --- /dev/null +++ b/homeassistant/components/omnilogic/translations/fr.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." + }, + "error": { + "cannot_connect": "\u00c9chec de connexion", + "invalid_auth": "Authentification invalide", + "unknown": "Erreur inattendue" + }, + "step": { + "user": { + "data": { + "password": "Mot de passe", + "username": "Nom d'utilisateur" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/omnilogic/translations/it.json b/homeassistant/components/omnilogic/translations/it.json new file mode 100644 index 00000000000000..38ace995177264 --- /dev/null +++ b/homeassistant/components/omnilogic/translations/it.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." + }, + "error": { + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "step": { + "user": { + "data": { + "password": "Password", + "username": "Nome utente" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "polling_interval": "Intervallo di scansione (in secondi)" + } + } + } + }, + "title": "Omnilogic" +} \ No newline at end of file diff --git a/homeassistant/components/omnilogic/translations/ko.json b/homeassistant/components/omnilogic/translations/ko.json new file mode 100644 index 00000000000000..686ca520bff1db --- /dev/null +++ b/homeassistant/components/omnilogic/translations/ko.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\uc774\ubbf8 \uc124\uc815\ub418\uc5b4 \uc788\uc74c. \ud558\ub098\uc758 \uc124\uc815\ub9cc \uac00\ub2a5\ud568." + }, + "error": { + "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", + "invalid_auth": "\uc798\ubabb\ub41c \uc778\uc99d", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec" + }, + "step": { + "user": { + "data": { + "password": "\uc554\ud638", + "username": "\uc0ac\uc6a9\uc790\uba85" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "polling_interval": "\ud3f4\ub9c1 \uac04\uaca9(\ucd08)" + } + } + } + }, + "title": "Omnilogic" +} \ No newline at end of file diff --git a/homeassistant/components/omnilogic/translations/lb.json b/homeassistant/components/omnilogic/translations/lb.json new file mode 100644 index 00000000000000..22f3cb54a6ed0d --- /dev/null +++ b/homeassistant/components/omnilogic/translations/lb.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech." + }, + "error": { + "cannot_connect": "Feeler beim verbannen", + "invalid_auth": "\u00a7", + "unknown": "Onerwaarte Feeler" + }, + "step": { + "user": { + "data": { + "password": "Passwuert", + "username": "Benotzernumm" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "polling_interval": "Intervall vun den Offroen (sekonnen)" + } + } + } + }, + "title": "Omnilogic" +} \ No newline at end of file diff --git a/homeassistant/components/omnilogic/translations/nl.json b/homeassistant/components/omnilogic/translations/nl.json new file mode 100644 index 00000000000000..2f7e9cfbd12e4c --- /dev/null +++ b/homeassistant/components/omnilogic/translations/nl.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Benutzername" + } + } + } + }, + "title": "Omnilogic" +} \ No newline at end of file diff --git a/homeassistant/components/omnilogic/translations/no.json b/homeassistant/components/omnilogic/translations/no.json new file mode 100644 index 00000000000000..ebadb3a40e453a --- /dev/null +++ b/homeassistant/components/omnilogic/translations/no.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." + }, + "error": { + "cannot_connect": "Tilkobling mislyktes.", + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" + }, + "step": { + "user": { + "data": { + "password": "Passord", + "username": "Brukernavn" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "polling_interval": "Avstemningsintervall (i sekunder)" + } + } + } + }, + "title": "Omnilogic" +} \ No newline at end of file diff --git a/homeassistant/components/omnilogic/translations/pl.json b/homeassistant/components/omnilogic/translations/pl.json new file mode 100644 index 00000000000000..10cbbdb12b792f --- /dev/null +++ b/homeassistant/components/omnilogic/translations/pl.json @@ -0,0 +1,30 @@ +{ + "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", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "polling_interval": "Cz\u0119stotliwo\u015b\u0107 aktualizacji (w sekundach)" + } + } + } + }, + "title": "Omnilogic" +} \ No newline at end of file diff --git a/homeassistant/components/omnilogic/translations/ru.json b/homeassistant/components/omnilogic/translations/ru.json new file mode 100644 index 00000000000000..3b05c74695df26 --- /dev/null +++ b/homeassistant/components/omnilogic/translations/ru.json @@ -0,0 +1,30 @@ +{ + "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\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": { + "data": { + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "username": "\u041b\u043e\u0433\u0438\u043d" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "polling_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043e\u043f\u0440\u043e\u0441\u0430 (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" + } + } + } + }, + "title": "Omnilogic" +} \ No newline at end of file diff --git a/homeassistant/components/omnilogic/translations/zh-Hant.json b/homeassistant/components/omnilogic/translations/zh-Hant.json new file mode 100644 index 00000000000000..335e26ced8cf23 --- /dev/null +++ b/homeassistant/components/omnilogic/translations/zh-Hant.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "user": { + "data": { + "password": "\u5bc6\u78bc", + "username": "\u4f7f\u7528\u8005\u540d\u7a31" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "polling_interval": "\u66f4\u65b0\u9593\u8ddd\uff08\u79d2\uff09" + } + } + } + }, + "title": "Omnilogic" +} \ No newline at end of file diff --git a/homeassistant/components/onboarding/manifest.json b/homeassistant/components/onboarding/manifest.json index 81e88e99edb23f..e2fb8e084b83b8 100644 --- a/homeassistant/components/onboarding/manifest.json +++ b/homeassistant/components/onboarding/manifest.json @@ -2,7 +2,16 @@ "domain": "onboarding", "name": "Home Assistant Onboarding", "documentation": "https://www.home-assistant.io/integrations/onboarding", - "dependencies": ["auth", "http", "person"], - "codeowners": ["@home-assistant/core"], + "after_dependencies": [ + "hassio" + ], + "dependencies": [ + "auth", + "http", + "person" + ], + "codeowners": [ + "@home-assistant/core" + ], "quality_scale": "internal" } diff --git a/homeassistant/components/onboarding/views.py b/homeassistant/components/onboarding/views.py index a2a4fb15fd74e4..0faf099b9bf0fb 100644 --- a/homeassistant/components/onboarding/views.py +++ b/homeassistant/components/onboarding/views.py @@ -159,6 +159,14 @@ async def post(self, request): "met", context={"source": "onboarding"} ) + if ( + hass.components.hassio.is_hassio() + and "raspberrypi" in hass.components.hassio.get_core_info()["machine"] + ): + await hass.config_entries.flow.async_init( + "rpi_power", context={"source": "onboarding"} + ) + return self.json({}) diff --git a/homeassistant/components/onewire/__init__.py b/homeassistant/components/onewire/__init__.py index ac5d139337814d..21dc0c2ead351f 100644 --- a/homeassistant/components/onewire/__init__.py +++ b/homeassistant/components/onewire/__init__.py @@ -1 +1 @@ -"""The onewire component.""" +"""The 1-Wire component.""" diff --git a/homeassistant/components/onewire/const.py b/homeassistant/components/onewire/const.py new file mode 100644 index 00000000000000..af68135af1090c --- /dev/null +++ b/homeassistant/components/onewire/const.py @@ -0,0 +1,12 @@ +"""Constants for 1-Wire component.""" +CONF_MOUNT_DIR = "mount_dir" +CONF_NAMES = "names" + +DEFAULT_OWSERVER_PORT = 4304 +DEFAULT_SYSBUS_MOUNT_DIR = "/sys/bus/w1/devices/" + +DOMAIN = "onewire" + +SUPPORTED_PLATFORMS = [ + "sensor", +] diff --git a/homeassistant/components/onewire/sensor.py b/homeassistant/components/onewire/sensor.py index 148c596e130bb8..2ac00c814b2fea 100644 --- a/homeassistant/components/onewire/sensor.py +++ b/homeassistant/components/onewire/sensor.py @@ -12,6 +12,7 @@ CONF_HOST, CONF_PORT, ELECTRICAL_CURRENT_AMPERE, + LIGHT_LUX, PERCENTAGE, TEMP_CELSIUS, VOLT, @@ -19,12 +20,15 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -_LOGGER = logging.getLogger(__name__) +from .const import ( + CONF_MOUNT_DIR, + CONF_NAMES, + DEFAULT_OWSERVER_PORT, + DEFAULT_SYSBUS_MOUNT_DIR, +) -CONF_MOUNT_DIR = "mount_dir" -CONF_NAMES = "names" +_LOGGER = logging.getLogger(__name__) -DEFAULT_MOUNT_DIR = "/sys/bus/w1/devices/" DEVICE_SENSORS = { # Family : { SensorType: owfs path } "10": {"temperature": "temperature"}, @@ -33,6 +37,10 @@ "26": { "temperature": "temperature", "humidity": "humidity", + "humidity_hih3600": "HIH3600/humidity", + "humidity_hih4000": "HIH4000/humidity", + "humidity_hih5030": "HIH5030/humidity", + "humidity_htm1735": "HTM1735/humidity", "pressure": "B1-R1-A/pressure", "illuminance": "S3-R1-A/illuminance", "voltage_VAD": "VAD", @@ -68,9 +76,13 @@ # SensorType: [ Measured unit, Unit ] "temperature": ["temperature", TEMP_CELSIUS], "humidity": ["humidity", PERCENTAGE], + "humidity_hih3600": ["humidity", PERCENTAGE], + "humidity_hih4000": ["humidity", PERCENTAGE], + "humidity_hih5030": ["humidity", PERCENTAGE], + "humidity_htm1735": ["humidity", PERCENTAGE], "humidity_raw": ["humidity", PERCENTAGE], "pressure": ["pressure", "mb"], - "illuminance": ["illuminance", "lux"], + "illuminance": ["illuminance", LIGHT_LUX], "wetness_0": ["wetness", PERCENTAGE], "wetness_1": ["wetness", PERCENTAGE], "wetness_2": ["wetness", PERCENTAGE], @@ -91,9 +103,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Optional(CONF_NAMES): {cv.string: cv.string}, - vol.Optional(CONF_MOUNT_DIR, default=DEFAULT_MOUNT_DIR): cv.string, + vol.Optional(CONF_MOUNT_DIR, default=DEFAULT_SYSBUS_MOUNT_DIR): cv.string, vol.Optional(CONF_HOST): cv.string, - vol.Optional(CONF_PORT, default=4304): cv.port, + vol.Optional(CONF_PORT, default=DEFAULT_OWSERVER_PORT): cv.port, } ) @@ -107,14 +119,10 @@ def hb_info_from_type(dev_type="std"): def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the one wire Sensors.""" + """Set up 1-Wire platform.""" base_dir = config[CONF_MOUNT_DIR] owport = config[CONF_PORT] owhost = config.get(CONF_HOST) - if owhost: - _LOGGER.debug("Initializing using %s:%s", owhost, owport) - else: - _LOGGER.debug("Initializing using %s", base_dir) devs = [] device_names = {} @@ -124,6 +132,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): # We have an owserver on a remote(or local) host/port if owhost: + _LOGGER.debug("Initializing using %s:%s", owhost, owport) try: owproxy = protocol.proxy(host=owhost, port=owport) devices = owproxy.dir() @@ -154,7 +163,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): owproxy.read(f"{device}moisture/is_leaf.{s_id}").decode() ) if is_leaf: - sensor_key = f"wetness_{id}" + sensor_key = f"wetness_{s_id}" sensor_id = os.path.split(os.path.split(device)[0])[1] device_file = os.path.join(os.path.split(device)[0], sensor_value) devs.append( @@ -167,7 +176,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ) # We have a raw GPIO ow sensor on a Pi - elif base_dir == DEFAULT_MOUNT_DIR: + elif base_dir == DEFAULT_SYSBUS_MOUNT_DIR: + _LOGGER.debug("Initializing using SysBus %s", base_dir) for device_family in DEVICE_SENSORS: for device_folder in glob(os.path.join(base_dir, f"{device_family}[.-]*")): sensor_id = os.path.split(device_folder)[1] @@ -182,6 +192,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): # We have an owfs mounted else: + _LOGGER.debug("Initializing using OWFS %s", base_dir) for family_file_path in glob(os.path.join(base_dir, "*", "family")): with open(family_file_path) as family_file: family = family_file.read() @@ -213,7 +224,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class OneWire(Entity): - """Implementation of an One wire Sensor.""" + """Implementation of a 1-Wire sensor.""" def __init__(self, name, device_file, sensor_type): """Initialize the sensor.""" @@ -258,10 +269,10 @@ def unique_id(self) -> str: class OneWireProxy(OneWire): - """Implementation of a One wire Sensor through owserver.""" + """Implementation of a 1-Wire sensor through owserver.""" def __init__(self, name, device_file, sensor_type, owproxy): - """Initialize the onewire sensor via owserver.""" + """Initialize the sensor.""" super().__init__(name, device_file, sensor_type) self._owproxy = owproxy @@ -287,7 +298,7 @@ def update(self): class OneWireDirect(OneWire): - """Implementation of an One wire Sensor directly connected to RPI GPIO.""" + """Implementation of a 1-Wire sensor directly connected to RPI GPIO.""" def update(self): """Get the latest data from the device.""" @@ -305,7 +316,7 @@ def update(self): class OneWireOWFS(OneWire): - """Implementation of an One wire Sensor through owfs.""" + """Implementation of a 1-Wire sensor through owfs.""" def update(self): """Get the latest data from the device.""" diff --git a/homeassistant/components/onvif/__init__.py b/homeassistant/components/onvif/__init__.py index 964c7a70a6ddb7..cf92f3df3ba35b 100644 --- a/homeassistant/components/onvif/__init__.py +++ b/homeassistant/components/onvif/__init__.py @@ -17,6 +17,7 @@ EVENT_HOMEASSISTANT_STOP, HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION, + HTTP_UNAUTHORIZED, ) from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady @@ -138,7 +139,7 @@ def _get(): try: response = await hass.async_add_executor_job(_get) - if response.status_code == 401: + if response.status_code == HTTP_UNAUTHORIZED: return HTTP_BASIC_AUTHENTICATION return HTTP_DIGEST_AUTHENTICATION diff --git a/homeassistant/components/onvif/config_flow.py b/homeassistant/components/onvif/config_flow.py index cbe6d03fa68b25..ccb301a455fd3e 100644 --- a/homeassistant/components/onvif/config_flow.py +++ b/homeassistant/components/onvif/config_flow.py @@ -262,7 +262,7 @@ async def async_step_profiles(self, user_input=None): return self.async_abort(reason="onvif_error") except Fault: - errors["base"] = "connection_failed" + errors["base"] = "cannot_connect" return self.async_show_form(step_id="auth", errors=errors) diff --git a/homeassistant/components/onvif/strings.json b/homeassistant/components/onvif/strings.json index b6ae9b98d9abb7..dac8ef8647de2d 100644 --- a/homeassistant/components/onvif/strings.json +++ b/homeassistant/components/onvif/strings.json @@ -1,14 +1,14 @@ { "config": { "abort": { - "already_configured": "ONVIF device is already configured.", - "already_in_progress": "Config flow for ONVIF device is already in progress.", + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", "onvif_error": "Error setting up ONVIF device. Check logs for more information.", "no_h264": "There were no H264 streams available. Check the profile configuration on your device.", "no_mac": "Could not configure unique ID for ONVIF device." }, "error": { - "connection_failed": "Could not connect to ONVIF service with provided credentials." + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, "step": { "user": { @@ -23,7 +23,7 @@ }, "manual_input": { "data": { - "name": "Name", + "name": "[%key:common::config_flow::data::name%]", "host": "[%key:common::config_flow::data::host%]", "port": "[%key:common::config_flow::data::port%]" }, diff --git a/homeassistant/components/onvif/translations/ca.json b/homeassistant/components/onvif/translations/ca.json index a0362abbb8fa3e..99701f4020ba12 100644 --- a/homeassistant/components/onvif/translations/ca.json +++ b/homeassistant/components/onvif/translations/ca.json @@ -1,13 +1,14 @@ { "config": { "abort": { - "already_configured": "Dispositiu ONVIF ja configurat.", - "already_in_progress": "El flux de dades de configuraci\u00f3 pel dispositiu ONVIF ja est\u00e0 en curs.", + "already_configured": "El dispositiu ja est\u00e0 configurat", + "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", "no_h264": "No s'han torbat fluxos (streams) H264 disponibles. Comporva la configuraci\u00f3 de perfil en el dispositiu.", "no_mac": "No s'ha pogut configurar un ID \u00fanic pel dispositiu ONVIF.", "onvif_error": "Error durant la configuraci\u00f3 del dispositiu ONVIF. Consulta els registres per a m\u00e9s informaci\u00f3." }, "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", "connection_failed": "No s'ha pogut connectar al servei ONVIF amb les credencials proporcionades." }, "step": { diff --git a/homeassistant/components/onvif/translations/en.json b/homeassistant/components/onvif/translations/en.json index a20b1fcb7e4826..e414d5a6d8dbb9 100644 --- a/homeassistant/components/onvif/translations/en.json +++ b/homeassistant/components/onvif/translations/en.json @@ -1,13 +1,14 @@ { "config": { "abort": { - "already_configured": "ONVIF device is already configured.", - "already_in_progress": "Config flow for ONVIF device is already in progress.", + "already_configured": "Device is already configured", + "already_in_progress": "Configuration flow is already in progress", "no_h264": "There were no H264 streams available. Check the profile configuration on your device.", "no_mac": "Could not configure unique ID for ONVIF device.", "onvif_error": "Error setting up ONVIF device. Check logs for more information." }, "error": { + "cannot_connect": "Failed to connect", "connection_failed": "Could not connect to ONVIF service with provided credentials." }, "step": { diff --git a/homeassistant/components/onvif/translations/es.json b/homeassistant/components/onvif/translations/es.json index af283fe038bedf..9cb854b5988f62 100644 --- a/homeassistant/components/onvif/translations/es.json +++ b/homeassistant/components/onvif/translations/es.json @@ -8,6 +8,7 @@ "onvif_error": "Error de configuraci\u00f3n del dispositivo ONVIF. Revise los registros para m\u00e1s informaci\u00f3n." }, "error": { + "cannot_connect": "No se pudo conectar", "connection_failed": "No se pudo conectar al servicio ONVIF con las credenciales proporcionadas." }, "step": { diff --git a/homeassistant/components/onvif/translations/et.json b/homeassistant/components/onvif/translations/et.json new file mode 100644 index 00000000000000..8f0eb1c05f1ebc --- /dev/null +++ b/homeassistant/components/onvif/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00dchendamine nurjus" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/onvif/translations/it.json b/homeassistant/components/onvif/translations/it.json index 374c65b99a1118..2183945d680951 100644 --- a/homeassistant/components/onvif/translations/it.json +++ b/homeassistant/components/onvif/translations/it.json @@ -1,13 +1,14 @@ { "config": { "abort": { - "already_configured": "Il dispositivo ONVIF \u00e8 gi\u00e0 configurato.", - "already_in_progress": "Il flusso di configurazione per il dispositivo ONVIF \u00e8 gi\u00e0 in corso.", + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "no_h264": "Non c'erano flussi H264 disponibili. Controllare la configurazione del profilo sul dispositivo.", "no_mac": "Impossibile configurare l'ID univoco per il dispositivo ONVIF.", "onvif_error": "Errore durante la configurazione del dispositivo ONVIF. Controllare i registri per ulteriori informazioni." }, "error": { + "cannot_connect": "Impossibile connettersi", "connection_failed": "Impossibile connettersi al servizio ONVIF con le credenziali fornite." }, "step": { diff --git a/homeassistant/components/onvif/translations/no.json b/homeassistant/components/onvif/translations/no.json index 4f605a518d765b..13cff6e14e2fc4 100644 --- a/homeassistant/components/onvif/translations/no.json +++ b/homeassistant/components/onvif/translations/no.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "ONVIF-enheten er allerede konfigurert.", - "already_in_progress": "Konfigurasjonsflyt for ONVIF-enhet p\u00e5g\u00e5r allerede.", + "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", "no_h264": "Det var ingen H264-str\u00f8mmer tilgjengelig. Sjekk profilkonfigurasjonen p\u00e5 enheten din.", "no_mac": "Kunne ikke konfigurere unik ID for ONVIF-enhet.", "onvif_error": "Feil ved konfigurering av ONVIF-enhet. Sjekk logger for mer informasjon." diff --git a/homeassistant/components/onvif/translations/pl.json b/homeassistant/components/onvif/translations/pl.json index d60d45c746f78d..57389baafbd270 100644 --- a/homeassistant/components/onvif/translations/pl.json +++ b/homeassistant/components/onvif/translations/pl.json @@ -1,13 +1,14 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", "already_in_progress": "Proces konfiguracji dla urz\u0105dzenia ONVIF jest ju\u017c w toku.", "no_h264": "Nie by\u0142o dost\u0119pnych \u017cadnych strumieni H264. Sprawd\u017a konfiguracj\u0119 profilu w swoim urz\u0105dzeniu.", "no_mac": "Nie mo\u017cna utworzy\u0107 unikalnego identyfikatora urz\u0105dzenia ONVIF.", "onvif_error": "Wyst\u0105pi\u0142 b\u0142\u0105d podczas konfigurowania urz\u0105dzenia ONVIF. Sprawd\u017a logi, aby uzyska\u0107 wi\u0119cej informacji." }, "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "connection_failed": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z us\u0142ug\u0105 ONVIF z podanymi po\u015bwiadczeniami." }, "step": { diff --git a/homeassistant/components/onvif/translations/ru.json b/homeassistant/components/onvif/translations/ru.json index 84ff17750803f7..7d6cd66f8cd9c2 100644 --- a/homeassistant/components/onvif/translations/ru.json +++ b/homeassistant/components/onvif/translations/ru.json @@ -1,13 +1,14 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", - "already_in_progress": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.", + "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.", "no_h264": "\u041d\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b\u0445 \u043f\u043e\u0442\u043e\u043a\u043e\u0432 H264. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043d\u0430 \u0412\u0430\u0448\u0435\u043c \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435.", "no_mac": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \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.", "onvif_error": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043b\u043e\u0433\u0438 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438." }, "error": { + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "connection_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u0441\u043b\u0443\u0436\u0431\u0435 ONVIF \u0441 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u043c\u0438 \u0443\u0447\u0435\u0442\u043d\u044b\u043c\u0438 \u0434\u0430\u043d\u043d\u044b\u043c\u0438." }, "step": { diff --git a/homeassistant/components/onvif/translations/zh-Hant.json b/homeassistant/components/onvif/translations/zh-Hant.json index 794cae79683d8b..c4bac0857a0ebf 100644 --- a/homeassistant/components/onvif/translations/zh-Hant.json +++ b/homeassistant/components/onvif/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "ONVIF \u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3002", - "already_in_progress": "ONVIF \u8a2d\u5099\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d\u3002", + "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "no_h264": "\u8a72\u8a2d\u5099\u4e0d\u652f\u63f4 H264 \u4e32\u6d41\uff0c\u8acb\u6aa2\u67e5\u8a2d\u5099\u8a2d\u5b9a\u3002", "no_mac": "\u7121\u6cd5\u70ba ONVIF \u8a2d\u5099\u8a2d\u5b9a\u552f\u4e00 ID\u3002", "onvif_error": "\u8a2d\u5b9a ONVIF \u8a2d\u5099\u932f\u8aa4\uff0c\u8acb\u53c3\u95b1\u65e5\u8a8c\u4ee5\u7372\u5f97\u66f4\u8a73\u7d30\u8cc7\u8a0a\u3002" diff --git a/homeassistant/components/openalpr_local/image_processing.py b/homeassistant/components/openalpr_local/image_processing.py index c7c96b05872c25..644f2c687b298a 100644 --- a/homeassistant/components/openalpr_local/image_processing.py +++ b/homeassistant/components/openalpr_local/image_processing.py @@ -104,9 +104,7 @@ def device_class(self): @property def state_attributes(self): """Return device specific state attributes.""" - attr = {ATTR_PLATES: self.plates, ATTR_VEHICLES: self.vehicles} - - return attr + return {ATTR_PLATES: self.plates, ATTR_VEHICLES: self.vehicles} def process_plates(self, plates, vehicles): """Send event with new plates and store data.""" diff --git a/homeassistant/components/openhome/media_player.py b/homeassistant/components/openhome/media_player.py index b4258a883473ef..f9dbd4272ba184 100644 --- a/homeassistant/components/openhome/media_player.py +++ b/homeassistant/components/openhome/media_player.py @@ -187,11 +187,6 @@ def supported_features(self): """Flag of features commands that are supported.""" return self._supported_features - @property - def should_poll(self): - """Return the polling state.""" - return True - @property def unique_id(self): """Return a unique ID.""" diff --git a/homeassistant/components/opentherm_gw/const.py b/homeassistant/components/opentherm_gw/const.py index 0b696ed933948b..3ff1577c43657b 100644 --- a/homeassistant/components/opentherm_gw/const.py +++ b/homeassistant/components/opentherm_gw/const.py @@ -1,9 +1,11 @@ """Constants for the opentherm_gw integration.""" import pyotgw.vars as gw_vars +from homeassistant.components.binary_sensor import DEVICE_CLASS_PROBLEM from homeassistant.const import ( DEVICE_CLASS_TEMPERATURE, PERCENTAGE, + PRESSURE_BAR, TEMP_CELSIUS, TIME_HOURS, TIME_MINUTES, @@ -23,7 +25,6 @@ DEVICE_CLASS_COLD = "cold" DEVICE_CLASS_HEAT = "heat" -DEVICE_CLASS_PROBLEM = "problem" DOMAIN = "opentherm_gw" @@ -39,7 +40,6 @@ SERVICE_SET_OAT = "set_outside_temperature" SERVICE_SET_SB_TEMP = "set_setback_temperature" -UNIT_BAR = "bar" UNIT_KW = "kW" UNIT_L_MIN = f"L/{TIME_MINUTES}" @@ -152,7 +152,11 @@ "Room Setpoint {}", ], gw_vars.DATA_REL_MOD_LEVEL: [None, PERCENTAGE, "Relative Modulation Level {}"], - gw_vars.DATA_CH_WATER_PRESS: [None, UNIT_BAR, "Central Heating Water Pressure {}"], + gw_vars.DATA_CH_WATER_PRESS: [ + None, + PRESSURE_BAR, + "Central Heating Water Pressure {}", + ], gw_vars.DATA_DHW_FLOW_RATE: [None, UNIT_L_MIN, "Hot Water Flow Rate {}"], gw_vars.DATA_ROOM_SETPOINT_2: [ DEVICE_CLASS_TEMPERATURE, diff --git a/homeassistant/components/opentherm_gw/strings.json b/homeassistant/components/opentherm_gw/strings.json index eb074e608ca6be..332b97eb5ee3a6 100644 --- a/homeassistant/components/opentherm_gw/strings.json +++ b/homeassistant/components/opentherm_gw/strings.json @@ -3,7 +3,11 @@ "step": { "init": { "title": "OpenTherm Gateway", - "data": { "name": "Name", "device": "Path or URL", "id": "ID" } + "data": { + "name": "[%key:common::config_flow::data::name%]", + "device": "Path or URL", + "id": "ID" + } } }, "error": { diff --git a/homeassistant/components/openuv/strings.json b/homeassistant/components/openuv/strings.json index 0777b139cf97cb..039a7a9d68912e 100644 --- a/homeassistant/components/openuv/strings.json +++ b/homeassistant/components/openuv/strings.json @@ -6,8 +6,8 @@ "data": { "api_key": "[%key:common::config_flow::data::api_key%]", "elevation": "Elevation", - "latitude": "Latitude", - "longitude": "Longitude" + "latitude": "[%key:common::config_flow::data::latitude%]", + "longitude": "[%key:common::config_flow::data::longitude%]" } } }, diff --git a/homeassistant/components/openuv/translations/et.json b/homeassistant/components/openuv/translations/et.json new file mode 100644 index 00000000000000..aae3ef835bbe20 --- /dev/null +++ b/homeassistant/components/openuv/translations/et.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "latitude": "Laiuskraad", + "longitude": "Pikkuskraad" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openuv/translations/it.json b/homeassistant/components/openuv/translations/it.json index e6113500fbe6b1..dc150a34db84c2 100644 --- a/homeassistant/components/openuv/translations/it.json +++ b/homeassistant/components/openuv/translations/it.json @@ -13,7 +13,7 @@ "api_key": "Chiave API", "elevation": "Altitudine", "latitude": "Latitudine", - "longitude": "Longitudine" + "longitude": "Logitudine" }, "title": "Inserisci i tuoi dati" } diff --git a/homeassistant/components/openuv/translations/pl.json b/homeassistant/components/openuv/translations/pl.json index 28a43bd10d9616..003f898dfab98f 100644 --- a/homeassistant/components/openuv/translations/pl.json +++ b/homeassistant/components/openuv/translations/pl.json @@ -1,5 +1,8 @@ { "config": { + "abort": { + "already_configured": "Wsp\u00f3\u0142rz\u0119dne s\u0105 ju\u017c zarejestrowane." + }, "error": { "identifier_exists": "Wsp\u00f3\u0142rz\u0119dne s\u0105 ju\u017c zarejestrowane.", "invalid_api_key": "Nieprawid\u0142owy klucz API" diff --git a/homeassistant/components/openweathermap/config_flow.py b/homeassistant/components/openweathermap/config_flow.py index 365f55c5d44c82..1a50b035db868e 100644 --- a/homeassistant/components/openweathermap/config_flow.py +++ b/homeassistant/components/openweathermap/config_flow.py @@ -69,11 +69,11 @@ async def async_step_user(self, user_input=None): self.hass, user_input[CONF_API_KEY] ) if not api_online: - errors["base"] = "auth" + errors["base"] = "invalid_api_key" except UnauthorizedError: - errors["base"] = "auth" + errors["base"] = "invalid_api_key" except APICallError: - errors["base"] = "connection" + errors["base"] = "cannot_connect" if not errors: return self.async_create_entry( @@ -91,7 +91,7 @@ async def async_step_import(self, import_input=None): if CONF_LONGITUDE not in config: config[CONF_LONGITUDE] = self.hass.config.longitude if CONF_MODE not in config: - config[CONF_MODE] = DEFAULT_LANGUAGE + config[CONF_MODE] = DEFAULT_FORECAST_MODE if CONF_LANGUAGE not in config: config[CONF_LANGUAGE] = DEFAULT_LANGUAGE return await self.async_step_user(config) diff --git a/homeassistant/components/openweathermap/const.py b/homeassistant/components/openweathermap/const.py index bc7a428f366575..03ed97d4075ef9 100644 --- a/homeassistant/components/openweathermap/const.py +++ b/homeassistant/components/openweathermap/const.py @@ -14,6 +14,7 @@ DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TIMESTAMP, + LENGTH_MILLIMETERS, PERCENTAGE, PRESSURE_PA, SPEED_METERS_PER_SECOND, @@ -72,7 +73,57 @@ ATTR_FORECAST_WIND_BEARING, ATTR_FORECAST_WIND_SPEED, ] -LANGUAGES = ["en", "es", "ru", "it"] +LANGUAGES = [ + "af", + "al", + "ar", + "az", + "bg", + "ca", + "cz", + "da", + "de", + "el", + "en", + "es", + "eu", + "fa", + "fi", + "fr", + "gl", + "he", + "hi", + "hr", + "hu", + "id", + "it", + "ja", + "kr", + "la", + "lt", + "mk", + "nl", + "no", + "pl", + "pt", + "pt_br", + "ro", + "ru", + "se", + "sk", + "sl", + "sp", + "sr", + "sv", + "th", + "tr", + "ua", + "uk", + "vi", + "zh_cn", + "zh_tw", + "zu", +] CONDITION_CLASSES = { "cloudy": [803, 804], "fog": [701, 741], @@ -112,8 +163,8 @@ SENSOR_DEVICE_CLASS: DEVICE_CLASS_PRESSURE, }, ATTR_API_CLOUDS: {SENSOR_NAME: "Cloud coverage", SENSOR_UNIT: PERCENTAGE}, - ATTR_API_RAIN: {SENSOR_NAME: "Rain", SENSOR_UNIT: "mm"}, - ATTR_API_SNOW: {SENSOR_NAME: "Snow", SENSOR_UNIT: "mm"}, + ATTR_API_RAIN: {SENSOR_NAME: "Rain", SENSOR_UNIT: LENGTH_MILLIMETERS}, + ATTR_API_SNOW: {SENSOR_NAME: "Snow", SENSOR_UNIT: LENGTH_MILLIMETERS}, ATTR_API_CONDITION: {SENSOR_NAME: "Condition"}, ATTR_API_WEATHER_CODE: {SENSOR_NAME: "Weather Code"}, } diff --git a/homeassistant/components/openweathermap/strings.json b/homeassistant/components/openweathermap/strings.json index e068bb919642b1..15b5c0f4d57587 100644 --- a/homeassistant/components/openweathermap/strings.json +++ b/homeassistant/components/openweathermap/strings.json @@ -1,19 +1,19 @@ { "config": { "abort": { - "already_configured": "OpenWeatherMap integration for these coordinates is already configured." + "already_configured": "[%key:common::config_flow::abort::already_configured_service%] for these coordinates." }, "error": { - "auth": "API key is not correct.", - "connection": "Can't connect to OWM API" + "invalid_api_key": "[%key:common::config_flow::error::invalid_api_key%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, "step": { "user": { "data": { - "api_key": "OpenWeatherMap API key", + "api_key": "[%key:common::config_flow::data::api_key%]", "language": "Language", - "latitude": "Latitude", - "longitude": "Longitude", + "latitude": "[%key:common::config_flow::data::latitude%]", + "longitude": "[%key:common::config_flow::data::longitude%]", "mode": "Mode", "name": "Name of the integration" }, diff --git a/homeassistant/components/openweathermap/translations/ca.json b/homeassistant/components/openweathermap/translations/ca.json index 240e378c0fa459..46b721f86cb5fc 100644 --- a/homeassistant/components/openweathermap/translations/ca.json +++ b/homeassistant/components/openweathermap/translations/ca.json @@ -1,16 +1,18 @@ { "config": { "abort": { - "already_configured": "La integraci\u00f3 OpenWeatherMap per a aquestes coordenades ja est\u00e0 configurada." + "already_configured": "El servei ja est\u00e0 configurat per a aquestes coordenades." }, "error": { "auth": "La clau API no \u00e9s correcta.", - "connection": "No s'ha pogut connectar amb l'API d'OWM" + "cannot_connect": "Ha fallat la connexi\u00f3", + "connection": "No s'ha pogut connectar amb l'API d'OWM", + "invalid_api_key": "Clau API inv\u00e0lida" }, "step": { "user": { "data": { - "api_key": "Clau API d'OpenWeatherMap", + "api_key": "Clau API", "language": "Idioma", "latitude": "Latitud", "longitude": "Longitud", diff --git a/homeassistant/components/openweathermap/translations/de.json b/homeassistant/components/openweathermap/translations/de.json new file mode 100644 index 00000000000000..6582b2046b81c3 --- /dev/null +++ b/homeassistant/components/openweathermap/translations/de.json @@ -0,0 +1,28 @@ +{ + "config": { + "error": { + "auth": "Der API-Schl\u00fcssel ist nicht korrekt." + }, + "step": { + "user": { + "data": { + "language": "Sprache", + "latitude": "Breitengrad", + "longitude": "L\u00e4ngengrad", + "mode": "Modus" + }, + "title": "OpenWeatherMap" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "language": "Sprache", + "mode": "Modus" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openweathermap/translations/el.json b/homeassistant/components/openweathermap/translations/el.json new file mode 100644 index 00000000000000..bdd4c34a58609d --- /dev/null +++ b/homeassistant/components/openweathermap/translations/el.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 OpenWeatherMap \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03ad\u03c2 \u03c4\u03b9\u03c2 \u03c3\u03c5\u03bd\u03c4\u03b5\u03c4\u03b1\u03b3\u03bc\u03ad\u03bd\u03b5\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c1\u03c5\u03b8\u03bc\u03b9\u03c3\u03c4\u03b5\u03af." + }, + "error": { + "auth": "\u03a4\u03bf \u03ba\u03bb\u03b5\u03b9\u03b4\u03af API \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03c3\u03c9\u03c3\u03c4\u03cc.", + "connection": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7 \u03c3\u03c4\u03bf OWM API" + }, + "step": { + "user": { + "data": { + "api_key": "\u039a\u03bb\u03b5\u03b9\u03b4\u03af API OpenWeatherMap", + "language": "\u0393\u03bb\u03ce\u03c3\u03c3\u03b1", + "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", + "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" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "language": "\u0393\u03bb\u03ce\u03c3\u03c3\u03b1", + "mode": "\u039b\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openweathermap/translations/en.json b/homeassistant/components/openweathermap/translations/en.json index d42c4479711fb8..1689f7e201b169 100644 --- a/homeassistant/components/openweathermap/translations/en.json +++ b/homeassistant/components/openweathermap/translations/en.json @@ -1,16 +1,18 @@ { "config": { "abort": { - "already_configured": "OpenWeatherMap integration for these coordinates is already configured." + "already_configured": "Service is already configured for these coordinates." }, "error": { "auth": "API key is not correct.", - "connection": "Can't connect to OWM API" + "cannot_connect": "Failed to connect", + "connection": "Can't connect to OWM API", + "invalid_api_key": "Invalid API key" }, "step": { "user": { "data": { - "api_key": "OpenWeatherMap API key", + "api_key": "API Key", "language": "Language", "latitude": "Latitude", "longitude": "Longitude", diff --git a/homeassistant/components/openweathermap/translations/es.json b/homeassistant/components/openweathermap/translations/es.json new file mode 100644 index 00000000000000..a3d783ef84dceb --- /dev/null +++ b/homeassistant/components/openweathermap/translations/es.json @@ -0,0 +1,37 @@ +{ + "config": { + "abort": { + "already_configured": "La integraci\u00f3n de OpenWeatherMap para estas coordenadas ya est\u00e1 configurada." + }, + "error": { + "auth": "La clave de API no es correcta.", + "cannot_connect": "No se pudo conectar", + "connection": "No se puede conectar a la API de OWM", + "invalid_api_key": "Clave API no v\u00e1lida" + }, + "step": { + "user": { + "data": { + "api_key": "Clave de API de OpenWeatherMap", + "language": "Idioma", + "latitude": "Latitud", + "longitude": "Longitud", + "mode": "Modo", + "name": "Nombre de la integraci\u00f3n" + }, + "description": "Configurar la integraci\u00f3n de OpenWeatherMap. Para generar la clave API, ve a https://openweathermap.org/appid", + "title": "OpenWeatherMap" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "language": "Idioma", + "mode": "Modo" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openweathermap/translations/et.json b/homeassistant/components/openweathermap/translations/et.json new file mode 100644 index 00000000000000..6ae485f2350778 --- /dev/null +++ b/homeassistant/components/openweathermap/translations/et.json @@ -0,0 +1,37 @@ +{ + "config": { + "abort": { + "already_configured": "Nende koordinaatidele on OpenWeatherMapi sidumine juba tehtud." + }, + "error": { + "auth": "API v\u00f5ti on vale.", + "cannot_connect": "\u00dchendamine nurjus", + "connection": "OWM API-ga ei saa \u00fchendust luua", + "invalid_api_key": "Vigane API v\u00f5ti" + }, + "step": { + "user": { + "data": { + "api_key": "OpenWeatherMapi API v\u00f5ti", + "language": "Keel", + "latitude": "Laiuskraad", + "longitude": "Pikkuskraad", + "mode": "Re\u017eiim", + "name": "Sidumise nimi" + }, + "description": "Seadistage OpenWeatherMapi sidumine. API-v\u00f5tme loomiseks minge aadressile https://openweathermap.org/appid", + "title": "OpenWeatherMap" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "language": "Keel", + "mode": "Re\u017eiim" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openweathermap/translations/fr.json b/homeassistant/components/openweathermap/translations/fr.json index ab53d663f48add..aaa7887c120f81 100644 --- a/homeassistant/components/openweathermap/translations/fr.json +++ b/homeassistant/components/openweathermap/translations/fr.json @@ -5,7 +5,9 @@ }, "error": { "auth": "La cl\u00e9 API n'est pas correcte.", - "connection": "Impossible de se connecter \u00e0 l'API OWM" + "cannot_connect": "\u00c9chec de connexion", + "connection": "Impossible de se connecter \u00e0 l'API OWM", + "invalid_api_key": "Cl\u00e9 API invalide" }, "step": { "user": { diff --git a/homeassistant/components/openweathermap/translations/it.json b/homeassistant/components/openweathermap/translations/it.json index c53e88d9558b1e..7f4dc75e1829eb 100644 --- a/homeassistant/components/openweathermap/translations/it.json +++ b/homeassistant/components/openweathermap/translations/it.json @@ -1,16 +1,18 @@ { "config": { "abort": { - "already_configured": "L'integrazione di OpenWeatherMap per queste coordinate \u00e8 gi\u00e0 configurata." + "already_configured": "Il servizio \u00e8 gi\u00e0 configurato per queste coordinate." }, "error": { "auth": "La chiave API non \u00e8 corretta.", - "connection": "Impossibile connettersi all'API OWM" + "cannot_connect": "Impossibile connettersi", + "connection": "Impossibile connettersi all'API OWM", + "invalid_api_key": "Chiave API non valida" }, "step": { "user": { "data": { - "api_key": "Chiave API OpenWeatherMap", + "api_key": "Chiave API", "language": "Lingua", "latitude": "Latitudine", "longitude": "Logitudine", diff --git a/homeassistant/components/openweathermap/translations/ko.json b/homeassistant/components/openweathermap/translations/ko.json new file mode 100644 index 00000000000000..12e76d85506c67 --- /dev/null +++ b/homeassistant/components/openweathermap/translations/ko.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "\uc774\ub7ec\ud55c \uc88c\ud45c\uc5d0 \ub300\ud55c OpenWeatherMap \ud1b5\ud569\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4." + }, + "error": { + "auth": "API \ud0a4\uac00 \uc62c\ubc14\ub974\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.", + "connection": "OWM API\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4." + }, + "step": { + "user": { + "data": { + "api_key": "OpenWeatherMap API \ud0a4", + "language": "\uc5b8\uc5b4", + "latitude": "\uc704\ub3c4", + "longitude": "\uacbd\ub3c4", + "mode": "\ubaa8\ub4dc", + "name": "\ud1b5\ud569 \uad6c\uc131\uc694\uc18c\uba85" + }, + "description": "OpenWeatherMap \ud1b5\ud569\uc744 \uc124\uc815\ud558\uc138\uc694. API \ud0a4\ub97c \uc0dd\uc131\ud558\ub824\uba74 https://openweathermap.org/appid\ub85c \uc774\ub3d9\ud558\uc2ed\uc2dc\uc624.", + "title": "OpenWeatherMap" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "language": "\uc5b8\uc5b4", + "mode": "\ubaa8\ub4dc" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openweathermap/translations/lb.json b/homeassistant/components/openweathermap/translations/lb.json new file mode 100644 index 00000000000000..ffc3a75a4530f8 --- /dev/null +++ b/homeassistant/components/openweathermap/translations/lb.json @@ -0,0 +1,32 @@ +{ + "config": { + "error": { + "auth": "Api Schl\u00ebssel ass net korrekt.", + "connection": "Kann sech net mat der OWM API verbannen." + }, + "step": { + "user": { + "data": { + "api_key": "OpenWeatherMap API Schl\u00ebssel", + "language": "Sproch", + "latitude": "Breedegrad", + "longitude": "L\u00e4ngegrad", + "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" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "language": "Sproch", + "mode": "Modus" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openweathermap/translations/nl.json b/homeassistant/components/openweathermap/translations/nl.json new file mode 100644 index 00000000000000..fdff089bddb148 --- /dev/null +++ b/homeassistant/components/openweathermap/translations/nl.json @@ -0,0 +1,35 @@ +{ + "config": { + "abort": { + "already_configured": "OpenWeatherMap-integratie voor deze co\u00f6rdinaten is al geconfigureerd." + }, + "error": { + "auth": "API-sleutel is niet correct.", + "connection": "Kan geen verbinding maken met OWM API" + }, + "step": { + "user": { + "data": { + "api_key": "OpenWeatherMap API-sleutel", + "language": "Taal", + "latitude": "Breedtegraad", + "longitude": "Lengtegraad", + "mode": "Mode", + "name": "Naam van de integratie" + }, + "description": "Stel OpenWeatherMap-integratie in. Ga naar https://openweathermap.org/appid om een API-sleutel te genereren", + "title": "OpenWeatherMap" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "language": "Taal", + "mode": "Mode" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openweathermap/translations/no.json b/homeassistant/components/openweathermap/translations/no.json index cda3666ff18ab5..c83d60d7856073 100644 --- a/homeassistant/components/openweathermap/translations/no.json +++ b/homeassistant/components/openweathermap/translations/no.json @@ -1,16 +1,18 @@ { "config": { "abort": { - "already_configured": "OpenWeatherMap-integrasjon for disse koordinatene er allerede konfigurert." + "already_configured": "Tjenesten er allerede konfigurert for these coordinates." }, "error": { "auth": "API-n\u00f8kkelen er ikke korrekt.", - "connection": "Kan ikke koble til OWM API" + "cannot_connect": "Tilkobling mislyktes.", + "connection": "Kan ikke koble til OWM API", + "invalid_api_key": "Ugyldig API-n\u00f8kkel" }, "step": { "user": { "data": { - "api_key": "OpenWeatherMap API-n\u00f8kkel", + "api_key": "API-n\u00f8kkel", "language": "Spr\u00e5k", "latitude": "Breddegrad", "longitude": "Lengdegrad", diff --git a/homeassistant/components/openweathermap/translations/pl.json b/homeassistant/components/openweathermap/translations/pl.json index 74e74422bd2140..153228485edfa1 100644 --- a/homeassistant/components/openweathermap/translations/pl.json +++ b/homeassistant/components/openweathermap/translations/pl.json @@ -1,7 +1,11 @@ { "config": { + "abort": { + "already_configured": "Us\u0142uga jest ju\u017c skonfigurowana. dla tych wsp\u00f3\u0142rz\u0119dnych." + }, "error": { "auth": "Klucz API jest nieprawid\u0142owy.", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "connection": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z OWM" }, "step": { @@ -14,6 +18,7 @@ "mode": "Tryb", "name": "Nazwa integracji" }, + "description": "Konfiguracja integracji OpenWeatherMap. Aby wygenerowa\u0107 klucz API, przejd\u017a do https://openweathermap.org/appid", "title": "OpenWeatherMap" } } diff --git a/homeassistant/components/openweathermap/translations/ru.json b/homeassistant/components/openweathermap/translations/ru.json index a79e51f6053922..1721102bf74bf8 100644 --- a/homeassistant/components/openweathermap/translations/ru.json +++ b/homeassistant/components/openweathermap/translations/ru.json @@ -1,11 +1,13 @@ { "config": { "abort": { - "already_configured": "\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f OpenWeatherMap \u0441 \u0442\u0430\u043a\u0438\u043c\u0438 \u0436\u0435 \u043a\u043e\u043e\u0440\u0434\u0438\u043d\u0430\u0442\u0430\u043c\u0438 \u0443\u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u0430." + "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": { "auth": "\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API.", - "connection": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a API OpenWeatherMap." + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "connection": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a API OpenWeatherMap.", + "invalid_api_key": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043b\u044e\u0447 API." }, "step": { "user": { diff --git a/homeassistant/components/openweathermap/translations/sv.json b/homeassistant/components/openweathermap/translations/sv.json new file mode 100644 index 00000000000000..a6fe05a8346560 --- /dev/null +++ b/homeassistant/components/openweathermap/translations/sv.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "already_configured": "OpenWeatherMap-integrationen f\u00f6r dessa koordinater \u00e4r redan konfigurerad." + }, + "error": { + "auth": "API-nyckeln \u00e4r inte korrekt.", + "connection": "Kan inte ansluta till OWM API" + }, + "step": { + "user": { + "data": { + "api_key": "OpenWeatherMap API-nyckel", + "language": "Spr\u00e5k", + "latitude": "Latitud", + "longitude": "Longitud", + "mode": "L\u00e4ge", + "name": "Integrationens namn" + }, + "title": "OpenWeatherMap" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "language": "Spr\u00e5k", + "mode": "L\u00e4ge" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/openweathermap/translations/zh-Hant.json b/homeassistant/components/openweathermap/translations/zh-Hant.json index e1ed3c9f89d7bb..e3b6591734713f 100644 --- a/homeassistant/components/openweathermap/translations/zh-Hant.json +++ b/homeassistant/components/openweathermap/translations/zh-Hant.json @@ -1,16 +1,18 @@ { "config": { "abort": { - "already_configured": "\u6b64 OpenWeatherMap \u6574\u5408\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3002" + "already_configured": "\u6b64\u4e9b\u5354\u8abf\u5668\u4e4b\u670d\u52d9\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3002" }, "error": { "auth": "API \u5bc6\u9470\u4e0d\u6b63\u78ba\u3002", - "connection": "\u7121\u6cd5\u9023\u7dda\u81f3 OWM API" + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "connection": "\u7121\u6cd5\u9023\u7dda\u81f3 OWM API", + "invalid_api_key": "API \u5bc6\u9470\u7121\u6548" }, "step": { "user": { "data": { - "api_key": "OpenWeatherMap API \u5bc6\u9470", + "api_key": "API \u5bc6\u9470", "language": "\u8a9e\u8a00", "latitude": "\u7def\u5ea6", "longitude": "\u7d93\u5ea6", diff --git a/homeassistant/components/opnsense/device_tracker.py b/homeassistant/components/opnsense/device_tracker.py index c64e0b0679a759..f7743d04a6d9f0 100644 --- a/homeassistant/components/opnsense/device_tracker.py +++ b/homeassistant/components/opnsense/device_tracker.py @@ -61,6 +61,6 @@ def get_extra_attributes(self, device): if device not in self.last_results: return None mfg = self.last_results[device].get("manufacturer") - if mfg: - return {"manufacturer": mfg} - return {} + if not mfg: + return {} + return {"manufacturer": mfg} diff --git a/homeassistant/components/orvibo/switch.py b/homeassistant/components/orvibo/switch.py index fec30cdade7b8d..a03d9fc5ff0d8a 100644 --- a/homeassistant/components/orvibo/switch.py +++ b/homeassistant/components/orvibo/switch.py @@ -73,11 +73,6 @@ def __init__(self, name, s20): self._state = False self._exc = S20Exception - @property - def should_poll(self): - """Return the polling state.""" - return True - @property def name(self): """Return the name of the switch.""" diff --git a/homeassistant/components/ovo_energy/translations/de.json b/homeassistant/components/ovo_energy/translations/de.json new file mode 100644 index 00000000000000..6f39806287630f --- /dev/null +++ b/homeassistant/components/ovo_energy/translations/de.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Passwort", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ovo_energy/translations/fr.json b/homeassistant/components/ovo_energy/translations/fr.json index f8bdce3d3a20da..b900e75787f3b3 100644 --- a/homeassistant/components/ovo_energy/translations/fr.json +++ b/homeassistant/components/ovo_energy/translations/fr.json @@ -11,6 +11,7 @@ "password": "Mot de passe", "username": "Nom d'utilisateur" }, + "description": "Configurez une instance OVO Energy pour acc\u00e9der \u00e0 votre consommation d'\u00e9nergie.", "title": "Ajouter un compte OVO Energy" } } diff --git a/homeassistant/components/ovo_energy/translations/hu.json b/homeassistant/components/ovo_energy/translations/hu.json new file mode 100644 index 00000000000000..f5481afa94af8e --- /dev/null +++ b/homeassistant/components/ovo_energy/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ovo_energy/translations/ko.json b/homeassistant/components/ovo_energy/translations/ko.json index 09d002bc161098..7d6882c2bd3dba 100644 --- a/homeassistant/components/ovo_energy/translations/ko.json +++ b/homeassistant/components/ovo_energy/translations/ko.json @@ -1,7 +1,9 @@ { "config": { "error": { - "connection_error": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4" + "already_configured": "\uacc4\uc815\uc740 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", + "authorization_error": "\uc778\uc99d \uc624\ub958\uc785\ub2c8\ub2e4. \uc790\uaca9 \uc99d\uba85\uc744 \ud655\uc778\ud558\uc2ed\uc2dc\uc624.", + "connection_error": "\uc5f0\uacb0 \uc2e4\ud328" }, "step": { "user": { diff --git a/homeassistant/components/ovo_energy/translations/nl.json b/homeassistant/components/ovo_energy/translations/nl.json new file mode 100644 index 00000000000000..4d00f0bfc74883 --- /dev/null +++ b/homeassistant/components/ovo_energy/translations/nl.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Gebruikersnaam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ovo_energy/translations/pl.json b/homeassistant/components/ovo_energy/translations/pl.json index 42afe86d48a0f7..092d65aef8ea7d 100644 --- a/homeassistant/components/ovo_energy/translations/pl.json +++ b/homeassistant/components/ovo_energy/translations/pl.json @@ -1,7 +1,19 @@ { "config": { "error": { - "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia." + "already_configured": "Konto jest ju\u017c skonfigurowane", + "authorization_error": "B\u0142\u0105d autoryzacji. Sprawd\u017a swoje po\u015bwiadczenia.", + "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" + }, + "step": { + "user": { + "data": { + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + }, + "description": "Skonfiguruj instancj\u0119 OVO Energy, aby uzyska\u0107 dost\u0119p do swojego zu\u017cycia energii.", + "title": "Dodaj konto OVO Energy" + } } } } \ No newline at end of file diff --git a/homeassistant/components/owntracks/__init__.py b/homeassistant/components/owntracks/__init__.py index 24dc99de71ce3e..d3091d7d027135 100644 --- a/homeassistant/components/owntracks/__init__.py +++ b/homeassistant/components/owntracks/__init__.py @@ -9,7 +9,12 @@ from homeassistant import config_entries from homeassistant.components import mqtt -from homeassistant.const import CONF_WEBHOOK_ID +from homeassistant.const import ( + ATTR_GPS_ACCURACY, + ATTR_LATITUDE, + ATTR_LONGITUDE, + CONF_WEBHOOK_ID, +) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.setup import async_when_setup @@ -292,9 +297,9 @@ def async_see_beacons(self, hass, dev_id, kwargs_param): device_tracker_state = hass.states.get(f"device_tracker.{dev_id}") if device_tracker_state is not None: - acc = device_tracker_state.attributes.get("gps_accuracy") - lat = device_tracker_state.attributes.get("latitude") - lon = device_tracker_state.attributes.get("longitude") + acc = device_tracker_state.attributes.get(ATTR_GPS_ACCURACY) + lat = device_tracker_state.attributes.get(ATTR_LATITUDE) + lon = device_tracker_state.attributes.get(ATTR_LONGITUDE) if lat is not None and lon is not None: kwargs["gps"] = (lat, lon) diff --git a/homeassistant/components/owntracks/config_flow.py b/homeassistant/components/owntracks/config_flow.py index 0aba24217cc6fe..f0838b510ec8b3 100644 --- a/homeassistant/components/owntracks/config_flow.py +++ b/homeassistant/components/owntracks/config_flow.py @@ -19,7 +19,7 @@ class OwnTracksFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user(self, user_input=None): """Handle a user initiated set up flow to create OwnTracks webhook.""" if self._async_current_entries(): - return self.async_abort(reason="one_instance_allowed") + return self.async_abort(reason="single_instance_allowed") if user_input is None: return self.async_show_form(step_id="user") @@ -52,7 +52,7 @@ async def async_step_user(self, user_input=None): async def async_step_import(self, user_input): """Import a config flow from configuration.""" if self._async_current_entries(): - return self.async_abort(reason="one_instance_allowed") + return self.async_abort(reason="single_instance_allowed") webhook_id, _webhook_url, cloudhook = await self._get_webhook_id() secret = secrets.token_hex(16) return self.async_create_entry( diff --git a/homeassistant/components/owntracks/messages.py b/homeassistant/components/owntracks/messages.py index 5e610d861fe1cf..3a4aac6bfd1bde 100644 --- a/homeassistant/components/owntracks/messages.py +++ b/homeassistant/components/owntracks/messages.py @@ -10,7 +10,7 @@ SOURCE_TYPE_BLUETOOTH_LE, SOURCE_TYPE_GPS, ) -from homeassistant.const import STATE_HOME +from homeassistant.const import ATTR_LATITUDE, ATTR_LONGITUDE, STATE_HOME from homeassistant.util import decorator, slugify from .helper import supports_encryption @@ -97,7 +97,10 @@ def _set_gps_from_zone(kwargs, location, zone): Async friendly. """ if zone is not None: - kwargs["gps"] = (zone.attributes["latitude"], zone.attributes["longitude"]) + kwargs["gps"] = ( + zone.attributes[ATTR_LATITUDE], + zone.attributes[ATTR_LONGITUDE], + ) kwargs["gps_accuracy"] = zone.attributes["radius"] kwargs["location_name"] = location return kwargs diff --git a/homeassistant/components/owntracks/strings.json b/homeassistant/components/owntracks/strings.json index 12aba21be72ce3..ddb700cc6424bb 100644 --- a/homeassistant/components/owntracks/strings.json +++ b/homeassistant/components/owntracks/strings.json @@ -6,7 +6,9 @@ "description": "Are you sure you want to set up OwnTracks?" } }, - "abort": { "one_instance_allowed": "Only a single instance is necessary." }, + "abort": { + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" + }, "create_entry": { "default": "\n\nOn Android, open [the OwnTracks app]({android_url}), go to preferences -> connection. Change the following settings:\n - Mode: Private HTTP\n - Host: {webhook_url}\n - Identification:\n - Username: `''`\n - Device ID: `''`\n\nOn iOS, open [the OwnTracks app]({ios_url}), tap (i) icon in top left -> settings. Change the following settings:\n - Mode: HTTP\n - URL: {webhook_url}\n - Turn on authentication\n - UserID: `''`\n\n{secret}\n\nSee [the documentation]({docs_url}) for more information." } diff --git a/homeassistant/components/owntracks/translations/ca.json b/homeassistant/components/owntracks/translations/ca.json index 6979bcb31a827c..c7cf0bb457a2ca 100644 --- a/homeassistant/components/owntracks/translations/ca.json +++ b/homeassistant/components/owntracks/translations/ca.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia." + "one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia.", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "create_entry": { "default": "\n\nPer Android: obre [l'app d'OwnTracks]({android_url}), ves a prefer\u00e8ncies -> connexi\u00f3. Canvia els par\u00e0metres seguents:\n - Mode: Private HTTP\n - Host: {webhook_url}\n - Identification:\n - Username: `''`\n - Device ID: `''`\n\nPer iOS: obre [l'app d'OwnTracks]({ios_url}), clica l'icona (i) a dalt a l'esquerra -> configuraci\u00f3 (settings). Canvia els par\u00e0metres seg\u00fcents:\n - Mode: HTTP\n - URL: {webhook_url}\n - Turn on authentication\n - UserID: `''`\n\n{secret}\n\nConsulta [la documentaci\u00f3]({docs_url}) per a m\u00e9s informaci\u00f3." diff --git a/homeassistant/components/owntracks/translations/el.json b/homeassistant/components/owntracks/translations/el.json new file mode 100644 index 00000000000000..aecb2ee553fa42 --- /dev/null +++ b/homeassistant/components/owntracks/translations/el.json @@ -0,0 +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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/owntracks/translations/en.json b/homeassistant/components/owntracks/translations/en.json index a1cf38a91a2429..9090cea70f2c8e 100644 --- a/homeassistant/components/owntracks/translations/en.json +++ b/homeassistant/components/owntracks/translations/en.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "one_instance_allowed": "Only a single instance is necessary." + "one_instance_allowed": "Only a single instance is necessary.", + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "create_entry": { "default": "\n\nOn Android, open [the OwnTracks app]({android_url}), go to preferences -> connection. Change the following settings:\n - Mode: Private HTTP\n - Host: {webhook_url}\n - Identification:\n - Username: `''`\n - Device ID: `''`\n\nOn iOS, open [the OwnTracks app]({ios_url}), tap (i) icon in top left -> settings. Change the following settings:\n - Mode: HTTP\n - URL: {webhook_url}\n - Turn on authentication\n - UserID: `''`\n\n{secret}\n\nSee [the documentation]({docs_url}) for more information." diff --git a/homeassistant/components/owntracks/translations/es.json b/homeassistant/components/owntracks/translations/es.json index ce558a70cdd280..0bc37535fb3574 100644 --- a/homeassistant/components/owntracks/translations/es.json +++ b/homeassistant/components/owntracks/translations/es.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "one_instance_allowed": "S\u00f3lo se necesita una instancia." + "one_instance_allowed": "S\u00f3lo se necesita una instancia.", + "single_instance_allowed": "Ya est\u00e1 configurado. S\u00f3lo es posible una \u00fanica configuraci\u00f3n." }, "create_entry": { "default": "\n\nEn Android, abre [la aplicaci\u00f3n OwnTracks]({android_url}), ve a preferencias -> conexi\u00f3n. Cambia los siguientes ajustes:\n - Modo: HTTP privado\n - Host: {webhook_url}\n - Identificaci\u00f3n:\n - Nombre de usuario: \n - ID de dispositivo: \n\nEn iOS, abre [la aplicaci\u00f3n OwnTracks] ({ios_url}), pulsa el icono (i) en la parte superior izquierda -> configuraci\u00f3n. Cambia los siguientes ajustes:\n - Modo: HTTP\n - URL: {webhook_url}\n - Activar la autenticaci\u00f3n\n - UserID: \n\n{secret}\n\nConsulta [la documentaci\u00f3n]({docs_url}) para obtener m\u00e1s informaci\u00f3n." diff --git a/homeassistant/components/owntracks/translations/et.json b/homeassistant/components/owntracks/translations/et.json new file mode 100644 index 00000000000000..e8b5eae41495c6 --- /dev/null +++ b/homeassistant/components/owntracks/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/owntracks/translations/fr.json b/homeassistant/components/owntracks/translations/fr.json index 69f36504051cdb..ba894ff43e76f0 100644 --- a/homeassistant/components/owntracks/translations/fr.json +++ b/homeassistant/components/owntracks/translations/fr.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "one_instance_allowed": "Une seule instance est n\u00e9cessaire." + "one_instance_allowed": "Une seule instance est n\u00e9cessaire.", + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." }, "create_entry": { "default": "\n\n Sous Android, ouvrez [l'application OwnTracks] ( {android_url} ), acc\u00e9dez \u00e0 Pr\u00e9f\u00e9rences - > Connexion. Modifiez les param\u00e8tres suivants: \n - Mode: HTTP priv\u00e9 \n - H\u00f4te: {webhook_url} \n - Identification: \n - Nom d'utilisateur: ` ` \n - ID de p\u00e9riph\u00e9rique: ` ` \n\n Sur iOS, ouvrez [l'application OwnTracks] ( {ios_url} ), appuyez sur l'ic\u00f4ne (i) en haut \u00e0 gauche - > param\u00e8tres. Modifiez les param\u00e8tres suivants: \n - Mode: HTTP \n - URL: {webhook_url} \n - Activer l'authentification \n - ID utilisateur: ` ` \n\n {secret} \n \n Voir [la documentation] ( {docs_url} ) pour plus d'informations." diff --git a/homeassistant/components/owntracks/translations/it.json b/homeassistant/components/owntracks/translations/it.json index a198bc33fda34e..60485d590b2405 100644 --- a/homeassistant/components/owntracks/translations/it.json +++ b/homeassistant/components/owntracks/translations/it.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "one_instance_allowed": "\u00c8 necessaria una sola istanza." + "one_instance_allowed": "\u00c8 necessaria una sola istanza.", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "create_entry": { "default": "\n\nSu Android, apri l'[app OwnTracks]({android_url}), vai su preferenze -> connessione. Modifica le seguenti impostazioni: \n - Modalit\u00e0: HTTP privato \n - Host: {webhook_url} \n - Identificazione: \n - Nome utente: `''` \n - ID dispositivo: `''`\n\nSu iOS, apri l'[app OwnTracks]({ios_url}), tocca l'icona (i) in alto a sinistra -> impostazioni. Modifica le seguenti impostazioni: \n - Modalit\u00e0: HTTP \n - URL: {webhook_url} \n - Attiva autenticazione \n - UserID: `''` \n\n {secret} \n \n Vedi [la documentazione]({docs_url}) per maggiori informazioni." diff --git a/homeassistant/components/owntracks/translations/lb.json b/homeassistant/components/owntracks/translations/lb.json index 4504ca6a74a7f0..4c9cba9e6ca0dd 100644 --- a/homeassistant/components/owntracks/translations/lb.json +++ b/homeassistant/components/owntracks/translations/lb.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg." + "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg.", + "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech." }, "create_entry": { "default": "\n\nOp Android, an [der OwnTracks App]({android_url}), g\u00e9i an Preferences -> Connection. \u00c4nnert folgend Astellungen:\n- Mode: Private HTTP\n- Host {webhool_url}\n- Identification:\n - Username: ``\n - Device ID: ``\n\nOp IOS, an [der OwnTracks App]({ios_url}), klick op (i) Ikon uewen l\u00e9nks -> Settings. \u00c4nnert folgend Astellungen:\n- Mode: HTTP\n- URL: {webhool_url}\n- Turn on authentication:\n- UserID: ``\n\n{secret}\n\nKuckt w.e.g. [Dokumentatioun]({docs_url}) fir m\u00e9i Informatiounen." diff --git a/homeassistant/components/owntracks/translations/no.json b/homeassistant/components/owntracks/translations/no.json index 594f6f923cbfac..1ff911561647fc 100644 --- a/homeassistant/components/owntracks/translations/no.json +++ b/homeassistant/components/owntracks/translations/no.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "one_instance_allowed": "Kun en forekomst er n\u00f8dvendig." + "one_instance_allowed": "Kun en forekomst er n\u00f8dvendig.", + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "create_entry": { "default": "\n\nP\u00e5 Android, \u00e5pne [OwnTracks appen]({android_url}), g\u00e5 til Instillinger -> tilkobling. Endre f\u00f8lgende innstillinger: \n - Modus: Privat HTTP\n - Vert: {webhook_url}\n - Identificasjon:\n - Brukernavn: ''\n - Enhets ID: ''\n\nP\u00e5 iOS, \u00e5pne [OwnTracks appen]({ios_url}), trykk p\u00e5 (i) ikonet \u00f8verst til venstre - > innstillinger. Endre f\u00f8lgende innstillinger: \n - Modus: HTTP\n - URL: {webhook_url}\n - Sl\u00e5 p\u00e5 autensiering\n - BrukerID: ''\n\n{secret}\n \n Se [dokumentasjonen]({docs_url}) for mer informasjon." diff --git a/homeassistant/components/owntracks/translations/ru.json b/homeassistant/components/owntracks/translations/ru.json index 816f2c8608744d..c04394820f975a 100644 --- a/homeassistant/components/owntracks/translations/ru.json +++ b/homeassistant/components/owntracks/translations/ru.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "one_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "one_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e." }, "create_entry": { "default": "\u0415\u0441\u043b\u0438 \u0412\u0430\u0448\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043d\u0430 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u0435 Android, \u043e\u0442\u043a\u0440\u043e\u0439\u0442\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 [OwnTracks]({android_url}), \u0437\u0430\u0442\u0435\u043c preferences -> connection. \u0418\u0437\u043c\u0435\u043d\u0438\u0442\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0442\u0430\u043a, \u043a\u0430\u043a \u0443\u043a\u0430\u0437\u0430\u043d\u043e \u043d\u0438\u0436\u0435:\n - Mode: Private HTTP\n - Host: {webhook_url}\n - Identification:\n - Username: ``\n - Device ID: ``\n\n\u0415\u0441\u043b\u0438 \u0412\u0430\u0448\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043d\u0430 iOS, \u043e\u0442\u043a\u0440\u043e\u0439\u0442\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 [OwnTracks]({ios_url}), \u043d\u0430\u0436\u043c\u0438\u0442\u0435 \u043d\u0430 \u0437\u043d\u0430\u0447\u043e\u043a (i) \u0432 \u043b\u0435\u0432\u043e\u043c \u0432\u0435\u0440\u0445\u043d\u0435\u043c \u0443\u0433\u043b\u0443 -> settings. \u0418\u0437\u043c\u0435\u043d\u0438\u0442\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0442\u0430\u043a, \u043a\u0430\u043a \u0443\u043a\u0430\u0437\u0430\u043d\u043e \u043d\u0438\u0436\u0435:\n - Mode: HTTP\n - URL: {webhook_url}\n - Turn on authentication\n - UserID: ``\n\n{secret}\n\n\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]({docs_url}) \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." diff --git a/homeassistant/components/owntracks/translations/zh-Hant.json b/homeassistant/components/owntracks/translations/zh-Hant.json index d86a5376d077a3..4d9871ffae23c3 100644 --- a/homeassistant/components/owntracks/translations/zh-Hant.json +++ b/homeassistant/components/owntracks/translations/zh-Hant.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u7269\u4ef6\u5373\u53ef\u3002" + "one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u7269\u4ef6\u5373\u53ef\u3002", + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002" }, "create_entry": { "default": "\n\n\u65bc Android \u8a2d\u5099\uff0c\u6253\u958b [OwnTracks app]({android_url})\u3001\u9ede\u9078\u8a2d\u5b9a\uff08preferences\uff09 -> \u9023\u7dda\uff08connection\uff09\u3002\u8b8a\u66f4\u4ee5\u4e0b\u8a2d\u5b9a\uff1a\n - \u6a21\u5f0f\uff08Mode\uff09\uff1aPrivate HTTP\n - \u4e3b\u6a5f\u7aef\uff08Host\uff09\uff1a{webhook_url}\n - Identification\uff1a\n - Username\uff1a ``\n - Device ID\uff1a``\n\n\u65bc iOS \u8a2d\u5099\uff0c\u6253\u958b [OwnTracks app]({ios_url})\u3001\u9ede\u9078\u5de6\u4e0a\u65b9\u7684 (i) \u5716\u793a -> \u8a2d\u5b9a\uff08settings\uff09\u3002\u8b8a\u66f4\u4ee5\u4e0b\u8a2d\u5b9a\uff1a\n - \u6a21\u5f0f\uff08Mode\uff09\uff1aHTTP\n - URL: {webhook_url}\n - \u958b\u555f authentication\n - UserID: ``\n\n{secret}\n\n\u8acb\u53c3\u95b1 [\u6587\u4ef6]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u8a73\u7d30\u8cc7\u6599\u3002" diff --git a/homeassistant/components/ozw/config_flow.py b/homeassistant/components/ozw/config_flow.py index 8822490132c54c..3153324322e034 100644 --- a/homeassistant/components/ozw/config_flow.py +++ b/homeassistant/components/ozw/config_flow.py @@ -15,7 +15,7 @@ class DomainConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): async def async_step_user(self, user_input=None): """Handle the initial step.""" if self._async_current_entries(): - return self.async_abort(reason="one_instance_allowed") + return self.async_abort(reason="single_instance_allowed") if "mqtt" not in self.hass.config.components: return self.async_abort(reason="mqtt_required") if user_input is not None: diff --git a/homeassistant/components/ozw/const.py b/homeassistant/components/ozw/const.py index 5164bbbe0445f0..8e5007a8419d0e 100644 --- a/homeassistant/components/ozw/const.py +++ b/homeassistant/components/ozw/const.py @@ -40,6 +40,7 @@ # Service specific SERVICE_ADD_NODE = "add_node" SERVICE_REMOVE_NODE = "remove_node" +SERVICE_CANCEL_COMMAND = "cancel_command" SERVICE_SET_CONFIG_PARAMETER = "set_config_parameter" # Home Assistant Events diff --git a/homeassistant/components/ozw/discovery.py b/homeassistant/components/ozw/discovery.py index 8bbb6741020c6e..a83f763c8106cd 100644 --- a/homeassistant/components/ozw/discovery.py +++ b/homeassistant/components/ozw/discovery.py @@ -136,6 +136,7 @@ const.DISC_GENERIC_DEVICE_CLASS: (const_ozw.GENERIC_TYPE_THERMOSTAT,), const.DISC_SPECIFIC_DEVICE_CLASS: ( const_ozw.SPECIFIC_TYPE_SETPOINT_THERMOSTAT, + const_ozw.SPECIFIC_TYPE_NOT_USED, ), const.DISC_VALUES: { const.DISC_PRIMARY: { diff --git a/homeassistant/components/ozw/lock.py b/homeassistant/components/ozw/lock.py index 3797734dfeb28d..68acb3f96913af 100644 --- a/homeassistant/components/ozw/lock.py +++ b/homeassistant/components/ozw/lock.py @@ -1,7 +1,9 @@ """Representation of Z-Wave locks.""" import logging -from openzwavemqtt.const import CommandClass, ValueIndex +from openzwavemqtt.const import ATTR_CODE_SLOT +from openzwavemqtt.exceptions import BaseOZWError +from openzwavemqtt.util.lock import clear_usercode, set_usercode import voluptuous as vol from homeassistant.components.lock import DOMAIN as LOCK_DOMAIN, LockEntity @@ -12,7 +14,6 @@ from .const import DATA_UNSUBSCRIBE, DOMAIN from .entity import ZWaveDeviceEntity -ATTR_CODE_SLOT = "code_slot" ATTR_USERCODE = "usercode" SERVICE_SET_USERCODE = "set_usercode" @@ -54,6 +55,17 @@ def async_add_lock(value): ) +def _call_util_lock_function(function, *args): + """Call an openzwavemqtt.util.lock function and return success of call.""" + try: + function(*args) + except BaseOZWError as err: + _LOGGER.error("%s: %s", type(err), err.args[0]) + return False + + return True + + class ZWaveLock(ZWaveDeviceEntity, LockEntity): """Representation of a Z-Wave lock.""" @@ -73,25 +85,15 @@ async def async_unlock(self, **kwargs): @callback def async_set_usercode(self, code_slot, usercode): """Set the usercode to index X on the lock.""" - value = self.values.primary.node.get_value(CommandClass.USER_CODE, code_slot) - - if len(str(usercode)) < 4: - _LOGGER.error( - "Invalid code provided: (%s) user code must be at least 4 digits", - usercode, - ) - return - value.send_value(usercode) - _LOGGER.debug("User code at slot %s set", code_slot) + if _call_util_lock_function( + set_usercode, self.values.primary.node, code_slot, usercode + ): + _LOGGER.debug("User code at slot %s set", code_slot) @callback def async_clear_usercode(self, code_slot): """Clear usercode in slot X on the lock.""" - value = self.values.primary.node.get_value( - CommandClass.USER_CODE, ValueIndex.CLEAR_USER_CODE - ) - - value.send_value(code_slot) - # Sending twice because the first time it doesn't take - value.send_value(code_slot) - _LOGGER.info("Usercode at slot %s is cleared", code_slot) + if _call_util_lock_function( + clear_usercode, self.values.primary.node, code_slot + ): + _LOGGER.info("Usercode at slot %s is cleared", code_slot) diff --git a/homeassistant/components/ozw/manifest.json b/homeassistant/components/ozw/manifest.json index 6fa4174da4aa4e..667a50428e19e4 100644 --- a/homeassistant/components/ozw/manifest.json +++ b/homeassistant/components/ozw/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/ozw", "requirements": [ - "python-openzwave-mqtt==1.0.5" + "python-openzwave-mqtt==1.2.0" ], "after_dependencies": [ "mqtt" diff --git a/homeassistant/components/ozw/services.py b/homeassistant/components/ozw/services.py index 500289cec21508..e1f71e636b3efd 100644 --- a/homeassistant/components/ozw/services.py +++ b/homeassistant/components/ozw/services.py @@ -1,7 +1,8 @@ """Methods and classes related to executing Z-Wave commands and publishing these to hass.""" import logging -from openzwavemqtt.const import CommandClass, ValueType +from openzwavemqtt.const import ATTR_LABEL, ATTR_POSITION, ATTR_VALUE +from openzwavemqtt.util.node import get_node_from_manager, set_config_parameter import voluptuous as vol from homeassistant.core import callback @@ -42,6 +43,14 @@ def async_register(self): {vol.Optional(const.ATTR_INSTANCE_ID, default=1): vol.Coerce(int)} ), ) + self._hass.services.async_register( + const.DOMAIN, + const.SERVICE_CANCEL_COMMAND, + self.async_cancel_command, + schema=vol.Schema( + {vol.Optional(const.ATTR_INSTANCE_ID, default=1): vol.Coerce(int)} + ), + ) self._hass.services.async_register( const.DOMAIN, @@ -53,7 +62,24 @@ def async_register(self): vol.Required(const.ATTR_NODE_ID): vol.Coerce(int), vol.Required(const.ATTR_CONFIG_PARAMETER): vol.Coerce(int), vol.Required(const.ATTR_CONFIG_VALUE): vol.Any( - vol.Coerce(int), cv.string + vol.All( + cv.ensure_list, + [ + vol.All( + { + vol.Exclusive(ATTR_LABEL, "bit"): cv.string, + vol.Exclusive(ATTR_POSITION, "bit"): vol.Coerce( + int + ), + vol.Required(ATTR_VALUE): bool, + }, + cv.has_at_least_one_key(ATTR_LABEL, ATTR_POSITION), + ) + ], + ), + vol.Coerce(int), + bool, + cv.string, ), } ), @@ -66,57 +92,12 @@ def async_set_config_parameter(self, service): node_id = service.data[const.ATTR_NODE_ID] param = service.data[const.ATTR_CONFIG_PARAMETER] selection = service.data[const.ATTR_CONFIG_VALUE] - payload = None - - value = ( - self._manager.get_instance(instance_id) - .get_node(node_id) - .get_value(CommandClass.CONFIGURATION, param) - ) - - if value.type == ValueType.BOOL: - payload = selection == "True" - if value.type == ValueType.LIST: - # accept either string from the list value OR the int value - for selected in value.value["List"]: - if selection not in (selected["Label"], selected["Value"]): - continue - payload = int(selected["Value"]) + # These function calls may raise an exception but that's ok because + # the exception will show in the UI to the user + node = get_node_from_manager(self._manager, instance_id, node_id) + payload = set_config_parameter(node, param, selection) - if payload is None: - _LOGGER.error( - "Invalid value %s for parameter %s", - selection, - param, - ) - return - - if value.type == ValueType.BUTTON: - # Unsupported at this time - _LOGGER.info("Button type not supported yet") - return - - if value.type == ValueType.STRING: - payload = selection - - if ( - value.type == ValueType.INT - or value.type == ValueType.BYTE - or value.type == ValueType.SHORT - ): - if selection > value.max or selection < value.min: - _LOGGER.error( - "Value %s out of range for parameter %s (Min: %s Max: %s)", - selection, - param, - value.min, - value.max, - ) - return - payload = int(selection) - - value.send_value(payload) # send the payload _LOGGER.info( "Setting configuration parameter %s on Node %s with value %s", param, @@ -130,6 +111,8 @@ def async_add_node(self, service): instance_id = service.data[const.ATTR_INSTANCE_ID] secure = service.data[const.ATTR_SECURE] instance = self._manager.get_instance(instance_id) + if instance is None: + raise ValueError(f"No OpenZWave Instance with ID {instance_id}") instance.add_node(secure) @callback @@ -137,4 +120,15 @@ def async_remove_node(self, service): """Enter exclusion mode on the controller.""" instance_id = service.data[const.ATTR_INSTANCE_ID] instance = self._manager.get_instance(instance_id) + if instance is None: + raise ValueError(f"No OpenZWave Instance with ID {instance_id}") instance.remove_node() + + @callback + def async_cancel_command(self, service): + """Tell the controller to cancel an add or remove command.""" + instance_id = service.data[const.ATTR_INSTANCE_ID] + instance = self._manager.get_instance(instance_id) + if instance is None: + raise ValueError(f"No OpenZWave Instance with ID {instance_id}") + instance.cancel_controller_command() diff --git a/homeassistant/components/ozw/services.yaml b/homeassistant/components/ozw/services.yaml index d7f5c540c67a27..641c086f524465 100644 --- a/homeassistant/components/ozw/services.yaml +++ b/homeassistant/components/ozw/services.yaml @@ -13,6 +13,12 @@ remove_node: instance_id: description: (Optional) The OZW Instance/Controller to use, defaults to 1. +cancel_command: + description: Cancel a pending add or remove node command. + fields: + instance_id: + description: (Optional) The OZW Instance/Controller to use, defaults to 1. + set_config_parameter: description: Set a config parameter to a node on the Z-Wave network. fields: diff --git a/homeassistant/components/ozw/strings.json b/homeassistant/components/ozw/strings.json index dd2aad7e4ce078..88f8911db0d5bc 100644 --- a/homeassistant/components/ozw/strings.json +++ b/homeassistant/components/ozw/strings.json @@ -6,7 +6,7 @@ } }, "abort": { - "one_instance_allowed": "The integration only supports one Z-Wave instance", + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", "mqtt_required": "The MQTT integration is not set up" } } diff --git a/homeassistant/components/ozw/translations/ca.json b/homeassistant/components/ozw/translations/ca.json index eba9a7e87572ae..3a1fdb29ba14f7 100644 --- a/homeassistant/components/ozw/translations/ca.json +++ b/homeassistant/components/ozw/translations/ca.json @@ -2,7 +2,8 @@ "config": { "abort": { "mqtt_required": "La integraci\u00f3 MQTT no est\u00e0 configurada", - "one_instance_allowed": "La integraci\u00f3 nom\u00e9s admet una inst\u00e0ncia Z-Wave" + "one_instance_allowed": "La integraci\u00f3 nom\u00e9s admet una inst\u00e0ncia Z-Wave", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "step": { "user": { diff --git a/homeassistant/components/ozw/translations/el.json b/homeassistant/components/ozw/translations/el.json new file mode 100644 index 00000000000000..aecb2ee553fa42 --- /dev/null +++ b/homeassistant/components/ozw/translations/el.json @@ -0,0 +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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ozw/translations/en.json b/homeassistant/components/ozw/translations/en.json index c6a45474880c86..4e41ee58d11b0d 100644 --- a/homeassistant/components/ozw/translations/en.json +++ b/homeassistant/components/ozw/translations/en.json @@ -2,7 +2,8 @@ "config": { "abort": { "mqtt_required": "The MQTT integration is not set up", - "one_instance_allowed": "The integration only supports one Z-Wave instance" + "one_instance_allowed": "The integration only supports one Z-Wave instance", + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "step": { "user": { diff --git a/homeassistant/components/ozw/translations/es.json b/homeassistant/components/ozw/translations/es.json index f78b62828cb9f6..66cebc62ec4fd9 100644 --- a/homeassistant/components/ozw/translations/es.json +++ b/homeassistant/components/ozw/translations/es.json @@ -2,7 +2,8 @@ "config": { "abort": { "mqtt_required": "La integraci\u00f3n de MQTT no est\u00e1 configurada", - "one_instance_allowed": "La integraci\u00f3n solo admite una instancia de Z-Wave" + "one_instance_allowed": "La integraci\u00f3n solo admite una instancia de Z-Wave", + "single_instance_allowed": "Ya est\u00e1 configurado. S\u00f3lo es posible una \u00fanica configuraci\u00f3n." }, "step": { "user": { diff --git a/homeassistant/components/ozw/translations/et.json b/homeassistant/components/ozw/translations/et.json new file mode 100644 index 00000000000000..16196205aec54e --- /dev/null +++ b/homeassistant/components/ozw/translations/et.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "mqtt_required": "MQTT sidumine pole seadistatud", + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ozw/translations/fr.json b/homeassistant/components/ozw/translations/fr.json index dbc609b93eb365..0c6c1e82da5551 100644 --- a/homeassistant/components/ozw/translations/fr.json +++ b/homeassistant/components/ozw/translations/fr.json @@ -2,7 +2,8 @@ "config": { "abort": { "mqtt_required": "L'int\u00e9gration MQTT n'est pas configur\u00e9e", - "one_instance_allowed": "L'int\u00e9gration ne prend en charge qu'une seule instance Z-Wave" + "one_instance_allowed": "L'int\u00e9gration ne prend en charge qu'une seule instance Z-Wave", + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." }, "step": { "user": { diff --git a/homeassistant/components/ozw/translations/it.json b/homeassistant/components/ozw/translations/it.json index 0b76d09cf08439..f97be82b374bf8 100644 --- a/homeassistant/components/ozw/translations/it.json +++ b/homeassistant/components/ozw/translations/it.json @@ -2,7 +2,8 @@ "config": { "abort": { "mqtt_required": "L'integrazione MQTT non \u00e8 impostata", - "one_instance_allowed": "L'integrazione supporta solo un'istanza Z-Wave" + "one_instance_allowed": "L'integrazione supporta solo un'istanza Z-Wave", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "step": { "user": { diff --git a/homeassistant/components/ozw/translations/lb.json b/homeassistant/components/ozw/translations/lb.json index 053fe63113368b..9b2a5e577c2ce4 100644 --- a/homeassistant/components/ozw/translations/lb.json +++ b/homeassistant/components/ozw/translations/lb.json @@ -2,7 +2,8 @@ "config": { "abort": { "mqtt_required": "MQTT Integratioun ass net ageriicht", - "one_instance_allowed": "D'Integratioun \u00ebnnerst\u00ebtzt n\u00ebmmen 1 Z-Wave Instanz" + "one_instance_allowed": "D'Integratioun \u00ebnnerst\u00ebtzt n\u00ebmmen 1 Z-Wave Instanz", + "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech." }, "step": { "user": { diff --git a/homeassistant/components/ozw/translations/no.json b/homeassistant/components/ozw/translations/no.json index 1d4049978f5a9f..62448d2a43eeb2 100644 --- a/homeassistant/components/ozw/translations/no.json +++ b/homeassistant/components/ozw/translations/no.json @@ -2,7 +2,8 @@ "config": { "abort": { "mqtt_required": "MQTT-integrasjonen er ikke satt opp", - "one_instance_allowed": "Integrasjonen st\u00f8tter bare \u00e9n Z-Wave-forekomst" + "one_instance_allowed": "Integrasjonen st\u00f8tter bare \u00e9n Z-Wave-forekomst", + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "step": { "user": { diff --git a/homeassistant/components/ozw/translations/ru.json b/homeassistant/components/ozw/translations/ru.json index ac968e9fdfa41b..71f21f9eb1709c 100644 --- a/homeassistant/components/ozw/translations/ru.json +++ b/homeassistant/components/ozw/translations/ru.json @@ -2,7 +2,8 @@ "config": { "abort": { "mqtt_required": "\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f MQTT \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u0430.", - "one_instance_allowed": "\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440 Z-Wave." + "one_instance_allowed": "\u0418\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440 Z-Wave.", + "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/ozw/translations/zh-Hans.json b/homeassistant/components/ozw/translations/zh-Hans.json index e4beac109fdf19..a3b0a71d63c1dc 100644 --- a/homeassistant/components/ozw/translations/zh-Hans.json +++ b/homeassistant/components/ozw/translations/zh-Hans.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "mqtt_required": "\u672a\u8bbe\u7f6e MQTT \u96c6\u6210" + "mqtt_required": "\u672a\u8bbe\u7f6e MQTT \u96c6\u6210", + "one_instance_allowed": "\u7ec4\u4ef6\u53ea\u652f\u6301\u4e00\u4e2a Z-Wave \u5b9e\u4f8b" }, "step": { "user": { diff --git a/homeassistant/components/ozw/translations/zh-Hant.json b/homeassistant/components/ozw/translations/zh-Hant.json index e9ad87042d2661..d9967bddae126c 100644 --- a/homeassistant/components/ozw/translations/zh-Hant.json +++ b/homeassistant/components/ozw/translations/zh-Hant.json @@ -2,7 +2,8 @@ "config": { "abort": { "mqtt_required": "MQTT \u6574\u5408\u5c1a\u672a\u8a2d\u5b9a", - "one_instance_allowed": "\u6574\u5408\u50c5\u652f\u63f4\u4e00\u7d44 Z-Wave \u5be6\u4f8b" + "one_instance_allowed": "\u6574\u5408\u50c5\u652f\u63f4\u4e00\u7d44 Z-Wave \u5be6\u4f8b", + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002" }, "step": { "user": { diff --git a/homeassistant/components/ozw/websocket_api.py b/homeassistant/components/ozw/websocket_api.py index da9c2b1f598051..482d78bb8784b4 100644 --- a/homeassistant/components/ozw/websocket_api.py +++ b/homeassistant/components/ozw/websocket_api.py @@ -1,21 +1,34 @@ """Web socket API for OpenZWave.""" - -import logging - -from openzwavemqtt.const import EVENT_NODE_ADDED, EVENT_NODE_CHANGED +from openzwavemqtt.const import ( + ATTR_CODE_SLOT, + ATTR_LABEL, + ATTR_POSITION, + ATTR_VALUE, + EVENT_NODE_ADDED, + EVENT_NODE_CHANGED, +) +from openzwavemqtt.exceptions import NotFoundError, NotSupportedError +from openzwavemqtt.util.lock import clear_usercode, get_code_slots, set_usercode +from openzwavemqtt.util.node import ( + get_config_parameters, + get_node_from_manager, + set_config_parameter, +) import voluptuous as vol from homeassistant.components import websocket_api from homeassistant.core import callback +from homeassistant.helpers import config_validation as cv -from .const import DOMAIN, MANAGER, OPTIONS - -_LOGGER = logging.getLogger(__name__) +from .const import ATTR_CONFIG_PARAMETER, ATTR_CONFIG_VALUE, DOMAIN, MANAGER, OPTIONS +from .lock import ATTR_USERCODE TYPE = "type" ID = "id" OZW_INSTANCE = "ozw_instance" NODE_ID = "node_id" +PARAMETER = ATTR_CONFIG_PARAMETER +VALUE = ATTR_CONFIG_VALUE ATTR_NODE_QUERY_STAGE = "node_query_stage" ATTR_IS_ZWAVE_PLUS = "is_zwave_plus" @@ -45,6 +58,52 @@ def async_register_api(hass): websocket_api.async_register_command(hass, websocket_node_status) websocket_api.async_register_command(hass, websocket_node_statistics) websocket_api.async_register_command(hass, websocket_refresh_node_info) + websocket_api.async_register_command(hass, websocket_get_config_parameters) + websocket_api.async_register_command(hass, websocket_set_config_parameter) + websocket_api.async_register_command(hass, websocket_set_usercode) + websocket_api.async_register_command(hass, websocket_clear_usercode) + websocket_api.async_register_command(hass, websocket_get_code_slots) + + +def _call_util_function(hass, connection, msg, send_result, function, *args): + """Call an openzwavemqtt.util function.""" + try: + node = get_node_from_manager( + hass.data[DOMAIN][MANAGER], msg[OZW_INSTANCE], msg[NODE_ID] + ) + except NotFoundError as err: + connection.send_error( + msg[ID], + websocket_api.const.ERR_NOT_FOUND, + err.args[0], + ) + return + + try: + payload = function(node, *args) + except NotFoundError as err: + connection.send_error( + msg[ID], + websocket_api.const.ERR_NOT_FOUND, + err.args[0], + ) + return + except NotSupportedError as err: + connection.send_error( + msg[ID], + websocket_api.const.ERR_NOT_SUPPORTED, + err.args[0], + ) + return + + if send_result: + connection.send_result( + msg[ID], + payload, + ) + return + + connection.send_result(msg[ID]) @websocket_api.websocket_command({vol.Required(TYPE): "ozw/get_instances"}) @@ -102,6 +161,94 @@ def websocket_get_nodes(hass, connection, msg): ) +@websocket_api.websocket_command( + { + vol.Required(TYPE): "ozw/set_usercode", + vol.Required(NODE_ID): vol.Coerce(int), + vol.Optional(OZW_INSTANCE, default=1): vol.Coerce(int), + vol.Required(ATTR_CODE_SLOT): vol.Coerce(int), + vol.Required(ATTR_USERCODE): cv.string, + } +) +def websocket_set_usercode(hass, connection, msg): + """Set a usercode to a node code slot.""" + _call_util_function( + hass, connection, msg, False, set_usercode, msg[ATTR_CODE_SLOT], ATTR_USERCODE + ) + + +@websocket_api.websocket_command( + { + vol.Required(TYPE): "ozw/clear_usercode", + vol.Required(NODE_ID): vol.Coerce(int), + vol.Optional(OZW_INSTANCE, default=1): vol.Coerce(int), + vol.Required(ATTR_CODE_SLOT): vol.Coerce(int), + } +) +def websocket_clear_usercode(hass, connection, msg): + """Clear a node code slot.""" + _call_util_function( + hass, connection, msg, False, clear_usercode, msg[ATTR_CODE_SLOT] + ) + + +@websocket_api.websocket_command( + { + vol.Required(TYPE): "ozw/get_code_slots", + vol.Required(NODE_ID): vol.Coerce(int), + vol.Optional(OZW_INSTANCE, default=1): vol.Coerce(int), + } +) +def websocket_get_code_slots(hass, connection, msg): + """Get status of node's code slots.""" + _call_util_function(hass, connection, msg, True, get_code_slots) + + +@websocket_api.websocket_command( + { + vol.Required(TYPE): "ozw/get_config_parameters", + vol.Required(NODE_ID): vol.Coerce(int), + vol.Optional(OZW_INSTANCE, default=1): vol.Coerce(int), + } +) +def websocket_get_config_parameters(hass, connection, msg): + """Get a list of configuration parameters for an OZW node instance.""" + _call_util_function(hass, connection, msg, True, get_config_parameters) + + +@websocket_api.websocket_command( + { + vol.Required(TYPE): "ozw/set_config_parameter", + vol.Required(NODE_ID): vol.Coerce(int), + vol.Optional(OZW_INSTANCE, default=1): vol.Coerce(int), + vol.Required(PARAMETER): vol.Coerce(int), + vol.Required(VALUE): vol.Any( + vol.All( + cv.ensure_list, + [ + vol.All( + { + vol.Exclusive(ATTR_LABEL, "bit"): cv.string, + vol.Exclusive(ATTR_POSITION, "bit"): vol.Coerce(int), + vol.Required(ATTR_VALUE): bool, + }, + cv.has_at_least_one_key(ATTR_LABEL, ATTR_POSITION), + ) + ], + ), + vol.Coerce(int), + bool, + cv.string, + ), + } +) +def websocket_set_config_parameter(hass, connection, msg): + """Set a config parameter to a node.""" + _call_util_function( + hass, connection, msg, False, set_config_parameter, msg[PARAMETER], msg[VALUE] + ) + + @websocket_api.websocket_command( { vol.Required(TYPE): "ozw/network_status", @@ -148,14 +295,15 @@ def websocket_network_statistics(hass, connection, msg): ) def websocket_node_status(hass, connection, msg): """Get the status for a Z-Wave node.""" - manager = hass.data[DOMAIN][MANAGER] - node = manager.get_instance(msg[OZW_INSTANCE]).get_node(msg[NODE_ID]) - - if not node: - connection.send_message( - websocket_api.error_message( - msg[ID], websocket_api.const.ERR_NOT_FOUND, "OZW Node not found" - ) + try: + node = get_node_from_manager( + hass.data[DOMAIN][MANAGER], msg[OZW_INSTANCE], msg[NODE_ID] + ) + except NotFoundError as err: + connection.send_error( + msg[ID], + websocket_api.const.ERR_NOT_FOUND, + err.args[0], ) return @@ -192,14 +340,15 @@ def websocket_node_status(hass, connection, msg): ) def websocket_node_metadata(hass, connection, msg): """Get the metadata for a Z-Wave node.""" - manager = hass.data[DOMAIN][MANAGER] - node = manager.get_instance(msg[OZW_INSTANCE]).get_node(msg[NODE_ID]) - - if not node: - connection.send_message( - websocket_api.error_message( - msg[ID], websocket_api.const.ERR_NOT_FOUND, "OZW Node not found" - ) + try: + node = get_node_from_manager( + hass.data[DOMAIN][MANAGER], msg[OZW_INSTANCE], msg[NODE_ID] + ) + except NotFoundError as err: + connection.send_error( + msg[ID], + websocket_api.const.ERR_NOT_FOUND, + err.args[0], ) return diff --git a/homeassistant/components/panasonic_viera/config_flow.py b/homeassistant/components/panasonic_viera/config_flow.py index d08e293eb451f7..8489c69fe66c87 100644 --- a/homeassistant/components/panasonic_viera/config_flow.py +++ b/homeassistant/components/panasonic_viera/config_flow.py @@ -17,9 +17,6 @@ DEFAULT_PORT, DOMAIN, ERROR_INVALID_PIN_CODE, - ERROR_NOT_CONNECTED, - REASON_NOT_CONNECTED, - REASON_UNKNOWN, ) _LOGGER = logging.getLogger(__name__) @@ -54,10 +51,10 @@ async def async_step_user(self, user_input=None): ) except (TimeoutError, URLError, SOAPError, OSError) as err: _LOGGER.error("Could not establish remote connection: %s", err) - errors["base"] = ERROR_NOT_CONNECTED + errors["base"] = "cannot_connect" except Exception as err: # pylint: disable=broad-except _LOGGER.exception("An unknown error occurred: %s", err) - return self.async_abort(reason=REASON_UNKNOWN) + return self.async_abort(reason="unknown") if "base" not in errors: if self._remote.type == TV_TYPE_ENCRYPTED: @@ -104,10 +101,10 @@ async def async_step_pairing(self, user_input=None): errors["base"] = ERROR_INVALID_PIN_CODE except (TimeoutError, URLError, OSError) as err: _LOGGER.error("The remote connection was lost: %s", err) - return self.async_abort(reason=REASON_NOT_CONNECTED) + return self.async_abort(reason="cannot_connect") except Exception as err: # pylint: disable=broad-except _LOGGER.exception("Unknown error: %s", err) - return self.async_abort(reason=REASON_UNKNOWN) + return self.async_abort(reason="unknown") if "base" not in errors: encryption_data = { @@ -128,10 +125,10 @@ async def async_step_pairing(self, user_input=None): ) except (TimeoutError, URLError, SOAPError, OSError) as err: _LOGGER.error("The remote connection was lost: %s", err) - return self.async_abort(reason=REASON_NOT_CONNECTED) + return self.async_abort(reason="cannot_connect") except Exception as err: # pylint: disable=broad-except _LOGGER.exception("Unknown error: %s", err) - return self.async_abort(reason=REASON_UNKNOWN) + return self.async_abort(reason="unknown") return self.async_show_form( step_id="pairing", diff --git a/homeassistant/components/panasonic_viera/const.py b/homeassistant/components/panasonic_viera/const.py index 529de4ebe672cc..2fb264a6b1c51c 100644 --- a/homeassistant/components/panasonic_viera/const.py +++ b/homeassistant/components/panasonic_viera/const.py @@ -12,8 +12,4 @@ ATTR_REMOTE = "remote" -ERROR_NOT_CONNECTED = "not_connected" ERROR_INVALID_PIN_CODE = "invalid_pin_code" - -REASON_NOT_CONNECTED = "not_connected" -REASON_UNKNOWN = "unknown" diff --git a/homeassistant/components/panasonic_viera/strings.json b/homeassistant/components/panasonic_viera/strings.json index f3943fde71d30c..fdb9af28303df8 100644 --- a/homeassistant/components/panasonic_viera/strings.json +++ b/homeassistant/components/panasonic_viera/strings.json @@ -7,25 +7,25 @@ "description": "Enter your Panasonic Viera TV's IP address", "data": { "host": "[%key:common::config_flow::data::ip%]", - "name": "Name" + "name": "[%key:common::config_flow::data::name%]" } }, "pairing": { "title": "Pairing", "description": "Enter the PIN displayed on your TV", "data": { - "pin": "PIN" + "pin": "[%key:common::config_flow::data::pin%]" } } }, "error": { - "not_connected": "Could not establish a remote connection with your Panasonic Viera TV", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "invalid_pin_code": "The PIN code you entered was invalid" }, "abort": { - "already_configured": "This Panasonic Viera TV is already configured.", - "not_connected": "The remote connection with your Panasonic Viera TV was lost. Check the logs for more information.", - "unknown": "An unknown error occured. Check the logs for more information." + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "unknown": "[%key:common::config_flow::error::unknown%]" } } } diff --git a/homeassistant/components/panasonic_viera/translations/fr.json b/homeassistant/components/panasonic_viera/translations/fr.json index 4ee07e94ad4563..9f8c9b672e526d 100644 --- a/homeassistant/components/panasonic_viera/translations/fr.json +++ b/homeassistant/components/panasonic_viera/translations/fr.json @@ -1,8 +1,14 @@ { "config": { "abort": { + "already_configured": "Ce t\u00e9l\u00e9viseur Panasonic Viera est d\u00e9j\u00e0 configur\u00e9.", + "not_connected": "La connexion \u00e0 distance avec votre t\u00e9l\u00e9viseur Panasonic Viera a \u00e9t\u00e9 perdue. Consultez les journaux pour plus d'informations.", "unknown": "Une erreur inconnue est survenue. Veuillez consulter les journaux pour obtenir plus de d\u00e9tails." }, + "error": { + "invalid_pin_code": "Le code PIN que vous avez entr\u00e9 n'est pas valide", + "not_connected": "Impossible d'\u00e9tablir une connexion \u00e0 distance avec votre t\u00e9l\u00e9viseur Panasonic Viera" + }, "step": { "pairing": { "data": { diff --git a/homeassistant/components/pandora/media_player.py b/homeassistant/components/pandora/media_player.py index 459e583c26765e..33ea72f94ffec3 100644 --- a/homeassistant/components/pandora/media_player.py +++ b/homeassistant/components/pandora/media_player.py @@ -88,11 +88,6 @@ def __init__(self, name): self._media_duration = 0 self._pianobar = None - @property - def should_poll(self): - """Return the polling state.""" - return True - @property def name(self): """Return the name of the media player.""" diff --git a/homeassistant/components/person/group.py b/homeassistant/components/person/group.py new file mode 100644 index 00000000000000..07ec2cfe985ed7 --- /dev/null +++ b/homeassistant/components/person/group.py @@ -0,0 +1,15 @@ +"""Describe group states.""" + + +from homeassistant.components.group import GroupIntegrationRegistry +from homeassistant.const import STATE_HOME, STATE_NOT_HOME +from homeassistant.core import callback +from homeassistant.helpers.typing import HomeAssistantType + + +@callback +def async_describe_on_off_states( + hass: HomeAssistantType, registry: GroupIntegrationRegistry +) -> None: + """Describe group on off states.""" + registry.on_off_states({STATE_HOME}, STATE_NOT_HOME) diff --git a/homeassistant/components/pi_hole/strings.json b/homeassistant/components/pi_hole/strings.json index 42faf5d5a46aa7..3bb5289777d5b0 100644 --- a/homeassistant/components/pi_hole/strings.json +++ b/homeassistant/components/pi_hole/strings.json @@ -5,11 +5,11 @@ "data": { "host": "[%key:common::config_flow::data::host%]", "port": "[%key:common::config_flow::data::port%]", - "name": "Name", + "name": "[%key:common::config_flow::data::name%]", "location": "Location", "api_key": "[%key:common::config_flow::data::api_key%]", - "ssl": "Use SSL", - "verify_ssl": "Verify SSL certificate" + "ssl": "[%key:common::config_flow::data::ssl%]", + "verify_ssl": "[%key:common::config_flow::data::verify_ssl%]" } } }, diff --git a/homeassistant/components/pi_hole/translations/ca.json b/homeassistant/components/pi_hole/translations/ca.json index 1a8b91fad6d820..37d4e890ef4a5e 100644 --- a/homeassistant/components/pi_hole/translations/ca.json +++ b/homeassistant/components/pi_hole/translations/ca.json @@ -14,7 +14,7 @@ "location": "Ubicaci\u00f3", "name": "Nom", "port": "Port", - "ssl": "Utilitza SSL", + "ssl": "Utilitza un certificat SSL", "verify_ssl": "Verifica el certificat SSL" } } diff --git a/homeassistant/components/pi_hole/translations/en.json b/homeassistant/components/pi_hole/translations/en.json index 98ac63514b6b2e..858e7c230ac085 100644 --- a/homeassistant/components/pi_hole/translations/en.json +++ b/homeassistant/components/pi_hole/translations/en.json @@ -14,7 +14,7 @@ "location": "Location", "name": "Name", "port": "Port", - "ssl": "Use SSL", + "ssl": "Uses an SSL certificate", "verify_ssl": "Verify SSL certificate" } } diff --git a/homeassistant/components/pi_hole/translations/it.json b/homeassistant/components/pi_hole/translations/it.json index b8a155e9374f8f..34590ee77bb14d 100644 --- a/homeassistant/components/pi_hole/translations/it.json +++ b/homeassistant/components/pi_hole/translations/it.json @@ -14,7 +14,7 @@ "location": "Posizione", "name": "Nome", "port": "Porta", - "ssl": "Utilizzare SSL", + "ssl": "Utilizza un certificato SSL", "verify_ssl": "Verificare il certificato SSL" } } diff --git a/homeassistant/components/pi_hole/translations/no.json b/homeassistant/components/pi_hole/translations/no.json index 387b6c0d1eb0ec..f6a3b66b4dc313 100644 --- a/homeassistant/components/pi_hole/translations/no.json +++ b/homeassistant/components/pi_hole/translations/no.json @@ -14,7 +14,7 @@ "location": "Beliggenhet", "name": "Navn", "port": "", - "ssl": "Bruk SSL", + "ssl": "Bruker et SSL-sertifikat", "verify_ssl": "Verifisere SSL-sertifikat" } } diff --git a/homeassistant/components/pi_hole/translations/pl.json b/homeassistant/components/pi_hole/translations/pl.json index b974e6c04c98ee..394a24a50502a3 100644 --- a/homeassistant/components/pi_hole/translations/pl.json +++ b/homeassistant/components/pi_hole/translations/pl.json @@ -4,7 +4,7 @@ "already_configured": "Us\u0142uga jest ju\u017c skonfigurowana." }, "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia." + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, "step": { "user": { diff --git a/homeassistant/components/pi_hole/translations/ru.json b/homeassistant/components/pi_hole/translations/ru.json index 6f7dd24e5e2a61..eb3cfa62c623ec 100644 --- a/homeassistant/components/pi_hole/translations/ru.json +++ b/homeassistant/components/pi_hole/translations/ru.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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." @@ -14,7 +14,7 @@ "location": "\u041c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435", "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435", "port": "\u041f\u043e\u0440\u0442", - "ssl": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c SSL", + "ssl": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL", "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/pi_hole/translations/zh-Hant.json b/homeassistant/components/pi_hole/translations/zh-Hant.json index 1a75757dcc6b91..1cea5a87f4b5f1 100644 --- a/homeassistant/components/pi_hole/translations/zh-Hant.json +++ b/homeassistant/components/pi_hole/translations/zh-Hant.json @@ -14,7 +14,7 @@ "location": "\u5ea7\u6a19", "name": "\u540d\u7a31", "port": "\u901a\u8a0a\u57e0", - "ssl": "\u4f7f\u7528 SSL", + "ssl": "\u4f7f\u7528 SSL \u8a8d\u8b49", "verify_ssl": "\u78ba\u8a8d SSL \u8a8d\u8b49" } } diff --git a/homeassistant/components/pilight/light.py b/homeassistant/components/pilight/light.py index 12d175817d74e7..11eeb02293e118 100644 --- a/homeassistant/components/pilight/light.py +++ b/homeassistant/components/pilight/light.py @@ -61,7 +61,20 @@ def supported_features(self): def turn_on(self, **kwargs): """Turn the switch on by calling pilight.send service with on code.""" - self._brightness = kwargs.get(ATTR_BRIGHTNESS, 255) - dimlevel = int(self._brightness / (255 / self._dimlevel_max)) + # Update brightness only if provided as an argument. + # This will allow the switch to keep its previous brightness level. + dimlevel = None + + if ATTR_BRIGHTNESS in kwargs: + self._brightness = kwargs[ATTR_BRIGHTNESS] + + # Calculate pilight brightness (as a range of 0 to 15) + # By creating a percentage + percentage = self._brightness / 255 + # Then calculate the dimmer range (aka amount of available brightness steps). + dimrange = self._dimlevel_max - self._dimlevel_min + # Finally calculate the pilight brightness. + # We add dimlevel_min back in to ensure the minimum is always reached. + dimlevel = int(percentage * dimrange + self._dimlevel_min) self.set_state(turn_on=True, dimlevel=dimlevel) diff --git a/homeassistant/components/ping/binary_sensor.py b/homeassistant/components/ping/binary_sensor.py index ac73da0a13ffa0..afbfe80b43f36c 100644 --- a/homeassistant/components/ping/binary_sensor.py +++ b/homeassistant/components/ping/binary_sensor.py @@ -10,7 +10,11 @@ from icmplib import SocketPermissionError, ping as icmp_ping import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_CONNECTIVITY, + PLATFORM_SCHEMA, + BinarySensorEntity, +) from homeassistant.const import CONF_HOST, CONF_NAME import homeassistant.helpers.config_validation as cv from homeassistant.helpers.reload import setup_reload_service @@ -30,7 +34,6 @@ DEFAULT_NAME = "Ping" DEFAULT_PING_COUNT = 5 -DEFAULT_DEVICE_CLASS = "connectivity" SCAN_INTERVAL = timedelta(minutes=5) @@ -94,7 +97,7 @@ def name(self) -> str: @property def device_class(self) -> str: """Return the class of this sensor.""" - return DEFAULT_DEVICE_CLASS + return DEVICE_CLASS_CONNECTIVITY @property def is_on(self) -> bool: @@ -210,7 +213,8 @@ async def async_ping(self): out_error, ) - if pinger.returncode != 0: + if pinger.returncode > 1: + # returncode of 1 means the host is unreachable _LOGGER.exception( "Error running command: `%s`, return code: %s", " ".join(self._ping_cmd), diff --git a/homeassistant/components/plaato/strings.json b/homeassistant/components/plaato/strings.json index f78943ca941877..e03d2508037012 100644 --- a/homeassistant/components/plaato/strings.json +++ b/homeassistant/components/plaato/strings.json @@ -7,7 +7,7 @@ } }, "abort": { - "one_instance_allowed": "Only a single instance is necessary.", + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive messages from Plaato Airlock." }, "create_entry": { diff --git a/homeassistant/components/plaato/translations/ca.json b/homeassistant/components/plaato/translations/ca.json index a0e610e452794a..5f872379d294cc 100644 --- a/homeassistant/components/plaato/translations/ca.json +++ b/homeassistant/components/plaato/translations/ca.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "La inst\u00e0ncia de Home Assistant ha de ser accessible des d'Internet per rebre missatges de Plaato Airlock.", - "one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia." + "one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia.", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "create_entry": { "default": "Per enviar esdeveniments a Home Assistant, haur\u00e0s de configurar l'opci\u00f3 webhook de Plaato Airlock.\n\nCompleta la seg\u00fcent informaci\u00f3:\n\n- URL: `{webhook_url}` \n- M\u00e8tode: POST \n\nConsulta la [documentaci\u00f3]({docs_url}) per a m\u00e9s detalls." diff --git a/homeassistant/components/plaato/translations/el.json b/homeassistant/components/plaato/translations/el.json new file mode 100644 index 00000000000000..aecb2ee553fa42 --- /dev/null +++ b/homeassistant/components/plaato/translations/el.json @@ -0,0 +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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plaato/translations/en.json b/homeassistant/components/plaato/translations/en.json index 42d37f042fb00f..34bd9fad511320 100644 --- a/homeassistant/components/plaato/translations/en.json +++ b/homeassistant/components/plaato/translations/en.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive messages from Plaato Airlock.", - "one_instance_allowed": "Only a single instance is necessary." + "one_instance_allowed": "Only a single instance is necessary.", + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "create_entry": { "default": "To send events to Home Assistant, you will need to setup the webhook feature in Plaato Airlock.\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nSee [the documentation]({docs_url}) for further details." diff --git a/homeassistant/components/plaato/translations/es.json b/homeassistant/components/plaato/translations/es.json index 90ad53085f1193..bce201af055523 100644 --- a/homeassistant/components/plaato/translations/es.json +++ b/homeassistant/components/plaato/translations/es.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Tu instancia de Home Assistant debe ser accesible desde Internet para recibir mensajes de Plaato Airlock.", - "one_instance_allowed": "S\u00f3lo se necesita una instancia." + "one_instance_allowed": "S\u00f3lo se necesita una instancia.", + "single_instance_allowed": "Ya est\u00e1 configurado. S\u00f3lo es posible una \u00fanica configuraci\u00f3n." }, "create_entry": { "default": "Para enviar eventos a Home Assistant, necesitar\u00e1s configurar la funci\u00f3n de webhook en Plaato Airlock.\n\nCompleta la siguiente informaci\u00f3n:\n\n- URL: `{webhook_url}`\n- M\u00e9todo: POST\n\nEcha un vistazo a [la documentaci\u00f3n]({docs_url}) para m\u00e1s detalles." diff --git a/homeassistant/components/plaato/translations/et.json b/homeassistant/components/plaato/translations/et.json new file mode 100644 index 00000000000000..e8b5eae41495c6 --- /dev/null +++ b/homeassistant/components/plaato/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plaato/translations/fr.json b/homeassistant/components/plaato/translations/fr.json index 79ea8c05b49926..cd8d46fa0c439c 100644 --- a/homeassistant/components/plaato/translations/fr.json +++ b/homeassistant/components/plaato/translations/fr.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Votre instance de Home Assistant doit \u00eatre accessible depuis Internet pour recevoir les messages de Plaato Airlock.", - "one_instance_allowed": "Une seule instance est n\u00e9cessaire." + "one_instance_allowed": "Une seule instance est n\u00e9cessaire.", + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." }, "create_entry": { "default": "Pour envoyer des \u00e9v\u00e9nements \u00e0 Home Assistant, vous devez configurer la fonction Webhook dans Plaato Airlock. \n\n Remplissez les informations suivantes: \n\n - URL: ` {webhook_url} ` \n - M\u00e9thode: POST \n\n Voir [la documentation] ( {docs_url} ) pour plus de d\u00e9tails." diff --git a/homeassistant/components/plaato/translations/it.json b/homeassistant/components/plaato/translations/it.json index d0f04bca9d991e..798aa63df5d4b4 100644 --- a/homeassistant/components/plaato/translations/it.json +++ b/homeassistant/components/plaato/translations/it.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "La tua istanza di Home Assistant deve essere accessibile da Internet per ricevere messaggi da Plaato Airlook.", - "one_instance_allowed": "\u00c8 necessaria solo una singola istanza." + "one_instance_allowed": "\u00c8 necessaria solo una singola istanza.", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "create_entry": { "default": "Per inviare eventi a Home Assistant, dovrai impostare la funzione webhook in Plaato Airlock. \n\n Inserisci le seguenti informazioni: \n\n - URL: `{webhook_url}` \n - Metodo: POST \n\n Vedi [la documentazione]({docs_url}) per ulteriori dettagli." diff --git a/homeassistant/components/plaato/translations/lb.json b/homeassistant/components/plaato/translations/lb.json index 5bded3676dbf36..61fa40ebb5a66c 100644 --- a/homeassistant/components/plaato/translations/lb.json +++ b/homeassistant/components/plaato/translations/lb.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "\u00c4r Home Assistant Instanz muss iwwert Internet accessibel si fir Plaato Airlock Noriichten z'empf\u00e4nken.", - "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg." + "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg.", + "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech." }, "create_entry": { "default": "Fir Evenementer un Home Assistant ze sch\u00e9cken, muss den Webhook Feature am Plaato Airlock ageriicht ginn.\n\nF\u00ebllt folgend Informatiounen aus:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nLiest [Dokumentatioun]({docs_url}) fir w\u00e9ider D\u00e9tailer." diff --git a/homeassistant/components/plaato/translations/no.json b/homeassistant/components/plaato/translations/no.json index 4f771b7b963eba..939c812348915e 100644 --- a/homeassistant/components/plaato/translations/no.json +++ b/homeassistant/components/plaato/translations/no.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Home Assistant m\u00e5 v\u00e6re tilgjengelig fra internett for \u00e5 kunne motta meldinger fra Plaato Airlock.", - "one_instance_allowed": "Kun en forekomst er n\u00f8dvendig." + "one_instance_allowed": "Kun en forekomst er n\u00f8dvendig.", + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "create_entry": { "default": "For \u00e5 sende hendelser til Home Assistant, m\u00e5 du sette opp webhook-funksjonen i Plaato Airlock. \n\n Fyll ut f\u00f8lgende informasjon: \n\n - URL: `{webhook_url}` \n - Metode: POST \n\n Se [dokumentasjonen]({docs_url}) for ytterligere detaljer." diff --git a/homeassistant/components/plaato/translations/ru.json b/homeassistant/components/plaato/translations/ru.json index 2e6e876e89688a..d9f7a5711373c5 100644 --- a/homeassistant/components/plaato/translations/ru.json +++ b/homeassistant/components/plaato/translations/ru.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 Plaato Airlock.", - "one_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "one_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e." }, "create_entry": { "default": "\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 Home Assistant \u0432\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Webhook \u0434\u043b\u044f Plaato Airlock.\n\n\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e:\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\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]({docs_url}) \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." diff --git a/homeassistant/components/plaato/translations/zh-Hant.json b/homeassistant/components/plaato/translations/zh-Hant.json index b374a3111e8aab..51a8bfcae19cbd 100644 --- a/homeassistant/components/plaato/translations/zh-Hant.json +++ b/homeassistant/components/plaato/translations/zh-Hant.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Home Assistant \u8a2d\u5099\u5fc5\u9808\u80fd\u5920\u7531\u7db2\u969b\u7db2\u8def\u5b58\u53d6\uff0c\u65b9\u80fd\u63a5\u53d7 Plaato Airlock \u8a0a\u606f\u3002", - "one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u5373\u53ef\u3002" + "one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u5373\u53ef\u3002", + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002" }, "create_entry": { "default": "\u6b32\u50b3\u9001\u4e8b\u4ef6\u81f3 Home Assistant\uff0c\u5c07\u9700\u65bc Plaato Airlock \u5167\u8a2d\u5b9a webhook \u529f\u80fd\u3002\n\n\u8acb\u586b\u5beb\u4e0b\u5217\u8cc7\u8a0a\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n\n\u8acb\u53c3\u95b1 [\u6587\u4ef6]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u8a73\u7d30\u8cc7\u6599\u3002" diff --git a/homeassistant/components/plant/__init__.py b/homeassistant/components/plant/__init__.py index d78b12c06e0fa2..1cb2416d12a113 100644 --- a/homeassistant/components/plant/__init__.py +++ b/homeassistant/components/plant/__init__.py @@ -12,6 +12,7 @@ ATTR_UNIT_OF_MEASUREMENT, CONDUCTIVITY, CONF_SENSORS, + LIGHT_LUX, PERCENTAGE, STATE_OK, STATE_PROBLEM, @@ -153,7 +154,7 @@ class Plant(Entity): "max": CONF_MAX_CONDUCTIVITY, }, READING_BRIGHTNESS: { - ATTR_UNIT_OF_MEASUREMENT: "lux", + ATTR_UNIT_OF_MEASUREMENT: LIGHT_LUX, "min": CONF_MIN_BRIGHTNESS, "max": CONF_MAX_BRIGHTNESS, }, diff --git a/homeassistant/components/plex/__init__.py b/homeassistant/components/plex/__init__.py index 4e5abad4f79b85..f0052ef88301a9 100644 --- a/homeassistant/components/plex/__init__.py +++ b/homeassistant/components/plex/__init__.py @@ -5,7 +5,14 @@ import logging import plexapi.exceptions -from plexwebsocket import PlexWebsocket +from plexwebsocket import ( + SIGNAL_CONNECTION_STATE, + SIGNAL_DATA, + STATE_CONNECTED, + STATE_DISCONNECTED, + STATE_STOPPED, + PlexWebsocket, +) import requests.exceptions import voluptuous as vol @@ -14,12 +21,15 @@ ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, ) +from homeassistant.config_entries import ENTRY_STATE_SETUP_RETRY, SOURCE_REAUTH from homeassistant.const import ( ATTR_ENTITY_ID, + CONF_SOURCE, CONF_URL, CONF_VERIFY_SSL, EVENT_HOMEASSISTANT_STOP, ) +from homeassistant.core import callback from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -75,7 +85,11 @@ async def async_setup_entry(hass, entry): hass.config_entries.async_update_entry(entry, options=options) plex_server = PlexServer( - hass, server_config, entry.data[CONF_SERVER_IDENTIFIER], entry.options + hass, + server_config, + entry.data[CONF_SERVER_IDENTIFIER], + entry.options, + entry.entry_id, ) try: await hass.async_add_executor_job(plex_server.connect) @@ -89,15 +103,28 @@ async def async_setup_entry(hass, entry): entry, data={**entry.data, PLEX_SERVER_CONFIG: new_server_data} ) except requests.exceptions.ConnectionError as error: + if entry.state != ENTRY_STATE_SETUP_RETRY: + _LOGGER.error( + "Plex server (%s) could not be reached: [%s]", + server_config[CONF_URL], + error, + ) + raise ConfigEntryNotReady from error + except plexapi.exceptions.Unauthorized: + hass.async_create_task( + hass.config_entries.flow.async_init( + PLEX_DOMAIN, + context={CONF_SOURCE: SOURCE_REAUTH}, + data=entry.data, + ) + ) _LOGGER.error( - "Plex server (%s) could not be reached: [%s]", - server_config[CONF_URL], - error, + "Token not accepted, please reauthenticate Plex server '%s'", + entry.data[CONF_SERVER], ) - raise ConfigEntryNotReady from error + return False except ( plexapi.exceptions.BadRequest, - plexapi.exceptions.Unauthorized, plexapi.exceptions.NotFound, ) as error: _LOGGER.error( @@ -124,13 +151,36 @@ async def async_setup_entry(hass, entry): hass.data[PLEX_DOMAIN][DISPATCHERS].setdefault(server_id, []) hass.data[PLEX_DOMAIN][DISPATCHERS][server_id].append(unsub) - def update_plex(): - async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) + @callback + def plex_websocket_callback(signal, data, error): + """Handle callbacks from plexwebsocket library.""" + if signal == SIGNAL_CONNECTION_STATE: + + if data == STATE_CONNECTED: + _LOGGER.debug("Websocket to %s successful", entry.data[CONF_SERVER]) + elif data == STATE_DISCONNECTED: + _LOGGER.debug( + "Websocket to %s disconnected, retrying", entry.data[CONF_SERVER] + ) + # Stopped websockets without errors are expected during shutdown and ignored + elif data == STATE_STOPPED and error: + _LOGGER.error( + "Websocket to %s failed, aborting [Error: %s]", + entry.data[CONF_SERVER], + error, + ) + hass.async_create_task(hass.config_entries.async_reload(entry.entry_id)) + + elif signal == SIGNAL_DATA: + async_dispatcher_send(hass, PLEX_UPDATE_PLATFORMS_SIGNAL.format(server_id)) session = async_get_clientsession(hass) verify_ssl = server_config.get(CONF_VERIFY_SSL) websocket = PlexWebsocket( - plex_server.plex_server, update_plex, session=session, verify_ssl=verify_ssl + plex_server.plex_server, + plex_websocket_callback, + session=session, + verify_ssl=verify_ssl, ) hass.data[PLEX_DOMAIN][WEBSOCKETS][server_id] = websocket @@ -207,7 +257,10 @@ async def async_unload_entry(hass, entry): async def async_options_updated(hass, entry): """Triggered by config entry options updates.""" server_id = entry.data[CONF_SERVER_IDENTIFIER] - hass.data[PLEX_DOMAIN][SERVERS][server_id].options = entry.options + + # Guard incomplete setup during reauth flows + if server_id in hass.data[PLEX_DOMAIN][SERVERS]: + hass.data[PLEX_DOMAIN][SERVERS][server_id].options = entry.options def play_on_sonos(hass, service_call): diff --git a/homeassistant/components/plex/config_flow.py b/homeassistant/components/plex/config_flow.py index ffadba63d3a220..bdbdc9c6cc9ae7 100644 --- a/homeassistant/components/plex/config_flow.py +++ b/homeassistant/components/plex/config_flow.py @@ -16,6 +16,7 @@ CONF_CLIENT_ID, CONF_HOST, CONF_PORT, + CONF_SOURCE, CONF_SSL, CONF_TOKEN, CONF_URL, @@ -70,7 +71,7 @@ async def async_discover(hass): for server_data in gdm.entries: await hass.config_entries.flow.async_init( DOMAIN, - context={"source": config_entries.SOURCE_INTEGRATION_DISCOVERY}, + context={CONF_SOURCE: config_entries.SOURCE_INTEGRATION_DISCOVERY}, data=server_data, ) @@ -209,10 +210,6 @@ async def async_step_server_validate(self, server_config): return await self.async_step_user(errors=errors) server_id = plex_server.machine_identifier - - await self.async_set_unique_id(server_id) - self._abort_if_unique_id_configured() - url = plex_server.url_in_use token = server_config.get(CONF_TOKEN) @@ -226,16 +223,27 @@ async def async_step_server_validate(self, server_config): CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL ) + data = { + CONF_SERVER: plex_server.friendly_name, + CONF_SERVER_IDENTIFIER: server_id, + PLEX_SERVER_CONFIG: entry_config, + } + + entry = await self.async_set_unique_id(server_id) + if ( + self.context[CONF_SOURCE] # pylint: disable=no-member + == config_entries.SOURCE_REAUTH + ): + self.hass.config_entries.async_update_entry(entry, data=data) + _LOGGER.debug("Updated config entry for %s", plex_server.friendly_name) + await self.hass.config_entries.async_reload(entry.entry_id) + return self.async_abort(reason="reauth_successful") + + self._abort_if_unique_id_configured() + _LOGGER.debug("Valid config created for %s", plex_server.friendly_name) - return self.async_create_entry( - title=plex_server.friendly_name, - data={ - CONF_SERVER: plex_server.friendly_name, - CONF_SERVER_IDENTIFIER: server_id, - PLEX_SERVER_CONFIG: entry_config, - }, - ) + return self.async_create_entry(title=plex_server.friendly_name, data=data) async def async_step_select_server(self, user_input=None): """Use selected Plex server.""" @@ -316,6 +324,11 @@ async def async_step_use_external_token(self, user_input=None): server_config = {CONF_TOKEN: self.token} return await self.async_step_server_validate(server_config) + async def async_step_reauth(self, data): + """Handle a reauthorization flow request.""" + self.current_login = dict(data) + return await self.async_step_user() + class PlexOptionsFlowHandler(config_entries.OptionsFlow): """Handle Plex options.""" diff --git a/homeassistant/components/plex/manifest.json b/homeassistant/components/plex/manifest.json index bbf7be9914ef80..f5bbc6ac53c2ed 100644 --- a/homeassistant/components/plex/manifest.json +++ b/homeassistant/components/plex/manifest.json @@ -4,9 +4,9 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/plex", "requirements": [ - "plexapi==4.1.0", + "plexapi==4.1.1", "plexauth==0.0.5", - "plexwebsocket==0.0.11" + "plexwebsocket==0.0.12" ], "dependencies": ["http"], "after_dependencies": ["sonos"], diff --git a/homeassistant/components/plex/media_browser.py b/homeassistant/components/plex/media_browser.py index 28444c4a3514c0..56e6f68a9686a3 100644 --- a/homeassistant/components/plex/media_browser.py +++ b/homeassistant/components/plex/media_browser.py @@ -103,7 +103,7 @@ def build_item_response(payload): children_media_class = ITEM_TYPE_MEDIA_CLASS[library_or_section.TYPE] except KeyError as err: raise BrowseError( - f"Media not found: {media_content_type} / {media_content_id}" + f"Unknown type received: {library_or_section.TYPE}" ) from err else: raise BrowseError( @@ -205,6 +205,7 @@ def special_library_payload(parent_payload, special_type): media_content_type=parent_payload.media_content_type, can_play=False, can_expand=True, + children_media_class=parent_payload.children_media_class, ) @@ -217,9 +218,9 @@ def server_payload(plex_server): media_content_type="server", can_play=False, can_expand=True, + children_media_class=MEDIA_CLASS_DIRECTORY, ) server_info.children = [] - server_info.children_media_class = MEDIA_CLASS_DIRECTORY server_info.children.append(special_library_payload(server_info, "On Deck")) server_info.children.append(special_library_payload(server_info, "Recently Added")) for library in plex_server.library.sections(): diff --git a/homeassistant/components/plex/media_player.py b/homeassistant/components/plex/media_player.py index f5dc98e4eb1d61..94bed1db7debbd 100644 --- a/homeassistant/components/plex/media_player.py +++ b/homeassistant/components/plex/media_player.py @@ -597,7 +597,7 @@ def play_media(self, media_type, media_id, **kwargs): @property def device_state_attributes(self): """Return the scene state attributes.""" - attr = { + return { "media_content_rating": self._media_content_rating, "session_username": self.username, "media_library_name": self._app_name, @@ -605,8 +605,6 @@ def device_state_attributes(self): "player_source": self.player_source, } - return attr - @property def device_info(self): """Return a device description for device registry.""" diff --git a/homeassistant/components/plex/server.py b/homeassistant/components/plex/server.py index f8706eadf22599..a5ac287328e3c2 100644 --- a/homeassistant/components/plex/server.py +++ b/homeassistant/components/plex/server.py @@ -62,9 +62,12 @@ class PlexServer: """Manages a single Plex server connection.""" - def __init__(self, hass, server_config, known_server_id=None, options=None): + def __init__( + self, hass, server_config, known_server_id=None, options=None, entry_id=None + ): """Initialize a Plex server instance.""" self.hass = hass + self.entry_id = entry_id self._plex_account = None self._plex_server = None self._created_clients = set() @@ -270,6 +273,12 @@ async def _async_update_platforms(self): devices, sessions, plextv_clients = await self.hass.async_add_executor_job( self._fetch_platform_data ) + except plexapi.exceptions.Unauthorized: + _LOGGER.debug( + "Token has expired for '%s', reloading integration", self.friendly_name + ) + await self.hass.config_entries.async_reload(self.entry_id) + return except ( plexapi.exceptions.BadRequest, requests.exceptions.RequestException, diff --git a/homeassistant/components/plex/strings.json b/homeassistant/components/plex/strings.json index 2f50e2d30908dc..bfe6375f5ac5cc 100644 --- a/homeassistant/components/plex/strings.json +++ b/homeassistant/components/plex/strings.json @@ -17,8 +17,8 @@ "data": { "host": "[%key:common::config_flow::data::host%]", "port": "[%key:common::config_flow::data::port%]", - "ssl": "Use SSL", - "verify_ssl": "Verify SSL certificate", + "ssl": "[%key:common::config_flow::data::ssl%]", + "verify_ssl": "[%key:common::config_flow::data::verify_ssl%]", "token": "Token (Optional)" } }, @@ -40,9 +40,10 @@ "abort": { "all_configured": "All linked servers already configured", "already_configured": "This Plex server is already configured", - "already_in_progress": "Plex is being configured", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", "token_request_timeout": "Timed out obtaining token", - "unknown": "Failed for unknown reason" + "unknown": "[%key:common::config_flow::error::unknown%]" } }, "options": { diff --git a/homeassistant/components/plex/translations/ca.json b/homeassistant/components/plex/translations/ca.json index be4b6215f8b656..32fcb976c38fef 100644 --- a/homeassistant/components/plex/translations/ca.json +++ b/homeassistant/components/plex/translations/ca.json @@ -3,9 +3,10 @@ "abort": { "all_configured": "Tots els servidors enlla\u00e7ats ja estan configurats", "already_configured": "Aquest servidor Plex ja est\u00e0 configurat", - "already_in_progress": "S'est\u00e0 configurant Plex", + "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", + "reauth_successful": "Re-autenticaci\u00f3 exitosa", "token_request_timeout": "S'ha acabat el temps d'espera durant l'obtenci\u00f3 del token.", - "unknown": "Ha fallat per motiu desconegut" + "unknown": "Error inesperat" }, "error": { "faulty_credentials": "Ha fallat l'autoritzaci\u00f3, comprova el Token", @@ -20,7 +21,7 @@ "data": { "host": "Amfitri\u00f3", "port": "Port", - "ssl": "Utilitza SSL", + "ssl": "Utilitza un certificat SSL", "token": "Token (opcional)", "verify_ssl": "Verifica el certificat SSL" }, diff --git a/homeassistant/components/plex/translations/de.json b/homeassistant/components/plex/translations/de.json index b14e3a3c574458..961ad4b3ed6e3e 100644 --- a/homeassistant/components/plex/translations/de.json +++ b/homeassistant/components/plex/translations/de.json @@ -14,6 +14,7 @@ "not_found": "Plex-Server nicht gefunden", "ssl_error": "SSL-Zertifikatsproblem" }, + "flow_title": "{name} ({host})", "step": { "manual_setup": { "data": { diff --git a/homeassistant/components/plex/translations/el.json b/homeassistant/components/plex/translations/el.json new file mode 100644 index 00000000000000..54f64b814fd6cf --- /dev/null +++ b/homeassistant/components/plex/translations/el.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "reauth_successful": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c4\u03b7\u03ba\u03b5 \u03be\u03b1\u03bd\u03ac \u03bc\u03b5 \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03af\u03b1" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plex/translations/en.json b/homeassistant/components/plex/translations/en.json index 83e5196fc357fe..a278fe2224d000 100644 --- a/homeassistant/components/plex/translations/en.json +++ b/homeassistant/components/plex/translations/en.json @@ -3,9 +3,10 @@ "abort": { "all_configured": "All linked servers already configured", "already_configured": "This Plex server is already configured", - "already_in_progress": "Plex is being configured", + "already_in_progress": "Configuration flow is already in progress", + "reauth_successful": "Successfully reauthenticated", "token_request_timeout": "Timed out obtaining token", - "unknown": "Failed for unknown reason" + "unknown": "Unexpected error" }, "error": { "faulty_credentials": "Authorization failed, verify Token", @@ -20,7 +21,7 @@ "data": { "host": "Host", "port": "Port", - "ssl": "Use SSL", + "ssl": "Uses an SSL certificate", "token": "Token (Optional)", "verify_ssl": "Verify SSL certificate" }, diff --git a/homeassistant/components/plex/translations/es.json b/homeassistant/components/plex/translations/es.json index 907025590c6efc..cc5f4569020000 100644 --- a/homeassistant/components/plex/translations/es.json +++ b/homeassistant/components/plex/translations/es.json @@ -4,6 +4,7 @@ "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", + "reauth_successful": "Se ha vuelto a autenticar con \u00e9xito", "token_request_timeout": "Tiempo de espera agotado para la obtenci\u00f3n del token", "unknown": "Fall\u00f3 por razones desconocidas" }, diff --git a/homeassistant/components/plex/translations/it.json b/homeassistant/components/plex/translations/it.json index b0996fad3d7aa9..ce487d6ec27449 100644 --- a/homeassistant/components/plex/translations/it.json +++ b/homeassistant/components/plex/translations/it.json @@ -3,9 +3,10 @@ "abort": { "all_configured": "Tutti i server collegati sono gi\u00e0 configurati", "already_configured": "Questo server Plex \u00e8 gi\u00e0 configurato", - "already_in_progress": "Plex \u00e8 in fase di configurazione", + "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", + "reauth_successful": "Ri-autenticato con successo", "token_request_timeout": "Timeout per l'ottenimento del token", - "unknown": "Non riuscito per motivo sconosciuto" + "unknown": "Errore imprevisto" }, "error": { "faulty_credentials": "Autorizzazione non riuscita, verificare il Token", @@ -20,7 +21,7 @@ "data": { "host": "Host", "port": "Porta", - "ssl": "Utilizzare SSL", + "ssl": "Utilizza un certificato SSL", "token": "Token (opzionale)", "verify_ssl": "Verificare il certificato SSL" }, diff --git a/homeassistant/components/plex/translations/lb.json b/homeassistant/components/plex/translations/lb.json index 3a01e3f67c1cdd..eda0cb04b0384e 100644 --- a/homeassistant/components/plex/translations/lb.json +++ b/homeassistant/components/plex/translations/lb.json @@ -4,6 +4,7 @@ "all_configured": "All verbonne Server sinn scho konfigur\u00e9iert", "already_configured": "D\u00ebse Plex Server ass scho konfigur\u00e9iert", "already_in_progress": "Plex g\u00ebtt konfigur\u00e9iert", + "reauth_successful": "Erfollegr\u00e4ich re-authentifiz\u00e9iert", "token_request_timeout": "Z\u00e4it Iwwerschreidung beim kr\u00e9ien vum Jeton", "unknown": "Onbekannte Feeler opgetrueden" }, diff --git a/homeassistant/components/plex/translations/no.json b/homeassistant/components/plex/translations/no.json index c7374e27b60174..33ed22fdb231c6 100644 --- a/homeassistant/components/plex/translations/no.json +++ b/homeassistant/components/plex/translations/no.json @@ -3,9 +3,10 @@ "abort": { "all_configured": "Alle knyttet servere som allerede er konfigurert", "already_configured": "Denne Plex-serveren er allerede konfigurert", - "already_in_progress": "Plex blir konfigurert", + "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", + "reauth_successful": "Godkjent p\u00e5 nytt", "token_request_timeout": "Tidsavbrudd ved innhenting av token", - "unknown": "Mislyktes av ukjent \u00e5rsak" + "unknown": "Uventet feil" }, "error": { "faulty_credentials": "Autorisasjonen mislyktes, bekreft token", @@ -20,7 +21,7 @@ "data": { "host": "Vert", "port": "", - "ssl": "Bruk SSL", + "ssl": "Bruker et SSL-sertifikat", "token": "Token (valgfritt)", "verify_ssl": "Verifisere SSL-sertifikat" }, diff --git a/homeassistant/components/plex/translations/pl.json b/homeassistant/components/plex/translations/pl.json index a1d4ee96a1bda0..9934c848f18451 100644 --- a/homeassistant/components/plex/translations/pl.json +++ b/homeassistant/components/plex/translations/pl.json @@ -4,6 +4,7 @@ "all_configured": "Wszystkie znalezione serwery s\u0105 ju\u017c skonfigurowane.", "already_configured": "Ten serwer Plex jest ju\u017c skonfigurowany.", "already_in_progress": "Plex jest konfigurowany", + "reauth_successful": "Ponowne uwierzytelnianie powiod\u0142o si\u0119", "token_request_timeout": "Przekroczono limit czasu na uzyskanie tokena.", "unknown": "Nieznany b\u0142\u0105d" }, diff --git a/homeassistant/components/plex/translations/ru.json b/homeassistant/components/plex/translations/ru.json index 29937ccea5fe65..62e8d49967077b 100644 --- a/homeassistant/components/plex/translations/ru.json +++ b/homeassistant/components/plex/translations/ru.json @@ -3,9 +3,10 @@ "abort": { "all_configured": "\u0412\u0441\u0435 \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u044b\u0435 \u0441\u0435\u0440\u0432\u0435\u0440\u044b \u0443\u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u044b.", "already_configured": "\u042d\u0442\u043e\u0442 \u0441\u0435\u0440\u0432\u0435\u0440 Plex \u0443\u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d.", - "already_in_progress": "\u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430.", + "already_in_progress": "\u041f\u0440\u043e\u0446\u0435\u0441\u0441 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.", + "reauth_successful": "\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.", "token_request_timeout": "\u0418\u0441\u0442\u0435\u043a\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0442\u043e\u043a\u0435\u043d\u0430.", - "unknown": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e \u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u043e\u0439 \u043f\u0440\u0438\u0447\u0438\u043d\u0435." + "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "error": { "faulty_credentials": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438, \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0422\u043e\u043a\u0435\u043d.", @@ -20,7 +21,7 @@ "data": { "host": "\u0425\u043e\u0441\u0442", "port": "\u041f\u043e\u0440\u0442", - "ssl": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c SSL", + "ssl": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL", "token": "\u0422\u043e\u043a\u0435\u043d (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)", "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/plex/translations/zh-Hant.json b/homeassistant/components/plex/translations/zh-Hant.json index 2d866880decb49..ea207f2464ef1a 100644 --- a/homeassistant/components/plex/translations/zh-Hant.json +++ b/homeassistant/components/plex/translations/zh-Hant.json @@ -3,9 +3,10 @@ "abort": { "all_configured": "\u6240\u6709\u7d81\u5b9a\u4f3a\u670d\u5668\u90fd\u5df2\u8a2d\u5b9a\u5b8c\u6210", "already_configured": "Plex \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", - "already_in_progress": "Plex \u5df2\u7d93\u8a2d\u5b9a", + "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", + "reauth_successful": "\u5df2\u6210\u529f\u91cd\u65b0\u8a8d\u8b49", "token_request_timeout": "\u53d6\u5f97\u5bc6\u9470\u903e\u6642", - "unknown": "\u672a\u77e5\u539f\u56e0\u5931\u6557" + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "error": { "faulty_credentials": "\u9a57\u8b49\u5931\u6557\u3001\u78ba\u8a8d\u5bc6\u9470", @@ -20,7 +21,7 @@ "data": { "host": "\u4e3b\u6a5f\u7aef", "port": "\u901a\u8a0a\u57e0", - "ssl": "\u4f7f\u7528 SSL", + "ssl": "\u4f7f\u7528 SSL \u8a8d\u8b49", "token": "\u5bc6\u9470\uff08\u9078\u9805\uff09", "verify_ssl": "\u78ba\u8a8d SSL \u8a8d\u8b49" }, diff --git a/homeassistant/components/plugwise/__init__.py b/homeassistant/components/plugwise/__init__.py index 8c140f65af91e5..f7986f915401c6 100644 --- a/homeassistant/components/plugwise/__init__.py +++ b/homeassistant/components/plugwise/__init__.py @@ -10,7 +10,7 @@ import voluptuous as vol from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_SCAN_INTERVAL +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_SCAN_INTERVAL from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import device_registry as dr @@ -21,7 +21,13 @@ UpdateFailed, ) -from .const import COORDINATOR, DEFAULT_SCAN_INTERVAL, DOMAIN, UNDO_UPDATE_LISTENER +from .const import ( + COORDINATOR, + DEFAULT_PORT, + DEFAULT_SCAN_INTERVAL, + DOMAIN, + UNDO_UPDATE_LISTENER, +) CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({})}, extra=vol.ALLOW_EXTRA) @@ -39,9 +45,12 @@ async def async_setup(hass: HomeAssistant, config: dict): async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up Plugwise Smiles from a config entry.""" websession = async_get_clientsession(hass, verify_ssl=False) + api = Smile( host=entry.data[CONF_HOST], password=entry.data[CONF_PASSWORD], + port=entry.data.get(CONF_PORT, DEFAULT_PORT), + timeout=30, websession=websession, ) @@ -94,6 +103,10 @@ async def async_update_data(): api.get_all_devices() + if entry.unique_id is None: + if api.smile_version[0] != "1.8.0": + hass.config_entries.async_update_entry(entry, unique_id=api.smile_hostname) + undo_listener = entry.add_update_listener(_update_listener) hass.data.setdefault(DOMAIN, {})[entry.entry_id] = { diff --git a/homeassistant/components/plugwise/config_flow.py b/homeassistant/components/plugwise/config_flow.py index 20c8a5a216c7bb..14405062231cf2 100644 --- a/homeassistant/components/plugwise/config_flow.py +++ b/homeassistant/components/plugwise/config_flow.py @@ -5,12 +5,16 @@ import voluptuous as vol from homeassistant import config_entries, core, exceptions -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_SCAN_INTERVAL +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_SCAN_INTERVAL from homeassistant.core import callback from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.typing import DiscoveryInfoType -from .const import DEFAULT_SCAN_INTERVAL, DOMAIN # pylint:disable=unused-import +from .const import ( # pylint:disable=unused-import + DEFAULT_PORT, + DEFAULT_SCAN_INTERVAL, + DOMAIN, +) _LOGGER = logging.getLogger(__name__) @@ -27,6 +31,7 @@ def _base_schema(discovery_info): if not discovery_info: base_schema[vol.Required(CONF_HOST)] = str + base_schema[vol.Optional(CONF_PORT, default=DEFAULT_PORT)] = int base_schema[vol.Required(CONF_PASSWORD)] = str @@ -40,9 +45,11 @@ async def validate_input(hass: core.HomeAssistant, data): Data has the keys from _base_schema() with values provided by the user. """ websession = async_get_clientsession(hass, verify_ssl=False) + api = Smile( host=data[CONF_HOST], password=data[CONF_PASSWORD], + port=data[CONF_PORT], timeout=30, websession=websession, ) @@ -83,6 +90,7 @@ async def async_step_zeroconf(self, discovery_info: DiscoveryInfoType): # pylint: disable=no-member # https://github.com/PyCQA/pylint/issues/3167 self.context["title_placeholders"] = { CONF_HOST: discovery_info[CONF_HOST], + CONF_PORT: discovery_info.get(CONF_PORT, DEFAULT_PORT), "name": _name, } return await self.async_step_user() @@ -95,6 +103,11 @@ async def async_step_user(self, user_input=None): if self.discovery_info: user_input[CONF_HOST] = self.discovery_info[CONF_HOST] + user_input[CONF_PORT] = self.discovery_info.get(CONF_PORT, DEFAULT_PORT) + + for entry in self._async_current_entries(): + if entry.data.get(CONF_HOST) == user_input[CONF_HOST]: + return self.async_abort(reason="already_configured") try: api = await validate_input(self.hass, user_input) @@ -107,7 +120,9 @@ async def async_step_user(self, user_input=None): _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" if not errors: - await self.async_set_unique_id(api.gateway_id) + await self.async_set_unique_id( + api.smile_hostname or api.gateway_id, raise_on_progress=False + ) self._abort_if_unique_id_configured() return self.async_create_entry(title=api.smile_name, data=user_input) diff --git a/homeassistant/components/plugwise/manifest.json b/homeassistant/components/plugwise/manifest.json index f4cb9164e5d52a..222db34b34423b 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_Smile==1.4.0"], + "requirements": ["Plugwise_Smile==1.5.1"], "codeowners": ["@CoMPaTech", "@bouwew"], "zeroconf": ["_plugwise._tcp.local."], "config_flow": true diff --git a/homeassistant/components/plugwise/strings.json b/homeassistant/components/plugwise/strings.json index 7dc8542698bca6..6a780282a1393e 100644 --- a/homeassistant/components/plugwise/strings.json +++ b/homeassistant/components/plugwise/strings.json @@ -13,20 +13,21 @@ "step": { "user": { "title": "Connect to the Smile", - "description": "Details", + "description": "Please enter:", "data": { - "host": "Smile IP address", - "password": "Smile ID" + "password": "Smile ID", + "host": "[%key:common::config_flow::data::ip%]", + "port": "[%key:common::config_flow::data::port%]" } } }, "error": { - "cannot_connect": "Failed to connect, please try again", - "invalid_auth": "Invalid authentication, check the 8 characters of your Smile ID", - "unknown": "Unexpected 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": "This Smile is already configured" + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]" }, "flow_title": "Smile: {name}" } diff --git a/homeassistant/components/plugwise/translations/ca.json b/homeassistant/components/plugwise/translations/ca.json index c61c28e66689f3..cabded862e272f 100644 --- a/homeassistant/components/plugwise/translations/ca.json +++ b/homeassistant/components/plugwise/translations/ca.json @@ -1,21 +1,22 @@ { "config": { "abort": { - "already_configured": "Smile ja est\u00e0 configurat" + "already_configured": "El servei ja est\u00e0 configurat" }, "error": { - "cannot_connect": "No s'ha pogut connectar, torna-ho a provar", - "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida, comprova els 8 car\u00e0cters de l'ID de Smile.", + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "unknown": "Error inesperat" }, "flow_title": "Smile: {name}", "step": { "user": { "data": { - "host": "Adre\u00e7a IP de Smile", - "password": "ID de Smile" + "host": "Adre\u00e7a IP", + "password": "ID de Smile", + "port": "Port" }, - "description": "Detalls", + "description": "Introdueix:", "title": "Connecta't amb el Smile" } } diff --git a/homeassistant/components/plugwise/translations/de.json b/homeassistant/components/plugwise/translations/de.json index 7ee92b1ead5018..19d3678b5e8599 100644 --- a/homeassistant/components/plugwise/translations/de.json +++ b/homeassistant/components/plugwise/translations/de.json @@ -4,6 +4,7 @@ "cannot_connect": "Verbindung fehlgeschlagen, versuchen Sie es erneut", "unknown": "Unerwarteter Fehler" }, + "flow_title": "Smile: {name}", "step": { "user": { "data": { diff --git a/homeassistant/components/plugwise/translations/el.json b/homeassistant/components/plugwise/translations/el.json new file mode 100644 index 00000000000000..1636ae0bef10c4 --- /dev/null +++ b/homeassistant/components/plugwise/translations/el.json @@ -0,0 +1,21 @@ +{ + "config": { + "step": { + "user": { + "data": { + "port": "\u0391\u03c1\u03b9\u03b8\u03bc\u03cc\u03c2 \u03b8\u03cd\u03c1\u03b1\u03c2 \u03c7\u03b1\u03bc\u03cc\u03b3\u03b5\u03bb\u03bf\u03c5" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "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": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ce\u03bd \u03c4\u03bf\u03c0\u03bf\u03b8\u03ad\u03c4\u03b7\u03c3\u03b7\u03c2" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plugwise/translations/en.json b/homeassistant/components/plugwise/translations/en.json index 238f435f3abc0f..5ee28addc6402c 100644 --- a/homeassistant/components/plugwise/translations/en.json +++ b/homeassistant/components/plugwise/translations/en.json @@ -1,21 +1,22 @@ { "config": { "abort": { - "already_configured": "This Smile is already configured" + "already_configured": "Service is already configured" }, "error": { - "cannot_connect": "Failed to connect, please try again", - "invalid_auth": "Invalid authentication, check the 8 characters of your Smile ID", + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", "unknown": "Unexpected error" }, "flow_title": "Smile: {name}", "step": { "user": { "data": { - "host": "Smile IP address", - "password": "Smile ID" + "host": "IP Address", + "password": "Smile ID", + "port": "Port" }, - "description": "Details", + "description": "Please enter:", "title": "Connect to the Smile" } } diff --git a/homeassistant/components/plugwise/translations/es.json b/homeassistant/components/plugwise/translations/es.json index 31e876cfe3a12a..9ab00348f81988 100644 --- a/homeassistant/components/plugwise/translations/es.json +++ b/homeassistant/components/plugwise/translations/es.json @@ -13,11 +13,22 @@ "user": { "data": { "host": "Direcci\u00f3n IP de Smile", - "password": "ID Smile" + "password": "ID Smile", + "port": "N\u00famero de puerto de Smile" }, "description": "Detalles", "title": "Conectarse a Smile" } } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Intervalo de escaneo (segundos)" + }, + "description": "Ajustar las opciones de Plugwise" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/plugwise/translations/et.json b/homeassistant/components/plugwise/translations/et.json new file mode 100644 index 00000000000000..4f7b631d2df09b --- /dev/null +++ b/homeassistant/components/plugwise/translations/et.json @@ -0,0 +1,25 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00dchenduse loomine nurjus. Proovi uuesti", + "unknown": "Ootamatu t\u00f5rge" + }, + "step": { + "user": { + "data": { + "port": "Smile pordi number" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "P\u00e4ringute intervall (sekundites)" + }, + "description": "Kohanda Plugwise s\u00e4tteid" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plugwise/translations/fr.json b/homeassistant/components/plugwise/translations/fr.json index 7ef9296193bead..fe4b88ab0f144f 100644 --- a/homeassistant/components/plugwise/translations/fr.json +++ b/homeassistant/components/plugwise/translations/fr.json @@ -13,9 +13,10 @@ "user": { "data": { "host": "Adresse IP de Smile", - "password": "ID Smile" + "password": "ID Smile", + "port": "Num\u00e9ro de port Smile" }, - "description": "D\u00e9tails", + "description": "Veuillez saisir :", "title": "Se connecter \u00e0 Smile" } } diff --git a/homeassistant/components/plugwise/translations/it.json b/homeassistant/components/plugwise/translations/it.json index b1b3365125a6b1..92f5d1a3426d21 100644 --- a/homeassistant/components/plugwise/translations/it.json +++ b/homeassistant/components/plugwise/translations/it.json @@ -1,21 +1,22 @@ { "config": { "abort": { - "already_configured": "Smile gi\u00e0 configurato" + "already_configured": "Il servizio \u00e8 gi\u00e0 configurato" }, "error": { - "cannot_connect": "Impossibile connettersi, si prega di riprovare", - "invalid_auth": "Autenticazione non valida. Controllare gli 8 caratteri dell'ID Smile", + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", "unknown": "Errore imprevisto" }, "flow_title": "Smile: {name}", "step": { "user": { "data": { - "host": "Indirizzo IP Smile", - "password": "ID Smile" + "host": "Indirizzo IP", + "password": "ID Smile", + "port": "Porta" }, - "description": "Dettagli", + "description": "Si prega di inserire:", "title": "Connettersi al dispositivo" } } diff --git a/homeassistant/components/plugwise/translations/ko.json b/homeassistant/components/plugwise/translations/ko.json index df480242f6f3d8..04fcb73828528f 100644 --- a/homeassistant/components/plugwise/translations/ko.json +++ b/homeassistant/components/plugwise/translations/ko.json @@ -13,11 +13,22 @@ "user": { "data": { "host": "Smile IP \uc8fc\uc18c", - "password": "Smile ID" + "password": "Smile ID", + "port": "\uc2a4\ub9c8\uc77c \ud3ec\ud2b8 \ubc88\ud638" }, "description": "\uc138\ubd80 \uc815\ubcf4", "title": "Smile \uc5d0 \uc5f0\uacb0\ud558\uae30" } } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "\uc2a4\uce94 \uac04\uaca9 (\ucd08)" + }, + "description": "Plugwise \uc635\uc158 \uc870\uc815" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/plugwise/translations/lb.json b/homeassistant/components/plugwise/translations/lb.json index 8b0ea38c2f6095..cd2100804a0a31 100644 --- a/homeassistant/components/plugwise/translations/lb.json +++ b/homeassistant/components/plugwise/translations/lb.json @@ -13,11 +13,22 @@ "user": { "data": { "host": "Smile IP Adresse", - "password": "Smile ID" + "password": "Smile ID", + "port": "Smile Port Nummer" }, "description": "Detailler", "title": "Mat Smile verbannen" } } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Scan Intervall (sekonnen)" + }, + "description": "Plugwise Optioune \u00e4nneren" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/plugwise/translations/nl.json b/homeassistant/components/plugwise/translations/nl.json index 964675e0c631e2..5d0bd789957160 100644 --- a/homeassistant/components/plugwise/translations/nl.json +++ b/homeassistant/components/plugwise/translations/nl.json @@ -18,5 +18,14 @@ "title": "Maak verbinding met de Smile" } } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Scaninterval (seconden)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/plugwise/translations/no.json b/homeassistant/components/plugwise/translations/no.json index 2ee14f0e1525cf..4902ada06c2052 100644 --- a/homeassistant/components/plugwise/translations/no.json +++ b/homeassistant/components/plugwise/translations/no.json @@ -13,9 +13,10 @@ "user": { "data": { "host": "Smile IP-adresse", - "password": "" + "password": "", + "port": "Smil portnummer" }, - "description": "Detaljer", + "description": "Vennligst skriv inn:", "title": "Koble til Smile" } } diff --git a/homeassistant/components/plugwise/translations/pl.json b/homeassistant/components/plugwise/translations/pl.json index 135b9d838fcfba..473b02c9b3c689 100644 --- a/homeassistant/components/plugwise/translations/pl.json +++ b/homeassistant/components/plugwise/translations/pl.json @@ -6,18 +6,29 @@ "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", "invalid_auth": "Nieudane uwierzytelnienie, sprawd\u017a Smile ID", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "flow_title": "Smile: {name}", "step": { "user": { "data": { "host": "Adres IP Smile", - "password": "Smile ID" + "password": "Smile ID", + "port": "Numer portu dla Smile" }, "description": "Szczeg\u00f3\u0142y", "title": "Po\u0142\u0105cz si\u0119 ze Smile" } } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Cz\u0119stotliwo\u015b\u0107 skanowania (w sekundach)" + }, + "description": "Dostosowywanie opcji Plugwise" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/plugwise/translations/ru.json b/homeassistant/components/plugwise/translations/ru.json index 1c4f8ded80cf57..5d3afb061fc581 100644 --- a/homeassistant/components/plugwise/translations/ru.json +++ b/homeassistant/components/plugwise/translations/ru.json @@ -13,7 +13,8 @@ "user": { "data": { "host": "IP-\u0430\u0434\u0440\u0435\u0441", - "password": "ID \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430" + "password": "ID \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430", + "port": "\u041d\u043e\u043c\u0435\u0440 \u043f\u043e\u0440\u0442\u0430 Smile" }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 Home Assistant \u0434\u043b\u044f \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0441 Plugwise.", "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/plugwise/translations/zh-Hant.json b/homeassistant/components/plugwise/translations/zh-Hant.json index ee6966c1bdbe6d..58dd71266b3c42 100644 --- a/homeassistant/components/plugwise/translations/zh-Hant.json +++ b/homeassistant/components/plugwise/translations/zh-Hant.json @@ -13,9 +13,10 @@ "user": { "data": { "host": "Smile IP \u4f4d\u5740", - "password": "Smile ID" + "password": "Smile ID", + "port": "Smile \u901a\u8a0a\u57e0" }, - "description": "\u8a73\u7d30\u8cc7\u8a0a", + "description": "\u8acb\u8f38\u5165\u8cc7\u8a0a\uff1a", "title": "\u9023\u7dda\u81f3 Smile" } } diff --git a/homeassistant/components/plum_lightpad/translations/de.json b/homeassistant/components/plum_lightpad/translations/de.json new file mode 100644 index 00000000000000..f55df964f86cbe --- /dev/null +++ b/homeassistant/components/plum_lightpad/translations/de.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Passwort", + "username": "E-Mail" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plum_lightpad/translations/hu.json b/homeassistant/components/plum_lightpad/translations/hu.json new file mode 100644 index 00000000000000..436e8b1fb7dd75 --- /dev/null +++ b/homeassistant/components/plum_lightpad/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/plum_lightpad/translations/pl.json b/homeassistant/components/plum_lightpad/translations/pl.json index 121744d0f0dcef..83d814d65dcc57 100644 --- a/homeassistant/components/plum_lightpad/translations/pl.json +++ b/homeassistant/components/plum_lightpad/translations/pl.json @@ -4,7 +4,7 @@ "already_configured": "Us\u0142uga jest ju\u017c skonfigurowana." }, "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia." + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, "step": { "user": { diff --git a/homeassistant/components/point/binary_sensor.py b/homeassistant/components/point/binary_sensor.py index 5a780c2e57a359..d82ecd096ee2c8 100644 --- a/homeassistant/components/point/binary_sensor.py +++ b/homeassistant/components/point/binary_sensor.py @@ -1,7 +1,11 @@ """Support for Minut Point binary sensors.""" import logging -from homeassistant.components.binary_sensor import DOMAIN, BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_CONNECTIVITY, + DOMAIN, + BinarySensorEntity, +) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -116,7 +120,7 @@ def _webhook_event(self, data, webhook): @property def is_on(self): """Return the state of the binary sensor.""" - if self.device_class == "connectivity": + if self.device_class == DEVICE_CLASS_CONNECTIVITY: # connectivity is the other way around. return not self._is_on return self._is_on diff --git a/homeassistant/components/point/sensor.py b/homeassistant/components/point/sensor.py index 4ac8f0c1832cba..9436877e434e80 100644 --- a/homeassistant/components/point/sensor.py +++ b/homeassistant/components/point/sensor.py @@ -7,6 +7,7 @@ DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, PERCENTAGE, + PRESSURE_HPA, TEMP_CELSIUS, ) from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -21,7 +22,7 @@ SENSOR_TYPES = { DEVICE_CLASS_TEMPERATURE: (None, 1, TEMP_CELSIUS), - DEVICE_CLASS_PRESSURE: (None, 0, "hPa"), + DEVICE_CLASS_PRESSURE: (None, 0, PRESSURE_HPA), DEVICE_CLASS_HUMIDITY: (None, 1, PERCENTAGE), DEVICE_CLASS_SOUND: ("mdi:ear-hearing", 1, "dBa"), } diff --git a/homeassistant/components/point/translations/ca.json b/homeassistant/components/point/translations/ca.json index 184a7c9df581be..d4126e9077118f 100644 --- a/homeassistant/components/point/translations/ca.json +++ b/homeassistant/components/point/translations/ca.json @@ -3,7 +3,7 @@ "abort": { "already_setup": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3.", "authorize_url_fail": "S'ha produ\u00eft un error desconegut al generar l'URL d'autoritzaci\u00f3.", - "authorize_url_timeout": "Temps d'espera durant la generaci\u00f3 de l'URL d'autoritzaci\u00f3 esgotat.", + "authorize_url_timeout": "Temps d'espera esgotat durant la generaci\u00f3 de l'URL d'autoritzaci\u00f3.", "external_setup": "Point s'ha configurat correctament des d'un altre flux de dades.", "no_flows": "El component no est\u00e0 configurat. Mira'n la documentaci\u00f3." }, diff --git a/homeassistant/components/point/translations/es.json b/homeassistant/components/point/translations/es.json index a7247b3d9b3677..5374d2808d9a0b 100644 --- a/homeassistant/components/point/translations/es.json +++ b/homeassistant/components/point/translations/es.json @@ -23,7 +23,7 @@ "data": { "flow_impl": "Proveedor" }, - "description": "\u00bfQuieres comenzar a configurar?", + "description": "\u00bfQuieres iniciar la configuraci\u00f3n?", "title": "Selecciona el m\u00e9todo de autenticaci\u00f3n" } } diff --git a/homeassistant/components/point/translations/pl.json b/homeassistant/components/point/translations/pl.json index 1596ba05916a3c..286c9e67fc88e5 100644 --- a/homeassistant/components/point/translations/pl.json +++ b/homeassistant/components/point/translations/pl.json @@ -12,7 +12,7 @@ }, "error": { "follow_link": "Prosz\u0119 klikn\u0105\u0107 link i uwierzytelni\u0107 przed naci\u015bni\u0119ciem przycisku \"Zatwierd\u017a\"", - "no_token": "Niepoprawny token dost\u0119pu." + "no_token": "Niepoprawny token dost\u0119pu" }, "step": { "auth": { diff --git a/homeassistant/components/poolsense/translations/de.json b/homeassistant/components/poolsense/translations/de.json index d54c09e3cef470..8fc660e8128f2a 100644 --- a/homeassistant/components/poolsense/translations/de.json +++ b/homeassistant/components/poolsense/translations/de.json @@ -6,6 +6,7 @@ "step": { "user": { "data": { + "email": "E-Mail", "password": "Passwort" } } diff --git a/homeassistant/components/poolsense/translations/hu.json b/homeassistant/components/poolsense/translations/hu.json new file mode 100644 index 00000000000000..3b2d79a34a77e2 --- /dev/null +++ b/homeassistant/components/poolsense/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/poolsense/translations/pl.json b/homeassistant/components/poolsense/translations/pl.json index d463be1c5dd4bd..521be99aa40cd5 100644 --- a/homeassistant/components/poolsense/translations/pl.json +++ b/homeassistant/components/poolsense/translations/pl.json @@ -1,17 +1,18 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" }, "error": { - "invalid_auth": "Niepoprawne uwierzytelnienie." + "invalid_auth": "Niepoprawne uwierzytelnienie" }, "step": { "user": { "data": { "email": "Adres e-mail", "password": "[%key_id:common::config_flow::data::password%]" - } + }, + "title": "PoolSense" } } } diff --git a/homeassistant/components/powerwall/strings.json b/homeassistant/components/powerwall/strings.json index ce7e6a1965fa90..86b7db3e9092e3 100644 --- a/homeassistant/components/powerwall/strings.json +++ b/homeassistant/components/powerwall/strings.json @@ -3,14 +3,18 @@ "step": { "user": { "title": "Connect to the powerwall", - "data": { "ip_address": "IP Address" } + "data": { + "ip_address": "[%key:common::config_flow::data::ip%]" + } } }, "error": { - "cannot_connect": "Failed to connect, please try again", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "wrong_version": "Your powerwall uses a software version that is not supported. Please consider upgrading or reporting this issue so it can be resolved.", - "unknown": "Unexpected error" + "unknown": "[%key:common::config_flow::error::unknown%]" }, - "abort": { "already_configured": "The powerwall is already configured" } + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + } } } diff --git a/homeassistant/components/powerwall/translations/ca.json b/homeassistant/components/powerwall/translations/ca.json index b0764b78234600..2c9becb17959e6 100644 --- a/homeassistant/components/powerwall/translations/ca.json +++ b/homeassistant/components/powerwall/translations/ca.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "El Powerwall ja est\u00e0 configurat" + "already_configured": "El dispositiu ja est\u00e0 configurat" }, "error": { - "cannot_connect": "No s'ha pogut connectar, torna-ho a provar", + "cannot_connect": "Ha fallat la connexi\u00f3", "unknown": "Error inesperat", "wrong_version": "El teu Powerwall utilitza una versi\u00f3 de programari no compatible. L'hauries d'actualitzar o informar d'aquest problema perqu\u00e8 sigui solucionat." }, diff --git a/homeassistant/components/powerwall/translations/en.json b/homeassistant/components/powerwall/translations/en.json index 8b45c665d858d5..773daace5c0bef 100644 --- a/homeassistant/components/powerwall/translations/en.json +++ b/homeassistant/components/powerwall/translations/en.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "The powerwall is already configured" + "already_configured": "Device is already configured" }, "error": { - "cannot_connect": "Failed to connect, please try again", + "cannot_connect": "Failed to connect", "unknown": "Unexpected error", "wrong_version": "Your powerwall uses a software version that is not supported. Please consider upgrading or reporting this issue so it can be resolved." }, diff --git a/homeassistant/components/powerwall/translations/et.json b/homeassistant/components/powerwall/translations/et.json new file mode 100644 index 00000000000000..0d70cd06fcaa70 --- /dev/null +++ b/homeassistant/components/powerwall/translations/et.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00dchenduse loomine nurjus. Proovi uuesti", + "unknown": "Ootamatu t\u00f5rge" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/powerwall/translations/it.json b/homeassistant/components/powerwall/translations/it.json index 09a3d5708014fa..422a28b6936958 100644 --- a/homeassistant/components/powerwall/translations/it.json +++ b/homeassistant/components/powerwall/translations/it.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Il Powerwall \u00e8 gi\u00e0 configurato" + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" }, "error": { - "cannot_connect": "Impossibile connettersi, si prega di riprovare", + "cannot_connect": "Impossibile connettersi", "unknown": "Errore imprevisto", "wrong_version": "Il tuo powerwall utilizza una versione del software non supportata. Si prega di considerare l'aggiornamento o la segnalazione di questo problema in modo che possa essere risolto." }, diff --git a/homeassistant/components/powerwall/translations/no.json b/homeassistant/components/powerwall/translations/no.json index a688a9ad8c4472..6690bb08efba38 100644 --- a/homeassistant/components/powerwall/translations/no.json +++ b/homeassistant/components/powerwall/translations/no.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Powerwall er allerede konfigurert" + "already_configured": "Enheten er allerede konfigurert" }, "error": { - "cannot_connect": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen", + "cannot_connect": "Tilkobling mislyktes.", "unknown": "Uventet feil", "wrong_version": "Powerwall bruker en programvareversjon som ikke st\u00f8ttes. Vennligst vurder \u00e5 oppgradere eller rapportere dette problemet, s\u00e5 det kan l\u00f8ses." }, diff --git a/homeassistant/components/powerwall/translations/pl.json b/homeassistant/components/powerwall/translations/pl.json index 20eb71e7c277d2..cc56f1e116dde2 100644 --- a/homeassistant/components/powerwall/translations/pl.json +++ b/homeassistant/components/powerwall/translations/pl.json @@ -5,7 +5,7 @@ }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "unknown": "Nieoczekiwany b\u0142\u0105d.", + "unknown": "Nieoczekiwany b\u0142\u0105d", "wrong_version": "Powerwall u\u017cywa wersji oprogramowania, kt\u00f3ra nie jest obs\u0142ugiwana. Rozwa\u017c uaktualnienie lub zg\u0142oszenie tego problemu, aby mo\u017cna go by\u0142o rozwi\u0105za\u0107." }, "step": { diff --git a/homeassistant/components/powerwall/translations/ru.json b/homeassistant/components/powerwall/translations/ru.json index cf3b1cd4ba04af..a8713bcd04ae31 100644 --- a/homeassistant/components/powerwall/translations/ru.json +++ b/homeassistant/components/powerwall/translations/ru.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", + "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.", "wrong_version": "\u0412\u0430\u0448 powerwall \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0432\u0435\u0440\u0441\u0438\u044e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u043e\u0433\u043e \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0435\u043d\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u043d\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u0438\u043b\u0438 \u0441\u043e\u043e\u0431\u0449\u0438\u0442\u0435 \u043e\u0431 \u044d\u0442\u043e\u0439 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0435, \u0447\u0442\u043e\u0431\u044b \u0435\u0435 \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0440\u0435\u0448\u0438\u0442\u044c." }, diff --git a/homeassistant/components/powerwall/translations/zh-Hant.json b/homeassistant/components/powerwall/translations/zh-Hant.json index 8883e669a8592b..8cfa8bdb46b5f3 100644 --- a/homeassistant/components/powerwall/translations/zh-Hant.json +++ b/homeassistant/components/powerwall/translations/zh-Hant.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Powerwall \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21", + "cannot_connect": "\u9023\u7dda\u5931\u6557", "unknown": "\u672a\u9810\u671f\u932f\u8aa4", "wrong_version": "\u4e0d\u652f\u63f4\u60a8\u6240\u4f7f\u7528\u7684 Powerwall \u7248\u672c\u3002\u8acb\u8003\u616e\u9032\u884c\u5347\u7d1a\u6216\u56de\u5831\u6b64\u554f\u984c\u3001\u4ee5\u671f\u554f\u984c\u53ef\u4ee5\u7372\u5f97\u89e3\u6c7a\u3002" }, diff --git a/homeassistant/components/profiler/__init__.py b/homeassistant/components/profiler/__init__.py new file mode 100644 index 00000000000000..3c989e13eeb458 --- /dev/null +++ b/homeassistant/components/profiler/__init__.py @@ -0,0 +1,83 @@ +"""The profiler integration.""" +import asyncio +import cProfile +import logging +import time + +from pyprof2calltree import convert +import voluptuous as vol + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, ServiceCall +from homeassistant.helpers.service import async_register_admin_service +from homeassistant.helpers.typing import ConfigType + +from .const import DOMAIN + +SERVICE_START = "start" +CONF_SECONDS = "seconds" + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: + """Set up the profiler component.""" + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): + """Set up Profiler from a config entry.""" + + lock = asyncio.Lock() + + async def _async_run_profile(call: ServiceCall): + async with lock: + await _async_generate_profile(hass, call) + + async_register_admin_service( + hass, + DOMAIN, + SERVICE_START, + _async_run_profile, + schema=vol.Schema( + {vol.Optional(CONF_SECONDS, default=60.0): vol.Coerce(float)} + ), + ) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Unload a config entry.""" + hass.services.async_remove(domain=DOMAIN, service=SERVICE_START) + return True + + +async def _async_generate_profile(hass: HomeAssistant, call: ServiceCall): + start_time = int(time.time() * 1000000) + hass.components.persistent_notification.async_create( + "The profile started. This notification will be updated when it is complete.", + title="Profile Started", + notification_id=f"profiler_{start_time}", + ) + profiler = cProfile.Profile() + profiler.enable() + await asyncio.sleep(float(call.data[CONF_SECONDS])) + profiler.disable() + + cprofile_path = hass.config.path(f"profile.{start_time}.cprof") + callgrind_path = hass.config.path(f"callgrind.out.{start_time}") + await hass.async_add_executor_job( + _write_profile, profiler, cprofile_path, callgrind_path + ) + hass.components.persistent_notification.async_create( + f"Wrote cProfile data to {cprofile_path} and callgrind data to {callgrind_path}", + title="Profile Complete", + notification_id=f"profiler_{start_time}", + ) + + +def _write_profile(profiler, cprofile_path, callgrind_path): + profiler.create_stats() + profiler.dump_stats(cprofile_path) + convert(profiler.getstats(), callgrind_path) diff --git a/homeassistant/components/profiler/config_flow.py b/homeassistant/components/profiler/config_flow.py new file mode 100644 index 00000000000000..73f4f86255b04e --- /dev/null +++ b/homeassistant/components/profiler/config_flow.py @@ -0,0 +1,28 @@ +"""Config flow for Profiler integration.""" +import logging + +import voluptuous as vol + +from homeassistant import config_entries + +from .const import DEFAULT_NAME +from .const import DOMAIN # pylint: disable=unused-import + +_LOGGER = logging.getLogger(__name__) + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Profiler.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_UNKNOWN + + async def async_step_user(self, user_input=None): + """Handle the initial step.""" + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + + if user_input is not None: + return self.async_create_entry(title=DEFAULT_NAME, data={}) + + return self.async_show_form(step_id="user", data_schema=vol.Schema({})) diff --git a/homeassistant/components/profiler/const.py b/homeassistant/components/profiler/const.py new file mode 100644 index 00000000000000..ee80a9175f861d --- /dev/null +++ b/homeassistant/components/profiler/const.py @@ -0,0 +1,4 @@ +"""Consts used by profiler.""" + +DOMAIN = "profiler" +DEFAULT_NAME = "Profiler" diff --git a/homeassistant/components/profiler/manifest.json b/homeassistant/components/profiler/manifest.json new file mode 100644 index 00000000000000..e740a083c77096 --- /dev/null +++ b/homeassistant/components/profiler/manifest.json @@ -0,0 +1,13 @@ +{ + "domain": "profiler", + "name": "Profiler", + "documentation": "https://www.home-assistant.io/integrations/profiler", + "requirements": [ + "pyprof2calltree==1.4.5" + ], + "codeowners": [ + "@bdraco" + ], + "quality_scale": "internal", + "config_flow": true +} \ No newline at end of file diff --git a/homeassistant/components/profiler/services.yaml b/homeassistant/components/profiler/services.yaml new file mode 100644 index 00000000000000..7033e988fc567a --- /dev/null +++ b/homeassistant/components/profiler/services.yaml @@ -0,0 +1,6 @@ +start: + description: Start the Profiler + fields: + seconds: + description: The number of seconds to run the profiler. + example: 60.0 diff --git a/homeassistant/components/profiler/strings.json b/homeassistant/components/profiler/strings.json new file mode 100644 index 00000000000000..80adf97390216d --- /dev/null +++ b/homeassistant/components/profiler/strings.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "description": "Are you sure you want to set up the Profiler?" + } + }, + "abort": { + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" + } + } +} diff --git a/homeassistant/components/profiler/translations/ca.json b/homeassistant/components/profiler/translations/ca.json new file mode 100644 index 00000000000000..1e6c1c03cf2462 --- /dev/null +++ b/homeassistant/components/profiler/translations/ca.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." + }, + "step": { + "user": { + "description": "Est\u00e0s segur que vols configurar Profiler?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/profiler/translations/en.json b/homeassistant/components/profiler/translations/en.json new file mode 100644 index 00000000000000..8a858322445c82 --- /dev/null +++ b/homeassistant/components/profiler/translations/en.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Already configured. Only a single configuration possible." + }, + "step": { + "user": { + "description": "Are you sure you want to set up the Profiler?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/profiler/translations/es.json b/homeassistant/components/profiler/translations/es.json new file mode 100644 index 00000000000000..c9a6215892e9fc --- /dev/null +++ b/homeassistant/components/profiler/translations/es.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ya est\u00e1 configurado. S\u00f3lo es posible una \u00fanica configuraci\u00f3n." + }, + "step": { + "user": { + "description": "\u00bfEst\u00e1s seguro de que quieres configurar el Profiler?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/profiler/translations/et.json b/homeassistant/components/profiler/translations/et.json new file mode 100644 index 00000000000000..553790181c3410 --- /dev/null +++ b/homeassistant/components/profiler/translations/et.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + }, + "step": { + "user": { + "description": "Kas soovid h\u00e4\u00e4lestada Profiler'i sidumist?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/profiler/translations/it.json b/homeassistant/components/profiler/translations/it.json new file mode 100644 index 00000000000000..7829a7c3017eaa --- /dev/null +++ b/homeassistant/components/profiler/translations/it.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." + }, + "step": { + "user": { + "description": "Sei sicuro di voler configurare il Profiler?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/profiler/translations/ru.json b/homeassistant/components/profiler/translations/ru.json new file mode 100644 index 00000000000000..1b91f5e62beb5c --- /dev/null +++ b/homeassistant/components/profiler/translations/ru.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e." + }, + "step": { + "user": { + "description": "\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 Profiler?" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/progettihwsw/translations/de.json b/homeassistant/components/progettihwsw/translations/de.json new file mode 100644 index 00000000000000..f772a8586d00c0 --- /dev/null +++ b/homeassistant/components/progettihwsw/translations/de.json @@ -0,0 +1,32 @@ +{ + "config": { + "step": { + "relay_modes": { + "data": { + "relay_1": "Relais 1", + "relay_10": "Relais 10", + "relay_11": "Relais 11", + "relay_12": "Relais 12", + "relay_13": "Relais 13", + "relay_14": "Relais 14", + "relay_15": "Relais 15", + "relay_16": "Relais 16", + "relay_2": "Relais 2", + "relay_3": "Relais 3", + "relay_4": "Relais 4", + "relay_5": "Relais 5", + "relay_6": "Relais 6", + "relay_7": "Relais 7", + "relay_8": "Relais 8", + "relay_9": "Relais 9" + } + }, + "user": { + "data": { + "host": "Host", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/progettihwsw/translations/el.json b/homeassistant/components/progettihwsw/translations/el.json new file mode 100644 index 00000000000000..76109815009215 --- /dev/null +++ b/homeassistant/components/progettihwsw/translations/el.json @@ -0,0 +1,33 @@ +{ + "config": { + "step": { + "relay_modes": { + "data": { + "relay_10": "\u03a1\u03b5\u03bb\u03ad 10", + "relay_11": "\u03a1\u03b5\u03bb\u03ad 11", + "relay_12": "\u03a1\u03b5\u03bb\u03ad 12", + "relay_13": "\u03a1\u03b5\u03bb\u03ad 13", + "relay_14": "\u03a1\u03b5\u03bb\u03ad 14", + "relay_15": "\u03a1\u03b5\u03bb\u03ad 15", + "relay_16": "\u03a1\u03b5\u03bb\u03ad 16", + "relay_2": "\u03a1\u03b5\u03bb\u03ad 2", + "relay_3": "\u03a1\u03b5\u03bb\u03ad 3", + "relay_4": "\u03a1\u03b5\u03bb\u03ad 4", + "relay_5": "\u03a1\u03b5\u03bb\u03ad 5", + "relay_6": "\u03a1\u03b5\u03bb\u03ad 6", + "relay_7": "\u03a1\u03b5\u03bb\u03ad 7", + "relay_8": "\u03a1\u03b5\u03bb\u03ad 8", + "relay_9": "\u03a1\u03b5\u03bb\u03ad 9" + }, + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c1\u03b5\u03bb\u03ad" + }, + "user": { + "data": { + "port": "\u0398\u03cd\u03c1\u03b1" + }, + "title": "\u03a1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7 \u03c0\u03af\u03bd\u03b1\u03ba\u03b1" + } + } + }, + "title": "\u0391\u03c5\u03c4\u03bf\u03bc\u03b1\u03c4\u03b9\u03c3\u03bc\u03cc\u03c2 ProgettiHWSW" +} \ No newline at end of file diff --git a/homeassistant/components/progettihwsw/translations/hu.json b/homeassistant/components/progettihwsw/translations/hu.json new file mode 100644 index 00000000000000..3b2d79a34a77e2 --- /dev/null +++ b/homeassistant/components/progettihwsw/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/progettihwsw/translations/ko.json b/homeassistant/components/progettihwsw/translations/ko.json new file mode 100644 index 00000000000000..b8b78de069c375 --- /dev/null +++ b/homeassistant/components/progettihwsw/translations/ko.json @@ -0,0 +1,43 @@ +{ + "config": { + "abort": { + "already_configured": "\uc7a5\uce58\ub294 \uc774\ubbf8 \uad6c\uc131\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4." + }, + "error": { + "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec", + "wrong_info_relay_modes": "\ub9b4\ub808\uc774 \ubaa8\ub4dc \uc120\ud0dd\uc740 Monostable \ub610\ub294 Bistable \uc774\uc5b4\uc57c \ud569\ub2c8\ub2e4." + }, + "step": { + "relay_modes": { + "data": { + "relay_1": "\ub9b4\ub808\uc774 1", + "relay_10": "\ub9b4\ub808\uc774 10", + "relay_11": "\ub9b4\ub808\uc774 11", + "relay_12": "\ub9b4\ub808\uc774 12", + "relay_13": "\ub9b4\ub808\uc774 13", + "relay_14": "\ub9b4\ub808\uc774 14", + "relay_15": "\ub9b4\ub808\uc774 15", + "relay_16": "\ub9b4\ub808\uc774 16", + "relay_2": "\ub9b4\ub808\uc774 2", + "relay_3": "\ub9b4\ub808\uc774 3", + "relay_4": "\ub9b4\ub808\uc774 4", + "relay_5": "\ub9b4\ub808\uc774 5", + "relay_6": "\ub9b4\ub808\uc774 6", + "relay_7": "\ub9b4\ub808\uc774 7", + "relay_8": "\ub9b4\ub808\uc774 8", + "relay_9": "\ub9b4\ub808\uc774 9" + }, + "title": "\ub9b4\ub808\uc774 \uc124\uc815" + }, + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8", + "port": "\ud3ec\ud2b8" + }, + "title": "\ubcf4\ub4dc \uc124\uc815" + } + } + }, + "title": "ProgettiHWSW \uc790\ub3d9\ud654" +} \ No newline at end of file diff --git a/homeassistant/components/progettihwsw/translations/lb.json b/homeassistant/components/progettihwsw/translations/lb.json new file mode 100644 index 00000000000000..2e5491da644070 --- /dev/null +++ b/homeassistant/components/progettihwsw/translations/lb.json @@ -0,0 +1,41 @@ +{ + "config": { + "abort": { + "already_configured": "Apparat ass scho konfigur\u00e9iert" + }, + "error": { + "cannot_connect": "Feeler beim verbannen" + }, + "step": { + "relay_modes": { + "data": { + "relay_1": "Relais 1", + "relay_10": "Relais 10", + "relay_11": "Relais 11", + "relay_12": "Relais 12", + "relay_13": "Relais 13", + "relay_14": "Relais 14", + "relay_15": "Relais 15", + "relay_16": "Relais 16", + "relay_2": "Relais 2", + "relay_3": "Relais 3", + "relay_4": "Relais 4", + "relay_5": "Relais 5", + "relay_6": "Relais 6", + "relay_7": "Relais 7", + "relay_8": "Relais 8", + "relay_9": "Relais 9" + }, + "title": "Relais ariichten" + }, + "user": { + "data": { + "host": "Host", + "port": "Port" + }, + "title": "Board ariichten" + } + } + }, + "title": "ProgettiHWSW Automatisme" +} \ No newline at end of file diff --git a/homeassistant/components/progettihwsw/translations/nl.json b/homeassistant/components/progettihwsw/translations/nl.json new file mode 100644 index 00000000000000..2b30a4f1caac53 --- /dev/null +++ b/homeassistant/components/progettihwsw/translations/nl.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + }, + "step": { + "relay_modes": { + "title": "Stel relais in" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/progettihwsw/translations/pl.json b/homeassistant/components/progettihwsw/translations/pl.json index ee25c598ecd2de..11a88b27477a0e 100644 --- a/homeassistant/components/progettihwsw/translations/pl.json +++ b/homeassistant/components/progettihwsw/translations/pl.json @@ -1,15 +1,43 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "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", + "wrong_info_relay_modes": "Tryb przeka\u017anika musi by\u0107 monostabilny lub bistabilny." }, "step": { + "relay_modes": { + "data": { + "relay_1": "Przeka\u017anik 1", + "relay_10": "Przeka\u017anik 10", + "relay_11": "Przeka\u017anik 11", + "relay_12": "Przeka\u017anik 12", + "relay_13": "Przeka\u017anik 13", + "relay_14": "Przeka\u017anik 14", + "relay_15": "Przeka\u017anik 15", + "relay_16": "Przeka\u017anik 16", + "relay_2": "Przeka\u017anik 2", + "relay_3": "Przeka\u017anik 3", + "relay_4": "Przeka\u017anik 4", + "relay_5": "Przeka\u017anik 5", + "relay_6": "Przeka\u017anik 6", + "relay_7": "Przeka\u017anik 7", + "relay_8": "Przeka\u017anik 8", + "relay_9": "Przeka\u017anik 9" + }, + "title": "Konfiguracja przeka\u017anik\u00f3w" + }, "user": { "data": { "host": "Nazwa hosta lub adres IP", "port": "Port" - } + }, + "title": "Konfiguracja uk\u0142adu" } } - } + }, + "title": "Automatyzacja ProgettiHWSW" } \ No newline at end of file diff --git a/homeassistant/components/prometheus/__init__.py b/homeassistant/components/prometheus/__init__.py index 87a8a2c41f3b13..bd9a6e35276df6 100644 --- a/homeassistant/components/prometheus/__init__.py +++ b/homeassistant/components/prometheus/__init__.py @@ -19,7 +19,9 @@ ATTR_MODE, ) from homeassistant.const import ( + ATTR_BATTERY_LEVEL, ATTR_DEVICE_CLASS, + ATTR_FRIENDLY_NAME, ATTR_TEMPERATURE, ATTR_UNIT_OF_MEASUREMENT, CONTENT_TYPE_TEXT_PLAIN, @@ -234,7 +236,7 @@ def _labels(state): return { "entity": state.entity_id, "domain": state.domain, - "friendly_name": state.attributes.get("friendly_name"), + "friendly_name": state.attributes.get(ATTR_FRIENDLY_NAME), } def _battery(self, state): @@ -245,7 +247,7 @@ def _battery(self, state): "Battery level as a percentage of its capacity", ) try: - value = float(state.attributes["battery_level"]) + value = float(state.attributes[ATTR_BATTERY_LEVEL]) metric.labels(**self._labels(state)).set(value) except ValueError: pass diff --git a/homeassistant/components/proximity/__init__.py b/homeassistant/components/proximity/__init__.py index 7beaaaf00e19b3..2d0d14a69c1887 100644 --- a/homeassistant/components/proximity/__init__.py +++ b/homeassistant/components/proximity/__init__.py @@ -4,6 +4,8 @@ import voluptuous as vol from homeassistant.const import ( + ATTR_LATITUDE, + ATTR_LONGITUDE, CONF_DEVICES, CONF_UNIT_OF_MEASUREMENT, CONF_ZONE, @@ -149,8 +151,8 @@ def check_proximity_state_change(self, entity, old_state, new_state): devices_in_zone = "" zone_state = self.hass.states.get(self.proximity_zone) - proximity_latitude = zone_state.attributes.get("latitude") - proximity_longitude = zone_state.attributes.get("longitude") + proximity_latitude = zone_state.attributes.get(ATTR_LATITUDE) + proximity_longitude = zone_state.attributes.get(ATTR_LONGITUDE) # Check for devices in the monitored zone. for device in self.proximity_devices: @@ -206,8 +208,8 @@ def check_proximity_state_change(self, entity, old_state, new_state): dist_to_zone = distance( proximity_latitude, proximity_longitude, - device_state.attributes["latitude"], - device_state.attributes["longitude"], + device_state.attributes[ATTR_LATITUDE], + device_state.attributes[ATTR_LONGITUDE], ) # Add the device and distance to a dictionary. @@ -250,14 +252,14 @@ def check_proximity_state_change(self, entity, old_state, new_state): old_distance = distance( proximity_latitude, proximity_longitude, - old_state.attributes["latitude"], - old_state.attributes["longitude"], + old_state.attributes[ATTR_LATITUDE], + old_state.attributes[ATTR_LONGITUDE], ) new_distance = distance( proximity_latitude, proximity_longitude, - new_state.attributes["latitude"], - new_state.attributes["longitude"], + new_state.attributes[ATTR_LATITUDE], + new_state.attributes[ATTR_LONGITUDE], ) distance_travelled = round(new_distance - old_distance, 1) diff --git a/homeassistant/components/proxy/camera.py b/homeassistant/components/proxy/camera.py index 754f09fa19999c..c3f7151431a394 100644 --- a/homeassistant/components/proxy/camera.py +++ b/homeassistant/components/proxy/camera.py @@ -77,6 +77,8 @@ def _precheck_image(image, opts): if imgfmt not in ("PNG", "JPEG"): _LOGGER.warning("Image is of unsupported type: %s", imgfmt) raise ValueError() + if not img.mode == "RGB": + img = img.convert("RGB") return img diff --git a/homeassistant/components/ps4/strings.json b/homeassistant/components/ps4/strings.json index c3a864565cf1e4..c538852db56b59 100644 --- a/homeassistant/components/ps4/strings.json +++ b/homeassistant/components/ps4/strings.json @@ -18,9 +18,9 @@ "description": "Enter your PlayStation 4 information. For 'PIN', navigate to 'Settings' on your PlayStation 4 console. Then navigate to 'Mobile App Connection Settings' and select 'Add Device'. Enter the PIN that is displayed. Refer to the [documentation](https://www.home-assistant.io/components/ps4/) for additional info.", "data": { "region": "Region", - "name": "Name", + "name": "[%key:common::config_flow::data::name%]", "code": "PIN", - "ip_address": "IP Address" + "ip_address": "[%key:common::config_flow::data::ip%]" } } }, diff --git a/homeassistant/components/pvpc_hourly_pricing/sensor.py b/homeassistant/components/pvpc_hourly_pricing/sensor.py index 25b4531cee0ee6..a9b53c970bd49f 100644 --- a/homeassistant/components/pvpc_hourly_pricing/sensor.py +++ b/homeassistant/components/pvpc_hourly_pricing/sensor.py @@ -6,7 +6,7 @@ from aiopvpc import PVPCData from homeassistant import config_entries -from homeassistant.const import CONF_NAME, ENERGY_KILO_WATT_HOUR +from homeassistant.const import CONF_NAME, CURRENCY_EURO, ENERGY_KILO_WATT_HOUR from homeassistant.core import HomeAssistant, callback from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.event import async_call_later, async_track_time_change @@ -19,7 +19,7 @@ ATTR_PRICE = "price" ICON = "mdi:currency-eur" -UNIT = f"€/{ENERGY_KILO_WATT_HOUR}" +UNIT = f"{CURRENCY_EURO}/{ENERGY_KILO_WATT_HOUR}" _DEFAULT_TIMEOUT = 10 diff --git a/homeassistant/components/pvpc_hourly_pricing/strings.json b/homeassistant/components/pvpc_hourly_pricing/strings.json index fbda22c51495d0..a1536d2186f301 100644 --- a/homeassistant/components/pvpc_hourly_pricing/strings.json +++ b/homeassistant/components/pvpc_hourly_pricing/strings.json @@ -11,7 +11,7 @@ } }, "abort": { - "already_configured": "Integration is already configured with an existing sensor with that tariff" + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]" } } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/ca.json b/homeassistant/components/pvpc_hourly_pricing/translations/ca.json index 8eeab499620e48..bc5f3a59428440 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/ca.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/ca.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Integraci\u00f3 ja configurada amb un sensor amb aquesta tarifa" + "already_configured": "El servei ja est\u00e0 configurat" }, "step": { "user": { diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/en.json b/homeassistant/components/pvpc_hourly_pricing/translations/en.json index 7716dab8ad7cd1..02acb46eeb62b9 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/en.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/en.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Integration is already configured with an existing sensor with that tariff" + "already_configured": "Service is already configured" }, "step": { "user": { diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/fr.json b/homeassistant/components/pvpc_hourly_pricing/translations/fr.json index 4048e3577517a2..5386529e43a185 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/fr.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/fr.json @@ -9,6 +9,7 @@ "name": "Nom du capteur", "tariff": "Tarif souscrit (1, 2, ou 3 p\u00e9riodes)" }, + "description": "Ce capteur utilise l'API officielle pour obtenir la [tarification horaire de l'\u00e9lectricit\u00e9 (PVPC)] (https://www.esios.ree.es/es/pvpc) en Espagne. \n Pour une explication plus pr\u00e9cise, visitez la [documentation d'int\u00e9gration] (https://www.home-assistant.io/integrations/pvpc_hourly_pricing/). \n\n S\u00e9lectionnez le tarif contract\u00e9 en fonction du nombre de p\u00e9riodes de facturation par jour: \n - 1 p\u00e9riode: normale \n - 2 p\u00e9riodes: discrimination (tarif \u00e0 la nuit) \n - 3 p\u00e9riodes: voiture \u00e9lectrique (tarif \u00e0 la nuit sur 3 p\u00e9riodes)", "title": "S\u00e9lection tarifaire" } } diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/it.json b/homeassistant/components/pvpc_hourly_pricing/translations/it.json index 84f2fddcb8331a..e36fc74688304c 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/it.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/it.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "L'integrazione \u00e8 gi\u00e0 configurata con un sensore esistente con quella tariffa" + "already_configured": "Il servizio \u00e8 gi\u00e0 configurato" }, "step": { "user": { diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/no.json b/homeassistant/components/pvpc_hourly_pricing/translations/no.json index d27b78615cf4d2..7eec443fcaa416 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/no.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/no.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Integrasjon er allerede konfigurert med en eksisterende sensor med den tariffen" + "already_configured": "Tjenesten er allerede konfigurert" }, "step": { "user": { diff --git a/homeassistant/components/pvpc_hourly_pricing/translations/ru.json b/homeassistant/components/pvpc_hourly_pricing/translations/ru.json index 4a2f4440a2a0df..e48e5222f3c6fe 100644 --- a/homeassistant/components/pvpc_hourly_pricing/translations/ru.json +++ b/homeassistant/components/pvpc_hourly_pricing/translations/ru.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "already_configured": "\u042d\u0442\u0430 \u0441\u043b\u0443\u0436\u0431\u0430 \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant." }, "step": { "user": { diff --git a/homeassistant/components/rachio/strings.json b/homeassistant/components/rachio/strings.json index faea255693e9ff..c6f1f4ca2647b8 100644 --- a/homeassistant/components/rachio/strings.json +++ b/homeassistant/components/rachio/strings.json @@ -10,12 +10,12 @@ } }, "error": { - "cannot_connect": "Failed to connect, please try again", - "invalid_auth": "Invalid authentication", - "unknown": "Unexpected 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": "Device is already configured" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } }, "options": { diff --git a/homeassistant/components/rachio/switch.py b/homeassistant/components/rachio/switch.py index 32ec6267941ffb..b5dc71b585c8d0 100644 --- a/homeassistant/components/rachio/switch.py +++ b/homeassistant/components/rachio/switch.py @@ -56,6 +56,7 @@ SUBTYPE_SLEEP_MODE_OFF, SUBTYPE_SLEEP_MODE_ON, SUBTYPE_ZONE_COMPLETED, + SUBTYPE_ZONE_PAUSED, SUBTYPE_ZONE_STARTED, SUBTYPE_ZONE_STOPPED, ) @@ -392,7 +393,11 @@ def _async_handle_update(self, *args, **kwargs) -> None: if args[0][KEY_SUBTYPE] == SUBTYPE_ZONE_STARTED: self._state = True - elif args[0][KEY_SUBTYPE] in [SUBTYPE_ZONE_STOPPED, SUBTYPE_ZONE_COMPLETED]: + elif args[0][KEY_SUBTYPE] in [ + SUBTYPE_ZONE_STOPPED, + SUBTYPE_ZONE_COMPLETED, + SUBTYPE_ZONE_PAUSED, + ]: self._state = False self.async_write_ha_state() diff --git a/homeassistant/components/rachio/translations/ca.json b/homeassistant/components/rachio/translations/ca.json index 8da88ec50e92fc..c451b4df83eb8a 100644 --- a/homeassistant/components/rachio/translations/ca.json +++ b/homeassistant/components/rachio/translations/ca.json @@ -4,7 +4,7 @@ "already_configured": "El dispositiu ja est\u00e0 configurat" }, "error": { - "cannot_connect": "No s'ha pogut connectar, torna-ho a provar", + "cannot_connect": "Ha fallat la connexi\u00f3", "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "unknown": "Error inesperat" }, diff --git a/homeassistant/components/rachio/translations/en.json b/homeassistant/components/rachio/translations/en.json index 21f7524008a28d..ca466938a3c23e 100644 --- a/homeassistant/components/rachio/translations/en.json +++ b/homeassistant/components/rachio/translations/en.json @@ -4,7 +4,7 @@ "already_configured": "Device is already configured" }, "error": { - "cannot_connect": "Failed to connect, please try again", + "cannot_connect": "Failed to connect", "invalid_auth": "Invalid authentication", "unknown": "Unexpected error" }, diff --git a/homeassistant/components/rachio/translations/it.json b/homeassistant/components/rachio/translations/it.json index f8013eb3551aea..49d64be152339d 100644 --- a/homeassistant/components/rachio/translations/it.json +++ b/homeassistant/components/rachio/translations/it.json @@ -4,7 +4,7 @@ "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" }, "error": { - "cannot_connect": "Impossibile connettersi, si prega di riprovare", + "cannot_connect": "Impossibile connettersi", "invalid_auth": "Autenticazione non valida", "unknown": "Errore imprevisto" }, diff --git a/homeassistant/components/rachio/translations/no.json b/homeassistant/components/rachio/translations/no.json index e5f9c0abbd3f7b..7d0c4feae6c836 100644 --- a/homeassistant/components/rachio/translations/no.json +++ b/homeassistant/components/rachio/translations/no.json @@ -4,7 +4,7 @@ "already_configured": "Enheten er allerede konfigurert" }, "error": { - "cannot_connect": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen", + "cannot_connect": "Tilkobling mislyktes.", "invalid_auth": "Ugyldig godkjenning", "unknown": "Uventet feil" }, diff --git a/homeassistant/components/rachio/translations/pl.json b/homeassistant/components/rachio/translations/pl.json index e077fea03a4412..55e8d18fe1c1c9 100644 --- a/homeassistant/components/rachio/translations/pl.json +++ b/homeassistant/components/rachio/translations/pl.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "invalid_auth": "Niepoprawne uwierzytelnienie.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "user": { diff --git a/homeassistant/components/rachio/translations/ru.json b/homeassistant/components/rachio/translations/ru.json index f6fe53b5f8dc5a..c85a07e58bf3cf 100644 --- a/homeassistant/components/rachio/translations/ru.json +++ b/homeassistant/components/rachio/translations/ru.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", + "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "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/rachio/translations/zh-Hant.json b/homeassistant/components/rachio/translations/zh-Hant.json index 7f05aa218e715f..c79264706c6919 100644 --- a/homeassistant/components/rachio/translations/zh-Hant.json +++ b/homeassistant/components/rachio/translations/zh-Hant.json @@ -4,7 +4,7 @@ "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21", + "cannot_connect": "\u9023\u7dda\u5931\u6557", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, diff --git a/homeassistant/components/rachio/webhooks.py b/homeassistant/components/rachio/webhooks.py index 5daf78527255e9..c175117efcb344 100644 --- a/homeassistant/components/rachio/webhooks.py +++ b/homeassistant/components/rachio/webhooks.py @@ -58,6 +58,7 @@ SUBTYPE_ZONE_COMPLETED = "ZONE_COMPLETED" SUBTYPE_ZONE_CYCLING = "ZONE_CYCLING" SUBTYPE_ZONE_CYCLING_COMPLETED = "ZONE_CYCLING_COMPLETED" +SUBTYPE_ZONE_PAUSED = "ZONE_PAUSED" # Webhook callbacks LISTEN_EVENT_TYPES = [ diff --git a/homeassistant/components/rainmachine/config_flow.py b/homeassistant/components/rainmachine/config_flow.py index d0513ac89fb232..b6e429f2e83c7b 100644 --- a/homeassistant/components/rainmachine/config_flow.py +++ b/homeassistant/components/rainmachine/config_flow.py @@ -69,7 +69,7 @@ async def async_step_user(self, user_input=None): ssl=user_input.get(CONF_SSL, True), ) except RainMachineError: - return await self._show_form({CONF_PASSWORD: "invalid_credentials"}) + return await self._show_form({CONF_PASSWORD: "invalid_auth"}) # Unfortunately, RainMachine doesn't provide a way to refresh the # access token without using the IP address and password, so we have to diff --git a/homeassistant/components/rainmachine/sensor.py b/homeassistant/components/rainmachine/sensor.py index 6f87c34d60765b..8534748978d68c 100644 --- a/homeassistant/components/rainmachine/sensor.py +++ b/homeassistant/components/rainmachine/sensor.py @@ -1,7 +1,7 @@ """This platform provides support for sensor data from RainMachine.""" import logging -from homeassistant.const import TEMP_CELSIUS +from homeassistant.const import TEMP_CELSIUS, VOLUME_CUBIC_METERS from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -26,7 +26,7 @@ TYPE_FLOW_SENSOR_CLICK_M3: ( "Flow Sensor Clicks", "mdi:water-pump", - "clicks/m^3", + f"clicks/{VOLUME_CUBIC_METERS}", None, False, DATA_PROVISION_SETTINGS, diff --git a/homeassistant/components/rainmachine/strings.json b/homeassistant/components/rainmachine/strings.json index 555230d1f0f9d9..d0a6adf468736c 100644 --- a/homeassistant/components/rainmachine/strings.json +++ b/homeassistant/components/rainmachine/strings.json @@ -11,11 +11,10 @@ } }, "error": { - "identifier_exists": "Account already registered", - "invalid_credentials": "Invalid credentials" + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" }, "abort": { - "already_configured": "This RainMachine controller is already configured." + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/rainmachine/translations/ca.json b/homeassistant/components/rainmachine/translations/ca.json index 37d842355c0fa4..cc77b66aef05eb 100644 --- a/homeassistant/components/rainmachine/translations/ca.json +++ b/homeassistant/components/rainmachine/translations/ca.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "Aquest controlador RainMachine ja est\u00e0 configurat." + "already_configured": "El dispositiu ja est\u00e0 configurat" }, "error": { - "identifier_exists": "Aquest compte ja est\u00e0 registrat", + "identifier_exists": "El compte ja ha estat configurat", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "invalid_credentials": "Credencials inv\u00e0lides" }, "step": { diff --git a/homeassistant/components/rainmachine/translations/en.json b/homeassistant/components/rainmachine/translations/en.json index aabe3d352935b3..d734cbd5d388e1 100644 --- a/homeassistant/components/rainmachine/translations/en.json +++ b/homeassistant/components/rainmachine/translations/en.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "This RainMachine controller is already configured." + "already_configured": "Device is already configured" }, "error": { - "identifier_exists": "Account already registered", + "identifier_exists": "Account is already configured", + "invalid_auth": "Invalid authentication", "invalid_credentials": "Invalid credentials" }, "step": { diff --git a/homeassistant/components/rainmachine/translations/es.json b/homeassistant/components/rainmachine/translations/es.json index 0767c509bf9bdc..d788b68dcbb41b 100644 --- a/homeassistant/components/rainmachine/translations/es.json +++ b/homeassistant/components/rainmachine/translations/es.json @@ -5,6 +5,7 @@ }, "error": { "identifier_exists": "Cuenta ya registrada", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", "invalid_credentials": "Credenciales no v\u00e1lidas" }, "step": { diff --git a/homeassistant/components/rainmachine/translations/et.json b/homeassistant/components/rainmachine/translations/et.json new file mode 100644 index 00000000000000..2227b7442a79c6 --- /dev/null +++ b/homeassistant/components/rainmachine/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Tuvastamise viga" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rainmachine/translations/it.json b/homeassistant/components/rainmachine/translations/it.json index 5ec522c9c24aed..7850ea19c33e61 100644 --- a/homeassistant/components/rainmachine/translations/it.json +++ b/homeassistant/components/rainmachine/translations/it.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "Questo controller RainMachine \u00e8 gi\u00e0 configurato." + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" }, "error": { - "identifier_exists": "Account gi\u00e0 registrato", + "identifier_exists": "L'account \u00e8 gi\u00e0 configurato", + "invalid_auth": "Autenticazione non valida", "invalid_credentials": "Credenziali non valide" }, "step": { diff --git a/homeassistant/components/rainmachine/translations/ru.json b/homeassistant/components/rainmachine/translations/ru.json index cd5e3d85d50c4d..3bb1a95f647457 100644 --- a/homeassistant/components/rainmachine/translations/ru.json +++ b/homeassistant/components/rainmachine/translations/ru.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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": { - "identifier_exists": "\u0423\u0447\u0451\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.", + "identifier_exists": "\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.", + "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "invalid_credentials": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0435 \u0443\u0447\u0451\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435." }, "step": { diff --git a/homeassistant/components/recorder/__init__.py b/homeassistant/components/recorder/__init__.py index 9ce950fedfe265..8e6a251f660d47 100644 --- a/homeassistant/components/recorder/__init__.py +++ b/homeassistant/components/recorder/__init__.py @@ -196,6 +196,10 @@ async def async_handle_purge_service(service): PurgeTask = namedtuple("PurgeTask", ["keep_days", "repack"]) +class WaitTask: + """An object to insert into the recorder queue to tell it set the _queue_watch event.""" + + class Recorder(threading.Thread): """A threaded recorder class.""" @@ -226,6 +230,7 @@ def __init__( self.db_retry_wait = db_retry_wait self.db_integrity_check = db_integrity_check self.async_db_ready = asyncio.Future() + self._queue_watch = threading.Event() self.engine: Any = None self.run_info: Any = None @@ -234,7 +239,8 @@ def __init__( self._timechanges_seen = 0 self._keepalive_count = 0 - self._old_state_ids = {} + self._old_states = {} + self._pending_expunge = [] self.event_session = None self.get_session = None self._completed_database_setup = False @@ -353,6 +359,9 @@ def async_purge(now): if not purge.purge_old_data(self, event.keep_days, event.repack): self.queue.put(PurgeTask(event.keep_days, event.repack)) continue + if isinstance(event, WaitTask): + self._queue_watch.set() + continue if event.event_type == EVENT_TIME_CHANGED: self._keepalive_count += 1 if self._keepalive_count >= KEEPALIVE_TIME: @@ -373,11 +382,11 @@ def async_purge(now): continue try: - dbevent = Events.from_event(event) if event.event_type == EVENT_STATE_CHANGED: - dbevent.event_data = "{}" + dbevent = Events.from_event(event, event_data="{}") + else: + dbevent = Events.from_event(event) self.event_session.add(dbevent) - self.event_session.flush() except (TypeError, ValueError): _LOGGER.warning("Event is not JSON serializable: %s", event) except Exception as err: # pylint: disable=broad-except @@ -388,16 +397,15 @@ def async_purge(now): try: dbstate = States.from_event(event) has_new_state = event.data.get("new_state") - dbstate.old_state_id = self._old_state_ids.get(dbstate.entity_id) + if dbstate.entity_id in self._old_states: + dbstate.old_state = self._old_states.pop(dbstate.entity_id) if not has_new_state: dbstate.state = None - dbstate.event_id = dbevent.event_id + dbstate.event = dbevent self.event_session.add(dbstate) - self.event_session.flush() if has_new_state: - self._old_state_ids[dbstate.entity_id] = dbstate.state_id - elif dbstate.entity_id in self._old_state_ids: - del self._old_state_ids[dbstate.entity_id] + self._old_states[dbstate.entity_id] = dbstate + self._pending_expunge.append(dbstate) except (TypeError, ValueError): _LOGGER.warning( "State is not JSON serializable: %s", @@ -483,6 +491,12 @@ def _reopen_event_session(self): def _commit_event_session(self): try: + 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 + self.event_session.expunge(dbstate) + self._pending_expunge = [] self.event_session.commit() except Exception as err: _LOGGER.error("Error executing query: %s", err) @@ -506,8 +520,9 @@ def block_till_done(self): after calling this to ensure the data is in the database. """ - while not self.queue.empty(): - time.sleep(0.025) + self._queue_watch.clear() + self.queue.put(WaitTask()) + self._queue_watch.wait() def _setup_connection(self): """Ensure database is ready to fly.""" diff --git a/homeassistant/components/recorder/models.py b/homeassistant/components/recorder/models.py index b5384cf84cbfaa..4756ac13ce3001 100644 --- a/homeassistant/components/recorder/models.py +++ b/homeassistant/components/recorder/models.py @@ -14,6 +14,7 @@ distinct, ) from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import relationship from sqlalchemy.orm.session import Session from homeassistant.core import Context, Event, EventOrigin, State, split_entity_id @@ -59,12 +60,12 @@ class Events(Base): # type: ignore ) @staticmethod - def from_event(event): + 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=json.dumps(event.data, cls=JSONEncoder), - origin=str(event.origin), + event_data=event_data or json.dumps(event.data, cls=JSONEncoder), + origin=str(event.origin.value), time_fired=event.time_fired, context_id=event.context.id, context_user_id=event.context.user_id, @@ -105,7 +106,9 @@ class States(Base): # type: ignore last_changed = Column(DateTime(timezone=True), default=dt_util.utcnow) last_updated = Column(DateTime(timezone=True), default=dt_util.utcnow, index=True) created = Column(DateTime(timezone=True), default=dt_util.utcnow) - old_state_id = Column(Integer) + old_state_id = Column(Integer, ForeignKey("states.state_id")) + event = relationship("Events", uselist=False) + old_state = relationship("States", remote_side=[state_id]) __table_args__ = ( # Used for fetching the state of entities at a specific time @@ -218,7 +221,8 @@ def process_timestamp_to_utc_isoformat(ts): """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 dt_util.as_utc(ts).isoformat() + return ts.astimezone(dt_util.UTC).isoformat() diff --git a/homeassistant/components/rejseplanen/manifest.json b/homeassistant/components/rejseplanen/manifest.json index 82ba4812592777..6f91e2a9abe187 100644 --- a/homeassistant/components/rejseplanen/manifest.json +++ b/homeassistant/components/rejseplanen/manifest.json @@ -3,5 +3,5 @@ "name": "Rejseplanen", "documentation": "https://www.home-assistant.io/integrations/rejseplanen", "requirements": ["rjpl==0.3.6"], - "codeowners": [] + "codeowners": ["@DarkFox"] } diff --git a/homeassistant/components/remote/group.py b/homeassistant/components/remote/group.py new file mode 100644 index 00000000000000..1636054663dc69 --- /dev/null +++ b/homeassistant/components/remote/group.py @@ -0,0 +1,15 @@ +"""Describe group states.""" + + +from homeassistant.components.group import GroupIntegrationRegistry +from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.core import callback +from homeassistant.helpers.typing import HomeAssistantType + + +@callback +def async_describe_on_off_states( + hass: HomeAssistantType, registry: GroupIntegrationRegistry +) -> None: + """Describe group on off states.""" + registry.on_off_states({STATE_ON}, STATE_OFF) diff --git a/homeassistant/components/remote/translations/de.json b/homeassistant/components/remote/translations/de.json index d1ec188e2b89c0..ffd542f27d935d 100644 --- a/homeassistant/components/remote/translations/de.json +++ b/homeassistant/components/remote/translations/de.json @@ -1,4 +1,19 @@ { + "device_automation": { + "action_type": { + "toggle": "{entity_name} umschalten", + "turn_off": "Schalte {entity_name} aus", + "turn_on": "Schalte {entity_name} an" + }, + "condition_type": { + "is_off": "{entity_name} ist ausgeschaltet", + "is_on": "{entity_name} ist eingeschaltet" + }, + "trigger_type": { + "turned_off": "{entity_name} ausgeschaltet", + "turned_on": "{entity_name} eingeschaltet" + } + }, "state": { "_": { "off": "Aus", diff --git a/homeassistant/components/remote/translations/el.json b/homeassistant/components/remote/translations/el.json index 79860300b96b37..0ef87433fce380 100644 --- a/homeassistant/components/remote/translations/el.json +++ b/homeassistant/components/remote/translations/el.json @@ -1,4 +1,19 @@ { + "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}" + }, + "condition_type": { + "is_off": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03b1\u03c0\u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf", + "is_on": "{entity_name} \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03b9\u03b7\u03bc\u03ad\u03bd\u03bf" + }, + "trigger_type": { + "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" + } + }, "state": { "_": { "off": "\u039a\u03bb\u03b5\u03b9\u03c3\u03c4\u03cc", diff --git a/homeassistant/components/remote/translations/es.json b/homeassistant/components/remote/translations/es.json index bf8b6d3a3eccdc..e2452b80e89cd1 100644 --- a/homeassistant/components/remote/translations/es.json +++ b/homeassistant/components/remote/translations/es.json @@ -1,4 +1,19 @@ { + "device_automation": { + "action_type": { + "toggle": "Alternar {entity_name}", + "turn_off": "Apagar {entity_name}", + "turn_on": "Encender {entity_name}" + }, + "condition_type": { + "is_off": "{entity_name} est\u00e1 apagado", + "is_on": "{entity_name} est\u00e1 activado" + }, + "trigger_type": { + "turned_off": "{entity_name} desactivado", + "turned_on": "{entity_name} activado" + } + }, "state": { "_": { "off": "Apagado", diff --git a/homeassistant/components/remote/translations/et.json b/homeassistant/components/remote/translations/et.json index 6bcfbf7f4cfef4..458704b9999934 100644 --- a/homeassistant/components/remote/translations/et.json +++ b/homeassistant/components/remote/translations/et.json @@ -1,4 +1,19 @@ { + "device_automation": { + "action_type": { + "toggle": "Muuda {entity_name} olekut", + "turn_off": "L\u00fclita {entity_name} v\u00e4lja", + "turn_on": "L\u00fclita {entity_name} sisse" + }, + "condition_type": { + "is_off": "{entity_name} on v\u00e4lja l\u00fclitatud", + "is_on": "{entity_name} on sisse l\u00fclitatud" + }, + "trigger_type": { + "turned_off": "{entity_name} l\u00fclitus v\u00e4lja", + "turned_on": "{entity_name} l\u00fclitus sisse" + } + }, "state": { "_": { "off": "V\u00e4ljas", diff --git a/homeassistant/components/remote/translations/ko.json b/homeassistant/components/remote/translations/ko.json index b866fd7fee56d1..bd055e21f5bb73 100644 --- a/homeassistant/components/remote/translations/ko.json +++ b/homeassistant/components/remote/translations/ko.json @@ -1,4 +1,19 @@ { + "device_automation": { + "action_type": { + "toggle": "{entity_name} \ud1a0\uae00", + "turn_off": "{entity_name} \ub044\uae30", + "turn_on": "{entity_name} \ucf1c\uae30" + }, + "condition_type": { + "is_off": "{entity_name} \uc774 \uaebc\uc838 \uc788\uc73c\uba74", + "is_on": "{entity_name} \uc774 \ucf1c\uc838 \uc788\uc73c\uba74" + }, + "trigger_type": { + "turned_off": "{entity_name} \uaebc\uc9d0", + "turned_on": "{entity_name} \ucf1c\uc9d0" + } + }, "state": { "_": { "off": "\uaebc\uc9d0", diff --git a/homeassistant/components/remote/translations/lb.json b/homeassistant/components/remote/translations/lb.json index b81e82470fc7fb..f9f81a85a2992b 100644 --- a/homeassistant/components/remote/translations/lb.json +++ b/homeassistant/components/remote/translations/lb.json @@ -1,4 +1,19 @@ { + "device_automation": { + "action_type": { + "toggle": "{entity_name} \u00ebmschalten", + "turn_off": "{entity_name} ausschalten", + "turn_on": "{entity_name} uschalten" + }, + "condition_type": { + "is_off": "{entity_name} ass ausgeschalt", + "is_on": "{entity_name} ass un" + }, + "trigger_type": { + "turned_off": "{entity_name} gouf ausgeschalt", + "turned_on": "{entity_name} gouf ugeschalt" + } + }, "state": { "_": { "off": "Aus", diff --git a/homeassistant/components/remote/translations/nl.json b/homeassistant/components/remote/translations/nl.json index b3ccad9ae2b5ab..18d984f5c68f5a 100644 --- a/homeassistant/components/remote/translations/nl.json +++ b/homeassistant/components/remote/translations/nl.json @@ -1,4 +1,19 @@ { + "device_automation": { + "action_type": { + "toggle": "Schakel {entity_name}", + "turn_off": "{entity_name} uitschakelen", + "turn_on": "{entity_name} inschakelen" + }, + "condition_type": { + "is_off": "{entity_name} staat uit", + "is_on": "{entity_name} staat aan" + }, + "trigger_type": { + "turned_off": "{entity_name} uitgeschakeld", + "turned_on": "{entity_name} ingeschakeld" + } + }, "state": { "_": { "off": "Uit", diff --git a/homeassistant/components/remote/translations/pl.json b/homeassistant/components/remote/translations/pl.json index e6621d8b42dd96..e36c438b85b967 100644 --- a/homeassistant/components/remote/translations/pl.json +++ b/homeassistant/components/remote/translations/pl.json @@ -1,4 +1,15 @@ { + "device_automation": { + "action_type": { + "toggle": "Prze\u0142\u0105cz {entity_name}", + "turn_off": "Wy\u0142\u0105cz {entity_name}", + "turn_on": "W\u0142\u0105cz {entity_name}" + }, + "condition_type": { + "is_off": "{entity_name} jest wy\u0142\u0105czony", + "is_on": "{entity_name} jest w\u0142\u0105czony" + } + }, "state": { "_": { "off": "wy\u0142\u0105czony", diff --git a/homeassistant/components/remote/translations/sv.json b/homeassistant/components/remote/translations/sv.json index ea82df41e758b3..1b6584c5bf8694 100644 --- a/homeassistant/components/remote/translations/sv.json +++ b/homeassistant/components/remote/translations/sv.json @@ -1,4 +1,18 @@ { + "device_automation": { + "action_type": { + "turn_off": "St\u00e4ng av {entity_name}", + "turn_on": "Sl\u00e5 p\u00e5 {entity_name}" + }, + "condition_type": { + "is_off": "{entity_name} \u00e4r avst\u00e4ngd", + "is_on": "{entity_name} \u00e4r p\u00e5" + }, + "trigger_type": { + "turned_off": "{entity_name} st\u00e4ngdes av", + "turned_on": "{entity_name} slogs p\u00e5" + } + }, "state": { "_": { "off": "Av", diff --git a/homeassistant/components/remote/translations/uk.json b/homeassistant/components/remote/translations/uk.json index bc52ed67ae5c0a..2feda4928e5847 100644 --- a/homeassistant/components/remote/translations/uk.json +++ b/homeassistant/components/remote/translations/uk.json @@ -1,4 +1,10 @@ { + "device_automation": { + "trigger_type": { + "turned_off": "{entity_name} \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e", + "turned_on": "{entity_name} \u0443\u0432\u0456\u043c\u043a\u043d\u0443\u0442\u043e" + } + }, "state": { "_": { "off": "\u0412\u0438\u043c\u043a\u043d\u0435\u043d\u043e", diff --git a/homeassistant/components/rest_command/__init__.py b/homeassistant/components/rest_command/__init__.py index 43b5c8bd7e89c6..b1996bdce50fdd 100644 --- a/homeassistant/components/rest_command/__init__.py +++ b/homeassistant/components/rest_command/__init__.py @@ -136,10 +136,10 @@ async def async_service_handler(service): ) except asyncio.TimeoutError: - _LOGGER.warning("Timeout call %s", request_url, exc_info=1) + _LOGGER.warning("Timeout call %s", request_url) except aiohttp.ClientError: - _LOGGER.error("Client error %s", request_url, exc_info=1) + _LOGGER.error("Client error %s", request_url) # register services hass.services.async_register(DOMAIN, name, async_service_handler) diff --git a/homeassistant/components/rflink/light.py b/homeassistant/components/rflink/light.py index 13e3a0f65da84b..6d63e12378d2db 100644 --- a/homeassistant/components/rflink/light.py +++ b/homeassistant/components/rflink/light.py @@ -197,10 +197,9 @@ def brightness(self): @property def device_state_attributes(self): """Return the device state attributes.""" - attr = {} - if self._brightness is not None: - attr[ATTR_BRIGHTNESS] = self._brightness - return attr + if self._brightness is None: + return {} + return {ATTR_BRIGHTNESS: self._brightness} @property def supported_features(self): @@ -260,10 +259,9 @@ def brightness(self): @property def device_state_attributes(self): """Return the device state attributes.""" - attr = {} - if self._brightness is not None: - attr[ATTR_BRIGHTNESS] = self._brightness - return attr + if self._brightness is None: + return {} + return {ATTR_BRIGHTNESS: self._brightness} @property def supported_features(self): diff --git a/homeassistant/components/rfxtrx/__init__.py b/homeassistant/components/rfxtrx/__init__.py index 6082d54df12d8b..22cc3ea4c9a3ef 100644 --- a/homeassistant/components/rfxtrx/__init__.py +++ b/homeassistant/components/rfxtrx/__init__.py @@ -5,6 +5,7 @@ import logging import RFXtrx as rfxtrxmod +import async_timeout import voluptuous as vol from homeassistant import config_entries @@ -18,18 +19,35 @@ CONF_DEVICES, CONF_HOST, CONF_PORT, + DEGREE, + ELECTRICAL_CURRENT_AMPERE, + ENERGY_KILO_WATT_HOUR, EVENT_HOMEASSISTANT_STOP, + LENGTH_MILLIMETERS, PERCENTAGE, POWER_WATT, + PRESSURE_HPA, + SIGNAL_STRENGTH_DECIBELS_MILLIWATT, + SPEED_METERS_PER_SECOND, TEMP_CELSIUS, + TIME_HOURS, UV_INDEX, + VOLT, ) from homeassistant.core import callback +from homeassistant.exceptions import ConfigEntryNotReady import homeassistant.helpers.config_validation as cv from homeassistant.helpers.restore_state import RestoreEntity from .const import ( ATTR_EVENT, + CONF_AUTOMATIC_ADD, + CONF_DATA_BITS, + CONF_DEBUG, + CONF_FIRE_EVENT, + CONF_OFF_DELAY, + CONF_REMOVE_DEVICE, + CONF_SIGNAL_REPETITIONS, DEVICE_PACKET_TYPE_LIGHTING4, EVENT_RFXTRX_EVENT, SERVICE_SEND, @@ -39,12 +57,6 @@ DEFAULT_SIGNAL_REPETITIONS = 1 -CONF_FIRE_EVENT = "fire_event" -CONF_DATA_BITS = "data_bits" -CONF_AUTOMATIC_ADD = "automatic_add" -CONF_SIGNAL_REPETITIONS = "signal_repetitions" -CONF_DEBUG = "debug" -CONF_OFF_DELAY = "off_delay" SIGNAL_EVENT = f"{DOMAIN}_event" DATA_TYPES = OrderedDict( @@ -52,32 +64,30 @@ ("Temperature", TEMP_CELSIUS), ("Temperature2", TEMP_CELSIUS), ("Humidity", PERCENTAGE), - ("Barometer", ""), - ("Wind direction", ""), - ("Rain rate", ""), + ("Barometer", PRESSURE_HPA), + ("Wind direction", DEGREE), + ("Rain rate", f"{LENGTH_MILLIMETERS}/{TIME_HOURS}"), ("Energy usage", POWER_WATT), - ("Total usage", POWER_WATT), - ("Sound", ""), - ("Sensor Status", ""), - ("Counter value", ""), + ("Total usage", ENERGY_KILO_WATT_HOUR), + ("Sound", None), + ("Sensor Status", None), + ("Counter value", "count"), ("UV", UV_INDEX), - ("Humidity status", ""), - ("Forecast", ""), - ("Forecast numeric", ""), - ("Rain total", ""), - ("Wind average speed", ""), - ("Wind gust", ""), - ("Chill", ""), - ("Total usage", ""), - ("Count", ""), - ("Current Ch. 1", ""), - ("Current Ch. 2", ""), - ("Current Ch. 3", ""), - ("Energy usage", ""), - ("Voltage", ""), - ("Current", ""), + ("Humidity status", None), + ("Forecast", None), + ("Forecast numeric", None), + ("Rain total", LENGTH_MILLIMETERS), + ("Wind average speed", SPEED_METERS_PER_SECOND), + ("Wind gust", SPEED_METERS_PER_SECOND), + ("Chill", TEMP_CELSIUS), + ("Count", "count"), + ("Current Ch. 1", ELECTRICAL_CURRENT_AMPERE), + ("Current Ch. 2", ELECTRICAL_CURRENT_AMPERE), + ("Current Ch. 3", ELECTRICAL_CURRENT_AMPERE), + ("Voltage", VOLT), + ("Current", ELECTRICAL_CURRENT_AMPERE), ("Battery numeric", PERCENTAGE), - ("Rssi numeric", "dBm"), + ("Rssi numeric", SIGNAL_STRENGTH_DECIBELS_MILLIWATT), ] ) @@ -120,10 +130,10 @@ def _ensure_device(value): BASE_SCHEMA = vol.Schema( { - vol.Optional(CONF_DEBUG, default=False): cv.boolean, + vol.Optional(CONF_DEBUG): cv.boolean, vol.Optional(CONF_AUTOMATIC_ADD, default=False): cv.boolean, vol.Optional(CONF_DEVICES, default={}): {cv.string: _ensure_device}, - } + }, ) DEVICE_SCHEMA = BASE_SCHEMA.extend({vol.Required(CONF_DEVICE): cv.string}) @@ -133,7 +143,8 @@ def _ensure_device(value): ) CONFIG_SCHEMA = vol.Schema( - {DOMAIN: vol.Any(DEVICE_SCHEMA, PORT_SCHEMA)}, extra=vol.ALLOW_EXTRA + {DOMAIN: vol.All(cv.deprecated(CONF_DEBUG), vol.Any(DEVICE_SCHEMA, PORT_SCHEMA))}, + extra=vol.ALLOW_EXTRA, ) DOMAINS = ["switch", "sensor", "light", "binary_sensor", "cover"] @@ -148,7 +159,6 @@ async def async_setup(hass, config): CONF_HOST: config[DOMAIN].get(CONF_HOST), CONF_PORT: config[DOMAIN].get(CONF_PORT), CONF_DEVICE: config[DOMAIN].get(CONF_DEVICE), - CONF_DEBUG: config[DOMAIN].get(CONF_DEBUG), CONF_AUTOMATIC_ADD: config[DOMAIN].get(CONF_AUTOMATIC_ADD), CONF_DEVICES: config[DOMAIN][CONF_DEVICES], } @@ -217,18 +227,17 @@ def _create_rfx(config): rfx = rfxtrxmod.Connect( (config[CONF_HOST], config[CONF_PORT]), None, - debug=config[CONF_DEBUG], transport_protocol=rfxtrxmod.PyNetworkTransport, ) else: - rfx = rfxtrxmod.Connect(config[CONF_DEVICE], None, debug=config[CONF_DEBUG]) + rfx = rfxtrxmod.Connect(config[CONF_DEVICE], None) return rfx def _get_device_lookup(devices): """Get a lookup structure for devices.""" - lookup = dict() + lookup = {} for event_code, event_config in devices.items(): event = get_rfx_object(event_code) if event is None: @@ -245,7 +254,11 @@ async def async_setup_internal(hass, entry: config_entries.ConfigEntry): config = entry.data # Initialize library - rfx_object = await hass.async_add_executor_job(_create_rfx, config) + try: + async with async_timeout.timeout(5): + rfx_object = await hass.async_add_executor_job(_create_rfx, config) + except asyncio.TimeoutError as err: + raise ConfigEntryNotReady from err # Setup some per device config devices = _get_device_lookup(config[CONF_DEVICES]) @@ -438,6 +451,12 @@ async def async_added_to_hass(self): ) ) + self.async_on_remove( + self.hass.helpers.dispatcher.async_dispatcher_connect( + f"{DOMAIN}_{CONF_REMOVE_DEVICE}_{self._device_id}", self.async_remove + ) + ) + @property def should_poll(self): """No polling needed for a RFXtrx switch.""" diff --git a/homeassistant/components/rfxtrx/binary_sensor.py b/homeassistant/components/rfxtrx/binary_sensor.py index 21f3e0b74b31e8..7fe89e747bcdfb 100644 --- a/homeassistant/components/rfxtrx/binary_sensor.py +++ b/homeassistant/components/rfxtrx/binary_sensor.py @@ -61,6 +61,18 @@ } +def supported(event): + """Return whether an event supports binary_sensor.""" + if isinstance(event, rfxtrxmod.ControlEvent): + return True + if isinstance(event, rfxtrxmod.SensorEvent): + return event.values.get("Sensor Status") in [ + *SENSOR_STATUS_ON, + *SENSOR_STATUS_OFF, + ] + return False + + async def async_setup_entry( hass, config_entry, @@ -74,16 +86,6 @@ async def async_setup_entry( discovery_info = config_entry.data - def supported(event): - if isinstance(event, rfxtrxmod.ControlEvent): - return True - if isinstance(event, rfxtrxmod.SensorEvent): - return event.values.get("Sensor Status") in [ - *SENSOR_STATUS_ON, - *SENSOR_STATUS_OFF, - ] - return False - for packet_id, entity_info in discovery_info[CONF_DEVICES].items(): event = get_rfx_object(packet_id) if event is None: diff --git a/homeassistant/components/rfxtrx/config_flow.py b/homeassistant/components/rfxtrx/config_flow.py index 287e1ec4baf6d1..db7ca49691a939 100644 --- a/homeassistant/components/rfxtrx/config_flow.py +++ b/homeassistant/components/rfxtrx/config_flow.py @@ -1,12 +1,404 @@ """Config flow for RFXCOM RFXtrx integration.""" +import copy import logging +import os -from homeassistant import config_entries +import RFXtrx as rfxtrxmod +import serial +import serial.tools.list_ports +import voluptuous as vol -from . import DOMAIN +from homeassistant import config_entries, exceptions +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import ( + CONF_COMMAND_OFF, + CONF_COMMAND_ON, + CONF_DEVICE, + CONF_DEVICE_ID, + CONF_DEVICES, + CONF_HOST, + CONF_PORT, + CONF_TYPE, +) +from homeassistant.core import callback +from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.device_registry import ( + 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 . import DOMAIN, get_device_id, get_rfx_object +from .binary_sensor import supported as binary_supported +from .const import ( + CONF_AUTOMATIC_ADD, + CONF_DATA_BITS, + CONF_FIRE_EVENT, + CONF_OFF_DELAY, + CONF_REMOVE_DEVICE, + CONF_REPLACE_DEVICE, + CONF_SIGNAL_REPETITIONS, + DEVICE_PACKET_TYPE_LIGHTING4, +) +from .cover import supported as cover_supported +from .light import supported as light_supported +from .switch import supported as switch_supported _LOGGER = logging.getLogger(__name__) +CONF_EVENT_CODE = "event_code" +CONF_MANUAL_PATH = "Enter Manually" + + +def none_or_int(value, base): + """Check if strin is one otherwise convert to int.""" + if value is None: + return None + return int(value, base) + + +class OptionsFlow(config_entries.OptionsFlow): + """Handle Rfxtrx options.""" + + def __init__(self, config_entry: ConfigEntry) -> None: + """Initialize rfxtrx options flow.""" + self._config_entry = config_entry + self._global_options = None + self._selected_device = None + self._selected_device_entry_id = None + self._selected_device_event_code = None + self._selected_device_object = None + self._device_entries = None + self._device_registry = None + + async def async_step_init(self, user_input=None): + """Manage the options.""" + return await self.async_step_prompt_options() + + async def async_step_prompt_options(self, user_input=None): + """Prompt for options.""" + errors = {} + + if user_input is not None: + self._global_options = { + CONF_AUTOMATIC_ADD: user_input[CONF_AUTOMATIC_ADD], + } + if CONF_DEVICE in user_input: + entry_id = user_input[CONF_DEVICE] + device_data = self._get_device_data(entry_id) + self._selected_device_entry_id = entry_id + event_code = device_data[CONF_EVENT_CODE] + self._selected_device_event_code = event_code + self._selected_device = self._config_entry.data[CONF_DEVICES][ + event_code + ] + self._selected_device_object = get_rfx_object(event_code) + return await self.async_step_set_device_options() + if CONF_REMOVE_DEVICE in user_input: + remove_devices = user_input[CONF_REMOVE_DEVICE] + devices = {} + for entry_id in remove_devices: + device_data = self._get_device_data(entry_id) + + event_code = device_data[CONF_EVENT_CODE] + device_id = device_data[CONF_DEVICE_ID] + self.hass.helpers.dispatcher.async_dispatcher_send( + f"{DOMAIN}_{CONF_REMOVE_DEVICE}_{device_id}" + ) + self._device_registry.async_remove_device(entry_id) + devices[event_code] = None + + self.update_config_data( + global_options=self._global_options, devices=devices + ) + + return self.async_create_entry(title="", data={}) + if CONF_EVENT_CODE in user_input: + self._selected_device_event_code = user_input[CONF_EVENT_CODE] + self._selected_device = {} + selected_device_object = get_rfx_object( + self._selected_device_event_code + ) + if selected_device_object is None: + errors[CONF_EVENT_CODE] = "invalid_event_code" + elif not self._can_add_device(selected_device_object): + errors[CONF_EVENT_CODE] = "already_configured_device" + else: + self._selected_device_object = selected_device_object + return await self.async_step_set_device_options() + + if not errors: + self.update_config_data(global_options=self._global_options) + + 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, self._config_entry.entry_id + ) + self._device_registry = device_registry + self._device_entries = device_entries + + devices = { + entry.id: entry.name_by_user if entry.name_by_user else entry.name + for entry in device_entries + } + + options = { + vol.Optional( + CONF_AUTOMATIC_ADD, + default=self._config_entry.data[CONF_AUTOMATIC_ADD], + ): bool, + vol.Optional(CONF_EVENT_CODE): str, + vol.Optional(CONF_DEVICE): vol.In(devices), + vol.Optional(CONF_REMOVE_DEVICE): cv.multi_select(devices), + } + + return self.async_show_form( + step_id="prompt_options", data_schema=vol.Schema(options), errors=errors + ) + + async def async_step_set_device_options(self, user_input=None): + """Manage device options.""" + errors = {} + + if user_input is not None: + device_id = get_device_id( + self._selected_device_object.device, + data_bits=user_input.get(CONF_DATA_BITS), + ) + + if CONF_REPLACE_DEVICE in user_input: + await self._async_replace_device(user_input[CONF_REPLACE_DEVICE]) + + devices = {self._selected_device_event_code: None} + self.update_config_data( + global_options=self._global_options, devices=devices + ) + + return self.async_create_entry(title="", data={}) + + try: + command_on = none_or_int(user_input.get(CONF_COMMAND_ON), 16) + except ValueError: + errors[CONF_COMMAND_ON] = "invalid_input_2262_on" + + try: + command_off = none_or_int(user_input.get(CONF_COMMAND_OFF), 16) + except ValueError: + errors[CONF_COMMAND_OFF] = "invalid_input_2262_off" + + try: + off_delay = none_or_int(user_input.get(CONF_OFF_DELAY), 10) + except ValueError: + errors[CONF_OFF_DELAY] = "invalid_input_off_delay" + + if not errors: + devices = {} + device = { + CONF_DEVICE_ID: device_id, + CONF_FIRE_EVENT: user_input.get(CONF_FIRE_EVENT, False), + CONF_SIGNAL_REPETITIONS: user_input.get(CONF_SIGNAL_REPETITIONS, 1), + } + + devices[self._selected_device_event_code] = device + + if off_delay: + device[CONF_OFF_DELAY] = off_delay + if user_input.get(CONF_DATA_BITS): + device[CONF_DATA_BITS] = user_input[CONF_DATA_BITS] + if command_on: + device[CONF_COMMAND_ON] = command_on + if command_off: + device[CONF_COMMAND_OFF] = command_off + + self.update_config_data( + global_options=self._global_options, devices=devices + ) + + return self.async_create_entry(title="", data={}) + + device_data = self._selected_device + + data_schema = { + vol.Optional( + CONF_FIRE_EVENT, default=device_data.get(CONF_FIRE_EVENT, False) + ): bool, + } + + if binary_supported(self._selected_device_object): + if device_data.get(CONF_OFF_DELAY): + off_delay_schema = { + vol.Optional( + CONF_OFF_DELAY, + description={"suggested_value": device_data[CONF_OFF_DELAY]}, + ): str, + } + else: + off_delay_schema = { + vol.Optional(CONF_OFF_DELAY): str, + } + data_schema.update(off_delay_schema) + + if ( + binary_supported(self._selected_device_object) + or cover_supported(self._selected_device_object) + or light_supported(self._selected_device_object) + or switch_supported(self._selected_device_object) + ): + data_schema.update( + { + vol.Optional( + CONF_SIGNAL_REPETITIONS, + default=device_data.get(CONF_SIGNAL_REPETITIONS, 1), + ): int, + } + ) + + if ( + self._selected_device_object.device.packettype + == DEVICE_PACKET_TYPE_LIGHTING4 + ): + data_schema.update( + { + vol.Optional( + CONF_DATA_BITS, default=device_data.get(CONF_DATA_BITS, 0) + ): int, + vol.Optional( + CONF_COMMAND_ON, + default=hex(device_data.get(CONF_COMMAND_ON, 0)), + ): str, + vol.Optional( + CONF_COMMAND_OFF, + default=hex(device_data.get(CONF_COMMAND_OFF, 0)), + ): str, + } + ) + + devices = { + entry.id: entry.name_by_user if entry.name_by_user else entry.name + for entry in self._device_entries + if self._can_replace_device(entry.id) + } + + if devices: + data_schema.update( + { + vol.Optional(CONF_REPLACE_DEVICE): vol.In(devices), + } + ) + + return self.async_show_form( + step_id="set_device_options", + data_schema=vol.Schema(data_schema), + errors=errors, + ) + + async def _async_replace_device(self, replace_device): + """Migrate properties of a device into another.""" + device_registry = self._device_registry + old_device = self._selected_device_entry_id + old_entry = device_registry.async_get(old_device) + device_registry.async_update_device( + replace_device, + area_id=old_entry.area_id, + name_by_user=old_entry.name_by_user, + ) + + old_device_data = self._get_device_data(old_device) + new_device_data = self._get_device_data(replace_device) + + 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, old_device) + entity_migration_map = {} + for entry in entity_entries: + unique_id = entry.unique_id + new_unique_id = unique_id.replace(old_device_id, new_device_id) + + new_entity_id = entity_registry.async_get_entity_id( + entry.domain, entry.platform, new_unique_id + ) + + if new_entity_id is not None: + entity_migration_map[new_entity_id] = entry + + for entry in entity_migration_map.values(): + entity_registry.async_remove(entry.entity_id) + + for entity_id, entry in entity_migration_map.items(): + entity_registry.async_update_entity( + entity_id, + new_entity_id=entry.entity_id, + name=entry.name, + icon=entry.icon, + ) + + device_registry.async_remove_device(old_device) + + def _can_add_device(self, new_rfx_obj): + """Check if device does not already exist.""" + new_device_id = get_device_id(new_rfx_obj.device) + for packet_id, entity_info in self._config_entry.data[CONF_DEVICES].items(): + rfx_obj = get_rfx_object(packet_id) + device_id = get_device_id(rfx_obj.device, entity_info.get(CONF_DATA_BITS)) + if new_device_id == device_id: + return False + + return True + + def _can_replace_device(self, entry_id): + """Check if device can be replaced with selected device.""" + device_data = self._get_device_data(entry_id) + event_code = device_data[CONF_EVENT_CODE] + rfx_obj = get_rfx_object(event_code) + if ( + rfx_obj.device.packettype == self._selected_device_object.device.packettype + and rfx_obj.device.subtype == self._selected_device_object.device.subtype + and self._selected_device_event_code != event_code + ): + return True + + return False + + def _get_device_data(self, entry_id): + """Get event code based on device identifier.""" + event_code = None + device_id = None + entry = self._device_registry.async_get(entry_id) + device_id = next(iter(entry.identifiers))[1:] + for packet_id, entity_info in self._config_entry.data[CONF_DEVICES].items(): + if tuple(entity_info.get(CONF_DEVICE_ID)) == device_id: + event_code = packet_id + break + + data = {CONF_EVENT_CODE: event_code, CONF_DEVICE_ID: device_id} + + return data + + @callback + def update_config_data(self, global_options=None, devices=None): + """Update data in ConfigEntry.""" + entry_data = self._config_entry.data.copy() + entry_data[CONF_DEVICES] = copy.deepcopy(self._config_entry.data[CONF_DEVICES]) + if global_options: + entry_data.update(global_options) + if devices: + for event_code, options in devices.items(): + if options is None: + entry_data[CONF_DEVICES].pop(event_code) + else: + entry_data[CONF_DEVICES][event_code] = options + self.hass.config_entries.async_update_entry(self._config_entry, data=entry_data) + self.hass.async_create_task( + self.hass.config_entries.async_reload(self._config_entry.entry_id) + ) + class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for RFXCOM RFXtrx.""" @@ -14,10 +406,190 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH + async def async_step_user(self, user_input=None): + """Step when user initializes a integration.""" + await self.async_set_unique_id(DOMAIN) + self._abort_if_unique_id_configured() + + errors = {} + if user_input is not None: + user_selection = user_input[CONF_TYPE] + if user_selection == "Serial": + return await self.async_step_setup_serial() + + return await self.async_step_setup_network() + + list_of_types = ["Serial", "Network"] + + schema = vol.Schema({vol.Required(CONF_TYPE): vol.In(list_of_types)}) + return self.async_show_form(step_id="user", data_schema=schema, errors=errors) + + async def async_step_setup_network(self, user_input=None): + """Step when setting up network configuration.""" + errors = {} + + if user_input is not None: + host = user_input[CONF_HOST] + port = user_input[CONF_PORT] + + try: + data = await self.async_validate_rfx(host=host, port=port) + except CannotConnect: + errors["base"] = "cannot_connect" + + if not errors: + return self.async_create_entry(title="RFXTRX", data=data) + + schema = vol.Schema( + {vol.Required(CONF_HOST): str, vol.Required(CONF_PORT): int} + ) + return self.async_show_form( + step_id="setup_network", + data_schema=schema, + errors=errors, + ) + + async def async_step_setup_serial(self, user_input=None): + """Step when setting up serial configuration.""" + errors = {} + + if user_input is not None: + user_selection = user_input[CONF_DEVICE] + if user_selection == CONF_MANUAL_PATH: + return await self.async_step_setup_serial_manual_path() + + dev_path = await self.hass.async_add_executor_job( + get_serial_by_id, user_selection + ) + + try: + data = await self.async_validate_rfx(device=dev_path) + except CannotConnect: + errors["base"] = "cannot_connect" + + if not errors: + return self.async_create_entry(title="RFXTRX", data=data) + + ports = await self.hass.async_add_executor_job(serial.tools.list_ports.comports) + list_of_ports = {} + for port in ports: + list_of_ports[ + port.device + ] = f"{port}, s/n: {port.serial_number or 'n/a'}" + ( + f" - {port.manufacturer}" if port.manufacturer else "" + ) + list_of_ports[CONF_MANUAL_PATH] = CONF_MANUAL_PATH + + schema = vol.Schema({vol.Required(CONF_DEVICE): vol.In(list_of_ports)}) + return self.async_show_form( + step_id="setup_serial", + data_schema=schema, + errors=errors, + ) + + async def async_step_setup_serial_manual_path(self, user_input=None): + """Select path manually.""" + errors = {} + + if user_input is not None: + device = user_input[CONF_DEVICE] + try: + data = await self.async_validate_rfx(device=device) + except CannotConnect: + errors["base"] = "cannot_connect" + + if not errors: + return self.async_create_entry(title="RFXTRX", data=data) + + schema = vol.Schema({vol.Required(CONF_DEVICE): str}) + return self.async_show_form( + step_id="setup_serial_manual_path", + data_schema=schema, + errors=errors, + ) + async def async_step_import(self, import_config=None): """Handle the initial step.""" entry = await self.async_set_unique_id(DOMAIN) - if entry and import_config.items() != entry.data.items(): - self.hass.config_entries.async_update_entry(entry, data=import_config) - return self.async_abort(reason="already_configured") + if entry: + if CONF_DEVICES not in entry.data: + # In version 0.113, devices key was not written to config entry. Update the entry with import data + self._abort_if_unique_id_configured(import_config) + else: + self._abort_if_unique_id_configured() + + host = import_config[CONF_HOST] + port = import_config[CONF_PORT] + device = import_config[CONF_DEVICE] + + try: + if host is not None: + await self.async_validate_rfx(host=host, port=port) + else: + await self.async_validate_rfx(device=device) + except CannotConnect: + return self.async_abort(reason="cannot_connect") + return self.async_create_entry(title="RFXTRX", data=import_config) + + async def async_validate_rfx(self, host=None, port=None, device=None): + """Create data for rfxtrx entry.""" + success = await self.hass.async_add_executor_job( + _test_transport, host, port, device + ) + if not success: + raise CannotConnect + + data = { + CONF_HOST: host, + CONF_PORT: port, + CONF_DEVICE: device, + CONF_AUTOMATIC_ADD: False, + CONF_DEVICES: {}, + } + return data + + @staticmethod + @callback + def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlow: + """Get the options flow for this handler.""" + return OptionsFlow(config_entry) + + +def _test_transport(host, port, device): + """Construct a rfx object based on config.""" + if port is not None: + try: + conn = rfxtrxmod.PyNetworkTransport((host, port)) + except OSError: + return False + + conn.close() + else: + try: + conn = rfxtrxmod.PySerialTransport(device) + except serial.serialutil.SerialException: + return False + + if conn.serial is None: + return False + + conn.close() + + return True + + +def get_serial_by_id(dev_path: str) -> str: + """Return a /dev/serial/by-id match for given device if available.""" + by_id = "/dev/serial/by-id" + if not os.path.isdir(by_id): + return dev_path + + for path in (entry.path for entry in os.scandir(by_id) if entry.is_symlink()): + if os.path.realpath(path) == dev_path: + return path + return dev_path + + +class CannotConnect(exceptions.HomeAssistantError): + """Error to indicate we cannot connect.""" diff --git a/homeassistant/components/rfxtrx/const.py b/homeassistant/components/rfxtrx/const.py index c0436bfcf60489..404d344cc7191d 100644 --- a/homeassistant/components/rfxtrx/const.py +++ b/homeassistant/components/rfxtrx/const.py @@ -1,5 +1,14 @@ """Constants for RFXtrx integration.""" +CONF_FIRE_EVENT = "fire_event" +CONF_DATA_BITS = "data_bits" +CONF_AUTOMATIC_ADD = "automatic_add" +CONF_SIGNAL_REPETITIONS = "signal_repetitions" +CONF_DEBUG = "debug" +CONF_OFF_DELAY = "off_delay" + +CONF_REMOVE_DEVICE = "remove_device" +CONF_REPLACE_DEVICE = "replace_device" COMMAND_ON_LIST = [ "On", diff --git a/homeassistant/components/rfxtrx/cover.py b/homeassistant/components/rfxtrx/cover.py index fc6ab6cbf15551..86950308f5550d 100644 --- a/homeassistant/components/rfxtrx/cover.py +++ b/homeassistant/components/rfxtrx/cover.py @@ -20,6 +20,11 @@ _LOGGER = logging.getLogger(__name__) +def supported(event): + """Return whether an event supports cover.""" + return event.device.known_to_be_rollershutter + + async def async_setup_entry( hass, config_entry, @@ -29,9 +34,6 @@ async def async_setup_entry( discovery_info = config_entry.data device_ids = set() - def supported(event): - return event.device.known_to_be_rollershutter - entities = [] for packet_id, entity_info in discovery_info[CONF_DEVICES].items(): event = get_rfx_object(packet_id) diff --git a/homeassistant/components/rfxtrx/light.py b/homeassistant/components/rfxtrx/light.py index 791cc158693ce9..33ee5ea4748ab2 100644 --- a/homeassistant/components/rfxtrx/light.py +++ b/homeassistant/components/rfxtrx/light.py @@ -28,6 +28,14 @@ SUPPORT_RFXTRX = SUPPORT_BRIGHTNESS +def supported(event): + """Return whether an event supports light.""" + return ( + isinstance(event.device, rfxtrxmod.LightingDevice) + and event.device.known_to_be_dimmable + ) + + async def async_setup_entry( hass, config_entry, @@ -37,12 +45,6 @@ async def async_setup_entry( discovery_info = config_entry.data device_ids = set() - def supported(event): - return ( - isinstance(event.device, rfxtrxmod.LightingDevice) - and event.device.known_to_be_dimmable - ) - # Add switch from config file entities = [] for packet_id, entity_info in discovery_info[CONF_DEVICES].items(): diff --git a/homeassistant/components/rfxtrx/manifest.json b/homeassistant/components/rfxtrx/manifest.json index 44b53ed0dacbb0..e62fc5c3c8311b 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.25"], - "codeowners": ["@danielhiversen", "@elupus"], - "config_flow": false -} \ No newline at end of file + "requirements": ["pyRFXtrx==0.26"], + "codeowners": ["@danielhiversen", "@elupus", "@RobBie1221"], + "config_flow": true +} diff --git a/homeassistant/components/rfxtrx/sensor.py b/homeassistant/components/rfxtrx/sensor.py index 4acde6b04506d1..81e0c60e05532e 100644 --- a/homeassistant/components/rfxtrx/sensor.py +++ b/homeassistant/components/rfxtrx/sensor.py @@ -9,7 +9,14 @@ DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_TEMPERATURE, ) -from homeassistant.const import CONF_DEVICES +from homeassistant.const import ( + CONF_DEVICES, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_POWER, + DEVICE_CLASS_PRESSURE, + DEVICE_CLASS_VOLTAGE, +) from homeassistant.core import callback from . import ( @@ -30,7 +37,7 @@ def _battery_convert(value): """Battery is given as a value between 0 and 9.""" if value is None: return None - return value * 10 + return (value + 1) * 10 def _rssi_convert(value): @@ -41,10 +48,17 @@ def _rssi_convert(value): DEVICE_CLASSES = { + "Barometer": DEVICE_CLASS_PRESSURE, "Battery numeric": DEVICE_CLASS_BATTERY, - "Rssi numeric": DEVICE_CLASS_SIGNAL_STRENGTH, + "Current Ch. 1": DEVICE_CLASS_CURRENT, + "Current Ch. 2": DEVICE_CLASS_CURRENT, + "Current Ch. 3": DEVICE_CLASS_CURRENT, + "Energy usage": DEVICE_CLASS_POWER, "Humidity": DEVICE_CLASS_HUMIDITY, + "Rssi numeric": DEVICE_CLASS_SIGNAL_STRENGTH, "Temperature": DEVICE_CLASS_TEMPERATURE, + "Total usage": DEVICE_CLASS_ENERGY, + "Voltage": DEVICE_CLASS_VOLTAGE, } @@ -124,7 +138,7 @@ def __init__(self, device, device_id, data_type, event=None): """Initialize the sensor.""" super().__init__(device, device_id, event=event) self.data_type = data_type - self._unit_of_measurement = DATA_TYPES.get(data_type, "") + self._unit_of_measurement = DATA_TYPES.get(data_type) self._name = f"{device.type_string} {device.id_string} {data_type}" self._unique_id = "_".join(x for x in (*self._device_id, data_type)) diff --git a/homeassistant/components/rfxtrx/strings.json b/homeassistant/components/rfxtrx/strings.json index e19265dec325f4..9e97699915746d 100644 --- a/homeassistant/components/rfxtrx/strings.json +++ b/homeassistant/components/rfxtrx/strings.json @@ -1,9 +1,74 @@ { - "config": { - "step": {}, - "error": {}, - "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" - } + "title": "Rfxtrx", + "config": { + "abort": { + "already_configured": "[%key:common::config_flow::abort::single_instance_allowed%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" + }, + "step": { + "user": { + "data": { + "type": "Connection type" + }, + "title": "Select connection type" + }, + "setup_network": { + "data": { + "host": "[%key:common::config_flow::data::host%]", + "port": "[%key:common::config_flow::data::port%]" + }, + "title": "Select connection address" + }, + "setup_serial": { + "data": { + "device": "Select device" + }, + "title": "Device" + }, + "setup_serial_manual_path": { + "data": { + "device": "[%key:common::config_flow::data::usb_path%]" + }, + "title": "Path" + } } + }, + "options": { + "step": { + "prompt_options": { + "data": { + "debug": "Enable debugging", + "automatic_add": "Enable automatic add", + "event_code": "Enter event code to add", + "device": "Select device to configure", + "remove_device": "Select device to delete" + }, + "title": "Rfxtrx Options" + }, + "set_device_options": { + "data": { + "fire_event": "Enable device event", + "off_delay": "Off delay", + "off_delay_enabled": "Enable off delay", + "data_bit": "Number of data bits", + "command_on": "Data bits value for command on", + "command_off": "Data bits value for command off", + "signal_repetitions": "Number of signal repetitions", + "replace_device": "Select device to replace" + }, + "title": "Configure device options" + } + }, + "error": { + "already_configured_device": "[%key:common::config_flow::abort::already_configured_device%]", + "invalid_event_code": "Invalid event code", + "invalid_input_2262_on": "Invalid input for command on", + "invalid_input_2262_off": "Invalid input for command off", + "invalid_input_off_delay": "Invalid input for off delay", + "unknown": "[%key:common::config_flow::error::unknown%]" + } + } } diff --git a/homeassistant/components/rfxtrx/switch.py b/homeassistant/components/rfxtrx/switch.py index bce5222b778c9f..5306921079499e 100644 --- a/homeassistant/components/rfxtrx/switch.py +++ b/homeassistant/components/rfxtrx/switch.py @@ -25,6 +25,16 @@ _LOGGER = logging.getLogger(__name__) +def supported(event): + """Return whether an event supports switch.""" + return ( + isinstance(event.device, rfxtrxmod.LightingDevice) + and not event.device.known_to_be_dimmable + and not event.device.known_to_be_rollershutter + or isinstance(event.device, rfxtrxmod.RfyDevice) + ) + + async def async_setup_entry( hass, config_entry, @@ -34,14 +44,6 @@ async def async_setup_entry( discovery_info = config_entry.data device_ids = set() - def supported(event): - return ( - isinstance(event.device, rfxtrxmod.LightingDevice) - and not event.device.known_to_be_dimmable - and not event.device.known_to_be_rollershutter - or isinstance(event.device, rfxtrxmod.RfyDevice) - ) - # Add switch from config file entities = [] for packet_id, entity_info in discovery_info[CONF_DEVICES].items(): diff --git a/homeassistant/components/rfxtrx/translations/ca.json b/homeassistant/components/rfxtrx/translations/ca.json index 14e637f5f98553..5861878e0792b2 100644 --- a/homeassistant/components/rfxtrx/translations/ca.json +++ b/homeassistant/components/rfxtrx/translations/ca.json @@ -1,7 +1,74 @@ { "config": { "abort": { - "already_configured": "El dispositiu ja est\u00e0 configurat" + "already_configured": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3.", + "cannot_connect": "Ha fallat la connexi\u00f3" + }, + "error": { + "cannot_connect": "Ha fallat la connexi\u00f3" + }, + "step": { + "setup_network": { + "data": { + "host": "Amfitri\u00f3", + "port": "Port" + }, + "title": "Selecciona l'adre\u00e7a de connexi\u00f3" + }, + "setup_serial": { + "data": { + "device": "Selecciona el dispositiu" + }, + "title": "Dispositiu" + }, + "setup_serial_manual_path": { + "data": { + "device": "Ruta del port USB del dispositiu" + }, + "title": "Ruta" + }, + "user": { + "data": { + "type": "Tipus de connexi\u00f3" + }, + "title": "Selecciona el tipus de connexi\u00f3" + } } - } + }, + "options": { + "error": { + "already_configured_device": "El dispositiu ja est\u00e0 configurat", + "invalid_event_code": "Codi d'esdeveniment inv\u00e0lid", + "invalid_input_2262_off": "Entrada no v\u00e0lida per a l'ordre OFF", + "invalid_input_2262_on": "Entrada no v\u00e0lida per a l'ordre ON", + "invalid_input_off_delay": "Entrada no v\u00e0lida per al retard OFF", + "unknown": "Error inesperat" + }, + "step": { + "prompt_options": { + "data": { + "automatic_add": "Activa l'addici\u00f3 autom\u00e0tica", + "debug": "Activa la depuraci\u00f3", + "device": "Selecciona el dispositiu a configurar", + "event_code": "Introdueix el codi de l'esdeveniment a afegir", + "remove_device": "Selecciona el dispositiu a eliminar" + }, + "title": "Opcions de Rfxtrx" + }, + "set_device_options": { + "data": { + "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" + }, + "title": "Configuraci\u00f3 de les opcions del dispositiu" + } + } + }, + "title": "Rfxtrx" } \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/de.json b/homeassistant/components/rfxtrx/translations/de.json new file mode 100644 index 00000000000000..da1d200c2a2099 --- /dev/null +++ b/homeassistant/components/rfxtrx/translations/de.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Ger\u00e4t ist bereits konfiguriert" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/el.json b/homeassistant/components/rfxtrx/translations/el.json new file mode 100644 index 00000000000000..fcf3a9ec83bca4 --- /dev/null +++ b/homeassistant/components/rfxtrx/translations/el.json @@ -0,0 +1,67 @@ +{ + "config": { + "abort": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, + "step": { + "setup_network": { + "data": { + "port": "\u0398\u03cd\u03c1\u03b1" + }, + "title": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, + "setup_serial": { + "data": { + "device": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" + }, + "title": "\u03a3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" + }, + "setup_serial_manual_path": { + "title": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae" + }, + "user": { + "data": { + "type": "\u03a4\u03cd\u03c0\u03bf\u03c2 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ae \u03c4\u03cd\u03c0\u03bf\u03c5 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + } + } + }, + "options": { + "error": { + "invalid_event_code": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03bf\u03c2 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03bf\u03c2", + "invalid_input_2262_off": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b5\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03b3\u03b9\u03b1 \u03b5\u03bd\u03c4\u03bf\u03bb\u03ae off", + "invalid_input_2262_on": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b5\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03b3\u03b9\u03b1 \u03b5\u03bd\u03c4\u03bf\u03bb\u03ae on", + "invalid_input_off_delay": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b5\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae \u03b3\u03b9\u03b1 \u03ba\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" + }, + "step": { + "prompt_options": { + "data": { + "automatic_add": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b1\u03c5\u03c4\u03cc\u03bc\u03b1\u03c4\u03b7\u03c2 \u03c0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7\u03c2", + "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", + "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" + }, + "title": "\u0395\u03c0\u03b9\u03bb\u03bf\u03b3\u03ad\u03c2 Rfxtrx" + }, + "set_device_options": { + "data": { + "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" + }, + "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" + } + } + }, + "title": "Rfxtrx" +} \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/en.json b/homeassistant/components/rfxtrx/translations/en.json index 1344d2f69881f4..5bc019699b70a1 100644 --- a/homeassistant/components/rfxtrx/translations/en.json +++ b/homeassistant/components/rfxtrx/translations/en.json @@ -1,7 +1,74 @@ { "config": { "abort": { - "already_configured": "Device is already configured" + "already_configured": "Already configured. Only a single configuration possible.", + "cannot_connect": "Failed to connect" + }, + "error": { + "cannot_connect": "Failed to connect" + }, + "step": { + "setup_network": { + "data": { + "host": "Host", + "port": "Port" + }, + "title": "Select connection address" + }, + "setup_serial": { + "data": { + "device": "Select device" + }, + "title": "Device" + }, + "setup_serial_manual_path": { + "data": { + "device": "USB Device Path" + }, + "title": "Path" + }, + "user": { + "data": { + "type": "Connection type" + }, + "title": "Select connection type" + } } - } + }, + "options": { + "error": { + "already_configured_device": "Device is already configured", + "invalid_event_code": "Invalid event code", + "invalid_input_2262_off": "Invalid input for command off", + "invalid_input_2262_on": "Invalid input for command on", + "invalid_input_off_delay": "Invalid input for off delay", + "unknown": "Unexpected error" + }, + "step": { + "prompt_options": { + "data": { + "automatic_add": "Enable automatic add", + "debug": "Enable debugging", + "device": "Select device to configure", + "event_code": "Enter event code to add", + "remove_device": "Select device to delete" + }, + "title": "Rfxtrx Options" + }, + "set_device_options": { + "data": { + "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" + }, + "title": "Configure device options" + } + } + }, + "title": "Rfxtrx" } \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/es.json b/homeassistant/components/rfxtrx/translations/es.json index e8e23bf8343ee3..ee4cc841d1492b 100644 --- a/homeassistant/components/rfxtrx/translations/es.json +++ b/homeassistant/components/rfxtrx/translations/es.json @@ -1,7 +1,74 @@ { "config": { "abort": { - "already_configured": "El dispositivo 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" + }, + "step": { + "setup_network": { + "data": { + "host": "Host", + "port": "Puerto" + }, + "title": "Seleccionar la direcci\u00f3n de conexi\u00f3n" + }, + "setup_serial": { + "data": { + "device": "Seleccionar dispositivo" + }, + "title": "Dispositivo" + }, + "setup_serial_manual_path": { + "data": { + "device": "Ruta del dispositivo USB" + }, + "title": "Ruta" + }, + "user": { + "data": { + "type": "Tipo de conexi\u00f3n" + }, + "title": "Seleccionar tipo de conexi\u00f3n" + } } - } + }, + "options": { + "error": { + "already_configured_device": "El dispositivo ya est\u00e1 configurado", + "invalid_event_code": "C\u00f3digo de evento no v\u00e1lido", + "invalid_input_2262_off": "Entrada inv\u00e1lida para el comando de apagado", + "invalid_input_2262_on": "Entrada inv\u00e1lida para el comando de encendido", + "invalid_input_off_delay": "Entrada inv\u00e1lida para el retardo de apagado", + "unknown": "Error inesperado" + }, + "step": { + "prompt_options": { + "data": { + "automatic_add": "Activar la adici\u00f3n autom\u00e1tica", + "debug": "Activar la depuraci\u00f3n", + "device": "Seleccionar dispositivo para configurar", + "event_code": "Introducir el c\u00f3digo de evento para a\u00f1adir", + "remove_device": "Seleccione el dispositivo a eliminar" + }, + "title": "Opciones de Rfxtrx" + }, + "set_device_options": { + "data": { + "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" + }, + "title": "Configurar las opciones del dispositivo" + } + } + }, + "title": "Rfxtrx" } \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/et.json b/homeassistant/components/rfxtrx/translations/et.json new file mode 100644 index 00000000000000..a7b66994d4b102 --- /dev/null +++ b/homeassistant/components/rfxtrx/translations/et.json @@ -0,0 +1,69 @@ +{ + "config": { + "abort": { + "cannot_connect": "\u00dchendus nurjus" + }, + "error": { + "cannot_connect": "\u00dchendus nurjus" + }, + "step": { + "setup_network": { + "title": "Vali \u00fchenduse aadress" + }, + "setup_serial": { + "data": { + "device": "Vali seade" + }, + "title": "Seade" + }, + "setup_serial_manual_path": { + "data": { + "device": "USB seadme rada" + }, + "title": "Rada" + }, + "user": { + "data": { + "type": "\u00dchenduse t\u00fc\u00fcp" + }, + "title": "Vali \u00fchenduse t\u00fc\u00fcp" + } + } + }, + "options": { + "error": { + "already_configured_device": "Seade on juba h\u00e4\u00e4lestatud", + "invalid_event_code": "Vale s\u00fcndmuse kood", + "invalid_input_2262_off": "Vigane sisestus v\u00e4ljal\u00fclitamiseks", + "invalid_input_2262_on": "Vigane sisestus sissel\u00fclitamisks", + "invalid_input_off_delay": "Vigane sisestus v\u00e4ljal\u00fclitamise viivituseks", + "unknown": "Tundmatu viga" + }, + "step": { + "prompt_options": { + "data": { + "automatic_add": "Luba automaatne lisamine", + "debug": "Luba silumine", + "device": "Vali seadistatav seade", + "event_code": "Sisesta lisatava s\u00fcndmuse kood", + "remove_device": "Vali eemaldatav seade" + }, + "title": "Rfxtrx valikud" + }, + "set_device_options": { + "data": { + "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" + }, + "title": "Seadista seadme valikud" + } + } + }, + "title": "Rfxtrx" +} \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/fr.json b/homeassistant/components/rfxtrx/translations/fr.json index c4bc0d48b1a08b..5155bf6dacf6cb 100644 --- a/homeassistant/components/rfxtrx/translations/fr.json +++ b/homeassistant/components/rfxtrx/translations/fr.json @@ -2,6 +2,30 @@ "config": { "abort": { "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + }, + "step": { + "setup_serial_manual_path": { + "data": { + "device": "Chemin du p\u00e9riph\u00e9rique USB" + } + } } - } + }, + "options": { + "error": { + "already_configured_device": "L'appareil est d\u00e9j\u00e0 configur\u00e9", + "invalid_event_code": "Code d'\u00e9v\u00e9nement non valide", + "unknown": "Erreur inattendue" + }, + "step": { + "set_device_options": { + "data": { + "replace_device": "S\u00e9lectionnez l'appareil \u00e0 remplacer", + "signal_repetitions": "Nombre de r\u00e9p\u00e9titions du signal" + }, + "title": "Configurer les options de l'appareil" + } + } + }, + "title": "Rfxtrx" } \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/hu.json b/homeassistant/components/rfxtrx/translations/hu.json new file mode 100644 index 00000000000000..3b2d79a34a77e2 --- /dev/null +++ b/homeassistant/components/rfxtrx/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/it.json b/homeassistant/components/rfxtrx/translations/it.json index cbea61b08cbedd..678047af145385 100644 --- a/homeassistant/components/rfxtrx/translations/it.json +++ b/homeassistant/components/rfxtrx/translations/it.json @@ -1,17 +1,80 @@ { "config": { "abort": { - "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + "already_configured": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione.", + "cannot_connect": "Impossibile connettersi" }, "error": { + "cannot_connect": "Impossibile connettersi", "one": "uno", "other": "altri" }, "step": { "one": "uno", - "other": "altri" + "other": "altri", + "setup_network": { + "data": { + "host": "Host", + "port": "Porta" + }, + "title": "Selezionare l'indirizzo di connessione" + }, + "setup_serial": { + "data": { + "device": "Selezionare il dispositivo" + }, + "title": "Dispositivo" + }, + "setup_serial_manual_path": { + "data": { + "device": "Percorso del dispositivo USB" + }, + "title": "Percorso" + }, + "user": { + "data": { + "type": "Tipo di connessione" + }, + "title": "Selezionare il tipo di connessione" + } } }, "one": "uno", - "other": "altri" + "options": { + "error": { + "already_configured_device": "Il dispositivo \u00e8 gi\u00e0 configurato", + "invalid_event_code": "Codice evento non valido", + "invalid_input_2262_off": "Immissione non valida per il comando disattivato", + "invalid_input_2262_on": "Immissione non valida per il comando attivato", + "invalid_input_off_delay": "Immissione non valida per il ritardo di spegnimento", + "unknown": "Errore imprevisto" + }, + "step": { + "prompt_options": { + "data": { + "automatic_add": "Attivare l'aggiunta automatica", + "debug": "Attivare il debug", + "device": "Selezionare il dispositivo da configurare", + "event_code": "Inserire il codice dell'evento da aggiungere", + "remove_device": "Selezionare il dispositivo da cancellare" + }, + "title": "Opzioni Rfxtrx" + }, + "set_device_options": { + "data": { + "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": "Attivare il ritardo di spegnimento", + "replace_device": "Selezionare il dispositivo da sostituire", + "signal_repetitions": "Numero di ripetizioni del segnale" + }, + "title": "Configurare le opzioni del dispositivo" + } + } + }, + "other": "altri", + "title": "Rfxtrx" } \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/ko.json b/homeassistant/components/rfxtrx/translations/ko.json new file mode 100644 index 00000000000000..aa8512da2850af --- /dev/null +++ b/homeassistant/components/rfxtrx/translations/ko.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/lb.json b/homeassistant/components/rfxtrx/translations/lb.json index 6469543442e1ff..6e9b0fcb9c007c 100644 --- a/homeassistant/components/rfxtrx/translations/lb.json +++ b/homeassistant/components/rfxtrx/translations/lb.json @@ -1,7 +1,69 @@ { "config": { "abort": { - "already_configured": "Apparat ass scho konfigur\u00e9iert" + "already_configured": "Apparat ass scho konfigur\u00e9iert", + "cannot_connect": "Feeler beim verbannen" + }, + "error": { + "cannot_connect": "Feeler beim verbannen" + }, + "step": { + "setup_network": { + "data": { + "host": "Host", + "port": "Port" + }, + "title": "Verbindung's Adress auswielen" + }, + "setup_serial": { + "data": { + "device": "Apparat auswielen" + }, + "title": "Apparat" + }, + "setup_serial_manual_path": { + "title": "Pad" + }, + "user": { + "data": { + "type": "Typ vun der Verbindung" + }, + "title": "Typ vun der Verbindung auswielen" + } } - } + }, + "options": { + "error": { + "invalid_event_code": "Ong\u00ebltegen Evenement Code", + "invalid_input_2262_off": "Ong\u00eblteg Agab fir Kommando Aus", + "invalid_input_2262_on": "Ong\u00eblteg Agab fir Kommando Un", + "invalid_input_off_delay": "Ong\u00eblteg Agab fir Aus Verz\u00f6gerung" + }, + "step": { + "prompt_options": { + "data": { + "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" + }, + "title": "Rfxtrx Optioune" + }, + "set_device_options": { + "data": { + "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" + }, + "title": "Apparat Optioune konfigur\u00e9ieren" + } + } + }, + "title": "Rfxtrx" } \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/no.json b/homeassistant/components/rfxtrx/translations/no.json index 6ba5a1f39783f4..c89ddc0bbbf5eb 100644 --- a/homeassistant/components/rfxtrx/translations/no.json +++ b/homeassistant/components/rfxtrx/translations/no.json @@ -1,7 +1,74 @@ { "config": { "abort": { - "already_configured": "Enheten er allerede konfigurert" + "already_configured": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig.", + "cannot_connect": "Tilkobling mislyktes." + }, + "error": { + "cannot_connect": "Tilkobling mislyktes." + }, + "step": { + "setup_network": { + "data": { + "host": "Vert", + "port": "Port" + }, + "title": "Velg tilkoblingsadresse" + }, + "setup_serial": { + "data": { + "device": "Velg enhet" + }, + "title": "Enhet" + }, + "setup_serial_manual_path": { + "data": { + "device": "USB enhetsbane" + }, + "title": "Sti" + }, + "user": { + "data": { + "type": "Tilkoblingstype" + }, + "title": "Velg tilkoblingstype" + } } - } + }, + "options": { + "error": { + "already_configured_device": "Enheten er allerede konfigurert", + "invalid_event_code": "Ugyldig hendelseskode", + "invalid_input_2262_off": "Ugyldig inndata for kommando av", + "invalid_input_2262_on": "Ugyldig inndata for kommando p\u00e5", + "invalid_input_off_delay": "Ugyldig inndata for av forsinkelse", + "unknown": "Uventet feil" + }, + "step": { + "prompt_options": { + "data": { + "automatic_add": "Aktivere automatisk legg til", + "debug": "Aktiver feils\u00f8king", + "device": "Velg enhet du vil konfigurere", + "event_code": "Angi hendelseskode for \u00e5 legge til", + "remove_device": "Velg enhet du vil slette" + }, + "title": "Rfxtrx Alternativer" + }, + "set_device_options": { + "data": { + "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" + }, + "title": "Konfigurer enhetsalternativer" + } + } + }, + "title": "Rfxtrx" } \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/pl.json b/homeassistant/components/rfxtrx/translations/pl.json new file mode 100644 index 00000000000000..77fa686542f5ac --- /dev/null +++ b/homeassistant/components/rfxtrx/translations/pl.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/ru.json b/homeassistant/components/rfxtrx/translations/ru.json index 4ad85f691befa0..0926734657d23c 100644 --- a/homeassistant/components/rfxtrx/translations/ru.json +++ b/homeassistant/components/rfxtrx/translations/ru.json @@ -1,7 +1,74 @@ { "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": "\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.", + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\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." + }, + "step": { + "setup_network": { + "data": { + "host": "\u0425\u043e\u0441\u0442", + "port": "\u041f\u043e\u0440\u0442" + }, + "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0430\u0434\u0440\u0435\u0441 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f" + }, + "setup_serial": { + "data": { + "device": "\u0412\u044b\u0431\u0435\u0440\u0438\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": { + "data": { + "device": "\u041f\u0443\u0442\u044c \u043a USB-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443" + }, + "title": "\u041f\u0443\u0442\u044c" + }, + "user": { + "data": { + "type": "\u0422\u0438\u043f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f" + }, + "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0442\u0438\u043f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f" + } } - } + }, + "options": { + "error": { + "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.", + "invalid_event_code": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043a\u043e\u0434 \u0441\u043e\u0431\u044b\u0442\u0438\u044f.", + "invalid_input_2262_off": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0432\u0432\u043e\u0434 \u0434\u043b\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u0432\u044b\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f.", + "invalid_input_2262_on": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0432\u0432\u043e\u0434 \u0434\u043b\u044f \u043a\u043e\u043c\u0430\u043d\u0434\u044b \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f.", + "invalid_input_off_delay": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0432\u0432\u043e\u0434 \u0434\u043b\u044f \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u0438 \u0432\u044b\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": { + "prompt_options": { + "data": { + "automatic_add": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435", + "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", + "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" + }, + "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438" + }, + "set_device_options": { + "data": { + "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" + }, + "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" + } + } + }, + "title": "Rfxtrx" } \ No newline at end of file diff --git a/homeassistant/components/rfxtrx/translations/zh-Hant.json b/homeassistant/components/rfxtrx/translations/zh-Hant.json index 1ab3e1f720fb13..dac2a6850b0933 100644 --- a/homeassistant/components/rfxtrx/translations/zh-Hant.json +++ b/homeassistant/components/rfxtrx/translations/zh-Hant.json @@ -1,7 +1,74 @@ { "config": { "abort": { - "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + "already_configured": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002", + "cannot_connect": "\u9023\u7dda\u5931\u6557" + }, + "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557" + }, + "step": { + "setup_network": { + "data": { + "host": "\u4e3b\u6a5f\u7aef", + "port": "\u901a\u8a0a\u57e0" + }, + "title": "\u9078\u64c7\u9023\u7dda\u4f4d\u5740" + }, + "setup_serial": { + "data": { + "device": "\u9078\u64c7\u8a2d\u5099" + }, + "title": "\u8a2d\u5099" + }, + "setup_serial_manual_path": { + "data": { + "device": "USB \u8a2d\u5099\u8def\u5f91" + }, + "title": "\u8def\u5f91" + }, + "user": { + "data": { + "type": "\u9023\u7dda\u985e\u578b" + }, + "title": "\u9078\u64c7\u9023\u7dda\u985e\u578b" + } } - } + }, + "options": { + "error": { + "already_configured_device": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "invalid_event_code": "\u4e8b\u4ef6\u4ee3\u78bc\u7121\u6548", + "invalid_input_2262_off": "\u547d\u4ee4\u95dc\u9589\u8f38\u5165\u7121\u6548", + "invalid_input_2262_on": "\u547d\u4ee4\u958b\u555f\u8f38\u5165\u7121\u6548", + "invalid_input_off_delay": "\u5ef6\u8aa4\u8f38\u5165\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" + }, + "step": { + "prompt_options": { + "data": { + "automatic_add": "\u958b\u555f\u81ea\u52d5\u65b0\u589e", + "debug": "\u958b\u555f\u9664\u932f", + "device": "\u9078\u64c7\u8a2d\u5099\u4ee5\u8a2d\u5b9a", + "event_code": "\u8f38\u5165\u4e8b\u4ef6\u4ee3\u78bc\u4ee5\u65b0\u589e", + "remove_device": "\u9078\u64c7\u8a2d\u5099\u4ee5\u522a\u9664" + }, + "title": "Rfxtrx \u9078\u9805" + }, + "set_device_options": { + "data": { + "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\u8a2d\u5099\u4e8b\u4ef6", + "off_delay": "\u5ef6\u9072", + "off_delay_enabled": "\u958b\u555f\u5ef6\u9072", + "replace_device": "\u9078\u64c7\u8a2d\u5099\u4ee5\u53d6\u4ee3", + "signal_repetitions": "\u8a0a\u865f\u91cd\u8907\u6b21\u6578" + }, + "title": "\u8a2d\u5b9a\u8a2d\u5099\u9078\u9805" + } + } + }, + "title": "Rfxtrx" } \ No newline at end of file diff --git a/homeassistant/components/ring/binary_sensor.py b/homeassistant/components/ring/binary_sensor.py index fa303b9437888a..ccec9e6ad360b7 100644 --- a/homeassistant/components/ring/binary_sensor.py +++ b/homeassistant/components/ring/binary_sensor.py @@ -2,7 +2,11 @@ from datetime import datetime import logging -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_MOTION, + DEVICE_CLASS_OCCUPANCY, + BinarySensorEntity, +) from homeassistant.core import callback from . import DOMAIN @@ -12,8 +16,12 @@ # Sensor types: Name, category, device_class SENSOR_TYPES = { - "ding": ["Ding", ["doorbots", "authorized_doorbots"], "occupancy"], - "motion": ["Motion", ["doorbots", "authorized_doorbots", "stickup_cams"], "motion"], + "ding": ["Ding", ["doorbots", "authorized_doorbots"], DEVICE_CLASS_OCCUPANCY], + "motion": [ + "Motion", + ["doorbots", "authorized_doorbots", "stickup_cams"], + DEVICE_CLASS_MOTION, + ], } diff --git a/homeassistant/components/ring/sensor.py b/homeassistant/components/ring/sensor.py index 24a5cd3b6fb75b..f59c3b2e61d2a7 100644 --- a/homeassistant/components/ring/sensor.py +++ b/homeassistant/components/ring/sensor.py @@ -1,7 +1,7 @@ """This component provides HA sensor support for Ring Door Bell/Chimes.""" import logging -from homeassistant.const import PERCENTAGE +from homeassistant.const import PERCENTAGE, SIGNAL_STRENGTH_DECIBELS_MILLIWATT from homeassistant.core import callback from homeassistant.helpers.entity import Entity from homeassistant.helpers.icon import icon_for_battery_level @@ -256,7 +256,7 @@ def device_state_attributes(self): "wifi_signal_strength": [ "WiFi Signal Strength", ["chimes", "doorbots", "authorized_doorbots", "stickup_cams"], - "dBm", + SIGNAL_STRENGTH_DECIBELS_MILLIWATT, "wifi", None, "signal_strength", diff --git a/homeassistant/components/ring/strings.json b/homeassistant/components/ring/strings.json index 04b19b6661478b..c5b448ad68b2d5 100644 --- a/homeassistant/components/ring/strings.json +++ b/homeassistant/components/ring/strings.json @@ -16,11 +16,11 @@ } }, "error": { - "invalid_auth": "Invalid authentication", - "unknown": "Unexpected error" + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { - "already_configured": "Device is already configured" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/ring/translations/et.json b/homeassistant/components/ring/translations/et.json new file mode 100644 index 00000000000000..255510c130ca95 --- /dev/null +++ b/homeassistant/components/ring/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "unknown": "Ootamatu t\u00f5rge" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/ring/translations/pl.json b/homeassistant/components/ring/translations/pl.json index 96aa7d391591e8..b095647d06cefa 100644 --- a/homeassistant/components/ring/translations/pl.json +++ b/homeassistant/components/ring/translations/pl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" }, "error": { - "invalid_auth": "Niepoprawne uwierzytelnienie.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "2fa": { diff --git a/homeassistant/components/ring/translations/ru.json b/homeassistant/components/ring/translations/ru.json index aa3b383bbce1c4..fb8c22c39af644 100644 --- a/homeassistant/components/ring/translations/ru.json +++ b/homeassistant/components/ring/translations/ru.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", diff --git a/homeassistant/components/risco/manifest.json b/homeassistant/components/risco/manifest.json index 80b132b0fb2618..7f13af252f3d42 100644 --- a/homeassistant/components/risco/manifest.json +++ b/homeassistant/components/risco/manifest.json @@ -4,7 +4,7 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/risco", "requirements": [ - "pyrisco==0.3.0" + "pyrisco==0.3.1" ], "codeowners": [ "@OnFreund" diff --git a/homeassistant/components/risco/strings.json b/homeassistant/components/risco/strings.json index dc6031e0ad31e2..d98273bf1854c5 100644 --- a/homeassistant/components/risco/strings.json +++ b/homeassistant/components/risco/strings.json @@ -5,7 +5,7 @@ "data": { "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]", - "pin": "Pin code" + "pin": "[%key:common::config_flow::data::pin%]" } } }, diff --git a/homeassistant/components/risco/translations/de.json b/homeassistant/components/risco/translations/de.json new file mode 100644 index 00000000000000..b0bae40bf4e6ec --- /dev/null +++ b/homeassistant/components/risco/translations/de.json @@ -0,0 +1,24 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Passwort", + "username": "Benutzername" + } + } + } + }, + "options": { + "step": { + "risco_to_ha": { + "data": { + "A": "Gruppe A", + "B": "Gruppe B", + "C": "Gruppe C", + "D": "Gruppe D" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/risco/translations/el.json b/homeassistant/components/risco/translations/el.json new file mode 100644 index 00000000000000..c38cfc72cc19d0 --- /dev/null +++ b/homeassistant/components/risco/translations/el.json @@ -0,0 +1,47 @@ +{ + "config": { + "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b1\u03c5\u03b8\u03b5\u03bd\u03c4\u03b9\u03ba\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7", + "unknown": "\u039c\u03b7 \u03b1\u03bd\u03b1\u03bc\u03b5\u03bd\u03cc\u03bc\u03b5\u03bd\u03bf \u03c3\u03c6\u03ac\u03bb\u03bc\u03b1" + }, + "step": { + "user": { + "data": { + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "pin": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN" + } + } + } + }, + "options": { + "step": { + "ha_to_risco": { + "data": { + "armed_away": "\u039f\u03c0\u03bb\u03b9\u03c3\u03bc\u03cc\u03c2 \u0395\u03ba\u03c4\u03cc\u03c2", + "armed_home": "\u039f\u03c0\u03bb\u03b9\u03c3\u03bc\u03cc\u03c2 \u0395\u03bd\u03c4\u03cc\u03c2", + "armed_night": "\u039f\u03c0\u03bb\u03b9\u03c3\u03bc\u03cc\u03c2 \u03bd\u03cd\u03c7\u03c4\u03b1\u03c2" + }, + "description": "\u0395\u03c0\u03b9\u03bb\u03ad\u03be\u03c4\u03b5 \u03c3\u03b5 \u03c0\u03bf\u03b9\u03b1 \u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7 \u03b8\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03c3\u03c5\u03bd\u03b1\u03b3\u03b5\u03c1\u03bc\u03cc Risco \u03ba\u03b1\u03c4\u03ac \u03c4\u03bf\u03bd \u03bf\u03c0\u03bb\u03b9\u03c3\u03bc\u03cc \u03c4\u03bf\u03c5 \u03c3\u03c5\u03bd\u03b1\u03b3\u03b5\u03c1\u03bc\u03bf\u03cd Home Assistant", + "title": "\u0391\u03bd\u03c4\u03b9\u03c3\u03c4\u03bf\u03af\u03c7\u03b9\u03c3\u03b7 \u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03ac\u03c3\u03b5\u03c9\u03bd Home Assistant \u03c3\u03b5 \u03ba\u03b1\u03c4\u03b1\u03c3\u03c4\u03ac\u03c3\u03b5\u03b9\u03c2 Risco" + }, + "init": { + "data": { + "code_arm_required": "\u039d\u03b1 \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN \u03b3\u03b9\u03b1 \u03bf\u03c0\u03bb\u03b9\u03c3\u03bc\u03cc", + "code_disarm_required": "\u039d\u03b1 \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 PIN \u03b3\u03b9\u03b1 \u03b1\u03c6\u03bf\u03c0\u03bb\u03b9\u03c3\u03bc\u03cc", + "scan_interval": "\u03a0\u03cc\u03c3\u03bf \u03c3\u03c5\u03c7\u03bd\u03ac \u03bd\u03b1 \u03ba\u03ac\u03bd\u03b5\u03c4\u03b5 \u03bb\u03ae\u03c8\u03b5\u03b9\u03c2 \u03b1\u03c0\u03cc \u03c4\u03bf Risco (\u03c3\u03b5 \u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1)" + }, + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b5\u03c0\u03b9\u03bb\u03bf\u03b3\u03ce\u03bd" + }, + "risco_to_ha": { + "data": { + "A": "\u039f\u03bc\u03ac\u03b4\u03b1 \u0391", + "B": "\u039f\u03bc\u03ac\u03b4\u03b1 \u0392", + "C": "\u039f\u03bc\u03ac\u03b4\u03b1 \u0393", + "D": "\u039f\u03bc\u03ac\u03b4\u03b1 \u0394", + "arm": "\u039f\u03c0\u03bb\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf\u03b9 (\u0395\u039a\u03a4\u039f\u03a3)", + "partial_arm": "\u039c\u03b5\u03c1\u03b9\u03ba\u03ce\u03c2 \u03bf\u03c0\u03bb\u03b9\u03c3\u03bc\u03ad\u03bd\u03bf\u03c2 (\u0395\u039d\u03a4\u039f\u03a3)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/risco/translations/hu.json b/homeassistant/components/risco/translations/hu.json new file mode 100644 index 00000000000000..3b2d79a34a77e2 --- /dev/null +++ b/homeassistant/components/risco/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/risco/translations/ko.json b/homeassistant/components/risco/translations/ko.json new file mode 100644 index 00000000000000..37d9a61307b062 --- /dev/null +++ b/homeassistant/components/risco/translations/ko.json @@ -0,0 +1,43 @@ +{ + "config": { + "abort": { + "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + }, + "error": { + "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", + "invalid_auth": "\uc798\ubabb\ub41c \uc778\uc99d", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec" + } + }, + "options": { + "step": { + "ha_to_risco": { + "data": { + "armed_away": "\uacbd\ube44\uc911(\uc678\ucd9c)", + "armed_custom_bypass": "\uacbd\ube44\uc911(\uc0ac\uc6a9\uc790 \uc6b0\ud68c)", + "armed_home": "\uc9d1\uc548 \uacbd\ube44\uc911", + "armed_night": "\uc57c\uac04 \uacbd\ube44\uc911" + }, + "description": "Home Assistant \uc54c\ub78c\uc744 \ud65c\uc131\ud654 \ud560 \ub54c Risco \uc54c\ub78c\uc758 \uc0c1\ud0dc\ub97c \uc120\ud0dd\ud558\uc2ed\uc2dc\uc624.", + "title": "Home Assistant \uc0c1\ud0dc\ub97c Risco \uc0c1\ud0dc\ub85c \ub9e4\ud551" + }, + "init": { + "data": { + "scan_interval": "Risco\ub97c \ud3f4\ub9c1\ud558\ub294 \ube48\ub3c4 (\ucd08)" + } + }, + "risco_to_ha": { + "data": { + "A": "\uadf8\ub8f9 A", + "B": "\uadf8\ub8f9 B", + "C": "\uadf8\ub8f9 C", + "D": "\uadf8\ub8f9 D", + "arm": "\uacbd\ube44\uc911(\uc678\ucd9c)", + "partial_arm": "\ubd80\ubd84 \uacbd\ube44 \uc124\uc815 (\uc7ac\uc2e4)" + }, + "description": "Risco\uc5d0\uc11c\ubcf4\uace0\ud558\ub294 \ubaa8\ub4e0 \uc0c1\ud0dc\uc5d0 \ub300\ud574 Home Assistant \uc54c\ub78c\uc774 \ubcf4\uace0 \ud560 \uc0c1\ud0dc\ub97c \uc120\ud0dd\ud569\ub2c8\ub2e4.", + "title": "Risco \uc0c1\ud0dc\ub97c \ud648 \uc5b4\uc2dc\uc2a4\ud134\ud2b8 \uc0c1\ud0dc\uc5d0 \ub9e4\ud551" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/risco/translations/lb.json b/homeassistant/components/risco/translations/lb.json index 933af1f4a38c55..985112bf4461f4 100644 --- a/homeassistant/components/risco/translations/lb.json +++ b/homeassistant/components/risco/translations/lb.json @@ -20,12 +20,27 @@ }, "options": { "step": { + "ha_to_risco": { + "data": { + "armed_away": "Aktiv\u00e9iert \u00cbnnerwee", + "armed_home": "Aktiv\u00e9iert Doheem", + "armed_night": "Aktiv\u00e9iert Nuecht" + } + }, "init": { "data": { "code_arm_required": "Pin Code n\u00e9ideg fir unzeschalten", "code_disarm_required": "Pin Code n\u00e9ideg fir auszeschalten" }, "title": "Optioune konfigur\u00e9ieren" + }, + "risco_to_ha": { + "data": { + "A": "Grupp A", + "B": "Grupp B", + "C": "Grupp C", + "D": "Grupp D" + } } } } diff --git a/homeassistant/components/risco/translations/nl.json b/homeassistant/components/risco/translations/nl.json new file mode 100644 index 00000000000000..614a896c3f8abe --- /dev/null +++ b/homeassistant/components/risco/translations/nl.json @@ -0,0 +1,39 @@ +{ + "config": { + "step": { + "user": { + "data": { + "pin": "Pincode", + "username": "Gebruikersnaam" + } + } + } + }, + "options": { + "step": { + "ha_to_risco": { + "data": { + "armed_away": "Ingeschakeld weg", + "armed_custom_bypass": "Ingeschakeld met overbrugging(en)", + "armed_home": "Ingeschakeld thuis", + "armed_night": "Ingeschakeld nacht" + } + }, + "init": { + "data": { + "code_arm_required": "Pincode vereist om in te schakelen", + "code_disarm_required": "Pincode vereist om uit te schakelen" + }, + "title": "Configureer opties" + }, + "risco_to_ha": { + "data": { + "A": "Groep A", + "B": "Groep B", + "C": "Groep C", + "D": "Groep D" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/risco/translations/pl.json b/homeassistant/components/risco/translations/pl.json index 9859923d31e6a1..92deb2da70a30a 100644 --- a/homeassistant/components/risco/translations/pl.json +++ b/homeassistant/components/risco/translations/pl.json @@ -1,20 +1,55 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" }, "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", - "invalid_auth": "Niepoprawne uwierzytelnienie.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "user": { "data": { "password": "Has\u0142o", + "pin": "Kod PIN", "username": "Nazwa u\u017cytkownika" } } } + }, + "options": { + "step": { + "ha_to_risco": { + "data": { + "armed_away": "Uzbrojony (pod nieobecno\u015b\u0107)", + "armed_custom_bypass": "Uzbrojony (cz\u0119\u015bciowo)", + "armed_home": "Uzbrojony (obecny)", + "armed_night": "Uzbrojony (noc)" + }, + "description": "Wybierz stan, w kt\u00f3rym chcesz ustawi\u0107 alarm Risco podczas uzbrajania alarmu w Home Assistant", + "title": "Mapuj stany Home Assistant do stan\u00f3w alarmu Risco" + }, + "init": { + "data": { + "code_arm_required": "Wymagaj kodu PIN do uzbrojenia", + "code_disarm_required": "Wymagaj kodu PIN do rozbrojenia", + "scan_interval": "Cz\u0119stotliwo\u015b\u0107 od\u015bwie\u017cania (w sekundach)" + }, + "title": "Opcje" + }, + "risco_to_ha": { + "data": { + "A": "Grupa A", + "B": "Grupa B", + "C": "Grupa C", + "D": "Grupa D", + "arm": "Uzbrojony (pod nieobecno\u015b\u0107)", + "partial_arm": "Uzbrojony (obecny)" + }, + "description": "Wybierz stan, kt\u00f3ry b\u0119dzie raportowa\u0142 alarm Home Assistant, dla ka\u017cdego stanu zg\u0142oszonego przez Risco", + "title": "Mapuj stany Risco do stan\u00f3w Home Assistant" + } + } } } \ No newline at end of file diff --git a/homeassistant/components/roku/browse_media.py b/homeassistant/components/roku/browse_media.py index f6f8c8976f1333..b5be3e99d9a094 100644 --- a/homeassistant/components/roku/browse_media.py +++ b/homeassistant/components/roku/browse_media.py @@ -13,8 +13,13 @@ CONTENT_TYPE_MEDIA_CLASS = { MEDIA_TYPE_APP: MEDIA_CLASS_APP, - MEDIA_TYPE_APPS: MEDIA_CLASS_DIRECTORY, + MEDIA_TYPE_APPS: MEDIA_CLASS_APP, MEDIA_TYPE_CHANNEL: MEDIA_CLASS_CHANNEL, + MEDIA_TYPE_CHANNELS: MEDIA_CLASS_CHANNEL, +} + +CONTAINER_TYPES_SPECIFIC_MEDIA_CLASS = { + MEDIA_TYPE_APPS: MEDIA_CLASS_DIRECTORY, MEDIA_TYPE_CHANNELS: MEDIA_CLASS_DIRECTORY, } @@ -37,6 +42,7 @@ def build_item_response(coordinator, payload): thumbnail = None title = None media = None + children_media_class = None if search_type == MEDIA_TYPE_APPS: title = "Apps" @@ -44,6 +50,7 @@ def build_item_response(coordinator, payload): {"app_id": item.app_id, "title": item.name, "type": MEDIA_TYPE_APP} for item in coordinator.data.apps ] + children_media_class = MEDIA_CLASS_APP elif search_type == MEDIA_TYPE_CHANNELS: title = "Channels" media = [ @@ -54,18 +61,22 @@ def build_item_response(coordinator, payload): } for item in coordinator.data.channels ] + children_media_class = MEDIA_CLASS_CHANNEL if media is None: return None return BrowseMedia( - media_class=MEDIA_CLASS_DIRECTORY, + media_class=CONTAINER_TYPES_SPECIFIC_MEDIA_CLASS.get( + search_type, MEDIA_CLASS_DIRECTORY + ), media_content_id=search_id, media_content_type=search_type, title=title, can_play=search_type in PLAYABLE_MEDIA_TYPES and search_id, can_expand=True, children=[item_payload(item, coordinator) for item in media], + children_media_class=children_media_class, thumbnail=thumbnail, ) @@ -148,7 +159,5 @@ def library_payload(coordinator): for child in library_info.children ): library_info.children_media_class = MEDIA_CLASS_CHANNEL - else: - library_info.children_media_class = MEDIA_CLASS_DIRECTORY return library_info diff --git a/homeassistant/components/roku/translations/pl.json b/homeassistant/components/roku/translations/pl.json index 7ca0148ce35c1f..21c969876e7720 100644 --- a/homeassistant/components/roku/translations/pl.json +++ b/homeassistant/components/roku/translations/pl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia." + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, "flow_title": "Roku: {name}", "step": { diff --git a/homeassistant/components/roomba/strings.json b/homeassistant/components/roomba/strings.json index d22a6e0509cf90..cbe7c06ae36fec 100644 --- a/homeassistant/components/roomba/strings.json +++ b/homeassistant/components/roomba/strings.json @@ -14,7 +14,7 @@ } }, "error": { - "cannot_connect": "Failed to connect, please try again" + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" } }, "options": { @@ -27,4 +27,4 @@ } } } -} \ No newline at end of file +} diff --git a/homeassistant/components/roomba/translations/ca.json b/homeassistant/components/roomba/translations/ca.json index 3d7efbe53ae43b..af358678144949 100644 --- a/homeassistant/components/roomba/translations/ca.json +++ b/homeassistant/components/roomba/translations/ca.json @@ -1,7 +1,7 @@ { "config": { "error": { - "cannot_connect": "No s'ha pogut connectar, torna-ho a provar" + "cannot_connect": "Ha fallat la connexi\u00f3" }, "step": { "user": { diff --git a/homeassistant/components/roomba/translations/en.json b/homeassistant/components/roomba/translations/en.json index 677ea5f4749bbb..b6222f2adf8a34 100644 --- a/homeassistant/components/roomba/translations/en.json +++ b/homeassistant/components/roomba/translations/en.json @@ -1,7 +1,7 @@ { "config": { "error": { - "cannot_connect": "Failed to connect, please try again" + "cannot_connect": "Failed to connect" }, "step": { "user": { diff --git a/homeassistant/components/roomba/translations/fr.json b/homeassistant/components/roomba/translations/fr.json index a8ff72b2ed52c6..1ec97dd3842e08 100644 --- a/homeassistant/components/roomba/translations/fr.json +++ b/homeassistant/components/roomba/translations/fr.json @@ -12,6 +12,7 @@ "host": "Nom d'h\u00f4te ou adresse IP", "password": "Mot de passe" }, + "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 \u00e0 l'appareil" } } diff --git a/homeassistant/components/roomba/translations/it.json b/homeassistant/components/roomba/translations/it.json index babd2082b3cbdb..d109aa8bcc099c 100644 --- a/homeassistant/components/roomba/translations/it.json +++ b/homeassistant/components/roomba/translations/it.json @@ -1,7 +1,7 @@ { "config": { "error": { - "cannot_connect": "Impossibile connettersi, si prega di riprovare" + "cannot_connect": "Impossibile connettersi" }, "step": { "user": { diff --git a/homeassistant/components/roomba/translations/no.json b/homeassistant/components/roomba/translations/no.json index b780f3e718b2c1..6dccb358dd27a8 100644 --- a/homeassistant/components/roomba/translations/no.json +++ b/homeassistant/components/roomba/translations/no.json @@ -1,7 +1,7 @@ { "config": { "error": { - "cannot_connect": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen" + "cannot_connect": "Tilkobling mislyktes." }, "step": { "user": { diff --git a/homeassistant/components/roomba/translations/ru.json b/homeassistant/components/roomba/translations/ru.json index 12217a1bc3f0b7..ee1192f69ec63c 100644 --- a/homeassistant/components/roomba/translations/ru.json +++ b/homeassistant/components/roomba/translations/ru.json @@ -1,7 +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, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437." + "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/roomba/translations/zh-Hant.json b/homeassistant/components/roomba/translations/zh-Hant.json index 5e51af5efb5237..794c67454fbd27 100644 --- a/homeassistant/components/roomba/translations/zh-Hant.json +++ b/homeassistant/components/roomba/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21" + "cannot_connect": "\u9023\u7dda\u5931\u6557" }, "step": { "user": { diff --git a/homeassistant/components/roon/translations/de.json b/homeassistant/components/roon/translations/de.json new file mode 100644 index 00000000000000..b73dc4d64444d1 --- /dev/null +++ b/homeassistant/components/roon/translations/de.json @@ -0,0 +1,3 @@ +{ + "title": "Roon" +} \ No newline at end of file diff --git a/homeassistant/components/roon/translations/el.json b/homeassistant/components/roon/translations/el.json new file mode 100644 index 00000000000000..4a17e5f128893a --- /dev/null +++ b/homeassistant/components/roon/translations/el.json @@ -0,0 +1,18 @@ +{ + "config": { + "error": { + "duplicate_entry": "\u0391\u03c5\u03c4\u03cc\u03c2 \u03bf \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2 \u03ad\u03c7\u03b5\u03b9 \u03ae\u03b4\u03b7 \u03c0\u03c1\u03bf\u03c3\u03c4\u03b5\u03b8\u03b5\u03af." + }, + "step": { + "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": { + "description": "\u0395\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 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae Roon.", + "title": "\u0394\u03b9\u03b1\u03bc\u03cc\u03c1\u03c6\u03c9\u03c3\u03b7 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae Roon" + } + } + }, + "title": "Roon" +} \ No newline at end of file diff --git a/homeassistant/components/roon/translations/hu.json b/homeassistant/components/roon/translations/hu.json new file mode 100644 index 00000000000000..3b2d79a34a77e2 --- /dev/null +++ b/homeassistant/components/roon/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/roon/translations/ko.json b/homeassistant/components/roon/translations/ko.json index 50c22e9e2560f3..cdffd6e88aefbc 100644 --- a/homeassistant/components/roon/translations/ko.json +++ b/homeassistant/components/roon/translations/ko.json @@ -4,8 +4,22 @@ "already_configured": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4" }, "error": { + "duplicate_entry": "\ud574\ub2f9 \ud638\uc2a4\ud2b8\ub294 \uc774\ubbf8 \ucd94\uac00\ub418\uc5c8\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": { + "link": { + "description": "Roon\uc5d0\uc11c \ud648 \uc5b4\uc2dc\uc2a4\ud134\ud2b8\ub97c \uc778\uc99d\ud574\uc57c\ud569\ub2c8\ub2e4. \uc81c\ucd9c\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 HomeAssistant\ub97c \ud65c\uc131\ud654\ud569\ub2c8\ub2e4.", + "title": "Roon\uc5d0\uc11c HomeAssistant \uc778\uc99d" + }, + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8" + }, + "description": "Roon \uc11c\ubc84 Hostname \ub610\ub294 IP\ub97c \uc785\ub825\ud558\uc2ed\uc2dc\uc624.", + "title": "Roon \uc11c\ubc84 \uad6c\uc131" + } } } } \ No newline at end of file diff --git a/homeassistant/components/roon/translations/pl.json b/homeassistant/components/roon/translations/pl.json new file mode 100644 index 00000000000000..15fc480be6787b --- /dev/null +++ b/homeassistant/components/roon/translations/pl.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" + }, + "error": { + "duplicate_entry": "Ten adres hosta zosta\u0142 ju\u017c dodany.", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "link": { + "description": "Musisz autoryzowa\u0107 Home Assistant w Roon. Po klikni\u0119ciu przycisku \"Prze\u015blij\", 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": { + "description": "Wprowad\u017a nazw\u0119 hosta lub adres IP swojego serwera Roon.", + "title": "Konfiguracja serwera Roon" + } + } + }, + "title": "Roon" +} \ No newline at end of file diff --git a/homeassistant/components/rpi_power/__init__.py b/homeassistant/components/rpi_power/__init__.py new file mode 100644 index 00000000000000..993d0b313c0a22 --- /dev/null +++ b/homeassistant/components/rpi_power/__init__.py @@ -0,0 +1,21 @@ +"""The Raspberry Pi Power Supply Checker integration.""" +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + + +async def async_setup(hass: HomeAssistant, config: dict): + """Set up the Raspberry Pi Power Supply Checker component.""" + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): + """Set up Raspberry Pi Power Supply Checker from a config entry.""" + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, "binary_sensor") + ) + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): + """Unload a config entry.""" + return await hass.config_entries.async_forward_entry_unload(entry, "binary_sensor") diff --git a/homeassistant/components/rpi_power/binary_sensor.py b/homeassistant/components/rpi_power/binary_sensor.py new file mode 100644 index 00000000000000..79ef36e891a868 --- /dev/null +++ b/homeassistant/components/rpi_power/binary_sensor.py @@ -0,0 +1,73 @@ +""" +A sensor platform which detects underruns and capped status from the official Raspberry Pi Kernel. + +Minimal Kernel needed is 4.14+ +""" +import logging + +from rpi_bad_power import new_under_voltage + +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_PROBLEM, + BinarySensorEntity, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +_LOGGER = logging.getLogger(__name__) + +DESCRIPTION_NORMALIZED = "Voltage normalized. Everything is working as intended." +DESCRIPTION_UNDER_VOLTAGE = "Under-voltage was detected. Consider getting a uninterruptible power supply for your Raspberry Pi." + + +async def async_setup_entry( + hass: HomeAssistant, config_entry: ConfigEntry, async_add_entities +): + """Set up rpi_power binary sensor.""" + under_voltage = await hass.async_add_executor_job(new_under_voltage) + async_add_entities([RaspberryChargerBinarySensor(under_voltage)], True) + + +class RaspberryChargerBinarySensor(BinarySensorEntity): + """Binary sensor representing the rpi power status.""" + + def __init__(self, under_voltage): + """Initialize the binary sensor.""" + self._under_voltage = under_voltage + self._is_on = None + self._last_is_on = False + + def update(self): + """Update the state.""" + self._is_on = self._under_voltage.get() + if self._is_on != self._last_is_on: + if self._is_on: + _LOGGER.warning(DESCRIPTION_UNDER_VOLTAGE) + else: + _LOGGER.info(DESCRIPTION_NORMALIZED) + self._last_is_on = self._is_on + + @property + def unique_id(self): + """Return the unique id of the sensor.""" + return "rpi_power" # only one sensor possible + + @property + def name(self): + """Return the name of the sensor.""" + return "RPi Power status" + + @property + def is_on(self): + """Return if there is a problem detected.""" + return self._is_on + + @property + def icon(self): + """Return the icon of the sensor.""" + return "mdi:raspberry-pi" + + @property + def device_class(self): + """Return the class of this device.""" + return DEVICE_CLASS_PROBLEM diff --git a/homeassistant/components/rpi_power/config_flow.py b/homeassistant/components/rpi_power/config_flow.py new file mode 100644 index 00000000000000..9924ebf0440cf2 --- /dev/null +++ b/homeassistant/components/rpi_power/config_flow.py @@ -0,0 +1,41 @@ +"""Config flow for Raspberry Pi Power Supply Checker.""" +from typing import Any, Dict, Optional + +from rpi_bad_power import new_under_voltage + +from homeassistant import config_entries +from homeassistant.core import HomeAssistant +from homeassistant.helpers.config_entry_flow import DiscoveryFlowHandler + +from .const import DOMAIN + + +async def _async_supported(hass: HomeAssistant) -> bool: + """Return if the system supports under voltage detection.""" + under_voltage = await hass.async_add_executor_job(new_under_voltage) + return under_voltage is not None + + +class RPiPowerFlow(DiscoveryFlowHandler, domain=DOMAIN): + """Discovery flow handler.""" + + VERSION = 1 + + def __init__(self) -> None: + """Set up config flow.""" + super().__init__( + DOMAIN, + "Raspberry Pi Power Supply Checker", + _async_supported, + config_entries.CONN_CLASS_LOCAL_POLL, + ) + + async def async_step_onboarding( + self, data: Optional[Dict[str, Any]] = None + ) -> Dict[str, Any]: + """Handle a flow initialized by onboarding.""" + has_devices = await self._discovery_function(self.hass) + + if not has_devices: + return self.async_abort(reason="no_devices_found") + return self.async_create_entry(title=self._title, data={}) diff --git a/homeassistant/components/rpi_power/const.py b/homeassistant/components/rpi_power/const.py new file mode 100644 index 00000000000000..98cfc438903759 --- /dev/null +++ b/homeassistant/components/rpi_power/const.py @@ -0,0 +1,3 @@ +"""Constants for Raspberry Pi Power Supply Checker.""" + +DOMAIN = "rpi_power" diff --git a/homeassistant/components/rpi_power/manifest.json b/homeassistant/components/rpi_power/manifest.json new file mode 100644 index 00000000000000..e0d2a6424e8f09 --- /dev/null +++ b/homeassistant/components/rpi_power/manifest.json @@ -0,0 +1,13 @@ +{ + "domain": "rpi_power", + "name": "Raspberry Pi Power Supply Checker", + "documentation": "https://www.home-assistant.io/integrations/rpi_power", + "codeowners": [ + "@shenxn", + "@swetoast" + ], + "requirements": [ + "rpi-bad-power==0.0.3" + ], + "config_flow": true +} diff --git a/homeassistant/components/rpi_power/strings.json b/homeassistant/components/rpi_power/strings.json new file mode 100644 index 00000000000000..a9cd6c2d907a61 --- /dev/null +++ b/homeassistant/components/rpi_power/strings.json @@ -0,0 +1,14 @@ +{ + "title": "Raspberry Pi Power Supply Checker", + "config": { + "step": { + "confirm": { + "description": "[%key:common::config_flow::description::confirm_setup%]" + } + }, + "abort": { + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", + "no_devices_found": "Can't find the system class needed for this component, make sure that your kernel is recent and the hardware is supported" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/rpi_power/translations/ca.json b/homeassistant/components/rpi_power/translations/ca.json new file mode 100644 index 00000000000000..c53fa570b7eccf --- /dev/null +++ b/homeassistant/components/rpi_power/translations/ca.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "no_devices_found": "No s'ha trobat la classe de sistema necess\u00e0ria per a aquest component, assegura't que el nucli sigui recent (versi\u00f3 del kernel) i que el maquinari sigui compatible", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." + }, + "step": { + "confirm": { + "description": "Vols comen\u00e7ar la configuraci\u00f3?" + } + } + }, + "title": "Comprovador de font d'alimentaci\u00f3 de Raspberry Pi" +} \ No newline at end of file diff --git a/homeassistant/components/rpi_power/translations/cs.json b/homeassistant/components/rpi_power/translations/cs.json new file mode 100644 index 00000000000000..b60cb60f985ead --- /dev/null +++ b/homeassistant/components/rpi_power/translations/cs.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "no_devices_found": "Nelze naj\u00edt t\u0159\u00eddu syst\u00e9mu pot\u0159ebnou pro tuto komponentu, ujist\u011bte se, \u017ee je va\u0161e j\u00e1dro aktu\u00e1ln\u00ed a hardware podporov\u00e1n", + "single_instance_allowed": "Ji\u017e je nakonfigurov\u00e1no.Je mo\u017en\u00e1 pouze jedna konfigurace." + }, + "step": { + "confirm": { + "description": "Chcete zah\u00e1jit nastaven\u00ed?" + } + } + }, + "title": "Kontrola nap\u00e1jec\u00edho zdroje Raspberry Pi" +} \ No newline at end of file diff --git a/homeassistant/components/rpi_power/translations/el.json b/homeassistant/components/rpi_power/translations/el.json new file mode 100644 index 00000000000000..89042f53e884b2 --- /dev/null +++ b/homeassistant/components/rpi_power/translations/el.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "no_devices_found": "\u0394\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 \u03b4\u03c5\u03bd\u03b1\u03c4\u03ae \u03b7 \u03b5\u03cd\u03c1\u03b5\u03c3\u03b7 \u03c4\u03b7\u03c2 \u03ba\u03bb\u03ac\u03c3\u03b7\u03c2 \u03c3\u03c5\u03c3\u03c4\u03ae\u03bc\u03b1\u03c4\u03bf\u03c2 \u03c0\u03bf\u03c5 \u03b1\u03c0\u03b1\u03b9\u03c4\u03b5\u03af\u03c4\u03b1\u03b9 \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf \u03c3\u03c4\u03bf\u03b9\u03c7\u03b5\u03af\u03bf, \u03b2\u03b5\u03b2\u03b1\u03b9\u03c9\u03b8\u03b5\u03af\u03c4\u03b5 \u03cc\u03c4\u03b9 \u03bf \u03c0\u03c5\u03c1\u03ae\u03bd\u03b1\u03c2 \u03c3\u03b1\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03c0\u03c1\u03cc\u03c3\u03c6\u03b1\u03c4\u03bf\u03c2 \u03ba\u03b1\u03b9 \u03cc\u03c4\u03b9 \u03c4\u03bf \u03c5\u03bb\u03b9\u03ba\u03cc \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9", + "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": { + "confirm": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03be\u03b5\u03ba\u03b9\u03bd\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03b7\u03bd \u03b5\u03b3\u03ba\u03b1\u03c4\u03ac\u03c3\u03c4\u03b1\u03c3\u03b7;" + } + } + }, + "title": "\u0388\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03c1\u03bf\u03c6\u03bf\u03b4\u03bf\u03c3\u03af\u03b1\u03c2 Raspberry Pi" +} \ No newline at end of file diff --git a/homeassistant/components/rpi_power/translations/en.json b/homeassistant/components/rpi_power/translations/en.json new file mode 100644 index 00000000000000..6995190979acb4 --- /dev/null +++ b/homeassistant/components/rpi_power/translations/en.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "no_devices_found": "Can't find the system class needed for this component, make sure that your kernel is recent and the hardware is supported", + "single_instance_allowed": "Already configured. Only a single configuration possible." + }, + "step": { + "confirm": { + "description": "Do you want to start set up?" + } + } + }, + "title": "Raspberry Pi Power Supply Checker" +} \ No newline at end of file diff --git a/homeassistant/components/rpi_power/translations/es.json b/homeassistant/components/rpi_power/translations/es.json new file mode 100644 index 00000000000000..215b15014abc9d --- /dev/null +++ b/homeassistant/components/rpi_power/translations/es.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "no_devices_found": "No se puede encontrar la clase de sistema necesaria para este componente, aseg\u00farate de que tu kernel es reciente y el hardware es compatible", + "single_instance_allowed": "Ya est\u00e1 configurado. S\u00f3lo es posible una \u00fanica configuraci\u00f3n." + }, + "step": { + "confirm": { + "description": "\u00bfQuieres iniciar la configuraci\u00f3n?" + } + } + }, + "title": "Comprobador de fuente de alimentaci\u00f3n de Raspberry Pi" +} \ No newline at end of file diff --git a/homeassistant/components/rpi_power/translations/et.json b/homeassistant/components/rpi_power/translations/et.json new file mode 100644 index 00000000000000..350e09ca86ff27 --- /dev/null +++ b/homeassistant/components/rpi_power/translations/et.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "no_devices_found": "Ei leia selle komponendi jaoks vajalikku s\u00fcsteemiklassi. Veenduge, et teie kernel on v\u00e4rske ja riistvara on toetatud", + "single_instance_allowed": "Seadistused on juba tehtud. Korraga saab olla ainult \u00fcks konfiguratsioon." + }, + "step": { + "confirm": { + "description": "Kas alustame paigaldusega?" + } + } + }, + "title": "Raspberry Pi toiteallika kontroll" +} \ No newline at end of file diff --git a/homeassistant/components/rpi_power/translations/fr.json b/homeassistant/components/rpi_power/translations/fr.json new file mode 100644 index 00000000000000..7e4fd715ee0e1d --- /dev/null +++ b/homeassistant/components/rpi_power/translations/fr.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "no_devices_found": "Impossible de trouver la classe syst\u00e8me n\u00e9cessaire pour ce composant, assurez-vous que votre noyau est r\u00e9cent et que le mat\u00e9riel est pris en charge", + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." + }, + "step": { + "confirm": { + "description": "Voulez-vous commencer la configuration ?" + } + } + }, + "title": "V\u00e9rificateur d'alimentation Raspberry Pi" +} \ No newline at end of file diff --git a/homeassistant/components/rpi_power/translations/it.json b/homeassistant/components/rpi_power/translations/it.json new file mode 100644 index 00000000000000..4e7a14d05e844b --- /dev/null +++ b/homeassistant/components/rpi_power/translations/it.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "no_devices_found": "Impossibile trovare la classe di sistema necessaria per questo componente, assicurarsi che il kernel sia recente e che l'hardware sia supportato", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." + }, + "step": { + "confirm": { + "description": "Vuoi iniziare la configurazione?" + } + } + }, + "title": "Controllo alimentazione Raspberry Pi" +} \ No newline at end of file diff --git a/homeassistant/components/rpi_power/translations/ko.json b/homeassistant/components/rpi_power/translations/ko.json new file mode 100644 index 00000000000000..022718332203b2 --- /dev/null +++ b/homeassistant/components/rpi_power/translations/ko.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "no_devices_found": "\uc774 \uad6c\uc131 \uc694\uc18c\uc5d0 \ud544\uc694\ud55c \uc2dc\uc2a4\ud15c \ud074\ub798\uc2a4\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ucee4\ub110\uc774 \ucd5c\uc2e0\uc774\uace0 \ud558\ub4dc\uc6e8\uc5b4\uac00 \uc9c0\uc6d0\ub418\ub294\uc9c0 \ud655\uc778\ud558\uc2ed\uc2dc\uc624.", + "single_instance_allowed": "\uc774\ubbf8 \uc124\uc815\ub418\uc5b4 \uc788\uc74c. \ud558\ub098\uc758 \uc124\uc815\ub9cc \uac00\ub2a5\ud568." + }, + "step": { + "confirm": { + "description": "\uc124\uc815\uc744 \uc2dc\uc791\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?" + } + } + }, + "title": "\ub77c\uc988\ubca0\ub9ac\ud30c\uc774 \uc804\uc6d0 \uacf5\uae09 \uc7a5\uce58 \uac80\uc0ac\uae30" +} \ No newline at end of file diff --git a/homeassistant/components/rpi_power/translations/lb.json b/homeassistant/components/rpi_power/translations/lb.json new file mode 100644 index 00000000000000..1b4c77f4fda928 --- /dev/null +++ b/homeassistant/components/rpi_power/translations/lb.json @@ -0,0 +1,8 @@ +{ + "config": { + "abort": { + "no_devices_found": "Kann d\u00e9i Systemklass fir d\u00ebs noutwendeg Komponent net fannen, stell s\u00e9cher dass de Kernel rezent ass an d'Hardware \u00ebnnerst\u00ebtzt g\u00ebtt." + } + }, + "title": "Raspberry Pi Netzdeel Checker" +} \ No newline at end of file diff --git a/homeassistant/components/rpi_power/translations/nl.json b/homeassistant/components/rpi_power/translations/nl.json new file mode 100644 index 00000000000000..a18ff63733ea1c --- /dev/null +++ b/homeassistant/components/rpi_power/translations/nl.json @@ -0,0 +1,3 @@ +{ + "title": "Raspberry Pi Voeding Checker" +} \ No newline at end of file diff --git a/homeassistant/components/rpi_power/translations/no.json b/homeassistant/components/rpi_power/translations/no.json new file mode 100644 index 00000000000000..63f46667e79baf --- /dev/null +++ b/homeassistant/components/rpi_power/translations/no.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "no_devices_found": "Finner ikke systemklassen som trengs for denne komponenten, s\u00f8rg for at kjernen din er ny og at maskinvaren st\u00f8ttes", + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." + }, + "step": { + "confirm": { + "description": "Vil du starte oppsettet?" + } + } + }, + "title": "Raspberry Pi str\u00f8mforsyningskontroll" +} \ No newline at end of file diff --git a/homeassistant/components/rpi_power/translations/pl.json b/homeassistant/components/rpi_power/translations/pl.json new file mode 100644 index 00000000000000..c9599d7182b20a --- /dev/null +++ b/homeassistant/components/rpi_power/translations/pl.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." + }, + "step": { + "confirm": { + "description": "Czy chcesz rozpocz\u0105\u0107 konfiguracj\u0119?" + } + } + }, + "title": "Sprawdzanie zasilacza Raspberry Pi" +} \ No newline at end of file diff --git a/homeassistant/components/rpi_power/translations/ru.json b/homeassistant/components/rpi_power/translations/ru.json new file mode 100644 index 00000000000000..f91df15e1b31d1 --- /dev/null +++ b/homeassistant/components/rpi_power/translations/ru.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "no_devices_found": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043d\u0430\u0439\u0442\u0438 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0439 \u043a\u043b\u0430\u0441\u0441, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0439 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u044d\u0442\u043e\u0433\u043e \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430. \u0423\u0431\u0435\u0434\u0438\u0442\u0435\u0441\u044c, \u0447\u0442\u043e \u0443 \u0412\u0430\u0441 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d\u043e \u043d\u043e\u0432\u0435\u0439\u0448\u0435\u0435 \u044f\u0434\u0440\u043e \u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u043e\u0435 \u043e\u0431\u043e\u0440\u0443\u0434\u043e\u0432\u0430\u043d\u0438\u0435.", + "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": { + "confirm": { + "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0447\u0430\u0442\u044c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443?" + } + } + }, + "title": "\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430 \u043f\u0438\u0442\u0430\u043d\u0438\u044f Raspberry Pi" +} \ No newline at end of file diff --git a/homeassistant/components/rpi_power/translations/zh-Hant.json b/homeassistant/components/rpi_power/translations/zh-Hant.json new file mode 100644 index 00000000000000..37dbb151d8e071 --- /dev/null +++ b/homeassistant/components/rpi_power/translations/zh-Hant.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "no_devices_found": "\u627e\u4e0d\u5230\u7cfb\u7d71\u6240\u9700\u7684\u5143\u4ef6\uff0c\u8acb\u78ba\u5b9a Kernel \u70ba\u6700\u65b0\u7248\u672c\u3001\u540c\u6642\u786c\u9ad4\u70ba\u652f\u63f4\u72c0\u614b", + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002" + }, + "step": { + "confirm": { + "description": "\u662f\u5426\u8981\u958b\u59cb\u8a2d\u5b9a\uff1f" + } + } + }, + "title": "Raspberry Pi \u96fb\u6e90\u4f9b\u61c9\u6aa2\u67e5\u5de5\u5177" +} \ No newline at end of file diff --git a/homeassistant/components/samsungtv/bridge.py b/homeassistant/components/samsungtv/bridge.py index ee761696cc0802..dc8eb862ff783e 100644 --- a/homeassistant/components/samsungtv/bridge.py +++ b/homeassistant/components/samsungtv/bridge.py @@ -22,7 +22,7 @@ LOGGER, METHOD_LEGACY, RESULT_AUTH_MISSING, - RESULT_NOT_SUCCESSFUL, + RESULT_CANNOT_CONNECT, RESULT_NOT_SUPPORTED, RESULT_SUCCESS, VALUE_CONF_ID, @@ -164,7 +164,7 @@ def try_connect(self): return RESULT_NOT_SUPPORTED except OSError as err: LOGGER.debug("Failing config: %s, error: %s", config, err) - return RESULT_NOT_SUCCESSFUL + return RESULT_CANNOT_CONNECT def _get_remote(self): """Create or return a remote control instance.""" @@ -232,7 +232,7 @@ def try_connect(self): if result: return result - return RESULT_NOT_SUCCESSFUL + return RESULT_CANNOT_CONNECT def _send_key(self, key): """Send the key using websocket protocol.""" diff --git a/homeassistant/components/samsungtv/config_flow.py b/homeassistant/components/samsungtv/config_flow.py index 9c18600670c606..7a7fa26f922584 100644 --- a/homeassistant/components/samsungtv/config_flow.py +++ b/homeassistant/components/samsungtv/config_flow.py @@ -31,7 +31,7 @@ METHOD_LEGACY, METHOD_WEBSOCKET, RESULT_AUTH_MISSING, - RESULT_NOT_SUCCESSFUL, + RESULT_CANNOT_CONNECT, RESULT_SUCCESS, ) @@ -87,10 +87,10 @@ def _try_connect(self): for method in SUPPORTED_METHODS: self._bridge = SamsungTVBridge.get_bridge(method, self._host) result = self._bridge.try_connect() - if result != RESULT_NOT_SUCCESSFUL: + if result != RESULT_CANNOT_CONNECT: return result LOGGER.debug("No working config found") - return RESULT_NOT_SUCCESSFUL + return RESULT_CANNOT_CONNECT async def async_step_import(self, user_input=None): """Handle configuration by yaml file.""" diff --git a/homeassistant/components/samsungtv/const.py b/homeassistant/components/samsungtv/const.py index c08f07e637945d..e043c74b34768f 100644 --- a/homeassistant/components/samsungtv/const.py +++ b/homeassistant/components/samsungtv/const.py @@ -16,7 +16,7 @@ RESULT_AUTH_MISSING = "auth_missing" RESULT_SUCCESS = "success" -RESULT_NOT_SUCCESSFUL = "not_successful" +RESULT_CANNOT_CONNECT = "cannot_connect" RESULT_NOT_SUPPORTED = "not_supported" METHOD_LEGACY = "legacy" diff --git a/homeassistant/components/samsungtv/strings.json b/homeassistant/components/samsungtv/strings.json index e615728776ebba..b326f2ab548943 100644 --- a/homeassistant/components/samsungtv/strings.json +++ b/homeassistant/components/samsungtv/strings.json @@ -6,7 +6,7 @@ "description": "Enter your Samsung TV information. If you never connected Home Assistant before you should see a popup on your TV asking for authorization.", "data": { "host": "[%key:common::config_flow::data::host%]", - "name": "Name" + "name": "[%key:common::config_flow::data::name%]" } }, "confirm": { @@ -15,11 +15,11 @@ } }, "abort": { - "already_in_progress": "Samsung TV configuration is already in progress.", - "already_configured": "This Samsung TV is already configured.", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "auth_missing": "Home Assistant is not authorized to connect to this Samsung TV. Please check your TV's settings to authorize Home Assistant.", - "not_successful": "Unable to connect to this Samsung TV device.", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "not_supported": "This Samsung TV device is currently not supported." } } -} \ No newline at end of file +} diff --git a/homeassistant/components/samsungtv/translations/ca.json b/homeassistant/components/samsungtv/translations/ca.json index 5a5fe7e7986da7..9b0b9a37844f17 100644 --- a/homeassistant/components/samsungtv/translations/ca.json +++ b/homeassistant/components/samsungtv/translations/ca.json @@ -1,9 +1,10 @@ { "config": { "abort": { - "already_configured": "Aquest televisor Samsung ja configurat.", - "already_in_progress": "La configuraci\u00f3 del televisor Samsung ja est\u00e0 en curs.", + "already_configured": "El dispositiu ja est\u00e0 configurat", + "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", "auth_missing": "Home Assistant no est\u00e0 autenticat per connectar-se amb aquest televisor Samsung. V\u00e9s a la configuraci\u00f3 del televisor per autoritzar a Home Assistant.", + "cannot_connect": "Ha fallat la connexi\u00f3", "not_successful": "No s'ha pogut connectar amb el dispositiu el televisor Samsung.", "not_supported": "Actualment aquest televisor Samsung no \u00e9s compatible." }, diff --git a/homeassistant/components/samsungtv/translations/en.json b/homeassistant/components/samsungtv/translations/en.json index 35510858c551b1..ff33876b93af76 100644 --- a/homeassistant/components/samsungtv/translations/en.json +++ b/homeassistant/components/samsungtv/translations/en.json @@ -1,9 +1,10 @@ { "config": { "abort": { - "already_configured": "This Samsung TV is already configured.", - "already_in_progress": "Samsung TV configuration is already in progress.", + "already_configured": "Device is already configured", + "already_in_progress": "Configuration flow is already in progress", "auth_missing": "Home Assistant is not authorized to connect to this Samsung TV. Please check your TV's settings to authorize Home Assistant.", + "cannot_connect": "Failed to connect", "not_successful": "Unable to connect to this Samsung TV device.", "not_supported": "This Samsung TV device is currently not supported." }, diff --git a/homeassistant/components/samsungtv/translations/es.json b/homeassistant/components/samsungtv/translations/es.json index 308df08de0d7af..10fd0cf358ac0a 100644 --- a/homeassistant/components/samsungtv/translations/es.json +++ b/homeassistant/components/samsungtv/translations/es.json @@ -4,6 +4,7 @@ "already_configured": "Este televisor Samsung ya est\u00e1 configurado.", "already_in_progress": "La configuraci\u00f3n del televisor Samsung ya est\u00e1 en marcha.", "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", "not_successful": "No se puede conectar a este dispositivo Samsung TV.", "not_supported": "Esta televisi\u00f3n Samsung actualmente no es compatible." }, diff --git a/homeassistant/components/samsungtv/translations/et.json b/homeassistant/components/samsungtv/translations/et.json new file mode 100644 index 00000000000000..d71a861b6b5659 --- /dev/null +++ b/homeassistant/components/samsungtv/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "cannot_connect": "\u00dchendamine nurjus" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/samsungtv/translations/it.json b/homeassistant/components/samsungtv/translations/it.json index 4236b1d8ed8de0..4abbcce70c06e1 100644 --- a/homeassistant/components/samsungtv/translations/it.json +++ b/homeassistant/components/samsungtv/translations/it.json @@ -1,9 +1,10 @@ { "config": { "abort": { - "already_configured": "Questo Samsung TV \u00e8 gi\u00e0 configurato.", - "already_in_progress": "La configurazione di Samsung TV \u00e8 gi\u00e0 in corso.", + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "auth_missing": "Home Assistant non \u00e8 autorizzato a connettersi a questo Samsung TV. Controlla le impostazioni del tuo TV per autorizzare Home Assistant.", + "cannot_connect": "Impossibile connettersi", "not_successful": "Impossibile connettersi a questo dispositivo Samsung TV.", "not_supported": "Questo dispositivo Samsung TV non \u00e8 attualmente supportato." }, diff --git a/homeassistant/components/samsungtv/translations/no.json b/homeassistant/components/samsungtv/translations/no.json index e0420ba74af86e..dd0b63581c07e3 100644 --- a/homeassistant/components/samsungtv/translations/no.json +++ b/homeassistant/components/samsungtv/translations/no.json @@ -1,9 +1,10 @@ { "config": { "abort": { - "already_configured": "Denne Samsung TV-en er allerede konfigurert.", - "already_in_progress": "Samsung TV-konfigurasjon p\u00e5g\u00e5r allerede.", + "already_configured": "Enheten er allerede konfigurert", + "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", "auth_missing": "Home Assistant er ikke godkjent til \u00e5 koble til denne Samsung-TV. Vennligst kontroller innstillingene for TV-en for \u00e5 godkjenne Home Assistent.", + "cannot_connect": "Tilkobling mislyktes.", "not_successful": "Kan ikke koble til denne Samsung TV-enheten.", "not_supported": "Denne Samsung TV-enhetene st\u00f8ttes forel\u00f8pig ikke." }, diff --git a/homeassistant/components/samsungtv/translations/ru.json b/homeassistant/components/samsungtv/translations/ru.json index c5ee5e8348c86d..8af4f95404cf5c 100644 --- a/homeassistant/components/samsungtv/translations/ru.json +++ b/homeassistant/components/samsungtv/translations/ru.json @@ -1,9 +1,10 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", - "already_in_progress": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.", + "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.", "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. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \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.", "not_successful": "\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_supported": "\u042d\u0442\u0430 \u043c\u043e\u0434\u0435\u043b\u044c \u0442\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440\u0430 \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." }, diff --git a/homeassistant/components/samsungtv/translations/zh-Hant.json b/homeassistant/components/samsungtv/translations/zh-Hant.json index 973b84dd2eed3a..1b0b0a09ec4a23 100644 --- a/homeassistant/components/samsungtv/translations/zh-Hant.json +++ b/homeassistant/components/samsungtv/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\u4e09\u661f\u96fb\u8996\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", - "already_in_progress": "\u4e09\u661f\u96fb\u8996\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d\u3002", + "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "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\u8a2d\u5b9a\u4ee5\u76e1\u8208\u9a57\u8b49\u3002", "not_successful": "\u7121\u6cd5\u9023\u7dda\u81f3\u4e09\u661f\u96fb\u8996\u8a2d\u5099\u3002", "not_supported": "\u4e0d\u652f\u63f4\u6b64\u6b3e\u4e09\u661f\u96fb\u8996\u3002" diff --git a/homeassistant/components/satel_integra/binary_sensor.py b/homeassistant/components/satel_integra/binary_sensor.py index 19763903f27845..ea9c19376f694b 100644 --- a/homeassistant/components/satel_integra/binary_sensor.py +++ b/homeassistant/components/satel_integra/binary_sensor.py @@ -1,7 +1,10 @@ """Support for Satel Integra zone states- represented as binary sensors.""" import logging -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_SMOKE, + BinarySensorEntity, +) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -89,7 +92,7 @@ def name(self): @property def icon(self): """Icon for device by its type.""" - if self._zone_type == "smoke": + if self._zone_type == DEVICE_CLASS_SMOKE: return "mdi:fire" @property diff --git a/homeassistant/components/season/sensor.py b/homeassistant/components/season/sensor.py index 6e072f7377e35b..e116fb0e8611ee 100644 --- a/homeassistant/components/season/sensor.py +++ b/homeassistant/components/season/sensor.py @@ -10,7 +10,7 @@ from homeassistant.const import CONF_NAME, CONF_TYPE import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -import homeassistant.util.dt as dt_util +from homeassistant.util.dt import utcnow _LOGGER = logging.getLogger(__name__) @@ -72,7 +72,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): hemisphere = EQUATOR _LOGGER.debug(_type) - add_entities([Season(hass, hemisphere, _type, name)]) + add_entities([Season(hass, hemisphere, _type, name)], True) return True @@ -117,9 +117,9 @@ def __init__(self, hass, hemisphere, season_tracking_type, name): self.hass = hass self._name = name self.hemisphere = hemisphere - self.datetime = dt_util.utcnow().replace(tzinfo=None) + self.datetime = None self.type = season_tracking_type - self.season = get_season(self.datetime, self.hemisphere, self.type) + self.season = None @property def name(self): @@ -143,5 +143,5 @@ def icon(self): def update(self): """Update season.""" - self.datetime = dt_util.utcnow().replace(tzinfo=None) + self.datetime = utcnow().replace(tzinfo=None) self.season = get_season(self.datetime, self.hemisphere, self.type) diff --git a/homeassistant/components/season/translations/sensor.et.json b/homeassistant/components/season/translations/sensor.et.json new file mode 100644 index 00000000000000..eb9953a73ce816 --- /dev/null +++ b/homeassistant/components/season/translations/sensor.et.json @@ -0,0 +1,16 @@ +{ + "state": { + "season__season": { + "autumn": "S\u00fcgis", + "spring": "Kevad", + "summer": "Suvi", + "winter": "Talv" + }, + "season__season__": { + "autumn": "S\u00fcgis", + "spring": "Kevad", + "summer": "Suvi", + "winter": "Talv" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sendgrid/notify.py b/homeassistant/components/sendgrid/notify.py index 6dbf4d5c2b73a1..8d8907f2dcf262 100644 --- a/homeassistant/components/sendgrid/notify.py +++ b/homeassistant/components/sendgrid/notify.py @@ -15,6 +15,7 @@ CONF_RECIPIENT, CONF_SENDER, CONTENT_TYPE_TEXT_PLAIN, + HTTP_ACCEPTED, ) import homeassistant.helpers.config_validation as cv @@ -65,5 +66,5 @@ def send_message(self, message="", **kwargs): } response = self._sg.client.mail.send.post(request_body=data) - if response.status_code != 202: + if response.status_code != HTTP_ACCEPTED: _LOGGER.error("Unable to send notification") diff --git a/homeassistant/components/sense/strings.json b/homeassistant/components/sense/strings.json index 1363d44d89f9b5..44a45f07ce92b0 100644 --- a/homeassistant/components/sense/strings.json +++ b/homeassistant/components/sense/strings.json @@ -10,12 +10,12 @@ } }, "error": { - "cannot_connect": "Failed to connect, please try again", - "invalid_auth": "Invalid authentication", - "unknown": "Unexpected 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": "Device is already configured" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/sense/translations/ca.json b/homeassistant/components/sense/translations/ca.json index 4b1035607d606f..096b4419dae7ad 100644 --- a/homeassistant/components/sense/translations/ca.json +++ b/homeassistant/components/sense/translations/ca.json @@ -4,7 +4,7 @@ "already_configured": "El dispositiu ja est\u00e0 configurat" }, "error": { - "cannot_connect": "No s'ha pogut connectar, torna-ho a provar", + "cannot_connect": "Ha fallat la connexi\u00f3", "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "unknown": "Error inesperat" }, diff --git a/homeassistant/components/sense/translations/en.json b/homeassistant/components/sense/translations/en.json index 181dadea06c2f9..5582a8424a6a2a 100644 --- a/homeassistant/components/sense/translations/en.json +++ b/homeassistant/components/sense/translations/en.json @@ -4,7 +4,7 @@ "already_configured": "Device is already configured" }, "error": { - "cannot_connect": "Failed to connect, please try again", + "cannot_connect": "Failed to connect", "invalid_auth": "Invalid authentication", "unknown": "Unexpected error" }, diff --git a/homeassistant/components/sense/translations/it.json b/homeassistant/components/sense/translations/it.json index 4e1ddd01b42f9c..277e2e1539b95a 100644 --- a/homeassistant/components/sense/translations/it.json +++ b/homeassistant/components/sense/translations/it.json @@ -4,7 +4,7 @@ "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" }, "error": { - "cannot_connect": "Impossibile connettersi, si prega di riprovare.", + "cannot_connect": "Impossibile connettersi", "invalid_auth": "Autenticazione non valida", "unknown": "Errore imprevisto" }, diff --git a/homeassistant/components/sense/translations/no.json b/homeassistant/components/sense/translations/no.json index c3457ccb280474..ca987279bb783a 100644 --- a/homeassistant/components/sense/translations/no.json +++ b/homeassistant/components/sense/translations/no.json @@ -4,7 +4,7 @@ "already_configured": "Enheten er allerede konfigurert" }, "error": { - "cannot_connect": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen", + "cannot_connect": "Tilkobling mislyktes.", "invalid_auth": "Ugyldig godkjenning", "unknown": "Uventet feil" }, diff --git a/homeassistant/components/sense/translations/pl.json b/homeassistant/components/sense/translations/pl.json index c32b61e30ad9d1..7cf2ebe47099b2 100644 --- a/homeassistant/components/sense/translations/pl.json +++ b/homeassistant/components/sense/translations/pl.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "invalid_auth": "Niepoprawne uwierzytelnienie.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "user": { diff --git a/homeassistant/components/sense/translations/ru.json b/homeassistant/components/sense/translations/ru.json index 163af6cb512aaa..74be3049a750be 100644 --- a/homeassistant/components/sense/translations/ru.json +++ b/homeassistant/components/sense/translations/ru.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", + "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "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/sense/translations/zh-Hant.json b/homeassistant/components/sense/translations/zh-Hant.json index e989026347764d..356e58f640b9f7 100644 --- a/homeassistant/components/sense/translations/zh-Hant.json +++ b/homeassistant/components/sense/translations/zh-Hant.json @@ -4,7 +4,7 @@ "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21", + "cannot_connect": "\u9023\u7dda\u5931\u6557", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, diff --git a/homeassistant/components/sensor/group.py b/homeassistant/components/sensor/group.py new file mode 100644 index 00000000000000..4741f8a3b548cd --- /dev/null +++ b/homeassistant/components/sensor/group.py @@ -0,0 +1,14 @@ +"""Describe group states.""" + + +from homeassistant.components.group import GroupIntegrationRegistry +from homeassistant.core import callback +from homeassistant.helpers.typing import HomeAssistantType + + +@callback +def async_describe_on_off_states( + hass: HomeAssistantType, registry: GroupIntegrationRegistry +) -> None: + """Describe group on off states.""" + registry.exclude_domain() diff --git a/homeassistant/components/sensor/translations/et.json b/homeassistant/components/sensor/translations/et.json index 8238a8b6ab095d..450f5b60537c93 100644 --- a/homeassistant/components/sensor/translations/et.json +++ b/homeassistant/components/sensor/translations/et.json @@ -1,4 +1,36 @@ { + "device_automation": { + "condition_type": { + "is_battery_level": "Praegune {entity_name} aku tase", + "is_current": "Praegune {entity_name} voolutugevus", + "is_energy": "Praegune {entity_name} v\u00f5imsus", + "is_humidity": "Praegune {entity_name} niiskus", + "is_illuminance": "Praegune {entity_name} valgustatus", + "is_power": "Praegune {entity_name} toide (v\u00f5imsus)", + "is_power_factor": "Praegune {entity_name} v\u00f5imsusfaktor", + "is_pressure": "Praegune {entity_name} r\u00f5hk", + "is_signal_strength": "Praegune {entity_name} signaali tugevus", + "is_temperature": "Praegune {entity_name} temperatuur", + "is_timestamp": "Praegune {entity_name} aeg", + "is_value": "Praegune {entity_name} v\u00e4\u00e4rtus", + "is_voltage": "Praegune {entity_name}pinge" + }, + "trigger_type": { + "battery_level": "{entity_name} aku tase muutub", + "current": "{entity_name} voolutugevus muutub", + "energy": "{entity_name} v\u00f5imsus muutub", + "humidity": "{entity_name} niiskus muutub", + "illuminance": "{entity_name} valgustustugevus muutub", + "power": "{entity_name} energiare\u017eiimi muutub", + "power_factor": "{entity_name} v\u00f5imsus muutub", + "pressure": "{entity_name} r\u00f5hk muutub", + "signal_strength": "{entity_name} signaalitugevus muutub", + "temperature": "{entity_name} temperatuur muutub", + "timestamp": "{entity_name} aeg muutub", + "value": "{entity_name} v\u00e4\u00e4rtus muutub", + "voltage": "{entity_name} pingemuutub" + } + }, "state": { "_": { "off": "V\u00e4ljas", diff --git a/homeassistant/components/sensor/translations/pl.json b/homeassistant/components/sensor/translations/pl.json index 84c32d8dc9a006..6e5d50c1caeedf 100644 --- a/homeassistant/components/sensor/translations/pl.json +++ b/homeassistant/components/sensor/translations/pl.json @@ -2,14 +2,18 @@ "device_automation": { "condition_type": { "is_battery_level": "obecny poziom na\u0142adowania baterii {entity_name}", + "is_current": "Bie\u017c\u0105cy pr\u0105d {entity_name}", + "is_energy": "Bie\u017c\u0105ca energia {entity_name}", "is_humidity": "obecna wilgotno\u015b\u0107 {entity_name}", "is_illuminance": "obecne nat\u0119\u017cenie o\u015bwietlenia {entity_name}", "is_power": "obecna moc {entity_name}", + "is_power_factor": "Bie\u017c\u0105cy wsp\u00f3\u0142czynnik mocy {entity_name}", "is_pressure": "obecne ci\u015bnienie {entity_name}", "is_signal_strength": "obecna si\u0142a sygna\u0142u {entity_name}", "is_temperature": "obecna temperatura {entity_name}", "is_timestamp": "obecny znacznik czasu {entity_name}", - "is_value": "obecna warto\u015b\u0107 {entity_name}" + "is_value": "obecna warto\u015b\u0107 {entity_name}", + "is_voltage": "Bie\u017c\u0105ce napi\u0119cie {entity_name}" }, "trigger_type": { "battery_level": "zmieni si\u0119 poziom baterii {entity_name}", diff --git a/homeassistant/components/sensor/translations/uk.json b/homeassistant/components/sensor/translations/uk.json index 56e587bb44c568..391415409f5bd1 100644 --- a/homeassistant/components/sensor/translations/uk.json +++ b/homeassistant/components/sensor/translations/uk.json @@ -1,4 +1,9 @@ { + "device_automation": { + "condition_type": { + "is_battery_level": "\u041f\u043e\u0442\u043e\u0447\u043d\u0438\u0439 \u0440\u0456\u0432\u0435\u043d\u044c \u0437\u0430\u0440\u044f\u0434\u0443 \u0430\u043a\u0443\u043c\u0443\u043b\u044f\u0442\u043e\u0440\u0430 {entity_name}" + } + }, "state": { "_": { "off": "\u0412\u0438\u043c\u043a\u043d\u0435\u043d\u043e", diff --git a/homeassistant/components/sentry/manifest.json b/homeassistant/components/sentry/manifest.json index 1a9bb74a8ee7a7..2bcb38ed168ac5 100644 --- a/homeassistant/components/sentry/manifest.json +++ b/homeassistant/components/sentry/manifest.json @@ -3,6 +3,6 @@ "name": "Sentry", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sentry", - "requirements": ["sentry-sdk==0.17.4"], + "requirements": ["sentry-sdk==0.18.0"], "codeowners": ["@dcramer", "@frenck"] } diff --git a/homeassistant/components/sentry/strings.json b/homeassistant/components/sentry/strings.json index 593a8c5c8d07ad..71196d52f8d864 100644 --- a/homeassistant/components/sentry/strings.json +++ b/homeassistant/components/sentry/strings.json @@ -4,10 +4,15 @@ "user": { "title": "Sentry", "description": "Enter your Sentry DSN", - "data": { "dsn": "DSN" } + "data": { + "dsn": "DSN" + } } }, - "error": { "unknown": "Unexpected error", "bad_dsn": "Invalid DSN" }, + "error": { + "unknown": "[%key:common::config_flow::error::unknown%]", + "bad_dsn": "Invalid DSN" + }, "abort": { "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" } diff --git a/homeassistant/components/sentry/translations/de.json b/homeassistant/components/sentry/translations/de.json index 5d6e27bd73786a..6e6d640cd452d5 100644 --- a/homeassistant/components/sentry/translations/de.json +++ b/homeassistant/components/sentry/translations/de.json @@ -9,6 +9,9 @@ }, "step": { "user": { + "data": { + "dsn": "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 new file mode 100644 index 00000000000000..a1c27233094890 --- /dev/null +++ b/homeassistant/components/sentry/translations/el.json @@ -0,0 +1,16 @@ +{ + "options": { + "step": { + "init": { + "data": { + "event_handled": "\u03a3\u03c4\u03b5\u03af\u03bb\u03c4\u03b5 \u03c7\u03b5\u03b9\u03c1\u03b9\u03c3\u03bc\u03ad\u03bd\u03b1 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd\u03c4\u03b1", + "event_third_party_packages": "\u03a3\u03c4\u03b5\u03af\u03bb\u03c4\u03b5 \u03b5\u03ba\u03b4\u03b7\u03bb\u03ce\u03c3\u03b5\u03b9\u03c2 \u03b1\u03c0\u03cc \u03c0\u03b1\u03ba\u03ad\u03c4\u03b1 \u03c4\u03c1\u03af\u03c4\u03c9\u03bd", + "logging_event_level": "\u03a4\u03bf \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03ba\u03b1\u03c4\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae\u03c2 Sentry \u03b8\u03b1 \u03ba\u03b1\u03c4\u03b1\u03c7\u03c9\u03c1\u03ae\u03c3\u03b5\u03b9 \u03ad\u03bd\u03b1 \u03c3\u03c5\u03bc\u03b2\u03ac\u03bd \u03b3\u03b9\u03b1", + "logging_level": "\u03a4\u03bf \u03b5\u03c0\u03af\u03c0\u03b5\u03b4\u03bf \u03ba\u03b1\u03c4\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae\u03c2 Sentry \u03b8\u03b1 \u03ba\u03b1\u03c4\u03b1\u03b3\u03c1\u03ac\u03c6\u03b5\u03b9 \u03b1\u03c1\u03c7\u03b5\u03af\u03b1 \u03ba\u03b1\u03c4\u03b1\u03b3\u03c1\u03b1\u03c6\u03ae\u03c2 \u03c9\u03c2 \u03b4\u03b9\u03b1\u03ba\u03c1\u03b9\u03c4\u03b9\u03ba\u03ac \u03b3\u03b9\u03b1", + "tracing": "\u0395\u03bd\u03b5\u03c1\u03b3\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7 \u03b1\u03bd\u03af\u03c7\u03bd\u03b5\u03c5\u03c3\u03b7\u03c2 \u03b5\u03c0\u03b9\u03b4\u03cc\u03c3\u03b5\u03c9\u03bd", + "tracing_sample_rate": "\u03a1\u03c5\u03b8\u03bc\u03cc\u03c2 \u03b1\u03bd\u03af\u03c7\u03bd\u03b5\u03c5\u03c3\u03b7\u03c2 \u03b4\u03b5\u03af\u03b3\u03bc\u03b1\u03c4\u03bf\u03c2. \u03bc\u03b5\u03c4\u03b1\u03be\u03cd 0,0 \u03ba\u03b1\u03b9 1,0 (1,0 = 100%)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sentry/translations/et.json b/homeassistant/components/sentry/translations/et.json new file mode 100644 index 00000000000000..255510c130ca95 --- /dev/null +++ b/homeassistant/components/sentry/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "unknown": "Ootamatu t\u00f5rge" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sentry/translations/ko.json b/homeassistant/components/sentry/translations/ko.json index 7e60891e166070..f3adbefa3d71cb 100644 --- a/homeassistant/components/sentry/translations/ko.json +++ b/homeassistant/components/sentry/translations/ko.json @@ -13,5 +13,21 @@ "title": "Sentry" } } + }, + "options": { + "step": { + "init": { + "data": { + "environment": "\ud658\uacbd\uc758 \uc120\ud0dd\uc801 \uba85\uce6d", + "event_custom_components": "\uc0ac\uc6a9\uc790 \uc9c0\uc815 \uad6c\uc131 \uc694\uc18c\uc5d0\uc11c \uc774\ubca4\ud2b8 \ubcf4\ub0b4\uae30", + "event_handled": "\ucc98\ub9ac\ub41c \uc774\ubca4\ud2b8 \ubcf4\ub0b4\uae30", + "event_third_party_packages": "\uc368\ub4dc\ud30c\ud2f0 \ud328\ud0a4\uc9c0\uc5d0\uc11c \uc774\ubca4\ud2b8 \ubcf4\ub0b4\uae30", + "logging_event_level": "Log level Sentry\ub294 \ub2e4\uc74c\uc5d0 \ub300\ud55c \uc774\ubca4\ud2b8\ub97c \ub4f1\ub85d\ud569\ub2c8\ub2e4.", + "logging_level": "Log level sentry\ub294 \ub2e4\uc74c\uc5d0 \ub300\ud55c \ub85c\uadf8\ub97c \ube0c\ub808\ub4dc \ud06c\ub7fc\uc73c\ub85c \uae30\ub85d\ud569\ub2c8\ub2e4.", + "tracing": "\uc131\ub2a5 \ucd94\uc801 \ud65c\uc131\ud654", + "tracing_sample_rate": "\uc0d8\ud50c\ub9c1 \uc18d\ub3c4 \ucd94\uc801; 0.0\uc5d0\uc11c 1.0 \uc0ac\uc774 (1.0 = 100 %)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/sentry/translations/pl.json b/homeassistant/components/sentry/translations/pl.json index fa87b8510c748b..5537d8d8ede96a 100644 --- a/homeassistant/components/sentry/translations/pl.json +++ b/homeassistant/components/sentry/translations/pl.json @@ -6,13 +6,29 @@ }, "error": { "bad_dsn": "Nieprawid\u0142owy DSN", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "user": { + "data": { + "dsn": "DSN" + }, "description": "Wprowad\u017a DSN Sentry", "title": "Sentry" } } + }, + "options": { + "step": { + "init": { + "data": { + "environment": "Opcjonalna nazwa \u015brodowiska.", + "event_custom_components": "Wysy\u0142aj zdarzenia z niestandardowych komponent\u00f3w", + "logging_event_level": "Poziom wpis\u00f3w dziennika, dla kt\u00f3rego Sentry zarejestruje zdarzenie", + "tracing": "W\u0142\u0105cz \u015bledzenie wydajno\u015bci", + "tracing_sample_rate": "Cz\u0119stotliwo\u015b\u0107 \u015bledzenia; od 0.0 do 1.0 (1.0 = 100%)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/sesame/lock.py b/homeassistant/components/sesame/lock.py index a2d205de240981..0ad3d87a1715f0 100644 --- a/homeassistant/components/sesame/lock.py +++ b/homeassistant/components/sesame/lock.py @@ -88,8 +88,8 @@ def update(self) -> None: @property def device_state_attributes(self) -> dict: """Return the state attributes.""" - attributes = {} - attributes[ATTR_DEVICE_ID] = self._device_id - attributes[ATTR_SERIAL_NO] = self._serial - attributes[ATTR_BATTERY_LEVEL] = self._battery - return attributes + return { + ATTR_DEVICE_ID: self._device_id, + ATTR_SERIAL_NO: self._serial, + ATTR_BATTERY_LEVEL: self._battery, + } diff --git a/homeassistant/components/seventeentrack/sensor.py b/homeassistant/components/seventeentrack/sensor.py index 42b198d48d98e5..7abed897346253 100644 --- a/homeassistant/components/seventeentrack/sensor.py +++ b/homeassistant/components/seventeentrack/sensor.py @@ -16,6 +16,7 @@ ) from homeassistant.helpers import aiohttp_client, config_validation as cv from homeassistant.helpers.entity import Entity +from homeassistant.helpers.event import async_call_later from homeassistant.util import Throttle, slugify _LOGGER = logging.getLogger(__name__) @@ -220,7 +221,8 @@ async def async_update(self): await self._data.async_update() if not self.available: - self.hass.async_create_task(self._remove()) + # Entity cannot be removed while its being added + async_call_later(self.hass, 1, self._remove) return package = self._data.packages.get(self._tracking_number, None) @@ -229,7 +231,8 @@ async def async_update(self): # delivered, post a notification: if package.status == VALUE_DELIVERED and not self._data.show_delivered: self._notify_delivered() - self.hass.async_create_task(self._remove()) + # Entity cannot be removed while its being added + async_call_later(self.hass, 1, self._remove) return self._attrs.update( @@ -238,7 +241,7 @@ async def async_update(self): self._state = package.status self._friendly_name = package.friendly_name - async def _remove(self): + async def _remove(self, *_): """Remove entity itself.""" await self.async_remove() diff --git a/homeassistant/components/sharkiq/strings.json b/homeassistant/components/sharkiq/strings.json index 114087697ad1af..ccd66a3829715b 100644 --- a/homeassistant/components/sharkiq/strings.json +++ b/homeassistant/components/sharkiq/strings.json @@ -22,7 +22,7 @@ "abort": { "already_configured_account": "[%key:common::config_flow::abort::already_configured_account%]", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "reauth_successful": "[%key:common::config_flow::data::access_token%] updated successfully", + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", "unknown": "[%key:common::config_flow::error::unknown%]" } } diff --git a/homeassistant/components/sharkiq/translations/de.json b/homeassistant/components/sharkiq/translations/de.json new file mode 100644 index 00000000000000..5a1d4f2f185183 --- /dev/null +++ b/homeassistant/components/sharkiq/translations/de.json @@ -0,0 +1,18 @@ +{ + "config": { + "step": { + "reauth": { + "data": { + "password": "Passwort", + "username": "Benutzername" + } + }, + "user": { + "data": { + "password": "Passwort", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sharkiq/translations/el.json b/homeassistant/components/sharkiq/translations/el.json new file mode 100644 index 00000000000000..4c6777955253d3 --- /dev/null +++ b/homeassistant/components/sharkiq/translations/el.json @@ -0,0 +1,14 @@ +{ + "config": { + "abort": { + "unknown": "\u039c\u03b7 \u03b1\u03bd\u03b1\u03bc\u03b5\u03bd\u03cc\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" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sharkiq/translations/et.json b/homeassistant/components/sharkiq/translations/et.json new file mode 100644 index 00000000000000..ff5b447a315a7b --- /dev/null +++ b/homeassistant/components/sharkiq/translations/et.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "reauth": { + "data": { + "password": "Salas\u00f5na", + "username": "Kasutajanimi" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sharkiq/translations/hu.json b/homeassistant/components/sharkiq/translations/hu.json new file mode 100644 index 00000000000000..9f2fd5d72f4336 --- /dev/null +++ b/homeassistant/components/sharkiq/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured_account": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sharkiq/translations/ko.json b/homeassistant/components/sharkiq/translations/ko.json new file mode 100644 index 00000000000000..d7e196c09fb277 --- /dev/null +++ b/homeassistant/components/sharkiq/translations/ko.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured_account": "\uacc4\uc815\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4.", + "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", + "reauth_successful": "\uc561\uc138\uc2a4 \ud1a0\ud070\uc774 \uc131\uacf5\uc801\uc73c\ub85c \uc5c5\ub370\uc774\ud2b8\ub418\uc5c8\uc2b5\ub2c8\ub2e4", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec" + }, + "error": { + "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328", + "invalid_auth": "\uc798\ubabb\ub41c \uc778\uc99d", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec" + }, + "step": { + "reauth": { + "data": { + "password": "\uc554\ud638", + "username": "\uc0ac\uc6a9\uc790\uba85" + } + }, + "user": { + "data": { + "password": "\uc554\ud638", + "username": "\uc0ac\uc6a9\uc790\uba85" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sharkiq/translations/lb.json b/homeassistant/components/sharkiq/translations/lb.json new file mode 100644 index 00000000000000..1da09e6b8ecd7a --- /dev/null +++ b/homeassistant/components/sharkiq/translations/lb.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "cannot_connect": "Feeler beim verbannen" + }, + "error": { + "cannot_connect": "Feeler beim verbannen", + "invalid_auth": "Ong\u00eblteg Authentifikatioun", + "unknown": "Onerwaarte Feeler" + }, + "step": { + "reauth": { + "data": { + "password": "Passwuert", + "username": "Benotzernumm" + } + }, + "user": { + "data": { + "password": "Passwuert", + "username": "Benotzernumm" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sharkiq/translations/nl.json b/homeassistant/components/sharkiq/translations/nl.json new file mode 100644 index 00000000000000..03605190c3ced1 --- /dev/null +++ b/homeassistant/components/sharkiq/translations/nl.json @@ -0,0 +1,17 @@ +{ + "config": { + "step": { + "reauth": { + "data": { + "password": "Paswoord", + "username": "Gebruikersnaam" + } + }, + "user": { + "data": { + "username": "Gebruikersnaam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sharkiq/translations/pl.json b/homeassistant/components/sharkiq/translations/pl.json index dcb12e8690653d..283e2c4f440a48 100644 --- a/homeassistant/components/sharkiq/translations/pl.json +++ b/homeassistant/components/sharkiq/translations/pl.json @@ -1,12 +1,15 @@ { "config": { "abort": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", + "already_configured_account": "Konto jest ju\u017c skonfigurowane", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "reauth_successful": "Token dost\u0119pu zosta\u0142 pomy\u015blnie zaktualizowany", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "error": { - "invalid_auth": "Niepoprawne uwierzytelnienie." + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "reauth": { diff --git a/homeassistant/components/sharkiq/translations/sv.json b/homeassistant/components/sharkiq/translations/sv.json new file mode 100644 index 00000000000000..75f4175c9af45a --- /dev/null +++ b/homeassistant/components/sharkiq/translations/sv.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "unknown": "Ov\u00e4ntat fel" + }, + "step": { + "reauth": { + "data": { + "password": "L\u00f6senord", + "username": "Anv\u00e4ndarnamn" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/shelly/__init__.py b/homeassistant/components/shelly/__init__.py index 83d5d7b9f3afcc..c63858f06523e2 100644 --- a/homeassistant/components/shelly/__init__.py +++ b/homeassistant/components/shelly/__init__.py @@ -40,7 +40,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): temperature_unit, ) try: - async with async_timeout.timeout(5): + async with async_timeout.timeout(10): device = await aioshelly.Device.create( aiohttp_client.async_get_clientsession(hass), options, @@ -86,7 +86,7 @@ async def _async_update_data(self): try: async with async_timeout.timeout(5): return await self.device.update() - except aiocoap_error.Error as err: + except (aiocoap_error.Error, OSError) as err: raise update_coordinator.UpdateFailed("Error fetching data") from err @property diff --git a/homeassistant/components/shelly/binary_sensor.py b/homeassistant/components/shelly/binary_sensor.py index c9a13249aa8688..1460c62f153d1c 100644 --- a/homeassistant/components/shelly/binary_sensor.py +++ b/homeassistant/components/shelly/binary_sensor.py @@ -20,31 +20,31 @@ name="Overheating", device_class=DEVICE_CLASS_PROBLEM ), ("device", "overpower"): BlockAttributeDescription( - name="Over Power", device_class=DEVICE_CLASS_PROBLEM + name="Overpowering", device_class=DEVICE_CLASS_PROBLEM ), ("light", "overpower"): BlockAttributeDescription( - name="Over Power", device_class=DEVICE_CLASS_PROBLEM + name="Overpowering", device_class=DEVICE_CLASS_PROBLEM ), ("relay", "overpower"): BlockAttributeDescription( - name="Over Power", device_class=DEVICE_CLASS_PROBLEM + name="Overpowering", device_class=DEVICE_CLASS_PROBLEM ), ("sensor", "dwIsOpened"): BlockAttributeDescription( name="Door", device_class=DEVICE_CLASS_OPENING ), ("sensor", "flood"): BlockAttributeDescription( - name="flood", device_class=DEVICE_CLASS_MOISTURE + name="Flood", device_class=DEVICE_CLASS_MOISTURE ), ("sensor", "gas"): BlockAttributeDescription( - name="gas", + name="Gas", device_class=DEVICE_CLASS_GAS, value=lambda value: value in ["mild", "heavy"], device_state_attributes=lambda block: {"detected": block.gas}, ), ("sensor", "smoke"): BlockAttributeDescription( - name="smoke", device_class=DEVICE_CLASS_SMOKE + name="Smoke", device_class=DEVICE_CLASS_SMOKE ), ("sensor", "vibration"): BlockAttributeDescription( - name="vibration", device_class=DEVICE_CLASS_VIBRATION + name="Vibration", device_class=DEVICE_CLASS_VIBRATION ), } diff --git a/homeassistant/components/shelly/config_flow.py b/homeassistant/components/shelly/config_flow.py index 6446d2dd2d2e59..b13c4090a1095a 100644 --- a/homeassistant/components/shelly/config_flow.py +++ b/homeassistant/components/shelly/config_flow.py @@ -8,7 +8,12 @@ import voluptuous as vol from homeassistant import config_entries, core -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import ( + CONF_HOST, + CONF_PASSWORD, + CONF_USERNAME, + HTTP_UNAUTHORIZED, +) from homeassistant.helpers import aiohttp_client from .const import DOMAIN # pylint:disable=unused-import @@ -57,6 +62,8 @@ async def async_step_user(self, user_input=None): info = await self._async_get_info(host) except HTTP_CONNECT_ERRORS: errors["base"] = "cannot_connect" + except aioshelly.FirmwareUnsupported: + return self.async_abort(reason="unsupported_firmware") except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" @@ -91,7 +98,7 @@ async def async_step_credentials(self, user_input=None): try: device_info = await validate_input(self.hass, self.host, user_input) except aiohttp.ClientResponseError as error: - if error.status == 401: + if error.status == HTTP_UNAUTHORIZED: errors["base"] = "invalid_auth" else: errors["base"] = "cannot_connect" @@ -128,6 +135,8 @@ async def async_step_zeroconf(self, zeroconf_info): self.info = info = await self._async_get_info(zeroconf_info["host"]) except HTTP_CONNECT_ERRORS: return self.async_abort(reason="cannot_connect") + except aioshelly.FirmwareUnsupported: + return self.async_abort(reason="unsupported_firmware") await self.async_set_unique_id(info["mac"]) self._abort_if_unique_id_configured({CONF_HOST: zeroconf_info["host"]}) diff --git a/homeassistant/components/shelly/entity.py b/homeassistant/components/shelly/entity.py index b1a61bbbf59e70..237deec4da1460 100644 --- a/homeassistant/components/shelly/entity.py +++ b/homeassistant/components/shelly/entity.py @@ -20,6 +20,47 @@ def temperature_unit(block_info: dict) -> str: return TEMP_CELSIUS +def shelly_naming(self, block, entity_type: str): + """Naming for switch and sensors.""" + + entity_name = self.wrapper.name + if not block: + return f"{entity_name} {self.description.name}" + + channels = 0 + mode = "relays" + if "num_outputs" in self.wrapper.device.shelly: + channels = self.wrapper.device.shelly["num_outputs"] + if ( + self.wrapper.model in ["SHSW-21", "SHSW-25"] + and self.wrapper.device.settings["mode"] == "roller" + ): + channels = 1 + if block.type == "emeter" and "num_emeters" in self.wrapper.device.shelly: + channels = self.wrapper.device.shelly["num_emeters"] + mode = "emeters" + if channels > 1 and block.type != "device": + # Shelly EM (SHEM) with firmware v1.8.1 doesn't have "name" key; will be fixed in next firmware release + if "name" in self.wrapper.device.settings[mode][int(block.channel)]: + entity_name = self.wrapper.device.settings[mode][int(block.channel)]["name"] + else: + entity_name = None + if not entity_name: + if self.wrapper.model == "SHEM-3": + base = ord("A") + else: + base = ord("1") + entity_name = f"{self.wrapper.name} channel {chr(int(block.channel)+base)}" + + if entity_type == "switch": + return entity_name + + if entity_type == "sensor": + return f"{entity_name} {self.description.name}" + + raise ValueError + + async def async_setup_entry_attribute_entities( hass, config_entry, async_add_entities, sensors, sensor_class ): @@ -42,11 +83,11 @@ async def async_setup_entry_attribute_entities( if not blocks: return - counts = Counter([item[0].type for item in blocks]) + counts = Counter([item[1] for item in blocks]) async_add_entities( [ - sensor_class(wrapper, block, sensor_id, description, counts[block.type]) + sensor_class(wrapper, block, sensor_id, description, counts[sensor_id]) for block, sensor_id, description in blocks ] ) @@ -75,7 +116,7 @@ def __init__(self, wrapper: ShellyDeviceWrapper, block): """Initialize Shelly entity.""" self.wrapper = wrapper self.block = block - self._name = f"{self.wrapper.name} {self.block.description.replace('_', ' ')}" + self._name = shelly_naming(self, block, "switch") @property def name(self): @@ -142,13 +183,7 @@ def __init__( self._unit = unit self._unique_id = f"{super().unique_id}-{self.attribute}" - - name_parts = [self.wrapper.name] - if same_type_count > 1: - name_parts.append(str(block.channel)) - name_parts.append(self.description.name) - - self._name = " ".join(name_parts) + self._name = shelly_naming(self, block, "sensor") @property def unique_id(self): diff --git a/homeassistant/components/shelly/manifest.json b/homeassistant/components/shelly/manifest.json index 38ccc9e0f74934..3816d31222eae4 100644 --- a/homeassistant/components/shelly/manifest.json +++ b/homeassistant/components/shelly/manifest.json @@ -3,7 +3,7 @@ "name": "Shelly", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/shelly", - "requirements": ["aioshelly==0.3.1"], - "zeroconf": [{"type": "_http._tcp.local.", "name":"shelly*"}], + "requirements": ["aioshelly==0.3.4"], + "zeroconf": [{ "type": "_http._tcp.local.", "name": "shelly*" }], "codeowners": ["@balloob", "@bieniu"] } diff --git a/homeassistant/components/shelly/sensor.py b/homeassistant/components/shelly/sensor.py index 8a24a6380ed803..e82f167ca6055e 100644 --- a/homeassistant/components/shelly/sensor.py +++ b/homeassistant/components/shelly/sensor.py @@ -5,8 +5,10 @@ DEGREE, ELECTRICAL_CURRENT_AMPERE, ENERGY_KILO_WATT_HOUR, + LIGHT_LUX, PERCENTAGE, POWER_WATT, + VOLT, ) from .entity import ( @@ -52,12 +54,30 @@ value=lambda value: round(value, 1), device_class=sensor.DEVICE_CLASS_POWER, ), + ("emeter", "voltage"): BlockAttributeDescription( + name="Voltage", + unit=VOLT, + value=lambda value: round(value, 1), + device_class=sensor.DEVICE_CLASS_VOLTAGE, + ), + ("emeter", "powerFactor"): BlockAttributeDescription( + name="Power Factor", + unit=PERCENTAGE, + value=lambda value: round(value * 100, 1), + device_class=sensor.DEVICE_CLASS_POWER_FACTOR, + ), ("relay", "power"): BlockAttributeDescription( name="Power", unit=POWER_WATT, value=lambda value: round(value, 1), device_class=sensor.DEVICE_CLASS_POWER, ), + ("roller", "rollerPower"): BlockAttributeDescription( + name="Power", + unit=POWER_WATT, + value=lambda value: round(value, 1), + device_class=sensor.DEVICE_CLASS_POWER, + ), ("device", "energy"): BlockAttributeDescription( name="Energy", unit=ENERGY_KILO_WATT_HOUR, @@ -70,6 +90,12 @@ value=lambda value: round(value / 1000, 2), device_class=sensor.DEVICE_CLASS_ENERGY, ), + ("emeter", "energyReturned"): BlockAttributeDescription( + name="Energy Returned", + unit=ENERGY_KILO_WATT_HOUR, + value=lambda value: round(value / 1000, 2), + device_class=sensor.DEVICE_CLASS_ENERGY, + ), ("light", "energy"): BlockAttributeDescription( name="Energy", unit=ENERGY_KILO_WATT_HOUR, @@ -83,6 +109,12 @@ value=lambda value: round(value / 60 / 1000, 2), device_class=sensor.DEVICE_CLASS_ENERGY, ), + ("roller", "rollerEnergy"): BlockAttributeDescription( + name="Energy", + unit=ENERGY_KILO_WATT_HOUR, + value=lambda value: round(value / 60 / 1000, 2), + device_class=sensor.DEVICE_CLASS_ENERGY, + ), ("sensor", "concentration"): BlockAttributeDescription( name="Gas Concentration", unit=CONCENTRATION_PARTS_PER_MILLION, @@ -104,7 +136,7 @@ ), ("sensor", "luminosity"): BlockAttributeDescription( name="Luminosity", - unit="lx", + unit=LIGHT_LUX, device_class=sensor.DEVICE_CLASS_ILLUMINANCE, ), ("sensor", "tilt"): BlockAttributeDescription(name="tilt", unit=DEGREE), diff --git a/homeassistant/components/shelly/strings.json b/homeassistant/components/shelly/strings.json index 16dc331e4523f8..69a0920422735a 100644 --- a/homeassistant/components/shelly/strings.json +++ b/homeassistant/components/shelly/strings.json @@ -4,6 +4,7 @@ "flow_title": "Shelly: {name}", "step": { "user": { + "description": "Before set up, the battery-powered device must be woken up by pressing the button on the device.", "data": { "host": "[%key:common::config_flow::data::host%]" } @@ -15,7 +16,7 @@ } }, "confirm_discovery": { - "description": "Do you want to set up the {model} at {host}?" + "description": "Do you want to set up the {model} at {host}?\n\nBefore set up, the battery-powered device must be woken up by pressing the button on the device." } }, "error": { @@ -24,7 +25,8 @@ "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "unsupported_firmware": "The device is using an unsupported firmware version." } } } diff --git a/homeassistant/components/shelly/switch.py b/homeassistant/components/shelly/switch.py index 0aaa6dbc911bee..0dcefb51cf991b 100644 --- a/homeassistant/components/shelly/switch.py +++ b/homeassistant/components/shelly/switch.py @@ -14,7 +14,10 @@ async def async_setup_entry(hass, config_entry, async_add_entities): wrapper = hass.data[DOMAIN][config_entry.entry_id] # In roller mode the relay blocks exist but do not contain required info - if wrapper.model == "SHSW-25" and wrapper.device.settings["mode"] != "relay": + if ( + wrapper.model in ["SHSW-21", "SHSW-25"] + and wrapper.device.settings["mode"] != "relay" + ): return relay_blocks = [block for block in wrapper.device.blocks if block.type == "relay"] diff --git a/homeassistant/components/shelly/translations/ca.json b/homeassistant/components/shelly/translations/ca.json index 7e8c3873c11141..41ffcb34b89f1c 100644 --- a/homeassistant/components/shelly/translations/ca.json +++ b/homeassistant/components/shelly/translations/ca.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "El dispositiu ja est\u00e0 configurat" + "already_configured": "El dispositiu ja est\u00e0 configurat", + "unsupported_firmware": "El dispositiu utilitza una versi\u00f3 de programari no compatible." }, "error": { "auth_not_supported": "Actualment els dispositius Shelly amb autenticaci\u00f3 no son compatibles.", @@ -12,7 +13,7 @@ "flow_title": "Shelly: {name}", "step": { "confirm_discovery": { - "description": "Voleu configurar {model} a {host}?" + "description": "Vols configurar {model} a {host}? \n\nAbans de configurar-lo, el dispositiu amb bateria ha d'estar despert, prem el bot\u00f3 del dispositiu per despertar-lo." }, "credentials": { "data": { @@ -23,7 +24,8 @@ "user": { "data": { "host": "Amfitri\u00f3" - } + }, + "description": "Abans de configurar-lo, el dispositiu amb bateria ha d'estar despert, prem el bot\u00f3 del dispositiu per despertar-lo." } } }, diff --git a/homeassistant/components/shelly/translations/de.json b/homeassistant/components/shelly/translations/de.json new file mode 100644 index 00000000000000..aad0d1fa47dcf5 --- /dev/null +++ b/homeassistant/components/shelly/translations/de.json @@ -0,0 +1,19 @@ +{ + "config": { + "flow_title": "Shelly: {name}", + "step": { + "credentials": { + "data": { + "password": "Passwort", + "username": "Benutzername" + } + }, + "user": { + "data": { + "host": "Host" + } + } + } + }, + "title": "Shelly" +} \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/el.json b/homeassistant/components/shelly/translations/el.json new file mode 100644 index 00000000000000..753e7ee7505a8f --- /dev/null +++ b/homeassistant/components/shelly/translations/el.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "unsupported_firmware": "\u0397 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b5\u03af \u03bc\u03b9\u03b1 \u03bc\u03b7 \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03b9\u03b6\u03cc\u03bc\u03b5\u03bd\u03b7 \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7 \u03c5\u03bb\u03b9\u03ba\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03bc\u03b9\u03ba\u03bf\u03cd." + }, + "error": { + "auth_not_supported": "\u039f\u03b9 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ad\u03c2 Shelly \u03c0\u03bf\u03c5 \u03b1\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bd \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03bf\u03bd\u03c4\u03b1\u03b9 \u03c0\u03c1\u03bf\u03c2 \u03c4\u03bf \u03c0\u03b1\u03c1\u03cc\u03bd." + }, + "flow_title": "Shelly: {\u03cc\u03bd\u03bf\u03bc\u03b1}", + "step": { + "confirm_discovery": { + "description": "\u0398\u03ad\u03bb\u03b5\u03c4\u03b5 \u03bd\u03b1 \u03c1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf {model} \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 {host};\n\n\u03a0\u03c1\u03b9\u03bd \u03b1\u03c0\u03cc \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7, \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03bc\u03b5 \u03bc\u03c0\u03b1\u03c4\u03b1\u03c1\u03af\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03be\u03c5\u03c0\u03bd\u03ae\u03c3\u03b5\u03b9 \u03c0\u03b1\u03c4\u03ce\u03bd\u03c4\u03b1\u03c2 \u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae." + }, + "user": { + "description": "\u03a0\u03c1\u03b9\u03bd \u03b1\u03c0\u03cc \u03c4\u03b7 \u03c1\u03cd\u03b8\u03bc\u03b9\u03c3\u03b7, \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03bc\u03b5 \u03bc\u03c0\u03b1\u03c4\u03b1\u03c1\u03af\u03b1 \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03be\u03c5\u03c0\u03bd\u03ae\u03c3\u03b5\u03b9 \u03c0\u03b1\u03c4\u03ce\u03bd\u03c4\u03b1\u03c2 \u03c4\u03bf \u03ba\u03bf\u03c5\u03bc\u03c0\u03af \u03c3\u03c4\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae." + } + } + }, + "title": "Shelly" +} \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/en.json b/homeassistant/components/shelly/translations/en.json index 546007af0d1cdd..2727cfbceeb08e 100644 --- a/homeassistant/components/shelly/translations/en.json +++ b/homeassistant/components/shelly/translations/en.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Device is already configured" + "already_configured": "Device is already configured", + "unsupported_firmware": "The device is using an unsupported firmware version." }, "error": { "auth_not_supported": "Shelly devices requiring authentication are not currently supported.", @@ -12,7 +13,7 @@ "flow_title": "Shelly: {name}", "step": { "confirm_discovery": { - "description": "Do you want to set up the {model} at {host}?" + "description": "Do you want to set up the {model} at {host}?\n\nBefore set up, the battery-powered device must be woken up by pressing the button on the device." }, "credentials": { "data": { @@ -23,7 +24,8 @@ "user": { "data": { "host": "Host" - } + }, + "description": "Before set up, the battery-powered device must be woken up by pressing the button on the device." } } }, diff --git a/homeassistant/components/shelly/translations/es.json b/homeassistant/components/shelly/translations/es.json index bdc05b734ba8b3..814586abdaea1a 100644 --- a/homeassistant/components/shelly/translations/es.json +++ b/homeassistant/components/shelly/translations/es.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "El dispositivo ya est\u00e1 configurado" + "already_configured": "El dispositivo ya est\u00e1 configurado", + "unsupported_firmware": "El dispositivo est\u00e1 usando una versi\u00f3n de firmware no compatible." }, "error": { "auth_not_supported": "Los dispositivos Shelly que requieren autenticaci\u00f3n no son compatibles actualmente.", @@ -23,7 +24,8 @@ "user": { "data": { "host": "Host" - } + }, + "description": "Antes de configurarlo, el dispositivo que funciona con bater\u00eda debe despertarse presionando el bot\u00f3n del dispositivo." } } }, diff --git a/homeassistant/components/shelly/translations/hu.json b/homeassistant/components/shelly/translations/hu.json new file mode 100644 index 00000000000000..3b2d79a34a77e2 --- /dev/null +++ b/homeassistant/components/shelly/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/it.json b/homeassistant/components/shelly/translations/it.json index 595a57b0a00210..8f878924010c01 100644 --- a/homeassistant/components/shelly/translations/it.json +++ b/homeassistant/components/shelly/translations/it.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "unsupported_firmware": "Il dispositivo utilizza una versione del firmware non supportata." }, "error": { "auth_not_supported": "I dispositivi Shelly che richiedono l'autenticazione non sono attualmente supportati.", @@ -12,7 +13,7 @@ "flow_title": "Shelly: {name}", "step": { "confirm_discovery": { - "description": "Vuoi impostare {model} su {host}?" + "description": "Vuoi impostare il {model} presso {host}?\n\nPrima di configurare, il dispositivo alimentato a batteria deve essere svegliato premendo il pulsante sul dispositivo." }, "credentials": { "data": { @@ -23,7 +24,8 @@ "user": { "data": { "host": "Host" - } + }, + "description": "Prima di configurare, il dispositivo alimentato a batteria deve essere svegliato premendo il pulsante sul dispositivo." } } }, diff --git a/homeassistant/components/shelly/translations/ko.json b/homeassistant/components/shelly/translations/ko.json new file mode 100644 index 00000000000000..5fb84e0ac90486 --- /dev/null +++ b/homeassistant/components/shelly/translations/ko.json @@ -0,0 +1,18 @@ +{ + "config": { + "abort": { + "unsupported_firmware": "\uc774 \uc7a5\uce58\ub294 \uc9c0\uc6d0\ub418\uc9c0 \uc54a\ub294 \ud38c\uc6e8\uc5b4 \ubc84\uc804\uc744 \uc0ac\uc6a9\ud558\uace0 \uc788\uc2b5\ub2c8\ub2e4." + }, + "error": { + "invalid_auth": "\uc798\ubabb\ub41c \uc778\uc99d" + }, + "step": { + "credentials": { + "data": { + "password": "\uc554\ud638", + "username": "\uc0ac\uc6a9\uc790\uba85" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/lb.json b/homeassistant/components/shelly/translations/lb.json index b50f528c3c0058..714b42e9fcce2a 100644 --- a/homeassistant/components/shelly/translations/lb.json +++ b/homeassistant/components/shelly/translations/lb.json @@ -1,11 +1,13 @@ { "config": { "abort": { - "already_configured": "Apparat ass scho konfigur\u00e9iert" + "already_configured": "Apparat ass scho konfigur\u00e9iert", + "unsupported_firmware": "Den Apparat benotzt eng net \u00ebnnerst\u00ebtzte Firmware Versioun." }, "error": { "auth_not_supported": "Shelly Apparaten d\u00e9i eng Authentifikatioun ben\u00e9idegen ginn aktuell net \u00ebnnerst\u00ebtzt.", "cannot_connect": "Feeler beim verbannen", + "invalid_auth": "Ong\u00eblteg Authentifikatioun", "unknown": "Onerwaarte Feeler" }, "flow_title": "Shelly: {name}", @@ -13,10 +15,17 @@ "confirm_discovery": { "description": "Soll de {model} um {host} konfigur\u00e9iert ginn?" }, + "credentials": { + "data": { + "password": "Passwuert", + "username": "Benotzernumm" + } + }, "user": { "data": { "host": "Host" - } + }, + "description": "Virum ariichten muss dat Batterie bedriwwen Ger\u00e4t aktiv\u00e9iert ginn andeems de Kn\u00e4ppchen um Apparat gedr\u00e9ckt g\u00ebtt." } } }, diff --git a/homeassistant/components/shelly/translations/nl.json b/homeassistant/components/shelly/translations/nl.json new file mode 100644 index 00000000000000..92a172ff081897 --- /dev/null +++ b/homeassistant/components/shelly/translations/nl.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + }, + "error": { + "auth_not_supported": "Shelly apparaten die verificatie vereisen, worden momenteel niet ondersteund.", + "cannot_connect": "Kan geen verbinding maken", + "unknown": "Onverwachte fout" + }, + "flow_title": "Shelly: {name}", + "step": { + "confirm_discovery": { + "description": "Wilt u het {model} bij {host} opzetten?" + }, + "credentials": { + "data": { + "username": "Benutzername" + } + }, + "user": { + "data": { + "host": "Host" + } + } + } + }, + "title": "Shelly" +} \ No newline at end of file diff --git a/homeassistant/components/shelly/translations/no.json b/homeassistant/components/shelly/translations/no.json index 898c05e89aa02a..ac5067d32735a3 100644 --- a/homeassistant/components/shelly/translations/no.json +++ b/homeassistant/components/shelly/translations/no.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "Enheten er allerede konfigurert" + "already_configured": "Enheten er allerede konfigurert", + "unsupported_firmware": "Enheten bruker en ikke-st\u00f8ttet firmwareversjon." }, "error": { "auth_not_supported": "Shelly-enheter som krever godkjenning st\u00f8ttes for \u00f8yeblikket ikke.", @@ -12,7 +13,7 @@ "flow_title": "Shelly: {name}", "step": { "confirm_discovery": { - "description": "Vil du konfigurere {model} p\u00e5 {host}?" + "description": "Vil du konfigurere {model} p\u00e5 {host} ?\n\n F\u00f8r du setter opp, m\u00e5 den batteridrevne enheten vekkes ved \u00e5 trykke p\u00e5 knappen p\u00e5 enheten." }, "credentials": { "data": { @@ -23,7 +24,8 @@ "user": { "data": { "host": "Vert" - } + }, + "description": "F\u00f8r du setter opp, m\u00e5 den batteridrevne enheten vekkes ved \u00e5 trykke p\u00e5 knappen p\u00e5 enheten." } } }, diff --git a/homeassistant/components/shelly/translations/pl.json b/homeassistant/components/shelly/translations/pl.json index 77a7f045671a71..2266fee5356456 100644 --- a/homeassistant/components/shelly/translations/pl.json +++ b/homeassistant/components/shelly/translations/pl.json @@ -1,18 +1,19 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "unsupported_firmware": "Urz\u0105dzenie u\u017cywa nieobs\u0142ugiwanej wersji firmware." }, "error": { "auth_not_supported": "Urz\u0105dzenia Shelly wymagaj\u0105ce uwierzytelnienia nie s\u0105 obecnie obs\u0142ugiwane.", - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", - "invalid_auth": "Niepoprawne uwierzytelnienie.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "flow_title": "Shelly: {name}", "step": { "confirm_discovery": { - "description": "Czy chcesz skonfigurowa\u0107 {model} ({host})?" + "description": "Czy chcesz skonfigurowa\u0107 {model} ({host})?\n\nPrzed konfiguracj\u0105 urz\u0105dzenie zasilane bateryjnie nale\u017cy wybudzi\u0107, naciskaj\u0105c przycisk na urz\u0105dzeniu." }, "credentials": { "data": { @@ -23,7 +24,8 @@ "user": { "data": { "host": "Nazwa hosta lub adres IP" - } + }, + "description": "Przed konfiguracj\u0105, urz\u0105dzenie zasilane bateryjnie nale\u017cy wybudzi\u0107, naciskaj\u0105c przycisk na urz\u0105dzeniu." } } }, diff --git a/homeassistant/components/shelly/translations/ru.json b/homeassistant/components/shelly/translations/ru.json index 570e6f8d7c7dee..24478afe0a4d9d 100644 --- a/homeassistant/components/shelly/translations/ru.json +++ b/homeassistant/components/shelly/translations/ru.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant." + "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant.", + "unsupported_firmware": "\u0412 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043d\u0435\u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u0430\u044f \u0432\u0435\u0440\u0441\u0438\u044f \u043f\u0440\u043e\u0448\u0438\u0432\u043a\u0438." }, "error": { "auth_not_supported": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 Shelly, \u0442\u0440\u0435\u0431\u0443\u044e\u0449\u0438\u0435 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438, \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\u044e\u0442\u0441\u044f.", @@ -12,7 +13,7 @@ "flow_title": "Shelly: {name}", "step": { "confirm_discovery": { - "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} ({host}) ?" + "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} ({host}) ?\n\n\u041f\u0435\u0440\u0435\u0434 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u043e\u0439 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430, \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0438\u0435 \u043e\u0442 \u0431\u0430\u0442\u0430\u0440\u0435\u0438, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0432\u044b\u0432\u0435\u0441\u0442\u0438 \u0438\u0437 \u0441\u043f\u044f\u0449\u0435\u0433\u043e \u0440\u0435\u0436\u0438\u043c\u0430, \u043d\u0430\u0436\u0430\u0432 \u043a\u043d\u043e\u043f\u043a\u0443 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435." }, "credentials": { "data": { @@ -23,7 +24,8 @@ "user": { "data": { "host": "\u0425\u043e\u0441\u0442" - } + }, + "description": "\u041f\u0435\u0440\u0435\u0434 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u043e\u0439 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430, \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0449\u0438\u0435 \u043e\u0442 \u0431\u0430\u0442\u0430\u0440\u0435\u0438, \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0432\u044b\u0432\u0435\u0441\u0442\u0438 \u0438\u0437 \u0441\u043f\u044f\u0449\u0435\u0433\u043e \u0440\u0435\u0436\u0438\u043c\u0430, \u043d\u0430\u0436\u0430\u0432 \u043a\u043d\u043e\u043f\u043a\u0443 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0435." } } }, diff --git a/homeassistant/components/shelly/translations/zh-Hant.json b/homeassistant/components/shelly/translations/zh-Hant.json index e8fe857c476eea..ebd7df5f9195a0 100644 --- a/homeassistant/components/shelly/translations/zh-Hant.json +++ b/homeassistant/components/shelly/translations/zh-Hant.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" + "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "unsupported_firmware": "\u8a2d\u5099\u4f7f\u7528\u7684\u97cc\u9ad4\u4e0d\u652f\u63f4\u3002" }, "error": { "auth_not_supported": "\u76ee\u524d\u4e0d\u652f\u63f4 Shelly \u8a2d\u5099\u6240\u9700\u8a8d\u8b49\u3002", @@ -12,7 +13,7 @@ "flow_title": "Shelly\uff1a{name}", "step": { "confirm_discovery": { - "description": "\u662f\u5426\u8981\u8a2d\u5b9a\u4f4d\u65bc {host} \u7684 {model}\uff1f" + "description": "\u662f\u5426\u8981\u8a2d\u5b9a\u4f4d\u65bc {host} \u7684 {model}\uff1f\n\n\u958b\u59cb\u8a2d\u5b9a\u524d\uff0c\u5fc5\u9808\u6309\u4e0b\u8a2d\u5099\u4e0a\u7684\u6309\u9215\u4ee5\u559a\u9192\u96fb\u6c60\u4f9b\u96fb\u8a2d\u5099\u3002" }, "credentials": { "data": { @@ -23,7 +24,8 @@ "user": { "data": { "host": "\u4e3b\u6a5f\u7aef" - } + }, + "description": "\u958b\u59cb\u8a2d\u5b9a\u524d\uff0c\u5fc5\u9808\u6309\u4e0b\u8a2d\u5099\u4e0a\u7684\u6309\u9215\u4ee5\u559a\u9192\u96fb\u6c60\u4f9b\u96fb\u8a2d\u5099\u3002" } } }, diff --git a/homeassistant/components/shiftr/manifest.json b/homeassistant/components/shiftr/manifest.json index 79189e6b047f29..21977c286d08bd 100644 --- a/homeassistant/components/shiftr/manifest.json +++ b/homeassistant/components/shiftr/manifest.json @@ -2,6 +2,6 @@ "domain": "shiftr", "name": "shiftr.io", "documentation": "https://www.home-assistant.io/integrations/shiftr", - "requirements": ["paho-mqtt==1.5.0"], + "requirements": ["paho-mqtt==1.5.1"], "codeowners": ["@fabaff"] } diff --git a/homeassistant/components/shopping_list/strings.json b/homeassistant/components/shopping_list/strings.json index 8c17d68388662b..5b8197177a02be 100644 --- a/homeassistant/components/shopping_list/strings.json +++ b/homeassistant/components/shopping_list/strings.json @@ -8,7 +8,7 @@ } }, "abort": { - "already_configured": "The shopping list is already configured." + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]" } } } diff --git a/homeassistant/components/shopping_list/translations/ca.json b/homeassistant/components/shopping_list/translations/ca.json index d384e46641d671..92a206a3e3ac78 100644 --- a/homeassistant/components/shopping_list/translations/ca.json +++ b/homeassistant/components/shopping_list/translations/ca.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La llista de compres ja est\u00e0 configurada." + "already_configured": "El servei ja est\u00e0 configurat" }, "step": { "user": { diff --git a/homeassistant/components/shopping_list/translations/en.json b/homeassistant/components/shopping_list/translations/en.json index e28b5076dcba8a..2da8b0db8d40f9 100644 --- a/homeassistant/components/shopping_list/translations/en.json +++ b/homeassistant/components/shopping_list/translations/en.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "The shopping list is already configured." + "already_configured": "Service is already configured" }, "step": { "user": { diff --git a/homeassistant/components/shopping_list/translations/it.json b/homeassistant/components/shopping_list/translations/it.json index c1b10bc84b69b8..bd267168c8f4ed 100644 --- a/homeassistant/components/shopping_list/translations/it.json +++ b/homeassistant/components/shopping_list/translations/it.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "La lista della spesa \u00e8 gi\u00e0 configurata." + "already_configured": "Il servizio \u00e8 gi\u00e0 configurato" }, "step": { "user": { diff --git a/homeassistant/components/shopping_list/translations/no.json b/homeassistant/components/shopping_list/translations/no.json index 56a92234c70af0..6bad2dd5774d18 100644 --- a/homeassistant/components/shopping_list/translations/no.json +++ b/homeassistant/components/shopping_list/translations/no.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Handlelisten er allerede konfigurert." + "already_configured": "Tjenesten er allerede konfigurert" }, "step": { "user": { diff --git a/homeassistant/components/shopping_list/translations/ru.json b/homeassistant/components/shopping_list/translations/ru.json index 84c6e2762f7a87..1dc4a274ab6b6b 100644 --- a/homeassistant/components/shopping_list/translations/ru.json +++ b/homeassistant/components/shopping_list/translations/ru.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0439 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "already_configured": "\u042d\u0442\u0430 \u0441\u043b\u0443\u0436\u0431\u0430 \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant." }, "step": { "user": { diff --git a/homeassistant/components/shopping_list/translations/zh-Hant.json b/homeassistant/components/shopping_list/translations/zh-Hant.json index dbb7d941b2d6f0..9c8520545824eb 100644 --- a/homeassistant/components/shopping_list/translations/zh-Hant.json +++ b/homeassistant/components/shopping_list/translations/zh-Hant.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "\u8cfc\u7269\u6e05\u55ae\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3002" + "already_configured": "\u670d\u52d9\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "step": { "user": { diff --git a/homeassistant/components/sigfox/sensor.py b/homeassistant/components/sigfox/sensor.py index 6df6e1d0c82f05..1de3cfeb8a0913 100644 --- a/homeassistant/components/sigfox/sensor.py +++ b/homeassistant/components/sigfox/sensor.py @@ -8,7 +8,7 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_NAME, HTTP_OK +from homeassistant.const import CONF_NAME, HTTP_OK, HTTP_UNAUTHORIZED import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -67,7 +67,7 @@ def check_credentials(self): url = urljoin(API_URL, "devicetypes") response = requests.get(url, auth=self._auth, timeout=10) if response.status_code != HTTP_OK: - if response.status_code == 401: + if response.status_code == HTTP_UNAUTHORIZED: _LOGGER.error("Invalid credentials for Sigfox API") else: _LOGGER.error( diff --git a/homeassistant/components/sighthound/image_processing.py b/homeassistant/components/sighthound/image_processing.py index 7e9e789423ebba..e15fab1aaa3139 100644 --- a/homeassistant/components/sighthound/image_processing.py +++ b/homeassistant/components/sighthound/image_processing.py @@ -172,7 +172,6 @@ def unit_of_measurement(self): @property def device_state_attributes(self): """Return the attributes.""" - attr = {} - if self._last_detection: - attr["last_person"] = self._last_detection - return attr + if not self._last_detection: + return {} + return {"last_person": self._last_detection} diff --git a/homeassistant/components/simplisafe/manifest.json b/homeassistant/components/simplisafe/manifest.json index dd9ab53cb98fcd..613187ef7446ec 100644 --- a/homeassistant/components/simplisafe/manifest.json +++ b/homeassistant/components/simplisafe/manifest.json @@ -3,6 +3,6 @@ "name": "SimpliSafe", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/simplisafe", - "requirements": ["simplisafe-python==9.3.0"], + "requirements": ["simplisafe-python==9.4.1"], "codeowners": ["@bachya"] } diff --git a/homeassistant/components/simplisafe/strings.json b/homeassistant/components/simplisafe/strings.json index 7f724de9db5f77..44b69bdf6bf9ce 100644 --- a/homeassistant/components/simplisafe/strings.json +++ b/homeassistant/components/simplisafe/strings.json @@ -29,7 +29,7 @@ }, "abort": { "already_configured": "This SimpliSafe account is already in use.", - "reauth_successful": "SimpliSafe successfully reauthenticated." + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" } }, "options": { diff --git a/homeassistant/components/simplisafe/translations/ca.json b/homeassistant/components/simplisafe/translations/ca.json index bdfd5d76198725..590f4bbc630e14 100644 --- a/homeassistant/components/simplisafe/translations/ca.json +++ b/homeassistant/components/simplisafe/translations/ca.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Aquest compte SimpliSafe ja est\u00e0 en \u00fas.", - "reauth_successful": "Reautenticaci\u00f3 amb SimpliSafe exitosa." + "reauth_successful": "Re-autenticaci\u00f3 amb SimpliSafe exitosa." }, "error": { "identifier_exists": "Aquest compte ja est\u00e0 registrat", diff --git a/homeassistant/components/simplisafe/translations/de.json b/homeassistant/components/simplisafe/translations/de.json index 42fc575f650d43..6b71d78673c121 100644 --- a/homeassistant/components/simplisafe/translations/de.json +++ b/homeassistant/components/simplisafe/translations/de.json @@ -8,6 +8,11 @@ "invalid_credentials": "Ung\u00fcltige Anmeldeinformationen" }, "step": { + "reauth_confirm": { + "data": { + "password": "Passwort" + } + }, "user": { "data": { "code": "Code (wird in der Benutzeroberfl\u00e4che von Home Assistant verwendet)", diff --git a/homeassistant/components/simplisafe/translations/fr.json b/homeassistant/components/simplisafe/translations/fr.json index 9f62eb20823c45..54f89eb3ab4d5e 100644 --- a/homeassistant/components/simplisafe/translations/fr.json +++ b/homeassistant/components/simplisafe/translations/fr.json @@ -1,17 +1,26 @@ { "config": { "abort": { - "already_configured": "Ce compte SimpliSafe est d\u00e9j\u00e0 utilis\u00e9." + "already_configured": "Ce compte SimpliSafe est d\u00e9j\u00e0 utilis\u00e9.", + "reauth_successful": "SimpliSafe a \u00e9t\u00e9 r\u00e9 authentifi\u00e9 avec succ\u00e8s." }, "error": { "identifier_exists": "Compte d\u00e9j\u00e0 enregistr\u00e9", - "invalid_credentials": "Informations d'identification invalides" + "invalid_credentials": "Informations d'identification invalides", + "still_awaiting_mfa": "En attente de clic sur le message \u00e9lectronique d'authentification multi facteur", + "unknown": "Erreur inattendue" }, "step": { + "mfa": { + "description": "V\u00e9rifiez votre messagerie pour un lien de SimpliSafe. Apr\u00e8s avoir v\u00e9rifi\u00e9 le lien, revenez ici pour terminer l'installation de l'int\u00e9gration.", + "title": "Authentification multi facteur SimpliSafe" + }, "reauth_confirm": { "data": { "password": "Mot de passe" - } + }, + "description": "Votre jeton d'acc\u00e8s a expir\u00e9 ou a \u00e9t\u00e9 r\u00e9voqu\u00e9. Entrez votre mot de passe pour r\u00e9 associer votre compte.", + "title": "Relier le compte SimpliSafe" }, "user": { "data": { diff --git a/homeassistant/components/simplisafe/translations/pl.json b/homeassistant/components/simplisafe/translations/pl.json index 0562222eea32ba..13f6aa5ffb1d15 100644 --- a/homeassistant/components/simplisafe/translations/pl.json +++ b/homeassistant/components/simplisafe/translations/pl.json @@ -1,13 +1,26 @@ { "config": { "abort": { - "already_configured": "To konto SimpliSafe jest ju\u017c w u\u017cyciu." + "already_configured": "To konto SimpliSafe jest ju\u017c w u\u017cyciu.", + "reauth_successful": "SimpliSafe zosta\u0142 ponownie uwierzytelniony." }, "error": { "identifier_exists": "Konto jest ju\u017c zarejestrowane.", - "invalid_credentials": "Nieprawid\u0142owe dane uwierzytelniaj\u0105ce" + "invalid_credentials": "Nieprawid\u0142owe dane uwierzytelniaj\u0105ce", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { + "mfa": { + "description": "Sprawd\u017a email od SimpliSafe. Po zweryfikowaniu linka, wr\u00f3\u0107 tutaj, aby doko\u0144czy\u0107 instalacj\u0119 integracji.", + "title": "Uwierzytelnianie wielosk\u0142adnikowe SimpliSafe" + }, + "reauth_confirm": { + "data": { + "password": "Has\u0142o" + }, + "description": "Tw\u00f3j token dost\u0119pu wygas\u0142 lub zosta\u0142 uniewa\u017cniony. Wprowad\u017a has\u0142o, aby ponownie po\u0142\u0105czy\u0107 swoje konto.", + "title": "Po\u0142\u0105cz ponownie konto SimpliSafe" + }, "user": { "data": { "code": "Kod (u\u017cywany w interfejsie Home Assistanta)", diff --git a/homeassistant/components/simulated/sensor.py b/homeassistant/components/simulated/sensor.py index d05448a82c7fd4..fba8b497d51243 100644 --- a/homeassistant/components/simulated/sensor.py +++ b/homeassistant/components/simulated/sensor.py @@ -142,7 +142,7 @@ def unit_of_measurement(self): @property def device_state_attributes(self): """Return other details about the sensor state.""" - attr = { + return { "amplitude": self._amp, "mean": self._mean, "period": self._period, @@ -151,4 +151,3 @@ def device_state_attributes(self): "seed": self._seed, "relative_to_epoch": self._relative_to_epoch, } - return attr diff --git a/homeassistant/components/skybell/__init__.py b/homeassistant/components/skybell/__init__.py index 9606d9bcf12fb3..c1c9d76314c901 100644 --- a/homeassistant/components/skybell/__init__.py +++ b/homeassistant/components/skybell/__init__.py @@ -77,11 +77,6 @@ def __init__(self, device): """Initialize a sensor for Skybell device.""" self._device = device - @property - def should_poll(self): - """Return the polling state.""" - return True - def update(self): """Update automation state.""" self._device.refresh() diff --git a/homeassistant/components/skybell/binary_sensor.py b/homeassistant/components/skybell/binary_sensor.py index a5c6681eb2bf62..94f64a4eb43571 100644 --- a/homeassistant/components/skybell/binary_sensor.py +++ b/homeassistant/components/skybell/binary_sensor.py @@ -4,7 +4,12 @@ import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_MOTION, + DEVICE_CLASS_OCCUPANCY, + PLATFORM_SCHEMA, + BinarySensorEntity, +) from homeassistant.const import CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv @@ -16,8 +21,8 @@ # Sensor types: Name, device_class, event SENSOR_TYPES = { - "button": ["Button", "occupancy", "device:sensor:button"], - "motion": ["Motion", "motion", "device:sensor:motion"], + "button": ["Button", DEVICE_CLASS_OCCUPANCY, "device:sensor:button"], + "motion": ["Motion", DEVICE_CLASS_MOTION, "device:sensor:motion"], } PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( diff --git a/homeassistant/components/slack/notify.py b/homeassistant/components/slack/notify.py index d43a1fc25ad911..8fb45c36d9f52c 100644 --- a/homeassistant/components/slack/notify.py +++ b/homeassistant/components/slack/notify.py @@ -202,22 +202,24 @@ async def _async_send_text_only_message( self, targets, message, title, blocks, username, icon ): """Send a text-only message.""" - if self._icon.lower().startswith(("http://", "https://")): - icon_type = "url" - else: - icon_type = "emoji" + message_dict = { + "blocks": blocks, + "link_names": True, + "text": message, + "username": username, + } + + icon = icon or self._icon + if icon: + if icon.lower().startswith(("http://", "https://")): + icon_type = "url" + else: + icon_type = "emoji" + + message_dict[f"icon_{icon_type}"] = icon tasks = { - target: self._client.chat_postMessage( - **{ - "blocks": blocks, - "channel": target, - "link_names": True, - "text": message, - "username": username, - f"icon_{icon_type}": icon, - } - ) + target: self._client.chat_postMessage(**message_dict, channel=target) for target in targets } diff --git a/homeassistant/components/sleepiq/binary_sensor.py b/homeassistant/components/sleepiq/binary_sensor.py index 39ae3e7c6584b7..cfbd6f576bef97 100644 --- a/homeassistant/components/sleepiq/binary_sensor.py +++ b/homeassistant/components/sleepiq/binary_sensor.py @@ -1,5 +1,8 @@ """Support for SleepIQ sensors.""" -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_OCCUPANCY, + BinarySensorEntity, +) from . import SleepIQSensor from .const import DOMAIN, IS_IN_BED, SENSOR_TYPES, SIDES @@ -39,7 +42,7 @@ def is_on(self): @property def device_class(self): """Return the class of this sensor.""" - return "occupancy" + return DEVICE_CLASS_OCCUPANCY def update(self): """Get the latest data from SleepIQ and updates the states.""" diff --git a/homeassistant/components/smappee/binary_sensor.py b/homeassistant/components/smappee/binary_sensor.py index ecc00f12370088..49d21f2b2c14a5 100644 --- a/homeassistant/components/smappee/binary_sensor.py +++ b/homeassistant/components/smappee/binary_sensor.py @@ -1,7 +1,10 @@ """Support for monitoring a Smappee appliance binary sensor.""" import logging -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_PRESENCE, + BinarySensorEntity, +) from .const import DOMAIN @@ -58,7 +61,7 @@ def is_on(self): @property def device_class(self): """Return the class of this device, from component DEVICE_CLASSES.""" - return "presence" + return DEVICE_CLASS_PRESENCE @property def unique_id( @@ -68,7 +71,7 @@ def unique_id( return ( f"{self._service_location.device_serial_number}-" f"{self._service_location.service_location_id}-" - f"presence" + f"{DEVICE_CLASS_PRESENCE}" ) @property diff --git a/homeassistant/components/smappee/config_flow.py b/homeassistant/components/smappee/config_flow.py index 26a47815f346dd..a31dac6912e282 100644 --- a/homeassistant/components/smappee/config_flow.py +++ b/homeassistant/components/smappee/config_flow.py @@ -94,7 +94,7 @@ async def async_step_zeroconf_confirm(self, user_input=None): smappee_api = api.api.SmappeeLocalApi(ip=ip_address) logon = await self.hass.async_add_executor_job(smappee_api.logon) if logon is None: - return self.async_abort(reason="connection_error") + return self.async_abort(reason="cannot_connect") return self.async_create_entry( title=f"{DOMAIN}{serial_number}", @@ -149,7 +149,7 @@ async def async_step_local(self, user_input=None): smappee_api = api.api.SmappeeLocalApi(ip=ip_address) logon = await self.hass.async_add_executor_job(smappee_api.logon) if logon is None: - return self.async_abort(reason="connection_error") + return self.async_abort(reason="cannot_connect") advanced_config = await self.hass.async_add_executor_job( smappee_api.load_advanced_config diff --git a/homeassistant/components/smappee/strings.json b/homeassistant/components/smappee/strings.json index 1bec8fda0cc20b..25fa28461173af 100644 --- a/homeassistant/components/smappee/strings.json +++ b/homeassistant/components/smappee/strings.json @@ -19,15 +19,15 @@ "title": "Discovered Smappee device" }, "pick_implementation": { - "title": "Pick Authentication Method" + "title": "[%key:common::config_flow::title::oauth2_pick_implementation%]" } }, "abort": { "already_configured_device": "[%key:common::config_flow::abort::already_configured_device%]", "already_configured_local_device": "Local device(s) is already configured. Please remove those first before configuring a cloud device.", - "authorize_url_timeout": "Timeout generating authorize url.", - "connection_error": "Failed to connect to Smappee device.", - "missing_configuration": "The component is not configured. Please follow the documentation.", + "authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]", "invalid_mdns": "Unsupported device for the Smappee integration.", "no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]" } diff --git a/homeassistant/components/smappee/translations/ca.json b/homeassistant/components/smappee/translations/ca.json index df15bdf3ed42f2..ee905f0ca84d50 100644 --- a/homeassistant/components/smappee/translations/ca.json +++ b/homeassistant/components/smappee/translations/ca.json @@ -24,7 +24,7 @@ "description": "Introdueix l'amfitri\u00f3 per iniciar la integraci\u00f3 local de Smappee" }, "pick_implementation": { - "title": "Selecciona un m\u00e8tode d'autenticaci\u00f3" + "title": "Selecci\u00f3 del m\u00e8tode d'autenticaci\u00f3" }, "zeroconf_confirm": { "description": "Vols afegir el dispositiu Smappee amb n\u00famero de s\u00e8rie `{serialnumber}` a Home Assistant?", diff --git a/homeassistant/components/smappee/translations/de.json b/homeassistant/components/smappee/translations/de.json new file mode 100644 index 00000000000000..0e77c8fbd7a44e --- /dev/null +++ b/homeassistant/components/smappee/translations/de.json @@ -0,0 +1,12 @@ +{ + "config": { + "flow_title": "Smappee: {name}", + "step": { + "environment": { + "data": { + "environment": "Umgebung" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smappee/translations/es.json b/homeassistant/components/smappee/translations/es.json index 543c988b356830..5d65081f9f6f01 100644 --- a/homeassistant/components/smappee/translations/es.json +++ b/homeassistant/components/smappee/translations/es.json @@ -6,7 +6,8 @@ "authorize_url_timeout": "Tiempo de espera agotado generando la url de autorizaci\u00f3n.", "connection_error": "No se pudo conectar al dispositivo Smappee.", "invalid_mdns": "Dispositivo no compatible para la integraci\u00f3n de Smappee.", - "missing_configuration": "El componente no est\u00e1 configurado. Por favor, siga la documentaci\u00f3n." + "missing_configuration": "El componente no est\u00e1 configurado. Por favor, siga 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})" }, "flow_title": "Smappee: {name}", "step": { diff --git a/homeassistant/components/smappee/translations/et.json b/homeassistant/components/smappee/translations/et.json new file mode 100644 index 00000000000000..8909689e09869d --- /dev/null +++ b/homeassistant/components/smappee/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "connection_error": "Smappee seadmega \u00fchenduse loomine nurjus." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smappee/translations/fr.json b/homeassistant/components/smappee/translations/fr.json index d1dca6c5895897..4bbed6615ca869 100644 --- a/homeassistant/components/smappee/translations/fr.json +++ b/homeassistant/components/smappee/translations/fr.json @@ -2,22 +2,33 @@ "config": { "abort": { "already_configured_device": "L'appareil est d\u00e9j\u00e0 configur\u00e9", + "already_configured_local_device": "Le ou les p\u00e9riph\u00e9riques locaux sont d\u00e9j\u00e0 configur\u00e9s. Veuillez les supprimer avant de configurer un appareil cloud.", "authorize_url_timeout": "D\u00e9lai de g\u00e9n\u00e9ration de l'URL d'authentification d\u00e9pass\u00e9.", - "missing_configuration": "Le composant n'est pas configur\u00e9. Veuillez suivre la documentation." + "connection_error": "\u00c9chec de la connexion \u00e0 l'appareil Smappee.", + "invalid_mdns": "Appareil non pris en charge pour l'int\u00e9gration Smappee.", + "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} )" }, + "flow_title": "Smappee: {name}", "step": { "environment": { "data": { "environment": "Environnement" - } + }, + "description": "Configurez votre Smappee pour qu'il s'int\u00e8gre \u00e0 Home Assistant." }, "local": { "data": { "host": "H\u00f4te" - } + }, + "description": "Entrez l'h\u00f4te pour lancer l'int\u00e9gration locale Smappee" }, "pick_implementation": { "title": "Choisissez la m\u00e9thode d'authentification" + }, + "zeroconf_confirm": { + "description": "Voulez-vous ajouter l'appareil Smappee avec le num\u00e9ro de s\u00e9rie \u00ab {serialnumber} \u00bb \u00e0 Home Assistant?", + "title": "Appareil Smappee d\u00e9couvert" } } } diff --git a/homeassistant/components/smappee/translations/hu.json b/homeassistant/components/smappee/translations/hu.json new file mode 100644 index 00000000000000..5bb10e0f851da1 --- /dev/null +++ b/homeassistant/components/smappee/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured_device": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smappee/translations/it.json b/homeassistant/components/smappee/translations/it.json index ad85b94abaeddf..3a2cc535c6d069 100644 --- a/homeassistant/components/smappee/translations/it.json +++ b/homeassistant/components/smappee/translations/it.json @@ -24,7 +24,7 @@ "description": "Immettere l'host per avviare l'integrazione locale di Smappee" }, "pick_implementation": { - "title": "Scegliere il metodo di autenticazione" + "title": "Scegli il metodo di autenticazione" }, "zeroconf_confirm": { "description": "Vuoi aggiungere il dispositivo Smappee con numero di serie `{serialnumber}` a Home Assistant?", diff --git a/homeassistant/components/smappee/translations/ko.json b/homeassistant/components/smappee/translations/ko.json index 05557a6046de15..b3e37ee6d01ed0 100644 --- a/homeassistant/components/smappee/translations/ko.json +++ b/homeassistant/components/smappee/translations/ko.json @@ -1,10 +1,17 @@ { "config": { "abort": { + "already_configured_device": "\uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\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." + "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": "\uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc5d0\ub7ec\uc5d0 \ub300\ud55c \uc815\ubcf4\ub294 \ub3c4\uc6c0\ub9d0 \uc139\uc158\uc744 \ud655\uc778\ud558\uc138\uc694({docs_url})" }, "step": { + "local": { + "data": { + "host": "\ud638\uc2a4\ud2b8" + } + }, "pick_implementation": { "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" } diff --git a/homeassistant/components/smappee/translations/lb.json b/homeassistant/components/smappee/translations/lb.json index 7f514644918948..90dd0b10486b7a 100644 --- a/homeassistant/components/smappee/translations/lb.json +++ b/homeassistant/components/smappee/translations/lb.json @@ -6,7 +6,8 @@ "authorize_url_timeout": "Z\u00e4it Iwwerschreidung beim gener\u00e9ieren vun der Autorisatiouns URL.", "connection_error": "Feeler beim verbannen mam Smappee Apparat.", "invalid_mdns": "Net \u00ebnnerst\u00ebtzten Apparat fir Smappee Integratioun.", - "missing_configuration": "Komponent ass nach net konfigur\u00e9iert. Follegt w.e.g der Dokumentatioun." + "missing_configuration": "Komponent ass nach net konfigur\u00e9iert. Follegt w.e.g der Dokumentatioun.", + "no_url_available": "Keng URL disponibel. Fir Informatiounen iwwert d\u00ebse Feeler, [kuck H\u00ebllef Sektioun]({docs_url})" }, "flow_title": "Smappee: {name}", "step": { diff --git a/homeassistant/components/smappee/translations/no.json b/homeassistant/components/smappee/translations/no.json index 76e96614aa611a..11b8b1bdd30cfc 100644 --- a/homeassistant/components/smappee/translations/no.json +++ b/homeassistant/components/smappee/translations/no.json @@ -6,7 +6,8 @@ "authorize_url_timeout": "Tidsavbrudd ved generering av autoriseringsadresse.", "connection_error": "Kunne ikke koble til Smappee-enheten.", "invalid_mdns": "Ikke-st\u00f8ttet enhet for Smappee-integrasjonen.", - "missing_configuration": "Komponenten er ikke konfigurert. Vennligst f\u00f8lg dokumentasjonen." + "missing_configuration": "Komponenten er ikke konfigurert. Vennligst f\u00f8lg dokumentasjonen.", + "no_url_available": "Ingen URL tilgjengelig. For informasjon om denne feilen, [sjekk {docs_url} ] ( {docs_url} )" }, "flow_title": "Smappee: {navn}", "step": { diff --git a/homeassistant/components/smappee/translations/pl.json b/homeassistant/components/smappee/translations/pl.json index da5a481c22bd33..87cd8ff3e12f01 100644 --- a/homeassistant/components/smappee/translations/pl.json +++ b/homeassistant/components/smappee/translations/pl.json @@ -1,12 +1,33 @@ { "config": { "abort": { + "already_configured_device": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "already_configured_local_device": "Urz\u0105dzenie(-a) lokalne jest ju\u017c skonfigurowane. Usu\u0144 je najpierw przed skonfigurowaniem urz\u0105dzenia w chmurze.", "authorize_url_timeout": "Przekroczono limit czasu generowania URL autoryzacji.", + "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia z urz\u0105dzeniem Smappee.", + "invalid_mdns": "Nieobs\u0142ugiwane urz\u0105dzenie dla integracji Smappee.", "missing_configuration": "Komponent nie jest skonfigurowany. Post\u0119puj zgodnie z dokumentacj\u0105." }, + "flow_title": "Smappee: {name}", "step": { + "environment": { + "data": { + "environment": "\u015arodowisko" + }, + "description": "Skonfiguruj Smappee, aby zintegrowa\u0107 go z Home Assistant." + }, + "local": { + "data": { + "host": "Nazwa hosta lub adres IP" + }, + "description": "Wprowad\u017a nazw\u0119 hosta, aby zainicjowa\u0107 lokaln\u0105 integracj\u0119 Smappee" + }, "pick_implementation": { "title": "Wybierz metod\u0119 uwierzytelniania" + }, + "zeroconf_confirm": { + "description": "Czy chcesz doda\u0107 do Home Assistant urz\u0105dzenie Smappee o numerze seryjnym `{serialnumber}`?", + "title": "Wykryto urz\u0105dzenie Smappee" } } } diff --git a/homeassistant/components/smappee/translations/zh-Hant.json b/homeassistant/components/smappee/translations/zh-Hant.json index 7636ea5b34b697..5374c535a12133 100644 --- a/homeassistant/components/smappee/translations/zh-Hant.json +++ b/homeassistant/components/smappee/translations/zh-Hant.json @@ -6,7 +6,8 @@ "authorize_url_timeout": "\u7522\u751f\u8a8d\u8b49 URL \u6642\u903e\u6642\u3002", "connection_error": "Smappee \u8a2d\u5099\u9023\u7dda\u5931\u6557\u3002", "invalid_mdns": "Smappee \u6574\u5408\u4e0d\u652f\u63f4\u7684\u8a2d\u5099\u3002", - "missing_configuration": "\u5143\u4ef6\u5c1a\u672a\u8a2d\u7f6e\uff0c\u8acb\u53c3\u95b1\u6587\u4ef6\u8aaa\u660e\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})" }, "flow_title": "Smappee\uff1a{name}", "step": { diff --git a/homeassistant/components/smart_meter_texas/translations/de.json b/homeassistant/components/smart_meter_texas/translations/de.json new file mode 100644 index 00000000000000..936e9817d927ed --- /dev/null +++ b/homeassistant/components/smart_meter_texas/translations/de.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Passwort", + "username": "Benutzername" + } + } + } + }, + "title": "Smart Meter Texas" +} \ No newline at end of file diff --git a/homeassistant/components/smart_meter_texas/translations/hu.json b/homeassistant/components/smart_meter_texas/translations/hu.json new file mode 100644 index 00000000000000..3b2d79a34a77e2 --- /dev/null +++ b/homeassistant/components/smart_meter_texas/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smart_meter_texas/translations/nl.json b/homeassistant/components/smart_meter_texas/translations/nl.json new file mode 100644 index 00000000000000..a40ab60cd1e214 --- /dev/null +++ b/homeassistant/components/smart_meter_texas/translations/nl.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smart_meter_texas/translations/pl.json b/homeassistant/components/smart_meter_texas/translations/pl.json new file mode 100644 index 00000000000000..19257e3cf5c626 --- /dev/null +++ b/homeassistant/components/smart_meter_texas/translations/pl.json @@ -0,0 +1,22 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + }, + "description": "Podaj swoj\u0105 nazw\u0119 u\u017cytkownika i has\u0142o do Smart Meter Texas." + } + } + }, + "title": "Smart Meter Texas" +} \ No newline at end of file diff --git a/homeassistant/components/smarthab/__init__.py b/homeassistant/components/smarthab/__init__.py index 2d22841660afaa..c826b5d8f4d2b0 100644 --- a/homeassistant/components/smarthab/__init__.py +++ b/homeassistant/components/smarthab/__init__.py @@ -34,15 +34,18 @@ async def async_setup(hass, config) -> bool: """Set up the SmartHab platform.""" hass.data.setdefault(DOMAIN, {}) - sh_conf = config.get(DOMAIN) - hass.async_create_task( - hass.config_entries.flow.async_init( - DOMAIN, - context={"source": SOURCE_IMPORT}, - data=sh_conf, + if DOMAIN not in config: + return True + + if not hass.config_entries.async_entries(DOMAIN): + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": SOURCE_IMPORT}, + data=config[DOMAIN], + ) ) - ) return True diff --git a/homeassistant/components/smarthab/config_flow.py b/homeassistant/components/smarthab/config_flow.py index f0a1df88695636..a277388c140b8e 100644 --- a/homeassistant/components/smarthab/config_flow.py +++ b/homeassistant/components/smarthab/config_flow.py @@ -16,6 +16,9 @@ class SmartHabConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """SmartHab config flow.""" + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL + def _show_setup_form(self, user_input=None, errors=None): """Show the setup form to the user.""" @@ -72,6 +75,6 @@ async def async_step_user(self, user_input=None): return self._show_setup_form(user_input, errors) - async def async_step_import(self, user_input): + async def async_step_import(self, import_info): """Handle import from legacy config.""" - return await self.async_step_user(user_input) + return await self.async_step_user(import_info) diff --git a/homeassistant/components/smarthab/translations/pl.json b/homeassistant/components/smarthab/translations/pl.json index 7279eb6ca79123..3d366edbf73a8d 100644 --- a/homeassistant/components/smarthab/translations/pl.json +++ b/homeassistant/components/smarthab/translations/pl.json @@ -2,8 +2,8 @@ "config": { "error": { "service": "B\u0142\u0105d podczas pr\u00f3by osi\u0105gni\u0119cia SmartHab. Us\u0142uga mo\u017ce by\u0107 wy\u0142\u0105czna. Sprawd\u017a po\u0142\u0105czenie.", - "unknown_error": "Nieoczekiwany b\u0142\u0105d.", - "wrong_login": "Niepoprawne uwierzytelnienie." + "unknown_error": "Nieoczekiwany b\u0142\u0105d", + "wrong_login": "Niepoprawne uwierzytelnienie" }, "step": { "user": { diff --git a/homeassistant/components/smartthings/__init__.py b/homeassistant/components/smartthings/__init__.py index 974cde35faf426..d184a3ca6ce20f 100644 --- a/homeassistant/components/smartthings/__init__.py +++ b/homeassistant/components/smartthings/__init__.py @@ -14,6 +14,7 @@ CONF_CLIENT_ID, CONF_CLIENT_SECRET, HTTP_FORBIDDEN, + HTTP_UNAUTHORIZED, ) from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -158,7 +159,7 @@ async def retrieve_device_status(device): hass.data[DOMAIN][DATA_BROKERS][entry.entry_id] = broker except ClientResponseError as ex: - if ex.status in (401, HTTP_FORBIDDEN): + if ex.status in (HTTP_UNAUTHORIZED, HTTP_FORBIDDEN): _LOGGER.exception( "Unable to setup configuration entry '%s' - please reconfigure the integration", entry.title, diff --git a/homeassistant/components/smartthings/binary_sensor.py b/homeassistant/components/smartthings/binary_sensor.py index 825cf14995216f..41e915d5c955f4 100644 --- a/homeassistant/components/smartthings/binary_sensor.py +++ b/homeassistant/components/smartthings/binary_sensor.py @@ -3,7 +3,16 @@ from pysmartthings import Attribute, Capability -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_MOISTURE, + DEVICE_CLASS_MOTION, + DEVICE_CLASS_MOVING, + DEVICE_CLASS_OPENING, + DEVICE_CLASS_PRESENCE, + DEVICE_CLASS_PROBLEM, + DEVICE_CLASS_SOUND, + BinarySensorEntity, +) from . import SmartThingsEntity from .const import DATA_BROKERS, DOMAIN @@ -20,15 +29,15 @@ Capability.water_sensor: Attribute.water, } ATTRIB_TO_CLASS = { - Attribute.acceleration: "moving", - Attribute.contact: "opening", - Attribute.filter_status: "problem", - Attribute.motion: "motion", - Attribute.presence: "presence", - Attribute.sound: "sound", - Attribute.tamper: "problem", - Attribute.valve: "opening", - Attribute.water: "moisture", + Attribute.acceleration: DEVICE_CLASS_MOVING, + Attribute.contact: DEVICE_CLASS_OPENING, + Attribute.filter_status: DEVICE_CLASS_PROBLEM, + Attribute.motion: DEVICE_CLASS_MOTION, + Attribute.presence: DEVICE_CLASS_PRESENCE, + Attribute.sound: DEVICE_CLASS_SOUND, + Attribute.tamper: DEVICE_CLASS_PROBLEM, + Attribute.valve: DEVICE_CLASS_OPENING, + Attribute.water: DEVICE_CLASS_MOISTURE, } diff --git a/homeassistant/components/smartthings/manifest.json b/homeassistant/components/smartthings/manifest.json index 58ea833cb7d7ad..30ef278d1d1153 100644 --- a/homeassistant/components/smartthings/manifest.json +++ b/homeassistant/components/smartthings/manifest.json @@ -3,7 +3,7 @@ "name": "SmartThings", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/smartthings", - "requirements": ["pysmartapp==0.3.2", "pysmartthings==0.7.3"], + "requirements": ["pysmartapp==0.3.2", "pysmartthings==0.7.4"], "dependencies": ["webhook"], "after_dependencies": ["cloud"], "codeowners": ["@andrewsayre"] diff --git a/homeassistant/components/smartthings/sensor.py b/homeassistant/components/smartthings/sensor.py index 7de3b98b1da497..f0240886913efe 100644 --- a/homeassistant/components/smartthings/sensor.py +++ b/homeassistant/components/smartthings/sensor.py @@ -5,6 +5,7 @@ from pysmartthings import Attribute, Capability from homeassistant.const import ( + AREA_SQUARE_METERS, CONCENTRATION_PARTS_PER_MILLION, DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, @@ -12,6 +13,7 @@ DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TIMESTAMP, ENERGY_KILO_WATT_HOUR, + LIGHT_LUX, MASS_KILOGRAMS, PERCENTAGE, POWER_WATT, @@ -41,7 +43,12 @@ Map(Attribute.battery, "Battery", PERCENTAGE, DEVICE_CLASS_BATTERY) ], Capability.body_mass_index_measurement: [ - Map(Attribute.bmi_measurement, "Body Mass Index", f"{MASS_KILOGRAMS}/m^2", None) + Map( + Attribute.bmi_measurement, + "Body Mass Index", + f"{MASS_KILOGRAMS}/{AREA_SQUARE_METERS}", + None, + ) ], Capability.body_weight_measurement: [ Map(Attribute.body_weight_measurement, "Body Weight", MASS_KILOGRAMS, None) @@ -110,7 +117,7 @@ ) ], Capability.illuminance_measurement: [ - Map(Attribute.illuminance, "Illuminance", "lux", DEVICE_CLASS_ILLUMINANCE) + Map(Attribute.illuminance, "Illuminance", LIGHT_LUX, DEVICE_CLASS_ILLUMINANCE) ], Capability.infrared_level: [ Map(Attribute.infrared_level, "Infrared Level", PERCENTAGE, None) diff --git a/homeassistant/components/smartthings/translations/et.json b/homeassistant/components/smartthings/translations/et.json new file mode 100644 index 00000000000000..91299004ed30a0 --- /dev/null +++ b/homeassistant/components/smartthings/translations/et.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "select_location": { + "data": { + "location_id": "Asukoht" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smartthings/translations/fr.json b/homeassistant/components/smartthings/translations/fr.json index c355c437689ed7..6051cbbabce9ff 100644 --- a/homeassistant/components/smartthings/translations/fr.json +++ b/homeassistant/components/smartthings/translations/fr.json @@ -1,5 +1,9 @@ { "config": { + "abort": { + "invalid_webhook_url": "Home Assistant n'est pas configur\u00e9 correctement pour recevoir les mises \u00e0 jour de SmartThings. L'URL du webhook n'est pas valide: \n > {webhook_url} \n\n Veuillez mettre \u00e0 jour votre configuration en suivant les [instructions] ({component_url}), red\u00e9marrez Home Assistant et r\u00e9essayez.", + "no_available_locations": "Il n'y a pas d'emplacements SmartThings disponibles \u00e0 configurer dans Home Assistant." + }, "error": { "app_setup_error": "Impossible de configurer la SmartApp. Veuillez r\u00e9essayer.", "token_forbidden": "Le jeton n'a pas les port\u00e9es OAuth requises.", @@ -15,12 +19,14 @@ "data": { "access_token": "Jeton d'acc\u00e8s" }, + "description": "Veuillez saisir un [jeton d'acc\u00e8s personnel] {token_url} ( {token_url} ) qui a \u00e9t\u00e9 cr\u00e9\u00e9 conform\u00e9ment aux [instructions] ( {component_url} ). Cela sera utilis\u00e9 pour cr\u00e9er l'int\u00e9gration de Home Assistant dans votre compte SmartThings.", "title": "Entrer un jeton d'acc\u00e8s personnel" }, "select_location": { "data": { "location_id": "Emplacement" }, + "description": "Veuillez s\u00e9lectionner l'emplacement SmartThings que vous souhaitez ajouter \u00e0 Home Assistant. Nous ouvrirons alors une nouvelle fen\u00eatre et vous demanderons de vous connecter et d'autoriser l'installation de l'int\u00e9gration de Home Assistant \u00e0 l'emplacement s\u00e9lectionn\u00e9.", "title": "S\u00e9lectionnez l'emplacement" }, "user": { diff --git a/homeassistant/components/smarty/binary_sensor.py b/homeassistant/components/smarty/binary_sensor.py index f8b9114ae0ee76..965102f07f7c78 100644 --- a/homeassistant/components/smarty/binary_sensor.py +++ b/homeassistant/components/smarty/binary_sensor.py @@ -2,7 +2,10 @@ import logging -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_PROBLEM, + BinarySensorEntity, +) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -83,7 +86,9 @@ class AlarmSensor(SmartyBinarySensor): def __init__(self, name, smarty): """Alarm Sensor Init.""" - super().__init__(name=f"{name} Alarm", device_class="problem", smarty=smarty) + super().__init__( + name=f"{name} Alarm", device_class=DEVICE_CLASS_PROBLEM, smarty=smarty + ) def update(self) -> None: """Update state.""" @@ -96,7 +101,9 @@ class WarningSensor(SmartyBinarySensor): def __init__(self, name, smarty): """Warning Sensor Init.""" - super().__init__(name=f"{name} Warning", device_class="problem", smarty=smarty) + super().__init__( + name=f"{name} Warning", device_class=DEVICE_CLASS_PROBLEM, smarty=smarty + ) def update(self) -> None: """Update state.""" diff --git a/homeassistant/components/smhi/strings.json b/homeassistant/components/smhi/strings.json index 245260b0fce428..0bba0c2ab48bef 100644 --- a/homeassistant/components/smhi/strings.json +++ b/homeassistant/components/smhi/strings.json @@ -4,9 +4,9 @@ "user": { "title": "Location in Sweden", "data": { - "name": "Name", - "latitude": "Latitude", - "longitude": "Longitude" + "name": "[%key:common::config_flow::data::name%]", + "latitude": "[%key:common::config_flow::data::latitude%]", + "longitude": "[%key:common::config_flow::data::longitude%]" } } }, diff --git a/homeassistant/components/smhi/translations/et.json b/homeassistant/components/smhi/translations/et.json new file mode 100644 index 00000000000000..984b43015d77c5 --- /dev/null +++ b/homeassistant/components/smhi/translations/et.json @@ -0,0 +1,17 @@ +{ + "config": { + "error": { + "wrong_location": "Asukoht saab olla ainult Rootsis" + }, + "step": { + "user": { + "data": { + "latitude": "Laiuskraad", + "longitude": "Pikkuskraad", + "name": "Nimi" + }, + "title": "Asukoht Rootsis" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/smhi/translations/it.json b/homeassistant/components/smhi/translations/it.json index b45b60babfe81e..e68f697c678a8b 100644 --- a/homeassistant/components/smhi/translations/it.json +++ b/homeassistant/components/smhi/translations/it.json @@ -8,7 +8,7 @@ "user": { "data": { "latitude": "Latitudine", - "longitude": "Longitudine", + "longitude": "Logitudine", "name": "Nome" }, "title": "Localit\u00e0 in Svezia" diff --git a/homeassistant/components/sms/gateway.py b/homeassistant/components/sms/gateway.py index 2d36444c03d90e..9bdfbad0f5597f 100644 --- a/homeassistant/components/sms/gateway.py +++ b/homeassistant/components/sms/gateway.py @@ -59,9 +59,11 @@ def sms_callback(self, state_machine, callback_type, callback_data): if inner_entry["Buffer"] is not None: text = text + inner_entry["Buffer"] - event_data = dict( - phone=message["Number"], date=str(message["DateTime"]), message=text - ) + event_data = { + "phone": message["Number"], + "date": str(message["DateTime"]), + "message": text, + } _LOGGER.debug("Append event data:%s", event_data) data.append(event_data) diff --git a/homeassistant/components/sms/sensor.py b/homeassistant/components/sms/sensor.py index 64d2bf9bd987a8..eaad395eaa6910 100644 --- a/homeassistant/components/sms/sensor.py +++ b/homeassistant/components/sms/sensor.py @@ -3,7 +3,7 @@ import gammu # pylint: disable=import-error, no-member -from homeassistant.const import DEVICE_CLASS_SIGNAL_STRENGTH +from homeassistant.const import DEVICE_CLASS_SIGNAL_STRENGTH, SIGNAL_STRENGTH_DECIBELS from homeassistant.helpers.entity import Entity from .const import DOMAIN, SMS_GATEWAY @@ -50,7 +50,7 @@ def name(self): @property def unit_of_measurement(self): """Return the unit the value is expressed in.""" - return "dB" + return SIGNAL_STRENGTH_DECIBELS @property def device_class(self): diff --git a/homeassistant/components/sms/translations/de.json b/homeassistant/components/sms/translations/de.json new file mode 100644 index 00000000000000..273daf6ef0a9e8 --- /dev/null +++ b/homeassistant/components/sms/translations/de.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "device": "Ger\u00e4t" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sms/translations/fr.json b/homeassistant/components/sms/translations/fr.json index 25c08a1e7fe875..b4c479cfd50ec2 100644 --- a/homeassistant/components/sms/translations/fr.json +++ b/homeassistant/components/sms/translations/fr.json @@ -12,7 +12,8 @@ "user": { "data": { "device": "Appareil" - } + }, + "title": "Se connecter au modem" } } } diff --git a/homeassistant/components/sms/translations/hu.json b/homeassistant/components/sms/translations/hu.json new file mode 100644 index 00000000000000..3b2d79a34a77e2 --- /dev/null +++ b/homeassistant/components/sms/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sms/translations/pl.json b/homeassistant/components/sms/translations/pl.json index eec34cc0197c50..bfe331ee89e2c3 100644 --- a/homeassistant/components/sms/translations/pl.json +++ b/homeassistant/components/sms/translations/pl.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." }, "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", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "user": { diff --git a/homeassistant/components/snapcast/manifest.json b/homeassistant/components/snapcast/manifest.json index 31eb0491eb4fea..4e65b60280b8d9 100644 --- a/homeassistant/components/snapcast/manifest.json +++ b/homeassistant/components/snapcast/manifest.json @@ -2,6 +2,6 @@ "domain": "snapcast", "name": "Snapcast", "documentation": "https://www.home-assistant.io/integrations/snapcast", - "requirements": ["snapcast==2.0.10"], + "requirements": ["snapcast==2.1.1"], "codeowners": [] } diff --git a/homeassistant/components/solaredge/sensor.py b/homeassistant/components/solaredge/sensor.py index 3888b8bf53662c..0cd498b4e3bc9d 100644 --- a/homeassistant/components/solaredge/sensor.py +++ b/homeassistant/components/solaredge/sensor.py @@ -149,7 +149,7 @@ def __init__(self, platform_name, sensor_key, data_service): def update(self): """Get the latest data from the sensor and update the state.""" self.data_service.update() - self._state = self.data_service.data[self._json_key] + self._state = self.data_service.data.get(self._json_key) class SolarEdgeDetailsSensor(SolarEdgeSensor): @@ -192,8 +192,8 @@ def device_state_attributes(self): def update(self): """Get the latest inventory data and update state and attributes.""" self.data_service.update() - self._state = self.data_service.data[self._json_key] - self._attributes = self.data_service.attributes[self._json_key] + self._state = self.data_service.data.get(self._json_key) + self._attributes = self.data_service.attributes.get(self._json_key) class SolarEdgeEnergyDetailsSensor(SolarEdgeSensor): @@ -267,7 +267,8 @@ def update(self): """Get the latest inventory data and update state and attributes.""" self.data_service.update() attr = self.data_service.attributes.get(self._json_key) - self._state = attr["soc"] + if attr and "soc" in attr: + self._state = attr["soc"] class SolarEdgeDataService: diff --git a/homeassistant/components/solarlog/strings.json b/homeassistant/components/solarlog/strings.json index 1a196315a3031e..068132dea41b9f 100644 --- a/homeassistant/components/solarlog/strings.json +++ b/homeassistant/components/solarlog/strings.json @@ -10,11 +10,11 @@ } }, "error": { - "already_configured": "Device is already configured", - "cannot_connect": "Failed to connect, please verify host address" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, "abort": { - "already_configured": "Device is already configured" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/solarlog/translations/ca.json b/homeassistant/components/solarlog/translations/ca.json index c62b69fa976459..6b0883b51fd426 100644 --- a/homeassistant/components/solarlog/translations/ca.json +++ b/homeassistant/components/solarlog/translations/ca.json @@ -5,7 +5,7 @@ }, "error": { "already_configured": "El dispositiu ja est\u00e0 configurat", - "cannot_connect": "No s'ha pogut connectar, verifica l'adre\u00e7a de l'amfitri\u00f3" + "cannot_connect": "Ha fallat la connexi\u00f3" }, "step": { "user": { diff --git a/homeassistant/components/solarlog/translations/en.json b/homeassistant/components/solarlog/translations/en.json index 22dd35574ad6ca..7512da0d2dcd60 100644 --- a/homeassistant/components/solarlog/translations/en.json +++ b/homeassistant/components/solarlog/translations/en.json @@ -5,7 +5,7 @@ }, "error": { "already_configured": "Device is already configured", - "cannot_connect": "Failed to connect, please verify host address" + "cannot_connect": "Failed to connect" }, "step": { "user": { diff --git a/homeassistant/components/solarlog/translations/et.json b/homeassistant/components/solarlog/translations/et.json new file mode 100644 index 00000000000000..3f33ed633403ee --- /dev/null +++ b/homeassistant/components/solarlog/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00dchenduse loomine nurjus. Kontrolli host'i aadressi" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/solarlog/translations/it.json b/homeassistant/components/solarlog/translations/it.json index c227ba7a16cf2e..a4a27c1a0f5f09 100644 --- a/homeassistant/components/solarlog/translations/it.json +++ b/homeassistant/components/solarlog/translations/it.json @@ -5,7 +5,7 @@ }, "error": { "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", - "cannot_connect": "Impossibile connettersi, verifica l'indirizzo host" + "cannot_connect": "Impossibile connettersi" }, "step": { "user": { diff --git a/homeassistant/components/solarlog/translations/no.json b/homeassistant/components/solarlog/translations/no.json index 5c0bc0524eb172..437da00617cd29 100644 --- a/homeassistant/components/solarlog/translations/no.json +++ b/homeassistant/components/solarlog/translations/no.json @@ -5,7 +5,7 @@ }, "error": { "already_configured": "Enheten er allerede konfigurert", - "cannot_connect": "Kunne ikke koble til, vennligst bekreft vertsadresse" + "cannot_connect": "Tilkobling mislyktes." }, "step": { "user": { diff --git a/homeassistant/components/solarlog/translations/pl.json b/homeassistant/components/solarlog/translations/pl.json index 6769d51c2c27a4..1577982d3d78b7 100644 --- a/homeassistant/components/solarlog/translations/pl.json +++ b/homeassistant/components/solarlog/translations/pl.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" }, "error": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, sprawd\u017a adres hosta" }, "step": { diff --git a/homeassistant/components/solarlog/translations/ru.json b/homeassistant/components/solarlog/translations/ru.json index cf4adc2d623c6e..ada5ebfc406d30 100644 --- a/homeassistant/components/solarlog/translations/ru.json +++ b/homeassistant/components/solarlog/translations/ru.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\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, \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0430\u0434\u0440\u0435\u0441 \u0445\u043e\u0441\u0442\u0430." + "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.", + "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/solarlog/translations/zh-Hant.json b/homeassistant/components/solarlog/translations/zh-Hant.json index 85ea05369c5b3a..b8f53a74ff3d5e 100644 --- a/homeassistant/components/solarlog/translations/zh-Hant.json +++ b/homeassistant/components/solarlog/translations/zh-Hant.json @@ -5,7 +5,7 @@ }, "error": { "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", - "cannot_connect": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u78ba\u8a8d\u4e3b\u6a5f\u4f4d\u5740" + "cannot_connect": "\u9023\u7dda\u5931\u6557" }, "step": { "user": { diff --git a/homeassistant/components/solax/manifest.json b/homeassistant/components/solax/manifest.json index 5d8590389d820f..bf2d3d72cc53c0 100644 --- a/homeassistant/components/solax/manifest.json +++ b/homeassistant/components/solax/manifest.json @@ -2,6 +2,6 @@ "domain": "solax", "name": "SolaX Power", "documentation": "https://www.home-assistant.io/integrations/solax", - "requirements": ["solax==0.2.3"], + "requirements": ["solax==0.2.4"], "codeowners": ["@squishykid"] } diff --git a/homeassistant/components/soma/translations/et.json b/homeassistant/components/soma/translations/et.json new file mode 100644 index 00000000000000..0254817718aa14 --- /dev/null +++ b/homeassistant/components/soma/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "connection_error": "SOMA Connect seadmega \u00fchenduse loomine nurjus." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/somfy/__init__.py b/homeassistant/components/somfy/__init__.py index fbc76e7c93811f..7b1430b19c03f9 100644 --- a/homeassistant/components/somfy/__init__.py +++ b/homeassistant/components/somfy/__init__.py @@ -89,6 +89,7 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry): ) hass.data[DOMAIN][API] = api.ConfigEntrySomfyApi(hass, entry, implementation) + hass.data[DOMAIN][DEVICES] = [] await update_all_devices(hass) diff --git a/homeassistant/components/somfy/config_flow.py b/homeassistant/components/somfy/config_flow.py index 2d143fbd196327..80fc2192d8e5db 100644 --- a/homeassistant/components/somfy/config_flow.py +++ b/homeassistant/components/somfy/config_flow.py @@ -24,6 +24,6 @@ def logger(self) -> logging.Logger: async def async_step_user(self, user_input=None): """Handle a flow start.""" if self.hass.config_entries.async_entries(DOMAIN): - return self.async_abort(reason="already_setup") + return self.async_abort(reason="single_instance_allowed") return await super().async_step_user(user_input) diff --git a/homeassistant/components/somfy/strings.json b/homeassistant/components/somfy/strings.json index d1fa921bb8edf3..85ef981e356e76 100644 --- a/homeassistant/components/somfy/strings.json +++ b/homeassistant/components/somfy/strings.json @@ -1,14 +1,18 @@ { "config": { "step": { - "pick_implementation": { "title": "Pick Authentication Method" } + "pick_implementation": { + "title": "[%key:common::config_flow::title::oauth2_pick_implementation%]" + } }, "abort": { - "already_setup": "You can only configure one Somfy account.", - "authorize_url_timeout": "Timeout generating authorize url.", - "missing_configuration": "The Somfy component is not configured. Please follow the documentation.", + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", + "authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]", + "missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]", "no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]" }, - "create_entry": { "default": "Successfully authenticated with Somfy." } + "create_entry": { + "default": "[%key:common::config_flow::create_entry::authenticated%]" + } } } diff --git a/homeassistant/components/somfy/translations/ca.json b/homeassistant/components/somfy/translations/ca.json index 489ab7a7f9f959..55729e859a9627 100644 --- a/homeassistant/components/somfy/translations/ca.json +++ b/homeassistant/components/somfy/translations/ca.json @@ -2,12 +2,13 @@ "config": { "abort": { "already_setup": "Nom\u00e9s pots configurar un \u00fanic compte amb Somfy.", - "authorize_url_timeout": "S'ha acabat el temps d'espera durant la generaci\u00f3 de l'URL d'autoritzaci\u00f3.", - "missing_configuration": "El component Somfy 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})" + "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})", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "create_entry": { - "default": "Autenticaci\u00f3 exitosa amb Somfy." + "default": "Autenticaci\u00f3 exitosa" }, "step": { "pick_implementation": { diff --git a/homeassistant/components/somfy/translations/el.json b/homeassistant/components/somfy/translations/el.json new file mode 100644 index 00000000000000..aecb2ee553fa42 --- /dev/null +++ b/homeassistant/components/somfy/translations/el.json @@ -0,0 +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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/somfy/translations/en.json b/homeassistant/components/somfy/translations/en.json index 2a2bb689860ada..9dede6ab53fd39 100644 --- a/homeassistant/components/somfy/translations/en.json +++ b/homeassistant/components/somfy/translations/en.json @@ -2,12 +2,13 @@ "config": { "abort": { "already_setup": "You can only configure one Somfy account.", - "authorize_url_timeout": "Timeout generating authorize url.", - "missing_configuration": "The Somfy 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})" + "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})", + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "create_entry": { - "default": "Successfully authenticated with Somfy." + "default": "Successfully authenticated" }, "step": { "pick_implementation": { diff --git a/homeassistant/components/somfy/translations/es.json b/homeassistant/components/somfy/translations/es.json index bbb1cedad98ff6..6d11afcba4737c 100644 --- a/homeassistant/components/somfy/translations/es.json +++ b/homeassistant/components/somfy/translations/es.json @@ -3,7 +3,9 @@ "abort": { "already_setup": "Solo puedes configurar una cuenta de Somfy.", "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." + "missing_configuration": "El componente Somfy no est\u00e1 configurado. Por favor, sigue 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. S\u00f3lo es posible una \u00fanica configuraci\u00f3n." }, "create_entry": { "default": "Autenticado correctamente con Somfy." diff --git a/homeassistant/components/somfy/translations/fr.json b/homeassistant/components/somfy/translations/fr.json index 5df01fe951be16..3214b3a36deff2 100644 --- a/homeassistant/components/somfy/translations/fr.json +++ b/homeassistant/components/somfy/translations/fr.json @@ -3,7 +3,9 @@ "abort": { "already_setup": "Vous ne pouvez configurer qu'un seul compte Somfy.", "authorize_url_timeout": "D\u00e9lai de g\u00e9n\u00e9ration d'url autoriser.", - "missing_configuration": "Le composant Somfy n'est pas configur\u00e9. Veuillez suivre la documentation." + "missing_configuration": "Le composant Somfy 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} )", + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." }, "create_entry": { "default": "Authentifi\u00e9 avec succ\u00e8s avec Somfy." diff --git a/homeassistant/components/somfy/translations/it.json b/homeassistant/components/somfy/translations/it.json index 001739a7a99015..20b645159ccde5 100644 --- a/homeassistant/components/somfy/translations/it.json +++ b/homeassistant/components/somfy/translations/it.json @@ -2,16 +2,17 @@ "config": { "abort": { "already_setup": "\u00c8 possibile configurare un solo account Somfy.", - "authorize_url_timeout": "Tempo scaduto nel generare l'url di autorizzazione", - "missing_configuration": "Il componente Somfy non \u00e8 configurato. Si prega di seguire la documentazione.", - "no_url_available": "Nessun URL disponibile. Per informazioni su questo errore, [controlla la sezione della guida]({docs_url})" + "authorize_url_timeout": "Tempo scaduto nel generare l'URL di autorizzazione.", + "missing_configuration": "Il componente non \u00e8 configurato. Si prega di seguire la documentazione.", + "no_url_available": "Nessun URL disponibile. Per informazioni su questo errore, [controlla la sezione della guida]({docs_url})", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "create_entry": { - "default": "Autenticato con successo con Somfy." + "default": "Autenticazione riuscita" }, "step": { "pick_implementation": { - "title": "Seleziona il metodo di autenticazione" + "title": "Scegli il metodo di autenticazione" } } } diff --git a/homeassistant/components/somfy/translations/ko.json b/homeassistant/components/somfy/translations/ko.json index 9748da483bfeeb..43d4ba146b321a 100644 --- a/homeassistant/components/somfy/translations/ko.json +++ b/homeassistant/components/somfy/translations/ko.json @@ -3,7 +3,9 @@ "abort": { "already_setup": "\ud558\ub098\uc758 Somfy \uacc4\uc815\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "authorize_url_timeout": "\uc778\uc99d url \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "missing_configuration": "Somfy \uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694." + "missing_configuration": "Somfy \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": "\uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc5d0\ub7ec\uc5d0 \ub300\ud55c \uc815\ubcf4\ub294 \ub3c4\uc6c0\ub9d0 \uc139\uc158\uc744 \ud655\uc778\ud558\uc138\uc694({docs_url})", + "single_instance_allowed": "\uc774\ubbf8 \uc124\uc815\ub418\uc5b4 \uc788\uc74c. \ud558\ub098\uc758 \uc124\uc815\ub9cc \uac00\ub2a5\ud568." }, "create_entry": { "default": "Somfy \ub85c \uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4." diff --git a/homeassistant/components/somfy/translations/lb.json b/homeassistant/components/somfy/translations/lb.json index f34c07efd06c79..849e2fbadceb38 100644 --- a/homeassistant/components/somfy/translations/lb.json +++ b/homeassistant/components/somfy/translations/lb.json @@ -3,7 +3,9 @@ "abort": { "already_setup": "Dir k\u00ebnnt n\u00ebmmen een eenzegen Somfy Kont konfigur\u00e9ieren.", "authorize_url_timeout": "Z\u00e4it Iwwerschreidung beim gener\u00e9ieren vun der Autorisatiouns URL.", - "missing_configuration": "D'Somfy Komponent ass nach net konfigur\u00e9iert. Follegt w.e.g der Dokumentatioun." + "missing_configuration": "D'Somfy Komponent ass nach net konfigur\u00e9iert. Follegt w.e.g der Dokumentatioun.", + "no_url_available": "Keng URL disponibel. Fir Informatiounen iwwert d\u00ebse Feeler, [kuck H\u00ebllef Sektioun]({docs_url})", + "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech." }, "create_entry": { "default": "Erfollegr\u00e4ich mat Somfy authentifiz\u00e9iert." diff --git a/homeassistant/components/somfy/translations/no.json b/homeassistant/components/somfy/translations/no.json index 6f8e3c3b993686..e5c9c388dd3528 100644 --- a/homeassistant/components/somfy/translations/no.json +++ b/homeassistant/components/somfy/translations/no.json @@ -2,11 +2,13 @@ "config": { "abort": { "already_setup": "Du kan kun konfigurere \u00e9n Somfy-konto.", - "authorize_url_timeout": "Tidsavbrudd ved oppretting av godkjenningsadresse.", - "missing_configuration": "Somfy-komponenten er ikke konfigurert. Vennligst f\u00f8lg dokumentasjonen." + "authorize_url_timeout": "Tidsavbrudd som genererer autorer URL-adresse.", + "missing_configuration": "Komponenten er ikke konfigurert. Vennligst f\u00f8lg dokumentasjonen.", + "no_url_available": "Ingen URL tilgjengelig. For informasjon om denne feilen, [sjekk {docs_url} ] ( {docs_url} )", + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "create_entry": { - "default": "Vellykket godkjenning med Somfy." + "default": "Vellykket godkjenning" }, "step": { "pick_implementation": { diff --git a/homeassistant/components/somfy/translations/ru.json b/homeassistant/components/somfy/translations/ru.json index 46c1e080480fd5..16205dabc3f1cd 100644 --- a/homeassistant/components/somfy/translations/ru.json +++ b/homeassistant/components/somfy/translations/ru.json @@ -3,8 +3,9 @@ "abort": { "already_setup": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", "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": "\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 Somfy \u043d\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d. \u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438.", - "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." + "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 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438.", + "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.", + "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e." }, "create_entry": { "default": "\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." diff --git a/homeassistant/components/somfy/translations/zh-Hant.json b/homeassistant/components/somfy/translations/zh-Hant.json index b5875aaf088adb..4d3d5dc85fba81 100644 --- a/homeassistant/components/somfy/translations/zh-Hant.json +++ b/homeassistant/components/somfy/translations/zh-Hant.json @@ -2,11 +2,13 @@ "config": { "abort": { "already_setup": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44 Somfy \u5e33\u865f\u3002", - "authorize_url_timeout": "\u7522\u751f\u8a8d\u8b49 URL \u6642\u903e\u6642", - "missing_configuration": "Somfy \u5143\u4ef6\u5c1a\u672a\u8a2d\u7f6e\uff0c\u8acb\u53c3\u95b1\u6587\u4ef6\u8aaa\u660e\u3002" + "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})", + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002" }, "create_entry": { - "default": "\u5df2\u6210\u529f\u8a8d\u8b49 Somfy \u8a2d\u5099\u3002" + "default": "\u5df2\u6210\u529f\u8a8d\u8b49" }, "step": { "pick_implementation": { diff --git a/homeassistant/components/somfy_mylink/cover.py b/homeassistant/components/somfy_mylink/cover.py index b849a490940443..ac3bf0673f180d 100644 --- a/homeassistant/components/somfy_mylink/cover.py +++ b/homeassistant/components/somfy_mylink/cover.py @@ -1,7 +1,11 @@ """Cover Platform for the Somfy MyLink component.""" import logging -from homeassistant.components.cover import ENTITY_ID_FORMAT, CoverEntity +from homeassistant.components.cover import ( + DEVICE_CLASS_WINDOW, + ENTITY_ID_FORMAT, + CoverEntity, +) from homeassistant.util import slugify from . import CONF_DEFAULT_REVERSE, DATA_SOMFY_MYLINK @@ -49,7 +53,7 @@ def __init__( target_id, name="SomfyShade", reverse=False, - device_class="window", + device_class=DEVICE_CLASS_WINDOW, ): """Initialize the cover.""" self.somfy_mylink = somfy_mylink diff --git a/homeassistant/components/sonarr/__init__.py b/homeassistant/components/sonarr/__init__.py index 601509aa5755fe..8cb64bb527a892 100644 --- a/homeassistant/components/sonarr/__init__.py +++ b/homeassistant/components/sonarr/__init__.py @@ -1,16 +1,19 @@ """The Sonarr component.""" import asyncio from datetime import timedelta +import logging from typing import Any, Dict -from sonarr import Sonarr, SonarrError +from sonarr import Sonarr, SonarrAccessRestricted, SonarrError -from homeassistant.config_entries import ConfigEntry +from homeassistant.components import persistent_notification +from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry from homeassistant.const import ( ATTR_NAME, CONF_API_KEY, CONF_HOST, CONF_PORT, + CONF_SOURCE, CONF_SSL, CONF_VERIFY_SSL, ) @@ -36,6 +39,7 @@ PLATFORMS = ["sensor"] SCAN_INTERVAL = timedelta(seconds=30) +_LOGGER = logging.getLogger(__name__) async def async_setup(hass: HomeAssistantType, config: Dict) -> bool: @@ -69,6 +73,9 @@ async def async_setup_entry(hass: HomeAssistantType, entry: ConfigEntry) -> bool try: await sonarr.update() + except SonarrAccessRestricted: + _async_start_reauth(hass, entry) + return False except SonarrError as err: raise ConfigEntryNotReady from err @@ -106,6 +113,24 @@ async def async_unload_entry(hass: HomeAssistantType, entry: ConfigEntry) -> boo return unload_ok +def _async_start_reauth(hass: HomeAssistantType, entry: ConfigEntry): + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, + context={CONF_SOURCE: SOURCE_REAUTH}, + data={"config_entry_id": entry.entry_id, **entry.data}, + ) + ) + _LOGGER.error("API Key is no longer valid. Please reauthenticate") + + persistent_notification.async_create( + hass, + f"Sonarr integration for the Sonarr API hosted at {entry.entry_data[CONF_HOST]} needs to be re-authenticated. Please go to the integrations page to re-configure it.", + "Sonarr re-authentication", + "sonarr_reauth", + ) + + async def _async_update_listener(hass: HomeAssistantType, entry: ConfigEntry) -> None: """Handle options update.""" async_dispatcher_send( diff --git a/homeassistant/components/sonarr/config_flow.py b/homeassistant/components/sonarr/config_flow.py index ec1a29c660bc4f..753fb829268a79 100644 --- a/homeassistant/components/sonarr/config_flow.py +++ b/homeassistant/components/sonarr/config_flow.py @@ -5,6 +5,7 @@ from sonarr import Sonarr, SonarrAccessRestricted, SonarrError import voluptuous as vol +from homeassistant.components import persistent_notification from homeassistant.config_entries import CONN_CLASS_LOCAL_POLL, ConfigFlow, OptionsFlow from homeassistant.const import ( CONF_API_KEY, @@ -61,6 +62,12 @@ class SonarrConfigFlow(ConfigFlow, domain=DOMAIN): VERSION = 1 CONNECTION_CLASS = CONN_CLASS_LOCAL_POLL + def __init__(self): + """Initialize the flow.""" + self._reauth = False + self._entry_id = None + self._entry_data = {} + @staticmethod @callback def async_get_options_flow(config_entry): @@ -73,30 +80,87 @@ async def async_step_import( """Handle a flow initiated by configuration file.""" return await self.async_step_user(user_input) + async def async_step_reauth( + self, data: Optional[ConfigType] = None + ) -> Dict[str, Any]: + """Handle configuration by re-auth.""" + self._reauth = True + self._entry_data = dict(data) + self._entry_id = self._entry_data.pop("config_entry_id") + + return await self.async_step_reauth_confirm() + + async def async_step_reauth_confirm( + self, user_input: Optional[ConfigType] = None + ) -> Dict[str, Any]: + """Confirm reauth dialog.""" + if user_input is None: + return self.async_show_form( + step_id="reauth_confirm", + description_placeholders={"host": self._entry_data[CONF_HOST]}, + data_schema=vol.Schema({}), + errors={}, + ) + + assert self.hass + persistent_notification.async_dismiss(self.hass, "sonarr_reauth") + + return await self.async_step_user() + async def async_step_user( self, user_input: Optional[ConfigType] = None ) -> Dict[str, Any]: """Handle a flow initiated by the user.""" - if user_input is None: - return self._show_setup_form() + errors = {} - if CONF_VERIFY_SSL not in user_input: - user_input[CONF_VERIFY_SSL] = DEFAULT_VERIFY_SSL + if user_input is not None: + if self._reauth: + user_input = {**self._entry_data, **user_input} + + if CONF_VERIFY_SSL not in user_input: + user_input[CONF_VERIFY_SSL] = DEFAULT_VERIFY_SSL + + try: + await validate_input(self.hass, user_input) + except SonarrAccessRestricted: + errors = {"base": "invalid_auth"} + except SonarrError: + errors = {"base": "cannot_connect"} + except Exception: # pylint: disable=broad-except + _LOGGER.exception("Unexpected exception") + return self.async_abort(reason="unknown") + else: + if self._reauth: + return await self._async_reauth_update_entry( + self._entry_id, user_input + ) + + return self.async_create_entry( + title=user_input[CONF_HOST], data=user_input + ) + + data_schema = self._get_user_data_schema() + return self.async_show_form( + step_id="user", + data_schema=vol.Schema(data_schema), + errors=errors, + ) - try: - await validate_input(self.hass, user_input) - except SonarrAccessRestricted: - return self._show_setup_form({"base": "invalid_auth"}) - except SonarrError: - return self._show_setup_form({"base": "cannot_connect"}) - except Exception: # pylint: disable=broad-except - _LOGGER.exception("Unexpected exception") - return self.async_abort(reason="unknown") + async def _async_reauth_update_entry( + self, entry_id: str, data: dict + ) -> Dict[str, Any]: + """Update existing config entry.""" + entry = self.hass.config_entries.async_get_entry(entry_id) + self.hass.config_entries.async_update_entry(entry, data=data) + await self.hass.config_entries.async_reload(entry.entry_id) - return self.async_create_entry(title=user_input[CONF_HOST], data=user_input) + return self.async_abort(reason="reauth_successful") + + def _get_user_data_schema(self) -> Dict[str, Any]: + """Get the data schema to display user form.""" + if self._reauth: + return {vol.Required(CONF_API_KEY): str} - def _show_setup_form(self, errors: Optional[Dict] = None) -> Dict[str, Any]: - """Show the setup form to the user.""" data_schema = { vol.Required(CONF_HOST): str, vol.Required(CONF_API_KEY): str, @@ -110,11 +174,7 @@ def _show_setup_form(self, errors: Optional[Dict] = None) -> Dict[str, Any]: vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL) ] = bool - return self.async_show_form( - step_id="user", - data_schema=vol.Schema(data_schema), - errors=errors or {}, - ) + return data_schema class SonarrOptionsFlowHandler(OptionsFlow): diff --git a/homeassistant/components/sonarr/manifest.json b/homeassistant/components/sonarr/manifest.json index 3cd6e88913b4b1..65146b90759ac1 100644 --- a/homeassistant/components/sonarr/manifest.json +++ b/homeassistant/components/sonarr/manifest.json @@ -3,7 +3,7 @@ "name": "Sonarr", "documentation": "https://www.home-assistant.io/integrations/sonarr", "codeowners": ["@ctalkington"], - "requirements": ["sonarr==0.2.3"], + "requirements": ["sonarr==0.3.0"], "config_flow": true, "quality_scale": "silver" } diff --git a/homeassistant/components/sonarr/strings.json b/homeassistant/components/sonarr/strings.json index 481a3d381f0535..830b2ccdf7a0b6 100644 --- a/homeassistant/components/sonarr/strings.json +++ b/homeassistant/components/sonarr/strings.json @@ -10,9 +10,13 @@ "api_key": "[%key:common::config_flow::data::api_key%]", "base_path": "Path to API", "port": "[%key:common::config_flow::data::port%]", - "ssl": "Sonarr uses a SSL certificate", - "verify_ssl": "Sonarr uses a proper certificate" + "ssl": "[%key:common::config_flow::data::ssl%]", + "verify_ssl": "[%key:common::config_flow::data::verify_ssl%]" } + }, + "reauth_confirm": { + "title": "Re-authenticate with Sonarr", + "description": "The Sonarr integration needs to be manually re-authenticated with the Sonarr API hosted at: {host}" } }, "error": { @@ -21,6 +25,7 @@ }, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_service%]", + "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]", "unknown": "[%key:common::config_flow::error::unknown%]" } }, diff --git a/homeassistant/components/sonarr/translations/ca.json b/homeassistant/components/sonarr/translations/ca.json index ed59caf89df53a..7d82b5042ce12e 100644 --- a/homeassistant/components/sonarr/translations/ca.json +++ b/homeassistant/components/sonarr/translations/ca.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "El servei ja est\u00e0 configurat", + "reauth_successful": "Re-autenticaci\u00f3 exitosa", "unknown": "Error inesperat" }, "error": { @@ -10,14 +11,18 @@ }, "flow_title": "Sonarr: {name}", "step": { + "reauth_confirm": { + "description": "La integraci\u00f3 de Sonarr ha de tornar a autenticar-se manualment amb l'API de Sonarr allotjada a: {host}", + "title": "Re-autenticaci\u00f3 amb Sonarr" + }, "user": { "data": { "api_key": "Clau API", "base_path": "Ruta a l'API", "host": "Amfitri\u00f3", "port": "Port", - "ssl": "Sonarr utilitza un certificat SSL", - "verify_ssl": "Sonarr utilitza un certificat adequat" + "ssl": "Utilitza un certificat SSL", + "verify_ssl": "Verifica el certificat SSL" }, "title": "Connexi\u00f3 amb Sonarr" } diff --git a/homeassistant/components/sonarr/translations/el.json b/homeassistant/components/sonarr/translations/el.json new file mode 100644 index 00000000000000..f76b9222a410e8 --- /dev/null +++ b/homeassistant/components/sonarr/translations/el.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "reauth_successful": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c4\u03b7\u03ba\u03b5 \u03be\u03b1\u03bd\u03ac \u03bc\u03b5 \u03b5\u03c0\u03b9\u03c4\u03c5\u03c7\u03af\u03b1" + }, + "step": { + "reauth_confirm": { + "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 Sonarr \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bd\u03b1\u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03c7\u03b5\u03b9\u03c1\u03bf\u03ba\u03af\u03bd\u03b7\u03c4\u03b1 \u03bc\u03b5 \u03c4\u03bf Sonarr API \u03c0\u03bf\u03c5 \u03c6\u03b9\u03bb\u03bf\u03be\u03b5\u03bd\u03b5\u03af\u03c4\u03b1\u03b9 \u03c3\u03c4\u03b7 \u03b4\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7: {host}", + "title": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf\u03bd \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03bc\u03b5 \u03c4\u03bf Sonarr" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sonarr/translations/en.json b/homeassistant/components/sonarr/translations/en.json index 9e62ea16d77523..6bd9e34c142f9f 100644 --- a/homeassistant/components/sonarr/translations/en.json +++ b/homeassistant/components/sonarr/translations/en.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Service is already configured", + "reauth_successful": "Successfully re-authenticated", "unknown": "Unexpected error" }, "error": { @@ -10,14 +11,18 @@ }, "flow_title": "Sonarr: {name}", "step": { + "reauth_confirm": { + "description": "The Sonarr integration needs to be manually re-authenticated with the Sonarr API hosted at: {host}", + "title": "Re-authenticate with Sonarr" + }, "user": { "data": { "api_key": "API Key", "base_path": "Path to API", "host": "Host", "port": "Port", - "ssl": "Sonarr uses a SSL certificate", - "verify_ssl": "Sonarr uses a proper certificate" + "ssl": "Uses an SSL certificate", + "verify_ssl": "Verify SSL certificate" }, "title": "Connect to Sonarr" } diff --git a/homeassistant/components/sonarr/translations/es.json b/homeassistant/components/sonarr/translations/es.json index 29db7cfbd77f67..343af035865165 100644 --- a/homeassistant/components/sonarr/translations/es.json +++ b/homeassistant/components/sonarr/translations/es.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "El servicio ya est\u00e1 configurado", + "reauth_successful": "Se ha vuelto autenticar con \u00e9xito", "unknown": "Error inesperado" }, "error": { @@ -10,6 +11,10 @@ }, "flow_title": "Sonarr: {name}", "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" + }, "user": { "data": { "api_key": "Clave API", diff --git a/homeassistant/components/sonarr/translations/et.json b/homeassistant/components/sonarr/translations/et.json new file mode 100644 index 00000000000000..946402a1731741 --- /dev/null +++ b/homeassistant/components/sonarr/translations/et.json @@ -0,0 +1,9 @@ +{ + "config": { + "step": { + "reauth_confirm": { + "title": "Autentige uuesti Sonarriga" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sonarr/translations/fr.json b/homeassistant/components/sonarr/translations/fr.json index 0b35d915f2bd01..91d3b0db419c98 100644 --- a/homeassistant/components/sonarr/translations/fr.json +++ b/homeassistant/components/sonarr/translations/fr.json @@ -10,6 +10,9 @@ }, "flow_title": "Sonarr: {name}", "step": { + "reauth_confirm": { + "title": "R\u00e9-authentifier avec Sonarr" + }, "user": { "data": { "api_key": "Cl\u00e9 API", diff --git a/homeassistant/components/sonarr/translations/hu.json b/homeassistant/components/sonarr/translations/hu.json new file mode 100644 index 00000000000000..f5301e874eae05 --- /dev/null +++ b/homeassistant/components/sonarr/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/sonarr/translations/it.json b/homeassistant/components/sonarr/translations/it.json index ae6774db11d294..87e6d1ecd7fb3c 100644 --- a/homeassistant/components/sonarr/translations/it.json +++ b/homeassistant/components/sonarr/translations/it.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Il servizio \u00e8 gi\u00e0 configurato", + "reauth_successful": "Ri-autenticato con successo", "unknown": "Errore imprevisto" }, "error": { @@ -10,14 +11,18 @@ }, "flow_title": "Sonarr: {name}", "step": { + "reauth_confirm": { + "description": "L'integrazione di Sonarr deve essere nuovamente autenticata manualmente con l'API Sonarr ospitata su: {host}", + "title": "Eseguire nuovamente l'autenticazione con Sonarr" + }, "user": { "data": { "api_key": "Chiave API", "base_path": "Percorso dell'API", "host": "Host", "port": "Porta", - "ssl": "Sonarr utilizza un certificato SSL", - "verify_ssl": "Sonarr utilizza un certificato adeguato" + "ssl": "Utilizza un certificato SSL", + "verify_ssl": "Verificare il certificato SSL" }, "title": "Connettiti a Sonarr" } diff --git a/homeassistant/components/sonarr/translations/lb.json b/homeassistant/components/sonarr/translations/lb.json index 23c8116498c434..554bb3eebd264c 100644 --- a/homeassistant/components/sonarr/translations/lb.json +++ b/homeassistant/components/sonarr/translations/lb.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Service ass scho konfigur\u00e9iert", + "reauth_successful": "Erfollegr\u00e4ich re-authentifiz\u00e9iert", "unknown": "Onerwaarte Feeler" }, "error": { @@ -10,6 +11,10 @@ }, "flow_title": "Sonarr: {name}", "step": { + "reauth_confirm": { + "description": "Sonarr Integratioun muss manuell mat der Sonarr API um {host} re-authentifiz\u00e9iert ginn.", + "title": "Mat Sonarr re-authentifiz\u00e9ieren" + }, "user": { "data": { "api_key": "API Schl\u00ebssel", diff --git a/homeassistant/components/sonarr/translations/no.json b/homeassistant/components/sonarr/translations/no.json index 694565b72d6074..3cfe42583608f1 100644 --- a/homeassistant/components/sonarr/translations/no.json +++ b/homeassistant/components/sonarr/translations/no.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "Tjenesten er allerede konfigurert", + "reauth_successful": "Godkjent p\u00e5 nytt", "unknown": "Uventet feil" }, "error": { @@ -10,14 +11,18 @@ }, "flow_title": "", "step": { + "reauth_confirm": { + "description": "Sonarr-integrasjonen m\u00e5 autentiseres p\u00e5 nytt med Sonarr API vert p\u00e5: {host}", + "title": "Autentiser p\u00e5 nytt med Sonarr" + }, "user": { "data": { "api_key": "API N\u00f8kkel", "base_path": "Bane til API", "host": "Vert", "port": "", - "ssl": "Sonarr bruker et SSL-sertifikat", - "verify_ssl": "Sonarr bruker et riktig sertifikat" + "ssl": "Bruker et SSL-sertifikat", + "verify_ssl": "Verifisere SSL-sertifikat" }, "title": "Koble til Sonarr" } diff --git a/homeassistant/components/sonarr/translations/pl.json b/homeassistant/components/sonarr/translations/pl.json index e2c60427b7e2e7..d3038cc29eb1ae 100644 --- a/homeassistant/components/sonarr/translations/pl.json +++ b/homeassistant/components/sonarr/translations/pl.json @@ -2,14 +2,18 @@ "config": { "abort": { "already_configured": "Us\u0142uga jest ju\u017c skonfigurowana.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "reauth_successful": "Ponowne uwierzytelnianie powiod\u0142o si\u0119", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "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", + "invalid_auth": "Niepoprawne uwierzytelnienie" }, "flow_title": "Sonarr: {name}", "step": { + "reauth_confirm": { + "description": "Integracja Sonarr musi by\u0107 r\u0119cznie ponownie uwierzytelniona za pomoc\u0105 API Sonarr pod adresem: {host}" + }, "user": { "data": { "api_key": "Klucz API", diff --git a/homeassistant/components/sonarr/translations/ru.json b/homeassistant/components/sonarr/translations/ru.json index 158a3d59391f23..f8f92340bfd084 100644 --- a/homeassistant/components/sonarr/translations/ru.json +++ b/homeassistant/components/sonarr/translations/ru.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "already_configured": "\u042d\u0442\u0430 \u0441\u043b\u0443\u0436\u0431\u0430 \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0432 Home Assistant.", + "reauth_successful": "\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.", "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, "error": { @@ -10,14 +11,18 @@ }, "flow_title": "Sonarr: {name}", "step": { + "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\u0438 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e API Sonarr \u043f\u043e \u0430\u0434\u0440\u0435\u0441\u0443: {host}", + "title": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u043f\u0440\u043e\u0444\u0438\u043b\u044f" + }, "user": { "data": { "api_key": "\u041a\u043b\u044e\u0447 API", "base_path": "\u041f\u0443\u0442\u044c \u043a API", "host": "\u0425\u043e\u0441\u0442", "port": "\u041f\u043e\u0440\u0442", - "ssl": "Sonarr \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL", - "verify_ssl": "Sonarr \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\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", + "verify_ssl": "\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL" }, "title": "\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a Sonarr" } diff --git a/homeassistant/components/sonarr/translations/zh-Hant.json b/homeassistant/components/sonarr/translations/zh-Hant.json index dc03a007099e5d..f876907ec90caf 100644 --- a/homeassistant/components/sonarr/translations/zh-Hant.json +++ b/homeassistant/components/sonarr/translations/zh-Hant.json @@ -2,6 +2,7 @@ "config": { "abort": { "already_configured": "\u670d\u52d9\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "reauth_successful": "\u5df2\u6210\u529f\u91cd\u65b0\u8a8d\u8b49", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "error": { @@ -10,14 +11,18 @@ }, "flow_title": "Sonarr\uff1a{name}", "step": { + "reauth_confirm": { + "description": "Sonarr \u6574\u5408\u9700\u8981\u624b\u52d5\u91cd\u65b0\u8a8d\u8b49 Sonarr API\uff1a{host}", + "title": "\u91cd\u65b0\u8a8d\u8b49 Sonarr" + }, "user": { "data": { "api_key": "API \u5bc6\u9470", "base_path": "API \u8def\u5f91", "host": "\u4e3b\u6a5f\u7aef", "port": "\u901a\u8a0a\u57e0", - "ssl": "Sonarr \u4f7f\u7528 SSL \u8a8d\u8b49", - "verify_ssl": "Sonarr \u4f7f\u7528\u5c0d\u61c9\u8a8d\u8b49" + "ssl": "\u4f7f\u7528 SSL \u8a8d\u8b49", + "verify_ssl": "\u78ba\u8a8d SSL \u8a8d\u8b49" }, "title": "\u9023\u7dda\u81f3 Sonarr" } diff --git a/homeassistant/components/songpal/translations/fr.json b/homeassistant/components/songpal/translations/fr.json index a5f52833f4e8ab..5975bb955fa9bc 100644 --- a/homeassistant/components/songpal/translations/fr.json +++ b/homeassistant/components/songpal/translations/fr.json @@ -11,6 +11,11 @@ "step": { "init": { "description": "Voulez-vous configurer {name} ({host})?" + }, + "user": { + "data": { + "endpoint": "Terminaison" + } } } } diff --git a/homeassistant/components/songpal/translations/pl.json b/homeassistant/components/songpal/translations/pl.json index cc420f0f83a85d..6b5d29e06b384c 100644 --- a/homeassistant/components/songpal/translations/pl.json +++ b/homeassistant/components/songpal/translations/pl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", "not_songpal_device": "To nie jest urz\u0105dzenie Songpal." }, "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia." + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, "flow_title": "Sony Songpal {name} ({host})", "step": { diff --git a/homeassistant/components/sonos/manifest.json b/homeassistant/components/sonos/manifest.json index efad23ee1f2e74..0bb15d2949e150 100644 --- a/homeassistant/components/sonos/manifest.json +++ b/homeassistant/components/sonos/manifest.json @@ -3,11 +3,13 @@ "name": "Sonos", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/sonos", - "requirements": ["pysonos==0.0.33"], + "requirements": ["pysonos==0.0.34"], "ssdp": [ { "st": "urn:schemas-upnp-org:device:ZonePlayer:1" } ], - "codeowners": [] + "codeowners": [ + "@cgtobi" + ] } diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index 2b50f2864dca59..307fee923a3435 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -1547,6 +1547,13 @@ def library_payload(media_library): Used by async_browse_media. """ + if not media_library.browse_by_idstring( + "tracks", + "", + max_items=1, + ): + raise BrowseError("Local library not found") + children = [] for item in media_library.browse(): try: diff --git a/homeassistant/components/sonos/strings.json b/homeassistant/components/sonos/strings.json index 22414453af9cfd..12812d66692922 100644 --- a/homeassistant/components/sonos/strings.json +++ b/homeassistant/components/sonos/strings.json @@ -6,8 +6,8 @@ } }, "abort": { - "single_instance_allowed": "Only a single configuration of Sonos is necessary.", - "no_devices_found": "No Sonos devices found on the network." + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" } } } diff --git a/homeassistant/components/sonos/translations/ca.json b/homeassistant/components/sonos/translations/ca.json index c7bbae58a414e0..4f9995c6c118c2 100644 --- a/homeassistant/components/sonos/translations/ca.json +++ b/homeassistant/components/sonos/translations/ca.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "No s'han trobat dispositius Sonos a la xarxa.", - "single_instance_allowed": "Nom\u00e9s cal una sola configuraci\u00f3 de Sonos." + "no_devices_found": "No s'han trobat dispositius a la xarxa", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "step": { "confirm": { diff --git a/homeassistant/components/sonos/translations/en.json b/homeassistant/components/sonos/translations/en.json index 3cd46eae627ea1..38aecd5e965d0d 100644 --- a/homeassistant/components/sonos/translations/en.json +++ b/homeassistant/components/sonos/translations/en.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "No Sonos devices found on the network.", - "single_instance_allowed": "Only a single configuration of Sonos is necessary." + "no_devices_found": "No devices found on the network", + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "step": { "confirm": { diff --git a/homeassistant/components/sonos/translations/it.json b/homeassistant/components/sonos/translations/it.json index 70353a2613a5cc..1a646649a1bd6f 100644 --- a/homeassistant/components/sonos/translations/it.json +++ b/homeassistant/components/sonos/translations/it.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Non sono presenti dispositivi Sonos in rete.", - "single_instance_allowed": "\u00c8 necessaria una sola configurazione di Sonos." + "no_devices_found": "Nessun dispositivo trovato sulla rete", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "step": { "confirm": { diff --git a/homeassistant/components/sonos/translations/no.json b/homeassistant/components/sonos/translations/no.json index e5e4792eaebcd4..2da0b5a1b0bfd9 100644 --- a/homeassistant/components/sonos/translations/no.json +++ b/homeassistant/components/sonos/translations/no.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Ingen Sonos enheter funnet p\u00e5 nettverket.", - "single_instance_allowed": "Kun en konfigurasjon av Sonos er n\u00f8dvendig." + "no_devices_found": "Ingen enheter funnet p\u00e5 nettverket", + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "step": { "confirm": { diff --git a/homeassistant/components/sonos/translations/ru.json b/homeassistant/components/sonos/translations/ru.json index fa153fda3ce77d..f0b6ca6b6bf74c 100644 --- a/homeassistant/components/sonos/translations/ru.json +++ b/homeassistant/components/sonos/translations/ru.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 Sonos \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b.", - "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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." }, "step": { "confirm": { diff --git a/homeassistant/components/sonos/translations/zh-Hant.json b/homeassistant/components/sonos/translations/zh-Hant.json index 8ffa1577abef08..b47280e3a9e2e1 100644 --- a/homeassistant/components/sonos/translations/zh-Hant.json +++ b/homeassistant/components/sonos/translations/zh-Hant.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "\u5728\u7db2\u8def\u4e0a\u627e\u4e0d\u5230 Sonos \u8a2d\u5099\u3002", - "single_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u6b21 Sonos \u5373\u53ef\u3002" + "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u8a2d\u5099", + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002" }, "step": { "confirm": { diff --git a/homeassistant/components/spc/binary_sensor.py b/homeassistant/components/spc/binary_sensor.py index 75256b60cfbdcb..1fda59207ec6f1 100644 --- a/homeassistant/components/spc/binary_sensor.py +++ b/homeassistant/components/spc/binary_sensor.py @@ -3,7 +3,12 @@ from pyspcwebgw.const import ZoneInput, ZoneType -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_MOTION, + DEVICE_CLASS_OPENING, + DEVICE_CLASS_SMOKE, + BinarySensorEntity, +) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect @@ -14,9 +19,9 @@ def _get_device_class(zone_type): return { - ZoneType.ALARM: "motion", - ZoneType.ENTRY_EXIT: "opening", - ZoneType.FIRE: "smoke", + ZoneType.ALARM: DEVICE_CLASS_MOTION, + ZoneType.ENTRY_EXIT: DEVICE_CLASS_OPENING, + ZoneType.FIRE: DEVICE_CLASS_SMOKE, ZoneType.TECHNICAL: "power", }.get(zone_type) diff --git a/homeassistant/components/speedtestdotnet/__init__.py b/homeassistant/components/speedtestdotnet/__init__.py index 57557d4558a1ef..32562251ed4cfe 100644 --- a/homeassistant/components/speedtestdotnet/__init__.py +++ b/homeassistant/components/speedtestdotnet/__init__.py @@ -143,9 +143,12 @@ def update_servers(self): self.servers[DEFAULT_SERVER] = {} for server in sorted( - server_list.values(), key=lambda server: server[0]["country"] + server_list.values(), + key=lambda server: server[0]["country"] + server[0]["sponsor"], ): - self.servers[f"{server[0]['country']} - {server[0]['sponsor']}"] = server[0] + self.servers[ + f"{server[0]['country']} - {server[0]['sponsor']} - {server[0]['name']}" + ] = server[0] def update_data(self): """Get the latest data from speedtest.net.""" diff --git a/homeassistant/components/speedtestdotnet/config_flow.py b/homeassistant/components/speedtestdotnet/config_flow.py index 57076c2a90bab9..2bc462afdb0bc8 100644 --- a/homeassistant/components/speedtestdotnet/config_flow.py +++ b/homeassistant/components/speedtestdotnet/config_flow.py @@ -36,7 +36,7 @@ def async_get_options_flow(config_entry): async def async_step_user(self, user_input=None): """Handle a flow initialized by the user.""" if self._async_current_entries(): - return self.async_abort(reason="one_instance_allowed") + return self.async_abort(reason="single_instance_allowed") if user_input is None: return self.async_show_form(step_id="user") diff --git a/homeassistant/components/speedtestdotnet/strings.json b/homeassistant/components/speedtestdotnet/strings.json index f638c25a5494f1..c4b92b16ff347e 100644 --- a/homeassistant/components/speedtestdotnet/strings.json +++ b/homeassistant/components/speedtestdotnet/strings.json @@ -7,7 +7,7 @@ } }, "abort": { - "one_instance_allowed": "Only a single instance is necessary.", + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", "wrong_server_id": "Server id is not valid" } }, diff --git a/homeassistant/components/speedtestdotnet/translations/ca.json b/homeassistant/components/speedtestdotnet/translations/ca.json index 2cd81f8af2e2c8..2f09d5e15e57d3 100644 --- a/homeassistant/components/speedtestdotnet/translations/ca.json +++ b/homeassistant/components/speedtestdotnet/translations/ca.json @@ -2,6 +2,7 @@ "config": { "abort": { "one_instance_allowed": "Nom\u00e9s cal una \u00fanica inst\u00e0ncia.", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3.", "wrong_server_id": "L'identificador del servidor no \u00e9s v\u00e0lid" }, "step": { diff --git a/homeassistant/components/speedtestdotnet/translations/el.json b/homeassistant/components/speedtestdotnet/translations/el.json new file mode 100644 index 00000000000000..aecb2ee553fa42 --- /dev/null +++ b/homeassistant/components/speedtestdotnet/translations/el.json @@ -0,0 +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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/speedtestdotnet/translations/en.json b/homeassistant/components/speedtestdotnet/translations/en.json index 203640d350bcf6..98b5dd7b1837de 100644 --- a/homeassistant/components/speedtestdotnet/translations/en.json +++ b/homeassistant/components/speedtestdotnet/translations/en.json @@ -2,6 +2,7 @@ "config": { "abort": { "one_instance_allowed": "Only a single instance is necessary.", + "single_instance_allowed": "Already configured. Only a single configuration possible.", "wrong_server_id": "Server id is not valid" }, "step": { diff --git a/homeassistant/components/speedtestdotnet/translations/es.json b/homeassistant/components/speedtestdotnet/translations/es.json index 9c21c8e29a8a72..f9d16bc5a8c4c3 100644 --- a/homeassistant/components/speedtestdotnet/translations/es.json +++ b/homeassistant/components/speedtestdotnet/translations/es.json @@ -2,6 +2,7 @@ "config": { "abort": { "one_instance_allowed": "Solo se necesita una instancia.", + "single_instance_allowed": "Ya est\u00e1 configurado. S\u00f3lo es posible una \u00fanica configuraci\u00f3n.", "wrong_server_id": "Id del servidor no v\u00e1lido" }, "step": { diff --git a/homeassistant/components/speedtestdotnet/translations/et.json b/homeassistant/components/speedtestdotnet/translations/et.json new file mode 100644 index 00000000000000..b514abe05b64ba --- /dev/null +++ b/homeassistant/components/speedtestdotnet/translations/et.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "one_instance_allowed": "Vajalik on ainult \u00fcks sidumine.", + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine.", + "wrong_server_id": "Serveri ID ei sobi" + }, + "step": { + "user": { + "description": "Kas soovid seadistada SpeedTesti?", + "title": "Seadista SpeedTest" + } + } + }, + "options": { + "error": { + "retrive_error": "Viga serverite loendi hankimisel" + }, + "step": { + "init": { + "data": { + "manual": "Keela automaatne v\u00e4rskendamine", + "scan_interval": "Uuendamise sagedus (minutites)", + "server_name": "Testiserveri valimine" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/speedtestdotnet/translations/fr.json b/homeassistant/components/speedtestdotnet/translations/fr.json index 68f16cea44fd52..7b92a679f25c18 100644 --- a/homeassistant/components/speedtestdotnet/translations/fr.json +++ b/homeassistant/components/speedtestdotnet/translations/fr.json @@ -2,6 +2,7 @@ "config": { "abort": { "one_instance_allowed": "Une seule instance est n\u00e9cessaire.", + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible.", "wrong_server_id": "L'ID du serveur n'est pas valide" }, "step": { diff --git a/homeassistant/components/speedtestdotnet/translations/it.json b/homeassistant/components/speedtestdotnet/translations/it.json index 3ac6b03183c520..3c915c15265da1 100644 --- a/homeassistant/components/speedtestdotnet/translations/it.json +++ b/homeassistant/components/speedtestdotnet/translations/it.json @@ -2,6 +2,7 @@ "config": { "abort": { "one_instance_allowed": "\u00c8 necessaria solo una singola istanza.", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione.", "wrong_server_id": "L'ID del server non \u00e8 valido" }, "step": { diff --git a/homeassistant/components/speedtestdotnet/translations/lb.json b/homeassistant/components/speedtestdotnet/translations/lb.json index b9587e71087c24..7ceedc228e3bd9 100644 --- a/homeassistant/components/speedtestdotnet/translations/lb.json +++ b/homeassistant/components/speedtestdotnet/translations/lb.json @@ -2,6 +2,7 @@ "config": { "abort": { "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg.", + "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech.", "wrong_server_id": "Server ID ass ong\u00eblteg" }, "step": { diff --git a/homeassistant/components/speedtestdotnet/translations/no.json b/homeassistant/components/speedtestdotnet/translations/no.json index da2945b2985999..b346f1dae6df8e 100644 --- a/homeassistant/components/speedtestdotnet/translations/no.json +++ b/homeassistant/components/speedtestdotnet/translations/no.json @@ -2,6 +2,7 @@ "config": { "abort": { "one_instance_allowed": "Kun en enkelt forekomst er n\u00f8dvendig.", + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig.", "wrong_server_id": "Server-ID er ikke gyldig" }, "step": { diff --git a/homeassistant/components/speedtestdotnet/translations/pl.json b/homeassistant/components/speedtestdotnet/translations/pl.json new file mode 100644 index 00000000000000..014c93446f14d4 --- /dev/null +++ b/homeassistant/components/speedtestdotnet/translations/pl.json @@ -0,0 +1,28 @@ +{ + "config": { + "abort": { + "one_instance_allowed": "Wymagana jest tylko jedna instancja.", + "wrong_server_id": "Identyfikator serwera jest nieprawid\u0142owy" + }, + "step": { + "user": { + "description": "Czy na pewno chcesz skonfigurowa\u0107 SpeedTest?", + "title": "Konfiguracja SpeedTest" + } + } + }, + "options": { + "error": { + "retrive_error": "B\u0142\u0105d podczas pobierania listy serwer\u00f3w" + }, + "step": { + "init": { + "data": { + "manual": "Wy\u0142\u0105cz automatyczne aktualizacje", + "scan_interval": "Cz\u0119stotliwo\u015b\u0107 aktualizacji (w minutach)", + "server_name": "Wybierz serwer" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/speedtestdotnet/translations/ru.json b/homeassistant/components/speedtestdotnet/translations/ru.json index fe5cbc1d89073c..e17f1d4d05dc67 100644 --- a/homeassistant/components/speedtestdotnet/translations/ru.json +++ b/homeassistant/components/speedtestdotnet/translations/ru.json @@ -2,6 +2,7 @@ "config": { "abort": { "one_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "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." }, "step": { diff --git a/homeassistant/components/speedtestdotnet/translations/zh-Hant.json b/homeassistant/components/speedtestdotnet/translations/zh-Hant.json index 1661030b2f971f..5a5e072b99d4a3 100644 --- a/homeassistant/components/speedtestdotnet/translations/zh-Hant.json +++ b/homeassistant/components/speedtestdotnet/translations/zh-Hant.json @@ -2,6 +2,7 @@ "config": { "abort": { "one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u5373\u53ef\u3002", + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002", "wrong_server_id": "\u4f3a\u670d\u5668 ID \u7121\u6548" }, "step": { diff --git a/homeassistant/components/spider/translations/de.json b/homeassistant/components/spider/translations/de.json new file mode 100644 index 00000000000000..6f39806287630f --- /dev/null +++ b/homeassistant/components/spider/translations/de.json @@ -0,0 +1,12 @@ +{ + "config": { + "step": { + "user": { + "data": { + "password": "Passwort", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/spider/translations/fr.json b/homeassistant/components/spider/translations/fr.json index 959a28add769c2..8658343db6ac0c 100644 --- a/homeassistant/components/spider/translations/fr.json +++ b/homeassistant/components/spider/translations/fr.json @@ -12,7 +12,8 @@ "data": { "password": "Mot de passe", "username": "Nom d'utilisateur" - } + }, + "title": "Connectez-vous avec le compte mijn.ithodaalderop.nl" } } } diff --git a/homeassistant/components/spider/translations/ko.json b/homeassistant/components/spider/translations/ko.json new file mode 100644 index 00000000000000..1f08b96ee10939 --- /dev/null +++ b/homeassistant/components/spider/translations/ko.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "invalid_auth": "\uc798\ubabb\ub41c \uc778\uc99d", + "unknown": "\uc608\uc0c1\uce58 \ubabb\ud55c \uc5d0\ub7ec" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/spider/translations/nl.json b/homeassistant/components/spider/translations/nl.json new file mode 100644 index 00000000000000..4d00f0bfc74883 --- /dev/null +++ b/homeassistant/components/spider/translations/nl.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Gebruikersnaam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/spider/translations/pl.json b/homeassistant/components/spider/translations/pl.json new file mode 100644 index 00000000000000..f288bcbd78bb61 --- /dev/null +++ b/homeassistant/components/spider/translations/pl.json @@ -0,0 +1,20 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." + }, + "error": { + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "user": { + "data": { + "password": "Has\u0142o", + "username": "Nazwa u\u017cytkownika" + }, + "title": "Zaloguj si\u0119 na konto mijn.ithodaalderop.nl" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/splunk/__init__.py b/homeassistant/components/splunk/__init__.py index bbff510db1487a..a3ec307d67b15e 100644 --- a/homeassistant/components/splunk/__init__.py +++ b/homeassistant/components/splunk/__init__.py @@ -1,9 +1,11 @@ -"""Support to send data to an Splunk instance.""" +"""Support to send data to a Splunk instance.""" +import asyncio import json import logging +import time -from aiohttp.hdrs import AUTHORIZATION -import requests +from aiohttp import ClientConnectionError, ClientResponseError +from hass_splunk import SplunkPayloadError, hass_splunk import voluptuous as vol from homeassistant.const import ( @@ -16,14 +18,15 @@ EVENT_STATE_CHANGED, ) from homeassistant.helpers import state as state_helper +from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entityfilter import FILTER_SCHEMA from homeassistant.helpers.json import JSONEncoder _LOGGER = logging.getLogger(__name__) -CONF_FILTER = "filter" DOMAIN = "splunk" +CONF_FILTER = "filter" DEFAULT_HOST = "localhost" DEFAULT_PORT = 8088 @@ -48,23 +51,7 @@ ) -def post_request(event_collector, body, headers, verify_ssl): - """Post request to Splunk.""" - try: - payload = {"host": event_collector, "event": body} - requests.post( - event_collector, - data=json.dumps(payload, cls=JSONEncoder), - headers=headers, - timeout=10, - verify=verify_ssl, - ) - - except requests.exceptions.RequestException as error: - _LOGGER.exception("Error saving event to Splunk: %s", error) - - -def setup(hass, config): +async def async_setup(hass, config): """Set up the Splunk component.""" conf = config[DOMAIN] host = conf.get(CONF_HOST) @@ -75,18 +62,33 @@ def setup(hass, config): name = conf.get(CONF_NAME) entity_filter = conf[CONF_FILTER] - if use_ssl: - uri_scheme = "https://" - else: - uri_scheme = "http://" - - event_collector = f"{uri_scheme}{host}:{port}/services/collector/event" - headers = {AUTHORIZATION: f"Splunk {token}"} - - def splunk_event_listener(event): + event_collector = hass_splunk( + session=async_get_clientsession(hass), + host=host, + port=port, + token=token, + use_ssl=use_ssl, + verify_ssl=verify_ssl, + ) + + if not await event_collector.check(connectivity=False, token=True, busy=False): + return False + + payload = { + "time": time.time(), + "host": name, + "event": { + "domain": DOMAIN, + "meta": "Splunk integration has started", + }, + } + + await event_collector.queue(json.dumps(payload, cls=JSONEncoder), send=False) + + async def splunk_event_listener(event): """Listen for new messages on the bus and sends them to Splunk.""" - state = event.data.get("new_state") + state = event.data.get("new_state") if state is None or not entity_filter(state.entity_id): return @@ -95,19 +97,31 @@ def splunk_event_listener(event): except ValueError: _state = state.state - json_body = [ - { + payload = { + "time": event.time_fired.timestamp(), + "host": name, + "event": { "domain": state.domain, "entity_id": state.object_id, "attributes": dict(state.attributes), - "time": str(event.time_fired), "value": _state, - "host": name, - } - ] - - post_request(event_collector, json_body, headers, verify_ssl) + }, + } - hass.bus.listen(EVENT_STATE_CHANGED, splunk_event_listener) + try: + await event_collector.queue(json.dumps(payload, cls=JSONEncoder), send=True) + except SplunkPayloadError as err: + if err.status == 401: + _LOGGER.error(err) + else: + _LOGGER.warning(err) + except ClientConnectionError as err: + _LOGGER.warning(err) + except asyncio.TimeoutError: + _LOGGER.warning("Connection to %s:%s timed out", host, port) + except ClientResponseError as err: + _LOGGER.error(err.message) + + hass.bus.async_listen(EVENT_STATE_CHANGED, splunk_event_listener) return True diff --git a/homeassistant/components/splunk/manifest.json b/homeassistant/components/splunk/manifest.json index 337458b4c3f945..d51d6c712de797 100644 --- a/homeassistant/components/splunk/manifest.json +++ b/homeassistant/components/splunk/manifest.json @@ -2,5 +2,10 @@ "domain": "splunk", "name": "Splunk", "documentation": "https://www.home-assistant.io/integrations/splunk", - "codeowners": [] -} + "requirements": [ + "hass_splunk==0.1.1" + ], + "codeowners": [ + "@Bre77" + ] +} \ No newline at end of file diff --git a/homeassistant/components/spotify/manifest.json b/homeassistant/components/spotify/manifest.json index d17cd43c47f1af..db2f35ded91f0e 100644 --- a/homeassistant/components/spotify/manifest.json +++ b/homeassistant/components/spotify/manifest.json @@ -2,7 +2,7 @@ "domain": "spotify", "name": "Spotify", "documentation": "https://www.home-assistant.io/integrations/spotify", - "requirements": ["spotipy==2.14.0"], + "requirements": ["spotipy==2.16.0"], "zeroconf": ["_spotify-connect._tcp.local."], "dependencies": ["http"], "codeowners": ["@frenck"], diff --git a/homeassistant/components/spotify/media_player.py b/homeassistant/components/spotify/media_player.py index 7beea59a7bd219..0782cb2f3908ee 100644 --- a/homeassistant/components/spotify/media_player.py +++ b/homeassistant/components/spotify/media_player.py @@ -105,24 +105,57 @@ } CONTENT_TYPE_MEDIA_CLASS = { - "current_user_playlists": MEDIA_CLASS_DIRECTORY, - "current_user_followed_artists": MEDIA_CLASS_DIRECTORY, - "current_user_saved_albums": MEDIA_CLASS_DIRECTORY, - "current_user_saved_tracks": MEDIA_CLASS_DIRECTORY, - "current_user_saved_shows": MEDIA_CLASS_DIRECTORY, - "current_user_recently_played": MEDIA_CLASS_DIRECTORY, - "current_user_top_artists": MEDIA_CLASS_DIRECTORY, - "current_user_top_tracks": MEDIA_CLASS_DIRECTORY, - "featured_playlists": MEDIA_CLASS_DIRECTORY, - "categories": MEDIA_CLASS_DIRECTORY, - "category_playlists": MEDIA_CLASS_DIRECTORY, - "new_releases": MEDIA_CLASS_DIRECTORY, - MEDIA_TYPE_PLAYLIST: MEDIA_CLASS_PLAYLIST, - MEDIA_TYPE_ALBUM: MEDIA_CLASS_ALBUM, - MEDIA_TYPE_ARTIST: MEDIA_CLASS_ARTIST, - MEDIA_TYPE_EPISODE: MEDIA_CLASS_EPISODE, - MEDIA_TYPE_SHOW: MEDIA_CLASS_PODCAST, - MEDIA_TYPE_TRACK: MEDIA_CLASS_TRACK, + "current_user_playlists": { + "parent": MEDIA_CLASS_DIRECTORY, + "children": MEDIA_CLASS_PLAYLIST, + }, + "current_user_followed_artists": { + "parent": MEDIA_CLASS_DIRECTORY, + "children": MEDIA_CLASS_ARTIST, + }, + "current_user_saved_albums": { + "parent": MEDIA_CLASS_DIRECTORY, + "children": MEDIA_CLASS_ALBUM, + }, + "current_user_saved_tracks": { + "parent": MEDIA_CLASS_DIRECTORY, + "children": MEDIA_CLASS_TRACK, + }, + "current_user_saved_shows": { + "parent": MEDIA_CLASS_DIRECTORY, + "children": MEDIA_CLASS_PODCAST, + }, + "current_user_recently_played": { + "parent": MEDIA_CLASS_DIRECTORY, + "children": MEDIA_CLASS_TRACK, + }, + "current_user_top_artists": { + "parent": MEDIA_CLASS_DIRECTORY, + "children": MEDIA_CLASS_ARTIST, + }, + "current_user_top_tracks": { + "parent": MEDIA_CLASS_DIRECTORY, + "children": MEDIA_CLASS_TRACK, + }, + "featured_playlists": { + "parent": MEDIA_CLASS_DIRECTORY, + "children": MEDIA_CLASS_PLAYLIST, + }, + "categories": {"parent": MEDIA_CLASS_DIRECTORY, "children": MEDIA_CLASS_GENRE}, + "category_playlists": { + "parent": MEDIA_CLASS_DIRECTORY, + "children": MEDIA_CLASS_PLAYLIST, + }, + "new_releases": {"parent": MEDIA_CLASS_DIRECTORY, "children": MEDIA_CLASS_ALBUM}, + MEDIA_TYPE_PLAYLIST: { + "parent": MEDIA_CLASS_PLAYLIST, + "children": MEDIA_CLASS_TRACK, + }, + MEDIA_TYPE_ALBUM: {"parent": MEDIA_CLASS_ALBUM, "children": MEDIA_CLASS_TRACK}, + MEDIA_TYPE_ARTIST: {"parent": MEDIA_CLASS_ARTIST, "children": MEDIA_CLASS_ALBUM}, + MEDIA_TYPE_EPISODE: {"parent": MEDIA_CLASS_EPISODE, "children": None}, + MEDIA_TYPE_SHOW: {"parent": MEDIA_CLASS_PODCAST, "children": MEDIA_CLASS_EPISODE}, + MEDIA_TYPE_TRACK: {"parent": MEDIA_CLASS_TRACK, "children": None}, } @@ -543,7 +576,8 @@ def build_item_response(spotify, user, payload): if media_content_type == "categories": media_item = BrowseMedia( title=LIBRARY_MAP.get(media_content_id), - media_class=media_class, + media_class=media_class["parent"], + children_media_class=media_class["children"], media_content_id=media_content_id, media_content_type=media_content_type, can_play=False, @@ -560,6 +594,7 @@ def build_item_response(spotify, user, payload): BrowseMedia( title=item.get("name"), media_class=MEDIA_CLASS_PLAYLIST, + children_media_class=MEDIA_CLASS_TRACK, media_content_id=item_id, media_content_type="category_playlists", thumbnail=fetch_image_url(item, key="icons"), @@ -567,7 +602,6 @@ def build_item_response(spotify, user, payload): can_expand=True, ) ) - media_item.children_media_class = MEDIA_CLASS_GENRE return media_item if title is None: @@ -578,7 +612,8 @@ def build_item_response(spotify, user, payload): params = { "title": title, - "media_class": media_class, + "media_class": media_class["parent"], + "children_media_class": media_class["children"], "media_content_id": media_content_id, "media_content_type": media_content_type, "can_play": media_content_type in PLAYABLE_MEDIA_TYPES, @@ -625,7 +660,8 @@ def item_payload(item): payload = { "title": item.get("name"), - "media_class": media_class, + "media_class": media_class["parent"], + "children_media_class": media_class["children"], "media_content_id": media_id, "media_content_type": media_type, "can_play": media_type in PLAYABLE_MEDIA_TYPES, diff --git a/homeassistant/components/spotify/strings.json b/homeassistant/components/spotify/strings.json index 8e3fa6fc6790d0..76cad9bdf804a0 100644 --- a/homeassistant/components/spotify/strings.json +++ b/homeassistant/components/spotify/strings.json @@ -1,14 +1,15 @@ { "config": { "step": { - "pick_implementation": { "title": "Pick Authentication Method" }, + "pick_implementation": { + "title": "[%key:common::config_flow::title::oauth2_pick_implementation%]" + }, "reauth_confirm": { "title": "Re-authenticate with Spotify", "description": "The Spotify integration needs to re-authenticate with Spotify for account: {account}" } }, "abort": { - "already_setup": "You can only configure one Spotify account.", "authorize_url_timeout": "Timeout generating authorize url.", "no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]", "missing_configuration": "The Spotify integration is not configured. Please follow the documentation.", diff --git a/homeassistant/components/spotify/translations/el.json b/homeassistant/components/spotify/translations/el.json new file mode 100644 index 00000000000000..d2580d58c0ba9a --- /dev/null +++ b/homeassistant/components/spotify/translations/el.json @@ -0,0 +1,13 @@ +{ + "config": { + "abort": { + "reauth_account_mismatch": "\u039f \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc\u03c2 Spotify \u03bc\u03b5 \u03c4\u03bf\u03bd \u03bf\u03c0\u03bf\u03af\u03bf \u03ad\u03c7\u03b5\u03b9 \u03b3\u03af\u03bd\u03b5\u03b9 \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf\u03c2 \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2, \u03b4\u03b5\u03bd \u03c3\u03c5\u03bc\u03c6\u03c9\u03bd\u03b5\u03af \u03bc\u03b5 \u03c4\u03bf\u03bd \u03b1\u03c0\u03b1\u03b9\u03c4\u03bf\u03cd\u03bc\u03b5\u03bd\u03bf \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." + }, + "step": { + "reauth_confirm": { + "description": "\u0397 \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 Spotify \u03c0\u03c1\u03ad\u03c0\u03b5\u03b9 \u03bd\u03b1 \u03b5\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03b5\u03b9 \u03be\u03b1\u03bd\u03ac \u03c4\u03bf\u03bd \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03bc\u03b5 \u03c4\u03bf Spotify \u03b3\u03b9\u03b1 \u03c4\u03bf\u03bd \u03bb\u03bf\u03b3\u03b1\u03c1\u03b9\u03b1\u03c3\u03bc\u03cc: {account}", + "title": "\u0395\u03c0\u03b1\u03bb\u03b7\u03b8\u03b5\u03cd\u03c3\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ad\u03bb\u03b5\u03b3\u03c7\u03bf \u03c4\u03b1\u03c5\u03c4\u03cc\u03c4\u03b7\u03c4\u03b1\u03c2 \u03bc\u03b5 \u03c4\u03bf Spotify" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/spotify/translations/es.json b/homeassistant/components/spotify/translations/es.json index 95c29f7a33693c..025777ad3f6667 100644 --- a/homeassistant/components/spotify/translations/es.json +++ b/homeassistant/components/spotify/translations/es.json @@ -4,6 +4,7 @@ "already_setup": "S\u00f3lo puedes configurar una cuenta de Spotify.", "authorize_url_timeout": "Tiempo de espera agotado generando la url de autorizaci\u00f3n.", "missing_configuration": "La integraci\u00f3n de Spotify no est\u00e1 configurada. Por favor, siga 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_account_mismatch": "La cuenta de Spotify con la que est\u00e1s autenticado, no coincide con la cuenta necesaria para re-autenticaci\u00f3n." }, "create_entry": { diff --git a/homeassistant/components/spotify/translations/et.json b/homeassistant/components/spotify/translations/et.json new file mode 100644 index 00000000000000..aaeab0a4039ff5 --- /dev/null +++ b/homeassistant/components/spotify/translations/et.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "already_setup": "Saate konfigureerida ainult \u00fche Spotify konto.", + "authorize_url_timeout": "Kinnituse URLi ajal\u00f5pp", + "missing_configuration": "Spotify sidumine pole h\u00e4\u00e4lestatud. Palun j\u00e4rgige dokumentatsiooni.", + "no_url_available": "URL ploe saadaval. Rohkem teavet [check the help section]({docs_url})", + "reauth_account_mismatch": "Spotify konto mida autenditi ei vasta kontole mis vajas uuesti autentimist." + }, + "create_entry": { + "default": "Edukalt Spotifyga autenditud." + }, + "step": { + "pick_implementation": { + "title": "Valige autentimismeetod" + }, + "reauth_confirm": { + "description": "Spotify konto: {account} sidumine tuleb uuesti autentida", + "title": "Autendi Spotify uuesti" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/spotify/translations/fr.json b/homeassistant/components/spotify/translations/fr.json index 629aa5c681fce4..251c85920aa3f4 100644 --- a/homeassistant/components/spotify/translations/fr.json +++ b/homeassistant/components/spotify/translations/fr.json @@ -4,6 +4,7 @@ "already_setup": "Vous ne pouvez configurer qu'un seul compte Spotify.", "authorize_url_timeout": "D\u00e9lai d'expiration g\u00e9n\u00e9rant une URL d'autorisation.", "missing_configuration": "L'int\u00e9gration Spotify n'est pas configur\u00e9e. Veuillez suivre la documentation.", + "no_url_available": "Aucune URL disponible. Pour plus d'informations sur cette erreur, [consultez la section d'aide] ( {docs_url} )", "reauth_account_mismatch": "Le compte Spotify authentifi\u00e9 ne correspond pas au compte requis pour la r\u00e9-authentification." }, "create_entry": { diff --git a/homeassistant/components/spotify/translations/ko.json b/homeassistant/components/spotify/translations/ko.json index deb55479c1e561..37dccd8c1a6af4 100644 --- a/homeassistant/components/spotify/translations/ko.json +++ b/homeassistant/components/spotify/translations/ko.json @@ -3,7 +3,9 @@ "abort": { "already_setup": "\ud558\ub098\uc758 Spotify \uacc4\uc815\ub9cc \uad6c\uc131\ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4.", "authorize_url_timeout": "\uc778\uc99d url \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "missing_configuration": "Spotify \uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694." + "missing_configuration": "Spotify \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": "\uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc5d0\ub7ec\uc5d0 \ub300\ud55c \uc815\ubcf4\ub294 \ub3c4\uc6c0\ub9d0 \uc139\uc158\uc744 \ud655\uc778\ud558\uc138\uc694({docs_url})", + "reauth_account_mismatch": "\uc778\uc99d\ub41c Spotify \uacc4\uc815\uc740 \uc7ac\uc778\uc99d\uc774 \ud544\uc694\ud55c \uacc4\uc815\uacfc \uc77c\uce58\ud558\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4." }, "create_entry": { "default": "Spotify \ub85c \uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4." @@ -11,6 +13,10 @@ "step": { "pick_implementation": { "title": "\uc778\uc99d \ubc29\ubc95 \uc120\ud0dd\ud558\uae30" + }, + "reauth_confirm": { + "description": "Spotify \ud1b5\ud569\uc740 \uacc4\uc815 {account} \ub300\ud574 Spotify\ub85c \ub2e4\uc2dc \uc778\uc99d\ud574\uc57c\ud569\ub2c8\ub2e4.", + "title": "Spotify\ub85c \uc7ac \uc778\uc99d" } } } diff --git a/homeassistant/components/spotify/translations/lb.json b/homeassistant/components/spotify/translations/lb.json index fedaff526a62c1..59aaf936dc15ca 100644 --- a/homeassistant/components/spotify/translations/lb.json +++ b/homeassistant/components/spotify/translations/lb.json @@ -3,7 +3,8 @@ "abort": { "already_setup": "Dir k\u00ebnnt n\u00ebmmen een eenzegen Spotify Kont konfigur\u00e9ieren.", "authorize_url_timeout": "Z\u00e4it Iwwerschreidung beim gener\u00e9ieren vun der Autorisatiouns URL.", - "missing_configuration": "Spotifiy Integratioun ass nach net konfigur\u00e9iert. Follegt w.e.g der Dokumentatioun." + "missing_configuration": "Spotifiy Integratioun ass nach net konfigur\u00e9iert. Follegt w.e.g der Dokumentatioun.", + "no_url_available": "Keng URL disponibel. Fir Informatiounen iwwert d\u00ebse Feeler, [kuck H\u00ebllef Sektioun]({docs_url})" }, "create_entry": { "default": "Erfollegr\u00e4ich mat Spotify authentifiz\u00e9iert." diff --git a/homeassistant/components/spotify/translations/nl.json b/homeassistant/components/spotify/translations/nl.json index 82b25512001716..b300066ca95c64 100644 --- a/homeassistant/components/spotify/translations/nl.json +++ b/homeassistant/components/spotify/translations/nl.json @@ -3,7 +3,9 @@ "abort": { "already_setup": "U kunt slechts \u00e9\u00e9n Spotify-account configureren.", "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", - "missing_configuration": "De Spotify integratie is niet geconfigureerd. Gelieve de documentatie te volgen." + "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})", + "reauth_account_mismatch": "Het Spotify account waarmee er is geverifieerd, komt niet overeen met het account dat opnieuw moet worden geverifieerd." }, "create_entry": { "default": "Succesvol geauthenticeerd met Spotify." @@ -11,6 +13,10 @@ "step": { "pick_implementation": { "title": "Kies Authenticatiemethode" + }, + "reauth_confirm": { + "description": "De Spotify integratie moet opnieuw worden geverifieerd met Spotify voor account: {account}", + "title": "Verifieer opnieuw met Spotify" } } } diff --git a/homeassistant/components/spotify/translations/no.json b/homeassistant/components/spotify/translations/no.json index c2e151e5eb7848..6ab60ddbc0c5e8 100644 --- a/homeassistant/components/spotify/translations/no.json +++ b/homeassistant/components/spotify/translations/no.json @@ -4,6 +4,7 @@ "already_setup": "Du kan bare konfigurere en Spotify-konto.", "authorize_url_timeout": "Tidsavbrudd ved oppretting av godkjenningsadresse.", "missing_configuration": "Spotify-integrasjonen er ikke konfigurert. F\u00f8lg dokumentasjonen.", + "no_url_available": "Ingen URL tilgjengelig. For informasjon om denne feilen, [sjekk {docs_url} ] ( {docs_url} )", "reauth_account_mismatch": "Spotify-kontoen som er autentisert med, samsvarer ikke med den kontoen som trengs re-autentisering." }, "create_entry": { diff --git a/homeassistant/components/spotify/translations/pl.json b/homeassistant/components/spotify/translations/pl.json index 0ed5a24ce16c81..06ae75cc0bb60e 100644 --- a/homeassistant/components/spotify/translations/pl.json +++ b/homeassistant/components/spotify/translations/pl.json @@ -3,7 +3,8 @@ "abort": { "already_setup": "Mo\u017cesz skonfigurowa\u0107 tylko jedno konto Spotify.", "authorize_url_timeout": "Przekroczono limit czasu generowania URL autoryzacji.", - "missing_configuration": "Integracja ze Spotify nie jest skonfigurowana. Post\u0119puj zgodnie z dokumentacj\u0105." + "missing_configuration": "Integracja ze Spotify nie jest skonfigurowana. Post\u0119puj zgodnie z dokumentacj\u0105.", + "reauth_account_mismatch": "Uwierzytelnione konto Spotify, nie pasuje do konta, kt\u00f3re wymaga ponownego uwierzytelnienia." }, "create_entry": { "default": "Pomy\u015blnie uwierzytelniono z Spotify" @@ -13,6 +14,7 @@ "title": "Wybierz metod\u0119 uwierzytelniania" }, "reauth_confirm": { + "description": "Integracja Spotify wymaga ponownego uwierzytelnienia w Spotify dla konta: {account}", "title": "Ponownie uwierzytelnij ze Spotify" } } diff --git a/homeassistant/components/spotify/translations/zh-Hant.json b/homeassistant/components/spotify/translations/zh-Hant.json index a4fca36205f120..d7e52012c363d0 100644 --- a/homeassistant/components/spotify/translations/zh-Hant.json +++ b/homeassistant/components/spotify/translations/zh-Hant.json @@ -4,6 +4,7 @@ "already_setup": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44 Spotify \u5e33\u865f\u3002", "authorize_url_timeout": "\u7522\u751f\u8a8d\u8b49 URL \u6642\u903e\u6642\u3002", "missing_configuration": "Spotify \u6574\u5408\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})", "reauth_account_mismatch": "Spotify \u6240\u8a8d\u8b49\u5e33\u865f\u8207\u5e33\u865f\u4e0d\u7b26\u5408\uff0c\u9700\u91cd\u65b0\u9032\u884c\u8a8d\u8b49\u3002" }, "create_entry": { diff --git a/homeassistant/components/sql/sensor.py b/homeassistant/components/sql/sensor.py index f19941ed0433ff..27656c260d38fa 100644 --- a/homeassistant/components/sql/sensor.py +++ b/homeassistant/components/sql/sensor.py @@ -122,6 +122,7 @@ def device_state_attributes(self): def update(self): """Retrieve sensor data from the query.""" + data = None try: sess = self.sessionmaker() result = sess.execute(self._query) @@ -147,7 +148,7 @@ def update(self): finally: sess.close() - if self._template is not None: + if data is not None and self._template is not None: self._state = self._template.async_render_with_possible_json_value( data, None ) diff --git a/homeassistant/components/squeezebox/translations/de.json b/homeassistant/components/squeezebox/translations/de.json index 4d024c59d26478..8671822cdf8fbd 100644 --- a/homeassistant/components/squeezebox/translations/de.json +++ b/homeassistant/components/squeezebox/translations/de.json @@ -4,9 +4,16 @@ "edit": { "data": { "password": "Passwort", + "port": "Port", "username": "Benutzername" } + }, + "user": { + "data": { + "host": "Host" + } } } - } + }, + "title": "Logitech Squeezebox" } \ No newline at end of file diff --git a/homeassistant/components/squeezebox/translations/fr.json b/homeassistant/components/squeezebox/translations/fr.json index 9e7445a4a3200c..6119bc34f8d247 100644 --- a/homeassistant/components/squeezebox/translations/fr.json +++ b/homeassistant/components/squeezebox/translations/fr.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", + "no_server_found": "Aucun serveur LMS trouv\u00e9." }, "error": { "cannot_connect": "\u00c9chec de connexion", @@ -17,7 +18,8 @@ "password": "Mot de passe", "port": "Port", "username": "Username" - } + }, + "title": "Modifier les informations de connexion" }, "user": { "data": { diff --git a/homeassistant/components/squeezebox/translations/hu.json b/homeassistant/components/squeezebox/translations/hu.json new file mode 100644 index 00000000000000..3b2d79a34a77e2 --- /dev/null +++ b/homeassistant/components/squeezebox/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/squeezebox/translations/nl.json b/homeassistant/components/squeezebox/translations/nl.json new file mode 100644 index 00000000000000..bb140f4ca899d6 --- /dev/null +++ b/homeassistant/components/squeezebox/translations/nl.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "edit": { + "data": { + "username": "Gebruikersnaam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/squeezebox/translations/pl.json b/homeassistant/components/squeezebox/translations/pl.json index a43397119189b7..4bf27bbb3ec27f 100644 --- a/homeassistant/components/squeezebox/translations/pl.json +++ b/homeassistant/components/squeezebox/translations/pl.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" }, "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", - "invalid_auth": "Niepoprawne uwierzytelnienie.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "invalid_auth": "Niepoprawne uwierzytelnienie", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "edit": { diff --git a/homeassistant/components/ssdp/__init__.py b/homeassistant/components/ssdp/__init__.py index 555d68cd5d4518..af2ae21dac3641 100644 --- a/homeassistant/components/ssdp/__init__.py +++ b/homeassistant/components/ssdp/__init__.py @@ -17,16 +17,22 @@ # Attributes for accessing info from SSDP response ATTR_SSDP_LOCATION = "ssdp_location" ATTR_SSDP_ST = "ssdp_st" +ATTR_SSDP_USN = "ssdp_usn" +ATTR_SSDP_EXT = "ssdp_ext" +ATTR_SSDP_SERVER = "ssdp_server" # Attributes for accessing info from retrieved UPnP device description ATTR_UPNP_DEVICE_TYPE = "deviceType" ATTR_UPNP_FRIENDLY_NAME = "friendlyName" ATTR_UPNP_MANUFACTURER = "manufacturer" ATTR_UPNP_MANUFACTURER_URL = "manufacturerURL" +ATTR_UPNP_MODEL_DESCRIPTION = "modelDescription" ATTR_UPNP_MODEL_NAME = "modelName" ATTR_UPNP_MODEL_NUMBER = "modelNumber" -ATTR_UPNP_PRESENTATION_URL = "presentationURL" +ATTR_UPNP_MODEL_URL = "modelURL" ATTR_UPNP_SERIAL = "serialNumber" ATTR_UPNP_UDN = "UDN" +ATTR_UPNP_UPC = "UPC" +ATTR_UPNP_PRESENTATION_URL = "presentationURL" _LOGGER = logging.getLogger(__name__) @@ -107,6 +113,9 @@ async def _process_entry(self, entry): """Process a single entry.""" info = {"st": entry.st} + for key in "usn", "ext", "server": + if key in entry.values: + info[key] = entry.values[key] if entry.location: @@ -165,5 +174,12 @@ def info_from_entry(entry, device_info): } if device_info: info.update(device_info) + info.pop("st", None) + if "usn" in info: + info[ATTR_SSDP_USN] = info.pop("usn") + if "ext" in info: + info[ATTR_SSDP_EXT] = info.pop("ext") + if "server" in info: + info[ATTR_SSDP_SERVER] = info.pop("server") return info diff --git a/homeassistant/components/starline/translations/et.json b/homeassistant/components/starline/translations/et.json new file mode 100644 index 00000000000000..7e3c4103ccf038 --- /dev/null +++ b/homeassistant/components/starline/translations/et.json @@ -0,0 +1,15 @@ +{ + "config": { + "step": { + "auth_mfa": { + "title": "Kaheastmeline autoriseerimine" + }, + "auth_user": { + "data": { + "password": "Salas\u00f5na", + "username": "Kasutajanimi" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/stream/const.py b/homeassistant/components/stream/const.py index 91c4018d899a2d..b986cddaf68e9d 100644 --- a/homeassistant/components/stream/const.py +++ b/homeassistant/components/stream/const.py @@ -19,3 +19,4 @@ MIN_SEGMENT_DURATION = 1.5 # Each segment is at least this many seconds PACKETS_TO_WAIT_FOR_AUDIO = 20 # Some streams have an audio stream with no audio +MAX_TIMESTAMP_GAP = 10000 # seconds - anything from 10 to 50000 is probably reasonable diff --git a/homeassistant/components/stream/core.py b/homeassistant/components/stream/core.py index 5e4e85ceea6ef4..20931abf11e1d0 100644 --- a/homeassistant/components/stream/core.py +++ b/homeassistant/components/stream/core.py @@ -84,7 +84,7 @@ def target_duration(self) -> int: """Return the max duration of any given segment in seconds.""" segment_length = len(self._segments) if not segment_length: - return 0 + return 1 durations = [s.duration for s in self._segments] return round(max(durations)) or 1 diff --git a/homeassistant/components/stream/fmp4utils.py b/homeassistant/components/stream/fmp4utils.py index 00603807215a60..dc929e531c1c66 100644 --- a/homeassistant/components/stream/fmp4utils.py +++ b/homeassistant/components/stream/fmp4utils.py @@ -36,3 +36,114 @@ def get_m4s(segment: io.BytesIO, sequence: int) -> bytes: mfra_location = next(find_box(segment, b"mfra")) segment.seek(moof_location) return segment.read(mfra_location - moof_location) + + +def get_codec_string(segment: io.BytesIO) -> str: + """Get RFC 6381 codec string.""" + codecs = [] + + # Find moov + moov_location = next(find_box(segment, b"moov")) + + # Find tracks + for trak_location in find_box(segment, b"trak", moov_location): + # Drill down to media info + mdia_location = next(find_box(segment, b"mdia", trak_location)) + minf_location = next(find_box(segment, b"minf", mdia_location)) + stbl_location = next(find_box(segment, b"stbl", minf_location)) + stsd_location = next(find_box(segment, b"stsd", stbl_location)) + + # Get stsd box + segment.seek(stsd_location) + stsd_length = int.from_bytes(segment.read(4), byteorder="big") + segment.seek(stsd_location) + stsd_box = segment.read(stsd_length) + + # Base Codec + codec = stsd_box[20:24].decode("utf-8") + + # Handle H264 + if ( + codec in ("avc1", "avc2", "avc3", "avc4") + and stsd_length > 110 + and stsd_box[106:110] == b"avcC" + ): + profile = stsd_box[111:112].hex() + compatibility = stsd_box[112:113].hex() + level = stsd_box[113:114].hex() + codec += "." + profile + compatibility + level + + # Handle H265 + elif ( + codec in ("hev1", "hvc1") + and stsd_length > 110 + and stsd_box[106:110] == b"hvcC" + ): + tmp_byte = int.from_bytes(stsd_box[111:112], byteorder="big") + + # Profile Space + codec += "." + profile_space_map = {0: "", 1: "A", 2: "B", 3: "C"} + profile_space = tmp_byte >> 6 + codec += profile_space_map[profile_space] + general_profile_idc = tmp_byte & 31 + codec += str(general_profile_idc) + + # Compatibility + codec += "." + general_profile_compatibility = int.from_bytes( + stsd_box[112:116], byteorder="big" + ) + reverse = 0 + for i in range(0, 32): + reverse |= general_profile_compatibility & 1 + if i == 31: + break + reverse <<= 1 + general_profile_compatibility >>= 1 + codec += hex(reverse)[2:] + + # Tier Flag + if (tmp_byte & 32) >> 5 == 0: + codec += ".L" + else: + codec += ".H" + codec += str(int.from_bytes(stsd_box[122:123], byteorder="big")) + + # Constraint String + has_byte = False + constraint_string = "" + for i in range(121, 115, -1): + gci = int.from_bytes(stsd_box[i : i + 1], byteorder="big") + if gci or has_byte: + constraint_string = "." + hex(gci)[2:] + constraint_string + has_byte = True + codec += constraint_string + + # Handle Audio + elif codec == "mp4a": + oti = None + dsi = None + + # Parse ES Descriptors + oti_loc = stsd_box.find(b"\x04\x80\x80\x80") + if oti_loc > 0: + oti = stsd_box[oti_loc + 5 : oti_loc + 6].hex() + codec += f".{oti}" + + dsi_loc = stsd_box.find(b"\x05\x80\x80\x80") + if dsi_loc > 0: + dsi_length = int.from_bytes( + stsd_box[dsi_loc + 4 : dsi_loc + 5], byteorder="big" + ) + dsi_data = stsd_box[dsi_loc + 5 : dsi_loc + 5 + dsi_length] + dsi0 = int.from_bytes(dsi_data[0:1], byteorder="big") + dsi = (dsi0 & 248) >> 3 + if dsi == 31 and len(dsi_data) >= 2: + dsi1 = int.from_bytes(dsi_data[1:2], byteorder="big") + dsi = 32 + ((dsi0 & 7) << 3) + ((dsi1 & 224) >> 5) + codec += f".{dsi}" + + codecs.append(codec) + + return ",".join(codecs) diff --git a/homeassistant/components/stream/hls.py b/homeassistant/components/stream/hls.py index 1e97ac222ec413..09729f79ada099 100644 --- a/homeassistant/components/stream/hls.py +++ b/homeassistant/components/stream/hls.py @@ -1,4 +1,5 @@ """Provide functionality to stream HLS.""" +import io from typing import Callable from aiohttp import web @@ -7,7 +8,7 @@ from .const import FORMAT_CONTENT_TYPE from .core import PROVIDERS, StreamOutput, StreamView -from .fmp4utils import get_init, get_m4s +from .fmp4utils import get_codec_string, get_init, get_m4s @callback @@ -16,7 +17,43 @@ def async_setup_hls(hass): hass.http.register_view(HlsPlaylistView()) hass.http.register_view(HlsSegmentView()) hass.http.register_view(HlsInitView()) - return "/api/hls/{}/playlist.m3u8" + hass.http.register_view(HlsMasterPlaylistView()) + return "/api/hls/{}/master_playlist.m3u8" + + +class HlsMasterPlaylistView(StreamView): + """Stream view used only for Chromecast compatibility.""" + + url = r"/api/hls/{token:[a-f0-9]+}/master_playlist.m3u8" + name = "api:stream:hls:master_playlist" + cors_allowed = True + + @staticmethod + def render(track): + """Render M3U8 file.""" + # Need to calculate max bandwidth as input_container.bit_rate doesn't seem to work + # Calculate file size / duration and use a multiplier to account for variation + segment = track.get_segment(track.segments[-1]) + bandwidth = round( + segment.segment.seek(0, io.SEEK_END) * 8 / segment.duration * 3 + ) + codecs = get_codec_string(segment.segment) + lines = [ + "#EXTM3U", + f'#EXT-X-STREAM-INF:BANDWIDTH={bandwidth},CODECS="{codecs}"', + "playlist.m3u8", + ] + return "\n".join(lines) + "\n" + + async def handle(self, request, stream, sequence): + """Return m3u8 playlist.""" + track = stream.add_provider("hls") + stream.start() + # Wait for a segment to be ready + if not track.segments: + await track.recv() + headers = {"Content-Type": FORMAT_CONTENT_TYPE["hls"]} + return web.Response(body=self.render(track).encode("utf-8"), headers=headers) class HlsPlaylistView(StreamView): @@ -26,18 +63,50 @@ class HlsPlaylistView(StreamView): name = "api:stream:hls:playlist" cors_allowed = True + @staticmethod + def render_preamble(track): + """Render preamble.""" + return [ + "#EXT-X-VERSION:7", + f"#EXT-X-TARGETDURATION:{track.target_duration}", + '#EXT-X-MAP:URI="init.mp4"', + ] + + @staticmethod + def render_playlist(track): + """Render playlist.""" + segments = track.segments + + if not segments: + return [] + + playlist = ["#EXT-X-MEDIA-SEQUENCE:{}".format(segments[0])] + + for sequence in segments: + segment = track.get_segment(sequence) + playlist.extend( + [ + "#EXTINF:{:.04f},".format(float(segment.duration)), + f"./segment/{segment.sequence}.m4s", + ] + ) + + return playlist + + def render(self, track): + """Render M3U8 file.""" + lines = ["#EXTM3U"] + self.render_preamble(track) + self.render_playlist(track) + return "\n".join(lines) + "\n" + async def handle(self, request, stream, sequence): """Return m3u8 playlist.""" - renderer = M3U8Renderer(stream) track = stream.add_provider("hls") stream.start() # Wait for a segment to be ready if not track.segments: await track.recv() headers = {"Content-Type": FORMAT_CONTENT_TYPE["hls"]} - return web.Response( - body=renderer.render(track).encode("utf-8"), headers=headers - ) + return web.Response(body=self.render(track).encode("utf-8"), headers=headers) class HlsInitView(StreamView): @@ -77,49 +146,6 @@ async def handle(self, request, stream, sequence): ) -class M3U8Renderer: - """M3U8 Render Helper.""" - - def __init__(self, stream): - """Initialize renderer.""" - self.stream = stream - - @staticmethod - def render_preamble(track): - """Render preamble.""" - return [ - "#EXT-X-VERSION:7", - f"#EXT-X-TARGETDURATION:{track.target_duration}", - '#EXT-X-MAP:URI="init.mp4"', - ] - - @staticmethod - def render_playlist(track): - """Render playlist.""" - segments = track.segments - - if not segments: - return [] - - playlist = ["#EXT-X-MEDIA-SEQUENCE:{}".format(segments[0])] - - for sequence in segments: - segment = track.get_segment(sequence) - playlist.extend( - [ - "#EXTINF:{:.04f},".format(float(segment.duration)), - f"./segment/{segment.sequence}.m4s", - ] - ) - - return playlist - - def render(self, track): - """Render M3U8 file.""" - lines = ["#EXTM3U"] + self.render_preamble(track) + self.render_playlist(track) - return "\n".join(lines) + "\n" - - @PROVIDERS.register("hls") class HlsStreamOutput(StreamOutput): """Represents HLS Output formats.""" @@ -137,7 +163,7 @@ def format(self) -> str: @property def audio_codecs(self) -> str: """Return desired audio codecs.""" - return {"aac", "ac3", "mp3"} + return {"aac", "mp3"} @property def video_codecs(self) -> tuple: @@ -148,7 +174,8 @@ def video_codecs(self) -> tuple: def container_options(self) -> Callable[[int], dict]: """Return Callable which takes a sequence number and returns container options.""" return lambda sequence: { - "movflags": "frag_custom+empty_moov+default_base_moof+skip_sidx+frag_discont", + # Removed skip_sidx - see https://github.com/home-assistant/core/pull/39970 + "movflags": "frag_custom+empty_moov+default_base_moof+frag_discont", "avoid_negative_ts": "make_non_negative", "fragment_index": str(sequence), } diff --git a/homeassistant/components/stream/recorder.py b/homeassistant/components/stream/recorder.py index 82b146cc51f546..d0b8789f60248e 100644 --- a/homeassistant/components/stream/recorder.py +++ b/homeassistant/components/stream/recorder.py @@ -78,7 +78,7 @@ def format(self) -> str: @property def audio_codecs(self) -> str: """Return desired audio codec.""" - return {"aac", "ac3", "mp3"} + return {"aac", "mp3"} @property def video_codecs(self) -> tuple: diff --git a/homeassistant/components/stream/worker.py b/homeassistant/components/stream/worker.py index b76896b815a44c..22f67432a1b0ff 100644 --- a/homeassistant/components/stream/worker.py +++ b/homeassistant/components/stream/worker.py @@ -6,7 +6,7 @@ import av -from .const import MIN_SEGMENT_DURATION, PACKETS_TO_WAIT_FOR_AUDIO +from .const import MAX_TIMESTAMP_GAP, MIN_SEGMENT_DURATION, PACKETS_TO_WAIT_FOR_AUDIO from .core import Segment, StreamBuffer _LOGGER = logging.getLogger(__name__) @@ -77,6 +77,9 @@ def _stream_worker_internal(hass, stream, quit_event): # compatible with empty_moov and manual bitstream filters not in PyAV if container.format.name in {"hls", "mpegts"}: audio_stream = None + # Some audio streams do not have a profile and throw errors when remuxing + if audio_stream and audio_stream.profile is None: + audio_stream = None # The presentation timestamps of the first packet in each stream we receive # Use to adjust before muxing or outputting, but we don't adjust internally @@ -113,7 +116,11 @@ def empty_stream_dict(): # Get to first video keyframe while first_packet[video_stream] is None: packet = next(container.demux()) - if packet.stream == video_stream and packet.is_keyframe: + if ( + packet.stream == video_stream + and packet.is_keyframe + and packet.dts is not None + ): first_packet[video_stream] = packet initial_packets.append(packet) # Get first_pts from subsequent frame to first keyframe @@ -121,6 +128,8 @@ def empty_stream_dict(): [pts is None for pts in {**first_packet, **first_pts}.values()] ) and (len(initial_packets) < PACKETS_TO_WAIT_FOR_AUDIO): packet = next(container.demux((video_stream, audio_stream))) + if packet.dts is None: + continue # Discard packets with no dts if ( first_packet[packet.stream] is None ): # actually video already found above so only for audio @@ -191,6 +200,12 @@ def mux_audio_packet(packet): packet.stream = output_streams[audio_stream] buffer.output.mux(packet) + def finalize_stream(): + if not stream.keepalive: + # End of stream, clear listeners and stop thread + for fmt, _ in outputs.items(): + hass.loop.call_soon_threadsafe(stream.outputs[fmt].put, None) + if not peek_first_pts(): container.close() return @@ -213,15 +228,26 @@ def mux_audio_packet(packet): continue last_packet_was_without_dts = False except (av.AVError, StopIteration) as ex: - if not stream.keepalive: - # End of stream, clear listeners and stop thread - for fmt, _ in outputs.items(): - hass.loop.call_soon_threadsafe(stream.outputs[fmt].put, None) _LOGGER.error("Error demuxing stream: %s", str(ex)) + finalize_stream() break # Discard packet if dts is not monotonic if packet.dts <= last_dts[packet.stream]: + if (last_dts[packet.stream] - packet.dts) > ( + packet.time_base * MAX_TIMESTAMP_GAP + ): + _LOGGER.warning( + "Timestamp overflow detected: dts = %s, resetting stream", + packet.dts, + ) + finalize_stream() + break + _LOGGER.warning( + "Dropping out of order packet: %s <= %s", + packet.dts, + last_dts[packet.stream], + ) continue # Check for end of segment diff --git a/homeassistant/components/sun/__init__.py b/homeassistant/components/sun/__init__.py index 5fb777bd325a2a..2d921da4a46816 100644 --- a/homeassistant/components/sun/__init__.py +++ b/homeassistant/components/sun/__init__.py @@ -41,7 +41,7 @@ # The algorithm used here is somewhat complicated. It aims to cut down # the number of sensor updates over the day. It's documented best in # the PR for the change, see the Discussion section of: -# https://github.com/home-assistant/home-assistant/pull/23832 +# https://github.com/home-assistant/core/pull/23832 # As documented in wikipedia: https://en.wikipedia.org/wiki/Twilight diff --git a/homeassistant/components/surepetcare/manifest.json b/homeassistant/components/surepetcare/manifest.json index 659a6091299dd0..2fbe4fe245f66e 100644 --- a/homeassistant/components/surepetcare/manifest.json +++ b/homeassistant/components/surepetcare/manifest.json @@ -3,5 +3,5 @@ "name": "Sure Petcare", "documentation": "https://www.home-assistant.io/integrations/surepetcare", "codeowners": ["@benleb"], - "requirements": ["surepy==0.2.5"] + "requirements": ["surepy==0.2.6"] } diff --git a/homeassistant/components/swiss_public_transport/sensor.py b/homeassistant/components/swiss_public_transport/sensor.py index 1562a4980804b1..2c7fb483eff72a 100644 --- a/homeassistant/components/swiss_public_transport/sensor.py +++ b/homeassistant/components/swiss_public_transport/sensor.py @@ -103,7 +103,7 @@ def device_state_attributes(self): self._opendata.connections[0]["departure"] ) - dt_util.as_local(dt_util.utcnow()) - attr = { + return { ATTR_TRAIN_NUMBER: self._opendata.connections[0]["number"], ATTR_PLATFORM: self._opendata.connections[0]["platform"], ATTR_TRANSFERS: self._opendata.connections[0]["transfers"], @@ -116,7 +116,6 @@ def device_state_attributes(self): ATTR_ATTRIBUTION: ATTRIBUTION, ATTR_DELAY: self._opendata.connections[0]["delay"], } - return attr @property def icon(self): diff --git a/homeassistant/components/switch/group.py b/homeassistant/components/switch/group.py new file mode 100644 index 00000000000000..1636054663dc69 --- /dev/null +++ b/homeassistant/components/switch/group.py @@ -0,0 +1,15 @@ +"""Describe group states.""" + + +from homeassistant.components.group import GroupIntegrationRegistry +from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.core import callback +from homeassistant.helpers.typing import HomeAssistantType + + +@callback +def async_describe_on_off_states( + hass: HomeAssistantType, registry: GroupIntegrationRegistry +) -> None: + """Describe group on off states.""" + registry.on_off_states({STATE_ON}, STATE_OFF) diff --git a/homeassistant/components/switch/light.py b/homeassistant/components/switch/light.py index 8b75187101427d..c9dd355392821e 100644 --- a/homeassistant/components/switch/light.py +++ b/homeassistant/components/switch/light.py @@ -44,18 +44,31 @@ async def async_setup_platform( discovery_info: Optional[DiscoveryInfoType] = None, ) -> None: """Initialize Light Switch platform.""" + + registry = await hass.helpers.entity_registry.async_get_registry() + wrapped_switch = registry.async_get(config[CONF_ENTITY_ID]) + unique_id = wrapped_switch.unique_id if wrapped_switch else None + async_add_entities( - [LightSwitch(cast(str, config.get(CONF_NAME)), config[CONF_ENTITY_ID])], True + [ + LightSwitch( + cast(str, config.get(CONF_NAME)), + config[CONF_ENTITY_ID], + unique_id, + ) + ], + True, ) class LightSwitch(LightEntity): """Represents a Switch as a Light.""" - def __init__(self, name: str, switch_entity_id: str) -> None: + def __init__(self, name: str, switch_entity_id: str, unique_id: str) -> None: """Initialize Light Switch.""" self._name = name self._switch_entity_id = switch_entity_id + self._unique_id = unique_id self._is_on = False self._available = False self._async_unsub_state_changed: Optional[CALLBACK_TYPE] = None @@ -80,6 +93,11 @@ def should_poll(self) -> bool: """No polling needed for a light switch.""" return False + @property + def unique_id(self): + """Return the unique id of the light switch.""" + return self._unique_id + async def async_turn_on(self, **kwargs): """Forward the turn_on command to the switch in this light switch.""" data = {ATTR_ENTITY_ID: self._switch_entity_id} diff --git a/homeassistant/components/switch/translations/et.json b/homeassistant/components/switch/translations/et.json index d992df0421f3ec..d68938ddda0f16 100644 --- a/homeassistant/components/switch/translations/et.json +++ b/homeassistant/components/switch/translations/et.json @@ -1,4 +1,19 @@ { + "device_automation": { + "action_type": { + "toggle": "Muuda {entity_name} olekut", + "turn_off": "L\u00fclita {entity_name} v\u00e4lja", + "turn_on": "L\u00fclita {entity_name} sisse" + }, + "condition_type": { + "is_off": "{entity_name} on v\u00e4lja l\u00fclitatud", + "is_on": "{entity_name} on sisse l\u00fclitatud" + }, + "trigger_type": { + "turned_off": "{entity_name} l\u00fclitus v\u00e4lja", + "turned_on": "{entity_name} l\u00fclitus sisse" + } + }, "state": { "_": { "off": "V\u00e4ljas", diff --git a/homeassistant/components/switch/translations/uk.json b/homeassistant/components/switch/translations/uk.json index 7ac96bd7039059..bee9eb957d530f 100644 --- a/homeassistant/components/switch/translations/uk.json +++ b/homeassistant/components/switch/translations/uk.json @@ -1,4 +1,10 @@ { + "device_automation": { + "trigger_type": { + "turned_off": "{entity_name} \u0432\u0438\u043c\u043a\u043d\u0435\u043d\u043e", + "turned_on": "{entity_name} \u0443\u0432\u0456\u043c\u043a\u043d\u0435\u043d\u043e" + } + }, "state": { "_": { "off": "\u0412\u0438\u043c\u043a\u043d\u0435\u043d\u043e", diff --git a/homeassistant/components/syncthru/strings.json b/homeassistant/components/syncthru/strings.json index 1824763c8f8f6c..0164fdf6ddc2fe 100644 --- a/homeassistant/components/syncthru/strings.json +++ b/homeassistant/components/syncthru/strings.json @@ -18,7 +18,7 @@ }, "user": { "data": { - "name": "Name", + "name": "[%key:common::config_flow::data::name%]", "url": "Web interface URL" } } diff --git a/homeassistant/components/syncthru/translations/de.json b/homeassistant/components/syncthru/translations/de.json index 76eaf5f21f3c45..8e568131e6203f 100644 --- a/homeassistant/components/syncthru/translations/de.json +++ b/homeassistant/components/syncthru/translations/de.json @@ -8,6 +8,7 @@ "syncthru_not_supported": "Ger\u00e4t unterst\u00fctzt kein SyncThru", "unknown_state": "Druckerstatus unbekannt, \u00fcberpr\u00fcfe URL und Netzwerkverbindung" }, + "flow_title": "Samsung SyncThru Drucker: {name}", "step": { "confirm": { "data": { diff --git a/homeassistant/components/syncthru/translations/hu.json b/homeassistant/components/syncthru/translations/hu.json new file mode 100644 index 00000000000000..3b2d79a34a77e2 --- /dev/null +++ b/homeassistant/components/syncthru/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/syncthru/translations/pl.json b/homeassistant/components/syncthru/translations/pl.json index bd174d000c8b2a..ac9a511ba3270a 100644 --- a/homeassistant/components/syncthru/translations/pl.json +++ b/homeassistant/components/syncthru/translations/pl.json @@ -1,10 +1,11 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" }, "error": { "invalid_url": "Nieprawid\u0142owy URL", + "syncthru_not_supported": "Urz\u0105dzenie nie obs\u0142uguje SyncThru", "unknown_state": "Nieznany stan drukarki, sprawd\u017a adres URL i \u0142\u0105czno\u015b\u0107 sieciow\u0105" }, "flow_title": "Drukarka Samsung SyncThru: {name}" diff --git a/homeassistant/components/synology/camera.py b/homeassistant/components/synology/camera.py index 5a619c821dce2e..4417f72918d0dc 100644 --- a/homeassistant/components/synology/camera.py +++ b/homeassistant/components/synology/camera.py @@ -42,6 +42,13 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up a Synology IP Camera.""" + _LOGGER.warning( + "The Synology integration is deprecated." + " Please use the Synology DSM integration" + " (https://www.home-assistant.io/integrations/synology_dsm/) instead." + " This integration will be removed in version 0.118.0." + ) + verify_ssl = config.get(CONF_VERIFY_SSL) timeout = config.get(CONF_TIMEOUT) diff --git a/homeassistant/components/synology_chat/notify.py b/homeassistant/components/synology_chat/notify.py index c8f665cb408c09..df43c5668f388a 100644 --- a/homeassistant/components/synology_chat/notify.py +++ b/homeassistant/components/synology_chat/notify.py @@ -10,7 +10,7 @@ PLATFORM_SCHEMA, BaseNotificationService, ) -from homeassistant.const import CONF_RESOURCE, CONF_VERIFY_SSL, HTTP_OK +from homeassistant.const import CONF_RESOURCE, CONF_VERIFY_SSL, HTTP_CREATED, HTTP_OK import homeassistant.helpers.config_validation as cv ATTR_FILE_URL = "file_url" @@ -57,7 +57,7 @@ def send_message(self, message="", **kwargs): self._resource, data=to_send, timeout=10, verify=self._verify_ssl ) - if response.status_code not in (HTTP_OK, 201): + if response.status_code not in (HTTP_OK, HTTP_CREATED): _LOGGER.exception( "Error sending message. Response %d: %s:", response.status_code, diff --git a/homeassistant/components/synology_dsm/__init__.py b/homeassistant/components/synology_dsm/__init__.py index 223235a41215a2..7f6cef46cbcedd 100644 --- a/homeassistant/components/synology_dsm/__init__.py +++ b/homeassistant/components/synology_dsm/__init__.py @@ -23,6 +23,7 @@ CONF_PORT, CONF_SCAN_INTERVAL, CONF_SSL, + CONF_TIMEOUT, CONF_USERNAME, ) from homeassistant.core import callback @@ -233,6 +234,7 @@ def __init__(self, hass: HomeAssistantType, entry: ConfigEntry): self._with_security = True self._with_storage = True self._with_utilisation = True + self._with_information = True self._with_surveillance_station = True self._unsub_dispatcher = None @@ -250,6 +252,7 @@ async def async_setup(self): self._entry.data[CONF_USERNAME], self._entry.data[CONF_PASSWORD], self._entry.data[CONF_SSL], + timeout=self._entry.options.get(CONF_TIMEOUT), device_token=self._entry.data.get("device_token"), ) @@ -302,11 +305,14 @@ def _async_setup_api_requests(self): self._with_utilisation = bool( self._fetching_entities.get(SynoCoreUtilization.API_KEY) ) + self._with_information = bool( + self._fetching_entities.get(SynoDSMInformation.API_KEY) + ) self._with_surveillance_station = bool( self._fetching_entities.get(SynoSurveillanceStation.CAMERA_API_KEY) ) - # Reset not used API + # Reset not used API, information is not reset since it's used in device_info if not self._with_security: self.dsm.reset(self.security) self.security = None @@ -349,7 +355,7 @@ async def async_unload(self): async def async_update(self, now=None): """Update function for updating API information.""" self._async_setup_api_requests() - await self._hass.async_add_executor_job(self.dsm.update) + await self._hass.async_add_executor_job(self.dsm.update, self._with_information) async_dispatcher_send(self._hass, self.signal_sensor_update) diff --git a/homeassistant/components/synology_dsm/binary_sensor.py b/homeassistant/components/synology_dsm/binary_sensor.py index a75f57db678f6a..6c11d31b7f7c9c 100644 --- a/homeassistant/components/synology_dsm/binary_sensor.py +++ b/homeassistant/components/synology_dsm/binary_sensor.py @@ -67,7 +67,4 @@ class SynoDSMStorageBinarySensor(SynologyDSMDeviceEntity, BinarySensorEntity): @property def is_on(self) -> bool: """Return the state.""" - attr = getattr(self._api.storage, self.entity_type)(self._device_id) - if attr is None: - return None - return attr + return getattr(self._api.storage, self.entity_type)(self._device_id) diff --git a/homeassistant/components/synology_dsm/camera.py b/homeassistant/components/synology_dsm/camera.py index 4cb34ee04315ad..80e6802e4435fd 100644 --- a/homeassistant/components/synology_dsm/camera.py +++ b/homeassistant/components/synology_dsm/camera.py @@ -60,9 +60,13 @@ def device_info(self) -> Dict[str, any]: """Return the device information.""" return { "identifiers": {(DOMAIN, self._api.information.serial, self._camera.id)}, - "name": self.name, + "name": self._camera.name, "model": self._camera.model, - "via_device": (DOMAIN, self._api.information.serial), + "via_device": ( + DOMAIN, + self._api.information.serial, + SynoSurveillanceStation.INFO_API_KEY, + ), } @property diff --git a/homeassistant/components/synology_dsm/config_flow.py b/homeassistant/components/synology_dsm/config_flow.py index a9df6f362cc9a7..35efb253ed1d73 100644 --- a/homeassistant/components/synology_dsm/config_flow.py +++ b/homeassistant/components/synology_dsm/config_flow.py @@ -23,6 +23,7 @@ CONF_PORT, CONF_SCAN_INTERVAL, CONF_SSL, + CONF_TIMEOUT, CONF_USERNAME, ) from homeassistant.core import callback @@ -34,6 +35,7 @@ DEFAULT_PORT_SSL, DEFAULT_SCAN_INTERVAL, DEFAULT_SSL, + DEFAULT_TIMEOUT, ) from .const import DOMAIN # pylint: disable=unused-import @@ -138,10 +140,10 @@ async def async_step_user(self, user_input=None): return await self.async_step_2sa(user_input, errors) except SynologyDSMLoginInvalidException as ex: _LOGGER.error(ex) - errors[CONF_USERNAME] = "login" + errors[CONF_USERNAME] = "invalid_auth" except SynologyDSMRequestException as ex: _LOGGER.error(ex) - errors[CONF_HOST] = "connection" + errors[CONF_HOST] = "cannot_connect" except SynologyDSMException as ex: _LOGGER.error(ex) errors["base"] = "unknown" @@ -250,7 +252,13 @@ async def async_step_init(self, user_input=None): default=self.config_entry.options.get( CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL ), - ): cv.positive_int + ): cv.positive_int, + vol.Optional( + CONF_TIMEOUT, + default=self.config_entry.options.get( + CONF_TIMEOUT, DEFAULT_TIMEOUT + ), + ): cv.positive_int, } ) return self.async_show_form(step_id="init", data_schema=data_schema) diff --git a/homeassistant/components/synology_dsm/const.py b/homeassistant/components/synology_dsm/const.py index 693d8b2cd50301..82bb232461e201 100644 --- a/homeassistant/components/synology_dsm/const.py +++ b/homeassistant/components/synology_dsm/const.py @@ -2,18 +2,22 @@ from synology_dsm.api.core.security import SynoCoreSecurity from synology_dsm.api.core.utilization import SynoCoreUtilization +from synology_dsm.api.dsm.information import SynoDSMInformation from synology_dsm.api.storage.storage import SynoStorage +from synology_dsm.api.surveillance_station import SynoSurveillanceStation from homeassistant.components.binary_sensor import DEVICE_CLASS_SAFETY from homeassistant.const import ( DATA_MEGABYTES, DATA_RATE_KILOBYTES_PER_SECOND, DATA_TERABYTES, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_TIMESTAMP, PERCENTAGE, ) DOMAIN = "synology_dsm" -PLATFORMS = ["binary_sensor", "camera", "sensor"] +PLATFORMS = ["binary_sensor", "camera", "sensor", "switch"] # Entry keys SYNO_API = "syno_api" @@ -27,6 +31,7 @@ DEFAULT_PORT_SSL = 5001 # Options DEFAULT_SCAN_INTERVAL = 15 # min +DEFAULT_TIMEOUT = 10 # sec ENTITY_NAME = "name" @@ -38,26 +43,26 @@ # Entity keys should start with the API_KEY to fetch # Binary sensors -STORAGE_DISK_BINARY_SENSORS = { - f"{SynoStorage.API_KEY}:disk_exceed_bad_sector_thr": { - ENTITY_NAME: "Exceeded Max Bad Sectors", +SECURITY_BINARY_SENSORS = { + f"{SynoCoreSecurity.API_KEY}:status": { + ENTITY_NAME: "Security status", ENTITY_UNIT: None, ENTITY_ICON: None, ENTITY_CLASS: DEVICE_CLASS_SAFETY, ENTITY_ENABLE: True, }, - f"{SynoStorage.API_KEY}:disk_below_remain_life_thr": { - ENTITY_NAME: "Below Min Remaining Life", +} + +STORAGE_DISK_BINARY_SENSORS = { + f"{SynoStorage.API_KEY}:disk_exceed_bad_sector_thr": { + ENTITY_NAME: "Exceeded Max Bad Sectors", ENTITY_UNIT: None, ENTITY_ICON: None, ENTITY_CLASS: DEVICE_CLASS_SAFETY, ENTITY_ENABLE: True, }, -} - -SECURITY_BINARY_SENSORS = { - f"{SynoCoreSecurity.API_KEY}:status": { - ENTITY_NAME: "Security status", + f"{SynoStorage.API_KEY}:disk_below_remain_life_thr": { + ENTITY_NAME: "Below Min Remaining Life", ENTITY_UNIT: None, ENTITY_ICON: None, ENTITY_CLASS: DEVICE_CLASS_SAFETY, @@ -212,15 +217,15 @@ f"{SynoStorage.API_KEY}:volume_disk_temp_avg": { ENTITY_NAME: "Average Disk Temp", ENTITY_UNIT: None, - ENTITY_ICON: "mdi:thermometer", - ENTITY_CLASS: "temperature", + ENTITY_ICON: None, + ENTITY_CLASS: DEVICE_CLASS_TEMPERATURE, ENTITY_ENABLE: True, }, f"{SynoStorage.API_KEY}:volume_disk_temp_max": { ENTITY_NAME: "Maximum Disk Temp", ENTITY_UNIT: None, - ENTITY_ICON: "mdi:thermometer", - ENTITY_CLASS: "temperature", + ENTITY_ICON: None, + ENTITY_CLASS: DEVICE_CLASS_TEMPERATURE, ENTITY_ENABLE: False, }, } @@ -242,11 +247,44 @@ f"{SynoStorage.API_KEY}:disk_temp": { ENTITY_NAME: "Temperature", ENTITY_UNIT: None, - ENTITY_ICON: "mdi:thermometer", - ENTITY_CLASS: "temperature", + ENTITY_ICON: None, + ENTITY_CLASS: DEVICE_CLASS_TEMPERATURE, + ENTITY_ENABLE: True, + }, +} + +INFORMATION_SENSORS = { + f"{SynoDSMInformation.API_KEY}:temperature": { + ENTITY_NAME: "temperature", + ENTITY_UNIT: None, + ENTITY_ICON: None, + ENTITY_CLASS: DEVICE_CLASS_TEMPERATURE, + ENTITY_ENABLE: True, + }, + f"{SynoDSMInformation.API_KEY}:uptime": { + ENTITY_NAME: "last boot", + ENTITY_UNIT: None, + ENTITY_ICON: None, + ENTITY_CLASS: DEVICE_CLASS_TIMESTAMP, + ENTITY_ENABLE: False, + }, +} + +# Switch +SURVEILLANCE_SWITCH = { + f"{SynoSurveillanceStation.HOME_MODE_API_KEY}:home_mode": { + ENTITY_NAME: "home mode", + ENTITY_UNIT: None, + ENTITY_ICON: "mdi:home-account", + ENTITY_CLASS: None, ENTITY_ENABLE: True, }, } -TEMP_SENSORS_KEYS = ["volume_disk_temp_avg", "volume_disk_temp_max", "disk_temp"] +TEMP_SENSORS_KEYS = [ + "volume_disk_temp_avg", + "volume_disk_temp_max", + "disk_temp", + "temperature", +] diff --git a/homeassistant/components/synology_dsm/sensor.py b/homeassistant/components/synology_dsm/sensor.py index 22171fdf2f59ef..31013451682570 100644 --- a/homeassistant/components/synology_dsm/sensor.py +++ b/homeassistant/components/synology_dsm/sensor.py @@ -1,4 +1,7 @@ """Support for Synology DSM sensors.""" +from datetime import timedelta +from typing import Dict + from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_DISKS, @@ -10,11 +13,13 @@ ) from homeassistant.helpers.temperature import display_temp from homeassistant.helpers.typing import HomeAssistantType +from homeassistant.util.dt import utcnow -from . import SynologyDSMDeviceEntity, SynologyDSMEntity +from . import SynoApi, SynologyDSMDeviceEntity, SynologyDSMEntity from .const import ( CONF_VOLUMES, DOMAIN, + INFORMATION_SENSORS, STORAGE_DISK_SENSORS, STORAGE_VOL_SENSORS, SYNO_API, @@ -55,6 +60,11 @@ async def async_setup_entry( for sensor_type in STORAGE_DISK_SENSORS ] + entities += [ + SynoDSMInfoSensor(api, sensor_type, INFORMATION_SENSORS[sensor_type]) + for sensor_type in INFORMATION_SENSORS + ] + async_add_entities(entities) @@ -105,3 +115,34 @@ def state(self): return display_temp(self.hass, attr, TEMP_CELSIUS, PRECISION_TENTHS) return attr + + +class SynoDSMInfoSensor(SynologyDSMEntity): + """Representation a Synology information sensor.""" + + def __init__(self, api: SynoApi, entity_type: str, entity_info: Dict[str, str]): + """Initialize the Synology SynoDSMInfoSensor entity.""" + super().__init__(api, entity_type, entity_info) + self._previous_uptime = None + self._last_boot = None + + @property + def state(self): + """Return the state.""" + attr = getattr(self._api.information, self.entity_type) + if attr is None: + return None + + # Temperature + if self.entity_type in TEMP_SENSORS_KEYS: + return display_temp(self.hass, attr, TEMP_CELSIUS, PRECISION_TENTHS) + + if self.entity_type == "uptime": + # reboot happened or entity creation + if self._previous_uptime is None or self._previous_uptime > attr: + last_boot = utcnow() - timedelta(seconds=attr) + self._last_boot = last_boot.replace(microsecond=0).isoformat() + + self._previous_uptime = attr + return self._last_boot + return attr diff --git a/homeassistant/components/synology_dsm/strings.json b/homeassistant/components/synology_dsm/strings.json index c46f645719f2ad..9ff0b16f8fb125 100644 --- a/homeassistant/components/synology_dsm/strings.json +++ b/homeassistant/components/synology_dsm/strings.json @@ -7,7 +7,7 @@ "data": { "host": "[%key:common::config_flow::data::host%]", "port": "[%key:common::config_flow::data::port%]", - "ssl": "Use SSL/TLS to connect to your NAS", + "ssl": "[%key:common::config_flow::data::ssl%]", "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]" } @@ -22,7 +22,7 @@ "title": "Synology DSM", "description": "Do you want to setup {name} ({host})?", "data": { - "ssl": "Use SSL/TLS to connect to your NAS", + "ssl": "[%key:common::config_flow::data::ssl%]", "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]", "port": "[%key:common::config_flow::data::port%]" @@ -30,21 +30,22 @@ } }, "error": { - "connection": "Connection error: please check your host, port & ssl", - "login": "Login error: please check your username & password", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", "missing_data": "Missing data: please retry later or an other configuration", "otp_failed": "Two-step authentication failed, retry with a new pass code", - "unknown": "Unknown error: please check logs to get more details" + "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { - "already_configured": "Host already configured" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } }, "options": { "step": { "init": { "data": { - "scan_interval": "Minutes between scans" + "scan_interval": "Minutes between scans", + "timeout": "Timeout (seconds)" } } } diff --git a/homeassistant/components/synology_dsm/switch.py b/homeassistant/components/synology_dsm/switch.py new file mode 100644 index 00000000000000..ee29c9f2692801 --- /dev/null +++ b/homeassistant/components/synology_dsm/switch.py @@ -0,0 +1,98 @@ +"""Support for Synology DSM switch.""" +from typing import Dict + +from synology_dsm.api.surveillance_station import SynoSurveillanceStation + +from homeassistant.components.switch import ToggleEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.helpers.typing import HomeAssistantType + +from . import SynoApi, SynologyDSMEntity +from .const import DOMAIN, SURVEILLANCE_SWITCH, SYNO_API + + +async def async_setup_entry( + hass: HomeAssistantType, entry: ConfigEntry, async_add_entities +) -> None: + """Set up the Synology NAS switch.""" + + api = hass.data[DOMAIN][entry.unique_id][SYNO_API] + + entities = [] + + if SynoSurveillanceStation.INFO_API_KEY in api.dsm.apis: + info = await hass.async_add_executor_job(api.dsm.surveillance_station.get_info) + version = info["data"]["CMSMinVersion"] + entities += [ + SynoDSMSurveillanceHomeModeToggle( + api, sensor_type, SURVEILLANCE_SWITCH[sensor_type], version + ) + for sensor_type in SURVEILLANCE_SWITCH + ] + + async_add_entities(entities, True) + + +class SynoDSMSurveillanceHomeModeToggle(SynologyDSMEntity, ToggleEntity): + """Representation a Synology Surveillance Station Home Mode toggle.""" + + def __init__( + self, api: SynoApi, entity_type: str, entity_info: Dict[str, str], version: str + ): + """Initialize a Synology Surveillance Station Home Mode.""" + super().__init__( + api, + entity_type, + entity_info, + ) + self._version = version + self._state = None + + @property + def is_on(self) -> bool: + """Return the state.""" + if self.entity_type == "home_mode": + return self._state + return None + + @property + def should_poll(self) -> bool: + """No polling needed.""" + return True + + async def async_update(self): + """Update the toggle state.""" + self._state = await self.hass.async_add_executor_job( + self._api.surveillance_station.get_home_mode_status + ) + + def turn_on(self, **kwargs) -> None: + """Turn on Home mode.""" + self._api.surveillance_station.set_home_mode(True) + + def turn_off(self, **kwargs) -> None: + """Turn off Home mode.""" + self._api.surveillance_station.set_home_mode(False) + + @property + def available(self) -> bool: + """Return True if entity is available.""" + return bool(self._api.surveillance_station) + + @property + def device_info(self) -> Dict[str, any]: + """Return the device information.""" + return { + "identifiers": { + ( + DOMAIN, + self._api.information.serial, + SynoSurveillanceStation.INFO_API_KEY, + ) + }, + "name": "Surveillance Station", + "manufacturer": "Synology", + "model": self._api.information.model, + "sw_version": self._version, + "via_device": (DOMAIN, self._api.information.serial), + } diff --git a/homeassistant/components/synology_dsm/translations/ca.json b/homeassistant/components/synology_dsm/translations/ca.json index fef0bca2ce4e64..a635d4dcd5f95f 100644 --- a/homeassistant/components/synology_dsm/translations/ca.json +++ b/homeassistant/components/synology_dsm/translations/ca.json @@ -22,7 +22,7 @@ "data": { "password": "Contrasenya", "port": "Port", - "ssl": "Utilitza SSL/TLS per connectar-te al servidor NAS", + "ssl": "Utilitza un certificat SSL", "username": "Nom d'usuari" }, "description": "Vols configurar {name} ({host})?", @@ -33,7 +33,7 @@ "host": "Amfitri\u00f3", "password": "Contrasenya", "port": "Port", - "ssl": "Utilitza SSL/TLS per connectar-te al servidor NAS", + "ssl": "Utilitza un certificat SSL", "username": "Nom d'usuari" }, "title": "Synology DSM" @@ -44,7 +44,8 @@ "step": { "init": { "data": { - "scan_interval": "Minuts entre escanejos" + "scan_interval": "Minuts entre escanejos", + "timeout": "Temps d'espera (segons)" } } } diff --git a/homeassistant/components/synology_dsm/translations/cs.json b/homeassistant/components/synology_dsm/translations/cs.json index 57dc028c0f4c37..d1f593c7938caf 100644 --- a/homeassistant/components/synology_dsm/translations/cs.json +++ b/homeassistant/components/synology_dsm/translations/cs.json @@ -17,5 +17,14 @@ } } } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "\u010casov\u00fd limit (v sekund\u00e1ch)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/synology_dsm/translations/el.json b/homeassistant/components/synology_dsm/translations/el.json new file mode 100644 index 00000000000000..e23b1fe0cf6363 --- /dev/null +++ b/homeassistant/components/synology_dsm/translations/el.json @@ -0,0 +1,11 @@ +{ + "options": { + "step": { + "init": { + "data": { + "timeout": "\u039b\u03ae\u03be\u03b7 \u03c7\u03c1\u03cc\u03bd\u03bf\u03c5 (\u03b4\u03b5\u03c5\u03c4\u03b5\u03c1\u03cc\u03bb\u03b5\u03c0\u03c4\u03b1)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/synology_dsm/translations/en.json b/homeassistant/components/synology_dsm/translations/en.json index 48a8118528a04a..1a2fc17b35163f 100644 --- a/homeassistant/components/synology_dsm/translations/en.json +++ b/homeassistant/components/synology_dsm/translations/en.json @@ -22,7 +22,7 @@ "data": { "password": "Password", "port": "Port", - "ssl": "Use SSL/TLS to connect to your NAS", + "ssl": "Uses an SSL certificate", "username": "Username" }, "description": "Do you want to setup {name} ({host})?", @@ -33,7 +33,7 @@ "host": "Host", "password": "Password", "port": "Port", - "ssl": "Use SSL/TLS to connect to your NAS", + "ssl": "Uses an SSL certificate", "username": "Username" }, "title": "Synology DSM" @@ -44,7 +44,8 @@ "step": { "init": { "data": { - "scan_interval": "Minutes between scans" + "scan_interval": "Minutes between scans", + "timeout": "Timeout (seconds)" } } } diff --git a/homeassistant/components/synology_dsm/translations/es.json b/homeassistant/components/synology_dsm/translations/es.json index a498333e049e7a..ca02cf41e23213 100644 --- a/homeassistant/components/synology_dsm/translations/es.json +++ b/homeassistant/components/synology_dsm/translations/es.json @@ -44,7 +44,8 @@ "step": { "init": { "data": { - "scan_interval": "Minutos entre escaneos" + "scan_interval": "Minutos entre escaneos", + "timeout": "Tiempo de espera (segundos)" } } } diff --git a/homeassistant/components/synology_dsm/translations/et.json b/homeassistant/components/synology_dsm/translations/et.json new file mode 100644 index 00000000000000..084f1e53b08edf --- /dev/null +++ b/homeassistant/components/synology_dsm/translations/et.json @@ -0,0 +1,11 @@ +{ + "options": { + "step": { + "init": { + "data": { + "timeout": "Ajal\u00f5pp (sekundites)" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/synology_dsm/translations/fr.json b/homeassistant/components/synology_dsm/translations/fr.json index 6c8b627a76bcb0..1c411591f1aeb5 100644 --- a/homeassistant/components/synology_dsm/translations/fr.json +++ b/homeassistant/components/synology_dsm/translations/fr.json @@ -39,5 +39,15 @@ "title": "Synology DSM" } } + }, + "options": { + "step": { + "init": { + "data": { + "scan_interval": "Minutes entre les scans", + "timeout": "D\u00e9lai d'expiration (secondes)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/synology_dsm/translations/it.json b/homeassistant/components/synology_dsm/translations/it.json index 1bb6dc026d8b5a..4ab4bc5aac48ed 100644 --- a/homeassistant/components/synology_dsm/translations/it.json +++ b/homeassistant/components/synology_dsm/translations/it.json @@ -22,7 +22,7 @@ "data": { "password": "Password", "port": "Porta", - "ssl": "Utilizzare SSL/TLS per connettersi al NAS", + "ssl": "Utilizza un certificato SSL", "username": "Nome utente" }, "description": "Vuoi impostare {name} ({host})?", @@ -33,7 +33,7 @@ "host": "Host", "password": "Password", "port": "Porta", - "ssl": "Utilizzare SSL/TLS per connettersi al NAS", + "ssl": "Utilizza un certificato SSL", "username": "Nome utente" }, "title": "Synology DSM" @@ -44,7 +44,8 @@ "step": { "init": { "data": { - "scan_interval": "Minuti tra una scansione e l'altra" + "scan_interval": "Minuti tra una scansione e l'altra", + "timeout": "Timeout (in secondi)" } } } diff --git a/homeassistant/components/synology_dsm/translations/ko.json b/homeassistant/components/synology_dsm/translations/ko.json index 81b7d5f143596a..6c0dc98b4ae1f0 100644 --- a/homeassistant/components/synology_dsm/translations/ko.json +++ b/homeassistant/components/synology_dsm/translations/ko.json @@ -44,7 +44,8 @@ "step": { "init": { "data": { - "scan_interval": "\uc2a4\uce94 \uac04\uaca9(\ubd84)" + "scan_interval": "\uc2a4\uce94 \uac04\uaca9(\ubd84)", + "timeout": "\uc81c\ud55c \uc2dc\uac04 (\ucd08)" } } } diff --git a/homeassistant/components/synology_dsm/translations/lb.json b/homeassistant/components/synology_dsm/translations/lb.json index 9ca8d0cdfa55d3..63e1be3fa2d019 100644 --- a/homeassistant/components/synology_dsm/translations/lb.json +++ b/homeassistant/components/synology_dsm/translations/lb.json @@ -44,7 +44,8 @@ "step": { "init": { "data": { - "scan_interval": "Minutte t\u00ebscht Scannen" + "scan_interval": "Minutte t\u00ebscht Scannen", + "timeout": "Z\u00e4itiwwerscheidung (sekonnen)" } } } diff --git a/homeassistant/components/synology_dsm/translations/nl.json b/homeassistant/components/synology_dsm/translations/nl.json index 5798dce567d319..ee7c89f7192855 100644 --- a/homeassistant/components/synology_dsm/translations/nl.json +++ b/homeassistant/components/synology_dsm/translations/nl.json @@ -39,5 +39,14 @@ "title": "Synology DSM" } } + }, + "options": { + "step": { + "init": { + "data": { + "timeout": "Time-out (seconden)" + } + } + } } } \ No newline at end of file diff --git a/homeassistant/components/synology_dsm/translations/no.json b/homeassistant/components/synology_dsm/translations/no.json index f8d7add4dc2b55..38eb87793aeec8 100644 --- a/homeassistant/components/synology_dsm/translations/no.json +++ b/homeassistant/components/synology_dsm/translations/no.json @@ -22,7 +22,7 @@ "data": { "password": "Passord", "port": "", - "ssl": "Bruk SSL/TLS til \u00e5 koble til NAS-en", + "ssl": "Bruker et SSL-sertifikat", "username": "Brukernavn" }, "description": "Vil du konfigurere {name} ({host})?", @@ -33,7 +33,7 @@ "host": "Vert", "password": "Passord", "port": "", - "ssl": "Bruk SSL/TLS til \u00e5 koble til NAS-en", + "ssl": "Bruker et SSL-sertifikat", "username": "Brukernavn" }, "title": "" @@ -44,7 +44,8 @@ "step": { "init": { "data": { - "scan_interval": "Minutter mellom skanninger" + "scan_interval": "Minutter mellom skanninger", + "timeout": "Tidsavbrudd (sekunder)" } } } diff --git a/homeassistant/components/synology_dsm/translations/pl.json b/homeassistant/components/synology_dsm/translations/pl.json index 60c7ee849f1015..6c39999022d6cc 100644 --- a/homeassistant/components/synology_dsm/translations/pl.json +++ b/homeassistant/components/synology_dsm/translations/pl.json @@ -44,7 +44,8 @@ "step": { "init": { "data": { - "scan_interval": "Cz\u0119stotliwo\u015b\u0107 aktualizacji [min]" + "scan_interval": "Cz\u0119stotliwo\u015b\u0107 aktualizacji [min]", + "timeout": "Limit czasu (sekundy)" } } } diff --git a/homeassistant/components/synology_dsm/translations/ru.json b/homeassistant/components/synology_dsm/translations/ru.json index c0e5da7a1f1f4a..d1d65a79b68bae 100644 --- a/homeassistant/components/synology_dsm/translations/ru.json +++ b/homeassistant/components/synology_dsm/translations/ru.json @@ -22,7 +22,7 @@ "data": { "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", - "ssl": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c SSL / TLS \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f", + "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": "\u041b\u043e\u0433\u0438\u043d" }, "description": "\u0425\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c {name} ({host})?", @@ -33,7 +33,7 @@ "host": "\u0425\u043e\u0441\u0442", "password": "\u041f\u0430\u0440\u043e\u043b\u044c", "port": "\u041f\u043e\u0440\u0442", - "ssl": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c SSL / TLS \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f", + "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": "\u041b\u043e\u0433\u0438\u043d" }, "title": "Synology DSM" @@ -44,7 +44,8 @@ "step": { "init": { "data": { - "scan_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043c\u0435\u0436\u0434\u0443 \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f\u043c\u0438 (\u043c\u0438\u043d.)" + "scan_interval": "\u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043c\u0435\u0436\u0434\u0443 \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f\u043c\u0438 (\u043c\u0438\u043d.)", + "timeout": "\u0422\u0430\u0439\u043c-\u0430\u0443\u0442 (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)" } } } diff --git a/homeassistant/components/synology_dsm/translations/sv.json b/homeassistant/components/synology_dsm/translations/sv.json index 6aaee8b44aaa8b..690e622ecd2ebe 100644 --- a/homeassistant/components/synology_dsm/translations/sv.json +++ b/homeassistant/components/synology_dsm/translations/sv.json @@ -29,7 +29,8 @@ "step": { "init": { "data": { - "scan_interval": "Minuter mellan skanningar" + "scan_interval": "Minuter mellan skanningar", + "timeout": "Timeout (sekunder)" } } } diff --git a/homeassistant/components/synology_dsm/translations/zh-Hant.json b/homeassistant/components/synology_dsm/translations/zh-Hant.json index eec37c812f8981..57f79054b30ed1 100644 --- a/homeassistant/components/synology_dsm/translations/zh-Hant.json +++ b/homeassistant/components/synology_dsm/translations/zh-Hant.json @@ -22,7 +22,7 @@ "data": { "password": "\u5bc6\u78bc", "port": "\u901a\u8a0a\u57e0", - "ssl": "\u4f7f\u7528 SSL/TLS \u9023\u7dda\u81f3 NAS", + "ssl": "\u4f7f\u7528 SSL \u8a8d\u8b49", "username": "\u4f7f\u7528\u8005\u540d\u7a31" }, "description": "\u662f\u5426\u8981\u8a2d\u5b9a {name} ({host})\uff1f", @@ -33,7 +33,7 @@ "host": "\u4e3b\u6a5f\u7aef", "password": "\u5bc6\u78bc", "port": "\u901a\u8a0a\u57e0", - "ssl": "\u4f7f\u7528 SSL/TLS \u9023\u7dda\u81f3 NAS", + "ssl": "\u4f7f\u7528 SSL \u8a8d\u8b49", "username": "\u4f7f\u7528\u8005\u540d\u7a31" }, "title": "\u7fa4\u6689 DSM" @@ -44,7 +44,8 @@ "step": { "init": { "data": { - "scan_interval": "\u6383\u63cf\u9593\u9694\u5206\u6578" + "scan_interval": "\u6383\u63cf\u9593\u9694\u5206\u6578", + "timeout": "\u903e\u6642\uff08\u79d2\uff09" } } } diff --git a/homeassistant/components/system_log/__init__.py b/homeassistant/components/system_log/__init__.py index b8d6b1664ac7ef..bb255ba8bf38ef 100644 --- a/homeassistant/components/system_log/__init__.py +++ b/homeassistant/components/system_log/__init__.py @@ -211,6 +211,8 @@ async def async_setup(hass, config): handler = LogErrorHandler(hass, conf[CONF_MAX_ENTRIES], conf[CONF_FIRE_EVENT]) + hass.data[DOMAIN] = handler + listener = logging.handlers.QueueListener( simple_queue, handler, respect_handler_level=True ) @@ -222,6 +224,7 @@ def _async_stop_queue_handler(_) -> None: """Cleanup handler.""" logging.root.removeHandler(queue_handler) listener.stop() + del hass.data[DOMAIN] hass.bus.async_listen_once(EVENT_HOMEASSISTANT_CLOSE, _async_stop_queue_handler) diff --git a/homeassistant/components/systemmonitor/sensor.py b/homeassistant/components/systemmonitor/sensor.py index e7ad77e1842c24..e29081257e056e 100644 --- a/homeassistant/components/systemmonitor/sensor.py +++ b/homeassistant/components/systemmonitor/sensor.py @@ -87,7 +87,7 @@ None, False, ], - "swap_free": ["Swap free", DATA_MEBIBYTES, "mdi:harddisk", None, True], + "swap_free": ["Swap free", DATA_MEBIBYTES, "mdi:harddisk", None, False], "swap_use": ["Swap use", DATA_MEBIBYTES, "mdi:harddisk", None, False], "swap_use_percent": ["Swap use (percent)", PERCENTAGE, "mdi:harddisk", None, False], } diff --git a/homeassistant/components/tado/strings.json b/homeassistant/components/tado/strings.json index f0f2ce4ab99db1..e0eb90f7ddc95f 100644 --- a/homeassistant/components/tado/strings.json +++ b/homeassistant/components/tado/strings.json @@ -1,7 +1,7 @@ { "config": { "abort": { - "already_configured": "Device is already configured" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" }, "step": { "user": { @@ -13,10 +13,10 @@ } }, "error": { - "unknown": "Unexpected error", + "unknown": "[%key:common::config_flow::error::unknown%]", "no_homes": "There are no homes linked to this tado account.", - "invalid_auth": "Invalid authentication", - "cannot_connect": "Failed to connect, please try again" + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" } }, "options": { @@ -30,4 +30,4 @@ } } } -} \ No newline at end of file +} diff --git a/homeassistant/components/tado/translations/ca.json b/homeassistant/components/tado/translations/ca.json index df340784f7fc19..935b58483d7344 100644 --- a/homeassistant/components/tado/translations/ca.json +++ b/homeassistant/components/tado/translations/ca.json @@ -4,7 +4,7 @@ "already_configured": "El dispositiu ja est\u00e0 configurat" }, "error": { - "cannot_connect": "No s'ha pogut connectar, torna-ho a provar", + "cannot_connect": "Ha fallat la connexi\u00f3", "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "no_homes": "No hi ha cases enlla\u00e7ades a aquest compte Tado.", "unknown": "Error inesperat" diff --git a/homeassistant/components/tado/translations/en.json b/homeassistant/components/tado/translations/en.json index d6f28c43cb215d..4e0df459437cca 100644 --- a/homeassistant/components/tado/translations/en.json +++ b/homeassistant/components/tado/translations/en.json @@ -4,7 +4,7 @@ "already_configured": "Device is already configured" }, "error": { - "cannot_connect": "Failed to connect, please try again", + "cannot_connect": "Failed to connect", "invalid_auth": "Invalid authentication", "no_homes": "There are no homes linked to this tado account.", "unknown": "Unexpected error" diff --git a/homeassistant/components/tado/translations/et.json b/homeassistant/components/tado/translations/et.json new file mode 100644 index 00000000000000..0d70cd06fcaa70 --- /dev/null +++ b/homeassistant/components/tado/translations/et.json @@ -0,0 +1,8 @@ +{ + "config": { + "error": { + "cannot_connect": "\u00dchenduse loomine nurjus. Proovi uuesti", + "unknown": "Ootamatu t\u00f5rge" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tado/translations/fr.json b/homeassistant/components/tado/translations/fr.json index 18196a4bf13f47..0ebbe4054a183c 100644 --- a/homeassistant/components/tado/translations/fr.json +++ b/homeassistant/components/tado/translations/fr.json @@ -25,6 +25,7 @@ "data": { "fallback": "Activer le mode restreint." }, + "description": "Le mode de repli passera au programme intelligent au prochain changement de programme apr\u00e8s avoir ajust\u00e9 manuellement une zone.", "title": "Ajustez les options de Tado." } } diff --git a/homeassistant/components/tado/translations/it.json b/homeassistant/components/tado/translations/it.json index 775ef702b84971..2c09ca743127d7 100644 --- a/homeassistant/components/tado/translations/it.json +++ b/homeassistant/components/tado/translations/it.json @@ -4,7 +4,7 @@ "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" }, "error": { - "cannot_connect": "Impossibile connettersi, si prega di riprovare", + "cannot_connect": "Impossibile connettersi", "invalid_auth": "Autenticazione non valida", "no_homes": "Non ci sono case collegate a questo account tado.", "unknown": "Errore imprevisto" diff --git a/homeassistant/components/tado/translations/no.json b/homeassistant/components/tado/translations/no.json index 594084c6571a6f..0ac5518709cfff 100644 --- a/homeassistant/components/tado/translations/no.json +++ b/homeassistant/components/tado/translations/no.json @@ -4,7 +4,7 @@ "already_configured": "Enheten er allerede konfigurert" }, "error": { - "cannot_connect": "Klarte ikke \u00e5 koble til, vennligst pr\u00f8v igjen", + "cannot_connect": "Tilkobling mislyktes.", "invalid_auth": "Ugyldig godkjenning", "no_homes": "Det er ingen hjem knyttet til denne tado-kontoen.", "unknown": "Uventet feil" diff --git a/homeassistant/components/tado/translations/pl.json b/homeassistant/components/tado/translations/pl.json index f95374e0329621..46c78ce438ed68 100644 --- a/homeassistant/components/tado/translations/pl.json +++ b/homeassistant/components/tado/translations/pl.json @@ -1,13 +1,13 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia, spr\u00f3buj ponownie.", - "invalid_auth": "Niepoprawne uwierzytelnienie.", + "invalid_auth": "Niepoprawne uwierzytelnienie", "no_homes": "Brak dom\u00f3w powi\u0105zanych z tym kontem Tado.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "user": { diff --git a/homeassistant/components/tado/translations/ru.json b/homeassistant/components/tado/translations/ru.json index 04d1a0d9545797..8ffb14edc0efc2 100644 --- a/homeassistant/components/tado/translations/ru.json +++ b/homeassistant/components/tado/translations/ru.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", + "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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "no_homes": "\u041d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e \u0434\u043e\u043c\u043e\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." diff --git a/homeassistant/components/tado/translations/zh-Hant.json b/homeassistant/components/tado/translations/zh-Hant.json index 911520065fce9f..59e2d80c561f4b 100644 --- a/homeassistant/components/tado/translations/zh-Hant.json +++ b/homeassistant/components/tado/translations/zh-Hant.json @@ -4,7 +4,7 @@ "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557\uff0c\u8acb\u518d\u8a66\u4e00\u6b21", + "cannot_connect": "\u9023\u7dda\u5931\u6557", "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "no_homes": "\u6b64 Tado \u5e33\u865f\u672a\u7d81\u5b9a\u4efb\u4f55\u5bb6\u5ead\u3002", "unknown": "\u672a\u9810\u671f\u932f\u8aa4" diff --git a/homeassistant/components/tag/__init__.py b/homeassistant/components/tag/__init__.py index 872d097d5dea22..321dce9a296b38 100644 --- a/homeassistant/components/tag/__init__.py +++ b/homeassistant/components/tag/__init__.py @@ -70,7 +70,7 @@ async def _process_create_data(self, data: typing.Dict) -> typing.Dict: data[TAG_ID] = str(uuid.uuid4()) # make last_scanned JSON serializeable if LAST_SCANNED in data: - data[LAST_SCANNED] = str(data[LAST_SCANNED]) + data[LAST_SCANNED] = data[LAST_SCANNED].isoformat() return data @callback @@ -82,8 +82,8 @@ async def _update_data(self, data: dict, update_data: typing.Dict) -> typing.Dic """Return a new updated data object.""" data = {**data, **self.UPDATE_SCHEMA(update_data)} # make last_scanned JSON serializeable - if LAST_SCANNED in data: - data[LAST_SCANNED] = str(data[LAST_SCANNED]) + if LAST_SCANNED in update_data: + data[LAST_SCANNED] = data[LAST_SCANNED].isoformat() return data @@ -100,6 +100,7 @@ async def async_setup(hass: HomeAssistant, config: dict): collection.StorageCollectionWebsocket( storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS ).async_setup(hass) + return True diff --git a/homeassistant/components/tag/manifest.json b/homeassistant/components/tag/manifest.json index d330fdaf3f8f84..f2d3a4133b89e4 100644 --- a/homeassistant/components/tag/manifest.json +++ b/homeassistant/components/tag/manifest.json @@ -1,12 +1,7 @@ { "domain": "tag", - "name": "Tag", - "config_flow": false, + "name": "Tags", "documentation": "https://www.home-assistant.io/integrations/tag", - "requirements": [], - "ssdp": [], - "zeroconf": [], - "homekit": {}, - "dependencies": [], - "codeowners": ["@balloob", "@dmulcahey"] + "codeowners": ["@balloob", "@dmulcahey"], + "quality_scale": "internal" } diff --git a/homeassistant/components/tag/translations/ko.json b/homeassistant/components/tag/translations/ko.json new file mode 100644 index 00000000000000..8cee64dc465573 --- /dev/null +++ b/homeassistant/components/tag/translations/ko.json @@ -0,0 +1,3 @@ +{ + "title": "\ud0dc\uadf8" +} \ No newline at end of file diff --git a/homeassistant/components/tag/translations/pl.json b/homeassistant/components/tag/translations/pl.json new file mode 100644 index 00000000000000..fdac700612daf4 --- /dev/null +++ b/homeassistant/components/tag/translations/pl.json @@ -0,0 +1,3 @@ +{ + "title": "Tag" +} \ No newline at end of file diff --git a/homeassistant/components/tahoma/binary_sensor.py b/homeassistant/components/tahoma/binary_sensor.py index 39e492601bd83e..af06bf5ca4c77f 100644 --- a/homeassistant/components/tahoma/binary_sensor.py +++ b/homeassistant/components/tahoma/binary_sensor.py @@ -2,7 +2,10 @@ from datetime import timedelta import logging -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_SMOKE, + BinarySensorEntity, +) from homeassistant.const import ATTR_BATTERY_LEVEL, STATE_OFF, STATE_ON from . import DOMAIN as TAHOMA_DOMAIN, TahomaDevice @@ -45,7 +48,7 @@ def is_on(self): def device_class(self): """Return the class of the device.""" if self.tahoma_device.type == "rtds:RTDSSmokeSensor": - return "smoke" + return DEVICE_CLASS_SMOKE return None @property diff --git a/homeassistant/components/tahoma/sensor.py b/homeassistant/components/tahoma/sensor.py index 7b28989ad8e96e..fb1129cfa0edc1 100644 --- a/homeassistant/components/tahoma/sensor.py +++ b/homeassistant/components/tahoma/sensor.py @@ -2,7 +2,7 @@ from datetime import timedelta import logging -from homeassistant.const import ATTR_BATTERY_LEVEL, PERCENTAGE, TEMP_CELSIUS +from homeassistant.const import ATTR_BATTERY_LEVEL, LIGHT_LUX, PERCENTAGE, TEMP_CELSIUS from homeassistant.helpers.entity import Entity from . import DOMAIN as TAHOMA_DOMAIN, TahomaDevice @@ -49,7 +49,7 @@ def unit_of_measurement(self): if self.tahoma_device.type == "io:SomfyBasicContactIOSystemSensor": return None if self.tahoma_device.type == "io:LightIOSystemSensor": - return "lx" + return LIGHT_LUX if self.tahoma_device.type == "Humidity Sensor": return PERCENTAGE if self.tahoma_device.type == "rtds:RTDSContactSensor": diff --git a/homeassistant/components/tankerkoenig/sensor.py b/homeassistant/components/tankerkoenig/sensor.py index b0dd3368ad3a89..6985072b065e30 100644 --- a/homeassistant/components/tankerkoenig/sensor.py +++ b/homeassistant/components/tankerkoenig/sensor.py @@ -2,7 +2,12 @@ import logging -from homeassistant.const import ATTR_ATTRIBUTION, ATTR_LATITUDE, ATTR_LONGITUDE +from homeassistant.const import ( + ATTR_ATTRIBUTION, + ATTR_LATITUDE, + ATTR_LONGITUDE, + CURRENCY_EURO, +) from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, DataUpdateCoordinator, @@ -106,7 +111,7 @@ def icon(self): @property def unit_of_measurement(self): """Return unit of measurement.""" - return "€" + return CURRENCY_EURO @property def state(self): diff --git a/homeassistant/components/tasmota/__init__.py b/homeassistant/components/tasmota/__init__.py new file mode 100644 index 00000000000000..1e11aff448e7f1 --- /dev/null +++ b/homeassistant/components/tasmota/__init__.py @@ -0,0 +1,104 @@ +"""The Tasmota integration.""" +import logging + +from hatasmota.const import ( + CONF_MAC, + CONF_MANUFACTURER, + CONF_MODEL, + CONF_NAME, + CONF_SW_VERSION, +) +from hatasmota.discovery import clear_discovery_topic +from hatasmota.mqtt import TasmotaMQTTClient + +from homeassistant.components import mqtt +from homeassistant.components.mqtt.subscription import ( + async_subscribe_topics, + async_unsubscribe_topics, +) +from homeassistant.core import callback +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.typing import HomeAssistantType + +from . import discovery +from .const import CONF_DISCOVERY_PREFIX +from .discovery import TASMOTA_DISCOVERY_DEVICE + +_LOGGER = logging.getLogger(__name__) + +DEVICE_MACS = "tasmota_devices" + + +async def async_setup(hass: HomeAssistantType, config: dict): + """Set up the Tasmota component.""" + return True + + +async def async_setup_entry(hass, entry): + """Set up Tasmota from a config entry.""" + hass.data[DEVICE_MACS] = {} + + def _publish(*args, **kwds): + mqtt.async_publish(hass, *args, **kwds) + + async def _subscribe_topics(sub_state, topics): + # Optionally mark message handlers as callback + for topic in topics.values(): + if "msg_callback" in topic and "event_loop_safe" in topic: + topic["msg_callback"] = callback(topic["msg_callback"]) + return await async_subscribe_topics(hass, sub_state, topics) + + async def _unsubscribe_topics(sub_state): + return await async_unsubscribe_topics(hass, sub_state) + + tasmota_mqtt = TasmotaMQTTClient(_publish, _subscribe_topics, _unsubscribe_topics) + + discovery_prefix = entry.data[CONF_DISCOVERY_PREFIX] + await discovery.async_start(hass, discovery_prefix, entry, tasmota_mqtt) + + async def async_discover_device(config, mac): + """Discover and add a Tasmota device.""" + await async_setup_device(hass, mac, config, entry, tasmota_mqtt) + + async_dispatcher_connect(hass, TASMOTA_DISCOVERY_DEVICE, async_discover_device) + + return True + + +async def _remove_device(hass, config_entry, mac, tasmota_mqtt): + """Remove device from device registry.""" + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get_device(set(), {(CONNECTION_NETWORK_MAC, mac)}) + + if device is None: + return + + _LOGGER.debug("Removing tasmota device %s", mac) + device_registry.async_remove_device(device.id) + clear_discovery_topic(mac, config_entry.data[CONF_DISCOVERY_PREFIX], tasmota_mqtt) + + +async def _update_device(hass, config_entry, config): + """Add or update device registry.""" + device_registry = await hass.helpers.device_registry.async_get_registry() + config_entry_id = config_entry.entry_id + device_info = { + "connections": {(CONNECTION_NETWORK_MAC, config[CONF_MAC])}, + "manufacturer": config[CONF_MANUFACTURER], + "model": config[CONF_MODEL], + "name": config[CONF_NAME], + "sw_version": config[CONF_SW_VERSION], + "config_entry_id": config_entry_id, + } + _LOGGER.debug("Adding or updating tasmota device %s", config[CONF_MAC]) + device = device_registry.async_get_or_create(**device_info) + hass.data[DEVICE_MACS][device.id] = config[CONF_MAC] + + +async def async_setup_device(hass, mac, config, config_entry, tasmota_mqtt): + """Set up the Tasmota device.""" + if not config: + await _remove_device(hass, config_entry, mac, tasmota_mqtt) + else: + await _update_device(hass, config_entry, config) diff --git a/homeassistant/components/tasmota/config_flow.py b/homeassistant/components/tasmota/config_flow.py new file mode 100644 index 00000000000000..fbac4bd7dd2e80 --- /dev/null +++ b/homeassistant/components/tasmota/config_flow.py @@ -0,0 +1,56 @@ +"""Config flow for Tasmota.""" +import logging + +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.components.mqtt import valid_subscribe_topic + +from .const import ( # pylint:disable=unused-import + CONF_DISCOVERY_PREFIX, + DEFAULT_PREFIX, + DOMAIN, +) + +_LOGGER = logging.getLogger(__name__) + + +class FlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH + + async def async_step_user(self, user_input=None): + """Handle a flow initialized by the user.""" + if self._async_current_entries(): + return self.async_abort(reason="single_instance_allowed") + + return await self.async_step_config() + + async def async_step_config(self, user_input=None): + """Confirm the setup.""" + errors = {} + data = {CONF_DISCOVERY_PREFIX: DEFAULT_PREFIX} + + if user_input is not None: + bad_prefix = False + if self.show_advanced_options: + prefix = user_input[CONF_DISCOVERY_PREFIX] + try: + valid_subscribe_topic(f"{prefix}/#") + except vol.Invalid: + errors["base"] = "invalid_discovery_topic" + bad_prefix = True + else: + data = user_input + if not bad_prefix: + return self.async_create_entry(title="Tasmota", data=data) + + fields = {} + if self.show_advanced_options: + fields[vol.Optional(CONF_DISCOVERY_PREFIX, default=DEFAULT_PREFIX)] = str + + return self.async_show_form( + step_id="config", data_schema=vol.Schema(fields), errors=errors + ) diff --git a/homeassistant/components/tasmota/const.py b/homeassistant/components/tasmota/const.py new file mode 100644 index 00000000000000..62504b4a86f2f1 --- /dev/null +++ b/homeassistant/components/tasmota/const.py @@ -0,0 +1,6 @@ +"""Constants used by multiple Tasmota modules.""" +CONF_DISCOVERY_PREFIX = "discovery_prefix" + +DEFAULT_PREFIX = "tasmota/discovery" + +DOMAIN = "tasmota" diff --git a/homeassistant/components/tasmota/discovery.py b/homeassistant/components/tasmota/discovery.py new file mode 100644 index 00000000000000..e6bde93bee8b10 --- /dev/null +++ b/homeassistant/components/tasmota/discovery.py @@ -0,0 +1,123 @@ +"""Support for MQTT discovery.""" +import asyncio +import logging + +from hatasmota.discovery import ( + TasmotaDiscovery, + get_device_config as tasmota_get_device_config, + get_entities_for_platform as tasmota_get_entities_for_platform, + get_entity as tasmota_get_entity, + has_entities_with_platform as tasmota_has_entities_with_platform, + unique_id_from_hash, +) + +from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.helpers.typing import HomeAssistantType + +from .const import DOMAIN + +_LOGGER = logging.getLogger(__name__) + +SUPPORTED_PLATFORMS = [ + "switch", +] + +ALREADY_DISCOVERED = "tasmota_discovered_components" +CONFIG_ENTRY_IS_SETUP = "tasmota_config_entry_is_setup" +DATA_CONFIG_ENTRY_LOCK = "tasmota_config_entry_lock" +TASMOTA_DISCOVERY_DEVICE = "tasmota_discovery_device" +TASMOTA_DISCOVERY_ENTITY_NEW = "tasmota_discovery_entity_new_{}" +TASMOTA_DISCOVERY_ENTITY_UPDATED = "tasmota_discovery_entity_updated_{}_{}_{}_{}" + + +def clear_discovery_hash(hass, discovery_hash): + """Clear entry in ALREADY_DISCOVERED list.""" + del hass.data[ALREADY_DISCOVERED][discovery_hash] + + +def set_discovery_hash(hass, discovery_hash): + """Set entry in ALREADY_DISCOVERED list.""" + hass.data[ALREADY_DISCOVERED][discovery_hash] = {} + + +async def async_start( + hass: HomeAssistantType, discovery_topic, config_entry, tasmota_mqtt +) -> bool: + """Start MQTT Discovery.""" + + async def _load_platform(platform): + """Load a Tasmota platform if not already done.""" + async with hass.data[DATA_CONFIG_ENTRY_LOCK]: + config_entries_key = f"{platform}.tasmota" + if config_entries_key not in hass.data[CONFIG_ENTRY_IS_SETUP]: + await hass.config_entries.async_forward_entry_setup( + config_entry, platform + ) + hass.data[CONFIG_ENTRY_IS_SETUP].add(config_entries_key) + + async def _discover_entity(tasmota_entity_config, discovery_hash, platform): + """Handle adding or updating a discovered entity.""" + if not tasmota_entity_config: + # Entity disabled, clean up entity registry + entity_registry = await hass.helpers.entity_registry.async_get_registry() + unique_id = unique_id_from_hash(discovery_hash) + entity_id = entity_registry.async_get_entity_id(platform, DOMAIN, unique_id) + if entity_id: + _LOGGER.debug("Removing entity: %s %s", platform, discovery_hash) + entity_registry.async_remove(entity_id) + return + + if discovery_hash in hass.data[ALREADY_DISCOVERED]: + _LOGGER.debug( + "Entity already added, sending update: %s %s", + platform, + discovery_hash, + ) + async_dispatcher_send( + hass, + TASMOTA_DISCOVERY_ENTITY_UPDATED.format(*discovery_hash), + tasmota_entity_config, + ) + else: + _LOGGER.debug("Adding new entity: %s %s", platform, discovery_hash) + tasmota_entity = tasmota_get_entity(tasmota_entity_config, tasmota_mqtt) + + hass.data[ALREADY_DISCOVERED][discovery_hash] = None + + async_dispatcher_send( + hass, + TASMOTA_DISCOVERY_ENTITY_NEW.format(platform), + tasmota_entity, + discovery_hash, + ) + + async def async_device_discovered(payload, mac): + """Process the received message.""" + + if ALREADY_DISCOVERED not in hass.data: + hass.data[ALREADY_DISCOVERED] = {} + + _LOGGER.debug("Received discovery data for tasmota device: %s", mac) + tasmota_device_config = tasmota_get_device_config(payload) + async_dispatcher_send( + hass, TASMOTA_DISCOVERY_DEVICE, tasmota_device_config, mac + ) + + if not payload: + return + + for platform in SUPPORTED_PLATFORMS: + if not tasmota_has_entities_with_platform(payload, platform): + continue + await _load_platform(platform) + + for platform in SUPPORTED_PLATFORMS: + tasmota_entities = tasmota_get_entities_for_platform(payload, platform) + for (tasmota_entity_config, discovery_hash) in tasmota_entities: + await _discover_entity(tasmota_entity_config, discovery_hash, platform) + + hass.data[DATA_CONFIG_ENTRY_LOCK] = asyncio.Lock() + hass.data[CONFIG_ENTRY_IS_SETUP] = set() + + tasmota_discovery = TasmotaDiscovery(discovery_topic, tasmota_mqtt) + await tasmota_discovery.start_discovery(async_device_discovered, None) diff --git a/homeassistant/components/tasmota/manifest.json b/homeassistant/components/tasmota/manifest.json new file mode 100644 index 00000000000000..5540988edcc89e --- /dev/null +++ b/homeassistant/components/tasmota/manifest.json @@ -0,0 +1,9 @@ +{ + "domain": "tasmota", + "name": "Tasmota (beta)", + "config_flow": true, + "documentation": "https://www.home-assistant.io/integrations/tasmota", + "requirements": ["hatasmota==0.0.10"], + "dependencies": ["mqtt"], + "codeowners": ["@emontnemery"] +} diff --git a/homeassistant/components/tasmota/mixins.py b/homeassistant/components/tasmota/mixins.py new file mode 100644 index 00000000000000..c8099cfbb85e79 --- /dev/null +++ b/homeassistant/components/tasmota/mixins.py @@ -0,0 +1,111 @@ +"""Tasnmota entity mixins.""" +import logging + +from homeassistant.components.mqtt import ( + async_subscribe_connection_status, + is_connected as mqtt_connected, +) +from homeassistant.core import callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity import Entity + +from .discovery import ( + TASMOTA_DISCOVERY_ENTITY_UPDATED, + clear_discovery_hash, + set_discovery_hash, +) + +DATA_MQTT = "mqtt" + +_LOGGER = logging.getLogger(__name__) + + +class TasmotaEntity(Entity): + """Base class for Tasmota entities.""" + + def __init__(self, tasmota_entity) -> None: + """Initialize.""" + self._tasmota_entity = tasmota_entity + + +class TasmotaAvailability(TasmotaEntity): + """Mixin used for platforms that report availability.""" + + def __init__(self, **kwds) -> None: + """Initialize the availability mixin.""" + self._available = False + super().__init__(**kwds) + + async def async_added_to_hass(self) -> None: + """Subscribe MQTT events.""" + await super().async_added_to_hass() + self._tasmota_entity.set_on_availability_callback(self.availability_updated) + self.async_on_remove( + async_subscribe_connection_status(self.hass, self.async_mqtt_connected) + ) + + @callback + def availability_updated(self, available: bool) -> None: + """Handle updated availability.""" + self._available = available + self.async_write_ha_state() + + @callback + def async_mqtt_connected(self, _): + """Update state on connection/disconnection to MQTT broker.""" + if not self.hass.is_stopping: + self.async_write_ha_state() + + @property + def available(self) -> bool: + """Return if the device is available.""" + if not mqtt_connected(self.hass) and not self.hass.is_stopping: + return False + return self._available + + +class TasmotaDiscoveryUpdate(TasmotaEntity): + """Mixin used to handle updated discovery message.""" + + def __init__(self, discovery_hash, discovery_update, **kwds) -> None: + """Initialize the discovery update mixin.""" + self._discovery_hash = discovery_hash + self._discovery_update = discovery_update + self._removed_from_hass = False + super().__init__(**kwds) + + async def async_added_to_hass(self) -> None: + """Subscribe to discovery updates.""" + await super().async_added_to_hass() + self._removed_from_hass = False + + async def discovery_callback(config): + """Handle discovery update.""" + _LOGGER.debug( + "Got update for entity with hash: %s '%s'", + self._discovery_hash, + config, + ) + if not self._tasmota_entity.config_same(config): + # Changed payload: Notify component + _LOGGER.debug("Updating component: %s", self.entity_id) + await self._discovery_update(config) + else: + # Unchanged payload: Ignore to avoid changing states + _LOGGER.debug("Ignoring unchanged update for: %s", self.entity_id) + + # Set in case the entity has been removed and is re-added, for example when changing entity_id + set_discovery_hash(self.hass, self._discovery_hash) + self.async_on_remove( + async_dispatcher_connect( + self.hass, + TASMOTA_DISCOVERY_ENTITY_UPDATED.format(*self._discovery_hash), + discovery_callback, + ) + ) + + async def async_will_remove_from_hass(self) -> None: + """Stop listening to signal and cleanup discovery data..""" + if not self._removed_from_hass: + clear_discovery_hash(self.hass, self._discovery_hash) + self._removed_from_hass = True diff --git a/homeassistant/components/tasmota/strings.json b/homeassistant/components/tasmota/strings.json new file mode 100644 index 00000000000000..d19bb09326399a --- /dev/null +++ b/homeassistant/components/tasmota/strings.json @@ -0,0 +1,19 @@ +{ + "config": { + "step": { + "config": { + "title": "Tasmota", + "description": "Please enter the Tasmota configuration.", + "data": { + "discovery_prefix": "Discovery topic prefix" + } + } + }, + "abort": { + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" + }, + "error": { + "invalid_discovery_topic": "Invalid discovery topic prefix." + } + } +} diff --git a/homeassistant/components/tasmota/switch.py b/homeassistant/components/tasmota/switch.py new file mode 100644 index 00000000000000..f087a30958ce8e --- /dev/null +++ b/homeassistant/components/tasmota/switch.py @@ -0,0 +1,116 @@ +"""Support for Tasmota switches.""" +import logging + +from homeassistant.components import switch +from homeassistant.components.switch import SwitchEntity +from homeassistant.core import callback +from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from .const import DOMAIN as TASMOTA_DOMAIN +from .discovery import TASMOTA_DISCOVERY_ENTITY_NEW +from .mixins import TasmotaAvailability, TasmotaDiscoveryUpdate + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, config_entry, async_add_entities): + """Set up Tasmota switch dynamically through discovery.""" + + @callback + def async_discover(tasmota_entity, discovery_hash): + """Discover and add a Tasmota switch.""" + async_add_entities( + [ + TasmotaSwitch( + tasmota_entity=tasmota_entity, discovery_hash=discovery_hash + ) + ] + ) + + async_dispatcher_connect( + hass, + TASMOTA_DISCOVERY_ENTITY_NEW.format(switch.DOMAIN, TASMOTA_DOMAIN), + async_discover, + ) + + +class TasmotaSwitch( + TasmotaAvailability, + TasmotaDiscoveryUpdate, + SwitchEntity, +): + """Representation of a Tasmota switch.""" + + def __init__(self, tasmota_entity, **kwds): + """Initialize the Tasmota switch.""" + self._state = False + self._sub_state = None + + self._unique_id = tasmota_entity.unique_id + + super().__init__( + discovery_update=self.discovery_update, + tasmota_entity=tasmota_entity, + **kwds, + ) + + async def async_added_to_hass(self): + """Subscribe to MQTT events.""" + await super().async_added_to_hass() + self._tasmota_entity.set_on_state_callback(self.state_updated) + await self._subscribe_topics() + + async def discovery_update(self, update): + """Handle updated discovery message.""" + self._tasmota_entity.config_update(update) + await self._subscribe_topics() + self.async_write_ha_state() + + @callback + def state_updated(self, state): + """Handle new MQTT state messages.""" + self._state = state + self.async_write_ha_state() + + async def _subscribe_topics(self): + """(Re)Subscribe to topics.""" + await self._tasmota_entity.subscribe_topics() + + async def async_will_remove_from_hass(self): + """Unsubscribe when removed.""" + await self._tasmota_entity.unsubscribe_topics() + await super().async_will_remove_from_hass() + + @property + def should_poll(self): + """Return the polling state.""" + return False + + @property + def name(self): + """Return the name of the switch.""" + return self._tasmota_entity.name + + @property + def is_on(self): + """Return true if device is on.""" + return self._state + + @property + def unique_id(self): + """Return a unique ID.""" + return self._tasmota_entity.unique_id + + @property + def device_info(self): + """Return a device description for device registry.""" + return {"connections": {(CONNECTION_NETWORK_MAC, self._tasmota_entity.mac)}} + + async def async_turn_on(self, **kwargs): + """Turn the device on.""" + self._tasmota_entity.set_state(True) + + async def async_turn_off(self, **kwargs): + """Turn the device off.""" + self._tasmota_entity.set_state(False) diff --git a/homeassistant/components/teksavvy/__init__.py b/homeassistant/components/teksavvy/__init__.py deleted file mode 100644 index ee0dcd1c8102e4..00000000000000 --- a/homeassistant/components/teksavvy/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""The teksavvy component.""" diff --git a/homeassistant/components/teksavvy/manifest.json b/homeassistant/components/teksavvy/manifest.json deleted file mode 100644 index e114efdce9f003..00000000000000 --- a/homeassistant/components/teksavvy/manifest.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "domain": "teksavvy", - "name": "TekSavvy", - "documentation": "https://www.home-assistant.io/integrations/teksavvy", - "codeowners": [] -} diff --git a/homeassistant/components/teksavvy/sensor.py b/homeassistant/components/teksavvy/sensor.py deleted file mode 100644 index 4ff2bc84dbe23d..00000000000000 --- a/homeassistant/components/teksavvy/sensor.py +++ /dev/null @@ -1,173 +0,0 @@ -"""Support for TekSavvy Bandwidth Monitor.""" -from datetime import timedelta -import logging - -import async_timeout -import voluptuous as vol - -from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import ( - CONF_API_KEY, - CONF_MONITORED_VARIABLES, - CONF_NAME, - DATA_GIGABYTES, - HTTP_OK, - PERCENTAGE, -) -from homeassistant.helpers.aiohttp_client import async_get_clientsession -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.entity import Entity -from homeassistant.util import Throttle - -_LOGGER = logging.getLogger(__name__) - -DEFAULT_NAME = "TekSavvy" -CONF_TOTAL_BANDWIDTH = "total_bandwidth" - -MIN_TIME_BETWEEN_UPDATES = timedelta(hours=1) -REQUEST_TIMEOUT = 5 # seconds - -SENSOR_TYPES = { - "usage": ["Usage Ratio", PERCENTAGE, "mdi:percent"], - "usage_gb": ["Usage", DATA_GIGABYTES, "mdi:download"], - "limit": ["Data limit", DATA_GIGABYTES, "mdi:download"], - "onpeak_download": ["On Peak Download", DATA_GIGABYTES, "mdi:download"], - "onpeak_upload": ["On Peak Upload", DATA_GIGABYTES, "mdi:upload"], - "onpeak_total": ["On Peak Total", DATA_GIGABYTES, "mdi:download"], - "offpeak_download": ["Off Peak download", DATA_GIGABYTES, "mdi:download"], - "offpeak_upload": ["Off Peak Upload", DATA_GIGABYTES, "mdi:upload"], - "offpeak_total": ["Off Peak Total", DATA_GIGABYTES, "mdi:download"], - "onpeak_remaining": ["Remaining", DATA_GIGABYTES, "mdi:download"], -} - -API_HA_MAP = ( - ("OnPeakDownload", "onpeak_download"), - ("OnPeakUpload", "onpeak_upload"), - ("OffPeakDownload", "offpeak_download"), - ("OffPeakUpload", "offpeak_upload"), -) - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( - { - vol.Required(CONF_MONITORED_VARIABLES): vol.All( - cv.ensure_list, [vol.In(SENSOR_TYPES)] - ), - vol.Required(CONF_API_KEY): cv.string, - vol.Required(CONF_TOTAL_BANDWIDTH): cv.positive_int, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - } -) - - -async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): - """Set up the sensor platform.""" - websession = async_get_clientsession(hass) - apikey = config.get(CONF_API_KEY) - bandwidthcap = config.get(CONF_TOTAL_BANDWIDTH) - - ts_data = TekSavvyData(hass.loop, websession, apikey, bandwidthcap) - ret = await ts_data.async_update() - if ret is False: - _LOGGER.error("Invalid Teksavvy API key: %s", apikey) - return - - name = config.get(CONF_NAME) - sensors = [] - for variable in config[CONF_MONITORED_VARIABLES]: - sensors.append(TekSavvySensor(ts_data, variable, name)) - async_add_entities(sensors, True) - - -class TekSavvySensor(Entity): - """Representation of TekSavvy Bandwidth sensor.""" - - def __init__(self, teksavvydata, sensor_type, name): - """Initialize the sensor.""" - self.client_name = name - self.type = sensor_type - self._name = SENSOR_TYPES[sensor_type][0] - self._unit_of_measurement = SENSOR_TYPES[sensor_type][1] - self._icon = SENSOR_TYPES[sensor_type][2] - self.teksavvydata = teksavvydata - self._state = None - - @property - def name(self): - """Return the name of the sensor.""" - return f"{self.client_name} {self._name}" - - @property - def state(self): - """Return the state of the sensor.""" - return self._state - - @property - def unit_of_measurement(self): - """Return the unit of measurement of this entity, if any.""" - return self._unit_of_measurement - - @property - def icon(self): - """Icon to use in the frontend, if any.""" - return self._icon - - async def async_update(self): - """Get the latest data from TekSavvy and update the state.""" - await self.teksavvydata.async_update() - if self.type in self.teksavvydata.data: - self._state = round(self.teksavvydata.data[self.type], 2) - - -class TekSavvyData: - """Get data from TekSavvy API.""" - - def __init__(self, loop, websession, api_key, bandwidth_cap): - """Initialize the data object.""" - self.loop = loop - self.websession = websession - self.api_key = api_key - self.bandwidth_cap = bandwidth_cap - # Set unlimited users to infinite, otherwise the cap. - self.data = ( - {"limit": self.bandwidth_cap} - if self.bandwidth_cap > 0 - else {"limit": float("inf")} - ) - - @Throttle(MIN_TIME_BETWEEN_UPDATES) - async def async_update(self): - """Get the TekSavvy bandwidth data from the web service.""" - headers = {"TekSavvy-APIKey": self.api_key} - _LOGGER.debug("Updating TekSavvy data") - url = ( - "https://api.teksavvy.com/" - "web/Usage/UsageSummaryRecords?$filter=IsCurrent%20eq%20true" - ) - with async_timeout.timeout(REQUEST_TIMEOUT): - req = await self.websession.get(url, headers=headers) - if req.status != HTTP_OK: - _LOGGER.error("Request failed with status: %u", req.status) - return False - - try: - data = await req.json() - for (api, ha_name) in API_HA_MAP: - self.data[ha_name] = float(data["value"][0][api]) - on_peak_download = self.data["onpeak_download"] - on_peak_upload = self.data["onpeak_upload"] - off_peak_download = self.data["offpeak_download"] - off_peak_upload = self.data["offpeak_upload"] - limit = self.data["limit"] - # Support "unlimited" users - if self.bandwidth_cap > 0: - self.data["usage"] = 100 * on_peak_download / self.bandwidth_cap - else: - self.data["usage"] = 0 - self.data["usage_gb"] = on_peak_download - self.data["onpeak_total"] = on_peak_download + on_peak_upload - self.data["offpeak_total"] = off_peak_download + off_peak_upload - self.data["onpeak_remaining"] = limit - on_peak_download - return True - except ValueError: - _LOGGER.error("JSON Decode Failed") - return False diff --git a/homeassistant/components/telegram_bot/services.yaml b/homeassistant/components/telegram_bot/services.yaml index 8e42b25c0caf9d..a87bfdf3af6b9f 100644 --- a/homeassistant/components/telegram_bot/services.yaml +++ b/homeassistant/components/telegram_bot/services.yaml @@ -202,7 +202,7 @@ send_location: example: '["/button1, /button2", "/button3"] or [[["Text button1", "/button1"], ["Text button2", "/button2"]], [["Text button3", "/button3"]]]' edit_message: - description: Edit a previusly sent message. + description: Edit a previously sent message. fields: message_id: description: id of the message to edit. @@ -227,7 +227,7 @@ edit_message: example: '["/button1, /button2", "/button3"] or [[["Text button1", "/button1"], ["Text button2", "/button2"]], [["Text button3", "/button3"]]]' edit_caption: - description: Edit the caption of a previusly sent message. + description: Edit the caption of a previously sent message. fields: message_id: description: id of the message to edit. @@ -243,7 +243,7 @@ edit_caption: example: '["/button1, /button2", "/button3"] or [[["Text button1", "/button1"], ["Text button2", "/button2"]], [["Text button3", "/button3"]]]' edit_replymarkup: - description: Edit the inline keyboard of a previusly sent message. + description: Edit the inline keyboard of a previously sent message. fields: message_id: description: id of the message to edit. diff --git a/homeassistant/components/tellduslive/sensor.py b/homeassistant/components/tellduslive/sensor.py index c8f27a9412a406..e322481813a82c 100644 --- a/homeassistant/components/tellduslive/sensor.py +++ b/homeassistant/components/tellduslive/sensor.py @@ -6,6 +6,8 @@ DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE, + LENGTH_MILLIMETERS, + LIGHT_LUX, PERCENTAGE, POWER_WATT, SPEED_METERS_PER_SECOND, @@ -40,14 +42,19 @@ DEVICE_CLASS_TEMPERATURE, ], SENSOR_TYPE_HUMIDITY: ["Humidity", PERCENTAGE, None, DEVICE_CLASS_HUMIDITY], - SENSOR_TYPE_RAINRATE: ["Rain rate", f"mm/{TIME_HOURS}", "mdi:water", None], - SENSOR_TYPE_RAINTOTAL: ["Rain total", "mm", "mdi:water", None], + SENSOR_TYPE_RAINRATE: [ + "Rain rate", + f"{LENGTH_MILLIMETERS}/{TIME_HOURS}", + "mdi:water", + None, + ], + SENSOR_TYPE_RAINTOTAL: ["Rain total", LENGTH_MILLIMETERS, "mdi:water", None], SENSOR_TYPE_WINDDIRECTION: ["Wind direction", "", "", None], SENSOR_TYPE_WINDAVERAGE: ["Wind average", SPEED_METERS_PER_SECOND, "", None], SENSOR_TYPE_WINDGUST: ["Wind gust", SPEED_METERS_PER_SECOND, "", None], SENSOR_TYPE_UV: ["UV", UV_INDEX, "", None], SENSOR_TYPE_WATT: ["Power", POWER_WATT, "", None], - SENSOR_TYPE_LUMINANCE: ["Luminance", "lx", None, DEVICE_CLASS_ILLUMINANCE], + SENSOR_TYPE_LUMINANCE: ["Luminance", LIGHT_LUX, None, DEVICE_CLASS_ILLUMINANCE], SENSOR_TYPE_DEW_POINT: ["Dew Point", TEMP_CELSIUS, None, DEVICE_CLASS_TEMPERATURE], SENSOR_TYPE_BAROMETRIC_PRESSURE: ["Barometric Pressure", "kPa", "", None], } diff --git a/homeassistant/components/tellduslive/translations/pl.json b/homeassistant/components/tellduslive/translations/pl.json index 8145717b40e973..13054967365281 100644 --- a/homeassistant/components/tellduslive/translations/pl.json +++ b/homeassistant/components/tellduslive/translations/pl.json @@ -4,7 +4,7 @@ "already_configured": "TelldusLive jest ju\u017c skonfigurowany", "authorize_url_fail": "Nieznany b\u0142\u0105d podczas generowania url autoryzacji.", "authorize_url_timeout": "Przekroczono limit czasu generowania URL autoryzacji.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "error": { "auth_error": "B\u0142\u0105d uwierzytelniania, spr\u00f3buj ponownie" diff --git a/homeassistant/components/template/cover.py b/homeassistant/components/template/cover.py index dc9e5ead1d03d1..fe8b6568c1e3e7 100644 --- a/homeassistant/components/template/cover.py +++ b/homeassistant/components/template/cover.py @@ -29,7 +29,9 @@ CONF_UNIQUE_ID, CONF_VALUE_TEMPLATE, STATE_CLOSED, + STATE_CLOSING, STATE_OPEN, + STATE_OPENING, ) from homeassistant.core import callback from homeassistant.exceptions import TemplateError @@ -42,7 +44,14 @@ from .template_entity import TemplateEntity _LOGGER = logging.getLogger(__name__) -_VALID_STATES = [STATE_OPEN, STATE_CLOSED, "true", "false"] +_VALID_STATES = [ + STATE_OPEN, + STATE_OPENING, + STATE_CLOSED, + STATE_CLOSING, + "true", + "false", +] CONF_COVERS = "covers" diff --git a/homeassistant/components/template/template_entity.py b/homeassistant/components/template/template_entity.py index 2e42b28fbd2188..49b0edfab0230f 100644 --- a/homeassistant/components/template/template_entity.py +++ b/homeassistant/components/template/template_entity.py @@ -5,6 +5,7 @@ import voluptuous as vol +from homeassistant.const import ATTR_ENTITY_ID from homeassistant.core import EVENT_HOMEASSISTANT_START, CoreState, callback from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv @@ -121,7 +122,6 @@ def __init__( """Template Entity.""" self._template_attrs = {} self._async_update = None - self._async_update_entity_ids_filter = None self._attribute_templates = attribute_templates self._attributes = {} self._availability_template = availability_template @@ -130,6 +130,7 @@ def __init__( self._entity_picture_template = entity_picture_template self._icon = None self._entity_picture = None + self._self_ref_update_count = 0 @property def should_poll(self): @@ -212,7 +213,6 @@ def add_template_attribute( attribute = _TemplateAttribute( self, attribute, template, validator, on_update, none_on_template_error ) - attribute.async_setup() self._template_attrs.setdefault(template, []) self._template_attrs[template].append(attribute) @@ -223,38 +223,47 @@ def _handle_results( updates: List[TrackTemplateResult], ) -> None: """Call back the results to the attributes.""" + if event: self.async_set_context(event.context) + entity_id = event and event.data.get(ATTR_ENTITY_ID) + + if entity_id and entity_id == self.entity_id: + self._self_ref_update_count += 1 + else: + self._self_ref_update_count = 0 + + if self._self_ref_update_count > len(self._template_attrs): + for update in updates: + _LOGGER.warning( + "Template loop detected while processing event: %s, skipping template render for Template[%s]", + event, + update.template.template, + ) + return + for update in updates: for attr in self._template_attrs[update.template]: attr.handle_result( event, update.template, update.last_result, update.result ) - if self._async_update_entity_ids_filter: - self._async_update_entity_ids_filter({self.entity_id}) - - if self._async_update: - self.async_write_ha_state() + self.async_write_ha_state() async def _async_template_startup(self, *_) -> None: - # _handle_results will not write state until "_async_update" is set - template_var_tups = [ - TrackTemplate(template, None) for template in self._template_attrs - ] + template_var_tups = [] + for template, attributes in self._template_attrs.items(): + template_var_tups.append(TrackTemplate(template, None)) + for attribute in attributes: + attribute.async_setup() result_info = async_track_template_result( self.hass, template_var_tups, self._handle_results ) self.async_on_remove(result_info.async_remove) - result_info.async_refresh() - result_info.async_update_entity_ids_filter({self.entity_id}) - self.async_write_ha_state() self._async_update = result_info.async_refresh - self._async_update_entity_ids_filter = ( - result_info.async_update_entity_ids_filter - ) + result_info.async_refresh() async def async_added_to_hass(self) -> None: """Run when entity about to be added to hass.""" diff --git a/homeassistant/components/tesla/config_flow.py b/homeassistant/components/tesla/config_flow.py index 9e46a30972f5b7..debe896c9cf682 100644 --- a/homeassistant/components/tesla/config_flow.py +++ b/homeassistant/components/tesla/config_flow.py @@ -11,6 +11,7 @@ CONF_SCAN_INTERVAL, CONF_TOKEN, CONF_USERNAME, + HTTP_UNAUTHORIZED, ) from homeassistant.core import callback from homeassistant.helpers import aiohttp_client, config_validation as cv @@ -61,7 +62,7 @@ async def async_step_user(self, user_input=None): return self.async_show_form( step_id="user", data_schema=DATA_SCHEMA, - errors={CONF_USERNAME: "identifier_exists"}, + errors={CONF_USERNAME: "already_configured"}, description_placeholders={}, ) @@ -71,14 +72,14 @@ async def async_step_user(self, user_input=None): return self.async_show_form( step_id="user", data_schema=DATA_SCHEMA, - errors={"base": "connection_error"}, + errors={"base": "cannot_connect"}, description_placeholders={}, ) except InvalidAuth: return self.async_show_form( step_id="user", data_schema=DATA_SCHEMA, - errors={"base": "invalid_credentials"}, + errors={"base": "invalid_auth"}, description_placeholders={}, ) return self.async_create_entry(title=user_input[CONF_USERNAME], data=info) @@ -140,7 +141,7 @@ async def validate_input(hass: core.HomeAssistant, data): test_login=True ) except TeslaException as ex: - if ex.code == 401: + if ex.code == HTTP_UNAUTHORIZED: _LOGGER.error("Invalid credentials: %s", ex) raise InvalidAuth() from ex _LOGGER.error("Unable to communicate with Tesla API: %s", ex) diff --git a/homeassistant/components/tesla/strings.json b/homeassistant/components/tesla/strings.json index fb3c11a276fdd5..503124eedd4e8f 100644 --- a/homeassistant/components/tesla/strings.json +++ b/homeassistant/components/tesla/strings.json @@ -1,10 +1,9 @@ { "config": { "error": { - "connection_error": "Error connecting; check network and retry", - "identifier_exists": "Email already registered", - "invalid_credentials": "Invalid credentials", - "unknown_error": "Unknown error, please report log info" + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" }, "step": { "user": { @@ -27,4 +26,4 @@ } } } -} \ No newline at end of file +} diff --git a/homeassistant/components/tesla/translations/ca.json b/homeassistant/components/tesla/translations/ca.json index 35169f70245c4d..0d39ebc26392f6 100644 --- a/homeassistant/components/tesla/translations/ca.json +++ b/homeassistant/components/tesla/translations/ca.json @@ -1,8 +1,12 @@ { "config": { "error": { + "already_configured": "El compte ja ha estat configurat", + "already_configured_account": "El compte ja ha estat configurat", + "cannot_connect": "Ha fallat la connexi\u00f3", "connection_error": "Error de connexi\u00f3; comprova la xarxa i torna-ho a intentar", "identifier_exists": "Correu electr\u00f2nic ja registrat", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "invalid_credentials": "Credencials inv\u00e0lides", "unknown_error": "Error desconegut, si us plau, envia la informaci\u00f3 del registre" }, diff --git a/homeassistant/components/tesla/translations/en.json b/homeassistant/components/tesla/translations/en.json index 762a7f01d0ecbf..80d5591b0a129e 100644 --- a/homeassistant/components/tesla/translations/en.json +++ b/homeassistant/components/tesla/translations/en.json @@ -1,8 +1,12 @@ { "config": { "error": { + "already_configured": "Account is already configured", + "already_configured_account": "Account is already configured", + "cannot_connect": "Failed to connect", "connection_error": "Error connecting; check network and retry", "identifier_exists": "Email already registered", + "invalid_auth": "Invalid authentication", "invalid_credentials": "Invalid credentials", "unknown_error": "Unknown error, please report log info" }, diff --git a/homeassistant/components/tesla/translations/es.json b/homeassistant/components/tesla/translations/es.json index 8bb659d377fe22..08518205257cc4 100644 --- a/homeassistant/components/tesla/translations/es.json +++ b/homeassistant/components/tesla/translations/es.json @@ -1,8 +1,11 @@ { "config": { "error": { + "already_configured_account": "La cuenta ya ha sido configurada", + "cannot_connect": "No se pudo conectar", "connection_error": "Error de conexi\u00f3n; comprueba la red y vuelve a intentarlo", "identifier_exists": "Correo electr\u00f3nico ya registrado", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", "invalid_credentials": "Credenciales no v\u00e1lidas", "unknown_error": "Error desconocido, por favor reporte la informaci\u00f3n de registro" }, diff --git a/homeassistant/components/tesla/translations/et.json b/homeassistant/components/tesla/translations/et.json new file mode 100644 index 00000000000000..baf6178bb262fd --- /dev/null +++ b/homeassistant/components/tesla/translations/et.json @@ -0,0 +1,10 @@ +{ + "config": { + "error": { + "already_configured": "Konto on juba h\u00e4\u00e4lestatud", + "already_configured_account": "Kasutaja on juba lisatud", + "cannot_connect": "\u00dchendamine nurjus", + "invalid_auth": "Tuvastamise viga" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tesla/translations/it.json b/homeassistant/components/tesla/translations/it.json index 0c4ae68fe0ffd4..5a14b01cc88aec 100644 --- a/homeassistant/components/tesla/translations/it.json +++ b/homeassistant/components/tesla/translations/it.json @@ -1,8 +1,12 @@ { "config": { "error": { + "already_configured": "L'account \u00e8 gi\u00e0 configurato", + "already_configured_account": "L'account \u00e8 gi\u00e0 configurato", + "cannot_connect": "Impossibile connettersi", "connection_error": "Errore durante la connessione; controllare la rete e riprovare", "identifier_exists": "E-mail gi\u00e0 registrata", + "invalid_auth": "Autenticazione non valida", "invalid_credentials": "Credenziali non valide", "unknown_error": "Errore sconosciuto, si prega di segnalare le informazioni del registro" }, diff --git a/homeassistant/components/tesla/translations/no.json b/homeassistant/components/tesla/translations/no.json index a2ca81bf693ed0..6c9e047ff629fc 100644 --- a/homeassistant/components/tesla/translations/no.json +++ b/homeassistant/components/tesla/translations/no.json @@ -1,8 +1,11 @@ { "config": { "error": { + "already_configured_account": "Kontoen er allerede konfigurert", + "cannot_connect": "Tilkobling mislyktes.", "connection_error": "Feil ved tilkobling; sjekk nettverket og pr\u00f8v p\u00e5 nytt", "identifier_exists": "E-post er allerede registrert", + "invalid_auth": "Ugyldig godkjenning", "invalid_credentials": "Ugyldig legitimasjon", "unknown_error": "Ukjent feil, Vennligst rapporter informasjon fra Loggen" }, diff --git a/homeassistant/components/tesla/translations/pl.json b/homeassistant/components/tesla/translations/pl.json index e2a6b6a09c82a9..99623c0d7732af 100644 --- a/homeassistant/components/tesla/translations/pl.json +++ b/homeassistant/components/tesla/translations/pl.json @@ -1,6 +1,7 @@ { "config": { "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "connection_error": "B\u0142\u0105d po\u0142\u0105czenia; sprawd\u017a sie\u0107 i spr\u00f3buj ponownie", "identifier_exists": "Adres e-mail jest ju\u017c zarejestrowany.", "invalid_credentials": "Nieprawid\u0142owe po\u015bwiadczenia", diff --git a/homeassistant/components/tesla/translations/ru.json b/homeassistant/components/tesla/translations/ru.json index dae30a669b32d9..10a1239279c75e 100644 --- a/homeassistant/components/tesla/translations/ru.json +++ b/homeassistant/components/tesla/translations/ru.json @@ -1,8 +1,12 @@ { "config": { "error": { + "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_configured_account": "\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.", + "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": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0441\u0435\u0442\u044c \u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435 \u043f\u043e\u043f\u044b\u0442\u043a\u0443.", "identifier_exists": "\u0423\u0447\u0451\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": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "invalid_credentials": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0435 \u0443\u0447\u0451\u0442\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435.", "unknown_error": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430." }, diff --git a/homeassistant/components/thethingsnetwork/sensor.py b/homeassistant/components/thethingsnetwork/sensor.py index 35a16d30f32024..3555816213a7b8 100644 --- a/homeassistant/components/thethingsnetwork/sensor.py +++ b/homeassistant/components/thethingsnetwork/sensor.py @@ -8,7 +8,7 @@ import voluptuous as vol from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONTENT_TYPE_JSON, HTTP_NOT_FOUND +from homeassistant.const import CONTENT_TYPE_JSON, HTTP_NOT_FOUND, HTTP_UNAUTHORIZED from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity @@ -135,7 +135,7 @@ async def async_update(self): _LOGGER.error("The device is not available: %s", self._device_id) return None - if status == 401: + if status == HTTP_UNAUTHORIZED: _LOGGER.error("Not authorized for Application ID: %s", self._app_id) return None diff --git a/homeassistant/components/tibber/manifest.json b/homeassistant/components/tibber/manifest.json index b3decdd250a3cf..74f61f17d29404 100644 --- a/homeassistant/components/tibber/manifest.json +++ b/homeassistant/components/tibber/manifest.json @@ -2,7 +2,7 @@ "domain": "tibber", "name": "Tibber", "documentation": "https://www.home-assistant.io/integrations/tibber", - "requirements": ["pyTibber==0.15.2"], + "requirements": ["pyTibber==0.15.3"], "codeowners": ["@danielhiversen"], "quality_scale": "silver", "config_flow": true diff --git a/homeassistant/components/tibber/sensor.py b/homeassistant/components/tibber/sensor.py index 939c6d1597de59..c5484f908386fd 100644 --- a/homeassistant/components/tibber/sensor.py +++ b/homeassistant/components/tibber/sensor.py @@ -2,6 +2,7 @@ import asyncio from datetime import timedelta import logging +from random import randrange import aiohttp @@ -59,6 +60,7 @@ def __init__(self, tibber_home): self._name = tibber_home.info["viewer"]["home"]["address"].get( "address1", "" ) + self._spread_load_constant = randrange(3600) @property def device_state_attributes(self): @@ -110,7 +112,8 @@ async def async_update(self): if ( not self._tibber_home.last_data_timestamp - or (self._tibber_home.last_data_timestamp - now).total_seconds() / 3600 < 12 + or (self._tibber_home.last_data_timestamp - now).total_seconds() + < 12 * 3600 + self._spread_load_constant or not self._is_available ): _LOGGER.debug("Asking for new data") @@ -156,9 +159,9 @@ def unique_id(self): @Throttle(MIN_TIME_BETWEEN_UPDATES) async def _fetch_data(self): + _LOGGER.debug("Fetching data") try: - await self._tibber_home.update_info() - await self._tibber_home.update_price_info() + await self._tibber_home.update_info_and_price_info() except (asyncio.TimeoutError, aiohttp.ClientError): return data = self._tibber_home.info["viewer"]["home"] diff --git a/homeassistant/components/tibber/strings.json b/homeassistant/components/tibber/strings.json index cb5cc08552aded..25af2c7f30d348 100644 --- a/homeassistant/components/tibber/strings.json +++ b/homeassistant/components/tibber/strings.json @@ -2,11 +2,11 @@ "title": "Tibber", "config": { "abort": { - "already_configured": "A Tibber account is already configured." + "already_configured": "[%key:common::config_flow::abort::already_configured_service%]" }, "error": { "timeout": "Timeout connecting to Tibber", - "connection_error": "Error connecting to Tibber", + "connection_error": "[%key:common::config_flow::error::cannot_connect%]", "invalid_access_token": "[%key:common::config_flow::error::invalid_access_token%]" }, "step": { @@ -19,4 +19,4 @@ } } } -} \ No newline at end of file +} diff --git a/homeassistant/components/tibber/translations/ca.json b/homeassistant/components/tibber/translations/ca.json index 0b17cbb352459e..111c9e568f3102 100644 --- a/homeassistant/components/tibber/translations/ca.json +++ b/homeassistant/components/tibber/translations/ca.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Ja hi ha un compte Tibber configurat." + "already_configured": "El servei ja est\u00e0 configurat" }, "error": { - "connection_error": "S'ha produ\u00eft un error en connectar-se a Tibber", + "connection_error": "Ha fallat la connexi\u00f3", "invalid_access_token": "[%key::common::config_flow::error::invalid_access_token%]", "timeout": "S'ha acabat el temps d'espera durant la connexi\u00f3 a Tibber" }, diff --git a/homeassistant/components/tibber/translations/en.json b/homeassistant/components/tibber/translations/en.json index 658ce0049433e1..85d720a8c66d28 100644 --- a/homeassistant/components/tibber/translations/en.json +++ b/homeassistant/components/tibber/translations/en.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "A Tibber account is already configured." + "already_configured": "Service is already configured" }, "error": { - "connection_error": "Error connecting to Tibber", + "connection_error": "Failed to connect", "invalid_access_token": "Invalid access token", "timeout": "Timeout connecting to Tibber" }, diff --git a/homeassistant/components/tibber/translations/it.json b/homeassistant/components/tibber/translations/it.json index 3a5548360cf7cb..fcaf76721fdb8a 100644 --- a/homeassistant/components/tibber/translations/it.json +++ b/homeassistant/components/tibber/translations/it.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Un account Tibber \u00e8 gi\u00e0 configurato." + "already_configured": "Il servizio \u00e8 gi\u00e0 configurato" }, "error": { - "connection_error": "Errore durante la connessione a Tibber", + "connection_error": "Impossibile connettersi", "invalid_access_token": "Token di accesso non valido", "timeout": "Tempo scaduto per la connessione a Tibber" }, diff --git a/homeassistant/components/tibber/translations/no.json b/homeassistant/components/tibber/translations/no.json index 34e078f54677ab..bdfaa580365492 100644 --- a/homeassistant/components/tibber/translations/no.json +++ b/homeassistant/components/tibber/translations/no.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "En Tibber-konto er allerede konfigurert." + "already_configured": "Tjenesten er allerede konfigurert" }, "error": { - "connection_error": "Feil ved tilkobling til Tibber", + "connection_error": "Tilkobling mislyktes.", "invalid_access_token": "Ugyldig tilgangstoken", "timeout": "Tidsavbrudd for tilkobling til Tibber" }, diff --git a/homeassistant/components/tibber/translations/pl.json b/homeassistant/components/tibber/translations/pl.json index 8ef963583015bb..d69572b4a42b07 100644 --- a/homeassistant/components/tibber/translations/pl.json +++ b/homeassistant/components/tibber/translations/pl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Konto jest ju\u017c skonfigurowane." + "already_configured": "Konto jest ju\u017c skonfigurowane" }, "error": { "connection_error": "B\u0142\u0105d po\u0142\u0105czenia z Tibber.", - "invalid_access_token": "Niepoprawny token dost\u0119pu.", + "invalid_access_token": "Niepoprawny token dost\u0119pu", "timeout": "Przekroczono limit czasu \u0142\u0105czenia z Tibber." }, "step": { diff --git a/homeassistant/components/tibber/translations/ru.json b/homeassistant/components/tibber/translations/ru.json index 715fcf1179ae75..294c0c75ac80b2 100644 --- a/homeassistant/components/tibber/translations/ru.json +++ b/homeassistant/components/tibber/translations/ru.json @@ -1,10 +1,10 @@ { "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_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": { - "connection_error": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\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_access_token": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430.", "timeout": "\u0418\u0441\u0442\u0435\u043a\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f." }, diff --git a/homeassistant/components/tibber/translations/zh-Hant.json b/homeassistant/components/tibber/translations/zh-Hant.json index 0521ff792664a4..470def61daf6f0 100644 --- a/homeassistant/components/tibber/translations/zh-Hant.json +++ b/homeassistant/components/tibber/translations/zh-Hant.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "already_configured": "Tibber \u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3002" + "already_configured": "\u670d\u52d9\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { - "connection_error": "\u9023\u7dda\u81f3 Tibber \u932f\u8aa4", + "connection_error": "\u9023\u7dda\u5931\u6557", "invalid_access_token": "\u5b58\u53d6\u5bc6\u9470\u7121\u6548", "timeout": "\u9023\u7dda\u81f3 Tibber \u903e\u6642" }, diff --git a/homeassistant/components/tile/config_flow.py b/homeassistant/components/tile/config_flow.py index 15ac70eeb2c3e7..87f58193e9d595 100644 --- a/homeassistant/components/tile/config_flow.py +++ b/homeassistant/components/tile/config_flow.py @@ -47,6 +47,6 @@ async def async_step_user(self, user_input=None): user_input[CONF_USERNAME], user_input[CONF_PASSWORD], session=session ) except TileError: - return await self._show_form({"base": "invalid_credentials"}) + return await self._show_form({"base": "invalid_auth"}) return self.async_create_entry(title=user_input[CONF_USERNAME], data=user_input) diff --git a/homeassistant/components/tile/strings.json b/homeassistant/components/tile/strings.json index 8a1ee9660d918c..cdddbe54f96366 100644 --- a/homeassistant/components/tile/strings.json +++ b/homeassistant/components/tile/strings.json @@ -10,10 +10,10 @@ } }, "error": { - "invalid_credentials": "Invalid Tile credentials provided." + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" }, "abort": { - "already_configured": "This Tile account is already registered." + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" } }, "options": { diff --git a/homeassistant/components/tile/translations/de.json b/homeassistant/components/tile/translations/de.json index b76312d957f741..dfc968eb066552 100644 --- a/homeassistant/components/tile/translations/de.json +++ b/homeassistant/components/tile/translations/de.json @@ -5,7 +5,18 @@ "data": { "password": "Passwort", "username": "E-Mail Adresse" - } + }, + "title": "Kachel konfigurieren" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "show_inactive": "Inaktive Kacheln anzeigen" + }, + "title": "Kachel konfigurieren" } } } diff --git a/homeassistant/components/todoist/calendar.py b/homeassistant/components/todoist/calendar.py index 548cac6da92066..f81a4a7249a8fa 100644 --- a/homeassistant/components/todoist/calendar.py +++ b/homeassistant/components/todoist/calendar.py @@ -262,14 +262,13 @@ def device_state_attributes(self): # No tasks, we don't REALLY need to show anything. return None - attributes = {} - attributes[DUE_TODAY] = self.data.event[DUE_TODAY] - attributes[OVERDUE] = self.data.event[OVERDUE] - attributes[ALL_TASKS] = self._cal_data[ALL_TASKS] - attributes[PRIORITY] = self.data.event[PRIORITY] - attributes[LABELS] = self.data.event[LABELS] - - return attributes + return { + DUE_TODAY: self.data.event[DUE_TODAY], + OVERDUE: self.data.event[OVERDUE], + ALL_TASKS: self._cal_data[ALL_TASKS], + PRIORITY: self.data.event[PRIORITY], + LABELS: self.data.event[LABELS], + } class TodoistProjectData: diff --git a/homeassistant/components/tof/sensor.py b/homeassistant/components/tof/sensor.py index 58f50f4899ecfc..d9ad178cab284b 100644 --- a/homeassistant/components/tof/sensor.py +++ b/homeassistant/components/tof/sensor.py @@ -9,14 +9,12 @@ from homeassistant.components import rpi_gpio from homeassistant.components.sensor import PLATFORM_SCHEMA -from homeassistant.const import CONF_NAME +from homeassistant.const import CONF_NAME, LENGTH_MILLIMETERS import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity _LOGGER = logging.getLogger(__name__) -LENGTH_MILLIMETERS = "mm" - CONF_I2C_ADDRESS = "i2c_address" CONF_I2C_BUS = "i2c_bus" CONF_XSHUT = "xshut" diff --git a/homeassistant/components/tomato/device_tracker.py b/homeassistant/components/tomato/device_tracker.py index 873da5a78640e8..ce660a60280502 100644 --- a/homeassistant/components/tomato/device_tracker.py +++ b/homeassistant/components/tomato/device_tracker.py @@ -19,6 +19,7 @@ CONF_USERNAME, CONF_VERIFY_SSL, HTTP_OK, + HTTP_UNAUTHORIZED, ) import homeassistant.helpers.config_validation as cv @@ -111,7 +112,7 @@ def _update_tomato_info(self): self.last_results[param] = json.loads(value.replace("'", '"')) return True - if response.status_code == 401: + if response.status_code == HTTP_UNAUTHORIZED: # Authentication error _LOGGER.exception( "Failed to authenticate, please check your username and password" diff --git a/homeassistant/components/toon/translations/ca.json b/homeassistant/components/toon/translations/ca.json index 89c8c2425160a5..7fc0aef9ec6ace 100644 --- a/homeassistant/components/toon/translations/ca.json +++ b/homeassistant/components/toon/translations/ca.json @@ -3,7 +3,7 @@ "abort": { "already_configured": "L'acord seleccionat ja est\u00e0 configurat.", "authorize_url_fail": "S'ha produ\u00eft un error desconegut al generar l'URL d'autoritzaci\u00f3.", - "authorize_url_timeout": "Temps d'espera durant la generaci\u00f3 de l'URL d'autoritzaci\u00f3 esgotat.", + "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_agreements": "Aquest compte no t\u00e9 pantalles Toon.", "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})" diff --git a/homeassistant/components/toon/translations/es.json b/homeassistant/components/toon/translations/es.json index 28e8a1dcb61646..b6c6e7ad67d89b 100644 --- a/homeassistant/components/toon/translations/es.json +++ b/homeassistant/components/toon/translations/es.json @@ -5,7 +5,8 @@ "authorize_url_fail": "Error desconocido generando una url de autorizaci\u00f3n", "authorize_url_timeout": "Tiempo de espera agotado generando 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_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})" }, "step": { "agreement": { diff --git a/homeassistant/components/toon/translations/fr.json b/homeassistant/components/toon/translations/fr.json index c3384f56319967..caeed852d0a0e9 100644 --- a/homeassistant/components/toon/translations/fr.json +++ b/homeassistant/components/toon/translations/fr.json @@ -5,7 +5,8 @@ "authorize_url_fail": "Erreur inconnue lors de la g\u00e9n\u00e9ration d'une URL d'autorisation.", "authorize_url_timeout": "Timout de g\u00e9n\u00e9ration de l'URL d'autorisation.", "missing_configuration": "The composant n'est pas configur\u00e9. Veuillez vous r\u00e9f\u00e9rer \u00e0 la documentation.", - "no_agreements": "Ce compte n'a pas d'affichages Toon." + "no_agreements": "Ce compte n'a pas d'affichages Toon.", + "no_url_available": "Aucune URL disponible. Pour plus d'informations sur cette erreur, [consultez la section d'aide] ( {docs_url} )" }, "step": { "agreement": { diff --git a/homeassistant/components/toon/translations/ko.json b/homeassistant/components/toon/translations/ko.json index bebd8bb912e472..379058f68d1109 100644 --- a/homeassistant/components/toon/translations/ko.json +++ b/homeassistant/components/toon/translations/ko.json @@ -5,7 +5,8 @@ "authorize_url_fail": "\uc778\uc99d url \uc0dd\uc131\uc5d0 \uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\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_agreements": "\uc774 \uacc4\uc815\uc5d0\ub294 Toon \ub514\uc2a4\ud50c\ub808\uc774\uac00 \uc5c6\uc2b5\ub2c8\ub2e4." + "no_agreements": "\uc774 \uacc4\uc815\uc5d0\ub294 Toon \ub514\uc2a4\ud50c\ub808\uc774\uac00 \uc5c6\uc2b5\ub2c8\ub2e4.", + "no_url_available": "\uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc5d0\ub7ec\uc5d0 \ub300\ud55c \uc815\ubcf4\ub294 \ub3c4\uc6c0\ub9d0 \uc139\uc158\uc744 \ud655\uc778\ud558\uc138\uc694({docs_url})" }, "step": { "agreement": { diff --git a/homeassistant/components/toon/translations/lb.json b/homeassistant/components/toon/translations/lb.json index 5d7095d0c85ee5..6491c66673863c 100644 --- a/homeassistant/components/toon/translations/lb.json +++ b/homeassistant/components/toon/translations/lb.json @@ -5,7 +5,8 @@ "authorize_url_fail": "Onbekannte Feeler beim gener\u00e9ieren vun der Autorisatiouns URL.", "authorize_url_timeout": "Z\u00e4itiwwerschraidung beim erstellen vun der Autorisatioun's URL.", "missing_configuration": "Komponent ass net konfigur\u00e9iert. Folleg der Dokumentatioun.", - "no_agreements": "D\u00ebse Kont huet keen Toon Ecran." + "no_agreements": "D\u00ebse Kont huet keen Toon Ecran.", + "no_url_available": "Keng URL disponibel. Fir Informatiounen iwwert d\u00ebse Feeler, [kuck H\u00ebllef Sektioun]({docs_url})" }, "step": { "agreement": { diff --git a/homeassistant/components/toon/translations/nl.json b/homeassistant/components/toon/translations/nl.json index 69eabaaf28bada..da3ce6d84c7acd 100644 --- a/homeassistant/components/toon/translations/nl.json +++ b/homeassistant/components/toon/translations/nl.json @@ -1,7 +1,8 @@ { "config": { "abort": { - "no_agreements": "Dit account heeft geen Toon schermen." + "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 newline at end of file diff --git a/homeassistant/components/toon/translations/no.json b/homeassistant/components/toon/translations/no.json index 37652c4aee18a1..49103a77b3753c 100644 --- a/homeassistant/components/toon/translations/no.json +++ b/homeassistant/components/toon/translations/no.json @@ -5,7 +5,8 @@ "authorize_url_fail": "Ukjent feil ved generering av autoriseringsadresse.", "authorize_url_timeout": "Tidsavbrudd som genererer autorer URL-adresse.", "missing_configuration": "Komponenten er ikke konfigurert. Vennligst f\u00f8lg dokumentasjonen.", - "no_agreements": "Denne kontoen har ingen Toon skjermer." + "no_agreements": "Denne kontoen har ingen Toon skjermer.", + "no_url_available": "Ingen URL tilgjengelig. For informasjon om denne feilen, [sjekk {docs_url} ] ( {docs_url} )" }, "step": { "agreement": { diff --git a/homeassistant/components/toon/translations/zh-Hant.json b/homeassistant/components/toon/translations/zh-Hant.json index a6d5227afc65e4..020938792d63ee 100644 --- a/homeassistant/components/toon/translations/zh-Hant.json +++ b/homeassistant/components/toon/translations/zh-Hant.json @@ -5,7 +5,8 @@ "authorize_url_fail": "\u7522\u751f\u8a8d\u8b49 URL \u6642\u767c\u751f\u672a\u77e5\u932f\u8aa4", "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_agreements": "\u6b64\u5e33\u865f\u4e26\u672a\u64c1\u6709 Toon \u986f\u793a\u8a2d\u5099\u3002" + "no_agreements": "\u6b64\u5e33\u865f\u4e26\u672a\u64c1\u6709 Toon \u986f\u793a\u8a2d\u5099\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})" }, "step": { "agreement": { diff --git a/homeassistant/components/totalconnect/config_flow.py b/homeassistant/components/totalconnect/config_flow.py index 03ddd0a432a145..3bc15a441d13a5 100644 --- a/homeassistant/components/totalconnect/config_flow.py +++ b/homeassistant/components/totalconnect/config_flow.py @@ -38,7 +38,7 @@ async def async_step_user(self, user_input=None): data={CONF_USERNAME: username, CONF_PASSWORD: password}, ) # authentication failed / invalid - errors["base"] = "login" + errors["base"] = "invalid_auth" data_schema = vol.Schema( {vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str} diff --git a/homeassistant/components/totalconnect/strings.json b/homeassistant/components/totalconnect/strings.json index eb7a91ef4389ed..7b306554b7be6f 100644 --- a/homeassistant/components/totalconnect/strings.json +++ b/homeassistant/components/totalconnect/strings.json @@ -10,10 +10,10 @@ } }, "error": { - "login": "Login error: please check your username & password" + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" }, "abort": { - "already_configured": "Account already configured" + "already_configured": "[%key:common::config_flow::abort::already_configured_account%]" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/totalconnect/translations/ca.json b/homeassistant/components/totalconnect/translations/ca.json index ca9e6d66e2ea74..f217eb4f6bcf07 100644 --- a/homeassistant/components/totalconnect/translations/ca.json +++ b/homeassistant/components/totalconnect/translations/ca.json @@ -4,6 +4,7 @@ "already_configured": "El compte ja ha estat configurat" }, "error": { + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", "login": "Error d'inici de sessi\u00f3: comprova el nom d'usuari i la contrasenya" }, "step": { diff --git a/homeassistant/components/totalconnect/translations/el.json b/homeassistant/components/totalconnect/translations/el.json new file mode 100644 index 00000000000000..161d646f430ec8 --- /dev/null +++ b/homeassistant/components/totalconnect/translations/el.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "\u039c\u03b7 \u03ad\u03b3\u03ba\u03c5\u03c1\u03b7 \u03b1\u03c5\u03b8\u03b5\u03bd\u03c4\u03b9\u03ba\u03bf\u03c0\u03bf\u03af\u03b7\u03c3\u03b7" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/totalconnect/translations/en.json b/homeassistant/components/totalconnect/translations/en.json index 3486d42ae3d92d..1174d3e668753a 100644 --- a/homeassistant/components/totalconnect/translations/en.json +++ b/homeassistant/components/totalconnect/translations/en.json @@ -1,9 +1,10 @@ { "config": { "abort": { - "already_configured": "Account already configured" + "already_configured": "Account is already configured" }, "error": { + "invalid_auth": "Invalid authentication", "login": "Login error: please check your username & password" }, "step": { diff --git a/homeassistant/components/totalconnect/translations/es.json b/homeassistant/components/totalconnect/translations/es.json index 18b45f390b2b11..d308b2c7170d7d 100644 --- a/homeassistant/components/totalconnect/translations/es.json +++ b/homeassistant/components/totalconnect/translations/es.json @@ -4,6 +4,7 @@ "already_configured": "La cuenta ya est\u00e1 configurada" }, "error": { + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", "login": "Error de inicio de sesi\u00f3n: comprueba tu nombre de usuario y contrase\u00f1a" }, "step": { diff --git a/homeassistant/components/totalconnect/translations/et.json b/homeassistant/components/totalconnect/translations/et.json new file mode 100644 index 00000000000000..2227b7442a79c6 --- /dev/null +++ b/homeassistant/components/totalconnect/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "invalid_auth": "Tuvastamise viga" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/totalconnect/translations/fr.json b/homeassistant/components/totalconnect/translations/fr.json index ef7cf7f38fac23..cf841328d9cabb 100644 --- a/homeassistant/components/totalconnect/translations/fr.json +++ b/homeassistant/components/totalconnect/translations/fr.json @@ -4,6 +4,7 @@ "already_configured": "Compte d\u00e9j\u00e0 configur\u00e9" }, "error": { + "invalid_auth": "Authentification invalide", "login": "Erreur de connexion: veuillez v\u00e9rifier votre nom d'utilisateur et votre mot de passe" }, "step": { diff --git a/homeassistant/components/totalconnect/translations/it.json b/homeassistant/components/totalconnect/translations/it.json index 455b8a7967feb7..369b739e84146c 100644 --- a/homeassistant/components/totalconnect/translations/it.json +++ b/homeassistant/components/totalconnect/translations/it.json @@ -1,9 +1,10 @@ { "config": { "abort": { - "already_configured": "Account gi\u00e0 configurato" + "already_configured": "L'account \u00e8 gi\u00e0 configurato" }, "error": { + "invalid_auth": "Autenticazione non valida", "login": "Errore di accesso: si prega di controllare il nome utente e la password" }, "step": { diff --git a/homeassistant/components/totalconnect/translations/no.json b/homeassistant/components/totalconnect/translations/no.json index c312f98f3d2a8d..62e4fffb1fdc06 100644 --- a/homeassistant/components/totalconnect/translations/no.json +++ b/homeassistant/components/totalconnect/translations/no.json @@ -4,6 +4,7 @@ "already_configured": "Kontoen er allerede konfigurert" }, "error": { + "invalid_auth": "Ugyldig godkjenning", "login": "P\u00e5loggingsfeil: Vennligst sjekk brukernavnet ditt og passordet ditt" }, "step": { diff --git a/homeassistant/components/totalconnect/translations/ru.json b/homeassistant/components/totalconnect/translations/ru.json index a3de1ee4555ef7..29a070d841ed39 100644 --- a/homeassistant/components/totalconnect/translations/ru.json +++ b/homeassistant/components/totalconnect/translations/ru.json @@ -4,6 +4,7 @@ "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": { + "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "login": "\u041e\u0448\u0438\u0431\u043a\u0430 \u0432\u0445\u043e\u0434\u0430: \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043b\u043e\u0433\u0438\u043d \u0438 \u043f\u0430\u0440\u043e\u043b\u044c." }, "step": { diff --git a/homeassistant/components/totalconnect/translations/zh-Hant.json b/homeassistant/components/totalconnect/translations/zh-Hant.json index 0424db3cdc3cec..b9416b0fbf343e 100644 --- a/homeassistant/components/totalconnect/translations/zh-Hant.json +++ b/homeassistant/components/totalconnect/translations/zh-Hant.json @@ -4,6 +4,7 @@ "already_configured": "\u5e33\u865f\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", "login": "\u767b\u5165\u932f\u8aa4\uff1a\u8acb\u78ba\u8a8d\u96fb\u5b50\u90f5\u4ef6\u8207\u5bc6\u78bc" }, "step": { diff --git a/homeassistant/components/tplink/common.py b/homeassistant/components/tplink/common.py index 7ecced32341d0c..6c9d1f8b2e8a9f 100644 --- a/homeassistant/components/tplink/common.py +++ b/homeassistant/components/tplink/common.py @@ -119,7 +119,14 @@ def get_static_devices(config_data) -> SmartDevices: elif type_ == CONF_SWITCH: switches.append(SmartPlug(host)) elif type_ == CONF_STRIP: - for plug in SmartStrip(host).plugs.values(): + try: + ss_host = SmartStrip(host) + except SmartDeviceException as sde: + _LOGGER.error( + "Failed to setup SmartStrip at %s: %s; not retrying", host, sde + ) + continue + for plug in ss_host.plugs.values(): switches.append(plug) # Dimmers need to be defined as smart plugs to work correctly. elif type_ == CONF_DIMMER: diff --git a/homeassistant/components/tplink/strings.json b/homeassistant/components/tplink/strings.json index cbb896536072a9..a10c44b925262c 100644 --- a/homeassistant/components/tplink/strings.json +++ b/homeassistant/components/tplink/strings.json @@ -6,8 +6,8 @@ } }, "abort": { - "single_instance_allowed": "Only a single configuration is necessary.", - "no_devices_found": "No TP-Link devices found on the network." + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" } } } diff --git a/homeassistant/components/tplink/translations/ca.json b/homeassistant/components/tplink/translations/ca.json index 62baf289fd434a..69dfc1b4b9d6c1 100644 --- a/homeassistant/components/tplink/translations/ca.json +++ b/homeassistant/components/tplink/translations/ca.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "No s'han trobat dispositius TP-Link a la xarxa.", - "single_instance_allowed": "Nom\u00e9s cal una \u00fanica configuraci\u00f3." + "no_devices_found": "No s'han trobat dispositius a la xarxa", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "step": { "confirm": { diff --git a/homeassistant/components/tplink/translations/en.json b/homeassistant/components/tplink/translations/en.json index 3c1f7da52e0cbe..1105f6a383bf27 100644 --- a/homeassistant/components/tplink/translations/en.json +++ b/homeassistant/components/tplink/translations/en.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "No TP-Link devices found on the network.", - "single_instance_allowed": "Only a single configuration is necessary." + "no_devices_found": "No devices found on the network", + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "step": { "confirm": { diff --git a/homeassistant/components/tplink/translations/it.json b/homeassistant/components/tplink/translations/it.json index d5fc0a46a00ebb..8940b1c8ee6180 100644 --- a/homeassistant/components/tplink/translations/it.json +++ b/homeassistant/components/tplink/translations/it.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Nessun dispositivo TP-Link trovato in rete.", - "single_instance_allowed": "\u00c8 necessaria una sola configurazione." + "no_devices_found": "Nessun dispositivo trovato sulla rete", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "step": { "confirm": { diff --git a/homeassistant/components/tplink/translations/no.json b/homeassistant/components/tplink/translations/no.json index d480a5d996dbe9..1d1d624ab40ecf 100644 --- a/homeassistant/components/tplink/translations/no.json +++ b/homeassistant/components/tplink/translations/no.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Ingen TP-Link enheter funnet p\u00e5 nettverket.", - "single_instance_allowed": "Kun en konfigurasjon av TP-Link er n\u00f8dvendig." + "no_devices_found": "Ingen enheter funnet p\u00e5 nettverket", + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "step": { "confirm": { diff --git a/homeassistant/components/tplink/translations/ru.json b/homeassistant/components/tplink/translations/ru.json index 7f84067d9a2821..4df755bee4f09c 100644 --- a/homeassistant/components/tplink/translations/ru.json +++ b/homeassistant/components/tplink/translations/ru.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 TP-Link \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 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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." }, "step": { "confirm": { diff --git a/homeassistant/components/tplink/translations/zh-Hant.json b/homeassistant/components/tplink/translations/zh-Hant.json index 9fafcbbce7d676..e88d982b8a1198 100644 --- a/homeassistant/components/tplink/translations/zh-Hant.json +++ b/homeassistant/components/tplink/translations/zh-Hant.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "\u5728\u7db2\u8def\u4e0a\u627e\u4e0d\u5230 TP-Link \u8a2d\u5099\u3002", - "single_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u6b21\u5373\u53ef\u3002" + "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u8a2d\u5099", + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002" }, "step": { "confirm": { diff --git a/homeassistant/components/traccar/strings.json b/homeassistant/components/traccar/strings.json index 8574f4f34f11fe..aef269defcb3af 100644 --- a/homeassistant/components/traccar/strings.json +++ b/homeassistant/components/traccar/strings.json @@ -7,7 +7,7 @@ } }, "abort": { - "one_instance_allowed": "Only a single instance is necessary.", + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive messages from Traccar." }, "create_entry": { diff --git a/homeassistant/components/traccar/translations/ca.json b/homeassistant/components/traccar/translations/ca.json index 2fa9369055391e..8dd1cecf90a7ce 100644 --- a/homeassistant/components/traccar/translations/ca.json +++ b/homeassistant/components/traccar/translations/ca.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "La inst\u00e0ncia de Home Assistant ha de ser accessible des d'Internet per rebre missatges de Traccar.", - "one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia." + "one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia.", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "create_entry": { "default": "Per enviar esdeveniments a Home Assistant, haur\u00e0s de configurar l'opci\u00f3 webhook de Traccar.\n\nUtilitza el seg\u00fcent enlla\u00e7: `{webhook_url}`\n\nConsulta la [documentaci\u00f3]({docs_url}) per a m\u00e9s detalls." diff --git a/homeassistant/components/traccar/translations/el.json b/homeassistant/components/traccar/translations/el.json new file mode 100644 index 00000000000000..aecb2ee553fa42 --- /dev/null +++ b/homeassistant/components/traccar/translations/el.json @@ -0,0 +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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/traccar/translations/en.json b/homeassistant/components/traccar/translations/en.json index 1e6b286def2f58..2a9c92cc1d0386 100644 --- a/homeassistant/components/traccar/translations/en.json +++ b/homeassistant/components/traccar/translations/en.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive messages from Traccar.", - "one_instance_allowed": "Only a single instance is necessary." + "one_instance_allowed": "Only a single instance is necessary.", + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "create_entry": { "default": "To send events to Home Assistant, you will need to setup the webhook feature in Traccar.\n\nUse the following url: `{webhook_url}`\n\nSee [the documentation]({docs_url}) for further details." diff --git a/homeassistant/components/traccar/translations/es.json b/homeassistant/components/traccar/translations/es.json index b9cd4b9def9649..11933eafe12d7c 100644 --- a/homeassistant/components/traccar/translations/es.json +++ b/homeassistant/components/traccar/translations/es.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Su instancia de Home Assistant debe ser accesible desde Internet para recibir mensajes de Traccar.", - "one_instance_allowed": "S\u00f3lo se necesita una instancia." + "one_instance_allowed": "S\u00f3lo se necesita una instancia.", + "single_instance_allowed": "Ya est\u00e1 configurado. S\u00f3lo es posible una \u00fanica configuraci\u00f3n." }, "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." diff --git a/homeassistant/components/traccar/translations/et.json b/homeassistant/components/traccar/translations/et.json new file mode 100644 index 00000000000000..e8b5eae41495c6 --- /dev/null +++ b/homeassistant/components/traccar/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/traccar/translations/fr.json b/homeassistant/components/traccar/translations/fr.json index faf64359f0d1ab..e000bbaaac8f5f 100644 --- a/homeassistant/components/traccar/translations/fr.json +++ b/homeassistant/components/traccar/translations/fr.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Votre instance de Home Assistant doit \u00eatre accessible depuis Internet pour recevoir les messages de Traccar.", - "one_instance_allowed": "Une seule instance est n\u00e9cessaire." + "one_instance_allowed": "Une seule instance est n\u00e9cessaire.", + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." }, "create_entry": { "default": "Pour envoyer des \u00e9v\u00e9nements \u00e0 Home Assistant, vous devez configurer la fonction Webhook dans Traccar. \n\n Utilisez l'URL suivante: ` {webhook_url} ` \n\n Voir [la documentation] ( {docs_url} ) pour plus de d\u00e9tails." diff --git a/homeassistant/components/traccar/translations/it.json b/homeassistant/components/traccar/translations/it.json index 54e46e8d31d240..86e65ace3db3ee 100644 --- a/homeassistant/components/traccar/translations/it.json +++ b/homeassistant/components/traccar/translations/it.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "La tua istanza di Home Assistant deve essere accessibile da Internet per ricevere messaggi da Traccar.", - "one_instance_allowed": "\u00c8 necessaria solo una singola istanza." + "one_instance_allowed": "\u00c8 necessaria solo una singola istanza.", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "create_entry": { "default": "Per inviare eventi a Home Assistant, \u00e8 necessario configurare la funzionalit\u00e0 webhook in Traccar.\n\nUtilizzare l'URL seguente: `{webhook_url}`\n\nPer ulteriori dettagli, vedere [la documentazione]({docs_url}) ." diff --git a/homeassistant/components/traccar/translations/lb.json b/homeassistant/components/traccar/translations/lb.json index a4d1da866e03e2..3ccacf127e9ade 100644 --- a/homeassistant/components/traccar/translations/lb.json +++ b/homeassistant/components/traccar/translations/lb.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "\u00c4r Home Assistant Instanz muss iwwert Internet accessibel si fir Traccar Noriichten z'empf\u00e4nken.", - "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg." + "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg.", + "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech." }, "create_entry": { "default": "Fir Evenementer un Home Assistant ze sch\u00e9cken, muss den Webhook Feature am Traccar ageriicht ginn.\n\nF\u00ebllt folgend Informatiounen aus:\n\n- URL: `{webhook_url}`\n- Method: POST\n\nLiest [Dokumentatioun]({docs_url}) fir w\u00e9ider Informatiounen." diff --git a/homeassistant/components/traccar/translations/no.json b/homeassistant/components/traccar/translations/no.json index 53e6500e70e541..b471cbb2187bdc 100644 --- a/homeassistant/components/traccar/translations/no.json +++ b/homeassistant/components/traccar/translations/no.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Din Home Assistant-forekomst m\u00e5 v\u00e6re tilgjengelig fra Internett for \u00e5 motta meldinger fra Traccar.", - "one_instance_allowed": "Kun en forekomst er n\u00f8dvendig." + "one_instance_allowed": "Kun en forekomst er n\u00f8dvendig.", + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "create_entry": { "default": "Hvis du vil sende hendelser til Home Assistant, m\u00e5 du konfigurere webhook-funksjonen i Traccar.\n\nBruk f\u00f8lgende URL-adresse: `{webhook_url}`\n\nSe [dokumentasjonen]({docs_url}) for mer informasjon." diff --git a/homeassistant/components/traccar/translations/ru.json b/homeassistant/components/traccar/translations/ru.json index a2979379e18a76..f9a23647422dd5 100644 --- a/homeassistant/components/traccar/translations/ru.json +++ b/homeassistant/components/traccar/translations/ru.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 Traccar.", - "one_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "one_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e." }, "create_entry": { "default": "\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 Home Assistant \u0412\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Webhook \u0434\u043b\u044f Traccar.\n\n\u0414\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e:\n\n- URL: `{webhook_url}`\n\n\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]({docs_url}) \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." diff --git a/homeassistant/components/traccar/translations/zh-Hant.json b/homeassistant/components/traccar/translations/zh-Hant.json index 5135320f631cbb..b14b5f12b9fdb7 100644 --- a/homeassistant/components/traccar/translations/zh-Hant.json +++ b/homeassistant/components/traccar/translations/zh-Hant.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Home Assistant \u8a2d\u5099\u5fc5\u9808\u80fd\u5920\u7531\u7db2\u969b\u7db2\u8def\u5b58\u53d6\uff0c\u65b9\u80fd\u63a5\u53d7 Traccar \u8a0a\u606f\u3002", - "one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u5373\u53ef\u3002" + "one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u5373\u53ef\u3002", + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002" }, "create_entry": { "default": "\u6b32\u50b3\u9001\u4e8b\u4ef6\u81f3 Home Assistant\uff0c\u5c07\u9700\u65bc Traccar \u5167\u8a2d\u5b9a webhook \u529f\u80fd\u3002\n\n\u8acb\u4f7f\u7528 url: `{webhook_url}`\n\n\u8acb\u53c3\u95b1 [\u6587\u4ef6]({docs_url})\u4ee5\u4e86\u89e3\u66f4\u8a73\u7d30\u8cc7\u6599\u3002" diff --git a/homeassistant/components/tradfri/cover.py b/homeassistant/components/tradfri/cover.py index 2d99de7756ac75..72597637bd3e0b 100644 --- a/homeassistant/components/tradfri/cover.py +++ b/homeassistant/components/tradfri/cover.py @@ -31,8 +31,7 @@ def __init__(self, device, api, gateway_id): @property def device_state_attributes(self): """Return the state attributes.""" - attr = {ATTR_MODEL: self._device.device_info.model_number} - return attr + return {ATTR_MODEL: self._device.device_info.model_number} @property def current_cover_position(self): diff --git a/homeassistant/components/tradfri/strings.json b/homeassistant/components/tradfri/strings.json index 33a3f059098f39..45850fd639a0eb 100644 --- a/homeassistant/components/tradfri/strings.json +++ b/homeassistant/components/tradfri/strings.json @@ -12,12 +12,12 @@ }, "error": { "invalid_key": "Failed to register with provided key. If this keeps happening, try restarting the gateway.", - "cannot_connect": "Unable to connect to the gateway.", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "timeout": "Timeout validating the code." }, "abort": { - "already_configured": "Bridge is already configured.", - "already_in_progress": "Bridge configuration is already in progress." + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/tradfri/translations/ca.json b/homeassistant/components/tradfri/translations/ca.json index cee54ccca79eae..a1ab94a09204c9 100644 --- a/homeassistant/components/tradfri/translations/ca.json +++ b/homeassistant/components/tradfri/translations/ca.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "L'enlla\u00e7 ja est\u00e0 configurat", - "already_in_progress": "La configuraci\u00f3 de l'enlla\u00e7 ja est\u00e0 en curs." + "already_configured": "El dispositiu ja est\u00e0 configurat", + "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs" }, "error": { - "cannot_connect": "No s'ha pogut connectar a la passarel\u00b7la d'enlla\u00e7", + "cannot_connect": "Ha fallat la connexi\u00f3", "invalid_key": "Ha fallat el registre amb la clau proporcionada. Si aix\u00f2 continua passant, intenta reiniciar la passarel\u00b7la d'enlla\u00e7.", "timeout": "S'ha acabat el temps d'espera durant la validaci\u00f3 del codi." }, diff --git a/homeassistant/components/tradfri/translations/en.json b/homeassistant/components/tradfri/translations/en.json index f1ee9b9238a865..9035496a0cc4f8 100644 --- a/homeassistant/components/tradfri/translations/en.json +++ b/homeassistant/components/tradfri/translations/en.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Bridge is already configured.", - "already_in_progress": "Bridge configuration is already in progress." + "already_configured": "Device is already configured", + "already_in_progress": "Configuration flow is already in progress" }, "error": { - "cannot_connect": "Unable to connect to the gateway.", + "cannot_connect": "Failed to connect", "invalid_key": "Failed to register with provided key. If this keeps happening, try restarting the gateway.", "timeout": "Timeout validating the code." }, diff --git a/homeassistant/components/tradfri/translations/it.json b/homeassistant/components/tradfri/translations/it.json index 4fe9b2a7c9c841..c05098d97d209f 100644 --- a/homeassistant/components/tradfri/translations/it.json +++ b/homeassistant/components/tradfri/translations/it.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Bridge gi\u00e0 configurato.", - "already_in_progress": "La configurazione del Bridge \u00e8 gi\u00e0 in corso." + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso" }, "error": { - "cannot_connect": "Impossibile connettersi al gateway.", + "cannot_connect": "Impossibile connettersi", "invalid_key": "Impossibile registrarsi con la chiave fornita. Se questo continua a succedere, prova a riavviare il gateway.", "timeout": "Tempo scaduto per la validazione del codice." }, diff --git a/homeassistant/components/tradfri/translations/no.json b/homeassistant/components/tradfri/translations/no.json index 39e66e48a78934..3138c472531841 100644 --- a/homeassistant/components/tradfri/translations/no.json +++ b/homeassistant/components/tradfri/translations/no.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Bridge er allerede konfigurert.", - "already_in_progress": "Brokonfigurasjon er allerede i gang." + "already_configured": "Enheten er allerede konfigurert", + "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede" }, "error": { - "cannot_connect": "Kan ikke koble til gatewayen.", + "cannot_connect": "Tilkobling mislyktes.", "invalid_key": "Kunne ikke registrere med gitt n\u00f8kkel. Hvis dette fortsetter, pr\u00f8v \u00e5 starte gatewayen p\u00e5 nytt.", "timeout": "Tidsavbrudd ved validering av kode." }, diff --git a/homeassistant/components/tradfri/translations/ru.json b/homeassistant/components/tradfri/translations/ru.json index 03077907aab00c..adb2b3aa18f44f 100644 --- a/homeassistant/components/tradfri/translations/ru.json +++ b/homeassistant/components/tradfri/translations/ru.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", - "already_in_progress": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f." + "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." }, "error": { - "cannot_connect": "\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.", + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "invalid_key": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0441 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u043c \u043a\u043b\u044e\u0447\u043e\u043c. \u0415\u0441\u043b\u0438 \u044d\u0442\u043e \u043f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0441\u044f, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u0448\u043b\u044e\u0437.", "timeout": "\u0418\u0441\u0442\u0435\u043a\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 \u043a\u043e\u0434\u0430." }, diff --git a/homeassistant/components/tradfri/translations/zh-Hant.json b/homeassistant/components/tradfri/translations/zh-Hant.json index adbb0e148f45dd..e922bb26080c5b 100644 --- a/homeassistant/components/tradfri/translations/zh-Hant.json +++ b/homeassistant/components/tradfri/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Bridge \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3002", - "already_in_progress": "Bridge \u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d\u3002" + "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d" }, "error": { "cannot_connect": "\u7121\u6cd5\u9023\u7dda\u81f3\u9598\u9053\u5668\u3002", diff --git a/homeassistant/components/trafikverket_weatherstation/sensor.py b/homeassistant/components/trafikverket_weatherstation/sensor.py index 958adfd6915f29..bb1bad67f829b6 100644 --- a/homeassistant/components/trafikverket_weatherstation/sensor.py +++ b/homeassistant/components/trafikverket_weatherstation/sensor.py @@ -17,6 +17,7 @@ DEGREE, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_TEMPERATURE, + LENGTH_MILLIMETERS, PERCENTAGE, SPEED_METERS_PER_SECOND, TEMP_CELSIUS, @@ -97,7 +98,7 @@ ], "precipitation_amount": [ "Precipitation amount", - "mm", + LENGTH_MILLIMETERS, "precipitation_amount", "mdi:cup-water", None, diff --git a/homeassistant/components/transmission/config_flow.py b/homeassistant/components/transmission/config_flow.py index 56ed3081b6304f..890a0f3dfa9216 100644 --- a/homeassistant/components/transmission/config_flow.py +++ b/homeassistant/components/transmission/config_flow.py @@ -64,13 +64,12 @@ async def async_step_user(self, user_input=None): if entry.data[CONF_NAME] == user_input[CONF_NAME]: errors[CONF_NAME] = "name_exists" break - try: await get_api(self.hass, user_input) except AuthenticationError: - errors[CONF_USERNAME] = "wrong_credentials" - errors[CONF_PASSWORD] = "wrong_credentials" + errors[CONF_USERNAME] = "invalid_auth" + errors[CONF_PASSWORD] = "invalid_auth" except (CannotConnect, UnknownError): errors["base"] = "cannot_connect" diff --git a/homeassistant/components/transmission/strings.json b/homeassistant/components/transmission/strings.json index 66a5b757818d4e..81725ad7d16d01 100644 --- a/homeassistant/components/transmission/strings.json +++ b/homeassistant/components/transmission/strings.json @@ -4,7 +4,7 @@ "user": { "title": "Setup Transmission Client", "data": { - "name": "Name", + "name": "[%key:common::config_flow::data::name%]", "host": "[%key:common::config_flow::data::host%]", "username": "[%key:common::config_flow::data::username%]", "password": "[%key:common::config_flow::data::password%]", @@ -13,12 +13,12 @@ } }, "error": { - "name_exists": "Name already exists", - "wrong_credentials": "Wrong username or password", - "cannot_connect": "Unable to Connect to host" + "name_exists": "[%key:common::config_flow::data::name%] already exists", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, "abort": { - "already_configured": "Host is already configured." + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } }, "options": { @@ -33,4 +33,4 @@ } } } -} \ No newline at end of file +} diff --git a/homeassistant/components/transmission/translations/de.json b/homeassistant/components/transmission/translations/de.json index 66d6caf1b1732f..a09fbba4e856cd 100644 --- a/homeassistant/components/transmission/translations/de.json +++ b/homeassistant/components/transmission/translations/de.json @@ -25,6 +25,7 @@ "step": { "init": { "data": { + "limit": "Limit", "order": "Reihenfolge", "scan_interval": "Aktualisierungsfrequenz" }, diff --git a/homeassistant/components/transmission/translations/et.json b/homeassistant/components/transmission/translations/et.json new file mode 100644 index 00000000000000..d7f39519aad421 --- /dev/null +++ b/homeassistant/components/transmission/translations/et.json @@ -0,0 +1,13 @@ +{ + "config": { + "step": { + "user": { + "data": { + "name": "Nimi", + "password": "Salas\u00f5na", + "username": "Kasutajanimi" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py index 0f758d4a2ebee0..7ee4922677cbad 100644 --- a/homeassistant/components/tts/__init__.py +++ b/homeassistant/components/tts/__init__.py @@ -11,6 +11,7 @@ from aiohttp import web import mutagen +from mutagen.id3 import ID3FileType, TextFrame as ID3Text import voluptuous as vol from homeassistant.components.http import HomeAssistantView @@ -465,11 +466,16 @@ def write_tags(filename, data, provider, message, language, options): artist = options.get("voice") try: - tts_file = mutagen.File(data_bytes, easy=True) + tts_file = mutagen.File(data_bytes) if tts_file is not None: - tts_file["artist"] = artist - tts_file["album"] = album - tts_file["title"] = message + if isinstance(tts_file, ID3FileType): + tts_file["artist"] = ID3Text(encoding=3, text=artist) + tts_file["album"] = ID3Text(encoding=3, text=album) + tts_file["title"] = ID3Text(encoding=3, text=message) + else: + tts_file["artist"] = artist + tts_file["album"] = album + tts_file["title"] = message tts_file.save(data_bytes) except mutagen.MutagenError as err: _LOGGER.error("ID3 tag error: %s", err) diff --git a/homeassistant/components/tuya/config_flow.py b/homeassistant/components/tuya/config_flow.py index c905334aac78d5..f00396d4405b2f 100644 --- a/homeassistant/components/tuya/config_flow.py +++ b/homeassistant/components/tuya/config_flow.py @@ -22,8 +22,8 @@ } ) -RESULT_AUTH_FAILED = "auth_failed" -RESULT_CONN_ERROR = "conn_error" +RESULT_AUTH_FAILED = "invalid_auth" +RESULT_CONN_ERROR = "cannot_connect" RESULT_SUCCESS = "success" RESULT_LOG_MESSAGE = { diff --git a/homeassistant/components/tuya/strings.json b/homeassistant/components/tuya/strings.json index 891eeb4764343a..08123db3a36a6c 100644 --- a/homeassistant/components/tuya/strings.json +++ b/homeassistant/components/tuya/strings.json @@ -14,12 +14,12 @@ } }, "abort": { - "auth_failed": "[%key:common::config_flow::error::invalid_auth%]", - "conn_error": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" }, "error": { - "auth_failed": "[%key:common::config_flow::error::invalid_auth%]" + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" } } } diff --git a/homeassistant/components/tuya/translations/ca.json b/homeassistant/components/tuya/translations/ca.json index 20709b61a98ebb..ca88e8b54a42a7 100644 --- a/homeassistant/components/tuya/translations/ca.json +++ b/homeassistant/components/tuya/translations/ca.json @@ -2,11 +2,14 @@ "config": { "abort": { "auth_failed": "Autenticaci\u00f3 inv\u00e0lida", + "cannot_connect": "Ha fallat la connexi\u00f3", "conn_error": "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": { - "auth_failed": "Autenticaci\u00f3 inv\u00e0lida" + "auth_failed": "Autenticaci\u00f3 inv\u00e0lida", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida" }, "flow_title": "Configuraci\u00f3 de Tuya", "step": { diff --git a/homeassistant/components/tuya/translations/en.json b/homeassistant/components/tuya/translations/en.json index c66eb2d274fbba..8b013c6c0627c2 100644 --- a/homeassistant/components/tuya/translations/en.json +++ b/homeassistant/components/tuya/translations/en.json @@ -2,11 +2,14 @@ "config": { "abort": { "auth_failed": "Invalid authentication", + "cannot_connect": "Failed to connect", "conn_error": "Failed to connect", + "invalid_auth": "Invalid authentication", "single_instance_allowed": "Already configured. Only a single configuration possible." }, "error": { - "auth_failed": "Invalid authentication" + "auth_failed": "Invalid authentication", + "invalid_auth": "Invalid authentication" }, "flow_title": "Tuya configuration", "step": { diff --git a/homeassistant/components/tuya/translations/es.json b/homeassistant/components/tuya/translations/es.json index f0a607331f39de..b11fb8101b0eca 100644 --- a/homeassistant/components/tuya/translations/es.json +++ b/homeassistant/components/tuya/translations/es.json @@ -2,11 +2,14 @@ "config": { "abort": { "auth_failed": "Autenticaci\u00f3n no v\u00e1lida", + "cannot_connect": "No se pudo conectar", "conn_error": "No se pudo conectar", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida", "single_instance_allowed": "Ya est\u00e1 configurado. S\u00f3lo es posible una \u00fanica configuraci\u00f3n." }, "error": { - "auth_failed": "Autenticaci\u00f3n no v\u00e1lida" + "auth_failed": "Autenticaci\u00f3n no v\u00e1lida", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida" }, "flow_title": "Configuraci\u00f3n Tuya", "step": { diff --git a/homeassistant/components/tuya/translations/et.json b/homeassistant/components/tuya/translations/et.json new file mode 100644 index 00000000000000..7d4781fa23e05e --- /dev/null +++ b/homeassistant/components/tuya/translations/et.json @@ -0,0 +1,27 @@ +{ + "config": { + "abort": { + "auth_failed": "Viga tuvastamisel", + "cannot_connect": "\u00dchendamine nurjus", + "conn_error": "\u00dchendamine eba\u00f5nnestus", + "invalid_auth": "Tuvastamise viga", + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks sidumine." + }, + "error": { + "auth_failed": "Vigane tuvastamine", + "invalid_auth": "Tuvastamise viga" + }, + "flow_title": "Tuya seaded", + "step": { + "user": { + "data": { + "country_code": "Teie konto riigikood (nt 1 USA v\u00f5i 372 Eesti)", + "password": "Salas\u00f5na", + "platform": "\u00c4pp kus teie konto registreeriti", + "username": "Kasutajanimi" + }, + "description": "Sisestage oma Tuya konto andmed." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/tuya/translations/it.json b/homeassistant/components/tuya/translations/it.json index fc2c8fc49b337e..027764d614fb80 100644 --- a/homeassistant/components/tuya/translations/it.json +++ b/homeassistant/components/tuya/translations/it.json @@ -2,11 +2,14 @@ "config": { "abort": { "auth_failed": "Autenticazione non valida", + "cannot_connect": "Impossibile connettersi", "conn_error": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "error": { - "auth_failed": "Autenticazione non valida" + "auth_failed": "Autenticazione non valida", + "invalid_auth": "Autenticazione non valida" }, "flow_title": "Configurazione di Tuya", "step": { diff --git a/homeassistant/components/tuya/translations/no.json b/homeassistant/components/tuya/translations/no.json index 5681f95d9842f8..76994d91436d46 100644 --- a/homeassistant/components/tuya/translations/no.json +++ b/homeassistant/components/tuya/translations/no.json @@ -2,11 +2,14 @@ "config": { "abort": { "auth_failed": "Ugyldig godkjenning", + "cannot_connect": "Tilkobling mislyktes.", "conn_error": "Tilkobling mislyktes.", + "invalid_auth": "Ugyldig godkjenning", "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "error": { - "auth_failed": "Ugyldig godkjenning" + "auth_failed": "Ugyldig godkjenning", + "invalid_auth": "Ugyldig godkjenning" }, "flow_title": "Tuya konfigurasjon", "step": { diff --git a/homeassistant/components/tuya/translations/pl.json b/homeassistant/components/tuya/translations/pl.json index 7278806b5f6b93..d306172fab0edb 100644 --- a/homeassistant/components/tuya/translations/pl.json +++ b/homeassistant/components/tuya/translations/pl.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "auth_failed": "Niepoprawne uwierzytelnienie.", - "conn_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", + "auth_failed": "Niepoprawne uwierzytelnienie", + "conn_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "single_instance_allowed": "Ju\u017c skonfigurowano. Mo\u017cliwa jest tylko jedna konfiguracja." }, "error": { - "auth_failed": "Niepoprawne uwierzytelnienie." + "auth_failed": "Niepoprawne uwierzytelnienie" }, "flow_title": "Konfiguracja integracji Tuya", "step": { diff --git a/homeassistant/components/tuya/translations/ru.json b/homeassistant/components/tuya/translations/ru.json index 4eedc62396ea01..bdb3ad91c358e4 100644 --- a/homeassistant/components/tuya/translations/ru.json +++ b/homeassistant/components/tuya/translations/ru.json @@ -2,11 +2,14 @@ "config": { "abort": { "auth_failed": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "conn_error": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", + "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", "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": { - "auth_failed": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f." + "auth_failed": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f.", + "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f." }, "flow_title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Tuya", "step": { diff --git a/homeassistant/components/twentemilieu/translations/et.json b/homeassistant/components/twentemilieu/translations/et.json new file mode 100644 index 00000000000000..ddaa8004c97b9f --- /dev/null +++ b/homeassistant/components/twentemilieu/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "connection_error": "\u00dchenduse loomine nurjus" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twentemilieu/translations/pl.json b/homeassistant/components/twentemilieu/translations/pl.json index bfa38f9ef8a529..e654bbac6a4987 100644 --- a/homeassistant/components/twentemilieu/translations/pl.json +++ b/homeassistant/components/twentemilieu/translations/pl.json @@ -4,7 +4,7 @@ "address_exists": "Adres jest ju\u017c skonfigurowany." }, "error": { - "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", + "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "invalid_address": "Nie znaleziono adresu w obszarze us\u0142ugi Twente Milieu." }, "step": { diff --git a/homeassistant/components/twilio/strings.json b/homeassistant/components/twilio/strings.json index 96e0249df9a8b1..0480fdae7c890e 100644 --- a/homeassistant/components/twilio/strings.json +++ b/homeassistant/components/twilio/strings.json @@ -7,7 +7,7 @@ } }, "abort": { - "one_instance_allowed": "Only a single instance is necessary.", + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive Twilio messages." }, "create_entry": { diff --git a/homeassistant/components/twilio/translations/ca.json b/homeassistant/components/twilio/translations/ca.json index 591124fda28606..b4adcdb1cb49a9 100644 --- a/homeassistant/components/twilio/translations/ca.json +++ b/homeassistant/components/twilio/translations/ca.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "La inst\u00e0ncia de Home Assistant ha de ser accessible des d'Internet per rebre missatges de Twilio.", - "one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia." + "one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia.", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "create_entry": { "default": "Per enviar esdeveniments a Home Assistant, haur\u00e0s de configurar [Webhooks amb Twilio]({twilio_url}).\n\nCompleta la seg\u00fcent informaci\u00f3 : \n\n- URL: `{webhook_url}` \n- M\u00e8tode: POST \n- Tipus de contingut: application/x-www-form-urlencoded\n\nConsulta la [documentaci\u00f3]({docs_url}) sobre com configurar les automatitzacions per gestionar dades entrants." diff --git a/homeassistant/components/twilio/translations/el.json b/homeassistant/components/twilio/translations/el.json new file mode 100644 index 00000000000000..aecb2ee553fa42 --- /dev/null +++ b/homeassistant/components/twilio/translations/el.json @@ -0,0 +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." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twilio/translations/en.json b/homeassistant/components/twilio/translations/en.json index 32df9de104da5d..de5d921dec2b0c 100644 --- a/homeassistant/components/twilio/translations/en.json +++ b/homeassistant/components/twilio/translations/en.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive Twilio messages.", - "one_instance_allowed": "Only a single instance is necessary." + "one_instance_allowed": "Only a single instance is necessary.", + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "create_entry": { "default": "To send events to Home Assistant, you will need to setup [Webhooks with Twilio]({twilio_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data." diff --git a/homeassistant/components/twilio/translations/es.json b/homeassistant/components/twilio/translations/es.json index a470b3619a4975..8e5ade47c7bf1a 100644 --- a/homeassistant/components/twilio/translations/es.json +++ b/homeassistant/components/twilio/translations/es.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Tu instancia de Home Assistant debe ser accesible desde Internet para recibir mensajes de Twilio.", - "one_instance_allowed": "S\u00f3lo se necesita una instancia." + "one_instance_allowed": "S\u00f3lo se necesita una instancia.", + "single_instance_allowed": "Ya est\u00e1 configurado. S\u00f3lo es posible una \u00fanica configuraci\u00f3n." }, "create_entry": { "default": "Para enviar eventos a Home Assistant debes configurar los [Webhooks en Twilio]({twilio_url}). \n\n Completa la siguiente informaci\u00f3n: \n\n - URL: `{webhook_url}` \n - M\u00e9todo: POST \n - Tipo de contenido: application/x-www-form-urlencoded \n\nConsulta [la documentaci\u00f3n]({docs_url}) sobre c\u00f3mo configurar las automatizaciones para manejar los datos entrantes." diff --git a/homeassistant/components/twilio/translations/et.json b/homeassistant/components/twilio/translations/et.json new file mode 100644 index 00000000000000..e8b5eae41495c6 --- /dev/null +++ b/homeassistant/components/twilio/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/twilio/translations/fr.json b/homeassistant/components/twilio/translations/fr.json index 2144fdd1d48425..3136fb5397415e 100644 --- a/homeassistant/components/twilio/translations/fr.json +++ b/homeassistant/components/twilio/translations/fr.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Votre instance de Home Assistant doit \u00eatre accessible depuis Internet pour recevoir les messages Twilio.", - "one_instance_allowed": "Une seule instance est n\u00e9cessaire." + "one_instance_allowed": "Une seule instance est n\u00e9cessaire.", + "single_instance_allowed": "D\u00e9j\u00e0 configur\u00e9. Une seule configuration possible." }, "create_entry": { "default": "Pour envoyer des \u00e9v\u00e9nements \u00e0 Home Assistant, vous devez configurer [Webhooks avec Twilio] ( {twilio_url} ). \n\n Remplissez les informations suivantes: \n\n - URL: ` {webhook_url} ` \n - M\u00e9thode: POST \n - Type de contenu: application / x-www-form-urlencoded \n\n Voir [la documentation] ( {docs_url} ) pour savoir comment configurer les automatisations pour g\u00e9rer les donn\u00e9es entrantes." diff --git a/homeassistant/components/twilio/translations/it.json b/homeassistant/components/twilio/translations/it.json index 8bf92bf60a2b81..ee7ed374cea157 100644 --- a/homeassistant/components/twilio/translations/it.json +++ b/homeassistant/components/twilio/translations/it.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "La tua istanza di Home Assistant deve essere accessibile da Internet per ricevere messaggi da Twilio.", - "one_instance_allowed": "\u00c8 necessaria una sola istanza." + "one_instance_allowed": "\u00c8 necessaria una sola istanza.", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "create_entry": { "default": "Per inviare eventi a Home Assistant, dovrai configurare [Webhooks con Twilio]({twilio_url})\n\n Compila le seguenti informazioni: \n\n - URL: `{webhook_url}` \n - Method: POST \n - Content Type: application/x-www-form-urlencoded\n\n Vedi [la documentazione]({docs_url}) su come configurare le automazioni per gestire i dati in arrivo." diff --git a/homeassistant/components/twilio/translations/lb.json b/homeassistant/components/twilio/translations/lb.json index 8f741409059283..5183c9f12fd8ac 100644 --- a/homeassistant/components/twilio/translations/lb.json +++ b/homeassistant/components/twilio/translations/lb.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "\u00c4r Home Assistant Instanz muss iwwert Internet accessibel si fir Twilio Noriichten z'empf\u00e4nken.", - "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg." + "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg.", + "single_instance_allowed": "Scho konfigur\u00e9iert. N\u00ebmmen eng eenzeg Konfiguratioun ass m\u00e9iglech." }, "create_entry": { "default": "Fir Evenementer un Home Assistant ze sch\u00e9cken, mussen [Webhooks mat Twilio]({twilio_url}) ageriicht ginn.\n\nF\u00ebllt folgend Informatiounen aus:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\nLiest [Dokumentatioun]({docs_url}) w\u00e9i een Automatiounen ariicht welch eingehend Donn\u00e9\u00eb trait\u00e9ieren." diff --git a/homeassistant/components/twilio/translations/no.json b/homeassistant/components/twilio/translations/no.json index 98b2575d691842..b6b70d39d7d8ab 100644 --- a/homeassistant/components/twilio/translations/no.json +++ b/homeassistant/components/twilio/translations/no.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Din Home Assistant forekomst m\u00e5 v\u00e6re tilgjengelig fra internett for \u00e5 motta Twilio-meldinger.", - "one_instance_allowed": "Kun en forekomst er n\u00f8dvendig." + "one_instance_allowed": "Kun en forekomst er n\u00f8dvendig.", + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "create_entry": { "default": "For \u00e5 sende hendelser til Home Assistant, m\u00e5 du sette opp [Webhooks with Twilio]({twilio_url}). \n\nFyll ut f\u00f8lgende informasjon: \n\n- URL: `{webhook_url}` \n- Metode: POST\n- Innholdstype: application/x-www-form-urlencoded \n\n Se [dokumentasjonen]({docs_url}) om hvordan du konfigurerer automatiseringer for \u00e5 h\u00e5ndtere innkommende data." diff --git a/homeassistant/components/twilio/translations/ru.json b/homeassistant/components/twilio/translations/ru.json index cdb9377cd998fa..74972ab9b08282 100644 --- a/homeassistant/components/twilio/translations/ru.json +++ b/homeassistant/components/twilio/translations/ru.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 Twilio.", - "one_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "one_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430. \u0412\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e." }, "create_entry": { "default": "\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 Home Assistant \u0412\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Webhook \u0434\u043b\u044f [Twilio]({twilio_url}).\n\n\u0417\u0430\u043f\u043e\u043b\u043d\u0438\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\n\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]({docs_url}) \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0439 \u043f\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u043f\u043e\u0441\u0442\u0443\u043f\u0430\u044e\u0449\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445." diff --git a/homeassistant/components/twilio/translations/zh-Hant.json b/homeassistant/components/twilio/translations/zh-Hant.json index cc198a4f43d7f1..1f07a1f44e10d2 100644 --- a/homeassistant/components/twilio/translations/zh-Hant.json +++ b/homeassistant/components/twilio/translations/zh-Hant.json @@ -2,7 +2,8 @@ "config": { "abort": { "not_internet_accessible": "Home Assistant \u8a2d\u5099\u5fc5\u9808\u80fd\u5920\u7531\u7db2\u969b\u7db2\u8def\u5b58\u53d6\uff0c\u65b9\u80fd\u63a5\u53d7 Twilio \u8a0a\u606f\u3002", - "one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u7269\u4ef6\u5373\u53ef\u3002" + "one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u7269\u4ef6\u5373\u53ef\u3002", + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002" }, "create_entry": { "default": "\u6b32\u50b3\u9001\u4e8b\u4ef6\u81f3 Home Assistant\uff0c\u5c07\u9700\u8a2d\u5b9a [Webhooks with Twilio]({twilio_url})\u3002\n\n\u8acb\u586b\u5beb\u4e0b\u5217\u8cc7\u8a0a\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/x-www-form-urlencoded\n\n\u95dc\u65bc\u5982\u4f55\u50b3\u5165\u8cc7\u6599\u81ea\u52d5\u5316\u8a2d\u5b9a\uff0c\u8acb\u53c3\u95b1[\u6587\u4ef6]({docs_url})\u4ee5\u9032\u884c\u4e86\u89e3\u3002" diff --git a/homeassistant/components/twitch/sensor.py b/homeassistant/components/twitch/sensor.py index 52212fca0602f1..4b0196281584e4 100644 --- a/homeassistant/components/twitch/sensor.py +++ b/homeassistant/components/twitch/sensor.py @@ -72,11 +72,6 @@ def __init__(self, channel, client): self._follow = None self._statistics = None - @property - def should_poll(self): - """Device should be polled.""" - return True - @property def name(self): """Return the name of the sensor.""" diff --git a/homeassistant/components/unifi/config_flow.py b/homeassistant/components/unifi/config_flow.py index 6115821b00093a..e40fb30a62c855 100644 --- a/homeassistant/components/unifi/config_flow.py +++ b/homeassistant/components/unifi/config_flow.py @@ -16,6 +16,7 @@ from .const import ( CONF_ALLOW_BANDWIDTH_SENSORS, + CONF_ALLOW_UPTIME_SENSORS, CONF_BLOCK_CLIENT, CONF_CONTROLLER, CONF_DETECTION_TIME, @@ -312,7 +313,11 @@ async def async_step_statistics_sensors(self, user_input=None): vol.Optional( CONF_ALLOW_BANDWIDTH_SENSORS, default=self.controller.option_allow_bandwidth_sensors, - ): bool + ): bool, + vol.Optional( + CONF_ALLOW_UPTIME_SENSORS, + default=self.controller.option_allow_uptime_sensors, + ): bool, } ), ) diff --git a/homeassistant/components/unifi/const.py b/homeassistant/components/unifi/const.py index 803a892647fe0e..42d160f2dea0fd 100644 --- a/homeassistant/components/unifi/const.py +++ b/homeassistant/components/unifi/const.py @@ -12,6 +12,7 @@ UNIFI_WIRELESS_CLIENTS = "unifi_wireless_clients" CONF_ALLOW_BANDWIDTH_SENSORS = "allow_bandwidth_sensors" +CONF_ALLOW_UPTIME_SENSORS = "allow_uptime_sensors" CONF_BLOCK_CLIENT = "block_client" CONF_DETECTION_TIME = "detection_time" CONF_IGNORE_WIRED_BUG = "ignore_wired_bug" @@ -22,6 +23,7 @@ CONF_SSID_FILTER = "ssid_filter" DEFAULT_ALLOW_BANDWIDTH_SENSORS = False +DEFAULT_ALLOW_UPTIME_SENSORS = False DEFAULT_IGNORE_WIRED_BUG = False DEFAULT_POE_CLIENTS = True DEFAULT_TRACK_CLIENTS = True diff --git a/homeassistant/components/unifi/controller.py b/homeassistant/components/unifi/controller.py index 7c30a34f58f411..6fc5b3d9ed7d93 100644 --- a/homeassistant/components/unifi/controller.py +++ b/homeassistant/components/unifi/controller.py @@ -33,6 +33,7 @@ from .const import ( CONF_ALLOW_BANDWIDTH_SENSORS, + CONF_ALLOW_UPTIME_SENSORS, CONF_BLOCK_CLIENT, CONF_CONTROLLER, CONF_DETECTION_TIME, @@ -45,6 +46,7 @@ CONF_TRACK_WIRED_CLIENTS, CONTROLLER_ID, DEFAULT_ALLOW_BANDWIDTH_SENSORS, + DEFAULT_ALLOW_UPTIME_SENSORS, DEFAULT_DETECTION_TIME, DEFAULT_IGNORE_WIRED_BUG, DEFAULT_POE_CLIENTS, @@ -184,6 +186,13 @@ def option_allow_bandwidth_sensors(self): CONF_ALLOW_BANDWIDTH_SENSORS, DEFAULT_ALLOW_BANDWIDTH_SENSORS ) + @property + def option_allow_uptime_sensors(self): + """Config entry option to allow uptime sensors.""" + return self.config_entry.options.get( + CONF_ALLOW_UPTIME_SENSORS, DEFAULT_ALLOW_UPTIME_SENSORS + ) + @callback def async_unifi_signalling_callback(self, signal, data): """Handle messages back from UniFi library.""" diff --git a/homeassistant/components/unifi/device_tracker.py b/homeassistant/components/unifi/device_tracker.py index 831a833552893d..4c5d3309c08aa7 100644 --- a/homeassistant/components/unifi/device_tracker.py +++ b/homeassistant/components/unifi/device_tracker.py @@ -233,9 +233,7 @@ def unique_id(self) -> str: @property def device_state_attributes(self): """Return the client state attributes.""" - attributes = {} - - attributes["is_wired"] = self.is_wired + attributes = {"is_wired": self.is_wired} if self.is_connected: for variable in CLIENT_CONNECTED_ATTRIBUTES: diff --git a/homeassistant/components/unifi/sensor.py b/homeassistant/components/unifi/sensor.py index 8fdb0ac14611bb..59aff09811fff2 100644 --- a/homeassistant/components/unifi/sensor.py +++ b/homeassistant/components/unifi/sensor.py @@ -1,10 +1,11 @@ """Support for bandwidth sensors with UniFi clients.""" import logging -from homeassistant.components.sensor import DOMAIN +from homeassistant.components.sensor import DEVICE_CLASS_TIMESTAMP, DOMAIN from homeassistant.const import DATA_MEGABYTES from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect +import homeassistant.util.dt as dt_util from .const import DOMAIN as UNIFI_DOMAIN from .unifi_client import UniFiClient @@ -13,6 +14,7 @@ RX_SENSOR = "rx" TX_SENSOR = "tx" +UPTIME_SENSOR = "uptime" async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): @@ -22,7 +24,11 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async def async_setup_entry(hass, config_entry, async_add_entities): """Set up sensors for UniFi integration.""" controller = hass.data[UNIFI_DOMAIN][config_entry.entry_id] - controller.entities[DOMAIN] = {RX_SENSOR: set(), TX_SENSOR: set()} + controller.entities[DOMAIN] = { + RX_SENSOR: set(), + TX_SENSOR: set(), + UPTIME_SENSOR: set(), + } @callback def items_added( @@ -30,7 +36,10 @@ def items_added( ) -> None: """Update the values of the controller.""" if controller.option_allow_bandwidth_sensors: - add_entities(controller, async_add_entities, clients) + add_bandwith_entities(controller, async_add_entities, clients) + + if controller.option_allow_uptime_sensors: + add_uptime_entities(controller, async_add_entities, clients) for signal in (controller.signal_update, controller.signal_options_update): controller.listeners.append(async_dispatcher_connect(hass, signal, items_added)) @@ -39,7 +48,7 @@ def items_added( @callback -def add_entities(controller, async_add_entities, clients): +def add_bandwith_entities(controller, async_add_entities, clients): """Add new sensor entities from the controller.""" sensors = [] @@ -55,6 +64,22 @@ def add_entities(controller, async_add_entities, clients): async_add_entities(sensors) +@callback +def add_uptime_entities(controller, async_add_entities, clients): + """Add new sensor entities from the controller.""" + sensors = [] + + for mac in clients: + if mac in controller.entities[DOMAIN][UniFiUpTimeSensor.TYPE]: + continue + + client = controller.api.clients[mac] + sensors.append(UniFiUpTimeSensor(client, controller)) + + if sensors: + async_add_entities(sensors) + + class UniFiBandwidthSensor(UniFiClient): """UniFi bandwidth sensor base class.""" @@ -100,3 +125,30 @@ def state(self) -> int: if self._is_wired: return self.client.wired_tx_bytes / 1000000 return self.client.tx_bytes / 1000000 + + +class UniFiUpTimeSensor(UniFiClient): + """UniFi uptime sensor.""" + + DOMAIN = DOMAIN + TYPE = UPTIME_SENSOR + + @property + def device_class(self) -> str: + """Return device class.""" + return DEVICE_CLASS_TIMESTAMP + + @property + def name(self) -> str: + """Return the name of the client.""" + return f"{super().name} {self.TYPE.capitalize()}" + + @property + def state(self) -> int: + """Return the uptime of the client.""" + return dt_util.utc_from_timestamp(float(self.client.uptime)).isoformat() + + async def options_updated(self) -> None: + """Config entry options are updated, remove entity if option is disabled.""" + if not self.controller.option_allow_uptime_sensors: + await self.remove_item({self.client.mac}) diff --git a/homeassistant/components/unifi/strings.json b/homeassistant/components/unifi/strings.json index 95d273278bd672..9deb68f4e3bb5c 100644 --- a/homeassistant/components/unifi/strings.json +++ b/homeassistant/components/unifi/strings.json @@ -9,7 +9,7 @@ "password": "[%key:common::config_flow::data::password%]", "port": "[%key:common::config_flow::data::port%]", "site": "Site ID", - "verify_ssl": "Controller using proper certificate" + "verify_ssl": "[%key:common::config_flow::data::verify_ssl%]" } } }, @@ -54,7 +54,8 @@ }, "statistics_sensors": { "data": { - "allow_bandwidth_sensors": "Bandwidth usage sensors for network clients" + "allow_bandwidth_sensors": "Bandwidth usage sensors for network clients", + "allow_uptime_sensors": "Uptime sensors for network clients" }, "description": "Configure statistics sensors", "title": "UniFi options 3/3" diff --git a/homeassistant/components/unifi/translations/ca.json b/homeassistant/components/unifi/translations/ca.json index de2a8ffc562837..fb52e583d365fb 100644 --- a/homeassistant/components/unifi/translations/ca.json +++ b/homeassistant/components/unifi/translations/ca.json @@ -16,7 +16,7 @@ "port": "[%key::common::config_flow::data::port%]", "site": "ID del lloc", "username": "[%key::common::config_flow::data::username%]", - "verify_ssl": "El controlador est\u00e0 utilitzant un certificat adequat" + "verify_ssl": "Verifica el certificat SSL" }, "title": "Configuraci\u00f3 del controlador UniFi" } @@ -54,7 +54,8 @@ }, "statistics_sensors": { "data": { - "allow_bandwidth_sensors": "Sensors d'utilitzaci\u00f3 d'ample de banda per a clients de la xarxa" + "allow_bandwidth_sensors": "Sensors d'utilitzaci\u00f3 d'ample de banda per a clients de la xarxa", + "allow_uptime_sensors": "Sensors de temps d'activitat per a clients de xarxa" }, "description": "Configuraci\u00f3 dels sensors d'estad\u00edstiques", "title": "Opcions d'UniFi 3/3" diff --git a/homeassistant/components/unifi/translations/cs.json b/homeassistant/components/unifi/translations/cs.json index a2adfcce3084fd..f36ba9ac5ad79d 100644 --- a/homeassistant/components/unifi/translations/cs.json +++ b/homeassistant/components/unifi/translations/cs.json @@ -15,8 +15,7 @@ "password": "Heslo", "port": "Port", "site": "ID s\u00edt\u011b", - "username": "U\u017eivatelsk\u00e9 jm\u00e9no", - "verify_ssl": "Ovlada\u010d pou\u017e\u00edv\u00e1 spr\u00e1vn\u00fd certifik\u00e1t" + "username": "U\u017eivatelsk\u00e9 jm\u00e9no" }, "title": "Nastaven\u00ed UniFi ovlada\u010de" } @@ -49,7 +48,8 @@ }, "statistics_sensors": { "data": { - "allow_bandwidth_sensors": "Vytvo\u0159it senzory vyu\u017eit\u00ed \u0161\u00ed\u0159ky p\u00e1sma pro p\u0159ipojen\u00e9 klienty" + "allow_bandwidth_sensors": "Vytvo\u0159it senzory vyu\u017eit\u00ed \u0161\u00ed\u0159ky p\u00e1sma pro p\u0159ipojen\u00e9 klienty", + "allow_uptime_sensors": "Vytvo\u0159it senzory doby provozuschopnosti pro s\u00ed\u0165ov\u00e9 klienty" }, "description": "Konfigurovat statistick\u00e9 senzory", "title": "Mo\u017enosti UniFi 3/3" diff --git a/homeassistant/components/unifi/translations/el.json b/homeassistant/components/unifi/translations/el.json new file mode 100644 index 00000000000000..e6b521c1543360 --- /dev/null +++ b/homeassistant/components/unifi/translations/el.json @@ -0,0 +1,11 @@ +{ + "options": { + "step": { + "statistics_sensors": { + "data": { + "allow_uptime_sensors": "\u0391\u03b9\u03c3\u03b8\u03b7\u03c4\u03ae\u03c1\u03b5\u03c2 \u03c7\u03c1\u03cc\u03bd\u03bf\u03c5 \u03c3\u03c5\u03bd\u03b5\u03c7\u03bf\u03cd\u03c2 \u03bb\u03b5\u03b9\u03c4\u03bf\u03c5\u03c1\u03b3\u03af\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03c0\u03b5\u03bb\u03ac\u03c4\u03b5\u03c2 \u03b4\u03b9\u03ba\u03c4\u03cd\u03bf\u03c5" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/unifi/translations/en.json b/homeassistant/components/unifi/translations/en.json index 691f4fb6b01684..ed3a26b335a750 100644 --- a/homeassistant/components/unifi/translations/en.json +++ b/homeassistant/components/unifi/translations/en.json @@ -16,7 +16,7 @@ "port": "Port", "site": "Site ID", "username": "Username", - "verify_ssl": "Controller using proper certificate" + "verify_ssl": "Verify SSL certificate" }, "title": "Set up UniFi Controller" } @@ -54,7 +54,8 @@ }, "statistics_sensors": { "data": { - "allow_bandwidth_sensors": "Bandwidth usage sensors for network clients" + "allow_bandwidth_sensors": "Bandwidth usage sensors for network clients", + "allow_uptime_sensors": "Uptime sensors for network clients" }, "description": "Configure statistics sensors", "title": "UniFi options 3/3" diff --git a/homeassistant/components/unifi/translations/es.json b/homeassistant/components/unifi/translations/es.json index da67cd1bcafee4..35b1d3d6e299cc 100644 --- a/homeassistant/components/unifi/translations/es.json +++ b/homeassistant/components/unifi/translations/es.json @@ -54,7 +54,8 @@ }, "statistics_sensors": { "data": { - "allow_bandwidth_sensors": "Sensores de uso de ancho de banda para los clientes de la red" + "allow_bandwidth_sensors": "Sensores de uso de ancho de banda para los clientes de la red", + "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" diff --git a/homeassistant/components/unifi/translations/fr.json b/homeassistant/components/unifi/translations/fr.json index 21d422ace4d49e..03a872a8f96b46 100644 --- a/homeassistant/components/unifi/translations/fr.json +++ b/homeassistant/components/unifi/translations/fr.json @@ -26,13 +26,16 @@ "step": { "client_control": { "data": { - "block_client": "Clients contr\u00f4l\u00e9s par acc\u00e8s r\u00e9seau" + "block_client": "Clients contr\u00f4l\u00e9s par acc\u00e8s r\u00e9seau", + "poe_clients": "Autoriser le contr\u00f4le POE des clients" }, + "description": "Configurer les contr\u00f4les client \n\n Cr\u00e9ez des interrupteurs pour les num\u00e9ros de s\u00e9rie pour lesquels vous souhaitez contr\u00f4ler l'acc\u00e8s au r\u00e9seau.", "title": "Options UniFi 2/3" }, "device_tracker": { "data": { "detection_time": "Temps en secondes depuis la derni\u00e8re vue avant de consid\u00e9rer comme absent", + "ignore_wired_bug": "D\u00e9sactiver la logique de bogue filaire UniFi", "ssid_filter": "S\u00e9lectionnez les SSID pour suivre les clients sans fil", "track_clients": "Suivre les clients du r\u00e9seau", "track_devices": "Suivre les p\u00e9riph\u00e9riques r\u00e9seau (p\u00e9riph\u00e9riques Ubiquiti)", @@ -57,7 +60,8 @@ }, "statistics_sensors": { "data": { - "allow_bandwidth_sensors": "Cr\u00e9er des capteurs d'utilisation de la bande passante pour les clients r\u00e9seau" + "allow_bandwidth_sensors": "Cr\u00e9er des capteurs d'utilisation de la bande passante pour les clients r\u00e9seau", + "allow_uptime_sensors": "Capteurs de disponibilit\u00e9 pour les clients r\u00e9seau" }, "description": "Configurer des capteurs de statistiques", "title": "Options UniFi 3/3" diff --git a/homeassistant/components/unifi/translations/it.json b/homeassistant/components/unifi/translations/it.json index 8a06ca440c5573..91326796740db7 100644 --- a/homeassistant/components/unifi/translations/it.json +++ b/homeassistant/components/unifi/translations/it.json @@ -16,7 +16,7 @@ "port": "Porta", "site": "ID del sito", "username": "Nome utente", - "verify_ssl": "Il Controller sta utilizzando il certificato corretto" + "verify_ssl": "Verificare il certificato SSL" }, "title": "Configura l'UniFi Controller" } @@ -60,7 +60,8 @@ }, "statistics_sensors": { "data": { - "allow_bandwidth_sensors": "Sensori di utilizzo della larghezza di banda per i client di rete" + "allow_bandwidth_sensors": "Sensori di utilizzo della larghezza di banda per i client di rete", + "allow_uptime_sensors": "Sensori di tempo di funzionamento per i client di rete" }, "description": "Configurare i sensori delle statistiche", "title": "Opzioni UniFi 3/3" diff --git a/homeassistant/components/unifi/translations/ko.json b/homeassistant/components/unifi/translations/ko.json index a3d2c8f3b69c74..94160829bad4c6 100644 --- a/homeassistant/components/unifi/translations/ko.json +++ b/homeassistant/components/unifi/translations/ko.json @@ -54,7 +54,8 @@ }, "statistics_sensors": { "data": { - "allow_bandwidth_sensors": "\ub124\ud2b8\uc6cc\ud06c \ud074\ub77c\uc774\uc5b8\ud2b8 \ub300\uc5ed\ud3ed \uc0ac\uc6a9\ub7c9 \uc13c\uc11c" + "allow_bandwidth_sensors": "\ub124\ud2b8\uc6cc\ud06c \ud074\ub77c\uc774\uc5b8\ud2b8 \ub300\uc5ed\ud3ed \uc0ac\uc6a9\ub7c9 \uc13c\uc11c", + "allow_uptime_sensors": "\ub124\ud2b8\uc6cc\ud06c \ud074\ub77c\uc774\uc5b8\ud2b8\ub97c \uc704\ud55c \uac00\ub3d9 \uc2dc\uac04 \uc13c\uc11c" }, "description": "\ud1b5\uacc4 \uc13c\uc11c \uad6c\uc131", "title": "UniFi \uc635\uc158 3/3" diff --git a/homeassistant/components/unifi/translations/lb.json b/homeassistant/components/unifi/translations/lb.json index 5f7e7192bd95b9..992ee8192e3fc4 100644 --- a/homeassistant/components/unifi/translations/lb.json +++ b/homeassistant/components/unifi/translations/lb.json @@ -53,7 +53,8 @@ }, "statistics_sensors": { "data": { - "allow_bandwidth_sensors": "Bandbreet Benotzung Sensore fir Netzwierk Cliente" + "allow_bandwidth_sensors": "Bandbreet Benotzung Sensore fir Netzwierk Cliente", + "allow_uptime_sensors": "Uptime Sensoren fir Netzwierkklienten" }, "description": "Statistik Sensoren konfigur\u00e9ieren", "title": "UniFi Optiounen 3/3" diff --git a/homeassistant/components/unifi/translations/nl.json b/homeassistant/components/unifi/translations/nl.json index 5d64d73d1deca6..f945e5c4d6da3a 100644 --- a/homeassistant/components/unifi/translations/nl.json +++ b/homeassistant/components/unifi/translations/nl.json @@ -36,6 +36,7 @@ "data": { "detection_time": "Tijd in seconden vanaf laatst gezien tot beschouwd als weg", "ignore_wired_bug": "Schakel UniFi bedrade buglogica uit", + "ssid_filter": "Selecteer SSID's om draadloze clients op te volgen", "track_clients": "Volg netwerkclients", "track_devices": "Netwerkapparaten volgen (Ubiquiti-apparaten)", "track_wired_clients": "Inclusief bedrade netwerkcli\u00ebnten" @@ -57,8 +58,10 @@ }, "statistics_sensors": { "data": { - "allow_bandwidth_sensors": "Maak bandbreedtegebruiksensoren voor netwerkclients" + "allow_bandwidth_sensors": "Maak bandbreedtegebruiksensoren voor netwerkclients", + "allow_uptime_sensors": "Uptime-sensoren voor netwerkclients" }, + "description": "Configureer statistische sensoren", "title": "UniFi-opties 3/3" } } diff --git a/homeassistant/components/unifi/translations/no.json b/homeassistant/components/unifi/translations/no.json index a861790ba8d876..29e13a40182e18 100644 --- a/homeassistant/components/unifi/translations/no.json +++ b/homeassistant/components/unifi/translations/no.json @@ -16,7 +16,7 @@ "port": "", "site": "Nettsted-ID", "username": "Brukernavn", - "verify_ssl": "Kontroller bruker riktig sertifikat" + "verify_ssl": "Verifisere SSL-sertifikat" }, "title": "Sett opp UniFi kontroller" } @@ -60,7 +60,8 @@ }, "statistics_sensors": { "data": { - "allow_bandwidth_sensors": "B\u00e5ndbreddebrukssensorer for nettverksklienter" + "allow_bandwidth_sensors": "B\u00e5ndbreddebrukssensorer for nettverksklienter", + "allow_uptime_sensors": "Oppetidssensorer for nettverksklienter" }, "description": "Konfigurer statistikk sensorer", "title": "UniFi-alternativ 3/3" diff --git a/homeassistant/components/unifi/translations/pl.json b/homeassistant/components/unifi/translations/pl.json index c062d3929113f0..079b9933bace94 100644 --- a/homeassistant/components/unifi/translations/pl.json +++ b/homeassistant/components/unifi/translations/pl.json @@ -4,8 +4,8 @@ "already_configured": "Witryna kontrolera jest ju\u017c skonfigurowana." }, "error": { - "faulty_credentials": "Niepoprawne uwierzytelnienie.", - "service_unavailable": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", + "faulty_credentials": "Niepoprawne uwierzytelnienie", + "service_unavailable": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "unknown_client_mac": "Brak klienta z tym adresem MAC" }, "step": { @@ -62,7 +62,8 @@ }, "statistics_sensors": { "data": { - "allow_bandwidth_sensors": "Stw\u00f3rz sensory wykorzystania przepustowo\u015bci przez klient\u00f3w sieciowych" + "allow_bandwidth_sensors": "Stw\u00f3rz sensory wykorzystania przepustowo\u015bci przez klient\u00f3w sieciowych", + "allow_uptime_sensors": "Sensory czasu pracy dla klient\u00f3w sieciowych" }, "description": "Konfiguracja sensora statystyk", "title": "Opcje UniFi" diff --git a/homeassistant/components/unifi/translations/ru.json b/homeassistant/components/unifi/translations/ru.json index 550867b682b479..9096408474aec5 100644 --- a/homeassistant/components/unifi/translations/ru.json +++ b/homeassistant/components/unifi/translations/ru.json @@ -16,7 +16,7 @@ "port": "\u041f\u043e\u0440\u0442", "site": "ID \u0441\u0430\u0439\u0442\u0430", "username": "\u041b\u043e\u0433\u0438\u043d", - "verify_ssl": "\u041a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442" + "verify_ssl": "\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL" }, "title": "UniFi Controller" } @@ -62,7 +62,8 @@ }, "statistics_sensors": { "data": { - "allow_bandwidth_sensors": "\u0421\u0435\u043d\u0441\u043e\u0440\u044b \u043f\u043e\u043b\u043e\u0441\u044b \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430\u043d\u0438\u044f \u0434\u043b\u044f \u0441\u0435\u0442\u0435\u0432\u044b\u0445 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432" + "allow_bandwidth_sensors": "\u0421\u0435\u043d\u0441\u043e\u0440\u044b \u043f\u043e\u043b\u043e\u0441\u044b \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430\u043d\u0438\u044f \u0434\u043b\u044f \u0441\u0435\u0442\u0435\u0432\u044b\u0445 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432", + "allow_uptime_sensors": "\u0421\u0435\u043d\u0441\u043e\u0440\u044b \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0440\u0430\u0431\u043e\u0442\u044b \u0434\u043b\u044f \u0441\u0435\u0442\u0435\u0432\u044b\u0445 \u043a\u043b\u0438\u0435\u043d\u0442\u043e\u0432" }, "description": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0441\u0435\u043d\u0441\u043e\u0440\u043e\u0432 \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0438", "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 UniFi. \u0428\u0430\u0433 3" diff --git a/homeassistant/components/unifi/translations/zh-Hant.json b/homeassistant/components/unifi/translations/zh-Hant.json index 7ba51c9b621d2f..1ed8d8d8856f8e 100644 --- a/homeassistant/components/unifi/translations/zh-Hant.json +++ b/homeassistant/components/unifi/translations/zh-Hant.json @@ -16,7 +16,7 @@ "port": "\u901a\u8a0a\u57e0", "site": "\u4f4d\u5740 ID", "username": "\u4f7f\u7528\u8005\u540d\u7a31", - "verify_ssl": "\u63a7\u5236\u5668\u4f7f\u7528\u9a57\u8b49" + "verify_ssl": "\u78ba\u8a8d SSL \u8a8d\u8b49" }, "title": "\u8a2d\u5b9a UniFi \u63a7\u5236\u5668" } @@ -54,7 +54,8 @@ }, "statistics_sensors": { "data": { - "allow_bandwidth_sensors": "\u7db2\u8def\u5ba2\u6236\u7aef\u983b\u5bec\u7528\u91cf\u611f\u61c9\u5668" + "allow_bandwidth_sensors": "\u7db2\u8def\u5ba2\u6236\u7aef\u983b\u5bec\u7528\u91cf\u611f\u61c9\u5668", + "allow_uptime_sensors": "\u7db2\u8def\u5ba2\u6236\u7aef\u4e0a\u7dda\u6642\u9593\u611f\u6e2c\u5668" }, "description": "\u8a2d\u5b9a\u7d71\u8a08\u6578\u64da\u611f\u61c9\u5668", "title": "UniFi \u9078\u9805 3/3" diff --git a/homeassistant/components/universal/media_player.py b/homeassistant/components/universal/media_player.py index c38afc139cf626..aedc27c2a29fc7 100644 --- a/homeassistant/components/universal/media_player.py +++ b/homeassistant/components/universal/media_player.py @@ -145,8 +145,9 @@ async def async_added_to_hass(self): """Subscribe to children and template state changes.""" @callback - def _async_on_dependency_update(*_): + def _async_on_dependency_update(event): """Update ha state when dependencies update.""" + self.async_set_context(event.context) self.async_schedule_update_ha_state(True) @callback @@ -158,6 +159,10 @@ def _async_on_template_update(event, updates): self._state_template_result = None else: self._state_template_result = result + + if event: + self.async_set_context(event.context) + self.async_schedule_update_ha_state(True) if self._state_template is not None: diff --git a/homeassistant/components/upb/config_flow.py b/homeassistant/components/upb/config_flow.py index a84b71c71e4d05..3af9999bd9fe36 100644 --- a/homeassistant/components/upb/config_flow.py +++ b/homeassistant/components/upb/config_flow.py @@ -89,7 +89,7 @@ async def async_step_user(self, user_input=None): if user_input is not None: try: if self._url_already_configured(_make_url_from_data(user_input)): - return self.async_abort(reason="address_already_configured") + return self.async_abort(reason="already_configured") network_id, info = await _validate_input(user_input) except CannotConnect: errors["base"] = "cannot_connect" diff --git a/homeassistant/components/upb/strings.json b/homeassistant/components/upb/strings.json index fb4f82d555e6b9..9b2cc0a1b12f16 100644 --- a/homeassistant/components/upb/strings.json +++ b/homeassistant/components/upb/strings.json @@ -12,12 +12,12 @@ } }, "error": { - "cannot_connect": "Failed to connect to UPB PIM, please try again.", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "invalid_upb_file": "Missing or invalid UPB UPStart export file, check the name and path of the file.", - "unknown": "Unexpected error." + "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { - "address_already_configured": "An UPB PIM with this address is already configured." + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } } } diff --git a/homeassistant/components/upb/translations/et.json b/homeassistant/components/upb/translations/et.json new file mode 100644 index 00000000000000..8bf3b2170b4794 --- /dev/null +++ b/homeassistant/components/upb/translations/et.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "unknown": "Ootamatu t\u00f5rge." + } + } +} \ No newline at end of file diff --git a/homeassistant/components/upb/translations/pl.json b/homeassistant/components/upb/translations/pl.json index fcc6fb8bead184..ba19cd38cd213f 100644 --- a/homeassistant/components/upb/translations/pl.json +++ b/homeassistant/components/upb/translations/pl.json @@ -1,12 +1,17 @@ { "config": { + "abort": { + "address_already_configured": "UPB PIM z takim adresem jest ju\u017c skonfigurowany." + }, "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia z UPB PIM, spr\u00f3buj ponownie.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "invalid_upb_file": "Brak lub nieprawid\u0142owy plik eksportu UPB UPStart, sprawd\u017a nazw\u0119 i \u015bcie\u017ck\u0119 do pliku.", + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "user": { "data": { + "file_path": "\u015acie\u017cka i nazwa pliku eksportu UPStart UPB.", "protocol": "Protok\u00f3\u0142" } } diff --git a/homeassistant/components/upnp/__init__.py b/homeassistant/components/upnp/__init__.py index 52cada89333ff8..773a872f33fbd5 100644 --- a/homeassistant/components/upnp/__init__.py +++ b/homeassistant/components/upnp/__init__.py @@ -56,7 +56,9 @@ async def async_discover_and_construct( filtered = [di for di in discovery_infos if di[DISCOVERY_ST] == st] if not filtered: _LOGGER.warning( - 'Wanted UPnP/IGD device with UDN "%s" not found, aborting', udn + 'Wanted UPnP/IGD device with UDN/ST "%s"/"%s" not found, aborting', + udn, + st, ) return None @@ -104,7 +106,7 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry) """Set up UPnP/IGD device from a config entry.""" _LOGGER.debug("async_setup_entry, config_entry: %s", config_entry.data) - # discover and construct + # Discover and construct. udn = config_entry.data.get(CONFIG_ENTRY_UDN) st = config_entry.data.get(CONFIG_ENTRY_ST) # pylint: disable=invalid-name try: @@ -116,11 +118,11 @@ async def async_setup_entry(hass: HomeAssistantType, config_entry: ConfigEntry) _LOGGER.info("Unable to create UPnP/IGD, aborting") raise ConfigEntryNotReady - # Save device + # Save device. hass.data[DOMAIN][DOMAIN_DEVICES][device.udn] = device - # Ensure entry has proper unique_id. - if config_entry.unique_id != device.unique_id: + # Ensure entry has a unique_id. + if not config_entry.unique_id: hass.config_entries.async_update_entry( entry=config_entry, unique_id=device.unique_id, diff --git a/homeassistant/components/upnp/config_flow.py b/homeassistant/components/upnp/config_flow.py index 016a5a25017264..72efc4ffd554a3 100644 --- a/homeassistant/components/upnp/config_flow.py +++ b/homeassistant/components/upnp/config_flow.py @@ -54,7 +54,7 @@ async def async_step_user(self, user_input: Optional[Mapping] = None): if discovery[DISCOVERY_USN] == user_input["usn"] ] if not matching_discoveries: - return self.async_abort(reason="no_devices_discovered") + return self.async_abort(reason="no_devices_found") discovery = matching_discoveries[0] await self.async_set_unique_id( @@ -104,19 +104,10 @@ async def async_step_import(self, import_info: Optional[Mapping]): """ _LOGGER.debug("async_step_import: import_info: %s", import_info) - if import_info is None: - # Landed here via configuration.yaml entry. - # Any device already added, then abort. - if self._async_current_entries(): - _LOGGER.debug("aborting, already configured") - return self.async_abort(reason="already_configured") - - # Test if import_info isn't already configured. - if import_info is not None and any( - import_info["udn"] == entry.data[CONFIG_ENTRY_UDN] - and import_info["st"] == entry.data[CONFIG_ENTRY_ST] - for entry in self._async_current_entries() - ): + # Landed here via configuration.yaml entry. + # Any device already added, then abort. + if self._async_current_entries(): + _LOGGER.debug("Already configured, aborting") return self.async_abort(reason="already_configured") # Discover devices. @@ -127,8 +118,17 @@ async def async_step_import(self, import_info: Optional[Mapping]): _LOGGER.info("No UPnP devices discovered, aborting") return self.async_abort(reason="no_devices_found") - discovery = self._discoveries[0] - return await self._async_create_entry_from_discovery(discovery) + # Ensure complete discovery. + discovery_info = self._discoveries[0] + if DISCOVERY_USN not in discovery_info: + _LOGGER.debug("Incomplete discovery, ignoring") + return self.async_abort(reason="incomplete_discovery") + + # Ensure not already configuring/configured. + usn = discovery_info[DISCOVERY_USN] + await self.async_set_unique_id(usn) + + return await self._async_create_entry_from_discovery(discovery_info) async def async_step_ssdp(self, discovery_info: Mapping): """Handle a discovered UPnP/IGD device. @@ -191,7 +191,7 @@ async def _async_create_entry_from_discovery( ): """Create an entry from discovery.""" _LOGGER.debug( - "_async_create_entry_from_data: discovery: %s", + "_async_create_entry_from_discovery: discovery: %s", discovery, ) # Get name from device, if not found already. diff --git a/homeassistant/components/upnp/device.py b/homeassistant/components/upnp/device.py index c4a81db1ff44a6..5f29043a1fe84e 100644 --- a/homeassistant/components/upnp/device.py +++ b/homeassistant/components/upnp/device.py @@ -67,7 +67,7 @@ async def async_create_device(cls, hass: HomeAssistantType, ssdp_location: str): """Create UPnP/IGD device.""" # build async_upnp_client requester session = async_get_clientsession(hass) - requester = AiohttpSessionRequester(session, True) + requester = AiohttpSessionRequester(session, True, 10) # create async_upnp_client device factory = UpnpFactory(requester, disable_state_variable_validation=True) diff --git a/homeassistant/components/upnp/strings.json b/homeassistant/components/upnp/strings.json index 99e58698f2e5b5..97e91c490e3893 100644 --- a/homeassistant/components/upnp/strings.json +++ b/homeassistant/components/upnp/strings.json @@ -15,9 +15,8 @@ } }, "abort": { - "already_configured": "UPnP/IGD is already configured", - "no_devices_discovered": "No UPnP/IGDs discovered", - "no_devices_found": "No UPnP/IGD devices found on the network.", + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", "incomplete_discovery": "Incomplete discovery" } } diff --git a/homeassistant/components/upnp/translations/et.json b/homeassistant/components/upnp/translations/et.json new file mode 100644 index 00000000000000..76145d6e6e164d --- /dev/null +++ b/homeassistant/components/upnp/translations/et.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "already_configured": "UPnP / IGD on juba seadistatud", + "incomplete_discovery": "Mittet\u00e4ielik avastamine", + "no_devices_discovered": "\u00dchtegi UPnP / IGD-d ei avastatud", + "no_devices_found": "V\u00f5rgust ei leitud \u00fchtegi UPnP / IGD-seadet." + }, + "error": { + "one": "\u00fcks", + "other": "Teine" + }, + "flow_title": "UPnP / IGD: {name}", + "step": { + "init": { + "one": "\u00dcks", + "other": "Teine" + }, + "ssdp_confirm": { + "description": "Kas soovite UPnP / IGD seadme seadistada?" + }, + "user": { + "data": { + "scan_interval": "P\u00e4ringute intervall (sekundites, v\u00e4hemalt 30)", + "usn": "Seade" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/uptimerobot/binary_sensor.py b/homeassistant/components/uptimerobot/binary_sensor.py index 231dce9a402ef1..e31d8b44b100fa 100644 --- a/homeassistant/components/uptimerobot/binary_sensor.py +++ b/homeassistant/components/uptimerobot/binary_sensor.py @@ -4,7 +4,11 @@ from pyuptimerobot import UptimeRobot import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_CONNECTIVITY, + PLATFORM_SCHEMA, + BinarySensorEntity, +) from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY import homeassistant.helpers.config_validation as cv @@ -68,7 +72,7 @@ def is_on(self): @property def device_class(self): """Return the class of this device, from component DEVICE_CLASSES.""" - return "connectivity" + return DEVICE_CLASS_CONNECTIVITY @property def device_state_attributes(self): diff --git a/homeassistant/components/utility_meter/const.py b/homeassistant/components/utility_meter/const.py index 5be7dcf9b6974c..7b55ec4dcd0f1d 100644 --- a/homeassistant/components/utility_meter/const.py +++ b/homeassistant/components/utility_meter/const.py @@ -5,10 +5,11 @@ DAILY = "daily" WEEKLY = "weekly" MONTHLY = "monthly" +BIMONTHLY = "bimonthly" QUARTERLY = "quarterly" YEARLY = "yearly" -METER_TYPES = [HOURLY, DAILY, WEEKLY, MONTHLY, QUARTERLY, YEARLY] +METER_TYPES = [HOURLY, DAILY, WEEKLY, MONTHLY, BIMONTHLY, QUARTERLY, YEARLY] DATA_UTILITY = "utility_meter_data" diff --git a/homeassistant/components/utility_meter/sensor.py b/homeassistant/components/utility_meter/sensor.py index 8372d8e6b22bf2..54f93422abd377 100644 --- a/homeassistant/components/utility_meter/sensor.py +++ b/homeassistant/components/utility_meter/sensor.py @@ -24,6 +24,7 @@ from .const import ( ATTR_VALUE, + BIMONTHLY, CONF_METER, CONF_METER_NET_CONSUMPTION, CONF_METER_OFFSET, @@ -204,6 +205,12 @@ async def _async_reset_meter(self, event): and now != date(now.year, now.month, 1) + self._period_offset ): return + if ( + self._period == BIMONTHLY + and now + != date(now.year, (((now.month - 1) // 2) * 2 + 1), 1) + self._period_offset + ): + return if ( self._period == QUARTERLY and now @@ -241,7 +248,7 @@ async def async_added_to_hass(self): minute=self._period_offset.seconds // 60, second=self._period_offset.seconds % 60, ) - elif self._period in [DAILY, WEEKLY, MONTHLY, QUARTERLY, YEARLY]: + elif self._period in [DAILY, WEEKLY, MONTHLY, BIMONTHLY, QUARTERLY, YEARLY]: async_track_time_change( self.hass, self._async_reset_meter, diff --git a/homeassistant/components/uvc/camera.py b/homeassistant/components/uvc/camera.py index e07fac28d1f6a1..da748c20c1cfb3 100644 --- a/homeassistant/components/uvc/camera.py +++ b/homeassistant/components/uvc/camera.py @@ -152,7 +152,7 @@ def _login(self): camera.login() _LOGGER.debug( "Logged into UVC camera %(name)s via %(addr)s", - dict(name=self._name, addr=addr), + {"name": self._name, "addr": addr}, ) self._connect_addr = addr break diff --git a/homeassistant/components/vacuum/group.py b/homeassistant/components/vacuum/group.py new file mode 100644 index 00000000000000..0219ecdf795796 --- /dev/null +++ b/homeassistant/components/vacuum/group.py @@ -0,0 +1,19 @@ +"""Describe group states.""" + + +from homeassistant.components.group import GroupIntegrationRegistry +from homeassistant.const import STATE_OFF, STATE_ON +from homeassistant.core import callback +from homeassistant.helpers.typing import HomeAssistantType + +from . import STATE_CLEANING, STATE_ERROR, STATE_RETURNING + + +@callback +def async_describe_on_off_states( + hass: HomeAssistantType, registry: GroupIntegrationRegistry +) -> None: + """Describe group on off states.""" + registry.on_off_states( + {STATE_CLEANING, STATE_ON, STATE_RETURNING, STATE_ERROR}, STATE_OFF + ) diff --git a/homeassistant/components/vacuum/translations/et.json b/homeassistant/components/vacuum/translations/et.json index 56976340c5b8fd..fbdbe330b83d4f 100644 --- a/homeassistant/components/vacuum/translations/et.json +++ b/homeassistant/components/vacuum/translations/et.json @@ -1,4 +1,18 @@ { + "device_automation": { + "action_type": { + "clean": "{entity_name} puhastamise lubamine", + "dock": "Laske {entity_name} dokki naasta" + }, + "condition_type": { + "is_cleaning": "{entity_name} puhastab", + "is_docked": "{entity_name} on emajaamas" + }, + "trigger_type": { + "cleaning": "{entity_name} alustas puhastamist", + "docked": "{entity_name} on emajaamas" + } + }, "state": { "_": { "cleaning": "Puhastamine", @@ -7,9 +21,9 @@ "idle": "Ootel", "off": "V\u00e4ljas", "on": "Sees", - "paused": "Peatatud", - "returning": "P\u00f6\u00f6rdun tagasi dokki" + "paused": "Pausil", + "returning": "P\u00f6\u00f6rdun tagasi laadimisjaama" } }, - "title": "T\u00fchjenda" + "title": "Tolmuimeja" } \ No newline at end of file diff --git a/homeassistant/components/vallox/manifest.json b/homeassistant/components/vallox/manifest.json index 97e3955792c2d8..7a959654525815 100644 --- a/homeassistant/components/vallox/manifest.json +++ b/homeassistant/components/vallox/manifest.json @@ -1,6 +1,6 @@ { "domain": "vallox", - "name": "Valloxs", + "name": "Vallox", "documentation": "https://www.home-assistant.io/integrations/vallox", "requirements": ["vallox-websocket-api==2.4.0"], "codeowners": [] diff --git a/homeassistant/components/velbus/config_flow.py b/homeassistant/components/velbus/config_flow.py index 361b5a01175788..7d392ab37c5aa4 100644 --- a/homeassistant/components/velbus/config_flow.py +++ b/homeassistant/components/velbus/config_flow.py @@ -37,7 +37,7 @@ def _test_connection(self, prt): try: controller = velbus.Controller(prt) except Exception: # pylint: disable=broad-except - self._errors[CONF_PORT] = "connection_failed" + self._errors[CONF_PORT] = "cannot_connect" return False controller.stop() return True @@ -58,7 +58,7 @@ async def async_step_user(self, user_input=None): if self._test_connection(prt): return self._create_device(name, prt) else: - self._errors[CONF_PORT] = "port_exists" + self._errors[CONF_PORT] = "already_configured" else: user_input = {} user_input[CONF_NAME] = "" @@ -82,5 +82,5 @@ async def async_step_import(self, user_input=None): if self._prt_in_configuration_exists(prt): # if the velbus import is already in the config # we should not proceed the import - return self.async_abort(reason="port_exists") + return self.async_abort(reason="already_configured") return await self.async_step_user(user_input) diff --git a/homeassistant/components/velbus/manifest.json b/homeassistant/components/velbus/manifest.json index 455aa98b34c204..368c4865bab339 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": ["python-velbus==2.0.44"], + "requirements": ["python-velbus==2.0.46"], "config_flow": true, "codeowners": ["@Cereal2nd", "@brefra"] } diff --git a/homeassistant/components/velbus/strings.json b/homeassistant/components/velbus/strings.json index d5f9d4e7ccfc05..c2defd782f4418 100644 --- a/homeassistant/components/velbus/strings.json +++ b/homeassistant/components/velbus/strings.json @@ -10,9 +10,11 @@ } }, "error": { - "port_exists": "This port is already configured", - "connection_failed": "The velbus connection failed" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, - "abort": { "port_exists": "This port is already configured" } + "abort": { + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + } } } diff --git a/homeassistant/components/velbus/translations/ca.json b/homeassistant/components/velbus/translations/ca.json index 783bb6d7f3b193..b6680ace1d645f 100644 --- a/homeassistant/components/velbus/translations/ca.json +++ b/homeassistant/components/velbus/translations/ca.json @@ -1,9 +1,12 @@ { "config": { "abort": { + "already_configured": "El dispositiu ja est\u00e0 configurat", "port_exists": "El port ja est\u00e0 configurat" }, "error": { + "already_configured": "El dispositiu ja est\u00e0 configurat", + "cannot_connect": "Ha fallat la connexi\u00f3", "connection_failed": "Ha fallat la connexi\u00f3 Velbus", "port_exists": "El port ja est\u00e0 configurat" }, diff --git a/homeassistant/components/velbus/translations/el.json b/homeassistant/components/velbus/translations/el.json new file mode 100644 index 00000000000000..04b238a916d221 --- /dev/null +++ b/homeassistant/components/velbus/translations/el.json @@ -0,0 +1,7 @@ +{ + "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/velbus/translations/en.json b/homeassistant/components/velbus/translations/en.json index ab455442891157..f0b6d16c7bec73 100644 --- a/homeassistant/components/velbus/translations/en.json +++ b/homeassistant/components/velbus/translations/en.json @@ -1,9 +1,12 @@ { "config": { "abort": { + "already_configured": "Device is already configured", "port_exists": "This port is already configured" }, "error": { + "already_configured": "Device is already configured", + "cannot_connect": "Failed to connect", "connection_failed": "The velbus connection failed", "port_exists": "This port is already configured" }, diff --git a/homeassistant/components/velbus/translations/es.json b/homeassistant/components/velbus/translations/es.json index 1bde7176eaf3b8..0c29e2d850506f 100644 --- a/homeassistant/components/velbus/translations/es.json +++ b/homeassistant/components/velbus/translations/es.json @@ -1,9 +1,12 @@ { "config": { "abort": { + "already_configured": "El dispositivo ya est\u00e1 configurado", "port_exists": "Este puerto ya est\u00e1 configurado" }, "error": { + "already_configured": "El dispositivo ya est\u00e1 configurado", + "cannot_connect": "No se pudo conectar", "connection_failed": "La conexi\u00f3n velbus fall\u00f3", "port_exists": "Este puerto ya est\u00e1 configurado" }, diff --git a/homeassistant/components/velbus/translations/et.json b/homeassistant/components/velbus/translations/et.json new file mode 100644 index 00000000000000..cb3316a844f65f --- /dev/null +++ b/homeassistant/components/velbus/translations/et.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud", + "port_exists": "See port on juba h\u00e4\u00e4lestatud" + }, + "error": { + "already_configured": "Seade on juba h\u00e4\u00e4lestatud", + "cannot_connect": "\u00dchendus nurjus" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/velbus/translations/fr.json b/homeassistant/components/velbus/translations/fr.json index ab2e8d756e6696..1c771b9d7a2cd1 100644 --- a/homeassistant/components/velbus/translations/fr.json +++ b/homeassistant/components/velbus/translations/fr.json @@ -1,9 +1,11 @@ { "config": { "abort": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", "port_exists": "Ce port est d\u00e9j\u00e0 configur\u00e9" }, "error": { + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", "connection_failed": "La connexion velbus a \u00e9chou\u00e9", "port_exists": "Ce port est d\u00e9j\u00e0 configur\u00e9" }, diff --git a/homeassistant/components/velbus/translations/it.json b/homeassistant/components/velbus/translations/it.json index bf24ff11336dd6..3ee9a0c02deced 100644 --- a/homeassistant/components/velbus/translations/it.json +++ b/homeassistant/components/velbus/translations/it.json @@ -1,9 +1,12 @@ { "config": { "abort": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", "port_exists": "Questa porta \u00e8 gi\u00e0 configurata" }, "error": { + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "cannot_connect": "Impossibile connettersi", "connection_failed": "La connessione Velbus non \u00e8 riuscita", "port_exists": "Questa porta \u00e8 gi\u00e0 configurata" }, diff --git a/homeassistant/components/velbus/translations/lb.json b/homeassistant/components/velbus/translations/lb.json index 5bb18bc5fa495c..a791e4ce0462d8 100644 --- a/homeassistant/components/velbus/translations/lb.json +++ b/homeassistant/components/velbus/translations/lb.json @@ -1,9 +1,12 @@ { "config": { "abort": { + "already_configured": "Apparat ass scho konfigur\u00e9iert", "port_exists": "D\u00ebse Port ass scho konfigur\u00e9iert" }, "error": { + "already_configured": "Apparat ass scho konfigur\u00e9iert", + "cannot_connect": "Feeler beim verbannen", "connection_failed": "Feeler bei der velbus Verbindung", "port_exists": "D\u00ebse Port ass scho konfigur\u00e9iert" }, diff --git a/homeassistant/components/velbus/translations/no.json b/homeassistant/components/velbus/translations/no.json index 0cc2f475820c89..99732e6dd9a83b 100644 --- a/homeassistant/components/velbus/translations/no.json +++ b/homeassistant/components/velbus/translations/no.json @@ -1,9 +1,12 @@ { "config": { "abort": { + "already_configured": "Enheten er allerede konfigurert", "port_exists": "Denne porten er allerede konfigurert" }, "error": { + "already_configured": "Enheten er allerede konfigurert", + "cannot_connect": "Tilkobling mislyktes.", "connection_failed": "Velbus-tilkoblingen mislyktes", "port_exists": "Denne porten er allerede konfigurert" }, diff --git a/homeassistant/components/velbus/translations/pl.json b/homeassistant/components/velbus/translations/pl.json index c3f06b312f4580..40717086153342 100644 --- a/homeassistant/components/velbus/translations/pl.json +++ b/homeassistant/components/velbus/translations/pl.json @@ -4,6 +4,7 @@ "port_exists": "Ten port jest ju\u017c skonfigurowany." }, "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "connection_failed": "Po\u0142\u0105czenie Velbus nie powiod\u0142o si\u0119", "port_exists": "Ten port jest ju\u017c skonfigurowany." }, diff --git a/homeassistant/components/velbus/translations/ru.json b/homeassistant/components/velbus/translations/ru.json index e88f6209eeeb0d..7a80b18f03e959 100644 --- a/homeassistant/components/velbus/translations/ru.json +++ b/homeassistant/components/velbus/translations/ru.json @@ -1,9 +1,12 @@ { "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.", "port_exists": "\u042d\u0442\u043e\u0442 \u043f\u043e\u0440\u0442 \u0443\u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d." }, "error": { + "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.", + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.", "connection_failed": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u0441 Velbus.", "port_exists": "\u042d\u0442\u043e\u0442 \u043f\u043e\u0440\u0442 \u0443\u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d." }, diff --git a/homeassistant/components/velbus/translations/zh-Hant.json b/homeassistant/components/velbus/translations/zh-Hant.json index 48f9ef5919b9d7..6a9aeec0bd0241 100644 --- a/homeassistant/components/velbus/translations/zh-Hant.json +++ b/homeassistant/components/velbus/translations/zh-Hant.json @@ -1,9 +1,12 @@ { "config": { "abort": { + "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", "port_exists": "\u6b64\u901a\u8a0a\u57e0\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { + "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "cannot_connect": "\u9023\u7dda\u5931\u6557", "connection_failed": "Velbus \u9023\u7dda\u5931\u6557", "port_exists": "\u6b64\u901a\u8a0a\u57e0\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, diff --git a/homeassistant/components/velux/manifest.json b/homeassistant/components/velux/manifest.json index 73306bca7b5d68..0fdbfb649997f6 100644 --- a/homeassistant/components/velux/manifest.json +++ b/homeassistant/components/velux/manifest.json @@ -2,6 +2,6 @@ "domain": "velux", "name": "Velux", "documentation": "https://www.home-assistant.io/integrations/velux", - "requirements": ["pyvlx==0.2.16"], + "requirements": ["pyvlx==0.2.17"], "codeowners": ["@Julius2342"] } diff --git a/homeassistant/components/vera/__init__.py b/homeassistant/components/vera/__init__.py index b45716b33d6ff1..fdc8503ed70b12 100644 --- a/homeassistant/components/vera/__init__.py +++ b/homeassistant/components/vera/__init__.py @@ -2,6 +2,7 @@ import asyncio from collections import defaultdict import logging +from typing import Any, Dict, Generic, List, Optional, Type, TypeVar import pyvera as veraApi from requests.exceptions import RequestException @@ -19,17 +20,25 @@ EVENT_HOMEASSISTANT_STOP, ) from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import config_validation as cv from homeassistant.helpers.entity import Entity from homeassistant.util import convert, slugify from homeassistant.util.dt import utc_from_timestamp -from .common import ControllerData, SubscriptionRegistry, get_configured_platforms +from .common import ( + ControllerData, + SubscriptionRegistry, + get_configured_platforms, + get_controller_data, + set_controller_data, +) from .config_flow import fix_device_id_list, new_options from .const import ( ATTR_CURRENT_ENERGY_KWH, ATTR_CURRENT_POWER_W, CONF_CONTROLLER, + CONF_LEGACY_UNIQUE_ID, DOMAIN, VERA_ID_FORMAT, ) @@ -54,6 +63,8 @@ async def async_setup(hass: HomeAssistant, base_config: dict) -> bool: """Set up for Vera controllers.""" + hass.data[DOMAIN] = {} + config = base_config.get(DOMAIN) if not config: @@ -107,10 +118,10 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b all_devices = await hass.async_add_executor_job(controller.get_devices) all_scenes = await hass.async_add_executor_job(controller.get_scenes) - except RequestException: + except RequestException as exception: # There was a network related error connecting to the Vera controller. _LOGGER.exception("Error communicating with Vera API") - return False + raise ConfigEntryNotReady from exception # Exclude devices unwanted by user. devices = [device for device in all_devices if device.device_id not in exclude_ids] @@ -118,20 +129,21 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b vera_devices = defaultdict(list) for device in devices: device_type = map_vera_device(device, light_ids) - if device_type is None: - continue - - vera_devices[device_type].append(device) + if device_type is not None: + vera_devices[device_type].append(device) vera_scenes = [] for scene in all_scenes: vera_scenes.append(scene) controller_data = ControllerData( - controller=controller, devices=vera_devices, scenes=vera_scenes + controller=controller, + devices=vera_devices, + scenes=vera_scenes, + config_entry=config_entry, ) - hass.data[DOMAIN] = controller_data + set_controller_data(hass, config_entry, controller_data) # Forward the config data to the necessary platforms. for platform in get_configured_platforms(controller_data): @@ -144,7 +156,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: """Unload Withings config entry.""" - controller_data: ControllerData = hass.data[DOMAIN] + controller_data: ControllerData = get_controller_data(hass, config_entry) tasks = [ hass.config_entries.async_forward_entry_unload(config_entry, platform) @@ -156,66 +168,78 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> return True -def map_vera_device(vera_device, remap): +def map_vera_device(vera_device: veraApi.VeraDevice, remap: List[int]) -> str: """Map vera classes to Home Assistant types.""" - if isinstance(vera_device, veraApi.VeraDimmer): - return "light" - if isinstance(vera_device, veraApi.VeraBinarySensor): - return "binary_sensor" - if isinstance(vera_device, veraApi.VeraSensor): - return "sensor" - if isinstance(vera_device, veraApi.VeraArmableDevice): - return "switch" - if isinstance(vera_device, veraApi.VeraLock): - return "lock" - if isinstance(vera_device, veraApi.VeraThermostat): - return "climate" - if isinstance(vera_device, veraApi.VeraCurtain): - return "cover" - if isinstance(vera_device, veraApi.VeraSceneController): - return "sensor" - if isinstance(vera_device, veraApi.VeraSwitch): - if vera_device.device_id in remap: + type_map = { + veraApi.VeraDimmer: "light", + veraApi.VeraBinarySensor: "binary_sensor", + veraApi.VeraSensor: "sensor", + veraApi.VeraArmableDevice: "switch", + veraApi.VeraLock: "lock", + veraApi.VeraThermostat: "climate", + veraApi.VeraCurtain: "cover", + veraApi.VeraSceneController: "sensor", + veraApi.VeraSwitch: "switch", + } + + def map_special_case(instance_class: Type, entity_type: str) -> str: + if instance_class is veraApi.VeraSwitch and vera_device.device_id in remap: return "light" - return "switch" - return None + return entity_type + + return next( + iter( + map_special_case(instance_class, entity_type) + for instance_class, entity_type in type_map.items() + if isinstance(vera_device, instance_class) + ), + None, + ) -class VeraDevice(Entity): +DeviceType = TypeVar("DeviceType", bound=veraApi.VeraDevice) + + +class VeraDevice(Generic[DeviceType], Entity): """Representation of a Vera device entity.""" - def __init__(self, vera_device, controller): + def __init__(self, vera_device: DeviceType, controller_data: ControllerData): """Initialize the device.""" self.vera_device = vera_device - self.controller = controller + self.controller = controller_data.controller self._name = self.vera_device.name # Append device id to prevent name clashes in HA. self.vera_id = VERA_ID_FORMAT.format( - slugify(vera_device.name), vera_device.device_id + slugify(vera_device.name), vera_device.vera_device_id ) - async def async_added_to_hass(self): + if controller_data.config_entry.data.get(CONF_LEGACY_UNIQUE_ID): + self._unique_id = str(self.vera_device.vera_device_id) + else: + self._unique_id = f"vera_{controller_data.config_entry.unique_id}_{self.vera_device.vera_device_id}" + + async def async_added_to_hass(self) -> None: """Subscribe to updates.""" self.controller.register(self.vera_device, self._update_callback) - def _update_callback(self, _device): + def _update_callback(self, _device: DeviceType) -> None: """Update the state.""" self.schedule_update_ha_state(True) @property - def name(self): + def name(self) -> str: """Return the name of the device.""" return self._name @property - def should_poll(self): + def should_poll(self) -> bool: """Get polling requirement from vera device.""" return self.vera_device.should_poll @property - def device_state_attributes(self): + def device_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the device.""" attr = {} @@ -254,4 +278,4 @@ def unique_id(self) -> str: The Vera assigns a unique and immutable ID number to each device. """ - return str(self.vera_device.vera_device_id) + return self._unique_id diff --git a/homeassistant/components/vera/binary_sensor.py b/homeassistant/components/vera/binary_sensor.py index 557874f846a4bb..2e66d38e2492ac 100644 --- a/homeassistant/components/vera/binary_sensor.py +++ b/homeassistant/components/vera/binary_sensor.py @@ -1,6 +1,8 @@ """Support for Vera binary sensors.""" import logging -from typing import Callable, List +from typing import Callable, List, Optional + +import pyvera as veraApi from homeassistant.components.binary_sensor import ( DOMAIN as PLATFORM_DOMAIN, @@ -12,7 +14,7 @@ from homeassistant.helpers.entity import Entity from . import VeraDevice -from .const import DOMAIN +from .common import ControllerData, get_controller_data _LOGGER = logging.getLogger(__name__) @@ -23,29 +25,31 @@ async def async_setup_entry( async_add_entities: Callable[[List[Entity], bool], None], ) -> None: """Set up the sensor config entry.""" - controller_data = hass.data[DOMAIN] + controller_data = get_controller_data(hass, entry) async_add_entities( [ - VeraBinarySensor(device, controller_data.controller) + VeraBinarySensor(device, controller_data) for device in controller_data.devices.get(PLATFORM_DOMAIN) ] ) -class VeraBinarySensor(VeraDevice, BinarySensorEntity): +class VeraBinarySensor(VeraDevice[veraApi.VeraBinarySensor], BinarySensorEntity): """Representation of a Vera Binary Sensor.""" - def __init__(self, vera_device, controller): + def __init__( + self, vera_device: veraApi.VeraBinarySensor, controller_data: ControllerData + ): """Initialize the binary_sensor.""" self._state = False - VeraDevice.__init__(self, vera_device, controller) + VeraDevice.__init__(self, vera_device, controller_data) self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id) @property - def is_on(self): + def is_on(self) -> Optional[bool]: """Return true if sensor is on.""" return self._state - def update(self): + def update(self) -> None: """Get the latest data and update the state.""" self._state = self.vera_device.is_tripped diff --git a/homeassistant/components/vera/climate.py b/homeassistant/components/vera/climate.py index 9b8601e45d193b..0946de4a37961d 100644 --- a/homeassistant/components/vera/climate.py +++ b/homeassistant/components/vera/climate.py @@ -1,6 +1,8 @@ """Support for Vera thermostats.""" import logging -from typing import Callable, List +from typing import Any, Callable, List, Optional + +import pyvera as veraApi from homeassistant.components.climate import ( DOMAIN as PLATFORM_DOMAIN, @@ -24,7 +26,7 @@ from homeassistant.util import convert from . import VeraDevice -from .const import DOMAIN +from .common import ControllerData, get_controller_data _LOGGER = logging.getLogger(__name__) @@ -40,30 +42,32 @@ async def async_setup_entry( async_add_entities: Callable[[List[Entity], bool], None], ) -> None: """Set up the sensor config entry.""" - controller_data = hass.data[DOMAIN] + controller_data = get_controller_data(hass, entry) async_add_entities( [ - VeraThermostat(device, controller_data.controller) + VeraThermostat(device, controller_data) for device in controller_data.devices.get(PLATFORM_DOMAIN) ] ) -class VeraThermostat(VeraDevice, ClimateEntity): +class VeraThermostat(VeraDevice[veraApi.VeraThermostat], ClimateEntity): """Representation of a Vera Thermostat.""" - def __init__(self, vera_device, controller): + def __init__( + self, vera_device: veraApi.VeraThermostat, controller_data: ControllerData + ): """Initialize the Vera device.""" - VeraDevice.__init__(self, vera_device, controller) + VeraDevice.__init__(self, vera_device, controller_data) self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id) @property - def supported_features(self): + def supported_features(self) -> Optional[int]: """Return the list of supported features.""" return SUPPORT_FLAGS @property - def hvac_mode(self): + def hvac_mode(self) -> str: """Return hvac operation ie. heat, cool mode. Need to be one of HVAC_MODE_*. @@ -78,7 +82,7 @@ def hvac_mode(self): return HVAC_MODE_OFF @property - def hvac_modes(self): + def hvac_modes(self) -> List[str]: """Return the list of available hvac operation modes. Need to be a subset of HVAC_MODES. @@ -86,7 +90,7 @@ def hvac_modes(self): return SUPPORT_HVAC @property - def fan_mode(self): + def fan_mode(self) -> Optional[str]: """Return the fan setting.""" mode = self.vera_device.get_fan_mode() if mode == "ContinuousOn": @@ -94,11 +98,11 @@ def fan_mode(self): return FAN_AUTO @property - def fan_modes(self): + def fan_modes(self) -> Optional[List[str]]: """Return a list of available fan modes.""" return FAN_OPERATION_LIST - def set_fan_mode(self, fan_mode): + def set_fan_mode(self, fan_mode) -> None: """Set new target temperature.""" if fan_mode == FAN_ON: self.vera_device.fan_on() @@ -108,14 +112,14 @@ def set_fan_mode(self, fan_mode): self.schedule_update_ha_state() @property - def current_power_w(self): + def current_power_w(self) -> Optional[float]: """Return the current power usage in W.""" power = self.vera_device.power if power: return convert(power, float, 0.0) @property - def temperature_unit(self): + def temperature_unit(self) -> str: """Return the unit of measurement.""" vera_temp_units = self.vera_device.vera_controller.temperature_units @@ -125,28 +129,28 @@ def temperature_unit(self): return TEMP_CELSIUS @property - def current_temperature(self): + def current_temperature(self) -> Optional[float]: """Return the current temperature.""" return self.vera_device.get_current_temperature() @property - def operation(self): + def operation(self) -> str: """Return current operation ie. heat, cool, idle.""" return self.vera_device.get_hvac_mode() @property - def target_temperature(self): + def target_temperature(self) -> Optional[float]: """Return the temperature we try to reach.""" return self.vera_device.get_current_goal_temperature() - def set_temperature(self, **kwargs): + def set_temperature(self, **kwargs: Any) -> None: """Set new target temperatures.""" if kwargs.get(ATTR_TEMPERATURE) is not None: self.vera_device.set_temperature(kwargs.get(ATTR_TEMPERATURE)) self.schedule_update_ha_state() - def set_hvac_mode(self, hvac_mode): + def set_hvac_mode(self, hvac_mode) -> None: """Set new target hvac mode.""" if hvac_mode == HVAC_MODE_OFF: self.vera_device.turn_off() diff --git a/homeassistant/components/vera/common.py b/homeassistant/components/vera/common.py index 17536bcae69d2d..66a2d6879ddf02 100644 --- a/homeassistant/components/vera/common.py +++ b/homeassistant/components/vera/common.py @@ -5,9 +5,12 @@ import pyvera as pv from homeassistant.components.scene import DOMAIN as SCENE_DOMAIN +from homeassistant.config_entries import ConfigEntry from homeassistant.core import HomeAssistant from homeassistant.helpers.event import call_later +from .const import DOMAIN + _LOGGER = logging.getLogger(__name__) @@ -17,6 +20,7 @@ class ControllerData(NamedTuple): controller: pv.VeraController devices: DefaultDict[str, List[pv.VeraDevice]] scenes: List[pv.VeraScene] + config_entry: ConfigEntry def get_configured_platforms(controller_data: ControllerData) -> Set[str]: @@ -31,6 +35,20 @@ def get_configured_platforms(controller_data: ControllerData) -> Set[str]: return set(platforms) +def get_controller_data( + hass: HomeAssistant, config_entry: ConfigEntry +) -> ControllerData: + """Get controller data from hass data.""" + return hass.data[DOMAIN][config_entry.entry_id] + + +def set_controller_data( + hass: HomeAssistant, config_entry: ConfigEntry, data: ControllerData +) -> None: + """Set controller data in hass data.""" + hass.data[DOMAIN][config_entry.entry_id] = data + + class SubscriptionRegistry(pv.AbstractSubscriptionRegistry): """Manages polling for data from vera.""" diff --git a/homeassistant/components/vera/config_flow.py b/homeassistant/components/vera/config_flow.py index a040e4b96b533f..754d2eca542801 100644 --- a/homeassistant/components/vera/config_flow.py +++ b/homeassistant/components/vera/config_flow.py @@ -8,10 +8,16 @@ import voluptuous as vol 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 .const import CONF_CONTROLLER, DOMAIN +from .const import ( # pylint: disable=unused-import + CONF_CONTROLLER, + CONF_LEGACY_UNIQUE_ID, + DOMAIN, +) LIST_REGEX = re.compile("[^0-9]+") _LOGGER = logging.getLogger(__name__) @@ -63,11 +69,11 @@ def options_data(user_input: dict) -> dict: class OptionsFlowHandler(config_entries.OptionsFlow): """Options for the component.""" - def __init__(self, config_entry: config_entries.ConfigEntry): + def __init__(self, config_entry: ConfigEntry): """Init object.""" self.config_entry = config_entry - async def async_step_init(self, user_input=None): + async def async_step_init(self, user_input: dict = None): """Manage the options.""" if user_input is not None: return self.async_create_entry( @@ -86,21 +92,19 @@ class VeraFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): @staticmethod @callback - def async_get_options_flow(config_entry) -> OptionsFlowHandler: + def async_get_options_flow(config_entry: ConfigEntry) -> OptionsFlowHandler: """Get the options flow.""" return OptionsFlowHandler(config_entry) async def async_step_user(self, user_input: dict = None): """Handle user initiated flow.""" - if self.hass.config_entries.async_entries(DOMAIN): - return self.async_abort(reason="already_configured") - if user_input is not None: return await self.async_step_finish( { **user_input, **options_data(user_input), **{CONF_SOURCE: config_entries.SOURCE_USER}, + **{CONF_LEGACY_UNIQUE_ID: False}, } ) @@ -113,8 +117,29 @@ async def async_step_user(self, user_input: dict = None): async def async_step_import(self, config: dict): """Handle a flow initialized by import.""" + + # 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() + ) + use_legacy_unique_id = ( + len( + [ + entry + for entry in entity_registry.entities.values() + if entry.platform == DOMAIN and entry.unique_id.isdigit() + ] + ) + > 0 + ) + return await self.async_step_finish( - {**config, **{CONF_SOURCE: config_entries.SOURCE_IMPORT}} + { + **config, + **{CONF_SOURCE: config_entries.SOURCE_IMPORT}, + **{CONF_LEGACY_UNIQUE_ID: use_legacy_unique_id}, + } ) async def async_step_finish(self, config: dict): diff --git a/homeassistant/components/vera/const.py b/homeassistant/components/vera/const.py index c4f1d0efa3a0b2..34ac7faa6698d4 100644 --- a/homeassistant/components/vera/const.py +++ b/homeassistant/components/vera/const.py @@ -2,6 +2,7 @@ DOMAIN = "vera" CONF_CONTROLLER = "vera_controller_url" +CONF_LEGACY_UNIQUE_ID = "legacy_unique_id" VERA_ID_FORMAT = "{}_{}" diff --git a/homeassistant/components/vera/cover.py b/homeassistant/components/vera/cover.py index a1f536d9cc12b8..49b15e91eb225b 100644 --- a/homeassistant/components/vera/cover.py +++ b/homeassistant/components/vera/cover.py @@ -1,6 +1,8 @@ """Support for Vera cover - curtains, rollershutters etc.""" import logging -from typing import Callable, List +from typing import Any, Callable, List + +import pyvera as veraApi from homeassistant.components.cover import ( ATTR_POSITION, @@ -13,7 +15,7 @@ from homeassistant.helpers.entity import Entity from . import VeraDevice -from .const import DOMAIN +from .common import ControllerData, get_controller_data _LOGGER = logging.getLogger(__name__) @@ -24,25 +26,27 @@ async def async_setup_entry( async_add_entities: Callable[[List[Entity], bool], None], ) -> None: """Set up the sensor config entry.""" - controller_data = hass.data[DOMAIN] + controller_data = get_controller_data(hass, entry) async_add_entities( [ - VeraCover(device, controller_data.controller) + VeraCover(device, controller_data) for device in controller_data.devices.get(PLATFORM_DOMAIN) ] ) -class VeraCover(VeraDevice, CoverEntity): +class VeraCover(VeraDevice[veraApi.VeraCurtain], CoverEntity): """Representation a Vera Cover.""" - def __init__(self, vera_device, controller): + def __init__( + self, vera_device: veraApi.VeraCurtain, controller_data: ControllerData + ): """Initialize the Vera device.""" - VeraDevice.__init__(self, vera_device, controller) + VeraDevice.__init__(self, vera_device, controller_data) self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id) @property - def current_cover_position(self): + def current_cover_position(self) -> int: """ Return current position of cover. @@ -55,28 +59,28 @@ def current_cover_position(self): return 100 return position - def set_cover_position(self, **kwargs): + def set_cover_position(self, **kwargs) -> None: """Move the cover to a specific position.""" self.vera_device.set_level(kwargs.get(ATTR_POSITION)) self.schedule_update_ha_state() @property - def is_closed(self): + def is_closed(self) -> bool: """Return if the cover is closed.""" if self.current_cover_position is not None: return self.current_cover_position == 0 - def open_cover(self, **kwargs): + def open_cover(self, **kwargs: Any) -> None: """Open the cover.""" self.vera_device.open() self.schedule_update_ha_state() - def close_cover(self, **kwargs): + def close_cover(self, **kwargs: Any) -> None: """Close the cover.""" self.vera_device.close() self.schedule_update_ha_state() - def stop_cover(self, **kwargs): + def stop_cover(self, **kwargs: Any) -> None: """Stop the cover.""" self.vera_device.stop() self.schedule_update_ha_state() diff --git a/homeassistant/components/vera/light.py b/homeassistant/components/vera/light.py index 250842f16874b2..47d2d039d2acee 100644 --- a/homeassistant/components/vera/light.py +++ b/homeassistant/components/vera/light.py @@ -1,6 +1,8 @@ """Support for Vera lights.""" import logging -from typing import Callable, List +from typing import Any, Callable, List, Optional, Tuple + +import pyvera as veraApi from homeassistant.components.light import ( ATTR_BRIGHTNESS, @@ -17,7 +19,7 @@ import homeassistant.util.color as color_util from . import VeraDevice -from .const import DOMAIN +from .common import ControllerData, get_controller_data _LOGGER = logging.getLogger(__name__) @@ -28,44 +30,46 @@ async def async_setup_entry( async_add_entities: Callable[[List[Entity], bool], None], ) -> None: """Set up the sensor config entry.""" - controller_data = hass.data[DOMAIN] + controller_data = get_controller_data(hass, entry) async_add_entities( [ - VeraLight(device, controller_data.controller) + VeraLight(device, controller_data) for device in controller_data.devices.get(PLATFORM_DOMAIN) ] ) -class VeraLight(VeraDevice, LightEntity): +class VeraLight(VeraDevice[veraApi.VeraDimmer], LightEntity): """Representation of a Vera Light, including dimmable.""" - def __init__(self, vera_device, controller): + def __init__( + self, vera_device: veraApi.VeraDimmer, controller_data: ControllerData + ): """Initialize the light.""" self._state = False self._color = None self._brightness = None - VeraDevice.__init__(self, vera_device, controller) + VeraDevice.__init__(self, vera_device, controller_data) self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id) @property - def brightness(self): + def brightness(self) -> Optional[int]: """Return the brightness of the light.""" return self._brightness @property - def hs_color(self): + def hs_color(self) -> Optional[Tuple[float, float]]: """Return the color of the light.""" return self._color @property - def supported_features(self): + def supported_features(self) -> int: """Flag supported features.""" if self._color: return SUPPORT_BRIGHTNESS | SUPPORT_COLOR return SUPPORT_BRIGHTNESS - def turn_on(self, **kwargs): + def turn_on(self, **kwargs: Any) -> None: """Turn the light on.""" if ATTR_HS_COLOR in kwargs and self._color: rgb = color_util.color_hs_to_RGB(*kwargs[ATTR_HS_COLOR]) @@ -78,18 +82,18 @@ def turn_on(self, **kwargs): self._state = True self.schedule_update_ha_state(True) - def turn_off(self, **kwargs): + def turn_off(self, **kwargs: Any): """Turn the light off.""" self.vera_device.switch_off() self._state = False self.schedule_update_ha_state() @property - def is_on(self): + def is_on(self) -> bool: """Return true if device is on.""" return self._state - def update(self): + def update(self) -> None: """Call to update state.""" self._state = self.vera_device.is_switched_on() if self.vera_device.is_dimmable: diff --git a/homeassistant/components/vera/lock.py b/homeassistant/components/vera/lock.py index f85beb5ba69bc6..46f8c6f189e5fb 100644 --- a/homeassistant/components/vera/lock.py +++ b/homeassistant/components/vera/lock.py @@ -1,6 +1,8 @@ """Support for Vera locks.""" import logging -from typing import Callable, List +from typing import Any, Callable, Dict, List, Optional + +import pyvera as veraApi from homeassistant.components.lock import ( DOMAIN as PLATFORM_DOMAIN, @@ -13,7 +15,7 @@ from homeassistant.helpers.entity import Entity from . import VeraDevice -from .const import DOMAIN +from .common import ControllerData, get_controller_data _LOGGER = logging.getLogger(__name__) @@ -27,41 +29,41 @@ async def async_setup_entry( async_add_entities: Callable[[List[Entity], bool], None], ) -> None: """Set up the sensor config entry.""" - controller_data = hass.data[DOMAIN] + controller_data = get_controller_data(hass, entry) async_add_entities( [ - VeraLock(device, controller_data.controller) + VeraLock(device, controller_data) for device in controller_data.devices.get(PLATFORM_DOMAIN) ] ) -class VeraLock(VeraDevice, LockEntity): +class VeraLock(VeraDevice[veraApi.VeraLock], LockEntity): """Representation of a Vera lock.""" - def __init__(self, vera_device, controller): + def __init__(self, vera_device: veraApi.VeraLock, controller_data: ControllerData): """Initialize the Vera device.""" self._state = None - VeraDevice.__init__(self, vera_device, controller) + VeraDevice.__init__(self, vera_device, controller_data) self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id) - def lock(self, **kwargs): + def lock(self, **kwargs: Any) -> None: """Lock the device.""" self.vera_device.lock() self._state = STATE_LOCKED - def unlock(self, **kwargs): + def unlock(self, **kwargs: Any) -> None: """Unlock the device.""" self.vera_device.unlock() self._state = STATE_UNLOCKED @property - def is_locked(self): + def is_locked(self) -> Optional[bool]: """Return true if device is on.""" return self._state == STATE_LOCKED @property - def device_state_attributes(self): + def device_state_attributes(self) -> Optional[Dict[str, Any]]: """Who unlocked the lock and did a low battery alert fire. Reports on the previous poll cycle. @@ -78,7 +80,7 @@ def device_state_attributes(self): return data @property - def changed_by(self): + def changed_by(self) -> Optional[str]: """Who unlocked the lock. Reports on the previous poll cycle. @@ -89,7 +91,7 @@ def changed_by(self): return last_user[0] return None - def update(self): + def update(self) -> None: """Update state by the Vera device callback.""" self._state = ( STATE_LOCKED if self.vera_device.is_locked(True) else STATE_UNLOCKED diff --git a/homeassistant/components/vera/manifest.json b/homeassistant/components/vera/manifest.json index a6afcce65b315e..b41d289e6b388d 100644 --- a/homeassistant/components/vera/manifest.json +++ b/homeassistant/components/vera/manifest.json @@ -3,6 +3,6 @@ "name": "Vera", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/vera", - "requirements": ["pyvera==0.3.9"], + "requirements": ["pyvera==0.3.10"], "codeowners": ["@vangorra"] } diff --git a/homeassistant/components/vera/scene.py b/homeassistant/components/vera/scene.py index 2f3069f5332211..8bd4473e1c828e 100644 --- a/homeassistant/components/vera/scene.py +++ b/homeassistant/components/vera/scene.py @@ -1,6 +1,8 @@ """Support for Vera scenes.""" import logging -from typing import Any, Callable, List +from typing import Any, Callable, Dict, List, Optional + +import pyvera as veraApi from homeassistant.components.scene import Scene from homeassistant.config_entries import ConfigEntry @@ -8,7 +10,8 @@ from homeassistant.helpers.entity import Entity from homeassistant.util import slugify -from .const import DOMAIN, VERA_ID_FORMAT +from .common import ControllerData, get_controller_data +from .const import VERA_ID_FORMAT _LOGGER = logging.getLogger(__name__) @@ -19,22 +22,19 @@ async def async_setup_entry( async_add_entities: Callable[[List[Entity], bool], None], ) -> None: """Set up the sensor config entry.""" - controller_data = hass.data[DOMAIN] + controller_data = get_controller_data(hass, entry) async_add_entities( - [ - VeraScene(device, controller_data.controller) - for device in controller_data.scenes - ] + [VeraScene(device, controller_data) for device in controller_data.scenes] ) class VeraScene(Scene): """Representation of a Vera scene entity.""" - def __init__(self, vera_scene, controller): + def __init__(self, vera_scene: veraApi.VeraScene, controller_data: ControllerData): """Initialize the scene.""" self.vera_scene = vera_scene - self.controller = controller + self.controller = controller_data.controller self._name = self.vera_scene.name # Append device id to prevent name clashes in HA. @@ -42,7 +42,7 @@ def __init__(self, vera_scene, controller): slugify(vera_scene.name), vera_scene.scene_id ) - def update(self): + def update(self) -> None: """Update the scene status.""" self.vera_scene.refresh() @@ -51,11 +51,11 @@ def activate(self, **kwargs: Any) -> None: self.vera_scene.activate() @property - def name(self): + def name(self) -> str: """Return the name of the scene.""" return self._name @property - def device_state_attributes(self): + def device_state_attributes(self) -> Optional[Dict[str, Any]]: """Return the state attributes of the scene.""" return {"vera_scene_id": self.vera_scene.vera_scene_id} diff --git a/homeassistant/components/vera/sensor.py b/homeassistant/components/vera/sensor.py index 3c4e0974b850e5..9c3dd097a78d2b 100644 --- a/homeassistant/components/vera/sensor.py +++ b/homeassistant/components/vera/sensor.py @@ -1,19 +1,19 @@ """Support for Vera sensors.""" from datetime import timedelta import logging -from typing import Callable, List +from typing import Callable, List, Optional, cast import pyvera as veraApi from homeassistant.components.sensor import DOMAIN as PLATFORM_DOMAIN, ENTITY_ID_FORMAT from homeassistant.config_entries import ConfigEntry -from homeassistant.const import PERCENTAGE, TEMP_CELSIUS, TEMP_FAHRENHEIT +from homeassistant.const import LIGHT_LUX, PERCENTAGE, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.core import HomeAssistant from homeassistant.helpers.entity import Entity from homeassistant.util import convert from . import VeraDevice -from .const import DOMAIN +from .common import ControllerData, get_controller_data _LOGGER = logging.getLogger(__name__) @@ -26,39 +26,41 @@ async def async_setup_entry( async_add_entities: Callable[[List[Entity], bool], None], ) -> None: """Set up the sensor config entry.""" - controller_data = hass.data[DOMAIN] + controller_data = get_controller_data(hass, entry) async_add_entities( [ - VeraSensor(device, controller_data.controller) + VeraSensor(device, controller_data) for device in controller_data.devices.get(PLATFORM_DOMAIN) ] ) -class VeraSensor(VeraDevice, Entity): +class VeraSensor(VeraDevice[veraApi.VeraSensor], Entity): """Representation of a Vera Sensor.""" - def __init__(self, vera_device, controller): + def __init__( + self, vera_device: veraApi.VeraSensor, controller_data: ControllerData + ): """Initialize the sensor.""" self.current_value = None self._temperature_units = None self.last_changed_time = None - VeraDevice.__init__(self, vera_device, controller) + VeraDevice.__init__(self, vera_device, controller_data) self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id) @property - def state(self): + def state(self) -> str: """Return the name of the sensor.""" return self.current_value @property - def unit_of_measurement(self): + def unit_of_measurement(self) -> Optional[str]: """Return the unit of measurement of this entity, if any.""" if self.vera_device.category == veraApi.CATEGORY_TEMPERATURE_SENSOR: return self._temperature_units if self.vera_device.category == veraApi.CATEGORY_LIGHT_SENSOR: - return "lx" + return LIGHT_LUX if self.vera_device.category == veraApi.CATEGORY_UV_SENSOR: return "level" if self.vera_device.category == veraApi.CATEGORY_HUMIDITY_SENSOR: @@ -66,7 +68,7 @@ def unit_of_measurement(self): if self.vera_device.category == veraApi.CATEGORY_POWER_METER: return "watts" - def update(self): + def update(self) -> None: """Update the state.""" if self.vera_device.category == veraApi.CATEGORY_TEMPERATURE_SENSOR: @@ -86,8 +88,9 @@ def update(self): elif self.vera_device.category == veraApi.CATEGORY_HUMIDITY_SENSOR: self.current_value = self.vera_device.humidity elif self.vera_device.category == veraApi.CATEGORY_SCENE_CONTROLLER: - value = self.vera_device.get_last_scene_id(True) - time = self.vera_device.get_last_scene_time(True) + controller = cast(veraApi.VeraSceneController, self.vera_device) + value = controller.get_last_scene_id(True) + time = controller.get_last_scene_time(True) if time == self.last_changed_time: self.current_value = None else: diff --git a/homeassistant/components/vera/strings.json b/homeassistant/components/vera/strings.json index 7b294eddbb9d30..844d1777f5d752 100644 --- a/homeassistant/components/vera/strings.json +++ b/homeassistant/components/vera/strings.json @@ -1,7 +1,6 @@ { "config": { "abort": { - "already_configured": "A controller is already configured.", "cannot_connect": "Could not connect to controller with url {base_url}" }, "step": { diff --git a/homeassistant/components/vera/switch.py b/homeassistant/components/vera/switch.py index 0a9a94d63722da..9e8360bf67346b 100644 --- a/homeassistant/components/vera/switch.py +++ b/homeassistant/components/vera/switch.py @@ -1,6 +1,8 @@ """Support for Vera switches.""" import logging -from typing import Callable, List +from typing import Any, Callable, List, Optional + +import pyvera as veraApi from homeassistant.components.switch import ( DOMAIN as PLATFORM_DOMAIN, @@ -13,7 +15,7 @@ from homeassistant.util import convert from . import VeraDevice -from .const import DOMAIN +from .common import ControllerData, get_controller_data _LOGGER = logging.getLogger(__name__) @@ -24,48 +26,50 @@ async def async_setup_entry( async_add_entities: Callable[[List[Entity], bool], None], ) -> None: """Set up the sensor config entry.""" - controller_data = hass.data[DOMAIN] + controller_data = get_controller_data(hass, entry) async_add_entities( [ - VeraSwitch(device, controller_data.controller) + VeraSwitch(device, controller_data) for device in controller_data.devices.get(PLATFORM_DOMAIN) ] ) -class VeraSwitch(VeraDevice, SwitchEntity): +class VeraSwitch(VeraDevice[veraApi.VeraSwitch], SwitchEntity): """Representation of a Vera Switch.""" - def __init__(self, vera_device, controller): + def __init__( + self, vera_device: veraApi.VeraSwitch, controller_data: ControllerData + ): """Initialize the Vera device.""" self._state = False - VeraDevice.__init__(self, vera_device, controller) + VeraDevice.__init__(self, vera_device, controller_data) self.entity_id = ENTITY_ID_FORMAT.format(self.vera_id) - def turn_on(self, **kwargs): + def turn_on(self, **kwargs: Any) -> None: """Turn device on.""" self.vera_device.switch_on() self._state = True self.schedule_update_ha_state() - def turn_off(self, **kwargs): + def turn_off(self, **kwargs: Any) -> None: """Turn device off.""" self.vera_device.switch_off() self._state = False self.schedule_update_ha_state() @property - def current_power_w(self): + def current_power_w(self) -> Optional[float]: """Return the current power usage in W.""" power = self.vera_device.power if power: return convert(power, float, 0.0) @property - def is_on(self): + def is_on(self) -> bool: """Return true if device is on.""" return self._state - def update(self): + def update(self) -> None: """Update device state.""" self._state = self.vera_device.is_switched_on() diff --git a/homeassistant/components/vera/translations/fr.json b/homeassistant/components/vera/translations/fr.json index 9cc6d871dd7992..e54613cdb7843e 100644 --- a/homeassistant/components/vera/translations/fr.json +++ b/homeassistant/components/vera/translations/fr.json @@ -7,8 +7,11 @@ "step": { "user": { "data": { + "exclude": "Identifiants d'appareils Vera \u00e0 exclure de Home Assistant.", + "lights": "Identifiants des interrupteurs vera \u00e0 traiter comme des lumi\u00e8res dans Home Assistant", "vera_controller_url": "URL du contr\u00f4leur" }, + "description": "Fournissez une URL de contr\u00f4leur Vera ci-dessous. Cela devrait ressembler \u00e0 ceci : http://192.168.1.161:3480.", "title": "Configurer le contr\u00f4leur Vera" } } @@ -16,6 +19,11 @@ "options": { "step": { "init": { + "data": { + "exclude": "Identifiants d'appareils Vera \u00e0 exclure de Home Assistant.", + "lights": "Identifiants des interrupteurs vera \u00e0 traiter comme des lumi\u00e8res dans Home Assistant" + }, + "description": "Consultez la documentation de vera pour plus de d\u00e9tails sur les param\u00e8tres facultatifs: https://www.home-assistant.io/integrations/vera/. Remarque: toute modification ici n\u00e9cessitera un red\u00e9marrage du serveur Home Assistant. Pour effacer les valeurs, entrez un espace.", "title": "Options du contr\u00f4leur Vera" } } diff --git a/homeassistant/components/verisure/__init__.py b/homeassistant/components/verisure/__init__.py index 757e299792acef..8cd8b0672cfd15 100644 --- a/homeassistant/components/verisure/__init__.py +++ b/homeassistant/components/verisure/__init__.py @@ -12,6 +12,7 @@ CONF_SCAN_INTERVAL, CONF_USERNAME, EVENT_HOMEASSISTANT_STOP, + HTTP_SERVICE_UNAVAILABLE, ) from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv @@ -189,7 +190,7 @@ def update_overview(self): self.overview = self.session.get_overview() except verisure.ResponseError as ex: _LOGGER.error("Could not read overview, %s", ex) - if ex.status_code == 503: # Service unavailable + if ex.status_code == HTTP_SERVICE_UNAVAILABLE: # Service unavailable _LOGGER.info("Trying to log in again") self.login() else: diff --git a/homeassistant/components/version/manifest.json b/homeassistant/components/version/manifest.json index ed3158040d5804..7fc1a097d81a05 100644 --- a/homeassistant/components/version/manifest.json +++ b/homeassistant/components/version/manifest.json @@ -2,7 +2,7 @@ "domain": "version", "name": "Version", "documentation": "https://www.home-assistant.io/integrations/version", - "requirements": ["pyhaversion==3.3.0"], - "codeowners": ["@fabaff"], + "requirements": ["pyhaversion==3.4.2"], + "codeowners": ["@fabaff", "@ludeeus"], "quality_scale": "internal" } diff --git a/homeassistant/components/version/sensor.py b/homeassistant/components/version/sensor.py index 636e564b816394..dcfdc11537672f 100644 --- a/homeassistant/components/version/sensor.py +++ b/homeassistant/components/version/sensor.py @@ -35,6 +35,7 @@ "raspberrypi4-64", "tinker", "odroid-c2", + "odroid-n2", "odroid-xu", ] ALL_SOURCES = ["local", "pypi", "hassio", "docker", "haio"] diff --git a/homeassistant/components/vesync/config_flow.py b/homeassistant/components/vesync/config_flow.py index 8b0e8ae6781852..90db4db14b8676 100644 --- a/homeassistant/components/vesync/config_flow.py +++ b/homeassistant/components/vesync/config_flow.py @@ -51,7 +51,7 @@ async def async_step_import(self, import_config): async def async_step_user(self, user_input=None): """Handle a flow start.""" if configured_instances(self.hass): - return self.async_abort(reason="already_setup") + return self.async_abort(reason="single_instance_allowed") if not user_input: return self._show_form() @@ -62,7 +62,7 @@ async def async_step_user(self, user_input=None): manager = VeSync(self._username, self._password) login = await self.hass.async_add_executor_job(manager.login) if not login: - return self._show_form(errors={"base": "invalid_login"}) + return self._show_form(errors={"base": "invalid_auth"}) return self.async_create_entry( title=self._username, diff --git a/homeassistant/components/vesync/fan.py b/homeassistant/components/vesync/fan.py index 7d395d93a74dd5..7cc3f00e1a0269 100644 --- a/homeassistant/components/vesync/fan.py +++ b/homeassistant/components/vesync/fan.py @@ -21,8 +21,8 @@ "LV-PUR131S": "fan", } -SPEED_AUTO = "auto" -FAN_SPEEDS = [SPEED_AUTO, SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] +FAN_SPEEDS = [SPEED_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] +FAN_MODE_AUTO = "auto" async def async_setup_entry(hass, config_entry, async_add_entities): @@ -36,7 +36,6 @@ async def async_discover(devices): hass.data[DOMAIN][VS_DISPATCHERS].append(disp) _async_setup_entities(hass.data[DOMAIN][VS_FANS], async_add_entities) - return True @callback @@ -71,8 +70,8 @@ def supported_features(self): @property def speed(self): """Return the current speed.""" - if self.smartfan.mode == SPEED_AUTO: - return SPEED_AUTO + if self.smartfan.mode == FAN_MODE_AUTO: + return None if self.smartfan.mode == "manual": current_level = self.smartfan.fan_level if current_level is not None: @@ -105,11 +104,8 @@ def set_speed(self, speed): if not self.smartfan.is_on: self.smartfan.turn_on() - if speed is None or speed == SPEED_AUTO: - self.smartfan.auto_mode() - else: - self.smartfan.manual_mode() - self.smartfan.change_fan_speed(FAN_SPEEDS.index(speed)) + self.smartfan.manual_mode() + self.smartfan.change_fan_speed(FAN_SPEEDS.index(speed)) def turn_on(self, speed: str = None, **kwargs) -> None: """Turn the device on.""" diff --git a/homeassistant/components/vesync/manifest.json b/homeassistant/components/vesync/manifest.json index a4e786c2a12500..667cb16d12839c 100644 --- a/homeassistant/components/vesync/manifest.json +++ b/homeassistant/components/vesync/manifest.json @@ -8,7 +8,7 @@ "@thegardenmonkey" ], "requirements": [ - "pyvesync==1.1.0" + "pyvesync==1.2.0" ], "config_flow": true } \ No newline at end of file diff --git a/homeassistant/components/vesync/strings.json b/homeassistant/components/vesync/strings.json index 1bdf32e1037525..8359691effef14 100644 --- a/homeassistant/components/vesync/strings.json +++ b/homeassistant/components/vesync/strings.json @@ -10,10 +10,10 @@ } }, "error": { - "invalid_login": "Invalid username or password" + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" }, "abort": { - "already_setup": "Only one Vesync instance is allowed" + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/vesync/switch.py b/homeassistant/components/vesync/switch.py index 939240349d1af1..0ce4b931def2f1 100644 --- a/homeassistant/components/vesync/switch.py +++ b/homeassistant/components/vesync/switch.py @@ -74,13 +74,14 @@ def __init__(self, plug): @property def device_state_attributes(self): """Return the state attributes of the device.""" - attr = {} - if hasattr(self.smartplug, "weekly_energy_total"): - attr["voltage"] = self.smartplug.voltage - attr["weekly_energy_total"] = self.smartplug.weekly_energy_total - attr["monthly_energy_total"] = self.smartplug.monthly_energy_total - attr["yearly_energy_total"] = self.smartplug.yearly_energy_total - return attr + if not hasattr(self.smartplug, "weekly_energy_total"): + return {} + return { + "voltage": self.smartplug.voltage, + "weekly_energy_total": self.smartplug.weekly_energy_total, + "monthly_energy_total": self.smartplug.monthly_energy_total, + "yearly_energy_total": self.smartplug.yearly_energy_total, + } @property def current_power_w(self): diff --git a/homeassistant/components/vilfo/strings.json b/homeassistant/components/vilfo/strings.json index 6de59a51f96707..c9b8882c264601 100644 --- a/homeassistant/components/vilfo/strings.json +++ b/homeassistant/components/vilfo/strings.json @@ -11,12 +11,12 @@ } }, "error": { - "cannot_connect": "Failed to connect. Please check the information you provided and try again.", - "invalid_auth": "Invalid authentication. Please check the access token and try again.", - "unknown": "An unexpected error occurred while setting up the integration." + "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": "This Vilfo Router is already configured." + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/vilfo/translations/ca.json b/homeassistant/components/vilfo/translations/ca.json index 639167e4cecfa3..827545f5b54a00 100644 --- a/homeassistant/components/vilfo/translations/ca.json +++ b/homeassistant/components/vilfo/translations/ca.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "L'encaminador Vilfo ja est\u00e0 configurat." + "already_configured": "El dispositiu ja est\u00e0 configurat" }, "error": { - "cannot_connect": "No s'ha pogut connectar. Verifica la informaci\u00f3 proporcionada i torna-ho a provar.", - "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida. Comprova el token d'acc\u00e9s i torna-ho a provar.", - "unknown": "S'ha produ\u00eft un error inesperat durant la configuraci\u00f3 de la integraci\u00f3." + "cannot_connect": "Ha fallat la connexi\u00f3", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida", + "unknown": "Error inesperat" }, "step": { "user": { diff --git a/homeassistant/components/vilfo/translations/en.json b/homeassistant/components/vilfo/translations/en.json index 57815aa039322f..03f9a9bd23a3a4 100644 --- a/homeassistant/components/vilfo/translations/en.json +++ b/homeassistant/components/vilfo/translations/en.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "This Vilfo Router is already configured." + "already_configured": "Device is already configured" }, "error": { - "cannot_connect": "Failed to connect. Please check the information you provided and try again.", - "invalid_auth": "Invalid authentication. Please check the access token and try again.", - "unknown": "An unexpected error occurred while setting up the integration." + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" }, "step": { "user": { diff --git a/homeassistant/components/vilfo/translations/fr.json b/homeassistant/components/vilfo/translations/fr.json index 3777492192070a..b6790d98d399b3 100644 --- a/homeassistant/components/vilfo/translations/fr.json +++ b/homeassistant/components/vilfo/translations/fr.json @@ -14,6 +14,7 @@ "access_token": "Jeton d'Acc\u00e8s", "host": "Nom d'h\u00f4te ou adresse IP" }, + "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/it.json b/homeassistant/components/vilfo/translations/it.json index 8d41d74c79d80d..ad15381b1db7ad 100644 --- a/homeassistant/components/vilfo/translations/it.json +++ b/homeassistant/components/vilfo/translations/it.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "Questo Vilfo Router \u00e8 gi\u00e0 configurato." + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato" }, "error": { - "cannot_connect": "Impossibile connettersi. Controllare le informazioni fornite e riprovare.", - "invalid_auth": "Autenticazione non valida. Controllare il token di accesso e riprovare.", - "unknown": "Si \u00e8 verificato un errore imprevisto durante l'impostazione dell'integrazione." + "cannot_connect": "Impossibile connettersi", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" }, "step": { "user": { diff --git a/homeassistant/components/vilfo/translations/no.json b/homeassistant/components/vilfo/translations/no.json index 18b51391d8575d..ce99f9d81abbec 100644 --- a/homeassistant/components/vilfo/translations/no.json +++ b/homeassistant/components/vilfo/translations/no.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "Denne Vilfo Ruteren er allerede konfigurert." + "already_configured": "Enheten er allerede konfigurert" }, "error": { - "cannot_connect": "Tilkobling mislyktes. Vennligst sjekk informasjonen du oppga, og pr\u00f8v igjen.", - "invalid_auth": "Ugyldig godkjenning. Vennligst sjekk access token, og pr\u00f8v p\u00e5 nytt.", - "unknown": "Det oppstod en uventet feil under installasjonen av integrasjonen." + "cannot_connect": "Tilkobling mislyktes.", + "invalid_auth": "Ugyldig godkjenning", + "unknown": "Uventet feil" }, "step": { "user": { diff --git a/homeassistant/components/vilfo/translations/pl.json b/homeassistant/components/vilfo/translations/pl.json index 29a8a05636136a..fb439da9bfe9a1 100644 --- a/homeassistant/components/vilfo/translations/pl.json +++ b/homeassistant/components/vilfo/translations/pl.json @@ -6,7 +6,7 @@ "error": { "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia. Sprawd\u017a wprowadzone dane i spr\u00f3buj ponownie.", "invalid_auth": "Nieudane uwierzytelnienie. Sprawd\u017a token dost\u0119pu i spr\u00f3buj ponownie.", - "unknown": "Nieoczekiwany b\u0142\u0105d." + "unknown": "Nieoczekiwany b\u0142\u0105d" }, "step": { "user": { diff --git a/homeassistant/components/vilfo/translations/ru.json b/homeassistant/components/vilfo/translations/ru.json index 329a5e400e5d70..8e61be904004e8 100644 --- a/homeassistant/components/vilfo/translations/ru.json +++ b/homeassistant/components/vilfo/translations/ru.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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, \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0435 \u0434\u0430\u043d\u043d\u044b\u0435 \u0438 \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0435\u0449\u0435 \u0440\u0430\u0437.", - "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u0442\u043e\u043a\u0435\u043d \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0438 \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0441\u043d\u043e\u0432\u0430.", - "unknown": "\u041d\u0435\u043f\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043d\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0435 \u0438\u043d\u0442\u0435\u0433\u0440\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.", + "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\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": { diff --git a/homeassistant/components/vilfo/translations/zh-Hant.json b/homeassistant/components/vilfo/translations/zh-Hant.json index c1fe87f65e8df4..abbc12e6d8f58a 100644 --- a/homeassistant/components/vilfo/translations/zh-Hant.json +++ b/homeassistant/components/vilfo/translations/zh-Hant.json @@ -1,12 +1,12 @@ { "config": { "abort": { - "already_configured": "Vilfo \u8def\u7531\u5668\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210\u3002" + "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210" }, "error": { - "cannot_connect": "\u9023\u7dda\u5931\u6557\u3002\u8acb\u6aa2\u67e5\u8f38\u5165\u8cc7\u6599\u5f8c\uff0c\u518d\u8a66\u4e00\u6b21\u3002", - "invalid_auth": "\u9a57\u8b49\u7121\u6548\uff0c\u8acb\u6aa2\u67e5\u5b58\u53d6\u5bc6\u9470\u5f8c\u518d\u8a66\u4e00\u6b21\u3002", - "unknown": "\u8a2d\u5b9a\u6574\u5408\u6642\u767c\u751f\u672a\u77e5\u932f\u8aa4\u3002" + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548", + "unknown": "\u672a\u9810\u671f\u932f\u8aa4" }, "step": { "user": { diff --git a/homeassistant/components/vivotek/camera.py b/homeassistant/components/vivotek/camera.py index 6bf9fdace5a77b..b1c7d2bb2a79f9 100644 --- a/homeassistant/components/vivotek/camera.py +++ b/homeassistant/components/vivotek/camera.py @@ -52,9 +52,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up a Vivotek IP Camera.""" creds = f"{config[CONF_USERNAME]}:{config[CONF_PASSWORD]}" - args = dict( - config=config, - cam=VivotekCamera( + args = { + "config": config, + "cam": VivotekCamera( host=config[CONF_IP_ADDRESS], port=(443 if config[CONF_SSL] else 80), verify_ssl=config[CONF_VERIFY_SSL], @@ -63,8 +63,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): digest_auth=config[CONF_AUTHENTICATION] == HTTP_DIGEST_AUTHENTICATION, sec_lvl=config[CONF_SECURITY_LEVEL], ), - stream_source=f"rtsp://{creds}@{config[CONF_IP_ADDRESS]}:554/{config[CONF_STREAM_PATH]}", - ) + "stream_source": f"rtsp://{creds}@{config[CONF_IP_ADDRESS]}:554/{config[CONF_STREAM_PATH]}", + } add_entities([VivotekCam(**args)], True) diff --git a/homeassistant/components/vizio/__init__.py b/homeassistant/components/vizio/__init__.py index 25960da72cfc9c..a7a9c404f74d9c 100644 --- a/homeassistant/components/vizio/__init__.py +++ b/homeassistant/components/vizio/__init__.py @@ -94,7 +94,7 @@ async def async_unload_entry( and entry.data[CONF_DEVICE_CLASS] == DEVICE_CLASS_TV for entry in hass.config_entries.async_entries(DOMAIN) ): - hass.data[DOMAIN].pop(CONF_APPS) + hass.data[DOMAIN].pop(CONF_APPS, None) if not hass.data[DOMAIN]: hass.data.pop(DOMAIN) diff --git a/homeassistant/components/vizio/media_player.py b/homeassistant/components/vizio/media_player.py index ab5386c151b02f..61c9ca54854f8a 100644 --- a/homeassistant/components/vizio/media_player.py +++ b/homeassistant/components/vizio/media_player.py @@ -143,7 +143,6 @@ def __init__( ) -> None: """Initialize Vizio device.""" self._config_entry = config_entry - self._async_unsub_listeners = [] self._apps_coordinator = apps_coordinator self._name = name @@ -290,7 +289,7 @@ async def _async_send_update_options_signal( ) -> None: """Send update event when Vizio config entry is updated.""" # Move this method to component level if another entity ever gets added for a single config entry. - # See here: https://github.com/home-assistant/home-assistant/pull/30653#discussion_r366426121 + # See here: https://github.com/home-assistant/core/pull/30653#discussion_r366426121 async_dispatcher_send(hass, config_entry.entry_id, config_entry) async def _async_update_options(self, config_entry: ConfigEntry) -> None: @@ -312,14 +311,14 @@ async def async_update_setting( async def async_added_to_hass(self) -> None: """Register callbacks when entity is added.""" # Register callback for when config entry is updated. - self._async_unsub_listeners.append( + self.async_on_remove( self._config_entry.add_update_listener( self._async_send_update_options_signal ) ) # Register callback for update event - self._async_unsub_listeners.append( + self.async_on_remove( async_dispatcher_connect( self.hass, self._config_entry.entry_id, self._async_update_options ) @@ -333,17 +332,10 @@ def apps_list_update(): self.async_write_ha_state() if self._device_class == DEVICE_CLASS_TV: - self._async_unsub_listeners.append( + self.async_on_remove( self._apps_coordinator.async_add_listener(apps_list_update) ) - async def async_will_remove_from_hass(self) -> None: - """Disconnect callbacks when entity is removed.""" - for listener in self._async_unsub_listeners: - listener() - - self._async_unsub_listeners.clear() - @property def available(self) -> bool: """Return the availabiliity of the device.""" diff --git a/homeassistant/components/vizio/strings.json b/homeassistant/components/vizio/strings.json index 8979f6fd82ea25..da039b6a89e5e5 100644 --- a/homeassistant/components/vizio/strings.json +++ b/homeassistant/components/vizio/strings.json @@ -5,7 +5,7 @@ "title": "VIZIO SmartCast Device", "description": "An [%key:common::config_flow::data::access_token%] is only needed for TVs. If you are configuring a TV and do not have an [%key:common::config_flow::data::access_token%] yet, leave it blank to go through a pairing process.", "data": { - "name": "Name", + "name": "[%key:common::config_flow::data::name%]", "host": "[%key:common::config_flow::data::host%]", "device_class": "Device Type", "access_token": "[%key:common::config_flow::data::access_token%]" @@ -15,7 +15,7 @@ "title": "Complete Pairing Process", "description": "Your TV should be displaying a code. Enter that code into the form and then continue to the next step to complete the pairing.", "data": { - "pin": "PIN" + "pin": "[%key:common::config_flow::data::pin%]" } }, "pairing_complete": { @@ -50,4 +50,4 @@ } } } -} \ No newline at end of file +} diff --git a/homeassistant/components/vizio/translations/hu.json b/homeassistant/components/vizio/translations/hu.json index 469649275e1153..9c0b31ca4270eb 100644 --- a/homeassistant/components/vizio/translations/hu.json +++ b/homeassistant/components/vizio/translations/hu.json @@ -1,6 +1,7 @@ { "config": { "abort": { + "already_configured_device": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van", "updated_entry": "Ez a bejegyz\u00e9s m\u00e1r be van \u00e1ll\u00edtva, de a konfigur\u00e1ci\u00f3ban defini\u00e1lt n\u00e9v, appok \u00e9s/vagy be\u00e1ll\u00edt\u00e1sok nem egyeznek meg a kor\u00e1bban import\u00e1lt konfigur\u00e1ci\u00f3val, \u00edgy a konfigur\u00e1ci\u00f3s bejegyz\u00e9s ennek megfelel\u0151en friss\u00fclt." }, "error": { diff --git a/homeassistant/components/vizio/translations/ko.json b/homeassistant/components/vizio/translations/ko.json index c56171e9319244..310fd7650265a7 100644 --- a/homeassistant/components/vizio/translations/ko.json +++ b/homeassistant/components/vizio/translations/ko.json @@ -7,6 +7,7 @@ "error": { "cannot_connect": "\uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4", "complete_pairing_failed": "\ud398\uc5b4\ub9c1\uc744 \uc644\ub8cc\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc81c\ucd9c\ud558\uae30 \uc804\uc5d0 \uc785\ub825\ud55c PIN \uc774 \uc62c\ubc14\ub978\uc9c0, TV \uc758 \uc804\uc6d0\uc774 \ucf1c\uc838 \uc788\uace0 \ub124\ud2b8\uc6cc\ud06c\uc5d0 \uc5f0\uacb0\ub418\uc5b4 \uc788\ub294\uc9c0 \ud655\uc778\ud574\uc8fc\uc138\uc694.", + "existing_config_entry_found": "\uc77c\ub828 \ubc88\ud638\uac00 \ub3d9\uc77c\ud55c \uae30\uc874 VIZIO SmartCast \uae30\uae30 \uad6c\uc131 \ud56d\ubaa9\uc774 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \uc774 \ud56d\ubaa9\uc744 \uad6c\uc131\ud558\ub824\uba74 \uae30\uc874 \ud56d\ubaa9\uc744 \uc0ad\uc81c\ud574\uc57c\ud569\ub2c8\ub2e4.", "host_exists": "\uc124\uc815\ub41c \ud638\uc2a4\ud2b8\uc758 VIZIO SmartCast \uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "name_exists": "\uc124\uc815\ub41c \uc774\ub984\uc758 VIZIO SmartCast \uae30\uae30\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4." }, diff --git a/homeassistant/components/vizio/translations/pl.json b/homeassistant/components/vizio/translations/pl.json index 9d22796ea44f2c..ccba8f7de1cf54 100644 --- a/homeassistant/components/vizio/translations/pl.json +++ b/homeassistant/components/vizio/translations/pl.json @@ -1,12 +1,13 @@ { "config": { "abort": { - "already_configured_device": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", + "already_configured_device": "Urz\u0105dzenie jest ju\u017c skonfigurowane", "updated_entry": "Ten wpis zosta\u0142 ju\u017c skonfigurowany, ale nazwa i/lub opcje zdefiniowane w konfiguracji nie pasuj\u0105 do wcze\u015bniej zaimportowanych warto\u015bci, wi\u0119c wpis konfiguracji zosta\u0142 odpowiednio zaktualizowany." }, "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "complete_pairing_failed": "Nie mo\u017cna uko\u0144czy\u0107 parowania. Upewnij si\u0119, \u017ce podany kod PIN jest prawid\u0142owy, a telewizor jest zasilany i pod\u0142\u0105czony do sieci przed ponownym przes\u0142aniem.", + "existing_config_entry_found": "Istnieje ju\u017c wpis konfiguracyjny Konfiguracja klienta Vizio SmartCast z tym samym numerem seryjnym. W celu skonfigurowania tego wpisu nale\u017cy usun\u0105\u0107 istniej\u0105cy.", "host_exists": "Urz\u0105dzenie Vizio z okre\u015blonym hostem jest ju\u017c skonfigurowane.", "name_exists": "Urz\u0105dzenie Vizio o okre\u015blonej nazwie jest ju\u017c skonfigurowane." }, diff --git a/homeassistant/components/voicerss/tts.py b/homeassistant/components/voicerss/tts.py index 23d4c9ff864d93..69029ea7031cd9 100644 --- a/homeassistant/components/voicerss/tts.py +++ b/homeassistant/components/voicerss/tts.py @@ -28,32 +28,55 @@ ] SUPPORT_LANGUAGES = [ + "ar-eg", + "ar-sa", + "bg-bg", "ca-es", "zh-cn", "zh-hk", "zh-tw", + "hr-hr", + "cs-cz", "da-dk", + "nl-be", "nl-nl", "en-au", "en-ca", "en-gb", "en-in", + "en-ie", "en-us", "fi-fi", "fr-ca", "fr-fr", + "fr-ch", + "de-at", "de-de", + "de-ch", + "el-gr", + "he-il", + "hi-in", + "hu-hu", + "id-id", "it-it", "ja-jp", "ko-kr", + "ms-my", "nb-no", "pl-pl", "pt-br", "pt-pt", + "ro-ro", "ru-ru", + "sk-sk", + "sl-si", "es-mx", "es-es", "sv-se", + "ta-in", + "th-th", + "tr-tr", + "vi-vn", ] SUPPORT_CODECS = ["mp3", "wav", "aac", "ogg", "caf"] diff --git a/homeassistant/components/volumio/translations/fr.json b/homeassistant/components/volumio/translations/fr.json index 03844ccf99b016..6dee3fb9fafd72 100644 --- a/homeassistant/components/volumio/translations/fr.json +++ b/homeassistant/components/volumio/translations/fr.json @@ -1,13 +1,18 @@ { "config": { "abort": { - "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9" + "already_configured": "L'appareil est d\u00e9j\u00e0 configur\u00e9", + "cannot_connect": "Impossible de se connecter au Volumio d\u00e9couvert" }, "error": { "cannot_connect": "\u00c9chec de connexion", "unknown": "Erreur inattendue" }, "step": { + "discovery_confirm": { + "description": "Voulez-vous ajouter Volumio (` {name} `) \u00e0 Home Assistant?", + "title": "Volumio d\u00e9couvert" + }, "user": { "data": { "host": "H\u00f4te", diff --git a/homeassistant/components/volumio/translations/hu.json b/homeassistant/components/volumio/translations/hu.json new file mode 100644 index 00000000000000..3b2d79a34a77e2 --- /dev/null +++ b/homeassistant/components/volumio/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/volumio/translations/pl.json b/homeassistant/components/volumio/translations/pl.json new file mode 100644 index 00000000000000..431788c2c52fc0 --- /dev/null +++ b/homeassistant/components/volumio/translations/pl.json @@ -0,0 +1,24 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "cannot_connect": "Nie mo\u017cna po\u0142\u0105czy\u0107 si\u0119 z odnalezionym Volumio" + }, + "error": { + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "unknown": "Nieoczekiwany b\u0142\u0105d" + }, + "step": { + "discovery_confirm": { + "description": "Czy chcesz doda\u0107 Volumio (\"{name}\") do Home Assistant?", + "title": "Wykryte Volumio" + }, + "user": { + "data": { + "host": "Nazwa hosta lub adres IP", + "port": "Port" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/water_heater/group.py b/homeassistant/components/water_heater/group.py new file mode 100644 index 00000000000000..f4ec0ecbc2654c --- /dev/null +++ b/homeassistant/components/water_heater/group.py @@ -0,0 +1,34 @@ +"""Describe group states.""" + + +from homeassistant.components.group import GroupIntegrationRegistry +from homeassistant.const import STATE_OFF +from homeassistant.core import callback +from homeassistant.helpers.typing import HomeAssistantType + +from . import ( + STATE_ECO, + STATE_ELECTRIC, + STATE_GAS, + STATE_HEAT_PUMP, + STATE_HIGH_DEMAND, + STATE_PERFORMANCE, +) + + +@callback +def async_describe_on_off_states( + hass: HomeAssistantType, registry: GroupIntegrationRegistry +) -> None: + """Describe group on off states.""" + registry.on_off_states( + { + STATE_ECO, + STATE_ELECTRIC, + STATE_PERFORMANCE, + STATE_HIGH_DEMAND, + STATE_HEAT_PUMP, + STATE_GAS, + }, + STATE_OFF, + ) diff --git a/homeassistant/components/weather/group.py b/homeassistant/components/weather/group.py new file mode 100644 index 00000000000000..4741f8a3b548cd --- /dev/null +++ b/homeassistant/components/weather/group.py @@ -0,0 +1,14 @@ +"""Describe group states.""" + + +from homeassistant.components.group import GroupIntegrationRegistry +from homeassistant.core import callback +from homeassistant.helpers.typing import HomeAssistantType + + +@callback +def async_describe_on_off_states( + hass: HomeAssistantType, registry: GroupIntegrationRegistry +) -> None: + """Describe group on off states.""" + registry.exclude_domain() diff --git a/homeassistant/components/webostv/media_player.py b/homeassistant/components/webostv/media_player.py index e53e31856516d9..82950e4f7f8d2f 100644 --- a/homeassistant/components/webostv/media_player.py +++ b/homeassistant/components/webostv/media_player.py @@ -305,7 +305,9 @@ def supported_features(self): """Flag media player features that are supported.""" supported = SUPPORT_WEBOSTV - if self._client.sound_output == "external_arc": + if (self._client.sound_output == "external_arc") or ( + self._client.sound_output == "external_speaker" + ): supported = supported | SUPPORT_WEBOSTV_VOLUME elif self._client.sound_output != "lineout": supported = supported | SUPPORT_WEBOSTV_VOLUME | SUPPORT_VOLUME_SET @@ -318,10 +320,9 @@ def supported_features(self): @property def device_state_attributes(self): """Return device specific state attributes.""" - attributes = {} - if self._client.sound_output is not None and self.state != STATE_OFF: - attributes[ATTR_SOUND_OUTPUT] = self._client.sound_output - return attributes + if self._client.sound_output is None and self.state == STATE_OFF: + return {} + return {ATTR_SOUND_OUTPUT: self._client.sound_output} @cmd async def async_turn_off(self): diff --git a/homeassistant/components/webostv/services.yaml b/homeassistant/components/webostv/services.yaml index 5719e339793646..430916f7c71fb6 100644 --- a/homeassistant/components/webostv/services.yaml +++ b/homeassistant/components/webostv/services.yaml @@ -11,7 +11,7 @@ button: Name of the button to press. Known possible values are LEFT, RIGHT, DOWN, UP, HOME, MENU, BACK, ENTER, DASH, INFO, ASTERISK, CC, EXIT, MUTE, RED, GREEN, BLUE, VOLUMEUP, VOLUMEDOWN, CHANNELUP, CHANNELDOWN, - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + PLAY, PAUSE, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 example: "LEFT" command: diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py index 036cd690da2639..11d97f58f50642 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -17,6 +17,7 @@ from homeassistant.helpers import config_validation as cv, entity from homeassistant.helpers.event import TrackTemplate, async_track_template_result from homeassistant.helpers.service import async_get_all_descriptions +from homeassistant.helpers.template import Template from homeassistant.loader import IntegrationNotFound, async_get_integration from . import const, decorators, messages @@ -77,7 +78,7 @@ def forward_events(event): ): return - connection.send_message(messages.event_message(msg["id"], event)) + connection.send_message(messages.cached_event_message(msg["id"], event)) else: @@ -87,7 +88,7 @@ def forward_events(event): if event.event_type == EVENT_TIME_CHANGED: return - connection.send_message(messages.event_message(msg["id"], event.as_dict())) + connection.send_message(messages.cached_event_message(msg["id"], event)) connection.subscriptions[msg["id"]] = hass.bus.async_listen( event_type, forward_events @@ -238,36 +239,40 @@ def handle_ping(hass, connection, msg): connection.send_message(pong_message(msg["id"])) -@callback @decorators.websocket_command( { vol.Required("type"): "render_template", - vol.Required("template"): cv.template, + vol.Required("template"): str, vol.Optional("entity_ids"): cv.entity_ids, vol.Optional("variables"): dict, + vol.Optional("timeout"): vol.Coerce(float), } ) -def handle_render_template(hass, connection, msg): +@decorators.async_response +async def handle_render_template(hass, connection, msg): """Handle render_template command.""" - template = msg["template"] - template.hass = hass - + template_str = msg["template"] + template = Template(template_str, hass) variables = msg.get("variables") + timeout = msg.get("timeout") info = None + if timeout and await template.async_render_will_timeout(timeout): + connection.send_error( + msg["id"], + const.ERR_TEMPLATE_ERROR, + f"Exceeded maximum execution time of {timeout}s", + ) + return + @callback def _template_listener(event, updates): nonlocal info track_template_result = updates.pop() result = track_template_result.result if isinstance(result, TemplateError): - _LOGGER.error( - "TemplateError('%s') " "while processing template '%s'", - result, - track_template_result.template, - ) - - result = None + connection.send_error(msg["id"], const.ERR_TEMPLATE_ERROR, str(result)) + return connection.send_message( messages.event_message( @@ -275,9 +280,16 @@ def _template_listener(event, updates): ) ) - info = async_track_template_result( - hass, [TrackTemplate(template, variables)], _template_listener - ) + try: + info = async_track_template_result( + hass, + [TrackTemplate(template, variables)], + _template_listener, + raise_on_template_error=True, + ) + except TemplateError as ex: + connection.send_error(msg["id"], const.ERR_TEMPLATE_ERROR, str(ex)) + return connection.subscriptions[msg["id"]] = info.async_remove diff --git a/homeassistant/components/websocket_api/const.py b/homeassistant/components/websocket_api/const.py index f01a2880b9d817..5f2cfb2257dc8a 100644 --- a/homeassistant/components/websocket_api/const.py +++ b/homeassistant/components/websocket_api/const.py @@ -29,6 +29,7 @@ ERR_UNKNOWN_ERROR = "unknown_error" ERR_UNAUTHORIZED = "unauthorized" ERR_TIMEOUT = "timeout" +ERR_TEMPLATE_ERROR = "template_error" TYPE_RESULT = "result" diff --git a/homeassistant/components/websocket_api/http.py b/homeassistant/components/websocket_api/http.py index 7c56fcbc606862..b71b19d5181cec 100644 --- a/homeassistant/components/websocket_api/http.py +++ b/homeassistant/components/websocket_api/http.py @@ -11,17 +11,11 @@ from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import callback from homeassistant.helpers.event import async_call_later -from homeassistant.util.json import ( - find_paths_unserializable_data, - format_unserializable_data, -) from .auth import AuthPhase, auth_required_message from .const import ( CANCELLATION_ERRORS, DATA_CONNECTIONS, - ERR_UNKNOWN_ERROR, - JSON_DUMP, MAX_PENDING_MSG, PENDING_MSG_PEAK, PENDING_MSG_PEAK_TIME, @@ -30,7 +24,7 @@ URL, ) from .error import Disconnect -from .messages import error_message +from .messages import message_to_json # mypy: allow-untyped-calls, allow-untyped-defs, no-check-untyped-defs @@ -72,27 +66,10 @@ async def _writer(self): self._logger.debug("Sending %s", message) - if isinstance(message, str): - await self.wsock.send_str(message) - continue + if not isinstance(message, str): + message = message_to_json(message) - try: - dumped = JSON_DUMP(message) - except (ValueError, TypeError): - await self.wsock.send_json( - error_message( - message["id"], ERR_UNKNOWN_ERROR, "Invalid JSON in response" - ) - ) - self._logger.error( - "Unable to serialize to JSON. Bad data found at %s", - format_unserializable_data( - find_paths_unserializable_data(message, dump=JSON_DUMP) - ), - ) - continue - - await self.wsock.send_str(dumped) + await self.wsock.send_str(message) # Clean up the peaker checker when we shut down the writer if self._peak_checker_unsub: @@ -153,7 +130,7 @@ async def async_handle(self) -> web.WebSocketResponse: request = self.request wsock = self.wsock = web.WebSocketResponse(heartbeat=55) await wsock.prepare(request) - self._logger.debug("Connected") + self._logger.debug("Connected from %s", request.remote) self._handle_task = asyncio.current_task() @callback diff --git a/homeassistant/components/websocket_api/messages.py b/homeassistant/components/websocket_api/messages.py index 27d557e8110d33..52e97b60ccf074 100644 --- a/homeassistant/components/websocket_api/messages.py +++ b/homeassistant/components/websocket_api/messages.py @@ -1,11 +1,21 @@ """Message templates for websocket commands.""" +from functools import lru_cache +import logging +from typing import Any, Dict + import voluptuous as vol +from homeassistant.core import Event from homeassistant.helpers import config_validation as cv +from homeassistant.util.json import ( + find_paths_unserializable_data, + format_unserializable_data, +) from . import const +_LOGGER = logging.getLogger(__name__) # mypy: allow-untyped-defs # Minimal requirements of a message @@ -18,12 +28,12 @@ BASE_COMMAND_MESSAGE_SCHEMA = vol.Schema({vol.Required("id"): cv.positive_int}) -def result_message(iden, result=None): +def result_message(iden: int, result: Any = None) -> Dict: """Return a success result message.""" return {"id": iden, "type": const.TYPE_RESULT, "success": True, "result": result} -def error_message(iden, code, message): +def error_message(iden: int, code: str, message: str) -> Dict: """Return an error result message.""" return { "id": iden, @@ -33,6 +43,37 @@ def error_message(iden, code, message): } -def event_message(iden, event): +def event_message(iden: int, event: Any) -> Dict: """Return an event message.""" return {"id": iden, "type": "event", "event": event} + + +@lru_cache(maxsize=128) +def cached_event_message(iden: int, event: Event) -> str: + """Return an event message. + + Serialize to json once per message. + + Since we can have many clients connected that are + all getting many of the same events (mostly state changed) + we can avoid serializing the same data for each connection. + """ + return message_to_json(event_message(iden, event)) + + +def message_to_json(message: Any) -> str: + """Serialize a websocket message to json.""" + try: + return const.JSON_DUMP(message) + except (ValueError, TypeError): + _LOGGER.error( + "Unable to serialize to JSON. Bad data found at %s", + format_unserializable_data( + find_paths_unserializable_data(message, dump=const.JSON_DUMP) + ), + ) + return const.JSON_DUMP( + error_message( + message["id"], const.ERR_UNKNOWN_ERROR, "Invalid JSON in response" + ) + ) diff --git a/homeassistant/components/wemo/manifest.json b/homeassistant/components/wemo/manifest.json index bc9755414c654a..357d9d9548382d 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.4.46"], + "requirements": ["pywemo==0.5.0"], "ssdp": [ { "manufacturer": "Belkin International Inc." diff --git a/homeassistant/components/wemo/strings.json b/homeassistant/components/wemo/strings.json index fe8cc10a74c3b6..f7c6329b1af11c 100644 --- a/homeassistant/components/wemo/strings.json +++ b/homeassistant/components/wemo/strings.json @@ -6,8 +6,8 @@ } }, "abort": { - "single_instance_allowed": "Only a single configuration of Wemo is possible.", - "no_devices_found": "No Wemo devices found on the network." + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]", + "no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]" } } } diff --git a/homeassistant/components/wemo/translations/ca.json b/homeassistant/components/wemo/translations/ca.json index 007252042cc8c9..1216504eb855f3 100644 --- a/homeassistant/components/wemo/translations/ca.json +++ b/homeassistant/components/wemo/translations/ca.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "No s'han trobat dispositius Wemo a la xarxa.", - "single_instance_allowed": "Nom\u00e9s \u00e9s possible una \u00fanica configuraci\u00f3 de Wemo." + "no_devices_found": "No s'han trobat dispositius a la xarxa", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "step": { "confirm": { diff --git a/homeassistant/components/wemo/translations/en.json b/homeassistant/components/wemo/translations/en.json index 32ef4cc4cf597c..20a9af468ff19a 100644 --- a/homeassistant/components/wemo/translations/en.json +++ b/homeassistant/components/wemo/translations/en.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "No Wemo devices found on the network.", - "single_instance_allowed": "Only a single configuration of Wemo is possible." + "no_devices_found": "No devices found on the network", + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "step": { "confirm": { diff --git a/homeassistant/components/wemo/translations/it.json b/homeassistant/components/wemo/translations/it.json index 46204dc6057514..691c7ca46b3f61 100644 --- a/homeassistant/components/wemo/translations/it.json +++ b/homeassistant/components/wemo/translations/it.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Nessun dispositivo Wemo trovato in rete.", - "single_instance_allowed": "\u00c8 consentita solo una singola configurazione di Wemo." + "no_devices_found": "Nessun dispositivo trovato sulla rete", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "step": { "confirm": { diff --git a/homeassistant/components/wemo/translations/no.json b/homeassistant/components/wemo/translations/no.json index 59ff68d5790f53..59958ac048db2c 100644 --- a/homeassistant/components/wemo/translations/no.json +++ b/homeassistant/components/wemo/translations/no.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "Ingen Sonos enheter funnet p\u00e5 nettverket.", - "single_instance_allowed": "Kun en konfigurasjon av Wemo er mulig." + "no_devices_found": "Ingen enheter funnet p\u00e5 nettverket", + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "step": { "confirm": { diff --git a/homeassistant/components/wemo/translations/ru.json b/homeassistant/components/wemo/translations/ru.json index 7123ac1b60157f..60b1efb3dea4c7 100644 --- a/homeassistant/components/wemo/translations/ru.json +++ b/homeassistant/components/wemo/translations/ru.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 Wemo \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b.", - "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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." }, "step": { "confirm": { diff --git a/homeassistant/components/wemo/translations/zh-Hant.json b/homeassistant/components/wemo/translations/zh-Hant.json index 29167a13480049..0b9966135b158b 100644 --- a/homeassistant/components/wemo/translations/zh-Hant.json +++ b/homeassistant/components/wemo/translations/zh-Hant.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "no_devices_found": "\u5728\u7db2\u8def\u4e0a\u627e\u4e0d\u5230 Wemo \u8a2d\u5099\u3002", - "single_instance_allowed": "\u50c5\u5141\u8a31\u8a2d\u5b9a\u4e00\u7d44 Wemo\u3002" + "no_devices_found": "\u7db2\u8def\u4e0a\u627e\u4e0d\u5230\u8a2d\u5099", + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002" }, "step": { "confirm": { diff --git a/homeassistant/components/wiffi/strings.json b/homeassistant/components/wiffi/strings.json index e219b2ecae7d00..d4dc66972c768b 100644 --- a/homeassistant/components/wiffi/strings.json +++ b/homeassistant/components/wiffi/strings.json @@ -4,7 +4,7 @@ "user": { "title": "Setup TCP server for WIFFI devices", "data": { - "port": "Server Port" + "port": "[%key:common::config_flow::data::port%]" } } }, diff --git a/homeassistant/components/wiffi/translations/ca.json b/homeassistant/components/wiffi/translations/ca.json index 33fc5015ecdf97..6fe2792888c51a 100644 --- a/homeassistant/components/wiffi/translations/ca.json +++ b/homeassistant/components/wiffi/translations/ca.json @@ -7,7 +7,7 @@ "step": { "user": { "data": { - "port": "Port del servidor" + "port": "Port" }, "title": "Configuraci\u00f3 del servidor TCP per a dispositius WIFFI" } diff --git a/homeassistant/components/wiffi/translations/en.json b/homeassistant/components/wiffi/translations/en.json index 0ac1868714dd70..046f37de2c749f 100644 --- a/homeassistant/components/wiffi/translations/en.json +++ b/homeassistant/components/wiffi/translations/en.json @@ -7,7 +7,7 @@ "step": { "user": { "data": { - "port": "Server Port" + "port": "Port" }, "title": "Setup TCP server for WIFFI devices" } diff --git a/homeassistant/components/wiffi/translations/it.json b/homeassistant/components/wiffi/translations/it.json index 0884f31cbd7e5d..054bcbc986226d 100644 --- a/homeassistant/components/wiffi/translations/it.json +++ b/homeassistant/components/wiffi/translations/it.json @@ -7,7 +7,7 @@ "step": { "user": { "data": { - "port": "Porta del server" + "port": "Porta" }, "title": "Configurare il server TCP per i dispositivi WIFFI" } diff --git a/homeassistant/components/wiffi/translations/no.json b/homeassistant/components/wiffi/translations/no.json index 9f745e0e4a80f5..06e8f3449e14c8 100644 --- a/homeassistant/components/wiffi/translations/no.json +++ b/homeassistant/components/wiffi/translations/no.json @@ -7,7 +7,7 @@ "step": { "user": { "data": { - "port": "Serverport" + "port": "Port" }, "title": "Sett opp TCP-server for WIFFI-enheter" } diff --git a/homeassistant/components/wiffi/translations/pl.json b/homeassistant/components/wiffi/translations/pl.json index 810bc4fb1ecf58..5af119f71f2430 100644 --- a/homeassistant/components/wiffi/translations/pl.json +++ b/homeassistant/components/wiffi/translations/pl.json @@ -9,7 +9,7 @@ "data": { "port": "Port serwera" }, - "title": "Konfiguracja serwera TCP dla urz\u0105dze\u0144 WIFFI" + "title": "Konfiguracja serwera TCP dla urz\u0105dze\u0144 WiFi" } } }, diff --git a/homeassistant/components/wiffi/translations/ru.json b/homeassistant/components/wiffi/translations/ru.json index 50d0ec067180fb..f703edbf23b548 100644 --- a/homeassistant/components/wiffi/translations/ru.json +++ b/homeassistant/components/wiffi/translations/ru.json @@ -7,7 +7,7 @@ "step": { "user": { "data": { - "port": "\u041f\u043e\u0440\u0442 \u0441\u0435\u0440\u0432\u0435\u0440\u0430" + "port": "\u041f\u043e\u0440\u0442" }, "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 TCP-\u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u0434\u043b\u044f \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 WIFFI" } diff --git a/homeassistant/components/wiffi/translations/zh-Hant.json b/homeassistant/components/wiffi/translations/zh-Hant.json index 77b1488025cfab..ae2956cc5e9faa 100644 --- a/homeassistant/components/wiffi/translations/zh-Hant.json +++ b/homeassistant/components/wiffi/translations/zh-Hant.json @@ -7,7 +7,7 @@ "step": { "user": { "data": { - "port": "\u4f3a\u670d\u5668\u901a\u8a0a\u57e0" + "port": "\u901a\u8a0a\u57e0" }, "title": "\u8a2d\u5b9a WIFFI \u8a2d\u5099 TCP \u4f3a\u670d\u5668" } diff --git a/homeassistant/components/wilight/translations/de.json b/homeassistant/components/wilight/translations/de.json new file mode 100644 index 00000000000000..07d00495af7f9d --- /dev/null +++ b/homeassistant/components/wilight/translations/de.json @@ -0,0 +1,10 @@ +{ + "config": { + "flow_title": "WiLight: {name}", + "step": { + "confirm": { + "title": "WiLight" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wilight/translations/el.json b/homeassistant/components/wilight/translations/el.json new file mode 100644 index 00000000000000..3f5afd4d729ce7 --- /dev/null +++ b/homeassistant/components/wilight/translations/el.json @@ -0,0 +1,15 @@ +{ + "config": { + "abort": { + "not_supported_device": "\u0391\u03c5\u03c4\u03cc \u03c4\u03bf WiLight \u03b4\u03b5\u03bd \u03c5\u03c0\u03bf\u03c3\u03c4\u03b7\u03c1\u03af\u03b6\u03b5\u03c4\u03b1\u03b9 \u03c0\u03c1\u03bf\u03c2 \u03c4\u03bf \u03c0\u03b1\u03c1\u03cc\u03bd", + "not_wilight_device": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae \u03b4\u03b5\u03bd \u03b5\u03af\u03bd\u03b1\u03b9 WiLight" + }, + "flow_title": "WiLight: {\u03cc\u03bd\u03bf\u03bc\u03b1}", + "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" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wilight/translations/hu.json b/homeassistant/components/wilight/translations/hu.json new file mode 100644 index 00000000000000..3b2d79a34a77e2 --- /dev/null +++ b/homeassistant/components/wilight/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wilight/translations/ko.json b/homeassistant/components/wilight/translations/ko.json new file mode 100644 index 00000000000000..677b104c065634 --- /dev/null +++ b/homeassistant/components/wilight/translations/ko.json @@ -0,0 +1,12 @@ +{ + "config": { + "abort": { + "not_wilight_device": "\uc774 \uc7a5\uce58\ub294 WiLight\uac00 \uc544\ub2d9\ub2c8\ub2e4." + }, + "step": { + "confirm": { + "description": "WiLight {name} \uc744 \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c? \n\n \uc9c0\uc6d0 : {components}" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wilight/translations/nl.json b/homeassistant/components/wilight/translations/nl.json new file mode 100644 index 00000000000000..c04105e087870b --- /dev/null +++ b/homeassistant/components/wilight/translations/nl.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd", + "not_supported_device": "Deze WiLight wordt momenteel niet ondersteund", + "not_wilight_device": "Dit apparaat is geen WiLight" + }, + "flow_title": "WiLight: {name}", + "step": { + "confirm": { + "description": "Wil je WiLight {name} ? \n\n Het ondersteunt: {components}", + "title": "WiLight" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wilight/translations/pl.json b/homeassistant/components/wilight/translations/pl.json new file mode 100644 index 00000000000000..45c3d1f8990bed --- /dev/null +++ b/homeassistant/components/wilight/translations/pl.json @@ -0,0 +1,16 @@ +{ + "config": { + "abort": { + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "not_supported_device": "Ten WiLight nie jest obecnie obs\u0142ugiwany", + "not_wilight_device": "To urz\u0105dzenie nie jest urz\u0105dzeniem WiLight" + }, + "flow_title": "WiLight: {name}", + "step": { + "confirm": { + "description": "Czy chcesz skonfigurowa\u0107 WiLight {name}?\n\nObs\u0142uguje: {components}", + "title": "WiLight" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wink/binary_sensor.py b/homeassistant/components/wink/binary_sensor.py index d8967dd064dc89..77ff464a5bf523 100644 --- a/homeassistant/components/wink/binary_sensor.py +++ b/homeassistant/components/wink/binary_sensor.py @@ -3,7 +3,16 @@ import pywink -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_MOISTURE, + DEVICE_CLASS_MOTION, + DEVICE_CLASS_OCCUPANCY, + DEVICE_CLASS_OPENING, + DEVICE_CLASS_SMOKE, + DEVICE_CLASS_SOUND, + DEVICE_CLASS_VIBRATION, + BinarySensorEntity, +) from . import DOMAIN, WinkDevice @@ -12,17 +21,17 @@ # These are the available sensors mapped to binary_sensor class SENSOR_TYPES = { "brightness": "light", - "capturing_audio": "sound", + "capturing_audio": DEVICE_CLASS_SOUND, "capturing_video": None, "co_detected": "gas", - "liquid_detected": "moisture", - "loudness": "sound", - "motion": "motion", - "noise": "sound", - "opened": "opening", - "presence": "occupancy", - "smoke_detected": "smoke", - "vibration": "vibration", + "liquid_detected": DEVICE_CLASS_MOISTURE, + "loudness": DEVICE_CLASS_SOUND, + "motion": DEVICE_CLASS_MOTION, + "noise": DEVICE_CLASS_SOUND, + "opened": DEVICE_CLASS_OPENING, + "presence": DEVICE_CLASS_OCCUPANCY, + "smoke_detected": DEVICE_CLASS_SMOKE, + "vibration": DEVICE_CLASS_VIBRATION, } diff --git a/homeassistant/components/wirelesstag/__init__.py b/homeassistant/components/wirelesstag/__init__.py index 702479c4112c27..83e92c2250b72d 100644 --- a/homeassistant/components/wirelesstag/__init__.py +++ b/homeassistant/components/wirelesstag/__init__.py @@ -12,6 +12,7 @@ CONF_PASSWORD, CONF_USERNAME, PERCENTAGE, + SIGNAL_STRENGTH_DECIBELS_MILLIWATT, VOLT, ) import homeassistant.helpers.config_validation as cv @@ -226,11 +227,6 @@ def __init__(self, api, tag): self._name = self._tag.name self._state = None - @property - def should_poll(self): - """Return the polling state.""" - return True - @property def name(self): """Return the name of the sensor.""" @@ -281,7 +277,7 @@ def device_state_attributes(self): return { ATTR_BATTERY_LEVEL: int(self._tag.battery_remaining * 100), ATTR_VOLTAGE: f"{self._tag.battery_volts:.2f}{VOLT}", - ATTR_TAG_SIGNAL_STRENGTH: f"{self._tag.signal_strength}dBm", + ATTR_TAG_SIGNAL_STRENGTH: f"{self._tag.signal_strength}{SIGNAL_STRENGTH_DECIBELS_MILLIWATT}", ATTR_TAG_OUT_OF_RANGE: not self._tag.is_in_range, ATTR_TAG_POWER_CONSUMPTION: f"{self._tag.power_consumption:.2f}{PERCENTAGE}", } diff --git a/homeassistant/components/withings/common.py b/homeassistant/components/withings/common.py index af81c0e68d3390..067133f331ffd8 100644 --- a/homeassistant/components/withings/common.py +++ b/homeassistant/components/withings/common.py @@ -29,6 +29,7 @@ from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_WEBHOOK_ID, + HTTP_UNAUTHORIZED, MASS_KILOGRAMS, PERCENTAGE, SPEED_METERS_PER_SECOND, @@ -54,7 +55,7 @@ _LOGGER = logging.getLogger(const.LOG_NAMESPACE) NOT_AUTHENTICATED_ERROR = re.compile( - "^401,.*", + f"^{HTTP_UNAUTHORIZED},.*", re.IGNORECASE, ) DATA_UPDATED_SIGNAL = "withings_entity_state_updated" diff --git a/homeassistant/components/withings/strings.json b/homeassistant/components/withings/strings.json index c9d2d7ca22ce6d..05f6d15ca1191f 100644 --- a/homeassistant/components/withings/strings.json +++ b/homeassistant/components/withings/strings.json @@ -7,7 +7,7 @@ "description": "Provide a unique profile name for this data. Typically this is the name of the profile you selected in the previous step.", "data": { "profile": "Profile Name" } }, - "pick_implementation": { "title": "Pick Authentication Method" }, + "pick_implementation": { "title": "[%key:common::config_flow::title::oauth2_pick_implementation%]" }, "reauth": { "title": "Re-authenticate Profile", "description": "The \"{profile}\" profile needs to be re-authenticated in order to continue receiving Withings data." diff --git a/homeassistant/components/withings/translations/es.json b/homeassistant/components/withings/translations/es.json index e59b6e96775466..d8b69013bbddde 100644 --- a/homeassistant/components/withings/translations/es.json +++ b/homeassistant/components/withings/translations/es.json @@ -3,7 +3,8 @@ "abort": { "already_configured": "Configuraci\u00f3n actualizada para el perfil.", "authorize_url_timeout": "Tiempo de espera agotado generando la url de autorizaci\u00f3n.", - "missing_configuration": "La integraci\u00f3n de Withings no est\u00e1 configurada. Por favor, siga la documentaci\u00f3n." + "missing_configuration": "La integraci\u00f3n de Withings no est\u00e1 configurada. Por favor, siga 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})" }, "create_entry": { "default": "Autenticado correctamente con Withings." diff --git a/homeassistant/components/withings/translations/fr.json b/homeassistant/components/withings/translations/fr.json index 7ddb1049abb428..a51efff72766d2 100644 --- a/homeassistant/components/withings/translations/fr.json +++ b/homeassistant/components/withings/translations/fr.json @@ -3,7 +3,8 @@ "abort": { "already_configured": "Configuration mise \u00e0 jour pour le profil.", "authorize_url_timeout": "D\u00e9lai d'expiration g\u00e9n\u00e9rant une URL d'autorisation.", - "missing_configuration": "L'int\u00e9gration Withings n'est pas configur\u00e9e. Veuillez suivre la documentation." + "missing_configuration": "L'int\u00e9gration Withings n'est pas configur\u00e9e. Veuillez suivre la documentation.", + "no_url_available": "Aucune URL disponible. Pour plus d'informations sur cette erreur, [consultez la section d'aide] ( {docs_url} )" }, "create_entry": { "default": "Authentifi\u00e9 avec succ\u00e8s \u00e0 Withings pour le profil s\u00e9lectionn\u00e9." diff --git a/homeassistant/components/withings/translations/ko.json b/homeassistant/components/withings/translations/ko.json index 74dacdd2cac024..a01672f22272a9 100644 --- a/homeassistant/components/withings/translations/ko.json +++ b/homeassistant/components/withings/translations/ko.json @@ -3,7 +3,8 @@ "abort": { "already_configured": "\ud504\ub85c\ud544\uc5d0 \ub300\ud55c \uad6c\uc131\uc774 \uc5c5\ub370\uc774\ud2b8\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", "authorize_url_timeout": "\uc778\uc99d url \uc0dd\uc131 \uc2dc\uac04\uc774 \ucd08\uacfc\ub418\uc5c8\uc2b5\ub2c8\ub2e4.", - "missing_configuration": "Withings \uad6c\uc131\uc694\uc18c\uac00 \uad6c\uc131\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4. \uc124\uba85\uc11c\ub97c \ucc38\uace0\ud574\uc8fc\uc138\uc694." + "missing_configuration": "Withings \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": "\uac00\ub2a5\ud55c URL\uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uc774 \uc5d0\ub7ec\uc5d0 \ub300\ud55c \uc815\ubcf4\ub294 \ub3c4\uc6c0\ub9d0 \uc139\uc158\uc744 \ud655\uc778\ud558\uc138\uc694({docs_url})" }, "create_entry": { "default": "Withings \ub85c \uc131\uacf5\uc801\uc73c\ub85c \uc778\uc99d\ub418\uc5c8\uc2b5\ub2c8\ub2e4." diff --git a/homeassistant/components/withings/translations/lb.json b/homeassistant/components/withings/translations/lb.json index a5102baf917a2f..e3ff23e392a65c 100644 --- a/homeassistant/components/withings/translations/lb.json +++ b/homeassistant/components/withings/translations/lb.json @@ -3,7 +3,8 @@ "abort": { "already_configured": "Konfiguratioun aktualis\u00e9iert fir de Profil.", "authorize_url_timeout": "Z\u00e4it Iwwerschreidung beim gener\u00e9ieren vun der Autorisatiouns URL.", - "missing_configuration": "Withings Integratioun ass nach net konfigur\u00e9iert. Follegt w.e.g der Dokumentatioun." + "missing_configuration": "Withings Integratioun ass nach net konfigur\u00e9iert. Follegt w.e.g der Dokumentatioun.", + "no_url_available": "Keng URL disponibel. Fir Informatiounen iwwert d\u00ebse Feeler, [kuck H\u00ebllef Sektioun]({docs_url})" }, "create_entry": { "default": "Erfollegr\u00e4ich mat Withings authentifiz\u00e9iert." diff --git a/homeassistant/components/withings/translations/nl.json b/homeassistant/components/withings/translations/nl.json index 4f382f02a57019..a54333ab8f2cec 100644 --- a/homeassistant/components/withings/translations/nl.json +++ b/homeassistant/components/withings/translations/nl.json @@ -2,7 +2,8 @@ "config": { "abort": { "authorize_url_timeout": "Time-out tijdens genereren autorisatie url.", - "missing_configuration": "De Withings integratie is niet geconfigureerd. Gelieve de documentatie te volgen." + "missing_configuration": "De Withings integratie is niet geconfigureerd. Gelieve de documentatie te volgen.", + "no_url_available": "Geen URL beschikbaar. Voor informatie over deze fout, [check de helpsectie]({docs_url})" }, "create_entry": { "default": "Succesvol geverifieerd met Withings voor het geselecteerde profiel." diff --git a/homeassistant/components/withings/translations/no.json b/homeassistant/components/withings/translations/no.json index 2b39f8fceab77b..bdec62a716087c 100644 --- a/homeassistant/components/withings/translations/no.json +++ b/homeassistant/components/withings/translations/no.json @@ -3,7 +3,8 @@ "abort": { "already_configured": "Konfigurasjon oppdatert for profil.", "authorize_url_timeout": "Tidsavbrudd ved oppretting av godkjenningsadresse.", - "missing_configuration": "Withings-integrasjonen er ikke konfigurert. Vennligst f\u00f8lg dokumentasjonen." + "missing_configuration": "Withings-integrasjonen er ikke konfigurert. Vennligst f\u00f8lg dokumentasjonen.", + "no_url_available": "Ingen URL tilgjengelig. For informasjon om denne feilen, sjekk {docs_url} ] ( {docs_url} )" }, "create_entry": { "default": "Vellykket godkjenning med Withings." diff --git a/homeassistant/components/withings/translations/zh-Hant.json b/homeassistant/components/withings/translations/zh-Hant.json index 3f31c0585f819a..02e6d6f669cc3c 100644 --- a/homeassistant/components/withings/translations/zh-Hant.json +++ b/homeassistant/components/withings/translations/zh-Hant.json @@ -3,7 +3,8 @@ "abort": { "already_configured": "\u6b64\u500b\u4eba\u8a2d\u7f6e\u8a2d\u5b9a\u5df2\u66f4\u65b0\u3002", "authorize_url_timeout": "\u7522\u751f\u8a8d\u8b49 URL \u6642\u903e\u6642\u3002", - "missing_configuration": "Withings \u6574\u5408\u5c1a\u672a\u8a2d\u7f6e\uff0c\u8acb\u53c3\u95b1\u6587\u4ef6\u8aaa\u660e\u3002" + "missing_configuration": "Withings \u6574\u5408\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})" }, "create_entry": { "default": "\u5df2\u6210\u529f\u8a8d\u8b49 Withings \u8a2d\u5099\u3002" diff --git a/homeassistant/components/wled/config_flow.py b/homeassistant/components/wled/config_flow.py index ecf8ca6e1e008b..a3113335fb5dec 100644 --- a/homeassistant/components/wled/config_flow.py +++ b/homeassistant/components/wled/config_flow.py @@ -36,7 +36,7 @@ async def async_step_zeroconf( ) -> Dict[str, Any]: """Handle zeroconf discovery.""" if user_input is None: - return self.async_abort(reason="connection_error") + return self.async_abort(reason="cannot_connect") # Hostname is format: wled-livingroom.local. host = user_input["hostname"].rstrip(".") @@ -86,8 +86,8 @@ async def _handle_config_flow( device = await wled.update() except WLEDConnectionError: if source == SOURCE_ZEROCONF: - return self.async_abort(reason="connection_error") - return self._show_setup_form({"base": "connection_error"}) + return self.async_abort(reason="cannot_connect") + return self._show_setup_form({"base": "cannot_connect"}) user_input[CONF_MAC] = device.info.mac_address # Check if already configured diff --git a/homeassistant/components/wled/const.py b/homeassistant/components/wled/const.py index 6006952a5801f9..f50e17c01cca2b 100644 --- a/homeassistant/components/wled/const.py +++ b/homeassistant/components/wled/const.py @@ -26,7 +26,6 @@ # Units of measurement CURRENT_MA = "mA" -SIGNAL_DBM = "dBm" # Services SERVICE_EFFECT = "effect" diff --git a/homeassistant/components/wled/sensor.py b/homeassistant/components/wled/sensor.py index 63a253e6efc1e8..06445d03ba947a 100644 --- a/homeassistant/components/wled/sensor.py +++ b/homeassistant/components/wled/sensor.py @@ -10,13 +10,14 @@ DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_TIMESTAMP, PERCENTAGE, + SIGNAL_STRENGTH_DECIBELS_MILLIWATT, ) from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util.dt import utcnow from . import WLEDDataUpdateCoordinator, WLEDDeviceEntity -from .const import ATTR_LED_COUNT, ATTR_MAX_POWER, CURRENT_MA, DOMAIN, SIGNAL_DBM +from .const import ATTR_LED_COUNT, ATTR_MAX_POWER, CURRENT_MA, DOMAIN _LOGGER = logging.getLogger(__name__) @@ -192,7 +193,7 @@ def __init__(self, entry_id: str, coordinator: WLEDDataUpdateCoordinator) -> Non icon="mdi:wifi", key="wifi_rssi", name=f"{coordinator.data.info.name} Wi-Fi RSSI", - unit_of_measurement=SIGNAL_DBM, + unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT, ) @property diff --git a/homeassistant/components/wled/strings.json b/homeassistant/components/wled/strings.json index 1197517917c862..f60a4bf3563224 100644 --- a/homeassistant/components/wled/strings.json +++ b/homeassistant/components/wled/strings.json @@ -14,11 +14,11 @@ } }, "error": { - "connection_error": "Failed to connect to WLED device." + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, "abort": { - "already_configured": "This WLED device is already configured.", - "connection_error": "Failed to connect to WLED device." + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/wled/translations/ca.json b/homeassistant/components/wled/translations/ca.json index 72a8fc519b4707..6eee56045b67d5 100644 --- a/homeassistant/components/wled/translations/ca.json +++ b/homeassistant/components/wled/translations/ca.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Aquest dispositiu WLED ja est\u00e0 configurat.", + "already_configured": "El dispositiu ja est\u00e0 configurat", + "cannot_connect": "Ha fallat la connexi\u00f3", "connection_error": "No s'ha pogut connectar amb el dispositiu WLED." }, "error": { + "cannot_connect": "Ha fallat la connexi\u00f3", "connection_error": "No s'ha pogut connectar amb el dispositiu WLED." }, "flow_title": "WLED: {name}", diff --git a/homeassistant/components/wled/translations/el.json b/homeassistant/components/wled/translations/el.json new file mode 100644 index 00000000000000..58012c1e4e38de --- /dev/null +++ b/homeassistant/components/wled/translations/el.json @@ -0,0 +1,10 @@ +{ + "config": { + "abort": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wled/translations/en.json b/homeassistant/components/wled/translations/en.json index 2576270808d882..aa448672bf2ea9 100644 --- a/homeassistant/components/wled/translations/en.json +++ b/homeassistant/components/wled/translations/en.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "This WLED device is already configured.", + "already_configured": "Device is already configured", + "cannot_connect": "Failed to connect", "connection_error": "Failed to connect to WLED device." }, "error": { + "cannot_connect": "Failed to connect", "connection_error": "Failed to connect to WLED device." }, "flow_title": "WLED: {name}", diff --git a/homeassistant/components/wled/translations/es.json b/homeassistant/components/wled/translations/es.json index aab9752d8b6ed9..5f8a721153156f 100644 --- a/homeassistant/components/wled/translations/es.json +++ b/homeassistant/components/wled/translations/es.json @@ -2,9 +2,11 @@ "config": { "abort": { "already_configured": "Este dispositivo WLED ya est\u00e1 configurado.", + "cannot_connect": "No se pudo conectar", "connection_error": "No se ha podido conectar al dispositivo WLED." }, "error": { + "cannot_connect": "No se pudo conectar", "connection_error": "No se ha podido conectar al dispositivo WLED." }, "flow_title": "WLED: {name}", diff --git a/homeassistant/components/wled/translations/et.json b/homeassistant/components/wled/translations/et.json new file mode 100644 index 00000000000000..4c5b7f22a9887d --- /dev/null +++ b/homeassistant/components/wled/translations/et.json @@ -0,0 +1,26 @@ +{ + "config": { + "abort": { + "already_configured": "See WLED seade on juba h\u00e4\u00e4lestatud.", + "cannot_connect": "\u00dchendamine nurjus", + "connection_error": "WLED-seadmega \u00fchenduse loomine nurjus." + }, + "error": { + "cannot_connect": "\u00dchendamine nurjus", + "connection_error": "WLED-seadmega \u00fchenduse loomine nurjus." + }, + "flow_title": "WLED: {name}", + "step": { + "user": { + "data": { + "host": "Host" + }, + "description": "Seadista WLED-i sidumine Home Assistantiga." + }, + "zeroconf_confirm": { + "description": "Kas soovid lisada WLED {nimi} Home Assistanti?", + "title": "Leitud WLED seade" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wled/translations/fr.json b/homeassistant/components/wled/translations/fr.json index 694627b8baf90b..79ab5ab876b06c 100644 --- a/homeassistant/components/wled/translations/fr.json +++ b/homeassistant/components/wled/translations/fr.json @@ -2,9 +2,11 @@ "config": { "abort": { "already_configured": "Cet appareil WLED est d\u00e9j\u00e0 configur\u00e9.", + "cannot_connect": "\u00c9chec de connexion", "connection_error": "Impossible de se connecter au p\u00e9riph\u00e9rique WLED." }, "error": { + "cannot_connect": "\u00c9chec de connexion", "connection_error": "Impossible de se connecter au p\u00e9riph\u00e9rique WLED." }, "flow_title": "WLED: {name}", diff --git a/homeassistant/components/wled/translations/it.json b/homeassistant/components/wled/translations/it.json index 897c04539f03b5..d6760328db5c74 100644 --- a/homeassistant/components/wled/translations/it.json +++ b/homeassistant/components/wled/translations/it.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Questo dispositivo WLED \u00e8 gi\u00e0 configurato.", + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "cannot_connect": "Impossibile connettersi", "connection_error": "Impossibile connettersi al dispositivo WLED." }, "error": { + "cannot_connect": "Impossibile connettersi", "connection_error": "Impossibile connettersi al dispositivo WLED." }, "flow_title": "WLED: {name}", diff --git a/homeassistant/components/wled/translations/lb.json b/homeassistant/components/wled/translations/lb.json index 1490e62e068b2b..6843b74174fb99 100644 --- a/homeassistant/components/wled/translations/lb.json +++ b/homeassistant/components/wled/translations/lb.json @@ -2,9 +2,11 @@ "config": { "abort": { "already_configured": "D\u00ebsen WLED Apparat ass scho konfigur\u00e9iert.", + "cannot_connect": "Feeler beim verbannen", "connection_error": "Feeler beim verbannen mam WLED Apparat." }, "error": { + "cannot_connect": "Feeler beim verbannen", "connection_error": "Feeler beim verbannen mam WLED Apparat." }, "flow_title": "WLED: {name}", diff --git a/homeassistant/components/wled/translations/nl.json b/homeassistant/components/wled/translations/nl.json index 794f68256a731c..ebbc0e52cde98d 100644 --- a/homeassistant/components/wled/translations/nl.json +++ b/homeassistant/components/wled/translations/nl.json @@ -1,10 +1,23 @@ { "config": { + "abort": { + "already_configured": "Dit WLED-apparaat is al geconfigureerd.", + "connection_error": "Kan geen verbinding maken met WLED-apparaat." + }, + "error": { + "connection_error": "Kan geen verbinding maken met WLED-apparaat." + }, + "flow_title": "WLED: {name}", "step": { "user": { "data": { "host": "Hostnaam of IP-adres" - } + }, + "description": "Stel uw WLED-integratie in met Home Assistant." + }, + "zeroconf_confirm": { + "description": "Wil je de WLED genaamd `{name}` toevoegen aan Home Assistant?", + "title": "Ontdekt WLED-apparaat" } } } diff --git a/homeassistant/components/wled/translations/no.json b/homeassistant/components/wled/translations/no.json index da372daad11b95..6352c5ccae561f 100644 --- a/homeassistant/components/wled/translations/no.json +++ b/homeassistant/components/wled/translations/no.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "Denne WLED-enheten er allerede konfigurert.", + "already_configured": "Enheten er allerede konfigurert", + "cannot_connect": "Tilkobling mislyktes.", "connection_error": "Kunne ikke koble til WLED-enheten." }, "error": { + "cannot_connect": "Tilkobling mislyktes.", "connection_error": "Kunne ikke koble til WLED-enheten." }, "flow_title": "WLED: {name}", diff --git a/homeassistant/components/wled/translations/pl.json b/homeassistant/components/wled/translations/pl.json index 6f68055d385485..7ff1a406320caf 100644 --- a/homeassistant/components/wled/translations/pl.json +++ b/homeassistant/components/wled/translations/pl.json @@ -1,11 +1,12 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", - "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, "error": { - "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia." + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "connection_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, "flow_title": "WLED: {name}", "step": { diff --git a/homeassistant/components/wled/translations/ru.json b/homeassistant/components/wled/translations/ru.json index 5d50b75b94f6e2..3700aa6cc308b6 100644 --- a/homeassistant/components/wled/translations/ru.json +++ b/homeassistant/components/wled/translations/ru.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", + "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.", + "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 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443." }, "error": { + "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 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443." }, "flow_title": "WLED: {name}", diff --git a/homeassistant/components/wled/translations/zh-Hant.json b/homeassistant/components/wled/translations/zh-Hant.json index 87490dc4595c60..26d49a34688668 100644 --- a/homeassistant/components/wled/translations/zh-Hant.json +++ b/homeassistant/components/wled/translations/zh-Hant.json @@ -1,10 +1,12 @@ { "config": { "abort": { - "already_configured": "WLED \u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", + "cannot_connect": "\u9023\u7dda\u5931\u6557", "connection_error": "WLED \u8a2d\u5099\u9023\u7dda\u5931\u6557\u3002" }, "error": { + "cannot_connect": "\u9023\u7dda\u5931\u6557", "connection_error": "WLED \u8a2d\u5099\u9023\u7dda\u5931\u6557\u3002" }, "flow_title": "WLED\uff1a{name}", diff --git a/homeassistant/components/wolflink/__init__.py b/homeassistant/components/wolflink/__init__.py index 9a272c502a0345..611fa7da315031 100644 --- a/homeassistant/components/wolflink/__init__.py +++ b/homeassistant/components/wolflink/__init__.py @@ -2,7 +2,7 @@ from datetime import timedelta import logging -from httpcore import ConnectError +from httpcore import ConnectError, ConnectTimeout from wolf_smartset.token_auth import InvalidAuth from wolf_smartset.wolf_client import WolfClient @@ -99,7 +99,7 @@ async def fetch_parameters(client: WolfClient, gateway_id: int, device_id: int): try: fetched_parameters = await client.fetch_parameters(gateway_id, device_id) return [param for param in fetched_parameters if param.name != "Reglertyp"] - except ConnectError as exception: + except (ConnectError, ConnectTimeout) as exception: raise UpdateFailed(f"Error communicating with API: {exception}") from exception except InvalidAuth as exception: - raise UpdateFailed("Invalid authentication during update.") from exception + raise UpdateFailed("Invalid authentication during update") from exception diff --git a/homeassistant/components/wolflink/manifest.json b/homeassistant/components/wolflink/manifest.json index c188c0903695a9..633318f2f62b32 100644 --- a/homeassistant/components/wolflink/manifest.json +++ b/homeassistant/components/wolflink/manifest.json @@ -3,6 +3,6 @@ "name": "Wolf SmartSet Service", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/wolflink", - "requirements": ["wolf_smartset==0.1.4"], + "requirements": ["wolf_smartset==0.1.6"], "codeowners": ["@adamkrol93"] } diff --git a/homeassistant/components/wolflink/sensor.py b/homeassistant/components/wolflink/sensor.py index 97f48e279889a5..1cae006824bee4 100644 --- a/homeassistant/components/wolflink/sensor.py +++ b/homeassistant/components/wolflink/sensor.py @@ -11,13 +11,6 @@ Temperature, ) -from homeassistant.components.wolflink.const import ( - COORDINATOR, - DEVICE_ID, - DOMAIN, - PARAMETERS, - STATES, -) from homeassistant.const import ( DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, @@ -27,6 +20,8 @@ ) from homeassistant.helpers.update_coordinator import CoordinatorEntity +from .const import COORDINATOR, DEVICE_ID, DOMAIN, PARAMETERS, STATES + _LOGGER = logging.getLogger(__name__) @@ -63,6 +58,7 @@ def __init__(self, coordinator, wolf_object: Parameter, device_id): super().__init__(coordinator) self.wolf_object = wolf_object self.device_id = device_id + self._state = None @property def name(self): @@ -71,8 +67,10 @@ def name(self): @property def state(self): - """Return the state.""" - return self.coordinator.data[self.wolf_object.value_id] + """Return the state. Wolf Client is returning only changed values so we need to store old value here.""" + if self.wolf_object.value_id in self.coordinator.data: + self._state = self.coordinator.data[self.wolf_object.value_id] + return self._state @property def device_state_attributes(self): @@ -151,7 +149,7 @@ def device_class(self): @property def state(self): """Return the state converting with supported values.""" - state = self.coordinator.data[self.wolf_object.value_id] + state = super().state resolved_state = [ item for item in self.wolf_object.items if item.value == int(state) ] diff --git a/homeassistant/components/wolflink/strings.sensor.json b/homeassistant/components/wolflink/strings.sensor.json index 2ce7df6fae5dd6..75c8199a117517 100644 --- a/homeassistant/components/wolflink/strings.sensor.json +++ b/homeassistant/components/wolflink/strings.sensor.json @@ -6,7 +6,7 @@ "aus": "Disabled", "standby": "Standby", "auto": "Auto", - "permanent": "Permament", + "permanent": "Permanent", "initialisierung": "Initialization", "antilegionellenfunktion": "Anti-legionella Function", "fernschalter_ein": "Remote control enabled", diff --git a/homeassistant/components/wolflink/translations/de.json b/homeassistant/components/wolflink/translations/de.json new file mode 100644 index 00000000000000..cb7e571d1e6998 --- /dev/null +++ b/homeassistant/components/wolflink/translations/de.json @@ -0,0 +1,17 @@ +{ + "config": { + "step": { + "device": { + "data": { + "device_name": "Ger\u00e4t" + } + }, + "user": { + "data": { + "password": "Passwort", + "username": "Benutzername" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wolflink/translations/fr.json b/homeassistant/components/wolflink/translations/fr.json index aa84ec33d8cdad..6e3348c3647559 100644 --- a/homeassistant/components/wolflink/translations/fr.json +++ b/homeassistant/components/wolflink/translations/fr.json @@ -9,11 +9,18 @@ "unknown": "Erreur inattendue" }, "step": { + "device": { + "data": { + "device_name": "Appareil" + }, + "title": "S\u00e9lectionnez l'appareil WOLF" + }, "user": { "data": { "password": "Mot de passe", "username": "Nom d'utilisateur" - } + }, + "title": "Connexion WOLF SmartSet" } } } diff --git a/homeassistant/components/wolflink/translations/hu.json b/homeassistant/components/wolflink/translations/hu.json new file mode 100644 index 00000000000000..3b2d79a34a77e2 --- /dev/null +++ b/homeassistant/components/wolflink/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wolflink/translations/nl.json b/homeassistant/components/wolflink/translations/nl.json new file mode 100644 index 00000000000000..4d00f0bfc74883 --- /dev/null +++ b/homeassistant/components/wolflink/translations/nl.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "user": { + "data": { + "username": "Gebruikersnaam" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wolflink/translations/pl.json b/homeassistant/components/wolflink/translations/pl.json index 483c73aac3d762..d6d42eafb8aa7f 100644 --- a/homeassistant/components/wolflink/translations/pl.json +++ b/homeassistant/components/wolflink/translations/pl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane." + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane" }, "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", + "invalid_auth": "Niepoprawne uwierzytelnienie", "unknown": "[%key::common::config_flow::error::unknown%]" }, "step": { diff --git a/homeassistant/components/wolflink/translations/sensor.de.json b/homeassistant/components/wolflink/translations/sensor.de.json new file mode 100644 index 00000000000000..ef60c1c1ae1487 --- /dev/null +++ b/homeassistant/components/wolflink/translations/sensor.de.json @@ -0,0 +1,8 @@ +{ + "state": { + "wolflink__state": { + "test": "Test", + "tpw": "TPW" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wolflink/translations/sensor.en.json b/homeassistant/components/wolflink/translations/sensor.en.json index ea60e2339077fd..bd505e845aec22 100644 --- a/homeassistant/components/wolflink/translations/sensor.en.json +++ b/homeassistant/components/wolflink/translations/sensor.en.json @@ -50,7 +50,7 @@ "parallelbetrieb": "Parallel mode", "partymodus": "Party mode", "perm_cooling": "PermCooling", - "permanent": "Permament", + "permanent": "Permanent", "permanentbetrieb": "Permanent mode", "reduzierter_betrieb": "Limited mode", "rt_abschaltung": "RT shutdown", diff --git a/homeassistant/components/wolflink/translations/sensor.fr.json b/homeassistant/components/wolflink/translations/sensor.fr.json new file mode 100644 index 00000000000000..57cd8435f352b0 --- /dev/null +++ b/homeassistant/components/wolflink/translations/sensor.fr.json @@ -0,0 +1,87 @@ +{ + "state": { + "wolflink__state": { + "1_x_warmwasser": "1 x ECS", + "abgasklappe": "Amortisseur de gaz de combustion", + "absenkbetrieb": "Mode Recul", + "absenkstop": "Arr\u00eat de recul", + "aktiviert": "Activ\u00e9", + "antilegionellenfunktion": "Fonction anti-l\u00e9gionelle", + "at_abschaltung": "Arr\u00eat OT", + "at_frostschutz": "Protection antigel OT", + "aus": "D\u00e9sactiv\u00e9", + "auto": "Auto", + "auto_off_cool": "AutoOffCool", + "auto_on_cool": "AutoOnCool", + "automatik_aus": "Arr\u00eat automatique", + "automatik_ein": "Mise en marche automatique", + "bereit_keine_ladung": "Pr\u00eat, pas de chargement", + "betrieb_ohne_brenner": "Travaille sans br\u00fbleur", + "cooling": "Refroidissement", + "deaktiviert": "Inactif", + "dhw_prior": "Priorit\u00e9 ECS", + "eco": "\u00c9co", + "ein": "Activ\u00e9", + "estrichtrocknung": "S\u00e9chage de chape", + "externe_deaktivierung": "D\u00e9sactivation externe", + "fernschalter_ein": "Contr\u00f4le \u00e0 distance activ\u00e9", + "frost_heizkreis": "Gel du circuit de chauffage", + "frost_warmwasser": "Gel ECS", + "frostschutz": "Protection antigel", + "gasdruck": "Pression du gaz", + "glt_betrieb": "Mode BMS", + "gradienten_uberwachung": "Surveillance de gradient", + "heizbetrieb": "Mode chauffage", + "heizgerat_mit_speicher": "Chaudi\u00e8re \u00e0 cylindre", + "heizung": "En chauffe", + "initialisierung": "Initialisation", + "kalibration": "\u00c9talonnage", + "kalibration_heizbetrieb": "Calibrage du mode de chauffage", + "kalibration_kombibetrieb": "\u00c9talonnage du mode Combi", + "kalibration_warmwasserbetrieb": "Calibrage ECS", + "kaskadenbetrieb": "Fonctionnement en cascade", + "kombibetrieb": "Mode Combi", + "kombigerat": "Chaudi\u00e8re combi", + "kombigerat_mit_solareinbindung": "Chaudi\u00e8re mixte avec int\u00e9gration solaire", + "mindest_kombizeit": "Temps combin\u00e9 minimum", + "nachlauf_heizkreispumpe": "Pompe du circuit de chauffage en marche", + "nachspulen": "Apr\u00e8s rin\u00e7age", + "nur_heizgerat": "Chaudi\u00e8re seulement", + "parallelbetrieb": "Mode parall\u00e8le", + "partymodus": "Mode festif", + "perm_cooling": "Refroidissement permanent", + "permanent": "Permanent", + "permanentbetrieb": "Mode permanent", + "reduzierter_betrieb": "Mode limit\u00e9", + "rt_abschaltung": "Arr\u00eat RT", + "rt_frostschutz": "Protection antigel RT", + "ruhekontakt": "Contact de repos", + "schornsteinfeger": "Test d'\u00e9missions", + "smart_grid": "SmartGrid", + "smart_home": "SmartHome", + "softstart": "D\u00e9marrage progressif", + "solarbetrieb": "Mode solaire", + "sparbetrieb": "Mode \u00e9conomie", + "sparen": "\u00c9conomie", + "spreizung_hoch": "dT trop large", + "spreizung_kf": "Spread KF", + "stabilisierung": "Stabilisation", + "standby": "En veille", + "start": "D\u00e9marrer", + "storung": "Faute", + "taktsperre": "Anti-cycle", + "telefonfernschalter": "Commutateur \u00e0 distance t\u00e9l\u00e9phonique", + "test": "Test", + "tpw": "TPW", + "urlaubsmodus": "Mode vacances", + "ventilprufung": "Test de valve", + "vorspulen": "Rin\u00e7age d'entr\u00e9e", + "warmwasser": "ECS", + "warmwasser_schnellstart": "D\u00e9marrage rapide ECS", + "warmwasserbetrieb": "Mode ECS", + "warmwassernachlauf": "ECS en marche", + "warmwasservorrang": "Priorit\u00e9 ECS", + "zunden": "Allumage" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/wolflink/translations/sensor.no.json b/homeassistant/components/wolflink/translations/sensor.no.json index fcd93f0b01b12c..75aa91bcf9cc58 100644 --- a/homeassistant/components/wolflink/translations/sensor.no.json +++ b/homeassistant/components/wolflink/translations/sensor.no.json @@ -50,7 +50,7 @@ "parallelbetrieb": "Parallell modus", "partymodus": "Festmodus", "perm_cooling": "PermKj\u00f8ling", - "permanent": "permament", + "permanent": "Permanent", "permanentbetrieb": "Permanent modus", "reduzierter_betrieb": "Begrenset modus", "rt_abschaltung": "RT-avstengning", diff --git a/homeassistant/components/wolflink/translations/sensor.zh-Hant.json b/homeassistant/components/wolflink/translations/sensor.zh-Hant.json index c2be9263bcf391..d9c90824742a6f 100644 --- a/homeassistant/components/wolflink/translations/sensor.zh-Hant.json +++ b/homeassistant/components/wolflink/translations/sensor.zh-Hant.json @@ -50,7 +50,7 @@ "parallelbetrieb": "\u4e26\u884c\u6a21\u5f0f", "partymodus": "\u6d3e\u5c0d\u6a21\u5f0f", "perm_cooling": "PermCooling", - "permanent": "\u6c38\u4e45", + "permanent": "\u56fa\u5b9a", "permanentbetrieb": "\u6c38\u4e45\u6a21\u5f0f", "reduzierter_betrieb": "\u9650\u5236\u6a21\u5f0f", "rt_abschaltung": "RT \u95dc\u6a5f", diff --git a/homeassistant/components/wunderground/sensor.py b/homeassistant/components/wunderground/sensor.py index 2d9c4f5c9c15ed..e1bd79b7ea0efe 100644 --- a/homeassistant/components/wunderground/sensor.py +++ b/homeassistant/components/wunderground/sensor.py @@ -23,7 +23,9 @@ LENGTH_INCHES, LENGTH_KILOMETERS, LENGTH_MILES, + LENGTH_MILLIMETERS, PERCENTAGE, + PRESSURE_INHG, SPEED_KILOMETERS_PER_HOUR, SPEED_MILES_PER_HOUR, TEMP_CELSIUS, @@ -391,7 +393,7 @@ def _get_attributes(rest): "Precipitation 1hr", "precip_1hr_in", "mdi:umbrella", LENGTH_INCHES ), "precip_1hr_metric": WUCurrentConditionsSensorConfig( - "Precipitation 1hr", "precip_1hr_metric", "mdi:umbrella", "mm" + "Precipitation 1hr", "precip_1hr_metric", "mdi:umbrella", LENGTH_MILLIMETERS ), "precip_1hr_string": WUCurrentConditionsSensorConfig( "Precipitation 1hr", "precip_1hr_string", "mdi:umbrella" @@ -400,13 +402,13 @@ def _get_attributes(rest): "Precipitation Today", "precip_today_in", "mdi:umbrella", LENGTH_INCHES ), "precip_today_metric": WUCurrentConditionsSensorConfig( - "Precipitation Today", "precip_today_metric", "mdi:umbrella", "mm" + "Precipitation Today", "precip_today_metric", "mdi:umbrella", LENGTH_MILLIMETERS ), "precip_today_string": WUCurrentConditionsSensorConfig( "Precipitation Today", "precip_today_string", "mdi:umbrella" ), "pressure_in": WUCurrentConditionsSensorConfig( - "Pressure", "pressure_in", "mdi:gauge", "inHg", device_class="pressure" + "Pressure", "pressure_in", "mdi:gauge", PRESSURE_INHG, device_class="pressure" ), "pressure_mb": WUCurrentConditionsSensorConfig( "Pressure", "pressure_mb", "mdi:gauge", "mb", device_class="pressure" @@ -878,16 +880,36 @@ def _get_attributes(rest): "mdi:weather-windy", ), "precip_1d_mm": WUDailySimpleForecastSensorConfig( - "Precipitation Intensity Today", 0, "qpf_allday", "mm", "mm", "mdi:umbrella" + "Precipitation Intensity Today", + 0, + "qpf_allday", + LENGTH_MILLIMETERS, + LENGTH_MILLIMETERS, + "mdi:umbrella", ), "precip_2d_mm": WUDailySimpleForecastSensorConfig( - "Precipitation Intensity Tomorrow", 1, "qpf_allday", "mm", "mm", "mdi:umbrella" + "Precipitation Intensity Tomorrow", + 1, + "qpf_allday", + LENGTH_MILLIMETERS, + LENGTH_MILLIMETERS, + "mdi:umbrella", ), "precip_3d_mm": WUDailySimpleForecastSensorConfig( - "Precipitation Intensity in 3 Days", 2, "qpf_allday", "mm", "mm", "mdi:umbrella" + "Precipitation Intensity in 3 Days", + 2, + "qpf_allday", + LENGTH_MILLIMETERS, + LENGTH_MILLIMETERS, + "mdi:umbrella", ), "precip_4d_mm": WUDailySimpleForecastSensorConfig( - "Precipitation Intensity in 4 Days", 3, "qpf_allday", "mm", "mm", "mdi:umbrella" + "Precipitation Intensity in 4 Days", + 3, + "qpf_allday", + LENGTH_MILLIMETERS, + LENGTH_MILLIMETERS, + "mdi:umbrella", ), "precip_1d_in": WUDailySimpleForecastSensorConfig( "Precipitation Intensity Today", diff --git a/homeassistant/components/xbox_live/sensor.py b/homeassistant/components/xbox_live/sensor.py index 1f46267967a97a..300fcbfb095e63 100644 --- a/homeassistant/components/xbox_live/sensor.py +++ b/homeassistant/components/xbox_live/sensor.py @@ -106,9 +106,7 @@ def state(self): @property def device_state_attributes(self): """Return the state attributes.""" - attributes = {} - attributes["gamerscore"] = self._gamerscore - attributes["tier"] = self._tier + attributes = {"gamerscore": self._gamerscore, "tier": self._tier} for device in self._presence: for title in device["titles"]: diff --git a/homeassistant/components/xiaomi_aqara/binary_sensor.py b/homeassistant/components/xiaomi_aqara/binary_sensor.py index fecdfd91a754f7..8fbecee46e9d70 100644 --- a/homeassistant/components/xiaomi_aqara/binary_sensor.py +++ b/homeassistant/components/xiaomi_aqara/binary_sensor.py @@ -1,7 +1,11 @@ """Support for Xiaomi aqara binary sensors.""" import logging -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_MOISTURE, + DEVICE_CLASS_OPENING, + BinarySensorEntity, +) from homeassistant.core import callback from homeassistant.helpers.event import async_call_later @@ -250,7 +254,7 @@ def parse_data(self, data, raw_data): _LOGGER.debug( "Skipping heartbeat of the motion sensor. " "It can introduce an incorrect state because of a firmware " - "bug (https://github.com/home-assistant/home-assistant/pull/" + "bug (https://github.com/home-assistant/core/pull/" "11631#issuecomment-357507744)" ) return @@ -299,7 +303,7 @@ def __init__(self, device, xiaomi_hub, config_entry): "Door Window Sensor", xiaomi_hub, data_key, - "opening", + DEVICE_CLASS_OPENING, config_entry, ) @@ -349,7 +353,7 @@ def __init__(self, device, xiaomi_hub, config_entry): "Water Leak Sensor", xiaomi_hub, data_key, - "moisture", + DEVICE_CLASS_MOISTURE, config_entry, ) diff --git a/homeassistant/components/xiaomi_aqara/const.py b/homeassistant/components/xiaomi_aqara/const.py index fcfa3939c2c9bb..1cc3b2d4633e95 100644 --- a/homeassistant/components/xiaomi_aqara/const.py +++ b/homeassistant/components/xiaomi_aqara/const.py @@ -36,10 +36,12 @@ "sensor_86sw1", "sensor_86sw1.aq1", "remote.b186acn01", + "remote.b186acn02", "86sw2", "sensor_86sw2", "sensor_86sw2.aq1", "remote.b286acn01", + "remote.b286acn02", "cube", "sensor_cube", "sensor_cube.aqgl01", diff --git a/homeassistant/components/xiaomi_aqara/manifest.json b/homeassistant/components/xiaomi_aqara/manifest.json index 1a00fc3afd228d..4b6cd76985dda5 100644 --- a/homeassistant/components/xiaomi_aqara/manifest.json +++ b/homeassistant/components/xiaomi_aqara/manifest.json @@ -3,7 +3,7 @@ "name": "Xiaomi Gateway (Aqara)", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/xiaomi_aqara", - "requirements": ["PyXiaomiGateway==0.13.2"], + "requirements": ["PyXiaomiGateway==0.13.3"], "after_dependencies": ["discovery"], "codeowners": ["@danielhiversen", "@syssi"], "zeroconf": ["_miio._udp.local."] diff --git a/homeassistant/components/xiaomi_aqara/sensor.py b/homeassistant/components/xiaomi_aqara/sensor.py index 4a6c7ac14fd80c..5b1d3467d25b63 100644 --- a/homeassistant/components/xiaomi_aqara/sensor.py +++ b/homeassistant/components/xiaomi_aqara/sensor.py @@ -9,8 +9,10 @@ DEVICE_CLASS_POWER, DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, + LIGHT_LUX, PERCENTAGE, POWER_WATT, + PRESSURE_HPA, TEMP_CELSIUS, ) @@ -23,8 +25,8 @@ "temperature": [TEMP_CELSIUS, None, DEVICE_CLASS_TEMPERATURE], "humidity": [PERCENTAGE, None, DEVICE_CLASS_HUMIDITY], "illumination": ["lm", None, DEVICE_CLASS_ILLUMINANCE], - "lux": ["lx", None, DEVICE_CLASS_ILLUMINANCE], - "pressure": ["hPa", None, DEVICE_CLASS_PRESSURE], + "lux": [LIGHT_LUX, None, DEVICE_CLASS_ILLUMINANCE], + "pressure": [PRESSURE_HPA, None, DEVICE_CLASS_PRESSURE], "bed_activity": ["μm", None, None], "load_power": [POWER_WATT, None, DEVICE_CLASS_POWER], } @@ -86,8 +88,12 @@ async def async_setup_entry(hass, config_entry, async_add_entities): _LOGGER.warning("Unmapped Device Model") # Set up battery sensors + seen_sids = set() # Set of device sids that are already seen for devices in gateway.devices.values(): for device in devices: + if device["sid"] in seen_sids: + continue + seen_sids.add(device["sid"]) if device["model"] in BATTERY_MODELS: entities.append( XiaomiBatterySensor(device, "Battery", gateway, config_entry) diff --git a/homeassistant/components/xiaomi_aqara/strings.json b/homeassistant/components/xiaomi_aqara/strings.json index bc50c4918664b5..65711b02f2a2ba 100644 --- a/homeassistant/components/xiaomi_aqara/strings.json +++ b/homeassistant/components/xiaomi_aqara/strings.json @@ -36,7 +36,7 @@ }, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", - "already_in_progress": "Config flow for this gateway is already in progress", + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]", "not_xiaomi_aqara": "Not a Xiaomi Aqara Gateway, discovered device did not match known gateways" } } diff --git a/homeassistant/components/xiaomi_aqara/translations/ca.json b/homeassistant/components/xiaomi_aqara/translations/ca.json index 22d3bbaccd3833..f513f36d1814af 100644 --- a/homeassistant/components/xiaomi_aqara/translations/ca.json +++ b/homeassistant/components/xiaomi_aqara/translations/ca.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "El dispositiu ja est\u00e0 configurat", - "already_in_progress": "El flux de configuraci\u00f3 de la passarel\u00b7la ja est\u00e0 en curs.", + "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs", "not_xiaomi_aqara": "No \u00e9s una passarel\u00b7la Xiaomi Aqara, el dispositiu descobert no coincideix amb cap passarel\u00b7la coneguda" }, "error": { diff --git a/homeassistant/components/xiaomi_aqara/translations/de.json b/homeassistant/components/xiaomi_aqara/translations/de.json new file mode 100644 index 00000000000000..75aa3d537e80ca --- /dev/null +++ b/homeassistant/components/xiaomi_aqara/translations/de.json @@ -0,0 +1,10 @@ +{ + "config": { + "flow_title": "Xiaomi Aqara Gateway: {name}", + "step": { + "user": { + "title": "Xiaomi Aqara Gateway" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_aqara/translations/en.json b/homeassistant/components/xiaomi_aqara/translations/en.json index 4cdc7ee497a9fd..6cbbea96220ad3 100644 --- a/homeassistant/components/xiaomi_aqara/translations/en.json +++ b/homeassistant/components/xiaomi_aqara/translations/en.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Device is already configured", - "already_in_progress": "Config flow for this gateway is already in progress", + "already_in_progress": "Configuration flow is already in progress", "not_xiaomi_aqara": "Not a Xiaomi Aqara Gateway, discovered device did not match known gateways" }, "error": { diff --git a/homeassistant/components/xiaomi_aqara/translations/fr.json b/homeassistant/components/xiaomi_aqara/translations/fr.json index a46dc7563903ca..c5e03cc5c14780 100644 --- a/homeassistant/components/xiaomi_aqara/translations/fr.json +++ b/homeassistant/components/xiaomi_aqara/translations/fr.json @@ -7,7 +7,7 @@ }, "error": { "discovery_error": "Impossible de d\u00e9couvrir une passerelle Xiaomi Aqara, essayez d'utiliser l'IP du p\u00e9riph\u00e9rique ex\u00e9cutant HomeAssistant comme interface", - "invalid_host": "Adresse IP non valide", + "invalid_host": "Adresse IP non valide, voir https://www.home-assistant.io/integrations/xiaomi_aqara/#connection-problem", "invalid_interface": "Interface r\u00e9seau non valide", "invalid_key": "Cl\u00e9 de passerelle non valide", "invalid_mac": "Adresse MAC non valide", diff --git a/homeassistant/components/xiaomi_aqara/translations/hu.json b/homeassistant/components/xiaomi_aqara/translations/hu.json new file mode 100644 index 00000000000000..3b2d79a34a77e2 --- /dev/null +++ b/homeassistant/components/xiaomi_aqara/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_aqara/translations/it.json b/homeassistant/components/xiaomi_aqara/translations/it.json index dfffec72272d3d..1e5792aad3e623 100644 --- a/homeassistant/components/xiaomi_aqara/translations/it.json +++ b/homeassistant/components/xiaomi_aqara/translations/it.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", - "already_in_progress": "Il flusso di configurazione per questo gateway \u00e8 gi\u00e0 in corso", + "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso", "not_xiaomi_aqara": "Non \u00e8 un Gateway Xiaomi Aqara, il dispositivo scoperto non corrisponde ai gateway noti" }, "error": { diff --git a/homeassistant/components/xiaomi_aqara/translations/ko.json b/homeassistant/components/xiaomi_aqara/translations/ko.json index 90a22ace2b876e..f222ac58babfa7 100644 --- a/homeassistant/components/xiaomi_aqara/translations/ko.json +++ b/homeassistant/components/xiaomi_aqara/translations/ko.json @@ -30,7 +30,9 @@ }, "user": { "data": { - "interface": "\uc0ac\uc6a9\ud560 \ub124\ud2b8\uc6cc\ud06c \uc778\ud130\ud398\uc774\uc2a4" + "host": "IP \uc8fc\uc18c (\uc120\ud0dd \uc0ac\ud56d)", + "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 \ubc0f Mac \uc8fc\uc18c\uac00 \uc124\uc815\ub418\uc9c0 \uc54a\uc740 \uacbd\uc6b0 \uc790\ub3d9 \uac80\uc0c9\uc774 \uc0ac\uc6a9\ub429\ub2c8\ub2e4", "title": "Xiaomi Aqara \uac8c\uc774\ud2b8\uc6e8\uc774" diff --git a/homeassistant/components/xiaomi_aqara/translations/no.json b/homeassistant/components/xiaomi_aqara/translations/no.json index 39f472299d7dff..184670ef7a3fa6 100644 --- a/homeassistant/components/xiaomi_aqara/translations/no.json +++ b/homeassistant/components/xiaomi_aqara/translations/no.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Enheten er allerede konfigurert", - "already_in_progress": "Konfigurasjonsflyt for denne porten p\u00e5g\u00e5r allerede", + "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede", "not_xiaomi_aqara": "Ikke en Xiaomi Aqara Gateway, oppdaget enhet ikke samsvarer med kjente gatewayer" }, "error": { diff --git a/homeassistant/components/xiaomi_aqara/translations/pl.json b/homeassistant/components/xiaomi_aqara/translations/pl.json index a603566d569c72..fba37ad0249b21 100644 --- a/homeassistant/components/xiaomi_aqara/translations/pl.json +++ b/homeassistant/components/xiaomi_aqara/translations/pl.json @@ -1,8 +1,8 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", - "already_in_progress": "Konfiguracja dla tej bramki jest ju\u017c w toku.", + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", + "already_in_progress": "Konfiguracja dla tej bramki jest ju\u017c w toku", "not_xiaomi_aqara": "To nie jest bramka Xiaomi Aqara, wykryte urz\u0105dzenie nie pasuje do znanych bramek." }, "error": { @@ -10,7 +10,7 @@ "invalid_host": "Adres IP jest nieprawid\u0142owy, po pomoc w rozwi\u0105zaniu problemu wejd\u017a tutaj: https://www.home-assistant.io/integrations/xiaomi_aqara/#connection-problem", "invalid_interface": "Nieprawid\u0142owy interfejs sieciowy.", "invalid_key": "Nieprawid\u0142owy klucz bramki.", - "invalid_mac": "Nieprawid\u0142owy adres MAC.", + "invalid_mac": "Nieprawid\u0142owy adres MAC", "not_found_error": "Nie mo\u017cna odnale\u017a\u0107 wykrytej bramki, aby uzyska\u0107 niezb\u0119dne informacje, spr\u00f3buj u\u017cy\u0107 adresu IP urz\u0105dzenia, na kt\u00f3rym pracuje Home Assistant jako interfejsu." }, "flow_title": "Bramka Xiaomi Aqara: {name}", diff --git a/homeassistant/components/xiaomi_aqara/translations/ru.json b/homeassistant/components/xiaomi_aqara/translations/ru.json index ddce68aa2bf1cf..690d1eb3067c18 100644 --- a/homeassistant/components/xiaomi_aqara/translations/ru.json +++ b/homeassistant/components/xiaomi_aqara/translations/ru.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant.", - "already_in_progress": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f.", + "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_xiaomi_aqara": "\u042d\u0442\u043e \u043d\u0435 \u0448\u043b\u044e\u0437 Xiaomi Aqara. \u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u043d\u0435 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u043c \u0448\u043b\u044e\u0437\u0430\u043c." }, "error": { diff --git a/homeassistant/components/xiaomi_aqara/translations/zh-Hans.json b/homeassistant/components/xiaomi_aqara/translations/zh-Hans.json new file mode 100644 index 00000000000000..02e2a01961b045 --- /dev/null +++ b/homeassistant/components/xiaomi_aqara/translations/zh-Hans.json @@ -0,0 +1,44 @@ +{ + "config": { + "abort": { + "already_configured": "\u8bbe\u5907\u5df2\u7ecf\u914d\u7f6e\u8fc7\u4e86", + "already_in_progress": "\u914d\u7f6e\u6d41\u7a0b\u5df2\u5728\u8fdb\u884c\u4e2d", + "not_xiaomi_aqara": "\u8fd9\u4e0d\u662f\u5c0f\u7c73 Aqara \u7f51\u5173\u3002\u53d1\u73b0\u7684\u8bbe\u5907\u4e0e\u5df2\u77e5\u7f51\u5173\u4e0d\u5339\u914d" + }, + "error": { + "discovery_error": "\u672a\u53d1\u73b0\u5c0f\u7c73 Aqara \u7f51\u5173\u3002\u8bf7\u5c1d\u8bd5\u4f7f\u7528\u8fd0\u884c Home Assistant \u7684\u8bbe\u5907 IP \u4f5c\u4e3a\u63a5\u53e3", + "invalid_host": "IP \u5730\u5740\u65e0\u6548\u3002\u8bf7\u53c2\u9605 https://www.home-assistant.io/integrations/xiaomi_aqara/#connection-problem", + "invalid_interface": "\u7f51\u7edc\u63a5\u53e3\u65e0\u6548", + "invalid_key": "\u7f51\u5173 key \u65e0\u6548", + "invalid_mac": "MAC \u5730\u5740\u65e0\u6548", + "not_found_error": "Zeroconf \u53d1\u73b0\u7684\u7f51\u5173\u65e0\u6cd5\u5b9a\u4f4d\uff0c\u56e0\u6b64\u65e0\u6cd5\u83b7\u5f97\u5fc5\u8981\u4fe1\u606f\u3002\u8bf7\u5c1d\u8bd5\u4f7f\u7528\u8fd0\u884c HomeAssistant \u7684\u8bbe\u5907 IP \u4f5c\u4e3a\u63a5\u53e3" + }, + "flow_title": "\u5c0f\u7c73 Aqara \u7f51\u5173\uff1a{name}", + "step": { + "select": { + "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" + }, + "settings": { + "data": { + "key": "\u7f51\u5173 key", + "name": "\u7f51\u5173\u540d\u79f0" + }, + "description": "\u6240\u9700\u7684 key \u53ef\u4ee5\u53c2\u8003\u4ee5\u4e0b\u6559\u7a0b\u83b7\u53d6: https://www.domoticz.com/wiki/Xiaomi_Gateway_(Aqara)#Adding_the_Xiaomi_Gateway_to_Domoticz \u3002\u5982\u679c\u672a\u63d0\u4f9b key\uff0c\u5219\u53ea\u80fd\u8bbf\u95ee\u4f20\u611f\u5668", + "title": "\u5c0f\u7c73 Aqara \u7f51\u5173\u7684\u53ef\u9009\u8bbe\u7f6e" + }, + "user": { + "data": { + "host": "IP \u5730\u5740 (\u53ef\u9009)", + "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" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_aqara/translations/zh-Hant.json b/homeassistant/components/xiaomi_aqara/translations/zh-Hant.json index 87ea0c6028a0f7..611108c6513c8d 100644 --- a/homeassistant/components/xiaomi_aqara/translations/zh-Hant.json +++ b/homeassistant/components/xiaomi_aqara/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", - "already_in_progress": "\u7db2\u95dc\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", + "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d", "not_xiaomi_aqara": "\u4e26\u975e\u5c0f\u7c73 Aqara \u7db2\u95dc\uff0c\u6240\u63a2\u7d22\u4e4b\u8a2d\u5099\u8207\u5df2\u77e5\u7db2\u95dc\u4e0d\u7b26\u5408" }, "error": { diff --git a/homeassistant/components/xiaomi_miio/light.py b/homeassistant/components/xiaomi_miio/light.py index 29c80faa892ab1..34ebb550adf589 100644 --- a/homeassistant/components/xiaomi_miio/light.py +++ b/homeassistant/components/xiaomi_miio/light.py @@ -278,11 +278,6 @@ def __init__(self, name, light, model, unique_id): self._state = None self._state_attrs = {ATTR_MODEL: self._model} - @property - def should_poll(self): - """Poll the light.""" - return True - @property def unique_id(self): """Return an unique ID.""" diff --git a/homeassistant/components/xiaomi_miio/sensor.py b/homeassistant/components/xiaomi_miio/sensor.py index 15dc1bea8bd713..5dcf1f8d1e7e0d 100644 --- a/homeassistant/components/xiaomi_miio/sensor.py +++ b/homeassistant/components/xiaomi_miio/sensor.py @@ -21,6 +21,7 @@ DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_PRESSURE, DEVICE_CLASS_TEMPERATURE, + LIGHT_LUX, PERCENTAGE, PRESSURE_HPA, TEMP_CELSIUS, @@ -173,11 +174,6 @@ def __init__(self, name, device, model, unique_id): ATTR_MODEL: self._model, } - @property - def should_poll(self): - """Poll the miio device.""" - return True - @property def unique_id(self): """Return an unique ID.""" @@ -307,7 +303,7 @@ def available(self): @property def unit_of_measurement(self): """Return the unit of measurement of this entity.""" - return "lux" + return LIGHT_LUX @property def device_class(self): diff --git a/homeassistant/components/xiaomi_miio/strings.json b/homeassistant/components/xiaomi_miio/strings.json index 171e358c90ef0f..cc032a341e9fee 100644 --- a/homeassistant/components/xiaomi_miio/strings.json +++ b/homeassistant/components/xiaomi_miio/strings.json @@ -14,7 +14,7 @@ "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 token is different from the key used by the Xiaomi Aqara integration.", "data": { "host": "[%key:common::config_flow::data::ip%]", - "token": "API Token", + "token": "[%key:common::config_flow::data::api_token%]", "name": "Name of the Gateway" } } @@ -25,7 +25,7 @@ }, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", - "already_in_progress": "Config flow for this Xiaomi Miio device is already in progress." + "already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]" } } } diff --git a/homeassistant/components/xiaomi_miio/switch.py b/homeassistant/components/xiaomi_miio/switch.py index 6dfcc539a44bbb..e406e344ebf644 100644 --- a/homeassistant/components/xiaomi_miio/switch.py +++ b/homeassistant/components/xiaomi_miio/switch.py @@ -235,11 +235,6 @@ def __init__(self, name, plug, model, unique_id): self._device_features = FEATURE_FLAGS_GENERIC self._skip_update = False - @property - def should_poll(self): - """Poll the plug.""" - return True - @property def unique_id(self): """Return an unique ID.""" diff --git a/homeassistant/components/xiaomi_miio/translations/ca.json b/homeassistant/components/xiaomi_miio/translations/ca.json index 9bd8f91c947ee4..920548d858bc68 100644 --- a/homeassistant/components/xiaomi_miio/translations/ca.json +++ b/homeassistant/components/xiaomi_miio/translations/ca.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "[%key::common::config_flow::abort::already_configured_device%]", - "already_in_progress": "El flux de dades de configuraci\u00f3 pel dispositiu Xiaomi Miio ja est\u00e0 en curs." + "already_in_progress": "El flux de configuraci\u00f3 ja est\u00e0 en curs" }, "error": { "connect_error": "[%key::common::config_flow::error::cannot_connect%]", @@ -14,7 +14,7 @@ "data": { "host": "Adre\u00e7a IP", "name": "Nom de la passarel\u00b7la", - "token": "Token de l'API" + "token": "Token d'API" }, "description": "Necessitar\u00e0s el token de l'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 \u00e9s diferent a la clau utilitzada per la integraci\u00f3 Xiaomi Aqara.", "title": "Connexi\u00f3 amb la passarel\u00b7la de Xiaomi" diff --git a/homeassistant/components/xiaomi_miio/translations/de.json b/homeassistant/components/xiaomi_miio/translations/de.json index 6ec92566adee3f..d52715249b9212 100644 --- a/homeassistant/components/xiaomi_miio/translations/de.json +++ b/homeassistant/components/xiaomi_miio/translations/de.json @@ -8,6 +8,7 @@ "connect_error": "Verbindung fehlgeschlagen", "no_device_selected": "Kein Ger\u00e4t ausgew\u00e4hlt, bitte w\u00e4hlen Sie ein Ger\u00e4t aus." }, + "flow_title": "Xiaomi Miio: {name}", "step": { "gateway": { "data": { diff --git a/homeassistant/components/xiaomi_miio/translations/en.json b/homeassistant/components/xiaomi_miio/translations/en.json index e4b8cb70be6540..9597f0466af7a5 100644 --- a/homeassistant/components/xiaomi_miio/translations/en.json +++ b/homeassistant/components/xiaomi_miio/translations/en.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Device is already configured", - "already_in_progress": "Config flow for this Xiaomi Miio device is already in progress." + "already_in_progress": "Configuration flow is already in progress" }, "error": { "connect_error": "Failed to connect", diff --git a/homeassistant/components/xiaomi_miio/translations/et.json b/homeassistant/components/xiaomi_miio/translations/et.json new file mode 100644 index 00000000000000..ef3998a3baf6d0 --- /dev/null +++ b/homeassistant/components/xiaomi_miio/translations/et.json @@ -0,0 +1,20 @@ +{ + "config": { + "flow_title": "Xiaomi Miio: {name}", + "step": { + "gateway": { + "data": { + "host": "IP aadress" + }, + "title": "Loo \u00fchendus Xiaomi l\u00fc\u00fcsiga" + }, + "user": { + "data": { + "gateway": "Loo \u00fchendus Xiaomi l\u00fc\u00fcsiga" + }, + "description": "Vali seade millega soovid \u00fchenduse luua.", + "title": "Xiaomi Miio" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/xiaomi_miio/translations/it.json b/homeassistant/components/xiaomi_miio/translations/it.json index 3514d759926c17..6981bc498c7f82 100644 --- a/homeassistant/components/xiaomi_miio/translations/it.json +++ b/homeassistant/components/xiaomi_miio/translations/it.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", - "already_in_progress": "Il flusso di configurazione per questo dispositivo Xiaomi Miio \u00e8 gi\u00e0 in corso." + "already_in_progress": "Il flusso di configurazione \u00e8 gi\u00e0 in corso" }, "error": { "connect_error": "Impossibile connettersi", diff --git a/homeassistant/components/xiaomi_miio/translations/no.json b/homeassistant/components/xiaomi_miio/translations/no.json index cf978d4a015da2..408481875d0c3e 100644 --- a/homeassistant/components/xiaomi_miio/translations/no.json +++ b/homeassistant/components/xiaomi_miio/translations/no.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "Enheten er allerede konfigurert", - "already_in_progress": "Konfigurasjonsflyt for denne Xiaomi Miio-enheten p\u00e5g\u00e5r allerede." + "already_in_progress": "Konfigurasjonsflyten p\u00e5g\u00e5r allerede" }, "error": { "connect_error": "Tilkobling mislyktes.", diff --git a/homeassistant/components/xiaomi_miio/translations/pl.json b/homeassistant/components/xiaomi_miio/translations/pl.json index 90f191a58c4a9f..0c32eaf77ca7cf 100644 --- a/homeassistant/components/xiaomi_miio/translations/pl.json +++ b/homeassistant/components/xiaomi_miio/translations/pl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", "already_in_progress": "Konfiguracja tego urz\u0105dzenia Xiaomi Miio jest ju\u017c w toku." }, "error": { - "connect_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia.", + "connect_error": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", "no_device_selected": "Nie wybrano \u017cadnego urz\u0105dzenia, wybierz jedno urz\u0105dzenie." }, "flow_title": "Xiaomi Miio: {name}", diff --git a/homeassistant/components/xiaomi_miio/translations/ru.json b/homeassistant/components/xiaomi_miio/translations/ru.json index 1c934e2784c508..d839705e1e45e9 100644 --- a/homeassistant/components/xiaomi_miio/translations/ru.json +++ b/homeassistant/components/xiaomi_miio/translations/ru.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant.", - "already_in_progress": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u044d\u0442\u043e\u0433\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f." + "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." }, "error": { "connect_error": "\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/xiaomi_miio/translations/zh-Hans.json b/homeassistant/components/xiaomi_miio/translations/zh-Hans.json index 086f449e0caa90..9a54104116af46 100644 --- a/homeassistant/components/xiaomi_miio/translations/zh-Hans.json +++ b/homeassistant/components/xiaomi_miio/translations/zh-Hans.json @@ -8,7 +8,7 @@ "connect_error": "\u8fde\u63a5\u5931\u8d25\uff0c\u8bf7\u91cd\u8bd5", "no_device_selected": "\u672a\u9009\u62e9\u8bbe\u5907\uff0c\u8bf7\u9009\u62e9\u4e00\u4e2a\u8bbe\u5907\u3002" }, - "flow_title": "\u5c0f\u7c73 Miio: {name}", + "flow_title": "Xiaomi Miio: {name}", "step": { "gateway": { "data": { @@ -24,7 +24,7 @@ "gateway": "\u8fde\u63a5\u5230\u5c0f\u7c73\u7f51\u5173" }, "description": "\u8bf7\u9009\u62e9\u8981\u8fde\u63a5\u7684\u8bbe\u5907\u3002", - "title": "\u5c0f\u7c73 Miio" + "title": "Xiaomi Miio" } } } diff --git a/homeassistant/components/xiaomi_miio/translations/zh-Hant.json b/homeassistant/components/xiaomi_miio/translations/zh-Hant.json index 7b459c30803212..dc7833d2037767 100644 --- a/homeassistant/components/xiaomi_miio/translations/zh-Hant.json +++ b/homeassistant/components/xiaomi_miio/translations/zh-Hant.json @@ -2,7 +2,7 @@ "config": { "abort": { "already_configured": "\u8a2d\u5099\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210", - "already_in_progress": "\u5c0f\u7c73 Miio \u8a2d\u5099\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d\u3002" + "already_in_progress": "\u8a2d\u5b9a\u5df2\u7d93\u9032\u884c\u4e2d" }, "error": { "connect_error": "\u9023\u7dda\u5931\u6557", diff --git a/homeassistant/components/xiaomi_miio/vacuum.py b/homeassistant/components/xiaomi_miio/vacuum.py index 3e414016fc5394..53fc458c50d83f 100644 --- a/homeassistant/components/xiaomi_miio/vacuum.py +++ b/homeassistant/components/xiaomi_miio/vacuum.py @@ -484,14 +484,21 @@ def update(self): self.last_clean = self._vacuum.last_clean_details() self.dnd_state = self._vacuum.dnd_status() - self._timers = self._vacuum.timer() - self._available = True except OSError as exc: _LOGGER.error("Got OSError while fetching the state: %s", exc) except DeviceException as exc: _LOGGER.warning("Got exception while fetching the state: %s", exc) + # Fetch timers separately, see #38285 + try: + self._timers = self._vacuum.timer() + except DeviceException as exc: + _LOGGER.debug( + "Unable to fetch timers, this may happen on some devices: %s", exc + ) + self._timers = [] + async def async_clean_zone(self, zone, repeats=1): """Clean selected area for the number of repeats indicated.""" for _zone in zone: diff --git a/homeassistant/components/yamaha_musiccast/media_player.py b/homeassistant/components/yamaha_musiccast/media_player.py index bfec5b932a8fe8..b21f6d3a3f416d 100644 --- a/homeassistant/components/yamaha_musiccast/media_player.py +++ b/homeassistant/components/yamaha_musiccast/media_player.py @@ -137,11 +137,6 @@ def state(self): return self.status return self.power - @property - def should_poll(self): - """Push an update after each command.""" - return True - @property def is_volume_muted(self): """Boolean if volume is currently muted.""" diff --git a/homeassistant/components/yandex_transport/sensor.py b/homeassistant/components/yandex_transport/sensor.py index cde115cb12ff6b..957844e519d9ae 100644 --- a/homeassistant/components/yandex_transport/sensor.py +++ b/homeassistant/components/yandex_transport/sensor.py @@ -60,7 +60,7 @@ def __init__(self, requester: YandexMapsRequester, stop_id, routes, name): self._name = name self._attrs = None - async def async_update(self): + async def async_update(self, *, tries=0): """Get the latest data from maps.yandex.ru and update the states.""" attrs = {} closer_time = None @@ -73,8 +73,12 @@ async def async_update(self): key_error, yandex_reply, ) + if tries > 0: + return await self.requester.set_new_session() - data = (await self.requester.get_stop_info(self._stop_id))["data"] + await self.async_update(tries=tries + 1) + return + stop_name = data["name"] transport_list = data["transports"] for transport in transport_list: diff --git a/homeassistant/components/yeelight/translations/de.json b/homeassistant/components/yeelight/translations/de.json new file mode 100644 index 00000000000000..6930fca0a5b45e --- /dev/null +++ b/homeassistant/components/yeelight/translations/de.json @@ -0,0 +1,27 @@ +{ + "config": { + "step": { + "pick_device": { + "data": { + "device": "Ger\u00e4te" + } + }, + "user": { + "data": { + "host": "Host", + "ip_address": "IP-Addresse" + } + } + } + }, + "options": { + "step": { + "init": { + "data": { + "use_music_mode": "Musik-Modus aktivieren" + } + } + } + }, + "title": "Yeelight" +} \ No newline at end of file diff --git a/homeassistant/components/yeelight/translations/el.json b/homeassistant/components/yeelight/translations/el.json new file mode 100644 index 00000000000000..6495e7a489fb7f --- /dev/null +++ b/homeassistant/components/yeelight/translations/el.json @@ -0,0 +1,35 @@ +{ + "config": { + "error": { + "cannot_connect": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2" + }, + "step": { + "pick_device": { + "data": { + "device": "\u03a3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ae" + } + }, + "user": { + "data": { + "ip_address": "\u0394\u03b9\u03b5\u03cd\u03b8\u03c5\u03bd\u03c3\u03b7 IP" + }, + "description": "\u0395\u03ac\u03bd \u03b1\u03c6\u03ae\u03c3\u03b5\u03c4\u03b5 \u03c4\u03bf\u03bd \u03ba\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae \u03ba\u03b5\u03bd\u03cc, \u03bf \u03b5\u03bd\u03c4\u03bf\u03c0\u03b9\u03c3\u03bc\u03cc\u03c2 \u03b8\u03b1 \u03c7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03b7\u03b8\u03b5\u03af \u03b3\u03b9\u03b1 \u03c4\u03b7\u03bd \u03b5\u03cd\u03c1\u03b5\u03c3\u03b7 \u03c3\u03c5\u03c3\u03ba\u03b5\u03c5\u03ce\u03bd." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "model": "\u039c\u03bf\u03bd\u03c4\u03ad\u03bb\u03bf (\u03a0\u03c1\u03bf\u03b1\u03b9\u03c1\u03b5\u03c4\u03b9\u03ba\u03cc)", + "nightlight_switch": "\u03a7\u03c1\u03ae\u03c3\u03b7 \u03b4\u03b9\u03b1\u03ba\u03cc\u03c0\u03c4\u03b7 \u03bd\u03c5\u03c7\u03c4\u03b5\u03c1\u03b9\u03bd\u03bf\u03cd \u03c6\u03c9\u03c4\u03b9\u03c3\u03bc\u03bf\u03cd", + "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." + } + } + }, + "title": "Yeelight" +} \ No newline at end of file diff --git a/homeassistant/components/yeelight/translations/es.json b/homeassistant/components/yeelight/translations/es.json index 08c2ca92dea132..fdfbdfc5634a6d 100644 --- a/homeassistant/components/yeelight/translations/es.json +++ b/homeassistant/components/yeelight/translations/es.json @@ -15,6 +15,7 @@ }, "user": { "data": { + "host": "Host", "ip_address": "Direcci\u00f3n IP" }, "description": "Si dejas la direcci\u00f3n IP vac\u00eda, se usar\u00e1 descubrimiento para encontrar dispositivos." diff --git a/homeassistant/components/yeelight/translations/hu.json b/homeassistant/components/yeelight/translations/hu.json new file mode 100644 index 00000000000000..3b2d79a34a77e2 --- /dev/null +++ b/homeassistant/components/yeelight/translations/hu.json @@ -0,0 +1,7 @@ +{ + "config": { + "abort": { + "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/yeelight/translations/ko.json b/homeassistant/components/yeelight/translations/ko.json new file mode 100644 index 00000000000000..c04006e2c8f9f7 --- /dev/null +++ b/homeassistant/components/yeelight/translations/ko.json @@ -0,0 +1,40 @@ +{ + "config": { + "abort": { + "already_configured": "\uc7a5\uce58\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5b4 \uc788\uc2b5\ub2c8\ub2e4.", + "no_devices_found": "\ub124\ud2b8\uc6cc\ud06c \uc0c1\uc5d0 \ubc1c\uacac\ub41c \uc7a5\uce58\uac00 \uc5c6\uc2b5\ub2c8\ub2e4." + }, + "error": { + "cannot_connect": "\uc5f0\uacb0 \uc2e4\ud328" + }, + "step": { + "pick_device": { + "data": { + "device": "\uc7a5\uce58" + } + }, + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8", + "ip_address": "IP \uc8fc\uc18c" + }, + "description": "\ud638\uc2a4\ud2b8\ub97c \ube44\uc6cc\ub450\uba74 \uc7a5\uce58\ub97c \ucc3e\ub294 \ub370 \uac80\uc0c9\uc774 \uc0ac\uc6a9\ub429\ub2c8\ub2e4." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "model": "\ubaa8\ub378(\uc120\ud0dd \uc0ac\ud56d)", + "nightlight_switch": "\uc57c\uac04 \uc870\uba85 \uc2a4\uc704\uce58 \uc0ac\uc6a9", + "save_on_change": "\ubcc0\uacbd\uc2dc \uc0c1\ud0dc \uc800\uc7a5", + "transition": "\uc804\ud658 \uc2dc\uac04(ms)", + "use_music_mode": "\uc74c\uc545 \ubaa8\ub4dc \ud65c\uc131\ud654" + }, + "description": "\ubaa8\ub378\uc744 \ube44\uc6cc \ub450\uba74 \uc790\ub3d9\uc73c\ub85c \uac80\uc0c9\ub429\ub2c8\ub2e4." + } + } + }, + "title": "\uc774\ub77c\uc774\ud2b8" +} \ No newline at end of file diff --git a/homeassistant/components/yeelight/translations/lb.json b/homeassistant/components/yeelight/translations/lb.json new file mode 100644 index 00000000000000..a57a41c215995f --- /dev/null +++ b/homeassistant/components/yeelight/translations/lb.json @@ -0,0 +1,28 @@ +{ + "config": { + "step": { + "pick_device": { + "data": { + "device": "Apparat" + } + }, + "user": { + "description": "Falls Host eidel gelass g\u00ebtt, g\u00ebtt eng automatesch Sich gestart" + } + } + }, + "options": { + "step": { + "init": { + "data": { + "model": "Modell (Optionell)", + "nightlight_switch": "Nuechtliicht Schalter benotzen", + "save_on_change": "Status sp\u00e4icheren bei \u00c4nnerung", + "use_music_mode": "Musek Modus aktiv\u00e9ieren" + }, + "description": "Falls Modell eidel gelass g\u00ebtt, g\u00ebtt et automatesch erkannt." + } + } + }, + "title": "Yeelight" +} \ No newline at end of file diff --git a/homeassistant/components/yeelight/translations/nl.json b/homeassistant/components/yeelight/translations/nl.json new file mode 100644 index 00000000000000..f9f78ffb6f6f05 --- /dev/null +++ b/homeassistant/components/yeelight/translations/nl.json @@ -0,0 +1,40 @@ +{ + "config": { + "abort": { + "already_configured": "Apparaat is al geconfigureerd", + "no_devices_found": "Geen apparaten gevonden op het netwerk" + }, + "error": { + "cannot_connect": "Kon niet verbinden" + }, + "step": { + "pick_device": { + "data": { + "device": "Apparaat" + } + }, + "user": { + "data": { + "host": "Host", + "ip_address": "IP adres" + }, + "description": "Als u host leeg laat, wordt detectie gebruikt om apparaten te vinden." + } + } + }, + "options": { + "step": { + "init": { + "data": { + "model": "Model (optioneel)", + "nightlight_switch": "Gebruik Nachtlichtschakelaar", + "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." + } + } + }, + "title": "Yeelight" +} \ No newline at end of file diff --git a/homeassistant/components/yeelight/translations/pl.json b/homeassistant/components/yeelight/translations/pl.json index 4a2636457aa562..325e3cbc7ae1b5 100644 --- a/homeassistant/components/yeelight/translations/pl.json +++ b/homeassistant/components/yeelight/translations/pl.json @@ -1,11 +1,11 @@ { "config": { "abort": { - "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane.", + "already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane", "no_devices_found": "Nie znaleziono urz\u0105dze\u0144 w sieci." }, "error": { - "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia." + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia" }, "step": { "pick_device": { diff --git a/homeassistant/components/yeelight/translations/sv.json b/homeassistant/components/yeelight/translations/sv.json new file mode 100644 index 00000000000000..9fdd341e941646 --- /dev/null +++ b/homeassistant/components/yeelight/translations/sv.json @@ -0,0 +1,11 @@ +{ + "config": { + "step": { + "pick_device": { + "data": { + "device": "Enhet" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zamg/sensor.py b/homeassistant/components/zamg/sensor.py index 0aa5dea0687c38..4852e874672ac5 100644 --- a/homeassistant/components/zamg/sensor.py +++ b/homeassistant/components/zamg/sensor.py @@ -12,6 +12,7 @@ import voluptuous as vol from homeassistant.const import ( + AREA_SQUARE_METERS, ATTR_ATTRIBUTION, CONF_LATITUDE, CONF_LONGITUDE, @@ -20,6 +21,7 @@ DEGREE, LENGTH_METERS, PERCENTAGE, + PRESSURE_HPA, SPEED_KILOMETERS_PER_HOUR, TEMP_CELSIUS, __version__, @@ -41,8 +43,8 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=10) SENSOR_TYPES = { - "pressure": ("Pressure", "hPa", "LDstat hPa", float), - "pressure_sealevel": ("Pressure at Sea Level", "hPa", "LDred hPa", float), + "pressure": ("Pressure", PRESSURE_HPA, "LDstat hPa", float), + "pressure_sealevel": ("Pressure at Sea Level", PRESSURE_HPA, "LDred hPa", float), "humidity": ("Humidity", PERCENTAGE, "RF %", int), "wind_speed": ( "Wind Speed", @@ -60,7 +62,12 @@ "wind_max_bearing": ("Top Wind Bearing", DEGREE, f"WSR {DEGREE}", int), "sun_last_hour": ("Sun Last Hour", PERCENTAGE, f"SO {PERCENTAGE}", int), "temperature": ("Temperature", TEMP_CELSIUS, f"T {TEMP_CELSIUS}", float), - "precipitation": ("Precipitation", "l/m²", "N l/m²", float), + "precipitation": ( + "Precipitation", + f"l/{AREA_SQUARE_METERS}", + f"N l/{AREA_SQUARE_METERS}", + float, + ), "dewpoint": ("Dew Point", TEMP_CELSIUS, f"TP {TEMP_CELSIUS}", float), # The following probably not useful for general consumption, # but we need them to fill in internal attributes diff --git a/homeassistant/components/zengge/light.py b/homeassistant/components/zengge/light.py index fa2c7d50addddb..30776eabbb3625 100644 --- a/homeassistant/components/zengge/light.py +++ b/homeassistant/components/zengge/light.py @@ -97,11 +97,6 @@ def supported_features(self): """Flag supported features.""" return SUPPORT_ZENGGE_LED - @property - def should_poll(self): - """Feel free to poll.""" - return True - @property def assumed_state(self): """We can report the actual state.""" diff --git a/homeassistant/components/zeroconf/__init__.py b/homeassistant/components/zeroconf/__init__.py index 51da3638a9e1c6..68300adbcfe97c 100644 --- a/homeassistant/components/zeroconf/__init__.py +++ b/homeassistant/components/zeroconf/__init__.py @@ -1,6 +1,6 @@ """Support for exposing Home Assistant via Zeroconf.""" -import asyncio import fnmatch +from functools import partial import ipaddress import logging import socket @@ -81,26 +81,21 @@ @singleton(DOMAIN) async def async_get_instance(hass): """Zeroconf instance to be shared with other integrations that use it.""" - return await hass.async_add_executor_job(_get_instance, hass) + return await _async_get_instance(hass) -def _get_instance(hass, default_interface=False, ipv6=True): - """Create an instance.""" +async def _async_get_instance(hass, **zcargs): logging.getLogger("zeroconf").setLevel(logging.NOTSET) - zc_args = {} - if default_interface: - zc_args["interfaces"] = InterfaceChoice.Default - if not ipv6: - zc_args["ip_version"] = IPVersion.V4Only + zeroconf = await hass.async_add_executor_job(partial(HaZeroconf, **zcargs)) - zeroconf = HaZeroconf(**zc_args) + install_multiple_zeroconf_catcher(zeroconf) - def stop_zeroconf(_): + def _stop_zeroconf(_): """Stop Zeroconf.""" zeroconf.ha_close() - hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, stop_zeroconf) + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _stop_zeroconf) return zeroconf @@ -135,24 +130,42 @@ def close(self): ha_close = Zeroconf.close -def setup(hass, config): +async def async_setup(hass, config): """Set up Zeroconf and make Home Assistant discoverable.""" zc_config = config.get(DOMAIN, {}) - zeroconf = hass.data[DOMAIN] = _get_instance( - hass, - default_interface=zc_config.get( - CONF_DEFAULT_INTERFACE, DEFAULT_DEFAULT_INTERFACE - ), - ipv6=zc_config.get(CONF_IPV6, DEFAULT_IPV6), + zc_args = {} + if zc_config.get(CONF_DEFAULT_INTERFACE, DEFAULT_DEFAULT_INTERFACE): + zc_args["interfaces"] = InterfaceChoice.Default + if not zc_config.get(CONF_IPV6, DEFAULT_IPV6): + zc_args["ip_version"] = IPVersion.V4Only + + zeroconf = hass.data[DOMAIN] = await _async_get_instance(hass, **zc_args) + + async def _async_zeroconf_hass_start(_event): + """Expose Home Assistant on zeroconf when it starts. + + Wait till started or otherwise HTTP is not up and running. + """ + uuid = await hass.helpers.instance_id.async_get() + await hass.async_add_executor_job( + _register_hass_zc_service, hass, zeroconf, uuid + ) + + async def _async_zeroconf_hass_started(_event): + """Start the service browser.""" + + await _async_start_zeroconf_browser(hass, zeroconf) + + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, _async_zeroconf_hass_start) + hass.bus.async_listen_once( + EVENT_HOMEASSISTANT_STARTED, _async_zeroconf_hass_started ) - install_multiple_zeroconf_catcher(zeroconf) + return True - # Get instance UUID - uuid = asyncio.run_coroutine_threadsafe( - hass.helpers.instance_id.async_get(), hass.loop - ).result() +def _register_hass_zc_service(hass, zeroconf, uuid): + # Get instance UUID valid_location_name = _truncate_location_name_to_valid(hass.config.location_name) params = { @@ -199,23 +212,25 @@ def setup(hass, config): properties=params, ) - def zeroconf_hass_start(_event): - """Expose Home Assistant on zeroconf when it starts. + _LOGGER.info("Starting Zeroconf broadcast") + try: + zeroconf.register_service(info) + except NonUniqueNameException: + _LOGGER.error( + "Home Assistant instance with identical name present in the local network" + ) - Wait till started or otherwise HTTP is not up and running. - """ - _LOGGER.info("Starting Zeroconf broadcast") - try: - zeroconf.register_service(info) - except NonUniqueNameException: - _LOGGER.error( - "Home Assistant instance with identical name present in the local network" - ) - hass.bus.listen_once(EVENT_HOMEASSISTANT_START, zeroconf_hass_start) +async def _async_start_zeroconf_browser(hass, zeroconf): + """Start the zeroconf browser.""" + + zeroconf_types = await async_get_zeroconf(hass) + homekit_models = await async_get_homekit(hass) + + types = list(zeroconf_types) - zeroconf_types = {} - homekit_models = {} + if HOMEKIT_TYPE not in zeroconf_types: + types.append(HOMEKIT_TYPE) def service_update(zeroconf, service_type, name, state_change): """Service state changed.""" @@ -292,25 +307,8 @@ def service_update(zeroconf, service_type, name, state_change): ) ) - async def zeroconf_hass_started(_event): - """Start the service browser.""" - nonlocal zeroconf_types - nonlocal homekit_models - - zeroconf_types = await async_get_zeroconf(hass) - homekit_models = await async_get_homekit(hass) - - types = list(zeroconf_types) - - if HOMEKIT_TYPE not in zeroconf_types: - types.append(HOMEKIT_TYPE) - - _LOGGER.debug("Starting Zeroconf browser") - HaServiceBrowser(zeroconf, types, handlers=[service_update]) - - hass.bus.listen_once(EVENT_HOMEASSISTANT_STARTED, zeroconf_hass_started) - - return True + _LOGGER.debug("Starting Zeroconf browser") + HaServiceBrowser(zeroconf, types, handlers=[service_update]) def handle_homekit(hass, homekit_models, info) -> bool: diff --git a/homeassistant/components/zerproc/translations/es.json b/homeassistant/components/zerproc/translations/es.json index 192afd87e65854..a0bb855fc5ddd2 100644 --- a/homeassistant/components/zerproc/translations/es.json +++ b/homeassistant/components/zerproc/translations/es.json @@ -6,7 +6,7 @@ }, "step": { "confirm": { - "description": "\u00bfQuieres comenzar a configurar?" + "description": "\u00bfQuieres iniciar la configuraci\u00f3n?" } } }, diff --git a/homeassistant/components/zha/api.py b/homeassistant/components/zha/api.py index bdfeb7815c5353..a5b409c7116994 100644 --- a/homeassistant/components/zha/api.py +++ b/homeassistant/components/zha/api.py @@ -23,6 +23,7 @@ ATTR_COMMAND, ATTR_COMMAND_TYPE, ATTR_ENDPOINT_ID, + ATTR_IEEE, ATTR_LEVEL, ATTR_MANUFACTURER, ATTR_MEMBERS, @@ -54,7 +55,12 @@ WARNING_DEVICE_STROBE_YES, ) from .core.group import GroupMember -from .core.helpers import async_is_bindable_target, get_matched_clusters +from .core.helpers import ( + async_is_bindable_target, + convert_install_code, + get_matched_clusters, + qr_to_install_code, +) _LOGGER = logging.getLogger(__name__) @@ -67,9 +73,10 @@ ATTR_DURATION = "duration" ATTR_GROUP = "group" ATTR_IEEE_ADDRESS = "ieee_address" -ATTR_IEEE = "ieee" +ATTR_INSTALL_CODE = "install_code" ATTR_SOURCE_IEEE = "source_ieee" ATTR_TARGET_IEEE = "target_ieee" +ATTR_QR_CODE = "qr_code" SERVICE_PERMIT = "permit" SERVICE_REMOVE = "remove" @@ -83,23 +90,36 @@ SERVICE_ZIGBEE_BIND = "service_zigbee_bind" IEEE_SERVICE = "ieee_based_service" +SERVICE_PERMIT_PARAMS = { + vol.Optional(ATTR_IEEE, default=None): EUI64.convert, + vol.Optional(ATTR_DURATION, default=60): vol.All( + vol.Coerce(int), vol.Range(0, 254) + ), + vol.Inclusive(ATTR_SOURCE_IEEE, "install_code"): EUI64.convert, + vol.Inclusive(ATTR_INSTALL_CODE, "install_code"): convert_install_code, + vol.Exclusive(ATTR_QR_CODE, "install_code"): vol.All(str, qr_to_install_code), +} + SERVICE_SCHEMAS = { SERVICE_PERMIT: vol.Schema( - { - vol.Optional(ATTR_IEEE_ADDRESS, default=None): EUI64.convert, - vol.Optional(ATTR_DURATION, default=60): vol.All( - vol.Coerce(int), vol.Range(0, 254) - ), - } + vol.All( + cv.deprecated(ATTR_IEEE_ADDRESS, replacement_key=ATTR_IEEE), + SERVICE_PERMIT_PARAMS, + ) + ), + IEEE_SERVICE: vol.Schema( + vol.All( + cv.deprecated(ATTR_IEEE_ADDRESS, replacement_key=ATTR_IEEE), + {vol.Required(ATTR_IEEE): EUI64.convert}, + ) ), - IEEE_SERVICE: vol.Schema({vol.Required(ATTR_IEEE_ADDRESS): EUI64.convert}), SERVICE_SET_ZIGBEE_CLUSTER_ATTRIBUTE: vol.Schema( { vol.Required(ATTR_IEEE): EUI64.convert, vol.Required(ATTR_ENDPOINT_ID): cv.positive_int, vol.Required(ATTR_CLUSTER_ID): cv.positive_int, vol.Optional(ATTR_CLUSTER_TYPE, default=CLUSTER_TYPE_IN): cv.string, - vol.Required(ATTR_ATTRIBUTE): cv.positive_int, + vol.Required(ATTR_ATTRIBUTE): vol.Any(int, cv.boolean, cv.string), vol.Required(ATTR_VALUE): cv.string, vol.Optional(ATTR_MANUFACTURER): cv.positive_int, } @@ -169,13 +189,7 @@ @websocket_api.require_admin @websocket_api.async_response @websocket_api.websocket_command( - { - vol.Required("type"): "zha/devices/permit", - vol.Optional(ATTR_IEEE, default=None): EUI64.convert, - vol.Optional(ATTR_DURATION, default=60): vol.All( - vol.Coerce(int), vol.Range(0, 254) - ), - } + {vol.Required("type"): "zha/devices/permit", **SERVICE_PERMIT_PARAMS} ) async def websocket_permit_devices(hass, connection, msg): """Permit ZHA zigbee devices.""" @@ -199,7 +213,21 @@ def async_cleanup() -> None: connection.subscriptions[msg["id"]] = async_cleanup zha_gateway.async_enable_debug_mode() - await zha_gateway.application_controller.permit(time_s=duration, node=ieee) + if ATTR_SOURCE_IEEE in msg: + src_ieee = msg[ATTR_SOURCE_IEEE] + code = msg[ATTR_INSTALL_CODE] + _LOGGER.debug("Allowing join for %s device with install code", src_ieee) + await zha_gateway.application_controller.permit_with_key( + time_s=duration, node=src_ieee, code=code + ) + elif ATTR_QR_CODE in msg: + src_ieee, code = msg[ATTR_QR_CODE] + _LOGGER.debug("Allowing join for %s device with install code", src_ieee) + await zha_gateway.application_controller.permit_with_key( + time_s=duration, node=src_ieee, code=code + ) + else: + await zha_gateway.application_controller.permit(time_s=duration, node=ieee) connection.send_result(msg["id"]) @@ -826,8 +854,25 @@ def async_load_api(hass): async def permit(service): """Allow devices to join this network.""" - duration = service.data.get(ATTR_DURATION) - ieee = service.data.get(ATTR_IEEE_ADDRESS) + duration = service.data[ATTR_DURATION] + ieee = service.data.get(ATTR_IEEE) + if ATTR_SOURCE_IEEE in service.data: + src_ieee = service.data[ATTR_SOURCE_IEEE] + code = service.data[ATTR_INSTALL_CODE] + _LOGGER.info("Allowing join for %s device with install code", src_ieee) + await application_controller.permit_with_key( + time_s=duration, node=src_ieee, code=code + ) + return + + if ATTR_QR_CODE in service.data: + src_ieee, code = service.data[ATTR_QR_CODE] + _LOGGER.info("Allowing join for %s device with install code", src_ieee) + await application_controller.permit_with_key( + time_s=duration, node=src_ieee, code=code + ) + return + if ieee: _LOGGER.info("Permitting joins for %ss on %s device", duration, ieee) else: @@ -840,7 +885,7 @@ async def permit(service): async def remove(service): """Remove a node from the network.""" - ieee = service.data[ATTR_IEEE_ADDRESS] + ieee = service.data[ATTR_IEEE] zha_gateway = hass.data[DATA_ZHA][DATA_ZHA_GATEWAY] zha_device = zha_gateway.get_device(ieee) if zha_device is not None and zha_device.is_coordinator: diff --git a/homeassistant/components/zha/binary_sensor.py b/homeassistant/components/zha/binary_sensor.py index 2548060c0fd9a8..ba95a0e4bc63f3 100644 --- a/homeassistant/components/zha/binary_sensor.py +++ b/homeassistant/components/zha/binary_sensor.py @@ -143,6 +143,25 @@ class Opening(BinarySensor): DEVICE_CLASS = DEVICE_CLASS_OPENING +@STRICT_MATCH( + channel_names=CHANNEL_ON_OFF, + manufacturers="IKEA of Sweden", + models=lambda model: isinstance(model, str) + and model is not None + and model.find("motion") != -1, +) +@STRICT_MATCH( + channel_names=CHANNEL_ON_OFF, + manufacturers="Philips", + models={"SML001", "SML002"}, +) +class Motion(BinarySensor): + """ZHA BinarySensor.""" + + SENSOR_ATTR = "on_off" + DEVICE_CLASS = DEVICE_CLASS_MOTION + + @STRICT_MATCH(channel_names=CHANNEL_ZONE) class IASZone(BinarySensor): """ZHA IAS BinarySensor.""" diff --git a/homeassistant/components/zha/core/channels/base.py b/homeassistant/components/zha/core/channels/base.py index ebc2cd5cd0f9b5..0570070785fbdd 100644 --- a/homeassistant/components/zha/core/channels/base.py +++ b/homeassistant/components/zha/core/channels/base.py @@ -57,7 +57,13 @@ async def wrapper(*args, **kwds): return result except (zigpy.exceptions.ZigbeeException, asyncio.TimeoutError) as ex: - channel.debug("command failed: %s exception: %s", command.__name__, str(ex)) + channel.debug( + "command failed: '%s' args: '%s' kwargs '%s' exception: '%s'", + command.__name__, + args, + kwds, + str(ex), + ) return ex return wrapper diff --git a/homeassistant/components/zha/core/channels/smartenergy.py b/homeassistant/components/zha/core/channels/smartenergy.py index 9138ea097822d0..a4bd2bf2d40104 100644 --- a/homeassistant/components/zha/core/channels/smartenergy.py +++ b/homeassistant/components/zha/core/channels/smartenergy.py @@ -3,7 +3,13 @@ import zigpy.zcl.clusters.smartenergy as smartenergy -from homeassistant.const import LENGTH_FEET, POWER_WATT, TIME_HOURS, TIME_SECONDS +from homeassistant.const import ( + POWER_WATT, + TIME_HOURS, + TIME_SECONDS, + VOLUME_CUBIC_FEET, + VOLUME_CUBIC_METERS, +) from homeassistant.core import callback from .. import registries, typing as zha_typing @@ -61,8 +67,8 @@ class Metering(ZigbeeChannel): unit_of_measure_map = { 0x00: POWER_WATT, - 0x01: f"m³/{TIME_HOURS}", - 0x02: f"{LENGTH_FEET}³/{TIME_HOURS}", + 0x01: f"{VOLUME_CUBIC_METERS}/{TIME_HOURS}", + 0x02: f"{VOLUME_CUBIC_FEET}/{TIME_HOURS}", 0x03: f"ccf/{TIME_HOURS}", 0x04: f"US gal/{TIME_HOURS}", 0x05: f"IMP gal/{TIME_HOURS}", diff --git a/homeassistant/components/zha/core/const.py b/homeassistant/components/zha/core/const.py index 402c1505415f8d..22f8f0f261de53 100644 --- a/homeassistant/components/zha/core/const.py +++ b/homeassistant/components/zha/core/const.py @@ -35,6 +35,7 @@ ATTR_DEVICE_IEEE = "device_ieee" ATTR_DEVICE_TYPE = "device_type" ATTR_ENDPOINTS = "endpoints" +ATTR_ENDPOINT_NAMES = "endpoint_names" ATTR_ENDPOINT_ID = "endpoint_id" ATTR_IEEE = "ieee" ATTR_IN_CLUSTERS = "in_clusters" @@ -46,6 +47,7 @@ ATTR_MEMBERS = "members" ATTR_MODEL = "model" ATTR_NAME = "name" +ATTR_NEIGHBORS = "neighbors" ATTR_NODE_DESCRIPTOR = "node_descriptor" ATTR_NWK = "nwk" ATTR_OUT_CLUSTERS = "out_clusters" diff --git a/homeassistant/components/zha/core/device.py b/homeassistant/components/zha/core/device.py index b8229793a4823a..68fb7393cd51b5 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -9,7 +9,7 @@ from zigpy import types import zigpy.exceptions -from zigpy.profiles import zha, zll +from zigpy.profiles import PROFILES import zigpy.quirks from zigpy.zcl.clusters.general import Groups import zigpy.zdo.types as zdo_types @@ -33,6 +33,7 @@ ATTR_DEVICE_IEEE, ATTR_DEVICE_TYPE, ATTR_ENDPOINT_ID, + ATTR_ENDPOINT_NAMES, ATTR_ENDPOINTS, ATTR_IEEE, ATTR_LAST_SEEN, @@ -41,6 +42,7 @@ ATTR_MANUFACTURER_CODE, ATTR_MODEL, ATTR_NAME, + ATTR_NEIGHBORS, ATTR_NODE_DESCRIPTOR, ATTR_NWK, ATTR_POWER_SOURCE, @@ -436,6 +438,39 @@ def zha_device_info(self): } for entity_ref in self.gateway.device_registry[self.ieee] ] + + # Return the neighbor information + device_info[ATTR_NEIGHBORS] = [ + { + "device_type": neighbor.neighbor.device_type.name, + "rx_on_when_idle": neighbor.neighbor.rx_on_when_idle.name, + "relationship": neighbor.neighbor.relationship.name, + "extended_pan_id": str(neighbor.neighbor.extended_pan_id), + "ieee": str(neighbor.neighbor.ieee), + "nwk": str(neighbor.neighbor.nwk), + "permit_joining": neighbor.neighbor.permit_joining.name, + "depth": str(neighbor.neighbor.depth), + "lqi": str(neighbor.neighbor.lqi), + } + for neighbor in self._zigpy_device.neighbors + ] + + # Return endpoint device type Names + names = [] + for endpoint in (ep for epid, ep in self.device.endpoints.items() if epid): + profile = PROFILES.get(endpoint.profile_id) + if profile and endpoint.device_type is not None: + # DeviceType provides undefined enums + names.append({ATTR_NAME: profile.DeviceType(endpoint.device_type).name}) + else: + names.append( + { + ATTR_NAME: f"unknown {endpoint.device_type} device_type " + "of 0x{endpoint.profile_id:04x} profile id" + } + ) + device_info[ATTR_ENDPOINT_NAMES] = names + reg_device = self.gateway.ha_device_registry.async_get(self.device_id) if reg_device is not None: device_info["user_given_name"] = reg_device.name_by_user @@ -474,7 +509,7 @@ def async_get_std_clusters(self): CLUSTER_TYPE_OUT: endpoint.out_clusters, } for (ep_id, endpoint) in self._zigpy_device.endpoints.items() - if ep_id != 0 and endpoint.profile_id in (zha.PROFILE_ID, zll.PROFILE_ID) + if ep_id != 0 and endpoint.profile_id in PROFILES } @callback diff --git a/homeassistant/components/zha/core/helpers.py b/homeassistant/components/zha/core/helpers.py index 7813c7133ad5f7..0e967a7a123a20 100644 --- a/homeassistant/components/zha/core/helpers.py +++ b/homeassistant/components/zha/core/helpers.py @@ -6,15 +6,19 @@ """ import asyncio +import binascii import collections import functools import itertools import logging from random import uniform -from typing import Any, Callable, Iterator, List, Optional +import re +from typing import Any, Callable, Iterator, List, Optional, Tuple +import voluptuous as vol import zigpy.exceptions import zigpy.types +import zigpy.util from homeassistant.core import State, callback @@ -205,3 +209,63 @@ async def wrapper(channel, *args, **kwargs): return wrapper return decorator + + +def convert_install_code(value: str) -> bytes: + """Convert string to install code bytes and validate length.""" + + try: + code = binascii.unhexlify(value.replace("-", "").lower()) + except binascii.Error as exc: + raise vol.Invalid(f"invalid hex string: {value}") from exc + + if len(code) != 18: # 16 byte code + 2 crc bytes + raise vol.Invalid("invalid length of the install code") + + if zigpy.util.convert_install_code(code) is None: + raise vol.Invalid("invalid install code") + + return code + + +QR_CODES = ( + # Consciot + r"^([\da-fA-F]{16})\|([\da-fA-F]{36})$", + # Enbrighten + r""" + ^Z: + ([0-9a-fA-F]{16}) # IEEE address + \$I: + ([0-9a-fA-F]{36}) # install code + $ + """, + # Aqara + r""" + \$A: + ([0-9a-fA-F]{16}) # IEEE address + \$I: + ([0-9a-fA-F]{36}) # install code + $ + """, +) + + +def qr_to_install_code(qr_code: str) -> Tuple[zigpy.types.EUI64, bytes]: + """Try to parse the QR code. + + if successful, return a tuple of a EUI64 address and install code. + """ + + for code_pattern in QR_CODES: + match = re.search(code_pattern, qr_code, re.VERBOSE) + if match is None: + continue + + ieee_hex = binascii.unhexlify(match[1]) + ieee = zigpy.types.EUI64(ieee_hex[::-1]) + install_code = match[2] + # install_code sanity check + install_code = convert_install_code(install_code) + return ieee, install_code + + raise vol.Invalid(f"couldn't convert qr code: {qr_code}") diff --git a/homeassistant/components/zha/core/registries.py b/homeassistant/components/zha/core/registries.py index 0cc350ba4b8b9c..81521748da0919 100644 --- a/homeassistant/components/zha/core/registries.py +++ b/homeassistant/components/zha/core/registries.py @@ -180,10 +180,12 @@ def weight(self) -> int: """ weight = 0 if self.models: - weight += 401 - len(self.models) + weight += 401 - (1 if callable(self.models) else len(self.models)) if self.manufacturers: - weight += 301 - len(self.manufacturers) + weight += 301 - ( + 1 if callable(self.manufacturers) else len(self.manufacturers) + ) weight += 10 * len(self.channel_names) weight += 5 * len(self.generic_ids) diff --git a/homeassistant/components/zha/manifest.json b/homeassistant/components/zha/manifest.json index 4bde073a933431..a26cd6cc8e6007 100644 --- a/homeassistant/components/zha/manifest.json +++ b/homeassistant/components/zha/manifest.json @@ -4,15 +4,15 @@ "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zha", "requirements": [ - "bellows==0.20.1", + "bellows==0.20.3", "pyserial==3.4", - "zha-quirks==0.0.44", + "zha-quirks==0.0.45", "zigpy-cc==0.5.2", - "zigpy-deconz==0.9.2", - "zigpy==0.23.2", + "zigpy-deconz==0.10.0", + "zigpy==0.25.0", "zigpy-xbee==0.13.0", "zigpy-zigate==0.6.2", - "zigpy-znp==0.1.1" + "zigpy-znp==0.2.1" ], "codeowners": ["@dmulcahey", "@adminiuga"] } diff --git a/homeassistant/components/zha/sensor.py b/homeassistant/components/zha/sensor.py index 6e2878f371b8dc..6f06b3818444c2 100644 --- a/homeassistant/components/zha/sensor.py +++ b/homeassistant/components/zha/sensor.py @@ -14,8 +14,10 @@ ) from homeassistant.const import ( ATTR_UNIT_OF_MEASUREMENT, + LIGHT_LUX, PERCENTAGE, POWER_WATT, + PRESSURE_HPA, STATE_UNKNOWN, TEMP_CELSIUS, ) @@ -25,6 +27,7 @@ from .core import discovery from .core.const import ( + CHANNEL_ANALOG_INPUT, CHANNEL_ELECTRICAL_MEASUREMENT, CHANNEL_HUMIDITY, CHANNEL_ILLUMINANCE, @@ -151,6 +154,13 @@ def formatter(self, value): return round(float(value * self._multiplier) / self._divisor) +@STRICT_MATCH(channel_names=CHANNEL_ANALOG_INPUT, manufacturers="Digi") +class AnalogInput(Sensor): + """Sensor that displays analog input values.""" + + SENSOR_ATTR = "present_value" + + @STRICT_MATCH(channel_names=CHANNEL_POWER_CONFIGURATION) class Battery(Sensor): """Battery sensor of power configuration cluster.""" @@ -234,7 +244,7 @@ class Illuminance(Sensor): SENSOR_ATTR = "measured_value" _device_class = DEVICE_CLASS_ILLUMINANCE - _unit = "lx" + _unit = LIGHT_LUX @staticmethod def formatter(value): @@ -266,7 +276,7 @@ class Pressure(Sensor): SENSOR_ATTR = "measured_value" _device_class = DEVICE_CLASS_PRESSURE _decimals = 0 - _unit = "hPa" + _unit = PRESSURE_HPA @STRICT_MATCH(channel_names=CHANNEL_TEMPERATURE) diff --git a/homeassistant/components/zha/services.yaml b/homeassistant/components/zha/services.yaml index 257d1026f7f3fd..74793d6000f571 100644 --- a/homeassistant/components/zha/services.yaml +++ b/homeassistant/components/zha/services.yaml @@ -9,6 +9,15 @@ permit: ieee_address: description: IEEE address of the node permitting new joins example: "00:0d:6f:00:05:7d:2d:34" + source_ieee: + description: IEEE address of the joining device (must be used with install code) + example: "00:0a:bf:00:01:10:23:35" + install_code: + description: Install code of the joining device (must be used with source_ieee) + example: "1234-5678-1234-5678-AABB-CCDD-AABB-CCDD-EEFF" + qr_code: + description: value of the QR install code (different between vendors) + example: "Z:000D6FFFFED4163B$I:52797BF4A5084DAA8E1712B61741CA024051" remove: description: Remove a node from the Zigbee network. diff --git a/homeassistant/components/zha/strings.json b/homeassistant/components/zha/strings.json index 915501b2437d6a..93b5cd7ccf5d6b 100644 --- a/homeassistant/components/zha/strings.json +++ b/homeassistant/components/zha/strings.json @@ -21,9 +21,9 @@ } } }, - "error": { "cannot_connect": "Unable to connect to ZHA device." }, + "error": { "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" }, "abort": { - "single_instance_allowed": "Only a single configuration of ZHA is allowed." + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" } }, "device_automation": { diff --git a/homeassistant/components/zha/translations/ca.json b/homeassistant/components/zha/translations/ca.json index c1092875b01bb9..7fb36f115324c3 100644 --- a/homeassistant/components/zha/translations/ca.json +++ b/homeassistant/components/zha/translations/ca.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "single_instance_allowed": "Nom\u00e9s es permet una \u00fanica configuraci\u00f3 de ZHA." + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "error": { - "cannot_connect": "No s'ha pogut connectar amb el dispositiu ZHA." + "cannot_connect": "Ha fallat la connexi\u00f3" }, "step": { "pick_radio": { diff --git a/homeassistant/components/zha/translations/en.json b/homeassistant/components/zha/translations/en.json index d54eec90b5f2ce..d9c78171d943d1 100644 --- a/homeassistant/components/zha/translations/en.json +++ b/homeassistant/components/zha/translations/en.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "single_instance_allowed": "Only a single configuration of ZHA is allowed." + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "error": { - "cannot_connect": "Unable to connect to ZHA device." + "cannot_connect": "Failed to connect" }, "step": { "pick_radio": { diff --git a/homeassistant/components/zha/translations/et.json b/homeassistant/components/zha/translations/et.json new file mode 100644 index 00000000000000..27a7113e3d2c70 --- /dev/null +++ b/homeassistant/components/zha/translations/et.json @@ -0,0 +1,57 @@ +{ + "config": { + "step": { + "port_config": { + "title": "Seaded" + } + } + }, + "device_automation": { + "action_type": { + "warn": "Hoiata" + }, + "trigger_subtype": { + "both_buttons": "M\u00f5lemad nupud", + "button_1": "Esimene nupp", + "button_2": "Teine nupp", + "button_3": "Kolmas nupp", + "button_4": "Neljas nupp", + "button_5": "Viies nupp", + "button_6": "Kuues nupp", + "close": "Sulge", + "dim_down": "H\u00e4marda", + "dim_up": "Tee heledamaks", + "left": "Vasakule", + "open": "Ava", + "right": "Paremale", + "turn_off": "L\u00fclita v\u00e4lja", + "turn_on": "L\u00fclita sisse" + }, + "trigger_type": { + "device_dropped": "Seade kukkus", + "device_flipped": "Seade \" {subtype} \" p\u00f6\u00f6rati \u00fcmber", + "device_knocked": "Seadet \" {subtype} \" koputati", + "device_offline": "Seade on v\u00f5rgu\u00fchenduseta", + "device_rotated": "Seadet \" {subtype} \" keerati", + "device_shaken": "Seadet raputati", + "device_slid": "Seade \" {subtype} \" libises", + "device_tilted": "Seadet kallutati", + "remote_button_alt_double_press": "\"{subtype}\" on topeltkl\u00f5psatud (alternatiivre\u017eiim)", + "remote_button_alt_long_press": "\"{subtype}\" nuppu vajutati pikalt (alternatiivre\u017eiim)", + "remote_button_alt_long_release": "\"{subtype}\" nupp vabastati peale pikka vajutust (alternatiivre\u017eiim)", + "remote_button_alt_quadruple_press": "\"{subtype}\" on neljakordselt kl\u00f5psatud (alternatiivre\u017eiim)", + "remote_button_alt_quintuple_press": "\"{subtype}\" on neljakordselt kl\u00f5psatud (alternatiivre\u017eiim)", + "remote_button_alt_short_press": "\"{subtype}\" nuppu vajutati (alternatiivre\u017eiim)", + "remote_button_alt_short_release": "\"{subtype}\" nupp vabastati (alternatiivre\u017eiim)", + "remote_button_alt_triple_press": "\"{subtype}\" on kolmekordselt kl\u00f5psatud (alternatiivre\u017eiim)", + "remote_button_double_press": "\" {subtype} \" on topeltkl\u00f5psatud", + "remote_button_long_press": "\" {subtype} \" on pikalt alla vajutatud", + "remote_button_long_release": "\"{subtype}\" nupp vabastatati p\u00e4rast pikka vajutust", + "remote_button_quadruple_press": "\"{subtype}\" nuppu on neljakordselt kl\u00f5psatud", + "remote_button_quintuple_press": "\"{subtype}\" nuppu on viiekordselt kl\u00f5psatud", + "remote_button_short_press": "\"{subtype}\" nupp on vajutatud", + "remote_button_short_release": "\"{subtype}\" nupp vabastati", + "remote_button_triple_press": "Nuppu \"{subtype}\" kl\u00f5psati kolm korda" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zha/translations/it.json b/homeassistant/components/zha/translations/it.json index 8330583cf74a9a..066a45feab1d14 100644 --- a/homeassistant/components/zha/translations/it.json +++ b/homeassistant/components/zha/translations/it.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "single_instance_allowed": "\u00c8 consentita solo una singola configurazione di ZHA." + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "error": { - "cannot_connect": "Impossibile connettersi al dispositivo ZHA." + "cannot_connect": "Impossibile connettersi" }, "step": { "pick_radio": { diff --git a/homeassistant/components/zha/translations/no.json b/homeassistant/components/zha/translations/no.json index e97a4e27ccd661..6dac24f8256dbf 100644 --- a/homeassistant/components/zha/translations/no.json +++ b/homeassistant/components/zha/translations/no.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "single_instance_allowed": "Kun en konfigurasjon av ZHA er tillatt." + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "error": { - "cannot_connect": "Kan ikke koble til ZHA-enhet." + "cannot_connect": "Tilkobling mislyktes." }, "step": { "pick_radio": { diff --git a/homeassistant/components/zha/translations/pl.json b/homeassistant/components/zha/translations/pl.json index b2089e0dad8356..5e98269028274f 100644 --- a/homeassistant/components/zha/translations/pl.json +++ b/homeassistant/components/zha/translations/pl.json @@ -65,6 +65,7 @@ "device_dropped": "nast\u0105pi upadek urz\u0105dzenia", "device_flipped": "nast\u0105pi odwr\u00f3cenie urz\u0105dzenia \"{subtype}\"", "device_knocked": "nast\u0105pi pukni\u0119cie w urz\u0105dzenie \"{subtype}\"", + "device_offline": "Urz\u0105dzenie offline", "device_rotated": "nast\u0105pi obr\u00f3cenie urz\u0105dzenia \"{subtype}\"", "device_shaken": "nast\u0105pi potrz\u0105\u015bni\u0119cie urz\u0105dzeniem", "device_slid": "nast\u0105pi przesuni\u0119cie urz\u0105dzenia \"{subtype}\"", diff --git a/homeassistant/components/zha/translations/ru.json b/homeassistant/components/zha/translations/ru.json index eea55972c62e83..4070d759a90a97 100644 --- a/homeassistant/components/zha/translations/ru.json +++ b/homeassistant/components/zha/translations/ru.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430." + "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 \u043a \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443." + "cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f." }, "step": { "pick_radio": { diff --git a/homeassistant/components/zha/translations/zh-Hant.json b/homeassistant/components/zha/translations/zh-Hant.json index 5965911d459927..e62d61ac8eac60 100644 --- a/homeassistant/components/zha/translations/zh-Hant.json +++ b/homeassistant/components/zha/translations/zh-Hant.json @@ -1,10 +1,10 @@ { "config": { "abort": { - "single_instance_allowed": "\u50c5\u5141\u8a31\u8a2d\u5b9a\u4e00\u7d44 ZHA\u3002" + "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44\u8a2d\u5099\u3002" }, "error": { - "cannot_connect": "\u7121\u6cd5\u9023\u7dda\u81f3 ZHA \u8a2d\u5099\u3002" + "cannot_connect": "\u9023\u7dda\u5931\u6557" }, "step": { "pick_radio": { diff --git a/homeassistant/components/zodiac/__init__.py b/homeassistant/components/zodiac/__init__.py new file mode 100644 index 00000000000000..d00cc560f22d7a --- /dev/null +++ b/homeassistant/components/zodiac/__init__.py @@ -0,0 +1,19 @@ +"""The zodiac component.""" +import voluptuous as vol + +from homeassistant.core import HomeAssistant +from homeassistant.helpers.discovery import async_load_platform + +from .const import DOMAIN + +CONFIG_SCHEMA = vol.Schema( + {vol.Optional(DOMAIN): {}}, + extra=vol.ALLOW_EXTRA, +) + + +async def async_setup(hass: HomeAssistant, config: dict): + """Set up the zodiac component.""" + hass.async_create_task(async_load_platform(hass, "sensor", DOMAIN, {}, config)) + + return True diff --git a/homeassistant/components/zodiac/const.py b/homeassistant/components/zodiac/const.py new file mode 100644 index 00000000000000..c3e7f13d5e372b --- /dev/null +++ b/homeassistant/components/zodiac/const.py @@ -0,0 +1,31 @@ +"""Constants for Zodiac.""" +DOMAIN = "zodiac" + +# Signs +SIGN_ARIES = "aries" +SIGN_TAURUS = "taurus" +SIGN_GEMINI = "gemini" +SIGN_CANCER = "cancer" +SIGN_LEO = "leo" +SIGN_VIRGO = "virgo" +SIGN_LIBRA = "libra" +SIGN_SCORPIO = "scorpio" +SIGN_SAGITTARIUS = "sagittarius" +SIGN_CAPRICORN = "capricorn" +SIGN_AQUARIUS = "aquarius" +SIGN_PISCES = "pisces" + +# Elements +ELEMENT_FIRE = "fire" +ELEMENT_AIR = "air" +ELEMENT_EARTH = "earth" +ELEMENT_WATER = "water" + +# Modality +MODALITY_CARDINAL = "cardinal" +MODALITY_FIXED = "fixed" +MODALITY_MUTABLE = "mutable" + +# Attributes +ATTR_ELEMENT = "element" +ATTR_MODALITY = "modality" diff --git a/homeassistant/components/zodiac/manifest.json b/homeassistant/components/zodiac/manifest.json new file mode 100644 index 00000000000000..9d38c2cff39139 --- /dev/null +++ b/homeassistant/components/zodiac/manifest.json @@ -0,0 +1,7 @@ +{ + "domain": "zodiac", + "name": "Zodiac", + "documentation": "https://www.home-assistant.io/integrations/zodiac", + "codeowners": ["@JulienTant"], + "quality_scale": "silver" +} diff --git a/homeassistant/components/zodiac/sensor.py b/homeassistant/components/zodiac/sensor.py new file mode 100644 index 00000000000000..06bd52f6bf5a1e --- /dev/null +++ b/homeassistant/components/zodiac/sensor.py @@ -0,0 +1,220 @@ +"""Support for tracking the zodiac sign.""" +import logging + +from homeassistant.helpers.entity import Entity +from homeassistant.util.dt import as_local, utcnow + +from .const import ( + ATTR_ELEMENT, + ATTR_MODALITY, + DOMAIN, + ELEMENT_AIR, + ELEMENT_EARTH, + ELEMENT_FIRE, + ELEMENT_WATER, + MODALITY_CARDINAL, + MODALITY_FIXED, + MODALITY_MUTABLE, + SIGN_AQUARIUS, + SIGN_ARIES, + SIGN_CANCER, + SIGN_CAPRICORN, + SIGN_GEMINI, + SIGN_LEO, + SIGN_LIBRA, + SIGN_PISCES, + SIGN_SAGITTARIUS, + SIGN_SCORPIO, + SIGN_TAURUS, + SIGN_VIRGO, +) + +_LOGGER = logging.getLogger(__name__) + +ZODIAC_BY_DATE = ( + ( + (21, 3), + (20, 4), + SIGN_ARIES, + { + ATTR_ELEMENT: ELEMENT_FIRE, + ATTR_MODALITY: MODALITY_CARDINAL, + }, + ), + ( + (21, 4), + (20, 5), + SIGN_TAURUS, + { + ATTR_ELEMENT: ELEMENT_EARTH, + ATTR_MODALITY: MODALITY_FIXED, + }, + ), + ( + (21, 5), + (21, 6), + SIGN_GEMINI, + { + ATTR_ELEMENT: ELEMENT_AIR, + ATTR_MODALITY: MODALITY_MUTABLE, + }, + ), + ( + (22, 6), + (22, 7), + SIGN_CANCER, + { + ATTR_ELEMENT: ELEMENT_WATER, + ATTR_MODALITY: MODALITY_CARDINAL, + }, + ), + ( + (23, 7), + (22, 8), + SIGN_LEO, + { + ATTR_ELEMENT: ELEMENT_FIRE, + ATTR_MODALITY: MODALITY_FIXED, + }, + ), + ( + (23, 8), + (21, 9), + SIGN_VIRGO, + { + ATTR_ELEMENT: ELEMENT_EARTH, + ATTR_MODALITY: MODALITY_MUTABLE, + }, + ), + ( + (22, 9), + (22, 10), + SIGN_LIBRA, + { + ATTR_ELEMENT: ELEMENT_AIR, + ATTR_MODALITY: MODALITY_CARDINAL, + }, + ), + ( + (23, 10), + (22, 11), + SIGN_SCORPIO, + { + ATTR_ELEMENT: ELEMENT_WATER, + ATTR_MODALITY: MODALITY_FIXED, + }, + ), + ( + (23, 11), + (21, 12), + SIGN_SAGITTARIUS, + { + ATTR_ELEMENT: ELEMENT_FIRE, + ATTR_MODALITY: MODALITY_MUTABLE, + }, + ), + ( + (22, 12), + (20, 1), + SIGN_CAPRICORN, + { + ATTR_ELEMENT: ELEMENT_EARTH, + ATTR_MODALITY: MODALITY_CARDINAL, + }, + ), + ( + (21, 1), + (19, 2), + SIGN_AQUARIUS, + { + ATTR_ELEMENT: ELEMENT_AIR, + ATTR_MODALITY: MODALITY_FIXED, + }, + ), + ( + (20, 2), + (20, 3), + SIGN_PISCES, + { + ATTR_ELEMENT: ELEMENT_WATER, + ATTR_MODALITY: MODALITY_MUTABLE, + }, + ), +) + +ZODIAC_ICONS = { + SIGN_ARIES: "mdi:zodiac-aries", + SIGN_TAURUS: "mdi:zodiac-taurus", + SIGN_GEMINI: "mdi:zodiac-gemini", + SIGN_CANCER: "mdi:zodiac-cancer", + SIGN_LEO: "mdi:zodiac-leo", + SIGN_VIRGO: "mdi:zodiac-virgo", + SIGN_LIBRA: "mdi:zodiac-libra", + SIGN_SCORPIO: "mdi:zodiac-scorpio", + SIGN_SAGITTARIUS: "mdi:zodiac-sagittarius", + SIGN_CAPRICORN: "mdi:zodiac-capricorn", + SIGN_AQUARIUS: "mdi:zodiac-aquarius", + SIGN_PISCES: "mdi:zodiac-pisces", +} + + +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): + """Set up the Zodiac sensor platform.""" + if discovery_info is None: + return + + async_add_entities([ZodiacSensor()], True) + + +class ZodiacSensor(Entity): + """Representation of a Zodiac sensor.""" + + def __init__(self): + """Initialize the zodiac sensor.""" + self._attrs = None + self._state = None + + @property + def unique_id(self): + """Return a unique ID.""" + return DOMAIN + + @property + def name(self): + """Return the name of the entity.""" + return "Zodiac" + + @property + def device_class(self): + """Return the device class of the entity.""" + return "zodiac__sign" + + @property + def state(self): + """Return the state of the device.""" + return self._state + + @property + def icon(self): + """Icon to use in the frontend, if any.""" + return ZODIAC_ICONS.get(self._state) + + @property + def device_state_attributes(self): + """Return the state attributes.""" + return self._attrs + + async def async_update(self): + """Get the time and updates the state.""" + today = as_local(utcnow()).date() + + month = int(today.month) + day = int(today.day) + + for sign in ZODIAC_BY_DATE: + if (month == sign[0][1] and day >= sign[0][0]) or ( + month == sign[1][1] and day <= sign[1][0] + ): + self._state = sign[2] + self._attrs = sign[3] + break diff --git a/homeassistant/components/zodiac/strings.sensor.json b/homeassistant/components/zodiac/strings.sensor.json new file mode 100644 index 00000000000000..e33465967e322f --- /dev/null +++ b/homeassistant/components/zodiac/strings.sensor.json @@ -0,0 +1,18 @@ +{ + "state": { + "zodiac__sign": { + "aries": "Aries", + "taurus": "Taurus", + "gemini": "Gemini", + "cancer": "Cancer", + "leo": "Leo", + "virgo": "Virgo", + "libra": "Libra", + "scorpio": "Scorpio", + "sagittarius": "Sagittarius", + "capricorn": "Capricorn", + "aquarius": "Aquarius", + "pisces": "Pisces" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zodiac/translations/sensor.ca.json b/homeassistant/components/zodiac/translations/sensor.ca.json new file mode 100644 index 00000000000000..f4699838cdeb51 --- /dev/null +++ b/homeassistant/components/zodiac/translations/sensor.ca.json @@ -0,0 +1,18 @@ +{ + "state": { + "zodiac__sign": { + "aquarius": "Aquari", + "aries": "\u00c0ries", + "cancer": "C\u00e0ncer", + "capricorn": "Capricorn", + "gemini": "Bessons", + "leo": "Lle\u00f3", + "libra": "Balan\u00e7a", + "pisces": "Peixos", + "sagittarius": "Sagitari", + "scorpio": "Escorp\u00ed", + "taurus": "Taure", + "virgo": "Verge" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zodiac/translations/sensor.de.json b/homeassistant/components/zodiac/translations/sensor.de.json new file mode 100644 index 00000000000000..d60bd068b8966e --- /dev/null +++ b/homeassistant/components/zodiac/translations/sensor.de.json @@ -0,0 +1,18 @@ +{ + "state": { + "zodiac__sign": { + "aquarius": "Wassermann", + "aries": "Widder", + "cancer": "Krebs", + "capricorn": "Steinbock", + "gemini": "Zwillinge", + "leo": "L\u00f6we", + "libra": "Waage", + "pisces": "Fische", + "sagittarius": "Sch\u00fctze", + "scorpio": "Skorpion", + "taurus": "Stier", + "virgo": "Jungfrau" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zodiac/translations/sensor.el.json b/homeassistant/components/zodiac/translations/sensor.el.json new file mode 100644 index 00000000000000..df3931e6346285 --- /dev/null +++ b/homeassistant/components/zodiac/translations/sensor.el.json @@ -0,0 +1,18 @@ +{ + "state": { + "zodiac__sign": { + "aquarius": "\u03a5\u03b4\u03c1\u03bf\u03c7\u03cc\u03bf\u03c2", + "aries": "\u039a\u03c1\u03b9\u03cc\u03c2", + "cancer": "\u039a\u03b1\u03c1\u03ba\u03af\u03bd\u03bf\u03c2", + "capricorn": "\u0391\u03b9\u03b3\u03cc\u03ba\u03b5\u03c1\u03c9\u03c2", + "gemini": "\u0394\u03af\u03b4\u03c5\u03bc\u03bf\u03c2", + "leo": "\u039b\u03ad\u03c9\u03bd", + "libra": "\u0396\u03c5\u03b3\u03cc\u03c2", + "pisces": "\u0399\u03c7\u03b8\u03cd\u03c2", + "sagittarius": "\u03a4\u03bf\u03be\u03cc\u03c4\u03b7\u03c2", + "scorpio": "\u03a3\u03ba\u03bf\u03c1\u03c0\u03b9\u03cc\u03c2", + "taurus": "\u03a4\u03b1\u03cd\u03c1\u03bf\u03c2", + "virgo": "\u03a0\u03b1\u03c1\u03b8\u03ad\u03bd\u03bf\u03c2" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zodiac/translations/sensor.en.json b/homeassistant/components/zodiac/translations/sensor.en.json new file mode 100644 index 00000000000000..cd671e146ed712 --- /dev/null +++ b/homeassistant/components/zodiac/translations/sensor.en.json @@ -0,0 +1,18 @@ +{ + "state": { + "zodiac__sign": { + "aquarius": "Aquarius", + "aries": "Aries", + "cancer": "Cancer", + "capricorn": "Capricorn", + "gemini": "Gemini", + "leo": "Leo", + "libra": "Libra", + "pisces": "Pisces", + "sagittarius": "Sagittarius", + "scorpio": "Scorpio", + "taurus": "Taurus", + "virgo": "Virgo" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zodiac/translations/sensor.es.json b/homeassistant/components/zodiac/translations/sensor.es.json new file mode 100644 index 00000000000000..fbd9d1bd653245 --- /dev/null +++ b/homeassistant/components/zodiac/translations/sensor.es.json @@ -0,0 +1,18 @@ +{ + "state": { + "zodiac__sign": { + "aquarius": "Acuario", + "aries": "Aries", + "cancer": "C\u00e1ncer", + "capricorn": "Capricornio", + "gemini": "G\u00e9minis", + "leo": "Leo", + "libra": "Libra", + "pisces": "Piscis", + "sagittarius": "Sagitario", + "scorpio": "Escorpio", + "taurus": "Tauro", + "virgo": "Virgo" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zodiac/translations/sensor.et.json b/homeassistant/components/zodiac/translations/sensor.et.json new file mode 100644 index 00000000000000..caf26a0130e3fd --- /dev/null +++ b/homeassistant/components/zodiac/translations/sensor.et.json @@ -0,0 +1,18 @@ +{ + "state": { + "zodiac__sign": { + "aquarius": "Veevalaja", + "aries": "J\u00e4\u00e4r", + "cancer": "V\u00e4hk", + "capricorn": "Kaljukits", + "gemini": "Kaksikud", + "leo": "L\u00f5vi", + "libra": "Kaalud", + "pisces": "Kalad", + "sagittarius": "Ambur", + "scorpio": "Skorpion", + "taurus": "S\u00f5nn", + "virgo": "Neitsi" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zodiac/translations/sensor.fr.json b/homeassistant/components/zodiac/translations/sensor.fr.json new file mode 100644 index 00000000000000..8c492c29f0b49d --- /dev/null +++ b/homeassistant/components/zodiac/translations/sensor.fr.json @@ -0,0 +1,18 @@ +{ + "state": { + "zodiac__sign": { + "aquarius": "Verseau", + "aries": "B\u00e9lier", + "cancer": "Cancer", + "capricorn": "Capricorne", + "gemini": "G\u00e9meaux", + "leo": "Lion", + "libra": "Balance", + "pisces": "Poissons", + "sagittarius": "Sagittaire", + "scorpio": "Scorpion", + "taurus": "Taureau", + "virgo": "Vierge" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zodiac/translations/sensor.it.json b/homeassistant/components/zodiac/translations/sensor.it.json new file mode 100644 index 00000000000000..f814476b9cd36a --- /dev/null +++ b/homeassistant/components/zodiac/translations/sensor.it.json @@ -0,0 +1,18 @@ +{ + "state": { + "zodiac__sign": { + "aquarius": "Acquario", + "aries": "Ariete", + "cancer": "Cancro", + "capricorn": "Capricorno", + "gemini": "Gemelli", + "leo": "Leone", + "libra": "Bilancia", + "pisces": "Pesci", + "sagittarius": "Sagittario", + "scorpio": "Scorpione", + "taurus": "Toro", + "virgo": "Vergine" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zodiac/translations/sensor.ko.json b/homeassistant/components/zodiac/translations/sensor.ko.json new file mode 100644 index 00000000000000..0a9fc83cdeacf9 --- /dev/null +++ b/homeassistant/components/zodiac/translations/sensor.ko.json @@ -0,0 +1,18 @@ +{ + "state": { + "zodiac__sign": { + "aquarius": "\ubb3c\ubcd1 \uc790\ub9ac", + "aries": "\uc591 \uc790\ub9ac", + "cancer": "\uac8c \uc790\ub9ac", + "capricorn": "\uc5fc\uc18c \uc790\ub9ac", + "gemini": "\uc30d\ub465\uc774 \uc790\ub9ac", + "leo": "\uc0ac\uc790 \uc790\ub9ac", + "libra": "\ucc9c\uce6d \uc790\ub9ac", + "pisces": "\ubb3c\uace0\uae30 \uc790\ub9ac", + "sagittarius": "\uad81\uc218 \uc790\ub9ac", + "scorpio": "\uc804\uac08 \uc790\ub9ac", + "taurus": "\ud669\uc18c \uc790\ub9ac", + "virgo": "\ucc98\ub140 \uc790\ub9ac" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zodiac/translations/sensor.lb.json b/homeassistant/components/zodiac/translations/sensor.lb.json new file mode 100644 index 00000000000000..65ae5095c39dbe --- /dev/null +++ b/homeassistant/components/zodiac/translations/sensor.lb.json @@ -0,0 +1,18 @@ +{ + "state": { + "zodiac__sign": { + "aquarius": "Waassermann", + "aries": "Widder", + "cancer": "Kriibs", + "capricorn": "Steebock", + "gemini": "Zwillinge", + "leo": "L\u00e9iw", + "libra": "Wo", + "pisces": "F\u00ebsch", + "sagittarius": "Sch\u00ebtz", + "scorpio": "Skorpioun", + "taurus": "St\u00e9ier", + "virgo": "Jongfra" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zodiac/translations/sensor.nl.json b/homeassistant/components/zodiac/translations/sensor.nl.json new file mode 100644 index 00000000000000..c07b20de21b819 --- /dev/null +++ b/homeassistant/components/zodiac/translations/sensor.nl.json @@ -0,0 +1,17 @@ +{ + "state": { + "zodiac__sign": { + "aquarius": "Waterman", + "aries": "Ram", + "capricorn": "Steenbok", + "gemini": "Tweelingen", + "leo": "Leo", + "libra": "Weegschaal", + "pisces": "Vissen", + "sagittarius": "Boogschutter", + "scorpio": "Schorpioen", + "taurus": "Stier", + "virgo": "Maagd" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zodiac/translations/sensor.no.json b/homeassistant/components/zodiac/translations/sensor.no.json new file mode 100644 index 00000000000000..dea02eb8ce76d4 --- /dev/null +++ b/homeassistant/components/zodiac/translations/sensor.no.json @@ -0,0 +1,18 @@ +{ + "state": { + "zodiac__sign": { + "aquarius": "Vannmannen", + "aries": "V\u00e6ren", + "cancer": "Kreft", + "capricorn": "Steinbukken", + "gemini": "Tvillingene", + "leo": "L\u00f8ven", + "libra": "Vekten", + "pisces": "Fiskene", + "sagittarius": "Skytten", + "scorpio": "Skorpionen", + "taurus": "Tyren", + "virgo": "Jomfruen" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zodiac/translations/sensor.pl.json b/homeassistant/components/zodiac/translations/sensor.pl.json new file mode 100644 index 00000000000000..7aecf4724a117c --- /dev/null +++ b/homeassistant/components/zodiac/translations/sensor.pl.json @@ -0,0 +1,18 @@ +{ + "state": { + "zodiac__sign": { + "aquarius": "Wodnik", + "aries": "Baran", + "cancer": "Rak", + "capricorn": "Kozioro\u017cec", + "gemini": "Bli\u017ani\u0119ta", + "leo": "Lew", + "libra": "Waga", + "pisces": "Ryby", + "sagittarius": "Strzelec", + "scorpio": "Skorpion", + "taurus": "Byk", + "virgo": "Panna" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zodiac/translations/sensor.ru.json b/homeassistant/components/zodiac/translations/sensor.ru.json new file mode 100644 index 00000000000000..3a314918428f4b --- /dev/null +++ b/homeassistant/components/zodiac/translations/sensor.ru.json @@ -0,0 +1,18 @@ +{ + "state": { + "zodiac__sign": { + "aquarius": "\u0412\u043e\u0434\u043e\u043b\u0435\u0439", + "aries": "\u041e\u0432\u0435\u043d", + "cancer": "\u0420\u0430\u043a", + "capricorn": "\u041a\u043e\u0437\u0435\u0440\u043e\u0433", + "gemini": "\u0411\u043b\u0438\u0437\u043d\u0435\u0446\u044b", + "leo": "\u041b\u0435\u0432", + "libra": "\u0412\u0435\u0441\u044b", + "pisces": "\u0420\u044b\u0431\u044b", + "sagittarius": "\u0421\u0442\u0440\u0435\u043b\u0435\u0446", + "scorpio": "\u0421\u043a\u043e\u0440\u043f\u0438\u043e\u043d", + "taurus": "\u0422\u0435\u043b\u0435\u0446", + "virgo": "\u0414\u0435\u0432\u0430" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zodiac/translations/sensor.zh-Hant.json b/homeassistant/components/zodiac/translations/sensor.zh-Hant.json new file mode 100644 index 00000000000000..938a5b6cbe5d48 --- /dev/null +++ b/homeassistant/components/zodiac/translations/sensor.zh-Hant.json @@ -0,0 +1,18 @@ +{ + "state": { + "zodiac__sign": { + "aquarius": "\u6c34\u74f6\u5ea7", + "aries": "\u7261\u7f8a\u5ea7", + "cancer": "\u5de8\u87f9\u5ea7", + "capricorn": "\u6469\u7faf\u5ea7", + "gemini": "\u96d9\u5b50\u5ea7", + "leo": "\u7345\u5b50\u5ea7", + "libra": "\u5929\u79e4\u5ea7", + "pisces": "\u96d9\u9b5a\u5ea7", + "sagittarius": "\u5c04\u624b\u5ea7", + "scorpio": "\u5929\u880d\u5ea7", + "taurus": "\u91d1\u725b\u5ea7", + "virgo": "\u8655\u5973\u5ea7" + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zone/translations/et.json b/homeassistant/components/zone/translations/et.json index aa921f376e70f4..5452dc408265fc 100644 --- a/homeassistant/components/zone/translations/et.json +++ b/homeassistant/components/zone/translations/et.json @@ -1,16 +1,21 @@ { "config": { + "error": { + "name_exists": "Nimi on juba kasutusel" + }, "step": { "init": { "data": { "icon": "Ikoon", - "latitude": "Laius", - "longitude": "Pikkus", + "latitude": "Laiuskraad", + "longitude": "Pikkuskraad", "name": "Nimi", + "passive": "Passiivne", "radius": "Raadius" }, - "title": "M\u00e4\u00e4ra tsooni parameetrid" + "title": "M\u00e4\u00e4ra ala parameetrid" } - } + }, + "title": "Ala" } } \ No newline at end of file diff --git a/homeassistant/components/zoneminder/__init__.py b/homeassistant/components/zoneminder/__init__.py index c631406b0e34f5..92186b7a0b5c78 100644 --- a/homeassistant/components/zoneminder/__init__.py +++ b/homeassistant/components/zoneminder/__init__.py @@ -2,97 +2,169 @@ import logging import voluptuous as vol -from zoneminder.zm import ZoneMinder +from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN +from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN +import homeassistant.config_entries as config_entries +from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( ATTR_ID, ATTR_NAME, CONF_HOST, CONF_PASSWORD, CONF_PATH, + CONF_PLATFORM, + CONF_SOURCE, CONF_SSL, CONF_USERNAME, CONF_VERIFY_SSL, ) -import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.discovery import async_load_platform +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import ConfigEntryNotReady +from homeassistant.helpers import config_validation as cv + +from . import const +from .common import ( + ClientAvailabilityResult, + async_test_client_availability, + create_client_from_config, + del_client_from_data, + get_client_from_data, + is_client_in_data, + set_client_to_data, + set_platform_configs, +) _LOGGER = logging.getLogger(__name__) - -CONF_PATH_ZMS = "path_zms" - -DEFAULT_PATH = "/zm/" -DEFAULT_PATH_ZMS = "/zm/cgi-bin/nph-zms" -DEFAULT_SSL = False -DEFAULT_TIMEOUT = 10 -DEFAULT_VERIFY_SSL = True -DOMAIN = "zoneminder" +PLATFORM_DOMAINS = tuple( + [BINARY_SENSOR_DOMAIN, CAMERA_DOMAIN, SENSOR_DOMAIN, SWITCH_DOMAIN] +) HOST_CONFIG_SCHEMA = vol.Schema( { vol.Required(CONF_HOST): cv.string, vol.Optional(CONF_PASSWORD): cv.string, - vol.Optional(CONF_PATH, default=DEFAULT_PATH): cv.string, - vol.Optional(CONF_PATH_ZMS, default=DEFAULT_PATH_ZMS): cv.string, - vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, + vol.Optional(CONF_PATH, default=const.DEFAULT_PATH): cv.string, + vol.Optional(const.CONF_PATH_ZMS, default=const.DEFAULT_PATH_ZMS): cv.string, + vol.Optional(CONF_SSL, default=const.DEFAULT_SSL): cv.boolean, vol.Optional(CONF_USERNAME): cv.string, - vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, + vol.Optional(CONF_VERIFY_SSL, default=const.DEFAULT_VERIFY_SSL): cv.boolean, } ) -CONFIG_SCHEMA = vol.Schema( - {DOMAIN: vol.All(cv.ensure_list, [HOST_CONFIG_SCHEMA])}, extra=vol.ALLOW_EXTRA +CONFIG_SCHEMA = vol.All( + cv.deprecated(const.DOMAIN, invalidation_version="0.118"), + vol.Schema( + {const.DOMAIN: vol.All(cv.ensure_list, [HOST_CONFIG_SCHEMA])}, + extra=vol.ALLOW_EXTRA, + ), ) -SERVICE_SET_RUN_STATE = "set_run_state" SET_RUN_STATE_SCHEMA = vol.Schema( {vol.Required(ATTR_ID): cv.string, vol.Required(ATTR_NAME): cv.string} ) -def setup(hass, config): +async def async_setup(hass: HomeAssistant, base_config: dict): """Set up the ZoneMinder component.""" - hass.data[DOMAIN] = {} + # Collect the platform specific configs. It's necessary to collect these configs + # here instead of the platform's setup_platform function because the invocation order + # of setup_platform and async_setup_entry is not consistent. + set_platform_configs( + hass, + SENSOR_DOMAIN, + [ + platform_config + for platform_config in base_config.get(SENSOR_DOMAIN, []) + if platform_config[CONF_PLATFORM] == const.DOMAIN + ], + ) + set_platform_configs( + hass, + SWITCH_DOMAIN, + [ + platform_config + for platform_config in base_config.get(SWITCH_DOMAIN, []) + if platform_config[CONF_PLATFORM] == const.DOMAIN + ], + ) + + config = base_config.get(const.DOMAIN) + + if not config: + return True + + for config_item in config: + hass.async_create_task( + hass.config_entries.flow.async_init( + const.DOMAIN, + context={CONF_SOURCE: config_entries.SOURCE_IMPORT}, + data=config_item, + ) + ) + + return True + + +async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: + """Set up Zoneminder config entry.""" + zm_client = create_client_from_config(config_entry.data) - success = True + result = await async_test_client_availability(hass, zm_client) + if result != ClientAvailabilityResult.AVAILABLE: + raise ConfigEntryNotReady - for conf in config[DOMAIN]: - protocol = "https" if conf[CONF_SSL] else "http" + set_client_to_data(hass, config_entry.unique_id, zm_client) - host_name = conf[CONF_HOST] - server_origin = f"{protocol}://{host_name}" - zm_client = ZoneMinder( - server_origin, - conf.get(CONF_USERNAME), - conf.get(CONF_PASSWORD), - conf.get(CONF_PATH), - conf.get(CONF_PATH_ZMS), - conf.get(CONF_VERIFY_SSL), + for platform_domain in PLATFORM_DOMAINS: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, platform_domain) ) - hass.data[DOMAIN][host_name] = zm_client - - success = zm_client.login() and success - - def set_active_state(call): - """Set the ZoneMinder run state to the given state name.""" - zm_id = call.data[ATTR_ID] - state_name = call.data[ATTR_NAME] - if zm_id not in hass.data[DOMAIN]: - _LOGGER.error("Invalid ZoneMinder host provided: %s", zm_id) - if not hass.data[DOMAIN][zm_id].set_active_state(state_name): - _LOGGER.error( - "Unable to change ZoneMinder state. Host: %s, state: %s", - zm_id, - state_name, + + if not hass.services.has_service(const.DOMAIN, const.SERVICE_SET_RUN_STATE): + + @callback + def set_active_state(call): + """Set the ZoneMinder run state to the given state name.""" + zm_id = call.data[ATTR_ID] + state_name = call.data[ATTR_NAME] + if not is_client_in_data(hass, zm_id): + _LOGGER.error("Invalid ZoneMinder host provided: %s", zm_id) + return + + if not get_client_from_data(hass, zm_id).set_active_state(state_name): + _LOGGER.error( + "Unable to change ZoneMinder state. Host: %s, state: %s", + zm_id, + state_name, + ) + + hass.services.async_register( + const.DOMAIN, + const.SERVICE_SET_RUN_STATE, + set_active_state, + schema=SET_RUN_STATE_SCHEMA, + ) + + return True + + +async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: + """Unload Zoneminder config entry.""" + for platform_domain in PLATFORM_DOMAINS: + hass.async_create_task( + hass.config_entries.async_forward_entry_unload( + config_entry, platform_domain ) + ) - hass.services.register( - DOMAIN, SERVICE_SET_RUN_STATE, set_active_state, schema=SET_RUN_STATE_SCHEMA - ) + # If this is the last config to exist, remove the service too. + if len(hass.config_entries.async_entries(const.DOMAIN)) <= 1: + hass.services.async_remove(const.DOMAIN, const.SERVICE_SET_RUN_STATE) - hass.async_create_task( - async_load_platform(hass, "binary_sensor", DOMAIN, {}, config) - ) + del_client_from_data(hass, config_entry.unique_id) - return success + return True diff --git a/homeassistant/components/zoneminder/binary_sensor.py b/homeassistant/components/zoneminder/binary_sensor.py index 739864fdea8054..73f7ce2f4c9b07 100644 --- a/homeassistant/components/zoneminder/binary_sensor.py +++ b/homeassistant/components/zoneminder/binary_sensor.py @@ -1,26 +1,43 @@ """Support for ZoneMinder binary sensors.""" -from homeassistant.components.binary_sensor import BinarySensorEntity +from typing import Callable, List, Optional -from . import DOMAIN as ZONEMINDER_DOMAIN +from zoneminder.zm import ZoneMinder +from homeassistant.components.binary_sensor import ( + DEVICE_CLASS_CONNECTIVITY, + BinarySensorEntity, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity -async def async_setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the ZoneMinder binary sensor platform.""" - sensors = [] - for host_name, zm_client in hass.data[ZONEMINDER_DOMAIN].items(): - sensors.append(ZMAvailabilitySensor(host_name, zm_client)) - add_entities(sensors) - return True +from .common import get_client_from_data + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: Callable[[List[Entity], Optional[bool]], None], +) -> None: + """Set up the sensor config entry.""" + zm_client = get_client_from_data(hass, config_entry.unique_id) + async_add_entities([ZMAvailabilitySensor(zm_client, config_entry)]) class ZMAvailabilitySensor(BinarySensorEntity): """Representation of the availability of ZoneMinder as a binary sensor.""" - def __init__(self, host_name, client): + def __init__(self, client: ZoneMinder, config_entry: ConfigEntry): """Initialize availability sensor.""" self._state = None - self._name = host_name + self._name = config_entry.unique_id self._client = client + self._config_entry = config_entry + + @property + def unique_id(self) -> Optional[str]: + """Return a unique ID.""" + return f"{self._config_entry.unique_id}_availability" @property def name(self): @@ -35,7 +52,7 @@ def is_on(self): @property def device_class(self): """Return the class of this device, from component DEVICE_CLASSES.""" - return "connectivity" + return DEVICE_CLASS_CONNECTIVITY def update(self): """Update the state of this sensor (availability of ZoneMinder).""" diff --git a/homeassistant/components/zoneminder/camera.py b/homeassistant/components/zoneminder/camera.py index 6144fe112266ba..c4ef1b14772a25 100644 --- a/homeassistant/components/zoneminder/camera.py +++ b/homeassistant/components/zoneminder/camera.py @@ -1,5 +1,8 @@ """Support for ZoneMinder camera streaming.""" import logging +from typing import Callable, List, Optional + +from zoneminder.monitor import Monitor from homeassistant.components.mjpeg.camera import ( CONF_MJPEG_URL, @@ -7,9 +10,12 @@ MjpegCamera, filter_urllib3_logging, ) +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_NAME, CONF_VERIFY_SSL +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity -from . import DOMAIN as ZONEMINDER_DOMAIN +from .common import get_client_from_data _LOGGER = logging.getLogger(__name__) @@ -17,23 +23,28 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the ZoneMinder cameras.""" filter_urllib3_logging() - cameras = [] - for zm_client in hass.data[ZONEMINDER_DOMAIN].values(): - monitors = zm_client.get_monitors() - if not monitors: - _LOGGER.warning("Could not fetch monitors from ZoneMinder host: %s") - return - for monitor in monitors: - _LOGGER.info("Initializing camera %s", monitor.id) - cameras.append(ZoneMinderCamera(monitor, zm_client.verify_ssl)) - add_entities(cameras) + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: Callable[[List[Entity], Optional[bool]], None], +) -> None: + """Set up the sensor config entry.""" + zm_client = get_client_from_data(hass, config_entry.unique_id) + + async_add_entities( + [ + ZoneMinderCamera(monitor, zm_client.verify_ssl, config_entry) + for monitor in await hass.async_add_job(zm_client.get_monitors) + ] + ) class ZoneMinderCamera(MjpegCamera): """Representation of a ZoneMinder Monitor Stream.""" - def __init__(self, monitor, verify_ssl): + def __init__(self, monitor: Monitor, verify_ssl: bool, config_entry: ConfigEntry): """Initialize as a subclass of MjpegCamera.""" device_info = { CONF_NAME: monitor.name, @@ -45,6 +56,12 @@ def __init__(self, monitor, verify_ssl): self._is_recording = None self._is_available = None self._monitor = monitor + self._config_entry = config_entry + + @property + def unique_id(self) -> Optional[str]: + """Return a unique ID.""" + return f"{self._config_entry.unique_id}_{self._monitor.id}_camera" @property def should_poll(self): diff --git a/homeassistant/components/zoneminder/common.py b/homeassistant/components/zoneminder/common.py new file mode 100644 index 00000000000000..9b5498b0cdad90 --- /dev/null +++ b/homeassistant/components/zoneminder/common.py @@ -0,0 +1,110 @@ +"""Common code for the ZoneMinder component.""" +from enum import Enum +from typing import List + +import requests +from zoneminder.zm import ZoneMinder + +from homeassistant.const import ( + CONF_HOST, + CONF_PASSWORD, + CONF_PATH, + CONF_SSL, + CONF_USERNAME, + CONF_VERIFY_SSL, +) +from homeassistant.core import HomeAssistant + +from . import const + + +def prime_domain_data(hass: HomeAssistant) -> None: + """Prime the data structures.""" + hass.data.setdefault(const.DOMAIN, {}) + + +def prime_platform_configs(hass: HomeAssistant, domain: str) -> None: + """Prime the data structures.""" + prime_domain_data(hass) + hass.data[const.DOMAIN].setdefault(const.PLATFORM_CONFIGS, {}) + hass.data[const.DOMAIN][const.PLATFORM_CONFIGS].setdefault(domain, []) + + +def set_platform_configs(hass: HomeAssistant, domain: str, configs: List[dict]) -> None: + """Set platform configs.""" + prime_platform_configs(hass, domain) + hass.data[const.DOMAIN][const.PLATFORM_CONFIGS][domain] = configs + + +def get_platform_configs(hass: HomeAssistant, domain: str) -> List[dict]: + """Get platform configs.""" + prime_platform_configs(hass, domain) + return hass.data[const.DOMAIN][const.PLATFORM_CONFIGS][domain] + + +def prime_config_data(hass: HomeAssistant, unique_id: str) -> None: + """Prime the data structures.""" + prime_domain_data(hass) + hass.data[const.DOMAIN].setdefault(const.CONFIG_DATA, {}) + hass.data[const.DOMAIN][const.CONFIG_DATA].setdefault(unique_id, {}) + + +def set_client_to_data(hass: HomeAssistant, unique_id: str, client: ZoneMinder) -> None: + """Put a ZoneMinder client in the Home Assistant data.""" + prime_config_data(hass, unique_id) + hass.data[const.DOMAIN][const.CONFIG_DATA][unique_id][const.API_CLIENT] = client + + +def is_client_in_data(hass: HomeAssistant, unique_id: str) -> bool: + """Check if ZoneMinder client is in the Home Assistant data.""" + prime_config_data(hass, unique_id) + return const.API_CLIENT in hass.data[const.DOMAIN][const.CONFIG_DATA][unique_id] + + +def get_client_from_data(hass: HomeAssistant, unique_id: str) -> ZoneMinder: + """Get a ZoneMinder client from the Home Assistant data.""" + prime_config_data(hass, unique_id) + return hass.data[const.DOMAIN][const.CONFIG_DATA][unique_id][const.API_CLIENT] + + +def del_client_from_data(hass: HomeAssistant, unique_id: str) -> None: + """Delete a ZoneMinder client from the Home Assistant data.""" + prime_config_data(hass, unique_id) + del hass.data[const.DOMAIN][const.CONFIG_DATA][unique_id][const.API_CLIENT] + + +def create_client_from_config(conf: dict) -> ZoneMinder: + """Create a new ZoneMinder client from a config.""" + protocol = "https" if conf[CONF_SSL] else "http" + + host_name = conf[CONF_HOST] + server_origin = f"{protocol}://{host_name}" + + return ZoneMinder( + server_origin, + conf.get(CONF_USERNAME), + conf.get(CONF_PASSWORD), + conf.get(CONF_PATH), + conf.get(const.CONF_PATH_ZMS), + conf.get(CONF_VERIFY_SSL), + ) + + +class ClientAvailabilityResult(Enum): + """Client availability test result.""" + + AVAILABLE = "available" + ERROR_AUTH_FAIL = "invalid_auth" + ERROR_CONNECTION_ERROR = "cannot_connect" + + +async def async_test_client_availability( + hass: HomeAssistant, client: ZoneMinder +) -> ClientAvailabilityResult: + """Test the availability of a ZoneMinder client.""" + try: + if await hass.async_add_job(client.login): + return ClientAvailabilityResult.AVAILABLE + return ClientAvailabilityResult.ERROR_AUTH_FAIL + except requests.exceptions.ConnectionError: + return ClientAvailabilityResult.ERROR_CONNECTION_ERROR diff --git a/homeassistant/components/zoneminder/config_flow.py b/homeassistant/components/zoneminder/config_flow.py new file mode 100644 index 00000000000000..8b0b94107f3f7f --- /dev/null +++ b/homeassistant/components/zoneminder/config_flow.py @@ -0,0 +1,99 @@ +"""ZoneMinder config flow.""" +from urllib.parse import urlparse + +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import ( + CONF_HOST, + CONF_PASSWORD, + CONF_PATH, + CONF_SOURCE, + CONF_SSL, + CONF_USERNAME, + CONF_VERIFY_SSL, +) + +from .common import ( + ClientAvailabilityResult, + async_test_client_availability, + create_client_from_config, +) +from .const import ( + CONF_PATH_ZMS, + DEFAULT_PATH, + DEFAULT_PATH_ZMS, + DEFAULT_SSL, + DEFAULT_VERIFY_SSL, +) +from .const import DOMAIN # pylint: disable=unused-import + + +class ZoneminderFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): + """Flow handler for zoneminder integration.""" + + VERSION = 1 + CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL + + async def async_step_import(self, config: dict): + """Handle a flow initialized by import.""" + return await self.async_step_finish( + {**config, **{CONF_SOURCE: config_entries.SOURCE_IMPORT}} + ) + + async def async_step_user(self, user_input: dict = None): + """Handle user step.""" + user_input = user_input or {} + errors = {} + + if user_input: + zm_client = create_client_from_config(user_input) + result = await async_test_client_availability(self.hass, zm_client) + if result == ClientAvailabilityResult.AVAILABLE: + return await self.async_step_finish(user_input) + + errors["base"] = result.value + + return self.async_show_form( + step_id=config_entries.SOURCE_USER, + data_schema=vol.Schema( + { + vol.Required(CONF_HOST, default=user_input.get(CONF_HOST)): str, + vol.Optional( + CONF_USERNAME, default=user_input.get(CONF_USERNAME, "") + ): str, + vol.Optional( + CONF_PASSWORD, default=user_input.get(CONF_PASSWORD, "") + ): str, + vol.Optional( + CONF_PATH, default=user_input.get(CONF_PATH, DEFAULT_PATH) + ): str, + vol.Optional( + CONF_PATH_ZMS, + default=user_input.get(CONF_PATH_ZMS, DEFAULT_PATH_ZMS), + ): str, + vol.Optional( + CONF_SSL, default=user_input.get(CONF_SSL, DEFAULT_SSL) + ): bool, + vol.Optional( + CONF_VERIFY_SSL, + default=user_input.get(CONF_VERIFY_SSL, DEFAULT_VERIFY_SSL), + ): bool, + } + ), + errors=errors, + ) + + async def async_step_finish(self, config: dict): + """Finish config flow.""" + zm_client = create_client_from_config(config) + hostname = urlparse(zm_client.get_zms_url()).hostname + result = await async_test_client_availability(self.hass, zm_client) + + if result != ClientAvailabilityResult.AVAILABLE: + return self.async_abort(reason=str(result.value)) + + await self.async_set_unique_id(hostname) + self._abort_if_unique_id_configured(config) + + return self.async_create_entry(title=hostname, data=config) diff --git a/homeassistant/components/zoneminder/const.py b/homeassistant/components/zoneminder/const.py new file mode 100644 index 00000000000000..ad890a1d4d6c29 --- /dev/null +++ b/homeassistant/components/zoneminder/const.py @@ -0,0 +1,14 @@ +"""Constants for zoneminder component.""" + +CONF_PATH_ZMS = "path_zms" + +DEFAULT_PATH = "/zm/" +DEFAULT_PATH_ZMS = "/zm/cgi-bin/nph-zms" +DEFAULT_SSL = False +DEFAULT_VERIFY_SSL = True +DOMAIN = "zoneminder" +SERVICE_SET_RUN_STATE = "set_run_state" + +PLATFORM_CONFIGS = "platform_configs" +CONFIG_DATA = "config_data" +API_CLIENT = "api_client" diff --git a/homeassistant/components/zoneminder/manifest.json b/homeassistant/components/zoneminder/manifest.json index b3a87510e5ace0..13bec8c4d9a0f6 100644 --- a/homeassistant/components/zoneminder/manifest.json +++ b/homeassistant/components/zoneminder/manifest.json @@ -1,7 +1,8 @@ { "domain": "zoneminder", "name": "ZoneMinder", + "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/zoneminder", "requirements": ["zm-py==0.4.0"], - "codeowners": ["@rohankapoorcom"] + "codeowners": ["@rohankapoorcom", "@vangorra"] } diff --git a/homeassistant/components/zoneminder/sensor.py b/homeassistant/components/zoneminder/sensor.py index 75531e79e13b81..8605e842813775 100644 --- a/homeassistant/components/zoneminder/sensor.py +++ b/homeassistant/components/zoneminder/sensor.py @@ -1,15 +1,19 @@ """Support for ZoneMinder sensors.""" import logging +from typing import Callable, List, Optional import voluptuous as vol -from zoneminder.monitor import TimePeriod +from zoneminder.monitor import Monitor, TimePeriod +from zoneminder.zm import ZoneMinder -from homeassistant.components.sensor import PLATFORM_SCHEMA +from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN, PLATFORM_SCHEMA +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_MONITORED_CONDITIONS +from homeassistant.core import HomeAssistant import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import Entity -from . import DOMAIN as ZONEMINDER_DOMAIN +from .common import get_client_from_data, get_platform_configs _LOGGER = logging.getLogger(__name__) @@ -37,35 +41,50 @@ ) -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the ZoneMinder sensor platform.""" - include_archived = config.get(CONF_INCLUDE_ARCHIVED) +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: Callable[[List[Entity], Optional[bool]], None], +) -> None: + """Set up the sensor config entry.""" + zm_client = get_client_from_data(hass, config_entry.unique_id) + monitors = await hass.async_add_job(zm_client.get_monitors) + + if not monitors: + _LOGGER.warning("Did not fetch any monitors from ZoneMinder") sensors = [] - for zm_client in hass.data[ZONEMINDER_DOMAIN].values(): - monitors = zm_client.get_monitors() - if not monitors: - _LOGGER.warning("Could not fetch any monitors from ZoneMinder") + for monitor in monitors: + sensors.append(ZMSensorMonitors(monitor, config_entry)) - for monitor in monitors: - sensors.append(ZMSensorMonitors(monitor)) + for config in get_platform_configs(hass, SENSOR_DOMAIN): + include_archived = config.get(CONF_INCLUDE_ARCHIVED) for sensor in config[CONF_MONITORED_CONDITIONS]: - sensors.append(ZMSensorEvents(monitor, include_archived, sensor)) + sensors.append( + ZMSensorEvents(monitor, include_archived, sensor, config_entry) + ) + + sensors.append(ZMSensorRunState(zm_client, config_entry)) - sensors.append(ZMSensorRunState(zm_client)) - add_entities(sensors) + async_add_entities(sensors, True) class ZMSensorMonitors(Entity): """Get the status of each ZoneMinder monitor.""" - def __init__(self, monitor): + def __init__(self, monitor: Monitor, config_entry: ConfigEntry): """Initialize monitor sensor.""" self._monitor = monitor + self._config_entry = config_entry self._state = None self._is_available = None + @property + def unique_id(self) -> Optional[str]: + """Return a unique ID.""" + return f"{self._config_entry.unique_id}_{self._monitor.id}_status" + @property def name(self): """Return the name of the sensor.""" @@ -94,14 +113,26 @@ def update(self): class ZMSensorEvents(Entity): """Get the number of events for each monitor.""" - def __init__(self, monitor, include_archived, sensor_type): + def __init__( + self, + monitor: Monitor, + include_archived: bool, + sensor_type: str, + config_entry: ConfigEntry, + ): """Initialize event sensor.""" self._monitor = monitor self._include_archived = include_archived self.time_period = TimePeriod.get_time_period(sensor_type) + self._config_entry = config_entry self._state = None + @property + def unique_id(self) -> Optional[str]: + """Return a unique ID.""" + return f"{self._config_entry.unique_id}_{self._monitor.id}_{self.time_period.value}_{self._include_archived}_events" + @property def name(self): """Return the name of the sensor.""" @@ -125,11 +156,17 @@ def update(self): class ZMSensorRunState(Entity): """Get the ZoneMinder run state.""" - def __init__(self, client): + def __init__(self, client: ZoneMinder, config_entry: ConfigEntry): """Initialize run state sensor.""" self._state = None self._is_available = None self._client = client + self._config_entry = config_entry + + @property + def unique_id(self) -> Optional[str]: + """Return a unique ID.""" + return f"{self._config_entry.unique_id}_runstate" @property def name(self): diff --git a/homeassistant/components/zoneminder/services.yaml b/homeassistant/components/zoneminder/services.yaml index a6fb85b641de10..52e8a3bf0bbcb4 100644 --- a/homeassistant/components/zoneminder/services.yaml +++ b/homeassistant/components/zoneminder/services.yaml @@ -1,6 +1,9 @@ set_run_state: - description: Set the ZoneMinder run state + description: "Set the ZoneMinder run state" fields: + id: + description: "The host name or IP address of the ZoneMinder instance." + example: "10.10.0.2" name: - description: The string name of the ZoneMinder run state to set as active. + description: "The string name of the ZoneMinder run state to set as active." example: "Home" diff --git a/homeassistant/components/zoneminder/strings.json b/homeassistant/components/zoneminder/strings.json new file mode 100644 index 00000000000000..2973e193b86447 --- /dev/null +++ b/homeassistant/components/zoneminder/strings.json @@ -0,0 +1,28 @@ +{ + "config": { + "flow_title": "ZoneMinder", + "step": { + "user": { + "title": "Add ZoneMinder Server.", + "data": { + "host": "Host and Port (ex 10.10.0.4:8010)", + "username": "[%key:common::config_flow::data::username%]", + "password": "[%key:common::config_flow::data::password%]", + "path": "ZM Path", + "path_zms": "ZMS Path", + "ssl": "[%key:common::config_flow::data::ssl%]", + "verify_ssl": "[%key:common::config_flow::data::verify_ssl%]" + } + } + }, + "abort": { + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" + }, + "error": { + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" + }, + "create_entry": { "default": "ZoneMinder server added." } + } +} diff --git a/homeassistant/components/zoneminder/switch.py b/homeassistant/components/zoneminder/switch.py index 0428ddbf888f89..d8d1cc78797c15 100644 --- a/homeassistant/components/zoneminder/switch.py +++ b/homeassistant/components/zoneminder/switch.py @@ -1,41 +1,61 @@ """Support for ZoneMinder switches.""" import logging +from typing import Callable, List, Optional import voluptuous as vol -from zoneminder.monitor import MonitorState +from zoneminder.monitor import Monitor, MonitorState -from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchEntity +from homeassistant.components.switch import ( + DOMAIN as SWITCH_DOMAIN, + PLATFORM_SCHEMA, + SwitchEntity, +) +from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_COMMAND_OFF, CONF_COMMAND_ON -import homeassistant.helpers.config_validation as cv +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import Entity -from . import DOMAIN as ZONEMINDER_DOMAIN +from .common import get_client_from_data, get_platform_configs _LOGGER = logging.getLogger(__name__) +MONITOR_STATES = { + MonitorState[name].value: MonitorState[name] + for name in dir(MonitorState) + if not name.startswith("_") +} PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { - vol.Required(CONF_COMMAND_ON): cv.string, - vol.Required(CONF_COMMAND_OFF): cv.string, + vol.Required(CONF_COMMAND_ON): vol.All(vol.In(MONITOR_STATES.keys())), + vol.Required(CONF_COMMAND_OFF): vol.All(vol.In(MONITOR_STATES.keys())), } ) -def setup_platform(hass, config, add_entities, discovery_info=None): - """Set up the ZoneMinder switch platform.""" +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: Callable[[List[Entity], Optional[bool]], None], +) -> None: + """Set up the sensor config entry.""" + zm_client = get_client_from_data(hass, config_entry.unique_id) + monitors = await hass.async_add_job(zm_client.get_monitors) - on_state = MonitorState(config.get(CONF_COMMAND_ON)) - off_state = MonitorState(config.get(CONF_COMMAND_OFF)) + if not monitors: + _LOGGER.warning("Could not fetch monitors from ZoneMinder") + return switches = [] - for zm_client in hass.data[ZONEMINDER_DOMAIN].values(): - monitors = zm_client.get_monitors() - if not monitors: - _LOGGER.warning("Could not fetch monitors from ZoneMinder") - return + for monitor in monitors: + for config in get_platform_configs(hass, SWITCH_DOMAIN): + on_state = MONITOR_STATES[config[CONF_COMMAND_ON]] + off_state = MONITOR_STATES[config[CONF_COMMAND_OFF]] + + switches.append( + ZMSwitchMonitors(monitor, on_state, off_state, config_entry) + ) - for monitor in monitors: - switches.append(ZMSwitchMonitors(monitor, on_state, off_state)) - add_entities(switches) + async_add_entities(switches, True) class ZMSwitchMonitors(SwitchEntity): @@ -43,13 +63,25 @@ class ZMSwitchMonitors(SwitchEntity): icon = "mdi:record-rec" - def __init__(self, monitor, on_state, off_state): + def __init__( + self, + monitor: Monitor, + on_state: MonitorState, + off_state: MonitorState, + config_entry: ConfigEntry, + ): """Initialize the switch.""" self._monitor = monitor self._on_state = on_state self._off_state = off_state + self._config_entry = config_entry self._state = None + @property + def unique_id(self) -> Optional[str]: + """Return a unique ID.""" + return f"{self._config_entry.unique_id}_{self._monitor.id}_switch_{self._on_state.value}_{self._off_state.value}" + @property def name(self): """Return the name of the switch.""" diff --git a/homeassistant/components/zoneminder/translations/ca.json b/homeassistant/components/zoneminder/translations/ca.json new file mode 100644 index 00000000000000..5be4bbfc8b47c2 --- /dev/null +++ b/homeassistant/components/zoneminder/translations/ca.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "auth_fail": "Nom d'usuari i/o contrasenya incorrectes.", + "cannot_connect": "Ha fallat la connexi\u00f3", + "connection_error": "No s'ha pogut connectar al servidor ZoneMinder.", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida" + }, + "create_entry": { + "default": "S'ha afegit el servidor ZoneMinder." + }, + "error": { + "auth_fail": "Nom d'usuari i/o contrasenya incorrectes.", + "cannot_connect": "Ha fallat la connexi\u00f3", + "connection_error": "No s'ha pogut connectar al servidor ZoneMinder.", + "invalid_auth": "Autenticaci\u00f3 inv\u00e0lida" + }, + "flow_title": "ZoneMinder", + "step": { + "user": { + "data": { + "host": "Amfitri\u00f3 i port (ex: 10.10.0.4:8010)", + "password": "Contrasenya", + "path": "Ruta de ZM", + "path_zms": "Ruta de ZMS", + "ssl": "Utilitza un certificat SSL", + "username": "Nom d'usuari", + "verify_ssl": "Verifica el certificat SSL" + }, + "title": "Afegeix un servidor ZoneMinder." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zoneminder/translations/de.json b/homeassistant/components/zoneminder/translations/de.json new file mode 100644 index 00000000000000..1362dcbd62dba3 --- /dev/null +++ b/homeassistant/components/zoneminder/translations/de.json @@ -0,0 +1,14 @@ +{ + "config": { + "flow_title": "ZoneMinder", + "step": { + "user": { + "data": { + "password": "Passwort", + "username": "Benutzername", + "verify_ssl": "SSL-Zertifikat \u00fcberpr\u00fcfen" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zoneminder/translations/el.json b/homeassistant/components/zoneminder/translations/el.json new file mode 100644 index 00000000000000..81511935386070 --- /dev/null +++ b/homeassistant/components/zoneminder/translations/el.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "auth_fail": "\u03a4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03ae \u03bf \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03c3\u03c6\u03b1\u03bb\u03bc\u03ad\u03bd\u03b1.", + "connection_error": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03b5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae ZoneMinder." + }, + "create_entry": { + "default": "\u03a0\u03c1\u03bf\u03c3\u03c4\u03ad\u03b8\u03b7\u03ba\u03b5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae\u03c2 ZoneMinder." + }, + "error": { + "auth_fail": "\u03a4\u03bf \u03cc\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7 \u03ae \u03bf \u03ba\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2 \u03b5\u03af\u03bd\u03b1\u03b9 \u03b5\u03c3\u03c6\u03b1\u03bb\u03bc\u03ad\u03bd\u03b1.", + "connection_error": "\u0391\u03c0\u03bf\u03c4\u03c5\u03c7\u03af\u03b1 \u03c3\u03cd\u03bd\u03b4\u03b5\u03c3\u03b7\u03c2 \u03c3\u03b5 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae ZoneMinder." + }, + "flow_title": "ZoneMinder", + "step": { + "user": { + "data": { + "host": "\u039a\u03b5\u03bd\u03c4\u03c1\u03b9\u03ba\u03cc\u03c2 \u03c5\u03c0\u03bf\u03bb\u03bf\u03b3\u03b9\u03c3\u03c4\u03ae\u03c2 \u03ba\u03b1\u03b9 \u03b8\u03cd\u03c1\u03b1 (\u03c0\u03c1\u03ce\u03b7\u03bd 10.10.0.4:8010)", + "password": "\u039a\u03c9\u03b4\u03b9\u03ba\u03cc\u03c2 \u03c0\u03c1\u03cc\u03c3\u03b2\u03b1\u03c3\u03b7\u03c2", + "path": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae ZMS", + "path_zms": "\u0394\u03b9\u03b1\u03b4\u03c1\u03bf\u03bc\u03ae ZMS", + "ssl": "\u03a7\u03c1\u03b7\u03c3\u03b9\u03bc\u03bf\u03c0\u03bf\u03b9\u03ae\u03c3\u03c4\u03b5 SSL \u03b3\u03b9\u03b1 \u03c3\u03c5\u03bd\u03b4\u03ad\u03c3\u03b5\u03b9\u03c2 \u03c3\u03c4\u03bf ZoneMinder", + "username": "\u038c\u03bd\u03bf\u03bc\u03b1 \u03c7\u03c1\u03ae\u03c3\u03c4\u03b7", + "verify_ssl": "\u0395\u03c0\u03b1\u03bb\u03ae\u03b8\u03b5\u03c5\u03c3\u03b7 \u03c0\u03b9\u03c3\u03c4\u03bf\u03c0\u03bf\u03b9\u03b7\u03c4\u03b9\u03ba\u03bf\u03cd SSL" + }, + "title": "\u03a0\u03c1\u03bf\u03c3\u03b8\u03ae\u03ba\u03b7 \u03b4\u03b9\u03b1\u03ba\u03bf\u03bc\u03b9\u03c3\u03c4\u03ae ZoneMinder." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zoneminder/translations/en.json b/homeassistant/components/zoneminder/translations/en.json new file mode 100644 index 00000000000000..eab5f5f1e72449 --- /dev/null +++ b/homeassistant/components/zoneminder/translations/en.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "auth_fail": "Username or password is incorrect.", + "cannot_connect": "Failed to connect", + "connection_error": "Failed to connect to a ZoneMinder server.", + "invalid_auth": "Invalid authentication" + }, + "create_entry": { + "default": "ZoneMinder server added." + }, + "error": { + "auth_fail": "Username or password is incorrect.", + "cannot_connect": "Failed to connect", + "connection_error": "Failed to connect to a ZoneMinder server.", + "invalid_auth": "Invalid authentication" + }, + "flow_title": "ZoneMinder", + "step": { + "user": { + "data": { + "host": "Host and Port (ex 10.10.0.4:8010)", + "password": "Password", + "path": "ZM Path", + "path_zms": "ZMS Path", + "ssl": "Uses an SSL certificate", + "username": "Username", + "verify_ssl": "Verify SSL certificate" + }, + "title": "Add ZoneMinder Server." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zoneminder/translations/es.json b/homeassistant/components/zoneminder/translations/es.json new file mode 100644 index 00000000000000..7bd3264b2f38a7 --- /dev/null +++ b/homeassistant/components/zoneminder/translations/es.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "auth_fail": "Nombre de usuario o contrase\u00f1a incorrectos.", + "cannot_connect": "No se pudo conectar", + "connection_error": "No se pudo conectar con un servidor ZoneMinder.", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida" + }, + "create_entry": { + "default": "Servidor ZoneMinder a\u00f1adido." + }, + "error": { + "auth_fail": "Nombre de usuario o contrase\u00f1a incorrectos.", + "cannot_connect": "No se pudo conectar", + "connection_error": "No se pudo conectar con un servidor ZoneMinder.", + "invalid_auth": "Autenticaci\u00f3n no v\u00e1lida" + }, + "flow_title": "ZoneMinder", + "step": { + "user": { + "data": { + "host": "Host y Puerto (ej 10.10.0.4:8010)", + "password": "Contrase\u00f1a", + "path": "Ruta ZM", + "path_zms": "Ruta ZMS", + "ssl": "Usar SSL para conexiones a ZoneMinder", + "username": "Usuario", + "verify_ssl": "Verificar certificado SSL" + }, + "title": "A\u00f1adir Servidor ZoneMinder" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zoneminder/translations/et.json b/homeassistant/components/zoneminder/translations/et.json new file mode 100644 index 00000000000000..00ec75faee696b --- /dev/null +++ b/homeassistant/components/zoneminder/translations/et.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "auth_fail": "Kasutajanimi v\u00f5i salas\u00f5na on vale.", + "cannot_connect": "\u00dchendamine nurjus", + "connection_error": "ZoneMinderi serveriga \u00fchenduse loomine nurjus.", + "invalid_auth": "Tuvastamise viga" + }, + "create_entry": { + "default": "ZoneMinderi server on lisatud." + }, + "error": { + "auth_fail": "Vale kasutajanimi v\u00f5i salas\u00f5na", + "cannot_connect": "\u00dchendamine nurjus", + "connection_error": "ZoneMinderi serveriga \u00fchenduse loomine nurjus.", + "invalid_auth": "Tuvastamise viga" + }, + "flow_title": "ZoneMinder", + "step": { + "user": { + "data": { + "host": "Host ja port (n\u00e4iteks 10.10.0.4:8010)", + "password": "Salas\u00f5na", + "path": "ZM aadress", + "path_zms": "ZMS-i aadress", + "ssl": "Kasutage ZoneMinderiga \u00fchenduse loomiseks SSL-i", + "username": "Kasutajanimi", + "verify_ssl": "Kontrollige SSL-sertifikaati" + }, + "title": "Lisa ZoneMinderi server." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zoneminder/translations/fr.json b/homeassistant/components/zoneminder/translations/fr.json new file mode 100644 index 00000000000000..3f3729ce02f3df --- /dev/null +++ b/homeassistant/components/zoneminder/translations/fr.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "auth_fail": "L'identifiant ou le mot de passe est incorrect.", + "cannot_connect": "\u00c9chec de connexion", + "connection_error": "\u00c9chec de la connexion \u00e0 un serveur ZoneMinder.", + "invalid_auth": "Authentification invalide" + }, + "create_entry": { + "default": "Serveur Zoneminder ajout\u00e9." + }, + "error": { + "auth_fail": "L'identifiant ou le mot de passe est incorrect.", + "cannot_connect": "\u00c9chec de connexion", + "connection_error": "\u00c9chec de la connexion \u00e0 un serveur ZoneMinder.", + "invalid_auth": "Authentification invalide" + }, + "flow_title": "ZoneMinder", + "step": { + "user": { + "data": { + "host": "H\u00f4te et port (ex 10.10.0.4:8010)", + "password": "Mot de passe", + "path": "Chemin ZM", + "path_zms": "Chemin ZMS", + "ssl": "Utiliser SSL pour les connexions \u00e0 ZoneMinder", + "username": "Nom d'utilisateur", + "verify_ssl": "V\u00e9rifier le certificat SSL" + }, + "title": "Ajouter le serveur ZoneMinder." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zoneminder/translations/it.json b/homeassistant/components/zoneminder/translations/it.json new file mode 100644 index 00000000000000..cf2a3a6355369a --- /dev/null +++ b/homeassistant/components/zoneminder/translations/it.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "auth_fail": "Nome utente o password non corretti.", + "cannot_connect": "Impossibile connettersi", + "connection_error": "Impossibile connettersi a un server ZoneMinder.", + "invalid_auth": "Autenticazione non valida" + }, + "create_entry": { + "default": "Server ZoneMinder aggiunto." + }, + "error": { + "auth_fail": "Nome utente o password non corretti.", + "cannot_connect": "Impossibile connettersi", + "connection_error": "Impossibile connettersi a un server ZoneMinder.", + "invalid_auth": "Autenticazione non valida" + }, + "flow_title": "ZoneMinder", + "step": { + "user": { + "data": { + "host": "Host e porta (ad es. 10.10.0.4:8010)", + "password": "Password", + "path": "Percorso ZM", + "path_zms": "Percorso ZMS", + "ssl": "Utilizza un certificato SSL", + "username": "Nome utente", + "verify_ssl": "Verificare il certificato SSL" + }, + "title": "Aggiungi Server ZoneMinder." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zoneminder/translations/ko.json b/homeassistant/components/zoneminder/translations/ko.json new file mode 100644 index 00000000000000..3625d6e402ead4 --- /dev/null +++ b/homeassistant/components/zoneminder/translations/ko.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "auth_fail": "\uc0ac\uc6a9\uc790\uba85\uacfc \uc554\ud638\uac00 \uc62c\ubc14\ub974\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.", + "connection_error": "ZoneMinder \uc11c\ubc84\uc5d0 \uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4." + }, + "create_entry": { + "default": "ZoneMinder \uc11c\ubc84\uac00 \ucd94\uac00\ub418\uc5c8\uc2b5\ub2c8\ub2e4." + }, + "error": { + "auth_fail": "\uc0ac\uc6a9\uc790\uba85\uacfc \uc554\ud638\uac00 \uc62c\ubc14\ub974\uc9c0 \uc54a\uc2b5\ub2c8\ub2e4.", + "connection_error": "ZoneMinder \uc11c\ubc84\uc5d0 \uc5f0\uacb0\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4." + }, + "flow_title": "ZoneMinder", + "step": { + "user": { + "data": { + "host": "\ud638\uc2a4\ud2b8 \ubc0f \ud3ec\ud2b8(\uc608: 10.10.0.4:8010)", + "password": "\uc554\ud638", + "path": "ZMS \uacbd\ub85c", + "path_zms": "ZMS \uacbd\ub85c", + "ssl": "ZoneMinder \uc5f0\uacb0\uc5d0 SSL \uc0ac\uc6a9", + "username": "\uc0ac\uc6a9\uc790\uba85", + "verify_ssl": "SSL \uc778\uc99d\uc11c \ud655\uc778" + }, + "title": "ZoneMinder \uc11c\ubc84\ub97c \ucd94\uac00\ud558\uc138\uc694." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zoneminder/translations/lb.json b/homeassistant/components/zoneminder/translations/lb.json new file mode 100644 index 00000000000000..ad0669b1040104 --- /dev/null +++ b/homeassistant/components/zoneminder/translations/lb.json @@ -0,0 +1,30 @@ +{ + "config": { + "abort": { + "auth_fail": "Benotzernumm oder Passwuert inkorrekt", + "connection_error": "Feeler beim verbannen mam ZoneMinder Server." + }, + "create_entry": { + "default": "Zoneminder Server dob\u00e4igesat." + }, + "error": { + "auth_fail": "Benotzernumm oder Passwuert inkorrekt", + "connection_error": "Feeler beim verbannen mam ZoneMinder Server." + }, + "flow_title": "ZoneMinder", + "step": { + "user": { + "data": { + "host": "Host a Port (beispill 10.10.0.4:8010)", + "password": "Passwuert", + "path": "ZM Pad", + "path_zms": "ZMS Pad", + "ssl": "Benotz SSL fir d'Verbindung mat ZoneMinder", + "username": "Benotzernumm", + "verify_ssl": "SSL Zertifikat iwwerpr\u00e9iwen" + }, + "title": "ZoneMinder Server dob\u00e4isetzen." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zoneminder/translations/nl.json b/homeassistant/components/zoneminder/translations/nl.json new file mode 100644 index 00000000000000..a9ad121c32e00d --- /dev/null +++ b/homeassistant/components/zoneminder/translations/nl.json @@ -0,0 +1,23 @@ +{ + "config": { + "abort": { + "auth_fail": "Gebruikersnaam of wachtwoord is onjuist.", + "connection_error": "Kan geen verbinding maken met een ZoneMinder-server." + }, + "flow_title": "ZoneMinder", + "step": { + "user": { + "data": { + "host": "Host en poort (ex 10.10.0.4:8010)", + "password": "Wachtwoord", + "path": "ZM-pad", + "path_zms": "ZMS-pad", + "ssl": "Gebruik SSL voor verbindingen met ZoneMinder", + "username": "Gebruikersnaam", + "verify_ssl": "Verifieer SSLcertificaat" + }, + "title": "Voeg ZoneMinder server toe." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zoneminder/translations/no.json b/homeassistant/components/zoneminder/translations/no.json new file mode 100644 index 00000000000000..096f5024ac5427 --- /dev/null +++ b/homeassistant/components/zoneminder/translations/no.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "auth_fail": "Brukernavn eller passord er feil.", + "cannot_connect": "Tilkobling mislyktes.", + "connection_error": "Kunne ikke koble til en ZoneMinder-server.", + "invalid_auth": "Ugyldig godkjenning" + }, + "create_entry": { + "default": "ZoneMinder-serveren er lagt til." + }, + "error": { + "auth_fail": "Brukernavn eller passord er feil.", + "cannot_connect": "Tilkobling mislyktes.", + "connection_error": "Kunne ikke koble til en ZoneMinder-server.", + "invalid_auth": "Ugyldig godkjenning" + }, + "flow_title": "ZoneMinder", + "step": { + "user": { + "data": { + "host": "Vert og port (f.eks. 10.10.0.4:8010)", + "password": "Passord", + "path": "ZM-bane", + "path_zms": "ZMS-bane", + "ssl": "Bruker et SSL-sertifikat", + "username": "Brukernavn", + "verify_ssl": "Verifisere SSL-sertifikat" + }, + "title": "Legg til ZoneMinder Server." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zoneminder/translations/pl.json b/homeassistant/components/zoneminder/translations/pl.json new file mode 100644 index 00000000000000..1795b192e7fae5 --- /dev/null +++ b/homeassistant/components/zoneminder/translations/pl.json @@ -0,0 +1,29 @@ +{ + "config": { + "abort": { + "auth_fail": "Nazwa u\u017cytkownika lub has\u0142o jest niepoprawne.", + "connection_error": "Nie uda\u0142o si\u0119 po\u0142\u0105czy\u0107 z serwerem ZoneMinder." + }, + "create_entry": { + "default": "Dodano serwer ZoneMinder." + }, + "error": { + "auth_fail": "Nazwa u\u017cytkownika lub has\u0142o jest niepoprawne.", + "cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia", + "connection_error": "Nie uda\u0142o si\u0119 po\u0142\u0105czy\u0107 z serwerem ZoneMinder." + }, + "flow_title": "ZoneMinder", + "step": { + "user": { + "data": { + "host": "Host i port (np. 10.10.0.4:8010)", + "password": "Has\u0142o", + "path": "\u015acie\u017cka do ZM", + "path_zms": "\u015acie\u017cka do ZMS", + "username": "Nazwa u\u017cytkownika" + }, + "title": "Dodawanie serwera ZoneMinder." + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zoneminder/translations/ru.json b/homeassistant/components/zoneminder/translations/ru.json new file mode 100644 index 00000000000000..d599e767f64a49 --- /dev/null +++ b/homeassistant/components/zoneminder/translations/ru.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "auth_fail": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043b\u043e\u0433\u0438\u043d \u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c.", + "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 \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 ZoneMinder.", + "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f." + }, + "create_entry": { + "default": "\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d \u0441\u0435\u0440\u0432\u0435\u0440 ZoneMinder." + }, + "error": { + "auth_fail": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043b\u043e\u0433\u0438\u043d \u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c.", + "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 \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 ZoneMinder.", + "invalid_auth": "\u041d\u0435\u0432\u0435\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f." + }, + "flow_title": "ZoneMinder", + "step": { + "user": { + "data": { + "host": "\u0425\u043e\u0441\u0442 \u0438 \u043f\u043e\u0440\u0442 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440: 10.10.0.4:8010)", + "password": "\u041f\u0430\u0440\u043e\u043b\u044c", + "path": "\u041f\u0443\u0442\u044c \u043a ZM", + "path_zms": "\u041f\u0443\u0442\u044c \u043a ZMS", + "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": "\u041b\u043e\u0433\u0438\u043d", + "verify_ssl": "\u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 SSL" + }, + "title": "ZoneMinder" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zoneminder/translations/sv.json b/homeassistant/components/zoneminder/translations/sv.json new file mode 100644 index 00000000000000..37fd73d32f007a --- /dev/null +++ b/homeassistant/components/zoneminder/translations/sv.json @@ -0,0 +1,17 @@ +{ + "config": { + "abort": { + "auth_fail": "Anv\u00e4ndarnamn eller l\u00f6senord \u00e4r felaktigt." + }, + "error": { + "auth_fail": "Anv\u00e4ndarnamn eller l\u00f6senord \u00e4r felaktigt." + }, + "step": { + "user": { + "data": { + "verify_ssl": "Verifiera SSL-certifikat" + } + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zoneminder/translations/zh-Hant.json b/homeassistant/components/zoneminder/translations/zh-Hant.json new file mode 100644 index 00000000000000..310a15014f6127 --- /dev/null +++ b/homeassistant/components/zoneminder/translations/zh-Hant.json @@ -0,0 +1,34 @@ +{ + "config": { + "abort": { + "auth_fail": "\u4f7f\u7528\u8005\u540d\u7a31\u6216\u5bc6\u78bc\u932f\u8aa4\u3002", + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "connection_error": "ZoneMinder \u4f3a\u670d\u5668\u9023\u7dda\u5931\u6557\u3002", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548" + }, + "create_entry": { + "default": "ZoneMinder \u4f3a\u670d\u5668\u5df2\u65b0\u589e\u3002" + }, + "error": { + "auth_fail": "\u4f7f\u7528\u8005\u540d\u7a31\u6216\u5bc6\u78bc\u932f\u8aa4\u3002", + "cannot_connect": "\u9023\u7dda\u5931\u6557", + "connection_error": "ZoneMinder \u4f3a\u670d\u5668\u9023\u7dda\u5931\u6557\u3002", + "invalid_auth": "\u9a57\u8b49\u78bc\u7121\u6548" + }, + "flow_title": "ZoneMinder", + "step": { + "user": { + "data": { + "host": "\u4e3b\u6a5f\u7aef\u8207\u901a\u8a0a\u57e0\uff08\u4f8b\u5982 10.10.0.4:8010\uff09", + "password": "\u5bc6\u78bc", + "path": "ZM \u8def\u5f91", + "path_zms": "ZMS \u8def\u5f91", + "ssl": "\u4f7f\u7528 SSL \u8a8d\u8b49", + "username": "\u4f7f\u7528\u8005\u540d\u7a31", + "verify_ssl": "\u78ba\u8a8d SSL \u8a8d\u8b49" + }, + "title": "\u65b0\u589e ZoneMinder \u4f3a\u670d\u5668\u3002" + } + } + } +} \ No newline at end of file diff --git a/homeassistant/components/zwave/config_flow.py b/homeassistant/components/zwave/config_flow.py index d6c64e914f6cc5..6357d9929b1011 100644 --- a/homeassistant/components/zwave/config_flow.py +++ b/homeassistant/components/zwave/config_flow.py @@ -31,7 +31,7 @@ def __init__(self): async def async_step_user(self, user_input=None): """Handle a flow start.""" if self._async_current_entries(): - return self.async_abort(reason="one_instance_only") + return self.async_abort(reason="single_instance_allowed") errors = {} diff --git a/homeassistant/components/zwave/strings.json b/homeassistant/components/zwave/strings.json index cab8e7461cb59e..852b8ca22fab11 100644 --- a/homeassistant/components/zwave/strings.json +++ b/homeassistant/components/zwave/strings.json @@ -14,8 +14,8 @@ "option_error": "Z-Wave validation failed. Is the path to the USB stick correct?" }, "abort": { - "already_configured": "Z-Wave is already configured", - "one_instance_only": "Component only supports one Z-Wave instance" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]" } }, "state": { @@ -30,4 +30,4 @@ "ready": "Ready" } } -} \ No newline at end of file +} diff --git a/homeassistant/components/zwave/translations/ca.json b/homeassistant/components/zwave/translations/ca.json index dedf7f60e4a920..ce4e7f5301b469 100644 --- a/homeassistant/components/zwave/translations/ca.json +++ b/homeassistant/components/zwave/translations/ca.json @@ -1,8 +1,9 @@ { "config": { "abort": { - "already_configured": "Z-Wave ja est\u00e0 configurat", - "one_instance_only": "El component nom\u00e9s admet una inst\u00e0ncia de Z-Wave" + "already_configured": "El dispositiu ja est\u00e0 configurat", + "one_instance_only": "El component nom\u00e9s admet una inst\u00e0ncia de Z-Wave", + "single_instance_allowed": "Ja configurat. Nom\u00e9s \u00e9s possible una sola configuraci\u00f3." }, "error": { "option_error": "Ha fallat la validaci\u00f3 de Z-Wave. \u00c9s correcta la ruta al port USB on hi ha connectat el dispositiu?" diff --git a/homeassistant/components/zwave/translations/en.json b/homeassistant/components/zwave/translations/en.json index bd27966b6a5c13..19732f99e6f36e 100644 --- a/homeassistant/components/zwave/translations/en.json +++ b/homeassistant/components/zwave/translations/en.json @@ -1,8 +1,9 @@ { "config": { "abort": { - "already_configured": "Z-Wave is already configured", - "one_instance_only": "Component only supports one Z-Wave instance" + "already_configured": "Device is already configured", + "one_instance_only": "Component only supports one Z-Wave instance", + "single_instance_allowed": "Already configured. Only a single configuration possible." }, "error": { "option_error": "Z-Wave validation failed. Is the path to the USB stick correct?" diff --git a/homeassistant/components/zwave/translations/es.json b/homeassistant/components/zwave/translations/es.json index 02b95f0e0285dd..34dd9dd66530fb 100644 --- a/homeassistant/components/zwave/translations/es.json +++ b/homeassistant/components/zwave/translations/es.json @@ -2,7 +2,8 @@ "config": { "abort": { "already_configured": "Z-Wave ya est\u00e1 configurado", - "one_instance_only": "El componente solo admite una instancia de Z-Wave" + "one_instance_only": "El componente solo admite una instancia de Z-Wave", + "single_instance_allowed": "Ya est\u00e1 configurado. S\u00f3lo es posible una \u00fanica configuraci\u00f3n." }, "error": { "option_error": "Z-Wave error de validaci\u00f3n. \u00bfLa ruta de acceso a la memoria USB escorrecta?" diff --git a/homeassistant/components/zwave/translations/et.json b/homeassistant/components/zwave/translations/et.json index e33b5e32827894..18fc2b7f571a87 100644 --- a/homeassistant/components/zwave/translations/et.json +++ b/homeassistant/components/zwave/translations/et.json @@ -1,4 +1,9 @@ { + "config": { + "abort": { + "single_instance_allowed": "Juba seadistatud. V\u00f5imalik on ainult \u00fcks seadistamine." + } + }, "state": { "_": { "dead": "Surnud", @@ -7,8 +12,8 @@ "sleeping": "Ootel" }, "query_stage": { - "dead": "Surnud ({query_stage})", - "initializing": "L\u00e4htestan ( {query_stage} )" + "dead": "Surnud", + "initializing": "L\u00e4htestan" } } } \ No newline at end of file diff --git a/homeassistant/components/zwave/translations/it.json b/homeassistant/components/zwave/translations/it.json index 2b1b248e92c475..d8647733fad4af 100644 --- a/homeassistant/components/zwave/translations/it.json +++ b/homeassistant/components/zwave/translations/it.json @@ -1,8 +1,9 @@ { "config": { "abort": { - "already_configured": "Z-Wave \u00e8 gi\u00e0 configurato", - "one_instance_only": "Il componente supporta solo un'istanza di Z-Wave" + "already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato", + "one_instance_only": "Il componente supporta solo un'istanza di Z-Wave", + "single_instance_allowed": "Gi\u00e0 configurato. \u00c8 possibile una sola configurazione." }, "error": { "option_error": "Convalida Z-Wave fallita. Il percorso della chiavetta USB \u00e8 corretto?" diff --git a/homeassistant/components/zwave/translations/no.json b/homeassistant/components/zwave/translations/no.json index d2614bbb2c7d43..aa1e74264ed802 100644 --- a/homeassistant/components/zwave/translations/no.json +++ b/homeassistant/components/zwave/translations/no.json @@ -1,8 +1,9 @@ { "config": { "abort": { - "already_configured": "Z-Wave er allerede konfigurert", - "one_instance_only": "Komponenten st\u00f8tter kun en Z-Wave-forekomst" + "already_configured": "Enheten er allerede konfigurert", + "one_instance_only": "Komponenten st\u00f8tter kun en Z-Wave-forekomst", + "single_instance_allowed": "Allerede konfigurert. Bare \u00e9n enkelt konfigurasjon er mulig." }, "error": { "option_error": "Z-Wave-validering mislyktes. Er banen til USB dongel riktig?" diff --git a/homeassistant/components/zwave/translations/ru.json b/homeassistant/components/zwave/translations/ru.json index 72255110b6a916..c74405767a247d 100644 --- a/homeassistant/components/zwave/translations/ru.json +++ b/homeassistant/components/zwave/translations/ru.json @@ -1,8 +1,9 @@ { "config": { "abort": { - "already_configured": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430.", - "one_instance_only": "\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043c\u043e\u0436\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0441 \u043e\u0434\u043d\u0438\u043c \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u043e\u043c Z-Wave." + "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.", + "one_instance_only": "\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043c\u043e\u0436\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0441 \u043e\u0434\u043d\u0438\u043c \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u043b\u0435\u0440\u043e\u043c Z-Wave.", + "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": { "option_error": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 Z-Wave. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043f\u0443\u0442\u044c \u043a USB-\u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0443." diff --git a/homeassistant/config.py b/homeassistant/config.py index 36a81f98fa3824..3e9dd27458d3f6 100644 --- a/homeassistant/config.py +++ b/homeassistant/config.py @@ -33,6 +33,7 @@ CONF_INTERNAL_URL, CONF_LATITUDE, CONF_LONGITUDE, + CONF_MEDIA_DIRS, CONF_NAME, CONF_PACKAGES, CONF_TEMPERATURE_UNIT, @@ -221,6 +222,8 @@ def _no_duplicate_auth_mfa_module( ], _no_duplicate_auth_mfa_module, ), + # pylint: disable=no-value-for-parameter + vol.Optional(CONF_MEDIA_DIRS): cv.schema_with_slug_keys(vol.IsDir()), } ) @@ -496,6 +499,7 @@ async def async_process_ha_core_config(hass: HomeAssistant, config: Dict) -> Non (CONF_ELEVATION, "elevation"), (CONF_INTERNAL_URL, "internal_url"), (CONF_EXTERNAL_URL, "external_url"), + (CONF_MEDIA_DIRS, "media_dirs"), ): if key in config: setattr(hac, attr, config[key]) @@ -503,8 +507,14 @@ async def async_process_ha_core_config(hass: HomeAssistant, config: Dict) -> Non if CONF_TIME_ZONE in config: hac.set_time_zone(config[CONF_TIME_ZONE]) + if CONF_MEDIA_DIRS not in config: + if is_docker_env(): + hac.media_dirs = {"local": "/media"} + else: + hac.media_dirs = {"local": hass.config.path("media")} + # Init whitelist external dir - hac.allowlist_external_dirs = {hass.config.path("www"), hass.config.path("media")} + hac.allowlist_external_dirs = {hass.config.path("www"), *hac.media_dirs.values()} if CONF_ALLOWLIST_EXTERNAL_DIRS in config: hac.allowlist_external_dirs.update(set(config[CONF_ALLOWLIST_EXTERNAL_DIRS])) diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index 347ca294d34e66..139e2066d17e70 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -39,6 +39,9 @@ # been removed and unloaded. SOURCE_UNIGNORE = "unignore" +# This is used to signal that re-authentication is required by the user. +SOURCE_REAUTH = "reauth" + HANDLERS = Registry() STORAGE_KEY = "core.config_entries" diff --git a/homeassistant/const.py b/homeassistant/const.py index 81f2243bca3350..d0f17b5de3d914 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -1,6 +1,6 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 -MINOR_VERSION = 116 +MINOR_VERSION = 117 PATCH_VERSION = "0.dev0" __short_version__ = f"{MAJOR_VERSION}.{MINOR_VERSION}" __version__ = f"{__short_version__}.{PATCH_VERSION}" @@ -116,6 +116,7 @@ CONF_LONGITUDE = "longitude" CONF_MAC = "mac" CONF_MAXIMUM = "maximum" +CONF_MEDIA_DIRS = "media_dirs" CONF_METHOD = "method" CONF_MINIMUM = "minimum" CONF_MODE = "mode" @@ -380,10 +381,15 @@ # Degree units DEGREE = "°" +# Currency units +CURRENCY_EURO = "€" +CURRENCY_DOLLAR = "$" +CURRENCY_CENT = "¢" + # Temperature units TEMP_CELSIUS = f"{DEGREE}C" TEMP_FAHRENHEIT = f"{DEGREE}F" -TEMP_KELVIN = f"{DEGREE}K" +TEMP_KELVIN = "K" # Time units TIME_MICROSECONDS = "μs" @@ -397,6 +403,7 @@ TIME_YEARS = "y" # Length units +LENGTH_MILLIMETERS: str = "mm" LENGTH_CENTIMETERS: str = "cm" LENGTH_METERS: str = "m" LENGTH_KILOMETERS: str = "km" @@ -422,6 +429,7 @@ VOLUME_LITERS: str = "L" VOLUME_MILLILITERS: str = "mL" VOLUME_CUBIC_METERS = f"{LENGTH_METERS}³" +VOLUME_CUBIC_FEET = f"{LENGTH_FEET}³" VOLUME_GALLONS: str = "gal" VOLUME_FLUID_OUNCE: str = "fl. oz." @@ -441,6 +449,9 @@ # Conductivity units CONDUCTIVITY: str = f"µS/{LENGTH_CENTIMETERS}" +# Light units +LIGHT_LUX: str = "lx" + # UV Index units UV_INDEX: str = "UV index" @@ -461,6 +472,10 @@ SPEED_KILOMETERS_PER_HOUR = f"{LENGTH_KILOMETERS}/{TIME_HOURS}" SPEED_MILES_PER_HOUR = "mph" +# Signal_strength units +SIGNAL_STRENGTH_DECIBELS = "dB" +SIGNAL_STRENGTH_DECIBELS_MILLIWATT = "dBm" + # Data units DATA_BITS = "bit" DATA_KILOBITS = "kbit" @@ -564,6 +579,7 @@ HTTP_OK = 200 HTTP_CREATED = 201 +HTTP_ACCEPTED = 202 HTTP_MOVED_PERMANENTLY = 301 HTTP_BAD_REQUEST = 400 HTTP_UNAUTHORIZED = 401 @@ -573,6 +589,7 @@ HTTP_UNPROCESSABLE_ENTITY = 422 HTTP_TOO_MANY_REQUESTS = 429 HTTP_INTERNAL_SERVER_ERROR = 500 +HTTP_BAD_GATEWAY = 502 HTTP_SERVICE_UNAVAILABLE = 503 HTTP_BASIC_AUTHENTICATION = "basic" @@ -607,3 +624,10 @@ # Static list of entities that will never be exposed to # cloud, alexa, or google_home components CLOUD_NEVER_EXPOSED_ENTITIES = ["group.all_locks"] + +# The ID of the Home Assistant Cast App +CAST_APP_ID_HOMEASSISTANT = "B12CE3CA" + +# The tracker error allow when converting +# loop time to human readable time +MAX_TIME_TRACKING_ERROR = 0.001 diff --git a/homeassistant/core.py b/homeassistant/core.py index 8f3809bbd4c4a8..9f598d4641027c 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -21,6 +21,7 @@ Any, Awaitable, Callable, + Collection, Coroutine, Dict, Iterable, @@ -538,7 +539,7 @@ def __init__( event_type: str, data: Optional[Dict[str, Any]] = None, origin: EventOrigin = EventOrigin.local, - time_fired: Optional[int] = None, + time_fired: Optional[datetime.datetime] = None, context: Optional[Context] = None, ) -> None: """Initialize a new event.""" @@ -548,6 +549,11 @@ def __init__( self.time_fired = time_fired or dt_util.utcnow() self.context: Context = context or Context() + def __hash__(self) -> int: + """Make hashable.""" + # The only event type that shares context are the TIME_CHANGED + return hash((self.event_type, self.context.id, self.time_fired)) + def as_dict(self) -> Dict: """Create a dict representation of this Event. @@ -556,8 +562,8 @@ def as_dict(self) -> Dict: return { "event_type": self.event_type, "data": dict(self.data), - "origin": str(self.origin), - "time_fired": self.time_fired, + "origin": str(self.origin.value), + "time_fired": self.time_fired.isoformat(), "context": self.context.as_dict(), } @@ -621,6 +627,7 @@ def async_fire( event_data: Optional[Dict] = None, origin: EventOrigin = EventOrigin.local, context: Optional[Context] = None, + time_fired: Optional[datetime.datetime] = None, ) -> None: """Fire an event. @@ -633,7 +640,7 @@ def async_fire( if match_all_listeners is not None and event_type != EVENT_HOMEASSISTANT_CLOSE: listeners = match_all_listeners + listeners - event = Event(event_type, event_data, origin, None, context) + event = Event(event_type, event_data, origin, time_fired, context) if event_type != EVENT_TIME_CHANGED: _LOGGER.debug("Bus:Handling %s", event) @@ -754,6 +761,7 @@ class State: last_updated: last time this object was updated. context: Context in which it was created domain: Domain of this state. + object_id: Object id of this state. """ __slots__ = [ @@ -764,6 +772,8 @@ class State: "last_updated", "context", "domain", + "object_id", + "_as_dict", ] def __init__( @@ -797,12 +807,8 @@ def __init__( self.last_updated = last_updated or dt_util.utcnow() self.last_changed = last_changed or self.last_updated self.context = context or Context() - self.domain = split_entity_id(self.entity_id)[0] - - @property - def object_id(self) -> str: - """Object id of this state.""" - return split_entity_id(self.entity_id)[1] + self.domain, self.object_id = split_entity_id(self.entity_id) + self._as_dict: Optional[Dict[str, Collection[Any]]] = None @property def name(self) -> str: @@ -819,14 +825,21 @@ def as_dict(self) -> Dict: To be used for JSON serialization. Ensures: state == State.from_dict(state.as_dict()) """ - return { - "entity_id": self.entity_id, - "state": self.state, - "attributes": dict(self.attributes), - "last_changed": self.last_changed, - "last_updated": self.last_updated, - "context": self.context.as_dict(), - } + if not self._as_dict: + 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() + self._as_dict = { + "entity_id": self.entity_id, + "state": self.state, + "attributes": dict(self.attributes), + "last_changed": last_changed_isoformat, + "last_updated": last_updated_isoformat, + "context": self.context.as_dict(), + } + return self._as_dict @classmethod def from_dict(cls, json_dict: Dict) -> Any: @@ -907,7 +920,7 @@ def async_entity_ids( This method must be run in the event loop. """ if domain_filter is None: - return list(self._states.keys()) + return list(self._states) if isinstance(domain_filter, str): domain_filter = (domain_filter.lower(),) @@ -918,6 +931,24 @@ def async_entity_ids( if state.domain in domain_filter ] + @callback + def async_entity_ids_count( + self, domain_filter: Optional[Union[str, Iterable]] = None + ) -> int: + """Count the entity ids that are being tracked. + + This method must be run in the event loop. + """ + if domain_filter is None: + return len(self._states) + + if isinstance(domain_filter, str): + domain_filter = (domain_filter.lower(),) + + return len( + [None for state in self._states.values() if state.domain in domain_filter] + ) + def all(self, domain_filter: Optional[Union[str, Iterable]] = None) -> List[State]: """Create a list of all states.""" return run_callback_threadsafe( @@ -1390,6 +1421,9 @@ def __init__(self, hass: HomeAssistant) -> None: # List of allowed external URLs that integrations may use self.allowlist_external_urls: Set[str] = set() + # Dictionary of Media folders that integrations may use + self.media_dirs: Dict[str, str] = {} + # If Home Assistant is running in safe mode self.safe_mode: bool = False @@ -1620,13 +1654,18 @@ def fire_time_event(target: float) -> None: """Fire next time event.""" now = dt_util.utcnow() - hass.bus.async_fire(EVENT_TIME_CHANGED, {ATTR_NOW: now}, context=timer_context) + hass.bus.async_fire( + EVENT_TIME_CHANGED, {ATTR_NOW: now}, time_fired=now, context=timer_context + ) # If we are more than a second late, a tick was missed late = monotonic() - target if late > 1: hass.bus.async_fire( - EVENT_TIMER_OUT_OF_SYNC, {ATTR_SECONDS: late}, context=timer_context + EVENT_TIMER_OUT_OF_SYNC, + {ATTR_SECONDS: late}, + time_fired=now, + context=timer_context, ) schedule_tick(now) diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 86d778db825666..c7a29e549f7679 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -13,6 +13,7 @@ "agent_dvr", "airly", "airvisual", + "alarmdecoder", "almond", "ambiclimate", "ambient_station", @@ -30,6 +31,7 @@ "broadlink", "brother", "bsblan", + "canary", "cast", "cert_expiry", "control4", @@ -66,6 +68,7 @@ "geonetnz_volcano", "gios", "glances", + "goalzero", "gogogate2", "gpslogger", "griddy", @@ -125,6 +128,7 @@ "nut", "nws", "nzbget", + "omnilogic", "onvif", "opentherm_gw", "openuv", @@ -141,16 +145,19 @@ "point", "poolsense", "powerwall", + "profiler", "progettihwsw", "ps4", "pvpc_hourly_pricing", "rachio", "rainmachine", + "rfxtrx", "ring", "risco", "roku", "roomba", "roon", + "rpi_power", "samsungtv", "sense", "sentry", @@ -179,6 +186,7 @@ "syncthru", "synology_dsm", "tado", + "tasmota", "tellduslive", "tesla", "tibber", @@ -212,5 +220,6 @@ "yeelight", "zerproc", "zha", + "zoneminder", "zwave" ] diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 1b09348415c16e..c982b58d8d9933 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -297,7 +297,7 @@ def if_numeric_state( def state( hass: HomeAssistant, entity: Union[None, str, State], - req_state: Union[str, List[str]], + req_state: Any, for_period: Optional[timedelta] = None, attribute: Optional[str] = None, ) -> bool: @@ -314,17 +314,20 @@ def state( assert isinstance(entity, State) if attribute is None: - value = entity.state + value: Any = entity.state else: - value = str(entity.attributes.get(attribute)) + value = entity.attributes.get(attribute) - if isinstance(req_state, str): + if not isinstance(req_state, list): req_state = [req_state] is_state = False for req_state_value in req_state: state_value = req_state_value - if INPUT_ENTITY_ID.match(req_state_value) is not None: + if ( + isinstance(req_state_value, str) + and INPUT_ENTITY_ID.match(req_state_value) is not None + ): state_entity = hass.states.get(req_state_value) if not state_entity: continue @@ -649,13 +652,16 @@ async def async_validate_condition_config( @callback -def async_extract_entities(config: ConfigType) -> Set[str]: +def async_extract_entities(config: Union[ConfigType, Template]) -> Set[str]: """Extract entities from a condition.""" referenced: Set[str] = set() to_process = deque([config]) while to_process: config = to_process.popleft() + if isinstance(config, Template): + continue + condition = config[CONF_CONDITION] if condition in ("and", "not", "or"): @@ -674,13 +680,16 @@ def async_extract_entities(config: ConfigType) -> Set[str]: @callback -def async_extract_devices(config: ConfigType) -> Set[str]: +def async_extract_devices(config: Union[ConfigType, Template]) -> Set[str]: """Extract devices from a condition.""" referenced = set() to_process = deque([config]) while to_process: config = to_process.popleft() + if isinstance(config, Template): + continue + condition = config[CONF_CONDITION] if condition in ("and", "not", "or"): diff --git a/homeassistant/helpers/config_entry_flow.py b/homeassistant/helpers/config_entry_flow.py index 2b967286b959a3..f957d884d8d4ab 100644 --- a/homeassistant/helpers/config_entry_flow.py +++ b/homeassistant/helpers/config_entry_flow.py @@ -136,7 +136,7 @@ async def async_step_user( ) -> Dict[str, Any]: """Handle a user initiated set up flow to create a webhook.""" if not self._allow_multiple and self._async_current_entries(): - return self.async_abort(reason="one_instance_allowed") + return self.async_abort(reason="single_instance_allowed") if user_input is None: return self.async_show_form(step_id="user") diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 282e63e6440ba0..ad514e044aa4d4 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -702,7 +702,7 @@ def deprecated( else: # If Python is unable to access the sources files, the call stack frame # will be missing information, so let's guard. - # https://github.com/home-assistant/home-assistant/issues/24982 + # https://github.com/home-assistant/core/issues/24982 module_name = __name__ if replacement_key and invalidation_version: @@ -929,22 +929,44 @@ def script_action(value: Any) -> dict: has_at_least_one_key(CONF_BELOW, CONF_ABOVE), ) -STATE_CONDITION_SCHEMA = vol.All( - vol.Schema( - { - vol.Required(CONF_CONDITION): "state", - vol.Required(CONF_ENTITY_ID): entity_ids, - vol.Optional(CONF_ATTRIBUTE): str, - vol.Required(CONF_STATE): vol.Any(str, [str]), - vol.Optional(CONF_FOR): positive_time_period, - # To support use_trigger_value in automation - # Deprecated 2016/04/25 - vol.Optional("from"): str, - } - ), - key_dependency("for", "state"), +STATE_CONDITION_BASE_SCHEMA = { + vol.Required(CONF_CONDITION): "state", + vol.Required(CONF_ENTITY_ID): entity_ids, + vol.Optional(CONF_ATTRIBUTE): str, + vol.Optional(CONF_FOR): positive_time_period, + # To support use_trigger_value in automation + # Deprecated 2016/04/25 + vol.Optional("from"): str, +} + +STATE_CONDITION_STATE_SCHEMA = vol.Schema( + { + **STATE_CONDITION_BASE_SCHEMA, + vol.Required(CONF_STATE): vol.Any(str, [str]), + } ) +STATE_CONDITION_ATTRIBUTE_SCHEMA = vol.Schema( + { + **STATE_CONDITION_BASE_SCHEMA, + vol.Required(CONF_STATE): match_all, + } +) + + +def STATE_CONDITION_SCHEMA(value: Any) -> dict: # pylint: disable=invalid-name + """Validate a state condition.""" + if not isinstance(value, dict): + raise vol.Invalid("Expected a dictionary") + + if CONF_ATTRIBUTE in value: + validated: dict = STATE_CONDITION_ATTRIBUTE_SCHEMA(value) + else: + validated = STATE_CONDITION_STATE_SCHEMA(value) + + return key_dependency("for", "state")(validated) + + SUN_CONDITION_SCHEMA = vol.All( vol.Schema( { diff --git a/homeassistant/helpers/data_entry_flow.py b/homeassistant/helpers/data_entry_flow.py index 1cf1fa4545c5c7..e686dd2ae4b910 100644 --- a/homeassistant/helpers/data_entry_flow.py +++ b/homeassistant/helpers/data_entry_flow.py @@ -8,7 +8,7 @@ from homeassistant import config_entries, data_entry_flow from homeassistant.components.http import HomeAssistantView from homeassistant.components.http.data_validator import RequestDataValidator -from homeassistant.const import HTTP_NOT_FOUND +from homeassistant.const import HTTP_BAD_REQUEST, HTTP_NOT_FOUND import homeassistant.helpers.config_validation as cv @@ -76,7 +76,7 @@ async def post(self, request: web.Request, data: Dict[str, Any]) -> web.Response except data_entry_flow.UnknownHandler: return self.json_message("Invalid handler specified", HTTP_NOT_FOUND) except data_entry_flow.UnknownStep: - return self.json_message("Handler does not support user", 400) + return self.json_message("Handler does not support user", HTTP_BAD_REQUEST) result = self._prepare_result_json(result) @@ -107,7 +107,7 @@ async def post( except data_entry_flow.UnknownFlow: return self.json_message("Invalid flow specified", HTTP_NOT_FOUND) except vol.Invalid: - return self.json_message("User input malformed", 400) + return self.json_message("User input malformed", HTTP_BAD_REQUEST) result = self._prepare_result_json(result) diff --git a/homeassistant/helpers/deprecation.py b/homeassistant/helpers/deprecation.py index 2a4fafde75bad9..a62a2e6380493a 100644 --- a/homeassistant/helpers/deprecation.py +++ b/homeassistant/helpers/deprecation.py @@ -60,7 +60,7 @@ def get_deprecated( else: # If Python is unable to access the sources files, the call stack frame # will be missing information, so let's guard. - # https://github.com/home-assistant/home-assistant/issues/24982 + # https://github.com/home-assistant/core/issues/24982 module_name = __name__ logger = logging.getLogger(module_name) diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py index 5b3366d755430a..11c8535feee9c5 100644 --- a/homeassistant/helpers/entity.py +++ b/homeassistant/helpers/entity.py @@ -362,7 +362,7 @@ def _async_write_ha_state(self) -> None: else: extra = ( "Please create a bug report at " - "https://github.com/home-assistant/home-assistant/issues?q=is%3Aopen+is%3Aissue" + "https://github.com/home-assistant/core/issues?q=is%3Aopen+is%3Aissue" ) if self.platform: extra += ( @@ -453,26 +453,35 @@ async def async_device_update(self, warning: bool = True) -> None: if self.parallel_updates: await self.parallel_updates.acquire() - assert self.hass is not None - if warning: - update_warn = self.hass.loop.call_later( - SLOW_UPDATE_WARNING, - _LOGGER.warning, - "Update of %s is taking over %s seconds", - self.entity_id, - SLOW_UPDATE_WARNING, - ) - try: # pylint: disable=no-member if hasattr(self, "async_update"): - await self.async_update() # type: ignore + task = self.hass.async_create_task(self.async_update()) # type: ignore elif hasattr(self, "update"): - await self.hass.async_add_executor_job(self.update) # type: ignore + task = self.hass.async_add_executor_job(self.update) # type: ignore + else: + return + + if not warning: + await task + return + + finished, _ = await asyncio.wait([task], timeout=SLOW_UPDATE_WARNING) + + for done in finished: + exc = done.exception() + if exc: + raise exc + return + + _LOGGER.warning( + "Update of %s is taking over %s seconds", + self.entity_id, + SLOW_UPDATE_WARNING, + ) + await task finally: self._update_staged = False - if warning: - update_warn.cancel() if self.parallel_updates: self.parallel_updates.release() diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py index da1a3635d728d0..39b09cef193986 100644 --- a/homeassistant/helpers/entity_platform.py +++ b/homeassistant/helpers/entity_platform.py @@ -546,7 +546,7 @@ def async_register_entity_service(self, name, schema, func, required_features=No async def handle_service(call: ServiceCall) -> None: """Handle the service.""" - await service.entity_service_call( # type: ignore + await service.entity_service_call( self.hass, [ plf diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index 8e126c7c14cbc9..b6d59bb500cc19 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -27,6 +27,7 @@ EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL, + MAX_TIME_TRACKING_ERROR, SUN_EVENT_SUNRISE, SUN_EVENT_SUNSET, ) @@ -40,6 +41,7 @@ ) from homeassistant.exceptions import TemplateError from homeassistant.helpers.entity_registry import EVENT_ENTITY_REGISTRY_UPDATED +from homeassistant.helpers.ratelimit import KeyedRateLimit from homeassistant.helpers.sun import get_astral_event_next from homeassistant.helpers.template import RenderInfo, Template, result_as_boolean from homeassistant.helpers.typing import TemplateVarsType @@ -47,8 +49,6 @@ from homeassistant.util import dt as dt_util from homeassistant.util.async_ import run_callback_threadsafe -MAX_TIME_TRACKING_ERROR = 0.001 - TRACK_STATE_CHANGE_CALLBACKS = "track_state_change_callbacks" TRACK_STATE_CHANGE_LISTENER = "track_state_change_listener" @@ -61,19 +61,39 @@ TRACK_ENTITY_REGISTRY_UPDATED_CALLBACKS = "track_entity_registry_updated_callbacks" TRACK_ENTITY_REGISTRY_UPDATED_LISTENER = "track_entity_registry_updated_listener" +_ALL_LISTENER = "all" +_DOMAINS_LISTENER = "domains" +_ENTITIES_LISTENER = "entities" + _LOGGER = logging.getLogger(__name__) +@dataclass +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 + """ + + all_states: bool + entities: Set + domains: Set + + @dataclass class TrackTemplate: """Class for keeping track of a template with variables. The template is template to calculate. The variables are variables to pass to the template. + The rate_limit is a rate limit on how often the template is re-rendered. """ template: Template variables: TemplateVarsType + rate_limit: Optional[timedelta] = None @dataclass @@ -448,6 +468,158 @@ def _async_string_to_lower_list(instr: Union[str, Iterable[str]]) -> List[str]: return [mstr.lower() for mstr in instr] +class _TrackStateChangeFiltered: + """Handle removal / refresh of tracker.""" + + def __init__( + self, + hass: HomeAssistant, + track_states: TrackStates, + action: Callable[[Event], Any], + ): + """Handle removal / refresh of tracker init.""" + self.hass = hass + self._action = action + self._listeners: Dict[str, Callable] = {} + self._last_track_states: TrackStates = track_states + + @callback + def async_setup(self) -> None: + """Create listeners to track states.""" + track_states = self._last_track_states + + if ( + not track_states.all_states + and not track_states.domains + and not track_states.entities + ): + return + + if track_states.all_states: + self._setup_all_listener() + return + + self._setup_domains_listener(track_states.domains) + self._setup_entities_listener(track_states.domains, track_states.entities) + + @property + def listeners(self) -> Dict: + """State changes that will cause a re-render.""" + track_states = self._last_track_states + return { + _ALL_LISTENER: track_states.all_states, + _ENTITIES_LISTENER: track_states.entities, + _DOMAINS_LISTENER: track_states.domains, + } + + @callback + def async_update_listeners(self, new_track_states: TrackStates) -> None: + """Update the listeners based on the new TrackStates.""" + last_track_states = self._last_track_states + self._last_track_states = new_track_states + + had_all_listener = last_track_states.all_states + + if new_track_states.all_states: + if had_all_listener: + return + self._cancel_listener(_DOMAINS_LISTENER) + self._cancel_listener(_ENTITIES_LISTENER) + self._setup_all_listener() + return + + if had_all_listener: + self._cancel_listener(_ALL_LISTENER) + + domains_changed = new_track_states.domains != last_track_states.domains + + if had_all_listener or domains_changed: + domains_changed = True + self._cancel_listener(_DOMAINS_LISTENER) + self._setup_domains_listener(new_track_states.domains) + + if ( + had_all_listener + or domains_changed + or new_track_states.entities != last_track_states.entities + ): + self._cancel_listener(_ENTITIES_LISTENER) + self._setup_entities_listener( + new_track_states.domains, new_track_states.entities + ) + + @callback + def async_remove(self) -> None: + """Cancel the listeners.""" + for key in list(self._listeners): + self._listeners.pop(key)() + + @callback + def _cancel_listener(self, listener_name: str) -> None: + if listener_name not in self._listeners: + return + + self._listeners.pop(listener_name)() + + @callback + def _setup_entities_listener(self, domains: Set, entities: Set) -> None: + if domains: + entities = entities.copy() + entities.update(self.hass.states.async_entity_ids(domains)) + + # Entities has changed to none + if not entities: + return + + self._listeners[_ENTITIES_LISTENER] = async_track_state_change_event( + self.hass, entities, self._action + ) + + @callback + def _setup_domains_listener(self, domains: Set) -> None: + if not domains: + return + + self._listeners[_DOMAINS_LISTENER] = async_track_state_added_domain( + self.hass, domains, self._action + ) + + @callback + def _setup_all_listener(self) -> None: + self._listeners[_ALL_LISTENER] = self.hass.bus.async_listen( + EVENT_STATE_CHANGED, self._action + ) + + +@callback +@bind_hass +def async_track_state_change_filtered( + hass: HomeAssistant, + track_states: TrackStates, + action: Callable[[Event], Any], +) -> _TrackStateChangeFiltered: + """Track state changes with a TrackStates filter that can be updated. + + Parameters + ---------- + hass + Home assistant object. + track_states + A TrackStates data class. + action + Callable to call with results. + + Returns + ------- + Object used to update the listeners (async_update_listeners) with a new TrackStates or + cancel the tracking (async_remove). + + """ + tracker = _TrackStateChangeFiltered(hass, track_states, action) + tracker.async_setup() + return tracker + + @callback @bind_hass def async_track_template( @@ -553,18 +725,13 @@ def __init__( track_template_.template.hass = hass self._track_templates = track_templates - self._all_listener: Optional[Callable] = None - self._domains_listener: Optional[Callable] = None - self._entities_listener: Optional[Callable] = None - self._last_result: Dict[Template, Union[str, TemplateError]] = {} - self._last_info: Dict[Template, RenderInfo] = {} + + self._rate_limit = KeyedRateLimit(hass) self._info: Dict[Template, RenderInfo] = {} - self._last_domains: Set = set() - self._last_entities: Set = set() - self._entity_ids_filter: Set = set() + self._track_state_changes: Optional[_TrackStateChangeFiltered] = None - def async_setup(self) -> None: + def async_setup(self, raise_on_template_error: bool) -> None: """Activation of template tracking.""" for track_template_ in self._track_templates: template = track_template_.template @@ -572,213 +739,126 @@ def async_setup(self) -> None: self._info[template] = template.async_render_to_info(variables) if self._info[template].exception: + if raise_on_template_error: + raise self._info[template].exception _LOGGER.error( "Error while processing template: %s", track_template_.template, exc_info=self._info[template].exception, ) - self._last_info = self._info.copy() - self._create_listeners() + self._track_state_changes = async_track_state_change_filtered( + self.hass, _render_infos_to_track_states(self._info.values()), self._refresh + ) + _LOGGER.debug( + "Template group %s listens for %s", + self._track_templates, + self.listeners, + ) @property def listeners(self) -> Dict: """State changes that will cause a re-render.""" - return { - "all": self._all_listener is not None, - "entities": self._last_entities, - "domains": self._last_domains, - } - - @property - def _needs_all_listener(self) -> bool: - for track_template_ in self._track_templates: - template = track_template_.template - - # Tracking all states - if self._info[template].all_states: - return True - - # Previous call had an exception - # so we do not know which states - # to track - if self._info[template].exception: - return True - - return False - - @property - def _all_templates_are_static(self) -> bool: - for track_template_ in self._track_templates: - if not self._info[track_template_.template].is_static: - return False - - return True - - @callback - def _create_listeners(self) -> None: - if self._all_templates_are_static: - return - - if self._needs_all_listener: - self._setup_all_listener() - return - - self._last_entities, self._last_domains = _entities_domains_from_info( - self._info.values() - ) - self._setup_domains_listener(self._last_domains) - self._setup_entities_listener(self._last_domains, self._last_entities) + assert self._track_state_changes + return self._track_state_changes.listeners @callback - def _cancel_domains_listener(self) -> None: - if self._domains_listener is None: - return - self._domains_listener() - self._domains_listener = None - - @callback - def _cancel_entities_listener(self) -> None: - if self._entities_listener is None: - return - self._entities_listener() - self._entities_listener = None - - @callback - def _cancel_all_listener(self) -> None: - if self._all_listener is None: - return - self._all_listener() - self._all_listener = None + def async_remove(self) -> None: + """Cancel the listener.""" + assert self._track_state_changes + self._track_state_changes.async_remove() + self._rate_limit.async_remove() @callback - def _update_listeners(self) -> None: - if self._needs_all_listener: - if self._all_listener: - return - self._last_domains = set() - self._last_entities = set() - self._cancel_domains_listener() - self._cancel_entities_listener() - self._setup_all_listener() - return + def async_refresh(self) -> None: + """Force recalculate the template.""" + self._refresh(None) - had_all_listener = self._all_listener is not None - if had_all_listener: - self._cancel_all_listener() + def _render_template_if_ready( + self, + track_template_: TrackTemplate, + now: datetime, + event: Optional[Event], + ) -> Union[bool, TrackTemplateResult]: + """Re-render the template if conditions match. - entities, domains = _entities_domains_from_info(self._info.values()) - domains_changed = domains != self._last_domains + Returns False if the template was not be re-rendered - if had_all_listener or domains_changed: - domains_changed = True - self._cancel_domains_listener() - self._setup_domains_listener(domains) + Returns True if the template re-rendered and did not + change. - if had_all_listener or domains_changed or entities != self._last_entities: - self._cancel_entities_listener() - self._setup_entities_listener(domains, entities) + Returns TrackTemplateResult if the template re-render + generates a new result. + """ + template = track_template_.template - self._last_domains = domains - self._last_entities = entities + if event: + info = self._info[template] - @callback - def _setup_entities_listener(self, domains: Set, entities: Set) -> None: - if domains: - entities = entities.copy() - entities.update(self.hass.states.async_entity_ids(domains)) + if not self._rate_limit.async_has_timer( + template + ) and not _event_triggers_rerender(event, info): + return False - # Entities has changed to none - if not entities: - return + if self._rate_limit.async_schedule_action( + template, + _rate_limit_for_event(event, info, track_template_), + now, + self._refresh, + event, + ): + return False - self._entities_listener = async_track_state_change_event( - self.hass, entities, self._refresh - ) + _LOGGER.debug( + "Template update %s triggered by event: %s", + template.template, + event, + ) - @callback - def _setup_domains_listener(self, domains: Set) -> None: - if not domains: - return + self._rate_limit.async_triggered(template, now) + self._info[template] = template.async_render_to_info(track_template_.variables) - self._domains_listener = async_track_state_added_domain( - self.hass, domains, self._refresh - ) + try: + result: Union[str, TemplateError] = self._info[template].result() + except TemplateError as ex: + result = ex - @callback - def _setup_all_listener(self) -> None: - self._all_listener = self.hass.bus.async_listen( - EVENT_STATE_CHANGED, self._refresh - ) + last_result = self._last_result.get(template) - @callback - def async_remove(self) -> None: - """Cancel the listener.""" - self._cancel_all_listener() - self._cancel_domains_listener() - self._cancel_entities_listener() + # Check to see if the result has changed + if result == last_result: + return True - @callback - def async_refresh(self) -> None: - """Force recalculate the template.""" - self._refresh(None) + if isinstance(result, TemplateError) and isinstance(last_result, TemplateError): + return True - @callback - def async_update_entity_ids_filter(self, entity_ids: Set) -> None: - """Update the filtered entity_ids.""" - self._entity_ids_filter = entity_ids + return TrackTemplateResult(template, last_result, result) @callback def _refresh(self, event: Optional[Event]) -> None: - entity_id = event and event.data.get(ATTR_ENTITY_ID) updates = [] info_changed = False - - if entity_id and entity_id in self._entity_ids_filter: - # Skip self-referencing updates - for track_template_ in self._track_templates: - _LOGGER.warning( - "Template loop detected while processing event: %s, skipping template render for Template[%s]", - event, - track_template_.template.template, - ) - return + now = dt_util.utcnow() for track_template_ in self._track_templates: - template = track_template_.template - if ( - entity_id - and len(self._last_info) > 1 - and not self._last_info[template].filter_lifecycle(entity_id) - ): + update = self._render_template_if_ready(track_template_, now, event) + if not update: continue - self._info[template] = template.async_render_to_info( - track_template_.variables - ) info_changed = True - - try: - result: Union[str, TemplateError] = self._info[template].result() - except TemplateError as ex: - result = ex - - last_result = self._last_result.get(template) - - # Check to see if the result has changed - if result == last_result: - continue - - if isinstance(result, TemplateError) and isinstance( - last_result, TemplateError - ): - continue - - updates.append(TrackTemplateResult(template, last_result, result)) + if isinstance(update, TrackTemplateResult): + updates.append(update) if info_changed: - self._update_listeners() - self._last_info = self._info.copy() + assert self._track_state_changes + self._track_state_changes.async_update_listeners( + _render_infos_to_track_states(self._info.values()), + ) + _LOGGER.debug( + "Template group %s listens for %s", + self._track_templates, + self.listeners, + ) if not updates: return @@ -814,6 +894,7 @@ def async_track_template_result( hass: HomeAssistant, track_templates: Iterable[TrackTemplate], action: TrackTemplateResultListener, + raise_on_template_error: bool = False, ) -> _TrackTemplateResultInfo: """Add a listener that fires when a the result of a template changes. @@ -835,9 +916,13 @@ def async_track_template_result( Home assistant object. track_templates An iterable of TrackTemplate. - action Callable to call with results. + raise_on_template_error + When set to True, if there is an exception + processing the template during setup, the system + will raise the exception instead of setting up + tracking. Returns ------- @@ -845,7 +930,7 @@ def async_track_template_result( """ tracker = _TrackTemplateResultInfo(hass, track_templates, action) - tracker.async_setup() + tracker.async_setup(raise_on_template_error) return tracker @@ -1232,7 +1317,10 @@ def process_state_match( return lambda state: state in parameter_set -def _entities_domains_from_info(render_infos: Iterable[RenderInfo]) -> Tuple[Set, Set]: +@callback +def _entities_domains_from_render_infos( + render_infos: Iterable[RenderInfo], +) -> Tuple[Set, Set]: """Combine from multiple RenderInfo.""" entities = set() domains = set() @@ -1242,4 +1330,68 @@ def _entities_domains_from_info(render_infos: Iterable[RenderInfo]) -> Tuple[Set entities.update(render_info.entities) if render_info.domains: domains.update(render_info.domains) + if render_info.domains_lifecycle: + domains.update(render_info.domains_lifecycle) return entities, domains + + +@callback +def _render_infos_needs_all_listener(render_infos: Iterable[RenderInfo]) -> bool: + """Determine if an all listener is needed from RenderInfo.""" + for render_info in render_infos: + # Tracking all states + if render_info.all_states or render_info.all_states_lifecycle: + return True + + # Previous call had an exception + # so we do not know which states + # to track + if render_info.exception: + return True + + return False + + +@callback +def _render_infos_to_track_states(render_infos: Iterable[RenderInfo]) -> TrackStates: + """Create a TrackStates dataclass from the latest RenderInfo.""" + if _render_infos_needs_all_listener(render_infos): + return TrackStates(True, set(), set()) + + return TrackStates(False, *_entities_domains_from_render_infos(render_infos)) + + +@callback +def _event_triggers_rerender(event: Event, info: RenderInfo) -> bool: + """Determine if a template should be re-rendered from an event.""" + entity_id = event.data.get(ATTR_ENTITY_ID) + + if info.filter(entity_id): + return True + + if ( + event.data.get("new_state") is not None + and event.data.get("old_state") is not None + ): + return False + + return bool(info.filter_lifecycle(entity_id)) + + +@callback +def _rate_limit_for_event( + event: Event, info: RenderInfo, track_template_: TrackTemplate +) -> Optional[timedelta]: + """Determine the rate limit for an event.""" + entity_id = event.data.get(ATTR_ENTITY_ID) + + # Specifically referenced entities are excluded + # from the rate limit + if entity_id in info.entities: + return None + + if track_template_.rate_limit is not None: + return track_template_.rate_limit + + rate_limit: Optional[timedelta] = info.rate_limit + return rate_limit diff --git a/homeassistant/helpers/network.py b/homeassistant/helpers/network.py index 8bdfc286c1a4b6..9cff5058a0070e 100644 --- a/homeassistant/helpers/network.py +++ b/homeassistant/helpers/network.py @@ -88,10 +88,12 @@ def get_url( scheme=scheme, host=request_host, port=hass.config.api.port ) - known_hostname = None + known_hostnames = ["localhost"] if hass.components.hassio.is_hassio(): host_info = hass.components.hassio.get_host_info() - known_hostname = f"{host_info['hostname']}.local" + known_hostnames.extend( + [host_info["hostname"], f"{host_info['hostname']}.local"] + ) if ( ( @@ -100,7 +102,7 @@ def get_url( and is_ip_address(request_host) and is_loopback(ip_address(request_host)) ) - or request_host in ["localhost", known_hostname] + or request_host in known_hostnames ) and (not require_ssl or current_url.scheme == "https") and (not require_standard_port or current_url.is_default_port()) diff --git a/homeassistant/helpers/ratelimit.py b/homeassistant/helpers/ratelimit.py new file mode 100644 index 00000000000000..422ebdf2eee7ed --- /dev/null +++ b/homeassistant/helpers/ratelimit.py @@ -0,0 +1,97 @@ +"""Ratelimit helper.""" +import asyncio +from datetime import datetime, timedelta +import logging +from typing import Any, Callable, Dict, Hashable, Optional + +from homeassistant.const import MAX_TIME_TRACKING_ERROR +from homeassistant.core import HomeAssistant, callback +import homeassistant.util.dt as dt_util + +_LOGGER = logging.getLogger(__name__) + + +class KeyedRateLimit: + """Class to track rate limits.""" + + def __init__( + self, + hass: HomeAssistant, + ): + """Initialize ratelimit tracker.""" + self.hass = hass + self._last_triggered: Dict[Hashable, datetime] = {} + self._rate_limit_timers: Dict[Hashable, asyncio.TimerHandle] = {} + + @callback + def async_has_timer(self, key: Hashable) -> bool: + """Check if a rate limit timer is running.""" + return key in self._rate_limit_timers + + @callback + def async_triggered(self, key: Hashable, now: Optional[datetime] = None) -> None: + """Call when the action we are tracking was triggered.""" + self.async_cancel_timer(key) + self._last_triggered[key] = now or dt_util.utcnow() + + @callback + def async_cancel_timer(self, key: Hashable) -> None: + """Cancel a rate limit time that will call the action.""" + if not self.async_has_timer(key): + return + + self._rate_limit_timers.pop(key).cancel() + + @callback + def async_remove(self) -> None: + """Remove all timers.""" + for timer in self._rate_limit_timers.values(): + timer.cancel() + self._rate_limit_timers.clear() + + @callback + def async_schedule_action( + self, + key: Hashable, + rate_limit: Optional[timedelta], + now: datetime, + action: Callable, + *args: Any, + ) -> Optional[datetime]: + """Check rate limits and schedule an action if we hit the limit. + + If the rate limit is hit: + Schedules the action for when the rate limit expires + if there are no pending timers. The action must + be called in async. + + Returns the time the rate limit will expire + + If the rate limit is not hit: + + Return None + """ + if rate_limit is None or key not in self._last_triggered: + return None + + next_call_time = self._last_triggered[key] + rate_limit + + if next_call_time <= now: + self.async_cancel_timer(key) + return None + + _LOGGER.debug( + "Reached rate limit of %s for %s and deferred action until %s", + rate_limit, + key, + next_call_time, + ) + + if key not in self._rate_limit_timers: + self._rate_limit_timers[key] = self.hass.loop.call_later( + (next_call_time - now).total_seconds() + MAX_TIME_TRACKING_ERROR, + action, + *args, + ) + + return next_call_time diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 717e9c3980c3b4..4d958fe431fd90 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -123,30 +123,71 @@ def make_script_schema(schema, default_script_mode, extra=vol.PREVENT_EXTRA): ) +STATIC_VALIDATION_ACTION_TYPES = ( + cv.SCRIPT_ACTION_CALL_SERVICE, + cv.SCRIPT_ACTION_DELAY, + cv.SCRIPT_ACTION_WAIT_TEMPLATE, + cv.SCRIPT_ACTION_FIRE_EVENT, + cv.SCRIPT_ACTION_ACTIVATE_SCENE, + cv.SCRIPT_ACTION_VARIABLES, +) + + +async def async_validate_actions_config( + hass: HomeAssistant, actions: List[ConfigType] +) -> List[ConfigType]: + """Validate a list of actions.""" + return await asyncio.gather( + *[async_validate_action_config(hass, action) for action in actions] + ) + + async def async_validate_action_config( hass: HomeAssistant, config: ConfigType ) -> ConfigType: """Validate config.""" action_type = cv.determine_script_action(config) - if action_type == cv.SCRIPT_ACTION_DEVICE_AUTOMATION: + if action_type in STATIC_VALIDATION_ACTION_TYPES: + pass + + elif action_type == cv.SCRIPT_ACTION_DEVICE_AUTOMATION: platform = await device_automation.async_get_device_automation_platform( hass, config[CONF_DOMAIN], "action" ) config = platform.ACTION_SCHEMA(config) # type: ignore - elif ( - action_type == cv.SCRIPT_ACTION_CHECK_CONDITION - and config[CONF_CONDITION] == "device" - ): - platform = await device_automation.async_get_device_automation_platform( - hass, config[CONF_DOMAIN], "condition" - ) - config = platform.CONDITION_SCHEMA(config) # type: ignore + + elif action_type == cv.SCRIPT_ACTION_CHECK_CONDITION: + if config[CONF_CONDITION] == "device": + platform = await device_automation.async_get_device_automation_platform( + hass, config[CONF_DOMAIN], "condition" + ) + config = platform.CONDITION_SCHEMA(config) # type: ignore + elif action_type == cv.SCRIPT_ACTION_WAIT_FOR_TRIGGER: config[CONF_WAIT_FOR_TRIGGER] = await async_validate_trigger_config( hass, config[CONF_WAIT_FOR_TRIGGER] ) + elif action_type == cv.SCRIPT_ACTION_REPEAT: + config[CONF_SEQUENCE] = await async_validate_actions_config( + hass, config[CONF_REPEAT][CONF_SEQUENCE] + ) + + elif action_type == cv.SCRIPT_ACTION_CHOOSE: + if CONF_DEFAULT in config: + config[CONF_DEFAULT] = await async_validate_actions_config( + hass, config[CONF_DEFAULT] + ) + + for choose_conf in config[CONF_CHOOSE]: + choose_conf[CONF_SEQUENCE] = await async_validate_actions_config( + hass, choose_conf[CONF_SEQUENCE] + ) + + else: + raise ValueError(f"No validation for {action_type}") + return config @@ -850,7 +891,7 @@ def referenced_entities(self): entity_ids = data.get(ATTR_ENTITY_ID) - if entity_ids is None: + if entity_ids is None or isinstance(entity_ids, template.Template): continue if isinstance(entity_ids, str): diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index ad5a36467cfe97..20f7aa2d2d76d7 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -13,6 +13,7 @@ Optional, Set, Tuple, + Union, ) import voluptuous as vol @@ -43,10 +44,9 @@ if TYPE_CHECKING: from homeassistant.helpers.entity import Entity # noqa + from homeassistant.helpers.entity_platform import EntityPlatform -# mypy: allow-untyped-defs, no-check-untyped-defs - CONF_SERVICE_ENTITY_ID = "entity_id" CONF_SERVICE_DATA = "data" CONF_SERVICE_DATA_TEMPLATE = "data_template" @@ -340,7 +340,13 @@ def async_set_service_schema( @bind_hass -async def entity_service_call(hass, platforms, func, call, required_features=None): +async def entity_service_call( + hass: HomeAssistantType, + platforms: Iterable["EntityPlatform"], + func: Union[str, Callable[..., Any]], + call: ha.ServiceCall, + required_features: Optional[Iterable[int]] = None, +) -> None: """Handle an entity service call. Calls all platforms simultaneously. @@ -349,7 +355,9 @@ async def entity_service_call(hass, platforms, func, call, required_features=Non user = await hass.auth.async_get_user(call.context.user_id) if user is None: raise UnknownUser(context=call.context) - entity_perms = user.permissions.check_entity + entity_perms: Optional[ + Callable[[str, str], bool] + ] = user.permissions.check_entity else: entity_perms = None @@ -361,7 +369,7 @@ async def entity_service_call(hass, platforms, func, call, required_features=Non # If the service function is a string, we'll pass it the service call data if isinstance(func, str): - data = { + data: Union[Dict, ha.ServiceCall] = { key: val for key, val in call.data.items() if key not in cv.ENTITY_SERVICE_FIELDS @@ -373,7 +381,7 @@ async def entity_service_call(hass, platforms, func, call, required_features=Non # Check the permissions # A list with entities to call the service on. - entity_candidates = [] + entity_candidates: List["Entity"] = [] if entity_perms is None: for platform in platforms: @@ -435,9 +443,12 @@ async def entity_service_call(hass, platforms, func, call, required_features=Non continue # Skip entities that don't have the required feature. - if required_features is not None and not any( - entity.supported_features & feature_set == feature_set - for feature_set in required_features + if required_features is not None and ( + entity.supported_features is None + or not any( + entity.supported_features & feature_set == feature_set + for feature_set in required_features + ) ): continue @@ -476,12 +487,18 @@ async def entity_service_call(hass, platforms, func, call, required_features=Non future.result() # pop exception if have -async def _handle_entity_call(hass, entity, func, data, context): +async def _handle_entity_call( + hass: HomeAssistantType, + entity: "Entity", + func: Union[str, Callable[..., Any]], + data: Union[Dict, ha.ServiceCall], + context: ha.Context, +) -> None: """Handle calling service method.""" entity.async_set_context(context) if isinstance(func, str): - result = hass.async_add_job(partial(getattr(entity, func), **data)) + result = hass.async_add_job(partial(getattr(entity, func), **data)) # type: ignore else: result = hass.async_add_job(func, entity, data) @@ -495,7 +512,7 @@ async def _handle_entity_call(hass, entity, func, data, context): func, entity.entity_id, ) - await result + await result # type: ignore @bind_hass @@ -530,12 +547,12 @@ async def admin_handler(call: ha.ServiceCall) -> None: def verify_domain_control(hass: HomeAssistantType, domain: str) -> Callable: """Ensure permission to access any entity under domain in service call.""" - def decorator(service_handler: Callable) -> Callable: + def decorator(service_handler: Callable[[ha.ServiceCall], Any]) -> Callable: """Decorate.""" if not asyncio.iscoroutinefunction(service_handler): raise HomeAssistantError("Can only decorate async functions.") - async def check_permissions(call): + async def check_permissions(call: ha.ServiceCall) -> Any: """Check user permission and raise before call if unauthorized.""" if not call.context.user_id: return await service_handler(call) diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index a0512178fdc47b..9c849bee22efb0 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -1,4 +1,5 @@ """Template helper methods for rendering strings with Home Assistant data.""" +import asyncio import base64 import collections.abc from datetime import datetime, timedelta @@ -6,9 +7,10 @@ import json import logging import math +from operator import attrgetter import random import re -from typing import Any, Iterable, List, Optional, Union +from typing import Any, Generator, Iterable, List, Optional, Union from urllib.parse import urlencode as urllib_urlencode import weakref @@ -35,6 +37,7 @@ from homeassistant.loader import bind_hass from homeassistant.util import convert, dt as dt_util, location as loc_util from homeassistant.util.async_ import run_callback_threadsafe +from homeassistant.util.thread import ThreadWithException # mypy: allow-untyped-calls, allow-untyped-defs # mypy: no-check-untyped-defs, no-warn-return-any @@ -58,6 +61,19 @@ _GROUP_DOMAIN_PREFIX = "group." +_COLLECTABLE_STATE_ATTRIBUTES = { + "state", + "attributes", + "last_changed", + "last_updated", + "context", + "domain", + "object_id", + "name", +} + +DEFAULT_RATE_LIMIT = timedelta(minutes=1) + @bind_hass def attach(hass: HomeAssistantType, obj: Any) -> None: @@ -163,6 +179,10 @@ def _true(arg: Any) -> bool: return True +def _false(arg: Any) -> bool: + return False + + class RenderInfo: """Holds information about a template render.""" @@ -171,23 +191,31 @@ def __init__(self, template): self.template = template # Will be set sensibly once frozen. self.filter_lifecycle = _true + self.filter = _true self._result = None self.is_static = False self.exception = None self.all_states = False + self.all_states_lifecycle = False self.domains = set() + self.domains_lifecycle = set() self.entities = set() + self.rate_limit = None - def filter(self, entity_id: str) -> bool: - """Template should re-render if the state changes.""" - return entity_id in self.entities + def __repr__(self) -> str: + """Representation of RenderInfo.""" + return f"" - def _filter_lifecycle(self, entity_id: str) -> bool: - """Template should re-render if the state changes.""" + def _filter_domains_and_entities(self, entity_id: str) -> bool: + """Template should re-render if the entity state changes when we match specific domains or entities.""" return ( split_entity_id(entity_id)[0] in self.domains or entity_id in self.entities ) + def _filter_lifecycle_domains(self, entity_id: str) -> bool: + """Template should re-render if the entity is added or removed with domains watched.""" + return split_entity_id(entity_id)[0] in self.domains_lifecycle + def result(self) -> str: """Results of the template computation.""" if self.exception is not None: @@ -196,21 +224,40 @@ def result(self) -> str: def _freeze_static(self) -> None: self.is_static = True - self.entities = frozenset(self.entities) - self.domains = frozenset(self.domains) + self._freeze_sets() self.all_states = False - def _freeze(self) -> None: + def _freeze_sets(self) -> None: self.entities = frozenset(self.entities) self.domains = frozenset(self.domains) + self.domains_lifecycle = frozenset(self.domains_lifecycle) - if self.all_states or self.exception: + def _freeze(self) -> None: + self._freeze_sets() + + if self.rate_limit is None and ( + self.domains or self.domains_lifecycle or self.all_states or self.exception + ): + # If the template accesses all states or an entire + # domain, and no rate limit is set, we use the default. + self.rate_limit = DEFAULT_RATE_LIMIT + + if self.exception: + return + + if not self.all_states_lifecycle: + if self.domains_lifecycle: + self.filter_lifecycle = self._filter_lifecycle_domains + else: + self.filter_lifecycle = _false + + if self.all_states: return - if not self.domains: - self.filter_lifecycle = self.filter + if self.entities or self.domains: + self.filter = self._filter_domains_and_entities else: - self.filter_lifecycle = self._filter_lifecycle + self.filter = _false class Template: @@ -243,7 +290,7 @@ def ensure_valid(self): try: self._compiled_code = self._env.compile(self.template) - except jinja2.exceptions.TemplateSyntaxError as err: + except jinja2.TemplateError as err: raise TemplateError(err) from err def extract_entities( @@ -286,6 +333,54 @@ def async_render(self, variables: TemplateVarsType = None, **kwargs: Any) -> str except jinja2.TemplateError as err: raise TemplateError(err) from err + async def async_render_will_timeout( + self, timeout: float, variables: TemplateVarsType = None, **kwargs: Any + ) -> bool: + """Check to see if rendering a template will timeout during render. + + This is intended to check for expensive templates + that will make the system unstable. The template + is rendered in the executor to ensure it does not + tie up the event loop. + + This function is not a security control and is only + intended to be used as a safety check when testing + templates. + + This method must be run in the event loop. + """ + assert self.hass + + if self.is_static: + return False + + compiled = self._compiled or self._ensure_compiled() + + if variables is not None: + kwargs.update(variables) + + finish_event = asyncio.Event() + + def _render_template(): + try: + compiled.render(kwargs) + except TimeoutError: + pass + finally: + run_callback_threadsafe(self.hass.loop, finish_event.set) + + try: + template_render_thread = ThreadWithException(target=_render_template) + template_render_thread.start() + await asyncio.wait_for(finish_event.wait(), timeout=timeout) + except asyncio.TimeoutError: + template_render_thread.raise_exc(TimeoutError) + return True + finally: + template_render_thread.join() + + return False + @callback def async_render_to_info( self, variables: TemplateVarsType = None, **kwargs: Any @@ -404,9 +499,7 @@ def __init__(self, hass): def __getattr__(self, name): """Return the domain state.""" if "." in name: - if not valid_entity_id(name): - raise TemplateError(f"Invalid entity ID '{name}'") - return _get_state(self._hass, name) + return _get_state_if_valid(self._hass, name) if name in _RESERVED_NAMES: return None @@ -416,25 +509,29 @@ def __getattr__(self, name): return DomainStates(self._hass, name) + # Jinja will try __getitem__ first and it avoids the need + # to call is_safe_attribute + __getitem__ = __getattr__ + def _collect_all(self) -> None: render_info = self._hass.data.get(_RENDER_INFO) if render_info is not None: render_info.all_states = True + def _collect_all_lifecycle(self) -> None: + render_info = self._hass.data.get(_RENDER_INFO) + if render_info is not None: + render_info.all_states_lifecycle = True + def __iter__(self): """Return all states.""" self._collect_all() - return iter( - _wrap_state(self._hass, state) - for state in sorted( - self._hass.states.async_all(), key=lambda state: state.entity_id - ) - ) + return _state_generator(self._hass, None) def __len__(self) -> int: """Return number of states.""" - self._collect_all() - return len(self._hass.states.async_entity_ids()) + self._collect_all_lifecycle() + return self._hass.states.async_entity_ids_count() def __call__(self, entity_id): """Return the states.""" @@ -456,33 +553,31 @@ def __init__(self, hass, domain): def __getattr__(self, name): """Return the states.""" - entity_id = f"{self._domain}.{name}" - if not valid_entity_id(entity_id): - raise TemplateError(f"Invalid entity ID '{entity_id}'") - return _get_state(self._hass, entity_id) + return _get_state_if_valid(self._hass, f"{self._domain}.{name}") + + # Jinja will try __getitem__ first and it avoids the need + # to call is_safe_attribute + __getitem__ = __getattr__ def _collect_domain(self) -> None: entity_collect = self._hass.data.get(_RENDER_INFO) if entity_collect is not None: entity_collect.domains.add(self._domain) + def _collect_domain_lifecycle(self) -> None: + entity_collect = self._hass.data.get(_RENDER_INFO) + if entity_collect is not None: + entity_collect.domains_lifecycle.add(self._domain) + def __iter__(self): """Return the iteration over all the states.""" self._collect_domain() - return iter( - sorted( - ( - _wrap_state(self._hass, state) - for state in self._hass.states.async_all(self._domain) - ), - key=lambda state: state.entity_id, - ) - ) + return _state_generator(self._hass, self._domain) def __len__(self) -> int: """Return number of states.""" - self._collect_domain() - return len(self._hass.states.async_entity_ids(self._domain)) + self._collect_domain_lifecycle() + return self._hass.states.async_entity_ids_count(self._domain) def __repr__(self) -> str: """Representation of Domain States.""" @@ -492,53 +587,106 @@ def __repr__(self) -> str: class TemplateState(State): """Class to represent a state object in a template.""" + __slots__ = ("_hass", "_state", "_collect") + # Inheritance is done so functions that check against State keep working # pylint: disable=super-init-not-called - def __init__(self, hass, state): + def __init__(self, hass, state, collect=True): """Initialize template state.""" self._hass = hass self._state = state + self._collect = collect + + def _collect_state(self): + if self._collect and _RENDER_INFO in self._hass.data: + self._hass.data[_RENDER_INFO].entities.add(self._state.entity_id) + + # Jinja will try __getitem__ first and it avoids the need + # to call is_safe_attribute + def __getitem__(self, item): + """Return a property as an attribute for jinja.""" + if item in _COLLECTABLE_STATE_ATTRIBUTES: + # _collect_state inlined here for performance + if self._collect and _RENDER_INFO in self._hass.data: + self._hass.data[_RENDER_INFO].entities.add(self._state.entity_id) + return getattr(self._state, item) + if item == "entity_id": + return self._state.entity_id + if item == "state_with_unit": + return self.state_with_unit + raise KeyError + + @property + def entity_id(self): + """Wrap State.entity_id. + + Intentionally does not collect state + """ + return self._state.entity_id + + @property + def state(self): + """Wrap State.state.""" + self._collect_state() + return self._state.state + + @property + def attributes(self): + """Wrap State.attributes.""" + self._collect_state() + return self._state.attributes + + @property + def last_changed(self): + """Wrap State.last_changed.""" + self._collect_state() + return self._state.last_changed + + @property + def last_updated(self): + """Wrap State.last_updated.""" + self._collect_state() + return self._state.last_updated - def _access_state(self): - state = object.__getattribute__(self, "_state") - hass = object.__getattribute__(self, "_hass") - _collect_state(hass, state.entity_id) - return state + @property + def context(self): + """Wrap State.context.""" + self._collect_state() + return self._state.context + + @property + def domain(self): + """Wrap State.domain.""" + self._collect_state() + return self._state.domain + + @property + def object_id(self): + """Wrap State.object_id.""" + self._collect_state() + return self._state.object_id + + @property + def name(self): + """Wrap State.name.""" + self._collect_state() + return self._state.name @property def state_with_unit(self) -> str: """Return the state concatenated with the unit if available.""" - state = object.__getattribute__(self, "_access_state")() - unit = state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) - if unit is None: - return state.state - return f"{state.state} {unit}" + self._collect_state() + unit = self._state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) + return f"{self._state.state} {unit}" if unit else self._state.state def __eq__(self, other: Any) -> bool: """Ensure we collect on equality check.""" - state = object.__getattribute__(self, "_state") - hass = object.__getattribute__(self, "_hass") - _collect_state(hass, state.entity_id) - return super().__eq__(other) - - def __getattribute__(self, name): - """Return an attribute of the state.""" - # This one doesn't count as an access of the state - # since we either found it by looking direct for the ID - # or got it off an iterator. - if name == "entity_id" or name in object.__dict__: - state = object.__getattribute__(self, "_state") - return getattr(state, name) - if name in TemplateState.__dict__: - return object.__getattribute__(self, name) - state = object.__getattribute__(self, "_access_state")() - return getattr(state, name) + self._collect_state() + return self._state.__eq__(other) def __repr__(self) -> str: """Representation of Template State.""" - state = object.__getattribute__(self, "_access_state")() - rep = state.__repr__() - return f"