1717[NotNull]
1818private IModbusFactory? ModbusFactory { get ; set ; } </Pre >
1919
20- <p class =" code-label" >3. 通过工厂获得实例</p >
20+ <p class =" code-label" >3. 通过工厂获得相对应协议 <code >IModbusClient</code > 实例</p >
21+
22+ <p >Modbus 可以通过不同的物理介质传输,主要有以下几种方式:</p >
23+
24+ <ul class =" ul-demo" >
25+ <li ><code >Modbus RTU (Remote Terminal Unit)</code >: 采用二进制编码,使用紧凑的二进制表示数据,效率高,是最常用的串行通信模式。通常基于 <code >RS-485</code >(支持多设备)或 <code >RS-232</code >(点对点)物理层,CRC 校验确保数据完整性。</li >
26+ <li ><code >Modbus TCP/IP</code >: 运行于以太网上,使用 <code >TCP/IP</code > 协议,默认端口 <code >502</code >。它在 Modbus RTU 协议基础上添加了 MBAP 报文头,并由于TCP本身是可靠连接的服务,因此去掉了 CRC 校验码。</li >
27+ </ul >
28+
29+ <p ><code >IModbusFactory</code > 实例方法</p >
2130
2231<ul class =" ul-demo" >
23- <li >通过 <code >GetOrCreateTcpMaster</code > 方法得到 <code >IModbusTcpClient</code > 实例</li >
32+ <li >通过 <code >GetOrCreateTcpMaster</code > 方法得到 <code >IModbusTcpClient</code > 实例</li >
33+ <li >通过 <code >GetOrCreateUdpMaster</code > 方法得到 <code >IModbusTcpClient</code > 实例</li >
2434 <li >通过 <code >GetOrCreateRtuMaster</code > 方法得到 <code >IModbusRtuClient</code > 实例</li >
25- <li >通过 <code >GetOrCreateUdpMaster</code > 方法得到 <code >IModbusTcpClient</code > 实例</li >
2635 <li >通过 <code >GetOrCreateRtuOverTcpMaster</code > 方法得到 <code >IModbusTcpClient</code > 实例</li >
2736 <li >通过 <code >GetOrCreateRtuOverUdpMaster</code > 方法得到 <code >IModbusTcpClient</code > 实例</li >
2837</ul >
@@ -36,108 +45,17 @@ private IModbusFactory? ModbusFactory { get; set; }</Pre>
3645<p ><code >Modbus</code > 数据类型共四种</p >
3746
3847<ul class =" ul-demo" >
39- <li >线圈 (Coils) 可读可写 数字量输出,如开关状态</li >
40- <li >离散输入 (Discrete Inputs) 只读 数字量输出,如开关状态</li >
41- <li >输入寄存器 (Input Registers) 只读 模拟量输入,如温度、压力传感器数据</li >
42- <li >保持寄存器 (Holding Registers) 可读可写 模拟量输出,如设定值、控制参数</li >
48+ <li >线圈 (Coils) 可读可写 数字量输出,如开关状态</li >
49+ <li >离散输入 (Discrete Inputs) 只读 数字量输出,如开关状态</li >
50+ <li >输入寄存器 (Input Registers) 只读 模拟量输入,如温度、压力传感器数据</li >
51+ <li >保持寄存器 (Holding Registers) 可读可写 模拟量输出,如设定值、控制参数</li >
4352</ul >
4453
45-
46-
47- <p >数据处理器设计初衷就是为了契合通讯协议大大简化我们开发逻辑,我们已通讯协议每次通讯电文均为 <b >4</b > 位定长举例说明,在实际的通讯过程中,我们接收到的通讯数据存在粘包或者分包的现象</p >
54+ <p >对应 <code >IModbusClient</code > 实例方法如下</p >
4855
4956<ul class =" ul-demo" >
50- <li ><b >粘包</b >:比如我们期望收到 <b >1234</b > 四个字符,实际上我们接收到的是 <b >123412</b > 多出来的 <b >12</b > 其实是下一个数据包的内容,我们需要截取前 4 位数据作为一个数据包才能正确处理数据,这种相邻两个通讯数据包的粘连称为<b >粘包</b ></li >
51- <li ><b >分包</b >:比如我们期望收到 <b >1234</b > 四个字符,实际上我们可能分两次接收到,分别是 <b >12</b > 和 <b >34</b >,我们需要将两个数据包拼接成一个才能正确的处理数据。这种情况称为<b >分包</b ></li >
57+ <li >线圈 (Coils) <code >ReadCoilsAsync</code > <code >WriteCoilAsync</code > <code >WriteMultipleCoilsAsync</code ></li >
58+ <li >离散输入 (Discrete Inputs) <code >ReadInputsAsync</code ></li >
59+ <li >输入寄存器 (Input Registers) <code >ReadInputRegistersAsync</code ></li >
60+ <li >保持寄存器 (Holding Registers) <code >ReadHoldingRegistersAsync</code > <code >WriteRegisterAsync</code > <code >WriteMultipleRegistersAsync</code ></li >
5261</ul >
53-
54- <p >我们内置了一些常用的数据处理类 <code >IDataPackageHandler</code > 接口为数据包处理接口,虚类 <code >DataPackageHandlerBase</code > 作为数据处理器基类已经内置了 <b >粘包</b > <b >分包</b > 的逻辑,继承此类后专注自己处理的业务即可</p >
55-
56- <p >使用方法如下:</p >
57-
58- <Pre >[Inject]
59- [NotNull]
60- private ITcpSocketFactory? TcpSocketFactory { get ; set ; }
61-
62- private async Task CreateClient()
63- {
64- // 创建 ITcpSocketClient 实例
65- var client = TcpSocketFactory .GetOrCreate (" localhost" , 0 );
66-
67- // 设置数据适配器 使用 FixLengthDataPackageHandler 数据处理器处理数据定长 4 的数据
68- var adapter = new DataPackageAdapter
69- {
70- DataPackageHandler = new FixLengthDataPackageHandler (4 )
71- };
72-
73- // 如果 client 不销毁切记使用 RemoveDataPackageAdapter 移除回调委托防止内存泄露
74- client .AddDataPackageAdapter (adapter , buffer =>
75- {
76- // buffer 即是接收到的数据
77- return ValueTask .CompletedTask ;
78- });
79-
80- // 连接远端节点 连接成功后自动开始接收数据
81- var connected = await client .ConnectAsync (" 192.168.10.100" , 6688 );
82- }
83- </Pre >
84-
85- <p >内置数据处理器</p >
86-
87- <ul class =" ul-demo" >
88- <li ><code >FixLengthDataPackageHandler</code > <b >固定长度数据处理器</b > 即每个通讯包都是固定长度</li >
89- <li ><code >DelimiterDataPackageHandler</code > <b >分隔符数据处理器</b > 即通讯包以特定一个或一组字节分割</li >
90- </ul >
91-
92- <p class =" code-label" >5. 数据适配器</p >
93-
94- <p >在我们实际应用中,接收到数据包后(已经过数据处理器)大多情况下是需要将电文转化为应用中的具体数据类型 <code >Class</code > 或 <code >Struct</code >。将原始数据包转化为类或者结构体的过程由我们的数据适配器来实现</p >
95-
96- <p >数据适配器设计思路如下</p >
97-
98- <ol class =" ul-demo" >
99- <li >使用 <code >DataTypeConverterAttribute</code > 标签约定通讯数据使用那个转换类型进行转换 指定类型需继承 <code >IDataConverter</code >
100- 接口
101- </li >
102- <li >使用 <code >DataPropertyConverterAttribute</code > 标签约定如何转换数据类型 (Property) 属性值</li >
103- </ol >
104-
105- <Pre >[DataTypeConverter(Type = typeof(DataConverter< ; MockEntity> ; ))]
106- class MockEntity
107- {
108- [DataPropertyConverter (Type = typeof (byte []), Offset = 0 , Length = 5 )]
109- public byte []? Header { get ; set ; }
110-
111- [DataPropertyConverter (Type = typeof (byte []), Offset = 5 , Length = 2 )]
112- public byte []? Body { get ; set ; }
113-
114- [DataPropertyConverter (Type = typeof (Foo ), Offset = 7 , Length = 1 , ConverterType = typeof (FooConverter ), ConverterParameters = [" test" ])]
115- public string ? Value1 { get ; set ; }
116- } </Pre >
117-
118- <Pre >class FooConverter(string name) : IDataPropertyConverter
119- {
120- public object ? Convert (ReadOnlyMemory & lt ;byte & gt ; data )
121- {
122- return new Foo () { Id = data .Span [0 ], Name = name };
123- }
124- } </Pre >
125-
126- <p class =" code-label" >针对第三方程序集的数据类型解决方案如下:</p >
127- <p >使用 <code ></code ></p >
128-
129- <Pre >builder.Services.ConfigureDataConverters(options =>
130- {
131- options .AddTypeConverter < ; MockEntity > ; ();
132- options .AddPropertyConverter < ; MockEntity > ; (entity = & gt ; entity .Header , new DataPropertyConverterAttribute ()
133- {
134- Offset = 0 ,
135- Length = 5
136- });
137- options .AddPropertyConverter < ; MockEntity > ; (entity = & gt ; entity .Body , new DataPropertyConverterAttribute ()
138- {
139- Offset = 5 ,
140- Length = 2
141- });
142- } );
143- </Pre >
0 commit comments