Add button platform to Opengarage (#103569)

* Add button entity to reboot OpenGarage device

* Addressing code review comments

* Another code-review fix

* Update homeassistant/components/opengarage/button.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
pull/104208/head^2
Joshua Krall 2024-01-03 23:37:24 -07:00 committed by GitHub
parent 4fa76801af
commit 53717523e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 173 additions and 4 deletions

View File

@ -18,13 +18,11 @@ from .const import CONF_DEVICE_KEY, DOMAIN
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.BINARY_SENSOR, Platform.COVER, Platform.SENSOR]
PLATFORMS = [Platform.BINARY_SENSOR, Platform.BUTTON, Platform.COVER, Platform.SENSOR]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up OpenGarage from a config entry."""
hass.data.setdefault(DOMAIN, {})
open_garage_connection = opengarage.OpenGarage(
f"{entry.data[CONF_HOST]}:{entry.data[CONF_PORT]}",
entry.data[CONF_DEVICE_KEY],
@ -36,7 +34,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
open_garage_connection=open_garage_connection,
)
await open_garage_data_coordinator.async_config_entry_first_refresh()
hass.data[DOMAIN][entry.entry_id] = open_garage_data_coordinator
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = open_garage_data_coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

View File

@ -0,0 +1,79 @@
"""OpenGarage button."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from typing import Any, cast
from opengarage import OpenGarage
from homeassistant.components.button import (
ButtonDeviceClass,
ButtonEntity,
ButtonEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import OpenGarageDataUpdateCoordinator
from .const import DOMAIN
from .entity import OpenGarageEntity
@dataclass(frozen=True, kw_only=True)
class OpenGarageButtonEntityDescription(ButtonEntityDescription):
"""OpenGarage Browser button description."""
press_action: Callable[[OpenGarage], Any]
BUTTONS: tuple[OpenGarageButtonEntityDescription, ...] = (
OpenGarageButtonEntityDescription(
key="restart",
device_class=ButtonDeviceClass.RESTART,
entity_category=EntityCategory.CONFIG,
press_action=lambda opengarage: opengarage.reboot(),
),
)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the OpenGarage button entities."""
coordinator: OpenGarageDataUpdateCoordinator = hass.data[DOMAIN][
config_entry.entry_id
]
async_add_entities(
OpenGarageButtonEntity(
coordinator, cast(str, config_entry.unique_id), description
)
for description in BUTTONS
)
class OpenGarageButtonEntity(OpenGarageEntity, ButtonEntity):
"""Representation of an OpenGarage button."""
entity_description: OpenGarageButtonEntityDescription
def __init__(
self,
coordinator: OpenGarageDataUpdateCoordinator,
device_id: str,
description: OpenGarageButtonEntityDescription,
) -> None:
"""Initialize the button."""
super().__init__(coordinator, device_id, description)
async def async_press(self) -> None:
"""Press the button."""
await self.entity_description.press_action(
self.coordinator.open_garage_connection
)
await self.coordinator.async_refresh()

View File

@ -0,0 +1,59 @@
"""Fixtures for the OpenGarage integration tests."""
from __future__ import annotations
from collections.abc import Generator
from unittest.mock import MagicMock, patch
import pytest
from homeassistant.components.opengarage.const import CONF_DEVICE_KEY, DOMAIN
from homeassistant.const import CONF_HOST, CONF_PORT, CONF_VERIFY_SSL
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
@pytest.fixture
def mock_config_entry() -> MockConfigEntry:
"""Return the default mocked config entry."""
return MockConfigEntry(
title="Test device",
domain=DOMAIN,
data={
CONF_HOST: "http://1.1.1.1",
CONF_PORT: "80",
CONF_DEVICE_KEY: "abc123",
CONF_VERIFY_SSL: False,
},
unique_id="12345",
)
@pytest.fixture
def mock_opengarage() -> Generator[MagicMock, None, None]:
"""Return a mocked OpenGarage client."""
with patch(
"homeassistant.components.opengarage.opengarage.OpenGarage",
autospec=True,
) as client_mock:
client = client_mock.return_value
client.device_url = "http://1.1.1.1:80"
client.update_state.return_value = {
"name": "abcdef",
"mac": "aa:bb:cc:dd:ee:ff",
"fwv": "1.2.0",
}
yield client
@pytest.fixture
async def init_integration(
hass: HomeAssistant, mock_config_entry: MockConfigEntry, mock_opengarage: MagicMock
) -> MockConfigEntry:
"""Set up the OpenGarage integration for testing."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
return mock_config_entry

View File

@ -0,0 +1,33 @@
"""Test the OpenGarage Browser buttons."""
from unittest.mock import MagicMock
import homeassistant.components.button as button
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr, entity_registry as er
from tests.common import MockConfigEntry
async def test_buttons(
hass: HomeAssistant,
mock_opengarage: MagicMock,
init_integration: MockConfigEntry,
entity_registry: er.EntityRegistry,
device_registry: dr.DeviceRegistry,
) -> None:
"""Test standard OpenGarage buttons."""
entry = entity_registry.async_get("button.abcdef_restart")
assert entry
assert entry.unique_id == "12345_restart"
await hass.services.async_call(
button.DOMAIN,
button.SERVICE_PRESS,
{ATTR_ENTITY_ID: "button.abcdef_restart"},
blocking=True,
)
assert len(mock_opengarage.reboot.mock_calls) == 1
assert entry.device_id
device_entry = device_registry.async_get(entry.device_id)
assert device_entry