Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 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
7 changes: 5 additions & 2 deletions homeassistant/components/panasonic_viera/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import voluptuous as vol

from homeassistant.components.media_player.const import DOMAIN as MEDIA_PLAYER_DOMAIN
from homeassistant.components.remote import DOMAIN as REMOTE_DOMAIN
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_ON
import homeassistant.helpers.config_validation as cv
Expand Down Expand Up @@ -46,7 +47,7 @@
extra=vol.ALLOW_EXTRA,
)

PLATFORMS = [MEDIA_PLAYER_DOMAIN]
PLATFORMS = [MEDIA_PLAYER_DOMAIN, REMOTE_DOMAIN]


async def async_setup(hass, config):
Expand Down Expand Up @@ -245,7 +246,9 @@ async def async_get_device_info(self):
"""Return device info."""
if self._control is None:
return None
return await self._handle_errors(self._control.get_device_info)
device_info = await self._handle_errors(self._control.get_device_info)
_LOGGER.debug("Fetched device info: %s", str(device_info))
return device_info

async def _handle_errors(self, func, *args):
"""Handle errors from func, set available and reconnect if needed."""
Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/panasonic_viera/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,7 @@
ATTR_MODEL_NUMBER = "modelNumber"
ATTR_UDN = "UDN"

DEFAULT_MANUFACTURER = "Panasonic"
DEFAULT_MODEL_NUMBER = "Panasonic Viera"
Comment thread
joogps marked this conversation as resolved.
Outdated

ERROR_INVALID_PIN_CODE = "invalid_pin_code"
16 changes: 10 additions & 6 deletions homeassistant/components/panasonic_viera/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
ATTR_MODEL_NUMBER,
ATTR_REMOTE,
ATTR_UDN,
DEFAULT_MANUFACTURER,
DEFAULT_MODEL_NUMBER,
DOMAIN,
)

Expand Down Expand Up @@ -69,11 +71,11 @@ def __init__(self, remote, name, device_info):
self._device_info = device_info

@property
def unique_id(self) -> str:
def unique_id(self):
"""Return the unique ID of the device."""
if self._device_info is not None:
return self._device_info[ATTR_UDN]
return None
if self._device_info is None:
return None
return self._device_info[ATTR_UDN]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is still a UUID in the response by Tom, isn't that the same as UDN ?

@joogps joogps Oct 27, 2020

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually the problem was that his TV apparently has no model number explicit in the descriptor file
so I just set a bunch of default values in case they can't be found

the unique id was ok (I think)


@property
def device_info(self):
Expand All @@ -83,8 +85,10 @@ def device_info(self):
return {
"name": self._name,
"identifiers": {(DOMAIN, self._device_info[ATTR_UDN])},
"manufacturer": self._device_info[ATTR_MANUFACTURER],
"model": self._device_info[ATTR_MODEL_NUMBER],
"manufacturer": self._device_info.get(
ATTR_MANUFACTURER, DEFAULT_MANUFACTURER
),
"model": self._device_info.get(ATTR_MODEL_NUMBER, DEFAULT_MODEL_NUMBER),
}

@property
Expand Down
91 changes: 91 additions & 0 deletions homeassistant/components/panasonic_viera/remote.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"""Remote control support for Panasonic Viera TV."""
import logging

from homeassistant.components.remote import RemoteEntity
from homeassistant.const import CONF_NAME, STATE_ON

from .const import (
ATTR_DEVICE_INFO,
ATTR_MANUFACTURER,
ATTR_MODEL_NUMBER,
ATTR_REMOTE,
ATTR_UDN,
DEFAULT_MANUFACTURER,
DEFAULT_MODEL_NUMBER,
DOMAIN,
)

_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(hass, config_entry, async_add_entities):
"""Set up Panasonic Viera TV Remote from a config entry."""

config = config_entry.data

remote = hass.data[DOMAIN][config_entry.entry_id][ATTR_REMOTE]
name = config[CONF_NAME]
device_info = config[ATTR_DEVICE_INFO]

remote_device = PanasonicVieraRemoteEntity(remote, name, device_info)
async_add_entities([remote_device])


class PanasonicVieraRemoteEntity(RemoteEntity):
"""Representation of a Panasonic Viera TV Remote."""

def __init__(self, remote, name, device_info):
"""Initialize the entity."""
# Save a reference to the imported class
self._remote = remote
self._name = name
self._device_info = device_info

@property
def unique_id(self):
"""Return the unique ID of the device."""
if self._device_info is None:
return None
return self._device_info[ATTR_UDN]

@property
def device_info(self):
"""Return device specific attributes."""
if self._device_info is None:
return None
return {
"name": self._name,
"identifiers": {(DOMAIN, self._device_info[ATTR_UDN])},
"manufacturer": self._device_info.get(
ATTR_MANUFACTURER, DEFAULT_MANUFACTURER
),
"model": self._device_info.get(ATTR_MODEL_NUMBER, DEFAULT_MODEL_NUMBER),
}

@property
def name(self):
"""Return the name of the device."""
return self._name

@property
def available(self):
"""Return True if the device is available."""
return self._remote.available

@property
def is_on(self):
"""Return true if device is on."""
return self._remote.state == STATE_ON

async def async_turn_on(self, **kwargs):
"""Turn the device on."""
await self._remote.async_turn_on()

