SpringBoot整合RXTXComm案例

发布于 2023-03-09  2.37k 次阅读


1.需求

现在有8个小车它的的功能:

  • 前进
  • 倒退
  • 加速 减速
  • 停止

串口通信的协议(与嵌入式开发者进行协商):字头 小车编号 运动方式 速度 CRC效验码

  • 字头 1字节 (规定20)
  • 小车编号 1字节(8位 1位表示一个小车 最多只能表示8个小车)
  • 运动方式 1字节 (01 前进 02倒退 03停止)
  • 速度 1字节 (1-100)
  • CRC效验码 2字节 (根据前面的所有数字进行效验)

当速度为0就表示停止

2.通过sscom串口工具测试协议是否可行

sscom的串口工具下载:https://www.aliyundrive.com/s/VEuGRQBc1fq

串口工具测试时不需要CRC效验码,测试主要用于看指令是否有效,为下一步编程纠错

sscom测试使用:

3.SpringBoot整合RXTXComm实现案例

3.1 SerialPort串口类交给IOC容器管理

步骤方法:

  1. 创建配置类@Configuration
  2. 获取系统接口列表
  3. 打开串口并设置监听器
  4. 将SerialPort放入到ioc容器中让spring管理
@Configuration
@Slf4j
public class RxtxcommConfig {

    @Value("${RXTX.baudRate}")
    int baudRate;//波特率

    //打开端口
    @Bean
    @Scope(value = "singleton")//单例
    public SerialPort openSerialPort() throws PortInUseException {
        System.out.println(baudRate);
        //获取所有的串口
        List<CommPortIdentifier> portSet = getPortSet();

        //判断列表的接口是否是串口,如果是获取第一个串口就ok
        Optional<CommPortIdentifier> first = portSet.stream().filter(x -> x.getPortType() == CommPortIdentifier.PORT_SERIAL).findFirst();

        //打开串口
        SerialPort serialPort = openPort(first.get(),first.get().getName(),50);

        return serialPort;
    }

    //获取接口列表
    private List<CommPortIdentifier> getPortSet(){
        //系统端口列表
        List<CommPortIdentifier> portList = new ArrayList<>();
        //枚举类
        Enumeration tempPortList;
        //串口管理和设置类
        CommPortIdentifier portIdentifier;
        //得到系统中的端口枚举列表
        tempPortList = CommPortIdentifier.getPortIdentifiers();
        //遍历端口枚举列表
        while (tempPortList.hasMoreElements()) {
            //把端口添加倒list集合中
            portList.add((CommPortIdentifier) tempPortList.nextElement());
        }
        return portList;
    }

    //串口打开方法
    private SerialPort openPort(CommPortIdentifier portIdentifier, String portName, int delayed) throws PortInUseException {
        //打开并获取串口
        SerialPort serialPort = (SerialPort)portIdentifier.open(portName, delayed);
        try {
            //设置串口参数(比特率、数据位、停止位、奇偶校验位)
            serialPort.setSerialPortParams(baudRate,SerialPort.DATABITS_8,SerialPort.STOPBITS_1,SerialPort.PARITY_NONE);
            //设置监听器
            addListener(serialPort);
            //通信中断时触发监听
            serialPort.notifyOnBreakInterrupt(true);
        } catch (UnsupportedCommOperationException | TooManyListenersException e) {
            log.error("设置端口失败");
        }
        return serialPort;
    }

    //设置异常报警侦听器
    private void addListener(SerialPort serialPort) throws TooManyListenersException {
        serialPort.addEventListener(new RS485Listener());
    }

 设置初始化监听类(可选)

public class RS485Listener implements Runnable, SerialPortEventListener {
    @Override
    public void serialEvent(SerialPortEvent serialPortEvent) {
        switch (serialPortEvent.getEventType()) {
            case SerialPortEvent.BI://通信中断捕获
                System.out.println("通信中断");
                break;
        }
    }

