mirror of
https://github.com/home-assistant/core.git
synced 2025-07-27 07:07:28 +00:00
Add config flow to panasonic_viera component (#33829)
* Updating the panasonic_viera component * Updating .coveragerc * Removing testplatform * Updating strings.json * Commit before rebase * Commit before rebase * Commit before rebase * Commit before rebase * Commit before rebase * Adding tests and stuff * Fixing permission issues * Ignoring Pylint warnings * Fixing one more Pylint warning * Refactoring * Commiting changes - part 1 * Commiting changes: part 2 * Turning unknown error logs into exception logs * Update strings.json * Rebasing * Updating the panasonic_viera component * Removing testplatform * Updating strings.json * Commit before rebase * Commit before rebase * Commit before rebase * Commit before rebase * Commit before rebase * Adding tests and stuff * Fixing permission issues * Ignoring Pylint warnings * Fixing one more Pylint warning * Refactoring * Commiting changes - part 1 * Commiting changes: part 2 * Turning unknown error logs into exception logs * Adding pt-BR translation * Removing Brazilian Portugues translations * Modifying error handling * Adding SOAPError to except handling * Updating translation * Refactoring async_step_import * Fixing indentation * Fixing requirements after rebase * Fixing translations * Fixing issues after rebase * Routing import step to user step * Adding myself as a codeowner
This commit is contained in:
parent
dd4a350bd5
commit
42b6ec2fb5
@ -523,6 +523,7 @@ omit =
|
|||||||
homeassistant/components/osramlightify/light.py
|
homeassistant/components/osramlightify/light.py
|
||||||
homeassistant/components/otp/sensor.py
|
homeassistant/components/otp/sensor.py
|
||||||
homeassistant/components/panasonic_bluray/media_player.py
|
homeassistant/components/panasonic_bluray/media_player.py
|
||||||
|
homeassistant/components/panasonic_viera/__init__.py
|
||||||
homeassistant/components/panasonic_viera/media_player.py
|
homeassistant/components/panasonic_viera/media_player.py
|
||||||
homeassistant/components/pandora/media_player.py
|
homeassistant/components/pandora/media_player.py
|
||||||
homeassistant/components/pcal9535a/*
|
homeassistant/components/pcal9535a/*
|
||||||
|
@ -279,6 +279,7 @@ homeassistant/components/openweathermap/* @fabaff
|
|||||||
homeassistant/components/opnsense/* @mtreinish
|
homeassistant/components/opnsense/* @mtreinish
|
||||||
homeassistant/components/orangepi_gpio/* @pascallj
|
homeassistant/components/orangepi_gpio/* @pascallj
|
||||||
homeassistant/components/oru/* @bvlaicu
|
homeassistant/components/oru/* @bvlaicu
|
||||||
|
homeassistant/components/panasonic_viera/* @joogps
|
||||||
homeassistant/components/panel_custom/* @home-assistant/frontend
|
homeassistant/components/panel_custom/* @home-assistant/frontend
|
||||||
homeassistant/components/panel_iframe/* @home-assistant/frontend
|
homeassistant/components/panel_iframe/* @home-assistant/frontend
|
||||||
homeassistant/components/pcal9535a/* @Shulyaka
|
homeassistant/components/pcal9535a/* @Shulyaka
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "This Panasonic Viera TV is already configured.",
|
||||||
|
"not_connected": "The remote connection with your Panasonic Viera TV was lost. Check the logs for more information.",
|
||||||
|
"unknown": "An unknown error occured. Check the logs for more information."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"invalid_pin_code": "The PIN code you entered was invalid",
|
||||||
|
"not_connected": "Could not establish a remote connection with your Panasonic Viera TV"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"pairing": {
|
||||||
|
"data": {
|
||||||
|
"pin": "PIN"
|
||||||
|
},
|
||||||
|
"description": "Enter the PIN displayed on your TV",
|
||||||
|
"title": "Pairing"
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"host": "IP address",
|
||||||
|
"name": "Name"
|
||||||
|
},
|
||||||
|
"description": "Enter your Panasonic Viera TV's IP address",
|
||||||
|
"title": "Setup your TV"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Panasonic Viera"
|
||||||
|
}
|
@ -1 +1,69 @@
|
|||||||
"""The panasonic_viera component."""
|
"""The Panasonic Viera integration."""
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.config_entries import SOURCE_IMPORT
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT
|
||||||
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
|
from .const import CONF_ON_ACTION, DEFAULT_NAME, DEFAULT_PORT, DOMAIN
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
|
{
|
||||||
|
DOMAIN: vol.All(
|
||||||
|
cv.ensure_list,
|
||||||
|
[
|
||||||
|
vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(CONF_HOST): cv.string,
|
||||||
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||||
|
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
||||||
|
vol.Optional(CONF_ON_ACTION): cv.SCRIPT_SCHEMA,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
},
|
||||||
|
extra=vol.ALLOW_EXTRA,
|
||||||
|
)
|
||||||
|
|
||||||
|
PLATFORMS = ["media_player"]
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup(hass, config):
|
||||||
|
"""Set up Panasonic Viera from configuration.yaml."""
|
||||||
|
if DOMAIN not in config:
|
||||||
|
return True
|
||||||
|
|
||||||
|
for conf in config[DOMAIN]:
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_IMPORT}, data=conf
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass, config_entry):
|
||||||
|
"""Set up Panasonic Viera from a config entry."""
|
||||||
|
|
||||||
|
for component in PLATFORMS:
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.async_forward_entry_setup(config_entry, component)
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass, config_entry):
|
||||||
|
"""Unload a config entry."""
|
||||||
|
return all(
|
||||||
|
await asyncio.gather(
|
||||||
|
*[
|
||||||
|
hass.config_entries.async_forward_entry_unload(config_entry, component)
|
||||||
|
for component in PLATFORMS
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
152
homeassistant/components/panasonic_viera/config_flow.py
Normal file
152
homeassistant/components/panasonic_viera/config_flow.py
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
"""Config flow for Panasonic Viera TV integration."""
|
||||||
|
from functools import partial
|
||||||
|
import logging
|
||||||
|
from urllib.request import URLError
|
||||||
|
|
||||||
|
from panasonic_viera import TV_TYPE_ENCRYPTED, RemoteControl, SOAPError
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PIN, CONF_PORT
|
||||||
|
|
||||||
|
from .const import ( # pylint: disable=unused-import
|
||||||
|
CONF_APP_ID,
|
||||||
|
CONF_ENCRYPTION_KEY,
|
||||||
|
CONF_ON_ACTION,
|
||||||
|
DEFAULT_NAME,
|
||||||
|
DEFAULT_PORT,
|
||||||
|
DOMAIN,
|
||||||
|
ERROR_INVALID_PIN_CODE,
|
||||||
|
ERROR_NOT_CONNECTED,
|
||||||
|
REASON_NOT_CONNECTED,
|
||||||
|
REASON_UNKNOWN,
|
||||||
|
)
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Config flow for Panasonic Viera."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""Initialize the Panasonic Viera config flow."""
|
||||||
|
self._data = {
|
||||||
|
CONF_HOST: None,
|
||||||
|
CONF_NAME: None,
|
||||||
|
CONF_PORT: None,
|
||||||
|
CONF_ON_ACTION: None,
|
||||||
|
}
|
||||||
|
|
||||||
|
self._remote = None
|
||||||
|
|
||||||
|
async def async_step_user(self, user_input=None):
|
||||||
|
"""Handle the initial step."""
|
||||||
|
errors = {}
|
||||||
|
|
||||||
|
if user_input is not None:
|
||||||
|
await self.async_load_data(user_input)
|
||||||
|
try:
|
||||||
|
self._remote = await self.hass.async_add_executor_job(
|
||||||
|
partial(RemoteControl, self._data[CONF_HOST], self._data[CONF_PORT])
|
||||||
|
)
|
||||||
|
except (TimeoutError, URLError, SOAPError, OSError) as err:
|
||||||
|
_LOGGER.error("Could not establish remote connection: %s", err)
|
||||||
|
errors["base"] = ERROR_NOT_CONNECTED
|
||||||
|
except Exception as err: # pylint: disable=broad-except
|
||||||
|
_LOGGER.exception("An unknown error occurred: %s", err)
|
||||||
|
return self.async_abort(reason=REASON_UNKNOWN)
|
||||||
|
|
||||||
|
if "base" not in errors:
|
||||||
|
if self._remote.type == TV_TYPE_ENCRYPTED:
|
||||||
|
return await self.async_step_pairing()
|
||||||
|
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=self._data[CONF_NAME], data=self._data,
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user",
|
||||||
|
data_schema=vol.Schema(
|
||||||
|
{
|
||||||
|
vol.Required(
|
||||||
|
CONF_HOST,
|
||||||
|
default=self._data[CONF_HOST]
|
||||||
|
if self._data[CONF_HOST] is not None
|
||||||
|
else "",
|
||||||
|
): str,
|
||||||
|
vol.Optional(
|
||||||
|
CONF_NAME,
|
||||||
|
default=self._data[CONF_NAME]
|
||||||
|
if self._data[CONF_NAME] is not None
|
||||||
|
else DEFAULT_NAME,
|
||||||
|
): str,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
errors=errors,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_pairing(self, user_input=None):
|
||||||
|
"""Handle the pairing step."""
|
||||||
|
errors = {}
|
||||||
|
|
||||||
|
if user_input is not None:
|
||||||
|
pin = user_input[CONF_PIN]
|
||||||
|
try:
|
||||||
|
self._remote.authorize_pin_code(pincode=pin)
|
||||||
|
except SOAPError as err:
|
||||||
|
_LOGGER.error("Invalid PIN code: %s", err)
|
||||||
|
errors["base"] = ERROR_INVALID_PIN_CODE
|
||||||
|
except (TimeoutError, URLError, OSError) as err:
|
||||||
|
_LOGGER.error("The remote connection was lost: %s", err)
|
||||||
|
return self.async_abort(reason=REASON_NOT_CONNECTED)
|
||||||
|
except Exception as err: # pylint: disable=broad-except
|
||||||
|
_LOGGER.exception("Unknown error: %s", err)
|
||||||
|
return self.async_abort(reason=REASON_UNKNOWN)
|
||||||
|
|
||||||
|
if "base" not in errors:
|
||||||
|
encryption_data = {
|
||||||
|
CONF_APP_ID: self._remote.app_id,
|
||||||
|
CONF_ENCRYPTION_KEY: self._remote.enc_key,
|
||||||
|
}
|
||||||
|
|
||||||
|
self._data = {**self._data, **encryption_data}
|
||||||
|
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=self._data[CONF_NAME], data=self._data,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._remote.request_pin_code(name="Home Assistant")
|
||||||
|
except (TimeoutError, URLError, SOAPError, OSError) as err:
|
||||||
|
_LOGGER.error("The remote connection was lost: %s", err)
|
||||||
|
return self.async_abort(reason=REASON_NOT_CONNECTED)
|
||||||
|
except Exception as err: # pylint: disable=broad-except
|
||||||
|
_LOGGER.exception("Unknown error: %s", err)
|
||||||
|
return self.async_abort(reason=REASON_UNKNOWN)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="pairing",
|
||||||
|
data_schema=vol.Schema({vol.Required(CONF_PIN): str}),
|
||||||
|
errors=errors,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_import(self, import_config):
|
||||||
|
"""Import a config entry from configuration.yaml."""
|
||||||
|
return await self.async_step_user(user_input=import_config)
|
||||||
|
|
||||||
|
async def async_load_data(self, config):
|
||||||
|
"""Load the data."""
|
||||||
|
self._data = config
|
||||||
|
|
||||||
|
self._data[CONF_PORT] = (
|
||||||
|
self._data[CONF_PORT] if CONF_PORT in self._data else DEFAULT_PORT
|
||||||
|
)
|
||||||
|
self._data[CONF_ON_ACTION] = (
|
||||||
|
self._data[CONF_ON_ACTION] if CONF_ON_ACTION in self._data else None
|
||||||
|
)
|
||||||
|
|
||||||
|
await self.async_set_unique_id(self._data[CONF_HOST])
|
||||||
|
self._abort_if_unique_id_configured()
|
17
homeassistant/components/panasonic_viera/const.py
Normal file
17
homeassistant/components/panasonic_viera/const.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
"""Constants for the Panasonic Viera integration."""
|
||||||
|
DOMAIN = "panasonic_viera"
|
||||||
|
|
||||||
|
DEVICE_MANUFACTURER = "Panasonic"
|
||||||
|
|
||||||
|
CONF_ON_ACTION = "turn_on_action"
|
||||||
|
CONF_APP_ID = "app_id"
|
||||||
|
CONF_ENCRYPTION_KEY = "encryption_key"
|
||||||
|
|
||||||
|
DEFAULT_NAME = "Panasonic Viera TV"
|
||||||
|
DEFAULT_PORT = 55000
|
||||||
|
|
||||||
|
ERROR_NOT_CONNECTED = "not_connected"
|
||||||
|
ERROR_INVALID_PIN_CODE = "invalid_pin_code"
|
||||||
|
|
||||||
|
REASON_NOT_CONNECTED = "not_connected"
|
||||||
|
REASON_UNKNOWN = "unknown"
|
@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"domain": "panasonic_viera",
|
"domain": "panasonic_viera",
|
||||||
"name": "Panasonic Viera TV",
|
"name": "Panasonic Viera",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/panasonic_viera",
|
"documentation": "https://www.home-assistant.io/integrations/panasonic_viera",
|
||||||
"requirements": ["panasonic_viera==0.3.2", "wakeonlan==1.1.6"],
|
"requirements": ["panasonic_viera==0.3.5"],
|
||||||
"codeowners": []
|
"codeowners": ["@joogps"],
|
||||||
|
"config_flow": true
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
"""Support for interface with a Panasonic Viera TV."""
|
"""Support for interface with a Panasonic Viera TV."""
|
||||||
|
from functools import partial
|
||||||
import logging
|
import logging
|
||||||
|
from urllib.request import URLError
|
||||||
|
|
||||||
from panasonic_viera import RemoteControl
|
from panasonic_viera import EncryptionRequired, Keys, RemoteControl, SOAPError
|
||||||
import voluptuous as vol
|
|
||||||
import wakeonlan
|
|
||||||
|
|
||||||
from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice
|
from homeassistant.components.media_player import MediaPlayerDevice
|
||||||
from homeassistant.components.media_player.const import (
|
from homeassistant.components.media_player.const import (
|
||||||
MEDIA_TYPE_URL,
|
MEDIA_TYPE_URL,
|
||||||
SUPPORT_NEXT_TRACK,
|
SUPPORT_NEXT_TRACK,
|
||||||
@ -20,25 +20,10 @@ from homeassistant.components.media_player.const import (
|
|||||||
SUPPORT_VOLUME_SET,
|
SUPPORT_VOLUME_SET,
|
||||||
SUPPORT_VOLUME_STEP,
|
SUPPORT_VOLUME_STEP,
|
||||||
)
|
)
|
||||||
from homeassistant.const import (
|
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT, STATE_OFF, STATE_ON
|
||||||
CONF_BROADCAST_ADDRESS,
|
from homeassistant.helpers.script import Script
|
||||||
CONF_HOST,
|
|
||||||
CONF_MAC,
|
|
||||||
CONF_NAME,
|
|
||||||
CONF_PORT,
|
|
||||||
STATE_OFF,
|
|
||||||
STATE_ON,
|
|
||||||
)
|
|
||||||
import homeassistant.helpers.config_validation as cv
|
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
from .const import CONF_APP_ID, CONF_ENCRYPTION_KEY, CONF_ON_ACTION
|
||||||
|
|
||||||
CONF_APP_POWER = "app_power"
|
|
||||||
|
|
||||||
DEFAULT_NAME = "Panasonic Viera TV"
|
|
||||||
DEFAULT_PORT = 55000
|
|
||||||
DEFAULT_BROADCAST_ADDRESS = "255.255.255.255"
|
|
||||||
DEFAULT_APP_POWER = False
|
|
||||||
|
|
||||||
SUPPORT_VIERATV = (
|
SUPPORT_VIERATV = (
|
||||||
SUPPORT_PAUSE
|
SUPPORT_PAUSE
|
||||||
@ -48,99 +33,58 @@ SUPPORT_VIERATV = (
|
|||||||
| SUPPORT_PREVIOUS_TRACK
|
| SUPPORT_PREVIOUS_TRACK
|
||||||
| SUPPORT_NEXT_TRACK
|
| SUPPORT_NEXT_TRACK
|
||||||
| SUPPORT_TURN_OFF
|
| SUPPORT_TURN_OFF
|
||||||
|
| SUPPORT_TURN_ON
|
||||||
| SUPPORT_PLAY
|
| SUPPORT_PLAY
|
||||||
| SUPPORT_PLAY_MEDIA
|
| SUPPORT_PLAY_MEDIA
|
||||||
| SUPPORT_STOP
|
| SUPPORT_STOP
|
||||||
)
|
)
|
||||||
|
|
||||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
|
_LOGGER = logging.getLogger(__name__)
|
||||||
{
|
|
||||||
vol.Required(CONF_HOST): cv.string,
|
|
||||||
vol.Optional(CONF_MAC): cv.string,
|
|
||||||
vol.Optional(
|
|
||||||
CONF_BROADCAST_ADDRESS, default=DEFAULT_BROADCAST_ADDRESS
|
|
||||||
): cv.string,
|
|
||||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
|
||||||
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
|
|
||||||
vol.Optional(CONF_APP_POWER, default=DEFAULT_APP_POWER): cv.boolean,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||||
"""Set up the Panasonic Viera TV platform."""
|
"""Set up Panasonic Viera TV from a config entry."""
|
||||||
mac = config.get(CONF_MAC)
|
|
||||||
broadcast = config.get(CONF_BROADCAST_ADDRESS)
|
|
||||||
name = config.get(CONF_NAME)
|
|
||||||
port = config.get(CONF_PORT)
|
|
||||||
app_power = config.get(CONF_APP_POWER)
|
|
||||||
|
|
||||||
if discovery_info:
|
config = config_entry.data
|
||||||
_LOGGER.debug("%s", discovery_info)
|
|
||||||
name = discovery_info.get("name")
|
|
||||||
host = discovery_info.get("host")
|
|
||||||
port = discovery_info.get("port")
|
|
||||||
udn = discovery_info.get("udn")
|
|
||||||
if udn and udn.startswith("uuid:"):
|
|
||||||
uuid = udn[len("uuid:") :]
|
|
||||||
else:
|
|
||||||
uuid = None
|
|
||||||
remote = RemoteControl(host, port)
|
|
||||||
add_entities([PanasonicVieraTVDevice(mac, name, remote, host, app_power, uuid)])
|
|
||||||
return True
|
|
||||||
|
|
||||||
host = config.get(CONF_HOST)
|
host = config[CONF_HOST]
|
||||||
remote = RemoteControl(host, port)
|
port = config[CONF_PORT]
|
||||||
|
name = config[CONF_NAME]
|
||||||
|
|
||||||
add_entities(
|
on_action = config[CONF_ON_ACTION]
|
||||||
[PanasonicVieraTVDevice(mac, name, remote, host, broadcast, app_power)]
|
if on_action is not None:
|
||||||
)
|
on_action = Script(hass, on_action)
|
||||||
return True
|
|
||||||
|
params = {}
|
||||||
|
if CONF_APP_ID in config and CONF_ENCRYPTION_KEY in config:
|
||||||
|
params["app_id"] = config[CONF_APP_ID]
|
||||||
|
params["encryption_key"] = config[CONF_ENCRYPTION_KEY]
|
||||||
|
|
||||||
|
remote = Remote(hass, host, port, on_action, **params)
|
||||||
|
await remote.async_create_remote_control(during_setup=True)
|
||||||
|
|
||||||
|
tv_device = PanasonicVieraTVDevice(remote, name)
|
||||||
|
|
||||||
|
async_add_entities([tv_device])
|
||||||
|
|
||||||
|
|
||||||
class PanasonicVieraTVDevice(MediaPlayerDevice):
|
class PanasonicVieraTVDevice(MediaPlayerDevice):
|
||||||
"""Representation of a Panasonic Viera TV."""
|
"""Representation of a Panasonic Viera TV."""
|
||||||
|
|
||||||
def __init__(self, mac, name, remote, host, broadcast, app_power, uuid=None):
|
def __init__(
|
||||||
|
self, remote, name, uuid=None,
|
||||||
|
):
|
||||||
"""Initialize the Panasonic device."""
|
"""Initialize the Panasonic device."""
|
||||||
# Save a reference to the imported class
|
# Save a reference to the imported class
|
||||||
self._wol = wakeonlan
|
self._remote = remote
|
||||||
self._mac = mac
|
|
||||||
self._name = name
|
self._name = name
|
||||||
self._uuid = uuid
|
self._uuid = uuid
|
||||||
self._muted = False
|
|
||||||
self._playing = True
|
|
||||||
self._state = None
|
|
||||||
self._remote = remote
|
|
||||||
self._host = host
|
|
||||||
self._broadcast = broadcast
|
|
||||||
self._volume = 0
|
|
||||||
self._app_power = app_power
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self) -> str:
|
def unique_id(self) -> str:
|
||||||
"""Return the unique ID of this Viera TV."""
|
"""Return the unique ID of this Viera TV."""
|
||||||
return self._uuid
|
return self._uuid
|
||||||
|
|
||||||
def update(self):
|
|
||||||
"""Retrieve the latest data."""
|
|
||||||
try:
|
|
||||||
self._muted = self._remote.get_mute()
|
|
||||||
self._volume = self._remote.get_volume() / 100
|
|
||||||
self._state = STATE_ON
|
|
||||||
except OSError:
|
|
||||||
self._state = STATE_OFF
|
|
||||||
|
|
||||||
def send_key(self, key):
|
|
||||||
"""Send a key to the tv and handles exceptions."""
|
|
||||||
try:
|
|
||||||
self._remote.send_key(key)
|
|
||||||
self._state = STATE_ON
|
|
||||||
except OSError:
|
|
||||||
self._state = STATE_OFF
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
"""Return the name of the device."""
|
"""Return the name of the device."""
|
||||||
@ -149,98 +93,208 @@ class PanasonicVieraTVDevice(MediaPlayerDevice):
|
|||||||
@property
|
@property
|
||||||
def state(self):
|
def state(self):
|
||||||
"""Return the state of the device."""
|
"""Return the state of the device."""
|
||||||
return self._state
|
return self._remote.state
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self):
|
||||||
|
"""Return if True the device is available."""
|
||||||
|
return self._remote.available
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def volume_level(self):
|
def volume_level(self):
|
||||||
"""Volume level of the media player (0..1)."""
|
"""Volume level of the media player (0..1)."""
|
||||||
return self._volume
|
return self._remote.volume
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_volume_muted(self):
|
def is_volume_muted(self):
|
||||||
"""Boolean if volume is currently muted."""
|
"""Boolean if volume is currently muted."""
|
||||||
return self._muted
|
return self._remote.muted
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def supported_features(self):
|
def supported_features(self):
|
||||||
"""Flag media player features that are supported."""
|
"""Flag media player features that are supported."""
|
||||||
if self._mac or self._app_power:
|
|
||||||
return SUPPORT_VIERATV | SUPPORT_TURN_ON
|
|
||||||
return SUPPORT_VIERATV
|
return SUPPORT_VIERATV
|
||||||
|
|
||||||
def turn_on(self):
|
async def async_update(self):
|
||||||
|
"""Retrieve the latest data."""
|
||||||
|
await self._remote.async_update()
|
||||||
|
|
||||||
|
async def async_turn_on(self):
|
||||||
"""Turn on the media player."""
|
"""Turn on the media player."""
|
||||||
if self._mac:
|
await self._remote.async_turn_on()
|
||||||
self._wol.send_magic_packet(self._mac, ip_address=self._broadcast)
|
|
||||||
self._state = STATE_ON
|
|
||||||
elif self._app_power:
|
|
||||||
self._remote.turn_on()
|
|
||||||
self._state = STATE_ON
|
|
||||||
|
|
||||||
def turn_off(self):
|
async def async_turn_off(self):
|
||||||
"""Turn off media player."""
|
"""Turn off media player."""
|
||||||
if self._state != STATE_OFF:
|
await self._remote.async_turn_off()
|
||||||
self._remote.turn_off()
|
|
||||||
self._state = STATE_OFF
|
|
||||||
|
|
||||||
def volume_up(self):
|
async def async_volume_up(self):
|
||||||
"""Volume up the media player."""
|
"""Volume up the media player."""
|
||||||
self._remote.volume_up()
|
await self._remote.async_send_key(Keys.volume_up)
|
||||||
|
|
||||||
def volume_down(self):
|
async def async_volume_down(self):
|
||||||
"""Volume down media player."""
|
"""Volume down media player."""
|
||||||
self._remote.volume_down()
|
await self._remote.async_send_key(Keys.volume_down)
|
||||||
|
|
||||||
def mute_volume(self, mute):
|
async def async_mute_volume(self, mute):
|
||||||
"""Send mute command."""
|
"""Send mute command."""
|
||||||
self._remote.set_mute(mute)
|
await self._remote.async_set_mute(mute)
|
||||||
|
|
||||||
def set_volume_level(self, volume):
|
async def async_set_volume_level(self, volume):
|
||||||
|
"""Set volume level, range 0..1."""
|
||||||
|
await self._remote.async_set_volume(volume)
|
||||||
|
|
||||||
|
async def async_media_play_pause(self):
|
||||||
|
"""Simulate play pause media player."""
|
||||||
|
if self._remote.playing:
|
||||||
|
await self._remote.async_send_key(Keys.pause)
|
||||||
|
self._remote.playing = False
|
||||||
|
else:
|
||||||
|
await self._remote.async_send_key(Keys.play)
|
||||||
|
self._remote.playing = True
|
||||||
|
|
||||||
|
async def async_media_play(self):
|
||||||
|
"""Send play command."""
|
||||||
|
await self._remote.async_send_key(Keys.play)
|
||||||
|
self._remote.playing = True
|
||||||
|
|
||||||
|
async def async_media_pause(self):
|
||||||
|
"""Send pause command."""
|
||||||
|
await self._remote.async_send_key(Keys.pause)
|
||||||
|
self._remote.playing = False
|
||||||
|
|
||||||
|
async def async_media_stop(self):
|
||||||
|
"""Stop playback."""
|
||||||
|
await self._remote.async_send_key(Keys.stop)
|
||||||
|
|
||||||
|
async def async_media_next_track(self):
|
||||||
|
"""Send the fast forward command."""
|
||||||
|
await self._remote.async_send_key(Keys.fast_forward)
|
||||||
|
|
||||||
|
async def async_media_previous_track(self):
|
||||||
|
"""Send the rewind command."""
|
||||||
|
await self._remote.async_send_key(Keys.rewind)
|
||||||
|
|
||||||
|
async def async_play_media(self, media_type, media_id, **kwargs):
|
||||||
|
"""Play media."""
|
||||||
|
await self._remote.async_play_media(media_type, media_id)
|
||||||
|
|
||||||
|
|
||||||
|
class Remote:
|
||||||
|
"""The Remote class. It stores the TV properties and the remote control connection itself."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, hass, host, port, on_action=None, app_id=None, encryption_key=None,
|
||||||
|
):
|
||||||
|
"""Initialize the Remote class."""
|
||||||
|
self._hass = hass
|
||||||
|
|
||||||
|
self._host = host
|
||||||
|
self._port = port
|
||||||
|
|
||||||
|
self._on_action = on_action
|
||||||
|
|
||||||
|
self._app_id = app_id
|
||||||
|
self._encryption_key = encryption_key
|
||||||
|
|
||||||
|
self.state = None
|
||||||
|
self.available = False
|
||||||
|
self.volume = 0
|
||||||
|
self.muted = False
|
||||||
|
self.playing = True
|
||||||
|
|
||||||
|
self._control = None
|
||||||
|
|
||||||
|
async def async_create_remote_control(self, during_setup=False):
|
||||||
|
"""Create remote control."""
|
||||||
|
control_existed = self._control is not None
|
||||||
|
try:
|
||||||
|
params = {}
|
||||||
|
if self._app_id and self._encryption_key:
|
||||||
|
params["app_id"] = self._app_id
|
||||||
|
params["encryption_key"] = self._encryption_key
|
||||||
|
|
||||||
|
self._control = await self._hass.async_add_executor_job(
|
||||||
|
partial(RemoteControl, self._host, self._port, **params)
|
||||||
|
)
|
||||||
|
|
||||||
|
self.state = STATE_ON
|
||||||
|
self.available = True
|
||||||
|
except (TimeoutError, URLError, SOAPError, OSError) as err:
|
||||||
|
if control_existed or during_setup:
|
||||||
|
_LOGGER.error("Could not establish remote connection: %s", err)
|
||||||
|
|
||||||
|
self._control = None
|
||||||
|
self.state = STATE_OFF
|
||||||
|
self.available = self._on_action is not None
|
||||||
|
except Exception as err: # pylint: disable=broad-except
|
||||||
|
if control_existed or during_setup:
|
||||||
|
_LOGGER.exception("An unknown error occurred: %s", err)
|
||||||
|
self._control = None
|
||||||
|
self.state = STATE_OFF
|
||||||
|
self.available = self._on_action is not None
|
||||||
|
|
||||||
|
async def async_update(self):
|
||||||
|
"""Update device data."""
|
||||||
|
if self._control is None:
|
||||||
|
await self.async_create_remote_control()
|
||||||
|
return
|
||||||
|
|
||||||
|
await self._handle_errors(self._update)
|
||||||
|
|
||||||
|
async def _update(self):
|
||||||
|
"""Retrieve the latest data."""
|
||||||
|
self.muted = self._control.get_mute()
|
||||||
|
self.volume = self._control.get_volume() / 100
|
||||||
|
|
||||||
|
self.state = STATE_ON
|
||||||
|
self.available = True
|
||||||
|
|
||||||
|
async def async_send_key(self, key):
|
||||||
|
"""Send a key to the TV and handle exceptions."""
|
||||||
|
await self._handle_errors(self._control.send_key, key)
|
||||||
|
|
||||||
|
async def async_turn_on(self):
|
||||||
|
"""Turn on the TV."""
|
||||||
|
if self._on_action is not None:
|
||||||
|
await self._on_action.async_run()
|
||||||
|
self.state = STATE_ON
|
||||||
|
elif self.state != STATE_ON:
|
||||||
|
await self.async_send_key(Keys.power)
|
||||||
|
self.state = STATE_ON
|
||||||
|
|
||||||
|
async def async_turn_off(self):
|
||||||
|
"""Turn off the TV."""
|
||||||
|
if self.state != STATE_OFF:
|
||||||
|
await self.async_send_key(Keys.power)
|
||||||
|
self.state = STATE_OFF
|
||||||
|
await self.async_update()
|
||||||
|
|
||||||
|
async def async_set_mute(self, enable):
|
||||||
|
"""Set mute based on 'enable'."""
|
||||||
|
await self._handle_errors(self._control.set_mute, enable)
|
||||||
|
|
||||||
|
async def async_set_volume(self, volume):
|
||||||
"""Set volume level, range 0..1."""
|
"""Set volume level, range 0..1."""
|
||||||
volume = int(volume * 100)
|
volume = int(volume * 100)
|
||||||
try:
|
await self._handle_errors(self._control.set_volume, volume)
|
||||||
self._remote.set_volume(volume)
|
|
||||||
self._state = STATE_ON
|
|
||||||
except OSError:
|
|
||||||
self._state = STATE_OFF
|
|
||||||
|
|
||||||
def media_play_pause(self):
|
async def async_play_media(self, media_type, media_id):
|
||||||
"""Simulate play pause media player."""
|
|
||||||
if self._playing:
|
|
||||||
self.media_pause()
|
|
||||||
else:
|
|
||||||
self.media_play()
|
|
||||||
|
|
||||||
def media_play(self):
|
|
||||||
"""Send play command."""
|
|
||||||
self._playing = True
|
|
||||||
self._remote.media_play()
|
|
||||||
|
|
||||||
def media_pause(self):
|
|
||||||
"""Send media pause command to media player."""
|
|
||||||
self._playing = False
|
|
||||||
self._remote.media_pause()
|
|
||||||
|
|
||||||
def media_next_track(self):
|
|
||||||
"""Send next track command."""
|
|
||||||
self._remote.media_next_track()
|
|
||||||
|
|
||||||
def media_previous_track(self):
|
|
||||||
"""Send the previous track command."""
|
|
||||||
self._remote.media_previous_track()
|
|
||||||
|
|
||||||
def play_media(self, media_type, media_id, **kwargs):
|
|
||||||
"""Play media."""
|
"""Play media."""
|
||||||
_LOGGER.debug("Play media: %s (%s)", media_id, media_type)
|
_LOGGER.debug("Play media: %s (%s)", media_id, media_type)
|
||||||
|
|
||||||
if media_type == MEDIA_TYPE_URL:
|
if media_type != MEDIA_TYPE_URL:
|
||||||
try:
|
|
||||||
self._remote.open_webpage(media_id)
|
|
||||||
except (TimeoutError, OSError):
|
|
||||||
self._state = STATE_OFF
|
|
||||||
else:
|
|
||||||
_LOGGER.warning("Unsupported media_type: %s", media_type)
|
_LOGGER.warning("Unsupported media_type: %s", media_type)
|
||||||
|
return
|
||||||
|
|
||||||
def media_stop(self):
|
await self._handle_errors(self._control.open_webpage, media_id)
|
||||||
"""Stop playback."""
|
|
||||||
self.send_key("NRC_CANCEL-ONOFF")
|
async def _handle_errors(self, func, *args):
|
||||||
|
"""Handle errors from func, set available and reconnect if needed."""
|
||||||
|
try:
|
||||||
|
await self._hass.async_add_executor_job(func, *args)
|
||||||
|
except EncryptionRequired:
|
||||||
|
_LOGGER.error("The connection couldn't be encrypted")
|
||||||
|
except (TimeoutError, URLError, SOAPError, OSError):
|
||||||
|
self.state = STATE_OFF
|
||||||
|
self.available = self._on_action is not None
|
||||||
|
await self.async_create_remote_control()
|
||||||
|
31
homeassistant/components/panasonic_viera/strings.json
Normal file
31
homeassistant/components/panasonic_viera/strings.json
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"title": "Panasonic Viera",
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"title": "Setup your TV",
|
||||||
|
"description": "Enter your Panasonic Viera TV's IP address",
|
||||||
|
"data": {
|
||||||
|
"host": "IP address",
|
||||||
|
"name": "Name"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"pairing": {
|
||||||
|
"title": "Pairing",
|
||||||
|
"description": "Enter the PIN displayed on your TV",
|
||||||
|
"data": {
|
||||||
|
"pin": "PIN"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"not_connected": "Could not establish a remote connection with your Panasonic Viera TV",
|
||||||
|
"invalid_pin_code": "The PIN code you entered was invalid"
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "This Panasonic Viera TV is already configured.",
|
||||||
|
"not_connected": "The remote connection with your Panasonic Viera TV was lost. Check the logs for more information.",
|
||||||
|
"unknown": "An unknown error occured. Check the logs for more information."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -89,6 +89,7 @@ FLOWS = [
|
|||||||
"opentherm_gw",
|
"opentherm_gw",
|
||||||
"openuv",
|
"openuv",
|
||||||
"owntracks",
|
"owntracks",
|
||||||
|
"panasonic_viera",
|
||||||
"plaato",
|
"plaato",
|
||||||
"plex",
|
"plex",
|
||||||
"point",
|
"point",
|
||||||
|
@ -1000,7 +1000,7 @@ paho-mqtt==1.5.0
|
|||||||
panacotta==0.1
|
panacotta==0.1
|
||||||
|
|
||||||
# homeassistant.components.panasonic_viera
|
# homeassistant.components.panasonic_viera
|
||||||
panasonic_viera==0.3.2
|
panasonic_viera==0.3.5
|
||||||
|
|
||||||
# homeassistant.components.pcal9535a
|
# homeassistant.components.pcal9535a
|
||||||
pcal9535a==0.7
|
pcal9535a==0.7
|
||||||
@ -2105,7 +2105,6 @@ vtjp==0.1.14
|
|||||||
# homeassistant.components.vultr
|
# homeassistant.components.vultr
|
||||||
vultr==0.1.2
|
vultr==0.1.2
|
||||||
|
|
||||||
# homeassistant.components.panasonic_viera
|
|
||||||
# homeassistant.components.wake_on_lan
|
# homeassistant.components.wake_on_lan
|
||||||
wakeonlan==1.1.6
|
wakeonlan==1.1.6
|
||||||
|
|
||||||
|
@ -383,6 +383,9 @@ openerz-api==0.1.0
|
|||||||
# homeassistant.components.shiftr
|
# homeassistant.components.shiftr
|
||||||
paho-mqtt==1.5.0
|
paho-mqtt==1.5.0
|
||||||
|
|
||||||
|
# homeassistant.components.panasonic_viera
|
||||||
|
panasonic_viera==0.3.5
|
||||||
|
|
||||||
# homeassistant.components.aruba
|
# homeassistant.components.aruba
|
||||||
# homeassistant.components.cisco_ios
|
# homeassistant.components.cisco_ios
|
||||||
# homeassistant.components.pandora
|
# homeassistant.components.pandora
|
||||||
@ -790,7 +793,6 @@ vsure==1.5.4
|
|||||||
# homeassistant.components.vultr
|
# homeassistant.components.vultr
|
||||||
vultr==0.1.2
|
vultr==0.1.2
|
||||||
|
|
||||||
# homeassistant.components.panasonic_viera
|
|
||||||
# homeassistant.components.wake_on_lan
|
# homeassistant.components.wake_on_lan
|
||||||
wakeonlan==1.1.6
|
wakeonlan==1.1.6
|
||||||
|
|
||||||
|
1
tests/components/panasonic_viera/__init__.py
Normal file
1
tests/components/panasonic_viera/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Tests for the Panasonic Viera component."""
|
587
tests/components/panasonic_viera/test_config_flow.py
Normal file
587
tests/components/panasonic_viera/test_config_flow.py
Normal file
@ -0,0 +1,587 @@
|
|||||||
|
"""Test the Panasonic Viera config flow."""
|
||||||
|
from unittest.mock import Mock
|
||||||
|
|
||||||
|
from asynctest import patch
|
||||||
|
from panasonic_viera import TV_TYPE_ENCRYPTED, TV_TYPE_NONENCRYPTED, SOAPError
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.components.panasonic_viera.const import (
|
||||||
|
CONF_APP_ID,
|
||||||
|
CONF_ENCRYPTION_KEY,
|
||||||
|
CONF_ON_ACTION,
|
||||||
|
DEFAULT_NAME,
|
||||||
|
DEFAULT_PORT,
|
||||||
|
DOMAIN,
|
||||||
|
ERROR_INVALID_PIN_CODE,
|
||||||
|
ERROR_NOT_CONNECTED,
|
||||||
|
REASON_NOT_CONNECTED,
|
||||||
|
REASON_UNKNOWN,
|
||||||
|
)
|
||||||
|
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PIN, CONF_PORT
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(name="panasonic_viera_setup", autouse=True)
|
||||||
|
def panasonic_viera_setup_fixture():
|
||||||
|
"""Mock panasonic_viera setup."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.panasonic_viera.async_setup", return_value=True
|
||||||
|
), patch(
|
||||||
|
"homeassistant.components.panasonic_viera.async_setup_entry", return_value=True,
|
||||||
|
):
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
def get_mock_remote(
|
||||||
|
host="1.2.3.4",
|
||||||
|
authorize_error=None,
|
||||||
|
encrypted=False,
|
||||||
|
app_id=None,
|
||||||
|
encryption_key=None,
|
||||||
|
):
|
||||||
|
"""Return a mock remote."""
|
||||||
|
mock_remote = Mock()
|
||||||
|
|
||||||
|
mock_remote.type = TV_TYPE_ENCRYPTED if encrypted else TV_TYPE_NONENCRYPTED
|
||||||
|
mock_remote.app_id = app_id
|
||||||
|
mock_remote.enc_key = encryption_key
|
||||||
|
|
||||||
|
def request_pin_code(name=None):
|
||||||
|
return
|
||||||
|
|
||||||
|
mock_remote.request_pin_code = request_pin_code
|
||||||
|
|
||||||
|
def authorize_pin_code(pincode):
|
||||||
|
if pincode == "1234":
|
||||||
|
return
|
||||||
|
|
||||||
|
if authorize_error is not None:
|
||||||
|
raise authorize_error
|
||||||
|
|
||||||
|
mock_remote.authorize_pin_code = authorize_pin_code
|
||||||
|
|
||||||
|
return mock_remote
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_non_encrypted(hass):
|
||||||
|
"""Test flow without encryption."""
|
||||||
|
|
||||||
|
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=False)
|
||||||
|
|
||||||
|
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"] == "create_entry"
|
||||||
|
assert result["title"] == DEFAULT_NAME
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_HOST: "1.2.3.4",
|
||||||
|
CONF_NAME: DEFAULT_NAME,
|
||||||
|
CONF_PORT: DEFAULT_PORT,
|
||||||
|
CONF_ON_ACTION: None,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_not_connected_error(hass):
|
||||||
|
"""Test flow with connection error."""
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.panasonic_viera.config_flow.RemoteControl",
|
||||||
|
side_effect=TimeoutError,
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {"base": ERROR_NOT_CONNECTED}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_unknown_abort(hass):
|
||||||
|
"""Test flow with unknown error abortion."""
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.panasonic_viera.config_flow.RemoteControl",
|
||||||
|
side_effect=Exception,
|
||||||
|
):
|
||||||
|
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"] == REASON_UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_encrypted_valid_pin_code(hass):
|
||||||
|
"""Test flow with encryption and valid PIN code."""
|
||||||
|
|
||||||
|
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, app_id="test-app-id", encryption_key="test-encryption-key",
|
||||||
|
)
|
||||||
|
|
||||||
|
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"] == "form"
|
||||||
|
assert result["step_id"] == "pairing"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {CONF_PIN: "1234"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["title"] == DEFAULT_NAME
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_HOST: "1.2.3.4",
|
||||||
|
CONF_NAME: DEFAULT_NAME,
|
||||||
|
CONF_PORT: DEFAULT_PORT,
|
||||||
|
CONF_ON_ACTION: None,
|
||||||
|
CONF_APP_ID: "test-app-id",
|
||||||
|
CONF_ENCRYPTION_KEY: "test-encryption-key",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_encrypted_invalid_pin_code_error(hass):
|
||||||
|
"""Test flow with encryption and invalid PIN code error during pairing 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, authorize_error=SOAPError)
|
||||||
|
|
||||||
|
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"] == "form"
|
||||||
|
assert result["step_id"] == "pairing"
|
||||||
|
|
||||||
|
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_PIN: "0000"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "pairing"
|
||||||
|
assert result["errors"] == {"base": ERROR_INVALID_PIN_CODE}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_encrypted_not_connected_abort(hass):
|
||||||
|
"""Test flow with encryption and PIN code connection error abortion during pairing 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, authorize_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"] == "form"
|
||||||
|
assert result["step_id"] == "pairing"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {CONF_PIN: "0000"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "abort"
|
||||||
|
assert result["reason"] == REASON_NOT_CONNECTED
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_encrypted_unknown_abort(hass):
|
||||||
|
"""Test flow with encryption and PIN code unknown error abortion during pairing 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, authorize_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"] == "form"
|
||||||
|
assert result["step_id"] == "pairing"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {CONF_PIN: "0000"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "abort"
|
||||||
|
assert result["reason"] == REASON_UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_non_encrypted_already_configured_abort(hass):
|
||||||
|
"""Test flow without encryption and existing config entry abortion."""
|
||||||
|
|
||||||
|
MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
unique_id="1.2.3.4",
|
||||||
|
data={CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME, CONF_PORT: DEFAULT_PORT},
|
||||||
|
).add_to_hass(hass)
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_USER},
|
||||||
|
data={CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "abort"
|
||||||
|
assert result["reason"] == "already_configured"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_flow_encrypted_already_configured_abort(hass):
|
||||||
|
"""Test flow with encryption and existing config entry abortion."""
|
||||||
|
|
||||||
|
MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
unique_id="1.2.3.4",
|
||||||
|
data={
|
||||||
|
CONF_HOST: "1.2.3.4",
|
||||||
|
CONF_NAME: DEFAULT_NAME,
|
||||||
|
CONF_PORT: DEFAULT_PORT,
|
||||||
|
CONF_APP_ID: "test-app-id",
|
||||||
|
CONF_ENCRYPTION_KEY: "test-encryption-key",
|
||||||
|
},
|
||||||
|
).add_to_hass(hass)
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_USER},
|
||||||
|
data={CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "abort"
|
||||||
|
assert result["reason"] == "already_configured"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_imported_flow_non_encrypted(hass):
|
||||||
|
"""Test imported flow without encryption."""
|
||||||
|
|
||||||
|
mock_remote = get_mock_remote(encrypted=False)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.panasonic_viera.config_flow.RemoteControl",
|
||||||
|
return_value=mock_remote,
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_IMPORT},
|
||||||
|
data={
|
||||||
|
CONF_HOST: "1.2.3.4",
|
||||||
|
CONF_NAME: DEFAULT_NAME,
|
||||||
|
CONF_PORT: DEFAULT_PORT,
|
||||||
|
CONF_ON_ACTION: "test-on-action",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["title"] == DEFAULT_NAME
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_HOST: "1.2.3.4",
|
||||||
|
CONF_NAME: DEFAULT_NAME,
|
||||||
|
CONF_PORT: DEFAULT_PORT,
|
||||||
|
CONF_ON_ACTION: "test-on-action",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_imported_flow_encrypted_valid_pin_code(hass):
|
||||||
|
"""Test imported flow with encryption and valid PIN code."""
|
||||||
|
|
||||||
|
mock_remote = get_mock_remote(
|
||||||
|
encrypted=True, app_id="test-app-id", encryption_key="test-encryption-key",
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.panasonic_viera.config_flow.RemoteControl",
|
||||||
|
return_value=mock_remote,
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_IMPORT},
|
||||||
|
data={
|
||||||
|
CONF_HOST: "1.2.3.4",
|
||||||
|
CONF_NAME: DEFAULT_NAME,
|
||||||
|
CONF_PORT: DEFAULT_PORT,
|
||||||
|
CONF_ON_ACTION: "test-on-action",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "pairing"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {CONF_PIN: "1234"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "create_entry"
|
||||||
|
assert result["title"] == DEFAULT_NAME
|
||||||
|
assert result["data"] == {
|
||||||
|
CONF_HOST: "1.2.3.4",
|
||||||
|
CONF_NAME: DEFAULT_NAME,
|
||||||
|
CONF_PORT: DEFAULT_PORT,
|
||||||
|
CONF_ON_ACTION: "test-on-action",
|
||||||
|
CONF_APP_ID: "test-app-id",
|
||||||
|
CONF_ENCRYPTION_KEY: "test-encryption-key",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_imported_flow_encrypted_invalid_pin_code_error(hass):
|
||||||
|
"""Test imported flow with encryption and invalid PIN code error during pairing step."""
|
||||||
|
|
||||||
|
mock_remote = get_mock_remote(encrypted=True, authorize_error=SOAPError)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.panasonic_viera.config_flow.RemoteControl",
|
||||||
|
return_value=mock_remote,
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_IMPORT},
|
||||||
|
data={
|
||||||
|
CONF_HOST: "1.2.3.4",
|
||||||
|
CONF_NAME: DEFAULT_NAME,
|
||||||
|
CONF_PORT: DEFAULT_PORT,
|
||||||
|
CONF_ON_ACTION: "test-on-action",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "pairing"
|
||||||
|
|
||||||
|
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_PIN: "0000"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "pairing"
|
||||||
|
assert result["errors"] == {"base": ERROR_INVALID_PIN_CODE}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_imported_flow_encrypted_not_connected_abort(hass):
|
||||||
|
"""Test imported flow with encryption and PIN code connection error abortion during pairing step."""
|
||||||
|
|
||||||
|
mock_remote = get_mock_remote(encrypted=True, authorize_error=TimeoutError)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.panasonic_viera.config_flow.RemoteControl",
|
||||||
|
return_value=mock_remote,
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_IMPORT},
|
||||||
|
data={
|
||||||
|
CONF_HOST: "1.2.3.4",
|
||||||
|
CONF_NAME: DEFAULT_NAME,
|
||||||
|
CONF_PORT: DEFAULT_PORT,
|
||||||
|
CONF_ON_ACTION: "test-on-action",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "pairing"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {CONF_PIN: "0000"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "abort"
|
||||||
|
assert result["reason"] == REASON_NOT_CONNECTED
|
||||||
|
|
||||||
|
|
||||||
|
async def test_imported_flow_encrypted_unknown_abort(hass):
|
||||||
|
"""Test imported flow with encryption and PIN code unknown error abortion during pairing step."""
|
||||||
|
|
||||||
|
mock_remote = get_mock_remote(encrypted=True, authorize_error=Exception)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.panasonic_viera.config_flow.RemoteControl",
|
||||||
|
return_value=mock_remote,
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_IMPORT},
|
||||||
|
data={
|
||||||
|
CONF_HOST: "1.2.3.4",
|
||||||
|
CONF_NAME: DEFAULT_NAME,
|
||||||
|
CONF_PORT: DEFAULT_PORT,
|
||||||
|
CONF_ON_ACTION: "test-on-action",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "pairing"
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"], {CONF_PIN: "0000"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "abort"
|
||||||
|
assert result["reason"] == REASON_UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
|
async def test_imported_flow_not_connected_error(hass):
|
||||||
|
"""Test imported flow with connection error abortion."""
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.panasonic_viera.config_flow.RemoteControl",
|
||||||
|
side_effect=TimeoutError,
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_IMPORT},
|
||||||
|
data={
|
||||||
|
CONF_HOST: "1.2.3.4",
|
||||||
|
CONF_NAME: DEFAULT_NAME,
|
||||||
|
CONF_PORT: DEFAULT_PORT,
|
||||||
|
CONF_ON_ACTION: "test-on-action",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "form"
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
assert result["errors"] == {"base": ERROR_NOT_CONNECTED}
|
||||||
|
|
||||||
|
|
||||||
|
async def test_imported_flow_unknown_abort(hass):
|
||||||
|
"""Test imported flow with unknown error abortion."""
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.panasonic_viera.config_flow.RemoteControl",
|
||||||
|
side_effect=Exception,
|
||||||
|
):
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_IMPORT},
|
||||||
|
data={
|
||||||
|
CONF_HOST: "1.2.3.4",
|
||||||
|
CONF_NAME: DEFAULT_NAME,
|
||||||
|
CONF_PORT: DEFAULT_PORT,
|
||||||
|
CONF_ON_ACTION: "test-on-action",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "abort"
|
||||||
|
assert result["reason"] == REASON_UNKNOWN
|
||||||
|
|
||||||
|
|
||||||
|
async def test_imported_flow_non_encrypted_already_configured_abort(hass):
|
||||||
|
"""Test imported flow without encryption and existing config entry abortion."""
|
||||||
|
|
||||||
|
MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
unique_id="1.2.3.4",
|
||||||
|
data={
|
||||||
|
CONF_HOST: "1.2.3.4",
|
||||||
|
CONF_NAME: DEFAULT_NAME,
|
||||||
|
CONF_PORT: DEFAULT_PORT,
|
||||||
|
CONF_ON_ACTION: "test-on-action",
|
||||||
|
},
|
||||||
|
).add_to_hass(hass)
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_IMPORT},
|
||||||
|
data={CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "abort"
|
||||||
|
assert result["reason"] == "already_configured"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_imported_flow_encrypted_already_configured_abort(hass):
|
||||||
|
"""Test imported flow with encryption and existing config entry abortion."""
|
||||||
|
|
||||||
|
MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
unique_id="1.2.3.4",
|
||||||
|
data={
|
||||||
|
CONF_HOST: "1.2.3.4",
|
||||||
|
CONF_NAME: DEFAULT_NAME,
|
||||||
|
CONF_PORT: DEFAULT_PORT,
|
||||||
|
CONF_ON_ACTION: "test-on-action",
|
||||||
|
CONF_APP_ID: "test-app-id",
|
||||||
|
CONF_ENCRYPTION_KEY: "test-encryption-key",
|
||||||
|
},
|
||||||
|
).add_to_hass(hass)
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": config_entries.SOURCE_IMPORT},
|
||||||
|
data={CONF_HOST: "1.2.3.4", CONF_NAME: DEFAULT_NAME},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == "abort"
|
||||||
|
assert result["reason"] == "already_configured"
|
Loading…
x
Reference in New Issue
Block a user