Powerview refactor prep for all shade types (#79862)
This commit is contained in:
parent
257ae4d8d3
commit
3ab294e8ef
4 changed files with 211 additions and 146 deletions
|
@ -12,16 +12,12 @@ from aiopvapi.helpers.constants import (
|
||||||
ATTR_POSITION1,
|
ATTR_POSITION1,
|
||||||
ATTR_POSITION2,
|
ATTR_POSITION2,
|
||||||
ATTR_POSITION_DATA,
|
ATTR_POSITION_DATA,
|
||||||
)
|
|
||||||
from aiopvapi.resources.shade import (
|
|
||||||
ATTR_POSKIND1,
|
ATTR_POSKIND1,
|
||||||
ATTR_POSKIND2,
|
ATTR_POSKIND2,
|
||||||
MAX_POSITION,
|
MAX_POSITION,
|
||||||
MIN_POSITION,
|
MIN_POSITION,
|
||||||
BaseShade,
|
|
||||||
ShadeTopDownBottomUp,
|
|
||||||
factory as PvShade,
|
|
||||||
)
|
)
|
||||||
|
from aiopvapi.resources.shade import BaseShade, factory as PvShade
|
||||||
import async_timeout
|
import async_timeout
|
||||||
|
|
||||||
from homeassistant.components.cover import (
|
from homeassistant.components.cover import (
|
||||||
|
@ -107,32 +103,6 @@ async def async_setup_entry(
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
def create_powerview_shade_entity(
|
|
||||||
coordinator: PowerviewShadeUpdateCoordinator,
|
|
||||||
device_info: PowerviewDeviceInfo,
|
|
||||||
room_name: str,
|
|
||||||
shade: BaseShade,
|
|
||||||
name_before_refresh: str,
|
|
||||||
) -> Iterable[ShadeEntity]:
|
|
||||||
"""Create a PowerViewShade entity."""
|
|
||||||
|
|
||||||
classes: list[BaseShade] = []
|
|
||||||
if isinstance(shade, ShadeTopDownBottomUp):
|
|
||||||
classes.extend([PowerViewShadeTDBUTop, PowerViewShadeTDBUBottom])
|
|
||||||
elif ( # this will be extended further in next release for more defined control
|
|
||||||
shade.capability.capabilities.tiltOnClosed
|
|
||||||
or shade.capability.capabilities.tiltAnywhere
|
|
||||||
):
|
|
||||||
classes.append(PowerViewShadeWithTilt)
|
|
||||||
else:
|
|
||||||
classes.append(PowerViewShade)
|
|
||||||
_LOGGER.debug("%s (%s) detected as %a", shade.name, shade.capability.type, classes)
|
|
||||||
return [
|
|
||||||
cls(coordinator, device_info, room_name, shade, name_before_refresh)
|
|
||||||
for cls in classes
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def hd_position_to_hass(hd_position: int, max_val: int = MAX_POSITION) -> int:
|
def hd_position_to_hass(hd_position: int, max_val: int = MAX_POSITION) -> int:
|
||||||
"""Convert hunter douglas position to hass position."""
|
"""Convert hunter douglas position to hass position."""
|
||||||
return round((hd_position / max_val) * 100)
|
return round((hd_position / max_val) * 100)
|
||||||
|
@ -392,6 +362,185 @@ class PowerViewShade(PowerViewShadeBase):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PowerViewShadeWithTiltBase(PowerViewShade):
|
||||||
|
"""Representation for PowerView shades with tilt capabilities."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
coordinator: PowerviewShadeUpdateCoordinator,
|
||||||
|
device_info: PowerviewDeviceInfo,
|
||||||
|
room_name: str,
|
||||||
|
shade: BaseShade,
|
||||||
|
name: str,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize the shade."""
|
||||||
|
super().__init__(coordinator, device_info, room_name, shade, name)
|
||||||
|
self._attr_supported_features |= (
|
||||||
|
CoverEntityFeature.OPEN_TILT
|
||||||
|
| CoverEntityFeature.CLOSE_TILT
|
||||||
|
| CoverEntityFeature.SET_TILT_POSITION
|
||||||
|
)
|
||||||
|
if self._device_info.model != LEGACY_DEVICE_MODEL:
|
||||||
|
self._attr_supported_features |= CoverEntityFeature.STOP_TILT
|
||||||
|
self._max_tilt = self._shade.shade_limits.tilt_max
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_cover_tilt_position(self) -> int:
|
||||||
|
"""Return the current cover tile position."""
|
||||||
|
return hd_position_to_hass(self.positions.vane, self._max_tilt)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def transition_steps(self):
|
||||||
|
"""Return the steps to make a move."""
|
||||||
|
return hd_position_to_hass(
|
||||||
|
self.positions.primary, MAX_POSITION
|
||||||
|
) + hd_position_to_hass(self.positions.vane, self._max_tilt)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def open_tilt_position(self) -> PowerviewShadeMove:
|
||||||
|
"""Return the open tilt position and required additional positions."""
|
||||||
|
return PowerviewShadeMove(self._shade.open_position_tilt, {})
|
||||||
|
|
||||||
|
@property
|
||||||
|
def close_tilt_position(self) -> PowerviewShadeMove:
|
||||||
|
"""Return the close tilt position and required additional positions."""
|
||||||
|
return PowerviewShadeMove(self._shade.close_position_tilt, {})
|
||||||
|
|
||||||
|
async def async_close_cover_tilt(self, **kwargs: Any) -> None:
|
||||||
|
"""Close the cover tilt."""
|
||||||
|
self._async_schedule_update_for_transition(self.transition_steps)
|
||||||
|
await self._async_execute_move(self.close_tilt_position)
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
async def async_open_cover_tilt(self, **kwargs: Any) -> None:
|
||||||
|
"""Open the cover tilt."""
|
||||||
|
self._async_schedule_update_for_transition(100 - self.transition_steps)
|
||||||
|
await self._async_execute_move(self.open_tilt_position)
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
async def async_set_cover_tilt_position(self, **kwargs: Any) -> None:
|
||||||
|
"""Move the vane to a specific position."""
|
||||||
|
await self._async_set_cover_tilt_position(kwargs[ATTR_TILT_POSITION])
|
||||||
|
|
||||||
|
async def _async_set_cover_tilt_position(
|
||||||
|
self, target_hass_tilt_position: int
|
||||||
|
) -> None:
|
||||||
|
"""Move the vane to a specific position."""
|
||||||
|
final_position = self.current_cover_position + target_hass_tilt_position
|
||||||
|
self._async_schedule_update_for_transition(
|
||||||
|
abs(self.transition_steps - final_position)
|
||||||
|
)
|
||||||
|
await self._async_execute_move(self._get_shade_tilt(target_hass_tilt_position))
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _get_shade_tilt(self, target_hass_tilt_position: int) -> PowerviewShadeMove:
|
||||||
|
"""Return a PowerviewShadeMove."""
|
||||||
|
position_vane = hass_position_to_hd(target_hass_tilt_position, self._max_tilt)
|
||||||
|
return PowerviewShadeMove(
|
||||||
|
{ATTR_POSITION1: position_vane, ATTR_POSKIND1: POS_KIND_VANE}, {}
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_stop_cover_tilt(self, **kwargs: Any) -> None:
|
||||||
|
"""Stop the cover tilting."""
|
||||||
|
await self.async_stop_cover()
|
||||||
|
|
||||||
|
|
||||||
|
class PowerViewShadeWithTiltOnClosed(PowerViewShadeWithTiltBase):
|
||||||
|
"""Representation of a PowerView shade with tilt when closed capabilities.
|
||||||
|
|
||||||
|
API Class: ShadeBottomUpTiltOnClosed + ShadeBottomUpTiltOnClosed90
|
||||||
|
|
||||||
|
Type 1 - Bottom Up w/ 90° Tilt
|
||||||
|
Shade 44 - a shade thought to have been a firmware issue (type 0 usually dont tilt)
|
||||||
|
"""
|
||||||
|
|
||||||
|
@property
|
||||||
|
def open_position(self) -> PowerviewShadeMove:
|
||||||
|
"""Return the open position and required additional positions."""
|
||||||
|
return PowerviewShadeMove(
|
||||||
|
self._shade.open_position, {POS_KIND_VANE: MIN_POSITION}
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def close_position(self) -> PowerviewShadeMove:
|
||||||
|
"""Return the close position and required additional positions."""
|
||||||
|
return PowerviewShadeMove(
|
||||||
|
self._shade.close_position, {POS_KIND_VANE: MIN_POSITION}
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def open_tilt_position(self) -> PowerviewShadeMove:
|
||||||
|
"""Return the open tilt position and required additional positions."""
|
||||||
|
return PowerviewShadeMove(
|
||||||
|
self._shade.open_position_tilt, {POS_KIND_PRIMARY: MIN_POSITION}
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def close_tilt_position(self) -> PowerviewShadeMove:
|
||||||
|
"""Return the close tilt position and required additional positions."""
|
||||||
|
return PowerviewShadeMove(
|
||||||
|
self._shade.close_position_tilt, {POS_KIND_PRIMARY: MIN_POSITION}
|
||||||
|
)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _get_shade_move(self, target_hass_position: int) -> PowerviewShadeMove:
|
||||||
|
"""Return a PowerviewShadeMove."""
|
||||||
|
position_shade = hass_position_to_hd(target_hass_position)
|
||||||
|
return PowerviewShadeMove(
|
||||||
|
{ATTR_POSITION1: position_shade, ATTR_POSKIND1: POS_KIND_PRIMARY},
|
||||||
|
{POS_KIND_VANE: MIN_POSITION},
|
||||||
|
)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _get_shade_tilt(self, target_hass_tilt_position: int) -> PowerviewShadeMove:
|
||||||
|
"""Return a PowerviewShadeMove."""
|
||||||
|
position_vane = hass_position_to_hd(target_hass_tilt_position, self._max_tilt)
|
||||||
|
return PowerviewShadeMove(
|
||||||
|
{ATTR_POSITION1: position_vane, ATTR_POSKIND1: POS_KIND_VANE},
|
||||||
|
{POS_KIND_PRIMARY: MIN_POSITION},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PowerViewShadeWithTiltAnywhere(PowerViewShadeWithTiltBase):
|
||||||
|
"""Representation of a PowerView shade with tilt anywhere capabilities.
|
||||||
|
|
||||||
|
API Class: ShadeBottomUpTiltAnywhere, ShadeVerticalTiltAnywhere
|
||||||
|
|
||||||
|
Type 2 - Bottom Up w/ 180° Tilt
|
||||||
|
Type 4 - Vertical (Traversing) w/ 180° Tilt
|
||||||
|
"""
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _get_shade_move(self, target_hass_position: int) -> PowerviewShadeMove:
|
||||||
|
position_shade = hass_position_to_hd(target_hass_position, MAX_POSITION)
|
||||||
|
position_vane = self.positions.vane
|
||||||
|
return PowerviewShadeMove(
|
||||||
|
{
|
||||||
|
ATTR_POSITION1: position_shade,
|
||||||
|
ATTR_POSITION2: position_vane,
|
||||||
|
ATTR_POSKIND1: POS_KIND_PRIMARY,
|
||||||
|
ATTR_POSKIND2: POS_KIND_VANE,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _get_shade_tilt(self, target_hass_tilt_position: int) -> PowerviewShadeMove:
|
||||||
|
"""Return a PowerviewShadeMove."""
|
||||||
|
position_shade = self.positions.primary
|
||||||
|
position_vane = hass_position_to_hd(target_hass_tilt_position, self._max_tilt)
|
||||||
|
return PowerviewShadeMove(
|
||||||
|
{
|
||||||
|
ATTR_POSITION1: position_shade,
|
||||||
|
ATTR_POSITION2: position_vane,
|
||||||
|
ATTR_POSKIND1: POS_KIND_PRIMARY,
|
||||||
|
ATTR_POSKIND2: POS_KIND_VANE,
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PowerViewShadeDualRailBase(PowerViewShade):
|
class PowerViewShadeDualRailBase(PowerViewShade):
|
||||||
"""Representation of a shade with top/down bottom/up capabilities.
|
"""Representation of a shade with top/down bottom/up capabilities.
|
||||||
|
|
||||||
|
@ -528,117 +677,33 @@ class PowerViewShadeTDBUTop(PowerViewShadeDualRailBase):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class PowerViewShadeWithTilt(PowerViewShade):
|
TYPE_TO_CLASSES = {
|
||||||
"""Representation of a PowerView shade with tilt capabilities."""
|
1: (PowerViewShadeWithTiltOnClosed,),
|
||||||
|
2: (PowerViewShadeWithTiltAnywhere,),
|
||||||
|
4: (PowerViewShadeWithTiltAnywhere,),
|
||||||
|
7: (PowerViewShadeTDBUTop, PowerViewShadeTDBUBottom),
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
coordinator: PowerviewShadeUpdateCoordinator,
|
|
||||||
device_info: PowerviewDeviceInfo,
|
|
||||||
room_name: str,
|
|
||||||
shade: BaseShade,
|
|
||||||
name: str,
|
|
||||||
) -> None:
|
|
||||||
"""Initialize the shade."""
|
|
||||||
super().__init__(coordinator, device_info, room_name, shade, name)
|
|
||||||
self._attr_supported_features |= (
|
|
||||||
CoverEntityFeature.OPEN_TILT
|
|
||||||
| CoverEntityFeature.CLOSE_TILT
|
|
||||||
| CoverEntityFeature.SET_TILT_POSITION
|
|
||||||
)
|
|
||||||
if self._device_info.model != LEGACY_DEVICE_MODEL:
|
|
||||||
self._attr_supported_features |= CoverEntityFeature.STOP_TILT
|
|
||||||
self._max_tilt = self._shade.shade_limits.tilt_max
|
|
||||||
|
|
||||||
@property
|
def create_powerview_shade_entity(
|
||||||
def current_cover_tilt_position(self) -> int:
|
coordinator: PowerviewShadeUpdateCoordinator,
|
||||||
"""Return the current cover tile position."""
|
device_info: PowerviewDeviceInfo,
|
||||||
return hd_position_to_hass(self.positions.vane, self._max_tilt)
|
room_name: str,
|
||||||
|
shade: BaseShade,
|
||||||
@property
|
name_before_refresh: str,
|
||||||
def transition_steps(self):
|
) -> Iterable[ShadeEntity]:
|
||||||
"""Return the steps to make a move."""
|
"""Create a PowerViewShade entity."""
|
||||||
return hd_position_to_hass(
|
classes: Iterable[BaseShade] = TYPE_TO_CLASSES.get(
|
||||||
self.positions.primary, MAX_POSITION
|
shade.capability.type, (PowerViewShade,)
|
||||||
) + hd_position_to_hass(self.positions.vane, self._max_tilt)
|
)
|
||||||
|
_LOGGER.debug(
|
||||||
@property
|
"%s (%s) detected as %a %s",
|
||||||
def open_position(self) -> PowerviewShadeMove:
|
shade.name,
|
||||||
"""Return the open position and required additional positions."""
|
shade.capability.type,
|
||||||
return PowerviewShadeMove(
|
classes,
|
||||||
self._shade.open_position, {POS_KIND_VANE: MIN_POSITION}
|
shade.raw_data,
|
||||||
)
|
)
|
||||||
|
return [
|
||||||
@property
|
cls(coordinator, device_info, room_name, shade, name_before_refresh)
|
||||||
def close_position(self) -> PowerviewShadeMove:
|
for cls in classes
|
||||||
"""Return the close position and required additional positions."""
|
]
|
||||||
return PowerviewShadeMove(
|
|
||||||
self._shade.close_position, {POS_KIND_VANE: MIN_POSITION}
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def open_tilt_position(self) -> PowerviewShadeMove:
|
|
||||||
"""Return the open tilt position and required additional positions."""
|
|
||||||
# next upstream api release to include self._shade.open_tilt_position
|
|
||||||
return PowerviewShadeMove(
|
|
||||||
{ATTR_POSKIND1: POS_KIND_VANE, ATTR_POSITION1: self._max_tilt},
|
|
||||||
{POS_KIND_PRIMARY: MIN_POSITION},
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def close_tilt_position(self) -> PowerviewShadeMove:
|
|
||||||
"""Return the close tilt position and required additional positions."""
|
|
||||||
# next upstream api release to include self._shade.close_tilt_position
|
|
||||||
return PowerviewShadeMove(
|
|
||||||
{ATTR_POSKIND1: POS_KIND_VANE, ATTR_POSITION1: MIN_POSITION},
|
|
||||||
{POS_KIND_PRIMARY: MIN_POSITION},
|
|
||||||
)
|
|
||||||
|
|
||||||
async def async_close_cover_tilt(self, **kwargs: Any) -> None:
|
|
||||||
"""Close the cover tilt."""
|
|
||||||
self._async_schedule_update_for_transition(self.transition_steps)
|
|
||||||
await self._async_execute_move(self.close_tilt_position)
|
|
||||||
self.async_write_ha_state()
|
|
||||||
|
|
||||||
async def async_open_cover_tilt(self, **kwargs: Any) -> None:
|
|
||||||
"""Open the cover tilt."""
|
|
||||||
self._async_schedule_update_for_transition(100 - self.transition_steps)
|
|
||||||
await self._async_execute_move(self.open_tilt_position)
|
|
||||||
self.async_write_ha_state()
|
|
||||||
|
|
||||||
async def async_set_cover_tilt_position(self, **kwargs: Any) -> None:
|
|
||||||
"""Move the vane to a specific position."""
|
|
||||||
await self._async_set_cover_tilt_position(kwargs[ATTR_TILT_POSITION])
|
|
||||||
|
|
||||||
async def _async_set_cover_tilt_position(
|
|
||||||
self, target_hass_tilt_position: int
|
|
||||||
) -> None:
|
|
||||||
"""Move the vane to a specific position."""
|
|
||||||
final_position = self.current_cover_position + target_hass_tilt_position
|
|
||||||
self._async_schedule_update_for_transition(
|
|
||||||
abs(self.transition_steps - final_position)
|
|
||||||
)
|
|
||||||
await self._async_execute_move(self._get_shade_tilt(target_hass_tilt_position))
|
|
||||||
self.async_write_ha_state()
|
|
||||||
|
|
||||||
@callback
|
|
||||||
def _get_shade_move(self, target_hass_position: int) -> PowerviewShadeMove:
|
|
||||||
"""Return a PowerviewShadeMove."""
|
|
||||||
position_shade = hass_position_to_hd(target_hass_position)
|
|
||||||
return PowerviewShadeMove(
|
|
||||||
{ATTR_POSITION1: position_shade, ATTR_POSKIND1: POS_KIND_PRIMARY},
|
|
||||||
{POS_KIND_VANE: MIN_POSITION},
|
|
||||||
)
|
|
||||||
|
|
||||||
@callback
|
|
||||||
def _get_shade_tilt(self, target_hass_tilt_position: int) -> PowerviewShadeMove:
|
|
||||||
"""Return a PowerviewShadeMove."""
|
|
||||||
position_vane = hass_position_to_hd(target_hass_tilt_position, self._max_tilt)
|
|
||||||
return PowerviewShadeMove(
|
|
||||||
{ATTR_POSITION1: position_vane, ATTR_POSKIND1: POS_KIND_VANE},
|
|
||||||
{POS_KIND_PRIMARY: MIN_POSITION},
|
|
||||||
)
|
|
||||||
|
|
||||||
async def async_stop_cover_tilt(self, **kwargs: Any) -> None:
|
|
||||||
"""Stop the cover tilting."""
|
|
||||||
await self.async_stop_cover()
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"domain": "hunterdouglas_powerview",
|
"domain": "hunterdouglas_powerview",
|
||||||
"name": "Hunter Douglas PowerView",
|
"name": "Hunter Douglas PowerView",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/hunterdouglas_powerview",
|
"documentation": "https://www.home-assistant.io/integrations/hunterdouglas_powerview",
|
||||||
"requirements": ["aiopvapi==2.0.2"],
|
"requirements": ["aiopvapi==2.0.3"],
|
||||||
"codeowners": ["@bdraco", "@kingy444", "@trullock"],
|
"codeowners": ["@bdraco", "@kingy444", "@trullock"],
|
||||||
"config_flow": true,
|
"config_flow": true,
|
||||||
"homekit": {
|
"homekit": {
|
||||||
|
|
|
@ -232,7 +232,7 @@ aioopenexchangerates==0.4.0
|
||||||
aiopulse==0.4.3
|
aiopulse==0.4.3
|
||||||
|
|
||||||
# homeassistant.components.hunterdouglas_powerview
|
# homeassistant.components.hunterdouglas_powerview
|
||||||
aiopvapi==2.0.2
|
aiopvapi==2.0.3
|
||||||
|
|
||||||
# homeassistant.components.pvpc_hourly_pricing
|
# homeassistant.components.pvpc_hourly_pricing
|
||||||
aiopvpc==3.0.0
|
aiopvpc==3.0.0
|
||||||
|
|
|
@ -207,7 +207,7 @@ aioopenexchangerates==0.4.0
|
||||||
aiopulse==0.4.3
|
aiopulse==0.4.3
|
||||||
|
|
||||||
# homeassistant.components.hunterdouglas_powerview
|
# homeassistant.components.hunterdouglas_powerview
|
||||||
aiopvapi==2.0.2
|
aiopvapi==2.0.3
|
||||||
|
|
||||||
# homeassistant.components.pvpc_hourly_pricing
|
# homeassistant.components.pvpc_hourly_pricing
|
||||||
aiopvpc==3.0.0
|
aiopvpc==3.0.0
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue