Server code examples

Simple server code example

..
uses SysUtils, Simplex.Server, Simplex.Types;
..

var OpcUaServer: TOpcUaServer;
  ServerConfig: SpxServerConfig;
begin
  try
    ServerConfig.EndpointUrl := 'opc.tcp://localhost:4848';
    ServerConfig.ApplicationInfo.ApplicationName := 'Simplex OPC UA Server SDK';
    ServerConfig.ApplicationInfo.ApplicationUri := 'urn:localhost:Simplex OPC UA:Server SDK';
    ServerConfig.ApplicationInfo.ProductUri := 'urn:Simplex OPC UA:Server SDK';
    ServerConfig.ApplicationInfo.ManufacturerName := 'Simplex OPC UA';
    ServerConfig.ApplicationInfo.SoftwareVersion := '1.0';
    ServerConfig.ApplicationInfo.BuildNumber := '123';
    ServerConfig.ApplicationInfo.BuildDate := Now;
    ServerConfig.AllowAuthorizationAnonymous := True;
    ServerConfig.TraceLevel := tlNone;
    ServerConfig.Callback := nil;
    OpcUaServer := TOpcUaServer.Create(ServerConfig);

    if (not OpcUaServer.Open()) then
    begin
      OpcUaServer.Free();
      Writeln(Format('Error open OPC UA server, EndpointUrl=%s',
        [
ServerConfig.EndpointUrl]));
      Exit;
    end;
    Writeln(Format('Open OPC UA server - OK, EndpointUrl=%s',
      [ServerConfig.EndpointUrl]));

    Writeln('Press Enter for close OPC UA server ...');
    Readln;

    OpcUaServer.Free();
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.


Advanced server code example

 ..
uses SysUtils, Simplex.Server, Simplex.Types;
..

var Server: TSimplexServerTest; //
see details below
  EndpointUrl: string;
begin
  try
    EndpointUrl := 'opc.tcp://localhost:4848';
    Server := TSimplexServerTest.Create(EndpointUrl);
    Server.AddNodes();

    if (not Server.Open()) then
    begin
     
Server.Free();
      Writeln(Format('Error open OPC UA server, EndpointUrl=%s',
        [
EndpointUrl]));
      Exit;
    end;
    Writeln(Format('Open OPC UA server - OK, EndpointUrl=%s',
      [EndpointUrl]));

    Writeln('Press Enter for close OPC UA server ...');
    Readln;

    Server.Free();
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.


SimplexServerTest.pas

unit SimplexServerTest;

{$DEFINE SIMPLEX_SECURITY}
{$DEFINE SIMPLEX_AUTHENTICATION}

interface

uses SysUtils, Simplex.Server, Simplex.Types, System.Classes;

type
  TSimplexServerTest = class(SpxServerCallback)
  private
    FServer: TOpcUaServer;
    FStream: TStream;
    function GetNodeId(ANamespaceIndex: Word; AIdent: SpxUInt32): SpxNodeId; overload;
    function GetNodeId(ANamespaceIndex: Word; AIdent: SpxString): SpxNodeId; overload;
    function GetLocalizedText(ALocale, AText: SpxString): SpxLocalizedText;
    function GetDataValue(AValue: SpxInt32): SpxDataValue; overload;
    function GetDataValue(AValue: SpxString): SpxDataValue; overload;
    function GetFileStream(): TStream;
  private // callbacks
    function OnAuthorizationUserName(AUserName, APassword: SpxString): SpxBoolean; override;
    function OnWriteValue(ANodeId: SpxNodeId; AValue: SpxVariant): SpxStatusCode; override;
    function OnCallMethod(ANodeId, AParentNodeId: SpxNodeId;
      AInputArguments: SpxVariantArray; out AInputArgumentResults: SpxStatusCodeArray;
      out AOutputArguments: SpxVariantArray): SpxStatusCode; override;
    function OnReadHistory(ATimestampsToReturn: SpxTimestampsToReturn;
      AReleaseContinuationPoints, AIsReadModified: SpxBoolean;
      AStartTime, AEndTime: SpxDateTime; ANumValuesPerNode: SpxUInt32;
      AReturnBounds: SpxBoolean; ANodeId: SpxNodeId; AIndexRange: SpxString;
      ADataEncoding: SpxQualifiedName; AContinuationPoint: SpxByteArray;
      out AAnswerContinuationPoint: SpxByteArray; out AHistoryData: SpxDataValueArray)
      : SpxStatusCode; 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);
    destructor Destroy; override;
    procedure AddNodes();
    function Open(): boolean;
    procedure Close();
  end;

