如何在C#中合并Opc UA订阅以解决BadTooManySubscriptions错误
解决S7-1200 PLC OPC UA订阅数量超限问题
问题描述
我用C#编写小型HMI程序,需要订阅约200个西门子S7-1200 PLC的标签。目前每个标签单独创建订阅,超过5个就触发Opc.Ua.ServiceResultException: 'BadTooManySubscriptions'错误。我刚接触C#,知道要把订阅合并成一个,但找不到具体实现代码。
以下是当前的错误代码(注释掉5个以外的订阅就能正常获取数据):
try { myClient.AddSubscription("IDODChuck1", "ns=4;i=203", SubCallback); myClient.AddSubscription("IDODChuck2", "ns=4;i=204", SubCallback); myClient.AddSubscription("HMI_High_Thresh_Chuck1", "ns=4;i=9", SubCallback); myClient.AddSubscription("Calculated1VOLTS", "ns=4;i=148", SubCallback); myClient.AddSubscription("Calculated2VOLTS", "ns=4;i=150", SubCallback); myClient.AddSubscription("Arrow2", "ns=4;i=149", SubCallback); myClient.AddSubscription("B", "ns=4;i=150", SubCallback); myClient.AddSubscription("HMI_High_Thresh_Chuck1", "ns=4;i=9", SubCallback); myClient.AddSubscription("Calculated1VOLTS", "ns=4;i=154", SubCallback); myClient.AddSubscription("Chuck1Nominal", "ns=4;i=2", SubCallback); myClient.AddSubscription("Chuck2Nominal", "ns=4;i=3", SubCallback); myClient.AddSubscription("CurHMIMeasureDiaLPS1", "ns=4;i=4", SubCallback); myClient.AddSubscription("CurHMIMeasureDiaLPS2", "ns=4;i=5", SubCallback); myClient.AddSubscription("CurHMIMeasureRadLPS1", "ns=4;i=6", SubCallback); myClient.AddSubscription("CurHMIMeasureRadLPS2", "ns=4;i=7", SubCallback); myClient.AddSubscription("CurrentChuckScreen", "ns=4;i=8", SubCallback); myClient.AddSubscription("HMIClmpHighThreshArrowChuck1", "ns=4;i=9", SubCallback); myClient.AddSubscription("HMIClmpHighThreshArrowChuck2", "ns=4;i=10", SubCallback); myClient.AddSubscription("HMIClmpLowThreshArrowChuck1", "ns=4;i=11", SubCallback); myClient.AddSubscription("HMIClmpLowThreshArrowChuck2", "ns=4;i=12", SubCallback); myClient.AddSubscription("HMICloseEOSLineChuck1", "ns=4;i=13", SubCallback); myClient.AddSubscription("HMICloseEOSLineChuck2", "ns=4;i=14", SubCallback); myClient.AddSubscription("HMIClampHighThreshChuck1", "ns=4;i=15", SubCallback); myClient.AddSubscription("HMIClampHighThreshChuck2", "ns=4;i=16", SubCallback); myClient.AddSubscription("HMIClampLowThreshChuck1", "ns=4;i=17", SubCallback); myClient.AddSubscription("HMIClampLowThreshChuck2", "ns=4;i=18", SubCallback); myClient.AddSubscription("HMIUnClampThreshChuck1", "ns=4;i=19", SubCallback); myClient.AddSubscription("HMIUnClampThreshChuck2", "ns=4;i=20", SubCallback); myClient.AddSubscription("PlusClampHighThreshChuck1", "ns=4;i=35", SubCallback); myClient.AddSubscription("PlusClampHighThreshChuck2", "ns=4;i=36", SubCallback); myClient.AddSubscription("PlusClampLowThreshChuck1", "ns=4;i=37", SubCallback); myClient.AddSubscription("PlusClampLowThreshChuck2", "ns=4;i=38", SubCallback); myClient.AddSubscription("PlusUnClampThreshChuck1", "ns=4;i=39", SubCallback); myClient.AddSubscription("PlusUnClampThreshChuck2", "ns=4;i=40", SubCallback); myClient.AddSubscription("ApplyTolereanceLPS1", "ns=4;i=41", SubCallback); myClient.AddSubscription("ApplyTolereanceLPS2", "ns=4;i=42", SubCallback); myClient.AddSubscription("MinusClampHighThreshChuck1", "ns=4;i=49", SubCallback); myClient.AddSubscription("MinusClampHighThreshChuck2", "ns=4;i=50", SubCallback); myClient.AddSubscription("MinusClampLowThreshChuck1", "ns=4;i=51", SubCallback); myClient.AddSubscription("MinusClampLowThreshChuck2", "ns=4;i=52", SubCallback); myClient.AddSubscription("CLOSEEOSONLPS1", "ns=4;i=55", SubCallback); myClient.AddSubscription("CLOSEEOSONLPS2", "ns=4;i=56", SubCallback); myClient.AddSubscription("ENBLClmpConfChuck1", "ns=4;i=57", SubCallback); myClient.AddSubscription("ENBLClmpConfChuck2", "ns=4;i=58", SubCallback); myClient.AddSubscription("ENBLUnClmpConfChuck1", "ns=4;i=69", SubCallback); myClient.AddSubscription("ENBLUnClmpConfChuck2", "ns=4;i=70", SubCallback); myClient.AddSubscription("LPS1Online", "ns=4;i=79", SubCallback); myClient.AddSubscription("LPS2Online", "ns=4;i=81", SubCallback); myClient.AddSubscription("MinusUnClampThreshChuck1", "ns=4;i=103", SubCallback); myClient.AddSubscription("MinusUnClampThreshChuck2", "ns=4;i=104", SubCallback); myClient.AddSubscription("ChangeTexttoEOSLPS1", "ns=4;i=123", SubCallback); myClient.AddSubscription("ChangeTexttoEOSLPS2", "ns=4;i=124", SubCallback); myClient.AddSubscription("HMIClampLightChuck1", "ns=4;i=127", SubCallback); myClient.AddSubscription("HMIClampLightChuck2", "ns=4;i=128", SubCallback); myClient.AddSubscription("HMIOTLightChuck1", "ns=4;i=131", SubCallback); myClient.AddSubscription("HMIOTLightChuck2", "ns=4;i=132", SubCallback); myClient.AddSubscription("HMIUnClampLightChuck1", "ns=4;i=133", SubCallback); myClient.AddSubscription("HMIUnClampLightChuck2", "ns=4;i=134", SubCallback); myClient.AddSubscription("LowClmpHigherthanHighChuck1", "ns=4;i=137", SubCallback); myClient.AddSubscription("LowClmpHigherthanHighChuck2", "ns=4;i=138", SubCallback); myClient.AddSubscription("ShowCANNOTTEACHmsg", "ns=4;i=139", SubCallback); myClient.AddSubscription("ShowTeachUnclamLPS1", "ns=4;i=140", SubCallback); myClient.AddSubscription("ShowTeachUnclamLPS2", "ns=4;i=141", SubCallback); myClient.AddSubscription("TeachIDODSameTimeLPS1", "ns=4;i=142", SubCallback); myClient.AddSubscription("TeachIDODSameTimeLPS2", "ns=4;i=143", SubCallback); myClient.AddSubscription("Calculated1VOLTS", "ns=4;i=154", SubCallback); myClient.AddSubscription("Calculated2VOLTS", "ns=4;i=155", SubCallback); myClient.AddSubscription("RetentiveMemoryMaintUnclmpClampChuck1", "ns=4;i=191", SubCallback); myClient.AddSubscription("RetentiveMemoryMaintUnclmpOTChuck1", "ns=4;i=192", SubCallback); myClient.AddSubscription("RetentiveMemoryMaintUnclmpClampChuck2", "ns=4;i=193", SubCallback); myClient.AddSubscription("RetentiveMemoryMaintUnclmpOTChuck2", "ns=4;i=194", SubCallback); myClient.AddSubscription("HMIODCLMPNOMINPUTLPS1", "ns=4;i=195", SubCallback); myClient.AddSubscription("HMIODCLMPNOMINPUTLPS2", "ns=4;i=196", SubCallback); myClient.AddSubscription("HMIODUNCLMPNOMINPUTLPS1", "ns=4;i=197", SubCallback); myClient.AddSubscription("HMIODUNCLMPNOMINPUTLPS2", "ns=4;i=198", SubCallback); myClient.AddSubscription("HMIIDCLMPNOMINPUTLPS1", "ns=4;i=199", SubCallback); myClient.AddSubscription("HMIIDCLMPNOMINPUTLPS2", "ns=4;i=200", SubCallback); myClient.AddSubscription("HMIIDUNCLMPNOMINPUTLPS1", "ns=4;i=201", SubCallback); myClient.AddSubscription("HMIIDUNCLMPNOMINPUTLPS2", "ns=4;i=202", SubCallback); myClient.AddSubscription("IDODChuck1", "ns=4;i=203", SubCallback); myClient.AddSubscription("IDODChuck2", "ns=4;i=204", SubCallback); } catch (Exception) { }
解决方案:合并订阅为单个订阅下的多个监控项
1. 定义标签信息类(可选,方便管理)
先创建一个类存储标签名称和节点ID,便于批量管理:
public class TagInfo { public string Name { get; set; } public string NodeId { get; set; } }
2. 批量创建监控项并添加到单个订阅
核心思路是创建单个Subscription,然后向其中添加多个MonitoredItem,示例代码如下:
try { // 1. 整理所有需要订阅的标签列表 var tags = new List<TagInfo> { new TagInfo { Name = "IDODChuck1", NodeId = "ns=4;i=203" }, new TagInfo { Name = "IDODChuck2", NodeId = "ns=4;i=204" }, new TagInfo { Name = "HMI_High_Thresh_Chuck1", NodeId = "ns=4;i=9" }, new TagInfo { Name = "Calculated1VOLTS", NodeId = "ns=4;i=148" }, new TagInfo { Name = "Calculated2VOLTS", NodeId = "ns=4;i=150" }, // 补充剩余所有标签... new TagInfo { Name = "HMIIDUNCLMPNOMINPUTLPS2", NodeId = "ns=4;i=202" } }; // 2. 创建单个订阅 var subscription = new Subscription(myClient.Session) { PublishingInterval = 500, // 发布间隔,根据需求调整 Enabled = true }; // 3. 为每个标签创建监控项并加入订阅 foreach (var tag in tags) { var monitoredItem = new MonitoredItem(subscription.DefaultItem) { DisplayName = tag.Name, StartNodeId = new NodeId(tag.NodeId) }; // 绑定回调,传递标签名称以便区分数据 monitoredItem.Notification += (sender, e) => SubCallback(tag.Name, e); subscription.AddItem(monitoredItem); } // 4. 激活订阅 myClient.Session.AddSubscription(subscription); subscription.Create(); } catch (Exception ex) { // 建议打印错误信息方便调试 Console.WriteLine($"订阅创建失败:{ex.Message}"); }
3. 调整回调函数(适配多标签)
修改原回调函数,支持区分不同标签的数据更新:
private void SubCallback(string tagName, NotificationEventArgs e) { foreach (var value in e.NotificationValue) { if (value is DataValue dataValue) { Console.WriteLine($"标签{tagName}更新:{dataValue.Value}"); // 根据标签名称处理对应的数据更新逻辑 } } }
关键说明
- 单个订阅+多监控项:OPC UA规范中,一个订阅可包含多个监控项,这是批量订阅的正确方式,能规避PLC的订阅数量限制。
- S7-1200限制:西门子S7-1200的OPC UA服务器默认对客户端订阅数量限制较严(通常为5个),必须合并成单个订阅下的多监控项。
- 清理重复标签:原代码存在重复订阅的标签(如多次订阅
IDODChuck1),建议先去重,避免资源浪费。
内容的提问来源于stack exchange,提问作者Zen501
相关产品推荐
相关产品推荐

