I changed the directory according to the changes in the update description to: /custom_component/samsungtv_custom/media_player.py. (simply renamed the old samsungtv_custom.py to media_player.py)
Changed is only Line 11 → here block 9 - 16
import voluptuous as vol
import homeassistant.components.samsungtv.media_player as stv
from homeassistant.components.media_player import (DOMAIN)
from homeassistant.const import (ATTR_ENTITY_ID)
import homeassistant.helpers.config_validation as cv
as i mentioned the folder structure changed, so you have to rename both the folder where the file is in to samsungtv_custom and the file itself to media_player.py.
my media_player.py in /custom_components/samsungtv_custom/ looks like the original only line changed is 11:
"""
Custom component to allow overriding or adding features to the samsungtv media_player component.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.samsungtv/
"""
import logging
import voluptuous as vol
import homeassistant.components.samsungtv.media_player as stv
from homeassistant.components.media_player import (DOMAIN)
from homeassistant.const import (ATTR_ENTITY_ID)
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
SAMSUNG_TV_CUSTOM_DATA = 'samsungtv_custom'
SERVICE_KEY = 'send_key'
# Service call validation schemas
ATTR_KEY = 'key_code'
SAMSUNG_TV_CUSTOM_KEY_SCHEMA = vol.Schema({
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_KEY): cv.string,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
if SAMSUNG_TV_CUSTOM_DATA not in hass.data:
hass.data[SAMSUNG_TV_CUSTOM_DATA] = []
# Use this to get my hands on the SamsungTVDevices that get added
def add_devices_custom(devices):
add_devices(devices)
for device in devices:
hass.data[SAMSUNG_TV_CUSTOM_DATA].append(device)
# pass in my add_devices_custom function
stv.setup_platform(hass, config, add_devices_custom, discovery_info)
_LOGGER.debug("hass.data[SAMSUNG_TV_CUSTOM_DATA] = %s.", hass.data[SAMSUNG_TV_CUSTOM_DATA])
def service_handle(service):
_LOGGER.debug("service_handle called for %s with %s", service.service, service.data)
entity_ids = service.data.get('entity_id')
devices = hass.data[SAMSUNG_TV_CUSTOM_DATA]
for device in devices:
if device.entity_id in entity_ids:
if service.service == SERVICE_KEY:
device.send_key(service.data.get(ATTR_KEY))
device.schedule_update_ha_state(True)
hass.services.register(
DOMAIN, SERVICE_KEY, service_handle,
schema=SAMSUNG_TV_CUSTOM_KEY_SCHEMA)
I already did that, so I hope I’m on the way.
Hmm your file doesn’t seem to be that long. I just checked the original file and that is also way longer:
Here is mine:
"""
Support for interface with an Samsung TV.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.samsungtv/
"""
import logging
import socket
from bs4 import BeautifulSoup
import homeassistant.components.samsungtv.media_player as stv
#import voluptuous as vol
from homeassistant.components.media_player import (
SUPPORT_SELECT_SOURCE, SUPPORT_TURN_OFF, SUPPORT_VOLUME_MUTE, SUPPORT_VOLUME_SET,
SUPPORT_VOLUME_STEP, MediaPlayerDevice, PLATFORM_SCHEMA, SUPPORT_TURN_ON)
from homeassistant.const import (
CONF_HOST, CONF_NAME, STATE_OFF, STATE_ON, STATE_UNKNOWN, CONF_PORT,
CONF_MAC)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['wakeonlan==0.2.2', 'beautifulsoup4==4.6.0']
_LOGGER = logging.getLogger(__name__)
CONF_TIMEOUT = 'timeout'
DEFAULT_NAME = 'Samsung TV Remote'
DEFAULT_PORT = 55000
DEFAULT_TIMEOUT = 0
KNOWN_DEVICES_KEY = 'samsungtv_known_devices'
SUPPORT_SAMSUNGTV = SUPPORT_SELECT_SOURCE | SUPPORT_VOLUME_SET | \
SUPPORT_VOLUME_STEP | SUPPORT_VOLUME_MUTE | SUPPORT_TURN_OFF
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
})
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Samsung TV platform."""
known_devices = hass.data.get(KNOWN_DEVICES_KEY)
if known_devices is None:
known_devices = set()
hass.data[KNOWN_DEVICES_KEY] = known_devices
# Is this a manual configuration?
if config.get(CONF_HOST) is not None:
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
name = config.get(CONF_NAME)
mac = config.get(CONF_MAC)
timeout = config.get(CONF_TIMEOUT)
elif discovery_info is not None:
tv_name = discovery_info.get('name')
model = discovery_info.get('model_name')
host = discovery_info.get('host')
name = "{} ({})".format(tv_name, model)
port = DEFAULT_PORT
timeout = DEFAULT_TIMEOUT
mac = None
else:
_LOGGER.warning("Cannot determine device")
return
# Only add a device once, so discovered devices do not override manual
# config.
ip_addr = socket.gethostbyname(host)
if ip_addr not in known_devices:
known_devices.add(ip_addr)
add_devices([SamsungTVDevice(host, port, name, timeout, mac)])
_LOGGER.info("Samsung TV %s:%d added as '%s'", host, port, name)
else:
_LOGGER.info("Ignoring duplicate Samsung TV %s:%d", host, port)
class SamsungTVDevice(MediaPlayerDevice):
"""Representation of a Samsung TV."""
def __init__(self, host, port, name, timeout, mac):
"""Initialize the Samsung device."""
from wakeonlan import wol
# Save a reference to the imported classes
self._name = name
self._mac = mac
self._wol = wol
# Assume that the TV is not muted
self._muted = False
self._volume = 0
self._state = STATE_OFF
# Generate a configuration for the Samsung library
self._config = {
'name': 'HomeAssistant',
'description': name,
'id': 'ha.component.samsung',
'port': 7676,
'host': host,
'timeout': timeout,
}
self._selected_source = ''
self._source_names = self.SendSOAP('smp_4_', 'urn:samsung.com:service:MainTVAgent2:1', 'GetSourceList', '', 'sourcetype')
if self._source_names:
del self._source_names[0]
self._source_ids = self.SendSOAP('smp_4_', 'urn:samsung.com:service:MainTVAgent2:1', 'GetSourceList', '', 'id')
self._sources = dict(zip(self._source_names, self._source_ids))
else:
self._source_names = {}
self._source_ids = {}
self._sources = {}
def update(self):
"""Retrieve the latest data."""
currentvolume = self.SendSOAP('smp_17_', 'urn:schemas-upnp-org:service:RenderingControl:1', 'GetVolume', '<InstanceID>0</InstanceID><Channel>Master</Channel>','currentvolume')
if currentvolume:
self._volume = int(currentvolume) / 100
currentmute = self.SendSOAP('smp_17_', 'urn:schemas-upnp-org:service:RenderingControl:1', 'GetMute', '<InstanceID>0</InstanceID><Channel>Master</Channel>','currentmute')
if currentmute == '1':
self._muted = True
else:
self._muted = False
source = self.SendSOAP('smp_4_', 'urn:samsung.com:service:MainTVAgent2:1', 'GetCurrentExternalSource', '','currentexternalsource')
self._selected_source = source
self._state = STATE_ON
return True
else:
self._state = STATE_OFF
return False
def SendSOAP(self,path,urn,service,body,XMLTag):
CRLF = "\r\n"
xmlBody = "";
xmlBody += '<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">'
xmlBody += '<s:Body>'
xmlBody += '<u:{service} xmlns:u="{urn}">{body}</u:{service}>'
xmlBody += '</s:Body>'
xmlBody += '</s:Envelope>'
xmlBody = xmlBody.format(urn = urn, service = service, body = body)
soapRequest = "POST /{path} HTTP/1.0%s" % (CRLF)
soapRequest += "HOST: {host}:{port}%s" % (CRLF)
soapRequest += "CONTENT-TYPE: text/xml;charset=\"utf-8\"%s" % (CRLF)
soapRequest += "SOAPACTION: \"{urn}#{service}\"%s" % (CRLF)
soapRequest += "%s" % (CRLF)
soapRequest += "{xml}%s" % (CRLF)
soapRequest = soapRequest.format(host = self._config['host'], port = self._config['port'], xml = xmlBody, path = path, urn = urn, service = service)
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.settimeout(0.5)
client.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
dataBuffer = ''
response_xml = ''
_LOGGER.info("Samsung TV sending: %s", soapRequest)
try:
client.connect( (self._config['host'], self._config['port']) )
client.send(bytes(soapRequest, 'utf-8'))
while True:
dataBuffer = client.recv(4096)
if not dataBuffer: break
response_xml += str(dataBuffer)
except socket.error as e:
return
response_xml = bytes(response_xml, 'utf-8')
response_xml = response_xml.decode(encoding="utf-8")
response_xml = response_xml.replace("<","<")
response_xml = response_xml.replace(">",">")
response_xml = response_xml.replace(""","\"")
_LOGGER.info("Samsung TV received: %s", response_xml)
if XMLTag:
soup = BeautifulSoup(str(response_xml), 'html.parser')
xmlValues = soup.find_all(XMLTag)
xmlValues_names = [xmlValue.string for xmlValue in xmlValues]
if len(xmlValues_names)== 1:
return xmlValues_names[0]
else:
return xmlValues_names
else:
return response_xml[response_xml.find('<s:Envelope'):]
@property
def name(self):
"""Return the name of the device."""
return self._name
@property
def state(self):
"""Return the state of the device."""
return self._state
@property
def volume_level(self):
"""Volume level of the media player (0..1)."""
return self._volume
@property
def is_volume_muted(self):
"""Boolean if volume is currently muted."""
return self._muted
@property
def source(self):
"""Return the current input source."""
return self._selected_source
@property
def source_list(self):
"""List of available input sources."""
return self._source_names
@property
def media_title(self):
"""Title of current playing media."""
return self._selected_source
@property
def supported_features(self):
"""Flag media player features that are supported."""
if self._mac:
return SUPPORT_SAMSUNGTV | SUPPORT_TURN_ON
return SUPPORT_SAMSUNGTV
def select_source(self, source):
"""Select input source."""
self.SendSOAP('smp_4_', 'urn:samsung.com:service:MainTVAgent2:1', 'SetMainTVSource', '<Source>'+source+'</Source><ID>' + self._sources[source] + '</ID><UiID>0</UiID>','')
def turn_off(self):
"""Turn off media player."""
def set_volume_level(self, volume):
"""Volume up the media player."""
volset = str(round(volume * 100))
self.SendSOAP('smp_17_', 'urn:schemas-upnp-org:service:RenderingControl:1', 'SetVolume', '<InstanceID>0</InstanceID><DesiredVolume>' + volset + '</DesiredVolume><Channel>Master</Channel>','')
def volume_up(self):
"""Volume up the media player."""
volume = self._volume + 0.01
self.set_volume_level(volume)
def volume_down(self):
"""Volume down media player."""
volume = self._volume - 0.01
self.set_volume_level(volume)
def mute_volume(self, mute):
"""Send mute command."""
if self._muted == True:
doMute = '0'
else:
doMute = '1'
self.SendSOAP('smp_17_', 'urn:schemas-upnp-org:service:RenderingControl:1', 'SetMute', '<InstanceID>0</InstanceID><DesiredMute>' + doMute + '</DesiredMute><Channel>Master</Channel>','')
def turn_on(self):
"""Turn the media player on."""
if self._mac:
self._wol.send_magic_packet(self._mac)
platform: samsungtv_custom
name: Samsung TV
host: “192.168.178.26”
mac: “D0:66:XX:XX:XX:XX”
i also struggled hard to get this thing working, im hoping for a better integration at all, also what doesn’t work is jumping to a specified hdmi channel like hdmi1 or hdmi2 only switching through all works… but i think with more time there would be a way over hdmi-cec to solve this issue.
I hope you are right. I will try later this afternoon. Previously I was on openhab and the Samsung Integration worked very well for me, also the part where you can switch to a specific Hdmi channel.
i really don’t have a clue, why it isn’t working - also get to my solution by accident, thought by myself why don’t change the line like i changed the folder logic? and that does the trick - never have done any custom components or something bevor, only wanted to share this so the creator of the component can integrate this fix…
so again, my working solution is posted in this thread by Drywcyfyr on post 10. --> can’t get links to work properly and can’t repost the code due to the forum rules.
But if I try to change the platform to: samsungtv_custom
Then HA won’t accept it.
I did change the folder structure so I now have:
www/custom_components/samsungtv_custom/media_player.py
EDIT: Seems that some of the caracters got messes up when copy pasting, but now another error shows up:
File “/config/custom_components/samsungtv_custom/media_player.py”, line 41, in <module> stv.setup_platform(hass, config, add_devices_custom, discovery_info) NameError: name ‘hass’ is not defined
I’m having the exact same issue on my MU6070. Created the folder structure under the /config folder but when I rename the component like below, the check config just spins and HA wont restart.
media_player:
platform: samsungtv_custom
host: 192.168.1.3
port: 8001
name: Living Room TV
mac: REDACTED
I was able to get it to power on but not power off or do anything else. When i use the standard component samsungtv I get an Invalid OPCODE error which seems to have changed recently. I’m at the latest vers of HA at 91.3
I’ve gotten most of everything else in HA to work but really struggling having it control this TV. Any suggestions or help would be much appreciated.
I’ve only been able to turn it on with the WoL. I was able to setup SmartThings integration so I can now sometimes turn it off as well but its a separate switch. It also has sensors for Volume, Channel, and Source, but they only update if you go into the app and refresh the settings there. Then it updates in HA.
I just put in an order for a Broadlink RM Mini3 that Im going to install in my livingroom to take care of this. its pretty crappy that Samsung nerfed the webhooks with the firmware update. But im tired of waiting for them to make any changes to get it back. Plus this way I can control any other IR based devices as well ultimately via voice through Google Mini’s.
Love to hear about anyone else’s setup or if you were able to get true TCP/IP control of your Samsung TV through the samsungctl component. So far all I get is " Invalid OpCode" or nothing at all.