Define entity attributes as entity class variables (#50925)

* Define entity attributes as entity class variables

* Example coronavirus integration

* Example verisure

* Cleanup/typing fixes

* Fix Coronavirus

* Revert "Fix Coronavirus"

This reverts commit 060843860fe300f8448d0d2476de8963d5ddf5a2.

* Revert "Cleanup/typing fixes"

This reverts commit 659b79e75a97007f7181064e446c3e988c2d54bb.

* Define entity attributes as entity class variables (attr alternative)

* Example coronavirus

* Example nut

* Example verisure

* Mark private

* Cleanup after all reverting/cherrypicking/merging

* Implement all entity properties

* Update coronavirus example

* Update nut example

* Update verisure example

* Lets not talk about this one...

* Fix multiple class attribute
This commit is contained in:
Franck Nijhof 2021-05-22 18:13:50 +02:00 committed by GitHub
parent b9a0fb93eb
commit 38d095aa18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 80 additions and 173 deletions

View File

@ -27,17 +27,21 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
class CoronavirusSensor(CoordinatorEntity, SensorEntity): class CoronavirusSensor(CoordinatorEntity, SensorEntity):
"""Sensor representing corona virus data.""" """Sensor representing corona virus data."""
name = None _attr_unit_of_measurement = "people"
unique_id = None
def __init__(self, coordinator, country, info_type): def __init__(self, coordinator, country, info_type):
"""Initialize coronavirus sensor.""" """Initialize coronavirus sensor."""
super().__init__(coordinator) super().__init__(coordinator)
self._attr_extra_state_attributes = {ATTR_ATTRIBUTION: ATTRIBUTION}
self._attr_icon = SENSORS[info_type]
self._attr_unique_id = f"{country}-{info_type}"
if country == OPTION_WORLDWIDE: if country == OPTION_WORLDWIDE:
self.name = f"Worldwide Coronavirus {info_type}" self._attr_name = f"Worldwide Coronavirus {info_type}"
else: else:
self.name = f"{coordinator.data[country].country} Coronavirus {info_type}" self._attr_name = (
self.unique_id = f"{country}-{info_type}" f"{coordinator.data[country].country} Coronavirus {info_type}"
)
self.country = country self.country = country
self.info_type = info_type self.info_type = info_type
@ -62,18 +66,3 @@ class CoronavirusSensor(CoordinatorEntity, SensorEntity):
return sum_cases return sum_cases
return getattr(self.coordinator.data[self.country], self.info_type) return getattr(self.coordinator.data[self.country], self.info_type)
@property
def icon(self):
"""Return the icon."""
return SENSORS[self.info_type]
@property
def unit_of_measurement(self):
"""Return unit of measurement."""
return "people"
@property
def extra_state_attributes(self):
"""Return device attributes."""
return {ATTR_ATTRIBUTION: ATTRIBUTION}

View File

@ -98,11 +98,14 @@ class NUTSensor(CoordinatorEntity, SensorEntity):
self._firmware = firmware self._firmware = firmware
self._model = model self._model = model
self._device_name = name self._device_name = name
self._name = f"{name} {SENSOR_TYPES[sensor_type][SENSOR_NAME]}"
self._unit = SENSOR_TYPES[sensor_type][SENSOR_UNIT]
self._data = data self._data = data
self._unique_id = unique_id self._unique_id = unique_id
self._attr_device_class = SENSOR_TYPES[self._type][SENSOR_DEVICE_CLASS]
self._attr_icon = SENSOR_TYPES[self._type][SENSOR_ICON]
self._attr_name = f"{name} {SENSOR_TYPES[sensor_type][SENSOR_NAME]}"
self._attr_unit_of_measurement = SENSOR_TYPES[sensor_type][SENSOR_UNIT]
@property @property
def device_info(self): def device_info(self):
"""Device info for the ups.""" """Device info for the ups."""
@ -127,25 +130,6 @@ class NUTSensor(CoordinatorEntity, SensorEntity):
return None return None
return f"{self._unique_id}_{self._type}" return f"{self._unique_id}_{self._type}"
@property
def name(self):
"""Return the name of the UPS sensor."""
return self._name
@property
def icon(self):
"""Icon to use in the frontend, if any."""
if SENSOR_TYPES[self._type][SENSOR_DEVICE_CLASS]:
# The UI will assign an icon
# if it has a class
return None
return SENSOR_TYPES[self._type][SENSOR_ICON]
@property
def device_class(self):
"""Device class of the sensor."""
return SENSOR_TYPES[self._type][SENSOR_DEVICE_CLASS]
@property @property
def state(self): def state(self):
"""Return entity state from ups.""" """Return entity state from ups."""
@ -155,11 +139,6 @@ class NUTSensor(CoordinatorEntity, SensorEntity):
return _format_display_state(self._data.status) return _format_display_state(self._data.status)
return self._data.status.get(self._type) return self._data.status.get(self._type)
@property
def unit_of_measurement(self):
"""Return the unit of measurement of this entity, if any."""
return self._unit
@property @property
def extra_state_attributes(self): def extra_state_attributes(self):
"""Return the sensor attributes.""" """Return the sensor attributes."""

View File

@ -35,18 +35,10 @@ class VerisureAlarm(CoordinatorEntity, AlarmControlPanelEntity):
coordinator: VerisureDataUpdateCoordinator coordinator: VerisureDataUpdateCoordinator
_attr_name = "Verisure Alarm"
_attr_supported_features = SUPPORT_ALARM_ARM_HOME | SUPPORT_ALARM_ARM_AWAY
_changed_by: str | None = None _changed_by: str | None = None
_state: str | None = None
@property
def name(self) -> str:
"""Return the name of the entity."""
return "Verisure Alarm"
@property
def unique_id(self) -> str:
"""Return the unique ID for this entity."""
return self.coordinator.entry.data[CONF_GIID]
@property @property
def device_info(self) -> DeviceInfo: def device_info(self) -> DeviceInfo:
@ -58,11 +50,6 @@ class VerisureAlarm(CoordinatorEntity, AlarmControlPanelEntity):
"identifiers": {(DOMAIN, self.coordinator.entry.data[CONF_GIID])}, "identifiers": {(DOMAIN, self.coordinator.entry.data[CONF_GIID])},
} }
@property
def state(self) -> str | None:
"""Return the state of the entity."""
return self._state
@property @property
def supported_features(self) -> int: def supported_features(self) -> int:
"""Return the list of supported features.""" """Return the list of supported features."""
@ -109,7 +96,7 @@ class VerisureAlarm(CoordinatorEntity, AlarmControlPanelEntity):
@callback @callback
def _handle_coordinator_update(self) -> None: def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator.""" """Handle updated data from the coordinator."""
self._state = ALARM_STATE_TO_HA.get( self._attr_state = ALARM_STATE_TO_HA.get(
self.coordinator.data["alarm"]["statusType"] self.coordinator.data["alarm"]["statusType"]
) )
self._changed_by = self.coordinator.data["alarm"].get("name") self._changed_by = self.coordinator.data["alarm"].get("name")

View File

@ -39,23 +39,17 @@ class VerisureDoorWindowSensor(CoordinatorEntity, BinarySensorEntity):
coordinator: VerisureDataUpdateCoordinator coordinator: VerisureDataUpdateCoordinator
_attr_device_class = DEVICE_CLASS_OPENING
def __init__( def __init__(
self, coordinator: VerisureDataUpdateCoordinator, serial_number: str self, coordinator: VerisureDataUpdateCoordinator, serial_number: str
) -> None: ) -> None:
"""Initialize the Verisure door window sensor.""" """Initialize the Verisure door window sensor."""
super().__init__(coordinator) super().__init__(coordinator)
self._attr_name = coordinator.data["door_window"][serial_number]["area"]
self._attr_unique_id = f"{serial_number}_door_window"
self.serial_number = serial_number self.serial_number = serial_number
@property
def name(self) -> str:
"""Return the name of this entity."""
return self.coordinator.data["door_window"][self.serial_number]["area"]
@property
def unique_id(self) -> str:
"""Return the unique ID for this entity."""
return f"{self.serial_number}_door_window"
@property @property
def device_info(self) -> DeviceInfo: def device_info(self) -> DeviceInfo:
"""Return device information about this entity.""" """Return device information about this entity."""
@ -69,11 +63,6 @@ class VerisureDoorWindowSensor(CoordinatorEntity, BinarySensorEntity):
"via_device": (DOMAIN, self.coordinator.entry.data[CONF_GIID]), "via_device": (DOMAIN, self.coordinator.entry.data[CONF_GIID]),
} }
@property
def device_class(self) -> str:
"""Return the class of this entity."""
return DEVICE_CLASS_OPENING
@property @property
def is_on(self) -> bool: def is_on(self) -> bool:
"""Return the state of the sensor.""" """Return the state of the sensor."""
@ -95,10 +84,8 @@ class VerisureEthernetStatus(CoordinatorEntity, BinarySensorEntity):
coordinator: VerisureDataUpdateCoordinator coordinator: VerisureDataUpdateCoordinator
@property _attr_name = "Verisure Ethernet status"
def name(self) -> str: _attr_device_class = DEVICE_CLASS_CONNECTIVITY
"""Return the name of this entity."""
return "Verisure Ethernet status"
@property @property
def unique_id(self) -> str: def unique_id(self) -> str:
@ -124,8 +111,3 @@ class VerisureEthernetStatus(CoordinatorEntity, BinarySensorEntity):
def available(self) -> bool: def available(self) -> bool:
"""Return True if entity is available.""" """Return True if entity is available."""
return super().available and self.coordinator.data["ethernet"] is not None return super().available and self.coordinator.data["ethernet"] is not None
@property
def device_class(self) -> str:
"""Return the class of this entity."""
return DEVICE_CLASS_CONNECTIVITY

View File

@ -58,21 +58,14 @@ class VerisureSmartcam(CoordinatorEntity, Camera):
super().__init__(coordinator) super().__init__(coordinator)
Camera.__init__(self) Camera.__init__(self)
self._attr_name = coordinator.data["cameras"][serial_number]["area"]
self._attr_unique_id = serial_number
self.serial_number = serial_number self.serial_number = serial_number
self._directory_path = directory_path self._directory_path = directory_path
self._image = None self._image = None
self._image_id = None self._image_id = None
@property
def name(self) -> str:
"""Return the name of this entity."""
return self.coordinator.data["cameras"][self.serial_number]["area"]
@property
def unique_id(self) -> str:
"""Return the unique ID for this entity."""
return self.serial_number
@property @property
def device_info(self) -> DeviceInfo: def device_info(self) -> DeviceInfo:
"""Return device information about this entity.""" """Return device information about this entity."""

View File

@ -65,22 +65,16 @@ class VerisureDoorlock(CoordinatorEntity, LockEntity):
) -> None: ) -> None:
"""Initialize the Verisure lock.""" """Initialize the Verisure lock."""
super().__init__(coordinator) super().__init__(coordinator)
self._attr_name = coordinator.data["locks"][serial_number]["area"]
self._attr_unique_id = serial_number
self.serial_number = serial_number self.serial_number = serial_number
self._state = None self._state = None
self._digits = coordinator.entry.options.get( self._digits = coordinator.entry.options.get(
CONF_LOCK_CODE_DIGITS, DEFAULT_LOCK_CODE_DIGITS CONF_LOCK_CODE_DIGITS, DEFAULT_LOCK_CODE_DIGITS
) )
@property
def name(self) -> str:
"""Return the name of this entity."""
return self.coordinator.data["locks"][self.serial_number]["area"]
@property
def unique_id(self) -> str:
"""Return the unique ID for this entity."""
return self.serial_number
@property @property
def device_info(self) -> DeviceInfo: def device_info(self) -> DeviceInfo:
"""Return device information about this entity.""" """Return device information about this entity."""

View File

@ -50,11 +50,15 @@ class VerisureThermometer(CoordinatorEntity, SensorEntity):
coordinator: VerisureDataUpdateCoordinator coordinator: VerisureDataUpdateCoordinator
_attr_device_class = DEVICE_CLASS_TEMPERATURE
_attr_unit_of_measurement = TEMP_CELSIUS
def __init__( def __init__(
self, coordinator: VerisureDataUpdateCoordinator, serial_number: str self, coordinator: VerisureDataUpdateCoordinator, serial_number: str
) -> None: ) -> None:
"""Initialize the sensor.""" """Initialize the sensor."""
super().__init__(coordinator) super().__init__(coordinator)
self._attr_unique_id = f"{serial_number}_temperature"
self.serial_number = serial_number self.serial_number = serial_number
@property @property
@ -63,16 +67,6 @@ class VerisureThermometer(CoordinatorEntity, SensorEntity):
name = self.coordinator.data["climate"][self.serial_number]["deviceArea"] name = self.coordinator.data["climate"][self.serial_number]["deviceArea"]
return f"{name} Temperature" return f"{name} Temperature"
@property
def unique_id(self) -> str:
"""Return the unique ID for this entity."""
return f"{self.serial_number}_temperature"
@property
def device_class(self) -> str:
"""Return the class of this entity."""
return DEVICE_CLASS_TEMPERATURE
@property @property
def device_info(self) -> DeviceInfo: def device_info(self) -> DeviceInfo:
"""Return device information about this entity.""" """Return device information about this entity."""
@ -103,22 +97,21 @@ class VerisureThermometer(CoordinatorEntity, SensorEntity):
and "temperature" in self.coordinator.data["climate"][self.serial_number] and "temperature" in self.coordinator.data["climate"][self.serial_number]
) )
@property
def unit_of_measurement(self) -> str:
"""Return the unit of measurement of this entity."""
return TEMP_CELSIUS
class VerisureHygrometer(CoordinatorEntity, SensorEntity): class VerisureHygrometer(CoordinatorEntity, SensorEntity):
"""Representation of a Verisure hygrometer.""" """Representation of a Verisure hygrometer."""
coordinator: VerisureDataUpdateCoordinator coordinator: VerisureDataUpdateCoordinator
_attr_device_class = DEVICE_CLASS_HUMIDITY
_attr_unit_of_measurement = PERCENTAGE
def __init__( def __init__(
self, coordinator: VerisureDataUpdateCoordinator, serial_number: str self, coordinator: VerisureDataUpdateCoordinator, serial_number: str
) -> None: ) -> None:
"""Initialize the sensor.""" """Initialize the sensor."""
super().__init__(coordinator) super().__init__(coordinator)
self._attr_unique_id = f"{serial_number}_humidity"
self.serial_number = serial_number self.serial_number = serial_number
@property @property
@ -127,16 +120,6 @@ class VerisureHygrometer(CoordinatorEntity, SensorEntity):
name = self.coordinator.data["climate"][self.serial_number]["deviceArea"] name = self.coordinator.data["climate"][self.serial_number]["deviceArea"]
return f"{name} Humidity" return f"{name} Humidity"
@property
def unique_id(self) -> str:
"""Return the unique ID for this entity."""
return f"{self.serial_number}_humidity"
@property
def device_class(self) -> str:
"""Return the class of this entity."""
return DEVICE_CLASS_HUMIDITY
@property @property
def device_info(self) -> DeviceInfo: def device_info(self) -> DeviceInfo:
"""Return device information about this entity.""" """Return device information about this entity."""
@ -167,22 +150,20 @@ class VerisureHygrometer(CoordinatorEntity, SensorEntity):
and "humidity" in self.coordinator.data["climate"][self.serial_number] and "humidity" in self.coordinator.data["climate"][self.serial_number]
) )
@property
def unit_of_measurement(self) -> str:
"""Return the unit of measurement of this entity."""
return PERCENTAGE
class VerisureMouseDetection(CoordinatorEntity, SensorEntity): class VerisureMouseDetection(CoordinatorEntity, SensorEntity):
"""Representation of a Verisure mouse detector.""" """Representation of a Verisure mouse detector."""
coordinator: VerisureDataUpdateCoordinator coordinator: VerisureDataUpdateCoordinator
_attr_unit_of_measurement = "Mice"
def __init__( def __init__(
self, coordinator: VerisureDataUpdateCoordinator, serial_number: str self, coordinator: VerisureDataUpdateCoordinator, serial_number: str
) -> None: ) -> None:
"""Initialize the sensor.""" """Initialize the sensor."""
super().__init__(coordinator) super().__init__(coordinator)
self._attr_unique_id = f"{serial_number}_mice"
self.serial_number = serial_number self.serial_number = serial_number
@property @property
@ -191,11 +172,6 @@ class VerisureMouseDetection(CoordinatorEntity, SensorEntity):
name = self.coordinator.data["mice"][self.serial_number]["area"] name = self.coordinator.data["mice"][self.serial_number]["area"]
return f"{name} Mouse" return f"{name} Mouse"
@property
def unique_id(self) -> str:
"""Return the unique ID for this entity."""
return f"{self.serial_number}_mice"
@property @property
def device_info(self) -> DeviceInfo: def device_info(self) -> DeviceInfo:
"""Return device information about this entity.""" """Return device information about this entity."""
@ -222,8 +198,3 @@ class VerisureMouseDetection(CoordinatorEntity, SensorEntity):
and self.serial_number in self.coordinator.data["mice"] and self.serial_number in self.coordinator.data["mice"]
and "detections" in self.coordinator.data["mice"][self.serial_number] and "detections" in self.coordinator.data["mice"][self.serial_number]
) )
@property
def unit_of_measurement(self) -> str:
"""Return the unit of measurement of this entity."""
return "Mice"

View File

@ -37,20 +37,14 @@ class VerisureSmartplug(CoordinatorEntity, SwitchEntity):
) -> None: ) -> None:
"""Initialize the Verisure device.""" """Initialize the Verisure device."""
super().__init__(coordinator) super().__init__(coordinator)
self._attr_name = coordinator.data["smart_plugs"][serial_number]["area"]
self._attr_unique_id = serial_number
self.serial_number = serial_number self.serial_number = serial_number
self._change_timestamp = 0 self._change_timestamp = 0
self._state = False self._state = False
@property
def name(self) -> str:
"""Return the name of this entity."""
return self.coordinator.data["smart_plugs"][self.serial_number]["area"]
@property
def unique_id(self) -> str:
"""Return the unique ID for this entity."""
return self.serial_number
@property @property
def device_info(self) -> DeviceInfo: def device_info(self) -> DeviceInfo:
"""Return device information about this entity.""" """Return device information about this entity."""

View File

@ -168,28 +168,46 @@ class Entity(ABC):
# If entity is added to an entity platform # If entity is added to an entity platform
_added = False _added = False
# Entity Properties
_attr_assumed_state: bool = False
_attr_available: bool = True
_attr_context_recent_time: timedelta = timedelta(seconds=5)
_attr_device_class: str | None = None
_attr_device_info: DeviceInfo | None = None
_attr_entity_picture: str | None = None
_attr_entity_registry_enabled_default: bool = True
_attr_extra_state_attributes: Mapping[str, Any] | None = None
_attr_force_update: bool = False
_attr_icon: str | None = None
_attr_name: str | None = None
_attr_should_poll: bool = True
_attr_state: StateType = STATE_UNKNOWN
_attr_supported_features: int | None = None
_attr_unique_id: str | None = None
_attr_unit_of_measurement: str | None = None
@property @property
def should_poll(self) -> bool: def should_poll(self) -> bool:
"""Return True if entity has to be polled for state. """Return True if entity has to be polled for state.
False if entity pushes its state to HA. False if entity pushes its state to HA.
""" """
return True return self._attr_should_poll
@property @property
def unique_id(self) -> str | None: def unique_id(self) -> str | None:
"""Return a unique ID.""" """Return a unique ID."""
return None return self._attr_unique_id
@property @property
def name(self) -> str | None: def name(self) -> str | None:
"""Return the name of the entity.""" """Return the name of the entity."""
return None return self._attr_name
@property @property
def state(self) -> StateType: def state(self) -> StateType:
"""Return the state of the entity.""" """Return the state of the entity."""
return STATE_UNKNOWN return self._attr_state
@property @property
def capability_attributes(self) -> Mapping[str, Any] | None: def capability_attributes(self) -> Mapping[str, Any] | None:
@ -227,7 +245,7 @@ class Entity(ABC):
Implemented by platform classes. Convention for attribute names Implemented by platform classes. Convention for attribute names
is lowercase snake_case. is lowercase snake_case.
""" """
return None return self._attr_extra_state_attributes
@property @property
def device_info(self) -> DeviceInfo | None: def device_info(self) -> DeviceInfo | None:
@ -235,37 +253,37 @@ class Entity(ABC):
Implemented by platform classes. Implemented by platform classes.
""" """
return None return self._attr_device_info
@property @property
def device_class(self) -> str | None: def device_class(self) -> str | None:
"""Return the class of this device, from component DEVICE_CLASSES.""" """Return the class of this device, from component DEVICE_CLASSES."""
return None return self._attr_device_class
@property @property
def unit_of_measurement(self) -> str | None: def unit_of_measurement(self) -> str | None:
"""Return the unit of measurement of this entity, if any.""" """Return the unit of measurement of this entity, if any."""
return None return self._attr_unit_of_measurement
@property @property
def icon(self) -> str | None: def icon(self) -> str | None:
"""Return the icon to use in the frontend, if any.""" """Return the icon to use in the frontend, if any."""
return None return self._attr_icon
@property @property
def entity_picture(self) -> str | None: def entity_picture(self) -> str | None:
"""Return the entity picture to use in the frontend, if any.""" """Return the entity picture to use in the frontend, if any."""
return None return self._attr_entity_picture
@property @property
def available(self) -> bool: def available(self) -> bool:
"""Return True if entity is available.""" """Return True if entity is available."""
return True return self._attr_available
@property @property
def assumed_state(self) -> bool: def assumed_state(self) -> bool:
"""Return True if unable to access real state of the entity.""" """Return True if unable to access real state of the entity."""
return False return self._attr_assumed_state
@property @property
def force_update(self) -> bool: def force_update(self) -> bool:
@ -274,22 +292,22 @@ class Entity(ABC):
If True, a state change will be triggered anytime the state property is If True, a state change will be triggered anytime the state property is
updated, not just when the value changes. updated, not just when the value changes.
""" """
return False return self._attr_force_update
@property @property
def supported_features(self) -> int | None: def supported_features(self) -> int | None:
"""Flag supported features.""" """Flag supported features."""
return None return self._attr_supported_features
@property @property
def context_recent_time(self) -> timedelta: def context_recent_time(self) -> timedelta:
"""Time that a context is considered recent.""" """Time that a context is considered recent."""
return timedelta(seconds=5) return self._attr_context_recent_time
@property @property
def entity_registry_enabled_default(self) -> bool: def entity_registry_enabled_default(self) -> bool:
"""Return if the entity should be enabled when first added to the entity registry.""" """Return if the entity should be enabled when first added to the entity registry."""
return True return self._attr_entity_registry_enabled_default
# DO NOT OVERWRITE # DO NOT OVERWRITE
# These properties and methods are either managed by Home Assistant or they # These properties and methods are either managed by Home Assistant or they