Skip to content

Commit

Permalink
Entity Inheritance
Browse files Browse the repository at this point in the history
 * Create a new class, HubSpaceEntity that standardizes
   all duplicate code
 * Update all objects to inherit from HubSpaceEntity

Sem-Ver: feature
  • Loading branch information
Expl0dingBanana committed Nov 4, 2024
1 parent c4459e8 commit 83c5039
Show file tree
Hide file tree
Showing 16 changed files with 368 additions and 667 deletions.
64 changes: 19 additions & 45 deletions custom_components/hubspace/binary_sensor.py
Original file line number Diff line number Diff line change
@@ -1,60 +1,52 @@
import logging
from typing import Any
from typing import Any, Optional

from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from hubspace_async import HubSpaceDevice, HubSpaceState
from hubspace_async import HubSpaceDevice

from . import HubSpaceConfigEntry
from .const import DOMAIN, ENTITY_BINARY_SENSOR
from .const import ENTITY_BINARY_SENSOR
from .coordinator import HubSpaceDataUpdateCoordinator
from .hubspace_entity import HubSpaceEntity

_LOGGER = logging.getLogger(__name__)


class HubSpaceBinarySensor(CoordinatorEntity, BinarySensorEntity):
"""HubSpace child sensor component"""
class HubSpaceBinarySensor(HubSpaceEntity, BinarySensorEntity):
"""HubSpace child sensor component
:ivar _function_class: functionClass within the payload
:ivar _function_instance: functionInstance within the payload
:ivar _sensor_value: Current value of the sensor
"""

ENTITY_TYPE: str = ENTITY_BINARY_SENSOR

def __init__(
self,
coordinator: HubSpaceDataUpdateCoordinator,
description: BinarySensorEntityDescription,
device: HubSpaceDevice,
) -> None:
super().__init__(coordinator, context=device.id)
self.coordinator = coordinator
super().__init__(coordinator, device)
self.entity_description = description
search_data = description.key.split("|", 1)
self._function_instance = None
self._function_class: str
self._function_instance: Optional[str] = None
try:
self._function_class, self._function_instance = search_data
except ValueError:
self._function_class = search_data
self._device = device
self._sensor_value = None

@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
self.update_states()
self.async_write_ha_state()
self._sensor_value: Optional[str] = None

def update_states(self) -> None:
"""Handle updated data from the coordinator."""
states: list[HubSpaceState] = self.coordinator.data[ENTITY_BINARY_SENSOR][
self._device.id
]["device"].states
if not states:
_LOGGER.debug(
"No states found for %s. Maybe hasn't polled yet?", self._device.id
)
for state in states:
for state in self.get_device_states():
if state.functionClass == self._function_class:
if (
self._function_instance
Expand All @@ -63,24 +55,6 @@ def update_states(self) -> None:
continue
self._sensor_value = state.value

@property
def unique_id(self) -> str:
return f"{self._device.id}_{self.entity_description.key}"

@property
def name(self) -> str:
return f"{self._device.friendly_name}: {self.entity_description.name}"

@property
def device_info(self) -> DeviceInfo:
"""Return the device info."""
model = self._device.model if self._device.model != "TBD" else None
return DeviceInfo(
identifiers={(DOMAIN, self._device.device_id)},
name=self._device.friendly_name,
model=model,
)

@property
def device_class(self) -> Any:
"""Return the state."""
Expand Down
118 changes: 19 additions & 99 deletions custom_components/hubspace/fan.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,19 @@
from typing import Any, Optional, Union

from homeassistant.components.fan import FanEntity, FanEntityFeature
from homeassistant.core import HomeAssistant, callback
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util.percentage import (
ordered_list_item_to_percentage,
percentage_to_ordered_list_item,
)
from hubspace_async import HubSpaceState
from hubspace_async import HubSpaceDevice, HubSpaceState

from . import HubSpaceConfigEntry
from .const import DOMAIN, ENTITY_FAN
from .coordinator import HubSpaceDataUpdateCoordinator
from .hubspace_entity import HubSpaceEntity

_LOGGER = logging.getLogger(__name__)

Expand All @@ -28,72 +27,37 @@
PRESET_HA_TO_HS = {val: key for key, val in PRESET_HS_TO_HA.items()}


class HubspaceFan(CoordinatorEntity, FanEntity):
class HubspaceFan(HubSpaceEntity, FanEntity):
"""HubSpace fan that can communicate with Home Assistant
:ivar _name: Name of the device
:ivar _hs: HubSpace connector
:ivar _child_id: ID used when making requests to HubSpace
:ivar _state: If the device is on / off
:ivar _current_direction: Current direction of the device, or if a
direction change is in progress
:ivar _fan_speed: Current fan speed
:ivar _fan_speeds: List of available fan speeds for the device from HubSpace
:ivar _preset_mode: Current preset mode of the device, such as breeze
:ivar _preset_modes: List of available preset modes for the device
:ivar _state: If the device is on / off
:ivar _supported_features: Features that the fan supports, where each
feature is an Enum from FanEntityFeature.
:ivar _availability: If the device is available within HubSpace
:ivar _fan_speeds: List of available fan speeds for the device from HubSpace
:ivar _bonus_attrs: Attributes relayed to Home Assistant that do not need to be
tracked in their own class variables
:ivar _instance_attrs: Additional attributes that are required when
POSTing to HubSpace
:param hs: HubSpace connector
:param friendly_name: The friendly name of the device
:param child_id: ID used when making requests to HubSpace
:param model: Model of the device
:param device_id: Parent Device ID
:param functions: List of supported functions for the device
"""

ENTITY_TYPE = ENTITY_FAN
_enable_turn_on_off_backwards_compatibility = False

def __init__(
self,
hs: HubSpaceDataUpdateCoordinator,
friendly_name: str,
child_id: Optional[str] = None,
model: Optional[str] = None,
device_id: Optional[str] = None,
functions: Optional[list[dict]] = None,
coordinator: HubSpaceDataUpdateCoordinator,
device: HubSpaceDevice,
) -> None:
super().__init__(hs, context=child_id)
self._name: str = friendly_name
self.coordinator = hs
self._hs = hs.conn
self._child_id: str = child_id
super().__init__(coordinator, device)
self._state: Optional[str] = None
self._current_direction: Optional[str] = None
self._preset_mode: Optional[str] = None
self._preset_modes: set[str] = set()
self._supported_features: FanEntityFeature = FanEntityFeature(0)
self._availability: Optional[bool] = None
self._fan_speeds: list[Union[str, int]] = []
self._fan_speed: Optional[str] = None
self._bonus_attrs = {
"model": model,
"deviceId": device_id,
"Child ID": self._child_id,
}
self._instance_attrs: dict[str, str] = {}
functions = functions or []
self.process_functions(functions)

@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
self.update_states()
self.async_write_ha_state()

def process_functions(self, functions: list[dict]) -> None:
"""Process available functions
Expand Down Expand Up @@ -137,16 +101,12 @@ def process_functions(self, functions: list[dict]) -> None:

def update_states(self) -> None:
"""Load initial states into the device"""
states: list[HubSpaceState] = self.coordinator.data[ENTITY_FAN][
self._child_id
].states
additional_attrs = [
"wifi-ssid",
"wifi-mac-address",
"ble-mac-address",
]
# functionClass -> internal attribute
for state in states:
for state in self.get_device_states():
if state.functionClass == "toggle":
if state.value == "enabled":
self._preset_mode = state.functionInstance
Expand All @@ -161,30 +121,6 @@ def update_states(self) -> None:
elif state.functionClass in additional_attrs:
self._bonus_attrs[state.functionClass] = state.value

@property
def should_poll(self):
return False

# Entity-specific properties
@property
def name(self) -> str:
"""Return the display name of this light."""
return self._name

@property
def unique_id(self) -> str:
"""Return the display name of this light."""
return self._child_id

@property
def available(self) -> bool:
return self._availability is True

@property
def extra_state_attributes(self):
"""Return the state attributes."""
return self._bonus_attrs

@property
def is_on(self) -> bool | None:
"""Return true if light is on."""
Expand All @@ -193,18 +129,6 @@ def is_on(self) -> bool | None:
else:
return self._state == "on"

@property
def device_info(self) -> DeviceInfo:
"""Return the device info."""
model = (
self._bonus_attrs["model"] if self._bonus_attrs["model"] != "TBD" else None
)
return DeviceInfo(
identifiers={(DOMAIN, self._bonus_attrs["deviceId"])},
name=self._name,
model=model,
)

@property
def current_direction(self):
return self._current_direction
Expand Down Expand Up @@ -259,7 +183,7 @@ async def async_turn_on(
functionInstance=self._instance_attrs.get("power", None),
value="on",
)
await self._hs.set_device_state(self._child_id, power_state)
await self.set_device_state(power_state)
await self.async_set_percentage(percentage)
await self.async_set_preset_mode(preset_mode)
self.async_write_ha_state()
Expand All @@ -275,7 +199,7 @@ async def async_turn_off(self, **kwargs: Any) -> None:
functionInstance=self._instance_attrs.get("power", None),
value="off",
)
await self._hs.set_device_state(self._child_id, power_state)
await self.set_device_state(power_state)
self.async_write_ha_state()

async def async_set_percentage(self, percentage: int) -> None:
Expand All @@ -292,7 +216,7 @@ async def async_set_percentage(self, percentage: int) -> None:
functionInstance=self._instance_attrs.get("fan-speed", None),
value=self._fan_speed,
)
await self._hs.set_device_state(self._child_id, speed_state)
await self.set_device_state(speed_state)
self.async_write_ha_state()

async def async_set_preset_mode(self, preset_mode: str) -> None:
Expand All @@ -312,7 +236,7 @@ async def async_set_preset_mode(self, preset_mode: str) -> None:
functionInstance=self._preset_mode,
value="enabled",
)
await self._hs.set_device_state(self._child_id, preset_state)
await self.set_device_state(preset_state)
self.async_write_ha_state()

async def async_set_direction(self, direction: str) -> None:
Expand All @@ -324,7 +248,7 @@ async def async_set_direction(self, direction: str) -> None:
functionInstance=self._instance_attrs.get("fan-reverse", None),
value=direction,
)
await self._hs.set_device_state(self._child_id, direction_state)
await self.set_device_state(direction_state)
self.async_write_ha_state()


Expand All @@ -350,11 +274,7 @@ async def async_setup_entry(
)
ha_entity = HubspaceFan(
coordinator_hubspace,
entity.friendly_name,
child_id=entity.id,
model=entity.model,
device_id=entity.device_id,
functions=entity.functions,
entity,
)
fans.append(ha_entity)
async_add_entities(fans)
Loading

0 comments on commit 83c5039

Please sign in to comment.