需求是java 整合ModBus,使用RTU协议

引入依赖

当前使用环境是jdk17 + spring boot 3.5.5

     <dependency>
            <groupId>com.ghgande</groupId>
            <artifactId>j2mod</artifactId>
            <version>3.2.1</version>
        </dependency>

添加ModBusConfig

采用条件注解开关

@ConditionalOnProperty(name = "modbus.enable", havingValue = "true")
如果配置开启 则注入该Bean

其他关键信息都是采用配置

串口协议

波特率

停止位

校验位



@Configuration
@ConditionalOnProperty(name = "modbus.enable", havingValue = "true")
public class ModbusConfig {

    // 从配置文件读取串口参数
    @Value("${modbus.port:COM1}")
    private String portName;

    @Value("${modbus.baud-rate:9600}")
    private int baudRate;

    @Value("${modbus.data-bits:8}")
    private int dataBits;

    @Value("${modbus.parity:None}")
    private String parity;

    @Value("${modbus.stop-bits:1}")
    private int stopBits;

    @Value("${modbus.unit-id:1}")
    private int unitId;

    /**
     * 创建Modbus RTU连接(单例模式,自动关闭)
     */
    @Bean(destroyMethod = "close")
    public SerialConnection serialConnection() {
        try {
            SerialParameters params = new SerialParameters();
            params.setPortName(portName);
            params.setBaudRate(baudRate);
            params.setDatabits(dataBits);
            params.setParity(parity);
            params.setStopbits(stopBits);
            params.setEncoding("rtu"); // RTU协议
            params.setEcho(false);

            SerialConnection connection = new SerialConnection(params);
            connection.open(); // 启动时建立连接
            return connection;
        } catch (Exception e) {
            throw new RuntimeException("Modbus连接初始化失败", e);
        }
    }

    /**
     * 注册Modbus服务(注入连接)
     */
    @Bean
    public ModbusService modbusService(SerialConnection connection) {
        return new ModbusService();
    }
}

yml配置

modbus:
  enable: false
  port: COM1          # 串口名称
  baud-rate: 9600     # 波特率(与设备匹配)
  data-bits: 8        # 数据位
  parity: None        # 校验位(None/Even/Odd)
  stop-bits: 1        # 停止位
  unit-id: 1          # 设备地址(从机ID)
  timeout: 3000

读取寄存器数据

具体读取逻辑取决于寄存器上的数据内容和表达内容,对应解析即可



@Service
@ConditionalOnProperty(name = "modbus.enable", havingValue = "true")
@Slf4j
public class ModbusService {

    @Value("${modbus.unit-id:1}")
    private int unitId; // 设备地址

    @Resource
    private SerialConnection connection;

    @Resource
    private WindingShortMapper windingShortMapper;


    /**
     * 读取所有寄存器数据并转换为ModbusData对象
     */
    public WindingShortPO readAllData() {

        WindingShortPO windingShortPO = new WindingShortPO();

        try {
            // 1. 读取时间寄存器(40001-40006,共6个寄存器)
            // 注意:Modbus寄存器地址通常为0-based,40001对应参考地址0
            int[] timeRegisters = readRegisters(0, 6); // 起始地址0,读取6个寄存器

            // 2. 读取highV1-highV16(40007-40022,共16个寄存器)
            int[] highVRegisters = readRegisters(6, 16); // 读取16个

            // 3. 读取lowV1及后续(40023-40038,共16个寄存器)
            int[] lowVRegisters = readRegisters(22, 16); // 读取16个

            // 4. 读取feature及后续(40039-40054,共16个寄存器)
            int[] valueRegisters = readRegisters(38, 16); // 读取16个

            // 5. 读取status及后续(40055-40070,共16个寄存器)
            int[] statusRegisters = readRegisters(54, 16); // 读取16个

            windingShortPO.setCollectTime(parseTime(timeRegisters));
            windingShortPO.setHighV(arrayToCsv(highVRegisters));
            windingShortPO.setLowV(arrayToCsv(lowVRegisters));
            windingShortPO.setFeature(arrayToCsv(valueRegisters));
            windingShortPO.setStatus(arrayToCsv(statusRegisters));

            windingShortMapper.insert(windingShortPO);

        } catch (Exception e) {
            throw new RuntimeException("Modbus数据读取失败", e);
        }

        return windingShortPO;
    }


    /**
     * 通用寄存器读取方法(读取保持寄存器,对应40001+系列)
     * @param startRef 起始参考地址(0-based,40001对应0)
     * @param count 读取数量
     */
    private int[] readRegisters(int startRef, int count) throws Exception {
        log.info("=== Modbus 读取配置 ===");
        log.info("串口路径: {}", connection.getPortName());
        log.info("波特率: {}", connection.getBaudRate());
        log.info("数据位: {}", connection.getNumDataBits());
        log.info("校验位: {}", connection.getParity());
        log.info("停止位: {}", connection.getNumStopBits());
        log.info("设备地址(unitId): {}", unitId);
        log.info("请求参数: 起始地址={}, 读取数量={}", startRef, count);
        log.info("======================");

        ModbusSerialTransaction transaction = new ModbusSerialTransaction(connection);
        ReadMultipleRegistersRequest request = new ReadMultipleRegistersRequest(startRef, count);
        request.setUnitID(unitId);
        transaction.setRequest(request);
        transaction.setRetries(1); // 设置重试次数

        try {
            transaction.execute();
            ReadMultipleRegistersResponse response = (ReadMultipleRegistersResponse) transaction.getResponse();

            if (response != null) {
                log.info("读取成功,返回寄存器数量: {}", response.getWordCount());
                int[] values = new int[count];
                for (int i = 0; i < count; i++) {
                    int rawValue = response.getRegisterValue(i); // 原始值 0~65535
                    values[i] = (short) rawValue; // 转为有符号 short,自动扩展为 int
                }
                return values;
            } else {
                log.error("读取失败: 未收到响应");
                return new int[0];
            }
        } catch (ModbusIOException e) {
            log.error("Modbus IO 异常: 无法从串口读取数据", e);
            log.error("可能原因: 串口参数不匹配、设备未连接、权限不足或设备地址错误");
            throw e;
        }
    }

    /**
     * 解析时间寄存器(40001-40006)为LocalDateTime
     * 寄存器顺序:[年, 月, 日, 时, 分, 秒]
     */
    private LocalDateTime parseTime(int[] timeRegisters) {
        if (timeRegisters.length != 6) {
            throw new IllegalArgumentException("时间寄存器数量错误");
        }
        int year = timeRegisters[0];   // 40001: 年(如2025)
        int month = timeRegisters[1];  // 40002: 月(1-12)
        int day = timeRegisters[2];    // 40003: 日(1-31)
        int hour = timeRegisters[3];   // 40004: 时(0-23)
        int minute = timeRegisters[4]; // 40005: 分(0-59)
        int second = timeRegisters[5]; // 40006: 秒(0-59)

        return LocalDateTime.of(year, month, day, hour, minute, second);
    }

    /**
     * 将 int 数组转换为英文逗号分隔的字符串(如 [1,2,3] → "1,2,3")
     */
    private String arrayToCsv(int[] array) {
        if (array == null || array.length == 0) {
            return ""; // 空数组返回空字符串(或根据需求返回 "null")
        }
        // 使用 Stream 流拼接数组元素
        return Arrays.stream(array)
                .mapToObj(String::valueOf) // 转为 String
                .collect(Collectors.joining(",")); // 用逗号连接
    }


}

更多推荐