|
|
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 |