Roku API Example

Continuing the discussion from Roku Binding Request:

Awhile back someone requested the creation of a Roku binding and my response was that Roku has a simple REST API and the HTTP binding would work. I shall post here an example of what I did to solve a little problem I had.

#Problem
At night my wife likes to go to sleep with the TV on so we put the Roku on some show and let it run all night. The TV has a timer we set to go off after an hour. But even though the TV is odd, the Roku keeps playing all night. I need a way to stop the Roku from playing all night once the TV is off.

Eventually I will create a simple sensor (probably sound) that will trigger the below when the room goes silent at night but for now I’ll just stop the Roku playing at 2 AM by sending it to the home screen.

#Finding Roku IPs
The Rokus get their IP address via DHCP so you cannot always guarantee they get the same IP every time unless you configure it to on the router. Luckily for us the Roku uses SSDP so we can discover their IPs. The following Python script uses SSDP to discover and print the serial number and URL of each Roku on your network.

#!/usr/bin/python

import sys
import socket
import re

ssdpRequest = "M-SEARCH * HTTP/1.1\r\n" + \
        "HOST: 239.255.255.250:1900\r\n" + \
        "Man: \"ssdp:discover\"\r\n" + \
        "MX: 5\r\n" + \
        "ST: roku:ecp\r\n\r\n";
socket.setdefaulttimeout(10)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2)
sock.sendto(ssdpRequest, ("239.255.255.250", 1900))
while True:
    try:
        resp = sock.recv(1024)
        #print(resp)
        #print("Matches")
        matchObj = re.match(r'.*USN: uuid:roku:ecp:([\w\d]{12}).*LOCATION: (http://.*/).*', resp, re.S)
        print (matchObj.group(1) + " " + matchObj.group(2))
    except socket.timeout:
        break

#Items

String BedroomRokuAddress "Bedroom Roku [%s]"
String DenRokuAddress "Den Roku [%s]"
Switch RefreshRokuAddresses // used for testing

Switch S_C_BedroomRokuHome "Go Home on Bedroom Roku"

#Rules

/*
 * Roku REST API (see https://sdkdocs.roku.com/display/sdkdocs/External+Control+Guide#ExternalControlGuide-3.7DIAL(DiscoveryandLaunch):
 *  - query/apps: Returns a map of all channels installed on the Roku box along with their app id (needed for other commands)
 *  - keydown/<key> : send a key down command. Supported keys include: Home, Rev, Fwd, Play, Select, Left, Right, Down, Up,
 *    Back, InstantReplay, Info, Backspace, Search, Enter, (Roku TVs: VolumeDown, VolumeMute, VolumeUp)
 *  - keyup/<key> : send the key up command
 *  - keypress/<key> : the same as a keydown/<key> followed by a keyup/<key>
 *  - launch/<app id> : launch the given channel
 *  - install/<app id> : closes the current channel and opens the channel store to the passed in app's page
 *  - query/device-info : get system info, useful to match up serial numbers to IP address
 *  - query/icon/<app id> : returns the png icon of the passed in app
 *  - input : allows custom input to apps hat support it
 *
 * NOTE: there is no way to determine what channel is currently running
 */

rule "Get Roku Addresses"
when
        Time cron "0 0 0/1 ? * *" or
        Item RefreshRokuAddresses received command
then
    logInfo("Roku", "Refreshing Roku Addresses")
    var String results = executeCommandLine("chmod a+x /etc/openhab/configurations/scripts/searchRokus.py", 5000)
    logInfo("Roku", "chmod results: " + results)
    results = executeCommandLine("/etc/openhab/configurations/scripts/searchRokus.py", 20000)
    logInfo("Roku", "searchRoku.py results:\n" + results)
    val rokuAddrs = results.split("\n")
    rokuAddrs.forEach[addr |
        val s = addr.split(" ")
        switch (s.get(0)) {
                case "1RE3CD071416" : BedroomRokuAddress.postUpdate(s.get(1))
                case "1RE3CN071402" : DenRokuAddress.postUpdate(s.get(1))
                default : logInfo("Roku", s.get(0) + " is an unknown Roku")
        }
    ]
end

rule "Go Home on Home Roku after bed"
when
        Time cron "0 0 2 ? * *" // 2 am
then
        logInfo("Roku", "Sending Bedroom Roku to Home")
        sendHttpGetRequest(BedroomRokuAddress+"keypress/Home")
end

If you set a static IP for your Rokus or don’t mind periodically changing your OH files when the IPs change you can skip the script and the rules and just hard code the URL for the Roku and use the HTTP binding instead of rules and the HTTP actions.

NOTE: This is moderately tested, there may be some errors above.

OK, maybe someday I’ll learn my lesson and not post until I’ve thoroughly tested. Apparently one must send a POST, not a GET when sending keypresses (GET works with query like commands like /query/device-info). Also, there is a stupid rookie mistake in the HTTP get command above. So change the above to:

to

sendHttpPostRequest(BedroomRokuAddress.state.toString+"keypress/home")

how would I need to format this to use it in a windows environment?

It should be the same. The only difference is what page you give the executeCommandLine to the Python script, and you may need to run Python directly (e.g. python <path to Python script>