Roku Developer Program

Developers and content creators—a complete solution for growing an audience directly.
cancel
Showing results for 
Search instead for 
Did you mean: 
belltown
Level 7

Re: FInd box on LAN

If you decide to go down the SSDP avenue, here's some BrightScript code you may be able to adapt to discover your Pi on the network. The code scans for all Rokus on the network so it can send them a wake-up request every hour. I left it running on my previously-unused Roku HD to prevent other Rokus on the network returning to the Home Screen after Roku implemented that "feature".

You'll need to change the search target, ST: roku:ecp, to correspond to what your Pi server uses.

You'll need code on your Pi that listens to port 1900 for the M-SEARCH request and responds appropriately. The code doesn't have to run in your Pi server; it can be running as a separate service in the Pi.


Sub Main ()

   SLEEP_TIME_MINS = 60

   Q = Chr (34)
   CR = Chr (13)
   LF = Chr (10)

   port = CreateObject ("roMessagePort")

   uiMsg = ""
   uiMsg = uiMsg + "Keep this channel running to prevent" + LF
   uiMsg = uiMsg + "all Rokus on this network" + LF
   uiMsg = uiMsg + "returning to the Home Screen" + LF
   uiMsg = uiMsg + LF + LF + "Press any key to exit"

   ui = CreateObject ("roImageCanvas")
   ui.SetMessagePort (port)
   ui.SetLayer (0, {Color: "#303030"})
   ui.SetLayer (1, {Text: uiMsg, TextAttrs: {Color: "#EBEBEB", Font: "Large", HAlign: "HCenter", VAlign: "VCenter"}})
   ui.Show ()

   ssdpStr = ""
   ssdpStr = ssdpStr + "M-SEARCH * HTTP/1.1" + CR + LF
   ssdpStr = ssdpStr + "HOST: 239.255.255.250:1900" + CR + LF
   ssdpStr = ssdpStr + "MAN: " + Q + "ssdp:discover" + Q + CR + LF
   ssdpStr = ssdpStr + "ST: roku:ecp" + CR + LF
   ssdpStr = ssdpStr + "MX: 2" + CR + LF
   ssdpStr = ssdpStr + CR + LF

   ssdpAddr = CreateObject ("roSocketAddress")
   ssdpAddr.SetAddress ("239.255.255.250:1900")

   ssdp = CreateObject ("roDatagramSocket")
   ssdp.SetMessagePort (port)
   ssdp.SetSendToAddress (ssdpAddr)
   ssdp.NotifyReadable (True)
   ssdp.SendStr (ssdpStr)

   ecpAddrList = {}

   ut = CreateObject ("roUrlTransfer")
   ut.SetPort (port)

   ts = CreateObject ("roTimespan")
   sleepTimeMs = SLEEP_TIME_MINS * 60 * 1000

   While True
       elapsed = ts.TotalMilliseconds ()
       If elapsed >= sleepTimeMs
           ts.Mark ()
           elapsed = 0
           For Each ecpAddr In ecpAddrList
               ping (ut, ecpAddr)
           End For
           ssdp.SendStr (ssdpStr)
       End If

       msg = Wait (sleepTimeMs - elapsed, port)

       If Type (msg) = "roSocketEvent"
           If msg.GetSocketId () = ssdp.GetId ()
               If ssdp.IsReadable ()
                   recvStr = ssdp.ReceiveStr (4096)
                   ma = CreateObject ("roRegex", "\r\nLocation:\s*(.*?)\s*\r\n", "i").Match (recvStr)
                   If ma.Count () = 2
                       ecpAddr = ma [1]
                       If Not ecpAddrList.DoesExist (ecpAddr)
                           ecpAddrList.AddReplace (ecpAddr, 0)
                           ping (ut, ecpAddr)
                       End If
                   End If
               End If
           End If

       Else If Type (msg) = "roImageCanvasEvent"
           If msg.IsRemoteKeyPressed ()
               key = msg.GetIndex ()
               If key <> 9200 And key <> 9300  ' Alarm Clock Key Press/Release '
                   Exit While
               End If
           End If
       End If
   End While
End Sub

Sub ping (ut As Object, ecpAddr As String)
   ecpReq = ecpAddr + "keypress/Lit_%E2%8F%B0" ' Alarm Clock (U+23F0) '
   ut.SetUrl (ecpReq)
   ut.PostFromString("")
End Sub
https://github.com/belltown/
0 Kudos
squirreltown
Level 9

Re: FInd box on LAN

Awesome, thank you.
Kinetics Screensavers
0 Kudos
EnTerr
Level 9

Re: FInd box on LAN

"squirreltown" wrote:
As was inevitable, I've built a thing that is too complex, uses too much bandwidth, and is too artsy to be a screensaver so I've decided it's an installation. The Pi is a web server, holds all the files and does Imagemagick processing. So anything I can do to make it slicker while demonstrating it seems worth the effort. I think I will try @belltown 's way first, but what you describe sounds like second choice for sure, If I get there I'll ask again about how I might go about that.

I see... well here is a 3rd option Smiley Happy - you can have RaspberryPi discover your Roku via SSDP (a few lines in Python, say) and then notify your channel about its own IP address via ECP /launch or /input. That way you won't have to find and implement UPnP server on R-Pi, something which (put understatedly) is an overkill
0 Kudos
squirreltown
Level 9

Re: FInd box on LAN

