PICKPLACE Hub

MQTT-Telemetrie mit Paho und LTE-SoMs

Geschrieben von Hendrik Schnack | Oct 4, 2023 6:39:00 PM

Damit Geräte miteinander und mit dem Internet kommunizieren können, werden verschiedene Kommunikationsprotokolle und -technologien verwendet, darunter Wi-Fi, Bluetooth, Zigbee und LoRaWAN. Diese Protokolle ermöglichen es den Geräten, Daten über das Internet an cloudbasierte Dienste oder andere Geräte zu übertragen, wo sie in Echtzeit analysiert und verarbeitet werden können.

In diesem Artikel werden wir die Welt des IoT für Mikrocontroller praxisnah und anhand eines einfachen Beispiels erkunden. Auf Basis schlüsselfertiger SoMs können über AT-Befehl-Systematiken protokollkonform Lösungen umgesetzt werden,  bei denen Controller Topics abonnieren oder publishen. Auf Basis eines Protokolls wie MQTT können so leichtgewichtige IoT-Lösungen geschaffen werden, die einem Kunden potenzielle neue Geschäftsfelder eröffnen. 

MQTT

Message Queuing Telemetry Transport (MQTT) ist ein Open-Source-Nachrichtenprotokoll, das für ressourcenminimale Geräte und Netzwerke mit geringer Bandbreite, hoher Latenz oder Unzuverlässigkeit entwickelt wurde. Es wird häufig im Internet der Dinge (IoT) und anderen verteilten Systemen eingesetzt, um eine zuverlässige Echtzeitkommunikation zwischen Geräten und Anwendungen zu ermöglichen. Secure MQTT (MQTT-S) als eine Variante von MQTT bietet zusätzliche Sicherheitsfunktionen um Vertraulichkeit, Integrität und Authentizität der Datenübertragung zu gewährleisten. MQTT-S unterstützt unterschiedliche Sicherheitsmechanismen wie Transport Layer Security (TLS), Secure Sockets Layer (SSL) und Secure Real-Time Transport Protocol (SRTP). Das meist verwendete Feature ist dabei der Schutz vor Eavesdropping durch eine sichere Verbindung, ähnlich wie bei HTTPS, mit TLS. TLS ist ein weit verbreitetes Sicherheitsprotokoll, das eine Ende-zu-Ende-Verschlüsselung, Authentifizierung und Integritätsprüfung bietet. Es bietet damit einen sicheren Kanal für die Kommunikation zwischen Geräten und Anwendungen, der vor Abhören und anderen Arten von Angriffen schützt.

 

AT-/Hayes-Befehle

Der Hayes-Befehlssatzbesteht aus einer Reihe von kurzen Textstrings, die zu Befehlen für Vorgänge wie Wählen, Auflegen und Ändern der Verbindungsparameter kombiniert werden können. Die überwiegende Mehrheit von Modems und IoT-SoMs verwendet den Hayes-Befehlssatz in zahlreichen Variationen.

AT-Befehle haben in der Regel eine standardisierte Syntaxregel - die meisten Befehle haben drei verschiedene Arten: Schreiben, Lesen und Testen. Und dann gibt es noch andere, die nur zum Abrufen von Informationen ausgeführt werden können. Alle Standard-AT-Befehle beginnen mit AT+<Befehl>. Die Antworten auf die Befehle enthalten kein AT und verwenden nur +<Befehl>:

Schreiben: AT+<x>=<...>
Setzt die benutzerdefinierten Parameterwerte des Modems
Die erwartete Antwort ist normalerweise nur "OK".

Lesen: AT+<x>?
Gibt die Einstellungen des Modems zurück

Test: AT+<x>=?
Prüft die unterstützten Konfigurationen / Dienste des Modems

Ausführen: AT+<x>


Einige Beispiele wie folgt:

  • AT Attention-Befehl, der das Modem anweist, auf Befehle zu warten.
  • ATZ Reset-Befehl, der das Modem auf seine Standardeinstellungen zurücksetzt.
  • ATS0=1 Mit diesem Befehl wird das Modem nach einem Klingeln in den automatischen Antwortmodus versetzt.
  • ATDT5551234 Dieser Befehl wählt die Rufnummer 5551234.
  • AT+CPIN="1234" PIN-Code für die SIM-Karte festlegen
  • AT+COPS? Den Netzbetreiber nach Verbindungsstatus abfragen
uBlox LARA LTE SoM

Auch moderne SoMs nutzen den AT-Befehlsssatz. Herstellerseitig wird das immer über serielle Schnittstellen gelöst.

Eine Verbindung zwischen einem Mikrocontroller und einem LTE-SoM ist daher erst einmal eine denkbar einfache Verbindung über eine serielle Schnittstelle, bei der ein Stringbuffer an eine UART-Send-Funktion übermittelt wird. In diesem Bespiel zeigen wir den Code für das uBlox LARA R211. Das uBlox LARA R211 ist ein Mobilfunkmodul, das für den Einsatz in Internet-of-Things- (IoT) und Machine-to-Machine- (M2M) Anwendungen entwickelt wurde. Es ist in der Lage, über Mobilfunknetze mit verschiedenen Mobilfunktechnologien zu kommunizieren, darunter LTE Cat 1, 3G und 2G. Eiunmal mit einem Mobilfunknetz verbunden, kann es zum Senden und Empfangen von Daten über das Netz unter Verwendung von Standard-Protokollen wie TCP/IP, UDP und HTTP verwendet werden. Es unterstützt auch eine Reihe von Sicherheitsfunktionen, darunter TLS/SSL-Verschlüsselung, IPsec VPN und Secure Boot.

LTE-Modul von uBlox (Quelle: Mouser)

eclipse Paho

Eclipse Paho ist ein Open-Source-Softwareprojekt, das eine Reihe von Client-Bibliotheken für die Implementierung des MQTT-Protokolls (Message Queuing Telemetry Transport) bereitstellt. MQTT ist ein leichtgewichtiges Nachrichtenprotokoll, das häufig in Internet-of-Things- (IoT) und Machine-to-Machine- (M2M) Anwendungen verwendet wird.

Paho wurde erstmals 2012 als Eclipse IoT-Projekt veröffentlicht und hat sich seitdem zu einer beliebten Wahl für die Implementierung von MQTT-Clients in einer Vielzahl von Programmiersprachen, darunter Java, Python, C und C++, entwickelt. Es ist benutzerfreundlich und flexibel und bietet eine Vielzahl von Konfigurationsoptionen und Funktionen.

Die Kernpunkte zu Paho sind die folgenden:

  • MQTT Versionen: Paho C Client unterstützt MQTT Version 3.1 und 3.1.1, welche die meistgenutzten Versionen des Protokolls sind.
  • APIs: Paho bietet sowohl synchrone als auch asynchrone API-Funktionen an. Die asynchrone API ermöglicht es, dass die Kommunikation im Hintergrund erfolgt, während die Anwendung andere Aufgaben ausführt.
  • Client-Objekt: Das Herzstück des Clients ist das MQTTClient-Objekt, das alle Informationen und den Zustand enthält, den der Client benötigt, um eine Verbindung herzustellen und Nachrichten zu senden und zu empfangen.
  • Verbindungsoptionen: Vor dem Verbinden müssen die Verbindungsoptionen festgelegt werden, welche die Details zum MQTT-Server (wie Hostname und Port), sowie die Client-ID und optionale Anmeldeinformationen (Benutzername und Passwort) enthalten.
  • Callbacks: Um asynchron zu arbeiten, können Callback-Funktionen für Ereignisse wie das Eintreffen einer Nachricht, den Verlust der Verbindung oder das Senden einer Nachricht eingerichtet werden. Diese Funktionen werden von der Client-Bibliothek aufgerufen, wenn entsprechende Ereignisse auftreten.
  • Themen abonnieren: Der Client kann eines oder mehrere Themen abonnieren, um Nachrichten, die unter diesen Themen veröffentlicht werden, zu empfangen.
  • Nachrichten veröffentlichen: Der Client kann Nachrichten an jedes Thema senden, das der MQTT-Server kennt. Das beinhaltet die Festlegung der Qualität der Dienstgüte (Quality of Service - QoS), die bestimmt, wie die Nachrichten geliefert werden.
  • Netzwerkschleife: In einem typischen asynchronen Client wird eine Netzwerkschleife ausgeführt, die eingehende Nachrichten und andere Ereignisse verarbeitet. In einer synchronen Anwendung steuert der Benutzer diesen Prozess manuell.
  • Verbindung trennen und aufräumen: Nachdem der Client seine Aufgaben erfüllt hat, sollte er die Verbindung zum Server ordnungsgemäß trennen und alle reservierten Ressourcen freigeben.

Es gibt gute Beispiele zu Paho, die aber eher auf einen OS-Kontext abzielen. Folgende Beispiele dienen dem Einstieg:

 

