Compare commits

..

5 Commits

Author SHA1 Message Date
abmantis
196a42a44d Fix stale docstrings 2025-12-11 20:05:02 +00:00
abmantis
4cc46ecea4 Fix module docstring 2025-12-11 20:04:49 +00:00
abmantis
1a0791b59a Simplify condition 2025-12-11 19:59:11 +00:00
abmantis
3d23f43461 Add button pressed trigger 2025-12-11 19:52:12 +00:00
abmantis
2b3de7fa2b Move Trigger from_state validation to is_valid_transition 2025-12-11 19:50:12 +00:00
117 changed files with 959 additions and 4417 deletions

View File

@@ -263,7 +263,7 @@ jobs:
check-latest: true
- name: Restore base Python virtual environment
id: cache-venv
uses: &actions-cache actions/cache@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.0
uses: &actions-cache actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: venv
key: &key-pre-commit-venv >-
@@ -304,7 +304,7 @@ jobs:
- &cache-restore-pre-commit-venv
name: Restore base Python virtual environment
id: cache-venv
uses: &actions-cache-restore actions/cache/restore@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.0
uses: &actions-cache-restore actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: venv
fail-on-cache-miss: true
@@ -511,7 +511,7 @@ jobs:
fi
- name: Save apt cache
if: steps.cache-apt-check.outputs.cache-hit != 'true'
uses: &actions-cache-save actions/cache/save@a7833574556fa59680c1b7cb190c1735db73ebf0 # v5.0.0
uses: &actions-cache-save actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: *path-apt-cache
key: *key-apt-cache

View File

@@ -10,7 +10,6 @@
}
],
"documentation": "https://www.home-assistant.io/integrations/actron_air",
"integration_type": "hub",
"iot_class": "cloud_polling",
"quality_scale": "bronze",
"requirements": ["actron-neo-api==0.1.87"]

View File

@@ -4,7 +4,6 @@
"codeowners": ["@elupus"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/arcam_fmj",
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["arcam"],
"requirements": ["arcam-fmj==1.8.2"],

View File

@@ -27,7 +27,6 @@
}
],
"documentation": "https://www.home-assistant.io/integrations/august",
"integration_type": "hub",
"iot_class": "cloud_push",
"loggers": ["pubnub", "yalexs"],
"requirements": ["yalexs==9.2.0", "yalexs-ble==3.2.2"]

View File

@@ -6,7 +6,10 @@ rules:
This integration does not provide additional actions.
appropriate-polling: done
brands: done
common-modules: done
common-modules:
status: todo
comment: |
The entity.py file is not used in this integration.
config-flow-test-coverage: done
config-flow: done
dependency-transparency: done

View File

@@ -204,25 +204,13 @@ async def async_setup_entry(
async_add_entities(entities)
class AutarcoSensorBase(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):
class AutarcoBatterySensorEntity(
CoordinatorEntity[AutarcoDataUpdateCoordinator], SensorEntity
):
"""Defines an Autarco battery sensor."""
entity_description: AutarcoBatterySensorEntityDescription
_attr_has_entity_name = True
def __init__(
self,
@@ -230,8 +218,10 @@ class AutarcoBatterySensorEntity(AutarcoSensorBase):
coordinator: AutarcoDataUpdateCoordinator,
description: AutarcoBatterySensorEntityDescription,
) -> None:
"""Initialize Autarco battery sensor."""
super().__init__(coordinator, description)
"""Initialize Autarco sensor."""
super().__init__(coordinator)
self.entity_description = description
self._attr_unique_id = (
f"{coordinator.account_site.site_id}_battery_{description.key}"
)
@@ -249,10 +239,13 @@ class AutarcoBatterySensorEntity(AutarcoSensorBase):
return self.entity_description.value_fn(self.coordinator.data.battery)
class AutarcoSolarSensorEntity(AutarcoSensorBase):
class AutarcoSolarSensorEntity(
CoordinatorEntity[AutarcoDataUpdateCoordinator], SensorEntity
):
"""Defines an Autarco solar sensor."""
entity_description: AutarcoSolarSensorEntityDescription
_attr_has_entity_name = True
def __init__(
self,
@@ -260,8 +253,10 @@ class AutarcoSolarSensorEntity(AutarcoSensorBase):
coordinator: AutarcoDataUpdateCoordinator,
description: AutarcoSolarSensorEntityDescription,
) -> None:
"""Initialize Autarco solar sensor."""
super().__init__(coordinator, description)
"""Initialize Autarco sensor."""
super().__init__(coordinator)
self.entity_description = description
self._attr_unique_id = (
f"{coordinator.account_site.site_id}_solar_{description.key}"
)
@@ -278,10 +273,13 @@ class AutarcoSolarSensorEntity(AutarcoSensorBase):
return self.entity_description.value_fn(self.coordinator.data.solar)
class AutarcoInverterSensorEntity(AutarcoSensorBase):
class AutarcoInverterSensorEntity(
CoordinatorEntity[AutarcoDataUpdateCoordinator], SensorEntity
):
"""Defines an Autarco inverter sensor."""
entity_description: AutarcoInverterSensorEntityDescription
_attr_has_entity_name = True
def __init__(
self,
@@ -290,8 +288,10 @@ class AutarcoInverterSensorEntity(AutarcoSensorBase):
description: AutarcoInverterSensorEntityDescription,
serial_number: str,
) -> None:
"""Initialize Autarco inverter sensor."""
super().__init__(coordinator, description)
"""Initialize Autarco sensor."""
super().__init__(coordinator)
self.entity_description = description
self._serial_number = serial_number
self._attr_unique_id = f"{serial_number}_{description.key}"
self._attr_device_info = DeviceInfo(

View File

@@ -125,13 +125,13 @@ _EXPERIMENTAL_TRIGGER_PLATFORMS = {
"alarm_control_panel",
"assist_satellite",
"binary_sensor",
"button",
"climate",
"cover",
"fan",
"lawn_mower",
"light",
"media_player",
"switch",
"text",
"vacuum",
}

View File

@@ -4,7 +4,6 @@
"codeowners": ["@bdraco", "@jfroy"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/baf",
"integration_type": "device",
"iot_class": "local_push",
"requirements": ["aiobafi6==0.9.0"],
"zeroconf": [

View File

@@ -22,7 +22,6 @@ class BeoSource:
NET_RADIO: Final[Source] = Source(name="B&O Radio", id="netRadio")
SPDIF: Final[Source] = Source(name="Optical", id="spdif")
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")
URI_STREAMER: Final[Source] = Source(name="Audio Streamer", id="uriStreamer")
@@ -56,13 +55,12 @@ BEO_REPEAT_TO_HA: dict[str, RepeatMode] = {
class BeoMediaType(StrEnum):
"""Bang & Olufsen specific media types."""
DEEZER = "deezer"
FAVOURITE = "favourite"
OVERLAY_TTS = "overlay_tts"
DEEZER = "deezer"
RADIO = "radio"
TIDAL = "tidal"
TTS = "provider"
TV = "tv"
OVERLAY_TTS = "overlay_tts"
class BeoModel(StrEnum):

View File

@@ -218,7 +218,6 @@ class BeoMediaPlayer(BeoEntity, MediaPlayerEntity):
self._sources: dict[str, str] = {}
self._state: str = MediaPlayerState.IDLE
self._video_sources: dict[str, str] = {}
self._video_source_id_map: dict[str, str] = {}
self._sound_modes: dict[str, int] = {}
# Beolink compatible sources
@@ -356,9 +355,6 @@ class BeoMediaPlayer(BeoEntity, MediaPlayerEntity):
and menu_item.label != "TV"
):
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
self._sources = self._audio_sources | self._video_sources
@@ -631,11 +627,10 @@ class BeoMediaPlayer(BeoEntity, MediaPlayerEntity):
def media_content_type(self) -> MediaType | str | None:
"""Return the current media type."""
content_type = {
BeoSource.DEEZER.id: BeoMediaType.DEEZER,
BeoSource.NET_RADIO.id: BeoMediaType.RADIO,
BeoSource.TIDAL.id: BeoMediaType.TIDAL,
BeoSource.TV.id: BeoMediaType.TV,
BeoSource.URI_STREAMER.id: MediaType.URL,
BeoSource.DEEZER.id: BeoMediaType.DEEZER,
BeoSource.TIDAL.id: BeoMediaType.TIDAL,
BeoSource.NET_RADIO.id: BeoMediaType.RADIO,
}
# Hard to determine content type.
if self._source_change.id in content_type:
@@ -695,11 +690,7 @@ class BeoMediaPlayer(BeoEntity, MediaPlayerEntity):
@property
def source(self) -> str | None:
"""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 the current audio source."""
return self._source_change.name
@property

View File

@@ -64,12 +64,6 @@ async def async_migrate_entry(hass: HomeAssistant, entry: BlinkConfigEntry) -> b
if entry.version == 2:
await _reauth_flow_wrapper(hass, entry, data)
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

View File

@@ -21,7 +21,7 @@ from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN, HARDWARE_ID
from .const import DEVICE_ID, DOMAIN
_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):
"""Handle a Blink config flow."""
VERSION = 4
VERSION = 3
def __init__(self) -> None:
"""Initialize the blink flow."""
@@ -53,7 +53,7 @@ class BlinkConfigFlow(ConfigFlow, domain=DOMAIN):
async def _handle_user_input(self, user_input: dict[str, Any]):
"""Handle user input."""
self.auth = Auth(
{**user_input, "hardware_id": HARDWARE_ID},
{**user_input, "device_id": DEVICE_ID},
no_prompt=True,
session=async_get_clientsession(self.hass),
)

View File

@@ -3,7 +3,7 @@
from homeassistant.const import Platform
DOMAIN = "blink"
HARDWARE_ID = "Home Assistant"
DEVICE_ID = "Home Assistant"
CONF_MIGRATE = "migrate"
CONF_CAMERA = "camera"

View File

@@ -13,25 +13,32 @@ from bluecurrent_api.exceptions import (
RequestLimitReached,
WebsocketError,
)
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_TOKEN, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
from homeassistant.const import CONF_API_TOKEN, CONF_DEVICE_ID, Platform
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import (
ConfigEntryAuthFailed,
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.typing import ConfigType
from .const import (
BCU_APP,
CHARGEPOINT_SETTINGS,
CHARGEPOINT_STATUS,
CHARGING_CARD_ID,
DOMAIN,
EVSE_ID,
LOGGER,
PLUG_AND_CHARGE,
SERVICE_START_CHARGE_SESSION,
VALUE,
)
from .services import async_setup_services
type BlueCurrentConfigEntry = ConfigEntry[Connector]
@@ -47,12 +54,13 @@ VALUE_TYPES = [CHARGEPOINT_STATUS, CHARGEPOINT_SETTINGS]
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up Blue Current."""
async_setup_services(hass)
return True
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 async_setup_entry(
@@ -80,6 +88,66 @@ async def async_setup_entry(
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(
hass: HomeAssistant, config_entry: BlueCurrentConfigEntry
) -> bool:

View File

@@ -1,79 +0,0 @@
"""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,
)

View File

@@ -11,7 +11,6 @@
"config_flow": true,
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/bluemaestro",
"integration_type": "device",
"iot_class": "local_push",
"requirements": ["bluemaestro-ble==0.4.1"]
}

View File

@@ -14,7 +14,6 @@
}
],
"documentation": "https://www.home-assistant.io/integrations/bond",
"integration_type": "hub",
"iot_class": "local_push",
"loggers": ["bond_async"],
"requirements": ["bond-async==0.2.1"],

View File

@@ -17,5 +17,10 @@
"press": {
"service": "mdi:gesture-tap-button"
}
},
"triggers": {
"pressed": {
"trigger": "mdi:gesture-tap-button"
}
}
}

View File

@@ -27,5 +27,11 @@
"name": "Press"
}
},
"title": "Button"
"title": "Button",
"triggers": {
"pressed": {
"description": "Triggers after one or several buttons were pressed.",
"name": "Button pressed"
}
}
}

View File

@@ -0,0 +1,42 @@
"""Provides triggers for buttons."""
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN
from homeassistant.core import HomeAssistant, State
from homeassistant.helpers.trigger import (
ENTITY_STATE_TRIGGER_SCHEMA,
EntityTriggerBase,
Trigger,
)
from . import DOMAIN
class ButtonPressedTrigger(EntityTriggerBase):
"""Trigger for button entity presses."""
_domain = DOMAIN
_schema = ENTITY_STATE_TRIGGER_SCHEMA
def is_valid_transition(self, from_state: State, to_state: State) -> bool:
"""Check if the origin state is not an expected target states."""
# UNKNOWN is a valid from_state, otherwise the first time the button is pressed
# would not trigger
if from_state.state == STATE_UNAVAILABLE:
return False
return from_state.state != to_state.state
def is_valid_state(self, state: State) -> bool:
"""Check if the new state is not invalid."""
return state.state not in (STATE_UNAVAILABLE, STATE_UNKNOWN)
TRIGGERS: dict[str, type[Trigger]] = {
"pressed": ButtonPressedTrigger,
}
async def async_get_triggers(hass: HomeAssistant) -> dict[str, type[Trigger]]:
"""Return the triggers for buttons."""
return TRIGGERS

View File

@@ -0,0 +1,4 @@
pressed:
target:
entity:
domain: button

View File

@@ -4,7 +4,6 @@
"codeowners": ["@ol-iver", "@starkillerOG"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/denonavr",
"integration_type": "device",
"iot_class": "local_push",
"loggers": ["denonavr"],
"requirements": ["denonavr==1.2.0"],

View File

@@ -8,5 +8,5 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["googleapiclient"],
"requirements": ["gcal-sync==8.0.0", "oauth2client==4.1.3", "ical==12.1.1"]
"requirements": ["gcal-sync==8.0.0", "oauth2client==4.1.3", "ical==11.1.0"]
}

View File

@@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"loggers": ["google_air_quality_api"],
"quality_scale": "bronze",
"requirements": ["google_air_quality_api==2.0.2"]
"requirements": ["google_air_quality_api==2.0.0"]
}

View File

@@ -88,16 +88,16 @@
"1b_good_air_quality": "1B - Good air quality",
"2_cyan": "2 - Cyan",
"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",
"2b_acceptable_air_quality": "2B - Acceptable air quality",
"3_green": "3 - Green",
"3_yellow": "3 - Yellow",
"3a_aggravated_air_quality": "3A - Aggravated air quality",
"3b_bad_air_quality": "3B - Bad air quality",
"4_orange": "4 - Orange",
"4_yellow_watch": "4 - Yellow/Watch",
"5_orange_alert": "5 - Orange/Alert",
"5_red": "5 - Red",
"6_red_alert": "6 - Red/Alert+",
"10_33": "10-33% of guideline",
"33_66": "33-66% of guideline",

