一、背景描述
公司最近在做一款车间产线质检工具桌面软件,其中客户提出不合格情况达到一定的比例后,车间操作台红蓝报警灯 响铃并告警,该款工具底层使用java 语言开发。所以开始java 打通硬件的通讯的调研。
以下是客户的硬件报文通讯协议
二、连接配置过程
经过一段时间的调研和研究,对串口通讯有了基础的认知,串口通讯一般依赖于物理传输介质,通过该种方式传输信号。我使用的是RS232-USB转接线母头,USB口端接入电脑,母头端接入硬件电路板公头
公头是硬件电路板预留三条线出来,和RS232母头连接,
连接后,在电脑端会形成新的串口,串口号需要通过的串口调试工具xcom2.6助手来获取。打开这个助手会默认显示内置的串口号,接入硬件电路板 再查看串口号是否有变化
三、硬件编码过程
串口通讯是很老的技术,对于java而言 有不少依赖包 ,主要是rxtx相关系列的,在开发过程中,有不少老的博文,引入的依赖的包 至少三个并且,操作系统不一样,还需要额外的配置,比较复杂。经过调研后总结了一个比较轻量化的接入方式。
1.引入POM
<dependency>
<groupId>com.github.purejavacomm</groupId>
<artifactId>purejavacomm</artifactId>
<version>1.0.2.RELEASE</version>
</dependency>
2.通讯模块代码
package com.taopu.smart.serialcom;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.util.StringUtil;
import org.springframework.util.StringUtils;
import purejavacomm.CommPortIdentifier;
import purejavacomm.SerialPort;
import purejavacomm.SerialPortEvent;
import purejavacomm.SerialPortEventListener;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
/**
* @author:ms.y
* @create 2019/1/24-8:48
* * 串口监听器
* * // FC00 6A 0000803F 5ED2 16
* * FC 头部
* * 00 地址
* * 6A表示 功能类型
*
* https://www.23bei.com/tool/59.html FC005A0000803F 经过CRC校验 产出CRC0 和 CRC1
*
* 拼接CRC: FC00 6A 0000803F 5ED2
* 拼接尾部 16
*
*
* 预警硬件通信协议文档
* 例:打开预警:FC006A000000103ECA16
*
* -FC:前缀
* -00:地址
* -6A:功能项[亮光+声音]
* -00000010 打开
* -00000000 关闭
* -00000001-0000000A 范围声音分为10个档位值
* -3ECA 校验位CRC0CRC1
* -16 拼接后缀
*/
@Slf4j
public class CommUtil implements SerialPortEventListener {
public static final String PREFIX_SIX_SPLIT="FC,00,6A,00,00,00";
public static String PORT_NAME = "";
private static final int BIT_RATE = 9600;
public static final int DATA_BITS = SerialPort.DATABITS_8;
public static final int STOP_BIT = SerialPort.STOPBITS_1;
public static final int PARITY_BIT = SerialPort.PARITY_NONE;
private static SerialPort serialPort;
private static InputStream in;
private static OutputStream out;
private static CommUtil commUtil;
private CommUtil() {
}
public static synchronized CommUtil getInstance() {
if (commUtil == null) {
commUtil = new CommUtil();
commUtil.init();
}
return commUtil;
}
public ArrayList<String> findPort() {
//获得当前所有可用串口
Enumeration<CommPortIdentifier> portList = CommPortIdentifier.getPortIdentifiers();
ArrayList<String> portNameList = new ArrayList<>();
//将可用串口名添加到List并返回该List
while (portList.hasMoreElements()) {
String portName = portList.nextElement().getName();
portNameList.add(portName);
}
return portNameList;
}
public void init() {
try {
if(StringUtils.isEmpty(PORT_NAME)){
log.error("init PORT_NAME is null");
}else {
log.info("init PORT_NAME is :{}",PORT_NAME);
}
CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(PORT_NAME);
if (portIdentifier.isCurrentlyOwned()) {
log.error("Port is currently in use");
} else if (portIdentifier.getPortType() == 1) {
serialPort = (SerialPort) portIdentifier.open(PORT_NAME, 1000);
serialPort.setSerialPortParams(BIT_RATE, DATA_BITS, STOP_BIT, PARITY_BIT);
in = serialPort.getInputStream();
out = serialPort.getOutputStream();
serialPort.addEventListener(this);
serialPort.notifyOnDataAvailable(true);
} else {
log.error("Error: Only serial ports are handled by this example.");
}
} catch (Exception e) {
log.error("init failed",e);
}
}
public void send(String message) {
try {
log.info("sendmsg:{}",message);
byte[] bytes = hexStrToByteArray(message);
out.write(bytes);
Thread.sleep(1000);
} catch (Exception e) {
log.error("send failed",e);
}
}
@Override
public void serialEvent(SerialPortEvent event) {
switch (event.getEventType()) {
case SerialPortEvent.DATA_AVAILABLE:
receive();
break;
}
}
public String receive() {
byte[] buffer = new byte[128];
int data;
String result = null;
try {
int len = 0;
while ((data = in.read()) > -1) {
buffer[len++] = (byte) data;
}
byte[] copyValue = new byte[len];
System.arraycopy(buffer, 0, copyValue, 0, len);
result = ByteArrayToString(copyValue);
} catch (Exception e) {
log.error("receive",e);
}
return result;
}
public void close() {
try {
in.close();
out.close();
serialPort.notifyOnDataAvailable(false);
serialPort.removeEventListener();
serialPort.close();
} catch (Exception e) {
log.error("close",e);
}
}
//16进制转byte数组
public static byte[] hexStrToByteArray(String str) {
if (str == null) {
return null;
}
if (str.length() == 0) {
return new byte[0];
}
byte[] byteArray = new byte[str.length() / 2];
for (int i = 0; i < byteArray.length; i++) {
String subStr = str.substring(2 * i, 2 * i + 2);
byteArray[i] = ((byte) Integer.parseInt(subStr, 16));
}
return byteArray;
}
public static String ByteArrayToString(byte[] by) {
String str = "";
for (int i = 0; i < by.length; i++) {
String hex = Integer.toHexString(by[i] & 0xFF);
if (hex.length() == 1) {
hex = "0" + hex;
}
str += hex.toUpperCase();
}
return str;
}
public static void main(String[] args) {
// String dec2Hex = dec2Hex(9);
// CommUtil commUtil = CommUtil.getInstance();
// if (Objects.isNull(commUtil)) {
// log.error("commUtil is null ");
// return;
// }
// commUtil.send("FC005A0000803F5ED216");
//
// log.error("commUtil send success ");
// commUtil.close();
// System.err.println(dec2Hex);
byte[] fc005A0000803FS = hexStrToByteArray("FC 00 5A 00 00 00 00 7F 02 16");
System.err.println(fc005A0000803FS);
int[] data = new int[7];
data[0]=252;
data[1]=0;
data[2]=90;
data[3]=0;
data[4]=0;
data[5]=0;
data[6]=9;
System.err.println(data);
}
}
3.CRC16 校验代码
package com.taopu.smart.serialcom;
/**
* 基于Modbus CRC16的校验算法工具类
*/
public class Crc16Util {
/**
* 获取源数据和验证码的组合byte数组
* @param strings 可变长度的十六进制字符串
* @return
*/
public static byte[] getData(String...strings) {
byte[] data = new byte[]{};
for (int i = 0; i<strings.length;i++) {
int x = Integer.parseInt(strings[i], 16);
byte n = (byte)x;
byte[] buffer = new byte[data.length+1];
byte[] aa = {n};
System.arraycopy( data,0,buffer,0,data.length);
System.arraycopy( aa,0,buffer,data.length,aa.length);
data = buffer;
}
return getData(data);
}
/**
* 获取源数据和验证码的组合byte数组
* @param aa 字节数组
* @return
*/
private static byte[] getData(byte[] aa) {
byte[] bb = getCrc16(aa);
byte[] cc = new byte[aa.length+bb.length];
System.arraycopy(aa,0,cc,0,aa.length);
System.arraycopy(bb,0,cc,aa.length,bb.length);
return cc;
}
/**
* 获取验证码byte数组,基于Modbus CRC16的校验算法
*/
private static byte[] getCrc16(byte[] arr_buff) {
int len = arr_buff.length;
// 预置 1 个 16 位的寄存器为十六进制FFFF, 称此寄存器为 CRC寄存器。
int crc = 0xFFFF;
int i, j;
for (i = 0; i < len; i++) {
// 把第一个 8 位二进制数据 与 16 位的 CRC寄存器的低 8 位相异或, 把结果放于 CRC寄存器
crc = ((crc & 0xFF00) | (crc & 0x00FF) ^ (arr_buff[i] & 0xFF));
for (j = 0; j < 8; j++) {
// 把 CRC 寄存器的内容右移一位( 朝低位)用 0 填补最高位, 并检查右移后的移出位
if ((crc & 0x0001) > 0) {
// 如果移出位为 1, CRC寄存器与多项式A001进行异或
crc = crc >> 1;
crc = crc ^ 0xA001;
} else
// 如果移出位为 0,再次右移一位
crc = crc >> 1;
}
}
return intToBytes(crc);
}
/**
* 将int转换成byte数组,低位在前,高位在后
* 改变高低位顺序只需调换数组序号
*/
private static byte[] intToBytes(int value) {
byte[] src = new byte[2];
src[1] = (byte) ((value>>8) & 0xFF);
src[0] = (byte) (value & 0xFF);
return src;
}
/**
* 将字节数组转换成十六进制字符串
*/
public static String byteTo16String(byte[] data) {
StringBuffer buffer = new StringBuffer();
for (byte b : data) {
buffer.append(byteTo16String(b));
}
return buffer.toString();
}
/**
* 将字节转换成十六进制字符串
* int转byte对照表
* [128,255],0,[1,128)
* [-128,-1],0,[1,128)
*/
public static String byteTo16String(byte b) {
StringBuffer buffer = new StringBuffer();
int aa = (int)b;
if (aa<0) {
buffer.append(Integer.toString(aa+256, 16)+" ");
}else if (aa==0) {
buffer.append("00 ");
}else if (aa>0 && aa<=15) {
buffer.append("0"+Integer.toString(aa, 16)+" ");
}else if (aa>15) {
buffer.append(Integer.toString(aa, 16)+" ");
}
return buffer.toString();
}
public static void main(String[] args) {
// 判断
byte[] dd = Crc16Util.getData("FC", "00","6A","00","00","00","01");
// byte[] dd = Crc16Util.getData("FC", "00","6A","00","00","00","01");
String str = Crc16Util.byteTo16String(dd).toUpperCase();
System.out.println(str);
}
}
4 业务调用代码-增加crc16校验位
public void giveAlarm(TcpParams tcpParams){
//获取当前规则
List<FormulaItem> formulaItems=formulaService.getCurrentFormulaItem();
if(formulaItems==null||formulaItems.isEmpty()){
return;
}
Map<Byte,Integer> formulaItemMap=formulaItems.stream().collect(Collectors.toMap(FormulaItem::getType,FormulaItem::getAmount));
//获取对应缺陷数量
MasterRoll masterRoll = masterRollService.currentMasterRoll();
//获取告警是否开启
String status=redisTemplate.opsForValue().get(ALARM_STATUS).toString();
if(status.equals("1")){
return;
}
List<TypeCountVO> typeCountVOS=defectRecordMapperEx.statistic(masterRoll.getId(),null,Convert.toByte(tcpParams.getDetail().getDefectType()));
if(!typeCountVOS.isEmpty()){
//缺陷数量超过规定
if(typeCountVOS.get(0).getAmount()>formulaItemMap.get(Convert.toByte(tcpParams.getDetail().getDefectType()))){
// 触发预警
StringBuffer buffer = new StringBuffer();
if (!StringUtils.isEmpty(defaultOpenAlarmPrefix)) {
String[] split = defaultOpenAlarmPrefix.split(",");
log.info("giveAlarm split:{}",split);
byte[] data = Crc16Util.getData(split[0], split[1], split[2], split[3], split[4], split[5],split[6]);
buffer.append(Crc16Util.byteTo16String(data).toUpperCase());
} else {
// 用自定义的触发告警
byte[] data = Crc16Util.getData("FC", "00", "6A", "00", "00", "00", "00");
buffer.append(Crc16Util.byteTo16String(data).toUpperCase());
}
buffer.append("16");
String sendMsg= buffer.toString();
sendMsg=sendMsg.replace(" ","");
log.info("giveAlarm sendMsg:{}",sendMsg);
CommUtil instance = CommUtil.getInstance();
instance.send(sendMsg);
}
}
}
协议对接内容
预警硬件通信协议文档
例:打开预警:FC006A000000103ECA16
-FC:前缀
-00:地址
-6A:功能项[亮光+声音]
-00000010 打开
-00000000 关闭
-00000001-0000000A 范围声音分为10个档位值
-3ECA 校验位CRC0CRC1
-16 拼接后缀
最后成功通讯,控制电路板开关灯!!!!
原网址: 访问
创建于: 2023-11-22 10:06:43
目录: default
标签: 无
未标明原创文章均为采集,版权归作者所有,转载无需和我联系,请注明原出处,南摩阿彌陀佛,知识,不只知道,要得到
最新评论