EonaCat.OPC.UA 0.0.4

Prefix Reserved
dotnet add package EonaCat.OPC.UA --version 0.0.4
                    
NuGet\Install-Package EonaCat.OPC.UA -Version 0.0.4
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="EonaCat.OPC.UA" Version="0.0.4" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="EonaCat.OPC.UA" Version="0.0.4" />
                    
Directory.Packages.props
<PackageReference Include="EonaCat.OPC.UA" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add EonaCat.OPC.UA --version 0.0.4
                    
#r "nuget: EonaCat.OPC.UA, 0.0.4"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package EonaCat.OPC.UA@0.0.4
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=EonaCat.OPC.UA&version=0.0.4
                    
Install as a Cake Addin
#tool nuget:?package=EonaCat.OPC.UA&version=0.0.4
                    
Install as a Cake Tool

EonaCat.OPC.UA

EonaCat OPC UA client and server stack for .NET Standard 2.0.

The whole stack (binary encoding, UA-TCP transport, secure conversation, address space, sessions, subscriptions and certificate handling) is implemented from scratch on top of the base class library, so the package has no external NuGet dependencies.

Features

  • Transport — OPC UA TCP (opc.tcp://) with hello/acknowledge negotiation, message chunking, abort handling and security token renewal.
  • Encoding — complete OPC UA binary encoding including variants, matrices, extension objects and diagnostic information, with configurable limits against malformed input.
  • Security — every security policy defined by the specification: None, Basic128Rsa15, Basic256, Basic256Sha256, Aes128_Sha256_RsaOaep and Aes256_Sha256_RsaPss, in None, Sign and SignAndEncrypt modes.
  • Certificates — self signed application instance certificates are generated in process (X.509 v3 with subject alternative name, PKCS#12 packaging) without any external library.
  • Server — discovery, session, view, attribute, method, node management, subscription and monitored item service sets, plus hooks for history access.
  • Client — endpoint discovery, anonymous / user name / certificate authentication, read, write, browse, browse next, translate browse paths, method calls and subscriptions.
  • Model binding — expose plain CLR objects as nodes with a few attributes.
  • PubSub — UADP binary encoding of network and data set messages (Part 14) over UDP multicast/unicast, with a UaPubSubPublisher that periodically publishes node values from an AddressSpace and a UaPubSubSubscriber that applies received values back onto target nodes.
var configuration = new UaServerConfiguration
{
    ApplicationName = "My Server",
    EndpointHost = "localhost",
    Port = 4840,
    EndpointPath = "/MyServer"
};

using var server = new UaServer(configuration);

var ns = server.AddressSpace.AddNamespace("https://example.com/opcua");

var folder = server.AddressSpace.AddFolder(new NodeId(ObjectIds.ObjectsFolder), new NodeId("Demo", ns), "Demo", ns);
var counter = server.AddressSpace.AddVariable(folder.NodeId, new NodeId("Demo/Counter", ns), "Counter", new NodeId(DataTypeIds.Int32), 0, ns);

server.Start();

counter.SetValue(42); // subscribers are notified automatically

By default the server offers None, Basic256Sha256 (sign and sign and encrypt) and Aes256_Sha256_RsaPss. Override UaServerConfiguration.SecurityPolicies to change that, and supply UaServerConfiguration.ApplicationCertificate to use your own certificate instead of a generated one.

Exposing your own models

[UaObject(BrowseName = "Boiler")]
public class Boiler : INotifyPropertyChanged
{
    [UaVariable(MinimumSamplingInterval = 250)]
    public double Temperature { get; set; }

    [UaVariable(IsProperty = true, ReadOnly = true)]
    public string SerialNumber { get; } = "EONA-0001";

    [UaMethod]
    public bool Start(double setPoint) { ... }
}

server.AddressSpace.AddModel(new Boiler(), folder.NodeId, "Boiler", ns);

Properties become variables, methods become method nodes with generated input and output arguments, and members marked with [UaChildObject] become nested objects. When the model implements INotifyPropertyChanged the matching variables raise data change notifications.

Authenticating users

configuration.UserIdentityValidator = token =>
{
    if (token is UserNameIdentityToken user)
    {
        var password = Encoding.UTF8.GetString(user.Password);
        if (user.UserName == "admin" && password == "secret")
        {
            return new UserIdentity(user.UserName, UserTokenType.UserName, token);
        }

        throw new ServiceResultException(StatusCodes.BadUserAccessDenied);
    }

    return UserIdentity.Anonymous;
};

Passwords are transported encrypted with the server certificate and are decrypted and nonce checked before the validator is called.

Creating a client

using var client = new UaClient();

// Connect without security.
await client.ConnectAsync("opc.tcp://localhost:4840/MyServer");

// Or pick a secure endpoint and authenticate.
var endpoint = await UaClient.SelectEndpointAsync(
    "opc.tcp://localhost:4840/MyServer",
    MessageSecurityMode.SignAndEncrypt,
    SecurityPolicyUris.Basic256Sha256);

await client.ConnectAsync(endpoint, new UserNameIdentity("admin", "secret"));

var value = await client.ReadValueAsync(nodeId);
await client.WriteValueAsync(nodeId, 123);

foreach (var reference in await client.BrowseAsync(new NodeId(ObjectIds.ObjectsFolder)))
{
    Console.WriteLine(reference.BrowseName);
}

var results = await client.CallAsync(objectId, methodId, new[] { new Variant(20), new Variant(22) });

var subscription = await client.CreateSubscriptionAsync(publishingInterval: 500);
var item = await subscription.AddMonitoredItemAsync(nodeId, samplingInterval: 250);
item.Notification += (s, e) => Console.WriteLine(e.Value);

The client keeps publish requests outstanding automatically for as long as subscriptions exist and renews the security token of the channel before it expires.

Certificate trust

CertificateValidator checks the validity period, the application uri and the trust status of a peer certificate. Point it at directories to persist the trust lists:

configuration.CertificateValidator = new CertificateValidator
{
    TrustedStore = new CertificateStore(@"pki\trusted"),
    IssuerStore = new CertificateStore(@"pki\issuers"),
    RejectedStore = new CertificateStore(@"pki\rejected"),
    AutoAcceptUntrustedCertificates = false
};

configuration.CertificateValidator.CertificateValidationFailed += (s, e) =>
{
    // Inspect e.Certificate and set e.Accept to override the decision.
};

Extending the type system

Any structure can be sent over the wire by implementing IEncodeable and registering it:

public class MyStructure : IEncodeable
{
    public ExpandedNodeId TypeId => new ExpandedNodeId(new NodeId("MyStructure", 3));
    public ExpandedNodeId BinaryEncodingId => new ExpandedNodeId(new NodeId("MyStructure_Encoding_DefaultBinary", 3));

    public int Value { get; set; }

    public void Encode(IEncoder encoder) => encoder.WriteInt32(Value);
    public void Decode(IDecoder decoder) => Value = decoder.ReadInt32();
}

EncodeableFactory.GlobalFactory.AddEncodeableType(typeof(MyStructure));

PubSub

The publisher periodically samples node values from an AddressSpace and sends them as UADP network messages over UDP; the subscriber listens on the same address/port and writes the values it receives onto its own nodes.

PubSub publisher

var addressSpace = new AddressSpace();
var counter = addressSpace.AddVariable(ObjectIds.ObjectsFolder, new NodeId("Counter", 2), "Counter", new NodeId(DataTypeIds.Int32), 0);

var connection = new PubSubConnectionConfiguration("Connection1", "239.0.0.1", 4840) { PublisherId = (ushort)1 };

var dataSet = new PublishedDataSet("Set1");
dataSet.Fields.Add(new PublishedDataSetField("Counter", counter.NodeId));
connection.PublishedDataSets.Add(dataSet);

var writerGroup = new WriterGroupConfiguration(writerGroupId: 1, publishingIntervalMilliseconds: 500);
writerGroup.DataSetWriters.Add(new DataSetWriterConfiguration(dataSetWriterId: 1, publishedDataSetName: "Set1"));
connection.WriterGroups.Add(writerGroup);

using var publisher = new UaPubSubPublisher(connection, addressSpace);
publisher.Start();

// Update the source value on some interval; the publisher picks it up on the next publishing cycle.
counter.SetValue(counter.Value is int current ? current + 1 : 0);

PubSub subscriber

var addressSpace = new AddressSpace();
var target = addressSpace.AddVariable(ObjectIds.ObjectsFolder, new NodeId("RemoteCounter", 2), "RemoteCounter", new NodeId(DataTypeIds.Int32), 0);

// Same address, port, writer group id and data set writer id as the publisher above.
var connection = new PubSubConnectionConfiguration("Connection1", "239.0.0.1", 4840);

var reader = new DataSetReaderConfiguration(writerGroupId: 1, dataSetWriterId: 1);
reader.TargetNodeIds[0] = target.NodeId; // field index 0 ("Counter") maps to this node

var readerGroup = new ReaderGroupConfiguration();
readerGroup.DataSetReaders.Add(reader);
connection.ReaderGroups.Add(readerGroup);

using var subscriber = new UaPubSubSubscriber(connection, addressSpace);
subscriber.DataSetReceived += (sender, e) => Console.WriteLine("Received data set from writer " + e.DataSetMessage.DataSetWriterId);
subscriber.Start();

// target.Value is now kept in sync with the publisher's Counter node.

EonaCat OPC Simulator

A large OPC UA industrial simulation + OPC Designer HMI demo.

Run

dotnet run --project EonaCat.OPC.Example.Server
dotnet run --project EonaCat.OPC.Designer

The simulator endpoint is:

opc.tcp://localhost:48400/EonaCat/Simulator

Namespace:

urn:EonaCat:OPC:Simulator:Plant

The default Designer project contains a 72-widget industrial dashboard.

Simulated plant

  • 3 production areas
  • 18 devices (mixers, pumps, compressors, fans, ovens and tanks)
  • Temperature, pressure, flow, level, RPM, current, voltage, power and vibration
  • Setpoints, running state, remote mode, alarms, interlocks, modes, status and cycle counters
  • Device diagnostics and communication health
  • Steam, cooling water, compressed air, electricity and natural gas utilities
  • Plant-wide KPIs
  • Control methods: StartPlant, StopPlant, ResetAlarms
  • Stable explicit ns=2;s=... NodeIds and a stable namespace URI
  • Values update continuously once per second

Designer widgets

The canvas uses dedicated industrial layouts:

  • Gauge: round analog meter with needle and scale
  • Switch: physical-style ON/OFF switch
  • Value: large digital process value
  • Trend: miniature process trend chart
  • Alarm: alarm state indicator
  • Table: compact process table
  • Button: HMI action button
  • Text: display label

Dragging an OPC UA node onto the canvas preserves its complete binding metadata including NodeId, namespace index, namespace URI, identifier type and AttributeId.

EonaCat OPC Tool - production server bootstrap and OPC UA security

The OPC Tool can run one or more server configurations directly from the command line:

EonaCat.OPC.Tool.exe --server-config C:\OPC\Servers\server1.json --server-config C:\OPC\Servers\server2.json
EonaCat.OPC.Tool.exe --server-config-dir C:\OPC\Servers

Each configuration can set AutoStart to true. Use environment variables for certificate and OPC UA user passwords so credentials are not stored in the JSON file.

Production certificate layout

  • ApplicationCertificatePath: persistent .pfx/.p12 application instance certificate with private key.
  • TrustStorePath: persistent directory containing trusted .der/.crt/.cer peer certificates.
  • IssuerStorePath: persistent directory containing issuer certificates.
  • RejectedStorePath: persistent directory for rejected certificates.
  • CreateSelfSignedCertificate: creates and persists a self-signed application certificate when no application certificate exists.
  • AcceptSelfSignedCertificates: accepts otherwise-valid self-signed peer certificates. This is intentionally separate from accepting every untrusted certificate.
  • AutoAcceptUntrustedCertificates: development-only escape hatch; leave this false in production.

For unattended operation, set ApplicationCertificatePasswordEnvironmentVariable and PasswordEnvironmentVariable instead of putting passwords in JSON.

The tool supports the OPC UA security profiles implemented by EonaCat.OPC.UA: None, Basic128Rsa15, Basic256, Basic256Sha256, Aes128-Sha256-RsaOaep and Aes256-Sha256-RsaPss, plus Anonymous, UserName and X.509 user authentication. Endpoint discovery selects the server's advertised endpoint, so the client does not require a hard-coded endpoint list.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.0.4 110 9/5/2026
0.0.3 102 9/4/2026
0.0.2 99 9/2/2026
0.0.1 94 9/1/2026