|
17 | 17 | [NotNull]
|
18 | 18 | private IModbusFactory? ModbusFactory { get; set; }</Pre>
|
19 | 19 |
|
20 |
| -<Pre>var client = ModbusFactory.GetOrCreateTcpMaster("bb", options => |
21 |
| -{ |
22 |
| - options.LocalEndPoint = new IPEndPoint(IPAddress.Loopback, 0); |
23 |
| -});</Pre> |
| 20 | +<p class="code-label">3. 通过工厂获得相对应协议 <code>IModbusClient</code> 实例</p> |
24 | 21 |
|
25 |
| -<p class="code-label">3. 使用方法</p> |
| 22 | +<p>Modbus 可以通过不同的物理介质传输,主要有以下几种方式:</p> |
26 | 23 |
|
27 | 24 | <ul class="ul-demo">
|
28 |
| - <li>通过 <code>ITcpSocketClient</code> 实例方法 <code>ConnectAsync</code> 连接远端节点</li> |
29 |
| - <li>通过 <code>ITcpSocketClient</code> 实例方法 <code>SendAsync</code> 发送协议数据</li> |
30 |
| - <li>通过 <code>ITcpSocketClient</code> 实例方法 <code>Close</code> 关闭连接</li> |
31 |
| - <li>通过 <code>ITcpSocketClient</code> 实例方法 <code>SetDataHandler</code> 方法设置数据处理器</li> |
32 |
| - <li>通过 <code>ITcpSocketClient</code> 实例属性 <code>ReceivedCallBack</code> 方法设置接收数据处理器(注意:此回调未做任何数据处理为原始数据)</li> |
| 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> |
33 | 27 | </ul>
|
34 | 28 |
|
35 |
| -<p class="code-label">4. 数据处理器</p> |
36 |
| - |
37 |
| -<p>在我们实际应用中,建立套接字连接后就会进行数据通信,数据通信不会是杂乱无章的随机数据,在应用中都是有双方遵守的规约简称通讯协议,在通讯协议的约束下,发送方与接收方均根据通讯协议进行编码或解码工作,将数据有条不紊的传输</p> |
38 |
| - |
39 |
| -<p>数据处理器设计初衷就是为了契合通讯协议大大简化我们开发逻辑,我们已通讯协议每次通讯电文均为 <b>4</b> 位定长举例说明,在实际的通讯过程中,我们接收到的通讯数据存在粘包或者分包的现象</p> |
| 29 | +<p><code>IModbusFactory</code> 实例方法</p> |
40 | 30 |
|
41 | 31 | <ul class="ul-demo">
|
42 |
| - <li><b>粘包</b>:比如我们期望收到 <b>1234</b> 四个字符,实际上我们接收到的是 <b>123412</b> 多出来的 <b>12</b> 其实是下一个数据包的内容,我们需要截取前 4 位数据作为一个数据包才能正确处理数据,这种相邻两个通讯数据包的粘连称为<b>粘包</b></li> |
43 |
| - <li><b>分包</b>:比如我们期望收到 <b>1234</b> 四个字符,实际上我们可能分两次接收到,分别是 <b>12</b> 和 <b>34</b>,我们需要将两个数据包拼接成一个才能正确的处理数据。这种情况称为<b>分包</b></li> |
| 32 | + <li>通过 <code>GetOrCreateTcpMaster</code> 方法得到 <code>IModbusTcpClient</code> 实例</li> |
| 33 | + <li>通过 <code>GetOrCreateUdpMaster</code> 方法得到 <code>IModbusTcpClient</code> 实例</li> |
| 34 | + <li>通过 <code>GetOrCreateRtuMaster</code> 方法得到 <code>IModbusRtuClient</code> 实例</li> |
| 35 | + <li>通过 <code>GetOrCreateRtuOverTcpMaster</code> 方法得到 <code>IModbusTcpClient</code> 实例</li> |
| 36 | + <li>通过 <code>GetOrCreateRtuOverUdpMaster</code> 方法得到 <code>IModbusTcpClient</code> 实例</li> |
44 | 37 | </ul>
|
45 | 38 |
|
46 |
| -<p>我们内置了一些常用的数据处理类 <code>IDataPackageHandler</code> 接口为数据包处理接口,虚类 <code>DataPackageHandlerBase</code> 作为数据处理器基类已经内置了 <b>粘包</b> <b>分包</b> 的逻辑,继承此类后专注自己处理的业务即可</p> |
47 |
| - |
48 |
| -<p>使用方法如下:</p> |
| 39 | +<p>调用其对应的 <code>Remove</code> 方法即可从缓存中移除指定名称的 <code>IModbusClient</code> 实例。如</p> |
49 | 40 |
|
50 |
| -<Pre>[Inject] |
51 |
| -[NotNull] |
52 |
| -private ITcpSocketFactory? TcpSocketFactory { get; set; } |
| 41 | +<Pre>ModbusFactory.RemoveTcpMaster("test");</Pre> |
53 | 42 |
|
54 |
| -private async Task CreateClient() |
55 |
| -{ |
56 |
| - // 创建 ITcpSocketClient 实例 |
57 |
| - var client = TcpSocketFactory.GetOrCreate("localhost", 0); |
| 43 | +<p class="code-label">4. 数据操作</p> |
58 | 44 |
|
59 |
| - // 设置数据适配器 使用 FixLengthDataPackageHandler 数据处理器处理数据定长 4 的数据 |
60 |
| - var adapter = new DataPackageAdapter |
61 |
| - { |
62 |
| - DataPackageHandler = new FixLengthDataPackageHandler(4) |
63 |
| - }; |
| 45 | +<p><code>Modbus</code> 数据类型共四种</p> |
64 | 46 |
|
65 |
| - // 如果 client 不销毁切记使用 RemoveDataPackageAdapter 移除回调委托防止内存泄露 |
66 |
| - client.AddDataPackageAdapter(adapter, buffer => |
67 |
| - { |
68 |
| - // buffer 即是接收到的数据 |
69 |
| - return ValueTask.CompletedTask; |
70 |
| - }); |
71 |
| - |
72 |
| - // 连接远端节点 连接成功后自动开始接收数据 |
73 |
| - var connected = await client.ConnectAsync("192.168.10.100", 6688); |
74 |
| -} |
75 |
| -</Pre> |
| 47 | +<ul class="ul-demo"> |
| 48 | + <li>线圈 (Coils) 可读可写 数字量输出,如开关状态</li> |
| 49 | + <li>离散输入 (Discrete Inputs) 只读 数字量输出,如开关状态</li> |
| 50 | + <li>输入寄存器 (Input Registers) 只读 模拟量输入,如温度、压力传感器数据</li> |
| 51 | + <li>保持寄存器 (Holding Registers) 可读可写 模拟量输出,如设定值、控制参数</li> |
| 52 | +</ul> |
76 | 53 |
|
77 |
| -<p>内置数据处理器</p> |
| 54 | +<p>对应 <code>IModbusClient</code> 实例方法如下</p> |
78 | 55 |
|
79 | 56 | <ul class="ul-demo">
|
80 |
| - <li><code>FixLengthDataPackageHandler</code> <b>固定长度数据处理器</b> 即每个通讯包都是固定长度</li> |
81 |
| - <li><code>DelimiterDataPackageHandler</code> <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> |
82 | 61 | </ul>
|
83 |
| - |
84 |
| -<p class="code-label">5. 数据适配器</p> |
85 |
| - |
86 |
| -<p>在我们实际应用中,接收到数据包后(已经过数据处理器)大多情况下是需要将电文转化为应用中的具体数据类型 <code>Class</code> 或 <code>Struct</code>。将原始数据包转化为类或者结构体的过程由我们的数据适配器来实现</p> |
87 |
| - |
88 |
| -<p>数据适配器设计思路如下</p> |
89 |
| - |
90 |
| -<ol class="ul-demo"> |
91 |
| - <li>使用 <code>DataTypeConverterAttribute</code> 标签约定通讯数据使用那个转换类型进行转换 指定类型需继承 <code>IDataConverter</code> |
92 |
| - 接口 |
93 |
| - </li> |
94 |
| - <li>使用 <code>DataPropertyConverterAttribute</code> 标签约定如何转换数据类型 (Property) 属性值</li> |
95 |
| -</ol> |
96 |
| - |
97 |
| -<Pre>[DataTypeConverter(Type = typeof(DataConverter<MockEntity>))] |
98 |
| -class MockEntity |
99 |
| -{ |
100 |
| - [DataPropertyConverter(Type = typeof(byte[]), Offset = 0, Length = 5)] |
101 |
| - public byte[]? Header { get; set; } |
102 |
| - |
103 |
| - [DataPropertyConverter(Type = typeof(byte[]), Offset = 5, Length = 2)] |
104 |
| - public byte[]? Body { get; set; } |
105 |
| - |
106 |
| - [DataPropertyConverter(Type = typeof(Foo), Offset = 7, Length = 1, ConverterType = typeof(FooConverter), ConverterParameters = ["test"])] |
107 |
| - public string? Value1 { get; set; } |
108 |
| -}</Pre> |
109 |
| - |
110 |
| -<Pre>class FooConverter(string name) : IDataPropertyConverter |
111 |
| -{ |
112 |
| - public object? Convert(ReadOnlyMemory<byte> data) |
113 |
| - { |
114 |
| - return new Foo() { Id = data.Span[0], Name = name }; |
115 |
| - } |
116 |
| -}</Pre> |
117 |
| - |
118 |
| -<p class="code-label">针对第三方程序集的数据类型解决方案如下:</p> |
119 |
| -<p>使用 <code></code></p> |
120 |
| - |
121 |
| -<Pre>builder.Services.ConfigureDataConverters(options => |
122 |
| -{ |
123 |
| - options.AddTypeConverter<MockEntity>(); |
124 |
| - options.AddPropertyConverter<MockEntity>(entity => entity.Header, new DataPropertyConverterAttribute() |
125 |
| - { |
126 |
| - Offset = 0, |
127 |
| - Length = 5 |
128 |
| - }); |
129 |
| - options.AddPropertyConverter<MockEntity>(entity => entity.Body, new DataPropertyConverterAttribute() |
130 |
| - { |
131 |
| - Offset = 5, |
132 |
| - Length = 2 |
133 |
| - }); |
134 |
| -}); |
135 |
| -</Pre> |
0 commit comments