August 2019

Band 34, Nummer 8

[.NET Core]

Plattformübergreifende IoT-Programmierung mit .NET Core 3.0

Von Dawid Borycki

Microsoft Build 2019 war mit spannenden Neuigkeiten für .NET-Entwickler verbunden: .NET Core 3.0 unterstützt jetzt C# 8.0, Windows Desktop und IoT, sodass Sie Ihre vorhandenen .NET-Kenntnisse nutzen können, um plattformübergreifende Apps für intelligente Geräte zu entwickeln. In diesem Artikel zeige ich Ihnen, wie Sie eine .NET Core-App für Raspberry Pi 2/3 mit dem Sense HAT-Add-On-Board erstellen können. Die App ruft verschiedene Sensormesswerte ab, und die neuesten Messwerte sind über den ASP.NET Core-Web-API-Dienst verfügbar. Ich werde die einfache Benutzeroberfläche für diesen Dienst mit Swagger (Abbildung 1) erstellen, über die Sie problemlos mit dem IoT-Gerät interagieren können. Neben dem Abrufen von Daten vom Gerät können Sie auch die Farbe der LED-Matrix des Sense HAT remote ändern (Abbildung 2). Der begleitende Code ist über meine GitHub-Seite verfügbar: bit.ly/2WCj0G2.

Abrufen von Sensormesswerten vom IoT-Gerät mit Ausführung der .NET Core 3.0-App über die Web-API
Abbildung 1: Abrufen von Sensormesswerten vom IoT-Gerät mit Ausführung der .NET Core 3.0-App über die Web-API

Remotesteuerung des IoT-Geräts (Raspberry Pi 2 mit dem Sense HAT-Add-On-Board)
Abbildung 2: Remotesteuerung des IoT-Geräts (Raspberry Pi 2 mit dem Sense HAT-Add-On-Board)

Mein Gerät

Ich habe damit begonnen, mein IoT-Gerät (bestehend aus Raspberry Pi 2 (kurz RPi2) mit dem Sense HAT-Add-On-Board) einzurichten (siehe rechte Seite von Abbildung 2). RPi2 ist ein beliebter Single-Board-Computer, der die Betriebssysteme Linux oder Windows 10 IoT Core ausführen kann. Sie können dieses Gerät beispielsweise über adafruit.com beziehen. Das Sense HAT-Add-On Board ist mit mehreren Sensoren ausgestattet, darunter Thermometer, Barometer, Magnetometer, Gyroskop und Beschleunigungsmesser. Zusätzlich verfügt Sense HAT über 64 RGB-LEDs, die Sie als Kontrollanzeige oder als niedrigauflösenden Bildschirm verwenden können. Sense HAT lässt sich leicht mit dem RPi2 verbinden, sodass Sie schnell und ohne Lötarbeiten auf die Sensorwerte zugreifen können. Um den RPi2 zu aktivieren, habe ich ein USB-Kabel verwendet und das Gerät dann mit einem USB-Dongle mit dem lokalen WLAN verbunden (da RPi2 im Gegensatz zu RPi3 kein integriertes WLAN-Modul aufweist).

Nach dem Einrichten der Hardware habe ich Windows 10 IoT Core installiert. Die vollständigen Anleitungen finden Sie unter bit.ly/2Ic1Ew1. Kurz gesagt: Sie flashen das Betriebssystem auf die microSD-Karte. Dies kann mit dem Windows 10 Core-Dashboard (bit.ly/2VvXm76) auf einfache Weise erreicht werden. Sobald das Dashboard installiert ist, navigieren Sie zur Registerkarte „Set up a new device“ (Neues Gerät einrichten) und wählen den Gerätetyp, den Build des Betriebssystems, den Gerätenamen, das Administratorkennwort und die für Ihren PC verfügbare WLAN-Verbindung aus. Akzeptieren Sie die Softwarelizenzbedingungen, und klicken Sie auf „Download and install“ (Herunterladen und installieren). Wenn dieser Vorgang abgeschlossen ist, stecken Sie die microSD-Karte in das IoT-Gerät und schalten es ein. Bald wird Ihr Gerät als Eintrag auf der Registerkarte „My devices“ (Meine Geräte) im Dashboard angezeigt.

Gemeinsame Bibliothek

