core: Samsung TV's not working [Testing fix]

*****UPDATED***** PLEASE READ ALL THE INFORMATION BELOW THERE ARE PARTS THAT HAVE CHANGED

I am moving the conversation that is taking place in #17802 as the current conversation really has nothing to do with the issue that was reported.

If this is not OK then delete this issue. I am the person that is currently updating the code to samsungctl. It has undergone a lot of additions which I do believe the users of Home Assistant will enjoy being able to use.

thee library also now supports ALL Samsung TV’s that have the capability of being controlled over a network or WiFi Connection from 2008 to present. This includes the encrypted websocket connections (H (2014) and J (2015) TV’s) as well as the SSL websocket connection (latest firmware release).

I have simplified the connection process so that the only things that are needed in thee Home Assistant config file is the host (ip) of the TV. nothing else is needed. the samsungctl library now handles hammering all of the connection specific details. I also added to it a a class that handles loading and saving of config data. I did this to handle the dynamically changing tokens that are used with the encrypted websockets and SSL websockets. the library now also does not stop running if the connection get severed… (TV gets powered off). I also changed the handling of the power keys to make them function like they should. KEY_POWER = power toggle, KEY_POWERON = power on and KEY_POWEROFF = power off.

I am currently testing the replacement for /homeassistant/components/media_player/samsungtv.py, because I am not familiar with Home Assistant it is better to hammer these problems out in an issue where they can be tested then to open a PR for non functioning code and end up with a very messy commit history.

PLEASE READ

This is a replacement for the /homeassistant/components/media_player/samsungtv.py file please do not edit the code and then report an issue. only report problems relating to this code. also do not provide vague errors. like it says config error… I am willing to bet the error stats more then “config error”. I need a copy of the error message in it’s entirety. not little bits and pieces. because of the nature of Home Assistant you may need to scroll back in the logs and read for a bit to make sure that you have grabbed all of the relevant log messages.

If you state anything about “custom_components” or if i see that in any of the errors I am not going to answer you. this is because you have modified the code. and it is a pretty good chance that your modification is what is causing the problem.

If you do not do the above then I have no way of helping to solve any issues.

config files for the TV are going to be saved in a directory called samsung_tv that is located in the Home Assistant config folder. the config files for the TVs are going to be named using the IP address you supply in the Home Assistant config yaml file.

HOME ASSISTANT CONFIG FILE This is the only thing that needs to be added to your hass configuration.yaml file. you will not need to specify any kind of a host. or mac or name. nothing. The whole system will dynamically add TV’s as it finds them (of coarse with your permission). if a TV is found you will get a notification in the hass UI. when you open this notification you will be prompted to change the display name, the mac address and the description. if you do not wish to change them simply click on the accept button. You may need to refresh the page once the device is added for it to show up. If you have more then a single TV you DO NOT need to add multiple entries into the hass config file. You only need to add the below code a single time.

If you do not want to add a TV to hass (not sure why you wouldn’t) simply ignore the notification. it will disappear after 60 seconds. if you change your mind afterwards or you simply miss the timeout you will need to go into thee samsung_tv config directory and delete the file that has an extension of .noinclude.

media_player:
  - platform: samsungtv

the config file will only get saved once there is a successful connection to the TV made. I have changed about the detection mechanism for the 4 different types of connections. I am hoping this is going to be more of a dock solid mechanism of detecting the connection type. IF you have an issue detecting your TV let me know. There are some tasks I will have you do in order to get it to detect the TV properly. I do not own every Samsung TV made so there is no way to test it on my end. so it is up to you guys to follow the bug reporting directions as outline above.

Here is the updated code.

I WILL NOT MAKE CHANGES AND MAKE ANOTHER POST FOR IT. I WILL UPDATE THE CODE BELOW WITH ANY NEW CHANGES AND INFORM YOU THAT THE CODE HAS CHANGED

click to expand

