LG TV Webos: A rule to restore the last application on power on

Tags: #<Tag:0x00007f1751aaf590>

What bugs me with the LG TV is whenever I turn it on, it defaults to either Live TV, or some HDMI input. So if I was watching Netflix, turn off the TV, it won’t go back to Netflix when I powered it back on.

Sure, Netflix has its own dedicated button, but Plex, Youtube etc don’t. Sure, you can create application shortcut on the magic remote, but that requires pressing and holding the corresponding key, an extra few seconds.

So I wrote a rule to save the last used application prior to powering off the TV, and start up that app when the TV turns on.

I use Jython for my rule engine, but the idea can be implemented in RulesDSL too

Some notes:

  • I used the following naming convention for my TV items: RoomName_TV_Application for the application item, and they’re a member of gTVApplication group. This makes the rule works for all TVs around the house.
  • I noticed a quirk on one of my TVs, in that whenever I’m switching from Plex (cdp-30) to Amazon, the Application would cycle like this: “cdp-30” -> “”, “” -> “amazon”. This doesn’t happen on my other TV, so I implemented a timer, otherwise the rule will think the TV is switching off, and back on again, and it will keep trying to switch the app to Plex.
Group gTVApplication

Switch LivingRoom_TV_Power  "Living Room TV Power" { channel="lgwebos:WebOSTV:living:power", autoupdate="false" }
String LivingRoom_TV_Application "Application [%s]" (gTVApplication) { channel="lgwebos:WebOSTV:living:appLauncher"}

Switch LoungeRoom_TV_Power  "Living Room TV Power" { channel="lgwebos:WebOSTV:lounge:power", autoupdate="false" }
String LoungeRoom_TV_Application "Application [%s]" (gTVApplication) { channel="lgwebos:WebOSTV:lounge:appLauncher"}
# Global variables
lastApp = {}
timers = {}

@rule("Save/Restore Last TV App")
@when("Member of gTVApplication changed")
def app_restorer(event):
    def save_app(tv_name, last_app):
        global lastApp
        app_restorer.log.info("Saving last app for {}: {}".format(tv_name, last_app))
        lastApp[tv_name] = last_app
        # postUpdate(tv_name + "_Power", "OFF") # The Power item doesn't get updated to off for some time. Post an update here to change it immediately

    def restore_app(tv_name):
        # postUpdate(tv_name + "_Power", "ON")
        startup_app = items[tv_name + "_Application"].toString()
        if (
            tv_name in lastApp
            and (
                startup_app == "com.webos.app.livetv"
                or startup_app.startswith("com.webos.app.hdmi")
                or startup_app.startswith("HDMI")
            )
            and (startup_app != lastApp[tv_name])
        ):
            app_restorer.log.info("Restoring last app for {}: {}".format(tv_name, lastApp[tv_name]))
            sendCommand(tv_name + "_Application", lastApp[tv_name])

    global timers
    tv_name = event.itemName.replace("_Application", "")

    # new app is empty, tv is turning off, save the previous app
    # from xx to ""
    if isinstance(event.itemState, UnDefType) or event.itemState.toString() == "":
        timers[event.itemName] = ScriptExecution.createTimer(
            DateTime.now().plusSeconds(2), lambda: save_app(tv_name, event.oldItemState.toString()),
        )

    # old app is empty, tv is powering on
    # from "" to xx
    elif isinstance(event.oldItemState, UnDefType) or event.oldItemState.toString() == "":
        startup_app = event.itemState.toString()
        if event.itemName in timers and not timers[event.itemName].hasTerminated():
            timers[event.itemName].cancel()
        else:
            ScriptExecution.createTimer(DateTime.now().plusSeconds(1), lambda: restore_app(tv_name))
3 Likes

Implementation in JRuby. The last app is stored in the corresponding TV_Application item’s metadata

rule 'TV: Save Last App' do
  changed gTVApplication.items, to: UNDEF, for: 5.seconds
  run do |event|
    event.item.meta['last_app'] = event.last
    logger.info("TV State Saved last app: #{event.last} on #{event.item.name}")
  end
end

rule 'TV: Restore Last App' do
  changed gTVApplication.items, from: UNDEF, for: 5.seconds
  only_if { |event| %w[com.webos.app.livetv com.webos.app.hdmi HDMI].include? event.state.to_s }
  only_if { |event| event.item.meta['last_app']&.value }
  run do |event|
    event.item << event.item.meta['last_app'].value
    logger.info("TV State Restored last app: #{event.item.meta['last_app'].value} on #{event.item.name}")
    event.item.meta.delete 'last_app'
  end
end

Could you also use store_states?

No, because I need to save event.last (the state before the change, not the current state). I just had an idea to use metadata for storage instead of a class variable. I’ll update my code above.

I do something similar to this, but I just use an unbound string item to store the most recent state.

That’s a great idea too. Why not metadata?

An unbound item was generally the recommended solution in OH2 for anyone wanting global variables, so I’ve never given it more thought than that.

My scenario is that I’m controlling an IR fan through OH with a Logitech Harmony remote as an intermediary. I’m basically just sending button presses to the fan. The fan automatically returns to the last setting when you turn it on, but OH doesn’t know that since there’s no feedback.

So, I store the setting in the unbound item. Whenever I change the setting via OH, a case statement triggers the correct sequence of button presses based on the last stored setting.

With persistence, the value will survive a system restart (I assume that will also be the case with metadata). It continues to work in OH3, so I’ve no reason to change it.

1 Like

So if the fan setting is changed on the fan itself, it would get out of sync?

Yep, but it’s in a corner far from the door, and I can control it via Google Assistant. So, I have no reason to touch the controls.