17 Oct 2015

DIY IOT - XMPP Chat client for Android

In an earlier blog post we had explained how a public XMPP chat server can be used to transmit messages between two computers. In that post we had shown how we can build a chatbot listener in Python that waits for messages and when messages arrive, there was a some simple logic that allowed it to either ring an alarm ( sound a bell tone) or insert the data into an SQLite database.

This chatbot -- ChatIOT -- was tested  by sending messages from a Xabber, a free XMPP chat client using two userids registered on a free, public XMPP server available at adastra.re. This post shows how to build a basic XMPP client for Android, from scratch, using Java and the Smack XMPP library from the Openfire opensource project.

Non-trivial Android app development is not for the faint hearted. If one has never developed an Android application in the past, it would be good idea to look through this post Building an Android App -- Getting Started.

Start Eclipse and create a new Android project with the following parameters :

Use the default options on the next couple of screens and then choose the template for Blank Activity


Click through the rest of screens until the project is created and is visible on the Package Explorer panel on the left of the Eclipse Screen.

Two files would have been opened in the Editor panel, but if not, open the file ChatIOT > res > layout > activity_main.xml Replace the contents of the file with the following code :
activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
   android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
   android:paddingRight="@dimen/activity_horizontal_margin"
   android:paddingTop="@dimen/activity_vertical_margin"
   android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
   
   <TextView
      android:id="@+id/textView1"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="@string/Heading1text"
      android:layout_alignParentTop="true"
      android:textColor="#104E8B"
      android:layout_centerHorizontal="true"
      android:textSize="30dp" />
   
   <ImageButton
        android:id="@+id/imageButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/textView1"
        android:layout_marginTop="15dp"
        android:layout_marginBottom="30dp"
        android:layout_centerHorizontal="true"
        android:src="@drawable/iotlogo_v1" />
   
   <Button
        android:id="@+id/button1"
        android:layout_width="120dp"
        android:layout_height="wrap_content"
        android:layout_below="@+id/imageButton"
        android:onClick="startCon"
        android:layout_marginBottom="10dp"
        android:layout_centerHorizontal="true"
        android:background="@drawable/pmbutton"
        android:text="@string/button1text" />
   
    <Button
        android:id="@+id/button2"
        android:layout_width="120dp"
        android:layout_height="wrap_content"
        android:layout_below="@+id/button1"
        android:layout_marginBottom="10dp"
        android:layout_centerHorizontal="true"
        android:onClick="pushData"
        android:background="@drawable/pmbutton"
        android:text="@string/button2text" />
    
    <Button
        android:id="@+id/button3"
        android:layout_width="120dp"
        android:layout_height="wrap_content"
        android:layout_below="@+id/button2"
        android:layout_marginBottom="10dp"
        android:layout_centerHorizontal="true"
        android:onClick="stopCon"
        android:background="@drawable/pmbutton"
        android:text="@string/button3text" />
    
    <TextView
         android:id="@+id/textView2"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:text="@string/Heading2text"
         android:textColor="#104E8B"
         android:textSize="20dp"
         android:layout_below="@+id/button3"
         android:layout_centerHorizontal="true"
         android:layout_marginBottom="40dp" />

</RelativeLayout>



This will show many errors because many of the required resources, artifacts are still missing.

Next, open the file ChatIOT > res > values > strings.xml and replace the contents with this code
strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">ChatIOT</string>
    <string name="action_settings">Settings</string>
    <string name="button1text">Connect</string>
    <string name="button2text">Send</string>
    <string name="button3text">Disconnect</string>
    <string name="Heading2text">xmpp chat for IOT</string>
    <string name="Heading1text">chatIOT</string>

</resources>


Create a new folder in ChatIOT > res and name it drawable. Inside this folder, create new, empty file called ChatIOT > res > drawable > pmbutton.xml. Replace the default contents of the file with the following code :

pmbutton.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
 <item android:state_pressed="true" >
     <shape android:shape="rectangle"  >
         <corners android:radius="3dip" />
         <stroke android:width="1dip" android:color="#5e7974" />
         <gradient android:angle="-90" android:startColor="#345953" android:endColor="#689a92"  />            
     </shape>
 </item>
