news 2026/7/8 4:52:14

springboot 对接RS485串口协议 实现和设备通信

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
springboot 对接RS485串口协议 实现和设备通信

引入依赖

<!-- 串口通信 --> <dependency> <groupId>com.fazecast</groupId> <artifactId>jSerialComm</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.13</version> </dependency>

引入工具

packagecom.lhbl.digitaltwin.utils.com;importcom.fazecast.jSerialComm.SerialPort;importcom.fazecast.jSerialComm.SerialPortDataListener;importcom.fazecast.jSerialComm.SerialPortEvent;importcom.lhbl.digitaltwin.utils.web.ConvertHexStrAndStrUtils;importlombok.extern.slf4j.Slf4j;importorg.springframework.stereotype.Component;importjava.util.ArrayList;importjava.util.List;importjava.util.Map;importjava.util.concurrent.ConcurrentHashMap;importjava.util.stream.Collectors;@Slf4j@ComponentpublicclassSerialPortManager{// 波特率 根据实际情况改publicstaticfinalintSERIAL_BAUD_RATE=9600;privatestaticfinalMap<String,SerialPortHolder>PORT_MAP=newConcurrentHashMap<>();@FunctionalInterfacepublicinterfaceSerialPortCallback{voidonDataReceived(StringportName,byte[]data);}privatestaticclassSerialPortHolder{volatileSerialPortserialPort;volatilebooleanconnected;SerialPortCallbackcallback;SerialPortHolder(SerialPortserialPort,booleanconnected,SerialPortCallbackcallback){this.serialPort=serialPort;this.connected=connected;this.callback=callback;}}privatestaticclassDataListenerimplementsSerialPortDataListener{privatefinalStringportName;privatefinalSerialPortCallbackcallback;DataListener(StringportName,SerialPortCallbackcallback){this.portName=portName;this.callback=callback;}@OverridepublicintgetListeningEvents(){returnSerialPort.LISTENING_EVENT_DATA_AVAILABLE;}@OverridepublicvoidserialEvent(SerialPortEventevent){if(callback!=null){byte[]data=readSerialPortData(portName);if(data!=null){try{callback.onDataReceived(portName,data);}catch(Exceptione){log.error("串口数据处理失败: {}",e.getMessage());}}}}}publicstaticList<String>getSerialPortList(){SerialPort[]serialPorts=SerialPort.getCommPorts();List<String>portNameList=newArrayList<>();for(SerialPortserialPort:serialPorts){log.debug("可用串口: {}",serialPort.getSystemPortName());portNameList.add(serialPort.getSystemPortName());}returnportNameList.stream().distinct().collect(Collectors.toList());}publicstaticvoidconnectSerialPort(StringportName){connectSerialPort(portName,SERIAL_BAUD_RATE,null);}publicstaticvoidconnectSerialPort(StringportName,IntegerbaudRate,SerialPortCallbackcallback){if(PORT_MAP.containsKey(portName)){log.warn("串口已连接: {}",portName);return;}try{SerialPortserialPort=openSerialPort(portName,baudRate);if(serialPort.isOpen()){SerialPortCallbackdataCallback=callback!=null?callback:SerialPortManager::defaultDataHandler;serialPort.addDataListener(newDataListener(portName,dataCallback));PORT_MAP.put(portName,newSerialPortHolder(serialPort,true,dataCallback));log.info("串口连接成功: {}",portName);}else{log.error("串口连接失败: {},请查看端口!",portName);}}catch(Exceptionex){log.error("串口连接异常: {}",ex.getMessage(),ex);}}privatestaticvoiddefaultDataHandler(StringportName,byte[]data){try{StringhexStr=ConvertHexStrAndStrUtils.bytesToHexStr(data);if(hexStr.length()>=8){StringcomStatus=hexStr.substring(6,8);if("00".equals(comStatus)){log.debug("行程开关关闭 - 端口: {}, 数据: {}",portName,hexStr);}elseif("FF".equals(comStatus)){log.debug("行程开关闭合 - 端口: {}, 数据: {}",portName,hexStr);}}}catch(Exceptione){log.error("串口数据解析失败: {}",e.getMessage());}}publicstaticSerialPortopenSerialPort(StringportName,IntegerbaudRate){SerialPortserialPort=SerialPort.getCommPort(portName);if(baudRate!=null){serialPort.setBaudRate(baudRate);}if(!serialPort.isOpen()){serialPort.openPort(1000);}serialPort.setFlowControl(SerialPort.FLOW_CONTROL_DISABLED);serialPort.setComPortParameters(baudRate,8,SerialPort.ONE_STOP_BIT,SerialPort.NO_PARITY);serialPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_BLOCKING|SerialPort.TIMEOUT_WRITE_BLOCKING,1000,1000);returnserialPort;}publicstaticvoidcloseSerialPort(StringportName){SerialPortHolderholder=PORT_MAP.remove(portName);if(holder!=null&&holder.serialPort!=null&&holder.serialPort.isOpen()){holder.serialPort.closePort();log.info("串口关闭: {}",portName);}}publicstaticvoidcloseAllSerialPorts(){PORT_MAP.forEach((portName,holder)->{if(holder.serialPort!=null&&holder.serialPort.isOpen()){holder.serialPort.closePort();log.info("串口关闭: {}",portName);}});PORT_MAP.clear();}publicstaticvoidsendSerialPortData(StringportName,byte[]content){SerialPortHolderholder=PORT_MAP.get(portName);if(holder!=null&&holder.serialPort!=null&&holder.serialPort.isOpen()){holder.serialPort.writeBytes(content,content.length);log.debug("串口发送数据 - 端口: {}, 长度: {}",portName,content.length);}}publicstaticbyte[]readSerialPortData(StringportName){SerialPortHolderholder=PORT_MAP.get(portName);if(holder!=null&&holder.serialPort!=null&&holder.serialPort.isOpen()){try{inti=0;while(holder.serialPort.bytesAvailable()>0&&i++<5){Thread.sleep(20);}byte[]readBuffer=newbyte[holder.serialPort.bytesAvailable()];intnumRead=holder.serialPort.readBytes(readBuffer,readBuffer.length);if(numRead>0){returnreadBuffer;}}catch(InterruptedExceptione){log.error("串口读取中断: {}",e.getMessage());Thread.currentThread().interrupt();}}returnnull;}publicstaticbooleanisConnected(StringportName){SerialPortHolderholder=PORT_MAP.get(portName);returnholder!=null&&holder.connected&&holder.serialPort!=null&&holder.serialPort.isOpen();}}

开机连接串口

@SpringBootApplicationpublicclassDigitalTwinApplicationimplementsApplicationRunner{publicstaticvoidmain(String[]args){SpringApplication.run(DigitalTwinApplication.class,args);}@Overridepublicvoidrun(ApplicationArgumentsargs)throwsException{SerialPortManager.connectSerialPort("COM4",9600,(comName,data)->{System.out.println(data);});}}

定时任务实时发送

@Component@Slf4jpublicclassScheduledCOM{privateStringsendMsg="00 04 00 00 00 03 B1 DA";@Scheduled(fixedRate=1000)publicvoidreadComData(){newThread(newRunnable(){@Overridepublicvoidrun(){byte[]bytes=HexUtil.decodeHex(sendMsg);SerialPortManager.sendSerialPortData(bytes);}}).start();}}

其他辅助工具

publicclassConvertHexStrAndStrUtils{privatestaticfinalchar[]HEXES={'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};publicstaticStringbytesToHexStr(byte[]bytes){if(bytes==null||bytes.length==0){returnnull;}StringBuilderhex=newStringBuilder(bytes.length*2);for(byteb:bytes){hex.append(HEXES[(b>>4)&0x0F]);hex.append(HEXES[b&0x0F]);}returnhex.toString().toUpperCase();}publicstaticbyte[]hexStrToBytes(Stringhex){if(hex==null||hex.length()==0){returnnull;}char[]hexChars=hex.toCharArray();byte[]bytes=newbyte[hexChars.length/2];// 如果 hex 中的字符不是偶数个, 则忽略最后一个for(inti=0;i<bytes.length;i++){bytes[i]=(byte)Integer.parseInt(""+hexChars[i*2]+hexChars[i*2+1],16);}returnbytes;}publicstaticStringstrToHexStr(Stringstr){StringBuildersb=newStringBuilder();byte[]bs=str.getBytes();intbit;for(inti=0;i<bs.length;i++){bit=(bs[i]&0x0f0)>>4;sb.append(HEXES[bit]);bit=bs[i]&0x0f;sb.append(HEXES[bit]);}returnsb.toString().trim();}publicstaticStringhexStrToStr(StringhexStr){//能被16整除,肯定可以被2整除byte[]array=newbyte[hexStr.length()/2];try{for(inti=0;i<array.length;i++){array[i]=(byte)(0xff&Integer.parseInt(hexStr.substring(i*2,i*2+2),16));}hexStr=newString(array,StandardCharsets.UTF_8);}catch(Exceptione){e.printStackTrace();return"";}returnhexStr;}}

流程:启动时先对串口进行 连接 连接上后发送数据给设备 数据格式和设备的数据格式一致时 设备会返回数据到回调 data 里面 处理这个data即可

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/8 4:50:03

Spark MLlib 3.0+ 实战:9大算法模块从数据加载到模型评估完整流程

Spark MLlib 3.0 全流程实战&#xff1a;从数据加载到模型评估的工业级实现1. 环境准备与数据加载在开始Spark MLlib实战之前&#xff0c;我们需要确保环境配置正确。首先确认已安装以下组件&#xff1a;Apache Spark 3.0Python 3.7PySparkNumPy 1.4初始化Spark会话的最佳实践&…

作者头像 李华
网站建设 2026/7/8 4:49:43

模型token缓存命中 vs 缓存未命中?

一、先说清楚&#xff1a;缓存命中 vs 缓存未命中 1.1 一句话说清楚 大模型处理你的请求时&#xff0c;主要成本在计算而不是“读取”。如果这次请求的前缀内容和之前某次请求一模一样&#xff0c;平台就不用从头再算一遍&#xff0c;而是复用之前保存下来的中间结果。 缓存命…

作者头像 李华
网站建设 2026/7/8 4:49:13

影刀RPA API调用的重试退避与超时策略

影刀RPA API调用的重试退避与超时策略 作者&#xff1a;林焱 什么情况用这个 调用外部API时&#xff0c;有三种情况一定会遇到&#xff1a;超时、限流、服务暂时不可用。如果每种情况都让流程直接报错中断&#xff0c;那你每天有三分之一的时间在手动重启流程。 API调用的稳定…

作者头像 李华
网站建设 2026/7/8 4:48:36

【LeetCode704】二分查找

目录 题目 解法一、二分查找&#xff08;闭区间&#xff09; 解法二、二分查找&#xff08;半开半闭区间&#xff09; 题目 解法一、二分查找&#xff08;闭区间&#xff09; int search(int *nums, int numsSize, int target) {int left 0;int right numsSize - 1;int …

作者头像 李华
网站建设 2026/7/8 4:47:33

基于STM32设计的PPG脉搏波采集装置_370

文章目录 一、前言 1.1 项目介绍 【1】项目开发背景 【2】设计实现的功能 【3】项目硬件模块组成 【4】设计意义 【5】国内外研究现状 【6】摘要 1.2 设计思路 1.3 系统功能总结 1.4 开发工具的选择 【1】设备端开发 【2】上位机开发 1.5 参考文献 1.6 系统框架图 1.7 系统原理…

作者头像 李华