Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion homeassistant/components/directv/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
extra=vol.ALLOW_EXTRA,
)

PLATFORMS = ["media_player"]
PLATFORMS = ["media_player", "remote"]
SCAN_INTERVAL = timedelta(seconds=30)


Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/directv/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"domain": "directv",
"name": "DirecTV",
"documentation": "https://www.home-assistant.io/integrations/directv",
"requirements": ["directv==0.2.0"],
"requirements": ["directv==0.3.0"],
"dependencies": [],
"codeowners": ["@ctalkington"],
"quality_scale": "gold",
Expand Down
106 changes: 106 additions & 0 deletions homeassistant/components/directv/remote.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"""Support for the DIRECTV remote."""
from datetime import timedelta
import logging
from typing import Any, Callable, Iterable, List

from directv import DIRECTV, DIRECTVError

from homeassistant.components.remote import RemoteDevice
from homeassistant.config_entries import ConfigEntry
from homeassistant.helpers.typing import HomeAssistantType

from . import DIRECTVEntity
from .const import DOMAIN

_LOGGER = logging.getLogger(__name__)

SCAN_INTERVAL = timedelta(minutes=2)


async def async_setup_entry(
hass: HomeAssistantType,
entry: ConfigEntry,
async_add_entities: Callable[[List, bool], None],
) -> bool:
"""Load DirecTV remote based on a config entry."""
dtv = hass.data[DOMAIN][entry.entry_id]
entities = []

for location in dtv.device.locations:
entities.append(
DIRECTVRemote(
dtv=dtv, name=str.title(location.name), address=location.address,
)
)

async_add_entities(entities, True)


class DIRECTVRemote(DIRECTVEntity, RemoteDevice):
"""Device that sends commands to a DirecTV receiver."""

def __init__(self, *, dtv: DIRECTV, name: str, address: str = "0") -> None:
"""Initialize DirecTV remote."""
super().__init__(
dtv=dtv, name=name, address=address,
)

self._available = False
self._is_on = True

@property
def available(self):
"""Return if able to retrieve information from device or not."""
return self._available

@property
def unique_id(self):
"""Return a unique ID."""
if self._address == "0":
return self.dtv.device.info.receiver_id

return self._address

@property
def is_on(self) -> bool:
"""Return True if entity is on."""
return self._is_on

async def async_update(self) -> None:
"""Update device state."""
status = await self.dtv.status(self._address)

if status in ("active", "standby"):
self._available = True
self._is_on = status == "active"
else:
self._available = False
self._is_on = False

async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the device on."""
await self.dtv.remote("poweron", self._address)

async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the device off."""
await self.dtv.remote("poweroff", self._address)

async def async_send_command(self, command: Iterable[str], **kwargs: Any) -> None:
"""Send a command to a device.

Supported keys: power, poweron, poweroff, format,
pause, rew, replay, stop, advance, ffwd, record,
play, guide, active, list, exit, back, menu, info,
up, down, left, right, select, red, green, yellow,
blue, chanup, chandown, prev, 0, 1, 2, 3, 4, 5,
6, 7, 8, 9, dash, enter
"""
for single_command in command:
try:
await self.dtv.remote(single_command, self._address)
except DIRECTVError:
_LOGGER.exception(
"Sending command %s to device %s failed",
single_command,
self._device_id,
)
2 changes: 1 addition & 1 deletion requirements_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,7 @@ deluge-client==1.7.1
denonavr==0.8.1

# homeassistant.components.directv
directv==0.2.0
directv==0.3.0

# homeassistant.components.discogs
discogs_client==2.2.2
Expand Down
2 changes: 1 addition & 1 deletion requirements_test_all.txt
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ defusedxml==0.6.0
denonavr==0.8.1

# homeassistant.components.directv
directv==0.2.0
directv==0.3.0

# homeassistant.components.updater
distro==1.4.0
Expand Down
130 changes: 130 additions & 0 deletions tests/components/directv/test_remote.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
"""The tests for the DirecTV remote platform."""
from typing import Any, List

from asynctest import patch