View File

@@ -27,7 +27,6 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="eBatChargeToday",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
GrowattSensorEntityDescription(
key="mix_battery_charge_lifetime",
@@ -43,7 +42,6 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="eBatDisChargeToday",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
GrowattSensorEntityDescription(
key="mix_battery_discharge_lifetime",
@@ -59,7 +57,6 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="epvToday",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
GrowattSensorEntityDescription(
key="mix_solar_generation_lifetime",
@@ -75,7 +72,6 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="pDischarge1",
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
GrowattSensorEntityDescription(
key="mix_battery_voltage",
@@ -105,7 +101,6 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="elocalLoadToday",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
GrowattSensorEntityDescription(
key="mix_load_consumption_lifetime",
@@ -121,7 +116,6 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="etoGridToday",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
GrowattSensorEntityDescription(
key="mix_export_to_grid_lifetime",
@@ -138,7 +132,6 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="chargePower",
native_unit_of_measurement=UnitOfPower.KILO_WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
GrowattSensorEntityDescription(
key="mix_load_consumption",
@@ -146,7 +139,6 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="pLocalLoad",
native_unit_of_measurement=UnitOfPower.KILO_WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
GrowattSensorEntityDescription(
key="mix_wattage_pv_1",
@@ -154,7 +146,6 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="pPv1",
native_unit_of_measurement=UnitOfPower.KILO_WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
GrowattSensorEntityDescription(
key="mix_wattage_pv_2",
@@ -162,7 +153,6 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="pPv2",
native_unit_of_measurement=UnitOfPower.KILO_WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
GrowattSensorEntityDescription(
key="mix_wattage_pv_all",
@@ -170,7 +160,6 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="ppv",
native_unit_of_measurement=UnitOfPower.KILO_WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
GrowattSensorEntityDescription(
key="mix_export_to_grid",
@@ -178,7 +167,6 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="pactogrid",
native_unit_of_measurement=UnitOfPower.KILO_WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
GrowattSensorEntityDescription(
key="mix_import_from_grid",
@@ -186,7 +174,6 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="pactouser",
native_unit_of_measurement=UnitOfPower.KILO_WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
GrowattSensorEntityDescription(
key="mix_battery_discharge_kw",
@@ -194,7 +181,6 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="pdisCharge1",
native_unit_of_measurement=UnitOfPower.KILO_WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
GrowattSensorEntityDescription(
key="mix_grid_voltage",
@@ -210,7 +196,6 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="eCharge",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
GrowattSensorEntityDescription(
key="mix_load_consumption_solar_today",
@@ -218,7 +203,6 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="eChargeToday",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
GrowattSensorEntityDescription(
key="mix_self_consumption_today",
@@ -226,7 +210,6 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="eChargeToday1",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
GrowattSensorEntityDescription(
key="mix_load_consumption_battery_today",
@@ -234,7 +217,6 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="echarge1",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
GrowattSensorEntityDescription(
key="mix_import_from_grid_today",
@@ -242,7 +224,6 @@ MIX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="etouser",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
# This sensor is manually created using the most recent X-Axis value from the chartData
GrowattSensorEntityDescription(

View File

@@ -79,7 +79,6 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="ppv1",
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
precision=1,
),
GrowattSensorEntityDescription(
@@ -123,7 +122,6 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="ppv2",
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
precision=1,
),
GrowattSensorEntityDescription(
@@ -167,7 +165,6 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="ppv3",
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
precision=1,
),
GrowattSensorEntityDescription(
@@ -211,7 +208,6 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="ppv4",
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
precision=1,
),
GrowattSensorEntityDescription(
@@ -238,7 +234,6 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="ppv",
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
precision=1,
),
GrowattSensorEntityDescription(
@@ -263,7 +258,6 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="pac",
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
precision=1,
),
GrowattSensorEntityDescription(
@@ -329,7 +323,6 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="bdc1DischargePower",
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
GrowattSensorEntityDescription(
key="tlx_battery_1_discharge_total",
@@ -346,7 +339,6 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="bdc2DischargePower",
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
GrowattSensorEntityDescription(
key="tlx_battery_2_discharge_total",
@@ -380,7 +372,6 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="bdc1ChargePower",
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
GrowattSensorEntityDescription(
key="tlx_battery_1_charge_total",
@@ -397,7 +388,6 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="bdc2ChargePower",
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
GrowattSensorEntityDescription(
key="tlx_battery_2_charge_total",
@@ -455,7 +445,6 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="pacToLocalLoad",
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
precision=1,
),
GrowattSensorEntityDescription(
@@ -464,7 +453,6 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="pacToUserTotal",
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
precision=1,
),
GrowattSensorEntityDescription(
@@ -473,7 +461,6 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="pacToGridTotal",
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
precision=1,
),
GrowattSensorEntityDescription(
@@ -558,7 +545,6 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="psystem",
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
precision=1,
),
GrowattSensorEntityDescription(
@@ -567,7 +553,6 @@ TLX_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="pself",
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
precision=1,
),
)

View File

@@ -50,6 +50,5 @@ TOTAL_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="nominalPower",
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
)

View File

@@ -37,5 +37,5 @@
"iot_class": "cloud_push",
"loggers": ["pylamarzocco"],
"quality_scale": "platinum",
"requirements": ["pylamarzocco==2.2.4"]
"requirements": ["pylamarzocco==2.2.3"]
}

View File

@@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/local_calendar",
"iot_class": "local_polling",
"loggers": ["ical"],
"requirements": ["ical==12.1.1"]
"requirements": ["ical==11.1.0"]
}

View File

@@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/local_todo",
"iot_class": "local_polling",
"requirements": ["ical==12.1.1"]
"requirements": ["ical==11.1.0"]
}

View File

@@ -499,53 +499,4 @@ DISCOVERY_SCHEMAS = [
entity_class=MatterBinarySensor,
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,
),
]

View File

@@ -183,48 +183,6 @@ class MatterModeSelectEntity(MatterAttributeSelectEntity):
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):
"""Representation of a select entity from Matter list and selected item Cluster attribute(s)."""
@@ -636,18 +594,15 @@ DISCOVERY_SCHEMAS = [
),
MatterDiscoverySchema(
platform=Platform.SELECT,
entity_description=MatterMapSelectEntityDescription(
entity_description=MatterSelectEntityDescription(
key="DoorLockOperatingMode",
entity_category=EntityCategory.CONFIG,
translation_key="door_lock_operating_mode",
list_attribute=clusters.DoorLock.Attributes.SupportedOperatingModes,
options=list(DOOR_LOCK_OPERATING_MODE_MAP.values()),
device_to_ha=DOOR_LOCK_OPERATING_MODE_MAP.get,
ha_to_device=DOOR_LOCK_OPERATING_MODE_MAP_REVERSE.get,
),
entity_class=MatterDoorLockOperatingModeSelectEntity,
required_attributes=(
clusters.DoorLock.Attributes.OperatingMode,
clusters.DoorLock.Attributes.SupportedOperatingModes,
),
entity_class=MatterAttributeSelectEntity,
required_attributes=(clusters.DoorLock.Attributes.OperatingMode,),
),
]

View File

@@ -89,15 +89,6 @@
"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": {
"name": "Valve blocked"
},

View File

@@ -46,7 +46,7 @@
"ws_path": "WebSocket path"
},
"data_description": {
"advanced_options": "Enable and select **Submit** to set advanced options.",
"advanced_options": "Enable and select **Next** to set advanced options.",
"broker": "The hostname or IP address of your MQTT broker.",
"certificate": "The custom CA certificate file to validate your MQTT brokers certificate.",
"client_cert": "The client certificate to authenticate against your MQTT broker.",

View File

@@ -1,122 +0,0 @@
"""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

View File

@@ -27,8 +27,6 @@ from .const import (
DATA_CAMERAS,
DATA_EVENTS,
DOMAIN,
EVENT_TYPE_CONNECTION,
EVENT_TYPE_DISCONNECTION,
EVENT_TYPE_LIGHT_MODE,
EVENT_TYPE_OFF,
EVENT_TYPE_ON,
@@ -125,13 +123,7 @@ class NetatmoCamera(NetatmoModuleEntity, Camera):
"""Entity created."""
await super().async_added_to_hass()
for event_type in (
EVENT_TYPE_LIGHT_MODE,
EVENT_TYPE_OFF,
EVENT_TYPE_ON,
EVENT_TYPE_CONNECTION,
EVENT_TYPE_DISCONNECTION,
):
for event_type in (EVENT_TYPE_LIGHT_MODE, EVENT_TYPE_OFF, EVENT_TYPE_ON):
self.async_on_remove(
async_dispatcher_connect(
self.hass,
@@ -154,19 +146,12 @@ class NetatmoCamera(NetatmoModuleEntity, Camera):
data["home_id"] == self.home.entity_id
and data["camera_id"] == self.device.entity_id
):
if data[WEBHOOK_PUSH_TYPE] in (
"NACamera-off",
"NOCamera-off",
"NACamera-disconnection",
"NOCamera-disconnection",
):
if data[WEBHOOK_PUSH_TYPE] in ("NACamera-off", "NACamera-disconnection"):
self._attr_is_streaming = False
self._monitoring = False
elif data[WEBHOOK_PUSH_TYPE] in (
"NACamera-on",
"NOCamera-on",
WEBHOOK_NACAMERA_CONNECTION,
"NOCamera-connection",
):
self._attr_is_streaming = True
self._monitoring = True

View File

@@ -127,9 +127,6 @@ EVENT_TYPE_ALARM_STARTED = "alarm_started"
EVENT_TYPE_DOOR_TAG_BIG_MOVE = "tag_big_move"
EVENT_TYPE_DOOR_TAG_OPEN = "tag_open"
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_ON = "on"

View File

@@ -7,5 +7,5 @@
"iot_class": "cloud_polling",
"loggers": ["pynintendoauth", "pynintendoparental"],
"quality_scale": "bronze",
"requirements": ["pynintendoauth==1.0.0", "pynintendoparental==2.1.1"]
"requirements": ["pynintendoauth==1.0.0", "pynintendoparental==2.1.0"]
}

View File

@@ -9,5 +9,5 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"quality_scale": "bronze",
"requirements": ["openai==2.11.0", "python-open-router==0.3.3"]
"requirements": ["openai==2.9.0", "python-open-router==0.3.3"]
}

View File

@@ -8,5 +8,5 @@
"documentation": "https://www.home-assistant.io/integrations/openai_conversation",
"integration_type": "service",
"iot_class": "cloud_polling",
"requirements": ["openai==2.11.0"]
"requirements": ["openai==2.9.0"]
}

View File

@@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"loggers": ["ical"],
"quality_scale": "silver",
"requirements": ["ical==12.1.1"]
"requirements": ["ical==11.1.0"]
}

View File

@@ -394,14 +394,7 @@ class RoborockWashingMachineUpdateCoordinator(
async def _async_update_data(
self,
) -> dict[RoborockZeoProtocol, StateType]:
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
return await self.api.query_values(self.request_protocols)
class RoborockWetDryVacUpdateCoordinator(
@@ -432,11 +425,4 @@ class RoborockWetDryVacUpdateCoordinator(
async def _async_update_data(
self,
) -> dict[RoborockDyadDataProtocol, StateType]:
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
return await self.api.query_values(self.request_protocols)

View File

@@ -63,7 +63,6 @@ from .repairs import (
async_manage_open_wifi_ap_issue,
async_manage_outbound_websocket_incorrectly_enabled_issue,
)
from .services import async_setup_services
from .utils import (
async_create_issue_unsupported_firmware,
async_migrate_rpc_virtual_components_unique_ids,
@@ -118,8 +117,6 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
if (conf := config.get(DOMAIN)) is not None:
hass.data[DOMAIN] = {CONF_COAP_PORT: conf[CONF_COAP_PORT]}
async_setup_services(hass)
return True

View File

@@ -343,6 +343,3 @@ MODEL_FRANKEVER_IRRIGATION_CONTROLLER = "Irrigation"
ROLE_GENERIC = "generic"
TRV_CHANNEL = 0
ATTR_KEY = "key"
ATTR_VALUE = "value"

View File

@@ -105,13 +105,5 @@
}
}
}
},
"services": {
"get_kvs_value": {
"service": "mdi:import"
},
"set_kvs_value": {
"service": "mdi:export"
}
}
}

View File

@@ -1,13 +1,17 @@
rules:
# Bronze
action-setup: done
action-setup:
status: exempt
comment: The integration does not register services.
appropriate-polling: done
brands: done
common-modules: done
config-flow-test-coverage: done
config-flow: done
dependency-transparency: done
docs-actions: done
docs-actions:
status: exempt
comment: The integration does not register services.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
@@ -20,7 +24,9 @@ rules:
unique-config-entry: done
# Silver
action-exceptions: done
action-exceptions:
status: exempt
comment: The integration does not register services.
config-entry-unloading: done
docs-configuration-parameters: done
docs-installation-parameters: done

View File

@@ -1,170 +0,0 @@
"""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,
)

View File

@@ -1,27 +0,0 @@
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:

View File

@@ -603,9 +603,6 @@
"auth_error": {
"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": {
"message": "Device communication error occurred while calling action for {entity} of {device}"
},
@@ -615,24 +612,12 @@
"device_not_found": {
"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": {
"message": "{device} is running an unsupported firmware, please update the firmware"
},
"invalid_device_id": {
"message": "Invalid device ID specified: {device_id}"
},
"invalid_trigger": {
"message": "Invalid device automation trigger (type, subtype): {trigger}"
},
"kvs_not_supported": {
"message": "{device} does not support KVS"
},
"ota_update_connection_error": {
"message": "Device communication error occurred while triggering OTA update for {device}"
},
@@ -642,9 +627,6 @@
"rpc_call_action_error": {
"message": "RPC call error occurred while calling action for {entity} of {device}"
},
"rpc_call_error": {
"message": "RPC call error occurred for {device}"
},
"update_error": {
"message": "An error occurred while retrieving data from {device}"
},
@@ -766,39 +748,5 @@
"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"
}
}
}

View File

@@ -31,5 +31,5 @@
"iot_class": "cloud_push",
"loggers": ["pysmartthings"],
"quality_scale": "bronze",
"requirements": ["pysmartthings==3.5.1"]
"requirements": ["pysmartthings==3.5.0"]
}

View File

@@ -155,12 +155,9 @@ class SqueezeboxConfigFlow(ConfigFlow, domain=DOMAIN):
_LOGGER.exception("Unknown exception while validating connection")
return "unknown"
if "uuid" not in status:
_LOGGER.exception("Discovered server did not provide a uuid")
return "missing_uuid"
await self.async_set_unique_id(status["uuid"])
self._abort_if_unique_id_configured()
if "uuid" in status:
await self.async_set_unique_id(status["uuid"])
self._abort_if_unique_id_configured()
return None

View File

@@ -7,7 +7,6 @@
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"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.",
"unknown": "[%key:common::config_flow::error::unknown%]"
},

View File

@@ -29,13 +29,5 @@
"turn_on": {
"service": "mdi:toggle-switch-variant"
}
},
"triggers": {
"turned_off": {
"trigger": "mdi:toggle-switch-variant-off"
},
"turned_on": {
"trigger": "mdi:toggle-switch-variant"
}
}
}

View File

@@ -1,8 +1,4 @@
{
"common": {
"trigger_behavior_description": "The behavior of the targeted switches to trigger on.",
"trigger_behavior_name": "Behavior"
},
"device_automation": {
"action_type": {
"toggle": "[%key:common::device_automation::action_type::toggle%]",
@@ -45,15 +41,6 @@
}
}
},
"selector": {
"trigger_behavior": {
"options": {
"any": "Any",
"first": "First",
"last": "Last"
}
}
},
"services": {
"toggle": {
"description": "Toggles a switch on/off.",
@@ -68,27 +55,5 @@
"name": "[%key:common::action::turn_on%]"
}
},
"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"
}
}
"title": "Switch"
}

View File

@@ -1,17 +0,0 @@
"""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

View File

@@ -1,18 +0,0 @@
.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

View File

@@ -147,16 +147,16 @@ class SwitchBotCloudBinarySensor(SwitchBotCloudEntity, BinarySensorEntity):
self.entity_description = description
self._attr_unique_id = f"{device.device_id}_{description.key}"
def _set_attributes(self) -> None:
@property
def is_on(self) -> bool | None:
"""Set attributes from coordinator data."""
if not self.coordinator.data:
return
return None
if self.entity_description.value_fn:
self._attr_is_on = self.entity_description.value_fn(self.coordinator.data)
return
return self.entity_description.value_fn(self.coordinator.data)
self._attr_is_on = (
return (
self.coordinator.data.get(self.entity_description.key)
== self.entity_description.on_value
)

View File

@@ -17,10 +17,6 @@ class TextChangedTrigger(EntityTriggerBase):
_domain = DOMAIN
_schema = ENTITY_STATE_TRIGGER_SCHEMA
def is_valid_transition(self, from_state: State, to_state: State) -> bool:
"""Check if the old and new states are different."""
return from_state.state != to_state.state
def is_valid_state(self, state: State) -> bool:
"""Check if the new state is not invalid."""
return state.state not in (STATE_UNAVAILABLE, STATE_UNKNOWN)

View File

@@ -23,18 +23,11 @@ from . import TuyaConfigEntry
from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode
from .entity import TuyaEntity
from .models import DPCodeBooleanWrapper, DPCodeEnumWrapper, DPCodeIntegerWrapper
from .type_information import IntegerTypeInformation
from .util import RemapHelper
class _DPCodePercentageMappingWrapper(DPCodeIntegerWrapper):
"""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:
"""Check if the position and direction should be reversed."""
return False
@@ -44,15 +37,21 @@ class _DPCodePercentageMappingWrapper(DPCodeIntegerWrapper):
return None
return round(
self._remap_helper.remap_value_to(
value, reverse=self._position_reversed(device)
self.type_information.remap_value_to(
value,
0,
100,
self._position_reversed(device),
)
)
def _convert_value_to_raw_value(self, device: CustomerDevice, value: Any) -> Any:
return round(
self._remap_helper.remap_value_from(
value, reverse=self._position_reversed(device)
self.type_information.remap_value_from(
value,
0,
100,
self._position_reversed(device),
)
)

View File

@@ -24,8 +24,7 @@ from . import TuyaConfigEntry
from .const import TUYA_DISCOVERY_NEW, DeviceCategory, DPCode
from .entity import TuyaEntity
from .models import DPCodeBooleanWrapper, DPCodeEnumWrapper, DPCodeIntegerWrapper
from .type_information import IntegerTypeInformation
from .util import RemapHelper, get_dpcode
from .util import get_dpcode
_DIRECTION_DPCODES = (DPCode.FAN_DIRECTION,)
_MODE_DPCODES = (DPCode.FAN_MODE, DPCode.MODE)
@@ -95,11 +94,6 @@ class _FanSpeedEnumWrapper(DPCodeEnumWrapper):
class _FanSpeedIntegerWrapper(DPCodeIntegerWrapper):
"""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:
"""Get the number of speeds supported by the fan."""
return 100
@@ -108,11 +102,11 @@ class _FanSpeedIntegerWrapper(DPCodeIntegerWrapper):
"""Get the current speed as a percentage."""
if (value := super().read_device_status(device)) is None:
return None
return round(self._remap_helper.remap_value_to(value))
return round(self.type_information.remap_value_to(value, 1, 100))
def _convert_value_to_raw_value(self, device: CustomerDevice, value: Any) -> Any:
"""Convert a Home Assistant value back to a raw device value."""
return round(self._remap_helper.remap_value_from(value))
return round(self.type_information.remap_value_from(value, 1, 100))
def _get_speed_wrapper(

View File

@@ -37,7 +37,7 @@ from .models import (
DPCodeJsonWrapper,
)
from .type_information import IntegerTypeInformation
from .util import RemapHelper
from .util import remap_value
class _BrightnessWrapper(DPCodeIntegerWrapper):
@@ -50,13 +50,6 @@ class _BrightnessWrapper(DPCodeIntegerWrapper):
brightness_min: 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:
"""Return the brightness of this light between 0..255."""
@@ -64,31 +57,29 @@ class _BrightnessWrapper(DPCodeIntegerWrapper):
return None
# Remap value to our scale
brightness = self._remap_helper.remap_value_to(brightness)
brightness = self.type_information.remap_value_to(brightness)
# If there is a min/max value, the brightness is actually limited.
# Meaning it is actually not on a 0-255 scale.
if (
self.brightness_max 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))
is not None
and (brightness_min := device.status.get(self.brightness_min.dpcode))
is not None
):
# Remap values onto our scale
brightness_max = self.brightness_max_remap.remap_value_to(brightness_max)
brightness_min = self.brightness_min_remap.remap_value_to(brightness_min)
brightness_max = self.brightness_max.type_information.remap_value_to(
brightness_max
)
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
brightness = RemapHelper.remap_value(
brightness,
from_min=brightness_min,
from_max=brightness_max,
to_min=0,
to_max=255,
brightness = remap_value(
brightness, from_min=brightness_min, from_max=brightness_max
)
return round(brightness)
@@ -100,69 +91,72 @@ class _BrightnessWrapper(DPCodeIntegerWrapper):
if (
self.brightness_max 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))
is not None
and (brightness_min := device.status.get(self.brightness_min.dpcode))
is not None
):
# Remap values onto our scale
brightness_max = self.brightness_max_remap.remap_value_to(brightness_max)
brightness_min = self.brightness_min_remap.remap_value_to(brightness_min)
brightness_max = self.brightness_max.type_information.remap_value_to(
brightness_max
)
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
value = RemapHelper.remap_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))
value = remap_value(value, to_min=brightness_min, to_max=brightness_max)
return round(self.type_information.remap_value_from(value))
class _ColorTempWrapper(DPCodeIntegerWrapper):
"""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:
"""Return the color temperature value in Kelvin."""
if (temperature := device.status.get(self.dpcode)) is None:
return None
return color_util.color_temperature_mired_to_kelvin(
self._remap_helper.remap_value_to(temperature, reverse=True)
self.type_information.remap_value_to(
temperature,
MIN_MIREDS,
MAX_MIREDS,
reverse=True,
)
)
def _convert_value_to_raw_value(self, device: CustomerDevice, value: Any) -> Any:
"""Convert a Home Assistant value (Kelvin) back to a raw device value."""
return round(
self._remap_helper.remap_value_from(
color_util.color_temperature_kelvin_to_mired(value), reverse=True
self.type_information.remap_value_from(
color_util.color_temperature_kelvin_to_mired(value),
MIN_MIREDS,
MAX_MIREDS,
reverse=True,
)
)
DEFAULT_H_TYPE = RemapHelper(source_min=1, source_max=360, target_min=0, target_max=360)
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_H_TYPE = IntegerTypeInformation(
dpcode=DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=360, step=1
)
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 = RemapHelper(
source_min=1, source_max=360, target_min=0, target_max=360
DEFAULT_H_TYPE_V2 = IntegerTypeInformation(
dpcode=DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=360, step=1
)
DEFAULT_S_TYPE_V2 = RemapHelper(
source_min=1, source_max=1000, target_min=0, target_max=100
DEFAULT_S_TYPE_V2 = IntegerTypeInformation(
dpcode=DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=1000, step=1
)
DEFAULT_V_TYPE_V2 = RemapHelper(
source_min=1, source_max=1000, target_min=0, target_max=255
DEFAULT_V_TYPE_V2 = IntegerTypeInformation(
dpcode=DPCode.COLOUR_DATA_HSV, min=1, scale=0, max=1000, step=1
)
@@ -178,23 +172,23 @@ class _ColorDataWrapper(DPCodeJsonWrapper):
if (status := self.read_device_status(device)) is None:
return None
return (
self.h_type.remap_value_to(status["h"]),
self.s_type.remap_value_to(status["s"]),
self.h_type.remap_value_to(cast(int, status["h"]), 0, 360),
self.s_type.remap_value_to(cast(int, status["s"]), 0, 100),
)
def read_brightness(self, device: CustomerDevice) -> int | None:
"""Get the brightness value from this color data."""
if (status := self.read_device_status(device)) is None:
return None
return round(self.v_type.remap_value_to(status["v"]))
return round(self.v_type.remap_value_to(cast(int, status["v"]), 0, 255))
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."""
color, brightness = value
return json.dumps(
{
"h": round(self.h_type.remap_value_from(color[0])),
"s": round(self.s_type.remap_value_from(color[1])),
"h": round(self.h_type.remap_value_from(color[0], 0, 360)),
"s": round(self.s_type.remap_value_from(color[1], 0, 100)),
"v": round(self.v_type.remap_value_from(brightness)),
}
)
@@ -551,20 +545,12 @@ def _get_brightness_wrapper(
)
) is None:
return None
if brightness_max := DPCodeIntegerWrapper.find_dpcode(
brightness_wrapper.brightness_max = DPCodeIntegerWrapper.find_dpcode(
device, description.brightness_max, prefer_function=True
):
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(
)
brightness_wrapper.brightness_min = DPCodeIntegerWrapper.find_dpcode(
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
@@ -582,16 +568,19 @@ def _get_color_data_wrapper(
# Fetch color data type information
if function_data := json_loads_object(
color_data_wrapper.type_information.type_data
cast(str, color_data_wrapper.type_information.type_data)
):
color_data_wrapper.h_type = RemapHelper.from_function_data(
cast(dict, function_data["h"]), 0, 360
color_data_wrapper.h_type = IntegerTypeInformation(
dpcode=color_data_wrapper.dpcode,
**cast(dict, function_data["h"]),
)
color_data_wrapper.s_type = RemapHelper.from_function_data(
cast(dict, function_data["s"]), 0, 100
color_data_wrapper.s_type = IntegerTypeInformation(
dpcode=color_data_wrapper.dpcode,
**cast(dict, function_data["s"]),
)
color_data_wrapper.v_type = RemapHelper.from_function_data(
cast(dict, function_data["v"]), 0, 255
color_data_wrapper.v_type = IntegerTypeInformation(
dpcode=color_data_wrapper.dpcode,
**cast(dict, function_data["v"]),
)
elif (
description.fallback_color_data_mode == FallbackColorDataMode.V2

View File

@@ -11,7 +11,7 @@ from tuya_sharing import CustomerDevice
from homeassistant.util.json import json_loads_object
from .const import LOGGER, DPType
from .util import parse_dptype
from .util import parse_dptype, remap_value
# Dictionary to track logged warnings to avoid spamming logs
# Keyed by device ID
@@ -41,7 +41,7 @@ class TypeInformation[T]:
_DPTYPE: ClassVar[DPType]
dpcode: str
type_data: str
type_data: str | None = None
def process_raw_value(
self, raw_value: Any | None, device: CustomerDevice
@@ -223,6 +223,26 @@ class IntegerTypeInformation(TypeInformation[float]):
"""Return raw value for scaled."""
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(
self, raw_value: Any | None, device: CustomerDevice
) -> float | None:

View File

@@ -2,18 +2,12 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any
from tuya_sharing import CustomerDevice
from homeassistant.exceptions import ServiceValidationError
from .const import DOMAIN, DPCode, DPType
if TYPE_CHECKING:
from .type_information import IntegerTypeInformation
_DPTYPE_MAPPING: dict[str, DPType] = {
"bitmap": DPType.BITMAP,
"bool": DPType.BOOLEAN,
@@ -56,78 +50,18 @@ def parse_dptype(dptype: str) -> DPType | None:
return _DPTYPE_MAPPING.get(dptype)
@dataclass(kw_only=True)
class RemapHelper:
"""Helper class for remapping values."""
source_min: int
source_max: int
target_min: int
target_max: int
@classmethod
def from_type_information(
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
def remap_value(
value: float,
from_min: float = 0,
from_max: float = 255,
to_min: float = 0,
to_max: float = 255,
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):

View File

@@ -67,11 +67,13 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="dark",
translation_key="is_dark",
icon="mdi:brightness-6",
ufp_value="is_dark",
),
ProtectBinaryEntityDescription(
key="ssh",
translation_key="ssh_enabled",
icon="mdi:lock",
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
ufp_value="is_ssh_enabled",
@@ -80,6 +82,7 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="status_light",
translation_key="status_light",
icon="mdi:led-on",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_required_field="feature_flags.has_led_status",
ufp_value="led_settings.is_enabled",
@@ -88,6 +91,7 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="hdr_mode",
translation_key="hdr_mode",
icon="mdi:brightness-7",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_required_field="feature_flags.has_hdr",
ufp_value="hdr_mode",
@@ -96,6 +100,7 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="high_fps",
translation_key="high_fps",
icon="mdi:video-high-definition",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_required_field="feature_flags.has_highfps",
ufp_value="is_high_fps_enabled",
@@ -104,6 +109,7 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="system_sounds",
translation_key="system_sounds",
icon="mdi:speaker",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_required_field="has_speaker",
ufp_value="speaker_settings.are_system_sounds_enabled",
@@ -113,6 +119,7 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="osd_name",
translation_key="overlay_show_name",
icon="mdi:fullscreen",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_value="osd_settings.is_name_enabled",
ufp_perm=PermRequired.NO_WRITE,
@@ -120,6 +127,7 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="osd_date",
translation_key="overlay_show_date",
icon="mdi:fullscreen",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_value="osd_settings.is_date_enabled",
ufp_perm=PermRequired.NO_WRITE,
@@ -127,6 +135,7 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="osd_logo",
translation_key="overlay_show_logo",
icon="mdi:fullscreen",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_value="osd_settings.is_logo_enabled",
ufp_perm=PermRequired.NO_WRITE,
@@ -134,6 +143,7 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="osd_bitrate",
translation_key="overlay_show_nerd_mode",
icon="mdi:fullscreen",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_value="osd_settings.is_debug_enabled",
ufp_perm=PermRequired.NO_WRITE,
@@ -141,12 +151,14 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="motion_enabled",
translation_key="detections_motion",
icon="mdi:run-fast",
ufp_value="recording_settings.enable_motion_detection",
ufp_perm=PermRequired.NO_WRITE,
),
ProtectBinaryEntityDescription(
key="smart_person",
translation_key="detections_person",
icon="mdi:walk",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_required_field="can_detect_person",
ufp_value="is_person_detection_on",
@@ -155,6 +167,7 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="smart_vehicle",
translation_key="detections_vehicle",
icon="mdi:car",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_required_field="can_detect_vehicle",
ufp_value="is_vehicle_detection_on",
@@ -163,6 +176,7 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="smart_animal",
translation_key="detections_animal",
icon="mdi:paw",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_required_field="can_detect_animal",
ufp_value="is_animal_detection_on",
@@ -171,6 +185,7 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="smart_package",
translation_key="detections_package",
icon="mdi:package-variant-closed",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_required_field="can_detect_package",
ufp_value="is_package_detection_on",
@@ -179,6 +194,7 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="smart_licenseplate",
translation_key="detections_license_plate",
icon="mdi:car",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_required_field="can_detect_license_plate",
ufp_value="is_license_plate_detection_on",
@@ -187,6 +203,7 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="smart_smoke",
translation_key="detections_smoke",
icon="mdi:fire",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_required_field="can_detect_smoke",
ufp_value="is_smoke_detection_on",
@@ -195,6 +212,7 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="smart_cmonx",
translation_key="detections_co_alarm",
icon="mdi:molecule-co",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_required_field="can_detect_co",
ufp_value="is_co_detection_on",
@@ -203,6 +221,7 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="smart_siren",
translation_key="detections_siren",
icon="mdi:alarm-bell",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_required_field="can_detect_siren",
ufp_value="is_siren_detection_on",
@@ -211,6 +230,7 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="smart_baby_cry",
translation_key="detections_baby_cry",
icon="mdi:cradle",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_required_field="can_detect_baby_cry",
ufp_value="is_baby_cry_detection_on",
@@ -219,6 +239,7 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="smart_speak",
translation_key="detections_speaking",
icon="mdi:account-voice",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_required_field="can_detect_speaking",
ufp_value="is_speaking_detection_on",
@@ -227,6 +248,7 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="smart_bark",
translation_key="detections_barking",
icon="mdi:dog",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_required_field="can_detect_bark",
ufp_value="is_bark_detection_on",
@@ -235,6 +257,7 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="smart_car_alarm",
translation_key="detections_car_alarm",
icon="mdi:car",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_required_field="can_detect_car_alarm",
ufp_value="is_car_alarm_detection_on",
@@ -243,6 +266,7 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="smart_car_horn",
translation_key="detections_car_horn",
icon="mdi:bugle",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_required_field="can_detect_car_horn",
ufp_value="is_car_horn_detection_on",
@@ -251,6 +275,7 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="smart_glass_break",
translation_key="detections_glass_break",
icon="mdi:glass-fragile",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_required_field="can_detect_glass_break",
ufp_value="is_glass_break_detection_on",
@@ -259,6 +284,7 @@ CAMERA_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="track_person",
translation_key="tracking_person",
icon="mdi:walk",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_required_field="feature_flags.is_ptz",
ufp_value="is_person_tracking_enabled",
@@ -270,6 +296,7 @@ LIGHT_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="dark",
translation_key="is_dark",
icon="mdi:brightness-6",
ufp_value="is_dark",
),
ProtectBinaryEntityDescription(
@@ -280,6 +307,7 @@ LIGHT_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="light",
translation_key="flood_light",
icon="mdi:spotlight-beam",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_value="is_light_on",
ufp_perm=PermRequired.NO_WRITE,
@@ -287,6 +315,7 @@ LIGHT_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="ssh",
translation_key="ssh_enabled",
icon="mdi:lock",
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
ufp_value="is_ssh_enabled",
@@ -295,6 +324,7 @@ LIGHT_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="status_light",
translation_key="status_light",
icon="mdi:led-on",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_value="light_device_settings.is_indicator_enabled",
ufp_perm=PermRequired.NO_WRITE,
@@ -340,6 +370,7 @@ SENSE_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="status_light",
translation_key="status_light",
icon="mdi:led-on",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_value="led_settings.is_enabled",
ufp_perm=PermRequired.NO_WRITE,
@@ -347,6 +378,7 @@ SENSE_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="motion_enabled",
translation_key="detections_motion",
icon="mdi:walk",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_value="motion_settings.is_enabled",
ufp_perm=PermRequired.NO_WRITE,
@@ -354,6 +386,7 @@ SENSE_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="temperature",
translation_key="temperature_sensor",
icon="mdi:thermometer",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_value="temperature_settings.is_enabled",
ufp_perm=PermRequired.NO_WRITE,
@@ -361,6 +394,7 @@ SENSE_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="humidity",
translation_key="humidity_sensor",
icon="mdi:water-percent",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_value="humidity_settings.is_enabled",
ufp_perm=PermRequired.NO_WRITE,
@@ -368,6 +402,7 @@ SENSE_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="light",
translation_key="light_sensor",
icon="mdi:brightness-5",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_value="light_settings.is_enabled",
ufp_perm=PermRequired.NO_WRITE,
@@ -386,6 +421,7 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
key="doorbell",
translation_key="doorbell",
device_class=BinarySensorDeviceClass.OCCUPANCY,
icon="mdi:doorbell-video",
ufp_required_field="feature_flags.is_doorbell",
ufp_event_obj="last_ring_event",
),
@@ -398,6 +434,7 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
ProtectBinaryEventEntityDescription(
key="smart_obj_any",
translation_key="object_detected",
icon="mdi:eye",
ufp_required_field="feature_flags.has_smart_detect",
ufp_event_obj="last_smart_detect_event",
entity_registry_enabled_default=False,
@@ -405,6 +442,7 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
ProtectBinaryEventEntityDescription(
key="smart_obj_person",
translation_key="person_detected",
icon="mdi:walk",
ufp_obj_type=SmartDetectObjectType.PERSON,
ufp_required_field="can_detect_person",
ufp_enabled="is_person_detection_on",
@@ -413,6 +451,7 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
ProtectBinaryEventEntityDescription(
key="smart_obj_vehicle",
translation_key="vehicle_detected",
icon="mdi:car",
ufp_obj_type=SmartDetectObjectType.VEHICLE,
ufp_required_field="can_detect_vehicle",
ufp_enabled="is_vehicle_detection_on",
@@ -421,6 +460,7 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
ProtectBinaryEventEntityDescription(
key="smart_obj_animal",
translation_key="animal_detected",
icon="mdi:paw",
ufp_obj_type=SmartDetectObjectType.ANIMAL,
ufp_required_field="can_detect_animal",
ufp_enabled="is_animal_detection_on",
@@ -429,6 +469,7 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
ProtectBinaryEventEntityDescription(
key="smart_obj_package",
translation_key="package_detected",
icon="mdi:package-variant-closed",
entity_registry_enabled_default=False,
ufp_obj_type=SmartDetectObjectType.PACKAGE,
ufp_required_field="can_detect_package",
@@ -438,6 +479,7 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
ProtectBinaryEventEntityDescription(
key="smart_audio_any",
translation_key="audio_object_detected",
icon="mdi:eye",
ufp_required_field="feature_flags.has_smart_detect",
ufp_event_obj="last_smart_audio_detect_event",
entity_registry_enabled_default=False,
@@ -445,6 +487,7 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
ProtectBinaryEventEntityDescription(
key="smart_audio_smoke",
translation_key="smoke_alarm_detected",
icon="mdi:fire",
ufp_obj_type=SmartDetectObjectType.SMOKE,
ufp_required_field="can_detect_smoke",
ufp_enabled="is_smoke_detection_on",
@@ -453,6 +496,7 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
ProtectBinaryEventEntityDescription(
key="smart_audio_cmonx",
translation_key="co_alarm_detected",
icon="mdi:molecule-co",
ufp_required_field="can_detect_co",
ufp_enabled="is_co_detection_on",
ufp_event_obj="last_cmonx_detect_event",
@@ -461,6 +505,7 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
ProtectBinaryEventEntityDescription(
key="smart_audio_siren",
translation_key="siren_detected",
icon="mdi:alarm-bell",
ufp_obj_type=SmartDetectObjectType.SIREN,
ufp_required_field="can_detect_siren",
ufp_enabled="is_siren_detection_on",
@@ -469,6 +514,7 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
ProtectBinaryEventEntityDescription(
key="smart_audio_baby_cry",
translation_key="baby_cry_detected",
icon="mdi:cradle",
ufp_obj_type=SmartDetectObjectType.BABY_CRY,
ufp_required_field="can_detect_baby_cry",
ufp_enabled="is_baby_cry_detection_on",
@@ -477,6 +523,7 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
ProtectBinaryEventEntityDescription(
key="smart_audio_speak",
translation_key="speaking_detected",
icon="mdi:account-voice",
ufp_obj_type=SmartDetectObjectType.SPEAK,
ufp_required_field="can_detect_speaking",
ufp_enabled="is_speaking_detection_on",
@@ -485,6 +532,7 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
ProtectBinaryEventEntityDescription(
key="smart_audio_bark",
translation_key="barking_detected",
icon="mdi:dog",
ufp_obj_type=SmartDetectObjectType.BARK,
ufp_required_field="can_detect_bark",
ufp_enabled="is_bark_detection_on",
@@ -493,6 +541,7 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
ProtectBinaryEventEntityDescription(
key="smart_audio_car_alarm",
translation_key="car_alarm_detected",
icon="mdi:car",
ufp_obj_type=SmartDetectObjectType.BURGLAR,
ufp_required_field="can_detect_car_alarm",
ufp_enabled="is_car_alarm_detection_on",
@@ -501,6 +550,7 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
ProtectBinaryEventEntityDescription(
key="smart_audio_car_horn",
translation_key="car_horn_detected",
icon="mdi:bugle",
ufp_obj_type=SmartDetectObjectType.CAR_HORN,
ufp_required_field="can_detect_car_horn",
ufp_enabled="is_car_horn_detection_on",
@@ -509,6 +559,7 @@ EVENT_SENSORS: tuple[ProtectBinaryEventEntityDescription, ...] = (
ProtectBinaryEventEntityDescription(
key="smart_audio_glass_break",
translation_key="glass_break_detected",
icon="mdi:glass-fragile",
ufp_obj_type=SmartDetectObjectType.GLASS_BREAK,
ufp_required_field="can_detect_glass_break",
ufp_enabled="is_glass_break_detection_on",
@@ -526,6 +577,7 @@ DOORLOCK_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="status_light",
translation_key="status_light",
icon="mdi:led-on",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_value="led_settings.is_enabled",
ufp_perm=PermRequired.NO_WRITE,
@@ -536,6 +588,7 @@ VIEWER_SENSORS: tuple[ProtectBinaryEntityDescription, ...] = (
ProtectBinaryEntityDescription(
key="ssh",
translation_key="ssh_enabled",
icon="mdi:lock",
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
ufp_value="is_ssh_enabled",

View File

@@ -60,6 +60,7 @@ ALL_DEVICE_BUTTONS: tuple[ProtectButtonEntityDescription, ...] = (
key="unadopt",
translation_key="unadopt_device",
entity_registry_enabled_default=False,
icon="mdi:delete",
ufp_press="unadopt",
ufp_perm=PermRequired.DELETE,
),
@@ -68,6 +69,7 @@ ALL_DEVICE_BUTTONS: tuple[ProtectButtonEntityDescription, ...] = (
ADOPT_BUTTON = ProtectButtonEntityDescription[ProtectAdoptableDeviceModel](
key="adopt",
translation_key="adopt_device",
icon="mdi:plus-circle",
ufp_press="adopt",
)
@@ -75,6 +77,7 @@ SENSOR_BUTTONS: tuple[ProtectButtonEntityDescription, ...] = (
ProtectButtonEntityDescription(
key="clear_tamper",
translation_key="clear_tamper",
icon="mdi:notification-clear-all",
ufp_press="clear_tamper",
ufp_perm=PermRequired.WRITE,
),
@@ -85,11 +88,13 @@ CHIME_BUTTONS: tuple[ProtectButtonEntityDescription, ...] = (
key="play",
translation_key="play_chime",
device_class=DEVICE_CLASS_CHIME_BUTTON,
icon="mdi:play",
ufp_press="play",
),
ProtectButtonEntityDescription(
key="play_buzzer",
translation_key="play_buzzer",
icon="mdi:play",
ufp_press="play_buzzer",
),
)

View File

@@ -365,6 +365,7 @@ EVENT_DESCRIPTIONS: tuple[ProtectEventEntityDescription, ...] = (
key="doorbell",
translation_key="doorbell",
device_class=EventDeviceClass.DOORBELL,
icon="mdi:doorbell-video",
ufp_required_field="feature_flags.is_doorbell",
ufp_event_obj="last_ring_event",
event_types=[EVENT_TYPE_DOORBELL_RING],
@@ -373,6 +374,7 @@ EVENT_DESCRIPTIONS: tuple[ProtectEventEntityDescription, ...] = (
ProtectEventEntityDescription(
key="nfc",
translation_key="nfc",
icon="mdi:nfc",
ufp_required_field="feature_flags.support_nfc",
ufp_event_obj="last_nfc_card_scanned_event",
event_types=[EVENT_TYPE_NFC_SCANNED],
@@ -381,6 +383,7 @@ EVENT_DESCRIPTIONS: tuple[ProtectEventEntityDescription, ...] = (
ProtectEventEntityDescription(
key="fingerprint",
translation_key="fingerprint",
icon="mdi:fingerprint",
ufp_required_field="feature_flags.has_fingerprint_sensor",
ufp_event_obj="last_fingerprint_identified_event",
event_types=[
@@ -392,6 +395,7 @@ EVENT_DESCRIPTIONS: tuple[ProtectEventEntityDescription, ...] = (
ProtectEventEntityDescription(
key="vehicle",
translation_key="vehicle",
icon="mdi:car",
ufp_required_field="feature_flags.has_smart_detect",
ufp_event_obj="last_smart_detect_event",
event_types=[EVENT_TYPE_VEHICLE_DETECTED],

View File

@@ -1,422 +1,4 @@
{
"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": {
"add_doorbell_text": {
"service": "mdi:message-plus"

View File

@@ -67,6 +67,7 @@ CAMERA_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
ProtectNumberEntityDescription(
key="wdr_value",
translation_key="wide_dynamic_range",
icon="mdi:state-machine",
entity_category=EntityCategory.CONFIG,
ufp_min=0,
ufp_max=3,
@@ -79,6 +80,7 @@ CAMERA_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
ProtectNumberEntityDescription(
key="mic_level",
translation_key="microphone_level",
icon="mdi:microphone",
entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=PERCENTAGE,
ufp_min=0,
@@ -93,6 +95,7 @@ CAMERA_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
ProtectNumberEntityDescription(
key="system_sounds_volume",
translation_key="system_sounds_volume",
icon="mdi:volume-high",
entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=PERCENTAGE,
ufp_min=0,
@@ -107,6 +110,7 @@ CAMERA_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
ProtectNumberEntityDescription(
key="doorbell_ring_volume",
translation_key="doorbell_ring_volume",
icon="mdi:bell-ring",
entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=PERCENTAGE,
ufp_min=0,
@@ -121,6 +125,7 @@ CAMERA_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
ProtectNumberEntityDescription(
key="zoom_position",
translation_key="zoom_level",
icon="mdi:magnify-plus-outline",
entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=PERCENTAGE,
ufp_min=0,
@@ -134,6 +139,7 @@ CAMERA_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
ProtectNumberEntityDescription(
key="chime_duration",
translation_key="chime_duration",
icon="mdi:bell",
entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=UnitOfTime.SECONDS,
ufp_min=1,
@@ -148,6 +154,7 @@ CAMERA_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
ProtectNumberEntityDescription(
key="icr_lux",
translation_key="infrared_custom_lux_trigger",
icon="mdi:white-balance-sunny",
entity_category=EntityCategory.CONFIG,
ufp_min=0,
ufp_max=30,
@@ -164,6 +171,7 @@ LIGHT_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
ProtectNumberEntityDescription(
key="sensitivity",
translation_key="motion_sensitivity",
icon="mdi:walk",
entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=PERCENTAGE,
ufp_min=0,
@@ -177,6 +185,7 @@ LIGHT_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
ProtectNumberEntityDescription[Light](
key="duration",
translation_key="auto_shutoff_duration",
icon="mdi:camera-timer",
entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=UnitOfTime.SECONDS,
ufp_min=15,
@@ -193,6 +202,7 @@ SENSE_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
ProtectNumberEntityDescription(
key="sensitivity",
translation_key="motion_sensitivity",
icon="mdi:walk",
entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=PERCENTAGE,
ufp_min=0,
@@ -209,6 +219,7 @@ DOORLOCK_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
ProtectNumberEntityDescription[Doorlock](
key="auto_lock_time",
translation_key="auto_lock_timeout",
icon="mdi:walk",
entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=UnitOfTime.SECONDS,
ufp_min=0,
@@ -225,6 +236,7 @@ CHIME_NUMBERS: tuple[ProtectNumberEntityDescription, ...] = (
ProtectNumberEntityDescription(
key="volume",
translation_key="volume",
icon="mdi:speaker",
entity_category=EntityCategory.CONFIG,
native_unit_of_measurement=PERCENTAGE,
ufp_min=0,

View File

@@ -195,6 +195,7 @@ CAMERA_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
ProtectSelectEntityDescription(
key="recording_mode",
translation_key="recording_mode",
icon="mdi:video-outline",
entity_category=EntityCategory.CONFIG,
ufp_options=DEVICE_RECORDING_MODES,
ufp_enum_type=RecordingMode,
@@ -205,6 +206,7 @@ CAMERA_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
ProtectSelectEntityDescription(
key="infrared",
translation_key="infrared_mode",
icon="mdi:circle-opacity",
entity_category=EntityCategory.CONFIG,
ufp_required_field="feature_flags.has_led_ir",
ufp_options=INFRARED_MODES,
@@ -216,6 +218,7 @@ CAMERA_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
ProtectSelectEntityDescription[Camera](
key="doorbell_text",
translation_key="doorbell_text",
icon="mdi:card-text",
entity_category=EntityCategory.CONFIG,
device_class=DEVICE_CLASS_LCD_MESSAGE,
ufp_required_field="feature_flags.has_lcd_screen",
@@ -227,6 +230,7 @@ CAMERA_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
ProtectSelectEntityDescription(
key="chime_type",
translation_key="chime_type",
icon="mdi:bell",
entity_category=EntityCategory.CONFIG,
ufp_required_field="feature_flags.has_chime",
ufp_options=CHIME_TYPES,
@@ -238,6 +242,7 @@ CAMERA_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
ProtectSelectEntityDescription(
key="hdr_mode",
translation_key="hdr_mode",
icon="mdi:brightness-7",
entity_category=EntityCategory.CONFIG,
ufp_required_field="feature_flags.has_hdr",
ufp_options=HDR_MODES,
@@ -251,6 +256,7 @@ LIGHT_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
ProtectSelectEntityDescription[Light](
key=_KEY_LIGHT_MOTION,
translation_key="light_mode",
icon="mdi:spotlight",
entity_category=EntityCategory.CONFIG,
ufp_options=MOTION_MODE_TO_LIGHT_MODE,
ufp_value_fn=async_get_light_motion_current,
@@ -260,6 +266,7 @@ LIGHT_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
ProtectSelectEntityDescription[Light](
key="paired_camera",
translation_key="paired_camera",
icon="mdi:cctv",
entity_category=EntityCategory.CONFIG,
ufp_value="camera_id",
ufp_options_fn=_get_paired_camera_options,
@@ -272,6 +279,7 @@ SENSE_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
ProtectSelectEntityDescription(
key="mount_type",
translation_key="mount_type",
icon="mdi:screwdriver",
entity_category=EntityCategory.CONFIG,
ufp_options=MOUNT_TYPES,
ufp_enum_type=MountType,
@@ -282,6 +290,7 @@ SENSE_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
ProtectSelectEntityDescription[Sensor](
key="paired_camera",
translation_key="paired_camera",
icon="mdi:cctv",
entity_category=EntityCategory.CONFIG,
ufp_value="camera_id",
ufp_options_fn=_get_paired_camera_options,
@@ -294,6 +303,7 @@ DOORLOCK_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
ProtectSelectEntityDescription[Doorlock](
key="paired_camera",
translation_key="paired_camera",
icon="mdi:cctv",
entity_category=EntityCategory.CONFIG,
ufp_value="camera_id",
ufp_options_fn=_get_paired_camera_options,
@@ -306,6 +316,7 @@ VIEWER_SELECTS: tuple[ProtectSelectEntityDescription, ...] = (
ProtectSelectEntityDescription[Viewer](
key="viewer",
translation_key="liveview",
icon="mdi:view-dashboard",
entity_category=None,
ufp_options_fn=_get_viewer_options,
ufp_value_fn=_get_viewer_current,

View File

@@ -126,6 +126,7 @@ ALL_DEVICES_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
ProtectSensorEntityDescription(
key="uptime",
translation_key="uptime",
icon="mdi:clock",
device_class=SensorDeviceClass.TIMESTAMP,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
@@ -214,6 +215,7 @@ CAMERA_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
key="doorbell_last_trip_time",
translation_key="last_doorbell_ring",
device_class=SensorDeviceClass.TIMESTAMP,
icon="mdi:doorbell-video",
ufp_required_field="feature_flags.is_doorbell",
ufp_value="last_ring",
entity_registry_enabled_default=False,
@@ -222,12 +224,14 @@ CAMERA_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
key="lens_type",
translation_key="lens_type",
entity_category=EntityCategory.DIAGNOSTIC,
icon="mdi:camera-iris",
ufp_required_field="has_removable_lens",
ufp_value="feature_flags.lens_type",
),
ProtectSensorEntityDescription(
key="mic_level",
translation_key="microphone_level",
icon="mdi:microphone",
native_unit_of_measurement=PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
ufp_required_field="has_mic",
@@ -238,6 +242,7 @@ CAMERA_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
ProtectSensorEntityDescription(
key="recording_mode",
translation_key="recording_mode",
icon="mdi:video-outline",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_value="recording_settings.mode.value",
ufp_perm=PermRequired.NO_WRITE,
@@ -245,6 +250,7 @@ CAMERA_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
ProtectSensorEntityDescription(
key="infrared",
translation_key="infrared_mode",
icon="mdi:circle-opacity",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_required_field="feature_flags.has_led_ir",
ufp_value="isp_settings.ir_led_mode.value",
@@ -253,6 +259,7 @@ CAMERA_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
ProtectSensorEntityDescription(
key="doorbell_text",
translation_key="doorbell_text",
icon="mdi:card-text",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_required_field="feature_flags.has_lcd_screen",
ufp_value="lcd_message.text",
@@ -261,6 +268,7 @@ CAMERA_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
ProtectSensorEntityDescription(
key="chime_type",
translation_key="chime_type",
icon="mdi:bell",
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
ufp_required_field="feature_flags.has_chime",
@@ -358,6 +366,7 @@ SENSE_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
ProtectSensorEntityDescription(
key="sensitivity",
translation_key="sensitivity",
icon="mdi:walk",
native_unit_of_measurement=PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
ufp_value="motion_settings.sensitivity",
@@ -366,6 +375,7 @@ SENSE_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
ProtectSensorEntityDescription(
key="mount_type",
translation_key="mount_type",
icon="mdi:screwdriver",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_value="mount_type",
ufp_perm=PermRequired.NO_WRITE,
@@ -373,6 +383,7 @@ SENSE_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
ProtectSensorEntityDescription(
key="paired_camera",
translation_key="paired_camera",
icon="mdi:cctv",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_value="camera.display_name",
ufp_perm=PermRequired.NO_WRITE,
@@ -391,6 +402,7 @@ DOORLOCK_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
ProtectSensorEntityDescription(
key="paired_camera",
translation_key="paired_camera",
icon="mdi:cctv",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_value="camera.display_name",
ufp_perm=PermRequired.NO_WRITE,
@@ -401,6 +413,7 @@ NVR_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
ProtectSensorEntityDescription(
key="uptime",
translation_key="uptime",
icon="mdi:clock",
device_class=SensorDeviceClass.TIMESTAMP,
entity_category=EntityCategory.DIAGNOSTIC,
ufp_value_fn=_get_uptime,
@@ -409,6 +422,7 @@ NVR_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
key="storage_utilization",
translation_key="storage_utilization",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:harddisk",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
ufp_value="storage_stats.utilization",
@@ -418,6 +432,7 @@ NVR_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
key="record_rotating",
translation_key="type_timelapse_video",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:server",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
ufp_value="storage_stats.storage_distribution.timelapse_recordings.percentage",
@@ -427,6 +442,7 @@ NVR_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
key="record_timelapse",
translation_key="type_continuous_video",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:server",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
ufp_value="storage_stats.storage_distribution.continuous_recordings.percentage",
@@ -436,6 +452,7 @@ NVR_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
key="record_detections",
translation_key="type_detections_video",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:server",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
ufp_value="storage_stats.storage_distribution.detections_recordings.percentage",
@@ -445,6 +462,7 @@ NVR_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
key="resolution_HD",
translation_key="resolution_hd_video",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:cctv",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
ufp_value="storage_stats.storage_distribution.hd_usage.percentage",
@@ -454,6 +472,7 @@ NVR_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
key="resolution_4K",
translation_key="resolution_4k_video",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:cctv",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
ufp_value="storage_stats.storage_distribution.uhd_usage.percentage",
@@ -463,6 +482,7 @@ NVR_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
key="resolution_free",
translation_key="resolution_free_space",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:cctv",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
ufp_value="storage_stats.storage_distribution.free.percentage",
@@ -472,6 +492,7 @@ NVR_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
key="record_capacity",
translation_key="recording_capacity",
native_unit_of_measurement=UnitOfTime.SECONDS,
icon="mdi:record-rec",
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
ufp_value_fn=_get_nvr_recording_capacity,
@@ -483,6 +504,7 @@ NVR_DISABLED_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
key="cpu_utilization",
translation_key="cpu_utilization",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:speedometer",
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
@@ -502,6 +524,7 @@ NVR_DISABLED_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
key="memory_utilization",
translation_key="memory_utilization",
native_unit_of_measurement=PERCENTAGE,
icon="mdi:memory",
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
@@ -521,6 +544,7 @@ LIGHT_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
ProtectSensorEntityDescription(
key="sensitivity",
translation_key="motion_sensitivity",
icon="mdi:walk",
native_unit_of_measurement=PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
ufp_value="light_device_settings.pir_sensitivity",
@@ -529,6 +553,7 @@ LIGHT_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
ProtectSensorEntityDescription[Light](
key="light_motion",
translation_key="light_mode",
icon="mdi:spotlight",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_value_fn=async_get_light_motion_current,
ufp_perm=PermRequired.NO_WRITE,
@@ -536,6 +561,7 @@ LIGHT_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
ProtectSensorEntityDescription(
key="paired_camera",
translation_key="paired_camera",
icon="mdi:cctv",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_value="camera.display_name",
ufp_perm=PermRequired.NO_WRITE,
@@ -557,11 +583,13 @@ CHIME_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
key="last_ring",
translation_key="last_ring",
device_class=SensorDeviceClass.TIMESTAMP,
icon="mdi:bell",
ufp_value="last_ring",
),
ProtectSensorEntityDescription(
key="volume",
translation_key="volume",
icon="mdi:speaker",
native_unit_of_measurement=PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
ufp_value="volume",
@@ -573,6 +601,7 @@ VIEWER_SENSORS: tuple[ProtectSensorEntityDescription, ...] = (
ProtectSensorEntityDescription(
key="viewer",
translation_key="liveview",
icon="mdi:view-dashboard",
entity_category=EntityCategory.DIAGNOSTIC,
ufp_value="liveview.name",
ufp_perm=PermRequired.NO_WRITE,

View File

@@ -54,6 +54,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="ssh",
translation_key="ssh_enabled",
icon="mdi:lock",
entity_registry_enabled_default=False,
entity_category=EntityCategory.CONFIG,
ufp_value="is_ssh_enabled",
@@ -63,6 +64,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="status_light",
translation_key="status_light",
icon="mdi:led-on",
entity_category=EntityCategory.CONFIG,
ufp_required_field="feature_flags.has_led_status",
ufp_value="led_settings.is_enabled",
@@ -72,6 +74,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="hdr_mode",
translation_key="hdr_mode",
icon="mdi:brightness-7",
entity_category=EntityCategory.CONFIG,
entity_registry_enabled_default=False,
ufp_required_field="feature_flags.has_hdr",
@@ -82,6 +85,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription[Camera](
key="high_fps",
translation_key="high_fps",
icon="mdi:video-high-definition",
entity_category=EntityCategory.CONFIG,
ufp_required_field="feature_flags.has_highfps",
ufp_value="is_high_fps_enabled",
@@ -91,6 +95,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="system_sounds",
translation_key="system_sounds",
icon="mdi:speaker",
entity_category=EntityCategory.CONFIG,
ufp_required_field="has_speaker",
ufp_value="speaker_settings.are_system_sounds_enabled",
@@ -101,6 +106,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="osd_name",
translation_key="overlay_show_name",
icon="mdi:fullscreen",
entity_category=EntityCategory.CONFIG,
ufp_value="osd_settings.is_name_enabled",
ufp_set_method="set_osd_name",
@@ -109,6 +115,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="osd_date",
translation_key="overlay_show_date",
icon="mdi:fullscreen",
entity_category=EntityCategory.CONFIG,
ufp_value="osd_settings.is_date_enabled",
ufp_set_method="set_osd_date",
@@ -117,6 +124,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="osd_logo",
translation_key="overlay_show_logo",
icon="mdi:fullscreen",
entity_category=EntityCategory.CONFIG,
ufp_value="osd_settings.is_logo_enabled",
ufp_set_method="set_osd_logo",
@@ -125,6 +133,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="osd_bitrate",
translation_key="overlay_show_nerd_mode",
icon="mdi:fullscreen",
entity_category=EntityCategory.CONFIG,
ufp_value="osd_settings.is_debug_enabled",
ufp_set_method="set_osd_bitrate",
@@ -133,6 +142,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="color_night_vision",
translation_key="color_night_vision",
icon="mdi:light-flood-down",
entity_category=EntityCategory.CONFIG,
ufp_required_field="has_color_night_vision",
ufp_value="isp_settings.is_color_night_vision_enabled",
@@ -142,6 +152,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="motion",
translation_key="motion",
icon="mdi:run-fast",
entity_category=EntityCategory.CONFIG,
ufp_value="recording_settings.enable_motion_detection",
ufp_enabled="is_recording_enabled",
@@ -151,6 +162,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="smart_person",
translation_key="detections_person",
icon="mdi:walk",
entity_category=EntityCategory.CONFIG,
ufp_required_field="can_detect_person",
ufp_value="is_person_detection_on",
@@ -161,6 +173,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="smart_vehicle",
translation_key="detections_vehicle",
icon="mdi:car",
entity_category=EntityCategory.CONFIG,
ufp_required_field="can_detect_vehicle",
ufp_value="is_vehicle_detection_on",
@@ -171,6 +184,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="smart_animal",
translation_key="detections_animal",
icon="mdi:paw",
entity_category=EntityCategory.CONFIG,
ufp_required_field="can_detect_animal",
ufp_value="is_animal_detection_on",
@@ -181,6 +195,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="smart_package",
translation_key="detections_package",
icon="mdi:package-variant-closed",
entity_category=EntityCategory.CONFIG,
ufp_required_field="can_detect_package",
ufp_value="is_package_detection_on",
@@ -191,6 +206,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="smart_licenseplate",
translation_key="detections_license_plate",
icon="mdi:car",
entity_category=EntityCategory.CONFIG,
ufp_required_field="can_detect_license_plate",
ufp_value="is_license_plate_detection_on",
@@ -201,6 +217,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="smart_smoke",
translation_key="detections_smoke",
icon="mdi:fire",
entity_category=EntityCategory.CONFIG,
ufp_required_field="can_detect_smoke",
ufp_value="is_smoke_detection_on",
@@ -211,6 +228,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="smart_cmonx",
translation_key="detections_co_alarm",
icon="mdi:molecule-co",
entity_category=EntityCategory.CONFIG,
ufp_required_field="can_detect_co",
ufp_value="is_co_detection_on",
@@ -221,6 +239,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="smart_siren",
translation_key="detections_siren",
icon="mdi:alarm-bell",
entity_category=EntityCategory.CONFIG,
ufp_required_field="can_detect_siren",
ufp_value="is_siren_detection_on",
@@ -231,6 +250,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="smart_baby_cry",
translation_key="detections_baby_cry",
icon="mdi:cradle",
entity_category=EntityCategory.CONFIG,
ufp_required_field="can_detect_baby_cry",
ufp_value="is_baby_cry_detection_on",
@@ -241,6 +261,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="smart_speak",
translation_key="detections_speak",
icon="mdi:account-voice",
entity_category=EntityCategory.CONFIG,
ufp_required_field="can_detect_speaking",
ufp_value="is_speaking_detection_on",
@@ -251,6 +272,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="smart_bark",
translation_key="detections_bark",
icon="mdi:dog",
entity_category=EntityCategory.CONFIG,
ufp_required_field="can_detect_bark",
ufp_value="is_bark_detection_on",
@@ -261,6 +283,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="smart_car_alarm",
translation_key="detections_car_alarm",
icon="mdi:car",
entity_category=EntityCategory.CONFIG,
ufp_required_field="can_detect_car_alarm",
ufp_value="is_car_alarm_detection_on",
@@ -271,6 +294,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="smart_car_horn",
translation_key="detections_car_horn",
icon="mdi:bugle",
entity_category=EntityCategory.CONFIG,
ufp_required_field="can_detect_car_horn",
ufp_value="is_car_horn_detection_on",
@@ -281,6 +305,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="smart_glass_break",
translation_key="detections_glass_break",
icon="mdi:glass-fragile",
entity_category=EntityCategory.CONFIG,
ufp_required_field="can_detect_glass_break",
ufp_value="is_glass_break_detection_on",
@@ -291,6 +316,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="track_person",
translation_key="tracking_person",
icon="mdi:walk",
entity_category=EntityCategory.CONFIG,
ufp_required_field="feature_flags.is_ptz",
ufp_value="is_person_tracking_enabled",
@@ -302,6 +328,7 @@ CAMERA_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
PRIVACY_MODE_SWITCH = ProtectSwitchEntityDescription[Camera](
key="privacy_mode",
translation_key="privacy_mode",
icon="mdi:eye-settings",
entity_category=EntityCategory.CONFIG,
ufp_required_field="feature_flags.has_privacy_mask",
ufp_value="is_privacy_on",
@@ -312,6 +339,7 @@ SENSE_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="status_light",
translation_key="status_light",
icon="mdi:led-on",
entity_category=EntityCategory.CONFIG,
ufp_value="led_settings.is_enabled",
ufp_set_method="set_status_light",
@@ -320,6 +348,7 @@ SENSE_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="motion",
translation_key="detections_motion",
icon="mdi:walk",
entity_category=EntityCategory.CONFIG,
ufp_value="motion_settings.is_enabled",
ufp_set_method="set_motion_status",
@@ -328,6 +357,7 @@ SENSE_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="temperature",
translation_key="temperature_sensor",
icon="mdi:thermometer",
entity_category=EntityCategory.CONFIG,
ufp_value="temperature_settings.is_enabled",
ufp_set_method="set_temperature_status",
@@ -336,6 +366,7 @@ SENSE_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="humidity",
translation_key="humidity_sensor",
icon="mdi:water-percent",
entity_category=EntityCategory.CONFIG,
ufp_value="humidity_settings.is_enabled",
ufp_set_method="set_humidity_status",
@@ -344,6 +375,7 @@ SENSE_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="light",
translation_key="light_sensor",
icon="mdi:brightness-5",
entity_category=EntityCategory.CONFIG,
ufp_value="light_settings.is_enabled",
ufp_set_method="set_light_status",
@@ -364,6 +396,7 @@ LIGHT_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="ssh",
translation_key="ssh_enabled",
icon="mdi:lock",
entity_registry_enabled_default=False,
entity_category=EntityCategory.CONFIG,
ufp_value="is_ssh_enabled",
@@ -373,6 +406,7 @@ LIGHT_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="status_light",
translation_key="status_light",
icon="mdi:led-on",
entity_category=EntityCategory.CONFIG,
ufp_value="light_device_settings.is_indicator_enabled",
ufp_set_method="set_status_light",
@@ -384,6 +418,7 @@ DOORLOCK_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="status_light",
translation_key="status_light",
icon="mdi:led-on",
entity_category=EntityCategory.CONFIG,
ufp_value="led_settings.is_enabled",
ufp_set_method="set_status_light",
@@ -395,6 +430,7 @@ VIEWER_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="ssh",
translation_key="ssh_enabled",
icon="mdi:lock",
entity_registry_enabled_default=False,
entity_category=EntityCategory.CONFIG,
ufp_value="is_ssh_enabled",
@@ -407,6 +443,7 @@ NVR_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="analytics_enabled",
translation_key="analytics_enabled",
icon="mdi:google-analytics",
entity_category=EntityCategory.CONFIG,
ufp_value="is_analytics_enabled",
ufp_set_method="set_anonymous_analytics",
@@ -414,6 +451,7 @@ NVR_SWITCHES: tuple[ProtectSwitchEntityDescription, ...] = (
ProtectSwitchEntityDescription(
key="insights_enabled",
translation_key="insights_enabled",
icon="mdi:magnify",
entity_category=EntityCategory.CONFIG,
ufp_value="is_insights_enabled",
ufp_set_method="set_insights",

View File

@@ -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.typing import ConfigType
from .const import CONF_VLP_FILE, DOMAIN
from .const import DOMAIN
from .services import async_setup_services
_LOGGER = logging.getLogger(__name__)
@@ -98,9 +98,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: VelbusConfigEntry) -> bool:
"""Establish connection with velbus."""
controller = Velbus(
dsn=entry.data[CONF_PORT],
entry.data[CONF_PORT],
cache_dir=hass.config.path(STORAGE_DIR, f"velbuscache-{entry.entry_id}"),
vlp_file=entry.data.get(CONF_VLP_FILE),
)
try:
await controller.connect()

View File

@@ -2,35 +2,18 @@
from __future__ import annotations
from pathlib import Path
import shutil
from typing import Any, Final
from typing import Any
import serial.tools.list_ports
import velbusaio.controller
from velbusaio.exceptions import VelbusConnectionFailed
from velbusaio.vlp_reader import VlpFile
import voluptuous as vol
from homeassistant.components.file_upload import process_uploaded_file
from homeassistant.config_entries import (
SOURCE_RECONFIGURE,
ConfigFlow,
ConfigFlowResult,
)
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
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 .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."""
from .const import CONF_TLS, DOMAIN
class VelbusConfigFlow(ConfigFlow, domain=DOMAIN):
@@ -41,15 +24,14 @@ class VelbusConfigFlow(ConfigFlow, domain=DOMAIN):
def __init__(self) -> None:
"""Initialize the velbus config flow."""
self._errors: dict[str, str] = {}
self._device: str = ""
self._vlp_file: str | None = None
self._title: str = ""
def _create_device(self) -> ConfigFlowResult:
"""Create an entry async."""
return self.async_create_entry(
title=self._title,
data={CONF_PORT: self._device, CONF_VLP_FILE: self._vlp_file},
title=self._title, data={CONF_PORT: self._device}
)
async def _test_connection(self) -> bool:
@@ -59,6 +41,7 @@ class VelbusConfigFlow(ConfigFlow, domain=DOMAIN):
await controller.connect()
await controller.stop()
except VelbusConnectionFailed:
self._errors[CONF_PORT] = "cannot_connect"
return False
return True
@@ -74,7 +57,6 @@ class VelbusConfigFlow(ConfigFlow, domain=DOMAIN):
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle network step."""
step_errors: dict[str, str] = {}
if user_input is not None:
self._title = "Velbus Network"
if user_input[CONF_TLS]:
@@ -86,8 +68,7 @@ class VelbusConfigFlow(ConfigFlow, domain=DOMAIN):
self._device += f"{user_input[CONF_HOST]}:{user_input[CONF_PORT]}"
self._async_abort_entries_match({CONF_PORT: self._device})
if await self._test_connection():
return await self.async_step_vlp()
step_errors[CONF_HOST] = "cannot_connect"
return self._create_device()
else:
user_input = {
CONF_TLS: True,
@@ -107,14 +88,13 @@ class VelbusConfigFlow(ConfigFlow, domain=DOMAIN):
),
suggested_values=user_input,
),
errors=step_errors,
errors=self._errors,
)
async def async_step_usbselect(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle usb select step."""
step_errors: dict[str, str] = {}
ports = await self.hass.async_add_executor_job(serial.tools.list_ports.comports)
list_of_ports = [
f"{p}{', s/n: ' + p.serial_number if p.serial_number else ''}"
@@ -127,8 +107,7 @@ class VelbusConfigFlow(ConfigFlow, domain=DOMAIN):
self._device = ports[list_of_ports.index(user_input[CONF_PORT])].device
self._async_abort_entries_match({CONF_PORT: self._device})
if await self._test_connection():
return await self.async_step_vlp()
step_errors[CONF_PORT] = "cannot_connect"
return self._create_device()
else:
user_input = {}
user_input[CONF_PORT] = ""
@@ -139,7 +118,7 @@ class VelbusConfigFlow(ConfigFlow, domain=DOMAIN):
vol.Schema({vol.Required(CONF_PORT): vol.In(list_of_ports)}),
suggested_values=user_input,
),
errors=step_errors,
errors=self._errors,
)
async def async_step_usb(self, discovery_info: UsbServiceInfo) -> ConfigFlowResult:
@@ -165,75 +144,3 @@ class VelbusConfigFlow(ConfigFlow, domain=DOMAIN):
step_id="discovery_confirm",
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)

View File

@@ -14,7 +14,6 @@ DOMAIN: Final = "velbus"
CONF_CONFIG_ENTRY: Final = "config_entry"
CONF_MEMO_TEXT: Final = "memo_text"
CONF_TLS: Final = "tls"
CONF_VLP_FILE: Final = "vlp_file"
SERVICE_SCAN: Final = "scan"
SERVICE_SYNC: Final = "sync_clock"

View File

@@ -3,7 +3,7 @@
"name": "Velbus",
"codeowners": ["@Cereal2nd", "@brefra"],
"config_flow": true,
"dependencies": ["usb", "file_upload"],
"dependencies": ["usb"],
"documentation": "https://www.home-assistant.io/integrations/velbus",
"integration_type": "hub",
"iot_class": "local_push",

View File

@@ -1,13 +1,11 @@
{
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
},
"error": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"no_modules": "No Velbus modules found, please check your VLP file."
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"
},
"step": {
"network": {
@@ -43,16 +41,6 @@
"usbselect": "Via USB device"
},
"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"
}
}
},

View File

@@ -6,7 +6,6 @@ from typing import Any
import voluptuous as vol
from wled import WLED, Device, WLEDConnectionError, WLEDUnsupportedVersionError
import yarl
from homeassistant.components import onboarding
from homeassistant.config_entries import (
@@ -25,15 +24,6 @@ from .const import CONF_KEEP_MAIN_LIGHT, DEFAULT_KEEP_MAIN_LIGHT, DOMAIN
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):
"""Handle a WLED config flow."""
@@ -56,9 +46,8 @@ class WLEDFlowHandler(ConfigFlow, domain=DOMAIN):
errors = {}
if user_input is not None:
host = _normalize_host(user_input[CONF_HOST])
try:
device = await self._async_get_device(host)
device = await self._async_get_device(user_input[CONF_HOST])
except WLEDUnsupportedVersionError:
errors["base"] = "unsupported_version"
except WLEDConnectionError:
@@ -78,12 +67,16 @@ class WLEDFlowHandler(ConfigFlow, domain=DOMAIN):
)
return self.async_update_reload_and_abort(
entry,
data_updates={CONF_HOST: host},
data_updates=user_input,
)
self._abort_if_unique_id_configured(updates={CONF_HOST: host})
self._abort_if_unique_id_configured(
updates={CONF_HOST: user_input[CONF_HOST]}
)
return self.async_create_entry(
title=device.info.name,
data={CONF_HOST: host},
data={
CONF_HOST: user_input[CONF_HOST],
},
)
data_schema = vol.Schema({vol.Required(CONF_HOST): str})
if self.source == SOURCE_RECONFIGURE:

View File

@@ -81,11 +81,10 @@ class XboxUpdateCoordinator(DataUpdateCoordinator[XboxData]):
_LOGGER,
config_entry=config_entry,
name=DOMAIN,
update_interval=timedelta(seconds=15),
update_interval=timedelta(seconds=10),
)
self.data = XboxData()
self.current_friends: set[str] = set()
self.title_data: dict[str, Title] = {}
async def _async_setup(self) -> None:
"""Set up coordinator."""
@@ -218,6 +217,7 @@ class XboxUpdateCoordinator(DataUpdateCoordinator[XboxData]):
)
# retrieve title details
title_data: dict[str, Title] = {}
for person in presence_data.values():
if presence_detail := next(
(
@@ -227,12 +227,6 @@ class XboxUpdateCoordinator(DataUpdateCoordinator[XboxData]):
),
None,
):
if (
person.xuid in self.title_data
and presence_detail.title_id
== self.title_data[person.xuid].title_id
):
continue
try:
title = await self.client.titlehub.get_title_info(
presence_detail.title_id
@@ -256,9 +250,7 @@ class XboxUpdateCoordinator(DataUpdateCoordinator[XboxData]):
translation_domain=DOMAIN,
translation_key="request_exception",
) from e
self.title_data[person.xuid] = title.titles[0]
else:
self.title_data.pop(person.xuid, None)
title_data[person.xuid] = title.titles[0]
person.last_seen_date_time_utc = self.last_seen_timestamp(person)
if (
self.current_friends - (new_friends := set(presence_data))
@@ -267,7 +259,7 @@ class XboxUpdateCoordinator(DataUpdateCoordinator[XboxData]):
self.remove_stale_devices(new_friends)
self.current_friends = new_friends
return XboxData(new_console_data, presence_data, self.title_data)
return XboxData(new_console_data, presence_data, title_data)
def last_seen_timestamp(self, person: Person) -> datetime | None:
"""Returns the most recent of two timestamps."""

View File

@@ -505,7 +505,7 @@
},
"arcam_fmj": {
"name": "Arcam FMJ Receivers",
"integration_type": "device",
"integration_type": "hub",
"config_flow": true,
"iot_class": "local_polling"
},
@@ -663,7 +663,7 @@
},
"baf": {
"name": "Big Ass Fans",
"integration_type": "device",
"integration_type": "hub",
"config_flow": true,
"iot_class": "local_push"
},
@@ -778,7 +778,7 @@
},
"bluemaestro": {
"name": "BlueMaestro",
"integration_type": "device",
"integration_type": "hub",
"config_flow": true,
"iot_class": "local_push"
},
@@ -1279,7 +1279,7 @@
"name": "Denon Network Receivers"
},
"denonavr": {
"integration_type": "device",
"integration_type": "hub",
"config_flow": true,
"iot_class": "local_push",
"name": "Denon AVR Network Receivers"

View File

@@ -338,8 +338,11 @@ class EntityTriggerBase(Trigger):
self._target = config.target
def is_valid_transition(self, from_state: State, to_state: State) -> bool:
"""Check if the origin state is not an expected target states."""
return not self.is_valid_state(from_state)
"""Check if the origin state is valid and the state has changed."""
if from_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN):
return False
return from_state.state != to_state.state
@abc.abstractmethod
def is_valid_state(self, state: State) -> bool:
@@ -390,12 +393,11 @@ class EntityTriggerBase(Trigger):
from_state = event.data["old_state"]
to_state = event.data["new_state"]
# The trigger should never fire if the previous state was not a valid state
if not from_state or from_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN):
if not from_state or not to_state:
return
# The trigger should never fire if the new state is not valid
if not to_state or not self.is_valid_state(to_state):
if not self.is_valid_state(to_state):
return
# The trigger should never fire if the transition is not valid
@@ -446,6 +448,9 @@ class ConditionalEntityStateTriggerBase(EntityTriggerBase):
def is_valid_transition(self, from_state: State, to_state: State) -> bool:
"""Check if the origin state matches the expected ones."""
if not super().is_valid_transition(from_state, to_state):
return False
return from_state.state in self._from_states
def is_valid_state(self, state: State) -> bool:
@@ -459,6 +464,15 @@ class EntityStateAttributeTriggerBase(EntityTriggerBase):
_attribute: str
_attribute_to_state: str
def is_valid_transition(self, from_state: State, to_state: State) -> bool:
"""Check if the origin state is valid and the state has changed."""
if from_state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN):
return False
return from_state.attributes.get(self._attribute) != to_state.attributes.get(
self._attribute
)
def is_valid_state(self, state: State) -> bool:
"""Check if the new state attribute matches the expected one."""
return state.attributes.get(self._attribute) == self._attribute_to_state