Vor Beginn der eigentlichen Implementierung habe ich .NET Core 3.0 Vorschau 5 installiert. Dann habe ich Visual Studio 2019 geöffnet und das neue Projekt mit der Vorlage „Klassenbibliothek“ (.NET Core) erstellt. Ich habe die Projekt- und Projektmappennamen auf „SenseHat.DotNetCore.Common“ bzw. „SenseHat.DotNetCore“ festgelegt. Dann habe ich das NuGet-Paket Iot.Device.Bindings (github.com/dotnet/iot) installiert, indem ich den folgenden Befehl in der Paket-Manager-Konsole (bit.ly/2KRvCHj) aufgerufen habe:

Install-Package IoT.Device.Bindings -PreRelease
  -Source https://dotnetfeed.blob.core.windows.net/dotnet-iot/index.json

IoT.Device.Bindings ist eine .NET Core-Open-Source-Implementierung für gängige IoT-Hardwarekomponenten, mit der Sie schnell .NET Core-Apps für IoT-Geräte implementieren können. Die Sense HAT-Bindung, die hier am relevantesten ist, ist im Unterordner „src/devices/SenseHat“ verfügbar. Ein kurzer Blick auf diesen Code zeigt, dass IoT.Device.Bindings den I2C-Bus für den Zugriff auf Sense HAT-Komponenten verwendet. Bevor ich Ihnen die Verwendung von IoT.Device.Bindings für Sense HAT zeige, habe ich zunächst die einfache POCO-Klasse SensorReadings (SenseHat.DotNetCore.Common/Sensors/Sensor/SensorReadings.cs) implementiert:

public class SensorReadings
{
  public Temperature Temperature { get; set; }
  public Temperature Temperature2 { get; set; }
  public float Humidity { get; set; }
  public float Pressure { get; set; }
  public DateTime TimeStamp { get; } = DateTime.UtcNow;
}

Diese Klasse enthält fünf öffentliche Member. Vier Member speichern die eigentlichen Sensorwerte (zwei Temperaturwerte sowie Feuchtigkeit und Druck). Es gibt zwei Temperaturwerte, da Sense HAT zwei Temperatursensoren besitzt, einen eingebettet in den LPS25H-Drucksensor (bit.ly/2MiYZEI) und den anderen im HTS221-Feuchtigkeitssensor (bit.ly/2HMP9GO). Temperaturen werden durch die Iot.Units.Temperature-Struktur aus Iot.Device.Bindings dargestellt. Diese Struktur besitzt keine öffentlichen Konstruktoren, kann aber mit einer der statischen Methoden instanziiert werden: FromCelsius, FromFahrenheit oder FromKelvin. Wenn die Temperatur in einer dieser Maßeinheiten angegeben wird, konvertiert die Struktur einen Wert in andere Einheiten. Sie können dann die Temperatur in ausgewählten Einheiten abrufen, indem Sie die entsprechenden Eigenschaften lesen: Celsius, Fahrenheit oder Kelvin.

Der fünfte Member der SensorReadings-Klasse (TimeStamp) enthält den Zeitpunkt, zu dem die Sensormesswerte aufgezeichnet wurden. Dies kann nützlich sein, wenn Sie Daten in die Cloud streamen und dann eine Zeitreihenanalyse mit speziellen Diensten wie Azure Stream Analytics oder Time Series Insights durchführen.

Darüber hinaus überschreibt die SensorReadings-Klasse die ToString-Methode, um Sensorwerte in der Konsole ordnungsgemäß anzuzeigen:

public override string ToString()
{
  return $"Temperature: {Temperature.Celsius,5:F2} °C"
    + $" Temperature2: {Temperature2.Celsius,5:F2} °C"
    + $" Humidity: {Humidity,4:F1} %"
    + $" Pressure: {Pressure,6:F1} hPa";
}

Der nächste Schritt ist die Implementierung der SenseHatService-Klasse, um auf ausgewählte Sense HAT-Komponenten zuzugreifen. Zu Testzwecken habe ich mich außerdem entschieden, eine weitere Klasse (SenseHatEmulationService) zu implementieren, die als Emulator fungiert. Ich wollte die Web-API und andere Elemente des Codes schnell testen, ohne die Hardware verbinden zu müssen. Um einfach zwischen zwei konkreten Implementierungen wechseln zu können, habe ich das Softwareentwurfsmuster der Abhängigkeitsinjektion (Dependency Injection, DI) verwendet. Zu diesem Zweck habe ich zunächst eine Schnittstelle deklariert:

public interface ISenseHatService
{
  public SensorReadings SensorReadings { get; }
  public void Fill(Color color);
  public bool EmulationMode { get; }
}

Diese Schnittstelle definiert die gemeinsamen Member für die beiden konkreten Implementierungen:

  • SensorReadings: Mit dieser Eigenschaft kann die aufrufende Funktion von den Sensoren abgerufene Werte – im Fall von SenseHatService echte Werte – und mit SenseHatEmulationService zufällig generierte Werte abfragen.
  • Fill: Diese Methode wird verwendet, um die Farbe aller LEDs einheitlich festzulegen.
  • EmulationMode: Diese Eigenschaft gibt an, ob der Sense-HAT emuliert ist (TRUE) oder nicht (FALSE).

Dann habe ich die SenseHatService-Klasse implementiert. Sehen wir uns zuerst den Klassenkonstruktor an, wie in Abbildung 3 dargestellt. Dieser Konstruktor instanziiert drei private schreibgeschützte Felder: ledMatrix, pressureAndTemperatureSensor und temperatureAndHumiditySensor. Dazu habe ich entsprechende Klassen aus dem IoT.Device.Bindings-Paket verwendet: SenseHatLedMatrixI2c, SenseHatPressureAndTemperature bzw. SenseHatTemperatureAndHumidity. Jede Klasse hat einen öffentlichen Konstruktor, der einen Parameter akzeptiert: i2cDevice vom abstrakten Typ System.Device.I2c.I2cDevice. Der Standardwert dieses Parameters ist NULL. In einem solchen Fall verwendet IoT.Device.Bindings intern abhängig vom Betriebssystem eine Instanz von System.Device.I2c.Drivers.UnixI2cDevice oder System.Device.I2c.Drivers.Windows10I2cDevice.

Abbildung 3: Ausgewählte Member der SenseHatService-Klasse

public class SenseHatService : ISenseHatService
{
  // Other members of the SenseHatService (refer to the companion code)
  private readonly SenseHatLedMatrix ledMatrix;
  private readonly SenseHatPressureAndTemperature pressureAndTemperatureSensor;
  private readonly SenseHatTemperatureAndHumidity temperatureAndHumiditySensor;
  public SenseHatService()
  {
    ledMatrix = new SenseHatLedMatrixI2c();   
    pressureAndTemperatureSensor = new SenseHatPressureAndTemperature();
    temperatureAndHumiditySensor = new SenseHatTemperatureAndHumidity(); 
  }
}

Danach können Sie Sensorwerte abrufen (siehe „SenseHat.DotNetCore.Common/Services/SenseHatService.cs“ im begleitenden Code):

public SensorReadings SensorReadings => GetReadings();
private SensorReadings GetReadings()
{
  return new SensorReadings
  {
    Temperature = temperatureAndHumiditySensor.Temperature,
    Humidity = temperatureAndHumiditySensor.Humidity,
    Temperature2 = pressureAndTemperatureSensor.Temperature,
    Pressure = pressureAndTemperatureSensor.Pressure
  };
}

Außerdem können Sie die Farbe der LED-Matrix festlegen:

public void Fill(Color color) => ledMatrix.Fill(color);

Bevor wir das ausprobieren, implementieren wir zunächst den Emulator: SenseHatEmulationService. Der vollständige Code für diese Klasse ist im begleitenden Code verfügbar (SenseHat.DotNetCore.Common/Services/SenseHatEmulationService.cs). Die Klasse ist von der ISenseHatService-Schnittstelle abgeleitet, sodass sie die drei zuvor beschriebenen öffentlichen Member implementieren muss: SensorReadings, Fill und EmulationMode.

Ich habe damit begonnen, Sensormesswerte zu synthetisieren. Zu diesem Zweck habe ich die folgende Hilfsmethode implementiert:

private double GetRandomValue(SensorReadingRange sensorReadingRange)
{
  var randomValueRescaled = randomNumberGenerator.NextDouble()
    * sensorReadingRange.ValueRange();
  return sensorReadingRange.Min + randomValueRescaled;
}
private readonly Random randomNumberGenerator = new Random();

GetRandomValue verwendet die System.Random-Klasse, um das double-Element zu generieren, dessen Wert in den angegebenen Bereich fällt. Dieser Bereich wird durch eine Instanz von SensorReadingRange (SenseHat.DotNetCore.Common/Sensors/Sensor/SensorReadingRange.cs) dargestellt, die zwei öffentliche Eigenschaften (Min und Max) aufweist, die die Minimal- und Maximalwerte für die emulierte Sensormessung angeben. Außerdem implementiert die SensorReadingRange-Klasse eine Instanzmethode (ValueRange), die die Differenz zwischen Max und Min zurückgibt.

