Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/trakhound/uns.net
.NET SDK for implementing a Unified Namespace for use with IIoT. Supports plain MQTT and SparkplugB.
https://github.com/trakhound/uns.net
iiot iot mqtt
Last synced: about 1 month ago
JSON representation
.NET SDK for implementing a Unified Namespace for use with IIoT. Supports plain MQTT and SparkplugB.
- Host: GitHub
- URL: https://github.com/trakhound/uns.net
- Owner: TrakHound
- License: mit
- Created: 2024-02-20T07:06:59.000Z (11 months ago)
- Default Branch: main
- Last Pushed: 2024-03-03T00:48:37.000Z (10 months ago)
- Last Synced: 2024-05-01T11:37:49.599Z (8 months ago)
- Topics: iiot, iot, mqtt
- Language: C#
- Homepage: http://www.trakhound.com
- Size: 147 KB
- Stars: 4
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Overview
.NET SDK for implementing a Unified Namespace for use with IIOT. Supports plain MQTT and SparkplugB.- Consolidate multiple connections (Plain MQTT, SparkplugB, etc.)
- Subscribe & Publish with SparkplugB using standardized Paths
- Apply Namespace configuration based on Path
- Implement Middleware such as Report By Exception, Unit Conversion, etc.# Nuget
```
dotnet add package Uns.NET
```# UnsClient
The UnsClient class is the primary class that handles Connections, Namespace Configurations, and Subscriptions. The UnsClient allows an application to Subscribe and Publish data in the form of a standardized **UnsEventMessage**. A UnsEventMessage contains the content of the message along with information about the Namespace and Connection.## UnsEventMessage
```
Path = Plant1/Area3/Line4/Cell2/PLC/PLC-01/Temperature
Namespace.Path = Plant1/Area3/Line4/Cell2/PLC
Namespace.Type = Functional
Namespace.Kind = Heterogenous
Connection.Id = spB
Connection.Type = SPARKPLUG_B_APPLICATION
ContentType = SPARKPLUG_B_METRIC
Content = {"Name":"Temperature","Timestamp":1708139619918,"BytesValue":"","Value":514,"ValueCase":9,"DataType":9}
```## Usage
```c#
using Uns;var client = new UnsClient();
// Add Middleware
client.AddMiddleware(new UnsReportByExceptionMiddleware());
client.AddMiddleware(new UnsDeadbandPeriodMiddleware(TimeSpan.FromSeconds(5)));
client.AddMiddleware(new UnsDeadbandValueMiddleware(10));// Add Namespace Configurations
var mesNamespaceConfig = new NamespaceConfiguration();
mesNamespaceConfig.Path = "Plant1/Area3/Line4/MES";
mesNamespaceConfig.Kind = NamespaceKind.Heterogenous;
mesNamespaceConfig.Type = NamespaceType.Functional;
client.AddNamespace(mesNamespaceConfig);var plcNamespaceConfig = new NamespaceConfiguration();
plcNamespaceConfig.Path = "Plant1/Area3/Line4/Cell2/PLC";
plcNamespaceConfig.Kind = NamespaceKind.Heterogenous;
plcNamespaceConfig.Type = NamespaceType.Functional;
client.AddNamespace(plcNamespaceConfig);// Add MQTT (Plain) Connection
var mqttConnection = new UnsMqttConnection("localhost", 1883);
mqttConnection.AddSubscription("input/ERP/#", erpNamespaceConfig.Path);
mqttConnection.AddSubscription("input/MES/#", mesNamespaceConfig.Path);
mqttConnection.AddDestination(erpNamespaceConfig.Path);
mqttConnection.AddDestination(mesNamespaceConfig.Path);
client.AddConnection(mqttConnection);// Add Sparkplug Connection
var spBConnection = new UnsSparkplugConnection("localhost", 1883, "testing");
spBConnection.AddApplication("Plant1/Area3/Line4/Cell2/PLC");
spBConnection.AddNode("Plant1/Area3/Line4/Cell2/PLC");
spBConnection.AddDevice("Plant1/Area3/Line4/Cell2/PLC/PLC-01");
spBConnection.AddDevice("Plant1/Area3/Line4/Cell2/PLC/PLC-02");
client.AddConnection(spBConnection);var consumer = client.Subscribe("#");
consumer.Received += EventReceived;var temperatureConsumer = client.Subscribe("Temperature");
temperatureConsumer.Received += (c, o) => Console.WriteLine(o.ToJson(true));var descriptionConsumer = client.Subscribe("Description");
descriptionConsumer.Received += (c, o) => Console.WriteLine(o.ToJson(true));var modelConsumer = client.SubscribeJson("#");
modelConsumer.Received += (c, o) => Console.WriteLine(o.ToJson(true));await client.Start();
while (true)
{
Console.ReadLine();await client.Publish("Plant1/Area3/Line4/Cell2/PLC/PLC-01/Temperature", 100.32);
await client.Publish("Plant1/Area3/Line4/Cell2/PLC/PLC-02/Text", "THIS IS FROM A UNS CLIENT");
}await client.Stop();
async void EventReceived(object? sender, UnsEventMessage message)
{
Console.WriteLine("######################");
Console.WriteLine($"Path = {message.Path}");
Console.WriteLine($"Namespace = {message.Namespace?.Path}");
Console.WriteLine($"Connection = {message.Connection?.Id}");
Console.WriteLine($"ContentType = {message.ContentType}");
Console.WriteLine($"Content = {GetContentString(message.ContentType, message.Content)}");
Console.WriteLine($"Timestamp = {message.Timestamp.ToString("o")}");await client.Publish("mqtt-01", message);
}string GetContentString(NamespaceContentType contentType, byte[] content)
{
switch (contentType)
{
case NamespaceContentType.PlainText: return System.Text.Encoding.UTF8.GetString(content);
case NamespaceContentType.Json: return System.Text.Encoding.UTF8.GetString(content);
case NamespaceContentType.SparkplugB: return System.Text.Encoding.UTF8.GetString(content);
}return null;
}
```## Connections
**UnsConnections** are used to connect to external systems and either subscribe to or publish data in the form of **UnsEvents**.### Connection Interfaces
- `IUnsInputConnection` : Only allow to subscribe to data (Read-Only)
- `IUnsOutputConnection` : Only allow to publish new data (Write-Only)### UnsMqttConnection
```c#
var mqttConnection = new UnsMqttConnection("localhost", 1883);
mqttConnection.AddSubscription("input/ERP/#");
mqttConnection.AddSubscription("input/MES/#");
mqttConnection.AddDestination("Plant1/ERP");
mqttConnection.AddDestination("Plant1/Area3/Line4/MES");
client.AddConnection(mqttConnection);
```### UnsSparkplugConnection
#### AddApplication
```c#
var spBConnection = new UnsSparkplugConnection("localhost", 1883);
spBConnection.AddApplication("Plant1/Area3/Line4/Cell2");
client.AddConnection(spBConnection);
```#### AddNode
```c#
var spBConnection = new UnsSparkplugConnection("localhost", 1883);
spBConnection.AddNode("Plant1/Area3/Line4/Cell2/PLC");
client.AddConnection(spBConnection);
```#### AddDevice
```c#
var spBConnection = new UnsSparkplugConnection("localhost", 1883);
spBConnection.AddDevice("Plant1/Area3/Line4/Cell2/PLC/PLC-01");
client.AddConnection(spBConnection);
```## Namespace Configuration
Namespace Configurations are used to define the Namespace that match events. This can be used to filter events or to send as information to an external application. Configurations can be either manually set as shown below or be populated from an external Broker, API, or Database.
```c#
var siteNamespaceConfig = new NamespaceConfiguration();
siteNamespaceConfig.Path = "Plant1";
siteNamespaceConfig.Kind = NamespaceKind.Homogeneous;
siteNamespaceConfig.Type = NamespaceType.Informational;
client.AddNamespace(siteNamespaceConfig);var plcNamespaceConfig = new NamespaceConfiguration();
plcNamespaceConfig.Path = "Plant1/Area3/Line4/Cell2/PLC";
plcNamespaceConfig.Kind = NamespaceKind.Heterogenous;
plcNamespaceConfig.Type = NamespaceType.Functional;
plcNamespaceConfig.ContentType = NamespaceContentType.SPARKPLUG_B;
client.AddNamespace(plcNamespaceConfig);
```## Middleware
Middleware is used to filter, transform, etc. events that are either sent or received.### UnsReportByExceptionMiddleware
Report by Exception (RBE) is a fundamental principle of a Unified Namespace as it reduces the amount of data sent over the network by filtering out duplicate data.
```c#
// Add RBE Middleware to a UnsClient
client.AddMiddleware(new UnsReportByExceptionMiddleware());
```### UnsDeadbandValueMiddleware
A Value Deadband filter is used to filter out values that havent' changed by the specified MinimumDelta. This can be used to filter out "noise" as well as reduce the amount of data sent that may be negligible.
```c#
// Add a Value Deadband filter to a UnsClient to filter
// requests whose value hasn't changed by more than +/- 10
client.AddMiddleware(new UnsDeadbandValueMiddleware(10));
```### UnsDeadbandPeriodMiddleware
A Period Deadband filter is used to filter out values that havent' changed within the specified MinimumPeriod time span. This can be used to filter out "noise" as well as reduce the amount of data sent that may be negligible.
```c#
// Add a Period Deadband filter to a UnsClient to filter
// requests whose value hasn't changed in the last 5 seconds
client.AddMiddleware(new UnsDeadbandPeriodMiddleware(TimeSpan.FromSeconds(5)));
```# Examples
## Example #1
This is an example of a Sparkplug Device publishing a metric named Temperature using the Path **"Plant1/Area3/Line4/Cell2/PLC/PLC-02/Temperature"**.### Publish
```c#
var client = new UnsClient();var spBConnection = new UnsSparkplugConnection("localhost", 1883);
spBConnection.AddDevice("Plant1/Area3/Line4/Cell2/PLC/PLC-02");
client.AddConnection(spBConnection);await client.Start();
await client.Publish("Plant1/Area3/Line4/Cell2/PLC/PLC-02/Temperature", 1);
```This gets published to the MQTT broker as shown below:
![Example-01-IMG-01](img/screenshots/sparkplug-plc-publish-example-01.png)
> Notice that the GroupId, NodeId, and DeviceId are all inferred based on the Path
### Subscribe
```c#
var client = new UnsClient();var plcNamespaceConfig = new NamespaceConfiguration();
plcNamespaceConfig.Path = "Plant1/Area3/Line4/Cell2/PLC";
plcNamespaceConfig.Kind = NamespaceKind.Heterogenous;
plcNamespaceConfig.Type = NamespaceType.Functional;
client.AddNamespace(plcNamespaceConfig);var spBConnection = new UnsSparkplugConnection("localhost", 1883, "testing");
spBConnection.AddApplication("Plant1/Area3/Line4/Cell2/PLC");
client.AddConnection(spBConnection);var consumer = client.Subscribe("Plant1/Area3/Line4/Cell2/PLC/#");
consumer.Received += (c, o) =>
{
Console.WriteLine("-------------------------");
Console.WriteLine($"Path = {o.Path}");
Console.WriteLine($"Namespace.Path = {o.Namespace?.Path}");
Console.WriteLine($"Namespace.Type = {o.Namespace?.Type}");
Console.WriteLine($"Namespace.Kind = {o.Namespace?.Kind}");
Console.WriteLine($"Connection.Id = {o.Connection?.Id}");
Console.WriteLine($"Connection.Type = {o.Connection?.Type}");
Console.WriteLine($"ContentType = {o.ContentType}");
Console.WriteLine($"Content = {GetContentString(o.ContentType, o.Content)}");
Console.WriteLine($"Timestamp = {o.Timestamp.ToString("o")}");
};await client.Start();
```This results in the following being written to the console:
![Example-01-IMG-02](img/screenshots/sparkplug-plc-publish-example-02.png)
> Note that the Namespace configuration is automatically applied based on the Path of the Event that is received and that the Sparkplug Metric is parsed as an individual message