ROKU Support on OH3 using HTTP binding

With the release of OH3, and with the new shiny HTTP binding working very well, I spent a little time to update the OH2 rules setup from Roku Support with one using the HTTP binding and XPATH transform. Below is relatively copy/paste and seems to work pretty cleanly. No rules or scripts are necessary. I have this running on 7 Roku devices (2x are TVs) and it seems to have no issues.

Some notes in comparison to the OH2 version linked above:
-I have removed the keyboard for now. I just havenā€™t had a chance to mess with the Lit_ function in the API on this and I honestly never use it so I may just leave it out.
-Iā€™m not a huge fan of the 1 second refresh timer. It has caused some visible load on the Rokuā€™s. Iā€™ve submitted [HTTP] Feature Request - Off Cycle Refresh & Agent String Ā· Issue #9265 Ā· openhab/openhab-addons Ā· GitHub as a hope to fix this. If I can trigger an off-cycle refresh, then I can dial the ā€œnormalā€ refresh up higher.
-This method gets away from the ssdp concept from OH2. I found over time that ssdp wasnā€™t 100% reliable during times of network congestion and would occasionally lose a device. This assumes static IPs of your Rokuā€™s.
-The first two lines on the sitemap (power/volume) are only applicable to the Roku TVs, not the physical ones. Also, some Roku TVs go into a deeper sleep than others. You will need to change offValue=ā€œReadyā€ to whatever your off is being set to on POWERMODE for the switch to work properly. Yes I could do this with a transform map, but I donā€™t have a comprehensive list and in general you only have one ā€œoffā€ for a given device.

Thing http:url:ROKU<SERIAL-NUMBER> [ baseURL="http://<IP-ADDRESS>:8060/" , commandMethod="POST" , refresh="1" ] {
        Channels:
                Type string : UDN "UDN" [ stateExtension="query/device-info" , stateTransformation="XPath://udn" , mode="READONLY" ]
                Type string : SERIALNUMBER "Serial Number" [ stateExtension="query/device-info" , stateTransformation="XPath://serial-number" , mode="READONLY" ]
                Type string : DEVICEID "Device ID" [ stateExtension="query/device-info" , stateTransformation="XPath://device-id" , mode="READONLY" ]
                Type string : VENDORNAME "Vendor Name" [ stateExtension="query/device-info" , stateTransformation="XPath://vendor-name" , mode="READONLY" ]
                Type string : MODELNAME "Model Name" [ stateExtension="query/device-info" , stateTransformation="XPath://model-name" , mode="READONLY" ]
                Type string : MODELNUMBER "Model Number" [ stateExtension="query/device-info" , stateTransformation="XPath://model-number" , mode="READONLY" ]
                Type string : SUPPORTSETHERNET "Supports Ethernet" [ stateExtension="query/device-info" , stateTransformation="XPath://supports-ethernet" , mode="READONLY" ]
                Type string : WIFIMAC "Wifi MAC" [ stateExtension="query/device-info" , stateTransformation="XPath://wifi-mac" , mode="READONLY" ]
                Type string : ETHERNETMAC "Ethernet MAC" [ stateExtension="query/device-info" , stateTransformation="XPath://ethernet-mac" , mode="READONLY" ]
                Type string : NETWORKTYPE "Network Type" [ stateExtension="query/device-info" , stateTransformation="XPath://network-type" , mode="READONLY" ]
                Type string : USERDEVICENAME "User Device Name" [ stateExtension="query/device-info" , stateTransformation="XPath://user-device-name" , mode="READONLY" ]
                Type string : SOFTWAREVERSION "Software Version" [ stateExtension="query/device-info" , stateTransformation="XPath://software-version" , mode="READONLY" ]
                Type string : SOFTWAREBUILD "Software Build" [ stateExtension="query/device-info" , stateTransformation="XPath://software-build" , mode="READONLY" ]
                Type string : SECUREDEVICE "Secure Device" [ stateExtension="query/device-info" , stateTransformation="XPath://secure-device" , mode="READONLY" ]
                Type string : POWERMODE "Power Mode" [ stateExtension="query/device-info" , stateTransformation="XPath://power-mode" , mode="READONLY" ]
                Type switch : POWER "Power" [ stateExtension="query/device-info" , stateTransformation="XPath://power-mode" , onValue="PowerOn", offValue="Ready" , mode="READONLY" ]
                Type string : APP "App" [ stateExtension="query/active-app" , stateTransformation="XPath://app" , mode="READONLY" ]
                Type string : SCREENSAVER "Screen Saver" [ stateExtension="query/active-app" , stateTransformation="XPath://screensaver" , mode="READONLY" ]
                Type string : LAUNCH "Launch" [ commandExtension="launch/%2$s" , mode="WRITEONLY" ]
                Type string : KEYPRESS "Key Press" [ commandExtension="keypress/%2$s" , mode="WRITEONLY" ]
}