GetRandomValue wird in der privaten Methode GetSensorReadings verwendet, um Sensormesswerte wie in Abbildung 4 dargestellt zu synthetisieren.

Abbildung 4: SenseHatEmulationService

private SensorReadings GetSensorReadings()
{
  return new SensorReadings
  {
    Temperature =
      Temperature.FromCelsius(GetRandomValue(temperatureRange)),
    Humidity = (float)GetRandomValue(humidityRange),
    Temperature2 =
      Temperature.FromCelsius(GetRandomValue(temperatureRange)),
    Pressure = (float)GetRandomValue(pressureRange)
  };
}
private readonly SensorReadingRange temperatureRange =   new SensorReadingRange { Min = 20, Max = 40 };
private readonly SensorReadingRange humidityRange =   new SensorReadingRange { Min = 0, Max = 100 };
private readonly SensorReadingRange pressureRange =   new SensorReadingRange { Min = 1000, Max = 1050 };

Schließlich habe ich die öffentlichen Member implementiert:

public SensorReadings SensorReadings => GetSensorReadings();
public void Fill(Color color) {/* Intentionally do nothing*/}
public bool EmulationMode => true;

Ich habe außerdem die SenseHatServiceHelper-Klasse erstellt, die ich später verwenden werde (SenseHat.DotNetCore.Common/Helpers/SenseHatServiceHelper.cs). Die SenseHatServiceHelper-Klasse hat eine öffentliche Methode, die abhängig vom booleschen Parameter emulationMode eine Instanz von SenseHatService oder von SenseHatEmulationService zurückgibt:

public static ISenseHatService GetService(bool emulationMode = false)
{
  if (emulationMode)
  {
    return new SenseHatEmulationService();
  }
  else
  {
    return new SenseHatService();
  }
}

Konsolen-App

Ich kann den Code nun mit meiner Konsolen-App testen. Diese App kann auf Ihrem Entwicklungs-PC oder auf dem IoT-Gerät ausgeführt werden. Wenn die Ausführung auf dem PC erfolgt, kann die App den Emulator verwenden. Um zwischen Emulations- und Nicht-Emulationsmodus zu wechseln, verwende ich ein Befehlszeilenargument, das eine Zeichenfolge ist, die aus dem Buchstaben Y oder N besteht. Die Konsolen-App analysiert dieses Argument und verwendet dann entweder SenseHatEmulationService (Y) oder SenseHatService (N). Im Emulationsmodus zeigt die App nur synthetisierte Sensormesswerte an. Im Nicht-Emulationsmodus zeigt die App Werte an, die von realen Sensoren abgerufen wurden, und ändert auch nacheinander die Farbe der LED-Matrix.

Um die Konsolen-App zu erstellen, habe ich die SenseHat.DotNetCore-Lösung um ein neues Projekt (SenseHat.DotNetCore.ConsoleApp) ergänzt, das ich mit der Projektvorlage „Konsolen-App“ (.NET Core) erstellt habe. Dann habe ich auf SenseHat.DotNetCore.Common verwiesen und mit der Implementierung der Program-Klasse begonnen. Ich habe drei private Member definiert:

private static readonly Color[] ledColors =
  { Color.Red, Color.Blue, Color.Green };
private static readonly int msDelayTime = 1000;
private static int ledColorIndex = 0;

Der erste Member (ledColors) ist eine Sammlung von Farben, die nacheinander verwendet werden, um die LED-Matrix einheitlich zu verändern. Der zweite Member (msDelayTime) gibt eine Zeitdauer zwischen dem Zugriff auf aufeinanderfolgende Sensormesswerte und der Änderung der LED-Matrix an. Der letzte Member (ledColorIndex) speichert den Wert der aktuell angezeigten Farbe aus der ledColors-Sammlung.

Dann habe ich zwei Hilfsmethoden geschrieben:

private static bool ParseInputArgumentsToSetEmulationMode(string[] args)
{
    return args.Length == 1 && string.Equals(
      args[0], "Y", StringComparison.OrdinalIgnoreCase);
}

und:

private static void ChangeFillColor(ISenseHatService senseHatService)
{
  if (!senseHatService.EmulationMode)
  {
    senseHatService.Fill(ledColors[ledColorIndex]);
    ledColorIndex = ++ledColorIndex % ledColors.Length;
  }
}

Die erste Methode (ParseInputArgumentsToSetEmulationMode) analysiert die Sammlung von Eingabeargumenten (die an die Main-Methode übergeben wird), um festzustellen, ob der Emulationsmodus verwendet werden soll. Die Methode gibt nur dann TRUE zurück, wenn die Sammlung ein Element hat, das y oder Y entspricht.

Die zweite Methode (ChangeFillColor) ist nur wirksam, wenn der Emulationsmodus deaktiviert ist. Wenn dies der Fall ist, nimmt die Methode eine Farbe aus der ledColors-Sammlung und übergibt sie an die Fill-Methode der konkreten Implementierung von SenseHatService. ledColorIndex wird dann inkrementiert. In dieser spezifischen Implementierung kann die if-Anweisung in ChangeFillColor ausgelassen werden, da die Fill-Methode von SenseHatEmulationService nichts bewirkt. Im Allgemeinen sollte die if-Anweisung jedoch einbezogen werden.

Schließlich habe ich die Main-Methode implementiert, die alles zusammenfügt. Dies wird in Abbildung 5 gezeigt. Zunächst werden die Eingabeargumente analysiert. Basierend auf dem Ergebnis rufe ich dann die statische Methode GetService von SenseHatServiceHelper auf. Als nächstes zeige ich die Zeichenfolge an, um den Benutzer darüber zu informieren, ob die App im Emulationsmodus arbeitet. Im dritten Schritt starte ich die Endlosschleife, in der Sensormesswerte abgerufen werden und schließlich die Farbe der LED-Matrix geändert wird. Die Schleife verwendet den msDelayTime-Wert, um die Ausführung der App zu unterbrechen.

Abbildung 5: Einstiegspunkt der Konsolen-App

static void Main(string[] args)
{
  // Parse input arguments, and set emulation mode accordingly
  var emulationMode = ParseInputArgumentsToSetEmulationMode(args);
  // Instantiate service
  var senseHatService = SenseHatServiceHelper.GetService(emulationMode);
  // Display the mode
  Console.WriteLine($"Emulation mode: {senseHatService.EmulationMode}");
  // Infinite loop
  while (true)
  {
    // Display sensor readings
    Console.WriteLine(senseHatService.SensorReadings);
    // Change the LED array color
    ChangeFillColor(senseHatService);
    // Delay
    Task.Delay(msDelayTime).Wait();
  }
}

Bereitstellen einer App auf einem Gerät

Um die App im RPi auszuführen, können Sie das .NET Core 3.0 SDK auf Ihr Gerät herunterladen, Ihren Code dorthin kopieren, die App erstellen und sie schließlich mit dem Befehl „dotnet run“ der .NET Core-CLI ausführen. Alternativ können Sie die App mit Ihrem Entwicklungs-PC veröffentlichen und dann die Binärdateien auf das Gerät kopieren. Ich entscheide mich hier für die zweite Option.

Sie erstellen zunächst die Projektmappe SenseHat.DotNetCore und rufen dann im Projektmappenordner den folgenden Befehl auf:

dotnet publish -r win-arm

Der Parameter -r win-arm kann entfallen, wenn die Projektdatei die folgende Eigenschaft enthält: <RuntimeIdentifier>win-arm</RuntimeIdentifier>. Sie können die Befehlszeilenschnittstelle Ihrer Wahl oder die Paket-Manager-Konsole von Visual Studio verwenden. Wenn Sie Visual Studio 2019 verwenden, können Sie die App auch mit den UI-Tools veröffentlichen. Navigieren Sie dazu zum Projektmappen-Explorer, klicken Sie mit der rechten Maustaste auf das ConsoleApp-Projekt, und wählen Sie dann „Veröffentlichen“ aus dem Kontextmenü aus. Visual Studio zeigt ein Dialogfeld an, in dem Sie „Ordner“ als Veröffentlichungsziel auswählen. Legen Sie dann unter den Einstellungen für das Veröffentlichungsprofil den Bereitstellungsmodus auf „Eigenständig“ und die Ziellaufzeit auf „win-arm“ fest.

