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

View File

@ -15,7 +15,7 @@ body:
attributes:
label: The problem
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.
Provide a clear and concise description of what the problem is.
@ -28,10 +28,12 @@ body:
validations:
required: true
attributes:
label: What is version of Home Assistant Core has the issue?
label: What version of Home Assistant Core has the issue?
placeholder: core-
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
attributes:
label: What was the last working version of Home Assistant Core?
@ -44,7 +46,9 @@ body:
attributes:
label: What type of installation are you running?
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:
- Home Assistant OS
- Home Assistant Container
@ -55,15 +59,15 @@ body:
attributes:
label: Integration causing the issue
description: >
The name of the integration, for example, Automation or Philips Hue.
The name of the integration. For example: Automation, Philips Hue
- type: input
id: integration_link
attributes:
label: Link to integration documentation on our website
placeholder: "https://www.home-assistant.io/integrations/..."
description: |
Providing a link [to the documentation][docs] help us categorizing the
issue, while providing a useful reference at the same time.
Providing a link [to the documentation][docs] helps us categorize the
issue, while also providing a useful reference for others.
[docs]: https://www.home-assistant.io/integrations
@ -75,8 +79,8 @@ body:
attributes:
label: Example YAML snippet
description: |
If this issue has an example piece of YAML that can help reproducing this problem, please provide.
This can be an piece of YAML from, e.g., an automation, script, scene or configuration.
If applicable, please provide an example piece of YAML that can help reproduce this problem.
This can be from an automation, script, scene or configuration.
render: yaml
- type: textarea
attributes:
@ -88,5 +92,3 @@ body:
label: Additional information
description: >
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 }}
steps:
- name: Checkout the repository
uses: actions/checkout@v2.3.4
uses: actions/checkout@v2.3.5
with:
fetch-depth: 0
@ -67,7 +67,7 @@ jobs:
if: needs.init.outputs.publish == 'true'
steps:
- name: Checkout the repository
uses: actions/checkout@v2.3.4
uses: actions/checkout@v2.3.5
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v2.2.2
@ -97,7 +97,7 @@ jobs:
arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps:
- name: Checkout the repository
uses: actions/checkout@v2.3.4
uses: actions/checkout@v2.3.5
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
if: needs.init.outputs.channel == 'dev'
@ -170,7 +170,7 @@ jobs:
- tinker
steps:
- name: Checkout the repository
uses: actions/checkout@v2.3.4
uses: actions/checkout@v2.3.5
- name: Login to DockerHub
uses: docker/login-action@v1.10.0
@ -201,7 +201,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v2.3.4
uses: actions/checkout@v2.3.5
- name: Initialize git
uses: home-assistant/actions/helpers/git-init@master
@ -233,7 +233,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v2.3.4
uses: actions/checkout@v2.3.5
- name: Login to DockerHub
uses: docker/login-action@v1.10.0

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,7 +9,7 @@ from typing import Any, Dict, Mapping, Optional, Tuple, cast
import jwt
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.util import dt as dt_util
@ -155,6 +155,7 @@ class AuthManager:
self._providers = providers
self._mfa_modules = mfa_modules
self.login_flow = AuthManagerFlowManager(hass, self)
self._revoke_callbacks: dict[str, list[CALLBACK_TYPE]] = {}
@property
def auth_providers(self) -> list[AuthProvider]:
@ -275,6 +276,12 @@ class AuthManager:
self, user: models.User, credentials: models.Credentials
) -> None:
"""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)
async def async_remove_user(self, user: models.User) -> None:
@ -285,7 +292,7 @@ class AuthManager:
]
if tasks:
await asyncio.wait(tasks)
await asyncio.gather(*tasks)
await self._store.async_remove_user(user)
@ -446,6 +453,28 @@ class AuthManager:
"""Delete a 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
def async_create_access_token(
self, refresh_token: models.RefreshToken, remote_ip: str | None = None

View File

@ -25,7 +25,7 @@ from . import (
SetupFlow,
)
REQUIREMENTS = ["pyotp==2.3.0"]
REQUIREMENTS = ["pyotp==2.6.0"]
CONF_MESSAGE = "message"
@ -56,10 +56,10 @@ def _generate_secret() -> str:
def _generate_random() -> int:
"""Generate a 8 digit number."""
"""Generate a 32 digit number."""
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:
@ -245,8 +245,7 @@ class NotifyAuthModule(MultiFactorAuthModule):
await self._async_load()
assert self._user_settings is not None
notify_setting = self._user_settings.get(user_id)
if notify_setting is None:
if (notify_setting := self._user_settings.get(user_id)) is None:
_LOGGER.error("Cannot find user %s", user_id)
return

View File

@ -18,7 +18,7 @@ from . import (
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)
@ -181,7 +181,7 @@ class TotpSetupFlow(SetupFlow):
# to fix typing complaint
self._auth_module: TotpAuthModule = auth_module
self._user = user
self._ota_secret: str | None = None
self._ota_secret: str = ""
self._url = None # type Optional[str]
self._image = None # type Optional[str]

View File

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

View File

@ -17,7 +17,10 @@ import yarl
from homeassistant import config as conf_util, config_entries, core, loader
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.helpers import area_registry, device_registry, entity_registry
from homeassistant.helpers.dispatcher import async_dispatcher_send
@ -240,11 +243,14 @@ async def async_from_config_dict(
stop = monotonic()
_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 = (
"Support for the running Python version "
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 "
f"{'.'.join(str(x) for x in REQUIRED_NEXT_PYTHON_VER)} or "
"higher."

View File

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

View File

@ -1,4 +1,6 @@
"""Config flow for the Abode Security System component."""
from http import HTTPStatus
from abodepy import Abode
from abodepy.exceptions import AbodeAuthenticationException, AbodeException
from abodepy.helpers.errors import MFA_CODE_REQUIRED
@ -6,7 +8,7 @@ from requests.exceptions import ConnectTimeout, HTTPError
import voluptuous as vol
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
@ -51,7 +53,7 @@ class AbodeFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
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"}
else:

View File

@ -1,9 +1,19 @@
{
"config": {
"abort": {
"reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0442\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435 \u0431\u0435\u0448\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e",
"single_instance_allowed": "\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": {
"reauth_confirm": {
"data": {
"password": "\u041f\u0430\u0440\u043e\u043b\u0430",
"username": "Email"
}
},
"user": {
"data": {
"password": "\u041f\u0430\u0440\u043e\u043b\u0430",

View File

@ -1,7 +1,7 @@
{
"config": {
"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."
},
"error": {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,6 @@
"""Support for Adax wifi-enabled home heaters."""
from __future__ import annotations
import logging
from typing import Any
from adax import Adax
@ -21,11 +20,10 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import ACCOUNT_ID
_LOGGER = logging.getLogger(__name__)
from .const import ACCOUNT_ID, DOMAIN
async def async_setup_entry(
@ -41,8 +39,11 @@ async def async_setup_entry(
)
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:
"""Initialize the heater."""
self._heater_data = heater_data
self._device_id = heater_data["id"]
self._adax_data_handler = adax_data_handler
self._attr_unique_id = f"{heater_data['homeId']}_{heater_data['id']}"
@property
def name(self) -> str:
"""Return the name of the device, if any."""
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"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, heater_data["id"])},
name=self.name,
manufacturer="Adax",
)
async def async_set_hvac_mode(self, hvac_mode: str) -> None:
"""Set hvac mode."""
if hvac_mode == HVAC_MODE_HEAT:
temperature = max(
self.min_temp, self._heater_data.get("targetTemperature", self.min_temp)
)
temperature = max(self.min_temp, self.target_temperature or self.min_temp)
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:
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:
return
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:
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None:
return
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:
"""Get the latest data."""
for room in await self._adax_data_handler.get_rooms():
if room["id"] == self._heater_data["id"]:
self._heater_data = room
return
if room["id"] != self._device_id:
continue
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
def device_info(self) -> DeviceInfo:
"""Return device information about this AdGuard Home instance."""
return {
"identifiers": {
return DeviceInfo(
entry_type="service",
identifiers={
(DOMAIN, self.adguard.host, self.adguard.port, self.adguard.base_path) # type: ignore
},
"name": "AdGuard Home",
"manufacturer": "AdGuard Team",
"sw_version": self.hass.data[DOMAIN][self._entry.entry_id].get(
manufacturer="AdGuard Team",
name="AdGuard Home",
sw_version=self.hass.data[DOMAIN][self._entry.entry_id].get(
DATA_ADGUARD_VERSION
),
"entry_type": "service",
}
)

View File

@ -3,6 +3,9 @@
"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."
},
"error": {
"cannot_connect": "\u041d\u0435\u0443\u0441\u043f\u0435\u0445 \u043f\u0440\u0438 \u0441\u0432\u044a\u0440\u0437\u0432\u0430\u043d\u0435"
},
"step": {
"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} ?",
@ -10,7 +13,9 @@
},
"user": {
"data": {
"host": "\u0425\u043e\u0441\u0442",
"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",
"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"

View File

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

View File

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

View File

@ -1,5 +1,6 @@
"""Advantage Air parent entity class."""
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
@ -14,13 +15,13 @@ class AdvantageAirEntity(CoordinatorEntity):
self.async_change = instance["async_change"]
self.ac_key = ac_key
self.zone_key = zone_key
self._attr_device_info = {
"identifiers": {(DOMAIN, self.coordinator.data["system"]["rid"])},
"name": self.coordinator.data["system"]["name"],
"manufacturer": "Advantage Air",
"model": self.coordinator.data["system"]["sysType"],
"sw_version": self.coordinator.data["system"]["myAppRev"],
}
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self.coordinator.data["system"]["rid"])},
manufacturer="Advantage Air",
model=self.coordinator.data["system"]["sysType"],
name=self.coordinator.data["system"]["name"],
sw_version=self.coordinator.data["system"]["myAppRev"],
)
@property
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,
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 .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."""
_attr_native_unit_of_measurement = ADVANTAGE_AIR_SET_COUNTDOWN_UNIT
_attr_entity_category = ENTITY_CATEGORY_DIAGNOSTIC
def __init__(self, instance, ac_key, action):
"""Initialize the Advantage Air timer control."""
@ -84,6 +85,7 @@ class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
_attr_native_unit_of_measurement = PERCENTAGE
_attr_state_class = STATE_CLASS_MEASUREMENT
_attr_entity_category = ENTITY_CATEGORY_DIAGNOSTIC
def __init__(self, instance, ac_key, zone_key):
"""Initialize an Advantage Air Zone Vent Sensor."""
@ -113,6 +115,7 @@ class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
_attr_native_unit_of_measurement = PERCENTAGE
_attr_state_class = STATE_CLASS_MEASUREMENT
_attr_entity_category = ENTITY_CATEGORY_DIAGNOSTIC
def __init__(self, instance, ac_key, zone_key):
"""Initialize an Advantage Air Zone wireless signal sensor."""
@ -148,6 +151,7 @@ class AdvantageAirZoneTemp(AdvantageAirEntity, SensorEntity):
_attr_device_class = DEVICE_CLASS_TEMPERATURE
_attr_state_class = STATE_CLASS_MEASUREMENT
_attr_entity_registry_enabled_default = False
_attr_entity_category = ENTITY_CATEGORY_DIAGNOSTIC
def __init__(self, instance, ac_key, zone_key):
"""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)
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@ -1,7 +1,10 @@
"""Constant values for the AEMET OpenData component."""
from __future__ import annotations
from homeassistant.components.sensor import SensorEntityDescription
from homeassistant.components.sensor import (
STATE_CLASS_MEASUREMENT,
SensorEntityDescription,
)
from homeassistant.components.weather import (
ATTR_CONDITION_CLEAR_NIGHT,
ATTR_CONDITION_CLOUDY,
@ -252,12 +255,14 @@ WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
name="Humidity",
native_unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
SensorEntityDescription(
key=ATTR_API_PRESSURE,
name="Pressure",
native_unit_of_measurement=PRESSURE_HPA,
device_class=DEVICE_CLASS_PRESSURE,
state_class=STATE_CLASS_MEASUREMENT,
),
SensorEntityDescription(
key=ATTR_API_RAIN,
@ -268,6 +273,7 @@ WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
key=ATTR_API_RAIN_PROB,
name="Rain probability",
native_unit_of_measurement=PERCENTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
SensorEntityDescription(
key=ATTR_API_SNOW,
@ -278,6 +284,7 @@ WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
key=ATTR_API_SNOW_PROB,
name="Snow probability",
native_unit_of_measurement=PERCENTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
SensorEntityDescription(
key=ATTR_API_STATION_ID,
@ -296,18 +303,21 @@ WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
key=ATTR_API_STORM_PROB,
name="Storm probability",
native_unit_of_measurement=PERCENTAGE,
state_class=STATE_CLASS_MEASUREMENT,
),
SensorEntityDescription(
key=ATTR_API_TEMPERATURE,
name="Temperature",
native_unit_of_measurement=TEMP_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
SensorEntityDescription(
key=ATTR_API_TEMPERATURE_FEELING,
name="Temperature feeling",
native_unit_of_measurement=TEMP_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
SensorEntityDescription(
key=ATTR_API_TOWN_ID,
@ -326,6 +336,7 @@ WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
key=ATTR_API_WIND_BEARING,
name="Wind bearing",
native_unit_of_measurement=DEGREE,
state_class=STATE_CLASS_MEASUREMENT,
),
SensorEntityDescription(
key=ATTR_API_WIND_MAX_SPEED,
@ -336,6 +347,7 @@ WEATHER_SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
key=ATTR_API_WIND_SPEED,
name="Wind speed",
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."""
from __future__ import annotations
from http import HTTPStatus
import logging
from typing import Any, Final
@ -11,7 +12,7 @@ from homeassistant.components.sensor import (
PLATFORM_SCHEMA as BASE_PLATFORM_SCHEMA,
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.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
@ -64,7 +65,7 @@ async def async_setup_platform(
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(
"No tracking data found. Check API key is correct: %s", aftership.meta
)
@ -109,6 +110,7 @@ async def async_setup_platform(
class AfterShipSensor(SensorEntity):
"""Representation of a AfterShip sensor."""
_attr_attribution = ATTRIBUTION
_attr_native_unit_of_measurement: str = "packages"
_attr_icon: str = ICON
@ -150,7 +152,7 @@ class AfterShipSensor(SensorEntity):
if not self.aftership.meta:
_LOGGER.error("Unknown errors when querying")
return
if self.aftership.meta["code"] != HTTP_OK:
if self.aftership.meta["code"] != HTTPStatus.OK:
_LOGGER.error(
"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)
self._attributes = {
ATTR_ATTRIBUTION: ATTRIBUTION,
**status_counts,
ATTR_TRACKINGS: trackings,
}

View File

@ -11,6 +11,7 @@ from homeassistant.const import (
STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_DISARMED,
)
from homeassistant.helpers.entity import DeviceInfo
from .const import CONNECTION, DOMAIN as AGENT_DOMAIN
@ -45,12 +46,12 @@ class AgentBaseStation(AlarmControlPanelEntity):
self._client = client
self._attr_name = f"{client.name} {CONST_ALARM_CONTROL_PANEL_NAME}"
self._attr_unique_id = f"{client.unique}_CP"
self._attr_device_info = {
"identifiers": {(AGENT_DOMAIN, client.unique)},
"manufacturer": "Agent",
"model": CONST_ALARM_CONTROL_PANEL_NAME,
"sw_version": client.version,
}
self._attr_device_info = DeviceInfo(
identifiers={(AGENT_DOMAIN, client.unique)},
manufacturer="Agent",
model=CONST_ALARM_CONTROL_PANEL_NAME,
sw_version=client.version,
)
async def async_update(self):
"""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.helpers import entity_platform
from homeassistant.helpers.entity import DeviceInfo
from .const import (
ATTRIBUTION,
@ -79,13 +80,13 @@ class AgentCamera(MjpegCamera):
self._attr_name = f"{device.client.name} {device.name}"
self._attr_unique_id = f"{device._client.unique}_{device.typeID}_{device.id}"
super().__init__(device_info)
self._attr_device_info = {
"identifiers": {(AGENT_DOMAIN, self.unique_id)},
"name": self.name,
"manufacturer": "Agent",
"model": "Camera",
"sw_version": device.client.version,
}
self._attr_device_info = DeviceInfo(
identifiers={(AGENT_DOMAIN, self.unique_id)},
manufacturer="Agent",
model="Camera",
name=self.name,
sw_version=device.client.version,
)
async def async_update(self):
"""Update our state from the Agent API."""

View File

@ -33,9 +33,7 @@ class AgentFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
try:
await agent_client.update()
except AgentConnectionError:
pass
except AgentError:
except (AgentConnectionError, AgentError):
pass
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"
},
"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"
},
"step": {

View File

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

View File

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

View File

@ -32,3 +32,4 @@ MANUFACTURER: Final = "Airly sp. z o.o."
MAX_UPDATE_INTERVAL: Final = 90
MIN_UPDATE_INTERVAL: Final = 5
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,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
@ -54,6 +55,7 @@ from .const import (
MANUFACTURER,
SUFFIX_LIMIT,
SUFFIX_PERCENT,
URL,
)
PARALLEL_UPDATES = 1
@ -151,14 +153,15 @@ class AirlySensor(CoordinatorEntity, SensorEntity):
) -> None:
"""Initialize."""
super().__init__(coordinator)
self._attr_device_info = {
"identifiers": {
(DOMAIN, f"{coordinator.latitude}-{coordinator.longitude}")
},
"name": DEFAULT_NAME,
"manufacturer": MANUFACTURER,
"entry_type": "service",
}
self._attr_device_info = DeviceInfo(
entry_type="service",
identifiers={(DOMAIN, f"{coordinator.latitude}-{coordinator.longitude}")},
manufacturer=MANUFACTURER,
name=DEFAULT_NAME,
configuration_url=URL.format(
latitude=coordinator.latitude, longitude=coordinator.longitude
),
)
self._attr_name = f"{name} {description.name}"
self._attr_unique_id = (
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
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@ -1,7 +1,22 @@
{
"config": {
"abort": {
"already_configured": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e\u0442\u043e \u0432\u0435\u0447\u0435 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d\u043e"
},
"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"
},
"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_SIGNAL_STRENGTH,
DEVICE_CLASS_TEMPERATURE,
ENTITY_CATEGORY_DIAGNOSTIC,
PERCENTAGE,
PRESSURE_MBAR,
SIGNAL_STRENGTH_DECIBELS,
TEMP_CELSIUS,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
@ -64,6 +66,7 @@ SENSORS: dict[str, SensorEntityDescription] = {
key="battery",
device_class=DEVICE_CLASS_BATTERY,
native_unit_of_measurement=PERCENTAGE,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
name="Battery",
),
"co2": SensorEntityDescription(
@ -96,6 +99,7 @@ SENSORS: dict[str, SensorEntityDescription] = {
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
name="RSSI",
entity_registry_enabled_default=False,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
"pm1": SensorEntityDescription(
key="pm1",
@ -152,11 +156,12 @@ class AirthingsHeaterEnergySensor(CoordinatorEntity, SensorEntity):
self._attr_name = f"{airthings_device.name} {entity_description.name}"
self._attr_unique_id = f"{airthings_device.device_id}_{entity_description.key}"
self._id = airthings_device.device_id
self._attr_device_info = {
"identifiers": {(DOMAIN, airthings_device.device_id)},
"name": airthings_device.name,
"manufacturer": "Airthings",
}
self._attr_device_info = DeviceInfo(
configuration_url="https://dashboard.airthings.com/",
identifiers={(DOMAIN, airthings_device.device_id)},
name=airthings_device.name,
manufacturer="Airthings",
)
@property
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.core import callback
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
@ -96,14 +97,14 @@ class AirtouchAC(CoordinatorEntity, ClimateEntity):
return super()._handle_coordinator_update()
@property
def device_info(self):
def device_info(self) -> DeviceInfo:
"""Return device info for this device."""
return {
"identifiers": {(DOMAIN, self.unique_id)},
"name": self.name,
"manufacturer": "Airtouch",
"model": "Airtouch 4",
}
return DeviceInfo(
identifiers={(DOMAIN, self.unique_id)},
name=self.name,
manufacturer="Airtouch",
model="Airtouch 4",
)
@property
def unique_id(self):
@ -211,14 +212,14 @@ class AirtouchGroup(CoordinatorEntity, ClimateEntity):
return super()._handle_coordinator_update()
@property
def device_info(self):
def device_info(self) -> DeviceInfo:
"""Return device info for this device."""
return {
"identifiers": {(DOMAIN, self.unique_id)},
"name": self.name,
"manufacturer": "Airtouch",
"model": "Airtouch 4",
}
return DeviceInfo(
identifiers={(DOMAIN, self.unique_id)},
manufacturer="Airtouch",
model="Airtouch 4",
name=self.name,
)
@property
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 datetime import timedelta
from math import ceil
from typing import Any
from typing import Any, Dict, cast
from pyairvisual import CloudAPI, NodeSamba
from pyairvisual.errors import (
@ -54,8 +54,6 @@ from .const import (
PLATFORMS = ["sensor"]
DATA_LISTENER = "listener"
DEFAULT_ATTRIBUTION = "Data provided by AirVisual"
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
) -> list[DataUpdateCoordinator]:
"""Get all DataUpdateCoordinator objects related to a particular API key."""
coordinators = []
for entry_id, coordinator in hass.data[DOMAIN][DATA_COORDINATOR].items():
config_entry = hass.config_entries.async_get_entry(entry_id)
if config_entry and config_entry.data.get(CONF_API_KEY) == api_key:
coordinators.append(coordinator)
return coordinators
return [
attrs[DATA_COORDINATOR]
for entry_id, attrs in hass.data[DOMAIN].items()
if (entry := hass.config_entries.async_get_entry(entry_id))
and entry.data.get(CONF_API_KEY) == api_key
]
@callback
@ -139,25 +137,25 @@ def async_sync_geo_coordinator_update_intervals(
@callback
def _standardize_geography_config_entry(
hass: HomeAssistant, config_entry: ConfigEntry
hass: HomeAssistant, entry: ConfigEntry
) -> None:
"""Ensure that geography config entries have appropriate properties."""
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:
entry_updates["unique_id"] = config_entry.data[CONF_API_KEY]
if not config_entry.options:
entry_updates["unique_id"] = entry.data[CONF_API_KEY]
if not entry.options:
# If the config entry doesn't already have any options set, set defaults:
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_NAME,
]:
# If the config entry data doesn't contain an integration type that we know
# about, infer it from the data we have:
entry_updates["data"] = {**config_entry.data}
if CONF_CITY in config_entry.data:
entry_updates["data"] = {**entry.data}
if CONF_CITY in entry.data:
entry_updates["data"][
CONF_INTEGRATION_TYPE
] = INTEGRATION_TYPE_GEOGRAPHY_NAME
@ -169,55 +167,55 @@ def _standardize_geography_config_entry(
if not entry_updates:
return
hass.config_entries.async_update_entry(config_entry, **entry_updates)
hass.config_entries.async_update_entry(entry, **entry_updates)
@callback
def _standardize_node_pro_config_entry(
hass: HomeAssistant, config_entry: ConfigEntry
) -> None:
def _standardize_node_pro_config_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Ensure that Node/Pro config entries have appropriate properties."""
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:
entry_updates["data"] = {
**config_entry.data,
**entry.data,
CONF_INTEGRATION_TYPE: INTEGRATION_TYPE_NODE_PRO,
}
if not entry_updates:
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."""
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:
_standardize_geography_config_entry(hass, config_entry)
if CONF_API_KEY in entry.data:
_standardize_geography_config_entry(hass, entry)
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]:
"""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(
config_entry.data[CONF_CITY],
config_entry.data[CONF_STATE],
config_entry.data[CONF_COUNTRY],
entry.data[CONF_CITY],
entry.data[CONF_STATE],
entry.data[CONF_COUNTRY],
)
else:
api_coro = cloud_api.air_quality.nearest_city(
config_entry.data[CONF_LATITUDE],
config_entry.data[CONF_LONGITUDE],
entry.data[CONF_LATITUDE],
entry.data[CONF_LONGITUDE],
)
try:
return await api_coro
data = await api_coro
return cast(Dict[str, Any], data)
except (InvalidKeyError, KeyExpiredError) as ex:
raise ConfigEntryAuthFailed from ex
except AirVisualError as err:
@ -226,7 +224,7 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
coordinator = DataUpdateCoordinator(
hass,
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;
# then, below, we use the coordinator's presence (along with any other
# 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:
hass.data[DOMAIN][DATA_LISTENER][
config_entry.entry_id
] = config_entry.add_update_listener(async_reload_entry)
entry.async_on_unload(entry.add_update_listener(async_reload_entry))
else:
# Remove outdated air_quality entities from the entity registry if they exist:
ent_reg = entity_registry.async_get(hass)
for entity_entry in [
e
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")
]:
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)
_standardize_node_pro_config_entry(hass, config_entry)
_standardize_node_pro_config_entry(hass, entry)
async def async_update_data() -> dict[str, Any]:
"""Get new data from the API."""
try:
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:
return await node.async_get_latest_measurements()
data = await node.async_get_latest_measurements()
return cast(Dict[str, Any], data)
except NodeProError as 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()
hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id] = coordinator
hass.data[DOMAIN][entry.entry_id][DATA_COORDINATOR] = coordinator
# Reassess the interval between 2 server requests
if CONF_API_KEY in config_entry.data:
async_sync_geo_coordinator_update_intervals(
hass, config_entry.data[CONF_API_KEY]
)
if CONF_API_KEY in entry.data:
async_sync_geo_coordinator_update_intervals(hass, entry.data[CONF_API_KEY])
hass.config_entries.async_setup_platforms(config_entry, PLATFORMS)
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
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."""
version = config_entry.version
version = entry.version
LOGGER.debug("Migrating from version %s", version)
# 1 -> 2: One geography per config entry
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
# 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_id = async_get_geography_id(first_geography)
hass.config_entries.async_update_entry(
config_entry,
entry,
unique_id=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:
@ -321,7 +315,7 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
hass.config_entries.flow.async_init(
DOMAIN,
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
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_ok = await hass.config_entries.async_unload_platforms(
config_entry, PLATFORMS
)
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN][DATA_COORDINATOR].pop(config_entry.entry_id)
remove_listener = hass.data[DOMAIN][DATA_LISTENER].pop(config_entry.entry_id)
remove_listener()
if CONF_API_KEY in config_entry.data:
hass.data[DOMAIN].pop(entry.entry_id)
if CONF_API_KEY in entry.data:
# Re-calculate the update interval period for any remaining consumers of
# this API key:
async_sync_geo_coordinator_update_intervals(
hass, config_entry.data[CONF_API_KEY]
)
async_sync_geo_coordinator_update_intervals(hass, entry.data[CONF_API_KEY])
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."""
await hass.config_entries.async_reload(config_entry.entry_id)
await hass.config_entries.async_reload(entry.entry_id)
class AirVisualEntity(CoordinatorEntity):
"""Define a generic AirVisual entity."""
def __init__(
self, coordinator: DataUpdateCoordinator, description: EntityDescription
self,
coordinator: DataUpdateCoordinator,
entry: ConfigEntry,
description: EntityDescription,
) -> None:
"""Initialize."""
super().__init__(coordinator)
self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION}
self._entry = entry
self.entity_description = description
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):
"""Handle an AirVisual options flow."""
def __init__(self, config_entry: ConfigEntry) -> None:
def __init__(self, entry: ConfigEntry) -> None:
"""Initialize."""
self.config_entry = config_entry
self.entry = entry
async def async_step_init(
self, user_input: dict[str, str] | None = None
@ -279,7 +279,7 @@ class AirVisualOptionsFlowHandler(config_entries.OptionsFlow):
{
vol.Required(
CONF_SHOW_ON_MAP,
default=self.config_entry.options.get(CONF_SHOW_ON_MAP),
default=self.entry.options.get(CONF_SHOW_ON_MAP),
): bool
}
),

View File

@ -27,6 +27,7 @@ from homeassistant.const import (
DEVICE_CLASS_PM25,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
ENTITY_CATEGORY_DIAGNOSTIC,
PERCENTAGE,
TEMP_CELSIUS,
)
@ -103,6 +104,7 @@ NODE_PRO_SENSOR_DESCRIPTIONS = (
key=SENSOR_KIND_BATTERY_LEVEL,
name="Battery",
device_class=DEVICE_CLASS_BATTERY,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
native_unit_of_measurement=PERCENTAGE,
),
SensorEntityDescription(
@ -189,26 +191,24 @@ POLLUTANT_UNITS = {
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""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]
if config_entry.data[CONF_INTEGRATION_TYPE] in (
if entry.data[CONF_INTEGRATION_TYPE] in (
INTEGRATION_TYPE_GEOGRAPHY_COORDS,
INTEGRATION_TYPE_GEOGRAPHY_NAME,
):
sensors = [
AirVisualGeographySensor(coordinator, config_entry, description, locale)
AirVisualGeographySensor(coordinator, entry, description, locale)
for locale in GEOGRAPHY_SENSOR_LOCALES
for description in GEOGRAPHY_SENSOR_DESCRIPTIONS
]
else:
sensors = [
AirVisualNodeProSensor(coordinator, description)
AirVisualNodeProSensor(coordinator, entry, description)
for description in NODE_PRO_SENSOR_DESCRIPTIONS
]
@ -221,23 +221,22 @@ class AirVisualGeographySensor(AirVisualEntity, SensorEntity):
def __init__(
self,
coordinator: DataUpdateCoordinator,
config_entry: ConfigEntry,
entry: ConfigEntry,
description: SensorEntityDescription,
locale: str,
) -> None:
"""Initialize."""
super().__init__(coordinator, description)
super().__init__(coordinator, entry, description)
self._attr_extra_state_attributes.update(
{
ATTR_CITY: config_entry.data.get(CONF_CITY),
ATTR_STATE: config_entry.data.get(CONF_STATE),
ATTR_COUNTRY: config_entry.data.get(CONF_COUNTRY),
ATTR_CITY: entry.data.get(CONF_CITY),
ATTR_STATE: entry.data.get(CONF_STATE),
ATTR_COUNTRY: entry.data.get(CONF_COUNTRY),
}
)
self._attr_name = f"{GEOGRAPHY_SENSOR_LOCALES[locale]} {description.name}"
self._attr_unique_id = f"{config_entry.unique_id}_{locale}_{description.key}"
self._config_entry = config_entry
self._attr_unique_id = f"{entry.unique_id}_{locale}_{description.key}"
self._locale = locale
@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
# 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,
self.coordinator.data["location"]["coordinates"][1],
)
longitude = self._config_entry.data.get(
longitude = self._entry.data.get(
CONF_LONGITUDE,
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_LONGITUDE] = longitude
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."""
def __init__(
self, coordinator: DataUpdateCoordinator, description: SensorEntityDescription
self,
coordinator: DataUpdateCoordinator,
entry: ConfigEntry,
description: SensorEntityDescription,
) -> None:
"""Initialize."""
super().__init__(coordinator, description)
super().__init__(coordinator, entry, description)
self._attr_name = (
f"{coordinator.data['settings']['node_name']} Node/Pro: {description.name}"
@ -317,16 +319,16 @@ class AirVisualNodeProSensor(AirVisualEntity, SensorEntity):
@property
def device_info(self) -> DeviceInfo:
"""Return device registry information for this entity."""
return {
"identifiers": {(DOMAIN, self.coordinator.data["serial_number"])},
"name": self.coordinator.data["settings"]["node_name"],
"manufacturer": "AirVisual",
"model": f'{self.coordinator.data["status"]["model"]}',
"sw_version": (
return DeviceInfo(
identifiers={(DOMAIN, self.coordinator.data["serial_number"])},
manufacturer="AirVisual",
model=f'{self.coordinator.data["status"]["model"]}',
name=self.coordinator.data["settings"]["node_name"],
sw_version=(
f'Version {self.coordinator.data["status"]["system_version"]}'
f'{self.coordinator.data["status"]["app_version"]}'
),
}
)
@callback
def update_from_latest_data(self) -> None:

View File

@ -1,11 +1,30 @@
{
"config": {
"abort": {
"reauth_successful": "\u041f\u043e\u0432\u0442\u043e\u0440\u043d\u043e\u0442\u043e \u0443\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435 \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": {
"geography_by_name": {
"data": {
"city": "\u0413\u0440\u0430\u0434",
"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": {
"abort": {
"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": {
"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,
) -> None:
"""Reproduce a single state."""
cur_state = hass.states.get(state.entity_id)
if cur_state is None:
if (cur_state := hass.states.get(state.entity_id)) is None:
_LOGGER.warning("Unable to find entity %s", state.entity_id)
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": {
"_": {
"armed": "\u05d3\u05e8\u05d5\u05da",

View File

@ -1,23 +1,40 @@
{
"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": {
"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"
}
},
"state": {
"_": {
"armed": "Etkin",
"armed_away": "Etkin d\u0131\u015far\u0131da",
"armed_custom_bypass": "Alarm etkin \u00f6zel baypas",
"armed_home": "Etkin evde",
"armed_night": "Etkin gece",
"armed": "Aktif",
"armed_away": "D\u0131\u015farda Aktif",
"armed_custom_bypass": "\u00d6zel Mod Aktif",
"armed_home": "Evde Aktif",
"armed_night": "Gece Aktif",
"arming": "Alarm etkinle\u015fiyor",
"disarmed": "Etkisiz",
"disarmed": "Devre D\u0131\u015f\u0131",
"disarming": "Alarm devre d\u0131\u015f\u0131",
"pending": "Beklemede",
"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_home": "\u5728\u5bb6\u8b66\u6212",
"armed_night": "\u591c\u95f4\u8b66\u6212",
"armed_vacation": "\u5ea6\u5047\u8b66\u6212",
"arming": "\u8b66\u6212\u4e2d",
"disarmed": "\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": {
"zone_name": "B\u00f6lge Ad\u0131",
"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": {

View File

@ -221,8 +221,7 @@ class Alert(ToggleEntity):
async def watched_entity_change(self, ev):
"""Determine if the alert should start or stop."""
to_state = ev.data.get("new_state")
if to_state is None:
if (to_state := ev.data.get("new_state")) is None:
return
_LOGGER.debug("Watched entity (%s) has changed", ev.data.get("entity_id"))
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,
) -> None:
"""Reproduce a single state."""
cur_state = hass.states.get(state.entity_id)
if cur_state is None:
if (cur_state := hass.states.get(state.entity_id)) is None:
_LOGGER.warning("Unable to find entity %s", state.entity_id)
return

View File

@ -1,13 +1,14 @@
"""Support for Alexa skill auth."""
import asyncio
from datetime import timedelta
from http import HTTPStatus
import json
import logging
import aiohttp
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.helpers import aiohttp_client
from homeassistant.util import dt
@ -119,7 +120,7 @@ class Auth:
_LOGGER.debug("LWA response header: %s", response.headers)
_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")
return None

View File

@ -182,8 +182,7 @@ class AlexaCapability:
"""Serialize according to the Discovery API."""
result = {"type": "AlexaInterface", "interface": self.name(), "version": "3"}
instance = self.instance
if instance is not None:
if (instance := self.instance) is not None:
result["instance"] = instance
properties_supported = self.properties_supported()
@ -264,8 +263,7 @@ class AlexaCapability:
"timeOfSample": dt_util.utcnow().strftime(DATE_FORMAT),
"uncertaintyInMilliseconds": 0,
}
instance = self.instance
if instance is not None:
if (instance := self.instance) is not None:
result["instance"] = instance
yield result
@ -1098,8 +1096,7 @@ class AlexaThermostatController(AlexaCapability):
supported_modes = []
hvac_modes = self.entity.attributes.get(climate.ATTR_HVAC_MODES)
for mode in hvac_modes:
thermostat_mode = API_THERMOSTAT_MODES.get(mode)
if thermostat_mode:
if thermostat_mode := API_THERMOSTAT_MODES.get(mode):
supported_modes.append(thermostat_mode)
preset_modes = self.entity.attributes.get(climate.ATTR_PRESET_MODES)
@ -1538,7 +1535,9 @@ class AlexaRangeController(AlexaCapability):
labels=["Percentage", AlexaGlobalCatalog.SETTING_FAN_SPEED],
min_value=0,
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,
)
return self._resource.serialize_capability_resources()

View File

@ -1,11 +1,12 @@
"""Support for Alexa skill service end point."""
import copy
import hmac
from http import HTTPStatus
import logging
import uuid
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.helpers import template
import homeassistant.util.dt as dt_util
@ -58,7 +59,7 @@ class AlexaFlashBriefingView(http.HomeAssistantView):
if request.query.get(API_PASSWORD) is None:
err = "No password provided for Alexa flash briefing: %s"
_LOGGER.error(err, briefing_id)
return b"", HTTP_UNAUTHORIZED
return b"", HTTPStatus.UNAUTHORIZED
if not hmac.compare_digest(
request.query[API_PASSWORD].encode("utf-8"),
@ -66,12 +67,12 @@ class AlexaFlashBriefingView(http.HomeAssistantView):
):
err = "Wrong password for Alexa flash briefing: %s"
_LOGGER.error(err, briefing_id)
return b"", HTTP_UNAUTHORIZED
return b"", HTTPStatus.UNAUTHORIZED
if not isinstance(self.flash_briefings.get(briefing_id), list):
err = "No configured Alexa flash briefing was found for: %s"
_LOGGER.error(err, briefing_id)
return b"", HTTP_NOT_FOUND
return b"", HTTPStatus.NOT_FOUND
briefing = []
@ -93,8 +94,7 @@ class AlexaFlashBriefingView(http.HomeAssistantView):
else:
output[ATTR_MAIN_TEXT] = item.get(CONF_TEXT)
uid = item.get(CONF_UID)
if uid is None:
if (uid := item.get(CONF_UID)) is None:
uid = str(uuid.uuid4())
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):
"""Process a turn on request."""
entity = directive.entity
domain = entity.domain
if domain == group.DOMAIN:
if (domain := entity.domain) == group.DOMAIN:
domain = ha.DOMAIN
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}":
range_delta = int(range_delta * 20) if range_delta_default else int(range_delta)
service = SERVICE_SET_COVER_POSITION
current = entity.attributes.get(cover.ATTR_POSITION)
if not current:
if not (current := entity.attributes.get(cover.ATTR_POSITION)):
msg = f"Unable to determine {entity.entity_id} current position"
raise AlexaInvalidValueError(msg)
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)
)
service = fan.SERVICE_SET_PERCENTAGE
current = entity.attributes.get(fan.ATTR_PERCENTAGE)
if not current:
if not (current := entity.attributes.get(fan.ATTR_PERCENTAGE)):
msg = f"Unable to determine {entity.entity_id} current fan speed"
raise AlexaInvalidValueError(msg)
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_type = req["type"]
handler = HANDLERS.get(req_type)
if not handler:
if not (handler := HANDLERS.get(req_type)):
raise UnknownRequest(f"Received unknown request {req_type}")
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):
"""Describe a logbook event."""
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)
name = state.name if state else entity_id
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.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 .config import AbstractConfig
@ -60,7 +66,18 @@ class AlexaConfig(AbstractConfig):
def should_expose(self, entity_id):
"""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
def async_invalidate_access_token(self):

View File

@ -2,13 +2,14 @@
from __future__ import annotations
import asyncio
from http import HTTPStatus
import json
import logging
import aiohttp
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.helpers.significant_change import create_checker
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("Received (%s): %s", response.status, response_text)
if response.status == HTTP_ACCEPTED:
if response.status == HTTPStatus.ACCEPTED:
return
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("Received (%s): %s", response.status, response_text)
if response.status == HTTP_ACCEPTED:
if response.status == HTTPStatus.ACCEPTED:
return
response_json = json.loads(response_text)

View File

@ -2,7 +2,8 @@
"config": {
"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.",
"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": {
"pick_implementation": {

View File

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

View File

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

View File

@ -7,8 +7,8 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_IDENTIFIERS, ATTR_MANUFACTURER, ATTR_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import (
@ -16,7 +16,7 @@ from homeassistant.helpers.update_coordinator import (
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(
@ -58,12 +58,12 @@ class AmbeeSensorEntity(CoordinatorEntity, SensorEntity):
self.entity_description = description
self._attr_unique_id = f"{entry_id}_{service_key}_{description.key}"
self._attr_device_info = {
ATTR_IDENTIFIERS: {(DOMAIN, f"{entry_id}_{service_key}")},
ATTR_NAME: service,
ATTR_MANUFACTURER: "Ambee",
ATTR_ENTRY_TYPE: ENTRY_TYPE_SERVICE,
}
self._attr_device_info = DeviceInfo(
entry_type=ENTRY_TYPE_SERVICE,
identifiers={(DOMAIN, f"{entry_id}_{service_key}")},
manufacturer="Ambee",
name=service,
)
@property
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": {
"abort": {
"reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt"
"reauth_successful": "Az \u00fajrahiteles\u00edt\u00e9s sikeres volt."
},
"error": {
"cannot_connect": "Sikertelen csatlakoz\u00e1s",

View File

@ -11,7 +11,7 @@
"reauth_confirm": {
"data": {
"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": {

View File

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

View File

@ -20,7 +20,7 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
)
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.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
@ -54,6 +54,8 @@ def friendly_channel_type(channel_type: str) -> str:
class AmberSensor(CoordinatorEntity, SensorEntity):
"""Amber Base Sensor."""
_attr_attribution = ATTRIBUTION
def __init__(
self,
coordinator: AmberUpdateCoordinator,
@ -88,7 +90,7 @@ class AmberPriceSensor(AmberSensor):
"""Return additional pieces of information about the price."""
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:
return data
@ -143,7 +145,6 @@ class AmberForecastSensor(AmberSensor):
data = {
"forecasts": [],
"channel_type": intervals[0].channel_type.value,
ATTR_ATTRIBUTION: ATTRIBUTION,
}
for interval in intervals:
@ -172,6 +173,8 @@ class AmberForecastSensor(AmberSensor):
class AmberGridSensor(CoordinatorEntity, SensorEntity):
"""Sensor to show single grid specific values."""
_attr_attribution = ATTRIBUTION
def __init__(
self,
coordinator: AmberUpdateCoordinator,
@ -181,7 +184,6 @@ class AmberGridSensor(CoordinatorEntity, SensorEntity):
super().__init__(coordinator)
self.site_id = coordinator.site_id
self.entity_description = description
self._attr_device_state_attributes = {ATTR_ATTRIBUTION: ATTRIBUTION}
self._attr_unique_id = f"{coordinator.site_id}-{description.key}"
@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.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity import DeviceInfo
from .const import (
ATTR_VALUE,
@ -149,16 +150,15 @@ class AmbiclimateEntity(ClimateEntity):
self._store = store
self._attr_unique_id = heater.device_id
self._attr_name = heater.name
self._attr_device_info = {
"identifiers": {(DOMAIN, self.unique_id)},
"name": self.name,
"manufacturer": "Ambiclimate",
}
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self.unique_id)},
manufacturer="Ambiclimate",
name=self.name,
)
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
if (temperature := kwargs.get(ATTR_TEMPERATURE)) is None:
return
await self._heater.set_target_temperature(temperature)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -39,7 +39,7 @@ from . import (
AmbientStation,
AmbientWeatherEntity,
)
from .const import ATTR_LAST_DATA, DATA_CLIENT, DOMAIN
from .const import ATTR_LAST_DATA, DOMAIN
TYPE_24HOURRAININ = "24hourrainin"
TYPE_BAROMABSIN = "baromabsin"
@ -609,7 +609,7 @@ async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""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(
[

View File

@ -13,7 +13,7 @@
"api_key": "API kulcs",
"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