Android Things 工业物联网:基于 Android Things 的工业物联网网关

介绍

这个项目是基于 Android Things 的工业网关概念的证明。Android Things 可以通过促进与以太网、UART、SPI 和 I2C 等所需接口的集成来缩短网关开发时间。Raspberry Pi 3 已经开发了一种新型的硬件来测试现场的设备。

工业化,智能化

在尖端技术方面,工业部门并不像工商部门那么快,也不会被误解,工业部门需要更多的时间去采用新技术,因为有大量的工作设计和成熟的产品,所以新的工具,软件或协议在成为业务的一部分会遇到一些阻碍。

Android 试图成为的一部分是可以在工业环境中使用的技术,通过创建一个网关设备来替换旧设备,这些旧设备已经不再被维护。

关于网关

在这种情况下,网关可以被理解为可以翻译通信协议的设备,但是也可以具有控制和监视能力。

工业网关能够通过处理工业通信协议来实现这一点,并且能够在现场部署,以在恶劣的环境中进行接口。

Android Things 将负责支持高层次的软件设计,我们稍后会看到。为了满足物理需求,我们设计了一个特殊的电路板,因为没有提供的 iMX 电路板(很不幸我没有收到)可以直接使用。 因为我有一个备用的 Raspberry Pi 3,所以我决定用它来制作一个原型。

硬件

Raspberry Pi 3 通常被称为教育或实验室 SBC,甚至爱好者市场,所以起初很难用它来实现工业设备。

直到我发现菲尼克斯电气(Phoenix Contact)的工程师为 Raspberry Pi 电脑创建了一套 DIN 导轨配件。

Raspberry Pi DIN 配件

该解决方案适用于工业网关,所以我决定在 PCB 上进行这方面的工作。

我们的想法是,Raspberry Pi 连接到一个具有特定硬件的上板,用于处理恶劣的环境,机箱被设计为暴露以太网,USB 和 SD 卡,就像在这个视频中看到的一样。

因为您可能想知道这里没有 HDMI 视频输出,实际上我们并不需要它,实际上我们会将 SBC 转换成嵌入式产品,这些嵌入式产品在某些功能方面做得非常好,但也限制了它的使用。

Raspberry Pi 和开发的 PCB(称为 Industrial Pi Hat)使用 40 针 GPIO 接头互连,无需安装孔,因为它通过一些夹子连接到机箱,如下图所示。

工业外壳

最后一张图片显示了两个矩形孔,左侧较大的是 Raspberry Pi 的以太网和 USB 连接器,以便它们暴露。正确的更薄的孔径是用于,我们将用来暴露我们的接口的定制连接器。

在内部,PCB 不能做得比 Raspberry Pi 大,下图显示了更多的细节,Phoenix Contact 的工程师由于某种原因决定这样做,另外两个电路板可以根据需要放置在外壳的另一侧。

额外的板可以用来提供更多的接口。

扩展外壳

在 PCB 上安装尽可能多的部件是困难的,因为朝向外壳的层不能用来放置组件,因为它们将靠近它,并且最小的错误可能意味着组装它的问题。

PCB 的框图如下所示。

区块图

实际的工业帽没有组件。

完整的组装板。

Pi Industrial Hat

该板提供隔离的串行接口,可以配置为 RS-485 或 RS-232。由于 Pi GPIO 只提供一个串行 UART 端口,用户可以根据需要进行配置。

SPI0 和 I2C1信号以及一些 GPIO 被隔离并暴露在一个 18 引脚的接头上。 引脚背后的想法是与 DIN 外壳内的另一个电路板连接,或者甚至使用 DIN 导轨连接一个总线,因为菲尼克斯电气也为这种互连提供了套件,如下图所示。

Pi Con

电源为 Raspberry Pi 提供 5V 电源,并且能够提供 4.5A 的电流。

工业帽的 Pi 完整的组装如标题所示。 请注意,GPIO 接头并不是 Phoenix Contact 所需的确切尺寸,因为我的库存中没有它。