Unabhängig davon, für welche Methode Sie sich entscheiden, bereitet das .NET Core SDK die Binärdateien für die Bereitstellung vor. Standardmäßig finden Sie sie im Ausgabeordner „bin\$(Configuration)\netcoreapp3.0\win-arm\publish“. Kopieren Sie diesen Ordner auf Ihr Gerät. Der einfachste Weg, diese Dateien zu kopieren, ist die Verwendung des Datei-Explorers von Windows (bit.ly/2WYtnrT). Öffnen Sie den Datei-Explorer, und geben Sie die IP-Adresse Ihres Geräts in die Adressleiste ein, der ein doppelter umgekehrter Schrägstrich vorangestellt ist, gefolgt von c$. Mein RPi weist die IP-Adresse 192.168.0.109 auf, daher habe ich \\192.168.0.109\c$ eingegeben. Nach kurzer Zeit zeigt der Datei-Explorer eine Eingabeaufforderung an, in der Sie aufgefordert werden, das Kennwort des Administrators einzugeben. Haben Sie etwas Geduld, dies kann einige Sekunden dauern. Kopieren Sie schließlich die Binärdateien auf Ihr Gerät.

Um die App tatsächlich auszuführen, können Sie PowerShell verwenden. Die einfachste Möglichkeit ist die Verwendung des IoT-Dashboards, das in Abbildung 6 gezeigt wird. Klicken Sie einfach mit der rechten Maustaste unter der Registerkarte „Meine Geräte“ auf Ihr Gerät, und wählen Sie dann PowerShell aus. Sie müssen das Administratorkennwort erneut eingeben, wenn Sie dazu aufgefordert werden. Navigieren Sie dann zu dem Ordner mit Ihren Binärdateien, und führen Sie die App aus, indem Sie Folgendes aufrufen:

 

.\SenseHat.DotNetCore.ConsoleApp N

Starten von PowerShell über das IoT-Dashboard von Windows 10
Abbildung 6: Starten von PowerShell über das IoT-Dashboard von Windows 10

Sie sehen die tatsächlichen Sensormesswerte wie in Abbildung 7 gezeigt, und die SenseHat-LED-Matrix ändert ihre Farbe. Sie können erkennen, dass die von den beiden Sensoren gemeldete Temperatur nahezu gleich ist. Auch die Luftfeuchtigkeit und der Druck liegen im erwarteten Bereich. Um weiter zu bestätigen, dass alles ordnungsgemäß funktioniert, nehmen wir einige Änderungen vor. Bedecken Sie dazu das Gerät mit der Hand. Wie Sie feststellen werden, steigt die Luftfeuchtigkeit an. In meinem Fall stieg die Luftfeuchtigkeit von etwa 37 % auf 51 %.

Abrufen von Sensormesswerten mit der Konsolen-App, die auf dem Raspberry Pi 2 ausgeführt wird
Abbildung 7: Abrufen von Sensormesswerten mit der Konsolen-App, die auf dem Raspberry Pi 2 ausgeführt wird

Web-API

Mit .NET Core kann ich sogar noch weiter gehen und die Sensormesswerte über den Web-API-Dienst anzeigen. Ich erstelle dazu eine einfache Benutzeroberfläche mit Swagger UI (bit.ly/2IEnXXV). Mit dieser Benutzeroberfläche kann der Endbenutzer HTTP-Anforderungen genau so an das IoT-Gerät senden, wie er sie an eine normale Web-App senden würde! Wir untersuchen, wie das funktioniert.

Ich habe damit begonnen, die SenseHat.DotNetCore-Lösung um ein weiteres ASP.NET Core-Webanwendungsprojekt (SenseHat.DotNetCore.WebApp) zu erweitern und das Projekt mit der API-Vorlage zu erstellen. Dann habe ich auf das Projekt SenseHat.DotNetCore.Common verwiesen und das Swashbuckle-NuGet-Paket (Install-Package Swashbuckle.AspNetCore) installiert. Ausführliche Anleitungen zur Einrichtung von Swagger in der ASP.NET Core-Webanwendung finden Sie unter bit.ly/2BpFzWC, sodass ich auf alle Details verzichten kann und nur die Anweisungen zeige, die für die Einrichtung der Swagger-Benutzeroberfläche in meiner App erforderlich sind. Alle diese Änderungen werden in der Datei „Startup.cs“ des Projekts SenseHat.DotNetCore.WebApp vorgenommen.

Zunächst habe ich das schreibgeschützte Feld openApiInfo implementiert:

private readonly OpenApiInfo openApiInfo = new OpenApiInfo
{
  Title = "Sense HAT API",
  Version = "v1"
};

Dann habe ich die ConfigureServices-Methode geändert, indem ich die folgenden Anweisungen hinzugefügt habe:

services.AddSwaggerGen(o =>
{
  o.SwaggerDoc(openApiInfo.Version, openApiInfo);
});

