unit SimplexClientTest;
{$DEFINE SIMPLEX_SECURITY} {$DEFINE SIMPLEX_AUTHENTICATION}
interface
uses SysUtils, DateUtils, Simplex.Client, Simplex.Types;
type TWriteln = procedure(AInfo: string) of object; TProcessMessages = procedure() of object;
TSimplexClientTest = class(SpxClientCallback) private FWriteln: TWriteln; FProcessMessages: TProcessMessages; FClient: TOpcUaClient; FClientConfig: SpxClientConfig; function ExistServer(AServers: SpxApplicationDescriptionArray): boolean; function ExistSecurityNone(AEndpoints: SpxEndpointDescriptionArray): boolean; function GetNodeId(ANamespaceIndex: Word; AIdent: Cardinal): SpxNodeId; overload; function GetNodeId(ANamespaceIndex: Word; AIdent: string): SpxNodeId; overload; function GetReadValueId(ANodeId: SpxNodeId): SpxReadValueId; function GetWriteValue(ANodeId: SpxNodeId; AValue: string): SpxWriteValue; function GetValue(AValue: string): SpxVariant; function GetMonitoredItem(ANodeId: SpxNodeId; AClientHandle: Cardinal; ASamplingInterval: double): SpxMonitoredItem; function GetModifyItem(AMonitoredItemId: Cardinal; AClientHandle: Cardinal; ASamplingInterval: double): SpxModifyMonitoredItem; procedure ConsoleWriteln(AInfo: string); procedure ConsoleProcessMessages(); private // callbacks procedure OnChannelEvent(AEvent: SpxChannelEvent; AStatus: SpxStatusCode); override; procedure OnMonitoredItemChange(ASubscriptionId: SpxUInt32; AValues : SpxMonitoredItemNotifyArray); override; procedure OnLog(AMessage: string); override; procedure OnLogWarning(AMessage: string); override; procedure OnLogError(AMessage: string); override; procedure OnLogException(AMessage: string; AException: Exception); override; public constructor Create(AEndpointUrl: string); overload; constructor Create(AEndpointUrl: string; AWriteln: TWriteln; AProcessMessages: TProcessMessages); overload; destructor Destroy; override; function Connect(): boolean; procedure Disconnect(); function ReadData(): boolean; end;
implementation
const FormatDT = 'yy.mm.dd hh:nn:ss.zzz'; Localhost = 'localhost'; VariableClientHandle = 1;
/// <summary> /// Constructor /// </summary> constructor TSimplexClientTest.Create(AEndpointUrl: string); begin Create(AEndpointUrl, ConsoleWriteln, ConsoleProcessMessages); end;
/// <summary> /// Constructor /// </summary> constructor TSimplexClientTest.Create(AEndpointUrl: string; AWriteln: TWriteln; AProcessMessages: TProcessMessages); begin inherited Create(); FWriteln := AWriteln; FProcessMessages := AProcessMessages; FClientConfig.EndpointUrl := AEndpointUrl; FClientConfig.ApplicationInfo.ApplicationName := 'Simplex OPC UA Cllient SDK'; // application uri from ClientCertificate.der FClientConfig.ApplicationInfo.ApplicationUri := 'urn:localhost:Simplex OPC UA:Client SDK'; FClientConfig.ApplicationInfo.ProductUri := 'urn:Simplex OPC UA:Client SDK'; FClientConfig.ApplicationInfo.ManufacturerName := 'Simplex OPC UA'; FClientConfig.ApplicationInfo.SoftwareVersion := '1.0'; FClientConfig.ApplicationInfo.BuildNumber := '123'; FClientConfig.ApplicationInfo.BuildDate := Now; FClientConfig.CertificateFileName := ''; FClientConfig.PrivateKeyFileName := ''; FClientConfig.TrustedCertificatesFolder := ''; FClientConfig.ServerCertificateFileName := ''; FClientConfig.Authentication.AuthenticationType := SpxAuthenticationType_Anonymous; FClientConfig.Authentication.UserName := ''; FClientConfig.Authentication.Password := ''; {$IFDEF SIMPLEX_SECURITY} FClientConfig.CertificateFileName := 'ClientCertificate.der'; FClientConfig.PrivateKeyFileName := 'ClientPrivateKey.pem'; FClientConfig.TrustedCertificatesFolder := 'TrustedCertificates'; FClientConfig.ServerCertificateFileName := 'TrustedCertificates\ServerCertificate.der'; {$IFDEF SIMPLEX_AUTHENTICATION} FClientConfig.Authentication.AuthenticationType := SpxAuthenticationType_UserName; FClientConfig.Authentication.UserName := 'root'; FClientConfig.Authentication.Password := 'secret'; {$ENDIF} {$ENDIF} FClientConfig.ConnectTimeout := 30; FClientConfig.SessionTimeout := 300; FClientConfig.TraceLevel := tlDebug; FClientConfig.Callback := Self; FClient := TOpcUaClient.Create(FClientConfig); end;
/// <summary> /// Destructor /// </summary> destructor TSimplexClientTest.Destroy(); begin FClient.Free(); inherited; end;
/// <summary> /// Connect to OPC UA server with security mode - none /// </summary> function TSimplexClientTest.Connect(): boolean; var Servers: SpxApplicationDescriptionArray; Endpoints: SpxEndpointDescriptionArray; begin Result := False;
// connect {$IFDEF SIMPLEX_SECURITY} if (not FClient.Connect(SpxMessageSecurityMode_SignAndEncrypt, SpxSecurityPolicy_Basic128Rsa15)) then Exit; {$ELSE} if (not FClient.Connect()) then Exit; {$ENDIF}
// exist OPC UA server if (not FClient.FindServers(Servers)) then Exit; if (not ExistServer(Servers)) then Exit;
// exist endpoint with security mode - none if (not FClient.GetEndpoints(Endpoints)) then Exit; if (not ExistSecurityNone(Endpoints)) then Exit;
// init session if (not FClient.InitSession()) then Exit;
Result := True; end;
/// <summary> /// Disconnect from OPC UA server /// </summary> procedure TSimplexClientTest.Disconnect(); begin FClient.Disconnect(); end;
/// <summary> /// Read data /// </summary> function TSimplexClientTest.ReadData(): boolean; var NodeId: SpxNodeId; ReadValues: SpxReadValueIdArray; Values: SpxDataValueArray; NodesToRead: SpxHistoryReadValueIdArray; HistoryValues: SpxHistoryReadResultArray; WriteValues: SpxWriteValueArray; StatusCodes: SpxStatusCodeArray; MethodsToCall: SpxCallMethodRequestArray; MethodResults: SpxCallMethodResultArray; BrowseDescriptions: SpxBrowseDescriptionArray; BrowseResults: SpxBrowseResultArray; Subscription: SpxSubscription; SubscriptionResult: SpxSubscriptionResult; SubcriptionId, MonitoredItemId: SpxUInt32; SubcriptionIds, MonitoredItemIds: SpxUInt32Array; MonitoredItems: SpxMonitoredItemArray; MonitoredItemResult: SpxMonitoredItemResultArray; ModifyItems: SpxModifyMonitoredItemArray; ModifyItemResult: SpxModifyMonitoredItemResultArray; ContinuationPoints: SpxByteArrayArray; i: integer; EndTime: TDateTime; begin Result := False;
// Read current time NodeId := GetNodeId(0, SpxNodeId_Server_ServerStatus_CurrentTime); SetLength(ReadValues, 1); ReadValues[0] := GetReadValueId(NodeId); if (not FClient.ReadValue(0, SpxTimestampsToReturn_Both, ReadValues, Values)) then Exit; if (Length(Values) = 0) then Exit; FWriteln(Format('ReadValue - OK, StatusCode = %u', [Ord(Values[0].StatusCode)])); FWriteln(Format('CurrentUtcTime = %s', [FormatDateTime(FormatDT, Values[0].Value.AsDateTime)]));
// Read history value from Simplex OPC UA Server SDK - see "Advanced server code example" NodeId := GetNodeId(1, 'MyFolder.MyVarStr'); SetLength(NodesToRead, 1); NodesToRead[0].NodeId := NodeId; NodesToRead[0].IndexRange := ''; NodesToRead[0].DataEncoding.NamespaceIndex := 0; NodesToRead[0].DataEncoding.Name := ''; NodesToRead[0].ContinuationPoint := nil; if (not FClient.ReadHistory(False, IncHour(Now, -3), Now, 1000, True, SpxTimestampsToReturn_Both, False, NodesToRead, HistoryValues)) then Exit; if (Length(HistoryValues) = 0) then Exit; FWriteln(Format('ReadHistory - OK, StatusCode = %u', [Ord(HistoryValues[0].StatusCode)])); for i := Low(HistoryValues[0].HistoryData) to High(HistoryValues[0].HistoryData) do FWriteln(Format('HistoryValue[%d] = %s', [i, HistoryValues[0].HistoryData[i].Value.AsString]));
// Write value to Simplex OPC UA Server SDK - see "Advanced server code example" NodeId := GetNodeId(1, 'MyFolder.MyVarStr'); SetLength(WriteValues, 1); WriteValues[0] := GetWriteValue(NodeId, 'value123'); if (not FClient.WriteValue(WriteValues, StatusCodes)) then Exit; if (Length(StatusCodes) = 0) then Exit; FWriteln(Format('WriteValue - OK, StatusCode = %u', [Ord(StatusCodes[0])]));
// Call method from Simplex OPC UA Server SDK - see "Advanced server code example" NodeId := GetNodeId(1, 'MyFolder.MyMethod'); SetLength(MethodsToCall, 1); MethodsToCall[0].MethodId := NodeId; MethodsToCall[0].ObjectId := GetNodeId(1, 0); SetLength(MethodsToCall[0].InputArguments, 2); MethodsToCall[0].InputArguments[0] := GetValue('One'); MethodsToCall[0].InputArguments[1] := GetValue('Two'); if (not FClient.CallMethod(MethodsToCall, MethodResults)) then Exit; if (Length(MethodResults) = 0) then Exit; FWriteln(Format('CallMethod - OK, StatusCode = %u', [Ord(MethodResults[0].StatusCode)])); for i := Low(MethodResults[0].OutputArguments) to High(MethodResults[0].OutputArguments) do FWriteln(Format('MethodOutputArgument[%d] = %s', [i, MethodResults[0].OutputArguments[i].AsString]));
// Browse from Root ContinuationPoints := nil; SetLength(BrowseDescriptions, 1); BrowseDescriptions[0].NodeId := GetNodeId(0, SpxNodeId_RootFolder); BrowseDescriptions[0].BrowseDirection := SpxBrowseDirection_Forward; BrowseDescriptions[0].ReferenceTypeId := GetNodeId(0, SpxNodeId_HierarchicalReferences); BrowseDescriptions[0].IncludeSubtypes := True; BrowseDescriptions[0].NodeClassMask := SpxUInt32(SpxNodeClass_Unspecified); BrowseDescriptions[0].ResultMask := SpxUInt32(SpxBrowseResultMask_All); while True do begin if (Length(ContinuationPoints) = 0) then begin if (not FClient.Browse(1000, BrowseDescriptions, BrowseResults)) then Exit; end else begin if (not FClient.BrowseNext(False, ContinuationPoints, BrowseResults)) then Exit; end; if (Length(BrowseResults) = 0) then Exit; FWriteln(Format('Browse - OK, StatusCode = %u', [Ord(BrowseResults[0].StatusCode)])); for i := Low(BrowseResults[0].References) to High(BrowseResults[0].References) do FWriteln(Format('BrowseDisplayName[%d] = %s', [i, BrowseResults[0].References[i].DisplayName.Text]));
// continue browse (if more than 1000 references) if (Length(BrowseResults[0].ContinuationPoint) > 0) then begin SetLength(ContinuationPoints, 1); ContinuationPoints[0] := BrowseResults[0].ContinuationPoint; end else Break; end;
// Create subscription Subscription.RequestedPublishingInterval := 2000; Subscription.RequestedLifetimeCount := 200; Subscription.RequestedMaxKeepAliveCount := 20; Subscription.MaxNotificationsPerPublish := 20000; Subscription.Priority := 0; if (not FClient.CreateSubscription(True, Subscription, SubcriptionId, SubscriptionResult)) then Exit; FWriteln(Format('CreateSubscription - OK, SubscriptionId = %d, '+ 'RevisedPublishingInterval=%f, RevisedLifetimeCount=%d, RevisedMaxKeepAliveCount=%d', [SubcriptionId, SubscriptionResult.RevisedPublishingInterval, SubscriptionResult.RevisedLifetimeCount, SubscriptionResult.RevisedMaxKeepAliveCount]));
// Modify subscription Subscription.RequestedPublishingInterval := 3000; Subscription.RequestedLifetimeCount := 300; Subscription.RequestedMaxKeepAliveCount := 30; Subscription.MaxNotificationsPerPublish := 30000; Subscription.Priority := 1; if (not FClient.ModifySubscriptiont(SubcriptionId, Subscription, SubscriptionResult)) then Exit; FWriteln(Format('ModifySubscriptiont - OK, RevisedPublishingInterval=%f, '+ 'RevisedLifetimeCount=%d, RevisedMaxKeepAliveCount=%d', [SubscriptionResult.RevisedPublishingInterval, SubscriptionResult.RevisedLifetimeCount, SubscriptionResult.RevisedMaxKeepAliveCount]));
// Create monitored item NodeId := GetNodeId(0, SpxNodeId_Server_ServerStatus_CurrentTime); SetLength(MonitoredItems, 1); MonitoredItems[0] := GetMonitoredItem(NodeId, VariableClientHandle, 500); if (not FClient.CreateMonitoredItems(SubcriptionId, SpxTimestampsToReturn_Both, MonitoredItems, MonitoredItemResult)) then Exit; if (Length(MonitoredItemResult) = 0) then Exit; FWriteln(Format('CreateMonitoredItems - OK, StatusCode=%u, MonitoredItemId=%d, '+ 'SamplingInterval=%f, QueueSize=%d', [MonitoredItemResult[0].StatusCode, MonitoredItemResult[0].MonitoredItemId, MonitoredItemResult[0].SamplingInterval, MonitoredItemResult[0].QueueSize])); MonitoredItemId := MonitoredItemResult[0].MonitoredItemId;
// Modify monitored item SetLength(ModifyItems, 1); ModifyItems[0] := GetModifyItem(MonitoredItemId, VariableClientHandle, 600); if (not FClient.ModifyMonitoredItems(SubcriptionId, SpxTimestampsToReturn_Both, ModifyItems, ModifyItemResult)) then Exit; if (Length(ModifyItemResult) = 0) then Exit; FWriteln(Format('ModifyMonitoredItems - OK, StatusCode=%u, SamplingInterval=%f, '+ 'QueueSize=%d', [ModifyItemResult[0].StatusCode, ModifyItemResult[0].SamplingInterval, ModifyItemResult[0].QueueSize]));
// Set monitoring mode SetLength(MonitoredItemIds, 1); MonitoredItemIds[0] := MonitoredItemId; if (not FClient.SetMonitoringMode(SubcriptionId, SpxMonitoringMode_Reporting, MonitoredItemIds, StatusCodes)) then Exit; if (Length(StatusCodes) = 0) then Exit; FWriteln(Format('SetMonitoringMode - OK, StatusCode=%u', [StatusCodes[0]]));
// Wait for receive data EndTime := IncSecond(Now, 30); while (Now < EndTime) do FProcessMessages();
// Delete monitored item if (not FClient.DeleteMonitoredItems(SubcriptionId, MonitoredItemIds, StatusCodes)) then Exit; if (Length(StatusCodes) = 0) then Exit; FWriteln(Format('DeleteMonitoredItems - OK, StatusCode=%u', [StatusCodes[0]]));
// Delete subscription SetLength(SubcriptionIds, 1); SubcriptionIds[0] := SubcriptionId; if (not FClient.DeleteSubscriptions(SubcriptionIds, StatusCodes)) then Exit; if (Length(StatusCodes) = 0) then Exit; FWriteln(Format('DeleteSubscriptions- OK, StatusCode = %u', [Ord(StatusCodes[0])]));
Result := True; end;
/// <summary> /// Callback - change channel state /// </summary> procedure TSimplexClientTest.OnChannelEvent(AEvent: SpxChannelEvent; AStatus: SpxStatusCode); begin if (AEvent = SpxChannelEvent_Disconnected) then FWriteln('OnChannelEvent: Disconnected'); end;
/// <summary> /// Callback - change monitored item /// </summary> procedure TSimplexClientTest.OnMonitoredItemChange(ASubscriptionId: SpxUInt32; AValues : SpxMonitoredItemNotifyArray); var i: integer; begin FWriteln(Format('OnMonitoredItemChange: Values.Count=%d', [Length(AValues)])); for i := Low(AValues) to High(AValues) do if (AValues[i].ClientHandle = VariableClientHandle) then FWriteln(Format('CurrentUtcTime = %s', [FormatDateTime(FormatDT, AValues[i].Value.Value.AsDateTime)])); end;
/// <summary> /// Callback - output trace message /// </summary> procedure TSimplexClientTest.OnLog(AMessage: SpxString); begin FWriteln(AMessage); end;
/// <summary> /// Callback - output warning message /// </summary> procedure TSimplexClientTest.OnLogWarning(AMessage: SpxString); begin FWriteln(Format('[WARNING] %s', [AMessage])); end;
/// <summary> /// Callback - output error message /// </summary> procedure TSimplexClientTest.OnLogError(AMessage: SpxString); begin FWriteln(Format('[ERROR] %s', [AMessage])); end;
/// <summary> /// Callback - output exception /// </summary> procedure TSimplexClientTest.OnLogException(AMessage: SpxString; AException: Exception); begin FWriteln(Format('[EXCEPTION] %s, E.Message=%s', [AMessage, AException.Message])); end;
/// <summary> /// Exist OPC UA server /// </summary> function TSimplexClientTest.ExistServer(AServers: SpxApplicationDescriptionArray): boolean; var i: integer; begin Result := False; for i := Low(AServers) to High(AServers) do if (AServers[i].ApplicationType = SpxApplicationType_Server) or (AServers[i].ApplicationType = SpxApplicationType_ClientAndServer) then begin Result := True; Exit; end; end;
/// <summary> /// Exist endpoint with security mode - none /// </summary> function TSimplexClientTest.ExistSecurityNone(AEndpoints: SpxEndpointDescriptionArray): boolean; var i: integer; begin Result := False; for i := Low(AEndpoints) to High(AEndpoints) do if (AEndpoints[i].SecurityMode = SpxMessageSecurityMode_None) then begin Result := True; Exit; end; end;
/// <summary> /// Get numeric SpxNodeId /// </summary> function TSimplexClientTest.GetNodeId(ANamespaceIndex: Word; AIdent: Cardinal): SpxNodeId; begin Result.NamespaceIndex := ANamespaceIndex; Result.IdentifierType := SpxIdentifierType_Numeric; Result.IdentifierNumeric := AIdent; end;
/// <summary> /// Get string SpxNodeId /// </summary> function TSimplexClientTest.GetNodeId(ANamespaceIndex: Word; AIdent: string): SpxNodeId; begin Result.NamespaceIndex := ANamespaceIndex; Result.IdentifierType := SpxIdentifierType_String; Result.IdentifierString := AIdent; end;
/// <summary> /// Get parameters for read value /// </summary> function TSimplexClientTest.GetReadValueId(ANodeId: SpxNodeId): SpxReadValueId; begin Result.NodeId := ANodeId; Result.AttributeId := SpxAttributes_Value; Result.IndexRange := ''; Result.DataEncoding.NamespaceIndex := 0; Result.DataEncoding.Name := ''; end;
/// <summary> /// Get parameters for write value /// </summary> function TSimplexClientTest.GetWriteValue(ANodeId: SpxNodeId; AValue: string): SpxWriteValue; begin Result.NodeId := ANodeId; Result.AttributeId := SpxAttributes_Value; Result.IndexRange := ''; Result.Value.Value := GetValue(AValue); Result.Value.StatusCode := SpxStatusCode_Good; Result.Value.SourceTimestamp := 0; Result.Value.SourcePicoseconds := 0; Result.Value.ServerTimestamp := 0; Result.Value.ServerPicoseconds := 0; end;
/// <summary> /// Get value /// </summary> function TSimplexClientTest.GetValue(AValue: string): SpxVariant; begin Result.ValueRank := SpxValueRanks_Scalar; Result.BuiltInType := SpxType_String; Result.AsString := AValue; end;
/// <summary> /// Get monitored item /// </summary> function TSimplexClientTest.GetMonitoredItem(ANodeId: SpxNodeId; AClientHandle: Cardinal; ASamplingInterval: double): SpxMonitoredItem; begin Result.NodeId := ANodeId; Result.AttributeId := SpxAttributes_Value; Result.IndexRange := ''; Result.DataEncoding.NamespaceIndex := 0; Result.DataEncoding.Name := ''; Result.MonitoringMode := SpxMonitoringMode_Reporting; Result.ClientHandle := AClientHandle; Result.SamplingInterval := ASamplingInterval; Result.QueueSize := 1000; Result.DiscardOldest := True; end;
/// <summary> /// Get modify monitored item /// </summary> function TSimplexClientTest.GetModifyItem(AMonitoredItemId: Cardinal; AClientHandle: Cardinal; ASamplingInterval: double): SpxModifyMonitoredItem; begin Result.MonitoredItemId := AMonitoredItemId; Result.ClientHandle := AClientHandle; Result.SamplingInterval := ASamplingInterval; Result.DiscardOldest := True; end;
/// <summary> /// Console output /// </summary> procedure TSimplexClientTest.ConsoleWriteln(AInfo: string); begin Writeln(AInfo); end;
/// <summary> /// Console process messages /// </summary> procedure TSimplexClientTest.ConsoleProcessMessages(); begin Sleep(100); end;
end.
|