Einführung in die Entwicklung von MeshCore Sensor-Knoten
MeshCore Blog von scottpowell

Einführung in die Entwicklung von MeshCore Sensor-Knoten

Die Entwicklung und Inbetriebnahme eines MeshCore Sensor-Knotens erfordert im Vergleich zu Repeater-, Companion- oder Room-Server-Varianten fortgeschrittenere Kenntnisse. Der Grund hierfür liegt in der Vielzahl potenzieller Anwendungsszenarien und der daraus resultierenden Notwendigkeit individueller Anpassungen, die eine Vorkonfiguration ab Werk unpraktikabel machen. Sensor-Knoten sind daher spezifisch für ihren vorgesehenen Einsatzzweck zu gestalten, beispielsweise zur Überwachung des Wasserstands in einem Tank.

Setup der Entwicklungsumgebung

Für die Entwicklung eines Sensor-Knotens wird Visual Studio Code (VSCode) benötigt. Nach der Installation von VSCode ist die Erweiterung PlatformIO zu installieren. PlatformIO ist ein Open-Source-Ökosystem für die Entwicklung von Embedded-Systemen, das eine plattformunabhängige Entwicklungsumgebung bietet.

Anschließend wird das MeshCore Firmware-Repository mittels Git geklont:

CLI
git clone https://github.com/meshcore-dev/MeshCore.git

Startpunkt: Das Sensor-Beispiel

Die Sensor-Firmware dient als Vorlage (template), die bereits die meisten notwendigen Komponenten enthält. Sie ist im Projektbaum unter /examples/simple_sensor zu finden. Für typische Anpassungen genügt es meist, die Datei main.cpp zu modifizieren. Hier wird eine Klasse namens MyMesh definiert, die von SensorMesh erbt. Diese Basisklasse stellt die gesamte grundlegende Infrastruktur und die notwendigen Hooks bereit, die für die Sensorfunktionalität erforderlich sind.

Board-Varianten konfigurieren

Viele der unterstützten Platinen verfügen bereits über eine vordefinierte PlatformIO-Umgebung für das simple_sensor-Beispiel, wie beispielsweise [env:Heltec_v3_sensor]. Es kann jedoch notwendig sein, eine eigene Umgebung im platformio.ini-File in einem der /variant-Ordner zu definieren.

Diese Konfiguration legt die Build-Regeln und Abhängigkeiten für die spezifische Kombination aus Board und Firmware-Rolle fest. Gegebenenfalls müssen auch die target.h und target.cpp-Dateien der Variante angepasst werden. Diese target-Module instanziieren verschiedene globale Objekte, wie das Beispiel EnvironmentSensorManager zeigt:

CLI
EnvironmentSensorManager sensors;
EnvironmentSensorManager ist eine vielseitige Helferklasse, die auch in anderen Firmware-Typen (z.B. Repeater) verwendet wird. Sie übernimmt die Low-Level-Verwaltung physischer Sensoren wie BME180s. Die meisten dieser Funktionen sind bereits implementiert. Dennoch ist es typischerweise erforderlich, die im Knoten verwendeten Module explizit zu aktivieren. Dies geschieht in der platformio.ini der Variante über build_flags:
CLI
build_flags =
  -D ENV_INCLUDE_GPS=1
  -D ENV_INCLUDE_AHTX0=1
  -D ENV_INCLUDE_BME280=1
  -D ENV_INCLUDE_BMP280=1
  -D ENV_INCLUDE_SHTC3=1
  -D ENV_INCLUDE_SHT4X=1
  -D ENV_INCLUDE_LPS22HB=1
  -D ENV_INCLUDE_INA3221=1
  -D ENV_INCLUDE_INA219=1
  -D ENV_INCLUDE_INA226=1
  -D ENV_INCLUDE_INA260=1
  -D ENV_INCLUDE_MLX90614=1
  -D ENV_INCLUDE_VL53L0X=1
  -D ENV_INCLUDE_BME680=1
  -D ENV_INCLUDE_BMP085=1

Grundlegende Konzepte

Ein MeshCore Sensor-Knoten kann folgende Funktionen nutzen:

  • Alarme (mit hoher oder niedriger Priorität)
  • Telemetrie-Abfragen (andere Knoten können Telemetriedaten abrufen)
  • Zeitreihendaten
  • Benutzerdefinierte CLI-Befehlslogik (z.B. "Schalter A einschalten")
  • Telemetrie-Push-Abonnements (zukünftig unterstützt)

Alarme

Das Beispiel demonstriert die Alarmfunktion mittels der Trigger-Klasse und alertIf()-Aufrufen:

