Merge remote-tracking branch 'upstream/dev' into conf_state_class

This commit is contained in:
farmio
2021-08-19 12:13:53 +02:00
1412 changed files with 25132 additions and 11045 deletions

View File

@@ -36,6 +36,9 @@ omit =
homeassistant/components/agent_dvr/helpers.py homeassistant/components/agent_dvr/helpers.py
homeassistant/components/airnow/__init__.py homeassistant/components/airnow/__init__.py
homeassistant/components/airnow/sensor.py homeassistant/components/airnow/sensor.py
homeassistant/components/airtouch4/__init__.py
homeassistant/components/airtouch4/climate.py
homeassistant/components/airtouch4/const.py
homeassistant/components/airvisual/__init__.py homeassistant/components/airvisual/__init__.py
homeassistant/components/airvisual/sensor.py homeassistant/components/airvisual/sensor.py
homeassistant/components/aladdin_connect/* homeassistant/components/aladdin_connect/*
@@ -375,6 +378,7 @@ omit =
homeassistant/components/google/* homeassistant/components/google/*
homeassistant/components/google_cloud/tts.py homeassistant/components/google_cloud/tts.py
homeassistant/components/google_maps/device_tracker.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/__init__.py
homeassistant/components/google_travel_time/helpers.py homeassistant/components/google_travel_time/helpers.py
homeassistant/components/google_travel_time/sensor.py homeassistant/components/google_travel_time/sensor.py
@@ -666,17 +670,19 @@ omit =
homeassistant/components/mysensors/helpers.py homeassistant/components/mysensors/helpers.py
homeassistant/components/mysensors/light.py homeassistant/components/mysensors/light.py
homeassistant/components/mysensors/notify.py homeassistant/components/mysensors/notify.py
homeassistant/components/mysensors/sensor.py
homeassistant/components/mysensors/switch.py homeassistant/components/mysensors/switch.py
homeassistant/components/mystrom/binary_sensor.py homeassistant/components/mystrom/binary_sensor.py
homeassistant/components/mystrom/light.py homeassistant/components/mystrom/light.py
homeassistant/components/mystrom/switch.py homeassistant/components/mystrom/switch.py
homeassistant/components/myq/__init__.py homeassistant/components/myq/__init__.py
homeassistant/components/myq/cover.py
homeassistant/components/myq/light.py
homeassistant/components/nad/media_player.py homeassistant/components/nad/media_player.py
homeassistant/components/nanoleaf/light.py homeassistant/components/nanoleaf/light.py
homeassistant/components/neato/__init__.py homeassistant/components/neato/__init__.py
homeassistant/components/neato/api.py homeassistant/components/neato/api.py
homeassistant/components/neato/camera.py homeassistant/components/neato/camera.py
homeassistant/components/neato/hub.py
homeassistant/components/neato/sensor.py homeassistant/components/neato/sensor.py
homeassistant/components/neato/switch.py homeassistant/components/neato/switch.py
homeassistant/components/neato/vacuum.py homeassistant/components/neato/vacuum.py
@@ -695,7 +701,8 @@ omit =
homeassistant/components/niko_home_control/light.py homeassistant/components/niko_home_control/light.py
homeassistant/components/nilu/air_quality.py homeassistant/components/nilu/air_quality.py
homeassistant/components/nissan_leaf/* 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/nmbs/sensor.py
homeassistant/components/notion/__init__.py homeassistant/components/notion/__init__.py
homeassistant/components/notion/binary_sensor.py homeassistant/components/notion/binary_sensor.py
@@ -1114,10 +1121,6 @@ omit =
homeassistant/components/upcloud/switch.py homeassistant/components/upcloud/switch.py
homeassistant/components/upnp/* homeassistant/components/upnp/*
homeassistant/components/upc_connect/* homeassistant/components/upc_connect/*
homeassistant/components/uptimerobot/__init__.py
homeassistant/components/uptimerobot/binary_sensor.py
homeassistant/components/uptimerobot/const.py
homeassistant/components/uptimerobot/entity.py
homeassistant/components/uscis/sensor.py homeassistant/components/uscis/sensor.py
homeassistant/components/vallox/* homeassistant/components/vallox/*
homeassistant/components/vasttrafik/sensor.py homeassistant/components/vasttrafik/sensor.py
@@ -1201,6 +1204,7 @@ omit =
homeassistant/components/xiaomi_miio/__init__.py homeassistant/components/xiaomi_miio/__init__.py
homeassistant/components/xiaomi_miio/air_quality.py homeassistant/components/xiaomi_miio/air_quality.py
homeassistant/components/xiaomi_miio/alarm_control_panel.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.py
homeassistant/components/xiaomi_miio/device_tracker.py homeassistant/components/xiaomi_miio/device_tracker.py
homeassistant/components/xiaomi_miio/fan.py homeassistant/components/xiaomi_miio/fan.py

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`. Updated and included derived files by running: `python3 -m script.hassfest`.
- [ ] New or updated dependencies have been added to `requirements_all.txt`. - [ ] New or updated dependencies have been added to `requirements_all.txt`.
Updated by running `python3 -m script.gen_requirements_all`. 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`. - [ ] Untested files have been added to `.coveragerc`.
The integration reached or maintains the following [Integration Quality Scale][quality-scale]: The integration reached or maintains the following [Integration Quality Scale][quality-scale]:

View File

@@ -248,11 +248,12 @@ jobs:
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Install VCN tools
uses: home-assistant/actions/helpers/vcn@master
- name: Build Meta Image - name: Build Meta Image
shell: bash shell: bash
run: | run: |
bash <(curl https://getvcn.codenotary.com -L)
export DOCKER_CLI_EXPERIMENTAL=enabled export DOCKER_CLI_EXPERIMENTAL=enabled
function create_manifest() { function create_manifest() {

View File

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

View File

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

View File

@@ -63,6 +63,7 @@ homeassistant.components.mailbox.*
homeassistant.components.media_player.* homeassistant.components.media_player.*
homeassistant.components.mysensors.* homeassistant.components.mysensors.*
homeassistant.components.nam.* homeassistant.components.nam.*
homeassistant.components.neato.*
homeassistant.components.nest.* homeassistant.components.nest.*
homeassistant.components.netatmo.* homeassistant.components.netatmo.*
homeassistant.components.network.* homeassistant.components.network.*

View File

@@ -29,6 +29,7 @@ homeassistant/components/aemet/* @noltari
homeassistant/components/agent_dvr/* @ispysoftware homeassistant/components/agent_dvr/* @ispysoftware
homeassistant/components/airly/* @bieniu homeassistant/components/airly/* @bieniu
homeassistant/components/airnow/* @asymworks homeassistant/components/airnow/* @asymworks
homeassistant/components/airtouch4/* @LonePurpleWolf
homeassistant/components/airvisual/* @bachya homeassistant/components/airvisual/* @bachya
homeassistant/components/alarmdecoder/* @ajschmidt8 homeassistant/components/alarmdecoder/* @ajschmidt8
homeassistant/components/alexa/* @home-assistant/cloud @ochlocracy homeassistant/components/alexa/* @home-assistant/cloud @ochlocracy
@@ -187,6 +188,7 @@ homeassistant/components/geo_rss_events/* @exxamalte
homeassistant/components/geonetnz_quakes/* @exxamalte homeassistant/components/geonetnz_quakes/* @exxamalte
homeassistant/components/geonetnz_volcano/* @exxamalte homeassistant/components/geonetnz_volcano/* @exxamalte
homeassistant/components/gios/* @bieniu homeassistant/components/gios/* @bieniu
homeassistant/components/github/* @timmo001
homeassistant/components/gitter/* @fabaff homeassistant/components/gitter/* @fabaff
homeassistant/components/glances/* @fabaff @engrbm87 homeassistant/components/glances/* @fabaff @engrbm87
homeassistant/components/goalzero/* @tkdrob homeassistant/components/goalzero/* @tkdrob
@@ -320,7 +322,7 @@ homeassistant/components/msteams/* @peroyvind
homeassistant/components/mullvad/* @meichthys homeassistant/components/mullvad/* @meichthys
homeassistant/components/mutesync/* @currentoor homeassistant/components/mutesync/* @currentoor
homeassistant/components/my/* @home-assistant/core homeassistant/components/my/* @home-assistant/core
homeassistant/components/myq/* @bdraco homeassistant/components/myq/* @bdraco @ehendrix23
homeassistant/components/mysensors/* @MartinHjelmare @functionpointer homeassistant/components/mysensors/* @MartinHjelmare @functionpointer
homeassistant/components/mystrom/* @fabaff homeassistant/components/mystrom/* @fabaff
homeassistant/components/nam/* @bieniu homeassistant/components/nam/* @bieniu
@@ -338,6 +340,7 @@ homeassistant/components/nfandroidtv/* @tkdrob
homeassistant/components/nightscout/* @marciogranzotto homeassistant/components/nightscout/* @marciogranzotto
homeassistant/components/nilu/* @hfurubotten homeassistant/components/nilu/* @hfurubotten
homeassistant/components/nissan_leaf/* @filcole homeassistant/components/nissan_leaf/* @filcole
homeassistant/components/nmap_tracker/* @bdraco
homeassistant/components/nmbs/* @thibmaek homeassistant/components/nmbs/* @thibmaek
homeassistant/components/no_ip/* @fabaff homeassistant/components/no_ip/* @fabaff
homeassistant/components/noaa_tides/* @jdelaney72 homeassistant/components/noaa_tides/* @jdelaney72
@@ -503,7 +506,7 @@ homeassistant/components/synology_dsm/* @hacf-fr @Quentame @mib1185
homeassistant/components/synology_srm/* @aerialls homeassistant/components/synology_srm/* @aerialls
homeassistant/components/syslog/* @fabaff homeassistant/components/syslog/* @fabaff
homeassistant/components/system_bridge/* @timmo001 homeassistant/components/system_bridge/* @timmo001
homeassistant/components/tado/* @michaelarnauts @bdraco @noltari homeassistant/components/tado/* @michaelarnauts @noltari
homeassistant/components/tag/* @balloob @dmulcahey homeassistant/components/tag/* @balloob @dmulcahey
homeassistant/components/tahoma/* @philklei homeassistant/components/tahoma/* @philklei
homeassistant/components/tankerkoenig/* @guillempages homeassistant/components/tankerkoenig/* @guillempages
@@ -527,6 +530,7 @@ homeassistant/components/tplink/* @rytilahti @thegardenmonkey
homeassistant/components/traccar/* @ludeeus homeassistant/components/traccar/* @ludeeus
homeassistant/components/trace/* @home-assistant/core homeassistant/components/trace/* @home-assistant/core
homeassistant/components/tractive/* @Danielhiversen @zhulik homeassistant/components/tractive/* @Danielhiversen @zhulik
homeassistant/components/tradfri/* @janiversen
homeassistant/components/trafikverket_train/* @endor-force homeassistant/components/trafikverket_train/* @endor-force
homeassistant/components/trafikverket_weatherstation/* @endor-force homeassistant/components/trafikverket_weatherstation/* @endor-force
homeassistant/components/transmission/* @engrbm87 @JPHutchins homeassistant/components/transmission/* @engrbm87 @JPHutchins
@@ -541,7 +545,7 @@ homeassistant/components/upb/* @gwww
homeassistant/components/upc_connect/* @pvizeli @fabaff homeassistant/components/upc_connect/* @pvizeli @fabaff
homeassistant/components/upcloud/* @scop homeassistant/components/upcloud/* @scop
homeassistant/components/updater/* @home-assistant/core homeassistant/components/updater/* @home-assistant/core
homeassistant/components/upnp/* @StevenLooman homeassistant/components/upnp/* @StevenLooman @ehendrix23
homeassistant/components/uptimerobot/* @ludeeus homeassistant/components/uptimerobot/* @ludeeus
homeassistant/components/usgs_earthquakes_feed/* @exxamalte homeassistant/components/usgs_earthquakes_feed/* @exxamalte
homeassistant/components/utility_meter/* @dgomes homeassistant/components/utility_meter/* @dgomes
@@ -584,7 +588,7 @@ homeassistant/components/xmpp/* @fabaff @flowolf
homeassistant/components/yale_smart_alarm/* @gjohansson-ST homeassistant/components/yale_smart_alarm/* @gjohansson-ST
homeassistant/components/yamaha_musiccast/* @vigonotion @micha91 homeassistant/components/yamaha_musiccast/* @vigonotion @micha91
homeassistant/components/yandex_transport/* @rishatik92 @devbis 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/yeelightsunflower/* @lindsaymarkward
homeassistant/components/yi/* @bachya homeassistant/components/yi/* @bachya
homeassistant/components/youless/* @gjong homeassistant/components/youless/* @gjong

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 import PermissionLookup, system_policies
from .permissions.types import PolicyType from .permissions.types import PolicyType
# mypy: disallow-any-generics
STORAGE_VERSION = 1 STORAGE_VERSION = 1
STORAGE_KEY = "auth" STORAGE_KEY = "auth"
GROUP_NAME_ADMIN = "Administrators" GROUP_NAME_ADMIN = "Administrators"
@@ -491,7 +493,7 @@ class AuthStore:
self._store.async_delay_save(self._data_to_save, 1) self._store.async_delay_save(self._data_to_save, 1)
@callback @callback
def _data_to_save(self) -> dict: def _data_to_save(self) -> dict[str, list[dict[str, Any]]]:
"""Return the data to store.""" """Return the data to store."""
assert self._users is not None assert self._users is not None
assert self._groups 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 ..const import MFA_SESSION_EXPIRATION
from ..models import Credentials, RefreshToken, User, UserMeta from ..models import Credentials, RefreshToken, User, UserMeta
# mypy: disallow-any-generics
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DATA_REQS = "auth_prov_reqs_processed" DATA_REQS = "auth_prov_reqs_processed"
@@ -96,7 +98,7 @@ class AuthProvider:
# Implement by extending class # 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. """Return the data flow for logging in with auth provider.
Auth provider should extend LoginFlow and return an instance. 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 . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from ..models import Credentials, UserMeta from ..models import Credentials, UserMeta
# mypy: disallow-any-generics
CONF_ARGS = "args" CONF_ARGS = "args"
CONF_META = "meta" CONF_META = "meta"
@@ -56,7 +58,7 @@ class CommandLineAuthProvider(AuthProvider):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self._user_meta: dict[str, dict[str, Any]] = {} 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 a flow to login."""
return CommandLineLoginFlow(self) return CommandLineLoginFlow(self)

View File

@@ -19,6 +19,8 @@ from homeassistant.exceptions import HomeAssistantError
from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow from . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from ..models import Credentials, UserMeta from ..models import Credentials, UserMeta
# mypy: disallow-any-generics
STORAGE_VERSION = 1 STORAGE_VERSION = 1
STORAGE_KEY = "auth_provider.homeassistant" STORAGE_KEY = "auth_provider.homeassistant"
@@ -235,7 +237,7 @@ class HassAuthProvider(AuthProvider):
await data.async_load() await data.async_load()
self.data = data 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 a flow to login."""
return HassLoginFlow(self) return HassLoginFlow(self)

View File

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

View File

@@ -7,7 +7,7 @@ from __future__ import annotations
from collections.abc import Mapping from collections.abc import Mapping
import hmac import hmac
from typing import cast from typing import Any, cast
import voluptuous as vol 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 . import AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, AuthProvider, LoginFlow
from ..models import Credentials, UserMeta from ..models import Credentials, UserMeta
# mypy: disallow-any-generics
AUTH_PROVIDER_TYPE = "legacy_api_password" AUTH_PROVIDER_TYPE = "legacy_api_password"
CONF_API_PASSWORD = "api_password" CONF_API_PASSWORD = "api_password"
@@ -44,7 +46,7 @@ class LegacyApiPasswordAuthProvider(AuthProvider):
"""Return api_password.""" """Return api_password."""
return str(self.config[CONF_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 a flow to login."""
return LegacyLoginFlow(self) return LegacyLoginFlow(self)

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,8 @@
}, },
"error": { "error": {
"cannot_connect": "Sikertelen csatlakoz\u00e1s", "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": { "step": {
"user": { "user": {
@@ -15,6 +16,7 @@
"longitude": "Hossz\u00fas\u00e1g", "longitude": "Hossz\u00fas\u00e1g",
"name": "N\u00e9v" "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" "title": "AccuWeather"
} }
} }
@@ -22,6 +24,10 @@
"options": { "options": {
"step": { "step": {
"user": { "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" "title": "AccuWeather be\u00e1ll\u00edt\u00e1sok"
} }
} }

View File

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

View File

@@ -2,6 +2,14 @@
"config": { "config": {
"abort": { "abort": {
"no_devices_found": "Nem tal\u00e1lhat\u00f3 eszk\u00f6z a h\u00e1l\u00f3zaton" "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

@@ -49,20 +49,19 @@ async def async_setup_entry(
class AdaxDevice(ClimateEntity): class AdaxDevice(ClimateEntity):
"""Representation of a heater.""" """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: def __init__(self, heater_data: dict[str, Any], adax_data_handler: Adax) -> None:
"""Initialize the heater.""" """Initialize the heater."""
self._heater_data = heater_data self._heater_data = heater_data
self._adax_data_handler = adax_data_handler self._adax_data_handler = adax_data_handler
@property self._attr_unique_id = f"{heater_data['homeId']}_{heater_data['id']}"
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']}"
@property @property
def name(self) -> str: def name(self) -> str:
@@ -83,11 +82,6 @@ class AdaxDevice(ClimateEntity):
return "mdi:radiator" return "mdi:radiator"
return "mdi:radiator-off" 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: async def async_set_hvac_mode(self, hvac_mode: str) -> None:
"""Set hvac mode.""" """Set hvac mode."""
if hvac_mode == HVAC_MODE_HEAT: if hvac_mode == HVAC_MODE_HEAT:
@@ -105,21 +99,6 @@ class AdaxDevice(ClimateEntity):
return return
await self._adax_data_handler.update() 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 @property
def current_temperature(self) -> float | None: def current_temperature(self) -> float | None:
"""Return the current temperature.""" """Return the current temperature."""
@@ -130,11 +109,6 @@ class AdaxDevice(ClimateEntity):
"""Return the temperature we try to reach.""" """Return the temperature we try to reach."""
return self._heater_data.get("targetTemperature") 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: async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature.""" """Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE) temperature = kwargs.get(ATTR_TEMPERATURE)

View File

@@ -4,7 +4,7 @@
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/adax", "documentation": "https://www.home-assistant.io/integrations/adax",
"requirements": [ "requirements": [
"adax==0.0.1" "adax==0.1.1"
], ],
"codeowners": [ "codeowners": [
"@danielhiversen" "@danielhiversen"

View File

@@ -10,6 +10,7 @@
"step": { "step": {
"user": { "user": {
"data": { "data": {
"account_id": "ID \u00fa\u010dtu",
"host": "Hostitel", "host": "Hostitel",
"password": "Heslo" "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

@@ -82,12 +82,12 @@ class AdGuardHomeSensor(AdGuardHomeDeviceEntity, SensorEntity):
) )
@property @property
def state(self) -> str | None: def native_value(self) -> str | None:
"""Return the state of the sensor.""" """Return the state of the sensor."""
return self._state return self._state
@property @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 the unit this state is expressed in."""
return self._unit_of_measurement return self._unit_of_measurement

View File

@@ -1,7 +1,8 @@
{ {
"config": { "config": {
"abort": { "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": { "error": {
"cannot_connect": "Sikertelen csatlakoz\u00e1s" "cannot_connect": "Sikertelen csatlakoz\u00e1s"
@@ -19,7 +20,8 @@
"ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata", "ssl": "SSL tan\u00fas\u00edtv\u00e1ny haszn\u00e1lata",
"username": "Felhaszn\u00e1l\u00f3n\u00e9v", "username": "Felhaszn\u00e1l\u00f3n\u00e9v",
"verify_ssl": "SSL-tan\u00fas\u00edtv\u00e1ny ellen\u0151rz\u00e9se" "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": { "config": {
"abort": { "abort": {
"already_configured": "\u670d\u52a1\u5df2\u88ab\u914d\u7f6e",
"existing_instance_updated": "\u66f4\u65b0\u4e86\u73b0\u6709\u914d\u7f6e\u3002" "existing_instance_updated": "\u66f4\u65b0\u4e86\u73b0\u6709\u914d\u7f6e\u3002"
}, },
"error": {
"cannot_connect": "\u8fde\u63a5\u5931\u8d25"
},
"step": { "step": {
"user": { "user": {
"data": { "data": {
"host": "\u4e3b\u673a\u5730\u5740",
"password": "\u5bc6\u7801", "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

@@ -91,13 +91,13 @@ class AdsCover(AdsEntity, CoverEntity):
): ):
"""Initialize AdsCover entity.""" """Initialize AdsCover entity."""
super().__init__(ads_hub, name, ads_var_is_closed) super().__init__(ads_hub, name, ads_var_is_closed)
if self._ads_var is None: if self._attr_unique_id is None:
if ads_var_position is not None: if ads_var_position is not None:
self._unique_id = ads_var_position self._attr_unique_id = ads_var_position
elif ads_var_pos_set is not None: elif ads_var_pos_set is not None:
self._unique_id = ads_var_pos_set self._attr_unique_id = ads_var_pos_set
elif ads_var_open is not None: elif ads_var_open is not None:
self._unique_id = ads_var_open self._attr_unique_id = ads_var_open
self._state_dict[STATE_KEY_POSITION] = None self._state_dict[STATE_KEY_POSITION] = None
self._ads_var_position = ads_var_position self._ads_var_position = ads_var_position

View File

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

View File

@@ -15,7 +15,6 @@ from homeassistant.components.climate.const import (
SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE,
) )
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS
from homeassistant.core import callback
from homeassistant.helpers import entity_platform from homeassistant.helpers import entity_platform
from .const import ( from .const import (
@@ -166,19 +165,22 @@ class AdvantageAirZone(AdvantageAirClimateEntity):
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}' f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}'
) )
async def async_added_to_hass(self): @property
"""When entity is added to hass.""" def hvac_mode(self):
self.async_on_remove(self.coordinator.async_add_listener(self._update_callback)) """Return the current state as HVAC mode."""
@callback
def _update_callback(self) -> None:
"""Load data from integration."""
self._attr_current_temperature = self._zone["measuredTemp"]
self._attr_target_temperature = self._zone["setTemp"]
self._attr_hvac_mode = HVAC_MODE_OFF
if self._zone["state"] == ADVANTAGE_AIR_STATE_OPEN: if self._zone["state"] == ADVANTAGE_AIR_STATE_OPEN:
self._attr_hvac_mode = HVAC_MODE_FAN_ONLY return HVAC_MODE_FAN_ONLY
self.async_write_ha_state() return HVAC_MODE_OFF
@property
def current_temperature(self):
"""Return the current temperature."""
return self._zone["measuredTemp"]
@property
def target_temperature(self):
"""Return the target temperature."""
return self._zone["setTemp"]
async def async_set_hvac_mode(self, hvac_mode): async def async_set_hvac_mode(self, hvac_mode):
"""Set the HVAC Mode and State.""" """Set the HVAC Mode and State."""

View File

@@ -45,7 +45,7 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity): class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
"""Representation of Advantage Air timer control.""" """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): def __init__(self, instance, ac_key, action):
"""Initialize the Advantage Air timer control.""" """Initialize the Advantage Air timer control."""
@@ -58,7 +58,7 @@ class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
) )
@property @property
def state(self): def native_value(self):
"""Return the current value.""" """Return the current value."""
return self._ac[self._time_key] return self._ac[self._time_key]
@@ -78,7 +78,7 @@ class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity): class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
"""Representation of Advantage Air Zone Vent Sensor.""" """Representation of Advantage Air Zone Vent Sensor."""
_attr_unit_of_measurement = PERCENTAGE _attr_native_unit_of_measurement = PERCENTAGE
_attr_state_class = STATE_CLASS_MEASUREMENT _attr_state_class = STATE_CLASS_MEASUREMENT
def __init__(self, instance, ac_key, zone_key): def __init__(self, instance, ac_key, zone_key):
@@ -90,7 +90,7 @@ class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
) )
@property @property
def state(self): def native_value(self):
"""Return the current value of the air vent.""" """Return the current value of the air vent."""
if self._zone["state"] == ADVANTAGE_AIR_STATE_OPEN: if self._zone["state"] == ADVANTAGE_AIR_STATE_OPEN:
return self._zone["value"] return self._zone["value"]
@@ -107,19 +107,19 @@ class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity): class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
"""Representation of Advantage Air Zone wireless signal sensor.""" """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 _attr_state_class = STATE_CLASS_MEASUREMENT
def __init__(self, instance, ac_key, zone_key): def __init__(self, instance, ac_key, zone_key):
"""Initialize an Advantage Air Zone wireless signal sensor.""" """Initialize an Advantage Air Zone wireless signal sensor."""
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_name = f'{self._zone["name"]} Signal'
self._attr_unique_id = ( self._attr_unique_id = (
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-signal' f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-signal'
) )
@property @property
def state(self): def native_value(self):
"""Return the current value of the wireless signal.""" """Return the current value of the wireless signal."""
return self._zone["rssi"] return self._zone["rssi"]
@@ -140,7 +140,7 @@ class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
class AdvantageAirZoneTemp(AdvantageAirEntity, SensorEntity): class AdvantageAirZoneTemp(AdvantageAirEntity, SensorEntity):
"""Representation of Advantage Air Zone wireless signal sensor.""" """Representation of Advantage Air Zone wireless signal sensor."""
_attr_unit_of_measurement = TEMP_CELSIUS _attr_native_unit_of_measurement = TEMP_CELSIUS
_attr_state_class = STATE_CLASS_MEASUREMENT _attr_state_class = STATE_CLASS_MEASUREMENT
_attr_icon = "mdi:thermometer" _attr_icon = "mdi:thermometer"
_attr_entity_registry_enabled_default = False _attr_entity_registry_enabled_default = False
@@ -149,9 +149,11 @@ class AdvantageAirZoneTemp(AdvantageAirEntity, SensorEntity):
"""Initialize an Advantage Air Zone Temp Sensor.""" """Initialize an Advantage Air Zone Temp Sensor."""
super().__init__(instance, ac_key, zone_key) super().__init__(instance, ac_key, zone_key)
self._attr_name = f'{self._zone["name"]} Temperature' 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 @property
def state(self): def native_value(self):
"""Return the current value of the measured temperature.""" """Return the current value of the measured temperature."""
return self._zone["measuredTemp"] return self._zone["measuredTemp"]

View File

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

View File

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

View File

@@ -67,8 +67,6 @@ async def async_setup_entry(
class AgentCamera(MjpegCamera): class AgentCamera(MjpegCamera):
"""Representation of an Agent Device Stream.""" """Representation of an Agent Device Stream."""
_attr_supported_features = SUPPORT_ON_OFF
def __init__(self, device): def __init__(self, device):
"""Initialize as a subclass of MjpegCamera.""" """Initialize as a subclass of MjpegCamera."""
device_info = { device_info = {
@@ -80,7 +78,6 @@ class AgentCamera(MjpegCamera):
self._removed = False self._removed = False
self._attr_name = f"{device.client.name} {device.name}" self._attr_name = f"{device.client.name} {device.name}"
self._attr_unique_id = f"{device._client.unique}_{device.typeID}_{device.id}" self._attr_unique_id = f"{device._client.unique}_{device.typeID}_{device.id}"
self._attr_should_poll = True
super().__init__(device_info) super().__init__(device_info)
self._attr_device_info = { self._attr_device_info = {
"identifiers": {(AGENT_DOMAIN, self.unique_id)}, "identifiers": {(AGENT_DOMAIN, self.unique_id)},
@@ -102,10 +99,10 @@ class AgentCamera(MjpegCamera):
if self.device.client.is_available and not self._removed: if self.device.client.is_available and not self._removed:
_LOGGER.error("%s lost", self.name) _LOGGER.error("%s lost", self.name)
self._removed = True self._removed = True
self._attr_available = self.device.client.is_available
self._attr_icon = "mdi:camcorder-off" self._attr_icon = "mdi:camcorder-off"
if self.is_on: if self.is_on:
self._attr_icon = "mdi:camcorder" self._attr_icon = "mdi:camcorder"
self._attr_available = self.device.client.is_available
self._attr_extra_state_attributes = { self._attr_extra_state_attributes = {
ATTR_ATTRIBUTION: ATTRIBUTION, ATTR_ATTRIBUTION: ATTRIBUTION,
"editable": False, "editable": False,
@@ -117,6 +114,11 @@ class AgentCamera(MjpegCamera):
"alerts_enabled": self.device.alerts_active, "alerts_enabled": self.device.alerts_active,
} }
@property
def should_poll(self) -> bool:
"""Update the state periodically."""
return True
@property @property
def is_recording(self) -> bool: def is_recording(self) -> bool:
"""Return whether the monitor is recording.""" """Return whether the monitor is recording."""
@@ -137,6 +139,11 @@ class AgentCamera(MjpegCamera):
"""Return True if entity is connected.""" """Return True if entity is connected."""
return self.device.connected return self.device.connected
@property
def supported_features(self) -> int:
"""Return supported features."""
return SUPPORT_ON_OFF
@property @property
def is_on(self) -> bool: def is_on(self) -> bool:
"""Return true if on.""" """Return true if on."""

View File

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

View File

@@ -1,7 +1,20 @@
{ {
"config": { "config": {
"abort": {
"already_configured": "\u8bbe\u5907\u5df2\u88ab\u914d\u7f6e"
},
"error": { "error": {
"already_in_progress": "\u914d\u7f6e\u6d41\u5df2\u8fdb\u884c\u4e2d",
"cannot_connect": "\u8fde\u63a5\u5931\u8d25" "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.components.sensor import STATE_CLASS_MEASUREMENT
from homeassistant.const import ( from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER, CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
DEVICE_CLASS_AQI,
DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_PM1,
DEVICE_CLASS_PM10,
DEVICE_CLASS_PM25,
DEVICE_CLASS_PRESSURE, DEVICE_CLASS_PRESSURE,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
PERCENTAGE, PERCENTAGE,
@@ -49,35 +53,36 @@ NO_AIRLY_SENSORS: Final = "There are no Airly sensors in this area yet."
SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = ( SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
AirlySensorEntityDescription( AirlySensorEntityDescription(
key=ATTR_API_CAQI, key=ATTR_API_CAQI,
device_class=DEVICE_CLASS_AQI,
name=ATTR_API_CAQI, name=ATTR_API_CAQI,
unit_of_measurement="CAQI", native_unit_of_measurement="CAQI",
), ),
AirlySensorEntityDescription( AirlySensorEntityDescription(
key=ATTR_API_PM1, key=ATTR_API_PM1,
icon="mdi:blur", device_class=DEVICE_CLASS_PM1,
name=ATTR_API_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, state_class=STATE_CLASS_MEASUREMENT,
), ),
AirlySensorEntityDescription( AirlySensorEntityDescription(
key=ATTR_API_PM25, key=ATTR_API_PM25,
icon="mdi:blur", device_class=DEVICE_CLASS_PM25,
name="PM2.5", 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, state_class=STATE_CLASS_MEASUREMENT,
), ),
AirlySensorEntityDescription( AirlySensorEntityDescription(
key=ATTR_API_PM10, key=ATTR_API_PM10,
icon="mdi:blur", device_class=DEVICE_CLASS_PM10,
name=ATTR_API_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, state_class=STATE_CLASS_MEASUREMENT,
), ),
AirlySensorEntityDescription( AirlySensorEntityDescription(
key=ATTR_API_HUMIDITY, key=ATTR_API_HUMIDITY,
device_class=DEVICE_CLASS_HUMIDITY, device_class=DEVICE_CLASS_HUMIDITY,
name=ATTR_API_HUMIDITY.capitalize(), name=ATTR_API_HUMIDITY.capitalize(),
unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
value=lambda value: round(value, 1), value=lambda value: round(value, 1),
), ),
@@ -85,14 +90,14 @@ SENSOR_TYPES: tuple[AirlySensorEntityDescription, ...] = (
key=ATTR_API_PRESSURE, key=ATTR_API_PRESSURE,
device_class=DEVICE_CLASS_PRESSURE, device_class=DEVICE_CLASS_PRESSURE,
name=ATTR_API_PRESSURE.capitalize(), name=ATTR_API_PRESSURE.capitalize(),
unit_of_measurement=PRESSURE_HPA, native_unit_of_measurement=PRESSURE_HPA,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
), ),
AirlySensorEntityDescription( AirlySensorEntityDescription(
key=ATTR_API_TEMPERATURE, key=ATTR_API_TEMPERATURE,
device_class=DEVICE_CLASS_TEMPERATURE, device_class=DEVICE_CLASS_TEMPERATURE,
name=ATTR_API_TEMPERATURE.capitalize(), name=ATTR_API_TEMPERATURE.capitalize(),
unit_of_measurement=TEMP_CELSIUS, native_unit_of_measurement=TEMP_CELSIUS,
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
value=lambda value: round(value, 1), value=lambda value: round(value, 1),
), ),

View File

@@ -84,7 +84,7 @@ class AirlySensor(CoordinatorEntity, SensorEntity):
self.entity_description = description self.entity_description = description
@property @property
def state(self) -> StateType: def native_value(self) -> StateType:
"""Return the state.""" """Return the state."""
state = self.coordinator.data[self.entity_description.key] state = self.coordinator.data[self.entity_description.key]
return cast(StateType, self.entity_description.value(state)) 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_name = f"AirNow {SENSOR_TYPES[self.kind][ATTR_LABEL]}"
self._attr_icon = SENSOR_TYPES[self.kind][ATTR_ICON] self._attr_icon = SENSOR_TYPES[self.kind][ATTR_ICON]
self._attr_device_class = SENSOR_TYPES[self.kind][ATTR_DEVICE_CLASS] 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()}" self._attr_unique_id = f"{self.coordinator.latitude}-{self.coordinator.longitude}-{self.kind.lower()}"
@property @property
def state(self): def native_value(self):
"""Return the state.""" """Return the state."""
self._state = self.coordinator.data[self.kind] self._state = self.coordinator.data[self.kind]
return self._state 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,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,17 @@
{
"config": {
"abort": {
"already_configured": "Apparaat is al geconfigureerd"
},
"error": {
"no_units": "Kan geen AirTouch 4-groepen vinden."
},
"step": {
"user": {
"data": {
"host": "Host"
}
}
}
}
}

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

@@ -212,7 +212,7 @@ class AirVisualGeographySensor(AirVisualEntity, SensorEntity):
self._attr_icon = icon self._attr_icon = icon
self._attr_name = f"{GEOGRAPHY_SENSOR_LOCALES[locale]} {name}" self._attr_name = f"{GEOGRAPHY_SENSOR_LOCALES[locale]} {name}"
self._attr_unique_id = f"{config_entry.unique_id}_{locale}_{kind}" self._attr_unique_id = f"{config_entry.unique_id}_{locale}_{kind}"
self._attr_unit_of_measurement = unit self._attr_native_unit_of_measurement = unit
self._config_entry = config_entry self._config_entry = config_entry
self._kind = kind self._kind = kind
self._locale = locale self._locale = locale
@@ -232,16 +232,16 @@ class AirVisualGeographySensor(AirVisualEntity, SensorEntity):
if self._kind == SENSOR_KIND_LEVEL: if self._kind == SENSOR_KIND_LEVEL:
aqi = data[f"aqi{self._locale}"] aqi = data[f"aqi{self._locale}"]
[(self._attr_state, self._attr_icon)] = [ [(self._attr_native_value, self._attr_icon)] = [
(name, icon) (name, icon)
for (floor, ceiling), (name, icon) in POLLUTANT_LEVELS.items() for (floor, ceiling), (name, icon) in POLLUTANT_LEVELS.items()
if floor <= aqi <= ceiling if floor <= aqi <= ceiling
] ]
elif self._kind == SENSOR_KIND_AQI: elif self._kind == SENSOR_KIND_AQI:
self._attr_state = data[f"aqi{self._locale}"] self._attr_native_value = data[f"aqi{self._locale}"]
elif self._kind == SENSOR_KIND_POLLUTANT: elif self._kind == SENSOR_KIND_POLLUTANT:
symbol = data[f"main{self._locale}"] symbol = data[f"main{self._locale}"]
self._attr_state = symbol self._attr_native_value = symbol
self._attr_extra_state_attributes.update( self._attr_extra_state_attributes.update(
{ {
ATTR_POLLUTANT_SYMBOL: symbol, ATTR_POLLUTANT_SYMBOL: symbol,
@@ -298,7 +298,7 @@ class AirVisualNodeProSensor(AirVisualEntity, SensorEntity):
f"{coordinator.data['settings']['node_name']} Node/Pro: {name}" f"{coordinator.data['settings']['node_name']} Node/Pro: {name}"
) )
self._attr_unique_id = f"{coordinator.data['serial_number']}_{kind}" self._attr_unique_id = f"{coordinator.data['serial_number']}_{kind}"
self._attr_unit_of_measurement = unit self._attr_native_unit_of_measurement = unit
self._kind = kind self._kind = kind
@property @property
@@ -320,24 +320,30 @@ class AirVisualNodeProSensor(AirVisualEntity, SensorEntity):
"""Update the entity from the latest data.""" """Update the entity from the latest data."""
if self._kind == SENSOR_KIND_AQI: if self._kind == SENSOR_KIND_AQI:
if self.coordinator.data["settings"]["is_aqi_usa"]: 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: else:
self._attr_state = self.coordinator.data["measurements"]["aqi_cn"] self._attr_native_value = self.coordinator.data["measurements"][
"aqi_cn"
]
elif self._kind == SENSOR_KIND_BATTERY_LEVEL: elif self._kind == SENSOR_KIND_BATTERY_LEVEL:
self._attr_state = self.coordinator.data["status"]["battery"] self._attr_native_value = self.coordinator.data["status"]["battery"]
elif self._kind == SENSOR_KIND_CO2: elif self._kind == SENSOR_KIND_CO2:
self._attr_state = self.coordinator.data["measurements"].get("co2") self._attr_native_value = self.coordinator.data["measurements"].get("co2")
elif self._kind == SENSOR_KIND_HUMIDITY: elif self._kind == SENSOR_KIND_HUMIDITY:
self._attr_state = self.coordinator.data["measurements"].get("humidity") self._attr_native_value = self.coordinator.data["measurements"].get(
"humidity"
)
elif self._kind == SENSOR_KIND_PM_0_1: elif self._kind == SENSOR_KIND_PM_0_1:
self._attr_state = self.coordinator.data["measurements"].get("pm0_1") self._attr_native_value = self.coordinator.data["measurements"].get("pm0_1")
elif self._kind == SENSOR_KIND_PM_1_0: elif self._kind == SENSOR_KIND_PM_1_0:
self._attr_state = self.coordinator.data["measurements"].get("pm1_0") self._attr_native_value = self.coordinator.data["measurements"].get("pm1_0")
elif self._kind == SENSOR_KIND_PM_2_5: elif self._kind == SENSOR_KIND_PM_2_5:
self._attr_state = self.coordinator.data["measurements"].get("pm2_5") self._attr_native_value = self.coordinator.data["measurements"].get("pm2_5")
elif self._kind == SENSOR_KIND_TEMPERATURE: elif self._kind == SENSOR_KIND_TEMPERATURE:
self._attr_state = self.coordinator.data["measurements"].get( self._attr_native_value = self.coordinator.data["measurements"].get(
"temperature_C" "temperature_C"
) )
elif self._kind == SENSOR_KIND_VOC: elif self._kind == SENSOR_KIND_VOC:
self._attr_state = self.coordinator.data["measurements"].get("voc") self._attr_native_value = self.coordinator.data["measurements"].get("voc")

View File

@@ -34,13 +34,29 @@
"data": { "data": {
"ip_address": "Hoszt", "ip_address": "Hoszt",
"password": "Jelsz\u00f3" "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": { "reauth_confirm": {
"data": { "data": {
"api_key": "API kulcs" "api_key": "API kulcs"
}, },
"title": "Az AirVisual \u00fajb\u00f3li hiteles\u00edt\u00e9se" "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": "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": { "state": {
"airvisual__pollutant_label": { "airvisual__pollutant_label": {
"co": "Karbonmonoksid",
"n2": "Nitrogendioksid",
"o3": "Ozon",
"p1": "PM10", "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

@@ -49,7 +49,10 @@ def setup_platform(
try: try:
if not acc.login(): if not acc.login():
raise ValueError("Username or Password is incorrect") raise ValueError("Username or Password is incorrect")
add_entities(AladdinDevice(acc, door) for door in acc.get_doors()) add_entities(
(AladdinDevice(acc, door) for door in acc.get_doors()),
update_before_add=True,
)
except (TypeError, KeyError, NameError, ValueError) as ex: except (TypeError, KeyError, NameError, ValueError) as ex:
_LOGGER.error("%s", ex) _LOGGER.error("%s", ex)
hass.components.persistent_notification.create( hass.components.persistent_notification.create(

View File

@@ -12,6 +12,7 @@
"is_armed_away": "{entity_name} est arm\u00e9", "is_armed_away": "{entity_name} est arm\u00e9",
"is_armed_home": "{entity_name} est arm\u00e9 \u00e0 la maison", "is_armed_home": "{entity_name} est arm\u00e9 \u00e0 la maison",
"is_armed_night": "{entity_name} est arm\u00e9 la nuit", "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_disarmed": "{entity_name} est d\u00e9sarm\u00e9",
"is_triggered": "{entity_name} est d\u00e9clench\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" "trigger": "{entity_name} riaszt\u00e1si esem\u00e9ny ind\u00edt\u00e1sa"
}, },
"condition_type": { "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": { "trigger_type": {
"armed_away": "{entity_name} t\u00e1voz\u00f3 m\u00f3dban lett \u00e9les\u00edtve", "armed_away": "{entity_name} t\u00e1voz\u00f3 m\u00f3dban lett \u00e9les\u00edtve",

View File

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

View File

@@ -111,13 +111,13 @@ class AlarmDecoderBinarySensor(BinarySensorEntity):
def _fault_callback(self, zone): def _fault_callback(self, zone):
"""Update the zone's state, if needed.""" """Update the zone's state, if needed."""
if zone is None or int(zone) == self._zone_number: if zone is None or int(zone) == self._zone_number:
self._attr_state = 1 self._attr_is_on = True
self.schedule_update_ha_state() self.schedule_update_ha_state()
def _restore_callback(self, zone): def _restore_callback(self, zone):
"""Update the zone's state, if needed.""" """Update the zone's state, if needed."""
if zone is None or (int(zone) == self._zone_number and not self._loop): if zone is None or (int(zone) == self._zone_number and not self._loop):
self._attr_state = 0 self._attr_is_on = False
self.schedule_update_ha_state() self.schedule_update_ha_state()
def _rfx_message_callback(self, message): def _rfx_message_callback(self, message):
@@ -125,7 +125,7 @@ class AlarmDecoderBinarySensor(BinarySensorEntity):
if self._rfid and message and message.serial_number == self._rfid: if self._rfid and message and message.serial_number == self._rfid:
rfstate = message.value rfstate = message.value
if self._loop: if self._loop:
self._attr_state = 1 if message.loop[self._loop - 1] else 0 self._attr_is_on = bool(message.loop[self._loop - 1])
attr = {CONF_ZONE_NUMBER: self._zone_number} attr = {CONF_ZONE_NUMBER: self._zone_number}
if self._rfid and rfstate is not None: if self._rfid and rfstate is not None:
attr[ATTR_RF_BIT0] = bool(rfstate & 0x01) attr[ATTR_RF_BIT0] = bool(rfstate & 0x01)
@@ -150,5 +150,5 @@ class AlarmDecoderBinarySensor(BinarySensorEntity):
message.channel, message.channel,
message.value, message.value,
) )
self._attr_state = message.value self._attr_is_on = bool(message.value)
self.schedule_update_ha_state() self.schedule_update_ha_state()

View File

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

View File

@@ -31,6 +31,7 @@
"error": { "error": {
"int": "Az al\u00e1bbi mez\u0151nek eg\u00e9sz sz\u00e1mnak kell lennie.", "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_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." "relay_inclusive": "A rel\u00e9c\u00edm \u00e9s a rel\u00e9csatorna egym\u00e1st\u00f3l f\u00fcgg, \u00e9s egy\u00fctt kell felt\u00fcntetni."
}, },
"step": { "step": {
@@ -55,6 +56,7 @@
"zone_name": "Z\u00f3na neve", "zone_name": "Z\u00f3na neve",
"zone_relayaddr": "Rel\u00e9 c\u00edm", "zone_relayaddr": "Rel\u00e9 c\u00edm",
"zone_relaychan": "Rel\u00e9 csatorna", "zone_relaychan": "Rel\u00e9 csatorna",
"zone_rfid": "RF soros",
"zone_type": "Z\u00f3na t\u00edpusa" "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.", "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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -154,8 +154,6 @@ class AmbiclimateEntity(ClimateEntity):
"name": self.name, "name": self.name,
"manufacturer": "Ambiclimate", "manufacturer": "Ambiclimate",
} }
self._attr_min_temp = heater.get_min_temp()
self._attr_max_temp = heater.get_max_temp()
async def async_set_temperature(self, **kwargs: Any) -> None: async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature.""" """Set new target temperature."""
@@ -184,6 +182,8 @@ class AmbiclimateEntity(ClimateEntity):
await self._store.async_save(token_info) await self._store.async_save(token_info)
data = await self._heater.update_device() data = await self._heater.update_device()
self._attr_min_temp = self._heater.get_min_temp()
self._attr_max_temp = self._heater.get_max_temp()
self._attr_target_temperature = data.get("target_temperature") self._attr_target_temperature = data.get("target_temperature")
self._attr_current_temperature = data.get("temperature") self._attr_current_temperature = data.get("temperature")
self._attr_current_humidity = data.get("humidity") self._attr_current_humidity = data.get("humidity")

View File

@@ -1,11 +1,22 @@
{ {
"config": { "config": {
"abort": { "abort": {
"access_token": "Ismeretlen hiba a hozz\u00e1f\u00e9r\u00e9si token gener\u00e1l\u00e1s\u00e1ban.",
"already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van", "already_configured": "A fi\u00f3k m\u00e1r konfigur\u00e1lva van",
"missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t." "missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t."
}, },
"create_entry": { "create_entry": {
"default": "Sikeres hiteles\u00edt\u00e9s" "default": "Sikeres hiteles\u00edt\u00e9s"
},
"error": {
"follow_link": "K\u00e9rlek, k\u00f6vesd a hivatkoz\u00e1st \u00e9s hiteles\u00edtsd magad miel\u0151tt megnyomod a K\u00fcld\u00e9s gombot",
"no_token": "Nem hiteles\u00edtett Ambiclimate"
},
"step": {
"auth": {
"description": "K\u00e9rj\u00fck, k\u00f6vesse ezt a [link] ({authorization_url} Author_url}) \u00e9s ** Enged\u00e9lyezze ** a hozz\u00e1f\u00e9r\u00e9st Ambiclimate -fi\u00f3kj\u00e1hoz, majd t\u00e9rjen vissza, \u00e9s nyomja meg az al\u00e1bbi ** K\u00fcld\u00e9s ** gombot.\n (Gy\u0151z\u0151dj\u00f6n meg arr\u00f3l, hogy a megadott visszah\u00edv\u00e1si URL {cb_url})",
"title": "Ambiclimate hiteles\u00edt\u00e9se"
}
} }
} }
} }

View File

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

View File

@@ -61,7 +61,7 @@ class AmbientWeatherSensor(AmbientWeatherEntity, SensorEntity):
ambient, mac_address, station_name, sensor_type, sensor_name, device_class ambient, mac_address, station_name, sensor_type, sensor_name, device_class
) )
self._attr_unit_of_measurement = unit self._attr_native_unit_of_measurement = unit
@callback @callback
def update_from_latest_data(self) -> None: def update_from_latest_data(self) -> None:
@@ -75,10 +75,10 @@ class AmbientWeatherSensor(AmbientWeatherEntity, SensorEntity):
].get(TYPE_SOLARRADIATION) ].get(TYPE_SOLARRADIATION)
if w_m2_brightness_val is None: if w_m2_brightness_val is None:
self._attr_state = None self._attr_native_value = None
else: else:
self._attr_state = round(float(w_m2_brightness_val) / 0.0079) self._attr_native_value = round(float(w_m2_brightness_val) / 0.0079)
else: else:
self._attr_state = self._ambient.stations[self._mac_address][ self._attr_native_value = self._ambient.stations[self._mac_address][
ATTR_LAST_DATA ATTR_LAST_DATA
].get(self._sensor_type) ].get(self._sensor_type)

View File

@@ -53,7 +53,7 @@ _CROSSLINE_DETECTED_PARAMS = (
DEVICE_CLASS_MOTION, DEVICE_CLASS_MOTION,
"CrossLineDetection", "CrossLineDetection",
) )
BINARY_SENSORS = { RAW_BINARY_SENSORS = {
BINARY_SENSOR_AUDIO_DETECTED: _AUDIO_DETECTED_PARAMS, BINARY_SENSOR_AUDIO_DETECTED: _AUDIO_DETECTED_PARAMS,
BINARY_SENSOR_AUDIO_DETECTED_POLLED: _AUDIO_DETECTED_PARAMS, BINARY_SENSOR_AUDIO_DETECTED_POLLED: _AUDIO_DETECTED_PARAMS,
BINARY_SENSOR_MOTION_DETECTED: _MOTION_DETECTED_PARAMS, BINARY_SENSOR_MOTION_DETECTED: _MOTION_DETECTED_PARAMS,
@@ -64,7 +64,7 @@ BINARY_SENSORS = {
} }
BINARY_SENSORS = { BINARY_SENSORS = {
k: dict(zip((SENSOR_NAME, SENSOR_DEVICE_CLASS, SENSOR_EVENT_CODE), v)) k: dict(zip((SENSOR_NAME, SENSOR_DEVICE_CLASS, SENSOR_EVENT_CODE), v))
for k, v in BINARY_SENSORS.items() for k, v in RAW_BINARY_SENSORS.items()
} }
_EXCLUSIVE_OPTIONS = [ _EXCLUSIVE_OPTIONS = [
{BINARY_SENSOR_MOTION_DETECTED, BINARY_SENSOR_MOTION_DETECTED_POLLED}, {BINARY_SENSOR_MOTION_DETECTED, BINARY_SENSOR_MOTION_DETECTED_POLLED},

View File

@@ -1,4 +1,6 @@
"""Support for Amcrest IP cameras.""" """Support for Amcrest IP cameras."""
from __future__ import annotations
import asyncio import asyncio
from datetime import timedelta from datetime import timedelta
from functools import partial from functools import partial
@@ -181,7 +183,9 @@ class AmcrestCam(Camera):
finally: finally:
self._snapshot_task = None self._snapshot_task = None
async def async_camera_image(self): async def async_camera_image(
self, width: int | None = None, height: int | None = None
) -> bytes | None:
"""Return a still image response from the camera.""" """Return a still image response from the camera."""
_LOGGER.debug("Take snapshot from %s", self._name) _LOGGER.debug("Take snapshot from %s", self._name)
try: try:

View File

@@ -61,7 +61,7 @@ class AmcrestSensor(SensorEntity):
return self._name return self._name
@property @property
def state(self): def native_value(self):
"""Return the state of the sensor.""" """Return the state of the sensor."""
return self._state return self._state
@@ -76,7 +76,7 @@ class AmcrestSensor(SensorEntity):
return self._icon return self._icon
@property @property
def unit_of_measurement(self): def native_unit_of_measurement(self):
"""Return the units of measurement.""" """Return the units of measurement."""
return self._unit_of_measurement return self._unit_of_measurement

View File

@@ -5,12 +5,13 @@ from homeassistant.components import websocket_api
from homeassistant.const import EVENT_HOMEASSISTANT_STARTED from homeassistant.const import EVENT_HOMEASSISTANT_STARTED
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.event import async_call_later, async_track_time_interval from homeassistant.helpers.event import async_call_later, async_track_time_interval
from homeassistant.helpers.typing import ConfigType
from .analytics import Analytics from .analytics import Analytics
from .const import ATTR_ONBOARDED, ATTR_PREFERENCES, DOMAIN, INTERVAL, PREFERENCE_SCHEMA from .const import ATTR_ONBOARDED, ATTR_PREFERENCES, DOMAIN, INTERVAL, PREFERENCE_SCHEMA
async def async_setup(hass: HomeAssistant, _): async def async_setup(hass: HomeAssistant, _: ConfigType) -> bool:
"""Set up the analytics integration.""" """Set up the analytics integration."""
analytics = Analytics(hass) analytics = Analytics(hass)

View File

@@ -50,12 +50,12 @@ class IPWebcamSensor(AndroidIPCamEntity, SensorEntity):
return self._name return self._name
@property @property
def unit_of_measurement(self): def native_unit_of_measurement(self):
"""Return the unit the value is expressed in.""" """Return the unit the value is expressed in."""
return self._unit return self._unit
@property @property
def state(self): def native_value(self):
"""Return the state of the sensor.""" """Return the state of the sensor."""
return self._state return self._state

View File

@@ -3,7 +3,7 @@
"name": "Android TV", "name": "Android TV",
"documentation": "https://www.home-assistant.io/integrations/androidtv", "documentation": "https://www.home-assistant.io/integrations/androidtv",
"requirements": [ "requirements": [
"adb-shell[async]==0.3.4", "adb-shell[async]==0.4.0",
"androidtv[async]==0.0.60", "androidtv[async]==0.0.60",
"pure-python-adb[async]==0.3.0.dev0" "pure-python-adb[async]==0.3.0.dev0"
], ],

View File

@@ -449,6 +449,11 @@ class ADBDevice(MediaPlayerEntity):
ATTR_HDMI_INPUT: None, ATTR_HDMI_INPUT: None,
} }
@property
def media_image_hash(self):
"""Hash value for media image."""
return f"{datetime.now().timestamp()}" if self._screencap else None
@adb_decorator() @adb_decorator()
async def _adb_screencap(self): async def _adb_screencap(self):
"""Take a screen capture from the device.""" """Take a screen capture from the device."""
@@ -458,9 +463,6 @@ class ADBDevice(MediaPlayerEntity):
"""Fetch current playing image.""" """Fetch current playing image."""
if not self._screencap or self.state in (STATE_OFF, None) or not self.available: if not self._screencap or self.state in (STATE_OFF, None) or not self.available:
return None, None return None, None
self._attr_media_image_hash = (
f"{datetime.now().timestamp()}" if self._screencap else None
)
media_data = await self._adb_screencap() media_data = await self._adb_screencap()
if media_data: if media_data:

View File

@@ -165,16 +165,16 @@ class APCUPSdSensor(SensorEntity):
self.type = sensor_type self.type = sensor_type
self._attr_name = SENSOR_PREFIX + SENSOR_TYPES[sensor_type][0] self._attr_name = SENSOR_PREFIX + SENSOR_TYPES[sensor_type][0]
self._attr_icon = SENSOR_TYPES[self.type][2] self._attr_icon = SENSOR_TYPES[self.type][2]
self._attr_unit_of_measurement = SENSOR_TYPES[sensor_type][1] self._attr_native_unit_of_measurement = SENSOR_TYPES[sensor_type][1]
self._attr_device_class = SENSOR_TYPES[sensor_type][3] self._attr_device_class = SENSOR_TYPES[sensor_type][3]
def update(self): def update(self):
"""Get the latest status and use it to update our sensor state.""" """Get the latest status and use it to update our sensor state."""
if self.type.upper() not in self._data.status: if self.type.upper() not in self._data.status:
self._attr_state = None self._attr_native_value = None
else: else:
self._attr_state, inferred_unit = infer_unit( self._attr_native_value, inferred_unit = infer_unit(
self._data.status[self.type.upper()] self._data.status[self.type.upper()]
) )
if not self._attr_unit_of_measurement: if not self._attr_native_unit_of_measurement:
self._attr_unit_of_measurement = inferred_unit self._attr_native_unit_of_measurement = inferred_unit

View File

@@ -43,7 +43,6 @@ from homeassistant.helpers.system_info import async_get_system_info
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
ATTR_BASE_URL = "base_url" ATTR_BASE_URL = "base_url"
ATTR_CURRENCY = "currency"
ATTR_EXTERNAL_URL = "external_url" ATTR_EXTERNAL_URL = "external_url"
ATTR_INTERNAL_URL = "internal_url" ATTR_INTERNAL_URL = "internal_url"
ATTR_LOCATION_NAME = "location_name" ATTR_LOCATION_NAME = "location_name"
@@ -196,7 +195,6 @@ class APIDiscoveryView(HomeAssistantView):
# always needs authentication # always needs authentication
ATTR_REQUIRES_API_PASSWORD: True, ATTR_REQUIRES_API_PASSWORD: True,
ATTR_VERSION: __version__, ATTR_VERSION: __version__,
ATTR_CURRENCY: None,
} }
with suppress(NoURLAvailableError): with suppress(NoURLAvailableError):

View File

@@ -92,10 +92,11 @@ class AquaLogicSensor(SensorEntity):
panel = self._processor.panel panel = self._processor.panel
if panel is not None: if panel is not None:
if panel.is_metric: if panel.is_metric:
self._attr_unit_of_measurement = SENSOR_TYPES[self._type][1][0] self._attr_native_unit_of_measurement = SENSOR_TYPES[self._type][1][0]
self._attr_state = getattr(panel, self._type)
self.async_write_ha_state()
else: else:
self._attr_unit_of_measurement = SENSOR_TYPES[self._type][1][1] self._attr_native_unit_of_measurement = SENSOR_TYPES[self._type][1][1]
self._attr_native_value = getattr(panel, self._type)
self.async_write_ha_state()
else: else:
self._attr_unit_of_measurement = None self._attr_native_unit_of_measurement = None

View File

@@ -36,7 +36,7 @@ async def _await_cancel(task):
await task await task
async def async_setup(hass: HomeAssistant, config: ConfigType): async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the component.""" """Set up the component."""
hass.data[DOMAIN_DATA_ENTRIES] = {} hass.data[DOMAIN_DATA_ENTRIES] = {}
hass.data[DOMAIN_DATA_TASKS] = {} hass.data[DOMAIN_DATA_TASKS] = {}

View File

@@ -5,14 +5,27 @@
"already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.", "already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.",
"cannot_connect": "Sikertelen csatlakoz\u00e1s" "cannot_connect": "Sikertelen csatlakoz\u00e1s"
}, },
"error": {
"one": "\u00dcres",
"other": "\u00dcres"
},
"flow_title": "{host}", "flow_title": "{host}",
"step": { "step": {
"confirm": {
"description": "Hozz\u00e1 szeretn\u00e9 adni az Arcam FMJ \"{host}\" eszk\u00f6zt a HomeAssistanthoz?"
},
"user": { "user": {
"data": { "data": {
"host": "Hoszt", "host": "Hoszt",
"port": "Port" "port": "Port"
} },
"description": "K\u00e9rj\u00fck, adja meg az eszk\u00f6z gazdag\u00e9pnev\u00e9t vagy IP-c\u00edm\u00e9t."
} }
} }
},
"device_automation": {
"trigger_type": {
"turn_on": "{entity_name} bekapcsol\u00e1s\u00e1t k\u00e9rt\u00e9k"
}
} }
} }

View File

@@ -42,4 +42,4 @@ class ArduinoSensor(SensorEntity):
def update(self): def update(self):
"""Get the latest value from the pin.""" """Get the latest value from the pin."""
self._attr_state = self._board.get_analog_inputs()[self._pin][1] self._attr_native_value = self._board.get_analog_inputs()[self._pin][1]

View File

@@ -141,7 +141,7 @@ class ArestSensor(SensorEntity):
self.arest = arest self.arest = arest
self._attr_name = f"{location.title()} {name.title()}" self._attr_name = f"{location.title()} {name.title()}"
self._variable = variable self._variable = variable
self._attr_unit_of_measurement = unit_of_measurement self._attr_native_unit_of_measurement = unit_of_measurement
self._renderer = renderer self._renderer = renderer
if pin is not None: if pin is not None:
@@ -155,9 +155,9 @@ class ArestSensor(SensorEntity):
self._attr_available = self.arest.available self._attr_available = self.arest.available
values = self.arest.data values = self.arest.data
if "error" in values: if "error" in values:
self._attr_state = values["error"] self._attr_native_value = values["error"]
else: else:
self._attr_state = self._renderer( self._attr_native_value = self._renderer(
values.get("value", values.get(self._variable, None)) values.get("value", values.get(self._variable, None))
) )

View File

@@ -1,4 +1,6 @@
"""Support for Netgear Arlo IP cameras.""" """Support for Netgear Arlo IP cameras."""
from __future__ import annotations
import logging import logging
from haffmpeg.camera import CameraMjpeg from haffmpeg.camera import CameraMjpeg
@@ -62,7 +64,9 @@ class ArloCam(Camera):
self._last_refresh = None self._last_refresh = None
self.attrs = {} self.attrs = {}
def camera_image(self): def camera_image(
self, width: int | None = None, height: int | None = None
) -> bytes | None:
"""Return a still image response from the camera.""" """Return a still image response from the camera."""
return self._camera.last_image_from_cache return self._camera.last_image_from_cache

View File

@@ -1,13 +1,21 @@
"""Sensor support for Netgear Arlo IP cameras.""" """Sensor support for Netgear Arlo IP cameras."""
from __future__ import annotations
from dataclasses import replace
import logging import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.components.sensor import (
PLATFORM_SCHEMA,
SensorEntity,
SensorEntityDescription,
)
from homeassistant.const import ( from homeassistant.const import (
ATTR_ATTRIBUTION, ATTR_ATTRIBUTION,
CONCENTRATION_PARTS_PER_MILLION, CONCENTRATION_PARTS_PER_MILLION,
CONF_MONITORED_CONDITIONS, CONF_MONITORED_CONDITIONS,
DEVICE_CLASS_BATTERY,
DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_TEMPERATURE,
PERCENTAGE, PERCENTAGE,
@@ -22,22 +30,59 @@ from . import ATTRIBUTION, DATA_ARLO, DEFAULT_BRAND, SIGNAL_UPDATE_ARLO
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
# sensor_type [ description, unit, icon ] SENSOR_TYPES: tuple[SensorEntityDescription, ...] = (
SENSOR_TYPES = { SensorEntityDescription(
"last_capture": ["Last", None, "run-fast"], key="last_capture",
"total_cameras": ["Arlo Cameras", None, "video"], name="Last",
"captured_today": ["Captured Today", None, "file-video"], icon="mdi:run-fast",
"battery_level": ["Battery Level", PERCENTAGE, "battery-50"], ),
"signal_strength": ["Signal Strength", None, "signal"], SensorEntityDescription(
"temperature": ["Temperature", TEMP_CELSIUS, "thermometer"], key="total_cameras",
"humidity": ["Humidity", PERCENTAGE, "water-percent"], name="Arlo Cameras",
"air_quality": ["Air Quality", CONCENTRATION_PARTS_PER_MILLION, "biohazard"], icon="mdi:video",
} ),
SensorEntityDescription(
key="captured_today",
name="Captured Today",
icon="mdi:file-video",
),
SensorEntityDescription(
key="battery_level",
name="Battery Level",
unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_BATTERY,
),
SensorEntityDescription(
key="signal_strength",
name="Signal Strength",
icon="mdi:signal",
),
SensorEntityDescription(
key="temperature",
name="Temperature",
unit_of_measurement=TEMP_CELSIUS,
device_class=DEVICE_CLASS_TEMPERATURE,
),
SensorEntityDescription(
key="humidity",
name="Humidity",
unit_of_measurement=PERCENTAGE,
device_class=DEVICE_CLASS_HUMIDITY,
),
SensorEntityDescription(
key="air_quality",
name="Air Quality",
unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
icon="mdi:biohazard",
),
)
SENSOR_KEYS = [desc.key for desc in SENSOR_TYPES]
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{ {
vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): vol.All( vol.Required(CONF_MONITORED_CONDITIONS, default=SENSOR_KEYS): vol.All(
cv.ensure_list, [vol.In(SENSOR_TYPES)] cv.ensure_list, [vol.In(SENSOR_KEYS)]
) )
} }
) )
@@ -50,24 +95,27 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
return return
sensors = [] sensors = []
for sensor_type in config[CONF_MONITORED_CONDITIONS]: for sensor_original in SENSOR_TYPES:
if sensor_type == "total_cameras": if sensor_original.key not in config[CONF_MONITORED_CONDITIONS]:
sensors.append(ArloSensor(SENSOR_TYPES[sensor_type][0], arlo, sensor_type)) continue
sensor_entry = replace(sensor_original)
if sensor_entry.key == "total_cameras":
sensors.append(ArloSensor(arlo, sensor_entry))
else: else:
for camera in arlo.cameras: for camera in arlo.cameras:
if sensor_type in ("temperature", "humidity", "air_quality"): if sensor_entry.key in ("temperature", "humidity", "air_quality"):
continue continue
name = f"{SENSOR_TYPES[sensor_type][0]} {camera.name}" sensor_entry.name = f"{sensor_entry.name} {camera.name}"
sensors.append(ArloSensor(name, camera, sensor_type)) sensors.append(ArloSensor(camera, sensor_entry))
for base_station in arlo.base_stations: for base_station in arlo.base_stations:
if ( if (
sensor_type in ("temperature", "humidity", "air_quality") sensor_entry.key in ("temperature", "humidity", "air_quality")
and base_station.model_id == "ABC1000" and base_station.model_id == "ABC1000"
): ):
name = f"{SENSOR_TYPES[sensor_type][0]} {base_station.name}" sensor_entry.name = f"{sensor_entry.name} {base_station.name}"
sensors.append(ArloSensor(name, base_station, sensor_type)) sensors.append(ArloSensor(base_station, sensor_entry))
add_entities(sensors, True) add_entities(sensors, True)
@@ -75,19 +123,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class ArloSensor(SensorEntity): class ArloSensor(SensorEntity):
"""An implementation of a Netgear Arlo IP sensor.""" """An implementation of a Netgear Arlo IP sensor."""
def __init__(self, name, device, sensor_type): def __init__(self, device, sensor_entry):
"""Initialize an Arlo sensor.""" """Initialize an Arlo sensor."""
_LOGGER.debug("ArloSensor created for %s", name) self.entity_description = sensor_entry
self._name = name
self._data = device self._data = device
self._sensor_type = sensor_type
self._state = None self._state = None
self._icon = f"mdi:{SENSOR_TYPES.get(self._sensor_type)[2]}"
@property
def name(self):
"""Return the name of this camera."""
return self._name
async def async_added_to_hass(self): async def async_added_to_hass(self):
"""Register callbacks.""" """Register callbacks."""
@@ -103,43 +143,29 @@ class ArloSensor(SensorEntity):
self.async_schedule_update_ha_state(True) self.async_schedule_update_ha_state(True)
@property @property
def state(self): def native_value(self):
"""Return the state of the sensor.""" """Return the state of the sensor."""
return self._state return self._state
@property @property
def icon(self): def icon(self):
"""Icon to use in the frontend, if any.""" """Icon to use in the frontend, if any."""
if self._sensor_type == "battery_level" and self._state is not None: if self.entity_description.key == "battery_level" and self._state is not None:
return icon_for_battery_level( return icon_for_battery_level(
battery_level=int(self._state), charging=False battery_level=int(self._state), charging=False
) )
return self._icon return self.entity_description.icon
@property
def unit_of_measurement(self):
"""Return the units of measurement."""
return SENSOR_TYPES.get(self._sensor_type)[1]
@property
def device_class(self):
"""Return the device class of the sensor."""
if self._sensor_type == "temperature":
return DEVICE_CLASS_TEMPERATURE
if self._sensor_type == "humidity":
return DEVICE_CLASS_HUMIDITY
return None
def update(self): def update(self):
"""Get the latest data and updates the state.""" """Get the latest data and updates the state."""
_LOGGER.debug("Updating Arlo sensor %s", self.name) _LOGGER.debug("Updating Arlo sensor %s", self.name)
if self._sensor_type == "total_cameras": if self.entity_description.key == "total_cameras":
self._state = len(self._data.cameras) self._state = len(self._data.cameras)
elif self._sensor_type == "captured_today": elif self.entity_description.key == "captured_today":
self._state = len(self._data.captured_today) self._state = len(self._data.captured_today)
elif self._sensor_type == "last_capture": elif self.entity_description.key == "last_capture":
try: try:
video = self._data.last_video video = self._data.last_video
self._state = video.created_at_pretty("%m-%d-%Y %H:%M:%S") self._state = video.created_at_pretty("%m-%d-%Y %H:%M:%S")
@@ -151,31 +177,31 @@ class ArloSensor(SensorEntity):
_LOGGER.debug(error_msg) _LOGGER.debug(error_msg)
self._state = None self._state = None
elif self._sensor_type == "battery_level": elif self.entity_description.key == "battery_level":
try: try:
self._state = self._data.battery_level self._state = self._data.battery_level
except TypeError: except TypeError:
self._state = None self._state = None
elif self._sensor_type == "signal_strength": elif self.entity_description.key == "signal_strength":
try: try:
self._state = self._data.signal_strength self._state = self._data.signal_strength
except TypeError: except TypeError:
self._state = None self._state = None
elif self._sensor_type == "temperature": elif self.entity_description.key == "temperature":
try: try:
self._state = self._data.ambient_temperature self._state = self._data.ambient_temperature
except TypeError: except TypeError:
self._state = None self._state = None
elif self._sensor_type == "humidity": elif self.entity_description.key == "humidity":
try: try:
self._state = self._data.ambient_humidity self._state = self._data.ambient_humidity
except TypeError: except TypeError:
self._state = None self._state = None
elif self._sensor_type == "air_quality": elif self.entity_description.key == "air_quality":
try: try:
self._state = self._data.ambient_air_quality self._state = self._data.ambient_air_quality
except TypeError: except TypeError:
@@ -189,7 +215,7 @@ class ArloSensor(SensorEntity):
attrs[ATTR_ATTRIBUTION] = ATTRIBUTION attrs[ATTR_ATTRIBUTION] = ATTRIBUTION
attrs["brand"] = DEFAULT_BRAND attrs["brand"] = DEFAULT_BRAND
if self._sensor_type != "total_cameras": if self.entity_description.key != "total_cameras":
attrs["model"] = self._data.model_id attrs["model"] = self._data.model_id
return attrs return attrs

View File

@@ -138,7 +138,7 @@ class ArwnSensor(SensorEntity):
# This mqtt topic for the sensor which is its uid # This mqtt topic for the sensor which is its uid
self._attr_unique_id = topic self._attr_unique_id = topic
self._state_key = state_key self._state_key = state_key
self._attr_unit_of_measurement = units self._attr_native_unit_of_measurement = units
self._attr_icon = icon self._attr_icon = icon
self._attr_device_class = device_class self._attr_device_class = device_class
@@ -147,5 +147,5 @@ class ArwnSensor(SensorEntity):
ev = {} ev = {}
ev.update(event) ev.update(event)
self._attr_extra_state_attributes = ev self._attr_extra_state_attributes = ev
self._attr_state = ev.get(self._state_key, None) self._attr_native_value = ev.get(self._state_key, None)
self.async_write_ha_state() self.async_write_ha_state()

View File

@@ -60,6 +60,12 @@ class AsusWrtDevice(ScannerEntity):
self._device = device self._device = device
self._attr_unique_id = device.mac self._attr_unique_id = device.mac
self._attr_name = device.name or DEFAULT_DEVICE_NAME self._attr_name = device.name or DEFAULT_DEVICE_NAME
self._attr_device_info = {
"connections": {(CONNECTION_NETWORK_MAC, device.mac)},
"default_model": "ASUSWRT Tracked device",
}
if device.name:
self._attr_device_info["default_name"] = device.name
@property @property
def is_connected(self): def is_connected(self):
@@ -90,11 +96,6 @@ class AsusWrtDevice(ScannerEntity):
def async_on_demand_update(self): def async_on_demand_update(self):
"""Update state.""" """Update state."""
self._device = self._router.devices[self._device.mac] self._device = self._router.devices[self._device.mac]
self._attr_device_info = {
"connections": {(CONNECTION_NETWORK_MAC, self._device.mac)},
}
if self._device.name:
self._attr_device_info["default_name"] = self._device.name
self._attr_extra_state_attributes = {} self._attr_extra_state_attributes = {}
if self._device.last_activity: if self._device.last_activity:
self._attr_extra_state_attributes[ self._attr_extra_state_attributes[

View File

@@ -1,15 +1,19 @@
"""Asuswrt status sensors.""" """Asuswrt status sensors."""
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass
import logging import logging
from numbers import Number from numbers import Number
from typing import Any
from homeassistant.components.sensor import SensorEntity from homeassistant.components.sensor import (
STATE_CLASS_MEASUREMENT,
STATE_CLASS_TOTAL_INCREASING,
SensorEntity,
SensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import DATA_GIGABYTES, DATA_RATE_MEGABITS_PER_SECOND from homeassistant.const import DATA_GIGABYTES, DATA_RATE_MEGABITS_PER_SECOND
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import ( from homeassistant.helpers.update_coordinator import (
CoordinatorEntity, CoordinatorEntity,
DataUpdateCoordinator, DataUpdateCoordinator,
@@ -25,62 +29,90 @@ from .const import (
) )
from .router import KEY_COORDINATOR, KEY_SENSORS, AsusWrtRouter from .router import KEY_COORDINATOR, KEY_SENSORS, AsusWrtRouter
@dataclass
class AsusWrtSensorEntityDescription(SensorEntityDescription):
"""A class that describes AsusWrt sensor entities."""
factor: int | None = None
precision: int = 2
DEFAULT_PREFIX = "Asuswrt" DEFAULT_PREFIX = "Asuswrt"
SENSOR_DEVICE_CLASS = "device_class"
SENSOR_ICON = "icon"
SENSOR_NAME = "name"
SENSOR_UNIT = "unit"
SENSOR_FACTOR = "factor"
SENSOR_DEFAULT_ENABLED = "default_enabled"
UNIT_DEVICES = "Devices" UNIT_DEVICES = "Devices"
CONNECTION_SENSORS = { CONNECTION_SENSORS: tuple[AsusWrtSensorEntityDescription, ...] = (
SENSORS_CONNECTED_DEVICE[0]: { AsusWrtSensorEntityDescription(
SENSOR_NAME: "Devices Connected", key=SENSORS_CONNECTED_DEVICE[0],
SENSOR_UNIT: UNIT_DEVICES, name="Devices Connected",
SENSOR_FACTOR: 0, icon="mdi:router-network",
SENSOR_ICON: "mdi:router-network", state_class=STATE_CLASS_MEASUREMENT,
SENSOR_DEFAULT_ENABLED: True, native_unit_of_measurement=UNIT_DEVICES,
}, ),
SENSORS_RATES[0]: { AsusWrtSensorEntityDescription(
SENSOR_NAME: "Download Speed", key=SENSORS_RATES[0],
SENSOR_UNIT: DATA_RATE_MEGABITS_PER_SECOND, name="Download Speed",
SENSOR_FACTOR: 125000, icon="mdi:download-network",
SENSOR_ICON: "mdi:download-network", state_class=STATE_CLASS_MEASUREMENT,
}, native_unit_of_measurement=DATA_RATE_MEGABITS_PER_SECOND,
SENSORS_RATES[1]: { entity_registry_enabled_default=False,
SENSOR_NAME: "Upload Speed", factor=125000,
SENSOR_UNIT: DATA_RATE_MEGABITS_PER_SECOND, ),
SENSOR_FACTOR: 125000, AsusWrtSensorEntityDescription(
SENSOR_ICON: "mdi:upload-network", key=SENSORS_RATES[1],
}, name="Upload Speed",
SENSORS_BYTES[0]: { icon="mdi:upload-network",
SENSOR_NAME: "Download", state_class=STATE_CLASS_MEASUREMENT,
SENSOR_UNIT: DATA_GIGABYTES, native_unit_of_measurement=DATA_RATE_MEGABITS_PER_SECOND,
SENSOR_FACTOR: 1000000000, entity_registry_enabled_default=False,
SENSOR_ICON: "mdi:download", factor=125000,
}, ),
SENSORS_BYTES[1]: { AsusWrtSensorEntityDescription(
SENSOR_NAME: "Upload", key=SENSORS_BYTES[0],
SENSOR_UNIT: DATA_GIGABYTES, name="Download",
SENSOR_FACTOR: 1000000000, icon="mdi:download",
SENSOR_ICON: "mdi:upload", state_class=STATE_CLASS_TOTAL_INCREASING,
}, native_unit_of_measurement=DATA_GIGABYTES,
SENSORS_LOAD_AVG[0]: { entity_registry_enabled_default=False,
SENSOR_NAME: "Load Avg (1m)", factor=1000000000,
SENSOR_ICON: "mdi:cpu-32-bit", ),
}, AsusWrtSensorEntityDescription(
SENSORS_LOAD_AVG[1]: { key=SENSORS_BYTES[1],
SENSOR_NAME: "Load Avg (5m)", name="Upload",
SENSOR_ICON: "mdi:cpu-32-bit", icon="mdi:upload",
}, state_class=STATE_CLASS_TOTAL_INCREASING,
SENSORS_LOAD_AVG[2]: { native_unit_of_measurement=DATA_GIGABYTES,
SENSOR_NAME: "Load Avg (15m)", entity_registry_enabled_default=False,
SENSOR_ICON: "mdi:cpu-32-bit", factor=1000000000,
}, ),
} AsusWrtSensorEntityDescription(
key=SENSORS_LOAD_AVG[0],
name="Load Avg (1m)",
icon="mdi:cpu-32-bit",
state_class=STATE_CLASS_MEASUREMENT,
entity_registry_enabled_default=False,
factor=1,
precision=1,
),
AsusWrtSensorEntityDescription(
key=SENSORS_LOAD_AVG[1],
name="Load Avg (5m)",
icon="mdi:cpu-32-bit",
state_class=STATE_CLASS_MEASUREMENT,
entity_registry_enabled_default=False,
factor=1,
precision=1,
),
AsusWrtSensorEntityDescription(
key=SENSORS_LOAD_AVG[2],
name="Load Avg (15m)",
icon="mdi:cpu-32-bit",
state_class=STATE_CLASS_MEASUREMENT,
entity_registry_enabled_default=False,
factor=1,
precision=1,
),
)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -95,13 +127,13 @@ async def async_setup_entry(
for sensor_data in router.sensors_coordinator.values(): for sensor_data in router.sensors_coordinator.values():
coordinator = sensor_data[KEY_COORDINATOR] coordinator = sensor_data[KEY_COORDINATOR]
sensors = sensor_data[KEY_SENSORS] sensors = sensor_data[KEY_SENSORS]
for sensor_key in sensors: entities.extend(
if sensor_key in CONNECTION_SENSORS: [
entities.append( AsusWrtSensor(coordinator, router, sensor_descr)
AsusWrtSensor( for sensor_descr in CONNECTION_SENSORS
coordinator, router, sensor_key, CONNECTION_SENSORS[sensor_key] if sensor_descr.key in sensors
) ]
) )
async_add_entities(entities, True) async_add_entities(entities, True)
@@ -113,39 +145,23 @@ class AsusWrtSensor(CoordinatorEntity, SensorEntity):
self, self,
coordinator: DataUpdateCoordinator, coordinator: DataUpdateCoordinator,
router: AsusWrtRouter, router: AsusWrtRouter,
sensor_type: str, description: AsusWrtSensorEntityDescription,
sensor_def: dict[str, Any],
) -> None: ) -> None:
"""Initialize a AsusWrt sensor.""" """Initialize a AsusWrt sensor."""
super().__init__(coordinator) super().__init__(coordinator)
self._router = router self.entity_description = description
self._sensor_type = sensor_type
self._attr_name = f"{DEFAULT_PREFIX} {sensor_def[SENSOR_NAME]}" self._attr_name = f"{DEFAULT_PREFIX} {description.name}"
self._factor = sensor_def.get(SENSOR_FACTOR)
self._attr_unique_id = f"{DOMAIN} {self.name}" self._attr_unique_id = f"{DOMAIN} {self.name}"
self._attr_entity_registry_enabled_default = sensor_def.get( self._attr_device_info = router.device_info
SENSOR_DEFAULT_ENABLED, False self._attr_extra_state_attributes = {"hostname": router.host}
)
self._attr_unit_of_measurement = sensor_def.get(SENSOR_UNIT)
self._attr_icon = sensor_def.get(SENSOR_ICON)
self._attr_device_class = sensor_def.get(SENSOR_DEVICE_CLASS)
@property @property
def state(self) -> str: def native_value(self) -> str | None:
"""Return current state.""" """Return current state."""
state = self.coordinator.data.get(self._sensor_type) descr = self.entity_description
if state is None: state = self.coordinator.data.get(descr.key)
return None if state is not None:
if self._factor and isinstance(state, Number): if descr.factor and isinstance(state, Number):
return round(state / self._factor, 2) return round(state / descr.factor, descr.precision)
return state return state
@property
def extra_state_attributes(self) -> dict[str, Any]:
"""Return the attributes."""
return {"hostname": self._router.host}
@property
def device_info(self) -> DeviceInfo:
"""Return the device information."""
return self._router.device_info

View File

@@ -0,0 +1,44 @@
{
"config": {
"abort": {
"single_instance_allowed": "\u5df2\u7ecf\u914d\u7f6e\u8fc7\u4e86\uff0c\u4e14\u53ea\u80fd\u914d\u7f6e\u4e00\u6b21\u3002"
},
"error": {
"cannot_connect": "\u8fde\u63a5\u5931\u8d25",
"invalid_host": "\u65e0\u6548\u7684\u4e3b\u673a\u5730\u5740\u6216 IP \u5730\u5740",
"pwd_and_ssh": "\u53ea\u63d0\u4f9b\u5bc6\u7801\u6216 SSH \u5bc6\u94a5\u6587\u4ef6",
"pwd_or_ssh": "\u8bf7\u63d0\u4f9b\u5bc6\u7801\u6216 SSH \u5bc6\u94a5\u6587\u4ef6",
"ssh_not_file": "\u672a\u627e\u5230 SSH \u5bc6\u94a5\u6587\u4ef6",
"unknown": "\u672a\u77e5\u9519\u8bef"
},
"step": {
"user": {
"data": {
"host": "\u4e3b\u673a\u5730\u5740",
"mode": "\u4f7f\u7528\u6a21\u5f0f",
"name": "\u540d\u79f0",
"password": "\u5bc6\u7801",
"port": "\u7aef\u53e3",
"protocol": "\u901a\u4fe1\u534f\u8bae",
"ssh_key": "SSH \u5bc6\u94a5\u6587\u4ef6\u8def\u5f84 (\u4e0d\u662f\u5bc6\u7801)",
"username": "\u7528\u6237\u540d"
},
"description": "\u8bbe\u7f6e\u8fde\u63a5\u5230\u8def\u7531\u5668\u6240\u9700\u7684\u53c2\u6570",
"title": "AsusWRT"
}
}
},
"options": {
"step": {
"init": {
"data": {
"consider_home": "\u7b49\u5f85\u591a\u5c11\u79d2\u540e\u5219\u5224\u5b9a\u8bbe\u5907\u79bb\u5f00",
"dnsmasq": "\u8def\u7531\u5668\u4e2d\u7684 dnsmasq.leases \u6587\u4ef6\u4f4d\u7f6e",
"interface": "\u60f3\u8981\u76d1\u6d4b\u7684\u7aef\u53e3(\u4f8b\u5982: eth0,eth1 \u7b49)",
"require_ip": "\u8bbe\u5907\u5fc5\u987b\u5177\u6709 IP (\u7528\u4e8e\u63a5\u5165\u70b9\u6a21\u5f0f)"
},
"title": "AsusWRT \u9009\u9879"
}
}
}
}

View File

@@ -49,10 +49,12 @@ class AtagSensor(AtagEntity, SensorEntity):
PERCENTAGE, PERCENTAGE,
TIME_HOURS, TIME_HOURS,
): ):
self._attr_unit_of_measurement = coordinator.data.report[self._id].measure self._attr_native_unit_of_measurement = coordinator.data.report[
self._id
].measure
@property @property
def state(self): def native_value(self):
"""Return the state of the sensor.""" """Return the state of the sensor."""
return self.coordinator.data.report[self._id].state return self.coordinator.data.report[self._id].state

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