WPF 数据采集网关系统设计与实现
一、系统概述
本系统是一个基于 WPF 的数据采集网关,支持主流 PLC(可编程逻辑控制器)的数据采集,并将采集到的数据汇总存储到数据库中。系统采用模块化设计,具有良好的扩展性和可维护性。
二、系统架构
1. 整体架构
+-------------------+
| WPF客户端 | <-- 用户界面
+-------------------+
|
v
+-------------------+
| 数据采集服务 | <-- 核心业务逻辑
+-------------------+
|
v
+-------------------+
| PLC通信模块 | <-- 支持多种PLC协议
+-------------------+
|
v
+-------------------+
| 数据库存储 | <-- 数据持久化
+-------------------+
2. 技术选型
- 前端界面:WPF(Windows Presentation Foundation)
- PLC通信:OPC UA/DA、Modbus TCP/RTU、S7协议等
- 数据库:SQL Server/MySQL/PostgreSQL(根据需求选择)
- 通信框架:.NET Core/.NET Framework + 第三方PLC库(如libnodave、NModbus等)
三、核心模块设计
1. PLC通信模块
1.1 支持的PLC类型及协议
PLC品牌 | 支持协议 | 实现方式 |
---|---|---|
Siemens | S7(S7Comm) | 使用libnodave或S7.Net |
Siemens | OPC UA/DA | 使用OPC基金会库 |
Mitsubishi | MELSEC | 使用MelsecNet |
Omron | SYSMAC | 使用OmronPlc |
Allen-Bradley | ControlLogix | 使用RSLinx |
Modbus设备 | Modbus TCP/RTU | 使用NModbus |
1.2 接口设计
// IPlcCommunication.cs
public interface IPlcCommunication
{
bool Connect(string connectionString);
void Disconnect();
bool IsConnected { get; }
// 读取数据
Task<T> ReadAsync<T>(string address);
Task<IEnumerable<T>> ReadMultipleAsync<T>(IEnumerable<string> addresses);
// 写入数据
Task<bool> WriteAsync<T>(string address, T value);
}
1.3 具体实现示例(Siemens S7)
// SiemensS7Plc.cs
using Snap7;
using System;
using System.Threading.Tasks;
public class SiemensS7Plc : IPlcCommunication
{
private S7Client _client;
public bool Connect(string connectionString)
{
// 解析连接字符串,格式如:"IP=192.168.0.1;Rack=0;Slot=1"
var parameters = ParseConnectionString(connectionString);
_client = new S7Client();
int result = _client.ConnectTo(
parameters["IP"],
Convert.ToInt32(parameters["Rack"]),
Convert.ToInt32(parameters["Slot"]));
return result == 0; // 0表示成功
}
public async Task<bool> ReadBoolAsync(string address)
{
int dbNumber;
int byteOffset;
int bitOffset;
ParseAddress(address, out dbNumber, out byteOffset, out bitOffset);
int result = await Task.Run(() =>
_client.DBRead(dbNumber, byteOffset, 1, out byte[] buffer));
if (result != 0) return false;
return (buffer[0] & (1 << bitOffset)) != 0;
}
// 其他读写方法实现...
}
2. 数据采集服务
2.1 核心功能
- 定时采集PLC数据
- 数据缓存与处理
- 异常处理与重试机制
- 数据格式转换
2.2 实现示例
// PlcDataCollector.cs
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
public class PlcDataCollector : IDisposable
{
private readonly List<IPlcCommunication> _plcs = new List<IPlcCommunication>();
private readonly ConcurrentDictionary<string, object> _dataCache = new ConcurrentDictionary<string, object>();
private readonly Timer _timer;
private readonly int _collectionIntervalMs = 1000; // 默认1秒
public event EventHandler<DataChangedEventArgs> DataChanged;
public PlcDataCollector(IEnumerable<IPlcCommunication> plcs, int collectionIntervalMs = 1000)
{
_plcs.AddRange(plcs);
_collectionIntervalMs = collectionIntervalMs;
_timer = new Timer(CollectData, null, 0, _collectionIntervalMs);
}
private void CollectData(object state)
{
try
{
var newData = new Dictionary<string, object>();
foreach (var plc in _plcs)
{
if (!plc.IsConnected) continue;
// 示例:读取多个地址的数据
var tags = new[] { "DB1.DBX0.0", "DB1.DBW2", "DB1.DBD4" };
foreach (var tag in tags)
{
try
{
// 根据数据类型调用不同的读取方法
if (tag.EndsWith("X")) // BOOL
{
var value = plc.ReadAsync<bool>(tag).Result;
newData[tag] = value;
}
else if (tag.EndsWith("W")) // INT
{
var value = plc.ReadAsync<int>(tag).Result;
newData[tag] = value;
}
else if (tag.EndsWith("D")) // REAL
{
var value = plc.ReadAsync<double>(tag).Result;
newData[tag] = value;
}
}
catch (Exception ex)
{
// 记录错误日志
LogError($"读取PLC数据失败: {tag}, 错误: {ex.Message}");
}
}
}
// 更新缓存并触发事件
foreach (var kvp in newData)
{