Implementing a Zeroconf service for your home automation Android app

The concept of Home Automation has been around for years. From sprinkler systems that run on their own schedule to lights and windows operated by voice commands, people have always been inventing ways to make our lives easier and our home more “automatic”. Now that smartphones are serving us in countless ways, it’s obvious that they will eventually take part in helping us control our homes as well.
Zeroconf is a set of technologies dedicated to easily creating TCP/IP networks from interconnected devices, without having to call your computer genius brother-in-law to help you set it up.
I am going to show you how you can use your Android device to discover and gather data from services and devices within your home network, by implementing a Zeroconf service with no more than a few lines of code. From there, the things you can do will be limited only by your imagination…

Why Do I Want a Zeroconf service?

Say you have a printer, and you want to print a pdf file that is currently on your smartphone. You can try sending the file to a computer that’s connected to the printer and have that computer print the file, transfer the file directly to the printer for printing via USB, or even use third party services like Google’s Cloud Print.
But the easiest option would probably be looking for an app that will connect your phone to the printer and send the file directly from your phone. Apps like this can be easily implemented with a  Zeroconf service, like Avahi or Bonjour.
The Zeroconf service handles the process of discovering, registering and configuring the services available in a network.
Zeroconf services rely on protocols like MDNS, DNS-SD, and LLMNR (the least popular of these three). The main advantage of Zeroconf is that everything pretty much happens by itself, and there is no need for any manual configuration by the user or any special configuration services like DHCP or DNS (hence the name “Zero-Conf”). Meaning having 2 devices communicate with each other can be as simple as connecting a cable between them, to quote the Zeroconf website: “without needing a man in a white lab coat to set it all up for you”.

Implementations

We will show and discuss 2 popular Zeroconf implementations:

  1. jMDNS – The most popular java implementation of a Zeroconf service, supported by Froyo, API level 8 and up.
  2. Nsd (short for Network Service Discovery) – Google’s own implementation of a Zeroconf service, supported by Jelly Bean, API level 16 and up.

Each implementation has its own advantages over the other. Nsd is already included in the Android SDK, works seamlessly with it, and is a bit simpler to use. On the other hand,  jMDNS has been around for a bit longer (which probably means it has less bugs) and is pure Java, which means you can use the same code for any Java application. Each developer can choose the implementation he wants to use according to his/her needs.

jMDNS

First, download the library from the jMDNS website and include it in your project’s Java Build Path.
Next, you will need to give the application to access the internet and the device’s wifi state. Also, since this process involves sending multicast packets, and this is disabled by default on Android devices to save battery, the device will also need permission to change the wifi module’s multicast state. To add these permissions, add the following lines to your project’s Android manifest.xml file:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />

Before you can start scanning, you will need to allow multicast packets by creating a multicast lock to acquire when we start scanning:

WifiManager wm = (WifiManager) yourActivity.getSystemService(Context.WIFI_SERVICE);
MulticastLock multicastLock = wm.createMulticastLock(getClass().getName());
multicastLock.setReferenceCounted(false);

To start scanning, we will need to: get the device’s IP address as an InetAddress object (you will need to create one), acquire a multicast lock, call the jMDNS constructor and add service type and service listeners as you wish:

final InetAddress deviceIpAddress = getDeviceIpAddressMethod(wm);
multicastLock.acquire();
jmdns = JmDNS.create(deviceIpAddress, HOSTNAME);
jmdns.addServiceTypeListener(listner);
jmdns.addServiceListener(serviceType, listener);

To stop scanning, you will need to release the multicast lock (very important) and close the jMDNS object:

multicastLock.release();
jmdns.unregisterAllServices();
jmdns.close();
jmdns = null;

Important Note: The jMDNS object takes about 5-6 seconds to close (don’t worry, this happens asynchronously in the background and won’t bother your UI), so once you stop scanning, you can’t start scanning again for 5-6 seconds.

Now that we have the scanner up and running, we need to handle the callbacks. To do so, all you need to do is have a class that implements the “ServiceListener” and “ServiceTypeListener” interfaces (can also be the same class that handles the scanning) and implement their methods:

//ServiceListener interface methods
@Override
public void serviceAdded(ServiceEvent service) {
//do stuff...
}
@Override
public void serviceRemoved(ServiceEvent service) {
//do stuff...
}

@Override
public void serviceResolved(ServiceEvent service) {
//do stuff...
}

//ServiceTypeListener interface method
@Override
public void serviceTypeAdded(ServiceEvent event) {
//do stuff...
}

And that’s it! You have a fully functional jMDNS scanning service!

Nsd

Using Nsd requires the same permissions as jMDNS does in the Android manifest.xml, don’t forget to add them. Now let’s initialize the NsdManager object:

NsdManager nsdMgr = (NsdManager) yourContext.getSystemService(Context.NSD_SERVICE);

To start scanning call the “discoverServices” method:

