Add device info to Panasonic Viera (#41028)

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
João Gabriel 2020-10-13 08:14:59 -03:00 committed by GitHub
parent 72289b8171
commit d1041efedf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 250 additions and 10 deletions

View file

@ -14,7 +14,9 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.script import Script from homeassistant.helpers.script import Script
from .const import ( from .const import (
ATTR_DEVICE_INFO,
ATTR_REMOTE, ATTR_REMOTE,
ATTR_UDN,
CONF_APP_ID, CONF_APP_ID,
CONF_ENCRYPTION_KEY, CONF_ENCRYPTION_KEY,
CONF_ON_ACTION, CONF_ON_ACTION,
@ -86,6 +88,22 @@ async def async_setup_entry(hass, config_entry):
panasonic_viera_data[config_entry.entry_id] = {ATTR_REMOTE: remote} panasonic_viera_data[config_entry.entry_id] = {ATTR_REMOTE: remote}
# Add device_info to older config entries
if ATTR_DEVICE_INFO not in config or config[ATTR_DEVICE_INFO] is None:
device_info = await remote.async_get_device_info()
unique_id = config_entry.unique_id
if device_info is None:
_LOGGER.error(
"Couldn't gather device info. Please restart Home Assistant with your TV turned on and connected to your network."
)
else:
unique_id = device_info[ATTR_UDN]
hass.config_entries.async_update_entry(
config_entry,
unique_id=unique_id,
data={**config, ATTR_DEVICE_INFO: device_info},
)
for component in PLATFORMS: for component in PLATFORMS:
hass.async_create_task( hass.async_create_task(
hass.config_entries.async_forward_entry_setup(config_entry, component) hass.config_entries.async_forward_entry_setup(config_entry, component)
@ -223,6 +241,12 @@ class Remote:
_LOGGER.debug("Play media: %s (%s)", media_id, media_type) _LOGGER.debug("Play media: %s (%s)", media_id, media_type)
await self._handle_errors(self._control.open_webpage, media_id) await self._handle_errors(self._control.open_webpage, media_id)
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)
async def _handle_errors(self, func, *args): async def _handle_errors(self, func, *args):
"""Handle errors from func, set available and reconnect if needed.""" """Handle errors from func, set available and reconnect if needed."""
try: try:

View file

@ -10,6 +10,9 @@ from homeassistant import config_entries
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PIN, CONF_PORT from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PIN, CONF_PORT
from .const import ( # pylint: disable=unused-import from .const import ( # pylint: disable=unused-import
ATTR_DEVICE_INFO,
ATTR_FRIENDLY_NAME,
ATTR_UDN,
CONF_APP_ID, CONF_APP_ID,
CONF_ENCRYPTION_KEY, CONF_ENCRYPTION_KEY,
CONF_ON_ACTION, CONF_ON_ACTION,
@ -35,6 +38,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
CONF_NAME: None, CONF_NAME: None,
CONF_PORT: None, CONF_PORT: None,
CONF_ON_ACTION: None, CONF_ON_ACTION: None,
ATTR_DEVICE_INFO: None,
} }
self._remote = None self._remote = None
@ -49,6 +53,10 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
self._remote = await self.hass.async_add_executor_job( self._remote = await self.hass.async_add_executor_job(
partial(RemoteControl, self._data[CONF_HOST], self._data[CONF_PORT]) partial(RemoteControl, self._data[CONF_HOST], self._data[CONF_PORT])
) )
self._data[ATTR_DEVICE_INFO] = await self.hass.async_add_executor_job(
self._remote.get_device_info
)
except (TimeoutError, URLError, SOAPError, OSError) as err: except (TimeoutError, URLError, SOAPError, OSError) as err:
_LOGGER.error("Could not establish remote connection: %s", err) _LOGGER.error("Could not establish remote connection: %s", err)
errors["base"] = "cannot_connect" errors["base"] = "cannot_connect"
@ -57,6 +65,14 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
return self.async_abort(reason="unknown") return self.async_abort(reason="unknown")
if "base" not in errors: if "base" not in errors:
await self.async_set_unique_id(self._data[ATTR_DEVICE_INFO][ATTR_UDN])
self._abort_if_unique_id_configured()
if self._data[CONF_NAME] == DEFAULT_NAME:
self._data[CONF_NAME] = self._data[ATTR_DEVICE_INFO][
ATTR_FRIENDLY_NAME
].replace("_", " ")
if self._remote.type == TV_TYPE_ENCRYPTED: if self._remote.type == TV_TYPE_ENCRYPTED:
return await self.async_step_pairing() return await self.async_step_pairing()

View file

@ -12,4 +12,10 @@ DEFAULT_PORT = 55000
ATTR_REMOTE = "remote" ATTR_REMOTE = "remote"
ATTR_DEVICE_INFO = "device_info"
ATTR_FRIENDLY_NAME = "friendlyName"
ATTR_MANUFACTURER = "manufacturer"
ATTR_MODEL_NUMBER = "modelNumber"
ATTR_UDN = "UDN"
ERROR_INVALID_PIN_CODE = "invalid_pin_code" ERROR_INVALID_PIN_CODE = "invalid_pin_code"

View file

@ -20,7 +20,14 @@ from homeassistant.components.media_player.const import (
) )
from homeassistant.const import CONF_NAME from homeassistant.const import CONF_NAME
from .const import ATTR_REMOTE, DOMAIN from .const import (
ATTR_DEVICE_INFO,
ATTR_MANUFACTURER,
ATTR_MODEL_NUMBER,
ATTR_REMOTE,
ATTR_UDN,
DOMAIN,
)
SUPPORT_VIERATV = ( SUPPORT_VIERATV = (
SUPPORT_PAUSE SUPPORT_PAUSE
@ -46,24 +53,39 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
remote = hass.data[DOMAIN][config_entry.entry_id][ATTR_REMOTE] remote = hass.data[DOMAIN][config_entry.entry_id][ATTR_REMOTE]
name = config[CONF_NAME] name = config[CONF_NAME]
device_info = config[ATTR_DEVICE_INFO]
tv_device = PanasonicVieraTVEntity(remote, name) tv_device = PanasonicVieraTVEntity(remote, name, device_info)
async_add_entities([tv_device]) async_add_entities([tv_device])
class PanasonicVieraTVEntity(MediaPlayerEntity): class PanasonicVieraTVEntity(MediaPlayerEntity):
"""Representation of a Panasonic Viera TV.""" """Representation of a Panasonic Viera TV."""
def __init__(self, remote, name, uuid=None): def __init__(self, remote, name, device_info):
"""Initialize the entity.""" """Initialize the entity."""
self._remote = remote self._remote = remote
self._name = name self._name = name
self._uuid = uuid self._device_info = device_info
@property @property
def unique_id(self): def unique_id(self) -> str:
"""Return the unique ID of the device.""" """Return the unique ID of the device."""
return self._uuid if self._device_info is not None:
return self._device_info[ATTR_UDN]
return None
@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[ATTR_MANUFACTURER],
"model": self._device_info[ATTR_MODEL_NUMBER],
}
@property @property
def name(self): def name(self):

View file

@ -4,6 +4,11 @@ import pytest
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components.panasonic_viera.const import ( from homeassistant.components.panasonic_viera.const import (
ATTR_DEVICE_INFO,
ATTR_FRIENDLY_NAME,
ATTR_MANUFACTURER,
ATTR_MODEL_NUMBER,
ATTR_UDN,
CONF_APP_ID, CONF_APP_ID,
CONF_ENCRYPTION_KEY, CONF_ENCRYPTION_KEY,
CONF_ON_ACTION, CONF_ON_ACTION,
@ -36,6 +41,10 @@ def get_mock_remote(
encrypted=False, encrypted=False,
app_id=None, app_id=None,
encryption_key=None, encryption_key=None,
name=DEFAULT_NAME,
manufacturer="mock-manufacturer",
model_number="mock-model-number",
unique_id="mock-unique-id",
): ):
"""Return a mock remote.""" """Return a mock remote."""
mock_remote = Mock() mock_remote = Mock()
@ -58,6 +67,16 @@ def get_mock_remote(
mock_remote.authorize_pin_code = authorize_pin_code mock_remote.authorize_pin_code = authorize_pin_code
def get_device_info():
return {
ATTR_FRIENDLY_NAME: name,
ATTR_MANUFACTURER: manufacturer,
ATTR_MODEL_NUMBER: model_number,
ATTR_UDN: unique_id,
}
mock_remote.get_device_info = get_device_info
return mock_remote return mock_remote
@ -89,6 +108,12 @@ async def test_flow_non_encrypted(hass):
CONF_NAME: DEFAULT_NAME, CONF_NAME: DEFAULT_NAME,
CONF_PORT: DEFAULT_PORT, CONF_PORT: DEFAULT_PORT,
CONF_ON_ACTION: None, CONF_ON_ACTION: None,
ATTR_DEVICE_INFO: {
ATTR_FRIENDLY_NAME: DEFAULT_NAME,
ATTR_MANUFACTURER: "mock-manufacturer",
ATTR_MODEL_NUMBER: "mock-model-number",
ATTR_UDN: "mock-unique-id",
},
} }
@ -181,6 +206,12 @@ async def test_flow_encrypted_valid_pin_code(hass):
CONF_ON_ACTION: None, CONF_ON_ACTION: None,
CONF_APP_ID: "test-app-id", CONF_APP_ID: "test-app-id",
CONF_ENCRYPTION_KEY: "test-encryption-key", 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_UDN: "mock-unique-id",
},
} }
@ -359,6 +390,12 @@ async def test_imported_flow_non_encrypted(hass):
CONF_NAME: DEFAULT_NAME, CONF_NAME: DEFAULT_NAME,
CONF_PORT: DEFAULT_PORT, CONF_PORT: DEFAULT_PORT,
CONF_ON_ACTION: "test-on-action", 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_UDN: "mock-unique-id",
},
} }
@ -403,6 +440,12 @@ async def test_imported_flow_encrypted_valid_pin_code(hass):
CONF_ON_ACTION: "test-on-action", CONF_ON_ACTION: "test-on-action",
CONF_APP_ID: "test-app-id", CONF_APP_ID: "test-app-id",
CONF_ENCRYPTION_KEY: "test-encryption-key", 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_UDN: "mock-unique-id",
},
} }

