1. 项目背景与核心需求
在工业自动化领域,上位机与PLC的通信监控一直是刚需场景。我最近完成了一个用C#开发的上位机APP,专门用于监控西门子S7-1200 PLC的运行状态。这个项目源于某生产线改造需求——原有系统缺乏移动端监控能力,工程师必须守在工控机前才能查看设备状态。
西门子S7-1200作为中小型PLC的典型代表,在制造业应用广泛。但官方提供的监控方案往往价格昂贵且灵活性不足。通过自研上位机,我们实现了:
- 实时读取PLC寄存器数据(如I/O状态、定时器值)
- 历史数据存储与分析
- 异常状态手机推送告警
- 远程参数调整(需授权)
关键点:选择C#而非组态软件开发,主要考虑后期需要与企业MES系统深度集成,以及跨平台部署的需求(通过MAUI实现iOS/Android支持)
2. 开发环境搭建
2.1 硬件准备
- 西门子S7-1200 PLC(固件版本V4.5以上)
- 工业级交换机(推荐使用带端口镜像功能的型号,便于抓包调试)
- 测试用DI/DO模块(如SM1221)
2.2 软件工具链
bash复制Visual Studio 2019 Community(必需组件:
- .NET Desktop Development
- Mobile development with .NET
- NuGet包管理器)
关键NuGet包:
xml复制<S7NetPlus>2.0.0</S7NetPlus> // PLC通信库
<LiveCharts2>2.0.0</LiveCharts2> // 实时曲线绘制
<MQTTnet>3.1.2</MQTTnet> // 手机推送
2.3 网络配置要点
-
PLC侧设置:
- 启用"允许来自远程对象的PUT/GET通信"
- 配置IP地址与子网掩码(需与上位机同网段)
- 建议关闭防火墙或添加端口例外(默认TCP 102)
-
调试技巧:
- 使用Wireshark过滤
s7comm协议包 - 先通过TIA Portal的"监控与强制表"功能验证PLC基础通信
- 使用Wireshark过滤
3. 通信协议实现细节
3.1 S7协议报文解析
西门子私有协议的核心是TPKT+ISO-COTP+S7三层封装。实际开发中我们主要处理:
- 读请求:Item结构体中的Address需要按字节偏移计算
csharp复制// 读取DB1.DBW20开始的10个字
var address = new DataItemAddress {
Area = DataItemAddress.DataArea.DB,
DbNumber = 1,
StartByte = 20,
Amount = 10,
WordLength = DataItemAddress.WordLength.TwoBytes
};
3.2 性能优化实践
- 批量读取:单次请求最多读取440字节(S7-1200限制)
- 异步处理:避免UI线程阻塞
csharp复制async Task UpdatePlcDataAsync()
{
while (!cts.IsCancellationRequested)
{
var values = await plc.ReadBytesAsync(DataType.DataBlock, 1, 0, 20);
// 更新UI...
await Task.Delay(200); // 采样间隔
}
}
- 错误重试机制:
mermaid复制graph TD
A[发起请求] --> B{成功?}
B -->|是| C[处理数据]
B -->|否| D[记录错误计数]
D --> E{计数>3?}
E -->|否| A
E -->|是| F[触发告警]
实测发现:当网络延迟>100ms时,需要调整PLC的"通信负载"参数(默认为50%)
4. 手机端关键技术实现
4.1 跨平台方案选型
对比了三种方案:
| 方案 | 优点 | 缺点 |
|---|---|---|
| Xamarin | 原生性能 | 学习曲线陡峭 |
| MAUI | 单代码库 | 控件库较少 |
| Blazor Hybrid | 前端生态丰富 | 调试复杂 |
最终选择MAUI+Shell导航模式,关键页面布局:
xml复制<ContentPage>
<Grid>
<VerticalStackLayout>
<Label Text="{Binding PlcStatus}"
Style="{StaticResource StatusStyle}"/>
<Button Command="{Binding RefreshCommand}"
Text="强制刷新"/>
</VerticalStackLayout>
<ActivityIndicator IsRunning="{Binding IsBusy}"
VerticalOptions="Center"/>
</Grid>
</ContentPage>
4.2 后台服务保活
Android端需要:
- 添加WAKE_LOCK权限
- 实现ForegroundService
csharp复制[Service(ForegroundServiceType = ForegroundService.TypeDataSync)]
public class PlcBackgroundService : Service
{
public override StartCommandResult OnStartCommand(...)
{
var notification = new NotificationCompat.Builder(...)
.SetContentTitle("PLC监控中")
.Build();
StartForeground(1001, notification);
// 启动轮询任务...
}
}
iOS端则需在Info.plist中添加:
xml复制<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
</array>
5. 典型问题排查实录
5.1 连接超时问题
现象:首次连接成功率仅60%
排查过程:
- 用Ping测试基础网络(正常)
- 抓包发现TCP三次握手成功后,PLC未响应S7协商包
- 对比TIA Portal的通信过程,发现缺少COTP连接请求
- 最终定位到S7NetPlus库的Connect()需要先调用Open()
修正代码:
csharp复制// 错误写法
plc.Connect();
// 正确写法
await plc.OpenAsync();
await plc.ConnectAsync();
5.2 数据错位问题
当读取DB块中大于200字节的数据时,偶尔会出现前几个字节为0的情况。通过以下方法确认是PLC端OB块执行周期过长导致:
- 在TIA Portal中监控PLC循环时间
- 添加OB35中断组织块
- 上位机增加校验和检查
优化后的读取策略:
csharp复制// 先读前4字节校验
var header = await plc.ReadBytesAsync(DataType.DataBlock, 1, 0, 4);
if (header[0] != 0xAA || header[1] != 0x55)
throw new InvalidDataException();
// 再读剩余数据
var data = await plc.ReadBytesAsync(DataType.DataBlock, 1, 4, length);
6. 安全增强措施
6.1 通信加密
虽然S7协议本身未加密,但我们通过:
- TLS隧道代理(使用Stunnel)
- 应用层AES加密关键参数
csharp复制string EncryptWriteValue(int address, float value)
{
var payload = $"{address}:{value}";
using var aes = Aes.Create();
// ...初始化密钥等
return Convert.ToBase64String(
aes.EncryptCbc(Encoding.UTF8.GetBytes(payload), iv));
}
6.2 权限控制
实现基于角色的访问控制:
csharp复制[Authorize(Roles = "Engineer")]
public async Task WriteParameterAsync(int address, float value)
{
// 只有工程师角色可以写参数
}
在PLC端配合设置:
- 给上位机账户分配"基本权限+HMI访问"
- 关键DB块设置为"只读"
7. 项目优化方向
-
协议扩展:目前仅实现S7基本通信,后续可加入:
- 事件订阅(通过S7-1200的WebAPI)
- 批量写入的流水线优化
-
诊断功能:
csharp复制// 读取PLC诊断缓冲区 var diag = await plc.ReadSzlAsync(SzlId.CpuDiagnosticData); -
离线缓存:使用SQLite存储最近24小时数据,网络中断时仍可查看历史趋势
-
UI改进:基于SkiaSharp实现仿TIA Portal的HMI元件库
这个项目让我深刻体会到工业软件开发的特殊性——比起纯技术实现,更重要的是理解产线实际需求。比如最初设计的自动重连机制,在客户现场反而导致故障被掩盖,后来改为三次重试后必须人工确认。
