Developing IoT apps: connecting to smart devices with Android

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.

About the author.