Control newer Samsung TVs

Thanks to MrUart232 custom samsungtv.py I can control my Samsung H6400 TV.
Now I can: mute / unmute, set volume, increase / decrease volume and change the source.
It would have been great if I could turn it on or off but at least i have some control of the tv.

I have a 2018 Q7F. @Kent may have an earlier model. I can turn on the TV if the TV has been off for a short amount of time (like 10s, you can hear a click in the control box). I can’t get it to do anything else.

I tried samsungctl and it can only control the TV if I manually confirm a popup “grant permission?” screen, so it kinda nulls out any automation value…

I bought the TV 2018 and I doubt that Samsung has changed the design for Q7F, I also have this delay when I turn of the TV about 5-10 sec. until I can turn it on again via Hassio, I have set the grant permission for external devices so it only prompts the first time Hassio connects to the TV, it’s under General settings, external devices.

1 Like

I’m trying with my UN55LS003 (Frame) TV. Discovers it, but I’m unable to do much. It does not ask for permission.

How does one install BeautifulSoap and wakeonlan if running Hassio OS? I’m interested in trying this as I have an H series Samsung TV, but I am extremely new to Home Assistant.

Hi,
try this version out, it puts the requirements on the top of the code. Then it should work automatically.

"""
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

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

from bs4 import BeautifulSoup

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

_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)
1 Like

Tried this in hassio and get…

Error while setting up platform samsungtv
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/homeassistant/helpers/entity_platform.py", line 129, in _async_setup_platform
    SLOW_SETUP_MAX_WAIT, loop=hass.loop)
  File "/usr/local/lib/python3.6/asyncio/tasks.py", line 358, in wait_for
    return fut.result()
  File "/usr/local/lib/python3.6/concurrent/futures/thread.py", line 56, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/config/custom_components/media_player/samsungtv.py", line 74, in setup_platform
    ip_addr = socket.gethostbyname(host)
socket.gaierror: [Errno -2] Name does not resolve

@MrUart232 it has a slider or volume up/down button?

I get the same error on Hass OS.

I get the error…

Traceback (most recent call last):
  File "samsungtv.py", line 13, in <module>
    from homeassistant.components.media_player import (
ImportError: No module named homeassistant.components.media_player

Had some progress, deleted everything in the media_player folder and restarted so it picked up the built in component instead. No errors on restart and it can now sense when the TV is on and off, which is an improvement on when I tried it a year or so ago and gave up.

Next put the custom component back in and restarted, now it doesn’t error anymore and I have control of the volume. This implies there’s something missing from the set up of this component that the built in one installs correctly.

Obviously not much you can do with this as is, really needs play and pause to be useful, but appreciate the effort.

I noticed that there has been some work on the Samsung TVs in the Node-Red library https://flows.nodered.org/node/node-red-contrib-newsamsungtv would the associated file help make the needed components?

They seem to preform most of the needed functions. I am not that good with the programming, but hoping to maybe come up with an IR transmitter.

1 Like

So I finally got it working… @Kent, is your TV connected to the internet via WiFi or ethernet?

Can you share how

I have a 2018 Q7F. I found that I could not control it when the TV was connected to my network via WiFi but it works like a charm when connected via Ethernet. Like @Kent, I did not use the custom script… Here is my config:

media_player:
  - platform: samsungtv
    host: 192.168.1.117
    mac: !secret samsung_mac
    port: 8001
    name: Samsung TV
1 Like

Nice, that was too easy. thanks

1 Like

I’m using WiFi for my setup and it works fine, pls. make sure that you configured the WiFi mac adress right. @dapowers87

1 Like

I had updated the MAC to the WiFi MAC, so I just don’t know why it isn’t working for me. I’m sure it is some small thing I am overlooking. Thanks for continually checking back in, Kent.

Have you tried this using Node-RED from within HA? This seems very promising!

Hi everyone!
I own a Samsung Frame 2018 (LS03). It kinda works well with the samsungtv platform, except for the turn off command. The main issue I have is with the let’s call it frame mode (static image with a picture).

  • When I send the turn on command, it goes from complete shutdown (black screen) into whichever mode it was the last time It shut down (frame or tv mode). The turn on command does not change from frame mode to TV mode.
  • When I send the turn off command it switches between frame mode and tv mode (in both directions)

Thus, if I turn it on it may go into picture mode and then I have to send the turn off command for it to go into tv mode. So there’s no way I can make a switch that ensures it will go into tv mode from any previous state.
Any advices / workarounds?
Thanks!