home-assistant / core

:house_with_garden: Open source home automation that puts local control and privacy first.
https://www.home-assistant.io
Apache License 2.0
70.08k stars 29.15k forks source link

Samsung TV's not working [Testing fix] #20795

Closed kdschlosser closed 5 years ago

kdschlosser commented 5 years ago

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

```python """ 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() ) ```

arsaboo commented 5 years ago

There is a missing , after the line vol.Optional(CONF_METHOD): cv.string. After adding the comma, I am getting the following error:

Log Details (ERROR)
Wed Feb 06 2019 15:01:28 GMT-0500 (Eastern Standard Time)
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 128, 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 109, in setup_platform
    [SamsungTVDevice(host, name, description, method, uuid, config_path)]
  File "/config/custom_components/media_player/samsungtv.py", line 136, in __init__
    self._config = samsungctl.Config()
  File "/config/deps/lib/python3.6/site-packages/samsungctl/config.py", line 57, in __init__
    raise exceptions.ConfigHostError
samsungctl.exceptions.ConfigHostError: <unprintable ConfigHostError object>

My TV is UN75NU8000.

kdschlosser commented 5 years ago

I updated the code in the first post to solve both problems.

arsaboo commented 5 years ago

UPNP errors now:

Log Details (ERROR)
Wed Feb 06 2019 15:13:35 GMT-0500 (Eastern Standard Time)
Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/homeassistant/helpers/entity_platform.py", line 352, in _async_add_entity
    await entity.async_update_ha_state()
  File "/usr/local/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 239, in async_update_ha_state
    attr = self.state_attributes or {}
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/media_player/__init__.py", line 787, in state_attributes
    in ATTR_TO_PROPERTY if getattr(self, attr) is not None
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/media_player/__init__.py", line 787, in <dictcomp>
    in ATTR_TO_PROPERTY if getattr(self, attr) is not None
  File "/config/custom_components/media_player/samsungtv.py", line 302, in volume_level
    volume = self.get_remote().volume
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/__init__.py", line 1215, in volume
    if not self.connected:
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/__init__.py", line 30, in connected
    self._connect_upnp()
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/__init__.py", line 37, in _connect_upnp
    if not self._connected and self.power:
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/UPNP_Device/upnp_class.py", line 123, in __getattr__
    if item in self._devices:
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/UPNP_Device/upnp_class.py", line 123, in __getattr__
    if item in self._devices:
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/UPNP_Device/upnp_class.py", line 123, in __getattr__
    if item in self._devices:
  [Previous line repeated 322 more times]
RecursionError: maximum recursion depth exceeded

Are you on Home Assistant Discord?

kdschlosser commented 5 years ago

Are you on Home Assistant Discord?

dunno what that is.

can you do me a favor. please attach this file to a post. samsungctl/upnp/__init__.py

arsaboo commented 5 years ago

Here it is https://hastebin.com/efuzucutiw.rb

arsaboo commented 5 years ago

Discord has Home Assistant chat server, easy to coordinate https://www.home-assistant.io/help/

Here's the Discord server link: https://discord.gg/6JJ3Rb

kdschlosser commented 5 years ago

Nevermind on that last post.

I am not trying to be rude at all. but I can see you did not follow my directions. This is the reason why I cannot stress enough to FOLLOW DIRECTIONS

it clearly states in the first post.

DO THIS FIRST

we are going to want to clean out any installed versions of the current samsungctl library. you can do this from a command prompt (shell)

pip uninstall samsungctl

I have updated some code of the code in samsungctl and the new code needs to be downloaded. and installed. I really would prefer if you also installed it from the command line using

pip install --no-cache-dir "https://github.com/kdschlosser/samsungctl/archive/develop.zip#samsungctl==0.8.0b"

this will make sure it gets installed without using a cached copy of it that may be on your system

You have an old copy of samsungctl that is being used. that is why you are getting the error.

arsaboo commented 5 years ago

Let me reinstall Hassio afresh and test it out again. Gimme 10 min.

arsaboo commented 5 years ago

Ok....started afresh and now I see the entity created :tada:

I have not tested anything yet. But I see the media_player entity, which is huge progress :)

kdschlosser commented 5 years ago

I am going nutz over here trying to locate the problem. and i have not been able to come up with anything. I even coded up a replica of the process that is taking place and it runs fine.

you got me on this one. But it appears as tho you have has some progress. and there was possibly something latent kicking about somewhere causing the issue.

arsaboo commented 5 years ago

I think there is definitely something wrong here. After restarting I am getting the same UPNP errors.

