Experiments with Avahi and Python-Zeroconf

I finally got it working today. So I used Avahi to announce the services I had running on the Beaglebone Black. I had a flask server running just to test it out. Here's the code:

from flask import Flask  
app = Flask(__name__)  
@app.route('/')
def home():  
    return "Hello World"

if __name__ == "__main__":  
    app.run(host='0.0.0.0', port=9999, debug=True)

Simple enough, going to the ip:9999 should give you "Hello World" as the response. So, now let's announce it.

sudo apt-get install avahi-utils  

That should install avahi-daemon if it's not already installed. Great now, go to /etc/avahi/services/ and create a new service. I called mine flaskwebserver.service

sudo vim flaskwebserver.service  
<?xml version="1.0" standalone='no'?><!--*-nxml-*-->  
<!DOCTYPE service-group SYSTEM "avahi-service.dtd">  
<service-group>  
<name replace-wildcards="yes">%h flaskwebserver</name>  
<service protocol="ipv4">  
<type>_http._tcp</type>  
<port>9999</port>  
<txt-record>path=/</txt-record>  
</service>  
</service-group>  

That's done, just reload the daemon

sudo service avahi-daemon restart  

And then check if the service is running by

sudo avahi-browse --all  

On the client side where you actually need to know what services are running, I used Python-Zeroconf and the script is from its examples

from six.moves import input  
from zeroconf import ServiceBrowser, Zeroconf


class MyListener(object):  
    def remove_service(self, zeroconf, type, name):
        print("Service %s removed" % (name,))

    def add_service(self, zeroconf, type, name):
        info = zeroconf.get_service_info(type, name)
        # print name, info.get_name(), info.server,
        print name, info


zeroconf = Zeroconf()  
listener = MyListener()  
browser = ServiceBrowser(zeroconf, "_http._tcp.local.", listener)  
try:  
    input("Press enter to exit...\n\n")
finally:  
    zeroconf.close()

And voila! It displays all the services that go up and come down

beaglea5c flaskwebserver._http._tcp.local. ServiceInfo(type='_http._tcp.local.', name=u'beaglea5c flaskwebserver._http._tcp.local.', address='\xc0\xa8\x01\x8f', port=9999, weight=0, priority=0, server=u'beaglea5c.local.', properties={'path': '/'})  

Now there are several good ways to get to the webserver from your browser. One would be to just take the server address and the port and it'll resolve automatically. So, in this case, http://beaglea5c.local:9999 should give you a "Hello world". But what if it's unable to resolve somehow? Well, in that case the address should give us the IP.

address = '\xc0\xa8\x01\x8f'  
# they are hex values
ipv4_address = ':'.join(str(ord(i)) for i in address)  

Which essentially gives us 192.168.1.143. So there we are, announcing services on a local network. Of course if it's anything other than that, then this won't work.