12

Java实现RS485串口通信,SpringBoot整合WebSocket实现前后端互推消息

 3 years ago
source link: https://www.daqianduan.com/17526.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

前言

前段时间赶项目的过程中,遇到一个调用RS485串口通信的需求,赶完项目因为楼主处理私事,没来得及完成文章的更新,现在终于可以整理一下当时的demo,记录下来。

首先说一下大概需求:这个项目是机器视觉方面的,AI算法通过摄像头视频流检测画面中的目标事件,比如:火焰、烟雾、人员离岗、吸烟、打手机、车辆超速等,检测到目标事件后上传检测结果到后台系统,

后台系统存储检测结果并推送结果到前端,这里用的是 SpringBoot整合WebSocket实现前后端互推消息 ,感兴趣的同学可以看一看,大家多交流。然后就是今天的主题,系统在推送检测结果到前端的同时,需要触发

声光报警器,现有条件就是系统调用支持RS485串口的继电器控制电路,进而达到打开和关闭报警器的目的。

准备工作

说了这么多可能没什么具体的概念,下面先列出需要的硬件设备及准备工作:

硬件:

USB串口转换器(现在很多主机和笔记本已经没有485串口的接口了,转换器淘宝可以买到);

RS485继电器(12V,继电器模块有8个通道,模块的寄存器有对应8个通道的命令);

声光报警器(12V);

12V电源转换器;

电线若干;

驱动:

USB串口转换驱动;

看了这些硬件,感觉楼主是电工是吧?没错,楼主确实是自己摸索着连接的,下面上图:

3IZ7Jn.png

线路如何接不是本文的重点,用12V的硬件就是因为安全,楼主可以大胆尝试。。。

接通硬件设备后,在系统中查看串口名称,如下图,可以看到通信端口名称是COM1,其实电脑上每个硬件接口都是有固定名称的,USB插在不同的USB接口上,系统读取到的通信端口名称就是对应接口的名称,这里

的端口名称要记下来,后面编码要用到。

jmAn6v.png

然后是搬砖前的最后一步准备工作:安装驱动。楼主的USB串口转换器是在淘宝上买的,商家提供驱动,在电脑上正常安装驱动即可。

开发实现

首先需要引入rxtx的jar包,Java实现串口通信的依赖,如下:

<dependency>
            <groupId>org.rxtx</groupId>
            <artifactId>rxtx</artifactId>
            <version>2.1.7</version>
        </dependency>

引入jar包后,就可以搬砖了,大概思路如下:

1、获取到与串口通信的对象;

2、打开对应串口的端口并建立连接;

3、获取对应通道的命令并发送;

4、接收返回的信息;

5、关闭端口连接。

代码如下:

package com.XXX.utils;

