C#利用HSLCommunication库实现PLC数据采集与监控系统实战
1. 工业场景下的PLC数据采集需求在现代化工厂的生产线上PLC可编程逻辑控制器就像车间里的大脑24小时不间断地控制着各种设备的运转。想象一下你管理的是一条汽车装配线几十台机器人正在协同工作每个焊接点的温度、每台拧紧枪的扭矩、每段传送带的速度这些关键数据都实时存储在PLC中。作为工程师你需要随时掌握这些数据才能确保生产质量及时发现异常。传统的手动记录方式显然跟不上节奏这时候就需要一套自动化的数据采集系统。我去年负责过一个轮胎生产线的改造项目客户要求每5秒采集一次12台PLC的300多个数据点。最初尝试用OPC方案结果发现配置复杂性能也不稳定。后来改用HSLCommunication库配合C#开发只用两周就完成了数据采集模块运行至今零故障。2. HSLCommunication库环境搭建2.1 开发环境准备工欲善其事必先利其器。在开始编码前你需要准备好这些工具Visual Studio 2019/2022社区版就够用.NET Framework 4.6或.NET Core 3.1能ping通PLC的局域网环境安装HSLCommunication库就像在手机上下载APP一样简单。打开VS的NuGet包管理器搜索HslCommunication时你会注意到有多个版本。这里有个小技巧生产环境建议选择带Standard后缀的稳定版比如我常用的HslCommunication.Standard 10.1.3。这个版本经过多个工业项目验证对西门子S7系列的兼容性最好。// 在Package Manager Console执行安装命令 Install-Package HslCommunication.Standard -Version 10.1.32.2 PLC通信基础配置第一次连接PLC时我踩过不少坑。有次在汽车厂调试死活连不上PLC最后发现是子网掩码设错了。这里分享几个必查项确认PC和PLC在同一网段比如都是192.168.1.x关闭PC防火墙或添加出入站规则西门子PLC需要勾选允许来自远程对象的PUT/GET通信以西门子S7-1200为例创建通信实例时要注意槽号这个参数。虽然文档说默认是1但有些国产化设备会改成2。有个快速验证的方法用TIA Portal软件在线查看PLC属性里面的插槽编号就是你要填的值。using HslCommunication; using HslCommunication.Profinet.Siemens; // 创建S7-1200通信实例 var plc new SiemensS7Net(SiemensPLCS.S71200, 192.168.1.10, 0, 1) { ConnectTimeOut 5000, // 5秒连接超时 ReceiveTimeOut 10000 // 10秒操作超时 };3. 数据采集核心实现3.1 单点数据读取优化直接调用Read方法虽然简单但在高频采集时性能不够理想。通过实测对比我总结出几个优化技巧对于BOOL类型使用ReadBool替代通用的Read方法速度提升约30%浮点数读取时指定DB块偏移量比用绝对地址快设置合理的缓存策略对不变的数据如设备序列号只读一次// 优化后的读取示例 var tempResult plc.ReadFloat(DB100.DBD40); if(tempResult.IsSuccess) { float temperature tempResult.Content; // 温度补偿算法 float calibratedTemp temperature * 0.98f 2.5f; Console.WriteLine($当前温度{calibratedTemp:F1}℃); } else { Logger.Error($温度读取失败{tempResult.Message}); }3.2 批量采集策略在注塑机监控项目中需要同时采集200个数据点。如果逐个读取一个周期要3秒多完全达不到客户要求的500ms间隔。这时候就要用批量读取就像去超市购物一次性把需要的商品都拿齐而不是来回跑多次。HSLCommunication提供两种批量方式同类型批量ReadBoolArray、ReadFloatArray等混合类型批量使用Read方法配合特定地址格式// 混合类型批量读取 var addressMap new Dictionarystring, DataType { {DB1.DBX0.0, DataType.Bool}, // 急停状态 {DB1.DBD2, DataType.Float}, // 压力值 {DB1.DBD6, DataType.Int32} // 计数器 }; var batchResult plc.Read(addressMap); if(batchResult.IsSuccess) { var emergencyStop (bool)batchResult[DB1.DBX0.0]; var pressure (float)batchResult[DB1.DBD2]; var count (int)batchResult[DB1.DBD6]; }4. 数据存储与异常处理4.1 数据库存储方案采集到的数据就像金矿需要妥善保存。根据数据量大小我有这些推荐方案小规模1万点/天SQLite本地存储中规模MySQL分表存储大规模时序数据库InfluxDB这里分享一个InfluxDB的写入封装类我在多个项目中都复用这个方案public class InfluxWriter { private readonly InfluxDBClient _client; public InfluxWriter(string url, string token, string org) { _client new InfluxDBClient(url, token, org); } public async Task WriteAsync(string bucket, PointData measurement) { try { var writeApi _client.GetWriteApiAsync(); await writeApi.WritePointAsync(bucket, measurement); } catch(Exception ex) { // 失败时写入本地缓存文件 File.AppendAllText(backup.log, ${DateTime.Now}:{measurement}\n); } } } // 使用示例 var point PointData.Measurement(temperature) .Tag(device, press_01) .Field(value, 25.3) .Timestamp(DateTime.UtcNow, WritePrecision.Ns); await influxWriter.WriteAsync(factory, point);4.2 异常处理机制工业现场网络环境复杂断线重连是必修课。我设计了一个带指数退避的重连策略第一次断线立即重试第二次等待2秒第三次等待4秒以此类推最大间隔30秒private async Task ReconnectWithRetry(SiemensS7Net plc, int maxRetries 5) { int delay 1000; for(int i0; imaxRetries; i) { try { var result plc.ConnectServer(); if(result.IsSuccess) return; await Task.Delay(delay); delay Math.Min(delay * 2, 30000); } catch { // 记录异常日志 } } throw new TimeoutException(PLC连接失败); }5. 监控系统界面集成5.1 WPF实时数据显示用WPF的MVVM模式配合Binding可以轻松实现数据实时刷新。这里有个性能优化点不要每个数据变化都刷新UI而是用定时器批量更新。!-- XAML界面示例 -- Grid StackPanel TextBlock Text{Binding Temperature} FontSize24/ ProgressBar Value{Binding Pressure} Maximum100/ Ellipse Fill{Binding StatusColor} Width20 Height20/ /StackPanel /Grid// ViewModel实现 public class PlcViewModel : INotifyPropertyChanged { private Timer _updateTimer; public PlcViewModel() { _updateTimer new Timer(200); // 200ms刷新间隔 _updateTimer.Elapsed (s,e) UpdateData(); } private float _temperature; public float Temperature { get _temperature; set SetField(ref _temperature, value); } private void UpdateData() { var result _plc.ReadFloat(DB1.DBD4); if(result.IsSuccess) Temperature result.Content; } }5.2 报警功能实现完善的报警系统需要包含这些要素多级报警警告、严重、紧急延时触发避免抖动误报报警抑制维护期间屏蔽历史记录查询public class AlarmManager { private readonly Dictionarystring, Alarm _alarms new(); public void CheckAlarms() { foreach(var alarm in _alarms.Values) { bool currentState GetAlarmState(alarm.Address); if(currentState ! alarm.IsActive) { if(DateTime.Now - alarm.LastChange alarm.Delay) { alarm.IsActive currentState; // 触发报警事件 OnAlarmChanged?.Invoke(alarm); } } else { alarm.LastChange DateTime.Now; } } } public event ActionAlarm OnAlarmChanged; } public class Alarm { public string Name {get; set;} public string Address {get; set;} public bool IsActive {get; set;} public TimeSpan Delay {get; set;} public DateTime LastChange {get; set;} }6. 系统部署与维护6.1 安装包制作用Inno Setup制作安装包时记得包含这些组件.NET运行时可选在线安装VC RedistributableHSLCommunication依赖自动创建桌面快捷方式开机自启动配置; Inno Setup脚本片段 [Files] Source: bin\Release\*.exe; DestDir: {app} Source: bin\Release\*.dll; DestDir: {app} [Run] Filename: {dotnet}\dotnet.exe; Parameters: install --runtime win-x64 Microsoft.NETCore.App; StatusMsg: 安装.NET运行时...; Check: not IsDotNetInstalled [Icons] Name: {commondesktop}\数据采集系统; Filename: {app}\Monitor.exe6.2 远程诊断方案现场设备出问题时最头疼的就是无法远程查看状态。我采用MQTT协议实现了远程监控功能核心思路是设备定时发布运行状态服务端订阅指令主题通过SSL加密确保安全public class MqttService { private readonly IMqttClient _client; public async Task ConnectAsync() { var options new MqttClientOptionsBuilder() .WithTcpServer(iot.example.com, 8883) .WithCredentials(client01, password123) .WithTls() .Build(); await _client.ConnectAsync(options); _client.SubscribeAsync(cmd/device01); _client.ApplicationMessageReceived OnMessageReceived; } private void OnMessageReceived(MqttApplicationMessageReceivedEventArgs e) { var payload Encoding.UTF8.GetString(e.ApplicationMessage.Payload); if(payload restart) { // 执行重启逻辑 } } }7. 性能优化实战技巧经过多个项目的积累我总结出这些提升系统稳定性的经验连接池管理不要频繁创建/销毁PLC连接而是维护一个连接池。我通常设置5个常驻连接根据负载动态调整。读写分离采集数据和下发控制命令使用不同的连接实例避免互相阻塞。在涂装线项目中这样设计使得控制响应时间从200ms降低到50ms。数据压缩对于需要存储的历史数据采用Delta压缩算法。实测可以将存储空间减少60-70%。public class DataCompressor { public static byte[] Compress(float[] values) { using var output new MemoryStream(); using (var compressor new GZipStream(output, CompressionLevel.Optimal)) { var buffer new byte[values.Length * 4]; Buffer.BlockCopy(values, 0, buffer, 0, buffer.Length); compressor.Write(buffer, 0, buffer.Length); } return output.ToArray(); } }心跳检测除了PLC自带的状态检测我还增加了应用层的心跳包。每30秒向PLC写入特定地址的时间戳超时3次即判定为断线。负载监控在系统托盘显示实时负载指标包括采集频率队列积压数最近错误计数public class PerformanceCounter { private readonly QueueDateTime _timeStamps new(); public double CallsPerSecond { get { var now DateTime.Now; _timeStamps.Enqueue(now); while(_timeStamps.Peek() now.AddSeconds(-1)) { _timeStamps.Dequeue(); } return _timeStamps.Count; } } }