Update C Sharp Module walkthrough authored by Daniel Kluge's avatar Daniel Kluge
......@@ -36,9 +36,13 @@ As the first task, we log something.
```cs
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.
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
AssetAdministrationShellHttpServer server = new AssetAdministrationShellHttpServer(serverSettings);
......@@ -47,14 +51,16 @@ We create a new AAS HTTP server with the prior parsed settings.
This is provided by `BaSyx.AAS.Server.Http`.
```cs
string userRegistry = Environment.GetEnvironmentVariable("REGISTRYPATH") ?? "http://registry.local:4000/registry";
RegistryClientSettings registryClientSettings = RegistryClientSettings.CreateSettings();
registryClientSettings.RegistryConfig.RegistryUrl = "http://registry.local:4000/registry";
registryClientSettings.RegistryConfig.RegistryUrl = userRegistry;
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.
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, 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
server.WebHostBuilder.UseNLog();
......@@ -126,14 +132,32 @@ Similar to before we use and define some namespaces.
public class GreenLightAdministrationShellService : AssetAdministrationShellServiceProvider {
private readonly SubmodelServiceProvider lightSubmodelServiceProvider;
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.
Also the `LED_PIN` environment variable is fetched.
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() {
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.BindTo(AssetAdministrationShell.Submodels["lightSwitch"]);
......@@ -147,6 +171,28 @@ public GreenLightAdministrationShellService() {
```
This is the constructor.
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
lightSubmodelServiceProvider = new SubmodelServiceProvider();
```
......@@ -188,22 +234,38 @@ As defined in the constructor it is called when a get operation for `lightState`
```cs
private Task<OperationResult> LightActivateOperationHandler(IOperation operation, IOperationVariableSet inputArguments, IOperationVariableSet inoutputArguments, IOperationVariableSet outputArguments, CancellationToken cancellationToken) {
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!");
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);
return new OperationResult(result);
}
```
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
public override IAssetAdministrationShell BuildAssetAdministrationShell() {
......@@ -213,7 +275,7 @@ Here we create all the assets, submodules, properties, operations, etc. we want
```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.") },
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)) {
Description = new LangStringSet() { new LangString("en", "This is a demo light asset reference from the Asset Administration Shell") },
......
......