1
背景与痛点
问题来源

当前 Modbus 驱动写保持寄存器时统一使用 0x10(Write Multiple Registers,写多个寄存器) 功能码。但部分 Modbus 从站设备不支持 0x10,仅支持 0x06(Write Single Register,写单个寄存器)。

这导致平台对这些设备下发控制指令时,会收到超时或异常响应,控制失效。需要增加功能码配置和自动切换能力,以兼容这类设备。

对比 1.4 主线的增强

本实现参考 1.4主线/ibms-driver/ibms-driver-modbus-tcp 的写功能码切换设计,并做了两项关键增强:

增强一:超时重试 3 次

场景 A(超时)不再立即降级,而是重试 3 次后才降级,避免网络抖动误判。

增强二:成功才确认

只有 0x06 实际写入成功才最终确认切换,防止降级误判。

2
新增实例属性
2 项配置属性

为支持功能码切换,Modbus 驱动实例新增两项属性(写入 iot_driver_attribute):

属性attr_name类型默认值说明
写功能码设定writeFunctionCodestring"0x10"控制下置使用哪个功能码:0x10(写多个)/ 0x06(写单个)
写功能码切换模式writeCodeSwitchModestring"auto"auto(自动降级)/ manual(手动,不降级)
默认行为

默认 0x10 + auto:优先用高效的 0x10 批量写,遇不支持的设备自动降级为 0x06,对用户完全透明。

3
核心逻辑:超时重试 + 降级确认
两种失败场景

由 ModbusTcpServer.writeRegisterWithFallback() 实现,区分两种失败场景:

场景触发条件处理
场景 A:超时无响应ModbusTransportException重试 3 次(每次 reConnected() + retryInterval)→ 全失败才降级 0x06
场景 B:异常响应返回 false(isException())直接降级 0x06(不重试)
完整决策流程
flowchart TD Start(["平台下发写保持寄存器"]) --> W10["用 0x10 写"] W10 --> R1{结果?} R1 -->|成功| Done(["完成 ✓"]) R1 -->|异常响应 false| SceneB R1 -->|超时| Retry{"重试 < 3 次?"} Retry -->|是| RC1["reConnected + sleep(retryInterval)"] --> W10 Retry -->|否, 超时耗尽| SceneA subgraph SceneA["场景 A:超时 3 次耗尽"] SA1{"switchMode = auto?"} -->|是| SA2["reConnected → 降级 0x06"] SA2 --> SA3{0x06 成功?} SA3 -->|是| Persist(["✓ 持久化切换为 0x06"]) SA3 -->|否| FailA(["失败 ✗ 抛超时"]) SA1 -->|否 manual| FailA end subgraph SceneB["场景 B:异常响应"] SB1{"switchMode = auto?"} -->|是| SB2["直接降级 0x06
不重连不重试"] SB2 --> SB3{0x06 成功?} SB3 -->|是| Persist SB3 -->|否| FailB(["失败 ✗"]) SB1 -->|否 manual| FailB end style Start fill:#e8f0fe,stroke:#007aff,color:#1c1c1e style Done fill:#e8f8f0,stroke:#34c759,color:#1c1c1e style Persist fill:#e8f8f0,stroke:#34c759,stroke-width:2px,color:#1c1c1e style FailA fill:#fdecee,stroke:#ff3b30,color:#1c1c1e style FailB fill:#fdecee,stroke:#ff3b30,color:#1c1c1e
防误判关键
switchWriteFunctionCode 仅在 0x06 成功时调用

switchWriteFunctionCode() 只在 0x06 实际写入成功(isSuccess==true)时才调用,更新内存 volatile 对象 + 异步写平台库。这避免了因偶发网络问题导致的误切换——只有设备真正响应 0x06 成功,才认为它「确实只支持 0x06」。

4
常量定义
ValueConstant 关键常量

功能码切换相关的常量集中在 constant/ValueConstant.java:

常量值用途
WRITE_CODE_0X10"0x10"写多个寄存器功能码
WRITE_CODE_0X06"0x06"写单个寄存器功能码
SWITCH_MODE_AUTO"auto"自动降级模式
WRITE_TIMEOUT_MAX_RETRY3超时最大重试次数
5
实现代码位置
修改文件索引

本特性涉及的代码改动分布在以下文件(均为 Modbus 驱动包内):

文件修改内容
entity/ModbusTcpDriverInstance.java新增 writeFunctionCode(volatile)、writeCodeSwitchMode 字段
constant/ValueConstant.java新增 WRITE_CODE_0X10/WRITE_CODE_0X06/SWITCH_MODE_AUTO/WRITE_TIMEOUT_MAX_RETRY=3
util/BuildBean.javabuildDriverInstance() 读取新属性(默认 0x10/auto)
server/ModbusTcpServer.javawrite() 的 HOLDING 分支调 writeRegisterWithFallback();新增 writeSingleRegister() / switchWriteFunctionCode();修复 writeRegister() NPE
base/SendService.java + 实现updateWriteFunctionCode() 平台同步(异步写库)
核心方法签名
java
// ModbusTcpServer.java — 功能码回退主入口
private boolean writeRegisterWithFallback(String str, List<ModbusTcpPoint> list)

// 降级到单寄存器写(0x06),逐寄存器写入
private boolean writeSingleRegister(String str, List<ModbusTcpPoint> list)

// 功能码降级确认(仅 0x06 成功时调用)
private void switchWriteFunctionCode(String newFc)
6
属性升级 SQL
存量项目迁移

对于已部署的存量项目,Modbus 驱动已存在但缺少这两项新属性。需执行属性升级 SQL 为每个项目的 Modbus 驱动补充属性行:

  • 脚本位置:同目录 modbus写功能码切换_属性升级SQL.sql
  • 存储过程:proc_upgrade_modbus_write_function_code()
  • 特性:幂等——按项目遍历 sys_project,已存在则跳过,可重复执行
新增属性行规格
attr_nameattr_sort可选值默认
writeFunctionCode13写多个寄存器(0x10) / 写单个寄存器(0x06)0x10
writeCodeSwitchMode14auto / manualauto
列结构

完整写入 iot_driver_attribute 的全部列:driver_id, attr_display_name, attr_name, attr_description, attr_sort, data_type, option_check_type, option_element_type, option_required, option_length, option_max, option_min, option_select_data, project_id, driver_name, default_value, linkage_trigger, linkage_is_triggered。

7
与主文档的关系
互补定位

本文档是 《Modbus TCP 驱动功能设计》 的实现细节专题,两者互补:

维度主文档(功能设计)本文档(写功能码专题)
定位面向读者的全面讲解面向维护者的实现索引
范围Modbus 驱动全部(连接/读/写/属性/FAQ)仅写功能码切换这一个特性
侧重原理图解、流程时序、配置表代码位置、常量、SQL 升级、方法签名
读者想了解驱动如何工作的人要修改/排查此特性的开发者
阅读建议

想理解「为什么」和「怎么用」→ 看主文档第 7 节;想知道「改了哪些代码」「怎么升级存量库」→ 看本文档。