A:增加订阅类型选择
This commit is contained in:
1 parent
6ed0d3f1c2
commit
de1a29c522
4 files changed
+144
-20
No files matched your search
@@ -73,6 +73,7 @@
|
||||
<GroupBox Header="实时监控 (Add Watch)" Grid.Row="2" Margin="0,0,0,15">
|
||||
<StackPanel Orientation="Horizontal" Margin="10">
|
||||
<TextBox hc:InfoElement.Placeholder="地址 (如 DB1.DBW0)" Text="{Binding MonitorAddress}" Width="200" Margin="0,0,10,0"/>
|
||||
<ComboBox ItemsSource="{Binding SupportedMonitorTypes}" SelectedItem="{Binding SelectedMonitorType}" Width="120" Margin="0,0,10,0" DisplayMemberPath="Name"/>
|
||||
<Button Content="添加监控" Command="{Binding AddMonitorCommand}"/>
|
||||
</StackPanel>
|
||||
</GroupBox>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
using Plc.Models;
|
||||
using Sharp.Wpf.Mvvm;
|
||||
using System;
|
||||
|
||||
namespace Client.ViewModels;
|
||||
|
||||
public class PlcTagViewModel : ViewModelBase
|
||||
{
|
||||
private readonly PlcTag _tag;
|
||||
|
||||
public PlcTagViewModel(PlcTag tag)
|
||||
{
|
||||
_tag = tag ?? throw new ArgumentNullException(nameof(tag));
|
||||
}
|
||||
|
||||
public string Id => _tag.Id;
|
||||
|
||||
public string Address => _tag.Address;
|
||||
|
||||
public Type DataType => _tag.DataType;
|
||||
|
||||
public object? Value => _tag.Value;
|
||||
|
||||
public PlcTag Tag => _tag;
|
||||
|
||||
public void UpdateValue()
|
||||
{
|
||||
OnPropertyChanged(nameof(Value));
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ public class PlcViewModel : ViewModelBase
|
||||
|
||||
// 监控
|
||||
private string _monitorAddress = "DB1.DBW0";
|
||||
private Type _selectedMonitorType = typeof(short);
|
||||
|
||||
public PlcViewModel(
|
||||
IPlcService plcService,
|
||||
@@ -116,10 +117,30 @@ public class PlcViewModel : ViewModelBase
|
||||
get => _monitorAddress;
|
||||
set => SetProperty(ref _monitorAddress, value);
|
||||
}
|
||||
|
||||
public Type SelectedMonitorType
|
||||
{
|
||||
get => _selectedMonitorType;
|
||||
set => SetProperty(ref _selectedMonitorType, value);
|
||||
}
|
||||
|
||||
// 数据源
|
||||
public ObservableCollection<PlcBrand> Brands { get; } = new(Enum.GetValues<PlcBrand>());
|
||||
public ObservableCollection<CpuType> CpuTypes { get; } = new(Enum.GetValues<CpuType>());
|
||||
public ObservableCollection<Type> SupportedMonitorTypes { get; } = new()
|
||||
{
|
||||
typeof(bool),
|
||||
typeof(byte),
|
||||
typeof(short),
|
||||
typeof(ushort),
|
||||
typeof(int),
|
||||
typeof(uint),
|
||||
typeof(long),
|
||||
typeof(ulong),
|
||||
typeof(float),
|
||||
typeof(double),
|
||||
typeof(string)
|
||||
};
|
||||
public ObservableCollection<PlcTagViewModel> MonitoredTags { get; } = new();
|
||||
|
||||
#endregion
|
||||
@@ -206,7 +227,7 @@ public class PlcViewModel : ViewModelBase
|
||||
var tag = new PlcTag
|
||||
{
|
||||
Address = MonitorAddress,
|
||||
DataType = typeof(short)
|
||||
DataType = SelectedMonitorType
|
||||
};
|
||||
|
||||
var vm = new PlcTagViewModel(tag);
|
||||
|
||||
+91
-19
@@ -189,6 +189,96 @@ _plcService.Subscribe(tag);
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ 核心通信优化机制
|
||||
|
||||
本项目采用了**分桶(Batching/Bucketing)**优化策略,这是高性能 PLC 通信的关键。
|
||||
|
||||
### 1. 为什么需要优化?
|
||||
PLC 通信通常基于 TCP/IP 协议,每次读写请求都包含网络握手、协议头封装和 PLC 内部处理时间。如果用户订阅了 100 个离散点位,如果发送 100 次网络请求,延迟将极高(例如 100 * 20ms = 2000ms),无法满足实时监控需求。
|
||||
|
||||
### 2. 优化原理
|
||||
系统通过 `IPlcOptimizer`(如 `SiemensOptimizer`)在内存中动态重组读写请求,将多个小的、地址相邻的请求合并为一个大的网络请求。
|
||||
|
||||
#### 处理流程(伪代码)
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
A[原始订阅Tags] --> B[1. 分组 Grouping]
|
||||
B --> C[2. 排序 Sorting]
|
||||
C --> D[3. 合并 Merging]
|
||||
D --> E[执行网络请求]
|
||||
```
|
||||
|
||||
1. **分组 (Grouping)**: 根据内存区域(如 DB1, DB2, M区, I区)将 Tag 分组。不同区域无法合并。
|
||||
2. **排序 (Sorting)**: 在同一组内,按字节偏移量(Byte Offset)从小到大排序。
|
||||
3. **合并 (Merging)**:
|
||||
* 遍历排序后的 Tags。
|
||||
* 计算两个 Tag 之间的**间隙 (Gap)**。
|
||||
* 如果 `Gap <= MaxGapBytes` (默认20字节) 且 `TotalLength <= MaxPduSize` (最大包长),则合并。
|
||||
* 否则,创建一个新的**批次 (Batch)**。
|
||||
|
||||
### 3. 实际案例分析
|
||||
|
||||
假设用户订阅了以下点位(西门子 S7):
|
||||
- Tag A: `DB1.DBW0` (2 bytes)
|
||||
- Tag B: `DB1.DBW4` (2 bytes)
|
||||
- Tag C: `DB1.DBW1000` (2 bytes)
|
||||
|
||||
**处理逻辑:**
|
||||
1. **Tag A 和 Tag B**:
|
||||
* Tag A 结束于 byte 2,Tag B 开始于 byte 4。
|
||||
* 间隙 Gap = 4 - 2 = 2 bytes。
|
||||
* 2 bytes < 20 bytes (阈值),且总长未超 PDU。
|
||||
* **结果**: 合并为一个请求:`Start: 0, Length: 6`。虽然读回了无用的 byte 2-3,但节省了一次网络请求。
|
||||
2. **Tag B 和 Tag C**:
|
||||
* Tag B 结束于 byte 6,Tag C 开始于 byte 1000。
|
||||
* 间隙 Gap = 994 bytes。
|
||||
* 994 bytes > 20 bytes。
|
||||
* **结果**: 断开,Tag C 放入新的批次。
|
||||
|
||||
**最终网络请求**: 2 次(而不是 3 次)。
|
||||
|
||||
### 4. 极限情况处理
|
||||
|
||||
* **只订阅 1 个点位**: 优化器生成 1 个批次,包含 1 个 Tag。无额外开销。
|
||||
* **连续订阅 1000 个点位 (例如 DB1.DBW0 - DB1.DBW2000)**:
|
||||
* 总长度 2000 字节。
|
||||
* 假设 PLC PDU 限制为 960 字节。
|
||||
* 优化器会自动切分为 3 个批次:
|
||||
* Batch 1: 0 - 960
|
||||
* Batch 2: 960 - 1920
|
||||
* Batch 3: 1920 - 2000
|
||||
* 系统自动处理分包,上层业务无感。
|
||||
|
||||
### 5. 未来扩展性 (适配欧姆龙/三菱)
|
||||
|
||||
本架构设计之初就考虑了多品牌支持。适配新品牌只需实现以下接口,**无需修改 Client/ViewModel 代码**:
|
||||
|
||||
1. **实现 `IPlcDriver`**: 处理底层的 Socket/串口 通信(如 FINS, MC 协议)。
|
||||
2. **实现 `IPlcAddress`**: 定义新品牌的地址格式(如欧姆龙的 `D100`, `W0.00`)。
|
||||
3. **实现 `IPlcOptimizer`**: 针对新协议调整优化参数。
|
||||
|
||||
#### 扩展示例(伪代码)
|
||||
|
||||
```csharp
|
||||
// 欧姆龙 FINS 协议优化器示例
|
||||
public class OmronOptimizer : IPlcOptimizer
|
||||
{
|
||||
// 欧姆龙按 Word (2byte) 寻址,且单次读取限制不同
|
||||
private const int MaxWordCount = 2000;
|
||||
|
||||
public List<PlcReadBatch> Optimize(IEnumerable<PlcTag> tags, int maxPduSize)
|
||||
{
|
||||
// 1. 按内存区 (DM, CIO, WR) 分组
|
||||
// 2. 按 Word 地址排序
|
||||
// 3. 合并逻辑:如果两个地址相差 < 10 个 Words,则合并读取
|
||||
// 4. 返回适用于 FINS 协议的 ReadRequest 列表
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 高级功能
|
||||
|
||||
### 1. 标签订阅与监控
|
||||
@@ -211,24 +301,6 @@ foreach (var tag in tags)
|
||||
await _plcService.StartAsync();
|
||||
```
|
||||
|
||||
### 2. 批量优化原理
|
||||
|
||||
优化器会自动:
|
||||
1. **按区域分组** - DB1、M、I、Q 等分开处理
|
||||
2. **按地址排序** - 确保连续性
|
||||
3. **间隙合并** - 小于 `MaxGapBytes` 的间隙合并为一次读取
|
||||
4. **PDU限制** - 单次读取不超过 `MaxPduSize`
|
||||
|
||||
**示例:**
|
||||
```
|
||||
标签1: DB1.DBW0 (2 bytes)
|
||||
标签2: DB1.DBW4 (2 bytes) ← 间隙 2 bytes
|
||||
标签3: DB1.DBW8 (2 bytes) ← 间隙 2 bytes
|
||||
|
||||
优化后: 读取 DB1.DBW0, 10 bytes
|
||||
包含所有三个标签和中间的间隙
|
||||
```
|
||||
|
||||
### 3. 使用特性绑定
|
||||
|
||||
```csharp
|
||||
@@ -555,4 +627,4 @@ MIT License - 详见 [LICENSE](LICENSE) 文件
|
||||
|
||||
- 项目主页: [GitHub](https://github.com/yourcompany/plc)
|
||||
- 问题反馈: [Issues](https://github.com/yourcompany/plc/issues)
|
||||
- 文档: [Wiki](https://github.com/yourcompany/plc/wiki)
|
||||
- 文档: [Wiki](https://github.com/yourcompany/plc/wiki)
|
||||
Reference in new issue
Block a user