implementation

const
  MethodIdent = 'MyFolder.MyMethod';
  VariableIdent = 'MyFolder.MyVarStr';
  TestFileName = 'MyFile.dat';

/// <summary>
/// Constructor
/// </summary>
constructor TSimplexServerTest.Create(AEndpointUrl: string);
var ServerConfig: SpxServerConfig;
begin
  inherited Create();
  ServerConfig.EndpointUrl := AEndpointUrl;
  ServerConfig.ApplicationInfo.ApplicationName := 'Simplex OPC UA Server SDK';
  ServerConfig.ApplicationInfo.ApplicationUri := 'urn:localhost:Simplex OPC UA:Server SDK';
  ServerConfig.ApplicationInfo.ProductUri := 'urn:Simplex OPC UA:Server SDK';
  ServerConfig.ApplicationInfo.ManufacturerName := 'Simplex OPC UA';
  ServerConfig.ApplicationInfo.SoftwareVersion := '1.0';
  ServerConfig.ApplicationInfo.BuildNumber := '123';
  ServerConfig.ApplicationInfo.BuildDate := Now;
  ServerConfig.AllowSecurityNone := True;
  ServerConfig.AllowSecuritySignAndCrypt := False;
  ServerConfig.AllowAuthorizationAnonymous := True;
  ServerConfig.AllowAuthorizationUserName := False;
  ServerConfig.CertificateFileName := '';
  ServerConfig.TrustedCertificatesFolder := '';
{$IFDEF SIMPLEX_SECURITY}
  ServerConfig.AllowSecuritySignAndCrypt := True;
  ServerConfig.CertificateFileName := 'ServerCertificate.pfx';
  ServerConfig.TrustedCertificatesFolder := 'TrustedCertificates';
{$IFDEF SIMPLEX_AUTHENTICATION}
  ServerConfig.AllowAuthorizationUserName := True;
{$ENDIF}
{$ENDIF}
  ServerConfig.TraceLevel := tlWarning;
  ServerConfig.Callback := Self;
  FServer := TOpcUaServer.Create(ServerConfig);
  FStream := nil;
end;

/// <summary>
/// Destructor
/// </summary>
destructor TSimplexServerTest.Destroy();
begin
  FServer.Free();
  if (FStream <> nil) then
    FStream.Free();
  inherited;
end;

/// <summary>
/// Add nodes for testing
/// </summary>
procedure TSimplexServerTest.AddNodes();
const Namespace = 'http://www.simplexopcua.com/SimplexOpcUaServer';
var NamespaceIndex: Word;
  RootNodeId, VarNodeId, TypeNodeId: SpxNodeId;
  DisplayName, Description: SpxLocalizedText;
  EnumStrings: SpxLocalizedTextArray;
  EnumValues: SpxEnumValues;
  DataType: SpxDataType;
  InputArguments, OutputArguments: SpxArguments;
  i: integer;
  ChangeNodeParams: SpxChangeNodeParams;
  DataValue: SpxDataValue;
