Merge pull request #55532 from home-assistant/rc

This commit is contained in:
Paulus Schoutsen 2021-09-01 11:28:21 -07:00 committed by GitHub
commit 245eec7041
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2189 changed files with 56776 additions and 23064 deletions

View File

@ -36,6 +36,9 @@ omit =
homeassistant/components/agent_dvr/helpers.py
homeassistant/components/airnow/__init__.py
homeassistant/components/airnow/sensor.py
homeassistant/components/airtouch4/__init__.py
homeassistant/components/airtouch4/climate.py
homeassistant/components/airtouch4/const.py
homeassistant/components/airvisual/__init__.py
homeassistant/components/airvisual/sensor.py
homeassistant/components/aladdin_connect/*
@ -314,6 +317,12 @@ omit =
homeassistant/components/firmata/switch.py
homeassistant/components/fitbit/*
homeassistant/components/fixer/sensor.py
homeassistant/components/fjaraskupan/__init__.py
homeassistant/components/fjaraskupan/binary_sensor.py
homeassistant/components/fjaraskupan/const.py
homeassistant/components/fjaraskupan/fan.py
homeassistant/components/fjaraskupan/light.py
homeassistant/components/fjaraskupan/sensor.py
homeassistant/components/fleetgo/device_tracker.py
homeassistant/components/flexit/climate.py
homeassistant/components/flic/binary_sensor.py
@ -375,6 +384,7 @@ omit =
homeassistant/components/google/*
homeassistant/components/google_cloud/tts.py
homeassistant/components/google_maps/device_tracker.py
homeassistant/components/google_pubsub/__init__.py
homeassistant/components/google_travel_time/__init__.py
homeassistant/components/google_travel_time/helpers.py
homeassistant/components/google_travel_time/sensor.py
@ -636,6 +646,7 @@ omit =
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
@ -666,18 +677,21 @@ omit =
homeassistant/components/mysensors/helpers.py
homeassistant/components/mysensors/light.py
homeassistant/components/mysensors/notify.py
homeassistant/components/mysensors/sensor.py
homeassistant/components/mysensors/switch.py
homeassistant/components/mystrom/binary_sensor.py
homeassistant/components/mystrom/light.py
homeassistant/components/mystrom/switch.py
homeassistant/components/myq/__init__.py
homeassistant/components/myq/cover.py
homeassistant/components/myq/light.py
homeassistant/components/nad/media_player.py
homeassistant/components/nanoleaf/__init__.py
homeassistant/components/nanoleaf/light.py
homeassistant/components/nanoleaf/util.py
homeassistant/components/neato/__init__.py
homeassistant/components/neato/api.py
homeassistant/components/neato/camera.py
homeassistant/components/neato/hub.py
homeassistant/components/neato/sensor.py
homeassistant/components/neato/switch.py
homeassistant/components/neato/vacuum.py
@ -696,7 +710,8 @@ omit =
homeassistant/components/niko_home_control/light.py
homeassistant/components/nilu/air_quality.py
homeassistant/components/nissan_leaf/*
homeassistant/components/nmap_tracker/*
homeassistant/components/nmap_tracker/__init__.py
homeassistant/components/nmap_tracker/device_tracker.py
homeassistant/components/nmbs/sensor.py
homeassistant/components/notion/__init__.py
homeassistant/components/notion/binary_sensor.py
@ -830,9 +845,9 @@ omit =
homeassistant/components/raincloud/*
homeassistant/components/rainmachine/__init__.py
homeassistant/components/rainmachine/binary_sensor.py
homeassistant/components/rainmachine/model.py
homeassistant/components/rainmachine/sensor.py
homeassistant/components/rainmachine/switch.py
homeassistant/components/rainforest_eagle/sensor.py
homeassistant/components/raspihats/*
homeassistant/components/raspyrfm/*
homeassistant/components/recollect_waste/__init__.py
@ -850,12 +865,8 @@ omit =
homeassistant/components/ring/camera.py
homeassistant/components/ripple/sensor.py
homeassistant/components/rituals_perfume_genie/binary_sensor.py
homeassistant/components/rituals_perfume_genie/entity.py
homeassistant/components/rituals_perfume_genie/number.py
homeassistant/components/rituals_perfume_genie/select.py
homeassistant/components/rituals_perfume_genie/sensor.py
homeassistant/components/rituals_perfume_genie/switch.py
homeassistant/components/rituals_perfume_genie/__init__.py
homeassistant/components/rocketchat/notify.py
homeassistant/components/roomba/__init__.py
homeassistant/components/roomba/binary_sensor.py
@ -893,7 +904,9 @@ omit =
homeassistant/components/screenlogic/switch.py
homeassistant/components/scsgate/*
homeassistant/components/sendgrid/notify.py
homeassistant/components/sense/*
homeassistant/components/sense/__init__.py
homeassistant/components/sense/binary_sensor.py
homeassistant/components/sense/sensor.py
homeassistant/components/sensehat/light.py
homeassistant/components/sensehat/sensor.py
homeassistant/components/sensibo/climate.py
@ -988,6 +1001,7 @@ omit =
homeassistant/components/suez_water/*
homeassistant/components/supervisord/sensor.py
homeassistant/components/surepetcare/__init__.py
homeassistant/components/surepetcare/binary_sensor.py
homeassistant/components/surepetcare/sensor.py
homeassistant/components/swiss_hydrological_data/sensor.py
homeassistant/components/swiss_public_transport/sensor.py
@ -1008,8 +1022,9 @@ omit =
homeassistant/components/synology_srm/device_tracker.py
homeassistant/components/syslog/notify.py
homeassistant/components/system_bridge/__init__.py
homeassistant/components/system_bridge/const.py
homeassistant/components/system_bridge/binary_sensor.py
homeassistant/components/system_bridge/const.py
homeassistant/components/system_bridge/coordinator.py
homeassistant/components/system_bridge/sensor.py
homeassistant/components/systemmonitor/sensor.py
homeassistant/components/tado/*
@ -1079,6 +1094,10 @@ omit =
homeassistant/components/traccar/device_tracker.py
homeassistant/components/traccar/const.py
homeassistant/components/trackr/device_tracker.py
homeassistant/components/tractive/__init__.py
homeassistant/components/tractive/device_tracker.py
homeassistant/components/tractive/entity.py
homeassistant/components/tractive/sensor.py
homeassistant/components/tradfri/*
homeassistant/components/trafikverket_train/sensor.py
homeassistant/components/trafikverket_weatherstation/sensor.py
@ -1112,7 +1131,6 @@ omit =
homeassistant/components/upcloud/switch.py
homeassistant/components/upnp/*
homeassistant/components/upc_connect/*
homeassistant/components/uptimerobot/binary_sensor.py
homeassistant/components/uscis/sensor.py
homeassistant/components/vallox/*
homeassistant/components/vasttrafik/sensor.py
@ -1196,6 +1214,7 @@ omit =
homeassistant/components/xiaomi_miio/__init__.py
homeassistant/components/xiaomi_miio/air_quality.py
homeassistant/components/xiaomi_miio/alarm_control_panel.py
homeassistant/components/xiaomi_miio/binary_sensor.py
homeassistant/components/xiaomi_miio/device.py
homeassistant/components/xiaomi_miio/device_tracker.py
homeassistant/components/xiaomi_miio/fan.py

View File

@ -24,7 +24,12 @@
"editor.formatOnSave": true,
"editor.formatOnType": true,
"files.trimTrailingWhitespace": true,
"terminal.integrated.shell.linux": "/usr/bin/zsh",
"terminal.integrated.profiles.linux": {
"zsh": {
"path": "/usr/bin/zsh"
}
},
"terminal.integrated.defaultProfile.linux": "zsh",
"yaml.customTags": [
"!input scalar",
"!secret scalar",

View File

@ -71,6 +71,7 @@ If the code communicates with devices, web services, or third-party tools:
Updated and included derived files by running: `python3 -m script.hassfest`.
- [ ] New or updated dependencies have been added to `requirements_all.txt`.
Updated by running `python3 -m script.gen_requirements_all`.
- [ ] For the updated dependencies - a link to the changelog, or at minimum a diff between library versions is added to the PR description.
- [ ] Untested files have been added to `.coveragerc`.
The integration reached or maintains the following [Integration Quality Scale][quality-scale]:

View File

@ -47,6 +47,19 @@ jobs:
with:
ignore-dev: true
- name: Generate meta info
shell: bash
run: |
echo "${{ github.sha }};${{ github.ref }};${{ github.event_name }};${{ github.actor }}" > OFFICIAL_IMAGE
- name: Signing meta info file
uses: home-assistant/actions/helpers/codenotary@master
with:
source: file://${{ github.workspace }}/OFFICIAL_IMAGE
user: ${{ secrets.VCN_USER }}
password: ${{ secrets.VCN_PASSWORD }}
organisation: home-assistant.io
build_python:
name: Build PyPi package
needs: init
@ -101,6 +114,11 @@ jobs:
python3 script/version_bump.py nightly
version="$(python setup.py -V)"
- name: Write meta info file
shell: bash
run: |
echo "${{ github.sha }};${{ github.ref }};${{ github.event_name }};${{ github.actor }}" > rootfs/OFFICIAL_IMAGE
- name: Login to DockerHub
uses: docker/login-action@v1.10.0
with:
@ -230,11 +248,12 @@ jobs:
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Install VCN tools
uses: home-assistant/actions/helpers/vcn@master
- name: Build Meta Image
shell: bash
run: |
bash <(curl https://getvcn.codenotary.com -L)
export DOCKER_CLI_EXPERIMENTAL=enabled
function create_manifest() {

View File

@ -740,4 +740,4 @@ jobs:
coverage report --fail-under=94
coverage xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v2.0.2
uses: codecov/codecov-action@v2.0.3

View File

@ -9,7 +9,7 @@ jobs:
lock:
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v2.1.1
- uses: dessant/lock-threads@v2.1.2
with:
github-token: ${{ github.token }}
issue-lock-inactive-days: "30"

View File

@ -66,6 +66,7 @@ jobs:
arch: ${{ fromJson(needs.init.outputs.architectures) }}
tag:
- "3.9-alpine3.13"
- "3.9-alpine3.14"
steps:
- name: Checkout the repository
uses: actions/checkout@v2.3.4
@ -106,6 +107,7 @@ jobs:
arch: ${{ fromJson(needs.init.outputs.architectures) }}
tag:
- "3.9-alpine3.13"
- "3.9-alpine3.14"
steps:
- name: Checkout the repository
uses: actions/checkout@v2.3.4

2
.gitignore vendored
View File

@ -2,7 +2,7 @@ config/*
config2/*
tests/testing_config/deps
tests/testing_config/home-assistant.log
tests/testing_config/home-assistant.log*
# hass-release
data/

View File

@ -1,6 +1,6 @@
repos:
- repo: https://github.com/asottile/pyupgrade
rev: v2.23.0
rev: v2.23.3
hooks:
- id: pyupgrade
args: [--py38-plus]
@ -45,7 +45,7 @@ repos:
- --configfile=tests/bandit.yaml
files: ^(homeassistant|script|tests)/.+\.py$
- repo: https://github.com/PyCQA/isort
rev: 5.8.0
rev: 5.9.3
hooks:
- id: isort
- repo: https://github.com/pre-commit/pre-commit-hooks

View File

@ -15,6 +15,7 @@ homeassistant.components.alarm_control_panel.*
homeassistant.components.amazon_polly.*
homeassistant.components.ambee.*
homeassistant.components.ambient_station.*
homeassistant.components.amcrest.*
homeassistant.components.ampio.*
homeassistant.components.automation.*
homeassistant.components.binary_sensor.*
@ -63,6 +64,7 @@ homeassistant.components.mailbox.*
homeassistant.components.media_player.*
homeassistant.components.mysensors.*
homeassistant.components.nam.*
homeassistant.components.neato.*
homeassistant.components.nest.*
homeassistant.components.netatmo.*
homeassistant.components.network.*
@ -103,6 +105,7 @@ homeassistant.components.tile.*
homeassistant.components.tts.*
homeassistant.components.upcloud.*
homeassistant.components.uptime.*
homeassistant.components.uptimerobot.*
homeassistant.components.vacuum.*
homeassistant.components.water_heater.*
homeassistant.components.weather.*

22
.vscode/tasks.json vendored
View File

@ -60,6 +60,21 @@
},
"problemMatcher": []
},
{
"label": "Code Coverage",
"detail": "Generate code coverage report for a given integration.",
"type": "shell",
"command": "pytest ./tests/components/${input:integrationName}/ --cov=homeassistant.components.${input:integrationName} --cov-report term-missing",
"group": {
"kind": "test",
"isDefault": true
},
"presentation": {
"reveal": "always",
"panel": "new"
},
"problemMatcher": []
},
{
"label": "Generate Requirements",
"type": "shell",
@ -102,5 +117,12 @@
},
"problemMatcher": []
}
],
"inputs": [
{
"id": "integrationName",
"type": "promptString",
"description": "For which integration should the task run?"
}
]
}

View File

@ -29,6 +29,7 @@ homeassistant/components/aemet/* @noltari
homeassistant/components/agent_dvr/* @ispysoftware
homeassistant/components/airly/* @bieniu
homeassistant/components/airnow/* @asymworks
homeassistant/components/airtouch4/* @LonePurpleWolf
homeassistant/components/airvisual/* @bachya
homeassistant/components/alarmdecoder/* @ajschmidt8
homeassistant/components/alexa/* @home-assistant/cloud @ochlocracy
@ -37,6 +38,7 @@ homeassistant/components/alpha_vantage/* @fabaff
homeassistant/components/ambee/* @frenck
homeassistant/components/ambiclimate/* @danielhiversen
homeassistant/components/ambient_station/* @bachya
homeassistant/components/amcrest/* @flacjacket
homeassistant/components/analytics/* @home-assistant/core @ludeeus
homeassistant/components/androidtv/* @JeffLIrion
homeassistant/components/apache_kafka/* @bachya
@ -117,7 +119,6 @@ homeassistant/components/dexcom/* @gagebenne
homeassistant/components/dhcp/* @bdraco
homeassistant/components/dht/* @thegardenmonkey
homeassistant/components/digital_ocean/* @fabaff
homeassistant/components/directv/* @ctalkington
homeassistant/components/discogs/* @thibmaek
homeassistant/components/doorbird/* @oblogic7 @bdraco
homeassistant/components/dsmr/* @Robbie1221 @frenck
@ -161,6 +162,7 @@ homeassistant/components/filter/* @dgomes
homeassistant/components/fireservicerota/* @cyberjunky
homeassistant/components/firmata/* @DaAwesomeP
homeassistant/components/fixer/* @fabaff
homeassistant/components/fjaraskupan/* @elupus
homeassistant/components/flick_electric/* @ZephireNZ
homeassistant/components/flipr/* @cnico
homeassistant/components/flo/* @dmulcahey
@ -186,6 +188,7 @@ homeassistant/components/geo_rss_events/* @exxamalte
homeassistant/components/geonetnz_quakes/* @exxamalte
homeassistant/components/geonetnz_volcano/* @exxamalte
homeassistant/components/gios/* @bieniu
homeassistant/components/github/* @timmo001 @ludeeus
homeassistant/components/gitter/* @fabaff
homeassistant/components/glances/* @fabaff @engrbm87
homeassistant/components/goalzero/* @tkdrob
@ -196,7 +199,7 @@ homeassistant/components/gpsd/* @fabaff
homeassistant/components/gree/* @cmroche
homeassistant/components/greeneye_monitor/* @jkeljo
homeassistant/components/group/* @home-assistant/core
homeassistant/components/growatt_server/* @indykoning @muppet3000
homeassistant/components/growatt_server/* @indykoning @muppet3000 @JasperPlant
homeassistant/components/guardian/* @bachya
homeassistant/components/habitica/* @ASMfreaK @leikoilja
homeassistant/components/harmony/* @ehendrix23 @bramkragten @bdraco @mkeesey
@ -245,6 +248,7 @@ homeassistant/components/integration/* @dgomes
homeassistant/components/intent/* @home-assistant/core
homeassistant/components/intesishome/* @jnimmo
homeassistant/components/ios/* @robbiet480
homeassistant/components/iotawatt/* @gtdiehl
homeassistant/components/iperf3/* @rohankapoorcom
homeassistant/components/ipma/* @dgomes @abmantis
homeassistant/components/ipp/* @ctalkington
@ -319,10 +323,11 @@ homeassistant/components/msteams/* @peroyvind
homeassistant/components/mullvad/* @meichthys
homeassistant/components/mutesync/* @currentoor
homeassistant/components/my/* @home-assistant/core
homeassistant/components/myq/* @bdraco
homeassistant/components/myq/* @bdraco @ehendrix23
homeassistant/components/mysensors/* @MartinHjelmare @functionpointer
homeassistant/components/mystrom/* @fabaff
homeassistant/components/nam/* @bieniu
homeassistant/components/nanoleaf/* @milanmeu
homeassistant/components/neato/* @dshokouhi @Santobert
homeassistant/components/nederlandse_spoorwegen/* @YarmoM
homeassistant/components/nello/* @pschmitt
@ -337,6 +342,7 @@ homeassistant/components/nfandroidtv/* @tkdrob
homeassistant/components/nightscout/* @marciogranzotto
homeassistant/components/nilu/* @hfurubotten
homeassistant/components/nissan_leaf/* @filcole
homeassistant/components/nmap_tracker/* @bdraco
homeassistant/components/nmbs/* @thibmaek
homeassistant/components/no_ip/* @fabaff
homeassistant/components/noaa_tides/* @jdelaney72
@ -370,6 +376,7 @@ homeassistant/components/orangepi_gpio/* @pascallj
homeassistant/components/oru/* @bvlaicu
homeassistant/components/ovo_energy/* @timmo001
homeassistant/components/ozw/* @cgarwood @marcelveldt @MartinHjelmare
homeassistant/components/p1_monitor/* @klaasnicolaas
homeassistant/components/panel_custom/* @home-assistant/frontend
homeassistant/components/panel_iframe/* @home-assistant/frontend
homeassistant/components/pcal9535a/* @Shulyaka
@ -502,7 +509,7 @@ homeassistant/components/synology_dsm/* @hacf-fr @Quentame @mib1185
homeassistant/components/synology_srm/* @aerialls
homeassistant/components/syslog/* @fabaff
homeassistant/components/system_bridge/* @timmo001
homeassistant/components/tado/* @michaelarnauts @bdraco @noltari
homeassistant/components/tado/* @michaelarnauts @noltari
homeassistant/components/tag/* @balloob @dmulcahey
homeassistant/components/tahoma/* @philklei
homeassistant/components/tankerkoenig/* @guillempages
@ -525,6 +532,8 @@ homeassistant/components/totalconnect/* @austinmroczek
homeassistant/components/tplink/* @rytilahti @thegardenmonkey
homeassistant/components/traccar/* @ludeeus
homeassistant/components/trace/* @home-assistant/core
homeassistant/components/tractive/* @Danielhiversen @zhulik @bieniu
homeassistant/components/tradfri/* @janiversen
homeassistant/components/trafikverket_train/* @endor-force
homeassistant/components/trafikverket_weatherstation/* @endor-force
homeassistant/components/transmission/* @engrbm87 @JPHutchins
@ -539,8 +548,9 @@ homeassistant/components/upb/* @gwww
homeassistant/components/upc_connect/* @pvizeli @fabaff
homeassistant/components/upcloud/* @scop
homeassistant/components/updater/* @home-assistant/core
homeassistant/components/upnp/* @StevenLooman
homeassistant/components/upnp/* @StevenLooman @ehendrix23
homeassistant/components/uptimerobot/* @ludeeus
homeassistant/components/usb/* @bdraco
homeassistant/components/usgs_earthquakes_feed/* @exxamalte
homeassistant/components/utility_meter/* @dgomes
homeassistant/components/velbus/* @Cereal2nd @brefra
@ -576,13 +586,13 @@ homeassistant/components/worldclock/* @fabaff
homeassistant/components/xbox/* @hunterjm
homeassistant/components/xbox_live/* @MartinHjelmare
homeassistant/components/xiaomi_aqara/* @danielhiversen @syssi
homeassistant/components/xiaomi_miio/* @rytilahti @syssi @starkillerOG
homeassistant/components/xiaomi_miio/* @rytilahti @syssi @starkillerOG @bieniu
homeassistant/components/xiaomi_tv/* @simse
homeassistant/components/xmpp/* @fabaff @flowolf
homeassistant/components/yale_smart_alarm/* @gjohansson-ST
homeassistant/components/yamaha_musiccast/* @vigonotion @micha91
homeassistant/components/yandex_transport/* @rishatik92 @devbis
homeassistant/components/yeelight/* @rytilahti @zewelor @shenxn
homeassistant/components/yeelight/* @rytilahti @zewelor @shenxn @starkillerOG
homeassistant/components/yeelightsunflower/* @lindsaymarkward
homeassistant/components/yi/* @bachya
homeassistant/components/youless/* @gjong

View File

@ -2,11 +2,11 @@
"image": "homeassistant/{arch}-homeassistant",
"shadow_repository": "ghcr.io/home-assistant",
"build_from": {
"aarch64": "ghcr.io/home-assistant/aarch64-homeassistant-base:2021.07.0",
"armhf": "ghcr.io/home-assistant/armhf-homeassistant-base:2021.07.0",
"armv7": "ghcr.io/home-assistant/armv7-homeassistant-base:2021.07.0",
"amd64": "ghcr.io/home-assistant/amd64-homeassistant-base:2021.07.0",
"i386": "ghcr.io/home-assistant/i386-homeassistant-base:2021.07.0"
"aarch64": "ghcr.io/home-assistant/aarch64-homeassistant-base:2021.08.0",
"armhf": "ghcr.io/home-assistant/armhf-homeassistant-base:2021.08.0",
"armv7": "ghcr.io/home-assistant/armv7-homeassistant-base:2021.08.0",
"amd64": "ghcr.io/home-assistant/amd64-homeassistant-base:2021.08.0",
"i386": "ghcr.io/home-assistant/i386-homeassistant-base:2021.08.0"
},
"labels": {
"io.hass.type": "core",

View File

@ -17,6 +17,8 @@ from .const import GROUP_ID_ADMIN, GROUP_ID_READ_ONLY, GROUP_ID_USER
from .permissions import PermissionLookup, system_policies
from .permissions.types import PolicyType
# mypy: disallow-any-generics
STORAGE_VERSION = 1
STORAGE_KEY = "auth"
GROUP_NAME_ADMIN = "Administrators"
@ -491,7 +493,7 @@ class AuthStore:
self._store.async_delay_save(self._data_to_save, 1)
@callback
def _data_to_save(self) -> dict:
def _data_to_save(self) -> dict[str, list[dict[str, Any]]]:
"""Return the data to store."""
assert self._users is not None
assert self._groups is not None

View File

@ -22,6 +22,8 @@ from ..auth_store import AuthStore
from ..const import MFA_SESSION_EXPIRATION
from ..models import Credentials, RefreshToken, User, UserMeta
# mypy: disallow-any-generics
_LOGGER = logging.getLogger(__name__)
DATA_REQS = "auth_prov_reqs_processed"
@ -96,7 +98,7 @@ class AuthProvider:
# Implement by extending class
async def async_login_flow(self, context: dict | None) -> LoginFlow:
async def async_login_flow(self, context: dict[str, Any] | None) -> LoginFlow:
"""Return the data flow for logging in with auth provider.
Auth provider should extend LoginFlow and return an instance.

View File

@ -17,6 +17,8 @@ from homeassistant.exceptions import HomeAssistantError
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from ..models import Credentials, UserMeta
# mypy: disallow-any-generics
CONF_ARGS = "args"
CONF_META = "meta"
@ -56,7 +58,7 @@ class CommandLineAuthProvider(AuthProvider):
super().__init__(*args, **kwargs)
self._user_meta: dict[str, dict[str, Any]] = {}
async def async_login_flow(self, context: dict | None) -> LoginFlow:
async def async_login_flow(self, context: dict[str, Any] | None) -> LoginFlow:
"""Return a flow to login."""
return CommandLineLoginFlow(self)

View File

@ -19,6 +19,8 @@ from homeassistant.exceptions import HomeAssistantError
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from ..models import Credentials, UserMeta
# mypy: disallow-any-generics
STORAGE_VERSION = 1
STORAGE_KEY = "auth_provider.homeassistant"
@ -235,7 +237,7 @@ class HassAuthProvider(AuthProvider):
await data.async_load()
self.data = data
async def async_login_flow(self, context: dict | None) -> LoginFlow:
async def async_login_flow(self, context: dict[str, Any] | None) -> LoginFlow:
"""Return a flow to login."""
return HassLoginFlow(self)

View File

@ -4,7 +4,7 @@ from __future__ import annotations
from collections import OrderedDict
from collections.abc import Mapping
import hmac
from typing import cast
from typing import Any, cast
import voluptuous as vol
@ -15,6 +15,8 @@ from homeassistant.exceptions import HomeAssistantError
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from ..models import Credentials, UserMeta
# mypy: disallow-any-generics
USER_SCHEMA = vol.Schema(
{
vol.Required("username"): str,
@ -37,7 +39,7 @@ class InvalidAuthError(HomeAssistantError):
class ExampleAuthProvider(AuthProvider):
"""Example auth provider based on hardcoded usernames and passwords."""
async def async_login_flow(self, context: dict | None) -> LoginFlow:
async def async_login_flow(self, context: dict[str, Any] | None) -> LoginFlow:
"""Return a flow to login."""
return ExampleLoginFlow(self)

View File

@ -7,7 +7,7 @@ from __future__ import annotations
from collections.abc import Mapping
import hmac
from typing import cast
from typing import Any, cast
import voluptuous as vol
@ -19,6 +19,8 @@ import homeassistant.helpers.config_validation as cv
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from ..models import Credentials, UserMeta
# mypy: disallow-any-generics
AUTH_PROVIDER_TYPE = "legacy_api_password"
CONF_API_PASSWORD = "api_password"
@ -44,7 +46,7 @@ class LegacyApiPasswordAuthProvider(AuthProvider):
"""Return api_password."""
return str(self.config[CONF_API_PASSWORD])
async def async_login_flow(self, context: dict | None) -> LoginFlow:
async def async_login_flow(self, context: dict[str, Any] | None) -> LoginFlow:
"""Return a flow to login."""
return LegacyLoginFlow(self)

View File

@ -27,6 +27,8 @@ from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from .. import InvalidAuthError
from ..models import Credentials, RefreshToken, UserMeta
# mypy: disallow-any-generics
IPAddress = Union[IPv4Address, IPv6Address]
IPNetwork = Union[IPv4Network, IPv6Network]
@ -97,7 +99,7 @@ class TrustedNetworksAuthProvider(AuthProvider):
"""Trusted Networks auth provider does not support MFA."""
return False
async def async_login_flow(self, context: dict | None) -> LoginFlow:
async def async_login_flow(self, context: dict[str, Any] | None) -> LoginFlow:
"""Return a flow to login."""
assert context is not None
ip_addr = cast(IPAddress, context.get("ip_address"))

View File

@ -332,15 +332,17 @@ def async_enable_logging(
not err_path_exists and os.access(err_dir, os.W_OK)
):
err_handler: logging.handlers.RotatingFileHandler | logging.handlers.TimedRotatingFileHandler
if log_rotate_days:
err_handler: logging.FileHandler = (
logging.handlers.TimedRotatingFileHandler(
err_log_path, when="midnight", backupCount=log_rotate_days
)
err_handler = logging.handlers.TimedRotatingFileHandler(
err_log_path, when="midnight", backupCount=log_rotate_days
)
else:
err_handler = logging.FileHandler(err_log_path, mode="w", delay=True)
err_handler = logging.handlers.RotatingFileHandler(
err_log_path, backupCount=1
)
err_handler.doRollover()
err_handler.setLevel(logging.INFO if verbose else logging.WARNING)
err_handler.setFormatter(logging.Formatter(fmt, datefmt=datefmt))

View File

@ -1,4 +1,6 @@
"""Support for Abode Security System cameras."""
from __future__ import annotations
from datetime import timedelta
import abodepy.helpers.constants as CONST
@ -73,7 +75,9 @@ class AbodeCamera(AbodeDevice, Camera):
else:
self._response = None
def camera_image(self):
def camera_image(
self, width: int | None = None, height: int | None = None
) -> bytes | None:
"""Get a camera image."""
self.refresh_image()

View File

@ -1,7 +1,9 @@
"""Support for Abode Security System sensors."""
from __future__ import annotations
import abodepy.helpers.constants as CONST
from homeassistant.components.sensor import SensorEntity
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
from homeassistant.const import (
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_ILLUMINANCE,
@ -11,12 +13,23 @@ from homeassistant.const import (
from . import AbodeDevice
from .const import DOMAIN
# Sensor types: Name, icon
SENSOR_TYPES = {
CONST.TEMP_STATUS_KEY: ["Temperature", DEVICE_CLASS_TEMPERATURE],
CONST.HUMI_STATUS_KEY: ["Humidity", DEVICE_CLASS_HUMIDITY],
CONST.LUX_STATUS_KEY: ["Lux", DEVICE_CLASS_ILLUMINANCE],
}
SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SensorEntityDescription(
key=CONST.TEMP_STATUS_KEY,
name="Temperature",
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key=CONST.HUMI_STATUS_KEY,
name="Humidity",
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key=CONST.LUX_STATUS_KEY,
name="Lux",
device_class=DEVICE_CLASS_ILLUMINANCE,
),
)
async def async_setup_entry(hass, config_entry, async_add_entities):
@ -26,10 +39,14 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
entities = []
for device in data.abode.get_devices(generic_type=CONST.TYPE_SENSOR):
for sensor_type in SENSOR_TYPES:
if sensor_type not in device.get_value(CONST.STATUSES_KEY):
continue
entities.append(AbodeSensor(data, device, sensor_type))
conditions = device.get_value(CONST.STATUSES_KEY)
entities.extend(
[
AbodeSensor(data, device, description)
for description in SENSOR_TYPES
if description.key in conditions
]
)
async_add_entities(entities)
@ -37,26 +54,25 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class AbodeSensor(AbodeDevice, SensorEntity):
"""A sensor implementation for Abode devices."""
def __init__(self, data, device, sensor_type):
def __init__(self, data, device, description: SensorEntityDescription):
"""Initialize a sensor for an Abode device."""
super().__init__(data, device)
self._sensor_type = sensor_type
self._attr_name = f"{device.name} {SENSOR_TYPES[sensor_type][0]}"
self._attr_device_class = SENSOR_TYPES[self._sensor_type][1]
self._attr_unique_id = f"{device.device_uuid}-{sensor_type}"
if self._sensor_type == CONST.TEMP_STATUS_KEY:
self._attr_unit_of_measurement = device.temp_unit
elif self._sensor_type == CONST.HUMI_STATUS_KEY:
self._attr_unit_of_measurement = device.humidity_unit
elif self._sensor_type == CONST.LUX_STATUS_KEY:
self._attr_unit_of_measurement = device.lux_unit
self.entity_description = description
self._attr_name = f"{device.name} {description.name}"
self._attr_unique_id = f"{device.device_uuid}-{description.key}"
if description.key == CONST.TEMP_STATUS_KEY:
self._attr_native_unit_of_measurement = device.temp_unit
elif description.key == CONST.HUMI_STATUS_KEY:
self._attr_native_unit_of_measurement = device.humidity_unit
elif description.key == CONST.LUX_STATUS_KEY:
self._attr_native_unit_of_measurement = device.lux_unit
@property
def state(self):
def native_value(self):
"""Return the state of the sensor."""
if self._sensor_type == CONST.TEMP_STATUS_KEY:
if self.entity_description.key == CONST.TEMP_STATUS_KEY:
return self._device.temp
if self._sensor_type == CONST.HUMI_STATUS_KEY:
if self.entity_description.key == CONST.HUMI_STATUS_KEY:
return self._device.humidity
if self._sensor_type == CONST.LUX_STATUS_KEY:
if self.entity_description.key == CONST.LUX_STATUS_KEY:
return self._device.lux

View File

@ -88,10 +88,10 @@ class AccuWeatherSensor(CoordinatorEntity, SensorEntity):
)
if coordinator.is_metric:
self._unit_system = API_METRIC
self._attr_unit_of_measurement = description.unit_metric
self._attr_native_unit_of_measurement = description.unit_metric
else:
self._unit_system = API_IMPERIAL
self._attr_unit_of_measurement = description.unit_imperial
self._attr_native_unit_of_measurement = description.unit_imperial
self._attr_device_info = {
"identifiers": {(DOMAIN, coordinator.location_key)},
"name": NAME,
@ -101,7 +101,7 @@ class AccuWeatherSensor(CoordinatorEntity, SensorEntity):
self.forecast_day = forecast_day
@property
def state(self) -> StateType:
def native_value(self) -> StateType:
"""Return the state."""
if self.forecast_day is not None:
if self.entity_description.device_class == DEVICE_CLASS_TEMPERATURE:

View File

@ -5,7 +5,8 @@
},
"error": {
"cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4",
"invalid_api_key": "\u05de\u05e4\u05ea\u05d7 API \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9"
"invalid_api_key": "\u05de\u05e4\u05ea\u05d7 API \u05dc\u05d0 \u05d7\u05d5\u05e7\u05d9",
"requests_exceeded": "\u05d0\u05d9\u05e8\u05e2\u05d4 \u05d7\u05e8\u05d9\u05d2\u05d4 \u05de\u05de\u05e1\u05e4\u05e8 \u05d4\u05d1\u05e7\u05e9\u05d5\u05ea \u05d4\u05de\u05d5\u05ea\u05e8 \u05dc-API \u05e9\u05dc Accuweather. \u05e2\u05dc\u05d9\u05da \u05dc\u05d4\u05de\u05ea\u05d9\u05df \u05d0\u05d5 \u05dc\u05e9\u05e0\u05d5\u05ea \u05d0\u05ea \u05de\u05e4\u05ea\u05d7 \u05d4-API."
},
"step": {
"user": {
@ -15,6 +16,7 @@
"longitude": "\u05e7\u05d5 \u05d0\u05d5\u05e8\u05da",
"name": "\u05e9\u05dd"
},
"description": "\u05d0\u05dd \u05d4\u05d9\u05e0\u05da \u05d6\u05e7\u05d5\u05e7 \u05dc\u05e2\u05d6\u05e8\u05d4 \u05e2\u05dd \u05d4\u05ea\u05e6\u05d5\u05e8\u05d4, \u05d9\u05e9 \u05dc\u05e2\u05d9\u05d9\u05df \u05db\u05d0\u05df: https://www.home-assistant.io/integrations/accuweather/\n\n\u05d7\u05d9\u05d9\u05e9\u05e0\u05d9\u05dd \u05de\u05e1\u05d5\u05d9\u05de\u05d9\u05dd \u05d0\u05d9\u05e0\u05dd \u05d6\u05de\u05d9\u05e0\u05d9\u05dd \u05db\u05d1\u05e8\u05d9\u05e8\u05ea \u05de\u05d7\u05d3\u05dc. \u05d1\u05d0\u05e4\u05e9\u05e8\u05d5\u05ea\u05da \u05dc\u05d4\u05e4\u05d5\u05da \u05d0\u05d5\u05ea\u05dd \u05dc\u05d6\u05de\u05d9\u05e0\u05d9\u05dd \u05d1\u05e8\u05d9\u05e9\u05d5\u05dd \u05d4\u05d9\u05e9\u05d5\u05d9\u05d5\u05ea \u05dc\u05d0\u05d7\u05e8 \u05e7\u05d1\u05d9\u05e2\u05ea \u05d4\u05ea\u05e6\u05d5\u05e8\u05d4 \u05e9\u05dc \u05d4\u05e9\u05d9\u05dc\u05d5\u05d1.\n\u05ea\u05d7\u05d6\u05d9\u05ea \u05de\u05d6\u05d2 \u05d4\u05d0\u05d5\u05d5\u05d9\u05e8 \u05d0\u05d9\u05e0\u05d4 \u05d6\u05de\u05d9\u05e0\u05d4 \u05db\u05d1\u05e8\u05d9\u05e8\u05ea \u05de\u05d7\u05d3\u05dc. \u05d1\u05d0\u05e4\u05e9\u05e8\u05d5\u05ea\u05da \u05dc\u05d4\u05e4\u05d5\u05da \u05d0\u05d5\u05ea\u05d5 \u05dc\u05d6\u05de\u05d9\u05df \u05d1\u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea \u05d4\u05e9\u05d9\u05dc\u05d5\u05d1.",
"title": "AccuWeather"
}
}
@ -22,6 +24,9 @@
"options": {
"step": {
"user": {
"data": {
"forecast": "\u05ea\u05d7\u05d6\u05d9\u05ea \u05de\u05d6\u05d2 \u05d4\u05d0\u05d5\u05d5\u05d9\u05e8"
},
"description": "\u05d1\u05e9\u05dc \u05de\u05d2\u05d1\u05dc\u05d5\u05ea \u05d4\u05d2\u05d9\u05e8\u05e1\u05d4 \u05d4\u05d7\u05d9\u05e0\u05de\u05d9\u05ea \u05e9\u05dc \u05de\u05e4\u05ea\u05d7 \u05d4-API \u05e9\u05dc AccuWeather, \u05db\u05d0\u05e9\u05e8 \u05ea\u05e4\u05e2\u05d9\u05dc \u05ea\u05d7\u05d6\u05d9\u05ea \u05de\u05d6\u05d2 \u05d0\u05d5\u05d5\u05d9\u05e8, \u05e2\u05d3\u05db\u05d5\u05e0\u05d9 \u05e0\u05ea\u05d5\u05e0\u05d9\u05dd \u05d9\u05d1\u05d5\u05e6\u05e2\u05d5 \u05db\u05dc 80 \u05d3\u05e7\u05d5\u05ea \u05d1\u05de\u05e7\u05d5\u05dd \u05db\u05dc 40 \u05d3\u05e7\u05d5\u05ea.",
"title": "\u05d0\u05e4\u05e9\u05e8\u05d5\u05d9\u05d5\u05ea AccuWeather"
}
@ -29,7 +34,8 @@
},
"system_health": {
"info": {
"can_reach_server": "\u05d4\u05e9\u05d2\u05ea \u05e9\u05e8\u05ea AccuWeather"
"can_reach_server": "\u05d4\u05e9\u05d2\u05ea \u05e9\u05e8\u05ea AccuWeather",
"remaining_requests": "\u05d4\u05d1\u05e7\u05e9\u05d5\u05ea \u05d4\u05e0\u05d5\u05ea\u05e8\u05d5\u05ea \u05de\u05d5\u05ea\u05e8\u05d5\u05ea"
}
}
}

View File

@ -5,7 +5,8 @@
},
"error": {
"cannot_connect": "Sikertelen csatlakoz\u00e1s",
"invalid_api_key": "\u00c9rv\u00e9nytelen API kulcs"
"invalid_api_key": "\u00c9rv\u00e9nytelen API kulcs",
"requests_exceeded": "T\u00fall\u00e9pt\u00e9k az Accuweather API-hoz beny\u00fajtott k\u00e9relmek megengedett sz\u00e1m\u00e1t. Meg kell v\u00e1rnia vagy m\u00f3dos\u00edtania kell az API-kulcsot."
},
"step": {
"user": {
@ -15,6 +16,7 @@
"longitude": "Hossz\u00fas\u00e1g",
"name": "N\u00e9v"
},
"description": "Ha seg\u00edts\u00e9gre van sz\u00fcks\u00e9ge a konfigur\u00e1l\u00e1shoz, n\u00e9zze meg itt: https://www.home-assistant.io/integrations/accuweather/ \n\nEgyes \u00e9rz\u00e9kel\u0151k alap\u00e9rtelmez\u00e9s szerint nincsenek enged\u00e9lyezve. Az integr\u00e1ci\u00f3s konfigur\u00e1ci\u00f3 ut\u00e1n enged\u00e9lyezheti \u0151ket az entit\u00e1s-nyilv\u00e1ntart\u00e1sban.\nAz id\u0151j\u00e1r\u00e1s-el\u0151rejelz\u00e9s alap\u00e9rtelmez\u00e9s szerint nincs enged\u00e9lyezve. Ezt az integr\u00e1ci\u00f3s be\u00e1ll\u00edt\u00e1sokban enged\u00e9lyezheti.",
"title": "AccuWeather"
}
}
@ -22,6 +24,10 @@
"options": {
"step": {
"user": {
"data": {
"forecast": "Id\u0151j\u00e1r\u00e1s el\u0151rejelz\u00e9s"
},
"description": "Az AccuWeather API kulcs ingyenes verzi\u00f3j\u00e1nak korl\u00e1tai miatt, amikor enged\u00e9lyezi az id\u0151j\u00e1r\u00e1s -el\u0151rejelz\u00e9st, az adatfriss\u00edt\u00e9seket 40 percenk\u00e9nt 80 percenk\u00e9nt hajtj\u00e1k v\u00e9gre.",
"title": "AccuWeather be\u00e1ll\u00edt\u00e1sok"
}
}

View File

@ -0,0 +1,9 @@
{
"state": {
"accuweather__pressure_tendency": {
"falling": "\u05d9\u05d5\u05e8\u05d3",
"rising": "\u05e2\u05d5\u05dc\u05d4",
"steady": "\u05d9\u05e6\u05d9\u05d1"
}
}
}

View File

@ -61,7 +61,7 @@ class AcmedaCover(AcmedaBase, CoverEntity):
None is unknown, 0 is closed, 100 is fully open.
"""
position = None
if self.roller.type in [7, 10]:
if self.roller.type in (7, 10):
position = 100 - self.roller.closed_percent
return position

View File

@ -34,7 +34,7 @@ class AcmedaBattery(AcmedaBase, SensorEntity):
"""Representation of a Acmeda cover device."""
device_class = DEVICE_CLASS_BATTERY
unit_of_measurement = PERCENTAGE
_attr_native_unit_of_measurement = PERCENTAGE
@property
def name(self):
@ -42,6 +42,6 @@ class AcmedaBattery(AcmedaBase, SensorEntity):
return f"{super().name} Battery"
@property
def state(self):
def native_value(self):
"""Return the state of the device."""
return self.roller.battery

View File

@ -2,6 +2,14 @@
"config": {
"abort": {
"no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton"
},
"step": {
"user": {
"data": {
"id": "Gazdag\u00e9p azonos\u00edt\u00f3"
},
"title": "V\u00e1lassza ki a hozz\u00e1adni k\u00edv\u00e1nt hubot"
}
}
}
}

View File

@ -4,7 +4,9 @@ from __future__ import annotations
import re
from typing import Final
LEASES_REGEX: Final[re.Pattern] = re.compile(
# mypy: disallow-any-generics
LEASES_REGEX: Final[re.Pattern[str]] = re.compile(
r"(?P<ip>([0-9]{1,3}[\.]){3}[0-9]{1,3})"
+ r"\smac:\s(?P<mac>([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))"
+ r"\svalid\sfor:\s(?P<timevalid>(-?\d+))"

View File

@ -49,20 +49,19 @@ async def async_setup_entry(
class AdaxDevice(ClimateEntity):
"""Representation of a heater."""
_attr_hvac_modes = [HVAC_MODE_HEAT, HVAC_MODE_OFF]
_attr_max_temp = 35
_attr_min_temp = 5
_attr_supported_features = SUPPORT_TARGET_TEMPERATURE
_attr_target_temperature_step = PRECISION_WHOLE
_attr_temperature_unit = TEMP_CELSIUS
def __init__(self, heater_data: dict[str, Any], adax_data_handler: Adax) -> None:
"""Initialize the heater."""
self._heater_data = heater_data
self._adax_data_handler = adax_data_handler
@property
def supported_features(self) -> int:
"""Return the list of supported features."""
return SUPPORT_TARGET_TEMPERATURE
@property
def unique_id(self) -> str:
"""Return a unique ID."""
return f"{self._heater_data['homeId']}_{self._heater_data['id']}"
self._attr_unique_id = f"{heater_data['homeId']}_{heater_data['id']}"
@property
def name(self) -> str:
@ -83,11 +82,6 @@ class AdaxDevice(ClimateEntity):
return "mdi:radiator"
return "mdi:radiator-off"
@property
def hvac_modes(self) -> list[str]:
"""Return the list of available hvac operation modes."""
return [HVAC_MODE_HEAT, HVAC_MODE_OFF]
async def async_set_hvac_mode(self, hvac_mode: str) -> None:
"""Set hvac mode."""
if hvac_mode == HVAC_MODE_HEAT:
@ -105,21 +99,6 @@ class AdaxDevice(ClimateEntity):
return
await self._adax_data_handler.update()
@property
def temperature_unit(self) -> str:
"""Return the unit of measurement which this device uses."""
return TEMP_CELSIUS
@property
def min_temp(self) -> int:
"""Return the minimum temperature."""
return 5
@property
def max_temp(self) -> int:
"""Return the maximum temperature."""
return 35
@property
def current_temperature(self) -> float | None:
"""Return the current temperature."""
@ -130,11 +109,6 @@ class AdaxDevice(ClimateEntity):
"""Return the temperature we try to reach."""
return self._heater_data.get("targetTemperature")
@property
def target_temperature_step(self) -> int:
"""Return the supported step of target temperature."""
return PRECISION_WHOLE
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)

View File

@ -10,6 +10,7 @@
"step": {
"user": {
"data": {
"account_id": "ID \u00fa\u010dtu",
"host": "Hostitel",
"password": "Heslo"
}

View File

@ -0,0 +1,11 @@
{
"config": {
"step": {
"user": {
"data": {
"account_id": "ID de la cuenta"
}
}
}
}
}

View File

@ -0,0 +1,20 @@
{
"config": {
"abort": {
"already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van"
},
"error": {
"cannot_connect": "Nem siker\u00fclt csatlakozni",
"invalid_auth": "\u00c9rv\u00e9nytelen hiteles\u00edt\u00e9s"
},
"step": {
"user": {
"data": {
"account_id": "Fi\u00f3k ID",
"host": "Gazdag\u00e9p",
"password": "Jelsz\u00f3"
}
}
}
}
}

View File

@ -0,0 +1,20 @@
{
"config": {
"abort": {
"already_configured": "Enheten er allerede konfigurert"
},
"error": {
"cannot_connect": "Tilkobling mislyktes",
"invalid_auth": "Ugyldig godkjenning"
},
"step": {
"user": {
"data": {
"account_id": "Konto-ID",
"host": "Vert",
"password": "Passord"
}
}
}
}
}

View File

@ -0,0 +1,14 @@
{
"config": {
"error": {
"cannot_connect": "\u8fde\u63a5\u5931\u8d25"
},
"step": {
"user": {
"data": {
"password": "\u5bc6\u7801"
}
}
}
}
}

View File

@ -198,7 +198,7 @@ class AdGuardHomeDeviceEntity(AdGuardHomeEntity):
"""Return device information about this AdGuard Home instance."""
return {
"identifiers": {
(DOMAIN, self.adguard.host, self.adguard.port, self.adguard.base_path)
(DOMAIN, self.adguard.host, self.adguard.port, self.adguard.base_path) # type: ignore
},
"name": "AdGuard Home",
"manufacturer": "AdGuard Team",

View File

@ -51,6 +51,7 @@ class AdGuardHomeFlowHandler(ConfigFlow, domain=DOMAIN):
self, errors: dict[str, str] | None = None
) -> FlowResult:
"""Show the Hass.io confirmation form to the user."""
assert self._hassio_discovery
return self.async_show_form(
step_id="hassio_confirm",
description_placeholders={"addon": self._hassio_discovery["addon"]},
@ -73,11 +74,13 @@ class AdGuardHomeFlowHandler(ConfigFlow, domain=DOMAIN):
session = async_get_clientsession(self.hass, user_input[CONF_VERIFY_SSL])
username: str | None = user_input.get(CONF_USERNAME)
password: str | None = user_input.get(CONF_PASSWORD)
adguard = AdGuardHome(
user_input[CONF_HOST],
port=user_input[CONF_PORT],
username=user_input.get(CONF_USERNAME),
password=user_input.get(CONF_PASSWORD),
username=username, # type:ignore[arg-type]
password=password, # type:ignore[arg-type]
tls=user_input[CONF_SSL],
verify_ssl=user_input[CONF_VERIFY_SSL],
session=session,
@ -122,6 +125,7 @@ class AdGuardHomeFlowHandler(ConfigFlow, domain=DOMAIN):
session = async_get_clientsession(self.hass, False)
assert self._hassio_discovery
adguard = AdGuardHome(
self._hassio_discovery[CONF_HOST],
port=self._hassio_discovery[CONF_PORT],

View File

@ -62,7 +62,7 @@ class AdGuardHomeSensor(AdGuardHomeDeviceEntity, SensorEntity):
enabled_default: bool = True,
) -> None:
"""Initialize AdGuard Home sensor."""
self._state = None
self._state: int | str | None = None
self._unit_of_measurement = unit_of_measurement
self.measurement = measurement
@ -82,12 +82,12 @@ class AdGuardHomeSensor(AdGuardHomeDeviceEntity, SensorEntity):
)
@property
def state(self) -> str | None:
def native_value(self) -> int | str | None:
"""Return the state of the sensor."""
return self._state
@property
def unit_of_measurement(self) -> str | None:
def native_unit_of_measurement(self) -> str | None:
"""Return the unit this state is expressed in."""
return self._unit_of_measurement

View File

@ -1,7 +1,8 @@
{
"config": {
"abort": {
"already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van"
"already_configured": "A szolg\u00e1ltat\u00e1s m\u00e1r konfigur\u00e1lva van",
"existing_instance_updated": "Friss\u00edtette a megl\u00e9v\u0151 konfigur\u00e1ci\u00f3t."
},
"error": {
"cannot_connect": "Sikertelen csatlakoz\u00e1s"
@ -19,7 +20,8 @@
"ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata",
"username": "Felhaszn\u00e1l\u00f3n\u00e9v",
"verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se"
}
},
"description": "\u00c1ll\u00edtsa be az AdGuard Home p\u00e9ld\u00e1nyt, hogy lehet\u0151v\u00e9 tegye a fel\u00fcgyeletet \u00e9s az ir\u00e1ny\u00edt\u00e1st."
}
}
}

View File

@ -1,14 +1,23 @@
{
"config": {
"abort": {
"already_configured": "\u670d\u52a1\u5df2\u88ab\u914d\u7f6e",
"existing_instance_updated": "\u66f4\u65b0\u4e86\u73b0\u6709\u914d\u7f6e\u3002"
},
"error": {
"cannot_connect": "\u8fde\u63a5\u5931\u8d25"
},
"step": {
"user": {
"data": {
"host": "\u4e3b\u673a\u5730\u5740",
"password": "\u5bc6\u7801",
"username": "\u7528\u6237\u540d"
}
"port": "\u7aef\u53e3",
"ssl": "\u4f7f\u7528 SSL \u8bc1\u4e66\u51ed\u8bc1",
"username": "\u7528\u6237\u540d",
"verify_ssl": "\u9a8c\u8bc1 SSL \u8bc1\u4e66\u51ed\u8bc1"
},
"description": "\u8bbe\u7f6e\u60a8\u7684 AdGuard Home \u5b9e\u4f8b\u4ee5\u5141\u8bb8\u76d1\u89c6\u548c\u63a7\u5236"
}
}
}

View File

@ -50,7 +50,7 @@ class AdsSensor(AdsEntity, SensorEntity):
def __init__(self, ads_hub, ads_var, ads_type, name, unit_of_measurement, factor):
"""Initialize AdsSensor entity."""
super().__init__(ads_hub, name, ads_var)
self._attr_unit_of_measurement = unit_of_measurement
self._attr_native_unit_of_measurement = unit_of_measurement
self._ads_type = ads_type
self._factor = factor
@ -64,6 +64,6 @@ class AdsSensor(AdsEntity, SensorEntity):
)
@property
def state(self) -> StateType:
def native_value(self) -> StateType:
"""Return the state of the device."""
return self._state_dict[STATE_KEY_STATE]

View File

@ -1,7 +1,11 @@
"""Sensor platform for Advantage Air integration."""
import voluptuous as vol
from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT, SensorEntity
from homeassistant.components.sensor import (
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
SensorEntity,
)
from homeassistant.const import PERCENTAGE, TEMP_CELSIUS
from homeassistant.helpers import config_validation as cv, entity_platform
@ -45,7 +49,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
"""Representation of Advantage Air timer control."""
_attr_unit_of_measurement = ADVANTAGE_AIR_SET_COUNTDOWN_UNIT
_attr_native_unit_of_measurement = ADVANTAGE_AIR_SET_COUNTDOWN_UNIT
def __init__(self, instance, ac_key, action):
"""Initialize the Advantage Air timer control."""
@ -58,7 +62,7 @@ class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
)
@property
def state(self):
def native_value(self):
"""Return the current value."""
return self._ac[self._time_key]
@ -78,7 +82,7 @@ class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
"""Representation of Advantage Air Zone Vent Sensor."""
_attr_unit_of_measurement = PERCENTAGE
_attr_native_unit_of_measurement = PERCENTAGE
_attr_state_class = STATE_CLASS_MEASUREMENT
def __init__(self, instance, ac_key, zone_key):
@ -90,7 +94,7 @@ class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
)
@property
def state(self):
def native_value(self):
"""Return the current value of the air vent."""
if self._zone["state"] == ADVANTAGE_AIR_STATE_OPEN:
return self._zone["value"]
@ -107,19 +111,19 @@ class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
"""Representation of Advantage Air Zone wireless signal sensor."""
_attr_unit_of_measurement = PERCENTAGE
_attr_native_unit_of_measurement = PERCENTAGE
_attr_state_class = STATE_CLASS_MEASUREMENT
def __init__(self, instance, ac_key, zone_key):
"""Initialize an Advantage Air Zone wireless signal sensor."""
super().__init__(instance, ac_key, zone_key=zone_key)
super().__init__(instance, ac_key, zone_key)
self._attr_name = f'{self._zone["name"]} Signal'
self._attr_unique_id = (
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-signal'
)
@property
def state(self):
def native_value(self):
"""Return the current value of the wireless signal."""
return self._zone["rssi"]
@ -138,20 +142,22 @@ class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
class AdvantageAirZoneTemp(AdvantageAirEntity, SensorEntity):
"""Representation of Advantage Air Zone wireless signal sensor."""
"""Representation of Advantage Air Zone temperature sensor."""
_attr_unit_of_measurement = TEMP_CELSIUS
_attr_native_unit_of_measurement = TEMP_CELSIUS
_attr_device_class = DEVICE_CLASS_TEMPERATURE
_attr_state_class = STATE_CLASS_MEASUREMENT
_attr_icon = "mdi:thermometer"
_attr_entity_registry_enabled_default = False
def __init__(self, instance, ac_key, zone_key):
"""Initialize an Advantage Air Zone Temp Sensor."""
super().__init__(instance, ac_key, zone_key)
self._attr_name = f'{self._zone["name"]} Temperature'
self._attr_unique_id = f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-{self.zone_key}-temp'
self._attr_unique_id = (
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-temp'
)
@property
def state(self):
def native_value(self):
"""Return the current value of the measured temperature."""
return self._zone["measuredTemp"]

View File

@ -2,6 +2,7 @@
"config": {
"step": {
"user": {
"description": "Con\u00e9ctese a la API de su tableta de pared Advantage Air.",
"title": "Conectar"
}
}

View File

@ -85,7 +85,7 @@ class AbstractAemetSensor(CoordinatorEntity, SensorEntity):
self._attr_name = f"{self._name} {self._sensor_name}"
self._attr_unique_id = self._unique_id
self._attr_device_class = sensor_configuration.get(SENSOR_DEVICE_CLASS)
self._attr_unit_of_measurement = sensor_configuration.get(SENSOR_UNIT)
self._attr_native_unit_of_measurement = sensor_configuration.get(SENSOR_UNIT)
class AemetSensor(AbstractAemetSensor):
@ -106,7 +106,7 @@ class AemetSensor(AbstractAemetSensor):
self._weather_coordinator = weather_coordinator
@property
def state(self):
def native_value(self):
"""Return the state of the device."""
return self._weather_coordinator.data.get(self._sensor_type)
@ -134,7 +134,7 @@ class AemetForecastSensor(AbstractAemetSensor):
)
@property
def state(self):
def native_value(self):
"""Return the state of the device."""
forecast = None
forecasts = self._weather_coordinator.data.get(

View File

@ -5,8 +5,18 @@
"data": {
"name": "Nombre de la integraci\u00f3n"
},
"description": "Configure la integraci\u00f3n de AEMET OpenData. Para generar la clave API vaya a https://opendata.aemet.es/centrodedescargas/altaUsuario",
"title": "AEMET OpenData"
}
}
},
"options": {
"step": {
"init": {
"data": {
"station_updates": "Recopile datos de las estaciones meteorol\u00f3gicas de AEMET"
}
}
}
}
}

View File

@ -1,4 +1,6 @@
"""Weather data coordinator for the AEMET OpenData service."""
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import timedelta
import logging
@ -95,7 +97,7 @@ def format_condition(condition: str) -> str:
return condition
def format_float(value) -> float:
def format_float(value) -> float | None:
"""Try converting string to float."""
try:
return float(value)
@ -103,7 +105,7 @@ def format_float(value) -> float:
return None
def format_int(value) -> int:
def format_int(value) -> int | None:
"""Try converting string to int."""
try:
return int(value)

View File

@ -109,7 +109,7 @@ async def async_setup_platform(
class AfterShipSensor(SensorEntity):
"""Representation of a AfterShip sensor."""
_attr_unit_of_measurement: str = "packages"
_attr_native_unit_of_measurement: str = "packages"
_attr_icon: str = ICON
def __init__(self, aftership: Tracking, name: str) -> None:
@ -120,7 +120,7 @@ class AfterShipSensor(SensorEntity):
self._attr_name = name
@property
def state(self) -> int | None:
def native_value(self) -> int | None:
"""Return the state of the sensor."""
return self._state

View File

@ -12,7 +12,8 @@
"data": {
"host": "Hoszt",
"port": "Port"
}
},
"title": "\u00c1ll\u00edtsa be az Agent DVR-t"
}
}
}

View File

@ -1,7 +1,20 @@
{
"config": {
"abort": {
"already_configured": "\u8bbe\u5907\u5df2\u88ab\u914d\u7f6e"
},
"error": {
"already_in_progress": "\u914d\u7f6e\u6d41\u5df2\u8fdb\u884c\u4e2d",
"cannot_connect": "\u8fde\u63a5\u5931\u8d25"
},
"step": {
"user": {
"data": {
"host": "\u4e3b\u673a\u5730\u5740",
"port": "\u7aef\u53e3"
},
"title": "\u914d\u7f6e Agent DVR"
}
}
}
}

View File

@ -6,7 +6,11 @@ from typing import Final
from homeassistant.components.sensor import STATE_CLASS_MEASUREMENT
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
DEVICE_CLASS_AQI,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PM1,
DEVICE_CLASS_PM10,
DEVICE_CLASS_PM25,
DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE,
PERCENTAGE,
@ -49,35 +53,36 @@ NO_AIRLY_SENSORS: Final = "There are no Airly sensors in this area yet."
SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
AirlySensorEntityDescription(
key=ATTR_API_CAQI,
device_class=DEVICE_CLASS_AQI,
name=ATTR_API_CAQI,
unit_of_measurement="CAQI",
native_unit_of_measurement="CAQI",
),
AirlySensorEntityDescription(
key=ATTR_API_PM1,
icon="mdi:blur",
device_class=DEVICE_CLASS_PM1,
name=ATTR_API_PM1,
unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=STATE_CLASS_MEASUREMENT,
),
AirlySensorEntityDescription(
key=ATTR_API_PM25,
icon="mdi:blur",
device_class=DEVICE_CLASS_PM25,
name="PM2.5",
unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=STATE_CLASS_MEASUREMENT,
),
AirlySensorEntityDescription(
key=ATTR_API_PM10,
icon="mdi:blur",
device_class=DEVICE_CLASS_PM10,
name=ATTR_API_PM10,
unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=STATE_CLASS_MEASUREMENT,
),
AirlySensorEntityDescription(
key=ATTR_API_HUMIDITY,
device_class=DEVICE_CLASS_HUMIDITY,
name=ATTR_API_HUMIDITY.capitalize(),
unit_of_measurement=PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=STATE_CLASS_MEASUREMENT,
value=lambda value: round(value, 1),
),
@ -85,14 +90,14 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
key=ATTR_API_PRESSURE,
device_class=DEVICE_CLASS_PRESSURE,
name=ATTR_API_PRESSURE.capitalize(),
unit_of_measurement=PRESSURE_HPA,
native_unit_of_measurement=PRESSURE_HPA,
state_class=STATE_CLASS_MEASUREMENT,
),
AirlySensorEntityDescription(
key=ATTR_API_TEMPERATURE,
device_class=DEVICE_CLASS_TEMPERATURE,
name=ATTR_API_TEMPERATURE.capitalize(),
unit_of_measurement=TEMP_CELSIUS,
native_unit_of_measurement=TEMP_CELSIUS,
state_class=STATE_CLASS_MEASUREMENT,
value=lambda value: round(value, 1),
),

View File

@ -84,7 +84,7 @@ class AirlySensor(CoordinatorEntity, SensorEntity):
self.entity_description = description
@property
def state(self) -> StateType:
def native_value(self) -> StateType:
"""Return the state."""
state = self.coordinator.data[self.entity_description.key]
return cast(StateType, self.entity_description.value(state))

View File

@ -72,11 +72,11 @@ class AirNowSensor(CoordinatorEntity, SensorEntity):
self._attr_name = f"AirNow {SENSOR_TYPES[self.kind][ATTR_LABEL]}"
self._attr_icon = SENSOR_TYPES[self.kind][ATTR_ICON]
self._attr_device_class = SENSOR_TYPES[self.kind][ATTR_DEVICE_CLASS]
self._attr_unit_of_measurement = SENSOR_TYPES[self.kind][ATTR_UNIT]
self._attr_native_unit_of_measurement = SENSOR_TYPES[self.kind][ATTR_UNIT]
self._attr_unique_id = f"{self.coordinator.latitude}-{self.coordinator.longitude}-{self.kind.lower()}"
@property
def state(self):
def native_value(self):
"""Return the state."""
self._state = self.coordinator.data[self.kind]
return self._state

View File

@ -0,0 +1,81 @@
"""The AirTouch4 integration."""
import logging
from airtouch4pyapi import AirTouch
from airtouch4pyapi.airtouch import AirTouchStatus
from homeassistant.components.climate import SCAN_INTERVAL
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
PLATFORMS = ["climate"]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up AirTouch4 from a config entry."""
hass.data.setdefault(DOMAIN, {})
host = entry.data[CONF_HOST]
airtouch = AirTouch(host)
await airtouch.UpdateInfo()
info = airtouch.GetAcs()
if not info:
raise ConfigEntryNotReady
coordinator = AirtouchDataUpdateCoordinator(hass, airtouch)
await coordinator.async_config_entry_first_refresh()
hass.data[DOMAIN][entry.entry_id] = coordinator
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
class AirtouchDataUpdateCoordinator(DataUpdateCoordinator):
"""Class to manage fetching Airtouch data."""
def __init__(self, hass, airtouch):
"""Initialize global Airtouch data updater."""
self.airtouch = airtouch
super().__init__(
hass,
_LOGGER,
name=DOMAIN,
update_interval=SCAN_INTERVAL,
)
async def _async_update_data(self):
"""Fetch data from Airtouch."""
await self.airtouch.UpdateInfo()
if self.airtouch.Status != AirTouchStatus.OK:
raise UpdateFailed("Airtouch connection issue")
return {
"acs": [
{"ac_number": ac.AcNumber, "is_on": ac.IsOn}
for ac in self.airtouch.GetAcs()
],
"groups": [
{
"group_number": group.GroupNumber,
"group_name": group.GroupName,
"is_on": group.IsOn,
}
for group in self.airtouch.GetGroups()
],
}

View File

@ -0,0 +1,335 @@
"""AirTouch 4 component to control of AirTouch 4 Climate Devices."""
import logging
from homeassistant.components.climate import ClimateEntity
from homeassistant.components.climate.const import (
FAN_AUTO,
FAN_DIFFUSE,
FAN_FOCUS,
FAN_HIGH,
FAN_LOW,
FAN_MEDIUM,
HVAC_MODE_AUTO,
HVAC_MODE_COOL,
HVAC_MODE_DRY,
HVAC_MODE_FAN_ONLY,
HVAC_MODE_HEAT,
HVAC_MODE_OFF,
SUPPORT_FAN_MODE,
SUPPORT_TARGET_TEMPERATURE,
)
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
from homeassistant.core import callback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
AT_TO_HA_STATE = {
"Heat": HVAC_MODE_HEAT,
"Cool": HVAC_MODE_COOL,
"AutoHeat": HVAC_MODE_AUTO, # airtouch reports either autoheat or autocool
"AutoCool": HVAC_MODE_AUTO,
"Auto": HVAC_MODE_AUTO,
"Dry": HVAC_MODE_DRY,
"Fan": HVAC_MODE_FAN_ONLY,
}
HA_STATE_TO_AT = {
HVAC_MODE_HEAT: "Heat",
HVAC_MODE_COOL: "Cool",
HVAC_MODE_AUTO: "Auto",
HVAC_MODE_DRY: "Dry",
HVAC_MODE_FAN_ONLY: "Fan",
HVAC_MODE_OFF: "Off",
}
AT_TO_HA_FAN_SPEED = {
"Quiet": FAN_DIFFUSE,
"Low": FAN_LOW,
"Medium": FAN_MEDIUM,
"High": FAN_HIGH,
"Powerful": FAN_FOCUS,
"Auto": FAN_AUTO,
"Turbo": "turbo",
}
AT_GROUP_MODES = [HVAC_MODE_OFF, HVAC_MODE_FAN_ONLY]
HA_FAN_SPEED_TO_AT = {value: key for key, value in AT_TO_HA_FAN_SPEED.items()}
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up the Airtouch 4."""
coordinator = hass.data[DOMAIN][config_entry.entry_id]
info = coordinator.data
entities = [
AirtouchGroup(coordinator, group["group_number"], info)
for group in info["groups"]
] + [AirtouchAC(coordinator, ac["ac_number"], info) for ac in info["acs"]]
_LOGGER.debug(" Found entities %s", entities)
async_add_entities(entities)
class AirtouchAC(CoordinatorEntity, ClimateEntity):
"""Representation of an AirTouch 4 ac."""
_attr_supported_features = SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE
_attr_temperature_unit = TEMP_CELSIUS
def __init__(self, coordinator, ac_number, info):
"""Initialize the climate device."""
super().__init__(coordinator)
self._ac_number = ac_number
self._airtouch = coordinator.airtouch
self._info = info
self._unit = self._airtouch.GetAcs()[self._ac_number]
@callback
def _handle_coordinator_update(self):
self._unit = self._airtouch.GetAcs()[self._ac_number]
return super()._handle_coordinator_update()
@property
def device_info(self):
"""Return device info for this device."""
return {
"identifiers": {(DOMAIN, self.unique_id)},
"name": self.name,
"manufacturer": "Airtouch",
"model": "Airtouch 4",
}
@property
def unique_id(self):
"""Return unique ID for this device."""
return f"ac_{self._ac_number}"
@property
def current_temperature(self):
"""Return the current temperature."""
return self._unit.Temperature
@property
def name(self):
"""Return the name of the climate device."""
return f"AC {self._ac_number}"
@property
def fan_mode(self):
"""Return fan mode of the AC this group belongs to."""
return AT_TO_HA_FAN_SPEED[self._airtouch.acs[self._ac_number].AcFanSpeed]
@property
def fan_modes(self):
"""Return the list of available fan modes."""
airtouch_fan_speeds = self._airtouch.GetSupportedFanSpeedsForAc(self._ac_number)
return [AT_TO_HA_FAN_SPEED[speed] for speed in airtouch_fan_speeds]
@property
def hvac_mode(self):
"""Return hvac target hvac state."""
is_off = self._unit.PowerState == "Off"
if is_off:
return HVAC_MODE_OFF
return AT_TO_HA_STATE[self._airtouch.acs[self._ac_number].AcMode]
@property
def hvac_modes(self):
"""Return the list of available operation modes."""
airtouch_modes = self._airtouch.GetSupportedCoolingModesForAc(self._ac_number)
modes = [AT_TO_HA_STATE[mode] for mode in airtouch_modes]
modes.append(HVAC_MODE_OFF)
return modes
async def async_set_hvac_mode(self, hvac_mode):
"""Set new operation mode."""
if hvac_mode not in HA_STATE_TO_AT:
raise ValueError(f"Unsupported HVAC mode: {hvac_mode}")
if hvac_mode == HVAC_MODE_OFF:
return await self.async_turn_off()
await self._airtouch.SetCoolingModeForAc(
self._ac_number, HA_STATE_TO_AT[hvac_mode]
)
# in case it isn't already, unless the HVAC mode was off, then the ac should be on
await self.async_turn_on()
self._unit = self._airtouch.GetAcs()[self._ac_number]
_LOGGER.debug("Setting operation mode of %s to %s", self._ac_number, hvac_mode)
self.async_write_ha_state()
async def async_set_fan_mode(self, fan_mode):
"""Set new fan mode."""
if fan_mode not in self.fan_modes:
raise ValueError(f"Unsupported fan mode: {fan_mode}")
_LOGGER.debug("Setting fan mode of %s to %s", self._ac_number, fan_mode)
await self._airtouch.SetFanSpeedForAc(
self._ac_number, HA_FAN_SPEED_TO_AT[fan_mode]
)
self._unit = self._airtouch.GetAcs()[self._ac_number]
self.async_write_ha_state()
async def async_turn_on(self):
"""Turn on."""
_LOGGER.debug("Turning %s on", self.unique_id)
# in case ac is not on. Airtouch turns itself off if no groups are turned on
# (even if groups turned back on)
await self._airtouch.TurnAcOn(self._ac_number)
async def async_turn_off(self):
"""Turn off."""
_LOGGER.debug("Turning %s off", self.unique_id)
await self._airtouch.TurnAcOff(self._ac_number)
self.async_write_ha_state()
class AirtouchGroup(CoordinatorEntity, ClimateEntity):
"""Representation of an AirTouch 4 group."""
_attr_supported_features = SUPPORT_TARGET_TEMPERATURE
_attr_temperature_unit = TEMP_CELSIUS
_attr_hvac_modes = AT_GROUP_MODES
def __init__(self, coordinator, group_number, info):
"""Initialize the climate device."""
super().__init__(coordinator)
self._group_number = group_number
self._airtouch = coordinator.airtouch
self._info = info
self._unit = self._airtouch.GetGroupByGroupNumber(self._group_number)
@callback
def _handle_coordinator_update(self):
self._unit = self._airtouch.GetGroupByGroupNumber(self._group_number)
return super()._handle_coordinator_update()
@property
def device_info(self):
"""Return device info for this device."""
return {
"identifiers": {(DOMAIN, self.unique_id)},
"name": self.name,
"manufacturer": "Airtouch",
"model": "Airtouch 4",
}
@property
def unique_id(self):
"""Return unique ID for this device."""
return self._group_number
@property
def min_temp(self):
"""Return Minimum Temperature for AC of this group."""
return self._airtouch.acs[self._unit.BelongsToAc].MinSetpoint
@property
def max_temp(self):
"""Return Max Temperature for AC of this group."""
return self._airtouch.acs[self._unit.BelongsToAc].MaxSetpoint
@property
def name(self):
"""Return the name of the climate device."""
return self._unit.GroupName
@property
def current_temperature(self):
"""Return the current temperature."""
return self._unit.Temperature
@property
def target_temperature(self):
"""Return the temperature we are trying to reach."""
return self._unit.TargetSetpoint
@property
def hvac_mode(self):
"""Return hvac target hvac state."""
# there are other power states that aren't 'on' but still count as on (eg. 'Turbo')
is_off = self._unit.PowerState == "Off"
if is_off:
return HVAC_MODE_OFF
return HVAC_MODE_FAN_ONLY
async def async_set_hvac_mode(self, hvac_mode):
"""Set new operation mode."""
if hvac_mode not in HA_STATE_TO_AT:
raise ValueError(f"Unsupported HVAC mode: {hvac_mode}")
if hvac_mode == HVAC_MODE_OFF:
return await self.async_turn_off()
if self.hvac_mode == HVAC_MODE_OFF:
await self.async_turn_on()
self._unit = self._airtouch.GetGroups()[self._group_number]
_LOGGER.debug(
"Setting operation mode of %s to %s", self._group_number, hvac_mode
)
self.async_write_ha_state()
@property
def fan_mode(self):
"""Return fan mode of the AC this group belongs to."""
return AT_TO_HA_FAN_SPEED[self._airtouch.acs[self._unit.BelongsToAc].AcFanSpeed]
@property
def fan_modes(self):
"""Return the list of available fan modes."""
airtouch_fan_speeds = self._airtouch.GetSupportedFanSpeedsByGroup(
self._group_number
)
return [AT_TO_HA_FAN_SPEED[speed] for speed in airtouch_fan_speeds]
async def async_set_temperature(self, **kwargs):
"""Set new target temperatures."""
temp = kwargs.get(ATTR_TEMPERATURE)
_LOGGER.debug("Setting temp of %s to %s", self._group_number, str(temp))
self._unit = await self._airtouch.SetGroupToTemperature(
self._group_number, int(temp)
)
self.async_write_ha_state()
async def async_set_fan_mode(self, fan_mode):
"""Set new fan mode."""
if fan_mode not in self.fan_modes:
raise ValueError(f"Unsupported fan mode: {fan_mode}")
_LOGGER.debug("Setting fan mode of %s to %s", self._group_number, fan_mode)
self._unit = await self._airtouch.SetFanSpeedByGroup(
self._group_number, HA_FAN_SPEED_TO_AT[fan_mode]
)
self.async_write_ha_state()
async def async_turn_on(self):
"""Turn on."""
_LOGGER.debug("Turning %s on", self.unique_id)
await self._airtouch.TurnGroupOn(self._group_number)
# in case ac is not on. Airtouch turns itself off if no groups are turned on
# (even if groups turned back on)
await self._airtouch.TurnAcOn(
self._airtouch.GetGroupByGroupNumber(self._group_number).BelongsToAc
)
# this might cause the ac object to be wrong, so force the shared data
# store to update
await self.coordinator.async_request_refresh()
self.async_write_ha_state()
async def async_turn_off(self):
"""Turn off."""
_LOGGER.debug("Turning %s off", self.unique_id)
await self._airtouch.TurnGroupOff(self._group_number)
# this will cause the ac object to be wrong
# (ac turns off automatically if no groups are running)
# so force the shared data store to update
await self.coordinator.async_request_refresh()
self.async_write_ha_state()

View File

@ -0,0 +1,50 @@
"""Config flow for AirTouch4."""
from airtouch4pyapi import AirTouch, AirTouchStatus
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_HOST
from .const import DOMAIN
DATA_SCHEMA = vol.Schema({vol.Required(CONF_HOST): str})
class AirtouchConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle an Airtouch config flow."""
VERSION = 1
async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
if user_input is None:
return self.async_show_form(step_id="user", data_schema=DATA_SCHEMA)
errors = {}
host = user_input[CONF_HOST]
self._async_abort_entries_match({CONF_HOST: host})
airtouch = AirTouch(host)
await airtouch.UpdateInfo()
airtouch_status = airtouch.Status
airtouch_has_groups = bool(
airtouch.Status == AirTouchStatus.OK and airtouch.GetGroups()
)
if airtouch_status != AirTouchStatus.OK:
errors["base"] = "cannot_connect"
elif not airtouch_has_groups:
errors["base"] = "no_units"
if errors:
return self.async_show_form(
step_id="user", data_schema=DATA_SCHEMA, errors=errors
)
return self.async_create_entry(
title=user_input[CONF_HOST],
data={
CONF_HOST: user_input[CONF_HOST],
},
)

View File

@ -0,0 +1,3 @@
"""Constants for the AirTouch4 integration."""
DOMAIN = "airtouch4"

View File

@ -0,0 +1,13 @@
{
"domain": "airtouch4",
"name": "AirTouch 4",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/airtouch4",
"requirements": [
"airtouch4pyapi==1.0.5"
],
"codeowners": [
"@LonePurpleWolf"
],
"iot_class": "local_polling"
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"no_units": "Could not find any AirTouch 4 Groups."
},
"step": {
"user": {
"title": "Setup your AirTouch 4 connection details.",
"data": {
"host": "[%key:common::config_flow::data::host%]"
}
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "El dispositiu ja est\u00e0 configurat"
},
"error": {
"cannot_connect": "Ha fallat la connexi\u00f3",
"no_units": "No s'han trobat grups AirTouch 4."
},
"step": {
"user": {
"data": {
"host": "Amfitri\u00f3"
},
"title": "Configura els detalls de connexi\u00f3 d'AirTouch 4."
}
}
}
}

View File

@ -0,0 +1,17 @@
{
"config": {
"abort": {
"already_configured": "Za\u0159\u00edzen\u00ed je ji\u017e nastaveno"
},
"error": {
"cannot_connect": "Nepoda\u0159ilo se p\u0159ipojit"
},
"step": {
"user": {
"data": {
"host": "Hostitel"
}
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "Ger\u00e4t ist bereits konfiguriert"
},
"error": {
"cannot_connect": "Verbindung fehlgeschlagen",
"no_units": "Es konnten keine AirTouch 4-Gruppen gefunden werden."
},
"step": {
"user": {
"data": {
"host": "Host"
},
"title": "Richte deine AirTouch 4-Verbindungsdetails ein."
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "Device is already configured"
},
"error": {
"cannot_connect": "Failed to connect",
"no_units": "Could not find any AirTouch 4 Groups."
},
"step": {
"user": {
"data": {
"host": "Host"
},
"title": "Setup your AirTouch 4 connection details."
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "Seade on juba h\u00e4\u00e4lestatud"
},
"error": {
"cannot_connect": "\u00dchendamine nurjus",
"no_units": "Ei leidnud \u00fchtegi AirTouch 4 gruppi."
},
"step": {
"user": {
"data": {
"host": "Host"
},
"title": "AirTouch 4 \u00fchenduse \u00fcksikasjade seadistamine."
}
}
}
}

View File

@ -0,0 +1,17 @@
{
"config": {
"abort": {
"already_configured": "\u05ea\u05e6\u05d5\u05e8\u05ea \u05d4\u05d4\u05ea\u05e7\u05df \u05db\u05d1\u05e8 \u05e0\u05e7\u05d1\u05e2\u05d4"
},
"error": {
"cannot_connect": "\u05d4\u05d4\u05ea\u05d7\u05d1\u05e8\u05d5\u05ea \u05e0\u05db\u05e9\u05dc\u05d4"
},
"step": {
"user": {
"data": {
"host": "\u05de\u05d0\u05e8\u05d7"
}
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "Az eszk\u00f6z m\u00e1r konfigur\u00e1lva van"
},
"error": {
"cannot_connect": "Sikertelen kapcsol\u00f3d\u00e1s",
"no_units": "Nem tal\u00e1lhat\u00f3 AirTouch 4 csoport."
},
"step": {
"user": {
"data": {
"host": "Gazdag\u00e9p"
},
"title": "\u00c1ll\u00edtsa be az AirTouch 4 csatlakoz\u00e1si adatait."
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "Il dispositivo \u00e8 gi\u00e0 configurato"
},
"error": {
"cannot_connect": "Impossibile connettersi",
"no_units": "Impossibile trovare alcun gruppo AirTouch 4."
},
"step": {
"user": {
"data": {
"host": "Host"
},
"title": "Imposta i dettagli della connessione AirTouch 4."
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "Apparaat is al geconfigureerd"
},
"error": {
"cannot_connect": "Kan geen verbinding maken",
"no_units": "Kan geen AirTouch 4-groepen vinden."
},
"step": {
"user": {
"data": {
"host": "Host"
},
"title": "Stel uw AirTouch 4 verbindingsgegevens in."
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "Enheten er allerede konfigurert"
},
"error": {
"cannot_connect": "Tilkobling mislyktes",
"no_units": "Kan ikke finne noen AirTouch 4 -grupper."
},
"step": {
"user": {
"data": {
"host": "Vert"
},
"title": "Konfigurer AirTouch 4 -tilkoblingsdetaljer."
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane"
},
"error": {
"cannot_connect": "Nie mo\u017cna nawi\u0105za\u0107 po\u0142\u0105czenia",
"no_units": "Nie mo\u017cna znale\u017a\u0107 \u017cadnych grup AirTouch 4."
},
"step": {
"user": {
"data": {
"host": "Nazwa hosta lub adres IP"
},
"title": "Konfiguracja po\u0142\u0105czenia AirTouch 4."
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "\u042d\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e \u0443\u0436\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e \u0432 Home Assistant."
},
"error": {
"cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f.",
"no_units": "\u0413\u0440\u0443\u043f\u043f\u044b AirTouch 4 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b."
},
"step": {
"user": {
"data": {
"host": "\u0425\u043e\u0441\u0442"
},
"title": "AirTouch 4"
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"config": {
"abort": {
"already_configured": "\u88dd\u7f6e\u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210"
},
"error": {
"cannot_connect": "\u9023\u7dda\u5931\u6557",
"no_units": "\u627e\u4e0d\u5230\u4efb\u4f55 AirTouch 4 \u7fa4\u7d44\u3002"
},
"step": {
"user": {
"data": {
"host": "\u4e3b\u6a5f\u7aef"
},
"title": "\u8a2d\u5b9a AirTouch 4 \u9023\u7dda\u8cc7\u8a0a\u3002"
}
}
}
}

View File

@ -32,6 +32,7 @@ from homeassistant.helpers import (
config_validation as cv,
entity_registry,
)
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
@ -358,11 +359,14 @@ async def async_reload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
class AirVisualEntity(CoordinatorEntity):
"""Define a generic AirVisual entity."""
def __init__(self, coordinator: DataUpdateCoordinator) -> None:
def __init__(
self, coordinator: DataUpdateCoordinator, description: EntityDescription
) -> None:
"""Initialize."""
super().__init__(coordinator)
self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION}
self.entity_description = description
async def async_added_to_hass(self) -> None:
"""Register callbacks."""

View File

@ -1,7 +1,7 @@
"""Support for AirVisual air quality sensors."""
from __future__ import annotations
from homeassistant.components.sensor import SensorEntity
from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_LATITUDE,
@ -14,10 +14,15 @@ from homeassistant.const import (
CONF_LONGITUDE,
CONF_SHOW_ON_MAP,
CONF_STATE,
DEVICE_CLASS_AQI,
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_CO2,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PM1,
DEVICE_CLASS_PM10,
DEVICE_CLASS_PM25,
DEVICE_CLASS_TEMPERATURE,
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
PERCENTAGE,
TEMP_CELSIUS,
)
@ -59,60 +64,84 @@ SENSOR_KIND_SENSOR_LIFE = "sensor_life"
SENSOR_KIND_TEMPERATURE = "temperature"
SENSOR_KIND_VOC = "voc"
GEOGRAPHY_SENSORS = [
(SENSOR_KIND_LEVEL, "Air Pollution Level", "mdi:gauge", None),
(SENSOR_KIND_AQI, "Air Quality Index", "mdi:chart-line", "AQI"),
(SENSOR_KIND_POLLUTANT, "Main Pollutant", "mdi:chemical-weapon", None),
]
GEOGRAPHY_SENSOR_DESCRIPTIONS = (
SensorEntityDescription(
key=SENSOR_KIND_LEVEL,
name="Air Pollution Level",
device_class=DEVICE_CLASS_POLLUTANT_LEVEL,
icon="mdi:gauge",
),
SensorEntityDescription(
key=SENSOR_KIND_AQI,
name="Air Quality Index",
device_class=DEVICE_CLASS_AQI,
native_unit_of_measurement="AQI",
),
SensorEntityDescription(
key=SENSOR_KIND_POLLUTANT,
name="Main Pollutant",
device_class=DEVICE_CLASS_POLLUTANT_LABEL,
icon="mdi:chemical-weapon",
),
)
GEOGRAPHY_SENSOR_LOCALES = {"cn": "Chinese", "us": "U.S."}
NODE_PRO_SENSORS = [
(SENSOR_KIND_AQI, "Air Quality Index", None, "mdi:chart-line", "AQI"),
(SENSOR_KIND_BATTERY_LEVEL, "Battery", DEVICE_CLASS_BATTERY, None, PERCENTAGE),
(
SENSOR_KIND_CO2,
"C02",
DEVICE_CLASS_CO2,
None,
CONCENTRATION_PARTS_PER_MILLION,
NODE_PRO_SENSOR_DESCRIPTIONS = (
SensorEntityDescription(
key=SENSOR_KIND_AQI,
name="Air Quality Index",
device_class=DEVICE_CLASS_AQI,
native_unit_of_measurement="AQI",
),
(SENSOR_KIND_HUMIDITY, "Humidity", DEVICE_CLASS_HUMIDITY, None, PERCENTAGE),
(
SENSOR_KIND_PM_0_1,
"PM 0.1",
None,
"mdi:sprinkler",
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
SensorEntityDescription(
key=SENSOR_KIND_BATTERY_LEVEL,
name="Battery",
device_class=DEVICE_CLASS_BATTERY,
native_unit_of_measurement=PERCENTAGE,
),
(
SENSOR_KIND_PM_1_0,
"PM 1.0",
None,
"mdi:sprinkler",
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
SensorEntityDescription(
key=SENSOR_KIND_CO2,
name="C02",
device_class=DEVICE_CLASS_CO2,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
),
(
SENSOR_KIND_PM_2_5,
"PM 2.5",
None,
"mdi:sprinkler",
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
SensorEntityDescription(
key=SENSOR_KIND_HUMIDITY,
name="Humidity",
device_class=DEVICE_CLASS_HUMIDITY,
native_unit_of_measurement=PERCENTAGE,
),
(
SENSOR_KIND_TEMPERATURE,
"Temperature",
DEVICE_CLASS_TEMPERATURE,
None,
TEMP_CELSIUS,
SensorEntityDescription(
key=SENSOR_KIND_PM_0_1,
name="PM 0.1",
device_class=DEVICE_CLASS_PM1,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
),
(
SENSOR_KIND_VOC,
"VOC",
None,
"mdi:sprinkler",
CONCENTRATION_PARTS_PER_MILLION,
SensorEntityDescription(
key=SENSOR_KIND_PM_1_0,
name="PM 1.0",
device_class=DEVICE_CLASS_PM10,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
),
]
SensorEntityDescription(
key=SENSOR_KIND_PM_2_5,
name="PM 2.5",
device_class=DEVICE_CLASS_PM25,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
),
SensorEntityDescription(
key=SENSOR_KIND_TEMPERATURE,
name="Temperature",
device_class=DEVICE_CLASS_TEMPERATURE,
native_unit_of_measurement=TEMP_CELSIUS,
),
SensorEntityDescription(
key=SENSOR_KIND_VOC,
name="VOC",
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
),
)
STATE_POLLUTANT_LABEL_CO = "co"
STATE_POLLUTANT_LABEL_N2 = "n2"
@ -156,27 +185,19 @@ async def async_setup_entry(
coordinator = hass.data[DOMAIN][DATA_COORDINATOR][config_entry.entry_id]
sensors: list[AirVisualGeographySensor | AirVisualNodeProSensor]
if config_entry.data[CONF_INTEGRATION_TYPE] in [
if config_entry.data[CONF_INTEGRATION_TYPE] in (
INTEGRATION_TYPE_GEOGRAPHY_COORDS,
INTEGRATION_TYPE_GEOGRAPHY_NAME,
]:
):
sensors = [
AirVisualGeographySensor(
coordinator,
config_entry,
kind,
name,
icon,
unit,
locale,
)
AirVisualGeographySensor(coordinator, config_entry, description, locale)
for locale in GEOGRAPHY_SENSOR_LOCALES
for kind, name, icon, unit in GEOGRAPHY_SENSORS
for description in GEOGRAPHY_SENSOR_DESCRIPTIONS
]
else:
sensors = [
AirVisualNodeProSensor(coordinator, kind, name, device_class, icon, unit)
for kind, name, device_class, icon, unit in NODE_PRO_SENSORS
AirVisualNodeProSensor(coordinator, description)
for description in NODE_PRO_SENSOR_DESCRIPTIONS
]
async_add_entities(sensors, True)
@ -189,19 +210,12 @@ class AirVisualGeographySensor(AirVisualEntity, SensorEntity):
self,
coordinator: DataUpdateCoordinator,
config_entry: ConfigEntry,
kind: str,
name: str,
icon: str,
unit: str | None,
description: SensorEntityDescription,
locale: str,
) -> None:
"""Initialize."""
super().__init__(coordinator)
super().__init__(coordinator, description)
if kind == SENSOR_KIND_LEVEL:
self._attr_device_class = DEVICE_CLASS_POLLUTANT_LEVEL
elif kind == SENSOR_KIND_POLLUTANT:
self._attr_device_class = DEVICE_CLASS_POLLUTANT_LABEL
self._attr_extra_state_attributes.update(
{
ATTR_CITY: config_entry.data.get(CONF_CITY),
@ -209,12 +223,9 @@ class AirVisualGeographySensor(AirVisualEntity, SensorEntity):
ATTR_COUNTRY: config_entry.data.get(CONF_COUNTRY),
}
)
self._attr_icon = icon
self._attr_name = f"{GEOGRAPHY_SENSOR_LOCALES[locale]} {name}"
self._attr_unique_id = f"{config_entry.unique_id}_{locale}_{kind}"
self._attr_unit_of_measurement = unit
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._kind = kind
self._locale = locale
@property
@ -230,18 +241,18 @@ class AirVisualGeographySensor(AirVisualEntity, SensorEntity):
except KeyError:
return
if self._kind == SENSOR_KIND_LEVEL:
if self.entity_description.key == SENSOR_KIND_LEVEL:
aqi = data[f"aqi{self._locale}"]
[(self._attr_state, self._attr_icon)] = [
[(self._attr_native_value, self._attr_icon)] = [
(name, icon)
for (floor, ceiling), (name, icon) in POLLUTANT_LEVELS.items()
if floor <= aqi <= ceiling
]
elif self._kind == SENSOR_KIND_AQI:
self._attr_state = data[f"aqi{self._locale}"]
elif self._kind == SENSOR_KIND_POLLUTANT:
elif self.entity_description.key == SENSOR_KIND_AQI:
self._attr_native_value = data[f"aqi{self._locale}"]
elif self.entity_description.key == SENSOR_KIND_POLLUTANT:
symbol = data[f"main{self._locale}"]
self._attr_state = symbol
self._attr_native_value = symbol
self._attr_extra_state_attributes.update(
{
ATTR_POLLUTANT_SYMBOL: symbol,
@ -281,25 +292,15 @@ class AirVisualNodeProSensor(AirVisualEntity, SensorEntity):
"""Define an AirVisual sensor related to a Node/Pro unit."""
def __init__(
self,
coordinator: DataUpdateCoordinator,
kind: str,
name: str,
device_class: str | None,
icon: str | None,
unit: str,
self, coordinator: DataUpdateCoordinator, description: SensorEntityDescription
) -> None:
"""Initialize."""
super().__init__(coordinator)
super().__init__(coordinator, description)
self._attr_device_class = device_class
self._attr_icon = icon
self._attr_name = (
f"{coordinator.data['settings']['node_name']} Node/Pro: {name}"
f"{coordinator.data['settings']['node_name']} Node/Pro: {description.name}"
)
self._attr_unique_id = f"{coordinator.data['serial_number']}_{kind}"
self._attr_unit_of_measurement = unit
self._kind = kind
self._attr_unique_id = f"{coordinator.data['serial_number']}_{description.key}"
@property
def device_info(self) -> DeviceInfo:
@ -318,26 +319,32 @@ class AirVisualNodeProSensor(AirVisualEntity, SensorEntity):
@callback
def update_from_latest_data(self) -> None:
"""Update the entity from the latest data."""
if self._kind == SENSOR_KIND_AQI:
if self.entity_description.key == SENSOR_KIND_AQI:
if self.coordinator.data["settings"]["is_aqi_usa"]:
self._attr_state = self.coordinator.data["measurements"]["aqi_us"]
self._attr_native_value = self.coordinator.data["measurements"][
"aqi_us"
]
else:
self._attr_state = self.coordinator.data["measurements"]["aqi_cn"]
elif self._kind == SENSOR_KIND_BATTERY_LEVEL:
self._attr_state = self.coordinator.data["status"]["battery"]
elif self._kind == SENSOR_KIND_CO2:
self._attr_state = self.coordinator.data["measurements"].get("co2")
elif self._kind == SENSOR_KIND_HUMIDITY:
self._attr_state = self.coordinator.data["measurements"].get("humidity")
elif self._kind == SENSOR_KIND_PM_0_1:
self._attr_state = self.coordinator.data["measurements"].get("pm0_1")
elif self._kind == SENSOR_KIND_PM_1_0:
self._attr_state = self.coordinator.data["measurements"].get("pm1_0")
elif self._kind == SENSOR_KIND_PM_2_5:
self._attr_state = self.coordinator.data["measurements"].get("pm2_5")
elif self._kind == SENSOR_KIND_TEMPERATURE:
self._attr_state = self.coordinator.data["measurements"].get(
self._attr_native_value = self.coordinator.data["measurements"][
"aqi_cn"
]
elif self.entity_description.key == SENSOR_KIND_BATTERY_LEVEL:
self._attr_native_value = self.coordinator.data["status"]["battery"]
elif self.entity_description.key == SENSOR_KIND_CO2:
self._attr_native_value = self.coordinator.data["measurements"].get("co2")
elif self.entity_description.key == SENSOR_KIND_HUMIDITY:
self._attr_native_value = self.coordinator.data["measurements"].get(
"humidity"
)
elif self.entity_description.key == SENSOR_KIND_PM_0_1:
self._attr_native_value = self.coordinator.data["measurements"].get("pm0_1")
elif self.entity_description.key == SENSOR_KIND_PM_1_0:
self._attr_native_value = self.coordinator.data["measurements"].get("pm1_0")
elif self.entity_description.key == SENSOR_KIND_PM_2_5:
self._attr_native_value = self.coordinator.data["measurements"].get("pm2_5")
elif self.entity_description.key == SENSOR_KIND_TEMPERATURE:
self._attr_native_value = self.coordinator.data["measurements"].get(
"temperature_C"
)
elif self._kind == SENSOR_KIND_VOC:
self._attr_state = self.coordinator.data["measurements"].get("voc")
elif self.entity_description.key == SENSOR_KIND_VOC:
self._attr_native_value = self.coordinator.data["measurements"].get("voc")

View File

@ -13,6 +13,15 @@
"description": "Utilice la API en la nube de AirVisual para monitorear una latitud / longitud.",
"title": "Configurar una geograf\u00eda"
},
"geography_by_name": {
"data": {
"city": "Ciudad",
"country": "Pa\u00eds",
"state": "estado"
},
"description": "Utilice la API en la nube de AirVisual para monitorear una ciudad/estado/pa\u00eds.",
"title": "Configurar una geograf\u00eda"
},
"node_pro": {
"data": {
"ip_address": "Direcci\u00f3n IP/nombre de host de la unidad",
@ -21,6 +30,9 @@
"description": "Monitoree una unidad AirVisual personal. La contrase\u00f1a se puede recuperar de la interfaz de usuario de la unidad.",
"title": "Configurar un AirVisual Node/Pro"
},
"reauth_confirm": {
"title": "Vuelva a autenticar AirVisual"
},
"user": {
"description": "Monitoree la calidad del aire en una ubicaci\u00f3n geogr\u00e1fica.",
"title": "Configurar AirVisual"

View File

@ -34,13 +34,29 @@
"data": {
"ip_address": "Hoszt",
"password": "Jelsz\u00f3"
}
},
"description": "Szem\u00e9lyes AirVisual egys\u00e9g figyel\u00e9se. A jelsz\u00f3 lek\u00e9rhet\u0151 a k\u00e9sz\u00fcl\u00e9k felhaszn\u00e1l\u00f3i fel\u00fclet\u00e9r\u0151l.",
"title": "AirVisual Node/Pro konfigur\u00e1l\u00e1sa"
},
"reauth_confirm": {
"data": {
"api_key": "API kulcs"
},
"title": "Az AirVisual \u00fajb\u00f3li hiteles\u00edt\u00e9se"
},
"user": {
"description": "V\u00e1lassza ki, hogy milyen t\u00edpus\u00fa AirVisual adatokat szeretne figyelni.",
"title": "Az AirVisual konfigur\u00e1l\u00e1sa"
}
}
},
"options": {
"step": {
"init": {
"data": {
"show_on_map": "A megfigyelt f\u00f6ldrajz megjelen\u00edt\u00e9se a t\u00e9rk\u00e9pen"
},
"title": "Az AirVisual konfigur\u00e1l\u00e1sa"
}
}
}

View File

@ -0,0 +1,20 @@
{
"state": {
"airvisual__pollutant_label": {
"co": "Oxid uhelnat\u00fd",
"n2": "Oxid dusi\u010dit\u00fd",
"o3": "Oz\u00f3n",
"p1": "PM10",
"p2": "PM2,5",
"s2": "Oxid si\u0159i\u010dit\u00fd"
},
"airvisual__pollutant_level": {
"good": "Dobr\u00e9",
"hazardous": "Riskantn\u00ed",
"moderate": "M\u00edrn\u00e9",
"unhealthy": "Nezdrav\u00e9",
"unhealthy_sensitive": "Nezdrav\u00e9 pro citliv\u00e9 skupiny",
"very_unhealthy": "Velmi nezdrav\u00e9"
}
}
}

View File

@ -0,0 +1,20 @@
{
"state": {
"airvisual__pollutant_label": {
"co": "Mon\u00f3xido de carbono",
"n2": "Dioxido de nitrogeno",
"o3": "Ozono",
"p1": "PM10",
"p2": "PM2.5",
"s2": "Di\u00f3xido de azufre"
},
"airvisual__pollutant_level": {
"good": "Bueno",
"hazardous": "Peligroso",
"moderate": "Moderado",
"unhealthy": "Insalubre",
"unhealthy_sensitive": "Insalubre para grupos sensibles",
"very_unhealthy": "Muy insalubre"
}
}
}

View File

@ -0,0 +1,20 @@
{
"state": {
"airvisual__pollutant_label": {
"co": "Mon\u00f3xido de carbono",
"n2": "Di\u00f3xido de nitr\u00f3geno",
"o3": "Ozono",
"p1": "PM10",
"p2": "PM2.5",
"s2": "Di\u00f3xido de azufre"
},
"airvisual__pollutant_level": {
"good": "Bien",
"hazardous": "Peligroso",
"moderate": "Moderado",
"unhealthy": "Insalubre",
"unhealthy_sensitive": "Incorrecto para grupos sensibles",
"very_unhealthy": "Muy poco saludable"
}
}
}

View File

@ -0,0 +1,20 @@
{
"state": {
"airvisual__pollutant_label": {
"co": "Sz\u00e9n-monoxid",
"n2": "Nitrog\u00e9n-dioxid",
"o3": "\u00d3zon",
"p1": "PM10",
"p2": "PM2.5",
"s2": "K\u00e9n-dioxid"
},
"airvisual__pollutant_level": {
"good": "J\u00f3",
"hazardous": "Vesz\u00e9lyes",
"moderate": "M\u00e9rs\u00e9kelt",
"unhealthy": "Eg\u00e9szs\u00e9gtelen",
"unhealthy_sensitive": "Eg\u00e9szs\u00e9gtelen az \u00e9rz\u00e9keny csoportok sz\u00e1m\u00e1ra",
"very_unhealthy": "Nagyon eg\u00e9szs\u00e9gtelen"
}
}
}

View File

@ -1,8 +1,20 @@
{
"state": {
"airvisual__pollutant_label": {
"co": "Karbonmonoksid",
"n2": "Nitrogendioksid",
"o3": "Ozon",
"p1": "PM10",
"p2": "PM2.5"
"p2": "PM2.5",
"s2": "Svoveldioksid"
},
"airvisual__pollutant_level": {
"good": "Bra",
"hazardous": "Farlig",
"moderate": "Moderat",
"unhealthy": "Usunt",
"unhealthy_sensitive": "Usunt for sensitive grupper",
"very_unhealthy": "Veldig usunt"
}
}
}

View File

@ -1,7 +1,7 @@
"""Provides device automations for Alarm control panel."""
from __future__ import annotations
from typing import Final
from typing import Any, Final
import voluptuous as vol
@ -55,7 +55,7 @@ TRIGGER_SCHEMA: Final = DEVICE_TRIGGER_BASE_SCHEMA.extend(
async def async_get_triggers(
hass: HomeAssistant, device_id: str
) -> list[dict[str, str]]:
) -> list[dict[str, Any]]:
"""List device triggers for Alarm control panel devices."""
registry = await entity_registry.async_get_registry(hass)
triggers: list[dict[str, str]] = []

View File

@ -4,6 +4,7 @@
"arm_away": "Aktivovat {entity_name} v re\u017eimu nep\u0159\u00edtomnost",
"arm_home": "Aktivovat {entity_name} v re\u017eimu domov",
"arm_night": "Aktivovat {entity_name} v no\u010dn\u00edm re\u017eimu",
"arm_vacation": "Aktivovat {entity_name} v re\u017eimu dovolen\u00e1",
"disarm": "Odbezpe\u010dit {entity_name}",
"trigger": "Spustit {entity_name}"
},
@ -11,6 +12,7 @@
"is_armed_away": "{entity_name} je v re\u017eimu nep\u0159\u00edtomnost",
"is_armed_home": "{entity_name} je v re\u017eimu domov",
"is_armed_night": "{entity_name} je v no\u010dn\u00edm re\u017eimu",
"is_armed_vacation": "{entity_name} je v re\u017eimu dovolen\u00e1",
"is_disarmed": "{entity_name} nen\u00ed zabezpe\u010den",
"is_triggered": "{entity_name} je spu\u0161t\u011bn"
},
@ -18,6 +20,7 @@
"armed_away": "{entity_name} v re\u017eimu nep\u0159\u00edtomnost",
"armed_home": "{entity_name} v re\u017eimu domov",
"armed_night": "{entity_name} v no\u010dn\u00edm re\u017eimu",
"armed_vacation": "{entity_name} v re\u017eimu dovolen\u00e1",
"disarmed": "{entity_name} nezabezpe\u010den",
"triggered": "{entity_name} spu\u0161t\u011bn"
}
@ -29,6 +32,7 @@
"armed_custom_bypass": "Zabezpe\u010deno u\u017eivatelsk\u00fdm obejit\u00edm",
"armed_home": "Re\u017eim domov",
"armed_night": "No\u010dn\u00ed re\u017eim",
"armed_vacation": "V re\u017eimu dovolen\u00e1",
"arming": "Zabezpe\u010dov\u00e1n\u00ed",
"disarmed": "Nezabezpe\u010deno",
"disarming": "Odbezpe\u010dov\u00e1n\u00ed",

View File

@ -12,6 +12,7 @@
"is_armed_away": "{entity_name} est arm\u00e9",
"is_armed_home": "{entity_name} est arm\u00e9 \u00e0 la maison",
"is_armed_night": "{entity_name} est arm\u00e9 la nuit",
"is_armed_vacation": "{entity_name} est arm\u00e9 en mode vacances",
"is_disarmed": "{entity_name} est d\u00e9sarm\u00e9",
"is_triggered": "{entity_name} est d\u00e9clench\u00e9"
},

View File

@ -9,7 +9,12 @@
"trigger": "{entity_name} riaszt\u00e1si esem\u00e9ny ind\u00edt\u00e1sa"
},
"condition_type": {
"is_armed_vacation": "{entity_name} nyaral\u00e1s \u00e9les\u00edtve"
"is_armed_away": "{entity_name} \u00e9les\u00edtve van",
"is_armed_home": "{entity_name} \u00e9les\u00edtett otthoni m\u00f3dban",
"is_armed_night": "{entity_name} \u00e9les\u00edtett \u00e9jszaka m\u00f3dban",
"is_armed_vacation": "{entity_name} nyaral\u00e1s \u00e9les\u00edtve",
"is_disarmed": "{entity_name} hat\u00e1stalan\u00edtva",
"is_triggered": "{entity_name} aktiv\u00e1lva van"
},
"trigger_type": {
"armed_away": "{entity_name} t\u00e1voz\u00f3 m\u00f3dban lett \u00e9les\u00edtve",

View File

@ -1,43 +1,43 @@
{
"device_automation": {
"action_type": {
"arm_away": "Inschakelen {entity_name} afwezig",
"arm_home": "Inschakelen {entity_name} thuis",
"arm_night": "Inschakelen {entity_name} nacht",
"arm_away": "Schakel {entity_name} in voor vertrek",
"arm_home": "Schakel {entity_name} in voor thuis",
"arm_night": "Schakel {entity_name} in voor 's nachts",
"arm_vacation": "Schakel {entity_name} in op vakantie",
"disarm": "Uitschakelen {entity_name}",
"trigger": "Trigger {entity_name}"
"disarm": "Schakel {entity_name} uit",
"trigger": "Laat {entity_name} afgaan"
},
"condition_type": {
"is_armed_away": "{entity_name} afwezig ingeschakeld",
"is_armed_home": "{entity_name} thuis ingeschakeld",
"is_armed_night": "{entity_name} nachtstand ingeschakeld",
"is_armed_away": "{entity_name} ingeschakeld voor vertrek",
"is_armed_home": "{entity_name} ingeschakeld voor thuis",
"is_armed_night": "{entity_name} is ingeschakeld voor 's nachts",
"is_armed_vacation": "{entity_name} is in vakantie geschakeld",
"is_disarmed": "{entity_name} is uitgeschakeld",
"is_triggered": "{entity_name} wordt geactiveerd"
"is_triggered": "{entity_name} gaat af"
},
"trigger_type": {
"armed_away": "{entity_name} afwezig ingeschakeld",
"armed_home": "{entity_name} thuis ingeschakeld",
"armed_night": "{entity_name} nachtstand ingeschakeld",
"armed_away": "{entity_name} ingeschakeld voor vertrek",
"armed_home": "{entity_name} ingeschakeld voor thuis",
"armed_night": "{entity_name} ingeschakeld voor 's nachts",
"armed_vacation": "{entity_name} schakelde vakantie in",
"disarmed": "{entity_name} uitgeschakeld",
"triggered": "{entity_name} geactiveerd"
"triggered": "{entity_name} afgegaan"
}
},
"state": {
"_": {
"armed": "Ingeschakeld",
"armed_away": "Ingeschakeld afwezig",
"armed_custom_bypass": "Ingeschakeld met overbrugging(en)",
"armed_home": "Ingeschakeld thuis",
"armed_night": "Ingeschakeld nacht",
"armed_away": "Ingeschakeld voor vertrek",
"armed_custom_bypass": "Ingeschakeld met overbrugging",
"armed_home": "Ingeschakeld voor thuis",
"armed_night": "Ingeschakeld voor 's nachts",
"armed_vacation": "Vakantie ingeschakeld",
"arming": "Schakelt in",
"disarmed": "Uitgeschakeld",
"disarming": "Schakelt uit",
"pending": "In wacht",
"triggered": "Geactiveerd"
"triggered": "Gaat af"
}
},
"title": "Alarm bedieningspaneel"

View File

@ -4,6 +4,7 @@
"arm_away": "Aktiver {entity_name} borte",
"arm_home": "Aktiver {entity_name} hjemme",
"arm_night": "Aktiver {entity_name} natt",
"arm_vacation": "{entity_name} ferie",
"disarm": "Deaktiver {entity_name}",
"trigger": "Utl\u00f8ser {entity_name}"
},
@ -11,6 +12,7 @@
"is_armed_away": "{entity_name} er aktivert borte",
"is_armed_home": "{entity_name} er aktivert hjemme",
"is_armed_night": "{entity_name} er aktivert natt",
"is_armed_vacation": "{entity_name} er armert ferie",
"is_disarmed": "{entity_name} er deaktivert",
"is_triggered": "{entity_name} er utl\u00f8st"
},
@ -18,6 +20,7 @@
"armed_away": "{entity_name} aktivert borte",
"armed_home": "{entity_name} aktivert hjemme",
"armed_night": "{entity_name} aktivert natt",
"armed_vacation": "{entity_name} armert ferie",
"disarmed": "{entity_name} deaktivert",
"triggered": "{entity_name} utl\u00f8st"
}
@ -29,6 +32,7 @@
"armed_custom_bypass": "Armert tilpasset unntak",
"armed_home": "Armert hjemme",
"armed_night": "Armert natt",
"armed_vacation": "Armert ferie",
"arming": "Armerer",
"disarmed": "Avsl\u00e5tt",
"disarming": "Disarmer",

View File

@ -32,6 +32,6 @@ class AlarmDecoderSensor(SensorEntity):
)
def _message_callback(self, message):
if self._attr_state != message.text:
self._attr_state = message.text
if self._attr_native_value != message.text:
self._attr_native_value = message.text
self.schedule_update_ha_state()

View File

@ -20,6 +20,10 @@
}
},
"options": {
"error": {
"int": "El campo siguiente debe ser un n\u00famero entero.",
"loop_range": "El bucle de RF debe ser un n\u00famero entero entre 1 y 4."
},
"step": {
"arm_settings": {
"data": {
@ -30,6 +34,17 @@
"data": {
"edit_select": "Editar"
}
},
"zone_details": {
"data": {
"zone_name": "Nombre de zona",
"zone_rfid": "Serie RF"
}
},
"zone_select": {
"data": {
"zone_number": "N\u00famero de zona"
}
}
}
}

View File

@ -31,6 +31,7 @@
"error": {
"int": "Az al\u00e1bbi mez\u0151nek eg\u00e9sz sz\u00e1mnak kell lennie.",
"loop_range": "Az RF hurok eg\u00e9sz sz\u00e1m\u00e1nak 1 \u00e9s 4 k\u00f6z\u00f6tt kell lennie.",
"loop_rfid": "Az RF hurok nem haszn\u00e1lhat\u00f3 RF sorozat n\u00e9lk\u00fcl.",
"relay_inclusive": "A rel\u00e9c\u00edm \u00e9s a rel\u00e9csatorna egym\u00e1st\u00f3l f\u00fcgg, \u00e9s egy\u00fctt kell felt\u00fcntetni."
},
"step": {
@ -55,6 +56,7 @@
"zone_name": "Z\u00f3na neve",
"zone_relayaddr": "Rel\u00e9 c\u00edm",
"zone_relaychan": "Rel\u00e9 csatorna",
"zone_rfid": "RF soros",
"zone_type": "Z\u00f3na t\u00edpusa"
},
"description": "Adja meg a {zone_number} z\u00f3na adatait. {zone_number} z\u00f3na t\u00f6rl\u00e9s\u00e9hez hagyja \u00fcresen a Z\u00f3na neve elemet.",

View File

@ -32,7 +32,7 @@
"int": "Het onderstaande veld moet een geheel getal zijn.",
"loop_range": "RF Lus moet een geheel getal zijn tussen 1 en 4.",
"loop_rfid": "RF Lus kan niet worden gebruikt zonder RF Serieel.",
"relay_inclusive": "Het relais-adres en het relais-kanaal zijn codeafhankelijk en moeten samen worden opgenomen."
"relay_inclusive": "Het relaisadres en het relaiskanaal zijn onderling afhankelijk en moeten samen worden opgenomen."
},
"step": {
"arm_settings": {
@ -53,18 +53,18 @@
"zone_details": {
"data": {
"zone_loop": "RF Lus",
"zone_name": "Zone naam",
"zone_relayaddr": "Relais Adres",
"zone_relaychan": "Relais Kanaal",
"zone_name": "Zonenaam",
"zone_relayaddr": "Relaisadres",
"zone_relaychan": "Relaiskanaal",
"zone_rfid": "RF Serieel",
"zone_type": "Zone Type"
"zone_type": "Zonetype"
},
"description": "Voer details in voor zone {zone_number}. Om zone {zone_number} te verwijderen, laat u Zone Name leeg.",
"description": "Voer details in voor zone {zone_number}. Om zone {zone_number} te verwijderen, laat u Zonenaam leeg.",
"title": "Configureer AlarmDecoder"
},
"zone_select": {
"data": {
"zone_number": "Zone nummer"
"zone_number": "Zonenummer"
},
"description": "Voer het zone nummer in dat u wilt toevoegen, bewerken of verwijderen.",
"title": "Configureer AlarmDecoder"

View File

@ -99,7 +99,7 @@ class AlexaCapability:
return False
@staticmethod
def properties_non_controllable() -> bool:
def properties_non_controllable() -> bool | None:
"""Return True if non controllable."""
return None

View File

@ -1,4 +1,6 @@
"""Alexa related errors."""
from __future__ import annotations
from homeassistant.exceptions import HomeAssistantError
from .const import API_TEMP_UNITS
@ -22,8 +24,8 @@ class AlexaError(Exception):
A handler can raise subclasses of this to return an error to the request.
"""
namespace = None
error_type = None
namespace: str | None = None
error_type: str | None = None
def __init__(self, error_message, payload=None):
"""Initialize an alexa error."""

View File

@ -5,6 +5,7 @@ import asyncio
from datetime import timedelta
import logging
import time
from typing import Optional, cast
from aiohttp import ClientError, ClientSession
import async_timeout
@ -166,7 +167,7 @@ async def _configure_almond_for_ha(
_LOGGER.debug("Configuring Almond to connect to Home Assistant at %s", hass_url)
store = storage.Store(hass, STORAGE_VERSION, STORAGE_KEY)
data = await store.async_load()
data = cast(Optional[dict], await store.async_load())
if data is None:
data = {}
@ -204,7 +205,7 @@ async def _configure_almond_for_ha(
)
except (asyncio.TimeoutError, ClientError) as err:
if isinstance(err, asyncio.TimeoutError):
msg = "Request timeout"
msg: str | ClientError = "Request timeout"
else:
msg = err
_LOGGER.warning("Unable to configure Almond: %s", msg)

View File

@ -1,6 +1,9 @@
"""Config flow to connect with Home Assistant."""
from __future__ import annotations
import asyncio
import logging
from typing import Any
from aiohttp import ClientError
import async_timeout
@ -9,6 +12,7 @@ import voluptuous as vol
from yarl import URL
from homeassistant import config_entries, core, data_entry_flow
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers import aiohttp_client, config_entry_oauth2_flow
from .const import DOMAIN as ALMOND_DOMAIN, TYPE_LOCAL, TYPE_OAUTH2
@ -64,7 +68,7 @@ class AlmondFlowHandler(config_entry_oauth2_flow.AbstractOAuth2FlowHandler):
return result
async def async_oauth_create_entry(self, data: dict) -> dict:
async def async_oauth_create_entry(self, data: dict) -> FlowResult:
"""Create an entry for the flow.
Ok to override if you want to fetch extra info or even add another step.
@ -73,7 +77,7 @@ class AlmondFlowHandler(config_entry_oauth2_flow.AbstractOAuth2FlowHandler):
data["host"] = self.host
return self.async_create_entry(title=self.flow_impl.name, data=data)
async def async_step_import(self, user_input: dict = None) -> dict:
async def async_step_import(self, user_input: dict[str, Any]) -> FlowResult:
"""Import data."""
# Only allow 1 instance.
if self._async_current_entries():

View File

@ -112,7 +112,7 @@ class AlphaVantageSensor(SensorEntity):
self._symbol = symbol[CONF_SYMBOL]
self._attr_name = symbol.get(CONF_NAME, self._symbol)
self._timeseries = timeseries
self._attr_unit_of_measurement = symbol.get(CONF_CURRENCY, self._symbol)
self._attr_native_unit_of_measurement = symbol.get(CONF_CURRENCY, self._symbol)
self._attr_icon = ICONS.get(symbol.get(CONF_CURRENCY, "USD"))
def update(self):
@ -120,7 +120,7 @@ class AlphaVantageSensor(SensorEntity):
_LOGGER.debug("Requesting new data for symbol %s", self._symbol)
all_values, _ = self._timeseries.get_intraday(self._symbol)
values = next(iter(all_values.values()))
self._attr_state = values["1. open"]
self._attr_native_value = values["1. open"]
self._attr_extra_state_attributes = (
{
ATTR_ATTRIBUTION: ATTRIBUTION,
@ -148,7 +148,7 @@ class AlphaVantageForeignExchange(SensorEntity):
else f"{self._to_currency}/{self._from_currency}"
)
self._attr_icon = ICONS.get(self._from_currency, "USD")
self._attr_unit_of_measurement = self._to_currency
self._attr_native_unit_of_measurement = self._to_currency
def update(self):
"""Get the latest data and updates the states."""
@ -160,7 +160,7 @@ class AlphaVantageForeignExchange(SensorEntity):
values, _ = self._foreign_exchange.get_currency_exchange_rate(
from_currency=self._from_currency, to_currency=self._to_currency
)
self._attr_state = round(float(values["5. Exchange Rate"]), 4)
self._attr_native_value = round(float(values["5. Exchange Rate"]), 4)
self._attr_extra_state_attributes = (
{
ATTR_ATTRIBUTION: ATTRIBUTION,

View File

@ -39,38 +39,38 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
SensorEntityDescription(
key="particulate_matter_2_5",
name="Particulate Matter < 2.5 μm",
unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=STATE_CLASS_MEASUREMENT,
),
SensorEntityDescription(
key="particulate_matter_10",
name="Particulate Matter < 10 μm",
unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
state_class=STATE_CLASS_MEASUREMENT,
),
SensorEntityDescription(
key="sulphur_dioxide",
name="Sulphur Dioxide (SO2)",
unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
state_class=STATE_CLASS_MEASUREMENT,
),
SensorEntityDescription(
key="nitrogen_dioxide",
name="Nitrogen Dioxide (NO2)",
unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
state_class=STATE_CLASS_MEASUREMENT,
),
SensorEntityDescription(
key="ozone",
name="Ozone",
unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
state_class=STATE_CLASS_MEASUREMENT,
),
SensorEntityDescription(
key="carbon_monoxide",
name="Carbon Monoxide (CO)",
device_class=DEVICE_CLASS_CO,
unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=STATE_CLASS_MEASUREMENT,
),
SensorEntityDescription(
@ -85,21 +85,21 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Grass Pollen",
icon="mdi:grass",
state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
),
SensorEntityDescription(
key="tree",
name="Tree Pollen",
icon="mdi:tree",
state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
),
SensorEntityDescription(
key="weed",
name="Weed Pollen",
icon="mdi:sprout",
state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
),
SensorEntityDescription(
key="grass_risk",
@ -124,7 +124,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Poaceae Grass Pollen",
icon="mdi:grass",
state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
@ -132,7 +132,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Alder Tree Pollen",
icon="mdi:tree",
state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
@ -140,7 +140,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Birch Tree Pollen",
icon="mdi:tree",
state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
@ -148,7 +148,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Cypress Tree Pollen",
icon="mdi:tree",
state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
@ -156,7 +156,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Elm Tree Pollen",
icon="mdi:tree",
state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
@ -164,7 +164,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Hazel Tree Pollen",
icon="mdi:tree",
state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
@ -172,7 +172,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Oak Tree Pollen",
icon="mdi:tree",
state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
@ -180,7 +180,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Pine Tree Pollen",
icon="mdi:tree",
state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
@ -188,7 +188,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Plane Tree Pollen",
icon="mdi:tree",
state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
@ -196,7 +196,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Poplar Tree Pollen",
icon="mdi:tree",
state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
@ -204,7 +204,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Chenopod Weed Pollen",
icon="mdi:sprout",
state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
@ -212,7 +212,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Mugwort Weed Pollen",
icon="mdi:sprout",
state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
@ -220,7 +220,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Nettle Weed Pollen",
icon="mdi:sprout",
state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
),
SensorEntityDescription(
@ -228,7 +228,7 @@ SENSORS: dict[str, list[SensorEntityDescription]] = {
name="Ragweed Weed Pollen",
icon="mdi:sprout",
state_class=STATE_CLASS_MEASUREMENT,
unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_CUBIC_METER,
entity_registry_enabled_default=False,
),
],

View File

@ -66,7 +66,7 @@ class AmbeeSensorEntity(CoordinatorEntity, SensorEntity):
}
@property
def state(self) -> StateType:
def native_value(self) -> StateType:
"""Return the state of the sensor."""
value = getattr(self.coordinator.data, self.entity_description.key)
if isinstance(value, str):

View File

@ -21,7 +21,7 @@
"longitude": "Longitud",
"name": "Nom"
},
"description": "Configura Ambee per a integrar-lo amb Home Assistant."
"description": "Configura la integraci\u00f3 d'Ambee amb Home Assistant."
}
}
}

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