Android Things 和快速原型

本项目展示的其中一项功能是能够快速构建原型,而且对于需要随时间变化的高要求的应用程序而言,它也是强大的,原因在于它支持一个巨大的社区。

我为这个项目考虑过的工业网关是一个能够将 Modbus RTU 转换到 modbus 以太网的网关,这是我包含 RS-485 接口的原因,因为它是现场最常用的 RTU 接口。

由于时间很短,我可以投入到这个项目中,modbus 堆栈没有完成。尽管如此,任何可敬的网关都有一个嵌入式Web服务器来进行配置和可视化,这足以证明 Android Things 已经完成。

Web Server

Gateway 内置的 Web Server 基于开源项目 NanoHTTPD。为了允许与硬件和其他软件层的集成,进行了一些修改和添加。

任何 Web 服务器的开发都是一项耗时的任务,特别是对于嵌入式设备,如果调试不友好,部署可能会减慢速度。

NanoHTTPD 与所谓的 Nanolets 一起工作,它们用作 MVC(模型-控制器-视图),除了这里没有Model。视图通过添加路由而暴露,即:

addRoute("/(.)+", StaticPageTestHandler.class, new File(mContext.getCacheDir().getAbsolutePath()).getAbsoluteFile());

这最后一条路由很难实现,它是部署的高级 Web 服务器的基础,它允许我们提供文件,所以你猜,我们的朋友的 JavaScript 将加入这里。 javascript 的美妙之处在于,帮助我们构建响应式网页,在客户端处理能力上进行中继。

智能网关 Web 服务器索引页面如下所示。

智能网关 Web 服务器

我们使用 jquery-ui 来建立一个菜单,并根据所选项加载不同的 html。

index.html 代码如下。

<html>
<head>
<link href="style.css" rel="stylesheet" type="text/css"/>
<link href="jquery-ui.css" rel="stylesheet" type="text/css"/>
<script src="jquery-3.2.1.js" type="text/javascript"></script>
<script src="raphael-2.1.4.min.js" type="text/javascript"></script>
<script src="justgage.js" type="text/javascript"></script>
<title>IoT Gateway</title>
</head>
<body>
<div id="all">
<h1 class="vertical">SMART INDUSTRIAL IoT GATEWAY
        <img alt="Smiley face" id="plcmainlogo" src="SmartGWLogo.png"/></h1>
<script src="jquery-ui.js" type="text/javascript"></script>
<div class="DynamicMenuContainer">
<ul id="menu">
<li><a href="" id="system">System</a></li>
<li>The 
                <a href="">GPIO</a>
<ul>
<li><a href="" id="inputs">Inputs</a></li>
<li><a href="" id="outputs">Outputs</a></li>
</ul>
</li>
<li>
<a href="" id="#">Sensors</a>
<ul>
<li><a href="" id="sensor_data">HDC1080</a></li>
</ul>
</li>
<li>
<a href="" id="#">Communications</a>
<ul>
<li><a href="" id="serial">Serial</a></li>
</ul>
</li>
<li><a href="" id="about">About</a></li>
</ul>
</div>
<div class="clear"> </div>
<script src="links.js" type="text/javascript"></script>
<script> 
        $( "#menu" ).menu({ 
            position: { 
                my:'left top', 
                at:'left bottom' 
            } 
        }); 
        $( document ).ready(function(){ 
        }); 
   </script>
<div id="divMainArea"></div>
</div>
</body>
</html> 

正如你所看到的,菜单点击加载一个名为 “divMainArea” 的 div,它可以让菜单一直保留在网页上。

这些文件驻留在应用程序目录下,可以在应用程序运行时进行更新,这有助于进行大量的调试,尤其是在前端使用 javascript 或 jquery 时出现问题。为了做到这一点,必须完成几个步骤。

首先,您需要找到应用程序文件夹。在这里,包名是:

com.example.androidthings.loopback

应用程序数据位于 pi android 目录下

/data/data/com.example.androidthings.loopback/cache

