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

pull/54106/head
farmio 2021-08-19 12:13:53 +02:00
commit 1d616e920d
1412 changed files with 25132 additions and 11045 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -91,13 +91,13 @@ class AdsCover(AdsEntity, CoverEntity):
):
"""Initialize AdsCover entity."""
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:
self._unique_id = ads_var_position
self._attr_unique_id = ads_var_position
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:
self._unique_id = ads_var_open
self._attr_unique_id = ads_var_open
self._state_dict[STATE_KEY_POSITION] = None
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):
"""Initialize AdsSensor entity."""
super().__init__(ads_hub, name, ads_var)
self._attr_unit_of_measurement = unit_of_measurement
self._attr_native_unit_of_measurement = unit_of_measurement
self._ads_type = ads_type
self._factor = factor
@ -64,6 +64,6 @@ class AdsSensor(AdsEntity, SensorEntity):
)
@property
def state(self) -> StateType:
def native_value(self) -> StateType:
"""Return the state of the device."""
return self._state_dict[STATE_KEY_STATE]

View File

@ -15,7 +15,6 @@ from homeassistant.components.climate.const import (
SUPPORT_TARGET_TEMPERATURE,
)
from homeassistant.const import ATTR_TEMPERATURE, PRECISION_WHOLE, TEMP_CELSIUS
from homeassistant.core import callback
from homeassistant.helpers import entity_platform
from .const import (
@ -166,19 +165,22 @@ class AdvantageAirZone(AdvantageAirClimateEntity):
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}'
)
async def async_added_to_hass(self):
"""When entity is added to hass."""
self.async_on_remove(self.coordinator.async_add_listener(self._update_callback))
@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
@property
def hvac_mode(self):
"""Return the current state as HVAC mode."""
if self._zone["state"] == ADVANTAGE_AIR_STATE_OPEN:
self._attr_hvac_mode = HVAC_MODE_FAN_ONLY
self.async_write_ha_state()
return HVAC_MODE_FAN_ONLY
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):
"""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):
"""Representation of Advantage Air timer control."""
_attr_unit_of_measurement = ADVANTAGE_AIR_SET_COUNTDOWN_UNIT
_attr_native_unit_of_measurement = ADVANTAGE_AIR_SET_COUNTDOWN_UNIT
def __init__(self, instance, ac_key, action):
"""Initialize the Advantage Air timer control."""
@ -58,7 +58,7 @@ class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
)
@property
def state(self):
def native_value(self):
"""Return the current value."""
return self._ac[self._time_key]
@ -78,7 +78,7 @@ class AdvantageAirTimeTo(AdvantageAirEntity, SensorEntity):
class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
"""Representation of Advantage Air Zone Vent Sensor."""
_attr_unit_of_measurement = PERCENTAGE
_attr_native_unit_of_measurement = PERCENTAGE
_attr_state_class = STATE_CLASS_MEASUREMENT
def __init__(self, instance, ac_key, zone_key):
@ -90,7 +90,7 @@ class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
)
@property
def state(self):
def native_value(self):
"""Return the current value of the air vent."""
if self._zone["state"] == ADVANTAGE_AIR_STATE_OPEN:
return self._zone["value"]
@ -107,19 +107,19 @@ class AdvantageAirZoneVent(AdvantageAirEntity, SensorEntity):
class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
"""Representation of Advantage Air Zone wireless signal sensor."""
_attr_unit_of_measurement = PERCENTAGE
_attr_native_unit_of_measurement = PERCENTAGE
_attr_state_class = STATE_CLASS_MEASUREMENT
def __init__(self, instance, ac_key, zone_key):
"""Initialize an Advantage Air Zone wireless signal sensor."""
super().__init__(instance, ac_key, zone_key=zone_key)
super().__init__(instance, ac_key, zone_key)
self._attr_name = f'{self._zone["name"]} Signal'
self._attr_unique_id = (
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-signal'
)
@property
def state(self):
def native_value(self):
"""Return the current value of the wireless signal."""
return self._zone["rssi"]
@ -140,7 +140,7 @@ class AdvantageAirZoneSignal(AdvantageAirEntity, SensorEntity):
class AdvantageAirZoneTemp(AdvantageAirEntity, SensorEntity):
"""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_icon = "mdi:thermometer"
_attr_entity_registry_enabled_default = False
@ -149,9 +149,11 @@ class AdvantageAirZoneTemp(AdvantageAirEntity, SensorEntity):
"""Initialize an Advantage Air Zone Temp Sensor."""
super().__init__(instance, ac_key, zone_key)
self._attr_name = f'{self._zone["name"]} Temperature'
self._attr_unique_id = f'{self.coordinator.data["system"]["rid"]}-{self.ac_key}-{self.zone_key}-temp'
self._attr_unique_id = (
f'{self.coordinator.data["system"]["rid"]}-{ac_key}-{zone_key}-temp'
)
@property
def state(self):
def native_value(self):
"""Return the current value of the measured temperature."""
return self._zone["measuredTemp"]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -49,7 +49,10 @@ def setup_platform(
try:
if not acc.login():
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:
_LOGGER.error("%s", ex)
hass.components.persistent_notification.create(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -154,8 +154,6 @@ class AmbiclimateEntity(ClimateEntity):
"name": self.name,
"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:
"""Set new target temperature."""
@ -184,6 +182,8 @@ class AmbiclimateEntity(ClimateEntity):
await self._store.async_save(token_info)
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_current_temperature = data.get("temperature")
self._attr_current_humidity = data.get("humidity")

View File

@ -1,11 +1,22 @@
{
"config": {
"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",
"missing_configuration": "A komponens nincs konfigur\u00e1lva. K\u00e9rlek, k\u00f6vesd a dokument\u00e1ci\u00f3t."
},
"create_entry": {
"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",
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/ambient_station",
"requirements": ["aioambient==1.2.5"],
"requirements": ["aioambient==1.2.6"],
"codeowners": ["@bachya"],
"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
)
self._attr_unit_of_measurement = unit
self._attr_native_unit_of_measurement = unit
@callback
def update_from_latest_data(self) -> None:
@ -75,10 +75,10 @@ class AmbientWeatherSensor(AmbientWeatherEntity, SensorEntity):
].get(TYPE_SOLARRADIATION)
if w_m2_brightness_val is None:
self._attr_state = None
self._attr_native_value = None
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:
self._attr_state = self._ambient.stations[self._mac_address][
self._attr_native_value = self._ambient.stations[self._mac_address][
ATTR_LAST_DATA
].get(self._sensor_type)

View File

@ -53,7 +53,7 @@ _CROSSLINE_DETECTED_PARAMS = (
DEVICE_CLASS_MOTION,
"CrossLineDetection",
)
BINARY_SENSORS = {
RAW_BINARY_SENSORS = {
BINARY_SENSOR_AUDIO_DETECTED: _AUDIO_DETECTED_PARAMS,
BINARY_SENSOR_AUDIO_DETECTED_POLLED: _AUDIO_DETECTED_PARAMS,
BINARY_SENSOR_MOTION_DETECTED: _MOTION_DETECTED_PARAMS,
@ -64,7 +64,7 @@ BINARY_SENSORS = {
}
BINARY_SENSORS = {
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 = [
{BINARY_SENSOR_MOTION_DETECTED, BINARY_SENSOR_MOTION_DETECTED_POLLED},

View File

@ -1,4 +1,6 @@
"""Support for Amcrest IP cameras."""
from __future__ import annotations
import asyncio
from datetime import timedelta
from functools import partial
@ -181,7 +183,9 @@ class AmcrestCam(Camera):
finally:
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."""
_LOGGER.debug("Take snapshot from %s", self._name)
try:

View File

@ -61,7 +61,7 @@ class AmcrestSensor(SensorEntity):
return self._name
@property
def state(self):
def native_value(self):
"""Return the state of the sensor."""
return self._state
@ -76,7 +76,7 @@ class AmcrestSensor(SensorEntity):
return self._icon
@property
def unit_of_measurement(self):
def native_unit_of_measurement(self):
"""Return the units 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.core import HomeAssistant
from homeassistant.helpers.event import async_call_later, async_track_time_interval
from homeassistant.helpers.typing import ConfigType
from .analytics import Analytics
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."""
analytics = Analytics(hass)

View File

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

View File

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

View File

@ -449,6 +449,11 @@ class ADBDevice(MediaPlayerEntity):
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()
async def _adb_screencap(self):
"""Take a screen capture from the device."""
@ -458,9 +463,6 @@ class ADBDevice(MediaPlayerEntity):
"""Fetch current playing image."""
if not self._screencap or self.state in (STATE_OFF, None) or not self.available:
return None, None
self._attr_media_image_hash = (
f"{datetime.now().timestamp()}" if self._screencap else None
)
media_data = await self._adb_screencap()
if media_data:

View File

@ -165,16 +165,16 @@ class APCUPSdSensor(SensorEntity):
self.type = sensor_type
self._attr_name = SENSOR_PREFIX + SENSOR_TYPES[sensor_type][0]
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]
def update(self):
"""Get the latest status and use it to update our sensor state."""
if self.type.upper() not in self._data.status:
self._attr_state = None
self._attr_native_value = None
else:
self._attr_state, inferred_unit = infer_unit(
self._attr_native_value, inferred_unit = infer_unit(
self._data.status[self.type.upper()]
)
if not self._attr_unit_of_measurement:
self._attr_unit_of_measurement = inferred_unit
if not self._attr_native_unit_of_measurement:
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__)
ATTR_BASE_URL = "base_url"
ATTR_CURRENCY = "currency"
ATTR_EXTERNAL_URL = "external_url"
ATTR_INTERNAL_URL = "internal_url"
ATTR_LOCATION_NAME = "location_name"
@ -196,7 +195,6 @@ class APIDiscoveryView(HomeAssistantView):
# always needs authentication
ATTR_REQUIRES_API_PASSWORD: True,
ATTR_VERSION: __version__,
ATTR_CURRENCY: None,
}
with suppress(NoURLAvailableError):

View File

@ -92,10 +92,11 @@ class AquaLogicSensor(SensorEntity):
panel = self._processor.panel
if panel is not None:
if panel.is_metric:
self._attr_unit_of_measurement = SENSOR_TYPES[self._type][1][0]
self._attr_state = getattr(panel, self._type)
self.async_write_ha_state()
self._attr_native_unit_of_measurement = SENSOR_TYPES[self._type][1][0]
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:
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
async def async_setup(hass: HomeAssistant, config: ConfigType):
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the component."""
hass.data[DOMAIN_DATA_ENTRIES] = {}
hass.data[DOMAIN_DATA_TASKS] = {}

View File

@ -5,14 +5,27 @@
"already_in_progress": "A konfigur\u00e1ci\u00f3 m\u00e1r folyamatban van.",
"cannot_connect": "Sikertelen csatlakoz\u00e1s"
},
"error": {
"one": "\u00dcres",
"other": "\u00dcres"
},
"flow_title": "{host}",
"step": {
"confirm": {
"description": "Hozz\u00e1 szeretn\u00e9 adni az Arcam FMJ \"{host}\" eszk\u00f6zt a HomeAssistanthoz?"
},
"user": {
"data": {
"host": "Hoszt",
"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):
"""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._attr_name = f"{location.title()} {name.title()}"
self._variable = variable
self._attr_unit_of_measurement = unit_of_measurement
self._attr_native_unit_of_measurement = unit_of_measurement
self._renderer = renderer
if pin is not None:
@ -155,9 +155,9 @@ class ArestSensor(SensorEntity):
self._attr_available = self.arest.available
values = self.arest.data
if "error" in values:
self._attr_state = values["error"]
self._attr_native_value = values["error"]
else:
self._attr_state = self._renderer(
self._attr_native_value = self._renderer(
values.get("value", values.get(self._variable, None))
)

View File

@ -1,4 +1,6 @@
"""Support for Netgear Arlo IP cameras."""
from __future__ import annotations
import logging
from haffmpeg.camera import CameraMjpeg
@ -62,7 +64,9 @@ class ArloCam(Camera):
self._last_refresh = None
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 self._camera.last_image_from_cache

View File

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

View File

@ -138,7 +138,7 @@ class ArwnSensor(SensorEntity):
# This mqtt topic for the sensor which is its uid
self._attr_unique_id = topic
self._state_key = state_key
self._attr_unit_of_measurement = units
self._attr_native_unit_of_measurement = units
self._attr_icon = icon
self._attr_device_class = device_class
@ -147,5 +147,5 @@ class ArwnSensor(SensorEntity):
ev = {}
ev.update(event)
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()

View File

@ -60,6 +60,12 @@ class AsusWrtDevice(ScannerEntity):
self._device = device
self._attr_unique_id = device.mac
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
def is_connected(self):
@ -90,11 +96,6 @@ class AsusWrtDevice(ScannerEntity):
def async_on_demand_update(self):
"""Update state."""
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 = {}
if self._device.last_activity:
self._attr_extra_state_attributes[

View File

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

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