FInd box on LAN

Yes, it is not the most efficient - it works by attrition. Typical run, find roku# - 2,2,1,2,3,1,3,4. So, it finds them eventually, because it’s looping . No clue why it picks one or the other in the first place - perhaps the most recent roku broadcast?
It’s only been a day but no hiccups so far with 4 rokus.

“squirreltown” wrote:
Yes, it is not the most efficient - it works by attrition. Typical run, find roku# - 2,2,1,2,3,1,3,4. So, it finds them eventually, because it’s looping . No clue why it picks one or the other in the first place - perhaps the most recent roku broadcast?
It’s only been a day but no hiccups so far with 4 rokus.
Ah… when there are 4 players listening, the likelihood of all 4 datagrams being lost is very low. Say if probability of loss is 5%, then for a “quadruple whammy” to happen it will take upwards of 100,000 calls.

But allow me to demonstrate - unplug all but 1 Rokus and try running in a loop again, it should hiccup within 100 tries.
Or unplug all Rokus and it should hiccup on the 1st try :slightly_smiling_face:

“squirreltown” wrote:
How does a media player find a server? The pi is setup as a webserver. The Roku is set up to enter the I.P. manually now. Sure would be nice…
This will be the simplest option, i think. Here, i wrote you this:


function discover_my_server_ip() as String:
  port = createObject("roMessagePort")
  for each hash in createObject("roDeviceInfo").getIpAddrs().items():
    octets = hash.value.split(".")
    xfers = { }
    for i = 1 to 254:
      octets[3] = i.toStr()
      url = "http://" + octets.join(".") + "/path/unique/to/squirreltown"
      req = createObject("roUrlTransfer")
      xfers[req.getIdentity().toStr()] = req
      req.setUrl(url)
      req.setMessagePort(port)
      req.asyncHead()
    next 
    while not xfers.isEmpty():
      msg = port.waitMessage(0)
      if type(msg) = "roUrlEvent":
          if msg.getResponseCode() = 200 then return msg.GetTargetIpAddress()
          xfers.delete(msg.GetSourceIdentity().toStr())
      end if
    end while
  next
  return ""
end function

Change “/path/unique/to/squirreltown” to some URL that’s unique to your server and try it out. If the server is on the network, it finds it almost instantly. If not, it may take a while until all probes fail.

@belltown @EnTerr
I’ve taken EnTerr’s ECP solution and made it into a nice system where you send it some text and flies it across the Roku screen.
As for finding the Pi from the Roku, I now have a python file on the Pi broadcasting a UDP message every second. - “piip:”. An SDK socket function on the Roku receives this fine.
So i’ve solved the problem two ways, and am still going for the third.
I’m still not able to see the Pi with belltown’s UPnP code. I’ve changed

"ST: roku:ecp"  to "ST:ssdp:all"

And it finds all my Rokus, 2 NAS and DSL modem. I’ve noticed that if take the Pi python file and send an “M-SEARCH” formated message instead of my own, Wireshark will list the protocol as SSDP instead of UDP, but belltown’s code still won’t see it. I’m wondering what it is I’m not sending. Why can’t my Pi be a grown-up box?

What is the Pi sending, and how is it sending it, in order to respond to an M-SEARCH request that your Roku sends?

Here’s a little Python 3 program that you can run on your Pi to respond to M-SEARCH requests from a Roku. As it’s written right now, it impersonates a Roku device, but you can change the “ST” field to something else, and make sure the Roku code searches for the same value.

import socket
import struct

MSEARCH_RESPONSE = ('HTTP/1.1 200 OK\r\n'
                    'Cache-Control: max-age=3600\r\n'
                    'ST: roku:ecp\r\n'
                    'USN: uuid:roku:ecp:H0A0AP999999\r\n'
                    'Ext:\r\n'
                    'Server: Roku UPnP/1.0 MiniUPnPd/1.4\r\n'
                    'Location: http://{ipAddr}:8060/\r\n'
                    '\r\n')

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('', 1900))
addMembershipData = struct.pack('4sl', socket.inet_aton('239.255.255.250'), socket.INADDR_ANY)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, addMembershipData)

while True:
    data, addr = sock.recvfrom(4096)
    if data.startswith(b'M-SEARCH'):
        response = str.encode(MSEARCH_RESPONSE.format(ipAddr=addr[0]))
        sock.sendto(response, addr)

I have only one question and that is “why?”
Above I gave code that will instantly find the normal web server, does not need extra service to run and does not rely on multicast (support for which is wonky in many home routers). Why do we keep beating the SSDP horse?

It’s just another way to beat a dead horse, or skin a dead cat.

I’ve done it both ways (subnet scan and SSDP). Granted, SSDP, being connectionless, is flaky, so when doing an SSDP discovery, I’ll usually send out several M-SEARCH requests. At least one will almost always succeed.

One issue with your subnet scan method is that it assumes your local network is configured to use a 24-bit subnet id (8-bit host id). That may be common for most home networks, but it’s by no means guaranteed. Ideally, you’d have to figure out the subnet mask somehow and use that determine how many possible hosts to scan. That’s a lot more network traffic if you’ve configured a 16-bit subnet id for, example.

“belltown” wrote:
One issue with your subnet scan method is that it assumes your local network is configured to use a 24-bit subnet id (8-bit host id). That may be common for most home networks, but it’s by no means guaranteed. Ideally, you’d have to figure out the subnet mask somehow and use that determine how many possible hosts to scan. That’s a lot more network traffic if you’ve configured a 16-bit subnet id for, example.
The subnet mask is not related - but yes, i assumed that the 2 hosts share the first 3 octets. The odds of that in SOHO network are so high that i’d take such bet any day of the year that’s not 2/29 :). I just won’t try my hooliganism to scan >254 hosts. I had the code that maintained pool of pending async requests (Roku survives up to 500 simultaneous or so) but stripped it for simplicity of the example; i had better reason for it before.

“belltown” wrote:
Here’s a little Python 3 program that you can run on your Pi to respond to M-SEARCH requests from a Roku. As it’s written right now, it impersonates a Roku device, but you can change the “ST” field to something else, and make sure the Roku code searches for the same value.
Thank you! I added a print to see what was happening and then added a function to insert the local ip address into the MSEARCH response and am now getting this Pi to show up in the ECPaddr list in your roku code. I’m pretty sure that’s it - I can fill in all the rest. There are a bunch of python examples like this, but they are all written slightly differently - yours wins.

EnTerr - Here I am trying to be less ignorant and you’re complaining. :slightly_smiling_face:

“squirreltown” wrote:
I … added a function to insert the local ip address into the MSEARCH response
The code I posted already does that. Unless you mean you re-wrote that part as a function to make it clearer, which is always a good idea.

“belltown” wrote:

“squirreltown” wrote:
I … added a function to insert the local ip address into the MSEARCH response
The code I posted already does that. Unless you mean you re-wrote that part as a function to make it clearer, which is always a good idea.
Hey, i’m totally not knowing this python thing, i guess {ipAddr} is supposed to grab it? I did this. Thanks again, I’ll make the xml file and finish this mañana.

import socket
import struct

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('', 1900))
addMembershipData = struct.pack('4sl', socket.inet_aton('239.255.255.250'), socket.INADDR_ANY)
sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, addMembershipData)

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

MSEARCH_RESPONSE = ('HTTP/1.1 200 OK\r\n'
                    'Cache-Control: max-age=3600\r\n'
                    'ST: raspberr\r\r\n'
                    'Location: http://'+get_ip_address()+':8060/\r\n'
                    '\r\n')
while True:
    data, addr = sock.recvfrom(4096)
    if data.startswith(b'M-SEARCH'):
        print data
        response = str.encode(MSEARCH_RESPONSE.format(ipAddr=addr[0]))
        sock.sendto(response, addr)

You’re right, you do need to get your local address and pass that into the M-SEARCH string. In my example, I was passing in the address obtained from recvfrom(), which only works if the client and server are running on the same machine, as they were when I “tested” it.

What I would do though is leave the M-SEARCH statement as I originally had it, but change:

response = str.encode(MSEARCH_RESPONSE.format(ipAddr=addr[0]))

to:


response = str.encode(MSEARCH_RESPONSE.format(ipAddr=get_ip_address())

or, use your version of the MSEARCH_RESPONSE statement, and change the above line to:


response = str.encode(MSEARCH_RESPONSE)

“belltown” wrote:
You’re right, you do need to get your local address and pass that into the M-SEARCH string. In my example, I was passing in the address obtained from recvfrom(), which only works if the client and server are running on the same machine, as they were when I “tested” it.
Should just do sock.getsockname() on the socket at hand instead of the contraption to connect google DNS. Not only faster/simpler but also more “righteous” - imagine a multihomed (>1 IP) host, where Roku is on a different interface than the internet.

“squirreltown” wrote:
EnTerr - Here I am trying to be less ignorant and you’re complaining. :slightly_smiling_face:
it pains me to see a sledgehammer used to crack nuts :smiling_face_with_sunglasses:
also i am firm believer that the more complicated (more elements, more interactions, more points of failure) a system is, the more fragile it is.

“EnTerr” wrote:

“belltown” wrote:
You’re right, you do need to get your local address and pass that into the M-SEARCH string. In my example, I was passing in the address obtained from recvfrom(), which only works if the client and server are running on the same machine, as they were when I “tested” it.
Should just do sock.getsockname() on the socket at hand instead of the contraption to connect google DNS. Not only faster/simpler but also more “righteous” - imagine a multihomed (>1 IP) host, where Roku is on a different interface than the internet.
In Python, getsockname() is a socket function exported by the socket module, not a socket method, so you wouldn’t call it on the socket object; i.e. you’d call socket.getsockname(), not sock.getsockname(). Also, it would return ‘0.0.0.0’ if sock.bind((‘’, 1900)) was used – at least it does on my system. Sometimes socket.gethostbyname(socket.gethostname()) works; however, depending on which OS you use, and how your hosts file is set up, you may get back a localhost address (127.0.1.1 in my case). I think squirreltown’s method has a better chance of determining the local ip address, providing you can connect to Google DNS. If you can’t then you probably have other problems to worry about.

“EnTerr” wrote:
the more complicated (more elements, more interactions, more points of failure) a system is, the more fragile it is.
That’s a true statement for sure.