String ROKU3_<SERIAL-NUMBER>_serialnumber "Serial Number [%s]" { channel="http:url:ROKU<SERIAL-NUMBER>:SERIALNUMBER" }
String ROKU3_<SERIAL-NUMBER>_deviceid "Device ID [%s]" { channel="http:url:ROKU<SERIAL-NUMBER>:DEVICEID" }
String ROKU3_<SERIAL-NUMBER>_vendorname "Vendor Name [%s]" { channel="http:url:ROKU<SERIAL-NUMBER>:VENDORNAME" }
String ROKU3_<SERIAL-NUMBER>_modelname "Model Name [%s]" { channel="http:url:ROKU<SERIAL-NUMBER>:MODELNAME" }
String ROKU3_<SERIAL-NUMBER>_modelnumber "Model Number [%s]" { channel="http:url:ROKU<SERIAL-NUMBER>:MODELNUMBER" }
String ROKU3_<SERIAL-NUMBER>_supportsethernet "Supports Ethernet [%s]" { channel="http:url:ROKU<SERIAL-NUMBER>:SUPPORTSETHERNET" }
String ROKU3_<SERIAL-NUMBER>_wifimac "Wifi MAC [%s]" { channel="http:url:ROKU<SERIAL-NUMBER>:WIFIMAC" }
String ROKU3_<SERIAL-NUMBER>_ethernetmac "Ethernet MAC [%s]" { channel="http:url:ROKU<SERIAL-NUMBER>:ETHERNETMAC" }
String ROKU3_<SERIAL-NUMBER>_networktype "Network Type [%s]" { channel="http:url:ROKU<SERIAL-NUMBER>:NETWORKTYPE" }
String ROKU3_<SERIAL-NUMBER>_userdevicename "User Device Name [%s]" { channel="http:url:ROKU<SERIAL-NUMBER>:USERDEVICENAME" }
String ROKU3_<SERIAL-NUMBER>_softwareversion "Software Version [%s]" { channel="http:url:ROKU<SERIAL-NUMBER>:SOFTWAREVERSION" }
String ROKU3_<SERIAL-NUMBER>_softwarebuild "Software Build [%s]" { channel="http:url:ROKU<SERIAL-NUMBER>:SOFTWAREBUILD" }
String ROKU3_<SERIAL-NUMBER>_securedevice "Secure Device [%s]" { channel="http:url:ROKU<SERIAL-NUMBER>:SECUREDEVICE" }
String ROKU3_<SERIAL-NUMBER>_powermode "Power Mode [%s]" (gRokuPowerMode) { channel="http:url:ROKU<SERIAL-NUMBER>:POWERMODE" }
Switch ROKU3_<SERIAL-NUMBER>_powerstate "Power [%s]" { channel="http:url:ROKU<SERIAL-NUMBER>:POWER" }
String ROKU3_<SERIAL-NUMBER>_activeapp "App [%s]" { channel="http:url:ROKU<SERIAL-NUMBER>:APP" }
String ROKU3_<SERIAL-NUMBER>_screensaver "Screen Saver [%s]" { channel="http:url:ROKU<SERIAL-NUMBER>:SCREENSAVER" }
String ROKU3_<SERIAL-NUMBER>_launch "Launch [%s]" (gRokuLaunch) { channel="http:url:ROKU<SERIAL-NUMBER>:LAUNCH" }
String ROKU3_<SERIAL-NUMBER>_keypress "Key Press [%s]" (gRokuKeypres) { channel="http:url:ROKU<SERIAL-NUMBER>:KEYPRESS" }