begin
  // Create namespace
  NamespaceIndex := FServer.CreateNamespace(Namespace);

  // Create root folder
  RootNodeId := GetNodeId(NamespaceIndex, 0);
  DisplayName := GetLocalizedText('en', 'MyFolder');
  Description := GetLocalizedText('en', 'Folder for testing');
  FServer.CreateFolder(RootNodeId, 'MyFolder', DisplayName, Description,
    GetNodeId(0, SpxNodeId_ObjectsFolder));

  // Create variable integer type
  VarNodeId := GetNodeId(NamespaceIndex, 1);
  DisplayName := GetLocalizedText('en', 'MyVarInt');
  Description := GetLocalizedText('en', 'Integer variable for testing');
  DataType.ValueRank := SpxValueRanks_Scalar;
  DataType.BuiltInType := SpxType_Int32;
  FServer.CreateVariable(VarNodeId, 'MyVarInt', DisplayName,
    Description, RootNodeId, DataType, False, False);

  // Set value of variable
  DataValue := GetDataValue(12345);
  FServer.SetVariableValue(VarNodeId, DataValue);

  // Create variable string type
  VarNodeId := GetNodeId(NamespaceIndex, VariableIdent);
  DisplayName := GetLocalizedText('en', 'MyVarStr');
  Description := GetLocalizedText('en', 'String variable for testing');
  DataType.ValueRank := SpxValueRanks_Scalar;
  DataType.BuiltInType := SpxType_String;
  FServer.CreateVariable(VarNodeId, 'MyVarStr', DisplayName,
    Description, RootNodeId, DataType, True, True);

  // Set value of variable
  DataValue := GetDataValue('value12345');
  FServer.SetVariableValue(VarNodeId, DataValue);

  // Create enum type
  TypeNodeId := GetNodeId(NamespaceIndex, 2);
  SetLength(EnumStrings, 3);
  for i := Low(EnumStrings) to High(EnumStrings) do
    EnumStrings[i] := GetLocalizedText('en', Format('Value%d', [i]));
  DisplayName := GetLocalizedText('en', 'MyEnumStringsType');
  Description := GetLocalizedText('en', 'Enum strings type for testing');
  FServer.CreateEnumVariableType(TypeNodeId, 'MyEnumStringsType', DisplayName,
    Description, EnumStrings);

  // Create variable of enum type
  VarNodeId := GetNodeId(NamespaceIndex, 3);
  DisplayName := GetLocalizedText('en', 'MyEnumStrings');
  Description := GetLocalizedText('en', 'Enumerated variable for testing');
  DataType.ValueRank := SpxValueRanks_Scalar;
  DataType.BuiltInType := SpxType_Enumeration;
  DataType.EnumTypeNodeId := TypeNodeId;
  FServer.CreateVariable(VarNodeId, 'MyEnumStrings', DisplayName,
    Description, RootNodeId, DataType, True, False);

  // Create enum type with values
  TypeNodeId := GetNodeId(NamespaceIndex, 4);
  SetLength(EnumValues, 3);
  for i := Low(EnumValues) to High(EnumValues) do
  begin
    EnumValues[i].Value := i * 2;
    EnumValues[i].Name := GetLocalizedText('en', Format('Value%d', [i]));
    EnumValues[i].Description := GetLocalizedText('en', Format('Value%d', [i]));
  end;
  DisplayName := GetLocalizedText('en', 'MyEnumValuesType');
  Description := GetLocalizedText('en', 'Enum values type for testing');
  FServer.CreateEnumVariableType(TypeNodeId, 'MyEnumValuesType', DisplayName,
    Description, EnumValues);

  // Create variable of enum type with values
  VarNodeId := GetNodeId(NamespaceIndex, 5);
  DisplayName := GetLocalizedText('en', 'MyEnumValues');
  Description := GetLocalizedText('en', 'Enumerated variable with values for testing');
  DataType.ValueRank := SpxValueRanks_Scalar;
  DataType.BuiltInType := SpxType_Enumeration;
  DataType.EnumTypeNodeId := TypeNodeId;
  FServer.CreateVariable(VarNodeId, 'MyEnumValues', DisplayName,
    Description, RootNodeId, DataType, True, False);

  // Create method
  VarNodeId := GetNodeId(NamespaceIndex, MethodIdent);
  DisplayName := GetLocalizedText('en', 'MyMethod');
  Description := GetLocalizedText('en', 'Method for testing');
  SetLength(InputArguments, 2);
  for i := 0 to Length(InputArguments)-1 do
  begin
    InputArguments[i].Name := Format('Argument%d', [i]);
    InputArguments[i].Description := GetLocalizedText('en', Format('Argument %d for testing', [i]));
    InputArguments[i].DataType.ValueRank := SpxValueRanks_Scalar;
    InputArguments[i].DataType.BuiltInType := SpxType_String;
  end;
  SetLength(OutputArguments, 1);
  OutputArguments[0].Name := 'OutArgument1';
  OutputArguments[0].Description := GetLocalizedText('en', 'Output argument for testing');
  OutputArguments[0].DataType.ValueRank := SpxValueRanks_Scalar;
  OutputArguments[0].DataType.BuiltInType := SpxType_String;
  FServer.CreateMethod(VarNodeId, 'MyMethod', DisplayName,
    Description, RootNodeId, InputArguments, OutputArguments);

  // Create file
  FStream := GetFileStream();
  VarNodeId := GetNodeId(NamespaceIndex, 'MyFolder.MyFile');
  DisplayName := GetLocalizedText('en', 'MyFile');
  Description := GetLocalizedText('en', 'File for testing');
  FServer.CreateFile(VarNodeId, 'MyFile', DisplayName,
    Description, RootNodeId, FStream, True);

  // Change node
  ChangeNodeParams.ChangeType := spxChange_DisplayName;
  ChangeNodeParams.DisplayName := GetLocalizedText('en', 'MyFile123');
  FServer.ChangeNode(VarNodeId, ChangeNodeParams);

  // Delete node
  //FServer.DeleteNode(VarNodeId);
end;

