The aim of this library is to let users to leverage the power of modbus RTU protocol through RS485 on
In order to communicate with your instruments and devices from Android through RS485 and modbus, you can link the modbus RTU library in your projects and call the methods, such as function code 3 Read Holding Registers or function 16 Preset Multiple Registers, the most widely used modbus functions.
This document helps you to understand how to design a project that uses the native modbus library for Android. Many approaches exist in the way a project could be structured; we suggest one of that is complete and efficient though.
The most frequent situation that is encountered in industrial and home automations applications is the following: one or more slave devices (such as PLCs, Power inverters, remote I/O modules) connected in a bus line and a touch panel that acts as a master displaying devices' states accepting commands from users. Basically, this is at the heart of Human Machine Interface (HMI).
The HMI interface is designed using the Android framework. For those of you that are not familiar with creating Android projects, we suggest to take a look at this brief tutorial on how to create your first app with Android. Many other tutorials and books are available on the net.
The high-level architecture of a hypothetical Android project can be summarized in the picture below. The rounded boxes represent respectively:
The first question may arise is the following: why using a FIFO queue and critical sections? Because we supposed that the project has to manage regular graphical updates, therefore the stream of read requests has to be correctly overlapped with writes. Indeed, the FIFO queue garantees the requests will be processed with the same order as they arrive.
In this tutorial, modbus reads and writes are considered as synonyms of modbus function 3 and 16 respectively.
The mainUIthread represent the main thread Activity("Window") that the users see. It contains all the code necessary to manage the UI, like buttons, progress bars, text boxes, etc. We used only one Activity, but obviously, real world applications use more than one activity for accomplish their jobs. Indeed, it starts and stops the thread that manages the modbus reads/writes. The java code might be:
public class MainActivity extends Activity{ private int fid = 0; //file handler to the serial port private Thread workingthread = null; //modbus thread private int ReadsRefreshRate = 1000; //declare the queue with maximum 2000 elements private BlockingQueuequeue_writes = new ArrayBlockingQueue (2000); protected void onResume() { super.onResume(); if (fid == 0){ //Serial port opening with baudrate 38400bps and 40000ns for read/write timeout fid = ModbusLib.openCom(38400,40000,40000); } //start the thread that manage the modbus RTU requests workingthread=new ReadsWrites(queue_writes, mHandler, fid, ReadsRefreshRate); workingthread.start(); } }
The onResume method accounts for opening the serial port (in this case with baudrate set to 38400bps and timeouts 40000ns for both modbus reads and writes) and for starting the thread that manages the modbus requests. When creating the modbus thread, it needs four parameters:
The queue_writes is a blocking queue with a maximum length of 2000. We used a BlockingQueue data structure because the elements must be added atomically, preserving also the order with which requests arrives. Because of that, the code that inserts the elements into the queue will be in a critical section.
The objects that are stored in the queue must be of type WriteRequest. The class WriteRequest simply defines the object "modbus function" and is coded as follow:
public class WriteRequest { private int address; //register address to start writing private int node_id; //slave id device number private int regs; //number of registers to write private int[] holdingRegs; //registers to write public int getAddress() { return address; } public void setAddress(int address) { this.address = address; } public int getNode_id() { return node_id; } public void setNode_id(int node_id) { this.node_id = node_id; } public int getRegs() { return regs; } public void setRegs(int regs) { this.regs = regs; } public int[] getHoldingRegs() { return holdingRegs; } public void setHoldingRegs(int[] holdingRegs) { this.holdingRegs = holdingRegs; } public WriteRequest(int node_id, int address, int regs, int[] holdingRegs) { //class constructor super(); this.address = address; this.node_id = node_id; this.regs = regs; this.holdingRegs = holdingRegs; } }
A modbus write request can be defined as
int hr = new int[6]; //the holding register WriteRequest w1 = new WriteRequest(1,1,6,hr);
with hr as a valid reference to an integer array. As previously stated, the code that adds write requests has to be in a critical section to preserve the order. This is accomplished by using a Java synchronized statement specifying the name of the variable to be accessed atomically (i.e., the queue), namely:
synchronized (queue_writes) { WriteRequest w1 = new WriteRequest(1,1,6,hr); queue_writes.add(w1); workingthread.interrupt(); }
The final statement wakes up the modbus thread in case it was sleeping, therefore it can process the new requests that are on the queue.
We are now ready to analyze how can be designed the main thread that manage the modbus requests. We called this class ReadsWrites and it extends Thread in order to be executed in parallel with the other threads in the Android OS. The class private variables wrqueue, hd, fid and reads_rr are the same as those passed to the object constructor together with some other variables referring to modbus reads.
The core of the class stands in the critical section. It works in a way that when the queue is not empty, it executes all the writing requests; otherwise it cyclically reads registers from the slave(s) and pause itself for a limited timespan. The thread wakes up when a new write request has to be executed.
Since in Android background threads can not access the graphical objects directly, it is mandatory to manage the UI updates with messages and in particular using the handle that were gathered at the time of thread creation. The messages are created and sent by calling the obtainMessage and sendMessage methods. The class' code might be the following:
public class ReadsWrites extends Thread{ private BlockingQueuewrqueue = null; private Handler hd = null; private int max_retries = 1; //defines how many retries before giving up private int reads_rr = 1000; //Read refresh rate, default value private int node_to_write = 1; private int starting_address = 1; private int no_of_registers = 10; private int offset = no_of_registers; private int fid = 0; int retries = 0; public ReadsWrites(BlockingQueue q, Handler handle, int serial_port, int rrefresh) { wrqueue = q; hd = handle; fid = serial_port; reads_rr = rrefresh; } @Override public void run() { do{ int[] holdingRegs; //registers that stores the read values holdingRegs = new int[15]; long bytes_received1 = 0; try { synchronized (wrqueue) { if (!wrqueue.isEmpty()){ //extract Modbus Presets Registers from queue WriteRequest wr = wrqueue.poll(); //wr will be not null because isEmpty is false //use the preset parameters from wr object node_to_write = wr.getNode_id(); starting_address = wr.getAddress(); no_of_registers = wr.getRegs(); holdingRegs = wr.getHoldingRegs(); do{ if (bytes_received1 <= 0){ bytes_received1 = ModbusLib.PresetMultipleRegisters( fid, node_to_write, starting_address, no_of_registers, holdingRegs); } retries++; }while (retries <= max_retries); }else{ node_to_write = 1; starting_address = 0; no_of_registers = 10; do{ //Modbus Reads bytes_received2 = ModbusLib.ReadHoldingRegisters( fid, node_to_write, starting_address, no_of_registers, holdingRegs); if (bytes_received2 >= 7){ Message msg = hd.obtainMessage(); msg.what = 1; msg.arg1 = holdingRegs[0]; msg.arg2 = holdingRegs[1]; hd.sendMessage(msg); } retries++; }while (retries <= max_retries); } } Thread.sleep(reads_rr); } catch (InterruptedException e) { Log.d("Modbus_THREAD_", "wake up!"); } }while(true); } }
In this demo project, two values only are sent back to the main UI thread. If you need to exchange more than few values, please take a look at our blog post in which Android bundle is used to figure out this issue.
The design we presented in this overview helps you to lay out the architecture of your Android application. It is quite general so can be adopted in many situations and by many professional and home users of our Android touch panels and development boards. We strongly suggest to starting with it and extending as your needs.