Kapitel 4. Datenemulation

Diese Arbeit wurde mithilfe von KI übersetzt. Wir freuen uns über dein Feedback und deine Kommentare: translation-feedback@oreilly.com

Ich sehe, höre und rieche.

Soll ich eine Warnung übermitteln?

Falscher Alarm. Ignorieren.

Die Verarbeitung von Sensordaten aus Simulatoren und die Generierung von Protokollausgaben als Teil eines Auslöseereignisses ist schon ziemlich cool, aber wäre es nicht noch cooler, die Werte, die deine Sensoraufgabe anzeigt, mit Schiebereglern anzupassen? Noch besser wäre es, wenn du Nachrichten auf einer echten (oder emulierten) LED-Anzeige anzeigen könntest, wenn ein Auslöseereignis ausgelöst wird, oder?

In diesem Kapitel erfährst du, wie du einen Emulator einrichtest, konfigurierst und verwendest, der eine virtuelle LED-Anzeige sowie Temperatur-, Druck-, Luftfeuchtigkeits- und andere Messwerte liefern kann.

Was du in diesem Kapitel lernen wirst

Ein Großteil dieses Kapitels ist der Einrichtung und Konfiguration des Sense-Emu-Emulators gewidmet, einem virtuellen Sense HAT1 Gerät ist, das du auf Windows-, Mac- oder Linux-Plattformen betreiben kannst (obwohl ich es am einfachsten finde, es auf Linux zu betreiben).

Hinweis

Das Sense HAT ist eine Platine, die an den 40-poligen GPIO eines Raspberry Pi angeschlossen werden kann und eine Reihe von Messfunktionen bietet, darunter Feuchtigkeit, Druck und Temperatur. Die LED-Matrix zeigt Text oder Grafiken in mehreren Farben auf einem 8x8-Raster an. Außerdem sind eine Trägheitsmesseinheit (IMU) und ein Magnetometer eingebaut, die Kompasswerte, die x/y/z-Achsenausrichtung und die Beschleunigung messen. Der Sense-Emu-Emulator stellt eine virtuelle Instanz des Sense HAT zur Verfügung, um die es in diesem Kapitel geht.

Die Konfiguration eines IoT-Geräts kann dich vor interessante Herausforderungen stellen, ebenso wie die Installation und Konfiguration eines Emulators. In diesem Kapitel konzentriere ich mich auf den Emulator und gehe davon aus, dass du ihn für zukünftige Übungen verwenden wirst. Du kannst aber natürlich auch echte Hardware, wie z. B. einen Raspberry Pi, verwenden, um deinen CDA (und GDA) auszuführen.2 Wenn du dich für diese Einsatzstrategie entscheidest, musst du dein Gerät so einrichten, dass es sowohl als Gateway-Gerät als auch als eingeschränktes Gerät funktionieren kann.

Hinweis

Wenn du beschließt, Jenkins 2 für CI/CD auf deinem lokalen System einzurichten, solltest du dir das Buch Jenkins 2 Up & Running von Brent Laster (O'Reilly) ansehen. Wenn du Jenkins direkt auf einem Raspberry Pi mit einem Linux-basierten Betriebssystem (z. B. Raspbian) einsetzen willst, solltest du die Einrichtung und Konfiguration für Linux-basierte Umgebungen beachten. Auch wenn es den Rahmen dieses Buches sprengt, solltest du diesen zusätzlichen Konfigurationsschritt in Betracht ziehen, da er bei der Automatisierung deiner Entwicklungsumgebung helfen kann.

Zur Erinnerung: Dieses Kapitel befasst sich ausschließlich mit dem CDA, daher ist der Code in Python geschrieben. Wie bei den vorherigen Kapiteln solltest du die Schritte in PIOT-CDA-04-000 befolgen und einen neuen Zweig für dieses Kapitel auschecken.

Emulation von Sensoren und Aktuatoren

Sensoren lesen Daten aus einem System aus und decken ein breites Spektrum an Funktionen ab. Für die nächste Anwendung baust du Emulatoren, die die Funktion von Sensoren simulieren und es dir ermöglichen, ein oder mehrere Auslöseereignisse auszulösen, wenn die simulierten Sensoren Daten erzeugen, die eine Aktion deines Geräts erfordern.

Letztendlich liegt die Entscheidung über diese Aktion in den Händen der Gateway Device App und vielleicht später in der Analysekomponente deiner Cloud-Service-Infrastruktur. Für den Moment wirst du eine einfache Testfunktion in deiner Constrained Device App erstellen, die nach dem Überschreiten eines Schwellenwerts sucht und diesen nutzt, um ein Auslöseereignis an den simulierten Aktor zu senden. Sobald diese Funktion richtig funktioniert, wirst du sehen, dass die von dir entwickelte Infrastruktur ein wichtiger Bestandteil der nächsten Übungen sein wird.

Einrichten und Konfigurieren eines Emulators

Die Modularität des CDA-Designs ermöglicht es dir, neue Emulator-Funktionen hinzuzufügen, ohne allzu viele Änderungen vorzunehmen - im Wesentlichen fügst du den Klassen SensorAdapterManager und ActuatorAdapterManager Funktionen hinzu, um den Wechsel von der Simulation zur Emulation zu unterstützen, und fügst dann neue Emulator-Aufgaben für die Sensorik und Aktorik hinzu.

Die Aufgaben des Emulators für die Wahrnehmung werden von BaseSensorSimTask abgeleitet, während die Aufgaben für die Betätigung von BaseActuatorSimTask abgeleitet werden. Innerhalb der Managerklassen wird der Wechsel zwischen Simulation und Emulation im Konstruktor der jeweiligen Klasse verwaltet.

Bevor ich mich mit den Übungen und dem Code beschäftige, wollen wir uns einige Abhängigkeiten ansehen, damit die Emulatorfunktionen richtig funktionieren.

Der Sense-Emu Sense HAT Emulator

Die Entwicklung von Emulatoren kann kompliziert sein, und wir haben das Glück, Zugang zu einer Reihe verschiedener Tools zu haben, die das Testen und Emulieren verschiedener Hardwarekomponenten für die Verarbeitung von IoT Edge Tier unterstützen. Ein solches Tool ist der bereits erwähnte Sense-Emu Sense HAT-Emulator, der eine grafische Benutzeroberfläche (GUI) mit Hebeln enthält, mit denen du die Sensormesswerte einer Sense HAT-Karte emulieren kannst.3

Hinweis

Das Sense HAT ist eine Platine, die über den 40-poligen GPIO-Anschluss (General Purpose Input/Output) in einen Raspberry Pi eingebaut werden kann. Eine ausführliche Beschreibung dieser Funktion würde den Rahmen dieses Buches sprengen. Eine schnelle Online-Suche führt jedoch zu zahlreichen Ressourcen mit detaillierten Beschreibungen des Boards und seiner Sensoren, Beispielprojekten und Beispielcode, die nützlich sein können, wenn du echte Hardware in dein Design einbauen willst.

Abbildung 4-1 ist ein Screenshot des Sense-Emu-Emulators, der auf meinem lokalen System läuft.

Sense-Emu Sense HAT emulator screenshot
Abbildung 4-1. Sense-Emu Sense HAT-Emulator Screenshot

Wie du siehst, gibt es mehrere Möglichkeiten, Daten in der GUI zu erzeugen. Bevor ich diesen Screenshot gemacht habe, habe ich die Temperatur auf 28,8°C, den Druck auf 984,5 mbar und die Luftfeuchtigkeit auf 40,2% eingestellt.

Sobald der Emulator läuft, brauchst du eine Möglichkeit, ihn mit deinem CDA zu verbinden. Die Python-Bibliothek pisense unterstützt die Integration mit dem Sense-Emu-Emulator und dem eigentlichen Sense HAT-Board. Du kannst zwischen beiden umschalten, indem du bei der Initialisierung der Klasse Sense HAT in der pisense-Bibliothek ein Flag setzt.

Bevor wir uns mit dem Code beschäftigen, solltest du dir PIOT-CFG-04-001 ansehen, in dem die Installation und Konfiguration des Sense-Emu-Emulators beschrieben wird. Ich werde mich hier nicht lange damit aufhalten, aber es gibt wahrscheinlich noch andere Abhängigkeiten, damit der Emulator auf deinem System funktioniert. Ich habe den Sense-Emu-Emulator erfolgreich unter Windows mit WSL (mit einem separaten X11-Server) und unter macOS betrieben.

Die Konfigurationsdatei PiotConfig.props enthält im Abschnitt ConstrainedDevice eine Eigenschaft namens enableEmulator. Wenn dein Emulator eingerichtet und richtig konfiguriert ist und du die Übungen in diesem Abschnitt abgeschlossen hast, kannst du den Wert der Eigenschaft auf "True" setzen und deinen Code mit dem Emulator testen.

Lass uns mit dem Codieren beginnen.

Programmierübungen

Du wirst ein gemeinsames Muster in den Komponentenbeziehungen für die Emulatorfunktionalität feststellen - es ist dasselbe wie dein Entwurf und deine Implementierung aus Kapitel 3 für die Simulatorfunktionalität. Das bietet dir eine große Flexibilität für deine Implementierung, da du deinem CDA neue Funktionen hinzufügen kannst, die über die in diesen Übungen beschriebenen Anforderungen hinausgehen.

Integration von Sensor- und Aktuatoremulation in dein Anwendungsdesign

Abbildung 4-2 zeigt eine einfache design Ansicht des CDA, wenn diese neuen Funktionen integriert sind.

Constrained Device Application—integrated sensing and actuation app design
Abbildung 4-2. Constrained Device Application-integriertes Design für Sensorik und Aktorik

Das Design sieht dem Design in Abbildung 3-3, in dem du die Simulationsfunktionalität in deinen CDA integriert hast, verblüffend ähnlich. Die Modularität des Entwurfs ermöglicht das Hinzufügen von Emulatoraufgaben mit geringen Änderungen an der Managerlogik.

Werfen wir nun einen Blick auf den detaillierten Entwurf, der als UML dargestellt wird. Abbildung 4-3 zeigt eine Möglichkeit, die wichtigsten Komponenten dieser Übung darzustellen, wobei einige Details aus der vorherigen Übung aus Gründen der Übersichtlichkeit weggelassen wurden.

Wie der High-Level-Entwurf in Abbildung 4-2 ähnelt auch dieser detaillierte Entwurf der in Abbildung 3-4 dargestellten UML.

Constrained Device Application—integrated sensing and actuation app UML
Abbildung 4-3. Constrained Device Application-integrierte Sensorik- und Aktorik-App UML

Schauen wir uns die Anforderungen und die Umsetzung für Labormodul 04 an.

Sensoren emulieren

Erinnere dich an die beiden Hauptfunktionen von , die BaseSensorSimTask ausführt: erstellt eine Instanz von SensorData, in der die neuesten Sensorsimulationsdaten gespeichert werden, und stellt eine öffentliche Schnittstelle bereit, um eine neue Instanz zu erzeugen und auf deren Daten zuzugreifen.

In PIOT-CDA-04-001 erstellst du drei neue Sensor-Emulator-Tasks, die jeweils von BaseSensorSimTask abgeleitet werden, genau wie die Sensor-Simulator-Tasks in Kapitel 3. Der Unterschied besteht im Konstruktor und in der Methode generateTelemetry(). Diese Sensoraufgaben befinden sich in dem Paket ./programmingtheiot/cda/emulated. Hier ist eine Zusammenfassung der beteiligten Aktionen:

  • Erstelle (oder bearbeite) HumiditySensorEmulatorTask, PressureSensorEmulatorTask, und TemperatureSensorEmulatorTask.

  • Initialisiere die Instanz der Klasse SenseHAT mit Hilfe der Pisense-Bibliothek.

  • Aktualisiere generateTelemetry(), um Daten aus dem Sense HAT-Emulator abzurufen.

Lass uns diese Aktionen Schritt für Schritt auspacken. Ich werde mich auf TemperatureSensorEmulatorTask konzentrieren, da die anderen Aktionen sehr ähnlich aussehen werden.

Wie bei den anderen Python-Modulen, die du bearbeitet oder erstellt hast, musst du die Klasse erstellen und die entsprechenden Import-Anweisungen hinzufügen. Wenn du die Python-Komponenten-Codebasis verwendest, ist dies bereits für dich erledigt und sieht ähnlich aus wie im Folgenden:

from programmingtheiot.data.SensorData import SensorData

import programmingtheiot.common.ConfigConst as ConfigConst

from programmingtheiot.common.ConfigUtil import ConfigUtil
from programmingtheiot.cda.sim.BaseSensorSimTask import BaseSensorSimTask
from programmingtheiot.cda.sim.SensorDataGenerator import SensorDataGenerator

Die Klassendeklaration für TemperatureSensorEmulatorTask sieht dann so aus:

class TemperatureSensorEmulatorTask(BaseSensorSimTask):

Wenn du den Konstruktor erstellst, beachte seine Ähnlichkeit mit den Simulatoraufgaben, die du bereits geschrieben hast. Die wichtigsten Unterschiede sind die Konstruktionslogik und die Instanzlogik der Klasse SenseHAT aus der pisense-Bibliothek:

def __init__(self):
  super(TemperatureSensorEmulatorTask, self).__init__( \
    name = ConfigConst.TEMP_SENSOR_NAME, \
    typeID = ConfigConst.TEMP_SENSOR_TYPE)
  
  self.sh = SenseHAT(emulate = True)
Hinweis

Wenn du dich entscheidest, eine Kombination aus einem real Sense HAT Board und einem Raspberry Pi Einplatinencomputer zu verwenden, kannst du in der PiotConfig.props im Abschnitt ConstrainedDevice einen weiteren Parameter hinzufügen, der angibt, dass Hardware vorhanden ist (oder nicht). Wenn in deinem CDA echte Hardware verwendet werden soll, sollte die Klasse SenseHAT stattdessen mit emulate = False initialisiert werden. Wenn du dynamisch zwischen simulierten Daten, emulierten Daten und realen Daten (unter Verwendung von Hardware) umschalten willst, solltest du stattdessen einen ganzzahligen Wert verwenden, der dieses Initialisierungsverhalten in deiner Konfigurationsdatei widerspiegelt.

Wenn das erledigt ist, können wir uns der Funktion generateTelemetry() zuwenden. Hier ist ein Beispiel, das du vielleicht verwenden möchtest (wieder für die Klasse TemperatureSensorEmulatorTask ):

def generateTelemetry(self) -> SensorData:
  sensorData = \
    SensorData(name = self.getName(), typeID = self.getTypeID())
  
  sensorVal = self.sh.environ.temperature
  
  sensorData.setValue(sensorVal)
  self.latestSensorData = sensorData
          
  return sensorData

Ohne die simulierten (oder zufälligen) Daten ist es eigentlich etwas einfacher, oder?

Jetzt kannst du mit den beiden anderen Aufgaben weitermachen: HumiditySensorEmulatorTask und PressureSensorEmulatorTask. Achte nur darauf, dass du den richtigen SenseHAT Funktionsaufruf verwendest, um den entsprechenden Wert für jede Aufgabe zu erhalten.

Zeit für die Betätigungsemulatoren.

Aktoren emulieren

Erinnerst du dich an die Basisklasse BaseActuatorSimTask aus Kapitel 3? Wie BaseSensorSimTask führt sie zwei Hauptfunktionen aus - in diesem Fall abstrahiert sie die Funktionen "Aktivieren" und "Deaktivieren" eines Aktors mit der öffentlichen Methode updateActuator(ActuatorData). Das wird für die emulierten Aktuatoraufgaben sehr nützlich sein, also werden wir diese durch Ableitung von BaseActuatorSimTask erstellen.

Jetzt kommt der schwierige Teil. Welche Arten von Ereignissen willst du auslösen? Erinnere dich an unsere Problemstellung aus Kapitel 1:

Ich möchte die Umwelt in meinem Haus verstehen, wie sie sich im Laufe der Zeit verändert, und Anpassungen vornehmen, um den Komfort zu verbessern und gleichzeitig Geld zu sparen.

Eine Möglichkeit, den Begriff "Komfort" zu interpretieren, ist die Kontrolle der Temperatur und der Luftfeuchtigkeit (die eine Rolle dabei spielt, wie der Mensch die Temperatur empfindet, und die zum allgemeinen Komfort und zur Gesundheit einer Innenraumumgebung beiträgt). Verwenden wir beide Messwerte als potenzielle Auslöser und nennen wir zwei der Antriebsaufgaben HvacEmulatorTask und HumidifierEmulatorTask. Mit der HLK wird die Temperatur geregelt und mit dem Luftbefeuchter die relative Luftfeuchtigkeit.

Hinweis

Bei der Benennung der Aktuator-Aufgaben wird das Wort "Aktuator" weggelassen, da es meiner Meinung nach ziemlich klar ist, dass diese Geräte ein- oder ausgeschaltet werden. Ein reales System kann auch Sensormesswerte von diesen Geräten sammeln. Der Einfachheit halber werden unsere virtuellen Geräte nur Befehle zur Aktivierung annehmen.

Da du auch die LED-Matrix auf dem Emulator verwenden wirst, könnte es hilfreich sein, einen Aktor für die Beleuchtung des Bildschirms zu verwenden, daher spezifiziert PIOT-CDA-04-002 auch eine LedDisplayEmulatorTask.

Schauen wir uns die in PIOT-CDA-04-002 aufgeführten Aktionen an:

  • Erstelle (oder bearbeite) HvacEmulatorTask, HumidifierEmulatorTask, und LedDisplayEmulatorTask.

  • Initialisiere die SenseHAT Instanz mit der pisense Bibliothek.

  • Aktualisiere _handleActuation(), um die entsprechenden Daten über die Instanz SenseHAT in die LED-Anzeige zu schreiben.

Betrachte den letzten Punkt. Da der Emulator nicht wirklich eine Klimaanlage oder einen Luftbefeuchter emuliert, verwenden wir nur die LED-Anzeige, um den Benutzer darüber zu informieren, dass ein Ereignis eintritt und etwas ein- oder ausgeschaltet wird. Du kannst bei der Umsetzung natürlich kreativ werden, aber für den Moment lassen wir einfach eine Nachricht auf dem Bildschirm durchlaufen.

Hier ist ein Beispiel für die HvacEmulatorTask, einschließlich der Importe, der Klassendeklaration und der Konstruktorinitialisierung. Es sieht TemperatureSensorEmulatorTask sehr ähnlich, nicht wahr?

import logging

from time import sleep

import programmingtheiot.common.ConfigConst as ConfigConst

from programmingtheiot.common.ConfigUtil import ConfigUtil
from programmingtheiot.cda.sim.BaseActuatorSimTask import BaseActuatorSimTask

from pisense import SenseHAT

class HvacEmulatorTask(BaseActuatorSimTask):
  def __init__(self):
    super(HvacEmulatorTask, self).__init__( \
      name = ConfigConst.HVAC_ACTUATOR_NAME, \
      typeID = ConfigConst.HVAC_ACTUATOR_TYPE, \
      simpleName = "HVAC")
  
  self.sh = SenseHAT(emulate = True)

Erinnere dich daran, dass die Basisklasse BaseActuatorSimTask zwei private Methoden namens _activateActuator() und _deactivateActuator() definiert. Beide werden automatisch aufgerufen, wenn der Befehl an die Methode updateActuator() in der Basisklasse gesendet wird. Zugegeben, das ist nicht ideal für jede Situation, aber für unseren Bedarf ist es ausreichend.

Hier ist eine Beispielimplementierung für jede dieser Methoden:

def _activateActuator(self, \
  val: float = ConfigConst.DEFAULT_VAL, \
  stateData: str = None) -> int:
  
  if self.sh.screen:
    msg = self.getSimpleName() + ' ON: ' + \
    str(val) + 'C'

    self.sh.screen.scroll_text(msg)
    return 0
  else:
    logging.warning('No LED screen instance available.')
    return -1

Die Implementierung sieht dem simulierten Aktor, der eine Nachricht an die Konsole protokolliert hat, ziemlich ähnlich, nicht wahr?

Die Deaktivierungsfunktion ist ähnlich. Hier ist ein Blick auf die Implementierung:

def _deactivateActuator(self, \
  val: float = ConfigConst.DEFAULT_VAL, \
  stateData: str = None) -> int:
  
  if self.sh.screen:
    msg = self.getSimpleName() + ' OFF'
    self.sh.screen.scroll_text(msg)
    
    sleep(5)
    
    # optionally, clear the screen when done scrolling
    self.sh.screen.clear()
    return 0
  else:
    logging.warning('No LED screen instance available.')
    return -1

Der große Unterschied ist die Schlafverzögerung. Sie ist zwar völlig optional, aber ich habe sie hier eingebaut, um der Nachricht Zeit zum Scrollen zu geben. Allerdings beeinträchtigt sie das Timing des Aufrufs, da sie andere CDA-Verarbeitungen blockiert und aufhält, wenn sie nicht innerhalb eines Threads ausgeführt wird.

Tipp

Wenn du dich für einen Multithreading-Ansatz entscheidest, solltest du eine Warteschlange verwenden, um Nachrichten zu speichern, falls sie schneller eingehen, als das Display sie verarbeiten kann. Das bringt eine weitere interessante Herausforderung mit sich: Woher kannst du wissen, wie schnell ein emulierter (oder echter) Sensor abgefragt werden muss oder wie lange ein emulierter (oder echter) Auslösevorgang dauern wird?

Die kurze Antwort ist, dass du die Hardwarespezifikation des Sensors oder Aktors, die zeitlichen Beschränkungen des Geräts (oder des Emulators), die Häufigkeit des Aufrufs und die Berücksichtigung dieser Zeitvorgaben in deinem CDA-Design kennen musst. Das kann ein echtes Problem sein, wenn es nicht richtig gehandhabt wird. Wir können hier nicht darauf eingehen, aber ich möchte dich ermutigen, dies bei der Entwicklung deiner Kanten-Lösung zu bedenken.

Was wäre dann der bessere Weg für die Deaktivierungsfunktion? Wenn die Nachricht Zeit zum Scrollen haben muss und du weißt, wie lange jedes Zeichen braucht (nehmen wir an, es ist etwa eine halbe Sekunde oder 500 Millisekunden), berechne einfach die Länge der anzuzeigenden Zeichenfolge und multipliziere sie dann mit der Anzahl der Sekunden (oder Teilsekunden). Wenn die Zeichenfolge zum Beispiel 20 Zeichen lang ist und jedes Zeichen 500 Millisekunden braucht, um angezeigt zu werden und zu scrollen, musst du ungefähr 10 Sekunden warten, bis jedes Zeichen scrollt.

Vielleicht ist es besser, eine grafische 8x8-Darstellung mit Symbolen und/oder Farben zu verwenden, die den Status anzeigen, und sie leuchten zu lassen, solange der jeweilige Status in Kraft ist. Ah, ja - das wäre eine hervorragende zusätzliche Übung, nicht wahr? Aber ich schweife ab. Jetzt, da du die Herausforderung kennst und einen plausiblen Weg zu ihrer Lösung gefunden hast, kannst du einen anderen Umsetzungsansatz wählen!

Du bist fast fertig. Es sind nur noch zwei Schritte nötig: die Integration mit SensorAdapterManager und die Integration mit ActuatorAdapterManager.

Verbinden von emulierten Sensoren mit dem Sensor Adapter Manager

Diese Übung, die in PIOT-CDA-04-003 definiert ist, aktualisiert SensorAdapterManager, indem die neu erstellten Emulator-Tasks innerhalb der Konstruktorinitialisierung einführt. Der Konstruktionsansatz ermöglicht es dir, ganz einfach zwischen emulierten Sensor-Tasks und simulierten Sensor-Tasks zu wechseln, indem du das enableEmulator Flag in PiotConfig.props von True auf False umstellst.

Die wichtigsten Maßnahmen sind folgende:

  • Unterstützung für das enableEmulator Flag von PiotConfig.props hinzufügen.

  • Füge die Emulator-Sensoraufgaben für Temperatur, Druck und Luftfeuchtigkeit (und alle anderen, die du hinzufügen möchtest) hinzu.

  • Verwende das enableEmulator Flag, um zwischen der Verwendung von simulierten und emulierten Sensor-Tasks zu wechseln.

Da du bereits mit dem Konstruktor von SensorAdapterManager vertraut bist, folgt hier der Code, der direkt nach der Deklaration von self.dataMsgListener = None folgt (das sollte die letzte Codezeile in der Konstruktorinitialisierung sein):

if not self.useEmulator:
  self.dataGenerator = SensorDataGenerator()
  
  tempFloor = \
    configUtil.getFloat( \
      section = ConfigConst.CONSTRAINED_DEVICE, \
      key = ConfigConst.TEMP_SIM_FLOOR_KEY, \
      defaultVal = SensorDataGenerator.LOW_NORMAL_INDOOR_TEMP)

  tempCeiling = \
    configUtil.getFloat( \
      section = ConfigConst.CONSTRAINED_DEVICE, \
      key = ConfigConst.TEMP_SIM_CEILING_KEY, \
      defaultVal = SensorDataGenerator.HI_NORMAL_INDOOR_TEMP)
  
  tempData = \
    self.dataGenerator.generateDailyIndoorTemperatureDataSet( \
      minValue = tempFloor, \
      maxValue = tempCeiling, \
      useSeconds = False)
               
  self.tempAdapter = \
    TemperatureSensorSimTask(dataSet = tempData)
  
  # TODO: add other sensor simulator tasks

else:
  # load the Temperature emulator
  tempModule = \
    import_module( \
      'programmingtheiot.cda.emulated.TemperatureSensorEmulatorTask', \
      'TemperatureSensorEmulatorTask')

  teClazz = \
    getattr(tempModule, 'TemperatureSensorEmulatorTask')

  self.tempAdapter = teClazz()
    
  # TODO: add other sensor emulator tasks

Wenn das self.useEmulator Flag False ist, wird die simulierte Sensorfunktionalität verwendet. Der restliche Code in diesem Abschnitt ist derselbe Code, den du in Kapitel 3 implementiert hast. Aber was ist mit der else-Klausel? Lasst sie uns aufschlüsseln.

Die erste Zeile nach der else-Klausel lautet wie folgt:

  tempModule = \
    import_module( \
      'programmingtheiot.cda.emulated.TemperatureSensorEmulatorTask', \
      'TemperatureSensorEmulatorTask')

Wenn du schon eine Weile in Python programmierst, wird dir das vielleicht bekannt vorkommen. Der Code weist den Python-Interpreter an, das Modul TemperatureSensorEmulatorTask (und die gleichnamige Klasse) mit import_module dynamisch zu laden. Damit wird das integrierte Modul __import__ einfach mit einer etwas benutzerfreundlicheren Schnittstelle versehen, um das Modul zu laden (wiederum nur, wenn self.useEmulator True ist).

Tipp

Wenn du stattdessen lieber das integrierte __import__ verwenden möchtest, kannst du den Aufruf import_module() durch den folgenden ersetzen:

tempModule = \
  __import__( \

    'programmingtheiot.cda.emulated.TemperatureSens
orEmulatorTask', \

    fromlist =
['TemperatureSensorEmulatorTask']).

Ist diese dynamische Modul- und Klassenladefähigkeit für die Zwecke dieses Kapitels unbedingt erforderlich? Nein, das ist sie nicht. Es ist jedoch ein Werkzeug, das du für die zukünftige Integration nutzen kannst. Es dient zwei Zwecken, von denen keiner zu diesem Zeitpunkt benötigt wird:

  1. Er erstellt ein Muster für das dynamische Laden von Modulen, die möglicherweise nicht existieren, indem er ein einfaches Konfigurationsdateiflag verwendet. Eine interessante Übung, aber nicht kritisch. Noch nicht.

  2. Mit diesem Muster kannst du hardwarespezifischen Code innerhalb des bestehenden python-components Pakets `programmingtheiot.cda.embedded` implementieren. Das kann nützlich sein, wenn du dich entscheidest, einige der optionalen Übungen des Labormoduls 04 in Angriff zu nehmen.

Nach demselben Muster kannst du auch die Aufgaben für die Luftfeuchtigkeit und den Luftdruck dynamisch laden: HumiditySensorEmulatorTask bzw. PressureSensorEmulatorTask. Achte darauf, dass du diese Komponenten in deinen Code einfügst, bevor du den manuellen Integrationstest durchführst.

Lass uns testen, ob du zwischen simuliertem und emuliertem Fühlen wechseln kannst. Schau dir den Test am Ende der Anforderungskarte an - du wirst sehen, dass die Anweisungen etwas komplizierter sind als bei den vorherigen Tests. Hier ist eine Zusammenfassung:

  • Stelle sicher, dass das enableEmulator Flag in PiotConfig.props auf True gesetzt ist und starte dann den Sense-Emu Emulator. In der Kommandozeile solltest du einfach sense_emu_gui eingeben können.

  • Führe SensorAdapterManagerTest innerhalb des Pfades python-components ./src/test/python/programmingtheiot/part02/integration/system aus.

Wenn du den Sense-Emu-Emulator unter WSL und deine IDE unter Windows verwendest, kann es sein, dass bei den manuellen Integrationstests für SensorAdapterManagerTest einen Fehler erzeugt. Das liegt wahrscheinlich daran, dass deine IDE nicht in der Lage ist, mit dem X11-Server zu kommunizieren. Die einfache (und schnelle) Lösung besteht darin, den Test über die Kommandozeile auszuführen. Dazu navigierst du zu dem Pfad, der SensorAdapterManagerTest enthält, und führst ihn mit dem folgenden Befehl in deiner Virtualenv aus:

python -m unittest SensorAdapterManagerTest
Hinweis

Wenn du dich entschieden hast, deine Python-Umgebung ohne eine Virtualenv einzurichten, musst du sicherstellen, dass du den passenden Python-Interpreter verwendest (z. B. Python 3). Vergewissere dich außerdem, dass dein PYTHONPATH in deiner Umgebung festgelegt ist. Er muss sowohl den Pfad ./src/main/python als auch ./src/test/python enthalten.

Die Ausgabe sollte verschiedene Meldungssequenzen enthalten, die den folgenden ähneln (je nachdem, wie lange du deine Tests laufen lässt):

2021-01-01 15:06:39,776:SensorAdapterManagerTest:INFO:Testing SensorAdapterManager
class...
.
.
2021-01-01 15:06:45,126:SensorAdapterManager:INFO:Generated humidity data:
name=HumiditySensor,typeID=1,timeStamp=2021-01-
01T20:06:45.125842+00:00,statusCode=0,hasError=False,locationID=constraineddevice00
1,elevation=0.0,latitude=0.0,longitude=0.0,value=0.0
2021-01-01 15:06:45,128:SensorAdapterManager:INFO:Generated pressure data:
name=PressureSensor,typeID=2,timeStamp=2021-01-
01T20:06:45.125956+00:00,statusCode=0,hasError=False,locationID=constraineddevice00
1,elevation=0.0,latitude=0.0,longitude=0.0,value=848.703369140625
2021-01-01 15:06:45,129:SensorAdapterManager:INFO:Generated temp data:
name=TempSensor,typeID=3,timeStamp=2021-01-
01T20:06:45.125996+00:00,statusCode=0,hasError=False,locationID=constraineddevice00
1,elevation=0.0,latitude=0.0,longitude=0.0,value=24.984375
.
.
2021-01-01 15:07:20,139:base:INFO:Job "SensorAdapterManager.handleTelemetry
(trigger: interval[0:00:05], next run at: 2021-01-01 15:07:25 EST)" executed
successfully
.
.

Wenn diese Log-Ausgabe einigermaßen gut mit deiner eigenen übereinstimmt, ist das großartig! Es ist Zeit, zu den Aktuator-Emulatoren überzugehen.

Emulierte Stellantriebe mit dem Stellantriebsadapter-Manager verbinden

Die letzte formale Übung in diesem Kapitel, die in PIOT-CDA-04-004 definiert ist, folgt demselben Muster wie die vorherige für ActuatorAdapterManager:

  • Unterstützung für das enableEmulator Flag von PiotConfig.props hinzufügen.

  • Füge die Emulator-Aktoraufgaben für die HLK, den Befeuchter und die LED-Matrix hinzu.

  • Verwende das enableEmulator Flag, um zwischen der Verwendung von simulierten Aktuatoraufgaben und der Verwendung von emulierten Aktuatoraufgaben zu wechseln.

Denke daran, dass sich das Konstruktionsmuster für ActuatorAdapterManager nur ändert, um den Wechsel zwischen Emulator und Simulator zu unterstützen. Unmittelbar nach der Deklaration von self.dataMsgListener = None (die wiederum die letzte Codezeile in der Konstruktorinitialisierung sein sollte) fügst du Folgendes hinzu:

if not self.useEmulator:
  # create the humidifier actuator
  self.humidifierActuator = HumidifierActuatorSimTask()

  # create the HVAC actuator
  self.hvacActuator = HvacActuatorSimTask()
else:
  # load the HVAC emulator
  hvacModule = \
    import_module( \
      'programmingtheiot.cda.emulated.HvacEmulatorTask', \
      'HvacEmulatorTask')
  
  hveClazz = \
    getattr(hvacModule, 'HvacEmulatorTask')
  
  self.hvacActuator = hveClazz()
  
  # TODO: add other actuator tasks

Das ist doch nichts Neues, oder? Dieses Mal lädst du die HvacEmulatorTask dynamisch, aber mit der gleichen Logik wie bei der TemperatureSensorEmulatorTask.

Vergewissere dich, dass du HumidifierEmulatorTask und LedDisplayEmulatorTask einfügst, bevor du mit den manuellen Integrationstests fortfährst. Hier gelten die gleichen Bedingungen wie für SensorAdapterManager, wenn du den Sense-Emu-Emulator verwendest: Stelle sicher, dass das enable​Emulator Flag in PiotConfig.props auf True gesetzt ist und starte den Emulator.

Wie bei SensorAdapterManagerTest kannst du auch ActuatorAdapterManagerTest aus dem Python-Komponenten-Pfad ./src/test/python/programmingtheiot/part02/integration/system/ ausführen:

python -m unittest ActuatorAdapterManagerTest

Achte bei diesem Test auch auf die Sense-Emu GUI und beobachte die LED-Anzeige sowie die Log-Ausgabe.

Abbildung 4-4 zeigt ein Beispielbildschirmfoto, auf dem der erste Buchstabe der LED-Meldung "ON" zu sehen ist.

Sending an “ON” message to the Sense-Emu LED matrix
Abbildung 4-4. Senden einer "ON"-Nachricht an die Sense-Emu-LED-Matrix

Abbildung 4-4 zeigt einen kleinen Teil der Lauftextmeldung, die anzeigt, dass die HLK eingeschaltet ist (das "O" ist abgebildet). Eine Übung, die du in Betracht ziehen solltest, ist das Auslösen von zwei Ereignissen, wenn die Temperatur angepasst werden muss: eines an den HLK-Emulator (der technisch gesehen nur eine Meldung protokollieren kann) und das andere an die LED-Matrix, wo du eine kreativere Visualisierung der HLK und des Befeuchters innerhalb der 8×8-Matrix erstellen kannst.

Die Log-Meldungen sehen ähnlich aus wie das hier beschriebene Muster:

2021-01-01 16:58:17,797:ActuatorAdapterManagerTest:INFO:Testing ActuatorAdapterManager
class...
.
.
.
2021-01-01 16:58:25,909:ActuatorAdapterManager:INFO:Actuator command received for
location ID constraineddevice001. Processing...
2021-01-01 16:58:25,910:BaseActuatorSimTask:INFO:Deactivating actuator...
2021-01-01 16:58:36,729:DefaultDataMessageListener:INFO:Actuator Command: 0
2021-01-01 16:58:36,736:ActuatorAdapterManager:INFO:Actuator command received for
location ID constraineddevice001. Processing...
2021-01-01 16:58:36,745:BaseActuatorSimTask:INFO:Activating actuator...
2021-01-01 16:58:42,325:DefaultDataMessageListener:INFO:Actuator Command: 1
2021-01-01 16:58:42,326:ActuatorAdapterManager:INFO:Actuator command received for
location ID constraineddevice001. Processing...
2021-01-01 16:58:42,327:BaseActuatorSimTask:INFO:Deactivating actuator...
2021-01-01 16:58:51,120:DefaultDataMessageListener:INFO:Actuator Command: 0
.
.
.

Wenn deine Tests erfolgreich verlaufen sind, ist es Zeit zu feiern. Jetzt funktioniert nicht nur die simulierte CDA-Funktionalität, sondern auch die Emulation von Sensoren und Aktoren! Im nächsten Kapitel geht es darum, wie du die Daten, die du jetzt erzeugst, verwalten kannst, damit sie gesammelt, (eventuell) übertragen und ausgewertet werden können.

Zusätzliche Übungen

Die Benutzeroberfläche des Sense-Emu-Emulators ermöglicht es dir, Werte über die vorgegebenen Schieberegler und den Joystick zu verändern. Dies kann besonders nützlich sein, wenn du die Werte in deiner emulierten Sensorumgebung manuell ändern willst, um verschiedene Schwellenwertüberschreitungen zu testen.

Schwellenwert-Management

Erstelle einen weiteren Emulator-Adapter, der die Joystick-Steuerung der Sense-Emu-Benutzeroberfläche interpretiert und die Untergrenze (Abwärts-Taste) und Obergrenze (Aufwärts-Taste) deines Temperatur- oder Feuchtigkeitsschwellenwerts anpasst.

Mit dieser kannst du die Hysteresefunktion, die du in der zusätzlichen Übung in Kapitel 3 implementiert hast, mit diesen neuen Schwellenwerten und den simulierten Datenwerten von SensorDataGenerator (oder deiner eigenen Version) ausprobieren.

Fazit

In diesem Kapitel hast du etwas über die Hardware-Emulation gelernt und wie du den Sense HAT-Emulator in deinen CDA integrierst. Außerdem hast du erfahren, welche Herausforderungen mit der Auslösung von Ereignissen verbunden sind und dass die Logik zur Verarbeitung solcher Ereignisse nicht immer so einfach ist wie das Senden eines EIN- oder AUS-Befehls. In Bezug auf das Design hast du mehr über die Modularität des CDAs erfahren und wie du damit einige dieser Herausforderungen meistern kannst.

Jetzt bist du bereit für das letzte Kapitel von Teil II - Kapitel5 - in demdu etwas über die Datentransformation erfährst, die dir helfen wird, deine CDA und GDA zu integrieren.

1 Das Sense HAT ist eine Platine, die über ihre 40-polige GPIO-Schnittstelle an verschiedene Raspberry Pi Einplatinencomputer-Module angeschlossen werden kann. Mehr über den Sense HAT erfährst du auf der Raspberry Pi Website.

2 Wenn du daran interessiert bist, den Raspberry Pi zu benutzen, lies das Raspberry Pi Cookbook, Third Edition, von Simon Monk (O'Reilly).

3 Du kannst online mehr über das Sense HAT Board lesen. Wenn du dich für andere Funktionen des Emulators interessierst, kannst du die Online-Dokumentation lesen und die integrierten Demos im Menü Datei → "Beispiel öffnen" der Emulator-GUI ausprobieren.

Get Programmierung des Internets der Dinge now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.