feat: modbus-tcp写功能及测试

This commit is contained in:
2026-03-03 16:17:58 +08:00
parent 30a5f68905
commit 79ce8f976b
6 changed files with 427 additions and 181 deletions

View File

@@ -46,80 +46,6 @@ public class AttributeBO implements Serializable {
*/
private String value;
/**
* 类型, value type, 用于确定value的真实类型
*/
// private AttributeTypeFlagEnum type;
/**
* 根据类型转换数据
*
* @param clazz T Class
* @param <T> T
* @return T
*/
@SuppressWarnings("unchecked")
// public <T> T getValue(Class<T> clazz) {
// if (Objects.isNull(type)) {
// throw new CommonException("Unsupported attribute type of " + type);
// }
// if (StringUtils.isEmpty(value)) {
// throw new CommonException("Attribute value is empty");
// }
//
// final String message = "Attribute type is: {}, can't be cast to class: {}";
// return switch (type) {
// case STRING -> {
// if (!clazz.equals(String.class)) {
// throw new CommonException(message, type.getCode(), clazz.getName());
// }
// yield (T) value;
// }
// case BYTE -> {
// if (!clazz.equals(Byte.class)) {
// throw new CommonException(message, type.getCode(), clazz.getName());
// }
// yield (T) Byte.valueOf(value);
// }
// case SHORT -> {
// if (!clazz.equals(Short.class)) {
// throw new CommonException(message, type.getCode(), clazz.getName());
// }
// yield (T) Short.valueOf(value);
// }
// case INT -> {
// if (!clazz.equals(Integer.class)) {
// throw new CommonException(message, type.getCode(), clazz.getName());
// }
// yield (T) Integer.valueOf(value);
// }
// case LONG -> {
// if (!clazz.equals(Long.class)) {
// throw new CommonException(message, type.getCode(), clazz.getName());
// }
// yield (T) Long.valueOf(value);
// }
// case FLOAT -> {
// if (!clazz.equals(Float.class)) {
// throw new CommonException(message, type.getCode(), clazz.getName());
// }
// yield (T) Float.valueOf(value);
// }
// case DOUBLE -> {
// if (!clazz.equals(Double.class)) {
// throw new CommonException(message, type.getCode(), clazz.getName());
// }
// yield (T) Double.valueOf(value);
// }
// case BOOLEAN -> {
// if (!clazz.equals(Boolean.class)) {
// throw new CommonException(message, type.getCode(), clazz.getName());
// }
// yield (T) Boolean.valueOf(value);
// }
// };
// }
/**
* 将私有变量value转换为指定的基础数据类型
* @param clazz 目标类型仅支持String/Byte/Short/Integer/Long/Double/Float/Boolean

View File

@@ -0,0 +1,90 @@
package org.nl.iot.core.driver.entity;
import lombok.*;
import org.nl.common.exception.CommonException;
import java.io.Serial;
import java.io.Serializable;
/**
*
* @author: lyd
* @date: 2026/3/3
*/
@Getter
@Setter
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class WValue implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 值, string, 需要根据type确定真实的数据类型
*/
private String value;
/**
* 类型, value type, 用于确定value的真实类型
* <p>
* 同位号数据类型一致
*/
private String type;
/**
* 将私有变量value转换为指定的基础数据类型
* @param clazz 目标类型仅支持String/Byte/Short/Integer/Long/Double/Float/Boolean
* @return 转换后的目标类型实例
* @throws CommonException 类型不支持或转换失败时抛出
*/
public <T> T getValueByClass(Class<T> clazz) {
// 先校验原始值是否为null根据业务需求也可调整为允许null转换
if (value == null) {
throw new CommonException("原始值value为null无法转换为" + clazz.getSimpleName());
}
// 去除字符串首尾空格(可选,根据业务需求决定是否保留)
String trimmedValue = value.trim();
if (trimmedValue.isEmpty()) {
throw new CommonException("原始值value为空字符串无法转换为" + clazz.getSimpleName());
}
// 按类型分支处理转换逻辑
try {
if (clazz == String.class) {
return clazz.cast(value); // String类型直接返回原字符串
} else if (clazz == Byte.class || clazz == byte.class) {
return clazz.cast(Byte.parseByte(trimmedValue));
} else if (clazz == Short.class || clazz == short.class) {
return clazz.cast(Short.parseShort(trimmedValue));
} else if (clazz == Integer.class || clazz == int.class) {
return clazz.cast(Integer.parseInt(trimmedValue));
} else if (clazz == Long.class || clazz == long.class) {
return clazz.cast(Long.parseLong(trimmedValue));
} else if (clazz == Double.class || clazz == double.class) {
return clazz.cast(Double.parseDouble(trimmedValue));
} else if (clazz == Float.class || clazz == float.class) {
return clazz.cast(Float.parseFloat(trimmedValue));
} else if (clazz == Boolean.class || clazz == boolean.class) {
// 严格匹配布尔值:仅"true"/"false"(忽略大小写),避免"1"/"0"误判
if ("true".equalsIgnoreCase(trimmedValue)) {
return clazz.cast(Boolean.TRUE);
} else if ("false".equalsIgnoreCase(trimmedValue)) {
return clazz.cast(Boolean.FALSE);
} else {
throw new NumberFormatException("布尔值仅支持\"true\"/\"false\"(忽略大小写),当前值:" + trimmedValue);
}
} else {
// 不支持的类型
throw new CommonException("不支持的转换类型:" + clazz.getSimpleName() +
"仅支持String/Byte/Short/Integer/Long/Double/Float/Boolean");
}
} catch (NumberFormatException e) {
// 转换失败(如字符串非数字、布尔值格式错误)
throw new CommonException("转换失败:无法将值\"" + value + "\"转换为" + clazz.getSimpleName(), e);
}
}
}

View File

@@ -5,15 +5,14 @@ import lombok.extern.slf4j.Slf4j;
import org.nl.common.exception.CommonException;
import org.nl.iot.core.driver.bo.AttributeBO;
import org.nl.iot.core.driver.entity.RValue;
import org.nl.iot.core.driver.enums.PointTypeFlagEnum;
import org.nl.iot.core.driver.entity.WValue;
import org.nl.iot.core.driver.protocol.modbustcp.com.serotonin.modbus4j.ModbusFactory;
import org.nl.iot.core.driver.protocol.modbustcp.com.serotonin.modbus4j.ModbusMaster;
import org.nl.iot.core.driver.protocol.modbustcp.com.serotonin.modbus4j.code.DataType;
import org.nl.iot.core.driver.protocol.modbustcp.com.serotonin.modbus4j.exception.ErrorResponseException;
import org.nl.iot.core.driver.protocol.modbustcp.com.serotonin.modbus4j.exception.ModbusInitException;
import org.nl.iot.core.driver.protocol.modbustcp.com.serotonin.modbus4j.exception.ModbusTransportException;
import org.nl.iot.core.driver.protocol.modbustcp.com.serotonin.modbus4j.ip.IpParameters;
import org.nl.iot.core.driver.protocol.modbustcp.com.serotonin.modbus4j.locator.BaseLocator;
import org.nl.iot.core.driver.protocol.modbustcp.com.serotonin.modbus4j.msg.WriteCoilResponse;
import org.nl.iot.core.driver.protocol.modbustcp.util.ModBusTcpUtils;
import org.nl.iot.core.driver.service.DriverCustomService;
import org.nl.iot.modular.iot.entity.IotConfig;
import org.nl.iot.modular.iot.entity.IotConnect;
@@ -55,6 +54,18 @@ public class ModBusProtocolDriverImpl implements DriverCustomService {
return new RValue(config, connect, readValue(getConnector(connect.getId().toString(), driverConfig), pointConfig));
}
@Override
public Boolean write(Map<String, AttributeBO> driverConfig, Map<String, AttributeBO> pointConfig, IotConnect connect, IotConfig config, WValue wValue) {
/*
* 写入设备点位数据
*
* 提示: 此处逻辑仅供参考, 请务必结合实际应用场景进行修改。
* 通过 Modbus 连接器将指定值写入设备的点位, 并返回写入结果。
*/
ModbusMaster modbusMaster = getConnector(connect.getId().toString(), driverConfig);
return writeValue(modbusMaster, pointConfig, wValue);
}
/**
* 获取 Modbus Master 连接器
* <p>
@@ -106,27 +117,27 @@ public class ModBusProtocolDriverImpl implements DriverCustomService {
String type = pointConfig.get("data_type").getValueByClass(String.class);
int slaveId = pointConfig.get("slaveId").getValueByClass(Integer.class);
int offset = pointConfig.get("offset").getValueByClass(Integer.class);
int functionCode = getFunctionCode(offset);
int functionCode = ModBusTcpUtils.getFunctionCode(offset);
// 计算实际的寄存器地址Modbus协议地址从0开始
int actualAddress = getActualAddress(offset, functionCode);
int actualAddress = ModBusTcpUtils.getActualAddress(offset, functionCode);
switch (functionCode) {
case 1:
BaseLocator<Boolean> coilLocator = BaseLocator.coilStatus(slaveId, actualAddress);
Boolean coilValue = getMasterValue(modbusMaster, coilLocator);
Boolean coilValue = ModBusTcpUtils.getMasterValue(modbusMaster, coilLocator);
return String.valueOf(coilValue);
case 2:
BaseLocator<Boolean> inputLocator = BaseLocator.inputStatus(slaveId, actualAddress);
Boolean inputStatusValue = getMasterValue(modbusMaster, inputLocator);
Boolean inputStatusValue = ModBusTcpUtils.getMasterValue(modbusMaster, inputLocator);
return String.valueOf(inputStatusValue);
case 3:
BaseLocator<Number> holdingLocator = BaseLocator.holdingRegister(slaveId, actualAddress, getValueType(type));
Number holdingValue = getMasterValue(modbusMaster, holdingLocator);
BaseLocator<Number> holdingLocator = BaseLocator.holdingRegister(slaveId, actualAddress, ModBusTcpUtils.getValueType(type));
Number holdingValue = ModBusTcpUtils.getMasterValue(modbusMaster, holdingLocator);
return String.valueOf(holdingValue);
case 4:
BaseLocator<Number> inputRegister = BaseLocator.inputRegister(slaveId, actualAddress, getValueType(type));
Number inputRegisterValue = getMasterValue(modbusMaster, inputRegister);
BaseLocator<Number> inputRegister = BaseLocator.inputRegister(slaveId, actualAddress, ModBusTcpUtils.getValueType(type));
Number inputRegisterValue = ModBusTcpUtils.getMasterValue(modbusMaster, inputRegister);
return String.valueOf(inputRegisterValue);
default:
return "0";
@@ -134,109 +145,41 @@ public class ModBusProtocolDriverImpl implements DriverCustomService {
}
/**
* ModbusMaster 连接器中读取指定点位的数据
* Modbus 设备写入点位值
* <p>
* 该方法通过给定的 {@link BaseLocator} 从 ModbusMaster 连接器中读取数据
* 如果读取过程中发生 {@link ModbusTransportException} 或 {@link ErrorResponseException} 异常,
* 将记录错误日志并抛出 {@link CommonException} 异常。
* 根据点位配置中的功能码(functionCode)和偏移量(offset), 将指定值写入 Modbus 设备的相应点位
* 支持的功能码包括:
* - 1: 写入线圈状态(Coil Status)
* - 3: 写入保持寄存器(Holding Register)
* <p>
* 对于功能码 1, 写入布尔值到线圈状态, 并返回写入结果。
* 对于功能码 3, 写入数值到保持寄存器, 并返回写入成功状态。
* 其他功能码暂不支持, 返回 false。
*
* @param modbusMaster ModbusMaster 连接器, 用于与设备通信
* @param locator 点位定位器, 包含从站ID, 功能码, 偏移量等信息
* @param <T> 返回值类型, 根据点位的数据类型确定
* @return T 返回读取到的点位数据
* @throws CommonException 如果读取过程中发生异常, 抛出此异常
* @param pointConfig 点位配置, 包含从站ID(slaveId), 功能码(functionCode), 偏移量(offset)等信息
* @param wValue 待写入的值, 包含值类型和具体数值
* @return boolean 返回写入结果, true 表示写入成功, false 表示写入失败或不支持的功能码
*/
private <T> T getMasterValue(ModbusMaster modbusMaster, BaseLocator<T> locator) {
try {
return modbusMaster.getValue(locator);
} catch (ModbusTransportException | ErrorResponseException e) {
log.error("Read modbus master value error: {}", e.getMessage(), e);
throw new CommonException(e.getMessage());
}
}
private boolean writeValue(ModbusMaster modbusMaster, Map<String, AttributeBO> pointConfig, WValue wValue) {
String type = pointConfig.get("data_type").getValueByClass(String.class);
wValue.setType(type);
int slaveId = pointConfig.get("slaveId").getValueByClass(Integer.class);
int offset = pointConfig.get("offset").getValueByClass(Integer.class);
int functionCode = ModBusTcpUtils.getFunctionCode(offset);
/**
* 获取 Modbus 数据类型
* <p>
* 根据点位值类型(type)返回对应的 Modbus 数据类型。
* - 其他类型: 返回 2 字节有符号整数({@link DataType#TWO_BYTE_INT_SIGNED})
* <p>
* 提示: 该方法可根据实际项目需求进行扩展, 例如支持字节交换, 大端/小端模式等。
*
* @param type 点位值类型, 用于确定 Modbus 数据类型
* @return int 返回对应的 Modbus 数据类型
* @throws CommonException 如果点位值类型不支持, 抛出此异常
*/
private int getValueType(String type) {
switch (type.toLowerCase()) {
case "int32":
return DataType.FOUR_BYTE_INT_SIGNED;
case "uint32":
return DataType.FOUR_BYTE_INT_UNSIGNED;
case "double":
return DataType.EIGHT_BYTE_FLOAT;
case "float32":
return DataType.FOUR_BYTE_FLOAT;
default:
return DataType.TWO_BYTE_INT_SIGNED;
}
}
/**
* 根据Modbus地址偏移量获取对应的功能码
* 注意:不同厂商对地址范围的定义可能不同,这里采用常见的约定:
* - 00001-09999: 功能码01 (线圈 Coil)
* - 10001-19999: 功能码02 (离散输入 Discrete Input)
* - 30001-39999: 功能码04 (输入寄存器 Input Register)
* - 40001-49999: 功能码03 (保持寄存器 Holding Register)
*
* @param offset 地址偏移量1-9999/10001-19999/30001-39999/40001-49999
* @return 对应的功能码1-4
* @throws CommonException 当偏移量不在合法范围时抛出异常
*/
public static int getFunctionCode(int offset) {
int functionCode;
if (offset >= 1 && offset <= 9999) {
functionCode = 1; // 线圈
} else if (offset >= 10001 && offset <= 19999) {
functionCode = 2; // 离散输入
} else if (offset >= 30001 && offset <= 39999) {
functionCode = 4; // 输入寄存器
} else if (offset >= 40001 && offset <= 49999) {
functionCode = 3; // 保持寄存器
} else {
throw new CommonException("无效的偏移量:" + offset);
}
return functionCode;
}
/**
* 根据Modbus地址偏移量和功能码计算实际的寄存器地址
* Modbus协议中寄存器地址从0开始需要减去相应的偏移量
*
* @param offset 地址偏移量1-9999/10001-19999/30001-39999/40001-49999
* @param functionCode 功能码1-4
* @return 实际的寄存器地址从0开始
*/
private int getActualAddress(int offset, int functionCode) {
// 计算实际的寄存器地址Modbus协议地址从0开始
int actualAddress = ModBusTcpUtils.getActualAddress(offset, functionCode);
switch (functionCode) {
case 1:
// 线圈状态1-9999 -> 0-9998
return offset - 1;
case 2:
// 离散输入10001-19999 -> 0-9998
return offset - 10001;
WriteCoilResponse coilResponse = ModBusTcpUtils.setMasterValue(modbusMaster, slaveId, actualAddress, wValue);
return !coilResponse.isException();
case 3:
// 保持寄存器40001-49999 -> 0-9998
return offset - 40001;
case 4:
// 输入寄存器30001-39999 -> 0-9998
return offset - 30001;
BaseLocator<Number> locator = BaseLocator.holdingRegister(slaveId, actualAddress, ModBusTcpUtils.getValueType(type));
ModBusTcpUtils.setMasterValue(modbusMaster, locator, wValue);
return true;
default:
return 0;
return false;
}
}
}

View File

@@ -0,0 +1,214 @@
package org.nl.iot.core.driver.protocol.modbustcp.util;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.formula.functions.T;
import org.nl.common.exception.CommonException;
import org.nl.iot.core.driver.entity.WValue;
import org.nl.iot.core.driver.protocol.modbustcp.com.serotonin.modbus4j.ModbusMaster;
import org.nl.iot.core.driver.protocol.modbustcp.com.serotonin.modbus4j.code.DataType;
import org.nl.iot.core.driver.protocol.modbustcp.com.serotonin.modbus4j.exception.ErrorResponseException;
import org.nl.iot.core.driver.protocol.modbustcp.com.serotonin.modbus4j.exception.ModbusTransportException;
import org.nl.iot.core.driver.protocol.modbustcp.com.serotonin.modbus4j.locator.BaseLocator;
import org.nl.iot.core.driver.protocol.modbustcp.com.serotonin.modbus4j.msg.WriteCoilRequest;
import org.nl.iot.core.driver.protocol.modbustcp.com.serotonin.modbus4j.msg.WriteCoilResponse;
/**
* modbus tcp工具类
* @author: lyd
* @date: 2026/3/3
*/
@Slf4j
public class ModBusTcpUtils {
/**
* 获取 Modbus 数据类型
* <p>
* 根据点位值类型(type)返回对应的 Modbus 数据类型。
* - 其他类型: 返回 2 字节有符号整数({@link DataType#TWO_BYTE_INT_SIGNED})
* <p>
* 提示: 该方法可根据实际项目需求进行扩展, 例如支持字节交换, 大端/小端模式等。
*
* @param type 点位值类型, 用于确定 Modbus 数据类型
* @return int 返回对应的 Modbus 数据类型
* @throws CommonException 如果点位值类型不支持, 抛出此异常
*/
public static int getValueType(String type) {
switch (type.toLowerCase()) {
case "int32":
return DataType.FOUR_BYTE_INT_SIGNED;
case "uint32":
return DataType.FOUR_BYTE_INT_UNSIGNED;
case "double":
return DataType.EIGHT_BYTE_FLOAT;
case "float32":
return DataType.FOUR_BYTE_FLOAT;
default:
return DataType.TWO_BYTE_INT_SIGNED;
}
}
/**
* 根据Modbus等场景常用的数值类型字符串获取对应的Java类型Class
* @param type 类型字符串如int32、uint32、double、float32
* @return 对应的Java类型Class如果不支持该类型则返回null
* @param <T> 泛型类型匹配返回的Class类型
*/
@SuppressWarnings("unchecked")
public static <T> Class<T> getJavaValueType(String type) {
// 处理空值避免NullPointerException
if (type == null) {
return null;
}
return switch (type.toLowerCase()) {
case "int32" ->
// 32位有符号整数对应Java的Integer
(Class<T>) Integer.class;
case "uint32" ->
// 32位无符号整数超出Java int范围int最大2^31-1需用Long存储
(Class<T>) Long.class;
case "double" ->
// 64位双精度浮点数对应Java的Double
(Class<T>) Double.class;
case "float32" ->
// 32位单精度浮点数对应Java的Float
(Class<T>) Float.class;
// 可扩展其他常用类型
case "int16" -> (Class<T>) Short.class;
case "uint16" -> (Class<T>) Integer.class;
case "boolean" -> (Class<T>) Boolean.class;
default ->
// 不支持的类型返回null也可根据需求抛出异常
null;
};
}
/**
* 根据Modbus地址偏移量和功能码计算实际的寄存器地址
* Modbus协议中寄存器地址从0开始需要减去相应的偏移量
*
* @param offset 地址偏移量1-9999/10001-19999/30001-39999/40001-49999
* @param functionCode 功能码1-4
* @return 实际的寄存器地址从0开始
*/
public static int getActualAddress(int offset, int functionCode) {
switch (functionCode) {
case 1:
// 线圈状态1-9999 -> 0-9998
return offset - 1;
case 2:
// 离散输入10001-19999 -> 0-9998
return offset - 10001;
case 3:
// 保持寄存器40001-49999 -> 0-9998
return offset - 40001;
case 4:
// 输入寄存器30001-39999 -> 0-9998
return offset - 30001;
default:
return 0;
}
}
/**
* 根据Modbus地址偏移量获取对应的功能码
* 注意:不同厂商对地址范围的定义可能不同,这里采用常见的约定:
* - 00001-09999: 功能码01 (线圈 Coil)
* - 10001-19999: 功能码02 (离散输入 Discrete Input)
* - 30001-39999: 功能码04 (输入寄存器 Input Register)
* - 40001-49999: 功能码03 (保持寄存器 Holding Register)
*
* @param offset 地址偏移量1-9999/10001-19999/30001-39999/40001-49999
* @return 对应的功能码1-4
* @throws CommonException 当偏移量不在合法范围时抛出异常
*/
public static int getFunctionCode(int offset) {
int functionCode;
if (offset >= 1 && offset <= 9999) {
functionCode = 1; // 线圈
} else if (offset >= 10001 && offset <= 19999) {
functionCode = 2; // 离散输入
} else if (offset >= 30001 && offset <= 39999) {
functionCode = 4; // 输入寄存器
} else if (offset >= 40001 && offset <= 49999) {
functionCode = 3; // 保持寄存器
} else {
throw new CommonException("无效的偏移量:" + offset);
}
return functionCode;
}
/**
* 从 ModbusMaster 连接器中读取指定点位的数据
* <p>
* 该方法通过给定的 {@link BaseLocator} 从 ModbusMaster 连接器中读取数据。
* 如果读取过程中发生 {@link ModbusTransportException} 或 {@link ErrorResponseException} 异常,
* 将记录错误日志并抛出 {@link CommonException} 异常。
*
* @param modbusMaster ModbusMaster 连接器, 用于与设备通信
* @param locator 点位定位器, 包含从站ID, 功能码, 偏移量等信息
* @param <T> 返回值类型, 根据点位的数据类型确定
* @return T 返回读取到的点位数据
* @throws CommonException 如果读取过程中发生异常, 抛出此异常
*/
public static <T> T getMasterValue(ModbusMaster modbusMaster, BaseLocator<T> locator) {
try {
return modbusMaster.getValue(locator);
} catch (ModbusTransportException | ErrorResponseException e) {
log.error("Read modbus master value error: {}", e.getMessage(), e);
throw new CommonException(e.getMessage());
}
}
/**
* 向 Modbus 设备写入线圈状态值
* <p>
* 该方法通过 ModbusMaster 连接器向指定从站(slaveId)的线圈(offset)写入布尔值。
* 如果写入过程中发生 {@link ModbusTransportException} 异常, 将记录错误日志并抛出 {@link WritePointException} 异常。
*
* @param modbusMaster ModbusMaster 连接器, 用于与设备通信
* @param slaveId 从站ID, 标识目标设备
* @param offset 线圈偏移量, 标识目标线圈
* @param wValue 待写入的值, 包含布尔值
* @return WriteCoilResponse 返回写入操作的响应结果
* @throws CommonException 如果写入过程中发生异常, 抛出此异常
*/
public static WriteCoilResponse setMasterValue(ModbusMaster modbusMaster, int slaveId, int offset, WValue wValue) {
try {
WriteCoilRequest coilRequest = new WriteCoilRequest(slaveId, offset, wValue.getValueByClass(Boolean.class));
return (WriteCoilResponse) modbusMaster.send(coilRequest);
} catch (ModbusTransportException e) {
log.error("Write modbus master value error: {}", e.getMessage(), e);
throw new CommonException(e.getMessage());
}
}
/**
* 向 Modbus 设备写入指定类型的值
* <p>
* 该方法通过 ModbusMaster 连接器向指定点位写入值。点位信息由 {@link BaseLocator} 定义,
* 写入的值类型由 {@link WValue} 指定。支持写入浮点数类型的数据。
* <p>
* 如果写入过程中发生 {@link ModbusTransportException} 或 {@link ErrorResponseException} 异常,
* 将记录错误日志并抛出 {@link CommonException} 异常。
*
* @param modbusMaster ModbusMaster 连接器, 用于与设备通信
* @param locator 点位定位器, 包含从站ID, 功能码, 偏移量等信息
* @param wValue 待写入的值, 包含值类型和具体数值
* @param <T> 返回值类型, 根据点位的数据类型确定
* @throws CommonException 如果写入过程中发生异常, 抛出此异常
*/
public static <T> void setMasterValue(ModbusMaster modbusMaster, BaseLocator<T> locator, WValue wValue) {
try {
modbusMaster.setValue(locator, wValue.getValueByClass(ModBusTcpUtils.getJavaValueType(wValue.getType())));
} catch (ModbusTransportException | ErrorResponseException e) {
log.error("Write modbus master value error: {}", e.getMessage(), e);
throw new CommonException(e.getMessage());
}
}
}

