mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Add additional entities for Shelly BLU TRV (#135244)
* Add valve position sensor * Add valve position and external sensor temperature numbers * Fix method name * Better name * Add remove condition * Add calibration binary sensor * Add battery and signal strength sensors * Remove condition from ShellyRpcEntity * Typo * Add get_entity_class helper * Add tests * Use snapshots in tests
This commit is contained in:
parent
d7ec99de7d
commit
11d44e608b
@ -15,11 +15,12 @@ from homeassistant.components.binary_sensor import (
|
||||
)
|
||||
from homeassistant.const import STATE_ON, EntityCategory
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import CONNECTION_BLUETOOTH, DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.restore_state import RestoreEntity
|
||||
|
||||
from .const import CONF_SLEEP_PERIOD
|
||||
from .coordinator import ShellyConfigEntry
|
||||
from .coordinator import ShellyConfigEntry, ShellyRpcCoordinator
|
||||
from .entity import (
|
||||
BlockEntityDescription,
|
||||
RestEntityDescription,
|
||||
@ -59,6 +60,36 @@ class RestBinarySensorDescription(RestEntityDescription, BinarySensorEntityDescr
|
||||
"""Class to describe a REST binary sensor."""
|
||||
|
||||
|
||||
class RpcBinarySensor(ShellyRpcAttributeEntity, BinarySensorEntity):
|
||||
"""Represent a RPC binary sensor entity."""
|
||||
|
||||
entity_description: RpcBinarySensorDescription
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return true if RPC sensor state is on."""
|
||||
return bool(self.attribute_value)
|
||||
|
||||
|
||||
class RpcBluTrvBinarySensor(RpcBinarySensor):
|
||||
"""Represent a RPC BluTrv binary sensor."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: ShellyRpcCoordinator,
|
||||
key: str,
|
||||
attribute: str,
|
||||
description: RpcBinarySensorDescription,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
|
||||
super().__init__(coordinator, key, attribute, description)
|
||||
ble_addr: str = coordinator.device.config[key]["addr"]
|
||||
self._attr_device_info = DeviceInfo(
|
||||
connections={(CONNECTION_BLUETOOTH, ble_addr)}
|
||||
)
|
||||
|
||||
|
||||
SENSORS: dict[tuple[str, str], BlockBinarySensorDescription] = {
|
||||
("device", "overtemp"): BlockBinarySensorDescription(
|
||||
key="device|overtemp",
|
||||
@ -232,6 +263,15 @@ RPC_SENSORS: Final = {
|
||||
sub_key="value",
|
||||
has_entity_name=True,
|
||||
),
|
||||
"calibration": RpcBinarySensorDescription(
|
||||
key="blutrv",
|
||||
sub_key="errors",
|
||||
name="Calibration",
|
||||
device_class=BinarySensorDeviceClass.PROBLEM,
|
||||
value=lambda status, _: False if status is None else "not_calibrated" in status,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_class=RpcBluTrvBinarySensor,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@ -320,17 +360,6 @@ class RestBinarySensor(ShellyRestAttributeEntity, BinarySensorEntity):
|
||||
return bool(self.attribute_value)
|
||||
|
||||
|
||||
class RpcBinarySensor(ShellyRpcAttributeEntity, BinarySensorEntity):
|
||||
"""Represent a RPC binary sensor entity."""
|
||||
|
||||
entity_description: RpcBinarySensorDescription
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Return true if RPC sensor state is on."""
|
||||
return bool(self.attribute_value)
|
||||
|
||||
|
||||
class BlockSleepingBinarySensor(
|
||||
ShellySleepingBlockAttributeEntity, BinarySensorEntity, RestoreEntity
|
||||
):
|
||||
|
@ -196,10 +196,16 @@ def async_setup_rpc_attribute_entities(
|
||||
elif description.use_polling_coordinator:
|
||||
if not sleep_period:
|
||||
entities.append(
|
||||
sensor_class(polling_coordinator, key, sensor_id, description)
|
||||
get_entity_class(sensor_class, description)(
|
||||
polling_coordinator, key, sensor_id, description
|
||||
)
|
||||
)
|
||||
else:
|
||||
entities.append(sensor_class(coordinator, key, sensor_id, description))
|
||||
entities.append(
|
||||
get_entity_class(sensor_class, description)(
|
||||
coordinator, key, sensor_id, description
|
||||
)
|
||||
)
|
||||
if not entities:
|
||||
return
|
||||
|
||||
@ -232,7 +238,9 @@ def async_restore_rpc_attribute_entities(
|
||||
|
||||
if description := sensors.get(attribute):
|
||||
entities.append(
|
||||
sensor_class(coordinator, key, attribute, description, entry)
|
||||
get_entity_class(sensor_class, description)(
|
||||
coordinator, key, attribute, description, entry
|
||||
)
|
||||
)
|
||||
|
||||
if not entities:
|
||||
@ -293,6 +301,7 @@ class RpcEntityDescription(EntityDescription):
|
||||
supported: Callable = lambda _: False
|
||||
unit: Callable[[dict], str | None] | None = None
|
||||
options_fn: Callable[[dict], list[str]] | None = None
|
||||
entity_class: Callable | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@ -673,3 +682,13 @@ class ShellySleepingRpcAttributeEntity(ShellyRpcAttributeEntity):
|
||||
"Entity %s comes from a sleeping device, update is not possible",
|
||||
self.entity_id,
|
||||
)
|
||||
|
||||
|
||||
def get_entity_class(
|
||||
sensor_class: Callable, description: RpcEntityDescription
|
||||
) -> Callable:
|
||||
"""Return entity class."""
|
||||
if description.entity_class is not None:
|
||||
return description.entity_class
|
||||
|
||||
return sensor_class
|
||||
|
@ -12,6 +12,9 @@
|
||||
}
|
||||
},
|
||||
"number": {
|
||||
"external_temperature": {
|
||||
"default": "mdi:thermometer-check"
|
||||
},
|
||||
"valve_position": {
|
||||
"default": "mdi:pipe-valve"
|
||||
}
|
||||
@ -29,6 +32,9 @@
|
||||
"tilt": {
|
||||
"default": "mdi:angle-acute"
|
||||
},
|
||||
"valve_position": {
|
||||
"default": "mdi:pipe-valve"
|
||||
},
|
||||
"valve_status": {
|
||||
"default": "mdi:valve"
|
||||
}
|
||||
|
@ -18,9 +18,10 @@ from homeassistant.components.number import (
|
||||
NumberMode,
|
||||
RestoreNumber,
|
||||
)
|
||||
from homeassistant.const import PERCENTAGE, EntityCategory
|
||||
from homeassistant.const import PERCENTAGE, EntityCategory, UnitOfTemperature
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers.device_registry import CONNECTION_BLUETOOTH, DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.entity_registry import RegistryEntry
|
||||
|
||||
@ -57,6 +58,74 @@ class RpcNumberDescription(RpcEntityDescription, NumberEntityDescription):
|
||||
min_fn: Callable[[dict], float] | None = None
|
||||
step_fn: Callable[[dict], float] | None = None
|
||||
mode_fn: Callable[[dict], NumberMode] | None = None
|
||||
method: str
|
||||
method_params_fn: Callable[[int, float], dict]
|
||||
|
||||
|
||||
class RpcNumber(ShellyRpcAttributeEntity, NumberEntity):
|
||||
"""Represent a RPC number entity."""
|
||||
|
||||
entity_description: RpcNumberDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: ShellyRpcCoordinator,
|
||||
key: str,
|
||||
attribute: str,
|
||||
description: RpcNumberDescription,
|
||||
) -> None:
|
||||
"""Initialize sensor."""
|
||||
super().__init__(coordinator, key, attribute, description)
|
||||
|
||||
if description.max_fn is not None:
|
||||
self._attr_native_max_value = description.max_fn(
|
||||
coordinator.device.config[key]
|
||||
)
|
||||
if description.min_fn is not None:
|
||||
self._attr_native_min_value = description.min_fn(
|
||||
coordinator.device.config[key]
|
||||
)
|
||||
if description.step_fn is not None:
|
||||
self._attr_native_step = description.step_fn(coordinator.device.config[key])
|
||||
if description.mode_fn is not None:
|
||||
self._attr_mode = description.mode_fn(coordinator.device.config[key])
|
||||
|
||||
@property
|
||||
def native_value(self) -> float | None:
|
||||
"""Return value of number."""
|
||||
if TYPE_CHECKING:
|
||||
assert isinstance(self.attribute_value, float | None)
|
||||
|
||||
return self.attribute_value
|
||||
|
||||
async def async_set_native_value(self, value: float) -> None:
|
||||
"""Change the value."""
|
||||
if TYPE_CHECKING:
|
||||
assert isinstance(self._id, int)
|
||||
|
||||
await self.call_rpc(
|
||||
self.entity_description.method,
|
||||
self.entity_description.method_params_fn(self._id, value),
|
||||
)
|
||||
|
||||
|
||||
class RpcBluTrvNumber(RpcNumber):
|
||||
"""Represent a RPC BluTrv number."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: ShellyRpcCoordinator,
|
||||
key: str,
|
||||
attribute: str,
|
||||
description: RpcNumberDescription,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
|
||||
super().__init__(coordinator, key, attribute, description)
|
||||
ble_addr: str = coordinator.device.config[key]["addr"]
|
||||
self._attr_device_info = DeviceInfo(
|
||||
connections={(CONNECTION_BLUETOOTH, ble_addr)}
|
||||
)
|
||||
|
||||
|
||||
NUMBERS: dict[tuple[str, str], BlockNumberDescription] = {
|
||||
@ -78,6 +147,25 @@ NUMBERS: dict[tuple[str, str], BlockNumberDescription] = {
|
||||
|
||||
|
||||
RPC_NUMBERS: Final = {
|
||||
"external_temperature": RpcNumberDescription(
|
||||
key="blutrv",
|
||||
sub_key="current_C",
|
||||
translation_key="external_temperature",
|
||||
name="External temperature",
|
||||
native_min_value=-50,
|
||||
native_max_value=50,
|
||||
native_step=0.1,
|
||||
mode=NumberMode.BOX,
|
||||
entity_category=EntityCategory.CONFIG,
|
||||
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
|
||||
method="BluTRV.Call",
|
||||
method_params_fn=lambda idx, value: {
|
||||
"id": idx,
|
||||
"method": "Trv.SetExternalTemperature",
|
||||
"params": {"id": 0, "t_C": value},
|
||||
},
|
||||
entity_class=RpcBluTrvNumber,
|
||||
),
|
||||
"number": RpcNumberDescription(
|
||||
key="number",
|
||||
sub_key="value",
|
||||
@ -92,6 +180,28 @@ RPC_NUMBERS: Final = {
|
||||
unit=lambda config: config["meta"]["ui"]["unit"]
|
||||
if config["meta"]["ui"]["unit"]
|
||||
else None,
|
||||
method="Number.Set",
|
||||
method_params_fn=lambda idx, value: {"id": idx, "value": value},
|
||||
),
|
||||
"valve_position": RpcNumberDescription(
|
||||
key="blutrv",
|
||||
sub_key="pos",
|
||||
translation_key="valve_position",
|
||||
name="Valve position",
|
||||
native_min_value=0,
|
||||
native_max_value=100,
|
||||
native_step=1,
|
||||
mode=NumberMode.SLIDER,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
method="BluTRV.Call",
|
||||
method_params_fn=lambda idx, value: {
|
||||
"id": idx,
|
||||
"method": "Trv.SetPosition",
|
||||
"params": {"id": 0, "pos": value},
|
||||
},
|
||||
removal_condition=lambda config, _status, key: config[key].get("enable", True)
|
||||
is True,
|
||||
entity_class=RpcBluTrvNumber,
|
||||
),
|
||||
}
|
||||
|
||||
@ -190,44 +300,3 @@ class BlockSleepingNumber(ShellySleepingBlockAttributeEntity, RestoreNumber):
|
||||
) from err
|
||||
except InvalidAuthError:
|
||||
await self.coordinator.async_shutdown_device_and_start_reauth()
|
||||
|
||||
|
||||
class RpcNumber(ShellyRpcAttributeEntity, NumberEntity):
|
||||
"""Represent a RPC number entity."""
|
||||
|
||||
entity_description: RpcNumberDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: ShellyRpcCoordinator,
|
||||
key: str,
|
||||
attribute: str,
|
||||
description: RpcNumberDescription,
|
||||
) -> None:
|
||||
"""Initialize sensor."""
|
||||
super().__init__(coordinator, key, attribute, description)
|
||||
|
||||
if description.max_fn is not None:
|
||||
self._attr_native_max_value = description.max_fn(
|
||||
coordinator.device.config[key]
|
||||
)
|
||||
if description.min_fn is not None:
|
||||
self._attr_native_min_value = description.min_fn(
|
||||
coordinator.device.config[key]
|
||||
)
|
||||
if description.step_fn is not None:
|
||||
self._attr_native_step = description.step_fn(coordinator.device.config[key])
|
||||
if description.mode_fn is not None:
|
||||
self._attr_mode = description.mode_fn(coordinator.device.config[key])
|
||||
|
||||
@property
|
||||
def native_value(self) -> float | None:
|
||||
"""Return value of number."""
|
||||
if TYPE_CHECKING:
|
||||
assert isinstance(self.attribute_value, float | None)
|
||||
|
||||
return self.attribute_value
|
||||
|
||||
async def async_set_native_value(self, value: float) -> None:
|
||||
"""Change the value."""
|
||||
await self.call_rpc("Number.Set", {"id": self._id, "value": value})
|
||||
|
@ -33,6 +33,7 @@ from homeassistant.const import (
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import CONNECTION_BLUETOOTH, DeviceInfo
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.entity_registry import RegistryEntry
|
||||
from homeassistant.helpers.typing import StateType
|
||||
@ -76,6 +77,57 @@ class RestSensorDescription(RestEntityDescription, SensorEntityDescription):
|
||||
"""Class to describe a REST sensor."""
|
||||
|
||||
|
||||
class RpcSensor(ShellyRpcAttributeEntity, SensorEntity):
|
||||
"""Represent a RPC sensor."""
|
||||
|
||||
entity_description: RpcSensorDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: ShellyRpcCoordinator,
|
||||
key: str,
|
||||
attribute: str,
|
||||
description: RpcSensorDescription,
|
||||
) -> None:
|
||||
"""Initialize select."""
|
||||
super().__init__(coordinator, key, attribute, description)
|
||||
|
||||
if self.option_map:
|
||||
self._attr_options = list(self.option_map.values())
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return value of sensor."""
|
||||
attribute_value = self.attribute_value
|
||||
|
||||
if not self.option_map:
|
||||
return attribute_value
|
||||
|
||||
if not isinstance(attribute_value, str):
|
||||
return None
|
||||
|
||||
return self.option_map[attribute_value]
|
||||
|
||||
|
||||
class RpcBluTrvSensor(RpcSensor):
|
||||
"""Represent a RPC BluTrv sensor."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: ShellyRpcCoordinator,
|
||||
key: str,
|
||||
attribute: str,
|
||||
description: RpcSensorDescription,
|
||||
) -> None:
|
||||
"""Initialize."""
|
||||
|
||||
super().__init__(coordinator, key, attribute, description)
|
||||
ble_addr: str = coordinator.device.config[key]["addr"]
|
||||
self._attr_device_info = DeviceInfo(
|
||||
connections={(CONNECTION_BLUETOOTH, ble_addr)}
|
||||
)
|
||||
|
||||
|
||||
SENSORS: dict[tuple[str, str], BlockSensorDescription] = {
|
||||
("device", "battery"): BlockSensorDescription(
|
||||
key="device|battery",
|
||||
@ -1222,6 +1274,38 @@ RPC_SENSORS: Final = {
|
||||
options_fn=lambda config: config["options"],
|
||||
device_class=SensorDeviceClass.ENUM,
|
||||
),
|
||||
"valve_position": RpcSensorDescription(
|
||||
key="blutrv",
|
||||
sub_key="pos",
|
||||
name="Valve position",
|
||||
translation_key="valve_position",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
removal_condition=lambda config, _status, key: config[key].get("enable", False)
|
||||
is False,
|
||||
entity_class=RpcBluTrvSensor,
|
||||
),
|
||||
"blutrv_battery": RpcSensorDescription(
|
||||
key="blutrv",
|
||||
sub_key="battery",
|
||||
name="Battery",
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_class=RpcBluTrvSensor,
|
||||
),
|
||||
"blutrv_rssi": RpcSensorDescription(
|
||||
key="blutrv",
|
||||
sub_key="rssi",
|
||||
name="Signal strength",
|
||||
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
|
||||
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
entity_category=EntityCategory.DIAGNOSTIC,
|
||||
entity_class=RpcBluTrvSensor,
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@ -1327,38 +1411,6 @@ class RestSensor(ShellyRestAttributeEntity, SensorEntity):
|
||||
return self.attribute_value
|
||||
|
||||
|
||||
class RpcSensor(ShellyRpcAttributeEntity, SensorEntity):
|
||||
"""Represent a RPC sensor."""
|
||||
|
||||
entity_description: RpcSensorDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: ShellyRpcCoordinator,
|
||||
key: str,
|
||||
attribute: str,
|
||||
description: RpcSensorDescription,
|
||||
) -> None:
|
||||
"""Initialize select."""
|
||||
super().__init__(coordinator, key, attribute, description)
|
||||
|
||||
if self.option_map:
|
||||
self._attr_options = list(self.option_map.values())
|
||||
|
||||
@property
|
||||
def native_value(self) -> StateType:
|
||||
"""Return value of sensor."""
|
||||
attribute_value = self.attribute_value
|
||||
|
||||
if not self.option_map:
|
||||
return attribute_value
|
||||
|
||||
if not isinstance(attribute_value, str):
|
||||
return None
|
||||
|
||||
return self.option_map[attribute_value]
|
||||
|
||||
|
||||
class BlockSleepingSensor(ShellySleepingBlockAttributeEntity, RestoreSensor):
|
||||
"""Represent a block sleeping sensor."""
|
||||
|
||||
|
@ -255,6 +255,8 @@ MOCK_BLU_TRV_REMOTE_STATUS = {
|
||||
"current_C": 15.2,
|
||||
"target_C": 17.1,
|
||||
"schedule_rev": 0,
|
||||
"rssi": -60,
|
||||
"battery": 100,
|
||||
"errors": [],
|
||||
},
|
||||
}
|
||||
|
48
tests/components/shelly/snapshots/test_binary_sensor.ambr
Normal file
48
tests/components/shelly/snapshots/test_binary_sensor.ambr
Normal file
@ -0,0 +1,48 @@
|
||||
# serializer version: 1
|
||||
# name: test_blu_trv_binary_sensor_entity[binary_sensor.trv_name_calibration-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': None,
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'binary_sensor',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'binary_sensor.trv_name_calibration',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <BinarySensorDeviceClass.PROBLEM: 'problem'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'TRV-Name calibration',
|
||||
'platform': 'shelly',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '123456789ABC-blutrv:200-calibration',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_blu_trv_binary_sensor_entity[binary_sensor.trv_name_calibration-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'problem',
|
||||
'friendly_name': 'TRV-Name calibration',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'binary_sensor.trv_name_calibration',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'off',
|
||||
})
|
||||
# ---
|
113
tests/components/shelly/snapshots/test_number.ambr
Normal file
113
tests/components/shelly/snapshots/test_number.ambr
Normal file
@ -0,0 +1,113 @@
|
||||
# serializer version: 1
|
||||
# name: test_blu_trv_number_entity[number.trv_name_external_temperature-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 50,
|
||||
'min': -50,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 0.1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': <EntityCategory.CONFIG: 'config'>,
|
||||
'entity_id': 'number.trv_name_external_temperature',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'TRV-Name external temperature',
|
||||
'platform': 'shelly',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'external_temperature',
|
||||
'unique_id': '123456789ABC-blutrv:200-external_temperature',
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
})
|
||||
# ---
|
||||
# name: test_blu_trv_number_entity[number.trv_name_external_temperature-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'TRV-Name external temperature',
|
||||
'max': 50,
|
||||
'min': -50,
|
||||
'mode': <NumberMode.BOX: 'box'>,
|
||||
'step': 0.1,
|
||||
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.trv_name_external_temperature',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '15.2',
|
||||
})
|
||||
# ---
|
||||
# name: test_blu_trv_number_entity[number.trv_name_valve_position-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'max': 100,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.SLIDER: 'slider'>,
|
||||
'step': 1,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'number',
|
||||
'entity_category': None,
|
||||
'entity_id': 'number.trv_name_valve_position',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'TRV-Name valve position',
|
||||
'platform': 'shelly',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'valve_position',
|
||||
'unique_id': '123456789ABC-blutrv:200-valve_position',
|
||||
'unit_of_measurement': '%',
|
||||
})
|
||||
# ---
|
||||
# name: test_blu_trv_number_entity[number.trv_name_valve_position-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'TRV-Name valve position',
|
||||
'max': 100,
|
||||
'min': 0,
|
||||
'mode': <NumberMode.SLIDER: 'slider'>,
|
||||
'step': 1,
|
||||
'unit_of_measurement': '%',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'number.trv_name_valve_position',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0',
|
||||
})
|
||||
# ---
|
153
tests/components/shelly/snapshots/test_sensor.ambr
Normal file
153
tests/components/shelly/snapshots/test_sensor.ambr
Normal file
@ -0,0 +1,153 @@
|
||||
# serializer version: 1
|
||||
# name: test_blu_trv_sensor_entity[sensor.trv_name_battery-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'sensor.trv_name_battery',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.BATTERY: 'battery'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'TRV-Name battery',
|
||||
'platform': 'shelly',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '123456789ABC-blutrv:200-blutrv_battery',
|
||||
'unit_of_measurement': '%',
|
||||
})
|
||||
# ---
|
||||
# name: test_blu_trv_sensor_entity[sensor.trv_name_battery-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'battery',
|
||||
'friendly_name': 'TRV-Name battery',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': '%',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.trv_name_battery',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '100',
|
||||
})
|
||||
# ---
|
||||
# name: test_blu_trv_sensor_entity[sensor.trv_name_signal_strength-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'sensor.trv_name_signal_strength',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.SIGNAL_STRENGTH: 'signal_strength'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'TRV-Name signal strength',
|
||||
'platform': 'shelly',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': None,
|
||||
'unique_id': '123456789ABC-blutrv:200-blutrv_rssi',
|
||||
'unit_of_measurement': 'dBm',
|
||||
})
|
||||
# ---
|
||||
# name: test_blu_trv_sensor_entity[sensor.trv_name_signal_strength-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'device_class': 'signal_strength',
|
||||
'friendly_name': 'TRV-Name signal strength',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': 'dBm',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.trv_name_signal_strength',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '-60',
|
||||
})
|
||||
# ---
|
||||
# name: test_blu_trv_sensor_entity[sensor.trv_name_valve_position-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': set({
|
||||
}),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'sensor.trv_name_valve_position',
|
||||
'has_entity_name': False,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': 'TRV-Name valve position',
|
||||
'platform': 'shelly',
|
||||
'previous_unique_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'valve_position',
|
||||
'unique_id': '123456789ABC-blutrv:200-valve_position',
|
||||
'unit_of_measurement': '%',
|
||||
})
|
||||
# ---
|
||||
# name: test_blu_trv_sensor_entity[sensor.trv_name_valve_position-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'friendly_name': 'TRV-Name valve position',
|
||||
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
|
||||
'unit_of_measurement': '%',
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.trv_name_valve_position',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': '0',
|
||||
})
|
||||
# ---
|
@ -3,9 +3,10 @@
|
||||
from copy import deepcopy
|
||||
from unittest.mock import Mock
|
||||
|
||||
from aioshelly.const import MODEL_MOTION
|
||||
from aioshelly.const import MODEL_BLU_GATEWAY_GEN3, MODEL_MOTION
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.binary_sensor import DOMAIN as BINARY_SENSOR_DOMAIN
|
||||
from homeassistant.components.shelly.const import UPDATE_PERIOD_MULTIPLIER
|
||||
@ -477,3 +478,22 @@ async def test_rpc_remove_virtual_binary_sensor_when_orphaned(
|
||||
|
||||
entry = entity_registry.async_get(entity_id)
|
||||
assert not entry
|
||||
|
||||
|
||||
async def test_blu_trv_binary_sensor_entity(
|
||||
hass: HomeAssistant,
|
||||
mock_blu_trv: Mock,
|
||||
entity_registry: er.EntityRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test BLU TRV binary sensor entity."""
|
||||
await init_integration(hass, 3, model=MODEL_BLU_GATEWAY_GEN3)
|
||||
|
||||
for entity in ("calibration",):
|
||||
entity_id = f"{BINARY_SENSOR_DOMAIN}.trv_name_{entity}"
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state == snapshot(name=f"{entity_id}-state")
|
||||
|
||||
entry = entity_registry.async_get(entity_id)
|
||||
assert entry == snapshot(name=f"{entity_id}-entry")
|
||||
|
@ -3,8 +3,10 @@
|
||||
from copy import deepcopy
|
||||
from unittest.mock import AsyncMock, Mock
|
||||
|
||||
from aioshelly.const import MODEL_BLU_GATEWAY_GEN3
|
||||
from aioshelly.exceptions import DeviceConnectionError, InvalidAuthError
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.number import (
|
||||
ATTR_MAX,
|
||||
@ -390,3 +392,26 @@ async def test_rpc_remove_virtual_number_when_orphaned(
|
||||
|
||||
entry = entity_registry.async_get(entity_id)
|
||||
assert not entry
|
||||
|
||||
|
||||
async def test_blu_trv_number_entity(
|
||||
hass: HomeAssistant,
|
||||
mock_blu_trv: Mock,
|
||||
entity_registry: EntityRegistry,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test BLU TRV number entity."""
|
||||
# disable automatic temperature control in the device
|
||||
monkeypatch.setitem(mock_blu_trv.config["blutrv:200"], "enable", False)
|
||||
|
||||
await init_integration(hass, 3, model=MODEL_BLU_GATEWAY_GEN3)
|
||||
|
||||
for entity in ("external_temperature", "valve_position"):
|
||||
entity_id = f"{NUMBER_DOMAIN}.trv_name_{entity}"
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state == snapshot(name=f"{entity_id}-state")
|
||||
|
||||
entry = entity_registry.async_get(entity_id)
|
||||
assert entry == snapshot(name=f"{entity_id}-entry")
|
||||
|
@ -3,8 +3,10 @@
|
||||
from copy import deepcopy
|
||||
from unittest.mock import Mock
|
||||
|
||||
from aioshelly.const import MODEL_BLU_GATEWAY_GEN3
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
import pytest
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.homeassistant import (
|
||||
DOMAIN as HA_DOMAIN,
|
||||
@ -1405,3 +1407,22 @@ async def test_rpc_voltmeter_value(
|
||||
entry = entity_registry.async_get(entity_id)
|
||||
assert entry
|
||||
assert entry.unique_id == "123456789ABC-voltmeter:100-voltmeter_value"
|
||||
|
||||
|
||||
async def test_blu_trv_sensor_entity(
|
||||
hass: HomeAssistant,
|
||||
mock_blu_trv: Mock,
|
||||
entity_registry: EntityRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
) -> None:
|
||||
"""Test BLU TRV sensor entity."""
|
||||
await init_integration(hass, 3, model=MODEL_BLU_GATEWAY_GEN3)
|
||||
|
||||
for entity in ("battery", "signal_strength", "valve_position"):
|
||||
entity_id = f"{SENSOR_DOMAIN}.trv_name_{entity}"
|
||||
|
||||
state = hass.states.get(entity_id)
|
||||
assert state == snapshot(name=f"{entity_id}-state")
|
||||
|
||||
entry = entity_registry.async_get(entity_id)
|
||||
assert entry == snapshot(name=f"{entity_id}-entry")
|
||||
|
Loading…
x
Reference in New Issue
Block a user