nsdMgr.discoverServices("SERVICE_TYPE", NsdManager.PROTOCOL_DNS_SD, discoveryListener);

To stop scanning call the “stopServiceDiscovery” method:

nsdMgr.stopServiceDiscovery(discoveryListener);

Now that we have the scanner up and running, we need to handle the callbacks. To do so, have a class implement the “DiscoveryListener” and “ResolveListener” interfaces (can be the same class that handles the scanning) and implement their methods:

//DiscoveryListener interface methods
@Override
public void onStopDiscoveryFailed(String serviceType, int errorCode) {
//do stuff
}

@Override
public void onStartDiscoveryFailed(String serviceType, int errorCode) {
//do stuff
}

@Override
public void onServiceLost(NsdServiceInfo service) {
//do stuff
}

@Override
public void onServiceFound(NsdServiceInfo service) {
//here we usually resolve the new discovered service by calling:
//nsdMgr.resolveService(service, resolveListener);
}

@Override
public void onDiscoveryStopped(String serviceType) {
//do stuff
}

@Override
public void onDiscoveryStarted(String serviceType) {
//do stuff
}

//ResolveListener interface methods
@Override
public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
//do stuff
}

@Override
public void onServiceResolved(NsdServiceInfo serviceInfo) {
//do stuff
}

And that’s it! Now you have a fully functional Nsd scanning service!

As you can see, both implementations follow pretty much the same pattern and preform more or less the same actions, but work a bit differently. This is also just the discovery stage, which is the first stage of building this kind of app. From here we will need to apply filters that uncover only the devices we want, and setup a way of communicating with them. This is even before talking about security, limitations, UI, etc…
I have implemented this type of service for a few of my projects, and have used both jMDNS and Nsd. My current favorite is jMDNS, mainly because it supports older versions of Android (because people still use Froyo, Gingerbread, and Ice Cream Sandwich whether we like it or not). Personally, I would recommend trying both before picking a favorite. Perhaps even implementing a general “helper” class for each one so you’ll have 2 different drop-in scanners whenever you need them.

Feel free to ask questions and share your own thoughts and ideas in the comments below.

Enjoy! 🙂

Oren

5 thoughts on “Implementing a Zeroconf service for your home automation Android app

  1. Hy Oren, thanks a lot for the nice article! I’ve been trying to use nsd but there are two issues I don’t know how to circumvent. I appreciate if you can shed some light on it:
    1. the mdnsd service consumes a lot of battery in the background just to keep the simple chat app working
    2. if app exits unexpectedly, the already started nsd service hangs in the background and cannot go away till restarting phone.

    Thanks a lot!

    1. Hey Bo, thanks for reaching out! I’m glad you enjoyed the article. 🙂
      Regarding your questions:

      1. By default, the device’s wifi module is programmed to filter out packets that aren’t addressed to the specific device. Implementing this kind of service (be it with Nsd or jMDNS) involves acquiring a multicast lock, causing the wifi module to also receive and process multicast packets. This will usually cause a substantial increase in battery drain.
      The only solution to this problem (except perhaps for leaving the devices constantly connected to a power outlet) is to try using this service as little as possible. If you’re implementing a chat app, perhaps you should consider using the service only to discover the devices around you (i.e. scan for 15-20 seconds once every 5 minutes), and use GCM or some kind of backend API to send and receive messages. You can also use GCM or your backend to notify when a new device has “entered/left the chatroom”, so the other devices know when they need to start scanning again to refresh the devices list.
      You can read more about efficient data transfer here.

      2. This is actually expected behavior, because the app has quit unexpectedly before releasing the multicast lock and stopping the scan. Naturally the device’s wifi module will continue to receive and process multicast packets until it is told otherwise.
      There are a few approaches for handling this. The best one I can think of is to implement a watchdog service like this one. This service basically monitors the UI thread periodically to check if the app is still responding, and has a callback function that runs when ANR (Application Not Responding) is detected. You can override the callback function to do whatever you wish (i.e. stop the scan, or even re-launch the app).

      Hope this helps.

  2. Pingback: Determine your Android device's internet connection status - Video Platform Development | Video Streaming Solutions | PandaOS

  3. Hi.
    Thanks for the article. Could you give us any example about how to config the Zeroconf to send message at all the devices android?
    Best Regards
    Marco

    1. Hi Marco, thanks for reaching out!
      MDNS is actually only used for advertising and discovering devices over your network. The way you send messages to the devices you found is all up to you.
      The article currently only covers the discovery part. I’ll update it and add some info about how to register or unregister your own device as a service, so all of the devices with the app installed can discover each other.

      As for actually sending the messages, there are many different methods. I think that GCM would be a good choice, although it will require a 3rd party server or push notification service like Pushwoosh or Parse. Another neat method is broadcasting the messages with UDP packets.
      I’ll write a new post in the next few days, demonstrating how to send and receive UDP packets over a Wifi network.
      If you can’t wait, you can find good implementations here and here. Either way, I’ll keep you posted… 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *