![]() |
VOOZH | about |
dotnet add package Opc.Ua.Expressions --version 1.1.22
NuGet\Install-Package Opc.Ua.Expressions -Version 1.1.22
<PackageReference Include="Opc.Ua.Expressions" Version="1.1.22" />
<PackageVersion Include="Opc.Ua.Expressions" Version="1.1.22" />Directory.Packages.props
<PackageReference Include="Opc.Ua.Expressions" />Project file
paket add Opc.Ua.Expressions --version 1.1.22
#r "nuget: Opc.Ua.Expressions, 1.1.22"
#:package Opc.Ua.Expressions@1.1.22
#addin nuget:?package=Opc.Ua.Expressions&version=1.1.22Install as a Cake Addin
#tool nuget:?package=Opc.Ua.Expressions&version=1.1.22Install as a Cake Tool
The OPC UA Expressions is a library that can maybe save the world or not.
Install-Package Opc.Ua.Expressions -Version 1.0.1
First of you create the model you want to read from your OPC UA server.
public class Universe {
public List<Star> Stars { get; set; }
public List<Planet> Planets { get; set; }
public long Age { get; set; }
public Planet BestPlanet { get; set; }
}
public class Planet {
public string Name {get; set;}
public long Population { get; set; }
}
public class Star {
public bool IsHabitable { get; set; }
}
This is the minimal you have to do to read the structured data from an OPC device. Next I will show how to actually use this model.
using Opc.Ua.Expressions;
using Opc.Ua.Client;
using Opc.Ua.Expressions;
Session session = ... // your OPC UA session instance
// Create the client
OpcUaClient client = new OpcUaClient(session);
string name = client.ReadValueAsync<Universe, string>(x => x.Planets[2].Name);
// The line above is the same as if you read the following NodeId
// ns=3;s="Universe"."Planets"[2]."Name"
This way you never need to type an address again. Everything is strongly typed using expressions.
NOTE: The library does not create or mantain your OPC UA session. It uses your session. When your session is no longer valid you will need to create a new client or replace the session.
Not only do we support interaction with simple types like int, float ... but the library also supports complex types and lists. Using the same model as defined above we can do the following:
// Read the second planet from a list
Planet planet = await client.ReadValueAsync<Universe, Planet>(x => x.Planets[2]);
// Read all planets
List<Planet> planets = await client.ReadValueAsync<Universe, List<Planet>>(x => x.Planets);
// Read all planets as an array
Planet[] planets = await client.ReadValueAsync<Universe, Planet[]>(x => x.Planets);
// Writing a complex object
var result = await client.WriteValueAsync(x => x.Stars[0], new Star() { IsHabitable = false });
// Writing a list
var result = await client.WriteValueAsync(x => x.Stars, new List<Star>() { new Star() { IsHabitable = false }});
NOTE: When writing a list you must always write the complete list. So if the OPC UA server defines a collection of 10 items then you must always write a list of 10 items.
Subscription does not change much from the way the OPC UA foudation has implemented it. The difference is that expressions are supported.
// Create the subscription
Subscription subscription = new Subscription(session.DefaultSubscription)
{
PublishingInterval = 1000
};
// Add the subscription to the session
client.Session.AddSubscription(subscription);
// Apply changes
await subscription.CreateAsync();
// Create the monitored item
var monitoredItem = await client.SubscribeAsync<Universe, bool>(subscription, x => x.Stars[2].IsHabitable, applyChanges: true);
// Subscribe to any changes for this item
monitoredItem.Notification += MonitoredItem_Notification;
private async void MonitoredItem_Notification(MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs e)
{
// This manual mapping step is needed for now
bool isHabitable = await client.MapNotificationValueAsync<bool>(monitoredItem);
}
Just like when reading and writing are complex types supported with subscriptions:
var monitoredItem = await client.SubscribeAsync<Universe, bool>(subscription, x => x.Stars[2], applyChanges: true);
// Subscribe to any changes for this item
monitoredItem.Notification += MonitoredItem_Notification;
private async void MonitoredItem_Notification(MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs e)
{
// This manual mapping step is needed for now
Star star = await client.MapNotificationValueAsync<Star>(monitoredItem);
}
For every change in the complex type (or a collection) the event will be triggered. This way you must not place subscriptions on every address but only on the parent address.
To unsubscribing can be done the following way:
await client.Unsubscribe(subscription, monitoredItem, applyChanges: true);
The apply changes parameter in both subscribe and unsubscribe is if the change should be pushed to the server or not. You can unsubscribe/subscribe multiple tags and only push these changes at the end.
As this uses the build-in OPC UA subscriptions you must still take into account the limitations of your specific OPC UA server.
When writing it can be usefull to group multiple operations as one transaction.
using Opc.Ua.Expressions.Transactions;
Transaction transaction = client.Begintransaction();
transaction.Write<Universe, string>(x => x.BestPlanet.Name, "earth");
transaction.Write<Universe, bool>(x => x.Stars[0].IsHabitable, true);
var result = await transaction.CommitAsync();
// Using the following extension method (namespace Opc.Ua.Expressions) you can easly check if all operations where good
bool success = result.IsGood();
Why many words when few do trick...
If you have a model and your property name does not match the name in the OPC UA server you can add the following attribute:
using Opc.Ua.Expressions.Attributes;
[OpcAttribute("sName")]
public string Name { get; set; }
This can be useful when your naming conventions do not match the conventions used in the OPC UA server. Or when the language is different.
If you want to set a fixed address to a property for reading the "Current Time" from the server:
using Opc.Ua.Expressions.Attributes;
[OpcAddressAttribute("i=2258")]
public DateTime CurrentTime { get; set; }
NOTE: this can only be used when reading the property directly. NOT when reading the parent of the property.
In the examples above we used the name "Universe" as root object in our address bu what if this name does not match what is defined in the OPC UA server?
using Opc.Ua.Expressions.Attributes;
// configure a different root name
[OpcRootAttribute("BigUniverse")]
public class Universe {
...
}
// Addresses will now look like this:
// ns=3;s="BigUniverse"."Planets"[2]."Name"
Instead of using attributes you can configure everything using a configuration object that is added as a second parameter when creating your client. This is not required
public OpcUaClient(Session session, ClientTypeConfiguration configuration = null)
{
...
}
// Access the configuration later using the property
client.Configuration ...
Example:
var session = ...
var configuration = new ClientTypeConfiguration();
configuration.RegisterType<Universe>();
// configure one property
configuration.RegisterType<Star>()
.ForProperty(x => x.IsHabitable)
.UseName("Is_Habitable"); // When the server defines a different name
// configure multiple properties
configuration.RegisterType<Planet>(tc => {
tc.ForProperty(x => x.Population).UseName("Planet_Population");
tc.ForProperty(x => x.Name).UseName("Planet_Name");
});
var client = new OpcUaClient(session, configuration);
OpcRootAttribute. This may change in later updates.TODO
implement ITypeConverter and register it with configuration.RegisterType<Star>.UseConverter<StarConverter>(). Now this converter will be used whenever the Star type is encountered.
implement IConvertibleType on the type you want to support. No other configuration is needed.
Example:
public class RecordControl : IConvertibleType
{
public int RecordStatus { get; set; }
public int UserID { get; set; }
public DateTime ChangedDateTime { get; set; }
public DateTime Changed => ChangedDateTime;
public void Encode(ITypeEncoder encoder)
{
encoder.Write(nameof(RecordStatus), RecordStatus);
encoder.Write(nameof(UserID), UserID);
encoder.Write(nameof(ChangedDateTime), ChangedDateTime);
}
public void Decode(ITypeDecoder decoder)
{
RecordStatus = decoder.Read<int>(nameof(RecordStatus));
UserID = decoder.Read<int>(nameof(UserID));
ChangedDateTime = decoder.Read<DTL>(nameof(ChangedDateTime));
}
}
TODO
use
.ForProperty(...).UseConverter<...>();
or
... .ForProperty(...).UseConversion(
(decoder) => { return new DateTime(); },
(encoder, value) => { encoder.Write("YEAR", ((DateTime)value).Year); }
);
Good luck
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 net8.0 is compatible. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 was computed. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 was computed. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
Showing the top 1 NuGet packages that depend on Opc.Ua.Expressions:
| Package | Downloads |
|---|---|
|
OpcUa.ExpressionServer
Simulation using an SQL database |
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.1.22 | 422 | 12/2/2024 |
| 1.1.21 | 469 | 2/29/2024 |
| 1.1.20 | 265 | 2/29/2024 |
| 1.1.19 | 246 | 2/29/2024 |
| 1.1.18 | 291 | 2/8/2024 |
| 1.1.17 | 500 | 7/14/2023 |
| 1.1.16 | 298 | 6/28/2023 |
| 1.1.15 | 416 | 4/24/2023 |
| 1.1.14 | 407 | 4/21/2023 |
| 1.1.13 | 369 | 4/21/2023 |
| 1.1.12 | 369 | 3/29/2023 |
| 1.1.11 | 630 | 12/1/2022 |
| 1.1.10 | 518 | 11/5/2022 |
| 1.1.9 | 512 | 11/5/2022 |
| 1.1.8 | 498 | 11/5/2022 |
| 1.1.6 | 529 | 11/3/2022 |
| 1.1.5 | 544 | 10/5/2022 |
| 1.1.4 | 520 | 10/5/2022 |
| 1.1.3 | 591 | 8/17/2022 |
| 1.1.2 | 582 | 8/16/2022 |
Added non-generic type registration