[WORKAROUND] LG TV Remote

Continuing the discussion from [WORKAROUND] LG TV Remote:

Are you in the US?

I have an LG LW5600 and from what I’ve read, there is an alternate firmware which only applies to European models.

http://openlgtv.org.ru/wiki/index.php/Achievements

I’ve tried to telnet to my TV on port 1900 as shown in the script but the TV doesn’t respond.

Wow. Okay will do it again.

In configuration.yaml add

#################################################################
## Customize
#################################################################
  customize:
    script.powerofftv:
      hidden: true
    script.powerontv:
      hidden: true

**

Shell commands calling tv.py for LG TV

**

Exposes service shell_command.wol_bd_br

#################################################################
## Shell commands
#################################################################
shell_command:
 change_to_tv: python3 /home/pi/.homeassistant/tv.py 15
 power_off_tv: python3 /home/pi/.homeassistant/tv.py 8
 wol_bd_br: wakeonlan EE:BB:BB:EE:00:FF

**

State TV

**

input_boolean:
  television:
    name: Bedroom
    initial: off
    icon: mdi:television

group:
  Television:
   - input_boolean.television

**

Automations Television

**

#################################################################
## Automations
#################################################################
automation:
- alias: 'turn on tv'
  trigger:
    platform: state
    entity_id: input_boolean.television
    state: 'on'
  action:
    service: script.powerontv

- alias: 'turn off tv'
  trigger:
    platform: state
    entity_id: input_boolean.television
    state: 'off'
  action:
    service: script.powerofftv

**

Scripts

**

#################################################################
## Scripts
#################################################################
script:
  # Turns on the TV and then changes input 20 seconds later
  powerontv:
    alias: poweron_tv
    sequence:
      - alias: 'turn on tv'
        service: shell_command.wol_bd_br
      - delay:
          seconds: 20
      - alias: 'change input to tv'
        service: shell_command.change_to_tv
  powerofftv:
    alias: poweroff_tv
    sequence:
      - alias: 'turn off tv'
        service: shell_command.power_off_tv

In the tv.py add your pairing key instead of DDDDDD copy and paste the entire script into a file named tv.py and your good to go. Of course restart HA
This is the tv.py script.

#!/usr/bin/env python3

import http.client
from tkinter import *
import xml.etree.ElementTree as etree
import socket
import re
import sys
lgtv = {}
dialogMsg =""
headers = {"Content-Type": "application/atom+xml"}
lgtv["pairingKey"] = "DDDDDD"

def getip():
    strngtoXmit =   'M-SEARCH * HTTP/1.1' + '\r\n' + \
                    'HOST: 239.255.255.250:1900'  + '\r\n' + \
                    'MAN: "ssdp:discover"'  + '\r\n' + \
                    'MX: 2'  + '\r\n' + \
                    'ST: urn:schemas-upnp-org:device:MediaRenderer:1'  + '\r\n' +  '\r\n'

    bytestoXmit = strngtoXmit.encode()
    sock = socket.socket( socket.AF_INET, socket.SOCK_DGRAM )
    sock.settimeout(3)
    found = False
    gotstr = 'notyet'
    i = 0
    ipaddress = None
    sock.sendto( bytestoXmit,  ('239.255.255.250', 1900 ) )
    while not found and i <= 5 and gotstr == 'notyet':
        try:
            gotbytes, addressport = sock.recvfrom(512)
            gotstr = gotbytes.decode()
        except:
            i += 1
            sock.sendto( bytestoXmit, ( '239.255.255.250', 1900 ) )
        if re.search('LG', gotstr):
            ipaddress, _ = addressport
            found = True
        else:
            gotstr = 'notyet'
        i += 1
    sock.close()
    if not found : sys.exit("Lg TV not found")
    return ipaddress


def displayKey():
    conn = http.client.HTTPConnection( lgtv["ipaddress"], port=8080)
    reqKey = "<?xml version=\"1.0\" encoding=\"utf-8\"?><auth><type>AuthKeyReq</type></auth>"
    conn.request("POST", "/hdcp/api/auth", reqKey, headers=headers)
    httpResponse = conn.getresponse()
    if httpResponse.reason != "OK" : sys.exit("Network error")
    return httpResponse.reason


def getSessionid():
    conn = http.client.HTTPConnection( lgtv["ipaddress"], port=8080)
    pairCmd = "<?xml version=\"1.0\" encoding=\"utf-8\"?><auth><type>AuthReq</type><value>" \
            + lgtv["pairingKey"] + "</value></auth>"
    conn.request("POST", "/hdcp/api/auth", pairCmd, headers=headers)
    httpResponse = conn.getresponse()
    if httpResponse.reason != "OK" : return httpResponse.reason
    tree = etree.XML(httpResponse.read())
    return tree.find('session').text


def getPairingKey():
    displayKey()
    root = Tk()
    root.withdraw()
    dialogMsg = "Please enter the pairing key\nyou see on your TV screen\n"
    d = MyDialog(root, dialogMsg)
    root.wait_window(d.top)
    lgtv["pairingKey"] = result
    d.top.destroy()

def handleCommand(cmdcode):
    conn = http.client.HTTPConnection( lgtv["ipaddress"], port=8080)
    cmdText = "<?xml version=\"1.0\" encoding=\"utf-8\"?><command><session>" \
                + lgtv["session"]  \
                + "</session><type>HandleKeyInput</type><value>" \
                + cmdcode \
                + "</value></command>"
    conn.request("POST", "/hdcp/api/dtv_wifirc", cmdText, headers=headers)
    httpResponse = conn.getresponse()


#main()

lgtv["ipaddress"] = getip()
theSessionid = getSessionid()
while theSessionid == "Unauthorized" :
    getPairingKey()
    theSessionid = getSessionid()

if len(theSessionid) < 8 : sys.exit("Could not get Session Id: " + theSessionid)

lgtv["session"] = theSessionid


#displayKey()
result = str(sys.argv[1])
handleCommand(result)



If the LG remote apk works for you (the 2011 supporting one) then the script will work for you as well.

anyone get this script work with 2013 models? mine respond “Unauthorized” to pairing request:

POST http://10.182.22.5:8080/hdcp/api/auth HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: application/xml
Content-Length: 36
Host: 10.182.22.5:8080
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)

<auth><type>AuthKeyReq</type></auth> 

and response:

<envelope>
   <ROAPError>401</ROAPError>
   <ROAPErrorDetail>Unauthorized</ROAPErrorDetail>
</envelope>

Try this one

#!/usr/bin/env python3

import http.client
from tkinter import *
import xml.etree.ElementTree as etree
import socket
import re
import sys
lgtv = {}
dialogMsg =""
headers = {"Content-Type": "application/atom+xml"}
lgtv["pairingKey"] = "DDDDDD"

class MyDialog:
    def __init__(self, parent, dialogMsg):
        top = self.top = Toplevel(parent)
        Label(top, text = dialogMsg, justify="left").pack()
        self.e = Entry(top)
        self.e.pack(padx=5)
        self.e.focus_set()
        b = Button(top, text="Ok", command=self.ok)
        b.pack(pady=5)
        top.bind("<Return>", self.ok)
        top.title("Lg 2012")
        top.geometry("410x280+10+10")
    def ok(self,dummy=None):
        global result
        result = self.e.get()
        self.top.destroy()


def getip():
    strngtoXmit =   'M-SEARCH * HTTP/1.1' + '\r\n' + \
                    'HOST: 239.255.255.250:1900'  + '\r\n' + \
                    'MAN: "ssdp:discover"'  + '\r\n' + \
                    'MX: 2'  + '\r\n' + \
                    'ST: urn:schemas-upnp-org:device:MediaRenderer:1'  + '\r\n' +  '\r\n'

    bytestoXmit = strngtoXmit.encode()
    sock = socket.socket( socket.AF_INET, socket.SOCK_DGRAM )
    sock.settimeout(3)
    found = False
    gotstr = 'notyet'
    i = 0
    ipaddress = None
    sock.sendto( bytestoXmit,  ('239.255.255.250', 1900 ) )
    while not found and i <= 5 and gotstr == 'notyet':
        try:
            gotbytes, addressport = sock.recvfrom(512)
            gotstr = gotbytes.decode()
        except:
            i += 1
            sock.sendto( bytestoXmit, ( '239.255.255.250', 1900 ) )
        if re.search('LG', gotstr):
            ipaddress, _ = addressport
            found = True
        else:
            gotstr = 'notyet'
        i += 1
    sock.close()
    if not found : sys.exit("Lg TV not found")
    return ipaddress


def displayKey():
    conn = http.client.HTTPConnection( lgtv["ipaddress"], port=8080)
    reqKey = "<?xml version=\"1.0\" encoding=\"utf-8\"?><auth><type>AuthKeyReq</type></auth>"
    conn.request("POST", "/roap/api/auth", reqKey, headers=headers)
    httpResponse = conn.getresponse()
    if httpResponse.reason != "OK" : sys.exit("Network error")
    return httpResponse.reason


def getSessionid():
    conn = http.client.HTTPConnection( lgtv["ipaddress"], port=8080)
    pairCmd = "<?xml version=\"1.0\" encoding=\"utf-8\"?><auth><type>AuthReq</type><value>" \
            + lgtv["pairingKey"] + "</value></auth>"
    conn.request("POST", "/roap/api/auth", pairCmd, headers=headers)
    httpResponse = conn.getresponse()
    if httpResponse.reason != "OK" : return httpResponse.reason
    tree = etree.XML(httpResponse.read())
    return tree.find('session').text


def getPairingKey():
    displayKey()
    root = Tk()
    root.withdraw()
    dialogMsg = "Please enter the pairing key\nyou see on your TV screen\n"
    d = MyDialog(root, dialogMsg)
    root.wait_window(d.top)
    lgtv["pairingKey"] = result
    d.top.destroy()

def handleCommand(cmdcode):
    conn = http.client.HTTPConnection( lgtv["ipaddress"], port=8080)
    cmdText = "<?xml version=\"1.0\" encoding=\"utf-8\"?><command>" \
                + "<name>HandleKeyInput</name><value>" \
                + cmdcode \
                + "</value></command>"
    conn.request("POST", "/roap/api/command", cmdText, headers=headers)
    httpResponse = conn.getresponse()


#main()

lgtv["ipaddress"] = getip()
theSessionid = getSessionid()
while theSessionid == "Unauthorized" :
    getPairingKey()
    theSessionid = getSessionid()

if len(theSessionid) < 8 : sys.exit("Could not get Session Id: " + theSessionid)

lgtv["session"] = theSessionid


dialogMsg =""
for lgkey in lgtv :
    dialogMsg += lgkey + ": " + lgtv[lgkey] + "\n"

dialogMsg += "Success in establishing command session\n"
dialogMsg += "=" * 28 + "\n"
dialogMsg += "Enter command code i.e. a number between 0 and 1024\n"
dialogMsg += "Enter a number greater than 1024 to quit.\n"
dialogMsg += "Some useful codes (not working with 2012 models):\n"
dialogMsg += "for EZ_ADJUST     menu enter   255 \n"
dialogMsg += "for IN START        menu enter   251 \n"
dialogMsg += "for Installation     menu enter   207 \n"
dialogMsg += "for POWER_ONLY mode enter   254 \n"
dialogMsg += "Warning: do not enter 254 if you \ndo not know what POWER_ONLY mode is. "


result = "91"
while int(result) < 1024:
    root = Tk()
    root.withdraw()
    d = MyDialog(root, dialogMsg)
    root.wait_window(d.top)
    handleCommand(result)

thanks, this one works for me

You’re welcome

Has anyone gotten this to work with a WebOS 2014 model?

Not sure what’s included and it doesn’t specify the year, but there are separate steps for LG WebOS

Media Player might have power options

I’ve set both of those up but no power on option. It’s just pause/play but it can turn it off. I’d like to remotely be able to turn it on though. I was thinking something to do with wake on LAN?

hi all… i followed this manual but unfortunately nothing works with it… how could i figure out whats wrong ? I have a 2011 model and remote apk works pretty good on my phone.

Yes, you need to use wake on lan to power on a Web OS tv. Make sure you enable waking the tv in the settings menu of the tv.

You could solve this without shell scripts with the native wake_on_lan component of homeassistant. I did the following:

  1. Define a media player for the webos tv. This will allow you to get the state of the tv and turn it off.
  2. Define a switch which updates itself with the current power state of the tv: tv_switch. Use this to turn the tv on or off. E.g. expose it to homebridge as ‘TV’ if you want to control the tv with Siri.
  3. Define a (hidden) switch to send the wake-on-lan packet when the tv_switch is turned on.

To switch sources, use the media_player.select_source service.

Btw, I haven’t figured out is how to detect when the tv is off but recording.

Switches config:

- platform: template
  switches:
    tv_switch:
      friendly_name: TV
      value_template: '{{ states.media_player.lg_webos_smart_tv.state != "off" }}'
      turn_on:
        service: switch.turn_on
        entity_id: switch.wol_lg_tv_eth
      turn_off:
        service: media_player.turn_off
        entity_id: media_player.lg_webos_smart_tv
      entity_id: media_player.lg_webos_smart_tv

- platform: wake_on_lan
  name: wol_lg_tv_eth
  mac_address: '00-08-00-00-00-00'
  host: 192.168.1.123 #Irrelevant, will monitor the state of host to set the switch state.
  turn_off:
    service: switch.turn_off
    entity_id: switch.tv_switch

Hello there the stuff written on top is for 2011 and 2012 models. Basically pre 2012 models.
As they don’t have WOL you would have to wake your TV via connected device like Bluray player that supports LAN (for WOL.)

You then wake the Bluray player that in turns wakes the TV through Simlink.

Then you can use the above work around to chance channels turn volume up and down or turn it off.
I posted more key commands here

You should just use the LG TV Component

Did you created the tv.py to be called?

Hi @dennisaion ! I’m trying to implement your code. Were do I have to put the tv.py file and can you confirm your code is working nowadays? I did changed “state:” to “from:” and “to:”. No error on the UI but error on the home assistant log:
ERROR (MainThread) [homeassistant.components.shell_command] Error running command: python3 /home/pi/.homeassistant/tv.py 8, return code: 2 NoneType: None
I placed the tv.py file at top level folder that configurator plugin can access - one level above the config folder.

Hello,
can you explain that to me in more detail? I have a PreWebOs LG TV. What do I have to write in the configuration and where does the TV.py file go? Do I have to write it myself? Sorry for the many questions.

My docker Home Assistant: error tkinter.
Traceback (most recent call last):
File “/config/shell_command/tv.py”, line 4, in
from tkinter import *
File “/usr/local/lib/python3.11/tkinter/init.py”, line 38, in
import _tkinter # If this fails your Python may not be configured for Tk
^^^^^^^^^^^^^^^
ImportError: Error loading shared library libtk8.6.so: No such file or
directory (needed by
/usr/local/lib/python3.11/lib-dynload/_tkinter.cpython-311-x86_64-linux-musl.so)
returncode: 1