async def async_turn_off(self, **kwargs):
"""Turn the device off."""
await self._remote.async_turn_off()

async def async_send_command(self, command, **kwargs):
"""Send a command to one device."""
for cmd in command:
await self._remote.async_send_key(cmd)
76 changes: 65 additions & 11 deletions tests/components/panasonic_viera/test_config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
CONF_APP_ID,
CONF_ENCRYPTION_KEY,
CONF_ON_ACTION,
DEFAULT_MANUFACTURER,
DEFAULT_MODEL_NUMBER,
DEFAULT_NAME,
DEFAULT_PORT,
DOMAIN,
Expand All @@ -37,13 +39,14 @@ def panasonic_viera_setup_fixture():

def get_mock_remote(
host="1.2.3.4",
request_error=None,
authorize_error=None,
encrypted=False,
app_id=None,
encryption_key=None,
name=DEFAULT_NAME,
manufacturer="mock-manufacturer",
model_number="mock-model-number",
manufacturer=DEFAULT_MANUFACTURER,
model_number=DEFAULT_MODEL_NUMBER,
unique_id="mock-unique-id",
):
"""Return a mock remote."""
Expand All @@ -54,7 +57,8 @@ def get_mock_remote(
mock_remote.enc_key = encryption_key

def request_pin_code(name=None):
return
if request_error is not None:
raise request_error

mock_remote.request_pin_code = request_pin_code

Expand Down Expand Up @@ -110,8 +114,8 @@ async def test_flow_non_encrypted(hass):
CONF_ON_ACTION: None,
ATTR_DEVICE_INFO: {
ATTR_FRIENDLY_NAME: DEFAULT_NAME,
ATTR_MANUFACTURER: "mock-manufacturer",
ATTR_MODEL_NUMBER: "mock-model-number",
ATTR_MANUFACTURER: DEFAULT_MANUFACTURER,
ATTR_MODEL_NUMBER: DEFAULT_MODEL_NUMBER,
ATTR_UDN: "mock-unique-id",
},
}
Expand Down Expand Up @@ -164,6 +168,56 @@ async def test_flow_unknown_abort(hass):
assert result["reason"] == "unknown"


async def test_flow_encrypted_not_connected_pin_code_request(hass):
"""Test flow with encryption and PIN code request connection error abortion during pairing request step."""

result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)

assert result["type"] == "form"
assert result["step_id"] == "user"

mock_remote = get_mock_remote(encrypted=True, request_error=TimeoutError)

with patch(
"homeassistant.components.panasonic_viera.config_flow.RemoteControl",
return_value=mock_remote,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME},
)

assert result["type"] == "abort"
assert result["reason"] == "cannot_connect"


async def test_flow_encrypted_unknown_pin_code_request(hass):
"""Test flow with encryption and PIN code request unknown error abortion during pairing request step."""

result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)

assert result["type"] == "form"
assert result["step_id"] == "user"

mock_remote = get_mock_remote(encrypted=True, request_error=Exception)

with patch(
"homeassistant.components.panasonic_viera.config_flow.RemoteControl",
return_value=mock_remote,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME},
)

assert result["type"] == "abort"
assert result["reason"] == "unknown"


async def test_flow_encrypted_valid_pin_code(hass):
"""Test flow with encryption and valid PIN code."""

Expand Down Expand Up @@ -208,8 +262,8 @@ async def test_flow_encrypted_valid_pin_code(hass):
CONF_ENCRYPTION_KEY: "test-encryption-key",
ATTR_DEVICE_INFO: {
ATTR_FRIENDLY_NAME: DEFAULT_NAME,
ATTR_MANUFACTURER: "mock-manufacturer",
ATTR_MODEL_NUMBER: "mock-model-number",
ATTR_MANUFACTURER: DEFAULT_MANUFACTURER,
ATTR_MODEL_NUMBER: DEFAULT_MODEL_NUMBER,
ATTR_UDN: "mock-unique-id",
},
}
Expand Down Expand Up @@ -392,8 +446,8 @@ async def test_imported_flow_non_encrypted(hass):
CONF_ON_ACTION: "test-on-action",
ATTR_DEVICE_INFO: {
ATTR_FRIENDLY_NAME: DEFAULT_NAME,
ATTR_MANUFACTURER: "mock-manufacturer",
ATTR_MODEL_NUMBER: "mock-model-number",
ATTR_MANUFACTURER: DEFAULT_MANUFACTURER,
ATTR_MODEL_NUMBER: DEFAULT_MODEL_NUMBER,
ATTR_UDN: "mock-unique-id",
},
}
Expand Down Expand Up @@ -442,8 +496,8 @@ async def test_imported_flow_encrypted_valid_pin_code(hass):
CONF_ENCRYPTION_KEY: "test-encryption-key",
ATTR_DEVICE_INFO: {
ATTR_FRIENDLY_NAME: DEFAULT_NAME,
ATTR_MANUFACTURER: "mock-manufacturer",
ATTR_MODEL_NUMBER: "mock-model-number",
ATTR_MANUFACTURER: DEFAULT_MANUFACTURER,
ATTR_MODEL_NUMBER: DEFAULT_MODEL_NUMBER,
ATTR_UDN: "mock-unique-id",
},
}
Expand Down
Loading