Update C Sharp Module walkthrough authored by Daniel Kluge's avatar Daniel Kluge
Here I will walk you through the code of the `GreenLight` asset in C#. I will try to compare it to Java when C# specific syntax is used.
This walkthrough is not the same as the [Java Light host walkthrough](/implementation/Light-host-walkthrough), because although both SDKs are "Eclipse BaSyx" and C# syntax and Java share a lot, the implementation share basically no similarities.
The discussed file `Program.cs` can be found [here](https://gitlab.hrz.tu-chemnitz.de/vws-demo/vws-spielwiese/-/blob/a7a78842b8d27ee4233c1f6648f2fc2b44e93f39/cs-module/Program.cs).
The discussed file `GreenLightAdministrationShellService.cs` can be found [here](https://gitlab.hrz.tu-chemnitz.de/vws-demo/vws-spielwiese/-/blob/a7a78842b8d27ee4233c1f6648f2fc2b44e93f39/cs-module/GreenLightAdministrationShellService.cs).
## `Program.cs`
```cs
using BaSyx.AAS.Server.Http;
using BaSyx.API.Components;
using BaSyx.Common.UI;
using BaSyx.Common.UI.Swagger;
using BaSyx.Discovery.mDNS;
using BaSyx.Utils.Settings.Types;
using BaSyx.Registry.Client.Http;
using NLog;
using NLog.Web;
namespace GreenLightAdministrationShell {
```
We start by using other namespaces and declaring our own.
Namespaces can be compared to Java packages and `using` is like importing them.
```cs
class Program{
private static readonly ILogger logger = LogManager.GetCurrentClassLogger();
static void Main(string[] args) {
logger.Info("Starting GreenLightAdministrationShell's HTTP server...");
```
This is really similar to Java.
We declare our class and create the `logger` as an attribute.
The function `Main(string[] args)` is called when this class is the start point of the application.
As the first task, we log something.
```cs
ServerSettings serverSettings = ServerSettings.LoadSettingsFromFile("ServerSettings.xml");
```
Here we parse the [`ServerSettings.xml` file](https://gitlab.hrz.tu-chemnitz.de/vws-demo/vws-spielwiese/-/blob/a7a78842b8d27ee4233c1f6648f2fc2b44e93f39/cs-module/ServerSettings.xml) for our server settings.
This file was part of the original example.
```cs
AssetAdministrationShellHttpServer server = new AssetAdministrationShellHttpServer(serverSettings);
```
We create a new AAS HTTP server with the prior parsed settings.
This is provided by `BaSyx.AAS.Server.Http`.
```cs
RegistryClientSettings registryClientSettings = RegistryClientSettings.CreateSettings();
registryClientSettings.RegistryConfig.RegistryUrl = "http://registry.local:4000/registry";
RegistryHttpClient registryHttpClient = new RegistryHttpClient(registryClientSettings);
```
We need a registry client to register and unregister our AAS at the remote registry.
The client also needs settings to know where to connect to.
The settings could also be placed in an XML file and an [example is provided](https://github.com/eclipse-basyx/basyx-dotnet-components/blob/main/BaSyx.Registry.Client.Http/RegistryClientSettings.xml) when installing the `BaSyx.Registry.Client.Http` dependency (only shown in the project map, not uploaded to the repository).
```cs
server.WebHostBuilder.UseNLog();
```
This makes our server use `NLog` as a logging tool.
```cs
GreenLightAdministrationShellService shellService = new GreenLightAdministrationShellService();
shellService.UseAutoEndpointRegistration(serverSettings.ServerConfig);
```
Here we create the AAS service which provides the descriptions and functionality for our AAS.
We set it to use the same endpoint our HTTP server uses.
```cs
server.SetServiceProvider(shellService);
```
We register the AAS service as a service provider in our HTTP server.
```cs
server.AddSwagger(Interface.AssetAdministrationShell);
server.AddBaSyxUI(PageNames.AssetAdministrationShellServer);
```
Because we use the predefined BaSyx AAS HTTP server we can add a few UI features.
Swagger is used as an API endpoint description and testing utility.
The BaSyx UI is a great way to explore the AAS and test functionality, too.
```cs
server.ApplicationStarted = () => {
registryHttpClient.CreateOrUpdateAssetAdministrationShellRegistration(shellService.ServiceDescriptor.Identification.Id, shellService.ServiceDescriptor);
};
```
Here we define an anonymous function that is called when the server is successfully started.
It registers our AAS at our registry.
```cs
server.ApplicationStopping = () => {
registryHttpClient.DeleteAssetAdministrationShellRegistration(shellService.ServiceDescriptor.Identification.Id);
}
```
Similar to the startup function there is an anonymous function called when the server shuts down.
It unregisters the AAS at the registry.
Be aware that this function is only called when the server is safely shut down (eg. pressing `Ctrl+C` in the console)! Otherwise, the AAS will remain in the registry but calls to it will fail.
```cs
server.Run();
```
This finally starts the defined HTTP server.
## `GreenLightAdministrationShellService.cs`
```cs
using BaSyx.API.Components;
using BaSyx.Models.Core.AssetAdministrationShell;
using BaSyx.Models.Core.AssetAdministrationShell.Generics;
using BaSyx.Models.Core.AssetAdministrationShell.Identification;
using BaSyx.Models.Core.AssetAdministrationShell.Implementations;
using BaSyx.Models.Core.Common;
using BaSyx.Models.Extensions;
using BaSyx.Utils.ResultHandling;
using System.Threading;
using System.Threading.Tasks;
using NLog;
namespace GreenLightAdministrationShell {
```
Similar to before we use and define some namespaces.
```cs
public class GreenLightAdministrationShellService : AssetAdministrationShellServiceProvider {
private readonly SubmodelServiceProvider lightSubmodelServiceProvider;
private static readonly ILogger logger = LogManager.GetCurrentClassLogger();
```
Start of the class definition and declaration of the `logger` and `lightSubodelServiceProvider` attributes.
The class inherits the `AssetAdministrationShellServiceProvider` so lots of functionality used later is already given.
For example, we don't need to call `BuildAssetAdministrationShell` manually to create the AAS we want to host, but it is done for us automatically.
```cs
public GreenLightAdministrationShellService() {
lightSubmodelServiceProvider = new SubmodelServiceProvider();
lightSubmodelServiceProvider.BindTo(AssetAdministrationShell.Submodels["lightSwitch"]);
lightSubmodelServiceProvider.RegisterMethodCalledHandler("activateLight", LightActivateOperationHandler);
lightSubmodelServiceProvider.RegisterMethodCalledHandler("deactivateLight", LightDeactivateOperationHandler);
lightSubmodelServiceProvider.RegisterSubmodelElementHandler("lightState", new SubmodelElementHandler(GreenLightElementGetHandler, null));
this.RegisterSubmodelServiceProvider("lightSwitch", lightSubmodelServiceProvider);
}
```
This is the constructor.
Lets go through it step by step:
```cs
lightSubmodelServiceProvider = new SubmodelServiceProvider();
```
At first we fill our `lightSubmodelServiceProvider` attribute with a new instance of a `SubmodelServiceProvider`.
```cs
lightSubmodelServiceProvider.BindTo(AssetAdministrationShell.Submodels["lightSwitch"]);
```
This line defines that our submodel service provider is used for the `lightSwitch` submodul (defined later).
```cs
lightSubmodelServiceProvider.RegisterMethodCalledHandler("activateLight", LightActivateOperationHandler);
lightSubmodelServiceProvider.RegisterMethodCalledHandler("deactivateLight", LightDeactivateOperationHandler);
```
As shown later we have two operations defined on the `lightSwitch` submodul.
In these two lines we append functions to them, which get called when the corresponding operation gets invoked.
```cs
lightSubmodelServiceProvider.RegisterSubmodelElementHandler("lightState", new SubmodelElementHandler(GreenLightElementGetHandler, null));
```
We also have a property in our `lightSwitch` called `lightState`.
Here we set functions which are called when a get operation or a set operation is called.
The first argument is the get, the second the set operation.
As the second argument is `null` no function will be called on set.
```cs
this.RegisterSubmodelServiceProvider("lightSwitch", lightSubmodelServiceProvider);
```
Here we finally add our submodel service to the `GreenLightAdministrationShellService` so the submodel can be explored and operations on the submodel invoked.
This concludes the constructor.
```cs
private IValue GreenLightElementGetHandler(ISubmodelElement submodelElement) {
var localProperty = AssetAdministrationShell.Submodels["lightSwitch"].SubmodelElements["lightState"].Cast<IProperty>();
return new ElementValue(localProperty.Value, localProperty.ValueType);
}
```
This is a simple get function for the `lightState` value.
It needs to return the value and the value type.
As defined in the constructor it is called when a get operation for `lightState` is invoked.
```cs
private Task<OperationResult> LightActivateOperationHandler(IOperation operation, IOperationVariableSet inputArguments, IOperationVariableSet inoutputArguments, IOperationVariableSet outputArguments, CancellationToken cancellationToken) {
AssetAdministrationShell.Submodels["lightSwitch"].SubmodelElements["lightState"].Cast<IProperty>().Value = "on";
logger.Info("Green light was turned on!");
return new OperationResult(true);
}
```
Even if the function signature is enormous the function itself is quite simple.
When `activateLight` is called, we set `lightState` to `on` and log the event to the console.
As this should never fail we can return `true` as result.
```cs
private Task<OperationResult> LightDeactivateOperationHandler(IOperation operation, IOperationVariableSet inputArguments, IOperationVariableSet inoutputArguments, IOperationVariableSet outputArguments, CancellationToken cancellationToken) {
AssetAdministrationShell.Submodels["lightSwitch"].SubmodelElements["lightState"].Cast<IProperty>().Value = "off";
logger.Info("Green light was turned off!");
return new OperationResult(true);
}
```
This basically is the same thing as above but for deactivating the light.
```cs
public override IAssetAdministrationShell BuildAssetAdministrationShell() {
```
It follows the function where we build our AAS.
Here we create all the assets, submodules, properties, operations, etc. we want in our AAS.
```cs
AssetAdministrationShell aas = new AssetAdministrationShell("greenLight_0", new Identifier("urn:de.olipar.basyx:cscomponent:greenLight_0", KeyType.IRI)) {
Description = new LangStringSet() { new LangString("en", "A demo dummy light AAS.") },
Asset = new Asset("lightSwitchAsset", new Identifier("urn:de.olipar.basyx:cscomponent:greenLight_0", KeyType.IRI)) {
Description = new LangStringSet() { new LangString("en", "This is a demo light asset reference from the Asset Administration Shell") },
Kind = AssetKind.Instance
}
};
```
First, we create the AAS object.
We initialize it with a description and an asset.
The identifiers are based on the existing Java implementation for optimal compatibility (even though only the shortId is relevant for now).
```cs
Submodel lightSwitchSubmodel = new Submodel("lightSwitch", new Identifier("LightSwitchLightstrand:greenLight_0", KeyType.IRI)) {
Description = new LangStringSet() { new LangString("enS", "This is a submodel to switch the light.") },
Kind = ModelingKind.Instance
};
```
Next, the submodel object is created and initialized with a description.
```cs
lightSwitchSubmodel.SubmodelElements = new ElementContainer<ISubmodelElement> {
new Property<string>("lightState", "off") {
Description = new LangStringSet() { new LangString("en", "This property shows the state of the light.") }
},
new Operation("activateLight") {
Description = new LangStringSet() { new LangString("en", "This operation turns the light on.") }
},
new Operation("deactivateLight") {
Description = new LangStringSet() { new LangString("en", "This operation turns the light off.") }
}
};
```
_All_ submodel elements are created in these few lines.
It should be pretty obvious by now how the necessary elements are created and initialized.
Be aware that the operations can not have any real functionality added to them in the AAS, but only get it from the `lightSubmodelServiceProvider`.
```cs
aas.Submodels = new ElementContainer<ISubmodel> {
lightSwitchSubmodel
};
```
In the last step, we add our defined submodel object to the AAS.
```cs
return aas;
}
```
And to close the function we return the defined AAS.
\ No newline at end of file