Skip to content

Commit

Permalink
Add PSK auth and SSDP discovery to Bravia TV (#77772)
Browse files Browse the repository at this point in the history
  • Loading branch information
Drafteed committed Sep 23, 2022
1 parent 83b426d commit 7c460cc
Show file tree
Hide file tree
Showing 9 changed files with 301 additions and 35 deletions.
11 changes: 9 additions & 2 deletions homeassistant/components/braviatv/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_create_clientsession

from .const import CONF_IGNORED_SOURCES, DOMAIN
from .const import CONF_IGNORED_SOURCES, CONF_USE_PSK, DOMAIN
from .coordinator import BraviaTVCoordinator

PLATFORMS: Final[list[Platform]] = [Platform.MEDIA_PLAYER, Platform.REMOTE]
Expand All @@ -22,13 +22,20 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
host = config_entry.data[CONF_HOST]
mac = config_entry.data[CONF_MAC]
pin = config_entry.data[CONF_PIN]
use_psk = config_entry.data.get(CONF_USE_PSK, False)
ignored_sources = config_entry.options.get(CONF_IGNORED_SOURCES, [])

session = async_create_clientsession(
hass, cookie_jar=CookieJar(unsafe=True, quote_cookie=False)
)
client = BraviaTV(host, mac, session=session)
coordinator = BraviaTVCoordinator(hass, client, pin, ignored_sources)
coordinator = BraviaTVCoordinator(
hass=hass,
client=client,
pin=pin,
use_psk=use_psk,
ignored_sources=ignored_sources,
)
config_entry.async_on_unload(config_entry.add_update_listener(update_listener))

await coordinator.async_config_entry_first_refresh()
Expand Down
94 changes: 78 additions & 16 deletions homeassistant/components/braviatv/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
from __future__ import annotations

from typing import Any
from urllib.parse import urlparse

from aiohttp import CookieJar
from pybravia import BraviaTV, BraviaTVError, BraviaTVNotSupported
from pybravia import BraviaTV, BraviaTVAuthError, BraviaTVError, BraviaTVNotSupported
import voluptuous as vol

from homeassistant import config_entries
from homeassistant.components import ssdp
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_PIN
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME, CONF_PIN
from homeassistant.core import callback
from homeassistant.data_entry_flow import FlowResult
from homeassistant.helpers.aiohttp_client import async_create_clientsession
Expand All @@ -23,6 +25,7 @@
ATTR_MODEL,
CLIENTID_PREFIX,
CONF_IGNORED_SOURCES,
CONF_USE_PSK,
DOMAIN,
NICKNAME,
)
Expand All @@ -33,10 +36,9 @@ class BraviaTVConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):

VERSION = 1

client: BraviaTV

def __init__(self) -> None:
"""Initialize config flow."""
self.client: BraviaTV | None = None
self.device_config: dict[str, Any] = {}

@staticmethod
Expand All @@ -45,11 +47,28 @@ def async_get_options_flow(config_entry: ConfigEntry) -> BraviaTVOptionsFlowHand
"""Bravia TV options callback."""
return BraviaTVOptionsFlowHandler(config_entry)

async def async_init_device(self) -> FlowResult:
def create_client(self) -> None:
"""Create Bravia TV client from config."""
host = self.device_config[CONF_HOST]
session = async_create_clientsession(
self.hass,
cookie_jar=CookieJar(unsafe=True, quote_cookie=False),
)
self.client = BraviaTV(host=host, session=session)

async def async_create_device(self) -> FlowResult:
"""Initialize and create Bravia TV device from config."""
pin = self.device_config[CONF_PIN]
assert self.client

await self.client.connect(pin=pin, clientid=CLIENTID_PREFIX, nickname=NICKNAME)
pin = self.device_config[CONF_PIN]
use_psk = self.device_config[CONF_USE_PSK]

if use_psk:
await self.client.connect(psk=pin)
else:
await self.client.connect(
pin=pin, clientid=CLIENTID_PREFIX, nickname=NICKNAME
)
await self.client.set_wol_mode(True)