View File

@@ -2,10 +2,18 @@
from collections.abc import Mapping
from enum import Enum
from functools import partial
from typing import Any, Never
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 ConfigType = dict[str, Any]
type DiscoveryInfoType = dict[str, Any]
@@ -27,3 +35,32 @@ class UndefinedType(Enum):
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())

View File

@@ -492,15 +492,15 @@ filterwarnings = [
# -- fixed, waiting for release / update
# https://github.com/httplib2/httplib2/pull/226 - >=0.21.0
"ignore:ssl.PROTOCOL_TLS is deprecated:DeprecationWarning:httplib2",
# https://github.com/BerriAI/litellm/pull/17657 - >1.80.9
"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/ReactiveX/RxPY/pull/716 - >4.0.4
"ignore:datetime.*utcfromtimestamp\\(\\) is deprecated and scheduled for removal:DeprecationWarning:reactivex.internal.constants",
# 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.miioprotocol",
# 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",
# 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
"ignore:verify_ssl is deprecated, use ssl=False instead:DeprecationWarning:aiohttp.client",
@@ -518,8 +518,6 @@ filterwarnings = [
# https://pypi.org/project/emulated-roku/ - v0.3.0 - 2023-12-19
# https://github.com/martonperei/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
"ignore:with timeout\\(\\) is deprecated:DeprecationWarning:foobot_async",
# https://pypi.org/project/motionblindsble/ - v0.1.3 - 2024-11-12
@@ -567,18 +565,13 @@ filterwarnings = [
"ignore:pkg_resources is deprecated as an API:UserWarning:pybotvac.version",
# - SyntaxWarning - is with literal
# 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
"ignore:\"is.*\" with '.*' literal:SyntaxWarning:.*opuslib.api.decoder",
# https://pypi.org/project/pyiss/ - v1.0.1 - 2016-12-19
"ignore:\"is.*\" with '.*' literal:SyntaxWarning:.*pyiss",
"ignore:\"is.*\" with '.*' literal:SyntaxWarning:importlib._bootstrap",
# - SyntaxWarning - return in finally
# 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
"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",
"ignore:'return' in a 'finally' block:SyntaxWarning:importlib._bootstrap",
# -- New in Python 3.13
# https://github.com/youknowone/python-deadlib - Backports for aifc, telnetlib
@@ -604,8 +597,6 @@ filterwarnings = [
"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
"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
"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

12
requirements_all.txt generated
View File

@@ -1102,7 +1102,7 @@ google-nest-sdm==9.1.2
google-photos-library-api==0.12.1
# homeassistant.components.google_air_quality
google_air_quality_api==2.0.2
google_air_quality_api==2.0.0
# homeassistant.components.slide
# homeassistant.components.slide_local
@@ -1249,7 +1249,7 @@ ibeacon-ble==1.2.0
# homeassistant.components.local_calendar
# homeassistant.components.local_todo
# homeassistant.components.remote_calendar
ical==12.1.1
ical==11.1.0
# homeassistant.components.caldav
icalendar==6.3.1
@@ -1654,7 +1654,7 @@ open-meteo==0.3.2
# homeassistant.components.open_router
# homeassistant.components.openai_conversation
openai==2.11.0
openai==2.9.0
# homeassistant.components.openerz
openerz-api==0.3.0
@@ -2160,7 +2160,7 @@ pykwb==0.0.8
pylacrosse==0.4
# homeassistant.components.lamarzocco
pylamarzocco==2.2.4
pylamarzocco==2.2.3
# homeassistant.components.lastfm
pylast==5.1.0
@@ -2235,7 +2235,7 @@ pynina==0.3.6
pynintendoauth==1.0.0
# homeassistant.components.nintendo_parental_controls
pynintendoparental==2.1.1
pynintendoparental==2.1.0
# homeassistant.components.nobo_hub
pynobo==1.8.1
@@ -2418,7 +2418,7 @@ pysmappee==0.2.29
pysmarlaapi==0.9.2
# homeassistant.components.smartthings
pysmartthings==3.5.1
pysmartthings==3.5.0
# homeassistant.components.smarty
pysmarty2==0.10.3

View File

@@ -978,7 +978,7 @@ google-nest-sdm==9.1.2
google-photos-library-api==0.12.1
# homeassistant.components.google_air_quality
google_air_quality_api==2.0.2
google_air_quality_api==2.0.0
# homeassistant.components.slide
# homeassistant.components.slide_local
@@ -1101,7 +1101,7 @@ ibeacon-ble==1.2.0
# homeassistant.components.local_calendar
# homeassistant.components.local_todo
# homeassistant.components.remote_calendar
ical==12.1.1
ical==11.1.0
# homeassistant.components.caldav
icalendar==6.3.1
@@ -1437,7 +1437,7 @@ open-meteo==0.3.2
# homeassistant.components.open_router
# homeassistant.components.openai_conversation
openai==2.11.0
openai==2.9.0
# homeassistant.components.openerz
openerz-api==0.3.0
@@ -1819,7 +1819,7 @@ pykrakenapi==0.1.8
pykulersky==0.5.8
# homeassistant.components.lamarzocco
pylamarzocco==2.2.4
pylamarzocco==2.2.3
# homeassistant.components.lastfm
pylast==5.1.0
@@ -1882,7 +1882,7 @@ pynina==0.3.6
pynintendoauth==1.0.0
# homeassistant.components.nintendo_parental_controls
pynintendoparental==2.1.1
pynintendoparental==2.1.0
# homeassistant.components.nobo_hub
pynobo==1.8.1
@@ -2035,7 +2035,7 @@ pysmappee==0.2.29
pysmarlaapi==0.9.2
# homeassistant.components.smartthings
pysmartthings==3.5.1
pysmartthings==3.5.0
# homeassistant.components.smarty
pysmarty2==0.10.3

View File

@@ -180,14 +180,6 @@ TEST_PLAYBACK_METADATA = PlaybackContentMetadata(
track=1,
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_PROGRESS = PlaybackProgress(progress=123)
TEST_PLAYBACK_STATE_PAUSED = RenderingState(value="paused")

View File

@@ -100,7 +100,6 @@ from .const import (
TEST_OVERLAY_OFFSET_VOLUME_TTS,
TEST_PLAYBACK_ERROR,
TEST_PLAYBACK_METADATA,
TEST_PLAYBACK_METADATA_VIDEO,
TEST_PLAYBACK_PROGRESS,
TEST_PLAYBACK_STATE_PAUSED,
TEST_PLAYBACK_STATE_PLAYING,
@@ -434,36 +433,6 @@ async def test_async_update_source_change(
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(
hass: HomeAssistant,
integration: None,
@@ -850,7 +819,7 @@ async def test_async_select_source(
audio_source_call: int,
video_source_call: int,
) -> None:
"""Test async_select_source with an invalid source and valid audio and video sources."""
"""Test async_select_source with an invalid source."""
with expected_result:
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,

View File

@@ -85,7 +85,7 @@ def mock_config_fixture():
data={
CONF_USERNAME: "test_user",
CONF_PASSWORD: "Password",
"hardware_id": "Home Assistant",
"device_id": "Home Assistant",
"uid": "BlinkCamera_e1233333e2-0909-09cd-777a-123456789012",
"token": "A_token",
"unique_id": "an_email@email.com",
@@ -95,5 +95,5 @@ def mock_config_fixture():
"account_id": 654321,
},
entry_id=str(uuid4()),
version=4,
version=3,
)

View File

@@ -28,7 +28,7 @@
'data': dict({
'account_id': 654321,
'client_id': 123456,
'hardware_id': 'Home Assistant',
'device_id': 'Home Assistant',
'host': 'u034.immedia-semi.com',
'password': '**REDACTED**',
'region_id': 'u034',
@@ -52,7 +52,7 @@
]),
'title': 'Mock Title',
'unique_id': None,
'version': 4,
'version': 3,
}),
})
# ---

View File

@@ -113,32 +113,3 @@ async def test_migrate(
await hass.async_block_till_done()
entry = hass.config_entries.async_get_entry(mock_config_entry.entry_id)
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"

View File

@@ -12,11 +12,11 @@ from bluecurrent_api.exceptions import (
import pytest
from voluptuous import MultipleInvalid
from homeassistant.components.blue_current import async_setup_entry
from homeassistant.components.blue_current.const import (
from homeassistant.components.blue_current import (
CHARGING_CARD_ID,
DOMAIN,
SERVICE_START_CHARGE_SESSION,
async_setup_entry,
)
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import CONF_DEVICE_ID, Platform

View File

@@ -12,13 +12,11 @@ from bring_api import (
from freezegun.api import FrozenDateTimeFactory
import pytest
from homeassistant.components.bring import async_setup_entry
from homeassistant.components.bring.const import DOMAIN
from homeassistant.config_entries import (
SOURCE_REAUTH,
ConfigEntryDisabler,
ConfigEntryState,
)
from homeassistant.config_entries import ConfigEntryDisabler, ConfigEntryState
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr
from .conftest import UUID
@@ -54,11 +52,11 @@ async def test_load_unload(
@pytest.mark.parametrize(
("exception", "status", "reauth_triggered"),
("exception", "status"),
[
(BringRequestException, ConfigEntryState.SETUP_RETRY, False),
(BringAuthException, ConfigEntryState.SETUP_ERROR, True),
(BringParseException, ConfigEntryState.SETUP_RETRY, False),
(BringRequestException, ConfigEntryState.SETUP_RETRY),
(BringAuthException, ConfigEntryState.SETUP_ERROR),
(BringParseException, ConfigEntryState.SETUP_RETRY),
],
)
async def test_init_failure(
@@ -66,7 +64,6 @@ async def test_init_failure(
mock_bring_client: AsyncMock,
status: ConfigEntryState,
exception: Exception,
reauth_triggered: bool,
bring_config_entry: MockConfigEntry,
) -> None:
"""Test an initialization error on integration load."""
@@ -74,14 +71,28 @@ async def test_init_failure(
await setup_integration(hass, bring_config_entry)
assert bring_config_entry.state == status
assert (
any(
flow
for flow in hass.config_entries.flow.async_progress()
if flow["context"]["source"] == SOURCE_REAUTH
)
is reauth_triggered
)
@pytest.mark.parametrize(
("exception", "expected"),
[
(BringRequestException, ConfigEntryNotReady),
(BringAuthException, ConfigEntryAuthFailed),
(BringParseException, ConfigEntryNotReady),
],
)
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])

View File

@@ -0,0 +1,192 @@
"""Test button trigger."""
from collections.abc import Generator
from unittest.mock import patch
import pytest
from homeassistant.const import (
ATTR_LABEL_ID,
CONF_ENTITY_ID,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
from homeassistant.core import HomeAssistant, ServiceCall
from tests.components import (
StateDescription,
arm_trigger,
parametrize_target_entities,
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_buttons(hass: HomeAssistant) -> list[str]:
"""Create multiple button entities associated with different targets."""
return (await target_entities(hass, "button"))["included"]
@pytest.mark.parametrize("trigger_key", ["button.pressed"])
async def test_button_triggers_gated_by_labs_flag(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture, trigger_key: str
) -> None:
"""Test the button 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("button"),
)
@pytest.mark.parametrize(
("trigger", "states"),
[
(
"button.pressed",
[
{"included": {"state": None, "attributes": {}}, "count": 0},
{
"included": {
"state": "2021-01-01T23:59:59+00:00",
"attributes": {},
},
"count": 0,
},
{
"included": {
"state": "2022-01-01T23:59:59+00:00",
"attributes": {},
},
"count": 1,
},
],
),
(
"button.pressed",
[
{"included": {"state": "foo", "attributes": {}}, "count": 0},
{
"included": {
"state": "2021-01-01T23:59:59+00:00",
"attributes": {},
},
"count": 1,
},
{
"included": {
"state": "2022-01-01T23:59:59+00:00",
"attributes": {},
},
"count": 1,
},
],
),
(
"button.pressed",
[
{
"included": {"state": STATE_UNAVAILABLE, "attributes": {}},
"count": 0,
},
{
"included": {
"state": "2021-01-01T23:59:59+00:00",
"attributes": {},
},
"count": 0,
},
{
"included": {
"state": "2022-01-01T23:59:59+00:00",
"attributes": {},
},
"count": 1,
},
{
"included": {"state": STATE_UNAVAILABLE, "attributes": {}},
"count": 0,
},
],
),
(
"button.pressed",
[
{"included": {"state": STATE_UNKNOWN, "attributes": {}}, "count": 0},
{
"included": {
"state": "2021-01-01T23:59:59+00:00",
"attributes": {},
},
"count": 1,
},
{
"included": {
"state": "2022-01-01T23:59:59+00:00",
"attributes": {},
},
"count": 1,
},
{"included": {"state": STATE_UNKNOWN, "attributes": {}}, "count": 0},
],
),
],
)
async def test_button_state_trigger_behavior_any(
hass: HomeAssistant,
service_calls: list[ServiceCall],
target_buttons: list[str],
trigger_target_config: dict,
entity_id: str,
entities_in_target: int,
trigger: str,
states: list[StateDescription],
) -> None:
"""Test that the button state trigger fires when any button state changes to a specific state."""
other_entity_ids = set(target_buttons) - {entity_id}
# Set all buttons, including the tested button, to the initial state
for eid in target_buttons:
set_or_remove_state(hass, eid, states[0]["included"])
await hass.async_block_till_done()
await arm_trigger(hass, trigger, None, 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 buttons 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()

View File

@@ -229,9 +229,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -270,7 +268,6 @@
'device_class': 'power',
'friendly_name': 'MIN123456 Battery 1 charging W',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -286,9 +283,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -327,7 +322,6 @@
'device_class': 'power',
'friendly_name': 'MIN123456 Battery 1 discharging W',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -343,9 +337,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -384,7 +376,6 @@
'device_class': 'power',
'friendly_name': 'MIN123456 Battery 2 charging W',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -400,9 +391,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -441,7 +430,6 @@
'device_class': 'power',
'friendly_name': 'MIN123456 Battery 2 discharging W',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -742,9 +730,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -783,7 +769,6 @@
'device_class': 'power',
'friendly_name': 'MIN123456 Export power',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -913,9 +898,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -954,7 +937,6 @@
'device_class': 'power',
'friendly_name': 'MIN123456 Import power',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -1078,9 +1060,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -1119,7 +1099,6 @@
'device_class': 'power',
'friendly_name': 'MIN123456 Input 1 wattage',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -1243,9 +1222,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -1284,7 +1261,6 @@
'device_class': 'power',
'friendly_name': 'MIN123456 Input 2 wattage',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -1408,9 +1384,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -1449,7 +1423,6 @@
'device_class': 'power',
'friendly_name': 'MIN123456 Input 3 wattage',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -1573,9 +1546,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -1614,7 +1585,6 @@
'device_class': 'power',
'friendly_name': 'MIN123456 Input 4 wattage',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -1630,9 +1600,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -1671,7 +1639,6 @@
'device_class': 'power',
'friendly_name': 'MIN123456 Internal wattage',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -2770,9 +2737,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -2811,7 +2776,6 @@
'device_class': 'power',
'friendly_name': 'MIN123456 Local load power',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -2827,9 +2791,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -2868,7 +2830,6 @@
'device_class': 'power',
'friendly_name': 'MIN123456 Output power',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -2995,9 +2956,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -3036,7 +2995,6 @@
'device_class': 'power',
'friendly_name': 'MIN123456 Self power',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -3160,9 +3118,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -3201,7 +3157,6 @@
'device_class': 'power',
'friendly_name': 'MIN123456 System power',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -3658,9 +3613,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -3699,7 +3652,6 @@
'device_class': 'power',
'friendly_name': 'Test Plant Total Maximum power',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -3984,9 +3936,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -4025,7 +3975,6 @@
'device_class': 'power',
'friendly_name': 'Test Plant Total Maximum power',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -4423,9 +4372,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -4464,7 +4411,6 @@
'device_class': 'power',
'friendly_name': 'TLX123456 Battery 1 charging W',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -4480,9 +4426,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -4521,7 +4465,6 @@
'device_class': 'power',
'friendly_name': 'TLX123456 Battery 1 discharging W',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -4537,9 +4480,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -4578,7 +4519,6 @@
'device_class': 'power',
'friendly_name': 'TLX123456 Battery 2 charging W',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -4594,9 +4534,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -4635,7 +4573,6 @@
'device_class': 'power',
'friendly_name': 'TLX123456 Battery 2 discharging W',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -4936,9 +4873,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -4977,7 +4912,6 @@
'device_class': 'power',
'friendly_name': 'TLX123456 Export power',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -5107,9 +5041,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -5148,7 +5080,6 @@
'device_class': 'power',
'friendly_name': 'TLX123456 Import power',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -5272,9 +5203,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -5313,7 +5242,6 @@
'device_class': 'power',
'friendly_name': 'TLX123456 Input 1 wattage',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -5437,9 +5365,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -5478,7 +5404,6 @@
'device_class': 'power',
'friendly_name': 'TLX123456 Input 2 wattage',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -5602,9 +5527,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -5643,7 +5566,6 @@
'device_class': 'power',
'friendly_name': 'TLX123456 Input 3 wattage',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -5767,9 +5689,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -5808,7 +5728,6 @@
'device_class': 'power',
'friendly_name': 'TLX123456 Input 4 wattage',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -5824,9 +5743,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -5865,7 +5782,6 @@
'device_class': 'power',
'friendly_name': 'TLX123456 Internal wattage',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -6964,9 +6880,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -7005,7 +6919,6 @@
'device_class': 'power',
'friendly_name': 'TLX123456 Local load power',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -7021,9 +6934,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -7062,7 +6973,6 @@
'device_class': 'power',
'friendly_name': 'TLX123456 Output power',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -7189,9 +7099,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -7230,7 +7138,6 @@
'device_class': 'power',
'friendly_name': 'TLX123456 Self power',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,
@@ -7354,9 +7261,7 @@
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
@@ -7395,7 +7300,6 @@
'device_class': 'power',
'friendly_name': 'TLX123456 System power',
'icon': 'mdi:solar-power',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfPower.WATT: 'W'>,
}),
'context': <ANY>,

View File

@@ -114,7 +114,6 @@ async def integration_fixture(
"light_sensor",
"microwave_oven",
"mock_lock",
"mock_thermostat",
"mounted_dimmable_load_control_fixture",
"multi_endpoint_light",
"occupancy_sensor",

View File

@@ -1,526 +0,0 @@
{
"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": []
}

View File

@@ -391,102 +391,6 @@
'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]
EntityRegistryEntrySnapshot({
'aliases': set({
@@ -732,150 +636,6 @@
'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]
EntityRegistryEntrySnapshot({
'aliases': set({

View File

@@ -2388,104 +2388,6 @@
'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]
EntityRegistryEntrySnapshot({
'aliases': set({

View File

@@ -325,77 +325,6 @@
'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]
EntityRegistryEntrySnapshot({
'aliases': set({

View File

@@ -241,7 +241,10 @@
'capabilities': dict({
'options': list([
'normal',
'vacation',
'privacy',
'no_remote_lock_unlock',
'passage',
]),
}),
'config_entry_id': <ANY>,
@@ -279,7 +282,10 @@
'friendly_name': 'Aqara Smart Lock U200 Operating mode',
'options': list([
'normal',
'vacation',
'privacy',
'no_remote_lock_unlock',
'passage',
]),
}),
'context': <ANY>,
@@ -678,7 +684,10 @@
'capabilities': dict({
'options': list([
'normal',
'vacation',
'privacy',
'no_remote_lock_unlock',
'passage',
]),
}),
'config_entry_id': <ANY>,
@@ -716,7 +725,10 @@
'friendly_name': 'Mock Door Lock Operating mode',
'options': list([
'normal',
'vacation',
'privacy',
'no_remote_lock_unlock',
'passage',
]),
}),
'context': <ANY>,
@@ -857,7 +869,10 @@
'capabilities': dict({
'options': list([
'normal',
'vacation',
'privacy',
'no_remote_lock_unlock',
'passage',
]),
}),
'config_entry_id': <ANY>,
@@ -895,7 +910,10 @@
'friendly_name': 'Mock Door Lock with unbolt Operating mode',
'options': list([
'normal',
'vacation',
'privacy',
'no_remote_lock_unlock',
'passage',
]),
}),
'context': <ANY>,
@@ -2505,7 +2523,10 @@
'capabilities': dict({
'options': list([
'normal',
'vacation',
'privacy',
'no_remote_lock_unlock',
'passage',
]),
}),
'config_entry_id': <ANY>,
@@ -2543,7 +2564,10 @@
'friendly_name': 'Mock Lock Operating mode',
'options': list([
'normal',
'vacation',
'privacy',
'no_remote_lock_unlock',
'passage',
]),
}),
'context': <ANY>,
@@ -2615,63 +2639,6 @@
'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]
EntityRegistryEntrySnapshot({
'aliases': set({
@@ -3759,8 +3726,10 @@
'capabilities': dict({
'options': list([
'normal',
'vacation',
'privacy',
'no_remote_lock_unlock',
'passage',
]),
}),
'config_entry_id': <ANY>,
@@ -3798,8 +3767,10 @@
'friendly_name': 'Secuyou Smart Lock Operating mode',
'options': list([
'normal',
'vacation',
'privacy',
'no_remote_lock_unlock',
'passage',
]),
}),
'context': <ANY>,

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