Skip to content

Commit

Permalink
Implement remote support (FR #275) (#277)
Browse files Browse the repository at this point in the history
  • Loading branch information
ollo69 committed Nov 3, 2023
1 parent cc7856a commit fb4d228
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 49 deletions.
66 changes: 63 additions & 3 deletions custom_components/samsungtv_smart/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@
)
from homeassistant.core import HomeAssistant, callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.storage import STORAGE_DIR
from homeassistant.helpers.typing import ConfigType

Expand All @@ -46,7 +48,9 @@
ATTR_DEVICE_OS,
CONF_APP_LIST,
CONF_CHANNEL_LIST,
CONF_DEVICE_MODEL,
CONF_DEVICE_NAME,
CONF_DEVICE_OS,
CONF_LOAD_ALL_APPS,
CONF_SCAN_APP_HTTP,
CONF_SHOW_CHANNEL_NR,
Expand All @@ -57,6 +61,7 @@
CONF_UPDATE_METHOD,
CONF_WS_NAME,
DATA_CFG_YAML,
DATA_DEV_INFO,
DATA_OPTIONS,
DEFAULT_PORT,
DEFAULT_SOURCE_LIST,
Expand All @@ -83,6 +88,8 @@
ATTR_DEVICE_OS: "OS",
}

SAMSMART_PLATFORM = [Platform.MEDIA_PLAYER, Platform.REMOTE]

