-
-
Notifications
You must be signed in to change notification settings - Fork 31.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Sky remote integration (#124507)
Co-authored-by: Kyle Cooke <[email protected]> Co-authored-by: Joost Lekkerkerker <[email protected]>
- Loading branch information
1 parent
f6bc5f0
commit 72b976f
Showing
17 changed files
with
530 additions
and
5 deletions.
There are no files selected for viewing
Validating CODEOWNERS rules …
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
{ | ||
"domain": "sky", | ||
"name": "Sky", | ||
"integrations": ["sky_hub", "sky_remote"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
"""The Sky Remote Control integration.""" | ||
|
||
import logging | ||
|
||
from skyboxremote import RemoteControl, SkyBoxConnectionError | ||
|
||
from homeassistant.config_entries import ConfigEntry | ||
from homeassistant.const import CONF_HOST, CONF_PORT, Platform | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.exceptions import ConfigEntryNotReady | ||
|
||
PLATFORMS = [Platform.REMOTE] | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
type SkyRemoteConfigEntry = ConfigEntry[RemoteControl] | ||
|
||
|
||
async def async_setup_entry(hass: HomeAssistant, entry: SkyRemoteConfigEntry) -> bool: | ||
"""Set up Sky remote.""" | ||
host = entry.data[CONF_HOST] | ||
port = entry.data[CONF_PORT] | ||
|
||
_LOGGER.debug("Setting up Host: %s, Port: %s", host, port) | ||
remote = RemoteControl(host, port) | ||
try: | ||
await remote.check_connectable() | ||
except SkyBoxConnectionError as e: | ||
raise ConfigEntryNotReady from e | ||
|
||
entry.runtime_data = remote | ||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) | ||
return True | ||
|
||
|
||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: | ||
"""Unload a config entry.""" | ||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
"""Config flow for sky_remote.""" | ||
|
||
import logging | ||
from typing import Any | ||
|
||
from skyboxremote import RemoteControl, SkyBoxConnectionError | ||
import voluptuous as vol | ||
|
||
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult | ||
from homeassistant.const import CONF_HOST, CONF_PORT | ||
import homeassistant.helpers.config_validation as cv | ||
|
||
from .const import DEFAULT_PORT, DOMAIN, LEGACY_PORT | ||
|
||
DATA_SCHEMA = vol.Schema( | ||
{ | ||
vol.Required(CONF_HOST): cv.string, | ||
} | ||
) | ||
|
||
|
||
async def async_find_box_port(host: str) -> int: | ||
"""Find port box uses for communication.""" | ||
logging.debug("Attempting to find port to connect to %s on", host) | ||
remote = RemoteControl(host, DEFAULT_PORT) | ||
try: | ||
await remote.check_connectable() | ||
except SkyBoxConnectionError: | ||
# Try legacy port if the default one failed | ||
remote = RemoteControl(host, LEGACY_PORT) | ||
await remote.check_connectable() | ||
return LEGACY_PORT | ||
return DEFAULT_PORT | ||
|
||
|
||
class SkyRemoteConfigFlow(ConfigFlow, domain=DOMAIN): | ||
"""Handle a config flow for Sky Remote.""" | ||
|
||
VERSION = 1 | ||
MINOR_VERSION = 1 | ||
|
||
async def async_step_user( | ||
self, user_input: dict[str, Any] | None = None | ||
) -> ConfigFlowResult: | ||
"""Handle the user step.""" | ||
|
||
errors: dict[str, str] = {} | ||
if user_input is not None: | ||
logging.debug("user_input: %s", user_input) | ||
self._async_abort_entries_match(user_input) | ||
try: | ||
port = await async_find_box_port(user_input[CONF_HOST]) | ||
except SkyBoxConnectionError: | ||
logging.exception("while finding port of skybox") | ||
errors["base"] = "cannot_connect" | ||
else: | ||
return self.async_create_entry( | ||
title=user_input[CONF_HOST], | ||
data={**user_input, CONF_PORT: port}, | ||
) | ||
|
||
return self.async_show_form( | ||
step_id="user", data_schema=DATA_SCHEMA, errors=errors | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
"""Constants.""" | ||
|
||
DOMAIN = "sky_remote" | ||
|
||
DEFAULT_PORT = 49160 | ||
LEGACY_PORT = 5900 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"domain": "sky_remote", | ||
"name": "Sky Remote Control", | ||
"codeowners": ["@dunnmj", "@saty9"], | ||
"config_flow": true, | ||
"documentation": "https://www.home-assistant.io/integrations/sky_remote", | ||
"integration_type": "device", | ||
"iot_class": "assumed_state", | ||
"requirements": ["skyboxremote==0.0.6"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
"""Home Assistant integration to control a sky box using the remote platform.""" | ||
|
||
from collections.abc import Iterable | ||
import logging | ||
from typing import Any | ||
|
||
from skyboxremote import VALID_KEYS, RemoteControl | ||
|
||
from homeassistant.components.remote import RemoteEntity | ||
from homeassistant.core import HomeAssistant | ||
from homeassistant.exceptions import ServiceValidationError | ||
from homeassistant.helpers.device_registry import DeviceInfo | ||
from homeassistant.helpers.entity_platform import AddEntitiesCallback | ||
|
||
from . import SkyRemoteConfigEntry | ||
from .const import DOMAIN | ||
|
||
_LOGGER = logging.getLogger(__name__) | ||
|
||
|
||
async def async_setup_entry( | ||
hass: HomeAssistant, | ||
config: SkyRemoteConfigEntry, | ||
async_add_entities: AddEntitiesCallback, | ||
) -> None: | ||
"""Set up the Sky remote platform.""" | ||
async_add_entities( | ||
[SkyRemote(config.runtime_data, config.entry_id)], | ||
True, | ||
) | ||
|
||
|
||
class SkyRemote(RemoteEntity): | ||
"""Representation of a Sky Remote.""" | ||
|
||
_attr_has_entity_name = True | ||
_attr_name = None | ||
|
||
def __init__(self, remote: RemoteControl, unique_id: str) -> None: | ||
"""Initialize the Sky Remote.""" | ||
self._remote = remote | ||
self._attr_unique_id = unique_id | ||
self._attr_device_info = DeviceInfo( | ||
identifiers={(DOMAIN, unique_id)}, | ||
manufacturer="SKY", | ||
model="Sky Box", | ||
name=remote.host, | ||
) | ||
|
||
def turn_on(self, activity: str | None = None, **kwargs: Any) -> None: | ||
"""Send the power on command.""" | ||
self.send_command(["sky"]) | ||
|
||
def turn_off(self, activity: str | None = None, **kwargs: Any) -> None: | ||
"""Send the power command.""" | ||
self.send_command(["power"]) | ||
|
||
def send_command(self, command: Iterable[str], **kwargs: Any) -> None: | ||
"""Send a list of commands to the device.""" | ||
for cmd in command: | ||
if cmd not in VALID_KEYS: | ||
raise ServiceValidationError( | ||
f"{cmd} is not in Valid Keys: {VALID_KEYS}" | ||
) | ||
try: | ||
self._remote.send_keys(command) | ||
except ValueError as err: | ||
_LOGGER.error("Invalid command: %s. Error: %s", command, err) | ||
return | ||
_LOGGER.debug("Successfully sent command %s", command) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
"config": { | ||
"error": { | ||
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]" | ||
}, | ||
"abort": { | ||
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]" | ||
}, | ||
"step": { | ||
"user": { | ||
"title": "Add Sky Remote", | ||
"data": { | ||
"host": "[%key:common::config_flow::data::host%]" | ||
}, | ||
"data_description": { | ||
"host": "Hostname or IP address of your Sky device" | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -537,6 +537,7 @@ | |
"simplefin", | ||
"simplepush", | ||
"simplisafe", | ||
"sky_remote", | ||
"skybell", | ||
"slack", | ||
"sleepiq", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
"""Tests for the Sky Remote component.""" | ||
|
||
from homeassistant.core import HomeAssistant | ||
|
||
from tests.common import MockConfigEntry | ||
|
||
|
||
async def setup_mock_entry(hass: HomeAssistant, entry: MockConfigEntry): | ||
"""Initialize a mock config entry.""" | ||
entry.add_to_hass(hass) | ||
await hass.config_entries.async_setup(entry.entry_id) | ||
|
||
await hass.async_block_till_done() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
"""Test mocks and fixtures.""" | ||
|
||
from collections.abc import Generator | ||
from unittest.mock import AsyncMock, MagicMock, patch | ||
|
||
import pytest | ||
|
||
from homeassistant.components.sky_remote.const import DEFAULT_PORT, DOMAIN | ||
from homeassistant.const import CONF_HOST, CONF_PORT | ||
|
||
from tests.common import MockConfigEntry | ||
|
||
SAMPLE_CONFIG = {CONF_HOST: "example.com", CONF_PORT: DEFAULT_PORT} | ||
|
||
|
||
@pytest.fixture | ||
def mock_config_entry() -> MockConfigEntry: | ||
"""Mock a config entry.""" | ||
return MockConfigEntry(domain=DOMAIN, data=SAMPLE_CONFIG) | ||
|
||
|
||
@pytest.fixture | ||
def mock_setup_entry() -> Generator[AsyncMock]: | ||
"""Stub out setup function.""" | ||
with patch( | ||
"homeassistant.components.sky_remote.async_setup_entry", | ||
return_value=True, | ||
) as mock_setup_entry: | ||
yield mock_setup_entry | ||
|
||
|
||
@pytest.fixture | ||
def mock_remote_control(request: pytest.FixtureRequest) -> Generator[MagicMock]: | ||
"""Mock skyboxremote library.""" | ||
with ( | ||
patch( | ||
"homeassistant.components.sky_remote.RemoteControl" | ||
) as mock_remote_control, | ||
patch( | ||
"homeassistant.components.sky_remote.config_flow.RemoteControl", | ||
mock_remote_control, | ||
), | ||
): | ||
mock_remote_control._instance_mock = MagicMock(host="example.com") | ||
mock_remote_control._instance_mock.check_connectable = AsyncMock(True) | ||
mock_remote_control.return_value = mock_remote_control._instance_mock | ||
yield mock_remote_control |
Oops, something went wrong.