The project is a proof of concept of an industrial gateway based on Android Things. Android Things allow to cut development time of the gateway by facilitating the integration with required interfaces such as Ethernet, UART, SPI and I2C. A novel hardware for the Raspberry Pi 3 was developed that allow to test the device on the field.
Be Industrial and Be SmartThe industrial sector doesn't move as fast as the commercial sector in terms of cutting edge technology, don't' misinterpret it, the idea is that Industrial sector takes more time to adopt a new technology since there are plenty of working designs and well established products, so new comers been tools, software or protocols have some barrier to get through in order to become part of the business.
Android Things is one of those technologies that is trying to be part of it, and this project demonstrates it's potential by creating a gateway device that can be used in an Industrial environment to replace legacy devices that are no longer supported by IC manufacturers such old processors or outdated software tools no longer maintained.
About the GatewayA gateway on this context can be understood as a device that can translate communications protocols, but also can have control and monitoring capabilities.
An industrial gateway is capable of this by handling industrial communication protocols and is able to be deployed on the field to interface in harsh environments.
The Android Things will be in charge of supporting high level software design as we will see in a moment. To address the physical requirements a special board was designed since none of the provided iMX boards (which unfortunately I didn't receive any) can be used directly. Because I have a spare Pi 3, I decide to build a prototype with it.
HardwareThe Raspberry Pi 3 is commonly known as an educational or a lab SBC or even for the hobbyist market, so at first it was difficult to realize an Industrial device with it.
That was until I found that Phoenix Contact engineers have created a set of DIN rail accessories for the Raspberry Pi computer.
The solution came handy for the Industrial Gateway so I decide to work on the PCB with this in mind.
The idea is that Raspberry Pi connects with an upper board that has specific hardware to deal with a harsh environment, the enclosure is designed to expose the Ethernet, USB and SD card as can be seeing in this video.
As you might wonder there is no HDMI video output here, actually we don't need it, actually we will convert the SBC into an embedded product that does certain functions very well but also restricts it's usage to only those required.
The Raspberry Pi and the developed PCB, called Industrial Pi Hat, interconnects using the 40 pin GPIO header, no mounting holes are needed as it attach to enclosure by means of some clips as can be seen on the following picture.
The last picture shows two rectangular apertures, the bigger at the left side is for the Ethernet and USB connectors of Pi so they get exposed. The right thinner aperture is for custom connectors that we will use to exposed our interfaces.
Internally the PCB cannot be done bigger than pi, below picture shows more details, Phoenix Contact engineers make it that way by some reason, other two boards can be placed at the other side of the enclosure as needed.
It was somehow difficult to fit as many components as possible on the PCB since the layer that go toward the enclosure cannot be used to place components as they would be to close to it and the minimum mistake can signify a problem to assembly it.
The Block diagram of the PCB is shown below.
The actual Pi Industrial Hat without components.
The complete assembled board.
The board provides isolated serial interface that can be configured to be RS-485 or RS-232. Since Pi GPIO only provides one serial UART port, user can configure it as desired.
The SPI0 and I2C1 signals and a few GPIO are isolated and exposed on a 18 pin header. The idea behind the header is to connect with another board inside the DIN enclosure or even a BUS using the DIN rail because Phoenix Contact also provides kits for this interconnection, below an image to exemplify.
The power supply provides 5V for the Raspberry Pi and it's capable of sourcing 4.5A of current.
The complete assembly of Pi with Industrial Hat. Notice that the GPIO header is not the exact size as required by Phoenix Contact since I didn't have it in my stock.
One of the things that this project demonstrate is the ability to build a prototypes fast, but also it's powerful to the user to accommodate demanding applications that need to change over time and the reason is the support for a huge community behind it.
The Industrial Gateway I have had in mind for the project was a gateway capable of translating Modbus RTU to modbus Ethernet, that was the reason I included the RS-485 interface since it's the most common interface used for RTU in the field.
Due to the short time I could dedicate to the project the modbus stack was not done. Nonetheless, as any respectable Gateway this one has an embedded Web Server for configuration and visualization, and this suffice to prove that Android Things is up to the task.
Web ServerThe Gateway built-in Web Server is based on the open source project NanoHTTPD. Several modifications and additions were done in order to allow the integration with the hardware and other software layers.
The development of any web server can be a time consuming task, specially for embedded devices the deployment might slow down things if it's not friendly to debugging.
The NanoHTTPD works with the so called Nanolets, they serve as sort of MVC (Model-Controller-View), except that here there is no Model. A view is exposed by adding a Route, i.e.
addRoute("/(.)+", StaticPageTestHandler.class, new File(mContext.getCacheDir().getAbsolutePath()).getAbsoluteFile());
This last route was difficult to realize and it's the base for the Advance Web Server deployed, it allow us to serve files and so you guess, javascript our friend will join here. The beauty of javascript is that help us to build responsive web pages relaying on the client side processing power.
The Smart Gateway Web Server index page looks as below.
We use jquery-ui to build a menu and load different html depending on the selected item.
The index html code is below.
<html>
<head>
<link rel="stylesheet" type="text/css" href="style.css" />
<link rel="stylesheet" type="text/css" href="jquery-ui.css" />
<script type=text/javascript src="jquery-3.2.1.js"></script>
<script type=text/javascript src="raphael-2.1.4.min.js"></script>
<script type=text/javascript src="justgage.js"></script>
<title>IoT Gateway</title>
</head>
<body>
<div id="all">
<h1 class="vertical">SMART INDUSTRIAL IoT GATEWAY
<img id="plcmainlogo" alt="Smiley face" src="SmartGWLogo.png"></h1>
<script type=text/javascript src="jquery-ui.js"></script>
<div class="DynamicMenuContainer">
<ul id="menu">
<li><a id="system" href="">System</a></li>
<li>The
<a href="">GPIO</a>
<ul>
<li><a id="inputs" href="">Inputs</a></li>
<li><a id="outputs" href="">Outputs</a></li>
</ul>
</li>
<li>
<a id="#" href="">Sensors</a>
<ul>
<li><a id="sensor_data" href="">HDC1080</a></li>
</ul>
</li>
<li>
<a id="#" href="">Communications</a>
<ul>
<li><a id="serial" href="">Serial</a></li>
</ul>
</li>
<li><a id="about" href="">About</a></li>
</ul>
</div>
<div class="clear"> </div>
<script type=text/javascript src="links.js"></script>
<script>
$( "#menu" ).menu({
position: {
my:'left top',
at:'left bottom'
}
});
$( document ).ready(function(){
});
</script>
<div id="divMainArea"></div>
</div>
</body>
</html>
As you can see, the menu click loads a div called "divMainArea", which allows to keep the menu all the time on the web page.
The Files reside under the application directory and can be updated while the application is running, which facilitate a lot the debugging, specially when there is a problem on the Front-End with javascript or jquery. In order to do that a couple of steps have to be done.
First you need to locate the application folder. In my case the package is
com.example.androidthings.loopback
The application data is located under the pi android directory
/data/data/com.example.androidthings.loopback/cache
in order to write to this directory you need to start adb as root by doing ./adb root, this will disconnect the device if already connected, so you should reconnect after that.
Now suppose you have adb under your home directory path as bellow (I assume you are under a Linux Host), you can copy files there by using the command
~/Android/Sdk/platform-tools/adb push ./*.* /data/data/com.example.androidthings.loopback/cache
This will push a complete folder contents you are into to the android things pi. Which is really nice while you are debugging. Perhaps at the end you want to use another storage folder for the html and static content so you need to adjust the path given to addRoute.
The Sensor HTML
addRoute("/sensor", SensorHandler.class);
let's use as example the url of my setup, i.e. http://192.168.1.108:9090/sensor
, if we make a GET request using that URL, the application will return the sensor data, which is temperature and humidity in a JSON. The SensorHandler class will deal with the query as follows:
public static class SensorHandler extends DefaultHandler {
private OnBoardSensor sens = new OnBoardSensor();
@Override
public String getText() {
throw new IllegalStateException("this method should not be called");
}
@Override
public String getMimeType() {
return "text/html";
}
@Override
public IStatus getStatus() {
return Status.OK;
}
public Response get(UriResource uriResource, Map<String, String> urlParams, IHTTPSession session) {
JSONArray messageArray = new JSONArray();
JSONObject obj = new JSONObject();
double Temp = 0.0;
double RH = 0.0;
try {
Temp = sens.readTemperature();
RH = sens.readHumidity();
} catch (IOException e) {
e.printStackTrace();
}
try {
obj.put("temp", String.format("%.1f", Temp));
obj.put("rh", String.format("%.1f", RH));
} catch (JSONException e) {
e.printStackTrace();
}
messageArray.put(obj);
String response = messageArray.toString();
return Response.newFixedLengthResponse(getStatus(), "application/json", response.toString());
}
}
As you can see, under the method get we read the sensor data and build the json.
The sensor html is shown below.
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title>Sensor</title>
<meta name="viewport" content="width=device-width">
<style>
.container {
width: 550px;
height: 300px;
margin: 0 auto;
text-align: center;
}
.gauge {
width: 250px;
height: 250px;
display: inline-block;
}
</style>
</head>
<body>
<h3 align="center">On-Board Sensor HDC1080</h4>
<div class="container">
<div id="temp" class="gauge"></div>
<div id="rh" class="gauge"></div>
</div>
<script>
var tdata = 10.0;
var rhdata = 40.0;
var temp;
var rh;
function update(){
$.getJSON( "/sensor", function(jres) {
tdata = parseFloat(jres[0].temp);
rhdata = parseFloat(jres[0].rh);
console.log("Temperature: " + jres[0].temp);
console.log("Humidity: " + jres[0].rh);
temp.refresh(tdata);
rh.refresh(rhdata);
});
};
$( document ).ready(function(){
update();
temp = new JustGage({
id: "temp",
value : 10,
min: 0,
max: 40,
decimals: 1,
title: "Temperarture",
gaugeWidthScale: 0.6,
customSectors: [{
color : "#0000ff",
lo : 0,
hi : 15
},{
color : "#00ff00",
lo : 15,
hi : 35
},{
color : "#ffff00",
lo : 35,
hi : 45
},{
color : "#ff0000",
lo : 45,
hi : 65
}],
counter: true
});
rh = new JustGage({
id: "rh",
value : 40,
min: 0,
max: 100,
decimals: 1,
title: "Humidity",
gaugeWidthScale: 0.6,
customSectors: [{
color : "#0000ff",
lo : 0,
hi : 30
},{
color : "#00ff00",
lo : 30,
hi : 70
},{
color : "#ff0000",
lo : 70,
hi : 100
}],
counter: true
});
});
</script>
</body>
</html>
We make use of a nice Gauge code to present the reading to user.
Here is how it looks like
The sensor p/n is HDC1080, a Texas Instruments I2C sensor, as it's the On-Board sensor, it's purpose is to have a reading of operating conditions under the DIN rail equipment, for that reason no slots were made on the PCB to isolate it from the PCB thermal conductivity, it's just located on the opposite corner of the power supply.
A Java class was implemented to deal with initialization and sensor reading as well as configuration. The code is below.
public class OnBoardSensor {
private static boolean init = false;
private static final String TAG = "SensorActivity";
// I2C Slave Address
private static final int I2C_ADDRESS = 0x40;
private static I2cDevice mDevice;
private static PeripheralManagerService mService = new PeripheralManagerService();
public enum HumidityRes {
_8_BITS, _11_BITS, _14_BITS
}
public enum TemperatureRes {
_8_BITS, _14_BITS
}
private static final Integer HDC1080_TEMPERATURE = 0x00;
private static final Integer HDC1080_HUMIDITY = 0x01;
private static final Integer HDC1080_CONFIGURATION = 0x02;
private static final Integer HDC1080_MANUFACTURER_ID = 0xFE;
private static final Integer HDC1080_DEVICE_ID = 0xFF;
private static final Integer HDC1080_SERIAL_ID_FIRST = 0xFB;
private static final Integer HDC1080_SERIAL_ID_MID = 0xFC;
private static final Integer HDC1080_SERIAL_ID_LAST = 0xFD;
public double readTemperature() throws IOException {
Integer rawT = readData(HDC1080_TEMPERATURE);
return ((rawT / 65536.0) * 165) - 40;
}
public double readHumidity() throws IOException {
Integer rawH = readData(HDC1080_HUMIDITY);
return (rawH / 65536.0) * 100;
}
public Integer readManufacturerId() throws IOException {
return readCfgData(HDC1080_MANUFACTURER_ID);
}
public Integer readDeviceId() throws IOException {
return readCfgData(HDC1080_DEVICE_ID);
}
public Integer readCfgData (Integer pointer){
int read = 0;
try {
read = mDevice.readRegWord(pointer);
} catch (IOException e) {
e.printStackTrace();
}
return (((read & 0x00ff) << 8) | ((read & 0xff00) >> 8));
}
public Integer readData (Integer pointer){
byte [] dummy = new byte[1];
dummy[0] = (byte)(pointer & 0x00ff);
Integer value = 0;
try {
mDevice.write(dummy, 1);
} catch (IOException e) {
e.printStackTrace();
}
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
byte[] buf = new byte[2];
mDevice.read(buf, 2);
value = (buf[0] & 0x00ff) << 8 | (buf[1] & 0x00ff);
Log.d(TAG, "buf idx 0 = 0x" + Integer.toHexString(buf[0]));
Log.d(TAG, "buf idx 1 = 0x" + Integer.toHexString(buf[1] & 0x00ff));
Log.d(TAG, "read = 0x" + Integer.toHexString(value));
} catch (IOException e) {
e.printStackTrace();
}
return value;
}
class HDC1080_Registers {
byte[] RawData = new byte[2];
public void setTemperatureResolution(TemperatureRes res){
switch(res){
case _8_BITS:
RawData[1] = (byte)(RawData[1] | 0b00000100);
break;
case _14_BITS:
RawData[1] = (byte)(RawData[1] & 0b11111011);
break;
default:
break;
}
}
public void setHumidityResolution(HumidityRes res){
switch(res){
case _8_BITS:
RawData[1] = (byte)(RawData[1] & 0b11111100);
RawData[1] = (byte)(RawData[1] | 0b00000010);
break;
case _11_BITS:
RawData[1] = (byte)(RawData[1] & 0b11111100);
RawData[1] = (byte)(RawData[1] | 0b00000001);
break;
case _14_BITS:
RawData[1] = (byte)(RawData[1] & 0b11111100);
break;
default:
break;
}
}
};
public void init() {
if(!init) {
Log.d(TAG, "Sensor Created");
try {
List<String> deviceList = mService.getI2cBusList();
if (deviceList.isEmpty()) {
Log.i(TAG, "No I2C bus available on this device.");
} else {
Log.i(TAG, "List of available devices: " + deviceList);
}
mDevice = mService.openI2cDevice(deviceList.get(0), I2C_ADDRESS);
HDC1080_Registers reg = new HDC1080_Registers();
reg.RawData[0] = 0;
reg.RawData[1] = 0;
reg.setHumidityResolution(HumidityRes._14_BITS);
reg.setTemperatureResolution(TemperatureRes._14_BITS);
// set sensor resolution
mDevice.writeRegWord(HDC1080_CONFIGURATION, (short) (reg.RawData[0] << 8 | reg.RawData[1]));
} catch (IOException e) {
Log.w(TAG, "Unable to access I2C device", e);
}
init = true;
}
}
public void close(){
if (mDevice != null) {
try {
mDevice.close();
mDevice = null;
} catch (IOException e) {
Log.w(TAG, "Unable to close I2C device", e);
}
}
}
}
Future WorkSome points to work on the Future. I hope the project likes the judges as a real candidate for a real product so the list below can be developed.
- Modbus Ethernet Stack.
- Modbus RTU, Master and Slave.
- SNMP protocol.
- Mapping between protocol.
- External Industrial I/O using SPI and I2C bus.
The project concept has been exposed and it proves it feasibility and viability to become a robust gateway in very short time.
Android Things can be used in common SBC such as Raspberry Pi to have a quick prototype but more embedded boards such as the iMX7 can be explored to develop advance peripheral or features.
If you like this project, you can go to Part II here.
Comments