SAMSMART_SCHEMA = {
vol.Optional(CONF_SOURCE_LIST, default=DEFAULT_SOURCE_LIST): cv.string,
vol.Optional(CONF_APP_LIST): cv.string,
Expand Down Expand Up @@ -533,19 +540,27 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
_migrate_options_format(hass, entry)

# setup entry
entry.async_on_unload(entry.add_update_listener(_update_listener))
hass.data.setdefault(DOMAIN, {}).setdefault(entry.entry_id, {})
if DATA_CFG_YAML in hass.data[DOMAIN][entry.entry_id]:
mac_addr = hass.data[DOMAIN][entry.entry_id][DATA_CFG_YAML].get(CONF_MAC)
else:
mac_addr = None

hass.data[DOMAIN][entry.entry_id][DATA_DEV_INFO] = SamsungTVDeviceInfo(
entry.data, entry.entry_id, mac_addr
)
hass.data[DOMAIN][entry.entry_id][DATA_OPTIONS] = entry.options.copy()
entry.async_on_unload(entry.add_update_listener(_update_listener))

await hass.config_entries.async_forward_entry_setups(entry, [Platform.MEDIA_PLAYER])
await hass.config_entries.async_forward_entry_setups(entry, SAMSMART_PLATFORM)

return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(
entry, [Platform.MEDIA_PLAYER]
entry, SAMSMART_PLATFORM
):
hass.data[DOMAIN][entry.entry_id].pop(DATA_OPTIONS)
if not hass.data[DOMAIN][entry.entry_id]:
Expand All @@ -567,3 +582,48 @@ async def _update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Update when config_entry options update."""
hass.data[DOMAIN][entry.entry_id][DATA_OPTIONS] = entry.options.copy()
async_dispatcher_send(hass, SIGNAL_CONFIG_ENTITY)


class SamsungTVDeviceInfo:
"""Define generic samsung device info."""

def __init__(
self, config: dict[str, str], entry_id: str, forced_mac: str | None = None
) -> None:
"""Initialize the class."""
self._config = config
self._unique_id = config.get(CONF_ID, entry_id)
self._name = config.get(CONF_NAME, config[CONF_HOST])
self._mac = forced_mac or config.get(CONF_MAC)

@property
def unique_id(self) -> str:
"""Return device unique id."""
return self._unique_id

@property
def name(self) -> str:
"""Return device name."""
return self._name

@property
def device_info(self) -> DeviceInfo:
"""Return device information."""
config = self._config

model = config.get(CONF_DEVICE_MODEL, "Samsung TV")
if dev_name := config.get(CONF_DEVICE_NAME):
model = f"{model} ({dev_name})"

device_info = DeviceInfo(
identifiers={(DOMAIN, self.unique_id)},
name=self.name,
manufacturer="Samsung Electronics",
model=model,
)
if dev_os := config.get(CONF_DEVICE_OS):
device_info["sw_version"] = dev_os
if self._mac:
device_info["connections"] = {(CONNECTION_NETWORK_MAC, self._mac)}

return device_info
2 changes: 1 addition & 1 deletion custom_components/samsungtv_smart/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
CONF_NAME,
CONF_PORT,
CONF_TOKEN,
SERVICE_TURN_ON,
__version__,
)
from homeassistant.core import HomeAssistant, callback
Expand Down Expand Up @@ -70,7 +71,6 @@
RESULT_ST_DEVICE_USED,
RESULT_SUCCESS,
RESULT_WRONG_APIKEY,
SERVICE_TURN_ON,
AppLaunchMethod,
AppLoadMethod,
PowerOnMethod,
Expand Down
4 changes: 1 addition & 3 deletions custom_components/samsungtv_smart/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class PowerOnMethod(Enum):
__min_ha_version__ = f"{MIN_HA_MAJ_VER}.{MIN_HA_MIN_VER}.0"

DATA_CFG_YAML = "cfg_yaml"
DATA_DEV_INFO = "dev_info"
DATA_OPTIONS = "options"
LOCAL_LOGO_PATH = "local_logo_path"
WS_PREFIX = "[Home Assistant]"
Expand Down Expand Up @@ -89,9 +90,6 @@ class PowerOnMethod(Enum):
SERVICE_SELECT_PICTURE_MODE = "select_picture_mode"
SERVICE_SET_ART_MODE = "set_art_mode"

SERVICE_TURN_OFF = "turn_off"
SERVICE_TURN_ON = "turn_on"

SIGNAL_CONFIG_ENTITY = f"{DOMAIN}_config"

STD_APP_LIST = {
Expand Down
52 changes: 10 additions & 42 deletions custom_components/samsungtv_smart/media_player.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,29 +36,29 @@
CONF_BROADCAST_ADDRESS,
CONF_DEVICE_ID,
CONF_HOST,
CONF_ID,
CONF_MAC,
CONF_NAME,
CONF_PORT,
CONF_SERVICE,
CONF_SERVICE_DATA,
CONF_TIMEOUT,
CONF_TOKEN,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
STATE_OFF,
STATE_ON,
)
from homeassistant.core import DOMAIN as HA_DOMAIN, HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.service import CONF_SERVICE_ENTITY_ID, async_call_from_config
from homeassistant.helpers.storage import STORAGE_DIR
from homeassistant.util import Throttle, dt as dt_util
from homeassistant.util.async_ import run_callback_threadsafe

from . import SamsungTVDeviceInfo
from .api.samsungcast import SamsungCastTube
from .api.samsungws import ArtModeStatus, SamsungTVAsyncRest, SamsungTVWS
from .api.smartthings import SmartThingsTV, STStatus
Expand All @@ -68,9 +68,6 @@
CONF_APP_LIST,
CONF_APP_LOAD_METHOD,
CONF_CHANNEL_LIST,
CONF_DEVICE_MODEL,
CONF_DEVICE_NAME,
CONF_DEVICE_OS,
CONF_DUMP_APPS,
CONF_EXT_POWER_ENTITY,
CONF_LOGO_OPTION,
Expand All @@ -88,6 +85,7 @@
CONF_WOL_REPEAT,
CONF_WS_NAME,
DATA_CFG_YAML,
DATA_DEV_INFO,
DATA_OPTIONS,
DEFAULT_APP,
DEFAULT_PORT,
Expand All @@ -98,8 +96,6 @@
MAX_WOL_REPEAT,
SERVICE_SELECT_PICTURE_MODE,
SERVICE_SET_ART_MODE,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
SIGNAL_CONFIG_ENTITY,
STD_APP_LIST,
WS_PREFIX,
Expand Down Expand Up @@ -169,6 +165,7 @@ async def async_setup_entry(
# session used by aiohttp
session = async_get_clientsession(hass)
local_logo_path = hass.data[DOMAIN].get(LOCAL_LOGO_PATH)
dev_info: SamsungTVDeviceInfo = hass.data[DOMAIN][entry.entry_id][DATA_DEV_INFO]

config = entry.data.copy()
add_conf = hass.data[DOMAIN][entry.entry_id].get(DATA_CFG_YAML, {})
Expand All @@ -190,7 +187,7 @@ def update_token_func(token: str) -> None:
[
SamsungTVDevice(
config,
config.get(CONF_ID, entry.entry_id),
dev_info,
hass.data[DOMAIN][entry.entry_id],
session,
update_token_func,
Expand Down Expand Up @@ -256,7 +253,7 @@ class SamsungTVDevice(MediaPlayerEntity):
def __init__(
self,
config: dict[str, Any],
unique_id: str,
dev_info: SamsungTVDeviceInfo,
entry_data: dict[str, Any] | None,
session: ClientSession,
update_token_func: Callable[[str], None],
Expand All @@ -270,7 +267,8 @@ def __init__(
self._mac = config.get(CONF_MAC)

# Set entity attributes
self._attr_unique_id = unique_id
self._attr_unique_id = dev_info.unique_id
self._attr_device_info = dev_info.device_info
self._attr_media_title = None
self._attr_media_image_url = None
self._attr_media_image_remotely_accessible = False
Expand All @@ -279,21 +277,6 @@ def __init__(
self._attr_is_volume_muted = False
self._attr_volume_level = 0.0

dev_name = config.get(CONF_NAME, self._host)
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self._attr_unique_id)},
name=dev_name,
manufacturer="Samsung Electronics",
)
self._attr_device_info.update(
self._get_add_dev_info(
config.get(CONF_DEVICE_MODEL),
config.get(CONF_DEVICE_NAME),
config.get(CONF_DEVICE_OS),
self._mac,
)
)

# Device information from TV
self._device_info: dict[str, Any] | None = None

Expand Down Expand Up @@ -332,7 +315,7 @@ def __init__(
self._show_channel_number: bool = False

# ws initialization
ws_name = config.get(CONF_WS_NAME, dev_name)
ws_name = config.get(CONF_WS_NAME, dev_info.name)
self._ws = SamsungTVWS(
host=self._host,
token=config.get(CONF_TOKEN),
Expand Down Expand Up @@ -405,21 +388,6 @@ async def async_will_remove_from_hass(self):
self._ws.unregister_status_callback()
await self.hass.async_add_executor_job(self._ws.stop_poll)

@staticmethod
def _get_add_dev_info(dev_model, dev_name, dev_os, dev_mac):
"""Get additional device information."""
model = dev_model or "Samsung TV"
if dev_name:
model = f"{model} ({dev_name})"

dev_info = DeviceInfo(model=model)
if dev_os:
dev_info["sw_version"] = dev_os
if dev_mac:
dev_info["connections"] = {(CONNECTION_NETWORK_MAC, dev_mac)}

return dict(dev_info)

@staticmethod
def _split_app_list(app_list: dict[str, str]) -> list[dict[str, str]]:
"""Split the application list for standard and SmartThings."""
Expand Down
Loading

0 comments on commit fb4d228

Please sign in to comment.