Samsung Smart TV - No control?

It is possible to set the HDMI input over UPnP. I don’t know if newer Samsung TVs has still the API, but on older TVs (2012, 2013) it was possible. The “MainTVAgent2” UPnP-service has the method “SetMainTVSource”. Also there are several other methods like “SetMainTVChannel”, “SetVolume”, “Play”, “Pause”, “Stop”, etc. I used gupnp-tools on Ubuntu to discover the services and play around with them.

I have Samsung 2011 ue40d6100sw is determined by HA but constantly shows that is turned off.

media_player:
  - platform: samsungtv
    host: 192.168.13.99
    mac: xx:xx:xx:xx:xx:xx
    port: 8001

Who can tell me what the problem is?

Please follow the thread here.

Thank you so much, my friend. Temporarily question decided to =)

How/where do I implement this code for KEY_“XX” sends?

I’m not sure where to add or find the custom_components/media_player/ folder.

Any insights would be appreciated!

EDIT

I think I’ve worked it out. The folders need to be created in /config.

I get “ms.channel.unauthorized” when connecting to Samsung TV UE55NU6035 and I haven’t been able to figure it out.

Here are the logs when pressing the on/off button in the UI:

2019-03-07 16:38:59 ERROR (MainThread) [homeassistant.components.websocket_api.http.connection.4552020656] {'event': 'ms.channel.unauthorized'}
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/homeassistant/components/websocket_api/commands.py", line 148, in handle_call_service
    connection.context(msg))
  File "/usr/local/lib/python3.7/site-packages/homeassistant/core.py", line 1133, in async_call
    self._execute_service(handler, service_call))
  File "/usr/local/lib/python3.7/site-packages/homeassistant/core.py", line 1155, in _execute_service
    await handler.func(service_call)
  File "/usr/local/lib/python3.7/site-packages/homeassistant/helpers/entity_component.py", line 188, in handle_service
    self._platforms.values(), func, call, service_name
  File "/usr/local/lib/python3.7/site-packages/homeassistant/helpers/service.py", line 278, in entity_service_call
    future.result()  # pop exception if have
  File "/usr/local/lib/python3.7/site-packages/homeassistant/helpers/service.py", line 292, in _handle_service_platform_call
    await getattr(entity, func)(**data)
  File "/usr/local/Cellar/python/3.7.2_1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/concurrent/futures/thread.py", line 57, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/local/lib/python3.7/site-packages/homeassistant/components/media_player/samsungtv.py", line 214, in turn_off
    self.get_remote().close()
  File "/usr/local/lib/python3.7/site-packages/homeassistant/components/media_player/samsungtv.py", line 138, in get_remote
    self._remote = self._remote_class(self._config)
  File "/Users/henrik/.homeassistant/deps/lib/python/site-packages/samsungctl/remote.py", line 11, in __init__
    self.remote = RemoteWebsocket(config)
  File "/Users/henrik/.homeassistant/deps/lib/python/site-packages/samsungctl/remote_websocket.py", line 30, in __init__
    self._read_response()
  File "/Users/he

Same problem on UE50MU6100. Running HA on HASSIO. He find the name of the device but can’t control the tv.

Failed to call service media_player/turn_off. {'event': 'ms.channel.unauthorized'}

I got this as well when pressing the on/off.

Samsung Tv Nu8000 (2018)

media_player:
  - platform: samsungtv
    host: xxx.xxx.xxx.xxx
    port: 8001
    name: Samsung TV
    timeout: 30
    mac: 64:1c:ae:11:14:d5
1 Like

After Update to 0.91.0 changing the Line 11 to

import homeassistant.components.samsungtv.media_player as stv

does the trick and the component is working again

greetings

where is the file where you changed?

In my setup this is the media_player.py file which is located in the custom_components/samsungtv_custom directory.

Hi,
I wonder what to do about the warnings I am getting that for all custom components all dependencies need to be included in the custom component. Which files should we move to the samsungtv_custom directory? How should the includes change in the media_player.py file which contains the samsungtv_custom code?