from homeassistant.components.remote import (
ATTR_COMMAND,
ATTR_DELAY_SECS,
ATTR_DEVICE,
ATTR_NUM_REPEATS,
DOMAIN as REMOTE_DOMAIN,
SERVICE_SEND_COMMAND,
)
from homeassistant.const import (
ATTR_ENTITY_ID,
ENTITY_MATCH_ALL,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
)
from homeassistant.helpers.typing import HomeAssistantType

from tests.components.directv import setup_integration
from tests.test_util.aiohttp import AiohttpClientMocker

ATTR_UNIQUE_ID = "unique_id"
CLIENT_ENTITY_ID = f"{REMOTE_DOMAIN}.client"
MAIN_ENTITY_ID = f"{REMOTE_DOMAIN}.host"
UNAVAILABLE_ENTITY_ID = f"{REMOTE_DOMAIN}.unavailable_client"

# pylint: disable=redefined-outer-name


async def async_send_command(
hass: HomeAssistantType,
command: List[str],
entity_id: Any = ENTITY_MATCH_ALL,
device: str = None,
num_repeats: str = None,
delay_secs: str = None,
) -> None:
"""Send a command to a device."""
data = {ATTR_COMMAND: command}

if entity_id:
data[ATTR_ENTITY_ID] = entity_id

if device:
data[ATTR_DEVICE] = device

if num_repeats:
data[ATTR_NUM_REPEATS] = num_repeats

if delay_secs:
data[ATTR_DELAY_SECS] = delay_secs

await hass.services.async_call(REMOTE_DOMAIN, SERVICE_SEND_COMMAND, data)


async def async_turn_on(
hass: HomeAssistantType, entity_id: Any = ENTITY_MATCH_ALL
) -> None:
"""Turn on device."""
data = {}

if entity_id:
data[ATTR_ENTITY_ID] = entity_id

await hass.services.async_call(REMOTE_DOMAIN, SERVICE_TURN_ON, data)


async def async_turn_off(
hass: HomeAssistantType, entity_id: Any = ENTITY_MATCH_ALL
) -> None:
"""Turn off remote."""
data = {}

if entity_id:
data[ATTR_ENTITY_ID] = entity_id

await hass.services.async_call(REMOTE_DOMAIN, SERVICE_TURN_OFF, data)


async def test_setup(
hass: HomeAssistantType, aioclient_mock: AiohttpClientMocker
) -> None:
"""Test setup with basic config."""
await setup_integration(hass, aioclient_mock)
assert hass.states.get(MAIN_ENTITY_ID)
assert hass.states.get(CLIENT_ENTITY_ID)
assert hass.states.get(UNAVAILABLE_ENTITY_ID)


async def test_unique_id(
hass: HomeAssistantType, aioclient_mock: AiohttpClientMocker
) -> None:
"""Test unique id."""
await setup_integration(hass, aioclient_mock)

entity_registry = await hass.helpers.entity_registry.async_get_registry()

main = entity_registry.async_get(MAIN_ENTITY_ID)
assert main.unique_id == "028877455858"

client = entity_registry.async_get(CLIENT_ENTITY_ID)
assert client.unique_id == "2CA17D1CD30X"

unavailable_client = entity_registry.async_get(UNAVAILABLE_ENTITY_ID)
assert unavailable_client.unique_id == "9XXXXXXXXXX9"


async def test_main_services(
hass: HomeAssistantType, aioclient_mock: AiohttpClientMocker
) -> None:
"""Test the different services."""
await setup_integration(hass, aioclient_mock)

with patch("directv.DIRECTV.remote") as remote_mock:
await async_turn_off(hass, MAIN_ENTITY_ID)
await hass.async_block_till_done()
remote_mock.assert_called_once_with("poweroff", "0")

with patch("directv.DIRECTV.remote") as remote_mock:
await async_turn_on(hass, MAIN_ENTITY_ID)
await hass.async_block_till_done()
remote_mock.assert_called_once_with("poweron", "0")

with patch("directv.DIRECTV.remote") as remote_mock:
await async_send_command(hass, ["dash"], MAIN_ENTITY_ID)
await hass.async_block_till_done()
remote_mock.assert_called_once_with("dash", "0")