diff --git a/custom_components/hubspace/binary_sensor.py b/custom_components/hubspace/binary_sensor.py index 699f766..cc9ea1a 100644 --- a/custom_components/hubspace/binary_sensor.py +++ b/custom_components/hubspace/binary_sensor.py @@ -1,25 +1,31 @@ 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, @@ -27,34 +33,20 @@ def __init__( 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 @@ -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.""" diff --git a/custom_components/hubspace/fan.py b/custom_components/hubspace/fan.py index 1ce654f..02ecdcd 100644 --- a/custom_components/hubspace/fan.py +++ b/custom_components/hubspace/fan.py @@ -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__) @@ -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 @@ -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 @@ -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.""" @@ -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 @@ -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() @@ -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: @@ -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: @@ -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: @@ -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() @@ -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) diff --git a/custom_components/hubspace/hubspace_entity.py b/custom_components/hubspace/hubspace_entity.py new file mode 100644 index 0000000..60db3d7 --- /dev/null +++ b/custom_components/hubspace/hubspace_entity.py @@ -0,0 +1,127 @@ +__all__ = ["HubSpaceEntity"] + +import logging +from typing import List, Optional + +from homeassistant.core import callback +from homeassistant.helpers.device_registry import DeviceInfo +from homeassistant.helpers.update_coordinator import CoordinatorEntity +from hubspace_async import HubSpaceDevice, HubSpaceState + +from .const import DOMAIN +from .coordinator import HubSpaceDataUpdateCoordinator + +_LOGGER = logging.getLogger(__name__) + + +class HubSpaceEntity(CoordinatorEntity): + """Base entity for HubSpace items + + :ivar _availability: If the device is available within HubSpace HS device + :ivar _bonus_attrs: Attributes relayed to Home Assistant that do not need to be + tracked in their own class variables + :ivar _device: HubSpace Device to represent + :ivar _hs: HubSpace connector + :ivar _instance_attrs: Additional attributes that are required when + POSTing to HubSpace + """ + + ENTITY_TYPE: str = None + + def __init__( + self, + coordinator: HubSpaceDataUpdateCoordinator, + device: HubSpaceDevice, + ) -> None: + super().__init__(coordinator, context=device.friendly_name) + self._device = device + self.coordinator = coordinator + self._hs = coordinator.conn + self._availability: Optional[bool] = None + self._bonus_attrs = { + "model": device.model, + "deviceId": device.device_id, + "Child ID": self._child_id, + } + self._instance_attrs: dict[str, str] = {} + functions = device.functions or [] + self.process_functions(functions) + + @property + def name(self) -> str: + """Return the display name""" + return self._device.friendly_name + + @property + def unique_id(self) -> str: + """Return the HubSpace ID""" + 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 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 should_poll(self): + return False + + @callback + def _handle_coordinator_update(self) -> None: + """Handle updated data from the coordinator.""" + self.update_states() + self.async_write_ha_state() + + @property + def _child_id(self): + return self._device.id + + def process_functions(self, functions: list[dict]) -> None: + """Implemented by the entity""" + pass + + def update_states(self) -> None: + """Implemented by the entity""" + pass + + async def set_device_state(self, state: HubSpaceState) -> None: + await self.set_device_states([state]) + + async def set_device_states(self, states: List[HubSpaceState]) -> None: + await self._hs.set_device_states(self._child_id, states) + + def get_device(self) -> HubSpaceDevice: + try: + return self.coordinator.data[self.ENTITY_TYPE][self._child_id] + except KeyError: + _LOGGER.debug( + "No device found for %s %s.", self.ENTITY_TYPE, self._child_id + ) + raise + + def get_device_states(self) -> list[HubSpaceState]: + try: + return self.get_device().states + except KeyError: + _LOGGER.debug( + "No device found for %s %s. Maybe hasn't polled yet?", + self.ENTITY_TYPE, + self._child_id, + ) + return [] diff --git a/custom_components/hubspace/light.py b/custom_components/hubspace/light.py index 3536219..b6b16a5 100644 --- a/custom_components/hubspace/light.py +++ b/custom_components/hubspace/light.py @@ -15,16 +15,15 @@ LightEntity, LightEntityFeature, ) -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 hubspace_async import HubSpaceState +from hubspace_async import HubSpaceDevice, HubSpaceState from . import HubSpaceConfigEntry from .const import DOMAIN, ENTITY_LIGHT from .coordinator import HubSpaceDataUpdateCoordinator +from .hubspace_entity import HubSpaceEntity _LOGGER = logging.getLogger(__name__) @@ -86,64 +85,33 @@ def process_color_temps(color_temps: dict) -> list[int]: return sorted(supported_temps) -class HubspaceLight(CoordinatorEntity, LightEntity): +class HubspaceLight(HubSpaceEntity, LightEntity): """HubSpace light that can communicate with Home Assistant @TODO - Support for HS, RGBW, RGBWW, XY - :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 _bonus_attrs: Attributes relayed to Home Assistant that do not need to be - tracked in their own class variables - :ivar _availability: If the device is available within HubSpace - :ivar _instance_attrs: Additional attributes that are required when - POSTing to HubSpace - :ivar _color_modes: Supported options for the light + :ivar _brightness: Current brightness of the light :ivar _color_mode: Current color mode of the light + :ivar _color_modes: Supported options for the light :ivar _color_temp: Current temperature of the light + :ivar _current_effect: Current effect of the light + :ivar _effects: Dictionary of supported effects + :ivar _rgb: Current RGB values + :ivar _state: Current state of the fan + :ivar _supported_brightness: Supported brightness of the light + :ivar _supported_features: Features supported by the light :ivar _temperature_choices: Supported temperatures of the light :ivar _temperature_prefix: Prefix for HubSpace - :ivar _brightness: Current brightness of the light - :ivar _supported_brightness: Supported brightness of the light - :ivar _rgb: Current RGB values - :ivar _effects: Dictionary of supported effects - - - :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_LIGHT + _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, + self, 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 - self._state: Optional[str] = None - self._bonus_attrs = { - "model": model, - "deviceId": device_id, - "Child ID": self._child_id, - } - self._availability: Optional[bool] = None - self._instance_attrs: dict[str, str] = {} - # Entity-specific + super().__init__(coordinator, device) self._color_modes: set[ColorMode] = set() self._color_mode: Optional[ColorMode] = None self._color_temp: Optional[int] = None @@ -155,17 +123,9 @@ def __init__( self._supported_features: LightEntityFeature = LightEntityFeature(0) self._effects: dict[str, list[str]] = defaultdict(list) self._current_effect: Optional[str] = None - - functions = functions or [] - self.process_functions(functions) + self._state: Optional[str] = None self._adjust_supported_modes() - @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 @@ -212,15 +172,6 @@ def process_functions(self, functions: list[dict]) -> None: ) self._instance_attrs.pop(function["functionClass"], None) - def get_device_states(self) -> list[HubSpaceState]: - try: - return self.coordinator.data[ENTITY_LIGHT][self._child_id].states - except KeyError: - _LOGGER.debug( - "No device found for %s. Maybe hasn't polled yet?", self._child_id - ) - return [] - def update_states(self) -> None: """Load initial states into the device @@ -295,30 +246,6 @@ def determine_states_from_hs_mode( ] return color_mode_states - @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.""" @@ -327,18 +254,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 supported_color_modes(self) -> set[ColorMode]: """Flag supported color modes.""" @@ -490,7 +405,7 @@ async def async_turn_on(self, **kwargs) -> None: if effect_states: states_to_set.extend(effect_states) self._color_mode = ColorMode.RGB - await self._hs.set_device_states(self._child_id, states_to_set) + await self.set_device_states(states_to_set) self.async_write_ha_state() async def determine_effect_states(self, effect: str) -> list[HubSpaceState]: @@ -533,7 +448,7 @@ async def async_turn_off(self, **kwargs) -> None: value=self._state, ) ] - await self._hs.set_device_states(self._child_id, states_to_set) + await self.set_device_states(states_to_set) self.async_write_ha_state() @@ -557,13 +472,6 @@ async def async_setup_entry( model=entity.model, manufacturer=entity.manufacturerName, ) - ha_entity = HubspaceLight( - coordinator_hubspace, - entity.friendly_name, - child_id=entity.id, - model=entity.model, - device_id=entity.device_id, - functions=entity.functions, - ) + ha_entity = HubspaceLight(coordinator_hubspace, entity) entities.append(ha_entity) async_add_entities(entities) diff --git a/custom_components/hubspace/lock.py b/custom_components/hubspace/lock.py index ff9c1b2..7106d41 100644 --- a/custom_components/hubspace/lock.py +++ b/custom_components/hubspace/lock.py @@ -2,58 +2,36 @@ from typing import Optional from homeassistant.components.lock import LockEntity, LockEntityFeature -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 hubspace_async import HubSpaceState +from hubspace_async import HubSpaceDevice, HubSpaceState from . import HubSpaceConfigEntry from .const import DOMAIN, ENTITY_LOCK from .coordinator import HubSpaceDataUpdateCoordinator +from .hubspace_entity import HubSpaceEntity _LOGGER = logging.getLogger(__name__) -class HubSpaceLock(CoordinatorEntity, LockEntity): +class HubSpaceLock(HubSpaceEntity, LockEntity): """HubSpace lock 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 _bonus_attrs: Attributes relayed to Home Assistant that do not need to be - tracked in their own class variables :ivar _current_position: Current position of the device :ivar _supported_features: Supported features of the device - :ivar _availability: If the device is available within HubSpace """ + ENTITY_TYPE = ENTITY_LOCK + 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] = 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 - self._bonus_attrs = { - "model": model, - "deviceId": device_id, - "Child ID": self._child_id, - } - self._availability: Optional[bool] = None - # Entity-specific + super().__init__(coordinator, device) self._current_position: Optional[str] = None self._supported_features: Optional[LockEntityFeature] = LockEntityFeature(0) - functions = functions or [] - self.process_functions(functions) def process_functions(self, functions: list[dict]) -> None: """Process available functions @@ -67,61 +45,15 @@ def process_functions(self, functions: list[dict]) -> None: if value["name"] == "unlocked": self._supported_features |= LockEntityFeature.OPEN - @callback - def _handle_coordinator_update(self) -> None: - """Handle updated data from the coordinator.""" - self.update_states() - self.async_write_ha_state() - def update_states(self) -> None: """Load initial states into the device""" - states: list[HubSpaceState] = self.coordinator.data[ENTITY_LOCK][ - self._child_id - ].states - if not states: - _LOGGER.debug( - "No states found for %s. Maybe hasn't polled yet?", self._child_id - ) - _LOGGER.debug("About to update using %s", states) - # functionClass -> internal attribute - for state in states: + for state in self.get_device_states(): if state.functionClass == "available": self._availability = state.value elif state.functionClass == "lock-control": _LOGGER.debug("Found lock-control and setting to %s", state.value) self._current_position = state.value - @property - def name(self) -> str: - """Return the display name""" - return self._name - - @property - def unique_id(self) -> str: - """Return the HubSpace ID""" - 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 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 supported_features(self) -> LockEntityFeature: return self._supported_features @@ -147,7 +79,7 @@ def is_open(self) -> bool: return self._current_position == "unlocked" async def async_unlock(self, **kwargs) -> None: - _LOGGER.debug("Unlocking %s [%s]", self._name, self._child_id) + _LOGGER.debug("Unlocking %s [%s]", self.name, self._child_id) self._current_position = "unlocking" states_to_set = [ HubSpaceState( @@ -156,11 +88,11 @@ async def async_unlock(self, **kwargs) -> None: value=self._current_position, ) ] - await self._hs.set_device_states(self._child_id, states_to_set) + await self.set_device_states(states_to_set) self.async_write_ha_state() async def async_lock(self, **kwargs) -> None: - _LOGGER.debug("Locking %s [%s]", self._name, self._child_id) + _LOGGER.debug("Locking %s [%s]", self.name, self._child_id) self._current_position = "locking" states_to_set = [ HubSpaceState( @@ -193,13 +125,6 @@ async def async_setup_entry( model=entity.model, manufacturer=entity.manufacturerName, ) - ha_entity = HubSpaceLock( - coordinator_hubspace, - entity.friendly_name, - child_id=entity.id, - model=entity.model, - device_id=entity.device_id, - functions=entity.functions, - ) + ha_entity = HubSpaceLock(coordinator_hubspace, entity) entities.append(ha_entity) async_add_entities(entities) diff --git a/custom_components/hubspace/sensor.py b/custom_components/hubspace/sensor.py index 61e3ce9..8447fff 100644 --- a/custom_components/hubspace/sensor.py +++ b/custom_components/hubspace/sensor.py @@ -1,23 +1,29 @@ import logging -from typing import Any +from typing import Any, Optional from homeassistant.components.sensor import SensorEntity, SensorEntityDescription from homeassistant.components.sensor import const as sensor_const -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_SENSOR +from .const import ENTITY_SENSOR from .coordinator import HubSpaceDataUpdateCoordinator +from .hubspace_entity import HubSpaceEntity _LOGGER = logging.getLogger(__name__) -class HubSpaceSensor(CoordinatorEntity, SensorEntity): - """HubSpace child sensor component""" +class HubSpaceSensor(HubSpaceEntity, SensorEntity): + """HubSpace child sensor component + + :ivar entity_description: Description of the entity + :ivar _is_numeric: If the sensor is a numeric value + :ivar _sensor_value: Current value of the sensor + """ + + ENTITY_TYPE = ENTITY_SENSOR def __init__( self, @@ -26,52 +32,19 @@ def __init__( device: HubSpaceDevice, is_numeric: bool, ) -> None: - super().__init__(coordinator, context=device.id) - self.coordinator = coordinator - self.entity_description = description - self._device = device + super().__init__(coordinator, device) + self.entity_description: SensorEntityDescription = description self._is_numeric: bool = is_numeric - 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[bool] = None def update_states(self) -> None: """Handle updated data from the coordinator.""" - states: list[HubSpaceState] = self.coordinator.data[ENTITY_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.entity_description.key: if self._is_numeric and isinstance(state.value, str): state.value = int("".join(i for i in state.value if i.isdigit())) 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.key}" - - @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 native_value(self) -> Any: """Return the state.""" diff --git a/custom_components/hubspace/switch.py b/custom_components/hubspace/switch.py index 70201a7..7ee3a9c 100644 --- a/custom_components/hubspace/switch.py +++ b/custom_components/hubspace/switch.py @@ -2,113 +2,48 @@ from typing import Optional from homeassistant.components.switch import SwitchEntity -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 hubspace_async import HubSpaceDevice, HubSpaceState from . import HubSpaceConfigEntry from .const import DOMAIN, ENTITY_SWITCH from .coordinator import HubSpaceDataUpdateCoordinator +from .hubspace_entity import HubSpaceEntity _LOGGER = logging.getLogger(__name__) -class HubSpaceSwitch(CoordinatorEntity, SwitchEntity): +class HubSpaceSwitch(HubSpaceEntity, SwitchEntity): """HubSpace switch-type 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 _bonus_attrs: Attributes relayed to Home Assistant that do not need to be - tracked in their own class variables - :ivar _availability: If the device is available within HubSpace - :ivar _device_class: Device class used during lookup :ivar _instance: functionInstance within the HS device + :ivar _state: Current state of the switch """ + ENTITY_TYPE = ENTITY_SWITCH + def __init__( self, - hs: HubSpaceDataUpdateCoordinator, - friendly_name: str, + coordinator: HubSpaceDataUpdateCoordinator, + device: HubSpaceDevice, instance: Optional[str], - child_id: Optional[str] = None, - model: Optional[str] = None, - device_id: Optional[str] = None, ) -> 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._instance: Optional[str] = instance self._state: Optional[str] = None - self._bonus_attrs = { - "model": model, - "deviceId": device_id, - "Child ID": self._child_id, - } - self._availability: Optional[bool] = None - # Entity-specific - self._instance = instance - - @callback - def _handle_coordinator_update(self) -> None: - """Handle updated data from the coordinator.""" - self.update_states() - self.async_write_ha_state() def update_states(self) -> None: """Load initial states into the device""" - states: list[HubSpaceState] = self.coordinator.data[ENTITY_SWITCH][ - self._child_id - ].states - if not states: - _LOGGER.debug( - "No states found for %s. Maybe hasn't polled yet?", self._child_id - ) - # functionClass -> internal attribute - for state in states: + for state in self.get_device_states(): if state.functionClass == "available": self._availability = state.value elif state.functionClass != self.primary_class: continue - elif self._instance and state.functionInstance != self._instance: - continue - else: + elif not self._instance or state.functionInstance == self._instance: self._state = state.value - @property - def should_poll(self): - return False - - @property - def name(self) -> str: - """Return the display name""" - if self._instance: - return f"{self._name} - {self._instance}" - else: - return self._name - - @property - def unique_id(self) -> str: - """Return the HubSpace ID""" - if self._instance: - return f"{self._child_id}-{self._instance}" - else: - 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 device is on.""" @@ -117,18 +52,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 primary_class(self) -> str: return "toggle" if self._instance else "power" @@ -172,11 +95,8 @@ async def setup_entry_toggled( _LOGGER.debug("Adding a %s [%s] @ %s", entity.device_class, entity.id, instance) ha_entity = HubSpaceSwitch( coordinator_hubspace, - entity.friendly_name, - instance, - child_id=entity.id, - model=entity.model, - device_id=entity.device_id, + entity, + instance=instance, ) valid.append(ha_entity) return valid @@ -189,11 +109,8 @@ async def setup_basic_switch( _LOGGER.debug("No toggleable elements found. Setting up as a basic switch") ha_entity = HubSpaceSwitch( coordinator_hubspace, - entity.friendly_name, - None, - child_id=entity.id, - model=entity.model, - device_id=entity.device_id, + entity, + instance=None, ) return ha_entity diff --git a/custom_components/hubspace/valve.py b/custom_components/hubspace/valve.py index 6a88d54..4acf3b6 100644 --- a/custom_components/hubspace/valve.py +++ b/custom_components/hubspace/valve.py @@ -6,124 +6,60 @@ ValveEntity, ValveEntityFeature, ) -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 hubspace_async import HubSpaceDevice, HubSpaceState from . import HubSpaceConfigEntry from .const import DOMAIN, ENTITY_VALVE from .coordinator import HubSpaceDataUpdateCoordinator +from .hubspace_entity import HubSpaceEntity _LOGGER = logging.getLogger(__name__) -class HubSpaceValve(CoordinatorEntity, ValveEntity): +class HubSpaceValve(HubSpaceEntity, ValveEntity): """HubSpace switch-type 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 _bonus_attrs: Attributes relayed to Home Assistant that do not need to be - tracked in their own class variables - :ivar _availability: If the device is available within HubSpace - :ivar _instance: functionInstance within the HS device :ivar _current_valve_position: Current position of the valve + :ivar _instance: functionInstance within the HS device :ivar _reports_position: Reports position of the valve + :ivar _state: If the device is on / off """ + ENTITY_TYPE = ENTITY_VALVE + def __init__( self, - hs: HubSpaceDataUpdateCoordinator, - friendly_name: str, + coordinator: HubSpaceDataUpdateCoordinator, + device: HubSpaceDevice, instance: Optional[str], - child_id: Optional[str] = None, - model: Optional[str] = None, - device_id: Optional[str] = None, ) -> 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 - self._state: Optional[str] = None - self._bonus_attrs = { - "model": model, - "deviceId": device_id, - "Child ID": self._child_id, - } - self._availability: Optional[bool] = None - # Entity-specific + super().__init__(coordinator, device) # Assume that all HubSpace devices allow for open / close self._supported_features: ValveEntityFeature = ( ValveEntityFeature.OPEN | ValveEntityFeature.CLOSE ) + self._state: Optional[str] = None self._instance = instance self._current_valve_position: int | None = None self._reports_position: bool = True - @callback - def _handle_coordinator_update(self) -> None: - """Handle updated data from the coordinator.""" - self.update_states() - self.async_write_ha_state() - def update_states(self) -> None: """Load initial states into the device""" - states: list[HubSpaceState] = self.coordinator.data[ENTITY_VALVE][ - self._child_id - ].states - if not states: - _LOGGER.debug( - "No states found for %s. Maybe hasn't polled yet?", self._child_id - ) - # functionClass -> internal attribute - for state in states: + for state in self.get_device_states(): if state.functionClass == "available": self._availability = state.value elif state.functionClass != "toggle": continue - if not self._instance: - self._state = state.value - elif state.functionInstance == self._instance: + if not self._instance or state.functionInstance == self._instance: self._state = state.value - @property - def should_poll(self): - return False - - @property - def name(self) -> str: - """Return the display name""" - if self._instance: - return f"{self._name} - {self._instance}" - else: - return self._name - - @property - def unique_id(self) -> str: - """Return the HubSpace ID""" - if self._instance: - return f"{self._child_id}-{self._instance}" - else: - return self._child_id - - @property - def available(self) -> bool: - return self._availability is True - @property def supported_features(self) -> ValveEntityFeature: return self._supported_features - @property - def extra_state_attributes(self): - """Return the state attributes.""" - return self._bonus_attrs - @property def reports_position(self) -> bool: """Return true if device is on.""" @@ -133,18 +69,6 @@ def reports_position(self) -> bool: def current_valve_position(self) -> Optional[int]: return 100 if self._state == "on" else 0 - @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 device_class(self) -> ValveDeviceClass: return ValveDeviceClass.WATER @@ -188,11 +112,8 @@ async def setup_entry_toggled( _LOGGER.debug("Adding a %s [%s] @ %s", entity.device_class, entity.id, instance) ha_entity = HubSpaceValve( coordinator_hubspace, - entity.friendly_name, - instance, - child_id=entity.id, - model=entity.model, - device_id=entity.device_id, + entity, + instance=instance, ) valid.append(ha_entity) return valid @@ -205,11 +126,8 @@ async def setup_basic_valve( _LOGGER.debug("No toggleable elements found. Setting up as a single valve") ha_entity = HubSpaceValve( coordinator_hubspace, - entity.friendly_name, - None, - child_id=entity.id, - model=entity.model, - device_id=entity.device_id, + entity, + instance=None, ) return ha_entity diff --git a/tests/test_binary_sensor.py b/tests/test_binary_sensor.py index 0669967..1a7d5a4 100644 --- a/tests/test_binary_sensor.py +++ b/tests/test_binary_sensor.py @@ -22,14 +22,12 @@ ), ], ) -def test_sensor(sensor_descr, device, expected, mocked_coordinator): +def test_sensor(sensor_descr, device, expected, mocked_coordinator, mocker): empty_sensor = binary_sensor.HubSpaceBinarySensor( mocked_coordinator, sensor_descr, device, ) - empty_sensor.coordinator.data[const.ENTITY_BINARY_SENSOR][device.id] = { - "device": device - } + mocker.patch.object(empty_sensor, "get_device_states", return_value=device.states) empty_sensor.update_states() assert empty_sensor.is_on == expected diff --git a/tests/test_fan.py b/tests/test_fan.py index d647af8..54b4548 100644 --- a/tests/test_fan.py +++ b/tests/test_fan.py @@ -2,6 +2,7 @@ import pytest from homeassistant.components.fan import FanEntityFeature +from hubspace_async import HubSpaceDevice from custom_components.hubspace import fan from custom_components.hubspace.const import ENTITY_FAN @@ -10,6 +11,19 @@ fan_zandra = create_devices_from_data("fan-ZandraFan.json") +dummy_device = HubSpaceDevice( + "child_id", + "device_id", + "test_model", + "fan", + "device_name", + "friendly_image", + "test fan", + functions=[], + states=[], + children=[], +) + process_functions_expected = ( FanEntityFeature.PRESET_MODE @@ -23,12 +37,12 @@ @pytest.fixture def empty_fan(mocked_coordinator): - yield fan.HubspaceFan(mocked_coordinator, "test fan") + yield fan.HubspaceFan(mocked_coordinator, dummy_device) @pytest.fixture def speed_fan(mocked_coordinator): - test_fan = fan.HubspaceFan(mocked_coordinator, "test fan") + test_fan = fan.HubspaceFan(mocked_coordinator, dummy_device) test_fan._supported_features = process_functions_expected test_fan._fan_speeds = [ "fan-speed-6-016", @@ -86,9 +100,9 @@ def test_process_functions(self, functions, expected_attrs, empty_fan): "_availability": True, }, { - "model": None, - "deviceId": None, - "Child ID": None, + "model": "test_model", + "deviceId": "device_id", + "Child ID": "child_id", "wifi-ssid": "71e7209f-b932-44b9-ba2f-a8179f68c3ac", "wifi-mac-address": "e1119e0a-688d-45df-9882-a76549db9bc3", "ble-mac-address": "07346a23-350b-4606-8d86-67217ec7a688", @@ -108,7 +122,7 @@ def test_name(self, empty_fan): assert empty_fan.name == "test fan" def test_unique_id(self, empty_fan): - empty_fan._child_id = "beans" + empty_fan._device.id = "beans" assert empty_fan.unique_id == "beans" @pytest.mark.parametrize( @@ -126,13 +140,19 @@ def test_extra_state_attributes(self, mocked_coordinator): model = "bean model" device_id = "bean-123" child_id = "bean-123-123" - test_fan = fan.HubspaceFan( - mocked_coordinator, + dummy_device = HubSpaceDevice( + child_id, + device_id, + model, + "fan", + "device_name", + "friendly_image", "test fan", - model=model, - device_id=device_id, - child_id=child_id, + functions=[], + states=[], + children=[], ) + test_fan = fan.HubspaceFan(mocked_coordinator, dummy_device) assert test_fan.extra_state_attributes == { "model": model, "deviceId": device_id, diff --git a/tests/test_hubspace_entity.py b/tests/test_hubspace_entity.py new file mode 100644 index 0000000..cfff7b5 --- /dev/null +++ b/tests/test_hubspace_entity.py @@ -0,0 +1,16 @@ +# def test_extra_state_attributes(mocked_coordinator): +# model = "bean model" +# device_id = "bean-123" +# child_id = "bean-123-123" +# test_fan = light.HubspaceLight( +# mocked_coordinator, +# "test light", +# model=model, +# device_id=device_id, +# child_id=child_id, +# ) +# assert test_fan.extra_state_attributes == { +# "model": model, +# "deviceId": device_id, +# "Child ID": child_id, +# } diff --git a/tests/test_light.py b/tests/test_light.py index ecca178..5bb88de 100644 --- a/tests/test_light.py +++ b/tests/test_light.py @@ -24,6 +24,20 @@ rgbw_led_strip = create_devices_from_data("rgbw-led-strip.json")[0] +dummy_device = HubSpaceDevice( + "child_id", + "device_id", + "test_model", + "light", + "device_name", + "friendly_image", + "test light", + functions=[], + states=[], + children=[], +) + + def modify_state(device: HubSpaceDevice, new_state): for ind, state in enumerate(device.states): if state.functionClass != new_state.functionClass: @@ -66,12 +80,12 @@ def modify_state(device: HubSpaceDevice, new_state): @pytest.fixture def empty_light(mocked_coordinator): - yield light.HubspaceLight(mocked_coordinator, "test light") + yield light.HubspaceLight(mocked_coordinator, dummy_device) @pytest.fixture def temperature_light(mocked_coordinator): - temp_light = light.HubspaceLight(mocked_coordinator, "test light") + temp_light = light.HubspaceLight(mocked_coordinator, dummy_device) temp_light._temperature_choices = [2700, 3000, 3500] yield temp_light @@ -234,7 +248,7 @@ def test_determine_states_from_hs_mode(device, expected, mocker, empty_light): @pytest.mark.parametrize( - "states, expected_attrs, extra_attrs", + "states, expected_attrs", [ ( fan_zandra_light.states, @@ -244,11 +258,6 @@ def test_determine_states_from_hs_mode(device, expected, mocker, empty_light): "_brightness": 114, "_availability": True, }, - { - "Child ID": None, - "deviceId": None, - "model": None, - }, ), # Switch from white to RGB ( @@ -261,7 +270,6 @@ def test_determine_states_from_hs_mode(device, expected, mocker, empty_light): "_color_mode": light.ColorMode.RGB, "_current_effect": None, }, - {}, ), # set current effect ( @@ -274,15 +282,12 @@ def test_determine_states_from_hs_mode(device, expected, mocker, empty_light): "_current_effect": "rainbow", "_color_mode": light.ColorMode.BRIGHTNESS, }, - {}, ), ], ) -def test_update_states(states, expected_attrs, extra_attrs, empty_light, mocker): +def test_update_states(states, expected_attrs, empty_light, mocker): mocker.patch.object(empty_light, "get_device_states", return_value=states) empty_light.update_states() - if extra_attrs: - assert empty_light.extra_state_attributes == extra_attrs for key, val in expected_attrs.items(): assert getattr(empty_light, key) == val @@ -292,7 +297,7 @@ def test_name(empty_light): def test_unique_id(empty_light): - empty_light._child_id = "beans" + empty_light._device.id = "beans" assert empty_light.unique_id == "beans" @@ -308,24 +313,6 @@ def test_is_on(state, expected, empty_light): assert empty_light.is_on == expected -def test_extra_state_attributes(mocked_coordinator): - model = "bean model" - device_id = "bean-123" - child_id = "bean-123-123" - test_fan = light.HubspaceLight( - mocked_coordinator, - "test light", - model=model, - device_id=device_id, - child_id=child_id, - ) - assert test_fan.extra_state_attributes == { - "model": model, - "deviceId": device_id, - "Child ID": child_id, - } - - @pytest.mark.asyncio @pytest.mark.parametrize( "device, effect, expected", diff --git a/tests/test_lock.py b/tests/test_lock.py index 0e26589..f8f7c18 100644 --- a/tests/test_lock.py +++ b/tests/test_lock.py @@ -1,4 +1,5 @@ import pytest +from hubspace_async import HubSpaceDevice from custom_components.hubspace import lock from custom_components.hubspace.const import ENTITY_LOCK @@ -8,14 +9,27 @@ lock_tbd = create_devices_from_data("door-lock-TBD.json") lock_tbd_instance = lock_tbd[0] +dummy_device = HubSpaceDevice( + "child_id", + "device_id", + "test_model", + "light", + "device_name", + "friendly_image", + "test lock", + functions=[], + states=[], + children=[], +) + @pytest.fixture def empty_lock(mocked_coordinator): - yield lock.HubSpaceLock(mocked_coordinator, "test lock") + yield lock.HubSpaceLock(mocked_coordinator, dummy_device) @pytest.mark.parametrize( - "states, expected_attrs, extra_attrs", + "states, expected_attrs", [ ( lock_tbd_instance.states, @@ -23,19 +37,13 @@ def empty_lock(mocked_coordinator): "_current_position": "locked", "_availability": True, }, - { - "Child ID": None, - "deviceId": None, - "model": None, - }, ) ], ) -def test_update_states(states, expected_attrs, extra_attrs, empty_lock): +def test_update_states(states, expected_attrs, empty_lock): empty_lock.states = states empty_lock.coordinator.data[ENTITY_LOCK][empty_lock._child_id] = empty_lock empty_lock.update_states() - assert empty_lock.extra_state_attributes == extra_attrs for key, val in expected_attrs.items(): assert getattr(empty_lock, key) == val @@ -45,28 +53,10 @@ def test_name(empty_lock): def test_unique_id(empty_lock): - empty_lock._child_id = "beans" + empty_lock._device.id = "beans" assert empty_lock.unique_id == "beans" -def test_extra_state_attributes(mocked_coordinator): - model = "bean model" - device_id = "bean-123" - child_id = "bean-123-123" - test_fan = lock.HubSpaceLock( - mocked_coordinator, - "test lock", - model=model, - device_id=device_id, - child_id=child_id, - ) - assert test_fan.extra_state_attributes == { - "model": model, - "deviceId": device_id, - "Child ID": child_id, - } - - @pytest.mark.asyncio async def test_async_lock(empty_lock): await empty_lock.async_lock() diff --git a/tests/test_sensor.py b/tests/test_sensor.py index 80ce22a..fa9bab2 100644 --- a/tests/test_sensor.py +++ b/tests/test_sensor.py @@ -17,14 +17,14 @@ (const.SENSORS_GENERAL["wifi-rssi"], transformer[0], True, -51), ], ) -def test_sensor(sensor_descr, device, is_numeric, expected, mocked_coordinator): +def test_sensor(sensor_descr, device, is_numeric, expected, mocked_coordinator, mocker): empty_sensor = sensor.HubSpaceSensor( mocked_coordinator, sensor_descr, device, is_numeric, ) - empty_sensor.coordinator.data[const.ENTITY_SENSOR][device.id] = {"device": device} + mocker.patch.object(empty_sensor, "get_device_states", return_value=device.states) empty_sensor.update_states() # Ensure the state can be correctly calculated empty_sensor.state diff --git a/tests/test_switch.py b/tests/test_switch.py index b331633..2f8ed09 100644 --- a/tests/test_switch.py +++ b/tests/test_switch.py @@ -1,14 +1,28 @@ import pytest +from hubspace_async import HubSpaceDevice from custom_components.hubspace import switch from custom_components.hubspace.const import ENTITY_SWITCH from .utils import create_devices_from_data +dummy_device = HubSpaceDevice( + "child_id", + "device_id", + "test_model", + "switch", + "device_name", + "friendly_image", + "test switch", + functions=[], + states=[], + children=[], +) + @pytest.fixture def single_switch(mocked_coordinator): - yield switch.HubSpaceSwitch(mocked_coordinator, "test switch", None) + yield switch.HubSpaceSwitch(mocked_coordinator, dummy_device, instance=None) transformer = create_devices_from_data("transformer.json")[0] diff --git a/tests/test_valve.py b/tests/test_valve.py index 99a3c18..a6eb024 100644 --- a/tests/test_valve.py +++ b/tests/test_valve.py @@ -1,14 +1,28 @@ import pytest +from hubspace_async import HubSpaceDevice from custom_components.hubspace import valve from custom_components.hubspace.const import ENTITY_VALVE from .utils import create_devices_from_data +dummy_device = HubSpaceDevice( + "child_id", + "device_id", + "test_model", + "valve", + "device_name", + "friendly_image", + "test valve", + functions=[], + states=[], + children=[], +) + @pytest.fixture def empty_valve(mocked_coordinator): - yield valve.HubSpaceValve(mocked_coordinator, "test valve", None) + yield valve.HubSpaceValve(mocked_coordinator, dummy_device, instance=None) spigot = create_devices_from_data("water-timer.json")[0]