Log Details (ERROR)
Wed Feb 06 2019 16:58:13 GMT-0500 (Eastern Standard Time)
Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/homeassistant/helpers/entity_platform.py", line 352, in _async_add_entity
    await entity.async_update_ha_state()
  File "/usr/local/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 239, in async_update_ha_state
    attr = self.state_attributes or {}
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/media_player/__init__.py", line 787, in state_attributes
    in ATTR_TO_PROPERTY if getattr(self, attr) is not None
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/media_player/__init__.py", line 787, in <dictcomp>
    in ATTR_TO_PROPERTY if getattr(self, attr) is not None
  File "/config/custom_components/media_player/samsungtv.py", line 258, in source
    source = self.get_remote().source
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/__init__.py", line 950, in source
    if not self.connected:
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/__init__.py", line 30, in connected
    self._connect_upnp()
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/__init__.py", line 37, in _connect_upnp
    if not self._connected and self.power:
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/UPNP_Device/upnp_class.py", line 123, in __getattr__
    if item in self._devices:
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/UPNP_Device/upnp_class.py", line 123, in __getattr__
    if item in self._devices:
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/UPNP_Device/upnp_class.py", line 123, in __getattr__
    if item in self._devices:
  [Previous line repeated 322 more times]
RecursionError: maximum recursion depth exceeded

Not sure if this may be causing it, but I am using a Sonos Playbase for audio. I know it is frustrating

kdschlosser commented 5 years ago

here is what I am going to do. I will bump the version on the develop branch and update the code in the first post to use the new version.

kdschlosser commented 5 years ago

ok I did the version bump.

arsaboo commented 5 years ago

So, unfortunately, the same issue, it worked after I put the code in for the first time. But after restarting HA, I get the same UPNP errors:

Log Details (ERROR)
Wed Feb 06 2019 15:47:31 GMT-0500 (Eastern Standard Time)
Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 239, in async_update_ha_state
    attr = self.state_attributes or {}
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/media_player/__init__.py", line 787, in state_attributes
    in ATTR_TO_PROPERTY if getattr(self, attr) is not None
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/media_player/__init__.py", line 787, in <dictcomp>
    in ATTR_TO_PROPERTY if getattr(self, attr) is not None
  File "/config/custom_components/media_player/samsungtv.py", line 302, in volume_level
    volume = self.get_remote().volume
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/__init__.py", line 1215, in volume
    if not self.connected:
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/__init__.py", line 30, in connected
    self._connect_upnp()
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/__init__.py", line 37, in _connect_upnp
    if not self._connected and self.power:
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/UPNP_Device/upnp_class.py", line 123, in __getattr__
    if item in self._devices:
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/UPNP_Device/upnp_class.py", line 123, in __getattr__
    if item in self._devices:
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/UPNP_Device/upnp_class.py", line 123, in __getattr__
    if item in self._devices:
  [Previous line repeated 323 more times]
RecursionError: maximum recursion depth exceeded while calling a Python object
arsaboo commented 5 years ago

If you are on Discord (https://discord.gg/6JJ3Rb) it will be easier to debug. I am at home and can work on it in real-time.

kdschlosser commented 5 years ago

my question is what is happening when you restart.. the code that is getting loaded is changing somehow

I do not know anything about your setup. but is there a possibility of some old copy of samsungctl kicking about in some other python installations site-packags folder?.

as an example. with another project that i work on. it uses python 2.7 but it is all set up as an executable. so technically speaking there is no python installation when you install it. But. if you install python 2.7 my program will load the site-packages directory from that python installation.

Now the reason why it works right after installation is because the neew version gets plopped in the begining of the search order. once you restart the program it is no longer going to be at the beginning of that search order.

I have also run into issues because of mixes installation types. example.

if you run python setup.py install it will install the library but you get either a file or a folder name that ends with .egg but if you do the install using pip that is not the case. the wacky thing is if you install via pip after you have already installed using python setup.py install you will end up with 2 installations.. and pip gets all mucked up when removing it.

it's a strange thing.

kdschlosser commented 5 years ago

I am going to add some information printouts. like the location of the library and the version.

kdschlosser commented 5 years ago

OK i updated the code in the first post to give us a printout of what is going on.

arsaboo commented 5 years ago

Here you go (had to change to error as info is not logged by default):

2019-02-07 03:14:30 ERROR (SyncWorker_7) [custom_components.media_player.samsungtv] samsungctl version: 0.8.1b
2019-02-07 03:14:30 ERROR (SyncWorker_7) [custom_components.media_player.samsungtv] samsungctl location: /config/deps/lib/python3.6/site-packages/samsungctl
2019-02-07 03:14:30 ERROR (SyncWorker_7) [custom_components.media_player.samsungtv] Samsung TV 192.168.2.252 added as 'Living Room TV'
2019-02-07 03:14:31 ERROR (MainThread) [homeassistant.core] Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/homeassistant/helpers/entity_platform.py", line 352, in _async_add_entity
    await entity.async_update_ha_state()
  File "/usr/local/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 239, in async_update_ha_state
    attr = self.state_attributes or {}
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/media_player/__init__.py", line 787, in state_attributes
    in ATTR_TO_PROPERTY if getattr(self, attr) is not None
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/media_player/__init__.py", line 787, in <dictcomp>
    in ATTR_TO_PROPERTY if getattr(self, attr) is not None
  File "/config/custom_components/media_player/samsungtv.py", line 305, in volume_level
    volume = self.get_remote().volume
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/__init__.py", line 1215, in volume
    if not self.connected:
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/__init__.py", line 30, in connected
    self._connect_upnp()
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/__init__.py", line 37, in _connect_upnp
    if not self._connected and self.power:
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/UPNP_Device/upnp_class.py", line 123, in __getattr__
    if item in self._devices:
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/UPNP_Device/upnp_class.py", line 123, in __getattr__
    if item in self._devices:
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/UPNP_Device/upnp_class.py", line 123, in __getattr__
    if item in self._devices:
  [Previous line repeated 322 more times]
RecursionError: maximum recursion depth exceeded
arsaboo commented 5 years ago

That is the whole log - I did not modify anything. The custom_component is how you test stuff in HA when it is not officially implemented, which is the case here.

arsaboo commented 5 years ago

What are you looking for specifically? My guess is once the entity is added, it tries to get all the properties and that is where the error appears.

Is there a way to eliminate UPNP (for now) to make sure that the basic media player works?

kdschlosser commented 5 years ago

give it a try again. delete samsungctl first. I updated the code in my repository

arsaboo commented 5 years ago
019-02-07 03:30:18 ERROR (SyncWorker_2) [custom_components.media_player.samsungtv] samsungctl version: 0.8.1b
2019-02-07 03:30:18 ERROR (SyncWorker_2) [custom_components.media_player.samsungtv] samsungctl location: /config/deps/lib/python3.6/site-packages/samsungctl
2019-02-07 03:30:18 ERROR (SyncWorker_2) [custom_components.media_player.samsungtv] Samsung TV 192.168.2.252 added as 'Living Room TV'
2019-02-07 03:30:19 ERROR (MainThread) [homeassistant.core] Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/homeassistant/helpers/entity_platform.py", line 352, in _async_add_entity
    await entity.async_update_ha_state()
  File "/usr/local/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 239, in async_update_ha_state
    attr = self.state_attributes or {}
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/media_player/__init__.py", line 787, in state_attributes
    in ATTR_TO_PROPERTY if getattr(self, attr) is not None
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/media_player/__init__.py", line 787, in <dictcomp>
    in ATTR_TO_PROPERTY if getattr(self, attr) is not None
  File "/config/custom_components/media_player/samsungtv.py", line 305, in volume_level
    volume = self.get_remote().volume
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/UPNP_Device/upnp_class.py", line 133, in __getattr__
    raise AttributeError(item)
AttributeError: volume
2019-02-07 03:30:30 ERROR (MainThread) [homeassistant.core] Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 239, in async_update_ha_state
    attr = self.state_attributes or {}
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/media_player/__init__.py", line 787, in state_attributes
    in ATTR_TO_PROPERTY if getattr(self, attr) is not None
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/media_player/__init__.py", line 787, in <dictcomp>
    in ATTR_TO_PROPERTY if getattr(self, attr) is not None
  File "/config/custom_components/media_player/samsungtv.py", line 305, in volume_level
    volume = self.get_remote().volume
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/UPNP_Device/upnp_class.py", line 133, in __getattr__
    raise AttributeError(item)
AttributeError: volume
2019-02-07 03:30:41 ERROR (MainThread) [homeassistant.core] Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 239, in async_update_ha_state
    attr = self.state_attributes or {}
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/media_player/__init__.py", line 787, in state_attributes
    in ATTR_TO_PROPERTY if getattr(self, attr) is not None
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/media_player/__init__.py", line 787, in <dictcomp>
    in ATTR_TO_PROPERTY if getattr(self, attr) is not None
  File "/config/custom_components/media_player/samsungtv.py", line 305, in volume_level
    volume = self.get_remote().volume
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/UPNP_Device/upnp_class.py", line 133, in __getattr__
    raise AttributeError(item)
AttributeError: volume
2019-02-07 03:30:52 ERROR (MainThread) [homeassistant.core] Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 239, in async_update_ha_state
    attr = self.state_attributes or {}
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/media_player/__init__.py", line 787, in state_attributes
    in ATTR_TO_PROPERTY if getattr(self, attr) is not None
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/media_player/__init__.py", line 787, in <dictcomp>
    in ATTR_TO_PROPERTY if getattr(self, attr) is not None
  File "/config/custom_components/media_player/samsungtv.py", line 305, in volume_level
    volume = self.get_remote().volume
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/UPNP_Device/upnp_class.py", line 133, in __getattr__
    raise AttributeError(item)
AttributeError: volume
2019-02-07 03:31:03 ERROR (MainThread) [homeassistant.core] Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 239, in async_update_ha_state
    attr = self.state_attributes or {}
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/media_player/__init__.py", line 787, in state_attributes
    in ATTR_TO_PROPERTY if getattr(self, attr) is not None
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/media_player/__init__.py", line 787, in <dictcomp>
    in ATTR_TO_PROPERTY if getattr(self, attr) is not None
  File "/config/custom_components/media_player/samsungtv.py", line 305, in volume_level
    volume = self.get_remote().volume
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/UPNP_Device/upnp_class.py", line 133, in __getattr__
    raise AttributeError(item)
AttributeError: volume
arsaboo commented 5 years ago

Just for kicks, I removed all the volume and source related code from the component and it kinda works now. The state is still messed up, but no errors.

kdschlosser commented 5 years ago

do me a favor. do not modify this one. try it first. so you ar going to have to return the changes you made back to the way they were.

I updated the develop branch of samsungctl. give it a go and see how you make out.

arsaboo commented 5 years ago

Slightly different error this time, but still not working.

2019-02-07 13:08:30 ERROR (MainThread) [homeassistant.core] Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/homeassistant/helpers/entity_platform.py", line 352, in _async_add_entity
    await entity.async_update_ha_state()
  File "/usr/local/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 239, in async_update_ha_state
    attr = self.state_attributes or {}
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/media_player/__init__.py", line 787, in state_attributes
    in ATTR_TO_PROPERTY if getattr(self, attr) is not None
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/media_player/__init__.py", line 787, in <dictcomp>
    in ATTR_TO_PROPERTY if getattr(self, attr) is not None
  File "/config/custom_components/media_player/samsungtv.py", line 305, in volume_level
    volume = self.get_remote().volume
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/UPNP_Device/upnp_class.py", line 133, in __getattr__
    raise AttributeError(item)
AttributeError: volume

Wondering if there is no value for volume = self.get_remote().volume when using a soundbar/receiver. Maybe guard against that.

ThaStealth commented 5 years ago

Okay, just a minor thing and not important for now at all, but I was setting everything up and I noticed the error below, it is just something to beautify the code:

The TV is not connected to the network (not at home now so can't investigate why it isn't), but I was doing some preparation work for later today

So I got this error (which is correct):

Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/samsungctl/remote_legacy.py", line 84, in open
    self.sock.connect((self.config.host, self.config.port))
OSError: [Errno 113] No route to host

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/src/app/homeassistant/helpers/entity_platform.py", line 352, in _async_add_entity
    await entity.async_update_ha_state()
  File "/usr/src/app/homeassistant/helpers/entity.py", line 232, in async_update_ha_state
    state = self.state
  File "/usr/src/app/homeassistant/components/media_player/samsungtv.py", line 224, in state
    power = self.get_remote().power
  File "/usr/src/app/homeassistant/components/media_player/samsungtv.py", line 178, in get_remote
    self._remote.open()
  File "/usr/local/lib/python3.6/site-packages/samsungctl/utils.py", line 24, in wrapper
    return func(*args, **kwargs)
  File "/usr/local/lib/python3.6/site-packages/samsungctl/remote_legacy.py", line 87, in open
    raise RuntimeError('Unable to pair with TV.. Is the TV on?!?')
RuntimeError: Unable to pair with TV.. Is the TV on?!?

But then it continues and gives this error every ~10 sec:

Update for media_player.living_room_tv fails
Traceback (most recent call last):
  File "/usr/src/app/homeassistant/helpers/entity.py", line 221, in async_update_ha_state
    await self.async_device_update()
  File "/usr/src/app/homeassistant/helpers/entity.py", line 349, in async_device_update
    await self.hass.async_add_executor_job(self.update)
  File "/usr/local/lib/python3.6/concurrent/futures/thread.py", line 56, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/src/app/homeassistant/components/media_player/samsungtv.py", line 167, in update
    power = self.get_remote().power
  File "/usr/local/lib/python3.6/site-packages/samsungctl/upnp/UPNP_Device/upnp_class.py", line 131, in __getattr__
    return self.__class__.__dict__[item].fget(self)
  File "/usr/local/lib/python3.6/site-packages/samsungctl/utils.py", line 44, in wrapper
    result = func(*args, **kwargs)
  File "/usr/local/lib/python3.6/site-packages/samsungctl/remote_legacy.py", line 45, in power
    self.sock.self.sock.setblocking(0)
AttributeError: 'socket' object has no attribute 'self'

Perhaps you can enhance to code to check if the socket is actually open before trying to do something with it?

arsaboo commented 5 years ago

@kdschlosser Also, see https://github.com/home-assistant/home-assistant/issues/4210 for the I/O issues. We are doing a lot of I/O inside properties, which should be avoided.

arsaboo commented 5 years ago

So, I modified one of the properties to:

    @property
    def volume_level(self):
        """Volume level of the media player scalar volume. 0.0-1.0."""
        try:
            volume = self.get_remote().volume
            if volume is None:
                volume = 0.0
            else:
                volume /= 100.0
        except: # we should catch the specific error
            volume = 0.0
            _LOGGER.error('No Volume returned')
        return volume

and now I get a lot of No Volume returned in the logs but not the same UPNP error. The error that I get now is about a different property (one that was not fixed):

Log Details (ERROR)
Thu Feb 07 2019 10:08:03 GMT-0500 (Eastern Standard Time)
Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/homeassistant/helpers/entity.py", line 239, in async_update_ha_state
    attr = self.state_attributes or {}
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/media_player/__init__.py", line 787, in state_attributes
    in ATTR_TO_PROPERTY if getattr(self, attr) is not None
  File "/usr/local/lib/python3.6/site-packages/homeassistant/components/media_player/__init__.py", line 787, in <dictcomp>
    in ATTR_TO_PROPERTY if getattr(self, attr) is not None
  File "/config/custom_components/media_player/samsungtv.py", line 327, in is_volume_muted
    return False if not self.get_remote().mute else True
  File "/config/deps/lib/python3.6/site-packages/samsungctl/upnp/UPNP_Device/upnp_class.py", line 133, in __getattr__
    raise AttributeError(item)
AttributeError: mute

This basically tells me that error handling has to be improved. We obviously should catch the specific error (instead of using a simple except:).

The state still does not update. The entity kept reporting off even when the TV was on

arsaboo commented 5 years ago

So, I tried to update the component code to remove the I/O from properties and here is a very crude attempt - code. It needs to be massively cleaned up and state update have to be fixed, but it is kind of working.

On the state, it looks like self.get_remote().power does not report the right state. In my case, it kept reporting False even when the TV was on.

kdschlosser commented 5 years ago

this error is a fat finger and is for older samsung TV's it is easily repairable.

Update for media_player.living_room_tv fails
Traceback (most recent call last):
  File "/usr/src/app/homeassistant/helpers/entity.py", line 221, in async_update_ha_state
    await self.async_device_update()
  File "/usr/src/app/homeassistant/helpers/entity.py", line 349, in async_device_update
    await self.hass.async_add_executor_job(self.update)
  File "/usr/local/lib/python3.6/concurrent/futures/thread.py", line 56, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/src/app/homeassistant/components/media_player/samsungtv.py", line 167, in update
    power = self.get_remote().power
  File "/usr/local/lib/python3.6/site-packages/samsungctl/upnp/UPNP_Device/upnp_class.py", line 131, in __getattr__
    return self.__class__.__dict__[item].fget(self)
  File "/usr/local/lib/python3.6/site-packages/samsungctl/utils.py", line 44, in wrapper
    result = func(*args, **kwargs)
  File "/usr/local/lib/python3.6/site-packages/samsungctl/remote_legacy.py", line 45, in power
    self.sock.self.sock.setblocking(0)
AttributeError: 'socket' object has no attribute 'self'

is a fat finger. a goof.

self.sock.self.sock.setblocking(0). see that line.. it is supposed to be self.sock.setblocking(0)

It took me all last night to code up a UDP SSDP discovery "server" and set up a flask server to handle the upnp requests and also handle returning the UPNP xervice xml data.

Thatss great that you are trying to do exception catching.. dong that is not the solution. there should be no error in the first place. it is supposed to work like this. if the TV is on. it will return the value. if it is off it is going to return None. there is no error that is supposed to be generated.

checking for power simply checks to see if the socket is None. because of how the websocket works and because different responses can come in for different things taking place. there is a thread that runs continually receiving data from the socket. if there is a specific piece of data requested then a call back is placed and when that receive thread gets a response it calls the callback. if receiving data fails and an error gets generated. it is caught. the socket gets closed and the thread then shifts to trying to reconnect it. this is where the power detection comes from. if the websocket is connected then the TV is on. if it is disconnected. the power is off. really simple.

Now this library works outside of Home Assistant. there are people using it. I do not know as to why it has issues in Home Assistant.

@arsaboo stated something which I am still not able to put a finger on.

the library works when you first install it. then after you restart Home Assistant it no longer functions. so now my question on that is. what is Home Assistant doing that can cause this kind of a problem? There is nothing in the code for samsungctl that is going to make it's behavior change when you restart it.

we need to stop trying to baind-aid the problem and we need to not band-aide the problem with try except clauses. w need to fnid the root cause of thee problem and repair it properly

Now python does have fantastic traceback information. and it will usually tell you spot on where the problem is. So long as everything is hard coded and nothing is dynamic made. Well the upnp end of things is all dynamic. so the error you are seeing is not where the problem is. it is up stream from there.

remove anything that is not needed and see if the error occurs. This is going to include Home Assistant. we have some strange behavior when first installed vs after restart. So what needs to be done form here is run samsungctl all on it's own. without home assistant.

this is stated by a user that is using samsungctl without HomeAssistant

At least now I've got mute status and the ability to get and set the volume level instead of stepping up and down. These alone are significant, so thank you very much for the time and effort you have put into improving the samsungctl library. I'll be keeping an eye on your repo in hopes that a way to select source is found without requiring the MainTVAgent2 service.

as you can see the user clearly states they are able to get the mute state and what the current volume is as well as set the volume. So those specific things do in fact work. when the program is not being run inside Home Assistant. so now we shift back to the question that is being raised with the library working when first installed and then not after a restart of Home Assistant. so now the real question at hand is what is Home Assistant doing that is changing how samsungctl runs?? This is do not have the answer to. maybe someone that works on Home Assistant might be able to help and to shed a little light on it for us. I know nothing about the internals of Home Assistant. I am trying to help to get this running for the users of Home Assistant. I need help doing this. I know how samsungctl works. I need help from someone that has a really in depth knowledge of the code in Home Assistant. Home Assistant seems to have quite a bit of "voodo magic" code in it. there are functions that I cannot get a location for what calls them. I cant get a location on what even starts the samsungtv portion of the code. So someone that knowns Home Assistant is needed to help explain some of the details to me. If there was a way to be able to plug the unit tests for the library into home assistant and produce a replica of how things should be running would be a great help. I do not know if that is possible.

So my suggestion to you guys is this. test samsungctl outside of Home Assistant if you are getting the same errors then let me know. I have statements telling me that samsungctl runs fine. so we need to find out why the 2 are bumping heads.

kdschlosser commented 5 years ago

Wee also need to remove one more thing form the equation. which is that pre built version of HomeAssistant.

Install Python 3.6 then run pip install homeassistant

run it that way. there could be some kind of a difference between the 2 that is causing a problem.

arsaboo commented 5 years ago

I am sure it is working outside Homeassistant. What we now need to figure out is how to get it working inside HA. My guess is the problem lies with the component and not samsungctl library.

I am not sure why a simple statement like power = self.get_remote().power does not return the right value (it mostly correctly reports False when the TV is off, but does not report True reliably when the TV is on).

arsaboo commented 5 years ago

Ok....so here is a slightly modified version of @jnimmo code that is working for me (state reporting is still iffy) https://paste.ubuntu.com/p/C6MfxT7JNz/

firstof9 commented 5 years ago

I'll also test your code out as I have a UN50J5200 TV that I'd like to get working with HA. I'll report back soon as I can.

firstof9 commented 5 years ago

I am using a non-Hass.IO install.

Config:

  - platform: samsungtv
    host: 192.168.1.46
    method: encrypted

This is what I get:

2019-02-07 16:44:01 WARNING (MainThread) [homeassistant.loader] You are using a custom component for media_player.samsungtv which has not been tested by Home Assistant. This component might cause stability problems, be sure to disable it if you do experience issues with Home Assistant.
2019-02-07 16:44:05 ERROR (Thread-2) [samsungctl] Unable to acquire TV's mac address
...
2019-02-07 17:28:38 INFO (Thread-10) [custom_components.media_player.samsungtv] Samsung TV 192.168.1.46 added as 'Samsung TV Remote'
2019-02-07 16:44:28 ERROR (MainThread) [samsungctl] Unable to power on the TV, check network connectivity
...
2019-02-07 16:44:29 ERROR (MainThread) [homeassistant.core] Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/usr/lib/python3.5/asyncio/tasks.py", line 239, in _step
    result = coro.send(None)
  File "/srv/homeassistant/homeassistant_venv/lib/python3.5/site-packages/homeassistant/helpers/entity_platform.py", line 352, in _async_add_entity
    await entity.async_update_ha_state()
  File "/srv/homeassistant/homeassistant_venv/lib/python3.5/site-packages/homeassistant/helpers/entity.py", line 232, in async_update_ha_state
    state = self.state
  File "/home/homeassistant/.homeassistant/custom_components/media_player/samsungtv.py", line 224, in state
    power = self.get_remote().power
  File "/home/homeassistant/.homeassistant/custom_components/media_player/samsungtv.py", line 178, in get_remote
    self._remote.open()
  File "/srv/homeassistant/homeassistant_venv/lib/python3.5/site-packages/samsungctl/utils.py", line 44, in wrapper
    result = func(*args, **kwargs)
  File "/srv/homeassistant/homeassistant_venv/lib/python3.5/site-packages/samsungctl/remote_encrypted/__init__.py", line 159, in open
    raise RuntimeError('Unable to pair with TV.')
RuntimeError: Unable to pair with TV.

I had the TV already turned on when I started Home Assistant.

kdschlosser commented 5 years ago

what is causing thee issue I think is the pairing process and Home Assistant. this is because the TV displays a pin and it has to be entered into the computer running the program.

Home assistant is not geared to deal with having to have user input like that. II am not sure how to fix that issue.

i believe that the 5200 series TV's are not capable of being controlled remotely. you can confirm this by using the smartview application. the application will give you DLNA ability and media control. but you will not be able to do things like change the channel. or bring up the setup menu on the TV

arsaboo commented 5 years ago

I don't see any PIN on my TV (UN75NU8000). I just see a prompt to allow access and I have done that. So, should not be a pairing issue here. Although not perfect, I am able to communicate with the TV (turn on/off).

I am also not able to use my SmartView app with my TV. I have heard a few other folks confirm this.

kdschlosser commented 5 years ago

I am talking about the encrypted method TV's the H and J series TV's.

I believe there is still going to be an issue with the SSL websocket baseed TV's. because of that get_remote method.

I say this because the pairing process for the SSL TV's can take up to 30 seconds. this is going to cause a problem because the "remote" can be created from any of the methods or properties that make a call to getremote. I believe the startup of the class has a timeout of 60 seconds we may want to move the initialization of the remote to the __init_\ method.

I also updated the develop branch for samsungctl. I reorganized the whole UPNP process. and got the unittests working for some of the upnp functions. everything is testing out with no errors.

remove samsungctl from your Home Assistant installation revert back to the code in the first post and lets see if it works. I am feeling pretty good about how it is set up. So i am hoping that all goes well.

I also added some "smoothing" code for the websocket connection. so if there is some kind of an error that happens but the connection is not terminated. it will absorb this and continue doing what it needs to do. if the error happens 3 times in a row then it will close the connection and try and reopen it.

kdschlosser commented 5 years ago

@arsaboo

you are unable to run SmartView because you have an SSL websocket connection. the application does not support it. but for the person with the 2015 TV. it does support it.

kdschlosser commented 5 years ago

@firstof9

There is something off with your log.

019-02-07 16:44:01 WARNING (MainThread) [homeassistant.loader] You are using a custom component for media_player.samsungtv which has not been tested by Home Assistant. This component might cause stability problems, be sure to disable it if you do experience issues with Home Assistant.
  ----->   2019-02-07 16:44:05 ERROR (Thread-2) [samsungctl] Unable to acquire TV's mac address
...
 ------>  2019-02-07 17:28:38 INFO (Thread-10) [custom_components.media_player.samsungtv] Samsung TV 192.168.1.46 added as 'Samsung TV Remote'
-------> 2019-02-07 16:44:28 ERROR (MainThread) [samsungctl] Unable to power on the TV, check network connectivity
...
2019-02-07 16:44:29 ERROR (MainThread) [homeassistant.core] Error doing job: Task exception was never retrieved
Traceback (most recent call last):
  File "/usr/lib/python3.5/asyncio/tasks.py", line 239, in _step
    result = coro.send(None)
  File "/srv/homeassistant/homeassistant_venv/lib/python3.5/site-packages/homeassistant/helpers/entity_platform.py", line 352, in _async_add_entity
    await entity.async_update_ha_state()
  File "/srv/homeassistant/homeassistant_venv/lib/python3.5/site-packages/homeassistant/helpers/entity.py", line 232, in async_update_ha_state
    state = self.state
  File "/home/homeassistant/.homeassistant/custom_components/media_player/samsungtv.py", line 224, in state
    power = self.get_remote().power
  File "/home/homeassistant/.homeassistant/custom_components/media_player/samsungtv.py", line 178, in get_remote
    self._remote.open()
  File "/srv/homeassistant/homeassistant_venv/lib/python3.5/site-packages/samsungctl/utils.py", line 44, in wrapper
    result = func(*args, **kwargs)
  File "/srv/homeassistant/homeassistant_venv/lib/python3.5/site-packages/samsungctl/remote_encrypted/__init__.py", line 159, in open
    raise RuntimeError('Unable to pair with TV.')
RuntimeError: Unable to pair with TV.

I marked the lines that are screwy. Look at the date/time stamps.. specifically the time.

Now I would say that the cause could be DST. but it is not an hour off. it is 44 minutes off and even if for some reason it's 44 minutes off, the seconds should line up properly. the powering on process of the TV takes 20 seconds in order for it to timeout and for you to get the "Unable to power on the TV, check network connectivity" message. the time stamp for the message above which would be the samsungtv component getting started shows the seconds being 38 and then what would be 10 seconds before the the message about the samsungtv component getting started, so that does not match the 20 seconds it takes from the start of the power on process to the time the error gets printed. Plus the fact that you have a message telling you that it is unable to locate the mac address. which the power process would not even run if that is the case.

here is the code for the power on process

            # here is the checking for the mac address
            if self.mac_address:
                # this is the stop counter  
                count = 0
                wake_on_lan.send_wol(self.mac_address)
                event.wait(1.0)
                # this is the loop to keep sending the wol packet it will only exit if the counter has reached 20 or if the TV is powered on
                while not self.power and count < 20:
                    if not self._running:
                        try:
                            self.open()
                        except:
                            pass
                    wake_on_lan.send_wol(self.mac_address)
                   # this is a wait of 1 second between loops. it will take at most 20 seconds before the loop exits
                    event.wait(1.0)

                    # increment the counter by 1 each loop
                    count += 1
                # after the loop exits check the counter. if it is 20 then print the message
                if count == 20:
                    logger.error(
                        'Unable to power on the TV, '
                        'check network connectivity'
                    )
            else:
                # this gets printed out if there is no mac address
                logging.error('Unable to get TV\'s mac address')

so as you can see the message in that log do not match what the code does not even close.

so I am not sure what is going on with your setup. my suggestion would be to uninstall everything and start from scratch. I do not even know where to begin to even try to help. That is if you are able to control the TV from SmartView.

kdschlosser commented 5 years ago

ok so here we go again.

I updated the code above. moves all statuses into the update method. I am thinking this method has a greater timeout then other properties/methods.

I also did away with the whole get_remote method. because of the longer connection times associated with the SSL potentially requiring a confirmation on the TV i did not want this happening in a call that has a small timeout.

I also added a pin input mechanism for those of you with the encrypted websocket connections H and J (2014, 2015) TV's. I had to change the samsungctl library in order to accept the new mechanism for the pin entry. so i did a version bump on the develop branch to handle this. this will ensure that if using the code in the first post the right samsungctl will be installed that can support it.

I also changed the source list based on some code that @arsaboo had posted. slightly modified to provide all of the source keys as well as the key to iterate over the sources I did this because some TV's do not support the discrete source key codes.

I also changed the source list for those TV's that do support querying the TV for the current source the list will be empty if the TV is off and populated when the TV is on. if the TV does not support being queried the displayed source will be "Unknown"

I also hold storage of the current source (if supported) as well as thee current volume even when the TV is off. these things will not change when you turn the TV on. so you can see if the TV will have a blasting volume before turning the thing on LOL.

if someone wants to give it a test run and see if it works that would be great.

I have hammered out quite a few issues this past day. I am going to connect my 2 Samsung TV's in the next day or 2. one is 2011 (the last year they made the TV's with a full API. The last year they also made that is considered reliable) and the other is a 2014 (it's a pretty dumb TV so dunno if it will support remote control)

ThaStealth commented 5 years ago

I got this exception:

Error while setting up platform samsungtv
Traceback (most recent call last):
  File "/usr/src/app/homeassistant/helpers/entity_platform.py", line 128, 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 "/usr/src/app/homeassistant/components/media_player/samsungtv.py", line 123, in setup_platform
    add_entities([SamsungTVDevice(config, uuid)])
  File "/usr/src/app/homeassistant/components/media_player/samsungtv.py", line 204, in __init__
    self._name = self._config.name
AttributeError: 'collections.OrderedDict' object has no attribute 'name'

Configuration.yaml:

media_player:
  - platform: samsungtv
    host: <IP>
    name: Living Room TV
    description: description of TV
kdschlosser commented 5 years ago

I updated the code in the first post to fix that error. sorry about that forgot to change the config that was being passed to the constructor of the component class

arsaboo commented 5 years ago

@kdschlosser This is SIGNIFICANTLY better. No errors in the logs. Turning on works fine, but it reports off even when the TV is on or a few times the other way around.

gabrielcolceriu commented 5 years ago

I got this:

2019-02-08 15:16:56 ERROR (SyncWorker_3) [samsungctl] Unable to acquire TV's mac address
2019-02-08 15:16:56 ERROR (MainThread) [homeassistant.components.media_player] 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 128, 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 121, in setup_platform
    request_configuration(samsung_config, hass, uuid, add_entities)
  File "/config/custom_components/media_player/samsungtv.py", line 136, in request_configuration
    configurator = hass.components.configurator
AttributeError: 'NoneType' object has no attribute 'components'

configuration.yaml

   platform: samsungtv
   host: !secret master_bedroom_tv_ip
   method: encrypted

Model: UE32J5200

sermayoral commented 5 years ago

Thanks @kdschlosser for your work. I have a UE50H6400 Samsung TV of 2014 year and i want to try your code ASAP :-).

Once again, thanks for your effort.

arsaboo commented 5 years ago

So, just to debug, I turned on the TV manually using the remote and waited for the state to update in HA. Even after 5 minutes, the state in HA stayed off.

phairplay commented 5 years ago

hi, im running hassio and have a Samsung UE55MU6120

my config is:

  - platform: samsungtv
    host: mytvip
    name: Samsung TV

edit2: it doesn't like it if the tv is off when HA is restarted. i kept getting this error. 2019-02-08 16:25:45 WARNING (MainThread) [homeassistant.components.media_player] Updating samsungtv media_player took longer than the scheduled update interval 0:00:10

manually turning it on it will appear on in HA roughly 5secs later, yet it never changes when manually turned off, still shows as on.

remotely if you turn it on after 10secs it appear off in HA, same with turning it off it'll appear on in HA.

its as if it think its on/off but doesn't really check

the volume works every time without fail.

i can't change source

hope this help, as i really want to have my tv hook up to home assistant.

kdschlosser commented 5 years ago

OK so it is check for power. just have to figure out why the state is not changing properly. if it thinks the TV is on there should be a list of sources you can select from.. can you tell me what is in that list?

if the list looks like this

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

have you tried each and every item in the list?

what works with the volume? using the volume up and down?? or the direct input of the volume? have you tried the mute?