Ok, so I made it work this way. Python file on Pi is setup as a looping service, sends the Pi i.p. to any roku device via ECP/input. The only issue is that the Python socket function that actually finds the roku ip seems to randomly find the boxes. i have 4 rokus, and it will find all of them within a few seconds, but it's random, if I had a large network it would be an issue. I'm going to try the more complex way but this looked like fruit i could reach.
Kinetics Screensavers
0 Kudos
renojim
Level 10

Re: FInd box on LAN

Would something like this combined with belltown's code be useful?  I was going to go through the Python code I have (fauxmo) that allows Alexa (Amazon Echo) to discover my Dockstar to see how it works, but then I stumbled upon that code.  I haven't tried it, but seems like it should work for SSDP discovery.

-JT
0 Kudos
squirreltown
Level 9

Re: FInd box on LAN

"renojim" wrote:
Would something like this combined with belltown's code be useful?  I was going to go through the Python code I have (fauxmo) that allows Alexa (Amazon Echo) to discover my Dockstar to see how it works, but then I stumbled upon that code.  I haven't tried it, but seems like it should work for SSDP discovery.

-JT

Hey thanks, that looks interesting too. I'm just learning python, and obviously the great thing about non-Brightscript languages is you can find enough examples in the wild to cobble together whatever you are trying to build.
Kinetics Screensavers
0 Kudos
EnTerr
Level 9

Re: FInd box on LAN

Deploying yet another UPnP server so that one box can find another? We are so wielding a microscope to hammer a nail here.

"squirreltown" wrote:
The only issue is that the Python socket function that actually finds the roku ip seems to randomly find the boxes.

Hmm, how exactly does this fn "actually find" the Roku IPs? I vaguely suspect something barbaric going on  :twisted:
0 Kudos
squirreltown
Level 9

Re: FInd box on LAN

EnTerr:Hmm, how exactly does this fn "actually find" the Roku IPs? I vaguely suspect something barbaric going on  :twisted:

Since you are going to read this, mind telling me why it doesn't work with python3? Smiley Wink  


import requests
import curses
import socket
import string
import urllib


DISCOVER_GROUP = ('239.255.255.250', 1900)

DISCOVER_MESSAGE = '''\
M-SEARCH * HTTP/1.1\r\n\
Host: %s:%s\r\n\
Man: "ssdp:discover"\r\n\
ST: roku:ecp\r\n\r\n\
''' % DISCOVER_GROUP


def find_roku():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.sendto(DISCOVER_MESSAGE, DISCOVER_GROUP)
    data = s.recv(1024)
    s.close()
    return data

def get_ip_address():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.connect(('8.8.8.8', 80))
    return s.getsockname()[0]

def keypress(url, key):
    request_url = url + key
    requests.post(request_url)

class HTTPResponse(object):
    def __init__(self, response_text):

        response = response_text.split('\r\n')
        status_line = response[0].split()

        self.http_version = status_line[0]
        self.status_code = status_line[1]
        self.status = status_line[2]
        self.headers = {}

        for line in response[1:]:
            line = line.split()
            if len(line) == 2:
                header_name = line[0][:-1]
                header_value = line[1]
                self.headers[header_name.lower()] = header_value.lower()


def main():
    value = get_ip_address() 
    print ('Searching for a Roku device...')
    while True:
        response_text = find_roku()
        response = HTTPResponse(response_text)
        url=response.headers['location']
        print ('Found Roku@'+url[7:-6])
        send = 'input?ip='+value
        keypress(url, send) 
       
    
    


if __name__ == '__main__':
    main()

Kinetics Screensavers
0 Kudos
EnTerr
Level 9

Re: FInd box on LAN

"squirreltown" wrote:
...

Since you are going to read this, mind telling me why it doesn't work with python3? Smiley Wink  

Not bad actually, where did you get the code?
Re Python 3 - no clue, i haven't moved to Py3k and just go with what comes in OSX (2.7). Generally code is compatible with minor tweaks though (e.g. py2 "print foo" -> py3 add parentheses "print(foo)")

For posterity here is something i whipped today being presumptuous you did not have the SSDP discovery code:

#!/usr/bin/python

def get_ecp_urls():
   import socket, re
   from time import time

   # send a SSDP M-SEARCH looking for a Roku
   sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
   sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
   MCAST_GRP = '239.255.255.250' # local network ssdp multicast address
   qry = 'M-SEARCH * HTTP/1.1|HOST: %s:1900|MAN: "ssdp:discover"|ST: roku:ecp||' % MCAST_GRP
   qry = qry.replace('|','\r\n')
   sock.sendto(qry, (MCAST_GRP, 1900))

   rokus = set()
   sock.settimeout(0.1)
   clk = time()
   while time() < clk + 1: # wait for a whopping second to hear back
       try:
           dat, ip = sock.recvfrom(1024)
           rokus.update(re.findall('\r\nLOCATION:\s*(.*)\r\n', dat, re.I))
       except socket.timeout: pass
       except socket.error, err: print "SSDP ERROR:", err
   
   return sorted(rokus)


# demo run
print(get_ecp_urls())
0 Kudos
squirreltown
Level 9

Re: FInd box on LAN

"EnTerr" wrote:

Not bad actually, where did you get the code? 

One example for the network stuff, another for grabbing the local ip and I re-wrote main(). The roku returns it's serial # in the response.headers so it's easy to keep track that way if needed.
Kinetics Screensavers
0 Kudos