mirror of
https://github.com/home-assistant/core.git
synced 2026-02-04 06:15:47 +01:00
Compare commits
4 Commits
whirlpool_
...
pr-162044
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b9647e55e | ||
|
|
1146899115 | ||
|
|
eb4577ef33 | ||
|
|
cd2c946107 |
@@ -10,7 +10,6 @@
|
||||
"preview_features": {
|
||||
"snapshots": {
|
||||
"feedback_url": "https://forms.gle/GqvRmgmghSDco8M46",
|
||||
"learn_more_url": "https://www.home-assistant.io/blog/2026/02/02/about-device-database/",
|
||||
"report_issue_url": "https://github.com/OHF-Device-Database/device-database/issues/new"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""Support for Baidu speech service."""
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from aip import AipSpeech
|
||||
import voluptuous as vol
|
||||
@@ -10,7 +9,6 @@ from homeassistant.components.tts import (
|
||||
CONF_LANG,
|
||||
PLATFORM_SCHEMA as TTS_PLATFORM_SCHEMA,
|
||||
Provider,
|
||||
TtsAudioType,
|
||||
)
|
||||
from homeassistant.const import CONF_API_KEY
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
@@ -87,17 +85,17 @@ class BaiduTTSProvider(Provider):
|
||||
}
|
||||
|
||||
@property
|
||||
def default_language(self) -> str:
|
||||
def default_language(self):
|
||||
"""Return the default language."""
|
||||
return self._lang
|
||||
|
||||
@property
|
||||
def supported_languages(self) -> list[str]:
|
||||
def supported_languages(self):
|
||||
"""Return a list of supported languages."""
|
||||
return SUPPORTED_LANGUAGES
|
||||
|
||||
@property
|
||||
def default_options(self) -> dict[str, Any]:
|
||||
def default_options(self):
|
||||
"""Return a dict including default options."""
|
||||
return {
|
||||
CONF_PERSON: self._speech_conf_data[_OPTIONS[CONF_PERSON]],
|
||||
@@ -107,16 +105,11 @@ class BaiduTTSProvider(Provider):
|
||||
}
|
||||
|
||||
@property
|
||||
def supported_options(self) -> list[str]:
|
||||
def supported_options(self):
|
||||
"""Return a list of supported options."""
|
||||
return SUPPORTED_OPTIONS
|
||||
|
||||
def get_tts_audio(
|
||||
self,
|
||||
message: str,
|
||||
language: str,
|
||||
options: dict[str, Any],
|
||||
) -> TtsAudioType:
|
||||
def get_tts_audio(self, message, language, options):
|
||||
"""Load TTS from BaiduTTS."""
|
||||
|
||||
aip_speech = AipSpeech(
|
||||
|
||||
@@ -6,9 +6,16 @@ from typing import Any
|
||||
|
||||
from blinkpy.auth import Auth
|
||||
from blinkpy.blinkpy import Blink
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components import persistent_notification
|
||||
from homeassistant.const import CONF_SCAN_INTERVAL
|
||||
from homeassistant.const import (
|
||||
CONF_FILE_PATH,
|
||||
CONF_FILENAME,
|
||||
CONF_NAME,
|
||||
CONF_PIN,
|
||||
CONF_SCAN_INTERVAL,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
@@ -20,6 +27,13 @@ from .services import async_setup_services
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SERVICE_SAVE_VIDEO_SCHEMA = vol.Schema(
|
||||
{vol.Required(CONF_NAME): cv.string, vol.Required(CONF_FILENAME): cv.string}
|
||||
)
|
||||
SERVICE_SEND_PIN_SCHEMA = vol.Schema({vol.Optional(CONF_PIN): cv.string})
|
||||
SERVICE_SAVE_RECENT_CLIPS_SCHEMA = vol.Schema(
|
||||
{vol.Required(CONF_NAME): cv.string, vol.Required(CONF_FILE_PATH): cv.string}
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||
|
||||
|
||||
@@ -9,23 +9,35 @@ from typing import Any
|
||||
from blinkpy.auth import UnauthorizedError
|
||||
from blinkpy.camera import BlinkCamera as BlinkCameraAPI
|
||||
from requests.exceptions import ChunkedEncodingError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.camera import Camera
|
||||
from homeassistant.const import CONF_FILE_PATH, CONF_FILENAME
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import (
|
||||
ConfigEntryAuthFailed,
|
||||
HomeAssistantError,
|
||||
ServiceValidationError,
|
||||
)
|
||||
from homeassistant.helpers import config_validation as cv, entity_platform
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DEFAULT_BRAND, DOMAIN
|
||||
from .const import (
|
||||
DEFAULT_BRAND,
|
||||
DOMAIN,
|
||||
SERVICE_RECORD,
|
||||
SERVICE_SAVE_RECENT_CLIPS,
|
||||
SERVICE_SAVE_VIDEO,
|
||||
SERVICE_TRIGGER,
|
||||
)
|
||||
from .coordinator import BlinkConfigEntry, BlinkUpdateCoordinator
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
ATTR_VIDEO_CLIP = "video"
|
||||
ATTR_IMAGE = "image"
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
|
||||
@@ -44,6 +56,20 @@ async def async_setup_entry(
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
platform = entity_platform.async_get_current_platform()
|
||||
platform.async_register_entity_service(SERVICE_RECORD, None, "record")
|
||||
platform.async_register_entity_service(SERVICE_TRIGGER, None, "trigger_camera")
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_SAVE_RECENT_CLIPS,
|
||||
{vol.Required(CONF_FILE_PATH): cv.string},
|
||||
"save_recent_clips",
|
||||
)
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_SAVE_VIDEO,
|
||||
{vol.Required(CONF_FILENAME): cv.string},
|
||||
"save_video",
|
||||
)
|
||||
|
||||
|
||||
class BlinkCamera(CoordinatorEntity[BlinkUpdateCoordinator], Camera):
|
||||
"""An implementation of a Blink Camera."""
|
||||
|
||||
@@ -20,6 +20,11 @@ TYPE_TEMPERATURE = "temperature"
|
||||
TYPE_BATTERY = "battery"
|
||||
TYPE_WIFI_STRENGTH = "wifi_strength"
|
||||
|
||||
SERVICE_RECORD = "record"
|
||||
SERVICE_TRIGGER = "trigger_camera"
|
||||
SERVICE_SAVE_VIDEO = "save_video"
|
||||
SERVICE_SAVE_RECENT_CLIPS = "save_recent_clips"
|
||||
SERVICE_SEND_PIN = "send_pin"
|
||||
|
||||
PLATFORMS = [
|
||||
Platform.ALARM_CONTROL_PANEL,
|
||||
|
||||
@@ -4,27 +4,13 @@ from __future__ import annotations
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN
|
||||
from homeassistant.const import (
|
||||
ATTR_CONFIG_ENTRY_ID,
|
||||
CONF_FILE_PATH,
|
||||
CONF_FILENAME,
|
||||
CONF_PIN,
|
||||
)
|
||||
from homeassistant.const import ATTR_CONFIG_ENTRY_ID, CONF_PIN
|
||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv, issue_registry as ir, service
|
||||
from homeassistant.helpers import config_validation as cv, issue_registry as ir
|
||||
|
||||
from .const import DOMAIN
|
||||
from .const import DOMAIN, SERVICE_SEND_PIN
|
||||
|
||||
SERVICE_RECORD = "record"
|
||||
SERVICE_TRIGGER = "trigger_camera"
|
||||
SERVICE_SAVE_VIDEO = "save_video"
|
||||
SERVICE_SAVE_RECENT_CLIPS = "save_recent_clips"
|
||||
|
||||
|
||||
# Deprecated
|
||||
SERVICE_SEND_PIN = "send_pin"
|
||||
SERVICE_SEND_PIN_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required(ATTR_CONFIG_ENTRY_ID): vol.All(cv.ensure_list, [cv.string]),
|
||||
@@ -66,36 +52,3 @@ def async_setup_services(hass: HomeAssistant) -> None:
|
||||
_send_pin,
|
||||
schema=SERVICE_SEND_PIN_SCHEMA,
|
||||
)
|
||||
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
SERVICE_RECORD,
|
||||
entity_domain=CAMERA_DOMAIN,
|
||||
schema=None,
|
||||
func="record",
|
||||
)
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
SERVICE_TRIGGER,
|
||||
entity_domain=CAMERA_DOMAIN,
|
||||
schema=None,
|
||||
func="trigger_camera",
|
||||
)
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
SERVICE_SAVE_RECENT_CLIPS,
|
||||
entity_domain=CAMERA_DOMAIN,
|
||||
schema={vol.Required(CONF_FILE_PATH): cv.string},
|
||||
func="save_recent_clips",
|
||||
)
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
SERVICE_SAVE_VIDEO,
|
||||
entity_domain=CAMERA_DOMAIN,
|
||||
schema={vol.Required(CONF_FILENAME): cv.string},
|
||||
func="save_video",
|
||||
)
|
||||
|
||||
@@ -16,17 +16,14 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import config_validation as cv, device_registry as dr
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.entity import SLOW_UPDATE_WARNING
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import BRIDGE_MAKE, DOMAIN
|
||||
from .models import BondData
|
||||
from .services import async_setup_services
|
||||
from .utils import BondHub
|
||||
|
||||
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||
PLATFORMS = [
|
||||
Platform.BUTTON,
|
||||
Platform.COVER,
|
||||
@@ -41,12 +38,6 @@ _LOGGER = logging.getLogger(__name__)
|
||||
type BondConfigEntry = ConfigEntry[BondData]
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the component."""
|
||||
async_setup_services(hass)
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: BondConfigEntry) -> bool:
|
||||
"""Set up Bond from a config entry."""
|
||||
host = entry.data[CONF_HOST]
|
||||
|
||||
@@ -5,3 +5,10 @@ BRIDGE_MAKE = "Olibra"
|
||||
DOMAIN = "bond"
|
||||
|
||||
CONF_BOND_ID: str = "bond_id"
|
||||
|
||||
|
||||
SERVICE_SET_FAN_SPEED_TRACKED_STATE = "set_fan_speed_tracked_state"
|
||||
SERVICE_SET_POWER_TRACKED_STATE = "set_switch_power_tracked_state"
|
||||
SERVICE_SET_LIGHT_POWER_TRACKED_STATE = "set_light_power_tracked_state"
|
||||
SERVICE_SET_LIGHT_BRIGHTNESS_TRACKED_STATE = "set_light_brightness_tracked_state"
|
||||
ATTR_POWER_STATE = "power_state"
|
||||
|
||||
@@ -8,6 +8,7 @@ from typing import Any
|
||||
|
||||
from aiohttp.client_exceptions import ClientResponseError
|
||||
from bond_async import Action, DeviceType, Direction
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.fan import (
|
||||
DIRECTION_FORWARD,
|
||||
@@ -17,6 +18,7 @@ from homeassistant.components.fan import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_platform
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.util.percentage import (
|
||||
percentage_to_ranged_value,
|
||||
@@ -25,6 +27,7 @@ from homeassistant.util.percentage import (
|
||||
from homeassistant.util.scaling import int_states_in_range
|
||||
|
||||
from . import BondConfigEntry
|
||||
from .const import SERVICE_SET_FAN_SPEED_TRACKED_STATE
|
||||
from .entity import BondEntity
|
||||
from .models import BondData
|
||||
from .utils import BondDevice
|
||||
@@ -41,6 +44,12 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Set up Bond fan devices."""
|
||||
data = entry.runtime_data
|
||||
platform = entity_platform.async_get_current_platform()
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_SET_FAN_SPEED_TRACKED_STATE,
|
||||
{vol.Required("speed"): vol.All(vol.Number(scale=0), vol.Range(0, 100))},
|
||||
"async_set_speed_belief",
|
||||
)
|
||||
|
||||
async_add_entities(
|
||||
BondFan(data, device)
|
||||
|
||||
@@ -7,20 +7,37 @@ from typing import Any
|
||||
|
||||
from aiohttp.client_exceptions import ClientResponseError
|
||||
from bond_async import Action, DeviceType
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv, entity_platform
|
||||
from homeassistant.helpers.entity import Entity
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import BondConfigEntry
|
||||
from .const import (
|
||||
ATTR_POWER_STATE,
|
||||
SERVICE_SET_LIGHT_BRIGHTNESS_TRACKED_STATE,
|
||||
SERVICE_SET_LIGHT_POWER_TRACKED_STATE,
|
||||
)
|
||||
from .entity import BondEntity
|
||||
from .models import BondData
|
||||
from .utils import BondDevice
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SERVICE_START_INCREASING_BRIGHTNESS = "start_increasing_brightness"
|
||||
SERVICE_START_DECREASING_BRIGHTNESS = "start_decreasing_brightness"
|
||||
SERVICE_STOP = "stop"
|
||||
|
||||
ENTITY_SERVICES = [
|
||||
SERVICE_START_INCREASING_BRIGHTNESS,
|
||||
SERVICE_START_DECREASING_BRIGHTNESS,
|
||||
SERVICE_STOP,
|
||||
]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
@@ -31,6 +48,14 @@ async def async_setup_entry(
|
||||
data = entry.runtime_data
|
||||
hub = data.hub
|
||||
|
||||
platform = entity_platform.async_get_current_platform()
|
||||
for service in ENTITY_SERVICES:
|
||||
platform.async_register_entity_service(
|
||||
service,
|
||||
None,
|
||||
f"async_{service}",
|
||||
)
|
||||
|
||||
fan_lights: list[Entity] = [
|
||||
BondLight(data, device)
|
||||
for device in hub.devices
|
||||
@@ -69,6 +94,22 @@ async def async_setup_entry(
|
||||
if DeviceType.is_light(device.type)
|
||||
]
|
||||
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_SET_LIGHT_BRIGHTNESS_TRACKED_STATE,
|
||||
{
|
||||
vol.Required(ATTR_BRIGHTNESS): vol.All(
|
||||
vol.Number(scale=0), vol.Range(0, 255)
|
||||
)
|
||||
},
|
||||
"async_set_brightness_belief",
|
||||
)
|
||||
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_SET_LIGHT_POWER_TRACKED_STATE,
|
||||
{vol.Required(ATTR_POWER_STATE): vol.All(cv.boolean)},
|
||||
"async_set_power_belief",
|
||||
)
|
||||
|
||||
async_add_entities(
|
||||
fan_lights + fan_up_lights + fan_down_lights + fireplaces + fp_lights + lights,
|
||||
)
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
"""Support for Bond services."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.fan import DOMAIN as FAN_DOMAIN
|
||||
from homeassistant.components.light import ATTR_BRIGHTNESS, DOMAIN as LIGHT_DOMAIN
|
||||
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import config_validation as cv, service
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
ATTR_POWER_STATE = "power_state"
|
||||
|
||||
# Fan
|
||||
SERVICE_SET_FAN_SPEED_TRACKED_STATE = "set_fan_speed_tracked_state"
|
||||
|
||||
# Switch
|
||||
SERVICE_SET_POWER_TRACKED_STATE = "set_switch_power_tracked_state"
|
||||
|
||||
# Light
|
||||
SERVICE_SET_LIGHT_POWER_TRACKED_STATE = "set_light_power_tracked_state"
|
||||
SERVICE_SET_LIGHT_BRIGHTNESS_TRACKED_STATE = "set_light_brightness_tracked_state"
|
||||
SERVICE_START_INCREASING_BRIGHTNESS = "start_increasing_brightness"
|
||||
SERVICE_START_DECREASING_BRIGHTNESS = "start_decreasing_brightness"
|
||||
SERVICE_STOP = "stop"
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_services(hass: HomeAssistant) -> None:
|
||||
"""Home Assistant services."""
|
||||
|
||||
# Fan entity services
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
SERVICE_SET_FAN_SPEED_TRACKED_STATE,
|
||||
entity_domain=FAN_DOMAIN,
|
||||
schema={vol.Required("speed"): vol.All(vol.Number(scale=0), vol.Range(0, 100))},
|
||||
func="async_set_speed_belief",
|
||||
)
|
||||
|
||||
# Light entity services
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
SERVICE_START_INCREASING_BRIGHTNESS,
|
||||
entity_domain=LIGHT_DOMAIN,
|
||||
schema=None,
|
||||
func="async_start_increasing_brightness",
|
||||
)
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
SERVICE_START_DECREASING_BRIGHTNESS,
|
||||
entity_domain=LIGHT_DOMAIN,
|
||||
schema=None,
|
||||
func="async_start_decreasing_brightness",
|
||||
)
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
SERVICE_STOP,
|
||||
entity_domain=LIGHT_DOMAIN,
|
||||
schema=None,
|
||||
func="async_stop",
|
||||
)
|
||||
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
SERVICE_SET_LIGHT_BRIGHTNESS_TRACKED_STATE,
|
||||
entity_domain=LIGHT_DOMAIN,
|
||||
schema={
|
||||
vol.Required(ATTR_BRIGHTNESS): vol.All(
|
||||
vol.Number(scale=0), vol.Range(0, 255)
|
||||
)
|
||||
},
|
||||
func="async_set_brightness_belief",
|
||||
)
|
||||
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
SERVICE_SET_LIGHT_POWER_TRACKED_STATE,
|
||||
entity_domain=LIGHT_DOMAIN,
|
||||
schema={vol.Required(ATTR_POWER_STATE): vol.All(cv.boolean)},
|
||||
func="async_set_power_belief",
|
||||
)
|
||||
|
||||
# Switch entity services
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
SERVICE_SET_POWER_TRACKED_STATE,
|
||||
entity_domain=SWITCH_DOMAIN,
|
||||
schema={vol.Required(ATTR_POWER_STATE): cv.boolean},
|
||||
func="async_set_power_belief",
|
||||
)
|
||||
@@ -6,13 +6,16 @@ from typing import Any
|
||||
|
||||
from aiohttp.client_exceptions import ClientResponseError
|
||||
from bond_async import Action, DeviceType
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.switch import SwitchEntity
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv, entity_platform
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import BondConfigEntry
|
||||
from .const import ATTR_POWER_STATE, SERVICE_SET_POWER_TRACKED_STATE
|
||||
from .entity import BondEntity
|
||||
|
||||
|
||||
@@ -23,6 +26,12 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Set up Bond generic devices."""
|
||||
data = entry.runtime_data
|
||||
platform = entity_platform.async_get_current_platform()
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_SET_POWER_TRACKED_STATE,
|
||||
{vol.Required(ATTR_POWER_STATE): cv.boolean},
|
||||
"async_set_power_belief",
|
||||
)
|
||||
|
||||
async_add_entities(
|
||||
BondSwitch(data, device)
|
||||
|
||||
@@ -21,7 +21,7 @@ async def fetch_latest_carbon_intensity(
|
||||
em: ElectricityMaps,
|
||||
config: Mapping[str, Any],
|
||||
) -> HomeAssistantCarbonIntensityResponse:
|
||||
"""Fetch the latest carbon intensity based on zone key or location coordinates."""
|
||||
"""Fetch the latest carbon intensity based on country code or location coordinates."""
|
||||
request: CoordinatesRequest | ZoneRequest = CoordinatesRequest(
|
||||
lat=config.get(CONF_LATITUDE, hass.config.latitude),
|
||||
lon=config.get(CONF_LONGITUDE, hass.config.longitude),
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
},
|
||||
"error": {
|
||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||
"no_data": "No data is available for the location or zone you have selected.",
|
||||
"no_data": "No data is available for the location you have selected.",
|
||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"step": {
|
||||
@@ -17,20 +17,20 @@
|
||||
},
|
||||
"country": {
|
||||
"data": {
|
||||
"country_code": "Zone key"
|
||||
"country_code": "Country code"
|
||||
}
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"data": {
|
||||
"api_key": "[%key:common::config_flow::data::api_key%]"
|
||||
"api_key": "[%key:common::config_flow::data::access_token%]"
|
||||
}
|
||||
},
|
||||
"user": {
|
||||
"data": {
|
||||
"api_key": "[%key:common::config_flow::data::api_key%]",
|
||||
"api_key": "[%key:common::config_flow::data::access_token%]",
|
||||
"location": "[%key:common::config_flow::data::location%]"
|
||||
},
|
||||
"description": "Visit the [Electricity Maps app]({register_link}) to request an API key."
|
||||
"description": "Visit the [Electricity Maps page]({register_link}) to request a token."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -40,7 +40,7 @@
|
||||
"name": "CO2 intensity",
|
||||
"state_attributes": {
|
||||
"country_code": {
|
||||
"name": "Zone key"
|
||||
"name": "Country code"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -58,7 +58,7 @@
|
||||
"location": {
|
||||
"options": {
|
||||
"specify_coordinates": "Specify coordinates",
|
||||
"specify_country_code": "Specify zone key",
|
||||
"specify_country_code": "Specify country code",
|
||||
"use_home_location": "Use home location"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,23 +2,12 @@
|
||||
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import ElgatoConfigEntry, ElgatoDataUpdateCoordinator
|
||||
from .services import async_setup_services
|
||||
|
||||
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||
PLATFORMS = [Platform.BUTTON, Platform.LIGHT, Platform.SENSOR, Platform.SWITCH]
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the component."""
|
||||
async_setup_services(hass)
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ElgatoConfigEntry) -> bool:
|
||||
"""Set up Elgato Light from a config entry."""
|
||||
coordinator = ElgatoDataUpdateCoordinator(hass, entry)
|
||||
|
||||
@@ -14,3 +14,6 @@ SCAN_INTERVAL = timedelta(seconds=10)
|
||||
|
||||
# Attributes
|
||||
ATTR_ON = "on"
|
||||
|
||||
# Services
|
||||
SERVICE_IDENTIFY = "identify"
|
||||
|
||||
@@ -15,9 +15,13 @@ from homeassistant.components.light import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.entity_platform import (
|
||||
AddConfigEntryEntitiesCallback,
|
||||
async_get_current_platform,
|
||||
)
|
||||
from homeassistant.util import color as color_util
|
||||
|
||||
from .const import SERVICE_IDENTIFY
|
||||
from .coordinator import ElgatoConfigEntry, ElgatoDataUpdateCoordinator
|
||||
from .entity import ElgatoEntity
|
||||
|
||||
@@ -33,6 +37,13 @@ async def async_setup_entry(
|
||||
coordinator = entry.runtime_data
|
||||
async_add_entities([ElgatoLight(coordinator)])
|
||||
|
||||
platform = async_get_current_platform()
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_IDENTIFY,
|
||||
None,
|
||||
ElgatoLight.async_identify.__name__,
|
||||
)
|
||||
|
||||
|
||||
class ElgatoLight(ElgatoEntity, LightEntity):
|
||||
"""Defines an Elgato Light."""
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
"""Support for Elgato services."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import service
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
SERVICE_IDENTIFY = "identify"
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_services(hass: HomeAssistant) -> None:
|
||||
"""Set up services."""
|
||||
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
SERVICE_IDENTIFY,
|
||||
entity_domain=LIGHT_DOMAIN,
|
||||
schema=None,
|
||||
func="async_identify",
|
||||
)
|
||||
@@ -21,5 +21,5 @@
|
||||
"integration_type": "system",
|
||||
"preview_features": { "winter_mode": {} },
|
||||
"quality_scale": "internal",
|
||||
"requirements": ["home-assistant-frontend==20260128.4"]
|
||||
"requirements": ["home-assistant-frontend==20260128.3"]
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ class GPSLoggerEntity(TrackerEntity, RestoreEntity):
|
||||
)
|
||||
|
||||
@property
|
||||
def battery_level(self) -> int | None:
|
||||
def battery_level(self):
|
||||
"""Return battery value of the device."""
|
||||
return self._battery
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ from .const import (
|
||||
)
|
||||
from .coordinator import GrowattConfigEntry, GrowattCoordinator
|
||||
from .models import GrowattRuntimeData
|
||||
from .services import async_setup_services
|
||||
from .services import async_register_services
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -37,7 +37,7 @@ CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the Growatt Server component."""
|
||||
# Register services
|
||||
async_setup_services(hass)
|
||||
await async_register_services(hass)
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ from datetime import datetime
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant, ServiceCall, SupportsResponse, callback
|
||||
from homeassistant.core import HomeAssistant, ServiceCall, SupportsResponse
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
@@ -21,8 +21,7 @@ if TYPE_CHECKING:
|
||||
from .coordinator import GrowattCoordinator
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_services(hass: HomeAssistant) -> None:
|
||||
async def async_register_services(hass: HomeAssistant) -> None:
|
||||
"""Register services for Growatt Server integration."""
|
||||
|
||||
def get_min_coordinators() -> dict[str, GrowattCoordinator]:
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"iot_class": "local_push",
|
||||
"loggers": ["pyhik"],
|
||||
"quality_scale": "legacy",
|
||||
"requirements": ["pyHik==0.4.2"]
|
||||
"requirements": ["pyHik==0.4.1"]
|
||||
}
|
||||
|
||||
@@ -115,6 +115,7 @@ SENSORS = (
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
native_unit_of_measurement=UnitOfVolume.MILLILITERS,
|
||||
device_class=SensorDeviceClass.VOLUME,
|
||||
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||
translation_key="hot_water_counter",
|
||||
),
|
||||
HomeConnectSensorEntityDescription(
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
"""The Husqvarna Automower integration."""
|
||||
|
||||
import logging
|
||||
|
||||
from aioautomower.session import AutomowerSession
|
||||
from aiohttp import ClientResponseError
|
||||
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers import (
|
||||
aiohttp_client,
|
||||
config_entry_oauth2_flow,
|
||||
config_validation as cv,
|
||||
)
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.helpers import aiohttp_client, config_entry_oauth2_flow
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from . import api
|
||||
from .const import DOMAIN
|
||||
from .coordinator import AutomowerConfigEntry, AutomowerDataUpdateCoordinator
|
||||
from .services import async_setup_services
|
||||
|
||||
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS: list[Platform] = [
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.BUTTON,
|
||||
@@ -34,12 +30,6 @@ PLATFORMS: list[Platform] = [
|
||||
]
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the component."""
|
||||
async_setup_services(hass)
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: AutomowerConfigEntry) -> bool:
|
||||
"""Set up this integration using UI."""
|
||||
implementation = (
|
||||
|
||||
@@ -142,6 +142,3 @@ ERROR_KEYS = [
|
||||
"wrong_pin_code",
|
||||
"zone_generator_problem",
|
||||
]
|
||||
|
||||
MOW = "mow"
|
||||
PARK = "park"
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
"""Husqvarna Automower lawn mower entity."""
|
||||
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from aioautomower.model import MowerActivities, MowerStates, WorkArea
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.lawn_mower import (
|
||||
LawnMowerActivity,
|
||||
@@ -12,13 +14,16 @@ from homeassistant.components.lawn_mower import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.helpers import config_validation as cv, entity_platform
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import AutomowerConfigEntry
|
||||
from .const import DOMAIN, ERROR_STATES, MOW, PARK
|
||||
from .const import DOMAIN, ERROR_STATES
|
||||
from .coordinator import AutomowerDataUpdateCoordinator
|
||||
from .entity import AutomowerBaseEntity, handle_sending_exception
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
DOCKED_ACTIVITIES = (MowerActivities.PARKED_IN_CS, MowerActivities.CHARGING)
|
||||
@@ -36,6 +41,9 @@ SUPPORT_STATE_SERVICES = (
|
||||
| LawnMowerEntityFeature.PAUSE
|
||||
| LawnMowerEntityFeature.START_MOWING
|
||||
)
|
||||
MOW = "mow"
|
||||
PARK = "park"
|
||||
OVERRIDE_MODES = [MOW, PARK]
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
@@ -54,6 +62,31 @@ async def async_setup_entry(
|
||||
_async_add_new_devices(set(coordinator.data))
|
||||
|
||||
coordinator.new_devices_callbacks.append(_async_add_new_devices)
|
||||
platform = entity_platform.async_get_current_platform()
|
||||
platform.async_register_entity_service(
|
||||
"override_schedule",
|
||||
{
|
||||
vol.Required("override_mode"): vol.In(OVERRIDE_MODES),
|
||||
vol.Required("duration"): vol.All(
|
||||
cv.time_period,
|
||||
cv.positive_timedelta,
|
||||
vol.Range(min=timedelta(minutes=1), max=timedelta(days=42)),
|
||||
),
|
||||
},
|
||||
"async_override_schedule",
|
||||
)
|
||||
platform.async_register_entity_service(
|
||||
"override_schedule_work_area",
|
||||
{
|
||||
vol.Required("work_area_id"): vol.Coerce(int),
|
||||
vol.Required("duration"): vol.All(
|
||||
cv.time_period,
|
||||
cv.positive_timedelta,
|
||||
vol.Range(min=timedelta(minutes=1), max=timedelta(days=42)),
|
||||
),
|
||||
},
|
||||
"async_override_schedule_work_area",
|
||||
)
|
||||
|
||||
|
||||
class AutomowerLawnMowerEntity(AutomowerBaseEntity, LawnMowerEntity):
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
"""Husqvarna Automower services."""
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.lawn_mower import DOMAIN as LAWN_MOWER_DOMAIN
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import config_validation as cv, service
|
||||
|
||||
from .const import DOMAIN, MOW, PARK
|
||||
|
||||
OVERRIDE_MODES = [MOW, PARK]
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_services(hass: HomeAssistant) -> None:
|
||||
"""Set up services."""
|
||||
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"override_schedule",
|
||||
entity_domain=LAWN_MOWER_DOMAIN,
|
||||
schema={
|
||||
vol.Required("override_mode"): vol.In(OVERRIDE_MODES),
|
||||
vol.Required("duration"): vol.All(
|
||||
cv.time_period,
|
||||
cv.positive_timedelta,
|
||||
vol.Range(min=timedelta(minutes=1), max=timedelta(days=42)),
|
||||
),
|
||||
},
|
||||
func="async_override_schedule",
|
||||
)
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
"override_schedule_work_area",
|
||||
entity_domain=LAWN_MOWER_DOMAIN,
|
||||
schema={
|
||||
vol.Required("work_area_id"): vol.Coerce(int),
|
||||
vol.Required("duration"): vol.All(
|
||||
cv.time_period,
|
||||
cv.positive_timedelta,
|
||||
vol.Range(min=timedelta(minutes=1), max=timedelta(days=42)),
|
||||
),
|
||||
},
|
||||
func="async_override_schedule_work_area",
|
||||
)
|
||||
@@ -17,7 +17,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
|
||||
from .coordinator import LiebherrConfigEntry, LiebherrCoordinator
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.NUMBER, Platform.SENSOR]
|
||||
PLATFORMS: list[Platform] = [Platform.SENSOR]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: LiebherrConfigEntry) -> bool:
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
"""Number platform for Liebherr integration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
|
||||
from pyliebherrhomeapi import (
|
||||
LiebherrConnectionError,
|
||||
LiebherrTimeoutError,
|
||||
TemperatureControl,
|
||||
TemperatureUnit,
|
||||
)
|
||||
|
||||
from homeassistant.components.number import (
|
||||
DEFAULT_MAX_VALUE,
|
||||
DEFAULT_MIN_VALUE,
|
||||
NumberDeviceClass,
|
||||
NumberEntity,
|
||||
NumberEntityDescription,
|
||||
)
|
||||
from homeassistant.const import UnitOfTemperature
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LiebherrConfigEntry, LiebherrCoordinator
|
||||
from .entity import LiebherrZoneEntity
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class LiebherrNumberEntityDescription(NumberEntityDescription):
|
||||
"""Describes Liebherr number entity."""
|
||||
|
||||
value_fn: Callable[[TemperatureControl], float | None]
|
||||
min_fn: Callable[[TemperatureControl], float | None]
|
||||
max_fn: Callable[[TemperatureControl], float | None]
|
||||
unit_fn: Callable[[TemperatureControl], str]
|
||||
|
||||
|
||||
NUMBER_TYPES: tuple[LiebherrNumberEntityDescription, ...] = (
|
||||
LiebherrNumberEntityDescription(
|
||||
key="setpoint_temperature",
|
||||
translation_key="setpoint_temperature",
|
||||
device_class=NumberDeviceClass.TEMPERATURE,
|
||||
native_step=1,
|
||||
value_fn=lambda control: control.target,
|
||||
min_fn=lambda control: control.min,
|
||||
max_fn=lambda control: control.max,
|
||||
unit_fn=lambda control: (
|
||||
UnitOfTemperature.FAHRENHEIT
|
||||
if control.unit == TemperatureUnit.FAHRENHEIT
|
||||
else UnitOfTemperature.CELSIUS
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: LiebherrConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Liebherr number entities."""
|
||||
coordinators = entry.runtime_data
|
||||
async_add_entities(
|
||||
LiebherrNumber(
|
||||
coordinator=coordinator,
|
||||
zone_id=temp_control.zone_id,
|
||||
description=description,
|
||||
)
|
||||
for coordinator in coordinators.values()
|
||||
for temp_control in coordinator.data.get_temperature_controls().values()
|
||||
for description in NUMBER_TYPES
|
||||
)
|
||||
|
||||
|
||||
class LiebherrNumber(LiebherrZoneEntity, NumberEntity):
|
||||
"""Representation of a Liebherr number entity."""
|
||||
|
||||
entity_description: LiebherrNumberEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: LiebherrCoordinator,
|
||||
zone_id: int,
|
||||
description: LiebherrNumberEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize the number entity."""
|
||||
super().__init__(coordinator, zone_id)
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{coordinator.device_id}_{description.key}_{zone_id}"
|
||||
|
||||
# If device has only one zone, use translation key without zone suffix
|
||||
temp_controls = coordinator.data.get_temperature_controls()
|
||||
if len(temp_controls) > 1 and (zone_key := self._get_zone_translation_key()):
|
||||
self._attr_translation_key = f"{description.translation_key}_{zone_key}"
|
||||
|
||||
@property
|
||||
def native_unit_of_measurement(self) -> str | None:
|
||||
"""Return the unit of measurement."""
|
||||
if (temp_control := self.temperature_control) is None:
|
||||
return None
|
||||
return self.entity_description.unit_fn(temp_control)
|
||||
|
||||
@property
|
||||
def native_value(self) -> float | None:
|
||||
"""Return the current value."""
|
||||
# temperature_control is guaranteed to exist when entity is available
|
||||
return self.entity_description.value_fn(
|
||||
self.temperature_control # type: ignore[arg-type]
|
||||
)
|
||||
|
||||
@property
|
||||
def native_min_value(self) -> float:
|
||||
"""Return the minimum value."""
|
||||
if (temp_control := self.temperature_control) is None:
|
||||
return DEFAULT_MIN_VALUE
|
||||
if (min_val := self.entity_description.min_fn(temp_control)) is None:
|
||||
return DEFAULT_MIN_VALUE
|
||||
return min_val
|
||||
|
||||
@property
|
||||
def native_max_value(self) -> float:
|
||||
"""Return the maximum value."""
|
||||
if (temp_control := self.temperature_control) is None:
|
||||
return DEFAULT_MAX_VALUE
|
||||
if (max_val := self.entity_description.max_fn(temp_control)) is None:
|
||||
return DEFAULT_MAX_VALUE
|
||||
return max_val
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return if entity is available."""
|
||||
return super().available and self.temperature_control is not None
|
||||
|
||||
async def async_set_native_value(self, value: float) -> None:
|
||||
"""Set new value."""
|
||||
# temperature_control is guaranteed to exist when entity is available
|
||||
temp_control = self.temperature_control
|
||||
|
||||
unit = (
|
||||
TemperatureUnit.FAHRENHEIT
|
||||
if temp_control.unit == TemperatureUnit.FAHRENHEIT # type: ignore[union-attr]
|
||||
else TemperatureUnit.CELSIUS
|
||||
)
|
||||
|
||||
try:
|
||||
await self.coordinator.client.set_temperature(
|
||||
device_id=self.coordinator.device_id,
|
||||
zone_id=self._zone_id,
|
||||
target=int(value),
|
||||
unit=unit,
|
||||
)
|
||||
except (LiebherrConnectionError, LiebherrTimeoutError) as err:
|
||||
raise HomeAssistantError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="set_temperature_failed",
|
||||
) from err
|
||||
|
||||
await self.coordinator.async_request_refresh()
|
||||
@@ -55,16 +55,23 @@ async def async_setup_entry(
|
||||
) -> None:
|
||||
"""Set up Liebherr sensor entities."""
|
||||
coordinators = entry.runtime_data
|
||||
async_add_entities(
|
||||
LiebherrSensor(
|
||||
coordinator=coordinator,
|
||||
zone_id=temp_control.zone_id,
|
||||
description=description,
|
||||
)
|
||||
for coordinator in coordinators.values()
|
||||
for temp_control in coordinator.data.get_temperature_controls().values()
|
||||
for description in SENSOR_TYPES
|
||||
)
|
||||
entities: list[LiebherrSensor] = []
|
||||
|
||||
for coordinator in coordinators.values():
|
||||
# Get all temperature controls for this device
|
||||
temp_controls = coordinator.data.get_temperature_controls()
|
||||
|
||||
for temp_control in temp_controls.values():
|
||||
entities.extend(
|
||||
LiebherrSensor(
|
||||
coordinator=coordinator,
|
||||
zone_id=temp_control.zone_id,
|
||||
description=description,
|
||||
)
|
||||
for description in SENSOR_TYPES
|
||||
)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class LiebherrSensor(LiebherrZoneEntity, SensorEntity):
|
||||
@@ -101,9 +108,9 @@ class LiebherrSensor(LiebherrZoneEntity, SensorEntity):
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return the current value."""
|
||||
# temperature_control is guaranteed to exist when entity is available
|
||||
assert self.temperature_control is not None
|
||||
return self.entity_description.value_fn(self.temperature_control)
|
||||
if (temp_control := self.temperature_control) is None:
|
||||
return None
|
||||
return self.entity_description.value_fn(temp_control)
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
|
||||
@@ -33,20 +33,6 @@
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"number": {
|
||||
"setpoint_temperature": {
|
||||
"name": "Setpoint"
|
||||
},
|
||||
"setpoint_temperature_bottom_zone": {
|
||||
"name": "Bottom zone setpoint"
|
||||
},
|
||||
"setpoint_temperature_middle_zone": {
|
||||
"name": "Middle zone setpoint"
|
||||
},
|
||||
"setpoint_temperature_top_zone": {
|
||||
"name": "Top zone setpoint"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"bottom_zone": {
|
||||
"name": "Bottom zone"
|
||||
@@ -58,10 +44,5 @@
|
||||
"name": "Top zone"
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
"set_temperature_failed": {
|
||||
"message": "Failed to set temperature"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,15 +6,11 @@ import itertools
|
||||
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.device_registry import DeviceEntry
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LitterRobotConfigEntry, LitterRobotDataUpdateCoordinator
|
||||
from .services import async_setup_services
|
||||
|
||||
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||
PLATFORMS = [
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.BUTTON,
|
||||
@@ -27,12 +23,6 @@ PLATFORMS = [
|
||||
]
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the component."""
|
||||
async_setup_services(hass)
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: LitterRobotConfigEntry) -> bool:
|
||||
"""Set up Litter-Robot from a config entry."""
|
||||
coordinator = LitterRobotDataUpdateCoordinator(hass, entry)
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
"""Litter-Robot services."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.vacuum import DOMAIN as VACUUM_DOMAIN
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import config_validation as cv, service
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
SERVICE_SET_SLEEP_MODE = "set_sleep_mode"
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_services(hass: HomeAssistant) -> None:
|
||||
"""Set up services."""
|
||||
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
SERVICE_SET_SLEEP_MODE,
|
||||
entity_domain=VACUUM_DOMAIN,
|
||||
schema={
|
||||
vol.Required("enabled"): cv.boolean,
|
||||
vol.Optional("start_time"): cv.time,
|
||||
},
|
||||
func="async_set_sleep_mode",
|
||||
)
|
||||
@@ -7,6 +7,7 @@ from typing import Any
|
||||
|
||||
from pylitterbot import LitterRobot
|
||||
from pylitterbot.enums import LitterBoxStatus
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.vacuum import (
|
||||
StateVacuumEntity,
|
||||
@@ -15,12 +16,15 @@ from homeassistant.components.vacuum import (
|
||||
VacuumEntityFeature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import config_validation as cv, entity_platform
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.util import dt as dt_util
|
||||
|
||||
from .coordinator import LitterRobotConfigEntry
|
||||
from .entity import LitterRobotEntity
|
||||
|
||||
SERVICE_SET_SLEEP_MODE = "set_sleep_mode"
|
||||
|
||||
LITTER_BOX_STATUS_STATE_MAP = {
|
||||
LitterBoxStatus.CLEAN_CYCLE: VacuumActivity.CLEANING,
|
||||
LitterBoxStatus.EMPTY_CYCLE: VacuumActivity.CLEANING,
|
||||
@@ -53,6 +57,16 @@ async def async_setup_entry(
|
||||
for robot in coordinator.litter_robots()
|
||||
)
|
||||
|
||||
platform = entity_platform.async_get_current_platform()
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_SET_SLEEP_MODE,
|
||||
{
|
||||
vol.Required("enabled"): cv.boolean,
|
||||
vol.Optional("start_time"): cv.time,
|
||||
},
|
||||
"async_set_sleep_mode",
|
||||
)
|
||||
|
||||
|
||||
class LitterRobotCleaner(LitterRobotEntity[LitterRobot], StateVacuumEntity):
|
||||
"""Litter-Robot "Vacuum" Cleaner."""
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from speak2mary import MaryTTS
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -11,7 +9,6 @@ from homeassistant.components.tts import (
|
||||
CONF_LANG,
|
||||
PLATFORM_SCHEMA as TTS_PLATFORM_SCHEMA,
|
||||
Provider,
|
||||
TtsAudioType,
|
||||
)
|
||||
from homeassistant.const import CONF_EFFECT, CONF_HOST, CONF_PORT
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
@@ -69,28 +66,26 @@ class MaryTTSProvider(Provider):
|
||||
self.name = "MaryTTS"
|
||||
|
||||
@property
|
||||
def default_language(self) -> str:
|
||||
def default_language(self):
|
||||
"""Return the default language."""
|
||||
return self._mary.locale
|
||||
|
||||
@property
|
||||
def supported_languages(self) -> list[str]:
|
||||
def supported_languages(self):
|
||||
"""Return list of supported languages."""
|
||||
return SUPPORT_LANGUAGES
|
||||
|
||||
@property
|
||||
def default_options(self) -> dict[str, Any]:
|
||||
def default_options(self):
|
||||
"""Return dict include default options."""
|
||||
return {CONF_EFFECT: self._effects}
|
||||
|
||||
@property
|
||||
def supported_options(self) -> list[str]:
|
||||
def supported_options(self):
|
||||
"""Return a list of supported options."""
|
||||
return SUPPORT_OPTIONS
|
||||
|
||||
def get_tts_audio(
|
||||
self, message: str, language: str, options: dict[str, Any]
|
||||
) -> TtsAudioType:
|
||||
def get_tts_audio(self, message, language, options):
|
||||
"""Load TTS from MaryTTS."""
|
||||
effects = options[CONF_EFFECT]
|
||||
|
||||
|
||||
@@ -1014,6 +1014,7 @@ DISCOVERY_SCHEMAS = [
|
||||
device_to_ha=lambda x: x.energy,
|
||||
),
|
||||
entity_class=MatterSensor,
|
||||
allow_none_value=True,
|
||||
required_attributes=(
|
||||
clusters.ElectricalEnergyMeasurement.Attributes.CumulativeEnergyImported,
|
||||
),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""Support for the Microsoft Cognitive Services text-to-speech service."""
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from pycsspeechtts import pycsspeechtts
|
||||
from requests.exceptions import HTTPError
|
||||
@@ -11,7 +10,6 @@ from homeassistant.components.tts import (
|
||||
CONF_LANG,
|
||||
PLATFORM_SCHEMA as TTS_PLATFORM_SCHEMA,
|
||||
Provider,
|
||||
TtsAudioType,
|
||||
)
|
||||
from homeassistant.const import CONF_API_KEY, CONF_REGION, CONF_TYPE, PERCENTAGE
|
||||
from homeassistant.generated.microsoft_tts import SUPPORTED_LANGUAGES
|
||||
@@ -91,28 +89,26 @@ class MicrosoftProvider(Provider):
|
||||
self.name = "Microsoft"
|
||||
|
||||
@property
|
||||
def default_language(self) -> str:
|
||||
def default_language(self):
|
||||
"""Return the default language."""
|
||||
return self._lang
|
||||
|
||||
@property
|
||||
def supported_languages(self) -> list[str]:
|
||||
def supported_languages(self):
|
||||
"""Return list of supported languages."""
|
||||
return list(SUPPORTED_LANGUAGES)
|
||||
return SUPPORTED_LANGUAGES
|
||||
|
||||
@property
|
||||
def supported_options(self) -> list[str]:
|
||||
def supported_options(self):
|
||||
"""Return list of supported options like voice, emotion."""
|
||||
return [CONF_GENDER, CONF_TYPE]
|
||||
|
||||
@property
|
||||
def default_options(self) -> dict[str, Any]:
|
||||
def default_options(self):
|
||||
"""Return a dict include default options."""
|
||||
return {CONF_GENDER: self._gender, CONF_TYPE: self._type}
|
||||
|
||||
def get_tts_audio(
|
||||
self, message: str, language: str, options: dict[str, Any]
|
||||
) -> TtsAudioType:
|
||||
def get_tts_audio(self, message, language, options):
|
||||
"""Load TTS from Microsoft."""
|
||||
if language is None:
|
||||
language = self._lang
|
||||
|
||||
@@ -5,7 +5,6 @@ import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
from typing import Any
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
@@ -13,7 +12,6 @@ from homeassistant.components.tts import (
|
||||
CONF_LANG,
|
||||
PLATFORM_SCHEMA as TTS_PLATFORM_SCHEMA,
|
||||
Provider,
|
||||
TtsAudioType,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -44,18 +42,16 @@ class PicoProvider(Provider):
|
||||
self.name = "PicoTTS"
|
||||
|
||||
@property
|
||||
def default_language(self) -> str:
|
||||
def default_language(self):
|
||||
"""Return the default language."""
|
||||
return self._lang
|
||||
|
||||
@property
|
||||
def supported_languages(self) -> list[str]:
|
||||
def supported_languages(self):
|
||||
"""Return list of supported languages."""
|
||||
return SUPPORT_LANGUAGES
|
||||
|
||||
def get_tts_audio(
|
||||
self, message: str, language: str, options: dict[str, Any]
|
||||
) -> TtsAudioType:
|
||||
def get_tts_audio(self, message, language, options):
|
||||
"""Load TTS using pico2wave."""
|
||||
with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmpf:
|
||||
fname = tmpf.name
|
||||
|
||||
@@ -18,26 +18,19 @@ from homeassistant.const import (
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers import (
|
||||
config_validation as cv,
|
||||
device_registry as dr,
|
||||
entity_registry as er,
|
||||
)
|
||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||
from homeassistant.helpers.device_registry import format_mac
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import CONF_SERIAL_NUMBER, DOMAIN
|
||||
from .const import CONF_SERIAL_NUMBER
|
||||
from .coordinator import (
|
||||
RainbirdScheduleUpdateCoordinator,
|
||||
RainbirdUpdateCoordinator,
|
||||
async_create_clientsession,
|
||||
)
|
||||
from .services import async_setup_services
|
||||
from .types import RainbirdConfigEntry, RainbirdData
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||
PLATFORMS = [
|
||||
Platform.BINARY_SENSOR,
|
||||
Platform.CALENDAR,
|
||||
@@ -47,6 +40,9 @@ PLATFORMS = [
|
||||
]
|
||||
|
||||
|
||||
DOMAIN = "rainbird"
|
||||
|
||||
|
||||
def _async_register_clientsession_shutdown(
|
||||
hass: HomeAssistant,
|
||||
entry: RainbirdConfigEntry,
|
||||
@@ -65,12 +61,6 @@ def _async_register_clientsession_shutdown(
|
||||
entry.async_on_unload(_async_close_websession)
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the component."""
|
||||
async_setup_services(hass)
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: RainbirdConfigEntry) -> bool:
|
||||
"""Set up the config entry for Rain Bird."""
|
||||
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
"""Rain Bird Irrigation system services."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.helpers import config_validation as cv, service
|
||||
from homeassistant.helpers.typing import VolDictType
|
||||
|
||||
from .const import ATTR_DURATION, DOMAIN
|
||||
|
||||
SERVICE_START_IRRIGATION = "start_irrigation"
|
||||
|
||||
SERVICE_SCHEMA_IRRIGATION: VolDictType = {
|
||||
vol.Required(ATTR_DURATION): cv.positive_float,
|
||||
}
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_services(hass: HomeAssistant) -> None:
|
||||
"""Set up services."""
|
||||
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
SERVICE_START_IRRIGATION,
|
||||
entity_domain=SWITCH_DOMAIN,
|
||||
schema=SERVICE_SCHEMA_IRRIGATION,
|
||||
func="async_turn_on",
|
||||
)
|
||||
@@ -6,12 +6,15 @@ import logging
|
||||
from typing import Any
|
||||
|
||||
from pyrainbird.exceptions import RainbirdApiException, RainbirdDeviceBusyException
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.switch import SwitchEntity
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv, entity_platform
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.typing import VolDictType
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import ATTR_DURATION, CONF_IMPORTED_NAMES, DOMAIN, MANUFACTURER
|
||||
@@ -20,6 +23,12 @@ from .types import RainbirdConfigEntry
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
SERVICE_START_IRRIGATION = "start_irrigation"
|
||||
|
||||
SERVICE_SCHEMA_IRRIGATION: VolDictType = {
|
||||
vol.Required(ATTR_DURATION): cv.positive_float,
|
||||
}
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
@@ -38,6 +47,13 @@ async def async_setup_entry(
|
||||
for zone in coordinator.data.zones
|
||||
)
|
||||
|
||||
platform = entity_platform.async_get_current_platform()
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_START_IRRIGATION,
|
||||
SERVICE_SCHEMA_IRRIGATION,
|
||||
"async_turn_on",
|
||||
)
|
||||
|
||||
|
||||
class RainBirdSwitch(CoordinatorEntity[RainbirdUpdateCoordinator], SwitchEntity):
|
||||
"""Representation of a Rain Bird switch."""
|
||||
|
||||
@@ -7,17 +7,22 @@ from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from reolink_aio.api import GuardEnum, Host, PtzEnum
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.button import (
|
||||
ButtonDeviceClass,
|
||||
ButtonEntity,
|
||||
ButtonEntityDescription,
|
||||
)
|
||||
from homeassistant.components.camera import CameraEntityFeature
|
||||
from homeassistant.const import EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.entity_platform import (
|
||||
AddConfigEntryEntitiesCallback,
|
||||
async_get_current_platform,
|
||||
)
|
||||
|
||||
from .const import SUPPORT_PTZ_SPEED
|
||||
from .entity import (
|
||||
ReolinkChannelCoordinatorEntity,
|
||||
ReolinkChannelEntityDescription,
|
||||
@@ -27,6 +32,9 @@ from .entity import (
|
||||
from .util import ReolinkConfigEntry, ReolinkData, raise_translated_error
|
||||
|
||||
PARALLEL_UPDATES = 0
|
||||
ATTR_SPEED = "speed"
|
||||
SUPPORT_PTZ_SPEED = CameraEntityFeature.STREAM
|
||||
SERVICE_PTZ_MOVE = "ptz_move"
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
@@ -170,6 +178,14 @@ async def async_setup_entry(
|
||||
)
|
||||
async_add_entities(entities)
|
||||
|
||||
platform = async_get_current_platform()
|
||||
platform.async_register_entity_service(
|
||||
SERVICE_PTZ_MOVE,
|
||||
{vol.Required(ATTR_SPEED): cv.positive_int},
|
||||
"async_ptz_move",
|
||||
[SUPPORT_PTZ_SPEED],
|
||||
)
|
||||
|
||||
|
||||
class ReolinkButtonEntity(ReolinkChannelCoordinatorEntity, ButtonEntity):
|
||||
"""Base button entity class for Reolink IP cameras."""
|
||||
@@ -203,8 +219,9 @@ class ReolinkButtonEntity(ReolinkChannelCoordinatorEntity, ButtonEntity):
|
||||
await self.entity_description.method(self._host.api, self._channel)
|
||||
|
||||
@raise_translated_error
|
||||
async def async_ptz_move(self, *, speed: int) -> None:
|
||||
async def async_ptz_move(self, **kwargs: Any) -> None:
|
||||
"""PTZ move with speed."""
|
||||
speed = kwargs[ATTR_SPEED]
|
||||
await self._host.api.set_ptz_command(
|
||||
self._channel, command=self.entity_description.ptz_cmd, speed=speed
|
||||
)
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
"""Constants for the Reolink Camera integration."""
|
||||
|
||||
from homeassistant.components.camera import CameraEntityFeature
|
||||
|
||||
DOMAIN = "reolink"
|
||||
|
||||
CONF_USE_HTTPS = "use_https"
|
||||
@@ -15,5 +13,3 @@ CONF_FIRMWARE_CHECK_TIME = "firmware_check_time"
|
||||
BATTERY_PASSIVE_WAKE_UPDATE_INTERVAL = 3600 # seconds
|
||||
BATTERY_WAKE_UPDATE_INTERVAL = 6 * BATTERY_PASSIVE_WAKE_UPDATE_INTERVAL
|
||||
BATTERY_ALL_WAKE_UPDATE_INTERVAL = 2 * BATTERY_WAKE_UPDATE_INTERVAL
|
||||
|
||||
SUPPORT_PTZ_SPEED = CameraEntityFeature.STREAM
|
||||
|
||||
@@ -6,24 +6,17 @@ from reolink_aio.api import Chime
|
||||
from reolink_aio.enums import ChimeToneEnum
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import ATTR_DEVICE_ID
|
||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.helpers import (
|
||||
config_validation as cv,
|
||||
device_registry as dr,
|
||||
service,
|
||||
)
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from .const import DOMAIN, SUPPORT_PTZ_SPEED
|
||||
from .const import DOMAIN
|
||||
from .host import ReolinkHost
|
||||
from .util import get_device_uid_and_ch, raise_translated_error
|
||||
|
||||
ATTR_RINGTONE = "ringtone"
|
||||
ATTR_SPEED = "speed"
|
||||
SERVICE_PTZ_MOVE = "ptz_move"
|
||||
|
||||
|
||||
@raise_translated_error
|
||||
@@ -83,12 +76,3 @@ def async_setup_services(hass: HomeAssistant) -> None:
|
||||
}
|
||||
),
|
||||
)
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
SERVICE_PTZ_MOVE,
|
||||
entity_domain=BUTTON_DOMAIN,
|
||||
schema={vol.Required(ATTR_SPEED): cv.positive_int},
|
||||
func="async_ptz_move",
|
||||
required_features=[SUPPORT_PTZ_SPEED],
|
||||
)
|
||||
|
||||
@@ -23,9 +23,8 @@ from roborock.mqtt.session import MqttSessionUnauthorized
|
||||
from homeassistant.const import CONF_USERNAME, EVENT_HOMEASSISTANT_STOP
|
||||
from homeassistant.core import Event, HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers import config_validation as cv, device_registry as dr
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import (
|
||||
CONF_BASE_URL,
|
||||
@@ -48,20 +47,12 @@ from .coordinator import (
|
||||
RoborockWetDryVacUpdateCoordinator,
|
||||
)
|
||||
from .roborock_storage import CacheStore, async_cleanup_map_storage
|
||||
from .services import async_setup_services
|
||||
|
||||
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||
SCAN_INTERVAL = timedelta(seconds=30)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the component."""
|
||||
async_setup_services(hass)
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: RoborockConfigEntry) -> bool:
|
||||
"""Set up roborock from a config entry."""
|
||||
await async_cleanup_map_storage(hass, entry.entry_id)
|
||||
|
||||
@@ -52,10 +52,12 @@ IMAGE_CACHE_INTERVAL = timedelta(seconds=30)
|
||||
|
||||
MAP_SLEEP = 3
|
||||
|
||||
|
||||
GET_MAPS_SERVICE_NAME = "get_maps"
|
||||
MAP_SCALE = 4
|
||||
MAP_FILE_FORMAT = "PNG"
|
||||
MAP_FILENAME_SUFFIX = ".png"
|
||||
SET_VACUUM_GOTO_POSITION_SERVICE_NAME = "set_vacuum_goto_position"
|
||||
GET_VACUUM_CURRENT_POSITION_SERVICE_NAME = "get_vacuum_current_position"
|
||||
|
||||
|
||||
A01_UPDATE_INTERVAL = timedelta(minutes=1)
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
"""Roborock services."""
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.vacuum import DOMAIN as VACUUM_DOMAIN
|
||||
from homeassistant.core import HomeAssistant, SupportsResponse, callback
|
||||
from homeassistant.helpers import config_validation as cv, service
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
GET_MAPS_SERVICE_NAME = "get_maps"
|
||||
SET_VACUUM_GOTO_POSITION_SERVICE_NAME = "set_vacuum_goto_position"
|
||||
GET_VACUUM_CURRENT_POSITION_SERVICE_NAME = "get_vacuum_current_position"
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_services(hass: HomeAssistant) -> None:
|
||||
"""Set up services."""
|
||||
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
GET_MAPS_SERVICE_NAME,
|
||||
entity_domain=VACUUM_DOMAIN,
|
||||
schema=None,
|
||||
func="get_maps",
|
||||
supports_response=SupportsResponse.ONLY,
|
||||
)
|
||||
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
GET_VACUUM_CURRENT_POSITION_SERVICE_NAME,
|
||||
entity_domain=VACUUM_DOMAIN,
|
||||
schema=None,
|
||||
func="get_vacuum_current_position",
|
||||
supports_response=SupportsResponse.ONLY,
|
||||
)
|
||||
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
SET_VACUUM_GOTO_POSITION_SERVICE_NAME,
|
||||
entity_domain=VACUUM_DOMAIN,
|
||||
schema=cv.make_entity_service_schema(
|
||||
{
|
||||
vol.Required("x"): vol.Coerce(int),
|
||||
vol.Required("y"): vol.Coerce(int),
|
||||
},
|
||||
),
|
||||
func="async_set_vacuum_goto_position",
|
||||
supports_response=SupportsResponse.NONE,
|
||||
)
|
||||
@@ -6,17 +6,24 @@ from typing import Any
|
||||
from roborock.data import RoborockStateCode, SCWindMapping, WorkStatusMapping
|
||||
from roborock.exceptions import RoborockException
|
||||
from roborock.roborock_typing import RoborockCommand
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.vacuum import (
|
||||
StateVacuumEntity,
|
||||
VacuumActivity,
|
||||
VacuumEntityFeature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, ServiceResponse
|
||||
from homeassistant.core import HomeAssistant, ServiceResponse, SupportsResponse
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv, entity_platform
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from .const import DOMAIN
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
GET_MAPS_SERVICE_NAME,
|
||||
GET_VACUUM_CURRENT_POSITION_SERVICE_NAME,
|
||||
SET_VACUUM_GOTO_POSITION_SERVICE_NAME,
|
||||
)
|
||||
from .coordinator import (
|
||||
RoborockB01Q7UpdateCoordinator,
|
||||
RoborockConfigEntry,
|
||||
@@ -85,6 +92,33 @@ async def async_setup_entry(
|
||||
for coordinator in config_entry.runtime_data.b01
|
||||
if isinstance(coordinator, RoborockB01Q7UpdateCoordinator)
|
||||
)
|
||||
platform = entity_platform.async_get_current_platform()
|
||||
|
||||
platform.async_register_entity_service(
|
||||
GET_MAPS_SERVICE_NAME,
|
||||
None,
|
||||
RoborockVacuum.get_maps.__name__,
|
||||
supports_response=SupportsResponse.ONLY,
|
||||
)
|
||||
|
||||
platform.async_register_entity_service(
|
||||
GET_VACUUM_CURRENT_POSITION_SERVICE_NAME,
|
||||
None,
|
||||
RoborockVacuum.get_vacuum_current_position.__name__,
|
||||
supports_response=SupportsResponse.ONLY,
|
||||
)
|
||||
|
||||
platform.async_register_entity_service(
|
||||
SET_VACUUM_GOTO_POSITION_SERVICE_NAME,
|
||||
cv.make_entity_service_schema(
|
||||
{
|
||||
vol.Required("x"): vol.Coerce(int),
|
||||
vol.Required("y"): vol.Coerce(int),
|
||||
},
|
||||
),
|
||||
RoborockVacuum.async_set_vacuum_goto_position.__name__,
|
||||
supports_response=SupportsResponse.NONE,
|
||||
)
|
||||
|
||||
|
||||
class RoborockVacuum(RoborockCoordinatedEntityV1, StateVacuumEntity):
|
||||
|
||||
@@ -14,7 +14,7 @@ from homeassistant.components.sensor import (
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import UnitOfTemperature
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
@@ -81,12 +81,6 @@ class SENZSensor(CoordinatorEntity[SENZDataUpdateCoordinator], SensorEntity):
|
||||
serial_number=thermostat.serial_number,
|
||||
)
|
||||
|
||||
@callback
|
||||
def _handle_coordinator_update(self) -> None:
|
||||
"""Handle updated data from the coordinator."""
|
||||
self._thermostat = self.coordinator.data[self._thermostat.serial_number]
|
||||
self.async_write_ha_state()
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return True if the thermostat is available."""
|
||||
|
||||
@@ -41,7 +41,7 @@ from .coordinator import (
|
||||
TeslemetryEnergySiteLiveCoordinator,
|
||||
TeslemetryVehicleDataCoordinator,
|
||||
)
|
||||
from .helpers import async_update_device_sw_version, flatten
|
||||
from .helpers import flatten
|
||||
from .models import TeslemetryData, TeslemetryEnergyData, TeslemetryVehicleData
|
||||
from .services import async_setup_services
|
||||
|
||||
@@ -161,16 +161,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: TeslemetryConfigEntry) -
|
||||
coordinator = TeslemetryVehicleDataCoordinator(
|
||||
hass, entry, vehicle, product
|
||||
)
|
||||
firmware = vehicle_metadata[vin].get("firmware")
|
||||
device = DeviceInfo(
|
||||
identifiers={(DOMAIN, vin)},
|
||||
manufacturer="Tesla",
|
||||
configuration_url="https://teslemetry.com/console",
|
||||
name=product["display_name"],
|
||||
model=vehicle.model,
|
||||
model_id=vin[3],
|
||||
serial_number=vin,
|
||||
sw_version=firmware,
|
||||
)
|
||||
current_devices.add((DOMAIN, vin))
|
||||
|
||||
@@ -188,6 +185,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: TeslemetryConfigEntry) -
|
||||
create_handle_vehicle_stream(vin, coordinator),
|
||||
{"vin": vin},
|
||||
)
|
||||
firmware = vehicle_metadata[vin].get("firmware", "Unknown")
|
||||
stream_vehicle = stream.get_vehicle(vin)
|
||||
poll = vehicle_metadata[vin].get("polling", False)
|
||||
|
||||
@@ -278,20 +276,23 @@ async def async_setup_entry(hass: HomeAssistant, entry: TeslemetryConfigEntry) -
|
||||
),
|
||||
)
|
||||
|
||||
# Register listeners for polling vehicle sw_version updates
|
||||
for vehicle_data in vehicles:
|
||||
if vehicle_data.poll:
|
||||
entry.async_on_unload(
|
||||
vehicle_data.coordinator.async_add_listener(
|
||||
create_vehicle_polling_listener(
|
||||
hass, vehicle_data.vin, vehicle_data.coordinator
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# Setup energy devices with models, versions, and listeners
|
||||
# Add energy device models
|
||||
for energysite in energysites:
|
||||
async_setup_energy_device(hass, entry, energysite, device_registry)
|
||||
models = set()
|
||||
for gateway in energysite.info_coordinator.data.get("components_gateways", []):
|
||||
if gateway.get("part_name"):
|
||||
models.add(gateway["part_name"])
|
||||
for battery in energysite.info_coordinator.data.get("components_batteries", []):
|
||||
if battery.get("part_name"):
|
||||
models.add(battery["part_name"])
|
||||
if models:
|
||||
energysite.device["model"] = ", ".join(sorted(models))
|
||||
|
||||
# Create the energy site device regardless of it having entities
|
||||
# This is so users with a Wall Connector but without a Powerwall can still make service calls
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id, **energysite.device
|
||||
)
|
||||
|
||||
# Remove devices that are no longer present
|
||||
for device_entry in dr.async_entries_for_config_entry(
|
||||
@@ -368,40 +369,6 @@ def create_handle_vehicle_stream(vin: str, coordinator) -> Callable[[dict], None
|
||||
return handle_vehicle_stream
|
||||
|
||||
|
||||
def async_setup_energy_device(
|
||||
hass: HomeAssistant,
|
||||
entry: TeslemetryConfigEntry,
|
||||
energysite: TeslemetryEnergyData,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
) -> None:
|
||||
"""Set up energy device with models, versions, and listeners."""
|
||||
data = energysite.info_coordinator.data
|
||||
models = set()
|
||||
for component in (
|
||||
*data.get("components_gateways", []),
|
||||
*data.get("components_batteries", []),
|
||||
):
|
||||
if part_name := component.get("part_name"):
|
||||
models.add(part_name)
|
||||
if models:
|
||||
energysite.device["model"] = ", ".join(sorted(models))
|
||||
|
||||
if version := data.get("version"):
|
||||
energysite.device["sw_version"] = version
|
||||
|
||||
device_registry.async_get_or_create(
|
||||
config_entry_id=entry.entry_id, **energysite.device
|
||||
)
|
||||
|
||||
entry.async_on_unload(
|
||||
energysite.info_coordinator.async_add_listener(
|
||||
create_energy_info_listener(
|
||||
hass, energysite.id, energysite.info_coordinator
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_stream(
|
||||
hass: HomeAssistant, entry: TeslemetryConfigEntry, vehicle: TeslemetryVehicleData
|
||||
):
|
||||
@@ -413,54 +380,3 @@ async def async_setup_stream(
|
||||
vehicle.stream_vehicle.prefer_typed(True),
|
||||
f"Prefer typed for {vehicle.vin}",
|
||||
)
|
||||
|
||||
entry.async_on_unload(
|
||||
vehicle.stream_vehicle.listen_Version(
|
||||
create_vehicle_streaming_listener(hass, vehicle.vin)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def create_vehicle_streaming_listener(
|
||||
hass: HomeAssistant, vin: str
|
||||
) -> Callable[[str | None], None]:
|
||||
"""Create a listener for vehicle streaming version updates."""
|
||||
|
||||
def handle_version(value: str | None) -> None:
|
||||
"""Handle version update from stream."""
|
||||
if value is not None:
|
||||
# Remove build from version (e.g., "2024.44.25 abc123" -> "2024.44.25")
|
||||
sw_version = value.split(" ")[0]
|
||||
async_update_device_sw_version(hass, vin, sw_version)
|
||||
|
||||
return handle_version
|
||||
|
||||
|
||||
def create_vehicle_polling_listener(
|
||||
hass: HomeAssistant, vin: str, coordinator: TeslemetryVehicleDataCoordinator
|
||||
) -> Callable[[], None]:
|
||||
"""Create a listener for vehicle polling coordinator updates."""
|
||||
|
||||
def handle_update() -> None:
|
||||
"""Handle coordinator update."""
|
||||
if version := coordinator.data.get("vehicle_state_car_version"):
|
||||
# Remove build from version (e.g., "2024.44.25 abc123" -> "2024.44.25")
|
||||
sw_version = version.split(" ")[0]
|
||||
async_update_device_sw_version(hass, vin, sw_version)
|
||||
|
||||
return handle_update
|
||||
|
||||
|
||||
def create_energy_info_listener(
|
||||
hass: HomeAssistant,
|
||||
site_id: int,
|
||||
coordinator: TeslemetryEnergySiteInfoCoordinator,
|
||||
) -> Callable[[], None]:
|
||||
"""Create a listener for energy site info coordinator updates."""
|
||||
|
||||
def handle_update() -> None:
|
||||
"""Handle coordinator update."""
|
||||
if version := coordinator.data.get("version"):
|
||||
async_update_device_sw_version(hass, str(site_id), version)
|
||||
|
||||
return handle_update
|
||||
|
||||
@@ -4,9 +4,7 @@ from typing import Any
|
||||
|
||||
from tesla_fleet_api.exceptions import TeslaFleetError
|
||||
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
|
||||
from .const import DOMAIN, LOGGER
|
||||
|
||||
@@ -70,14 +68,3 @@ async def handle_vehicle_command(command) -> Any:
|
||||
)
|
||||
# Response with result of true
|
||||
return result
|
||||
|
||||
|
||||
@callback
|
||||
def async_update_device_sw_version(
|
||||
hass: HomeAssistant, identifier: str, sw_version: str
|
||||
) -> None:
|
||||
"""Update the software version in the device registry."""
|
||||
dev_reg = dr.async_get(hass)
|
||||
if device := dev_reg.async_get_device(identifiers={(DOMAIN, identifier)}):
|
||||
if device.sw_version != sw_version:
|
||||
dev_reg.async_update_device(device.id, sw_version=sw_version)
|
||||
|
||||
@@ -46,7 +46,11 @@ rules:
|
||||
test fixture. Clarify _alt and _noscope fixture purposes. Test error messages in
|
||||
test_service_validation_errors.
|
||||
# Gold
|
||||
devices: done
|
||||
devices:
|
||||
status: todo
|
||||
comment: |
|
||||
Add model id to device info. VIN sensor may be redundant (already serial number in device).
|
||||
Version sensor should be sw_version in device info instead.
|
||||
diagnostics: done
|
||||
discovery:
|
||||
status: exempt
|
||||
|
||||
@@ -1532,6 +1532,10 @@ ENERGY_INFO_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = (
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
),
|
||||
SensorEntityDescription(
|
||||
key="version",
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
),
|
||||
)
|
||||
|
||||
ENERGY_HISTORY_DESCRIPTIONS: tuple[SensorEntityDescription, ...] = tuple(
|
||||
|
||||
@@ -1032,6 +1032,9 @@
|
||||
"vehicle_state_tpms_pressure_rr": {
|
||||
"name": "Tire pressure rear right"
|
||||
},
|
||||
"version": {
|
||||
"name": "Version"
|
||||
},
|
||||
"vin": {
|
||||
"name": "[%key:component::teslemetry::common::vehicle%]",
|
||||
"state": {
|
||||
|
||||
@@ -132,7 +132,7 @@ class TraccarEntity(TrackerEntity, RestoreEntity):
|
||||
)
|
||||
|
||||
@property
|
||||
def battery_level(self) -> int | None:
|
||||
def battery_level(self):
|
||||
"""Return battery value of the device."""
|
||||
return self._battery
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import asyncio
|
||||
from http import HTTPStatus
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import aiohttp
|
||||
import voluptuous as vol
|
||||
@@ -12,7 +11,6 @@ from homeassistant.components.tts import (
|
||||
CONF_LANG,
|
||||
PLATFORM_SCHEMA as TTS_PLATFORM_SCHEMA,
|
||||
Provider,
|
||||
TtsAudioType,
|
||||
)
|
||||
from homeassistant.const import CONF_API_KEY
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
@@ -184,21 +182,17 @@ class VoiceRSSProvider(Provider):
|
||||
}
|
||||
|
||||
@property
|
||||
def default_language(self) -> str:
|
||||
def default_language(self):
|
||||
"""Return the default language."""
|
||||
return self._lang
|
||||
|
||||
@property
|
||||
def supported_languages(self) -> list[str]:
|
||||
def supported_languages(self):
|
||||
"""Return list of supported languages."""
|
||||
return SUPPORT_LANGUAGES
|
||||
|
||||
async def async_get_tts_audio(
|
||||
self, message: str, language: str, options: dict[str, Any]
|
||||
) -> TtsAudioType:
|
||||
async def async_get_tts_audio(self, message, language, options):
|
||||
"""Load TTS from VoiceRSS."""
|
||||
if TYPE_CHECKING:
|
||||
assert self.hass
|
||||
websession = async_get_clientsession(self.hass)
|
||||
form_data = self._form_data.copy()
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""Support for IBM Watson TTS integration."""
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from ibm_cloud_sdk_core.authenticators import IAMAuthenticator
|
||||
from ibm_watson import TextToSpeechV1
|
||||
@@ -10,7 +9,6 @@ import voluptuous as vol
|
||||
from homeassistant.components.tts import (
|
||||
PLATFORM_SCHEMA as TTS_PLATFORM_SCHEMA,
|
||||
Provider,
|
||||
TtsAudioType,
|
||||
)
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
@@ -165,28 +163,26 @@ class WatsonTTSProvider(Provider):
|
||||
self.name = "Watson TTS"
|
||||
|
||||
@property
|
||||
def supported_languages(self) -> list[str]:
|
||||
def supported_languages(self):
|
||||
"""Return a list of supported languages."""
|
||||
return self.supported_langs
|
||||
|
||||
@property
|
||||
def default_language(self) -> str:
|
||||
def default_language(self):
|
||||
"""Return the default language."""
|
||||
return self.default_lang
|
||||
|
||||
@property
|
||||
def default_options(self) -> dict[str, Any]:
|
||||
def default_options(self):
|
||||
"""Return dict include default options."""
|
||||
return {CONF_VOICE: self.default_voice}
|
||||
|
||||
@property
|
||||
def supported_options(self) -> list[str]:
|
||||
def supported_options(self):
|
||||
"""Return a list of supported options."""
|
||||
return [CONF_VOICE]
|
||||
|
||||
def get_tts_audio(
|
||||
self, message: str, language: str, options: dict[str, Any]
|
||||
) -> TtsAudioType:
|
||||
def get_tts_audio(self, message, language, options):
|
||||
"""Request TTS file from Watson TTS."""
|
||||
response = self.service.synthesize(
|
||||
text=message, accept=self.output_format, voice=options[CONF_VOICE]
|
||||
|
||||
@@ -23,7 +23,6 @@ from homeassistant.helpers.typing import ConfigType
|
||||
|
||||
from .const import DATA_HASS_CONFIG, DOMAIN, PLATFORMS, WEBOSTV_EXCEPTIONS
|
||||
from .helpers import WebOsTvConfigEntry, update_client_key
|
||||
from .services import async_setup_services
|
||||
|
||||
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||
|
||||
@@ -32,8 +31,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||
"""Set up the LG webOS TV platform."""
|
||||
hass.data.setdefault(DOMAIN, {DATA_HASS_CONFIG: config})
|
||||
|
||||
async_setup_services(hass)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -12,12 +12,17 @@ PLATFORMS = [Platform.MEDIA_PLAYER]
|
||||
DATA_HASS_CONFIG = "hass_config"
|
||||
DEFAULT_NAME = "LG webOS TV"
|
||||
|
||||
ATTR_BUTTON = "button"
|
||||
ATTR_PAYLOAD = "payload"
|
||||
ATTR_SOUND_OUTPUT = "sound_output"
|
||||
|
||||
CONF_ON_ACTION = "turn_on_action"
|
||||
CONF_SOURCES = "sources"
|
||||
|
||||
SERVICE_BUTTON = "button"
|
||||
SERVICE_COMMAND = "command"
|
||||
SERVICE_SELECT_SOUND_OUTPUT = "select_sound_output"
|
||||
|
||||
LIVE_TV_APP_ID = "com.webos.app.livetv"
|
||||
|
||||
WEBOSTV_EXCEPTIONS = (
|
||||
|
||||
@@ -12,6 +12,7 @@ import logging
|
||||
from typing import Any, Concatenate, cast
|
||||
|
||||
from aiowebostv import WebOsTvPairError, WebOsTvState
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import util
|
||||
from homeassistant.components.media_player import (
|
||||
@@ -21,21 +22,27 @@ from homeassistant.components.media_player import (
|
||||
MediaPlayerState,
|
||||
MediaType,
|
||||
)
|
||||
from homeassistant.const import ATTR_SUPPORTED_FEATURES
|
||||
from homeassistant.core import HomeAssistant, ServiceResponse
|
||||
from homeassistant.const import ATTR_COMMAND, ATTR_SUPPORTED_FEATURES
|
||||
from homeassistant.core import HomeAssistant, ServiceResponse, SupportsResponse
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import config_validation as cv, entity_platform
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
from homeassistant.helpers.trigger import PluggableAction
|
||||
from homeassistant.helpers.typing import VolDictType
|
||||
|
||||
from .const import (
|
||||
ATTR_BUTTON,
|
||||
ATTR_PAYLOAD,
|
||||
ATTR_SOUND_OUTPUT,
|
||||
CONF_SOURCES,
|
||||
DOMAIN,
|
||||
LIVE_TV_APP_ID,
|
||||
SERVICE_BUTTON,
|
||||
SERVICE_COMMAND,
|
||||
SERVICE_SELECT_SOUND_OUTPUT,
|
||||
WEBOSTV_EXCEPTIONS,
|
||||
)
|
||||
from .helpers import WebOsTvConfigEntry, update_client_key
|
||||
@@ -63,6 +70,34 @@ MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(seconds=1)
|
||||
PARALLEL_UPDATES = 0
|
||||
SCAN_INTERVAL = timedelta(seconds=10)
|
||||
|
||||
BUTTON_SCHEMA: VolDictType = {vol.Required(ATTR_BUTTON): cv.string}
|
||||
COMMAND_SCHEMA: VolDictType = {
|
||||
vol.Required(ATTR_COMMAND): cv.string,
|
||||
vol.Optional(ATTR_PAYLOAD): dict,
|
||||
}
|
||||
SOUND_OUTPUT_SCHEMA: VolDictType = {vol.Required(ATTR_SOUND_OUTPUT): cv.string}
|
||||
|
||||
SERVICES = (
|
||||
(
|
||||
SERVICE_BUTTON,
|
||||
BUTTON_SCHEMA,
|
||||
"async_button",
|
||||
SupportsResponse.NONE,
|
||||
),
|
||||
(
|
||||
SERVICE_COMMAND,
|
||||
COMMAND_SCHEMA,
|
||||
"async_command",
|
||||
SupportsResponse.OPTIONAL,
|
||||
),
|
||||
(
|
||||
SERVICE_SELECT_SOUND_OUTPUT,
|
||||
SOUND_OUTPUT_SCHEMA,
|
||||
"async_select_sound_output",
|
||||
SupportsResponse.OPTIONAL,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
@@ -70,6 +105,12 @@ async def async_setup_entry(
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the LG webOS TV platform."""
|
||||
platform = entity_platform.async_get_current_platform()
|
||||
|
||||
for service_name, schema, method, supports_response in SERVICES:
|
||||
platform.async_register_entity_service(
|
||||
service_name, schema, method, supports_response=supports_response
|
||||
)
|
||||
|
||||
async_add_entities([LgWebOSMediaPlayerEntity(entry)])
|
||||
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
"""LG webOS TV services."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.media_player import DOMAIN as MEDIA_PLAYER_DOMAIN
|
||||
from homeassistant.const import ATTR_COMMAND
|
||||
from homeassistant.core import HomeAssistant, SupportsResponse, callback
|
||||
from homeassistant.helpers import config_validation as cv, service
|
||||
from homeassistant.helpers.typing import VolDictType
|
||||
|
||||
from .const import ATTR_PAYLOAD, ATTR_SOUND_OUTPUT, DOMAIN
|
||||
|
||||
ATTR_BUTTON = "button"
|
||||
|
||||
SERVICE_BUTTON = "button"
|
||||
SERVICE_COMMAND = "command"
|
||||
SERVICE_SELECT_SOUND_OUTPUT = "select_sound_output"
|
||||
|
||||
BUTTON_SCHEMA: VolDictType = {vol.Required(ATTR_BUTTON): cv.string}
|
||||
COMMAND_SCHEMA: VolDictType = {
|
||||
vol.Required(ATTR_COMMAND): cv.string,
|
||||
vol.Optional(ATTR_PAYLOAD): dict,
|
||||
}
|
||||
SOUND_OUTPUT_SCHEMA: VolDictType = {vol.Required(ATTR_SOUND_OUTPUT): cv.string}
|
||||
|
||||
SERVICES = (
|
||||
(
|
||||
SERVICE_BUTTON,
|
||||
BUTTON_SCHEMA,
|
||||
"async_button",
|
||||
SupportsResponse.NONE,
|
||||
),
|
||||
(
|
||||
SERVICE_COMMAND,
|
||||
COMMAND_SCHEMA,
|
||||
"async_command",
|
||||
SupportsResponse.OPTIONAL,
|
||||
),
|
||||
(
|
||||
SERVICE_SELECT_SOUND_OUTPUT,
|
||||
SOUND_OUTPUT_SCHEMA,
|
||||
"async_select_sound_output",
|
||||
SupportsResponse.OPTIONAL,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@callback
|
||||
def async_setup_services(hass: HomeAssistant) -> None:
|
||||
"""Set up services."""
|
||||
|
||||
for service_name, schema, method, supports_response in SERVICES:
|
||||
service.async_register_platform_entity_service(
|
||||
hass,
|
||||
DOMAIN,
|
||||
service_name,
|
||||
entity_domain=MEDIA_PLAYER_DOMAIN,
|
||||
schema=schema,
|
||||
func=method,
|
||||
supports_response=supports_response,
|
||||
)
|
||||
@@ -17,7 +17,7 @@ from .const import BRANDS_CONF_MAP, CONF_BRAND, DOMAIN, REGIONS_CONF_MAP
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS = [Platform.BINARY_SENSOR, Platform.CLIMATE, Platform.SELECT, Platform.SENSOR]
|
||||
PLATFORMS = [Platform.BINARY_SENSOR, Platform.CLIMATE, Platform.SENSOR]
|
||||
|
||||
type WhirlpoolConfigEntry = ConfigEntry[AppliancesManager]
|
||||
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
"""The select platform for Whirlpool Appliances."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Awaitable, Callable
|
||||
from dataclasses import dataclass
|
||||
import logging
|
||||
from typing import Final, override
|
||||
|
||||
from whirlpool.appliance import Appliance
|
||||
|
||||
from homeassistant.components.select import SelectEntity, SelectEntityDescription
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ServiceValidationError
|
||||
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
|
||||
from . import WhirlpoolConfigEntry
|
||||
from .const import DOMAIN
|
||||
from .entity import WhirlpoolEntity
|
||||
|
||||
PARALLEL_UPDATES = 1
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class WhirlpoolSelectDescription(SelectEntityDescription):
|
||||
"""Class describing Whirlpool select entities."""
|
||||
|
||||
value_fn: Callable[[Appliance], str | None]
|
||||
set_fn: Callable[[Appliance, str], Awaitable[bool]]
|
||||
|
||||
|
||||
REFRIGERATOR_DESCRIPTIONS: Final[tuple[WhirlpoolSelectDescription, ...]] = (
|
||||
WhirlpoolSelectDescription(
|
||||
key="refrigerator_temperature",
|
||||
translation_key="refrigerator_temperature",
|
||||
options=["-4", "-2", "0", "3", "5"],
|
||||
value_fn=lambda fridge: str(val)
|
||||
if (val := fridge.get_offset_temp()) is not None
|
||||
else None,
|
||||
set_fn=lambda fridge, option: fridge.set_offset_temp(int(option)),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: WhirlpoolConfigEntry,
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the select platform."""
|
||||
appliances_manager = config_entry.runtime_data
|
||||
|
||||
async_add_entities(
|
||||
[
|
||||
WhirlpoolSelectEntity(refrigerator, description)
|
||||
for refrigerator in appliances_manager.refrigerators
|
||||
for description in REFRIGERATOR_DESCRIPTIONS
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class WhirlpoolSelectEntity(WhirlpoolEntity, SelectEntity):
|
||||
"""Whirlpool select entity."""
|
||||
|
||||
def __init__(
|
||||
self, appliance: Appliance, description: WhirlpoolSelectDescription
|
||||
) -> None:
|
||||
"""Initialize the select entity."""
|
||||
super().__init__(appliance, unique_id_suffix=f"-{description.key}")
|
||||
self.entity_description: WhirlpoolSelectDescription = description
|
||||
|
||||
@override
|
||||
@property
|
||||
def current_option(self) -> str | None:
|
||||
"""Retrieve currently selected option."""
|
||||
return self.entity_description.value_fn(self._appliance)
|
||||
|
||||
@override
|
||||
async def async_select_option(self, option: str) -> None:
|
||||
"""Set the selected option."""
|
||||
try:
|
||||
WhirlpoolSelectEntity._check_service_request(
|
||||
await self.entity_description.set_fn(self._appliance, option)
|
||||
)
|
||||
except ValueError as err:
|
||||
raise ServiceValidationError(
|
||||
translation_domain=DOMAIN,
|
||||
translation_key="invalid_value_set",
|
||||
) from err
|
||||
@@ -46,11 +46,6 @@
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"select": {
|
||||
"refrigerator_temperature": {
|
||||
"name": "Temperature"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"dryer_state": {
|
||||
"name": "[%key:component::whirlpool::entity::sensor::washer_state::name%]",
|
||||
@@ -216,9 +211,6 @@
|
||||
"appliances_fetch_failed": {
|
||||
"message": "Failed to fetch appliances"
|
||||
},
|
||||
"invalid_value_set": {
|
||||
"message": "Invalid value provided"
|
||||
},
|
||||
"request_failed": {
|
||||
"message": "Request failed"
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
import asyncio
|
||||
from http import HTTPStatus
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import aiohttp
|
||||
import voluptuous as vol
|
||||
@@ -12,7 +11,6 @@ from homeassistant.components.tts import (
|
||||
CONF_LANG,
|
||||
PLATFORM_SCHEMA as TTS_PLATFORM_SCHEMA,
|
||||
Provider,
|
||||
TtsAudioType,
|
||||
)
|
||||
from homeassistant.const import CONF_API_KEY
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
@@ -106,26 +104,22 @@ class YandexSpeechKitProvider(Provider):
|
||||
self.name = "YandexTTS"
|
||||
|
||||
@property
|
||||
def default_language(self) -> str:
|
||||
def default_language(self):
|
||||
"""Return the default language."""
|
||||
return self._language
|
||||
|
||||
@property
|
||||
def supported_languages(self) -> list[str]:
|
||||
def supported_languages(self):
|
||||
"""Return list of supported languages."""
|
||||
return SUPPORT_LANGUAGES
|
||||
|
||||
@property
|
||||
def supported_options(self) -> list[str]:
|
||||
def supported_options(self):
|
||||
"""Return list of supported options."""
|
||||
return SUPPORTED_OPTIONS
|
||||
|
||||
async def async_get_tts_audio(
|
||||
self, message: str, language: str, options: dict[str, Any]
|
||||
) -> TtsAudioType:
|
||||
async def async_get_tts_audio(self, message, language, options):
|
||||
"""Load TTS from yandex."""
|
||||
if TYPE_CHECKING:
|
||||
assert self.hass
|
||||
websession = async_get_clientsession(self.hass)
|
||||
actual_language = language
|
||||
|
||||
|
||||
2
homeassistant/generated/labs.py
generated
2
homeassistant/generated/labs.py
generated
@@ -7,7 +7,7 @@ LABS_PREVIEW_FEATURES = {
|
||||
"analytics": {
|
||||
"snapshots": {
|
||||
"feedback_url": "https://forms.gle/GqvRmgmghSDco8M46",
|
||||
"learn_more_url": "https://www.home-assistant.io/blog/2026/02/02/about-device-database/",
|
||||
"learn_more_url": "",
|
||||
"report_issue_url": "https://github.com/OHF-Device-Database/device-database/issues/new",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -39,7 +39,7 @@ habluetooth==5.8.0
|
||||
hass-nabucasa==1.12.0
|
||||
hassil==3.5.0
|
||||
home-assistant-bluetooth==1.13.1
|
||||
home-assistant-frontend==20260128.4
|
||||
home-assistant-frontend==20260128.3
|
||||
home-assistant-intents==2026.1.28
|
||||
httpx==0.28.1
|
||||
ifaddr==0.2.0
|
||||
|
||||
@@ -1484,7 +1484,6 @@ _INHERITANCE_MATCH: dict[str, list[ClassTypeHintMatch]] = {
|
||||
TypeHintMatch(
|
||||
function_name="battery_level",
|
||||
return_type=["int", None],
|
||||
mandatory=True,
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="source_type",
|
||||
@@ -1509,22 +1508,18 @@ _INHERITANCE_MATCH: dict[str, list[ClassTypeHintMatch]] = {
|
||||
TypeHintMatch(
|
||||
function_name="location_name",
|
||||
return_type=["str", None],
|
||||
mandatory=True,
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="latitude",
|
||||
return_type=["float", None],
|
||||
mandatory=True,
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="longitude",
|
||||
return_type=["float", None],
|
||||
mandatory=True,
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="state",
|
||||
return_type=["str", None],
|
||||
mandatory=True,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -1534,17 +1529,14 @@ _INHERITANCE_MATCH: dict[str, list[ClassTypeHintMatch]] = {
|
||||
TypeHintMatch(
|
||||
function_name="ip_address",
|
||||
return_type=["str", None],
|
||||
mandatory=True,
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="mac_address",
|
||||
return_type=["str", None],
|
||||
mandatory=True,
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="hostname",
|
||||
return_type=["str", None],
|
||||
mandatory=True,
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="state",
|
||||
@@ -2710,29 +2702,24 @@ _INHERITANCE_MATCH: dict[str, list[ClassTypeHintMatch]] = {
|
||||
TypeHintMatch(
|
||||
function_name="default_language",
|
||||
return_type=["str", None],
|
||||
mandatory=True,
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="supported_languages",
|
||||
return_type=["list[str]", None],
|
||||
mandatory=True,
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="supported_options",
|
||||
return_type=["list[str]", None],
|
||||
mandatory=True,
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="default_options",
|
||||
return_type=["Mapping[str, Any]", None],
|
||||
mandatory=True,
|
||||
),
|
||||
TypeHintMatch(
|
||||
function_name="get_tts_audio",
|
||||
arg_types={1: "str", 2: "str", 3: "dict[str, Any]"},
|
||||
return_type="TtsAudioType",
|
||||
has_async_counterpart=True,
|
||||
mandatory=True,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
4
requirements_all.txt
generated
4
requirements_all.txt
generated
@@ -1219,7 +1219,7 @@ hole==0.9.0
|
||||
holidays==0.84
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20260128.4
|
||||
home-assistant-frontend==20260128.3
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2026.1.28
|
||||
@@ -1865,7 +1865,7 @@ pyElectra==1.2.4
|
||||
pyEmby==1.10
|
||||
|
||||
# homeassistant.components.hikvision
|
||||
pyHik==0.4.2
|
||||
pyHik==0.4.1
|
||||
|
||||
# homeassistant.components.homee
|
||||
pyHomee==1.3.8
|
||||
|
||||
4
requirements_test_all.txt
generated
4
requirements_test_all.txt
generated
@@ -1077,7 +1077,7 @@ hole==0.9.0
|
||||
holidays==0.84
|
||||
|
||||
# homeassistant.components.frontend
|
||||
home-assistant-frontend==20260128.4
|
||||
home-assistant-frontend==20260128.3
|
||||
|
||||
# homeassistant.components.conversation
|
||||
home-assistant-intents==2026.1.28
|
||||
@@ -1602,7 +1602,7 @@ pyDuotecno==2024.10.1
|
||||
pyElectra==1.2.4
|
||||
|
||||
# homeassistant.components.hikvision
|
||||
pyHik==0.4.2
|
||||
pyHik==0.4.1
|
||||
|
||||
# homeassistant.components.homee
|
||||
pyHomee==1.3.8
|
||||
|
||||
@@ -6,8 +6,7 @@ from aiohttp import ClientError
|
||||
from blinkpy.auth import LoginError
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.blink.const import DOMAIN
|
||||
from homeassistant.components.blink.services import SERVICE_SAVE_VIDEO
|
||||
from homeassistant.components.blink.const import DOMAIN, SERVICE_SAVE_VIDEO
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
|
||||
@@ -4,8 +4,7 @@ from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.blink.const import DOMAIN
|
||||
from homeassistant.components.blink.services import SERVICE_SEND_PIN
|
||||
from homeassistant.components.blink.const import DOMAIN, SERVICE_SEND_PIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import ATTR_CONFIG_ENTRY_ID, CONF_PIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
@@ -10,9 +10,11 @@ import pytest
|
||||
|
||||
from homeassistant import core
|
||||
from homeassistant.components import fan
|
||||
from homeassistant.components.bond.const import DOMAIN
|
||||
from homeassistant.components.bond.const import (
|
||||
DOMAIN,
|
||||
SERVICE_SET_FAN_SPEED_TRACKED_STATE,
|
||||
)
|
||||
from homeassistant.components.bond.fan import PRESET_MODE_BREEZE
|
||||
from homeassistant.components.bond.services import SERVICE_SET_FAN_SPEED_TRACKED_STATE
|
||||
from homeassistant.components.fan import (
|
||||
ATTR_DIRECTION,
|
||||
ATTR_PERCENTAGE,
|
||||
|
||||
@@ -5,11 +5,13 @@ from datetime import timedelta
|
||||
from bond_async import Action, DeviceType
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.bond.const import DOMAIN
|
||||
from homeassistant.components.bond.services import (
|
||||
from homeassistant.components.bond.const import (
|
||||
ATTR_POWER_STATE,
|
||||
DOMAIN,
|
||||
SERVICE_SET_LIGHT_BRIGHTNESS_TRACKED_STATE,
|
||||
SERVICE_SET_LIGHT_POWER_TRACKED_STATE,
|
||||
)
|
||||
from homeassistant.components.bond.light import (
|
||||
SERVICE_START_DECREASING_BRIGHTNESS,
|
||||
SERVICE_START_INCREASING_BRIGHTNESS,
|
||||
SERVICE_STOP,
|
||||
|
||||
@@ -5,9 +5,9 @@ from datetime import timedelta
|
||||
from bond_async import Action, DeviceType
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.bond.const import DOMAIN
|
||||
from homeassistant.components.bond.services import (
|
||||
from homeassistant.components.bond.const import (
|
||||
ATTR_POWER_STATE,
|
||||
DOMAIN,
|
||||
SERVICE_SET_POWER_TRACKED_STATE,
|
||||
)
|
||||
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
||||
|
||||
@@ -6,8 +6,7 @@ from elgato import ElgatoError
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.elgato.const import DOMAIN
|
||||
from homeassistant.components.elgato.services import SERVICE_IDENTIFY
|
||||
from homeassistant.components.elgato.const import DOMAIN, SERVICE_IDENTIFY
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS,
|
||||
ATTR_COLOR_TEMP_KELVIN,
|
||||
|
||||
@@ -36,9 +36,6 @@ MOCK_DEVICE_STATE = DeviceState(
|
||||
name="Fridge",
|
||||
type="fridge",
|
||||
value=5,
|
||||
target=4,
|
||||
min=2,
|
||||
max=8,
|
||||
unit=TemperatureUnit.CELSIUS,
|
||||
),
|
||||
TemperatureControl(
|
||||
@@ -47,9 +44,6 @@ MOCK_DEVICE_STATE = DeviceState(
|
||||
name="Freezer",
|
||||
type="freezer",
|
||||
value=-18,
|
||||
target=-18,
|
||||
min=-24,
|
||||
max=-16,
|
||||
unit=TemperatureUnit.CELSIUS,
|
||||
),
|
||||
],
|
||||
@@ -106,13 +100,11 @@ async def init_integration(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_liebherr_client: MagicMock,
|
||||
platforms: list[Platform],
|
||||
) -> MockConfigEntry:
|
||||
"""Set up the Liebherr integration for testing."""
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
|
||||
with patch("homeassistant.components.liebherr.PLATFORMS", platforms):
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
return mock_config_entry
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
# serializer version: 1
|
||||
# name: test_numbers[number.test_fridge_bottom_zone_setpoint-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': -16,
|
||||
'min': -24,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': None,
|
||||
'entity_id': 'number.test_fridge_bottom_zone_setpoint',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Bottom zone setpoint',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <NumberDeviceClass.TEMPERATURE: 'temperature'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Bottom zone setpoint',
|
||||
'platform': 'liebherr',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'setpoint_temperature_bottom_zone',
|
||||
'unique_id': 'test_device_id_setpoint_temperature_2',
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[number.test_fridge_bottom_zone_setpoint-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'temperature',
|
||||
'friendly_name': 'Test Fridge Bottom zone setpoint',
|
||||
'max': -16,
|
||||
'min': -24,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 1,
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.test_fridge_bottom_zone_setpoint',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '-18',
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[number.test_fridge_top_zone_setpoint-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 8,
|
||||
'min': 2,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': None,
|
||||
'entity_id': 'number.test_fridge_top_zone_setpoint',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Top zone setpoint',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <NumberDeviceClass.TEMPERATURE: 'temperature'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Top zone setpoint',
|
||||
'platform': 'liebherr',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'setpoint_temperature_top_zone',
|
||||
'unique_id': 'test_device_id_setpoint_temperature_1',
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[number.test_fridge_top_zone_setpoint-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'temperature',
|
||||
'friendly_name': 'Test Fridge Top zone setpoint',
|
||||
'max': 8,
|
||||
'min': 2,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 1,
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.test_fridge_top_zone_setpoint',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '4',
|
||||
})
|
||||
# ---
|
||||
# name: test_single_zone_number[number.single_zone_fridge_setpoint-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 8,
|
||||
'min': 2,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': None,
|
||||
'entity_id': 'number.single_zone_fridge_setpoint',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Setpoint',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <NumberDeviceClass.TEMPERATURE: 'temperature'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Setpoint',
|
||||
'platform': 'liebherr',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'setpoint_temperature',
|
||||
'unique_id': 'single_zone_id_setpoint_temperature_1',
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_single_zone_number[number.single_zone_fridge_setpoint-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'temperature',
|
||||
'friendly_name': 'Single Zone Fridge Setpoint',
|
||||
'max': 8,
|
||||
'min': 2,
|
||||
'mode': <NumberMode.AUTO: 'auto'>,
|
||||
'step': 1,
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.single_zone_fridge_setpoint',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '4',
|
||||
})
|
||||
# ---
|
||||
@@ -1,321 +0,0 @@
|
||||
"""Test the Liebherr number platform."""
|
||||
|
||||
from datetime import timedelta
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
from pyliebherrhomeapi import (
|
||||
Device,
|
||||
DeviceState,
|
||||
DeviceType,
|
||||
TemperatureControl,
|
||||
TemperatureUnit,
|
||||
ZonePosition,
|
||||
)
|
||||
from pyliebherrhomeapi.exceptions import LiebherrConnectionError
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.number import (
|
||||
ATTR_VALUE,
|
||||
DEFAULT_MAX_VALUE,
|
||||
DEFAULT_MIN_VALUE,
|
||||
DOMAIN as NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
)
|
||||
from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from .conftest import MOCK_DEVICE
|
||||
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def platforms() -> list[Platform]:
|
||||
"""Fixture to specify platforms to test."""
|
||||
return [Platform.NUMBER]
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def enable_all_entities(entity_registry_enabled_by_default: None) -> None:
|
||||
"""Make sure all entities are enabled."""
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
async def test_numbers(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
entity_registry: er.EntityRegistry,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test all number entities with multi-zone device."""
|
||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||
|
||||
|
||||
async def test_single_zone_number(
|
||||
hass: HomeAssistant,
|
||||
snapshot: SnapshotAssertion,
|
||||
entity_registry: er.EntityRegistry,
|
||||
mock_liebherr_client: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
platforms: list[Platform],
|
||||
) -> None:
|
||||
"""Test single zone device uses device name without zone suffix."""
|
||||
device = Device(
|
||||
device_id="single_zone_id",
|
||||
nickname="Single Zone Fridge",
|
||||
device_type=DeviceType.FRIDGE,
|
||||
device_name="K2601",
|
||||
)
|
||||
mock_liebherr_client.get_devices.return_value = [device]
|
||||
mock_liebherr_client.get_device_state.return_value = DeviceState(
|
||||
device=device,
|
||||
controls=[
|
||||
TemperatureControl(
|
||||
zone_id=1,
|
||||
zone_position=ZonePosition.TOP,
|
||||
name="Fridge",
|
||||
type="fridge",
|
||||
value=4,
|
||||
target=4,
|
||||
min=2,
|
||||
max=8,
|
||||
unit=TemperatureUnit.CELSIUS,
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
with patch("homeassistant.components.liebherr.PLATFORMS", platforms):
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||
|
||||
|
||||
async def test_multi_zone_with_none_position(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
mock_liebherr_client: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
platforms: list[Platform],
|
||||
) -> None:
|
||||
"""Test multi-zone device with None zone_position falls back to base translation key."""
|
||||
device = Device(
|
||||
device_id="multi_zone_none",
|
||||
nickname="Multi Zone Fridge",
|
||||
device_type=DeviceType.COMBI,
|
||||
device_name="CBNes9999",
|
||||
)
|
||||
mock_liebherr_client.get_devices.return_value = [device]
|
||||
mock_liebherr_client.get_device_state.return_value = DeviceState(
|
||||
device=device,
|
||||
controls=[
|
||||
TemperatureControl(
|
||||
zone_id=1,
|
||||
zone_position=None, # None triggers fallback
|
||||
name="Fridge",
|
||||
type="fridge",
|
||||
value=5,
|
||||
target=4,
|
||||
min=2,
|
||||
max=8,
|
||||
unit=TemperatureUnit.CELSIUS,
|
||||
),
|
||||
TemperatureControl(
|
||||
zone_id=2,
|
||||
zone_position=ZonePosition.BOTTOM,
|
||||
name="Freezer",
|
||||
type="freezer",
|
||||
value=-18,
|
||||
target=-18,
|
||||
min=-24,
|
||||
max=-16,
|
||||
unit=TemperatureUnit.CELSIUS,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
with patch("homeassistant.components.liebherr.PLATFORMS", platforms):
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Zone with None position should have base translation key
|
||||
zone1_entity = entity_registry.async_get("number.multi_zone_fridge_setpoint")
|
||||
assert zone1_entity is not None
|
||||
assert zone1_entity.translation_key == "setpoint_temperature"
|
||||
|
||||
# Zone with valid position should have zone-specific translation key
|
||||
zone2_entity = entity_registry.async_get(
|
||||
"number.multi_zone_fridge_bottom_zone_setpoint"
|
||||
)
|
||||
assert zone2_entity is not None
|
||||
assert zone2_entity.translation_key == "setpoint_temperature_bottom_zone"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
async def test_set_temperature(
|
||||
hass: HomeAssistant,
|
||||
mock_liebherr_client: MagicMock,
|
||||
) -> None:
|
||||
"""Test setting the temperature."""
|
||||
entity_id = "number.test_fridge_top_zone_setpoint"
|
||||
|
||||
await hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{ATTR_ENTITY_ID: entity_id, ATTR_VALUE: 6},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
mock_liebherr_client.set_temperature.assert_called_once_with(
|
||||
device_id="test_device_id",
|
||||
zone_id=1,
|
||||
target=6,
|
||||
unit=TemperatureUnit.CELSIUS,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
async def test_set_temperature_failure(
|
||||
hass: HomeAssistant,
|
||||
mock_liebherr_client: MagicMock,
|
||||
) -> None:
|
||||
"""Test setting temperature fails gracefully."""
|
||||
entity_id = "number.test_fridge_top_zone_setpoint"
|
||||
|
||||
mock_liebherr_client.set_temperature.side_effect = LiebherrConnectionError(
|
||||
"Connection failed"
|
||||
)
|
||||
|
||||
with pytest.raises(HomeAssistantError, match="Failed to set temperature"):
|
||||
await hass.services.async_call(
|
||||
NUMBER_DOMAIN,
|
||||
SERVICE_SET_VALUE,
|
||||
{ATTR_ENTITY_ID: entity_id, ATTR_VALUE: 6},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
async def test_number_update_failure(
|
||||
hass: HomeAssistant,
|
||||
mock_liebherr_client: MagicMock,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test number becomes unavailable when coordinator update fails and recovers."""
|
||||
entity_id = "number.test_fridge_top_zone_setpoint"
|
||||
|
||||
# Initial state should be available with value
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert state.state == "4"
|
||||
|
||||
# Simulate update error
|
||||
mock_liebherr_client.get_device_state.side_effect = LiebherrConnectionError(
|
||||
"Connection failed"
|
||||
)
|
||||
|
||||
# Advance time to trigger coordinator refresh (60 second interval)
|
||||
freezer.tick(timedelta(seconds=61))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Number should now be unavailable
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
# Simulate recovery
|
||||
mock_liebherr_client.get_device_state.side_effect = None
|
||||
|
||||
freezer.tick(timedelta(seconds=61))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Number should recover
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert state.state == "4"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_integration")
|
||||
async def test_number_when_control_missing(
|
||||
hass: HomeAssistant,
|
||||
mock_liebherr_client: MagicMock,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test number entity behavior when temperature control is removed."""
|
||||
entity_id = "number.test_fridge_top_zone_setpoint"
|
||||
|
||||
# Initial values should be from the control
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert state.state == "4"
|
||||
assert state.attributes["min"] == 2
|
||||
assert state.attributes["max"] == 8
|
||||
assert state.attributes["unit_of_measurement"] == "°C"
|
||||
|
||||
# Device stops reporting controls
|
||||
mock_liebherr_client.get_device_state.return_value = DeviceState(
|
||||
device=MOCK_DEVICE, controls=[]
|
||||
)
|
||||
|
||||
# Advance time to trigger coordinator refresh
|
||||
freezer.tick(timedelta(seconds=61))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# State should be unavailable
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
async def test_number_with_none_min_max(
|
||||
hass: HomeAssistant,
|
||||
mock_liebherr_client: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
platforms: list[Platform],
|
||||
) -> None:
|
||||
"""Test number entity returns defaults when control has None min/max."""
|
||||
device = Device(
|
||||
device_id="none_min_max_device",
|
||||
nickname="Test Fridge",
|
||||
device_type=DeviceType.FRIDGE,
|
||||
device_name="K2601",
|
||||
)
|
||||
mock_liebherr_client.get_devices.return_value = [device]
|
||||
mock_liebherr_client.get_device_state.return_value = DeviceState(
|
||||
device=device,
|
||||
controls=[
|
||||
TemperatureControl(
|
||||
zone_id=1,
|
||||
zone_position=ZonePosition.TOP,
|
||||
name="Fridge",
|
||||
type="fridge",
|
||||
value=4,
|
||||
target=4,
|
||||
min=None, # None min
|
||||
max=None, # None max
|
||||
unit=TemperatureUnit.CELSIUS,
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
with patch("homeassistant.components.liebherr.PLATFORMS", platforms):
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity_id = "number.test_fridge_setpoint"
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
|
||||
# Should return defaults when min/max are None
|
||||
assert state.attributes["min"] == DEFAULT_MIN_VALUE
|
||||
assert state.attributes["max"] == DEFAULT_MAX_VALUE
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Test the Liebherr sensor platform."""
|
||||
|
||||
from datetime import timedelta
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
from pyliebherrhomeapi import (
|
||||
@@ -22,7 +22,7 @@ from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.liebherr.const import DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import STATE_UNAVAILABLE, Platform
|
||||
from homeassistant.const import STATE_UNAVAILABLE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
@@ -49,7 +49,6 @@ async def test_single_zone_sensor(
|
||||
entity_registry: er.EntityRegistry,
|
||||
mock_liebherr_client: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
platforms: list[Platform],
|
||||
) -> None:
|
||||
"""Test single zone device uses device name without zone suffix."""
|
||||
device = Device(
|
||||
@@ -74,9 +73,8 @@ async def test_single_zone_sensor(
|
||||
)
|
||||
|
||||
mock_config_entry.add_to_hass(hass)
|
||||
with patch("homeassistant.components.liebherr.PLATFORMS", platforms):
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||
|
||||
@@ -250,3 +248,9 @@ async def test_sensor_unavailable_when_control_missing(
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
# Verify entity properties return None when control is missing
|
||||
entity = hass.data["entity_components"]["sensor"].get_entity(entity_id)
|
||||
assert entity is not None
|
||||
assert entity.native_value is None
|
||||
assert entity.native_unit_of_measurement is None
|
||||
|
||||
@@ -8,7 +8,7 @@ from unittest.mock import MagicMock
|
||||
from pylitterbot import Robot
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.litterrobot.services import SERVICE_SET_SLEEP_MODE
|
||||
from homeassistant.components.litterrobot.vacuum import SERVICE_SET_SLEEP_MODE
|
||||
from homeassistant.components.vacuum import (
|
||||
DOMAIN as VACUUM_DOMAIN,
|
||||
SERVICE_START,
|
||||
|
||||
@@ -74,6 +74,7 @@ async def integration_fixture(
|
||||
|
||||
@pytest.fixture(
|
||||
params=[
|
||||
"air_purifier",
|
||||
"air_quality_sensor",
|
||||
"aqara_door_window_p2",
|
||||
"aqara_motion_p2",
|
||||
@@ -100,9 +101,7 @@ async def integration_fixture(
|
||||
"ikea_air_quality_monitor",
|
||||
"ikea_scroll_wheel",
|
||||
"inovelli_vtm30",
|
||||
"inovelli_vtm31",
|
||||
"longan_link_thermostat",
|
||||
"mock_air_purifier",
|
||||
"mock_battery_storage",
|
||||
"mock_cooktop",
|
||||
"mock_dimmable_light",
|
||||
@@ -142,6 +141,7 @@ async def integration_fixture(
|
||||
"mock_window_covering_pa_lift",
|
||||
"mock_window_covering_pa_tilt",
|
||||
"mock_window_covering_tilt",
|
||||
"multi_endpoint_light",
|
||||
"onoff_light_with_levelcontrol_present",
|
||||
"resideo_x2s_thermostat",
|
||||
"secuyou_smart_lock",
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"0/40/0": 17,
|
||||
"0/40/1": "TEST_VENDOR",
|
||||
"0/40/2": 65521,
|
||||
"0/40/3": "Mock Air Purifier",
|
||||
"0/40/3": "Air Purifier",
|
||||
"0/40/4": 32769,
|
||||
"0/40/5": "",
|
||||
"0/40/6": "**REDACTED**",
|
||||
@@ -1,4 +1,102 @@
|
||||
# serializer version: 1
|
||||
# name: test_buttons[air_purifier][button.air_purifier_reset_filter_condition-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': None,
|
||||
'entity_id': 'button.air_purifier_reset_filter_condition',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Reset filter condition',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Reset filter condition',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'reset_filter_condition',
|
||||
'unique_id': '00000000000004D2-000000000000008F-MatterNodeDevice-1-HepaFilterMonitoringResetButton-113-65529',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[air_purifier][button.air_purifier_reset_filter_condition-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Air Purifier Reset filter condition',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.air_purifier_reset_filter_condition',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[air_purifier][button.air_purifier_reset_filter_condition_2-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': None,
|
||||
'entity_id': 'button.air_purifier_reset_filter_condition_2',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Reset filter condition',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Reset filter condition',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'reset_filter_condition',
|
||||
'unique_id': '00000000000004D2-000000000000008F-MatterNodeDevice-1-ActivatedCarbonFilterMonitoringResetButton-114-65529',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[air_purifier][button.air_purifier_reset_filter_condition_2-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Air Purifier Reset filter condition',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.air_purifier_reset_filter_condition_2',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[aqara_door_window_p2][button.aqara_door_and_window_sensor_p2_identify-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
@@ -1749,306 +1847,6 @@
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[inovelli_vtm31][button.inovelli_identify_1-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'button.inovelli_identify_1',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Identify (1)',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <ButtonDeviceClass.IDENTIFY: 'identify'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Identify (1)',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-1-IdentifyButton-3-1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[inovelli_vtm31][button.inovelli_identify_1-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'identify',
|
||||
'friendly_name': 'Inovelli Identify (1)',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.inovelli_identify_1',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[inovelli_vtm31][button.inovelli_identify_2-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'button.inovelli_identify_2',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Identify (2)',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <ButtonDeviceClass.IDENTIFY: 'identify'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Identify (2)',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-2-IdentifyButton-3-1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[inovelli_vtm31][button.inovelli_identify_2-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'identify',
|
||||
'friendly_name': 'Inovelli Identify (2)',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.inovelli_identify_2',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[inovelli_vtm31][button.inovelli_identify_6-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'button.inovelli_identify_6',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Identify (6)',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <ButtonDeviceClass.IDENTIFY: 'identify'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Identify (6)',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-6-IdentifyButton-3-1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[inovelli_vtm31][button.inovelli_identify_6-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'identify',
|
||||
'friendly_name': 'Inovelli Identify (6)',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.inovelli_identify_6',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[inovelli_vtm31][button.inovelli_identify_config-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'button.inovelli_identify_config',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Identify (Config)',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <ButtonDeviceClass.IDENTIFY: 'identify'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Identify (Config)',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-5-IdentifyButton-3-1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[inovelli_vtm31][button.inovelli_identify_config-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'identify',
|
||||
'friendly_name': 'Inovelli Identify (Config)',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.inovelli_identify_config',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[inovelli_vtm31][button.inovelli_identify_down-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'button.inovelli_identify_down',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Identify (Down)',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <ButtonDeviceClass.IDENTIFY: 'identify'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Identify (Down)',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-4-IdentifyButton-3-1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[inovelli_vtm31][button.inovelli_identify_down-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'identify',
|
||||
'friendly_name': 'Inovelli Identify (Down)',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.inovelli_identify_down',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[inovelli_vtm31][button.inovelli_identify_up-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'button.inovelli_identify_up',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Identify (Up)',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <ButtonDeviceClass.IDENTIFY: 'identify'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Identify (Up)',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-3-IdentifyButton-3-1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[inovelli_vtm31][button.inovelli_identify_up-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'identify',
|
||||
'friendly_name': 'Inovelli Identify (Up)',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.inovelli_identify_up',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[longan_link_thermostat][button.longan_link_hvac_identify-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
@@ -2493,104 +2291,6 @@
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[mock_air_purifier][button.mock_air_purifier_reset_filter_condition-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': None,
|
||||
'entity_id': 'button.mock_air_purifier_reset_filter_condition',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Reset filter condition',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Reset filter condition',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'reset_filter_condition',
|
||||
'unique_id': '00000000000004D2-000000000000008F-MatterNodeDevice-1-HepaFilterMonitoringResetButton-113-65529',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[mock_air_purifier][button.mock_air_purifier_reset_filter_condition-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mock Air Purifier Reset filter condition',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.mock_air_purifier_reset_filter_condition',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[mock_air_purifier][button.mock_air_purifier_reset_filter_condition_2-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': None,
|
||||
'entity_id': 'button.mock_air_purifier_reset_filter_condition_2',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Reset filter condition',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Reset filter condition',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'reset_filter_condition',
|
||||
'unique_id': '00000000000004D2-000000000000008F-MatterNodeDevice-1-ActivatedCarbonFilterMonitoringResetButton-114-65529',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[mock_air_purifier][button.mock_air_purifier_reset_filter_condition_2-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Mock Air Purifier Reset filter condition',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.mock_air_purifier_reset_filter_condition_2',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[mock_lock][button.mock_lock_identify-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
@@ -3187,6 +2887,306 @@
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[multi_endpoint_light][button.inovelli_identify_1-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'button.inovelli_identify_1',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Identify (1)',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <ButtonDeviceClass.IDENTIFY: 'identify'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Identify (1)',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-1-IdentifyButton-3-1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[multi_endpoint_light][button.inovelli_identify_1-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'identify',
|
||||
'friendly_name': 'Inovelli Identify (1)',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.inovelli_identify_1',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[multi_endpoint_light][button.inovelli_identify_2-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'button.inovelli_identify_2',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Identify (2)',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <ButtonDeviceClass.IDENTIFY: 'identify'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Identify (2)',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-2-IdentifyButton-3-1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[multi_endpoint_light][button.inovelli_identify_2-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'identify',
|
||||
'friendly_name': 'Inovelli Identify (2)',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.inovelli_identify_2',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[multi_endpoint_light][button.inovelli_identify_6-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'button.inovelli_identify_6',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Identify (6)',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <ButtonDeviceClass.IDENTIFY: 'identify'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Identify (6)',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-6-IdentifyButton-3-1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[multi_endpoint_light][button.inovelli_identify_6-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'identify',
|
||||
'friendly_name': 'Inovelli Identify (6)',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.inovelli_identify_6',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[multi_endpoint_light][button.inovelli_identify_config-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'button.inovelli_identify_config',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Identify (Config)',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <ButtonDeviceClass.IDENTIFY: 'identify'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Identify (Config)',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-5-IdentifyButton-3-1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[multi_endpoint_light][button.inovelli_identify_config-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'identify',
|
||||
'friendly_name': 'Inovelli Identify (Config)',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.inovelli_identify_config',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[multi_endpoint_light][button.inovelli_identify_down-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'button.inovelli_identify_down',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Identify (Down)',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <ButtonDeviceClass.IDENTIFY: 'identify'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Identify (Down)',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-4-IdentifyButton-3-1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[multi_endpoint_light][button.inovelli_identify_down-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'identify',
|
||||
'friendly_name': 'Inovelli Identify (Down)',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.inovelli_identify_down',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[multi_endpoint_light][button.inovelli_identify_up-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'button',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'button.inovelli_identify_up',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Identify (Up)',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <ButtonDeviceClass.IDENTIFY: 'identify'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Identify (Up)',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-3-IdentifyButton-3-1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[multi_endpoint_light][button.inovelli_identify_up-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'identify',
|
||||
'friendly_name': 'Inovelli Identify (Up)',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'button.inovelli_identify_up',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_buttons[onoff_light_with_levelcontrol_present][button.d215s_identify-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
@@ -1,4 +1,69 @@
|
||||
# serializer version: 1
|
||||
# name: test_climates[air_purifier][climate.air_purifier-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'hvac_modes': list([
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
]),
|
||||
'max_temp': 30.0,
|
||||
'min_temp': 5.0,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'climate',
|
||||
'entity_category': None,
|
||||
'entity_id': 'climate.air_purifier',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <ClimateEntityFeature: 385>,
|
||||
'translation_key': None,
|
||||
'unique_id': '00000000000004D2-000000000000008F-MatterNodeDevice-5-MatterThermostat-513-0',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_climates[air_purifier][climate.air_purifier-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 20.0,
|
||||
'friendly_name': 'Air Purifier',
|
||||
'hvac_modes': list([
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
]),
|
||||
'max_temp': 30.0,
|
||||
'min_temp': 5.0,
|
||||
'supported_features': <ClimateEntityFeature: 385>,
|
||||
'temperature': 20.0,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.air_purifier',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_climates[aqara_thermostat_w500][climate.floor_heating_thermostat-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
@@ -330,71 +395,6 @@
|
||||
'state': 'cool',
|
||||
})
|
||||
# ---
|
||||
# name: test_climates[mock_air_purifier][climate.mock_air_purifier-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'hvac_modes': list([
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
]),
|
||||
'max_temp': 30.0,
|
||||
'min_temp': 5.0,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'climate',
|
||||
'entity_category': None,
|
||||
'entity_id': 'climate.mock_air_purifier',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <ClimateEntityFeature: 385>,
|
||||
'translation_key': None,
|
||||
'unique_id': '00000000000004D2-000000000000008F-MatterNodeDevice-5-MatterThermostat-513-0',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_climates[mock_air_purifier][climate.mock_air_purifier-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 20.0,
|
||||
'friendly_name': 'Mock Air Purifier',
|
||||
'hvac_modes': list([
|
||||
<HVACMode.OFF: 'off'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
]),
|
||||
'max_temp': 30.0,
|
||||
'min_temp': 5.0,
|
||||
'supported_features': <ClimateEntityFeature: 385>,
|
||||
'temperature': 20.0,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.mock_air_purifier',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_climates[mock_room_airconditioner][climate.room_airconditioner-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
@@ -1415,216 +1415,6 @@
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_events[inovelli_vtm31][event.inovelli_button_config-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'event_types': list([
|
||||
'multi_press_1',
|
||||
'multi_press_2',
|
||||
'multi_press_3',
|
||||
'multi_press_4',
|
||||
'multi_press_5',
|
||||
'long_press',
|
||||
'long_release',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'event',
|
||||
'entity_category': None,
|
||||
'entity_id': 'event.inovelli_button_config',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Button (Config)',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <EventDeviceClass.BUTTON: 'button'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Button (Config)',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'button',
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-5-GenericSwitch-59-1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_events[inovelli_vtm31][event.inovelli_button_config-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'button',
|
||||
'event_type': None,
|
||||
'event_types': list([
|
||||
'multi_press_1',
|
||||
'multi_press_2',
|
||||
'multi_press_3',
|
||||
'multi_press_4',
|
||||
'multi_press_5',
|
||||
'long_press',
|
||||
'long_release',
|
||||
]),
|
||||
'friendly_name': 'Inovelli Button (Config)',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'event.inovelli_button_config',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_events[inovelli_vtm31][event.inovelli_button_down-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'event_types': list([
|
||||
'multi_press_1',
|
||||
'multi_press_2',
|
||||
'multi_press_3',
|
||||
'multi_press_4',
|
||||
'multi_press_5',
|
||||
'long_press',
|
||||
'long_release',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'event',
|
||||
'entity_category': None,
|
||||
'entity_id': 'event.inovelli_button_down',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Button (Down)',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <EventDeviceClass.BUTTON: 'button'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Button (Down)',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'button',
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-4-GenericSwitch-59-1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_events[inovelli_vtm31][event.inovelli_button_down-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'button',
|
||||
'event_type': None,
|
||||
'event_types': list([
|
||||
'multi_press_1',
|
||||
'multi_press_2',
|
||||
'multi_press_3',
|
||||
'multi_press_4',
|
||||
'multi_press_5',
|
||||
'long_press',
|
||||
'long_release',
|
||||
]),
|
||||
'friendly_name': 'Inovelli Button (Down)',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'event.inovelli_button_down',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_events[inovelli_vtm31][event.inovelli_button_up-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'event_types': list([
|
||||
'multi_press_1',
|
||||
'multi_press_2',
|
||||
'multi_press_3',
|
||||
'multi_press_4',
|
||||
'multi_press_5',
|
||||
'long_press',
|
||||
'long_release',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'event',
|
||||
'entity_category': None,
|
||||
'entity_id': 'event.inovelli_button_up',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Button (Up)',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <EventDeviceClass.BUTTON: 'button'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Button (Up)',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'button',
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-3-GenericSwitch-59-1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_events[inovelli_vtm31][event.inovelli_button_up-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'button',
|
||||
'event_type': None,
|
||||
'event_types': list([
|
||||
'multi_press_1',
|
||||
'multi_press_2',
|
||||
'multi_press_3',
|
||||
'multi_press_4',
|
||||
'multi_press_5',
|
||||
'long_press',
|
||||
'long_release',
|
||||
]),
|
||||
'friendly_name': 'Inovelli Button (Up)',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'event.inovelli_button_up',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_events[mock_generic_switch][event.mock_generic_switch_button-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
@@ -1821,6 +1611,216 @@
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_events[multi_endpoint_light][event.inovelli_button_config-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'event_types': list([
|
||||
'multi_press_1',
|
||||
'multi_press_2',
|
||||
'multi_press_3',
|
||||
'multi_press_4',
|
||||
'multi_press_5',
|
||||
'long_press',
|
||||
'long_release',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'event',
|
||||
'entity_category': None,
|
||||
'entity_id': 'event.inovelli_button_config',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Button (Config)',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <EventDeviceClass.BUTTON: 'button'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Button (Config)',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'button',
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-5-GenericSwitch-59-1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_events[multi_endpoint_light][event.inovelli_button_config-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'button',
|
||||
'event_type': None,
|
||||
'event_types': list([
|
||||
'multi_press_1',
|
||||
'multi_press_2',
|
||||
'multi_press_3',
|
||||
'multi_press_4',
|
||||
'multi_press_5',
|
||||
'long_press',
|
||||
'long_release',
|
||||
]),
|
||||
'friendly_name': 'Inovelli Button (Config)',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'event.inovelli_button_config',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_events[multi_endpoint_light][event.inovelli_button_down-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'event_types': list([
|
||||
'multi_press_1',
|
||||
'multi_press_2',
|
||||
'multi_press_3',
|
||||
'multi_press_4',
|
||||
'multi_press_5',
|
||||
'long_press',
|
||||
'long_release',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'event',
|
||||
'entity_category': None,
|
||||
'entity_id': 'event.inovelli_button_down',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Button (Down)',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <EventDeviceClass.BUTTON: 'button'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Button (Down)',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'button',
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-4-GenericSwitch-59-1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_events[multi_endpoint_light][event.inovelli_button_down-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'button',
|
||||
'event_type': None,
|
||||
'event_types': list([
|
||||
'multi_press_1',
|
||||
'multi_press_2',
|
||||
'multi_press_3',
|
||||
'multi_press_4',
|
||||
'multi_press_5',
|
||||
'long_press',
|
||||
'long_release',
|
||||
]),
|
||||
'friendly_name': 'Inovelli Button (Down)',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'event.inovelli_button_down',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_events[multi_endpoint_light][event.inovelli_button_up-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'event_types': list([
|
||||
'multi_press_1',
|
||||
'multi_press_2',
|
||||
'multi_press_3',
|
||||
'multi_press_4',
|
||||
'multi_press_5',
|
||||
'long_press',
|
||||
'long_release',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'event',
|
||||
'entity_category': None,
|
||||
'entity_id': 'event.inovelli_button_up',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Button (Up)',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <EventDeviceClass.BUTTON: 'button'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Button (Up)',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'button',
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-3-GenericSwitch-59-1',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_events[multi_endpoint_light][event.inovelli_button_up-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'button',
|
||||
'event_type': None,
|
||||
'event_types': list([
|
||||
'multi_press_1',
|
||||
'multi_press_2',
|
||||
'multi_press_3',
|
||||
'multi_press_4',
|
||||
'multi_press_5',
|
||||
'long_press',
|
||||
'long_release',
|
||||
]),
|
||||
'friendly_name': 'Inovelli Button (Up)',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'event.inovelli_button_up',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_events[silabs_light_switch][event.light_switch_example_button-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
@@ -1,4 +1,76 @@
|
||||
# serializer version: 1
|
||||
# name: test_fans[air_purifier][fan.air_purifier-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'preset_modes': list([
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
'auto',
|
||||
'natural_wind',
|
||||
'sleep_wind',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'fan',
|
||||
'entity_category': None,
|
||||
'entity_id': 'fan.air_purifier',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <FanEntityFeature: 63>,
|
||||
'translation_key': None,
|
||||
'unique_id': '00000000000004D2-000000000000008F-MatterNodeDevice-1-MatterFan-514-0',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_fans[air_purifier][fan.air_purifier-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'direction': 'forward',
|
||||
'friendly_name': 'Air Purifier',
|
||||
'oscillating': False,
|
||||
'percentage': None,
|
||||
'percentage_step': 10.0,
|
||||
'preset_mode': 'auto',
|
||||
'preset_modes': list([
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
'auto',
|
||||
'natural_wind',
|
||||
'sleep_wind',
|
||||
]),
|
||||
'supported_features': <FanEntityFeature: 63>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'fan.air_purifier',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_fans[longan_link_thermostat][fan.longan_link_hvac-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
@@ -63,78 +135,6 @@
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_fans[mock_air_purifier][fan.mock_air_purifier-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'preset_modes': list([
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
'auto',
|
||||
'natural_wind',
|
||||
'sleep_wind',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'fan',
|
||||
'entity_category': None,
|
||||
'entity_id': 'fan.mock_air_purifier',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <FanEntityFeature: 63>,
|
||||
'translation_key': None,
|
||||
'unique_id': '00000000000004D2-000000000000008F-MatterNodeDevice-1-MatterFan-514-0',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_fans[mock_air_purifier][fan.mock_air_purifier-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'direction': 'forward',
|
||||
'friendly_name': 'Mock Air Purifier',
|
||||
'oscillating': False,
|
||||
'percentage': None,
|
||||
'percentage_step': 10.0,
|
||||
'preset_mode': 'auto',
|
||||
'preset_modes': list([
|
||||
'low',
|
||||
'medium',
|
||||
'high',
|
||||
'auto',
|
||||
'natural_wind',
|
||||
'sleep_wind',
|
||||
]),
|
||||
'supported_features': <FanEntityFeature: 63>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'fan.mock_air_purifier',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_fans[mock_extractor_hood][fan.mock_extractor_hood-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
@@ -243,141 +243,6 @@
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_lights[inovelli_vtm31][light.inovelli_light_1-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'supported_color_modes': list([
|
||||
<ColorMode.BRIGHTNESS: 'brightness'>,
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'light',
|
||||
'entity_category': None,
|
||||
'entity_id': 'light.inovelli_light_1',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Light (1)',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Light (1)',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <LightEntityFeature: 32>,
|
||||
'translation_key': 'light',
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-1-MatterLight-6-0',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_lights[inovelli_vtm31][light.inovelli_light_1-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'brightness': None,
|
||||
'color_mode': None,
|
||||
'friendly_name': 'Inovelli Light (1)',
|
||||
'supported_color_modes': list([
|
||||
<ColorMode.BRIGHTNESS: 'brightness'>,
|
||||
]),
|
||||
'supported_features': <LightEntityFeature: 32>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'light.inovelli_light_1',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_lights[inovelli_vtm31][light.inovelli_light_6-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max_color_temp_kelvin': 6535,
|
||||
'max_mireds': 500,
|
||||
'min_color_temp_kelvin': 2000,
|
||||
'min_mireds': 153,
|
||||
'supported_color_modes': list([
|
||||
<ColorMode.COLOR_TEMP: 'color_temp'>,
|
||||
<ColorMode.HS: 'hs'>,
|
||||
<ColorMode.XY: 'xy'>,
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'light',
|
||||
'entity_category': None,
|
||||
'entity_id': 'light.inovelli_light_6',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Light (6)',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Light (6)',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <LightEntityFeature: 32>,
|
||||
'translation_key': 'light',
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-6-MatterLight-6-0',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_lights[inovelli_vtm31][light.inovelli_light_6-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'brightness': None,
|
||||
'color_mode': None,
|
||||
'color_temp': None,
|
||||
'color_temp_kelvin': None,
|
||||
'friendly_name': 'Inovelli Light (6)',
|
||||
'hs_color': None,
|
||||
'max_color_temp_kelvin': 6535,
|
||||
'max_mireds': 500,
|
||||
'min_color_temp_kelvin': 2000,
|
||||
'min_mireds': 153,
|
||||
'rgb_color': None,
|
||||
'supported_color_modes': list([
|
||||
<ColorMode.COLOR_TEMP: 'color_temp'>,
|
||||
<ColorMode.HS: 'hs'>,
|
||||
<ColorMode.XY: 'xy'>,
|
||||
]),
|
||||
'supported_features': <LightEntityFeature: 32>,
|
||||
'xy_color': None,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'light.inovelli_light_6',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_lights[mock_dimmable_light][light.mock_dimmable_light-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
@@ -763,6 +628,141 @@
|
||||
'state': 'on',
|
||||
})
|
||||
# ---
|
||||
# name: test_lights[multi_endpoint_light][light.inovelli_light_1-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'supported_color_modes': list([
|
||||
<ColorMode.BRIGHTNESS: 'brightness'>,
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'light',
|
||||
'entity_category': None,
|
||||
'entity_id': 'light.inovelli_light_1',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Light (1)',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Light (1)',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <LightEntityFeature: 32>,
|
||||
'translation_key': 'light',
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-1-MatterLight-6-0',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_lights[multi_endpoint_light][light.inovelli_light_1-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'brightness': None,
|
||||
'color_mode': None,
|
||||
'friendly_name': 'Inovelli Light (1)',
|
||||
'supported_color_modes': list([
|
||||
<ColorMode.BRIGHTNESS: 'brightness'>,
|
||||
]),
|
||||
'supported_features': <LightEntityFeature: 32>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'light.inovelli_light_1',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_lights[multi_endpoint_light][light.inovelli_light_6-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max_color_temp_kelvin': 6535,
|
||||
'max_mireds': 500,
|
||||
'min_color_temp_kelvin': 2000,
|
||||
'min_mireds': 153,
|
||||
'supported_color_modes': list([
|
||||
<ColorMode.COLOR_TEMP: 'color_temp'>,
|
||||
<ColorMode.HS: 'hs'>,
|
||||
<ColorMode.XY: 'xy'>,
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'light',
|
||||
'entity_category': None,
|
||||
'entity_id': 'light.inovelli_light_6',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Light (6)',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Light (6)',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <LightEntityFeature: 32>,
|
||||
'translation_key': 'light',
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-6-MatterLight-6-0',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_lights[multi_endpoint_light][light.inovelli_light_6-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'brightness': None,
|
||||
'color_mode': None,
|
||||
'color_temp': None,
|
||||
'color_temp_kelvin': None,
|
||||
'friendly_name': 'Inovelli Light (6)',
|
||||
'hs_color': None,
|
||||
'max_color_temp_kelvin': 6535,
|
||||
'max_mireds': 500,
|
||||
'min_color_temp_kelvin': 2000,
|
||||
'min_mireds': 153,
|
||||
'rgb_color': None,
|
||||
'supported_color_modes': list([
|
||||
<ColorMode.COLOR_TEMP: 'color_temp'>,
|
||||
<ColorMode.HS: 'hs'>,
|
||||
<ColorMode.XY: 'xy'>,
|
||||
]),
|
||||
'supported_features': <LightEntityFeature: 32>,
|
||||
'xy_color': None,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'light.inovelli_light_6',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
||||
# name: test_lights[onoff_light_with_levelcontrol_present][light.d215s-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
@@ -1116,415 +1116,6 @@
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[inovelli_vtm31][number.inovelli_led_off_intensity-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 75,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.inovelli_led_off_intensity',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'LED off intensity',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'LED off intensity',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'led_indicator_intensity_off',
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-1-InovelliLEDIndicatorIntensityOff-305134641-305070178',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[inovelli_vtm31][number.inovelli_led_off_intensity-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Inovelli LED off intensity',
|
||||
'max': 75,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.inovelli_led_off_intensity',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '1',
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[inovelli_vtm31][number.inovelli_led_on_intensity-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 75,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.inovelli_led_on_intensity',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'LED on intensity',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'LED on intensity',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'led_indicator_intensity_on',
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-1-InovelliLEDIndicatorIntensityOn-305134641-305070177',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[inovelli_vtm31][number.inovelli_led_on_intensity-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Inovelli LED on intensity',
|
||||
'max': 75,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.inovelli_led_on_intensity',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '33',
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[inovelli_vtm31][number.inovelli_off_transition_time-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 65534,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 0.1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.inovelli_off_transition_time',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Off transition time',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Off transition time',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'off_transition_time',
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-1-off_transition_time-8-19',
|
||||
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[inovelli_vtm31][number.inovelli_off_transition_time-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Inovelli Off transition time',
|
||||
'max': 65534,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 0.1,
|
||||
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.inovelli_off_transition_time',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.5',
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[inovelli_vtm31][number.inovelli_on_level_1-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 255,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.inovelli_on_level_1',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'On level (1)',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'On level (1)',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'on_level',
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-1-on_level-8-17',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[inovelli_vtm31][number.inovelli_on_level_1-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Inovelli On level (1)',
|
||||
'max': 255,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.inovelli_on_level_1',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '137',
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[inovelli_vtm31][number.inovelli_on_level_6-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 255,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.inovelli_on_level_6',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'On level (6)',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'On level (6)',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'on_level',
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-6-on_level-8-17',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[inovelli_vtm31][number.inovelli_on_level_6-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Inovelli On level (6)',
|
||||
'max': 255,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.inovelli_on_level_6',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '254',
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[inovelli_vtm31][number.inovelli_on_off_transition_time-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 65534,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 0.1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.inovelli_on_off_transition_time',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'On/Off transition time',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'On/Off transition time',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'on_off_transition_time',
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-1-on_off_transition_time-8-16',
|
||||
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[inovelli_vtm31][number.inovelli_on_off_transition_time-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Inovelli On/Off transition time',
|
||||
'max': 65534,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 0.1,
|
||||
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.inovelli_on_off_transition_time',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.5',
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[inovelli_vtm31][number.inovelli_on_transition_time-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 65534,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 0.1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.inovelli_on_transition_time',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'On transition time',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'On transition time',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'on_transition_time',
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-1-on_transition_time-8-18',
|
||||
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[inovelli_vtm31][number.inovelli_on_transition_time-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Inovelli On transition time',
|
||||
'max': 65534,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 0.1,
|
||||
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.inovelli_on_transition_time',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '1.5',
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[mock_dimmable_light][number.mock_dimmable_light_off_transition_time-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
@@ -3522,6 +3113,415 @@
|
||||
'state': '0',
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[multi_endpoint_light][number.inovelli_led_off_intensity-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 75,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.inovelli_led_off_intensity',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'LED off intensity',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'LED off intensity',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'led_indicator_intensity_off',
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-1-InovelliLEDIndicatorIntensityOff-305134641-305070178',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[multi_endpoint_light][number.inovelli_led_off_intensity-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Inovelli LED off intensity',
|
||||
'max': 75,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.inovelli_led_off_intensity',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '1',
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[multi_endpoint_light][number.inovelli_led_on_intensity-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 75,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.inovelli_led_on_intensity',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'LED on intensity',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'LED on intensity',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'led_indicator_intensity_on',
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-1-InovelliLEDIndicatorIntensityOn-305134641-305070177',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[multi_endpoint_light][number.inovelli_led_on_intensity-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Inovelli LED on intensity',
|
||||
'max': 75,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.inovelli_led_on_intensity',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '33',
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[multi_endpoint_light][number.inovelli_off_transition_time-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 65534,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 0.1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.inovelli_off_transition_time',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Off transition time',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Off transition time',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'off_transition_time',
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-1-off_transition_time-8-19',
|
||||
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[multi_endpoint_light][number.inovelli_off_transition_time-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Inovelli Off transition time',
|
||||
'max': 65534,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 0.1,
|
||||
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.inovelli_off_transition_time',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.5',
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[multi_endpoint_light][number.inovelli_on_level_1-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 255,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.inovelli_on_level_1',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'On level (1)',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'On level (1)',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'on_level',
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-1-on_level-8-17',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[multi_endpoint_light][number.inovelli_on_level_1-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Inovelli On level (1)',
|
||||
'max': 255,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.inovelli_on_level_1',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '137',
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[multi_endpoint_light][number.inovelli_on_level_6-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 255,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.inovelli_on_level_6',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'On level (6)',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'On level (6)',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'on_level',
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-6-on_level-8-17',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[multi_endpoint_light][number.inovelli_on_level_6-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Inovelli On level (6)',
|
||||
'max': 255,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.inovelli_on_level_6',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '254',
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[multi_endpoint_light][number.inovelli_on_off_transition_time-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 65534,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 0.1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.inovelli_on_off_transition_time',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'On/Off transition time',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'On/Off transition time',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'on_off_transition_time',
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-1-on_off_transition_time-8-16',
|
||||
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[multi_endpoint_light][number.inovelli_on_off_transition_time-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Inovelli On/Off transition time',
|
||||
'max': 65534,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 0.1,
|
||||
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.inovelli_on_off_transition_time',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0.5',
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[multi_endpoint_light][number.inovelli_on_transition_time-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 65534,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 0.1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.inovelli_on_transition_time',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'On transition time',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'On transition time',
|
||||
'platform': 'matter',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'on_transition_time',
|
||||
'unique_id': '00000000000004D2-00000000000000C5-MatterNodeDevice-1-on_transition_time-8-18',
|
||||
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[multi_endpoint_light][number.inovelli_on_transition_time-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Inovelli On transition time',
|
||||
'max': 65534,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 0.1,
|
||||
'unit_of_measurement': <UnitOfTime.SECONDS: 's'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.inovelli_on_transition_time',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '1.5',
|
||||
})
|
||||
# ---
|
||||
# name: test_numbers[onoff_light_with_levelcontrol_present][number.d215s_on_level-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -146,7 +146,7 @@ async def test_node_added_subscription(
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("matter_node")
|
||||
@pytest.mark.parametrize("node_fixture", ["mock_air_purifier"])
|
||||
@pytest.mark.parametrize("node_fixture", ["air_purifier"])
|
||||
async def test_device_registry_single_node_composed_device(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
@@ -156,7 +156,7 @@ async def test_device_registry_single_node_composed_device(
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("matter_node")
|
||||
@pytest.mark.parametrize("node_fixture", ["inovelli_vtm31"])
|
||||
@pytest.mark.parametrize("node_fixture", ["multi_endpoint_light"])
|
||||
async def test_multi_endpoint_name(hass: HomeAssistant) -> None:
|
||||
"""Test that the entity name gets postfixed if the device has multiple primary endpoints."""
|
||||
entity_state = hass.states.get("light.inovelli_light_1")
|
||||
|
||||
@@ -44,14 +44,14 @@ async def test_fans(
|
||||
snapshot_matter_entities(hass, entity_registry, snapshot, Platform.FAN)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("node_fixture", ["mock_air_purifier"])
|
||||
@pytest.mark.parametrize("node_fixture", ["air_purifier"])
|
||||
async def test_fan_base(
|
||||
hass: HomeAssistant,
|
||||
matter_client: MagicMock,
|
||||
matter_node: MatterNode,
|
||||
) -> None:
|
||||
"""Test Fan platform."""
|
||||
entity_id = "fan.mock_air_purifier"
|
||||
entity_id = "fan.air_purifier"
|
||||
state = hass.states.get(entity_id)
|
||||
assert state
|
||||
assert state.attributes["preset_modes"] == [
|
||||
@@ -112,14 +112,14 @@ async def test_fan_base(
|
||||
|
||||
|
||||
@pytest.mark.parametrize("expected_lingering_tasks", [True])
|
||||
@pytest.mark.parametrize("node_fixture", ["mock_air_purifier"])
|
||||
@pytest.mark.parametrize("node_fixture", ["air_purifier"])
|
||||
async def test_fan_turn_on_with_percentage(
|
||||
hass: HomeAssistant,
|
||||
matter_client: MagicMock,
|
||||
matter_node: MatterNode,
|
||||
) -> None:
|
||||
"""Test turning on the fan with a specific percentage."""
|
||||
entity_id = "fan.mock_air_purifier"
|
||||
entity_id = "fan.air_purifier"
|
||||
await hass.services.async_call(
|
||||
FAN_DOMAIN,
|
||||
SERVICE_TURN_ON,
|
||||
@@ -226,14 +226,14 @@ async def test_fan_turn_on_with_preset_mode(
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("node_fixture", ["mock_air_purifier"])
|
||||
@pytest.mark.parametrize("node_fixture", ["air_purifier"])
|
||||
async def test_fan_turn_off(
|
||||
hass: HomeAssistant,
|
||||
matter_client: MagicMock,
|
||||
matter_node: MatterNode,
|
||||
) -> None:
|
||||
"""Test turning off the fan."""
|
||||
entity_id = "fan.mock_air_purifier"
|
||||
entity_id = "fan.air_purifier"
|
||||
await hass.services.async_call(
|
||||
FAN_DOMAIN,
|
||||
SERVICE_TURN_OFF,
|
||||
@@ -269,14 +269,14 @@ async def test_fan_turn_off(
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("node_fixture", ["mock_air_purifier"])
|
||||
@pytest.mark.parametrize("node_fixture", ["air_purifier"])
|
||||
async def test_fan_oscillate(
|
||||
hass: HomeAssistant,
|
||||
matter_client: MagicMock,
|
||||
matter_node: MatterNode,
|
||||
) -> None:
|
||||
"""Test oscillating the fan."""
|
||||
entity_id = "fan.mock_air_purifier"
|
||||
entity_id = "fan.air_purifier"
|
||||
for oscillating, value in ((True, 1), (False, 0)):
|
||||
await hass.services.async_call(
|
||||
FAN_DOMAIN,
|
||||
@@ -293,14 +293,14 @@ async def test_fan_oscillate(
|
||||
matter_client.write_attribute.reset_mock()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("node_fixture", ["mock_air_purifier"])
|
||||
@pytest.mark.parametrize("node_fixture", ["air_purifier"])
|
||||
async def test_fan_set_direction(
|
||||
hass: HomeAssistant,
|
||||
matter_client: MagicMock,
|
||||
matter_node: MatterNode,
|
||||
) -> None:
|
||||
"""Test oscillating the fan."""
|
||||
entity_id = "fan.mock_air_purifier"
|
||||
entity_id = "fan.air_purifier"
|
||||
for direction, value in ((DIRECTION_FORWARD, 0), (DIRECTION_REVERSE, 1)):
|
||||
await hass.services.async_call(
|
||||
FAN_DOMAIN,
|
||||
|
||||
@@ -6,8 +6,8 @@ import pytest
|
||||
from reolink_aio.exceptions import ReolinkError
|
||||
|
||||
from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS
|
||||
from homeassistant.components.reolink.button import ATTR_SPEED, SERVICE_PTZ_MOVE
|
||||
from homeassistant.components.reolink.const import DOMAIN
|
||||
from homeassistant.components.reolink.services import ATTR_SPEED, SERVICE_PTZ_MOVE
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.const import ATTR_ENTITY_ID, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
@@ -10,7 +10,7 @@ from syrupy.assertion import SnapshotAssertion
|
||||
from vacuum_map_parser_base.map_data import Point
|
||||
|
||||
from homeassistant.components.roborock import DOMAIN
|
||||
from homeassistant.components.roborock.services import (
|
||||
from homeassistant.components.roborock.const import (
|
||||
GET_MAPS_SERVICE_NAME,
|
||||
GET_VACUUM_CURRENT_POSITION_SERVICE_NAME,
|
||||
SET_VACUUM_GOTO_POSITION_SERVICE_NAME,
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
'name_by_user': None,
|
||||
'primary_config_entry': <ANY>,
|
||||
'serial_number': '123456',
|
||||
'sw_version': '23.44.0 eb113390',
|
||||
'sw_version': None,
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
@@ -52,12 +52,12 @@
|
||||
}),
|
||||
'manufacturer': 'Tesla',
|
||||
'model': 'Model 3',
|
||||
'model_id': '3',
|
||||
'model_id': None,
|
||||
'name': 'Test',
|
||||
'name_by_user': None,
|
||||
'primary_config_entry': <ANY>,
|
||||
'serial_number': 'LRW3F7EK4NC700000',
|
||||
'sw_version': '2026.0.0',
|
||||
'sw_version': None,
|
||||
'via_device_id': None,
|
||||
})
|
||||
# ---
|
||||
|
||||
@@ -2362,6 +2362,68 @@
|
||||
'state': '40.727',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_version-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'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': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'sensor.energy_site_version',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Version',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'Version',
|
||||
'platform': 'teslemetry',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'version',
|
||||
'unique_id': '123456-version',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_version-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Energy Site Version',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.energy_site_version',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '23.44.0 eb113390',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_version-statealt]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'Energy Site Version',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.energy_site_version',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '23.44.0 eb113390',
|
||||
})
|
||||
# ---
|
||||
# name: test_sensors[sensor.energy_site_vpp_backup_reserve-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
|
||||
@@ -21,7 +21,6 @@ from homeassistant.components.teslemetry.const import CLIENT_ID, DOMAIN
|
||||
# Coordinator constants
|
||||
from homeassistant.components.teslemetry.coordinator import (
|
||||
ENERGY_HISTORY_INTERVAL,
|
||||
ENERGY_INFO_INTERVAL,
|
||||
ENERGY_LIVE_INTERVAL,
|
||||
VEHICLE_INTERVAL,
|
||||
)
|
||||
@@ -43,9 +42,7 @@ from .const import (
|
||||
ENERGY_HISTORY,
|
||||
LIVE_STATUS,
|
||||
PRODUCTS_MODERN,
|
||||
SITE_INFO,
|
||||
UNIQUE_ID,
|
||||
VEHICLE_DATA,
|
||||
VEHICLE_DATA_ALT,
|
||||
)
|
||||
|
||||
@@ -647,136 +644,3 @@ async def test_live_status_generic_error(
|
||||
|
||||
# Entry stays loaded but coordinator will have failed
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
|
||||
|
||||
async def test_vehicle_streaming_version_update(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
) -> None:
|
||||
"""Test vehicle sw_version is updated when streaming reports new version."""
|
||||
# Track listen_Version calls
|
||||
version_listeners: list = []
|
||||
|
||||
def mock_listen_version(callback):
|
||||
version_listeners.append(callback)
|
||||
return lambda: None # Return unsubscribe function
|
||||
|
||||
with patch(
|
||||
"teslemetry_stream.TeslemetryStreamVehicle.listen_Version",
|
||||
side_effect=mock_listen_version,
|
||||
):
|
||||
entry = await setup_platform(hass)
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
|
||||
# Check initial device sw_version
|
||||
vin = "LRW3F7EK4NC700000"
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, vin)})
|
||||
assert device is not None
|
||||
assert device.sw_version == "2026.0.0"
|
||||
|
||||
# Simulate streaming version update
|
||||
assert len(version_listeners) > 0
|
||||
version_listeners[0]("2026.1.0 abc123")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Check device sw_version was updated (build hash removed)
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, vin)})
|
||||
assert device is not None
|
||||
assert device.sw_version == "2026.1.0"
|
||||
|
||||
|
||||
async def test_vehicle_streaming_version_update_ignores_none(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
) -> None:
|
||||
"""Test vehicle sw_version is not updated when streaming reports None."""
|
||||
version_listeners: list = []
|
||||
|
||||
def mock_listen_version(callback):
|
||||
version_listeners.append(callback)
|
||||
return lambda: None
|
||||
|
||||
with patch(
|
||||
"teslemetry_stream.TeslemetryStreamVehicle.listen_Version",
|
||||
side_effect=mock_listen_version,
|
||||
):
|
||||
entry = await setup_platform(hass)
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
|
||||
vin = "LRW3F7EK4NC700000"
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, vin)})
|
||||
assert device is not None
|
||||
original_version = device.sw_version
|
||||
|
||||
# Simulate streaming version update with None
|
||||
assert len(version_listeners) > 0
|
||||
version_listeners[0](None)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Check device sw_version was not changed
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, vin)})
|
||||
assert device is not None
|
||||
assert device.sw_version == original_version
|
||||
|
||||
|
||||
async def test_vehicle_polling_version_update(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
mock_vehicle_data: AsyncMock,
|
||||
mock_legacy: AsyncMock,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test vehicle sw_version is updated when polling coordinator receives new version."""
|
||||
entry = await setup_platform(hass)
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
|
||||
vin = "LRW3F7EK4NC700000"
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, vin)})
|
||||
assert device is not None
|
||||
assert device.sw_version == "2026.0.0"
|
||||
|
||||
# Update mock to return new version on next poll
|
||||
updated_vehicle_data = deepcopy(VEHICLE_DATA)
|
||||
updated_vehicle_data["response"]["vehicle_state"]["car_version"] = "2026.2.0 def456"
|
||||
mock_vehicle_data.return_value = updated_vehicle_data
|
||||
|
||||
# Trigger coordinator refresh
|
||||
freezer.tick(VEHICLE_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Check device sw_version was updated (build hash removed)
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, vin)})
|
||||
assert device is not None
|
||||
assert device.sw_version == "2026.2.0"
|
||||
|
||||
|
||||
async def test_energy_site_version_update(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
mock_site_info: AsyncMock,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test energy site sw_version is updated when info coordinator receives new version."""
|
||||
entry = await setup_platform(hass)
|
||||
assert entry.state is ConfigEntryState.LOADED
|
||||
|
||||
site_id = "123456"
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, site_id)})
|
||||
assert device is not None
|
||||
assert device.sw_version == "23.44.0 eb113390"
|
||||
|
||||
# Update mock to return new version on next poll
|
||||
updated_site_info = deepcopy(SITE_INFO)
|
||||
updated_site_info["response"]["version"] = "24.1.0 abc123"
|
||||
mock_site_info.side_effect = lambda: updated_site_info
|
||||
|
||||
# Trigger coordinator refresh
|
||||
freezer.tick(ENERGY_INFO_INTERVAL)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Check device sw_version was updated
|
||||
device = device_registry.async_get_device(identifiers={(DOMAIN, site_id)})
|
||||
assert device is not None
|
||||
assert device.sw_version == "24.1.0 abc123"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user