I tried this but not much luck. I do see the TV comes, but it takes like 3-5 minutes before that happens and when turning my tv off manually, HA doesn’t reflect that. I do also get errors when trying to set the Voume:

Traceback (most recent call last):
File “/usr/local/lib/python3.7/site-packages/homeassistant/components/websocket_api/commands.py”, line 122, in handle_call_service
connection.context(msg))
File “/usr/local/lib/python3.7/site-packages/homeassistant/core.py”, line 1138, in async_call
self._execute_service(handler, service_call))
File “/usr/local/lib/python3.7/site-packages/homeassistant/core.py”, line 1160, in _execute_service
await handler.func(service_call)
File “/usr/local/lib/python3.7/site-packages/homeassistant/helpers/entity_component.py”, line 188, in handle_service
self._platforms.values(), func, call, service_name
File “/usr/local/lib/python3.7/site-packages/homeassistant/helpers/service.py”, line 314, in entity_service_call
future.result() # pop exception if have
File “/usr/local/lib/python3.7/site-packages/homeassistant/helpers/service.py”, line 330, in _handle_service_platform_call
await func(entity, data)
File “/usr/local/lib/python3.7/concurrent/futures/thread.py”, line 57, in run
result = self.fn(*self.args, **self.kwargs)
File “/usr/local/lib/python3.7/site-packages/homeassistant/components/media_player/init.py”, line 504, in set_volume_level
raise NotImplementedError()
NotImplementedErrorTraceback (most recent call last):
File “/usr/local/lib/python3.7/site-packages/homeassistant/components/websocket_api/commands.py”, line 122, in handle_call_service
connection.context(msg))
File “/usr/local/lib/python3.7/site-packages/homeassistant/core.py”, line 1138, in async_call
self._execute_service(handler, service_call))
File “/usr/local/lib/python3.7/site-packages/homeassistant/core.py”, line 1160, in _execute_service
await handler.func(service_call)
File “/usr/local/lib/python3.7/site-packages/homeassistant/helpers/entity_component.py”, line 188, in handle_service
self._platforms.values(), func, call, service_name
File “/usr/local/lib/python3.7/site-packages/homeassistant/helpers/service.py”, line 314, in entity_service_call
future.result() # pop exception if have
File “/usr/local/lib/python3.7/site-packages/homeassistant/helpers/service.py”, line 330, in _handle_service_platform_call
await func(entity, data)
File “/usr/local/lib/python3.7/concurrent/futures/thread.py”, line 57, in run
result = self.fn(*self.args, **self.kwargs)
File “/usr/local/lib/python3.7/site-packages/homeassistant/components/media_player/init.py”, line 504, in set_volume_level
raise NotImplementedError()
NotImplementedError

How does your samsungtv.py fole look like?

I changed the directory according to the changes in the update description to:
/custom_component/samsungtv_custom/media_player.py. (simply renamed the old samsungtv_custom.py to media_player.py)

Changed is only Line 11 → here block 9 - 16

import voluptuous as vol

import homeassistant.components.samsungtv.media_player as stv
from homeassistant.components.media_player import (DOMAIN)
from homeassistant.const import (ATTR_ENTITY_ID)
import homeassistant.helpers.config_validation as cv

_LOGGER = logging.getLogger(name)

Did the folder change as well, by py file looks like this (line 7-20):

import logging
import socket
from bs4 import BeautifulSoup

import homeassistant.components.samsungtv.media_player as stv
#import voluptuous as vol

from homeassistant.components.media_player import (
    SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
    SUPPORT_VOLUME_STEP, MediaPlayerDevice, PLATFORM_SCHEMA, SUPPORT_TURN_ON)
from homeassistant.const import (
    CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, STATE_UNKNOWN, CONF_PORT,
    CONF_MAC)
import homeassistant.helpers.config_validation as cv

Can you link / post the entire file?

as i mentioned the folder structure changed, so you have to rename both the folder where the file is in to samsungtv_custom and the file itself to media_player.py.
my media_player.py in /custom_components/samsungtv_custom/ looks like the original only line changed is 11:

"""
Custom component to allow overriding or adding features to the samsungtv media_player component.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.samsungtv/
"""

import logging

import voluptuous as vol

import homeassistant.components.samsungtv.media_player as stv
from homeassistant.components.media_player import (DOMAIN)
from homeassistant.const import (ATTR_ENTITY_ID)
import homeassistant.helpers.config_validation as cv

_LOGGER = logging.getLogger(__name__)

SAMSUNG_TV_CUSTOM_DATA = 'samsungtv_custom'

SERVICE_KEY = 'send_key'

# Service call validation schemas
ATTR_KEY = 'key_code'

SAMSUNG_TV_CUSTOM_KEY_SCHEMA = vol.Schema({
    vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
    vol.Required(ATTR_KEY): cv.string,
})

def setup_platform(hass, config, add_devices, discovery_info=None):
    if SAMSUNG_TV_CUSTOM_DATA not in hass.data:
        hass.data[SAMSUNG_TV_CUSTOM_DATA] = []

    # Use this to get my hands on the SamsungTVDevices that get added
    def add_devices_custom(devices):
        add_devices(devices)
        for device in devices:
            hass.data[SAMSUNG_TV_CUSTOM_DATA].append(device)

    # pass in my add_devices_custom function
    stv.setup_platform(hass, config, add_devices_custom, discovery_info)
    _LOGGER.debug("hass.data[SAMSUNG_TV_CUSTOM_DATA] = %s.", hass.data[SAMSUNG_TV_CUSTOM_DATA])

    def service_handle(service):
        _LOGGER.debug("service_handle called for %s with %s", service.service, service.data)
        entity_ids = service.data.get('entity_id')
        devices = hass.data[SAMSUNG_TV_CUSTOM_DATA]

        for device in devices:
            if device.entity_id in entity_ids:
                if service.service == SERVICE_KEY:
                    device.send_key(service.data.get(ATTR_KEY))

                    device.schedule_update_ha_state(True)

    hass.services.register(
        DOMAIN, SERVICE_KEY, service_handle,
        schema=SAMSUNG_TV_CUSTOM_KEY_SCHEMA)

I already did that, so I hope I’m on the way.
Hmm your file doesn’t seem to be that long. I just checked the original file and that is also way longer:

Here is mine:

"""
Support for interface with an Samsung TV.

For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.samsungtv/
"""
import logging
import socket
from bs4 import BeautifulSoup

import homeassistant.components.samsungtv.media_player as stv
#import voluptuous as vol

from homeassistant.components.media_player import (
    SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
    SUPPORT_VOLUME_STEP, MediaPlayerDevice, PLATFORM_SCHEMA, SUPPORT_TURN_ON)
from homeassistant.const import (
    CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, STATE_UNKNOWN, CONF_PORT,
    CONF_MAC)
import homeassistant.helpers.config_validation as cv

REQUIREMENTS = ['wakeonlan==0.2.2', 'beautifulsoup4==4.6.0']

_LOGGER = logging.getLogger(__name__)

CONF_TIMEOUT = 'timeout'

DEFAULT_NAME = 'Samsung TV Remote'
DEFAULT_PORT = 55000
DEFAULT_TIMEOUT = 0

KNOWN_DEVICES_KEY = 'samsungtv_known_devices'

SUPPORT_SAMSUNGTV = SUPPORT_SELECT_SOURCE | SUPPORT_VOLUME_SET | \
    SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_MUTE | SUPPORT_TURN_OFF

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
    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_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
})


# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
    """Set up the Samsung TV platform."""
    known_devices = hass.data.get(KNOWN_DEVICES_KEY)
    if known_devices is None:
        known_devices = set()
        hass.data[KNOWN_DEVICES_KEY] = known_devices

    # Is this a manual configuration?
    if config.get(CONF_HOST) is not None:
        host = config.get(CONF_HOST)
        port = config.get(CONF_PORT)
        name = config.get(CONF_NAME)
        mac = config.get(CONF_MAC)
        timeout = config.get(CONF_TIMEOUT)
    elif discovery_info is not None:
        tv_name = discovery_info.get('name')
        model = discovery_info.get('model_name')
        host = discovery_info.get('host')
        name = "{} ({})".format(tv_name, model)
        port = DEFAULT_PORT
        timeout = DEFAULT_TIMEOUT
        mac = None
    else:
        _LOGGER.warning("Cannot determine device")
        return

    # Only add a device once, so discovered devices do not override manual
    # config.
    ip_addr = socket.gethostbyname(host)
    if ip_addr not in known_devices:
        known_devices.add(ip_addr)
        add_devices([SamsungTVDevice(host, port, name, timeout, mac)])
        _LOGGER.info("Samsung TV %s:%d added as '%s'", host, port, name)
    else:
        _LOGGER.info("Ignoring duplicate Samsung TV %s:%d", host, port)