为了写入这个目录,你需要通过执行 ./adb root 作为 root 来启动 adb,如果已经连接,这将断开设备,所以你应该在那之后重新连接。

现在假设你的主目录路径下面有 adb(我假设你在一个Linux主机下),你可以使用命令复制文件:

~/Android/Sdk/platform-tools/adb push ./*.* /data/data/com.example.androidthings.loopback/cache

它将 push 一个完整的文件夹内容到你的 Android Things pi 上。你在调试的时候真的很好。 也许最后你想要使用另一个存储文件夹中的 html 和静态内容,所以你需要调整给 addRoute 的路径。

传感器 HTML

addRoute("/sensor", SensorHandler.class);

让我们以我的设置的 URL 为例,例如 http://192.168.1.108:9090/sensor,如果我们使用该 URL 进行 GET 请求,应用程序将返回传感器数据,这是 JSON 中的温度和湿度。SensorHandler 类将按如下方式处理查询:

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());
    }
}

正如你所看到的,在该方法下,我们读取传感器数据并构建 JSON。

sensor.html 如下所示。

<!DOCTYPE doctype html>

<html>
<head>
<meta charset="utf-8"/>
<title>Sensor</title>
<meta content="width=device-width" name="viewport"/>
<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</h3></body></html></string,>
<div class="container">
<div class="gauge" id="temp"></div>
<div class="gauge" id="rh"></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>

我们利用一个很好的 Gauge 代码向用户展示数据。

这是它的样子:

Android Things 传感器

传感器 p/n 是德州仪器的 I2C 传感器 HDC1080,因为它是板载传感器,所以它的目的是读取 DIN 导轨设备下的工作条件,因此 PCB 上没有插槽来隔离 它来自于 PCB 的导热性,它只是位于电源的对角。

一个 Java 类被实现来处理初始化和传感器读取以及配置。 代码如下:

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 &amp; 0x00ff) &lt;&lt; 8) | ((read &amp; 0xff00) &gt;&gt; 8));
    }
public Integer readData (Integer pointer){
    byte [] dummy = new byte[1];
    dummy[0] = (byte)(pointer &amp; 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] &amp; 0x00ff) &lt;&lt; 8 | (buf[1] &amp; 0x00ff);
        Log.d(TAG, "buf idx 0 = 0x" + Integer.toHexString(buf[0]));
        Log.d(TAG, "buf idx 1 = 0x" + Integer.toHexString(buf[1] &amp; 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] &amp; 0b11111011);
             break;
         default:
             break;
    }
}
public void setHumidityResolution(HumidityRes res){
     switch(res){
         case _8_BITS:
             RawData[1] = (byte)(RawData[1] &amp; 0b11111100);
             RawData[1] = (byte)(RawData[1] | 0b00000010);
                 break;
         case _11_BITS:
             RawData[1] = (byte)(RawData[1] &amp; 0b11111100);
             RawData[1] = (byte)(RawData[1] | 0b00000001);
         break;
         case _14_BITS:
             RawData[1] = (byte)(RawData[1] &amp; 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]             &lt;&lt; 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);
            }
        }
    }
}

未来

有些要点在未来工作。 我希望这个项目能把评委当作真正的产品的候选人,所以下面的列表就可以开发出来了。

  • Modbus 以太网堆栈。
  • Modbus RTU,主站和从站。
  • SNMP 协议。
  • 协议之间的映射。
  • 使用 SPI 和 I2C 总线的外部工业 I/O。

结论

这个项目的概念已经暴露出来,它证明了在很短的时间内成为一个强大的门户的可行性和可行性。

Android Things 可以用于树莓派等常见 SBC 有一个快速的原型,但更多的嵌入式板卡,如iMX7可以探索开发利用支持 OS 的先进外设。

英语原文链接:Industrial IoT Gateway Based on Android Things

尚未评分
您的评分将帮助我们做出更好的玩法

观光\评论区

Copyright © 2017 玩点什么. All Rights Reserved.