Mit MQTT Raspberry Pi und Arduino ins IOT bringen

In diesem Beitrag möchten wir über das Protokoll MQTT Raspberry Pi, der als MQTT-Broker eingerichtet ist, und einen Arduino Daten über der lokale Netzwerk austauschen lassen.

Diese Art der Kommunikation kann alternativ zu unserer einfachen Kommunikation per UDP in unserer Datenlogger Serie – Teil 4 eingesetzt werden. Der Vorteil ist, dass die Server-Seite nicht programmiert, sondern nur konfiguriert werden muss.

Hintergrund

Bei dem MQTT-Protokoll handelt es sich um ein offenes Netzwerkprotokoll, welches dafür entwickelt wurde Machine-to-Machine-Kommunikation (M2M) zu ermöglichen. Es hat einen Fokus auf die Einbindung von verteilten Geräten mit begrenzten Ressourcen.

Dadurch eignet sich MQTT sehr gut für Automatisierungslösungen und findet im Bereich IoT durch die einfache Verwendung große Verbreitung.“ – wikipedia.de/mqtt.

Kurz gesagt kann man sich das Protokoll so vorstellen, dass einige Teilnehmer als Clients sich zunächst beim Verwalter, dem Broker, anmelden. Die Clients teilen dem Broker, dann mit ob und welche Themen (Topics) sie abonnieren (subscribe) möchten. Ein Beispiel für ein Topic wäre z.B. „Raumtemperatur„. Wird ein solches Topic nun aktualisiert, weil ein Client einen neuen Wert für die Raumtemperatur veröffentlicht (published), sendet der Broker diese Aktualisierung an alle Clients die dieses Topic abonniert haben. Um in einem wachsenden Ökosystem von Topics noch den Überblick zu behalten (welche Raumtemperatur ist überhaupt gemeint?) können die Topics hierarchisch angeordnet werden. So würde man z.B. anstatt „Raumtemperatur“ besser „Wohnzimmer/Raumtemperatur“ wählen, um für spätere Erweiterungen von mehr Räumen und oder mehr Sensoren gewappnet zu sein.

Zum Thema IOT und MQTT gibt es bereits etliche Informationen im Internet, z.B. hier: https://www.industry-of-things.de/was-ist-mqtt-und-was-macht-man-damit-a-ff28e6a2cf45735049e13d81bdc73ace

Wir wollen nun endlich in die Praxis starten 🙂

MQTT Raspberry Pi – Installation von Mosquitto

Auf unserem Raspberry Pi 3 B+ mit Raspbian Version 11 (bullseye) installieren wir das leichtgewichtige open-source Software Paket mosquitto von der eclipse Foundation https://mosquitto.org/:

sudo apt install mosquitto mosquitto-clients

Die mosquitto-clients sind praktisch für einen lokalen Test. Nach der Installation müssen wir noch den Zugriff vom lokalen Netzwerk erlauben. Dies kann über die Konfigurationsdatei /etc/mosquitto/conf.d/local.conf gesteuert werden. Die Datei existierte bei mir zunächst nicht, sie kann aber einfach erstellt werden mit dem Inhalt:

listener 1883
allow_anonymous true

Dann können wir mosquitto starten:

sudo systemctl start mosquitto

Analog dazu können wir mosquitto stoppen und den Status anzeigen mit:

sudo systemctl stop mosquitto
sudo systemctl status mosquitto

Für einen Test reicht das. Soll mosquitto hingegen dauerhaft eingesetzt werden, bietet es sich an es automatisch zusammen mit dem Raspi zu starten. Das wird freigegeben mit:

sudo systemctl enable mosquitto

Ist mosquitto gestartet (bei Bedarf mit sudo systemctl status mosquitto überprüfen) können wir mit den Kommandozeilen-Clients testen 🙂

Test von Mosquitto

Mit mosquitto_sub können wir ein Topic abonnieren. Das geschieht mit der Option -t. Abonnieren wir z.B. das Topic test:

mosquitto_sub -t test

Nach Eingabe des Befehls passiert nichts, was nicht weiter überrascht, denn es wird darauf gewartet das etwas auf dieses Topic gepublished wird. Das erledigen wir mit:

mosquitto_pub -t test -m "Hallo Welt"

Die Option -t wieder für das Topic und die Option -m für den eigentlichen Inhalt. Sobald wir den Befehl abschicken tut sich auch etwas in der Subscriber Konsole:

mqtt raspberry pi

Der Broker ist also einsatzbereit und kann nun Daten unseres IOT-Devices annehmen.

MQTT Arduino – Sketch für D1 Mini

Da der D1 Mini bereits eine WLAN Schnittstelle integriert hat eignet er sich perfekt als IOT-Device mit MQTT.

Als Beispiel wird hier ein Basis-Topic und für jede physikalische Größe ein Sub-Topic erzeugt, so dass sich folgende Baumstruktur ergibt:

  • gartenhaus
    • sensorData
      • temp
      • humidity
      • pressure
    • cmd

Um die Subscribe Funktion zu demonstrieren wurde es in diesem Fall so implementiert, dass der D1 Mini die Sensordaten Temperatur, Luftfeuchtigkeit und Luftdruck nur auf Anforderung sendet. Dazu subscribed er unter dem Basis-Topic das topic „cmd„. Wird auf dem cmd-Topic eine 1 empfangen startet im nächsten loop-Durchlauf das publishen der Sensorwerte.

C++
/*******************************************************************************
 * @file main.cpp
 * @brief MQTT publish / subcribe datalogger.
 *
 * Tasks:
 * - login to wifi
 * - connect to mqtt server
 * - subscribe to command topic
 * - wait till command to publish data is received
 * - publish (sensor) data
 *
 ******************************************************************************/
