Developing IoT apps: connecting to smart devices with Android

Summary
  • The article presents three approaches for discovering and connecting to IoT devices on a network when developing IoT apps.
  • The first approach is manual network scanning, which involves calculating the IP range from the netmask, pinging addresses, and opening sockets on predefined ports to identify devices.
  • The second approach uses zeroconf/Bonjour via Android's Network Service Discovery (NSD), where devices broadcast services and listeners resolve matching services to obtain IP and port information.
  • The third approach uses the AllJoyn® framework from the AllSeen Alliance, which abstracts the transport layer through Sessions and a virtual Bus, enabling devices to discover each other and exchange messages via signals.

Internet of Things (IoT) is no longer something reserved for the future or limited to the realm of ideas. It is real and it’s begun to find its way into our homes turning lamps, locks, security cameras, and several other home appliances into smart devices controlled by your smartphone.

The crucial point, as a developer, is to know how to find and access these devices, allowing people to use this technology to its best extent. To help you in this process, this article lists some of the most popular approaches to finding and connecting to any device in your network when developing IoT apps, and presents some code snippets and examples to get you up and running in no time.

1. DIY – Network scanning

Let’s start with the most basic way, because not all devices implement fancy service discovery protocols. In this example, we need to do all the discovery and connection ourselves. So we roll up our programming sleeves, get our local network netmask and calculate the range of possible IPs.


public int getIpv4Netmask(int deviceIP) {
  try {
     final byte[] bytes = BigInteger.valueOf(deviceIP).toByteArray();
     final InetAddress netmaskAddress = InetAddress.getByAddress(bytes);
     NetworkInterface networkInterface = NetworkInterface.getByInetAddress(netmaskAddress);
     for (InterfaceAddress address : networkInterface.getInterfaceAddresses()) {
        final short networkPrefixLength = address.getNetworkPrefixLength();
        if (networkPrefixLength <= 32) {
           return networkPrefixLength;
        }
     }
  } catch (UnknownHostException | SocketException e) {
     e.printStackTrace();
  }
  return 0;
}

public void someBiggerMethod() {
  // ...
 
  WifiManager wifiManger = (WifiManager) getSystemService(Context.WIFI_SERVICE);
  DhcpInfo dhcpInfo = wifiManger.getDhcpInfo();
  int ipAddress = (ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN)) ?
        Integer.reverseBytes(dhcpInfo.ipAddress) : dhcpInfo.ipAddress;
  int prefix = getIpv4Netmask(ipAddress);
  int netmask = 0;
  for (int j = 0; j < prefix; ++j) {
     netmask |= (1 << 31 - j);
  }
  int baseNetwork = ipAddress & netmask;
  int range = (int) Math.pow(2, Integer.SIZE - prefix) - 1;
  
  // ...
}

This gets you the base network address and the number of possible devices, which will be at least 255 for local networks, and therefore determines the length of our for loop. Inside this loop, we’ll ping every one of these addresses and log the ones that ACKed us for later connection.


byte[] pingIP;
InetAddress address;
for (int i = 0; i < range; ++i) {
  pingIP = BigInteger.valueOf(baseNetwork | i).toByteArray();
  address = InetAddress.getByAddress(pingIP);
  if (address.isReachable(50)) reachableHosts.add(address.getHostName());
}

Unfortunately, this only gets you a list of connected IPs, so there’s still an extra step in identifying whether a particular IP corresponds to your smart device or is the smartTV hanging on your wall. Since we’re close to the metal here, the obvious course of action is opening a socket and connecting to a pre-defined port.


InetAddress serverAddr = InetAddress.getByName(ipAddress);
Socket socket = new Socket(serverAddr, PREDEFINED_PORT);
PrintWriter out = new PrintWriter(new BufferedWriter(
     new OutputStreamWriter(socket.getOutputStream())), true);

If you get a valid connection that passes through your validation process, you just found your device! Otherwise, rinse and repeat until you find it. Needless to say that the above code, as every network operation in Android, must not run in the UI.

2. Automating the discovery – zeroconf/Bonjour

