Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for setting up encrypted samsung tvs from config flow #68717

Merged
merged 6 commits into from Mar 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion homeassistant/components/samsungtv/__init__.py
Expand Up @@ -173,7 +173,7 @@ async def _async_create_bridge_with_updated_data(
else:
# When we imported from yaml we didn't setup the method
# because we didn't know it
port, method, info = await async_get_device_info(hass, None, host)
_result, port, method, info = await async_get_device_info(hass, host)
load_info_attempted = True
if not port or not method:
raise ConfigEntryNotReady(
Expand Down
70 changes: 42 additions & 28 deletions homeassistant/components/samsungtv/bridge.py
Expand Up @@ -23,7 +23,12 @@
MS_ERROR_EVENT,
parse_installed_app,
)
from samsungtvws.exceptions import ConnectionFailure, HttpApiError
from samsungtvws.exceptions import (
ConnectionFailure,
HttpApiError,
ResponseError,
UnauthorizedError,
)
from samsungtvws.remote import ChannelEmitCommand, SendRemoteKey
from websockets.exceptions import ConnectionClosedError, WebSocketException

Expand Down Expand Up @@ -54,6 +59,7 @@
RESULT_CANNOT_CONNECT,
RESULT_NOT_SUPPORTED,
RESULT_SUCCESS,
SUCCESSFUL_RESULTS,
TIMEOUT_REQUEST,
TIMEOUT_WEBSOCKET,
VALUE_CONF_ID,
Expand All @@ -66,6 +72,8 @@
ENCRYPTED_MODEL_USES_POWER_OFF = {"H6400"}
ENCRYPTED_MODEL_USES_POWER = {"JU6400", "JU641D"}

REST_EXCEPTIONS = (HttpApiError, AsyncioTimeoutError, ResponseError)


def mac_from_device_info(info: dict[str, Any]) -> str | None:
"""Extract the mac address from the device info."""
Expand All @@ -76,36 +84,39 @@ def mac_from_device_info(info: dict[str, Any]) -> str | None:

async def async_get_device_info(
hass: HomeAssistant,
bridge: SamsungTVBridge | None,
host: str,
) -> tuple[int | None, str | None, dict[str, Any] | None]:
) -> tuple[str, int | None, str | None, dict[str, Any] | None]:
"""Fetch the port, method, and device info."""
# Bridge is defined
if bridge and bridge.port:
return bridge.port, bridge.method, await bridge.async_device_info()

# Try websocket ports
# Try the websocket ssl and non-ssl ports
for port in WEBSOCKET_PORTS:
bridge = SamsungTVBridge.get_bridge(hass, METHOD_WEBSOCKET, host, port)
if info := await bridge.async_device_info():
return port, METHOD_WEBSOCKET, info

# Try encrypted websocket port
bridge = SamsungTVBridge.get_bridge(
hass, METHOD_ENCRYPTED_WEBSOCKET, host, ENCRYPTED_WEBSOCKET_PORT
)
result = await bridge.async_try_connect()
if result == RESULT_SUCCESS:
return port, METHOD_ENCRYPTED_WEBSOCKET, await bridge.async_device_info()
LOGGER.debug(
"Fetching rest info via %s was successful: %s, checking for encrypted",
port,
info,
)
encrypted_bridge = SamsungTVEncryptedBridge(
hass, METHOD_ENCRYPTED_WEBSOCKET, host, ENCRYPTED_WEBSOCKET_PORT
)
result = await encrypted_bridge.async_try_connect()
if result != RESULT_CANNOT_CONNECT:
return (
result,
ENCRYPTED_WEBSOCKET_PORT,
METHOD_ENCRYPTED_WEBSOCKET,
info,
)
return RESULT_SUCCESS, port, METHOD_WEBSOCKET, info

# Try legacy port
bridge = SamsungTVBridge.get_bridge(hass, METHOD_LEGACY, host, LEGACY_PORT)
result = await bridge.async_try_connect()
if result in (RESULT_SUCCESS, RESULT_AUTH_MISSING):
return LEGACY_PORT, METHOD_LEGACY, await bridge.async_device_info()
if result in SUCCESSFUL_RESULTS:
return result, LEGACY_PORT, METHOD_LEGACY, await bridge.async_device_info()

# Failed to get info
return None, None, None
return result, None, None, None


class SamsungTVBridge(ABC):
Expand Down Expand Up @@ -433,8 +444,11 @@ async def async_try_connect(self) -> str:
"Working but unsupported config: %s, error: %s", config, err
)
result = RESULT_NOT_SUPPORTED
except (OSError, AsyncioTimeoutError, ConnectionFailure) as err:
LOGGER.debug("Failing config: %s, error: %s", config, err)
except UnauthorizedError as err:
LOGGER.debug("Failing config: %s, %s error: %s", config, type(err), err)
return RESULT_AUTH_MISSING
except (ConnectionFailure, OSError, AsyncioTimeoutError) as err:
LOGGER.debug("Failing config: %s, %s error: %s", config, type(err), err)
# pylint: disable=useless-else-on-loop
else:
if result:
Expand All @@ -453,7 +467,7 @@ async def async_device_info(self) -> dict[str, Any] | None:
timeout=TIMEOUT_WEBSOCKET,
)

with contextlib.suppress(HttpApiError, AsyncioTimeoutError):
with contextlib.suppress(*REST_EXCEPTIONS):
device_info: dict[str, Any] = await rest_api.rest_device_info()
LOGGER.debug("Device info on %s is: %s", self.host, device_info)
self._device_info = device_info
Expand Down Expand Up @@ -654,8 +668,7 @@ async def async_try_connect(self) -> str:
CONF_HOST: self.host,
CONF_METHOD: self.method,
CONF_PORT: self.port,
# We need this high timeout because waiting for auth popup is just an open socket
CONF_TIMEOUT: TIMEOUT_REQUEST,
CONF_TIMEOUT: TIMEOUT_WEBSOCKET,
}

try:
Expand All @@ -669,13 +682,14 @@ async def async_try_connect(self) -> str:
timeout=TIMEOUT_REQUEST,
) as remote:
await remote.start_listening()
LOGGER.debug("Working config: %s", config)
return RESULT_SUCCESS
except WebSocketException as err:
LOGGER.debug("Working but unsupported config: %s, error: %s", config, err)
return RESULT_NOT_SUPPORTED
except (OSError, AsyncioTimeoutError, ConnectionFailure) as err:
LOGGER.debug("Failing config: %s, error: %s", config, err)
else:
LOGGER.debug("Working config: %s", config)
return RESULT_SUCCESS

return RESULT_CANNOT_CONNECT

Expand All @@ -696,7 +710,7 @@ async def async_device_info(self) -> dict[str, Any] | None:
timeout=TIMEOUT_WEBSOCKET,
)

with contextlib.suppress(HttpApiError, AsyncioTimeoutError):
with contextlib.suppress(*REST_EXCEPTIONS):
device_info: dict[str, Any] = await rest_api.rest_device_info()
LOGGER.debug("Device info on %s is: %s", self.host, device_info)
self._device_info = device_info
Expand Down