mirror of
https://github.com/home-assistant/core.git
synced 2025-12-12 19:08:05 +00:00
Compare commits
32 Commits
button_tri
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d445b320de | ||
|
|
7b6df1a8a0 | ||
|
|
2a151dcd19 | ||
|
|
adbab150af | ||
|
|
d20edf7928 | ||
|
|
7d6d37fe76 | ||
|
|
228e0453a7 | ||
|
|
1da31c0530 | ||
|
|
41ad15e577 | ||
|
|
421af881fe | ||
|
|
715a484f7e | ||
|
|
0a789f51b8 | ||
|
|
fa25d45123 | ||
|
|
6d255b2521 | ||
|
|
5ffb39f064 | ||
|
|
d642109436 | ||
|
|
10f6d8d14f | ||
|
|
a94678cb06 | ||
|
|
0d8d466003 | ||
|
|
8ddf3e1734 | ||
|
|
d88047a750 | ||
|
|
61c7ac81d6 | ||
|
|
bbe07bddb0 | ||
|
|
a3afc2beb1 | ||
|
|
374cd93d3d | ||
|
|
6e99411084 | ||
|
|
41d5415c86 | ||
|
|
052d56f358 | ||
|
|
0a676b5812 | ||
|
|
1f4cf67daa | ||
|
|
bb4ec229ce | ||
|
|
ff62b460d5 |
6
.github/workflows/ci.yaml
vendored
6
.github/workflows/ci.yaml
vendored
@@ -263,7 +263,7 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
- name: Restore base Python virtual environment
|
- name: Restore base Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: &actions-cache actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
uses: &actions-cache actions/cache@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.0
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
key: &key-pre-commit-venv >-
|
key: &key-pre-commit-venv >-
|
||||||
@@ -304,7 +304,7 @@ jobs:
|
|||||||
- &cache-restore-pre-commit-venv
|
- &cache-restore-pre-commit-venv
|
||||||
name: Restore base Python virtual environment
|
name: Restore base Python virtual environment
|
||||||
id: cache-venv
|
id: cache-venv
|
||||||
uses: &actions-cache-restore actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
uses: &actions-cache-restore actions/cache/restore@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.0
|
||||||
with:
|
with:
|
||||||
path: venv
|
path: venv
|
||||||
fail-on-cache-miss: true
|
fail-on-cache-miss: true
|
||||||
@@ -511,7 +511,7 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
- name: Save apt cache
|
- name: Save apt cache
|
||||||
if: steps.cache-apt-check.outputs.cache-hit != 'true'
|
if: steps.cache-apt-check.outputs.cache-hit != 'true'
|
||||||
uses: &actions-cache-save actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
|
uses: &actions-cache-save actions/cache/save@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.0
|
||||||
with:
|
with:
|
||||||
path: *path-apt-cache
|
path: *path-apt-cache
|
||||||
key: *key-apt-cache
|
key: *key-apt-cache
|
||||||
|
|||||||
@@ -6,10 +6,7 @@ rules:
|
|||||||
This integration does not provide additional actions.
|
This integration does not provide additional actions.
|
||||||
appropriate-polling: done
|
appropriate-polling: done
|
||||||
brands: done
|
brands: done
|
||||||
common-modules:
|
common-modules: done
|
||||||
status: todo
|
|
||||||
comment: |
|
|
||||||
The entity.py file is not used in this integration.
|
|
||||||
config-flow-test-coverage: done
|
config-flow-test-coverage: done
|
||||||
config-flow: done
|
config-flow: done
|
||||||
dependency-transparency: done
|
dependency-transparency: done
|
||||||
|
|||||||
@@ -204,13 +204,25 @@ async def async_setup_entry(
|
|||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
class AutarcoBatterySensorEntity(
|
class AutarcoSensorBase(CoordinatorEntity[AutarcoDataUpdateCoordinator], SensorEntity):
|
||||||
CoordinatorEntity[AutarcoDataUpdateCoordinator], SensorEntity
|
"""Base class for Autarco sensors."""
|
||||||
):
|
|
||||||
|
_attr_has_entity_name = True
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
coordinator: AutarcoDataUpdateCoordinator,
|
||||||
|
description: SensorEntityDescription,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize Autarco sensor base."""
|
||||||
|
super().__init__(coordinator)
|
||||||
|
self.entity_description = description
|
||||||
|
|
||||||
|
|
||||||
|
class AutarcoBatterySensorEntity(AutarcoSensorBase):
|
||||||
"""Defines an Autarco battery sensor."""
|
"""Defines an Autarco battery sensor."""
|
||||||
|
|
||||||
entity_description: AutarcoBatterySensorEntityDescription
|
entity_description: AutarcoBatterySensorEntityDescription
|
||||||
_attr_has_entity_name = True
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -218,10 +230,8 @@ class AutarcoBatterySensorEntity(
|
|||||||
coordinator: AutarcoDataUpdateCoordinator,
|
coordinator: AutarcoDataUpdateCoordinator,
|
||||||
description: AutarcoBatterySensorEntityDescription,
|
description: AutarcoBatterySensorEntityDescription,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize Autarco sensor."""
|
"""Initialize Autarco battery sensor."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator, description)
|
||||||
|
|
||||||
self.entity_description = description
|
|
||||||
self._attr_unique_id = (
|
self._attr_unique_id = (
|
||||||
f"{coordinator.account_site.site_id}_battery_{description.key}"
|
f"{coordinator.account_site.site_id}_battery_{description.key}"
|
||||||
)
|
)
|
||||||
@@ -239,13 +249,10 @@ class AutarcoBatterySensorEntity(
|
|||||||
return self.entity_description.value_fn(self.coordinator.data.battery)
|
return self.entity_description.value_fn(self.coordinator.data.battery)
|
||||||
|
|
||||||
|
|
||||||
class AutarcoSolarSensorEntity(
|
class AutarcoSolarSensorEntity(AutarcoSensorBase):
|
||||||
CoordinatorEntity[AutarcoDataUpdateCoordinator], SensorEntity
|
|
||||||
):
|
|
||||||
"""Defines an Autarco solar sensor."""
|
"""Defines an Autarco solar sensor."""
|
||||||
|
|
||||||
entity_description: AutarcoSolarSensorEntityDescription
|
entity_description: AutarcoSolarSensorEntityDescription
|
||||||
_attr_has_entity_name = True
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -253,10 +260,8 @@ class AutarcoSolarSensorEntity(
|
|||||||
coordinator: AutarcoDataUpdateCoordinator,
|
coordinator: AutarcoDataUpdateCoordinator,
|
||||||
description: AutarcoSolarSensorEntityDescription,
|
description: AutarcoSolarSensorEntityDescription,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize Autarco sensor."""
|
"""Initialize Autarco solar sensor."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator, description)
|
||||||
|
|
||||||
self.entity_description = description
|
|
||||||
self._attr_unique_id = (
|
self._attr_unique_id = (
|
||||||
f"{coordinator.account_site.site_id}_solar_{description.key}"
|
f"{coordinator.account_site.site_id}_solar_{description.key}"
|
||||||
)
|
)
|
||||||
@@ -273,13 +278,10 @@ class AutarcoSolarSensorEntity(
|
|||||||
return self.entity_description.value_fn(self.coordinator.data.solar)
|
return self.entity_description.value_fn(self.coordinator.data.solar)
|
||||||
|
|
||||||
|
|
||||||
class AutarcoInverterSensorEntity(
|
class AutarcoInverterSensorEntity(AutarcoSensorBase):
|
||||||
CoordinatorEntity[AutarcoDataUpdateCoordinator], SensorEntity
|
|
||||||
):
|
|
||||||
"""Defines an Autarco inverter sensor."""
|
"""Defines an Autarco inverter sensor."""
|
||||||
|
|
||||||
entity_description: AutarcoInverterSensorEntityDescription
|
entity_description: AutarcoInverterSensorEntityDescription
|
||||||
_attr_has_entity_name = True
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -288,10 +290,8 @@ class AutarcoInverterSensorEntity(
|
|||||||
description: AutarcoInverterSensorEntityDescription,
|
description: AutarcoInverterSensorEntityDescription,
|
||||||
serial_number: str,
|
serial_number: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize Autarco sensor."""
|
"""Initialize Autarco inverter sensor."""
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator, description)
|
||||||
|
|
||||||
self.entity_description = description
|
|
||||||
self._serial_number = serial_number
|
self._serial_number = serial_number
|
||||||
self._attr_unique_id = f"{serial_number}_{description.key}"
|
self._attr_unique_id = f"{serial_number}_{description.key}"
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ _EXPERIMENTAL_TRIGGER_PLATFORMS = {
|
|||||||
"lawn_mower",
|
"lawn_mower",
|
||||||
"light",
|
"light",
|
||||||
"media_player",
|
"media_player",
|
||||||
|
"switch",
|
||||||
"text",
|
"text",
|
||||||
"vacuum",
|
"vacuum",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class BeoSource:
|
|||||||
NET_RADIO: Final[Source] = Source(name="B&O Radio", id="netRadio")
|
NET_RADIO: Final[Source] = Source(name="B&O Radio", id="netRadio")
|
||||||
SPDIF: Final[Source] = Source(name="Optical", id="spdif")
|
SPDIF: Final[Source] = Source(name="Optical", id="spdif")
|
||||||
TIDAL: Final[Source] = Source(name="Tidal", id="tidal")
|
TIDAL: Final[Source] = Source(name="Tidal", id="tidal")
|
||||||
|
TV: Final[Source] = Source(name="TV", id="tv")
|
||||||
UNKNOWN: Final[Source] = Source(name="Unknown Source", id="unknown")
|
UNKNOWN: Final[Source] = Source(name="Unknown Source", id="unknown")
|
||||||
URI_STREAMER: Final[Source] = Source(name="Audio Streamer", id="uriStreamer")
|
URI_STREAMER: Final[Source] = Source(name="Audio Streamer", id="uriStreamer")
|
||||||
|
|
||||||
@@ -55,12 +56,13 @@ BEO_REPEAT_TO_HA: dict[str, RepeatMode] = {
|
|||||||
class BeoMediaType(StrEnum):
|
class BeoMediaType(StrEnum):
|
||||||
"""Bang & Olufsen specific media types."""
|
"""Bang & Olufsen specific media types."""
|
||||||
|
|
||||||
FAVOURITE = "favourite"
|
|
||||||
DEEZER = "deezer"
|
DEEZER = "deezer"
|
||||||
|
FAVOURITE = "favourite"
|
||||||
|
OVERLAY_TTS = "overlay_tts"
|
||||||
RADIO = "radio"
|
RADIO = "radio"
|
||||||
TIDAL = "tidal"
|
TIDAL = "tidal"
|
||||||
TTS = "provider"
|
TTS = "provider"
|
||||||
OVERLAY_TTS = "overlay_tts"
|
TV = "tv"
|
||||||
|
|
||||||
|
|
||||||
class BeoModel(StrEnum):
|
class BeoModel(StrEnum):
|
||||||
|
|||||||
@@ -218,6 +218,7 @@ class BeoMediaPlayer(BeoEntity, MediaPlayerEntity):
|
|||||||
self._sources: dict[str, str] = {}
|
self._sources: dict[str, str] = {}
|
||||||
self._state: str = MediaPlayerState.IDLE
|
self._state: str = MediaPlayerState.IDLE
|
||||||
self._video_sources: dict[str, str] = {}
|
self._video_sources: dict[str, str] = {}
|
||||||
|
self._video_source_id_map: dict[str, str] = {}
|
||||||
self._sound_modes: dict[str, int] = {}
|
self._sound_modes: dict[str, int] = {}
|
||||||
|
|
||||||
# Beolink compatible sources
|
# Beolink compatible sources
|
||||||
@@ -355,6 +356,9 @@ class BeoMediaPlayer(BeoEntity, MediaPlayerEntity):
|
|||||||
and menu_item.label != "TV"
|
and menu_item.label != "TV"
|
||||||
):
|
):
|
||||||
self._video_sources[key] = menu_item.label
|
self._video_sources[key] = menu_item.label
|
||||||
|
self._video_source_id_map[
|
||||||
|
menu_item.content.content_uri.removeprefix("tv://")
|
||||||
|
] = menu_item.label
|
||||||
|
|
||||||
# Combine the source dicts
|
# Combine the source dicts
|
||||||
self._sources = self._audio_sources | self._video_sources
|
self._sources = self._audio_sources | self._video_sources
|
||||||
@@ -627,10 +631,11 @@ class BeoMediaPlayer(BeoEntity, MediaPlayerEntity):
|
|||||||
def media_content_type(self) -> MediaType | str | None:
|
def media_content_type(self) -> MediaType | str | None:
|
||||||
"""Return the current media type."""
|
"""Return the current media type."""
|
||||||
content_type = {
|
content_type = {
|
||||||
BeoSource.URI_STREAMER.id: MediaType.URL,
|
|
||||||
BeoSource.DEEZER.id: BeoMediaType.DEEZER,
|
BeoSource.DEEZER.id: BeoMediaType.DEEZER,
|
||||||
BeoSource.TIDAL.id: BeoMediaType.TIDAL,
|
|
||||||
BeoSource.NET_RADIO.id: BeoMediaType.RADIO,
|
BeoSource.NET_RADIO.id: BeoMediaType.RADIO,
|
||||||
|
BeoSource.TIDAL.id: BeoMediaType.TIDAL,
|
||||||
|
BeoSource.TV.id: BeoMediaType.TV,
|
||||||
|
BeoSource.URI_STREAMER.id: MediaType.URL,
|
||||||
}
|
}
|
||||||
# Hard to determine content type.
|
# Hard to determine content type.
|
||||||
if self._source_change.id in content_type:
|
if self._source_change.id in content_type:
|
||||||
@@ -690,7 +695,11 @@ class BeoMediaPlayer(BeoEntity, MediaPlayerEntity):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def source(self) -> str | None:
|
def source(self) -> str | None:
|
||||||
"""Return the current audio source."""
|
"""Return the current audio/video source."""
|
||||||
|
# Associate TV content ID with a video source
|
||||||
|
if self.media_content_id in self._video_source_id_map:
|
||||||
|
return self._video_source_id_map[self.media_content_id]
|
||||||
|
|
||||||
return self._source_change.name
|
return self._source_change.name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|||||||
@@ -64,6 +64,12 @@ async def async_migrate_entry(hass: HomeAssistant, entry: BlinkConfigEntry) -> b
|
|||||||
if entry.version == 2:
|
if entry.version == 2:
|
||||||
await _reauth_flow_wrapper(hass, entry, data)
|
await _reauth_flow_wrapper(hass, entry, data)
|
||||||
return False
|
return False
|
||||||
|
if entry.version == 3:
|
||||||
|
# Migrate device_id to hardware_id for blinkpy 0.25.x OAuth2 compatibility
|
||||||
|
if "device_id" in data:
|
||||||
|
data["hardware_id"] = data.pop("device_id")
|
||||||
|
hass.config_entries.async_update_entry(entry, data=data, version=4)
|
||||||
|
return True
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from homeassistant.core import callback
|
|||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
|
||||||
from .const import DEVICE_ID, DOMAIN
|
from .const import DOMAIN, HARDWARE_ID
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ async def _send_blink_2fa_pin(blink: Blink, pin: str | None) -> bool:
|
|||||||
class BlinkConfigFlow(ConfigFlow, domain=DOMAIN):
|
class BlinkConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||||
"""Handle a Blink config flow."""
|
"""Handle a Blink config flow."""
|
||||||
|
|
||||||
VERSION = 3
|
VERSION = 4
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
"""Initialize the blink flow."""
|
"""Initialize the blink flow."""
|
||||||
@@ -53,7 +53,7 @@ class BlinkConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
async def _handle_user_input(self, user_input: dict[str, Any]):
|
async def _handle_user_input(self, user_input: dict[str, Any]):
|
||||||
"""Handle user input."""
|
"""Handle user input."""
|
||||||
self.auth = Auth(
|
self.auth = Auth(
|
||||||
{**user_input, "device_id": DEVICE_ID},
|
{**user_input, "hardware_id": HARDWARE_ID},
|
||||||
no_prompt=True,
|
no_prompt=True,
|
||||||
session=async_get_clientsession(self.hass),
|
session=async_get_clientsession(self.hass),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
|
|
||||||
DOMAIN = "blink"
|
DOMAIN = "blink"
|
||||||
DEVICE_ID = "Home Assistant"
|
HARDWARE_ID = "Home Assistant"
|
||||||
|
|
||||||
CONF_MIGRATE = "migrate"
|
CONF_MIGRATE = "migrate"
|
||||||
CONF_CAMERA = "camera"
|
CONF_CAMERA = "camera"
|
||||||
|
|||||||
@@ -13,32 +13,25 @@ from bluecurrent_api.exceptions import (
|
|||||||
RequestLimitReached,
|
RequestLimitReached,
|
||||||
WebsocketError,
|
WebsocketError,
|
||||||
)
|
)
|
||||||
import voluptuous as vol
|
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_API_TOKEN, CONF_DEVICE_ID, Platform
|
from homeassistant.const import CONF_API_TOKEN, Platform
|
||||||
from homeassistant.core import HomeAssistant, ServiceCall
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import (
|
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||||
ConfigEntryAuthFailed,
|
from homeassistant.helpers import config_validation as cv
|
||||||
ConfigEntryNotReady,
|
|
||||||
ServiceValidationError,
|
|
||||||
)
|
|
||||||
from homeassistant.helpers import config_validation as cv, device_registry as dr
|
|
||||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
BCU_APP,
|
|
||||||
CHARGEPOINT_SETTINGS,
|
CHARGEPOINT_SETTINGS,
|
||||||
CHARGEPOINT_STATUS,
|
CHARGEPOINT_STATUS,
|
||||||
CHARGING_CARD_ID,
|
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
EVSE_ID,
|
EVSE_ID,
|
||||||
LOGGER,
|
LOGGER,
|
||||||
PLUG_AND_CHARGE,
|
PLUG_AND_CHARGE,
|
||||||
SERVICE_START_CHARGE_SESSION,
|
|
||||||
VALUE,
|
VALUE,
|
||||||
)
|
)
|
||||||
|
from .services import async_setup_services
|
||||||
|
|
||||||
type BlueCurrentConfigEntry = ConfigEntry[Connector]
|
type BlueCurrentConfigEntry = ConfigEntry[Connector]
|
||||||
|
|
||||||
@@ -54,13 +47,12 @@ VALUE_TYPES = [CHARGEPOINT_STATUS, CHARGEPOINT_SETTINGS]
|
|||||||
|
|
||||||
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
|
||||||
|
|
||||||
SERVICE_START_CHARGE_SESSION_SCHEMA = vol.Schema(
|
|
||||||
{
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
vol.Required(CONF_DEVICE_ID): cv.string,
|
"""Set up Blue Current."""
|
||||||
# When no charging card is provided, use no charging card (BCU_APP = no charging card).
|
|
||||||
vol.Optional(CHARGING_CARD_ID, default=BCU_APP): cv.string,
|
async_setup_services(hass)
|
||||||
}
|
return True
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
@@ -88,66 +80,6 @@ async def async_setup_entry(
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|
||||||
"""Set up Blue Current."""
|
|
||||||
|
|
||||||
async def start_charge_session(service_call: ServiceCall) -> None:
|
|
||||||
"""Start a charge session with the provided device and charge card ID."""
|
|
||||||
# When no charge card is provided, use the default charge card set in the config flow.
|
|
||||||
charging_card_id = service_call.data[CHARGING_CARD_ID]
|
|
||||||
device_id = service_call.data[CONF_DEVICE_ID]
|
|
||||||
|
|
||||||
# Get the device based on the given device ID.
|
|
||||||
device = dr.async_get(hass).devices.get(device_id)
|
|
||||||
|
|
||||||
if device is None:
|
|
||||||
raise ServiceValidationError(
|
|
||||||
translation_domain=DOMAIN, translation_key="invalid_device_id"
|
|
||||||
)
|
|
||||||
|
|
||||||
blue_current_config_entry: ConfigEntry | None = None
|
|
||||||
|
|
||||||
for config_entry_id in device.config_entries:
|
|
||||||
config_entry = hass.config_entries.async_get_entry(config_entry_id)
|
|
||||||
if not config_entry or config_entry.domain != DOMAIN:
|
|
||||||
# Not the blue_current config entry.
|
|
||||||
continue
|
|
||||||
|
|
||||||
if config_entry.state is not ConfigEntryState.LOADED:
|
|
||||||
raise ServiceValidationError(
|
|
||||||
translation_domain=DOMAIN, translation_key="config_entry_not_loaded"
|
|
||||||
)
|
|
||||||
|
|
||||||
blue_current_config_entry = config_entry
|
|
||||||
break
|
|
||||||
|
|
||||||
if not blue_current_config_entry:
|
|
||||||
# The device is not connected to a valid blue_current config entry.
|
|
||||||
raise ServiceValidationError(
|
|
||||||
translation_domain=DOMAIN, translation_key="no_config_entry"
|
|
||||||
)
|
|
||||||
|
|
||||||
connector = blue_current_config_entry.runtime_data
|
|
||||||
|
|
||||||
# Get the evse_id from the identifier of the device.
|
|
||||||
evse_id = next(
|
|
||||||
identifier[1]
|
|
||||||
for identifier in device.identifiers
|
|
||||||
if identifier[0] == DOMAIN
|
|
||||||
)
|
|
||||||
|
|
||||||
await connector.client.start_session(evse_id, charging_card_id)
|
|
||||||
|
|
||||||
hass.services.async_register(
|
|
||||||
DOMAIN,
|
|
||||||
SERVICE_START_CHARGE_SESSION,
|
|
||||||
start_charge_session,
|
|
||||||
SERVICE_START_CHARGE_SESSION_SCHEMA,
|
|
||||||
)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
async def async_unload_entry(
|
async def async_unload_entry(
|
||||||
hass: HomeAssistant, config_entry: BlueCurrentConfigEntry
|
hass: HomeAssistant, config_entry: BlueCurrentConfigEntry
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
|||||||
79
homeassistant/components/blue_current/services.py
Normal file
79
homeassistant/components/blue_current/services.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
"""The Blue Current integration."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
||||||
|
from homeassistant.const import CONF_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
|
||||||
|
|
||||||
|
from .const import BCU_APP, CHARGING_CARD_ID, DOMAIN, SERVICE_START_CHARGE_SESSION
|
||||||
|
|
||||||
|
SERVICE_START_CHARGE_SESSION_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_DEVICE_ID): cv.string,
|
||||||
|
# When no charging card is provided, use no charging card (BCU_APP = no charging card).
|
||||||
|
vol.Optional(CHARGING_CARD_ID, default=BCU_APP): cv.string,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def start_charge_session(service_call: ServiceCall) -> None:
|
||||||
|
"""Start a charge session with the provided device and charge card ID."""
|
||||||
|
# When no charge card is provided, use the default charge card set in the config flow.
|
||||||
|
charging_card_id = service_call.data[CHARGING_CARD_ID]
|
||||||
|
device_id = service_call.data[CONF_DEVICE_ID]
|
||||||
|
|
||||||
|
# Get the device based on the given device ID.
|
||||||
|
device = dr.async_get(service_call.hass).devices.get(device_id)
|
||||||
|
|
||||||
|
if device is None:
|
||||||
|
raise ServiceValidationError(
|
||||||
|
translation_domain=DOMAIN, translation_key="invalid_device_id"
|
||||||
|
)
|
||||||
|
|
||||||
|
blue_current_config_entry: ConfigEntry | None = None
|
||||||
|
|
||||||
|
for config_entry_id in device.config_entries:
|
||||||
|
config_entry = service_call.hass.config_entries.async_get_entry(config_entry_id)
|
||||||
|
if not config_entry or config_entry.domain != DOMAIN:
|
||||||
|
# Not the blue_current config entry.
|
||||||
|
continue
|
||||||
|
|
||||||
|
if config_entry.state is not ConfigEntryState.LOADED:
|
||||||
|
raise ServiceValidationError(
|
||||||
|
translation_domain=DOMAIN, translation_key="config_entry_not_loaded"
|
||||||
|
)
|
||||||
|
|
||||||
|
blue_current_config_entry = config_entry
|
||||||
|
break
|
||||||
|
|
||||||
|
if not blue_current_config_entry:
|
||||||
|
# The device is not connected to a valid blue_current config entry.
|
||||||
|
raise ServiceValidationError(
|
||||||
|
translation_domain=DOMAIN, translation_key="no_config_entry"
|
||||||
|
)
|
||||||
|
|
||||||
|
connector = blue_current_config_entry.runtime_data
|
||||||
|
|
||||||
|
# Get the evse_id from the identifier of the device.
|
||||||
|
evse_id = next(
|
||||||
|
identifier[1] for identifier in device.identifiers if identifier[0] == DOMAIN
|
||||||
|
)
|
||||||
|
|
||||||
|
await connector.client.start_session(evse_id, charging_card_id)
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_setup_services(hass: HomeAssistant) -> None:
|
||||||
|
"""Register the services."""
|
||||||
|
|
||||||
|
hass.services.async_register(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_START_CHARGE_SESSION,
|
||||||
|
start_charge_session,
|
||||||
|
SERVICE_START_CHARGE_SESSION_SCHEMA,
|
||||||
|
)
|
||||||
@@ -8,5 +8,5 @@
|
|||||||
"integration_type": "service",
|
"integration_type": "service",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["googleapiclient"],
|
"loggers": ["googleapiclient"],
|
||||||
"requirements": ["gcal-sync==8.0.0", "oauth2client==4.1.3", "ical==11.1.0"]
|
"requirements": ["gcal-sync==8.0.0", "oauth2client==4.1.3", "ical==12.1.1"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,5 +8,5 @@
|
|||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["google_air_quality_api"],
|
"loggers": ["google_air_quality_api"],
|
||||||
"quality_scale": "bronze",
|
"quality_scale": "bronze",
|
||||||
"requirements": ["google_air_quality_api==2.0.0"]
|
"requirements": ["google_air_quality_api==2.0.2"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,16 +88,16 @@
|
|||||||
"1b_good_air_quality": "1B - Good air quality",
|
"1b_good_air_quality": "1B - Good air quality",
|
||||||
"2_cyan": "2 - Cyan",
|
"2_cyan": "2 - Cyan",
|
||||||
"2_light_green": "2 - Light green",
|
"2_light_green": "2 - Light green",
|
||||||
"2_orange": "4 - Orange",
|
|
||||||
"2_red": "5 - Red",
|
|
||||||
"2_yellow": "3 - Yellow",
|
|
||||||
"2a_acceptable_air_quality": "2A - Acceptable air quality",
|
"2a_acceptable_air_quality": "2A - Acceptable air quality",
|
||||||
"2b_acceptable_air_quality": "2B - Acceptable air quality",
|
"2b_acceptable_air_quality": "2B - Acceptable air quality",
|
||||||
"3_green": "3 - Green",
|
"3_green": "3 - Green",
|
||||||
|
"3_yellow": "3 - Yellow",
|
||||||
"3a_aggravated_air_quality": "3A - Aggravated air quality",
|
"3a_aggravated_air_quality": "3A - Aggravated air quality",
|
||||||
"3b_bad_air_quality": "3B - Bad air quality",
|
"3b_bad_air_quality": "3B - Bad air quality",
|
||||||
|
"4_orange": "4 - Orange",
|
||||||
"4_yellow_watch": "4 - Yellow/Watch",
|
"4_yellow_watch": "4 - Yellow/Watch",
|
||||||
"5_orange_alert": "5 - Orange/Alert",
|
"5_orange_alert": "5 - Orange/Alert",
|
||||||
|
"5_red": "5 - Red",
|
||||||
"6_red_alert": "6 - Red/Alert+",
|
"6_red_alert": "6 - Red/Alert+",
|
||||||
"10_33": "10-33% of guideline",
|
"10_33": "10-33% of guideline",
|
||||||
"33_66": "33-66% of guideline",
|
"33_66": "33-66% of guideline",
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="eBatChargeToday",
|
api_key="eBatChargeToday",
|
||||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
device_class=SensorDeviceClass.ENERGY,
|
device_class=SensorDeviceClass.ENERGY,
|
||||||
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
key="mix_battery_charge_lifetime",
|
key="mix_battery_charge_lifetime",
|
||||||
@@ -42,6 +43,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="eBatDisChargeToday",
|
api_key="eBatDisChargeToday",
|
||||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
device_class=SensorDeviceClass.ENERGY,
|
device_class=SensorDeviceClass.ENERGY,
|
||||||
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
key="mix_battery_discharge_lifetime",
|
key="mix_battery_discharge_lifetime",
|
||||||
@@ -57,6 +59,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="epvToday",
|
api_key="epvToday",
|
||||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
device_class=SensorDeviceClass.ENERGY,
|
device_class=SensorDeviceClass.ENERGY,
|
||||||
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
key="mix_solar_generation_lifetime",
|
key="mix_solar_generation_lifetime",
|
||||||
@@ -72,6 +75,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="pDischarge1",
|
api_key="pDischarge1",
|
||||||
native_unit_of_measurement=UnitOfPower.WATT,
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
key="mix_battery_voltage",
|
key="mix_battery_voltage",
|
||||||
@@ -101,6 +105,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="elocalLoadToday",
|
api_key="elocalLoadToday",
|
||||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
device_class=SensorDeviceClass.ENERGY,
|
device_class=SensorDeviceClass.ENERGY,
|
||||||
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
key="mix_load_consumption_lifetime",
|
key="mix_load_consumption_lifetime",
|
||||||
@@ -116,6 +121,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="etoGridToday",
|
api_key="etoGridToday",
|
||||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
device_class=SensorDeviceClass.ENERGY,
|
device_class=SensorDeviceClass.ENERGY,
|
||||||
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
key="mix_export_to_grid_lifetime",
|
key="mix_export_to_grid_lifetime",
|
||||||
@@ -132,6 +138,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="chargePower",
|
api_key="chargePower",
|
||||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
key="mix_load_consumption",
|
key="mix_load_consumption",
|
||||||
@@ -139,6 +146,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="pLocalLoad",
|
api_key="pLocalLoad",
|
||||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
key="mix_wattage_pv_1",
|
key="mix_wattage_pv_1",
|
||||||
@@ -146,6 +154,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="pPv1",
|
api_key="pPv1",
|
||||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
key="mix_wattage_pv_2",
|
key="mix_wattage_pv_2",
|
||||||
@@ -153,6 +162,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="pPv2",
|
api_key="pPv2",
|
||||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
key="mix_wattage_pv_all",
|
key="mix_wattage_pv_all",
|
||||||
@@ -160,6 +170,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="ppv",
|
api_key="ppv",
|
||||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
key="mix_export_to_grid",
|
key="mix_export_to_grid",
|
||||||
@@ -167,6 +178,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="pactogrid",
|
api_key="pactogrid",
|
||||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
key="mix_import_from_grid",
|
key="mix_import_from_grid",
|
||||||
@@ -174,6 +186,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="pactouser",
|
api_key="pactouser",
|
||||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
key="mix_battery_discharge_kw",
|
key="mix_battery_discharge_kw",
|
||||||
@@ -181,6 +194,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="pdisCharge1",
|
api_key="pdisCharge1",
|
||||||
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
native_unit_of_measurement=UnitOfPower.KILO_WATT,
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
key="mix_grid_voltage",
|
key="mix_grid_voltage",
|
||||||
@@ -196,6 +210,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="eCharge",
|
api_key="eCharge",
|
||||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
device_class=SensorDeviceClass.ENERGY,
|
device_class=SensorDeviceClass.ENERGY,
|
||||||
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
key="mix_load_consumption_solar_today",
|
key="mix_load_consumption_solar_today",
|
||||||
@@ -203,6 +218,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="eChargeToday",
|
api_key="eChargeToday",
|
||||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
device_class=SensorDeviceClass.ENERGY,
|
device_class=SensorDeviceClass.ENERGY,
|
||||||
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
key="mix_self_consumption_today",
|
key="mix_self_consumption_today",
|
||||||
@@ -210,6 +226,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="eChargeToday1",
|
api_key="eChargeToday1",
|
||||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
device_class=SensorDeviceClass.ENERGY,
|
device_class=SensorDeviceClass.ENERGY,
|
||||||
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
key="mix_load_consumption_battery_today",
|
key="mix_load_consumption_battery_today",
|
||||||
@@ -217,6 +234,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="echarge1",
|
api_key="echarge1",
|
||||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
device_class=SensorDeviceClass.ENERGY,
|
device_class=SensorDeviceClass.ENERGY,
|
||||||
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
key="mix_import_from_grid_today",
|
key="mix_import_from_grid_today",
|
||||||
@@ -224,6 +242,7 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="etouser",
|
api_key="etouser",
|
||||||
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
|
||||||
device_class=SensorDeviceClass.ENERGY,
|
device_class=SensorDeviceClass.ENERGY,
|
||||||
|
state_class=SensorStateClass.TOTAL_INCREASING,
|
||||||
),
|
),
|
||||||
# This sensor is manually created using the most recent X-Axis value from the chartData
|
# This sensor is manually created using the most recent X-Axis value from the chartData
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="ppv1",
|
api_key="ppv1",
|
||||||
native_unit_of_measurement=UnitOfPower.WATT,
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
precision=1,
|
precision=1,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
@@ -122,6 +123,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="ppv2",
|
api_key="ppv2",
|
||||||
native_unit_of_measurement=UnitOfPower.WATT,
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
precision=1,
|
precision=1,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
@@ -165,6 +167,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="ppv3",
|
api_key="ppv3",
|
||||||
native_unit_of_measurement=UnitOfPower.WATT,
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
precision=1,
|
precision=1,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
@@ -208,6 +211,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="ppv4",
|
api_key="ppv4",
|
||||||
native_unit_of_measurement=UnitOfPower.WATT,
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
precision=1,
|
precision=1,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
@@ -234,6 +238,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="ppv",
|
api_key="ppv",
|
||||||
native_unit_of_measurement=UnitOfPower.WATT,
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
precision=1,
|
precision=1,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
@@ -258,6 +263,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="pac",
|
api_key="pac",
|
||||||
native_unit_of_measurement=UnitOfPower.WATT,
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
precision=1,
|
precision=1,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
@@ -323,6 +329,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="bdc1DischargePower",
|
api_key="bdc1DischargePower",
|
||||||
native_unit_of_measurement=UnitOfPower.WATT,
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
key="tlx_battery_1_discharge_total",
|
key="tlx_battery_1_discharge_total",
|
||||||
@@ -339,6 +346,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="bdc2DischargePower",
|
api_key="bdc2DischargePower",
|
||||||
native_unit_of_measurement=UnitOfPower.WATT,
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
key="tlx_battery_2_discharge_total",
|
key="tlx_battery_2_discharge_total",
|
||||||
@@ -372,6 +380,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="bdc1ChargePower",
|
api_key="bdc1ChargePower",
|
||||||
native_unit_of_measurement=UnitOfPower.WATT,
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
key="tlx_battery_1_charge_total",
|
key="tlx_battery_1_charge_total",
|
||||||
@@ -388,6 +397,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="bdc2ChargePower",
|
api_key="bdc2ChargePower",
|
||||||
native_unit_of_measurement=UnitOfPower.WATT,
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
key="tlx_battery_2_charge_total",
|
key="tlx_battery_2_charge_total",
|
||||||
@@ -445,6 +455,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="pacToLocalLoad",
|
api_key="pacToLocalLoad",
|
||||||
native_unit_of_measurement=UnitOfPower.WATT,
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
precision=1,
|
precision=1,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
@@ -453,6 +464,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="pacToUserTotal",
|
api_key="pacToUserTotal",
|
||||||
native_unit_of_measurement=UnitOfPower.WATT,
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
precision=1,
|
precision=1,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
@@ -461,6 +473,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="pacToGridTotal",
|
api_key="pacToGridTotal",
|
||||||
native_unit_of_measurement=UnitOfPower.WATT,
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
precision=1,
|
precision=1,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
@@ -545,6 +558,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="psystem",
|
api_key="psystem",
|
||||||
native_unit_of_measurement=UnitOfPower.WATT,
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
precision=1,
|
precision=1,
|
||||||
),
|
),
|
||||||
GrowattSensorEntityDescription(
|
GrowattSensorEntityDescription(
|
||||||
@@ -553,6 +567,7 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="pself",
|
api_key="pself",
|
||||||
native_unit_of_measurement=UnitOfPower.WATT,
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
precision=1,
|
precision=1,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -50,5 +50,6 @@ TOTAL_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
|
|||||||
api_key="nominalPower",
|
api_key="nominalPower",
|
||||||
native_unit_of_measurement=UnitOfPower.WATT,
|
native_unit_of_measurement=UnitOfPower.WATT,
|
||||||
device_class=SensorDeviceClass.POWER,
|
device_class=SensorDeviceClass.POWER,
|
||||||
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -37,5 +37,5 @@
|
|||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["pylamarzocco"],
|
"loggers": ["pylamarzocco"],
|
||||||
"quality_scale": "platinum",
|
"quality_scale": "platinum",
|
||||||
"requirements": ["pylamarzocco==2.2.3"]
|
"requirements": ["pylamarzocco==2.2.4"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,5 +7,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/local_calendar",
|
"documentation": "https://www.home-assistant.io/integrations/local_calendar",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"loggers": ["ical"],
|
"loggers": ["ical"],
|
||||||
"requirements": ["ical==11.1.0"]
|
"requirements": ["ical==12.1.1"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,5 +5,5 @@
|
|||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"documentation": "https://www.home-assistant.io/integrations/local_todo",
|
"documentation": "https://www.home-assistant.io/integrations/local_todo",
|
||||||
"iot_class": "local_polling",
|
"iot_class": "local_polling",
|
||||||
"requirements": ["ical==11.1.0"]
|
"requirements": ["ical==12.1.1"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -499,4 +499,53 @@ DISCOVERY_SCHEMAS = [
|
|||||||
entity_class=MatterBinarySensor,
|
entity_class=MatterBinarySensor,
|
||||||
required_attributes=(clusters.WindowCovering.Attributes.ConfigStatus,),
|
required_attributes=(clusters.WindowCovering.Attributes.ConfigStatus,),
|
||||||
),
|
),
|
||||||
|
MatterDiscoverySchema(
|
||||||
|
platform=Platform.BINARY_SENSOR,
|
||||||
|
entity_description=MatterBinarySensorEntityDescription(
|
||||||
|
key="ThermostatRemoteSensing_LocalTemperature",
|
||||||
|
translation_key="thermostat_remote_sensing_local_temperature",
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
# LocalTemperature bit from RemoteSensing attribute
|
||||||
|
device_to_ha=lambda x: bool(
|
||||||
|
x
|
||||||
|
& clusters.Thermostat.Bitmaps.RemoteSensingBitmap.kLocalTemperature # Calculated Local Temperature is derived from a remote node
|
||||||
|
),
|
||||||
|
),
|
||||||
|
entity_class=MatterBinarySensor,
|
||||||
|
required_attributes=(clusters.Thermostat.Attributes.RemoteSensing,),
|
||||||
|
allow_multi=True,
|
||||||
|
),
|
||||||
|
MatterDiscoverySchema(
|
||||||
|
platform=Platform.BINARY_SENSOR,
|
||||||
|
entity_description=MatterBinarySensorEntityDescription(
|
||||||
|
key="ThermostatRemoteSensing_OutdoorTemperature",
|
||||||
|
translation_key="thermostat_remote_sensing_outdoor_temperature",
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
# OutdoorTemperature bit from RemoteSensing attribute
|
||||||
|
device_to_ha=lambda x: bool(
|
||||||
|
x
|
||||||
|
& clusters.Thermostat.Bitmaps.RemoteSensingBitmap.kOutdoorTemperature # OutdoorTemperature is derived from a remote node
|
||||||
|
),
|
||||||
|
),
|
||||||
|
entity_class=MatterBinarySensor,
|
||||||
|
required_attributes=(clusters.Thermostat.Attributes.RemoteSensing,),
|
||||||
|
allow_multi=True,
|
||||||
|
),
|
||||||
|
MatterDiscoverySchema(
|
||||||
|
platform=Platform.BINARY_SENSOR,
|
||||||
|
entity_description=MatterBinarySensorEntityDescription(
|
||||||
|
key="ThermostatRemoteSensing_Occupancy",
|
||||||
|
translation_key="thermostat_remote_sensing_occupancy",
|
||||||
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
|
# Occupancy bit from RemoteSensing attribute
|
||||||
|
device_to_ha=lambda x: bool(
|
||||||
|
x
|
||||||
|
& clusters.Thermostat.Bitmaps.RemoteSensingBitmap.kOccupancy # Occupancy is derived from a remote node
|
||||||
|
),
|
||||||
|
),
|
||||||
|
entity_class=MatterBinarySensor,
|
||||||
|
required_attributes=(clusters.Thermostat.Attributes.RemoteSensing,),
|
||||||
|
featuremap_contains=clusters.Thermostat.Bitmaps.Feature.kOccupancy,
|
||||||
|
allow_multi=True,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -183,6 +183,48 @@ class MatterModeSelectEntity(MatterAttributeSelectEntity):
|
|||||||
self._attr_name = desc
|
self._attr_name = desc
|
||||||
|
|
||||||
|
|
||||||
|
class MatterDoorLockOperatingModeSelectEntity(MatterAttributeSelectEntity):
|
||||||
|
"""Representation of a Door Lock Operating Mode select entity.
|
||||||
|
|
||||||
|
This entity dynamically filters available operating modes based on the device's
|
||||||
|
`SupportedOperatingModes` bitmap attribute. In this bitmap, bit=0 indicates a
|
||||||
|
supported mode and bit=1 indicates unsupported (inverted from typical bitmap conventions).
|
||||||
|
If the bitmap is unavailable, only mandatory modes are included. The mapping from
|
||||||
|
bitmap bits to operating mode values is defined by the Matter specification.
|
||||||
|
"""
|
||||||
|
|
||||||
|
entity_description: MatterMapSelectEntityDescription
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _update_from_device(self) -> None:
|
||||||
|
"""Update from device."""
|
||||||
|
# Get the bitmap of supported operating modes
|
||||||
|
supported_modes_bitmap = self.get_matter_attribute_value(
|
||||||
|
self.entity_description.list_attribute
|
||||||
|
)
|
||||||
|
|
||||||
|
# Convert bitmap to list of supported mode values
|
||||||
|
# NOTE: The Matter spec inverts the usual meaning: bit=0 means supported,
|
||||||
|
# bit=1 means not supported, undefined bits must be 1. Mandatory modes are
|
||||||
|
# bits 0 (Normal) and 3 (NoRemoteLockUnlock).
|
||||||
|
num_mode_bits = supported_modes_bitmap.bit_length()
|
||||||
|
supported_mode_values = [
|
||||||
|
bit_position
|
||||||
|
for bit_position in range(num_mode_bits)
|
||||||
|
if not supported_modes_bitmap & (1 << bit_position)
|
||||||
|
]
|
||||||
|
|
||||||
|
# Map supported mode values to their string representations
|
||||||
|
self._attr_options = [
|
||||||
|
mapped_value
|
||||||
|
for mode_value in supported_mode_values
|
||||||
|
if (mapped_value := self.entity_description.device_to_ha(mode_value))
|
||||||
|
]
|
||||||
|
|
||||||
|
# Use base implementation to set the current option
|
||||||
|
super()._update_from_device()
|
||||||
|
|
||||||
|
|
||||||
class MatterListSelectEntity(MatterEntity, SelectEntity):
|
class MatterListSelectEntity(MatterEntity, SelectEntity):
|
||||||
"""Representation of a select entity from Matter list and selected item Cluster attribute(s)."""
|
"""Representation of a select entity from Matter list and selected item Cluster attribute(s)."""
|
||||||
|
|
||||||
@@ -594,15 +636,18 @@ DISCOVERY_SCHEMAS = [
|
|||||||
),
|
),
|
||||||
MatterDiscoverySchema(
|
MatterDiscoverySchema(
|
||||||
platform=Platform.SELECT,
|
platform=Platform.SELECT,
|
||||||
entity_description=MatterSelectEntityDescription(
|
entity_description=MatterMapSelectEntityDescription(
|
||||||
key="DoorLockOperatingMode",
|
key="DoorLockOperatingMode",
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
translation_key="door_lock_operating_mode",
|
translation_key="door_lock_operating_mode",
|
||||||
options=list(DOOR_LOCK_OPERATING_MODE_MAP.values()),
|
list_attribute=clusters.DoorLock.Attributes.SupportedOperatingModes,
|
||||||
device_to_ha=DOOR_LOCK_OPERATING_MODE_MAP.get,
|
device_to_ha=DOOR_LOCK_OPERATING_MODE_MAP.get,
|
||||||
ha_to_device=DOOR_LOCK_OPERATING_MODE_MAP_REVERSE.get,
|
ha_to_device=DOOR_LOCK_OPERATING_MODE_MAP_REVERSE.get,
|
||||||
),
|
),
|
||||||
entity_class=MatterAttributeSelectEntity,
|
entity_class=MatterDoorLockOperatingModeSelectEntity,
|
||||||
required_attributes=(clusters.DoorLock.Attributes.OperatingMode,),
|
required_attributes=(
|
||||||
|
clusters.DoorLock.Attributes.OperatingMode,
|
||||||
|
clusters.DoorLock.Attributes.SupportedOperatingModes,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -89,6 +89,15 @@
|
|||||||
"test_in_progress": {
|
"test_in_progress": {
|
||||||
"name": "Test in progress"
|
"name": "Test in progress"
|
||||||
},
|
},
|
||||||
|
"thermostat_remote_sensing_local_temperature": {
|
||||||
|
"name": "Local temperature remote sensing"
|
||||||
|
},
|
||||||
|
"thermostat_remote_sensing_occupancy": {
|
||||||
|
"name": "Occupancy remote sensing"
|
||||||
|
},
|
||||||
|
"thermostat_remote_sensing_outdoor_temperature": {
|
||||||
|
"name": "Outdoor temperature remote sensing"
|
||||||
|
},
|
||||||
"valve_fault_blocked": {
|
"valve_fault_blocked": {
|
||||||
"name": "Valve blocked"
|
"name": "Valve blocked"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
"ws_path": "WebSocket path"
|
"ws_path": "WebSocket path"
|
||||||
},
|
},
|
||||||
"data_description": {
|
"data_description": {
|
||||||
"advanced_options": "Enable and select **Next** to set advanced options.",
|
"advanced_options": "Enable and select **Submit** to set advanced options.",
|
||||||
"broker": "The hostname or IP address of your MQTT broker.",
|
"broker": "The hostname or IP address of your MQTT broker.",
|
||||||
"certificate": "The custom CA certificate file to validate your MQTT brokers certificate.",
|
"certificate": "The custom CA certificate file to validate your MQTT brokers certificate.",
|
||||||
"client_cert": "The client certificate to authenticate against your MQTT broker.",
|
"client_cert": "The client certificate to authenticate against your MQTT broker.",
|
||||||
|
|||||||
122
homeassistant/components/nederlandse_spoorwegen/diagnostics.py
Normal file
122
homeassistant/components/nederlandse_spoorwegen/diagnostics.py
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
"""Diagnostics support for Nederlandse Spoorwegen."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from homeassistant.components.diagnostics import async_redact_data
|
||||||
|
from homeassistant.const import CONF_API_KEY
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.device_registry import DeviceEntry
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .coordinator import NSConfigEntry
|
||||||
|
|
||||||
|
TO_REDACT = [
|
||||||
|
CONF_API_KEY,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
async def async_get_config_entry_diagnostics(
|
||||||
|
hass: HomeAssistant, entry: NSConfigEntry
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""Return diagnostics for a config entry."""
|
||||||
|
coordinators_data = {}
|
||||||
|
|
||||||
|
# Collect data from all coordinators
|
||||||
|
for subentry_id, coordinator in entry.runtime_data.items():
|
||||||
|
coordinators_data[subentry_id] = {
|
||||||
|
"coordinator_info": {
|
||||||
|
"name": coordinator.name,
|
||||||
|
"departure": coordinator.departure,
|
||||||
|
"destination": coordinator.destination,
|
||||||
|
"via": coordinator.via,
|
||||||
|
"departure_time": coordinator.departure_time,
|
||||||
|
},
|
||||||
|
"route_data": {
|
||||||
|
"trips_count": len(coordinator.data.trips) if coordinator.data else 0,
|
||||||
|
"has_first_trip": coordinator.data.first_trip is not None
|
||||||
|
if coordinator.data
|
||||||
|
else False,
|
||||||
|
"has_next_trip": coordinator.data.next_trip is not None
|
||||||
|
if coordinator.data
|
||||||
|
else False,
|
||||||
|
}
|
||||||
|
if coordinator.data
|
||||||
|
else None,
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"entry_data": async_redact_data(entry.data, TO_REDACT),
|
||||||
|
"coordinators": coordinators_data,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def async_get_device_diagnostics(
|
||||||
|
hass: HomeAssistant, entry: NSConfigEntry, device: DeviceEntry
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""Return diagnostics for a route."""
|
||||||
|
# Find the coordinator for this device
|
||||||
|
coordinator = None
|
||||||
|
subentry_id = None
|
||||||
|
|
||||||
|
# Each device has an identifier (DOMAIN, subentry_id)
|
||||||
|
for identifier in device.identifiers:
|
||||||
|
if identifier[0] == DOMAIN:
|
||||||
|
subentry_id = identifier[1]
|
||||||
|
coordinator = entry.runtime_data.get(subentry_id)
|
||||||
|
break
|
||||||
|
|
||||||
|
# Collect detailed diagnostics for this specific route
|
||||||
|
device_data = {
|
||||||
|
"device_info": {
|
||||||
|
"subentry_id": subentry_id,
|
||||||
|
"device_name": device.name,
|
||||||
|
"manufacturer": device.manufacturer,
|
||||||
|
"model": device.model,
|
||||||
|
},
|
||||||
|
"coordinator_info": {
|
||||||
|
"name": coordinator.name,
|
||||||
|
"departure": coordinator.departure,
|
||||||
|
"destination": coordinator.destination,
|
||||||
|
"via": coordinator.via,
|
||||||
|
"departure_time": coordinator.departure_time,
|
||||||
|
}
|
||||||
|
if coordinator
|
||||||
|
else None,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add detailed trip data if available
|
||||||
|
if coordinator and coordinator.data:
|
||||||
|
device_data["trip_details"] = {
|
||||||
|
"trips_count": len(coordinator.data.trips),
|
||||||
|
"has_first_trip": coordinator.data.first_trip is not None,
|
||||||
|
"has_next_trip": coordinator.data.next_trip is not None,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add first trip details if available
|
||||||
|
if coordinator.data.first_trip:
|
||||||
|
first_trip = coordinator.data.first_trip
|
||||||
|
device_data["first_trip"] = {
|
||||||
|
"departure_time_planned": str(first_trip.departure_time_planned)
|
||||||
|
if first_trip.departure_time_planned
|
||||||
|
else None,
|
||||||
|
"departure_time_actual": str(first_trip.departure_time_actual)
|
||||||
|
if first_trip.departure_time_actual
|
||||||
|
else None,
|
||||||
|
"arrival_time_planned": str(first_trip.arrival_time_planned)
|
||||||
|
if first_trip.arrival_time_planned
|
||||||
|
else None,
|
||||||
|
"arrival_time_actual": str(first_trip.arrival_time_actual)
|
||||||
|
if first_trip.arrival_time_actual
|
||||||
|
else None,
|
||||||
|
"departure_platform_planned": first_trip.departure_platform_planned,
|
||||||
|
"departure_platform_actual": first_trip.departure_platform_actual,
|
||||||
|
"arrival_platform_planned": first_trip.arrival_platform_planned,
|
||||||
|
"arrival_platform_actual": first_trip.arrival_platform_actual,
|
||||||
|
"status": str(first_trip.status) if first_trip.status else None,
|
||||||
|
"nr_transfers": first_trip.nr_transfers,
|
||||||
|
"going": first_trip.going,
|
||||||
|
}
|
||||||
|
|
||||||
|
return device_data
|
||||||
@@ -27,6 +27,8 @@ from .const import (
|
|||||||
DATA_CAMERAS,
|
DATA_CAMERAS,
|
||||||
DATA_EVENTS,
|
DATA_EVENTS,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
EVENT_TYPE_CONNECTION,
|
||||||
|
EVENT_TYPE_DISCONNECTION,
|
||||||
EVENT_TYPE_LIGHT_MODE,
|
EVENT_TYPE_LIGHT_MODE,
|
||||||
EVENT_TYPE_OFF,
|
EVENT_TYPE_OFF,
|
||||||
EVENT_TYPE_ON,
|
EVENT_TYPE_ON,
|
||||||
@@ -123,7 +125,13 @@ class NetatmoCamera(NetatmoModuleEntity, Camera):
|
|||||||
"""Entity created."""
|
"""Entity created."""
|
||||||
await super().async_added_to_hass()
|
await super().async_added_to_hass()
|
||||||
|
|
||||||
for event_type in (EVENT_TYPE_LIGHT_MODE, EVENT_TYPE_OFF, EVENT_TYPE_ON):
|
for event_type in (
|
||||||
|
EVENT_TYPE_LIGHT_MODE,
|
||||||
|
EVENT_TYPE_OFF,
|
||||||
|
EVENT_TYPE_ON,
|
||||||
|
EVENT_TYPE_CONNECTION,
|
||||||
|
EVENT_TYPE_DISCONNECTION,
|
||||||
|
):
|
||||||
self.async_on_remove(
|
self.async_on_remove(
|
||||||
async_dispatcher_connect(
|
async_dispatcher_connect(
|
||||||
self.hass,
|
self.hass,
|
||||||
@@ -146,12 +154,19 @@ class NetatmoCamera(NetatmoModuleEntity, Camera):
|
|||||||
data["home_id"] == self.home.entity_id
|
data["home_id"] == self.home.entity_id
|
||||||
and data["camera_id"] == self.device.entity_id
|
and data["camera_id"] == self.device.entity_id
|
||||||
):
|
):
|
||||||
if data[WEBHOOK_PUSH_TYPE] in ("NACamera-off", "NACamera-disconnection"):
|
if data[WEBHOOK_PUSH_TYPE] in (
|
||||||
|
"NACamera-off",
|
||||||
|
"NOCamera-off",
|
||||||
|
"NACamera-disconnection",
|
||||||
|
"NOCamera-disconnection",
|
||||||
|
):
|
||||||
self._attr_is_streaming = False
|
self._attr_is_streaming = False
|
||||||
self._monitoring = False
|
self._monitoring = False
|
||||||
elif data[WEBHOOK_PUSH_TYPE] in (
|
elif data[WEBHOOK_PUSH_TYPE] in (
|
||||||
"NACamera-on",
|
"NACamera-on",
|
||||||
|
"NOCamera-on",
|
||||||
WEBHOOK_NACAMERA_CONNECTION,
|
WEBHOOK_NACAMERA_CONNECTION,
|
||||||
|
"NOCamera-connection",
|
||||||
):
|
):
|
||||||
self._attr_is_streaming = True
|
self._attr_is_streaming = True
|
||||||
self._monitoring = True
|
self._monitoring = True
|
||||||
|
|||||||
@@ -127,6 +127,9 @@ EVENT_TYPE_ALARM_STARTED = "alarm_started"
|
|||||||
EVENT_TYPE_DOOR_TAG_BIG_MOVE = "tag_big_move"
|
EVENT_TYPE_DOOR_TAG_BIG_MOVE = "tag_big_move"
|
||||||
EVENT_TYPE_DOOR_TAG_OPEN = "tag_open"
|
EVENT_TYPE_DOOR_TAG_OPEN = "tag_open"
|
||||||
EVENT_TYPE_DOOR_TAG_SMALL_MOVE = "tag_small_move"
|
EVENT_TYPE_DOOR_TAG_SMALL_MOVE = "tag_small_move"
|
||||||
|
# Generic events
|
||||||
|
EVENT_TYPE_CONNECTION = "connection"
|
||||||
|
EVENT_TYPE_DISCONNECTION = "disconnection"
|
||||||
EVENT_TYPE_OFF = "off"
|
EVENT_TYPE_OFF = "off"
|
||||||
EVENT_TYPE_ON = "on"
|
EVENT_TYPE_ON = "on"
|
||||||
|
|
||||||
|
|||||||
@@ -7,5 +7,5 @@
|
|||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["pynintendoauth", "pynintendoparental"],
|
"loggers": ["pynintendoauth", "pynintendoparental"],
|
||||||
"quality_scale": "bronze",
|
"quality_scale": "bronze",
|
||||||
"requirements": ["pynintendoauth==1.0.0", "pynintendoparental==2.1.0"]
|
"requirements": ["pynintendoauth==1.0.0", "pynintendoparental==2.1.1"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,5 +9,5 @@
|
|||||||
"integration_type": "service",
|
"integration_type": "service",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"quality_scale": "bronze",
|
"quality_scale": "bronze",
|
||||||
"requirements": ["openai==2.9.0", "python-open-router==0.3.3"]
|
"requirements": ["openai==2.11.0", "python-open-router==0.3.3"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,5 +8,5 @@
|
|||||||
"documentation": "https://www.home-assistant.io/integrations/openai_conversation",
|
"documentation": "https://www.home-assistant.io/integrations/openai_conversation",
|
||||||
"integration_type": "service",
|
"integration_type": "service",
|
||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"requirements": ["openai==2.9.0"]
|
"requirements": ["openai==2.11.0"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,5 +8,5 @@
|
|||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["ical"],
|
"loggers": ["ical"],
|
||||||
"quality_scale": "silver",
|
"quality_scale": "silver",
|
||||||
"requirements": ["ical==11.1.0"]
|
"requirements": ["ical==12.1.1"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -394,7 +394,14 @@ class RoborockWashingMachineUpdateCoordinator(
|
|||||||
async def _async_update_data(
|
async def _async_update_data(
|
||||||
self,
|
self,
|
||||||
) -> dict[RoborockZeoProtocol, StateType]:
|
) -> dict[RoborockZeoProtocol, StateType]:
|
||||||
return await self.api.query_values(self.request_protocols)
|
try:
|
||||||
|
return await self.api.query_values(self.request_protocols)
|
||||||
|
except RoborockException as ex:
|
||||||
|
_LOGGER.debug("Failed to update washing machine data: %s", ex)
|
||||||
|
raise UpdateFailed(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="update_data_fail",
|
||||||
|
) from ex
|
||||||
|
|
||||||
|
|
||||||
class RoborockWetDryVacUpdateCoordinator(
|
class RoborockWetDryVacUpdateCoordinator(
|
||||||
@@ -425,4 +432,11 @@ class RoborockWetDryVacUpdateCoordinator(
|
|||||||
async def _async_update_data(
|
async def _async_update_data(
|
||||||
self,
|
self,
|
||||||
) -> dict[RoborockDyadDataProtocol, StateType]:
|
) -> dict[RoborockDyadDataProtocol, StateType]:
|
||||||
return await self.api.query_values(self.request_protocols)
|
try:
|
||||||
|
return await self.api.query_values(self.request_protocols)
|
||||||
|
except RoborockException as ex:
|
||||||
|
_LOGGER.debug("Failed to update wet dry vac data: %s", ex)
|
||||||
|
raise UpdateFailed(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="update_data_fail",
|
||||||
|
) from ex
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ from .repairs import (
|
|||||||
async_manage_open_wifi_ap_issue,
|
async_manage_open_wifi_ap_issue,
|
||||||
async_manage_outbound_websocket_incorrectly_enabled_issue,
|
async_manage_outbound_websocket_incorrectly_enabled_issue,
|
||||||
)
|
)
|
||||||
|
from .services import async_setup_services
|
||||||
from .utils import (
|
from .utils import (
|
||||||
async_create_issue_unsupported_firmware,
|
async_create_issue_unsupported_firmware,
|
||||||
async_migrate_rpc_virtual_components_unique_ids,
|
async_migrate_rpc_virtual_components_unique_ids,
|
||||||
@@ -117,6 +118,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
if (conf := config.get(DOMAIN)) is not None:
|
if (conf := config.get(DOMAIN)) is not None:
|
||||||
hass.data[DOMAIN] = {CONF_COAP_PORT: conf[CONF_COAP_PORT]}
|
hass.data[DOMAIN] = {CONF_COAP_PORT: conf[CONF_COAP_PORT]}
|
||||||
|
|
||||||
|
async_setup_services(hass)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -343,3 +343,6 @@ MODEL_FRANKEVER_IRRIGATION_CONTROLLER = "Irrigation"
|
|||||||
ROLE_GENERIC = "generic"
|
ROLE_GENERIC = "generic"
|
||||||
|
|
||||||
TRV_CHANNEL = 0
|
TRV_CHANNEL = 0
|
||||||
|
|
||||||
|
ATTR_KEY = "key"
|
||||||
|
ATTR_VALUE = "value"
|
||||||
|
|||||||
@@ -105,5 +105,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"get_kvs_value": {
|
||||||
|
"service": "mdi:import"
|
||||||
|
},
|
||||||
|
"set_kvs_value": {
|
||||||
|
"service": "mdi:export"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
rules:
|
rules:
|
||||||
# Bronze
|
# Bronze
|
||||||
action-setup:
|
action-setup: done
|
||||||
status: exempt
|
|
||||||
comment: The integration does not register services.
|
|
||||||
appropriate-polling: done
|
appropriate-polling: done
|
||||||
brands: done
|
brands: done
|
||||||
common-modules: done
|
common-modules: done
|
||||||
config-flow-test-coverage: done
|
config-flow-test-coverage: done
|
||||||
config-flow: done
|
config-flow: done
|
||||||
dependency-transparency: done
|
dependency-transparency: done
|
||||||
docs-actions:
|
docs-actions: done
|
||||||
status: exempt
|
|
||||||
comment: The integration does not register services.
|
|
||||||
docs-high-level-description: done
|
docs-high-level-description: done
|
||||||
docs-installation-instructions: done
|
docs-installation-instructions: done
|
||||||
docs-removal-instructions: done
|
docs-removal-instructions: done
|
||||||
@@ -24,9 +20,7 @@ rules:
|
|||||||
unique-config-entry: done
|
unique-config-entry: done
|
||||||
|
|
||||||
# Silver
|
# Silver
|
||||||
action-exceptions:
|
action-exceptions: done
|
||||||
status: exempt
|
|
||||||
comment: The integration does not register services.
|
|
||||||
config-entry-unloading: done
|
config-entry-unloading: done
|
||||||
docs-configuration-parameters: done
|
docs-configuration-parameters: done
|
||||||
docs-installation-parameters: done
|
docs-installation-parameters: done
|
||||||
|
|||||||
170
homeassistant/components/shelly/services.py
Normal file
170
homeassistant/components/shelly/services.py
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
"""Support for services."""
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, Any, cast
|
||||||
|
|
||||||
|
from aioshelly.const import RPC_GENERATIONS
|
||||||
|
from aioshelly.exceptions import DeviceConnectionError, RpcCallError
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
|
from homeassistant.const import ATTR_DEVICE_ID
|
||||||
|
from homeassistant.core import (
|
||||||
|
HomeAssistant,
|
||||||
|
ServiceCall,
|
||||||
|
ServiceResponse,
|
||||||
|
SupportsResponse,
|
||||||
|
callback,
|
||||||
|
)
|
||||||
|
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||||
|
from homeassistant.helpers import config_validation as cv, device_registry as dr
|
||||||
|
from homeassistant.util.json import JsonValueType
|
||||||
|
|
||||||
|
from .const import ATTR_KEY, ATTR_VALUE, CONF_SLEEP_PERIOD, DOMAIN
|
||||||
|
from .coordinator import ShellyConfigEntry
|
||||||
|
from .utils import get_device_entry_gen
|
||||||
|
|
||||||
|
SERVICE_GET_KVS_VALUE = "get_kvs_value"
|
||||||
|
SERVICE_SET_KVS_VALUE = "set_kvs_value"
|
||||||
|
SERVICE_GET_KVS_VALUE_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(ATTR_DEVICE_ID): cv.string,
|
||||||
|
vol.Required(ATTR_KEY): str,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
SERVICE_SET_KVS_VALUE_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(ATTR_DEVICE_ID): cv.string,
|
||||||
|
vol.Required(ATTR_KEY): str,
|
||||||
|
vol.Required(ATTR_VALUE): vol.Any(str, int, float, bool, dict, list, None),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_get_config_entry_for_service_call(
|
||||||
|
call: ServiceCall,
|
||||||
|
) -> ShellyConfigEntry:
|
||||||
|
"""Get the config entry related to a service call (by device ID)."""
|
||||||
|
device_registry = dr.async_get(call.hass)
|
||||||
|
device_id = call.data[ATTR_DEVICE_ID]
|
||||||
|
|
||||||
|
if (device_entry := device_registry.async_get(device_id)) is None:
|
||||||
|
raise ServiceValidationError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="invalid_device_id",
|
||||||
|
translation_placeholders={"device_id": device_id},
|
||||||
|
)
|
||||||
|
|
||||||
|
for entry_id in device_entry.config_entries:
|
||||||
|
config_entry = call.hass.config_entries.async_get_entry(entry_id)
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
assert config_entry
|
||||||
|
|
||||||
|
if config_entry.domain != DOMAIN:
|
||||||
|
continue
|
||||||
|
if config_entry.state is not ConfigEntryState.LOADED:
|
||||||
|
raise ServiceValidationError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="entry_not_loaded",
|
||||||
|
translation_placeholders={"device": config_entry.title},
|
||||||
|
)
|
||||||
|
if get_device_entry_gen(config_entry) not in RPC_GENERATIONS:
|
||||||
|
raise ServiceValidationError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="kvs_not_supported",
|
||||||
|
translation_placeholders={"device": config_entry.title},
|
||||||
|
)
|
||||||
|
if config_entry.data.get(CONF_SLEEP_PERIOD, 0) > 0:
|
||||||
|
raise ServiceValidationError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="kvs_not_supported",
|
||||||
|
translation_placeholders={"device": config_entry.title},
|
||||||
|
)
|
||||||
|
return config_entry
|
||||||
|
|
||||||
|
raise ServiceValidationError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="config_entry_not_found",
|
||||||
|
translation_placeholders={"device_id": device_id},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def _async_execute_action(
|
||||||
|
call: ServiceCall, method: str, args: tuple
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""Execute action on the device."""
|
||||||
|
config_entry = async_get_config_entry_for_service_call(call)
|
||||||
|
|
||||||
|
runtime_data = config_entry.runtime_data
|
||||||
|
|
||||||
|
if not runtime_data.rpc:
|
||||||
|
raise ServiceValidationError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="device_not_initialized",
|
||||||
|
translation_placeholders={"device": config_entry.title},
|
||||||
|
)
|
||||||
|
|
||||||
|
action_method = getattr(runtime_data.rpc.device, method)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = await action_method(*args)
|
||||||
|
except RpcCallError as err:
|
||||||
|
raise HomeAssistantError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="rpc_call_error",
|
||||||
|
translation_placeholders={"device": config_entry.title},
|
||||||
|
) from err
|
||||||
|
except DeviceConnectionError as err:
|
||||||
|
raise HomeAssistantError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="device_communication_error",
|
||||||
|
translation_placeholders={"device": config_entry.title},
|
||||||
|
) from err
|
||||||
|
else:
|
||||||
|
return cast(dict[str, Any], response)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_get_kvs_value(call: ServiceCall) -> ServiceResponse:
|
||||||
|
"""Handle the get_kvs_value service call."""
|
||||||
|
key = call.data[ATTR_KEY]
|
||||||
|
|
||||||
|
response = await _async_execute_action(call, "kvs_get", (key,))
|
||||||
|
|
||||||
|
result: dict[str, JsonValueType] = {}
|
||||||
|
result[ATTR_VALUE] = response[ATTR_VALUE]
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
async def async_set_kvs_value(call: ServiceCall) -> None:
|
||||||
|
"""Handle the set_kvs_value service call."""
|
||||||
|
await _async_execute_action(
|
||||||
|
call, "kvs_set", (call.data[ATTR_KEY], call.data[ATTR_VALUE])
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_setup_services(hass: HomeAssistant) -> None:
|
||||||
|
"""Set up the services for Shelly integration."""
|
||||||
|
for service, method, schema, response in (
|
||||||
|
(
|
||||||
|
SERVICE_GET_KVS_VALUE,
|
||||||
|
async_get_kvs_value,
|
||||||
|
SERVICE_GET_KVS_VALUE_SCHEMA,
|
||||||
|
SupportsResponse.ONLY,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
SERVICE_SET_KVS_VALUE,
|
||||||
|
async_set_kvs_value,
|
||||||
|
SERVICE_SET_KVS_VALUE_SCHEMA,
|
||||||
|
SupportsResponse.NONE,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
hass.services.async_register(
|
||||||
|
DOMAIN,
|
||||||
|
service,
|
||||||
|
method,
|
||||||
|
schema=schema,
|
||||||
|
supports_response=response,
|
||||||
|
)
|
||||||
27
homeassistant/components/shelly/services.yaml
Normal file
27
homeassistant/components/shelly/services.yaml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
get_kvs_value:
|
||||||
|
fields:
|
||||||
|
device_id:
|
||||||
|
required: true
|
||||||
|
selector:
|
||||||
|
device:
|
||||||
|
integration: shelly
|
||||||
|
key:
|
||||||
|
required: true
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
|
||||||
|
set_kvs_value:
|
||||||
|
fields:
|
||||||
|
device_id:
|
||||||
|
required: true
|
||||||
|
selector:
|
||||||
|
device:
|
||||||
|
integration: shelly
|
||||||
|
key:
|
||||||
|
required: true
|
||||||
|
selector:
|
||||||
|
text:
|
||||||
|
value:
|
||||||
|
required: true
|
||||||
|
selector:
|
||||||
|
object:
|
||||||
@@ -603,6 +603,9 @@
|
|||||||
"auth_error": {
|
"auth_error": {
|
||||||
"message": "Authentication failed for {device}, please update your credentials"
|
"message": "Authentication failed for {device}, please update your credentials"
|
||||||
},
|
},
|
||||||
|
"config_entry_not_found": {
|
||||||
|
"message": "Config entry for device ID {device_id} not found"
|
||||||
|
},
|
||||||
"device_communication_action_error": {
|
"device_communication_action_error": {
|
||||||
"message": "Device communication error occurred while calling action for {entity} of {device}"
|
"message": "Device communication error occurred while calling action for {entity} of {device}"
|
||||||
},
|
},
|
||||||
@@ -612,12 +615,24 @@
|
|||||||
"device_not_found": {
|
"device_not_found": {
|
||||||
"message": "{device} not found while configuring device automation triggers"
|
"message": "{device} not found while configuring device automation triggers"
|
||||||
},
|
},
|
||||||
|
"device_not_initialized": {
|
||||||
|
"message": "{device} not initialized"
|
||||||
|
},
|
||||||
|
"entry_not_loaded": {
|
||||||
|
"message": "Config entry not loaded for {device}"
|
||||||
|
},
|
||||||
"firmware_unsupported": {
|
"firmware_unsupported": {
|
||||||
"message": "{device} is running an unsupported firmware, please update the firmware"
|
"message": "{device} is running an unsupported firmware, please update the firmware"
|
||||||
},
|
},
|
||||||
|
"invalid_device_id": {
|
||||||
|
"message": "Invalid device ID specified: {device_id}"
|
||||||
|
},
|
||||||
"invalid_trigger": {
|
"invalid_trigger": {
|
||||||
"message": "Invalid device automation trigger (type, subtype): {trigger}"
|
"message": "Invalid device automation trigger (type, subtype): {trigger}"
|
||||||
},
|
},
|
||||||
|
"kvs_not_supported": {
|
||||||
|
"message": "{device} does not support KVS"
|
||||||
|
},
|
||||||
"ota_update_connection_error": {
|
"ota_update_connection_error": {
|
||||||
"message": "Device communication error occurred while triggering OTA update for {device}"
|
"message": "Device communication error occurred while triggering OTA update for {device}"
|
||||||
},
|
},
|
||||||
@@ -627,6 +642,9 @@
|
|||||||
"rpc_call_action_error": {
|
"rpc_call_action_error": {
|
||||||
"message": "RPC call error occurred while calling action for {entity} of {device}"
|
"message": "RPC call error occurred while calling action for {entity} of {device}"
|
||||||
},
|
},
|
||||||
|
"rpc_call_error": {
|
||||||
|
"message": "RPC call error occurred for {device}"
|
||||||
|
},
|
||||||
"update_error": {
|
"update_error": {
|
||||||
"message": "An error occurred while retrieving data from {device}"
|
"message": "An error occurred while retrieving data from {device}"
|
||||||
},
|
},
|
||||||
@@ -748,5 +766,39 @@
|
|||||||
"manual": "Enter address manually"
|
"manual": "Enter address manually"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"get_kvs_value": {
|
||||||
|
"description": "Get a value from the device's Key-Value Storage.",
|
||||||
|
"fields": {
|
||||||
|
"device_id": {
|
||||||
|
"description": "The ID of the Shelly device to get the KVS value from.",
|
||||||
|
"name": "Device"
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"description": "The name of the key for which the KVS value will be retrieved.",
|
||||||
|
"name": "Key"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Get KVS value"
|
||||||
|
},
|
||||||
|
"set_kvs_value": {
|
||||||
|
"description": "Set a value in the device's Key-Value Storage.",
|
||||||
|
"fields": {
|
||||||
|
"device_id": {
|
||||||
|
"description": "The ID of the Shelly device to set the KVS value.",
|
||||||
|
"name": "Device"
|
||||||
|
},
|
||||||
|
"key": {
|
||||||
|
"description": "The name of the key under which the KVS value will be stored.",
|
||||||
|
"name": "Key"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"description": "Value to set.",
|
||||||
|
"name": "Value"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Set KVS value"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,5 +31,5 @@
|
|||||||
"iot_class": "cloud_push",
|
"iot_class": "cloud_push",
|
||||||
"loggers": ["pysmartthings"],
|
"loggers": ["pysmartthings"],
|
||||||
"quality_scale": "bronze",
|
"quality_scale": "bronze",
|
||||||
"requirements": ["pysmartthings==3.5.0"]
|
"requirements": ["pysmartthings==3.5.1"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,9 +155,12 @@ class SqueezeboxConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
_LOGGER.exception("Unknown exception while validating connection")
|
_LOGGER.exception("Unknown exception while validating connection")
|
||||||
return "unknown"
|
return "unknown"
|
||||||
|
|
||||||
if "uuid" in status:
|
if "uuid" not in status:
|
||||||
await self.async_set_unique_id(status["uuid"])
|
_LOGGER.exception("Discovered server did not provide a uuid")
|
||||||
self._abort_if_unique_id_configured()
|
return "missing_uuid"
|
||||||
|
|
||||||
|
await self.async_set_unique_id(status["uuid"])
|
||||||
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"error": {
|
"error": {
|
||||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
|
||||||
|
"missing_uuid": "Your LMS did not provide a unique identifier and is not compatible with this integration. Please check and update your LMS version.",
|
||||||
"no_server_found": "Could not automatically discover server.",
|
"no_server_found": "Could not automatically discover server.",
|
||||||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -29,5 +29,13 @@
|
|||||||
"turn_on": {
|
"turn_on": {
|
||||||
"service": "mdi:toggle-switch-variant"
|
"service": "mdi:toggle-switch-variant"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"triggers": {
|
||||||
|
"turned_off": {
|
||||||
|
"trigger": "mdi:toggle-switch-variant-off"
|
||||||
|
},
|
||||||
|
"turned_on": {
|
||||||
|
"trigger": "mdi:toggle-switch-variant"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
{
|
{
|
||||||
|
"common": {
|
||||||
|
"trigger_behavior_description": "The behavior of the targeted switches to trigger on.",
|
||||||
|
"trigger_behavior_name": "Behavior"
|
||||||
|
},
|
||||||
"device_automation": {
|
"device_automation": {
|
||||||
"action_type": {
|
"action_type": {
|
||||||
"toggle": "[%key:common::device_automation::action_type::toggle%]",
|
"toggle": "[%key:common::device_automation::action_type::toggle%]",
|
||||||
@@ -41,6 +45,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"selector": {
|
||||||
|
"trigger_behavior": {
|
||||||
|
"options": {
|
||||||
|
"any": "Any",
|
||||||
|
"first": "First",
|
||||||
|
"last": "Last"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"toggle": {
|
"toggle": {
|
||||||
"description": "Toggles a switch on/off.",
|
"description": "Toggles a switch on/off.",
|
||||||
@@ -55,5 +68,27 @@
|
|||||||
"name": "[%key:common::action::turn_on%]"
|
"name": "[%key:common::action::turn_on%]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"title": "Switch"
|
"title": "Switch",
|
||||||
|
"triggers": {
|
||||||
|
"turned_off": {
|
||||||
|
"description": "Triggers after one or more switches turn off.",
|
||||||
|
"fields": {
|
||||||
|
"behavior": {
|
||||||
|
"description": "[%key:component::switch::common::trigger_behavior_description%]",
|
||||||
|
"name": "[%key:component::switch::common::trigger_behavior_name%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Switch turned off"
|
||||||
|
},
|
||||||
|
"turned_on": {
|
||||||
|
"description": "Triggers after one or more switches turn on.",
|
||||||
|
"fields": {
|
||||||
|
"behavior": {
|
||||||
|
"description": "[%key:component::switch::common::trigger_behavior_description%]",
|
||||||
|
"name": "[%key:component::switch::common::trigger_behavior_name%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": "Switch turned on"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
17
homeassistant/components/switch/trigger.py
Normal file
17
homeassistant/components/switch/trigger.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
"""Provides triggers for switch platform."""
|
||||||
|
|
||||||
|
from homeassistant.const import STATE_OFF, STATE_ON
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.trigger import Trigger, make_entity_state_trigger
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
|
TRIGGERS: dict[str, type[Trigger]] = {
|
||||||
|
"turned_on": make_entity_state_trigger(DOMAIN, STATE_ON),
|
||||||
|
"turned_off": make_entity_state_trigger(DOMAIN, STATE_OFF),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def async_get_triggers(hass: HomeAssistant) -> dict[str, type[Trigger]]:
|
||||||
|
"""Return the triggers for switch platform."""
|
||||||
|
return TRIGGERS
|
||||||
18
homeassistant/components/switch/triggers.yaml
Normal file
18
homeassistant/components/switch/triggers.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
.trigger_common: &trigger_common
|
||||||
|
target:
|
||||||
|
entity:
|
||||||
|
domain: switch
|
||||||
|
fields:
|
||||||
|
behavior:
|
||||||
|
required: true
|
||||||
|
default: any
|
||||||
|
selector:
|
||||||
|
select:
|
||||||
|
options:
|
||||||
|
- first
|
||||||
|
- last
|
||||||
|
- any
|
||||||
|
translation_key: trigger_behavior
|
||||||
|
|
||||||
|
turned_off: *trigger_common
|
||||||
|
turned_on: *trigger_common
|
||||||
@@ -147,16 +147,16 @@ class SwitchBotCloudBinarySensor(SwitchBotCloudEntity, BinarySensorEntity):
|
|||||||
self.entity_description = description
|
self.entity_description = description
|
||||||
self._attr_unique_id = f"{device.device_id}_{description.key}"
|
self._attr_unique_id = f"{device.device_id}_{description.key}"
|
||||||
|
|
||||||
@property
|
def _set_attributes(self) -> None:
|
||||||
def is_on(self) -> bool | None:
|
|
||||||
"""Set attributes from coordinator data."""
|
"""Set attributes from coordinator data."""
|
||||||
if not self.coordinator.data:
|
if not self.coordinator.data:
|
||||||
return None
|
return
|
||||||
|
|
||||||
if self.entity_description.value_fn:
|
if self.entity_description.value_fn:
|
||||||
return self.entity_description.value_fn(self.coordinator.data)
|
self._attr_is_on = self.entity_description.value_fn(self.coordinator.data)
|
||||||
|
return
|
||||||
|
|
||||||
return (
|
self._attr_is_on = (
|
||||||
self.coordinator.data.get(self.entity_description.key)
|
self.coordinator.data.get(self.entity_description.key)
|
||||||
== self.entity_description.on_value
|
== self.entity_description.on_value
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -23,11 +23,18 @@ from . import TuyaConfigEntry
|
|||||||
from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode
|
from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode
|
||||||
from .entity import TuyaEntity
|
from .entity import TuyaEntity
|
||||||
from .models import DPCodeBooleanWrapper, DPCodeEnumWrapper, DPCodeIntegerWrapper
|
from .models import DPCodeBooleanWrapper, DPCodeEnumWrapper, DPCodeIntegerWrapper
|
||||||
|
from .type_information import IntegerTypeInformation
|
||||||
|
from .util import RemapHelper
|
||||||
|
|
||||||
|
|
||||||
class _DPCodePercentageMappingWrapper(DPCodeIntegerWrapper):
|
class _DPCodePercentageMappingWrapper(DPCodeIntegerWrapper):
|
||||||
"""Wrapper for DPCode position values mapping to 0-100 range."""
|
"""Wrapper for DPCode position values mapping to 0-100 range."""
|
||||||
|
|
||||||
|
def __init__(self, dpcode: str, type_information: IntegerTypeInformation) -> None:
|
||||||
|
"""Init DPCodeIntegerWrapper."""
|
||||||
|
super().__init__(dpcode, type_information)
|
||||||
|
self._remap_helper = RemapHelper.from_type_information(type_information, 0, 100)
|
||||||
|
|
||||||
def _position_reversed(self, device: CustomerDevice) -> bool:
|
def _position_reversed(self, device: CustomerDevice) -> bool:
|
||||||
"""Check if the position and direction should be reversed."""
|
"""Check if the position and direction should be reversed."""
|
||||||
return False
|
return False
|
||||||
@@ -37,21 +44,15 @@ class _DPCodePercentageMappingWrapper(DPCodeIntegerWrapper):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
return round(
|
return round(
|
||||||
self.type_information.remap_value_to(
|
self._remap_helper.remap_value_to(
|
||||||
value,
|
value, reverse=self._position_reversed(device)
|
||||||
0,
|
|
||||||
100,
|
|
||||||
self._position_reversed(device),
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
def _convert_value_to_raw_value(self, device: CustomerDevice, value: Any) -> Any:
|
def _convert_value_to_raw_value(self, device: CustomerDevice, value: Any) -> Any:
|
||||||
return round(
|
return round(
|
||||||
self.type_information.remap_value_from(
|
self._remap_helper.remap_value_from(
|
||||||
value,
|
value, reverse=self._position_reversed(device)
|
||||||
0,
|
|
||||||
100,
|
|
||||||
self._position_reversed(device),
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ from . import TuyaConfigEntry
|
|||||||
from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode
|
from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode
|
||||||
from .entity import TuyaEntity
|
from .entity import TuyaEntity
|
||||||
from .models import DPCodeBooleanWrapper, DPCodeEnumWrapper, DPCodeIntegerWrapper
|
from .models import DPCodeBooleanWrapper, DPCodeEnumWrapper, DPCodeIntegerWrapper
|
||||||
from .util import get_dpcode
|
from .type_information import IntegerTypeInformation
|
||||||
|
from .util import RemapHelper, get_dpcode
|
||||||
|
|
||||||
_DIRECTION_DPCODES = (DPCode.FAN_DIRECTION,)
|
_DIRECTION_DPCODES = (DPCode.FAN_DIRECTION,)
|
||||||
_MODE_DPCODES = (DPCode.FAN_MODE, DPCode.MODE)
|
_MODE_DPCODES = (DPCode.FAN_MODE, DPCode.MODE)
|
||||||
@@ -94,6 +95,11 @@ class _FanSpeedEnumWrapper(DPCodeEnumWrapper):
|
|||||||
class _FanSpeedIntegerWrapper(DPCodeIntegerWrapper):
|
class _FanSpeedIntegerWrapper(DPCodeIntegerWrapper):
|
||||||
"""Wrapper for fan speed DP code (from an integer)."""
|
"""Wrapper for fan speed DP code (from an integer)."""
|
||||||
|
|
||||||
|
def __init__(self, dpcode: str, type_information: IntegerTypeInformation) -> None:
|
||||||
|
"""Init DPCodeIntegerWrapper."""
|
||||||
|
super().__init__(dpcode, type_information)
|
||||||
|
self._remap_helper = RemapHelper.from_type_information(type_information, 1, 100)
|
||||||
|
|
||||||
def get_speed_count(self) -> int:
|
def get_speed_count(self) -> int:
|
||||||
"""Get the number of speeds supported by the fan."""
|
"""Get the number of speeds supported by the fan."""
|
||||||
return 100
|
return 100
|
||||||
@@ -102,11 +108,11 @@ class _FanSpeedIntegerWrapper(DPCodeIntegerWrapper):
|
|||||||
"""Get the current speed as a percentage."""
|
"""Get the current speed as a percentage."""
|
||||||
if (value := super().read_device_status(device)) is None:
|
if (value := super().read_device_status(device)) is None:
|
||||||
return None
|
return None
|
||||||
return round(self.type_information.remap_value_to(value, 1, 100))
|
return round(self._remap_helper.remap_value_to(value))
|
||||||
|
|
||||||
def _convert_value_to_raw_value(self, device: CustomerDevice, value: Any) -> Any:
|
def _convert_value_to_raw_value(self, device: CustomerDevice, value: Any) -> Any:
|
||||||
"""Convert a Home Assistant value back to a raw device value."""
|
"""Convert a Home Assistant value back to a raw device value."""
|
||||||
return round(self.type_information.remap_value_from(value, 1, 100))
|
return round(self._remap_helper.remap_value_from(value))
|
||||||
|
|
||||||
|
|
||||||
def _get_speed_wrapper(
|
def _get_speed_wrapper(
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ from .models import (
|
|||||||
DPCodeJsonWrapper,
|
DPCodeJsonWrapper,
|
||||||
)
|
)
|
||||||
from .type_information import IntegerTypeInformation
|
from .type_information import IntegerTypeInformation
|
||||||
from .util import remap_value
|
from .util import RemapHelper
|
||||||
|
|
||||||
|
|
||||||
class _BrightnessWrapper(DPCodeIntegerWrapper):
|
class _BrightnessWrapper(DPCodeIntegerWrapper):
|
||||||
@@ -50,6 +50,13 @@ class _BrightnessWrapper(DPCodeIntegerWrapper):
|
|||||||
|
|
||||||
brightness_min: DPCodeIntegerWrapper | None = None
|
brightness_min: DPCodeIntegerWrapper | None = None
|
||||||
brightness_max: DPCodeIntegerWrapper | None = None
|
brightness_max: DPCodeIntegerWrapper | None = None
|
||||||
|
brightness_min_remap: RemapHelper | None = None
|
||||||
|
brightness_max_remap: RemapHelper | None = None
|
||||||
|
|
||||||
|
def __init__(self, dpcode: str, type_information: IntegerTypeInformation) -> None:
|
||||||
|
"""Init DPCodeIntegerWrapper."""
|
||||||
|
super().__init__(dpcode, type_information)
|
||||||
|
self._remap_helper = RemapHelper.from_type_information(type_information, 0, 255)
|
||||||
|
|
||||||
def read_device_status(self, device: CustomerDevice) -> Any | None:
|
def read_device_status(self, device: CustomerDevice) -> Any | None:
|
||||||
"""Return the brightness of this light between 0..255."""
|
"""Return the brightness of this light between 0..255."""
|
||||||
@@ -57,29 +64,31 @@ class _BrightnessWrapper(DPCodeIntegerWrapper):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
# Remap value to our scale
|
# Remap value to our scale
|
||||||
brightness = self.type_information.remap_value_to(brightness)
|
brightness = self._remap_helper.remap_value_to(brightness)
|
||||||
|
|
||||||
# If there is a min/max value, the brightness is actually limited.
|
# If there is a min/max value, the brightness is actually limited.
|
||||||
# Meaning it is actually not on a 0-255 scale.
|
# Meaning it is actually not on a 0-255 scale.
|
||||||
if (
|
if (
|
||||||
self.brightness_max is not None
|
self.brightness_max is not None
|
||||||
and self.brightness_min is not None
|
and self.brightness_min is not None
|
||||||
|
and self.brightness_max_remap is not None
|
||||||
|
and self.brightness_min_remap is not None
|
||||||
and (brightness_max := device.status.get(self.brightness_max.dpcode))
|
and (brightness_max := device.status.get(self.brightness_max.dpcode))
|
||||||
is not None
|
is not None
|
||||||
and (brightness_min := device.status.get(self.brightness_min.dpcode))
|
and (brightness_min := device.status.get(self.brightness_min.dpcode))
|
||||||
is not None
|
is not None
|
||||||
):
|
):
|
||||||
# Remap values onto our scale
|
# Remap values onto our scale
|
||||||
brightness_max = self.brightness_max.type_information.remap_value_to(
|
brightness_max = self.brightness_max_remap.remap_value_to(brightness_max)
|
||||||
brightness_max
|
brightness_min = self.brightness_min_remap.remap_value_to(brightness_min)
|
||||||
)
|
|
||||||
brightness_min = self.brightness_min.type_information.remap_value_to(
|
|
||||||
brightness_min
|
|
||||||
)
|
|
||||||
|
|
||||||
# Remap the brightness value from their min-max to our 0-255 scale
|
# Remap the brightness value from their min-max to our 0-255 scale
|
||||||
brightness = remap_value(
|
brightness = RemapHelper.remap_value(
|
||||||
brightness, from_min=brightness_min, from_max=brightness_max
|
brightness,
|
||||||
|
from_min=brightness_min,
|
||||||
|
from_max=brightness_max,
|
||||||
|
to_min=0,
|
||||||
|
to_max=255,
|
||||||
)
|
)
|
||||||
|
|
||||||
return round(brightness)
|
return round(brightness)
|
||||||
@@ -91,72 +100,69 @@ class _BrightnessWrapper(DPCodeIntegerWrapper):
|
|||||||
if (
|
if (
|
||||||
self.brightness_max is not None
|
self.brightness_max is not None
|
||||||
and self.brightness_min is not None
|
and self.brightness_min is not None
|
||||||
|
and self.brightness_max_remap is not None
|
||||||
|
and self.brightness_min_remap is not None
|
||||||
and (brightness_max := device.status.get(self.brightness_max.dpcode))
|
and (brightness_max := device.status.get(self.brightness_max.dpcode))
|
||||||
is not None
|
is not None
|
||||||
and (brightness_min := device.status.get(self.brightness_min.dpcode))
|
and (brightness_min := device.status.get(self.brightness_min.dpcode))
|
||||||
is not None
|
is not None
|
||||||
):
|
):
|
||||||
# Remap values onto our scale
|
# Remap values onto our scale
|
||||||
brightness_max = self.brightness_max.type_information.remap_value_to(
|
brightness_max = self.brightness_max_remap.remap_value_to(brightness_max)
|
||||||
brightness_max
|
brightness_min = self.brightness_min_remap.remap_value_to(brightness_min)
|
||||||
)
|
|
||||||
brightness_min = self.brightness_min.type_information.remap_value_to(
|
|
||||||
brightness_min
|
|
||||||
)
|
|
||||||
|
|
||||||
# Remap the brightness value from our 0-255 scale to their min-max
|
# Remap the brightness value from our 0-255 scale to their min-max
|
||||||
value = remap_value(value, to_min=brightness_min, to_max=brightness_max)
|
value = RemapHelper.remap_value(
|
||||||
return round(self.type_information.remap_value_from(value))
|
value,
|
||||||
|
from_min=0,
|
||||||
|
from_max=255,
|
||||||
|
to_min=brightness_min,
|
||||||
|
to_max=brightness_max,
|
||||||
|
)
|
||||||
|
return round(self._remap_helper.remap_value_from(value))
|
||||||
|
|
||||||
|
|
||||||
class _ColorTempWrapper(DPCodeIntegerWrapper):
|
class _ColorTempWrapper(DPCodeIntegerWrapper):
|
||||||
"""Wrapper for color temperature DP code."""
|
"""Wrapper for color temperature DP code."""
|
||||||
|
|
||||||
|
def __init__(self, dpcode: str, type_information: IntegerTypeInformation) -> None:
|
||||||
|
"""Init DPCodeIntegerWrapper."""
|
||||||
|
super().__init__(dpcode, type_information)
|
||||||
|
self._remap_helper = RemapHelper.from_type_information(
|
||||||
|
type_information, MIN_MIREDS, MAX_MIREDS
|
||||||
|
)
|
||||||
|
|
||||||
def read_device_status(self, device: CustomerDevice) -> Any | None:
|
def read_device_status(self, device: CustomerDevice) -> Any | None:
|
||||||
"""Return the color temperature value in Kelvin."""
|
"""Return the color temperature value in Kelvin."""
|
||||||
if (temperature := device.status.get(self.dpcode)) is None:
|
if (temperature := device.status.get(self.dpcode)) is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return color_util.color_temperature_mired_to_kelvin(
|
return color_util.color_temperature_mired_to_kelvin(
|
||||||
self.type_information.remap_value_to(
|
self._remap_helper.remap_value_to(temperature, reverse=True)
|
||||||
temperature,
|
|
||||||
MIN_MIREDS,
|
|
||||||
MAX_MIREDS,
|
|
||||||
reverse=True,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def _convert_value_to_raw_value(self, device: CustomerDevice, value: Any) -> Any:
|
def _convert_value_to_raw_value(self, device: CustomerDevice, value: Any) -> Any:
|
||||||
"""Convert a Home Assistant value (Kelvin) back to a raw device value."""
|
"""Convert a Home Assistant value (Kelvin) back to a raw device value."""
|
||||||
return round(
|
return round(
|
||||||
self.type_information.remap_value_from(
|
self._remap_helper.remap_value_from(
|
||||||
color_util.color_temperature_kelvin_to_mired(value),
|
color_util.color_temperature_kelvin_to_mired(value), reverse=True
|
||||||
MIN_MIREDS,
|
|
||||||
MAX_MIREDS,
|
|
||||||
reverse=True,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_H_TYPE = IntegerTypeInformation(
|
DEFAULT_H_TYPE = RemapHelper(source_min=1, source_max=360, target_min=0, target_max=360)
|
||||||
dpcode=DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=360, step=1
|
DEFAULT_S_TYPE = RemapHelper(source_min=1, source_max=255, target_min=0, target_max=100)
|
||||||
)
|
DEFAULT_V_TYPE = RemapHelper(source_min=1, source_max=255, target_min=0, target_max=255)
|
||||||
DEFAULT_S_TYPE = IntegerTypeInformation(
|
|
||||||
dpcode=DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=255, step=1
|
|
||||||
)
|
|
||||||
DEFAULT_V_TYPE = IntegerTypeInformation(
|
|
||||||
dpcode=DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=255, step=1
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_H_TYPE_V2 = IntegerTypeInformation(
|
DEFAULT_H_TYPE_V2 = RemapHelper(
|
||||||
dpcode=DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=360, step=1
|
source_min=1, source_max=360, target_min=0, target_max=360
|
||||||
)
|
)
|
||||||
DEFAULT_S_TYPE_V2 = IntegerTypeInformation(
|
DEFAULT_S_TYPE_V2 = RemapHelper(
|
||||||
dpcode=DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=1000, step=1
|
source_min=1, source_max=1000, target_min=0, target_max=100
|
||||||
)
|
)
|
||||||
DEFAULT_V_TYPE_V2 = IntegerTypeInformation(
|
DEFAULT_V_TYPE_V2 = RemapHelper(
|
||||||
dpcode=DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=1000, step=1
|
source_min=1, source_max=1000, target_min=0, target_max=255
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -172,23 +178,23 @@ class _ColorDataWrapper(DPCodeJsonWrapper):
|
|||||||
if (status := self.read_device_status(device)) is None:
|
if (status := self.read_device_status(device)) is None:
|
||||||
return None
|
return None
|
||||||
return (
|
return (
|
||||||
self.h_type.remap_value_to(cast(int, status["h"]), 0, 360),
|
self.h_type.remap_value_to(status["h"]),
|
||||||
self.s_type.remap_value_to(cast(int, status["s"]), 0, 100),
|
self.s_type.remap_value_to(status["s"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
def read_brightness(self, device: CustomerDevice) -> int | None:
|
def read_brightness(self, device: CustomerDevice) -> int | None:
|
||||||
"""Get the brightness value from this color data."""
|
"""Get the brightness value from this color data."""
|
||||||
if (status := self.read_device_status(device)) is None:
|
if (status := self.read_device_status(device)) is None:
|
||||||
return None
|
return None
|
||||||
return round(self.v_type.remap_value_to(cast(int, status["v"]), 0, 255))
|
return round(self.v_type.remap_value_to(status["v"]))
|
||||||
|
|
||||||
def _convert_value_to_raw_value(self, device: CustomerDevice, value: Any) -> Any:
|
def _convert_value_to_raw_value(self, device: CustomerDevice, value: Any) -> Any:
|
||||||
"""Convert a Home Assistant color/brightness pair back to a raw device value."""
|
"""Convert a Home Assistant color/brightness pair back to a raw device value."""
|
||||||
color, brightness = value
|
color, brightness = value
|
||||||
return json.dumps(
|
return json.dumps(
|
||||||
{
|
{
|
||||||
"h": round(self.h_type.remap_value_from(color[0], 0, 360)),
|
"h": round(self.h_type.remap_value_from(color[0])),
|
||||||
"s": round(self.s_type.remap_value_from(color[1], 0, 100)),
|
"s": round(self.s_type.remap_value_from(color[1])),
|
||||||
"v": round(self.v_type.remap_value_from(brightness)),
|
"v": round(self.v_type.remap_value_from(brightness)),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -545,12 +551,20 @@ def _get_brightness_wrapper(
|
|||||||
)
|
)
|
||||||
) is None:
|
) is None:
|
||||||
return None
|
return None
|
||||||
brightness_wrapper.brightness_max = DPCodeIntegerWrapper.find_dpcode(
|
if brightness_max := DPCodeIntegerWrapper.find_dpcode(
|
||||||
device, description.brightness_max, prefer_function=True
|
device, description.brightness_max, prefer_function=True
|
||||||
)
|
):
|
||||||
brightness_wrapper.brightness_min = DPCodeIntegerWrapper.find_dpcode(
|
brightness_wrapper.brightness_max = brightness_max
|
||||||
|
brightness_wrapper.brightness_max_remap = RemapHelper.from_type_information(
|
||||||
|
brightness_max.type_information, 0, 255
|
||||||
|
)
|
||||||
|
if brightness_min := DPCodeIntegerWrapper.find_dpcode(
|
||||||
device, description.brightness_min, prefer_function=True
|
device, description.brightness_min, prefer_function=True
|
||||||
)
|
):
|
||||||
|
brightness_wrapper.brightness_min = brightness_min
|
||||||
|
brightness_wrapper.brightness_min_remap = RemapHelper.from_type_information(
|
||||||
|
brightness_min.type_information, 0, 255
|
||||||
|
)
|
||||||
return brightness_wrapper
|
return brightness_wrapper
|
||||||
|
|
||||||
|
|
||||||
@@ -568,19 +582,16 @@ def _get_color_data_wrapper(
|
|||||||
|
|
||||||
# Fetch color data type information
|
# Fetch color data type information
|
||||||
if function_data := json_loads_object(
|
if function_data := json_loads_object(
|
||||||
cast(str, color_data_wrapper.type_information.type_data)
|
color_data_wrapper.type_information.type_data
|
||||||
):
|
):
|
||||||
color_data_wrapper.h_type = IntegerTypeInformation(
|
color_data_wrapper.h_type = RemapHelper.from_function_data(
|
||||||
dpcode=color_data_wrapper.dpcode,
|
cast(dict, function_data["h"]), 0, 360
|
||||||
**cast(dict, function_data["h"]),
|
|
||||||
)
|
)
|
||||||
color_data_wrapper.s_type = IntegerTypeInformation(
|
color_data_wrapper.s_type = RemapHelper.from_function_data(
|
||||||
dpcode=color_data_wrapper.dpcode,
|
cast(dict, function_data["s"]), 0, 100
|
||||||
**cast(dict, function_data["s"]),
|
|
||||||
)
|
)
|
||||||
color_data_wrapper.v_type = IntegerTypeInformation(
|
color_data_wrapper.v_type = RemapHelper.from_function_data(
|
||||||
dpcode=color_data_wrapper.dpcode,
|
cast(dict, function_data["v"]), 0, 255
|
||||||
**cast(dict, function_data["v"]),
|
|
||||||
)
|
)
|
||||||
elif (
|
elif (
|
||||||
description.fallback_color_data_mode == FallbackColorDataMode.V2
|
description.fallback_color_data_mode == FallbackColorDataMode.V2
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from tuya_sharing import CustomerDevice
|
|||||||
from homeassistant.util.json import json_loads_object
|
from homeassistant.util.json import json_loads_object
|
||||||
|
|
||||||
from .const import LOGGER, DPType
|
from .const import LOGGER, DPType
|
||||||
from .util import parse_dptype, remap_value
|
from .util import parse_dptype
|
||||||
|
|
||||||
# Dictionary to track logged warnings to avoid spamming logs
|
# Dictionary to track logged warnings to avoid spamming logs
|
||||||
# Keyed by device ID
|
# Keyed by device ID
|
||||||
@@ -41,7 +41,7 @@ class TypeInformation[T]:
|
|||||||
|
|
||||||
_DPTYPE: ClassVar[DPType]
|
_DPTYPE: ClassVar[DPType]
|
||||||
dpcode: str
|
dpcode: str
|
||||||
type_data: str | None = None
|
type_data: str
|
||||||
|
|
||||||
def process_raw_value(
|
def process_raw_value(
|
||||||
self, raw_value: Any | None, device: CustomerDevice
|
self, raw_value: Any | None, device: CustomerDevice
|
||||||
@@ -223,26 +223,6 @@ class IntegerTypeInformation(TypeInformation[float]):
|
|||||||
"""Return raw value for scaled."""
|
"""Return raw value for scaled."""
|
||||||
return round(value * (10**self.scale))
|
return round(value * (10**self.scale))
|
||||||
|
|
||||||
def remap_value_to(
|
|
||||||
self,
|
|
||||||
value: float,
|
|
||||||
to_min: float = 0,
|
|
||||||
to_max: float = 255,
|
|
||||||
reverse: bool = False,
|
|
||||||
) -> float:
|
|
||||||
"""Remap a value from this range to a new range."""
|
|
||||||
return remap_value(value, self.min, self.max, to_min, to_max, reverse)
|
|
||||||
|
|
||||||
def remap_value_from(
|
|
||||||
self,
|
|
||||||
value: float,
|
|
||||||
from_min: float = 0,
|
|
||||||
from_max: float = 255,
|
|
||||||
reverse: bool = False,
|
|
||||||
) -> float:
|
|
||||||
"""Remap a value from its current range to this range."""
|
|
||||||
return remap_value(value, from_min, from_max, self.min, self.max, reverse)
|
|
||||||
|
|
||||||
def process_raw_value(
|
def process_raw_value(
|
||||||
self, raw_value: Any | None, device: CustomerDevice
|
self, raw_value: Any | None, device: CustomerDevice
|
||||||
) -> float | None:
|
) -> float | None:
|
||||||
|
|||||||
@@ -2,12 +2,18 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from tuya_sharing import CustomerDevice
|
from tuya_sharing import CustomerDevice
|
||||||
|
|
||||||
from homeassistant.exceptions import ServiceValidationError
|
from homeassistant.exceptions import ServiceValidationError
|
||||||
|
|
||||||
from .const import DOMAIN, DPCode, DPType
|
from .const import DOMAIN, DPCode, DPType
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .type_information import IntegerTypeInformation
|
||||||
|
|
||||||
_DPTYPE_MAPPING: dict[str, DPType] = {
|
_DPTYPE_MAPPING: dict[str, DPType] = {
|
||||||
"bitmap": DPType.BITMAP,
|
"bitmap": DPType.BITMAP,
|
||||||
"bool": DPType.BOOLEAN,
|
"bool": DPType.BOOLEAN,
|
||||||
@@ -50,18 +56,78 @@ def parse_dptype(dptype: str) -> DPType | None:
|
|||||||
return _DPTYPE_MAPPING.get(dptype)
|
return _DPTYPE_MAPPING.get(dptype)
|
||||||
|
|
||||||
|
|
||||||
def remap_value(
|
@dataclass(kw_only=True)
|
||||||
value: float,
|
class RemapHelper:
|
||||||
from_min: float = 0,
|
"""Helper class for remapping values."""
|
||||||
from_max: float = 255,
|
|
||||||
to_min: float = 0,
|
source_min: int
|
||||||
to_max: float = 255,
|
source_max: int
|
||||||
reverse: bool = False,
|
target_min: int
|
||||||
) -> float:
|
target_max: int
|
||||||
"""Remap a value from its current range, to a new range."""
|
|
||||||
if reverse:
|
@classmethod
|
||||||
value = from_max - value + from_min
|
def from_type_information(
|
||||||
return ((value - from_min) / (from_max - from_min)) * (to_max - to_min) + to_min
|
cls,
|
||||||
|
type_information: IntegerTypeInformation,
|
||||||
|
target_min: int,
|
||||||
|
target_max: int,
|
||||||
|
) -> RemapHelper:
|
||||||
|
"""Create RemapHelper from IntegerTypeInformation."""
|
||||||
|
return cls(
|
||||||
|
source_min=type_information.min,
|
||||||
|
source_max=type_information.max,
|
||||||
|
target_min=target_min,
|
||||||
|
target_max=target_max,
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_function_data(
|
||||||
|
cls, function_data: dict[str, Any], target_min: int, target_max: int
|
||||||
|
) -> RemapHelper:
|
||||||
|
"""Create RemapHelper from function_data."""
|
||||||
|
return cls(
|
||||||
|
source_min=function_data["min"],
|
||||||
|
source_max=function_data["max"],
|
||||||
|
target_min=target_min,
|
||||||
|
target_max=target_max,
|
||||||
|
)
|
||||||
|
|
||||||
|
def remap_value_to(self, value: float, *, reverse: bool = False) -> float:
|
||||||
|
"""Remap a value from this range to a new range."""
|
||||||
|
return self.remap_value(
|
||||||
|
value,
|
||||||
|
self.source_min,
|
||||||
|
self.source_max,
|
||||||
|
self.target_min,
|
||||||
|
self.target_max,
|
||||||
|
reverse=reverse,
|
||||||
|
)
|
||||||
|
|
||||||
|
def remap_value_from(self, value: float, *, reverse: bool = False) -> float:
|
||||||
|
"""Remap a value from its current range to this range."""
|
||||||
|
return self.remap_value(
|
||||||
|
value,
|
||||||
|
self.target_min,
|
||||||
|
self.target_max,
|
||||||
|
self.source_min,
|
||||||
|
self.source_max,
|
||||||
|
reverse=reverse,
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def remap_value(
|
||||||
|
value: float,
|
||||||
|
from_min: float,
|
||||||
|
from_max: float,
|
||||||
|
to_min: float,
|
||||||
|
to_max: float,
|
||||||
|
*,
|
||||||
|
reverse: bool = False,
|
||||||
|
) -> float:
|
||||||
|
"""Remap a value from its current range, to a new range."""
|
||||||
|
if reverse:
|
||||||
|
value = from_max - value + from_min
|
||||||
|
return ((value - from_min) / (from_max - from_min)) * (to_max - to_min) + to_min
|
||||||
|
|
||||||
|
|
||||||
class ActionDPCodeNotFoundError(ServiceValidationError):
|
class ActionDPCodeNotFoundError(ServiceValidationError):
|
||||||
|
|||||||
@@ -67,13 +67,11 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="dark",
|
key="dark",
|
||||||
translation_key="is_dark",
|
translation_key="is_dark",
|
||||||
icon="mdi:brightness-6",
|
|
||||||
ufp_value="is_dark",
|
ufp_value="is_dark",
|
||||||
),
|
),
|
||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="ssh",
|
key="ssh",
|
||||||
translation_key="ssh_enabled",
|
translation_key="ssh_enabled",
|
||||||
icon="mdi:lock",
|
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_value="is_ssh_enabled",
|
ufp_value="is_ssh_enabled",
|
||||||
@@ -82,7 +80,6 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="status_light",
|
key="status_light",
|
||||||
translation_key="status_light",
|
translation_key="status_light",
|
||||||
icon="mdi:led-on",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_required_field="feature_flags.has_led_status",
|
ufp_required_field="feature_flags.has_led_status",
|
||||||
ufp_value="led_settings.is_enabled",
|
ufp_value="led_settings.is_enabled",
|
||||||
@@ -91,7 +88,6 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="hdr_mode",
|
key="hdr_mode",
|
||||||
translation_key="hdr_mode",
|
translation_key="hdr_mode",
|
||||||
icon="mdi:brightness-7",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_required_field="feature_flags.has_hdr",
|
ufp_required_field="feature_flags.has_hdr",
|
||||||
ufp_value="hdr_mode",
|
ufp_value="hdr_mode",
|
||||||
@@ -100,7 +96,6 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="high_fps",
|
key="high_fps",
|
||||||
translation_key="high_fps",
|
translation_key="high_fps",
|
||||||
icon="mdi:video-high-definition",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_required_field="feature_flags.has_highfps",
|
ufp_required_field="feature_flags.has_highfps",
|
||||||
ufp_value="is_high_fps_enabled",
|
ufp_value="is_high_fps_enabled",
|
||||||
@@ -109,7 +104,6 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="system_sounds",
|
key="system_sounds",
|
||||||
translation_key="system_sounds",
|
translation_key="system_sounds",
|
||||||
icon="mdi:speaker",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_required_field="has_speaker",
|
ufp_required_field="has_speaker",
|
||||||
ufp_value="speaker_settings.are_system_sounds_enabled",
|
ufp_value="speaker_settings.are_system_sounds_enabled",
|
||||||
@@ -119,7 +113,6 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="osd_name",
|
key="osd_name",
|
||||||
translation_key="overlay_show_name",
|
translation_key="overlay_show_name",
|
||||||
icon="mdi:fullscreen",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_value="osd_settings.is_name_enabled",
|
ufp_value="osd_settings.is_name_enabled",
|
||||||
ufp_perm=PermRequired.NO_WRITE,
|
ufp_perm=PermRequired.NO_WRITE,
|
||||||
@@ -127,7 +120,6 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="osd_date",
|
key="osd_date",
|
||||||
translation_key="overlay_show_date",
|
translation_key="overlay_show_date",
|
||||||
icon="mdi:fullscreen",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_value="osd_settings.is_date_enabled",
|
ufp_value="osd_settings.is_date_enabled",
|
||||||
ufp_perm=PermRequired.NO_WRITE,
|
ufp_perm=PermRequired.NO_WRITE,
|
||||||
@@ -135,7 +127,6 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="osd_logo",
|
key="osd_logo",
|
||||||
translation_key="overlay_show_logo",
|
translation_key="overlay_show_logo",
|
||||||
icon="mdi:fullscreen",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_value="osd_settings.is_logo_enabled",
|
ufp_value="osd_settings.is_logo_enabled",
|
||||||
ufp_perm=PermRequired.NO_WRITE,
|
ufp_perm=PermRequired.NO_WRITE,
|
||||||
@@ -143,7 +134,6 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="osd_bitrate",
|
key="osd_bitrate",
|
||||||
translation_key="overlay_show_nerd_mode",
|
translation_key="overlay_show_nerd_mode",
|
||||||
icon="mdi:fullscreen",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_value="osd_settings.is_debug_enabled",
|
ufp_value="osd_settings.is_debug_enabled",
|
||||||
ufp_perm=PermRequired.NO_WRITE,
|
ufp_perm=PermRequired.NO_WRITE,
|
||||||
@@ -151,14 +141,12 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="motion_enabled",
|
key="motion_enabled",
|
||||||
translation_key="detections_motion",
|
translation_key="detections_motion",
|
||||||
icon="mdi:run-fast",
|
|
||||||
ufp_value="recording_settings.enable_motion_detection",
|
ufp_value="recording_settings.enable_motion_detection",
|
||||||
ufp_perm=PermRequired.NO_WRITE,
|
ufp_perm=PermRequired.NO_WRITE,
|
||||||
),
|
),
|
||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="smart_person",
|
key="smart_person",
|
||||||
translation_key="detections_person",
|
translation_key="detections_person",
|
||||||
icon="mdi:walk",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_required_field="can_detect_person",
|
ufp_required_field="can_detect_person",
|
||||||
ufp_value="is_person_detection_on",
|
ufp_value="is_person_detection_on",
|
||||||
@@ -167,7 +155,6 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="smart_vehicle",
|
key="smart_vehicle",
|
||||||
translation_key="detections_vehicle",
|
translation_key="detections_vehicle",
|
||||||
icon="mdi:car",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_required_field="can_detect_vehicle",
|
ufp_required_field="can_detect_vehicle",
|
||||||
ufp_value="is_vehicle_detection_on",
|
ufp_value="is_vehicle_detection_on",
|
||||||
@@ -176,7 +163,6 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="smart_animal",
|
key="smart_animal",
|
||||||
translation_key="detections_animal",
|
translation_key="detections_animal",
|
||||||
icon="mdi:paw",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_required_field="can_detect_animal",
|
ufp_required_field="can_detect_animal",
|
||||||
ufp_value="is_animal_detection_on",
|
ufp_value="is_animal_detection_on",
|
||||||
@@ -185,7 +171,6 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="smart_package",
|
key="smart_package",
|
||||||
translation_key="detections_package",
|
translation_key="detections_package",
|
||||||
icon="mdi:package-variant-closed",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_required_field="can_detect_package",
|
ufp_required_field="can_detect_package",
|
||||||
ufp_value="is_package_detection_on",
|
ufp_value="is_package_detection_on",
|
||||||
@@ -194,7 +179,6 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="smart_licenseplate",
|
key="smart_licenseplate",
|
||||||
translation_key="detections_license_plate",
|
translation_key="detections_license_plate",
|
||||||
icon="mdi:car",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_required_field="can_detect_license_plate",
|
ufp_required_field="can_detect_license_plate",
|
||||||
ufp_value="is_license_plate_detection_on",
|
ufp_value="is_license_plate_detection_on",
|
||||||
@@ -203,7 +187,6 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="smart_smoke",
|
key="smart_smoke",
|
||||||
translation_key="detections_smoke",
|
translation_key="detections_smoke",
|
||||||
icon="mdi:fire",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_required_field="can_detect_smoke",
|
ufp_required_field="can_detect_smoke",
|
||||||
ufp_value="is_smoke_detection_on",
|
ufp_value="is_smoke_detection_on",
|
||||||
@@ -212,7 +195,6 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="smart_cmonx",
|
key="smart_cmonx",
|
||||||
translation_key="detections_co_alarm",
|
translation_key="detections_co_alarm",
|
||||||
icon="mdi:molecule-co",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_required_field="can_detect_co",
|
ufp_required_field="can_detect_co",
|
||||||
ufp_value="is_co_detection_on",
|
ufp_value="is_co_detection_on",
|
||||||
@@ -221,7 +203,6 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="smart_siren",
|
key="smart_siren",
|
||||||
translation_key="detections_siren",
|
translation_key="detections_siren",
|
||||||
icon="mdi:alarm-bell",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_required_field="can_detect_siren",
|
ufp_required_field="can_detect_siren",
|
||||||
ufp_value="is_siren_detection_on",
|
ufp_value="is_siren_detection_on",
|
||||||
@@ -230,7 +211,6 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="smart_baby_cry",
|
key="smart_baby_cry",
|
||||||
translation_key="detections_baby_cry",
|
translation_key="detections_baby_cry",
|
||||||
icon="mdi:cradle",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_required_field="can_detect_baby_cry",
|
ufp_required_field="can_detect_baby_cry",
|
||||||
ufp_value="is_baby_cry_detection_on",
|
ufp_value="is_baby_cry_detection_on",
|
||||||
@@ -239,7 +219,6 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="smart_speak",
|
key="smart_speak",
|
||||||
translation_key="detections_speaking",
|
translation_key="detections_speaking",
|
||||||
icon="mdi:account-voice",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_required_field="can_detect_speaking",
|
ufp_required_field="can_detect_speaking",
|
||||||
ufp_value="is_speaking_detection_on",
|
ufp_value="is_speaking_detection_on",
|
||||||
@@ -248,7 +227,6 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="smart_bark",
|
key="smart_bark",
|
||||||
translation_key="detections_barking",
|
translation_key="detections_barking",
|
||||||
icon="mdi:dog",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_required_field="can_detect_bark",
|
ufp_required_field="can_detect_bark",
|
||||||
ufp_value="is_bark_detection_on",
|
ufp_value="is_bark_detection_on",
|
||||||
@@ -257,7 +235,6 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="smart_car_alarm",
|
key="smart_car_alarm",
|
||||||
translation_key="detections_car_alarm",
|
translation_key="detections_car_alarm",
|
||||||
icon="mdi:car",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_required_field="can_detect_car_alarm",
|
ufp_required_field="can_detect_car_alarm",
|
||||||
ufp_value="is_car_alarm_detection_on",
|
ufp_value="is_car_alarm_detection_on",
|
||||||
@@ -266,7 +243,6 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="smart_car_horn",
|
key="smart_car_horn",
|
||||||
translation_key="detections_car_horn",
|
translation_key="detections_car_horn",
|
||||||
icon="mdi:bugle",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_required_field="can_detect_car_horn",
|
ufp_required_field="can_detect_car_horn",
|
||||||
ufp_value="is_car_horn_detection_on",
|
ufp_value="is_car_horn_detection_on",
|
||||||
@@ -275,7 +251,6 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="smart_glass_break",
|
key="smart_glass_break",
|
||||||
translation_key="detections_glass_break",
|
translation_key="detections_glass_break",
|
||||||
icon="mdi:glass-fragile",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_required_field="can_detect_glass_break",
|
ufp_required_field="can_detect_glass_break",
|
||||||
ufp_value="is_glass_break_detection_on",
|
ufp_value="is_glass_break_detection_on",
|
||||||
@@ -284,7 +259,6 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="track_person",
|
key="track_person",
|
||||||
translation_key="tracking_person",
|
translation_key="tracking_person",
|
||||||
icon="mdi:walk",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_required_field="feature_flags.is_ptz",
|
ufp_required_field="feature_flags.is_ptz",
|
||||||
ufp_value="is_person_tracking_enabled",
|
ufp_value="is_person_tracking_enabled",
|
||||||
@@ -296,7 +270,6 @@ LIGHT_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="dark",
|
key="dark",
|
||||||
translation_key="is_dark",
|
translation_key="is_dark",
|
||||||
icon="mdi:brightness-6",
|
|
||||||
ufp_value="is_dark",
|
ufp_value="is_dark",
|
||||||
),
|
),
|
||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
@@ -307,7 +280,6 @@ LIGHT_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="light",
|
key="light",
|
||||||
translation_key="flood_light",
|
translation_key="flood_light",
|
||||||
icon="mdi:spotlight-beam",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_value="is_light_on",
|
ufp_value="is_light_on",
|
||||||
ufp_perm=PermRequired.NO_WRITE,
|
ufp_perm=PermRequired.NO_WRITE,
|
||||||
@@ -315,7 +287,6 @@ LIGHT_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="ssh",
|
key="ssh",
|
||||||
translation_key="ssh_enabled",
|
translation_key="ssh_enabled",
|
||||||
icon="mdi:lock",
|
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_value="is_ssh_enabled",
|
ufp_value="is_ssh_enabled",
|
||||||
@@ -324,7 +295,6 @@ LIGHT_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="status_light",
|
key="status_light",
|
||||||
translation_key="status_light",
|
translation_key="status_light",
|
||||||
icon="mdi:led-on",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_value="light_device_settings.is_indicator_enabled",
|
ufp_value="light_device_settings.is_indicator_enabled",
|
||||||
ufp_perm=PermRequired.NO_WRITE,
|
ufp_perm=PermRequired.NO_WRITE,
|
||||||
@@ -370,7 +340,6 @@ SENSE_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="status_light",
|
key="status_light",
|
||||||
translation_key="status_light",
|
translation_key="status_light",
|
||||||
icon="mdi:led-on",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_value="led_settings.is_enabled",
|
ufp_value="led_settings.is_enabled",
|
||||||
ufp_perm=PermRequired.NO_WRITE,
|
ufp_perm=PermRequired.NO_WRITE,
|
||||||
@@ -378,7 +347,6 @@ SENSE_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="motion_enabled",
|
key="motion_enabled",
|
||||||
translation_key="detections_motion",
|
translation_key="detections_motion",
|
||||||
icon="mdi:walk",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_value="motion_settings.is_enabled",
|
ufp_value="motion_settings.is_enabled",
|
||||||
ufp_perm=PermRequired.NO_WRITE,
|
ufp_perm=PermRequired.NO_WRITE,
|
||||||
@@ -386,7 +354,6 @@ SENSE_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="temperature",
|
key="temperature",
|
||||||
translation_key="temperature_sensor",
|
translation_key="temperature_sensor",
|
||||||
icon="mdi:thermometer",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_value="temperature_settings.is_enabled",
|
ufp_value="temperature_settings.is_enabled",
|
||||||
ufp_perm=PermRequired.NO_WRITE,
|
ufp_perm=PermRequired.NO_WRITE,
|
||||||
@@ -394,7 +361,6 @@ SENSE_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="humidity",
|
key="humidity",
|
||||||
translation_key="humidity_sensor",
|
translation_key="humidity_sensor",
|
||||||
icon="mdi:water-percent",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_value="humidity_settings.is_enabled",
|
ufp_value="humidity_settings.is_enabled",
|
||||||
ufp_perm=PermRequired.NO_WRITE,
|
ufp_perm=PermRequired.NO_WRITE,
|
||||||
@@ -402,7 +368,6 @@ SENSE_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="light",
|
key="light",
|
||||||
translation_key="light_sensor",
|
translation_key="light_sensor",
|
||||||
icon="mdi:brightness-5",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_value="light_settings.is_enabled",
|
ufp_value="light_settings.is_enabled",
|
||||||
ufp_perm=PermRequired.NO_WRITE,
|
ufp_perm=PermRequired.NO_WRITE,
|
||||||
@@ -421,7 +386,6 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
|
|||||||
key="doorbell",
|
key="doorbell",
|
||||||
translation_key="doorbell",
|
translation_key="doorbell",
|
||||||
device_class=BinarySensorDeviceClass.OCCUPANCY,
|
device_class=BinarySensorDeviceClass.OCCUPANCY,
|
||||||
icon="mdi:doorbell-video",
|
|
||||||
ufp_required_field="feature_flags.is_doorbell",
|
ufp_required_field="feature_flags.is_doorbell",
|
||||||
ufp_event_obj="last_ring_event",
|
ufp_event_obj="last_ring_event",
|
||||||
),
|
),
|
||||||
@@ -434,7 +398,6 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
|
|||||||
ProtectBinaryEventEntityDescription(
|
ProtectBinaryEventEntityDescription(
|
||||||
key="smart_obj_any",
|
key="smart_obj_any",
|
||||||
translation_key="object_detected",
|
translation_key="object_detected",
|
||||||
icon="mdi:eye",
|
|
||||||
ufp_required_field="feature_flags.has_smart_detect",
|
ufp_required_field="feature_flags.has_smart_detect",
|
||||||
ufp_event_obj="last_smart_detect_event",
|
ufp_event_obj="last_smart_detect_event",
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
@@ -442,7 +405,6 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
|
|||||||
ProtectBinaryEventEntityDescription(
|
ProtectBinaryEventEntityDescription(
|
||||||
key="smart_obj_person",
|
key="smart_obj_person",
|
||||||
translation_key="person_detected",
|
translation_key="person_detected",
|
||||||
icon="mdi:walk",
|
|
||||||
ufp_obj_type=SmartDetectObjectType.PERSON,
|
ufp_obj_type=SmartDetectObjectType.PERSON,
|
||||||
ufp_required_field="can_detect_person",
|
ufp_required_field="can_detect_person",
|
||||||
ufp_enabled="is_person_detection_on",
|
ufp_enabled="is_person_detection_on",
|
||||||
@@ -451,7 +413,6 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
|
|||||||
ProtectBinaryEventEntityDescription(
|
ProtectBinaryEventEntityDescription(
|
||||||
key="smart_obj_vehicle",
|
key="smart_obj_vehicle",
|
||||||
translation_key="vehicle_detected",
|
translation_key="vehicle_detected",
|
||||||
icon="mdi:car",
|
|
||||||
ufp_obj_type=SmartDetectObjectType.VEHICLE,
|
ufp_obj_type=SmartDetectObjectType.VEHICLE,
|
||||||
ufp_required_field="can_detect_vehicle",
|
ufp_required_field="can_detect_vehicle",
|
||||||
ufp_enabled="is_vehicle_detection_on",
|
ufp_enabled="is_vehicle_detection_on",
|
||||||
@@ -460,7 +421,6 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
|
|||||||
ProtectBinaryEventEntityDescription(
|
ProtectBinaryEventEntityDescription(
|
||||||
key="smart_obj_animal",
|
key="smart_obj_animal",
|
||||||
translation_key="animal_detected",
|
translation_key="animal_detected",
|
||||||
icon="mdi:paw",
|
|
||||||
ufp_obj_type=SmartDetectObjectType.ANIMAL,
|
ufp_obj_type=SmartDetectObjectType.ANIMAL,
|
||||||
ufp_required_field="can_detect_animal",
|
ufp_required_field="can_detect_animal",
|
||||||
ufp_enabled="is_animal_detection_on",
|
ufp_enabled="is_animal_detection_on",
|
||||||
@@ -469,7 +429,6 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
|
|||||||
ProtectBinaryEventEntityDescription(
|
ProtectBinaryEventEntityDescription(
|
||||||
key="smart_obj_package",
|
key="smart_obj_package",
|
||||||
translation_key="package_detected",
|
translation_key="package_detected",
|
||||||
icon="mdi:package-variant-closed",
|
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
ufp_obj_type=SmartDetectObjectType.PACKAGE,
|
ufp_obj_type=SmartDetectObjectType.PACKAGE,
|
||||||
ufp_required_field="can_detect_package",
|
ufp_required_field="can_detect_package",
|
||||||
@@ -479,7 +438,6 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
|
|||||||
ProtectBinaryEventEntityDescription(
|
ProtectBinaryEventEntityDescription(
|
||||||
key="smart_audio_any",
|
key="smart_audio_any",
|
||||||
translation_key="audio_object_detected",
|
translation_key="audio_object_detected",
|
||||||
icon="mdi:eye",
|
|
||||||
ufp_required_field="feature_flags.has_smart_detect",
|
ufp_required_field="feature_flags.has_smart_detect",
|
||||||
ufp_event_obj="last_smart_audio_detect_event",
|
ufp_event_obj="last_smart_audio_detect_event",
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
@@ -487,7 +445,6 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
|
|||||||
ProtectBinaryEventEntityDescription(
|
ProtectBinaryEventEntityDescription(
|
||||||
key="smart_audio_smoke",
|
key="smart_audio_smoke",
|
||||||
translation_key="smoke_alarm_detected",
|
translation_key="smoke_alarm_detected",
|
||||||
icon="mdi:fire",
|
|
||||||
ufp_obj_type=SmartDetectObjectType.SMOKE,
|
ufp_obj_type=SmartDetectObjectType.SMOKE,
|
||||||
ufp_required_field="can_detect_smoke",
|
ufp_required_field="can_detect_smoke",
|
||||||
ufp_enabled="is_smoke_detection_on",
|
ufp_enabled="is_smoke_detection_on",
|
||||||
@@ -496,7 +453,6 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
|
|||||||
ProtectBinaryEventEntityDescription(
|
ProtectBinaryEventEntityDescription(
|
||||||
key="smart_audio_cmonx",
|
key="smart_audio_cmonx",
|
||||||
translation_key="co_alarm_detected",
|
translation_key="co_alarm_detected",
|
||||||
icon="mdi:molecule-co",
|
|
||||||
ufp_required_field="can_detect_co",
|
ufp_required_field="can_detect_co",
|
||||||
ufp_enabled="is_co_detection_on",
|
ufp_enabled="is_co_detection_on",
|
||||||
ufp_event_obj="last_cmonx_detect_event",
|
ufp_event_obj="last_cmonx_detect_event",
|
||||||
@@ -505,7 +461,6 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
|
|||||||
ProtectBinaryEventEntityDescription(
|
ProtectBinaryEventEntityDescription(
|
||||||
key="smart_audio_siren",
|
key="smart_audio_siren",
|
||||||
translation_key="siren_detected",
|
translation_key="siren_detected",
|
||||||
icon="mdi:alarm-bell",
|
|
||||||
ufp_obj_type=SmartDetectObjectType.SIREN,
|
ufp_obj_type=SmartDetectObjectType.SIREN,
|
||||||
ufp_required_field="can_detect_siren",
|
ufp_required_field="can_detect_siren",
|
||||||
ufp_enabled="is_siren_detection_on",
|
ufp_enabled="is_siren_detection_on",
|
||||||
@@ -514,7 +469,6 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
|
|||||||
ProtectBinaryEventEntityDescription(
|
ProtectBinaryEventEntityDescription(
|
||||||
key="smart_audio_baby_cry",
|
key="smart_audio_baby_cry",
|
||||||
translation_key="baby_cry_detected",
|
translation_key="baby_cry_detected",
|
||||||
icon="mdi:cradle",
|
|
||||||
ufp_obj_type=SmartDetectObjectType.BABY_CRY,
|
ufp_obj_type=SmartDetectObjectType.BABY_CRY,
|
||||||
ufp_required_field="can_detect_baby_cry",
|
ufp_required_field="can_detect_baby_cry",
|
||||||
ufp_enabled="is_baby_cry_detection_on",
|
ufp_enabled="is_baby_cry_detection_on",
|
||||||
@@ -523,7 +477,6 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
|
|||||||
ProtectBinaryEventEntityDescription(
|
ProtectBinaryEventEntityDescription(
|
||||||
key="smart_audio_speak",
|
key="smart_audio_speak",
|
||||||
translation_key="speaking_detected",
|
translation_key="speaking_detected",
|
||||||
icon="mdi:account-voice",
|
|
||||||
ufp_obj_type=SmartDetectObjectType.SPEAK,
|
ufp_obj_type=SmartDetectObjectType.SPEAK,
|
||||||
ufp_required_field="can_detect_speaking",
|
ufp_required_field="can_detect_speaking",
|
||||||
ufp_enabled="is_speaking_detection_on",
|
ufp_enabled="is_speaking_detection_on",
|
||||||
@@ -532,7 +485,6 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
|
|||||||
ProtectBinaryEventEntityDescription(
|
ProtectBinaryEventEntityDescription(
|
||||||
key="smart_audio_bark",
|
key="smart_audio_bark",
|
||||||
translation_key="barking_detected",
|
translation_key="barking_detected",
|
||||||
icon="mdi:dog",
|
|
||||||
ufp_obj_type=SmartDetectObjectType.BARK,
|
ufp_obj_type=SmartDetectObjectType.BARK,
|
||||||
ufp_required_field="can_detect_bark",
|
ufp_required_field="can_detect_bark",
|
||||||
ufp_enabled="is_bark_detection_on",
|
ufp_enabled="is_bark_detection_on",
|
||||||
@@ -541,7 +493,6 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
|
|||||||
ProtectBinaryEventEntityDescription(
|
ProtectBinaryEventEntityDescription(
|
||||||
key="smart_audio_car_alarm",
|
key="smart_audio_car_alarm",
|
||||||
translation_key="car_alarm_detected",
|
translation_key="car_alarm_detected",
|
||||||
icon="mdi:car",
|
|
||||||
ufp_obj_type=SmartDetectObjectType.BURGLAR,
|
ufp_obj_type=SmartDetectObjectType.BURGLAR,
|
||||||
ufp_required_field="can_detect_car_alarm",
|
ufp_required_field="can_detect_car_alarm",
|
||||||
ufp_enabled="is_car_alarm_detection_on",
|
ufp_enabled="is_car_alarm_detection_on",
|
||||||
@@ -550,7 +501,6 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
|
|||||||
ProtectBinaryEventEntityDescription(
|
ProtectBinaryEventEntityDescription(
|
||||||
key="smart_audio_car_horn",
|
key="smart_audio_car_horn",
|
||||||
translation_key="car_horn_detected",
|
translation_key="car_horn_detected",
|
||||||
icon="mdi:bugle",
|
|
||||||
ufp_obj_type=SmartDetectObjectType.CAR_HORN,
|
ufp_obj_type=SmartDetectObjectType.CAR_HORN,
|
||||||
ufp_required_field="can_detect_car_horn",
|
ufp_required_field="can_detect_car_horn",
|
||||||
ufp_enabled="is_car_horn_detection_on",
|
ufp_enabled="is_car_horn_detection_on",
|
||||||
@@ -559,7 +509,6 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
|
|||||||
ProtectBinaryEventEntityDescription(
|
ProtectBinaryEventEntityDescription(
|
||||||
key="smart_audio_glass_break",
|
key="smart_audio_glass_break",
|
||||||
translation_key="glass_break_detected",
|
translation_key="glass_break_detected",
|
||||||
icon="mdi:glass-fragile",
|
|
||||||
ufp_obj_type=SmartDetectObjectType.GLASS_BREAK,
|
ufp_obj_type=SmartDetectObjectType.GLASS_BREAK,
|
||||||
ufp_required_field="can_detect_glass_break",
|
ufp_required_field="can_detect_glass_break",
|
||||||
ufp_enabled="is_glass_break_detection_on",
|
ufp_enabled="is_glass_break_detection_on",
|
||||||
@@ -577,7 +526,6 @@ DOORLOCK_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="status_light",
|
key="status_light",
|
||||||
translation_key="status_light",
|
translation_key="status_light",
|
||||||
icon="mdi:led-on",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_value="led_settings.is_enabled",
|
ufp_value="led_settings.is_enabled",
|
||||||
ufp_perm=PermRequired.NO_WRITE,
|
ufp_perm=PermRequired.NO_WRITE,
|
||||||
@@ -588,7 +536,6 @@ VIEWER_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
|
|||||||
ProtectBinaryEntityDescription(
|
ProtectBinaryEntityDescription(
|
||||||
key="ssh",
|
key="ssh",
|
||||||
translation_key="ssh_enabled",
|
translation_key="ssh_enabled",
|
||||||
icon="mdi:lock",
|
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_value="is_ssh_enabled",
|
ufp_value="is_ssh_enabled",
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ ALL_DEVICE_BUTTONS: tuple[ProtectButtonEntityDescription, ...] = (
|
|||||||
key="unadopt",
|
key="unadopt",
|
||||||
translation_key="unadopt_device",
|
translation_key="unadopt_device",
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
icon="mdi:delete",
|
|
||||||
ufp_press="unadopt",
|
ufp_press="unadopt",
|
||||||
ufp_perm=PermRequired.DELETE,
|
ufp_perm=PermRequired.DELETE,
|
||||||
),
|
),
|
||||||
@@ -69,7 +68,6 @@ ALL_DEVICE_BUTTONS: tuple[ProtectButtonEntityDescription, ...] = (
|
|||||||
ADOPT_BUTTON = ProtectButtonEntityDescription[ProtectAdoptableDeviceModel](
|
ADOPT_BUTTON = ProtectButtonEntityDescription[ProtectAdoptableDeviceModel](
|
||||||
key="adopt",
|
key="adopt",
|
||||||
translation_key="adopt_device",
|
translation_key="adopt_device",
|
||||||
icon="mdi:plus-circle",
|
|
||||||
ufp_press="adopt",
|
ufp_press="adopt",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -77,7 +75,6 @@ SENSOR_BUTTONS: tuple[ProtectButtonEntityDescription, ...] = (
|
|||||||
ProtectButtonEntityDescription(
|
ProtectButtonEntityDescription(
|
||||||
key="clear_tamper",
|
key="clear_tamper",
|
||||||
translation_key="clear_tamper",
|
translation_key="clear_tamper",
|
||||||
icon="mdi:notification-clear-all",
|
|
||||||
ufp_press="clear_tamper",
|
ufp_press="clear_tamper",
|
||||||
ufp_perm=PermRequired.WRITE,
|
ufp_perm=PermRequired.WRITE,
|
||||||
),
|
),
|
||||||
@@ -88,13 +85,11 @@ CHIME_BUTTONS: tuple[ProtectButtonEntityDescription, ...] = (
|
|||||||
key="play",
|
key="play",
|
||||||
translation_key="play_chime",
|
translation_key="play_chime",
|
||||||
device_class=DEVICE_CLASS_CHIME_BUTTON,
|
device_class=DEVICE_CLASS_CHIME_BUTTON,
|
||||||
icon="mdi:play",
|
|
||||||
ufp_press="play",
|
ufp_press="play",
|
||||||
),
|
),
|
||||||
ProtectButtonEntityDescription(
|
ProtectButtonEntityDescription(
|
||||||
key="play_buzzer",
|
key="play_buzzer",
|
||||||
translation_key="play_buzzer",
|
translation_key="play_buzzer",
|
||||||
icon="mdi:play",
|
|
||||||
ufp_press="play_buzzer",
|
ufp_press="play_buzzer",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -365,7 +365,6 @@ EVENT_DESCRIPTIONS: tuple[ProtectEventEntityDescription, ...] = (
|
|||||||
key="doorbell",
|
key="doorbell",
|
||||||
translation_key="doorbell",
|
translation_key="doorbell",
|
||||||
device_class=EventDeviceClass.DOORBELL,
|
device_class=EventDeviceClass.DOORBELL,
|
||||||
icon="mdi:doorbell-video",
|
|
||||||
ufp_required_field="feature_flags.is_doorbell",
|
ufp_required_field="feature_flags.is_doorbell",
|
||||||
ufp_event_obj="last_ring_event",
|
ufp_event_obj="last_ring_event",
|
||||||
event_types=[EVENT_TYPE_DOORBELL_RING],
|
event_types=[EVENT_TYPE_DOORBELL_RING],
|
||||||
@@ -374,7 +373,6 @@ EVENT_DESCRIPTIONS: tuple[ProtectEventEntityDescription, ...] = (
|
|||||||
ProtectEventEntityDescription(
|
ProtectEventEntityDescription(
|
||||||
key="nfc",
|
key="nfc",
|
||||||
translation_key="nfc",
|
translation_key="nfc",
|
||||||
icon="mdi:nfc",
|
|
||||||
ufp_required_field="feature_flags.support_nfc",
|
ufp_required_field="feature_flags.support_nfc",
|
||||||
ufp_event_obj="last_nfc_card_scanned_event",
|
ufp_event_obj="last_nfc_card_scanned_event",
|
||||||
event_types=[EVENT_TYPE_NFC_SCANNED],
|
event_types=[EVENT_TYPE_NFC_SCANNED],
|
||||||
@@ -383,7 +381,6 @@ EVENT_DESCRIPTIONS: tuple[ProtectEventEntityDescription, ...] = (
|
|||||||
ProtectEventEntityDescription(
|
ProtectEventEntityDescription(
|
||||||
key="fingerprint",
|
key="fingerprint",
|
||||||
translation_key="fingerprint",
|
translation_key="fingerprint",
|
||||||
icon="mdi:fingerprint",
|
|
||||||
ufp_required_field="feature_flags.has_fingerprint_sensor",
|
ufp_required_field="feature_flags.has_fingerprint_sensor",
|
||||||
ufp_event_obj="last_fingerprint_identified_event",
|
ufp_event_obj="last_fingerprint_identified_event",
|
||||||
event_types=[
|
event_types=[
|
||||||
@@ -395,7 +392,6 @@ EVENT_DESCRIPTIONS: tuple[ProtectEventEntityDescription, ...] = (
|
|||||||
ProtectEventEntityDescription(
|
ProtectEventEntityDescription(
|
||||||
key="vehicle",
|
key="vehicle",
|
||||||
translation_key="vehicle",
|
translation_key="vehicle",
|
||||||
icon="mdi:car",
|
|
||||||
ufp_required_field="feature_flags.has_smart_detect",
|
ufp_required_field="feature_flags.has_smart_detect",
|
||||||
ufp_event_obj="last_smart_detect_event",
|
ufp_event_obj="last_smart_detect_event",
|
||||||
event_types=[EVENT_TYPE_VEHICLE_DETECTED],
|
event_types=[EVENT_TYPE_VEHICLE_DETECTED],
|
||||||
|
|||||||
@@ -1,4 +1,422 @@
|
|||||||
{
|
{
|
||||||
|
"entity": {
|
||||||
|
"binary_sensor": {
|
||||||
|
"animal_detected": {
|
||||||
|
"default": "mdi:paw"
|
||||||
|
},
|
||||||
|
"audio_object_detected": {
|
||||||
|
"default": "mdi:eye"
|
||||||
|
},
|
||||||
|
"baby_cry_detected": {
|
||||||
|
"default": "mdi:cradle"
|
||||||
|
},
|
||||||
|
"barking_detected": {
|
||||||
|
"default": "mdi:dog"
|
||||||
|
},
|
||||||
|
"car_alarm_detected": {
|
||||||
|
"default": "mdi:car"
|
||||||
|
},
|
||||||
|
"car_horn_detected": {
|
||||||
|
"default": "mdi:bugle"
|
||||||
|
},
|
||||||
|
"co_alarm_detected": {
|
||||||
|
"default": "mdi:molecule-co"
|
||||||
|
},
|
||||||
|
"detections_animal": {
|
||||||
|
"default": "mdi:paw"
|
||||||
|
},
|
||||||
|
"detections_baby_cry": {
|
||||||
|
"default": "mdi:cradle"
|
||||||
|
},
|
||||||
|
"detections_barking": {
|
||||||
|
"default": "mdi:dog"
|
||||||
|
},
|
||||||
|
"detections_car_alarm": {
|
||||||
|
"default": "mdi:car"
|
||||||
|
},
|
||||||
|
"detections_car_horn": {
|
||||||
|
"default": "mdi:bugle"
|
||||||
|
},
|
||||||
|
"detections_co_alarm": {
|
||||||
|
"default": "mdi:molecule-co"
|
||||||
|
},
|
||||||
|
"detections_glass_break": {
|
||||||
|
"default": "mdi:glass-fragile"
|
||||||
|
},
|
||||||
|
"detections_license_plate": {
|
||||||
|
"default": "mdi:car"
|
||||||
|
},
|
||||||
|
"detections_motion": {
|
||||||
|
"default": "mdi:run-fast"
|
||||||
|
},
|
||||||
|
"detections_package": {
|
||||||
|
"default": "mdi:package-variant-closed"
|
||||||
|
},
|
||||||
|
"detections_person": {
|
||||||
|
"default": "mdi:walk"
|
||||||
|
},
|
||||||
|
"detections_siren": {
|
||||||
|
"default": "mdi:alarm-bell"
|
||||||
|
},
|
||||||
|
"detections_smoke": {
|
||||||
|
"default": "mdi:fire"
|
||||||
|
},
|
||||||
|
"detections_speaking": {
|
||||||
|
"default": "mdi:account-voice"
|
||||||
|
},
|
||||||
|
"detections_vehicle": {
|
||||||
|
"default": "mdi:car"
|
||||||
|
},
|
||||||
|
"doorbell": {
|
||||||
|
"default": "mdi:doorbell-video"
|
||||||
|
},
|
||||||
|
"flood_light": {
|
||||||
|
"default": "mdi:spotlight-beam"
|
||||||
|
},
|
||||||
|
"glass_break_detected": {
|
||||||
|
"default": "mdi:glass-fragile"
|
||||||
|
},
|
||||||
|
"hdr_mode": {
|
||||||
|
"default": "mdi:brightness-7"
|
||||||
|
},
|
||||||
|
"high_fps": {
|
||||||
|
"default": "mdi:video-high-definition"
|
||||||
|
},
|
||||||
|
"humidity_sensor": {
|
||||||
|
"default": "mdi:water-percent"
|
||||||
|
},
|
||||||
|
"is_dark": {
|
||||||
|
"default": "mdi:brightness-6"
|
||||||
|
},
|
||||||
|
"light_sensor": {
|
||||||
|
"default": "mdi:brightness-5"
|
||||||
|
},
|
||||||
|
"object_detected": {
|
||||||
|
"default": "mdi:eye"
|
||||||
|
},
|
||||||
|
"overlay_show_date": {
|
||||||
|
"default": "mdi:fullscreen"
|
||||||
|
},
|
||||||
|
"overlay_show_logo": {
|
||||||
|
"default": "mdi:fullscreen"
|
||||||
|
},
|
||||||
|
"overlay_show_name": {
|
||||||
|
"default": "mdi:fullscreen"
|
||||||
|
},
|
||||||
|
"overlay_show_nerd_mode": {
|
||||||
|
"default": "mdi:fullscreen"
|
||||||
|
},
|
||||||
|
"package_detected": {
|
||||||
|
"default": "mdi:package-variant-closed"
|
||||||
|
},
|
||||||
|
"person_detected": {
|
||||||
|
"default": "mdi:walk"
|
||||||
|
},
|
||||||
|
"siren_detected": {
|
||||||
|
"default": "mdi:alarm-bell"
|
||||||
|
},
|
||||||
|
"smoke_alarm_detected": {
|
||||||
|
"default": "mdi:fire"
|
||||||
|
},
|
||||||
|
"speaking_detected": {
|
||||||
|
"default": "mdi:account-voice"
|
||||||
|
},
|
||||||
|
"ssh_enabled": {
|
||||||
|
"default": "mdi:lock"
|
||||||
|
},
|
||||||
|
"status_light": {
|
||||||
|
"default": "mdi:led-on"
|
||||||
|
},
|
||||||
|
"system_sounds": {
|
||||||
|
"default": "mdi:speaker"
|
||||||
|
},
|
||||||
|
"temperature_sensor": {
|
||||||
|
"default": "mdi:thermometer"
|
||||||
|
},
|
||||||
|
"tracking_person": {
|
||||||
|
"default": "mdi:walk"
|
||||||
|
},
|
||||||
|
"vehicle_detected": {
|
||||||
|
"default": "mdi:car"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"button": {
|
||||||
|
"adopt_device": {
|
||||||
|
"default": "mdi:plus-circle"
|
||||||
|
},
|
||||||
|
"clear_tamper": {
|
||||||
|
"default": "mdi:notification-clear-all"
|
||||||
|
},
|
||||||
|
"play_buzzer": {
|
||||||
|
"default": "mdi:play"
|
||||||
|
},
|
||||||
|
"play_chime": {
|
||||||
|
"default": "mdi:play"
|
||||||
|
},
|
||||||
|
"unadopt_device": {
|
||||||
|
"default": "mdi:delete"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"event": {
|
||||||
|
"doorbell": {
|
||||||
|
"default": "mdi:doorbell-video"
|
||||||
|
},
|
||||||
|
"fingerprint": {
|
||||||
|
"default": "mdi:fingerprint"
|
||||||
|
},
|
||||||
|
"nfc": {
|
||||||
|
"default": "mdi:nfc"
|
||||||
|
},
|
||||||
|
"vehicle": {
|
||||||
|
"default": "mdi:car"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"number": {
|
||||||
|
"auto_lock_timeout": {
|
||||||
|
"default": "mdi:walk"
|
||||||
|
},
|
||||||
|
"auto_shutoff_duration": {
|
||||||
|
"default": "mdi:camera-timer"
|
||||||
|
},
|
||||||
|
"chime_duration": {
|
||||||
|
"default": "mdi:bell"
|
||||||
|
},
|
||||||
|
"doorbell_ring_volume": {
|
||||||
|
"default": "mdi:bell-ring"
|
||||||
|
},
|
||||||
|
"infrared_custom_lux_trigger": {
|
||||||
|
"default": "mdi:white-balance-sunny"
|
||||||
|
},
|
||||||
|
"microphone_level": {
|
||||||
|
"default": "mdi:microphone"
|
||||||
|
},
|
||||||
|
"motion_sensitivity": {
|
||||||
|
"default": "mdi:walk"
|
||||||
|
},
|
||||||
|
"system_sounds_volume": {
|
||||||
|
"default": "mdi:volume-high"
|
||||||
|
},
|
||||||
|
"volume": {
|
||||||
|
"default": "mdi:speaker"
|
||||||
|
},
|
||||||
|
"wide_dynamic_range": {
|
||||||
|
"default": "mdi:state-machine"
|
||||||
|
},
|
||||||
|
"zoom_level": {
|
||||||
|
"default": "mdi:magnify-plus-outline"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"select": {
|
||||||
|
"chime_type": {
|
||||||
|
"default": "mdi:bell"
|
||||||
|
},
|
||||||
|
"doorbell_text": {
|
||||||
|
"default": "mdi:card-text"
|
||||||
|
},
|
||||||
|
"hdr_mode": {
|
||||||
|
"default": "mdi:brightness-7"
|
||||||
|
},
|
||||||
|
"infrared_mode": {
|
||||||
|
"default": "mdi:circle-opacity"
|
||||||
|
},
|
||||||
|
"light_mode": {
|
||||||
|
"default": "mdi:spotlight"
|
||||||
|
},
|
||||||
|
"liveview": {
|
||||||
|
"default": "mdi:view-dashboard"
|
||||||
|
},
|
||||||
|
"mount_type": {
|
||||||
|
"default": "mdi:screwdriver"
|
||||||
|
},
|
||||||
|
"paired_camera": {
|
||||||
|
"default": "mdi:cctv"
|
||||||
|
},
|
||||||
|
"recording_mode": {
|
||||||
|
"default": "mdi:video-outline"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sensor": {
|
||||||
|
"chime_type": {
|
||||||
|
"default": "mdi:bell"
|
||||||
|
},
|
||||||
|
"cpu_utilization": {
|
||||||
|
"default": "mdi:speedometer"
|
||||||
|
},
|
||||||
|
"doorbell_text": {
|
||||||
|
"default": "mdi:card-text"
|
||||||
|
},
|
||||||
|
"infrared_mode": {
|
||||||
|
"default": "mdi:circle-opacity"
|
||||||
|
},
|
||||||
|
"last_doorbell_ring": {
|
||||||
|
"default": "mdi:doorbell-video"
|
||||||
|
},
|
||||||
|
"last_ring": {
|
||||||
|
"default": "mdi:bell"
|
||||||
|
},
|
||||||
|
"lens_type": {
|
||||||
|
"default": "mdi:camera-iris"
|
||||||
|
},
|
||||||
|
"light_mode": {
|
||||||
|
"default": "mdi:spotlight"
|
||||||
|
},
|
||||||
|
"liveview": {
|
||||||
|
"default": "mdi:view-dashboard"
|
||||||
|
},
|
||||||
|
"memory_utilization": {
|
||||||
|
"default": "mdi:memory"
|
||||||
|
},
|
||||||
|
"microphone_level": {
|
||||||
|
"default": "mdi:microphone"
|
||||||
|
},
|
||||||
|
"motion_sensitivity": {
|
||||||
|
"default": "mdi:walk"
|
||||||
|
},
|
||||||
|
"mount_type": {
|
||||||
|
"default": "mdi:screwdriver"
|
||||||
|
},
|
||||||
|
"paired_camera": {
|
||||||
|
"default": "mdi:cctv"
|
||||||
|
},
|
||||||
|
"recording_capacity": {
|
||||||
|
"default": "mdi:record-rec"
|
||||||
|
},
|
||||||
|
"recording_mode": {
|
||||||
|
"default": "mdi:video-outline"
|
||||||
|
},
|
||||||
|
"resolution_4k_video": {
|
||||||
|
"default": "mdi:cctv"
|
||||||
|
},
|
||||||
|
"resolution_free_space": {
|
||||||
|
"default": "mdi:cctv"
|
||||||
|
},
|
||||||
|
"resolution_hd_video": {
|
||||||
|
"default": "mdi:cctv"
|
||||||
|
},
|
||||||
|
"sensitivity": {
|
||||||
|
"default": "mdi:walk"
|
||||||
|
},
|
||||||
|
"storage_utilization": {
|
||||||
|
"default": "mdi:harddisk"
|
||||||
|
},
|
||||||
|
"type_continuous_video": {
|
||||||
|
"default": "mdi:server"
|
||||||
|
},
|
||||||
|
"type_detections_video": {
|
||||||
|
"default": "mdi:server"
|
||||||
|
},
|
||||||
|
"type_timelapse_video": {
|
||||||
|
"default": "mdi:server"
|
||||||
|
},
|
||||||
|
"uptime": {
|
||||||
|
"default": "mdi:clock"
|
||||||
|
},
|
||||||
|
"volume": {
|
||||||
|
"default": "mdi:speaker"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"switch": {
|
||||||
|
"analytics_enabled": {
|
||||||
|
"default": "mdi:google-analytics"
|
||||||
|
},
|
||||||
|
"color_night_vision": {
|
||||||
|
"default": "mdi:light-flood-down"
|
||||||
|
},
|
||||||
|
"detections_animal": {
|
||||||
|
"default": "mdi:paw"
|
||||||
|
},
|
||||||
|
"detections_baby_cry": {
|
||||||
|
"default": "mdi:cradle"
|
||||||
|
},
|
||||||
|
"detections_bark": {
|
||||||
|
"default": "mdi:dog"
|
||||||
|
},
|
||||||
|
"detections_car_alarm": {
|
||||||
|
"default": "mdi:car"
|
||||||
|
},
|
||||||
|
"detections_car_horn": {
|
||||||
|
"default": "mdi:bugle"
|
||||||
|
},
|
||||||
|
"detections_co_alarm": {
|
||||||
|
"default": "mdi:molecule-co"
|
||||||
|
},
|
||||||
|
"detections_glass_break": {
|
||||||
|
"default": "mdi:glass-fragile"
|
||||||
|
},
|
||||||
|
"detections_license_plate": {
|
||||||
|
"default": "mdi:car"
|
||||||
|
},
|
||||||
|
"detections_motion": {
|
||||||
|
"default": "mdi:walk"
|
||||||
|
},
|
||||||
|
"detections_package": {
|
||||||
|
"default": "mdi:package-variant-closed"
|
||||||
|
},
|
||||||
|
"detections_person": {
|
||||||
|
"default": "mdi:walk"
|
||||||
|
},
|
||||||
|
"detections_siren": {
|
||||||
|
"default": "mdi:alarm-bell"
|
||||||
|
},
|
||||||
|
"detections_smoke": {
|
||||||
|
"default": "mdi:fire"
|
||||||
|
},
|
||||||
|
"detections_speak": {
|
||||||
|
"default": "mdi:account-voice"
|
||||||
|
},
|
||||||
|
"detections_vehicle": {
|
||||||
|
"default": "mdi:car"
|
||||||
|
},
|
||||||
|
"hdr_mode": {
|
||||||
|
"default": "mdi:brightness-7"
|
||||||
|
},
|
||||||
|
"high_fps": {
|
||||||
|
"default": "mdi:video-high-definition"
|
||||||
|
},
|
||||||
|
"humidity_sensor": {
|
||||||
|
"default": "mdi:water-percent"
|
||||||
|
},
|
||||||
|
"insights_enabled": {
|
||||||
|
"default": "mdi:magnify"
|
||||||
|
},
|
||||||
|
"light_sensor": {
|
||||||
|
"default": "mdi:brightness-5"
|
||||||
|
},
|
||||||
|
"motion": {
|
||||||
|
"default": "mdi:run-fast"
|
||||||
|
},
|
||||||
|
"overlay_show_date": {
|
||||||
|
"default": "mdi:fullscreen"
|
||||||
|
},
|
||||||
|
"overlay_show_logo": {
|
||||||
|
"default": "mdi:fullscreen"
|
||||||
|
},
|
||||||
|
"overlay_show_name": {
|
||||||
|
"default": "mdi:fullscreen"
|
||||||
|
},
|
||||||
|
"overlay_show_nerd_mode": {
|
||||||
|
"default": "mdi:fullscreen"
|
||||||
|
},
|
||||||
|
"privacy_mode": {
|
||||||
|
"default": "mdi:eye-settings"
|
||||||
|
},
|
||||||
|
"ssh_enabled": {
|
||||||
|
"default": "mdi:lock"
|
||||||
|
},
|
||||||
|
"status_light": {
|
||||||
|
"default": "mdi:led-on"
|
||||||
|
},
|
||||||
|
"system_sounds": {
|
||||||
|
"default": "mdi:speaker"
|
||||||
|
},
|
||||||
|
"temperature_sensor": {
|
||||||
|
"default": "mdi:thermometer"
|
||||||
|
},
|
||||||
|
"tracking_person": {
|
||||||
|
"default": "mdi:walk"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"services": {
|
"services": {
|
||||||
"add_doorbell_text": {
|
"add_doorbell_text": {
|
||||||
"service": "mdi:message-plus"
|
"service": "mdi:message-plus"
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ CAMERA_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
|
|||||||
ProtectNumberEntityDescription(
|
ProtectNumberEntityDescription(
|
||||||
key="wdr_value",
|
key="wdr_value",
|
||||||
translation_key="wide_dynamic_range",
|
translation_key="wide_dynamic_range",
|
||||||
icon="mdi:state-machine",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_min=0,
|
ufp_min=0,
|
||||||
ufp_max=3,
|
ufp_max=3,
|
||||||
@@ -80,7 +79,6 @@ CAMERA_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
|
|||||||
ProtectNumberEntityDescription(
|
ProtectNumberEntityDescription(
|
||||||
key="mic_level",
|
key="mic_level",
|
||||||
translation_key="microphone_level",
|
translation_key="microphone_level",
|
||||||
icon="mdi:microphone",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
ufp_min=0,
|
ufp_min=0,
|
||||||
@@ -95,7 +93,6 @@ CAMERA_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
|
|||||||
ProtectNumberEntityDescription(
|
ProtectNumberEntityDescription(
|
||||||
key="system_sounds_volume",
|
key="system_sounds_volume",
|
||||||
translation_key="system_sounds_volume",
|
translation_key="system_sounds_volume",
|
||||||
icon="mdi:volume-high",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
ufp_min=0,
|
ufp_min=0,
|
||||||
@@ -110,7 +107,6 @@ CAMERA_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
|
|||||||
ProtectNumberEntityDescription(
|
ProtectNumberEntityDescription(
|
||||||
key="doorbell_ring_volume",
|
key="doorbell_ring_volume",
|
||||||
translation_key="doorbell_ring_volume",
|
translation_key="doorbell_ring_volume",
|
||||||
icon="mdi:bell-ring",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
ufp_min=0,
|
ufp_min=0,
|
||||||
@@ -125,7 +121,6 @@ CAMERA_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
|
|||||||
ProtectNumberEntityDescription(
|
ProtectNumberEntityDescription(
|
||||||
key="zoom_position",
|
key="zoom_position",
|
||||||
translation_key="zoom_level",
|
translation_key="zoom_level",
|
||||||
icon="mdi:magnify-plus-outline",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
ufp_min=0,
|
ufp_min=0,
|
||||||
@@ -139,7 +134,6 @@ CAMERA_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
|
|||||||
ProtectNumberEntityDescription(
|
ProtectNumberEntityDescription(
|
||||||
key="chime_duration",
|
key="chime_duration",
|
||||||
translation_key="chime_duration",
|
translation_key="chime_duration",
|
||||||
icon="mdi:bell",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
native_unit_of_measurement=UnitOfTime.SECONDS,
|
native_unit_of_measurement=UnitOfTime.SECONDS,
|
||||||
ufp_min=1,
|
ufp_min=1,
|
||||||
@@ -154,7 +148,6 @@ CAMERA_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
|
|||||||
ProtectNumberEntityDescription(
|
ProtectNumberEntityDescription(
|
||||||
key="icr_lux",
|
key="icr_lux",
|
||||||
translation_key="infrared_custom_lux_trigger",
|
translation_key="infrared_custom_lux_trigger",
|
||||||
icon="mdi:white-balance-sunny",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_min=0,
|
ufp_min=0,
|
||||||
ufp_max=30,
|
ufp_max=30,
|
||||||
@@ -171,7 +164,6 @@ LIGHT_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
|
|||||||
ProtectNumberEntityDescription(
|
ProtectNumberEntityDescription(
|
||||||
key="sensitivity",
|
key="sensitivity",
|
||||||
translation_key="motion_sensitivity",
|
translation_key="motion_sensitivity",
|
||||||
icon="mdi:walk",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
ufp_min=0,
|
ufp_min=0,
|
||||||
@@ -185,7 +177,6 @@ LIGHT_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
|
|||||||
ProtectNumberEntityDescription[Light](
|
ProtectNumberEntityDescription[Light](
|
||||||
key="duration",
|
key="duration",
|
||||||
translation_key="auto_shutoff_duration",
|
translation_key="auto_shutoff_duration",
|
||||||
icon="mdi:camera-timer",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
native_unit_of_measurement=UnitOfTime.SECONDS,
|
native_unit_of_measurement=UnitOfTime.SECONDS,
|
||||||
ufp_min=15,
|
ufp_min=15,
|
||||||
@@ -202,7 +193,6 @@ SENSE_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
|
|||||||
ProtectNumberEntityDescription(
|
ProtectNumberEntityDescription(
|
||||||
key="sensitivity",
|
key="sensitivity",
|
||||||
translation_key="motion_sensitivity",
|
translation_key="motion_sensitivity",
|
||||||
icon="mdi:walk",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
ufp_min=0,
|
ufp_min=0,
|
||||||
@@ -219,7 +209,6 @@ DOORLOCK_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
|
|||||||
ProtectNumberEntityDescription[Doorlock](
|
ProtectNumberEntityDescription[Doorlock](
|
||||||
key="auto_lock_time",
|
key="auto_lock_time",
|
||||||
translation_key="auto_lock_timeout",
|
translation_key="auto_lock_timeout",
|
||||||
icon="mdi:walk",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
native_unit_of_measurement=UnitOfTime.SECONDS,
|
native_unit_of_measurement=UnitOfTime.SECONDS,
|
||||||
ufp_min=0,
|
ufp_min=0,
|
||||||
@@ -236,7 +225,6 @@ CHIME_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
|
|||||||
ProtectNumberEntityDescription(
|
ProtectNumberEntityDescription(
|
||||||
key="volume",
|
key="volume",
|
||||||
translation_key="volume",
|
translation_key="volume",
|
||||||
icon="mdi:speaker",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
ufp_min=0,
|
ufp_min=0,
|
||||||
|
|||||||
@@ -195,7 +195,6 @@ CAMERA_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
|
|||||||
ProtectSelectEntityDescription(
|
ProtectSelectEntityDescription(
|
||||||
key="recording_mode",
|
key="recording_mode",
|
||||||
translation_key="recording_mode",
|
translation_key="recording_mode",
|
||||||
icon="mdi:video-outline",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_options=DEVICE_RECORDING_MODES,
|
ufp_options=DEVICE_RECORDING_MODES,
|
||||||
ufp_enum_type=RecordingMode,
|
ufp_enum_type=RecordingMode,
|
||||||
@@ -206,7 +205,6 @@ CAMERA_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
|
|||||||
ProtectSelectEntityDescription(
|
ProtectSelectEntityDescription(
|
||||||
key="infrared",
|
key="infrared",
|
||||||
translation_key="infrared_mode",
|
translation_key="infrared_mode",
|
||||||
icon="mdi:circle-opacity",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_required_field="feature_flags.has_led_ir",
|
ufp_required_field="feature_flags.has_led_ir",
|
||||||
ufp_options=INFRARED_MODES,
|
ufp_options=INFRARED_MODES,
|
||||||
@@ -218,7 +216,6 @@ CAMERA_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
|
|||||||
ProtectSelectEntityDescription[Camera](
|
ProtectSelectEntityDescription[Camera](
|
||||||
key="doorbell_text",
|
key="doorbell_text",
|
||||||
translation_key="doorbell_text",
|
translation_key="doorbell_text",
|
||||||
icon="mdi:card-text",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
device_class=DEVICE_CLASS_LCD_MESSAGE,
|
device_class=DEVICE_CLASS_LCD_MESSAGE,
|
||||||
ufp_required_field="feature_flags.has_lcd_screen",
|
ufp_required_field="feature_flags.has_lcd_screen",
|
||||||
@@ -230,7 +227,6 @@ CAMERA_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
|
|||||||
ProtectSelectEntityDescription(
|
ProtectSelectEntityDescription(
|
||||||
key="chime_type",
|
key="chime_type",
|
||||||
translation_key="chime_type",
|
translation_key="chime_type",
|
||||||
icon="mdi:bell",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_required_field="feature_flags.has_chime",
|
ufp_required_field="feature_flags.has_chime",
|
||||||
ufp_options=CHIME_TYPES,
|
ufp_options=CHIME_TYPES,
|
||||||
@@ -242,7 +238,6 @@ CAMERA_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
|
|||||||
ProtectSelectEntityDescription(
|
ProtectSelectEntityDescription(
|
||||||
key="hdr_mode",
|
key="hdr_mode",
|
||||||
translation_key="hdr_mode",
|
translation_key="hdr_mode",
|
||||||
icon="mdi:brightness-7",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_required_field="feature_flags.has_hdr",
|
ufp_required_field="feature_flags.has_hdr",
|
||||||
ufp_options=HDR_MODES,
|
ufp_options=HDR_MODES,
|
||||||
@@ -256,7 +251,6 @@ LIGHT_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
|
|||||||
ProtectSelectEntityDescription[Light](
|
ProtectSelectEntityDescription[Light](
|
||||||
key=_KEY_LIGHT_MOTION,
|
key=_KEY_LIGHT_MOTION,
|
||||||
translation_key="light_mode",
|
translation_key="light_mode",
|
||||||
icon="mdi:spotlight",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_options=MOTION_MODE_TO_LIGHT_MODE,
|
ufp_options=MOTION_MODE_TO_LIGHT_MODE,
|
||||||
ufp_value_fn=async_get_light_motion_current,
|
ufp_value_fn=async_get_light_motion_current,
|
||||||
@@ -266,7 +260,6 @@ LIGHT_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
|
|||||||
ProtectSelectEntityDescription[Light](
|
ProtectSelectEntityDescription[Light](
|
||||||
key="paired_camera",
|
key="paired_camera",
|
||||||
translation_key="paired_camera",
|
translation_key="paired_camera",
|
||||||
icon="mdi:cctv",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_value="camera_id",
|
ufp_value="camera_id",
|
||||||
ufp_options_fn=_get_paired_camera_options,
|
ufp_options_fn=_get_paired_camera_options,
|
||||||
@@ -279,7 +272,6 @@ SENSE_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
|
|||||||
ProtectSelectEntityDescription(
|
ProtectSelectEntityDescription(
|
||||||
key="mount_type",
|
key="mount_type",
|
||||||
translation_key="mount_type",
|
translation_key="mount_type",
|
||||||
icon="mdi:screwdriver",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_options=MOUNT_TYPES,
|
ufp_options=MOUNT_TYPES,
|
||||||
ufp_enum_type=MountType,
|
ufp_enum_type=MountType,
|
||||||
@@ -290,7 +282,6 @@ SENSE_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
|
|||||||
ProtectSelectEntityDescription[Sensor](
|
ProtectSelectEntityDescription[Sensor](
|
||||||
key="paired_camera",
|
key="paired_camera",
|
||||||
translation_key="paired_camera",
|
translation_key="paired_camera",
|
||||||
icon="mdi:cctv",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_value="camera_id",
|
ufp_value="camera_id",
|
||||||
ufp_options_fn=_get_paired_camera_options,
|
ufp_options_fn=_get_paired_camera_options,
|
||||||
@@ -303,7 +294,6 @@ DOORLOCK_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
|
|||||||
ProtectSelectEntityDescription[Doorlock](
|
ProtectSelectEntityDescription[Doorlock](
|
||||||
key="paired_camera",
|
key="paired_camera",
|
||||||
translation_key="paired_camera",
|
translation_key="paired_camera",
|
||||||
icon="mdi:cctv",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_value="camera_id",
|
ufp_value="camera_id",
|
||||||
ufp_options_fn=_get_paired_camera_options,
|
ufp_options_fn=_get_paired_camera_options,
|
||||||
@@ -316,7 +306,6 @@ VIEWER_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
|
|||||||
ProtectSelectEntityDescription[Viewer](
|
ProtectSelectEntityDescription[Viewer](
|
||||||
key="viewer",
|
key="viewer",
|
||||||
translation_key="liveview",
|
translation_key="liveview",
|
||||||
icon="mdi:view-dashboard",
|
|
||||||
entity_category=None,
|
entity_category=None,
|
||||||
ufp_options_fn=_get_viewer_options,
|
ufp_options_fn=_get_viewer_options,
|
||||||
ufp_value_fn=_get_viewer_current,
|
ufp_value_fn=_get_viewer_current,
|
||||||
|
|||||||
@@ -126,7 +126,6 @@ ALL_DEVICES_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
|||||||
ProtectSensorEntityDescription(
|
ProtectSensorEntityDescription(
|
||||||
key="uptime",
|
key="uptime",
|
||||||
translation_key="uptime",
|
translation_key="uptime",
|
||||||
icon="mdi:clock",
|
|
||||||
device_class=SensorDeviceClass.TIMESTAMP,
|
device_class=SensorDeviceClass.TIMESTAMP,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
@@ -215,7 +214,6 @@ CAMERA_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
|||||||
key="doorbell_last_trip_time",
|
key="doorbell_last_trip_time",
|
||||||
translation_key="last_doorbell_ring",
|
translation_key="last_doorbell_ring",
|
||||||
device_class=SensorDeviceClass.TIMESTAMP,
|
device_class=SensorDeviceClass.TIMESTAMP,
|
||||||
icon="mdi:doorbell-video",
|
|
||||||
ufp_required_field="feature_flags.is_doorbell",
|
ufp_required_field="feature_flags.is_doorbell",
|
||||||
ufp_value="last_ring",
|
ufp_value="last_ring",
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
@@ -224,14 +222,12 @@ CAMERA_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
|||||||
key="lens_type",
|
key="lens_type",
|
||||||
translation_key="lens_type",
|
translation_key="lens_type",
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
icon="mdi:camera-iris",
|
|
||||||
ufp_required_field="has_removable_lens",
|
ufp_required_field="has_removable_lens",
|
||||||
ufp_value="feature_flags.lens_type",
|
ufp_value="feature_flags.lens_type",
|
||||||
),
|
),
|
||||||
ProtectSensorEntityDescription(
|
ProtectSensorEntityDescription(
|
||||||
key="mic_level",
|
key="mic_level",
|
||||||
translation_key="microphone_level",
|
translation_key="microphone_level",
|
||||||
icon="mdi:microphone",
|
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_required_field="has_mic",
|
ufp_required_field="has_mic",
|
||||||
@@ -242,7 +238,6 @@ CAMERA_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
|||||||
ProtectSensorEntityDescription(
|
ProtectSensorEntityDescription(
|
||||||
key="recording_mode",
|
key="recording_mode",
|
||||||
translation_key="recording_mode",
|
translation_key="recording_mode",
|
||||||
icon="mdi:video-outline",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_value="recording_settings.mode.value",
|
ufp_value="recording_settings.mode.value",
|
||||||
ufp_perm=PermRequired.NO_WRITE,
|
ufp_perm=PermRequired.NO_WRITE,
|
||||||
@@ -250,7 +245,6 @@ CAMERA_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
|||||||
ProtectSensorEntityDescription(
|
ProtectSensorEntityDescription(
|
||||||
key="infrared",
|
key="infrared",
|
||||||
translation_key="infrared_mode",
|
translation_key="infrared_mode",
|
||||||
icon="mdi:circle-opacity",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_required_field="feature_flags.has_led_ir",
|
ufp_required_field="feature_flags.has_led_ir",
|
||||||
ufp_value="isp_settings.ir_led_mode.value",
|
ufp_value="isp_settings.ir_led_mode.value",
|
||||||
@@ -259,7 +253,6 @@ CAMERA_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
|||||||
ProtectSensorEntityDescription(
|
ProtectSensorEntityDescription(
|
||||||
key="doorbell_text",
|
key="doorbell_text",
|
||||||
translation_key="doorbell_text",
|
translation_key="doorbell_text",
|
||||||
icon="mdi:card-text",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_required_field="feature_flags.has_lcd_screen",
|
ufp_required_field="feature_flags.has_lcd_screen",
|
||||||
ufp_value="lcd_message.text",
|
ufp_value="lcd_message.text",
|
||||||
@@ -268,7 +261,6 @@ CAMERA_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
|||||||
ProtectSensorEntityDescription(
|
ProtectSensorEntityDescription(
|
||||||
key="chime_type",
|
key="chime_type",
|
||||||
translation_key="chime_type",
|
translation_key="chime_type",
|
||||||
icon="mdi:bell",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
ufp_required_field="feature_flags.has_chime",
|
ufp_required_field="feature_flags.has_chime",
|
||||||
@@ -366,7 +358,6 @@ SENSE_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
|||||||
ProtectSensorEntityDescription(
|
ProtectSensorEntityDescription(
|
||||||
key="sensitivity",
|
key="sensitivity",
|
||||||
translation_key="sensitivity",
|
translation_key="sensitivity",
|
||||||
icon="mdi:walk",
|
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_value="motion_settings.sensitivity",
|
ufp_value="motion_settings.sensitivity",
|
||||||
@@ -375,7 +366,6 @@ SENSE_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
|||||||
ProtectSensorEntityDescription(
|
ProtectSensorEntityDescription(
|
||||||
key="mount_type",
|
key="mount_type",
|
||||||
translation_key="mount_type",
|
translation_key="mount_type",
|
||||||
icon="mdi:screwdriver",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_value="mount_type",
|
ufp_value="mount_type",
|
||||||
ufp_perm=PermRequired.NO_WRITE,
|
ufp_perm=PermRequired.NO_WRITE,
|
||||||
@@ -383,7 +373,6 @@ SENSE_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
|||||||
ProtectSensorEntityDescription(
|
ProtectSensorEntityDescription(
|
||||||
key="paired_camera",
|
key="paired_camera",
|
||||||
translation_key="paired_camera",
|
translation_key="paired_camera",
|
||||||
icon="mdi:cctv",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_value="camera.display_name",
|
ufp_value="camera.display_name",
|
||||||
ufp_perm=PermRequired.NO_WRITE,
|
ufp_perm=PermRequired.NO_WRITE,
|
||||||
@@ -402,7 +391,6 @@ DOORLOCK_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
|||||||
ProtectSensorEntityDescription(
|
ProtectSensorEntityDescription(
|
||||||
key="paired_camera",
|
key="paired_camera",
|
||||||
translation_key="paired_camera",
|
translation_key="paired_camera",
|
||||||
icon="mdi:cctv",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_value="camera.display_name",
|
ufp_value="camera.display_name",
|
||||||
ufp_perm=PermRequired.NO_WRITE,
|
ufp_perm=PermRequired.NO_WRITE,
|
||||||
@@ -413,7 +401,6 @@ NVR_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
|||||||
ProtectSensorEntityDescription(
|
ProtectSensorEntityDescription(
|
||||||
key="uptime",
|
key="uptime",
|
||||||
translation_key="uptime",
|
translation_key="uptime",
|
||||||
icon="mdi:clock",
|
|
||||||
device_class=SensorDeviceClass.TIMESTAMP,
|
device_class=SensorDeviceClass.TIMESTAMP,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_value_fn=_get_uptime,
|
ufp_value_fn=_get_uptime,
|
||||||
@@ -422,7 +409,6 @@ NVR_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
|||||||
key="storage_utilization",
|
key="storage_utilization",
|
||||||
translation_key="storage_utilization",
|
translation_key="storage_utilization",
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
icon="mdi:harddisk",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
ufp_value="storage_stats.utilization",
|
ufp_value="storage_stats.utilization",
|
||||||
@@ -432,7 +418,6 @@ NVR_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
|||||||
key="record_rotating",
|
key="record_rotating",
|
||||||
translation_key="type_timelapse_video",
|
translation_key="type_timelapse_video",
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
icon="mdi:server",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
ufp_value="storage_stats.storage_distribution.timelapse_recordings.percentage",
|
ufp_value="storage_stats.storage_distribution.timelapse_recordings.percentage",
|
||||||
@@ -442,7 +427,6 @@ NVR_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
|||||||
key="record_timelapse",
|
key="record_timelapse",
|
||||||
translation_key="type_continuous_video",
|
translation_key="type_continuous_video",
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
icon="mdi:server",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
ufp_value="storage_stats.storage_distribution.continuous_recordings.percentage",
|
ufp_value="storage_stats.storage_distribution.continuous_recordings.percentage",
|
||||||
@@ -452,7 +436,6 @@ NVR_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
|||||||
key="record_detections",
|
key="record_detections",
|
||||||
translation_key="type_detections_video",
|
translation_key="type_detections_video",
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
icon="mdi:server",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
ufp_value="storage_stats.storage_distribution.detections_recordings.percentage",
|
ufp_value="storage_stats.storage_distribution.detections_recordings.percentage",
|
||||||
@@ -462,7 +445,6 @@ NVR_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
|||||||
key="resolution_HD",
|
key="resolution_HD",
|
||||||
translation_key="resolution_hd_video",
|
translation_key="resolution_hd_video",
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
icon="mdi:cctv",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
ufp_value="storage_stats.storage_distribution.hd_usage.percentage",
|
ufp_value="storage_stats.storage_distribution.hd_usage.percentage",
|
||||||
@@ -472,7 +454,6 @@ NVR_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
|||||||
key="resolution_4K",
|
key="resolution_4K",
|
||||||
translation_key="resolution_4k_video",
|
translation_key="resolution_4k_video",
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
icon="mdi:cctv",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
ufp_value="storage_stats.storage_distribution.uhd_usage.percentage",
|
ufp_value="storage_stats.storage_distribution.uhd_usage.percentage",
|
||||||
@@ -482,7 +463,6 @@ NVR_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
|||||||
key="resolution_free",
|
key="resolution_free",
|
||||||
translation_key="resolution_free_space",
|
translation_key="resolution_free_space",
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
icon="mdi:cctv",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
ufp_value="storage_stats.storage_distribution.free.percentage",
|
ufp_value="storage_stats.storage_distribution.free.percentage",
|
||||||
@@ -492,7 +472,6 @@ NVR_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
|||||||
key="record_capacity",
|
key="record_capacity",
|
||||||
translation_key="recording_capacity",
|
translation_key="recording_capacity",
|
||||||
native_unit_of_measurement=UnitOfTime.SECONDS,
|
native_unit_of_measurement=UnitOfTime.SECONDS,
|
||||||
icon="mdi:record-rec",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
ufp_value_fn=_get_nvr_recording_capacity,
|
ufp_value_fn=_get_nvr_recording_capacity,
|
||||||
@@ -504,7 +483,6 @@ NVR_DISABLED_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
|||||||
key="cpu_utilization",
|
key="cpu_utilization",
|
||||||
translation_key="cpu_utilization",
|
translation_key="cpu_utilization",
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
icon="mdi:speedometer",
|
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
@@ -524,7 +502,6 @@ NVR_DISABLED_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
|||||||
key="memory_utilization",
|
key="memory_utilization",
|
||||||
translation_key="memory_utilization",
|
translation_key="memory_utilization",
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
icon="mdi:memory",
|
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
state_class=SensorStateClass.MEASUREMENT,
|
state_class=SensorStateClass.MEASUREMENT,
|
||||||
@@ -544,7 +521,6 @@ LIGHT_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
|||||||
ProtectSensorEntityDescription(
|
ProtectSensorEntityDescription(
|
||||||
key="sensitivity",
|
key="sensitivity",
|
||||||
translation_key="motion_sensitivity",
|
translation_key="motion_sensitivity",
|
||||||
icon="mdi:walk",
|
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_value="light_device_settings.pir_sensitivity",
|
ufp_value="light_device_settings.pir_sensitivity",
|
||||||
@@ -553,7 +529,6 @@ LIGHT_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
|||||||
ProtectSensorEntityDescription[Light](
|
ProtectSensorEntityDescription[Light](
|
||||||
key="light_motion",
|
key="light_motion",
|
||||||
translation_key="light_mode",
|
translation_key="light_mode",
|
||||||
icon="mdi:spotlight",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_value_fn=async_get_light_motion_current,
|
ufp_value_fn=async_get_light_motion_current,
|
||||||
ufp_perm=PermRequired.NO_WRITE,
|
ufp_perm=PermRequired.NO_WRITE,
|
||||||
@@ -561,7 +536,6 @@ LIGHT_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
|||||||
ProtectSensorEntityDescription(
|
ProtectSensorEntityDescription(
|
||||||
key="paired_camera",
|
key="paired_camera",
|
||||||
translation_key="paired_camera",
|
translation_key="paired_camera",
|
||||||
icon="mdi:cctv",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_value="camera.display_name",
|
ufp_value="camera.display_name",
|
||||||
ufp_perm=PermRequired.NO_WRITE,
|
ufp_perm=PermRequired.NO_WRITE,
|
||||||
@@ -583,13 +557,11 @@ CHIME_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
|||||||
key="last_ring",
|
key="last_ring",
|
||||||
translation_key="last_ring",
|
translation_key="last_ring",
|
||||||
device_class=SensorDeviceClass.TIMESTAMP,
|
device_class=SensorDeviceClass.TIMESTAMP,
|
||||||
icon="mdi:bell",
|
|
||||||
ufp_value="last_ring",
|
ufp_value="last_ring",
|
||||||
),
|
),
|
||||||
ProtectSensorEntityDescription(
|
ProtectSensorEntityDescription(
|
||||||
key="volume",
|
key="volume",
|
||||||
translation_key="volume",
|
translation_key="volume",
|
||||||
icon="mdi:speaker",
|
|
||||||
native_unit_of_measurement=PERCENTAGE,
|
native_unit_of_measurement=PERCENTAGE,
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_value="volume",
|
ufp_value="volume",
|
||||||
@@ -601,7 +573,6 @@ VIEWER_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
|
|||||||
ProtectSensorEntityDescription(
|
ProtectSensorEntityDescription(
|
||||||
key="viewer",
|
key="viewer",
|
||||||
translation_key="liveview",
|
translation_key="liveview",
|
||||||
icon="mdi:view-dashboard",
|
|
||||||
entity_category=EntityCategory.DIAGNOSTIC,
|
entity_category=EntityCategory.DIAGNOSTIC,
|
||||||
ufp_value="liveview.name",
|
ufp_value="liveview.name",
|
||||||
ufp_perm=PermRequired.NO_WRITE,
|
ufp_perm=PermRequired.NO_WRITE,
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="ssh",
|
key="ssh",
|
||||||
translation_key="ssh_enabled",
|
translation_key="ssh_enabled",
|
||||||
icon="mdi:lock",
|
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_value="is_ssh_enabled",
|
ufp_value="is_ssh_enabled",
|
||||||
@@ -64,7 +63,6 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="status_light",
|
key="status_light",
|
||||||
translation_key="status_light",
|
translation_key="status_light",
|
||||||
icon="mdi:led-on",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_required_field="feature_flags.has_led_status",
|
ufp_required_field="feature_flags.has_led_status",
|
||||||
ufp_value="led_settings.is_enabled",
|
ufp_value="led_settings.is_enabled",
|
||||||
@@ -74,7 +72,6 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="hdr_mode",
|
key="hdr_mode",
|
||||||
translation_key="hdr_mode",
|
translation_key="hdr_mode",
|
||||||
icon="mdi:brightness-7",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
ufp_required_field="feature_flags.has_hdr",
|
ufp_required_field="feature_flags.has_hdr",
|
||||||
@@ -85,7 +82,6 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription[Camera](
|
ProtectSwitchEntityDescription[Camera](
|
||||||
key="high_fps",
|
key="high_fps",
|
||||||
translation_key="high_fps",
|
translation_key="high_fps",
|
||||||
icon="mdi:video-high-definition",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_required_field="feature_flags.has_highfps",
|
ufp_required_field="feature_flags.has_highfps",
|
||||||
ufp_value="is_high_fps_enabled",
|
ufp_value="is_high_fps_enabled",
|
||||||
@@ -95,7 +91,6 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="system_sounds",
|
key="system_sounds",
|
||||||
translation_key="system_sounds",
|
translation_key="system_sounds",
|
||||||
icon="mdi:speaker",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_required_field="has_speaker",
|
ufp_required_field="has_speaker",
|
||||||
ufp_value="speaker_settings.are_system_sounds_enabled",
|
ufp_value="speaker_settings.are_system_sounds_enabled",
|
||||||
@@ -106,7 +101,6 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="osd_name",
|
key="osd_name",
|
||||||
translation_key="overlay_show_name",
|
translation_key="overlay_show_name",
|
||||||
icon="mdi:fullscreen",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_value="osd_settings.is_name_enabled",
|
ufp_value="osd_settings.is_name_enabled",
|
||||||
ufp_set_method="set_osd_name",
|
ufp_set_method="set_osd_name",
|
||||||
@@ -115,7 +109,6 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="osd_date",
|
key="osd_date",
|
||||||
translation_key="overlay_show_date",
|
translation_key="overlay_show_date",
|
||||||
icon="mdi:fullscreen",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_value="osd_settings.is_date_enabled",
|
ufp_value="osd_settings.is_date_enabled",
|
||||||
ufp_set_method="set_osd_date",
|
ufp_set_method="set_osd_date",
|
||||||
@@ -124,7 +117,6 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="osd_logo",
|
key="osd_logo",
|
||||||
translation_key="overlay_show_logo",
|
translation_key="overlay_show_logo",
|
||||||
icon="mdi:fullscreen",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_value="osd_settings.is_logo_enabled",
|
ufp_value="osd_settings.is_logo_enabled",
|
||||||
ufp_set_method="set_osd_logo",
|
ufp_set_method="set_osd_logo",
|
||||||
@@ -133,7 +125,6 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="osd_bitrate",
|
key="osd_bitrate",
|
||||||
translation_key="overlay_show_nerd_mode",
|
translation_key="overlay_show_nerd_mode",
|
||||||
icon="mdi:fullscreen",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_value="osd_settings.is_debug_enabled",
|
ufp_value="osd_settings.is_debug_enabled",
|
||||||
ufp_set_method="set_osd_bitrate",
|
ufp_set_method="set_osd_bitrate",
|
||||||
@@ -142,7 +133,6 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="color_night_vision",
|
key="color_night_vision",
|
||||||
translation_key="color_night_vision",
|
translation_key="color_night_vision",
|
||||||
icon="mdi:light-flood-down",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_required_field="has_color_night_vision",
|
ufp_required_field="has_color_night_vision",
|
||||||
ufp_value="isp_settings.is_color_night_vision_enabled",
|
ufp_value="isp_settings.is_color_night_vision_enabled",
|
||||||
@@ -152,7 +142,6 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="motion",
|
key="motion",
|
||||||
translation_key="motion",
|
translation_key="motion",
|
||||||
icon="mdi:run-fast",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_value="recording_settings.enable_motion_detection",
|
ufp_value="recording_settings.enable_motion_detection",
|
||||||
ufp_enabled="is_recording_enabled",
|
ufp_enabled="is_recording_enabled",
|
||||||
@@ -162,7 +151,6 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="smart_person",
|
key="smart_person",
|
||||||
translation_key="detections_person",
|
translation_key="detections_person",
|
||||||
icon="mdi:walk",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_required_field="can_detect_person",
|
ufp_required_field="can_detect_person",
|
||||||
ufp_value="is_person_detection_on",
|
ufp_value="is_person_detection_on",
|
||||||
@@ -173,7 +161,6 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="smart_vehicle",
|
key="smart_vehicle",
|
||||||
translation_key="detections_vehicle",
|
translation_key="detections_vehicle",
|
||||||
icon="mdi:car",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_required_field="can_detect_vehicle",
|
ufp_required_field="can_detect_vehicle",
|
||||||
ufp_value="is_vehicle_detection_on",
|
ufp_value="is_vehicle_detection_on",
|
||||||
@@ -184,7 +171,6 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="smart_animal",
|
key="smart_animal",
|
||||||
translation_key="detections_animal",
|
translation_key="detections_animal",
|
||||||
icon="mdi:paw",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_required_field="can_detect_animal",
|
ufp_required_field="can_detect_animal",
|
||||||
ufp_value="is_animal_detection_on",
|
ufp_value="is_animal_detection_on",
|
||||||
@@ -195,7 +181,6 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="smart_package",
|
key="smart_package",
|
||||||
translation_key="detections_package",
|
translation_key="detections_package",
|
||||||
icon="mdi:package-variant-closed",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_required_field="can_detect_package",
|
ufp_required_field="can_detect_package",
|
||||||
ufp_value="is_package_detection_on",
|
ufp_value="is_package_detection_on",
|
||||||
@@ -206,7 +191,6 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="smart_licenseplate",
|
key="smart_licenseplate",
|
||||||
translation_key="detections_license_plate",
|
translation_key="detections_license_plate",
|
||||||
icon="mdi:car",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_required_field="can_detect_license_plate",
|
ufp_required_field="can_detect_license_plate",
|
||||||
ufp_value="is_license_plate_detection_on",
|
ufp_value="is_license_plate_detection_on",
|
||||||
@@ -217,7 +201,6 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="smart_smoke",
|
key="smart_smoke",
|
||||||
translation_key="detections_smoke",
|
translation_key="detections_smoke",
|
||||||
icon="mdi:fire",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_required_field="can_detect_smoke",
|
ufp_required_field="can_detect_smoke",
|
||||||
ufp_value="is_smoke_detection_on",
|
ufp_value="is_smoke_detection_on",
|
||||||
@@ -228,7 +211,6 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="smart_cmonx",
|
key="smart_cmonx",
|
||||||
translation_key="detections_co_alarm",
|
translation_key="detections_co_alarm",
|
||||||
icon="mdi:molecule-co",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_required_field="can_detect_co",
|
ufp_required_field="can_detect_co",
|
||||||
ufp_value="is_co_detection_on",
|
ufp_value="is_co_detection_on",
|
||||||
@@ -239,7 +221,6 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="smart_siren",
|
key="smart_siren",
|
||||||
translation_key="detections_siren",
|
translation_key="detections_siren",
|
||||||
icon="mdi:alarm-bell",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_required_field="can_detect_siren",
|
ufp_required_field="can_detect_siren",
|
||||||
ufp_value="is_siren_detection_on",
|
ufp_value="is_siren_detection_on",
|
||||||
@@ -250,7 +231,6 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="smart_baby_cry",
|
key="smart_baby_cry",
|
||||||
translation_key="detections_baby_cry",
|
translation_key="detections_baby_cry",
|
||||||
icon="mdi:cradle",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_required_field="can_detect_baby_cry",
|
ufp_required_field="can_detect_baby_cry",
|
||||||
ufp_value="is_baby_cry_detection_on",
|
ufp_value="is_baby_cry_detection_on",
|
||||||
@@ -261,7 +241,6 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="smart_speak",
|
key="smart_speak",
|
||||||
translation_key="detections_speak",
|
translation_key="detections_speak",
|
||||||
icon="mdi:account-voice",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_required_field="can_detect_speaking",
|
ufp_required_field="can_detect_speaking",
|
||||||
ufp_value="is_speaking_detection_on",
|
ufp_value="is_speaking_detection_on",
|
||||||
@@ -272,7 +251,6 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="smart_bark",
|
key="smart_bark",
|
||||||
translation_key="detections_bark",
|
translation_key="detections_bark",
|
||||||
icon="mdi:dog",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_required_field="can_detect_bark",
|
ufp_required_field="can_detect_bark",
|
||||||
ufp_value="is_bark_detection_on",
|
ufp_value="is_bark_detection_on",
|
||||||
@@ -283,7 +261,6 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="smart_car_alarm",
|
key="smart_car_alarm",
|
||||||
translation_key="detections_car_alarm",
|
translation_key="detections_car_alarm",
|
||||||
icon="mdi:car",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_required_field="can_detect_car_alarm",
|
ufp_required_field="can_detect_car_alarm",
|
||||||
ufp_value="is_car_alarm_detection_on",
|
ufp_value="is_car_alarm_detection_on",
|
||||||
@@ -294,7 +271,6 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="smart_car_horn",
|
key="smart_car_horn",
|
||||||
translation_key="detections_car_horn",
|
translation_key="detections_car_horn",
|
||||||
icon="mdi:bugle",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_required_field="can_detect_car_horn",
|
ufp_required_field="can_detect_car_horn",
|
||||||
ufp_value="is_car_horn_detection_on",
|
ufp_value="is_car_horn_detection_on",
|
||||||
@@ -305,7 +281,6 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="smart_glass_break",
|
key="smart_glass_break",
|
||||||
translation_key="detections_glass_break",
|
translation_key="detections_glass_break",
|
||||||
icon="mdi:glass-fragile",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_required_field="can_detect_glass_break",
|
ufp_required_field="can_detect_glass_break",
|
||||||
ufp_value="is_glass_break_detection_on",
|
ufp_value="is_glass_break_detection_on",
|
||||||
@@ -316,7 +291,6 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="track_person",
|
key="track_person",
|
||||||
translation_key="tracking_person",
|
translation_key="tracking_person",
|
||||||
icon="mdi:walk",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_required_field="feature_flags.is_ptz",
|
ufp_required_field="feature_flags.is_ptz",
|
||||||
ufp_value="is_person_tracking_enabled",
|
ufp_value="is_person_tracking_enabled",
|
||||||
@@ -328,7 +302,6 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
PRIVACY_MODE_SWITCH = ProtectSwitchEntityDescription[Camera](
|
PRIVACY_MODE_SWITCH = ProtectSwitchEntityDescription[Camera](
|
||||||
key="privacy_mode",
|
key="privacy_mode",
|
||||||
translation_key="privacy_mode",
|
translation_key="privacy_mode",
|
||||||
icon="mdi:eye-settings",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_required_field="feature_flags.has_privacy_mask",
|
ufp_required_field="feature_flags.has_privacy_mask",
|
||||||
ufp_value="is_privacy_on",
|
ufp_value="is_privacy_on",
|
||||||
@@ -339,7 +312,6 @@ SENSE_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="status_light",
|
key="status_light",
|
||||||
translation_key="status_light",
|
translation_key="status_light",
|
||||||
icon="mdi:led-on",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_value="led_settings.is_enabled",
|
ufp_value="led_settings.is_enabled",
|
||||||
ufp_set_method="set_status_light",
|
ufp_set_method="set_status_light",
|
||||||
@@ -348,7 +320,6 @@ SENSE_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="motion",
|
key="motion",
|
||||||
translation_key="detections_motion",
|
translation_key="detections_motion",
|
||||||
icon="mdi:walk",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_value="motion_settings.is_enabled",
|
ufp_value="motion_settings.is_enabled",
|
||||||
ufp_set_method="set_motion_status",
|
ufp_set_method="set_motion_status",
|
||||||
@@ -357,7 +328,6 @@ SENSE_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="temperature",
|
key="temperature",
|
||||||
translation_key="temperature_sensor",
|
translation_key="temperature_sensor",
|
||||||
icon="mdi:thermometer",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_value="temperature_settings.is_enabled",
|
ufp_value="temperature_settings.is_enabled",
|
||||||
ufp_set_method="set_temperature_status",
|
ufp_set_method="set_temperature_status",
|
||||||
@@ -366,7 +336,6 @@ SENSE_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="humidity",
|
key="humidity",
|
||||||
translation_key="humidity_sensor",
|
translation_key="humidity_sensor",
|
||||||
icon="mdi:water-percent",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_value="humidity_settings.is_enabled",
|
ufp_value="humidity_settings.is_enabled",
|
||||||
ufp_set_method="set_humidity_status",
|
ufp_set_method="set_humidity_status",
|
||||||
@@ -375,7 +344,6 @@ SENSE_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="light",
|
key="light",
|
||||||
translation_key="light_sensor",
|
translation_key="light_sensor",
|
||||||
icon="mdi:brightness-5",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_value="light_settings.is_enabled",
|
ufp_value="light_settings.is_enabled",
|
||||||
ufp_set_method="set_light_status",
|
ufp_set_method="set_light_status",
|
||||||
@@ -396,7 +364,6 @@ LIGHT_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="ssh",
|
key="ssh",
|
||||||
translation_key="ssh_enabled",
|
translation_key="ssh_enabled",
|
||||||
icon="mdi:lock",
|
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_value="is_ssh_enabled",
|
ufp_value="is_ssh_enabled",
|
||||||
@@ -406,7 +373,6 @@ LIGHT_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="status_light",
|
key="status_light",
|
||||||
translation_key="status_light",
|
translation_key="status_light",
|
||||||
icon="mdi:led-on",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_value="light_device_settings.is_indicator_enabled",
|
ufp_value="light_device_settings.is_indicator_enabled",
|
||||||
ufp_set_method="set_status_light",
|
ufp_set_method="set_status_light",
|
||||||
@@ -418,7 +384,6 @@ DOORLOCK_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="status_light",
|
key="status_light",
|
||||||
translation_key="status_light",
|
translation_key="status_light",
|
||||||
icon="mdi:led-on",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_value="led_settings.is_enabled",
|
ufp_value="led_settings.is_enabled",
|
||||||
ufp_set_method="set_status_light",
|
ufp_set_method="set_status_light",
|
||||||
@@ -430,7 +395,6 @@ VIEWER_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="ssh",
|
key="ssh",
|
||||||
translation_key="ssh_enabled",
|
translation_key="ssh_enabled",
|
||||||
icon="mdi:lock",
|
|
||||||
entity_registry_enabled_default=False,
|
entity_registry_enabled_default=False,
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_value="is_ssh_enabled",
|
ufp_value="is_ssh_enabled",
|
||||||
@@ -443,7 +407,6 @@ NVR_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="analytics_enabled",
|
key="analytics_enabled",
|
||||||
translation_key="analytics_enabled",
|
translation_key="analytics_enabled",
|
||||||
icon="mdi:google-analytics",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_value="is_analytics_enabled",
|
ufp_value="is_analytics_enabled",
|
||||||
ufp_set_method="set_anonymous_analytics",
|
ufp_set_method="set_anonymous_analytics",
|
||||||
@@ -451,7 +414,6 @@ NVR_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
|
|||||||
ProtectSwitchEntityDescription(
|
ProtectSwitchEntityDescription(
|
||||||
key="insights_enabled",
|
key="insights_enabled",
|
||||||
translation_key="insights_enabled",
|
translation_key="insights_enabled",
|
||||||
icon="mdi:magnify",
|
|
||||||
entity_category=EntityCategory.CONFIG,
|
entity_category=EntityCategory.CONFIG,
|
||||||
ufp_value="is_insights_enabled",
|
ufp_value="is_insights_enabled",
|
||||||
ufp_set_method="set_insights",
|
ufp_set_method="set_insights",
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ from homeassistant.helpers import config_validation as cv, device_registry as dr
|
|||||||
from homeassistant.helpers.storage import STORAGE_DIR
|
from homeassistant.helpers.storage import STORAGE_DIR
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import CONF_VLP_FILE, DOMAIN
|
||||||
from .services import async_setup_services
|
from .services import async_setup_services
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@@ -98,8 +98,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
async def async_setup_entry(hass: HomeAssistant, entry: VelbusConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: VelbusConfigEntry) -> bool:
|
||||||
"""Establish connection with velbus."""
|
"""Establish connection with velbus."""
|
||||||
controller = Velbus(
|
controller = Velbus(
|
||||||
entry.data[CONF_PORT],
|
dsn=entry.data[CONF_PORT],
|
||||||
cache_dir=hass.config.path(STORAGE_DIR, f"velbuscache-{entry.entry_id}"),
|
cache_dir=hass.config.path(STORAGE_DIR, f"velbuscache-{entry.entry_id}"),
|
||||||
|
vlp_file=entry.data.get(CONF_VLP_FILE),
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
await controller.connect()
|
await controller.connect()
|
||||||
|
|||||||
@@ -2,18 +2,35 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
from pathlib import Path
|
||||||
|
import shutil
|
||||||
|
from typing import Any, Final
|
||||||
|
|
||||||
import serial.tools.list_ports
|
import serial.tools.list_ports
|
||||||
import velbusaio.controller
|
import velbusaio.controller
|
||||||
from velbusaio.exceptions import VelbusConnectionFailed
|
from velbusaio.exceptions import VelbusConnectionFailed
|
||||||
|
from velbusaio.vlp_reader import VlpFile
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
|
from homeassistant.components.file_upload import process_uploaded_file
|
||||||
|
from homeassistant.config_entries import (
|
||||||
|
SOURCE_RECONFIGURE,
|
||||||
|
ConfigFlow,
|
||||||
|
ConfigFlowResult,
|
||||||
|
)
|
||||||
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT
|
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
from homeassistant.helpers import selector
|
||||||
from homeassistant.helpers.service_info.usb import UsbServiceInfo
|
from homeassistant.helpers.service_info.usb import UsbServiceInfo
|
||||||
|
|
||||||
from .const import CONF_TLS, DOMAIN
|
from .const import CONF_TLS, CONF_VLP_FILE, DOMAIN
|
||||||
|
|
||||||
|
STORAGE_PATH: Final = ".storage/velbus.{key}.vlp"
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidVlpFile(HomeAssistantError):
|
||||||
|
"""Error to indicate that the uploaded file is not a valid VLP file."""
|
||||||
|
|
||||||
|
|
||||||
class VelbusConfigFlow(ConfigFlow, domain=DOMAIN):
|
class VelbusConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||||
@@ -24,14 +41,15 @@ class VelbusConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
"""Initialize the velbus config flow."""
|
"""Initialize the velbus config flow."""
|
||||||
self._errors: dict[str, str] = {}
|
|
||||||
self._device: str = ""
|
self._device: str = ""
|
||||||
|
self._vlp_file: str | None = None
|
||||||
self._title: str = ""
|
self._title: str = ""
|
||||||
|
|
||||||
def _create_device(self) -> ConfigFlowResult:
|
def _create_device(self) -> ConfigFlowResult:
|
||||||
"""Create an entry async."""
|
"""Create an entry async."""
|
||||||
return self.async_create_entry(
|
return self.async_create_entry(
|
||||||
title=self._title, data={CONF_PORT: self._device}
|
title=self._title,
|
||||||
|
data={CONF_PORT: self._device, CONF_VLP_FILE: self._vlp_file},
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _test_connection(self) -> bool:
|
async def _test_connection(self) -> bool:
|
||||||
@@ -41,7 +59,6 @@ class VelbusConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
await controller.connect()
|
await controller.connect()
|
||||||
await controller.stop()
|
await controller.stop()
|
||||||
except VelbusConnectionFailed:
|
except VelbusConnectionFailed:
|
||||||
self._errors[CONF_PORT] = "cannot_connect"
|
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -57,6 +74,7 @@ class VelbusConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
self, user_input: dict[str, Any] | None = None
|
self, user_input: dict[str, Any] | None = None
|
||||||
) -> ConfigFlowResult:
|
) -> ConfigFlowResult:
|
||||||
"""Handle network step."""
|
"""Handle network step."""
|
||||||
|
step_errors: dict[str, str] = {}
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
self._title = "Velbus Network"
|
self._title = "Velbus Network"
|
||||||
if user_input[CONF_TLS]:
|
if user_input[CONF_TLS]:
|
||||||
@@ -68,7 +86,8 @@ class VelbusConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
self._device += f"{user_input[CONF_HOST]}:{user_input[CONF_PORT]}"
|
self._device += f"{user_input[CONF_HOST]}:{user_input[CONF_PORT]}"
|
||||||
self._async_abort_entries_match({CONF_PORT: self._device})
|
self._async_abort_entries_match({CONF_PORT: self._device})
|
||||||
if await self._test_connection():
|
if await self._test_connection():
|
||||||
return self._create_device()
|
return await self.async_step_vlp()
|
||||||
|
step_errors[CONF_HOST] = "cannot_connect"
|
||||||
else:
|
else:
|
||||||
user_input = {
|
user_input = {
|
||||||
CONF_TLS: True,
|
CONF_TLS: True,
|
||||||
@@ -88,13 +107,14 @@ class VelbusConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
),
|
),
|
||||||
suggested_values=user_input,
|
suggested_values=user_input,
|
||||||
),
|
),
|
||||||
errors=self._errors,
|
errors=step_errors,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_step_usbselect(
|
async def async_step_usbselect(
|
||||||
self, user_input: dict[str, Any] | None = None
|
self, user_input: dict[str, Any] | None = None
|
||||||
) -> ConfigFlowResult:
|
) -> ConfigFlowResult:
|
||||||
"""Handle usb select step."""
|
"""Handle usb select step."""
|
||||||
|
step_errors: dict[str, str] = {}
|
||||||
ports = await self.hass.async_add_executor_job(serial.tools.list_ports.comports)
|
ports = await self.hass.async_add_executor_job(serial.tools.list_ports.comports)
|
||||||
list_of_ports = [
|
list_of_ports = [
|
||||||
f"{p}{', s/n: ' + p.serial_number if p.serial_number else ''}"
|
f"{p}{', s/n: ' + p.serial_number if p.serial_number else ''}"
|
||||||
@@ -107,7 +127,8 @@ class VelbusConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
self._device = ports[list_of_ports.index(user_input[CONF_PORT])].device
|
self._device = ports[list_of_ports.index(user_input[CONF_PORT])].device
|
||||||
self._async_abort_entries_match({CONF_PORT: self._device})
|
self._async_abort_entries_match({CONF_PORT: self._device})
|
||||||
if await self._test_connection():
|
if await self._test_connection():
|
||||||
return self._create_device()
|
return await self.async_step_vlp()
|
||||||
|
step_errors[CONF_PORT] = "cannot_connect"
|
||||||
else:
|
else:
|
||||||
user_input = {}
|
user_input = {}
|
||||||
user_input[CONF_PORT] = ""
|
user_input[CONF_PORT] = ""
|
||||||
@@ -118,7 +139,7 @@ class VelbusConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
vol.Schema({vol.Required(CONF_PORT): vol.In(list_of_ports)}),
|
vol.Schema({vol.Required(CONF_PORT): vol.In(list_of_ports)}),
|
||||||
suggested_values=user_input,
|
suggested_values=user_input,
|
||||||
),
|
),
|
||||||
errors=self._errors,
|
errors=step_errors,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_step_usb(self, discovery_info: UsbServiceInfo) -> ConfigFlowResult:
|
async def async_step_usb(self, discovery_info: UsbServiceInfo) -> ConfigFlowResult:
|
||||||
@@ -144,3 +165,75 @@ class VelbusConfigFlow(ConfigFlow, domain=DOMAIN):
|
|||||||
step_id="discovery_confirm",
|
step_id="discovery_confirm",
|
||||||
description_placeholders={CONF_NAME: self._title},
|
description_placeholders={CONF_NAME: self._title},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
async def _validate_vlp_file(self, file_path: str) -> None:
|
||||||
|
"""Validate VLP file and raise exception if invalid."""
|
||||||
|
vlpfile = VlpFile(file_path)
|
||||||
|
await vlpfile.read()
|
||||||
|
if not vlpfile.get():
|
||||||
|
raise InvalidVlpFile("no_modules")
|
||||||
|
|
||||||
|
async def async_step_vlp(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> ConfigFlowResult:
|
||||||
|
"""Step when user wants to use the VLP file."""
|
||||||
|
step_errors: dict[str, str] = {}
|
||||||
|
if user_input is not None:
|
||||||
|
if CONF_VLP_FILE not in user_input or user_input[CONF_VLP_FILE] == "":
|
||||||
|
# The VLP file is optional, so allow skipping it
|
||||||
|
self._vlp_file = None
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
# handle the file upload
|
||||||
|
self._vlp_file = await self.hass.async_add_executor_job(
|
||||||
|
save_uploaded_vlp_file, self.hass, user_input[CONF_VLP_FILE]
|
||||||
|
)
|
||||||
|
# validate it
|
||||||
|
await self._validate_vlp_file(self._vlp_file)
|
||||||
|
except InvalidVlpFile as e:
|
||||||
|
step_errors[CONF_VLP_FILE] = str(e)
|
||||||
|
if self.source == SOURCE_RECONFIGURE:
|
||||||
|
old_entry = self._get_reconfigure_entry()
|
||||||
|
return self.async_update_reload_and_abort(
|
||||||
|
old_entry,
|
||||||
|
data={
|
||||||
|
CONF_VLP_FILE: self._vlp_file,
|
||||||
|
CONF_PORT: old_entry.data.get(CONF_PORT),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if not step_errors:
|
||||||
|
return self._create_device()
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="vlp",
|
||||||
|
data_schema=self.add_suggested_values_to_schema(
|
||||||
|
vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Optional(CONF_VLP_FILE): selector.FileSelector(
|
||||||
|
config=selector.FileSelectorConfig(accept=".vlp")
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
suggested_values=user_input,
|
||||||
|
),
|
||||||
|
errors=step_errors,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_reconfigure(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> ConfigFlowResult:
|
||||||
|
"""Handle reconfiguration."""
|
||||||
|
return await self.async_step_vlp()
|
||||||
|
|
||||||
|
|
||||||
|
def save_uploaded_vlp_file(hass: HomeAssistant, uploaded_file_id: str) -> str:
|
||||||
|
"""Validate the uploaded file and move it to the storage directory.
|
||||||
|
|
||||||
|
Blocking function needs to be called in executor.
|
||||||
|
"""
|
||||||
|
|
||||||
|
with process_uploaded_file(hass, uploaded_file_id) as file:
|
||||||
|
dest_path = Path(hass.config.path(STORAGE_PATH.format(key=uploaded_file_id)))
|
||||||
|
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
shutil.move(file, dest_path)
|
||||||
|
return str(dest_path)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ DOMAIN: Final = "velbus"
|
|||||||
CONF_CONFIG_ENTRY: Final = "config_entry"
|
CONF_CONFIG_ENTRY: Final = "config_entry"
|
||||||
CONF_MEMO_TEXT: Final = "memo_text"
|
CONF_MEMO_TEXT: Final = "memo_text"
|
||||||
CONF_TLS: Final = "tls"
|
CONF_TLS: Final = "tls"
|
||||||
|
CONF_VLP_FILE: Final = "vlp_file"
|
||||||
|
|
||||||
SERVICE_SCAN: Final = "scan"
|
SERVICE_SCAN: Final = "scan"
|
||||||
SERVICE_SYNC: Final = "sync_clock"
|
SERVICE_SYNC: Final = "sync_clock"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
"name": "Velbus",
|
"name": "Velbus",
|
||||||
"codeowners": ["@Cereal2nd", "@brefra"],
|
"codeowners": ["@Cereal2nd", "@brefra"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"dependencies": ["usb"],
|
"dependencies": ["usb", "file_upload"],
|
||||||
"documentation": "https://www.home-assistant.io/integrations/velbus",
|
"documentation": "https://www.home-assistant.io/integrations/velbus",
|
||||||
"integration_type": "hub",
|
"integration_type": "hub",
|
||||||
"iot_class": "local_push",
|
"iot_class": "local_push",
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"abort": {
|
"abort": {
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||||
|
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
|
||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
|
||||||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
|
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
|
||||||
|
"no_modules": "No Velbus modules found, please check your VLP file."
|
||||||
},
|
},
|
||||||
"step": {
|
"step": {
|
||||||
"network": {
|
"network": {
|
||||||
@@ -41,6 +43,16 @@
|
|||||||
"usbselect": "Via USB device"
|
"usbselect": "Via USB device"
|
||||||
},
|
},
|
||||||
"title": "Define the Velbus connection"
|
"title": "Define the Velbus connection"
|
||||||
|
},
|
||||||
|
"vlp": {
|
||||||
|
"data": {
|
||||||
|
"vlp_file": "Path to VLP file"
|
||||||
|
},
|
||||||
|
"data_description": {
|
||||||
|
"vlp_file": "Select the VLP file from your filesystem."
|
||||||
|
},
|
||||||
|
"description": "You can optionally provide a VLP file to improve module detection. The VLP file is the config file from VelbusLink that contains all module information. If you do not provide it now, you can always add it later in the integration options. Without this file, Home Assistant will try to detect the modules automatically, but this can take longer time and some modules might not be detected correctly.",
|
||||||
|
"title": "Optional VLP file"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from typing import Any
|
|||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
from wled import WLED, Device, WLEDConnectionError, WLEDUnsupportedVersionError
|
from wled import WLED, Device, WLEDConnectionError, WLEDUnsupportedVersionError
|
||||||
|
import yarl
|
||||||
|
|
||||||
from homeassistant.components import onboarding
|
from homeassistant.components import onboarding
|
||||||
from homeassistant.config_entries import (
|
from homeassistant.config_entries import (
|
||||||
@@ -24,6 +25,15 @@ from .const import CONF_KEEP_MAIN_LIGHT, DEFAULT_KEEP_MAIN_LIGHT, DOMAIN
|
|||||||
from .coordinator import WLEDConfigEntry
|
from .coordinator import WLEDConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_host(host: str) -> str:
|
||||||
|
"""Normalize host by extracting hostname if a URL is provided."""
|
||||||
|
try:
|
||||||
|
return yarl.URL(host).host or host
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return host
|
||||||
|
|
||||||
|
|
||||||
class WLEDFlowHandler(ConfigFlow, domain=DOMAIN):
|
class WLEDFlowHandler(ConfigFlow, domain=DOMAIN):
|
||||||
"""Handle a WLED config flow."""
|
"""Handle a WLED config flow."""
|
||||||
|
|
||||||
@@ -46,8 +56,9 @@ class WLEDFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
errors = {}
|
errors = {}
|
||||||
|
|
||||||
if user_input is not None:
|
if user_input is not None:
|
||||||
|
host = _normalize_host(user_input[CONF_HOST])
|
||||||
try:
|
try:
|
||||||
device = await self._async_get_device(user_input[CONF_HOST])
|
device = await self._async_get_device(host)
|
||||||
except WLEDUnsupportedVersionError:
|
except WLEDUnsupportedVersionError:
|
||||||
errors["base"] = "unsupported_version"
|
errors["base"] = "unsupported_version"
|
||||||
except WLEDConnectionError:
|
except WLEDConnectionError:
|
||||||
@@ -67,16 +78,12 @@ class WLEDFlowHandler(ConfigFlow, domain=DOMAIN):
|
|||||||
)
|
)
|
||||||
return self.async_update_reload_and_abort(
|
return self.async_update_reload_and_abort(
|
||||||
entry,
|
entry,
|
||||||
data_updates=user_input,
|
data_updates={CONF_HOST: host},
|
||||||
)
|
)
|
||||||
self._abort_if_unique_id_configured(
|
self._abort_if_unique_id_configured(updates={CONF_HOST: host})
|
||||||
updates={CONF_HOST: user_input[CONF_HOST]}
|
|
||||||
)
|
|
||||||
return self.async_create_entry(
|
return self.async_create_entry(
|
||||||
title=device.info.name,
|
title=device.info.name,
|
||||||
data={
|
data={CONF_HOST: host},
|
||||||
CONF_HOST: user_input[CONF_HOST],
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
data_schema = vol.Schema({vol.Required(CONF_HOST): str})
|
data_schema = vol.Schema({vol.Required(CONF_HOST): str})
|
||||||
if self.source == SOURCE_RECONFIGURE:
|
if self.source == SOURCE_RECONFIGURE:
|
||||||
|
|||||||
@@ -81,10 +81,11 @@ class XboxUpdateCoordinator(DataUpdateCoordinator[XboxData]):
|
|||||||
_LOGGER,
|
_LOGGER,
|
||||||
config_entry=config_entry,
|
config_entry=config_entry,
|
||||||
name=DOMAIN,
|
name=DOMAIN,
|
||||||
update_interval=timedelta(seconds=10),
|
update_interval=timedelta(seconds=15),
|
||||||
)
|
)
|
||||||
self.data = XboxData()
|
self.data = XboxData()
|
||||||
self.current_friends: set[str] = set()
|
self.current_friends: set[str] = set()
|
||||||
|
self.title_data: dict[str, Title] = {}
|
||||||
|
|
||||||
async def _async_setup(self) -> None:
|
async def _async_setup(self) -> None:
|
||||||
"""Set up coordinator."""
|
"""Set up coordinator."""
|
||||||
@@ -217,7 +218,6 @@ class XboxUpdateCoordinator(DataUpdateCoordinator[XboxData]):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# retrieve title details
|
# retrieve title details
|
||||||
title_data: dict[str, Title] = {}
|
|
||||||
for person in presence_data.values():
|
for person in presence_data.values():
|
||||||
if presence_detail := next(
|
if presence_detail := next(
|
||||||
(
|
(
|
||||||
@@ -227,6 +227,12 @@ class XboxUpdateCoordinator(DataUpdateCoordinator[XboxData]):
|
|||||||
),
|
),
|
||||||
None,
|
None,
|
||||||
):
|
):
|
||||||
|
if (
|
||||||
|
person.xuid in self.title_data
|
||||||
|
and presence_detail.title_id
|
||||||
|
== self.title_data[person.xuid].title_id
|
||||||
|
):
|
||||||
|
continue
|
||||||
try:
|
try:
|
||||||
title = await self.client.titlehub.get_title_info(
|
title = await self.client.titlehub.get_title_info(
|
||||||
presence_detail.title_id
|
presence_detail.title_id
|
||||||
@@ -250,7 +256,9 @@ class XboxUpdateCoordinator(DataUpdateCoordinator[XboxData]):
|
|||||||
translation_domain=DOMAIN,
|
translation_domain=DOMAIN,
|
||||||
translation_key="request_exception",
|
translation_key="request_exception",
|
||||||
) from e
|
) from e
|
||||||
title_data[person.xuid] = title.titles[0]
|
self.title_data[person.xuid] = title.titles[0]
|
||||||
|
else:
|
||||||
|
self.title_data.pop(person.xuid, None)
|
||||||
person.last_seen_date_time_utc = self.last_seen_timestamp(person)
|
person.last_seen_date_time_utc = self.last_seen_timestamp(person)
|
||||||
if (
|
if (
|
||||||
self.current_friends - (new_friends := set(presence_data))
|
self.current_friends - (new_friends := set(presence_data))
|
||||||
@@ -259,7 +267,7 @@ class XboxUpdateCoordinator(DataUpdateCoordinator[XboxData]):
|
|||||||
self.remove_stale_devices(new_friends)
|
self.remove_stale_devices(new_friends)
|
||||||
self.current_friends = new_friends
|
self.current_friends = new_friends
|
||||||
|
|
||||||
return XboxData(new_console_data, presence_data, title_data)
|
return XboxData(new_console_data, presence_data, self.title_data)
|
||||||
|
|
||||||
def last_seen_timestamp(self, person: Person) -> datetime | None:
|
def last_seen_timestamp(self, person: Person) -> datetime | None:
|
||||||
"""Returns the most recent of two timestamps."""
|
"""Returns the most recent of two timestamps."""
|
||||||
|
|||||||
@@ -2,18 +2,10 @@
|
|||||||
|
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from functools import partial
|
|
||||||
from typing import Any, Never
|
from typing import Any, Never
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from .deprecation import (
|
|
||||||
DeferredDeprecatedAlias,
|
|
||||||
all_with_deprecated_constants,
|
|
||||||
check_if_deprecated_constant,
|
|
||||||
dir_with_deprecated_constants,
|
|
||||||
)
|
|
||||||
|
|
||||||
type GPSType = tuple[float, float]
|
type GPSType = tuple[float, float]
|
||||||
type ConfigType = dict[str, Any]
|
type ConfigType = dict[str, Any]
|
||||||
type DiscoveryInfoType = dict[str, Any]
|
type DiscoveryInfoType = dict[str, Any]
|
||||||
@@ -35,32 +27,3 @@ class UndefinedType(Enum):
|
|||||||
|
|
||||||
|
|
||||||
UNDEFINED = UndefinedType._singleton # noqa: SLF001
|
UNDEFINED = UndefinedType._singleton # noqa: SLF001
|
||||||
|
|
||||||
|
|
||||||
def _deprecated_typing_helper(attr: str) -> DeferredDeprecatedAlias:
|
|
||||||
"""Help to make a DeferredDeprecatedAlias."""
|
|
||||||
|
|
||||||
def value_fn() -> Any:
|
|
||||||
import homeassistant.core # noqa: PLC0415
|
|
||||||
|
|
||||||
return getattr(homeassistant.core, attr)
|
|
||||||
|
|
||||||
return DeferredDeprecatedAlias(value_fn, f"homeassistant.core.{attr}", "2025.5")
|
|
||||||
|
|
||||||
|
|
||||||
# The following types should not used and
|
|
||||||
# are not present in the core code base.
|
|
||||||
# They are kept in order not to break custom integrations
|
|
||||||
# that may rely on them.
|
|
||||||
# Deprecated as of 2024.5 use types from homeassistant.core instead.
|
|
||||||
_DEPRECATED_ContextType = _deprecated_typing_helper("Context")
|
|
||||||
_DEPRECATED_EventType = _deprecated_typing_helper("Event")
|
|
||||||
_DEPRECATED_HomeAssistantType = _deprecated_typing_helper("HomeAssistant")
|
|
||||||
_DEPRECATED_ServiceCallType = _deprecated_typing_helper("ServiceCall")
|
|
||||||
|
|
||||||
# These can be removed if no deprecated constant are in this module anymore
|
|
||||||
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
|
|
||||||
__dir__ = partial(
|
|
||||||
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
|
|
||||||
)
|
|
||||||
__all__ = all_with_deprecated_constants(globals())
|
|
||||||
|
|||||||
@@ -492,15 +492,15 @@ filterwarnings = [
|
|||||||
# -- fixed, waiting for release / update
|
# -- fixed, waiting for release / update
|
||||||
# https://github.com/httplib2/httplib2/pull/226 - >=0.21.0
|
# https://github.com/httplib2/httplib2/pull/226 - >=0.21.0
|
||||||
"ignore:ssl.PROTOCOL_TLS is deprecated:DeprecationWarning:httplib2",
|
"ignore:ssl.PROTOCOL_TLS is deprecated:DeprecationWarning:httplib2",
|
||||||
# https://github.com/ReactiveX/RxPY/pull/716 - >4.0.4
|
# https://github.com/BerriAI/litellm/pull/17657 - >1.80.9
|
||||||
"ignore:datetime.*utcfromtimestamp\\(\\) is deprecated and scheduled for removal:DeprecationWarning:reactivex.internal.constants",
|
"ignore:Support for class-based `config` is deprecated, use ConfigDict instead:DeprecationWarning:litellm.types.llms.anthropic",
|
||||||
|
# https://github.com/allenporter/python-google-nest-sdm/pull/1229 - >9.1.2
|
||||||
|
"ignore:datetime.*utcnow\\(\\) is deprecated:DeprecationWarning:google_nest_sdm.device",
|
||||||
# https://github.com/rytilahti/python-miio/pull/1809 - >=0.6.0.dev0
|
# https://github.com/rytilahti/python-miio/pull/1809 - >=0.6.0.dev0
|
||||||
"ignore:datetime.*utcnow\\(\\) is deprecated and scheduled for removal:DeprecationWarning:miio.protocol",
|
"ignore:datetime.*utcnow\\(\\) is deprecated and scheduled for removal:DeprecationWarning:miio.protocol",
|
||||||
"ignore:datetime.*utcnow\\(\\) is deprecated and scheduled for removal:DeprecationWarning:miio.miioprotocol",
|
"ignore:datetime.*utcnow\\(\\) is deprecated and scheduled for removal:DeprecationWarning:miio.miioprotocol",
|
||||||
# https://github.com/rytilahti/python-miio/pull/1993 - >0.6.0.dev0
|
# https://github.com/rytilahti/python-miio/pull/1993 - >0.6.0.dev0
|
||||||
"ignore:functools.partial will be a method descriptor in future Python versions; wrap it in enum.member\\(\\) if you want to preserve the old behavior:FutureWarning:miio.miot_device",
|
"ignore:functools.partial will be a method descriptor in future Python versions; wrap it in enum.member\\(\\) if you want to preserve the old behavior:FutureWarning:miio.miot_device",
|
||||||
# https://github.com/okunishinishi/python-stringcase/commit/6a5c5bbd3fe5337862abc7fd0853a0f36e18b2e1 - >1.2.0
|
|
||||||
"ignore:.*invalid escape sequence:SyntaxWarning:.*stringcase",
|
|
||||||
# https://github.com/xchwarze/samsung-tv-ws-api/pull/151 - >2.7.2 - 2024-12-06 # wrong stacklevel in aiohttp
|
# https://github.com/xchwarze/samsung-tv-ws-api/pull/151 - >2.7.2 - 2024-12-06 # wrong stacklevel in aiohttp
|
||||||
"ignore:verify_ssl is deprecated, use ssl=False instead:DeprecationWarning:aiohttp.client",
|
"ignore:verify_ssl is deprecated, use ssl=False instead:DeprecationWarning:aiohttp.client",
|
||||||
|
|
||||||
@@ -518,6 +518,8 @@ filterwarnings = [
|
|||||||
# https://pypi.org/project/emulated-roku/ - v0.3.0 - 2023-12-19
|
# https://pypi.org/project/emulated-roku/ - v0.3.0 - 2023-12-19
|
||||||
# https://github.com/martonperei/emulated_roku
|
# https://github.com/martonperei/emulated_roku
|
||||||
"ignore:loop argument is deprecated:DeprecationWarning:emulated_roku",
|
"ignore:loop argument is deprecated:DeprecationWarning:emulated_roku",
|
||||||
|
# https://github.com/EnergieID/energyid-webhooks-py/ - v0.0.14 - 2025-05-06
|
||||||
|
"ignore:The V1 WebhookClient is deprecated:DeprecationWarning:energyid_webhooks",
|
||||||
# https://pypi.org/project/foobot_async/ - v1.0.1 - 2024-08-16
|
# https://pypi.org/project/foobot_async/ - v1.0.1 - 2024-08-16
|
||||||
"ignore:with timeout\\(\\) is deprecated:DeprecationWarning:foobot_async",
|
"ignore:with timeout\\(\\) is deprecated:DeprecationWarning:foobot_async",
|
||||||
# https://pypi.org/project/motionblindsble/ - v0.1.3 - 2024-11-12
|
# https://pypi.org/project/motionblindsble/ - v0.1.3 - 2024-11-12
|
||||||
@@ -565,13 +567,18 @@ filterwarnings = [
|
|||||||
"ignore:pkg_resources is deprecated as an API:UserWarning:pybotvac.version",
|
"ignore:pkg_resources is deprecated as an API:UserWarning:pybotvac.version",
|
||||||
# - SyntaxWarning - is with literal
|
# - SyntaxWarning - is with literal
|
||||||
# https://github.com/majuss/lupupy/pull/15 - >0.3.2
|
# https://github.com/majuss/lupupy/pull/15 - >0.3.2
|
||||||
|
"ignore:\"is.*\" with '.*' literal:SyntaxWarning:.*lupupy.devices.alarm",
|
||||||
# https://pypi.org/project/opuslib/ - v3.0.1 - 2018-01-16
|
# https://pypi.org/project/opuslib/ - v3.0.1 - 2018-01-16
|
||||||
|
"ignore:\"is.*\" with '.*' literal:SyntaxWarning:.*opuslib.api.decoder",
|
||||||
# https://pypi.org/project/pyiss/ - v1.0.1 - 2016-12-19
|
# https://pypi.org/project/pyiss/ - v1.0.1 - 2016-12-19
|
||||||
"ignore:\"is.*\" with '.*' literal:SyntaxWarning:importlib._bootstrap",
|
"ignore:\"is.*\" with '.*' literal:SyntaxWarning:.*pyiss",
|
||||||
# - SyntaxWarning - return in finally
|
# - SyntaxWarning - return in finally
|
||||||
# https://github.com/nextcord/nextcord/pull/1268 - >3.1.1 - 2025-08-16
|
# https://github.com/nextcord/nextcord/pull/1268 - >3.1.1 - 2025-08-16
|
||||||
|
"ignore:'return' in a 'finally' block:SyntaxWarning:.*nextcord.(gateway|player)",
|
||||||
# https://pypi.org/project/sleekxmppfs/ - v1.4.1 - 2022-08-18
|
# https://pypi.org/project/sleekxmppfs/ - v1.4.1 - 2022-08-18
|
||||||
"ignore:'return' in a 'finally' block:SyntaxWarning:importlib._bootstrap",
|
"ignore:'return' in a 'finally' block:SyntaxWarning:.*sleekxmppfs.(roster.single|xmlstream.xmlstream)",
|
||||||
|
# https://github.com/cereal2nd/velbus-aio/pull/153 - >2025.11.0
|
||||||
|
"ignore:'return' in a 'finally' block:SyntaxWarning:.*velbusaio.vlp_reader",
|
||||||
|
|
||||||
# -- New in Python 3.13
|
# -- New in Python 3.13
|
||||||
# https://github.com/youknowone/python-deadlib - Backports for aifc, telnetlib
|
# https://github.com/youknowone/python-deadlib - Backports for aifc, telnetlib
|
||||||
@@ -597,6 +604,8 @@ filterwarnings = [
|
|||||||
"ignore:'asyncio.iscoroutinefunction' is deprecated and slated for removal in Python 3.16:DeprecationWarning:(backoff._decorator|backoff._async)",
|
"ignore:'asyncio.iscoroutinefunction' is deprecated and slated for removal in Python 3.16:DeprecationWarning:(backoff._decorator|backoff._async)",
|
||||||
# https://github.com/albertogeniola/elmax-api - v0.0.6.3 - 2024-11-30
|
# https://github.com/albertogeniola/elmax-api - v0.0.6.3 - 2024-11-30
|
||||||
"ignore:'asyncio.iscoroutinefunction' is deprecated and slated for removal in Python 3.16:DeprecationWarning:elmax_api.http",
|
"ignore:'asyncio.iscoroutinefunction' is deprecated and slated for removal in Python 3.16:DeprecationWarning:elmax_api.http",
|
||||||
|
# https://github.com/BerriAI/litellm - v1.80.9 - 2025-12-08
|
||||||
|
"ignore:'asyncio.iscoroutinefunction' is deprecated and slated for removal in Python 3.16:DeprecationWarning:litellm.litellm_core_utils.logging_utils",
|
||||||
# https://github.com/nextcord/nextcord/pull/1269 - >3.1.1 - 2025-08-16
|
# https://github.com/nextcord/nextcord/pull/1269 - >3.1.1 - 2025-08-16
|
||||||
"ignore:'asyncio.iscoroutinefunction' is deprecated and slated for removal in Python 3.16:DeprecationWarning:nextcord.member",
|
"ignore:'asyncio.iscoroutinefunction' is deprecated and slated for removal in Python 3.16:DeprecationWarning:nextcord.member",
|
||||||
# https://github.com/SteveEasley/pykaleidescape/pull/7 - v2022.2.6 - 2022-03-07
|
# https://github.com/SteveEasley/pykaleidescape/pull/7 - v2022.2.6 - 2022-03-07
|
||||||
|
|||||||
12
requirements_all.txt
generated
12
requirements_all.txt
generated
@@ -1102,7 +1102,7 @@ google-nest-sdm==9.1.2
|
|||||||
google-photos-library-api==0.12.1
|
google-photos-library-api==0.12.1
|
||||||
|
|
||||||
# homeassistant.components.google_air_quality
|
# homeassistant.components.google_air_quality
|
||||||
google_air_quality_api==2.0.0
|
google_air_quality_api==2.0.2
|
||||||
|
|
||||||
# homeassistant.components.slide
|
# homeassistant.components.slide
|
||||||
# homeassistant.components.slide_local
|
# homeassistant.components.slide_local
|
||||||
@@ -1249,7 +1249,7 @@ ibeacon-ble==1.2.0
|
|||||||
# homeassistant.components.local_calendar
|
# homeassistant.components.local_calendar
|
||||||
# homeassistant.components.local_todo
|
# homeassistant.components.local_todo
|
||||||
# homeassistant.components.remote_calendar
|
# homeassistant.components.remote_calendar
|
||||||
ical==11.1.0
|
ical==12.1.1
|
||||||
|
|
||||||
# homeassistant.components.caldav
|
# homeassistant.components.caldav
|
||||||
icalendar==6.3.1
|
icalendar==6.3.1
|
||||||
@@ -1654,7 +1654,7 @@ open-meteo==0.3.2
|
|||||||
|
|
||||||
# homeassistant.components.open_router
|
# homeassistant.components.open_router
|
||||||
# homeassistant.components.openai_conversation
|
# homeassistant.components.openai_conversation
|
||||||
openai==2.9.0
|
openai==2.11.0
|
||||||
|
|
||||||
# homeassistant.components.openerz
|
# homeassistant.components.openerz
|
||||||
openerz-api==0.3.0
|
openerz-api==0.3.0
|
||||||
@@ -2160,7 +2160,7 @@ pykwb==0.0.8
|
|||||||
pylacrosse==0.4
|
pylacrosse==0.4
|
||||||
|
|
||||||
# homeassistant.components.lamarzocco
|
# homeassistant.components.lamarzocco
|
||||||
pylamarzocco==2.2.3
|
pylamarzocco==2.2.4
|
||||||
|
|
||||||
# homeassistant.components.lastfm
|
# homeassistant.components.lastfm
|
||||||
pylast==5.1.0
|
pylast==5.1.0
|
||||||
@@ -2235,7 +2235,7 @@ pynina==0.3.6
|
|||||||
pynintendoauth==1.0.0
|
pynintendoauth==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.nintendo_parental_controls
|
# homeassistant.components.nintendo_parental_controls
|
||||||
pynintendoparental==2.1.0
|
pynintendoparental==2.1.1
|
||||||
|
|
||||||
# homeassistant.components.nobo_hub
|
# homeassistant.components.nobo_hub
|
||||||
pynobo==1.8.1
|
pynobo==1.8.1
|
||||||
@@ -2418,7 +2418,7 @@ pysmappee==0.2.29
|
|||||||
pysmarlaapi==0.9.2
|
pysmarlaapi==0.9.2
|
||||||
|
|
||||||
# homeassistant.components.smartthings
|
# homeassistant.components.smartthings
|
||||||
pysmartthings==3.5.0
|
pysmartthings==3.5.1
|
||||||
|
|
||||||
# homeassistant.components.smarty
|
# homeassistant.components.smarty
|
||||||
pysmarty2==0.10.3
|
pysmarty2==0.10.3
|
||||||
|
|||||||
12
requirements_test_all.txt
generated
12
requirements_test_all.txt
generated
@@ -978,7 +978,7 @@ google-nest-sdm==9.1.2
|
|||||||
google-photos-library-api==0.12.1
|
google-photos-library-api==0.12.1
|
||||||
|
|
||||||
# homeassistant.components.google_air_quality
|
# homeassistant.components.google_air_quality
|
||||||
google_air_quality_api==2.0.0
|
google_air_quality_api==2.0.2
|
||||||
|
|
||||||
# homeassistant.components.slide
|
# homeassistant.components.slide
|
||||||
# homeassistant.components.slide_local
|
# homeassistant.components.slide_local
|
||||||
@@ -1101,7 +1101,7 @@ ibeacon-ble==1.2.0
|
|||||||
# homeassistant.components.local_calendar
|
# homeassistant.components.local_calendar
|
||||||
# homeassistant.components.local_todo
|
# homeassistant.components.local_todo
|
||||||
# homeassistant.components.remote_calendar
|
# homeassistant.components.remote_calendar
|
||||||
ical==11.1.0
|
ical==12.1.1
|
||||||
|
|
||||||
# homeassistant.components.caldav
|
# homeassistant.components.caldav
|
||||||
icalendar==6.3.1
|
icalendar==6.3.1
|
||||||
@@ -1437,7 +1437,7 @@ open-meteo==0.3.2
|
|||||||
|
|
||||||
# homeassistant.components.open_router
|
# homeassistant.components.open_router
|
||||||
# homeassistant.components.openai_conversation
|
# homeassistant.components.openai_conversation
|
||||||
openai==2.9.0
|
openai==2.11.0
|
||||||
|
|
||||||
# homeassistant.components.openerz
|
# homeassistant.components.openerz
|
||||||
openerz-api==0.3.0
|
openerz-api==0.3.0
|
||||||
@@ -1819,7 +1819,7 @@ pykrakenapi==0.1.8
|
|||||||
pykulersky==0.5.8
|
pykulersky==0.5.8
|
||||||
|
|
||||||
# homeassistant.components.lamarzocco
|
# homeassistant.components.lamarzocco
|
||||||
pylamarzocco==2.2.3
|
pylamarzocco==2.2.4
|
||||||
|
|
||||||
# homeassistant.components.lastfm
|
# homeassistant.components.lastfm
|
||||||
pylast==5.1.0
|
pylast==5.1.0
|
||||||
@@ -1882,7 +1882,7 @@ pynina==0.3.6
|
|||||||
pynintendoauth==1.0.0
|
pynintendoauth==1.0.0
|
||||||
|
|
||||||
# homeassistant.components.nintendo_parental_controls
|
# homeassistant.components.nintendo_parental_controls
|
||||||
pynintendoparental==2.1.0
|
pynintendoparental==2.1.1
|
||||||
|
|
||||||
# homeassistant.components.nobo_hub
|
# homeassistant.components.nobo_hub
|
||||||
pynobo==1.8.1
|
pynobo==1.8.1
|
||||||
@@ -2035,7 +2035,7 @@ pysmappee==0.2.29
|
|||||||
pysmarlaapi==0.9.2
|
pysmarlaapi==0.9.2
|
||||||
|
|
||||||
# homeassistant.components.smartthings
|
# homeassistant.components.smartthings
|
||||||
pysmartthings==3.5.0
|
pysmartthings==3.5.1
|
||||||
|
|
||||||
# homeassistant.components.smarty
|
# homeassistant.components.smarty
|
||||||
pysmarty2==0.10.3
|
pysmarty2==0.10.3
|
||||||
|
|||||||
@@ -180,6 +180,14 @@ TEST_PLAYBACK_METADATA = PlaybackContentMetadata(
|
|||||||
track=1,
|
track=1,
|
||||||
source_internal_id="123",
|
source_internal_id="123",
|
||||||
)
|
)
|
||||||
|
TEST_PLAYBACK_METADATA_VIDEO = PlaybackContentMetadata(
|
||||||
|
encoding="unknown",
|
||||||
|
organization="HDMI A",
|
||||||
|
title="HDMI A",
|
||||||
|
source_internal_id="hdmi_1",
|
||||||
|
output_channel_processing="TrueImage",
|
||||||
|
output_Channels="5.0.2",
|
||||||
|
)
|
||||||
TEST_PLAYBACK_ERROR = PlaybackError(error="Test error")
|
TEST_PLAYBACK_ERROR = PlaybackError(error="Test error")
|
||||||
TEST_PLAYBACK_PROGRESS = PlaybackProgress(progress=123)
|
TEST_PLAYBACK_PROGRESS = PlaybackProgress(progress=123)
|
||||||
TEST_PLAYBACK_STATE_PAUSED = RenderingState(value="paused")
|
TEST_PLAYBACK_STATE_PAUSED = RenderingState(value="paused")
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ from .const import (
|
|||||||
TEST_OVERLAY_OFFSET_VOLUME_TTS,
|
TEST_OVERLAY_OFFSET_VOLUME_TTS,
|
||||||
TEST_PLAYBACK_ERROR,
|
TEST_PLAYBACK_ERROR,
|
||||||
TEST_PLAYBACK_METADATA,
|
TEST_PLAYBACK_METADATA,
|
||||||
|
TEST_PLAYBACK_METADATA_VIDEO,
|
||||||
TEST_PLAYBACK_PROGRESS,
|
TEST_PLAYBACK_PROGRESS,
|
||||||
TEST_PLAYBACK_STATE_PAUSED,
|
TEST_PLAYBACK_STATE_PAUSED,
|
||||||
TEST_PLAYBACK_STATE_PLAYING,
|
TEST_PLAYBACK_STATE_PLAYING,
|
||||||
@@ -433,6 +434,36 @@ async def test_async_update_source_change(
|
|||||||
assert (ATTR_MEDIA_CONTENT_ID in states.attributes) == content_id_available
|
assert (ATTR_MEDIA_CONTENT_ID in states.attributes) == content_id_available
|
||||||
|
|
||||||
|
|
||||||
|
async def test_async_update_source_change_video(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
integration: None,
|
||||||
|
mock_mozart_client: AsyncMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test _async_update_source_change with a video source."""
|
||||||
|
playback_metadata_callback = (
|
||||||
|
mock_mozart_client.get_playback_metadata_notifications.call_args[0][0]
|
||||||
|
)
|
||||||
|
source_change_callback = (
|
||||||
|
mock_mozart_client.get_source_change_notifications.call_args[0][0]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (states := hass.states.get(TEST_MEDIA_PLAYER_ENTITY_ID))
|
||||||
|
assert ATTR_INPUT_SOURCE not in states.attributes
|
||||||
|
assert states.attributes[ATTR_MEDIA_CONTENT_TYPE] == MediaType.MUSIC
|
||||||
|
|
||||||
|
# Simulate metadata and source change
|
||||||
|
playback_metadata_callback(TEST_PLAYBACK_METADATA_VIDEO)
|
||||||
|
source_change_callback(Source(id="tv", name="TV"))
|
||||||
|
|
||||||
|
assert (states := hass.states.get(TEST_MEDIA_PLAYER_ENTITY_ID))
|
||||||
|
assert states.attributes[ATTR_INPUT_SOURCE] == TEST_PLAYBACK_METADATA_VIDEO.title
|
||||||
|
assert states.attributes[ATTR_MEDIA_CONTENT_TYPE] == BeoMediaType.TV
|
||||||
|
assert (
|
||||||
|
states.attributes[ATTR_MEDIA_CONTENT_ID]
|
||||||
|
== TEST_PLAYBACK_METADATA_VIDEO.source_internal_id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def test_async_turn_off(
|
async def test_async_turn_off(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
integration: None,
|
integration: None,
|
||||||
@@ -819,7 +850,7 @@ async def test_async_select_source(
|
|||||||
audio_source_call: int,
|
audio_source_call: int,
|
||||||
video_source_call: int,
|
video_source_call: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test async_select_source with an invalid source."""
|
"""Test async_select_source with an invalid source and valid audio and video sources."""
|
||||||
with expected_result:
|
with expected_result:
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
MEDIA_PLAYER_DOMAIN,
|
MEDIA_PLAYER_DOMAIN,
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ def mock_config_fixture():
|
|||||||
data={
|
data={
|
||||||
CONF_USERNAME: "test_user",
|
CONF_USERNAME: "test_user",
|
||||||
CONF_PASSWORD: "Password",
|
CONF_PASSWORD: "Password",
|
||||||
"device_id": "Home Assistant",
|
"hardware_id": "Home Assistant",
|
||||||
"uid": "BlinkCamera_e1233333e2-0909-09cd-777a-123456789012",
|
"uid": "BlinkCamera_e1233333e2-0909-09cd-777a-123456789012",
|
||||||
"token": "A_token",
|
"token": "A_token",
|
||||||
"unique_id": "an_email@email.com",
|
"unique_id": "an_email@email.com",
|
||||||
@@ -95,5 +95,5 @@ def mock_config_fixture():
|
|||||||
"account_id": 654321,
|
"account_id": 654321,
|
||||||
},
|
},
|
||||||
entry_id=str(uuid4()),
|
entry_id=str(uuid4()),
|
||||||
version=3,
|
version=4,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
'data': dict({
|
'data': dict({
|
||||||
'account_id': 654321,
|
'account_id': 654321,
|
||||||
'client_id': 123456,
|
'client_id': 123456,
|
||||||
'device_id': 'Home Assistant',
|
'hardware_id': 'Home Assistant',
|
||||||
'host': 'u034.immedia-semi.com',
|
'host': 'u034.immedia-semi.com',
|
||||||
'password': '**REDACTED**',
|
'password': '**REDACTED**',
|
||||||
'region_id': 'u034',
|
'region_id': 'u034',
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
]),
|
]),
|
||||||
'title': 'Mock Title',
|
'title': 'Mock Title',
|
||||||
'unique_id': None,
|
'unique_id': None,
|
||||||
'version': 3,
|
'version': 4,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
|||||||
@@ -113,3 +113,32 @@ async def test_migrate(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
entry = hass.config_entries.async_get_entry(mock_config_entry.entry_id)
|
entry = hass.config_entries.async_get_entry(mock_config_entry.entry_id)
|
||||||
assert entry.state is ConfigEntryState.MIGRATION_ERROR
|
assert entry.state is ConfigEntryState.MIGRATION_ERROR
|
||||||
|
|
||||||
|
|
||||||
|
async def test_migrate_v3_to_v4(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_blink_api: MagicMock,
|
||||||
|
mock_blink_auth_api: MagicMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Test migration from version 3 to 4 (device_id to hardware_id)."""
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
# Set up v3 config entry with device_id
|
||||||
|
data = {**mock_config_entry.data}
|
||||||
|
data.pop("hardware_id", None)
|
||||||
|
data["device_id"] = "Home Assistant"
|
||||||
|
hass.config_entries.async_update_entry(
|
||||||
|
mock_config_entry,
|
||||||
|
version=3,
|
||||||
|
data=data,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
entry = hass.config_entries.async_get_entry(mock_config_entry.entry_id)
|
||||||
|
assert entry.state is ConfigEntryState.LOADED
|
||||||
|
assert entry.version == 4
|
||||||
|
assert "hardware_id" in entry.data
|
||||||
|
assert "device_id" not in entry.data
|
||||||
|
assert entry.data["hardware_id"] == "Home Assistant"
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ from bluecurrent_api.exceptions import (
|
|||||||
import pytest
|
import pytest
|
||||||
from voluptuous import MultipleInvalid
|
from voluptuous import MultipleInvalid
|
||||||
|
|
||||||
from homeassistant.components.blue_current import (
|
from homeassistant.components.blue_current import async_setup_entry
|
||||||
|
from homeassistant.components.blue_current.const import (
|
||||||
CHARGING_CARD_ID,
|
CHARGING_CARD_ID,
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
SERVICE_START_CHARGE_SESSION,
|
SERVICE_START_CHARGE_SESSION,
|
||||||
async_setup_entry,
|
|
||||||
)
|
)
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.const import CONF_DEVICE_ID, Platform
|
from homeassistant.const import CONF_DEVICE_ID, Platform
|
||||||
|
|||||||
@@ -12,11 +12,13 @@ from bring_api import (
|
|||||||
from freezegun.api import FrozenDateTimeFactory
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.bring import async_setup_entry
|
|
||||||
from homeassistant.components.bring.const import DOMAIN
|
from homeassistant.components.bring.const import DOMAIN
|
||||||
from homeassistant.config_entries import ConfigEntryDisabler, ConfigEntryState
|
from homeassistant.config_entries import (
|
||||||
|
SOURCE_REAUTH,
|
||||||
|
ConfigEntryDisabler,
|
||||||
|
ConfigEntryState,
|
||||||
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
|
||||||
from homeassistant.helpers import device_registry as dr
|
from homeassistant.helpers import device_registry as dr
|
||||||
|
|
||||||
from .conftest import UUID
|
from .conftest import UUID
|
||||||
@@ -52,11 +54,11 @@ async def test_load_unload(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("exception", "status"),
|
("exception", "status", "reauth_triggered"),
|
||||||
[
|
[
|
||||||
(BringRequestException, ConfigEntryState.SETUP_RETRY),
|
(BringRequestException, ConfigEntryState.SETUP_RETRY, False),
|
||||||
(BringAuthException, ConfigEntryState.SETUP_ERROR),
|
(BringAuthException, ConfigEntryState.SETUP_ERROR, True),
|
||||||
(BringParseException, ConfigEntryState.SETUP_RETRY),
|
(BringParseException, ConfigEntryState.SETUP_RETRY, False),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
async def test_init_failure(
|
async def test_init_failure(
|
||||||
@@ -64,6 +66,7 @@ async def test_init_failure(
|
|||||||
mock_bring_client: AsyncMock,
|
mock_bring_client: AsyncMock,
|
||||||
status: ConfigEntryState,
|
status: ConfigEntryState,
|
||||||
exception: Exception,
|
exception: Exception,
|
||||||
|
reauth_triggered: bool,
|
||||||
bring_config_entry: MockConfigEntry,
|
bring_config_entry: MockConfigEntry,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test an initialization error on integration load."""
|
"""Test an initialization error on integration load."""
|
||||||
@@ -71,28 +74,14 @@ async def test_init_failure(
|
|||||||
await setup_integration(hass, bring_config_entry)
|
await setup_integration(hass, bring_config_entry)
|
||||||
assert bring_config_entry.state == status
|
assert bring_config_entry.state == status
|
||||||
|
|
||||||
|
assert (
|
||||||
@pytest.mark.parametrize(
|
any(
|
||||||
("exception", "expected"),
|
flow
|
||||||
[
|
for flow in hass.config_entries.flow.async_progress()
|
||||||
(BringRequestException, ConfigEntryNotReady),
|
if flow["context"]["source"] == SOURCE_REAUTH
|
||||||
(BringAuthException, ConfigEntryAuthFailed),
|
)
|
||||||
(BringParseException, ConfigEntryNotReady),
|
is reauth_triggered
|
||||||
],
|
)
|
||||||
)
|
|
||||||
async def test_init_exceptions(
|
|
||||||
hass: HomeAssistant,
|
|
||||||
mock_bring_client: AsyncMock,
|
|
||||||
exception: Exception,
|
|
||||||
expected: Exception,
|
|
||||||
bring_config_entry: MockConfigEntry,
|
|
||||||
) -> None:
|
|
||||||
"""Test an initialization error on integration load."""
|
|
||||||
bring_config_entry.add_to_hass(hass)
|
|
||||||
mock_bring_client.login.side_effect = exception
|
|
||||||
|
|
||||||
with pytest.raises(expected):
|
|
||||||
await async_setup_entry(hass, bring_config_entry)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("exception", [BringRequestException, BringParseException])
|
@pytest.mark.parametrize("exception", [BringRequestException, BringParseException])
|
||||||
|
|||||||
@@ -229,7 +229,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -268,6 +270,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'MIN123456 Battery 1 charging W',
|
'friendly_name': 'MIN123456 Battery 1 charging W',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -283,7 +286,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -322,6 +327,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'MIN123456 Battery 1 discharging W',
|
'friendly_name': 'MIN123456 Battery 1 discharging W',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -337,7 +343,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -376,6 +384,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'MIN123456 Battery 2 charging W',
|
'friendly_name': 'MIN123456 Battery 2 charging W',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -391,7 +400,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -430,6 +441,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'MIN123456 Battery 2 discharging W',
|
'friendly_name': 'MIN123456 Battery 2 discharging W',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -730,7 +742,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -769,6 +783,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'MIN123456 Export power',
|
'friendly_name': 'MIN123456 Export power',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -898,7 +913,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -937,6 +954,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'MIN123456 Import power',
|
'friendly_name': 'MIN123456 Import power',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -1060,7 +1078,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -1099,6 +1119,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'MIN123456 Input 1 wattage',
|
'friendly_name': 'MIN123456 Input 1 wattage',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -1222,7 +1243,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -1261,6 +1284,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'MIN123456 Input 2 wattage',
|
'friendly_name': 'MIN123456 Input 2 wattage',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -1384,7 +1408,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -1423,6 +1449,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'MIN123456 Input 3 wattage',
|
'friendly_name': 'MIN123456 Input 3 wattage',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -1546,7 +1573,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -1585,6 +1614,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'MIN123456 Input 4 wattage',
|
'friendly_name': 'MIN123456 Input 4 wattage',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -1600,7 +1630,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -1639,6 +1671,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'MIN123456 Internal wattage',
|
'friendly_name': 'MIN123456 Internal wattage',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -2737,7 +2770,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -2776,6 +2811,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'MIN123456 Local load power',
|
'friendly_name': 'MIN123456 Local load power',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -2791,7 +2827,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -2830,6 +2868,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'MIN123456 Output power',
|
'friendly_name': 'MIN123456 Output power',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -2956,7 +2995,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -2995,6 +3036,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'MIN123456 Self power',
|
'friendly_name': 'MIN123456 Self power',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -3118,7 +3160,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -3157,6 +3201,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'MIN123456 System power',
|
'friendly_name': 'MIN123456 System power',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -3613,7 +3658,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -3652,6 +3699,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'Test Plant Total Maximum power',
|
'friendly_name': 'Test Plant Total Maximum power',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -3936,7 +3984,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -3975,6 +4025,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'Test Plant Total Maximum power',
|
'friendly_name': 'Test Plant Total Maximum power',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -4372,7 +4423,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -4411,6 +4464,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'TLX123456 Battery 1 charging W',
|
'friendly_name': 'TLX123456 Battery 1 charging W',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -4426,7 +4480,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -4465,6 +4521,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'TLX123456 Battery 1 discharging W',
|
'friendly_name': 'TLX123456 Battery 1 discharging W',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -4480,7 +4537,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -4519,6 +4578,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'TLX123456 Battery 2 charging W',
|
'friendly_name': 'TLX123456 Battery 2 charging W',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -4534,7 +4594,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -4573,6 +4635,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'TLX123456 Battery 2 discharging W',
|
'friendly_name': 'TLX123456 Battery 2 discharging W',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -4873,7 +4936,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -4912,6 +4977,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'TLX123456 Export power',
|
'friendly_name': 'TLX123456 Export power',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -5041,7 +5107,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -5080,6 +5148,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'TLX123456 Import power',
|
'friendly_name': 'TLX123456 Import power',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -5203,7 +5272,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -5242,6 +5313,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'TLX123456 Input 1 wattage',
|
'friendly_name': 'TLX123456 Input 1 wattage',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -5365,7 +5437,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -5404,6 +5478,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'TLX123456 Input 2 wattage',
|
'friendly_name': 'TLX123456 Input 2 wattage',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -5527,7 +5602,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -5566,6 +5643,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'TLX123456 Input 3 wattage',
|
'friendly_name': 'TLX123456 Input 3 wattage',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -5689,7 +5767,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -5728,6 +5808,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'TLX123456 Input 4 wattage',
|
'friendly_name': 'TLX123456 Input 4 wattage',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -5743,7 +5824,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -5782,6 +5865,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'TLX123456 Internal wattage',
|
'friendly_name': 'TLX123456 Internal wattage',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -6880,7 +6964,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -6919,6 +7005,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'TLX123456 Local load power',
|
'friendly_name': 'TLX123456 Local load power',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -6934,7 +7021,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -6973,6 +7062,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'TLX123456 Output power',
|
'friendly_name': 'TLX123456 Output power',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -7099,7 +7189,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -7138,6 +7230,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'TLX123456 Self power',
|
'friendly_name': 'TLX123456 Self power',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -7261,7 +7354,9 @@
|
|||||||
'aliases': set({
|
'aliases': set({
|
||||||
}),
|
}),
|
||||||
'area_id': None,
|
'area_id': None,
|
||||||
'capabilities': None,
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
'config_subentry_id': <ANY>,
|
'config_subentry_id': <ANY>,
|
||||||
'device_class': None,
|
'device_class': None,
|
||||||
@@ -7300,6 +7395,7 @@
|
|||||||
'device_class': 'power',
|
'device_class': 'power',
|
||||||
'friendly_name': 'TLX123456 System power',
|
'friendly_name': 'TLX123456 System power',
|
||||||
'icon': 'mdi:solar-power',
|
'icon': 'mdi:solar-power',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ async def integration_fixture(
|
|||||||
"light_sensor",
|
"light_sensor",
|
||||||
"microwave_oven",
|
"microwave_oven",
|
||||||
"mock_lock",
|
"mock_lock",
|
||||||
|
"mock_thermostat",
|
||||||
"mounted_dimmable_load_control_fixture",
|
"mounted_dimmable_load_control_fixture",
|
||||||
"multi_endpoint_light",
|
"multi_endpoint_light",
|
||||||
"occupancy_sensor",
|
"occupancy_sensor",
|
||||||
|
|||||||
526
tests/components/matter/fixtures/nodes/mock_thermostat.json
Normal file
526
tests/components/matter/fixtures/nodes/mock_thermostat.json
Normal file
@@ -0,0 +1,526 @@
|
|||||||
|
{
|
||||||
|
"node_id": 150,
|
||||||
|
"date_commissioned": "2025-11-18T06:53:08.679289",
|
||||||
|
"last_interview": "2025-11-18T06:53:08.679325",
|
||||||
|
"interview_version": 6,
|
||||||
|
"available": true,
|
||||||
|
"is_bridge": false,
|
||||||
|
"attributes": {
|
||||||
|
"0/49/0": 1,
|
||||||
|
"0/49/1": [
|
||||||
|
{
|
||||||
|
"0": "ZW5zMzM=",
|
||||||
|
"1": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"0/49/4": true,
|
||||||
|
"0/49/5": 0,
|
||||||
|
"0/49/6": "ZW5zMzM=",
|
||||||
|
"0/49/7": null,
|
||||||
|
"0/49/65532": 4,
|
||||||
|
"0/49/65533": 2,
|
||||||
|
"0/49/65528": [],
|
||||||
|
"0/49/65529": [],
|
||||||
|
"0/49/65531": [0, 1, 4, 5, 6, 7, 65532, 65533, 65528, 65529, 65531],
|
||||||
|
"0/65/0": [],
|
||||||
|
"0/65/65532": 0,
|
||||||
|
"0/65/65533": 1,
|
||||||
|
"0/65/65528": [],
|
||||||
|
"0/65/65529": [],
|
||||||
|
"0/65/65531": [0, 65532, 65533, 65528, 65529, 65531],
|
||||||
|
"0/63/0": [],
|
||||||
|
"0/63/1": [],
|
||||||
|
"0/63/2": 4,
|
||||||
|
"0/63/3": 3,
|
||||||
|
"0/63/65532": 0,
|
||||||
|
"0/63/65533": 2,
|
||||||
|
"0/63/65528": [5, 2],
|
||||||
|
"0/63/65529": [0, 1, 3, 4],
|
||||||
|
"0/63/65531": [0, 1, 2, 3, 65532, 65533, 65528, 65529, 65531],
|
||||||
|
"0/62/0": [
|
||||||
|
{
|
||||||
|
"1": "FTABAQEkAgE3AyQTAhgmBIAigScmBYAlTTo3BiQVAiQRlhgkBwEkCAEwCUEE2p7AKvoklmZUFHB0JFUiCsv5FCm0dmeH35yXz4UUH4HAWUwpbeU+R7hMGbAITM3T1R/mVWYthssdVcPNsfIVcjcKNQEoARgkAgE2AwQCBAEYMAQUQbZ3toX8hpE/FmJz7M6xHTbh6RMwBRS5+zzv8ZPGnI9mC3wH9vq10JnwlhgwC0DughBITJJHW/pS7o0J6o6FYTe1ufe0vCpaCj3qYeWb/QxLUydUaJQbce5Z3lUcFeHybUa/M9HID+0PRp2Ker3/GA==",
|
||||||
|
"2": "FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQTAhgkBwEkCAEwCUEE/DujEcdTsX19xbxX+KuKKWiMaA5D9u99P/pVxIOmscd2BA2PadEMNnjvtPOpf+WE2Zxar4rby1IfAClGUUuQrTcKNQEpARgkAmAwBBS5+zzv8ZPGnI9mC3wH9vq10JnwljAFFPT6p93JKGcb7g+rTWnA6evF2EdGGDALQGkPpvsbkAFEbfPN6H3Kf23R0zzmW/gpAA3kgaL6wKB2Ofm+Tmylw22qM536Kj8mOMwaV0EL1dCCGcuxF98aL6gY",
|
||||||
|
"254": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"0/62/1": [
|
||||||
|
{
|
||||||
|
"1": "BBmX+KwLR5HGlVNbvlC+dO8Jv9fPthHiTfGpUzi2JJADX5az6GxBAFn02QKHwLcZHyh+lh9faf6rf38/nPYF7/M=",
|
||||||
|
"2": 4939,
|
||||||
|
"3": 2,
|
||||||
|
"4": 150,
|
||||||
|
"5": "ha",
|
||||||
|
"254": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"0/62/2": 16,
|
||||||
|
"0/62/3": 1,
|
||||||
|
"0/62/4": [
|
||||||
|
"FTABAQEkAgE3AyQUARgmBIAigScmBYAlTTo3BiQUARgkBwEkCAEwCUEEGZf4rAtHkcaVU1u+UL507wm/18+2EeJN8alTOLYkkANflrPobEEAWfTZAofAtxkfKH6WH19p/qt/fz+c9gXv8zcKNQEpARgkAmAwBBT0+qfdyShnG+4Pq01pwOnrxdhHRjAFFPT6p93JKGcb7g+rTWnA6evF2EdGGDALQPVrsFnfFplsQGV5m5EUua+rmo9hAr+OP1bvaifdLqiEIn3uXLTLoKmVUkPImRL2Fb+xcMEAqR2p7RM6ZlFCR20Y"
|
||||||
|
],
|
||||||
|
"0/62/5": 1,
|
||||||
|
"0/62/65532": 0,
|
||||||
|
"0/62/65533": 2,
|
||||||
|
"0/62/65528": [1, 3, 5, 8, 14],
|
||||||
|
"0/62/65529": [0, 2, 4, 6, 7, 9, 10, 11, 12, 13],
|
||||||
|
"0/62/65531": [0, 1, 2, 3, 4, 5, 65532, 65533, 65528, 65529, 65531],
|
||||||
|
"0/60/0": 0,
|
||||||
|
"0/60/1": null,
|
||||||
|
"0/60/2": null,
|
||||||
|
"0/60/65532": 0,
|
||||||
|
"0/60/65533": 1,
|
||||||
|
"0/60/65528": [],
|
||||||
|
"0/60/65529": [0, 2],
|
||||||
|
"0/60/65531": [0, 1, 2, 65532, 65533, 65528, 65529, 65531],
|
||||||
|
"0/55/2": 425,
|
||||||
|
"0/55/3": 61,
|
||||||
|
"0/55/4": 0,
|
||||||
|
"0/55/5": 0,
|
||||||
|
"0/55/6": 0,
|
||||||
|
"0/55/7": null,
|
||||||
|
"0/55/1": true,
|
||||||
|
"0/55/0": 2,
|
||||||
|
"0/55/8": 16,
|
||||||
|
"0/55/65532": 3,
|
||||||
|
"0/55/65533": 1,
|
||||||
|
"0/55/65528": [],
|
||||||
|
"0/55/65529": [0],
|
||||||
|
"0/55/65531": [
|
||||||
|
2, 3, 4, 5, 6, 7, 1, 0, 8, 65532, 65533, 65528, 65529, 65531
|
||||||
|
],
|
||||||
|
"0/54/0": null,
|
||||||
|
"0/54/1": null,
|
||||||
|
"0/54/2": 3,
|
||||||
|
"0/54/3": null,
|
||||||
|
"0/54/4": null,
|
||||||
|
"0/54/5": null,
|
||||||
|
"0/54/12": null,
|
||||||
|
"0/54/6": null,
|
||||||
|
"0/54/7": null,
|
||||||
|
"0/54/8": null,
|
||||||
|
"0/54/9": null,
|
||||||
|
"0/54/10": null,
|
||||||
|
"0/54/11": null,
|
||||||
|
"0/54/65532": 3,
|
||||||
|
"0/54/65533": 1,
|
||||||
|
"0/54/65528": [],
|
||||||
|
"0/54/65529": [0],
|
||||||
|
"0/54/65531": [
|
||||||
|
0, 1, 2, 3, 4, 5, 12, 6, 7, 8, 9, 10, 11, 65532, 65533, 65528, 65529,
|
||||||
|
65531
|
||||||
|
],
|
||||||
|
"0/52/0": [
|
||||||
|
{
|
||||||
|
"0": 6163,
|
||||||
|
"1": "6163"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"0": 6162,
|
||||||
|
"1": "6162"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"0": 6161,
|
||||||
|
"1": "6161"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"0": 6160,
|
||||||
|
"1": "6160"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"0": 6159,
|
||||||
|
"1": "6159"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"0/52/1": 545392,
|
||||||
|
"0/52/2": 650640,
|
||||||
|
"0/52/3": 650640,
|
||||||
|
"0/52/65532": 1,
|
||||||
|
"0/52/65533": 1,
|
||||||
|
"0/52/65528": [],
|
||||||
|
"0/52/65529": [0],
|
||||||
|
"0/52/65531": [0, 1, 2, 3, 65532, 65533, 65528, 65529, 65531],
|
||||||
|
"0/51/0": [
|
||||||
|
{
|
||||||
|
"0": "docker0",
|
||||||
|
"1": false,
|
||||||
|
"2": null,
|
||||||
|
"3": null,
|
||||||
|
"4": "8mJ0KirG",
|
||||||
|
"5": ["rBEAAQ=="],
|
||||||
|
"6": [],
|
||||||
|
"7": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"0": "ens33",
|
||||||
|
"1": true,
|
||||||
|
"2": null,
|
||||||
|
"3": null,
|
||||||
|
"4": "AAwpaqXN",
|
||||||
|
"5": ["wKgBxA=="],
|
||||||
|
"6": [
|
||||||
|
"KgEOCgKzOZAcmuLd4EsaUA==",
|
||||||
|
"KgEOCgKzOZA2wMm9YG06Ag==",
|
||||||
|
"/oAAAAAAAACluAo+qvkuxw=="
|
||||||
|
],
|
||||||
|
"7": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"0": "lo",
|
||||||
|
"1": true,
|
||||||
|
"2": null,
|
||||||
|
"3": null,
|
||||||
|
"4": "AAAAAAAA",
|
||||||
|
"5": ["fwAAAQ=="],
|
||||||
|
"6": ["AAAAAAAAAAAAAAAAAAAAAQ=="],
|
||||||
|
"7": 0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"0/51/1": 1,
|
||||||
|
"0/51/8": false,
|
||||||
|
"0/51/3": 0,
|
||||||
|
"0/51/4": 0,
|
||||||
|
"0/51/2": 16,
|
||||||
|
"0/51/65532": 0,
|
||||||
|
"0/51/65533": 2,
|
||||||
|
"0/51/65528": [2],
|
||||||
|
"0/51/65529": [0, 1],
|
||||||
|
"0/51/65531": [0, 1, 8, 3, 4, 2, 65532, 65533, 65528, 65529, 65531],
|
||||||
|
"0/50/65532": 0,
|
||||||
|
"0/50/65533": 1,
|
||||||
|
"0/50/65528": [1],
|
||||||
|
"0/50/65529": [0],
|
||||||
|
"0/50/65531": [65532, 65533, 65528, 65529, 65531],
|
||||||
|
"0/48/0": 0,
|
||||||
|
"0/48/1": {
|
||||||
|
"0": 60,
|
||||||
|
"1": 900
|
||||||
|
},
|
||||||
|
"0/48/2": 0,
|
||||||
|
"0/48/3": 2,
|
||||||
|
"0/48/4": true,
|
||||||
|
"0/48/65532": 0,
|
||||||
|
"0/48/65533": 2,
|
||||||
|
"0/48/65528": [1, 3, 5],
|
||||||
|
"0/48/65529": [0, 2, 4],
|
||||||
|
"0/48/65531": [0, 1, 2, 3, 4, 65532, 65533, 65528, 65529, 65531],
|
||||||
|
"0/43/0": "en-US",
|
||||||
|
"0/43/1": ["en-US"],
|
||||||
|
"0/43/65532": 0,
|
||||||
|
"0/43/65533": 1,
|
||||||
|
"0/43/65528": [],
|
||||||
|
"0/43/65529": [],
|
||||||
|
"0/43/65531": [0, 1, 65532, 65533, 65528, 65529, 65531],
|
||||||
|
"0/40/0": 19,
|
||||||
|
"0/40/1": "TEST_VENDOR",
|
||||||
|
"0/40/2": 65521,
|
||||||
|
"0/40/3": "Mock Thermostat",
|
||||||
|
"0/40/4": 32769,
|
||||||
|
"0/40/5": "",
|
||||||
|
"0/40/6": "**REDACTED**",
|
||||||
|
"0/40/7": 0,
|
||||||
|
"0/40/8": "TEST_VERSION",
|
||||||
|
"0/40/9": 1,
|
||||||
|
"0/40/10": "1.0",
|
||||||
|
"0/40/19": {
|
||||||
|
"0": 3,
|
||||||
|
"1": 65535
|
||||||
|
},
|
||||||
|
"0/40/21": 17104896,
|
||||||
|
"0/40/22": 1,
|
||||||
|
"0/40/24": 1,
|
||||||
|
"0/40/11": "20200101",
|
||||||
|
"0/40/12": "",
|
||||||
|
"0/40/13": "",
|
||||||
|
"0/40/14": "",
|
||||||
|
"0/40/15": "TEST_SN",
|
||||||
|
"0/40/16": false,
|
||||||
|
"0/40/18": "29DB8B9DB518F05F",
|
||||||
|
"0/40/65532": 0,
|
||||||
|
"0/40/65533": 5,
|
||||||
|
"0/40/65528": [],
|
||||||
|
"0/40/65529": [],
|
||||||
|
"0/40/65531": [
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 19, 21, 22, 24, 11, 12, 13, 14, 15, 16,
|
||||||
|
18, 65532, 65533, 65528, 65529, 65531
|
||||||
|
],
|
||||||
|
"0/31/0": [
|
||||||
|
{
|
||||||
|
"1": 5,
|
||||||
|
"2": 2,
|
||||||
|
"3": [112233],
|
||||||
|
"4": null,
|
||||||
|
"254": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"0/31/2": 4,
|
||||||
|
"0/31/3": 3,
|
||||||
|
"0/31/4": 4,
|
||||||
|
"0/31/65532": 0,
|
||||||
|
"0/31/65533": 3,
|
||||||
|
"0/31/65528": [],
|
||||||
|
"0/31/65529": [],
|
||||||
|
"0/31/65531": [0, 2, 3, 4, 65532, 65533, 65528, 65529, 65531],
|
||||||
|
"0/30/0": [],
|
||||||
|
"0/30/65532": 0,
|
||||||
|
"0/30/65533": 1,
|
||||||
|
"0/30/65528": [],
|
||||||
|
"0/30/65529": [],
|
||||||
|
"0/30/65531": [0, 65532, 65533, 65528, 65529, 65531],
|
||||||
|
"0/29/0": [
|
||||||
|
{
|
||||||
|
"0": 18,
|
||||||
|
"1": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"0": 22,
|
||||||
|
"1": 3
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"0/29/1": [
|
||||||
|
49, 65, 63, 62, 60, 55, 54, 52, 51, 50, 48, 43, 40, 31, 30, 29, 3, 42, 45,
|
||||||
|
53
|
||||||
|
],
|
||||||
|
"0/29/2": [41],
|
||||||
|
"0/29/3": [1],
|
||||||
|
"0/29/65532": 0,
|
||||||
|
"0/29/65533": 3,
|
||||||
|
"0/29/65528": [],
|
||||||
|
"0/29/65529": [],
|
||||||
|
"0/29/65531": [0, 1, 2, 3, 65532, 65533, 65528, 65529, 65531],
|
||||||
|
"0/3/0": 0,
|
||||||
|
"0/3/1": 2,
|
||||||
|
"0/3/65532": 0,
|
||||||
|
"0/3/65533": 6,
|
||||||
|
"0/3/65528": [],
|
||||||
|
"0/3/65529": [0, 64],
|
||||||
|
"0/3/65531": [0, 1, 65532, 65533, 65528, 65529, 65531],
|
||||||
|
"0/42/0": [],
|
||||||
|
"0/42/1": true,
|
||||||
|
"0/42/2": 0,
|
||||||
|
"0/42/3": 0,
|
||||||
|
"0/42/65532": 0,
|
||||||
|
"0/42/65533": 1,
|
||||||
|
"0/42/65528": [],
|
||||||
|
"0/42/65529": [0],
|
||||||
|
"0/42/65531": [0, 1, 2, 3, 65532, 65533, 65528, 65529, 65531],
|
||||||
|
"0/45/0": 1,
|
||||||
|
"0/45/65532": 1,
|
||||||
|
"0/45/65533": 2,
|
||||||
|
"0/45/65528": [],
|
||||||
|
"0/45/65529": [],
|
||||||
|
"0/45/65531": [0, 65532, 65533, 65528, 65529, 65531],
|
||||||
|
"0/53/0": null,
|
||||||
|
"0/53/1": null,
|
||||||
|
"0/53/2": null,
|
||||||
|
"0/53/3": null,
|
||||||
|
"0/53/4": null,
|
||||||
|
"0/53/5": null,
|
||||||
|
"0/53/6": 0,
|
||||||
|
"0/53/7": [],
|
||||||
|
"0/53/8": [],
|
||||||
|
"0/53/9": null,
|
||||||
|
"0/53/10": null,
|
||||||
|
"0/53/11": null,
|
||||||
|
"0/53/12": null,
|
||||||
|
"0/53/13": null,
|
||||||
|
"0/53/14": 0,
|
||||||
|
"0/53/15": 0,
|
||||||
|
"0/53/16": 0,
|
||||||
|
"0/53/17": 0,
|
||||||
|
"0/53/18": 0,
|
||||||
|
"0/53/19": 0,
|
||||||
|
"0/53/20": 0,
|
||||||
|
"0/53/21": 0,
|
||||||
|
"0/53/22": 0,
|
||||||
|
"0/53/23": 0,
|
||||||
|
"0/53/24": 0,
|
||||||
|
"0/53/25": 0,
|
||||||
|
"0/53/26": 0,
|
||||||
|
"0/53/27": 0,
|
||||||
|
"0/53/28": 0,
|
||||||
|
"0/53/29": 0,
|
||||||
|
"0/53/30": 0,
|
||||||
|
"0/53/31": 0,
|
||||||
|
"0/53/32": 0,
|
||||||
|
"0/53/33": 0,
|
||||||
|
"0/53/34": 0,
|
||||||
|
"0/53/35": 0,
|
||||||
|
"0/53/36": 0,
|
||||||
|
"0/53/37": 0,
|
||||||
|
"0/53/38": 0,
|
||||||
|
"0/53/39": 0,
|
||||||
|
"0/53/40": 0,
|
||||||
|
"0/53/41": 0,
|
||||||
|
"0/53/42": 0,
|
||||||
|
"0/53/43": 0,
|
||||||
|
"0/53/44": 0,
|
||||||
|
"0/53/45": 0,
|
||||||
|
"0/53/46": 0,
|
||||||
|
"0/53/47": 0,
|
||||||
|
"0/53/48": 0,
|
||||||
|
"0/53/49": 0,
|
||||||
|
"0/53/50": 0,
|
||||||
|
"0/53/51": 0,
|
||||||
|
"0/53/52": 0,
|
||||||
|
"0/53/53": 0,
|
||||||
|
"0/53/54": 0,
|
||||||
|
"0/53/55": 0,
|
||||||
|
"0/53/56": null,
|
||||||
|
"0/53/57": null,
|
||||||
|
"0/53/58": null,
|
||||||
|
"0/53/59": null,
|
||||||
|
"0/53/60": null,
|
||||||
|
"0/53/61": null,
|
||||||
|
"0/53/62": [],
|
||||||
|
"0/53/65532": 15,
|
||||||
|
"0/53/65533": 3,
|
||||||
|
"0/53/65528": [],
|
||||||
|
"0/53/65529": [0],
|
||||||
|
"0/53/65531": [
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
|
||||||
|
21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
|
||||||
|
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56,
|
||||||
|
57, 58, 59, 60, 61, 62, 65532, 65533, 65528, 65529, 65531
|
||||||
|
],
|
||||||
|
"1/29/0": [
|
||||||
|
{
|
||||||
|
"0": 769,
|
||||||
|
"1": 4
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"1/29/1": [29, 3, 4, 513, 516],
|
||||||
|
"1/29/2": [3],
|
||||||
|
"1/29/3": [],
|
||||||
|
"1/29/65532": 0,
|
||||||
|
"1/29/65533": 3,
|
||||||
|
"1/29/65528": [],
|
||||||
|
"1/29/65529": [],
|
||||||
|
"1/29/65531": [0, 1, 2, 3, 65532, 65533, 65528, 65529, 65531],
|
||||||
|
"1/3/0": 0,
|
||||||
|
"1/3/1": 2,
|
||||||
|
"1/3/65532": 0,
|
||||||
|
"1/3/65533": 6,
|
||||||
|
"1/3/65528": [],
|
||||||
|
"1/3/65529": [0, 64],
|
||||||
|
"1/3/65531": [0, 1, 65532, 65533, 65528, 65529, 65531],
|
||||||
|
"1/4/0": 128,
|
||||||
|
"1/4/65532": 1,
|
||||||
|
"1/4/65533": 4,
|
||||||
|
"1/4/65528": [0, 1, 2, 3],
|
||||||
|
"1/4/65529": [0, 1, 2, 3, 4, 5],
|
||||||
|
"1/4/65531": [0, 65532, 65533, 65528, 65529, 65531],
|
||||||
|
|
||||||
|
"1/513/0": 1800,
|
||||||
|
"1/513/1": 500,
|
||||||
|
"1/513/3": 700,
|
||||||
|
"1/513/4": 3000,
|
||||||
|
"1/513/5": 1600,
|
||||||
|
"1/513/6": 3200,
|
||||||
|
"1/513/7": 0,
|
||||||
|
"1/513/8": 25,
|
||||||
|
"1/513/16": 0,
|
||||||
|
"1/513/17": 2600,
|
||||||
|
"1/513/18": 2000,
|
||||||
|
"1/513/21": 700,
|
||||||
|
"1/513/22": 3000,
|
||||||
|
"1/513/23": 1600,
|
||||||
|
"1/513/24": 3200,
|
||||||
|
"1/513/25": 25,
|
||||||
|
"1/513/26": 0,
|
||||||
|
"1/513/27": 4,
|
||||||
|
"1/513/28": 1,
|
||||||
|
"1/513/30": 4,
|
||||||
|
"1/513/35": 0,
|
||||||
|
"1/513/36": 0,
|
||||||
|
"1/513/37": 0,
|
||||||
|
"1/513/41": 1,
|
||||||
|
"1/513/48": 0,
|
||||||
|
"1/513/49": 150,
|
||||||
|
"1/513/50": 789004800,
|
||||||
|
"1/513/72": [
|
||||||
|
{
|
||||||
|
"0": 1,
|
||||||
|
"1": 1,
|
||||||
|
"2": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"0": 2,
|
||||||
|
"1": 1,
|
||||||
|
"2": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"0": 3,
|
||||||
|
"1": 1,
|
||||||
|
"2": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"0": 4,
|
||||||
|
"1": 1,
|
||||||
|
"2": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"0": 5,
|
||||||
|
"1": 1,
|
||||||
|
"2": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"0": 254,
|
||||||
|
"1": 1,
|
||||||
|
"2": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"1/513/73": [
|
||||||
|
{
|
||||||
|
"0": 4,
|
||||||
|
"1": 1,
|
||||||
|
"2": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"0": 3,
|
||||||
|
"1": 1,
|
||||||
|
"2": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"1/513/74": 5,
|
||||||
|
"1/513/78": null,
|
||||||
|
"1/513/80": [
|
||||||
|
{
|
||||||
|
"0": "AQ==",
|
||||||
|
"1": 1,
|
||||||
|
"3": 2500,
|
||||||
|
"4": 2100,
|
||||||
|
"5": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"0": "Ag==",
|
||||||
|
"1": 2,
|
||||||
|
"3": 2600,
|
||||||
|
"4": 2000,
|
||||||
|
"5": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"1/513/82": 0,
|
||||||
|
"1/513/83": 5,
|
||||||
|
"1/513/84": [],
|
||||||
|
"1/513/85": null,
|
||||||
|
"1/513/86": null,
|
||||||
|
"1/513/65532": 423,
|
||||||
|
"1/513/65533": 9,
|
||||||
|
"1/513/65528": [2, 253],
|
||||||
|
"1/513/65529": [0, 6, 7, 8, 254],
|
||||||
|
"1/513/65531": [
|
||||||
|
0, 1, 3, 4, 5, 6, 7, 8, 16, 17, 18, 21, 22, 23, 24, 25, 26, 27, 28, 30,
|
||||||
|
35, 36, 37, 41, 48, 49, 50, 72, 73, 74, 78, 80, 82, 83, 84, 85, 86, 65532,
|
||||||
|
65533, 65528, 65529, 65531
|
||||||
|
],
|
||||||
|
"1/516/0": 0,
|
||||||
|
"1/516/1": 0,
|
||||||
|
"1/516/65532": 0,
|
||||||
|
"1/516/65533": 2,
|
||||||
|
"1/516/65528": [],
|
||||||
|
"1/516/65529": [],
|
||||||
|
"1/516/65531": [0, 1, 65532, 65533, 65528, 65529, 65531]
|
||||||
|
},
|
||||||
|
"attribute_subscriptions": []
|
||||||
|
}
|
||||||
@@ -391,6 +391,102 @@
|
|||||||
'state': 'off',
|
'state': 'off',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
# name: test_binary_sensors[eve_thermo][binary_sensor.eve_thermo_local_temperature_remote_sensing-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': 'binary_sensor',
|
||||||
|
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||||
|
'entity_id': 'binary_sensor.eve_thermo_local_temperature_remote_sensing',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': None,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Local temperature remote sensing',
|
||||||
|
'platform': 'matter',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'thermostat_remote_sensing_local_temperature',
|
||||||
|
'unique_id': '00000000000004D2-0000000000000021-MatterNodeDevice-1-ThermostatRemoteSensing_LocalTemperature-513-26',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_binary_sensors[eve_thermo][binary_sensor.eve_thermo_local_temperature_remote_sensing-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'friendly_name': 'Eve Thermo Local temperature remote sensing',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'binary_sensor.eve_thermo_local_temperature_remote_sensing',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'off',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_binary_sensors[eve_thermo][binary_sensor.eve_thermo_outdoor_temperature_remote_sensing-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': 'binary_sensor',
|
||||||
|
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||||
|
'entity_id': 'binary_sensor.eve_thermo_outdoor_temperature_remote_sensing',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': None,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Outdoor temperature remote sensing',
|
||||||
|
'platform': 'matter',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'thermostat_remote_sensing_outdoor_temperature',
|
||||||
|
'unique_id': '00000000000004D2-0000000000000021-MatterNodeDevice-1-ThermostatRemoteSensing_OutdoorTemperature-513-26',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_binary_sensors[eve_thermo][binary_sensor.eve_thermo_outdoor_temperature_remote_sensing-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'friendly_name': 'Eve Thermo Outdoor temperature remote sensing',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'binary_sensor.eve_thermo_outdoor_temperature_remote_sensing',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'off',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
# name: test_binary_sensors[heiman_motion_sensor_m1][binary_sensor.smart_motion_sensor_occupancy-entry]
|
# name: test_binary_sensors[heiman_motion_sensor_m1][binary_sensor.smart_motion_sensor_occupancy-entry]
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
@@ -636,6 +732,150 @@
|
|||||||
'state': 'off',
|
'state': 'off',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
# name: test_binary_sensors[mock_thermostat][binary_sensor.mock_thermostat_local_temperature_remote_sensing-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': 'binary_sensor',
|
||||||
|
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||||
|
'entity_id': 'binary_sensor.mock_thermostat_local_temperature_remote_sensing',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': None,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Local temperature remote sensing',
|
||||||
|
'platform': 'matter',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'thermostat_remote_sensing_local_temperature',
|
||||||
|
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-ThermostatRemoteSensing_LocalTemperature-513-26',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_binary_sensors[mock_thermostat][binary_sensor.mock_thermostat_local_temperature_remote_sensing-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'friendly_name': 'Mock Thermostat Local temperature remote sensing',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'binary_sensor.mock_thermostat_local_temperature_remote_sensing',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'off',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_binary_sensors[mock_thermostat][binary_sensor.mock_thermostat_occupancy_remote_sensing-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': 'binary_sensor',
|
||||||
|
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||||
|
'entity_id': 'binary_sensor.mock_thermostat_occupancy_remote_sensing',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': None,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Occupancy remote sensing',
|
||||||
|
'platform': 'matter',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'thermostat_remote_sensing_occupancy',
|
||||||
|
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-ThermostatRemoteSensing_Occupancy-513-26',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_binary_sensors[mock_thermostat][binary_sensor.mock_thermostat_occupancy_remote_sensing-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'friendly_name': 'Mock Thermostat Occupancy remote sensing',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'binary_sensor.mock_thermostat_occupancy_remote_sensing',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'off',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_binary_sensors[mock_thermostat][binary_sensor.mock_thermostat_outdoor_temperature_remote_sensing-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': 'binary_sensor',
|
||||||
|
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||||
|
'entity_id': 'binary_sensor.mock_thermostat_outdoor_temperature_remote_sensing',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': None,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Outdoor temperature remote sensing',
|
||||||
|
'platform': 'matter',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'thermostat_remote_sensing_outdoor_temperature',
|
||||||
|
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-ThermostatRemoteSensing_OutdoorTemperature-513-26',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_binary_sensors[mock_thermostat][binary_sensor.mock_thermostat_outdoor_temperature_remote_sensing-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'friendly_name': 'Mock Thermostat Outdoor temperature remote sensing',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'binary_sensor.mock_thermostat_outdoor_temperature_remote_sensing',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'off',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
# name: test_binary_sensors[occupancy_sensor][binary_sensor.mock_occupancy_sensor_occupancy-entry]
|
# name: test_binary_sensors[occupancy_sensor][binary_sensor.mock_occupancy_sensor_occupancy-entry]
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
|
|||||||
@@ -2388,6 +2388,104 @@
|
|||||||
'state': 'unknown',
|
'state': 'unknown',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
# name: test_buttons[mock_thermostat][button.mock_thermostat_identify_0-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.mock_thermostat_identify_0',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': <ButtonDeviceClass.IDENTIFY: 'identify'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Identify (0)',
|
||||||
|
'platform': 'matter',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': None,
|
||||||
|
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-0-IdentifyButton-3-1',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_buttons[mock_thermostat][button.mock_thermostat_identify_0-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'identify',
|
||||||
|
'friendly_name': 'Mock Thermostat Identify (0)',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'button.mock_thermostat_identify_0',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'unknown',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_buttons[mock_thermostat][button.mock_thermostat_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.mock_thermostat_identify_1',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'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-0000000000000096-MatterNodeDevice-1-IdentifyButton-3-1',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_buttons[mock_thermostat][button.mock_thermostat_identify_1-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'identify',
|
||||||
|
'friendly_name': 'Mock Thermostat Identify (1)',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'button.mock_thermostat_identify_1',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'unknown',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
# name: test_buttons[multi_endpoint_light][button.inovelli_identify_1-entry]
|
# name: test_buttons[multi_endpoint_light][button.inovelli_identify_1-entry]
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
|
|||||||
@@ -325,6 +325,77 @@
|
|||||||
'state': 'cool',
|
'state': 'cool',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
# name: test_climates[mock_thermostat][climate.mock_thermostat-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'hvac_modes': list([
|
||||||
|
<HVACMode.OFF: 'off'>,
|
||||||
|
<HVACMode.HEAT: 'heat'>,
|
||||||
|
<HVACMode.COOL: 'cool'>,
|
||||||
|
<HVACMode.HEAT_COOL: 'heat_cool'>,
|
||||||
|
]),
|
||||||
|
'max_temp': 32.0,
|
||||||
|
'min_temp': 7.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_thermostat',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': 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: 387>,
|
||||||
|
'translation_key': None,
|
||||||
|
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-MatterThermostat-513-0',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_climates[mock_thermostat][climate.mock_thermostat-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'current_temperature': 18.0,
|
||||||
|
'friendly_name': 'Mock Thermostat',
|
||||||
|
'hvac_action': <HVACAction.HEATING: 'heating'>,
|
||||||
|
'hvac_modes': list([
|
||||||
|
<HVACMode.OFF: 'off'>,
|
||||||
|
<HVACMode.HEAT: 'heat'>,
|
||||||
|
<HVACMode.COOL: 'cool'>,
|
||||||
|
<HVACMode.HEAT_COOL: 'heat_cool'>,
|
||||||
|
]),
|
||||||
|
'max_temp': 32.0,
|
||||||
|
'min_temp': 7.0,
|
||||||
|
'supported_features': <ClimateEntityFeature: 387>,
|
||||||
|
'target_temp_high': 26.0,
|
||||||
|
'target_temp_low': 20.0,
|
||||||
|
'temperature': None,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'climate.mock_thermostat',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'heat_cool',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
# name: test_climates[room_airconditioner][climate.room_airconditioner-entry]
|
# name: test_climates[room_airconditioner][climate.room_airconditioner-entry]
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
|
|||||||
@@ -241,10 +241,7 @@
|
|||||||
'capabilities': dict({
|
'capabilities': dict({
|
||||||
'options': list([
|
'options': list([
|
||||||
'normal',
|
'normal',
|
||||||
'vacation',
|
|
||||||
'privacy',
|
|
||||||
'no_remote_lock_unlock',
|
'no_remote_lock_unlock',
|
||||||
'passage',
|
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
@@ -282,10 +279,7 @@
|
|||||||
'friendly_name': 'Aqara Smart Lock U200 Operating mode',
|
'friendly_name': 'Aqara Smart Lock U200 Operating mode',
|
||||||
'options': list([
|
'options': list([
|
||||||
'normal',
|
'normal',
|
||||||
'vacation',
|
|
||||||
'privacy',
|
|
||||||
'no_remote_lock_unlock',
|
'no_remote_lock_unlock',
|
||||||
'passage',
|
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -684,10 +678,7 @@
|
|||||||
'capabilities': dict({
|
'capabilities': dict({
|
||||||
'options': list([
|
'options': list([
|
||||||
'normal',
|
'normal',
|
||||||
'vacation',
|
|
||||||
'privacy',
|
|
||||||
'no_remote_lock_unlock',
|
'no_remote_lock_unlock',
|
||||||
'passage',
|
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
@@ -725,10 +716,7 @@
|
|||||||
'friendly_name': 'Mock Door Lock Operating mode',
|
'friendly_name': 'Mock Door Lock Operating mode',
|
||||||
'options': list([
|
'options': list([
|
||||||
'normal',
|
'normal',
|
||||||
'vacation',
|
|
||||||
'privacy',
|
|
||||||
'no_remote_lock_unlock',
|
'no_remote_lock_unlock',
|
||||||
'passage',
|
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -869,10 +857,7 @@
|
|||||||
'capabilities': dict({
|
'capabilities': dict({
|
||||||
'options': list([
|
'options': list([
|
||||||
'normal',
|
'normal',
|
||||||
'vacation',
|
|
||||||
'privacy',
|
|
||||||
'no_remote_lock_unlock',
|
'no_remote_lock_unlock',
|
||||||
'passage',
|
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
@@ -910,10 +895,7 @@
|
|||||||
'friendly_name': 'Mock Door Lock with unbolt Operating mode',
|
'friendly_name': 'Mock Door Lock with unbolt Operating mode',
|
||||||
'options': list([
|
'options': list([
|
||||||
'normal',
|
'normal',
|
||||||
'vacation',
|
|
||||||
'privacy',
|
|
||||||
'no_remote_lock_unlock',
|
'no_remote_lock_unlock',
|
||||||
'passage',
|
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -2523,10 +2505,7 @@
|
|||||||
'capabilities': dict({
|
'capabilities': dict({
|
||||||
'options': list([
|
'options': list([
|
||||||
'normal',
|
'normal',
|
||||||
'vacation',
|
|
||||||
'privacy',
|
|
||||||
'no_remote_lock_unlock',
|
'no_remote_lock_unlock',
|
||||||
'passage',
|
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
@@ -2564,10 +2543,7 @@
|
|||||||
'friendly_name': 'Mock Lock Operating mode',
|
'friendly_name': 'Mock Lock Operating mode',
|
||||||
'options': list([
|
'options': list([
|
||||||
'normal',
|
'normal',
|
||||||
'vacation',
|
|
||||||
'privacy',
|
|
||||||
'no_remote_lock_unlock',
|
'no_remote_lock_unlock',
|
||||||
'passage',
|
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
@@ -2639,6 +2615,63 @@
|
|||||||
'state': 'silent',
|
'state': 'silent',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
# name: test_selects[mock_thermostat][select.mock_thermostat_temperature_display_mode-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'options': list([
|
||||||
|
'Celsius',
|
||||||
|
'Fahrenheit',
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'select',
|
||||||
|
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||||
|
'entity_id': 'select.mock_thermostat_temperature_display_mode',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': None,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Temperature display mode',
|
||||||
|
'platform': 'matter',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'temperature_display_mode',
|
||||||
|
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-TrvTemperatureDisplayMode-516-0',
|
||||||
|
'unit_of_measurement': None,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_selects[mock_thermostat][select.mock_thermostat_temperature_display_mode-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'friendly_name': 'Mock Thermostat Temperature display mode',
|
||||||
|
'options': list([
|
||||||
|
'Celsius',
|
||||||
|
'Fahrenheit',
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'select.mock_thermostat_temperature_display_mode',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': 'Celsius',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
# name: test_selects[mounted_dimmable_load_control_fixture][select.mock_mounted_dimmable_load_control_power_on_behavior_on_startup-entry]
|
# name: test_selects[mounted_dimmable_load_control_fixture][select.mock_mounted_dimmable_load_control_power_on_behavior_on_startup-entry]
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
@@ -3726,10 +3759,8 @@
|
|||||||
'capabilities': dict({
|
'capabilities': dict({
|
||||||
'options': list([
|
'options': list([
|
||||||
'normal',
|
'normal',
|
||||||
'vacation',
|
|
||||||
'privacy',
|
'privacy',
|
||||||
'no_remote_lock_unlock',
|
'no_remote_lock_unlock',
|
||||||
'passage',
|
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
'config_entry_id': <ANY>,
|
'config_entry_id': <ANY>,
|
||||||
@@ -3767,10 +3798,8 @@
|
|||||||
'friendly_name': 'Secuyou Smart Lock Operating mode',
|
'friendly_name': 'Secuyou Smart Lock Operating mode',
|
||||||
'options': list([
|
'options': list([
|
||||||
'normal',
|
'normal',
|
||||||
'vacation',
|
|
||||||
'privacy',
|
'privacy',
|
||||||
'no_remote_lock_unlock',
|
'no_remote_lock_unlock',
|
||||||
'passage',
|
|
||||||
]),
|
]),
|
||||||
}),
|
}),
|
||||||
'context': <ANY>,
|
'context': <ANY>,
|
||||||
|
|||||||
@@ -7634,6 +7634,167 @@
|
|||||||
'state': '5',
|
'state': '5',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_heating_demand-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.mock_thermostat_heating_demand',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
}),
|
||||||
|
'original_device_class': None,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Heating demand',
|
||||||
|
'platform': 'matter',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'pi_heating_demand',
|
||||||
|
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-ThermostatPIHeatingDemand-513-8',
|
||||||
|
'unit_of_measurement': '%',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_heating_demand-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'friendly_name': 'Mock Thermostat Heating demand',
|
||||||
|
'unit_of_measurement': '%',
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.mock_thermostat_heating_demand',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '25',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_outdoor_temperature-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.mock_thermostat_outdoor_temperature',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 1,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Outdoor temperature',
|
||||||
|
'platform': 'matter',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': 'outdoor_temperature',
|
||||||
|
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-ThermostatOutdoorTemperature-513-1',
|
||||||
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_outdoor_temperature-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'temperature',
|
||||||
|
'friendly_name': 'Mock Thermostat Outdoor temperature',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.mock_thermostat_outdoor_temperature',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '5.0',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_temperature-entry]
|
||||||
|
EntityRegistryEntrySnapshot({
|
||||||
|
'aliases': set({
|
||||||
|
}),
|
||||||
|
'area_id': None,
|
||||||
|
'capabilities': dict({
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
}),
|
||||||
|
'config_entry_id': <ANY>,
|
||||||
|
'config_subentry_id': <ANY>,
|
||||||
|
'device_class': None,
|
||||||
|
'device_id': <ANY>,
|
||||||
|
'disabled_by': None,
|
||||||
|
'domain': 'sensor',
|
||||||
|
'entity_category': None,
|
||||||
|
'entity_id': 'sensor.mock_thermostat_temperature',
|
||||||
|
'has_entity_name': True,
|
||||||
|
'hidden_by': None,
|
||||||
|
'icon': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'labels': set({
|
||||||
|
}),
|
||||||
|
'name': None,
|
||||||
|
'options': dict({
|
||||||
|
'sensor': dict({
|
||||||
|
'suggested_display_precision': 1,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
|
||||||
|
'original_icon': None,
|
||||||
|
'original_name': 'Temperature',
|
||||||
|
'platform': 'matter',
|
||||||
|
'previous_unique_id': None,
|
||||||
|
'suggested_object_id': None,
|
||||||
|
'supported_features': 0,
|
||||||
|
'translation_key': None,
|
||||||
|
'unique_id': '00000000000004D2-0000000000000096-MatterNodeDevice-1-ThermostatLocalTemperature-513-0',
|
||||||
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_sensors[mock_thermostat][sensor.mock_thermostat_temperature-state]
|
||||||
|
StateSnapshot({
|
||||||
|
'attributes': ReadOnlyDict({
|
||||||
|
'device_class': 'temperature',
|
||||||
|
'friendly_name': 'Mock Thermostat Temperature',
|
||||||
|
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||||
|
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||||
|
}),
|
||||||
|
'context': <ANY>,
|
||||||
|
'entity_id': 'sensor.mock_thermostat_temperature',
|
||||||
|
'last_changed': <ANY>,
|
||||||
|
'last_reported': <ANY>,
|
||||||
|
'last_updated': <ANY>,
|
||||||
|
'state': '18.0',
|
||||||
|
})
|
||||||
|
# ---
|
||||||
# name: test_sensors[multi_endpoint_light][sensor.inovelli_current_switch_position_config-entry]
|
# name: test_sensors[multi_endpoint_light][sensor.inovelli_current_switch_position_config-entry]
|
||||||
EntityRegistryEntrySnapshot({
|
EntityRegistryEntrySnapshot({
|
||||||
'aliases': set({
|
'aliases': set({
|
||||||
|
|||||||
@@ -435,3 +435,160 @@ async def test_shutter_problem(
|
|||||||
state = hass.states.get("binary_sensor.eve_shutter_switch_20eci1701_problem")
|
state = hass.states.get("binary_sensor.eve_shutter_switch_20eci1701_problem")
|
||||||
assert state
|
assert state
|
||||||
assert state.state == "on"
|
assert state.state == "on"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("node_fixture", ["mock_thermostat"])
|
||||||
|
async def test_thermostat_remote_sensing(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
matter_client: MagicMock,
|
||||||
|
matter_node: MatterNode,
|
||||||
|
) -> None:
|
||||||
|
"""Test thermostat remote sensing binary sensors."""
|
||||||
|
remote_sensing_attribute = clusters.Thermostat.Attributes.RemoteSensing
|
||||||
|
|
||||||
|
# Test initial state (RemoteSensing = 0, all bits off)
|
||||||
|
state = hass.states.get(
|
||||||
|
"binary_sensor.mock_thermostat_local_temperature_remote_sensing"
|
||||||
|
)
|
||||||
|
assert state
|
||||||
|
assert state.state == "off"
|
||||||
|
|
||||||
|
state = hass.states.get(
|
||||||
|
"binary_sensor.mock_thermostat_outdoor_temperature_remote_sensing"
|
||||||
|
)
|
||||||
|
assert state
|
||||||
|
assert state.state == "off"
|
||||||
|
|
||||||
|
state = hass.states.get("binary_sensor.mock_thermostat_occupancy_remote_sensing")
|
||||||
|
assert state
|
||||||
|
assert state.state == "off"
|
||||||
|
|
||||||
|
# Set LocalTemperature bit (bit 0)
|
||||||
|
set_node_attribute(
|
||||||
|
matter_node,
|
||||||
|
1,
|
||||||
|
remote_sensing_attribute.cluster_id,
|
||||||
|
remote_sensing_attribute.attribute_id,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
await trigger_subscription_callback(hass, matter_client)
|
||||||
|
|
||||||
|
state = hass.states.get(
|
||||||
|
"binary_sensor.mock_thermostat_local_temperature_remote_sensing"
|
||||||
|
)
|
||||||
|
assert state
|
||||||
|
assert state.state == "on"
|
||||||
|
|
||||||
|
state = hass.states.get(
|
||||||
|
"binary_sensor.mock_thermostat_outdoor_temperature_remote_sensing"
|
||||||
|
)
|
||||||
|
assert state
|
||||||
|
assert state.state == "off"
|
||||||
|
|
||||||
|
state = hass.states.get("binary_sensor.mock_thermostat_occupancy_remote_sensing")
|
||||||
|
assert state
|
||||||
|
assert state.state == "off"
|
||||||
|
|
||||||
|
# Set OutdoorTemperature bit (bit 1)
|
||||||
|
set_node_attribute(
|
||||||
|
matter_node,
|
||||||
|
1,
|
||||||
|
remote_sensing_attribute.cluster_id,
|
||||||
|
remote_sensing_attribute.attribute_id,
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
await trigger_subscription_callback(hass, matter_client)
|
||||||
|
|
||||||
|
state = hass.states.get(
|
||||||
|
"binary_sensor.mock_thermostat_local_temperature_remote_sensing"
|
||||||
|
)
|
||||||
|
assert state
|
||||||
|
assert state.state == "off"
|
||||||
|
|
||||||
|
state = hass.states.get(
|
||||||
|
"binary_sensor.mock_thermostat_outdoor_temperature_remote_sensing"
|
||||||
|
)
|
||||||
|
assert state
|
||||||
|
assert state.state == "on"
|
||||||
|
|
||||||
|
state = hass.states.get("binary_sensor.mock_thermostat_occupancy_remote_sensing")
|
||||||
|
assert state
|
||||||
|
assert state.state == "off"
|
||||||
|
|
||||||
|
# Set Occupancy bit (bit 2)
|
||||||
|
set_node_attribute(
|
||||||
|
matter_node,
|
||||||
|
1,
|
||||||
|
remote_sensing_attribute.cluster_id,
|
||||||
|
remote_sensing_attribute.attribute_id,
|
||||||
|
4,
|
||||||
|
)
|
||||||
|
await trigger_subscription_callback(hass, matter_client)
|
||||||
|
|
||||||
|
state = hass.states.get(
|
||||||
|
"binary_sensor.mock_thermostat_local_temperature_remote_sensing"
|
||||||
|
)
|
||||||
|
assert state
|
||||||
|
assert state.state == "off"
|
||||||
|
|
||||||
|
state = hass.states.get(
|
||||||
|
"binary_sensor.mock_thermostat_outdoor_temperature_remote_sensing"
|
||||||
|
)
|
||||||
|
assert state
|
||||||
|
assert state.state == "off"
|
||||||
|
|
||||||
|
state = hass.states.get("binary_sensor.mock_thermostat_occupancy_remote_sensing")
|
||||||
|
assert state
|
||||||
|
assert state.state == "on"
|
||||||
|
|
||||||
|
# Set multiple bits (bits 0 and 2 = value 5)
|
||||||
|
set_node_attribute(
|
||||||
|
matter_node,
|
||||||
|
1,
|
||||||
|
remote_sensing_attribute.cluster_id,
|
||||||
|
remote_sensing_attribute.attribute_id,
|
||||||
|
5,
|
||||||
|
)
|
||||||
|
await trigger_subscription_callback(hass, matter_client)
|
||||||
|
|
||||||
|
state = hass.states.get(
|
||||||
|
"binary_sensor.mock_thermostat_local_temperature_remote_sensing"
|
||||||
|
)
|
||||||
|
assert state
|
||||||
|
assert state.state == "on"
|
||||||
|
|
||||||
|
state = hass.states.get(
|
||||||
|
"binary_sensor.mock_thermostat_outdoor_temperature_remote_sensing"
|
||||||
|
)
|
||||||
|
assert state
|
||||||
|
assert state.state == "off"
|
||||||
|
|
||||||
|
state = hass.states.get("binary_sensor.mock_thermostat_occupancy_remote_sensing")
|
||||||
|
assert state
|
||||||
|
assert state.state == "on"
|
||||||
|
|
||||||
|
# Set all bits (value 7)
|
||||||
|
set_node_attribute(
|
||||||
|
matter_node,
|
||||||
|
1,
|
||||||
|
remote_sensing_attribute.cluster_id,
|
||||||
|
remote_sensing_attribute.attribute_id,
|
||||||
|
7,
|
||||||
|
)
|
||||||
|
await trigger_subscription_callback(hass, matter_client)
|
||||||
|
|
||||||
|
state = hass.states.get(
|
||||||
|
"binary_sensor.mock_thermostat_local_temperature_remote_sensing"
|
||||||
|
)
|
||||||
|
assert state
|
||||||
|
assert state.state == "on"
|
||||||
|
|
||||||
|
state = hass.states.get(
|
||||||
|
"binary_sensor.mock_thermostat_outdoor_temperature_remote_sensing"
|
||||||
|
)
|
||||||
|
assert state
|
||||||
|
assert state.state == "on"
|
||||||
|
|
||||||
|
state = hass.states.get("binary_sensor.mock_thermostat_occupancy_remote_sensing")
|
||||||
|
assert state
|
||||||
|
assert state.state == "on"
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ from matter_server.common.helpers.util import create_attribute_path_from_attribu
|
|||||||
import pytest
|
import pytest
|
||||||
from syrupy.assertion import SnapshotAssertion
|
from syrupy.assertion import SnapshotAssertion
|
||||||
|
|
||||||
from homeassistant.components.matter.select import DOOR_LOCK_OPERATING_MODE_MAP
|
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
@@ -314,22 +313,30 @@ async def test_door_lock_operating_mode_select(
|
|||||||
"""Test Door Lock Operating Mode select entity discovery and interaction.
|
"""Test Door Lock Operating Mode select entity discovery and interaction.
|
||||||
|
|
||||||
Verifies:
|
Verifies:
|
||||||
- Options match mapping in DOOR_LOCK_OPERATING_MODE_MAP
|
- Options are filtered based on SupportedOperatingModes bitmap
|
||||||
- Attribute updates reflect current option
|
- Attribute updates reflect current option
|
||||||
- Selecting an option writes correct enum value
|
- Selecting an option writes correct enum value
|
||||||
"""
|
"""
|
||||||
entity_id = "select.secuyou_smart_lock_operating_mode"
|
entity_id = "select.secuyou_smart_lock_operating_mode"
|
||||||
state = hass.states.get(entity_id)
|
state = hass.states.get(entity_id)
|
||||||
assert state, "Missing operating mode select entity"
|
assert state, "Missing operating mode select entity"
|
||||||
assert state.attributes["options"] == list(DOOR_LOCK_OPERATING_MODE_MAP.values())
|
# According to the spec, bit=0 means supported and bit=1 means not supported.
|
||||||
# Initial state should be one of the allowed options
|
# The fixture bitmap clears bits 0, 2, and 3, so the supported modes are
|
||||||
|
# Normal, Privacy, and NoRemoteLockUnlock; the other bits are set (not
|
||||||
|
# supported).
|
||||||
|
assert set(state.attributes["options"]) == {
|
||||||
|
"normal",
|
||||||
|
"privacy",
|
||||||
|
"no_remote_lock_unlock",
|
||||||
|
}
|
||||||
|
# Verify that the initial state is part of the allowed options
|
||||||
assert state.state in state.attributes["options"]
|
assert state.state in state.attributes["options"]
|
||||||
|
|
||||||
# Dynamically obtain ids instead of hardcoding
|
# Dynamically obtain ids instead of hardcoding
|
||||||
door_lock_cluster_id = clusters.DoorLock.Attributes.OperatingMode.cluster_id
|
door_lock_cluster_id = clusters.DoorLock.Attributes.OperatingMode.cluster_id
|
||||||
operating_mode_attr_id = clusters.DoorLock.Attributes.OperatingMode.attribute_id
|
operating_mode_attr_id = clusters.DoorLock.Attributes.OperatingMode.attribute_id
|
||||||
|
|
||||||
# Change OperatingMode attribute on the node to 'privacy'
|
# Change OperatingMode attribute on the node to a supported mode ('privacy')
|
||||||
set_node_attribute(
|
set_node_attribute(
|
||||||
matter_node,
|
matter_node,
|
||||||
1,
|
1,
|
||||||
@@ -341,12 +348,12 @@ async def test_door_lock_operating_mode_select(
|
|||||||
state = hass.states.get(entity_id)
|
state = hass.states.get(entity_id)
|
||||||
assert state.state == "privacy"
|
assert state.state == "privacy"
|
||||||
|
|
||||||
# Select another option (vacation) via service to validate mapping
|
# Select another supported option (NoRemoteLockUnlock) via service to validate mapping
|
||||||
matter_client.write_attribute.reset_mock()
|
matter_client.write_attribute.reset_mock()
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
"select",
|
"select",
|
||||||
"select_option",
|
"select_option",
|
||||||
{"entity_id": entity_id, "option": "vacation"},
|
{"entity_id": entity_id, "option": "no_remote_lock_unlock"},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
assert matter_client.write_attribute.call_count == 1
|
assert matter_client.write_attribute.call_count == 1
|
||||||
@@ -356,5 +363,5 @@ async def test_door_lock_operating_mode_select(
|
|||||||
endpoint_id=1,
|
endpoint_id=1,
|
||||||
attribute=clusters.DoorLock.Attributes.OperatingMode,
|
attribute=clusters.DoorLock.Attributes.OperatingMode,
|
||||||
),
|
),
|
||||||
value=clusters.DoorLock.Enums.OperatingModeEnum.kVacation,
|
value=clusters.DoorLock.Enums.OperatingModeEnum.kNoRemoteLockUnlock,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
# serializer version: 1
|
||||||
|
# name: test_device_diagnostics
|
||||||
|
dict({
|
||||||
|
'coordinator_info': dict({
|
||||||
|
'departure': 'Ams',
|
||||||
|
'departure_time': None,
|
||||||
|
'destination': 'Rot',
|
||||||
|
'name': 'To work',
|
||||||
|
'via': 'Ht',
|
||||||
|
}),
|
||||||
|
'device_info': dict({
|
||||||
|
'device_name': 'To work',
|
||||||
|
'manufacturer': 'Nederlandse Spoorwegen',
|
||||||
|
'model': 'Route',
|
||||||
|
'subentry_id': '01K721DZPMEN39R5DK0ATBMSY8',
|
||||||
|
}),
|
||||||
|
'first_trip': dict({
|
||||||
|
'arrival_platform_actual': '12',
|
||||||
|
'arrival_platform_planned': '12',
|
||||||
|
'arrival_time_actual': '2025-09-15 18:37:00+02:00',
|
||||||
|
'arrival_time_planned': '2025-09-15 18:37:00+02:00',
|
||||||
|
'departure_platform_actual': '4',
|
||||||
|
'departure_platform_planned': '4',
|
||||||
|
'departure_time_actual': '2025-09-15 16:35:00+02:00',
|
||||||
|
'departure_time_planned': '2025-09-15 16:34:00+02:00',
|
||||||
|
'going': True,
|
||||||
|
'nr_transfers': 2,
|
||||||
|
'status': 'NORMAL',
|
||||||
|
}),
|
||||||
|
'trip_details': dict({
|
||||||
|
'has_first_trip': True,
|
||||||
|
'has_next_trip': True,
|
||||||
|
'trips_count': 8,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
# ---
|
||||||
|
# name: test_entry_diagnostics
|
||||||
|
dict({
|
||||||
|
'coordinators': dict({
|
||||||
|
'01K721DZPMEN39R5DK0ATBMSY8': dict({
|
||||||
|
'coordinator_info': dict({
|
||||||
|
'departure': 'Ams',
|
||||||
|
'departure_time': None,
|
||||||
|
'destination': 'Rot',
|
||||||
|
'name': 'To work',
|
||||||
|
'via': 'Ht',
|
||||||
|
}),
|
||||||
|
'route_data': dict({
|
||||||
|
'has_first_trip': True,
|
||||||
|
'has_next_trip': True,
|
||||||
|
'trips_count': 8,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'01K721DZPMEN39R5DK0ATBMSY9': dict({
|
||||||
|
'coordinator_info': dict({
|
||||||
|
'departure': 'Hag',
|
||||||
|
'departure_time': '08:00',
|
||||||
|
'destination': 'Utr',
|
||||||
|
'name': 'To home',
|
||||||
|
'via': None,
|
||||||
|
}),
|
||||||
|
'route_data': dict({
|
||||||
|
'has_first_trip': True,
|
||||||
|
'has_next_trip': True,
|
||||||
|
'trips_count': 8,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'entry_data': dict({
|
||||||
|
'api_key': '**REDACTED**',
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
# ---
|
||||||
69
tests/components/nederlandse_spoorwegen/test_diagnostics.py
Normal file
69
tests/components/nederlandse_spoorwegen/test_diagnostics.py
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
"""Tests for the diagnostics data provided by the Nederlandse Spoorwegen integration."""
|
||||||
|
|
||||||
|
from unittest.mock import AsyncMock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from syrupy.assertion import SnapshotAssertion
|
||||||
|
from syrupy.filters import props
|
||||||
|
|
||||||
|
from homeassistant.components.nederlandse_spoorwegen.const import DOMAIN
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import device_registry as dr
|
||||||
|
|
||||||
|
from . import setup_integration
|
||||||
|
from .const import SUBENTRY_ID_1
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
from tests.components.diagnostics import (
|
||||||
|
get_diagnostics_for_config_entry,
|
||||||
|
get_diagnostics_for_device,
|
||||||
|
)
|
||||||
|
from tests.typing import ClientSessionGenerator
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.freeze_time("2025-09-15 14:30:00+00:00")
|
||||||
|
async def test_entry_diagnostics(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
hass_client: ClientSessionGenerator,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
snapshot: SnapshotAssertion,
|
||||||
|
mock_nsapi: AsyncMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test config entry diagnostics."""
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
await setup_integration(hass, mock_config_entry)
|
||||||
|
|
||||||
|
# Trigger update for all coordinators before diagnostics
|
||||||
|
for coordinator in mock_config_entry.runtime_data.values():
|
||||||
|
await coordinator.async_refresh()
|
||||||
|
|
||||||
|
result = await get_diagnostics_for_config_entry(
|
||||||
|
hass, hass_client, mock_config_entry
|
||||||
|
)
|
||||||
|
assert result == snapshot(exclude=props("created_at", "modified_at"))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.freeze_time("2025-09-15 14:30:00+00:00")
|
||||||
|
async def test_device_diagnostics(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
hass_client: ClientSessionGenerator,
|
||||||
|
device_registry: dr.DeviceRegistry,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
snapshot: SnapshotAssertion,
|
||||||
|
mock_nsapi: AsyncMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test device diagnostics."""
|
||||||
|
# Ensure integration is set up so device exists
|
||||||
|
await setup_integration(hass, mock_config_entry)
|
||||||
|
|
||||||
|
device = device_registry.async_get_device(identifiers={(DOMAIN, SUBENTRY_ID_1)})
|
||||||
|
assert device is not None
|
||||||
|
|
||||||
|
# Trigger update for the coordinator before diagnostics
|
||||||
|
coordinator = mock_config_entry.runtime_data[SUBENTRY_ID_1]
|
||||||
|
await coordinator.async_refresh()
|
||||||
|
|
||||||
|
result = await get_diagnostics_for_device(
|
||||||
|
hass, hass_client, mock_config_entry, device
|
||||||
|
)
|
||||||
|
assert result == snapshot(exclude=props("created_at", "modified_at"))
|
||||||
@@ -89,7 +89,7 @@ async def test_setup_component_with_webhook(
|
|||||||
|
|
||||||
assert hass.states.get(camera_entity_indoor).state == "streaming"
|
assert hass.states.get(camera_entity_indoor).state == "streaming"
|
||||||
|
|
||||||
# Test outdoor camera events - not yet supported
|
# Test outdoor camera events
|
||||||
assert hass.states.get(camera_entity_outdoor).state == "streaming"
|
assert hass.states.get(camera_entity_outdoor).state == "streaming"
|
||||||
response = {
|
response = {
|
||||||
"event_type": "off",
|
"event_type": "off",
|
||||||
@@ -100,8 +100,7 @@ async def test_setup_component_with_webhook(
|
|||||||
}
|
}
|
||||||
await simulate_webhook(hass, webhook_id, response)
|
await simulate_webhook(hass, webhook_id, response)
|
||||||
|
|
||||||
# The NOCamera-off push_type is not yet supported (assert should be "idle" when supported)
|
assert hass.states.get(camera_entity_outdoor).state == "idle"
|
||||||
assert hass.states.get(camera_entity_outdoor).state == "streaming"
|
|
||||||
|
|
||||||
response = {
|
response = {
|
||||||
"event_type": "on",
|
"event_type": "on",
|
||||||
@@ -425,8 +424,19 @@ async def test_service_set_camera_light_invalid_type(
|
|||||||
assert "NACamera <Hall> does not have a floodlight" in excinfo.value.args[0]
|
assert "NACamera <Hall> does not have a floodlight" in excinfo.value.args[0]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("camera_type", "camera_id", "camera_entity"),
|
||||||
|
[
|
||||||
|
("NACamera", "12:34:56:00:f1:62", "camera.hall"),
|
||||||
|
("NOCamera", "12:34:56:10:b9:0e", "camera.front"),
|
||||||
|
],
|
||||||
|
)
|
||||||
async def test_camera_reconnect_webhook(
|
async def test_camera_reconnect_webhook(
|
||||||
hass: HomeAssistant, config_entry: MockConfigEntry
|
hass: HomeAssistant,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
camera_type: str,
|
||||||
|
camera_id: str,
|
||||||
|
camera_entity: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test webhook event on camera reconnect."""
|
"""Test webhook event on camera reconnect."""
|
||||||
fake_post_hits = 0
|
fake_post_hits = 0
|
||||||
@@ -472,7 +482,7 @@ async def test_camera_reconnect_webhook(
|
|||||||
|
|
||||||
# Fake camera reconnect
|
# Fake camera reconnect
|
||||||
response = {
|
response = {
|
||||||
"push_type": "NACamera-connection",
|
"push_type": f"{camera_type}-connection",
|
||||||
}
|
}
|
||||||
await simulate_webhook(hass, webhook_id, response)
|
await simulate_webhook(hass, webhook_id, response)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@@ -484,6 +494,30 @@ async def test_camera_reconnect_webhook(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert fake_post_hits >= calls
|
assert fake_post_hits >= calls
|
||||||
|
|
||||||
|
# Real camera disconnect
|
||||||
|
assert hass.states.get(camera_entity).state == "streaming"
|
||||||
|
response = {
|
||||||
|
"event_type": "disconnection",
|
||||||
|
"device_id": camera_id,
|
||||||
|
"camera_id": camera_id,
|
||||||
|
"event_id": "601dce1560abca1ebad9b723",
|
||||||
|
"push_type": f"{camera_type}-disconnection",
|
||||||
|
}
|
||||||
|
await simulate_webhook(hass, webhook_id, response)
|
||||||
|
|
||||||
|
assert hass.states.get(camera_entity).state == "idle"
|
||||||
|
|
||||||
|
response = {
|
||||||
|
"event_type": "connection",
|
||||||
|
"device_id": camera_id,
|
||||||
|
"camera_id": camera_id,
|
||||||
|
"event_id": "646227f1dc0dfa000ec5f350",
|
||||||
|
"push_type": f"{camera_type}-connection",
|
||||||
|
}
|
||||||
|
await simulate_webhook(hass, webhook_id, response)
|
||||||
|
|
||||||
|
assert hass.states.get(camera_entity).state == "streaming"
|
||||||
|
|
||||||
|
|
||||||
async def test_webhook_person_event(
|
async def test_webhook_person_event(
|
||||||
hass: HomeAssistant, config_entry: MockConfigEntry, netatmo_auth: AsyncMock
|
hass: HomeAssistant, config_entry: MockConfigEntry, netatmo_auth: AsyncMock
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from roborock import (
|
|||||||
RoborockInvalidUserAgreement,
|
RoborockInvalidUserAgreement,
|
||||||
RoborockNoUserAgreement,
|
RoborockNoUserAgreement,
|
||||||
)
|
)
|
||||||
|
from roborock.exceptions import RoborockException
|
||||||
|
|
||||||
from homeassistant.components.roborock.const import DOMAIN
|
from homeassistant.components.roborock.const import DOMAIN
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
@@ -330,3 +331,71 @@ async def test_cloud_api_repair(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert len(issue_registry.issues) == 0
|
assert len(issue_registry.issues) == 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("platforms", [[Platform.SENSOR]])
|
||||||
|
async def test_zeo_device_fails_setup(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_roborock_entry: MockConfigEntry,
|
||||||
|
device_registry: DeviceRegistry,
|
||||||
|
fake_devices: list[FakeDevice],
|
||||||
|
) -> None:
|
||||||
|
"""Simulate an error while setting up a zeo device."""
|
||||||
|
# We have a single zeo device in the test setup. Find it then set it to fail.
|
||||||
|
zeo_device = next(
|
||||||
|
(device for device in fake_devices if device.zeo is not None),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
assert zeo_device is not None
|
||||||
|
zeo_device.zeo.query_values.side_effect = RoborockException("Simulated Zeo failure")
|
||||||
|
|
||||||
|
await hass.config_entries.async_setup(mock_roborock_entry.entry_id)
|
||||||
|
assert mock_roborock_entry.state is ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
# The current behavior is that we do not add the Zeo device if it fails to setup
|
||||||
|
found_devices = device_registry.devices.get_devices_for_config_entry_id(
|
||||||
|
mock_roborock_entry.entry_id
|
||||||
|
)
|
||||||
|
assert {device.name for device in found_devices} == {
|
||||||
|
"Roborock S7 MaxV",
|
||||||
|
"Roborock S7 MaxV Dock",
|
||||||
|
"Roborock S7 2",
|
||||||
|
"Roborock S7 2 Dock",
|
||||||
|
"Dyad Pro",
|
||||||
|
# Zeo device is missing
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("platforms", [[Platform.SENSOR]])
|
||||||
|
async def test_dyad_device_fails_setup(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_roborock_entry: MockConfigEntry,
|
||||||
|
device_registry: DeviceRegistry,
|
||||||
|
fake_devices: list[FakeDevice],
|
||||||
|
) -> None:
|
||||||
|
"""Simulate an error while setting up a dyad device."""
|
||||||
|
# We have a single dyad device in the test setup. Find it then set it to fail.
|
||||||
|
dyad_device = next(
|
||||||
|
(device for device in fake_devices if device.dyad is not None),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
assert dyad_device is not None
|
||||||
|
dyad_device.dyad.query_values.side_effect = RoborockException(
|
||||||
|
"Simulated Dyad failure"
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.config_entries.async_setup(mock_roborock_entry.entry_id)
|
||||||
|
assert mock_roborock_entry.state is ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
# The current behavior is that we do not add the Dyad device if it fails to setup
|
||||||
|
found_devices = device_registry.devices.get_devices_for_config_entry_id(
|
||||||
|
mock_roborock_entry.entry_id
|
||||||
|
)
|
||||||
|
assert {device.name for device in found_devices} == {
|
||||||
|
"Roborock S7 MaxV",
|
||||||
|
"Roborock S7 MaxV Dock",
|
||||||
|
"Roborock S7 2",
|
||||||
|
"Roborock S7 2 Dock",
|
||||||
|
# Dyad device is missing
|
||||||
|
"Zeo One",
|
||||||
|
}
|
||||||
|
|||||||
293
tests/components/shelly/test_services.py
Normal file
293
tests/components/shelly/test_services.py
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
"""Tests for Shelly services."""
|
||||||
|
|
||||||
|
from unittest.mock import Mock
|
||||||
|
|
||||||
|
from aioshelly.exceptions import DeviceConnectionError, RpcCallError
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components.shelly.const import ATTR_KEY, ATTR_VALUE, DOMAIN
|
||||||
|
from homeassistant.components.shelly.services import (
|
||||||
|
SERVICE_GET_KVS_VALUE,
|
||||||
|
SERVICE_SET_KVS_VALUE,
|
||||||
|
)
|
||||||
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
|
from homeassistant.const import ATTR_DEVICE_ID
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||||
|
from homeassistant.helpers import device_registry as dr
|
||||||
|
|
||||||
|
from . import init_integration
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
async def test_service_get_kvs_value(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_rpc_device: Mock,
|
||||||
|
device_registry: dr.DeviceRegistry,
|
||||||
|
) -> None:
|
||||||
|
"""Test get_kvs_value service."""
|
||||||
|
entry = await init_integration(hass, 2)
|
||||||
|
|
||||||
|
device = dr.async_entries_for_config_entry(device_registry, entry.entry_id)[0]
|
||||||
|
|
||||||
|
mock_rpc_device.kvs_get.return_value = {
|
||||||
|
"etag": "16mLia9TRt8lGhj9Zf5Dp6Hw==",
|
||||||
|
"value": "test_value",
|
||||||
|
}
|
||||||
|
|
||||||
|
response = await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_GET_KVS_VALUE,
|
||||||
|
{ATTR_DEVICE_ID: device.id, ATTR_KEY: "test_key"},
|
||||||
|
blocking=True,
|
||||||
|
return_response=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response == {"value": "test_value"}
|
||||||
|
mock_rpc_device.kvs_get.assert_called_once_with("test_key")
|
||||||
|
|
||||||
|
|
||||||
|
async def test_service_get_kvs_value_invalid_device(hass: HomeAssistant) -> None:
|
||||||
|
"""Test get_kvs_value service with invalid device ID."""
|
||||||
|
await init_integration(hass, 2)
|
||||||
|
|
||||||
|
with pytest.raises(ServiceValidationError) as exc_info:
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_GET_KVS_VALUE,
|
||||||
|
{ATTR_DEVICE_ID: "invalid_device_id", ATTR_KEY: "test_key"},
|
||||||
|
blocking=True,
|
||||||
|
return_response=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert exc_info.value.translation_domain == DOMAIN
|
||||||
|
assert exc_info.value.translation_key == "invalid_device_id"
|
||||||
|
assert exc_info.value.translation_placeholders == {
|
||||||
|
ATTR_DEVICE_ID: "invalid_device_id"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_service_get_kvs_value_block_device(
|
||||||
|
hass: HomeAssistant, mock_block_device: Mock, device_registry: dr.DeviceRegistry
|
||||||
|
) -> None:
|
||||||
|
"""Test get_kvs_value service with non-RPC (Gen1) device."""
|
||||||
|
entry = await init_integration(hass, 1)
|
||||||
|
|
||||||
|
device = dr.async_entries_for_config_entry(device_registry, entry.entry_id)[0]
|
||||||
|
|
||||||
|
with pytest.raises(ServiceValidationError) as exc_info:
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_GET_KVS_VALUE,
|
||||||
|
{ATTR_DEVICE_ID: device.id, ATTR_KEY: "test_key"},
|
||||||
|
blocking=True,
|
||||||
|
return_response=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert exc_info.value.translation_domain == DOMAIN
|
||||||
|
assert exc_info.value.translation_key == "kvs_not_supported"
|
||||||
|
assert exc_info.value.translation_placeholders == {"device": entry.title}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("exc", "translation_key"),
|
||||||
|
[
|
||||||
|
(RpcCallError(999), "rpc_call_error"),
|
||||||
|
(DeviceConnectionError, "device_communication_error"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_service_get_kvs_value_exc(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_rpc_device: Mock,
|
||||||
|
device_registry: dr.DeviceRegistry,
|
||||||
|
exc: Exception,
|
||||||
|
translation_key: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test get_kvs_value service with exception."""
|
||||||
|
entry = await init_integration(hass, 2)
|
||||||
|
|
||||||
|
device = dr.async_entries_for_config_entry(device_registry, entry.entry_id)[0]
|
||||||
|
|
||||||
|
mock_rpc_device.kvs_get.side_effect = exc
|
||||||
|
|
||||||
|
with pytest.raises(HomeAssistantError) as exc_info:
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_GET_KVS_VALUE,
|
||||||
|
{ATTR_DEVICE_ID: device.id, ATTR_KEY: "test_key"},
|
||||||
|
blocking=True,
|
||||||
|
return_response=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert exc_info.value.translation_domain == DOMAIN
|
||||||
|
assert exc_info.value.translation_key == translation_key
|
||||||
|
assert exc_info.value.translation_placeholders == {"device": entry.title}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_config_entry_not_loaded(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
device_registry: dr.DeviceRegistry,
|
||||||
|
mock_rpc_device: Mock,
|
||||||
|
) -> None:
|
||||||
|
"""Test config entry not loaded."""
|
||||||
|
entry = await init_integration(hass, 2)
|
||||||
|
|
||||||
|
device = dr.async_entries_for_config_entry(device_registry, entry.entry_id)[0]
|
||||||
|
|
||||||
|
await hass.config_entries.async_unload(entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert entry.state is ConfigEntryState.NOT_LOADED
|
||||||
|
|
||||||
|
with pytest.raises(ServiceValidationError) as exc_info:
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_GET_KVS_VALUE,
|
||||||
|
{ATTR_DEVICE_ID: device.id, ATTR_KEY: "test_key"},
|
||||||
|
blocking=True,
|
||||||
|
return_response=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert exc_info.value.translation_domain == DOMAIN
|
||||||
|
assert exc_info.value.translation_key == "entry_not_loaded"
|
||||||
|
assert exc_info.value.translation_placeholders == {"device": entry.title}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_service_get_kvs_value_sleeping_device(
|
||||||
|
hass: HomeAssistant, mock_rpc_device: Mock, device_registry: dr.DeviceRegistry
|
||||||
|
) -> None:
|
||||||
|
"""Test get_kvs_value service with RPC sleeping device."""
|
||||||
|
entry = await init_integration(hass, 2, sleep_period=1000)
|
||||||
|
|
||||||
|
# Make device online
|
||||||
|
mock_rpc_device.mock_online()
|
||||||
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
|
|
||||||
|
device = dr.async_entries_for_config_entry(device_registry, entry.entry_id)[0]
|
||||||
|
|
||||||
|
with pytest.raises(ServiceValidationError) as exc_info:
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_GET_KVS_VALUE,
|
||||||
|
{ATTR_DEVICE_ID: device.id, ATTR_KEY: "test_key"},
|
||||||
|
blocking=True,
|
||||||
|
return_response=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert exc_info.value.translation_domain == DOMAIN
|
||||||
|
assert exc_info.value.translation_key == "kvs_not_supported"
|
||||||
|
assert exc_info.value.translation_placeholders == {"device": entry.title}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_service_set_kvs_value(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_rpc_device: Mock,
|
||||||
|
device_registry: dr.DeviceRegistry,
|
||||||
|
) -> None:
|
||||||
|
"""Test set_kvs_value service."""
|
||||||
|
entry = await init_integration(hass, 2)
|
||||||
|
|
||||||
|
device = dr.async_entries_for_config_entry(device_registry, entry.entry_id)[0]
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_SET_KVS_VALUE,
|
||||||
|
{ATTR_DEVICE_ID: device.id, ATTR_KEY: "test_key", ATTR_VALUE: "test_value"},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_rpc_device.kvs_set.assert_called_once_with("test_key", "test_value")
|
||||||
|
|
||||||
|
|
||||||
|
async def test_service_get_kvs_value_config_entry_not_found(
|
||||||
|
hass: HomeAssistant, mock_rpc_device: Mock, device_registry: dr.DeviceRegistry
|
||||||
|
) -> None:
|
||||||
|
"""Test device with no config entries."""
|
||||||
|
entry = await init_integration(hass, 2)
|
||||||
|
|
||||||
|
device = dr.async_entries_for_config_entry(device_registry, entry.entry_id)[0]
|
||||||
|
|
||||||
|
# Remove all config entries from device
|
||||||
|
device_registry.devices[device.id].config_entries.clear()
|
||||||
|
|
||||||
|
with pytest.raises(ServiceValidationError) as exc_info:
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_GET_KVS_VALUE,
|
||||||
|
{ATTR_DEVICE_ID: device.id, ATTR_KEY: "test_key"},
|
||||||
|
blocking=True,
|
||||||
|
return_response=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert exc_info.value.translation_domain == DOMAIN
|
||||||
|
assert exc_info.value.translation_key == "config_entry_not_found"
|
||||||
|
assert exc_info.value.translation_placeholders == {"device_id": device.id}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_service_get_kvs_value_device_not_initialized(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_rpc_device: Mock,
|
||||||
|
device_registry: dr.DeviceRegistry,
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
) -> None:
|
||||||
|
"""Test get_kvs_value if runtime_data.rpc is None."""
|
||||||
|
entry = await init_integration(hass, 2)
|
||||||
|
|
||||||
|
device = dr.async_entries_for_config_entry(device_registry, entry.entry_id)[0]
|
||||||
|
|
||||||
|
monkeypatch.delattr(entry.runtime_data, "rpc")
|
||||||
|
|
||||||
|
with pytest.raises(ServiceValidationError) as exc_info:
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_GET_KVS_VALUE,
|
||||||
|
{ATTR_DEVICE_ID: device.id, ATTR_KEY: "test_key"},
|
||||||
|
blocking=True,
|
||||||
|
return_response=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert exc_info.value.translation_domain == DOMAIN
|
||||||
|
assert exc_info.value.translation_key == "device_not_initialized"
|
||||||
|
assert exc_info.value.translation_placeholders == {"device": entry.title}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_service_get_kvs_value_wrong_domain(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_rpc_device: Mock,
|
||||||
|
device_registry: dr.DeviceRegistry,
|
||||||
|
) -> None:
|
||||||
|
"""Test get_kvs_value when device has config entries from different domains."""
|
||||||
|
entry = await init_integration(hass, 2)
|
||||||
|
|
||||||
|
device = dr.async_entries_for_config_entry(device_registry, entry.entry_id)[0]
|
||||||
|
|
||||||
|
# Create a config entry with different domain and add it to the device
|
||||||
|
other_entry = MockConfigEntry(
|
||||||
|
domain="other_domain",
|
||||||
|
data={},
|
||||||
|
)
|
||||||
|
other_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
# Add the other domain's config entry to the device
|
||||||
|
device_registry.async_update_device(
|
||||||
|
device.id, add_config_entry_id=other_entry.entry_id
|
||||||
|
)
|
||||||
|
|
||||||
|
# Remove the original Shelly config entry
|
||||||
|
device_registry.async_update_device(
|
||||||
|
device.id, remove_config_entry_id=entry.entry_id
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ServiceValidationError) as exc_info:
|
||||||
|
await hass.services.async_call(
|
||||||
|
DOMAIN,
|
||||||
|
SERVICE_GET_KVS_VALUE,
|
||||||
|
{ATTR_DEVICE_ID: device.id, ATTR_KEY: "test_key"},
|
||||||
|
blocking=True,
|
||||||
|
return_response=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert exc_info.value.translation_domain == DOMAIN
|
||||||
|
assert exc_info.value.translation_key == "config_entry_not_found"
|
||||||
|
assert exc_info.value.translation_placeholders == {"device_id": device.id}
|
||||||
@@ -404,6 +404,62 @@ async def test_form_cannot_connect(hass: HomeAssistant) -> None:
|
|||||||
assert result["context"]["unique_id"] == UUID
|
assert result["context"]["unique_id"] == UUID
|
||||||
|
|
||||||
|
|
||||||
|
async def test_form_missing_uuid(hass: HomeAssistant) -> None:
|
||||||
|
"""Test we handle cannot connect error, then succeed after retry."""
|
||||||
|
|
||||||
|
# Start the flow
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": "edit"}
|
||||||
|
)
|
||||||
|
assert result["type"] is FlowResultType.FORM
|
||||||
|
|
||||||
|
# First attempt: simulate cannot connect
|
||||||
|
with patch(
|
||||||
|
"pysqueezebox.Server.async_query",
|
||||||
|
return_value={"some_other_key": "some_value"},
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
CONF_HOST: HOST,
|
||||||
|
CONF_PORT: PORT,
|
||||||
|
CONF_USERNAME: "",
|
||||||
|
CONF_PASSWORD: "",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
# We should still be in a form, with an error
|
||||||
|
assert result["type"] is FlowResultType.FORM
|
||||||
|
assert result["errors"] == {"base": "missing_uuid"}
|
||||||
|
|
||||||
|
# Second attempt: simulate a successful connection
|
||||||
|
with patch(
|
||||||
|
"pysqueezebox.Server.async_query",
|
||||||
|
return_value={"uuid": UUID},
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
CONF_HOST: HOST,
|
||||||
|
CONF_PORT: PORT,
|
||||||
|
CONF_USERNAME: "",
|
||||||
|
CONF_PASSWORD: "",
|
||||||
|
CONF_HTTPS: False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||||
|
assert result["title"] == HOST # the flow uses host as title
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_HOST: HOST,
|
||||||
|
CONF_PORT: PORT,
|
||||||
|
CONF_USERNAME: "",
|
||||||
|
CONF_PASSWORD: "",
|
||||||
|
CONF_HTTPS: False,
|
||||||
|
}
|
||||||
|
assert result["context"]["unique_id"] == UUID
|
||||||
|
|
||||||
|
|
||||||
async def test_discovery(hass: HomeAssistant) -> None:
|
async def test_discovery(hass: HomeAssistant) -> None:
|
||||||
"""Test handling of discovered server, then completing the flow."""
|
"""Test handling of discovered server, then completing the flow."""
|
||||||
|
|
||||||
|
|||||||
228
tests/components/switch/test_trigger.py
Normal file
228
tests/components/switch/test_trigger.py
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
"""Test switch triggers."""
|
||||||
|
|
||||||
|
from collections.abc import Generator
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components.switch import DOMAIN
|
||||||
|
from homeassistant.const import ATTR_LABEL_ID, CONF_ENTITY_ID, STATE_OFF, STATE_ON
|
||||||
|
from homeassistant.core import HomeAssistant, ServiceCall
|
||||||
|
|
||||||
|
from tests.components import (
|
||||||
|
StateDescription,
|
||||||
|
arm_trigger,
|
||||||
|
parametrize_target_entities,
|
||||||
|
parametrize_trigger_states,
|
||||||
|
set_or_remove_state,
|
||||||
|
target_entities,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True, name="stub_blueprint_populate")
|
||||||
|
def stub_blueprint_populate_autouse(stub_blueprint_populate: None) -> None:
|
||||||
|
"""Stub copying the blueprints to the config folder."""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="enable_experimental_triggers_conditions")
|
||||||
|
def enable_experimental_triggers_conditions() -> Generator[None]:
|
||||||
|
"""Enable experimental triggers and conditions."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.labs.async_is_preview_feature_enabled",
|
||||||
|
return_value=True,
|
||||||
|
):
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def target_switches(hass: HomeAssistant) -> list[str]:
|
||||||
|
"""Create multiple switch entities associated with different targets."""
|
||||||
|
return (await target_entities(hass, DOMAIN))["included"]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"trigger_key",
|
||||||
|
[
|
||||||
|
"switch.turned_off",
|
||||||
|
"switch.turned_on",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_switch_triggers_gated_by_labs_flag(
|
||||||
|
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, trigger_key: str
|
||||||
|
) -> None:
|
||||||
|
"""Test the switch triggers are gated by the labs flag."""
|
||||||
|
await arm_trigger(hass, trigger_key, None, {ATTR_LABEL_ID: "test_label"})
|
||||||
|
assert (
|
||||||
|
"Unnamed automation failed to setup triggers and has been disabled: Trigger "
|
||||||
|
f"'{trigger_key}' requires the experimental 'New triggers and conditions' "
|
||||||
|
"feature to be enabled in Home Assistant Labs settings (feature flag: "
|
||||||
|
"'new_triggers_conditions')"
|
||||||
|
) in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("trigger_target_config", "entity_id", "entities_in_target"),
|
||||||
|
parametrize_target_entities(DOMAIN),
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("trigger", "states"),
|
||||||
|
[
|
||||||
|
*parametrize_trigger_states(
|
||||||
|
trigger="switch.turned_off",
|
||||||
|
target_states=[STATE_OFF],
|
||||||
|
other_states=[STATE_ON],
|
||||||
|
),
|
||||||
|
*parametrize_trigger_states(
|
||||||
|
trigger="switch.turned_on",
|
||||||
|
target_states=[STATE_ON],
|
||||||
|
other_states=[STATE_OFF],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_switch_state_trigger_behavior_any(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
service_calls: list[ServiceCall],
|
||||||
|
target_switches: list[str],
|
||||||
|
trigger_target_config: dict,
|
||||||
|
entity_id: str,
|
||||||
|
entities_in_target: int,
|
||||||
|
trigger: str,
|
||||||
|
states: list[StateDescription],
|
||||||
|
) -> None:
|
||||||
|
"""Test that the switch state trigger fires when any switch state changes to a specific state."""
|
||||||
|
other_entity_ids = set(target_switches) - {entity_id}
|
||||||
|
|
||||||
|
# Set all switches, including the tested one, to the initial state
|
||||||
|
for eid in target_switches:
|
||||||
|
set_or_remove_state(hass, eid, states[0]["included"])
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
await arm_trigger(hass, trigger, {}, trigger_target_config)
|
||||||
|
|
||||||
|
for state in states[1:]:
|
||||||
|
included_state = state["included"]
|
||||||
|
set_or_remove_state(hass, entity_id, included_state)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(service_calls) == state["count"]
|
||||||
|
for service_call in service_calls:
|
||||||
|
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||||
|
service_calls.clear()
|
||||||
|
|
||||||
|
# Check if changing other switches also triggers
|
||||||
|
for other_entity_id in other_entity_ids:
|
||||||
|
set_or_remove_state(hass, other_entity_id, included_state)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(service_calls) == (entities_in_target - 1) * state["count"]
|
||||||
|
service_calls.clear()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("trigger_target_config", "entity_id", "entities_in_target"),
|
||||||
|
parametrize_target_entities(DOMAIN),
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("trigger", "states"),
|
||||||
|
[
|
||||||
|
*parametrize_trigger_states(
|
||||||
|
trigger="switch.turned_off",
|
||||||
|
target_states=[STATE_OFF],
|
||||||
|
other_states=[STATE_ON],
|
||||||
|
),
|
||||||
|
*parametrize_trigger_states(
|
||||||
|
trigger="switch.turned_on",
|
||||||
|
target_states=[STATE_ON],
|
||||||
|
other_states=[STATE_OFF],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_switch_state_trigger_behavior_first(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
service_calls: list[ServiceCall],
|
||||||
|
target_switches: list[str],
|
||||||
|
trigger_target_config: dict,
|
||||||
|
entity_id: str,
|
||||||
|
entities_in_target: int,
|
||||||
|
trigger: str,
|
||||||
|
states: list[StateDescription],
|
||||||
|
) -> None:
|
||||||
|
"""Test that the switch state trigger fires when the first switch changes to a specific state."""
|
||||||
|
other_entity_ids = set(target_switches) - {entity_id}
|
||||||
|
|
||||||
|
# Set all switches, including the tested one, to the initial state
|
||||||
|
for eid in target_switches:
|
||||||
|
set_or_remove_state(hass, eid, states[0]["included"])
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
await arm_trigger(hass, trigger, {"behavior": "first"}, trigger_target_config)
|
||||||
|
|
||||||
|
for state in states[1:]:
|
||||||
|
included_state = state["included"]
|
||||||
|
set_or_remove_state(hass, entity_id, included_state)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(service_calls) == state["count"]
|
||||||
|
for service_call in service_calls:
|
||||||
|
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||||
|
service_calls.clear()
|
||||||
|
|
||||||
|
# Triggering other switches should not cause the trigger to fire again
|
||||||
|
for other_entity_id in other_entity_ids:
|
||||||
|
set_or_remove_state(hass, other_entity_id, included_state)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(service_calls) == 0
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("enable_experimental_triggers_conditions")
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("trigger_target_config", "entity_id", "entities_in_target"),
|
||||||
|
parametrize_target_entities(DOMAIN),
|
||||||
|
)
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("trigger", "states"),
|
||||||
|
[
|
||||||
|
*parametrize_trigger_states(
|
||||||
|
trigger="switch.turned_off",
|
||||||
|
target_states=[STATE_OFF],
|
||||||
|
other_states=[STATE_ON],
|
||||||
|
),
|
||||||
|
*parametrize_trigger_states(
|
||||||
|
trigger="switch.turned_on",
|
||||||
|
target_states=[STATE_ON],
|
||||||
|
other_states=[STATE_OFF],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_switch_state_trigger_behavior_last(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
service_calls: list[ServiceCall],
|
||||||
|
target_switches: list[str],
|
||||||
|
trigger_target_config: dict,
|
||||||
|
entity_id: str,
|
||||||
|
entities_in_target: int,
|
||||||
|
trigger: str,
|
||||||
|
states: list[StateDescription],
|
||||||
|
) -> None:
|
||||||
|
"""Test that the switch state trigger fires when the last switch changes to a specific state."""
|
||||||
|
other_entity_ids = set(target_switches) - {entity_id}
|
||||||
|
|
||||||
|
# Set all switches, including the tested one, to the initial state
|
||||||
|
for eid in target_switches:
|
||||||
|
set_or_remove_state(hass, eid, states[0]["included"])
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
await arm_trigger(hass, trigger, {"behavior": "last"}, trigger_target_config)
|
||||||
|
|
||||||
|
for state in states[1:]:
|
||||||
|
included_state = state["included"]
|
||||||
|
for other_entity_id in other_entity_ids:
|
||||||
|
set_or_remove_state(hass, other_entity_id, included_state)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(service_calls) == 0
|
||||||
|
|
||||||
|
set_or_remove_state(hass, entity_id, included_state)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert len(service_calls) == state["count"]
|
||||||
|
for service_call in service_calls:
|
||||||
|
assert service_call.data[CONF_ENTITY_ID] == entity_id
|
||||||
|
service_calls.clear()
|
||||||
@@ -43,31 +43,6 @@ async def target_texts(hass: HomeAssistant) -> list[str]:
|
|||||||
return (await target_entities(hass, "text"))["included"]
|
return (await target_entities(hass, "text"))["included"]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"trigger_key",
|
|
||||||
[
|
|
||||||
"alarm_control_panel.armed",
|
|
||||||
"alarm_control_panel.armed_away",
|
|
||||||
"alarm_control_panel.armed_home",
|
|
||||||
"alarm_control_panel.armed_night",
|
|
||||||
"alarm_control_panel.armed_vacation",
|
|
||||||
"alarm_control_panel.disarmed",
|
|
||||||
"alarm_control_panel.triggered",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
async def test_alarm_control_panel_triggers_gated_by_labs_flag(
|
|
||||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, trigger_key: str
|
|
||||||
) -> None:
|
|
||||||
"""Test the ACP triggers are gated by the labs flag."""
|
|
||||||
await arm_trigger(hass, trigger_key, None, {ATTR_LABEL_ID: "test_label"})
|
|
||||||
assert (
|
|
||||||
"Unnamed automation failed to setup triggers and has been disabled: Trigger "
|
|
||||||
f"'{trigger_key}' requires the experimental 'New triggers and conditions' "
|
|
||||||
"feature to be enabled in Home Assistant Labs settings (feature flag: "
|
|
||||||
"'new_triggers_conditions')"
|
|
||||||
) in caplog.text
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("trigger_key", ["text.changed"])
|
@pytest.mark.parametrize("trigger_key", ["text.changed"])
|
||||||
async def test_text_triggers_gated_by_labs_flag(
|
async def test_text_triggers_gated_by_labs_flag(
|
||||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, trigger_key: str
|
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, trigger_key: str
|
||||||
|
|||||||
@@ -333,3 +333,82 @@ async def mock_config_entry(
|
|||||||
)
|
)
|
||||||
config_entry.add_to_hass(hass)
|
config_entry.add_to_hass(hass)
|
||||||
return config_entry
|
return config_entry
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="mock_vlp_file")
|
||||||
|
def mock_vlp_content():
|
||||||
|
"""Mock vlp file content."""
|
||||||
|
return b"""<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project>
|
||||||
|
<Settings>
|
||||||
|
<Name></Name>
|
||||||
|
<Comments></Comments>
|
||||||
|
<DateModified>21/03/2025 12:30:06</DateModified>
|
||||||
|
<Version>11.6.4.0</Version>
|
||||||
|
</Settings>
|
||||||
|
<Preferences>
|
||||||
|
<MasterClock>
|
||||||
|
<IgnoreMultipleClocks>0</IgnoreMultipleClocks>
|
||||||
|
<IgnoreNoSignumClock>0</IgnoreNoSignumClock>
|
||||||
|
<IgnoreNoMasterClock>0</IgnoreNoMasterClock>
|
||||||
|
</MasterClock>
|
||||||
|
</Preferences>
|
||||||
|
<Customer>
|
||||||
|
<Name></Name>
|
||||||
|
<Address></Address>
|
||||||
|
<City></City>
|
||||||
|
<Zip></Zip>
|
||||||
|
<Phone></Phone>
|
||||||
|
<Fax></Fax>
|
||||||
|
<Email></Email>
|
||||||
|
<Email2></Email2>
|
||||||
|
<Country></Country>
|
||||||
|
</Customer>
|
||||||
|
<Installer>
|
||||||
|
<Name></Name>
|
||||||
|
<Address></Address>
|
||||||
|
<City></City>
|
||||||
|
<Zip></Zip>
|
||||||
|
<Phone></Phone>
|
||||||
|
<Fax></Fax>
|
||||||
|
<Email></Email>
|
||||||
|
<Email2></Email2>
|
||||||
|
<Country></Country>
|
||||||
|
</Installer>
|
||||||
|
<Connection>
|
||||||
|
<NetworkHost>192.168.88.9</NetworkHost>
|
||||||
|
<NetworkPort>27015</NetworkPort>
|
||||||
|
<NetworkHostname>7b95834e</NetworkHostname>
|
||||||
|
<NetworkSsl>True</NetworkSsl>
|
||||||
|
<NetworkPassword></NetworkPassword>
|
||||||
|
<NetworkRequireAuth>False</NetworkRequireAuth>
|
||||||
|
</Connection>
|
||||||
|
<Modules>
|
||||||
|
<Module build="2235" address="FE" type="VMBSIG" serial="34DF" locked="0" layer="0" terminator="0">
|
||||||
|
<Caption>VMBSIG</Caption>
|
||||||
|
<Remark></Remark>
|
||||||
|
<Memory>564D42534947FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0300010001000101FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</Memory>
|
||||||
|
<Snapshot>564D42534947FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF030001000100010100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</Snapshot>
|
||||||
|
<SnapshotVerification>FFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000</SnapshotVerification>
|
||||||
|
</Module>
|
||||||
|
<Module build="2423" address="CE" type="VMB4LEDPWM-20" serial="BD30" locked="0" layer="0" terminator="0">
|
||||||
|
<Caption>VMB4LEDPWM-20</Caption>
|
||||||
|
<Remark></Remark>
|
||||||
|
<Memory>4368616E6E656C2031FFFFFFFFFFFFFF4368616E6E656C2032FFFFFFFFFFFFFF4368616E6E656C2033FFFFFFFFFFFFFF4368616E6E656C2034FFFFFFFFFFFFFF000000750700160008001700082AF5ECE3E7DDDFDEE5E8F2FD071115181818171A1A1A150E02FFFF1027171A1C151B181A17171209FEF2EAE2E0DCE1DEE4EAF5010DFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000564D42344C454450574D2D3230FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0601FE873FFFFFFFFF7FFFFFFFFFBFFFFFFFFFFEFFFFFFFFBFFFFFFFFF7FFFFFFFFF3FFFFFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000FFFFFFFF0601FE873FFFFFFFFF7FFFFFFFFFBFFFFFFFFFFEFFFFFFFFBFFFFFFFFF7FFFFFFFFF3FFFFFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000FFFFFFFF0601FE873FFFFFFFFF7FFFFFFFFFBFFFFFFFFFFEFFFFFFFFBFFFFFFFFF7FFFFFFFFF3FFFFFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000FFFFFFFF0601FE873FFFFFFFFF7FFFFFFFFFBFFFFFFFFFFEFFFFFFFFBFFFFFFFFF7FFFFFFFFF3FFFFFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000FFFFFFFFFFFFFFFF</Memory>
|
||||||
|
<Snapshot>4368616E6E656C2031FFFFFFFFFFFFFF4368616E6E656C2032FFFFFFFFFFFFFF4368616E6E656C2033FFFFFFFFFFFFFF4368616E6E656C2034FFFFFFFFFFFFFF000000750700160008001700082AF5ECE3E7DDDFDEE5E8F2FD071115181818171A1A1A150E02FFFF1027171A1C151B181A17171209FEF2EAE2E0DCE1DEE4EAF5010DFFFFFFFFFFFFFFFFFFFF000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000564D42344C454450574D2D3230FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0601FE873FFFFFFFFF7FFFFFFFFFBFFFFFFFFFFEFFFFFFFFBFFFFFFFFF7FFFFFFFFF3FFFFFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000FFFFFFFF0601FE873FFFFFFFFF7FFFFFFFFFBFFFFFFFFFFEFFFFFFFFBFFFFFFFFF7FFFFFFFFF3FFFFFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000FFFFFFFF0601FE873FFFFFFFFF7FFFFFFFFFBFFFFFFFFFFEFFFFFFFFBFFFFFFFFF7FFFFFFFFF3FFFFFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000FFFFFFFF0601FE873FFFFFFFFF7FFFFFFFFFBFFFFFFFFFFEFFFFFFFFBFFFFFFFFF7FFFFFFFFF3FFFFFFFFF00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000FFFFFFFFFFFFFFFF</Snapshot>
|
||||||
|
<SnapshotVerification>FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF</SnapshotVerification>
|
||||||
|
</Module>
|
||||||
|
</Modules>
|
||||||
|
<Layers/>
|
||||||
|
<EnergyMonitoring>
|
||||||
|
<Counters/>
|
||||||
|
<Consumers/>
|
||||||
|
<Functions/>
|
||||||
|
<Settings>
|
||||||
|
<AutoMaxConsumption>True</AutoMaxConsumption>
|
||||||
|
<MaxConsumption>0</MaxConsumption>
|
||||||
|
<MinInjection>0</MinInjection>
|
||||||
|
<IdleConsumption>0</IdleConsumption>
|
||||||
|
</Settings>
|
||||||
|
</EnergyMonitoring>
|
||||||
|
</Project>"""
|
||||||
|
|||||||
@@ -1,19 +1,23 @@
|
|||||||
"""Tests for the Velbus config flow."""
|
"""Tests for the Velbus config flow."""
|
||||||
|
|
||||||
from collections.abc import Generator
|
from collections.abc import Generator, Iterator
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from pathlib import Path
|
||||||
from unittest.mock import AsyncMock, MagicMock, patch
|
from unittest.mock import AsyncMock, MagicMock, patch
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import serial.tools.list_ports
|
import serial.tools.list_ports
|
||||||
from velbusaio.exceptions import VelbusConnectionFailed
|
from velbusaio.exceptions import VelbusConnectionFailed
|
||||||
|
|
||||||
from homeassistant.components.velbus.const import CONF_TLS, DOMAIN
|
from homeassistant.components.velbus.const import CONF_TLS, CONF_VLP_FILE, DOMAIN
|
||||||
from homeassistant.config_entries import SOURCE_USB, SOURCE_USER
|
from homeassistant.config_entries import SOURCE_USB, SOURCE_USER
|
||||||
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_SOURCE
|
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT, CONF_SOURCE
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.data_entry_flow import FlowResultType
|
from homeassistant.data_entry_flow import FlowResultType
|
||||||
from homeassistant.helpers.service_info.usb import UsbServiceInfo
|
from homeassistant.helpers.service_info.usb import UsbServiceInfo
|
||||||
|
|
||||||
|
from . import init_integration
|
||||||
from .const import PORT_SERIAL
|
from .const import PORT_SERIAL
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
@@ -40,6 +44,36 @@ def com_port():
|
|||||||
return port
|
return port
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_process_uploaded_file(
|
||||||
|
tmp_path: Path, mock_vlp_file: str
|
||||||
|
) -> Generator[MagicMock]:
|
||||||
|
"""Mock upload vlp file."""
|
||||||
|
file_id_vlp = str(uuid4())
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def _mock_process_uploaded_file(
|
||||||
|
hass: HomeAssistant, uploaded_file_id: str
|
||||||
|
) -> Iterator[Path | None]:
|
||||||
|
with open(tmp_path / uploaded_file_id, "wb") as vlpfile:
|
||||||
|
vlpfile.write(mock_vlp_file)
|
||||||
|
yield tmp_path / uploaded_file_id
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.velbus.config_flow.process_uploaded_file",
|
||||||
|
side_effect=_mock_process_uploaded_file,
|
||||||
|
) as mock_upload,
|
||||||
|
patch(
|
||||||
|
"shutil.move",
|
||||||
|
),
|
||||||
|
):
|
||||||
|
mock_upload.file_id = {
|
||||||
|
CONF_VLP_FILE: file_id_vlp,
|
||||||
|
}
|
||||||
|
yield mock_upload
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
@pytest.fixture(autouse=True)
|
||||||
def override_async_setup_entry() -> Generator[AsyncMock]:
|
def override_async_setup_entry() -> Generator[AsyncMock]:
|
||||||
"""Override async_setup_entry."""
|
"""Override async_setup_entry."""
|
||||||
@@ -109,16 +143,57 @@ async def test_user_network_succes(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
assert result
|
assert result
|
||||||
|
assert result.get("type") is FlowResultType.FORM
|
||||||
|
assert result.get("step_id") == "vlp"
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
assert result
|
||||||
assert result.get("type") is FlowResultType.CREATE_ENTRY
|
assert result.get("type") is FlowResultType.CREATE_ENTRY
|
||||||
assert result.get("title") == "Velbus Network"
|
|
||||||
data = result.get("data")
|
data = result.get("data")
|
||||||
assert data
|
assert data
|
||||||
assert data[CONF_PORT] == expected
|
assert data[CONF_PORT] == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("controller")
|
@pytest.mark.usefixtures("controller_connection_failed")
|
||||||
|
async def test_user_network_connect_failure(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
) -> None:
|
||||||
|
"""Test user network config."""
|
||||||
|
# inttial menu show
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_USER}
|
||||||
|
)
|
||||||
|
assert result
|
||||||
|
assert result.get("flow_id")
|
||||||
|
assert result.get("type") is FlowResultType.MENU
|
||||||
|
assert result.get("step_id") == "user"
|
||||||
|
assert result.get("menu_options") == ["network", "usbselect"]
|
||||||
|
# select the network option
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result.get("flow_id"),
|
||||||
|
{"next_step_id": "network"},
|
||||||
|
)
|
||||||
|
assert result["type"] is FlowResultType.FORM
|
||||||
|
# fill in the network form
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result.get("flow_id"),
|
||||||
|
{
|
||||||
|
CONF_HOST: "velbus",
|
||||||
|
CONF_PORT: 6000,
|
||||||
|
CONF_TLS: True,
|
||||||
|
CONF_PASSWORD: "password",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert result
|
||||||
|
assert result.get("type") is FlowResultType.FORM
|
||||||
|
assert result.get("errors") == {"host": "cannot_connect"}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("controller_connection_failed")
|
||||||
@patch("serial.tools.list_ports.comports", MagicMock(return_value=[com_port()]))
|
@patch("serial.tools.list_ports.comports", MagicMock(return_value=[com_port()]))
|
||||||
async def test_user_usb_succes(hass: HomeAssistant) -> None:
|
async def test_user_usb_connect_failure(hass: HomeAssistant) -> None:
|
||||||
"""Test user usb step."""
|
"""Test user usb step."""
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": SOURCE_USER}
|
DOMAIN, context={"source": SOURCE_USER}
|
||||||
@@ -135,6 +210,36 @@ async def test_user_usb_succes(hass: HomeAssistant) -> None:
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
assert result
|
assert result
|
||||||
|
assert result.get("type") is FlowResultType.FORM
|
||||||
|
assert result.get("errors") == {"port": "cannot_connect"}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("controller")
|
||||||
|
@patch("serial.tools.list_ports.comports", MagicMock(return_value=[com_port()]))
|
||||||
|
async def test_user_usb_success(hass: HomeAssistant) -> None:
|
||||||
|
"""Test user usb step."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_USER}
|
||||||
|
)
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result.get("flow_id"),
|
||||||
|
{"next_step_id": "usbselect"},
|
||||||
|
)
|
||||||
|
assert result["type"] is FlowResultType.FORM
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{
|
||||||
|
CONF_PORT: USB_DEV,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert result
|
||||||
|
assert result.get("type") is FlowResultType.FORM
|
||||||
|
assert result.get("step_id") == "vlp"
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
assert result
|
||||||
assert result.get("type") is FlowResultType.CREATE_ENTRY
|
assert result.get("type") is FlowResultType.CREATE_ENTRY
|
||||||
assert result.get("title") == "Velbus USB"
|
assert result.get("title") == "Velbus USB"
|
||||||
data = result.get("data")
|
data = result.get("data")
|
||||||
@@ -142,6 +247,142 @@ async def test_user_usb_succes(hass: HomeAssistant) -> None:
|
|||||||
assert data[CONF_PORT] == PORT_SERIAL
|
assert data[CONF_PORT] == PORT_SERIAL
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("controller")
|
||||||
|
async def test_vlp_step_no_modules(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_process_uploaded_file: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test VLP step."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_USER}
|
||||||
|
)
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result.get("flow_id"),
|
||||||
|
{"next_step_id": "network"},
|
||||||
|
)
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result.get("flow_id"),
|
||||||
|
{
|
||||||
|
CONF_TLS: False,
|
||||||
|
CONF_HOST: "192.168.88.9",
|
||||||
|
CONF_PORT: 27015,
|
||||||
|
CONF_PASSWORD: "",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert result
|
||||||
|
assert result.get("type") is FlowResultType.FORM
|
||||||
|
assert result.get("step_id") == "vlp"
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.velbus.async_setup_entry",
|
||||||
|
return_value=True,
|
||||||
|
),
|
||||||
|
patch(
|
||||||
|
"velbusaio.vlp_reader.VlpFile.read",
|
||||||
|
AsyncMock(return_value=True),
|
||||||
|
),
|
||||||
|
patch(
|
||||||
|
"velbusaio.vlp_reader.VlpFile.get",
|
||||||
|
return_value=[],
|
||||||
|
),
|
||||||
|
):
|
||||||
|
file_id = mock_process_uploaded_file.file_id
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result.get("flow_id"),
|
||||||
|
{CONF_VLP_FILE: file_id[CONF_VLP_FILE]},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result.get("type") is FlowResultType.FORM
|
||||||
|
assert result.get("errors") == {CONF_VLP_FILE: "no_modules"}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("controller")
|
||||||
|
async def test_vlp_step_success(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_process_uploaded_file: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test VLP step."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_USER}
|
||||||
|
)
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result.get("flow_id"),
|
||||||
|
{"next_step_id": "network"},
|
||||||
|
)
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result.get("flow_id"),
|
||||||
|
{
|
||||||
|
CONF_TLS: False,
|
||||||
|
CONF_HOST: "192.168.88.9",
|
||||||
|
CONF_PORT: 27015,
|
||||||
|
CONF_PASSWORD: "",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert result
|
||||||
|
assert result.get("type") is FlowResultType.FORM
|
||||||
|
assert result.get("step_id") == "vlp"
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"homeassistant.components.velbus.async_setup_entry",
|
||||||
|
return_value=True,
|
||||||
|
) as mock_setup_entry,
|
||||||
|
patch(
|
||||||
|
"velbusaio.vlp_reader.VlpFile.read",
|
||||||
|
AsyncMock(return_value=True),
|
||||||
|
),
|
||||||
|
patch(
|
||||||
|
"velbusaio.vlp_reader.VlpFile.get",
|
||||||
|
return_value=[1, 2, 3, 4],
|
||||||
|
),
|
||||||
|
):
|
||||||
|
file_id = mock_process_uploaded_file.file_id
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result.get("flow_id"),
|
||||||
|
{CONF_VLP_FILE: file_id[CONF_VLP_FILE]},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result.get("type") is FlowResultType.CREATE_ENTRY
|
||||||
|
assert len(mock_setup_entry.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("controller")
|
||||||
|
async def test_reconfigure_step(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_process_uploaded_file: MagicMock,
|
||||||
|
config_entry: MockConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Testcase for the reconfigure step."""
|
||||||
|
await init_integration(hass, config_entry)
|
||||||
|
result = await config_entry.start_reconfigure_flow(hass)
|
||||||
|
assert result
|
||||||
|
assert result.get("type") is FlowResultType.FORM
|
||||||
|
assert result.get("step_id") == "vlp"
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
"velbusaio.vlp_reader.VlpFile.read",
|
||||||
|
AsyncMock(return_value=True),
|
||||||
|
),
|
||||||
|
patch(
|
||||||
|
"velbusaio.vlp_reader.VlpFile.get",
|
||||||
|
return_value=[1, 2, 3, 4],
|
||||||
|
),
|
||||||
|
):
|
||||||
|
file_id = mock_process_uploaded_file.file_id
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result.get("flow_id"),
|
||||||
|
{CONF_VLP_FILE: file_id[CONF_VLP_FILE]},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert result.get("type") is FlowResultType.ABORT
|
||||||
|
assert result["reason"] == "reconfigure_successful"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("controller")
|
@pytest.mark.usefixtures("controller")
|
||||||
async def test_network_abort_if_already_setup(hass: HomeAssistant) -> None:
|
async def test_network_abort_if_already_setup(hass: HomeAssistant) -> None:
|
||||||
"""Test we abort if Velbus is already setup."""
|
"""Test we abort if Velbus is already setup."""
|
||||||
@@ -158,7 +399,7 @@ async def test_network_abort_if_already_setup(hass: HomeAssistant) -> None:
|
|||||||
{"next_step_id": "network"},
|
{"next_step_id": "network"},
|
||||||
)
|
)
|
||||||
result = await hass.config_entries.flow.async_configure(
|
result = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result.get("flow_id"),
|
||||||
{
|
{
|
||||||
CONF_TLS: False,
|
CONF_TLS: False,
|
||||||
CONF_HOST: "127.0.0.1",
|
CONF_HOST: "127.0.0.1",
|
||||||
|
|||||||
@@ -17,7 +17,18 @@ from tests.common import MockConfigEntry
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures("mock_setup_entry", "mock_wled")
|
@pytest.mark.usefixtures("mock_setup_entry", "mock_wled")
|
||||||
async def test_full_user_flow_implementation(hass: HomeAssistant) -> None:
|
@pytest.mark.parametrize(
|
||||||
|
"host_input",
|
||||||
|
[
|
||||||
|
"192.168.1.123",
|
||||||
|
"http://192.168.1.123",
|
||||||
|
"https://192.168.1.123/settings",
|
||||||
|
"https://192.168.1.123:80/settings",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_full_user_flow_implementation(
|
||||||
|
hass: HomeAssistant, host_input: str
|
||||||
|
) -> None:
|
||||||
"""Test the full manual user flow from start to finish."""
|
"""Test the full manual user flow from start to finish."""
|
||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@@ -28,7 +39,7 @@ async def test_full_user_flow_implementation(hass: HomeAssistant) -> None:
|
|||||||
assert result.get("type") is FlowResultType.FORM
|
assert result.get("type") is FlowResultType.FORM
|
||||||
|
|
||||||
result = await hass.config_entries.flow.async_configure(
|
result = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"], user_input={CONF_HOST: "192.168.1.123"}
|
result["flow_id"], user_input={CONF_HOST: host_input}
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result.get("title") == "WLED RGB Light"
|
assert result.get("title") == "WLED RGB Light"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""Test the xbox config flow."""
|
"""Test the xbox config flow."""
|
||||||
|
|
||||||
from http import HTTPStatus
|
from http import HTTPStatus
|
||||||
|
from typing import Any
|
||||||
from unittest.mock import AsyncMock, patch
|
from unittest.mock import AsyncMock, patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -10,6 +11,8 @@ from homeassistant.components.xbox.const import DOMAIN, OAUTH2_AUTHORIZE, OAUTH2
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.data_entry_flow import FlowResultType
|
from homeassistant.data_entry_flow import FlowResultType
|
||||||
from homeassistant.helpers import config_entry_oauth2_flow
|
from homeassistant.helpers import config_entry_oauth2_flow
|
||||||
|
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
|
||||||
|
from homeassistant.helpers.service_info.ssdp import SsdpServiceInfo
|
||||||
|
|
||||||
from .conftest import CLIENT_ID
|
from .conftest import CLIENT_ID
|
||||||
|
|
||||||
@@ -79,6 +82,99 @@ async def test_full_flow(
|
|||||||
assert len(mock_setup.mock_calls) == 1
|
assert len(mock_setup.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("source", "service_info"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
config_entries.SOURCE_DHCP,
|
||||||
|
DhcpServiceInfo(
|
||||||
|
hostname="xboxone",
|
||||||
|
ip="192.168.0.1",
|
||||||
|
macaddress="aaaaaaaaaaaa",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
config_entries.SOURCE_SSDP,
|
||||||
|
SsdpServiceInfo(
|
||||||
|
ssdp_usn="mock_usn",
|
||||||
|
ssdp_st="mock_st",
|
||||||
|
upnp={"manufacturer": "Microsoft Corporation", "modelName": "Xbox One"},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
@pytest.mark.usefixtures(
|
||||||
|
"current_request_with_host",
|
||||||
|
"xbox_live_client",
|
||||||
|
"authentication_manager",
|
||||||
|
)
|
||||||
|
async def test_discovery(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
hass_client_no_auth: ClientSessionGenerator,
|
||||||
|
aioclient_mock: AiohttpClientMocker,
|
||||||
|
source: str,
|
||||||
|
service_info: Any,
|
||||||
|
) -> None:
|
||||||
|
"""Check DHCP/SSDP discovery."""
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
"xbox", context={"source": source}, data=service_info
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.FORM
|
||||||
|
assert result["step_id"] == "oauth_discovery"
|
||||||
|
assert not result["errors"]
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
state = config_entry_oauth2_flow._encode_jwt(
|
||||||
|
hass,
|
||||||
|
{
|
||||||
|
"flow_id": result["flow_id"],
|
||||||
|
"redirect_uri": "https://example.com/auth/external/callback",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] is FlowResultType.EXTERNAL_STEP
|
||||||
|
|
||||||
|
assert result["url"] == (
|
||||||
|
f"{OAUTH2_AUTHORIZE}?response_type=code&client_id={CLIENT_ID}"
|
||||||
|
"&redirect_uri=https://example.com/auth/external/callback"
|
||||||
|
f"&state={state}&scope=Xboxlive.signin+Xboxlive.offline_access"
|
||||||
|
)
|
||||||
|
|
||||||
|
client = await hass_client_no_auth()
|
||||||
|
resp = await client.get(f"/auth/external/callback?code=abcd&state={state}")
|
||||||
|
assert resp.status == HTTPStatus.OK
|
||||||
|
assert resp.headers["content-type"] == "text/html; charset=utf-8"
|
||||||
|
|
||||||
|
aioclient_mock.post(
|
||||||
|
OAUTH2_TOKEN,
|
||||||
|
json={
|
||||||
|
"refresh_token": "mock-refresh-token",
|
||||||
|
"access_token": "mock-access-token",
|
||||||
|
"type": "Bearer",
|
||||||
|
"expires_in": 60,
|
||||||
|
"scope": "XboxLive.signin XboxLive.offline_access",
|
||||||
|
"service": "xbox",
|
||||||
|
"token_type": "bearer",
|
||||||
|
"user_id": "AAAAAAAAAAAAAAAAAAAAA",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.xbox.async_setup_entry", return_value=True
|
||||||
|
) as mock_setup:
|
||||||
|
result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||||
|
|
||||||
|
assert result["result"].unique_id == "271958441785640"
|
||||||
|
assert result["result"].title == "GSR Ae"
|
||||||
|
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
||||||
|
assert len(mock_setup.mock_calls) == 1
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.usefixtures(
|
@pytest.mark.usefixtures(
|
||||||
"current_request_with_host",
|
"current_request_with_host",
|
||||||
"xbox_live_client",
|
"xbox_live_client",
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user