View file

@ -1,5 +1,10 @@
"""Test the Panasonic Viera setup process.""" """Test the Panasonic Viera setup process."""
from homeassistant.components.panasonic_viera.const import ( from homeassistant.components.panasonic_viera.const import (
ATTR_DEVICE_INFO,
ATTR_FRIENDLY_NAME,
ATTR_MANUFACTURER,
ATTR_MODEL_NUMBER,
ATTR_UDN,
CONF_APP_ID, CONF_APP_ID,
CONF_ENCRYPTION_KEY, CONF_ENCRYPTION_KEY,
CONF_ON_ACTION, CONF_ON_ACTION,
@ -26,8 +31,15 @@ MOCK_ENCRYPTION_DATA = {
CONF_ENCRYPTION_KEY: "mock-encryption-key", CONF_ENCRYPTION_KEY: "mock-encryption-key",
} }
MOCK_DEVICE_INFO = {
ATTR_FRIENDLY_NAME: DEFAULT_NAME,
ATTR_MANUFACTURER: "mock-manufacturer",
ATTR_MODEL_NUMBER: "mock-model-number",
ATTR_UDN: "mock-unique-id",
}
def get_mock_remote():
def get_mock_remote(device_info=MOCK_DEVICE_INFO):
"""Return a mock remote.""" """Return a mock remote."""
mock_remote = Mock() mock_remote = Mock()
@ -36,6 +48,11 @@ def get_mock_remote():
mock_remote.async_create_remote_control = async_create_remote_control mock_remote.async_create_remote_control = async_create_remote_control
async def async_get_device_info():
return device_info
mock_remote.async_get_device_info = async_get_device_info
return mock_remote return mock_remote
@ -43,8 +60,8 @@ async def test_setup_entry_encrypted(hass):
"""Test setup with encrypted config entry.""" """Test setup with encrypted config entry."""
mock_entry = MockConfigEntry( mock_entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
unique_id=MOCK_CONFIG_DATA[CONF_HOST], unique_id=MOCK_DEVICE_INFO[ATTR_UDN],
data={**MOCK_CONFIG_DATA, **MOCK_ENCRYPTION_DATA}, data={**MOCK_CONFIG_DATA, **MOCK_ENCRYPTION_DATA, **MOCK_DEVICE_INFO},
) )
mock_entry.add_to_hass(hass) mock_entry.add_to_hass(hass)
@ -64,8 +81,89 @@ async def test_setup_entry_encrypted(hass):
assert state.name == DEFAULT_NAME assert state.name == DEFAULT_NAME
async def test_setup_entry_encrypted_missing_device_info(hass):
"""Test setup with encrypted config entry and missing device info."""
mock_entry = MockConfigEntry(
domain=DOMAIN,
unique_id=MOCK_CONFIG_DATA[CONF_HOST],
data={**MOCK_CONFIG_DATA, **MOCK_ENCRYPTION_DATA},
)
mock_entry.add_to_hass(hass)
mock_remote = get_mock_remote()
with patch(
"homeassistant.components.panasonic_viera.Remote",
return_value=mock_remote,
):
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
assert mock_entry.data[ATTR_DEVICE_INFO] == MOCK_DEVICE_INFO
assert mock_entry.unique_id == MOCK_DEVICE_INFO[ATTR_UDN]
state = hass.states.get("media_player.panasonic_viera_tv")
assert state
assert state.name == DEFAULT_NAME
async def test_setup_entry_encrypted_missing_device_info_none(hass):
"""Test setup with encrypted config entry and device info set to None."""
mock_entry = MockConfigEntry(
domain=DOMAIN,
unique_id=MOCK_CONFIG_DATA[CONF_HOST],
data={**MOCK_CONFIG_DATA, **MOCK_ENCRYPTION_DATA},
)
mock_entry.add_to_hass(hass)
mock_remote = get_mock_remote(device_info=None)
with patch(
"homeassistant.components.panasonic_viera.Remote",
return_value=mock_remote,
):
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
assert mock_entry.data[ATTR_DEVICE_INFO] is None
assert mock_entry.unique_id == MOCK_CONFIG_DATA[CONF_HOST]
state = hass.states.get("media_player.panasonic_viera_tv")
assert state
assert state.name == DEFAULT_NAME
async def test_setup_entry_unencrypted(hass): async def test_setup_entry_unencrypted(hass):
"""Test setup with unencrypted config entry.""" """Test setup with unencrypted config entry."""
mock_entry = MockConfigEntry(
domain=DOMAIN,
unique_id=MOCK_DEVICE_INFO[ATTR_UDN],
data={**MOCK_CONFIG_DATA, **MOCK_DEVICE_INFO},
)
mock_entry.add_to_hass(hass)
mock_remote = get_mock_remote()
with patch(
"homeassistant.components.panasonic_viera.Remote",
return_value=mock_remote,
):
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
state = hass.states.get("media_player.panasonic_viera_tv")
assert state
assert state.name == DEFAULT_NAME
async def test_setup_entry_unencrypted_missing_device_info(hass):
"""Test setup with unencrypted config entry and missing device info."""
mock_entry = MockConfigEntry( mock_entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
unique_id=MOCK_CONFIG_DATA[CONF_HOST], unique_id=MOCK_CONFIG_DATA[CONF_HOST],
@ -83,6 +181,37 @@ async def test_setup_entry_unencrypted(hass):
await hass.config_entries.async_setup(mock_entry.entry_id) await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
assert mock_entry.data[ATTR_DEVICE_INFO] == MOCK_DEVICE_INFO
assert mock_entry.unique_id == MOCK_DEVICE_INFO[ATTR_UDN]
state = hass.states.get("media_player.panasonic_viera_tv")
assert state
assert state.name == DEFAULT_NAME
async def test_setup_entry_unencrypted_missing_device_info_none(hass):
"""Test setup with unencrypted config entry and device info set to None."""
mock_entry = MockConfigEntry(
domain=DOMAIN,
unique_id=MOCK_CONFIG_DATA[CONF_HOST],
data=MOCK_CONFIG_DATA,
)
mock_entry.add_to_hass(hass)
mock_remote = get_mock_remote(device_info=None)
with patch(
"homeassistant.components.panasonic_viera.Remote",
return_value=mock_remote,
):
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
assert mock_entry.data[ATTR_DEVICE_INFO] is None
assert mock_entry.unique_id == MOCK_CONFIG_DATA[CONF_HOST]
state = hass.states.get("media_player.panasonic_viera_tv") state = hass.states.get("media_player.panasonic_viera_tv")
assert state assert state
@ -106,7 +235,7 @@ async def test_setup_config_flow_initiated(hass):
async def test_setup_unload_entry(hass): async def test_setup_unload_entry(hass):
"""Test if config entry is unloaded.""" """Test if config entry is unloaded."""
mock_entry = MockConfigEntry( mock_entry = MockConfigEntry(
domain=DOMAIN, unique_id=MOCK_CONFIG_DATA[CONF_HOST], data=MOCK_CONFIG_DATA domain=DOMAIN, unique_id=MOCK_DEVICE_INFO[ATTR_UDN], data=MOCK_CONFIG_DATA
) )
mock_entry.add_to_hass(hass) mock_entry.add_to_hass(hass)