    @Override 
    public void run() {
            try {
                Thread.sleep(50);//每次收发数据完毕后线程暂停50ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
}

3.2RXTXCommService串口服务类

串口服务类需要的方法:

  1. 获取串口数据
  2. 向串口发送控制数据

其他功能可拓展:

  • 拓展监听器
  • 拓展发送其他控制数据
@Service
public class RxtxcommService {

    @Autowired
    SerialPort serialPort;//获取串口

    @Autowired
    RS485DataProcess rs485DataProcess; //发送数据协议化处理

    //串口输出流
    OutputStream out = null;

    //往串口发送小车控制数据
    public void BaCarMoveSpeedsendToPort(String initial,//协议的字头 20表示小车控制
                                         String serialnumber,//小车编号
                                         String direction,//小车方向 01 前进  02 后退
                                         String speed//小车速度 速度为00表示停止
    ) {
        //获取发送数据
        byte[] baCarMoveSpeed = rs485DataProcess.getBaCarMoveSpeed(initial, serialnumber, direction, speed);
        try {
            out = serialPort.getOutputStream();
            out.write(baCarMoveSpeed);
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //读取串口数据
    public byte[] readFromPort() {
        InputStream in = null;
        byte[] bytes = null;
        try {
            //给串口返回数据时间
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            in = serialPort.getInputStream();
            // 获取buffer里的数据长度
            int bufferlength = in.available();
            while (bufferlength != 0) {
                // 初始化byte数组为buffer中数据的长度
                bytes = new byte[bufferlength];
                in.read(bytes);
                bufferlength = in.available();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return bytes;
    }

    //有需要可以拓展添加监听器

    //有需要可以拓展发送其他控制数据
}

3.3 RxtxcommDataProcess发送数据的协议化处理

协议化处理类的方法:对发送的数据进行协议化处理

@Component
public class RxtxcommDataProcess{
    //小车运行发送数据协议化处理
    public byte[] getBaCarMoveSpeed(String initial,//协议的字头 20表示小车控制
                                    String serialnumber,//小车车编号
                                    String direction,//小车行驶 01 前进     02 后退
                                    String speed//小车行驶速度 速度为00表示停止
    ){
        //行驶方向和行驶速度(<10)补零操作
        if(Integer.parseInt(speed)<10){
            speed="0"+speed;
        }
        direction="0"+direction;
        //获取小车16进制编号
        String numberProtocolProcess = BaCarNumberProtocolProcess(serialnumber);

        //控制字头、编号、方向、速度进行拼接
        String pjnumber = initial+numberProtocolProcess + direction + speed;

        //获取CRC16效验码
        String crc16 = getCRC16(pjnumber);

        //最终发送数据拼接并且去空格,转化成byte数组返回
        return RxtxcommUtil.toBytes((pjnumber + crc16).replace(" ", ""));
    }
    
    //可以拓展处理其他数据协议化

    //对小车编号进行协议化处理
    private String BaCarNumberProtocolProcess(String serialnumber){

        //总共1个字节8位,每一位代表一个小车(最多只有8个小车)
        StringBuilder bianHaoBin = new StringBuilder("00000000");

        if(!serialnumber.isEmpty()){
            //按照,分割成数组
            String[] splitlist = serialnumber.split(",");
            //补零
            for (int i = 0; i < splitlist.length; i++) {
                //对应位置补1
                bianHaoBin.replace(Integer.parseInt(splitlist[i])-1,Integer.parseInt(splitlist[i]), "1");
            }
        }
        //将转化好的二进制字符串转化位16进制字符串
        return RxtxcommUtil.bin2hex(bianHaoBin.reverse().toString());
    }

    //CRC16效验码
    private String getCRC16(String FasongData){
        return RxtxcommUtil.getCRC16(RxtxcommUtil.toBytes(FasongData));
    }

}

3.4 RxtxcommUtil工具类

工具类的方法:

  1. 二进制字符串转化位16进制字符串
  2. 获取CRC16效验码
  3. 将16进制字符串转换为byte[]
public class RxtxcommUtil{

    //一个字节包含位的数量 8
    private static final int BITS_OF_BYTE = 8;

    //多项式
    private static final int POLYNOMIAL = 0xA001;

    //初始值
    private static final int INITIAL_VALUE = 0xFFFF;


    //二进制字符串转16进制
    public static String bin2hex(String input) {
        StringBuilder sb = new StringBuilder();
        int len = input.length();

        for (int i = 0; i < len / 4; i++){
            //每4个二进制位转换为1个十六进制位
            String temp = input.substring(i * 4, (i + 1) * 4);
            int tempInt = Integer.parseInt(temp, 2);
            String tempHex = Integer.toHexString(tempInt).toUpperCase();
            sb.append(tempHex);
        }

        return sb.toString();
    }


    //将16进制字符串转换为byte[]
    public static byte[] toBytes(String str) {
        byte[] bytes = new BigInteger(str, 16).toByteArray();
        return bytes;
    }


    public static String getCRC16(byte[] bytes) {
        // CRC寄存器全为1
        int CRC = 0x0000ffff;
        // 多项式校验值
        int POLYNOMIAL = 0x0000a001;
        int i, j;
        for (i = 0; i < bytes.length; i++) {
            CRC ^= ((int) bytes[i] & 0x000000ff);
            for (j = 0; j < 8; j++) {
                if ((CRC & 0x00000001) != 0) {
                    CRC >>= 1;
                    CRC ^= POLYNOMIAL;
                } else {
                    CRC >>= 1;
                }
            }
        }
        // 结果转换为16进制
        String result = Integer.toHexString(CRC).toUpperCase();
        if (result.length() != 4) {
            StringBuffer sb = new StringBuffer("0000");
            result = sb.replace(4 - result.length(), 4, result).toString();
        }
        //高位在前地位在后
        //String str = result.substring(2, 4) + " " + result.substring(0, 2);
        // 交换高低位,低位在前高位在后
        String str = result.substring(2, 4) + " " + result.substring(0, 2);
        return str;
}}

路漫漫其修远兮,吾将上下而求索