Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
111 commits
Select commit Hold shift + click to select a range
0e8df84
@escoand work + small cleanup + filter Soundbars
chemelli74 Mar 16, 2021
ab1884d
More fixes for ignored devices
chemelli74 Mar 16, 2021
e3a1d97
Partial fix for tests
chemelli74 Mar 17, 2021
700b216
Fix tests
chemelli74 Mar 17, 2021
bf7039c
Added test: model not supported
chemelli74 Mar 30, 2021
0112a68
Rebase
chemelli74 Mar 30, 2021
080e530
Fix tests after rebase
chemelli74 Mar 30, 2021
837c373
Update coverage + small cleanup
chemelli74 Mar 31, 2021
0e1c102
Applied review comments
chemelli74 Apr 4, 2021
77934a8
Typo
chemelli74 Apr 4, 2021
a77d4b6
Applied review comments (part II)
chemelli74 Apr 4, 2021
100a934
Update homeassistant/components/samsungtv/config_flow.py
chemelli74 Apr 4, 2021
762ab34
Update homeassistant/components/samsungtv/config_flow.py
chemelli74 Apr 4, 2021
7c9b959
Update homeassistant/components/samsungtv/config_flow.py
chemelli74 Apr 4, 2021
4395b2b
Update homeassistant/components/samsungtv/config_flow.py
chemelli74 Apr 4, 2021
97a8c40
Applied review comments (part III)
chemelli74 Apr 4, 2021
94f4bc7
Unknown host
chemelli74 Apr 5, 2021
e3a8a50
Handle missing ID
chemelli74 Apr 6, 2021
3359f2f
Pylint fix
chemelli74 Apr 7, 2021
8e27642
Update homeassistant/components/samsungtv/config_flow.py
chemelli74 Apr 8, 2021
2a22a2b
Update homeassistant/components/samsungtv/config_flow.py
chemelli74 Apr 8, 2021
e0dd2ff
Update homeassistant/components/samsungtv/manifest.json
chemelli74 Apr 10, 2021
9cf952f
Add myself as codeowner
chemelli74 Apr 10, 2021
81ada7e
Migrate old ids
chemelli74 Apr 10, 2021
843442d
Fixed unique id migration
chemelli74 Apr 11, 2021
6df85f9
Handle socket closure
chemelli74 Apr 16, 2021
2a0d2c0
Rebase
chemelli74 Apr 17, 2021
06a44b5
Avoid useless connect
chemelli74 Apr 18, 2021
7970ebb
Fixed multiple TV authorization requests
chemelli74 Apr 19, 2021
7e4a90c
Update homeassistant/components/samsungtv/media_player.py
chemelli74 Apr 23, 2021
f5eb6f5
Better const name
chemelli74 Apr 23, 2021
9810afc
Black
chemelli74 Apr 23, 2021
a4643cb
Apply codereview comments
chemelli74 Apr 24, 2021
a7c5554
Fixed unique_id management
chemelli74 Apr 24, 2021
bd30a23
Fixed old entries migration
chemelli74 Apr 25, 2021
9570112
Pylint
chemelli74 Apr 25, 2021
884174f
Removed unused const
chemelli74 Apr 25, 2021
469fd9c
Update homeassistant/components/samsungtv/config_flow.py
chemelli74 Apr 25, 2021
7e8af06
Update homeassistant/components/samsungtv/config_flow.py
chemelli74 Apr 25, 2021
91289fd
Update homeassistant/components/samsungtv/config_flow.py
chemelli74 Apr 25, 2021
ff76b04
Update homeassistant/components/samsungtv/config_flow.py
chemelli74 Apr 25, 2021
b8e78b4
Address review comments
chemelli74 Apr 25, 2021
351b9d0
Add missing properties + some cleanup
chemelli74 Apr 26, 2021
d3ed2f2
Update homeassistant/components/samsungtv/config_flow.py
chemelli74 May 3, 2021
a7de5fb
Rever ip_address commit + limit device_info calls
chemelli74 May 5, 2021
c5dcead
Find tv by dhcp
bdraco May 6, 2021
b065677
Apply review comments
chemelli74 May 10, 2021
54a5a55
Handle exception during device discovery, Generate one executor job f…
bdraco May 11, 2021
3815406
Strip prefix from device name
chemelli74 May 11, 2021
5e3d65b
@escoand work + small cleanup + filter Soundbars
chemelli74 Mar 16, 2021
302b0d1
Rebase
chemelli74 Mar 30, 2021
f59a44d
Applied review comments
chemelli74 Apr 4, 2021
b68158e
Applied review comments (part II)
chemelli74 Apr 4, 2021
9fc4e12
Applied review comments (part III)
chemelli74 Apr 4, 2021
6f25ef5
Handle missing ID
chemelli74 Apr 6, 2021
05e8079
Migrate old ids
chemelli74 Apr 10, 2021
0b7ab91
Handle socket closure
chemelli74 Apr 16, 2021
7b49415
Avoid useless connect
chemelli74 Apr 18, 2021
34469f6
Better const name
chemelli74 Apr 23, 2021
5782c8b
Apply codereview comments
chemelli74 Apr 24, 2021
c9c26eb
Fixed unique_id management
chemelli74 Apr 24, 2021
c35abab
Fixed old entries migration
chemelli74 Apr 25, 2021
81712f8
Removed unused const
chemelli74 Apr 25, 2021
30b2bac
Find tv by dhcp
bdraco May 6, 2021
433f02b
Apply review comments
chemelli74 May 10, 2021
b4aa969
Update homeassistant/components/samsungtv/config_flow.py
chemelli74 May 12, 2021
a37e8b4
Cleanup after rebase
chemelli74 May 12, 2021
00bc1ef
test cleanup pass 1
bdraco May 11, 2021
60007c6
Small test cleanup
chemelli74 May 13, 2021
391b819
Cleanup timeout values
chemelli74 May 13, 2021
f0db08b
More on tests
chemelli74 May 13, 2021
8d89bfa
2 more tests
chemelli74 May 13, 2021
854eeca
1 more test
chemelli74 May 13, 2021
e8f2e10
Apply latest suggestion
chemelli74 May 13, 2021
843747a
more tweaks on tests
chemelli74 May 14, 2021
ae4710e
Test fixes
bdraco May 20, 2021
c7df0d4
more test cleanups
bdraco May 20, 2021
601dc61
more fixes
bdraco May 20, 2021
0686289
fix tests for turn on
bdraco May 21, 2021
6a0a335
fix unload
bdraco May 21, 2021
ce0f3ec
Test fixes
bdraco May 21, 2021
f3c16c9
Test fixes
bdraco May 21, 2021
e037400
fix more tests
bdraco May 21, 2021
da0d2aa
naming
bdraco May 21, 2021
c3b49a3
legacy import fix
bdraco May 21, 2021
116bfce
legacy import fix
bdraco May 21, 2021
a3e65fe
fix test
bdraco May 21, 2021
11ef372
logging
bdraco May 21, 2021
d74f005
fix unload
bdraco May 21, 2021
42d7887
missing await
bdraco May 21, 2021
7565291
more test fixes
bdraco May 21, 2021
f240bb0
more test fixes
bdraco May 21, 2021
9acbdff
tweaks
bdraco May 21, 2021
86c7a24
restore resolver
bdraco May 21, 2021
7231b86
add missing coverage
bdraco May 21, 2021
15e78d8
fix reauth
bdraco May 21, 2021
310c0e7
Get a new token on reauth and update
bdraco May 21, 2021
cda2c37
ensure they can try reauth multiple times
bdraco May 21, 2021
2111d3a
translations
bdraco May 21, 2021
bc6640b
fix reauth
bdraco May 21, 2021
ad28e95
fix reauth
bdraco May 21, 2021
68bbcad
fix multiple reauth
bdraco May 21, 2021
9a72f0a
tweak result
bdraco May 21, 2021
4e6d693
tweak
bdraco May 21, 2021
99de4d5
add missing coverage
bdraco May 21, 2021
5b9d4fe
Update homeassistant/components/samsungtv/manifest.json
bdraco May 21, 2021
3b4460d
regen
bdraco May 21, 2021
70e9e8c
fail fast
bdraco May 21, 2021
4931281
make pylint happy
bdraco May 21, 2021
67d2649
Minor cleanup
chemelli74 May 22, 2021
e10d1ea
Ensure missing mac address and missing unique id get populated when s…
bdraco May 22, 2021
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
1 change: 1 addition & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -866,6 +866,7 @@ omit =
homeassistant/components/russound_rnet/media_player.py
homeassistant/components/sabnzbd/*
homeassistant/components/saj/sensor.py
homeassistant/components/samsungtv/bridge.py
homeassistant/components/satel_integra/*
homeassistant/components/schluter/*
homeassistant/components/scrape/sensor.py
Expand Down
2 changes: 1 addition & 1 deletion CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,7 @@ homeassistant/components/rpi_power/* @shenxn @swetoast
homeassistant/components/ruckus_unleashed/* @gabe565
homeassistant/components/safe_mode/* @home-assistant/core
homeassistant/components/saj/* @fredericvl
homeassistant/components/samsungtv/* @escoand
homeassistant/components/samsungtv/* @escoand @chemelli74
homeassistant/components/scene/* @home-assistant/core
homeassistant/components/schluter/* @prairieapps
homeassistant/components/scrape/* @fabaff
Expand Down
110 changes: 89 additions & 21 deletions homeassistant/components/samsungtv/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,31 @@

from homeassistant import config_entries
from homeassistant.components.media_player.const import DOMAIN as MP_DOMAIN
from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PORT
from homeassistant.const import (
CONF_HOST,
CONF_METHOD,
CONF_NAME,
CONF_PORT,
CONF_TOKEN,
EVENT_HOMEASSISTANT_STOP,
)
from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv

from .const import CONF_ON_ACTION, DEFAULT_NAME, DOMAIN
from .bridge import SamsungTVBridge
from .const import CONF_ON_ACTION, DEFAULT_NAME, DOMAIN, LOGGER


def ensure_unique_hosts(value):
"""Validate that all configs have a unique host."""
vol.Schema(vol.Unique("duplicate host entries found"))(
[socket.gethostbyname(entry[CONF_HOST]) for entry in value]
[entry[CONF_HOST] for entry in value]
)
return value


PLATFORMS = [MP_DOMAIN]

CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.All(
Expand All @@ -43,30 +54,87 @@ def ensure_unique_hosts(value):

async def async_setup(hass, config):
"""Set up the Samsung TV integration."""
if DOMAIN in config:
hass.data[DOMAIN] = {}
for entry_config in config[DOMAIN]:
ip_address = await hass.async_add_executor_job(
socket.gethostbyname, entry_config[CONF_HOST]
)
hass.data[DOMAIN][ip_address] = {
CONF_ON_ACTION: entry_config.get(CONF_ON_ACTION)
}
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data=entry_config,
)
)
hass.data[DOMAIN] = {}
if DOMAIN not in config:
return True

for entry_config in config[DOMAIN]:
ip_address = await hass.async_add_executor_job(
socket.gethostbyname, entry_config[CONF_HOST]
)
hass.data[DOMAIN][ip_address] = {
CONF_ON_ACTION: entry_config.get(CONF_ON_ACTION)
}
hass.async_create_task(
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data=entry_config,
)
)
return True


@callback
def _async_get_device_bridge(data):
"""Get device bridge."""
return SamsungTVBridge.get_bridge(
data[CONF_METHOD],
data[CONF_HOST],
data[CONF_PORT],
data.get(CONF_TOKEN),
)


async def async_setup_entry(hass, entry):
"""Set up the Samsung TV platform."""
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, MP_DOMAIN)

# Initialize bridge
data = entry.data.copy()
bridge = _async_get_device_bridge(data)
if bridge.port is None and bridge.default_port is not None:
# For backward compat, set default port for websocket tv
data[CONF_PORT] = bridge.default_port
hass.config_entries.async_update_entry(entry, data=data)
bridge = _async_get_device_bridge(data)

def stop_bridge(event):
"""Stop SamsungTV bridge connection."""
bridge.stop()

entry.async_on_unload(
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, stop_bridge)
)

hass.data[DOMAIN][entry.entry_id] = bridge
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
return True

Comment thread
bdraco marked this conversation as resolved.

async def async_unload_entry(hass, entry):
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN][entry.entry_id].stop()
return unload_ok


async def async_migrate_entry(hass, config_entry):
"""Migrate old entry."""
version = config_entry.version

LOGGER.debug("Migrating from version %s", version)

# 1 -> 2: Unique ID format changed, so delete and re-import:
if version == 1:
dev_reg = await hass.helpers.device_registry.async_get_registry()
dev_reg.async_clear_config_entry(config_entry)

en_reg = await hass.helpers.entity_registry.async_get_registry()
en_reg.async_clear_config_entry(config_entry)

version = config_entry.version = 2
hass.config_entries.async_update_entry(config_entry)
LOGGER.debug("Migration to version %s successful", version)

return True
61 changes: 47 additions & 14 deletions homeassistant/components/samsungtv/bridge.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"""samsungctl and samsungtvws bridge classes."""
from abc import ABC, abstractmethod
import contextlib

from samsungctl import Remote
from samsungctl.exceptions import AccessDenied, ConnectionClosed, UnhandledResponse
from samsungtvws import SamsungTVWS
from samsungtvws.exceptions import ConnectionFailure
from samsungtvws.exceptions import ConnectionFailure, HttpApiError
from websocket import WebSocketException

from homeassistant.const import (
Expand All @@ -25,8 +26,11 @@
RESULT_CANNOT_CONNECT,
RESULT_NOT_SUPPORTED,
RESULT_SUCCESS,
TIMEOUT_REQUEST,
TIMEOUT_WEBSOCKET,
VALUE_CONF_ID,
VALUE_CONF_NAME,
WEBSOCKET_PORTS,
)


Expand Down Expand Up @@ -58,9 +62,14 @@ def register_reauth_callback(self, func):
def try_connect(self):
"""Try to connect to the TV."""

@abstractmethod
def device_info(self):
"""Try to gather infos of this TV."""

def is_on(self):
"""Tells if the TV is on."""
self.close_remote()
if self._remote:
self.close_remote()

try:
return self._get_remote() is not None
Expand Down Expand Up @@ -104,7 +113,7 @@ def _send_key(self, key):
"""Send the key."""

@abstractmethod
def _get_remote(self):
def _get_remote(self, avoid_open: bool = False):
"""Get Remote object."""

def close_remote(self):
Expand Down Expand Up @@ -149,7 +158,7 @@ def try_connect(self):
CONF_METHOD: self.method,
CONF_PORT: None,
# We need this high timeout because waiting for auth popup is just an open socket
CONF_TIMEOUT: 31,
CONF_TIMEOUT: TIMEOUT_REQUEST,
}
try:
LOGGER.debug("Try config: %s", config)
Expand All @@ -162,11 +171,15 @@ def try_connect(self):
except UnhandledResponse:
LOGGER.debug("Working but unsupported config: %s", config)
return RESULT_NOT_SUPPORTED
except OSError as err:
except (ConnectionClosed, OSError) as err:
LOGGER.debug("Failing config: %s, error: %s", config, err)
return RESULT_CANNOT_CONNECT

def _get_remote(self):
def device_info(self):
"""Try to gather infos of this device."""
return None

def _get_remote(self, avoid_open: bool = False):
"""Create or return a remote control instance."""
if self._remote is None:
# We need to create a new instance to reconnect.
Expand All @@ -184,6 +197,11 @@ def _send_key(self, key):
"""Send the key using legacy protocol."""
self._get_remote().control(key)

def stop(self):
"""Stop Bridge."""
LOGGER.debug("Stopping SamsungRemote")
self.close_remote()


class SamsungTVWSBridge(SamsungTVBridge):
"""The Bridge for WebSocket TVs."""
Expand All @@ -196,14 +214,14 @@ def __init__(self, method, host, port, token=None):

def try_connect(self):
"""Try to connect to the Websocket TV."""
for self.port in (8001, 8002):
for self.port in WEBSOCKET_PORTS:
config = {
CONF_NAME: VALUE_CONF_NAME,
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: 31,
CONF_TIMEOUT: TIMEOUT_REQUEST,
}

result = None
Expand Down Expand Up @@ -234,31 +252,46 @@ def try_connect(self):

return RESULT_CANNOT_CONNECT

def device_info(self):
"""Try to gather infos of this TV."""
remote = self._get_remote(avoid_open=True)
if not remote:
return None
with contextlib.suppress(HttpApiError):
return remote.rest_device_info()

def _send_key(self, key):
"""Send the key using websocket protocol."""
if key == "KEY_POWEROFF":
key = "KEY_POWER"
self._get_remote().send_key(key)

def _get_remote(self):
def _get_remote(self, avoid_open: bool = False):
"""Create or return a remote control instance."""
if self._remote is None:
# We need to create a new instance to reconnect.
try:
LOGGER.debug("Create SamsungTVWS")
LOGGER.debug(
"Create SamsungTVWS for %s (%s)", VALUE_CONF_NAME, self.host
)
self._remote = SamsungTVWS(
host=self.host,
port=self.port,
token=self.token,
timeout=8,
timeout=TIMEOUT_WEBSOCKET,
name=VALUE_CONF_NAME,
)
self._remote.open()
if not avoid_open:
self._remote.open()
# This is only happening when the auth was switched to DENY
# A removed auth will lead to socket timeout because waiting for auth popup is just an open socket
except ConnectionFailure:
self._notify_callback()
raise
except WebSocketException:
except (WebSocketException, OSError):
self._remote = None
return self._remote

def stop(self):
"""Stop Bridge."""
LOGGER.debug("Stopping SamsungTVWS")
self.close_remote()
Loading