class SamsungTVDevice(MediaPlayerDevice):
    """Representation of a Samsung TV."""

    def __init__(self, host, port, name, timeout, mac):
        """Initialize the Samsung device."""
        from wakeonlan import wol
        # Save a reference to the imported classes
        self._name = name
        self._mac = mac
        self._wol = wol
        # Assume that the TV is not muted
        self._muted = False
        self._volume = 0
        self._state = STATE_OFF
        # Generate a configuration for the Samsung library
        self._config = {
            'name': 'HomeAssistant',
            'description': name,
            'id': 'ha.component.samsung',
            'port': 7676,
            'host': host,
            'timeout': timeout,
        }
        self._selected_source = ''
        self._source_names = self.SendSOAP('smp_4_', 'urn:samsung.com:service:MainTVAgent2:1', 'GetSourceList', '', 'sourcetype')
        if self._source_names:
            del self._source_names[0]
            self._source_ids = self.SendSOAP('smp_4_', 'urn:samsung.com:service:MainTVAgent2:1', 'GetSourceList', '', 'id')
            self._sources = dict(zip(self._source_names, self._source_ids))
        else:
            self._source_names = {}
            self._source_ids = {}
            self._sources = {}        

    def update(self):
        """Retrieve the latest data."""
        currentvolume = self.SendSOAP('smp_17_', 'urn:schemas-upnp-org:service:RenderingControl:1', 'GetVolume', '<InstanceID>0</InstanceID><Channel>Master</Channel>','currentvolume')
        if currentvolume:
            self._volume = int(currentvolume) / 100
            currentmute = self.SendSOAP('smp_17_', 'urn:schemas-upnp-org:service:RenderingControl:1', 'GetMute', '<InstanceID>0</InstanceID><Channel>Master</Channel>','currentmute')
            if currentmute == '1':
                self._muted = True
            else:
                self._muted = False
            source = self.SendSOAP('smp_4_', 'urn:samsung.com:service:MainTVAgent2:1', 'GetCurrentExternalSource', '','currentexternalsource')
            self._selected_source = source
            self._state = STATE_ON
            return True
        else:
            self._state = STATE_OFF
            return False

    def SendSOAP(self,path,urn,service,body,XMLTag):
        CRLF = "\r\n"
        xmlBody = "";
        xmlBody += '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">'
        xmlBody += '<s:Body>'
        xmlBody += '<u:{service} xmlns:u="{urn}">{body}</u:{service}>'
        xmlBody += '</s:Body>'
        xmlBody += '</s:Envelope>'
        xmlBody = xmlBody.format(urn = urn, service = service, body = body)
    
        soapRequest  = "POST /{path} HTTP/1.0%s" % (CRLF)
        soapRequest += "HOST: {host}:{port}%s" % (CRLF)
        soapRequest += "CONTENT-TYPE: text/xml;charset=\"utf-8\"%s" % (CRLF)
        soapRequest += "SOAPACTION: \"{urn}#{service}\"%s" % (CRLF)
        soapRequest += "%s" % (CRLF)
        soapRequest += "{xml}%s" % (CRLF)
        soapRequest = soapRequest.format(host = self._config['host'], port = self._config['port'], xml = xmlBody, path = path, urn = urn, service = service)
    
        
        client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        client.settimeout(0.5)
        client.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        dataBuffer = ''
        response_xml = ''
        _LOGGER.info("Samsung TV sending: %s", soapRequest)
    
        try:
            client.connect( (self._config['host'], self._config['port']) )
            client.send(bytes(soapRequest, 'utf-8'))
            while True:
                dataBuffer = client.recv(4096)
                if not dataBuffer: break
                response_xml += str(dataBuffer)
        except socket.error as e:
            return
        
        response_xml = bytes(response_xml, 'utf-8')
        response_xml = response_xml.decode(encoding="utf-8")
        response_xml = response_xml.replace("&lt;","<")
        response_xml = response_xml.replace("&gt;",">")
        response_xml = response_xml.replace("&quot;","\"")
        _LOGGER.info("Samsung TV received: %s", response_xml)
        if XMLTag:
            soup = BeautifulSoup(str(response_xml), 'html.parser')
            xmlValues = soup.find_all(XMLTag)
            xmlValues_names = [xmlValue.string for xmlValue in xmlValues]
            if len(xmlValues_names)== 1: 
                return xmlValues_names[0]
            else:
                return xmlValues_names
        else:
            return response_xml[response_xml.find('<s:Envelope'):]

    @property
    def name(self):
        """Return the name of the device."""
        return self._name

    @property
    def state(self):
        """Return the state of the device."""
        return self._state

    @property
    def volume_level(self):
        """Volume level of the media player (0..1)."""
        return self._volume

    @property
    def is_volume_muted(self):
        """Boolean if volume is currently muted."""
        return self._muted
    @property

    def source(self):
        """Return the current input source."""
        return self._selected_source

    @property
    def source_list(self):
        """List of available input sources."""
        return self._source_names

    @property
    def media_title(self):
        """Title of current playing media."""
        return self._selected_source

    @property
    def supported_features(self):
        """Flag media player features that are supported."""
        if self._mac:
            return SUPPORT_SAMSUNGTV | SUPPORT_TURN_ON
        return SUPPORT_SAMSUNGTV

    def select_source(self, source):
        """Select input source."""
        self.SendSOAP('smp_4_', 'urn:samsung.com:service:MainTVAgent2:1', 'SetMainTVSource', '<Source>'+source+'</Source><ID>' + self._sources[source] + '</ID><UiID>0</UiID>','')

    def turn_off(self):
        """Turn off media player."""

    def set_volume_level(self, volume):
        """Volume up the media player."""
        volset = str(round(volume * 100))
        self.SendSOAP('smp_17_', 'urn:schemas-upnp-org:service:RenderingControl:1', 'SetVolume', '<InstanceID>0</InstanceID><DesiredVolume>' + volset + '</DesiredVolume><Channel>Master</Channel>','')

    def volume_up(self):
        """Volume up the media player."""
        volume = self._volume + 0.01
        self.set_volume_level(volume)

    def volume_down(self):
        """Volume down media player."""
        volume = self._volume - 0.01
        self.set_volume_level(volume)

    def mute_volume(self, mute):
        """Send mute command."""
        if self._muted == True:
            doMute = '0'
        else:
            doMute = '1'
        self.SendSOAP('smp_17_', 'urn:schemas-upnp-org:service:RenderingControl:1', 'SetMute', '<InstanceID>0</InstanceID><DesiredMute>' + doMute + '</DesiredMute><Channel>Master</Channel>','')

    def turn_on(self):
        """Turn the media player on."""
        if self._mac:
            self._wol.send_magic_packet(self._mac)

Sorry I just can’t see it.

dunni, can post it again, but its a 58 Rows long py file. perhaps im not using the newest version, got mine out of this forum

You can try pm it to me. Yours is working, so that’s all I need as well :slight_smile:

EDIT: I tried yours with thr 58 lines of code. Kinda works - I can see when my Samsung is On and Off, but I can’t control anything at all :frowning: