Share via



Juni 2017

Band 32, Nummer 6

Internet der Dinge: Verwenden von Azure IoT Suite zum Ankurbeln der IoT-Entwicklung

Von Dawid Borycki

Eine IoT-Lösung ) umfasst Telemetrieremotegeräte, ein Webportal, Cloudspeicherung und Echtzeitverarbeitung. Eine solch komplexe Struktur lässt Sie möglicherweise davor zurückschrecken, mit der IoT-Entwicklung zu beginnen. Microsoft Azure IoT Suite macht Ihnen das Leben durch das Bereitstellen von zwei vorkonfigurierten Lösungen leichter: Remoteüberwachung und vorhersagbare Wartung. In diesem Artikel erläutere ich, wie eine Lösung für Remoteüberwachung erstellt wird, die Daten von einem IoT-Remotegerät erfasst und analysiert, das von Windows 10 IoT Core gesteuert wird. Dieses Gerät (der Raspberry Pi) erfasst Bilder von einer USB-Kamera. Anschließend wird die Bildhelligkeit auf dem IoT-Gerät berechnet und dann in die Cloud gestreamt. Dort erfolgt die Speicherung, Verarbeitung und Anzeige (siehe Abbildung 1). Außerdem ist der Endbenutzer nicht nur in der Lage, die mit dem Remotegerät erfassten Informationen anzeigen, sondern er kann dieses Gerät auch remote steuern. Sie finden den vollständigen Quellcode zu diesem Artikel unter msdn.com/magazine/0617magcode.

Ein vorkonfiguriertes Portal der Azure IoT Suite-Lösung und die UWP-Remote-App, die den Videodatenstrom erfasst und verarbeitet
Abbildung 1A: Ein vorkonfiguriertes Portal der Azure IoT Suite-Lösung und die UWP-Remote-App, die den Videodatenstrom erfasst und verarbeitet

Remotegerät

Die Grundlagen der Programmierung des Raspberry Pi mit Windows 10 IoT Core wurden in diesem Magazine bereits von Frank LaVigne (msdn.com/magazine/mt694090) und Bruno Sonnino (msdn.com/magazine/mt808503) vorgestellt. LaVigne und Sonnino haben gezeigt, wie die Entwicklungsumgebung und das IoT-Board eingerichtet werden, wie die IoT-Einheit in Ihrem Browser mithilfe des Device Portal konfiguriert wird und wie GPIO-Ports mit Windows 10 IoT Core gesteuert werden. Außerdem hat LaVigne in seinem Artikel darauf hingewiesen, dass IoT zum Programmieren und Steuern von Remotekameras verwendet werden kann. Diesen Gedanken entwickele ich hier weiter und zeige explizit, wie der Raspberry Pi in ein solches Gerät verwandelt werden kann.

Zu diesem Zweck erstelle ich die UWP-App (universelle Windows-Plattform) „RemoteCamera“ unter Verwendung der Visual C#-Projektvorlage „Leere App (universelles Windows)“ und lege dann die API-Ziel- und Mindestversionen auf Windows 10 Anniversary Edition (10.0, Build 14393) fest. Ich verwende diese API-Version zum Aktivieren der Bindung an Methoden, durch die ich Methoden des Anzeigemodells direkt mit Ereignissen verknüpfen kann, die von visuellen Steuerelementen ausgelöst werden:

<Button x:Name="ButtonPreviewStart"
        Content="Start preview"
        Click="{x:Bind remoteCameraViewModel.PreviewStart}" />

Im nächsten Schritt deklariere ich die Benutzeroberfläche wie in Abbildung 1 gezeigt. Es stehen zwei Registerkarten zur Verfügung: „Camera capture“ (Kameraufnahme) und „Cloud“. Die erste Registerkarte enthält Steuerelemente zum Starten und Stoppen der Kameravorschau, Anzeigen des Videodatenstroms und Angeben der Bildhelligkeit (eine Bezeichnung und eine Statusanzeige). Die zweite Registerkarte enthält zwei Schaltflächen, die Sie zum Verbinden eines Geräts mit der Cloud und zum Registrieren eines Geräts im IoT-Portal verwenden können. Die Registerkarte „Cloud“ enthält außerdem ein Kontrollkästchen, mit dem Sie das Streamen von Telemetriedaten aktivieren können.

Die meiste Programmlogik, die der Benutzeroberfläche zugeordnet ist, wird in der RemoteCameraViewModel-Klasse implementiert (siehe Ordner „ViewModels“ des Projekts „RemoteCamera“). Diese Klasse verarbeitet (abgesehen vom Implementieren einiger Eigenschaften, die an die Benutzeroberfläche gebunden sind) die Videoerstellung, die Bildverarbeitung und die Interaktion mit der Cloud. Diese Teilfunktionen werden in separaten Klassen implementiert: in „CameraCapture“, „ImageProcessor“ bzw. „CloudHelper“. „CameraCapture“ und „ImageProcessor“ beschreibe ich gleich. „CloudHelper“ und dazu in Beziehung stehende Hilfsklassen beschreibe ich später im Kontext der vorkonfigurierten Azure IoT-Lösung.

Kameraaufnahme

Die Kameraaufnahme (siehe „CameraCapture.cs“ unter dem Ordner „Helpers“) wird über zwei Elementen erstellt: über der Windows.Media.Capture.MediaCapture-Klasse und „Windows.UI.Xaml.Controls.CaptureElement“. Das erste Element erfasst die Videodaten, das zweite zeigt den erfassten Videodatenstrom an. Ich erfasse die Videodaten mit einer Webcam. Ich muss also die entsprechende Gerätefunktion in „Package.appxmanifest“ deklarieren.

Zum Initialisieren der MediaCapture-Klasse rufen Sie die InitializeAsync-Methode auf. Sie können an diese Methode eine Instanz der MediaCaptureInitializationSettings-Klasse übergeben, mit der Sie Erfassungsoptionen angeben können. Sie können zwischen dem Streamen von Video und Audio wählen und die Erfassungshardware auswählen. Hier erfasse ich nur Videodaten von der Standardwebcam (siehe Abbildung 2).

Abbildung 2: Initialisierung der Kameraaufnahme

public MediaCapture { get; private set; } = new MediaCapture();
public bool IsInitialized { get; private set; } = false;
public async Task Initialize(CaptureElement captureElement)
{
  if (!IsInitialized)
  {
    var settings = new MediaCaptureInitializationSettings()
    {
      StreamingCaptureMode = StreamingCaptureMode.Video
    };
    try
    {
      await MediaCapture.InitializeAsync(settings);
      GetVideoProperties();
      captureElement.Source = MediaCapture;      IsInitialized = true;
    }
    catch (Exception ex)
    {
      Debug.WriteLine(ex.Message);
      IsInitialized = false;
    }
  }
}

Im nächsten Schritt verknüpfe ich dieses Objekt mithilfe der Eigenschaft „Source“ der CaptureElement-Klasseninstanz mit dem MediaCapture-Steuerelement, um den Videodatenstrom anzuzeigen. Außerdem rufe ich die Hilfsmethode „GetVideoProperties“ auf, die die Größe eines Videoframes liest und dann speichert. Ich verwende diese Informationen später, um den Vorschauframe für die Verarbeitung abzurufen. Schließlich rufe ich zum tatsächlichen Starten und Stoppen der Videoaufzeichnung „StartPreviewAsync“ und „StopPreviewAsync“ der MediaCapture-Klasse auf. In „CameraCapture“ habe ich weitere Programmlogik als Wrapper für diese Methoden verwendet, um die Initialisierung und den Vorschauzustand zu überprüfen:

public async Task Start()
{
  if (IsInitialized)
  {
    if (!IsPreviewActive)
    {
      await MediaCapture.StartPreviewAsync();
      IsPreviewActive = true;
    }
  }
}

Wenn Sie die App ausführen, können Sie die Schaltfläche „Start preview“ (Vorschau starten) drücken, die die Kameraaufnahme konfiguriert. Nach einer kurzen Zeit wird das Kamerabild angezeigt. Beachten Sie, dass die RemoteCamera-App universell ist. Sie kann daher ohne weitere Änderungen für Ihren Entwicklungscomputer, ein Smartphone, ein Tablet oder den Raspberry Pi bereitgestellt werden. Wenn Sie die RemoteCamera-App mit Ihrem Windows 10-Computer testen, müssen Sie sicherstellen, dass Apps ihre Kamera verwenden dürfen. Sie konfigurieren diese Einstellung mit der Settings-App („Privacy/Camera“). Zum Testen der App mit dem Raspberry Pi verwende ich eine preiswerte Microsoft Life Cam HD-3000. Dabei handelt es sich um eine USB-Webcam, die automatisch von Windows 10 IoT Core erkannt wird, nachdem ich sie an einen der vier USB-Ports des Raspberry Pi angeschlossen habe. Eine vollständige Liste der Kameras, die mit Windows 10 IoT Core kompatibel sind, finden Sie unter bit.ly/2p1ZHGD. Nachdem Sie Ihre Webcam an den Rasbperry Pi angeschlossen haben, wird sie auf der Registerkarte „Geräte“ im Device Portal angezeigt.

Bildprozessor

Die ImageProcessor-Klasse berechnet die Helligkeit des aktuellen Frames im Hintergrund. Zum Ausführen des Hintergrundvorgangs erstelle ich einen Thread mit dem taskbasierten asynchronen Muster. Abbildung 3 zeigt dies.

Abbildung 3: Berechnen der Helligkeit im Hintergrund

public event EventHandler<ImageProcessorEventArgs> ProcessingDone;
private void InitializeProcessingTask()
{
  processingCancellationTokenSource = new CancellationTokenSource();
  processingTask = new Task(async () =>
  {
    while (!processingCancellationTokenSource.IsCancellationRequested)
    {
      if (IsActive)
      {
        var brightness = await GetBrightness();
        ProcessingDone(this, new ImageProcessorEventArgs(brightness));
        Task.Delay(delay).Wait();
      }
    }
  }, processingCancellationTokenSource.Token);
}

In der while-Schleife ermittle ich die Bildhelligkeit und übergebe diesen Wert dann an Listener des ProcessingDone-Ereignisses. An dieses Ereignis wird eine Instanz der ImageProcessorEventArgs-Klasse übergeben, die nur eine öffentliche Eigenschaft besitzt: „Brightness“. Die Verarbeitung wird ausgeführt, bis der Task ein Abbruchsignal empfängt. Das Schlüsselelement der Bildverarbeitung ist die GetBrightness-Methode, die in Abbildung 4 gezeigt wird.

Abbildung 4: Die GetBrightness-Methode

private async Task<byte> GetBrightness()
{
  var brightness = new byte();
  if (cameraCapture.IsPreviewActive)
  {
    // Get current preview bitmap
    var previewBitmap = await cameraCapture.GetPreviewBitmap();
    // Get underlying pixel data
    var pixelBuffer = GetPixelBuffer(previewBitmap);
    // Process buffer to determine mean gray value (brightness)
    brightness = CalculateMeanGrayValue(pixelBuffer);
  }
  return brightness;
}

Ich greife auf den Vorschauframe mithilfe von „GetPreviewBitmap“ der CameraCapture-Klasseninstanz zu. Intern verwendet „GetPreviewBitmap“ „GetPreviewFrameAsync“ der MediaCapture-Klasse. Es sind zwei Versionen von „GetPreviewFrameAsync“ vorhanden. Die erste (eine parameterlose Methode) gibt eine Instanz der VideoFrame-Klasse zurück. In diesem Fall können Sie die tatsächlichen Pixeldaten abrufen, indem Sie die Direct3DSurface-Eigenschaft lesen. Die zweite Version nimmt eine Instanz der Video­Frame-Klasse an und kopiert Pixeldaten in ihre SoftwareBitmap-Eigenschaft. Hier verwende ich die zweite Option (siehe GetPreviewBitmap-Methode der CameraCapture-Klasse) und greife dann auf Pixeldaten über die CopyToBuffer-Methode der SoftwareBitmap-Klasseninstanz zu, die in Abbildung 5 gezeigt wird.

Abbildung 5: Zugreifen auf Pixeldaten

private byte[] GetPixelBuffer(SoftwareBitmap softwareBitmap)
{
  // Ensure bitmap pixel format is Bgra8
  if (softwareBitmap.BitmapPixelFormat != CameraCapture.BitmapPixelFormat)
  {
    SoftwareBitmap.Convert(softwareBitmap, CameraCapture.BitmapPixelFormat);
  }
  // Lock underlying bitmap buffer
  var bitmapBuffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.Read);
  // Use plane description to determine bitmap height
  // and stride (the actual buffer width)
  var planeDescription = bitmapBuffer.GetPlaneDescription(0);
  var pixelBuffer = new byte[planeDescription.Height * planeDescription.Stride];
  // Copy pixel data to a buffer
  softwareBitmap.CopyToBuffer(pixelBuffer.AsBuffer());
  return pixelBuffer;
}

Ich stelle zuerst sicher, dass das Pixelformat BGRA8 ist. Dies bedeutet, dass das Bild mit vier 8-Bit-Kanälen dargestellt wird: mit drei Kanälen für die Farben blau, grün und rot und einem Kanal für Alpha oder Transparenz. Wenn die Eingabebitmap ein anderes Pixelformat aufweist, führe ich eine entsprechende Konvertierung aus. Im nächsten Schritt kopiere ich Pixeldaten in das byte-Array, dessen Größe durch die Bildhöhe bestimmt wird, multipliziert mit der Bildschrittweite (bit.ly/2om8Ny9). Ich lese beide Werte aus einer Instanz von „BitmapPlaneDescription“, die ich aus dem BitmapBuffer-Objekt abrufe, das von der SoftwareBitmap.LockBuffer-Methode zurückgegeben wird.

Wenn ich das byte-Array mit den Pixeldaten kenne, muss ich nur noch den Mittelwert aller Pixel berechnen. Also iteriere ich durch den Pixelpuffer (siehe Abbildung 6).

Abbildung 6: Berechnen des Mittelwerts der Pixel

private byte CalculateMeanGrayValue(byte[] pixelBuffer)
{
  // Loop index increases by four since
  // there are four channels (blue, green, red and alpha).
  // Alpha is ignored for brightness calculation
  const int step = 4;
  double mean = 0.0;
  for (uint i = 0; i < pixelBuffer.Length; i += step)
  {
    mean += GetGrayscaleValue(pixelBuffer, i);
  }
  mean /= (pixelBuffer.Length / step);
  return Convert.ToByte(mean);
}

Bei jeder Iteration konvertiere ich dann ein angegebenes Pixel in die Grauskala, indem ich Durchschnittswerte aus jedem Farbkanal bilde:

private static byte GetGrayscaleValue(byte[] pixelBuffer, uint startIndex)
{
  var grayValue = (pixelBuffer[startIndex]
    + pixelBuffer[startIndex + 1]
    + pixelBuffer[startIndex + 2]) / 3.0;
  return Convert.ToByte(grayValue);
}

Die Helligkeit wird an die Ansicht über das ProcessingDone-Ereignis übergeben. Dieses Ereignis wird in der MainPage-Klasse („MainPage.xaml.cs“) verarbeitet. Ich zeige dort die Helligkeit in der Bezeichnung und eine Statusanzeige an. Beide Steuerelemente sind an die Brightness-Eigenschaft von „RemoteCameraViewModel“ gebunden. Beachten Sie, dass „ProcessingDone“ aus dem Hintergrundthread ausgelöst wird. Daher ändere ich „RemoteCameraViewModel.Brightness“ über den Benutzeroberflächenthread mithilfe der Dispatcher-Klasse. Abbildung 7 zeigt dies.

Abbildung 7: Ändern von „RemoteCameraViewModel.Brightness“ über den Benutzeroberflächenthread mithilfe der Dispatcher-Klasse

private async void DisplayBrightness(byte brightness)
{
  if (Dispatcher.HasThreadAccess)
  {
    remoteCameraViewModel.Brightness = brightness;
  }
  else
  {
    await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {
      DisplayBrightness(brightness);
    });
  }
}

Bereitstellen der Remoteüberwachungslösung

Für das Bereitstellen einer Lösung können Sie ein dediziertes Portal unter azureiotsuite.com verwenden. Nachdem Sie sich angemeldet und Ihr Azure-Abonnement ausgewählt haben, werden Sie an eine Seite weitergeleitet, auf der Sie auf das Rechteck „Neue Lösung erstellen“ klicken. Es wird eine Website geöffnet, auf der Sie eine von zwei vorkonfigurierten Lösungen auswählen können: „Vorhersagbare Wartung“ oder „Remoteüberwachung“ (siehe Abbildung 8). Nach dem Auswählen der Lösung wird ein weiteres Formular angezeigt, in dem Sie den Lösungsnamen und die Region für Azure-Ressourcen festlegen können. Hier lege ich den Lösungsnamen und die Region auf „RemoteCameraMonitoring“ bzw. „USA, Westen“ fest.

Vorkonfigurierte Lösungen von Azure IoT Suite (oben) und Konfiguration der Remoteüberwachungslösung (unten)
Abbildung 8: Vorkonfigurierte Lösungen von Azure IoT Suite (oben) und Konfiguration der Remoteüberwachungslösung (unten)

Wenn Sie die Remoteüberwachungslösung bereitstellen, erstellt das Azure IoT Suite-Portal mehrere Azure-Ressourcen: IoT Hub, Stream Analytics-Aufträge, Storage und den App Service. IoT Hub stellt bidirektionale Kommunikation zwischen der Cloud und Remotegeräten bereit. Von Remotegeräten gestreamte Daten werden von einem Stream Analytics-Auftrag transformiert. Dabei werden normalerweise nicht erforderliche Daten ausgefiltert. Die gefilterten Daten werden gespeichert oder zur weiteren Analyse weitergeleitet. Schließlich wird der App Service zum Hosten des Webportals verwendet.

Die Lösungsbereitstellung kann auch über die Befehlszeile erfolgen. Zu diesem Zweck können Sie den Lösungsquellcode klonen oder von bit.ly/2osI4RW, herunterladen und dann die Anleitungen unter bit.ly/2p7MPPc befolgen. Außerdem besteht die Option, eine Lösung lokal wie unter bit.ly/2nEePNi gezeigt bereitzustellen. In diesem Fall wird kein Azure App Service erstellt, weil das Lösungsportal auf einem lokalen Computer ausgeführt wird. Eine solche Vorgehensweise kann insbesondere für die Entwicklung und das Debuggen sinnvoll sein oder wenn Sie eine vorkonfigurierte Lösung ändern möchten.

Sobald die Bereitstellung abgeschlossen ist, können Sie die Lösung starten. Ihr Portal wird dann im Standardbrowser angezeigt (siehe Abbildung 1 weiter oben). Dieses Portal weist einige Registerkarten auf. Für diesen Artikel richte ich meine Aufmerksamkeit nur auf zwei dieser Registerkarten: „Dashboard“ und „Devices“ (Geräte). „Dashboard“ zeigt die Zuordnung der Remotegeräte und die Telemetriedaten an, die von diesen gestreamt werden. Die Registerkarte „Devices“ zeigt eine Liste der Remotegeräte (einschließlich ihres Status, der Funktionen und einer Beschreibung) an. Standardmäßig sind mehrere emulierte Geräte vorhanden. Lassen Sie mich nun erläutern, wie die neue, nicht emulierte Hardware registriert wird.

Registrieren eines Geräts

Zum Registrieren eines Geräts verwenden Sie das Lösungsportal. Dort klicken Sie auf den Link „Add a Device“ (Gerät hinzufügen), den Sie in der unteren linken Ecke finden. Anschließend können Sie zwischen einem simulierten und einem benutzerdefinierten Gerät wählen. Wählen Sie die zweite Option aus, und klicken Sie dann auf die Schaltfläche „Add New“ (Neues Gerät hinzufügen). Jetzt können Sie die Geräte-ID definieren. Ich habe diesen Wert auf „RemoteCamera“ festgelegt. Anschließend zeigt das Formular „ADD A CUSTOM DEVICE“ (Benutzerdefiniertes Gerät hinzufügen) die Anmeldeinformationen des Geräts an (siehe Abbildung 9), die ich später zum Verbinden meines IoT-Geräts mit dem IoT Hub verwende.

Zusammenfassung der Geräteregistrierung
Abbildung 9: Zusammenfassung der Geräteregistrierung

Gerätemetadaten und Kommunizieren mit der Cloud

Das Gerät, das Sie hinzufügen, wird in der Geräteliste angezeigt, und anschließend können Sie Gerätemetadaten oder Geräteinformationen senden. Die Geräteinformationen beinhalten ein JSON-Objekt, das das Remotegerät beschreibt. Dieses Objekt informiert den Cloudendpunkt über die Funktionen Ihres Geräts und umfasst eine Hardwarebeschreibung sowie die Liste der Remotebefehle, die Ihr Gerät akzeptiert. Diese Befehle können vom Endbenutzer über das IoT-Lösungsportal an Ihr Gerät gesendet werden. In der RemoteCamera-App werden Geräteinformationen als die DeviceInfo-Klasse (im Unterordner „AzureHelpers“) dargestellt:

public class DeviceInfo
{
  public bool IsSimulatedDevice { get; set; }
  public string Version { get; set; }
  public string ObjectType { get; set; }
  public DeviceProperties DeviceProperties { get; set; }
  public Command[] Commands { get; set; }
}

Die ersten beiden Eigenschaften der „DeviceInfo“ geben an, ob ein Gerät simuliert ist, und definieren die Version des DeviceInfo-Objekts. Wie Sie später sehen werden, wird die dritte Eigenschaft („ObjectType“) auf eine Zeichenfolgekonstante („DeviceInfo“) festgelegt. Diese Zeichenfolge wird von der Cloud (insbesondere vom Azure Stream Analytics-Auftrag) verwendet, um Geräteinformationen aus den Telemetriedaten zu filtern. „DeviceProperties“ (siehe Unterordner „AzureHelpers“) enthält eine Sammlung von Eigenschaften (z. B. Seriennummer, Arbeitsspeicher, Plattform, RAM), die Ihr Gerät beschreiben. Schließlich besteht die Commands-Eigenschaft aus einer Sammlung von Remotebefehlen, die Ihr Gerät erkennt. Sie definieren jeden Befehl, indem Sie seinen Namen und eine Liste von Parametern angeben, die von den Command- und CommandParameter-Klassen dargestellt werden (siehe „AzureHelpers\Command.cs“).

Zum Einrichten der Kommunikation zwischen Ihrem IoT-Gerät und dem IoT Hub verwenden Sie das NuGet-Paket „Microsoft.Azure.Devices.Client“. Dieses Paket stellt die DeviceClient-Klasse bereit, die Sie zum Senden von Nachrichten in die und Empfangen von Nachrichten aus der Cloud verwenden. Sie können eine Instanz von „DeviceClient“ mit den statischen Create- oder CreateFromConnectionString-Methoden erstellen. Hier verwende ich die erste Option („CloudHelper.cs“ im Ordner „AzureHelpers“):

public async Task Initialize()
{
  if (!IsInitialized)
  {
    deviceClient = DeviceClient.Create(
      Configuration.Hostname, Configuration.AuthenticationKey());
    await deviceClient.OpenAsync();
    IsInitialized = true;
    BeginRemoteCommandHandling();
  }
}

Wie Sie sehen können, verlangt die DeviceClient.Create-Methode, dass Sie den Hostnamen des IoT Hub und die Anmeldeinformationen des Geräts (den Bezeichner und den Schlüssel) angeben. Sie rufen diese Werte aus dem Lösungsportal während der Gerätebereitstellung ab (siehe Abbildung 9 weiter oben). In der RemoteCamera-App speichere ich den Hostnamen, die Geräte-ID und den Schlüssel in der statischen Configuration-Klasse:

public static class Configuration
{
  public static string Hostname { get; } = "<iot-hub-name>.azure-devices.net";
  public static string DeviceId { get; } = "RemoteCamera";
  public static string DeviceKey { get; } = "<your_key>";
  public static DeviceAuthenticationWithRegistrySymmetricKey AuthenticationKey()
  {
    return new DeviceAuthenticationWithRegistrySymmetricKey(DeviceId, DeviceKey);
  }
}

Außerdem implementiert die Configuration-Klasse eine statische Methode „AuthenticationKey“, die als Wrapper für die Anmeldeinformationen des Geräts in einer Instanz der DeviceAuthenticationWithRegistrySymmetricKey-Klasse dient. Ich verwende dies, um die Erstellung der DeviceClient-Klasseninstanz zu vereinfachen.

Sobald die Verbindung hergestellt wurde, müssen Sie nur noch die „DeviceInfo“ wie in Abbildung 10 gezeigt senden.

Abbildung 10: Senden von Geräteinformationen

public async Task SendDeviceInfo()
{
  var deviceInfo = new DeviceInfo()
  {
    IsSimulatedDevice = false,
    ObjectType = "DeviceInfo",
    Version = "1.0",
    DeviceProperties = new DeviceProperties(Configuration.DeviceId),
    // Commands collection
    Commands = new Command[]
    {
      CommandHelper.CreateCameraPreviewStatusCommand()
    }
  };
  await SendMessage(deviceInfo);
}

Die RemoteCamera-App sendet die Geräteinformationen, die die echte Hardware beschreiben. Die IsSimulatedDevice-Eigenschaft wird daher auf FALSE festgelegt. Wie bereits erwähnt, wird der „ObjectType“ auf „DeviceInfo“ festgelegt. Außerdem habe ich die Version-Eigenschaft auf 1.0 festgelegt. Für „DeviceProperties“ verwende ich frei wählbare Werte, die größtenteils aus statischen Zeichenfolgen bestehen (siehe SetDefaultValues-Methode der DeviceProperties-Klasse). Ich definiere außerdem einen Remotebefehl („Update camera preview“, Kameravorschau aktualisieren), der die Remotesteuerung der Kameravorschau ermöglicht. Dieser Befehl besitzt einen booleschen Parameter („IsPreviewActive“), der angibt, ob die Kameravorschau gestartet oder gestoppt werden soll (siehe Datei „CommandHelper.cs“ im Ordner „AzureHelpers“).

Zum tatsächlichen Senden von Daten in die Cloud implementiere ich die SendMessage-Methode:

private async Task SendMessage(Object message)
{
  var serializedMessage = MessageHelper.Serialize(message);
  await deviceClient.SendEventAsync(serializedMessage);
}

Grundsätzlich müssen Sie Ihr C#-Objekt in ein byte-Array serialisieren, das als JSON formatierte Objekte enthält (siehe statische MessageHelper-Klasse aus dem Unterordner „AzureHelpers“):

public static Message Serialize(object obj)
{
  ArgumentCheck.IsNull(obj, "obj");
  var jsonData = JsonConvert.SerializeObject(obj);
  return new Message(Encoding.UTF8.GetBytes(jsonData));
}

Anschließend verwenden Sie das sich ergebende Array als Wrapper in der Message-Klasse, die sie mit der SendEventAsync-Methode der DeviceClient-Klasseninstanz in die Cloud senden. Die Message-Klasse ist das Objekt, das Rohdaten (ein JSON-Objekt, das übertragen wird) mit weiteren Eigenschaften ergänzt. Diese Eigenschaften werden zum Nachverfolgen von Nachrichten verwendet, die zwischen Geräten und dem IoT Hub gesendet werden.

In der RemoteCamera-App wird das Herstellen einer Verbindung mit der Cloud und das Senden von Geräteinformationen durch zwei Schaltflächen ausgelöst, die sich auf der Registerkarte „Cloud“ befinden: „Connect and initialize“ (Verbinden und initialisieren) und „Send device info“ (Geräteinformationen senden). Der Klickereignishandler der ersten Schaltfläche ist an die Connect-Methode von „RemoteCameraViewModel“ gebunden:

public async Task Connect()
{
  await CloudHelper.Initialize();
  IsConnected = true;
}

Der Klickereignishandler der zweiten Schaltfläche ist mit der SendDeviceInfo-Methode der CloudHelper-Klasseninstanz verknüpft. Diese Methode wurde schon weiter oben behandelt.

Sobald Sie eine Verbindung mit der Cloud hergestellt haben, können Sie auch mit dem Senden von Telemetriedaten beginnen. Dies ähnelt dem Senden von Geräteinformationen. Sie können also die SendMessage-Methode verwenden, an die Sie ein telemetry-Objekt übergeben. In unserem Beispiel besitzt dieses Objekt (eine Instanz der Telemetry­Data-Klasse) nur eine einzige Eigenschaft: „Brightness“. Die folgende Abbildung zeigt ein vollständiges Beispiel für das Senden von Telemetriedaten in die Cloud, das in der SendBrightness-Methode der CloudHelper-Klasse implementiert wird:

public async void SendBrightness(byte brightness)
{
  if (IsInitialized)
  {
    // Construct TelemetryData
    var telemetryData = new TelemetryData()
    {
      Brightness = brightness
    };
    // Serialize TelemetryData and send it to the cloud
    await SendMessage(telemetryData);
  }
}

„SendBrightness“ wird direkt nach dem Abrufen der vom „ImageProcessor“ berechneten Helligkeit aufgerufen. Dies geschieht im ProcessingDone-Ereignishandler:

private void ImageProcessor_ProcessingDone(object sender, ImageProcessorEventArgs e)
{
  // Update display through dispatcher
  DisplayBrightness(e.Brightness);
  // Send telemetry
  if (remoteCameraViewModel.IsTelemetryActive)
  {
    remoteCameraViewModel.CloudHelper.SendBrightness(e.Brightness);
  }
}

Wenn Sie jetzt also die RemoteCamera-App ausführen, die Vorschau starten und eine Verbindung mit der Cloud herstellen, sehen Sie, dass Helligkeitswerte im entsprechenden Diagramm angezeigt werden. Abbildung 1 weiter oben zeigt dies. Beachten Sie Folgendes: Auch wenn simulierte Geräte der vorkonfigurierten Lösung für das Senden von Temperatur- und Luftfeuchtigkeitswerten als Telemetriedaten dediziert sind, können Sie auch andere Werte senden. Hier sende ich Helligkeitswerte, die automatisch im entsprechenden Diagramm angezeigt werden.

Verarbeiten von Remotebefehlen

Die CloudHelper-Klasse implementiert auch Methoden für die Verarbeitung von aus der Cloud empfangenen Remotebefehlen. Ähnlich wie weiter oben im Fall von „ImageProcessor“ verarbeite ich Befehle im Hintergrund („BeginRemoteCommandHandling“ der CloudHelper-Klasse). Ich verwende erneut das taskbasierte asynchrone Muster:

private void BeginRemoteCommandHandling()
{
  Task.Run(async () =>
  {
    while (true)
    {
      var message = await deviceClient.ReceiveAsync();
      if (message != null)
      {
        await HandleIncomingMessage(message);
      }
    }
  });
}

Diese Methode ist für das Erstellen eines Tasks verantwortlich, der kontinuierlich Nachrichten analysiert, die vom Cloudendpunkt empfangen werden. Zum Empfangen einer Remotenachricht rufen Sie die ReceiveAsync-Methode der DeviceClient-Klasse auf. „ReceiveAsync“ gibt eine Instanz der Message-Klasse zurück, die Sie zum Abrufen eines RAW-Bytearrays verwenden, das als JSON formatierte Remotebefehldaten enthält. Anschließend deserialisieren Sie dieses Array in das RemoteCommand-Objekt (siehe „RemoteCommand.cs“ im Ordner „AzureHelpers“), das in der MessageHelper-Klasse (Unterordner von „AzureHelpers“) implementiert wird:

public static RemoteCommand Deserialize(Message message)
{
  ArgumentCheck.IsNull(message, "message");
  var jsonData = Encoding.UTF8.GetString(message.GetBytes());
  return JsonConvert.DeserializeObject<RemoteCommand>(
    jsonData);
}

„RemoteCommand“ besitzt mehrere Eigenschaften, Sie verwenden aber normalerweise nur zwei Eigenschaften: „name“ und „parameters“, die den Befehlsnamen und Befehlsparameter enthalten. In der RemoteCamera-App verwende ich diese Werte zum Ermitteln, ob ein erwarteter Befehl empfangen wurde (siehe Abbildung 11). Wenn dies der Fall ist, löse ich das UpdateCameraPreviewCommandReceived-Ereignis aus, um diese Informationen an Listener zu übergeben. Dann informiere ich die Cloud, dass der Befehl akzeptiert wurde. Zu diesem Zweck verwende ich die CompleteAsync-Methode der DeviceClient-Klasse. Wenn der Befehl nicht erkannt wird, weise ich ihn mithilfe der RejectAsync-Methode zurück.

Abbildung 11: Deserialisierung und Analyse von Remotenachrichten

private async Task HandleIncomingMessage(Message message)
{
  try
  {
    // Deserialize message to remote command
    var remoteCommand = MessageHelper.Deserialize(message);
    // Parse command
    ParseCommand(remoteCommand);
    // Send confirmation to the cloud
    await deviceClient.CompleteAsync(message);
  }
  catch (Exception ex)
  {
    Debug.WriteLine(ex.Message);
    // Reject message, if it was not parsed correctly
    await deviceClient.RejectAsync(message);
  }
}
private void ParseCommand(RemoteCommand remoteCommand)
{
  // Verify remote command name
  if (string.Compare(remoteCommand.Name,
    CommandHelper.CameraPreviewCommandName) == 0)
  {
    // Raise an event, when the valid command was received
    UpdateCameraPreviewCommandReceived(this,
      new UpdateCameraPreviewCommandEventArgs(
      remoteCommand.Parameters.IsPreviewActive));
  }
}

Das UpdateCameraPreviewCommandReceived-Ereignis wird in der MainPage-Klasse verarbeitet. Anhängig vom Parameterwert, den ich aus dem Remotebefehl abrufe, stoppe oder starte ich die lokale Kameravorschau. Dieser Vorgang wird erneut an den Benutzeroberflächenthread gesendet. Abbildung 12 zeigt dies.

Abbildung 12: Aktualisieren der Kameravorschau

private async void CloudHelper_UpdateCameraPreviewCommandReceived(
  object sender, UpdateCameraPreviewCommandEventArgs e)
{
  if (Dispatcher.HasThreadAccess)
  {
    if (e.IsPreviewActive)
    {
      await remoteCameraViewModel.PreviewStart();
    }
    else
    {
      await remoteCameraViewModel.PreviewStop();
    }
  }
  else
  {
    await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
    {
      CloudHelper_UpdateCameraPreviewCommandReceived(sender, e);
    });
  }
}

Senden von Befehlen aus der Cloud

Abschließend möchte ich zeigen, dass das Lösungsportal für die Remotesteuerung Ihres IoT-Geräts verwendet werden kann. Zu diesem Zweck verwenden Sie die Registerkarte „Devices“ (Geräte), auf der Sie zunächst nach Ihrem Gerät suchen müssen. Klicken Sie dann auf das Gerät (siehe Abbildung 13).

Die Registerkarte „Devices“ des IoT-Portals mit angezeigten RemoteCamera-Details
Abbildung 13: Die Registerkarte „Devices“ des IoT-Portals mit angezeigten RemoteCamera-Details

Dies aktiviert einen Bereich mit Gerätedetails. Dort klicken Sie auf den Link „Commands“ (Befehle). Anschließend wird ein anderes Formular angezeigt, in dem Sie einen Remotebefehl auswählen und senden können. Das jeweilige Layout dieses Formulars hängt vom ausgewählten Befehl ab. Hier arbeite ich nur mit einem Befehl mit einem einzigen Parameter. Daher ist nur ein Kontrollkästchen verfügbar. Wenn Sie dieses Kontrollkästchen deaktivieren und den Befehl senden, hält die RemoteCamera-App die Vorschau an. Alle gesendeten Befehle werden zusammen mit ihrem Status in der Befehlsversionsgeschichte angezeigt. Abbildung 14 zeigt dies.

Ein Formular zum Senden von Remotebefehlen an das IoT-Gerät
Abbildung 14A: Ein Formular zum Senden von Remotebefehlen an das IoT-Gerät

Zusammenfassung

Ich habe gezeigt, wie die vorkonfigurierte Remoteüberwachungslösung der Azure IoT Suite eingerichtet wird. Diese Lösung dient zum Erfassen und Anzeigen von Informationen zu Bildern, die mit einer an das Remotegerät angeschlossenen Webcam erstellt wurden. Außerdem kann das IoT-Gerät remote gesteuert werden. Der begleitende Quellcode kann auf jedem UWP-Gerät ausgeführt werden. Sie müssen ihn daher nicht unbedingt auf dem Raspberry Pi bereitstellen. Dieser Artikel unterstützt Sie zusammen mit der Onlinedokumentation für die Remoteüberwachungslösung und ihrem Quellcode beim Schnelleinstieg in umfassende IoT-Entwicklung für praktische Remoteüberwachung. Wie Sie sehen, ist mit Windows 10 IoT Core weitaus mehr möglich als nur einfache blinkende LEDs.


Dawid Borycki ist Software Engineer und biomedizinischer Forscher, Autor und spricht häufig auf Konferenzen. Er beschäftigt sich gern mit neuen Technologien für Softwareexperimente und -prototypen.

Unser Dank gilt dem folgenden technischen Experten bei Microsoft für die Durchsicht dieses Artikels: Rachel Appel