system_info = await self.client.get_system_info()
Expand All @@ -72,13 +91,8 @@ async def async_step_user(
if user_input is not None:
host = user_input[CONF_HOST]
if is_host_valid(host):
session = async_create_clientsession(
self.hass,
cookie_jar=CookieJar(unsafe=True, quote_cookie=False),
)
self.client = BraviaTV(host=host, session=session)
self.device_config[CONF_HOST] = host

self.create_client()
return await self.async_step_authorize()

errors[CONF_HOST] = "invalid_host"
Expand All @@ -92,29 +106,77 @@ async def async_step_user(
async def async_step_authorize(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Get PIN from the Bravia TV device."""
"""Authorize Bravia TV device."""
errors: dict[str, str] = {}

if user_input is not None:
self.device_config[CONF_PIN] = user_input[CONF_PIN]
self.device_config[CONF_USE_PSK] = user_input[CONF_USE_PSK]
try:
return await self.async_init_device()
return await self.async_create_device()
except BraviaTVAuthError:
errors["base"] = "invalid_auth"
except BraviaTVNotSupported:
errors["base"] = "unsupported_model"
except BraviaTVError:
errors["base"] = "cannot_connect"

assert self.client

try:
await self.client.pair(CLIENTID_PREFIX, NICKNAME)
except BraviaTVError:
return self.async_abort(reason="no_ip_control")

return self.async_show_form(
step_id="authorize",
data_schema=vol.Schema({vol.Required(CONF_PIN, default=""): str}),
data_schema=vol.Schema(
{
vol.Required(CONF_PIN, default=""): str,
vol.Required(CONF_USE_PSK, default=False): bool,
}
),
errors=errors,
)

async def async_step_ssdp(self, discovery_info: ssdp.SsdpServiceInfo) -> FlowResult:
"""Handle a discovered device."""
parsed_url = urlparse(discovery_info.ssdp_location)
host = parsed_url.hostname

await self.async_set_unique_id(discovery_info.upnp[ssdp.ATTR_UPNP_UDN])
self._abort_if_unique_id_configured(updates={CONF_HOST: host})
self._async_abort_entries_match({CONF_HOST: host})

scalarweb_info = discovery_info.upnp["X_ScalarWebAPI_DeviceInfo"]
service_types = scalarweb_info["X_ScalarWebAPI_ServiceList"][
"X_ScalarWebAPI_ServiceType"
]

if "videoScreen" not in service_types:
return self.async_abort(reason="not_bravia_device")

model_name = discovery_info.upnp[ssdp.ATTR_UPNP_MODEL_NAME]
friendly_name = discovery_info.upnp[ssdp.ATTR_UPNP_FRIENDLY_NAME]

self.context["title_placeholders"] = {
CONF_NAME: f"{model_name} ({friendly_name})",
CONF_HOST: host,
}

self.device_config[CONF_HOST] = host
return await self.async_step_confirm()

async def async_step_confirm(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Allow the user to confirm adding the device."""
if user_input is not None:
self.create_client()
return await self.async_step_authorize()

return self.async_show_form(step_id="confirm")


class BraviaTVOptionsFlowHandler(config_entries.OptionsFlow):
"""Config flow options for Bravia TV."""
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/braviatv/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
ATTR_MODEL: Final = "model"

CONF_IGNORED_SOURCES: Final = "ignored_sources"
CONF_USE_PSK: Final = "use_psk"

CLIENTID_PREFIX: Final = "HomeAssistant"
DOMAIN: Final = "braviatv"
Expand Down
11 changes: 8 additions & 3 deletions homeassistant/components/braviatv/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,14 @@ def __init__(
hass: HomeAssistant,
client: BraviaTV,
pin: str,
use_psk: bool,
ignored_sources: list[str],
) -> None:
"""Initialize Bravia TV Client."""

self.client = client
self.pin = pin
self.use_psk = use_psk
self.ignored_sources = ignored_sources
self.source: str | None = None
self.source_list: list[str] = []
Expand Down Expand Up @@ -110,9 +112,12 @@ async def _async_update_data(self) -> None:
"""Connect and fetch data."""
try:
if not self.connected:
await self.client.connect(
pin=self.pin, clientid=CLIENTID_PREFIX, nickname=NICKNAME
)
if self.use_psk:
await self.client.connect(psk=self.pin)
else:
await self.client.connect(
pin=self.pin, clientid=CLIENTID_PREFIX, nickname=NICKNAME
)
self.connected = True

power_status = await self.client.get_power_status()
Expand Down
6 changes: 6 additions & 0 deletions homeassistant/components/braviatv/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
"documentation": "https://www.home-assistant.io/integrations/braviatv",
"requirements": ["pybravia==0.2.2"],
"codeowners": ["@bieniu", "@Drafteed"],
"ssdp": [
{
"st": "urn:schemas-sony-com:service:ScalarWebAPI:1",
"manufacturer": "Sony Corporation"
}
],
"config_flow": true,
"iot_class": "local_polling",
"loggers": ["pybravia"]
Expand Down
12 changes: 9 additions & 3 deletions homeassistant/components/braviatv/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,26 @@
},
"authorize": {
"title": "Authorize Sony Bravia TV",
"description": "Enter the PIN code shown on the Sony Bravia TV. \n\nIf the PIN code is not shown, you have to unregister Home Assistant on your TV, go to: Settings -> Network -> Remote device settings -> Unregister remote device.",
"description": "Enter the PIN code shown on the Sony Bravia TV. \n\nIf the PIN code is not shown, you have to unregister Home Assistant on your TV, go to: Settings -> Network -> Remote device settings -> Deregister remote device. \n\nYou can use PSK (Pre-Shared-Key) instead of PIN. PSK is a user-defined secret key used for access control. This authentication method is recommended as more stable. To enable PSK on your TV, go to: Settings -> Network -> Home Network Setup -> IP Control. Then check «Use PSK authentication» box and enter your PSK instead of PIN.",
"data": {
"pin": "[%key:common::config_flow::data::pin%]"
"pin": "[%key:common::config_flow::data::pin%]",
"use_psk": "Use PSK authentication"
}
},
"confirm": {
"description": "[%key:common::config_flow::description::confirm_setup%]"
}
},
"error": {
"invalid_host": "[%key:common::config_flow::error::invalid_host%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"unsupported_model": "Your TV model is not supported."
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"no_ip_control": "IP Control is disabled on your TV or the TV is not supported."
"no_ip_control": "IP Control is disabled on your TV or the TV is not supported.",
"not_bravia_device": "The device is not a Bravia TV."
}
},
"options": {
Expand Down
12 changes: 9 additions & 3 deletions homeassistant/components/braviatv/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,27 @@
"config": {
"abort": {
"already_configured": "Device is already configured",
"no_ip_control": "IP Control is disabled on your TV or the TV is not supported."
"no_ip_control": "IP Control is disabled on your TV or the TV is not supported.",
"not_bravia_device": "The device is not a Bravia TV."
},
"error": {
"cannot_connect": "Failed to connect",
"invalid_auth": "Invalid authentication",
"invalid_host": "Invalid hostname or IP address",
"unsupported_model": "Your TV model is not supported."
},
"step": {
"authorize": {
"data": {
"pin": "PIN Code"
"pin": "PIN Code",
"use_psk": "Use PSK authentication"
},
"description": "Enter the PIN code shown on the Sony Bravia TV. \n\nIf the PIN code is not shown, you have to unregister Home Assistant on your TV, go to: Settings -> Network -> Remote device settings -> Unregister remote device.",
"description": "Enter the PIN code shown on the Sony Bravia TV. \n\nIf the PIN code is not shown, you have to unregister Home Assistant on your TV, go to: Settings -> Network -> Remote device settings -> Deregister remote device. \n\nYou can use PSK (Pre-Shared-Key) instead of PIN. PSK is a user-defined secret key used for access control. This authentication method is recommended as more stable. To enable PSK on your TV, go to: Settings -> Network -> Home Network Setup -> IP Control. Then check \u00abUse PSK authentication\u00bb box and enter your PSK instead of PIN.",
"title": "Authorize Sony Bravia TV"
},
"confirm": {
"description": "Do you want to start set up?"
},
"user": {
"data": {
"host": "Host"
Expand Down
6 changes: 6 additions & 0 deletions homeassistant/generated/ssdp.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
"manufacturer": "AXIS",
},
],
"braviatv": [
{
"manufacturer": "Sony Corporation",
"st": "urn:schemas-sony-com:service:ScalarWebAPI:1"
}
],
"control4": [
{
"st": "c4:director",
Expand Down

0 comments on commit 7c460cc

Please sign in to comment.