/// <summary>
/// Open OPC UA server
/// </summary>
function TSimplexServerTest.Open(): boolean;
begin
  Result := FServer.Open();
end;

/// <summary>
/// Close OPC UA server
/// </summary>
procedure TSimplexServerTest.Close();
begin
  FServer.Close();
end;

/// <summary>
/// Callback - UserName authorization
/// </summary>
function TSimplexServerTest.OnAuthorizationUserName(AUserName, APassword: SpxString): SpxBoolean;
begin
  Result := (AUserName = 'admin') and (APassword = '123');
end;

/// <summary>
/// Callback - write value
/// </summary>
function TSimplexServerTest.OnWriteValue(ANodeId: SpxNodeId; AValue: SpxVariant): SpxStatusCode;
var DataValue: SpxDataValue;
begin
  Result := SpxStatusCode_BadNodeIdUnknown;
  if (FServer.GetVariableValue(ANodeId, DataValue) = False) then Exit;

  DataValue.Value := AValue;
  DataValue.SourceTimestamp := Now;
  DataValue.ServerTimestamp := Now;

  FServer.SetVariableValue(ANodeId, DataValue);
  Result := SpxStatusCode_Good;
end;

/// <summary>
/// Callback - call method
/// </summary>
function TSimplexServerTest.OnCallMethod(ANodeId, AParentNodeId: SpxNodeId;
  AInputArguments: SpxVariantArray; out AInputArgumentResults: SpxStatusCodeArray;
  out AOutputArguments: SpxVariantArray): SpxStatusCode;
var i: integer;
begin
  Result := SpxStatusCode_BadNodeIdUnknown;
  AInputArgumentResults := nil;
  AOutputArguments := nil;
  if (ANodeId.IdentifierType <> SpxIdentifierType_String) or
    (ANodeId.IdentifierString <> MethodIdent) then Exit;
  Result := SpxStatusCode_Good;
  SetLength(AOutputArguments, 0);

  // must have two string arguments
  SetLength(AInputArgumentResults, 2);
  for i := Low(AInputArgumentResults) to High(AInputArgumentResults) do
  begin
    if (i >= Length(AInputArguments)) then
      AInputArgumentResults[i] := SpxStatusCode_BadArgumentsMissing
    else if (AInputArguments[i].ValueRank <> SpxValueRanks_Scalar) or
      (AInputArguments[i].BuiltInType <> SpxType_String) then
      AInputArgumentResults[i] := SpxStatusCode_BadInvalidArgument
    else AInputArgumentResults[i] := SpxStatusCode_Good;
  end;
  for i := Low(AInputArgumentResults) to High(AInputArgumentResults) do
    if (AInputArgumentResults[i] <> SpxStatusCode_Good) then Exit;

  // result is string concatenation
  SetLength(AOutputArguments, 1);
  AOutputArguments[0].ValueRank := SpxValueRanks_Scalar;
  AOutputArguments[0].BuiltInType := SpxType_String;
  AOutputArguments[0].AsString := AInputArguments[0].AsString + AInputArguments[1].AsString;
end;

/// <summary>
/// Callback - read history
/// </summary>
function TSimplexServerTest.OnReadHistory(ATimestampsToReturn: SpxTimestampsToReturn;
  AReleaseContinuationPoints, AIsReadModified: SpxBoolean;
  AStartTime, AEndTime: SpxDateTime; ANumValuesPerNode: SpxUInt32;
  AReturnBounds: SpxBoolean; ANodeId: SpxNodeId; AIndexRange: SpxString;
  ADataEncoding: SpxQualifiedName; AContinuationPoint: SpxByteArray;
  out AAnswerContinuationPoint: SpxByteArray; out AHistoryData: SpxDataValueArray): SpxStatusCode;
var i: integer;
begin
  Result := SpxStatusCode_BadNodeIdUnknown;
  AAnswerContinuationPoint := nil;
  AHistoryData := nil;
  if (ANodeId.IdentifierType <> SpxIdentifierType_String) or
    (ANodeId.IdentifierString <> VariableIdent) then Exit;
  Result := SpxStatusCode_Good;

  SetLength(AHistoryData, 10);
  for i := Low(AHistoryData) to High(AHistoryData) do
  begin
    AHistoryData[i].Value.ValueRank := SpxValueRanks_Scalar;
    AHistoryData[i].Value.BuiltInType := SpxType_String;
    AHistoryData[i].Value.AsString := Format('History_%d', [i + 1]);
    AHistoryData[i].StatusCode := SpxStatusCode_Good;
    AHistoryData[i].SourceTimestamp := Now;
    AHistoryData[i].SourcePicoseconds := 0;
    AHistoryData[i].ServerTimestamp := Now;
    AHistoryData[i].ServerPicoseconds := 0;
  end;