Nutzung von paho im Bare-Metal-IoT-Kontext

Die Strategie beim Nutzen von paho ist die des Enqueuens von MQTT-Telemetrie-Messages über eine serielle Schnittstelle. Grundsätzlich gilt es dann, das MQTT-Protokoll über einen TCP-Socket zu serializen. Dies stellt grundsätzlich keine bedeutende Aufgabe dar, wenn der LTE-SoM bereits einen Socket-Connect unterstützt.

Ein Blick in das uBlox-Manual verrät die Befehlsstruktur:

Command: AT+USOWR=<socket>,<length> \n

Response: @ <data> \n +USOWR:<socket>,<length> \n OK

 

Diese gilt es nun in C-Code umzusetzen. Im Folgenden Beispiel ist das eine Funktion LARA_u8SendData, die einen char-Buffer per Socket an einen Socket schreibt. Die Funktion LARA_u8CatNumToString ist darin eine Hilfs-Funktion, in dem praktisch per itoa-Konversion die Länge des Strings an den Befehl angehangen wird. Die 0 im USOWR-Command ist eine fortlaufende Zahl für verschiedene Sockets, die im SoM eingerichtet werden können.

 

Der Befehl lara_u8Write verweist auf eine Funktion, in der ein Callback auf die Treiber-HAL implementiert ist (hier STM32). Es sollte besonderen Wert darauf gelegt werden, dass diese Funktion serielle UART-Daten entweder per DMA oder per Interrupt überträgt.

 


uint8_t LARA_u8SendData(uint8_t * pu8Buf, uint16_t u16Len)
{
  uint32_t u32Error = ERROR;
  uint8_t u8AnsResult = ANS_ERROR;
  char cCmdStr[LARA_AT_SIZE] = {0};

  LARA_u8CatNumToString(cCmdStr, "AT+USOWR=0,", u16Len);

  //sprintf(cCmdStr, "AT+USOWR=0,%d\r\n", u16Len);
  u32Error = LARA_u8SendAtCmd( cCmdStr );

  if(u32Error == SUCCESS)
  {
    u8AnsResult = lara_u8WaitResponse(10000, "@", ANSWER_NULL);

    if(u8AnsResult == ANS_OK)
    {
      appTimer_vDelayMS(50);
      // Send Data over UART
      u32Error = lara_u8Write( pu8Buf, u16Len );

      if(u32Error == SUCCESS)
      {
        // Wait of the "+USOWR:" answer
        // Response Time: <120s
        u8AnsResult = lara_u8WaitResponse(1200000, "+USOWR:", ANSWER_NULL);
      }
      else
      {
        return ANS_ERROR;
      }
    }
  }

  return u8AnsResult;
}

static uint8_t lara_u8Write(uint8_t * pu8Data, uint16_t u16Size)
{
  uint8_t u8Result = ERROR;

  u8Result = (uint8_t)HAL_UART_Transmit_IT(LARA_UART_IF, pu8Data, u16Size);

  if(u8Result != SUCCESS)
  {
    return ERROR;
  }
  else
  {
    return SUCCESS;
  }
}

Natürlich muss bis zum erfolgreichen Senden so einiges getan werden.

  • Einrichten des Sockets über USOCL und  USOCR
  • Connecten zum Socket über USOCO
  • Einrichten eines SSL-Profils über USOSEC

Je nach Sicherheitsprofil wird das SSL-Cert über einen HTTP-Request heruntergeladen und im Filesystem des SoMs gespeichert.

Die Call-Struktur der einzelnen SoM-Funktionen jedoch ist dabei immer ähnlich und dank eines gut gepflegten Manuals "straigt-forward".

 

Applikations-Library für MQTT

Grundsätzlich stellt Paho selbst nur eine Reihe von Funktionen, die eine Verbindung ermöglichen, aber kein plattformunabhängiges Skript für Connects, Publishes und Subscriptions. Der Zusammenhang muss daher über eine Library erfolgen. Der einfachste Weg ist zunächst die Einführung eines globalen Objekts (oder Structs) für die Verwaltung der MQTT-Zustandsmaschine.


typedef struct
{
    unsigned int next_packetid;
    unsigned int command_timeout_ms;
    unsigned int buf_size;
    unsigned int readbuf_size;
    unsigned int keepAliveInterval;
    int isconnected;
    int cleansession;
    uint8_t *writebuf;
    uint8_t *readbuf;
    char ping_outstanding;

    appMqtt_msg_handlers_t tMsgHandlers[APPMQTT_MAX_MSG_HANDLERS]; /* Message handlers are indexed by subscription topic */

    void (*defaultMessageHandler) (appMqtt_msg_data_t *);

    appMQTT_net_t *   ipstack;
    appTimer_t   last_sent;
    appTimer_t   last_received;

} appMQTT_client_t;

Die Strategie ist nun über das Schreiben und Lesen zweier Buffer (writebuf und readbuf) mit den zentralen Serializer-Funktionen der Paho-Library zu kommunizieren, und den über ByRef rückgewonnenen Buffer an die Socket-Schnittstelle vom SoM zu übergeben. 

Die Serializer-Funktionen von Paho MQTT in ANSI C sind Bestandteil der Bibliothek, die für das Packen und Entpacken von MQTT-Nachrichten zuständig sind. Diese Funktionen sind wichtig, weil MQTT als Netzwerkprotokoll bestimmte Formate für den Austausch von Nachrichten zwischen Client und Server vorgibt. Die Nachrichten müssen in einem binären Format serialisiert (für das Senden) und deserialisiert (nach dem Empfang) werden, das dem MQTT-Protokollstandard entspricht.

Folgende Aufgaben sind in den Funktionen Zusammengefasst

Serialisierung: Dieser Prozess konvertiert eine Nachrichtenstruktur (wie eine MQTTMessage-Struktur, die Topic, Payload und QoS enthält) in einen Byte-Stream, der über ein Netzwerk gesendet werden kann. Der Prozess muss effizient sein, um die Netzwerkbandbreite zu optimieren und die Overhead-Kosten gering zu halten, was besonders auf eingebetteten oder bandbreitenbeschränkten Systemen wichtig ist.

Deserialisierung: Nachdem eine Nachricht vom Netzwerk empfangen wurde, wird der Byte-Stream zurück in eine Nachrichtenstruktur konvertiert, die dann von der Anwendung verarbeitet werden kann. Die Deserialisierung muss korrekt die im MQTT-Protokoll definierten Felder extrahieren, wie Topic, Nachrichteninhalt und QoS-Level.


Fehlerbehandlung: Wenn beim Serialisierungs- oder Deserialisierungsprozess ein Fehler auftritt (z.B. wenn die Nachricht nicht dem Protokoll entspricht oder beschädigte Daten empfangen wurden), muss dieser erkannt und angemessen behandelt werden. Die Paho-Bibliothek stellt sicher, dass Fehlerbedingungen korrekt gemeldet und behandelt werden können.


Unterstützung für verschiedene Nachrichtentypen: MQTT definiert verschiedene Arten von Nachrichten, wie CONNECT, PUBLISH, SUBSCRIBE, UNSUBSCRIBE und andere. Jede Nachrichtenart hat ihr eigenes Format, und die Serializer-Funktionen von Paho können all diese Formate verarbeiten.

 

Folgende Grafik beschreibt die Interaktion einer fiktiven MQTT-Lib mit der Paho-Library und dem LTE-SoM. Es gilt das Client-Objekt kontinuierlich aktuell zu halten und die Sende- und Rückgabewerte sowie die Protokoll-Statusflags in der Library zentral zu managen.

 

MQTT-Library für das Managen von Status und Nachrichten

 

Zusammenfassung und Ausblick

Der Einsatz der Paho MQTT C-Bibliothek auf einem Mikrocontroller, der mit einem LTE-SoM (System on Module) ausgestattet ist, verdeutlicht die Flexibilität und Effizienz eines robusten Clients in einem modernen IoT-Ökosystem. Wie in unserem Artikel hervorgehoben, bietet Paho eine skalierbare und ressourcenschonende Lösung, um MQTT-Kommunikation zu implementieren. Die schlanke Natur der Paho-Bibliothek ist passgenau zum begrenzten Speicher und der Rechenleistung eines Mikrocontrollers und demonstriert Tauglichkeit für eingebettete Anwendungen. 

Zukünftige Aufgaben ausgehend von diesem Artikel ist die Einbindung in ein robustes Thread- und Realtime-System. Die Notwendigkeit ist dringend gegeben, weil Speicherbereiche exklusive Zugriffe benötigen. Zudem kann in einem IoT-Kontext die Datenmenge schnell gewisse Niveaus überschreiten, die Ressourcen über einen verhältnismäßig langen Zeitraum beschäftigen. Final haben SoMs klar definierte Timeout-Zyklen, die idealerweise mit einem RTOS zeitlich eingeplant werden können.