diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index aff7f9a3135..c9301eb7978 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -18,6 +18,7 @@ from homeassistant.helpers.config_validation import ( # noqa: F401 from homeassistant.helpers.entity import Entity, EntityDescription from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.typing import ConfigType, StateType +from homeassistant.util.enum import StrEnum _LOGGER = logging.getLogger(__name__) @@ -26,118 +27,124 @@ SCAN_INTERVAL = timedelta(seconds=30) ENTITY_ID_FORMAT = DOMAIN + ".{}" -# On means low, Off means normal -DEVICE_CLASS_BATTERY = "battery" -# On means charging, Off means not charging -DEVICE_CLASS_BATTERY_CHARGING = "battery_charging" +class BinarySensorDeviceClass(StrEnum): + """Device class for binary sensors.""" -# On means cold, Off means normal -DEVICE_CLASS_COLD = "cold" + # On means low, Off means normal + BATTERY = "battery" -# On means connected, Off means disconnected -DEVICE_CLASS_CONNECTIVITY = "connectivity" + # On means charging, Off means not charging + BATTERY_CHARGING = "battery_charging" -# On means open, Off means closed -DEVICE_CLASS_DOOR = "door" + # On means cold, Off means normal + COLD = "cold" -# On means open, Off means closed -DEVICE_CLASS_GARAGE_DOOR = "garage_door" + # On means connected, Off means disconnected + CONNECTIVITY = "connectivity" -# On means gas detected, Off means no gas (clear) -DEVICE_CLASS_GAS = "gas" + # On means open, Off means closed + DOOR = "door" -# On means hot, Off means normal -DEVICE_CLASS_HEAT = "heat" + # On means open, Off means closed + GARAGE_DOOR = "garage_door" -# On means light detected, Off means no light -DEVICE_CLASS_LIGHT = "light" + # On means gas detected, Off means no gas (clear) + GAS = "gas" -# On means open (unlocked), Off means closed (locked) -DEVICE_CLASS_LOCK = "lock" + # On means hot, Off means normal + HEAT = "heat" -# On means wet, Off means dry -DEVICE_CLASS_MOISTURE = "moisture" + # On means light detected, Off means no light + LIGHT = "light" -# On means motion detected, Off means no motion (clear) -DEVICE_CLASS_MOTION = "motion" + # On means open (unlocked), Off means closed (locked) + LOCK = "lock" -# On means moving, Off means not moving (stopped) -DEVICE_CLASS_MOVING = "moving" + # On means wet, Off means dry + MOISTURE = "moisture" -# On means occupied, Off means not occupied (clear) -DEVICE_CLASS_OCCUPANCY = "occupancy" + # On means motion detected, Off means no motion (clear) + MOTION = "motion" -# On means open, Off means closed -DEVICE_CLASS_OPENING = "opening" + # On means moving, Off means not moving (stopped) + MOVING = "moving" -# On means plugged in, Off means unplugged -DEVICE_CLASS_PLUG = "plug" + # On means occupied, Off means not occupied (clear) + OCCUPANCY = "occupancy" -# On means power detected, Off means no power -DEVICE_CLASS_POWER = "power" + # On means open, Off means closed + OPENING = "opening" -# On means home, Off means away -DEVICE_CLASS_PRESENCE = "presence" + # On means plugged in, Off means unplugged + PLUG = "plug" -# On means problem detected, Off means no problem (OK) -DEVICE_CLASS_PROBLEM = "problem" + # On means power detected, Off means no power + POWER = "power" -# On means running, Off means not running -DEVICE_CLASS_RUNNING = "running" + # On means home, Off means away + PRESENCE = "presence" -# On means unsafe, Off means safe -DEVICE_CLASS_SAFETY = "safety" + # On means problem detected, Off means no problem (OK) + PROBLEM = "problem" -# On means smoke detected, Off means no smoke (clear) -DEVICE_CLASS_SMOKE = "smoke" + # On means running, Off means not running + RUNNING = "running" -# On means sound detected, Off means no sound (clear) -DEVICE_CLASS_SOUND = "sound" + # On means unsafe, Off means safe + SAFETY = "safety" -# On means tampering detected, Off means no tampering (clear) -DEVICE_CLASS_TAMPER = "tamper" + # On means smoke detected, Off means no smoke (clear) + SMOKE = "smoke" -# On means update available, Off means up-to-date -DEVICE_CLASS_UPDATE = "update" + # On means sound detected, Off means no sound (clear) + SOUND = "sound" -# On means vibration detected, Off means no vibration -DEVICE_CLASS_VIBRATION = "vibration" + # On means tampering detected, Off means no tampering (clear) + TAMPER = "tamper" -# On means open, Off means closed -DEVICE_CLASS_WINDOW = "window" + # On means update available, Off means up-to-date + UPDATE = "update" -DEVICE_CLASSES = [ - DEVICE_CLASS_BATTERY, - DEVICE_CLASS_BATTERY_CHARGING, - DEVICE_CLASS_COLD, - DEVICE_CLASS_CONNECTIVITY, - DEVICE_CLASS_DOOR, - DEVICE_CLASS_GARAGE_DOOR, - DEVICE_CLASS_GAS, - DEVICE_CLASS_HEAT, - DEVICE_CLASS_LIGHT, - DEVICE_CLASS_LOCK, - DEVICE_CLASS_MOISTURE, - DEVICE_CLASS_MOTION, - DEVICE_CLASS_MOVING, - DEVICE_CLASS_OCCUPANCY, - DEVICE_CLASS_OPENING, - DEVICE_CLASS_PLUG, - DEVICE_CLASS_POWER, - DEVICE_CLASS_PRESENCE, - DEVICE_CLASS_PROBLEM, - DEVICE_CLASS_RUNNING, - DEVICE_CLASS_SAFETY, - DEVICE_CLASS_SMOKE, - DEVICE_CLASS_SOUND, - DEVICE_CLASS_TAMPER, - DEVICE_CLASS_UPDATE, - DEVICE_CLASS_VIBRATION, - DEVICE_CLASS_WINDOW, -] + # On means vibration detected, Off means no vibration + VIBRATION = "vibration" -DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.In(DEVICE_CLASSES)) + # On means open, Off means closed + WINDOW = "window" + + +DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.Coerce(BinarySensorDeviceClass)) + +# DEVICE_CLASS* below are deprecated as of 2021.12 +# use the BinarySensorDeviceClass enum instead. +DEVICE_CLASSES = [cls.value for cls in BinarySensorDeviceClass] +DEVICE_CLASS_BATTERY = BinarySensorDeviceClass.BATTERY.value +DEVICE_CLASS_BATTERY_CHARGING = BinarySensorDeviceClass.BATTERY_CHARGING.value +DEVICE_CLASS_COLD = BinarySensorDeviceClass.COLD.value +DEVICE_CLASS_CONNECTIVITY = BinarySensorDeviceClass.CONNECTIVITY.value +DEVICE_CLASS_DOOR = BinarySensorDeviceClass.DOOR.value +DEVICE_CLASS_GARAGE_DOOR = BinarySensorDeviceClass.GARAGE_DOOR.value +DEVICE_CLASS_GAS = BinarySensorDeviceClass.GAS.value +DEVICE_CLASS_HEAT = BinarySensorDeviceClass.HEAT.value +DEVICE_CLASS_LIGHT = BinarySensorDeviceClass.LIGHT.value +DEVICE_CLASS_LOCK = BinarySensorDeviceClass.LOCK.value +DEVICE_CLASS_MOISTURE = BinarySensorDeviceClass.MOISTURE.value +DEVICE_CLASS_MOTION = BinarySensorDeviceClass.MOTION.value +DEVICE_CLASS_MOVING = BinarySensorDeviceClass.MOVING.value +DEVICE_CLASS_OCCUPANCY = BinarySensorDeviceClass.OCCUPANCY.value +DEVICE_CLASS_OPENING = BinarySensorDeviceClass.OPENING.value +DEVICE_CLASS_PLUG = BinarySensorDeviceClass.PLUG.value +DEVICE_CLASS_POWER = BinarySensorDeviceClass.POWER.value +DEVICE_CLASS_PRESENCE = BinarySensorDeviceClass.PRESENCE.value +DEVICE_CLASS_PROBLEM = BinarySensorDeviceClass.PROBLEM.value +DEVICE_CLASS_RUNNING = BinarySensorDeviceClass.RUNNING.value +DEVICE_CLASS_SAFETY = BinarySensorDeviceClass.SAFETY.value +DEVICE_CLASS_SMOKE = BinarySensorDeviceClass.SMOKE.value +DEVICE_CLASS_SOUND = BinarySensorDeviceClass.SOUND.value +DEVICE_CLASS_TAMPER = BinarySensorDeviceClass.TAMPER.value +DEVICE_CLASS_UPDATE = BinarySensorDeviceClass.UPDATE.value +DEVICE_CLASS_VIBRATION = BinarySensorDeviceClass.VIBRATION.value +DEVICE_CLASS_WINDOW = BinarySensorDeviceClass.WINDOW.value async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: @@ -166,14 +173,26 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: class BinarySensorEntityDescription(EntityDescription): """A class that describes binary sensor entities.""" + device_class: BinarySensorDeviceClass | str | None = None + class BinarySensorEntity(Entity): """Represent a binary sensor.""" entity_description: BinarySensorEntityDescription + _attr_device_class: BinarySensorDeviceClass | str | None _attr_is_on: bool | None = None _attr_state: None = None + @property + def device_class(self) -> BinarySensorDeviceClass | str | None: + """Return the class of this entity.""" + if hasattr(self, "_attr_device_class"): + return self._attr_device_class + if hasattr(self, "entity_description"): + return self.entity_description.device_class + return None + @property def is_on(self) -> bool | None: """Return true if the binary sensor is on.""" diff --git a/homeassistant/components/demo/binary_sensor.py b/homeassistant/components/demo/binary_sensor.py index 7cf96d33f5c..8710308fc67 100644 --- a/homeassistant/components/demo/binary_sensor.py +++ b/homeassistant/components/demo/binary_sensor.py @@ -1,7 +1,6 @@ """Demo platform that has two fake binary sensors.""" from homeassistant.components.binary_sensor import ( - DEVICE_CLASS_MOISTURE, - DEVICE_CLASS_MOTION, + BinarySensorDeviceClass, BinarySensorEntity, ) from homeassistant.helpers.entity import DeviceInfo @@ -14,10 +13,13 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= async_add_entities( [ DemoBinarySensor( - "binary_1", "Basement Floor Wet", False, DEVICE_CLASS_MOISTURE + "binary_1", + "Basement Floor Wet", + False, + BinarySensorDeviceClass.MOISTURE, ), DemoBinarySensor( - "binary_2", "Movement Backyard", True, DEVICE_CLASS_MOTION + "binary_2", "Movement Backyard", True, BinarySensorDeviceClass.MOTION ), ] ) @@ -31,7 +33,13 @@ async def async_setup_entry(hass, config_entry, async_add_entities): class DemoBinarySensor(BinarySensorEntity): """representation of a Demo binary sensor.""" - def __init__(self, unique_id, name, state, device_class): + def __init__( + self, + unique_id: str, + name: str, + state: bool, + device_class: BinarySensorDeviceClass, + ) -> None: """Initialize the demo sensor.""" self._unique_id = unique_id self._name = name @@ -55,7 +63,7 @@ class DemoBinarySensor(BinarySensorEntity): return self._unique_id @property - def device_class(self): + def device_class(self) -> BinarySensorDeviceClass: """Return the class of this sensor.""" return self._sensor_type diff --git a/homeassistant/components/sia/binary_sensor.py b/homeassistant/components/sia/binary_sensor.py index eec4f9b2717..980596367b2 100644 --- a/homeassistant/components/sia/binary_sensor.py +++ b/homeassistant/components/sia/binary_sensor.py @@ -8,9 +8,7 @@ from typing import Any from pysiaalarm import SIAEvent from homeassistant.components.binary_sensor import ( - DEVICE_CLASS_MOISTURE, - DEVICE_CLASS_POWER, - DEVICE_CLASS_SMOKE, + BinarySensorDeviceClass, BinarySensorEntity, ) from homeassistant.config_entries import ConfigEntry @@ -81,11 +79,11 @@ class SIABinarySensorBase(SIABaseEntity, BinarySensorEntity): entry: ConfigEntry, account_data: dict[str, Any], zone: int, - device_class: str, + device_class: BinarySensorDeviceClass, ) -> None: """Initialize a base binary sensor.""" - super().__init__(entry, account_data, zone, device_class) - + super().__init__(entry, account_data, zone) + self._attr_device_class = device_class self._attr_unique_id = SIA_UNIQUE_ID_FORMAT_BINARY.format( self._entry.entry_id, self._account, self._zone, self._attr_device_class ) @@ -111,7 +109,7 @@ class SIABinarySensorMoisture(SIABinarySensorBase): zone: int, ) -> None: """Initialize a Moisture binary sensor.""" - super().__init__(entry, account_data, zone, DEVICE_CLASS_MOISTURE) + super().__init__(entry, account_data, zone, BinarySensorDeviceClass.MOISTURE) self._attr_entity_registry_enabled_default = False def update_state(self, sia_event: SIAEvent) -> None: @@ -132,7 +130,7 @@ class SIABinarySensorSmoke(SIABinarySensorBase): zone: int, ) -> None: """Initialize a Smoke binary sensor.""" - super().__init__(entry, account_data, zone, DEVICE_CLASS_SMOKE) + super().__init__(entry, account_data, zone, BinarySensorDeviceClass.SMOKE) self._attr_entity_registry_enabled_default = False def update_state(self, sia_event: SIAEvent) -> None: @@ -152,7 +150,9 @@ class SIABinarySensorPower(SIABinarySensorBase): account_data: dict[str, Any], ) -> None: """Initialize a Power binary sensor.""" - super().__init__(entry, account_data, SIA_HUB_ZONE, DEVICE_CLASS_POWER) + super().__init__( + entry, account_data, SIA_HUB_ZONE, BinarySensorDeviceClass.POWER + ) self._attr_entity_registry_enabled_default = True def update_state(self, sia_event: SIAEvent) -> None: diff --git a/homeassistant/components/sia/sia_entity_base.py b/homeassistant/components/sia/sia_entity_base.py index 14334d1f8cb..f4937e326eb 100644 --- a/homeassistant/components/sia/sia_entity_base.py +++ b/homeassistant/components/sia/sia_entity_base.py @@ -29,13 +29,13 @@ class SIABaseEntity(RestoreEntity): entry: ConfigEntry, account_data: dict[str, Any], zone: int, - device_class: str, + device_class: str | None = None, ) -> None: """Create SIABaseEntity object.""" self._entry: ConfigEntry = entry self._account_data: dict[str, Any] = account_data self._zone: int = zone - self._attr_device_class: str = device_class + self._attr_device_class = device_class self._port: int = self._entry.data[CONF_PORT] self._account: str = self._account_data[CONF_ACCOUNT] diff --git a/homeassistant/components/upcloud/binary_sensor.py b/homeassistant/components/upcloud/binary_sensor.py index de55a577610..ebdc30b69f1 100644 --- a/homeassistant/components/upcloud/binary_sensor.py +++ b/homeassistant/components/upcloud/binary_sensor.py @@ -2,7 +2,11 @@ import voluptuous as vol -from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorEntity +from homeassistant.components.binary_sensor import ( + PLATFORM_SCHEMA, + BinarySensorDeviceClass, + BinarySensorEntity, +) from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_USERNAME from homeassistant.core import HomeAssistant @@ -29,3 +33,5 @@ async def async_setup_entry( class UpCloudBinarySensor(UpCloudServerEntity, BinarySensorEntity): """Representation of an UpCloud server sensor.""" + + _attr_device_class = BinarySensorDeviceClass.POWER