安卓开发板之串口通信,通过modbus Rtu协议控制下位机

安卓开发板之串口通信,通过modbus Rtu协议控制下位机

      • 1.环境准备
      • 2.编写串口操作核心类
      • 3.编写测试类

前言:因为公司最近有个人脸识别门禁的项目,这个项目主要业务是实现远程人脸注册,管理员在后台管理中审核注册用户信息,到规定时间之后用户到会场即可通过刷脸进入。需要的硬件:百度人脸识别开发套件(2000rmb),支持modbus rtu协议的2 路458继电器(几十rmb),一个某宝购买的电子门禁(200多rmb)。技术难点(个人认为):百度人脸识别开发套件同步我司服务器上的人脸库(原本该开发板预装的人脸识别软件只支持离线数据库,也就是只能到场注册,现在要实现可以通过移动端口的h5界面注册,因为注册的用户信息是放到我司服务器上的数据库的,所以开发版要做的事就是将通过审核的用户人脸加载到本地数据库中),开发套件识别人脸成功后通过串口发送数据控制电子门的打开(原本以为这方面是最难的因为本人没有做过android相关的开发,更别提与串口通信了,所以最开始觉得这是不可能完成的任务,甚至想到了要重装一个linux才能实现串口通信,后来在csdn上随便下了一个代码竟然在串口收到了数据,这个最难的问题解决了后面的问题也都不是问题了)。原本本人是负责.net网站开发的,百度人脸识别开发板本来是交给另外一位之前做过一些android开发的大三的小学弟来的,后来别人听说了我们的想法,直接借口感冒一次都不来了,原本负责服务器搭建的我只好转战android开发,因为前两个月刚好看了java基础和javaEE网站开发,所以对java开发还算了解,ssh框架才只学了一半,原本准备学完之后出去外面找个javaEE网站开发工程师的工作的,后来因为一些原因辗转来到这个物联网创业公司,一切又得从头开始哇咔咔。所有android 开发 上手还是比较快的,而且不用从头开发,百度给了人脸识别软件的源码,但是大公司写的代码就是不一样,看了好几天才终于能动手,开发过程中也遇到了一系列的问题,其中最严重的问题就是软件运行容易闪退,以为是软件问题找售后解决了更新了一下版本后来还是一样容易崩,公司的人都以为是我把软件改出了bug,emmmm…。后来经过了一系列的测试终于发现是硬件问题,找到售后把板子寄回维修,再拿到它已然是一个月后,整个开发周期延后一个月,,,,依稀记得我第一次找售后的时候售后尽然和我说从来没遇到过我这样的情况,究竟是板子卖的太少还是我运气太背就不得而知了。导致了整个开发周期历时1个月又20天左右emmm.。。。现在将本人经验分享如下,这是其中的一个环节,如若需要后期更新或者其他帮助的话请在下方留言
——————helloword_xy

1.环境准备

1).使用该类需要将两个文件夹拷贝到项目目录中,这两个文件夹分别是jni,和jniLibs(jni就是Java本地接口,使用它可以实现java语言与其他语言的交互)jni和jniLibs的压缩包
安卓开发板之串口通信,通过modbus Rtu协议控制下位机_第1张图片
2)配置app文件夹下的bulid.gradle文件(配置好后重新编译一次)
安卓开发板之串口通信,通过modbus Rtu协议控制下位机_第2张图片
3)配置好并重新编译后调整到安卓特有的目录结构会看到,这说明环境准备完成
安卓开发板之串口通信,通过modbus Rtu协议控制下位机_第3张图片

2.编写串口操作核心类

该类的用来控制串口的打开和关闭,并获取到对应串口的输入输出流对象。

package android_serialport_api;

import android.util.Log;

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class SerialPort {

    private static final String TAG = "SerialPort";
    private FileDescriptor mFd;
    private FileInputStream mFileInputStream;
    private FileOutputStream mFileOutputStream;

    public SerialPort(File device, int baudrate, int flags) throws SecurityException, IOException {

        //检查访问权限,如果没有读写权限,进行文件操作,修改文件访问权限
        if (!device.canRead() || !device.canWrite()) {
            try {
                //通过挂在到linux的方式,修改文件的操作权限
                Process su = Runtime.getRuntime().exec("/system/xbin/su");
                String cmd = "chmod 777 " + device.getAbsolutePath() + "\n" + "exit\n";
                su.getOutputStream().write(cmd.getBytes());

                if ((su.waitFor() != 0) || !device.canRead() || !device.canWrite()) {
                    throw new SecurityException();
                }
            } catch (Exception e) {
                e.printStackTrace();
                throw new SecurityException();
            }
        }

        mFd = open(device.getAbsolutePath(), baudrate, flags);

        if (mFd == null) {
            Log.e(TAG, "native open returns null");
            throw new IOException();
        }

        mFileInputStream = new FileInputStream(mFd);
        mFileOutputStream = new FileOutputStream(mFd);
    }

    // Getters and setters
    public InputStream getInputStream() {
        return mFileInputStream;
    }

    public OutputStream getOutputStream() {
        return mFileOutputStream;
    }


    // JNI(调用java本地接口,实现串口的打开和关闭)
/**串口有五个重要的参数:串口设备名,波特率,检验位,数据位,停止位
 其中检验位一般默认位NONE,数据位一般默认为8,停止位默认为1*/
    /**
     * @param path     串口设备的据对路径
     * @param baudrate 波特率
     * @param flags    校验位
     */
    private native static FileDescriptor open(String path, int baudrate, int flags);

    public native void close();

    static {//加载jni下的C文件库
        System.loadLibrary("serial_port");
    }
}