import com.databus.Log;
import gnu.io.*;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class RS485Demo extends Thread implements SerialPortEventListener {
    //单例模式提供连接串口的对象
    private static RS485Demo getInstance(){
        if (cRead == null){
            synchronized (RS485Demo.class) {
                if (cRead == null) {
                    cRead = new RS485Demo();
                    // 启动线程来处理收到的数据
                    cRead.start();
                }
            }
        }
        return cRead;
    }
//    封装十六进制的打开、关闭命令
    private static final List<byte[]> onOrderList = Arrays.asList(
            new byte[]{0x01, 0x05, 0x00, 0x00, (byte) 0xFF, 0x00, (byte) 0x8C, 0x3A},       new byte[]{0x01, 0x05, 0x00, 0x01, (byte) 0xFF, 0x00, (byte) 0xDD, (byte)0xFA},
            new byte[]{0x01, 0x05, 0x00, 0x02, (byte) 0xFF, 0x00, (byte) 0x2D, (byte)0xFA}, new byte[]{0x01, 0x05, 0x00, 0x03, (byte) 0xFF, 0x00, (byte) 0x7C, 0x3A},
            new byte[]{0x01, 0x05, 0x00, 0x04, (byte) 0xFF, 0x00, (byte) 0xCD,(byte) 0xFB}, new byte[]{0x01, 0x05, 0x00, 0x05, (byte) 0xFF, 0x00, (byte) 0x9C, 0x3B},
            new byte[]{0x01, 0x05, 0x00, 0x06, (byte) 0xFF, 0x00, (byte) 0x6C, 0x3B},       new byte[]{0x01, 0x05, 0x00, 0x07, (byte) 0xFF, 0x00,  0x3D, (byte)0xFB});
    private static final List<byte[]> offOrderList = Arrays.asList(
            new byte[]{0x01, 0x05, 0x00, 0x00,  0x00, 0x00, (byte) 0xCD, (byte)0xCA},new byte[]{0x01, 0x05, 0x00, 0x01,  0x00, 0x00, (byte) 0x9C, (byte)0x0A},
            new byte[]{0x01, 0x05, 0x00, 0x02,  0x00, 0x00, (byte) 0x6C, (byte)0x0A},new byte[]{0x01, 0x05, 0x00, 0x03,  0x00, 0x00, (byte) 0x3D, (byte)0xCA},
            new byte[]{0x01, 0x05, 0x00, 0x04,  0x00, 0x00, (byte) 0x8C, (byte)0x0B},new byte[]{0x01, 0x05, 0x00, 0x05,  0x00, 0x00, (byte) 0xDD, (byte)0xCB},
            new byte[]{0x01, 0x05, 0x00, 0x06,  0x00, 0x00, (byte) 0x2D, (byte)0xCB},new byte[]{0x01, 0x05, 0x00, 0x07,  0x00, 0x00, (byte) 0x7C, (byte)0x0B});

    // 监听器,这里独立开辟一个线程监听串口数据
// 串口通信管理类
    static CommPortIdentifier portId;
    static RS485Demo cRead = null;
    //USB在主机上的通信端口名称,如:COM1、COM2等
    static String COMNUM = "";

    static Enumeration<?> portList;
    InputStream inputStream; // 从串口来的输入流
    static OutputStream outputStream;// 向串口输出的流
    static SerialPort serialPort; // 串口的引用
    // 堵塞队列用来存放读到的数据
    private BlockingQueue<String> msgQueue = new LinkedBlockingQueue<String>();

    /**
     * SerialPort EventListene 的方法,持续监听端口上是否有数据流
     */
    public void serialEvent(SerialPortEvent event) {

        switch (event.getEventType()) {
            case SerialPortEvent.BI:
            case SerialPortEvent.OE:
            case SerialPortEvent.FE:
            case SerialPortEvent.PE:
            case SerialPortEvent.CD:
            case SerialPortEvent.CTS:
            case SerialPortEvent.DSR:
            case SerialPortEvent.RI:
            case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
                break;
            case SerialPortEvent.DATA_AVAILABLE:// 当有可用数据时读取数据
                byte[] readBuffer = null;
                int availableBytes = 0;
                try {
                    availableBytes = inputStream.available();
                    while (availableBytes > 0) {
                        readBuffer = RS485Demo.readFromPort(serialPort);
                        String needData = printHexString(readBuffer);
                        System.out.println(new Date() + "真实收到的数据为:-----" + needData);
                        availableBytes = inputStream.available();
                        msgQueue.add(needData);
                    }
                } catch (IOException e) {
                }
            default:
                break;
        }
    }

    /**
     * 从串口读取数据
     *
     * @param serialPort 当前已建立连接的SerialPort对象
     * @return 读取到的数据
     */
    public static byte[] readFromPort(SerialPort serialPort) {
        InputStream in = null;
        byte[] bytes = {};
        try {
            in = serialPort.getInputStream();
            // 缓冲区大小为一个字节
            byte[] readBuffer = new byte[1];
            int bytesNum = in.read(readBuffer);
            while (bytesNum > 0) {
                bytes = concat(bytes, readBuffer);
                bytesNum = in.read(readBuffer);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    in.close();
                    in = null;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return bytes;
    }

    /**
     * 通过程序打开COM串口,设置监听器以及相关的参数
     * @return 返回1 表示端口打开成功,返回 0表示端口打开失败
     */
    public int startComPort() {
        // 通过串口通信管理类获得当前连接上的串口列表
        try {
            Log.info("开始获取串口。。。");
            portList = CommPortIdentifier.getPortIdentifiers();
            Log.info("获取串口。。。" + portList);
            Log.info("获取串口结果。。。" + portList.hasMoreElements());

            while (portList.hasMoreElements()) {
                // 获取相应串口对象
                Log.info(portList.nextElement());
                portId = (CommPortIdentifier) portList.nextElement();

                System.out.println("设备类型:--->" + portId.getPortType());
                System.out.println("设备名称:---->" + portId.getName());
                // 判断端口类型是否为串口
                if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) {
                    // 判断如果COM4串口存在,就打开该串口
//                if (portId.getName().equals(portId.getName())) {
                    if (portId.getName().equals(COMNUM)) {
                        try {
                            // 打开串口名字为COM_4(名字任意),延迟为1000毫秒
                            serialPort = (SerialPort) portId.open(portId.getName(), 1000);

                        } catch (PortInUseException e) {
                            System.out.println("打开端口失败!");
                            e.printStackTrace();
                            return 0;
                        }
                        // 设置当前串口的输入输出流
                        try {
                            inputStream = serialPort.getInputStream();
                            outputStream = serialPort.getOutputStream();
                        } catch (IOException e) {
                            e.printStackTrace();
                            return 0;
                        }
                        // 给当前串口添加一个监听器,serialEvent方法监听串口返回的数据
                        try {
                            serialPort.addEventListener(this);
                        } catch (TooManyListenersException e) {
                            e.printStackTrace();
                            return 0;
                        }
                        // 设置监听器生效,即:当有数据时通知
                        serialPort.notifyOnDataAvailable(true);

                        // 设置串口的一些读写参数
                        try {
                            // 比特率、数据位、停止位、奇偶校验位
                            serialPort.setSerialPortParams(9600,
                                    SerialPort.DATABITS_8, SerialPort.STOPBITS_1,
                                    SerialPort.PARITY_NONE);
                        } catch (UnsupportedCommOperationException e) {
                            e.printStackTrace();
                            return 0;
                        }
                        return 1;
                    }
                }
            }
        }catch (Exception e){
            e.printStackTrace();
            Log.info(e);
            return 0;
        }
        return 0;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        try {
            System.out.println("--------------任务处理线程运行了--------------");
            while (true) {
                // 如果堵塞队列中存在数据就将其输出
                try {
                    if (msgQueue.size() > 0) {
                        String vo = msgQueue.peek();
                        String vos[] = vo.split("  ", -1);
                        //根据返回数据可以做相应的业务逻辑操作
//                        getData(vos);
//                        sendOrder();
                        msgQueue.take();
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 16转10计算
    public long getNum(String num1, String num2) {
        long value = Long.parseLong(num1, 16) * 256 + Long.parseLong(num2, 16);
        return value;
    }

    // 字节数组转字符串
    private String printHexString(byte[] b) {
        StringBuffer sbf = new StringBuffer();
        for (int i = 0; i < b.length; i++) {
            String hex = Integer.toHexString(b[i] & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            sbf.append(hex.toUpperCase() + "  ");
        }
        return sbf.toString().trim();
    }

    /**
     * 合并数组
     *
     * @param firstArray  第一个数组
     * @param secondArray 第二个数组
     * @return 合并后的数组
     */
    public static byte[] concat(byte[] firstArray, byte[] secondArray) {
        if (firstArray == null || secondArray == null) {
            if (firstArray != null)
                return firstArray;
            if (secondArray != null)
                return secondArray;
            return null;
        }
        byte[] bytes = new byte[firstArray.length + secondArray.length];
        System.arraycopy(firstArray, 0, bytes, 0, firstArray.length);
        System.arraycopy(secondArray, 0, bytes, firstArray.length, secondArray.length);
        return bytes;
    }

    //num:偶数启动报警器,奇数关闭报警器
    //commandInfo:偶数打开,奇数关闭;channel:继电器通道;comNum:串口设备通信名称
    public static void startRS485(int commandInfo,int channel,String comNum) {
        try {
            if(cRead == null){
                cRead = getInstance();
            }
            if (!COMNUM.equals(comNum) && null != serialPort){
                serialPort.close();
                COMNUM = comNum;
            }
            int i = 1;
            if (serialPort == null){
                COMNUM = comNum;
                //打开串口通道并连接
                i = cRead.startComPort();
            }
            if (i == 1){
                Log.info("串口连接成功");
                try {
                    //根据提供的文档给出的发送命令,发送16进制数据给仪器
                    byte[] b;
                    if (commandInfo % 2 == 0) {
                        b = onOrderList.get(channel);
                    }else{
                        b = offOrderList.get(channel);
                    }
                    System.out.println("发送的数据:" + b);
                    System.out.println("发出字节数:" + b.length);
                    outputStream.write(b);
                    outputStream.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        if (outputStream != null) {
                            outputStream.close();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    //每次调用完以后关闭串口通道
                    if (null != cRead){
                        if (null != serialPort){
                            serialPort.close();
                            serialPort = null;
                        }
                        cRead.interrupt();
                        cRead = null;
                    }
                }
            }else{
                Log.info("串口连接失败");
                return;
            }
        }catch (Exception e){
            e.printStackTrace();
            Log.info("串口连接失败");

        }
    }

    public static void main(String[] args) {
        //打开通道1的电路,对应设备名称COM3
        startRS485(0,1,"COM3");
    }
}

代码比较繁杂,需要有点耐心才能完全了解,大家可以从startRS485()函数作为切入点阅读代码。当然,这个demo只是抛砖引玉,有相关开发需求的童鞋可以看一看,参考一下大概的思路。

#感谢您访问本站#
#本文转载自互联网,若侵权,请联系删除,谢谢!657271#qq.com#

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK