发布时间:2023-01-11 21:00
废水信息采用Modbus-RTU 协议
运维给过来的数据
废水 00.00.00.00:18800 心跳包 F020010F (HEX 60s一次)
0 通道 COD 量程0-1000
1 通道 PH 量程0-14
2 通道 氨氮 量程0-50
采集: 01 03 00 00 00 06 05 CB
CRC16
返回: 01 03 06 30 84 95 7B 0D BB CD AD
通道0 通道1 通道2 CRC16
公式:
COD= (30 84) / 65535 * 1000 = 189.51
(通道0十进制)12420 (int最大值) (最大量程)
科普一下,RTU和TCP区别是 RTU带有校验,没有协议报文头,用串口通信
我们是服务端,接受到心跳信息,回采集报文
上代码
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String strReq = Util.bytesToHexString(req);
log.info("收到的请求是:" + strReq);
if(strReq.equals(HEARTBEAT)){
ByteBuf echo = Unpooled.copiedBuffer(Util.hexString2Bytes("01030000000305CB"));
ctx.writeAndFlush(echo);
}else{
if(strReq.length() == 22 ){
if(CRC16Util.checkCrc(StrUtil.split(strReq, 2))){
Integer cod = Integer.parseInt(strReq.substring(6,10),16);
Integer ph = Integer.parseInt(strReq.substring(10,14),16);
Integer ad = Integer.parseInt(strReq.substring(14,18),16);
log.info(new BigDecimal(cod / INT_MAX * MAXIMUM1).setScale(2,BigDecimal.ROUND_DOWN));
log.info(new BigDecimal(ph / INT_MAX * MAXIMUM2).setScale(2,BigDecimal.ROUND_DOWN));
log.info(new BigDecimal(ad / INT_MAX * MAXIMUM3).setScale(2,BigDecimal.ROUND_DOWN));
}else{
log.error("crc error");
}
}else{
log.info("Invalid message");
}
}
super.channelRead(ctx, msg);
}
StrUitl使用hutool工具类
CRC16Class代码如下
/**
* ModbusCRC16工具类
* @author blackteachan
*
*/
public class CRC16Util {
/**
* ModeBusRTUCRC16校验及java代码
* @param msg 无校验位报文,eg:"01 03 00 00 00 01"
* @return
*/
public static String crc(String msg)
{
String[] info = msg.split(" ");
int[] data = new int[info.length];
for (int h = 0; h < info.length; h++)
{
data[h] = Integer.parseInt(info[h], 16);
}
int[] temdata = new int[data.length + 2];
//unsigned char alen = *aStr – 2; //CRC16只计算前两部分
int xda, xdapoly;
int i, j, xdabit;
xda = 0xFFFF;
xdapoly = 0xA001; // (X**16 + X**15 + X**2 + 1)
for (i = 0; i < data.length; i++)
{
xda ^= data[i];
for (j = 0; j < 8; j++)
{
xdabit = (int) (xda & 0x01);
xda >>= 1;
if (xdabit == 1)
{
xda ^= xdapoly;
}
}
}
System.arraycopy(data, 0, temdata, 0, data.length);
temdata[temdata.length - 2] = (int) (xda & 0xFF);
temdata[temdata.length - 1] = (int) (xda >> 8);
String crcInfo = getHexString(temdata);
return insertSpace(crcInfo);
}
public static String getHexString(int[] b) //获取16进制字符串
{
String crcInfo = "";
for (int p = 0; p < b.length; p++)
{
if (b[p] >= 16)
{
crcInfo += Integer.toHexString(b[p]).toString();
}
else
{
crcInfo = crcInfo + "0" + Integer.toHexString(b[p]).toString();
}
}
return crcInfo.toUpperCase();
}
//加入空格
public static String insertSpace(String marStr)
{
StringBuilder sb = new StringBuilder(marStr);
for (int i = marStr.length() - 2; i > 0; i -= 2)
{
sb.insert(i, " ");
}
String marStrNew = sb.toString();
return marStrNew;
}
/**
* 检查校验位
* @param arrayCommand 带校验位报文
* @return
*/
public static boolean checkCrc(String[] arrayCommand) {
// 带空格原始报文
String command1 = String.join(" ",arrayCommand);
// System.out.println("原始报文: " + command1);
// 原始报文校验位
String crc1 = (arrayCommand[arrayCommand.length - 2] +
arrayCommand[arrayCommand.length - 1]).toUpperCase();
// System.out.println("原始校验位: " + crc1);
// 带校验报文
String[] newArrayCommand = Arrays.copyOf(arrayCommand, arrayCommand.length - 2);
String command2 = String.join(" ", newArrayCommand);
String newCommand = crc(command2);
// System.out.println("新的报文: " + newCommand);
newArrayCommand = newCommand.split(" ");
// 新校验
String crc2 = (newArrayCommand[newArrayCommand.length - 2] +
newArrayCommand[newArrayCommand.length - 1]).toUpperCase();
// System.out.println("新的校验位: " + crc2);
return crc1.equals(crc2);
}
}