CLI
Trigger low_batt, critical_batt;

  void onSensorDataRead() override {
    float batt_voltage = getVoltage(TELEM_CHANNEL_SELF);

    alertIf(batt_voltage < 3.4f, critical_batt, HIGH_PRI_ALERT, "Battery is critical!");
    alertIf(batt_voltage < 3.6f, low_batt, LOW_PRI_ALERT, "Battery is low");
  }

Alarme mit hoher Priorität (HIGH_PRI_ALERT) werden wiederholt an Knoten im ACL (Access Control List) gesendet, die das Bit PERM_RECV_ALERTS_HI gesetzt haben, und warten auf eine Bestätigung (ACK), ähnlich wie bei Textnachrichten. Alarme mit niedriger Priorität (LOW_PRI_ALERT) senden die Nachricht nur einmal und warten nicht auf ein ACK. Diese werden an Knoten im ACL mit dem Bit PERM_RECV_ALERTS_LO gesendet.

Telemetrie

Telemetrie ist ein Kernmechanismus von MeshCore und weitgehend automatisiert. Das Sensorobjekt der Ziel- bzw. Variantenkonfiguration übernimmt die LPP (Low Power Payload)-Kodierung aller gesammelten Telemetriemesswerte.

Normalerweise müssen andere Knoten Telemetriedaten vom Sensor-Knoten anfordern, woraufhin eine Telemetrieantwort gesendet wird. In Kürze wird es jedoch auch möglich sein, Telemetrie-Push zu abonnieren. Hierbei sendet der Sensor-Knoten Telemetrie-Pakete an Abonnenten, aber nur, wenn bestimmte Werte sich um einen minimalen Betrag ändern. Ein Abonnent könnte beispielsweise eine SUBSCRIBE-Anfrage senden und festlegen, dass die Temperatur um mindestens 2 Grad Celsius ändern muss, um einen Push auszulösen.

Telemetrie-Push wird voraussichtlich mit Mechanismen zur Missbrauchsminderung ausgestattet sein:

  • Abonnements haben eine begrenzte Gültigkeitsdauer (im Subscribe-Response zurückgegeben), wodurch Abonnenten regelmäßig neu abonnieren müssen.
  • Ein Regions-Scope muss verwendet werden (als Fallback, falls kein direkter Pfad etabliert ist).
  • Der Abonnent muss angeben, welche LPP-Kanäle/-Typen und welche minimalen Deltas diese ändern müssen, um einen Push auszulösen.

Zeitreihendaten

Der Sensor kann periodische Messwerte erfassen und diese in einem zirkulären Puffer im flüchtigen Speicher ablegen, wobei die Helferklasse TimeSeriesData verwendet wird. Ein Beispiel:

CLI
TimeSeriesData  battery_data;

  MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables)
     : SensorMesh(board, radio, ms, rng, rtc, tables), 
       battery_data(12*24, 5*60)    // 24 Stunden Batteriedaten, alle 5 Minuten
  {
  }

  void onSensorDataRead() override {
    float batt_voltage = getVoltage(TELEM_CHANNEL_SELF);

    battery_data.recordData(getRTCClock(), batt_voltage);   // Batterie aufzeichnen
  }

  int querySeriesData(uint32_t start_secs_ago, uint32_t end_secs_ago, MinMaxAvg dest[], int max_num) override {
    battery_data.calcMinMaxAvg(getRTCClock(), start_secs_ago, end_secs_ago, &dest[0], TELEM_CHANNEL_SELF, LPP_VOLTAGE);
    return 1;
  }

Entfernte Knoten können dann Min/Max/Durchschnitts-Abfragen an den Sensor-Knoten senden, wobei ein Zeitbereich angegeben wird. Dazu muss die Methode querySeriesData() wie oben gezeigt überschrieben werden. Der Rückgabewert gibt die Anzahl der vom Knoten unterstützten Datenreihen an.

Benutzerdefinierte CLI-Befehle

Entfernte Knoten können auch Aktuatoren verschiedener Art steuern, wie z.B. das Ein- oder Ausschalten einer LED oder das Bewegen eines Servos, indem benutzerdefinierte CLI-Befehlslogik hinzugefügt wird. Dies geschieht in der Methode handleCustomCommand():

CLI
bool handleCustomCommand(uint32_t sender_timestamp, char* command, char* reply) override {
    if (strcmp(command, "magic") == 0) {    // Beispiel für benutzerdefinierte Befehlsbehandlung
      strcpy(reply, "**Magic now done**");
      return true;   // behandelt
    }
    return false;  // nicht behandelt
  }

Diese Methode ist relativ einfach an die spezifischen Anforderungen des Sensor-Knotens anzupassen. Ein Rückgabewert von true zeigt an, dass der Befehl abgefangen und verarbeitet wurde. Die CLI-Antwort wird in den reply-Puffer geschrieben und an den sendenden Knoten zurückgesendet.

Original auf blog.meshcore.io →