mirror of
https://github.com/home-assistant/core.git
synced 2026-06-24 23:55:21 +02:00
Compare commits
36 Commits
flic2
...
knx-button-ui
| Author | SHA1 | Date | |
|---|---|---|---|
| 73967ec3b8 | |||
| 7e7c846c78 | |||
| 7ff7c45b18 | |||
| 4481e90ee6 | |||
| 7670dcb227 | |||
| db0f9d1f54 | |||
| f1535bf8a4 | |||
| c460100fa1 | |||
| 53e2a9341f | |||
| 0e713d549c | |||
| a068574fe2 | |||
| f57418ed60 | |||
| 8d1bf68045 | |||
| c5bfad9bfe | |||
| 679b0ac2aa | |||
| 22a583c83f | |||
| d966f71832 | |||
| 08569420f6 | |||
| c1bcbca520 | |||
| c73c647162 | |||
| b2f1c38b6f | |||
| e8824bedf5 | |||
| 27b107f4a5 | |||
| 7536e8647f | |||
| 75c6058396 | |||
| 77533e5af5 | |||
| 31a1e7c5e1 | |||
| 20f4e7306b | |||
| 5c1ac08c92 | |||
| 5631c68069 | |||
| 8648611278 | |||
| 971d15be1e | |||
| b6f2429ff3 | |||
| c676e2a806 | |||
| a27ea536db | |||
| 22e25d9ce2 |
@@ -53,3 +53,4 @@ This repository contains the core of Home Assistant, a Python 3 based home autom
|
||||
- When validation guarantees a dict key exists, prefer direct key access (`data["key"]`) instead of `.get("key")` so contract violations are surfaced instead of silently masked.
|
||||
- Keep comments concise. Prefer one short line stating the non-obvious constraint, or no comment at all.
|
||||
- Do not add comments that just restate the code on the following line(s) (e.g. `# Check if initialized` above `if self.initialized:`). Comments should only explain why (non-obvious constraints, surprising behavior, or workarounds), never what. Never add comments that justify a change by referencing what the code looked like before.
|
||||
- Do not add section or divider comments (e.g. `# --- XYZ Triggers ---`) inside or outside of functions, since those can easily become stale and be misleading.
|
||||
|
||||
@@ -8,15 +8,11 @@ name: Check requirements (deterministic)
|
||||
|
||||
# yamllint disable-line rule:truthy
|
||||
on:
|
||||
# Auto-trigger on PRs that touch tracked requirement files is disabled
|
||||
# for now while we iterate — testing the workflow_run handoff to the
|
||||
# agentic stage is hard with an auto-trigger. Re-enable once the chain
|
||||
# has been validated end-to-end.
|
||||
# pull_request:
|
||||
# types: [opened, synchronize, reopened]
|
||||
# paths:
|
||||
# - "**/requirements*.txt"
|
||||
# - "homeassistant/package_constraints.txt"
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened]
|
||||
paths:
|
||||
- "requirements*.txt"
|
||||
- "homeassistant/package_constraints.txt"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pull_request_number:
|
||||
|
||||
@@ -42,3 +42,4 @@ This repository contains the core of Home Assistant, a Python 3 based home autom
|
||||
- When validation guarantees a dict key exists, prefer direct key access (`data["key"]`) instead of `.get("key")` so contract violations are surfaced instead of silently masked.
|
||||
- Keep comments concise. Prefer one short line stating the non-obvious constraint, or no comment at all.
|
||||
- Do not add comments that just restate the code on the following line(s) (e.g. `# Check if initialized` above `if self.initialized:`). Comments should only explain why (non-obvious constraints, surprising behavior, or workarounds), never what. Never add comments that justify a change by referencing what the code looked like before.
|
||||
- Do not add section or divider comments (e.g. `# --- XYZ Triggers ---`) inside or outside of functions, since those can easily become stale and be misleading.
|
||||
|
||||
Generated
-1
@@ -1900,7 +1900,6 @@ CLAUDE.md @home-assistant/core
|
||||
/tests/components/unifi_direct/ @tofuSCHNITZEL
|
||||
/homeassistant/components/unifi_discovery/ @RaHehl
|
||||
/tests/components/unifi_discovery/ @RaHehl
|
||||
/homeassistant/components/unifiled/ @florisvdk
|
||||
/homeassistant/components/unifiprotect/ @RaHehl
|
||||
/tests/components/unifiprotect/ @RaHehl
|
||||
/homeassistant/components/upb/ @gwww
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
"unifi_access",
|
||||
"unifi_direct",
|
||||
"unifi_discovery",
|
||||
"unifiled",
|
||||
"unifiprotect"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ PLATFORMS = [
|
||||
Platform.EVENT,
|
||||
Platform.MEDIA_PLAYER,
|
||||
Platform.NOTIFY,
|
||||
Platform.SELECT,
|
||||
Platform.SENSOR,
|
||||
Platform.SWITCH,
|
||||
Platform.TODO,
|
||||
|
||||
@@ -5,6 +5,16 @@
|
||||
"default": "mdi:chat-processing"
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"dropin": {
|
||||
"default": "mdi:account-multiple-plus",
|
||||
"state": {
|
||||
"all": "mdi:account-multiple-plus",
|
||||
"home": "mdi:home-plus",
|
||||
"off": "mdi:phone-off"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"voc_index": {
|
||||
"default": "mdi:molecule"
|
||||
|
||||
@@ -84,6 +84,7 @@ class AmazonNotifyEntity(AmazonEntity, NotifyEntity):
|
||||
entity_description: AmazonNotifyEntityDescription
|
||||
|
||||
@property
|
||||
@override
|
||||
def available(self) -> bool:
|
||||
"""Return if entity is available."""
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
"""Support for select entities."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, Final, override
|
||||
|
||||
from aioamazondevices.structures import AmazonDropInStatus
|
||||
|
||||
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .coordinator import AmazonConfigEntry, AmazonDevice, alexa_api_call
|
||||
from .entity import AmazonEntity
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class AmazonSelectEntityDescription(SelectEntityDescription):
|
||||
"""Alexa Devices select entity description."""
|
||||
|
||||
method: str
|
||||
|
||||
|
||||
SELECTS: Final = (
|
||||
AmazonSelectEntityDescription(
|
||||
key="dropin",
|
||||
translation_key="dropin",
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
method="set_dropin_status",
|
||||
# API values: "All", "Home", "Off"
|
||||
options=[status.value.lower() for status in AmazonDropInStatus],
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: AmazonConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up select entities based on a config entry."""
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
known_devices: set[str] = set()
|
||||
|
||||
def _check_device() -> None:
|
||||
current_devices = set(coordinator.data)
|
||||
new_devices = current_devices - known_devices
|
||||
if new_devices:
|
||||
known_devices.update(new_devices)
|
||||
select_entities = [
|
||||
AmazonSelectEntity(coordinator, serial_num, select_desc)
|
||||
for select_desc in SELECTS
|
||||
for serial_num in new_devices
|
||||
if select_desc.key
|
||||
in coordinator.data[serial_num].communication_settings
|
||||
]
|
||||
async_add_entities(select_entities)
|
||||
|
||||
_check_device()
|
||||
entry.async_on_unload(coordinator.async_add_listener(_check_device))
|
||||
|
||||
|
||||
class AmazonSelectEntity(AmazonEntity, SelectEntity):
|
||||
"""Representation of a select entity for the default Alexa device."""
|
||||
|
||||
entity_description: AmazonSelectEntityDescription
|
||||
|
||||
@property
|
||||
@override
|
||||
def options(self) -> list[str]:
|
||||
"""Return a list of available options."""
|
||||
if TYPE_CHECKING:
|
||||
assert self.entity_description.options is not None
|
||||
|
||||
return self.entity_description.options
|
||||
|
||||
@property
|
||||
def _device(self) -> AmazonDevice:
|
||||
"""Return the device."""
|
||||
return self.coordinator.data[self._serial_num]
|
||||
|
||||
@override
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Restore last known option."""
|
||||
await super().async_added_to_hass()
|
||||
self._attr_current_option = self._device.communication_settings[
|
||||
self.entity_description.key
|
||||
].lower()
|
||||
|
||||
@override
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
"""Change the selected option."""
|
||||
method = getattr(self.coordinator.api, self.entity_description.method)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
assert method is not None
|
||||
|
||||
async with alexa_api_call(self.coordinator):
|
||||
await method(self.device, AmazonDropInStatus(option.capitalize()))
|
||||
|
||||
self._attr_current_option = option
|
||||
self.async_write_ha_state()
|
||||
@@ -78,6 +78,16 @@
|
||||
"name": "Speak"
|
||||
}
|
||||
},
|
||||
"select": {
|
||||
"dropin": {
|
||||
"name": "Drop In",
|
||||
"state": {
|
||||
"all": "Allow drop in from anyone",
|
||||
"home": "Allow drop in from household members",
|
||||
"off": "Don't allow drop in"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"alarm": {
|
||||
"name": "Next alarm"
|
||||
@@ -140,6 +150,9 @@
|
||||
"invalid_sound_value": {
|
||||
"message": "Invalid sound {sound} specified"
|
||||
},
|
||||
"select_option_not_found": {
|
||||
"message": "Selected option not found: {option}"
|
||||
},
|
||||
"unknown_exception": {
|
||||
"message": "Unknown error occurred: {error}"
|
||||
}
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "cloud_polling",
|
||||
"loggers": ["pyaqvify"],
|
||||
"quality_scale": "platinum",
|
||||
"requirements": ["pyaqvify==0.0.11"]
|
||||
"requirements": ["pyaqvify==0.0.12"]
|
||||
}
|
||||
|
||||
@@ -174,6 +174,7 @@ class CCM15Climate(CoordinatorEntity[CCM15Coordinator], ClimateEntity):
|
||||
"""Set the fan mode."""
|
||||
await self.coordinator.async_set_fan_mode(self._ac_index, self.data, fan_mode)
|
||||
|
||||
@override
|
||||
async def async_set_swing_mode(self, swing_mode: str) -> None:
|
||||
"""Set the swing mode."""
|
||||
await self.coordinator.async_set_swing_mode(
|
||||
|
||||
@@ -84,7 +84,9 @@ from .const import ( # noqa: F401
|
||||
SWING_OFF,
|
||||
SWING_ON,
|
||||
SWING_VERTICAL,
|
||||
ClimateEntityCapabilityAttribute,
|
||||
ClimateEntityFeature,
|
||||
ClimateEntityStateAttribute,
|
||||
HVACAction,
|
||||
HVACMode,
|
||||
)
|
||||
@@ -242,16 +244,16 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
|
||||
_entity_component_unrecorded_attributes = frozenset(
|
||||
{
|
||||
ATTR_HVAC_MODES,
|
||||
ATTR_FAN_MODES,
|
||||
ATTR_SWING_MODES,
|
||||
ATTR_MIN_TEMP,
|
||||
ATTR_MAX_TEMP,
|
||||
ATTR_MIN_HUMIDITY,
|
||||
ATTR_MAX_HUMIDITY,
|
||||
ATTR_TARGET_HUMIDITY_STEP,
|
||||
ATTR_TARGET_TEMP_STEP,
|
||||
ATTR_PRESET_MODES,
|
||||
ClimateEntityCapabilityAttribute.HVAC_MODES,
|
||||
ClimateEntityCapabilityAttribute.FAN_MODES,
|
||||
ClimateEntityCapabilityAttribute.SWING_MODES,
|
||||
ClimateEntityCapabilityAttribute.MIN_TEMP,
|
||||
ClimateEntityCapabilityAttribute.MAX_TEMP,
|
||||
ClimateEntityCapabilityAttribute.MIN_HUMIDITY,
|
||||
ClimateEntityCapabilityAttribute.MAX_HUMIDITY,
|
||||
ClimateEntityCapabilityAttribute.TARGET_HUMIDITY_STEP,
|
||||
ClimateEntityCapabilityAttribute.TARGET_TEMP_STEP,
|
||||
ClimateEntityCapabilityAttribute.PRESET_MODES,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -315,32 +317,42 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
hass = self.hass
|
||||
|
||||
data: dict[str, Any] = {
|
||||
ATTR_HVAC_MODES: self.hvac_modes,
|
||||
ATTR_MIN_TEMP: show_temp(hass, self.min_temp, temperature_unit, precision),
|
||||
ATTR_MAX_TEMP: show_temp(hass, self.max_temp, temperature_unit, precision),
|
||||
ClimateEntityCapabilityAttribute.HVAC_MODES: self.hvac_modes,
|
||||
ClimateEntityCapabilityAttribute.MIN_TEMP: show_temp(
|
||||
hass, self.min_temp, temperature_unit, precision
|
||||
),
|
||||
ClimateEntityCapabilityAttribute.MAX_TEMP: show_temp(
|
||||
hass, self.max_temp, temperature_unit, precision
|
||||
),
|
||||
}
|
||||
|
||||
if target_temperature_step := self.target_temperature_step:
|
||||
data[ATTR_TARGET_TEMP_STEP] = target_temperature_step
|
||||
data[ClimateEntityCapabilityAttribute.TARGET_TEMP_STEP] = (
|
||||
target_temperature_step
|
||||
)
|
||||
|
||||
if ClimateEntityFeature.TARGET_HUMIDITY in supported_features:
|
||||
data[ATTR_MIN_HUMIDITY] = self.min_humidity
|
||||
data[ATTR_MAX_HUMIDITY] = self.max_humidity
|
||||
data[ClimateEntityCapabilityAttribute.MIN_HUMIDITY] = self.min_humidity
|
||||
data[ClimateEntityCapabilityAttribute.MAX_HUMIDITY] = self.max_humidity
|
||||
|
||||
if self.target_humidity_step is not None:
|
||||
data[ATTR_TARGET_HUMIDITY_STEP] = self.target_humidity_step
|
||||
data[ClimateEntityCapabilityAttribute.TARGET_HUMIDITY_STEP] = (
|
||||
self.target_humidity_step
|
||||
)
|
||||
|
||||
if ClimateEntityFeature.FAN_MODE in supported_features:
|
||||
data[ATTR_FAN_MODES] = self.fan_modes
|
||||
data[ClimateEntityCapabilityAttribute.FAN_MODES] = self.fan_modes
|
||||
|
||||
if ClimateEntityFeature.PRESET_MODE in supported_features:
|
||||
data[ATTR_PRESET_MODES] = self.preset_modes
|
||||
data[ClimateEntityCapabilityAttribute.PRESET_MODES] = self.preset_modes
|
||||
|
||||
if ClimateEntityFeature.SWING_MODE in supported_features:
|
||||
data[ATTR_SWING_MODES] = self.swing_modes
|
||||
data[ClimateEntityCapabilityAttribute.SWING_MODES] = self.swing_modes
|
||||
|
||||
if ClimateEntityFeature.SWING_HORIZONTAL_MODE in supported_features:
|
||||
data[ATTR_SWING_HORIZONTAL_MODES] = self.swing_horizontal_modes
|
||||
data[ClimateEntityCapabilityAttribute.SWING_HORIZONTAL_MODES] = (
|
||||
self.swing_horizontal_modes
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
@@ -355,13 +367,13 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
hass = self.hass
|
||||
|
||||
data: dict[str, str | float | None] = {
|
||||
ATTR_CURRENT_TEMPERATURE: show_temp(
|
||||
ClimateEntityStateAttribute.CURRENT_TEMPERATURE: show_temp(
|
||||
hass, self.current_temperature, temperature_unit, precision
|
||||
),
|
||||
}
|
||||
|
||||
if ClimateEntityFeature.TARGET_TEMPERATURE in supported_features:
|
||||
data[ATTR_TEMPERATURE] = show_temp(
|
||||
data[ClimateEntityStateAttribute.TEMPERATURE] = show_temp(
|
||||
hass,
|
||||
self.target_temperature,
|
||||
temperature_unit,
|
||||
@@ -369,33 +381,35 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
)
|
||||
|
||||
if ClimateEntityFeature.TARGET_TEMPERATURE_RANGE in supported_features:
|
||||
data[ATTR_TARGET_TEMP_HIGH] = show_temp(
|
||||
data[ClimateEntityStateAttribute.TARGET_TEMP_HIGH] = show_temp(
|
||||
hass, self.target_temperature_high, temperature_unit, precision
|
||||
)
|
||||
data[ATTR_TARGET_TEMP_LOW] = show_temp(
|
||||
data[ClimateEntityStateAttribute.TARGET_TEMP_LOW] = show_temp(
|
||||
hass, self.target_temperature_low, temperature_unit, precision
|
||||
)
|
||||
|
||||
if (current_humidity := self.current_humidity) is not None:
|
||||
data[ATTR_CURRENT_HUMIDITY] = current_humidity
|
||||
data[ClimateEntityStateAttribute.CURRENT_HUMIDITY] = current_humidity
|
||||
|
||||
if ClimateEntityFeature.TARGET_HUMIDITY in supported_features:
|
||||
data[ATTR_HUMIDITY] = self.target_humidity
|
||||
data[ClimateEntityStateAttribute.HUMIDITY] = self.target_humidity
|
||||
|
||||
if ClimateEntityFeature.FAN_MODE in supported_features:
|
||||
data[ATTR_FAN_MODE] = self.fan_mode
|
||||
data[ClimateEntityStateAttribute.FAN_MODE] = self.fan_mode
|
||||
|
||||
if hvac_action := self.hvac_action:
|
||||
data[ATTR_HVAC_ACTION] = hvac_action
|
||||
data[ClimateEntityStateAttribute.HVAC_ACTION] = hvac_action
|
||||
|
||||
if ClimateEntityFeature.PRESET_MODE in supported_features:
|
||||
data[ATTR_PRESET_MODE] = self.preset_mode
|
||||
data[ClimateEntityStateAttribute.PRESET_MODE] = self.preset_mode
|
||||
|
||||
if ClimateEntityFeature.SWING_MODE in supported_features:
|
||||
data[ATTR_SWING_MODE] = self.swing_mode
|
||||
data[ClimateEntityStateAttribute.SWING_MODE] = self.swing_mode
|
||||
|
||||
if ClimateEntityFeature.SWING_HORIZONTAL_MODE in supported_features:
|
||||
data[ATTR_SWING_HORIZONTAL_MODE] = self.swing_horizontal_mode
|
||||
data[ClimateEntityStateAttribute.SWING_HORIZONTAL_MODE] = (
|
||||
self.swing_horizontal_mode
|
||||
)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
@@ -137,6 +137,38 @@ SERVICE_SET_SWING_HORIZONTAL_MODE = "set_swing_horizontal_mode"
|
||||
SERVICE_SET_TEMPERATURE = "set_temperature"
|
||||
|
||||
|
||||
class ClimateEntityCapabilityAttribute(StrEnum):
|
||||
"""Capability attributes for climate entities."""
|
||||
|
||||
HVAC_MODES = "hvac_modes"
|
||||
MIN_TEMP = "min_temp"
|
||||
MAX_TEMP = "max_temp"
|
||||
TARGET_TEMP_STEP = "target_temp_step"
|
||||
MIN_HUMIDITY = "min_humidity"
|
||||
MAX_HUMIDITY = "max_humidity"
|
||||
TARGET_HUMIDITY_STEP = "target_humidity_step"
|
||||
FAN_MODES = "fan_modes"
|
||||
PRESET_MODES = "preset_modes"
|
||||
SWING_MODES = "swing_modes"
|
||||
SWING_HORIZONTAL_MODES = "swing_horizontal_modes"
|
||||
|
||||
|
||||
class ClimateEntityStateAttribute(StrEnum):
|
||||
"""State attributes for climate entities."""
|
||||
|
||||
CURRENT_TEMPERATURE = "current_temperature"
|
||||
TEMPERATURE = "temperature"
|
||||
TARGET_TEMP_HIGH = "target_temp_high"
|
||||
TARGET_TEMP_LOW = "target_temp_low"
|
||||
CURRENT_HUMIDITY = "current_humidity"
|
||||
HUMIDITY = "humidity"
|
||||
FAN_MODE = "fan_mode"
|
||||
HVAC_ACTION = "hvac_action"
|
||||
PRESET_MODE = "preset_mode"
|
||||
SWING_MODE = "swing_mode"
|
||||
SWING_HORIZONTAL_MODE = "swing_horizontal_mode"
|
||||
|
||||
|
||||
class ClimateEntityFeature(IntFlag):
|
||||
"""Supported features of the climate entity."""
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"title": "Doorbell",
|
||||
"triggers": {
|
||||
"rang": {
|
||||
"description": "Triggers after one or more doorbells rang.",
|
||||
"description": "Triggers when one or more doorbells ring.",
|
||||
"name": "Doorbell rang"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
"integration_type": "service",
|
||||
"iot_class": "cloud_polling",
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["python-dropbox-api==0.1.3"]
|
||||
"requirements": ["python-dropbox-api==0.1.4"]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""Data update coordinator for the Duco integration."""
|
||||
|
||||
from contextlib import suppress
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import cast, override
|
||||
@@ -32,6 +33,7 @@ class DucoData:
|
||||
nodes: dict[int, Node]
|
||||
node_actions: NodeListActionItemList
|
||||
rssi_wifi: int | None
|
||||
time_filter_remain: int | None
|
||||
|
||||
|
||||
class DucoCoordinator(DataUpdateCoordinator[DucoData]):
|
||||
@@ -39,6 +41,7 @@ class DucoCoordinator(DataUpdateCoordinator[DucoData]):
|
||||
|
||||
config_entry: DucoConfigEntry
|
||||
board_info: BoardInfo
|
||||
_supports_time_filter_remain: bool
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -55,6 +58,7 @@ class DucoCoordinator(DataUpdateCoordinator[DucoData]):
|
||||
update_interval=SCAN_INTERVAL,
|
||||
)
|
||||
self.client = client
|
||||
self._supports_time_filter_remain = True
|
||||
|
||||
@override
|
||||
async def _async_setup(self) -> None:
|
||||
@@ -126,8 +130,18 @@ class DucoCoordinator(DataUpdateCoordinator[DucoData]):
|
||||
else:
|
||||
rssi_wifi = lan_info.rssi_wifi
|
||||
|
||||
# Heat recovery info only backs the optional filter timer sensor, so
|
||||
# failures on this supplemental endpoint should not make the primary
|
||||
# node entities unavailable.
|
||||
time_filter_remain = None
|
||||
if self._supports_time_filter_remain:
|
||||
with suppress(DucoError):
|
||||
time_filter_remain = await self.client.async_get_time_filter_remaining()
|
||||
self._supports_time_filter_remain = time_filter_remain is not None
|
||||
|
||||
return DucoData(
|
||||
nodes={node.node_id: node for node in nodes},
|
||||
node_actions=node_actions,
|
||||
rssi_wifi=rssi_wifi,
|
||||
time_filter_remain=time_filter_remain,
|
||||
)
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"filter_remaining": {
|
||||
"default": "mdi:air-filter"
|
||||
},
|
||||
"iaq_co2": {
|
||||
"default": "mdi:molecule-co2"
|
||||
},
|
||||
|
||||
@@ -19,6 +19,7 @@ from homeassistant.const import (
|
||||
PERCENTAGE,
|
||||
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||
EntityCategory,
|
||||
UnitOfTime,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
@@ -46,6 +47,7 @@ class DucoSensorEntityDescription(SensorEntityDescription):
|
||||
class DucoBoxSensorEntityDescription(SensorEntityDescription):
|
||||
"""Duco sensor entity description for box-level diagnostic data."""
|
||||
|
||||
supported_fn: Callable[[DucoCoordinator], bool] = lambda _: True
|
||||
value_fn: Callable[[DucoCoordinator], int | float | None]
|
||||
|
||||
|
||||
@@ -137,6 +139,17 @@ SENSOR_DESCRIPTIONS: tuple[DucoSensorEntityDescription, ...] = (
|
||||
)
|
||||
|
||||
BOX_SENSOR_DESCRIPTIONS: tuple[DucoBoxSensorEntityDescription, ...] = (
|
||||
DucoBoxSensorEntityDescription(
|
||||
key="filter_remaining",
|
||||
translation_key="filter_remaining",
|
||||
device_class=SensorDeviceClass.DURATION,
|
||||
native_unit_of_measurement=UnitOfTime.DAYS,
|
||||
suggested_display_precision=0,
|
||||
supported_fn=lambda coordinator: (
|
||||
coordinator.data.time_filter_remain is not None
|
||||
),
|
||||
value_fn=lambda coordinator: coordinator.data.time_filter_remain,
|
||||
),
|
||||
DucoBoxSensorEntityDescription(
|
||||
key="rssi_wifi",
|
||||
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
||||
@@ -157,10 +170,13 @@ async def async_setup_entry(
|
||||
"""Set up Duco sensor entities."""
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
# Track the node IDs for which entities have already been created, so we
|
||||
# can detect both newly added and stale (deregistered) nodes on every
|
||||
# Track the node IDs for which node entities have already been created, so
|
||||
# we can detect both newly added and stale (deregistered) nodes on every
|
||||
# coordinator update.
|
||||
known_nodes: set[int] = set()
|
||||
# Track optional box-level sensors separately so they can still be added
|
||||
# later if their capability probe transiently failed during initial setup.
|
||||
known_box_sensors: set[tuple[int, str]] = set()
|
||||
|
||||
@callback
|
||||
def _async_add_new_entities() -> None:
|
||||
@@ -189,34 +205,47 @@ async def async_setup_entry(
|
||||
remove_config_entry_id=entry.entry_id,
|
||||
)
|
||||
known_nodes.difference_update(stale_node_ids)
|
||||
known_box_sensors.difference_update(
|
||||
{
|
||||
description_key
|
||||
for description_key in known_box_sensors
|
||||
if description_key[0] in stale_node_ids
|
||||
}
|
||||
)
|
||||
|
||||
new_entities: list[SensorEntity] = []
|
||||
for node in coordinator.data.nodes.values():
|
||||
if node.node_id in known_nodes:
|
||||
continue
|
||||
if node.general.node_type == NodeType.UNKNOWN:
|
||||
# Do not add the node to known_nodes so that it is re-evaluated
|
||||
# on every coordinator update. This allows entities to be
|
||||
# created automatically once a firmware update or library
|
||||
# update adds support for the device type.
|
||||
_LOGGER.debug(
|
||||
"Duco node %s (%s) has an unsupported device type and will be "
|
||||
"retried on subsequent coordinator updates",
|
||||
node.node_id,
|
||||
node.general.name,
|
||||
if node.node_id not in known_nodes:
|
||||
if node.general.node_type == NodeType.UNKNOWN:
|
||||
# Do not add the node to known_nodes so that it is re-evaluated
|
||||
# on every coordinator update. This allows entities to be
|
||||
# created automatically once a firmware update or library
|
||||
# update adds support for the device type.
|
||||
_LOGGER.debug(
|
||||
"Duco node %s (%s) has an unsupported device type and will be "
|
||||
"retried on subsequent coordinator updates",
|
||||
node.node_id,
|
||||
node.general.name,
|
||||
)
|
||||
continue
|
||||
known_nodes.add(node.node_id)
|
||||
new_entities.extend(
|
||||
DucoSensorEntity(coordinator, node, description)
|
||||
for description in SENSOR_DESCRIPTIONS
|
||||
if node.general.node_type in description.node_types
|
||||
)
|
||||
|
||||
if node.general.node_type != NodeType.BOX:
|
||||
continue
|
||||
known_nodes.add(node.node_id)
|
||||
new_entities.extend(
|
||||
DucoSensorEntity(coordinator, node, description)
|
||||
for description in SENSOR_DESCRIPTIONS
|
||||
if node.general.node_type in description.node_types
|
||||
)
|
||||
new_entities.extend(
|
||||
DucoBoxSensorEntity(coordinator, node, description)
|
||||
for description in BOX_SENSOR_DESCRIPTIONS
|
||||
if node.general.node_type == NodeType.BOX
|
||||
)
|
||||
|
||||
for description in BOX_SENSOR_DESCRIPTIONS:
|
||||
description_key = (node.node_id, description.key)
|
||||
if description_key in known_box_sensors:
|
||||
continue
|
||||
if not description.supported_fn(coordinator):
|
||||
continue
|
||||
known_box_sensors.add(description_key)
|
||||
new_entities.append(DucoBoxSensorEntity(coordinator, node, description))
|
||||
if new_entities:
|
||||
async_add_entities(new_entities)
|
||||
|
||||
|
||||
@@ -73,6 +73,9 @@
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"filter_remaining": {
|
||||
"name": "Filter remaining"
|
||||
},
|
||||
"iaq_co2": {
|
||||
"name": "CO2 air quality index"
|
||||
},
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
"""The eliqonline component."""
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"domain": "eliqonline",
|
||||
"name": "Eliqonline",
|
||||
"codeowners": [],
|
||||
"documentation": "https://www.home-assistant.io/integrations/eliqonline",
|
||||
"iot_class": "cloud_polling",
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["eliqonline==1.2.2"]
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
"""Monitors home energy use for the ELIQ Online service."""
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
import eliqonline
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_NAME, UnitOfPower
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONF_CHANNEL_ID = "channel_id"
|
||||
|
||||
DEFAULT_NAME = "ELIQ Online"
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=60)
|
||||
|
||||
PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_ACCESS_TOKEN): cv.string,
|
||||
vol.Required(CONF_CHANNEL_ID): cv.positive_int,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the ELIQ Online sensor."""
|
||||
access_token = config.get(CONF_ACCESS_TOKEN)
|
||||
name = config.get(CONF_NAME, DEFAULT_NAME)
|
||||
channel_id = config.get(CONF_CHANNEL_ID)
|
||||
session = async_get_clientsession(hass)
|
||||
|
||||
api = eliqonline.API(session=session, access_token=access_token)
|
||||
|
||||
try:
|
||||
_LOGGER.debug("Probing for access to ELIQ Online API")
|
||||
await api.get_data_now(channelid=channel_id)
|
||||
except OSError as error:
|
||||
_LOGGER.error("Could not access the ELIQ Online API: %s", error)
|
||||
return
|
||||
|
||||
async_add_entities([EliqSensor(api, channel_id, name)], True)
|
||||
|
||||
|
||||
class EliqSensor(SensorEntity):
|
||||
"""Implementation of an ELIQ Online sensor."""
|
||||
|
||||
_attr_device_class = SensorDeviceClass.POWER
|
||||
_attr_native_unit_of_measurement = UnitOfPower.WATT
|
||||
_attr_state_class = SensorStateClass.MEASUREMENT
|
||||
|
||||
def __init__(self, api, channel_id, name):
|
||||
"""Initialize the sensor."""
|
||||
self._attr_name = name
|
||||
self._api = api
|
||||
self._channel_id = channel_id
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Get the latest data."""
|
||||
try:
|
||||
response = await self._api.get_data_now(channelid=self._channel_id)
|
||||
self._attr_native_value = int(response["power"])
|
||||
_LOGGER.debug("Updated power from server %d W", self.native_value)
|
||||
except KeyError:
|
||||
_LOGGER.warning("Invalid response from ELIQ Online API")
|
||||
except (OSError, TimeoutError) as error:
|
||||
_LOGGER.warning("Could not connect to the ELIQ Online API: %s", error)
|
||||
@@ -6,7 +6,7 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/energieleser",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"quality_scale": "bronze",
|
||||
"quality_scale": "silver",
|
||||
"requirements": ["energieleser==0.1.4"],
|
||||
"zeroconf": [
|
||||
{
|
||||
|
||||
@@ -28,8 +28,10 @@ rules:
|
||||
status: exempt
|
||||
comment: Integration has no custom actions.
|
||||
config-entry-unloading: done
|
||||
docs-configuration-parameters: todo
|
||||
docs-installation-parameters: todo
|
||||
docs-configuration-parameters:
|
||||
status: exempt
|
||||
comment: Integration has no options flow or post-install configuration parameters.
|
||||
docs-installation-parameters: done
|
||||
entity-unavailable: done
|
||||
integration-owner: done
|
||||
log-when-unavailable: done
|
||||
|
||||
@@ -144,5 +144,5 @@
|
||||
"documentation": "https://www.home-assistant.io/integrations/govee_ble",
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_push",
|
||||
"requirements": ["govee-ble==1.2.0"]
|
||||
"requirements": ["govee-ble==1.4.0"]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"""Select platform for Qube Heat Pump."""
|
||||
|
||||
from typing import override
|
||||
|
||||
from homeassistant.components.select import SelectEntity
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
@@ -41,15 +43,18 @@ class QubeSGReadySelect(QubeEntity, SelectEntity):
|
||||
self._attr_unique_id = f"{entry.entry_id}-sg_ready_mode"
|
||||
|
||||
@property
|
||||
@override
|
||||
def available(self) -> bool:
|
||||
"""Return if entity is available."""
|
||||
return super().available and self.coordinator.data.sg_ready_mode is not None
|
||||
|
||||
@property
|
||||
@override
|
||||
def current_option(self) -> str | None:
|
||||
"""Return the current SG Ready mode."""
|
||||
return self.coordinator.data.sg_ready_mode
|
||||
|
||||
@override
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
"""Set the SG Ready mode."""
|
||||
try:
|
||||
|
||||
@@ -1,19 +1,24 @@
|
||||
"""Support for KNX button entities."""
|
||||
|
||||
from typing import override
|
||||
from typing import Any, override
|
||||
|
||||
from xknx.devices import RawValue as XknxRawValue
|
||||
from xknx.devices import ExposeSensor as XknxExposeSensor, 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.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.entity_platform import (
|
||||
AddConfigEntryEntitiesCallback,
|
||||
async_get_current_platform,
|
||||
)
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import CONF_PAYLOAD_LENGTH, KNX_ADDRESS, KNX_MODULE_KEY
|
||||
from .entity import KnxYamlEntity
|
||||
from .const import CONF_PAYLOAD_LENGTH, CONF_VALUE, DOMAIN, KNX_ADDRESS, KNX_MODULE_KEY
|
||||
from .entity import KnxUiEntity, KnxUiEntityPlatformController, KnxYamlEntity
|
||||
from .knx_module import KNXModule
|
||||
from .storage.const import CONF_DATA, CONF_ENTITY, CONF_GA_SEND
|
||||
from .storage.util import ConfigExtractor
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
@@ -21,27 +26,60 @@ async def async_setup_entry(
|
||||
config_entry: config_entries.ConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the KNX binary sensor platform."""
|
||||
"""Set up button(s) for KNX platform."""
|
||||
knx_module = hass.data[KNX_MODULE_KEY]
|
||||
config: list[ConfigType] = knx_module.config_yaml[Platform.BUTTON]
|
||||
platform = async_get_current_platform()
|
||||
knx_module.config_store.add_platform(
|
||||
platform=Platform.BUTTON,
|
||||
controller=KnxUiEntityPlatformController(
|
||||
knx_module=knx_module,
|
||||
entity_platform=platform,
|
||||
entity_class=KnxUiButton,
|
||||
),
|
||||
)
|
||||
|
||||
async_add_entities(KNXButton(knx_module, entity_config) for entity_config in config)
|
||||
entities: list[KnxYamlEntity | KnxUiEntity] = []
|
||||
if yaml_platform_config := knx_module.config_yaml.get(Platform.BUTTON):
|
||||
entities.extend(
|
||||
KnxYamlButton(knx_module, entity_config)
|
||||
for entity_config in yaml_platform_config
|
||||
)
|
||||
if ui_config := knx_module.config_store.data["entities"].get(Platform.BUTTON):
|
||||
entities.extend(
|
||||
KnxUiButton(knx_module, unique_id, config)
|
||||
for unique_id, config in ui_config.items()
|
||||
)
|
||||
if entities:
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class KNXButton(KnxYamlEntity, ButtonEntity):
|
||||
class _KnxButton(ButtonEntity):
|
||||
"""Representation of a KNX button."""
|
||||
|
||||
_device: XknxRawValue | XknxExposeSensor
|
||||
_payload: Any
|
||||
|
||||
@override
|
||||
async def async_press(self) -> None:
|
||||
"""Press the button."""
|
||||
await self._device.set(self._payload)
|
||||
|
||||
|
||||
class KnxYamlButton(_KnxButton, KnxYamlEntity):
|
||||
"""Representation of a KNX button configured via YAML."""
|
||||
|
||||
_device: XknxRawValue
|
||||
|
||||
def __init__(self, knx_module: KNXModule, config: ConfigType) -> None:
|
||||
"""Initialize a KNX button."""
|
||||
# dpt-value to payload conversion is done in schema validation for yaml config
|
||||
self._payload = config[CONF_PAYLOAD]
|
||||
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]
|
||||
super().__init__(
|
||||
knx_module=knx_module,
|
||||
unique_id=f"{self._device.remote_value.group_address}_{self._payload}",
|
||||
@@ -49,7 +87,39 @@ class KNXButton(KnxYamlEntity, ButtonEntity):
|
||||
entity_category=config.get(CONF_ENTITY_CATEGORY),
|
||||
)
|
||||
|
||||
@override
|
||||
async def async_press(self) -> None:
|
||||
"""Press the button."""
|
||||
await self._device.set(self._payload)
|
||||
|
||||
class KnxUiButton(_KnxButton, KnxUiEntity):
|
||||
"""Representation of a KNX button configured via the UI."""
|
||||
|
||||
_device: XknxRawValue | XknxExposeSensor
|
||||
|
||||
def __init__(
|
||||
self, knx_module: KNXModule, unique_id: str, config: dict[str, Any]
|
||||
) -> None:
|
||||
"""Initialize a KNX button."""
|
||||
knx_conf = ConfigExtractor(config[DOMAIN])
|
||||
button_data = knx_conf.get(CONF_DATA)
|
||||
if CONF_PAYLOAD in button_data and CONF_PAYLOAD_LENGTH in button_data:
|
||||
self._payload = int(button_data[CONF_PAYLOAD], 16)
|
||||
self._device = XknxRawValue(
|
||||
xknx=knx_module.xknx,
|
||||
name=config[CONF_ENTITY][CONF_NAME],
|
||||
payload_length=button_data[CONF_PAYLOAD_LENGTH],
|
||||
group_address=knx_conf.get_write(CONF_GA_SEND),
|
||||
)
|
||||
else:
|
||||
dpt_string = knx_conf.get_dpt(CONF_GA_SEND)
|
||||
self._payload = button_data[CONF_VALUE]
|
||||
self._device = XknxExposeSensor(
|
||||
xknx=knx_module.xknx,
|
||||
name=config[CONF_ENTITY][CONF_NAME],
|
||||
value_type=dpt_string,
|
||||
group_address=knx_conf.get_write(CONF_GA_SEND),
|
||||
respond_to_read=False,
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
knx_module=knx_module,
|
||||
unique_id=unique_id,
|
||||
entity_config=config[CONF_ENTITY],
|
||||
)
|
||||
|
||||
@@ -26,6 +26,7 @@ KNX_ADDRESS: Final = "address"
|
||||
CONF_INVERT: Final = "invert"
|
||||
CONF_KNX_EXPOSE: Final = "expose"
|
||||
CONF_KNX_INDIVIDUAL_ADDRESS: Final = "individual_address"
|
||||
CONF_VALUE: Final = "value"
|
||||
|
||||
##
|
||||
# Connection constants
|
||||
@@ -178,6 +179,7 @@ SUPPORTED_PLATFORMS_YAML: Final = {
|
||||
|
||||
SUPPORTED_PLATFORMS_UI: Final = {
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.BUTTON,
|
||||
Platform.CLIMATE,
|
||||
Platform.COVER,
|
||||
Platform.DATE,
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
from collections.abc import Mapping
|
||||
from functools import cache
|
||||
from typing import Literal, TypedDict
|
||||
from typing import Literal, NotRequired, TypedDict, cast
|
||||
|
||||
from xknx.dpt import DPTBase, DPTComplex, DPTEnum, DPTNumeric
|
||||
from xknx.dpt import DPTBase, DPTComplex, DPTComplexFieldSchema, DPTEnum, DPTNumeric
|
||||
from xknx.dpt.dpt_16 import DPTString
|
||||
|
||||
from homeassistant.components.sensor import SensorDeviceClass, SensorStateClass
|
||||
@@ -24,15 +24,28 @@ class DPTInfo(TypedDict):
|
||||
sensor_device_class: SensorDeviceClass | None
|
||||
sensor_state_class: SensorStateClass | None
|
||||
|
||||
payload_length: int
|
||||
|
||||
# numeric specific
|
||||
min: NotRequired[float]
|
||||
max: NotRequired[float]
|
||||
step: NotRequired[float]
|
||||
|
||||
# enum specific
|
||||
options: NotRequired[list[str]]
|
||||
|
||||
# complex specific
|
||||
schema: NotRequired[list[DPTComplexFieldSchema]]
|
||||
|
||||
|
||||
@cache
|
||||
def get_supported_dpts() -> Mapping[str, DPTInfo]:
|
||||
"""Return a mapping of supported DPTs with HA specific attributes."""
|
||||
dpts = {}
|
||||
dpts: dict[str, DPTInfo] = {}
|
||||
for dpt_class in DPTBase.dpt_class_tree():
|
||||
dpt_number_str = dpt_class.dpt_number_str()
|
||||
ha_dpt_class = _ha_dpt_class(dpt_class)
|
||||
dpts[dpt_number_str] = DPTInfo(
|
||||
info = DPTInfo(
|
||||
dpt_class=ha_dpt_class,
|
||||
main=dpt_class.dpt_main_number, # type: ignore[typeddict-item] # checked in xknx unit tests
|
||||
sub=dpt_class.dpt_sub_number,
|
||||
@@ -40,7 +53,15 @@ def get_supported_dpts() -> Mapping[str, DPTInfo]:
|
||||
unit=_sensor_unit_overrides.get(dpt_number_str, dpt_class.unit),
|
||||
sensor_device_class=_sensor_device_classes.get(dpt_number_str),
|
||||
sensor_state_class=_get_sensor_state_class(ha_dpt_class, dpt_number_str),
|
||||
payload_length=dpt_class.payload_length,
|
||||
)
|
||||
if ha_dpt_class == "numeric":
|
||||
_add_numeric_details(info, cast(type[DPTNumeric], dpt_class))
|
||||
elif ha_dpt_class == "enum":
|
||||
_add_enum_details(info, cast(type[DPTEnum], dpt_class))
|
||||
elif ha_dpt_class == "complex":
|
||||
_add_complex_details(info, cast(type[DPTComplex], dpt_class))
|
||||
dpts[dpt_number_str] = info
|
||||
return dpts
|
||||
|
||||
|
||||
@@ -57,6 +78,23 @@ def _ha_dpt_class(dpt_cls: type[DPTBase]) -> HaDptClass:
|
||||
raise ValueError("Unsupported DPT class")
|
||||
|
||||
|
||||
def _add_numeric_details(dpt_info: DPTInfo, dpt_cls: type[DPTNumeric]) -> None:
|
||||
"""Add numeric specific details to the DPTInfo."""
|
||||
dpt_info["min"] = dpt_cls.value_min
|
||||
dpt_info["max"] = dpt_cls.value_max
|
||||
dpt_info["step"] = dpt_cls.resolution
|
||||
|
||||
|
||||
def _add_enum_details(dpt_info: DPTInfo, dpt_cls: type[DPTEnum]) -> None:
|
||||
"""Add enum specific details to the DPTInfo."""
|
||||
dpt_info["options"] = [o.name.lower() for o in dpt_cls.get_valid_values()]
|
||||
|
||||
|
||||
def _add_complex_details(dpt_info: DPTInfo, dpt_cls: type[DPTComplex]) -> None:
|
||||
"""Add complex specific details to the DPTInfo."""
|
||||
dpt_info["schema"] = dpt_cls.get_dict_schema()
|
||||
|
||||
|
||||
_sensor_device_classes: Mapping[str, SensorDeviceClass] = {
|
||||
"7.011": SensorDeviceClass.DISTANCE,
|
||||
"7.012": SensorDeviceClass.CURRENT,
|
||||
|
||||
@@ -57,6 +57,7 @@ from .const import (
|
||||
CONF_RESPOND_TO_READ,
|
||||
CONF_STATE_ADDRESS,
|
||||
CONF_SYNC_STATE,
|
||||
CONF_VALUE,
|
||||
KNX_ADDRESS,
|
||||
ClimateConf,
|
||||
ColorTempModes,
|
||||
@@ -98,9 +99,12 @@ def _max_payload_value(payload_length: int) -> int:
|
||||
|
||||
|
||||
def button_payload_sub_validator(entity_config: OrderedDict) -> OrderedDict:
|
||||
"""Validate a button entity payload configuration."""
|
||||
"""Validate a button entity payload configuration.
|
||||
|
||||
Returns raw payload and length from value and type (DPT), if given.
|
||||
"""
|
||||
if _type := entity_config.get(CONF_TYPE):
|
||||
_payload = entity_config[ButtonSchema.CONF_VALUE]
|
||||
_payload = entity_config[CONF_VALUE]
|
||||
if (transcoder := DPTBase.parse_transcoder(_type)) is None:
|
||||
raise vol.Invalid(f"'type: {_type}' is not a valid sensor type.")
|
||||
entity_config[CONF_PAYLOAD_LENGTH] = transcoder.payload_length
|
||||
@@ -234,8 +238,6 @@ class ButtonSchema(KNXPlatformSchema):
|
||||
|
||||
PLATFORM = Platform.BUTTON
|
||||
|
||||
CONF_VALUE = "value"
|
||||
|
||||
payload_or_value_msg = f"Please use only one of `{CONF_PAYLOAD}` or `{CONF_VALUE}`"
|
||||
length_or_type_msg = (
|
||||
f"Please use only one of `{CONF_PAYLOAD_LENGTH}` or `{CONF_TYPE}`"
|
||||
|
||||
@@ -19,6 +19,9 @@ CONF_GA_TIME: Final = "ga_time"
|
||||
|
||||
CONF_GA_STEP: Final = "ga_step"
|
||||
|
||||
# Button
|
||||
CONF_GA_SEND: Final = "ga_send"
|
||||
|
||||
# Climate
|
||||
CONF_GA_TEMPERATURE_CURRENT: Final = "ga_temperature_current"
|
||||
CONF_GA_HUMIDITY_CURRENT: Final = "ga_humidity_current"
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
from enum import StrEnum, unique
|
||||
|
||||
import voluptuous as vol
|
||||
from xknx.dpt import DPTNumeric
|
||||
from xknx.dpt import DPTBase, DPTBinary, DPTNumeric
|
||||
from xknx.exceptions import ConversionError
|
||||
|
||||
from homeassistant.components.climate import HVACMode
|
||||
from homeassistant.components.number import (
|
||||
@@ -36,9 +37,11 @@ from ..const import (
|
||||
CONF_CONTEXT_TIMEOUT,
|
||||
CONF_IGNORE_INTERNAL_STATE,
|
||||
CONF_INVERT,
|
||||
CONF_PAYLOAD_LENGTH,
|
||||
CONF_RESET_AFTER,
|
||||
CONF_RESPOND_TO_READ,
|
||||
CONF_SYNC_STATE,
|
||||
CONF_VALUE,
|
||||
DOMAIN,
|
||||
SUPPORTED_PLATFORMS_UI,
|
||||
ClimateConf,
|
||||
@@ -92,6 +95,7 @@ from .const import (
|
||||
CONF_GA_RED_SWITCH,
|
||||
CONF_GA_SATURATION,
|
||||
CONF_GA_SCENE,
|
||||
CONF_GA_SEND,
|
||||
CONF_GA_SENSOR,
|
||||
CONF_GA_SETPOINT_SHIFT,
|
||||
CONF_GA_SPEED,
|
||||
@@ -115,6 +119,7 @@ from .knx_selector import (
|
||||
GASelector,
|
||||
GroupSelect,
|
||||
GroupSelectOption,
|
||||
KnxPayloadSelector,
|
||||
KNXSectionFlat,
|
||||
SyncStateSelector,
|
||||
)
|
||||
@@ -169,6 +174,55 @@ BINARY_SENSOR_KNX_SCHEMA = vol.Schema(
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _button_data_sub_validator(config: dict) -> dict:
|
||||
"""Validate data matching configured DPT."""
|
||||
dpt = config[CONF_GA_SEND].get(CONF_DPT)
|
||||
transcoder = None
|
||||
if dpt:
|
||||
transcoder = DPTBase.parse_transcoder(dpt)
|
||||
assert transcoder is not None # already checked by GASelector
|
||||
|
||||
if CONF_VALUE in config[CONF_DATA]:
|
||||
try:
|
||||
transcoder.to_knx(config[CONF_DATA][CONF_VALUE])
|
||||
except ConversionError as ex:
|
||||
raise vol.Invalid(
|
||||
f"Value invalid for DPT {transcoder.dpt_number_str()}",
|
||||
path=([CONF_DATA]),
|
||||
) from ex
|
||||
elif CONF_PAYLOAD_LENGTH in config[CONF_DATA]:
|
||||
length = config[CONF_DATA][CONF_PAYLOAD_LENGTH]
|
||||
if length != transcoder.payload_length or (
|
||||
length != 0 and transcoder.payload_type is DPTBinary
|
||||
):
|
||||
raise vol.Invalid(
|
||||
f"Payload length invalid for DPT {transcoder.dpt_number_str()}",
|
||||
path=([CONF_DATA]),
|
||||
)
|
||||
return config
|
||||
# without DPT only raw allowed -> payload + payload_length (checked by KnxPayloadSelector)
|
||||
if CONF_PAYLOAD_LENGTH in config[CONF_DATA]:
|
||||
return config
|
||||
raise vol.Invalid("Invalid configuration for button entity")
|
||||
|
||||
|
||||
BUTTON_KNX_SCHEMA = AllSerializeFirst(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_GA_SEND): GASelector(
|
||||
state=False,
|
||||
write_required=True,
|
||||
passive=False,
|
||||
dpt=["numeric", "enum", "complex", "string"],
|
||||
dpt_required=False, # for raw payload support
|
||||
),
|
||||
vol.Required(CONF_DATA): KnxPayloadSelector(ga_path=CONF_GA_SEND),
|
||||
},
|
||||
),
|
||||
_button_data_sub_validator,
|
||||
)
|
||||
|
||||
COVER_KNX_SCHEMA = AllSerializeFirst(
|
||||
vol.Schema(
|
||||
{
|
||||
@@ -741,6 +795,7 @@ SENSOR_KNX_SCHEMA = AllSerializeFirst(
|
||||
|
||||
KNX_SCHEMA_FOR_PLATFORM = {
|
||||
Platform.BINARY_SENSOR: BINARY_SENSOR_KNX_SCHEMA,
|
||||
Platform.BUTTON: BUTTON_KNX_SCHEMA,
|
||||
Platform.CLIMATE: CLIMATE_KNX_SCHEMA,
|
||||
Platform.COVER: COVER_KNX_SCHEMA,
|
||||
Platform.DATE: DATE_KNX_SCHEMA,
|
||||
|
||||
@@ -6,6 +6,9 @@ from typing import Any, override
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import CONF_PAYLOAD
|
||||
|
||||
from ..const import CONF_PAYLOAD_LENGTH, CONF_VALUE
|
||||
from ..dpt import HaDptClass, get_supported_dpts
|
||||
from ..validation import ga_validator, maybe_ga_validator, sync_state_validator
|
||||
from .const import CONF_DPT, CONF_GA_PASSIVE, CONF_GA_STATE, CONF_GA_WRITE
|
||||
@@ -159,7 +162,11 @@ class GroupSelect(KNXSelectorBase):
|
||||
|
||||
|
||||
class GASelector(KNXSelectorBase):
|
||||
"""Selector for a KNX group address structure."""
|
||||
"""Selector for a KNX group address structure.
|
||||
|
||||
`dpt_required` optional dpt only apply to dpt-class lists, enums are always required.
|
||||
`valid_dpt` is used in frontend to filter dropdown menu - no validation is done.
|
||||
"""
|
||||
|
||||
selector_type = "knx_group_address"
|
||||
|
||||
@@ -171,6 +178,7 @@ class GASelector(KNXSelectorBase):
|
||||
write_required: bool = False,
|
||||
state_required: bool = False,
|
||||
dpt: type[Enum] | list[HaDptClass] | None = None,
|
||||
dpt_required: bool = True,
|
||||
valid_dpt: str | Iterable[str] | None = None,
|
||||
) -> None:
|
||||
"""Initialize the group address selector."""
|
||||
@@ -180,7 +188,7 @@ class GASelector(KNXSelectorBase):
|
||||
self.write_required = write_required
|
||||
self.state_required = state_required
|
||||
self.dpt = dpt
|
||||
# valid_dpt is used in frontend to filter dropdown menu - no validation is done
|
||||
self.dpt_required = dpt_required
|
||||
self.valid_dpt = (valid_dpt,) if isinstance(valid_dpt, str) else valid_dpt
|
||||
|
||||
self.schema = self.build_schema()
|
||||
@@ -196,6 +204,7 @@ class GASelector(KNXSelectorBase):
|
||||
}
|
||||
if self.dpt is not None:
|
||||
if isinstance(self.dpt, list):
|
||||
# optional / required is not passed to FE - only validated in BE
|
||||
options["dptClasses"] = self.dpt
|
||||
else:
|
||||
options["dptSelect"] = [
|
||||
@@ -267,7 +276,8 @@ class GASelector(KNXSelectorBase):
|
||||
"""Add DPT validator to the schema."""
|
||||
if self.dpt is not None:
|
||||
if isinstance(self.dpt, list):
|
||||
schema[vol.Required(CONF_DPT)] = vol.In(get_supported_dpts())
|
||||
marker = vol.Required if self.dpt_required else vol.Optional
|
||||
schema[marker(CONF_DPT)] = vol.In(get_supported_dpts())
|
||||
else:
|
||||
schema[vol.Required(CONF_DPT)] = vol.In(
|
||||
{item.value for item in self.dpt}
|
||||
@@ -300,3 +310,64 @@ class SyncStateSelector(KNXSelectorBase):
|
||||
if not self.allow_false and not data:
|
||||
raise vol.Invalid(f"Sync state cannot be {data}")
|
||||
return self.schema(data)
|
||||
|
||||
|
||||
class KnxPayloadSelector(KNXSelectorBase):
|
||||
"""Selector for KNX payload configuration.
|
||||
|
||||
Raw payloads are stored as hex strings.
|
||||
"""
|
||||
|
||||
schema = vol.Any(
|
||||
{
|
||||
vol.Required(CONF_VALUE): object,
|
||||
},
|
||||
{
|
||||
vol.Required(CONF_PAYLOAD): str,
|
||||
vol.Required(CONF_PAYLOAD_LENGTH): vol.All(int, vol.Range(min=0, max=14)),
|
||||
},
|
||||
)
|
||||
selector_type = "knx_payload"
|
||||
|
||||
def __init__(self, ga_path: str) -> None:
|
||||
"""Initialize the KNX payload selector."""
|
||||
self.ga_path = ga_path
|
||||
|
||||
@override
|
||||
def serialize(self) -> dict[str, Any]:
|
||||
"""Serialize the selector to a dictionary."""
|
||||
return {
|
||||
"type": self.selector_type,
|
||||
"ga_path": self.ga_path,
|
||||
}
|
||||
|
||||
@override
|
||||
def __call__(self, data: Any) -> Any:
|
||||
"""Validate the passed data."""
|
||||
validated = self.schema(data)
|
||||
if CONF_PAYLOAD in validated and CONF_PAYLOAD_LENGTH in validated:
|
||||
payload = validated[CONF_PAYLOAD]
|
||||
payload_length = validated[CONF_PAYLOAD_LENGTH]
|
||||
try:
|
||||
int_payload = int(payload, 16)
|
||||
except ValueError as ex:
|
||||
raise vol.Invalid(f"Invalid payload format: {payload}") from ex
|
||||
validated[CONF_PAYLOAD] = hex(int_payload) # prepends "0x" if not present
|
||||
|
||||
if int_payload < 0:
|
||||
raise vol.Invalid(f"Payload cannot be negative: {payload}")
|
||||
if payload_length == 0:
|
||||
# DPT 1,2,3 is marked length 0, has 6 bit size
|
||||
if int_payload > 63:
|
||||
raise vol.Invalid(
|
||||
f"Payload exceeds DPT 1,2,3 limit of 0x3f (63): {payload}"
|
||||
)
|
||||
else:
|
||||
max_payload = (1 << (payload_length * 8)) - 1
|
||||
if int_payload > max_payload:
|
||||
raise vol.Invalid(
|
||||
f"Payload {payload} exceeds possible maximum for "
|
||||
f"length {payload_length}: {hex(max_payload)}"
|
||||
)
|
||||
# CONF_VALUE branch needs subvalidator as we don't have the DPT available here
|
||||
return validated
|
||||
|
||||
@@ -453,6 +453,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"button": {
|
||||
"description": "Entity for sending predefined values.",
|
||||
"knx": {
|
||||
"data": {
|
||||
"description": "The value sent when the button is pressed. The format of the value depends on the DPT of the configured address.",
|
||||
"label": "Data"
|
||||
},
|
||||
"ga_send": {
|
||||
"description": "Group address the value is sent to.",
|
||||
"label": "Address"
|
||||
}
|
||||
}
|
||||
},
|
||||
"climate": {
|
||||
"description": "The KNX climate platform is used as an interface to heating actuators, HVAC gateways, etc.",
|
||||
"knx": {
|
||||
@@ -991,7 +1004,7 @@
|
||||
"label": "[%key:component::knx::config_panel::entities::create::_::knx::respond_to_read::label%]"
|
||||
},
|
||||
"section_advanced_options": {
|
||||
"title": "Advanced options"
|
||||
"title": "Additional settings"
|
||||
},
|
||||
"show_raw_values": "Show raw values",
|
||||
"title": "Add exposure",
|
||||
@@ -1014,6 +1027,19 @@
|
||||
"project": {
|
||||
"description": "Inspect imported group addresses",
|
||||
"title": "Project"
|
||||
},
|
||||
"selectors": {
|
||||
"knx-payload-selector": {
|
||||
"dpt_missing": "No DPT selected – Typed mode not available",
|
||||
"mode": {
|
||||
"label": "Payload format",
|
||||
"raw": "Raw payload",
|
||||
"typed": "Typed value"
|
||||
},
|
||||
"raw_length": "Payload length",
|
||||
"raw_length_description": "Length of the raw payload in bytes. For DPT 1, 2 and 3 use `0`.",
|
||||
"raw_payload": "Raw payload"
|
||||
}
|
||||
}
|
||||
},
|
||||
"device_automation": {
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
"title": "Lawn mower",
|
||||
"triggers": {
|
||||
"errored": {
|
||||
"description": "Triggers after one or more lawn mowers encounter an error.",
|
||||
"description": "Triggers when one or more lawn mowers encounter an error.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::lawn_mower::common::trigger_behavior_name%]"
|
||||
@@ -108,7 +108,7 @@
|
||||
"name": "Lawn mower encountered an error"
|
||||
},
|
||||
"paused_mowing": {
|
||||
"description": "Triggers after one or more lawn mowers pause mowing.",
|
||||
"description": "Triggers when one or more lawn mowers pause mowing.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::lawn_mower::common::trigger_behavior_name%]"
|
||||
@@ -120,7 +120,7 @@
|
||||
"name": "Lawn mower paused mowing"
|
||||
},
|
||||
"returned_to_dock": {
|
||||
"description": "Triggers after one or more lawn mowers have returned to dock.",
|
||||
"description": "Triggers when one or more lawn mowers have returned to dock.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::lawn_mower::common::trigger_behavior_name%]"
|
||||
@@ -132,7 +132,7 @@
|
||||
"name": "Lawn mower returned to dock"
|
||||
},
|
||||
"started_mowing": {
|
||||
"description": "Triggers after one or more lawn mowers start mowing.",
|
||||
"description": "Triggers when one or more lawn mowers start mowing.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::lawn_mower::common::trigger_behavior_name%]"
|
||||
@@ -144,7 +144,7 @@
|
||||
"name": "Lawn mower started mowing"
|
||||
},
|
||||
"started_returning": {
|
||||
"description": "Triggers after one or more lawn mowers start returning to dock.",
|
||||
"description": "Triggers when one or more lawn mowers start returning to dock.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::lawn_mower::common::trigger_behavior_name%]"
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
"integration_type": "hub",
|
||||
"iot_class": "cloud_push",
|
||||
"loggers": ["thinqconnect"],
|
||||
"requirements": ["thinqconnect==1.0.12"]
|
||||
"requirements": ["thinqconnect==1.0.13"]
|
||||
}
|
||||
|
||||
@@ -370,7 +370,7 @@
|
||||
"name": "Media"
|
||||
}
|
||||
},
|
||||
"name": "Play media"
|
||||
"name": "Play specified media"
|
||||
},
|
||||
"repeat_set": {
|
||||
"description": "Sets the repeat mode of a media player.",
|
||||
|
||||
@@ -11,7 +11,10 @@
|
||||
}
|
||||
},
|
||||
"holiday_mode": {
|
||||
"default": "mdi:beach"
|
||||
"default": "mdi:beach",
|
||||
"state": {
|
||||
"off": "mdi:home"
|
||||
}
|
||||
},
|
||||
"overheat_protection": {
|
||||
"default": "mdi:fire",
|
||||
@@ -20,7 +23,10 @@
|
||||
}
|
||||
},
|
||||
"standby": {
|
||||
"default": "mdi:power-sleep"
|
||||
"default": "mdi:power-sleep",
|
||||
"state": {
|
||||
"off": "mdi:power"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from collections.abc import Callable, Coroutine
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
from typing import Any, override
|
||||
|
||||
from aiomelcloudhome import ATAUnit, ATWUnit, MELCloudHome
|
||||
from aiomelcloudhome.exceptions import (
|
||||
@@ -215,15 +215,18 @@ class ATASwitch(MelCloudHomeATAUnitEntity, SwitchEntity):
|
||||
self._attr_unique_id = f"{unit.id}_{entity_description.key}"
|
||||
|
||||
@property
|
||||
@override
|
||||
def available(self) -> bool:
|
||||
"""Return if the entity is available."""
|
||||
return super().available and self.entity_description.available_fn(self.unit)
|
||||
|
||||
@property
|
||||
@override
|
||||
def is_on(self) -> bool | None:
|
||||
"""Return the state of the switch."""
|
||||
return self.entity_description.is_on_fn(self.unit)
|
||||
|
||||
@override
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Enable the protection."""
|
||||
await _perform_action(
|
||||
@@ -231,6 +234,7 @@ class ATASwitch(MelCloudHomeATAUnitEntity, SwitchEntity):
|
||||
self.entity_description.turn_on_fn(self.coordinator.client, self.unit),
|
||||
)
|
||||
|
||||
@override
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Disable the protection."""
|
||||
await _perform_action(
|
||||
@@ -256,15 +260,18 @@ class ATWSwitch(MelCloudHomeATWUnitEntity, SwitchEntity):
|
||||
self._attr_unique_id = f"{unit.id}_{entity_description.key}"
|
||||
|
||||
@property
|
||||
@override
|
||||
def available(self) -> bool:
|
||||
"""Return if the entity is available."""
|
||||
return super().available and self.entity_description.available_fn(self.unit)
|
||||
|
||||
@property
|
||||
@override
|
||||
def is_on(self) -> bool | None:
|
||||
"""Return the state of the switch."""
|
||||
return self.entity_description.is_on_fn(self.unit)
|
||||
|
||||
@override
|
||||
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||
"""Enable the protection."""
|
||||
await _perform_action(
|
||||
@@ -272,6 +279,7 @@ class ATWSwitch(MelCloudHomeATWUnitEntity, SwitchEntity):
|
||||
self.entity_description.turn_on_fn(self.coordinator.client, self.unit),
|
||||
)
|
||||
|
||||
@override
|
||||
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||
"""Disable the protection."""
|
||||
await _perform_action(
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
"title": "Moisture",
|
||||
"triggers": {
|
||||
"changed": {
|
||||
"description": "Triggers after one or more moisture content values change.",
|
||||
"description": "Triggers when one or more moisture content values change.",
|
||||
"fields": {
|
||||
"threshold": {
|
||||
"name": "[%key:component::moisture::common::trigger_threshold_name%]"
|
||||
@@ -60,7 +60,7 @@
|
||||
"name": "Moisture content changed"
|
||||
},
|
||||
"cleared": {
|
||||
"description": "Triggers after one or more moisture sensors stop detecting moisture.",
|
||||
"description": "Triggers when one or more moisture sensors stop detecting moisture.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::moisture::common::trigger_behavior_name%]"
|
||||
@@ -72,7 +72,7 @@
|
||||
"name": "Moisture cleared"
|
||||
},
|
||||
"crossed_threshold": {
|
||||
"description": "Triggers after one or more moisture content values cross a threshold.",
|
||||
"description": "Triggers when one or more moisture content values cross a threshold.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::moisture::common::trigger_behavior_name%]"
|
||||
@@ -87,7 +87,7 @@
|
||||
"name": "Moisture content crossed threshold"
|
||||
},
|
||||
"detected": {
|
||||
"description": "Triggers after one or more moisture sensors start detecting moisture.",
|
||||
"description": "Triggers when one or more moisture sensors start detecting moisture.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::moisture::common::trigger_behavior_name%]"
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"title": "Motion",
|
||||
"triggers": {
|
||||
"cleared": {
|
||||
"description": "Triggers after one or more motion sensors stop detecting motion.",
|
||||
"description": "Triggers when one or more motion sensors stop detecting motion.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::motion::common::trigger_behavior_name%]"
|
||||
@@ -46,7 +46,7 @@
|
||||
"name": "Motion cleared"
|
||||
},
|
||||
"detected": {
|
||||
"description": "Triggers after one or more motion sensors start detecting motion.",
|
||||
"description": "Triggers when one or more motion sensors start detecting motion.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::motion::common::trigger_behavior_name%]"
|
||||
|
||||
@@ -83,6 +83,7 @@ class NFAndroidTVNotifyEntity(NotifyEntity):
|
||||
self.entry = entry
|
||||
self.client = entry.runtime_data
|
||||
|
||||
@override
|
||||
def send_message(self, message: str, title: str | None = None) -> None:
|
||||
"""Send a message via notify.send_message action."""
|
||||
try:
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"title": "Occupancy",
|
||||
"triggers": {
|
||||
"cleared": {
|
||||
"description": "Triggers after one or more occupancy sensors stop detecting occupancy.",
|
||||
"description": "Triggers when one or more occupancy sensors stop detecting occupancy.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::occupancy::common::trigger_behavior_name%]"
|
||||
@@ -46,7 +46,7 @@
|
||||
"name": "Occupancy cleared"
|
||||
},
|
||||
"detected": {
|
||||
"description": "Triggers after one or more occupancy sensors start detecting occupancy.",
|
||||
"description": "Triggers when one or more occupancy sensors start detecting occupancy.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::occupancy::common::trigger_behavior_name%]"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
from typing import Any, override
|
||||
|
||||
from openevsehttp.__main__ import OpenEVSE
|
||||
|
||||
@@ -91,6 +91,7 @@ class OpenEVSEButton(CoordinatorEntity[OpenEVSEDataUpdateCoordinator], ButtonEnt
|
||||
}
|
||||
self._attr_device_info[ATTR_SERIAL_NUMBER] = unique_id
|
||||
|
||||
@override
|
||||
async def async_press(self) -> None:
|
||||
"""Press the button."""
|
||||
with openevse_exception_handler(0.0):
|
||||
|
||||
@@ -5,7 +5,7 @@ from typing import Any, override
|
||||
|
||||
from ouman_eh_800_api import (
|
||||
EnumControlOumanEndpoint,
|
||||
IntControlOumanEndpoint,
|
||||
FloatControlOumanEndpoint,
|
||||
L1BaseEndpoints,
|
||||
L1RoomSensor,
|
||||
L2BaseEndpoints,
|
||||
@@ -57,7 +57,7 @@ class OumanEh800ClimateEntityDescription(
|
||||
|
||||
operation_mode_endpoint: EnumControlOumanEndpoint
|
||||
current_temperature_endpoint: NumberOumanEndpoint
|
||||
target_temperature_endpoint: IntControlOumanEndpoint
|
||||
target_temperature_endpoint: FloatControlOumanEndpoint
|
||||
valve_position_endpoint: NumberOumanEndpoint
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ class OumanEh800ClimateEntity(OumanEh800Entity, ClimateEntity):
|
||||
|
||||
_attr_name = None
|
||||
_attr_temperature_unit = UnitOfTemperature.CELSIUS
|
||||
_attr_target_temperature_step = 1
|
||||
_attr_target_temperature_step = 0.1
|
||||
_attr_hvac_modes = [HVACMode.HEAT, HVACMode.OFF]
|
||||
_attr_preset_modes = list(_PRESET_TO_OPERATION_MODE)
|
||||
_attr_supported_features = (
|
||||
@@ -187,7 +187,7 @@ class OumanEh800ClimateEntity(OumanEh800Entity, ClimateEntity):
|
||||
await self.async_set_hvac_mode(hvac_mode)
|
||||
await self.coordinator.async_set_endpoint_value(
|
||||
self.entity_description.target_temperature_endpoint,
|
||||
int(kwargs[ATTR_TEMPERATURE]),
|
||||
kwargs[ATTR_TEMPERATURE],
|
||||
)
|
||||
|
||||
@override
|
||||
|
||||
@@ -7,5 +7,5 @@
|
||||
"integration_type": "device",
|
||||
"iot_class": "local_polling",
|
||||
"quality_scale": "bronze",
|
||||
"requirements": ["ouman-eh-800-api==0.5.0"]
|
||||
"requirements": ["ouman-eh-800-api==1.0.0"]
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import ClientConnectorError, ServerDisconnectedError
|
||||
from pyoverkiz.enums import OverkizCommand
|
||||
from pyoverkiz.exceptions import BaseOverkizError
|
||||
from pyoverkiz.models import Action, Command, Device, StateDefinition
|
||||
@@ -63,6 +64,12 @@ class OverkizExecutor:
|
||||
# Catch Overkiz exceptions to support `continue_on_error` functionality
|
||||
except BaseOverkizError as exception:
|
||||
raise HomeAssistantError(exception) from exception
|
||||
except (
|
||||
TimeoutError,
|
||||
ClientConnectorError,
|
||||
ServerDisconnectedError,
|
||||
) as exception:
|
||||
raise HomeAssistantError("Failed to connect") from exception
|
||||
|
||||
# ExecutionRegisteredEvent doesn't contain the
|
||||
# device_url, thus we need to register it here
|
||||
@@ -96,6 +103,12 @@ class OverkizExecutor:
|
||||
# Catch Overkiz exceptions to support `continue_on_error` functionality
|
||||
except BaseOverkizError as exception:
|
||||
raise HomeAssistantError(exception) from exception
|
||||
except (
|
||||
TimeoutError,
|
||||
ClientConnectorError,
|
||||
ServerDisconnectedError,
|
||||
) as exception:
|
||||
raise HomeAssistantError("Failed to connect") from exception
|
||||
|
||||
self.coordinator.executions[exec_id] = {
|
||||
"device_url": self.device.device_url,
|
||||
|
||||
@@ -25,6 +25,7 @@ from .const import (
|
||||
SERVICE_SELECT_LAST,
|
||||
SERVICE_SELECT_NEXT,
|
||||
SERVICE_SELECT_PREVIOUS,
|
||||
SelectEntityCapabilityAttribute,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -121,7 +122,9 @@ CACHED_PROPERTIES_WITH_ATTR_ = {
|
||||
class SelectEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
"""Representation of a Select entity."""
|
||||
|
||||
_entity_component_unrecorded_attributes = frozenset({ATTR_OPTIONS})
|
||||
_entity_component_unrecorded_attributes = frozenset(
|
||||
{SelectEntityCapabilityAttribute.OPTIONS}
|
||||
)
|
||||
|
||||
entity_description: SelectEntityDescription
|
||||
_attr_current_option: str | None = None
|
||||
@@ -133,7 +136,7 @@ class SelectEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
|
||||
def capability_attributes(self) -> dict[str, Any]:
|
||||
"""Return capability attributes."""
|
||||
return {
|
||||
ATTR_OPTIONS: self.options,
|
||||
SelectEntityCapabilityAttribute.OPTIONS: self.options,
|
||||
}
|
||||
|
||||
@property
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
"""Provides the constants needed for the component."""
|
||||
|
||||
from enum import StrEnum
|
||||
|
||||
DOMAIN = "select"
|
||||
|
||||
|
||||
class SelectEntityCapabilityAttribute(StrEnum):
|
||||
"""Capability attributes for select entities."""
|
||||
|
||||
OPTIONS = "options"
|
||||
|
||||
|
||||
ATTR_CYCLE = "cycle"
|
||||
ATTR_OPTIONS = "options"
|
||||
|
||||
|
||||
@@ -35,5 +35,31 @@
|
||||
"default": "mdi:theme-light-dark"
|
||||
}
|
||||
}
|
||||
},
|
||||
"triggers": {
|
||||
"dawn": {
|
||||
"trigger": "mdi:weather-sunset-up"
|
||||
},
|
||||
"dusk": {
|
||||
"trigger": "mdi:weather-sunset-down"
|
||||
},
|
||||
"elevation_changed": {
|
||||
"trigger": "mdi:sun-angle"
|
||||
},
|
||||
"elevation_crossed_threshold": {
|
||||
"trigger": "mdi:sun-angle"
|
||||
},
|
||||
"solar_midnight": {
|
||||
"trigger": "mdi:weather-night"
|
||||
},
|
||||
"solar_noon": {
|
||||
"trigger": "mdi:weather-sunny"
|
||||
},
|
||||
"sunrise": {
|
||||
"trigger": "mdi:weather-sunset-up"
|
||||
},
|
||||
"sunset": {
|
||||
"trigger": "mdi:weather-sunset-down"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
{
|
||||
"common": {
|
||||
"trigger_for_name": "For at least",
|
||||
"trigger_threshold_name": "Threshold type",
|
||||
"twilight_type_description": "The phase of twilight.",
|
||||
"twilight_type_name": "Twilight type"
|
||||
},
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
@@ -36,5 +42,73 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Sun"
|
||||
"selector": {
|
||||
"twilight_type": {
|
||||
"options": {
|
||||
"astronomical": "Astronomical",
|
||||
"civil": "Civil",
|
||||
"nautical": "Nautical"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Sun",
|
||||
"triggers": {
|
||||
"dawn": {
|
||||
"description": "Triggers at dawn, when civil, nautical, or astronomical twilight begins.",
|
||||
"fields": {
|
||||
"type": {
|
||||
"description": "[%key:component::sun::common::twilight_type_description%]",
|
||||
"name": "[%key:component::sun::common::twilight_type_name%]"
|
||||
}
|
||||
},
|
||||
"name": "Dawn"
|
||||
},
|
||||
"dusk": {
|
||||
"description": "Triggers at dusk, when civil, nautical, or astronomical twilight ends.",
|
||||
"fields": {
|
||||
"type": {
|
||||
"description": "[%key:component::sun::common::twilight_type_description%]",
|
||||
"name": "[%key:component::sun::common::twilight_type_name%]"
|
||||
}
|
||||
},
|
||||
"name": "Dusk"
|
||||
},
|
||||
"elevation_changed": {
|
||||
"description": "Triggers whenever the sun's elevation changes, optionally limited to a threshold or range you set.",
|
||||
"fields": {
|
||||
"threshold": {
|
||||
"name": "[%key:component::sun::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
"name": "Sun elevation changed"
|
||||
},
|
||||
"elevation_crossed_threshold": {
|
||||
"description": "Triggers when the sun's elevation crosses a threshold you set.",
|
||||
"fields": {
|
||||
"for": {
|
||||
"name": "[%key:component::sun::common::trigger_for_name%]"
|
||||
},
|
||||
"threshold": {
|
||||
"name": "[%key:component::sun::common::trigger_threshold_name%]"
|
||||
}
|
||||
},
|
||||
"name": "Sun elevation crossed threshold"
|
||||
},
|
||||
"solar_midnight": {
|
||||
"description": "Triggers when the sun reaches its lowest point.",
|
||||
"name": "Solar midnight"
|
||||
},
|
||||
"solar_noon": {
|
||||
"description": "Triggers when the sun reaches its highest point.",
|
||||
"name": "Solar noon"
|
||||
},
|
||||
"sunrise": {
|
||||
"description": "Triggers when the sun rises.",
|
||||
"name": "Sunrise"
|
||||
},
|
||||
"sunset": {
|
||||
"description": "Triggers when the sun sets.",
|
||||
"name": "Sunset"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,61 +1,329 @@
|
||||
"""Offer sun based automation rules."""
|
||||
"""Provides triggers for the sun."""
|
||||
|
||||
from datetime import timedelta
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, cast, override
|
||||
|
||||
import astral
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
CONF_EVENT,
|
||||
CONF_FOR,
|
||||
CONF_OFFSET,
|
||||
CONF_PLATFORM,
|
||||
CONF_OPTIONS,
|
||||
CONF_TYPE,
|
||||
DEGREE,
|
||||
EVENT_CORE_CONFIG_UPDATE,
|
||||
SUN_EVENT_SUNRISE,
|
||||
SUN_EVENT_SUNSET,
|
||||
)
|
||||
from homeassistant.core import CALLBACK_TYPE, HassJob, HomeAssistant, callback
|
||||
from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, callback
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.event import async_track_sunrise, async_track_sunset
|
||||
from homeassistant.helpers.trigger import TriggerActionType, TriggerInfo
|
||||
from homeassistant.helpers.automation import (
|
||||
DomainSpec,
|
||||
move_top_level_schema_fields_to_options,
|
||||
)
|
||||
from homeassistant.helpers.event import async_track_point_in_utc_time
|
||||
from homeassistant.helpers.selector import (
|
||||
NumericThresholdMode,
|
||||
NumericThresholdSelector,
|
||||
NumericThresholdSelectorConfig,
|
||||
)
|
||||
from homeassistant.helpers.sun import (
|
||||
get_astral_event_next,
|
||||
get_astral_observer,
|
||||
get_observer_astral_event_next,
|
||||
)
|
||||
from homeassistant.helpers.trigger import (
|
||||
EntityNumericalStateChangedTriggerBase,
|
||||
EntityNumericalStateCrossedThresholdTriggerBase,
|
||||
EntityNumericalStateTriggerBase,
|
||||
Trigger,
|
||||
TriggerActionRunner,
|
||||
TriggerConfig,
|
||||
TriggerNotTriggeredReporter,
|
||||
)
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
TRIGGER_SCHEMA = cv.TRIGGER_BASE_SCHEMA.extend(
|
||||
from .const import DOMAIN, STATE_ATTR_ELEVATION
|
||||
|
||||
# Names of solar events supported by the astral.sun module
|
||||
_SUN_EVENT_SOLAR_NOON = "noon"
|
||||
_SUN_EVENT_SOLAR_MIDNIGHT = "midnight"
|
||||
_SUN_EVENT_DAWN = "dawn"
|
||||
_SUN_EVENT_DUSK = "dusk"
|
||||
|
||||
_TWILIGHT_CIVIL = "civil"
|
||||
_TWILIGHT_NAUTICAL = "nautical"
|
||||
_TWILIGHT_ASTRONOMICAL = "astronomical"
|
||||
|
||||
# Sun depression below the horizon for each twilight phase, as defined by astral.
|
||||
_TWILIGHT_DEPRESSIONS = {
|
||||
_TWILIGHT_CIVIL: astral.Depression.CIVIL,
|
||||
_TWILIGHT_NAUTICAL: astral.Depression.NAUTICAL,
|
||||
_TWILIGHT_ASTRONOMICAL: astral.Depression.ASTRONOMICAL,
|
||||
}
|
||||
|
||||
# The sun is a singleton, so the elevation triggers always target sun.sun
|
||||
# instead of asking the user to pick an entity.
|
||||
_SUN_ENTITY_ID = f"{DOMAIN}.{DOMAIN}"
|
||||
_ELEVATION_DOMAIN_SPECS = {DOMAIN: DomainSpec(value_source=STATE_ATTR_ELEVATION)}
|
||||
|
||||
_ELEVATION_CHANGED_TRIGGER_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_PLATFORM): "sun",
|
||||
vol.Required(CONF_EVENT): cv.sun_event,
|
||||
vol.Required(CONF_OFFSET, default=timedelta(0)): cv.time_period,
|
||||
vol.Required(CONF_OPTIONS, default=dict): {
|
||||
vol.Required("threshold"): NumericThresholdSelector(
|
||||
NumericThresholdSelectorConfig(mode=NumericThresholdMode.CHANGED)
|
||||
),
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
# Unlike the generic numerical triggers there is no behavior option: a behavior
|
||||
# (each/first/all) is only meaningful across multiple targeted entities.
|
||||
_ELEVATION_CROSSED_TRIGGER_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_OPTIONS, default=dict): {
|
||||
vol.Required("threshold"): NumericThresholdSelector(
|
||||
NumericThresholdSelectorConfig(mode=NumericThresholdMode.CROSSED)
|
||||
),
|
||||
vol.Optional(CONF_FOR): cv.positive_time_period,
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def async_attach_trigger(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
action: TriggerActionType,
|
||||
trigger_info: TriggerInfo,
|
||||
) -> CALLBACK_TYPE:
|
||||
"""Listen for events based on configuration."""
|
||||
trigger_data = trigger_info["trigger_data"]
|
||||
event = config.get(CONF_EVENT)
|
||||
offset = config.get(CONF_OFFSET)
|
||||
description = event
|
||||
if offset:
|
||||
description = f"{description} with offset"
|
||||
job = HassJob(action)
|
||||
class SunElevationTrigger(EntityNumericalStateTriggerBase):
|
||||
"""Trigger for the sun's elevation."""
|
||||
|
||||
@callback
|
||||
def call_action() -> None:
|
||||
"""Call action with right context."""
|
||||
hass.async_run_hass_job(
|
||||
job,
|
||||
{
|
||||
"trigger": {
|
||||
**trigger_data,
|
||||
"platform": "sun",
|
||||
"event": event,
|
||||
"offset": offset,
|
||||
"description": description,
|
||||
}
|
||||
},
|
||||
_domain_specs = _ELEVATION_DOMAIN_SPECS
|
||||
_valid_unit = DEGREE
|
||||
|
||||
def __init__(self, hass: HomeAssistant, config: TriggerConfig) -> None:
|
||||
"""Initialize the trigger, targeting the singleton sun entity."""
|
||||
super().__init__(
|
||||
hass,
|
||||
TriggerConfig(
|
||||
key=config.key,
|
||||
target={ATTR_ENTITY_ID: [_SUN_ENTITY_ID]},
|
||||
options=config.options,
|
||||
),
|
||||
)
|
||||
|
||||
if event == SUN_EVENT_SUNRISE:
|
||||
return async_track_sunrise(hass, call_action, offset)
|
||||
return async_track_sunset(hass, call_action, offset)
|
||||
|
||||
class SunElevationChangedTrigger(
|
||||
SunElevationTrigger, EntityNumericalStateChangedTriggerBase
|
||||
):
|
||||
"""Trigger for changes to the sun's elevation."""
|
||||
|
||||
_schema = _ELEVATION_CHANGED_TRIGGER_SCHEMA
|
||||
|
||||
|
||||
class SunElevationCrossedTrigger(
|
||||
SunElevationTrigger, EntityNumericalStateCrossedThresholdTriggerBase
|
||||
):
|
||||
"""Trigger for the sun's elevation crossing a threshold."""
|
||||
|
||||
_schema = _ELEVATION_CROSSED_TRIGGER_SCHEMA
|
||||
|
||||
|
||||
_EVENT_TRIGGER_SCHEMA = vol.Schema({vol.Required(CONF_OPTIONS, default=dict): {}})
|
||||
|
||||
|
||||
class SunEventTrigger(Trigger):
|
||||
"""Trigger that fires at a solar event time."""
|
||||
|
||||
_event: str
|
||||
_schema: vol.Schema = _EVENT_TRIGGER_SCHEMA
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
async def async_validate_config(
|
||||
cls, hass: HomeAssistant, config: ConfigType
|
||||
) -> ConfigType:
|
||||
"""Validate config."""
|
||||
return cast(ConfigType, cls._schema(config))
|
||||
|
||||
def __init__(self, hass: HomeAssistant, config: TriggerConfig) -> None:
|
||||
"""Initialize the trigger."""
|
||||
super().__init__(hass, config)
|
||||
self._options = config.options or {}
|
||||
|
||||
def _get_next_event(self, utc_point_in_time: datetime) -> datetime:
|
||||
"""Return the next time this solar event occurs."""
|
||||
return get_astral_event_next(self._hass, self._event, utc_point_in_time)
|
||||
|
||||
def _action_payload(self) -> dict[str, Any]:
|
||||
"""Return extra trigger payload passed to the action."""
|
||||
return {}
|
||||
|
||||
@override
|
||||
async def async_attach_runner(
|
||||
self,
|
||||
run_action: TriggerActionRunner,
|
||||
did_not_trigger: TriggerNotTriggeredReporter | None = None,
|
||||
) -> CALLBACK_TYPE:
|
||||
"""Attach the trigger to an action runner."""
|
||||
unsubs: dict[str, CALLBACK_TYPE | None] = {"event": None, "config": None}
|
||||
|
||||
@callback
|
||||
def schedule_next_event() -> None:
|
||||
unsubs["event"] = async_track_point_in_utc_time(
|
||||
self._hass, handle_event, self._get_next_event(dt_util.utcnow())
|
||||
)
|
||||
|
||||
@callback
|
||||
def handle_event(_now: datetime) -> None:
|
||||
unsubs["event"] = None
|
||||
schedule_next_event()
|
||||
run_action(self._action_payload(), f"sun event {self._event}")
|
||||
|
||||
@callback
|
||||
def handle_config(_event: Event) -> None:
|
||||
if unsubs["event"]:
|
||||
unsubs["event"]()
|
||||
schedule_next_event()
|
||||
|
||||
unsubs["config"] = self._hass.bus.async_listen(
|
||||
EVENT_CORE_CONFIG_UPDATE, handle_config
|
||||
)
|
||||
schedule_next_event()
|
||||
|
||||
@callback
|
||||
def async_remove() -> None:
|
||||
for unsub in unsubs.values():
|
||||
if unsub:
|
||||
unsub()
|
||||
|
||||
return async_remove
|
||||
|
||||
|
||||
class SunriseTrigger(SunEventTrigger):
|
||||
"""Trigger that fires at sunrise."""
|
||||
|
||||
_event = SUN_EVENT_SUNRISE
|
||||
|
||||
|
||||
class SunsetTrigger(SunEventTrigger):
|
||||
"""Trigger that fires at sunset."""
|
||||
|
||||
_event = SUN_EVENT_SUNSET
|
||||
|
||||
|
||||
class SolarNoonTrigger(SunEventTrigger):
|
||||
"""Trigger that fires at solar noon."""
|
||||
|
||||
_event = _SUN_EVENT_SOLAR_NOON
|
||||
|
||||
|
||||
class SolarMidnightTrigger(SunEventTrigger):
|
||||
"""Trigger that fires at solar midnight."""
|
||||
|
||||
_event = _SUN_EVENT_SOLAR_MIDNIGHT
|
||||
|
||||
|
||||
_DAWN_DUSK_TRIGGER_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(CONF_OPTIONS, default=dict): {
|
||||
vol.Optional(CONF_TYPE, default=_TWILIGHT_CIVIL): vol.In(
|
||||
_TWILIGHT_DEPRESSIONS
|
||||
),
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class SunDawnDuskTrigger(SunEventTrigger):
|
||||
"""Trigger that fires at dawn or dusk for a configurable twilight phase."""
|
||||
|
||||
_schema = _DAWN_DUSK_TRIGGER_SCHEMA
|
||||
|
||||
def __init__(self, hass: HomeAssistant, config: TriggerConfig) -> None:
|
||||
"""Initialize the trigger."""
|
||||
super().__init__(hass, config)
|
||||
self._twilight: str = self._options[CONF_TYPE]
|
||||
self._depression = _TWILIGHT_DEPRESSIONS[self._twilight]
|
||||
|
||||
@override
|
||||
def _get_next_event(self, utc_point_in_time: datetime) -> datetime:
|
||||
return get_observer_astral_event_next(
|
||||
get_astral_observer(self._hass),
|
||||
self._event,
|
||||
utc_point_in_time,
|
||||
depression=self._depression,
|
||||
)
|
||||
|
||||
@override
|
||||
def _action_payload(self) -> dict[str, Any]:
|
||||
return {CONF_TYPE: self._twilight}
|
||||
|
||||
|
||||
class DawnTrigger(SunDawnDuskTrigger):
|
||||
"""Trigger that fires at dawn."""
|
||||
|
||||
_event = _SUN_EVENT_DAWN
|
||||
|
||||
|
||||
class DuskTrigger(SunDawnDuskTrigger):
|
||||
"""Trigger that fires at dusk."""
|
||||
|
||||
_event = _SUN_EVENT_DUSK
|
||||
|
||||
|
||||
_LEGACY_OPTIONS_SCHEMA_DICT: dict[vol.Marker, Any] = {
|
||||
vol.Required(CONF_EVENT): cv.sun_event,
|
||||
vol.Optional(CONF_OFFSET, default=timedelta(0)): cv.time_period,
|
||||
}
|
||||
|
||||
|
||||
class LegacySunTrigger(SunEventTrigger):
|
||||
"""Backwards compatible trigger for the legacy ``platform: sun`` config."""
|
||||
|
||||
_schema = vol.Schema({vol.Required(CONF_OPTIONS): _LEGACY_OPTIONS_SCHEMA_DICT})
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
async def async_validate_complete_config(
|
||||
cls, hass: HomeAssistant, complete_config: ConfigType
|
||||
) -> ConfigType:
|
||||
"""Validate complete config, migrating the legacy top-level fields."""
|
||||
complete_config = move_top_level_schema_fields_to_options(
|
||||
complete_config, _LEGACY_OPTIONS_SCHEMA_DICT
|
||||
)
|
||||
return await super().async_validate_complete_config(hass, complete_config)
|
||||
|
||||
def __init__(self, hass: HomeAssistant, config: TriggerConfig) -> None:
|
||||
"""Initialize the trigger."""
|
||||
super().__init__(hass, config)
|
||||
self._event = self._options[CONF_EVENT]
|
||||
self._offset: timedelta = self._options[CONF_OFFSET]
|
||||
|
||||
@override
|
||||
def _get_next_event(self, utc_point_in_time: datetime) -> datetime:
|
||||
return get_astral_event_next(
|
||||
self._hass, self._event, utc_point_in_time, self._offset
|
||||
)
|
||||
|
||||
@override
|
||||
def _action_payload(self) -> dict[str, Any]:
|
||||
return {"event": self._event, "offset": self._offset}
|
||||
|
||||
|
||||
TRIGGERS: dict[str, type[Trigger]] = {
|
||||
"_": LegacySunTrigger,
|
||||
"sunrise": SunriseTrigger,
|
||||
"sunset": SunsetTrigger,
|
||||
"solar_noon": SolarNoonTrigger,
|
||||
"solar_midnight": SolarMidnightTrigger,
|
||||
"dawn": DawnTrigger,
|
||||
"dusk": DuskTrigger,
|
||||
"elevation_changed": SunElevationChangedTrigger,
|
||||
"elevation_crossed_threshold": SunElevationCrossedTrigger,
|
||||
}
|
||||
|
||||
|
||||
async def async_get_triggers(hass: HomeAssistant) -> dict[str, type[Trigger]]:
|
||||
"""Return the triggers for the sun."""
|
||||
return TRIGGERS
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
.type: &twilight_type
|
||||
required: true
|
||||
default: civil
|
||||
selector:
|
||||
select:
|
||||
translation_key: twilight_type
|
||||
options:
|
||||
- civil
|
||||
- nautical
|
||||
- astronomical
|
||||
|
||||
.for: &trigger_for
|
||||
required: true
|
||||
default: "00:00:00"
|
||||
selector:
|
||||
duration:
|
||||
|
||||
.elevation_threshold_entity: &trigger_elevation_threshold_entity
|
||||
- domain: input_number
|
||||
unit_of_measurement: "°"
|
||||
- domain: number
|
||||
unit_of_measurement: "°"
|
||||
- domain: sensor
|
||||
unit_of_measurement: "°"
|
||||
|
||||
.elevation_threshold_number: &trigger_elevation_threshold_number
|
||||
min: -90
|
||||
max: 90
|
||||
mode: box
|
||||
unit_of_measurement: "°"
|
||||
|
||||
sunrise: {}
|
||||
sunset: {}
|
||||
solar_noon: {}
|
||||
solar_midnight: {}
|
||||
|
||||
dawn:
|
||||
fields:
|
||||
type: *twilight_type
|
||||
|
||||
dusk:
|
||||
fields:
|
||||
type: *twilight_type
|
||||
|
||||
elevation_changed:
|
||||
fields:
|
||||
threshold:
|
||||
required: true
|
||||
selector:
|
||||
numeric_threshold:
|
||||
entity: *trigger_elevation_threshold_entity
|
||||
mode: changed
|
||||
number: *trigger_elevation_threshold_number
|
||||
|
||||
elevation_crossed_threshold:
|
||||
fields:
|
||||
for: *trigger_for
|
||||
threshold:
|
||||
required: true
|
||||
selector:
|
||||
numeric_threshold:
|
||||
entity: *trigger_elevation_threshold_entity
|
||||
mode: crossed
|
||||
number: *trigger_elevation_threshold_number
|
||||
@@ -289,19 +289,35 @@ async def _async_resolve_template_config(
|
||||
config = blueprint_inputs.async_substitute()
|
||||
|
||||
platforms = [platform for platform in PLATFORMS if platform in config]
|
||||
platform_config: list[ConfigType] | ConfigType
|
||||
if len(platforms) > 1:
|
||||
raise vol.Invalid("more than one platform defined per blueprint")
|
||||
if len(platforms) == 1:
|
||||
platform = platforms.pop()
|
||||
for prop in (CONF_NAME, CONF_UNIQUE_ID):
|
||||
if prop in config:
|
||||
config[platform][prop] = config.pop(prop)
|
||||
platform_config = config[platform]
|
||||
if isinstance(platform_config, dict):
|
||||
platform_config[prop] = config.pop(prop)
|
||||
continue
|
||||
|
||||
if len(platform_config) > 1:
|
||||
raise vol.Invalid(
|
||||
f"more than one {platform} entity defined in blueprint"
|
||||
)
|
||||
platform_config[0][prop] = config.pop(prop)
|
||||
|
||||
# State based template entities remove CONF_VARIABLES because they pass
|
||||
# blueprint inputs to the template entities. Trigger based template entities
|
||||
# retain CONF_VARIABLES because the variables are always executed between
|
||||
# the trigger and action.
|
||||
if CONF_TRIGGERS not in config and CONF_VARIABLES in config:
|
||||
_merge_section_variables(config[platform], config.pop(CONF_VARIABLES))
|
||||
section_variables = config.pop(CONF_VARIABLES)
|
||||
platform_config = config[platform]
|
||||
if isinstance(platform_config, dict):
|
||||
platform_config = [platform_config]
|
||||
for entity_config in platform_config:
|
||||
_merge_section_variables(entity_config, section_variables)
|
||||
|
||||
raw_config = dict(config)
|
||||
|
||||
@@ -313,7 +329,6 @@ async def _async_resolve_template_config(
|
||||
# variables at the entity level should be merged
|
||||
# together at the entity level.
|
||||
section_variables = config.pop(CONF_VARIABLES)
|
||||
platform_config: list[ConfigType] | ConfigType
|
||||
platforms = [platform for platform in PLATFORMS if platform in config]
|
||||
for platform in platforms:
|
||||
platform_config = config[platform]
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
"""Unifi LED Lights integration."""
|
||||
@@ -1,97 +0,0 @@
|
||||
"""Support for Unifi Led lights."""
|
||||
|
||||
import logging
|
||||
from typing import Any, override
|
||||
|
||||
from unifiled import unifiled
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
PLATFORM_SCHEMA as LIGHT_PLATFORM_SCHEMA,
|
||||
ColorMode,
|
||||
LightEntity,
|
||||
)
|
||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_USERNAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# Validation of the user's configuration
|
||||
PLATFORM_SCHEMA = LIGHT_PLATFORM_SCHEMA.extend(
|
||||
{
|
||||
vol.Required(CONF_HOST): cv.string,
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Optional(CONF_PORT, default=20443): vol.All(cv.port, cv.string),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def setup_platform(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
add_entities: AddEntitiesCallback,
|
||||
discovery_info: DiscoveryInfoType | None = None,
|
||||
) -> None:
|
||||
"""Set up the Unifi LED platform."""
|
||||
|
||||
# Assign configuration variables.
|
||||
# The configuration check takes care they are present.
|
||||
host = config[CONF_HOST]
|
||||
port = config[CONF_PORT]
|
||||
username = config[CONF_USERNAME]
|
||||
password = config[CONF_PASSWORD]
|
||||
|
||||
api = unifiled(host, port, username=username, password=password)
|
||||
|
||||
# Verify that passed in configuration works
|
||||
if not api.getloginstate():
|
||||
_LOGGER.error("Could not connect to unifiled controller")
|
||||
return
|
||||
|
||||
add_entities(UnifiLedLight(light, api) for light in api.getlights())
|
||||
|
||||
|
||||
class UnifiLedLight(LightEntity):
|
||||
"""Representation of an unifiled Light."""
|
||||
|
||||
_attr_color_mode = ColorMode.BRIGHTNESS
|
||||
_attr_supported_color_modes = {ColorMode.BRIGHTNESS}
|
||||
|
||||
def __init__(self, light: dict[str, Any], api: unifiled) -> None:
|
||||
"""Init Unifi LED Light."""
|
||||
|
||||
self._api = api
|
||||
self._light = light
|
||||
self._attr_name = light["name"]
|
||||
self._light_id = light["id"]
|
||||
self._attr_unique_id = light["id"]
|
||||
self._attr_is_on = light["status"]["output"]
|
||||
self._attr_available = light["isOnline"]
|
||||
self._attr_brightness = self._api.convertfrom100to255(light["status"]["led"])
|
||||
|
||||
@override
|
||||
def turn_on(self, **kwargs: Any) -> None:
|
||||
"""Instruct the light to turn on."""
|
||||
self._api.setdevicebrightness(
|
||||
self._light_id,
|
||||
str(self._api.convertfrom255to100(kwargs.get(ATTR_BRIGHTNESS, 255))),
|
||||
)
|
||||
self._api.setdeviceoutput(self._light_id, 1)
|
||||
|
||||
@override
|
||||
def turn_off(self, **kwargs: Any) -> None:
|
||||
"""Instruct the light to turn off."""
|
||||
self._api.setdeviceoutput(self._light_id, 0)
|
||||
|
||||
def update(self) -> None:
|
||||
"""Update the light states."""
|
||||
self._attr_is_on = self._api.getlightstate(self._light_id)
|
||||
self._attr_brightness = self._api.convertfrom100to255(
|
||||
self._api.getlightbrightness(self._light_id)
|
||||
)
|
||||
self._attr_available = self._api.getlightavailable(self._light_id)
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"domain": "unifiled",
|
||||
"name": "UniFi LED",
|
||||
"codeowners": ["@florisvdk"],
|
||||
"documentation": "https://www.home-assistant.io/integrations/unifiled",
|
||||
"iot_class": "local_polling",
|
||||
"loggers": ["unifiled"],
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["unifiled==0.11"]
|
||||
}
|
||||
@@ -381,6 +381,7 @@ class ProtectDeviceSmartDetectEventEntity(
|
||||
|
||||
entity_description: ProtectEventEntityDescription
|
||||
|
||||
@override
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Subscribe to public smart-detect events for this camera."""
|
||||
await super().async_added_to_hass()
|
||||
|
||||
@@ -191,7 +191,7 @@
|
||||
"title": "Vacuum",
|
||||
"triggers": {
|
||||
"errored": {
|
||||
"description": "Triggers after one or more vacuums encounter an error.",
|
||||
"description": "Triggers when one or more vacuum cleaners encounter an error.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::vacuum::common::trigger_behavior_name%]"
|
||||
@@ -200,10 +200,10 @@
|
||||
"name": "[%key:component::vacuum::common::trigger_for_name%]"
|
||||
}
|
||||
},
|
||||
"name": "Vacuum encountered an error"
|
||||
"name": "Vacuum cleaner encountered an error"
|
||||
},
|
||||
"paused_cleaning": {
|
||||
"description": "Triggers after one or more vacuums pause cleaning.",
|
||||
"description": "Triggers when one or more vacuum cleaners pause cleaning.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::vacuum::common::trigger_behavior_name%]"
|
||||
@@ -215,7 +215,7 @@
|
||||
"name": "Vacuum cleaner paused cleaning"
|
||||
},
|
||||
"returned_to_dock": {
|
||||
"description": "Triggers after one or more vacuums have returned to dock.",
|
||||
"description": "Triggers when one or more vacuum cleaners have returned to dock.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::vacuum::common::trigger_behavior_name%]"
|
||||
@@ -224,10 +224,10 @@
|
||||
"name": "[%key:component::vacuum::common::trigger_for_name%]"
|
||||
}
|
||||
},
|
||||
"name": "Vacuum returned to dock"
|
||||
"name": "Vacuum cleaner returned to dock"
|
||||
},
|
||||
"started_cleaning": {
|
||||
"description": "Triggers after one or more vacuums start cleaning.",
|
||||
"description": "Triggers when one or more vacuum cleaners start cleaning.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::vacuum::common::trigger_behavior_name%]"
|
||||
@@ -239,7 +239,7 @@
|
||||
"name": "Vacuum cleaner started cleaning"
|
||||
},
|
||||
"started_returning": {
|
||||
"description": "Triggers after one or more vacuums start returning to dock.",
|
||||
"description": "Triggers when one or more vacuum cleaners start returning to dock.",
|
||||
"fields": {
|
||||
"behavior": {
|
||||
"name": "[%key:component::vacuum::common::trigger_behavior_name%]"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Vistapool Select entities."""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
from typing import Any, override
|
||||
|
||||
from aioaquarite import AquariteError
|
||||
|
||||
@@ -111,6 +111,7 @@ class VistapoolSelect(VistapoolEntity, SelectEntity):
|
||||
self._attr_translation_placeholders = description.translation_placeholders
|
||||
|
||||
@property
|
||||
@override
|
||||
def current_option(self) -> str | None:
|
||||
"""Return the option that maps to the current API value."""
|
||||
index = _to_index(
|
||||
@@ -121,6 +122,7 @@ class VistapoolSelect(VistapoolEntity, SelectEntity):
|
||||
return None
|
||||
return options[index]
|
||||
|
||||
@override
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
"""Send the index of the chosen option to the controller."""
|
||||
assert self.entity_description.options is not None
|
||||
|
||||
@@ -368,6 +368,7 @@ class VolvoMediumIntervalCoordinator(VolvoBaseCoordinator):
|
||||
|
||||
self._supported_capabilities: list[str] = []
|
||||
|
||||
@override
|
||||
async def _async_update_data(self) -> CoordinatorData:
|
||||
"""Fetch data and trigger location update on engine-off."""
|
||||
previous_state = self.get_api_field("engineStatus")
|
||||
@@ -467,6 +468,7 @@ class VolvoFastIntervalCoordinator(VolvoBaseCoordinator):
|
||||
api.async_get_window_states,
|
||||
]
|
||||
|
||||
@override
|
||||
async def _async_update_data(self) -> CoordinatorData:
|
||||
"""Fetch data and trigger location update on lock."""
|
||||
previous_state = self.get_api_field("centralLock")
|
||||
|
||||
@@ -48,6 +48,7 @@ class WolfLinkCoordinator(DataUpdateCoordinator[dict[int, tuple[int, str]]]):
|
||||
self._system_share_id = device.system_share_id
|
||||
self._refetch_parameters = False
|
||||
|
||||
@override
|
||||
async def _async_setup(self) -> None:
|
||||
"""Fetch parameters once during initial setup."""
|
||||
try:
|
||||
|
||||
@@ -1759,12 +1759,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"eliqonline": {
|
||||
"name": "Eliqonline",
|
||||
"integration_type": "hub",
|
||||
"config_flow": false,
|
||||
"iot_class": "cloud_polling"
|
||||
},
|
||||
"elkm1": {
|
||||
"name": "Elk-M1 Control",
|
||||
"integration_type": "hub",
|
||||
@@ -7599,12 +7593,6 @@
|
||||
"iot_class": "local_polling",
|
||||
"name": "UniFi AP"
|
||||
},
|
||||
"unifiled": {
|
||||
"integration_type": "hub",
|
||||
"config_flow": false,
|
||||
"iot_class": "local_polling",
|
||||
"name": "UniFi LED"
|
||||
},
|
||||
"unifiprotect": {
|
||||
"integration_type": "hub",
|
||||
"config_flow": true,
|
||||
|
||||
@@ -1155,7 +1155,11 @@ class EntityRegistry(BaseRegistry):
|
||||
def async_get_entity_id(
|
||||
self, domain: str, platform: str, unique_id: str
|
||||
) -> str | None:
|
||||
"""Check if an entity_id is currently registered."""
|
||||
"""Check if an entity_id is currently registered.
|
||||
|
||||
domain: entity platform domain (e.g. light, sensor)
|
||||
platform: integration domain (e.g. hue, zwave)
|
||||
"""
|
||||
return self.entities.get_entity_id((domain, platform, unique_id))
|
||||
|
||||
@callback
|
||||
|
||||
@@ -17,7 +17,7 @@ no_implicit_optional = true
|
||||
warn_incomplete_stub = true
|
||||
warn_redundant_casts = true
|
||||
warn_unused_ignores = true
|
||||
enable_error_code = deprecated, ignore-without-code, redundant-self, truthy-iterable
|
||||
enable_error_code = deprecated, explicit-override, ignore-without-code, redundant-self, truthy-iterable
|
||||
disable_error_code = annotation-unchecked, import-not-found, import-untyped
|
||||
extra_checks = false
|
||||
check_untyped_defs = true
|
||||
|
||||
@@ -9,6 +9,7 @@ Material Design Icons set.
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import override
|
||||
|
||||
from astroid import nodes
|
||||
from pylint.checkers import BaseChecker
|
||||
@@ -51,6 +52,7 @@ class MdiIconsChecker(BaseChecker):
|
||||
_in_integration: bool
|
||||
_checked_icons_json: set[str]
|
||||
|
||||
@override
|
||||
def open(self) -> None:
|
||||
"""Initialize per-run state."""
|
||||
self._checked_icons_json = set()
|
||||
|
||||
Generated
+5
-11
@@ -909,9 +909,6 @@ elevenlabs==2.51.0
|
||||
# homeassistant.components.elgato
|
||||
elgato==5.1.2
|
||||
|
||||
# homeassistant.components.eliqonline
|
||||
eliqonline==1.2.2
|
||||
|
||||
# homeassistant.components.elkm1
|
||||
elkm1-lib==2.2.15
|
||||
|
||||
@@ -1168,7 +1165,7 @@ goslide-api==0.7.4
|
||||
gotailwind==0.4.0
|
||||
|
||||
# homeassistant.components.govee_ble
|
||||
govee-ble==1.2.0
|
||||
govee-ble==1.4.0
|
||||
|
||||
# homeassistant.components.govee_light_local
|
||||
govee-local-api==2.4.0
|
||||
@@ -1809,7 +1806,7 @@ oru==0.1.11
|
||||
orvibo==1.1.2
|
||||
|
||||
# homeassistant.components.ouman_eh_800
|
||||
ouman-eh-800-api==0.5.0
|
||||
ouman-eh-800-api==1.0.0
|
||||
|
||||
# homeassistant.components.ourgroceries
|
||||
ourgroceries==1.5.4
|
||||
@@ -2036,7 +2033,7 @@ pyanglianwater==3.2.2
|
||||
pyaprilaire==0.9.1
|
||||
|
||||
# homeassistant.components.aqvify
|
||||
pyaqvify==0.0.11
|
||||
pyaqvify==0.0.12
|
||||
|
||||
# homeassistant.components.atag
|
||||
pyatag==0.3.5.3
|
||||
@@ -2645,7 +2642,7 @@ python-clementine-remote==1.0.1
|
||||
python-digitalocean==1.13.2
|
||||
|
||||
# homeassistant.components.dropbox
|
||||
python-dropbox-api==0.1.3
|
||||
python-dropbox-api==0.1.4
|
||||
|
||||
# homeassistant.components.duco
|
||||
python-duco-connectivity==0.7.1
|
||||
@@ -3192,7 +3189,7 @@ thermopro-ble==1.1.4
|
||||
thingspeak==1.0.0
|
||||
|
||||
# homeassistant.components.lg_thinq
|
||||
thinqconnect==1.0.12
|
||||
thinqconnect==1.0.13
|
||||
|
||||
# homeassistant.components.tilt_ble
|
||||
tilt-ble==1.0.1
|
||||
@@ -3272,9 +3269,6 @@ unifi-discovery==1.5.0
|
||||
# homeassistant.components.unifi_direct
|
||||
unifi_ap==0.0.2
|
||||
|
||||
# homeassistant.components.unifiled
|
||||
unifiled==0.11
|
||||
|
||||
# homeassistant.components.homeassistant_hardware
|
||||
universal-silabs-flasher==1.1.0
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ pytest-github-actions-annotate-failures==0.4.0
|
||||
pytest-socket==0.8.0
|
||||
pytest-sugar==1.1.1
|
||||
pytest-timeout==2.4.0
|
||||
pytest-unordered==0.7.0
|
||||
pytest-unordered==0.8.0
|
||||
pytest-picked==0.5.1
|
||||
pytest-xdist==3.8.0
|
||||
pytest==9.0.3
|
||||
|
||||
@@ -54,6 +54,7 @@ GENERAL_SETTINGS: Final[dict[str, str]] = {
|
||||
"enable_error_code": ", ".join( # noqa: FLY002
|
||||
[
|
||||
"deprecated",
|
||||
"explicit-override",
|
||||
"ignore-without-code",
|
||||
"redundant-self",
|
||||
"truthy-iterable",
|
||||
|
||||
@@ -305,7 +305,6 @@ INTEGRATIONS_WITHOUT_QUALITY_SCALE_FILE = [
|
||||
"egardia",
|
||||
"eight_sleep",
|
||||
"electrasmart",
|
||||
"eliqonline",
|
||||
"elkm1",
|
||||
"elmax",
|
||||
"elv",
|
||||
@@ -963,7 +962,6 @@ INTEGRATIONS_WITHOUT_QUALITY_SCALE_FILE = [
|
||||
"uk_transport",
|
||||
"ukraine_alarm",
|
||||
"unifi_direct",
|
||||
"unifiled",
|
||||
"universal",
|
||||
"upb",
|
||||
"upc_connect",
|
||||
@@ -1265,7 +1263,6 @@ INTEGRATIONS_WITHOUT_SCALE = [
|
||||
"eight_sleep",
|
||||
"electrasmart",
|
||||
"elevenlabs",
|
||||
"eliqonline",
|
||||
"elkm1",
|
||||
"elmax",
|
||||
"elv",
|
||||
@@ -1955,7 +1952,6 @@ INTEGRATIONS_WITHOUT_SCALE = [
|
||||
"uk_transport",
|
||||
"ukraine_alarm",
|
||||
"unifi_direct",
|
||||
"unifiled",
|
||||
"universal",
|
||||
"upb",
|
||||
"upc_connect",
|
||||
|
||||
@@ -185,7 +185,6 @@ EXCEPTIONS = {
|
||||
"crownstone-core", # https://github.com/crownstone/crownstone-lib-python-core/pull/6
|
||||
"crownstone-sse", # https://github.com/crownstone/crownstone-lib-python-sse/pull/2
|
||||
"crownstone-uart", # https://github.com/crownstone/crownstone-lib-python-uart/pull/12
|
||||
"eliqonline", # https://github.com/molobrakos/eliqonline/pull/17
|
||||
"imutils", # https://github.com/PyImageSearch/imutils/pull/292
|
||||
"iso4217", # Public domain
|
||||
"kiwiki-client", # https://github.com/c7h/kiwiki_client/pull/6
|
||||
|
||||
@@ -6,15 +6,15 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.FAN_ONLY: 'fan_only'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 30,
|
||||
'min_temp': 16,
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 30,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 16,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -49,20 +49,20 @@
|
||||
# name: test_climate_entities[climate.living_room_living_room-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_humidity': 50.0,
|
||||
'current_temperature': 22.0,
|
||||
<ClimateEntityStateAttribute.CURRENT_HUMIDITY: 'current_humidity'>: 50.0,
|
||||
<ClimateEntityStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: 22.0,
|
||||
'friendly_name': 'Living Room',
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.FAN_ONLY: 'fan_only'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 30,
|
||||
'min_temp': 16,
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 30,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 16,
|
||||
'supported_features': <ClimateEntityFeature: 385>,
|
||||
'temperature': 24.0,
|
||||
<ClimateEntityStateAttribute.TEMPERATURE: 'temperature'>: 24.0,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.living_room_living_room',
|
||||
@@ -79,21 +79,21 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'fan_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.FAN_MODES: 'fan_modes'>: list([
|
||||
'auto',
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
]),
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.FAN_ONLY: 'fan_only'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 30.0,
|
||||
'min_temp': 16.0,
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 30.0,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 16.0,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -128,27 +128,27 @@
|
||||
# name: test_climate_entities[climate.test_system-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_humidity': 50.0,
|
||||
'current_temperature': 22.0,
|
||||
'fan_mode': 'low',
|
||||
'fan_modes': list([
|
||||
<ClimateEntityStateAttribute.CURRENT_HUMIDITY: 'current_humidity'>: 50.0,
|
||||
<ClimateEntityStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: 22.0,
|
||||
<ClimateEntityStateAttribute.FAN_MODE: 'fan_mode'>: 'low',
|
||||
<ClimateEntityCapabilityAttribute.FAN_MODES: 'fan_modes'>: list([
|
||||
'auto',
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
]),
|
||||
'friendly_name': 'Test System',
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.FAN_ONLY: 'fan_only'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 30.0,
|
||||
'min_temp': 16.0,
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 30.0,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 16.0,
|
||||
'supported_features': <ClimateEntityFeature: 393>,
|
||||
'temperature': 24.0,
|
||||
<ClimateEntityStateAttribute.TEMPERATURE: 'temperature'>: 24.0,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.test_system',
|
||||
|
||||
@@ -21,17 +21,17 @@
|
||||
# name: test_climate_myauto_main[climate.myauto]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': None,
|
||||
'fan_mode': 'auto',
|
||||
'fan_modes': list([
|
||||
<ClimateEntityStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: None,
|
||||
<ClimateEntityStateAttribute.FAN_MODE: 'fan_mode'>: 'auto',
|
||||
<ClimateEntityCapabilityAttribute.FAN_MODES: 'fan_modes'>: list([
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
'auto',
|
||||
]),
|
||||
'friendly_name': 'myauto',
|
||||
'hvac_action': <HVACAction.COOLING: 'cooling'>,
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityStateAttribute.HVAC_ACTION: 'hvac_action'>: <HVACAction.COOLING: 'cooling'>,
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
@@ -39,18 +39,18 @@
|
||||
<HVACMode.DRY: 'dry'>,
|
||||
<HVACMode.HEAT_COOL: 'heat_cool'>,
|
||||
]),
|
||||
'max_temp': 32,
|
||||
'min_temp': 16,
|
||||
'preset_mode': 'MyAuto',
|
||||
'preset_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 32,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 16,
|
||||
<ClimateEntityStateAttribute.PRESET_MODE: 'preset_mode'>: 'MyAuto',
|
||||
<ClimateEntityCapabilityAttribute.PRESET_MODES: 'preset_modes'>: list([
|
||||
'MyZone',
|
||||
'MyTemp',
|
||||
'MyAuto',
|
||||
]),
|
||||
'supported_features': <ClimateEntityFeature: 410>,
|
||||
'target_temp_high': 24,
|
||||
'target_temp_low': 20,
|
||||
'target_temp_step': 1,
|
||||
<ClimateEntityStateAttribute.TARGET_TEMP_HIGH: 'target_temp_high'>: 24,
|
||||
<ClimateEntityStateAttribute.TARGET_TEMP_LOW: 'target_temp_low'>: 20,
|
||||
<ClimateEntityCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 1,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.myauto',
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'1',
|
||||
'8',
|
||||
'30',
|
||||
@@ -49,7 +49,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Airgradient CO2 automatic baseline duration',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'1',
|
||||
'8',
|
||||
'30',
|
||||
@@ -73,7 +73,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'cloud',
|
||||
'local',
|
||||
]),
|
||||
@@ -112,7 +112,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Airgradient Configuration source',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'cloud',
|
||||
'local',
|
||||
]),
|
||||
@@ -132,7 +132,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'ugm3',
|
||||
'us_aqi',
|
||||
]),
|
||||
@@ -171,7 +171,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Airgradient Display PM standard',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'ugm3',
|
||||
'us_aqi',
|
||||
]),
|
||||
@@ -191,7 +191,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'c',
|
||||
'f',
|
||||
]),
|
||||
@@ -230,7 +230,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Airgradient Display temperature unit',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'c',
|
||||
'f',
|
||||
]),
|
||||
@@ -250,7 +250,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'off',
|
||||
'co2',
|
||||
'pm',
|
||||
@@ -290,7 +290,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Airgradient LED bar mode',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'off',
|
||||
'co2',
|
||||
'pm',
|
||||
@@ -311,7 +311,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'12',
|
||||
'60',
|
||||
'120',
|
||||
@@ -353,7 +353,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Airgradient NOx index learning offset',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'12',
|
||||
'60',
|
||||
'120',
|
||||
@@ -376,7 +376,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'12',
|
||||
'60',
|
||||
'120',
|
||||
@@ -418,7 +418,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Airgradient VOC index learning offset',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'12',
|
||||
'60',
|
||||
'120',
|
||||
@@ -441,7 +441,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'1',
|
||||
'8',
|
||||
'30',
|
||||
@@ -484,7 +484,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Airgradient CO2 automatic baseline duration',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'1',
|
||||
'8',
|
||||
'30',
|
||||
@@ -508,7 +508,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'cloud',
|
||||
'local',
|
||||
]),
|
||||
@@ -547,7 +547,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Airgradient Configuration source',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'cloud',
|
||||
'local',
|
||||
]),
|
||||
@@ -567,7 +567,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'12',
|
||||
'60',
|
||||
'120',
|
||||
@@ -609,7 +609,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Airgradient NOx index learning offset',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'12',
|
||||
'60',
|
||||
'120',
|
||||
@@ -632,7 +632,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'12',
|
||||
'60',
|
||||
'120',
|
||||
@@ -674,7 +674,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Airgradient VOC index learning offset',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'12',
|
||||
'60',
|
||||
'120',
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
]),
|
||||
'max_temp': 35.0,
|
||||
'min_temp': 5.0,
|
||||
'preset_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 35.0,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 5.0,
|
||||
<ClimateEntityCapabilityAttribute.PRESET_MODES: 'preset_modes'>: list([
|
||||
'home',
|
||||
'away',
|
||||
'boost',
|
||||
@@ -50,23 +50,23 @@
|
||||
# name: test_climate_entities[climate.test_thermostat-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_humidity': 45.0,
|
||||
'current_temperature': 22.0,
|
||||
<ClimateEntityStateAttribute.CURRENT_HUMIDITY: 'current_humidity'>: 45.0,
|
||||
<ClimateEntityStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: 22.0,
|
||||
'friendly_name': 'Test Thermostat',
|
||||
'hvac_action': <HVACAction.IDLE: 'idle'>,
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityStateAttribute.HVAC_ACTION: 'hvac_action'>: <HVACAction.IDLE: 'idle'>,
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
]),
|
||||
'max_temp': 35.0,
|
||||
'min_temp': 5.0,
|
||||
'preset_mode': 'home',
|
||||
'preset_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 35.0,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 5.0,
|
||||
<ClimateEntityStateAttribute.PRESET_MODE: 'preset_mode'>: 'home',
|
||||
<ClimateEntityCapabilityAttribute.PRESET_MODES: 'preset_modes'>: list([
|
||||
'home',
|
||||
'away',
|
||||
'boost',
|
||||
]),
|
||||
'supported_features': <ClimateEntityFeature: 17>,
|
||||
'temperature': 22.0,
|
||||
<ClimateEntityStateAttribute.TEMPERATURE: 'temperature'>: 22.0,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.test_thermostat',
|
||||
|
||||
@@ -6,19 +6,19 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'fan_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.FAN_MODES: 'fan_modes'>: list([
|
||||
'low',
|
||||
'high',
|
||||
'auto',
|
||||
]),
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 30.0,
|
||||
'min_temp': 16.0,
|
||||
'swing_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 30.0,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 16.0,
|
||||
<ClimateEntityCapabilityAttribute.SWING_MODES: 'swing_modes'>: list([
|
||||
'on',
|
||||
'off',
|
||||
]),
|
||||
@@ -56,21 +56,21 @@
|
||||
# name: test_climate_entities[None][climate.living_room-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'fan_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.FAN_MODES: 'fan_modes'>: list([
|
||||
'low',
|
||||
'high',
|
||||
'auto',
|
||||
]),
|
||||
'friendly_name': 'living room',
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 30.0,
|
||||
'min_temp': 16.0,
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 30.0,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 16.0,
|
||||
'supported_features': <ClimateEntityFeature: 425>,
|
||||
'swing_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.SWING_MODES: 'swing_modes'>: list([
|
||||
'on',
|
||||
'off',
|
||||
]),
|
||||
@@ -90,19 +90,19 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'fan_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.FAN_MODES: 'fan_modes'>: list([
|
||||
'low',
|
||||
'high',
|
||||
'auto',
|
||||
]),
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 30.0,
|
||||
'min_temp': 16.0,
|
||||
'swing_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 30.0,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 16.0,
|
||||
<ClimateEntityCapabilityAttribute.SWING_MODES: 'swing_modes'>: list([
|
||||
'on',
|
||||
'off',
|
||||
]),
|
||||
@@ -140,29 +140,29 @@
|
||||
# name: test_climate_entities[climate_data0][climate.living_room-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_humidity': 45.0,
|
||||
'current_temperature': 22.5,
|
||||
'fan_mode': 'high',
|
||||
'fan_modes': list([
|
||||
<ClimateEntityStateAttribute.CURRENT_HUMIDITY: 'current_humidity'>: 45.0,
|
||||
<ClimateEntityStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: 22.5,
|
||||
<ClimateEntityStateAttribute.FAN_MODE: 'fan_mode'>: 'high',
|
||||
<ClimateEntityCapabilityAttribute.FAN_MODES: 'fan_modes'>: list([
|
||||
'low',
|
||||
'high',
|
||||
'auto',
|
||||
]),
|
||||
'friendly_name': 'living room',
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 30.0,
|
||||
'min_temp': 16.0,
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 30.0,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 16.0,
|
||||
'supported_features': <ClimateEntityFeature: 425>,
|
||||
'swing_mode': 'off',
|
||||
'swing_modes': list([
|
||||
<ClimateEntityStateAttribute.SWING_MODE: 'swing_mode'>: 'off',
|
||||
<ClimateEntityCapabilityAttribute.SWING_MODES: 'swing_modes'>: list([
|
||||
'on',
|
||||
'off',
|
||||
]),
|
||||
'temperature': 22.0,
|
||||
<ClimateEntityStateAttribute.TEMPERATURE: 'temperature'>: 22.0,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.living_room',
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
# serializer version: 1
|
||||
# name: test_all_entities[select.echo_test_drop_in-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'all',
|
||||
'home',
|
||||
'off',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'select',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'select.echo_test_drop_in',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Drop In',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Drop In',
|
||||
'platform': 'alexa_devices',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'dropin',
|
||||
'unique_id': 'echo_test_serial_number-dropin',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_all_entities[select.echo_test_drop_in-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Echo Test Drop In',
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'all',
|
||||
'home',
|
||||
'off',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'select.echo_test_drop_in',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'all',
|
||||
})
|
||||
# ---
|
||||
@@ -0,0 +1,98 @@
|
||||
"""Tests for the Alexa Devices select platform."""
|
||||
|
||||
from copy import deepcopy
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.select import (
|
||||
DOMAIN as SELECT_DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, ATTR_OPTION, STATE_UNAVAILABLE, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from . import setup_integration
|
||||
from .const import TEST_DEVICE_1, TEST_DEVICE_1_SN
|
||||
|
||||
from tests.common import MockConfigEntry, snapshot_platform
|
||||
|
||||
ENTITY_ID = "select.echo_test_drop_in"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
|
||||
async def test_all_entities(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
mock_amazon_devices_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test all entities."""
|
||||
with patch("homeassistant.components.alexa_devices.PLATFORMS", [Platform.SELECT]):
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||
|
||||
|
||||
async def test_select_dropin_option(
|
||||
hass: HomeAssistant,
|
||||
mock_amazon_devices_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test selecting a drop-in option."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
assert (state := hass.states.get(ENTITY_ID))
|
||||
assert state.state == "all"
|
||||
|
||||
await hass.services.async_call(
|
||||
SELECT_DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_OPTION: "home"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert mock_amazon_devices_client.set_dropin_status.call_count == 1
|
||||
|
||||
assert (state := hass.states.get(ENTITY_ID))
|
||||
assert state.state == "home"
|
||||
|
||||
device_data = deepcopy(TEST_DEVICE_1)
|
||||
device_data.communication_settings = {
|
||||
"announcements": "ON",
|
||||
"communications": "ON",
|
||||
"dropin": "Off",
|
||||
}
|
||||
mock_amazon_devices_client.get_devices_data.return_value = {
|
||||
TEST_DEVICE_1_SN: device_data
|
||||
}
|
||||
|
||||
await hass.services.async_call(
|
||||
SELECT_DOMAIN,
|
||||
SERVICE_SELECT_OPTION,
|
||||
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_OPTION: "off"},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert mock_amazon_devices_client.set_dropin_status.call_count == 2
|
||||
assert (state := hass.states.get(ENTITY_ID))
|
||||
assert state.state == "off"
|
||||
|
||||
|
||||
async def test_offline_device(
|
||||
hass: HomeAssistant,
|
||||
mock_amazon_devices_client: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test offline device handling."""
|
||||
mock_amazon_devices_client.get_devices_data.return_value[
|
||||
TEST_DEVICE_1_SN
|
||||
].online = False
|
||||
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
assert (state := hass.states.get(ENTITY_ID))
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
@@ -6,7 +6,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'off',
|
||||
'level1',
|
||||
'level2',
|
||||
@@ -47,7 +47,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'My water heater Hot Water+ level',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'off',
|
||||
'level1',
|
||||
'level2',
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 40.0,
|
||||
'min_temp': 10.0,
|
||||
'preset_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 40.0,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 10.0,
|
||||
<ClimateEntityCapabilityAttribute.PRESET_MODES: 'preset_modes'>: list([
|
||||
'ready',
|
||||
'rest',
|
||||
]),
|
||||
@@ -50,22 +50,22 @@
|
||||
# name: test_climate[climate.fakespa-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 10.0,
|
||||
<ClimateEntityStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: 10.0,
|
||||
'friendly_name': 'FakeSpa',
|
||||
'hvac_action': <HVACAction.IDLE: 'idle'>,
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityStateAttribute.HVAC_ACTION: 'hvac_action'>: <HVACAction.IDLE: 'idle'>,
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 40.0,
|
||||
'min_temp': 10.0,
|
||||
'preset_mode': 'ready',
|
||||
'preset_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 40.0,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 10.0,
|
||||
<ClimateEntityStateAttribute.PRESET_MODE: 'preset_mode'>: 'ready',
|
||||
<ClimateEntityCapabilityAttribute.PRESET_MODES: 'preset_modes'>: list([
|
||||
'ready',
|
||||
'rest',
|
||||
]),
|
||||
'supported_features': <ClimateEntityFeature: 401>,
|
||||
'temperature': 40.0,
|
||||
<ClimateEntityStateAttribute.TEMPERATURE: 'temperature'>: 40.0,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.fakespa',
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'low',
|
||||
'high',
|
||||
]),
|
||||
@@ -45,7 +45,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'FakeSpa Temperature range',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'low',
|
||||
'high',
|
||||
]),
|
||||
|
||||
@@ -6,20 +6,20 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'fan_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.FAN_MODES: 'fan_modes'>: list([
|
||||
'auto',
|
||||
'low',
|
||||
'med',
|
||||
'high',
|
||||
]),
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.HEAT_COOL: 'heat_cool'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 95,
|
||||
'min_temp': 45,
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 95,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 45,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -54,28 +54,28 @@
|
||||
# name: test_setup_integration_success[climate.system_1_zone_1-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 75,
|
||||
'fan_mode': 'auto',
|
||||
'fan_modes': list([
|
||||
<ClimateEntityStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: 75,
|
||||
<ClimateEntityStateAttribute.FAN_MODE: 'fan_mode'>: 'auto',
|
||||
<ClimateEntityCapabilityAttribute.FAN_MODES: 'fan_modes'>: list([
|
||||
'auto',
|
||||
'low',
|
||||
'med',
|
||||
'high',
|
||||
]),
|
||||
'friendly_name': 'System 1 Zone 1',
|
||||
'hvac_action': <HVACAction.OFF: 'off'>,
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityStateAttribute.HVAC_ACTION: 'hvac_action'>: <HVACAction.OFF: 'off'>,
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.HEAT_COOL: 'heat_cool'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 95,
|
||||
'min_temp': 45,
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 95,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 45,
|
||||
'supported_features': <ClimateEntityFeature: 395>,
|
||||
'target_temp_high': None,
|
||||
'target_temp_low': None,
|
||||
'temperature': 72,
|
||||
<ClimateEntityStateAttribute.TARGET_TEMP_HIGH: 'target_temp_high'>: None,
|
||||
<ClimateEntityStateAttribute.TARGET_TEMP_LOW: 'target_temp_low'>: None,
|
||||
<ClimateEntityStateAttribute.TEMPERATURE: 'temperature'>: 72,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.system_1_zone_1',
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 20.0,
|
||||
'min_temp': 8.0,
|
||||
'preset_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 20.0,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 8.0,
|
||||
<ClimateEntityCapabilityAttribute.PRESET_MODES: 'preset_modes'>: list([
|
||||
'eco',
|
||||
'none',
|
||||
]),
|
||||
@@ -51,23 +51,23 @@
|
||||
# name: test_celsius_fahrenheit[climate.heating_circuit_1-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 18.6,
|
||||
<ClimateEntityStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: 18.6,
|
||||
'friendly_name': 'Heating circuit 1',
|
||||
'hvac_action': <HVACAction.OFF: 'off'>,
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityStateAttribute.HVAC_ACTION: 'hvac_action'>: <HVACAction.OFF: 'off'>,
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 20.0,
|
||||
'min_temp': 8.0,
|
||||
'preset_mode': 'none',
|
||||
'preset_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 20.0,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 8.0,
|
||||
<ClimateEntityStateAttribute.PRESET_MODE: 'preset_mode'>: 'none',
|
||||
<ClimateEntityCapabilityAttribute.PRESET_MODES: 'preset_modes'>: list([
|
||||
'eco',
|
||||
'none',
|
||||
]),
|
||||
'supported_features': <ClimateEntityFeature: 401>,
|
||||
'temperature': 18.5,
|
||||
<ClimateEntityStateAttribute.TEMPERATURE: 'temperature'>: 18.5,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.heating_circuit_1',
|
||||
@@ -84,14 +84,14 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 20.0,
|
||||
'min_temp': 8.0,
|
||||
'preset_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 20.0,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 8.0,
|
||||
<ClimateEntityCapabilityAttribute.PRESET_MODES: 'preset_modes'>: list([
|
||||
'eco',
|
||||
'none',
|
||||
]),
|
||||
@@ -129,23 +129,23 @@
|
||||
# name: test_climate_entity_properties[climate.heating_circuit_1-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 18.6,
|
||||
<ClimateEntityStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: 18.6,
|
||||
'friendly_name': 'Heating circuit 1',
|
||||
'hvac_action': <HVACAction.OFF: 'off'>,
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityStateAttribute.HVAC_ACTION: 'hvac_action'>: <HVACAction.OFF: 'off'>,
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 20.0,
|
||||
'min_temp': 8.0,
|
||||
'preset_mode': 'none',
|
||||
'preset_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 20.0,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 8.0,
|
||||
<ClimateEntityStateAttribute.PRESET_MODE: 'preset_mode'>: 'none',
|
||||
<ClimateEntityCapabilityAttribute.PRESET_MODES: 'preset_modes'>: list([
|
||||
'eco',
|
||||
'none',
|
||||
]),
|
||||
'supported_features': <ClimateEntityFeature: 401>,
|
||||
'temperature': 18.5,
|
||||
<ClimateEntityStateAttribute.TEMPERATURE: 'temperature'>: 18.5,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.heating_circuit_1',
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'Speaker A',
|
||||
'Speaker B',
|
||||
'Headphones',
|
||||
@@ -46,7 +46,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Cambridge Audio CXNv2 Audio output',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'Speaker A',
|
||||
'Speaker B',
|
||||
'Headphones',
|
||||
@@ -67,7 +67,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'amplifier',
|
||||
'receiver',
|
||||
'off',
|
||||
@@ -107,7 +107,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Cambridge Audio CXNv2 Control Bus mode',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'amplifier',
|
||||
'receiver',
|
||||
'off',
|
||||
@@ -128,7 +128,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'bright',
|
||||
'dim',
|
||||
'off',
|
||||
@@ -168,7 +168,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Cambridge Audio CXNv2 Display brightness',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'bright',
|
||||
'dim',
|
||||
'off',
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'15',
|
||||
'30',
|
||||
'45',
|
||||
@@ -48,7 +48,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Jar Dimming time',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'15',
|
||||
'30',
|
||||
'45',
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'fan_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.FAN_MODES: 'fan_modes'>: list([
|
||||
'auto',
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
]),
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
@@ -20,13 +20,13 @@
|
||||
<HVACMode.FAN_ONLY: 'fan_only'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
'swing_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 35,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 7,
|
||||
<ClimateEntityCapabilityAttribute.SWING_MODES: 'swing_modes'>: list([
|
||||
'off',
|
||||
'on',
|
||||
]),
|
||||
'target_temp_step': 1,
|
||||
<ClimateEntityCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -65,13 +65,13 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'fan_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.FAN_MODES: 'fan_modes'>: list([
|
||||
'auto',
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
]),
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
@@ -79,13 +79,13 @@
|
||||
<HVACMode.FAN_ONLY: 'fan_only'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
'swing_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 35,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 7,
|
||||
<ClimateEntityCapabilityAttribute.SWING_MODES: 'swing_modes'>: list([
|
||||
'off',
|
||||
'on',
|
||||
]),
|
||||
'target_temp_step': 1,
|
||||
<ClimateEntityCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -120,17 +120,17 @@
|
||||
# name: test_climate_state.2
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 27,
|
||||
<ClimateEntityStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: 27,
|
||||
'error_code': 0,
|
||||
'fan_mode': 'off',
|
||||
'fan_modes': list([
|
||||
<ClimateEntityStateAttribute.FAN_MODE: 'fan_mode'>: 'off',
|
||||
<ClimateEntityCapabilityAttribute.FAN_MODES: 'fan_modes'>: list([
|
||||
'auto',
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
]),
|
||||
'friendly_name': 'Midea 0',
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
@@ -138,16 +138,16 @@
|
||||
<HVACMode.FAN_ONLY: 'fan_only'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 35,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 7,
|
||||
'supported_features': <ClimateEntityFeature: 425>,
|
||||
'swing_mode': 'off',
|
||||
'swing_modes': list([
|
||||
<ClimateEntityStateAttribute.SWING_MODE: 'swing_mode'>: 'off',
|
||||
<ClimateEntityCapabilityAttribute.SWING_MODES: 'swing_modes'>: list([
|
||||
'off',
|
||||
'on',
|
||||
]),
|
||||
'target_temp_step': 1,
|
||||
'temperature': 23,
|
||||
<ClimateEntityCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 1,
|
||||
<ClimateEntityStateAttribute.TEMPERATURE: 'temperature'>: 23,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.midea_0',
|
||||
@@ -160,17 +160,17 @@
|
||||
# name: test_climate_state.3
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 26,
|
||||
<ClimateEntityStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: 26,
|
||||
'error_code': 0,
|
||||
'fan_mode': 'low',
|
||||
'fan_modes': list([
|
||||
<ClimateEntityStateAttribute.FAN_MODE: 'fan_mode'>: 'low',
|
||||
<ClimateEntityCapabilityAttribute.FAN_MODES: 'fan_modes'>: list([
|
||||
'auto',
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
]),
|
||||
'friendly_name': 'Midea 1',
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
@@ -178,16 +178,16 @@
|
||||
<HVACMode.FAN_ONLY: 'fan_only'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 35,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 7,
|
||||
'supported_features': <ClimateEntityFeature: 425>,
|
||||
'swing_mode': 'off',
|
||||
'swing_modes': list([
|
||||
<ClimateEntityStateAttribute.SWING_MODE: 'swing_mode'>: 'off',
|
||||
<ClimateEntityCapabilityAttribute.SWING_MODES: 'swing_modes'>: list([
|
||||
'off',
|
||||
'on',
|
||||
]),
|
||||
'target_temp_step': 1,
|
||||
'temperature': 24,
|
||||
<ClimateEntityCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 1,
|
||||
<ClimateEntityStateAttribute.TEMPERATURE: 'temperature'>: 24,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.midea_1',
|
||||
@@ -204,13 +204,13 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'fan_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.FAN_MODES: 'fan_modes'>: list([
|
||||
'auto',
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
]),
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
@@ -218,13 +218,13 @@
|
||||
<HVACMode.FAN_ONLY: 'fan_only'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
'swing_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 35,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 7,
|
||||
<ClimateEntityCapabilityAttribute.SWING_MODES: 'swing_modes'>: list([
|
||||
'off',
|
||||
'on',
|
||||
]),
|
||||
'target_temp_step': 1,
|
||||
<ClimateEntityCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -263,13 +263,13 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'fan_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.FAN_MODES: 'fan_modes'>: list([
|
||||
'auto',
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
]),
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
@@ -277,13 +277,13 @@
|
||||
<HVACMode.FAN_ONLY: 'fan_only'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
'swing_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 35,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 7,
|
||||
<ClimateEntityCapabilityAttribute.SWING_MODES: 'swing_modes'>: list([
|
||||
'off',
|
||||
'on',
|
||||
]),
|
||||
'target_temp_step': 1,
|
||||
<ClimateEntityCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -318,14 +318,14 @@
|
||||
# name: test_climate_state.6
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'fan_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.FAN_MODES: 'fan_modes'>: list([
|
||||
'auto',
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
]),
|
||||
'friendly_name': 'Midea 0',
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
@@ -333,14 +333,14 @@
|
||||
<HVACMode.FAN_ONLY: 'fan_only'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 35,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 7,
|
||||
'supported_features': <ClimateEntityFeature: 425>,
|
||||
'swing_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.SWING_MODES: 'swing_modes'>: list([
|
||||
'off',
|
||||
'on',
|
||||
]),
|
||||
'target_temp_step': 1,
|
||||
<ClimateEntityCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 1,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.midea_0',
|
||||
@@ -353,14 +353,14 @@
|
||||
# name: test_climate_state.7
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'fan_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.FAN_MODES: 'fan_modes'>: list([
|
||||
'auto',
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
]),
|
||||
'friendly_name': 'Midea 1',
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
@@ -368,14 +368,14 @@
|
||||
<HVACMode.FAN_ONLY: 'fan_only'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 35,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 7,
|
||||
'supported_features': <ClimateEntityFeature: 425>,
|
||||
'swing_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.SWING_MODES: 'swing_modes'>: list([
|
||||
'off',
|
||||
'on',
|
||||
]),
|
||||
'target_temp_step': 1,
|
||||
<ClimateEntityCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 1,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.midea_1',
|
||||
|
||||
@@ -6,18 +6,18 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 30,
|
||||
'min_temp': 5,
|
||||
'preset_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 30,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 5,
|
||||
<ClimateEntityCapabilityAttribute.PRESET_MODES: 'preset_modes'>: list([
|
||||
'automatic',
|
||||
'manual',
|
||||
]),
|
||||
'target_temp_step': 0.1,
|
||||
<ClimateEntityCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 0.1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -52,24 +52,24 @@
|
||||
# name: test_all_entities[climate.climate0-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 22.1,
|
||||
<ClimateEntityStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: 22.1,
|
||||
'friendly_name': 'Climate0',
|
||||
'hvac_action': <HVACAction.IDLE: 'idle'>,
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityStateAttribute.HVAC_ACTION: 'hvac_action'>: <HVACAction.IDLE: 'idle'>,
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 30,
|
||||
'min_temp': 5,
|
||||
'preset_mode': 'manual',
|
||||
'preset_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 30,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 5,
|
||||
<ClimateEntityStateAttribute.PRESET_MODE: 'preset_mode'>: 'manual',
|
||||
<ClimateEntityCapabilityAttribute.PRESET_MODES: 'preset_modes'>: list([
|
||||
'automatic',
|
||||
'manual',
|
||||
]),
|
||||
'supported_features': <ClimateEntityFeature: 401>,
|
||||
'target_temp_step': 0.1,
|
||||
'temperature': 5.0,
|
||||
<ClimateEntityCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 0.1,
|
||||
<ClimateEntityStateAttribute.TEMPERATURE: 'temperature'>: 5.0,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.climate0',
|
||||
|
||||
@@ -6,21 +6,21 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'fan_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.FAN_MODES: 'fan_modes'>: list([
|
||||
'off',
|
||||
'auto',
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
]),
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
'preset_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 35,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 7,
|
||||
<ClimateEntityCapabilityAttribute.PRESET_MODES: 'preset_modes'>: list([
|
||||
'home',
|
||||
'eco',
|
||||
'none',
|
||||
@@ -60,9 +60,9 @@
|
||||
# name: test_climate_entities_snapshot[climate.nano_color_2-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 21.0,
|
||||
'fan_mode': 'auto',
|
||||
'fan_modes': list([
|
||||
<ClimateEntityStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: 21.0,
|
||||
<ClimateEntityStateAttribute.FAN_MODE: 'fan_mode'>: 'auto',
|
||||
<ClimateEntityCapabilityAttribute.FAN_MODES: 'fan_modes'>: list([
|
||||
'off',
|
||||
'auto',
|
||||
'low',
|
||||
@@ -70,22 +70,22 @@
|
||||
'high',
|
||||
]),
|
||||
'friendly_name': 'Nano Color 2',
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
'preset_mode': 'home',
|
||||
'preset_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 35,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 7,
|
||||
<ClimateEntityStateAttribute.PRESET_MODE: 'preset_mode'>: 'home',
|
||||
<ClimateEntityCapabilityAttribute.PRESET_MODES: 'preset_modes'>: list([
|
||||
'home',
|
||||
'eco',
|
||||
'none',
|
||||
'away',
|
||||
]),
|
||||
'supported_features': <ClimateEntityFeature: 25>,
|
||||
'temperature': 22.0,
|
||||
<ClimateEntityStateAttribute.TEMPERATURE: 'temperature'>: 22.0,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.nano_color_2',
|
||||
@@ -102,13 +102,13 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
'preset_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 35,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 7,
|
||||
<ClimateEntityCapabilityAttribute.PRESET_MODES: 'preset_modes'>: list([
|
||||
'home',
|
||||
'away',
|
||||
]),
|
||||
@@ -146,16 +146,16 @@
|
||||
# name: test_climate_entities_snapshot[climate.r_900-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 21.0,
|
||||
<ClimateEntityStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: 21.0,
|
||||
'friendly_name': 'R 900',
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
'preset_mode': 'home',
|
||||
'preset_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 35,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 7,
|
||||
<ClimateEntityStateAttribute.PRESET_MODE: 'preset_mode'>: 'home',
|
||||
<ClimateEntityCapabilityAttribute.PRESET_MODES: 'preset_modes'>: list([
|
||||
'home',
|
||||
'away',
|
||||
]),
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'off',
|
||||
'auto',
|
||||
'on',
|
||||
@@ -46,7 +46,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Nano Color 2 Bypass',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'off',
|
||||
'auto',
|
||||
'on',
|
||||
@@ -67,7 +67,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'polish',
|
||||
'english',
|
||||
]),
|
||||
@@ -106,7 +106,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Nano Color 2 Language',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'polish',
|
||||
'english',
|
||||
]),
|
||||
@@ -126,7 +126,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'disabled',
|
||||
'eco',
|
||||
'hybrid',
|
||||
@@ -166,7 +166,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'R 900 Operating mode',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'disabled',
|
||||
'eco',
|
||||
'hybrid',
|
||||
|
||||
@@ -6,19 +6,19 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'fan_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.FAN_MODES: 'fan_modes'>: list([
|
||||
'auto',
|
||||
'on',
|
||||
'circulate',
|
||||
]),
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.HEAT_COOL: 'heat_cool'>,
|
||||
]),
|
||||
'max_temp': 95,
|
||||
'min_temp': 45,
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 95,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 45,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -53,28 +53,28 @@
|
||||
# name: test_climate_entities[climate.test_controller_residential_thermostat_v2-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_humidity': 45,
|
||||
'current_temperature': 72,
|
||||
'fan_mode': 'auto',
|
||||
'fan_modes': list([
|
||||
<ClimateEntityStateAttribute.CURRENT_HUMIDITY: 'current_humidity'>: 45,
|
||||
<ClimateEntityStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: 72,
|
||||
<ClimateEntityStateAttribute.FAN_MODE: 'fan_mode'>: 'auto',
|
||||
<ClimateEntityCapabilityAttribute.FAN_MODES: 'fan_modes'>: list([
|
||||
'auto',
|
||||
'on',
|
||||
'circulate',
|
||||
]),
|
||||
'friendly_name': 'Test Controller Residential Thermostat V2',
|
||||
'hvac_action': <HVACAction.OFF: 'off'>,
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityStateAttribute.HVAC_ACTION: 'hvac_action'>: <HVACAction.OFF: 'off'>,
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
<HVACMode.HEAT_COOL: 'heat_cool'>,
|
||||
]),
|
||||
'max_temp': 95,
|
||||
'min_temp': 45,
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 95,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 45,
|
||||
'supported_features': <ClimateEntityFeature: 395>,
|
||||
'target_temp_high': None,
|
||||
'target_temp_low': None,
|
||||
'temperature': 68,
|
||||
<ClimateEntityStateAttribute.TARGET_TEMP_HIGH: 'target_temp_high'>: None,
|
||||
<ClimateEntityStateAttribute.TARGET_TEMP_LOW: 'target_temp_low'>: None,
|
||||
<ClimateEntityStateAttribute.TEMPERATURE: 'temperature'>: 68,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.test_controller_residential_thermostat_v2',
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'fan_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.FAN_MODES: 'fan_modes'>: list([
|
||||
'smart',
|
||||
'auto',
|
||||
'high',
|
||||
@@ -15,14 +15,14 @@
|
||||
'on',
|
||||
'off',
|
||||
]),
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 35,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 7,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -57,9 +57,9 @@
|
||||
# name: test_climate_device_with_cooling_support[sensor_payload0][climate.zen_01-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 23.2,
|
||||
'fan_mode': 'off',
|
||||
'fan_modes': list([
|
||||
<ClimateEntityStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: 23.2,
|
||||
<ClimateEntityStateAttribute.FAN_MODE: 'fan_mode'>: 'off',
|
||||
<ClimateEntityCapabilityAttribute.FAN_MODES: 'fan_modes'>: list([
|
||||
'smart',
|
||||
'auto',
|
||||
'high',
|
||||
@@ -69,18 +69,18 @@
|
||||
'off',
|
||||
]),
|
||||
'friendly_name': 'Zen-01',
|
||||
'hvac_action': <HVACAction.IDLE: 'idle'>,
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityStateAttribute.HVAC_ACTION: 'hvac_action'>: <HVACAction.IDLE: 'idle'>,
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 35,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 7,
|
||||
'offset': 0,
|
||||
'supported_features': <ClimateEntityFeature: 393>,
|
||||
'temperature': 22.2,
|
||||
<ClimateEntityStateAttribute.TEMPERATURE: 'temperature'>: 22.2,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.zen_01',
|
||||
@@ -97,7 +97,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'fan_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.FAN_MODES: 'fan_modes'>: list([
|
||||
'smart',
|
||||
'auto',
|
||||
'high',
|
||||
@@ -106,14 +106,14 @@
|
||||
'on',
|
||||
'off',
|
||||
]),
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 35,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 7,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -148,9 +148,9 @@
|
||||
# name: test_climate_device_with_fan_support[sensor_payload0][climate.zen_01-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 23.2,
|
||||
'fan_mode': 'auto',
|
||||
'fan_modes': list([
|
||||
<ClimateEntityStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: 23.2,
|
||||
<ClimateEntityStateAttribute.FAN_MODE: 'fan_mode'>: 'auto',
|
||||
<ClimateEntityCapabilityAttribute.FAN_MODES: 'fan_modes'>: list([
|
||||
'smart',
|
||||
'auto',
|
||||
'high',
|
||||
@@ -160,18 +160,18 @@
|
||||
'off',
|
||||
]),
|
||||
'friendly_name': 'Zen-01',
|
||||
'hvac_action': <HVACAction.IDLE: 'idle'>,
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityStateAttribute.HVAC_ACTION: 'hvac_action'>: <HVACAction.IDLE: 'idle'>,
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 35,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 7,
|
||||
'offset': 0,
|
||||
'supported_features': <ClimateEntityFeature: 393>,
|
||||
'temperature': 22.2,
|
||||
<ClimateEntityStateAttribute.TEMPERATURE: 'temperature'>: 22.2,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.zen_01',
|
||||
@@ -188,7 +188,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'fan_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.FAN_MODES: 'fan_modes'>: list([
|
||||
'smart',
|
||||
'auto',
|
||||
'high',
|
||||
@@ -197,15 +197,15 @@
|
||||
'on',
|
||||
'off',
|
||||
]),
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
'preset_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 35,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 7,
|
||||
<ClimateEntityCapabilityAttribute.PRESET_MODES: 'preset_modes'>: list([
|
||||
'auto',
|
||||
'boost',
|
||||
'comfort',
|
||||
@@ -248,9 +248,9 @@
|
||||
# name: test_climate_device_with_preset[sensor_payload0][climate.zen_01-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 23.2,
|
||||
'fan_mode': 'off',
|
||||
'fan_modes': list([
|
||||
<ClimateEntityStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: 23.2,
|
||||
<ClimateEntityStateAttribute.FAN_MODE: 'fan_mode'>: 'off',
|
||||
<ClimateEntityCapabilityAttribute.FAN_MODES: 'fan_modes'>: list([
|
||||
'smart',
|
||||
'auto',
|
||||
'high',
|
||||
@@ -260,18 +260,18 @@
|
||||
'off',
|
||||
]),
|
||||
'friendly_name': 'Zen-01',
|
||||
'hvac_action': <HVACAction.IDLE: 'idle'>,
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityStateAttribute.HVAC_ACTION: 'hvac_action'>: <HVACAction.IDLE: 'idle'>,
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
<HVACMode.COOL: 'cool'>,
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 35,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 7,
|
||||
'offset': 0,
|
||||
'preset_mode': 'auto',
|
||||
'preset_modes': list([
|
||||
<ClimateEntityStateAttribute.PRESET_MODE: 'preset_mode'>: 'auto',
|
||||
<ClimateEntityCapabilityAttribute.PRESET_MODES: 'preset_modes'>: list([
|
||||
'auto',
|
||||
'boost',
|
||||
'comfort',
|
||||
@@ -281,7 +281,7 @@
|
||||
'manual',
|
||||
]),
|
||||
'supported_features': <ClimateEntityFeature: 409>,
|
||||
'temperature': 22.2,
|
||||
<ClimateEntityStateAttribute.TEMPERATURE: 'temperature'>: 22.2,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.zen_01',
|
||||
@@ -298,13 +298,13 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 35,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 7,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -339,19 +339,19 @@
|
||||
# name: test_climate_device_without_cooling_support[sensor_payload0][climate.thermostat-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 22.6,
|
||||
<ClimateEntityStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: 22.6,
|
||||
'friendly_name': 'Thermostat',
|
||||
'hvac_action': <HVACAction.HEATING: 'heating'>,
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityStateAttribute.HVAC_ACTION: 'hvac_action'>: <HVACAction.HEATING: 'heating'>,
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 35,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 7,
|
||||
'offset': 10,
|
||||
'supported_features': <ClimateEntityFeature: 385>,
|
||||
'temperature': 22.0,
|
||||
<ClimateEntityStateAttribute.TEMPERATURE: 'temperature'>: 22.0,
|
||||
'valve': 30,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -369,12 +369,12 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 35,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 7,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -409,17 +409,17 @@
|
||||
# name: test_clip_climate_device[config_entry_options0-sensor_payload0][climate.clip_thermostat-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 22.6,
|
||||
<ClimateEntityStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: 22.6,
|
||||
'friendly_name': 'CLIP thermostat',
|
||||
'hvac_action': <HVACAction.HEATING: 'heating'>,
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityStateAttribute.HVAC_ACTION: 'hvac_action'>: <HVACAction.HEATING: 'heating'>,
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 35,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 7,
|
||||
'supported_features': <ClimateEntityFeature: 385>,
|
||||
'temperature': None,
|
||||
<ClimateEntityStateAttribute.TEMPERATURE: 'temperature'>: None,
|
||||
'valve': 30,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -437,13 +437,13 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 35,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 7,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -478,19 +478,19 @@
|
||||
# name: test_clip_climate_device[config_entry_options0-sensor_payload0][climate.thermostat-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 22.6,
|
||||
<ClimateEntityStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: 22.6,
|
||||
'friendly_name': 'Thermostat',
|
||||
'hvac_action': <HVACAction.HEATING: 'heating'>,
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityStateAttribute.HVAC_ACTION: 'hvac_action'>: <HVACAction.HEATING: 'heating'>,
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 35,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 7,
|
||||
'offset': 10,
|
||||
'supported_features': <ClimateEntityFeature: 385>,
|
||||
'temperature': 22.0,
|
||||
<ClimateEntityStateAttribute.TEMPERATURE: 'temperature'>: 22.0,
|
||||
'valve': 30,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
@@ -508,12 +508,12 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 35,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 7,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -548,19 +548,19 @@
|
||||
# name: test_simple_climate_device[sensor_payload0][climate.thermostat-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 21.0,
|
||||
<ClimateEntityStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: 21.0,
|
||||
'friendly_name': 'thermostat',
|
||||
'hvac_action': <HVACAction.HEATING: 'heating'>,
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityStateAttribute.HVAC_ACTION: 'hvac_action'>: <HVACAction.HEATING: 'heating'>,
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'locked': True,
|
||||
'max_temp': 35,
|
||||
'min_temp': 7,
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 35,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 7,
|
||||
'offset': 0,
|
||||
'supported_features': <ClimateEntityFeature: 385>,
|
||||
'temperature': 21.0,
|
||||
<ClimateEntityStateAttribute.TEMPERATURE: 'temperature'>: 21.0,
|
||||
'valve': 24,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'leftright',
|
||||
'undirected',
|
||||
]),
|
||||
@@ -45,7 +45,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Aqara FP1 Device Mode',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'leftright',
|
||||
'undirected',
|
||||
]),
|
||||
@@ -65,7 +65,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'High',
|
||||
'Medium',
|
||||
'Low',
|
||||
@@ -105,7 +105,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Aqara FP1 Sensitivity',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'High',
|
||||
'Medium',
|
||||
'Low',
|
||||
@@ -126,7 +126,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'far',
|
||||
'medium',
|
||||
'near',
|
||||
@@ -166,7 +166,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Aqara FP1 Trigger Distance',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'far',
|
||||
'medium',
|
||||
'near',
|
||||
@@ -187,7 +187,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'leftright',
|
||||
'undirected',
|
||||
]),
|
||||
@@ -226,7 +226,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Aqara FP1 Device Mode',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'leftright',
|
||||
'undirected',
|
||||
]),
|
||||
@@ -246,7 +246,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'High',
|
||||
'Medium',
|
||||
'Low',
|
||||
@@ -286,7 +286,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Aqara FP1 Sensitivity',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'High',
|
||||
'Medium',
|
||||
'Low',
|
||||
@@ -307,7 +307,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'far',
|
||||
'medium',
|
||||
'near',
|
||||
@@ -347,7 +347,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Aqara FP1 Trigger Distance',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'far',
|
||||
'medium',
|
||||
'near',
|
||||
@@ -368,7 +368,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'leftright',
|
||||
'undirected',
|
||||
]),
|
||||
@@ -407,7 +407,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Aqara FP1 Device Mode',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'leftright',
|
||||
'undirected',
|
||||
]),
|
||||
@@ -427,7 +427,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'High',
|
||||
'Medium',
|
||||
'Low',
|
||||
@@ -467,7 +467,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Aqara FP1 Sensitivity',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'High',
|
||||
'Medium',
|
||||
'Low',
|
||||
@@ -488,7 +488,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'far',
|
||||
'medium',
|
||||
'near',
|
||||
@@ -528,7 +528,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Aqara FP1 Trigger Distance',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'far',
|
||||
'medium',
|
||||
'near',
|
||||
@@ -549,7 +549,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'off',
|
||||
'auto',
|
||||
'speed_1',
|
||||
@@ -593,7 +593,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'IKEA Starkvind Fan Mode',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'off',
|
||||
'auto',
|
||||
'speed_1',
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
# name: test_climate
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 20,
|
||||
<ClimateEntityStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: 20,
|
||||
'friendly_name': 'Test',
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
]),
|
||||
'max_temp': 24,
|
||||
'min_temp': 4,
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 24,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 4,
|
||||
'supported_features': <ClimateEntityFeature: 1>,
|
||||
'target_temp_step': 0.5,
|
||||
'temperature': 20,
|
||||
<ClimateEntityCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 0.5,
|
||||
<ClimateEntityStateAttribute.TEMPERATURE: 'temperature'>: 20,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.test_test',
|
||||
@@ -28,12 +28,12 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
]),
|
||||
'max_temp': 24,
|
||||
'min_temp': 4,
|
||||
'target_temp_step': 0.5,
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 24,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 4,
|
||||
<ClimateEntityCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 0.5,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
|
||||
@@ -237,6 +237,7 @@ def mock_duco_client(
|
||||
client.async_get_lan_info.return_value = mock_lan_info
|
||||
client.async_get_nodes.return_value = mock_nodes
|
||||
client.async_get_node_actions.return_value = mock_node_actions
|
||||
client.async_get_time_filter_remaining.return_value = 180
|
||||
client.async_get_diagnostics.return_value = [
|
||||
DiagComponent(component="Ventilation", status="Ok")
|
||||
]
|
||||
|
||||
@@ -435,7 +435,7 @@
|
||||
'state': '90',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_entities_state[sensor.living_state_end_time-entry]
|
||||
# name: test_sensor_entities_state[sensor.living_filter_remaining-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
@@ -449,7 +449,7 @@
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.living_state_end_time',
|
||||
'entity_id': 'sensor.living_filter_remaining',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
@@ -457,33 +457,37 @@
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'State end time',
|
||||
'object_id_base': 'Filter remaining',
|
||||
'options': dict({
|
||||
'sensor': dict({
|
||||
'suggested_display_precision': 0,
|
||||
}),
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
|
||||
'original_device_class': <SensorDeviceClass.DURATION: 'duration'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'State end time',
|
||||
'original_name': 'Filter remaining',
|
||||
'platform': 'duco',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'time_state_end',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff_1_time_state_end',
|
||||
'unit_of_measurement': None,
|
||||
'translation_key': 'filter_remaining',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff_1_filter_remaining',
|
||||
'unit_of_measurement': <UnitOfTime.DAYS: 'd'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_entities_state[sensor.living_state_end_time-state]
|
||||
# name: test_sensor_entities_state[sensor.living_filter_remaining-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'timestamp',
|
||||
'friendly_name': 'Living State end time',
|
||||
'device_class': 'duration',
|
||||
'friendly_name': 'Living Filter remaining',
|
||||
'unit_of_measurement': <UnitOfTime.DAYS: 'd'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.living_state_end_time',
|
||||
'entity_id': 'sensor.living_filter_remaining',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
'state': '180',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_entities_state[sensor.living_signal_strength-entry]
|
||||
@@ -541,6 +545,57 @@
|
||||
'state': '-60',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_entities_state[sensor.living_state_end_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': 'sensor',
|
||||
'entity_category': None,
|
||||
'entity_id': 'sensor.living_state_end_time',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'State end time',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.TIMESTAMP: 'timestamp'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'State end time',
|
||||
'platform': 'duco',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'time_state_end',
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff_1_time_state_end',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_entities_state[sensor.living_state_end_time-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'timestamp',
|
||||
'friendly_name': 'Living State end time',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.living_state_end_time',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensor_entities_state[sensor.living_target_flow_level-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
|
||||
@@ -26,6 +26,8 @@ from . import setup_platform_integration
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
|
||||
|
||||
FILTER_REMAINING_ENTITY_ID = "sensor.living_filter_remaining"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
async def init_integration(
|
||||
@@ -139,6 +141,57 @@ async def test_lan_info_failures_keep_node_entities_available(
|
||||
assert state.state == "-60"
|
||||
|
||||
|
||||
async def test_time_filter_remaining_missing_skips_sensor_creation(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_duco_client: AsyncMock,
|
||||
mock_sensor_nodes: list[Node],
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test the filter timer sensor is not created when unsupported."""
|
||||
mock_duco_client.async_get_nodes.return_value = mock_sensor_nodes
|
||||
|
||||
mock_duco_client.async_get_time_filter_remaining = AsyncMock(
|
||||
side_effect=[None, 180]
|
||||
)
|
||||
|
||||
await setup_platform_integration(hass, mock_config_entry, [Platform.SENSOR])
|
||||
|
||||
assert hass.states.get(FILTER_REMAINING_ENTITY_ID) is None
|
||||
|
||||
freezer.tick(SCAN_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
assert hass.states.get(FILTER_REMAINING_ENTITY_ID) is None
|
||||
|
||||
|
||||
async def test_time_filter_remaining_transient_failure_recovers_sensor_creation(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_duco_client: AsyncMock,
|
||||
mock_sensor_nodes: list[Node],
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test the filter timer sensor is added once a transient startup failure recovers."""
|
||||
mock_duco_client.async_get_nodes.return_value = mock_sensor_nodes
|
||||
mock_duco_client.async_get_time_filter_remaining = AsyncMock(
|
||||
side_effect=[DucoError("heat recovery info error"), 180]
|
||||
)
|
||||
|
||||
await setup_platform_integration(hass, mock_config_entry, [Platform.SENSOR])
|
||||
|
||||
assert hass.states.get(FILTER_REMAINING_ENTITY_ID) is None
|
||||
|
||||
freezer.tick(SCAN_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done(wait_background_tasks=True)
|
||||
|
||||
state = hass.states.get(FILTER_REMAINING_ENTITY_ID)
|
||||
assert state is not None
|
||||
assert state.state == "180"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"node_id",
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'Map 2',
|
||||
'1',
|
||||
]),
|
||||
@@ -45,7 +45,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'X8 PRO OMNI Active map',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'Map 2',
|
||||
'1',
|
||||
]),
|
||||
@@ -65,7 +65,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'auto',
|
||||
'smart',
|
||||
]),
|
||||
@@ -104,7 +104,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'X8 PRO OMNI Auto-empty frequency',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'auto',
|
||||
'smart',
|
||||
]),
|
||||
@@ -124,7 +124,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'mop',
|
||||
'mop_after_vacuum',
|
||||
'vacuum',
|
||||
@@ -165,7 +165,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'X8 PRO OMNI Work mode',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'mop',
|
||||
'mop_after_vacuum',
|
||||
'vacuum',
|
||||
@@ -187,7 +187,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'Map 2',
|
||||
'1',
|
||||
]),
|
||||
@@ -226,7 +226,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Dusty Active map',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'Map 2',
|
||||
'1',
|
||||
]),
|
||||
@@ -246,7 +246,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'auto',
|
||||
'smart',
|
||||
]),
|
||||
@@ -285,7 +285,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Dusty Auto-empty frequency',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'auto',
|
||||
'smart',
|
||||
]),
|
||||
@@ -305,7 +305,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
@@ -345,7 +345,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Dusty Water flow level',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
@@ -366,7 +366,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'Map 2',
|
||||
'1',
|
||||
]),
|
||||
@@ -405,7 +405,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Ozmo 950 Active map',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'Map 2',
|
||||
'1',
|
||||
]),
|
||||
@@ -425,7 +425,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
@@ -466,7 +466,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Ozmo 950 Water flow level',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
|
||||
@@ -6,18 +6,18 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
]),
|
||||
'max_temp': 32,
|
||||
'min_temp': 18,
|
||||
'preset_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 32,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 18,
|
||||
<ClimateEntityCapabilityAttribute.PRESET_MODES: 'preset_modes'>: list([
|
||||
'none',
|
||||
'bio_mode',
|
||||
'smart_mode',
|
||||
]),
|
||||
'target_temp_step': 0.5,
|
||||
<ClimateEntityCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 0.5,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -52,24 +52,24 @@
|
||||
# name: test_dynamic_new_devices[climate.mock_aquarium_mock_heater-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 24.2,
|
||||
<ClimateEntityStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: 24.2,
|
||||
'friendly_name': 'Mock Heater',
|
||||
'hvac_action': <HVACAction.HEATING: 'heating'>,
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityStateAttribute.HVAC_ACTION: 'hvac_action'>: <HVACAction.HEATING: 'heating'>,
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
]),
|
||||
'max_temp': 32,
|
||||
'min_temp': 18,
|
||||
'preset_mode': 'none',
|
||||
'preset_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 32,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 18,
|
||||
<ClimateEntityStateAttribute.PRESET_MODE: 'preset_mode'>: 'none',
|
||||
<ClimateEntityCapabilityAttribute.PRESET_MODES: 'preset_modes'>: list([
|
||||
'none',
|
||||
'bio_mode',
|
||||
'smart_mode',
|
||||
]),
|
||||
'supported_features': <ClimateEntityFeature: 401>,
|
||||
'target_temp_step': 0.5,
|
||||
'temperature': 25.5,
|
||||
<ClimateEntityCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 0.5,
|
||||
<ClimateEntityStateAttribute.TEMPERATURE: 'temperature'>: 25.5,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.mock_aquarium_mock_heater',
|
||||
@@ -86,18 +86,18 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
]),
|
||||
'max_temp': 32,
|
||||
'min_temp': 18,
|
||||
'preset_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 32,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 18,
|
||||
<ClimateEntityCapabilityAttribute.PRESET_MODES: 'preset_modes'>: list([
|
||||
'none',
|
||||
'bio_mode',
|
||||
'smart_mode',
|
||||
]),
|
||||
'target_temp_step': 0.5,
|
||||
<ClimateEntityCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 0.5,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
@@ -132,24 +132,24 @@
|
||||
# name: test_setup_heater[climate.mock_aquarium_mock_heater-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 24.2,
|
||||
<ClimateEntityStateAttribute.CURRENT_TEMPERATURE: 'current_temperature'>: 24.2,
|
||||
'friendly_name': 'Mock Heater',
|
||||
'hvac_action': <HVACAction.HEATING: 'heating'>,
|
||||
'hvac_modes': list([
|
||||
<ClimateEntityStateAttribute.HVAC_ACTION: 'hvac_action'>: <HVACAction.HEATING: 'heating'>,
|
||||
<ClimateEntityCapabilityAttribute.HVAC_MODES: 'hvac_modes'>: list([
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
]),
|
||||
'max_temp': 32,
|
||||
'min_temp': 18,
|
||||
'preset_mode': 'none',
|
||||
'preset_modes': list([
|
||||
<ClimateEntityCapabilityAttribute.MAX_TEMP: 'max_temp'>: 32,
|
||||
<ClimateEntityCapabilityAttribute.MIN_TEMP: 'min_temp'>: 18,
|
||||
<ClimateEntityStateAttribute.PRESET_MODE: 'preset_mode'>: 'none',
|
||||
<ClimateEntityCapabilityAttribute.PRESET_MODES: 'preset_modes'>: list([
|
||||
'none',
|
||||
'bio_mode',
|
||||
'smart_mode',
|
||||
]),
|
||||
'supported_features': <ClimateEntityFeature: 401>,
|
||||
'target_temp_step': 0.5,
|
||||
'temperature': 25.5,
|
||||
<ClimateEntityCapabilityAttribute.TARGET_TEMP_STEP: 'target_temp_step'>: 0.5,
|
||||
<ClimateEntityStateAttribute.TEMPERATURE: 'temperature'>: 25.5,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.mock_aquarium_mock_heater',
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'manual',
|
||||
'pulse',
|
||||
'bio',
|
||||
@@ -46,7 +46,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mock classicVARIO Filter mode',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'manual',
|
||||
'pulse',
|
||||
'bio',
|
||||
@@ -67,7 +67,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'400',
|
||||
'440',
|
||||
'480',
|
||||
@@ -119,7 +119,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mock filter Constant flow speed',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'400',
|
||||
'440',
|
||||
'480',
|
||||
@@ -153,7 +153,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'400',
|
||||
'440',
|
||||
'480',
|
||||
@@ -205,7 +205,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mock filter Day speed',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'400',
|
||||
'440',
|
||||
'480',
|
||||
@@ -239,7 +239,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'manual',
|
||||
'constant_flow',
|
||||
'pulse',
|
||||
@@ -280,7 +280,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mock filter Filter mode',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'manual',
|
||||
'constant_flow',
|
||||
'pulse',
|
||||
@@ -302,7 +302,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'400',
|
||||
'440',
|
||||
'480',
|
||||
@@ -354,7 +354,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mock filter High pulse speed',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'400',
|
||||
'440',
|
||||
'480',
|
||||
@@ -388,7 +388,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'400',
|
||||
'440',
|
||||
'480',
|
||||
@@ -440,7 +440,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mock filter Low pulse speed',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'400',
|
||||
'440',
|
||||
'480',
|
||||
@@ -474,7 +474,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'35',
|
||||
'37.5',
|
||||
'40.5',
|
||||
@@ -526,7 +526,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mock filter Manual speed',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'35',
|
||||
'37.5',
|
||||
'40.5',
|
||||
@@ -560,7 +560,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'400',
|
||||
'440',
|
||||
'480',
|
||||
@@ -612,7 +612,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mock filter Night speed',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'400',
|
||||
'440',
|
||||
'480',
|
||||
@@ -646,7 +646,7 @@
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'constant',
|
||||
'daycycle',
|
||||
]),
|
||||
@@ -685,7 +685,7 @@
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mock reeflex Operation mode',
|
||||
'options': list([
|
||||
<SelectEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'constant',
|
||||
'daycycle',
|
||||
]),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user