diff --git a/.coveragerc b/.coveragerc index fde7cb637f2..59ef37cd932 100644 --- a/.coveragerc +++ b/.coveragerc @@ -313,6 +313,7 @@ omit = homeassistant/components/hue/light.py homeassistant/components/hunterdouglas_powerview/__init__.py homeassistant/components/hunterdouglas_powerview/scene.py + homeassistant/components/hunterdouglas_powerview/sensor.py homeassistant/components/hunterdouglas_powerview/cover.py homeassistant/components/hunterdouglas_powerview/entity.py homeassistant/components/hydrawise/* diff --git a/homeassistant/components/hunterdouglas_powerview/__init__.py b/homeassistant/components/hunterdouglas_powerview/__init__.py index 44ebf25a4f4..89dc610a6fc 100644 --- a/homeassistant/components/hunterdouglas_powerview/__init__.py +++ b/homeassistant/components/hunterdouglas_powerview/__init__.py @@ -69,7 +69,7 @@ CONFIG_SCHEMA = vol.Schema( ) -PLATFORMS = ["cover", "scene"] +PLATFORMS = ["cover", "scene", "sensor"] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/hunterdouglas_powerview/const.py b/homeassistant/components/hunterdouglas_powerview/const.py index 9979cfb186c..17ff3821a7a 100644 --- a/homeassistant/components/hunterdouglas_powerview/const.py +++ b/homeassistant/components/hunterdouglas_powerview/const.py @@ -51,6 +51,8 @@ ROOM_NAME_UNICODE = "name_unicode" ROOM_ID = "id" SHADE_RESPONSE = "shade" +SHADE_BATTERY_LEVEL = "batteryStrength" +SHADE_BATTERY_LEVEL_MAX = 200 STATE_ATTRIBUTE_ROOM_NAME = "roomName" diff --git a/homeassistant/components/hunterdouglas_powerview/cover.py b/homeassistant/components/hunterdouglas_powerview/cover.py index 45fd798238f..8364b3273ca 100644 --- a/homeassistant/components/hunterdouglas_powerview/cover.py +++ b/homeassistant/components/hunterdouglas_powerview/cover.py @@ -5,7 +5,6 @@ import logging from aiopvapi.helpers.constants import ATTR_POSITION1, ATTR_POSITION_DATA from aiopvapi.resources.shade import ( ATTR_POSKIND1, - ATTR_TYPE, MAX_POSITION, MIN_POSITION, factory as PvShade, @@ -28,13 +27,7 @@ from .const import ( COORDINATOR, DEVICE_INFO, DEVICE_MODEL, - DEVICE_SERIAL_NUMBER, DOMAIN, - FIRMWARE_BUILD, - FIRMWARE_IN_SHADE, - FIRMWARE_REVISION, - FIRMWARE_SUB_REVISION, - MANUFACTURER, PV_API, PV_ROOM_DATA, PV_SHADE_DATA, @@ -43,7 +36,7 @@ from .const import ( SHADE_RESPONSE, STATE_ATTRIBUTE_ROOM_NAME, ) -from .entity import HDEntity +from .entity import ShadeEntity _LOGGER = logging.getLogger(__name__) @@ -93,21 +86,19 @@ def hass_position_to_hd(hass_positon): return int(hass_positon / 100 * MAX_POSITION) -class PowerViewShade(HDEntity, CoverEntity): +class PowerViewShade(ShadeEntity, CoverEntity): """Representation of a powerview shade.""" def __init__(self, shade, name, room_data, coordinator, device_info): """Initialize the shade.""" room_id = shade.raw_data.get(ROOM_ID_IN_SHADE) - super().__init__(coordinator, device_info, shade.id) + super().__init__(coordinator, device_info, shade, name) self._shade = shade self._device_info = device_info self._is_opening = False self._is_closing = False - self._room_name = None self._last_action_timestamp = 0 self._scheduled_transition_update = None - self._name = name self._room_name = room_data.get(room_id, {}).get(ROOM_NAME_UNICODE, "") self._current_cover_position = MIN_POSITION self._coordinator = coordinator @@ -153,7 +144,7 @@ class PowerViewShade(HDEntity, CoverEntity): @property def name(self): """Return the name of the shade.""" - return self._name + return self._shade_name async def async_close_cover(self, **kwargs): """Close the cover.""" @@ -268,26 +259,6 @@ class PowerViewShade(HDEntity, CoverEntity): self._async_update_current_cover_position() self.async_write_ha_state() - @property - def device_info(self): - """Return the device_info of the device.""" - firmware = self._shade.raw_data[FIRMWARE_IN_SHADE] - sw_version = f"{firmware[FIRMWARE_REVISION]}.{firmware[FIRMWARE_SUB_REVISION]}.{firmware[FIRMWARE_BUILD]}" - model = self._shade.raw_data[ATTR_TYPE] - for shade in self._shade.shade_types: - if shade.shade_type == model: - model = shade.description - break - - return { - "identifiers": {(DOMAIN, self.unique_id)}, - "name": self.name, - "model": str(model), - "sw_version": sw_version, - "manufacturer": MANUFACTURER, - "via_device": (DOMAIN, self._device_info[DEVICE_SERIAL_NUMBER]), - } - async def async_added_to_hass(self): """When entity is added to hass.""" self._async_update_current_cover_position() diff --git a/homeassistant/components/hunterdouglas_powerview/entity.py b/homeassistant/components/hunterdouglas_powerview/entity.py index 03d20e027b8..3c98eeaf615 100644 --- a/homeassistant/components/hunterdouglas_powerview/entity.py +++ b/homeassistant/components/hunterdouglas_powerview/entity.py @@ -1,5 +1,7 @@ """The nexia integration base entity.""" +from aiopvapi.resources.shade import ATTR_TYPE + import homeassistant.helpers.device_registry as dr from homeassistant.helpers.entity import Entity @@ -11,6 +13,7 @@ from .const import ( DEVICE_SERIAL_NUMBER, DOMAIN, FIRMWARE_BUILD, + FIRMWARE_IN_SHADE, FIRMWARE_REVISION, FIRMWARE_SUB_REVISION, MANUFACTURER, @@ -57,3 +60,33 @@ class HDEntity(Entity): "sw_version": sw_version, "manufacturer": MANUFACTURER, } + + +class ShadeEntity(HDEntity): + """Base class for hunter douglas shade entities.""" + + def __init__(self, coordinator, device_info, shade, shade_name): + """Initialize the shade.""" + super().__init__(coordinator, device_info, shade.id) + self._shade_name = shade_name + self._shade = shade + + @property + def device_info(self): + """Return the device_info of the device.""" + firmware = self._shade.raw_data[FIRMWARE_IN_SHADE] + sw_version = f"{firmware[FIRMWARE_REVISION]}.{firmware[FIRMWARE_SUB_REVISION]}.{firmware[FIRMWARE_BUILD]}" + model = self._shade.raw_data[ATTR_TYPE] + for shade in self._shade.shade_types: + if shade.shade_type == model: + model = shade.description + break + + return { + "identifiers": {(DOMAIN, self._shade.id)}, + "name": self._shade_name, + "model": str(model), + "sw_version": sw_version, + "manufacturer": MANUFACTURER, + "via_device": (DOMAIN, self._device_info[DEVICE_SERIAL_NUMBER]), + } diff --git a/homeassistant/components/hunterdouglas_powerview/sensor.py b/homeassistant/components/hunterdouglas_powerview/sensor.py new file mode 100644 index 00000000000..794fdac3eac --- /dev/null +++ b/homeassistant/components/hunterdouglas_powerview/sensor.py @@ -0,0 +1,86 @@ +"""Support for hunterdouglass_powerview sensors.""" +import logging + +from aiopvapi.resources.shade import factory as PvShade + +from homeassistant.const import DEVICE_CLASS_BATTERY, UNIT_PERCENTAGE +from homeassistant.core import callback + +from .const import ( + COORDINATOR, + DEVICE_INFO, + DOMAIN, + PV_API, + PV_SHADE_DATA, + SHADE_BATTERY_LEVEL, + SHADE_BATTERY_LEVEL_MAX, +) +from .entity import ShadeEntity + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry(hass, entry, async_add_entities): + """Set up the hunter douglas shades sensors.""" + + pv_data = hass.data[DOMAIN][entry.entry_id] + shade_data = pv_data[PV_SHADE_DATA] + pv_request = pv_data[PV_API] + coordinator = pv_data[COORDINATOR] + device_info = pv_data[DEVICE_INFO] + + entities = [] + for raw_shade in shade_data.values(): + shade = PvShade(raw_shade, pv_request) + if SHADE_BATTERY_LEVEL not in shade.raw_data: + continue + name_before_refresh = shade.name + entities.append( + PowerViewShadeBatterySensor( + coordinator, device_info, shade, name_before_refresh + ) + ) + async_add_entities(entities) + + +class PowerViewShadeBatterySensor(ShadeEntity): + """Representation of an shade battery charge sensor.""" + + @property + def unit_of_measurement(self): + """Return the unit of measurement.""" + return UNIT_PERCENTAGE + + @property + def name(self): + """Name of the shade battery.""" + return f"{self._shade_name} Battery" + + @property + def device_class(self): + """Shade battery Class.""" + return DEVICE_CLASS_BATTERY + + @property + def unique_id(self): + """Shade battery Uniqueid.""" + return f"{self._unique_id}_charge" + + @property + def state(self): + """Get the current value in percentage.""" + return round( + self._shade.raw_data[SHADE_BATTERY_LEVEL] / SHADE_BATTERY_LEVEL_MAX * 100 + ) + + async def async_added_to_hass(self): + """When entity is added to hass.""" + self.async_on_remove( + self._coordinator.async_add_listener(self._async_update_shade_from_group) + ) + + @callback + def _async_update_shade_from_group(self): + """Update with new data from the coordinator.""" + self._shade.raw_data = self._coordinator.data[self._shade.id] + self.async_write_ha_state()