#include <Arduino.h>
#include <Adafruit_BME280.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h> // https://github.com/knolleary/pubsubclient/tree/master
#include "mypassword.h" // for wifi password not to be published on github :)

// Beispiel von:
// https://github.com/knolleary/pubsubclient/blob/master/examples/mqtt_esp8266/mqtt_esp8266.ino

Adafruit_BME280 bme1;
float temp1(-99.9f), hum1(-88.8), press1(-77.7);

bool bme1SensorError = false; // bme sensor nicht vorhanden oder nicht ok?

const char* ssid = "GSXR750";
// password in mypassword.h
const char* MQTT_SERVER = "192.168.2.64";

String MQTT_TOPIC_BASE      = "gartenhaus"; // Beispiel fuer Station
String MQTT_TOPIC_SUBSCRIBE = MQTT_TOPIC_BASE+"/cmd"; // e.g. gartenhaus/cmd
String MQTT_TOPIC_PUBLISH   = MQTT_TOPIC_BASE+"/sensorData"; // e.g. gartenhaus/sensorData

WiFiClient espClient;
PubSubClient client(espClient);

unsigned long lastMsg = 0;
#define MSG_BUFFER_SIZE	(50)
char msg[MSG_BUFFER_SIZE];
int value = 0;
bool cmdSend = false;

void setupBME280()
{
  int i;
  for (i = 0; i < 5; i++) { // max 5x versuchen mit bme zu kommunizieren / initialisieren
    if (!bme1.begin()) { // KEIN erfolg?
      Serial.println("BME280 sensor nicht gefunden. Versuche es in 1s noch einmal.");
      delay(1000); // dann 1s warten
    } else {
      break; // nicht weiter probieren
    }
  }
  if (i >= 5) { // 5x ohne erfolg versucht zu kommunizieren?
    bme1SensorError = true;
    Serial.println("Konnte BME280 Sensor nicht finden!");
  } else {
    Serial.println("BME280 Sensor gefunden! ERFOLG!!");
  }
}

void setup_wifi() {
  delay(10);
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  randomSeed(micros());
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (unsigned int i = 0; i < length; i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();
  if ((char)payload[0] == '1') { // we subscribed to the right topic so '1' => start
    digitalWrite(LED_BUILTIN, LOW);
    cmdSend = true;
  } else {
    digitalWrite(LED_BUILTIN, HIGH);
  }
}

void reconnect() {
  while (!client.connected()) { // Loop until we're reconnected
    Serial.print("Attempting MQTT connection...");
    String clientId = "ESP8266Client-";
    clientId += String(random(0xffff), HEX); // Create a random client ID
    if (client.connect(clientId.c_str())) {
      Serial.println("connected");
      client.subscribe(MQTT_TOPIC_SUBSCRIBE.c_str()); // ... and resubscribe
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      delay(5000);
    }
  }
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(74880);
  setup_wifi();
  client.setServer(MQTT_SERVER, 1883);
  client.setCallback(callback);
}

void datenSchicken() {
  String topic = MQTT_TOPIC_PUBLISH + "/temp";
  client.publish(topic.c_str(), String(temp1).c_str());
  topic = MQTT_TOPIC_PUBLISH + "/humidity";
  client.publish(topic.c_str(), String(hum1).c_str());
  topic = MQTT_TOPIC_PUBLISH + "/pressure";
  client.publish(topic.c_str(), String(press1).c_str());
}

void loop() {
  if (!client.connected()) {
    reconnect();
  }
  client.loop();

  if (cmdSend) {
    cmdSend = false;
    datenSchicken();
  }
}
Vollständig anzeigen

Das Testergebnis mit den seriellen Ausgaben (ohne angeschlossenen Sensor, deshalb nur ungültige Testwerte):

MQTT python

Nun soll noch auf die Sensordaten reagiert werden. Das heißt wir brauchen noch einen Client, der die Sensortopics subscribed. Diesen realiseren wir diesmal in python. Alle topics „unterhalb“ von sensorData können mit der Wildcard '#' abonniert werden:

Python
# sudo apt install python3-pip
# pip install paho-mqtt

import paho.mqtt.client as mqtt
import paho.mqtt.subscribe as subscribe

HOST = '192.168.2.64'

def on_message_print(client, userdata, message):
    print("%s %s" % (message.topic, message.payload))

#subscribe.callback(on_message_print, "test", hostname=HOST)
subscribe.callback(on_message_print, "gartenhaus/sensorData/#", hostname=HOST)

# The callback for when the client receives a CONNACK response from the server.
def on_connect(client, userdata, flags, rc):
    print("Connected with result code "+str(rc))

    # Subscribing in on_connect() means that if we lose the connection and
    # reconnect then subscriptions will be renewed.
    client.subscribe("$SYS/#")

# The callback for when a PUBLISH message is received from the server.
def on_message(client, userdata, msg):
    print(msg.topic+" "+str(msg.payload))

client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message

client.connect(HOST, 1883, 60)

# Blocking call that processes network traffic, dispatches callbacks and
# handles reconnecting.
# Other loop*() functions are available that give a threaded interface and a
# manual interface.
client.loop_forever()

Das Testergebnis:

Weitere Schritte wären nun das Eintragen der Sensordaten in eine Datenbank und / oder die grafische Aufbereitung.

Der vollständige Sketch inklusive dem Python-Skript ist auch auf github zu finden.


Beitrag veröffentlicht

in

von

Schlagwörter:

Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert