Addition of Flipr hub with switch platform (#125866)

* Addition of Flipr hub with switch platform

* Remove of loggers in tests

* Review corrections

* Review corrections

* Apply suggestions from code review

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
cnico 2024-09-17 15:56:07 +02:00 committed by GitHub
parent f3facac016
commit 2ae4989031
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 213 additions and 11 deletions

View File

@ -13,9 +13,9 @@ from homeassistant.exceptions import ConfigEntryError
from homeassistant.helpers import issue_registry as ir
from .const import DOMAIN
from .coordinator import FliprDataUpdateCoordinator
from .coordinator import FliprDataUpdateCoordinator, FliprHubDataUpdateCoordinator
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR, Platform.SWITCH]
_LOGGER = logging.getLogger(__name__)
@ -25,6 +25,7 @@ class FliprData:
"""The Flipr data class."""
flipr_coordinators: list[FliprDataUpdateCoordinator]
hub_coordinators: list[FliprHubDataUpdateCoordinator]
type FliprConfigEntry = ConfigEntry[FliprData]
@ -53,7 +54,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: FliprConfigEntry) -> boo
await flipr_coordinator.async_config_entry_first_refresh()
flipr_coordinators.append(flipr_coordinator)
entry.runtime_data = FliprData(flipr_coordinators)
hub_coordinators = []
for hub_id in ids["hub"]:
hub_coordinator = FliprHubDataUpdateCoordinator(hass, client, hub_id)
await hub_coordinator.async_config_entry_first_refresh()
hub_coordinators.append(hub_coordinator)
entry.runtime_data = FliprData(flipr_coordinators, hub_coordinators)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

View File

@ -6,5 +6,3 @@ ATTRIBUTION = "Flipr Data"
MANUFACTURER = "CTAC-TECH"
NAME = "Flipr"
CONF_ENTRY_FLIPR_COORDINATORS = "flipr_coordinators"

View File