<item android:state_focused="true">
     <shape android:shape="rectangle"  >
         <corners android:radius="3dip" />
         <stroke android:width="1dip" android:color="#5e7974" />
         <solid android:color="#58857e"/>       
     </shape>
 </item>  
<item >
    <shape android:shape="rectangle"  >
         <corners android:radius="3dip" />
         <stroke android:width="1dip" android:color="#5e7974" />
         <gradient android:angle="-90" android:startColor="#8dbab3" android:endColor="#58857e" />            
     </shape>
 </item>
</selector>


Finally download this 9.png image file
and store it in the folder ChatIOT > res > drawable-hdpi with the name iotlogo_v1.9.png Make sure that all files have been saved, then right click on project ChatIOT and press Refresh. After this the layout should be free of errors, though there will be some warnings.  The screen will now look like this :


Open the MainActivity.java file located in ChatIOT > src > com.yantrajaal.chatiot > MainActivity.java and replace the contents of file with the following code :

MainActivity.java

package com.yantrajaal.chatiot;

import java.io.IOException;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;

import android.util.Log;
import android.view.View;
import android.widget.Toast;
// Smack libraries 
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.SmackException.NotConnectedException;
import org.jivesoftware.smack.chat.ChatManager;
import org.jivesoftware.smack.chat.Chat;
import org.jivesoftware.smack.chat.ChatMessageListener;
import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;

import java.util.Random; // required for Random Number Generation

public class MainActivity extends Activity {
 String msg = "PM1139 : ";
 
 XMPPTCPConnectionConfiguration configChatIOT = XMPPTCPConnectionConfiguration.builder()
            .setUsernameAndPassword("uid2", "password2")
            .setServiceName("adastra.re")
            .setHost("adastra.re")
            .setPort(5222)
            .setResource("chatIOT")
            .build();
    
    AbstractXMPPConnection conxChatIOT = new XMPPTCPConnection(configChatIOT);
 /** Called when the activity is first created. **/
 
 @Override
    public void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       SmackConfiguration.setDefaultPacketReplyTimeout(10000);
       Log.d(msg, "The onCreate() event");
    }

 private class pmConnect extends AsyncTask<Void, Void, Void> {
      protected Void doInBackground(Void...dummy) {
       
       try {
        conxChatIOT.connect();
        conxChatIOT.login();
    } catch (SmackException | IOException | XMPPException e) {
     // TODO Auto-generated catch block
     e.printStackTrace();
    } 
         return null;
      }
/*
// this piece of code is kept just for the sake of completeness of Async Tasks
      protected void onProgressUpdate(Void...progress) {
         // setProgressPercent(progress[0]);
      }

      protected void onPostExecute(Void...result) {
          //showDialog("Downloaded  bytes");
      }
*/
  }
 private class pmSend extends AsyncTask<Void, Void, Void> {
      protected Void doInBackground(Void...dummy) {
       Random randomGenerator = new Random();
       int pseudoSensorData;
        
   // Assume we've created an XMPPConnection name "conxChatIOT"._
    ChatManager chatmanager = ChatManager.getInstanceFor(conxChatIOT);
    
    Chat ChatIOT = chatmanager.createChat("uid3@adastra.re", new ChatMessageListener() {
     public void processMessage(Chat chat, Message message) {
      System.out.println("Received message: " + message);
     }
    });
    
    for(int i=0 ; i < 4 ; i++)
    {
        
     try {
      if (i==0){
       ChatIOT.sendMessage("Sound 2");
      } else {
      // this data needs to be generated from some Android physical sensor
       // instead of using a random number generator
       pseudoSensorData = randomGenerator.nextInt(100);
       String payLoad = "Push " + String.valueOf(pseudoSensorData);
      ChatIOT.sendMessage(payLoad);
      }
    } catch (NotConnectedException e1) {
     // TODO Auto-generated catch block
     e1.printStackTrace();
    }
     try {
         Thread.sleep(5000);                 //1000 milliseconds is one second.
     } catch(InterruptedException ex) {
         Thread.currentThread().interrupt();
     }
    }
         return null;
      }

  }
 
    //Method to start the Connection
    public void startCon(View view) {
     Toast.makeText(this, "Connecting", Toast.LENGTH_LONG).show();
     new pmConnect ().execute();
    }
    
  //Method to start the Connection
    public void pushData(View view) {
     Toast.makeText(this, "Push Data", Toast.LENGTH_LONG).show();
     new pmSend ().execute();
    }
    
  //Method to Quit 
    public void stopCon(View View) {
     Toast.makeText(this, "Disconnection", Toast.LENGTH_LONG).show();
     conxChatIOT.disconnect(); 
    }
  
}


