Skip to content

Commit

Permalink
Add support to Hunter Douglas for Silhouette Type 23 Tilting (home-as…
Browse files Browse the repository at this point in the history
…sistant#70775)

Co-authored-by: J. Nick Koston <[email protected]>
  • Loading branch information
trullock and bdraco authored May 9, 2022
1 parent 6da8893 commit 9ef5c23
Show file tree
Hide file tree
Showing 3 changed files with 154 additions and 22 deletions.
4 changes: 2 additions & 2 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -455,8 +455,8 @@ build.json @home-assistant/supervisor
/tests/components/huisbaasje/ @dennisschroer
/homeassistant/components/humidifier/ @home-assistant/core @Shulyaka
/tests/components/humidifier/ @home-assistant/core @Shulyaka
/homeassistant/components/hunterdouglas_powerview/ @bdraco
/tests/components/hunterdouglas_powerview/ @bdraco
/homeassistant/components/hunterdouglas_powerview/ @bdraco @trullock
/tests/components/hunterdouglas_powerview/ @bdraco @trullock
/homeassistant/components/hvv_departures/ @vigonotion
/tests/components/hvv_departures/ @vigonotion
/homeassistant/components/hydrawise/ @ptcryan
Expand Down
170 changes: 151 additions & 19 deletions homeassistant/components/hunterdouglas_powerview/cover.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Support for hunter douglas shades."""
from abc import abstractmethod
import asyncio
from contextlib import suppress
import logging
Expand All @@ -8,12 +9,14 @@
ATTR_POSKIND1,
MAX_POSITION,
MIN_POSITION,
Silhouette,
factory as PvShade,
)
import async_timeout

from homeassistant.components.cover import (
ATTR_POSITION,
ATTR_TILT_POSITION,
CoverDeviceClass,
CoverEntity,
CoverEntityFeature,
Expand Down Expand Up @@ -49,6 +52,12 @@

RESYNC_DELAY = 60

POSKIND_NONE = 0
POSKIND_PRIMARY = 1
POSKIND_SECONDARY = 2
POSKIND_VANE = 3
POSKIND_ERROR = 4


async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
Expand Down Expand Up @@ -82,24 +91,39 @@ async def async_setup_entry(
room_id = shade.raw_data.get(ROOM_ID_IN_SHADE)
room_name = room_data.get(room_id, {}).get(ROOM_NAME_UNICODE, "")
entities.append(
PowerViewShade(
create_powerview_shade_entity(
coordinator, device_info, room_name, shade, name_before_refresh
)
)
async_add_entities(entities)


def hd_position_to_hass(hd_position):
def create_powerview_shade_entity(
coordinator, device_info, room_name, shade, name_before_refresh
):
"""Create a PowerViewShade entity."""

if isinstance(shade, Silhouette):
return PowerViewShadeSilhouette(
coordinator, device_info, room_name, shade, name_before_refresh
)

return PowerViewShade(
coordinator, device_info, room_name, shade, name_before_refresh
)


def hd_position_to_hass(hd_position, max_val):
"""Convert hunter douglas position to hass position."""
return round((hd_position / MAX_POSITION) * 100)
return round((hd_position / max_val) * 100)


def hass_position_to_hd(hass_position):
def hass_position_to_hd(hass_position, max_val):
"""Convert hass position to hunter douglas position."""
return int(hass_position / 100 * MAX_POSITION)
return int(hass_position / 100 * max_val)


class PowerViewShade(ShadeEntity, CoverEntity):
class PowerViewShadeBase(ShadeEntity, CoverEntity):
"""Representation of a powerview shade."""

# The hub frequently reports stale states
Expand All @@ -113,12 +137,7 @@ def __init__(self, coordinator, device_info, room_name, shade, name):
self._is_closing = False
self._last_action_timestamp = 0
self._scheduled_transition_update = None
self._current_cover_position = MIN_POSITION
self._attr_supported_features = (
CoverEntityFeature.OPEN
| CoverEntityFeature.CLOSE
| CoverEntityFeature.SET_POSITION
)
self._current_hd_cover_position = MIN_POSITION
if self._device_info[DEVICE_MODEL] != LEGACY_DEVICE_MODEL:
self._attr_supported_features |= CoverEntityFeature.STOP
self._forced_resync = None
Expand All @@ -131,7 +150,7 @@ def extra_state_attributes(self):
@property
def is_closed(self):
"""Return if the cover is closed."""
return self._current_cover_position == MIN_POSITION
return self._current_hd_cover_position == MIN_POSITION

@property
def is_opening(self):
Expand All @@ -146,7 +165,7 @@ def is_closing(self):
@property
def current_cover_position(self):
"""Return the current position of cover."""
return hd_position_to_hass(self._current_cover_position)
return hd_position_to_hass(self._current_hd_cover_position, MAX_POSITION)

@property
def device_class(self):
Expand Down Expand Up @@ -181,14 +200,18 @@ async def async_set_cover_position(self, **kwargs):

async def _async_move(self, target_hass_position):
"""Move the shade to a position."""
current_hass_position = hd_position_to_hass(self._current_cover_position)
current_hass_position = hd_position_to_hass(
self._current_hd_cover_position, MAX_POSITION
)
steps_to_move = abs(current_hass_position - target_hass_position)
self._async_schedule_update_for_transition(steps_to_move)
self._async_update_from_command(
await self._shade.move(
{
ATTR_POSITION1: hass_position_to_hd(target_hass_position),
ATTR_POSKIND1: 1,
ATTR_POSITION1: hass_position_to_hd(
target_hass_position, MAX_POSITION
),
ATTR_POSKIND1: POSKIND_PRIMARY,
}
)
)
Expand Down Expand Up @@ -218,11 +241,15 @@ def _async_update_current_cover_position(self):
"""Update the current cover position from the data."""
_LOGGER.debug("Raw data update: %s", self._shade.raw_data)
position_data = self._shade.raw_data.get(ATTR_POSITION_DATA, {})
if ATTR_POSITION1 in position_data:
self._current_cover_position = int(position_data[ATTR_POSITION1])
self._async_process_updated_position_data(position_data)
self._is_opening = False
self._is_closing = False

@callback
@abstractmethod
def _async_process_updated_position_data(self, position_data):
"""Process position data."""

@callback
def _async_cancel_scheduled_transition_update(self):
"""Cancel any previous updates."""
Expand Down Expand Up @@ -299,3 +326,108 @@ def _async_update_shade_from_group(self):
return
self._async_process_new_shade_data(self.coordinator.data[self._shade.id])
self.async_write_ha_state()


class PowerViewShade(PowerViewShadeBase):
"""Represent a standard shade."""

_attr_supported_features = (
CoverEntityFeature.OPEN
| CoverEntityFeature.CLOSE
| CoverEntityFeature.SET_POSITION
)

@callback
def _async_process_updated_position_data(self, position_data):
"""Process position data."""
if ATTR_POSITION1 in position_data:
self._current_hd_cover_position = int(position_data[ATTR_POSITION1])


class PowerViewShadeWithTilt(PowerViewShade):
"""Representation of a PowerView shade with tilt capabilities."""

_attr_supported_features = (
CoverEntityFeature.OPEN
| CoverEntityFeature.CLOSE
| CoverEntityFeature.SET_POSITION
| CoverEntityFeature.OPEN_TILT
| CoverEntityFeature.CLOSE_TILT
| CoverEntityFeature.STOP_TILT
| CoverEntityFeature.SET_TILT_POSITION
)

_max_tilt = MAX_POSITION
_tilt_steps = 10

def __init__(self, coordinator, device_info, room_name, shade, name):
"""Initialize the shade."""
super().__init__(coordinator, device_info, room_name, shade, name)
self._attr_current_cover_tilt_position = 0

async def async_open_cover_tilt(self, **kwargs):
"""Open the cover tilt."""
current_hass_position = hd_position_to_hass(
self._current_hd_cover_position, MAX_POSITION
)
steps_to_move = current_hass_position + self._tilt_steps
self._async_schedule_update_for_transition(steps_to_move)
self._async_update_from_command(await self._shade.tilt_open())

async def async_close_cover_tilt(self, **kwargs):
"""Close the cover tilt."""
current_hass_position = hd_position_to_hass(
self._current_hd_cover_position, MAX_POSITION
)
steps_to_move = current_hass_position + self._tilt_steps
self._async_schedule_update_for_transition(steps_to_move)
self._async_update_from_command(await self._shade.tilt_close())

async def async_set_cover_tilt_position(self, **kwargs):
"""Move the cover tilt to a specific position."""
target_hass_tilt_position = kwargs[ATTR_TILT_POSITION]
current_hass_position = hd_position_to_hass(
self._current_hd_cover_position, MAX_POSITION
)
steps_to_move = current_hass_position + self._tilt_steps

self._async_schedule_update_for_transition(steps_to_move)
self._async_update_from_command(
await self._shade.move(
{
ATTR_POSITION1: hass_position_to_hd(
target_hass_tilt_position, self._max_tilt
),
ATTR_POSKIND1: POSKIND_VANE,
}
)
)

async def async_stop_cover_tilt(self, **kwargs):
"""Stop the cover tilting."""
# Cancel any previous updates
await self.async_stop_cover()

@callback
def _async_process_updated_position_data(self, position_data):
"""Process position data."""
if ATTR_POSKIND1 not in position_data:
return
if int(position_data[ATTR_POSKIND1]) == POSKIND_PRIMARY:
self._current_hd_cover_position = int(position_data[ATTR_POSITION1])
self._attr_current_cover_tilt_position = 0
if int(position_data[ATTR_POSKIND1]) == POSKIND_VANE:
self._current_hd_cover_position = MIN_POSITION
self._attr_current_cover_tilt_position = hd_position_to_hass(
int(position_data[ATTR_POSITION1]), self._max_tilt
)


class PowerViewShadeSilhouette(PowerViewShadeWithTilt):
"""Representation of a Silhouette PowerView shade."""

def __init__(self, coordinator, device_info, room_name, shade, name):
"""Initialize the shade."""
super().__init__(coordinator, device_info, room_name, shade, name)
self._max_tilt = 32767
self._tilt_steps = 4
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "Hunter Douglas PowerView",
"documentation": "https://www.home-assistant.io/integrations/hunterdouglas_powerview",
"requirements": ["aiopvapi==1.6.19"],
"codeowners": ["@bdraco"],
"codeowners": ["@bdraco", "@trullock"],
"config_flow": true,
"homekit": {
"models": ["PowerView"]
Expand Down

0 comments on commit 9ef5c23

Please sign in to comment.