View File

@@ -2,6 +2,7 @@ package org.nl.iot.core.driver.service;
import org.nl.iot.core.driver.bo.AttributeBO;
import org.nl.iot.core.driver.entity.RValue;
import org.nl.iot.core.driver.entity.WValue;
import org.nl.iot.modular.iot.entity.IotConfig;
import org.nl.iot.modular.iot.entity.IotConnect;
@@ -58,6 +59,6 @@ public interface DriverCustomService {
* @param wValue 待写入的数据, 封装在 {@link WValue} 对象中
* @return 返回写入操作是否成功, 若成功则返回 {@code true}, 否则返回 {@code false} 或抛出异常
*/
// Boolean write(Map<String, AttributeBO> driverConfig, Map<String, AttributeBO> pointConfig, IotConnect device, IotConfig point, WValue wValue);
Boolean write(Map<String, AttributeBO> driverConfig, Map<String, AttributeBO> pointConfig, IotConnect device, IotConfig point, WValue wValue);
}

View File

@@ -79,4 +79,76 @@ public class ApiTest {
e.printStackTrace();
}
}
@Test
public void modbusTestWrite() {
// 构建驱动配置(连接配置)
Map<String, AttributeBO> driverConfig = new HashMap<>();
driverConfig.put("host", AttributeBO.builder().value("192.168.81.251").build());
driverConfig.put("port", AttributeBO.builder().value("502").build());
// 构建点位配置 - 写入保持寄存器功能码3
Map<String, AttributeBO> pointConfig = new HashMap<>();
pointConfig.put("slaveId", AttributeBO.builder().value("1").build());
pointConfig.put("offset", AttributeBO.builder().value("40001").build()); // 功能码3保持寄存器地址0
pointConfig.put("data_type", AttributeBO.builder().value("int16").build());
// 构建连接对象
IotConnect connect = IotConnect.builder()
.id(1)
.code("MODBUS_TCP_001")
.host("192.168.81.251")
.port(502)
.protocol("modbus-tcp")
.enabled(true)
.description("测试Modbus TCP写入连接")
.build();
// 构建配置对象
IotConfig config = IotConfig.builder()
.id(1)
.connectId(1)
.alias("temperature")
.aliasName("温度传感器")
.registerAddress("40001")
.dataType("int16")
.readonly(false)
.enabled(true)
.description("测试温度写入")
.build();
try {
// 先读取当前值
System.out.println("=== Modbus写入测试开始 ===");
RValue beforeValue = modBusProtocolDriver.read(driverConfig, pointConfig, connect, config);
System.out.println("写入前的值: " + beforeValue.getValue());
// 构建写入值对象 - 写入新的温度值例如100
org.nl.iot.core.driver.entity.WValue wValue = new org.nl.iot.core.driver.entity.WValue();
wValue.setValue("100");
wValue.setType("int16");
// 调用write方法进行写入测试
Boolean writeResult = modBusProtocolDriver.write(driverConfig, pointConfig, connect, config, wValue);
System.out.println("写入结果: " + (writeResult ? "成功" : "失败"));
// 等待一小段时间后再次读取,验证写入是否成功
Thread.sleep(500);
RValue afterValue = modBusProtocolDriver.read(driverConfig, pointConfig, connect, config);
System.out.println("写入后的值: " + afterValue.getValue());
// 验证写入是否成功
if (writeResult && "100".equals(afterValue.getValue())) {
System.out.println("✓ 写入验证成功!值已更新为: " + afterValue.getValue());
} else {
System.out.println("✗ 写入验证失败!期望值: 100, 实际值: " + afterValue.getValue());
}
System.out.println("=== 测试完成 ===");
} catch (Exception e) {
System.err.println("测试失败: " + e.getMessage());
e.printStackTrace();
}
}
}