Diese Anweisungen sind für die Einrichtung des Swagger-Generators verantwortlich. Im nächsten Schritt habe ich die konkrete Implementierung der ISenseHatService-Schnittstelle für die Abhängigkeitsinjektion registriert:

var emulationMode = string.Equals(Configuration["Emulation"], "Y",
  StringComparison.OrdinalIgnoreCase);
var senseHatService = SenseHatServiceHelper.GetService(emulationMode);
services.AddSingleton(senseHatService);

Hier wird die konkrete Implementierung des SenseHat-Diensts abhängig von der Emulationseinstellung aus der Konfigurationsdatei festgelegt. Der Schlüssel „Emulation“ ist in „appsettings.Development.json“ auf „N“ und in „appsettings.json“ auf „Y“ festgelegt. Folglich verwendet die Web-App den Emulator in der Entwicklungsumgebung und die echte Sense HAT-Hardware in der Produktionsumgebung. Wie in jeder anderen ASP.NET Core-Web-App ist die Produktionsumgebung standardmäßig für die Release-Buildkonfiguration aktiviert.

Die letzte Änderung besteht darin, die Configure-Methode mit den Anweisungen zum Einrichten des Swagger-Endpunkts zu erweitern:

app.UseSwagger();
  app.UseSwaggerUI(o =>
  {
    o.SwaggerEndpoint($"/swagger/
      {openApiInfo.Version}/swagger.
      json",
        openApiInfo.Title);
  });

Dann habe ich die eigentliche Klasse für den Web-API-Controller implementiert. Im Ordner „Controllers“ habe ich die neue Datei „SenseHatController.cs“ erstellt, die ich wie in Abbildung 8 gezeigt geändert habe. SenseHatController verfügt über einen öffentlichen Konstruktor, der für die Abhängigkeitsinjektion verwendet wird, um eine Instanz von ISenseHatService abzurufen. Ein Verweis auf diese Instanz wird im Feld senseHatService gespeichert.

Abbildung 8: Eine vollständige Definition des SenseHatController-Web-API-Diensts

[Route("api/[controller]")]
[ApiController]
public class SenseHatController : ControllerBase
{
  private readonly ISenseHatService senseHatService;
  public SenseHatController(ISenseHatService senseHatService)
  {
    this.senseHatService = senseHatService;
  }
  [HttpGet]
  [ProducesResponseType(typeof(SensorReadings), (int)HttpStatusCode.OK)]
  public ActionResult<SensorReadings> Get()
  {
    return senseHatService.SensorReadings;
  }
  [HttpPost]
  [ProducesResponseType((int)HttpStatusCode.Accepted)]
  [ProducesResponseType((int)HttpStatusCode.BadRequest)]
  [ProducesResponseType((int)HttpStatusCode.InternalServerError)]
  public ActionResult SetColor(string colorName)
  {
    var color = Color.FromName(colorName);
    if (color.IsKnownColor)
    {
      senseHatService.Fill(color);
      return Accepted();
    }
    else
    {
      return BadRequest();
    }
  }
}

SenseHatController verfügt über zwei öffentliche Methoden (Get und SetColor). Die erste Methode verarbeitet HTTP GET-Anforderungen und gibt Sensormesswerte vom Sense HAT-Add-On-Board zurück. Die zweite Methode (SetColor) verarbeitet HTTP POST-Anforderungen. SetColor hat ein Zeichenfolgenargument (colorName). Die Client-Apps verwenden dieses Argument, um die Farbe auszuwählen, die dann verwendet wird, um die Farbe der LED-Matrix einheitlich zu ändern.

Ich kann nun die endgültige Version der App testen. Auch hier kann ich den Emulator oder echte Hardware verwenden. Beginnen wir mit dem Entwicklungs-PC, und führen wir die App mit der Debug-Buildkonfiguration aus. Geben Sie nach dem Ausführen der App „localhost:5000/swagger“ in die Adressleiste des Browsers ein. Beachten Sie, dass sich der Browser automatisch öffnet und Sie zum Swagger-Endpunkt umgeleitet werden, wenn Sie den begleitenden Code verwenden und die App über den Kestrel-Webserver ausführen. Ich habe dies mit launchUrl aus der Datei „launchSettings.json“ konfiguriert.

In der Swagger-Benutzeroberfläche wird eine Seite mit dem Sense HAT-API-Header angezeigt. Direkt unter diesem Header befinden sich zwei Zeilen mit GET- und POST-Bezeichnungen. Wenn Sie auf eines dieser Elemente klicken, wird eine detailliertere Ansicht angezeigt. Diese Ansicht ermöglicht es Ihnen, Anforderungen an den SenseHatController zu senden, Antworten anzuzeigen und die API-Dokumentation zu untersuchen (bereits zuvor auf der linken Seite von Abbildung 1 gezeigt). Um die Sensormesswerte anzuzeigen, erweitern Sie die GET-Zeile, und klicken Sie dann auf die Schaltflächen „Try It Out“ (Ausprobieren) und „Execute“ (Ausführen). Die Anforderung wird an den Web-API-Controller gesendet und verarbeitet, und Sensormesswerte werden unter dem Antworttext angezeigt (bereits zuvor auf der rechten Seite von Abbildung 1 gezeigt). Sie senden POST-Anforderungen auf ähnliche Weise: Sie müssen nur den Parameter colorName festlegen, bevor Sie auf die Schaltfläche „Execute“ (Ausführen) klicken.

Um die App auf meinem Gerät zu testen, habe ich die App mit der Releasekonfiguration veröffentlicht und die sich ergebenden Binärdateien dann für den Raspberry Pi (wie bei der Konsolen-App) bereitgestellt. Anschließend musste ich Port 5000 öffnen, was durch Aufrufen des folgenden Befehls aus PowerShell auf dem RPi2 geschehen ist:

netsh advfirewall firewall add rule name="ASP.NET Core Web Server port"
  dir=in action=allow protocol=TCP localport=5000

Schließlich habe ich die App aus PowerShell mit dem folgenden Befehl ausgeführt:

.\SenseHat.DotNetCore.WebApp.exe --urls http://*:5000

Das zusätzliche Befehlszeilenargument (urls) wird verwendet, um den Webserver-Standardendpunkt aus localhost in eine lokale IP-Adresse (bit.ly/2VEUIji) zu ändern, sodass auf den Webserver über andere Geräten im Netzwerk zugegriffen werden kann. Nachdem dies erledigt war, habe ich den Browser auf meinem Entwicklungs-PC geöffnet, „192.168.0.109:5000/swagger“ eingegeben, und die Swagger-Benutzeroberfläche wurde angezeigt (natürlich müssen Sie die IP-Adresse Ihres Geräts verwenden). Dann habe ich damit begonnen, HTTP-Anforderungen zu senden, um Sensormesswerte zu erhalten und die Farbe der LED-Matrix zu ändern, wie zuvor in Abbildung 1 und Abbildung 2 gezeigt.

Zusammenfassung

In diesem Artikel habe ich gezeigt, wie eine plattformübergreifende IoT-App mit .NET Core 3.0 implementiert werden kann. Die App wird auf dem Raspberry Pi 2/3 ausgeführt und interagiert mit Komponenten des Sense HAT-Add-On-Boards. Mit der App habe ich verschiedene Sensormesswerte (Temperatur, Luftfeuchtigkeit und Luftdruck) über die Iot.Device.Bindings-Bibliothek abgerufen. Dann habe ich den ASP.NET Core-Web-API-Dienst implementiert und eine einfache Benutzeroberfläche mit Swagger erstellt. Jetzt kann jeder mit wenigen Mausklicks auf diese Sensormesswerte zugreifen und das Gerät remote steuern. Der Code kann ohne Änderungen auf dem anderen System (einschließlich Raspbian) ausgeführt werden. Dieses Beispiel zeigt, wie Sie als .NET-Entwickler Ihre vorhandenen Fähigkeiten und Ihre Codebasis zur Programmierung verschiedener IoT-Geräte nutzen können.


Dawid Borycki ist Softwareentwickler und biomedizinischer Forscher mit umfangreicher Erfahrung in Microsoft-Technologien. Er hat eine Vielzahl von anspruchsvollen Projekten abgeschlossen, die die Entwicklung von Software für Geräteprototypen (zumeist medizinische Geräte), Embedded Device Interfacing sowie Desktop- und mobile Programmierung umfassen. Borycki ist Autor von zwei Microsoft Press-Veröffentlichungen: „Programming for Mixed Reality“ (2018) und „Programming for the Internet of Things“ (2017).

Unser Dank gilt dem folgenden technischen Experten bei Microsoft für die Durchsicht dieses Artikels: Krzysztof Wicher
Krzysztof Wicher ist Autor der Sense HAT-Gerätebindung im IoT-Repository und Softwareentwickler im .NET Core-Team (dies schließt das .NET-IoT-Team ein).


Diesen Artikel im MSDN Magazine-Forum diskutieren