end;

/// <summary>
/// Callback - output trace message
/// </summary>
procedure TSimplexServerTest.OnLog(AMessage: SpxString);
begin
  Writeln(AMessage);
end;

/// <summary>
/// Callback - output warning message
/// </summary>
procedure TSimplexServerTest.OnLogWarning(AMessage: SpxString);
begin
  Writeln(Format('[WARNING] %s',
    [AMessage]));
end;

/// <summary>
/// Callback - output error message
/// </summary>
procedure TSimplexServerTest.OnLogError(AMessage: SpxString);
begin
  Writeln(Format('[ERROR] %s',
    [AMessage]));
end;

/// <summary>
/// Callback - output exception
/// </summary>
procedure TSimplexServerTest.OnLogException(AMessage: SpxString; AException: Exception);
begin
  Writeln(Format('[EXCEPTION] %s, E.Message=%s',
    [AMessage, AException.Message]));
end;

/// <summary>
/// Get numeric SpxNodeId
/// </summary>
function TSimplexServerTest.GetNodeId(ANamespaceIndex: Word; AIdent: SpxUInt32): SpxNodeId;
begin
  Result.NamespaceIndex := ANamespaceIndex;
  Result.IdentifierType := SpxIdentifierType_Numeric;
  Result.IdentifierNumeric := AIdent;
end;

/// <summary>
/// Get string SpxNodeId
/// </summary>
function TSimplexServerTest.GetNodeId(ANamespaceIndex: Word; AIdent: SpxString): SpxNodeId;
begin
  Result.NamespaceIndex := ANamespaceIndex;
  Result.IdentifierType := SpxIdentifierType_String;
  Result.IdentifierString := AIdent;
end;

/// <summary>
/// Get SpxLocalizedText
/// </summary>
function TSimplexServerTest.GetLocalizedText(ALocale, AText: SpxString): SpxLocalizedText;
begin
  Result.Locale := ALocale;
  Result.Text := AText;
end;

/// <summary>
/// Get Int32 data value
/// </summary>
function TSimplexServerTest.GetDataValue(AValue: SpxInt32): SpxDataValue;
begin
  Result.Value.ValueRank := SpxValueRanks_Scalar;
  Result.Value.BuiltInType := SpxType_Int32;
  Result.Value.AsInt32 := AValue;
  Result.StatusCode := SpxStatusCode_Good;
  Result.SourceTimestamp := Now;
  Result.SourcePicoseconds := 0;
  Result.ServerTimestamp := Now;
  Result.ServerPicoseconds := 0;
end;

/// <summary>
/// Get String data value
/// </summary>
function TSimplexServerTest.GetDataValue(AValue: SpxString): SpxDataValue;
begin
  Result.Value.ValueRank := SpxValueRanks_Scalar;
  Result.Value.BuiltInType := SpxType_String;
  Result.Value.AsString := AValue;
  Result.StatusCode := SpxStatusCode_Good;
  Result.SourceTimestamp := Now;
  Result.SourcePicoseconds := 0;
  Result.ServerTimestamp := Now;
  Result.ServerPicoseconds := 0;
end;

/// <summary>
/// Get File stream
/// </summary>
function TSimplexServerTest.GetFileStream(): TStream;
var Buffer: TBytes;
  i: Integer;
begin
  if FileExists(TestFileName) then
    Result := TFileStream.Create(TestFileName, fmOpenReadWrite)
  else begin
    Result := TMemoryStream.Create();
    SetLength(Buffer, 10);
    for i := Low(Buffer) to High(Buffer) do
      Buffer[i] := $30 + i;
    Result.WriteBuffer(Buffer, Length(Buffer));
  end;
end;

end.



Add license code example

1) Copy the license file (license.lic) to the Delphi project directory.

2)
Create an text file SimplexLecense.RC, then add line like:
    SimplexLicense RCDATA license.lic

3) Add file SimplexLecense.RC to you Delphi project.

4)
Use the following code to add licenses:

...
uses Classes, Windows, Simplex.License;
...
function AddLicense(): boolean;
var Stream: TResourceStream;
  License: TBytes;
begin
  Result := False;
  try
    Stream := TResourceStream.Create(hInstance, 'SimplexLicense', RT_RCDATA);
    SetLength(License, Stream.Size);
    Stream.Read(License[0], Stream.Size);
    Stream.Free();

    if (AddLicence(License) = False) then Exit;

    Result := True;
  except
  end;
end;