OPC UA C#服务端如何与已加载的.NodeSet2.xml文件交互
以下实现基于Traeger OPC UA Toolkit 7.x版本,适配你正在用的Server.NodeSet.Umati.MachineTool示例框架,所有逻辑可以直接在你现有加载完MachineVision NodeSet2的项目里复用。
前置准备
Traeger的NodeSet2加载器会在项目编译时,根据你引入的Opc.Ua.MachineVision.NodeSet2.xml自动生成强类型静态类,包含所有节点的NodeId、BrowseName、类型定义,不需要手动拼NodeId字符串。命名空间索引不要硬编码,统一通过以下方式获取:
// 在服务端启动完成后调用,拿到MachineVision对应的命名空间索引 ushort visionNsIndex = ServerInternal.NamespaceUris.GetIndex(Opc.Ua.MachineVision.Namespaces.OpcUaMachineVision);
1. 节点定位
两种常用方式,优先用强类型定位,避免拼写错误:
// 方式1:强类型直接获取(推荐,编译期可校验) // 例:获取MachineVisionSystemType定义下的State变量、Trigger方法的NodeId NodeId stateVarNodeId = Opc.Ua.MachineVision.Variables.MachineVisionSystemType_State; NodeId triggerMethodNodeId = Opc.Ua.MachineVision.Methods.MachineVisionSystemType_Trigger; // 你自己实例化的视觉设备对象NodeId,实例化时按你设置的BrowseName生成即可 NodeId visionInstanceNodeId = new NodeId(visionNsIndex, "VisionDevice01"); // 方式2:按相对路径动态查找,适合无强类型的动态节点场景 IList<INode> foundNodes = ServerInternal.NodeManager.Find( context: ServerInternal.SystemContext, nodesToBrowse: new BrowseDescriptionCollection { new BrowseDescription { NodeId = ObjectIds.ObjectsFolder, BrowseDirection = BrowseDirection.Forward, ReferenceTypeId = ReferenceTypeIds.HierarchicalReferences, IncludeSubtypes = true, NodeClassMask = (uint)NodeClass.All, ResultMask = (uint)BrowseResultMask.All } }, relativePath: new RelativePath($"{visionNsIndex}:VisionDevice01.{visionNsIndex}:State"), throwOnError: false ); INode targetNode = foundNodes.FirstOrDefault();
2. 节点值读写
不建议每次业务逻辑触发时主动写值,优先给变量绑定读写回调,客户端访问时自动返回最新值、处理写入请求:
if (ServerInternal.NodeManager.FindNode(ServerInternal.SystemContext, stateVarNodeId) is BaseVariableState stateVar) { // 绑定读回调:客户端读取节点值时自动触发 stateVar.OnSimpleReadValue = (context, node) => { // 替换成你自己的业务逻辑:从实际硬件/业务层拿视觉设备实时状态 return GetVisionDeviceRealtimeState(); }; // 绑定写回调:客户端写入节点值时自动触发 stateVar.OnSimpleWriteValue = (context, node, value) => { try { int targetState = value.GetValueOrDefault<int>(); // 替换成你自己的业务逻辑:把客户端下发的状态值写到实际硬件 SetVisionDeviceState(targetState); return StatusCodes.Good; } catch { return StatusCodes.BadDeviceError; } }; } // 主动读写值的接口(适合业务层主动推送状态更新的场景) // 读 object currentState = ServerInternal.NodeManager.ReadValue(ServerInternal.SystemContext, stateVarNodeId); // 写 ServerInternal.NodeManager.WriteValue( context: ServerInternal.SystemContext, nodeId: stateVarNodeId, value: new DataValue(new Variant((int)MachineVisionState.Executing)) );
3. 方法调用响应(含输入输出参数处理)
直接给加载生成的方法节点绑定OnCall回调即可,输入输出参数的顺序、类型必须和NodeSet2 XML里的定义完全一致:
if (ServerInternal.NodeManager.FindNode(ServerInternal.SystemContext, triggerMethodNodeId) is MethodState triggerMethod) { triggerMethod.OnCall = (context, method, inputArgs, outputArgs) => { // 按NodeSet2定义的顺序读取输入参数 // 例:Trigger方法输入参数依次为触发模式(Int32枚举)、曝光时间(Float) int triggerMode = inputArgs[0].GetValueOrDefault<int>(0); float exposure = inputArgs[1].GetValueOrDefault<float>(10.0f); try { // 替换成你自己的业务逻辑:调用硬件触发拍照 string imgId = TriggerVisionCapture(triggerMode, exposure); int costMs = GetLastCaptureCostMs(); // 按NodeSet2定义的顺序填充输出参数 // 例:输出参数依次为结果图片ID(String)、处理耗时(Int32) outputArgs[0] = new Variant(imgId); outputArgs[1] = new Variant(costMs); return StatusCodes.Good; } catch (Exception ex) { // 业务异常返回对应错误码,客户端可直接捕获 return StatusCodes.BadResourceUnavailable; } }; }
4. 事件触发
先确认事件源节点的EventNotifier属性开启了事件订阅权限(NodeSet2加载生成的类型默认带该配置,自定义实例需要手动设置),然后构造事件实例上报即可:
// 开启事件源的事件订阅权限 if (ServerInternal.NodeManager.FindNode(ServerInternal.SystemContext, visionInstanceNodeId) is BaseObjectState visionObj) { visionObj.EventNotifier = EventNotifiers.SubscribeToEvents; } // 构造VisionResultReadyEvent事件(对应MachineVision NodeSet2定义的事件类型) BaseEventState resultEvent = ServerInternal.NodeManager.CreateEvent( context: ServerInternal.SystemContext, eventTypeId: Opc.Ua.MachineVision.ObjectTypes.VisionResultReadyEventType, sourceNode: visionInstanceNodeId, sourceName: "VisionDevice01", time: DateTime.UtcNow, message: new LocalizedText("Vision inspection completed") ); // 设置事件自定义字段,字段名和NodeSet2里定义的BrowseName一致 resultEvent.SetChildValue( ServerInternal.SystemContext, Opc.Ua.MachineVision.BrowseNames.VisionResultReadyEventType_ResultId, new Variant("RES_20240601_0001") ); resultEvent.SetChildValue( ServerInternal.SystemContext, Opc.Ua.MachineVision.BrowseNames.VisionResultReadyEventType_IsPass, new Variant(true) ); // 上报事件,所有订阅对应事件类型的客户端会实时收到 ServerInternal.NodeManager.ReportEvent(ServerInternal.SystemContext, resultEvent);
常见注意事项
- 枚举类型传值必须转成NodeSet2定义的基础数值类型(默认是Int32),直接传C#枚举对象会触发类型不匹配错误
- 方法参数顺序必须严格和XML定义一致,参数数量不对会直接返回BadArgumentsMissing错误
- 不要硬编码命名空间索引,服务端加载节点集顺序变化会导致索引变动,统一用
NamespaceUris.GetIndex获取
内容的提问来源于stack exchange,提问作者Andrew
相关产品推荐
相关产品推荐