This will immediately, throw lots and lots of errors because the Smack/XMPP libraries have not been loaded as yet. To get the libraries perform the following steps 

1. Go to the Smack 4.1 Readme and Upgrade Guide and go to the part for Using Eclipses Android Development Tools (ADT) Ant Based Build
2. Download a python script getMavenArtifactsNG.py  This is in Python 3. Hopefully you have that!
3. Create an artifacts.csv file with the text information given in the paragraph
4. Execute the command ./getMavenArtifactsNG.py -f artifacts.csv -p <path_to_Eclipse_Workspace_Directory>/ChatIOT which is the project directory. This will populate the libs and libs-sources directory with a zooful of jar files!

Refresh the project and this will remove all errors EXCEPT on the lines that refer to the ChatManager. For some reason, the script given above does not load the smack-IM libraries!

5. Go to the downloads section of the Smack library and download the smack-4.1.4.zip file. Unzip this file, locate ONE file smack-im-4.1.4.jar and copy it into ChatIOT/libs directory.

This should remove all compile time errors.

Finally, open the AndroidManifest.xml file located in the ChatIOT project folder and insert one line on permission to access the Internet :
AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.yantrajaal.chatiot"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="19"
        android:targetSdkVersion="19" />

    <uses-permission android:name="android.permission.INTERNET" />
    
    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>


Now if the MainActivity.java program is Run as an Android Application, it should show up on the Emulator like this :

To test the program we need to do the following tasks

  1. This test assumes that we have two registered userid at the xmpp server adastra.re.  So create two IDs say uid2 and uid3. In principle you can choose any public XMPP server but this program has been tested at AdAstra.
  2. Set up the ChatIOT listener chatbot by following these instructions and and start the chatbot using the uid3 userid and its corresponding password.
  3. Start the ChatIOT app either in the Emulator or in a physical Android device
  4. Press the Connect button -- this will connect to the AdAstra server and login as uid2
  5. Press the Send button -- this will first send a message to the listener that contains the text "SOUND 2". This will cause an alarm sound. Next it will send three messages consisting of the word PUSH followed by a random number. These three random numbers would be stored in the SQLite database on the listener machine. There will be a 5 second gap between the messages
  6. The Send button can be pressed repeatedly.
  7. The Disconnect button will cause a logout.
  8. Finally for uid2 to be able to send a message to uid3, the two users must be in each others contact lists! Please use a standard IM client like Pidgin or Xabber to first establish this contact. Otherwise no message will ever be relayed.
  9. You may be wondering why we have used uid2, uid3 as users. Actually there is another user uid1 through which we can keep a check on when uid2 and uid3 are logged in and able to communicate.
In this program we are simply transmitting numbers generated at random. In a real IOT case, these numbers will be generated by physical sensors connected to the Android device. [ this is explained in a subsequent post, where we see how to sense and report illumination levels ]

Java programming on Eclipse is a treacherous business because more often than not the libraries cannot be located and loaded. With Android and Smack there is another level of complexity. Hopefully the steps given here will work. If there is a problem, you can always download the entire project from GitHub, with all libraries in place, and import the same into Eclipse.



1 comment:

  1. Great information. Thanks for providing us such a useful information. Keep up the good work and continue providing us more quality information from time to time. Android Development

    ReplyDelete