@ -2,6 +2,7 @@
from datetime import timedelta
import logging
from typing import Any
from flipr_api import FliprAPIRestClient
from flipr_api.exceptions import FliprError
@ -13,8 +14,8 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
_LOGGER = logging.getLogger(__name__)
class FliprDataUpdateCoordinator(DataUpdateCoordinator):
"""Class to hold Flipr data retrieval."""
class BaseDataUpdateCoordinator[_DataT](DataUpdateCoordinator[_DataT]):
"""Parent class to hold Flipr and Hub data retrieval."""
config_entry: ConfigEntry
@ -32,7 +33,11 @@ class FliprDataUpdateCoordinator(DataUpdateCoordinator):
update_interval=timedelta(minutes=15),
)
async def _async_update_data(self):
class FliprDataUpdateCoordinator(BaseDataUpdateCoordinator[dict[str, Any]]):
"""Class to hold Flipr data retrieval."""
async def _async_update_data(self) -> dict[str, Any]:
"""Fetch data from API endpoint."""
try:
data = await self.hass.async_add_executor_job(
@ -42,3 +47,18 @@ class FliprDataUpdateCoordinator(DataUpdateCoordinator):
raise UpdateFailed(error) from error
return data
class FliprHubDataUpdateCoordinator(BaseDataUpdateCoordinator[dict[str, Any]]):
"""Class to hold Flipr hub data retrieval."""
async def _async_update_data(self) -> dict[str, Any]:
"""Fetch data from API endpoint."""
try:
data = await self.hass.async_add_executor_job(
self.client.get_hub_state, self.device_id
)
except FliprError as error:
raise UpdateFailed(error) from error
return data

View File

@ -5,10 +5,10 @@ from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import ATTRIBUTION, DOMAIN, MANUFACTURER
from .coordinator import FliprDataUpdateCoordinator
from .coordinator import BaseDataUpdateCoordinator
class FliprEntity(CoordinatorEntity):
class FliprEntity(CoordinatorEntity[BaseDataUpdateCoordinator]):
"""Implements a common class elements representing the Flipr component."""
_attr_attribution = ATTRIBUTION
@ -16,7 +16,7 @@ class FliprEntity(CoordinatorEntity):
def __init__(
self,
coordinator: FliprDataUpdateCoordinator,
coordinator: BaseDataUpdateCoordinator,
description: EntityDescription,
is_flipr_hub: bool = False,
) -> None:

View File

@ -0,0 +1,67 @@
"""Switch platform for the Flipr's Hub."""
import logging
from typing import Any
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import FliprConfigEntry
from .entity import FliprEntity
_LOGGER = logging.getLogger(__name__)
SWITCH_TYPES: tuple[SwitchEntityDescription, ...] = (
SwitchEntityDescription(
key="hubState",
name=None,
),
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: FliprConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up switch for Flipr hub."""
coordinators = config_entry.runtime_data.hub_coordinators
async_add_entities(
FliprHubSwitch(coordinator, description, True)
for description in SWITCH_TYPES
for coordinator in coordinators
)
class FliprHubSwitch(FliprEntity, SwitchEntity):
"""Switch representing Hub state."""
@property
def is_on(self) -> bool:
"""Return state of the switch."""
_LOGGER.debug("coordinator data = %s", self.coordinator.data)
return self.coordinator.data["state"]
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off the switch."""
_LOGGER.debug("Switching off %s", self.device_id)
data = await self.hass.async_add_executor_job(
self.coordinator.client.set_hub_state,
self.device_id,
False,
)
_LOGGER.debug("New hub infos are %s", data)
self.coordinator.async_set_updated_data(data)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on the switch."""
_LOGGER.debug("Switching on %s", self.device_id)
data = await self.hass.async_add_executor_job(
self.coordinator.client.set_hub_state,
self.device_id,
True,
)
_LOGGER.debug("New hub infos are %s", data)
self.coordinator.async_set_updated_data(data)

View File

@ -0,0 +1,110 @@
"""Test the Flipr switch for Hub."""
from unittest.mock import AsyncMock
from flipr_api.exceptions import FliprError
from homeassistant.components.switch import (
DOMAIN as SWITCH_DOMAIN,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
)
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import setup_integration
from .conftest import MOCK_HUB_STATE_OFF
from tests.common import MockConfigEntry
SWITCH_ENTITY_ID = "switch.flipr_hub_myhubid"
async def test_entities(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
mock_flipr_client: AsyncMock,
) -> None:
"""Test the creation and values of the Flipr switch."""
mock_flipr_client.search_all_ids.return_value = {"flipr": [], "hub": ["myhubid"]}
await setup_integration(hass, mock_config_entry)
# Check entity unique_id value that is generated in FliprEntity base class.
entity = entity_registry.async_get(SWITCH_ENTITY_ID)
assert entity.unique_id == "myhubid-hubState"
state = hass.states.get(SWITCH_ENTITY_ID)
assert state
assert state.state == STATE_ON
async def test_switch_actions(
hass: HomeAssistant,
mock_flipr_client: AsyncMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test the actions on the Flipr Hub switch."""
mock_flipr_client.search_all_ids.return_value = {"flipr": [], "hub": ["myhubid"]}
await setup_integration(hass, mock_config_entry)
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: SWITCH_ENTITY_ID},
blocking=True,
)
state = hass.states.get(SWITCH_ENTITY_ID)
assert state.state == STATE_ON
mock_flipr_client.set_hub_state.return_value = MOCK_HUB_STATE_OFF
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: SWITCH_ENTITY_ID},
blocking=True,
)
state = hass.states.get(SWITCH_ENTITY_ID)
assert state.state == STATE_OFF
async def test_no_switch_found(
hass: HomeAssistant,
mock_flipr_client: AsyncMock,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test the switch absence."""
mock_flipr_client.search_all_ids.return_value = {"flipr": [], "hub": []}
await setup_integration(hass, mock_config_entry)
assert not hass.states.async_entity_ids(SWITCH_DOMAIN)
async def test_error_flipr_api(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
mock_flipr_client: AsyncMock,
) -> None:
"""Test the Flipr sensors error."""
mock_flipr_client.search_all_ids.return_value = {"flipr": [], "hub": ["myhubid"]}
mock_flipr_client.get_hub_state.side_effect = FliprError(
"Error during flipr data retrieval..."
)
await setup_integration(hass, mock_config_entry)
# Check entity is not generated because of the FliprError raised.
entity = entity_registry.async_get(SWITCH_ENTITY_ID)
assert entity is None