Merge pull request #58994 from home-assistant/rc

This commit is contained in:
Franck Nijhof 2021-11-03 16:31:23 +01:00 committed by GitHub
commit 85a4ee68e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3000 changed files with 67177 additions and 29980 deletions

View File

@ -29,10 +29,8 @@ omit =
homeassistant/components/ads/* homeassistant/components/ads/*
homeassistant/components/aemet/weather_update_coordinator.py homeassistant/components/aemet/weather_update_coordinator.py
homeassistant/components/aftership/* homeassistant/components/aftership/*
homeassistant/components/agent_dvr/__init__.py
homeassistant/components/agent_dvr/alarm_control_panel.py homeassistant/components/agent_dvr/alarm_control_panel.py
homeassistant/components/agent_dvr/camera.py homeassistant/components/agent_dvr/camera.py
homeassistant/components/agent_dvr/const.py
homeassistant/components/agent_dvr/helpers.py homeassistant/components/agent_dvr/helpers.py
homeassistant/components/airnow/__init__.py homeassistant/components/airnow/__init__.py
homeassistant/components/airnow/sensor.py homeassistant/components/airnow/sensor.py
@ -85,7 +83,6 @@ omit =
homeassistant/components/aurora/binary_sensor.py homeassistant/components/aurora/binary_sensor.py
homeassistant/components/aurora/const.py homeassistant/components/aurora/const.py
homeassistant/components/aurora/sensor.py homeassistant/components/aurora/sensor.py
homeassistant/components/aurora_abb_powerone/sensor.py
homeassistant/components/avea/light.py homeassistant/components/avea/light.py
homeassistant/components/avion/light.py homeassistant/components/avion/light.py
homeassistant/components/azure_devops/__init__.py homeassistant/components/azure_devops/__init__.py
@ -134,6 +131,7 @@ omit =
homeassistant/components/braviatv/remote.py homeassistant/components/braviatv/remote.py
homeassistant/components/broadlink/__init__.py homeassistant/components/broadlink/__init__.py
homeassistant/components/broadlink/const.py homeassistant/components/broadlink/const.py
homeassistant/components/broadlink/light.py
homeassistant/components/broadlink/remote.py homeassistant/components/broadlink/remote.py
homeassistant/components/broadlink/switch.py homeassistant/components/broadlink/switch.py
homeassistant/components/broadlink/updater.py homeassistant/components/broadlink/updater.py
@ -269,7 +267,10 @@ omit =
homeassistant/components/enphase_envoy/__init__.py homeassistant/components/enphase_envoy/__init__.py
homeassistant/components/enphase_envoy/sensor.py homeassistant/components/enphase_envoy/sensor.py
homeassistant/components/entur_public_transport/* homeassistant/components/entur_public_transport/*
homeassistant/components/environment_canada/* homeassistant/components/environment_canada/__init__.py
homeassistant/components/environment_canada/camera.py
homeassistant/components/environment_canada/sensor.py
homeassistant/components/environment_canada/weather.py
homeassistant/components/envirophat/sensor.py homeassistant/components/envirophat/sensor.py
homeassistant/components/envisalink/* homeassistant/components/envisalink/*
homeassistant/components/ephember/climate.py homeassistant/components/ephember/climate.py
@ -290,7 +291,6 @@ omit =
homeassistant/components/esphome/select.py homeassistant/components/esphome/select.py
homeassistant/components/esphome/sensor.py homeassistant/components/esphome/sensor.py
homeassistant/components/esphome/switch.py homeassistant/components/esphome/switch.py
homeassistant/components/essent/sensor.py
homeassistant/components/etherscan/sensor.py homeassistant/components/etherscan/sensor.py
homeassistant/components/eufy/* homeassistant/components/eufy/*
homeassistant/components/everlights/light.py homeassistant/components/everlights/light.py
@ -299,6 +299,7 @@ omit =
homeassistant/components/ezviz/camera.py homeassistant/components/ezviz/camera.py
homeassistant/components/ezviz/coordinator.py homeassistant/components/ezviz/coordinator.py
homeassistant/components/ezviz/const.py homeassistant/components/ezviz/const.py
homeassistant/components/ezviz/entity.py
homeassistant/components/ezviz/binary_sensor.py homeassistant/components/ezviz/binary_sensor.py
homeassistant/components/ezviz/sensor.py homeassistant/components/ezviz/sensor.py
homeassistant/components/ezviz/switch.py homeassistant/components/ezviz/switch.py
@ -331,6 +332,7 @@ omit =
homeassistant/components/fjaraskupan/const.py homeassistant/components/fjaraskupan/const.py
homeassistant/components/fjaraskupan/fan.py homeassistant/components/fjaraskupan/fan.py
homeassistant/components/fjaraskupan/light.py homeassistant/components/fjaraskupan/light.py
homeassistant/components/fjaraskupan/number.py
homeassistant/components/fjaraskupan/sensor.py homeassistant/components/fjaraskupan/sensor.py
homeassistant/components/fleetgo/device_tracker.py homeassistant/components/fleetgo/device_tracker.py
homeassistant/components/flexit/climate.py homeassistant/components/flexit/climate.py
@ -343,7 +345,6 @@ omit =
homeassistant/components/flume/sensor.py homeassistant/components/flume/sensor.py
homeassistant/components/flunearyou/__init__.py homeassistant/components/flunearyou/__init__.py
homeassistant/components/flunearyou/sensor.py homeassistant/components/flunearyou/sensor.py
homeassistant/components/flux_led/light.py
homeassistant/components/folder/sensor.py homeassistant/components/folder/sensor.py
homeassistant/components/folder_watcher/* homeassistant/components/folder_watcher/*
homeassistant/components/foobot/sensor.py homeassistant/components/foobot/sensor.py
@ -457,7 +458,6 @@ omit =
homeassistant/components/hp_ilo/sensor.py homeassistant/components/hp_ilo/sensor.py
homeassistant/components/htu21d/sensor.py homeassistant/components/htu21d/sensor.py
homeassistant/components/huawei_lte/* homeassistant/components/huawei_lte/*
homeassistant/components/huawei_router/device_tracker.py
homeassistant/components/hue/light.py homeassistant/components/hue/light.py
homeassistant/components/hunterdouglas_powerview/__init__.py homeassistant/components/hunterdouglas_powerview/__init__.py
homeassistant/components/hunterdouglas_powerview/scene.py homeassistant/components/hunterdouglas_powerview/scene.py
@ -585,6 +585,11 @@ omit =
homeassistant/components/logi_circle/const.py homeassistant/components/logi_circle/const.py
homeassistant/components/logi_circle/sensor.py homeassistant/components/logi_circle/sensor.py
homeassistant/components/london_underground/sensor.py homeassistant/components/london_underground/sensor.py
homeassistant/components/lookin/__init__.py
homeassistant/components/lookin/entity.py
homeassistant/components/lookin/models.py
homeassistant/components/lookin/sensor.py
homeassistant/components/lookin/climate.py
homeassistant/components/loopenergy/sensor.py homeassistant/components/loopenergy/sensor.py
homeassistant/components/luci/device_tracker.py homeassistant/components/luci/device_tracker.py
homeassistant/components/luftdaten/__init__.py homeassistant/components/luftdaten/__init__.py
@ -599,7 +604,6 @@ omit =
homeassistant/components/lutron_caseta/scene.py homeassistant/components/lutron_caseta/scene.py
homeassistant/components/lutron_caseta/switch.py homeassistant/components/lutron_caseta/switch.py
homeassistant/components/lw12wifi/light.py homeassistant/components/lw12wifi/light.py
homeassistant/components/lyft/sensor.py
homeassistant/components/lyric/__init__.py homeassistant/components/lyric/__init__.py
homeassistant/components/lyric/api.py homeassistant/components/lyric/api.py
homeassistant/components/lyric/climate.py homeassistant/components/lyric/climate.py
@ -649,13 +653,7 @@ omit =
homeassistant/components/mitemp_bt/sensor.py homeassistant/components/mitemp_bt/sensor.py
homeassistant/components/mjpeg/camera.py homeassistant/components/mjpeg/camera.py
homeassistant/components/mochad/* homeassistant/components/mochad/*
homeassistant/components/modbus/base_platform.py
homeassistant/components/modbus/binary_sensor.py
homeassistant/components/modbus/cover.py
homeassistant/components/modbus/climate.py homeassistant/components/modbus/climate.py
homeassistant/components/modbus/modbus.py
homeassistant/components/modbus/sensor.py
homeassistant/components/modbus/validators.py
homeassistant/components/modem_callerid/sensor.py homeassistant/components/modem_callerid/sensor.py
homeassistant/components/motion_blinds/__init__.py homeassistant/components/motion_blinds/__init__.py
homeassistant/components/motion_blinds/const.py homeassistant/components/motion_blinds/const.py
@ -703,7 +701,6 @@ omit =
homeassistant/components/neato/switch.py homeassistant/components/neato/switch.py
homeassistant/components/neato/vacuum.py homeassistant/components/neato/vacuum.py
homeassistant/components/nederlandse_spoorwegen/sensor.py homeassistant/components/nederlandse_spoorwegen/sensor.py
homeassistant/components/nello/lock.py
homeassistant/components/nest/legacy/* homeassistant/components/nest/legacy/*
homeassistant/components/netdata/sensor.py homeassistant/components/netdata/sensor.py
homeassistant/components/netgear/__init__.py homeassistant/components/netgear/__init__.py
@ -734,11 +731,10 @@ omit =
homeassistant/components/nuki/const.py homeassistant/components/nuki/const.py
homeassistant/components/nuki/binary_sensor.py homeassistant/components/nuki/binary_sensor.py
homeassistant/components/nuki/lock.py homeassistant/components/nuki/lock.py
homeassistant/components/nut/sensor.py
homeassistant/components/nx584/alarm_control_panel.py homeassistant/components/nx584/alarm_control_panel.py
homeassistant/components/nzbget/coordinator.py homeassistant/components/nzbget/coordinator.py
homeassistant/components/obihai/* homeassistant/components/obihai/*
homeassistant/components/octoprint/* homeassistant/components/octoprint/__init__.py
homeassistant/components/oem/climate.py homeassistant/components/oem/climate.py
homeassistant/components/oasa_telematics/sensor.py homeassistant/components/oasa_telematics/sensor.py
homeassistant/components/ohmconnect/sensor.py homeassistant/components/ohmconnect/sensor.py
@ -765,7 +761,10 @@ omit =
homeassistant/components/openevse/sensor.py homeassistant/components/openevse/sensor.py
homeassistant/components/openexchangerates/sensor.py homeassistant/components/openexchangerates/sensor.py
homeassistant/components/opengarage/__init__.py homeassistant/components/opengarage/__init__.py
homeassistant/components/opengarage/binary_sensor.py
homeassistant/components/opengarage/cover.py homeassistant/components/opengarage/cover.py
homeassistant/components/opengarage/entity.py
homeassistant/components/opengarage/sensor.py
homeassistant/components/openhome/__init__.py homeassistant/components/openhome/__init__.py
homeassistant/components/openhome/media_player.py homeassistant/components/openhome/media_player.py
homeassistant/components/openhome/const.py homeassistant/components/openhome/const.py
@ -781,7 +780,6 @@ omit =
homeassistant/components/openweathermap/sensor.py homeassistant/components/openweathermap/sensor.py
homeassistant/components/openweathermap/weather.py homeassistant/components/openweathermap/weather.py
homeassistant/components/openweathermap/weather_update_coordinator.py homeassistant/components/openweathermap/weather_update_coordinator.py
homeassistant/components/openweathermap/abstract_owm_sensor.py
homeassistant/components/opnsense/* homeassistant/components/opnsense/*
homeassistant/components/opple/light.py homeassistant/components/opple/light.py
homeassistant/components/orangepi_gpio/* homeassistant/components/orangepi_gpio/*
@ -907,6 +905,7 @@ omit =
homeassistant/components/screenlogic/__init__.py homeassistant/components/screenlogic/__init__.py
homeassistant/components/screenlogic/binary_sensor.py homeassistant/components/screenlogic/binary_sensor.py
homeassistant/components/screenlogic/climate.py homeassistant/components/screenlogic/climate.py
homeassistant/components/screenlogic/light.py
homeassistant/components/screenlogic/sensor.py homeassistant/components/screenlogic/sensor.py
homeassistant/components/screenlogic/services.py homeassistant/components/screenlogic/services.py
homeassistant/components/screenlogic/switch.py homeassistant/components/screenlogic/switch.py
@ -1002,7 +1001,8 @@ omit =
homeassistant/components/starlingbank/sensor.py homeassistant/components/starlingbank/sensor.py
homeassistant/components/steam_online/sensor.py homeassistant/components/steam_online/sensor.py
homeassistant/components/stiebel_eltron/* homeassistant/components/stiebel_eltron/*
homeassistant/components/stookalert/* homeassistant/components/stookalert/__init__.py
homeassistant/components/stookalert/binary_sensor.py
homeassistant/components/stream/* homeassistant/components/stream/*
homeassistant/components/streamlabswater/* homeassistant/components/streamlabswater/*
homeassistant/components/suez_water/* homeassistant/components/suez_water/*
@ -1106,7 +1106,14 @@ omit =
homeassistant/components/tractive/entity.py homeassistant/components/tractive/entity.py
homeassistant/components/tractive/sensor.py homeassistant/components/tractive/sensor.py
homeassistant/components/tractive/switch.py homeassistant/components/tractive/switch.py
homeassistant/components/tradfri/* homeassistant/components/tradfri/__init__.py
homeassistant/components/tradfri/base_class.py
homeassistant/components/tradfri/config_flow.py
homeassistant/components/tradfri/cover.py
homeassistant/components/tradfri/fan.py
homeassistant/components/tradfri/light.py
homeassistant/components/tradfri/sensor.py
homeassistant/components/tradfri/switch.py
homeassistant/components/trafikverket_train/sensor.py homeassistant/components/trafikverket_train/sensor.py
homeassistant/components/trafikverket_weatherstation/sensor.py homeassistant/components/trafikverket_weatherstation/sensor.py
homeassistant/components/transmission/sensor.py homeassistant/components/transmission/sensor.py
@ -1116,12 +1123,22 @@ omit =
homeassistant/components/travisci/sensor.py homeassistant/components/travisci/sensor.py
homeassistant/components/tuya/__init__.py homeassistant/components/tuya/__init__.py
homeassistant/components/tuya/base.py homeassistant/components/tuya/base.py
homeassistant/components/tuya/binary_sensor.py
homeassistant/components/tuya/camera.py
homeassistant/components/tuya/climate.py homeassistant/components/tuya/climate.py
homeassistant/components/tuya/const.py homeassistant/components/tuya/const.py
homeassistant/components/tuya/cover.py
homeassistant/components/tuya/fan.py homeassistant/components/tuya/fan.py
homeassistant/components/tuya/humidifier.py
homeassistant/components/tuya/light.py homeassistant/components/tuya/light.py
homeassistant/components/tuya/number.py
homeassistant/components/tuya/scene.py homeassistant/components/tuya/scene.py
homeassistant/components/tuya/select.py
homeassistant/components/tuya/sensor.py
homeassistant/components/tuya/siren.py
homeassistant/components/tuya/switch.py homeassistant/components/tuya/switch.py
homeassistant/components/tuya/util.py
homeassistant/components/tuya/vacuum.py
homeassistant/components/twentemilieu/const.py homeassistant/components/twentemilieu/const.py
homeassistant/components/twentemilieu/sensor.py homeassistant/components/twentemilieu/sensor.py
homeassistant/components/twilio_call/notify.py homeassistant/components/twilio_call/notify.py
@ -1151,6 +1168,7 @@ omit =
homeassistant/components/velbus/sensor.py homeassistant/components/velbus/sensor.py
homeassistant/components/velbus/switch.py homeassistant/components/velbus/switch.py
homeassistant/components/velux/* homeassistant/components/velux/*
homeassistant/components/venstar/__init__.py
homeassistant/components/venstar/climate.py homeassistant/components/venstar/climate.py
homeassistant/components/verisure/__init__.py homeassistant/components/verisure/__init__.py
homeassistant/components/verisure/alarm_control_panel.py homeassistant/components/verisure/alarm_control_panel.py
@ -1174,6 +1192,7 @@ omit =
homeassistant/components/vilfo/const.py homeassistant/components/vilfo/const.py
homeassistant/components/vivotek/camera.py homeassistant/components/vivotek/camera.py
homeassistant/components/vlc/media_player.py homeassistant/components/vlc/media_player.py
homeassistant/components/vlc_telnet/__init__.py
homeassistant/components/vlc_telnet/media_player.py homeassistant/components/vlc_telnet/media_player.py
homeassistant/components/volkszaehler/sensor.py homeassistant/components/volkszaehler/sensor.py
homeassistant/components/volumio/__init__.py homeassistant/components/volumio/__init__.py
@ -1193,7 +1212,6 @@ omit =
homeassistant/components/webostv/* homeassistant/components/webostv/*
homeassistant/components/whois/sensor.py homeassistant/components/whois/sensor.py
homeassistant/components/wiffi/* homeassistant/components/wiffi/*
homeassistant/components/wink/*
homeassistant/components/wirelesstag/* homeassistant/components/wirelesstag/*
homeassistant/components/wolflink/__init__.py homeassistant/components/wolflink/__init__.py
homeassistant/components/wolflink/sensor.py homeassistant/components/wolflink/sensor.py
@ -1236,7 +1254,6 @@ omit =
homeassistant/components/xiaomi_miio/select.py homeassistant/components/xiaomi_miio/select.py
homeassistant/components/xiaomi_miio/sensor.py homeassistant/components/xiaomi_miio/sensor.py
homeassistant/components/xiaomi_miio/switch.py homeassistant/components/xiaomi_miio/switch.py
homeassistant/components/xiaomi_miio/vacuum.py
homeassistant/components/xiaomi_tv/media_player.py homeassistant/components/xiaomi_tv/media_player.py
homeassistant/components/xmpp/notify.py homeassistant/components/xmpp/notify.py
homeassistant/components/xs1/* homeassistant/components/xs1/*

View File

@ -15,7 +15,7 @@ body:
attributes: attributes:
label: The problem label: The problem
description: >- description: >-
Describe the issue you are experiencing here to communicate to the Describe the issue you are experiencing here, to communicate to the
maintainers. Tell us what you were trying to do and what happened. maintainers. Tell us what you were trying to do and what happened.
Provide a clear and concise description of what the problem is. Provide a clear and concise description of what the problem is.
@ -28,10 +28,12 @@ body:
validations: validations:
required: true required: true
attributes: attributes:
label: What is version of Home Assistant Core has the issue? label: What version of Home Assistant Core has the issue?
placeholder: core- placeholder: core-
description: > description: >
Can be found in the Configuration panel -> Info. Can be found in: [Configuration panel -> Info](https://my.home-assistant.io/redirect/info/).
[![Open your Home Assistant instance and show your Home Assistant version information.](https://my.home-assistant.io/badges/info.svg)](https://my.home-assistant.io/redirect/info/)
- type: input - type: input
attributes: attributes:
label: What was the last working version of Home Assistant Core? label: What was the last working version of Home Assistant Core?
@ -44,7 +46,9 @@ body:
attributes: attributes:
label: What type of installation are you running? label: What type of installation are you running?
description: > description: >
If you don't know, you can find it in: Configuration panel -> Info. Can be found in: [Configuration panel -> Info](https://my.home-assistant.io/redirect/info/).
[![Open your Home Assistant instance and show your Home Assistant version information.](https://my.home-assistant.io/badges/info.svg)](https://my.home-assistant.io/redirect/info/)
options: options:
- Home Assistant OS - Home Assistant OS
- Home Assistant Container - Home Assistant Container
@ -55,15 +59,15 @@ body:
attributes: attributes:
label: Integration causing the issue label: Integration causing the issue
description: > description: >
The name of the integration, for example, Automation or Philips Hue. The name of the integration. For example: Automation, Philips Hue
- type: input - type: input
id: integration_link id: integration_link
attributes: attributes:
label: Link to integration documentation on our website label: Link to integration documentation on our website
placeholder: "https://www.home-assistant.io/integrations/..." placeholder: "https://www.home-assistant.io/integrations/..."
description: | description: |
Providing a link [to the documentation][docs] help us categorizing the Providing a link [to the documentation][docs] helps us categorize the
issue, while providing a useful reference at the same time. issue, while also providing a useful reference for others.
[docs]: https://www.home-assistant.io/integrations [docs]: https://www.home-assistant.io/integrations
@ -75,8 +79,8 @@ body:
attributes: attributes:
label: Example YAML snippet label: Example YAML snippet
description: | description: |
If this issue has an example piece of YAML that can help reproducing this problem, please provide. If applicable, please provide an example piece of YAML that can help reproduce this problem.
This can be an piece of YAML from, e.g., an automation, script, scene or configuration. This can be from an automation, script, scene or configuration.
render: yaml render: yaml
- type: textarea - type: textarea
attributes: attributes:
@ -88,5 +92,3 @@ body:
label: Additional information label: Additional information
description: > description: >
If you have any additional information for us, use the field below. If you have any additional information for us, use the field below.
Please note, you can attach screenshots or screen recordings here, by
dragging and dropping files in the field below.

View File

@ -23,7 +23,7 @@ jobs:
publish: ${{ steps.version.outputs.publish }} publish: ${{ steps.version.outputs.publish }}
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.5
with: with:
fetch-depth: 0 fetch-depth: 0
@ -67,7 +67,7 @@ jobs:
if: needs.init.outputs.publish == 'true' if: needs.init.outputs.publish == 'true'
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.5
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2 uses: actions/setup-python@v2.2.2
@ -97,7 +97,7 @@ jobs:
arch: ${{ fromJson(needs.init.outputs.architectures) }} arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.5
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
if: needs.init.outputs.channel == 'dev' if: needs.init.outputs.channel == 'dev'
@ -170,7 +170,7 @@ jobs:
- tinker - tinker
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.5
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v1.10.0 uses: docker/login-action@v1.10.0
@ -201,7 +201,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.5
- name: Initialize git - name: Initialize git
uses: home-assistant/actions/helpers/git-init@master uses: home-assistant/actions/helpers/git-init@master
@ -233,7 +233,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.5
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v1.10.0 uses: docker/login-action@v1.10.0

View File

@ -26,7 +26,7 @@ jobs:
pre-commit-key: ${{ steps.generate-pre-commit-key.outputs.key }} pre-commit-key: ${{ steps.generate-pre-commit-key.outputs.key }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.5
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python id: python
uses: actions/setup-python@v2.2.2 uses: actions/setup-python@v2.2.2
@ -84,7 +84,7 @@ jobs:
needs: prepare-base needs: prepare-base
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.5
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2 uses: actions/setup-python@v2.2.2
id: python id: python
@ -124,7 +124,7 @@ jobs:
needs: prepare-base needs: prepare-base
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.5
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2 uses: actions/setup-python@v2.2.2
id: python id: python
@ -164,7 +164,7 @@ jobs:
needs: prepare-base needs: prepare-base
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.5
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2 uses: actions/setup-python@v2.2.2
id: python id: python
@ -207,7 +207,7 @@ jobs:
needs: prepare-base needs: prepare-base
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.5
- name: Register hadolint problem matcher - name: Register hadolint problem matcher
run: | run: |
echo "::add-matcher::.github/workflows/matchers/hadolint.json" echo "::add-matcher::.github/workflows/matchers/hadolint.json"
@ -226,7 +226,7 @@ jobs:
needs: prepare-base needs: prepare-base
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.5
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2 uses: actions/setup-python@v2.2.2
id: python id: python
@ -269,7 +269,7 @@ jobs:
needs: prepare-base needs: prepare-base
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.5
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2 uses: actions/setup-python@v2.2.2
id: python id: python
@ -312,7 +312,7 @@ jobs:
needs: prepare-base needs: prepare-base
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.5
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2 uses: actions/setup-python@v2.2.2
id: python id: python
@ -352,7 +352,7 @@ jobs:
needs: prepare-base needs: prepare-base
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.5
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2 uses: actions/setup-python@v2.2.2
id: python id: python
@ -395,7 +395,7 @@ jobs:
needs: prepare-base needs: prepare-base
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.5
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2 uses: actions/setup-python@v2.2.2
id: python id: python
@ -436,7 +436,7 @@ jobs:
# needs: prepare-base # needs: prepare-base
# steps: # steps:
# - name: Check out code from GitHub # - name: Check out code from GitHub
# uses: actions/checkout@v2.3.4 # uses: actions/checkout@v2.3.5
# - name: Run ShellCheck # - name: Run ShellCheck
# uses: ludeeus/action-shellcheck@0.3.0 # uses: ludeeus/action-shellcheck@0.3.0
@ -446,7 +446,7 @@ jobs:
needs: prepare-base needs: prepare-base
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.5
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2 uses: actions/setup-python@v2.2.2
id: python id: python
@ -493,7 +493,7 @@ jobs:
container: homeassistant/ci-azure:${{ matrix.python-version }} container: homeassistant/ci-azure:${{ matrix.python-version }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.5
- name: Restore full Python ${{ matrix.python-version }} virtual environment - name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.6
@ -517,7 +517,7 @@ jobs:
needs: prepare-base needs: prepare-base
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.5
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2 uses: actions/setup-python@v2.2.2
id: python id: python
@ -551,7 +551,7 @@ jobs:
container: homeassistant/ci-azure:${{ matrix.python-version }} container: homeassistant/ci-azure:${{ matrix.python-version }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.5
- name: Generate partial Python venv restore key - name: Generate partial Python venv restore key
id: generate-python-key id: generate-python-key
run: >- run: >-
@ -595,7 +595,7 @@ jobs:
container: homeassistant/ci-azure:${{ matrix.python-version }} container: homeassistant/ci-azure:${{ matrix.python-version }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.5
- name: Restore full Python ${{ matrix.python-version }} virtual environment - name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.6
@ -626,7 +626,7 @@ jobs:
container: homeassistant/ci-azure:${{ matrix.python-version }} container: homeassistant/ci-azure:${{ matrix.python-version }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.5
- name: Restore full Python ${{ matrix.python-version }} virtual environment - name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.6
@ -660,7 +660,7 @@ jobs:
container: homeassistant/ci-azure:${{ matrix.python-version }} container: homeassistant/ci-azure:${{ matrix.python-version }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.5
- name: Restore full Python ${{ matrix.python-version }} virtual environment - name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.6
@ -682,11 +682,11 @@ jobs:
# Ideally this should be part of our dependencies # Ideally this should be part of our dependencies
# However this plugin is fairly new and doesn't run correctly # However this plugin is fairly new and doesn't run correctly
# on a non-GitHub environment. # on a non-GitHub environment.
pip install pytest-github-actions-annotate-failures pip install pytest-github-actions-annotate-failures==0.1.3
- name: Run pytest - name: Run pytest
run: | run: |
. venv/bin/activate . venv/bin/activate
pytest \ python3 -X dev -bb -m pytest \
-qq \ -qq \
--timeout=9 \ --timeout=9 \
--durations=10 \ --durations=10 \
@ -718,7 +718,7 @@ jobs:
container: homeassistant/ci-azure:${{ matrix.python-version }} container: homeassistant/ci-azure:${{ matrix.python-version }}
steps: steps:
- name: Check out code from GitHub - name: Check out code from GitHub
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.5
- name: Restore full Python ${{ matrix.python-version }} virtual environment - name: Restore full Python ${{ matrix.python-version }} virtual environment
id: cache-venv id: cache-venv
uses: actions/cache@v2.1.6 uses: actions/cache@v2.1.6

View File

@ -20,7 +20,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2 uses: actions/checkout@v2.3.5
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2 uses: actions/setup-python@v2.2.2
@ -39,7 +39,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2 uses: actions/checkout@v2.3.5
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2 uses: actions/setup-python@v2.2.2

View File

@ -21,7 +21,7 @@ jobs:
architectures: ${{ steps.info.outputs.architectures }} architectures: ${{ steps.info.outputs.architectures }}
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.5
- name: Get information - name: Get information
id: info id: info
@ -68,7 +68,7 @@ jobs:
- "3.9-alpine3.14" - "3.9-alpine3.14"
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.5
- name: Download env_file - name: Download env_file
uses: actions/download-artifact@v2 uses: actions/download-artifact@v2
@ -108,7 +108,7 @@ jobs:
- "3.9-alpine3.14" - "3.9-alpine3.14"
steps: steps:
- name: Checkout the repository - name: Checkout the repository
uses: actions/checkout@v2.3.4 uses: actions/checkout@v2.3.5
- name: Download env_file - name: Download env_file
uses: actions/download-artifact@v2 uses: actions/download-artifact@v2

View File

@ -22,17 +22,17 @@ repos:
- --quiet-level=2 - --quiet-level=2
exclude_types: [csv, json] exclude_types: [csv, json]
exclude: ^tests/fixtures/ exclude: ^tests/fixtures/
- repo: https://gitlab.com/pycqa/flake8 - repo: https://github.com/PyCQA/flake8
rev: 3.9.2 rev: 4.0.1
hooks: hooks:
- id: flake8 - id: flake8
additional_dependencies: additional_dependencies:
- pycodestyle==2.7.0 - pycodestyle==2.8.0
- pyflakes==2.3.1 - pyflakes==2.4.0
- flake8-docstrings==1.6.0 - flake8-docstrings==1.6.0
- pydocstyle==6.0.0 - pydocstyle==6.1.1
- flake8-comprehensions==3.5.0 - flake8-comprehensions==3.7.0
- flake8-noqa==1.1.0 - flake8-noqa==1.2.0
- mccabe==0.6.1 - mccabe==0.6.1
files: ^(homeassistant|script|tests)/.+\.py$ files: ^(homeassistant|script|tests)/.+\.py$
- repo: https://github.com/PyCQA/bandit - repo: https://github.com/PyCQA/bandit

View File

@ -20,6 +20,7 @@ homeassistant.components.ampio.*
homeassistant.components.automation.* homeassistant.components.automation.*
homeassistant.components.binary_sensor.* homeassistant.components.binary_sensor.*
homeassistant.components.bluetooth_tracker.* homeassistant.components.bluetooth_tracker.*
homeassistant.components.bmw_connected_drive.*
homeassistant.components.bond.* homeassistant.components.bond.*
homeassistant.components.braviatv.* homeassistant.components.braviatv.*
homeassistant.components.brother.* homeassistant.components.brother.*
@ -35,18 +36,21 @@ homeassistant.components.dlna_dmr.*
homeassistant.components.dnsip.* homeassistant.components.dnsip.*
homeassistant.components.dsmr.* homeassistant.components.dsmr.*
homeassistant.components.dunehd.* homeassistant.components.dunehd.*
homeassistant.components.efergy.*
homeassistant.components.elgato.* homeassistant.components.elgato.*
homeassistant.components.esphome.* homeassistant.components.esphome.*
homeassistant.components.energy.* homeassistant.components.energy.*
homeassistant.components.fastdotcom.* homeassistant.components.fastdotcom.*
homeassistant.components.fitbit.* homeassistant.components.fitbit.*
homeassistant.components.flunearyou.* homeassistant.components.flunearyou.*
homeassistant.components.flux_led.*
homeassistant.components.forecast_solar.* homeassistant.components.forecast_solar.*
homeassistant.components.fritzbox.* homeassistant.components.fritzbox.*
homeassistant.components.frontend.* homeassistant.components.frontend.*
homeassistant.components.fritz.* homeassistant.components.fritz.*
homeassistant.components.geo_location.* homeassistant.components.geo_location.*
homeassistant.components.gios.* homeassistant.components.gios.*
homeassistant.components.goalzero.*
homeassistant.components.group.* homeassistant.components.group.*
homeassistant.components.guardian.* homeassistant.components.guardian.*
homeassistant.components.history.* homeassistant.components.history.*
@ -55,23 +59,30 @@ homeassistant.components.http.*
homeassistant.components.huawei_lte.* homeassistant.components.huawei_lte.*
homeassistant.components.hyperion.* homeassistant.components.hyperion.*
homeassistant.components.image_processing.* homeassistant.components.image_processing.*
homeassistant.components.input_select.*
homeassistant.components.integration.* homeassistant.components.integration.*
homeassistant.components.iqvia.* homeassistant.components.iqvia.*
homeassistant.components.jewish_calendar.*
homeassistant.components.knx.* homeassistant.components.knx.*
homeassistant.components.kraken.* homeassistant.components.kraken.*
homeassistant.components.lcn.* homeassistant.components.lcn.*
homeassistant.components.light.* homeassistant.components.light.*
homeassistant.components.local_ip.* homeassistant.components.local_ip.*
homeassistant.components.lock.* homeassistant.components.lock.*
homeassistant.components.lookin.*
homeassistant.components.mailbox.* homeassistant.components.mailbox.*
homeassistant.components.media_player.* homeassistant.components.media_player.*
homeassistant.components.modbus.* homeassistant.components.modbus.*
homeassistant.components.modem_callerid.*
homeassistant.components.media_source.*
homeassistant.components.mysensors.* homeassistant.components.mysensors.*
homeassistant.components.nam.* homeassistant.components.nam.*
homeassistant.components.nanoleaf.*
homeassistant.components.neato.* homeassistant.components.neato.*
homeassistant.components.nest.* homeassistant.components.nest.*
homeassistant.components.netatmo.* homeassistant.components.netatmo.*
homeassistant.components.network.* homeassistant.components.network.*
homeassistant.components.nfandroidtv.*
homeassistant.components.no_ip.* homeassistant.components.no_ip.*
homeassistant.components.notify.* homeassistant.components.notify.*
homeassistant.components.notion.* homeassistant.components.notion.*
@ -89,6 +100,7 @@ homeassistant.components.recorder.statistics
homeassistant.components.remote.* homeassistant.components.remote.*
homeassistant.components.renault.* homeassistant.components.renault.*
homeassistant.components.rituals_perfume_genie.* homeassistant.components.rituals_perfume_genie.*
homeassistant.components.rpi_power.*
homeassistant.components.samsungtv.* homeassistant.components.samsungtv.*
homeassistant.components.scene.* homeassistant.components.scene.*
homeassistant.components.select.* homeassistant.components.select.*
@ -98,6 +110,7 @@ homeassistant.components.simplisafe.*
homeassistant.components.slack.* homeassistant.components.slack.*
homeassistant.components.sonos.media_player homeassistant.components.sonos.media_player
homeassistant.components.ssdp.* homeassistant.components.ssdp.*
homeassistant.components.stookalert.*
homeassistant.components.stream.* homeassistant.components.stream.*
homeassistant.components.sun.* homeassistant.components.sun.*
homeassistant.components.surepetcare.* homeassistant.components.surepetcare.*
@ -110,6 +123,7 @@ homeassistant.components.tautulli.*
homeassistant.components.tcp.* homeassistant.components.tcp.*
homeassistant.components.tile.* homeassistant.components.tile.*
homeassistant.components.tplink.* homeassistant.components.tplink.*
homeassistant.components.tractive.*
homeassistant.components.tradfri.* homeassistant.components.tradfri.*
homeassistant.components.tts.* homeassistant.components.tts.*
homeassistant.components.upcloud.* homeassistant.components.upcloud.*
@ -118,6 +132,7 @@ homeassistant.components.uptimerobot.*
homeassistant.components.vacuum.* homeassistant.components.vacuum.*
homeassistant.components.vallox.* homeassistant.components.vallox.*
homeassistant.components.water_heater.* homeassistant.components.water_heater.*
homeassistant.components.watttime.*
homeassistant.components.weather.* homeassistant.components.weather.*
homeassistant.components.websocket_api.* homeassistant.components.websocket_api.*
homeassistant.components.zodiac.* homeassistant.components.zodiac.*

View File

@ -75,10 +75,10 @@ homeassistant/components/blink/* @fronzbot
homeassistant/components/blueprint/* @home-assistant/core homeassistant/components/blueprint/* @home-assistant/core
homeassistant/components/bmp280/* @belidzs homeassistant/components/bmp280/* @belidzs
homeassistant/components/bmw_connected_drive/* @gerard33 @rikroe homeassistant/components/bmw_connected_drive/* @gerard33 @rikroe
homeassistant/components/bond/* @prystupa @joshs85 homeassistant/components/bond/* @bdraco @prystupa @joshs85
homeassistant/components/bosch_shc/* @tschamm homeassistant/components/bosch_shc/* @tschamm
homeassistant/components/braviatv/* @bieniu @Drafteed homeassistant/components/braviatv/* @bieniu @Drafteed
homeassistant/components/broadlink/* @danielhiversen @felipediel homeassistant/components/broadlink/* @danielhiversen @felipediel @L-I-Am
homeassistant/components/brother/* @bieniu homeassistant/components/brother/* @bieniu
homeassistant/components/brunt/* @eavanvalkenburg homeassistant/components/brunt/* @eavanvalkenburg
homeassistant/components/bsblan/* @liudger homeassistant/components/bsblan/* @liudger
@ -114,7 +114,7 @@ homeassistant/components/debugpy/* @frenck
homeassistant/components/deconz/* @Kane610 homeassistant/components/deconz/* @Kane610
homeassistant/components/delijn/* @bollewolle @Emilv2 homeassistant/components/delijn/* @bollewolle @Emilv2
homeassistant/components/demo/* @home-assistant/core homeassistant/components/demo/* @home-assistant/core
homeassistant/components/denonavr/* @scarface-4711 @starkillerOG homeassistant/components/denonavr/* @ol-iver @starkillerOG
homeassistant/components/derivative/* @afaucogney homeassistant/components/derivative/* @afaucogney
homeassistant/components/device_automation/* @home-assistant/core homeassistant/components/device_automation/* @home-assistant/core
homeassistant/components/devolo_home_control/* @2Fake @Shutgun homeassistant/components/devolo_home_control/* @2Fake @Shutgun
@ -138,7 +138,7 @@ homeassistant/components/ecovacs/* @OverloadUT
homeassistant/components/edl21/* @mtdcr homeassistant/components/edl21/* @mtdcr
homeassistant/components/efergy/* @tkdrob homeassistant/components/efergy/* @tkdrob
homeassistant/components/egardia/* @jeroenterheerdt homeassistant/components/egardia/* @jeroenterheerdt
homeassistant/components/eight_sleep/* @mezz64 homeassistant/components/eight_sleep/* @mezz64 @raman325
homeassistant/components/elgato/* @frenck homeassistant/components/elgato/* @frenck
homeassistant/components/elkm1/* @gwww @bdraco homeassistant/components/elkm1/* @gwww @bdraco
homeassistant/components/elv/* @majuss homeassistant/components/elv/* @majuss
@ -151,13 +151,12 @@ homeassistant/components/enigma2/* @fbradyirl
homeassistant/components/enocean/* @bdurrer homeassistant/components/enocean/* @bdurrer
homeassistant/components/enphase_envoy/* @gtdiehl homeassistant/components/enphase_envoy/* @gtdiehl
homeassistant/components/entur_public_transport/* @hfurubotten homeassistant/components/entur_public_transport/* @hfurubotten
homeassistant/components/environment_canada/* @michaeldavie homeassistant/components/environment_canada/* @gwww @michaeldavie
homeassistant/components/ephember/* @ttroy50 homeassistant/components/ephember/* @ttroy50
homeassistant/components/epson/* @pszafer homeassistant/components/epson/* @pszafer
homeassistant/components/epsonworkforce/* @ThaStealth homeassistant/components/epsonworkforce/* @ThaStealth
homeassistant/components/eq3btsmart/* @rytilahti homeassistant/components/eq3btsmart/* @rytilahti
homeassistant/components/esphome/* @OttoWinter @jesserockz homeassistant/components/esphome/* @OttoWinter @jesserockz
homeassistant/components/essent/* @TheLastProject
homeassistant/components/evohome/* @zxdavb homeassistant/components/evohome/* @zxdavb
homeassistant/components/ezviz/* @RenierM26 @baqs homeassistant/components/ezviz/* @RenierM26 @baqs
homeassistant/components/faa_delays/* @ntilley905 homeassistant/components/faa_delays/* @ntilley905
@ -174,6 +173,7 @@ homeassistant/components/flo/* @dmulcahey
homeassistant/components/flock/* @fabaff homeassistant/components/flock/* @fabaff
homeassistant/components/flume/* @ChrisMandich @bdraco homeassistant/components/flume/* @ChrisMandich @bdraco
homeassistant/components/flunearyou/* @bachya homeassistant/components/flunearyou/* @bachya
homeassistant/components/flux_led/* @icemanch
homeassistant/components/forecast_solar/* @klaasnicolaas @frenck homeassistant/components/forecast_solar/* @klaasnicolaas @frenck
homeassistant/components/forked_daapd/* @uvjustin homeassistant/components/forked_daapd/* @uvjustin
homeassistant/components/fortios/* @kimfrellsen homeassistant/components/fortios/* @kimfrellsen
@ -181,7 +181,7 @@ homeassistant/components/foscam/* @skgsergio
homeassistant/components/freebox/* @hacf-fr @Quentame homeassistant/components/freebox/* @hacf-fr @Quentame
homeassistant/components/freedompro/* @stefano055415 homeassistant/components/freedompro/* @stefano055415
homeassistant/components/fritz/* @mammuth @AaronDavidSchneider @chemelli74 homeassistant/components/fritz/* @mammuth @AaronDavidSchneider @chemelli74
homeassistant/components/fritzbox/* @mib1185 homeassistant/components/fritzbox/* @mib1185 @flabbamann
homeassistant/components/fronius/* @nielstron homeassistant/components/fronius/* @nielstron
homeassistant/components/frontend/* @home-assistant/frontend homeassistant/components/frontend/* @home-assistant/frontend
homeassistant/components/garages_amsterdam/* @klaasnicolaas homeassistant/components/garages_amsterdam/* @klaasnicolaas
@ -227,9 +227,8 @@ homeassistant/components/homematic/* @pvizeli @danielperna84
homeassistant/components/honeywell/* @rdfurman homeassistant/components/honeywell/* @rdfurman
homeassistant/components/http/* @home-assistant/core homeassistant/components/http/* @home-assistant/core
homeassistant/components/huawei_lte/* @scop @fphammerle homeassistant/components/huawei_lte/* @scop @fphammerle
homeassistant/components/huawei_router/* @abmantis
homeassistant/components/hue/* @balloob @frenck homeassistant/components/hue/* @balloob @frenck
homeassistant/components/huisbaasje/* @denniss17 homeassistant/components/huisbaasje/* @dennisschroer
homeassistant/components/humidifier/* @home-assistant/core @Shulyaka homeassistant/components/humidifier/* @home-assistant/core @Shulyaka
homeassistant/components/hunterdouglas_powerview/* @bdraco homeassistant/components/hunterdouglas_powerview/* @bdraco
homeassistant/components/hvv_departures/* @vigonotion homeassistant/components/hvv_departures/* @vigonotion
@ -287,6 +286,7 @@ homeassistant/components/litterrobot/* @natekspencer
homeassistant/components/local_ip/* @issacg homeassistant/components/local_ip/* @issacg
homeassistant/components/logger/* @home-assistant/core homeassistant/components/logger/* @home-assistant/core
homeassistant/components/logi_circle/* @evanjd homeassistant/components/logi_circle/* @evanjd
homeassistant/components/lookin/* @ANMalko
homeassistant/components/loopenergy/* @pavoni homeassistant/components/loopenergy/* @pavoni
homeassistant/components/lovelace/* @home-assistant/frontend homeassistant/components/lovelace/* @home-assistant/frontend
homeassistant/components/luci/* @mzdrale homeassistant/components/luci/* @mzdrale
@ -336,7 +336,6 @@ homeassistant/components/nam/* @bieniu
homeassistant/components/nanoleaf/* @milanmeu homeassistant/components/nanoleaf/* @milanmeu
homeassistant/components/neato/* @dshokouhi @Santobert homeassistant/components/neato/* @dshokouhi @Santobert
homeassistant/components/nederlandse_spoorwegen/* @YarmoM homeassistant/components/nederlandse_spoorwegen/* @YarmoM
homeassistant/components/nello/* @pschmitt
homeassistant/components/ness_alarm/* @nickw444 homeassistant/components/ness_alarm/* @nickw444
homeassistant/components/nest/* @allenporter homeassistant/components/nest/* @allenporter
homeassistant/components/netatmo/* @cgtobi homeassistant/components/netatmo/* @cgtobi
@ -361,10 +360,11 @@ homeassistant/components/nsw_rural_fire_service_feed/* @exxamalte
homeassistant/components/nuki/* @pschmitt @pvizeli @pree homeassistant/components/nuki/* @pschmitt @pvizeli @pree
homeassistant/components/numato/* @clssn homeassistant/components/numato/* @clssn
homeassistant/components/number/* @home-assistant/core @Shulyaka homeassistant/components/number/* @home-assistant/core @Shulyaka
homeassistant/components/nut/* @bdraco homeassistant/components/nut/* @bdraco @ollo69
homeassistant/components/nws/* @MatthewFlamm homeassistant/components/nws/* @MatthewFlamm
homeassistant/components/nzbget/* @chriscla homeassistant/components/nzbget/* @chriscla
homeassistant/components/obihai/* @dshokouhi homeassistant/components/obihai/* @dshokouhi
homeassistant/components/octoprint/* @rfleming71
homeassistant/components/ohmconnect/* @robbiet480 homeassistant/components/ohmconnect/* @robbiet480
homeassistant/components/ombi/* @larssont homeassistant/components/ombi/* @larssont
homeassistant/components/omnilogic/* @oliver84 @djtimca @gentoosu homeassistant/components/omnilogic/* @oliver84 @djtimca @gentoosu
@ -423,6 +423,7 @@ homeassistant/components/rainforest_eagle/* @gtdiehl @jcalbert
homeassistant/components/rainmachine/* @bachya homeassistant/components/rainmachine/* @bachya
homeassistant/components/random/* @fabaff homeassistant/components/random/* @fabaff
homeassistant/components/recollect_waste/* @bachya homeassistant/components/recollect_waste/* @bachya
homeassistant/components/recorder/* @home-assistant/core
homeassistant/components/rejseplanen/* @DarkFox homeassistant/components/rejseplanen/* @DarkFox
homeassistant/components/renault/* @epenet homeassistant/components/renault/* @epenet
homeassistant/components/repetier/* @MTrab homeassistant/components/repetier/* @MTrab
@ -497,7 +498,7 @@ homeassistant/components/srp_energy/* @briglx
homeassistant/components/starline/* @anonym-tsk homeassistant/components/starline/* @anonym-tsk
homeassistant/components/statistics/* @fabaff homeassistant/components/statistics/* @fabaff
homeassistant/components/stiebel_eltron/* @fucm homeassistant/components/stiebel_eltron/* @fucm
homeassistant/components/stookalert/* @fwestenberg homeassistant/components/stookalert/* @fwestenberg @frenck
homeassistant/components/stream/* @hunterjm @uvjustin @allenporter homeassistant/components/stream/* @hunterjm @uvjustin @allenporter
homeassistant/components/stt/* @pvizeli homeassistant/components/stt/* @pvizeli
homeassistant/components/subaru/* @G-Two homeassistant/components/subaru/* @G-Two
@ -533,7 +534,6 @@ homeassistant/components/tile/* @bachya
homeassistant/components/time_date/* @fabaff homeassistant/components/time_date/* @fabaff
homeassistant/components/tmb/* @alemuro homeassistant/components/tmb/* @alemuro
homeassistant/components/todoist/* @boralyl homeassistant/components/todoist/* @boralyl
homeassistant/components/toon/* @frenck
homeassistant/components/totalconnect/* @austinmroczek homeassistant/components/totalconnect/* @austinmroczek
homeassistant/components/tplink/* @rytilahti @thegardenmonkey homeassistant/components/tplink/* @rytilahti @thegardenmonkey
homeassistant/components/traccar/* @ludeeus homeassistant/components/traccar/* @ludeeus
@ -544,7 +544,7 @@ homeassistant/components/trafikverket_train/* @endor-force
homeassistant/components/trafikverket_weatherstation/* @endor-force homeassistant/components/trafikverket_weatherstation/* @endor-force
homeassistant/components/transmission/* @engrbm87 @JPHutchins homeassistant/components/transmission/* @engrbm87 @JPHutchins
homeassistant/components/tts/* @pvizeli homeassistant/components/tts/* @pvizeli
homeassistant/components/tuya/* @Tuya @zlinoliver @METISU homeassistant/components/tuya/* @Tuya @zlinoliver @METISU @frenck
homeassistant/components/twentemilieu/* @frenck homeassistant/components/twentemilieu/* @frenck
homeassistant/components/twinkly/* @dr1rrb homeassistant/components/twinkly/* @dr1rrb
homeassistant/components/ubus/* @noltari homeassistant/components/ubus/* @noltari
@ -562,6 +562,7 @@ homeassistant/components/utility_meter/* @dgomes
homeassistant/components/vallox/* @andre-richter homeassistant/components/vallox/* @andre-richter
homeassistant/components/velbus/* @Cereal2nd @brefra homeassistant/components/velbus/* @Cereal2nd @brefra
homeassistant/components/velux/* @Julius2342 homeassistant/components/velux/* @Julius2342
homeassistant/components/venstar/* @garbled1
homeassistant/components/vera/* @pavoni homeassistant/components/vera/* @pavoni
homeassistant/components/verisure/* @frenck homeassistant/components/verisure/* @frenck
homeassistant/components/versasense/* @flamm3blemuff1n homeassistant/components/versasense/* @flamm3blemuff1n
@ -571,7 +572,7 @@ homeassistant/components/vicare/* @oischinger
homeassistant/components/vilfo/* @ManneW homeassistant/components/vilfo/* @ManneW
homeassistant/components/vivotek/* @HarlemSquirrel homeassistant/components/vivotek/* @HarlemSquirrel
homeassistant/components/vizio/* @raman325 homeassistant/components/vizio/* @raman325
homeassistant/components/vlc_telnet/* @rodripf @dmcc homeassistant/components/vlc_telnet/* @rodripf @dmcc @MartinHjelmare
homeassistant/components/volkszaehler/* @fabaff homeassistant/components/volkszaehler/* @fabaff
homeassistant/components/volumio/* @OnFreund homeassistant/components/volumio/* @OnFreund
homeassistant/components/wake_on_lan/* @ntilley905 homeassistant/components/wake_on_lan/* @ntilley905

View File

@ -9,7 +9,7 @@ from typing import Any, Dict, Mapping, Optional, Tuple, cast
import jwt import jwt
from homeassistant import data_entry_flow from homeassistant import data_entry_flow
from homeassistant.core import HomeAssistant, callback from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
@ -155,6 +155,7 @@ class AuthManager:
self._providers = providers self._providers = providers
self._mfa_modules = mfa_modules self._mfa_modules = mfa_modules
self.login_flow = AuthManagerFlowManager(hass, self) self.login_flow = AuthManagerFlowManager(hass, self)
self._revoke_callbacks: dict[str, list[CALLBACK_TYPE]] = {}
@property @property
def auth_providers(self) -> list[AuthProvider]: def auth_providers(self) -> list[AuthProvider]:
@ -275,6 +276,12 @@ class AuthManager:
self, user: models.User, credentials: models.Credentials self, user: models.User, credentials: models.Credentials
) -> None: ) -> None:
"""Link credentials to an existing user.""" """Link credentials to an existing user."""
linked_user = await self.async_get_user_by_credentials(credentials)
if linked_user == user:
return
if linked_user is not None:
raise ValueError("Credential is already linked to a user")
await self._store.async_link_user(user, credentials) await self._store.async_link_user(user, credentials)
async def async_remove_user(self, user: models.User) -> None: async def async_remove_user(self, user: models.User) -> None:
@ -285,7 +292,7 @@ class AuthManager:
] ]
if tasks: if tasks:
await asyncio.wait(tasks) await asyncio.gather(*tasks)
await self._store.async_remove_user(user) await self._store.async_remove_user(user)
@ -446,6 +453,28 @@ class AuthManager:
"""Delete a refresh token.""" """Delete a refresh token."""
await self._store.async_remove_refresh_token(refresh_token) await self._store.async_remove_refresh_token(refresh_token)
callbacks = self._revoke_callbacks.pop(refresh_token.id, [])
for revoke_callback in callbacks:
revoke_callback()
@callback
def async_register_revoke_token_callback(
self, refresh_token_id: str, revoke_callback: CALLBACK_TYPE
) -> CALLBACK_TYPE:
"""Register a callback to be called when the refresh token id is revoked."""
if refresh_token_id not in self._revoke_callbacks:
self._revoke_callbacks[refresh_token_id] = []
callbacks = self._revoke_callbacks[refresh_token_id]
callbacks.append(revoke_callback)
@callback
def unregister() -> None:
if revoke_callback in callbacks:
callbacks.remove(revoke_callback)
return unregister
@callback @callback
def async_create_access_token( def async_create_access_token(
self, refresh_token: models.RefreshToken, remote_ip: str | None = None self, refresh_token: models.RefreshToken, remote_ip: str | None = None

View File

@ -25,7 +25,7 @@ from . import (
SetupFlow, SetupFlow,
) )
REQUIREMENTS = ["pyotp==2.3.0"] REQUIREMENTS = ["pyotp==2.6.0"]
CONF_MESSAGE = "message" CONF_MESSAGE = "message"
@ -56,10 +56,10 @@ def _generate_secret() -> str:
def _generate_random() -> int: def _generate_random() -> int:
"""Generate a 8 digit number.""" """Generate a 32 digit number."""
import pyotp # pylint: disable=import-outside-toplevel import pyotp # pylint: disable=import-outside-toplevel
return int(pyotp.random_base32(length=8, chars=list("1234567890"))) return int(pyotp.random_base32(length=32, chars=list("1234567890")))
def _generate_otp(secret: str, count: int) -> str: def _generate_otp(secret: str, count: int) -> str:
@ -245,8 +245,7 @@ class NotifyAuthModule(MultiFactorAuthModule):
await self._async_load() await self._async_load()
assert self._user_settings is not None assert self._user_settings is not None
notify_setting = self._user_settings.get(user_id) if (notify_setting := self._user_settings.get(user_id)) is None:
if notify_setting is None:
_LOGGER.error("Cannot find user %s", user_id) _LOGGER.error("Cannot find user %s", user_id)
return return

View File

@ -18,7 +18,7 @@ from . import (
SetupFlow, SetupFlow,
) )
REQUIREMENTS = ["pyotp==2.3.0", "PyQRCode==1.2.1"] REQUIREMENTS = ["pyotp==2.6.0", "PyQRCode==1.2.1"]
CONFIG_SCHEMA = MULTI_FACTOR_AUTH_MODULE_SCHEMA.extend({}, extra=vol.PREVENT_EXTRA) CONFIG_SCHEMA = MULTI_FACTOR_AUTH_MODULE_SCHEMA.extend({}, extra=vol.PREVENT_EXTRA)
@ -181,7 +181,7 @@ class TotpSetupFlow(SetupFlow):
# to fix typing complaint # to fix typing complaint
self._auth_module: TotpAuthModule = auth_module self._auth_module: TotpAuthModule = auth_module
self._user = user self._user = user
self._ota_secret: str | None = None self._ota_secret: str = ""
self._url = None # type Optional[str] self._url = None # type Optional[str]
self._image = None # type Optional[str] self._image = None # type Optional[str]

View File

@ -2,7 +2,6 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable from collections.abc import Callable
import logging
from typing import Any from typing import Any
import voluptuous as vol import voluptuous as vol
@ -16,8 +15,6 @@ from .util import test_all
POLICY_SCHEMA = vol.Schema({vol.Optional(CAT_ENTITIES): ENTITY_POLICY_SCHEMA}) POLICY_SCHEMA = vol.Schema({vol.Optional(CAT_ENTITIES): ENTITY_POLICY_SCHEMA})
_LOGGER = logging.getLogger(__name__)
class AbstractPermissions: class AbstractPermissions:
"""Default permissions class.""" """Default permissions class."""

View File

@ -17,7 +17,10 @@ import yarl
from homeassistant import config as conf_util, config_entries, core, loader from homeassistant import config as conf_util, config_entries, core, loader
from homeassistant.components import http from homeassistant.components import http
from homeassistant.const import REQUIRED_NEXT_PYTHON_DATE, REQUIRED_NEXT_PYTHON_VER from homeassistant.const import (
REQUIRED_NEXT_PYTHON_HA_RELEASE,
REQUIRED_NEXT_PYTHON_VER,
)
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import area_registry, device_registry, entity_registry from homeassistant.helpers import area_registry, device_registry, entity_registry
from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.dispatcher import async_dispatcher_send
@ -240,11 +243,14 @@ async def async_from_config_dict(
stop = monotonic() stop = monotonic()
_LOGGER.info("Home Assistant initialized in %.2fs", stop - start) _LOGGER.info("Home Assistant initialized in %.2fs", stop - start)
if REQUIRED_NEXT_PYTHON_DATE and sys.version_info[:3] < REQUIRED_NEXT_PYTHON_VER: if (
REQUIRED_NEXT_PYTHON_HA_RELEASE
and sys.version_info[:3] < REQUIRED_NEXT_PYTHON_VER
):
msg = ( msg = (
"Support for the running Python version " "Support for the running Python version "
f"{'.'.join(str(x) for x in sys.version_info[:3])} is deprecated and will " f"{'.'.join(str(x) for x in sys.version_info[:3])} is deprecated and will "
f"be removed in the first release after {REQUIRED_NEXT_PYTHON_DATE}. " f"be removed in Home Assistant {REQUIRED_NEXT_PYTHON_HA_RELEASE}. "
"Please upgrade Python to " "Please upgrade Python to "
f"{'.'.join(str(x) for x in REQUIRED_NEXT_PYTHON_VER)} or " f"{'.'.join(str(x) for x in REQUIRED_NEXT_PYTHON_VER)} or "
"higher." "higher."

View File

@ -20,7 +20,7 @@ from homeassistant.const import (
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.dispatcher import dispatcher_send from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import DeviceInfo, Entity
from .const import ATTRIBUTION, DEFAULT_CACHEDB, DOMAIN, LOGGER from .const import ATTRIBUTION, DEFAULT_CACHEDB, DOMAIN, LOGGER
@ -322,14 +322,14 @@ class AbodeDevice(AbodeEntity):
} }
@property @property
def device_info(self): def device_info(self) -> DeviceInfo:
"""Return device registry information for this entity.""" """Return device registry information for this entity."""
return { return DeviceInfo(
"identifiers": {(DOMAIN, self._device.device_id)}, identifiers={(DOMAIN, self._device.device_id)},
"manufacturer": "Abode", manufacturer="Abode",
"name": self._device.name, model=self._device.type,
"device_type": self._device.type, name=self._device.name,
} )
def _update_callback(self, device): def _update_callback(self, device):
"""Update the device state.""" """Update the device state."""

View File

@ -1,4 +1,6 @@
"""Config flow for the Abode Security System component.""" """Config flow for the Abode Security System component."""
from http import HTTPStatus
from abodepy import Abode from abodepy import Abode
from abodepy.exceptions import AbodeAuthenticationException, AbodeException from abodepy.exceptions import AbodeAuthenticationException, AbodeException
from abodepy.helpers.errors import MFA_CODE_REQUIRED from abodepy.helpers.errors import MFA_CODE_REQUIRED
@ -6,7 +8,7 @@ from requests.exceptions import ConnectTimeout, HTTPError
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, HTTP_BAD_REQUEST from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from .const import DEFAULT_CACHEDB, DOMAIN, LOGGER from .const import DEFAULT_CACHEDB, DOMAIN, LOGGER
@ -51,7 +53,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
LOGGER.error("Unable to connect to Abode: %s", ex) LOGGER.error("Unable to connect to Abode: %s", ex)
if ex.errcode == HTTP_BAD_REQUEST: if ex.errcode == HTTPStatus.BAD_REQUEST:
errors = {"base": "invalid_auth"} errors = {"base": "invalid_auth"}
else: else:

View File

@ -1,9 +1,19 @@
{ {
"config": { "config": {
"abort": { "abort": {
"reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0442\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435 \u0431\u0435\u0448\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e",
"single_instance_allowed": "\u0420\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043d\u0430 Abode." "single_instance_allowed": "\u0420\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f \u043d\u0430 Abode."
}, },
"error": {
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435"
},
"step": { "step": {
"reauth_confirm": {
"data": {
"password": "\u041f\u0430\u0440\u043e\u043b\u0430",
"username": "Email"
}
},
"user": { "user": {
"data": { "data": {
"password": "\u041f\u0430\u0440\u043e\u043b\u0430", "password": "\u041f\u0430\u0440\u043e\u043b\u0430",

View File

@ -1,7 +1,7 @@
{ {
"config": { "config": {
"abort": { "abort": {
"reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt", "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt.",
"single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges." "single_instance_allowed": "M\u00e1r konfigur\u00e1lva van. Csak egy konfigur\u00e1ci\u00f3 lehets\u00e9ges."
}, },
"error": { "error": {

View File

@ -2,7 +2,7 @@
"domain": "accuweather", "domain": "accuweather",
"name": "AccuWeather", "name": "AccuWeather",
"documentation": "https://www.home-assistant.io/integrations/accuweather/", "documentation": "https://www.home-assistant.io/integrations/accuweather/",
"requirements": ["accuweather==0.2.0"], "requirements": ["accuweather==0.3.0"],
"codeowners": ["@bieniu"], "codeowners": ["@bieniu"],
"config_flow": true, "config_flow": true,
"quality_scale": "platinum", "quality_scale": "platinum",

View File

@ -5,8 +5,9 @@ from typing import Any, cast
from homeassistant.components.sensor import SensorEntity from homeassistant.components.sensor import SensorEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME, DEVICE_CLASS_TEMPERATURE from homeassistant.const import CONF_NAME, DEVICE_CLASS_TEMPERATURE
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
@ -59,6 +60,7 @@ async def async_setup_entry(
class AccuWeatherSensor(CoordinatorEntity, SensorEntity): class AccuWeatherSensor(CoordinatorEntity, SensorEntity):
"""Define an AccuWeather entity.""" """Define an AccuWeather entity."""
_attr_attribution = ATTRIBUTION
coordinator: AccuWeatherDataUpdateCoordinator coordinator: AccuWeatherDataUpdateCoordinator
entity_description: AccuWeatherSensorDescription entity_description: AccuWeatherSensorDescription
@ -75,7 +77,7 @@ class AccuWeatherSensor(CoordinatorEntity, SensorEntity):
self._sensor_data = _get_sensor_data( self._sensor_data = _get_sensor_data(
coordinator.data, forecast_day, description.key coordinator.data, forecast_day, description.key
) )
self._attrs = {ATTR_ATTRIBUTION: ATTRIBUTION} self._attrs: dict[str, Any] = {}
if forecast_day is not None: if forecast_day is not None:
self._attr_name = f"{name} {description.name} {forecast_day}d" self._attr_name = f"{name} {description.name} {forecast_day}d"
self._attr_unique_id = ( self._attr_unique_id = (
@ -92,12 +94,12 @@ class AccuWeatherSensor(CoordinatorEntity, SensorEntity):
else: else:
self._unit_system = API_IMPERIAL self._unit_system = API_IMPERIAL
self._attr_native_unit_of_measurement = description.unit_imperial self._attr_native_unit_of_measurement = description.unit_imperial
self._attr_device_info = { self._attr_device_info = DeviceInfo(
"identifiers": {(DOMAIN, coordinator.location_key)}, entry_type="service",
"name": NAME, identifiers={(DOMAIN, coordinator.location_key)},
"manufacturer": MANUFACTURER, manufacturer=MANUFACTURER,
"entry_type": "service", name=NAME,
} )
self.forecast_day = forecast_day self.forecast_day = forecast_day
@property @property

View File

@ -0,0 +1,7 @@
{
"config": {
"error": {
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435"
}
}
}

View File

@ -16,7 +16,7 @@
"longitude": "\u7d93\u5ea6", "longitude": "\u7d93\u5ea6",
"name": "\u540d\u7a31" "name": "\u540d\u7a31"
}, },
"description": "\u5047\u5982\u4f60\u9700\u8981\u5354\u52a9\u9032\u884c\u8a2d\u5b9a\uff0c\u8acb\u53c3\u95b1\uff1ahttps://www.home-assistant.io/integrations/accuweather/\n\n\u67d0\u4e9b\u50b3\u611f\u5668\u9810\u8a2d\u70ba\u672a\u555f\u7528\uff0c\u53ef\u4ee5\u65bc\u6574\u5408\u8a2d\u5b9a\u4e2d\u555f\u7528\u9019\u4e9b\u5be6\u9ad4\u3002\u5929\u6c23\u9810\u5831\u9810\u8a2d\u672a\u958b\u555f\u3002\u53ef\u4ee5\u65bc\u6574\u5408\u9078\u9805\u4e2d\u958b\u555f\u3002", "description": "\u5047\u5982\u4f60\u9700\u8981\u5354\u52a9\u9032\u884c\u8a2d\u5b9a\uff0c\u8acb\u53c3\u95b1\uff1ahttps://www.home-assistant.io/integrations/accuweather/\n\n\u67d0\u4e9b\u611f\u6e2c\u5668\u9810\u8a2d\u70ba\u672a\u555f\u7528\uff0c\u53ef\u4ee5\u65bc\u6574\u5408\u8a2d\u5b9a\u4e2d\u555f\u7528\u9019\u4e9b\u5be6\u9ad4\u3002\u5929\u6c23\u9810\u5831\u9810\u8a2d\u672a\u958b\u555f\u3002\u53ef\u4ee5\u65bc\u6574\u5408\u9078\u9805\u4e2d\u958b\u555f\u3002",
"title": "AccuWeather" "title": "AccuWeather"
} }
} }

View File

@ -19,6 +19,7 @@ from homeassistant.components.weather import (
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME, TEMP_CELSIUS, TEMP_FAHRENHEIT from homeassistant.const import CONF_NAME, TEMP_CELSIUS, TEMP_FAHRENHEIT
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util.dt import utc_from_timestamp from homeassistant.util.dt import utc_from_timestamp
@ -66,12 +67,12 @@ class AccuWeatherEntity(CoordinatorEntity, WeatherEntity):
TEMP_CELSIUS if coordinator.is_metric else TEMP_FAHRENHEIT TEMP_CELSIUS if coordinator.is_metric else TEMP_FAHRENHEIT
) )
self._attr_attribution = ATTRIBUTION self._attr_attribution = ATTRIBUTION
self._attr_device_info = { self._attr_device_info = DeviceInfo(
"identifiers": {(DOMAIN, coordinator.location_key)}, entry_type="service",
"name": NAME, identifiers={(DOMAIN, coordinator.location_key)},
"manufacturer": MANUFACTURER, manufacturer=MANUFACTURER,
"entry_type": "service", name=NAME,
} )
@property @property
def condition(self) -> str | None: def condition(self) -> str | None:

View File

@ -129,8 +129,7 @@ class AcerSwitch(SwitchEntity):
self._attr_available = False self._attr_available = False
for key in self._attributes: for key in self._attributes:
msg = CMD_DICT.get(key) if msg := CMD_DICT.get(key):
if msg:
awns = self._write_read_format(msg) awns = self._write_read_format(msg)
self._attributes[key] = awns self._attributes[key] = awns
self._attr_extra_state_attributes = self._attributes self._attr_extra_state_attributes = self._attributes

View File

@ -77,11 +77,11 @@ class AcmedaBase(entity.Entity):
return self.roller.name return self.roller.name
@property @property
def device_info(self): def device_info(self) -> entity.DeviceInfo:
"""Return the device info.""" """Return the device info."""
return { return entity.DeviceInfo(
"identifiers": {(DOMAIN, self.unique_id)}, identifiers={(DOMAIN, self.unique_id)},
"name": self.roller.name, manufacturer="Rollease Acmeda",
"manufacturer": "Rollease Acmeda", name=self.roller.name,
"via_device": (DOMAIN, self.roller.hub.id), via_device=(DOMAIN, self.roller.hub.id),
} )

View File

@ -1,7 +1,6 @@
"""Support for Adax wifi-enabled home heaters.""" """Support for Adax wifi-enabled home heaters."""
from __future__ import annotations from __future__ import annotations
import logging
from typing import Any from typing import Any
from adax import Adax from adax import Adax
@ -21,11 +20,10 @@ from homeassistant.const import (
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import ACCOUNT_ID from .const import ACCOUNT_ID, DOMAIN
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry( async def async_setup_entry(
@ -41,8 +39,11 @@ async def async_setup_entry(
) )
async_add_entities( async_add_entities(
AdaxDevice(room, adax_data_handler) (
for room in await adax_data_handler.get_rooms() AdaxDevice(room, adax_data_handler)
for room in await adax_data_handler.get_rooms()
),
True,
) )
@ -58,69 +59,51 @@ class AdaxDevice(ClimateEntity):
def __init__(self, heater_data: dict[str, Any], adax_data_handler: Adax) -> None: def __init__(self, heater_data: dict[str, Any], adax_data_handler: Adax) -> None:
"""Initialize the heater.""" """Initialize the heater."""
self._heater_data = heater_data self._device_id = heater_data["id"]
self._adax_data_handler = adax_data_handler self._adax_data_handler = adax_data_handler
self._attr_unique_id = f"{heater_data['homeId']}_{heater_data['id']}" self._attr_unique_id = f"{heater_data['homeId']}_{heater_data['id']}"
self._attr_device_info = DeviceInfo(
@property identifiers={(DOMAIN, heater_data["id"])},
def name(self) -> str: name=self.name,
"""Return the name of the device, if any.""" manufacturer="Adax",
return self._heater_data["name"] )
@property
def hvac_mode(self) -> str:
"""Return hvac operation ie. heat, cool mode."""
if self._heater_data["heatingEnabled"]:
return HVAC_MODE_HEAT
return HVAC_MODE_OFF
@property
def icon(self) -> str:
"""Return nice icon for heater."""
if self.hvac_mode == HVAC_MODE_HEAT:
return "mdi:radiator"
return "mdi:radiator-off"
async def async_set_hvac_mode(self, hvac_mode: str) -> None: async def async_set_hvac_mode(self, hvac_mode: str) -> None:
"""Set hvac mode.""" """Set hvac mode."""
if hvac_mode == HVAC_MODE_HEAT: if hvac_mode == HVAC_MODE_HEAT:
temperature = max( temperature = max(self.min_temp, self.target_temperature or self.min_temp)
self.min_temp, self._heater_data.get("targetTemperature", self.min_temp)
)
await self._adax_data_handler.set_room_target_temperature( await self._adax_data_handler.set_room_target_temperature(
self._heater_data["id"], temperature, True self._device_id, temperature, True
) )
elif hvac_mode == HVAC_MODE_OFF: elif hvac_mode == HVAC_MODE_OFF:
await self._adax_data_handler.set_room_target_temperature( await self._adax_data_handler.set_room_target_temperature(
self._heater_data["id"], self.min_temp, False self._device_id, self.min_temp, False
) )
else: else:
return return
await self._adax_data_handler.update() await self._adax_data_handler.update()
@property
def current_temperature(self) -> float | None:
"""Return the current temperature."""
return self._heater_data.get("temperature")
@property
def target_temperature(self) -> int | None:
"""Return the temperature we try to reach."""
return self._heater_data.get("targetTemperature")
async def async_set_temperature(self, **kwargs: Any) -> None: async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature.""" """Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE) if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None:
if temperature is None:
return return
await self._adax_data_handler.set_room_target_temperature( await self._adax_data_handler.set_room_target_temperature(
self._heater_data["id"], temperature, True self._device_id, temperature, True
) )
async def async_update(self) -> None: async def async_update(self) -> None:
"""Get the latest data.""" """Get the latest data."""
for room in await self._adax_data_handler.get_rooms(): for room in await self._adax_data_handler.get_rooms():
if room["id"] == self._heater_data["id"]: if room["id"] != self._device_id:
self._heater_data = room continue
return self._attr_name = room["name"]
self._attr_current_temperature = room.get("temperature")
self._attr_target_temperature = room.get("targetTemperature")
if room["heatingEnabled"]:
self._attr_hvac_mode = HVAC_MODE_HEAT
self._attr_icon = "mdi:radiator"
else:
self._attr_hvac_mode = HVAC_MODE_OFF
self._attr_icon = "mdi:radiator-off"
return

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e"
},
"error": {
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435",
"invalid_auth": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435"
},
"step": {
"user": {
"data": {
"host": "\u0425\u043e\u0441\u0442",
"password": "\u041f\u0430\u0440\u043e\u043b\u0430"
}
}
}
}
}

View File

@ -196,14 +196,14 @@ class AdGuardHomeDeviceEntity(AdGuardHomeEntity):
@property @property
def device_info(self) -> DeviceInfo: def device_info(self) -> DeviceInfo:
"""Return device information about this AdGuard Home instance.""" """Return device information about this AdGuard Home instance."""
return { return DeviceInfo(
"identifiers": { entry_type="service",
identifiers={
(DOMAIN, self.adguard.host, self.adguard.port, self.adguard.base_path) # type: ignore (DOMAIN, self.adguard.host, self.adguard.port, self.adguard.base_path) # type: ignore
}, },
"name": "AdGuard Home", manufacturer="AdGuard Team",
"manufacturer": "AdGuard Team", name="AdGuard Home",
"sw_version": self.hass.data[DOMAIN][self._entry.entry_id].get( sw_version=self.hass.data[DOMAIN][self._entry.entry_id].get(
DATA_ADGUARD_VERSION DATA_ADGUARD_VERSION
), ),
"entry_type": "service", )
}

View File

@ -3,6 +3,9 @@
"abort": { "abort": {
"existing_instance_updated": "\u0410\u043a\u0442\u0443\u0430\u043b\u0438\u0437\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0441\u044a\u0449\u0435\u0441\u0442\u0432\u0443\u0432\u0430\u0449\u0430\u0442\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f." "existing_instance_updated": "\u0410\u043a\u0442\u0443\u0430\u043b\u0438\u0437\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0441\u044a\u0449\u0435\u0441\u0442\u0432\u0443\u0432\u0430\u0449\u0430\u0442\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f."
}, },
"error": {
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435"
},
"step": { "step": {
"hassio_confirm": { "hassio_confirm": {
"description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u0442\u0435 Home Assistant \u0434\u0430 \u0441\u0435 \u0441\u0432\u044a\u0440\u0437\u0432\u0430 \u0441 AdGuard Home, \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0435\u043d \u043e\u0442 Supervisor \u0434\u043e\u0431\u0430\u0432\u043a\u0430\u0442\u0430: {addon} ?", "description": "\u0418\u0441\u043a\u0430\u0442\u0435 \u043b\u0438 \u0434\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u0442\u0435 Home Assistant \u0434\u0430 \u0441\u0435 \u0441\u0432\u044a\u0440\u0437\u0432\u0430 \u0441 AdGuard Home, \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u0435\u043d \u043e\u0442 Supervisor \u0434\u043e\u0431\u0430\u0432\u043a\u0430\u0442\u0430: {addon} ?",
@ -10,7 +13,9 @@
}, },
"user": { "user": {
"data": { "data": {
"host": "\u0425\u043e\u0441\u0442",
"password": "\u041f\u0430\u0440\u043e\u043b\u0430", "password": "\u041f\u0430\u0440\u043e\u043b\u0430",
"port": "\u041f\u043e\u0440\u0442",
"ssl": "AdGuard Home \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430 SSL \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442", "ssl": "AdGuard Home \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430 SSL \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442",
"username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435", "username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b\u0441\u043a\u043e \u0438\u043c\u0435",
"verify_ssl": "AdGuard Home \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430 \u043d\u0430\u0434\u0435\u0436\u0434\u0435\u043d \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442" "verify_ssl": "AdGuard Home \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430 \u043d\u0430\u0434\u0435\u0436\u0434\u0435\u043d \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442"

View File

@ -12,7 +12,7 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
from .const import ADVANTAGE_AIR_RETRY, DOMAIN from .const import ADVANTAGE_AIR_RETRY, DOMAIN
ADVANTAGE_AIR_SYNC_INTERVAL = 15 ADVANTAGE_AIR_SYNC_INTERVAL = 15
PLATFORMS = ["climate", "cover", "binary_sensor", "sensor", "switch"] PLATFORMS = ["binary_sensor", "climate", "cover", "select", "sensor", "switch"]
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@ -5,6 +5,7 @@ from homeassistant.components.binary_sensor import (
DEVICE_CLASS_PROBLEM, DEVICE_CLASS_PROBLEM,
BinarySensorEntity, BinarySensorEntity,
) )
from homeassistant.const import ENTITY_CATEGORY_DIAGNOSTIC
from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN
from .entity import AdvantageAirEntity from .entity import AdvantageAirEntity
@ -34,6 +35,7 @@ class AdvantageAirZoneFilter(AdvantageAirEntity, BinarySensorEntity):
"""Advantage Air Filter.""" """Advantage Air Filter."""
_attr_device_class = DEVICE_CLASS_PROBLEM _attr_device_class = DEVICE_CLASS_PROBLEM
_attr_entity_category = ENTITY_CATEGORY_DIAGNOSTIC
def __init__(self, instance, ac_key): def __init__(self, instance, ac_key):
"""Initialize an Advantage Air Filter.""" """Initialize an Advantage Air Filter."""
@ -65,13 +67,14 @@ class AdvantageAirZoneMotion(AdvantageAirEntity, BinarySensorEntity):
@property @property
def is_on(self): def is_on(self):
"""Return if motion is detect.""" """Return if motion is detect."""
return self._zone["motion"] return self._zone["motion"] == 20
class AdvantageAirZoneMyZone(AdvantageAirEntity, BinarySensorEntity): class AdvantageAirZoneMyZone(AdvantageAirEntity, BinarySensorEntity):
"""Advantage Air Zone MyZone.""" """Advantage Air Zone MyZone."""
_attr_entity_registry_enabled_default = False _attr_entity_registry_enabled_default = False
_attr_entity_category = ENTITY_CATEGORY_DIAGNOSTIC
def __init__(self, instance, ac_key, zone_key): def __init__(self, instance, ac_key, zone_key):
"""Initialize an Advantage Air Zone MyZone.""" """Initialize an Advantage Air Zone MyZone."""

View File

@ -1,5 +1,6 @@
"""Advantage Air parent entity class.""" """Advantage Air parent entity class."""
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN from .const import DOMAIN
@ -14,13 +15,13 @@ class AdvantageAirEntity(CoordinatorEntity):
self.async_change = instance["async_change"] self.async_change = instance["async_change"]
self.ac_key = ac_key self.ac_key = ac_key
self.zone_key = zone_key self.zone_key = zone_key
self._attr_device_info = { self._attr_device_info = DeviceInfo(
"identifiers": {(DOMAIN, self.coordinator.data["system"]["rid"])}, identifiers={(DOMAIN, self.coordinator.data["system"]["rid"])},
"name": self.coordinator.data["system"]["name"], manufacturer="Advantage Air",
"manufacturer": "Advantage Air", model=self.coordinator.data["system"]["sysType"],
"model": self.coordinator.data["system"]["sysType"], name=self.coordinator.data["system"]["name"],
"sw_version": self.coordinator.data["system"]["myAppRev"], sw_version=self.coordinator.data["system"]["myAppRev"],
} )
@property @property
def _ac(self): def _ac(self):

View File

@ -0,0 +1,53 @@
"""Select platform for Advantage Air integration."""
from homeassistant.components.select import SelectEntity
from .const import DOMAIN as ADVANTAGE_AIR_DOMAIN
from .entity import AdvantageAirEntity
ADVANTAGE_AIR_INACTIVE = "Inactive"
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up AdvantageAir toggle platform."""
instance = hass.data[ADVANTAGE_AIR_DOMAIN][config_entry.entry_id]
entities = []
for ac_key in instance["coordinator"].data["aircons"]:
entities.append(AdvantageAirMyZone(instance, ac_key))
async_add_entities(entities)
class AdvantageAirMyZone(AdvantageAirEntity, SelectEntity):
"""Representation of Advantage Air MyZone control."""
_attr_icon = "mdi:home-thermometer"
_attr_options = [ADVANTAGE_AIR_INACTIVE]
_number_to_name = {0: ADVANTAGE_AIR_INACTIVE}
_name_to_number = {ADVANTAGE_AIR_INACTIVE: 0}
def __init__(self, instance, ac_key):
"""Initialize an Advantage Air MyZone control."""
super().__init__(instance, ac_key)
self._attr_name = f'{self._ac["name"]} MyZone'
self._attr_unique_id = (
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-myzone'
)
for zone in instance["coordinator"].data["aircons"][ac_key]["zones"].values():
if zone["type"] > 0:
self._name_to_number[zone["name"]] = zone["number"]
self._number_to_name[zone["number"]] = zone["name"]
self._attr_options.append(zone["name"])
@property
def current_option(self):
"""Return the fresh air status."""
return self._number_to_name[self._ac["myZone"]]
async def async_select_option(self, option):
"""Set the MyZone."""
await self.async_change(
{self.ac_key: {"info": {"myZone": self._name_to_number[option]}}}
)

View File

@ -6,7 +6,7 @@ from homeassistant.components.sensor import (
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
SensorEntity, SensorEntity,
) )
from homeassistant.const import PERCENTAGE, TEMP_CELSIUS from homeassistant.const import ENTITY_CATEGORY_DIAGNOSTIC, PERCENTAGE, TEMP_CELSIUS
from homeassistant.helpers import config_validation as cv, entity_platform from homeassistant.helpers import config_validation as cv, entity_platform
from .const import ADVANTAGE_AIR_STATE_OPEN, DOMAIN as ADVANTAGE_AIR_DOMAIN from .const import ADVANTAGE_AIR_STATE_OPEN, DOMAIN as ADVANTAGE_AIR_DOMAIN
@ -50,6 +50,7 @@ class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
"""Representation of Advantage Air timer control.""" """Representation of Advantage Air timer control."""
_attr_native_unit_of_measurement = ADVANTAGE_AIR_SET_COUNTDOWN_UNIT _attr_native_unit_of_measurement = ADVANTAGE_AIR_SET_COUNTDOWN_UNIT
_attr_entity_category = ENTITY_CATEGORY_DIAGNOSTIC
def __init__(self, instance, ac_key, action): def __init__(self, instance, ac_key, action):
"""Initialize the Advantage Air timer control.""" """Initialize the Advantage Air timer control."""
@ -84,6 +85,7 @@ class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
_attr_native_unit_of_measurement = PERCENTAGE _attr_native_unit_of_measurement = PERCENTAGE
_attr_state_class = STATE_CLASS_MEASUREMENT _attr_state_class = STATE_CLASS_MEASUREMENT
_attr_entity_category = ENTITY_CATEGORY_DIAGNOSTIC
def __init__(self, instance, ac_key, zone_key): def __init__(self, instance, ac_key, zone_key):
"""Initialize an Advantage Air Zone Vent Sensor.""" """Initialize an Advantage Air Zone Vent Sensor."""
@ -113,6 +115,7 @@ class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
_attr_native_unit_of_measurement = PERCENTAGE _attr_native_unit_of_measurement = PERCENTAGE
_attr_state_class = STATE_CLASS_MEASUREMENT _attr_state_class = STATE_CLASS_MEASUREMENT
_attr_entity_category = ENTITY_CATEGORY_DIAGNOSTIC
def __init__(self, instance, ac_key, zone_key): def __init__(self, instance, ac_key, zone_key):
"""Initialize an Advantage Air Zone wireless signal sensor.""" """Initialize an Advantage Air Zone wireless signal sensor."""
@ -148,6 +151,7 @@ class AdvantageAirZoneTemp(AdvantageAirEntity, SensorEntity):
_attr_device_class = DEVICE_CLASS_TEMPERATURE _attr_device_class = DEVICE_CLASS_TEMPERATURE
_attr_state_class = STATE_CLASS_MEASUREMENT _attr_state_class = STATE_CLASS_MEASUREMENT
_attr_entity_registry_enabled_default = False _attr_entity_registry_enabled_default = False
_attr_entity_category = ENTITY_CATEGORY_DIAGNOSTIC
def __init__(self, instance, ac_key, zone_key): def __init__(self, instance, ac_key, zone_key):
"""Initialize an Advantage Air Zone Temp Sensor.""" """Initialize an Advantage Air Zone Temp Sensor."""

View File

@ -0,0 +1,18 @@
{
"config": {
"abort": {
"already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e"
},
"error": {
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435"
},
"step": {
"user": {
"data": {
"ip_address": "IP \u0430\u0434\u0440\u0435\u0441",
"port": "\u041f\u043e\u0440\u0442"
}
}
}
}
}

View File

@ -52,7 +52,7 @@ async def async_update_options(hass: HomeAssistant, entry: ConfigEntry) -> None:
await hass.config_entries.async_reload(entry.entry_id) await hass.config_entries.async_reload(entry.entry_id)
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@ -1,7 +1,10 @@
"""Constant values for the AEMET OpenData component.""" """Constant values for the AEMET OpenData component."""
from __future__ import annotations from __future__ import annotations
from homeassistant.components.sensor import SensorEntityDescription from homeassistant.components.sensor import (
STATE_CLASS_MEASUREMENT,
SensorEntityDescription,
)
from homeassistant.components.weather import ( from homeassistant.components.weather import (
ATTR_CONDITION_CLEAR_NIGHT, ATTR_CONDITION_CLEAR_NIGHT,
ATTR_CONDITION_CLOUDY, ATTR_CONDITION_CLOUDY,
@ -252,12 +255,14 @@ WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
name="Humidity", name="Humidity",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY, device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key=ATTR_API_PRESSURE, key=ATTR_API_PRESSURE,
name="Pressure", name="Pressure",
native_unit_of_measurement=PRESSURE_HPA, native_unit_of_measurement=PRESSURE_HPA,
device_class=DEVICE_CLASS_PRESSURE, device_class=DEVICE_CLASS_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key=ATTR_API_RAIN, key=ATTR_API_RAIN,
@ -268,6 +273,7 @@ WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
key=ATTR_API_RAIN_PROB, key=ATTR_API_RAIN_PROB,
name="Rain probability", name="Rain probability",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
state_class=STATE_CLASS_MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key=ATTR_API_SNOW, key=ATTR_API_SNOW,
@ -278,6 +284,7 @@ WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
key=ATTR_API_SNOW_PROB, key=ATTR_API_SNOW_PROB,
name="Snow probability", name="Snow probability",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
state_class=STATE_CLASS_MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key=ATTR_API_STATION_ID, key=ATTR_API_STATION_ID,
@ -296,18 +303,21 @@ WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
key=ATTR_API_STORM_PROB, key=ATTR_API_STORM_PROB,
name="Storm probability", name="Storm probability",
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
state_class=STATE_CLASS_MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key=ATTR_API_TEMPERATURE, key=ATTR_API_TEMPERATURE,
name="Temperature", name="Temperature",
native_unit_of_measurement=TEMP_CELSIUS, native_unit_of_measurement=TEMP_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE, device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key=ATTR_API_TEMPERATURE_FEELING, key=ATTR_API_TEMPERATURE_FEELING,
name="Temperature feeling", name="Temperature feeling",
native_unit_of_measurement=TEMP_CELSIUS, native_unit_of_measurement=TEMP_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE, device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key=ATTR_API_TOWN_ID, key=ATTR_API_TOWN_ID,
@ -326,6 +336,7 @@ WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
key=ATTR_API_WIND_BEARING, key=ATTR_API_WIND_BEARING,
name="Wind bearing", name="Wind bearing",
native_unit_of_measurement=DEGREE, native_unit_of_measurement=DEGREE,
state_class=STATE_CLASS_MEASUREMENT,
), ),
SensorEntityDescription( SensorEntityDescription(
key=ATTR_API_WIND_MAX_SPEED, key=ATTR_API_WIND_MAX_SPEED,
@ -336,6 +347,7 @@ WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
key=ATTR_API_WIND_SPEED, key=ATTR_API_WIND_SPEED,
name="Wind speed", name="Wind speed",
native_unit_of_measurement=SPEED_KILOMETERS_PER_HOUR, native_unit_of_measurement=SPEED_KILOMETERS_PER_HOUR,
state_class=STATE_CLASS_MEASUREMENT,
), ),
) )

View File

@ -1,6 +1,7 @@
"""Support for non-delivered packages recorded in AfterShip.""" """Support for non-delivered packages recorded in AfterShip."""
from __future__ import annotations from __future__ import annotations
from http import HTTPStatus
import logging import logging
from typing import Any, Final from typing import Any, Final
@ -11,7 +12,7 @@ from homeassistant.components.sensor import (
PLATFORM_SCHEMA as BASE_PLATFORM_SCHEMA, PLATFORM_SCHEMA as BASE_PLATFORM_SCHEMA,
SensorEntity, SensorEntity,
) )
from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY, CONF_NAME, HTTP_OK from homeassistant.const import CONF_API_KEY, CONF_NAME
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -64,7 +65,7 @@ async def async_setup_platform(
await aftership.get_trackings() await aftership.get_trackings()
if not aftership.meta or aftership.meta["code"] != HTTP_OK: if not aftership.meta or aftership.meta["code"] != HTTPStatus.OK:
_LOGGER.error( _LOGGER.error(
"No tracking data found. Check API key is correct: %s", aftership.meta "No tracking data found. Check API key is correct: %s", aftership.meta
) )
@ -109,6 +110,7 @@ async def async_setup_platform(
class AfterShipSensor(SensorEntity): class AfterShipSensor(SensorEntity):
"""Representation of a AfterShip sensor.""" """Representation of a AfterShip sensor."""
_attr_attribution = ATTRIBUTION
_attr_native_unit_of_measurement: str = "packages" _attr_native_unit_of_measurement: str = "packages"
_attr_icon: str = ICON _attr_icon: str = ICON
@ -150,7 +152,7 @@ class AfterShipSensor(SensorEntity):
if not self.aftership.meta: if not self.aftership.meta:
_LOGGER.error("Unknown errors when querying") _LOGGER.error("Unknown errors when querying")
return return
if self.aftership.meta["code"] != HTTP_OK: if self.aftership.meta["code"] != HTTPStatus.OK:
_LOGGER.error( _LOGGER.error(
"Errors when querying AfterShip. %s", str(self.aftership.meta) "Errors when querying AfterShip. %s", str(self.aftership.meta)
) )
@ -191,7 +193,6 @@ class AfterShipSensor(SensorEntity):
_LOGGER.debug("Ignoring %s as it has status: %s", name, status) _LOGGER.debug("Ignoring %s as it has status: %s", name, status)
self._attributes = { self._attributes = {
ATTR_ATTRIBUTION: ATTRIBUTION,
**status_counts, **status_counts,
ATTR_TRACKINGS: trackings, ATTR_TRACKINGS: trackings,
} }

View File

@ -11,6 +11,7 @@ from homeassistant.const import (
STATE_ALARM_ARMED_NIGHT, STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_DISARMED, STATE_ALARM_DISARMED,
) )
from homeassistant.helpers.entity import DeviceInfo
from .const import CONNECTION, DOMAIN as AGENT_DOMAIN from .const import CONNECTION, DOMAIN as AGENT_DOMAIN
@ -45,12 +46,12 @@ class AgentBaseStation(AlarmControlPanelEntity):
self._client = client self._client = client
self._attr_name = f"{client.name} {CONST_ALARM_CONTROL_PANEL_NAME}" self._attr_name = f"{client.name} {CONST_ALARM_CONTROL_PANEL_NAME}"
self._attr_unique_id = f"{client.unique}_CP" self._attr_unique_id = f"{client.unique}_CP"
self._attr_device_info = { self._attr_device_info = DeviceInfo(
"identifiers": {(AGENT_DOMAIN, client.unique)}, identifiers={(AGENT_DOMAIN, client.unique)},
"manufacturer": "Agent", manufacturer="Agent",
"model": CONST_ALARM_CONTROL_PANEL_NAME, model=CONST_ALARM_CONTROL_PANEL_NAME,
"sw_version": client.version, sw_version=client.version,
} )
async def async_update(self): async def async_update(self):
"""Update the state of the device.""" """Update the state of the device."""

View File

@ -13,6 +13,7 @@ from homeassistant.components.mjpeg.camera import (
) )
from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME from homeassistant.const import ATTR_ATTRIBUTION, CONF_NAME
from homeassistant.helpers import entity_platform from homeassistant.helpers import entity_platform
from homeassistant.helpers.entity import DeviceInfo
from .const import ( from .const import (
ATTRIBUTION, ATTRIBUTION,
@ -79,13 +80,13 @@ class AgentCamera(MjpegCamera):
self._attr_name = f"{device.client.name} {device.name}" self._attr_name = f"{device.client.name} {device.name}"
self._attr_unique_id = f"{device._client.unique}_{device.typeID}_{device.id}" self._attr_unique_id = f"{device._client.unique}_{device.typeID}_{device.id}"
super().__init__(device_info) super().__init__(device_info)
self._attr_device_info = { self._attr_device_info = DeviceInfo(
"identifiers": {(AGENT_DOMAIN, self.unique_id)}, identifiers={(AGENT_DOMAIN, self.unique_id)},
"name": self.name, manufacturer="Agent",
"manufacturer": "Agent", model="Camera",
"model": "Camera", name=self.name,
"sw_version": device.client.version, sw_version=device.client.version,
} )
async def async_update(self): async def async_update(self):
"""Update our state from the Agent API.""" """Update our state from the Agent API."""

View File

@ -33,9 +33,7 @@ class AgentFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
try: try:
await agent_client.update() await agent_client.update()
except AgentConnectionError: except (AgentConnectionError, AgentError):
pass
except AgentError:
pass pass
await agent_client.close() await agent_client.close()

View File

@ -0,0 +1,15 @@
{
"config": {
"abort": {
"already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e"
},
"step": {
"user": {
"data": {
"host": "\u0425\u043e\u0441\u0442",
"port": "\u041f\u043e\u0440\u0442"
}
}
}
}
}

View File

@ -4,7 +4,7 @@
"already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van" "already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van"
}, },
"error": { "error": {
"already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van.", "already_in_progress": "A konfigur\u00e1l\u00e1s m\u00e1r folyamatban van",
"cannot_connect": "Sikertelen csatlakoz\u00e1s" "cannot_connect": "Sikertelen csatlakoz\u00e1s"
}, },
"step": { "step": {

View File

@ -6,10 +6,7 @@ import logging
from typing import Final, final from typing import Final, final
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ( from homeassistant.const import CONCENTRATION_MICROGRAMS_PER_CUBIC_METER
ATTR_ATTRIBUTION,
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
)
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.config_validation import ( # noqa: F401 from homeassistant.helpers.config_validation import ( # noqa: F401
PLATFORM_SCHEMA, PLATFORM_SCHEMA,
@ -41,7 +38,6 @@ SCAN_INTERVAL: Final = timedelta(seconds=30)
PROP_TO_ATTR: Final[dict[str, str]] = { PROP_TO_ATTR: Final[dict[str, str]] = {
"air_quality_index": ATTR_AQI, "air_quality_index": ATTR_AQI,
"attribution": ATTR_ATTRIBUTION,
"carbon_dioxide": ATTR_CO2, "carbon_dioxide": ATTR_CO2,
"carbon_monoxide": ATTR_CO, "carbon_monoxide": ATTR_CO,
"nitrogen_oxide": ATTR_N2O, "nitrogen_oxide": ATTR_N2O,
@ -114,11 +110,6 @@ class AirQualityEntity(Entity):
"""Return the CO2 (carbon dioxide) level.""" """Return the CO2 (carbon dioxide) level."""
return None return None
@property
def attribution(self) -> StateType:
"""Return the attribution."""
return None
@property @property
def sulphur_dioxide(self) -> StateType: def sulphur_dioxide(self) -> StateType:
"""Return the SO2 (sulphur dioxide) level.""" """Return the SO2 (sulphur dioxide) level."""

View File

@ -1,6 +1,7 @@
"""Adds config flow for Airly.""" """Adds config flow for Airly."""
from __future__ import annotations from __future__ import annotations
from http import HTTPStatus
from typing import Any from typing import Any
from aiohttp import ClientSession from aiohttp import ClientSession
@ -10,14 +11,7 @@ import async_timeout
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.const import ( from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME
CONF_API_KEY,
CONF_LATITUDE,
CONF_LONGITUDE,
CONF_NAME,
HTTP_NOT_FOUND,
HTTP_UNAUTHORIZED,
)
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -60,9 +54,9 @@ class AirlyFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
use_nearest=True, use_nearest=True,
) )
except AirlyError as err: except AirlyError as err:
if err.status_code == HTTP_UNAUTHORIZED: if err.status_code == HTTPStatus.UNAUTHORIZED:
errors["base"] = "invalid_api_key" errors["base"] = "invalid_api_key"
if err.status_code == HTTP_NOT_FOUND: if err.status_code == HTTPStatus.NOT_FOUND:
errors["base"] = "wrong_location" errors["base"] = "wrong_location"
else: else:
if not location_point_valid: if not location_point_valid:

View File

@ -32,3 +32,4 @@ MANUFACTURER: Final = "Airly sp. z o.o."
MAX_UPDATE_INTERVAL: Final = 90 MAX_UPDATE_INTERVAL: Final = 90
MIN_UPDATE_INTERVAL: Final = 5 MIN_UPDATE_INTERVAL: Final = 5
NO_AIRLY_SENSORS: Final = "There are no Airly sensors in this area yet." NO_AIRLY_SENSORS: Final = "There are no Airly sensors in this area yet."
URL = "https://airly.org/map/#{latitude},{longitude}"

View File

@ -27,6 +27,7 @@ from homeassistant.const import (
TEMP_CELSIUS, TEMP_CELSIUS,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
@ -54,6 +55,7 @@ from .const import (
MANUFACTURER, MANUFACTURER,
SUFFIX_LIMIT, SUFFIX_LIMIT,
SUFFIX_PERCENT, SUFFIX_PERCENT,
URL,
) )
PARALLEL_UPDATES = 1 PARALLEL_UPDATES = 1
@ -151,14 +153,15 @@ class AirlySensor(CoordinatorEntity, SensorEntity):
) -> None: ) -> None:
"""Initialize.""" """Initialize."""
super().__init__(coordinator) super().__init__(coordinator)
self._attr_device_info = { self._attr_device_info = DeviceInfo(
"identifiers": { entry_type="service",
(DOMAIN, f"{coordinator.latitude}-{coordinator.longitude}") identifiers={(DOMAIN, f"{coordinator.latitude}-{coordinator.longitude}")},
}, manufacturer=MANUFACTURER,
"name": DEFAULT_NAME, name=DEFAULT_NAME,
"manufacturer": MANUFACTURER, configuration_url=URL.format(
"entry_type": "service", latitude=coordinator.latitude, longitude=coordinator.longitude
} ),
)
self._attr_name = f"{name} {description.name}" self._attr_name = f"{name} {description.name}"
self._attr_unique_id = ( self._attr_unique_id = (
f"{coordinator.latitude}-{coordinator.longitude}-{description.key}".lower() f"{coordinator.latitude}-{coordinator.longitude}-{description.key}".lower()

View File

@ -64,7 +64,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return True return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@ -1,7 +1,22 @@
{ {
"config": { "config": {
"abort": {
"already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e"
},
"error": { "error": {
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435",
"invalid_auth": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435",
"invalid_location": "\u041d\u044f\u043c\u0430 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438 \u0440\u0435\u0437\u0443\u043b\u0442\u0430\u0442\u0438 \u0437\u0430 \u0442\u043e\u0432\u0430 \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435",
"unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430" "unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430"
},
"step": {
"user": {
"data": {
"api_key": "API \u043a\u043b\u044e\u0447",
"latitude": "\u0413\u0435\u043e\u0433\u0440\u0430\u0444\u0441\u043a\u0430 \u0448\u0438\u0440\u0438\u043d\u0430",
"longitude": "\u0413\u0435\u043e\u0433\u0440\u0430\u0444\u0441\u043a\u0430 \u0434\u044a\u043b\u0436\u0438\u043d\u0430"
}
}
} }
} }
} }

View File

@ -22,12 +22,14 @@ from homeassistant.const import (
DEVICE_CLASS_PRESSURE, DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_SIGNAL_STRENGTH, DEVICE_CLASS_SIGNAL_STRENGTH,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
ENTITY_CATEGORY_DIAGNOSTIC,
PERCENTAGE, PERCENTAGE,
PRESSURE_MBAR, PRESSURE_MBAR,
SIGNAL_STRENGTH_DECIBELS, SIGNAL_STRENGTH_DECIBELS,
TEMP_CELSIUS, TEMP_CELSIUS,
) )
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import ( from homeassistant.helpers.update_coordinator import (
CoordinatorEntity, CoordinatorEntity,
@ -64,6 +66,7 @@ SENSORS: dict[str, SensorEntityDescription] = {
key="battery", key="battery",
device_class=DEVICE_CLASS_BATTERY, device_class=DEVICE_CLASS_BATTERY,
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
name="Battery", name="Battery",
), ),
"co2": SensorEntityDescription( "co2": SensorEntityDescription(
@ -96,6 +99,7 @@ SENSORS: dict[str, SensorEntityDescription] = {
device_class=DEVICE_CLASS_SIGNAL_STRENGTH, device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
name="RSSI", name="RSSI",
entity_registry_enabled_default=False, entity_registry_enabled_default=False,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
), ),
"pm1": SensorEntityDescription( "pm1": SensorEntityDescription(
key="pm1", key="pm1",
@ -152,11 +156,12 @@ class AirthingsHeaterEnergySensor(CoordinatorEntity, SensorEntity):
self._attr_name = f"{airthings_device.name} {entity_description.name}" self._attr_name = f"{airthings_device.name} {entity_description.name}"
self._attr_unique_id = f"{airthings_device.device_id}_{entity_description.key}" self._attr_unique_id = f"{airthings_device.device_id}_{entity_description.key}"
self._id = airthings_device.device_id self._id = airthings_device.device_id
self._attr_device_info = { self._attr_device_info = DeviceInfo(
"identifiers": {(DOMAIN, airthings_device.device_id)}, configuration_url="https://dashboard.airthings.com/",
"name": airthings_device.name, identifiers={(DOMAIN, airthings_device.device_id)},
"manufacturer": "Airthings", name=airthings_device.name,
} manufacturer="Airthings",
)
@property @property
def native_value(self) -> StateType: def native_value(self) -> StateType:

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "\u0410\u043a\u0430\u0443\u043d\u0442\u0430 \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d"
},
"error": {
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435",
"invalid_auth": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435",
"unknown": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430"
},
"step": {
"user": {
"data": {
"id": "ID"
}
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "\u00da\u010det je ji\u017e nastaven"
},
"error": {
"cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit",
"invalid_auth": "Neplatn\u00e9 ov\u011b\u0159en\u00ed",
"unknown": "Neo\u010dek\u00e1van\u00e1 chyba"
},
"step": {
"user": {
"data": {
"id": "ID"
}
}
}
}
}

View File

@ -0,0 +1,20 @@
{
"config": {
"abort": {
"already_configured": "Konto 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": {
"id": "ID",
"secret": "Sekret"
}
}
}
}
}

View File

@ -21,6 +21,7 @@ from homeassistant.components.climate.const import (
) )
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN from .const import DOMAIN
@ -96,14 +97,14 @@ class AirtouchAC(CoordinatorEntity, ClimateEntity):
return super()._handle_coordinator_update() return super()._handle_coordinator_update()
@property @property
def device_info(self): def device_info(self) -> DeviceInfo:
"""Return device info for this device.""" """Return device info for this device."""
return { return DeviceInfo(
"identifiers": {(DOMAIN, self.unique_id)}, identifiers={(DOMAIN, self.unique_id)},
"name": self.name, name=self.name,
"manufacturer": "Airtouch", manufacturer="Airtouch",
"model": "Airtouch 4", model="Airtouch 4",
} )
@property @property
def unique_id(self): def unique_id(self):
@ -211,14 +212,14 @@ class AirtouchGroup(CoordinatorEntity, ClimateEntity):
return super()._handle_coordinator_update() return super()._handle_coordinator_update()
@property @property
def device_info(self): def device_info(self) -> DeviceInfo:
"""Return device info for this device.""" """Return device info for this device."""
return { return DeviceInfo(
"identifiers": {(DOMAIN, self.unique_id)}, identifiers={(DOMAIN, self.unique_id)},
"name": self.name, manufacturer="Airtouch",
"manufacturer": "Airtouch", model="Airtouch 4",
"model": "Airtouch 4", name=self.name,
} )
@property @property
def unique_id(self): def unique_id(self):

View File

@ -0,0 +1,17 @@
{
"config": {
"abort": {
"already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e"
},
"error": {
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435"
},
"step": {
"user": {
"data": {
"host": "\u0425\u043e\u0441\u0442"
}
}
}
}
}

View File

@ -4,7 +4,7 @@ from __future__ import annotations
from collections.abc import Mapping from collections.abc import Mapping
from datetime import timedelta from datetime import timedelta
from math import ceil from math import ceil
from typing import Any from typing import Any, Dict, cast
from pyairvisual import CloudAPI, NodeSamba from pyairvisual import CloudAPI, NodeSamba
from pyairvisual.errors import ( from pyairvisual.errors import (
@ -54,8 +54,6 @@ from .const import (
PLATFORMS = ["sensor"] PLATFORMS = ["sensor"]
DATA_LISTENER = "listener"
DEFAULT_ATTRIBUTION = "Data provided by AirVisual" DEFAULT_ATTRIBUTION = "Data provided by AirVisual"
DEFAULT_NODE_PRO_UPDATE_INTERVAL = timedelta(minutes=1) DEFAULT_NODE_PRO_UPDATE_INTERVAL = timedelta(minutes=1)
@ -106,12 +104,12 @@ def async_get_cloud_coordinators_by_api_key(
hass: HomeAssistant, api_key: str hass: HomeAssistant, api_key: str
) -> list[DataUpdateCoordinator]: ) -> list[DataUpdateCoordinator]:
"""Get all DataUpdateCoordinator objects related to a particular API key.""" """Get all DataUpdateCoordinator objects related to a particular API key."""
coordinators = [] return [
for entry_id, coordinator in hass.data[DOMAIN][DATA_COORDINATOR].items(): attrs[DATA_COORDINATOR]
config_entry = hass.config_entries.async_get_entry(entry_id) for entry_id, attrs in hass.data[DOMAIN].items()
if config_entry and config_entry.data.get(CONF_API_KEY) == api_key: if (entry := hass.config_entries.async_get_entry(entry_id))
coordinators.append(coordinator) and entry.data.get(CONF_API_KEY) == api_key
return coordinators ]
@callback @callback
@ -139,25 +137,25 @@ def async_sync_geo_coordinator_update_intervals(
@callback @callback
def _standardize_geography_config_entry( def _standardize_geography_config_entry(
hass: HomeAssistant, config_entry: ConfigEntry hass: HomeAssistant, entry: ConfigEntry
) -> None: ) -> None:
"""Ensure that geography config entries have appropriate properties.""" """Ensure that geography config entries have appropriate properties."""
entry_updates = {} entry_updates = {}
if not config_entry.unique_id: if not entry.unique_id:
# If the config entry doesn't already have a unique ID, set one: # If the config entry doesn't already have a unique ID, set one:
entry_updates["unique_id"] = config_entry.data[CONF_API_KEY] entry_updates["unique_id"] = entry.data[CONF_API_KEY]
if not config_entry.options: if not entry.options:
# If the config entry doesn't already have any options set, set defaults: # If the config entry doesn't already have any options set, set defaults:
entry_updates["options"] = {CONF_SHOW_ON_MAP: True} entry_updates["options"] = {CONF_SHOW_ON_MAP: True}
if config_entry.data.get(CONF_INTEGRATION_TYPE) not in [ if entry.data.get(CONF_INTEGRATION_TYPE) not in [
INTEGRATION_TYPE_GEOGRAPHY_COORDS, INTEGRATION_TYPE_GEOGRAPHY_COORDS,
INTEGRATION_TYPE_GEOGRAPHY_NAME, INTEGRATION_TYPE_GEOGRAPHY_NAME,
]: ]:
# If the config entry data doesn't contain an integration type that we know # If the config entry data doesn't contain an integration type that we know
# about, infer it from the data we have: # about, infer it from the data we have:
entry_updates["data"] = {**config_entry.data} entry_updates["data"] = {**entry.data}
if CONF_CITY in config_entry.data: if CONF_CITY in entry.data:
entry_updates["data"][ entry_updates["data"][
CONF_INTEGRATION_TYPE CONF_INTEGRATION_TYPE
] = INTEGRATION_TYPE_GEOGRAPHY_NAME ] = INTEGRATION_TYPE_GEOGRAPHY_NAME
@ -169,55 +167,55 @@ def _standardize_geography_config_entry(
if not entry_updates: if not entry_updates:
return return
hass.config_entries.async_update_entry(config_entry, **entry_updates) hass.config_entries.async_update_entry(entry, **entry_updates)
@callback @callback
def _standardize_node_pro_config_entry( def _standardize_node_pro_config_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
hass: HomeAssistant, config_entry: ConfigEntry
) -> None:
"""Ensure that Node/Pro config entries have appropriate properties.""" """Ensure that Node/Pro config entries have appropriate properties."""
entry_updates: dict[str, Any] = {} entry_updates: dict[str, Any] = {}
if CONF_INTEGRATION_TYPE not in config_entry.data: if CONF_INTEGRATION_TYPE not in entry.data:
# If the config entry data doesn't contain the integration type, add it: # If the config entry data doesn't contain the integration type, add it:
entry_updates["data"] = { entry_updates["data"] = {
**config_entry.data, **entry.data,
CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_NODE_PRO, CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_NODE_PRO,
} }
if not entry_updates: if not entry_updates:
return return
hass.config_entries.async_update_entry(config_entry, **entry_updates) hass.config_entries.async_update_entry(entry, **entry_updates)
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up AirVisual as config entry.""" """Set up AirVisual as config entry."""
hass.data.setdefault(DOMAIN, {DATA_COORDINATOR: {}, DATA_LISTENER: {}}) hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = {}
if CONF_API_KEY in config_entry.data: if CONF_API_KEY in entry.data:
_standardize_geography_config_entry(hass, config_entry) _standardize_geography_config_entry(hass, entry)
websession = aiohttp_client.async_get_clientsession(hass) websession = aiohttp_client.async_get_clientsession(hass)
cloud_api = CloudAPI(config_entry.data[CONF_API_KEY], session=websession) cloud_api = CloudAPI(entry.data[CONF_API_KEY], session=websession)
async def async_update_data() -> dict[str, Any]: async def async_update_data() -> dict[str, Any]:
"""Get new data from the API.""" """Get new data from the API."""
if CONF_CITY in config_entry.data: if CONF_CITY in entry.data:
api_coro = cloud_api.air_quality.city( api_coro = cloud_api.air_quality.city(
config_entry.data[CONF_CITY], entry.data[CONF_CITY],
config_entry.data[CONF_STATE], entry.data[CONF_STATE],
config_entry.data[CONF_COUNTRY], entry.data[CONF_COUNTRY],
) )
else: else:
api_coro = cloud_api.air_quality.nearest_city( api_coro = cloud_api.air_quality.nearest_city(
config_entry.data[CONF_LATITUDE], entry.data[CONF_LATITUDE],
config_entry.data[CONF_LONGITUDE], entry.data[CONF_LONGITUDE],
) )
try: try:
return await api_coro data = await api_coro
return cast(Dict[str, Any], data)
except (InvalidKeyError, KeyExpiredError) as ex: except (InvalidKeyError, KeyExpiredError) as ex:
raise ConfigEntryAuthFailed from ex raise ConfigEntryAuthFailed from ex
except AirVisualError as err: except AirVisualError as err:
@ -226,7 +224,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
coordinator = DataUpdateCoordinator( coordinator = DataUpdateCoordinator(
hass, hass,
LOGGER, LOGGER,
name=async_get_geography_id(config_entry.data), name=async_get_geography_id(entry.data),
# We give a placeholder update interval in order to create the coordinator; # We give a placeholder update interval in order to create the coordinator;
# then, below, we use the coordinator's presence (along with any other # then, below, we use the coordinator's presence (along with any other
# coordinators using the same API key) to calculate an actual, leveled # coordinators using the same API key) to calculate an actual, leveled
@ -236,16 +234,14 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
) )
# Only geography-based entries have options: # Only geography-based entries have options:
hass.data[DOMAIN][DATA_LISTENER][ entry.async_on_unload(entry.add_update_listener(async_reload_entry))
config_entry.entry_id
] = config_entry.add_update_listener(async_reload_entry)
else: else:
# Remove outdated air_quality entities from the entity registry if they exist: # Remove outdated air_quality entities from the entity registry if they exist:
ent_reg = entity_registry.async_get(hass) ent_reg = entity_registry.async_get(hass)
for entity_entry in [ for entity_entry in [
e e
for e in ent_reg.entities.values() for e in ent_reg.entities.values()
if e.config_entry_id == config_entry.entry_id if e.config_entry_id == entry.entry_id
and e.entity_id.startswith("air_quality") and e.entity_id.startswith("air_quality")
]: ]:
LOGGER.debug( LOGGER.debug(
@ -253,15 +249,16 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
) )
ent_reg.async_remove(entity_entry.entity_id) ent_reg.async_remove(entity_entry.entity_id)
_standardize_node_pro_config_entry(hass, config_entry) _standardize_node_pro_config_entry(hass, entry)
async def async_update_data() -> dict[str, Any]: async def async_update_data() -> dict[str, Any]:
"""Get new data from the API.""" """Get new data from the API."""
try: try:
async with NodeSamba( async with NodeSamba(
config_entry.data[CONF_IP_ADDRESS], config_entry.data[CONF_PASSWORD] entry.data[CONF_IP_ADDRESS], entry.data[CONF_PASSWORD]
) as node: ) as node:
return await node.async_get_latest_measurements() data = await node.async_get_latest_measurements()
return cast(Dict[str, Any], data)
except NodeProError as err: except NodeProError as err:
raise UpdateFailed(f"Error while retrieving data: {err}") from err raise UpdateFailed(f"Error while retrieving data: {err}") from err
@ -274,41 +271,38 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
) )
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
hass.data[DOMAIN][entry.entry_id][DATA_COORDINATOR] = coordinator
hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id] = coordinator
# Reassess the interval between 2 server requests # Reassess the interval between 2 server requests
if CONF_API_KEY in config_entry.data: if CONF_API_KEY in entry.data:
async_sync_geo_coordinator_update_intervals( async_sync_geo_coordinator_update_intervals(hass, entry.data[CONF_API_KEY])
hass, config_entry.data[CONF_API_KEY]
)
hass.config_entries.async_setup_platforms(config_entry, PLATFORMS) hass.config_entries.async_setup_platforms(entry, PLATFORMS)
return True return True
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Migrate an old config entry.""" """Migrate an old config entry."""
version = config_entry.version version = entry.version
LOGGER.debug("Migrating from version %s", version) LOGGER.debug("Migrating from version %s", version)
# 1 -> 2: One geography per config entry # 1 -> 2: One geography per config entry
if version == 1: if version == 1:
version = config_entry.version = 2 version = entry.version = 2
# Update the config entry to only include the first geography (there is always # Update the config entry to only include the first geography (there is always
# guaranteed to be at least one): # guaranteed to be at least one):
geographies = list(config_entry.data[CONF_GEOGRAPHIES]) geographies = list(entry.data[CONF_GEOGRAPHIES])
first_geography = geographies.pop(0) first_geography = geographies.pop(0)
first_id = async_get_geography_id(first_geography) first_id = async_get_geography_id(first_geography)
hass.config_entries.async_update_entry( hass.config_entries.async_update_entry(
config_entry, entry,
unique_id=first_id, unique_id=first_id,
title=f"Cloud API ({first_id})", title=f"Cloud API ({first_id})",
data={CONF_API_KEY: config_entry.data[CONF_API_KEY], **first_geography}, data={CONF_API_KEY: entry.data[CONF_API_KEY], **first_geography},
) )
# For any geographies that remain, create a new config entry for each one: # For any geographies that remain, create a new config entry for each one:
@ -321,7 +315,7 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
hass.config_entries.flow.async_init( hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": source}, context={"source": source},
data={CONF_API_KEY: config_entry.data[CONF_API_KEY], **geography}, data={CONF_API_KEY: entry.data[CONF_API_KEY], **geography},
) )
) )
@ -330,42 +324,39 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
return True return True
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload an AirVisual config entry.""" """Unload an AirVisual config entry."""
unload_ok = await hass.config_entries.async_unload_platforms( unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
config_entry, PLATFORMS
)
if unload_ok: if unload_ok:
hass.data[DOMAIN][DATA_COORDINATOR].pop(config_entry.entry_id) hass.data[DOMAIN].pop(entry.entry_id)
remove_listener = hass.data[DOMAIN][DATA_LISTENER].pop(config_entry.entry_id) if CONF_API_KEY in entry.data:
remove_listener()
if CONF_API_KEY in config_entry.data:
# Re-calculate the update interval period for any remaining consumers of # Re-calculate the update interval period for any remaining consumers of
# this API key: # this API key:
async_sync_geo_coordinator_update_intervals( async_sync_geo_coordinator_update_intervals(hass, entry.data[CONF_API_KEY])
hass, config_entry.data[CONF_API_KEY]
)
return unload_ok return unload_ok
async def async_reload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> None: async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Handle an options update.""" """Handle an options update."""
await hass.config_entries.async_reload(config_entry.entry_id) await hass.config_entries.async_reload(entry.entry_id)
class AirVisualEntity(CoordinatorEntity): class AirVisualEntity(CoordinatorEntity):
"""Define a generic AirVisual entity.""" """Define a generic AirVisual entity."""
def __init__( def __init__(
self, coordinator: DataUpdateCoordinator, description: EntityDescription self,
coordinator: DataUpdateCoordinator,
entry: ConfigEntry,
description: EntityDescription,
) -> None: ) -> None:
"""Initialize.""" """Initialize."""
super().__init__(coordinator) super().__init__(coordinator)
self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION} self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION}
self._entry = entry
self.entity_description = description self.entity_description = description
async def async_added_to_hass(self) -> None: async def async_added_to_hass(self) -> None:

View File

@ -262,9 +262,9 @@ class AirVisualFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
class AirVisualOptionsFlowHandler(config_entries.OptionsFlow): class AirVisualOptionsFlowHandler(config_entries.OptionsFlow):
"""Handle an AirVisual options flow.""" """Handle an AirVisual options flow."""
def __init__(self, config_entry: ConfigEntry) -> None: def __init__(self, entry: ConfigEntry) -> None:
"""Initialize.""" """Initialize."""
self.config_entry = config_entry self.entry = entry
async def async_step_init( async def async_step_init(
self, user_input: dict[str, str] | None = None self, user_input: dict[str, str] | None = None
@ -279,7 +279,7 @@ class AirVisualOptionsFlowHandler(config_entries.OptionsFlow):
{ {
vol.Required( vol.Required(
CONF_SHOW_ON_MAP, CONF_SHOW_ON_MAP,
default=self.config_entry.options.get(CONF_SHOW_ON_MAP), default=self.entry.options.get(CONF_SHOW_ON_MAP),
): bool ): bool
} }
), ),

View File

@ -27,6 +27,7 @@ from homeassistant.const import (
DEVICE_CLASS_PM25, DEVICE_CLASS_PM25,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS, DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
ENTITY_CATEGORY_DIAGNOSTIC,
PERCENTAGE, PERCENTAGE,
TEMP_CELSIUS, TEMP_CELSIUS,
) )
@ -103,6 +104,7 @@ NODE_PRO_SENSOR_DESCRIPTIONS = (
key=SENSOR_KIND_BATTERY_LEVEL, key=SENSOR_KIND_BATTERY_LEVEL,
name="Battery", name="Battery",
device_class=DEVICE_CLASS_BATTERY, device_class=DEVICE_CLASS_BATTERY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
), ),
SensorEntityDescription( SensorEntityDescription(
@ -189,26 +191,24 @@ POLLUTANT_UNITS = {
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up AirVisual sensors based on a config entry.""" """Set up AirVisual sensors based on a config entry."""
coordinator = hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id] coordinator = hass.data[DOMAIN][entry.entry_id][DATA_COORDINATOR]
sensors: list[AirVisualGeographySensor | AirVisualNodeProSensor] sensors: list[AirVisualGeographySensor | AirVisualNodeProSensor]
if config_entry.data[CONF_INTEGRATION_TYPE] in ( if entry.data[CONF_INTEGRATION_TYPE] in (
INTEGRATION_TYPE_GEOGRAPHY_COORDS, INTEGRATION_TYPE_GEOGRAPHY_COORDS,
INTEGRATION_TYPE_GEOGRAPHY_NAME, INTEGRATION_TYPE_GEOGRAPHY_NAME,
): ):
sensors = [ sensors = [
AirVisualGeographySensor(coordinator, config_entry, description, locale) AirVisualGeographySensor(coordinator, entry, description, locale)
for locale in GEOGRAPHY_SENSOR_LOCALES for locale in GEOGRAPHY_SENSOR_LOCALES
for description in GEOGRAPHY_SENSOR_DESCRIPTIONS for description in GEOGRAPHY_SENSOR_DESCRIPTIONS
] ]
else: else:
sensors = [ sensors = [
AirVisualNodeProSensor(coordinator, description) AirVisualNodeProSensor(coordinator, entry, description)
for description in NODE_PRO_SENSOR_DESCRIPTIONS for description in NODE_PRO_SENSOR_DESCRIPTIONS
] ]
@ -221,23 +221,22 @@ class AirVisualGeographySensor(AirVisualEntity, SensorEntity):
def __init__( def __init__(
self, self,
coordinator: DataUpdateCoordinator, coordinator: DataUpdateCoordinator,
config_entry: ConfigEntry, entry: ConfigEntry,
description: SensorEntityDescription, description: SensorEntityDescription,
locale: str, locale: str,
) -> None: ) -> None:
"""Initialize.""" """Initialize."""
super().__init__(coordinator, description) super().__init__(coordinator, entry, description)
self._attr_extra_state_attributes.update( self._attr_extra_state_attributes.update(
{ {
ATTR_CITY: config_entry.data.get(CONF_CITY), ATTR_CITY: entry.data.get(CONF_CITY),
ATTR_STATE: config_entry.data.get(CONF_STATE), ATTR_STATE: entry.data.get(CONF_STATE),
ATTR_COUNTRY: config_entry.data.get(CONF_COUNTRY), ATTR_COUNTRY: entry.data.get(CONF_COUNTRY),
} }
) )
self._attr_name = f"{GEOGRAPHY_SENSOR_LOCALES[locale]} {description.name}" self._attr_name = f"{GEOGRAPHY_SENSOR_LOCALES[locale]} {description.name}"
self._attr_unique_id = f"{config_entry.unique_id}_{locale}_{description.key}" self._attr_unique_id = f"{entry.unique_id}_{locale}_{description.key}"
self._config_entry = config_entry
self._locale = locale self._locale = locale
@property @property
@ -279,16 +278,16 @@ class AirVisualGeographySensor(AirVisualEntity, SensorEntity):
# #
# We use any coordinates in the config entry and, in the case of a geography by # We use any coordinates in the config entry and, in the case of a geography by
# name, we fall back to the latitude longitude provided in the coordinator data: # name, we fall back to the latitude longitude provided in the coordinator data:
latitude = self._config_entry.data.get( latitude = self._entry.data.get(
CONF_LATITUDE, CONF_LATITUDE,
self.coordinator.data["location"]["coordinates"][1], self.coordinator.data["location"]["coordinates"][1],
) )
longitude = self._config_entry.data.get( longitude = self._entry.data.get(
CONF_LONGITUDE, CONF_LONGITUDE,
self.coordinator.data["location"]["coordinates"][0], self.coordinator.data["location"]["coordinates"][0],
) )
if self._config_entry.options[CONF_SHOW_ON_MAP]: if self._entry.options[CONF_SHOW_ON_MAP]:
self._attr_extra_state_attributes[ATTR_LATITUDE] = latitude self._attr_extra_state_attributes[ATTR_LATITUDE] = latitude
self._attr_extra_state_attributes[ATTR_LONGITUDE] = longitude self._attr_extra_state_attributes[ATTR_LONGITUDE] = longitude
self._attr_extra_state_attributes.pop("lati", None) self._attr_extra_state_attributes.pop("lati", None)
@ -304,10 +303,13 @@ class AirVisualNodeProSensor(AirVisualEntity, SensorEntity):
"""Define an AirVisual sensor related to a Node/Pro unit.""" """Define an AirVisual sensor related to a Node/Pro unit."""
def __init__( def __init__(
self, coordinator: DataUpdateCoordinator, description: SensorEntityDescription self,
coordinator: DataUpdateCoordinator,
entry: ConfigEntry,
description: SensorEntityDescription,
) -> None: ) -> None:
"""Initialize.""" """Initialize."""
super().__init__(coordinator, description) super().__init__(coordinator, entry, description)
self._attr_name = ( self._attr_name = (
f"{coordinator.data['settings']['node_name']} Node/Pro: {description.name}" f"{coordinator.data['settings']['node_name']} Node/Pro: {description.name}"
@ -317,16 +319,16 @@ class AirVisualNodeProSensor(AirVisualEntity, SensorEntity):
@property @property
def device_info(self) -> DeviceInfo: def device_info(self) -> DeviceInfo:
"""Return device registry information for this entity.""" """Return device registry information for this entity."""
return { return DeviceInfo(
"identifiers": {(DOMAIN, self.coordinator.data["serial_number"])}, identifiers={(DOMAIN, self.coordinator.data["serial_number"])},
"name": self.coordinator.data["settings"]["node_name"], manufacturer="AirVisual",
"manufacturer": "AirVisual", model=f'{self.coordinator.data["status"]["model"]}',
"model": f'{self.coordinator.data["status"]["model"]}', name=self.coordinator.data["settings"]["node_name"],
"sw_version": ( sw_version=(
f'Version {self.coordinator.data["status"]["system_version"]}' f'Version {self.coordinator.data["status"]["system_version"]}'
f'{self.coordinator.data["status"]["app_version"]}' f'{self.coordinator.data["status"]["app_version"]}'
), ),
} )
@callback @callback
def update_from_latest_data(self) -> None: def update_from_latest_data(self) -> None:

View File

@ -1,11 +1,30 @@
{ {
"config": { "config": {
"abort": {
"reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0442\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435 \u0431\u0435\u0448\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e"
},
"error": {
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435",
"general_error": "\u041d\u0435\u043e\u0447\u0430\u043a\u0432\u0430\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430",
"invalid_api_key": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d API \u043a\u043b\u044e\u0447"
},
"step": { "step": {
"geography_by_name": { "geography_by_name": {
"data": { "data": {
"city": "\u0413\u0440\u0430\u0434", "city": "\u0413\u0440\u0430\u0434",
"country": "\u0421\u0442\u0440\u0430\u043d\u0430" "country": "\u0421\u0442\u0440\u0430\u043d\u0430"
} }
},
"node_pro": {
"data": {
"ip_address": "\u0425\u043e\u0441\u0442",
"password": "\u041f\u0430\u0440\u043e\u043b\u0430"
}
},
"reauth_confirm": {
"data": {
"api_key": "API \u043a\u043b\u044e\u0447"
}
} }
} }
} }

View File

@ -2,7 +2,7 @@
"config": { "config": {
"abort": { "abort": {
"already_configured": "A hely m\u00e1r konfigur\u00e1lva van vagy a Node/Pro azonos\u00edt\u00f3 m\u00e1r regisztr\u00e1lva van.", "already_configured": "A hely m\u00e1r konfigur\u00e1lva van vagy a Node/Pro azonos\u00edt\u00f3 m\u00e1r regisztr\u00e1lva van.",
"reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt."
}, },
"error": { "error": {
"cannot_connect": "Sikertelen csatlakoz\u00e1s", "cannot_connect": "Sikertelen csatlakoz\u00e1s",

View File

@ -0,0 +1,9 @@
{
"state": {
"airvisual__pollutant_label": {
"o3": "\u041e\u0437\u043e\u043d",
"p1": "PM10",
"p2": "PM2.5"
}
}
}

View File

@ -0,0 +1,20 @@
{
"state": {
"airvisual__pollutant_label": {
"co": "\u4e00\u6c27\u5316\u78b3",
"n2": "\u4e8c\u6c27\u5316\u6c2e",
"o3": "\u81ed\u6c27",
"p1": "PM10",
"p2": "PM2.5",
"s2": "\u4e8c\u6c27\u5316\u786b"
},
"airvisual__pollutant_level": {
"good": "\u826f\u597d",
"hazardous": "\u5371\u5bb3\u5065\u5eb7",
"moderate": "\u4e2d\u7b49",
"unhealthy": "\u4e0d\u5229\u4e8e\u5065\u5eb7",
"unhealthy_sensitive": "\u4e0d\u5229\u4e8e\u654f\u611f\u4eba\u7fa4",
"very_unhealthy": "\u975e\u5e38\u4e0d\u5229\u4e8e\u5065\u5eb7"
}
}
}

View File

@ -48,9 +48,7 @@ async def _async_reproduce_state(
reproduce_options: dict[str, Any] | None = None, reproduce_options: dict[str, Any] | None = None,
) -> None: ) -> None:
"""Reproduce a single state.""" """Reproduce a single state."""
cur_state = hass.states.get(state.entity_id) if (cur_state := hass.states.get(state.entity_id)) is None:
if cur_state is None:
_LOGGER.warning("Unable to find entity %s", state.entity_id) _LOGGER.warning("Unable to find entity %s", state.entity_id)
return return

View File

@ -1,4 +1,30 @@
{ {
"device_automation": {
"action_type": {
"arm_away": "\u05d3\u05e8\u05d9\u05db\u05ea {entity_name} \u05dc\u05d0 \u05d1\u05d1\u05d9\u05ea",
"arm_home": "\u05d3\u05e8\u05d9\u05db\u05ea {entity_name} \u05d4\u05d1\u05d9\u05ea\u05d4",
"arm_night": "\u05d3\u05e8\u05d9\u05db\u05ea {entity_name} \u05dc\u05d9\u05dc\u05d4",
"arm_vacation": "\u05d3\u05e8\u05d9\u05db\u05ea {entity_name} \u05d7\u05d5\u05e4\u05e9\u05d4",
"disarm": "\u05e0\u05d9\u05d8\u05e8\u05d5\u05dc {entity_name}",
"trigger": "\u05d4\u05e4\u05e2\u05dc\u05ea {entity_name}"
},
"condition_type": {
"is_armed_away": "{entity_name} \u05d3\u05e8\u05d5\u05da \u05dc\u05d0 \u05d1\u05d1\u05d9\u05ea",
"is_armed_home": "{entity_name} \u05d3\u05e8\u05d5\u05da \u05d1\u05d9\u05ea",
"is_armed_night": "{entity_name} \u05d3\u05e8\u05d5\u05da \u05dc\u05d9\u05dc\u05d4",
"is_armed_vacation": "{entity_name} \u05d1\u05d7\u05d5\u05e4\u05e9\u05d4 \u05d3\u05e8\u05d5\u05db\u05d4",
"is_disarmed": "{entity_name} \u05de\u05e0\u05d5\u05d8\u05e8\u05dc",
"is_triggered": "{entity_name} \u05de\u05d5\u05e4\u05e2\u05dc"
},
"trigger_type": {
"armed_away": "{entity_name} \u05d3\u05e8\u05d5\u05da \u05dc\u05d0 \u05d1\u05d1\u05d9\u05ea",
"armed_home": "{entity_name} \u05d3\u05e8\u05d5\u05da \u05d1\u05d1\u05d9\u05ea",
"armed_night": "{entity_name} \u05d3\u05e8\u05d5\u05da \u05dc\u05d9\u05dc\u05d4",
"armed_vacation": "{entity_name} \u05d7\u05d5\u05e4\u05e9\u05d4 \u05d3\u05e8\u05d5\u05db\u05d4",
"disarmed": "{entity_name} \u05de\u05e0\u05d5\u05d8\u05e8\u05dc",
"triggered": "{entity_name} \u05de\u05d5\u05e4\u05e2\u05dc"
}
},
"state": { "state": {
"_": { "_": {
"armed": "\u05d3\u05e8\u05d5\u05da", "armed": "\u05d3\u05e8\u05d5\u05da",

View File

@ -1,23 +1,40 @@
{ {
"device_automation": { "device_automation": {
"action_type": {
"arm_away": "D\u0131\u015farda",
"arm_home": "Evde",
"arm_night": "Gece",
"disarm": "Devre d\u0131\u015f\u0131 {entity_name}",
"trigger": "Tetikle {entity_name}"
},
"condition_type": {
"is_armed_away": "{entity_name} D\u0131\u015farda Modu Aktif",
"is_armed_home": "{entity_name} Evde Modu Aktif",
"is_armed_night": "{entity_name} Gece Modu Aktif",
"is_disarmed": "{entity_name} Devre D\u0131\u015f\u0131",
"is_triggered": "{entity_name} tetiklendi"
},
"trigger_type": { "trigger_type": {
"disarmed": "{entity_name} b\u0131rak\u0131ld\u0131", "armed_away": "{entity_name} D\u0131\u015farda Modu Aktif",
"armed_home": "{entity_name} Evde Modu Aktif",
"armed_night": "{entity_name} Gece Modu Aktif",
"disarmed": "{entity_name} Devre D\u0131\u015f\u0131",
"triggered": "{entity_name} tetiklendi" "triggered": "{entity_name} tetiklendi"
} }
}, },
"state": { "state": {
"_": { "_": {
"armed": "Etkin", "armed": "Aktif",
"armed_away": "Etkin d\u0131\u015far\u0131da", "armed_away": "D\u0131\u015farda Aktif",
"armed_custom_bypass": "Alarm etkin \u00f6zel baypas", "armed_custom_bypass": "\u00d6zel Mod Aktif",
"armed_home": "Etkin evde", "armed_home": "Evde Aktif",
"armed_night": "Etkin gece", "armed_night": "Gece Aktif",
"arming": "Alarm etkinle\u015fiyor", "arming": "Alarm etkinle\u015fiyor",
"disarmed": "Etkisiz", "disarmed": "Devre D\u0131\u015f\u0131",
"disarming": "Alarm devre d\u0131\u015f\u0131", "disarming": "Alarm devre d\u0131\u015f\u0131",
"pending": "Beklemede", "pending": "Beklemede",
"triggered": "Tetiklendi" "triggered": "Tetiklendi"
} }
}, },
"title": "Alarm kontrol paneli" "title": "Alarm Kontrol Paneli"
} }

View File

@ -29,6 +29,7 @@
"armed_custom_bypass": "\u81ea\u5b9a\u4e49\u533a\u57df\u8b66\u6212", "armed_custom_bypass": "\u81ea\u5b9a\u4e49\u533a\u57df\u8b66\u6212",
"armed_home": "\u5728\u5bb6\u8b66\u6212", "armed_home": "\u5728\u5bb6\u8b66\u6212",
"armed_night": "\u591c\u95f4\u8b66\u6212", "armed_night": "\u591c\u95f4\u8b66\u6212",
"armed_vacation": "\u5ea6\u5047\u8b66\u6212",
"arming": "\u8b66\u6212\u4e2d", "arming": "\u8b66\u6212\u4e2d",
"disarmed": "\u8b66\u6212\u89e3\u9664", "disarmed": "\u8b66\u6212\u89e3\u9664",
"disarming": "\u8b66\u6212\u89e3\u9664", "disarming": "\u8b66\u6212\u89e3\u9664",

View File

@ -0,0 +1,20 @@
{
"config": {
"abort": {
"already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e"
},
"step": {
"protocol": {
"data": {
"host": "\u0425\u043e\u0441\u0442",
"port": "\u041f\u043e\u0440\u0442"
}
},
"user": {
"data": {
"protocol": "\u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b"
}
}
}
}
}

View File

@ -34,7 +34,9 @@
"data": { "data": {
"zone_name": "B\u00f6lge Ad\u0131", "zone_name": "B\u00f6lge Ad\u0131",
"zone_relayaddr": "R\u00f6le Adresi", "zone_relayaddr": "R\u00f6le Adresi",
"zone_relaychan": "R\u00f6le Kanal\u0131" "zone_relaychan": "R\u00f6le Kanal\u0131",
"zone_rfid": "RF Id",
"zone_type": "B\u00f6lge Tipi"
} }
}, },
"zone_select": { "zone_select": {

View File

@ -221,8 +221,7 @@ class Alert(ToggleEntity):
async def watched_entity_change(self, ev): async def watched_entity_change(self, ev):
"""Determine if the alert should start or stop.""" """Determine if the alert should start or stop."""
to_state = ev.data.get("new_state") if (to_state := ev.data.get("new_state")) is None:
if to_state is None:
return return
_LOGGER.debug("Watched entity (%s) has changed", ev.data.get("entity_id")) _LOGGER.debug("Watched entity (%s) has changed", ev.data.get("entity_id"))
if to_state.state == self._alert_state and not self._firing: if to_state.state == self._alert_state and not self._firing:

View File

@ -30,9 +30,7 @@ async def _async_reproduce_state(
reproduce_options: dict[str, Any] | None = None, reproduce_options: dict[str, Any] | None = None,
) -> None: ) -> None:
"""Reproduce a single state.""" """Reproduce a single state."""
cur_state = hass.states.get(state.entity_id) if (cur_state := hass.states.get(state.entity_id)) is None:
if cur_state is None:
_LOGGER.warning("Unable to find entity %s", state.entity_id) _LOGGER.warning("Unable to find entity %s", state.entity_id)
return return

View File

@ -1,13 +1,14 @@
"""Support for Alexa skill auth.""" """Support for Alexa skill auth."""
import asyncio import asyncio
from datetime import timedelta from datetime import timedelta
from http import HTTPStatus
import json import json
import logging import logging
import aiohttp import aiohttp
import async_timeout import async_timeout
from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET, HTTP_OK from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers import aiohttp_client from homeassistant.helpers import aiohttp_client
from homeassistant.util import dt from homeassistant.util import dt
@ -119,7 +120,7 @@ class Auth:
_LOGGER.debug("LWA response header: %s", response.headers) _LOGGER.debug("LWA response header: %s", response.headers)
_LOGGER.debug("LWA response status: %s", response.status) _LOGGER.debug("LWA response status: %s", response.status)
if response.status != HTTP_OK: if response.status != HTTPStatus.OK:
_LOGGER.error("Error calling LWA to get auth token") _LOGGER.error("Error calling LWA to get auth token")
return None return None

View File

@ -182,8 +182,7 @@ class AlexaCapability:
"""Serialize according to the Discovery API.""" """Serialize according to the Discovery API."""
result = {"type": "AlexaInterface", "interface": self.name(), "version": "3"} result = {"type": "AlexaInterface", "interface": self.name(), "version": "3"}
instance = self.instance if (instance := self.instance) is not None:
if instance is not None:
result["instance"] = instance result["instance"] = instance
properties_supported = self.properties_supported() properties_supported = self.properties_supported()
@ -264,8 +263,7 @@ class AlexaCapability:
"timeOfSample": dt_util.utcnow().strftime(DATE_FORMAT), "timeOfSample": dt_util.utcnow().strftime(DATE_FORMAT),
"uncertaintyInMilliseconds": 0, "uncertaintyInMilliseconds": 0,
} }
instance = self.instance if (instance := self.instance) is not None:
if instance is not None:
result["instance"] = instance result["instance"] = instance
yield result yield result
@ -1098,8 +1096,7 @@ class AlexaThermostatController(AlexaCapability):
supported_modes = [] supported_modes = []
hvac_modes = self.entity.attributes.get(climate.ATTR_HVAC_MODES) hvac_modes = self.entity.attributes.get(climate.ATTR_HVAC_MODES)
for mode in hvac_modes: for mode in hvac_modes:
thermostat_mode = API_THERMOSTAT_MODES.get(mode) if thermostat_mode := API_THERMOSTAT_MODES.get(mode):
if thermostat_mode:
supported_modes.append(thermostat_mode) supported_modes.append(thermostat_mode)
preset_modes = self.entity.attributes.get(climate.ATTR_PRESET_MODES) preset_modes = self.entity.attributes.get(climate.ATTR_PRESET_MODES)
@ -1538,7 +1535,9 @@ class AlexaRangeController(AlexaCapability):
labels=["Percentage", AlexaGlobalCatalog.SETTING_FAN_SPEED], labels=["Percentage", AlexaGlobalCatalog.SETTING_FAN_SPEED],
min_value=0, min_value=0,
max_value=100, max_value=100,
precision=percentage_step if percentage_step else 100, # precision must be a divider of 100 and must be an integer; set step
# size to 1 for a consistent behavior except for on/off fans
precision=1 if percentage_step else 100,
unit=AlexaGlobalCatalog.UNIT_PERCENT, unit=AlexaGlobalCatalog.UNIT_PERCENT,
) )
return self._resource.serialize_capability_resources() return self._resource.serialize_capability_resources()

View File

@ -1,11 +1,12 @@
"""Support for Alexa skill service end point.""" """Support for Alexa skill service end point."""
import copy import copy
import hmac import hmac
from http import HTTPStatus
import logging import logging
import uuid import uuid
from homeassistant.components import http from homeassistant.components import http
from homeassistant.const import CONF_PASSWORD, HTTP_NOT_FOUND, HTTP_UNAUTHORIZED from homeassistant.const import CONF_PASSWORD
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.helpers import template from homeassistant.helpers import template
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
@ -58,7 +59,7 @@ class AlexaFlashBriefingView(http.HomeAssistantView):
if request.query.get(API_PASSWORD) is None: if request.query.get(API_PASSWORD) is None:
err = "No password provided for Alexa flash briefing: %s" err = "No password provided for Alexa flash briefing: %s"
_LOGGER.error(err, briefing_id) _LOGGER.error(err, briefing_id)
return b"", HTTP_UNAUTHORIZED return b"", HTTPStatus.UNAUTHORIZED
if not hmac.compare_digest( if not hmac.compare_digest(
request.query[API_PASSWORD].encode("utf-8"), request.query[API_PASSWORD].encode("utf-8"),
@ -66,12 +67,12 @@ class AlexaFlashBriefingView(http.HomeAssistantView):
): ):
err = "Wrong password for Alexa flash briefing: %s" err = "Wrong password for Alexa flash briefing: %s"
_LOGGER.error(err, briefing_id) _LOGGER.error(err, briefing_id)
return b"", HTTP_UNAUTHORIZED return b"", HTTPStatus.UNAUTHORIZED
if not isinstance(self.flash_briefings.get(briefing_id), list): if not isinstance(self.flash_briefings.get(briefing_id), list):
err = "No configured Alexa flash briefing was found for: %s" err = "No configured Alexa flash briefing was found for: %s"
_LOGGER.error(err, briefing_id) _LOGGER.error(err, briefing_id)
return b"", HTTP_NOT_FOUND return b"", HTTPStatus.NOT_FOUND
briefing = [] briefing = []
@ -93,8 +94,7 @@ class AlexaFlashBriefingView(http.HomeAssistantView):
else: else:
output[ATTR_MAIN_TEXT] = item.get(CONF_TEXT) output[ATTR_MAIN_TEXT] = item.get(CONF_TEXT)
uid = item.get(CONF_UID) if (uid := item.get(CONF_UID)) is None:
if uid is None:
uid = str(uuid.uuid4()) uid = str(uuid.uuid4())
output[ATTR_UID] = uid output[ATTR_UID] = uid

View File

@ -117,8 +117,7 @@ async def async_api_accept_grant(hass, config, directive, context):
async def async_api_turn_on(hass, config, directive, context): async def async_api_turn_on(hass, config, directive, context):
"""Process a turn on request.""" """Process a turn on request."""
entity = directive.entity entity = directive.entity
domain = entity.domain if (domain := entity.domain) == group.DOMAIN:
if domain == group.DOMAIN:
domain = ha.DOMAIN domain = ha.DOMAIN
service = SERVICE_TURN_ON service = SERVICE_TURN_ON
@ -1151,8 +1150,7 @@ async def async_api_adjust_range(hass, config, directive, context):
if instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}": if instance == f"{cover.DOMAIN}.{cover.ATTR_POSITION}":
range_delta = int(range_delta * 20) if range_delta_default else int(range_delta) range_delta = int(range_delta * 20) if range_delta_default else int(range_delta)
service = SERVICE_SET_COVER_POSITION service = SERVICE_SET_COVER_POSITION
current = entity.attributes.get(cover.ATTR_POSITION) if not (current := entity.attributes.get(cover.ATTR_POSITION)):
if not current:
msg = f"Unable to determine {entity.entity_id} current position" msg = f"Unable to determine {entity.entity_id} current position"
raise AlexaInvalidValueError(msg) raise AlexaInvalidValueError(msg)
position = response_value = min(100, max(0, range_delta + current)) position = response_value = min(100, max(0, range_delta + current))
@ -1188,8 +1186,7 @@ async def async_api_adjust_range(hass, config, directive, context):
else int(range_delta) else int(range_delta)
) )
service = fan.SERVICE_SET_PERCENTAGE service = fan.SERVICE_SET_PERCENTAGE
current = entity.attributes.get(fan.ATTR_PERCENTAGE) if not (current := entity.attributes.get(fan.ATTR_PERCENTAGE)):
if not current:
msg = f"Unable to determine {entity.entity_id} current fan speed" msg = f"Unable to determine {entity.entity_id} current fan speed"
raise AlexaInvalidValueError(msg) raise AlexaInvalidValueError(msg)
percentage = response_value = min(100, max(0, range_delta + current)) percentage = response_value = min(100, max(0, range_delta + current))

View File

@ -120,9 +120,7 @@ async def async_handle_message(hass, message):
req = message.get("request") req = message.get("request")
req_type = req["type"] req_type = req["type"]
handler = HANDLERS.get(req_type) if not (handler := HANDLERS.get(req_type)):
if not handler:
raise UnknownRequest(f"Received unknown request {req_type}") raise UnknownRequest(f"Received unknown request {req_type}")
return await handler(hass, message) return await handler(hass, message)

View File

@ -12,9 +12,8 @@ def async_describe_events(hass, async_describe_event):
def async_describe_logbook_event(event): def async_describe_logbook_event(event):
"""Describe a logbook event.""" """Describe a logbook event."""
data = event.data data = event.data
entity_id = data["request"].get("entity_id")
if entity_id: if entity_id := data["request"].get("entity_id"):
state = hass.states.get(entity_id) state = hass.states.get(entity_id)
name = state.name if state else entity_id name = state.name if state else entity_id
message = f"sent command {data['request']['namespace']}/{data['request']['name']} for {name}" message = f"sent command {data['request']['namespace']}/{data['request']['name']} for {name}"

View File

@ -3,7 +3,13 @@ import logging
from homeassistant import core from homeassistant import core
from homeassistant.components.http.view import HomeAssistantView from homeassistant.components.http.view import HomeAssistantView
from homeassistant.const import CONF_CLIENT_ID, CONF_CLIENT_SECRET from homeassistant.const import (
CONF_CLIENT_ID,
CONF_CLIENT_SECRET,
ENTITY_CATEGORY_CONFIG,
ENTITY_CATEGORY_DIAGNOSTIC,
)
from homeassistant.helpers import entity_registry as er
from .auth import Auth from .auth import Auth
from .config import AbstractConfig from .config import AbstractConfig
@ -60,7 +66,18 @@ class AlexaConfig(AbstractConfig):
def should_expose(self, entity_id): def should_expose(self, entity_id):
"""If an entity should be exposed.""" """If an entity should be exposed."""
return self._config[CONF_FILTER](entity_id) if not self._config[CONF_FILTER].empty_filter:
return self._config[CONF_FILTER](entity_id)
entity_registry = er.async_get(self.hass)
if registry_entry := entity_registry.async_get(entity_id):
auxiliary_entity = registry_entry.entity_category in (
ENTITY_CATEGORY_CONFIG,
ENTITY_CATEGORY_DIAGNOSTIC,
)
else:
auxiliary_entity = False
return not auxiliary_entity
@core.callback @core.callback
def async_invalidate_access_token(self): def async_invalidate_access_token(self):

View File

@ -2,13 +2,14 @@
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
from http import HTTPStatus
import json import json
import logging import logging
import aiohttp import aiohttp
import async_timeout import async_timeout
from homeassistant.const import HTTP_ACCEPTED, MATCH_ALL, STATE_ON from homeassistant.const import MATCH_ALL, STATE_ON
from homeassistant.core import HomeAssistant, State, callback from homeassistant.core import HomeAssistant, State, callback
from homeassistant.helpers.significant_change import create_checker from homeassistant.helpers.significant_change import create_checker
import homeassistant.util.dt as dt_util import homeassistant.util.dt as dt_util
@ -148,7 +149,7 @@ async def async_send_changereport_message(
_LOGGER.debug("Sent: %s", json.dumps(message_serialized)) _LOGGER.debug("Sent: %s", json.dumps(message_serialized))
_LOGGER.debug("Received (%s): %s", response.status, response_text) _LOGGER.debug("Received (%s): %s", response.status, response_text)
if response.status == HTTP_ACCEPTED: if response.status == HTTPStatus.ACCEPTED:
return return
response_json = json.loads(response_text) response_json = json.loads(response_text)
@ -279,7 +280,7 @@ async def async_send_doorbell_event_message(hass, config, alexa_entity):
_LOGGER.debug("Sent: %s", json.dumps(message_serialized)) _LOGGER.debug("Sent: %s", json.dumps(message_serialized))
_LOGGER.debug("Received (%s): %s", response.status, response_text) _LOGGER.debug("Received (%s): %s", response.status, response_text)
if response.status == HTTP_ACCEPTED: if response.status == HTTPStatus.ACCEPTED:
return return
response_json = json.loads(response_text) response_json = json.loads(response_text)

View File

@ -2,7 +2,8 @@
"config": { "config": {
"abort": { "abort": {
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435 \u0441 Almond \u0441\u044a\u0440\u0432\u044a\u0440\u0430.", "cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435 \u0441 Almond \u0441\u044a\u0440\u0432\u044a\u0440\u0430.",
"missing_configuration": "\u041c\u043e\u043b\u044f, \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u0442\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f\u0442\u0430 \u043a\u0430\u043a \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 Almond." "missing_configuration": "\u041c\u043e\u043b\u044f, \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u0442\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f\u0442\u0430 \u043a\u0430\u043a \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 Almond.",
"single_instance_allowed": "\u0412\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e. \u0412\u044a\u0437\u043c\u043e\u0436\u043d\u0430 \u0435 \u0441\u0430\u043c\u043e \u0435\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f."
}, },
"step": { "step": {
"pick_implementation": { "pick_implementation": {

View File

@ -21,7 +21,6 @@ DOMAIN: Final = "ambee"
LOGGER = logging.getLogger(__package__) LOGGER = logging.getLogger(__package__)
SCAN_INTERVAL = timedelta(hours=1) SCAN_INTERVAL = timedelta(hours=1)
ATTR_ENTRY_TYPE: Final = "entry_type"
ENTRY_TYPE_SERVICE: Final = "service" ENTRY_TYPE_SERVICE: Final = "service"
DEVICE_CLASS_AMBEE_RISK: Final = "ambee__risk" DEVICE_CLASS_AMBEE_RISK: Final = "ambee__risk"

View File

@ -3,7 +3,7 @@
"name": "Ambee", "name": "Ambee",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/ambee", "documentation": "https://www.home-assistant.io/integrations/ambee",
"requirements": ["ambee==0.3.0"], "requirements": ["ambee==0.4.0"],
"codeowners": ["@frenck"], "codeowners": ["@frenck"],
"quality_scale": "platinum", "quality_scale": "platinum",
"iot_class": "cloud_polling" "iot_class": "cloud_polling"

View File

@ -7,8 +7,8 @@ from homeassistant.components.sensor import (
SensorEntityDescription, SensorEntityDescription,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_IDENTIFIERS, ATTR_MANUFACTURER, ATTR_NAME
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import ( from homeassistant.helpers.update_coordinator import (
@ -16,7 +16,7 @@ from homeassistant.helpers.update_coordinator import (
DataUpdateCoordinator, DataUpdateCoordinator,
) )
from .const import ATTR_ENTRY_TYPE, DOMAIN, ENTRY_TYPE_SERVICE, SENSORS, SERVICES from .const import DOMAIN, ENTRY_TYPE_SERVICE, SENSORS, SERVICES
async def async_setup_entry( async def async_setup_entry(
@ -58,12 +58,12 @@ class AmbeeSensorEntity(CoordinatorEntity, SensorEntity):
self.entity_description = description self.entity_description = description
self._attr_unique_id = f"{entry_id}_{service_key}_{description.key}" self._attr_unique_id = f"{entry_id}_{service_key}_{description.key}"
self._attr_device_info = { self._attr_device_info = DeviceInfo(
ATTR_IDENTIFIERS: {(DOMAIN, f"{entry_id}_{service_key}")}, entry_type=ENTRY_TYPE_SERVICE,
ATTR_NAME: service, identifiers={(DOMAIN, f"{entry_id}_{service_key}")},
ATTR_MANUFACTURER: "Ambee", manufacturer="Ambee",
ATTR_ENTRY_TYPE: ENTRY_TYPE_SERVICE, name=service,
} )
@property @property
def native_value(self) -> StateType: def native_value(self) -> StateType:

View File

@ -0,0 +1,26 @@
{
"config": {
"abort": {
"reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0442\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435 \u0431\u0435\u0448\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e"
},
"error": {
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435",
"invalid_api_key": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d API \u043a\u043b\u044e\u0447"
},
"step": {
"reauth_confirm": {
"data": {
"api_key": "API \u043a\u043b\u044e\u0447"
}
},
"user": {
"data": {
"api_key": "API \u043a\u043b\u044e\u0447",
"latitude": "\u0413\u0435\u043e\u0433\u0440\u0430\u0444\u0441\u043a\u0430 \u0448\u0438\u0440\u0438\u043d\u0430",
"longitude": "\u0413\u0435\u043e\u0433\u0440\u0430\u0444\u0441\u043a\u0430 \u0434\u044a\u043b\u0436\u0438\u043d\u0430",
"name": "\u0418\u043c\u0435"
}
}
}
}
}

View File

@ -1,7 +1,7 @@
{ {
"config": { "config": {
"abort": { "abort": {
"reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt" "reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt."
}, },
"error": { "error": {
"cannot_connect": "Sikertelen csatlakoz\u00e1s", "cannot_connect": "Sikertelen csatlakoz\u00e1s",

View File

@ -11,7 +11,7 @@
"reauth_confirm": { "reauth_confirm": {
"data": { "data": {
"api_key": "\u041a\u043b\u044e\u0447 API", "api_key": "\u041a\u043b\u044e\u0447 API",
"description": "\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 Ambee." "description": "\u0422\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u0430\u044f \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044f \u0443\u0447\u0435\u0442\u043d\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 Ambee"
} }
}, },
"user": { "user": {

View File

@ -10,7 +10,6 @@ from homeassistant.components.binary_sensor import (
BinarySensorEntityDescription, BinarySensorEntityDescription,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_ATTRIBUTION
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
@ -28,6 +27,8 @@ PRICE_SPIKE_ICONS = {
class AmberPriceGridSensor(CoordinatorEntity, BinarySensorEntity): class AmberPriceGridSensor(CoordinatorEntity, BinarySensorEntity):
"""Sensor to show single grid binary values.""" """Sensor to show single grid binary values."""
_attr_attribution = ATTRIBUTION
def __init__( def __init__(
self, self,
coordinator: AmberUpdateCoordinator, coordinator: AmberUpdateCoordinator,
@ -38,7 +39,6 @@ class AmberPriceGridSensor(CoordinatorEntity, BinarySensorEntity):
self.site_id = coordinator.site_id self.site_id = coordinator.site_id
self.entity_description = description self.entity_description = description
self._attr_unique_id = f"{coordinator.site_id}-{description.key}" self._attr_unique_id = f"{coordinator.site_id}-{description.key}"
self._attr_device_state_attributes = {ATTR_ATTRIBUTION: ATTRIBUTION}
@property @property
def is_on(self) -> bool | None: def is_on(self) -> bool | None:
@ -67,7 +67,6 @@ class AmberPriceSpikeBinarySensor(AmberPriceGridSensor):
spike_status = self.coordinator.data["grid"]["price_spike"] spike_status = self.coordinator.data["grid"]["price_spike"]
return { return {
"spike_status": spike_status, "spike_status": spike_status,
ATTR_ATTRIBUTION: ATTRIBUTION,
} }

View File

@ -20,7 +20,7 @@ from homeassistant.components.sensor import (
SensorEntityDescription, SensorEntityDescription,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_ATTRIBUTION, CURRENCY_DOLLAR, ENERGY_KILO_WATT_HOUR from homeassistant.const import CURRENCY_DOLLAR, ENERGY_KILO_WATT_HOUR
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
@ -54,6 +54,8 @@ def friendly_channel_type(channel_type: str) -> str:
class AmberSensor(CoordinatorEntity, SensorEntity): class AmberSensor(CoordinatorEntity, SensorEntity):
"""Amber Base Sensor.""" """Amber Base Sensor."""
_attr_attribution = ATTRIBUTION
def __init__( def __init__(
self, self,
coordinator: AmberUpdateCoordinator, coordinator: AmberUpdateCoordinator,
@ -88,7 +90,7 @@ class AmberPriceSensor(AmberSensor):
"""Return additional pieces of information about the price.""" """Return additional pieces of information about the price."""
interval = self.coordinator.data[self.entity_description.key][self.channel_type] interval = self.coordinator.data[self.entity_description.key][self.channel_type]
data: dict[str, Any] = {ATTR_ATTRIBUTION: ATTRIBUTION} data: dict[str, Any] = {}
if interval is None: if interval is None:
return data return data
@ -143,7 +145,6 @@ class AmberForecastSensor(AmberSensor):
data = { data = {
"forecasts": [], "forecasts": [],
"channel_type": intervals[0].channel_type.value, "channel_type": intervals[0].channel_type.value,
ATTR_ATTRIBUTION: ATTRIBUTION,
} }
for interval in intervals: for interval in intervals:
@ -172,6 +173,8 @@ class AmberForecastSensor(AmberSensor):
class AmberGridSensor(CoordinatorEntity, SensorEntity): class AmberGridSensor(CoordinatorEntity, SensorEntity):
"""Sensor to show single grid specific values.""" """Sensor to show single grid specific values."""
_attr_attribution = ATTRIBUTION
def __init__( def __init__(
self, self,
coordinator: AmberUpdateCoordinator, coordinator: AmberUpdateCoordinator,
@ -181,7 +184,6 @@ class AmberGridSensor(CoordinatorEntity, SensorEntity):
super().__init__(coordinator) super().__init__(coordinator)
self.site_id = coordinator.site_id self.site_id = coordinator.site_id
self.entity_description = description self.entity_description = description
self._attr_device_state_attributes = {ATTR_ATTRIBUTION: ATTRIBUTION}
self._attr_unique_id = f"{coordinator.site_id}-{description.key}" self._attr_unique_id = f"{coordinator.site_id}-{description.key}"
@property @property

View File

@ -0,0 +1,13 @@
{
"config": {
"step": {
"site": {
"title": "Amber Electric"
},
"user": {
"description": "\u041e\u0442\u0438\u0434\u0435\u0442\u0435 \u043d\u0430 {api_url}, \u0437\u0430 \u0434\u0430 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0430\u0442\u0435 API \u043a\u043b\u044e\u0447",
"title": "Amber Electric"
}
}
}
}

View File

@ -0,0 +1,11 @@
{
"config": {
"step": {
"user": {
"data": {
"api_token": "API\u30c8\u30fc\u30af\u30f3"
}
}
}
}
}

View File

@ -0,0 +1,9 @@
{
"config": {
"step": {
"user": {
"title": "Amber Electric"
}
}
}
}

View File

@ -21,6 +21,7 @@ from homeassistant.const import (
) )
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity import DeviceInfo
from .const import ( from .const import (
ATTR_VALUE, ATTR_VALUE,
@ -149,16 +150,15 @@ class AmbiclimateEntity(ClimateEntity):
self._store = store self._store = store
self._attr_unique_id = heater.device_id self._attr_unique_id = heater.device_id
self._attr_name = heater.name self._attr_name = heater.name
self._attr_device_info = { self._attr_device_info = DeviceInfo(
"identifiers": {(DOMAIN, self.unique_id)}, identifiers={(DOMAIN, self.unique_id)},
"name": self.name, manufacturer="Ambiclimate",
"manufacturer": "Ambiclimate", name=self.name,
} )
async def async_set_temperature(self, **kwargs: Any) -> None: async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature.""" """Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE) if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None:
if temperature is None:
return return
await self._heater.set_target_temperature(temperature) await self._heater.set_target_temperature(temperature)

View File

@ -143,8 +143,7 @@ class AmbiclimateAuthCallbackView(HomeAssistantView):
async def get(self, request: web.Request) -> str: async def get(self, request: web.Request) -> str:
"""Receive authorization token.""" """Receive authorization token."""
# pylint: disable=no-self-use # pylint: disable=no-self-use
code = request.query.get("code") if (code := request.query.get("code")) is None:
if code is None:
return "No code" return "No code"
hass = request.app["hass"] hass = request.app["hass"]
hass.async_create_task( hass.async_create_task(

View File

@ -1,7 +1,8 @@
{ {
"config": { "config": {
"abort": { "abort": {
"access_token": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430 \u043f\u0440\u0438 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u043a\u043e\u0434 \u0437\u0430 \u0434\u043e\u0441\u0442\u044a\u043f." "access_token": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430 \u0433\u0440\u0435\u0448\u043a\u0430 \u043f\u0440\u0438 \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u043a\u043e\u0434 \u0437\u0430 \u0434\u043e\u0441\u0442\u044a\u043f.",
"already_configured": "\u0410\u043a\u0430\u0443\u043d\u0442\u0430 \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d"
}, },
"create_entry": { "create_entry": {
"default": "\u0423\u0441\u043f\u0435\u0448\u043d\u043e \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u043a\u0438\u0440\u0430\u043d\u0435 \u0441 Ambiclimate." "default": "\u0423\u0441\u043f\u0435\u0448\u043d\u043e \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u043a\u0438\u0440\u0430\u043d\u0435 \u0441 Ambiclimate."

View File

@ -3,7 +3,7 @@ from __future__ import annotations
from typing import Any from typing import Any
from aioambient import Client from aioambient import Websocket
from aioambient.errors import WebsocketError from aioambient.errors import WebsocketError
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@ -15,18 +15,17 @@ from homeassistant.const import (
) )
from homeassistant.core import Event, HomeAssistant, callback from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import aiohttp_client, config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.dispatcher import ( from homeassistant.helpers.dispatcher import (
async_dispatcher_connect, async_dispatcher_connect,
async_dispatcher_send, async_dispatcher_send,
) )
from homeassistant.helpers.entity import Entity, EntityDescription from homeassistant.helpers.entity import DeviceInfo, Entity, EntityDescription
from homeassistant.helpers.event import async_call_later from homeassistant.helpers.event import async_call_later
from .const import ( from .const import (
ATTR_LAST_DATA, ATTR_LAST_DATA,
CONF_APP_KEY, CONF_APP_KEY,
DATA_CLIENT,
DOMAIN, DOMAIN,
LOGGER, LOGGER,
TYPE_SOLARRADIATION, TYPE_SOLARRADIATION,
@ -57,37 +56,32 @@ def async_hydrate_station_data(data: dict[str, Any]) -> dict[str, Any]:
return data return data
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up the Ambient PWS as config entry.""" """Set up the Ambient PWS as config entry."""
hass.data.setdefault(DOMAIN, {DATA_CLIENT: {}}) hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = {}
if not config_entry.unique_id: if not entry.unique_id:
hass.config_entries.async_update_entry( hass.config_entries.async_update_entry(
config_entry, unique_id=config_entry.data[CONF_APP_KEY] entry, unique_id=entry.data[CONF_APP_KEY]
) )
session = aiohttp_client.async_get_clientsession(hass)
try: try:
ambient = AmbientStation( ambient = AmbientStation(
hass, hass,
config_entry, entry,
Client( Websocket(entry.data[CONF_APP_KEY], entry.data[CONF_API_KEY]),
config_entry.data[CONF_API_KEY],
config_entry.data[CONF_APP_KEY],
session=session,
logger=LOGGER,
),
) )
hass.loop.create_task(ambient.ws_connect()) hass.loop.create_task(ambient.ws_connect())
hass.data[DOMAIN][DATA_CLIENT][config_entry.entry_id] = ambient hass.data[DOMAIN][entry.entry_id] = ambient
except WebsocketError as err: except WebsocketError as err:
LOGGER.error("Config entry failed: %s", err) LOGGER.error("Config entry failed: %s", err)
raise ConfigEntryNotReady from err raise ConfigEntryNotReady from err
async def _async_disconnect_websocket(_: Event) -> None: async def _async_disconnect_websocket(_: Event) -> None:
await ambient.client.websocket.disconnect() await ambient.websocket.disconnect()
config_entry.async_on_unload( entry.async_on_unload(
hass.bus.async_listen_once( hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, _async_disconnect_websocket EVENT_HOMEASSISTANT_STOP, _async_disconnect_websocket
) )
@ -96,30 +90,32 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
return True return True
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload an Ambient PWS config entry.""" """Unload an Ambient PWS config entry."""
ambient = hass.data[DOMAIN][DATA_CLIENT].pop(config_entry.entry_id) unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
hass.async_create_task(ambient.ws_disconnect()) if unload_ok:
ambient = hass.data[DOMAIN].pop(entry.entry_id)
hass.async_create_task(ambient.ws_disconnect())
return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS) return unload_ok
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool: async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Migrate old entry.""" """Migrate old entry."""
version = config_entry.version version = entry.version
LOGGER.debug("Migrating from version %s", version) LOGGER.debug("Migrating from version %s", version)
# 1 -> 2: Unique ID format changed, so delete and re-import: # 1 -> 2: Unique ID format changed, so delete and re-import:
if version == 1: if version == 1:
dev_reg = await hass.helpers.device_registry.async_get_registry() dev_reg = await hass.helpers.device_registry.async_get_registry()
dev_reg.async_clear_config_entry(config_entry) dev_reg.async_clear_config_entry(entry)
en_reg = await hass.helpers.entity_registry.async_get_registry() en_reg = await hass.helpers.entity_registry.async_get_registry()
en_reg.async_clear_config_entry(config_entry) en_reg.async_clear_config_entry(entry)
version = config_entry.version = 2 version = entry.version = 2
hass.config_entries.async_update_entry(config_entry) hass.config_entries.async_update_entry(entry)
LOGGER.info("Migration to version %s successful", version) LOGGER.info("Migration to version %s successful", version)
return True return True
@ -129,22 +125,22 @@ class AmbientStation:
"""Define a class to handle the Ambient websocket.""" """Define a class to handle the Ambient websocket."""
def __init__( def __init__(
self, hass: HomeAssistant, config_entry: ConfigEntry, client: Client self, hass: HomeAssistant, entry: ConfigEntry, websocket: Websocket
) -> None: ) -> None:
"""Initialize.""" """Initialize."""
self._config_entry = config_entry self._entry = entry
self._entry_setup_complete = False self._entry_setup_complete = False
self._hass = hass self._hass = hass
self._ws_reconnect_delay = DEFAULT_SOCKET_MIN_RETRY self._ws_reconnect_delay = DEFAULT_SOCKET_MIN_RETRY
self.client = client
self.stations: dict[str, dict] = {} self.stations: dict[str, dict] = {}
self.websocket = websocket
async def _attempt_connect(self) -> None: async def _attempt_connect(self) -> None:
"""Attempt to connect to the socket (retrying later on fail).""" """Attempt to connect to the socket (retrying later on fail)."""
async def connect(timestamp: int | None = None) -> None: async def connect(timestamp: int | None = None) -> None:
"""Connect.""" """Connect."""
await self.client.websocket.connect() await self.websocket.connect()
try: try:
await connect() await connect()
@ -194,22 +190,20 @@ class AmbientStation:
# attempt forward setup of the config entry (because it will have # attempt forward setup of the config entry (because it will have
# already been done): # already been done):
if not self._entry_setup_complete: if not self._entry_setup_complete:
self._hass.config_entries.async_setup_platforms( self._hass.config_entries.async_setup_platforms(self._entry, PLATFORMS)
self._config_entry, PLATFORMS
)
self._entry_setup_complete = True self._entry_setup_complete = True
self._ws_reconnect_delay = DEFAULT_SOCKET_MIN_RETRY self._ws_reconnect_delay = DEFAULT_SOCKET_MIN_RETRY
self.client.websocket.on_connect(on_connect) self.websocket.on_connect(on_connect)
self.client.websocket.on_data(on_data) self.websocket.on_data(on_data)
self.client.websocket.on_disconnect(on_disconnect) self.websocket.on_disconnect(on_disconnect)
self.client.websocket.on_subscribed(on_subscribed) self.websocket.on_subscribed(on_subscribed)
await self._attempt_connect() await self._attempt_connect()
async def ws_disconnect(self) -> None: async def ws_disconnect(self) -> None:
"""Disconnect from the websocket.""" """Disconnect from the websocket."""
await self.client.websocket.disconnect() await self.websocket.disconnect()
class AmbientWeatherEntity(Entity): class AmbientWeatherEntity(Entity):
@ -226,11 +220,11 @@ class AmbientWeatherEntity(Entity):
) -> None: ) -> None:
"""Initialize the entity.""" """Initialize the entity."""
self._ambient = ambient self._ambient = ambient
self._attr_device_info = { self._attr_device_info = DeviceInfo(
"identifiers": {(DOMAIN, mac_address)}, identifiers={(DOMAIN, mac_address)},
"name": station_name, manufacturer="Ambient Weather",
"manufacturer": "Ambient Weather", name=station_name,
} )
self._attr_name = f"{station_name}_{description.name}" self._attr_name = f"{station_name}_{description.name}"
self._attr_unique_id = f"{mac_address}_{description.key}" self._attr_unique_id = f"{mac_address}_{description.key}"
self._mac_address = mac_address self._mac_address = mac_address

View File

@ -11,12 +11,12 @@ from homeassistant.components.binary_sensor import (
BinarySensorEntityDescription, BinarySensorEntityDescription,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_NAME from homeassistant.const import ATTR_NAME, ENTITY_CATEGORY_DIAGNOSTIC
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import AmbientWeatherEntity from . import AmbientWeatherEntity
from .const import ATTR_LAST_DATA, DATA_CLIENT, DOMAIN from .const import ATTR_LAST_DATA, DOMAIN
TYPE_BATT1 = "batt1" TYPE_BATT1 = "batt1"
TYPE_BATT10 = "batt10" TYPE_BATT10 = "batt10"
@ -63,144 +63,168 @@ BINARY_SENSOR_DESCRIPTIONS = (
key=TYPE_BATTOUT, key=TYPE_BATTOUT,
name="Battery", name="Battery",
device_class=DEVICE_CLASS_BATTERY, device_class=DEVICE_CLASS_BATTERY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
on_state=0, on_state=0,
), ),
AmbientBinarySensorDescription( AmbientBinarySensorDescription(
key=TYPE_BATT1, key=TYPE_BATT1,
name="Battery 1", name="Battery 1",
device_class=DEVICE_CLASS_BATTERY, device_class=DEVICE_CLASS_BATTERY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
on_state=0, on_state=0,
), ),
AmbientBinarySensorDescription( AmbientBinarySensorDescription(
key=TYPE_BATT2, key=TYPE_BATT2,
name="Battery 2", name="Battery 2",
device_class=DEVICE_CLASS_BATTERY, device_class=DEVICE_CLASS_BATTERY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
on_state=0, on_state=0,
), ),
AmbientBinarySensorDescription( AmbientBinarySensorDescription(
key=TYPE_BATT3, key=TYPE_BATT3,
name="Battery 3", name="Battery 3",
device_class=DEVICE_CLASS_BATTERY, device_class=DEVICE_CLASS_BATTERY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
on_state=0, on_state=0,
), ),
AmbientBinarySensorDescription( AmbientBinarySensorDescription(
key=TYPE_BATT4, key=TYPE_BATT4,
name="Battery 4", name="Battery 4",
device_class=DEVICE_CLASS_BATTERY, device_class=DEVICE_CLASS_BATTERY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
on_state=0, on_state=0,
), ),
AmbientBinarySensorDescription( AmbientBinarySensorDescription(
key=TYPE_BATT5, key=TYPE_BATT5,
name="Battery 5", name="Battery 5",
device_class=DEVICE_CLASS_BATTERY, device_class=DEVICE_CLASS_BATTERY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
on_state=0, on_state=0,
), ),
AmbientBinarySensorDescription( AmbientBinarySensorDescription(
key=TYPE_BATT6, key=TYPE_BATT6,
name="Battery 6", name="Battery 6",
device_class=DEVICE_CLASS_BATTERY, device_class=DEVICE_CLASS_BATTERY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
on_state=0, on_state=0,
), ),
AmbientBinarySensorDescription( AmbientBinarySensorDescription(
key=TYPE_BATT7, key=TYPE_BATT7,
name="Battery 7", name="Battery 7",
device_class=DEVICE_CLASS_BATTERY, device_class=DEVICE_CLASS_BATTERY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
on_state=0, on_state=0,
), ),
AmbientBinarySensorDescription( AmbientBinarySensorDescription(
key=TYPE_BATT8, key=TYPE_BATT8,
name="Battery 8", name="Battery 8",
device_class=DEVICE_CLASS_BATTERY, device_class=DEVICE_CLASS_BATTERY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
on_state=0, on_state=0,
), ),
AmbientBinarySensorDescription( AmbientBinarySensorDescription(
key=TYPE_BATT9, key=TYPE_BATT9,
name="Battery 9", name="Battery 9",
device_class=DEVICE_CLASS_BATTERY, device_class=DEVICE_CLASS_BATTERY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
on_state=0, on_state=0,
), ),
AmbientBinarySensorDescription( AmbientBinarySensorDescription(
key=TYPE_BATT10, key=TYPE_BATT10,
name="Battery 10", name="Battery 10",
device_class=DEVICE_CLASS_BATTERY, device_class=DEVICE_CLASS_BATTERY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
on_state=0, on_state=0,
), ),
AmbientBinarySensorDescription( AmbientBinarySensorDescription(
key=TYPE_BATT_CO2, key=TYPE_BATT_CO2,
name="CO2 Battery", name="CO2 Battery",
device_class=DEVICE_CLASS_BATTERY, device_class=DEVICE_CLASS_BATTERY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
on_state=0, on_state=0,
), ),
AmbientBinarySensorDescription( AmbientBinarySensorDescription(
key=TYPE_PM25IN_BATT, key=TYPE_PM25IN_BATT,
name="PM25 Indoor Battery", name="PM25 Indoor Battery",
device_class=DEVICE_CLASS_BATTERY, device_class=DEVICE_CLASS_BATTERY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
on_state=0, on_state=0,
), ),
AmbientBinarySensorDescription( AmbientBinarySensorDescription(
key=TYPE_PM25_BATT, key=TYPE_PM25_BATT,
name="PM25 Battery", name="PM25 Battery",
device_class=DEVICE_CLASS_BATTERY, device_class=DEVICE_CLASS_BATTERY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
on_state=0, on_state=0,
), ),
AmbientBinarySensorDescription( AmbientBinarySensorDescription(
key=TYPE_RELAY1, key=TYPE_RELAY1,
name="Relay 1", name="Relay 1",
device_class=DEVICE_CLASS_CONNECTIVITY, device_class=DEVICE_CLASS_CONNECTIVITY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
on_state=1, on_state=1,
), ),
AmbientBinarySensorDescription( AmbientBinarySensorDescription(
key=TYPE_RELAY2, key=TYPE_RELAY2,
name="Relay 2", name="Relay 2",
device_class=DEVICE_CLASS_CONNECTIVITY, device_class=DEVICE_CLASS_CONNECTIVITY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
on_state=1, on_state=1,
), ),
AmbientBinarySensorDescription( AmbientBinarySensorDescription(
key=TYPE_RELAY3, key=TYPE_RELAY3,
name="Relay 3", name="Relay 3",
device_class=DEVICE_CLASS_CONNECTIVITY, device_class=DEVICE_CLASS_CONNECTIVITY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
on_state=1, on_state=1,
), ),
AmbientBinarySensorDescription( AmbientBinarySensorDescription(
key=TYPE_RELAY4, key=TYPE_RELAY4,
name="Relay 4", name="Relay 4",
device_class=DEVICE_CLASS_CONNECTIVITY, device_class=DEVICE_CLASS_CONNECTIVITY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
on_state=1, on_state=1,
), ),
AmbientBinarySensorDescription( AmbientBinarySensorDescription(
key=TYPE_RELAY5, key=TYPE_RELAY5,
name="Relay 5", name="Relay 5",
device_class=DEVICE_CLASS_CONNECTIVITY, device_class=DEVICE_CLASS_CONNECTIVITY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
on_state=1, on_state=1,
), ),
AmbientBinarySensorDescription( AmbientBinarySensorDescription(
key=TYPE_RELAY6, key=TYPE_RELAY6,
name="Relay 6", name="Relay 6",
device_class=DEVICE_CLASS_CONNECTIVITY, device_class=DEVICE_CLASS_CONNECTIVITY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
on_state=1, on_state=1,
), ),
AmbientBinarySensorDescription( AmbientBinarySensorDescription(
key=TYPE_RELAY7, key=TYPE_RELAY7,
name="Relay 7", name="Relay 7",
device_class=DEVICE_CLASS_CONNECTIVITY, device_class=DEVICE_CLASS_CONNECTIVITY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
on_state=1, on_state=1,
), ),
AmbientBinarySensorDescription( AmbientBinarySensorDescription(
key=TYPE_RELAY8, key=TYPE_RELAY8,
name="Relay 8", name="Relay 8",
device_class=DEVICE_CLASS_CONNECTIVITY, device_class=DEVICE_CLASS_CONNECTIVITY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
on_state=1, on_state=1,
), ),
AmbientBinarySensorDescription( AmbientBinarySensorDescription(
key=TYPE_RELAY9, key=TYPE_RELAY9,
name="Relay 9", name="Relay 9",
device_class=DEVICE_CLASS_CONNECTIVITY, device_class=DEVICE_CLASS_CONNECTIVITY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
on_state=1, on_state=1,
), ),
AmbientBinarySensorDescription( AmbientBinarySensorDescription(
key=TYPE_RELAY10, key=TYPE_RELAY10,
name="Relay 10", name="Relay 10",
device_class=DEVICE_CLASS_CONNECTIVITY, device_class=DEVICE_CLASS_CONNECTIVITY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
on_state=1, on_state=1,
), ),
) )
@ -210,7 +234,7 @@ async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None: ) -> None:
"""Set up Ambient PWS binary sensors based on a config entry.""" """Set up Ambient PWS binary sensors based on a config entry."""
ambient = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id] ambient = hass.data[DOMAIN][entry.entry_id]
async_add_entities( async_add_entities(
[ [

View File

@ -1,7 +1,7 @@
"""Config flow to configure the Ambient PWS component.""" """Config flow to configure the Ambient PWS component."""
from __future__ import annotations from __future__ import annotations
from aioambient import Client from aioambient import API
from aioambient.errors import AmbientError from aioambient.errors import AmbientError
import voluptuous as vol import voluptuous as vol
@ -41,12 +41,10 @@ class AmbientStationFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
self._abort_if_unique_id_configured() self._abort_if_unique_id_configured()
session = aiohttp_client.async_get_clientsession(self.hass) session = aiohttp_client.async_get_clientsession(self.hass)
client = Client( api = API(user_input[CONF_APP_KEY], user_input[CONF_API_KEY], session=session)
user_input[CONF_API_KEY], user_input[CONF_APP_KEY], session=session
)
try: try:
devices = await client.api.get_devices() devices = await api.get_devices()
except AmbientError: except AmbientError:
return await self._show_form({"base": "invalid_key"}) return await self._show_form({"base": "invalid_key"})

View File

@ -8,7 +8,5 @@ ATTR_LAST_DATA = "last_data"
CONF_APP_KEY = "app_key" CONF_APP_KEY = "app_key"
DATA_CLIENT = "data_client"
TYPE_SOLARRADIATION = "solarradiation" TYPE_SOLARRADIATION = "solarradiation"
TYPE_SOLARRADIATION_LX = "solarradiation_lx" TYPE_SOLARRADIATION_LX = "solarradiation_lx"

View File

@ -3,7 +3,7 @@
"name": "Ambient Weather Station", "name": "Ambient Weather Station",
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/ambient_station", "documentation": "https://www.home-assistant.io/integrations/ambient_station",
"requirements": ["aioambient==1.3.0"], "requirements": ["aioambient==2021.10.1"],
"codeowners": ["@bachya"], "codeowners": ["@bachya"],
"iot_class": "cloud_push" "iot_class": "cloud_push"
} }

View File

@ -39,7 +39,7 @@ from . import (
AmbientStation, AmbientStation,
AmbientWeatherEntity, AmbientWeatherEntity,
) )
from .const import ATTR_LAST_DATA, DATA_CLIENT, DOMAIN from .const import ATTR_LAST_DATA, DOMAIN
TYPE_24HOURRAININ = "24hourrainin" TYPE_24HOURRAININ = "24hourrainin"
TYPE_BAROMABSIN = "baromabsin" TYPE_BAROMABSIN = "baromabsin"
@ -609,7 +609,7 @@ async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None: ) -> None:
"""Set up Ambient PWS sensors based on a config entry.""" """Set up Ambient PWS sensors based on a config entry."""
ambient = hass.data[DOMAIN][DATA_CLIENT][entry.entry_id] ambient = hass.data[DOMAIN][entry.entry_id]
async_add_entities( async_add_entities(
[ [

View File

@ -13,7 +13,7 @@
"api_key": "API kulcs", "api_key": "API kulcs",
"app_key": "Alkalmaz\u00e1skulcs" "app_key": "Alkalmaz\u00e1skulcs"
}, },
"title": "T\u00f6ltsd ki az adataid" "title": "T\u00f6ltse ki az adatait"
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More