diff --git a/.vs/IoTGateway/DesignTimeBuild/.dtbcache.v2 b/.vs/IoTGateway/DesignTimeBuild/.dtbcache.v2 index 919d74d6..a4508958 100644 Binary files a/.vs/IoTGateway/DesignTimeBuild/.dtbcache.v2 and b/.vs/IoTGateway/DesignTimeBuild/.dtbcache.v2 differ diff --git a/.vs/IoTGateway/v16/.suo b/.vs/IoTGateway/v16/.suo index 4d51e5c3..e4544144 100644 Binary files a/.vs/IoTGateway/v16/.suo and b/.vs/IoTGateway/v16/.suo differ diff --git a/Dockerfile-win b/Dockerfile-win deleted file mode 100644 index 0b7e5156..00000000 --- a/Dockerfile-win +++ /dev/null @@ -1,35 +0,0 @@ -#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. - -FROM mcr.microsoft.com/dotnet/aspnet:5.0-buster-slim AS base -WORKDIR /app -EXPOSE 518 -EXPOSE 1888 - - -FROM mcr.microsoft.com/dotnet/sdk:5.0-buster-slim AS build -WORKDIR /src - -COPY ["IoTGateway/IoTGateway.csproj", "IoTGateway/"] -COPY ["IoTGateway.ViewModel/IoTGateway.ViewModel.csproj", "IoTGateway.ViewModel/"] -COPY ["Plugins/Plugin/Plugin.csproj", "Plugins/Plugin/"] -COPY ["IoTGateway.Model/IoTGateway.Model.csproj", "IoTGateway.Model/"] -COPY ["WalkingTec.Mvvm/WalkingTec.Mvvm.Core/WalkingTec.Mvvm.Core.csproj", "WalkingTec.Mvvm/WalkingTec.Mvvm.Core/"] -COPY ["Plugins/PluginInterface/PluginInterface.csproj", "Plugins/PluginInterface/"] -COPY ["IoTGateway.DataAccess/IoTGateway.DataAccess.csproj", "IoTGateway.DataAccess/"] -COPY ["WalkingTec.Mvvm/WalkingTec.Mvvm.TagHelpers.LayUI/WalkingTec.Mvvm.TagHelpers.LayUI.csproj", "WalkingTec.Mvvm/WalkingTec.Mvvm.TagHelpers.LayUI/"] -COPY ["WalkingTec.Mvvm/WalkingTec.Mvvm.Mvc/WalkingTec.Mvvm.Mvc.csproj", "WalkingTec.Mvvm/WalkingTec.Mvvm.Mvc/"] - -RUN dotnet restore "IoTGateway/IoTGateway.csproj" -COPY . . -WORKDIR "/src/IoTGateway" -RUN dotnet build "IoTGateway.csproj" -c Release -o /app/build - -FROM build AS publish -RUN dotnet publish "IoTGateway.csproj" -c Release -o /app/publish - -FROM base AS final -WORKDIR /app -COPY --from=publish /app/publish . - -ENV TZ=Asia/Shanghai -ENTRYPOINT ["dotnet", "IoTGateway.dll"] \ No newline at end of file diff --git a/IoTGateway/Areas/BasicData/Views/DeviceVariable/Index.cshtml b/IoTGateway/Areas/BasicData/Views/DeviceVariable/Index.cshtml index c026a2f1..559b26e6 100644 --- a/IoTGateway/Areas/BasicData/Views/DeviceVariable/Index.cshtml +++ b/IoTGateway/Areas/BasicData/Views/DeviceVariable/Index.cshtml @@ -51,10 +51,10 @@ }, 1500); //状态 - $('#id' + objmsg.VarId + '_StatusType').text(objmsg.StatusType); - $('#id' + objmsg.VarId + '_StatusType').addClass('animated bounceIn'); + $('#id' + objmsg.VarId + '_State').text(objmsg.StatusType); + $('#id' + objmsg.VarId + '_State').addClass('animated bounceIn'); setTimeout(function(){ - $('#id' + objmsg.VarId + '_StatusType').removeClass('bounceIn'); + $('#id' + objmsg.VarId + '_State').removeClass('bounceIn'); }, 1500); }) } diff --git a/IoTGateway/iotgateway.db b/IoTGateway/iotgateway.db index d0775f58..d63865c0 100644 Binary files a/IoTGateway/iotgateway.db and b/IoTGateway/iotgateway.db differ diff --git a/Plugins/Drivers/DriverOPCUaClient/DriverOPCUaClient.csproj b/Plugins/Drivers/DriverOPCUaClient/DriverOPCUaClient.csproj index 0efbbfa6..92d7e016 100644 --- a/Plugins/Drivers/DriverOPCUaClient/DriverOPCUaClient.csproj +++ b/Plugins/Drivers/DriverOPCUaClient/DriverOPCUaClient.csproj @@ -8,8 +8,6 @@ - - diff --git a/Plugins/Drivers/DriverOPCUaClient/OPCUaClient.cs b/Plugins/Drivers/DriverOPCUaClient/OPCUaClient.cs index 249fb5b0..5861d759 100644 --- a/Plugins/Drivers/DriverOPCUaClient/OPCUaClient.cs +++ b/Plugins/Drivers/DriverOPCUaClient/OPCUaClient.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Threading.Tasks; using Opc.Ua.Configuration; +using OpcUaHelper; namespace DriverOPCUaClient { @@ -14,9 +15,7 @@ namespace DriverOPCUaClient [DriverInfoAttribute("OPCUaClient", "V1.0.0", "Copyright WHD© 2021-12-19")] public class OPCUaClient : IDriver { - Session session = null; - ApplicationConfiguration config = null; - ConfiguredEndpoint endpoint = null; + OpcUaClientHelper opcUaClient = null; #region 配置参数 [ConfigParameter("设备Id")] @@ -37,18 +36,7 @@ public OPCUaClient(Guid deviceId) { DeviceId = deviceId; - ApplicationInstance application = new ApplicationInstance - { - ApplicationName = "ConsoleReferenceClient", - ApplicationType = ApplicationType.Client, - ConfigSectionName = "Quickstarts.ReferenceClient", - CertificatePasswordProvider = new CertificatePasswordProvider(null) - }; - config = application.LoadApplicationConfiguration(silent: false).Result; - - EndpointDescription endpointDescription = CoreClientUtils.SelectEndpoint(application.ApplicationConfiguration, Uri, false); - EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create(application.ApplicationConfiguration); - endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration); + opcUaClient = new OpcUaClientHelper(); } @@ -57,7 +45,7 @@ public bool IsConnected get { - return session != null && session.Connected; + return opcUaClient != null && opcUaClient.Connected; } } @@ -65,7 +53,7 @@ public bool Connect() { try { - session = Session.Create(config, endpoint, false, false, config.ApplicationName, 30 * 60 * 1000, new UserIdentity(), null).Result; + opcUaClient.ConnectServer(Uri).Wait((int)Timeout); } catch (Exception) { @@ -78,7 +66,7 @@ public bool Close() { try { - session?.Close(); + opcUaClient?.Disconnect(); return !IsConnected; } catch (Exception) @@ -92,8 +80,7 @@ public void Dispose() { try { - session?.Dispose(); - session = null; + opcUaClient = null; } catch (Exception) { @@ -111,7 +98,7 @@ public DriverReturnValueModel ReadNode(DriverAddressIoArgModel ioarg) { try { - var dataValue = session.ReadValue(new NodeId(ioarg.Address)); + var dataValue = opcUaClient.ReadNode(new NodeId(ioarg.Address)); if (DataValue.IsGood(dataValue)) ret.Value = dataValue.Value; } diff --git a/Plugins/Drivers/DriverOPCUaClient/OpcUaHelper/FormUtils.cs b/Plugins/Drivers/DriverOPCUaClient/OpcUaHelper/FormUtils.cs new file mode 100644 index 00000000..ff766d7b --- /dev/null +++ b/Plugins/Drivers/DriverOPCUaClient/OpcUaHelper/FormUtils.cs @@ -0,0 +1,1021 @@ +using Opc.Ua; +using Opc.Ua.Client; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpcUaHelper +{ + /// + /// 辅助类 + /// + public class FormUtils + { + /// + /// Gets the display text for the access level attribute. + /// + /// The access level. + /// The access level formatted as a string. + private static string GetAccessLevelDisplayText( byte accessLevel ) + { + StringBuilder buffer = new StringBuilder( ); + + if (accessLevel == AccessLevels.None) + { + buffer.Append( "None" ); + } + + if ((accessLevel & AccessLevels.CurrentRead) == AccessLevels.CurrentRead) + { + buffer.Append( "Read" ); + } + + if ((accessLevel & AccessLevels.CurrentWrite) == AccessLevels.CurrentWrite) + { + if (buffer.Length > 0) + { + buffer.Append( " | " ); + } + + buffer.Append( "Write" ); + } + + if ((accessLevel & AccessLevels.HistoryRead) == AccessLevels.HistoryRead) + { + if (buffer.Length > 0) + { + buffer.Append( " | " ); + } + + buffer.Append( "HistoryRead" ); + } + + if ((accessLevel & AccessLevels.HistoryWrite) == AccessLevels.HistoryWrite) + { + if (buffer.Length > 0) + { + buffer.Append( " | " ); + } + + buffer.Append( "HistoryWrite" ); + } + + if ((accessLevel & AccessLevels.SemanticChange) == AccessLevels.SemanticChange) + { + if (buffer.Length > 0) + { + buffer.Append( " | " ); + } + + buffer.Append( "SemanticChange" ); + } + + return buffer.ToString( ); + } + + /// + /// Gets the display text for the event notifier attribute. + /// + /// The event notifier. + /// The event notifier formatted as a string. + private static string GetEventNotifierDisplayText( byte eventNotifier ) + { + StringBuilder buffer = new StringBuilder( ); + + if (eventNotifier == EventNotifiers.None) + { + buffer.Append( "None" ); + } + + if ((eventNotifier & EventNotifiers.SubscribeToEvents) == EventNotifiers.SubscribeToEvents) + { + buffer.Append( "Subscribe" ); + } + + if ((eventNotifier & EventNotifiers.HistoryRead) == EventNotifiers.HistoryRead) + { + if (buffer.Length > 0) + { + buffer.Append( " | " ); + } + + buffer.Append( "HistoryRead" ); + } + + if ((eventNotifier & EventNotifiers.HistoryWrite) == EventNotifiers.HistoryWrite) + { + if (buffer.Length > 0) + { + buffer.Append( " | " ); + } + + buffer.Append( "HistoryWrite" ); + } + + return buffer.ToString( ); + } + + /// + /// Gets the display text for the value rank attribute. + /// + /// The value rank. + /// The value rank formatted as a string. + private static string GetValueRankDisplayText( int valueRank ) + { + switch (valueRank) + { + case ValueRanks.Any: return "Any"; + case ValueRanks.Scalar: return "Scalar"; + case ValueRanks.ScalarOrOneDimension: return "ScalarOrOneDimension"; + case ValueRanks.OneOrMoreDimensions: return "OneOrMoreDimensions"; + case ValueRanks.OneDimension: return "OneDimension"; + case ValueRanks.TwoDimensions: return "TwoDimensions"; + } + + return valueRank.ToString( ); + } + + /// + /// Gets the display text for the specified attribute. + /// + /// The currently active session. + /// The id of the attribute. + /// The value of the attribute. + /// The attribute formatted as a string. + public static string GetAttributeDisplayText( Session session, uint attributeId, Variant value ) + { + if (value == Variant.Null) + { + return String.Empty; + } + + switch (attributeId) + { + case Attributes.AccessLevel: + case Attributes.UserAccessLevel: + { + byte? field = value.Value as byte?; + + if (field != null) + { + return GetAccessLevelDisplayText( field.Value ); + } + + break; + } + + case Attributes.EventNotifier: + { + byte? field = value.Value as byte?; + + if (field != null) + { + return GetEventNotifierDisplayText( field.Value ); + } + + break; + } + + case Attributes.DataType: + { + return session.NodeCache.GetDisplayText( value.Value as NodeId ); + } + + case Attributes.ValueRank: + { + int? field = value.Value as int?; + + if (field != null) + { + return GetValueRankDisplayText( field.Value ); + } + + break; + } + + case Attributes.NodeClass: + { + int? field = value.Value as int?; + + if (field != null) + { + return ((NodeClass)field.Value).ToString( ); + } + + break; + } + + case Attributes.NodeId: + { + NodeId field = value.Value as NodeId; + + if (!NodeId.IsNull( field )) + { + return field.ToString( ); + } + + return "Null"; + } + } + + // check for byte strings. + if (value.Value is byte[]) + { + return Utils.ToHexString( value.Value as byte[] ); + } + + // use default format. + return value.ToString( ); + } + + /// + /// Discovers the servers on the local machine. + /// + /// The configuration. + /// A list of server urls. + public static IList DiscoverServers( ApplicationConfiguration configuration ) + { + List serverUrls = new List( ); + + // set a short timeout because this is happening in the drop down event. + EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create( configuration ); + endpointConfiguration.OperationTimeout = 5000; + + // Connect to the local discovery server and find the available servers. + using (DiscoveryClient client = DiscoveryClient.Create( new Uri( "opc.tcp://localhost:4840" ), endpointConfiguration )) + { + ApplicationDescriptionCollection servers = client.FindServers( null ); + + // populate the drop down list with the discovery URLs for the available servers. + for (int ii = 0; ii < servers.Count; ii++) + { + if (servers[ii].ApplicationType == ApplicationType.DiscoveryServer) + { + continue; + } + + for (int jj = 0; jj < servers[ii].DiscoveryUrls.Count; jj++) + { + string discoveryUrl = servers[ii].DiscoveryUrls[jj]; + + // Many servers will use the '/discovery' suffix for the discovery endpoint. + // The URL without this prefix should be the base URL for the server. + if (discoveryUrl.EndsWith( "/discovery" )) + { + discoveryUrl = discoveryUrl.Substring( 0, discoveryUrl.Length - "/discovery".Length ); + } + + // ensure duplicates do not get added. + if (!serverUrls.Contains( discoveryUrl )) + { + serverUrls.Add( discoveryUrl ); + } + } + } + } + + return serverUrls; + } + + /// + /// Finds the endpoint that best matches the current settings. + /// + /// The discovery URL. + /// if set to true select an endpoint that uses security. + /// The best available endpoint. + public static EndpointDescription SelectEndpoint( string discoveryUrl, bool useSecurity ) + { + // needs to add the '/discovery' back onto non-UA TCP URLs. + if (!discoveryUrl.StartsWith( Utils.UriSchemeOpcTcp )) + { + if (!discoveryUrl.EndsWith( "/discovery" )) + { + discoveryUrl += "/discovery"; + } + } + + // parse the selected URL. + Uri uri = new Uri( discoveryUrl ); + + // set a short timeout because this is happening in the drop down event. + EndpointConfiguration configuration = EndpointConfiguration.Create( ); + configuration.OperationTimeout = 5000; + + EndpointDescription selectedEndpoint = null; + + // Connect to the server's discovery endpoint and find the available configuration. + using (DiscoveryClient client = DiscoveryClient.Create( uri, configuration )) + { + EndpointDescriptionCollection endpoints = client.GetEndpoints( null ); + + // select the best endpoint to use based on the selected URL and the UseSecurity checkbox. + for (int ii = 0; ii < endpoints.Count; ii++) + { + EndpointDescription endpoint = endpoints[ii]; + + // check for a match on the URL scheme. + if (endpoint.EndpointUrl.StartsWith( uri.Scheme )) + { + // check if security was requested. + if (useSecurity) + { + if (endpoint.SecurityMode == MessageSecurityMode.None) + { + continue; + } + } + else + { + if (endpoint.SecurityMode != MessageSecurityMode.None) + { + continue; + } + } + + // pick the first available endpoint by default. + if (selectedEndpoint == null) + { + selectedEndpoint = endpoint; + } + + // The security level is a relative measure assigned by the server to the + // endpoints that it returns. Clients should always pick the highest level + // unless they have a reason not too. + if (endpoint.SecurityLevel > selectedEndpoint.SecurityLevel) + { + selectedEndpoint = endpoint; + } + } + } + + // pick the first available endpoint by default. + if (selectedEndpoint == null && endpoints.Count > 0) + { + selectedEndpoint = endpoints[0]; + } + } + + // if a server is behind a firewall it may return URLs that are not accessible to the client. + // This problem can be avoided by assuming that the domain in the URL used to call + // GetEndpoints can be used to access any of the endpoints. This code makes that conversion. + // Note that the conversion only makes sense if discovery uses the same protocol as the endpoint. + + Uri endpointUrl = Utils.ParseUri( selectedEndpoint.EndpointUrl ); + + if (endpointUrl != null && endpointUrl.Scheme == uri.Scheme) + { + UriBuilder builder = new UriBuilder( endpointUrl ); + builder.Host = uri.DnsSafeHost; + builder.Port = uri.Port; + selectedEndpoint.EndpointUrl = builder.ToString( ); + } + + // return the selected endpoint. + return selectedEndpoint; + } + + /// + /// Browses the address space and returns the references found. + /// + /// The session. + /// The set of browse operations to perform. + /// if set to true a exception will be thrown on an error. + /// + /// The references found. Null if an error occurred. + /// + public static ReferenceDescriptionCollection Browse( Session session, BrowseDescriptionCollection nodesToBrowse, bool throwOnError ) + { + try + { + ReferenceDescriptionCollection references = new ReferenceDescriptionCollection( ); + BrowseDescriptionCollection unprocessedOperations = new BrowseDescriptionCollection( ); + + while (nodesToBrowse.Count > 0) + { + // start the browse operation. + BrowseResultCollection results = null; + DiagnosticInfoCollection diagnosticInfos = null; + + session.Browse( + null, + null, + 0, + nodesToBrowse, + out results, + out diagnosticInfos ); + + ClientBase.ValidateResponse( results, nodesToBrowse ); + ClientBase.ValidateDiagnosticInfos( diagnosticInfos, nodesToBrowse ); + + ByteStringCollection continuationPoints = new ByteStringCollection( ); + + for (int ii = 0; ii < nodesToBrowse.Count; ii++) + { + // check for error. + if (StatusCode.IsBad( results[ii].StatusCode )) + { + // this error indicates that the server does not have enough simultaneously active + // continuation points. This request will need to be resent after the other operations + // have been completed and their continuation points released. + if (results[ii].StatusCode == StatusCodes.BadNoContinuationPoints) + { + unprocessedOperations.Add( nodesToBrowse[ii] ); + } + + continue; + } + + // check if all references have been fetched. + if (results[ii].References.Count == 0) + { + continue; + } + + // save results. + references.AddRange( results[ii].References ); + + // check for continuation point. + if (results[ii].ContinuationPoint != null) + { + continuationPoints.Add( results[ii].ContinuationPoint ); + } + } + + // process continuation points. + ByteStringCollection revisedContiuationPoints = new ByteStringCollection( ); + + while (continuationPoints.Count > 0) + { + // continue browse operation. + session.BrowseNext( + null, + true, + continuationPoints, + out results, + out diagnosticInfos ); + + ClientBase.ValidateResponse( results, continuationPoints ); + ClientBase.ValidateDiagnosticInfos( diagnosticInfos, continuationPoints ); + + for (int ii = 0; ii < continuationPoints.Count; ii++) + { + // check for error. + if (StatusCode.IsBad( results[ii].StatusCode )) + { + continue; + } + + // check if all references have been fetched. + if (results[ii].References.Count == 0) + { + continue; + } + + // save results. + references.AddRange( results[ii].References ); + + // check for continuation point. + if (results[ii].ContinuationPoint != null) + { + revisedContiuationPoints.Add( results[ii].ContinuationPoint ); + } + } + + // check if browsing must continue; + revisedContiuationPoints = continuationPoints; + } + + // check if unprocessed results exist. + nodesToBrowse = unprocessedOperations; + } + + // return complete list. + return references; + } + catch (Exception exception) + { + if (throwOnError) + { + throw new ServiceResultException( exception, StatusCodes.BadUnexpectedError ); + } + + return null; + } + } + + /// + /// Finds the type of the event for the notification. + /// + /// The monitored item. + /// The notification. + /// The NodeId of the EventType. + public static NodeId FindEventType( MonitoredItem monitoredItem, EventFieldList notification ) + { + EventFilter filter = monitoredItem.Status.Filter as EventFilter; + + if (filter != null) + { + for (int ii = 0; ii < filter.SelectClauses.Count; ii++) + { + SimpleAttributeOperand clause = filter.SelectClauses[ii]; + + if (clause.BrowsePath.Count == 1 && clause.BrowsePath[0] == BrowseNames.EventType) + { + return notification.EventFields[ii].Value as NodeId; + } + } + } + + return null; + } + + /// + /// Browses the address space and returns the references found. + /// + /// The session. + /// The NodeId for the starting node. + /// if set to true a exception will be thrown on an error. + /// + /// The references found. Null if an error occurred. + /// + public static ReferenceDescriptionCollection Browse( Session session, BrowseDescription nodeToBrowse, bool throwOnError ) + { + try + { + ReferenceDescriptionCollection references = new ReferenceDescriptionCollection( ); + + // construct browse request. + BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection( ); + nodesToBrowse.Add( nodeToBrowse ); + + // start the browse operation. + BrowseResultCollection results = null; + DiagnosticInfoCollection diagnosticInfos = null; + + session.Browse( + null, + null, + 0, + nodesToBrowse, + out results, + out diagnosticInfos ); + + ClientBase.ValidateResponse( results, nodesToBrowse ); + ClientBase.ValidateDiagnosticInfos( diagnosticInfos, nodesToBrowse ); + + do + { + // check for error. + if (StatusCode.IsBad( results[0].StatusCode )) + { + throw new ServiceResultException( results[0].StatusCode ); + } + + // process results. + for (int ii = 0; ii < results[0].References.Count; ii++) + { + references.Add( results[0].References[ii] ); + } + + // check if all references have been fetched. + if (results[0].References.Count == 0 || results[0].ContinuationPoint == null) + { + break; + } + + // continue browse operation. + ByteStringCollection continuationPoints = new ByteStringCollection( ); + continuationPoints.Add( results[0].ContinuationPoint ); + + session.BrowseNext( + null, + false, + continuationPoints, + out results, + out diagnosticInfos ); + + ClientBase.ValidateResponse( results, continuationPoints ); + ClientBase.ValidateDiagnosticInfos( diagnosticInfos, continuationPoints ); + } + while (true); + + //return complete list. + return references; + } + catch (Exception exception) + { + if (throwOnError) + { + throw new ServiceResultException( exception, StatusCodes.BadUnexpectedError ); + } + + return null; + } + } + + /// + /// Browses the address space and returns all of the supertypes of the specified type node. + /// + /// The session. + /// The NodeId for a type node in the address space. + /// if set to true a exception will be thrown on an error. + /// + /// The references found. Null if an error occurred. + /// + public static ReferenceDescriptionCollection BrowseSuperTypes( Session session, NodeId typeId, bool throwOnError ) + { + ReferenceDescriptionCollection supertypes = new ReferenceDescriptionCollection( ); + + try + { + // find all of the children of the field. + BrowseDescription nodeToBrowse = new BrowseDescription( ); + + nodeToBrowse.NodeId = typeId; + nodeToBrowse.BrowseDirection = BrowseDirection.Inverse; + nodeToBrowse.ReferenceTypeId = ReferenceTypeIds.HasSubtype; + nodeToBrowse.IncludeSubtypes = false; // more efficient to use IncludeSubtypes=False when possible. + nodeToBrowse.NodeClassMask = 0; // the HasSubtype reference already restricts the targets to Types. + nodeToBrowse.ResultMask = (uint)BrowseResultMask.All; + + ReferenceDescriptionCollection references = Browse( session, nodeToBrowse, throwOnError ); + + while (references != null && references.Count > 0) + { + // should never be more than one supertype. + supertypes.Add( references[0] ); + + // only follow references within this server. + if (references[0].NodeId.IsAbsolute) + { + break; + } + + // get the references for the next level up. + nodeToBrowse.NodeId = (NodeId)references[0].NodeId; + references = Browse( session, nodeToBrowse, throwOnError ); + } + + // return complete list. + return supertypes; + } + catch (Exception exception) + { + if (throwOnError) + { + throw new ServiceResultException( exception, StatusCodes.BadUnexpectedError ); + } + + return null; + } + } + + /// + /// Constructs an event object from a notification. + /// + /// The session. + /// The monitored item that produced the notification. + /// The notification. + /// The known event types. + /// Mapping between event types and known event types. + /// + /// The event object. Null if the notification is not a valid event type. + /// + public static BaseEventState ConstructEvent( + Session session, + MonitoredItem monitoredItem, + EventFieldList notification, + Dictionary knownEventTypes, + Dictionary eventTypeMappings ) + { + // find the event type. + NodeId eventTypeId = FindEventType( monitoredItem, notification ); + + if (eventTypeId == null) + { + return null; + } + + // look up the known event type. + Type knownType = null; + NodeId knownTypeId = null; + + if (eventTypeMappings.TryGetValue( eventTypeId, out knownTypeId )) + { + knownType = knownEventTypes[knownTypeId]; + } + + // try again. + if (knownType == null) + { + if (knownEventTypes.TryGetValue( eventTypeId, out knownType )) + { + knownTypeId = eventTypeId; + eventTypeMappings.Add( eventTypeId, eventTypeId ); + } + } + + // try mapping it to a known type. + if (knownType == null) + { + // browse for the supertypes of the event type. + ReferenceDescriptionCollection supertypes = FormUtils.BrowseSuperTypes( session, eventTypeId, false ); + + // can't do anything with unknown types. + if (supertypes == null) + { + return null; + } + + // find the first supertype that matches a known event type. + for (int ii = 0; ii < supertypes.Count; ii++) + { + NodeId superTypeId = (NodeId)supertypes[ii].NodeId; + + if (knownEventTypes.TryGetValue( superTypeId, out knownType )) + { + knownTypeId = superTypeId; + eventTypeMappings.Add( eventTypeId, superTypeId ); + } + + if (knownTypeId != null) + { + break; + } + } + + // can't do anything with unknown types. + if (knownTypeId == null) + { + return null; + } + } + + // construct the event based on the known event type. + BaseEventState e = (BaseEventState)Activator.CreateInstance( knownType, new object[] { (NodeState)null } ); + + // get the filter which defines the contents of the notification. + EventFilter filter = monitoredItem.Status.Filter as EventFilter; + + // initialize the event with the values in the notification. + e.Update( session.SystemContext, filter.SelectClauses, notification ); + + // save the orginal notification. + e.Handle = notification; + + return e; + } + + /// + /// Returns the node ids for a set of relative paths. + /// + /// An open session with the server to use. + /// The starting node for the relative paths. + /// The namespace URIs referenced by the relative paths. + /// The relative paths. + /// A collection of local nodes. + public static List TranslateBrowsePaths( + Session session, + NodeId startNodeId, + NamespaceTable namespacesUris, + params string[] relativePaths ) + { + // build the list of browse paths to follow by parsing the relative paths. + BrowsePathCollection browsePaths = new BrowsePathCollection( ); + + if (relativePaths != null) + { + for (int ii = 0; ii < relativePaths.Length; ii++) + { + BrowsePath browsePath = new BrowsePath( ); + + // The relative paths used indexes in the namespacesUris table. These must be + // converted to indexes used by the server. An error occurs if the relative path + // refers to a namespaceUri that the server does not recognize. + + // The relative paths may refer to ReferenceType by their BrowseName. The TypeTree object + // allows the parser to look up the server's NodeId for the ReferenceType. + + browsePath.RelativePath = RelativePath.Parse( + relativePaths[ii], + session.TypeTree, + namespacesUris, + session.NamespaceUris ); + + browsePath.StartingNode = startNodeId; + + browsePaths.Add( browsePath ); + } + } + + // make the call to the server. + BrowsePathResultCollection results; + DiagnosticInfoCollection diagnosticInfos; + + ResponseHeader responseHeader = session.TranslateBrowsePathsToNodeIds( + null, + browsePaths, + out results, + out diagnosticInfos ); + + // ensure that the server returned valid results. + Session.ValidateResponse( results, browsePaths ); + Session.ValidateDiagnosticInfos( diagnosticInfos, browsePaths ); + + // collect the list of node ids found. + List nodes = new List( ); + + for (int ii = 0; ii < results.Count; ii++) + { + // check if the start node actually exists. + if (StatusCode.IsBad( results[ii].StatusCode )) + { + nodes.Add( null ); + continue; + } + + // an empty list is returned if no node was found. + if (results[ii].Targets.Count == 0) + { + nodes.Add( null ); + continue; + } + + // Multiple matches are possible, however, the node that matches the type model is the + // one we are interested in here. The rest can be ignored. + BrowsePathTarget target = results[ii].Targets[0]; + + if (target.RemainingPathIndex != UInt32.MaxValue) + { + nodes.Add( null ); + continue; + } + + // The targetId is an ExpandedNodeId because it could be node in another server. + // The ToNodeId function is used to convert a local NodeId stored in a ExpandedNodeId to a NodeId. + nodes.Add( ExpandedNodeId.ToNodeId( target.TargetId, session.NamespaceUris ) ); + } + + // return whatever was found. + return nodes; + } + + /// + /// Collects the fields for the type. + /// + /// The session. + /// The fields. + /// The node id for the declaration of the field. + public static void CollectFieldsForType( Session session, NodeId typeId, SimpleAttributeOperandCollection fields, List fieldNodeIds ) + { + // get the supertypes. + ReferenceDescriptionCollection supertypes = FormUtils.BrowseSuperTypes( session, typeId, false ); + + if (supertypes == null) + { + return; + } + + // process the types starting from the top of the tree. + Dictionary foundNodes = new Dictionary( ); + QualifiedNameCollection parentPath = new QualifiedNameCollection( ); + + for (int ii = supertypes.Count - 1; ii >= 0; ii--) + { + CollectFields( session, (NodeId)supertypes[ii].NodeId, parentPath, fields, fieldNodeIds, foundNodes ); + } + + // collect the fields for the selected type. + CollectFields( session, typeId, parentPath, fields, fieldNodeIds, foundNodes ); + } + + /// + /// Collects the fields for the instance. + /// + /// The session. + /// The fields. + /// The node id for the declaration of the field. + public static void CollectFieldsForInstance( Session session, NodeId instanceId, SimpleAttributeOperandCollection fields, List fieldNodeIds ) + { + Dictionary foundNodes = new Dictionary( ); + QualifiedNameCollection parentPath = new QualifiedNameCollection( ); + CollectFields( session, instanceId, parentPath, fields, fieldNodeIds, foundNodes ); + } + + /// + /// Collects the fields for the instance node. + /// + /// The session. + /// The node id. + /// The parent path. + /// The event fields. + /// The node id for the declaration of the field. + /// The table of found nodes. + private static void CollectFields( + Session session, + NodeId nodeId, + QualifiedNameCollection parentPath, + SimpleAttributeOperandCollection fields, + List fieldNodeIds, + Dictionary foundNodes ) + { + // find all of the children of the field. + BrowseDescription nodeToBrowse = new BrowseDescription( ); + + nodeToBrowse.NodeId = nodeId; + nodeToBrowse.BrowseDirection = BrowseDirection.Forward; + nodeToBrowse.ReferenceTypeId = ReferenceTypeIds.Aggregates; + nodeToBrowse.IncludeSubtypes = true; + nodeToBrowse.NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable); + nodeToBrowse.ResultMask = (uint)BrowseResultMask.All; + + ReferenceDescriptionCollection children = FormUtils.Browse( session, nodeToBrowse, false ); + + if (children == null) + { + return; + } + + // process the children. + for (int ii = 0; ii < children.Count; ii++) + { + ReferenceDescription child = children[ii]; + + if (child.NodeId.IsAbsolute) + { + continue; + } + + // construct browse path. + QualifiedNameCollection browsePath = new QualifiedNameCollection( parentPath ); + browsePath.Add( child.BrowseName ); + + // check if the browse path is already in the list. + int index = ContainsPath( fields, browsePath ); + + if (index < 0) + { + SimpleAttributeOperand field = new SimpleAttributeOperand( ); + + field.TypeDefinitionId = ObjectTypeIds.BaseEventType; + field.BrowsePath = browsePath; + field.AttributeId = (child.NodeClass == NodeClass.Variable) ? Attributes.Value : Attributes.NodeId; + + fields.Add( field ); + fieldNodeIds.Add( (NodeId)child.NodeId ); + } + + // recusively find all of the children. + NodeId targetId = (NodeId)child.NodeId; + + // need to guard against loops. + if (!foundNodes.ContainsKey( targetId )) + { + foundNodes.Add( targetId, browsePath ); + CollectFields( session, (NodeId)child.NodeId, browsePath, fields, fieldNodeIds, foundNodes ); + } + } + } + + /// + /// Determines whether the specified select clause contains the browse path. + /// + /// The select clause. + /// The browse path. + /// + /// true if the specified select clause contains path; otherwise, false. + /// + private static int ContainsPath( SimpleAttributeOperandCollection selectClause, QualifiedNameCollection browsePath ) + { + for (int ii = 0; ii < selectClause.Count; ii++) + { + SimpleAttributeOperand field = selectClause[ii]; + + if (field.BrowsePath.Count != browsePath.Count) + { + continue; + } + + bool match = true; + + for (int jj = 0; jj < field.BrowsePath.Count; jj++) + { + if (field.BrowsePath[jj] != browsePath[jj]) + { + match = false; + break; + } + } + + if (match) + { + return ii; + } + } + + return -1; + } + } +} diff --git a/Plugins/Drivers/DriverOPCUaClient/OpcUaHelper/OpcUaClientHelper.cs b/Plugins/Drivers/DriverOPCUaClient/OpcUaHelper/OpcUaClientHelper.cs new file mode 100644 index 00000000..f9424e63 --- /dev/null +++ b/Plugins/Drivers/DriverOPCUaClient/OpcUaHelper/OpcUaClientHelper.cs @@ -0,0 +1,1446 @@ +using Opc.Ua; +using Opc.Ua.Client; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace OpcUaHelper +{ + /// + /// 一个二次封装了的OPC UA库,支持从opc ua服务器读写节点数据,批量读写,订阅,批量订阅,历史数据读取,方法调用操作。 + /// + public class OpcUaClientHelper + { + #region Constructors + + /// + /// 默认的构造函数,实例化一个新的OPC UA类 + /// + public OpcUaClientHelper( ) + { + dic_subscriptions = new Dictionary( ); + + var certificateValidator = new CertificateValidator( ); + certificateValidator.CertificateValidation += ( sender, eventArgs ) => + { + if (ServiceResult.IsGood( eventArgs.Error )) + eventArgs.Accept = true; + else if (eventArgs.Error.StatusCode.Code == StatusCodes.BadCertificateUntrusted) + eventArgs.Accept = true; + else + throw new Exception( string.Format( "Failed to validate certificate with error code {0}: {1}", eventArgs.Error.Code, eventArgs.Error.AdditionalInfo ) ); + }; + + SecurityConfiguration securityConfigurationcv = new SecurityConfiguration + { + AutoAcceptUntrustedCertificates = true, + RejectSHA1SignedCertificates = false, + MinimumCertificateKeySize = 1024, + }; + certificateValidator.Update( securityConfigurationcv ); + + // Build the application configuration + var configuration = new ApplicationConfiguration + { + ApplicationName = OpcUaName, + ApplicationType = ApplicationType.Client, + CertificateValidator = certificateValidator, + ApplicationUri = "urn:MyClient", //Kepp this syntax + ProductUri = "OpcUaClient", + + ServerConfiguration = new ServerConfiguration + { + MaxSubscriptionCount = 100000, + MaxMessageQueueSize = 1000000, + MaxNotificationQueueSize = 1000000, + MaxPublishRequestCount = 10000000, + }, + + SecurityConfiguration = new SecurityConfiguration + { + AutoAcceptUntrustedCertificates = true, + RejectSHA1SignedCertificates = false, + MinimumCertificateKeySize = 1024, + SuppressNonceValidationErrors = true, + + ApplicationCertificate = new CertificateIdentifier + { + StoreType = CertificateStoreType.X509Store, + StorePath = "CurrentUser\\My", + SubjectName = OpcUaName, + }, + TrustedIssuerCertificates = new CertificateTrustList + { + StoreType = CertificateStoreType.X509Store, + StorePath = "CurrentUser\\Root", + }, + TrustedPeerCertificates = new CertificateTrustList + { + StoreType = CertificateStoreType.X509Store, + StorePath = "CurrentUser\\Root", + } + }, + + TransportQuotas = new TransportQuotas + { + OperationTimeout = 6000000, + MaxStringLength = int.MaxValue, + MaxByteStringLength = int.MaxValue, + MaxArrayLength = 65535, + MaxMessageSize = 419430400, + MaxBufferSize = 65535, + ChannelLifetime = -1, + SecurityTokenLifetime = -1 + }, + ClientConfiguration = new ClientConfiguration + { + DefaultSessionTimeout = -1, + MinSubscriptionLifetime = -1, + }, + DisableHiResClock = true + }; + + configuration.Validate( ApplicationType.Client ); + m_configuration = configuration; + } + + #endregion Constructors + + #region Connect And Disconnect + + /// + /// connect to server + /// + /// remote url + public async Task ConnectServer( string serverUrl ) + { + m_session = await Connect( serverUrl ); + } + + /// + /// Creates a new session. + /// + /// The new session object. + private async Task Connect( string serverUrl ) + { + // disconnect from existing session. + Disconnect( ); + + if (m_configuration == null) + { + throw new ArgumentNullException( "_configuration" ); + } + + // select the best endpoint. + EndpointDescription endpointDescription = CoreClientUtils.SelectEndpoint( serverUrl, UseSecurity ); + EndpointConfiguration endpointConfiguration = EndpointConfiguration.Create( m_configuration ); + + ConfiguredEndpoint endpoint = new ConfiguredEndpoint( null, endpointDescription, endpointConfiguration ); + + m_session = await Session.Create( + m_configuration, + endpoint, + false, + false, + (string.IsNullOrEmpty( OpcUaName )) ? m_configuration.ApplicationName : OpcUaName, + 60000, + UserIdentity, + new string[] { } ); + + // set up keep alive callback. + m_session.KeepAlive += new KeepAliveEventHandler( Session_KeepAlive ); + + // update the client status + m_IsConnected = true; + + // raise an event. + DoConnectComplete( null ); + + // return the new session. + return m_session; + } + + /// + /// Disconnects from the server. + /// + public void Disconnect( ) + { + UpdateStatus( false, DateTime.UtcNow, "Disconnected" ); + + // stop any reconnect operation. + if (m_reConnectHandler != null) + { + m_reConnectHandler.Dispose( ); + m_reConnectHandler = null; + } + + // disconnect any existing session. + if (m_session != null) + { + m_session.Close( 10000 ); + m_session = null; + } + + // update the client status + m_IsConnected = false; + + // raise an event. + DoConnectComplete( null ); + } + + #endregion Connect And Disconnect + + #region Event Handlers + + /// + /// Report the client status + /// + /// Whether the status represents an error. + /// The time associated with the status. + /// The status message. + /// Arguments used to format the status message. + private void UpdateStatus( bool error, DateTime time, string status, params object[] args ) + { + m_OpcStatusChange?.Invoke( this, new OpcUaStatusEventArgs( ) + { + Error = error, + Time = time.ToLocalTime( ), + Text = String.Format( status, args ), + } ); + } + + /// + /// Handles a keep alive event from a session. + /// + private void Session_KeepAlive( Session session, KeepAliveEventArgs e ) + { + try + { + // check for events from discarded sessions. + if (!Object.ReferenceEquals( session, m_session )) + { + return; + } + + // start reconnect sequence on communication error. + if (ServiceResult.IsBad( e.Status )) + { + if (m_reconnectPeriod <= 0) + { + UpdateStatus( true, e.CurrentTime, "Communication Error ({0})", e.Status ); + return; + } + + UpdateStatus( true, e.CurrentTime, "Reconnecting in {0}s", m_reconnectPeriod ); + + if (m_reConnectHandler == null) + { + m_ReconnectStarting?.Invoke( this, e ); + + m_reConnectHandler = new SessionReconnectHandler( ); + m_reConnectHandler.BeginReconnect( m_session, m_reconnectPeriod * 1000, Server_ReconnectComplete ); + } + + return; + } + + // update status. + UpdateStatus( false, e.CurrentTime, "Connected [{0}]", session.Endpoint.EndpointUrl ); + + // raise any additional notifications. + m_KeepAliveComplete?.Invoke( this, e ); + } + catch (Exception exception) + { + throw; + } + } + + /// + /// Handles a reconnect event complete from the reconnect handler. + /// + private void Server_ReconnectComplete( object sender, EventArgs e ) + { + try + { + // ignore callbacks from discarded objects. + if (!Object.ReferenceEquals( sender, m_reConnectHandler )) + { + return; + } + + m_session = m_reConnectHandler.Session; + m_reConnectHandler.Dispose( ); + m_reConnectHandler = null; + + // raise any additional notifications. + m_ReconnectComplete?.Invoke( this, e ); + } + catch (Exception exception) + { + throw; + } + } + + #endregion Event Handlers + + #region LogOut Setting + + /// + /// 设置OPC客户端的日志输出 + /// + /// 完整的文件路径 + /// 是否删除原文件 + public void SetLogPathName( string filePath, bool deleteExisting ) + { + Utils.SetTraceLog( filePath, deleteExisting ); + Utils.SetTraceMask( 515 ); + } + + #endregion LogOut Setting + + #region Public Members + + /// + /// a name of application name show on server + /// + public string OpcUaName { get; set; } = "Opc Ua Helper"; + + /// + /// Whether to use security when connecting. + /// + public bool UseSecurity + { + get { return m_useSecurity; } + set { m_useSecurity = value; } + } + + /// + /// The user identity to use when creating the session. + /// + public IUserIdentity UserIdentity { get; set; } + + /// + /// The currently active session. + /// + public Session Session + { + get { return m_session; } + } + + /// + /// Indicate the connect status + /// + public bool Connected + { + get { return m_IsConnected; } + } + + /// + /// The number of seconds between reconnect attempts (0 means reconnect is disabled). + /// + public int ReconnectPeriod + { + get { return m_reconnectPeriod; } + set { m_reconnectPeriod = value; } + } + + /// + /// Raised when a good keep alive from the server arrives. + /// + public event EventHandler KeepAliveComplete + { + add { m_KeepAliveComplete += value; } + remove { m_KeepAliveComplete -= value; } + } + + /// + /// Raised when a reconnect operation starts. + /// + public event EventHandler ReconnectStarting + { + add { m_ReconnectStarting += value; } + remove { m_ReconnectStarting -= value; } + } + + /// + /// Raised when a reconnect operation completes. + /// + public event EventHandler ReconnectComplete + { + add { m_ReconnectComplete += value; } + remove { m_ReconnectComplete -= value; } + } + + /// + /// Raised after successfully connecting to or disconnecing from a server. + /// + public event EventHandler ConnectComplete + { + add { m_ConnectComplete += value; } + remove { m_ConnectComplete -= value; } + } + + /// + /// Raised after the client status change + /// + public event EventHandler OpcStatusChange + { + add { m_OpcStatusChange += value; } + remove { m_OpcStatusChange -= value; } + } + + /// + /// 配置信息 + /// + public ApplicationConfiguration AppConfig => m_configuration; + + #endregion Public Members + + #region Node Write/Read Support + + /// + /// Read a value node from server + /// + /// node id + /// DataValue + public DataValue ReadNode( NodeId nodeId ) + { + ReadValueIdCollection nodesToRead = new ReadValueIdCollection + { + new ReadValueId( ) + { + NodeId = nodeId, + AttributeId = Attributes.Value + } + }; + + // read the current value + m_session.Read( + null, + 0, + TimestampsToReturn.Neither, + nodesToRead, + out DataValueCollection results, + out DiagnosticInfoCollection diagnosticInfos ); + + ClientBase.ValidateResponse( results, nodesToRead ); + ClientBase.ValidateDiagnosticInfos( diagnosticInfos, nodesToRead ); + + return results[0]; + } + + /// + /// Read a value node from server + /// + /// type of value + /// node id + /// 实际值 + public T ReadNode( string tag ) + { + DataValue dataValue = ReadNode( new NodeId( tag ) ); + return (T)dataValue.Value; + } + + /// + /// Read a tag asynchronously + /// + /// The type of tag to read + /// tag值 + /// The value retrieved from the OPC + public Task ReadNodeAsync( string tag ) + { + ReadValueIdCollection nodesToRead = new ReadValueIdCollection + { + new ReadValueId() + { + NodeId = new NodeId(tag), + AttributeId = Attributes.Value + } + }; + + // Wrap the ReadAsync logic in a TaskCompletionSource, so we can use C# async/await syntax to call it: + var taskCompletionSource = new TaskCompletionSource( ); + m_session.BeginRead( + requestHeader: null, + maxAge: 0, + timestampsToReturn: TimestampsToReturn.Neither, + nodesToRead: nodesToRead, + callback: ar => + { + DataValueCollection results; + DiagnosticInfoCollection diag; + var response = m_session.EndRead( + result: ar, + results: out results, + diagnosticInfos: out diag ); + + try + { + CheckReturnValue( response.ServiceResult ); + CheckReturnValue( results[0].StatusCode ); + var val = results[0]; + taskCompletionSource.TrySetResult( (T)val.Value ); + } + catch (Exception ex) + { + taskCompletionSource.TrySetException( ex ); + } + }, + asyncState: null ); + + return taskCompletionSource.Task; + } + + /// + /// read several value nodes from server + /// + /// all NodeIds + /// all values + public List ReadNodes( NodeId[] nodeIds ) + { + ReadValueIdCollection nodesToRead = new ReadValueIdCollection( ); + for (int i = 0; i < nodeIds.Length; i++) + { + nodesToRead.Add( new ReadValueId( ) + { + NodeId = nodeIds[i], + AttributeId = Attributes.Value + } ); + } + + // 读取当前的值 + m_session.Read( + null, + 0, + TimestampsToReturn.Neither, + nodesToRead, + out DataValueCollection results, + out DiagnosticInfoCollection diagnosticInfos ); + + ClientBase.ValidateResponse( results, nodesToRead ); + ClientBase.ValidateDiagnosticInfos( diagnosticInfos, nodesToRead ); + + return results.ToList( ); + } + + /// + /// read several value nodes from server + /// + /// all NodeIds + /// all values + public Task> ReadNodesAsync( NodeId[] nodeIds ) + { + ReadValueIdCollection nodesToRead = new ReadValueIdCollection( ); + for (int i = 0; i < nodeIds.Length; i++) + { + nodesToRead.Add( new ReadValueId( ) + { + NodeId = nodeIds[i], + AttributeId = Attributes.Value + } ); + } + + var taskCompletionSource = new TaskCompletionSource>( ); + // 读取当前的值 + m_session.BeginRead( + null, + 0, + TimestampsToReturn.Neither, + nodesToRead, + callback: ar => + { + DataValueCollection results; + DiagnosticInfoCollection diag; + var response = m_session.EndRead( + result: ar, + results: out results, + diagnosticInfos: out diag ); + + try + { + CheckReturnValue( response.ServiceResult ); + taskCompletionSource.TrySetResult( results.ToList( ) ); + } + catch (Exception ex) + { + taskCompletionSource.TrySetException( ex ); + } + }, + asyncState: null ); + + return taskCompletionSource.Task; + } + + /// + /// read several value nodes from server + /// + /// 所以的节点数组信息 + /// all values + public List ReadNodes( string[] tags ) + { + List result = new List( ); + ReadValueIdCollection nodesToRead = new ReadValueIdCollection( ); + for (int i = 0; i < tags.Length; i++) + { + nodesToRead.Add( new ReadValueId( ) + { + NodeId = new NodeId( tags[i] ), + AttributeId = Attributes.Value + } ); + } + + // 读取当前的值 + m_session.Read( + null, + 0, + TimestampsToReturn.Neither, + nodesToRead, + out DataValueCollection results, + out DiagnosticInfoCollection diagnosticInfos ); + + ClientBase.ValidateResponse( results, nodesToRead ); + ClientBase.ValidateDiagnosticInfos( diagnosticInfos, nodesToRead ); + + foreach (var item in results) + { + result.Add( (T)item.Value ); + } + return result; + } + + /// + /// read several value nodes from server + /// + /// all NodeIds + /// all values + public Task> ReadNodesAsync( string[] tags ) + { + ReadValueIdCollection nodesToRead = new ReadValueIdCollection( ); + for (int i = 0; i < tags.Length; i++) + { + nodesToRead.Add( new ReadValueId( ) + { + NodeId = new NodeId( tags[i] ), + AttributeId = Attributes.Value + } ); + } + + var taskCompletionSource = new TaskCompletionSource>( ); + // 读取当前的值 + m_session.BeginRead( + null, + 0, + TimestampsToReturn.Neither, + nodesToRead, + callback: ar => + { + DataValueCollection results; + DiagnosticInfoCollection diag; + var response = m_session.EndRead( + result: ar, + results: out results, + diagnosticInfos: out diag ); + + try + { + CheckReturnValue( response.ServiceResult ); + List result = new List( ); + foreach (var item in results) + { + result.Add( (T)item.Value ); + } + taskCompletionSource.TrySetResult( result ); + } + catch (Exception ex) + { + taskCompletionSource.TrySetException( ex ); + } + }, + asyncState: null ); + + return taskCompletionSource.Task; + } + + /// + /// write a note to server(you should use try catch) + /// + /// The type of tag to write on + /// 节点名称 + /// 值 + /// if success True,otherwise False + public bool WriteNode( string tag, T value ) + { + WriteValue valueToWrite = new WriteValue( ) + { + NodeId = new NodeId( tag ), + AttributeId = Attributes.Value + }; + valueToWrite.Value.Value = value; + valueToWrite.Value.StatusCode = StatusCodes.Good; + valueToWrite.Value.ServerTimestamp = DateTime.MinValue; + valueToWrite.Value.SourceTimestamp = DateTime.MinValue; + + WriteValueCollection valuesToWrite = new WriteValueCollection + { + valueToWrite + }; + + // 写入当前的值 + + m_session.Write( + null, + valuesToWrite, + out StatusCodeCollection results, + out DiagnosticInfoCollection diagnosticInfos ); + + ClientBase.ValidateResponse( results, valuesToWrite ); + ClientBase.ValidateDiagnosticInfos( diagnosticInfos, valuesToWrite ); + + if (StatusCode.IsBad( results[0] )) + { + throw new ServiceResultException( results[0] ); + } + + return !StatusCode.IsBad( results[0] ); + } + + /// + /// Write a value on the specified opc tag asynchronously + /// + /// The type of tag to write on + /// The fully-qualified identifier of the tag. You can specify a subfolder by using a comma delimited name. E.g: the tag `foo.bar` writes on the tag `bar` on the folder `foo` + /// The value for the item to write + public Task WriteNodeAsync( string tag, T value ) + { + WriteValue valueToWrite = new WriteValue( ) + { + NodeId = new NodeId( tag ), + AttributeId = Attributes.Value, + }; + valueToWrite.Value.Value = value; + valueToWrite.Value.StatusCode = StatusCodes.Good; + valueToWrite.Value.ServerTimestamp = DateTime.MinValue; + valueToWrite.Value.SourceTimestamp = DateTime.MinValue; + WriteValueCollection valuesToWrite = new WriteValueCollection + { + valueToWrite + }; + + // Wrap the WriteAsync logic in a TaskCompletionSource, so we can use C# async/await syntax to call it: + var taskCompletionSource = new TaskCompletionSource( ); + m_session.BeginWrite( + requestHeader: null, + nodesToWrite: valuesToWrite, + callback: ar => + { + var response = m_session.EndWrite( + result: ar, + results: out StatusCodeCollection results, + diagnosticInfos: out DiagnosticInfoCollection diag ); + + try + { + ClientBase.ValidateResponse( results, valuesToWrite ); + ClientBase.ValidateDiagnosticInfos( diag, valuesToWrite ); + taskCompletionSource.SetResult( StatusCode.IsGood( results[0] ) ); + } + catch (Exception ex) + { + taskCompletionSource.TrySetException( ex ); + } + }, + asyncState: null ); + return taskCompletionSource.Task; + } + + /// + /// 所有的节点都写入成功,返回True,否则返回False + /// + /// 节点名称数组 + /// 节点的值数据 + /// 所有的是否都写入成功 + public bool WriteNodes( string[] tags, object[] values ) + { + WriteValueCollection valuesToWrite = new WriteValueCollection( ); + + for (int i = 0; i < tags.Length; i++) + { + if (i < values.Length) + { + WriteValue valueToWrite = new WriteValue( ) + { + NodeId = new NodeId( tags[i] ), + AttributeId = Attributes.Value + }; + valueToWrite.Value.Value = values[i]; + valueToWrite.Value.StatusCode = StatusCodes.Good; + valueToWrite.Value.ServerTimestamp = DateTime.MinValue; + valueToWrite.Value.SourceTimestamp = DateTime.MinValue; + valuesToWrite.Add( valueToWrite ); + } + } + + // 写入当前的值 + + m_session.Write( + null, + valuesToWrite, + out StatusCodeCollection results, + out DiagnosticInfoCollection diagnosticInfos ); + + ClientBase.ValidateResponse( results, valuesToWrite ); + ClientBase.ValidateDiagnosticInfos( diagnosticInfos, valuesToWrite ); + + bool result = true; + foreach (var r in results) + { + if (StatusCode.IsBad( r )) + { + result = false; + break; + } + } + + return result; + } + + #endregion Node Write/Read Support + + #region DeleteNode Support + + /// + /// 删除一个节点的操作,除非服务器配置允许,否则引发异常,成功返回True,否则返回False + /// + /// 节点文本描述 + /// 是否删除成功 + public bool DeleteExsistNode( string tag ) + { + DeleteNodesItemCollection waitDelete = new DeleteNodesItemCollection( ); + + DeleteNodesItem nodesItem = new DeleteNodesItem( ) + { + NodeId = new NodeId( tag ), + }; + + m_session.DeleteNodes( + null, + waitDelete, + out StatusCodeCollection results, + out DiagnosticInfoCollection diagnosticInfos ); + + ClientBase.ValidateResponse( results, waitDelete ); + ClientBase.ValidateDiagnosticInfos( diagnosticInfos, waitDelete ); + + return !StatusCode.IsBad( results[0] ); + } + + #endregion DeleteNode Support + + #region Test Function + + /// + /// 新增一个节点数据 + /// + /// 父节点tag名称 + [Obsolete( "还未经过测试,无法使用" )] + public void AddNewNode( NodeId parent ) + { + // Create a Variable node. + AddNodesItem node2 = new AddNodesItem( ); + node2.ParentNodeId = new NodeId( parent ); + node2.ReferenceTypeId = ReferenceTypes.HasComponent; + node2.RequestedNewNodeId = null; + node2.BrowseName = new QualifiedName( "DataVariable1" ); + node2.NodeClass = NodeClass.Variable; + node2.NodeAttributes = null; + node2.TypeDefinition = VariableTypeIds.BaseDataVariableType; + + //specify node attributes. + VariableAttributes node2Attribtues = new VariableAttributes( ); + node2Attribtues.DisplayName = "DataVariable1"; + node2Attribtues.Description = "DataVariable1 Description"; + node2Attribtues.Value = new Variant( 123 ); + node2Attribtues.DataType = (uint)BuiltInType.Int32; + node2Attribtues.ValueRank = ValueRanks.Scalar; + node2Attribtues.ArrayDimensions = new UInt32Collection( ); + node2Attribtues.AccessLevel = AccessLevels.CurrentReadOrWrite; + node2Attribtues.UserAccessLevel = AccessLevels.CurrentReadOrWrite; + node2Attribtues.MinimumSamplingInterval = 0; + node2Attribtues.Historizing = false; + node2Attribtues.WriteMask = (uint)AttributeWriteMask.None; + node2Attribtues.UserWriteMask = (uint)AttributeWriteMask.None; + node2Attribtues.SpecifiedAttributes = (uint)NodeAttributesMask.All; + + node2.NodeAttributes = new ExtensionObject( node2Attribtues ); + + AddNodesItemCollection nodesToAdd = new AddNodesItemCollection { node2 }; + + m_session.AddNodes( + null, + nodesToAdd, + out AddNodesResultCollection results, + out DiagnosticInfoCollection diagnosticInfos ); + + ClientBase.ValidateResponse( results, nodesToAdd ); + ClientBase.ValidateDiagnosticInfos( diagnosticInfos, nodesToAdd ); + } + + #endregion Test Function + + #region Monitor Support + + /// + /// 新增一个订阅,需要指定订阅的关键字,订阅的tag名,以及回调方法 + /// + /// 关键字 + /// tag + /// 回调方法 + public void AddSubscription( string key, string tag, Action callback ) + { + AddSubscription( key, new string[] { tag }, callback ); + } + + /// + /// 新增一批订阅,需要指定订阅的关键字,订阅的tag名数组,以及回调方法 + /// + /// 关键字 + /// 节点名称数组 + /// 回调方法 + public void AddSubscription( string key, string[] tags, Action callback ) + { + Subscription m_subscription = new Subscription( m_session.DefaultSubscription ); + + m_subscription.PublishingEnabled = true; + m_subscription.PublishingInterval = 0; + m_subscription.KeepAliveCount = uint.MaxValue; + m_subscription.LifetimeCount = uint.MaxValue; + m_subscription.MaxNotificationsPerPublish = uint.MaxValue; + m_subscription.Priority = 100; + m_subscription.DisplayName = key; + + for (int i = 0; i < tags.Length; i++) + { + var item = new MonitoredItem + { + StartNodeId = new NodeId( tags[i] ), + AttributeId = Attributes.Value, + DisplayName = tags[i], + SamplingInterval = 100, + }; + item.Notification += ( MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs args ) => + { + callback?.Invoke( key, monitoredItem, args ); + }; + m_subscription.AddItem( item ); + } + + m_session.AddSubscription( m_subscription ); + m_subscription.Create( ); + + lock (dic_subscriptions) + { + if (dic_subscriptions.ContainsKey( key )) + { + // remove + dic_subscriptions[key].Delete( true ); + m_session.RemoveSubscription( dic_subscriptions[key] ); + dic_subscriptions[key].Dispose( ); + dic_subscriptions[key] = m_subscription; + } + else + { + dic_subscriptions.Add( key, m_subscription ); + } + } + } + + /// + /// 移除订阅消息,如果该订阅消息是批量的,也直接移除 + /// + /// 订阅关键值 + public void RemoveSubscription( string key ) + { + lock (dic_subscriptions) + { + if (dic_subscriptions.ContainsKey( key )) + { + // remove + dic_subscriptions[key].Delete( true ); + m_session.RemoveSubscription( dic_subscriptions[key] ); + dic_subscriptions[key].Dispose( ); + dic_subscriptions.Remove( key ); + } + } + } + + /// + /// 移除所有的订阅消息 + /// + public void RemoveAllSubscription( ) + { + lock (dic_subscriptions) + { + foreach (var item in dic_subscriptions) + { + item.Value.Delete( true ); + m_session.RemoveSubscription( item.Value ); + item.Value.Dispose( ); + } + dic_subscriptions.Clear( ); + } + } + + #endregion Monitor Support + + #region ReadHistory Support + + /// + /// read History data + /// + /// 节点的索引 + /// 开始时间 + /// 结束时间 + /// 读取的个数 + /// 是否包含边界 + /// 读取的数据列表 + public IEnumerable ReadHistoryRawDataValues( string tag, DateTime start, DateTime end, uint count = 1, bool containBound = false ) + { + HistoryReadValueId m_nodeToContinue = new HistoryReadValueId( ) + { + NodeId = new NodeId( tag ), + }; + + ReadRawModifiedDetails m_details = new ReadRawModifiedDetails + { + StartTime = start, + EndTime = end, + NumValuesPerNode = count, + IsReadModified = false, + ReturnBounds = containBound + }; + + HistoryReadValueIdCollection nodesToRead = new HistoryReadValueIdCollection( ); + nodesToRead.Add( m_nodeToContinue ); + + m_session.HistoryRead( + null, + new ExtensionObject( m_details ), + TimestampsToReturn.Both, + false, + nodesToRead, + out HistoryReadResultCollection results, + out DiagnosticInfoCollection diagnosticInfos ); + + ClientBase.ValidateResponse( results, nodesToRead ); + ClientBase.ValidateDiagnosticInfos( diagnosticInfos, nodesToRead ); + + if (StatusCode.IsBad( results[0].StatusCode )) + { + throw new ServiceResultException( results[0].StatusCode ); + } + + HistoryData values = ExtensionObject.ToEncodeable( results[0].HistoryData ) as HistoryData; + foreach (var value in values.DataValues) + { + yield return value; + } + } + + /// + /// 读取一连串的历史数据,并将其转化成指定的类型 + /// + /// 节点的索引 + /// 开始时间 + /// 结束时间 + /// 读取的个数 + /// 是否包含边界 + /// 读取的数据列表 + public IEnumerable ReadHistoryRawDataValues( string tag, DateTime start, DateTime end, uint count = 1, bool containBound = false ) + { + HistoryReadValueId m_nodeToContinue = new HistoryReadValueId( ) + { + NodeId = new NodeId( tag ), + }; + + ReadRawModifiedDetails m_details = new ReadRawModifiedDetails + { + StartTime = start.ToUniversalTime( ), + EndTime = end.ToUniversalTime( ), + NumValuesPerNode = count, + IsReadModified = false, + ReturnBounds = containBound + }; + + HistoryReadValueIdCollection nodesToRead = new HistoryReadValueIdCollection( ); + nodesToRead.Add( m_nodeToContinue ); + + m_session.HistoryRead( + null, + new ExtensionObject( m_details ), + TimestampsToReturn.Both, + false, + nodesToRead, + out HistoryReadResultCollection results, + out DiagnosticInfoCollection diagnosticInfos ); + + ClientBase.ValidateResponse( results, nodesToRead ); + ClientBase.ValidateDiagnosticInfos( diagnosticInfos, nodesToRead ); + + if (StatusCode.IsBad( results[0].StatusCode )) + { + throw new ServiceResultException( results[0].StatusCode ); + } + + HistoryData values = ExtensionObject.ToEncodeable( results[0].HistoryData ) as HistoryData; + foreach (var value in values.DataValues) + { + yield return (T)value.Value; + } + } + + #endregion ReadHistory Support + + #region BrowseNode Support + + /// + /// 浏览一个节点的引用 + /// + /// 节点值 + /// 引用节点描述 + public ReferenceDescription[] BrowseNodeReference( string tag ) + { + NodeId sourceId = new NodeId( tag ); + + // 该节点可以读取到方法 + BrowseDescription nodeToBrowse1 = new BrowseDescription( ); + + nodeToBrowse1.NodeId = sourceId; + nodeToBrowse1.BrowseDirection = BrowseDirection.Forward; + nodeToBrowse1.ReferenceTypeId = ReferenceTypeIds.Aggregates; + nodeToBrowse1.IncludeSubtypes = true; + nodeToBrowse1.NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable | NodeClass.Method); + nodeToBrowse1.ResultMask = (uint)BrowseResultMask.All; + + // 该节点无论怎么样都读取不到方法 + // find all nodes organized by the node. + BrowseDescription nodeToBrowse2 = new BrowseDescription( ); + + nodeToBrowse2.NodeId = sourceId; + nodeToBrowse2.BrowseDirection = BrowseDirection.Forward; + nodeToBrowse2.ReferenceTypeId = ReferenceTypeIds.Organizes; + nodeToBrowse2.IncludeSubtypes = true; + nodeToBrowse2.NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable); + nodeToBrowse2.ResultMask = (uint)BrowseResultMask.All; + + BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection( ); + nodesToBrowse.Add( nodeToBrowse1 ); + nodesToBrowse.Add( nodeToBrowse2 ); + + // fetch references from the server. + ReferenceDescriptionCollection references = FormUtils.Browse( m_session, nodesToBrowse, false ); + + return references.ToArray( ); + } + + #endregion BrowseNode Support + + #region Read Attributes Support + + /// + /// 读取一个节点的所有属性 + /// + /// 节点信息 + /// 节点的特性值 + public OpcNodeAttribute[] ReadNoteAttributes( string tag ) + { + NodeId sourceId = new NodeId( tag ); + ReadValueIdCollection nodesToRead = new ReadValueIdCollection( ); + + // attempt to read all possible attributes. + // 尝试着去读取所有可能的特性 + for (uint ii = Attributes.NodeClass; ii <= Attributes.UserExecutable; ii++) + { + ReadValueId nodeToRead = new ReadValueId( ); + nodeToRead.NodeId = sourceId; + nodeToRead.AttributeId = ii; + nodesToRead.Add( nodeToRead ); + } + + int startOfProperties = nodesToRead.Count; + + // find all of the pror of the node. + BrowseDescription nodeToBrowse1 = new BrowseDescription( ); + + nodeToBrowse1.NodeId = sourceId; + nodeToBrowse1.BrowseDirection = BrowseDirection.Forward; + nodeToBrowse1.ReferenceTypeId = ReferenceTypeIds.HasProperty; + nodeToBrowse1.IncludeSubtypes = true; + nodeToBrowse1.NodeClassMask = 0; + nodeToBrowse1.ResultMask = (uint)BrowseResultMask.All; + + BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection( ); + nodesToBrowse.Add( nodeToBrowse1 ); + + // fetch property references from the server. + ReferenceDescriptionCollection references = FormUtils.Browse( m_session, nodesToBrowse, false ); + + if (references == null) + { + return new OpcNodeAttribute[0]; + } + + for (int ii = 0; ii < references.Count; ii++) + { + // ignore external references. + if (references[ii].NodeId.IsAbsolute) + { + continue; + } + + ReadValueId nodeToRead = new ReadValueId( ); + nodeToRead.NodeId = (NodeId)references[ii].NodeId; + nodeToRead.AttributeId = Attributes.Value; + nodesToRead.Add( nodeToRead ); + } + + // read all values. + DataValueCollection results = null; + DiagnosticInfoCollection diagnosticInfos = null; + + m_session.Read( + null, + 0, + TimestampsToReturn.Neither, + nodesToRead, + out results, + out diagnosticInfos ); + + ClientBase.ValidateResponse( results, nodesToRead ); + ClientBase.ValidateDiagnosticInfos( diagnosticInfos, nodesToRead ); + + // process results. + + List nodeAttribute = new List( ); + for (int ii = 0; ii < results.Count; ii++) + { + OpcNodeAttribute item = new OpcNodeAttribute( ); + + // process attribute value. + if (ii < startOfProperties) + { + // ignore attributes which are invalid for the node. + if (results[ii].StatusCode == StatusCodes.BadAttributeIdInvalid) + { + continue; + } + + // get the name of the attribute. + item.Name = Attributes.GetBrowseName( nodesToRead[ii].AttributeId ); + + // display any unexpected error. + if (StatusCode.IsBad( results[ii].StatusCode )) + { + item.Type = Utils.Format( "{0}", Attributes.GetDataTypeId( nodesToRead[ii].AttributeId ) ); + item.Value = Utils.Format( "{0}", results[ii].StatusCode ); + } + + // display the value. + else + { + TypeInfo typeInfo = TypeInfo.Construct( results[ii].Value ); + + item.Type = typeInfo.BuiltInType.ToString( ); + + if (typeInfo.ValueRank >= ValueRanks.OneOrMoreDimensions) + { + item.Type += "[]"; + } + + item.Value = results[ii].Value;//Utils.Format("{0}", results[ii].Value); + } + } + + // process property value. + else + { + // ignore properties which are invalid for the node. + if (results[ii].StatusCode == StatusCodes.BadNodeIdUnknown) + { + continue; + } + + // get the name of the property. + item.Name = Utils.Format( "{0}", references[ii - startOfProperties] ); + + // display any unexpected error. + if (StatusCode.IsBad( results[ii].StatusCode )) + { + item.Type = String.Empty; + item.Value = Utils.Format( "{0}", results[ii].StatusCode ); + } + + // display the value. + else + { + TypeInfo typeInfo = TypeInfo.Construct( results[ii].Value ); + + item.Type = typeInfo.BuiltInType.ToString( ); + + if (typeInfo.ValueRank >= ValueRanks.OneOrMoreDimensions) + { + item.Type += "[]"; + } + + item.Value = results[ii].Value; //Utils.Format("{0}", results[ii].Value); + } + } + + nodeAttribute.Add( item ); + } + + return nodeAttribute.ToArray( ); + } + + /// + /// 读取一个节点的所有属性 + /// + /// 节点值 + /// 所有的数据 + public DataValue[] ReadNoteDataValueAttributes( string tag ) + { + NodeId sourceId = new NodeId( tag ); + ReadValueIdCollection nodesToRead = new ReadValueIdCollection( ); + + // attempt to read all possible attributes. + // 尝试着去读取所有可能的特性 + for (uint ii = Attributes.NodeId; ii <= Attributes.UserExecutable; ii++) + { + ReadValueId nodeToRead = new ReadValueId( ); + nodeToRead.NodeId = sourceId; + nodeToRead.AttributeId = ii; + nodesToRead.Add( nodeToRead ); + } + + int startOfProperties = nodesToRead.Count; + + // find all of the pror of the node. + BrowseDescription nodeToBrowse1 = new BrowseDescription( ); + + nodeToBrowse1.NodeId = sourceId; + nodeToBrowse1.BrowseDirection = BrowseDirection.Forward; + nodeToBrowse1.ReferenceTypeId = ReferenceTypeIds.HasProperty; + nodeToBrowse1.IncludeSubtypes = true; + nodeToBrowse1.NodeClassMask = 0; + nodeToBrowse1.ResultMask = (uint)BrowseResultMask.All; + + BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection( ); + nodesToBrowse.Add( nodeToBrowse1 ); + + // fetch property references from the server. + ReferenceDescriptionCollection references = FormUtils.Browse( m_session, nodesToBrowse, false ); + + if (references == null) + { + return new DataValue[0]; + } + + for (int ii = 0; ii < references.Count; ii++) + { + // ignore external references. + if (references[ii].NodeId.IsAbsolute) + { + continue; + } + + ReadValueId nodeToRead = new ReadValueId( ); + nodeToRead.NodeId = (NodeId)references[ii].NodeId; + nodeToRead.AttributeId = Attributes.Value; + nodesToRead.Add( nodeToRead ); + } + + // read all values. + DataValueCollection results = null; + DiagnosticInfoCollection diagnosticInfos = null; + + m_session.Read( + null, + 0, + TimestampsToReturn.Neither, + nodesToRead, + out results, + out diagnosticInfos ); + + ClientBase.ValidateResponse( results, nodesToRead ); + ClientBase.ValidateDiagnosticInfos( diagnosticInfos, nodesToRead ); + + return results.ToArray( ); + } + + #endregion Read Attributes Support + + #region Method Call Support + + /// + /// call a server method + /// + /// 方法的父节点tag + /// 方法的节点tag + /// 传递的参数 + /// 输出的结果值 + public object[] CallMethodByNodeId( string tagParent, string tag, params object[] args ) + { + if (m_session == null) + { + return null; + } + + IList outputArguments = m_session.Call( + new NodeId( tagParent ), + new NodeId( tag ), + args ); + + return outputArguments.ToArray( ); + } + + #endregion Method Call Support + + #region Private Methods + + /// + /// Raises the connect complete event on the main GUI thread. + /// + private void DoConnectComplete( object state ) + { + m_ConnectComplete?.Invoke( this, null ); + } + + private void CheckReturnValue( StatusCode status ) + { + if (!StatusCode.IsGood( status )) + throw new Exception( string.Format( "Invalid response from the server. (Response Status: {0})", status ) ); + } + + #endregion Private Methods + + #region Private Fields + + private ApplicationConfiguration m_configuration; + private Session m_session; + private bool m_IsConnected; //是否已经连接过 + private int m_reconnectPeriod = 10; // 重连状态 + private bool m_useSecurity; + + private SessionReconnectHandler m_reConnectHandler; + private EventHandler m_ReconnectComplete; + private EventHandler m_ReconnectStarting; + private EventHandler m_KeepAliveComplete; + private EventHandler m_ConnectComplete; + private EventHandler m_OpcStatusChange; + + private Dictionary dic_subscriptions; // 系统所有的节点信息 + + #endregion Private Fields + } +} \ No newline at end of file diff --git a/Plugins/Drivers/DriverOPCUaClient/OpcUaHelper/OpcUaStatusEventArgs.cs b/Plugins/Drivers/DriverOPCUaClient/OpcUaHelper/OpcUaStatusEventArgs.cs new file mode 100644 index 00000000..891d021a --- /dev/null +++ b/Plugins/Drivers/DriverOPCUaClient/OpcUaHelper/OpcUaStatusEventArgs.cs @@ -0,0 +1,65 @@ +using Opc.Ua; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace OpcUaHelper +{ + /// + /// OPC UA的状态更新消息 + /// + public class OpcUaStatusEventArgs + { + + + /// + /// 是否异常 + /// + public bool Error { get; set; } + /// + /// 时间 + /// + public DateTime Time { get; set; } + /// + /// 文本 + /// + public string Text { get; set; } + + /// + /// 转化为字符串 + /// + /// + public override string ToString( ) + { + return Error ? "[异常]" : "[正常]" + Time.ToString( " yyyy-MM-dd HH:mm:ss " ) + Text; + } + + + } + + /// + /// 读取属性过程中用于描述的 + /// + public class OpcNodeAttribute + { + /// + /// 属性的名称 + /// + public string Name { get; set; } + /// + /// 属性的类型描述 + /// + public string Type { get; set; } + /// + /// 操作结果状态描述 + /// + public StatusCode StatusCode { get; set; } + /// + /// 属性的值,如果读取错误,返回文本描述 + /// + public object Value { get; set; } + + } +} diff --git a/Plugins/PluginInterface/DriverReturnValueModel.cs b/Plugins/PluginInterface/DriverReturnValueModel.cs index a891b825..fad546ac 100644 --- a/Plugins/PluginInterface/DriverReturnValueModel.cs +++ b/Plugins/PluginInterface/DriverReturnValueModel.cs @@ -1,4 +1,6 @@ -using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -11,6 +13,7 @@ public class DriverReturnValueModel public object Value { get; set; } public object CookedValue { get; set; } public string Message { get; set; } + [JsonConverter(typeof(StringEnumConverter))] public VaribaleStatusTypeEnum StatusType { get; set; } public Guid VarId { get; set; } } diff --git a/Quickstarts.ReferenceClient.Config.xml b/Quickstarts.ReferenceClient.Config.xml deleted file mode 100644 index bbf98d04..00000000 --- a/Quickstarts.ReferenceClient.Config.xml +++ /dev/null @@ -1,89 +0,0 @@ - - - Quickstart Console Reference Client - urn:localhost:UA:Quickstarts:ReferenceClient - uri:opcfoundation.org:Quickstarts:ReferenceClient - Client_1 - - - - - - Directory - %LocalApplicationData%/OPC Foundation/pki/own - CN=Console Reference Client, C=US, S=Arizona, O=OPC Foundation, DC=localhost - - - - - Directory - %LocalApplicationData%/OPC Foundation/pki/issuer - - - - - Directory - %LocalApplicationData%/OPC Foundation/pki/trusted - - - - - Directory - %LocalApplicationData%/OPC Foundation/pki/rejected - - - - false - - - - - - - 600000 - 1048576 - 1048576 - 65535 - 4194304 - 65535 - 300000 - 3600000 - - - - 60000 - - opc.tcp://{0}:4840 - http://{0}:52601/UADiscovery - http://{0}/UADiscovery/Default.svc - - - 10000 - - - - - - - %LocalApplicationData%/OPC Foundation/Logs/Quickstarts.ReferenceClient.log.txt - true - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/drivers/net5.0/DriverModbusMaster.dll b/drivers/net5.0/DriverModbusMaster.dll index c0f8248b..ac6c033a 100644 Binary files a/drivers/net5.0/DriverModbusMaster.dll and b/drivers/net5.0/DriverModbusMaster.dll differ diff --git a/drivers/net5.0/DriverModbusMaster.pdb b/drivers/net5.0/DriverModbusMaster.pdb index 6e338f90..5de12cf4 100644 Binary files a/drivers/net5.0/DriverModbusMaster.pdb and b/drivers/net5.0/DriverModbusMaster.pdb differ diff --git a/drivers/net5.0/DriverOPCUaClient.deps.json b/drivers/net5.0/DriverOPCUaClient.deps.json index 78e01cd7..50f7307f 100644 --- a/drivers/net5.0/DriverOPCUaClient.deps.json +++ b/drivers/net5.0/DriverOPCUaClient.deps.json @@ -9,8 +9,6 @@ "DriverOPCUaClient/1.0.0": { "dependencies": { "OPCFoundation.NetStandard.Opc.Ua.Client": "1.4.367.75", - "OPCFoundation.NetStandard.Opc.Ua.Configuration": "1.4.367.75", - "OPCFoundation.NetStandard.Opc.Ua.Core": "1.4.367.75", "PluginInterface": "1.0.0" }, "runtime": { diff --git a/drivers/net5.0/DriverOPCUaClient.dll b/drivers/net5.0/DriverOPCUaClient.dll index 795784b2..fdf14d28 100644 Binary files a/drivers/net5.0/DriverOPCUaClient.dll and b/drivers/net5.0/DriverOPCUaClient.dll differ diff --git a/drivers/net5.0/DriverOPCUaClient.pdb b/drivers/net5.0/DriverOPCUaClient.pdb index d1203c47..b523b7b0 100644 Binary files a/drivers/net5.0/DriverOPCUaClient.pdb and b/drivers/net5.0/DriverOPCUaClient.pdb differ diff --git a/drivers/net5.0/DriverSiemensS7.dll b/drivers/net5.0/DriverSiemensS7.dll index 2311cb70..ca3eb594 100644 Binary files a/drivers/net5.0/DriverSiemensS7.dll and b/drivers/net5.0/DriverSiemensS7.dll differ diff --git a/drivers/net5.0/DriverSiemensS7.pdb b/drivers/net5.0/DriverSiemensS7.pdb index 9aff9dc8..4d16c5a3 100644 Binary files a/drivers/net5.0/DriverSiemensS7.pdb and b/drivers/net5.0/DriverSiemensS7.pdb differ diff --git a/drivers/net5.0/PluginInterface.dll b/drivers/net5.0/PluginInterface.dll index 07ac2860..e0735475 100644 Binary files a/drivers/net5.0/PluginInterface.dll and b/drivers/net5.0/PluginInterface.dll differ diff --git a/drivers/net5.0/PluginInterface.pdb b/drivers/net5.0/PluginInterface.pdb index 58840f14..78af26bd 100644 Binary files a/drivers/net5.0/PluginInterface.pdb and b/drivers/net5.0/PluginInterface.pdb differ diff --git a/drivers/net5.0/ref/DriverModbusMaster.dll b/drivers/net5.0/ref/DriverModbusMaster.dll index ef6793c9..5a474746 100644 Binary files a/drivers/net5.0/ref/DriverModbusMaster.dll and b/drivers/net5.0/ref/DriverModbusMaster.dll differ diff --git a/drivers/net5.0/ref/DriverOPCUaClient.dll b/drivers/net5.0/ref/DriverOPCUaClient.dll index b8d429b0..0b286876 100644 Binary files a/drivers/net5.0/ref/DriverOPCUaClient.dll and b/drivers/net5.0/ref/DriverOPCUaClient.dll differ diff --git a/drivers/net5.0/ref/DriverSiemensS7.dll b/drivers/net5.0/ref/DriverSiemensS7.dll index 019ffbc1..4382e3ba 100644 Binary files a/drivers/net5.0/ref/DriverSiemensS7.dll and b/drivers/net5.0/ref/DriverSiemensS7.dll differ diff --git a/iotgateway.db b/iotgateway.db index d0775f58..d63865c0 100644 Binary files a/iotgateway.db and b/iotgateway.db differ