The project I’m working on is about making a home automation system using an Android touch screen and some Arduino slaves that communicates using rs485 and modbus.
In the previous article, I wrote about how to create Arduino slaves that wait for modbus requests. It is now time to present how to write an Android project that acts as a master and communicate with slave devices.
The nice thing of this design is that the logic that controls the smart home system is not constrained to be on the masters’ node. Instead, it can be distributed across all the Arduino devices. Indeed, since rs485 and modbus protocol were designed for industrial environments, this project could be easily used in industrial automation.
The prerequisites are the following:
- Eclipse IDE,
- Eclipse ADT plugin,
- Android SDK
For those of you that are not already familiar with Eclipse and with creating Android apps, this tutorial could be a good starting point.
Basically, the Android project is formed by the following files:
- on the /src folder, the main Activity and the Java class that defines the modbus signatures for the external modbus library
- on libs/armeabi folder, the modbus library
- on res/layout the layout of the main activity
- on res/drawable-hdpi, res/drawable-ldpi, res/drawable-mdpi and res/drawable-xdpi folders, the images for the yellow and red bulb (supplied by http://www.doublejdesign.co.uk)
- on res/values folder, string.xml file
- on the main folder, the AndroidManifest.xml
The user interface is displayed in the figure at the top of this page. It has some yellow/red bulb icons that account for the status of the bulb in the home, 4 toggle buttons and 2 analog statuses (for temperature sensors) and a slider for controlling the light intensity.
The xml layout follows in the next block of source code. I used the AbsoluteLayout, even though more responsive layouts could be used instead. I deliberately kept apart IU design principles for a moment, focusing firstly on describing the technical aspects behind this project, leaving to you, reader, to express yourself and design the best interfaces that follow your needs.
[sourcecode language=”xml”]
<AbsoluteLayout 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_background="@color/black">
<TextView
android_id="@+id/textView2"
android_layout_width="wrap_content"
android_layout_height="wrap_content"
android_layout_marginLeft="10dp"
android_layout_marginTop="10dp"
android_layout_x="12dp"
android_layout_y="20dp"
android_text="Switches status (Digital output):"
android_textColor="@color/white"
android_textColorHighlight="@color/white" />
<TextView
android_id="@+id/textView1"
android_layout_width="wrap_content"
android_layout_height="wrap_content"
android_layout_marginBottom="14dp"
android_layout_marginRight="20dp"
android_layout_x="376dp"
android_layout_y="10dp"
android_text="www.biemmeitalia.net"
android_textColorHighlight="@color/white"
android_textColor="@color/white"/>
<TextView
android_id="@+id/textView3"
android_layout_width="wrap_content"
android_layout_height="wrap_content"
android_layout_marginLeft="10dp"
android_layout_marginTop="180dp"
android_layout_x="12dp"
android_layout_y="210dp"
android_text="Analog input:"
android_textColor="@color/white" />
<ProgressBar
android_id="@+id/progressBar1"
style="?android:attr/progressBarStyleHorizontal"
android_layout_width="220dp"
android_layout_height="wrap_content"
android_layout_marginBottom="36dp"
android_layout_marginLeft="22dp"
android_layout_x="30dp"
android_layout_y="236dp"
android_max="1023" />
<ToggleButton
android_id="@+id/toggleButton3"
android_layout_width="wrap_content"
android_layout_height="wrap_content"
android_layout_x="282dp"
android_layout_y="54dp"
android_text="ToggleButton" />
<ToggleButton
android_id="@+id/toggleButton2"
android_layout_width="wrap_content"
android_layout_height="wrap_content"
android_layout_x="152dp"
android_layout_y="54dp"
android_text="ToggleButton" />
<ToggleButton
android_id="@+id/toggleButton4"
android_layout_width="wrap_content"
android_layout_height="wrap_content"
android_layout_x="414dp"
android_layout_y="54dp"
android_text="ToggleButton" />
<ToggleButton
android_id="@+id/toggleButton1"
android_layout_width="wrap_content"
android_layout_height="wrap_content"
android_layout_x="34dp"
android_layout_y="54dp"
android_text="ToggleButton" />
<ImageView
android_id="@+id/imageView1"
android_layout_width="wrap_content"
android_layout_height="wrap_content"
android_layout_x="25dp"
android_layout_y="152dp"
android_src="@drawable/red_bulb" />
<TextView
android_id="@+id/textView4"
android_layout_width="wrap_content"
android_layout_height="wrap_content"
android_layout_marginLeft="10dp"
android_layout_marginTop="180dp"
android_layout_x="274dp"
android_layout_y="125dp"
android_text="Light intensity (PWM):"
android_textColor="@color/white" />
<TextView
android_id="@+id/textView3"
android_layout_width="wrap_content"
android_layout_height="wrap_content"
android_layout_marginLeft="10dp"
android_layout_marginTop="115dp"
android_layout_x="18dp"
android_layout_y="121dp"
android_text="Bulb status (Digital input):"
android_textColor="@color/white" />
<ImageView
android_id="@+id/imageView3"
android_layout_width="wrap_content"
android_layout_height="wrap_content"
android_layout_x="147dp"
android_layout_y="152dp"
android_src="@drawable/red_bulb" />
<ImageView
android_id="@+id/imageView2"
android_layout_width="wrap_content"
android_layout_height="wrap_content"
android_layout_x="82dp"
android_layout_y="152dp"
android_src="@drawable/yellow_bulb" />
<ImageView
android_id="@+id/imageView4"
android_layout_width="wrap_content"
android_layout_height="wrap_content"
android_layout_x="205dp"
android_layout_y="152dp"
android_src="@drawable/red_bulb" />
<SeekBar
android_id="@+id/seekBar1"
android_layout_width="220dp"
android_layout_height="wrap_content"
android_layout_x="296dp"
android_layout_y="157dp"
android_max="128" />
<TextView
android_id="@+id/temperature"
android_layout_width="wrap_content"
android_layout_height="wrap_content"
android_layout_x="395dp"
android_layout_y="230dp"
android_text="10 °C"
android_textAppearance="?android:attr/textAppearanceLarge"
android_textColor="@color/white" />
<TextView
android_id="@+id/textView5"
android_layout_width="wrap_content"
android_layout_height="wrap_content"
android_layout_x="300dp"
android_layout_y="235dp"
android_text="Temperature:"
android_textColor="@color/white" />
</AbsoluteLayout>
[/sourcecode]
The references to the colors used throughout the code are stored in the string.xml file. It has to be placed inside the res/values folder in your Android project.
[sourcecode language=”xml”]
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">BiemmeIOdemo</string>
<color name="white">#FFFFFF</color>
<color name="black">#000000</color>
</resources>
[/sourcecode]
The ModbusLib class simply declares the signatures of the functions of the modbus library that are provided by a native library.
[sourcecode language=”java”]
package com.biemme.iodemo;
public class ModbusLib {
public native static long openCom();
public native static long ReadHoldingRegisters(int fd, int id, int address, int no_of_registers,int []holdingRegs);
public native static long WriteMultipleRegisters(int fd, int id, int address, int no_of_registers,int []holdingRegs);
public native static long closeCom(int fd);
static{
System.loadLibrary("com_biemme_iodemo_ModbusLib");
}
}
[/sourcecode]
It’s now time to analyze the java code that manages the main activity (called MainActivity.java). As a matter of clarity, I’ll comment out each key part of the class separately. If you want to take a look directly to the whole class, I’ll make it available in the following days.
As seen from the signature, the MainActivity extends the Activity class in order to be an Android displayable window and implements two interfaces OnClickListener and OnSeekBarChangeListener that manage the click event of the buttons and seekbar events.
[sourcecode language=”java”]
public class MainActivity extends Activity implements OnClickListener, OnSeekBarChangeListener
[/sourcecode]
The first method that is triggered (by an intent) when the windows is open is the OnCreate. It simply set the content view (i.e., it explicitly connects the view to the controller) and links the graphical elements like button, progress bar, image view to the referring class’ private attributes (in such a way they can be referenced back later on in the class). Calling the setOnClickListener method will register the referring object to the click event (this means that when a button is clicked the onClick method will be executed).
[sourcecode language=”java”]
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d("msg","onCreate");
setContentView(R.layout.activity_main);
btn = new ToggleButton[4];
inp = new ImageView[4];
temp = (TextView) findViewById(R.id.temperature);
setTitle("Biemme Android-Arduino via rs485, modbus");
btn[0] = (ToggleButton) findViewById(R.id.toggleButton1);
btn[1] = (ToggleButton) findViewById(R.id.toggleButton2);
btn[2] = (ToggleButton) findViewById(R.id.toggleButton3);
btn[3] = (ToggleButton) findViewById(R.id.toggleButton4);
for (int i=0; i < pins; i++){
btn[i].setOnClickListener(this);
}
prg1 = (ProgressBar) findViewById(R.id.progressBar1);
skb1 = (SeekBar) findViewById(R.id.seekBar1);
skb1.setOnSeekBarChangeListener(this);
inp[0] = (ImageView) findViewById(R.id.imageView1);
inp[1] = (ImageView) findViewById(R.id.imageView2);
inp[2] = (ImageView) findViewById(R.id.imageView3);
inp[3] = (ImageView) findViewById(R.id.imageView4);
}
[/sourcecode]
The following onResume method will be executed when the activity first starts (but after the onCreate) or after it is paused. It contains the methods that open the device’s serial port and stores the file id into a private variable. Indeed, it starts refreshing the statuses of the buttons by sending modbus requests to slave devices.
[sourcecode language=”java”]
protected void onResume() {
super.onResume();
Log.d("msg","onResume");
if (fid == 0){
fid = ModbusLib.openCom();
}
stopUpdates();
startRefreshValues(0,0);
}
[/sourcecode]
The startRefreshStatuses method creates a new task and repeatedly executes it at fixed rate. The task creates an object as an instance of updateView’s inner class and since it is a Runnable object, it runs by calling the execute method.
[sourcecode language=”java”]
private void startRefreshStatuses(final long delay){
TimerTask tt=new TimerTask() {
public void run() {
runOnUiThread(new Runnable() {
public void run() {
new updateView().execute();
}
});
}
};
t=new Timer();
t.scheduleAtFixedRate(tt, delay, 500);
}
[/sourcecode]
The following updateView AsnyncTask class implements doInBackground and onPostExecute methods. The former will be executed when the task starts and, as soon as it finishes, the onPostExecute method will be triggered (this method can access the graphical objects of an Activity whereas doInBackground does not). The background method is devoted to call the modbus Read Holding Register (Function Code 3) and pass the returned values to onPostExecute that updates the window.
[sourcecode language=”java”]
private class updateView extends AsyncTask<Integer, Integer, int[]>{
private int[] holdingRegs;
@Override
protected void onPostExecute(int[] result) {
if (result!=null){
for (int i=0; i < 4; i++){
if (btn[i].isChecked() && result[i+6]==0){
btn[i].setChecked(false);
}else if (!btn[i].isChecked() && result[i+6]==1){
btn[i].setChecked(true);
}
}
//Analog Input
prg1.setIndeterminate(false);
prg1.setProgress(result[0]);
double temperatureC = (double)holdingRegs[1];
temperatureC = (((temperatureC*5.0)/1024.0)-0.5)*100;
DecimalFormat df = new DecimalFormat("#.00");
temp.setText(String.valueOf(df.format(temperatureC)+" °C"));
//Digital Input
for (int i=0; i < 4; i++){
if (result[i+2]==1){
inp[i].setImageResource(R.drawable.yellow_bulb);
}else{
inp[i].setImageResource(R.drawable.red_bulb);
}
}
//PWM
skb1.setProgress(result[9]);
}
}
@Override
protected int[] doInBackground(Integer… params) {
try{
int retries = 0;
int no_of_registers;
int node_to_write = 1;
long bytes_received = 0;
int starting_address = 0;
holdingRegs = new int[35];
no_of_registers = 10;
do{
bytes_received = ModbusLib.ReadHoldingRegisters((int)fid,node_to_write,starting_address,no_of_registers, holdingRegs);
if (bytes_received>7){
String s="("+String.valueOf(bytes_received) + ")";
for (int i=0; i<no_of_registers; i++){
s+=String.valueOf(holdingRegs[i]);
s+=",";
}
Log.d("modbus3F:", s);
return holdingRegs;
}
retries++;
}while(bytes_received>0 || retries<5);
}catch(Throwable t){
Log.d("modbusERR", t.toString());
}
return null;
}
}
[/sourcecode]
Writing modbus registers could be performed by executing the DelayedWrites task in a similar way as with the updateView method. However, it is executed only once.
[sourcecode language=”java”]
private void DelayedWrites(final int address, final int value, final long delay){
TimerTask tt=new TimerTask() {
public void run() {
runOnUiThread(new Runnable() {
public void run() {
new WriteRegisters().execute(address, value);
}
});
}
};
tW=new Timer();
tW.schedule(tt, delay);
}
[/sourcecode]
[sourcecode language=”java”]
private class WriteRegisters extends AsyncTask<Integer, Void, int[]>{
private int[] holdingRegs;
@Override
protected void onPostExecute(int[] result) {
if (result!=null){
//if the writes give no error, enable the buttons
changeButtonsState(true);
startRefreshValues(0,900);
}
}
@Override
protected int[] doInBackground(Integer… params) {
try{
int no_of_registers = 1;
int node_to_write = 1;
long bytes_received = 0;
int starting_address = 1;
holdingRegs = new int[35];
int retries = 0;
starting_address = params[0]; //address of the register to write
holdingRegs[0]=params[1]; //value to write
do{
bytes_received = ModbusLib.WriteMultipleRegisters((int)fid, node_to_write, starting_address, 1, holdingRegs);
retries++;
if (bytes_received > 11){
String s="("+String.valueOf(bytes_received) + ")";
s+="["+ String.valueOf(calls++) +"]";
for (int i=0; i<bytes_received; i++){
s+=String.valueOf(holdingRegs[i]);
s+=",";
}
Log.d("modbus16F:", s);
return holdingRegs;
}
}while (bytes_received<=11 || retries<5);
return null;
}catch(Throwable t){
Log.d("modbusERR", t.toString());
}
return null;
}
}
[/sourcecode]
This demo Android project shows how to communicate with modbus slaves (not only to Arduino, but with PLC too) by using a modbus library written in native code.
Disclaimer: The demo library shipped within the tar archive works only with devices that has address=1 and only the first 10 registers (0 to 9) could be retrieved (for ReadHoldingRegisters function) and only the first register could be written (for WriteMultipleRegisters function). In order to test the project, extract the BiemmeIOdemo folder from the archive, create a new Eclipse project by specifying the folder just extracted.