3.编写测试类

(注意:红字代码只是本人在MyEclipse中调试所用,无需拷贝)
串口的打开和关闭很好实现,输入输出数据也很好实现,
关键的问题是输入的数据包下位机总是解析不出来,后来摸索了好久
终于发现问题是我发送的数据包的格式有问题。
1.首先我们要知道上位机发送给下位机的到底是什么格式的
(我就以控制从站号为1的从机的第一个线圈的开关命令为例)
开和关的16进制命令分别为:
开启第一个线圈:01 05 00 00 FF 00 8C 3A
关闭第一个线圈:01 05 00 00 00 00 CD CA
上面的命令是平常在主从机调试软件中输入的,但是下位机实际接收到的格式却并非如此)
2.首先我找了一个上位机(这里我用的是有人的网关,可以通过网页界面控制 通过rj485口
向下位机发送数据),配合usb转串口调试工具,以及串口调试软件 SSCOM32.EXE
我终于看到了这个命令的本来面目:“    ?”
what?这是什么?对,我也想问。还好SSCOM32.EXE可以选择以16进制格式显示选项
于是我就看了这个:01 05 00 00 FF 00 8C 3A。对没错,终于看到认识的了。
但是它内部是怎么实现的呢?
于是我用在网上找的字符串转16进制字符串方法
/** 
 * 字符串转换成十六进制字符串
 */  
public static String str2HexStr(String str) {  
    char[] chars = "0123456789ABCDEF".toCharArray();  
    StringBuilder sb = new StringBuilder("");
    byte[] bs = str.getBytes();  
    int bit;  
    for (int i = 0; i < bs.length; i++) {  
        bit = (bs[i] & 0x0f0) >> 4;  
        sb.append(chars[bit]);  
        bit = bs[i] & 0x0f;  
        sb.append(chars[bit]);  
    }  
    return sb.toString();  
} 

调用一下:
@Test
public void run1() {
// TODO Auto-generated method stub
   String string1=Hex2Bytes.str2HexStr("   ?");
   System.out.println(string1);
   }
于是看到了下面的结果

在这里插入图片描述

看起来挺像的不过中间的空格没有了
(为什么和去除空格之后的不完全一样,可能是代码问题,所以上面的代码“字符串
转换成十六进制字符串的”不建议拷贝)
顺着这条线我们继续往下走
于是我将命令:改成:01050000FF008C3A
当然你也可以用:"01 05 00 00 FF 00 8C 3A".trim();去除空格。
然后代码就变成了下面的样子(结果大功告成!!!)

测试类的真正面目(注意每个安卓开发板的串口名不一样,我的用的是百度人脸识别开发板485串口)
安卓开发板之串口通信,通过modbus Rtu协议控制下位机_第4张图片

package com.serial_test;

import android.util.Log;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;

import android_serialport_api.SerialPort;

public class SerialTest {
    protected SerialPort mSerialPort;
    protected OutputStream mOutputStream;
    private static String prot = "ttysWK0";
    private static int baudrate = 9600;

    public void openDoor() {
        new Thread() {
            @Override
            public void run() {
                try {
                    mSerialPort=new SerialPort(new File("/dev/" + prot), baudrate,0);
                    mOutputStream = mSerialPort.getOutputStream();
                    //mInputStream = mSerialPort.getInputStream();
                    //mOutputStream = mSerialPort.getOutputStream();
                    byte [] bytes1=HexStrToBytes.hexToByteArray("01050000FF008C3A");
                    mOutputStream.write(bytes1,0,bytes1.length);
                    Thread.sleep(1000*2);
                    byte [] bytes2=HexStrToBytes.hexToByteArray("010500000000CDCA");
                    mOutputStream.write(bytes2,0,bytes2.length);
                    Log.i("test", "发送成功");
                } catch (SecurityException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    Log.i("test", "打开失败");
                    e.printStackTrace();
                }catch (Exception e){
                    Log.i("test", "发送失败");
                }
                finally {
                    if (mSerialPort != null) {
                        mSerialPort.close();
                    }
                    if (mOutputStream != null) {
                        try {
                            mOutputStream.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }.start();
    }
}

16进制字符串与字节数组的转换工具类

package com.serial_test;

public class HexStrToBytes {
    /**
     * Hex字符串转byte
     * @param inHex 待转换的Hex字符串
     * @return  转换后的byte
     */
    public static byte hexToByte(String inHex){
        return (byte)Integer.parseInt(inHex,16);
    }
    /**
     * 16进制字符串转换为字节数组
     */

    public static byte[] hexToByteArray(String inHex){
        int hexlen = inHex.length();
        byte[] result;
        if (hexlen % 2 == 1){
            //奇数
            hexlen++;
            result = new byte[(hexlen/2)];
            inHex="0"+inHex;
        }else {
            //偶数
            result = new byte[(hexlen/2)];
        }
        int j=0;
        for (int i = 0; i < hexlen; i+=2){
            result[j]=hexToByte(inHex.substring(i,i+2));
            j++;
        }
        return result;
    }

}

特别鸣谢:作者:溪云一片闲舒卷 的Android Studio下的串口程序开发实战

龙小智物联网官网连接

你可能感兴趣的:(Android开发,安卓开发,串口通信)