Frame label="Roku TV" icon="house" {
        Text item=ROKU3_<SERIAL-NUMBER>_status label=""
        Switch item=ROKU3_<SERIAL-NUMBER>_keypress label="" mappings=['POWER'="Power"]
        Switch item=ROKU3_<SERIAL-NUMBER>_keypress label="" mappings=['VOLUMEMUTE'="Mute",'VOLUMEUP'="VOL UP",'VOLUMEDOWN'="VOL DN"]
        Switch item=ROKU3_<SERIAL-NUMBER>_keypress label="" mappings=['BACK'="Back",'HOME'="Home",'INFO'="Info"]
        Switch item=ROKU3_<SERIAL-NUMBER>_keypress label="" mappings=['NULL'="--",'UP'="ā–²",'NULL'="--"]
        Switch item=ROKU3_<SERIAL-NUMBER>_keypress label="" mappings=['LEFT'="ā—„",'SELECT'="ā—†",'RIGHT'="ā–ŗ"]
        Switch item=ROKU3_<SERIAL-NUMBER>_keypress label="" mappings=['NULL'="--",'DOWN'="ā–¼",'NULL'="--"]
        Switch item=ROKU3_<SERIAL-NUMBER>_keypress label="" mappings=['REV'="ā—„ā—„",'PLAY'="ā–ŗ||",'FWD'="ā–ŗā–ŗ"]
        Switch item=ROKU3_<SERIAL-NUMBER>_keypress label="" mappings=['INSTANTREPLAY'="Replay",'SEARCH'="Search"]
        Switch item=ROKU3_<SERIAL-NUMBER>_keypress label="" mappings=['BACKSPACE'="Backspace",'ENTER'="Enter"]
        Text label="Roku Apps" icon="office" {
                Switch item=ROKU3_<SERIAL-NUMBER>_launch label="" icon="12" mappings=['12'="Netflix"]
                Switch item=ROKU3_<SERIAL-NUMBER>_launch label="" icon="13842" mappings=['13842'="VUDU"]
                Switch item=ROKU3_<SERIAL-NUMBER>_launch label="" icon="186362" mappings=['186362'="Movies Anywhere"]
                Switch item=ROKU3_<SERIAL-NUMBER>_launch label="" icon="837" mappings=['837'="YouTube"]
                Switch item=ROKU3_<SERIAL-NUMBER>_launch label="" icon="13535" mappings=['13535'="PLEX"]
                Switch item=ROKU3_<SERIAL-NUMBER>_launch label="" icon="34376" mappings=['34376'="WatchESPN"]
                Switch item=ROKU3_<SERIAL-NUMBER>_launch label="" icon="13" mappings=['13'="Amazon Video"]
        }
        Text label="Device Info" icon="office" {
                Frame label="ROKU<SERIAL-NUMBER>" {
                	Text item=ROKU3_<SERIAL-NUMBER>_serialnumber
                	Text item=ROKU3_<SERIAL-NUMBER>_deviceid
                	Text item=ROKU3_<SERIAL-NUMBER>_vendorname
                	Text item=ROKU3_<SERIAL-NUMBER>_modelname
                	Text item=ROKU3_<SERIAL-NUMBER>_modelnumber
                	Text item=ROKU3_<SERIAL-NUMBER>_supportsethernet
                	Text item=ROKU3_<SERIAL-NUMBER>_wifimac
                	Text item=ROKU3_<SERIAL-NUMBER>_ethernetmac
                	Text item=ROKU3_<SERIAL-NUMBER>_networktype
                	Text item=ROKU3_<SERIAL-NUMBER>_userdevicename
                	Text item=ROKU3_<SERIAL-NUMBER>_softwareversion
                	Text item=ROKU3_<SERIAL-NUMBER>_softwarebuild
                	Text item=ROKU3_<SERIAL-NUMBER>_securedevice
                	Text item=ROKU3_<SERIAL-NUMBER>_powermode
                	Text item=ROKU3_<SERIAL-NUMBER>_activeapp
                	Text item=ROKU3_<SERIAL-NUMBER>_screensaver
                }
        }
}
2 Likes

I just wanted to thank you for providing this. I just tried adding my TV and using both the openhab2 method and what you have here. Either donā€™t seem to work for me, even though I can directly curl commands.

In your example I replaced <SERIAL-NUMBER> with the serial number I found by running a query on device-info. Iā€™ve also replaced <IP-ADDRESS> with the statically assigned address.

I put the things section in a things file. The items in an item file. Iā€™ve put the sitemap in a sitemap file, and assigned it to its own sitemap. I can see the buttons, but clicking them doesnā€™t do anything. Iā€™m not sure where to find out where Iā€™ve gone wrong. Iā€™m also getting all blank data for stats. Do I need to add these items to persistence as well?

Here is the logs from me pushing a few buttons:
2020-12-22 15:48:30.260 [INFO ] [openhab.event.ItemCommandEvent ] - Item ā€˜ROKU3_YK00UL915076_keypressā€™ received command POWER
2020-12-22 15:48:30.261 [INFO ] [penhab.event.ItemStatePredictedEvent] - Item ā€˜ROKU3_YK00UL915076_keypressā€™ predicted to become NULL
2020-12-22 15:48:30.404 [INFO ] [openhab.event.ItemCommandEvent ] - Item ā€˜ROKU3_YK00UL915076_keypressā€™ received command POWER
2020-12-22 15:48:30.405 [INFO ] [penhab.event.ItemStatePredictedEvent] - Item ā€˜ROKU3_YK00UL915076_keypressā€™ predicted to become NULL

That didnā€™t do anything. Running this turned the TV off from the ssh inside of the raspberry pi 4 running openhab3.
curl -d ā€˜ā€™ http://10.2.2.248:8060/keypress/Power

Iā€™m very intrigued that neither solution works. The OH2 variant worked for many people for years. The OH3 is no where near as complex and is based on the new HTTP binding which seems to work very well with really minimal configs. It sounds like OH itself is having issues making the HTTP calls. Also, Iā€™m not sure why they are predicting to become NULL, that doesnā€™t happen on my side at all. Is your OH running in Docker or anything like that?

Hmm, I donā€™t think Iā€™m running it in Docker. I checked just to be sure.
tom@openhab:~ $ sudo docker ps
[sudo] password for tom:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f39d9cde72ee adejong/grafana-image-renderer-pi:1.0.8-beta2 ā€œdumb-init ā€“ node bā€¦ā€ 5 months ago Up 24 hours 0.0.0.0:8081->8081/tcp sad_goldstine

Its starting to sound like the issue Iā€™m having is specific to my installation or something along those lines. I may need to start digging into it a bit more. Iā€™ve had a few annoying issues in the log such as javascript functions not being available, etc. Iā€™ll start by clearing those up and see if things improve that way. If I canā€™t make any progress after awhile Iā€™ll open up a new topic on the issue.

One thing you could try. Strip all the channels out and create one for the device info and use no transforms. This should pull all of the output into one item. Iā€™d be interested in knowing if youā€™re even getting data.

I got it partially figured out. Seems I never installed the HTTP bindings. Iā€™m still having issues with it grabbing the data, but Iā€™m guessing it is something equally as silly.

ProTip: If you want to use the HTTP bindingsā€¦ Make sure you have the HTTP bindings installed. It works so much better when it is installed. :slight_smile:

Here is what is currently wrongā€¦ (I get many more of these)
2020-12-22 19:50:41.242 [WARN ] [.transform.SingleValueTransformation] - Transformation service XPATH for pattern //model-name not found!
2020-12-22 19:50:42.267 [WARN ] [.transform.SingleValueTransformation] - Transformation service XPATH for pattern //model-number not found!

Iā€™m still reading up on this XPATH is installed by default, correct? I canā€™t seem to find out if Iā€™m missing that or not.

Based on that output, XPATH isnā€™t installed.

1 Like

Sure enough, now that Iā€™ve figured out how to install transformations on the new systemā€¦ All is working. :slight_smile:

Cool. Iā€™ve updated the main post to specify the binding and transform as required

Just implemented on my OH3 instance. I can see device info so its kind of working. Logs report it canā€™t find _status I canā€™t see that in your items part only in the sitemap?

Also mine which is UK badged NowTV stick doesnā€™t seem the have the screensaver bit.

Also all the buttons show < ? xml version=ā€œ1.0ā€?>ā€¦ etc

Maybe you didnā€™t install the XPATH Transformation service? Also there is a native binding available now if you want to try it out. You can side load the jar from here: [roku] binding - initial implementation by mlobstein Ā· Pull Request #9571 Ā· openhab/openhab-addons Ā· GitHub, Starting with 3.1.0, it will be available to automatically install.

Thanks tried connecting it to the upstairs (older) Roku and it works fine
Iā€™m still not sure this is defined or gets its value in your example

Text item=ROKU3_<SERIAL-NUMBER>_status label=ā€œā€

However Iā€™m going to try and connect to my Humax Fox T2 based on this example so really appreciate it the posting.

Sorry, status should come out. Thats a leave behind from OH2.

Agreed, native binding is much better. Caution on side loading, it should fail unless your openhab core is a 3.1 0 snapshot.

Thanks @morph166955. Seems I had a typo on the first one as thatā€™s working too now.
I also had success creating a link to my humax, Iā€™ll post that separately as other may want it.

Next step is to work out how to disable the thing when its not ping able to have lots of events when its off at the wall. Iā€™ve seen the api can be called and I already have the network bings so shouldnt be too hard.

Thanks for putting the Roku how to up onto the forum.

I just wanted a basic remote for my Telstra TV box that happens to be a Roku controller and I didnā€™t need to have the http binding or any feedback from the Roku.

What I did was create a rule, an item and modified a widget that someone else had created. (Thanks to whoever created it as I cannot find it again to see who did it)

Below is what it looks like on my phone.

Anyway I will put it here in case someone else wants to do it. I am sure there is a better way but this actually works.

The rule to receive the remote commands
triggers:
  - id: "1"
    configuration:
      itemName: rokuitem
    type: core.ItemStateUpdateTrigger
conditions: []
actions:
  - inputs: {}
    id: "2"
    configuration:
      type: application/javascript
      script: >+
        //need below to log to openhab.log file

        var logger = Java.type('org.slf4j.LoggerFactory').getLogger('org.openhab.rule.' + ctx.ruleUID);

        //below is if you are going to use the ececute command

        var Exec = Java.type("org.openhab.core.model.script.actions.Exec");

        //below is also needed for the execute command

        var Duration = Java.type("java.time.Duration");


        var HttpUtil = Java.type("org.openhab.core.io.net.http.HttpUtil");

        var HttpGet = Java.type("org.openhab.core.model.script.actions.HTTP");


        var PORT = "8060"

        var IP = "192.168.1.212"


        var stateof = event.itemState.toString() ;

        logger.info("results = " + stateof);



        switch(stateof) {
         case "KEY_POWER":
             HttpUtil.executeUrl("POST","http://"+IP+":"+PORT+"/keypress/PowerOn", 20000);
          logger.info('Play key pressed');
            break;

          case "KEY_PLAY":
             HttpUtil.executeUrl("POST","http://"+IP+":"+PORT+"/keypress/Play", 20000);
          logger.info('Play key pressed');
            break;

          case "KEY_PAUSE":
             HttpUtil.executeUrl("POST","http://"+IP+":"+PORT+"/keypress/Play", 20000);
          logger.info('Play key pressed');
            break;

          case "KEY_REWIND":
             HttpUtil.executeUrl("POST","http://"+IP+":"+PORT+"/keypress/Rev", 20000);
          logger.info('Play key pressed');
            break;

          case "KEY_FF":
             HttpUtil.executeUrl("POST","http://"+IP+":"+PORT+"/keypress/Fwd", 20000);
          logger.info('Play key pressed');
            break;

          case "KEY_FF":
             HttpUtil.executeUrl("POST","http://"+IP+":"+PORT+"/keypress/Up", 20000);
          logger.info('Play key pressed');
            break;

          case "KEY_LEFT":
             HttpUtil.executeUrl("POST","http://"+IP+":"+PORT+"/keypress/Left", 20000);
          logger.info('Play key pressed');
            break;

          case "KEY_ENTER":
             HttpUtil.executeUrl("POST","http://"+IP+":"+PORT+"/keypress/Select", 20000);
          logger.info('Play key pressed');
            break;

          case "KEY_RIGHT":
             HttpUtil.executeUrl("POST","http://"+IP+":"+PORT+"/keypress/Right", 20000);
          logger.info('Play key pressed');
            break;

          case "KEY_DOWN":
             HttpUtil.executeUrl("POST","http://"+IP+":"+PORT+"/keypress/Down", 20000);
          logger.info('Play key pressed');
            break;

          case "KEY_HOME":
             HttpUtil.executeUrl("POST","http://"+IP+":"+PORT+"/keypress/Home", 20000);
          logger.info('Play key pressed');
            break;

          case "KEY_RETURN":
             HttpUtil.executeUrl("POST","http://"+IP+":"+PORT+"/keypress/InstantReplay", 20000);
          logger.info('Play key pressed');
            break;

          case "KEY_BACK":
             HttpUtil.executeUrl("POST","http://"+IP+":"+PORT+"/keypress/Back", 20000);
          logger.info('Play key pressed');
            break;

          case "KEY_NETFLIX":
             HttpUtil.executeUrl("POST","http://"+IP+":"+PORT+"/launch/12", 20000);
          logger.info('Play key pressed');
            break;

          case "KEY_AMAZON":
             HttpUtil.executeUrl("POST","http://"+IP+":"+PORT+"/launch/13", 20000);
          logger.info('Play key pressed');
            break;

            
          default:
              logger.info('Nothing ran');

        }    

    type: script.ScriptAction
The widget code
uid: Remote roku
tags: []
props:
  parameters:
    - context: item
      description: Select the remote you wish to use at an equipment item level
      label: Select Equipment
      name: item
      required: true
      type: TEXT
  parameterGroups: []
timestamp: Jun 17, 2021, 4:16:55 PM
component: f7-block
config:
  style:
    box-shadow: 2px 3px rgb(150,150,150)
    background-color: rgb(192, 192, 192)
    --f7-card-margin-horizontal: 0px
    border-radius: 30px
    width: 15rem
    height: 40rem
slots:
  default:
    - component: f7-badge
      config:
        bgColor: black
        style:
          position: absolute
          left: 65px
          top: 20px
          width: 110px
          height: 40px
          border-radius: 12px
    - component: oh-link
      config:
        style:
          position: absolute
          top: 28px
          left: 108px
          color: green
        iconF7: power
        iconSize: 25
        action: command
        actionCommand: KEY_POWER
        actionItem: =props.item
    - component: f7-badge
      config:
        bgColor: black
        style:
          position: absolute
          left: 90px
          top: 110px
          width: 60px
          height: 40px
          border-radius: 12px
    - component: oh-link
      config:
        style:
          position: absolute
          top: 116px
          left: 95px
          color: white
        iconF7: play
        iconSize: 25
        action: command
        actionCommand: KEY_PLAY
        actionItem: =props.item
    - component: oh-link
      config:
        style:
          position: absolute
          top: 116px
          left: 117px
          color: white
        iconF7: pause
        iconSize: 25
        action: command
        actionCommand: KEY_PAUSE
        actionItem: =props.item
    - component: f7-badge
      config:
        bgColor: black
        style:
          position: absolute
          left: 20px
          top: 110px
          width: 60px
          height: 40px
          border-radius: 12px
    - component: oh-link
      config:
        style:
          position: absolute
          top: 117px
          left: 35px
          color: white
        iconF7: backward
        iconSize: 25
        action: command
        actionCommand: KEY_REWIND
        actionItem: =props.item
    - component: oh-link
      config:
        style:
          position: absolute
          top: 117px
          right: 35px
          color: white
        iconF7: forward
        iconSize: 25
        action: command
        actionCommand: KEY_FF
        actionItem: =props.item
    - component: f7-badge
      config:
        bgColor: black
        style:
          position: absolute
          right: 20px
          top: 110px
          width: 60px
          height: 40px
          border-radius: 12px
    - component: oh-link
      config:
        style:
          position: absolute
          top: 197px
          left: 103px
          color: white
          z-index: 2
        iconF7: arrowtriangle_up
        iconSize: 35
        action: command
        actionCommand: KEY_UP
        actionItem: =props.item
    - component: oh-link
      config:
        style:
          position: absolute
          top: 263px
          left: 35px
          color: white
          z-index: 2
        iconF7: arrowtriangle_left
        iconSize: 35
        action: command
        actionCommand: KEY_LEFT
        actionItem: =props.item
    - component: f7-badge
      config:
        bgColor: black
        style:
          position: absolute
          left: 80px
          top: 242px
          width: 80px
          height: 80px
          border-radius: 50%
          border: gray solid 1px
          z-index: 2
    - component: f7-badge
      config:
        bgColor: black
        style:
          position: absolute
          left: 30px
          top: 190px
          width: 180px
          height: 180px
          border-radius: 50%
          z-index: 1
    - component: oh-link
      config:
        text: OK
        style:
          font-size: 25px
          position: absolute
          top: 261px
          left: 103px
          color: white
          z-index: 3
        iconSize: 75
        action: command
        actionCommand: KEY_ENTER
        actionItem: =props.item
    - component: oh-link
      config:
        style:
          position: absolute
          top: 263px
          right: 35px
          color: white
        iconF7: arrowtriangle_right
        iconSize: 35
        action: command
        actionCommand: KEY_RIGHT
        actionItem: =props.item
    - component: oh-link
      config:
        style:
          position: absolute
          top: 330px
          left: 103px
          color: white
        iconF7: arrowtriangle_down
        iconSize: 35
        action: command
        actionCommand: KEY_DOWN
        actionItem: =props.item
    - component: f7-badge
      config:
        bgColor: black
        style:
          position: absolute
          left: 80px
          top: 385px
          width: 80px
          height: 40px
          border-radius: 12px
    - component: oh-link
      config:
        style:
          position: absolute
          top: 392px
          left: 108px
          color: white
        iconF7: house
        iconSize: 25
        action: command
        actionCommand: KEY_HOME
        actionItem: =props.item
    - component: f7-badge
      config:
        bgColor: black
        style:
          position: absolute
          left: 20px
          top: 440px
          width: 80px
          height: 40px
          border-radius: 12px
    - component: oh-link
      config:
        style:
          position: absolute
          top: 446px
          left: 45px
          color: white
        iconF7: arrow_left
        iconSize: 25
        action: command
        actionCommand: KEY_BACK
        actionItem: =props.item
    - component: f7-badge
      config:
        bgColor: black
        style:
          position: absolute
          right: 20px
          top: 440px
          width: 80px
          height: 40px
          border-radius: 12px
    - component: oh-link
      config:
        style:
          position: absolute
          top: 447px
          right: 45px
          color: white
        iconF7: arrow_counterclockwise
        iconSize: 25
        action: command
        actionCommand: KEY_RETURN
        actionItem: =props.item
    - component: f7-badge
      config:
        bgColor: black
        style:
          position: absolute
          right: 20px
          top: 495px
          width: 80px
          height: 40px
          border-radius: 12px
    - component: oh-link
      config:
        style:
          position: absolute
          top: 505px
          right: 35px
          color: white
        iconSize: 25
        action: command
        actionCommand: KEY_AMAZON
        actionItem: =props.item
        text: Amazon
    - component: f7-badge
      config:
        bgColor: black
        style:
          position: absolute
          left: 20px
          top: 495px
          width: 80px
          height: 40px
          border-radius: 12px
    - component: oh-link
      config:
        style:
          position: absolute
          top: 505px
          left: 35px
          color: white
        iconSize: 25
        action: command
        actionCommand: KEY_NETFLIX
        actionItem: =props.item
        text: Netflix
    - component: oh-link
      config:
        color: red
        style:
          font-size: 25px
          position: absolute
          left: 25px
          top: 600px
          width: 200px
          height: 40px
          display: flex
        text: Telstra TV

The item I created is called rokuitem and it is just a simple string item.
THe IP address of the Roku is hard coded in the rule. You need to change that to your IP adress.

Maybe someone else can use this?