apple bonjour logo: developing iot apps Let’s be honest: scanning the network is pretty similar to starting a fire by rubbing sticks – it takes time and doesn’t seem very reliable. This is especially true if your network has the bad habit of losing some packages or has DHCP enabled, which may force you to execute the discovery task frequently.

To overcome this, there’s zeroconf (or Bonjour for Apple products), a blazing fast network discovery tool, with the downside of needing to be configured in your Android app and the IoT device.

The general idea is that one device broadcasts its services to the network and another listens to these broadcasts until it finds something of interest. The latter then resolves the IP of the advertiser of that service and can start communicating.

In Android, this goes by another name: Network Service Discovery (NSD). All you need to make it work is the NsdManager class and to know the name of the service you’re looking for.


mNsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
mDiscoveryListener = new NsdManager.DiscoveryListener() {

   @Override
   public void onDiscoveryStarted(String regType) {
      // ...
   }

   @Override
   public void onServiceFound(NsdServiceInfo service) {
       if (service.getServiceName().contains(myServiceName)){
           mNsdManager.resolveService(service, mResolveListener);
       }
   }

   @Override
   public void onServiceLost(NsdServiceInfo service) {
      // ...
   }
  
   @Override
   public void onDiscoveryStopped(String serviceType) {
      // ...
   }

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

   @Override
   public void onStopDiscoveryFailed(String serviceType, int errorCode) {
      // ...
   }
};
mNsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);

The service type represents the protocol and the transport layer on which the service operates. The name may be arbitrary and more helpful in finding the right device.

With the NsdManager.DiscoveryListener configured, all that’s left is resolving any matching service to get its IP and port number through a resolve service.


mResolveListener = new NsdManager.ResolveListener() {

   @Override
   public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
      // ...
   }

   @Override
   public void onServiceResolved(NsdServiceInfo serviceInfo) {
       mService = serviceInfo;
       Log.i(TAG, "IP: " + mService.getHost() + " Port: " +  mService.getPort());
   }
};

You will still need to do something with this IP, i.e. open a connection by yourself, maybe using the socket from the first section to send/receive messages. The next section will discuss how to avoid doing even that.

3. Transparent network – AllJoyn®

AllJoyn logo - developing iot android apps

Designed by the AllSeen Alliance, since 2013, a cross-company consortium composed by some big companies such as Microsoft, Philips, and AT&T, AllJoyn® defines a common protocol for device discovery and communication and has SDKs for all major platforms. In their words:

AllJoyn® is an open source software framework that makes it easy for devices and apps to discover and communicate with each other. Developers can write applications for interoperability regardless of transport layer, manufacturer, and without the need for Internet access.

The framework works with the concept of Sessions that are created by at least 2 devices connected through a virtual Bus. In Java, a certain device can emit and receive signals, which are messages broadcasted to any device belonging to the same Session.

So first we need to create a BusObject, which will be used to send our messages. However, to do so we need to instantiate a BusAttachment object which is our main interface with the framework API. The BusAttachment is the entity that allows us to connect to a session and find other devices.


private ChatService mChatService = new BusObject();
private BusAttachment mBus = new BusAttachment(APPLICATION_NAME, BusAttachment.RemoteMessage.Receive);

public void connectToAllJoyn() {
  org.alljoyn.bus.alljoyn.DaemonInit.PrepareDaemon(getApplicationContext());
  mBus.registerBusListener(mBusListener)
  mBus.registerBusObject(mChatService, OBJECT_PATH);
  mBus.connect()
}

You may be wondering what the BusListener object is. This is the interface used to notify when a new service is discovered or when a found one is lost. It will only work after we start our network discovery.


