Update C Sharp Module walkthrough authored by Daniel Kluge's avatar Daniel Kluge
...@@ -36,9 +36,13 @@ As the first task, we log something. ...@@ -36,9 +36,13 @@ As the first task, we log something.
```cs ```cs
ServerSettings serverSettings = ServerSettings.LoadSettingsFromFile("ServerSettings.xml"); ServerSettings serverSettings = ServerSettings.LoadSettingsFromFile("ServerSettings.xml");
string userPort = Environment.GetEnvironmentVariable("PORT") ?? "4000";
serverSettings.ServerConfig.Hosting.Urls = new List<string> { "http://+:" + userPort };
``` ```
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. 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. This file was part of the original example and as there are settings for some image stuff I ued it as base.
After the initial parsing we get the `PORT` environment variable or set it to 4000 if it doesn't exist.
The list of endpoints in the `ServerSettings.xml` will then be replaced by just our endpoint.
```cs ```cs
AssetAdministrationShellHttpServer server = new AssetAdministrationShellHttpServer(serverSettings); AssetAdministrationShellHttpServer server = new AssetAdministrationShellHttpServer(serverSettings);
...@@ -47,14 +51,16 @@ We create a new AAS HTTP server with the prior parsed settings. ...@@ -47,14 +51,16 @@ We create a new AAS HTTP server with the prior parsed settings.
This is provided by `BaSyx.AAS.Server.Http`. This is provided by `BaSyx.AAS.Server.Http`.
```cs ```cs
string userRegistry = Environment.GetEnvironmentVariable("REGISTRYPATH") ?? "http://registry.local:4000/registry";
RegistryClientSettings registryClientSettings = RegistryClientSettings.CreateSettings(); RegistryClientSettings registryClientSettings = RegistryClientSettings.CreateSettings();
registryClientSettings.RegistryConfig.RegistryUrl = "http://registry.local:4000/registry"; registryClientSettings.RegistryConfig.RegistryUrl = userRegistry;
RegistryHttpClient registryHttpClient = new RegistryHttpClient(registryClientSettings); RegistryHttpClient registryHttpClient = new RegistryHttpClient(registryClientSettings);
``` ```
We need a registry client to register and unregister our AAS at the remote registry. We need a registry client to register and unregister our AAS descriptor at the remote registry.
The client also needs settings to know where to connect to. The client also needs settings to know where to connect to, so we get the `REGISTRYPATH` environment variable or set another default value.
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). These 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 ```cs
server.WebHostBuilder.UseNLog(); server.WebHostBuilder.UseNLog();
...@@ -126,14 +132,32 @@ Similar to before we use and define some namespaces. ...@@ -126,14 +132,32 @@ Similar to before we use and define some namespaces.
public class GreenLightAdministrationShellService : AssetAdministrationShellServiceProvider { public class GreenLightAdministrationShellService : AssetAdministrationShellServiceProvider {
private readonly SubmodelServiceProvider lightSubmodelServiceProvider; private readonly SubmodelServiceProvider lightSubmodelServiceProvider;
private static readonly ILogger logger = LogManager.GetCurrentClassLogger(); private static readonly ILogger logger = LogManager.GetCurrentClassLogger();
private string gpioPort = System.Environment.GetEnvironmentVariable("LED_PIN");
``` ```
Start of the class definition and declaration of the `logger` and `lightSubodelServiceProvider` attributes. Start of the class definition and declaration of the `logger` and `lightSubodelServiceProvider` attributes.
Also the `LED_PIN` environment variable is fetched.
The class inherits the `AssetAdministrationShellServiceProvider` so lots of functionality used later is already given. 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. 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 ```cs
public GreenLightAdministrationShellService() { public GreenLightAdministrationShellService() {
if (gpioPort != null)
{
try
{
Process exec = Process.Start("raspi-gpio", "set " + gpioPort + " op");
exec.WaitForExit();
exec = Process.Start("raspi-gpio", "set " + gpioPort + " dl");
exec.WaitForExit();
}
catch (System.Exception e)
{
logger.Error("Could not set output mode for gpio pin!");
logger.Error(e.Message);
}
}
lightSubmodelServiceProvider = new SubmodelServiceProvider(); lightSubmodelServiceProvider = new SubmodelServiceProvider();
lightSubmodelServiceProvider.BindTo(AssetAdministrationShell.Submodels["lightSwitch"]); lightSubmodelServiceProvider.BindTo(AssetAdministrationShell.Submodels["lightSwitch"]);
...@@ -147,6 +171,28 @@ public GreenLightAdministrationShellService() { ...@@ -147,6 +171,28 @@ public GreenLightAdministrationShellService() {
``` ```
This is the constructor. This is the constructor.
Lets go through it step by step: Lets go through it step by step:
```cs
if (gpioPort != null)
{
try
{
Process exec = Process.Start("raspi-gpio", "set " + gpioPort + " op");
exec.WaitForExit();
exec = Process.Start("raspi-gpio", "set " + gpioPort + " dl");
exec.WaitForExit();
}
catch (System.Exception e)
{
logger.Error("Could not set output mode for gpio pin!");
logger.Error(e.Message);
gpioPort = null;
}
}
```
When a `LED_PIN` environment variable is provided we setup the pin in `gpioPort` by calling `raspi-gpio`.
In case anything bad happens we can't use this ``gpioPort` so we will set it `null` for later.
```cs ```cs
lightSubmodelServiceProvider = new SubmodelServiceProvider(); lightSubmodelServiceProvider = new SubmodelServiceProvider();
``` ```
...@@ -188,22 +234,38 @@ As defined in the constructor it is called when a get operation for `lightState` ...@@ -188,22 +234,38 @@ As defined in the constructor it is called when a get operation for `lightState`
```cs ```cs
private Task<OperationResult> LightActivateOperationHandler(IOperation operation, IOperationVariableSet inputArguments, IOperationVariableSet inoutputArguments, IOperationVariableSet outputArguments, CancellationToken cancellationToken) { private Task<OperationResult> LightActivateOperationHandler(IOperation operation, IOperationVariableSet inputArguments, IOperationVariableSet inoutputArguments, IOperationVariableSet outputArguments, CancellationToken cancellationToken) {
AssetAdministrationShell.Submodels["lightSwitch"].SubmodelElements["lightState"].Cast<IProperty>().Value = "on"; AssetAdministrationShell.Submodels["lightSwitch"].SubmodelElements["lightState"].Cast<IProperty>().Value = "on";
bool result = true;
if (gpioPort != null)
{
try
{
Process exec = Process.Start("raspi-gpio", "set " + gpioPort + " dl");
exec.WaitForExit();
logger.Info("Green light was turned on!");
} catch (System.Exception e)
{
logger.Error("Light should turn on, but raspi-gpio cannot be started!");
logger.Error(e.Message);
result = false;
}
} else
{
logger.Info("Green light was turned 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 return new OperationResult(result);
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. Even if the function signature is enormous the function itself is quite simple.
When `activateLight` is called, we set `lightState` to `on`.
After that we will either call `raspi-gpio` but only if we have a `gpioPort` set or we will only log to the command line.
If something happens while calling `raspi-gpio` the result will be set to `false`.
In the end we return our operation result.
Basically the exact opposit is done in the function `LightDeactivateOperationHandler` so we will skip further explanation.
```cs ```cs
public override IAssetAdministrationShell BuildAssetAdministrationShell() { public override IAssetAdministrationShell BuildAssetAdministrationShell() {
...@@ -213,7 +275,7 @@ Here we create all the assets, submodules, properties, operations, etc. we want ...@@ -213,7 +275,7 @@ Here we create all the assets, submodules, properties, operations, etc. we want
```cs ```cs
AssetAdministrationShell aas = new AssetAdministrationShell("greenLight_0", new Identifier("urn:de.olipar.basyx:cscomponent:greenLight_0", KeyType.IRI)) { 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.") }, Description = new LangStringSet() { new LangString("en", "A demo light AAS.") },
Asset = new Asset("lightSwitchAsset", new Identifier("urn:de.olipar.basyx:cscomponent:greenLight_0", KeyType.IRI)) { 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") }, Description = new LangStringSet() { new LangString("en", "This is a demo light asset reference from the Asset Administration Shell") },
... ...
......