package com.tigeriot.globalcommonservice.model.productmanagerservice.modbusManager.utils;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.HexUtil;
import cn.hutool.core.util.NumberUtil;
import com.tigeriot.globalcommonservice.global.dto.devMessage.TigerIotMessage;
import com.tigeriot.globalcommonservice.global.exception.CodeException;
import com.tigeriot.globalcommonservice.importConfig.i18n.I18nUtil;
import com.tigeriot.globalcommonservice.model.productmanagerservice.iot.rundev.DevConst;
import com.tigeriot.globalcommonservice.model.productmanagerservice.modbusManager.vo.UniversalReadAndWriteReturn;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Map;
/**
* modbus常用工具类
*/
@Slf4j
public class ModbusManagerUtils {
/**
* 一个寄存器 两个字节
*/
public static final int RegisterAddressByteLength = 2;
/**
* 一个寄存器16比特
*/
public static final int RegisterAddressBitLength = 16;
/**
* 一个字节 8 比特长度
*/
public static final int ByteToBitLength = 8;
/**
* 默认广播地址
*/
public static final String boardCastModbusAddress = "00";
/**
* 是否是广播命令
*
* @param hex
* @return
*/
public static boolean isBoardCastCommand(String hex) {
return hex.startsWith(boardCastModbusAddress);
}
/**
* 快速发送消息
*
* @param deviceCommId
* @param content
* @return
* @throws CodeException
*/
public static TigerIotMessage sendCommonUrgentModbusTigerIotMessage(String deviceCommId, String content) throws CodeException {
try {
DevConst.MessageType messageType;
content = content.replace(" ", "");
if (content.length() >= 4) {
String function = content.substring(2, 4);
DevConst.FunctionCode functionCode = DevConst.FunctionCode.create(function);
if (functionCode != null) {
if (functionCode == DevConst.FunctionCode.ReadTheCoil ||
functionCode == DevConst.FunctionCode.ReadDiscreteInputs
|| functionCode == DevConst.FunctionCode.ReadHoldRegisters
|| functionCode == DevConst.FunctionCode.ReadTheInputRegister
) {
messageType = DevConst.MessageType.read;
} else {
messageType = DevConst.MessageType.write;
}
} else {
messageType = DevConst.MessageType.read;
}
} else {
messageType = DevConst.MessageType.read;
}
return TigerIotMessage.sendUrgent(deviceCommId, messageType, content, DevConst.ProtocolType.MODBUS_RTU, DevConst.TransportProtocol.TCP);
} catch (Exception e) {
throw new CodeException(HttpStatus.BAD_REQUEST.value(), I18nUtil.getMessage("消息不合法", "globalcommonservice.error.invalid.message"), e);
}
}
/**
* int转化为寄存器地址16进制补充2字节长度字符串 安全 转不成功会抛异常
*
* @param registerAddressDec
* @throws CodeException
*/
public static String safeDecToRegisterAddressHex(Integer registerAddressDec) throws CodeException {
if (registerAddressDec == null) {
log.trace("寄存器地址为空");
throw new CodeException(HttpStatus.BAD_REQUEST.value(), I18nUtil.getMessage("寄存器地址为空", "globalcommonservice.error.register.address.empty"));
}
String registerAddressHex = ModbusManagerUtils.intToHex(registerAddressDec);
return ModbusManagerUtils.supplyByteLength(registerAddressHex, ModbusManagerUtils.RegisterAddressByteLength);
}
/**
* 校验数据终端类型
*
* @param dataTerminalType
* @throws CodeException
*/
public static DevConst.DataTerminalType validateDataTerminalType(String dataTerminalType, int byteLength) throws CodeException {
dataTerminalType = dataTerminalType.replace(" ", "");
DevConst.DataTerminalType validate = DevConst.DataTerminalType.validate(dataTerminalType);
if (dataTerminalType.length() != byteLength) {
throw new CodeException(HttpStatus.BAD_REQUEST.value(), I18nUtil.getMessage("数据端类型与字节长度不匹配,必须为" + dataTerminalType.length() + "位", "globalcommonservice.error.data.terminal.type.byte.length.mismatch", dataTerminalType.length()));
}
return validate;
}
/**
* 校验数据类型和字节长度
*
* @param dataType
* @param byteLength
* @throws CodeException
*/
public static void validateDataTypeAndByteLength(byte dataType, int byteLength) throws CodeException {
log.trace("validateDataTypeAndByteLength dataType:{} byteLength:{}", dataType, byteLength);
if (DevConst.DataType.STRING.value == dataType) {
if (byteLength % 2 != 0 && byteLength > 0) {
throw new CodeException(HttpStatus.BAD_REQUEST.value(), I18nUtil.getMessage("字节长度必须是偶数", "globalcommonservice.error.byte.length.must.be.even"));
}
} else if (DevConst.DataType.INTEGER.value == dataType) {
//整数
if (byteLength > 8 || byteLength < 2) {
throw new CodeException(HttpStatus.BAD_REQUEST.value(), I18nUtil.getMessage("字节长度不合法", "globalcommonservice.error.invalid.byte.length"));
}
if (byteLength % 2 != 0) {
throw new CodeException(HttpStatus.BAD_REQUEST.value(), I18nUtil.getMessage("字节长度必须是偶数", "globalcommonservice.error.byte.length.must.be.even"));
}
} else if (DevConst.DataType.DOUBLE.value == dataType) {
if (byteLength != 4 && byteLength != 8) {
throw new CodeException(HttpStatus.BAD_REQUEST.value(), I18nUtil.getMessage("字节长度不合法", "globalcommonservice.error.invalid.byte.length"));
}
} else {
throw new CodeException(HttpStatus.BAD_REQUEST.value(), I18nUtil.getMessage("数据类型不合法", "globalcommonservice.error.invalid.data.type.simple"));
}
}
/**
* 校验modbus参数基本参数与数据类型校验 数据校验
*
* @param dataType
* @param byteLength
* @throws CodeException
*/
public static void validateModbusDataTypeParam(Byte dataType,
Integer byteLength,
String registerAddressHex,
Float conversionFactor,
Integer accuracy
) throws CodeException {
log.trace("validateModbusDataTypeParam registerAddressHex:{} dataType:{} byteLength:{} conversionFactor:{} accuracy:{} ", registerAddressHex, dataType, byteLength, conversionFactor, accuracy);
//!!判断都不能为空
if (dataType == null || byteLength == null || registerAddressHex == null || conversionFactor == null || accuracy == null) {
log.error("validateModbusDataTypeParam 参数不能为空 registerAddressHex:{} dataType:{} byteLength:{} conversionFactor:{} accuracy:{} ", registerAddressHex, dataType, byteLength, conversionFactor, accuracy);
throw new CodeException(HttpStatus.BAD_REQUEST.value(), I18nUtil.getMessage("参数不能为空", "globalcommonservice.error.param.not.empty"));
}
// 校验数据类型和字节长度 是否合法
validateDataTypeAndByteLength(dataType, byteLength);
//校验寄存器地址
if (!HexUtil.isHexNumber(registerAddressHex)) {
throw new CodeException(HttpStatus.BAD_REQUEST.value(), I18nUtil.getMessage("寄存器地址不属于16进制!!!", "globalcommonservice.error.register.address.not.hex"));
}
if (HexUtil.decodeHex(registerAddressHex).length != 2) {
throw new CodeException(HttpStatus.BAD_REQUEST.value(), I18nUtil.getMessage("寄存器地址必须为2字节!!!", "globalcommonservice.error.register.address.must.be.2bytes"));
}
//校验转换因子
if (conversionFactor < 0) {
throw new CodeException(HttpStatus.BAD_REQUEST.value(), I18nUtil.getMessage("转换因子不能小于0", "globalcommonservice.error.conversion.factor.not.negative"));
}
//精度
if (accuracy < 0) {
throw new CodeException(HttpStatus.BAD_REQUEST.value(), I18nUtil.getMessage("精度不能小于0", "globalcommonservice.error.precision.not.negative"));
}
//校验通过
}
//region modbus 指令相关
/**
* 计算 Modbus RTU CRC16 校验值
*
* @param buffer 要计算校验值的字节数组
* @return 包含两个字节的 CRC 校验值的字节数组,低位在前,高位在后
*/
public static byte[] calculateCRC16(byte[] buffer) {
int crc = 0xFFFF;
for (byte b : buffer) {
crc ^= (b & 0xFF);
for (int i = 0; i < 8; i++) {
if ((crc & 0x0001) != 0) {
crc >>= 1;
crc ^= 0xA001;
} else {
crc >>= 1;
}
}
}
// 将 CRC 结果拆分为两个字节
byte[] crcBytes = new byte[2];
crcBytes[0] = (byte) (crc & 0xFF);
crcBytes[1] = (byte) ((crc >> 8) & 0xFF);
return crcBytes;
}
/**
* 校验字节数组的 CRC 是否正确
*
* @param buffer 包含数据和 CRC 校验码的字节数组
* @return 如果 CRC 校验通过返回 true,否则返回 false
*/
public static boolean verifyCRC16(byte[] buffer) {
if (buffer.length < 2) {
return false;
}
// 提取原始数据部分(去除最后两个字节的 CRC 校验码)
byte[] data = new byte[buffer.length - 2];
System.arraycopy(buffer, 0, data, 0, data.length);
// 重新计算 CRC 校验码
byte[] calculatedCRC = calculateCRC16(data);
// 提取接收到的 CRC 校验码
byte[] receivedCRC = new byte[2];
System.arraycopy(buffer, buffer.length - 2, receivedCRC, 0, 2);
// 比较重新计算的 CRC 校验码和接收到的 CRC 校验码
return calculatedCRC[0] == receivedCRC[0] && calculatedCRC[1] == receivedCRC[1];
}
/**
* 生成 Modbus-RTU 报文命令 基础
*
* @param rs485 设备 ID
* @param functionCode 功能码
* @param registerAddressDec 起始寄存器地址
* @param dataValues 数据值
* @return 生成的 Modbus-RTU 报文命令字节数组
*/
public static String generateModbusRTUCommand(byte rs485,
DevConst.FunctionCode functionCode,
int registerAddressDec, byte[] dataValues) {
int commandLength = 4 + dataValues.length;
//指令内容
byte[] commandContent = new byte[commandLength];
commandContent[0] = rs485;// 设备ID号
commandContent[1] = HexUtil.decodeHex(functionCode.getValue())[0];//功能码
byte[] registerAddressBytes = intToTwoBytes(registerAddressDec);//寄存器地址
commandContent[2] = registerAddressBytes[0];
commandContent[3] = registerAddressBytes[1];
//将内容赋值进来
System.arraycopy(dataValues, 0, commandContent, 4, dataValues.length);
// 计算 CRC16 校验码
byte[] crcBytes = calculateCRC16(commandContent);
// 将 CRC 校验码添加到报文中
byte[] finalCommand = new byte[commandLength + 2];
System.arraycopy(commandContent, 0, finalCommand, 0, commandLength);
System.arraycopy(crcBytes, 0, finalCommand, commandLength, 2);
//将生成报文转化为16进制
return HexUtil.encodeHexStr(finalCommand).toUpperCase();
}
/**
* 生成读取寄存器指令
*
* @param rs485 485地址
* @param registerAddressDec 寄存器起始地址
* @param readByteLength 读取字节长度
* @return
*/
public static String generateReadRegisterCommand(byte rs485,
int registerAddressDec,
int readByteLength) {
return generateModbusRTUCommand(rs485, DevConst.FunctionCode.ReadHoldRegisters, registerAddressDec, intToTwoBytes(readByteLength / 2));
}
/**
* 写入读取寄存器指令
*
* @param rs485 485地址
* @param registerAddressDec 起始寄存器地址
* @param data 写入多字节长度
* @return
*/
public static String generateMultiWriteRegisterCommand(byte rs485,
int registerAddressDec,
byte[] data
) {
int writeByteLength = data.length;
byte[] writeRegisterNum = intToTwoBytes(writeByteLength / 2);
byte writeByteLengthBytes = (byte) writeByteLength;
data = ArrayUtil.addAll(writeRegisterNum, new byte[]{writeByteLengthBytes}, data);
return generateModbusRTUCommand(rs485, DevConst.FunctionCode.WriteMultipleRegisters, registerAddressDec, data);
}
/**
* 写入单个寄存器
*
* @param rs485 485地址
* @param registerAddressDec 起始寄存器地址
* @param highByte 高字节
* @param lowByte 低字节
* @return
*/
public static String generateSingleWriteRegisterCommand(byte rs485,
int registerAddressDec,
byte highByte, byte lowByte
) {
return generateModbusRTUCommand(rs485, DevConst.FunctionCode.WriteASingleRegister, registerAddressDec, new byte[]{highByte, lowByte});
}
//endregion
//region 16进制相关 工具
/**
* 补充长度 hex字节长度必须不超过byteLength
*
* @param hex hex字符串值
* @param byteLength 补齐字符串长度
* @return
*/
public static String supplyByteLength(String hex, int byteLength) {
int strLength = byteLength * 2;
int hexLength = hex.length();
if (hexLength > strLength) {
throw new IllegalArgumentException(I18nUtil.getMessage("hex的字节长度大于byteLength=" + byteLength, "globalcommonservice.error.hex.byte.length.exceeds", byteLength));
}
int supplyNum = strLength - hexLength;
StringBuilder result = new StringBuilder();
for (int i = 0; i < supplyNum; i++) {
result.append('0');
}
result.append(hex);
return result.toString();
}
/**
* 偏移16进制 暂时寄存器地址偏移用
* 注意:计算起始地址是属于自己值范围 计算最终地址是自己所在值范围寄存器地址+1
*
* @param hex 被偏移16进制
* @param offsetUnits 偏移单位
* @param offset 偏移值 必须大于0 以偏移单位偏移多少 字节或者位
* @return 10进制 后面自行处理 可自己转16进制 返回的是寄存器的地址
*/
public static int offsetHex(String hex, DevConst.OffsetUnits offsetUnits, int offset) {
if (offset < 0) {
throw new IllegalArgumentException(I18nUtil.getMessage("偏移值必须大于0", "globalcommonservice.error.offset.value.must.be.greater.than.zero"));
}
int startRegisterAddressDec = Integer.parseInt(hex, 16);
return offsetDec(startRegisterAddressDec, offsetUnits, offset);
}
/**
* 偏移10进制 暂时寄存器用 暂时寄存器地址偏移用
* 注意:计算起始地址是属于自己值范围 计算最终地址是自己所在值范围寄存器地址+1
*
* @param dec 被偏移10进制
* @param offsetUnits 偏移单位
* @param offset 偏移值 必须大于0 必须大于0 以偏移单位偏移多少 字节或者位
* @return 10进制 后面自行处理 可自己转16进制 返回的是寄存器的地址
*/
public static int offsetDec(int dec, DevConst.OffsetUnits offsetUnits, int offset) {
if (offset < 0) {
throw new IllegalArgumentException(I18nUtil.getMessage("偏移值必须大于等于0", "globalcommonservice.error.offset.value.must.be.greater.than.or.equal.to.zero"));
}
if (offsetUnits == DevConst.OffsetUnits.BYTE) {
//最终寄存器地址
return dec + offset / 2;
} else {
//偏移位
return dec + offset / 16;
}
}
public static String logHexCommand(String command) {
StringBuilder sb = new StringBuilder();
if (command.length() % 2 != 0) {
//如果不等于0
command = "0" + command;
}
char[] charArray = command.toCharArray();
for (int i = 0; i < charArray.length; i++) {
if (i % 2 != 0 && i != charArray.length - 1) {
sb.append(charArray[i]);
sb.append(" ");
} else {
sb.append(charArray[i]);
}
}
return sb.toString();
}
//endregion
//region String
/**
* 二进制字符串转字节数组 从后截取8位开始
* 举例子
* 11 00000001
* [3,1]
*
* @param binaryString
* @return
*/
public static byte[] binaryStringToByteArrayPost(String binaryString) {
int length = (binaryString.length() + 7) / 8;
byte[] byteArray = new byte[length];
int startIndex = binaryString.length();
for (int i = length - 1; i >= 0; i--) {
int endIndex = startIndex;
startIndex = Math.max(0, startIndex - 8);
StringBuilder byteStr = new StringBuilder(binaryString.substring(startIndex, endIndex));
while (byteStr.length() < 8) {
byteStr.insert(0, "0");
}
byteArray[i] = (byte) Integer.parseInt(byteStr.toString(), 2);
}
return byteArray;
}
/**
* 二进制字符串转字节数组 从前截取8位开始
* 举例子
* 11 00000001
* 11000000 01
* [192,1]
*
* @param binaryStr
* @return
*/
public static byte[] binaryStringToByteArrayFront(String binaryStr) {
int length = binaryStr.length();
// 计算需要的字节数
int byteCount = (length + 7) / 8;
byte[] byteArray = new byte[byteCount];
for (int i = 0; i < byteCount; i++) {
int startIndex = i * 8;
int endIndex = Math.min(startIndex + 8, length);
StringBuilder byteBinary = new StringBuilder(binaryStr.substring(startIndex, endIndex));
// 不足 8 位时,前面补 0
while (byteBinary.length() < 8) {
byteBinary.insert(0, "0");
}
byteArray[i] = (byte) Integer.parseInt(byteBinary.toString(), 2);
}
return byteArray;
}
/**
* 字节数组转二进制字符串
*
* @param bytes
* @return
*/
public static String bytesToBinaryStr(byte[] bytes) {
StringBuilder binaryString = new StringBuilder();
for (byte b : bytes) {
for (int i = 7; i >= 0; i--) {
binaryString.append((b >> i) & 1);
}
}
return binaryString.toString();
}
//endregion
//region double 转 8 字节
public static String doubleToHex(double value) {
// 将 double 转换为 long 类型的位表示
long bits = Double.doubleToLongBits(value);
// 将 long 类型的位表示转换为十六进制字符串
return Long.toHexString(bits);
}
public static double hexToDouble(String hex) {
// 将十六进制字符串转换为 long 类型的整数
long bits = Long.parseLong(hex, 16);
// 将 long 类型的整数转换为 double 类型的值
return Double.longBitsToDouble(bits);
}
public static byte[] doubleToEightBytes(double value) {
// 创建一个容量为 8 字节的 ByteBuffer
ByteBuffer buffer = ByteBuffer.allocate(8);
// 将 double 值放入 ByteBuffer
buffer.putDouble(value);
// 返回 ByteBuffer 中的字节数组
return buffer.array();
}
public static double eightBytesToDouble(byte[] bytes) {
// 创建一个容量为 8 字节的 ByteBuffer
ByteBuffer buffer = ByteBuffer.wrap(bytes);
// 从 ByteBuffer 中读取 double 类型的值
return buffer.getDouble();
}
public static double binaryStringToDouble(String binary) {
// 将二进制字符串转换为长整型
long longValue = Long.parseLong(binary, 2);
// 使用 Double.longBitsToDouble 方法将长整型转换为 double 类型
return Double.longBitsToDouble(longValue);
}
//endregion
//region long 转 8 字节
public static byte[] longToEightBytes(long value) {
ByteBuffer buffer = ByteBuffer.allocate(8);
buffer.putLong(value);
return buffer.array();
}
public static long eightBytesToLong(byte[] bytes) {
ByteBuffer buffer = ByteBuffer.wrap(bytes);
return buffer.getLong();
}
public static String longToHex(long value) {
return Long.toHexString(value);
}
public static long hexToLong(String hex) {
return Long.parseLong(hex, 16);
}
//endregion
//region float 转 4 字节
public static String floatToHex(float value) {
// 将 float 转换为 int 类型的位表示
int bits = Float.floatToIntBits(value);
// 将 int 类型的位表示转换为十六进制字符串
return Integer.toHexString(bits);
}
public static float hexToFloat(String hex) {
// 将十六进制字符串转换为 int 类型的整数
int bits = Integer.parseInt(hex, 16);
// 将 int 类型的整数转换为 float 类型的值
return Float.intBitsToFloat(bits);
}
public static byte[] floatToFourBytes(float value) {
ByteBuffer buffer = ByteBuffer.allocate(4);
buffer.putFloat(value);
return buffer.array();
}
public static float fourBytesToFloat(byte[] bytes) {
ByteBuffer buffer = ByteBuffer.wrap(bytes);
return buffer.getFloat();
}
//endregion
//region int 转4字节
public static byte[] intToFourBytes(int value) {
ByteBuffer buffer = ByteBuffer.allocate(4);
buffer.putInt(value);
return buffer.array();
}
public static int fourBytesToInt(byte[] bytes) {
ByteBuffer buffer = ByteBuffer.wrap(bytes);
return buffer.getInt();
}
public static int hexToInt(String hex) {
return Integer.parseInt(hex, 16);
}
public static String intToHex(int value) {
return Integer.toHexString(value);
}
/**
* 将 int 类型转换为 2 字节的字节数组 大端
*
* @param value 要转换的 int 值
* @return 包含 2 个字节的字节数组
*/
public static byte[] intToTwoBytes(int value) {
byte[] result = new byte[2];
// 提取低 8 位
result[1] = (byte) (value & 0xFF);
// 提取高 8 位
result[0] = (byte) ((value >> 8) & 0xFF);
return result;
}
/**
* 一字节转无符号Int
*
* @return
*/
public static int oneByteToInt(byte b) throws CodeException {
return b & 0xFF;
}
/**
* 两字节转Int
*
* @return
*/
public static int twoBytesToInt(byte[] bytes) throws CodeException {
if (bytes.length != 2) {
throw new CodeException(HttpStatus.BAD_REQUEST.value(), "字节数组长度必须为2");
}
return ByteBuffer.wrap(bytes).order(ByteOrder.BIG_ENDIAN).getShort() & 0xFFFF;
}
/**
* 两字节转Int 按照short的格式转的 无符号
*
* @param highByte
* @param lowByte
* @return
*/
public static int twoBytesToShortInt(byte highByte, byte lowByte) {
// 将高字节左移 8 位,然后与低字节进行按位或操作
return ((highByte << 8) | (lowByte & 0xFF)) & 0xFFFF;
}
//endregion
//region short
public static byte[] shortToTwoBytes(short value) {
ByteBuffer buffer = ByteBuffer.allocate(2);
buffer.putShort(value);
return buffer.array();
}
public static short twoBytesToShort(byte[] bytes) {
ByteBuffer buffer = ByteBuffer.wrap(bytes);
return buffer.getShort();
}
public static String shortToHex(short value) {
return Integer.toHexString(value & 0xFFFF);
}
public static short hexToShort(String hex) {
return Short.parseShort(hex, 16);
}
//endregion
//region byte
/**
* 必定两位字符串 1字节对应两个16进制字符串
*
* @param b
* @return
*/
public static String byteToHex(byte b) {
//这里与操作是因为 转化为 int的时候 需要变成无符号的 字节 所以与 是把符号位去除,最后转化为16进制
String hex = Integer.toHexString(b & 0xFF);
if (hex.length() == 1) {
hex = "0" + hex;
}
return hex;
}
public static byte hexToByte(String hex) {
return (byte) Integer.parseInt(hex, 16);
}
/**
* 将大端序双向转化自定义顺序 负负得正 返回新数字
*
* @param bytes
* @param endianType
* @return
* @throws CodeException
*/
public static byte[] convertBigEndianSwapCustomOrder(byte[] bytes, String endianType) throws CodeException {
int byteLength = bytes.length;
if (byteLength <= 1) {
return bytes;
}
DevConst.DataTerminalType dataTerminalType = validateDataTerminalType(endianType, byteLength);
byte[] newByte = null;
char[] order = null;
if (dataTerminalType == DevConst.DataTerminalType.AB
||
dataTerminalType == DevConst.DataTerminalType.AB_CD
||
dataTerminalType == DevConst.DataTerminalType.AB_CD_EF_GH
) {
//无需排序
return bytes;
} else if (dataTerminalType == DevConst.DataTerminalType.BA
||
dataTerminalType == DevConst.DataTerminalType.DC_BA
||
dataTerminalType == DevConst.DataTerminalType.HG_FE_DC_BA
) {
return ArrayUtil.reverse(bytes);
} else if (byteLength == 4) {
newByte = new byte[4];
order = DevConst.DataTerminalType.order4;
} else {
newByte = new byte[8];
order = DevConst.DataTerminalType.order8;
}
char[] charArray = dataTerminalType.value.toCharArray();
for (int i = 0; i < charArray.length; i++) {
//二分查找查询 自定义顺序 在大端序的某个索引
int index = Arrays.binarySearch(order, charArray[i]);
//将当前索引的值 赋予 传入数组自定义序在大端序中索引的值
newByte[i] = bytes[index];
}
return newByte;
}
//endregion
//region bean 字段类型
/**
* 将解析出的Pair 赋值到 update 中
*
* @param update
* @param pairs
*/
public static void fieldMappingValue(Object update, Map<String, Object> pairs) {
if (pairs != null && !pairs.isEmpty() && update != null) {
BeanUtil.copyProperties(pairs, update);
}
}
/**
* 从字节数组中读取寄存器的值或字节的值解析出来 根据数据类型(字节索引版本)
*
* @param data 源数据字节数组
* @param dataStartByteIndex 数据起始字节索引
* @param readStartByteIndex 参数起始字节索引
* @param offsetStartIndex 参数起始下标
* @param offsetNum 参数偏移数量
* @param offsetUnits 参数偏移单位
* @param dataType 参数类型
* @param dataTerminalType 数据终端类型
* @param conversionFactor 转换因子
* @param accuracy 精度
* @return
* @throws CodeException
*/
public static UniversalReadAndWriteReturn readModbusByteArrayParamValueByByteIndex(
byte[] data,
int dataStartByteIndex,
int readStartByteIndex,
int offsetStartIndex,
int offsetNum,
DevConst.OffsetUnits offsetUnits,
DevConst.DataType dataType,
String dataTerminalType,
double conversionFactor,
int accuracy
) throws CodeException {
if (offsetUnits == DevConst.OffsetUnits.BYTE) {
//字节读取模式:支持负数索引
//计算参数字节索引相对于数据起始位置的字节偏移
int byteOffset = readStartByteIndex - dataStartByteIndex;
//计算实际的字节起始索引(支持负数)
int actualByteStartIndex = byteOffset + offsetStartIndex;
//检查边界
if (actualByteStartIndex < 0) {
throw new IllegalArgumentException(I18nUtil.getMessage("字节起始索引不能小于0,计算结果:" + actualByteStartIndex,
"globalcommonservice.error.byte.start.index.negative", actualByteStartIndex));
}
int actualByteEndIndex = actualByteStartIndex + offsetNum;
if (actualByteEndIndex > data.length) {
throw new IllegalArgumentException(I18nUtil.getMessage("字节结束索引超出数据范围,结束索引:" + actualByteEndIndex + ",数据总字节数:" + data.length,
"globalcommonservice.error.byte.end.index.overflow", actualByteEndIndex, data.length));
}
//提取指定字节范围的数据
byte[] sub = Arrays.copyOfRange(data, actualByteStartIndex, actualByteEndIndex);
//根据数据类型解析
if (dataType == DevConst.DataType.INTEGER) {
//整数类型
Object originalValue;
if (offsetNum == 1) {
originalValue = (long) sub[0];
} else if (offsetNum == 2) {
sub = convertBigEndianSwapCustomOrder(sub, dataTerminalType);
originalValue = (long) twoBytesToShort(sub);
} else if (offsetNum == 4) {
sub = convertBigEndianSwapCustomOrder(sub, dataTerminalType);
originalValue = (long) fourBytesToInt(sub);
} else if (offsetNum == 8) {
sub = convertBigEndianSwapCustomOrder(sub, dataTerminalType);
originalValue = eightBytesToLong(sub);
} else {
throw new IllegalArgumentException(I18nUtil.getMessage("无法解析的整数类型,字节长度:" + offsetNum,
"globalcommonservice.error.unparseable.integer.type.with.length", offsetNum));
}
// 计算处理后的值:原始值 * 转换因子,并按精度四舍五入
double processedValue = NumberUtil.round(NumberUtil.mul(originalValue.toString(), String.valueOf(conversionFactor)), accuracy).doubleValue();
UniversalReadAndWriteReturn result = new UniversalReadAndWriteReturn();
result.setOriginalValue(originalValue);
result.setProcessedValue(processedValue);
return result;
} else if (dataType == DevConst.DataType.DOUBLE) {
//浮点类型
Object originalValue;
if (offsetNum == 4) {
sub = convertBigEndianSwapCustomOrder(sub, dataTerminalType);
originalValue = fourBytesToFloat(sub);
} else if (offsetNum == 8) {
sub = convertBigEndianSwapCustomOrder(sub, dataTerminalType);
originalValue = eightBytesToDouble(sub);
} else {
throw new IllegalArgumentException(I18nUtil.getMessage("无法解析的浮点类型,字节长度:" + offsetNum,
"globalcommonservice.error.unparseable.float.type.with.length", offsetNum));
}
// 计算处理后的值:原始值 * 转换因子,并按精度四舍五入
double processedValue = NumberUtil.round(NumberUtil.mul(originalValue.toString(), String.valueOf(conversionFactor)), accuracy).doubleValue();
UniversalReadAndWriteReturn result = new UniversalReadAndWriteReturn();
result.setOriginalValue(originalValue);
result.setProcessedValue(processedValue);
return result;
} else if (dataType == DevConst.DataType.STRING) {
//字符串类型
String originalValue;
if (dataTerminalType.equalsIgnoreCase(DevConst.DataTerminalType.AB.getValue())) {
originalValue = new String(sub, StandardCharsets.UTF_8);
} else {
sub = ArrayUtil.reverse(sub);
originalValue = new String(sub, StandardCharsets.UTF_8);
}
// 只返回有效字符:去除末尾的空字符(\0)和空白字符
String trimmedValue = trimTrailingNullAndWhitespace(originalValue);
// 字符串类型,原始值和处理值相同
UniversalReadAndWriteReturn result = new UniversalReadAndWriteReturn();
result.setOriginalValue(trimmedValue);
result.setProcessedValue(trimmedValue);
return result;
} else {
throw new IllegalArgumentException(I18nUtil.getMessage("无法解析的数据类型", "globalcommonservice.error.unparseable.data.type"));
}
} else {
//位读取模式:支持负数索引
//将整个数据源转换为二进制字符串
String fullBinaryStr = ModbusManagerUtils.bytesToBinaryStr(data);
//计算参数字节索引相对于数据起始位置的位偏移(1个字节=8位)
int bitOffset = (readStartByteIndex - dataStartByteIndex) * ByteToBitLength;
//计算实际的位起始索引(支持负数)
int actualBitStartIndex = bitOffset + offsetStartIndex;
//检查边界
if (actualBitStartIndex < 0) {
throw new IllegalArgumentException(I18nUtil.getMessage("位起始索引不能小于0,计算结果:" + actualBitStartIndex,
"globalcommonservice.error.bit.start.index.negative", actualBitStartIndex));
}
int actualBitEndIndex = actualBitStartIndex + offsetNum;
if (actualBitEndIndex > fullBinaryStr.length()) {
throw new IllegalArgumentException(I18nUtil.getMessage("位结束索引超出数据范围,结束索引:" + actualBitEndIndex + ",数据总位数:" + fullBinaryStr.length(),
"globalcommonservice.error.bit.end.index.overflow", actualBitEndIndex, fullBinaryStr.length()));
}
//提取指定位范围的二进制字符串
String binaryValue = fullBinaryStr.substring(actualBitStartIndex, actualBitEndIndex);
//根据数据类型解析
if (dataType == DevConst.DataType.INTEGER) {
Long originalValue = Long.parseLong(binaryValue, 2);
// 计算处理后的值:原始值 * 转换因子,并按精度四舍五入
double processedValue = NumberUtil.round(NumberUtil.mul(originalValue.toString(), String.valueOf(conversionFactor)), accuracy).doubleValue();
UniversalReadAndWriteReturn result = new UniversalReadAndWriteReturn();
result.setOriginalValue(originalValue);
result.setProcessedValue(processedValue);
return result;
} else if (dataType == DevConst.DataType.DOUBLE) {
Double originalValue = binaryStringToDouble(binaryValue);
// 计算处理后的值:原始值 * 转换因子,并按精度四舍五入
double processedValue = NumberUtil.round(NumberUtil.mul(originalValue.toString(), String.valueOf(conversionFactor)), accuracy).doubleValue();
UniversalReadAndWriteReturn result = new UniversalReadAndWriteReturn();
result.setOriginalValue(originalValue);
result.setProcessedValue(processedValue);
return result;
} else if (dataType == DevConst.DataType.STRING) {
String originalValue;
if (dataTerminalType.equalsIgnoreCase("AB")) {
byte[] bytes = binaryStringToByteArrayPost(binaryValue);
originalValue = new String(bytes, StandardCharsets.UTF_8);
} else {
byte[] bytes = binaryStringToByteArrayPost(binaryValue);
bytes = ArrayUtil.reverse(bytes);
originalValue = new String(bytes, StandardCharsets.UTF_8);
}
// 只返回有效字符:去除末尾的空字符(\0)和空白字符
String trimmedValue = trimTrailingNullAndWhitespace(originalValue);
// 字符串类型,原始值和处理值相同
UniversalReadAndWriteReturn result = new UniversalReadAndWriteReturn();
result.setOriginalValue(trimmedValue);
result.setProcessedValue(trimmedValue);
return result;
} else {
throw new IllegalArgumentException(I18nUtil.getMessage("无法解析的数据类型", "globalcommonservice.error.unparseable.data.type"));
}
}
}
/**
* 从字节数组中读取寄存器的值或字节的值解析出来 根据数据类型
*
* @param data 源数据字节数组
* @param dataStartRegisterAddrDec 指令起始寄存器地址 10进制
* @param readStartRegisterAddrDec 参数起始寄存器地址 10进制
* @param offsetStartIndex 参数起始下标
* @param offsetNum 参数偏移数量
* @param offsetUnits 参数偏移单位
* @param dataType 参数类型
* @param dataTerminalType 数据终端类型
* @param conversionFactor 转换因子
* @param accuracy 精度
* @return
* @throws CodeException
*/
public static UniversalReadAndWriteReturn readModbusByteArrayParamValue(
byte[] data,
int dataStartRegisterAddrDec,
int readStartRegisterAddrDec,
int offsetStartIndex,
int offsetNum,
DevConst.OffsetUnits offsetUnits,
DevConst.DataType dataType,
String dataTerminalType,
double conversionFactor,
int accuracy
) throws CodeException {
// 将寄存器地址转换为字节索引(1个寄存器=2字节)
int dataStartByteIndex = dataStartRegisterAddrDec * RegisterAddressByteLength;
int readStartByteIndex = readStartRegisterAddrDec * RegisterAddressByteLength;
// 复用字节索引版本的方法
return readModbusByteArrayParamValueByByteIndex(
data,
dataStartByteIndex,
readStartByteIndex,
offsetStartIndex,
offsetNum,
offsetUnits,
dataType,
dataTerminalType,
conversionFactor,
accuracy
);
}
/**
* 从字节数组写入字节或者位或者字符 根据数据类型(字节索引版本)
*
* @param data 源数据字节数组
* @param dataStartByteIndex 数据起始字节索引
* @param readStartByteIndex 参数起始字节索引
* @param offsetStartIndex 参数起始下标
* @param offsetNum 参数偏移数量
* @param offsetUnits 参数偏移单位
* @param dataType 参数类型
* @param dataTerminalType 数据终端类型
* @param value 写入的值
* @param conversionFactor 转换因子
* @param accuracy 精度
* @return 包含原始值和处理值的对象
* @throws CodeException
*/
public static UniversalReadAndWriteReturn writeModbusByteArrayParamValueByByteIndex(
byte[] data,
int dataStartByteIndex,
int readStartByteIndex,
int offsetStartIndex,
int offsetNum,
DevConst.OffsetUnits offsetUnits,
DevConst.DataType dataType,
String dataTerminalType,
Object value,
double conversionFactor,
int accuracy
) throws CodeException {
// 创建返回对象
UniversalReadAndWriteReturn result = new UniversalReadAndWriteReturn();
result.setOriginalValue(value);
if (offsetUnits == DevConst.OffsetUnits.BYTE) {
//字节写入模式:支持负数索引
//计算参数字节索引相对于数据起始位置的字节偏移
int byteOffset = readStartByteIndex - dataStartByteIndex;
//计算实际的字节起始索引(支持负数)
int actualByteStartIndex = byteOffset + offsetStartIndex;
//检查边界
if (actualByteStartIndex < 0) {
throw new IllegalArgumentException(I18nUtil.getMessage("字节起始索引不能小于0,计算结果:" + actualByteStartIndex,
"globalcommonservice.error.byte.start.index.negative", actualByteStartIndex));
}
int actualByteEndIndex = actualByteStartIndex + offsetNum;
if (actualByteEndIndex > data.length) {
throw new IllegalArgumentException(I18nUtil.getMessage("字节结束索引超出数据范围,结束索引:" + actualByteEndIndex + ",数据总字节数:" + data.length,
"globalcommonservice.error.byte.end.index.overflow", actualByteEndIndex, data.length));
}
//准备写入的字节数组
byte[] writeBytes = null;
if (dataType == DevConst.DataType.STRING && value instanceof String str) {
//字符串类型
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
if (bytes.length > offsetNum) {
throw new CodeException(HttpStatus.BAD_REQUEST.value(), I18nUtil.getMessage("字符串长度不能大于写入长度,字符串字节数:" + bytes.length + ",允许写入:" + offsetNum,
"globalcommonservice.error.string.length.exceeds.write.length.detail", bytes.length, offsetNum));
} else if (bytes.length < offsetNum) {
//如果长度小于偏移量,则用0填充
byte[] newBytes = new byte[offsetNum];
System.arraycopy(bytes, 0, newBytes, 0, bytes.length);
writeBytes = newBytes;
} else {
//如果长度等于偏移量,则直接赋值
writeBytes = bytes;
}
// 字符串类型,原始值和处理值相同
result.setProcessedValue(str);
} else if (dataType == DevConst.DataType.INTEGER && value instanceof Number num) {
//整数类型
double v = num.doubleValue();
double processedValue = NumberUtil.div(v, conversionFactor, accuracy);
if (offsetNum == 1) {
writeBytes = new byte[1];
writeBytes[0] = (byte) processedValue;
} else if (offsetNum == 2) {
writeBytes = shortToTwoBytes((short) processedValue);
} else if (offsetNum == 4) {
writeBytes = intToFourBytes((int) processedValue);
} else if (offsetNum == 8) {
writeBytes = longToEightBytes((long) processedValue);
} else {
throw new IllegalArgumentException(I18nUtil.getMessage("无法解析的整数类型,字节长度:" + offsetNum,
"globalcommonservice.error.unparseable.integer.type.with.length", offsetNum));
}
// 记录处理后的值
result.setProcessedValue(processedValue);
} else if (dataType == DevConst.DataType.DOUBLE && value instanceof Number num) {
//浮点类型
double v = num.doubleValue();
double processedValue = NumberUtil.div(v, conversionFactor, accuracy);
if (offsetNum == 4) {
writeBytes = floatToFourBytes((float) processedValue);
} else if (offsetNum == 8) {
writeBytes = doubleToEightBytes(processedValue);
} else {
throw new IllegalArgumentException(I18nUtil.getMessage("无法解析的浮点类型,字节长度:" + offsetNum,
"globalcommonservice.error.unparseable.float.type.with.length", offsetNum));
}
// 记录处理后的值
result.setProcessedValue(processedValue);
} else {
throw new IllegalArgumentException(I18nUtil.getMessage("无法解析的类型或值类型不匹配", "globalcommonservice.error.unparseable.type.or.mismatch"));
}
//转换字节序并写入到原数组中(不改变引用)
if (dataType == DevConst.DataType.STRING) {
// 字符串类型:根据数据端类型判断正序还是倒序
if (dataTerminalType.equalsIgnoreCase(DevConst.DataTerminalType.AB.getValue())) {
// AB类型:保持正序,不需要转换
// writeBytes 保持不变
} else {
// 其他类型:倒序
writeBytes = ArrayUtil.reverse(writeBytes);
}
} else {
// 非字符串类型:使用原有的字节序转换逻辑
writeBytes = convertBigEndianSwapCustomOrder(writeBytes, dataTerminalType);
}
System.arraycopy(writeBytes, 0, data, actualByteStartIndex, writeBytes.length);
} else {
//位写入模式:支持负数索引
//将整个数据源转换为二进制字符串
String fullBinaryStr = ModbusManagerUtils.bytesToBinaryStr(data);
char[] fullBinaryChars = fullBinaryStr.toCharArray();
//计算参数字节索引相对于数据起始位置的位偏移(1个字节=8位)
int bitOffset = (readStartByteIndex - dataStartByteIndex) * ByteToBitLength;
//计算实际的位起始索引(支持负数)
int actualBitStartIndex = bitOffset + offsetStartIndex;
//检查边界
if (actualBitStartIndex < 0) {
throw new IllegalArgumentException(I18nUtil.getMessage("位起始索引不能小于0,计算结果:" + actualBitStartIndex,
"globalcommonservice.error.bit.start.index.negative", actualBitStartIndex));
}
int actualBitEndIndex = actualBitStartIndex + offsetNum;
if (actualBitEndIndex > fullBinaryChars.length) {
throw new IllegalArgumentException(I18nUtil.getMessage("位结束索引超出数据范围,结束索引:" + actualBitEndIndex + ",数据总位数:" + fullBinaryChars.length,
"globalcommonservice.error.bit.end.index.overflow", actualBitEndIndex, fullBinaryChars.length));
}
//根据数据类型准备要写入的二进制字符串
char[] writeBinaryChars = null;
if (dataType == DevConst.DataType.STRING && value instanceof String str) {
//字符串类型
byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
int requiredBytes = offsetNum / ByteToBitLength;
if (offsetNum % ByteToBitLength != 0) {
throw new IllegalArgumentException(I18nUtil.getMessage("字符串写入的位数必须是8的倍数,当前位数:" + offsetNum,
"globalcommonservice.error.string.bit.count.must.be.multiple.of.8", offsetNum));
}
if (bytes.length > requiredBytes) {
throw new CodeException(HttpStatus.BAD_REQUEST.value(), I18nUtil.getMessage("字符串长度不能大于写入长度,字符串字节数:" + bytes.length + ",允许写入:" + requiredBytes,
"globalcommonservice.error.string.length.exceeds.write.length.detail", bytes.length, requiredBytes));
} else if (bytes.length < requiredBytes) {
//如果长度小于要求,则用0填充
byte[] newBytes = new byte[requiredBytes];
System.arraycopy(bytes, 0, newBytes, 0, bytes.length);
bytes = newBytes;
}
writeBinaryChars = ModbusManagerUtils.bytesToBinaryStr(bytes).toCharArray();
// 字符串类型,原始值和处理值相同
result.setProcessedValue(str);
} else if (dataType == DevConst.DataType.INTEGER && value instanceof Number num) {
//整数类型
double v = num.doubleValue();
double processedValue = NumberUtil.div(v, conversionFactor, accuracy);
long bitValue = (long) processedValue;
//转换为二进制字符串
String binaryStr = Long.toBinaryString(bitValue);
//检查位数是否超出
if (binaryStr.length() > offsetNum) {
throw new IllegalArgumentException(I18nUtil.getMessage("整数值需要的位数超出允许范围,需要:" + binaryStr.length() + ",允许:" + offsetNum,
"globalcommonservice.error.integer.bit.count.overflow", binaryStr.length(), offsetNum));
}
//补齐到指定位数(前面补0)
StringBuilder sb = new StringBuilder();
for (int i = 0; i < offsetNum - binaryStr.length(); i++) {
sb.append('0');
}
sb.append(binaryStr);
writeBinaryChars = sb.toString().toCharArray();
// 记录处理后的值
result.setProcessedValue(processedValue);
} else if (dataType == DevConst.DataType.DOUBLE) {
//按位写入浮点 不支持
throw new IllegalArgumentException(I18nUtil.getMessage("不支持按位写入浮点", "globalcommonservice.error.bitwise.write.float.not.supported"));
} else {
throw new IllegalArgumentException(I18nUtil.getMessage("无法解析的类型或值类型不匹配", "globalcommonservice.error.unparseable.type.or.mismatch"));
}
//将准备好的二进制字符写入到全局二进制字符数组的指定位置
System.arraycopy(writeBinaryChars, 0, fullBinaryChars, actualBitStartIndex, writeBinaryChars.length);
//将修改后的二进制字符数组转换回字节数组,并写入原数组(不改变引用)
byte[] modifiedBytes = ModbusManagerUtils.binaryStringToByteArrayFront(String.valueOf(fullBinaryChars));
System.arraycopy(modifiedBytes, 0, data, 0, modifiedBytes.length);
}
return result;
}
/**
* 从字节数组写入字节或者位或者字符 根据数据类型
*
* @param data 源数据字节数组
* @param dataStartRegisterAddrDec 指令起始寄存器地址 10进制
* @param readStartRegisterAddrDec 参数起始寄存器地址 10进制
* @param offsetStartIndex 参数起始下标
* @param offsetNum 参数偏移数量
* @param offsetUnits 参数偏移单位
* @param dataType 参数类型
* @param dataTerminalType 数据终端类型
* @return 包含原始值和处理值的对象
* @throws CodeException
*/
public static UniversalReadAndWriteReturn writeModbusByteArrayParamValue(
byte[] data,
int dataStartRegisterAddrDec,
int readStartRegisterAddrDec,
int offsetStartIndex,
int offsetNum,
DevConst.OffsetUnits offsetUnits,
DevConst.DataType dataType,
String dataTerminalType,
Object value,
double conversionFactor,
int accuracy
) throws CodeException {
// 将寄存器地址转换为字节索引(1个寄存器=2字节)
int dataStartByteIndex = dataStartRegisterAddrDec * RegisterAddressByteLength;
int readStartByteIndex = readStartRegisterAddrDec * RegisterAddressByteLength;
// 复用字节索引版本的方法
return writeModbusByteArrayParamValueByByteIndex(
data,
dataStartByteIndex,
readStartByteIndex,
offsetStartIndex,
offsetNum,
offsetUnits,
dataType,
dataTerminalType,
value,
conversionFactor,
accuracy
);
}
//endregion
public static void main(String[] args) throws CodeException {
System.out.println("========== 有符号整数处理测试 ==========\n");
System.out.println("此测试用于验证整数是否按有符号方式处理");
System.out.println("关键:当最高位为1时,有符号会得到负数,无符号会得到大的正数\n");
int passCount = 0;
int failCount = 0;
// ========== 测试1: 1字节有符号 - 正数 ==========
System.out.println("【测试1】1字节有符号 - 正数");
byte[] data1 = new byte[]{0x7F}; // 127
UniversalReadAndWriteReturn result1 = readModbusByteArrayParamValue(
data1, 0, 0, 0, 1,
DevConst.OffsetUnits.BYTE,
DevConst.DataType.INTEGER,
"AB", 1.0, 0
);
long value1 = (Long) result1.getOriginalValue();
boolean test1Pass = value1 == 127L;
System.out.println("原始数据(HEX): 0x7F");
System.out.println("读取值: " + value1);
System.out.println("期望值: 127 (有符号和无符号都相同)");
System.out.println("测试结果: " + (test1Pass ? "✅ 通过" : "❌ 失败"));
if (test1Pass) passCount++; else failCount++;
System.out.println();
// ========== 测试2: 1字节有符号 - 负数 ==========
System.out.println("【测试2】1字节有符号 - 最高位为1");
byte[] data2 = new byte[]{(byte) 0xFF}; // -1 (有符号) 或 255 (无符号)
UniversalReadAndWriteReturn result2 = readModbusByteArrayParamValue(
data2, 0, 0, 0, 1,
DevConst.OffsetUnits.BYTE,
DevConst.DataType.INTEGER,
"AB", 1.0, 0
);
long value2 = (Long) result2.getOriginalValue();
boolean test2Pass = value2 == -1L;
System.out.println("原始数据(HEX): 0xFF");
System.out.println("读取值: " + value2);
System.out.println("期望值: -1 (有符号) / 如果是255则说明还在用无符号");
System.out.println("测试结果: " + (test2Pass ? "✅ 通过 (有符号)" : "❌ 失败 (可能是无符号)"));
if (test2Pass) passCount++; else failCount++;
System.out.println();
// ========== 测试3: 1字节有符号 - 其他负数 ==========
System.out.println("【测试3】1字节有符号 - 其他负数");
byte[] data3 = new byte[]{(byte) 0x80}; // -128 (有符号) 或 128 (无符号)
UniversalReadAndWriteReturn result3 = readModbusByteArrayParamValue(
data3, 0, 0, 0, 1,
DevConst.OffsetUnits.BYTE,
DevConst.DataType.INTEGER,
"AB", 1.0, 0
);
long value3 = (Long) result3.getOriginalValue();
boolean test3Pass = value3 == -128L;
System.out.println("原始数据(HEX): 0x80");
System.out.println("读取值: " + value3);
System.out.println("期望值: -128 (有符号) / 如果是128则说明还在用无符号");
System.out.println("测试结果: " + (test3Pass ? "✅ 通过 (有符号)" : "❌ 失败 (可能是无符号)"));
if (test3Pass) passCount++; else failCount++;
System.out.println();
// ========== 测试4: 2字节有符号 - 正数 ==========
System.out.println("【测试4】2字节有符号 - 正数");
byte[] data4 = new byte[]{0x7F, (byte) 0xFF}; // 32767
UniversalReadAndWriteReturn result4 = readModbusByteArrayParamValue(
data4, 0, 0, 0, 2,
DevConst.OffsetUnits.BYTE,
DevConst.DataType.INTEGER,
"AB", 1.0, 0
);
long value4 = (Long) result4.getOriginalValue();
boolean test4Pass = value4 == 32767L;
System.out.println("原始数据(HEX): 0x7FFF");
System.out.println("读取值: " + value4);
System.out.println("期望值: 32767 (有符号和无符号都相同)");
System.out.println("测试结果: " + (test4Pass ? "✅ 通过" : "❌ 失败"));
if (test4Pass) passCount++; else failCount++;
System.out.println();
// ========== 测试5: 2字节有符号 - 负数 ==========
System.out.println("【测试5】2字节有符号 - 最高位为1");
byte[] data5 = new byte[]{(byte) 0xFF, (byte) 0xFF}; // -1 (有符号) 或 65535 (无符号)
UniversalReadAndWriteReturn result5 = readModbusByteArrayParamValue(
data5, 0, 0, 0, 2,
DevConst.OffsetUnits.BYTE,
DevConst.DataType.INTEGER,
"AB", 1.0, 0
);
long value5 = (Long) result5.getOriginalValue();
boolean test5Pass = value5 == -1L;
System.out.println("原始数据(HEX): 0xFFFF");
System.out.println("读取值: " + value5);
System.out.println("期望值: -1 (有符号) / 如果是65535则说明还在用无符号");
System.out.println("测试结果: " + (test5Pass ? "✅ 通过 (有符号)" : "❌ 失败 (可能是无符号)"));
if (test5Pass) passCount++; else failCount++;
System.out.println();
// ========== 测试6: 2字节有符号 - 其他负数 ==========
System.out.println("【测试6】2字节有符号 - 其他负数");
byte[] data6 = new byte[]{(byte) 0x80, 0x00}; // -32768 (有符号) 或 32768 (无符号)
UniversalReadAndWriteReturn result6 = readModbusByteArrayParamValue(
data6, 0, 0, 0, 2,
DevConst.OffsetUnits.BYTE,
DevConst.DataType.INTEGER,
"AB", 1.0, 0
);
long value6 = (Long) result6.getOriginalValue();
boolean test6Pass = value6 == -32768L;
System.out.println("原始数据(HEX): 0x8000");
System.out.println("读取值: " + value6);
System.out.println("期望值: -32768 (有符号) / 如果是32768则说明还在用无符号");
System.out.println("测试结果: " + (test6Pass ? "✅ 通过 (有符号)" : "❌ 失败 (可能是无符号)"));
if (test6Pass) passCount++; else failCount++;
System.out.println();
// ========== 测试7: 4字节有符号 - 正数 ==========
System.out.println("【测试7】4字节有符号 - 正数");
byte[] data7 = new byte[]{0x7F, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}; // 2147483647
UniversalReadAndWriteReturn result7 = readModbusByteArrayParamValue(
data7, 0, 0, 0, 4,
DevConst.OffsetUnits.BYTE,
DevConst.DataType.INTEGER,
"AB CD", 1.0, 0
);
long value7 = (Long) result7.getOriginalValue();
boolean test7Pass = value7 == 2147483647L;
System.out.println("原始数据(HEX): 0x7FFFFFFF");
System.out.println("读取值: " + value7);
System.out.println("期望值: 2147483647 (有符号和无符号都相同)");
System.out.println("测试结果: " + (test7Pass ? "✅ 通过" : "❌ 失败"));
if (test7Pass) passCount++; else failCount++;
System.out.println();
// ========== 测试8: 4字节有符号 - 负数 ==========
System.out.println("【测试8】4字节有符号 - 最高位为1");
byte[] data8 = new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}; // -1 (有符号) 或 4294967295 (无符号)
UniversalReadAndWriteReturn result8 = readModbusByteArrayParamValue(
data8, 0, 0, 0, 4,
DevConst.OffsetUnits.BYTE,
DevConst.DataType.INTEGER,
"AB CD", 1.0, 0
);
long value8 = (Long) result8.getOriginalValue();
boolean test8Pass = value8 == -1L;
System.out.println("原始数据(HEX): 0xFFFFFFFF");
System.out.println("读取值: " + value8);
System.out.println("期望值: -1 (有符号) / 如果是4294967295则说明还在用无符号");
System.out.println("测试结果: " + (test8Pass ? "✅ 通过 (有符号)" : "❌ 失败 (可能是无符号)"));
if (test8Pass) passCount++; else failCount++;
System.out.println();
// ========== 测试9: 4字节有符号 - 其他负数 ==========
System.out.println("【测试9】4字节有符号 - 其他负数");
byte[] data9 = new byte[]{(byte) 0x80, 0x00, 0x00, 0x00}; // -2147483648 (有符号) 或 2147483648 (无符号)
UniversalReadAndWriteReturn result9 = readModbusByteArrayParamValue(
data9, 0, 0, 0, 4,
DevConst.OffsetUnits.BYTE,
DevConst.DataType.INTEGER,
"AB CD", 1.0, 0
);
long value9 = (Long) result9.getOriginalValue();
boolean test9Pass = value9 == -2147483648L;
System.out.println("原始数据(HEX): 0x80000000");
System.out.println("读取值: " + value9);
System.out.println("期望值: -2147483648 (有符号) / 如果是2147483648则说明还在用无符号");
System.out.println("测试结果: " + (test9Pass ? "✅ 通过 (有符号)" : "❌ 失败 (可能是无符号)"));
if (test9Pass) passCount++; else failCount++;
System.out.println();
// ========== 测试10: 8字节有符号 - 正数 ==========
System.out.println("【测试10】8字节有符号 - 正数");
byte[] data10 = new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, (byte) 0xE8}; // 1000
UniversalReadAndWriteReturn result10 = readModbusByteArrayParamValue(
data10, 0, 0, 0, 8,
DevConst.OffsetUnits.BYTE,
DevConst.DataType.INTEGER,
"AB CD EF GH", 1.0, 0
);
long value10 = (Long) result10.getOriginalValue();
boolean test10Pass = value10 == 1000L;
System.out.println("原始数据(HEX): 0x00000000000003E8");
System.out.println("读取值: " + value10);
System.out.println("期望值: 1000");
System.out.println("测试结果: " + (test10Pass ? "✅ 通过" : "❌ 失败"));
if (test10Pass) passCount++; else failCount++;
System.out.println();
// ========== 测试11: 8字节有符号 - 负数 ==========
System.out.println("【测试11】8字节有符号 - 最高位为1");
byte[] data11 = new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF}; // -1
UniversalReadAndWriteReturn result11 = readModbusByteArrayParamValue(
data11, 0, 0, 0, 8,
DevConst.OffsetUnits.BYTE,
DevConst.DataType.INTEGER,
"AB CD EF GH", 1.0, 0
);
long value11 = (Long) result11.getOriginalValue();
boolean test11Pass = value11 == -1L;
System.out.println("原始数据(HEX): 0xFFFFFFFFFFFFFFFF");
System.out.println("读取值: " + value11);
System.out.println("期望值: -1 (有符号)");
System.out.println("测试结果: " + (test11Pass ? "✅ 通过 (有符号)" : "❌ 失败"));
if (test11Pass) passCount++; else failCount++;
System.out.println();
// ========== 测试12: 写入后回读验证 - 负数 ==========
System.out.println("【测试12】写入负数后回读验证");
byte[] data12 = new byte[4];
writeModbusByteArrayParamValue(
data12, 0, 0, 0, 4,
DevConst.OffsetUnits.BYTE,
DevConst.DataType.INTEGER,
"AB CD",
-100, // 写入负数
1.0, 0
);
UniversalReadAndWriteReturn result12 = readModbusByteArrayParamValue(
data12, 0, 0, 0, 4,
DevConst.OffsetUnits.BYTE,
DevConst.DataType.INTEGER,
"AB CD", 1.0, 0
);
long value12 = (Long) result12.getOriginalValue();
boolean test12Pass = value12 == -100L;
System.out.println("写入值: -100");
System.out.println("回读值: " + value12);
System.out.println("原始数据(HEX): " + HexUtil.encodeHexStr(data12).toUpperCase());
System.out.println("期望值: -100");
System.out.println("测试结果: " + (test12Pass ? "✅ 通过" : "❌ 失败"));
if (test12Pass) passCount++; else failCount++;
System.out.println();
// ========== 测试总结 ==========
System.out.println("========================================");
System.out.println(" 测试总结");
System.out.println("========================================");
System.out.println("总测试数: " + (passCount + failCount));
System.out.println("✅ 通过: " + passCount);
System.out.println("❌ 失败: " + failCount);
System.out.println("通过率: " + String.format("%.1f%%", (passCount * 100.0 / (passCount + failCount))));
System.out.println("========================================");
System.out.println();
if (failCount == 0) {
System.out.println("🎉 所有测试全部通过!");
System.out.println("✅ 确认:整数处理已改为有符号方式");
} else {
System.out.println("⚠️ 有测试失败!");
if (failCount >= 6) {
System.out.println("❌ 可能仍在使用无符号处理(失败的测试主要集中在负数检测)");
} else {
System.out.println("⚠️ 部分场景有问题,请检查具体失败的测试");
}
}
System.out.println();
}
/**
* 去除字符串末尾的空字符(\0)和空白字符
*
* @param originalValue 原始字符串
* @return 去除末尾空字符和空白字符后的字符串
*/
public static String trimTrailingNullAndWhitespace(String originalValue) {
if (originalValue == null || originalValue.isEmpty()) {
return originalValue;
}
int endIndex = originalValue.length();
while (endIndex > 0 && (originalValue.charAt(endIndex - 1) == '\0' || Character.isWhitespace(originalValue.charAt(endIndex - 1)))) {
endIndex--;
}
return originalValue.substring(0, endIndex);
}
}
package com.tigeriot.deviceduplexcodingservice.duplexcoding.tcp.modbus.duplex.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.tigeriot.deviceduplexcodingservice.api.productmanager.devDefineParam.DevDefineParamBring;
import com.tigeriot.deviceduplexcodingservice.api.tcp.modbus.modbusParam.service.ModbusParamBring;
import com.tigeriot.globalcommonservice.model.productmanagerservice.iot.rundev.DevConst;
import com.tigeriot.globalcommonservice.model.productmanagerservice.modbusManager.utils.ModbusManagerUtils;
import com.tigeriot.productmanagerserviceapiclient.model.modbusManager.modbusParam.entity.ModbusParam;
import com.tigeriot.productmanagerserviceapiclient.model.modbusManager.modbusParamBindTemplateRelation.entity.ModbusParamBindTemplateRelation;
import com.tigeriot.productmanagerserviceapiclient.model.productRegister.devDefineParam.entity.DevDefineParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@Data
public class ModbusParamCompleteTemplate extends ModbusParamBindTemplateRelation implements ModbusParamBring, DevDefineParamBring, Comparable<ModbusParamCompleteTemplate> {
@Schema(description = "设备定义参数ID")
private String devDefineParamId;
@Schema(description = "参数名称")
private String paramName;
@Schema(description = "设备参数唯一编码 对应实体字段名参数")
private String devParamUniqueCode;
@Schema(description = "读取寄存器终止地址 读取不到的位置 必须小于它才能够读取")
@JsonIgnore
private Integer readRegisterFinalAddressDec;
@Schema(description = "读取寄存器起始地址 已经计算过偏移量的")
@JsonIgnore
private Integer startRegisterOffsetAddressDec;
@Override
public void bringModbusParam(ModbusParam modbusParam) {
this.devDefineParamId = modbusParam.getDevDefineParamId();
}
@Override
public String provideModbusParamId() {
return this.getModbusParamId();
}
@Override
public void bringDevDefineParam(DevDefineParam devDefineParam) {
this.paramName = devDefineParam.getParamName();
this.devParamUniqueCode = devDefineParam.getDevParamUniqueCode();
}
@Override
public String provideDevDefineParamId() {
return this.devDefineParamId;
}
/**
* 获取改参数所读取最终寄存器地址 读取不到的开始 10进制
*
* @return
*/
public Integer getReadRegisterFinalAddressDec() {
if (this.readRegisterFinalAddressDec != null) {
return this.readRegisterFinalAddressDec;
}
Integer registerAddressDec = this.getRegisterAddressDec();
//这是计算最终寄存器地址 注意:计算起始地址是属于自己值范围 计算最终地址是自己所在值范围寄存器地址+1
this.readRegisterFinalAddressDec = ModbusManagerUtils.offsetDec(registerAddressDec, DevConst.OffsetUnits.create(this.getOffsetUnit()), this.getOffsetNum() + this.getOffsetStartIndex());
return this.readRegisterFinalAddressDec;
}
/**
* 获取改参数所读取起始寄存器地址 读取不到的开始 10进制
*
* @return
*/
public Integer getStartRegisterOffsetAddressDec() {
if (this.startRegisterOffsetAddressDec != null) {
return this.startRegisterOffsetAddressDec;
}
Integer registerAddressDec = this.getRegisterAddressDec();
//这是计算起始寄存器地址 注意:计算起始地址是属于自己值范围 计算最终地址是自己所在值范围寄存器地址+1
this.startRegisterOffsetAddressDec = ModbusManagerUtils.offsetDec(registerAddressDec, DevConst.OffsetUnits.create(this.getOffsetUnit()), this.getOffsetStartIndex());
return this.startRegisterOffsetAddressDec;
}
@Override
public int compareTo(ModbusParamCompleteTemplate o2) {
// 统一转换为位单位进行比较,确保精确性
int thisStartPositionInBits = calculateStartPositionInBits(this);
int o2StartPositionInBits = calculateStartPositionInBits(o2);
int compare = Integer.compare(thisStartPositionInBits, o2StartPositionInBits);
if (compare == 0) {
// 如果开始位置相同,则比较寄存器地址
return this.getRegisterAddressDec().compareTo(o2.getRegisterAddressDec());
} else {
return compare;
}
}
/**
* 计算参数在总体地址空间中的起始位置(以位为单位)
*
* @param param 参数对象
* @return 起始位置(位单位)
*/
private int calculateStartPositionInBits(ModbusParamCompleteTemplate param) {
int registerAddress = param.getRegisterAddressDec();
int offsetStartIndex = param.getOffsetStartIndex();
String offsetUnit = param.getOffsetUnit();
if (DevConst.OffsetUnits.BYTE.value.equals(offsetUnit)) {
// 字节偏移:寄存器地址转字节 + 字节偏移,再转为位
int positionInBytes = registerAddress * ModbusManagerUtils.RegisterAddressByteLength + offsetStartIndex;
return positionInBytes * ModbusManagerUtils.ByteToBitLength;
} else {
// 位偏移:寄存器地址转位 + 位偏移
return registerAddress * ModbusManagerUtils.RegisterAddressBitLength + offsetStartIndex;
}
}
/**
* 判断两个参数地址是否连续 要保证排过序之后调用这个方法
*
* @param o2 是否与该对象连续 若冲突则跳过O2
* |00 00 |00 00 00 00 00
* @return true表示连续,false表示不连续
*/
public boolean continuous(ModbusParamCompleteTemplate o2) {
// 统一转换为位单位进行计算
int thisEndPositionInBits = calculateEndPositionInBits(this);
int o2StartPositionInBits = calculateStartPositionInBits(o2);
// 判断当前参数的结束位置是否等于下一个参数的开始位置
return thisEndPositionInBits == o2StartPositionInBits;
}
/**
* 计算参数在总体地址空间中的结束位置(以位为单位)
*
* @param param 参数对象
* @return 结束位置(位单位)
*/
private int calculateEndPositionInBits(ModbusParamCompleteTemplate param) {
int startPosition = calculateStartPositionInBits(param);
int offsetNum = param.getOffsetNum();
String offsetUnit = param.getOffsetUnit();
if (DevConst.OffsetUnits.BYTE.value.equals(offsetUnit)) {
// 字节偏移:偏移数量转为位
return startPosition + (offsetNum * ModbusManagerUtils.ByteToBitLength);
} else {
// 位偏移:直接加上位数量
return startPosition + offsetNum;
}
}
}
/**
* 获取写入消息 肯定是变化过后的
*
* @param updateData
* @return
*/
public TigerIotMessage getWriteMessage(RunDevModbusCompleteTemplate runDevModbusCompleteTemplate, Map<String, Object> updateData) throws CodeException {
ModbusParamCompleteTemplate first = params.getFirst();
ModbusParamCompleteTemplate last = params.getLast();
//起始偏移地址
Integer startRegisterOffsetAddressDec = first.getStartRegisterOffsetAddressDec();
Integer lastRegisterAddress = last.getReadRegisterFinalAddressDec();
//计算出字节数组长度
int phaseDifferenceRegister = lastRegisterAddress - startRegisterOffsetAddressDec;
if (phaseDifferenceRegister == 0) {
phaseDifferenceRegister = 1;
}
int byteLength = (phaseDifferenceRegister) * ModbusManagerUtils.RegisterAddressByteLength;
byte[] data = new byte[byteLength];
for (ModbusParamCompleteTemplate param : params) {
//写入
try {
ModbusManagerUtils.writeModbusByteArrayParamValue(
data,
startRegisterOffsetAddressDec,
param.getRegisterAddressDec(),
param.getOffsetStartIndex(),
param.getOffsetNum(),
DevConst.OffsetUnits.create(param.getOffsetUnit()),
DevConst.DataType.create(param.getDataType()),
param.getDataTerminalType(),
updateData.get(param.getDevParamUniqueCode()),
param.getConversionFactor(),
param.getAccuracy()
);
} catch (CodeException e) {
log.error(StrUtil.format("写入参数不合法 参数名称:{} 参数key:{}", param.getParamName(), param.getDevParamUniqueCode()), e);
throw e;
}
}
String command;
if (data.length == 2) {
command = ModbusManagerUtils.generateSingleWriteRegisterCommand(runDevModbusCompleteTemplate.getModbusAddress().byteValue(), startRegisterOffsetAddressDec, data[0], data[1]);
} else if (data.length > 2) {
command = ModbusManagerUtils.generateMultiWriteRegisterCommand(runDevModbusCompleteTemplate.getModbusAddress().byteValue(), startRegisterOffsetAddressDec, data);
} else {
throw new CodeException(HttpStatus.BAD_REQUEST.value(), "写入数据长度不合法 数据字节长度必须大于等于2");
}
return TigerIotMessage.send(runDevModbusCompleteTemplate.getImei(), DevConst.MessageType.write, command, DevConst.ProtocolType.MODBUS_RTU, DevConst.TransportProtocol.TCP);
}
/**
* 根据寄存器地址以及字节和位连续来分组区分区间
* 将相邻的Modbus参数放在一组,支持字节和位级别的连续性判断
*
* @param modbusParam 原始Modbus参数列表
* @param updateField 更新字段映射,用于日志输出
* @return 按连续性分组的参数列表
*/
@Override
public List<RegisterContinuousIntervalGrouping> groupByContinuouslyRegisterAddress(List<ModbusParamCompleteTemplate> modbusParam, Map<String, Object> updateField) {
// 1. 过滤有效参数:只保留开启命令同步的读写参数
List<ModbusParamCompleteTemplate> validParams = modbusParam.stream()
.filter(param -> {
try {
isEffectiveModbusParamCompleteTemplate(param);
// 必须开启同步且为读写参数
return param.isEnableCommandSync() && param.getType() == DevConst.ParamType.READ_WRITE.value;
} catch (CodeException e) {
log.trace("ModbusRTU解析 过滤了不合法的参数: {}", param.getDevParamUniqueCode());
return false;
}
})
.sorted(ModbusParamCompleteTemplate::compareTo)
.toList();
if (validParams.isEmpty()) {
log.debug("ModbusRTU解析 精确过滤校验 没有需要同步的参数");
return new ArrayList<>();
}
// 2. 按连续性进行分组
List<RegisterContinuousIntervalGrouping> groupList = new ArrayList<>();
RegisterContinuousIntervalGrouping currentGroup = new RegisterContinuousIntervalGrouping();
currentGroup.add(validParams.getFirst()); // 添加第一个参数
for (int i = 1; i < validParams.size(); i++) {
ModbusParamCompleteTemplate currentParam = validParams.get(i);
ModbusParamCompleteTemplate lastParamInGroup = currentGroup.getParams().getLast();
// 判断当前参数是否与分组中最后一个参数连续
if (lastParamInGroup.continuous(currentParam)) {
// 连续则添加到当前分组
currentGroup.add(currentParam);
} else {
// 不连续则保存当前分组,创建新分组
groupList.add(currentGroup);
currentGroup = new RegisterContinuousIntervalGrouping();
currentGroup.add(currentParam);
}
}
// 添加最后一个分组(如果存在)
if (!currentGroup.getParams().isEmpty()) {
groupList.add(currentGroup);
}
// 3. 输出分组信息用于调试
logGroupingResults(groupList, updateField);
return groupList;
}
/**
* 输出分组结果日志
*
* @param groupList 分组列表
* @param updateField 更新字段映射
*/
private void logGroupingResults(List<RegisterContinuousIntervalGrouping> groupList, Map<String, Object> updateField) {
for (int i = 0; i < groupList.size(); i++) {
int groupNumber = i + 1;
log.debug("ModbusRTU解析 ============= 分组 同步参数 第{}组 开始 =============", groupNumber);
RegisterContinuousIntervalGrouping group = groupList.get(i);
List<ModbusParamCompleteTemplate> params = group.getParams();
for (ModbusParamCompleteTemplate param : params) {
log.debug("ModbusRTU解析 参数名:{}, 参数key:{}, 参数值:{}",
param.getParamName(),
param.getDevParamUniqueCode(),
updateField.get(param.getDevParamUniqueCode()));
}
log.debug("ModbusRTU解析 ============= 分组 同步参数 第{}组 结束 =============", groupNumber);
}
}