public void findService(String serviceName) {
  mBus.findAdvertisedName(serviceName)
  private ChatBusListener mBusListener = new BusListener {

    public void foundAdvertisedName(String name, short transport, String namePrefix) {
      If (name.equals(SERVICE_NAME) storeForLater(); 
    }

    public void lostAdvertisedName(String name, short transport, String namePrefix) {...}
  }
}

After you find the service with the name you were looking for, you should join it before being able to receive and send messages. Joining requires you to set the connection port, that was probably pre-defined, and other options related to the session you’re about to connect, which can be left empty if you’re not clear on them.


public void joinSession(String serviceName, int contactPort) {
  Mutable.IntegerValue sessionId = new Mutable.IntegerValue();
  SessionOpts sessionOpts = new SessionOpts(SessionOpts.TRAFFIC_MESSAGES, true, SessionOpts.PROXIMITY_ANY, SessionOpts.TRANSPORT_ANY);
  mBus.joinSession(serviceName, contactPort, sessionId, sessionOpts, mOnSessionLostListener();
}

To send messages, create a public method annotated with @BusSignalHandler and register it with registerSignalHandlers(), passing the object that contains that public method.


// Will search the class for @BusSignalHandler annotated methods
mBus.registerSignalHandlers(this); 

@BusSignalHandler(iface = "org.alljoyn.bus.samples.chat", signal = "Chat")
public void Chat(String message) {
	// Handle message
}

To send messages, you’ll need a @BusInterface annotated interface and a SignalEmitter from the Session as follows.


@BusInterface (name = "org.alljoyn.bus.samples.chat")
public interface ChatInterface {
   @BusSignal
   void Chat(String str) throws BusException;
}

private void sendMessages(String message) {
 SignalEmitter emitter = new SignalEmitter(mChatService, mSessionId, SignalEmitter.GlobalBroadcast.Off);
 mChatInterface = emitter.getInterface(ChatInterface.class);
 mChatInterface.Chat(message);
}

When the communication is done, you can leave session and disconnect.


mBus.leaveSession(mUseSessionId);
mBus.unregisterBusListener(mBusListener);
mBus.disconnect();

And that pretty much sums up the basic usage of AllJoyn®.

Wrapping up

Although not a comprehensive guide on all approaches to discovering and connection to devices in a network, this article sheds some light on how it can be achieved, be it by writing the whole protocol yourself or with the help of some frameworks.

Whichever approach you choose, it is a great time to invest in developing IoT apps and creating solutions for a connected home, car or work place, and if you need some help with that, Cheesecake Labs is a great match for you.

FAQ

What approaches to finding and connecting to IoT devices on a network are covered in this article?

The article covers three approaches: 1) DIY network scanning, where you manually calculate the network range and ping IPs; 2) Automating discovery with zeroconf/Bonjour, known as Network Service Discovery (NSD) on Android; and 3) Using AllJoyn®, a framework that provides a transparent network for discovery and communication.

How does the DIY network scanning approach work?

You obtain the local network netmask, calculate the range of possible IPs (at least 255 for local networks), and ping every address in that range, logging the ones that respond. Then you open a socket to a pre-defined port on each reachable IP to identify whether it corresponds to your smart device. All network operations must not run on the UI thread in Android.

What is zeroconf/Bonjour and how is it used on Android?

Zeroconf (or Bonjour for Apple products) is a network discovery tool where one device broadcasts its services and another listens until it finds something of interest. On Android it is called Network Service Discovery (NSD) and is implemented using the NsdManager class with a DiscoveryListener and a ResolveListener to obtain the IP and port of a matching service. It requires configuration on both the Android app and the IoT device.

What is AllJoyn® and who designed it?

AllJoyn® is an open source software framework designed by the AllSeen Alliance (a cross-company consortium including Microsoft, Philips, and AT&T, formed in 2013) that makes it easy for devices and apps to discover and communicate with each other regardless of transport layer or manufacturer, without needing Internet access. It has SDKs for all major platforms.

How does communication work in AllJoyn®?

AllJoyn® uses Sessions created by at least two devices connected through a virtual Bus. You create a BusAttachment and BusObject, register a BusListener, and call findAdvertisedName to discover services. After joining a session with a contact port, you can send messages using a @BusInterface annotated interface with a SignalEmitter, and receive messages via methods annotated with @BusSignalHandler. When done, you leave the session and disconnect.

About the author.