首页 / 资讯中心 / 文章详情

C#连接KEPServerEX指南:OPC UA与OPC DA读写节点实战

C#连接KEPServerEX指南:OPC UA与OPC DA读写节点实战 ★ FEATURED ARTICLE
做上位机开发的人应该都有体会设备一多、协议一杂直接拿串口或者以太网去怼PLC代码能写到怀疑人生。所以绝大多数项目里都会有一个中间层把五花八门的设备协议统一转换成标准接口供上层程序去读。KEPServerEX就是干这个事的而且是工控圈用得最多的一类软件。而“VS2015使用C#连接KepserverEX并操作读写节点”是几乎所有上位机开发者入门时都要踩一遍的路。这篇文章就只讲这一件事怎么用VS2015写一个C#程序把KEPServerEX里的节点数据读出来再把数据写进去。覆盖两条主流技术路线OPC DA和OPC UA、完整可运行的代码片段、数据节点路径规则、读写时的数据类型坑以及连接失败时最让人头疼的DCOM配置和位数不匹配问题。不管你是刚被领导安排去接数据的应届生还是已经在工控行业摸爬了几年的老兵这篇都值得看完因为里面很多结论是我实际调过的不是抄文档抄出来的。1. 开工前先想清楚选OPC DA还是OPC UAKEPServerEX侧又该怎么配1.1 两条路线的核心差异用C#连接KEPServerEX本质上是写一个OPC客户端。而OPC协议本身分了好几代最常用的就是OPC DA和OPC UA。OPC DA是经典的老牌协议基于Windows的COM/DCOM技术。优点是历史久、资料多几乎所有旧项目都是这么连的缺点也突出配置麻烦尤其是跨机器访问时要配DCOM稍有不慎就会碰到“拒绝访问”或者“类未注册”。而且32位和64位进程之间还经常闹幺蛾子。OPC UA是后来的新一代标准不依赖COM底层走TCP默认端口是49320。连接方式简单得多跨平台能力也强KEPServerEX从第6版开始默认就支持UA。如果你是从零开始的新项目我建议直接走OPC UA能少掉DCOM这一大堆破事。但考虑到很多老项目的上位机还是基于OPC DA开发两种写法我都讲你按自己的实际情况选。提示不管选哪条路线KEPServerEX这一侧都要先把通道、设备、标签准备好。否则客户端就算写对了也什么都没有可连。1.2 KEPServerEX侧的准备动作先说最基本的几步操作。打开KEPServerEX管理界面左侧是项目树右侧一般显示状态和事件。第一步是创建通道。在“项目”上右键选择“添加通道”驱动类型直接选“Simulator”也就是模拟器驱动。这个驱动不用接真实设备内部会自动产生数据变化非常适合测试程序。通道名字我建议用英文比如叫Channel1。第二步是在通道下创建设备。右键Channel1添加设备模型选择“Simulator”。设备名也叫英文比如Device1。第三步是添加标签。在Device1下添加静态标签这里可以新建几个不同类型的标签来测试一个Bool一个Int32一个Float再加一个String。每个标签可以手动设定初始值。做完这三步KEPServerEX这一侧的测试环境就备好了。后续如果接真实PLC只需要把Simulator驱动换成对应的PLC驱动再按照实际点位创建标签即可。只要标签名和路径规则不变C#代码完全不用改。1.3 VS2015工程配置和引用VS2015默认的.NET Framework版本一般是4.6或4.6.1。用OPC UA官方库时要求.NET Framework 4.6.1以上这一步基本能满足。用OPC DA自动化接口时则要用到COM组件稍后我细说。新建项目时建议选择.NET Framework 4.6.1。需要注意的是尽量用“控制台应用程序”做测试界面和逻辑先分开。等测试通过后再封装成类库给WinForm或者WPF用这样写起来干净很多。工程建立后的头等大事是确认目标平台。默认AnyCPU在64位系统上会跑成64位进程这往往会引发OPC DA的位数兼容问题。我的建议是直接改成x86后面再解释为什么。2. C#连接KEPServerEX两种客户端的完整代码实现2.1 基于OPC DA自动化接口的连接与读写OPC DA的C#开发有两种常见方案一种是用OPC自动化接口Interop.OPCAutomation.dll另一种是用老牌的OpcDaNet.dll。这里我重点讲自动化接口方案因为它在旧项目里最常见代码量也少。在VS2015里先添加COM引用项目右键 - 添加引用 - COM选项卡 - 找到“OPC Automation 2.0”。添加成功后工程里会出现Interop.OPCAutomation.dll。连接服务器的代码如下using OPCAutomation; private OPCServer kepServer; public bool Connect() { kepServer new OPCServer(); kepServer.Connect(Kepware.KEPServerEX.V6, ); return true; }Connect方法的两个参数一个服务器ProgID一个主机名。连本机时主机名传空字符串即可。注意V6版本就要写V6V5版本写V5别照抄要看自己装的KEPServerEX版本。连接成功后需要创建组、添加节点。这里有个容易忽略的点读取或写入的“节点”我们需要明确地添加到组里。代码如下private OPCGroup kepGroup; private const string TAG_PATH Channel1.Device1.Int32Tag; public bool AddItems() { kepGroup kepServer.OPCGroups.Add(Group1); kepGroup.UpdateRate 500; kepGroup.IsActive true; kepGroup.IsSubscribed true; Array itemIds new string[] { TAG_PATH }; Array itemHandles new int[] { 1 }; Array errors; kepGroup.OPCItems.AddItems(itemIds.Length, ref itemIds, ref itemHandles, out errors, out _); return true; }读取节点用SyncRead方法public object ReadValue(string itemId) { Array ids new string[] { itemId }; Array errors; int count 1; Array values kepGroup.SyncRead((short)OPCDataSource.OPCDevice, count, ref ids, out errors); return values.GetValue(1); }注意SyncRead返回的是一个二维数组下标从1开始不是0这是VB的底子遗传下来的。第一次写C#的人基本都在这上面吃过亏。写入节点用SyncWritepublic void WriteValue(string itemId, object value) { Array ids new string[] { itemId }; Array values new object[] { value }; Array errors; kepGroup.SyncWrite(1, ref ids, ref values, out errors); }关闭连接时依次释放组和服务器public void Disconnect() { kepGroup null; kepServer.Disconnect(); kepServer null; }这套代码跑通之后读写Simulator标签完全没有问题。但你在真实项目里还要处理一些异常情况比如服务器没启动、标签不存在、值被设备写保护等这些后面统一讲。2.2 基于OPC UA的连接与读写如果你决定不跟DCOM搏斗走OPC UA是更舒服的路。KEPServerEX默认开启UA服务端口是49320。在VS2015里用NuGet包管理器搜索安装Opc.Ua.Core和Opc.Ua.Client。注意选一个兼容.NET Framework 4.6.1的版本太新的包可能会要求.NET Core或者更高版本。连接代码如下using Opc.Ua; using Opc.Ua.Client; private Session session; private readonly string ENDPOINT_URL opc.tcp://127.0.0.1:49320; public bool Connect() { var config new ApplicationConfiguration { ApplicationName CSharp Kepware Client, ApplicationType ApplicationType.Client, SecurityConfiguration new SecurityConfiguration { ApplicationCertificate new CertificateIdentifier(), }, TransportConfigurations new TransportConfigurationCollection(), TransportQuotas new TransportQuotas(), ClientConfiguration new ClientConfiguration() }; var endpoint new ConfiguredEndpoint(null, new EndpointDescription(new Uri(ENDPOINT_URL))); session Session.Create( config, endpoint, false, KepwareSession, 60000, new UserIdentity(new AnonymousIdentityToken()), null).GetAwaiter().GetResult(); return session ! null session.Connected; }这段代码在控制台项目里用起来是没问题的。但要注意Session.Create是异步方法控制台里直接GetAwaiter().GetResult()会方便些。如果是在WinForm或WPF里建议用async/await免得不小心把界面线程卡住。读取UA节点的值核心代码是ReadValuepublic object ReadUaNode(string nodeId) { NodeId tagNode new NodeId(nodeId); ReadValueIdCollection readValues new ReadValueIdCollection { new ReadValueId { NodeId tagNode, AttributeId Attributes.Value } }; ReadResultCollection results; var response session.Read(null, 0, TimestampsToReturn.Both, readValues, out results); if (results null || results.Count 0) { return null; } var status results[0].StatusCode; if (StatusCode.IsBad(status)) { throw new Exception($读取失败: {status}); } return results[0].Value; }这里有一个很关键的细节UA的节点ID格式和OPC DA的ItemID不一样。OPC DA直接用Channel1.Device1.Int32Tag这种字符串而UA节点ID要写成ns2;sChannel1.Device1.Int32Tag其中ns是命名空间索引。KEPServerEX的UA命名空间索引往往不是固定的有疑问时可以在UA配置里查或者用客户端浏览工具找一下。UA写入的代码public void WriteUaNode(string nodeId, object value) { NodeId tagNode new NodeId(nodeId); WriteValue writeValue new WriteValue { NodeId tagNode, AttributeId Attributes.Value, Value new DataValue(new Variant(value)) }; WriteValueCollection writeValues new WriteValueCollection { writeValue }; WriteResultCollection results; var response session.Write(null, writeValues, out results); if (results null || results.Count 0) { throw new Exception(写入响应为空); } var status results[0].StatusCode; if (StatusCode.IsBad(status)) { throw new Exception($写入失败: {status}); } }UA方案最大的优势是不需要配置DCOM也不需要折腾32位、64位跨平台调用也舒服。如果你在写新项目真的建议优先考虑UA。2.3 订阅模式下的事件回调写法前面讲的是同步读写适合轮询或者按钮触发的场景。但实际采集系统里更多时候希望数据一变就立刻推给程序不要每次轮询都浪费网络开销。这时就要用订阅模式。OPC DA的订阅实现思路是把组的IsSubscribed属性设为true然后挂OPCGroup的DataChange事件。当组内的标签值改变时回调会被触发再异步读取组内所有活跃项。kepGroup.DataChange KepGroup_DataChange; private void KepGroup_DataChange(int transactionID, int numItems, ref Array clientHandles, ref Array values, ref Array qualities, ref Array timestamps) { for (int i 1; i numItems; i) { object value values.GetValue(i); int quality Convert.ToInt32(qualities.GetValue(i)); Console.WriteLine($值: {value}, 质量: {quality}); } }质量位很重要它是OPC协议里描述数据可靠性的一组状态码。如果数据质量不是192Good这个数值就算读出来了也别直接用很可能是设备断线或者数据超时。OPC UA的订阅稍微复杂一点涉及Subscription、MonitoredItem和Notification事件。这里给一个简化可用的骨架var subscription new Subscription(session.DefaultSubscription) { PublishingInterval 500, PublishingEnabled true }; session.AddSubscription(subscription); subscription.Create(); var monitorItem subscription.AddItem( new ReadValueId() { NodeId new NodeId(ns2;sChannel1.Device1.Int32Tag), AttributeId Attributes.Value }); monitorItem.Notification (MonitoredItem item, MonitoredItemNotificationEventArgs e) { var value item.LastValue; if (value ! null) { Console.WriteLine($UA订阅值: {value.Value}); } }; subscription.ApplyChanges();注意订阅频率PublishingInterval不能设得太小否则KEPServerEX和上位机会负载过高。我一般设500ms左右设备侧模拟量变化完全够用。3. 读写节点的核心细节路径、类型和权限3.1 节点路径命名规则前面已经提到过KEPServerEX的标签路径遵循“通道名.设备名.标签名”的规则比如Channel1.Device1.Int32Tag。但这里有几个细节要特别注意标签名称区分大小写吗在KEPServerEX里默认不强制区分但我建议从上到下全部统一用小驼峰或者大写避免从VB程序转过来的老项目里命名风格混乱。如果标签是数组或者结构体路径规则又会变。比如数组标签的访问方式是Channel1.Device1.ArrayTag[0]。结构体标签则是Channel1.Device1.StructTag.Member1。对于OPC UA来说还要加命名空间前缀。写死之前先看看KEPServerEX管理界面右侧属性栏里的“Node ID”是什么。另外不要靠猜打开KEPServerEX自带的“Quick Client”工具可以浏览到具体的OPC ItemID。这个工具的用法就是连上本地服务然后展开树找到对应标签右键查看属性里面会直接告诉你这个节点完整的ItemID或NodeID。先复制到代码里再把代码粘到实际程序里运行。这套流程比人肉拼字符串稳妥得多。3.2 读出来的数据到底是个什么类型这是新手最晕的地方之一。KEPServerEX里的标签类型有很多种Boolean、Word、DWord、Int16、Int32、Float、Double、String还有时间日期类型。C#程序读出来以后值是object类型的得自己转成目标类型。举几个实际转换的例子bool boolValue (bool)result; int intValue Convert.ToInt32(result); float floatValue Convert.ToSingle(result); string strValue result.ToString();为什么用Convert.ToInt32而不是强转int因为OPC服务器返回的实际类型可能是short、long或者uint直接强制转换可能抛出InvalidCastException。用Convert做类型转换兼容性最好。还有个经常被忽略的类型坑读出来的浮点数可能是float也可能是double读出来的整数可能是int也可能是short。尤其在PLC编程里很多地方用小整数短整型。建议统一用Convert类中转而不是盲目强转。3.3 写操作的类型对齐与授权细节写入比读取更容易踩坑因为写入一次不成功往往直接影响生产流程。类型对齐不用多说读是Int32的你写一个double过去大概率会报错。但这里还有一个KEPServerEX特有的“写入权限”问题如果标签的访问模式被设成“只读”那么客户端写入时会返回错误码“权限不足”或者直接不响应。在KEPServerEX里创建标签时默认所有标签都是可读可写的但真实PLC点位不一定允许你写有的点位是设备侧只读的。这一点在现场集成时最好提前和电气确认好能写才写。另外OPC DA自动化接口的SyncWrite有一点很坑它要求传入object[,]二维数组或者一维数组但下标从1开始。写数组标签时尤其容易下标越界。建议封装方法时统一把数组补一个“哨兵”元素比如想写3个值就声明new object[4]从索引1开始填数据索引0随便填一个null。这在老代码里其实是默认操作新人不注意就会懵半天。4. 连接失败和读写异常一次讲清楚排查套路4.1 远端无法连接DCOM配置与防火墙如果KEPServerEX安装在另一台电脑上OPC DA的远程连接就绕不开DCOM。这也是很多项目里最耗时间的环节没有之一。DCOM配置主要干三件事在Windows组件服务里找到KEPServerEX对应的DCOM配置项把该配置项的“身份验证级别”设为“无”开发环境下可以这么做生产环境建议配合安全策略看情况在“安全”选项卡里给Everyone或者ANONYMOUS LOGON用户组加上启动、访问权限。同时还要在防火墙里放行135端口以及动态RPC端口。DCOM默认使用动态端口这个在Windows防火墙里是比较麻烦的。更稳妥的办法是在KEPServerEX所在机器上把防火墙关闭来测试连通性确认问题一定出在端口后再精细化开放端口。我自己调试时的顺序是先在本机用Quick Client连KEPServerEX排除KEPServerEX自身问题再用本机C#客户端连排除程序问题最后才上远程客户端这时候如果再失败基本就是DCOM或者防火墙的问题。这个顺序能避免把时间浪费在不同环节间的猜疑上。OPC UA就没这么多麻烦只要网络能通端口49320能访问配置好证书或匿名账号就行。KEPServerEX默认情况下匿名连接可能是允许的如果连不上去KEPServerEX的“UA服务器配置”里检查一下用户管理和端点配置。4.2 32位与64位的隐形坑这个坑我当初也踩过。在64位系统上用VS2015默认配置跑OPC DA自动化接口的程序一运行就报“检索 COM 类工厂中 CLSID 为...的组件时失败”或者报“类未注册”。原因很简单Interop.OPCAutomation.dll这个COM组件是32位的而64位进程无法加载32位进程外组件所以必然失败。解决方案也很直接把生成平台从AnyCPU改成x86。右键项目 - 属性 - 生成 - 平台目标改为x86。改完再跑你会发现在64位系统上连接KEPServerEX V6也完全正常因为KEPServerEX本身的OPC DA服务器进程是32位的完全可以和32位客户端通信。用OPC UA就没有这个位数问题托管代码跨位数跑都没有关系。这也是我推荐UA的另一个重要原因。4.3 常见问题排查速查表整理了一份排查表基本都是我这几年反复遇到的情况现象可能原因解决思路连接时报类未注册或CLSID失败ProgID写错、COM组件位数不匹配检查KEPServerEX版本号确认V6还是V5平台目标改x86连接时报拒绝访问DCOM权限配置不足dcomcnfg里给用户加启动与访问权限读出来的值一直是nullItemID路径写错、标签未激活、质量位不是好值用Quick Client核对节点ID检查标签“活动状态”写入无反应标签只读、写入类型不匹配、KEPServerEX安全配置限制检查标签属性、转换正确数据类型、检查UA用户权限订阅回调不触发组未激活、订阅未启动、UpdateRate过小、事件挂载失败检查IsActive、IsSubscribed、PublishingEnabled远程UA连不上UA端点未启用、防火墙拦截49320、匿名登录被禁止检查KEPServerEX UA配置开放端口配置账号或启用匿名数据刷新很慢更新速率太大、网络阻塞、服务器负载高调小UpdateRate/PublishingInterval用订阅替代轮询主动请求堆积轮询过快造成服务器过载适当增加轮询间隔优先使用订阅模式这张表基本覆盖了90%的现场问题。如果你遇到的问题不在表里多半是环境层面的比如Windows补丁更新导致DCOM认证策略收紧、杀毒软件拦截了RPC动态端口等。这种问题只能靠抓取Windows事件日志和WireShark逐步排查了。5. 我最后还要再念叨几句做了这些年数据采集项目我对KEPServerEX的感觉是它本身很稳定绝大多数问题都出在连接方式、运行环境和点位管理上。如果你还在犹豫用哪种协议我的建议很简单新项目一律用UA维护老项目该用DA就用DA但要有心理准备花时间处理DCOM和各种位数问题。再说一个很多人都容易忽略的小习惯在写C#客户端之前把所有点位先在KEPServerEX的Quick Client里全部读写测试一遍。这个动作只需要十几分钟却能省掉后期联调时几天的排查时间。很多所谓的“C#连接不上”的问题其实在Quick Client里就暴露了比如标签名错了、设备没激活、驱动通道ID不对。先验证底层通道通畅再排查代码。最后代码封装尽量做成一个单独的类库把OPC连接、读取、写入、订阅、断线重连都封装好别在界面代码里直接到处裸写OPC调用。我见过太多项目点一下按钮就去OPC连接或者读写结果一会儿连接没释放一会儿异常没捕获整个上位机到了现场天天崩。给OPC访问加一个统一的连接管理器断线自动重连这才是能长期稳定跑在产线上的代码该有的样子。这些经验不算什么高深理论全是拿时间和现场故障换来的。希望这篇东西能帮你少走点弯路。
阅读完成 · 觉得有帮助?
咨询建站