mirror of
https://github.com/home-assistant/core.git
synced 2026-03-22 02:35:12 +01:00
Compare commits
6 Commits
matter_syn
...
knx-defaul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1e876c795 | ||
|
|
e7e388b578 | ||
|
|
c27144c810 | ||
|
|
eef3472c43 | ||
|
|
f9bd9f4982 | ||
|
|
e4620a208d |
44
homeassistant/components/google_weather/diagnostics.py
Normal file
44
homeassistant/components/google_weather/diagnostics.py
Normal file
@@ -0,0 +1,44 @@
|
||||
"""Diagnostics support for Google Weather."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.diagnostics import async_redact_data
|
||||
from homeassistant.const import CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from .const import CONF_REFERRER
|
||||
from .coordinator import GoogleWeatherConfigEntry
|
||||
|
||||
TO_REDACT = {
|
||||
CONF_API_KEY,
|
||||
CONF_REFERRER,
|
||||
CONF_LATITUDE,
|
||||
CONF_LONGITUDE,
|
||||
}
|
||||
|
||||
|
||||
async def async_get_config_entry_diagnostics(
|
||||
hass: HomeAssistant, entry: GoogleWeatherConfigEntry
|
||||
) -> dict[str, Any]:
|
||||
"""Return diagnostics for a config entry."""
|
||||
diag_data: dict[str, Any] = {
|
||||
"entry": entry.as_dict(),
|
||||
"subentries": {},
|
||||
}
|
||||
|
||||
for subentry_id, subentry_rt in entry.runtime_data.subentries_runtime_data.items():
|
||||
diag_data["subentries"][subentry_id] = {
|
||||
"observation_data": subentry_rt.coordinator_observation.data.to_dict()
|
||||
if subentry_rt.coordinator_observation.data
|
||||
else None,
|
||||
"daily_forecast_data": subentry_rt.coordinator_daily_forecast.data.to_dict()
|
||||
if subentry_rt.coordinator_daily_forecast.data
|
||||
else None,
|
||||
"hourly_forecast_data": subentry_rt.coordinator_hourly_forecast.data.to_dict()
|
||||
if subentry_rt.coordinator_hourly_forecast.data
|
||||
else None,
|
||||
}
|
||||
|
||||
return async_redact_data(diag_data, TO_REDACT)
|
||||
@@ -43,7 +43,7 @@ rules:
|
||||
|
||||
# Gold
|
||||
devices: done
|
||||
diagnostics: todo
|
||||
diagnostics: done
|
||||
discovery-update-info:
|
||||
status: exempt
|
||||
comment: No discovery.
|
||||
|
||||
@@ -7,10 +7,9 @@ from typing import Any
|
||||
from xknx.devices import BinarySensor as XknxBinarySensor
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.binary_sensor import BinarySensorEntity
|
||||
from homeassistant.components.binary_sensor import ENTITY_ID_FORMAT, BinarySensorEntity
|
||||
from homeassistant.const import (
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_NAME,
|
||||
STATE_ON,
|
||||
STATE_UNAVAILABLE,
|
||||
@@ -81,6 +80,7 @@ class _KnxBinarySensor(BinarySensorEntity, RestoreEntity):
|
||||
"""Representation of a KNX binary sensor."""
|
||||
|
||||
_device: XknxBinarySensor
|
||||
_entity_id_format = ENTITY_ID_FORMAT
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Restore last state."""
|
||||
@@ -114,24 +114,25 @@ class KnxYamlBinarySensor(_KnxBinarySensor, KnxYamlEntity):
|
||||
|
||||
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
|
||||
"""Initialize of KNX binary sensor."""
|
||||
self._device = XknxBinarySensor(
|
||||
xknx=knx_module.xknx,
|
||||
name=config[CONF_NAME],
|
||||
group_address_state=config[CONF_STATE_ADDRESS],
|
||||
invert=config[CONF_INVERT],
|
||||
sync_state=config[CONF_SYNC_STATE],
|
||||
ignore_internal_state=config[CONF_IGNORE_INTERNAL_STATE],
|
||||
context_timeout=config.get(CONF_CONTEXT_TIMEOUT),
|
||||
reset_after=config.get(CONF_RESET_AFTER),
|
||||
always_callback=True,
|
||||
)
|
||||
super().__init__(
|
||||
knx_module=knx_module,
|
||||
device=XknxBinarySensor(
|
||||
xknx=knx_module.xknx,
|
||||
name=config[CONF_NAME],
|
||||
group_address_state=config[CONF_STATE_ADDRESS],
|
||||
invert=config[CONF_INVERT],
|
||||
sync_state=config[CONF_SYNC_STATE],
|
||||
ignore_internal_state=config[CONF_IGNORE_INTERNAL_STATE],
|
||||
context_timeout=config.get(CONF_CONTEXT_TIMEOUT),
|
||||
reset_after=config.get(CONF_RESET_AFTER),
|
||||
always_callback=True,
|
||||
),
|
||||
unique_id=str(self._device.remote_value.group_address_state),
|
||||
entity_config=config,
|
||||
)
|
||||
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
|
||||
|
||||
self._attr_device_class = config.get(CONF_DEVICE_CLASS)
|
||||
self._attr_force_update = self._device.ignore_internal_state
|
||||
self._attr_unique_id = str(self._device.remote_value.group_address_state)
|
||||
|
||||
|
||||
class KnxUiBinarySensor(_KnxBinarySensor, KnxUiEntity):
|
||||
|
||||
@@ -5,8 +5,8 @@ from __future__ import annotations
|
||||
from xknx.devices import RawValue as XknxRawValue
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.button import ButtonEntity
|
||||
from homeassistant.const import CONF_ENTITY_CATEGORY, CONF_NAME, CONF_PAYLOAD, Platform
|
||||
from homeassistant.components.button import ENTITY_ID_FORMAT, ButtonEntity
|
||||
from homeassistant.const import CONF_NAME, CONF_PAYLOAD, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
@@ -32,22 +32,21 @@ class KNXButton(KnxYamlEntity, ButtonEntity):
|
||||
"""Representation of a KNX button."""
|
||||
|
||||
_device: XknxRawValue
|
||||
_entity_id_format = ENTITY_ID_FORMAT
|
||||
|
||||
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
|
||||
"""Initialize a KNX button."""
|
||||
super().__init__(
|
||||
knx_module=knx_module,
|
||||
device=XknxRawValue(
|
||||
xknx=knx_module.xknx,
|
||||
name=config[CONF_NAME],
|
||||
payload_length=config[CONF_PAYLOAD_LENGTH],
|
||||
group_address=config[KNX_ADDRESS],
|
||||
),
|
||||
self._device = XknxRawValue(
|
||||
xknx=knx_module.xknx,
|
||||
name=config[CONF_NAME],
|
||||
payload_length=config[CONF_PAYLOAD_LENGTH],
|
||||
group_address=config[KNX_ADDRESS],
|
||||
)
|
||||
self._payload = config[CONF_PAYLOAD]
|
||||
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
|
||||
self._attr_unique_id = (
|
||||
f"{self._device.remote_value.group_address}_{self._payload}"
|
||||
super().__init__(
|
||||
knx_module=knx_module,
|
||||
unique_id=f"{self._device.remote_value.group_address}_{self._payload}",
|
||||
entity_config=config,
|
||||
)
|
||||
|
||||
async def async_press(self) -> None:
|
||||
|
||||
@@ -16,6 +16,7 @@ from xknx.remote_value.remote_value_setpoint_shift import SetpointShiftMode
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.climate import (
|
||||
ENTITY_ID_FORMAT,
|
||||
FAN_HIGH,
|
||||
FAN_LOW,
|
||||
FAN_MEDIUM,
|
||||
@@ -27,13 +28,7 @@ from homeassistant.components.climate import (
|
||||
HVACAction,
|
||||
HVACMode,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE,
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_NAME,
|
||||
Platform,
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, CONF_NAME, Platform, UnitOfTemperature
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import (
|
||||
AddConfigEntryEntitiesCallback,
|
||||
@@ -119,7 +114,7 @@ async def async_setup_entry(
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
def _create_climate(xknx: XKNX, config: ConfigType) -> XknxClimate:
|
||||
def _create_climate_yaml(xknx: XKNX, config: ConfigType) -> XknxClimate:
|
||||
"""Return a KNX Climate device to be used within XKNX."""
|
||||
climate_mode = XknxClimateMode(
|
||||
xknx,
|
||||
@@ -323,6 +318,7 @@ class _KnxClimate(ClimateEntity, _KnxEntityBase):
|
||||
_device: XknxClimate
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_attr_translation_key = "knx_climate"
|
||||
_entity_id_format = ENTITY_ID_FORMAT
|
||||
|
||||
default_hvac_mode: HVACMode
|
||||
_last_hvac_mode: HVACMode
|
||||
@@ -646,9 +642,16 @@ class KnxYamlClimate(_KnxClimate, KnxYamlEntity):
|
||||
|
||||
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
|
||||
"""Initialize of a KNX climate device."""
|
||||
self._device = _create_climate_yaml(knx_module.xknx, config)
|
||||
super().__init__(
|
||||
knx_module=knx_module,
|
||||
device=_create_climate(knx_module.xknx, config),
|
||||
unique_id=(
|
||||
f"{self._device.temperature.group_address_state}_"
|
||||
f"{self._device.target_temperature.group_address_state}_"
|
||||
f"{self._device.target_temperature.group_address}_"
|
||||
f"{self._device._setpoint_shift.group_address}" # noqa: SLF001
|
||||
),
|
||||
entity_config=config,
|
||||
)
|
||||
default_hvac_mode: HVACMode = config[ClimateConf.DEFAULT_CONTROLLER_MODE]
|
||||
fan_max_step = config[ClimateConf.FAN_MAX_STEP]
|
||||
@@ -660,14 +663,6 @@ class KnxYamlClimate(_KnxClimate, KnxYamlEntity):
|
||||
fan_zero_mode=fan_zero_mode,
|
||||
)
|
||||
|
||||
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
|
||||
self._attr_unique_id = (
|
||||
f"{self._device.temperature.group_address_state}_"
|
||||
f"{self._device.target_temperature.group_address_state}_"
|
||||
f"{self._device.target_temperature.group_address}_"
|
||||
f"{self._device._setpoint_shift.group_address}" # noqa: SLF001
|
||||
)
|
||||
|
||||
|
||||
class KnxUiClimate(_KnxClimate, KnxUiEntity):
|
||||
"""Representation of a KNX climate device configured from the UI."""
|
||||
|
||||
@@ -67,6 +67,7 @@ CONF_KNX_SECURE_USER_PASSWORD: Final = "user_password"
|
||||
CONF_KNX_SECURE_DEVICE_AUTHENTICATION: Final = "device_authentication"
|
||||
|
||||
|
||||
CONF_DEFAULT_ENTITY_ID: Final = "default_entity_id"
|
||||
CONF_CONTEXT_TIMEOUT: Final = "context_timeout"
|
||||
CONF_IGNORE_INTERNAL_STATE: Final = "ignore_internal_state"
|
||||
CONF_PAYLOAD_LENGTH: Final = "payload_length"
|
||||
|
||||
@@ -11,16 +11,12 @@ from homeassistant import config_entries
|
||||
from homeassistant.components.cover import (
|
||||
ATTR_POSITION,
|
||||
ATTR_TILT_POSITION,
|
||||
ENTITY_ID_FORMAT,
|
||||
CoverDeviceClass,
|
||||
CoverEntity,
|
||||
CoverEntityFeature,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_NAME,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import (
|
||||
AddConfigEntryEntitiesCallback,
|
||||
@@ -80,6 +76,7 @@ class _KnxCover(CoverEntity):
|
||||
"""Representation of a KNX cover."""
|
||||
|
||||
_device: XknxCover
|
||||
_entity_id_format = ENTITY_ID_FORMAT
|
||||
|
||||
def init_base(self) -> None:
|
||||
"""Initialize common attributes - may be based on xknx device instance."""
|
||||
@@ -191,36 +188,33 @@ class KnxYamlCover(_KnxCover, KnxYamlEntity):
|
||||
|
||||
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
|
||||
"""Initialize the cover."""
|
||||
self._device = XknxCover(
|
||||
xknx=knx_module.xknx,
|
||||
name=config[CONF_NAME],
|
||||
group_address_long=config.get(CoverSchema.CONF_MOVE_LONG_ADDRESS),
|
||||
group_address_short=config.get(CoverSchema.CONF_MOVE_SHORT_ADDRESS),
|
||||
group_address_stop=config.get(CoverSchema.CONF_STOP_ADDRESS),
|
||||
group_address_position_state=config.get(
|
||||
CoverSchema.CONF_POSITION_STATE_ADDRESS
|
||||
),
|
||||
group_address_angle=config.get(CoverSchema.CONF_ANGLE_ADDRESS),
|
||||
group_address_angle_state=config.get(CoverSchema.CONF_ANGLE_STATE_ADDRESS),
|
||||
group_address_position=config.get(CoverSchema.CONF_POSITION_ADDRESS),
|
||||
travel_time_down=config[CoverConf.TRAVELLING_TIME_DOWN],
|
||||
travel_time_up=config[CoverConf.TRAVELLING_TIME_UP],
|
||||
invert_updown=config[CoverConf.INVERT_UPDOWN],
|
||||
invert_position=config[CoverConf.INVERT_POSITION],
|
||||
invert_angle=config[CoverConf.INVERT_ANGLE],
|
||||
)
|
||||
super().__init__(
|
||||
knx_module=knx_module,
|
||||
device=XknxCover(
|
||||
xknx=knx_module.xknx,
|
||||
name=config[CONF_NAME],
|
||||
group_address_long=config.get(CoverSchema.CONF_MOVE_LONG_ADDRESS),
|
||||
group_address_short=config.get(CoverSchema.CONF_MOVE_SHORT_ADDRESS),
|
||||
group_address_stop=config.get(CoverSchema.CONF_STOP_ADDRESS),
|
||||
group_address_position_state=config.get(
|
||||
CoverSchema.CONF_POSITION_STATE_ADDRESS
|
||||
),
|
||||
group_address_angle=config.get(CoverSchema.CONF_ANGLE_ADDRESS),
|
||||
group_address_angle_state=config.get(
|
||||
CoverSchema.CONF_ANGLE_STATE_ADDRESS
|
||||
),
|
||||
group_address_position=config.get(CoverSchema.CONF_POSITION_ADDRESS),
|
||||
travel_time_down=config[CoverConf.TRAVELLING_TIME_DOWN],
|
||||
travel_time_up=config[CoverConf.TRAVELLING_TIME_UP],
|
||||
invert_updown=config[CoverConf.INVERT_UPDOWN],
|
||||
invert_position=config[CoverConf.INVERT_POSITION],
|
||||
invert_angle=config[CoverConf.INVERT_ANGLE],
|
||||
unique_id=(
|
||||
f"{self._device.updown.group_address}_"
|
||||
f"{self._device.position_target.group_address}"
|
||||
),
|
||||
entity_config=config,
|
||||
)
|
||||
self.init_base()
|
||||
|
||||
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
|
||||
self._attr_unique_id = (
|
||||
f"{self._device.updown.group_address}_"
|
||||
f"{self._device.position_target.group_address}"
|
||||
)
|
||||
if custom_device_class := config.get(CONF_DEVICE_CLASS):
|
||||
self._attr_device_class = custom_device_class
|
||||
|
||||
|
||||
@@ -9,14 +9,8 @@ from xknx.devices import DateDevice as XknxDateDevice
|
||||
from xknx.dpt.dpt_11 import KNXDate as XKNXDate
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.date import DateEntity
|
||||
from homeassistant.const import (
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_NAME,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.components.date import ENTITY_ID_FORMAT, DateEntity
|
||||
from homeassistant.const import CONF_NAME, STATE_UNAVAILABLE, STATE_UNKNOWN, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import (
|
||||
AddConfigEntryEntitiesCallback,
|
||||
@@ -75,6 +69,7 @@ class _KNXDate(DateEntity, RestoreEntity):
|
||||
"""Representation of a KNX date."""
|
||||
|
||||
_device: XknxDateDevice
|
||||
_entity_id_format = ENTITY_ID_FORMAT
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Restore last state."""
|
||||
@@ -105,20 +100,20 @@ class KnxYamlDate(_KNXDate, KnxYamlEntity):
|
||||
|
||||
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
|
||||
"""Initialize a KNX date."""
|
||||
self._device = XknxDateDevice(
|
||||
knx_module.xknx,
|
||||
name=config[CONF_NAME],
|
||||
localtime=False,
|
||||
group_address=config[KNX_ADDRESS],
|
||||
group_address_state=config.get(CONF_STATE_ADDRESS),
|
||||
respond_to_read=config[CONF_RESPOND_TO_READ],
|
||||
sync_state=config[CONF_SYNC_STATE],
|
||||
)
|
||||
super().__init__(
|
||||
knx_module=knx_module,
|
||||
device=XknxDateDevice(
|
||||
knx_module.xknx,
|
||||
name=config[CONF_NAME],
|
||||
localtime=False,
|
||||
group_address=config[KNX_ADDRESS],
|
||||
group_address_state=config.get(CONF_STATE_ADDRESS),
|
||||
respond_to_read=config[CONF_RESPOND_TO_READ],
|
||||
sync_state=config[CONF_SYNC_STATE],
|
||||
),
|
||||
unique_id=str(self._device.remote_value.group_address),
|
||||
entity_config=config,
|
||||
)
|
||||
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
|
||||
self._attr_unique_id = str(self._device.remote_value.group_address)
|
||||
|
||||
|
||||
class KnxUiDate(_KNXDate, KnxUiEntity):
|
||||
|
||||
@@ -9,14 +9,8 @@ from xknx.devices import DateTimeDevice as XknxDateTimeDevice
|
||||
from xknx.dpt.dpt_19 import KNXDateTime as XKNXDateTime
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.datetime import DateTimeEntity
|
||||
from homeassistant.const import (
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_NAME,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.components.datetime import ENTITY_ID_FORMAT, DateTimeEntity
|
||||
from homeassistant.const import CONF_NAME, STATE_UNAVAILABLE, STATE_UNKNOWN, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import (
|
||||
AddConfigEntryEntitiesCallback,
|
||||
@@ -76,6 +70,7 @@ class _KNXDateTime(DateTimeEntity, RestoreEntity):
|
||||
"""Representation of a KNX datetime."""
|
||||
|
||||
_device: XknxDateTimeDevice
|
||||
_entity_id_format = ENTITY_ID_FORMAT
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Restore last state."""
|
||||
@@ -110,20 +105,20 @@ class KnxYamlDateTime(_KNXDateTime, KnxYamlEntity):
|
||||
|
||||
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
|
||||
"""Initialize a KNX datetime."""
|
||||
self._device = XknxDateTimeDevice(
|
||||
knx_module.xknx,
|
||||
name=config[CONF_NAME],
|
||||
localtime=False,
|
||||
group_address=config[KNX_ADDRESS],
|
||||
group_address_state=config.get(CONF_STATE_ADDRESS),
|
||||
respond_to_read=config[CONF_RESPOND_TO_READ],
|
||||
sync_state=config[CONF_SYNC_STATE],
|
||||
)
|
||||
super().__init__(
|
||||
knx_module=knx_module,
|
||||
device=XknxDateTimeDevice(
|
||||
knx_module.xknx,
|
||||
name=config[CONF_NAME],
|
||||
localtime=False,
|
||||
group_address=config[KNX_ADDRESS],
|
||||
group_address_state=config.get(CONF_STATE_ADDRESS),
|
||||
respond_to_read=config[CONF_RESPOND_TO_READ],
|
||||
sync_state=config[CONF_SYNC_STATE],
|
||||
),
|
||||
unique_id=str(self._device.remote_value.group_address),
|
||||
entity_config=config,
|
||||
)
|
||||
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
|
||||
self._attr_unique_id = str(self._device.remote_value.group_address)
|
||||
|
||||
|
||||
class KnxUiDateTime(_KNXDateTime, KnxUiEntity):
|
||||
|
||||
@@ -6,13 +6,13 @@ from typing import TYPE_CHECKING, Any
|
||||
|
||||
from xknx.devices import Device as XknxDevice
|
||||
|
||||
from homeassistant.const import CONF_ENTITY_CATEGORY, EntityCategory
|
||||
from homeassistant.const import CONF_ENTITY_CATEGORY, CONF_NAME, EntityCategory
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity import Entity, async_generate_entity_id
|
||||
from homeassistant.helpers.entity_platform import EntityPlatform
|
||||
from homeassistant.helpers.entity_registry import RegistryEntry
|
||||
|
||||
from .const import DOMAIN
|
||||
from .const import CONF_DEFAULT_ENTITY_ID, DOMAIN
|
||||
from .storage.config_store import PlatformControllerBase
|
||||
from .storage.const import CONF_DEVICE_INFO
|
||||
|
||||
@@ -52,14 +52,12 @@ class _KnxEntityBase(Entity):
|
||||
"""Representation of a KNX entity."""
|
||||
|
||||
_attr_should_poll = False
|
||||
|
||||
_attr_unique_id: str
|
||||
_entity_id_format: str
|
||||
_knx_module: KNXModule
|
||||
_device: XknxDevice
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""Return the name of the KNX device."""
|
||||
return self._device.name
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return True if entity is available."""
|
||||
@@ -100,16 +98,29 @@ class _KnxEntityBase(Entity):
|
||||
class KnxYamlEntity(_KnxEntityBase):
|
||||
"""Representation of a KNX entity configured from YAML."""
|
||||
|
||||
def __init__(self, knx_module: KNXModule, device: XknxDevice) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
knx_module: KNXModule,
|
||||
unique_id: str,
|
||||
entity_config: dict[str, Any],
|
||||
) -> None:
|
||||
"""Initialize the YAML entity."""
|
||||
self._knx_module = knx_module
|
||||
self._device = device
|
||||
self._attr_name = entity_config[CONF_NAME] or None
|
||||
self._attr_unique_id = unique_id
|
||||
self._attr_entity_category = entity_config.get(CONF_ENTITY_CATEGORY)
|
||||
|
||||
default_entity_id: str | None
|
||||
if (default_entity_id := entity_config.get(CONF_DEFAULT_ENTITY_ID)) is not None:
|
||||
_, _, object_id = default_entity_id.partition(".")
|
||||
self.entity_id = async_generate_entity_id(
|
||||
self._entity_id_format, object_id, hass=knx_module.hass
|
||||
)
|
||||
|
||||
|
||||
class KnxUiEntity(_KnxEntityBase):
|
||||
"""Representation of a KNX UI entity."""
|
||||
|
||||
_attr_unique_id: str
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
@@ -117,6 +128,8 @@ class KnxUiEntity(_KnxEntityBase):
|
||||
) -> None:
|
||||
"""Initialize the UI entity."""
|
||||
self._knx_module = knx_module
|
||||
|
||||
self._attr_name = entity_config[CONF_NAME]
|
||||
self._attr_unique_id = unique_id
|
||||
if entity_category := entity_config.get(CONF_ENTITY_CATEGORY):
|
||||
self._attr_entity_category = EntityCategory(entity_category)
|
||||
|
||||
@@ -11,8 +11,8 @@ from xknx.devices import Fan as XknxFan
|
||||
from xknx.telegram.address import parse_device_group_address
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.fan import FanEntity, FanEntityFeature
|
||||
from homeassistant.const import CONF_ENTITY_CATEGORY, CONF_NAME, Platform
|
||||
from homeassistant.components.fan import ENTITY_ID_FORMAT, FanEntity, FanEntityFeature
|
||||
from homeassistant.const import CONF_NAME, Platform
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers.entity_platform import (
|
||||
@@ -130,6 +130,7 @@ class _KnxFan(FanEntity):
|
||||
"""Representation of a KNX fan."""
|
||||
|
||||
_device: XknxFan
|
||||
_entity_id_format = ENTITY_ID_FORMAT
|
||||
_step_range: tuple[int, int] | None
|
||||
|
||||
def _get_knx_speed(self, percentage: int) -> int:
|
||||
@@ -208,35 +209,31 @@ class KnxYamlFan(_KnxFan, KnxYamlEntity):
|
||||
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
|
||||
"""Initialize of KNX fan."""
|
||||
max_step = config.get(FanConf.MAX_STEP)
|
||||
self._device = XknxFan(
|
||||
xknx=knx_module.xknx,
|
||||
name=config[CONF_NAME],
|
||||
group_address_speed=config.get(KNX_ADDRESS),
|
||||
group_address_speed_state=config.get(FanSchema.CONF_STATE_ADDRESS),
|
||||
group_address_oscillation=config.get(FanSchema.CONF_OSCILLATION_ADDRESS),
|
||||
group_address_oscillation_state=config.get(
|
||||
FanSchema.CONF_OSCILLATION_STATE_ADDRESS
|
||||
),
|
||||
group_address_switch=config.get(FanSchema.CONF_SWITCH_ADDRESS),
|
||||
group_address_switch_state=config.get(FanSchema.CONF_SWITCH_STATE_ADDRESS),
|
||||
max_step=max_step,
|
||||
sync_state=config.get(CONF_SYNC_STATE, True),
|
||||
)
|
||||
super().__init__(
|
||||
knx_module=knx_module,
|
||||
device=XknxFan(
|
||||
xknx=knx_module.xknx,
|
||||
name=config[CONF_NAME],
|
||||
group_address_speed=config.get(KNX_ADDRESS),
|
||||
group_address_speed_state=config.get(FanSchema.CONF_STATE_ADDRESS),
|
||||
group_address_oscillation=config.get(
|
||||
FanSchema.CONF_OSCILLATION_ADDRESS
|
||||
),
|
||||
group_address_oscillation_state=config.get(
|
||||
FanSchema.CONF_OSCILLATION_STATE_ADDRESS
|
||||
),
|
||||
group_address_switch=config.get(FanSchema.CONF_SWITCH_ADDRESS),
|
||||
group_address_switch_state=config.get(
|
||||
FanSchema.CONF_SWITCH_STATE_ADDRESS
|
||||
),
|
||||
max_step=max_step,
|
||||
sync_state=config.get(CONF_SYNC_STATE, True),
|
||||
unique_id=(
|
||||
str(self._device.speed.group_address)
|
||||
if self._device.speed.group_address
|
||||
else str(self._device.switch.group_address)
|
||||
),
|
||||
entity_config=config,
|
||||
)
|
||||
# FanSpeedMode.STEP if max_step is set
|
||||
self._step_range: tuple[int, int] | None = (1, max_step) if max_step else None
|
||||
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
|
||||
|
||||
if self._device.speed.group_address:
|
||||
self._attr_unique_id = str(self._device.speed.group_address)
|
||||
else:
|
||||
self._attr_unique_id = str(self._device.switch.group_address)
|
||||
|
||||
|
||||
class KnxUiFan(_KnxFan, KnxUiEntity):
|
||||
|
||||
@@ -16,10 +16,11 @@ from homeassistant.components.light import (
|
||||
ATTR_RGB_COLOR,
|
||||
ATTR_RGBW_COLOR,
|
||||
ATTR_XY_COLOR,
|
||||
ENTITY_ID_FORMAT,
|
||||
ColorMode,
|
||||
LightEntity,
|
||||
)
|
||||
from homeassistant.const import CONF_ENTITY_CATEGORY, CONF_NAME, Platform
|
||||
from homeassistant.const import CONF_NAME, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import (
|
||||
AddConfigEntryEntitiesCallback,
|
||||
@@ -324,6 +325,7 @@ class _KnxLight(LightEntity):
|
||||
_attr_max_color_temp_kelvin: int
|
||||
_attr_min_color_temp_kelvin: int
|
||||
_device: XknxLight
|
||||
_entity_id_format = ENTITY_ID_FORMAT
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
@@ -558,15 +560,15 @@ class KnxYamlLight(_KnxLight, KnxYamlEntity):
|
||||
|
||||
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
|
||||
"""Initialize of KNX light."""
|
||||
self._device = _create_yaml_light(knx_module.xknx, config)
|
||||
super().__init__(
|
||||
knx_module=knx_module,
|
||||
device=_create_yaml_light(knx_module.xknx, config),
|
||||
unique_id=self._device_unique_id(),
|
||||
entity_config=config,
|
||||
)
|
||||
self._attr_color_mode = next(iter(self.supported_color_modes))
|
||||
self._attr_max_color_temp_kelvin: int = config[LightSchema.CONF_MAX_KELVIN]
|
||||
self._attr_min_color_temp_kelvin: int = config[LightSchema.CONF_MIN_KELVIN]
|
||||
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
|
||||
self._attr_unique_id = self._device_unique_id()
|
||||
|
||||
def _device_unique_id(self) -> str:
|
||||
"""Return unique id for this device."""
|
||||
|
||||
@@ -6,8 +6,8 @@ from xknx import XKNX
|
||||
from xknx.devices import Notification as XknxNotification
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.notify import NotifyEntity
|
||||
from homeassistant.const import CONF_ENTITY_CATEGORY, CONF_NAME, CONF_TYPE, Platform
|
||||
from homeassistant.components.notify import ENTITY_ID_FORMAT, NotifyEntity
|
||||
from homeassistant.const import CONF_NAME, CONF_TYPE, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
@@ -43,15 +43,16 @@ class KNXNotify(KnxYamlEntity, NotifyEntity):
|
||||
"""Representation of a KNX notification entity."""
|
||||
|
||||
_device: XknxNotification
|
||||
_entity_id_format = ENTITY_ID_FORMAT
|
||||
|
||||
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
|
||||
"""Initialize a KNX notification."""
|
||||
self._device = _create_notification_instance(knx_module.xknx, config)
|
||||
super().__init__(
|
||||
knx_module=knx_module,
|
||||
device=_create_notification_instance(knx_module.xknx, config),
|
||||
unique_id=str(self._device.remote_value.group_address),
|
||||
entity_config=config,
|
||||
)
|
||||
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
|
||||
self._attr_unique_id = str(self._device.remote_value.group_address)
|
||||
|
||||
async def async_send_message(self, message: str, title: str | None = None) -> None:
|
||||
"""Send a notification to knx bus."""
|
||||
|
||||
@@ -7,10 +7,14 @@ from typing import cast
|
||||
from xknx.devices import NumericValue
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.number import NumberDeviceClass, NumberMode, RestoreNumber
|
||||
from homeassistant.components.number import (
|
||||
ENTITY_ID_FORMAT,
|
||||
NumberDeviceClass,
|
||||
NumberMode,
|
||||
RestoreNumber,
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_MODE,
|
||||
CONF_NAME,
|
||||
CONF_TYPE,
|
||||
@@ -79,6 +83,7 @@ class _KnxNumber(RestoreNumber):
|
||||
"""Representation of a KNX number."""
|
||||
|
||||
_device: NumericValue
|
||||
_entity_id_format = ENTITY_ID_FORMAT
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Restore last state."""
|
||||
@@ -109,16 +114,18 @@ class KnxYamlNumber(_KnxNumber, KnxYamlEntity):
|
||||
|
||||
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
|
||||
"""Initialize a KNX number."""
|
||||
self._device = NumericValue(
|
||||
knx_module.xknx,
|
||||
name=config[CONF_NAME],
|
||||
group_address=config[KNX_ADDRESS],
|
||||
group_address_state=config.get(CONF_STATE_ADDRESS),
|
||||
respond_to_read=config[CONF_RESPOND_TO_READ],
|
||||
value_type=config[CONF_TYPE],
|
||||
)
|
||||
super().__init__(
|
||||
knx_module=knx_module,
|
||||
device=NumericValue(
|
||||
knx_module.xknx,
|
||||
name=config[CONF_NAME],
|
||||
group_address=config[KNX_ADDRESS],
|
||||
group_address_state=config.get(CONF_STATE_ADDRESS),
|
||||
respond_to_read=config[CONF_RESPOND_TO_READ],
|
||||
value_type=config[CONF_TYPE],
|
||||
),
|
||||
unique_id=str(self._device.sensor_value.group_address),
|
||||
entity_config=config,
|
||||
)
|
||||
dpt_string = self._device.sensor_value.dpt_class.dpt_number_str()
|
||||
dpt_info = get_supported_dpts()[dpt_string]
|
||||
@@ -131,7 +138,6 @@ class KnxYamlNumber(_KnxNumber, KnxYamlEntity):
|
||||
dpt_info["sensor_device_class"],
|
||||
),
|
||||
)
|
||||
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
|
||||
self._attr_mode = config[CONF_MODE]
|
||||
self._attr_native_max_value = config.get(
|
||||
NumberConf.MAX,
|
||||
@@ -149,7 +155,6 @@ class KnxYamlNumber(_KnxNumber, KnxYamlEntity):
|
||||
CONF_UNIT_OF_MEASUREMENT,
|
||||
dpt_info["unit"],
|
||||
)
|
||||
self._attr_unique_id = str(self._device.sensor_value.group_address)
|
||||
|
||||
self._device.sensor_value.value = max(0, self._attr_native_min_value)
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ from xknx.devices import Device as XknxDevice, Scene as XknxScene
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.scene import BaseScene
|
||||
from homeassistant.const import CONF_ENTITY_CATEGORY, CONF_NAME, Platform
|
||||
from homeassistant.const import CONF_NAME, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import (
|
||||
AddConfigEntryEntitiesCallback,
|
||||
@@ -65,6 +65,7 @@ class _KnxScene(BaseScene, _KnxEntityBase):
|
||||
"""Representation of a KNX scene."""
|
||||
|
||||
_device: XknxScene
|
||||
_entity_id_format = "scene.{}"
|
||||
|
||||
async def _async_activate(self, **kwargs: Any) -> None:
|
||||
"""Activate the scene."""
|
||||
@@ -83,18 +84,18 @@ class KnxYamlScene(_KnxScene, KnxYamlEntity):
|
||||
|
||||
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
|
||||
"""Initialize KNX scene."""
|
||||
self._device = XknxScene(
|
||||
xknx=knx_module.xknx,
|
||||
name=config[CONF_NAME],
|
||||
group_address=config[KNX_ADDRESS],
|
||||
scene_number=config[SceneSchema.CONF_SCENE_NUMBER],
|
||||
)
|
||||
super().__init__(
|
||||
knx_module=knx_module,
|
||||
device=XknxScene(
|
||||
xknx=knx_module.xknx,
|
||||
name=config[CONF_NAME],
|
||||
group_address=config[KNX_ADDRESS],
|
||||
scene_number=config[SceneSchema.CONF_SCENE_NUMBER],
|
||||
unique_id=(
|
||||
f"{self._device.scene_value.group_address}_{self._device.scene_number}"
|
||||
),
|
||||
)
|
||||
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
|
||||
self._attr_unique_id = (
|
||||
f"{self._device.scene_value.group_address}_{self._device.scene_number}"
|
||||
entity_config=config,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ from homeassistant.helpers.entity import ENTITY_CATEGORIES_SCHEMA
|
||||
|
||||
from .const import (
|
||||
CONF_CONTEXT_TIMEOUT,
|
||||
CONF_DEFAULT_ENTITY_ID,
|
||||
CONF_IGNORE_INTERNAL_STATE,
|
||||
CONF_INVERT,
|
||||
CONF_KNX_EXPOSE,
|
||||
@@ -199,16 +200,27 @@ class KNXPlatformSchema(ABC):
|
||||
}
|
||||
|
||||
|
||||
def _entity_base_schema(platform: Platform) -> vol.Schema:
|
||||
"""Return a base schema for KNX entities."""
|
||||
return vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_NAME, default=""): cv.string,
|
||||
vol.Optional(CONF_DEFAULT_ENTITY_ID): vol.All(
|
||||
cv.entity_id, cv.entity_domain(platform)
|
||||
),
|
||||
vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class BinarySensorSchema(KNXPlatformSchema):
|
||||
"""Voluptuous schema for KNX binary sensors."""
|
||||
|
||||
PLATFORM = Platform.BINARY_SENSOR
|
||||
DEFAULT_NAME = "KNX Binary Sensor"
|
||||
|
||||
ENTITY_SCHEMA = vol.All(
|
||||
vol.Schema(
|
||||
_entity_base_schema(PLATFORM).extend(
|
||||
{
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_SYNC_STATE, default=True): sync_state_validator,
|
||||
vol.Optional(CONF_IGNORE_INTERNAL_STATE, default=False): cv.boolean,
|
||||
vol.Optional(CONF_INVERT, default=False): cv.boolean,
|
||||
@@ -218,7 +230,6 @@ class BinarySensorSchema(KNXPlatformSchema):
|
||||
),
|
||||
vol.Optional(CONF_DEVICE_CLASS): BINARY_SENSOR_DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_RESET_AFTER): cv.positive_float,
|
||||
vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA,
|
||||
}
|
||||
),
|
||||
)
|
||||
@@ -230,7 +241,6 @@ class ButtonSchema(KNXPlatformSchema):
|
||||
PLATFORM = Platform.BUTTON
|
||||
|
||||
CONF_VALUE = "value"
|
||||
DEFAULT_NAME = "KNX Button"
|
||||
|
||||
payload_or_value_msg = f"Please use only one of `{CONF_PAYLOAD}` or `{CONF_VALUE}`"
|
||||
length_or_type_msg = (
|
||||
@@ -238,9 +248,8 @@ class ButtonSchema(KNXPlatformSchema):
|
||||
)
|
||||
|
||||
ENTITY_SCHEMA = vol.All(
|
||||
vol.Schema(
|
||||
_entity_base_schema(PLATFORM).extend(
|
||||
{
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Required(KNX_ADDRESS): ga_validator,
|
||||
vol.Exclusive(
|
||||
CONF_PAYLOAD, "payload_or_value", msg=payload_or_value_msg
|
||||
@@ -254,7 +263,6 @@ class ButtonSchema(KNXPlatformSchema):
|
||||
vol.Exclusive(
|
||||
CONF_TYPE, "length_or_type", msg=length_or_type_msg
|
||||
): object,
|
||||
vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA,
|
||||
}
|
||||
),
|
||||
vol.Any(
|
||||
@@ -322,7 +330,6 @@ class ClimateSchema(KNXPlatformSchema):
|
||||
CONF_SWING_HORIZONTAL_ADDRESS = "swing_horizontal_address"
|
||||
CONF_SWING_HORIZONTAL_STATE_ADDRESS = "swing_horizontal_state_address"
|
||||
|
||||
DEFAULT_NAME = "KNX Climate"
|
||||
DEFAULT_SETPOINT_SHIFT_MODE = "DPT6010"
|
||||
DEFAULT_SETPOINT_SHIFT_MAX = 6
|
||||
DEFAULT_SETPOINT_SHIFT_MIN = -6
|
||||
@@ -331,9 +338,8 @@ class ClimateSchema(KNXPlatformSchema):
|
||||
DEFAULT_FAN_SPEED_MODE = "percent"
|
||||
|
||||
ENTITY_SCHEMA = vol.All(
|
||||
vol.Schema(
|
||||
_entity_base_schema(PLATFORM).extend(
|
||||
{
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(
|
||||
ClimateConf.SETPOINT_SHIFT_MAX, default=DEFAULT_SETPOINT_SHIFT_MAX
|
||||
): vol.All(int, vol.Range(min=0, max=32)),
|
||||
@@ -399,7 +405,6 @@ class ClimateSchema(KNXPlatformSchema):
|
||||
): vol.Coerce(HVACMode),
|
||||
vol.Optional(ClimateConf.MIN_TEMP): vol.Coerce(float),
|
||||
vol.Optional(ClimateConf.MAX_TEMP): vol.Coerce(float),
|
||||
vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA,
|
||||
vol.Optional(CONF_FAN_SPEED_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_FAN_SPEED_STATE_ADDRESS): ga_list_validator,
|
||||
vol.Optional(ClimateConf.FAN_MAX_STEP, default=3): cv.byte,
|
||||
@@ -433,12 +438,10 @@ class CoverSchema(KNXPlatformSchema):
|
||||
CONF_ANGLE_STATE_ADDRESS = "angle_state_address"
|
||||
|
||||
DEFAULT_TRAVEL_TIME = 25
|
||||
DEFAULT_NAME = "KNX Cover"
|
||||
|
||||
ENTITY_SCHEMA = vol.All(
|
||||
vol.Schema(
|
||||
_entity_base_schema(PLATFORM).extend(
|
||||
{
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_MOVE_LONG_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_MOVE_SHORT_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_STOP_ADDRESS): ga_list_validator,
|
||||
@@ -456,7 +459,6 @@ class CoverSchema(KNXPlatformSchema):
|
||||
vol.Optional(CoverConf.INVERT_POSITION, default=False): cv.boolean,
|
||||
vol.Optional(CoverConf.INVERT_ANGLE, default=False): cv.boolean,
|
||||
vol.Optional(CONF_DEVICE_CLASS): COVER_DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA,
|
||||
}
|
||||
),
|
||||
vol.Any(
|
||||
@@ -481,16 +483,12 @@ class DateSchema(KNXPlatformSchema):
|
||||
|
||||
PLATFORM = Platform.DATE
|
||||
|
||||
DEFAULT_NAME = "KNX Date"
|
||||
|
||||
ENTITY_SCHEMA = vol.Schema(
|
||||
ENTITY_SCHEMA = _entity_base_schema(PLATFORM).extend(
|
||||
{
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_RESPOND_TO_READ, default=False): cv.boolean,
|
||||
vol.Optional(CONF_SYNC_STATE, default=True): sync_state_validator,
|
||||
vol.Required(KNX_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_STATE_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -500,16 +498,12 @@ class DateTimeSchema(KNXPlatformSchema):
|
||||
|
||||
PLATFORM = Platform.DATETIME
|
||||
|
||||
DEFAULT_NAME = "KNX DateTime"
|
||||
|
||||
ENTITY_SCHEMA = vol.Schema(
|
||||
ENTITY_SCHEMA = _entity_base_schema(PLATFORM).extend(
|
||||
{
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_RESPOND_TO_READ, default=False): cv.boolean,
|
||||
vol.Optional(CONF_SYNC_STATE, default=True): sync_state_validator,
|
||||
vol.Required(KNX_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_STATE_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -571,12 +565,9 @@ class FanSchema(KNXPlatformSchema):
|
||||
CONF_SWITCH_ADDRESS = "switch_address"
|
||||
CONF_SWITCH_STATE_ADDRESS = "switch_state_address"
|
||||
|
||||
DEFAULT_NAME = "KNX Fan"
|
||||
|
||||
ENTITY_SCHEMA = vol.All(
|
||||
vol.Schema(
|
||||
_entity_base_schema(PLATFORM).extend(
|
||||
{
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(KNX_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_STATE_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_SWITCH_ADDRESS): ga_list_validator,
|
||||
@@ -584,7 +575,6 @@ class FanSchema(KNXPlatformSchema):
|
||||
vol.Optional(CONF_OSCILLATION_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_OSCILLATION_STATE_ADDRESS): ga_list_validator,
|
||||
vol.Optional(FanConf.MAX_STEP): cv.byte,
|
||||
vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA,
|
||||
vol.Optional(CONF_SYNC_STATE, default=True): sync_state_validator,
|
||||
}
|
||||
),
|
||||
@@ -629,7 +619,6 @@ class LightSchema(KNXPlatformSchema):
|
||||
CONF_MIN_KELVIN = "min_kelvin"
|
||||
CONF_MAX_KELVIN = "max_kelvin"
|
||||
|
||||
DEFAULT_NAME = "KNX Light"
|
||||
DEFAULT_COLOR_TEMP_MODE = "absolute"
|
||||
DEFAULT_MIN_KELVIN = 2700 # 370 mireds
|
||||
DEFAULT_MAX_KELVIN = 6000 # 166 mireds
|
||||
@@ -661,9 +650,8 @@ class LightSchema(KNXPlatformSchema):
|
||||
)
|
||||
|
||||
ENTITY_SCHEMA = vol.All(
|
||||
vol.Schema(
|
||||
_entity_base_schema(PLATFORM).extend(
|
||||
{
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(KNX_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_STATE_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_BRIGHTNESS_ADDRESS): ga_list_validator,
|
||||
@@ -713,7 +701,6 @@ class LightSchema(KNXPlatformSchema):
|
||||
vol.Optional(CONF_MAX_KELVIN, default=DEFAULT_MAX_KELVIN): vol.All(
|
||||
vol.Coerce(int), vol.Range(min=1)
|
||||
),
|
||||
vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA,
|
||||
}
|
||||
),
|
||||
vol.Any(
|
||||
@@ -759,14 +746,10 @@ class NotifySchema(KNXPlatformSchema):
|
||||
|
||||
PLATFORM = Platform.NOTIFY
|
||||
|
||||
DEFAULT_NAME = "KNX Notify"
|
||||
|
||||
ENTITY_SCHEMA = vol.Schema(
|
||||
ENTITY_SCHEMA = _entity_base_schema(PLATFORM).extend(
|
||||
{
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_TYPE, default="latin_1"): string_type_validator,
|
||||
vol.Required(KNX_ADDRESS): ga_validator,
|
||||
vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -775,12 +758,10 @@ class NumberSchema(KNXPlatformSchema):
|
||||
"""Voluptuous schema for KNX numbers."""
|
||||
|
||||
PLATFORM = Platform.NUMBER
|
||||
DEFAULT_NAME = "KNX Number"
|
||||
|
||||
ENTITY_SCHEMA = vol.All(
|
||||
vol.Schema(
|
||||
_entity_base_schema(PLATFORM).extend(
|
||||
{
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_RESPOND_TO_READ, default=False): cv.boolean,
|
||||
vol.Optional(CONF_MODE, default=NumberMode.AUTO): vol.Coerce(
|
||||
NumberMode
|
||||
@@ -793,7 +774,6 @@ class NumberSchema(KNXPlatformSchema):
|
||||
vol.Optional(NumberConf.STEP): cv.positive_float,
|
||||
vol.Optional(CONF_DEVICE_CLASS): NUMBER_DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
|
||||
vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA,
|
||||
}
|
||||
),
|
||||
_number_limit_sub_validator,
|
||||
@@ -807,15 +787,12 @@ class SceneSchema(KNXPlatformSchema):
|
||||
|
||||
CONF_SCENE_NUMBER = "scene_number"
|
||||
|
||||
DEFAULT_NAME = "KNX SCENE"
|
||||
ENTITY_SCHEMA = vol.Schema(
|
||||
ENTITY_SCHEMA = _entity_base_schema(PLATFORM).extend(
|
||||
{
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Required(KNX_ADDRESS): ga_list_validator,
|
||||
vol.Required(SceneConf.SCENE_NUMBER): vol.All(
|
||||
vol.Coerce(int), vol.Range(min=1, max=64)
|
||||
),
|
||||
vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -827,12 +804,10 @@ class SelectSchema(KNXPlatformSchema):
|
||||
|
||||
CONF_OPTION = "option"
|
||||
CONF_OPTIONS = "options"
|
||||
DEFAULT_NAME = "KNX Select"
|
||||
|
||||
ENTITY_SCHEMA = vol.All(
|
||||
vol.Schema(
|
||||
_entity_base_schema(PLATFORM).extend(
|
||||
{
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_SYNC_STATE, default=True): sync_state_validator,
|
||||
vol.Optional(CONF_RESPOND_TO_READ, default=False): cv.boolean,
|
||||
vol.Required(CONF_PAYLOAD_LENGTH): vol.All(
|
||||
@@ -846,7 +821,6 @@ class SelectSchema(KNXPlatformSchema):
|
||||
],
|
||||
vol.Required(KNX_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_STATE_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA,
|
||||
}
|
||||
),
|
||||
select_options_sub_validator,
|
||||
@@ -861,12 +835,10 @@ class SensorSchema(KNXPlatformSchema):
|
||||
CONF_ALWAYS_CALLBACK = "always_callback"
|
||||
CONF_STATE_ADDRESS = CONF_STATE_ADDRESS
|
||||
CONF_SYNC_STATE = CONF_SYNC_STATE
|
||||
DEFAULT_NAME = "KNX Sensor"
|
||||
|
||||
ENTITY_SCHEMA = vol.All(
|
||||
vol.Schema(
|
||||
_entity_base_schema(PLATFORM).extend(
|
||||
{
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_SYNC_STATE, default=True): sync_state_validator,
|
||||
vol.Optional(CONF_ALWAYS_CALLBACK, default=False): cv.boolean,
|
||||
vol.Optional(CONF_SENSOR_STATE_CLASS): STATE_CLASSES_SCHEMA,
|
||||
@@ -874,7 +846,6 @@ class SensorSchema(KNXPlatformSchema):
|
||||
vol.Required(CONF_STATE_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_DEVICE_CLASS): SENSOR_DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
|
||||
vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA,
|
||||
}
|
||||
),
|
||||
_sensor_attribute_sub_validator,
|
||||
@@ -889,16 +860,13 @@ class SwitchSchema(KNXPlatformSchema):
|
||||
CONF_INVERT = CONF_INVERT
|
||||
CONF_STATE_ADDRESS = CONF_STATE_ADDRESS
|
||||
|
||||
DEFAULT_NAME = "KNX Switch"
|
||||
ENTITY_SCHEMA = vol.Schema(
|
||||
ENTITY_SCHEMA = _entity_base_schema(PLATFORM).extend(
|
||||
{
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_INVERT, default=False): cv.boolean,
|
||||
vol.Optional(CONF_RESPOND_TO_READ, default=False): cv.boolean,
|
||||
vol.Required(KNX_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_STATE_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_DEVICE_CLASS): SWITCH_DEVICE_CLASSES_SCHEMA,
|
||||
vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -908,17 +876,13 @@ class TextSchema(KNXPlatformSchema):
|
||||
|
||||
PLATFORM = Platform.TEXT
|
||||
|
||||
DEFAULT_NAME = "KNX Text"
|
||||
|
||||
ENTITY_SCHEMA = vol.Schema(
|
||||
ENTITY_SCHEMA = _entity_base_schema(PLATFORM).extend(
|
||||
{
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_RESPOND_TO_READ, default=False): cv.boolean,
|
||||
vol.Optional(CONF_TYPE, default="latin_1"): string_type_validator,
|
||||
vol.Optional(CONF_MODE, default=TextMode.TEXT): vol.Coerce(TextMode),
|
||||
vol.Required(KNX_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_STATE_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -928,16 +892,12 @@ class TimeSchema(KNXPlatformSchema):
|
||||
|
||||
PLATFORM = Platform.TIME
|
||||
|
||||
DEFAULT_NAME = "KNX Time"
|
||||
|
||||
ENTITY_SCHEMA = vol.Schema(
|
||||
ENTITY_SCHEMA = _entity_base_schema(PLATFORM).extend(
|
||||
{
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_RESPOND_TO_READ, default=False): cv.boolean,
|
||||
vol.Optional(CONF_SYNC_STATE, default=True): sync_state_validator,
|
||||
vol.Required(KNX_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_STATE_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -962,27 +922,21 @@ class WeatherSchema(KNXPlatformSchema):
|
||||
CONF_KNX_AIR_PRESSURE_ADDRESS = "address_air_pressure"
|
||||
CONF_KNX_HUMIDITY_ADDRESS = "address_humidity"
|
||||
|
||||
DEFAULT_NAME = "KNX Weather Station"
|
||||
|
||||
ENTITY_SCHEMA = vol.All(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_SYNC_STATE, default=True): sync_state_validator,
|
||||
vol.Required(CONF_KNX_TEMPERATURE_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_KNX_BRIGHTNESS_SOUTH_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_KNX_BRIGHTNESS_EAST_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_KNX_BRIGHTNESS_WEST_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_KNX_BRIGHTNESS_NORTH_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_KNX_WIND_SPEED_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_KNX_WIND_BEARING_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_KNX_RAIN_ALARM_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_KNX_FROST_ALARM_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_KNX_WIND_ALARM_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_KNX_DAY_NIGHT_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_KNX_AIR_PRESSURE_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_KNX_HUMIDITY_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_ENTITY_CATEGORY): ENTITY_CATEGORIES_SCHEMA,
|
||||
}
|
||||
),
|
||||
ENTITY_SCHEMA = _entity_base_schema(PLATFORM).extend(
|
||||
{
|
||||
vol.Optional(CONF_SYNC_STATE, default=True): sync_state_validator,
|
||||
vol.Required(CONF_KNX_TEMPERATURE_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_KNX_BRIGHTNESS_SOUTH_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_KNX_BRIGHTNESS_EAST_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_KNX_BRIGHTNESS_WEST_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_KNX_BRIGHTNESS_NORTH_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_KNX_WIND_SPEED_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_KNX_WIND_BEARING_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_KNX_RAIN_ALARM_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_KNX_FROST_ALARM_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_KNX_WIND_ALARM_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_KNX_DAY_NIGHT_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_KNX_AIR_PRESSURE_ADDRESS): ga_list_validator,
|
||||
vol.Optional(CONF_KNX_HUMIDITY_ADDRESS): ga_list_validator,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -6,9 +6,8 @@ from xknx import XKNX
|
||||
from xknx.devices import Device as XknxDevice, RawValue
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.select import SelectEntity
|
||||
from homeassistant.components.select import ENTITY_ID_FORMAT, SelectEntity
|
||||
from homeassistant.const import (
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_NAME,
|
||||
CONF_PAYLOAD,
|
||||
STATE_UNAVAILABLE,
|
||||
@@ -62,12 +61,15 @@ class KNXSelect(KnxYamlEntity, SelectEntity, RestoreEntity):
|
||||
"""Representation of a KNX select."""
|
||||
|
||||
_device: RawValue
|
||||
_entity_id_format = ENTITY_ID_FORMAT
|
||||
|
||||
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
|
||||
"""Initialize a KNX select."""
|
||||
self._device = _create_raw_value(knx_module.xknx, config)
|
||||
super().__init__(
|
||||
knx_module=knx_module,
|
||||
device=_create_raw_value(knx_module.xknx, config),
|
||||
unique_id=str(self._device.remote_value.group_address),
|
||||
entity_config=config,
|
||||
)
|
||||
self._option_payloads: dict[str, int] = {
|
||||
option[SelectSchema.CONF_OPTION]: option[CONF_PAYLOAD]
|
||||
@@ -75,8 +77,6 @@ class KNXSelect(KnxYamlEntity, SelectEntity, RestoreEntity):
|
||||
}
|
||||
self._attr_options = list(self._option_payloads)
|
||||
self._attr_current_option = None
|
||||
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
|
||||
self._attr_unique_id = str(self._device.remote_value.group_address)
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Restore last state."""
|
||||
|
||||
@@ -14,6 +14,7 @@ from xknx.devices import Device as XknxDevice, Sensor as XknxSensor
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.sensor import (
|
||||
CONF_STATE_CLASS,
|
||||
ENTITY_ID_FORMAT,
|
||||
RestoreSensor,
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
@@ -22,7 +23,6 @@ from homeassistant.components.sensor import (
|
||||
)
|
||||
from homeassistant.const import (
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_NAME,
|
||||
CONF_TYPE,
|
||||
CONF_UNIT_OF_MEASUREMENT,
|
||||
@@ -170,6 +170,7 @@ class _KnxSensor(RestoreSensor, _KnxEntityBase):
|
||||
"""Representation of a KNX sensor."""
|
||||
|
||||
_device: XknxSensor
|
||||
_entity_id_format = ENTITY_ID_FORMAT
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Restore last state."""
|
||||
@@ -202,16 +203,18 @@ class KnxYamlSensor(_KnxSensor, KnxYamlEntity):
|
||||
|
||||
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
|
||||
"""Initialize of a KNX sensor."""
|
||||
self._device = XknxSensor(
|
||||
knx_module.xknx,
|
||||
name=config[CONF_NAME],
|
||||
group_address_state=config[SensorSchema.CONF_STATE_ADDRESS],
|
||||
sync_state=config[CONF_SYNC_STATE],
|
||||
always_callback=True,
|
||||
value_type=config[CONF_TYPE],
|
||||
)
|
||||
super().__init__(
|
||||
knx_module=knx_module,
|
||||
device=XknxSensor(
|
||||
knx_module.xknx,
|
||||
name=config[CONF_NAME],
|
||||
group_address_state=config[SensorSchema.CONF_STATE_ADDRESS],
|
||||
sync_state=config[CONF_SYNC_STATE],
|
||||
always_callback=True,
|
||||
value_type=config[CONF_TYPE],
|
||||
),
|
||||
unique_id=str(self._device.sensor_value.group_address_state),
|
||||
entity_config=config,
|
||||
)
|
||||
dpt_string = self._device.sensor_value.dpt_class.dpt_number_str()
|
||||
dpt_info = get_supported_dpts()[dpt_string]
|
||||
@@ -220,7 +223,6 @@ class KnxYamlSensor(_KnxSensor, KnxYamlEntity):
|
||||
CONF_DEVICE_CLASS,
|
||||
dpt_info["sensor_device_class"],
|
||||
)
|
||||
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
|
||||
self._attr_extra_state_attributes = {}
|
||||
self._attr_force_update = config[SensorSchema.CONF_ALWAYS_CALLBACK]
|
||||
self._attr_native_unit_of_measurement = config.get(
|
||||
@@ -231,7 +233,6 @@ class KnxYamlSensor(_KnxSensor, KnxYamlEntity):
|
||||
CONF_STATE_CLASS,
|
||||
dpt_info["sensor_state_class"],
|
||||
)
|
||||
self._attr_unique_id = str(self._device.sensor_value.group_address_state)
|
||||
|
||||
|
||||
class KnxUiSensor(_KnxSensor, KnxUiEntity):
|
||||
|
||||
@@ -7,10 +7,9 @@ from typing import Any
|
||||
from xknx.devices import Switch as XknxSwitch
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.switch import SwitchEntity
|
||||
from homeassistant.components.switch import ENTITY_ID_FORMAT, SwitchEntity
|
||||
from homeassistant.const import (
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_NAME,
|
||||
STATE_ON,
|
||||
STATE_UNAVAILABLE,
|
||||
@@ -76,6 +75,7 @@ class _KnxSwitch(SwitchEntity, RestoreEntity):
|
||||
"""Base class for a KNX switch."""
|
||||
|
||||
_device: XknxSwitch
|
||||
_entity_id_format = ENTITY_ID_FORMAT
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Restore last state."""
|
||||
@@ -107,20 +107,20 @@ class KnxYamlSwitch(_KnxSwitch, KnxYamlEntity):
|
||||
|
||||
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
|
||||
"""Initialize of KNX switch."""
|
||||
self._device = XknxSwitch(
|
||||
xknx=knx_module.xknx,
|
||||
name=config[CONF_NAME],
|
||||
group_address=config[KNX_ADDRESS],
|
||||
group_address_state=config.get(SwitchSchema.CONF_STATE_ADDRESS),
|
||||
respond_to_read=config[CONF_RESPOND_TO_READ],
|
||||
invert=config[SwitchSchema.CONF_INVERT],
|
||||
)
|
||||
super().__init__(
|
||||
knx_module=knx_module,
|
||||
device=XknxSwitch(
|
||||
xknx=knx_module.xknx,
|
||||
name=config[CONF_NAME],
|
||||
group_address=config[KNX_ADDRESS],
|
||||
group_address_state=config.get(SwitchSchema.CONF_STATE_ADDRESS),
|
||||
respond_to_read=config[CONF_RESPOND_TO_READ],
|
||||
invert=config[SwitchSchema.CONF_INVERT],
|
||||
),
|
||||
unique_id=str(self._device.switch.group_address),
|
||||
entity_config=config,
|
||||
)
|
||||
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
|
||||
self._attr_device_class = config.get(CONF_DEVICE_CLASS)
|
||||
self._attr_unique_id = str(self._device.switch.group_address)
|
||||
|
||||
|
||||
class KnxUiSwitch(_KnxSwitch, KnxUiEntity):
|
||||
|
||||
@@ -7,9 +7,8 @@ from xknx.devices import Notification as XknxNotification
|
||||
from xknx.dpt import DPTLatin1
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.text import TextEntity, TextMode
|
||||
from homeassistant.components.text import ENTITY_ID_FORMAT, TextEntity, TextMode
|
||||
from homeassistant.const import (
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_MODE,
|
||||
CONF_NAME,
|
||||
CONF_TYPE,
|
||||
@@ -75,6 +74,7 @@ class _KnxText(TextEntity, RestoreEntity):
|
||||
"""Representation of a KNX text."""
|
||||
|
||||
_device: XknxNotification
|
||||
_entity_id_format = ENTITY_ID_FORMAT
|
||||
_attr_native_max = 14
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
@@ -112,20 +112,20 @@ class KnxYamlText(_KnxText, KnxYamlEntity):
|
||||
|
||||
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
|
||||
"""Initialize a KNX text."""
|
||||
self._device = XknxNotification(
|
||||
knx_module.xknx,
|
||||
name=config[CONF_NAME],
|
||||
group_address=config[KNX_ADDRESS],
|
||||
group_address_state=config.get(CONF_STATE_ADDRESS),
|
||||
respond_to_read=config[CONF_RESPOND_TO_READ],
|
||||
value_type=config[CONF_TYPE],
|
||||
)
|
||||
super().__init__(
|
||||
knx_module=knx_module,
|
||||
device=XknxNotification(
|
||||
knx_module.xknx,
|
||||
name=config[CONF_NAME],
|
||||
group_address=config[KNX_ADDRESS],
|
||||
group_address_state=config.get(CONF_STATE_ADDRESS),
|
||||
respond_to_read=config[CONF_RESPOND_TO_READ],
|
||||
value_type=config[CONF_TYPE],
|
||||
),
|
||||
unique_id=str(self._device.remote_value.group_address),
|
||||
entity_config=config,
|
||||
)
|
||||
self._attr_mode = config[CONF_MODE]
|
||||
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
|
||||
self._attr_unique_id = str(self._device.remote_value.group_address)
|
||||
|
||||
|
||||
class KnxUiText(_KnxText, KnxUiEntity):
|
||||
|
||||
@@ -9,14 +9,8 @@ from xknx.devices import TimeDevice as XknxTimeDevice
|
||||
from xknx.dpt.dpt_10 import KNXTime as XknxTime
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.time import TimeEntity
|
||||
from homeassistant.const import (
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_NAME,
|
||||
STATE_UNAVAILABLE,
|
||||
STATE_UNKNOWN,
|
||||
Platform,
|
||||
)
|
||||
from homeassistant.components.time import ENTITY_ID_FORMAT, TimeEntity
|
||||
from homeassistant.const import CONF_NAME, STATE_UNAVAILABLE, STATE_UNKNOWN, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import (
|
||||
AddConfigEntryEntitiesCallback,
|
||||
@@ -75,6 +69,7 @@ class _KNXTime(TimeEntity, RestoreEntity):
|
||||
"""Representation of a KNX time."""
|
||||
|
||||
_device: XknxTimeDevice
|
||||
_entity_id_format = ENTITY_ID_FORMAT
|
||||
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Restore last state."""
|
||||
@@ -105,20 +100,20 @@ class KnxYamlTime(_KNXTime, KnxYamlEntity):
|
||||
|
||||
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
|
||||
"""Initialize a KNX time."""
|
||||
self._device = XknxTimeDevice(
|
||||
knx_module.xknx,
|
||||
name=config[CONF_NAME],
|
||||
localtime=False,
|
||||
group_address=config[KNX_ADDRESS],
|
||||
group_address_state=config.get(CONF_STATE_ADDRESS),
|
||||
respond_to_read=config[CONF_RESPOND_TO_READ],
|
||||
sync_state=config[CONF_SYNC_STATE],
|
||||
)
|
||||
super().__init__(
|
||||
knx_module=knx_module,
|
||||
device=XknxTimeDevice(
|
||||
knx_module.xknx,
|
||||
name=config[CONF_NAME],
|
||||
localtime=False,
|
||||
group_address=config[KNX_ADDRESS],
|
||||
group_address_state=config.get(CONF_STATE_ADDRESS),
|
||||
respond_to_read=config[CONF_RESPOND_TO_READ],
|
||||
sync_state=config[CONF_SYNC_STATE],
|
||||
),
|
||||
unique_id=str(self._device.remote_value.group_address),
|
||||
entity_config=config,
|
||||
)
|
||||
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
|
||||
self._attr_unique_id = str(self._device.remote_value.group_address)
|
||||
|
||||
|
||||
class KnxUiTime(_KNXTime, KnxUiEntity):
|
||||
|
||||
@@ -6,9 +6,8 @@ from xknx import XKNX
|
||||
from xknx.devices import Weather as XknxWeather
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.components.weather import WeatherEntity
|
||||
from homeassistant.components.weather import ENTITY_ID_FORMAT, WeatherEntity
|
||||
from homeassistant.const import (
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_NAME,
|
||||
Platform,
|
||||
UnitOfPressure,
|
||||
@@ -79,18 +78,19 @@ class KNXWeather(KnxYamlEntity, WeatherEntity):
|
||||
"""Representation of a KNX weather device."""
|
||||
|
||||
_device: XknxWeather
|
||||
_entity_id_format = ENTITY_ID_FORMAT
|
||||
_attr_native_pressure_unit = UnitOfPressure.PA
|
||||
_attr_native_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_attr_native_wind_speed_unit = UnitOfSpeed.METERS_PER_SECOND
|
||||
|
||||
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
|
||||
"""Initialize of a KNX sensor."""
|
||||
self._device = _create_weather(knx_module.xknx, config)
|
||||
super().__init__(
|
||||
knx_module=knx_module,
|
||||
device=_create_weather(knx_module.xknx, config),
|
||||
unique_id=str(self._device._temperature.group_address_state), # noqa: SLF001
|
||||
entity_config=config,
|
||||
)
|
||||
self._attr_unique_id = str(self._device._temperature.group_address_state) # noqa: SLF001
|
||||
self._attr_entity_category = config.get(CONF_ENTITY_CATEGORY)
|
||||
|
||||
@property
|
||||
def native_temperature(self) -> float | None:
|
||||
|
||||
@@ -4,11 +4,9 @@ from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
from datetime import UTC, datetime
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from chip.clusters import Objects as clusters
|
||||
from chip.clusters.Types import NullValue
|
||||
|
||||
from homeassistant.components.button import (
|
||||
ButtonDeviceClass,
|
||||
@@ -19,7 +17,6 @@ from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import EntityCategory, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .entity import MatterEntity, MatterEntityDescription
|
||||
from .helpers import get_matter
|
||||
@@ -55,67 +52,6 @@ class MatterCommandButton(MatterEntity, ButtonEntity):
|
||||
await self.send_device_command(self.entity_description.command())
|
||||
|
||||
|
||||
# CHIP epoch: 2000-01-01 00:00:00 UTC
|
||||
CHIP_EPOCH = datetime(2000, 1, 1, tzinfo=UTC)
|
||||
|
||||
|
||||
class MatterTimeSyncButton(MatterEntity, ButtonEntity):
|
||||
"""Button to synchronize time to a Matter device."""
|
||||
|
||||
entity_description: MatterButtonEntityDescription
|
||||
|
||||
async def async_press(self) -> None:
|
||||
"""Sync Home Assistant time to the Matter device."""
|
||||
now = dt_util.utcnow()
|
||||
tz = dt_util.get_default_time_zone()
|
||||
delta = now - CHIP_EPOCH
|
||||
utc_us = (
|
||||
(delta.days * 86400 * 1_000_000)
|
||||
+ (delta.seconds * 1_000_000)
|
||||
+ delta.microseconds
|
||||
)
|
||||
|
||||
# Compute timezone and DST offsets
|
||||
local_now = now.astimezone(tz)
|
||||
utc_offset_delta = local_now.utcoffset()
|
||||
utc_offset = int(utc_offset_delta.total_seconds()) if utc_offset_delta else 0
|
||||
dst_offset_delta = local_now.dst()
|
||||
dst_offset = int(dst_offset_delta.total_seconds()) if dst_offset_delta else 0
|
||||
standard_offset = utc_offset - dst_offset
|
||||
|
||||
# 1. Set timezone
|
||||
await self.send_device_command(
|
||||
clusters.TimeSynchronization.Commands.SetTimeZone(
|
||||
timeZone=[
|
||||
clusters.TimeSynchronization.Structs.TimeZoneStruct(
|
||||
offset=standard_offset, validAt=0, name=str(tz)
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
# 2. Set DST offset
|
||||
await self.send_device_command(
|
||||
clusters.TimeSynchronization.Commands.SetDSTOffset(
|
||||
DSTOffset=[
|
||||
clusters.TimeSynchronization.Structs.DSTOffsetStruct(
|
||||
offset=dst_offset,
|
||||
validStarting=0,
|
||||
validUntil=NullValue,
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
# 3. Set UTC time
|
||||
await self.send_device_command(
|
||||
clusters.TimeSynchronization.Commands.SetUTCTime(
|
||||
UTCTime=utc_us,
|
||||
granularity=clusters.TimeSynchronization.Enums.GranularityEnum.kMicrosecondsGranularity,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
# Discovery schema(s) to map Matter Attributes to HA entities
|
||||
DISCOVERY_SCHEMAS = [
|
||||
MatterDiscoverySchema(
|
||||
@@ -233,16 +169,4 @@ DISCOVERY_SCHEMAS = [
|
||||
value_contains=clusters.WaterHeaterManagement.Commands.CancelBoost.command_id,
|
||||
allow_multi=True, # Also used in water_heater
|
||||
),
|
||||
MatterDiscoverySchema(
|
||||
platform=Platform.BUTTON,
|
||||
entity_description=MatterButtonEntityDescription(
|
||||
key="TimeSynchronizationSyncTimeButton",
|
||||
translation_key="sync_time",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
),
|
||||
entity_class=MatterTimeSyncButton,
|
||||
required_attributes=(clusters.TimeSynchronization.Attributes.UTCTime,),
|
||||
allow_multi=True,
|
||||
allow_none_value=True,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -20,9 +20,6 @@
|
||||
},
|
||||
"stop": {
|
||||
"default": "mdi:stop"
|
||||
},
|
||||
"sync_time": {
|
||||
"default": "mdi:clock-check-outline"
|
||||
}
|
||||
},
|
||||
"fan": {
|
||||
|
||||
@@ -141,9 +141,6 @@
|
||||
},
|
||||
"stop": {
|
||||
"name": "[%key:common::action::stop%]"
|
||||
},
|
||||
"sync_time": {
|
||||
"name": "Sync time"
|
||||
}
|
||||
},
|
||||
"climate": {
|
||||
|
||||
@@ -6,5 +6,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/starlink",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"requirements": ["starlink-grpc-core==1.2.3"]
|
||||
"requirements": ["starlink-grpc-core==1.2.4"]
|
||||
}
|
||||
|
||||
2
requirements_all.txt
generated
2
requirements_all.txt
generated
@@ -3014,7 +3014,7 @@ starline==0.1.5
|
||||
starlingbank==3.2
|
||||
|
||||
# homeassistant.components.starlink
|
||||
starlink-grpc-core==1.2.3
|
||||
starlink-grpc-core==1.2.4
|
||||
|
||||
# homeassistant.components.statsd
|
||||
statsd==3.2.1
|
||||
|
||||
2
requirements_test_all.txt
generated
2
requirements_test_all.txt
generated
@@ -2547,7 +2547,7 @@ srpenergy==1.3.6
|
||||
starline==0.1.5
|
||||
|
||||
# homeassistant.components.starlink
|
||||
starlink-grpc-core==1.2.3
|
||||
starlink-grpc-core==1.2.4
|
||||
|
||||
# homeassistant.components.statsd
|
||||
statsd==3.2.1
|
||||
|
||||
375
tests/components/google_weather/snapshots/test_diagnostics.ambr
Normal file
375
tests/components/google_weather/snapshots/test_diagnostics.ambr
Normal file
@@ -0,0 +1,375 @@
|
||||
# serializer version: 1
|
||||
# name: test_diagnostics
|
||||
dict({
|
||||
'entry': dict({
|
||||
'created_at': '2026-03-20T21:22:23+00:00',
|
||||
'data': dict({
|
||||
'api_key': '**REDACTED**',
|
||||
}),
|
||||
'disabled_by': None,
|
||||
'discovery_keys': dict({
|
||||
}),
|
||||
'domain': 'google_weather',
|
||||
'minor_version': 1,
|
||||
'modified_at': '2026-03-20T21:22:23+00:00',
|
||||
'options': dict({
|
||||
}),
|
||||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
dict({
|
||||
'data': dict({
|
||||
'latitude': '**REDACTED**',
|
||||
'longitude': '**REDACTED**',
|
||||
}),
|
||||
'subentry_id': 'home-subentry-id',
|
||||
'subentry_type': 'location',
|
||||
'title': 'Home',
|
||||
'unique_id': None,
|
||||
}),
|
||||
]),
|
||||
'title': 'Google Weather',
|
||||
'unique_id': None,
|
||||
'version': 1,
|
||||
}),
|
||||
'subentries': dict({
|
||||
'home-subentry-id': dict({
|
||||
'daily_forecast_data': dict({
|
||||
'forecast_days': list([
|
||||
dict({
|
||||
'daytime_forecast': dict({
|
||||
'cloud_cover': 53,
|
||||
'ice_thickness': None,
|
||||
'interval': dict({
|
||||
'end_time': '2025-02-11T03:00:00Z',
|
||||
'start_time': '2025-02-10T15:00:00Z',
|
||||
}),
|
||||
'precipitation': dict({
|
||||
'probability': dict({
|
||||
'percent': 5,
|
||||
'type': 'RAIN',
|
||||
}),
|
||||
'qpf': dict({
|
||||
'quantity': 0.0,
|
||||
'unit': 'MILLIMETERS',
|
||||
}),
|
||||
'snow_qpf': None,
|
||||
}),
|
||||
'relative_humidity': 54,
|
||||
'thunderstorm_probability': 0,
|
||||
'uv_index': 3,
|
||||
'weather_condition': dict({
|
||||
'description': dict({
|
||||
'language_code': 'en',
|
||||
'text': 'Partly sunny',
|
||||
}),
|
||||
'icon_base_uri': 'https://maps.gstatic.com/weather/v1/party_cloudy',
|
||||
'type': 'PARTLY_CLOUDY',
|
||||
}),
|
||||
'wind': dict({
|
||||
'direction': dict({
|
||||
'cardinal': 'WEST',
|
||||
'degrees': 280,
|
||||
}),
|
||||
'gust': dict({
|
||||
'unit': 'KILOMETERS_PER_HOUR',
|
||||
'value': 14.0,
|
||||
}),
|
||||
'speed': dict({
|
||||
'unit': 'KILOMETERS_PER_HOUR',
|
||||
'value': 6.0,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
'display_date': dict({
|
||||
'day': 10,
|
||||
'month': 2,
|
||||
'year': 2025,
|
||||
}),
|
||||
'feels_like_max_temperature': dict({
|
||||
'degrees': 13.3,
|
||||
'unit': 'CELSIUS',
|
||||
}),
|
||||
'feels_like_min_temperature': dict({
|
||||
'degrees': 1.5,
|
||||
'unit': 'CELSIUS',
|
||||
}),
|
||||
'ice_thickness': dict({
|
||||
'thickness': 0.0,
|
||||
'unit': 'MILLIMETERS',
|
||||
}),
|
||||
'interval': dict({
|
||||
'end_time': '2025-02-11T15:00:00Z',
|
||||
'start_time': '2025-02-10T15:00:00Z',
|
||||
}),
|
||||
'max_heat_index': dict({
|
||||
'degrees': 13.3,
|
||||
'unit': 'CELSIUS',
|
||||
}),
|
||||
'max_temperature': dict({
|
||||
'degrees': 13.3,
|
||||
'unit': 'CELSIUS',
|
||||
}),
|
||||
'min_temperature': dict({
|
||||
'degrees': 1.5,
|
||||
'unit': 'CELSIUS',
|
||||
}),
|
||||
'moon_events': dict({
|
||||
'moon_phase': 'WAXING_GIBBOUS',
|
||||
'moonrise_times': list([
|
||||
'2025-02-10T23:54:17.713157984Z',
|
||||
]),
|
||||
'moonset_times': list([
|
||||
'2025-02-10T14:13:58.625181191Z',
|
||||
]),
|
||||
}),
|
||||
'nighttime_forecast': dict({
|
||||
'cloud_cover': 70,
|
||||
'ice_thickness': None,
|
||||
'interval': dict({
|
||||
'end_time': '2025-02-11T15:00:00Z',
|
||||
'start_time': '2025-02-11T03:00:00Z',
|
||||
}),
|
||||
'precipitation': dict({
|
||||
'probability': dict({
|
||||
'percent': 10,
|
||||
'type': 'RAIN_AND_SNOW',
|
||||
}),
|
||||
'qpf': dict({
|
||||
'quantity': 0.0,
|
||||
'unit': 'MILLIMETERS',
|
||||
}),
|
||||
'snow_qpf': None,
|
||||
}),
|
||||
'relative_humidity': 85,
|
||||
'thunderstorm_probability': 0,
|
||||
'uv_index': 0,
|
||||
'weather_condition': dict({
|
||||
'description': dict({
|
||||
'language_code': 'en',
|
||||
'text': 'Partly cloudy',
|
||||
}),
|
||||
'icon_base_uri': 'https://maps.gstatic.com/weather/v1/partly_clear',
|
||||
'type': 'PARTLY_CLOUDY',
|
||||
}),
|
||||
'wind': dict({
|
||||
'direction': dict({
|
||||
'cardinal': 'SOUTH_SOUTHWEST',
|
||||
'degrees': 201,
|
||||
}),
|
||||
'gust': dict({
|
||||
'unit': 'KILOMETERS_PER_HOUR',
|
||||
'value': 14.0,
|
||||
}),
|
||||
'speed': dict({
|
||||
'unit': 'KILOMETERS_PER_HOUR',
|
||||
'value': 6.0,
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
'sun_events': dict({
|
||||
'sunrise_time': '2025-02-10T15:02:35.703929582Z',
|
||||
'sunset_time': '2025-02-11T01:43:00.762932858Z',
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
'next_page_token': None,
|
||||
'time_zone': dict({
|
||||
'id': 'America/Los_Angeles',
|
||||
'version': None,
|
||||
}),
|
||||
}),
|
||||
'hourly_forecast_data': dict({
|
||||
'forecast_hours': list([
|
||||
dict({
|
||||
'air_pressure': dict({
|
||||
'mean_sea_level_millibars': 1019.13,
|
||||
}),
|
||||
'cloud_cover': 0,
|
||||
'dew_point': dict({
|
||||
'degrees': 2.7,
|
||||
'unit': 'CELSIUS',
|
||||
}),
|
||||
'display_date_time': dict({
|
||||
'day': 5,
|
||||
'hours': 15,
|
||||
'minutes': None,
|
||||
'month': 2,
|
||||
'nanos': None,
|
||||
'seconds': None,
|
||||
'time_zone': None,
|
||||
'utc_offset': '-28800s',
|
||||
'year': 2025,
|
||||
}),
|
||||
'feels_like_temperature': dict({
|
||||
'degrees': 12.0,
|
||||
'unit': 'CELSIUS',
|
||||
}),
|
||||
'heat_index': dict({
|
||||
'degrees': 12.7,
|
||||
'unit': 'CELSIUS',
|
||||
}),
|
||||
'ice_thickness': dict({
|
||||
'thickness': 0.0,
|
||||
'unit': 'MILLIMETERS',
|
||||
}),
|
||||
'interval': dict({
|
||||
'end_time': '2025-02-06T00:00:00Z',
|
||||
'start_time': '2025-02-05T23:00:00Z',
|
||||
}),
|
||||
'is_daytime': True,
|
||||
'precipitation': dict({
|
||||
'probability': dict({
|
||||
'percent': 0,
|
||||
'type': 'RAIN',
|
||||
}),
|
||||
'qpf': dict({
|
||||
'quantity': 0.0,
|
||||
'unit': 'MILLIMETERS',
|
||||
}),
|
||||
'snow_qpf': None,
|
||||
}),
|
||||
'relative_humidity': 51,
|
||||
'temperature': dict({
|
||||
'degrees': 12.7,
|
||||
'unit': 'CELSIUS',
|
||||
}),
|
||||
'thunderstorm_probability': 0,
|
||||
'uv_index': 1,
|
||||
'visibility': dict({
|
||||
'distance': 16.0,
|
||||
'unit': 'KILOMETERS',
|
||||
}),
|
||||
'weather_condition': dict({
|
||||
'description': dict({
|
||||
'language_code': 'en',
|
||||
'text': 'Sunny',
|
||||
}),
|
||||
'icon_base_uri': 'https://maps.gstatic.com/weather/v1/sunny',
|
||||
'type': 'CLEAR',
|
||||
}),
|
||||
'wet_bulb_temperature': dict({
|
||||
'degrees': 7.7,
|
||||
'unit': 'CELSIUS',
|
||||
}),
|
||||
'wind': dict({
|
||||
'direction': dict({
|
||||
'cardinal': 'NORTH_NORTHWEST',
|
||||
'degrees': 335,
|
||||
}),
|
||||
'gust': dict({
|
||||
'unit': 'KILOMETERS_PER_HOUR',
|
||||
'value': 19.0,
|
||||
}),
|
||||
'speed': dict({
|
||||
'unit': 'KILOMETERS_PER_HOUR',
|
||||
'value': 10.0,
|
||||
}),
|
||||
}),
|
||||
'wind_chill': dict({
|
||||
'degrees': 12.0,
|
||||
'unit': 'CELSIUS',
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
'next_page_token': None,
|
||||
'time_zone': dict({
|
||||
'id': 'America/Los_Angeles',
|
||||
'version': None,
|
||||
}),
|
||||
}),
|
||||
'observation_data': dict({
|
||||
'air_pressure': dict({
|
||||
'mean_sea_level_millibars': 1019.16,
|
||||
}),
|
||||
'cloud_cover': 0,
|
||||
'current_conditions_history': dict({
|
||||
'max_temperature': dict({
|
||||
'degrees': 14.3,
|
||||
'unit': 'CELSIUS',
|
||||
}),
|
||||
'min_temperature': dict({
|
||||
'degrees': 3.7,
|
||||
'unit': 'CELSIUS',
|
||||
}),
|
||||
'qpf': dict({
|
||||
'quantity': 0.0,
|
||||
'unit': 'MILLIMETERS',
|
||||
}),
|
||||
'temperature_change': dict({
|
||||
'degrees': -0.6,
|
||||
'unit': 'CELSIUS',
|
||||
}),
|
||||
}),
|
||||
'current_time': '2025-01-28T22:04:12.025273178Z',
|
||||
'dew_point': dict({
|
||||
'degrees': 1.1,
|
||||
'unit': 'CELSIUS',
|
||||
}),
|
||||
'feels_like_temperature': dict({
|
||||
'degrees': 13.1,
|
||||
'unit': 'CELSIUS',
|
||||
}),
|
||||
'heat_index': dict({
|
||||
'degrees': 13.7,
|
||||
'unit': 'CELSIUS',
|
||||
}),
|
||||
'is_daytime': True,
|
||||
'precipitation': dict({
|
||||
'probability': dict({
|
||||
'percent': 0,
|
||||
'type': 'RAIN',
|
||||
}),
|
||||
'qpf': dict({
|
||||
'quantity': 0.0,
|
||||
'unit': 'MILLIMETERS',
|
||||
}),
|
||||
'snow_qpf': None,
|
||||
}),
|
||||
'relative_humidity': 42,
|
||||
'temperature': dict({
|
||||
'degrees': 13.7,
|
||||
'unit': 'CELSIUS',
|
||||
}),
|
||||
'thunderstorm_probability': 0,
|
||||
'time_zone': dict({
|
||||
'id': 'America/Los_Angeles',
|
||||
'version': None,
|
||||
}),
|
||||
'uv_index': 1,
|
||||
'visibility': dict({
|
||||
'distance': 16.0,
|
||||
'unit': 'KILOMETERS',
|
||||
}),
|
||||
'weather_condition': dict({
|
||||
'description': dict({
|
||||
'language_code': 'en',
|
||||
'text': 'Sunny',
|
||||
}),
|
||||
'icon_base_uri': 'https://maps.gstatic.com/weather/v1/sunny',
|
||||
'type': 'CLEAR',
|
||||
}),
|
||||
'wind': dict({
|
||||
'direction': dict({
|
||||
'cardinal': 'NORTH_NORTHWEST',
|
||||
'degrees': 335,
|
||||
}),
|
||||
'gust': dict({
|
||||
'unit': 'KILOMETERS_PER_HOUR',
|
||||
'value': 18.0,
|
||||
}),
|
||||
'speed': dict({
|
||||
'unit': 'KILOMETERS_PER_HOUR',
|
||||
'value': 8.0,
|
||||
}),
|
||||
}),
|
||||
'wind_chill': dict({
|
||||
'degrees': 13.1,
|
||||
'unit': 'CELSIUS',
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
})
|
||||
# ---
|
||||
37
tests/components/google_weather/test_diagnostics.py
Normal file
37
tests/components/google_weather/test_diagnostics.py
Normal file
@@ -0,0 +1,37 @@
|
||||
"""Tests for the diagnostics data provided by the Google Weather integration."""
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from freezegun import freeze_time
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
from syrupy.filters import props
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.components.diagnostics import get_diagnostics_for_config_entry
|
||||
from tests.typing import ClientSessionGenerator
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def freeze_the_time():
|
||||
"""Freeze the time."""
|
||||
with freeze_time("2026-03-20 21:22:23", tz_offset=0):
|
||||
yield
|
||||
|
||||
|
||||
async def test_diagnostics(
|
||||
hass: HomeAssistant,
|
||||
hass_client: ClientSessionGenerator,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_google_weather_api: AsyncMock,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test diagnostics."""
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert await get_diagnostics_for_config_entry(
|
||||
hass, hass_client, mock_config_entry
|
||||
) == snapshot(exclude=props("entry_id"))
|
||||
118
tests/components/knx/test_entity.py
Normal file
118
tests/components/knx/test_entity.py
Normal file
@@ -0,0 +1,118 @@
|
||||
"""KNX base entity tests."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.knx.const import KNX_ADDRESS
|
||||
from homeassistant.const import STATE_OFF, EntityCategory, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from .conftest import KNXTestKit
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("config", "expected_entity_id", "expected_friendly_name"),
|
||||
[
|
||||
(
|
||||
{
|
||||
"name": "test",
|
||||
KNX_ADDRESS: "1/2/3",
|
||||
},
|
||||
"switch.test",
|
||||
"test",
|
||||
),
|
||||
(
|
||||
{
|
||||
KNX_ADDRESS: "1/2/3",
|
||||
},
|
||||
"switch.knx_1_2_3", # generated from unique_id
|
||||
None,
|
||||
),
|
||||
(
|
||||
{
|
||||
"name": "",
|
||||
KNX_ADDRESS: "1/2/3",
|
||||
},
|
||||
"switch.knx_1_2_3", # generated from unique_id
|
||||
None,
|
||||
),
|
||||
(
|
||||
{
|
||||
"default_entity_id": "switch.test_default_entity_id",
|
||||
KNX_ADDRESS: "1/2/3",
|
||||
},
|
||||
"switch.test_default_entity_id",
|
||||
None,
|
||||
),
|
||||
(
|
||||
{
|
||||
"name": "my_test_name",
|
||||
"default_entity_id": "switch.test_default_entity_id",
|
||||
KNX_ADDRESS: "1/2/3",
|
||||
},
|
||||
"switch.test_default_entity_id",
|
||||
"my_test_name",
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_yaml_entity_naming(
|
||||
hass: HomeAssistant,
|
||||
knx: KNXTestKit,
|
||||
config: dict[str, Any],
|
||||
expected_entity_id: str,
|
||||
expected_friendly_name: str | None,
|
||||
) -> None:
|
||||
"""Test KNX entity id and name setting from YAML configuration."""
|
||||
await knx.setup_integration({Platform.SWITCH: config})
|
||||
knx.assert_state(
|
||||
expected_entity_id,
|
||||
STATE_OFF,
|
||||
friendly_name=expected_friendly_name,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("config", "expected_entity_category"),
|
||||
[
|
||||
(
|
||||
{},
|
||||
None,
|
||||
),
|
||||
(
|
||||
{
|
||||
"entity_category": "diagnostic",
|
||||
},
|
||||
EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
(
|
||||
{
|
||||
"entity_category": "config",
|
||||
},
|
||||
EntityCategory.CONFIG,
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_yaml_entity_category(
|
||||
hass: HomeAssistant,
|
||||
knx: KNXTestKit,
|
||||
entity_registry: er.EntityRegistry,
|
||||
config: dict[str, Any],
|
||||
expected_entity_category: EntityCategory | None,
|
||||
) -> None:
|
||||
"""Test KNX entity category setting from YAML configuration."""
|
||||
await knx.setup_integration(
|
||||
{
|
||||
Platform.SWITCH: [
|
||||
{
|
||||
"default_entity_id": "switch.test",
|
||||
KNX_ADDRESS: "1/1/1",
|
||||
**config,
|
||||
},
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
entity = entity_registry.async_get("switch.test")
|
||||
assert entity.entity_category is expected_entity_category
|
||||
@@ -1325,56 +1325,6 @@
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[eve_shutter][button.eve_shutter_switch_20eci1701_sync_time-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'button.eve_shutter_switch_20eci1701_sync_time',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Sync time',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Sync time',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'sync_time',
|
||||
'unique_id': '00000000000004D2-0000000000000094-MatterNodeDevice-0-TimeSynchronizationSyncTimeButton-56-0',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[eve_shutter][button.eve_shutter_switch_20eci1701_sync_time-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Eve Shutter Switch 20ECI1701 Sync time',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.eve_shutter_switch_20eci1701_sync_time',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[eve_thermo_v4][button.eve_thermo_20ebp1701_identify-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
@@ -1426,56 +1376,6 @@
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[eve_thermo_v4][button.eve_thermo_20ebp1701_sync_time-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'button.eve_thermo_20ebp1701_sync_time',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Sync time',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Sync time',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'sync_time',
|
||||
'unique_id': '00000000000004D2-0000000000000021-MatterNodeDevice-0-TimeSynchronizationSyncTimeButton-56-0',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[eve_thermo_v4][button.eve_thermo_20ebp1701_sync_time-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Eve Thermo 20EBP1701 Sync time',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.eve_thermo_20ebp1701_sync_time',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[eve_thermo_v5][button.eve_thermo_20ecd1701_identify-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
@@ -1527,56 +1427,6 @@
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[eve_thermo_v5][button.eve_thermo_20ecd1701_sync_time-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'button.eve_thermo_20ecd1701_sync_time',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Sync time',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Sync time',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'sync_time',
|
||||
'unique_id': '00000000000004D2-000000000000000C-MatterNodeDevice-0-TimeSynchronizationSyncTimeButton-56-0',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[eve_thermo_v5][button.eve_thermo_20ecd1701_sync_time-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Eve Thermo 20ECD1701 Sync time',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.eve_thermo_20ecd1701_sync_time',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[eve_weather_sensor][button.eve_weather_identify_1-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
@@ -2085,56 +1935,6 @@
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[ikea_air_quality_monitor][button.alpstuga_air_quality_monitor_sync_time-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'button.alpstuga_air_quality_monitor_sync_time',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Sync time',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Sync time',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'sync_time',
|
||||
'unique_id': '00000000000004D2-0000000000000025-MatterNodeDevice-0-TimeSynchronizationSyncTimeButton-56-0',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[ikea_air_quality_monitor][button.alpstuga_air_quality_monitor_sync_time-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'ALPSTUGA air quality monitor Sync time',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.alpstuga_air_quality_monitor_sync_time',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[inovelli_vtm30][button.white_series_onoff_switch_identify_load_control-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
@@ -3045,56 +2845,6 @@
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[mock_leak_sensor][button.water_leak_detector_sync_time-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'button.water_leak_detector_sync_time',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Sync time',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Sync time',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'sync_time',
|
||||
'unique_id': '00000000000004D2-0000000000000020-MatterNodeDevice-0-TimeSynchronizationSyncTimeButton-56-0',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[mock_leak_sensor][button.water_leak_detector_sync_time-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Water Leak Detector Sync time',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.water_leak_detector_sync_time',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[mock_lock][button.mock_lock_identify-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
@@ -4461,56 +4211,6 @@
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[silabs_light_switch][button.light_switch_example_sync_time-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'button.light_switch_example_sync_time',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Sync time',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Sync time',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'sync_time',
|
||||
'unique_id': '00000000000004D2-000000000000008E-MatterNodeDevice-0-TimeSynchronizationSyncTimeButton-56-0',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[silabs_light_switch][button.light_switch_example_sync_time-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Light switch example Sync time',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.light_switch_example_sync_time',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[silabs_refrigerator][button.refrigerator_identify-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
"""Test Matter buttons."""
|
||||
"""Test Matter switches."""
|
||||
|
||||
from datetime import UTC, datetime
|
||||
from unittest.mock import MagicMock, call
|
||||
|
||||
from chip.clusters import Objects as clusters
|
||||
from chip.clusters.Types import NullValue
|
||||
from matter_server.client.models.node import MatterNode
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
@@ -12,7 +10,6 @@ from syrupy.assertion import SnapshotAssertion
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .common import snapshot_matter_entities
|
||||
|
||||
@@ -110,82 +107,3 @@ async def test_smoke_detector_self_test(
|
||||
endpoint_id=1,
|
||||
command=clusters.SmokeCoAlarm.Commands.SelfTestRequest(),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.freeze_time("2025-06-15T12:00:00+00:00")
|
||||
@pytest.mark.parametrize("node_fixture", ["ikea_air_quality_monitor"])
|
||||
async def test_time_sync_button(
|
||||
hass: HomeAssistant,
|
||||
matter_client: MagicMock,
|
||||
matter_node: MatterNode,
|
||||
) -> None:
|
||||
"""Test button entity is created for a Matter TimeSynchronization Cluster."""
|
||||
entity_id = "button.alpstuga_air_quality_monitor_sync_time"
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.attributes["friendly_name"] == "ALPSTUGA air quality monitor Sync time"
|
||||
# test press action
|
||||
await hass.services.async_call(
|
||||
"button",
|
||||
"press",
|
||||
{
|
||||
"entity_id": entity_id,
|
||||
},
|
||||
blocking=True,
|
||||
)
|
||||
assert matter_client.send_device_command.call_count == 3
|
||||
|
||||
# Compute expected values based on HA's configured timezone
|
||||
chip_epoch = datetime(2000, 1, 1, tzinfo=UTC)
|
||||
frozen_now = datetime(2025, 6, 15, 12, 0, 0, tzinfo=UTC)
|
||||
delta = frozen_now - chip_epoch
|
||||
expected_utc_us = (
|
||||
(delta.days * 86400 * 1_000_000)
|
||||
+ (delta.seconds * 1_000_000)
|
||||
+ delta.microseconds
|
||||
)
|
||||
ha_tz = dt_util.get_default_time_zone()
|
||||
local_now = frozen_now.astimezone(ha_tz)
|
||||
utc_offset_delta = local_now.utcoffset()
|
||||
utc_offset = int(utc_offset_delta.total_seconds()) if utc_offset_delta else 0
|
||||
dst_offset_delta = local_now.dst()
|
||||
dst_offset = int(dst_offset_delta.total_seconds()) if dst_offset_delta else 0
|
||||
standard_offset = utc_offset - dst_offset
|
||||
|
||||
# Verify SetTimeZone command
|
||||
assert matter_client.send_device_command.call_args_list[0] == call(
|
||||
node_id=matter_node.node_id,
|
||||
endpoint_id=0,
|
||||
command=clusters.TimeSynchronization.Commands.SetTimeZone(
|
||||
timeZone=[
|
||||
clusters.TimeSynchronization.Structs.TimeZoneStruct(
|
||||
offset=standard_offset,
|
||||
validAt=0,
|
||||
name=str(ha_tz),
|
||||
)
|
||||
]
|
||||
),
|
||||
)
|
||||
# Verify SetDSTOffset command
|
||||
assert matter_client.send_device_command.call_args_list[1] == call(
|
||||
node_id=matter_node.node_id,
|
||||
endpoint_id=0,
|
||||
command=clusters.TimeSynchronization.Commands.SetDSTOffset(
|
||||
DSTOffset=[
|
||||
clusters.TimeSynchronization.Structs.DSTOffsetStruct(
|
||||
offset=dst_offset,
|
||||
validStarting=0,
|
||||
validUntil=NullValue,
|
||||
)
|
||||
]
|
||||
),
|
||||
)
|
||||
# Verify SetUTCTime command
|
||||
assert matter_client.send_device_command.call_args_list[2] == call(
|
||||
node_id=matter_node.node_id,
|
||||
endpoint_id=0,
|
||||
command=clusters.TimeSynchronization.Commands.SetUTCTime(
|
||||
UTCTime=expected_utc_us,
|
||||
granularity=clusters.TimeSynchronization.Enums.GranularityEnum.kMicrosecondsGranularity,
|
||||
),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user