"""
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 asyncio
from datetime import timedelta
import logging
import threading
import os
import uuid

import voluptuous as vol

from homeassistant.components.media_player import (
    MEDIA_TYPE_CHANNEL, PLATFORM_SCHEMA, SUPPORT_NEXT_TRACK, SUPPORT_PAUSE,
    SUPPORT_PLAY, SUPPORT_PLAY_MEDIA, SUPPORT_PREVIOUS_TRACK, SUPPORT_TURN_OFF,
    SUPPORT_TURN_ON, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_STEP,
    SUPPORT_VOLUME_SET, SUPPORT_SELECT_SOURCE, MediaPlayerDevice)
from homeassistant.const import (
    CONF_NAME,
    STATE_OFF,
    STATE_ON
)
import homeassistant.helpers.config_validation as cv
from homeassistant.util import dt as dt_util


REQUIREMENTS = [
     'https://github.com/kdschlosser/'
     'samsungctl/archive/develop.zip#samsungctl==0.8.64b'
]

SAMSUNG_CONFIG_PATH = 'samsung_tv'

ICON_TV = 'mdi:television'
ICON_TV_OFF = 'mdi:television-off'
ICON_COMPONENT = 'mdi:video-input-component'
ICON_HDMI = 'mdi:video-input-hdmi'
ICON_SVIDEO = 'mdi:video-input-svideo'
ICON_USB = 'mdi:usb'
ICON_PC = 'mdi:console'
ICON_DLNA = 'mdi:dlna'
ICON_AV = 'mdi:audio-video'
ICON_YOUTUBE = 'mdi:youtube'
ICON_HULU = 'mdi:hulu'
ICON_NETFLIX = 'mdi:netflix'
ICON_PLEX = 'mdi:plex'
ICON_SPOTIFY = 'mdi:spotify'
ICON_AMAZON = 'mdi:amazon'
ICON_PLAYSTATION = 'mdi:playstation'
ICON_UNKNOWN = 'mdi:help'

_LOGGER = logging.getLogger(__name__)

CONF_DESCRIPTION = 'description'
CONF_ADD = 'add_tv'

KEY_PRESS_TIMEOUT = 1.2
KNOWN_DEVICES_KEY = 'samsungtv_known_devices'

SUPPORT_SAMSUNGTV = (
    SUPPORT_PAUSE |
    SUPPORT_VOLUME_STEP |
    SUPPORT_VOLUME_MUTE |
    SUPPORT_PREVIOUS_TRACK |
    SUPPORT_NEXT_TRACK |
    SUPPORT_TURN_OFF |
    SUPPORT_PLAY |
    SUPPORT_PLAY_MEDIA |
    SUPPORT_VOLUME_SET |
    SUPPORT_SELECT_SOURCE
)


SAMSUNG_TV_SCHEMA = vol.Schema({
    vol.Optional(CONF_NAME): cv.string,
    vol.Optional(CONF_DESCRIPTION): cv.string,
    vol.Optional(CONF_ADD): cv.boolean,
})

PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({})

_CONFIGURING = {}


def setup_platform(hass, config, add_entities, _=None):
    """Set up the Samsung TV platform."""

    config_path = hass.config.path(SAMSUNG_CONFIG_PATH)

    if not os.path.exists(config_path):
        os.mkdir(config_path)

    known_devices = hass.data.get(KNOWN_DEVICES_KEY, set())
    hass.data[KNOWN_DEVICES_KEY] = known_devices

    import samsungctl
    from samsungctl.upnp.discover import auto_discover

    config_files = list(
        os.path.join(config_path, file) for file in os.listdir(config_path)
            if file.endswith('config')
    )

    def callback(found_config):
        if found_config.uuid in known_devices:
            return

        _LOGGER.debug(str(found_config))
        known_devices.add(found_config.uuid)

        no_include = os.path.join(
            config_path,
            found_config.uuid + '.noinclude'
        )

        if os.path.exists(no_include):
            return

        if found_config.uuid not in _CONFIGURING:
            add_device(found_config, hass, config_path, add_entities)

    auto_discover.register_callback(callback)
    entities = []
    configs = []

    for config_file in config_files:
        _LOGGER.debug(config_file)
        samsung_config = samsungctl.Config.load(config_file)
        known_devices.add(samsung_config.uuid)
        configs += [samsung_config]

    auto_discover.start()

    for samsung_config in configs:
        entities += [SamsungTVDevice(samsung_config)]
    add_entities(entities)


def add_device(samsung_config, hass, config_path, add_entities):
    model = samsung_config.model
    uuid = samsung_config.uuid

    event = threading.Event()

    def samsung_configuration_callback(data):
        """Handle the entry of user PIN."""
        display_name = data.get('display_name')
        description = data.get('description')
        mac = data.get('mac')

        if display_name is None:
            display_name = samsung_config.display_name
        if description is None:
            description = samsung_config.description
        if mac is None:
            mac = samsung_config.mac

        samsung_config.display_name = display_name
        samsung_config.description = description
        samsung_config.mac = mac

        samsung_config.path = os.path.join(
            config_path,
            samsung_config.uuid + '.config'
        )

        hass.components.configurator.request_done(_CONFIGURING.pop(uuid))

        if samsung_config.method == 'encrypted':
            request_configuration(samsung_config, hass, add_entities)
        else:
            add_entities([SamsungTVDevice(samsung_config)])

        event.set()

    def do():
        event.wait(600.0)
        if not event.isSet():
            path = os.path.join(
                config_path,
                samsung_config.uuid + '.noinclude'
            )

            with open(path, 'w') as f:
                f.write('')

            hass.components.configurator.request_done(_CONFIGURING.pop(uuid))

    t = threading.Thread(target=do)
    t.daemon = True
    t.start()

    _CONFIGURING[uuid] = hass.components.configurator.request_config(
        model,
        samsung_configuration_callback,
        description='New TV discovered, would you like to add the TV?',
        description_image="/static/images/smart-tv.png",
        submit_caption="Accept",
        fields=[
            dict(
                id='display_name',
                name='Name: ' + samsung_config.display_name,
                type=''
            ),
            dict(
                id='description',
                name='Description: ' + samsung_config.description,
                type=''
            ),
            dict(
                id='mac',
                name='MAC Address : ' + str(samsung_config.mac),
                type=''
            )
        ]
    )


def request_configuration(samsung_config, hass, add_entities):
    """Request configuration steps from the user."""

    configurator = hass.components.configurator

    import samsungctl

    pin = []
    count = 0
    event = threading.Event()

    def samsung_configuration_callback(data):
        """Handle the entry of user PIN."""
        pin.append(data.get('pin'))
        event.set()

    def get_pin():
        global count

        if samsung_config.uuid in _CONFIGURING:
            count += 1
            event.clear()
            del pin[:]

            configurator.notify_errors(
                _CONFIGURING[samsung_config.uuid],
                "Failed to register, please try again."
            )
        else:
            _CONFIGURING[samsung_config.uuid] = configurator.request_config(
                samsung_config.display_name,
                samsung_configuration_callback,
                description='Enter the Pin shown on your Samsung TV.',
                description_image="/static/images/smart-tv.png",
                submit_caption="Confirm",
                fields=[{'id': 'pin', 'name': 'Enter the pin', 'type': ''}]
            )

        event.wait(60.0)

        if count == 3:
            _LOGGER.error(
                samsung_config.display_name + " TV: Pin entry failed"
            )
            return False
        elif not event.isSet():
            return None

        return pin[0]

    samsung_config.get_pin = get_pin

    def do():
        global count

        try:
            _ = samsungctl.Remote(samsung_config)
            add_entities([SamsungTVDevice(samsung_config)])
        except:
            pass

        hass.components.configurator.request_done(_CONFIGURING.pop(uuid))

    t = threading.Thread(target=do)
    t.daemon = True
    t.start()


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

    def __init__(self, config):
        """Initialize the Samsung device."""
        from samsungctl import exceptions
        from samsungctl import Remote

        # Save a reference to the imported classes
        self._exceptions_class = exceptions
        self._remote_class = Remote
        self._config = config
        self._mac = self._config.mac
        self._uuid = self._config.uuid
        self._playing = True
        self._state = None
        self._remote = None
        self._key_source = False
        self._mute = False
        self._sources = []
        self._source = ''
        self._volume = 0.0
        self._entity_image = None
        self._tv_image = None

        if self._config.method == 'websocket':
            self._has_apps = True
        else:
            self._has_apps = False
        self._icon = ICON_TV_OFF

        self._supported_features = SUPPORT_SAMSUNGTV
        if self._config.method != 'legacy':
            self._supported_features |= SUPPORT_TURN_ON

        # Mark the end of a shutdown command (need to wait 15 seconds before
        # sending the next command to avoid turning the TV back ON).
        self._end_of_power_off = None

        # Mark the end of the TV powering on.need to wait 20 seconds before
        # sending any commands.
        self._end_of_power_on = None
        # Generate a configuration for the Samsung library

        self._remote = self._remote_class(self._config)

    def update(self):
        """Update state of device."""
        if self._power_off_in_progress():
            _LOGGER.debug(
                self._config.display_name + ' TV: Powering Off'
            )
            self._state = STATE_OFF
            self._icon = ICON_TV_OFF
            # self._entity_image = self._tv_image

        elif self._power_on_in_progress():
            _LOGGER.debug(
                self._config.display_name + ' TV: Powering On'
            )
            self._state = STATE_OFF
            self._icon = ICON_TV_OFF
            # self._entity_image = self._tv_image
        else:
            power = self._remote.power
            if power is True and self._remote.is_connected:
                self._config.save()

                if self._tv_image is None:
                    tv_image = self._remote.icon
                    if tv_image is not None:
                        self._tv_image = tv_image.data
                sources = self._remote.sources
                entity_image = self._tv_image
                source = 'Unknown'

                if sources is None:
                    if self._has_apps:
                        sources = [
                            'TV',
                            'HDMI'
                        ]

                        for app in self._remote.applications:

                            if app.is_running and app.is_visible:
                                source = 'APP: ' +app.name
                                entity_image = app.icon

                            sources += ['APP: ' + app.name]

                        self._sources = sources
                        self._source = source
                        self._entity_image = entity_image
                    else:
                        self._sources = [
                            'Source',
                            'Component 1',
                            'Component 2',
                            'AV 1',
                            'AV 2',
                            'AV 3',
                            'S Video 1',
                            'S Video 2',
                            'S Video 3',
                            'HDMI',
                            'HDMI 1',
                            'HDMI 2',
                            'HDMI 3',
                            'HDMI 4',
                            'FM-Radio',
                            'DVI',
                            'DVR',
                            'TV',
                            'Analog TV',
                            'Digital TV'
                        ]
                        self._key_source = True
                else:
                    new_sources = []
                    for src in sources:
                        if src.is_active:
                            if src.label != src.name:
                                source = src.label + ':' + src.name
                            else:
                                source = src.name

                        if src.name != src.label:
                            new_sources += [src.label + ':' + src.name]
                        else:
                            new_sources += [src.name]

                    self._key_source = False
                    self._sources = new_sources[:]

                self._source = source
                # self._entity_image = entity_image

                if self._source.upper().endswith('TV'):
                    self._icon = ICON_TV
                elif self._source.upper().endswith('USB'):
                    self._icon = ICON_USB
                elif self._source.upper().endswith('PC'):
                    self._icon = ICON_PC
                elif self._source.upper().endswith('DLNA'):
                    self._icon = ICON_DLNA

                elif 'S VIDEO' in self._source.upper():
                    self._icon = ICON_SVIDEO
                elif 'COMPONENT' in self._source.upper():
                    self._icon = ICON_COMPONENT
                elif 'AV' in self._source.upper():
                    self._icon = ICON_AV
                elif 'HDMI' in self._source.upper():
                    self._icon = ICON_HDMI
                elif 'YOUTUBE' in self._source.upper():
                    self._icon = ICON_YOUTUBE
                elif 'HULU' in self._source.upper():
                    self._icon = ICON_HULU
                elif 'NETFLIX' in self._source.upper():
                    self._icon = ICON_NETFLIX
                elif 'PLEX' in self._source.upper():
                    self._icon = ICON_PLEX
                elif 'SPOTIFY' in self._source.upper():
                    self._icon = ICON_SPOTIFY
                elif 'AMAZON' in self._source.upper():
                    self._icon = ICON_AMAZON
                elif 'PLAYSTATION' in self._source.upper():
                    self._icon = ICON_PLAYSTATION
                else:
                    self._icon = ICON_UNKNOWN

                volume = self._remote.volume
                _LOGGER.debug(
                    self._config.display_name + ' TV: Volume = ' + str(volume)
                )
                if volume is not None:
                    self._volume = volume / 100.0

                mute = self._remote.mute
                _LOGGER.debug(
                    self._config.display_name + ' TV: Mute = ' + str(mute)
                )
                if mute is None:
                    self._mute = False
                else:
                    self._mute = mute

                _LOGGER.debug(
                    self._config.display_name + ' TV: Power is On'
                )
                self._state = STATE_ON
            else:
                _LOGGER.debug(
                    self._config.display_name + ' TV: Power is Off'
                )
                # self._entity_image = self._tv_image
                self._icon = ICON_TV_OFF
                self._state = STATE_OFF

    def send_key(self, key):
        """Send a key to the tv and handles exceptions."""
        if self._power_off_in_progress():
            _LOGGER.info(
                self._config.display_name +
                " TV: powering off, not sending command: %s",
                key
            )
            return

        elif self._power_on_in_progress():
            _LOGGER.info(
                self._config.display_name +
                " TV: powering on, not sending command: %s",
                key
            )
            return

        if self._state == STATE_OFF:
            _LOGGER.info(
                self._config.display_name +
                " TV: powered off, not sending command: %s",
                key
            )
            return

        self._remote.control(key)

    @property
    def icon(self):
        """Return the icon to use in the frontend, if any."""
        return self._icon

    @property
    def entity_picture(self):
        """Return the entity picture to use in the frontend, if any."""
        return self._entity_image

    @property
    def unique_id(self) -> str:
        """Return the unique ID of the device."""
        return '{' + self._config.uuid + '}'

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

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

    @property
    def supported_features(self):
        """Flag media player features that are supported."""
        return self._supported_features

    def select_source(self, source):
        """Select input source."""
        if self._key_source:
            if source == 'Analog TV':
                source = 'ANTENA'

            elif source == 'Digital TV':
                source = 'DTV'

            source = source.upper().replace('-', '_').replace(' ', '')
            source = 'KEY_' + source
            _LOGGER.debug(
                self._config.display_name + ' TV: changing source to ' + source
            )
            self.send_key(source)
        else:
            if 'APP' in source:
                app_name = source.rsplit(':', 1)[-1]
                app = self._remote.get_application(app_name)

                if app is not None:
                    app.run()

            if ':' in source:
                source = source.rsplit(':', 1)[-1]
            _LOGGER.debug(
                self._config.display_name + ' TV: changing source to ' + source
            )
            self._remote.source = source

    @property
    def source(self):
        """Name of the current input source."""
        return self._source

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

    def volume_up(self):
        """Volume up the media player."""
        self.send_key('KEY_VOLUP')

    def volume_down(self):
        """Volume down media player."""
        self.send_key('KEY_VOLDOWN')

    @property
    def volume_level(self):
        """Volume level of the media player scalar volume. 0.0-1.0."""
        return self._volume

    def set_volume_level(self, volume):
        """Set volume level, convert scalar volume. 0.0-1.0 to percent 0-100"""
        self._remote.volume = int(volume * 100)

    def mute_volume(self, mute):
        """Send mute command."""
        self._remote.mute = mute

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

    def media_play_pause(self):
        """Simulate play pause media player."""
        if self._playing:
            self.media_pause()
        else:
            self.media_play()

    def media_play(self):
        """Send play command."""
        self._playing = True
        self.send_key('KEY_PLAY')

    def media_pause(self):
        """Send media pause command to media player."""
        self._playing = False
        self.send_key('KEY_PAUSE')

    def media_next_track(self):
        """Send next track command."""
        self.send_key('KEY_FF')

    def media_previous_track(self):
        """Send the previous track command."""
        self.send_key('KEY_REWIND')

    async def async_play_media(self, media_type, media_id, **kwargs):
        """Support changing a channel."""
        if media_type != MEDIA_TYPE_CHANNEL:
            _LOGGER.error(
                self._config.display_name + ' TV: Unsupported media type'
            )
            return

        # media_id should only be a channel number
        try:
            cv.positive_int(media_id)
        except vol.Invalid:
            _LOGGER.error(
                self._config.display_name +
                ' TV: Media ID must be positive integer'
            )
            return

        for digit in media_id:
            await self.hass.async_add_job(self.send_key, 'KEY_' + digit)
            await asyncio.sleep(KEY_PRESS_TIMEOUT, self.hass.loop)

    @property
    def app_id(self):
        """ID of the current running app."""
        return None

    @property
    def app_name(self):
        """Name of the current running app."""
        return None

    def turn_on(self):
        """Turn the media player on."""

        if self._power_on_in_progress():
            return

        if self._config.mac:
            self._end_of_power_on = dt_util.utcnow() + timedelta(seconds=20)

            if self._power_off_in_progress():
                self._end_of_power_on += (
                    dt_util.utcnow() - self._end_of_power_off
                )

            def do():
                _LOGGER.debug(
                    self._config.display_name + ' TV: Power on process started'
                )
                event = threading.Event()
                while self._power_off_in_progress():
                    event.wait(0.5)

                self._remote.power = True

            t = threading.Thread(target=do)
            t.daemon = True
            t.start()
        elif self._config.method != 'legacy':
            _LOGGER.info(
                self._config.display_name +
                " TV: There was a problem detecting the TV's MAC address, "
                "you will have to update the MAC address in the Home "
                "Assistant config file manually."
            )

        else:
            _LOGGER.info(
                self._config.display_name +
                " TV: Legacy TV's (2008 - 2013) do not support "
                "being powered on remotely."
            )

    def _power_on_in_progress(self):
        return (
            self._end_of_power_on is not None and
            self._end_of_power_on > dt_util.utcnow()
        )

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

        if self._power_off_in_progress():
            return

        self._end_of_power_off = dt_util.utcnow() + timedelta(seconds=15)

        if self._power_on_in_progress():
            self._end_of_power_off += (
                dt_util.utcnow() - self._end_of_power_on
            )

        def do():
            _LOGGER.debug(
                self._config.display_name + ' TV: Power off process started'
            )
            event = threading.Event()
            while self._power_on_in_progress():
                event.wait(0.5)

            self._remote.power = False

        t = threading.Thread(target=do)
        t.daemon = True
        t.start()

    def _power_off_in_progress(self):
        return (
            self._end_of_power_off is not None and
            self._end_of_power_off > dt_util.utcnow()
        )

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 14
  • Comments: 375 (113 by maintainers)

Commits related to this issue

Most upvoted comments

After HA updates, I have decided to create a custom component, it is working on the latest version of HA 0.99.3, I have uploaded the code to:

https://github.com/roberodin/ha-samsungtv-custom

I hope it helps you.

ok I will tell you where I am at with the library. I need someone that uses the tuner on the TV. a TV that is 2015 or older. I am able to provide a channel lineup with channel names i simply need to know the format of the file.

if you go to this URL in your browser on your PC with the TV on and replacing YOUR_TV_IP with the IP of your TV.

http://YOUR_TV_IP:9090/BinaryBlob/3/ChannelList.dat

save the file and zip it up and attach it to an issue in the samsungctl repo.

that is the only thing left for the UPNP end of things. The library is 100% working for legacy TV’s I did some pretty serious housekeeping on the code. and I cleaned up a lot of things and improved the overall performance of the library. The TV discovery is 100% working now. The power state is also working I am in the process of getting the encrypted websocket back into running order. I removed a massive amount of code that was not needed. and removed multiple creations of the aes encryption object that gets used when connecting to the TV. I moved all of thee code that is specific to the TV control into the remote classes. you can see the large code update i did here.

https://github.com/kdschlosser/samsungctl/commit/b52b2ae13a3c418d9ed1b4e7d43187781076fa3e

I have not tested the websocket or websocket ssl portion of the library. I have not had someone that has one of these TV’s offer to help in the debugging

I created a script that will test every single component of the library so helping to test only requires you to install python 2.7 and python 3.5+ install a few modules using pip in both python installations. and then run the test program. then send me the data. everything gets writen to a series of files and from those files I am able to fix issues and i can fix them really quickly.

What I had wanted to do was to add all of the available functionality of samsungctl to hass. and not just volume up and down and power. I have asked several times for guidance/help doing that portion of it and have not heard a peep from anyone. because of how hass is coded an IDE is pretty much useless in the help department. and I simply do not have the time to learn by trial and error. I do have things that require my attention other then a PC. Most of the users here are on the opposite side of the planet from me. But it seems as tho I am the one that is staying up all night working on this thing. Frankly I am getting burnt and I am falling behind with other tasks.

The library works for my TV. so anything i do now with the library is not to my benefit. it is for yours. I should not be the only one that is sacrificing in order to provide you with a way to control your TV. any of the people that have run the test program has seen the amount of data that i have to shuffle through in order to get things working. and being tired does not help.

OK I have not updated anything yet. so continuing to mess around with the one that is posted is not going to get you anywhere. It does not work correctly. II know this. I have been working on it. There are still problems with samsungctl. I need to get those all cleaned up and everything running properly before I am going to do anything with getting it to run in hass. I am sorry but I am not going to be able to get the code updated today. Family business took most of my day. so I was not able to get the last couple of things fixed. I only have a 2-3 hour window for testing so I do not get to work on it all that much. The people that do the testing for me are an 8 hour time difference. so it makes it a bit challenging.

I wanted to let everyone know where we are at with this an not leave ya hanging.

OK so here is the skinny. discovery is working like a champ now. I found out where the issue wa and it is now all sorted out. I am still working out some bugs in the power portion of the program. I think I may have found what the cause is just waiting on a test. websocket, SSL websocket and encrypted are working good. i broke the legacy somehow. still looking into what I did there. all the issues with UPNP have been resolved. I only have the channels to deal with and the UPNP i am almost sure can be stamped COMPLETE. the config portion is working good. no problems there. after i fix the few issues and do up the channels and run some more tests across the 5-6 different models that people are running these tests on if everything works smoothly then I know the backend of the library is functioning like it should and I can move back to working on the hass portion. I would really like to expose more of what these TV’s can do to hass. But I need help doing that. the controls that hass gives doesn’t even scratch the surface of the control that is available. and I do not own any of the components in the components portion of the program. if i did I would be able to load the component and see how thee GUI is done for those items. I guess I could “hotwire” the things so they would display. That is quite a bit of work to do. I need a visual comparison to see if i can make something work for the samsung tv’s So again if there is someone out there that is really knowledgeable about hass and the different “flows” and things of that nature. I could really use the help on that end of it.

I will be updating the samsungctl library some time today. I will also update the code in the first post to work with the updated library.

the only thing you need to do is go into components/media_player/samsungtv.py delete all of the code in that file and paste in the code that is attached to the first post. it will update the samsungctl library as needed.

@sermayoral and @kdschlosser do you guys still need testers? I have take a version of @kdschlosser library and have it running via a custom component in my home assistant so may be able to help some if desired.

For Athom Homey, somebody managed to make a working app. Possibly this could give some clues for the Home assistant module.

https://github.com/balmli/com.samsung.smart/tree/cd455cbb5cb27988e7e32e2489cf604e7135843d

@kdschlosser is the samsungctl library developer. He doesn’t know the Home Assistant code. It is very annoying to understand all the code of a system that you have not developed, and he does not have to do it. We must understand that.

He also asked for help from a Home Assistant developer to help him integrate his code, but no one volunteered.

However, he continues to improve the samsungctl library to support ALL TV models, and some of us are helping him. This is the first step. Without the samsungctl library, nothing is possible in HA with Samsung TVs.

If there is a volunteer who wants to help us to debug samsungctl outside the Home Assistant environment, he just needs basic knowledge of Python, and have a TV model that tells us @kdschlosser

The first step is to complete the samsungctl library. Then we will see who can help us integrating it into Home Assistant. Some developer may see the samsungctl commit and decide to merge it in Home Assistant …

But of course, the first thing is to complete samsungctl. Thanks for everything you are doing @kdschlosser 😃

I have H series TV. I tried multiple components and they never work as expected

   from custom_components.samsungctl import exceptions
   from custom_components.samsungctl import Remote

Thanks for sharing @tmonck, could you please point to le line where i should input this code.

Thank you.

Line 89, 90.

@Gamelauncher The quick howto is:

  1. Copy this directory https://github.com/home-assistant/home-assistant/tree/dev/homeassistant/components/samsungtv into your custom components directory. (This is just the default samsungtv component.
  2. Inside the media_player.py file add the following code to the init function in the SamsungTVDevice class:
       from custom_components.samsungctl import exceptions
       from custom_components.samsungctl import Remote
  1. Change 8001 to 8002 for the port
  2. Clone the samsungctl repo that is being developed by @kdschlosser.
  3. Copy that entire repo in to a samsungctl directory under custom_components directory.

@arsaboo If I had made major modifications I would share but as you can see from my steps above I am just using the existing component with no fancy addons. I am excited to attempt to help integrate some of the new features you guys have been adding.

@kdschlosser I will jump over to that issue and see where I can assist.

@sermayoral, yep, my MU6300 uses ssl websocket. The tv was mostly working in HA before the 1250 Samsung firmware update. There was a fix to adapt the ssl websocket, which made it work again (mostly, on, off, mute, volume) but the tv got another firmware update and stopped working again.

I will gladly help if you need info from my tv. I have a PI2 laying around so I could get it running again and test what you need me to!

hass.io users:

go to the location where the configuration.yaml file is located open the custom_components folder open the media_player folder if it exists. if not make it. create a new fil called “samsungtv.py” thne paste the code form the first post into that new file. restart hass.io

for homeassistant users (pip install of homeassistant) open up your python site-packages folder. from there navigate to the folder homeassistant\components\media_player open the samsungtv.py file. select everything in the file and press delete past in the code form the first post. save the file and restart homeassistant.

thee code form the first post contains all of the code needed to download the version of samsungctl from my Github repository that is needed. when I update the code if you already have samsungctl installed into homeassistant it is going to upgrade\downgrade it to the version that is needed.

@maxkde TY for the complete post of the traceback. i do appreciate it.

I think because of the way samsung TV’s reply encoding is different between years/models as well as different based on the function. we are going to need to do some exception catching here.

so starting at line 114 in action.py which reads

        response = response.content.decode('utf-8')

        logger.debug(self.__name__ + ' <-- ' + response)

        try:
            envelope = etree.fromstring(response)
        except etree.ParseError:
            return [None] * len(self.ret_vals)

        envelope = strip_xmlns(envelope)

        body = envelope.find('Body')

        return_value = []

we need to change that to

        logger.debug(self.__name__ + ' <-- ' + response.content.decode('utf-8'))

        try:
            envelope = etree.fromstring(response.content.decode('utf-8'))
        except ValueError:
            try:
                envelope = etree.fromstring(response.content)
            except etree.ParseError:
                return [None] * len(self.ret_vals)
        except etree.ParseError:
            return [None] * len(self.ret_vals)

        envelope = strip_xmlns(envelope)

        body = envelope.find('Body')

        return_value = []

what is causing the issue is this. if the opening tag in the returned xml is this

<?xml version="1.0"?>

then the input needs to be a string. and because response.content in python 3 are bytes we need to decode the information before feeding it into lxml

Now when the return data has this as the first tag

<?xml version="1.0" encoding="UTF-8"?>

the data fed into lxml needs to be bytes.

it’s a crap shoot what TV is going to return what tag as well as the function. some functions may return the encoding some may not.

ya gotta love the dumb ass programmers at Samsung… they have ZERO consistency

ok i finally found where they are hiding that damned configuration dialog. it comes in as a notification instead of either in a device configuration page or as a simple popup

Update to version 3.0.0 and try the new protocols: -ws (fixed from @xchwarze) -ctl_beta -ctl_qled

https://github.com/roberodin/ha-samsungtv-custom

I can also try and help swap out the current implementation once it gets ready for prime time. I’ve at least contributed to the Samsung TV component in the past (tiny contribution of channel changing) but am willing to help out where I can, when I can! @kdschlosser I’ve got a websocket tv if it helps. Let me know what I can do!

@kdschlosser thank you for the hard work you put in attempting to get this working. My last comment was just wondering what the status was.

@kdschlosser thanks for being so responsive on here despite your family business 😃

Super, will test it tonight! When you said you’ll update the samsungctl library, does is it mean that you maintain the Ape library or we need to replace HA library with yours? Sorry for my noobness 😉

Just realize that your samsungtv.py will download the source from your repo. Sry

The code in the first post will download his version. The maintainer of the Ape library has gone awol and his was forked from that (and then new features added) IIRC he is trying to iron out all the bugs then it will be added to pippy and a PR done on the code for HA to replace the current samsungtv with his version.

The phrase “It cannot be done.” does not fit into my vocabulary. because the person saying it is actually saying “I am to lazy to figure it out.”, I will however accept a substitute of “Given our state of technology at the present time we cannot make it happen.” This is a good replacement because it is not saying that it can’t be done. it is saying it can’t be done right now but in the future, yes.

I have been told my bean does a very good job at solving problems or coming up with crafty ways to accomplish a goal.

Now as far as the remote button eventing… I know this in fact does work with Samsung TV’s (It’s a crap shoot as to what brands do and don’t) I have tested it with my Samsung TV. That is not to say that the dingbats over at Samsung didn’t remove this feature in newer models. I do not have that answer.

The other things is how does one go about creating something the automation pieces of hass can use. That I do not know. so if someone is willing to chime in and explain this system to me then we can have a go at it.

I wanted to throw something out there as an idea. Some time ago I wrote an API for liibcec. for those of you that do not know what libcec is, it is a software package for accessing a CEC interface device. Pulse Eight makes one of these devices. and one is built into Raspberry Pi. Seeing as how a lot of you folks are using raspberry pi’s I thought this would be a geat feature to add into samsungctl. for starters this would give you the direct source change that most of you would like to have. We can also capture events for button presses on the remote. the samsungctl volume control would change over to using libcec for the volume control. so if you have a CEC complient AVR the volume would change on that instead of the TV.

what are the thoughts on that??

@LeidenSpain

this is still an alpha test version. it does not function properly yet. as you can see from the posts I am trying to get as many features from these TV’s as possible. I am really thinking to going out and buying one for 2 weeks and then return it.

on my legacy everything works perfect. but it is different mechanics then the websocket TV’s

I have a HU7500 and basic knowledge about Python (but I’m a developer)

@kdschlosser I’ll be glad to help. I’ve got a QE65Q8FAMT

I have a UE55NU8006 and am willing to help with testing. Joined the slack chat already 😃 @kdschlosser I’ve sent you a DM on slack.

I have a UN75NU8000 that I can test with

@sermayoral

i have it outputting the information to the file requested. I sent you an updated version of samsungctl along with the program for running the tests.

to increase the output buffer of a command prompt if you click on the small icon in the caption bar on the top left of the window. you should see preferences in the drop down menu. in the properties dialog you should have a tab for “Layout”. this is where you will be given the controls to adjust the buffer. you can also adjust the number of columns and rows that are displayed as well.

as far as the serializing issue that is easily corrected as well.

line 434 in the code attached to the first post would need to be changed to

self._entity_image = "".join(map(chr, list(entity_image)))

OK I updated the code in the first post.

I changed a bunch of things around. IP addresses are no longer needed to define a TV, this means you do not have to set the TV to a static IP address. I am now relying on a UUID that is unique to each TV. With this change It also altered the power detection. I am now using UPNP to detect if the TV is on or not. so the constant trying to open a socket to detect if the TV is on or not is no longer being used. this has reduced the resource use on the device running the code. it would be more noticeable with multiple TV’s. This is a good thing for you folks running on Raspberries. In my setup only running a single TV it has also reduced the memory footprint a few meg.

I also changed the detection mechanism I am now grabbing the year of the TV via UPNP and using the services as a fallback in case the year is not detected properly. This is going to make for a much more solid detection of the connection type.

I did also change up the inputting of the PIN so the notification will be generated after the pin has been displayed on the TV and not before. I think this may have been causing some issues.

the new code is in the first post. everything needed to have it update samsungctl has been set in place.

@cristimi I am not sure as to why but your TV is not responding to the UPNP SSDP broadcast packets.

@cjsmns as a reminder the pin entry dialog should appear as a notification. The notification to enter the PIN should be working. if you are not getting a notification then I am going to need to need to do some more work on the detection. possibly parsing the model number. I think that is what I am going to do.

@phairplay OK so the first piece is tackled. we are now moving onto the power detection once again. I am working on this aspect of it.

I am in the process of changing the library to not be dependant on the IP address of the TV. I am putting more work into the UPNP end of things. this is going to be the way to go about detecting the TV’s state and also locating new TV’s added to your network.

so basically having to set your TV to a static IP will not have to be done anymore. and if you go out and buy a new TV and connect it to your network. hass will almost instantly notify you of the new TV and if you want to add it. no restarts needed!!

those changes will be made in the next 2 days or so. the reasoning for this is instead of trying to constantly open a socket every second or so (resource intensive) i am going to rely on UPNP discovery packets. the UPNP will keep on broadcasting keeping a single socket open and that single socket will handle all of the TV’s on the network. this will by far use less resources it will be a lot more apparent if there are multiple TV’s being handled

@phairplay

I updated the code. and I did add the upnp classes to the discovery correctly for your TV (lucky guess).

You can give the new code in the first post a go. just make sure you delete all of the files in the samsung_tv config path before running hass.

Where do you define de MAC address? In the configuration.yaml file in that way?

Yep. This is what I have.

  - platform: samsungtv
    host: 192.168.1.64
    name: UA60H6400
    mac: <mac_address> # obviously I have the correct MAC address
    method: encrypted

I noticed the config issue as well. It is not a big deal because this only gets used for thee actual connection to the TV. thee name that takes presidence and shows in your home assistant GUI is the on that is supplied in the home assistant config file. so it is pretty moot.

That is strange about the power state for the TV. I am going to put the code back to use the socket as the mechanism to determine the state of the TV. I like this mechanism better then making another call to the TV to see if it returns data.

I am guessing you are not able to test anything because the TV s reported as being off.

let me make a quick change and see if it solves your problem

I have most of the things you mentioned repaired. I will be pushing an update soon to samsungctl. We are making progress, albeit slow but at least there is movement in the right direction 😄

as far as the config goes. this is the hard part. because of the 10 second limitations that Home Assistant has in place it is making it difficult to really run a full detection suit. I think I have a way to go about it. that would be really fast and also I would be able to run all 3 detection types at the same time. this would greatly speed up the whole process.

I need the UPNP layout for the 2016+ TV’s. I have for the legacy and for the encrypted once i get the one for the websocket connection II will be able to see which type has what UPNP services. I am willing to bet there is a single unique service to each of the TV types. and if that is the case then I can send out a broadcast for each of the connection types specifically. from there i can determine the connection by seeing who answered what broadcast.

If someone has a newer TV and also have Python installed you can clone this repo

https://github.com/kdschlosser/UPNP_Device/tree/develop

and run

python setup.py install

and once it is done installing make sure you have your TV on. and run this command

upnp_device --dump "[DUMP_DIRECTORY]" --timeout 10 [IP]

changing the [IP] to the ip of your TV and changing [DUMP_DIRECTORY] to the directory where you want copied of the UPNP files form the TV dumped into. make sire the directory already exists and it also has to be an absolute path so no $'s or ~'s or %'s

after it finished the directory is going to contain an exact replica of the UPNP paths that the TV has as well as a SSDP.log file. which is a log of the SSDP responses form the TV. I would need the whole directory zipped up exactly how it is.

I am going to do a commit shortly to update the code in the UPNP_Device repo. so you have to give me 15 minutes or so before trying it.

You can also test out setting the TV volume if you wanted. and getting the volume and setting the mute and getting the mute as well.

upnp_device --execute RenderingControl1.GetVolume --InstanceID 0
upnp_device --execute RenderingControl1.SetVolume --InstanceID 0 --DesiredVolume 50

there may be an additional parameter needed depending on the TV

upnp_device --execute RenderingControl1.GetVolume --InstanceID 0 --Channel Master
upnp_device --execute RenderingControl1.SetVolume --InstanceID 0 --Channel Master --DesiredVolume 50

Mute is pretty much the same deal. just swap out Volume for Mute and you have to specify a 1 or a 0 for the value if setting it.

when you do the dump you will see a whole printout of every upnp function the TV offers. if you want to do that without dumping the data

upnp_device [IP]

if you omit thee IP it is going to print out every device on your network.

you can also get help on a specific function. this is going to tell you the arguments that are required and ones that are optional. default values. choices. min and max. crap like that

upnp_device --execute RenderingControl1.SetVolume --help

the --help MUST be after the function

@arsaboo

what do you mean??

@phairplay have a look at this…

I wrote a Sony TV Gen3+ API, I think it is still the most complete API made to date. I could be wrong in that tho. I have a develop branch that has a bunch more added to it. the develop branch is not in a working state and it actually has been a long time since I have put any work into it.

https://github.com/kdschlosser/SonyAPI

@phairplay

also I am starting to think you have a special-ed TV on your hands over there. the thing should be riding the little yellow short bus while wearing a helmet and licking the window. because it’s slowwwww… that is causing the time out problem in the async handler. I am going to move the construction of the remote to the __init__ method and see if that solves the problem . I know there is a 60 second timeout on that.

@phairplay

I am at 8500 feet (2590.8 meters) above sea level. in Colorado (Rocky Mountains) about an hour and a half away from the highest mountain in North America. Put it to ya this way. nearest gas station is a tad over 25 minutes away. (If i drove like the law says I should but since I don’t it takes me about 15). In the last 3 weeks we have gotten 4 feet of snow.

and -20c is nothing. we call that “brisk” here… now when it hits -30c (and it does and stays like that for a month sometimes) that is a might bit chilly. We get this really cool phenomenon that happens. we call it “Thunder Snow”. That is when you have a lightning storm while it is snowing…

from the logs it does appear the TV state is now working correctly. why exactly your TV is not turning on is the question. if the restart does not solve it then I will have to add some code to see what is going on.

@jnimmo No it is sending the command. When Home Assistant checks the source I am returning Unknown

plus I am 45… memory is slipping I do need to go back and see if i have tried something or not… 😉

I am glad to see all of the upnp errors are gone with this version of samsungctl.

I did also want to mention that the power on and power off sequence takes roughly 17 seconds for on and about 12 seconds for off.

do not pay attention to what the TV is doing. just because the screen is on or off is not what dictates when the TV is actually considered on or off. These TV’s are Computers. so when you turn you computer on. just because the screen is on does it mean the computer is booted?? No it does not. so the same thing goes for the TV. it takes a while for the TV to boot up. as well as it takes a while for it to shutdown. so when the websocket service is finally started or stopped (which is one of the last things the TV does) then it is considered on or off.

so be patient when telling it to turn on or off. it may take 20 seconds for the state to change in HomeAssistant. and just like any computer. the newer the TV the faster it will be. and do remember that if you have a lot of applications on it. this can also impact the speed in which the TV boots. Also as a good rule of thumb. the more ding dongs and ho ho’s (features) the TV has the longer it is going to take to boot. so the top of the line samsung TV of this year is probably going to take longer to turn on then say something middle of the road