Arbeiten mit dem Cayenne Dashboard - LoRa zu MQTT Gateway (Teil 3) - AZ-Delivery

Nach einigen missglückten Versuchen ein universelles LoRa-Gateway mit dem ESP32 zu bauen, entstand das folgende propritäre Gateway, das es ermöglicht LoRa basierende IoT Geräte über MQTT mit dem Cayenne Dashboard zu verbinden. In einer erweiterten Ausbaustufe wird das Gateway auch IoT Geräte basierend auf dem ESP-Now Protokoll zu verwalten.

Für das Gateway brauchen wir nur einen ESP32 mit LoRa und OLED Display es werden keine weiteren Bauteile benötigt. Die Stromversorgung kann mit jedem USB Netzteil erfolgen.

Funktionsbeschreibung:

Das Gateway kann 32 Geräte mit je bis zu 8 Kanälen verwalten. Die Datenübermittlung erfolgt asynchron. Wenn das Gateway ein LoRa Datenpaket empfängt werden die ersten sechs Bytes als Geräte-ID (ist typisch die MAC-Adresse) interpretiert. Das Gateway schaut dann in einer Geräteliste nach ob dieses Gerät bereits registriert ist. Ist das nicht der Fall wird die Geräte-Id gespeichert und über das Web-Interface zur Registrierung angezeigt. 

Ist das Gerät bereits registriert werden die Daten ausgelesen und pro Kanal in einen Nachrichtenbuffer gespeichert. Die Kanalnummer wird dabei aus der Gerätenummer (Index in der Geräteliste)  * 8 + Kanalnummer der empfangenen Daten ermittelt. Gerät 0 hat somit die Kanäle 0 bis 7, Gerät 1 die Kanäle 8 bis 15 u.s.w. 

Ein Datensatz beginnt mit einem Byte für die Kanalnummer gefolgt von einem Byte mit dem Typ und danach 1 bis 6 Datenbytes je nach Typ. Nachdem alle Daten in den Nachrichtenbuffer gespeichert und als Neu gekennzeichnet wurden, wird das Antwortpaket an das Lora Gerät zusammengestellt. Es beginnt wieder mit den sechs Bytes Geräte-Id. Anschließend werden im Nachrichtenbuffer die Kanäle für dieses Gerät überprüft, ob ein Ausgangs Datenpaket von Cayenne an das Gerät vorliegt, das mit Neu gekennzeichnet ist. Wird so ein Datenpaket gefunden, wird es an das Antwortpaket angefügt wobei wieder die relative Kanalnummer 0 bis 7 eingesetzt wird. Als letzter Schritt wird das Antwortpaket an das LoRa Gerät übertragen.

Die Funktion Cayenne.loop(1) übernimmt die Kommunikation mit dem IoT Server. In der Callback Funktion CAYENNE_OUT_DEFAULT() werden im Nachrichtenbuffer alle Kanäle gesucht die mit Neu gekennzeichnet sind und vom Typ ein Eingangs-Datenpaket für Cayenne enthalten. Diese Pakete werden nun je nach typ umgewandelt und an den IoT Server gesendet. Nach erfolgreicher Übertragung wird das Flag Neu wieder zurückgesetzt.

Eine zweite Callback Funktion CAYENNE_IN_DEFAULT() wird immer dann aufgerufen, wenn von Cayenne Daten für einen Action Kanal vorliegen. Die Daten vom IoT Server werden je nach Typ umgewandelt und in den Nachrichtenbuffer gespeichert. Sie werden mit Neu gekennzeichnet, sodass sie bei der nächsten LoRa Kommunikation mit dem Antwortpaket an das Gerät gesendet werden.

Anzeige:

Das Display zeigt neben dem Namen das aktuelle Datum und die aktuelle Uhrzeit. Darunter die IP-Adresse.
Die Zeile MQTT: zeigt die Anzahl der erfolgreichen Übertragungen an den IoT Server, LoRa die erfolgreichen Übertragungen zu LoRa Geräten und NOW die Anzahl der erfolgreichen Übertragungen an ESP-Now Geräte. Letztere ist derzeit immer 0, da diese Funktion noch nicht implementiert ist.

Geräte-Liste und Registrierung:

Die Geräteliste wird als CSV Datei im Flash-Filesystem (SPIFFS) gespeichert sodass sie auch bei fehlender Stromversorgung erhalten bleibt. Über dem im Gateway integrierten Web-Server kann diese Liste gewartet werden. Es können Geräte gelöscht und neue Geräte registriert werden.

Datenpakete:

Für den Typ werden die Codes entsprechend der  IPSO Alliance Smart Objects Guidelines wobei aber 3200 abgezogen wird.

 Typ IPSO TypNr. Bytes Auflösung
Digital Eingang 3200 0 1 1
Digital Ausgang 3201 1 1 1
Analog Eingang 3202 2 2 0.01 mit Vorzeichen
Analog Ausgang 3203 3 2 0.01 mit Vorzeichen
Beleuchtungs Sensor 3301 101 2 1 Lux
Anwesenheits Sensor 3302 102 1 1
Temperatur Sensor 3303 103 2 0.1°C mit Vorzeichen
Feuchte Sensor 3304 104 1 0.5%
Beschleunigungs Sensor 3313 113 6 0.001G mit Vorzeichen
je Achse X, Y und Z
Druck Sensor 3315 115 2 0.1 hPa
Gyrometer 3334 134 6 0.01%/s mit Vorzeichen
je Achse X, Y und Z
GPS Position 3336 136 9 Breitengrad 0.0001° 
mit Vorzeichen
Längengrad 0.0001°
mit Vorzeichen
Höhe 0.01m 
mit Vorzeichen

 

Sketch:

 Die Registrierung des Gateways bei Cayenne erfolgt auf die selbe Weise wie im Teil 1 dieser Blog Serie beschrieben. Die Zugriffsdaten müssen dann in den Sketch eingegeben werden (gelbe Markierung) ebenso die Zugriffsdaten zum Anmelden an das lokale WLAN. Das Cayenne Dashboard wird keine Widgets anzeigen, da noch kein Gerät an das Gateway angeschlossen wurde.

Board für Arduino IDE = TTGO LoRa32-OLED V1

/* Das MQTT Gateway bildet ein Interface zwischen LoRa Geräten bzw. ESP Nowe Geräten 
 *  und Cayenne MQTT Dashboards. Es läuft auf ESP32 mit LoRa und OLED Display
 *  Die Konfiguration erfolgt vom Browser
 */
#include <SPI.h>
#include <LoRa.h>
#include "SSD1306.h"
#include<Arduino.h>
#include <CayenneMQTTESP32.h>
#include <CayenneLPP.h>
#include <WiFi.h>
#include <WebServer.h>
#include <time.h>
#include "FS.h"
#include "SPIFFS.h"


//Die Daten für diese Einstellung erhalten wir vom Cayenne Dashboard
#define MQTT_USER ""
#define MQTT_PASSWORD ""
#define MQTT_CLIENTID ""

// Zugangsdaten für das lokale WLAN
#define WIFI_SSID ""
#define WIFI_PASSWORD ""

//NTP Server zur Zeitsynchronisation
#define NTP_SERVER "de.pool.ntp.org"
#define GMT_OFFSET_SEC 3600
#define DAYLIGHT_OFFSET_SEC 0

//Pins für den LoRa Chip
#define SS      18
#define RST     14
#define DI0     26
//Frequenz für den LoRa Chip
#define BAND    433175000

//
#define MAXCHANNELS 256 //maximale Zahl der verwalteten Kanäle
#define MAXDEVICE 32 //maximale Anzahl der verwalteten Geräte MAXCHANNELS/MAXDEVICE = 8 ergibt die maximale Anzahl von Kanälen pro Gerät

//Format Flash Filesystem wenn noch nicht geschehen
#define FORMAT_SPIFFS_IF_FAILED true

#define DEBUG 1

//Bausteine für den Web-Server
const char HTML_HEADER[] =
"<!DOCTYPE HTML>"
"<html>"
"<head>"
"<meta name = \"viewport\" content = \"width = device-width, initial-scale = 1.0, maximum-scale = 1.0, user-scalable=0>\">"
"<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">"
"<title>MQTT Gateway</title>"
"<script language=\"javascript\">"
"function reload() {"
"document.location=\"http://%s\";}"
"</script>"
"<style>"
"body { background-color: #d2f3eb; font-family: Arial, Helvetica, Sans-Serif; Color: #000000;font-size:12pt; }"
"th { background-color: #b6c0db; color: #050ed2;font-weight:lighter;font-size:10pt;}"
"table, th, td {border: 1px solid black;}"
".titel {font-size:18pt;font-weight:bold;text-align:center;}"
"</style>"
"</head>"
"<body><div style='margin-left:30px;'>";
const char HTML_END[] =
"</div><script language=\"javascript\">setTimeout(reload, 10000);</script></body>"
"</html>";
const char HTML_TAB_GERAETE[] =
"<table style=\"width:100%\"><tr><th style=\"width:20%\">ID</th><th style=\"width:10%\">Nr.</th>"
"<th style=\"width:20%\">Kanäle</th><th style=\"width:20%\">Name</th>"
"<th style=\"width:20%\">Letzte Daten</th><th style=\"width:10%\">Aktion</th></tr>";
const char HTML_TAB_END[] =
"</table>";
const char HTML_NEWDEVICE[] =
"<div style=\"margin-top:20px;\">%s Name: <input type=\"text\" style=\"width:200px\" name=\"devname\" maxlength=\"10\" value=\"\"> <button name=\"registrieren\" value=\"%s\">Registrieren</button></div>";
const char HTML_TAB_ZEILE[] =
"<tr><td>%s</td><td>%i</td><td>%i bis %i</td><td>%s</td><td>%s</td><td><button name=\"delete\" value=\"%i\">Löschen</button></td></tr>";

//Datenstrukturen
//Nachrichten Buffer
struct MSG_BUF {
  uint8_t typ;
  uint8_t neu;
  uint8_t daten[10];
};

//Gerätedefinition
struct DEVICE {
  uint8_t aktiv;
  uint8_t dienst; //0=LoRa, 1=ESP-Now
  uint8_t id[6];
  String name;
  String last;
};

//Globale Variable
//Webserver Instanz
WebServer server(80);

//OLED Display
SSD1306  display(0x3c, 4, 15);

//Buffer zum Zwischenspeichern der Nachrichten je Kanal
MSG_BUF messages[MAXCHANNELS];

//Liste der definierten Geräte
DEVICE devices[MAXDEVICE];

//Id eines nicht registrierten Gerätes
uint8_t unbekannt[6];
//Flag immer dann wahr wenn ein neues Gerät entdeckt wurde
boolean neuesGeraet = false;
//Typ des neuen Gerätes 0=LöRa 1 =ESPNow
uint8_t neuesGeraetTyp = 0;

//Zähler und Aktivitaets Status für das Display
uint32_t loraCnt = 0; //Anzahl der empfangenen LoRa Nachrichten
String loraLast = ""; //Datum und Zeit der letzten empfangenen LoRa Nachricht
uint32_t nowCnt = 0; //Anzahl der empfangenen ESP Now Nachrichten
String nowLast = ""; //Datum und Zeit der letzten empfangenen LoRa Nachricht
uint32_t cayCnt = 0; //Anzahl der gesendeten MQTT Nachrichten
String cayLast = ""; //Datum und Zeit der letzten gesendeten MQTT Nachricht


//Funktion liefert Datum und Uhrzeit im Format yyyy-mm-dd hh:mm:ss als String
String getLocalTime()
{
  char sttime[20] = "";
  struct tm timeinfo;
  if(!getLocalTime(&timeinfo)){
    Serial.println("Failed to obtain time");
    return sttime;
  }
  strftime(sttime, sizeof(sttime), "%Y-%m-%d %H:%M:%S", &timeinfo);
  return sttime;
}

//Funktion liefert eine 6-Byte Geräte-Id im format xx:xx:xx:xx:xx:xx als String
String getId(uint8_t id[6])
{
  String stid;
  char tmp[4];
  sprintf(tmp,"%02x",id[0]);
  stid=tmp;
  for (uint8_t j = 1; j<6; j++) {
    sprintf(tmp,":%02x",id[j]);
    stid = stid += tmp ;
  }
  return stid;
}

//bereitet den Nachrichtenbuffer vor
//setzt alle Nachrichten auf erledigt
void initMessageBuffer() {
  for (int i = 0;i<MAXCHANNELS;i++) messages[i].neu = 0;
}

//Funktion zum Speichern der Konfiguration
void schreibeKonfiguration(const char *fn) {
  File f = SPIFFS.open(fn, FILE_WRITE);
  if (!f) {
    Serial.println(F("ERROR: SPIFFS Kann Konfiguration nicht speichern"));
    return;
  }
  for (uint8_t i = 0; i<MAXDEVICE; i++) {
    f.print(devices[i].aktiv);f.print(",");
    f.print(devices[i].dienst);f.print(",");
    f.print(getId(devices[i].id));f.print(",");
    f.print(devices[i].name);f.print(",");
    f.println(devices[i].last);
  }
}

//Funktion zum Registrieren eines neuen Gerätes
void geraetRegistrieren() {
  uint8_t i = 0;
  //suche freien Eintrag
  while ((i<MAXDEVICE) && devices[i].aktiv) i++;
  //gibt es keinen neuen Eintrag tun wir nichts
  if (i < MAXDEVICE) {
    //sonst Geraet registrieren Name = eingegebener Name 
    //oder unbekannt wenn keiner eingegeben wurde
    if (server.hasArg("devname")) {
      devices[i].name = server.arg("devname");
    } else {
      devices[i].name = "unbekannt";
    }
    for (uint8_t j = 0; j<6; j++) devices[i].id[j]=unbekannt[j];
    devices[i].aktiv = 1;
    devices[i].dienst= neuesGeraetTyp;
    devices[i].last = "";
    schreibeKonfiguration("/konfiguration.csv");
    neuesGeraet = false;
  }
}

//Service Funktion des Web Servers
void handleRoot() {
  char htmlbuf[1024];
  char tmp1[20];
  char tmp2[20];
  char tmp3[20];
  int index;
  //wurde der Lösch Knopf geklickt ?
  if (server.hasArg("delete")) {
    index = server.arg("delete").toInt();
#ifdef DEGUG
    Serial.printf("Lösche device %i =  ",index);
    Serial.println(devices[index].name);
#endif
    devices[index].aktiv=0;
    schreibeKonfiguration("/konfiguration.csv");
  }
  //wurde der Registrieren Knopf geklickt ?
  if (server.hasArg("registrieren")) {
    geraetRegistrieren();
  }
  //Aktuelle HTML Seite an Browser senden
  server.setContentLength(CONTENT_LENGTH_UNKNOWN);
  //Header
  WiFi.localIP().toString().toCharArray(tmp1,20);
  sprintf(htmlbuf,HTML_HEADER,tmp1);
  server.send(200, "text/html",htmlbuf);
  //Formular Anfang
  server.sendContent("<div class=\"titel\">MQTT - Gateway</div><form>");
  //Tabelle der aktiven Geräte
  server.sendContent(HTML_TAB_GERAETE);
  for (uint8_t i = 0; i<MAXDEVICE; i++) { 
    if (devices[i].aktiv == 1) { 
      getId(devices[i].id).toCharArray(tmp1,20);
      devices[i].name.toCharArray(tmp2,20);
      devices[i].last.toCharArray(tmp3,20);
      sprintf(htmlbuf,HTML_TAB_ZEILE,tmp1,i,i*8,i*8+7,tmp2,tmp3,i);
      server.sendContent(htmlbuf);
    }
  }
  server.sendContent(HTML_TAB_END);
  //Falls ein neues Gerät gefunden wurde wird seine ID sowie ein Eingabefeld für den Namen
  // und ein Knopf zum Registrieren des neuen Gerätes angezeigt
  if (neuesGeraet) {
    getId(unbekannt).toCharArray(tmp1,20);
    sprintf(htmlbuf,HTML_NEWDEVICE,tmp1,tmp1);
    server.sendContent(htmlbuf);
  }
  server.sendContent(HTML_END);
}

//Funktion zum Suchen eines Gerätes in der Geräteliste
//Rückgabe Index des Gerätes oder -1 wenn es nicht gefunden wurde
int findDevice(uint8_t dev[6]) {
  uint8_t j;
  uint8_t i = 0;
  boolean found = false;
  do {
    j = 0;
    if (devices[i].aktiv == 0) {
      i++;
    } else {
      while ((j < 6) && (dev[j] == devices[i].id[j])) {j++;}
      found = (j == 6);
      if (!found) i++; 
    } 
  } while ((i<MAXDEVICE) && (!found));
  if (found) {return i;} else {return -1;}
}

//Funktion zum Anzeigen des Status am OLED Display
void anzeige() {
  display.clear();
  display.drawString(0,0,"MQTT Gateway");
  display.drawString(0,10,getLocalTime());
  display.drawString(0,20,WiFi.localIP().toString());
  display.drawString(0,34,"MQTT: ");
  display.drawString(60,34,String(cayCnt));
  display.drawString(0,44,"LoRa: ");
  display.drawString(60,44,String(loraCnt));
  display.drawString(0,54,"NOW:  ");
  display.drawString(60,54,String(nowCnt));
  display.display();
}


//Eine Nachricht von einem LoRa Client verarbeiten
void readLoRa() {
  int devnr;
  uint8_t devid[6];
  uint8_t channel;
  uint8_t typ;
  uint8_t len;
  uint8_t dat;
  boolean output;
  //Daten holen falls vorhanden
  int packetSize = LoRa.parsePacket();
  //haben wir Daten erhalten ?
  if (packetSize > 5) {
#ifdef DEBUG    
    Serial.println(getLocalTime());
    Serial.print(" RX ");
    Serial.print(packetSize);
    Serial.println(" Bytes");
    Serial.print("Device ID");
#endif 
    //zuerst die Geräte-Id lesen   
    for (uint8_t i=0; i<6;i++){
      devid[i]=LoRa.read();
#ifdef DEBUG
      Serial.printf("-%02x",devid[i]);
#endif
    }
#ifdef DEBUG
    Serial.println();
#endif
    //Restpaket berechnen
    packetSize -= 6;
    //nachschauen ob das Gerät registriert ist
    devnr = findDevice(devid);
    if (devnr >= 0)  {
      //wenn ja setzen wir den Zeitstempel für die letzte Meldung und
      //lesen die Daten
      devices[devnr].last = getLocalTime();
      schreibeKonfiguration("/konfiguration.csv");
      while (packetSize > 0) {
        //Kanalnummer = Gerätenummer * 16 + Gerätekanal
        channel = LoRa.read() + devnr*16;
#ifdef DEBUG
        Serial.printf("Kanal: %02x ",channel);
#endif
        //typ des Kanals
        typ = LoRa.read();
#ifdef DEBUG
        Serial.printf("Typ: %02x ",typ);
#endif
        //ermitteln der Länge des Datenpakets und ob der Kanal ein Aktuator ist
        output = false;
        switch(typ) {
          case LPP_DIGITAL_INPUT : len = LPP_DIGITAL_INPUT_SIZE - 2; break;
          case LPP_DIGITAL_OUTPUT : len = LPP_DIGITAL_OUTPUT_SIZE - 2; output = true; break;
          case LPP_ANALOG_INPUT : len = LPP_ANALOG_INPUT_SIZE - 2; break;
          case LPP_ANALOG_OUTPUT : len = LPP_ANALOG_OUTPUT_SIZE - 2; output = true; break;
          case LPP_LUMINOSITY : len = LPP_LUMINOSITY_SIZE - 2; break;
          case LPP_PRESENCE : len = LPP_PRESENCE_SIZE - 2; break;
          case LPP_TEMPERATURE : len = LPP_TEMPERATURE_SIZE - 2; break;
          case LPP_RELATIVE_HUMIDITY : len = LPP_RELATIVE_HUMIDITY_SIZE - 2; break;
          case LPP_ACCELEROMETER : len = LPP_ACCELEROMETER_SIZE - 2; break;
          case LPP_BAROMETRIC_PRESSURE : len = LPP_BAROMETRIC_PRESSURE_SIZE - 2; break;
          case LPP_GYROMETER : len = LPP_GYROMETER_SIZE - 2; break;
          case LPP_GPS : len = LPP_GPS_SIZE - 2; break;
          default: len =  0;
        }
        //ist der Kanal kein Aktuator, setzen wir im Nachrichtenbuffer neu auf 1
        //damit die Daten bei nächster Gelegenheit an den MQTT Server gesendet werden
        if (!output) messages[channel].neu =1;
        messages[channel].typ = typ;
        //Restpaket = 2 weniger da Kanal und Typ gelesen wurden
        packetSize -= 2;
#ifdef DEBUG
        Serial.print("Daten:");
#endif
        //nun lesen wir die empfangenen Daten mit der ermittelten Länge
        for (uint8_t i=0; i<len; i++) {
          dat = LoRa.read();
          //für Aktuatoren merken wir uns keine Daten
          if (! output) messages[channel].daten[i] = dat;
#ifdef DEBUG
          Serial.printf("-%02x",dat);
#endif
          //Restpaket um eins vermindern
          packetSize --;
        }
#ifdef DEBUG
        Serial.println();
#endif
      }
      //Status aktualisieren
      loraCnt++;
      loraLast = getLocalTime();
      anzeige();
    } else {
      //Das Gerät ist nicht registriert 
      //wir merken uns die Geräte-Id um sie für die Registriuerung anzuzeigen
      for (uint8_t i = 0; i<6; i++) unbekannt[i] = devid[i];
      neuesGeraet = true;
      neuesGeraetTyp = 0; //LoRa Gerät
    }
    //Teil zwei Antwort an das LoRa Gerät senden
    delay(100);
    LoRa.beginPacket();
    //am Anfang die Geräte-Id
    LoRa.write(devid,6);
    // wir prüfen ob wir Output Daten für das aktuelle LoRa-Gerät haben
    int devbase = devnr*16;
    for (int i = devbase; i<devbase+8; i++) {
      //je nach typ Digital oder Analogdaten
      switch (messages[i].typ) {
          case LPP_DIGITAL_OUTPUT : LoRa.write(i-devbase);
            LoRa.write(messages[i].typ);
            LoRa.write(messages[i].daten,1);
#ifdef DEBUG
            Serial.println("Digital Ausgang");
#endif
            break;
          case LPP_ANALOG_OUTPUT :  LoRa.write(i-devbase);
            LoRa.write(messages[i].typ);
            LoRa.write(messages[i].daten,2);
#ifdef DEBUG
            Serial.println("Analog Ausgang");
#endif
            break;
      }
    }
    
    int lstatus = LoRa.endPacket();
#ifdef DEBUG
    Serial.print("Sendestatus = ");
    Serial.println(lstatus);
#endif

  }
}

//Funktion zum lesen der Konfiguration
void leseKonfiguration(const char *fn) {
  uint8_t i = 0;
  String tmp;
  char hex[3];
  if (!SPIFFS.exists(fn)) {
    //existiert noch nicht dann erzeugen
    schreibeKonfiguration(fn);
    return;
  }
  File f = SPIFFS.open(fn, "r");
  if (!f) {
    Serial.println(F("ERROR:: SPIFFS Kann Konfiguration nicht öffnen"));
    return;
  }
  while (f.available() && (i<MAXDEVICE)) {
    tmp = f.readStringUntil(',');
    devices[i].aktiv = (tmp == "1");
    tmp = f.readStringUntil(',');
    devices[i].dienst = tmp.toInt();
    tmp = f.readStringUntil(',');
    for (uint8_t j=0; j<6; j++){
      hex[0]=tmp[j*3];
      hex[1]=tmp[j*3+1];
      hex[2]=0;
      devices[i].id[j]= (byte) strtol(hex,NULL,16);
    }
    tmp = f.readStringUntil(',');
    devices[i].name = tmp;
    tmp = f.readStringUntil(',');
    devices[i].last = tmp;
    i++;
  }
  
}

void setup() {
  //gerätespeicher initialisieren
  for (uint8_t i =0; i<MAXDEVICE; i++) devices[i].aktiv = 0;

  // OLED Display initialisieren
  pinMode(16,OUTPUT);
  digitalWrite(16, LOW);
  delay(50); 
  digitalWrite(16, HIGH);
  display.init();
  display.flipScreenVertically();
  display.setFont(ArialMT_Plain_10);
  display.setTextAlignment(TEXT_ALIGN_LEFT);
  //Serielle Schnittstelle starten
  Serial.begin(115200);
  while (!Serial); 
  Serial.println("Start");

  //Flash File syastem
  if (SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED)) Serial.println(F("SPIFFS geladen"));
  //Konfiguration einlesen
  leseKonfiguration("/konfiguration.csv");
 

  initMessageBuffer();

  //SPI und LoRa initialisieren
  SPI.begin(5,19,27,18);
  LoRa.setPins(SS,RST,DI0);
  Serial.println("LoRa TRX");
  if (!LoRa.begin(BAND)) {
    Serial.println("Starting LoRa failed!");
    while (1);
  }
  LoRa.enableCrc();
  Serial.println("LoRa Initial OK!");
  delay(2000);
  //Mit dem WLAN und MQTT Server verbinden
  Serial.println("WLAN verbinden");
  Cayenne.begin(MQTT_USER, MQTT_PASSWORD, MQTT_CLIENTID, WIFI_SSID, WIFI_PASSWORD);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  //Web Server initialisieren
  server.on("/", handleRoot);
  server.begin();
  //Uhr mit Zeitserver synchronisieren
  configTime(GMT_OFFSET_SEC, DAYLIGHT_OFFSET_SEC, NTP_SERVER);
  //Aktuelle Uhrzeit ausgeben
  Serial.println(getLocalTime());


}


void loop() {
  anzeige();
  //LoRa Interface auf Daten prüfen
  readLoRa();
  //mit Cayenne MQTT Server kommunizieren
  Cayenne.loop(1);
  //Web Server bedienen
  server.handleClient();

}

//Daten aus dem Nachrichtenbuffer an den MQTT Server senden
CAYENNE_OUT_DEFAULT()
{
  boolean output = false;
  boolean sentData = false;
#ifdef DEBUG
  Serial.println(getLocalTime());
  Serial.println("Cayenne send");
#endif
  for (int i = 0; i<MAXCHANNELS; i++) {
    //nur neue Nachrichten senden
    if (messages[i].neu == 1) {
#ifdef DEBUG
      Serial.printf("Sende MQTT Typ %i\n",messages[i].typ);
#endif
      //je nach Typ Daten senden
      switch (messages[i].typ) {
          case LPP_DIGITAL_INPUT : Cayenne.digitalSensorWrite(i,messages[i].daten[0]); break;
          case LPP_DIGITAL_OUTPUT : output = true; break;
          //case LPP_ANALOG_INPUT : Cayenne.virtualWrite(i,(messages[i].daten[0]*256 + messages[i].daten[1])/100,"analog_sensor",UNIT_UNDEFINED); break; break;
          case LPP_ANALOG_OUTPUT : output = true; break;
          case LPP_LUMINOSITY : Cayenne.luxWrite(i,messages[i].daten[0]*256 + messages[i].daten[1]); break;
          case LPP_PRESENCE : Cayenne.digitalSensorWrite(i,messages[i].daten[0]); break;
          case LPP_TEMPERATURE : Cayenne.celsiusWrite(i,(messages[i].daten[0]*256 + messages[i].daten[1])/10); break;
          case LPP_RELATIVE_HUMIDITY : Cayenne.virtualWrite(i,messages[i].daten[0]/2,TYPE_RELATIVE_HUMIDITY,UNIT_PERCENT); break;
          case LPP_ACCELEROMETER : Cayenne.virtualWrite(i,(messages[i].daten[0]*256 + messages[i].daten[1])/1000,"gx","g"); break;
          case LPP_BAROMETRIC_PRESSURE : Cayenne.hectoPascalWrite(i,(messages[i].daten[0]*256 + messages[i].daten[1])/10); break;
          //case LPP_GYROMETER : len = LPP_GYROMETER_SIZE - 2; break;
          //case LPP_GPS : len = LPP_GPS_SIZE - 2; break;
      }
      if (!output) {
        messages[i].neu = 0;
        sentData = true;
      }
      
    }
  }
  if (sentData) {
    //Status aktualisieren
    cayCnt++;
    cayLast = getLocalTime();
    anzeige();
  }

}

CAYENNE_IN_DEFAULT()
{
  uint8_t * pData;
  int val;
  int ch = request.channel;
#ifdef DEBUG
  Serial.println("Cayenne recive");
  Serial.printf("MQTT Daten für Kanal %i = %s\n",ch,getValue.asString());
#endif
  switch (messages[ch].typ) {
      case LPP_DIGITAL_OUTPUT : messages[ch].daten[0] = getValue.asInt();
        messages[ch].neu = 1;
        break;
      case LPP_ANALOG_OUTPUT :  val = round(getValue.asDouble()*100);
        messages[ch].daten[0] = val / 256;
        messages[ch].daten[1] = val % 256;
        messages[ch].neu = 1;
        break;
  }

  
}

 

 

DisplaysEsp-32Projekte für fortgeschrittene

3 comments

moi

moi

für was genau ist der cayenne server zuständig?

kann ich auch einfach eine lokale ip eines mqtt servers eingeben und die pakete werden an diesen weiter geleitet?

Gerald Lechner

Gerald Lechner

Hallo Marco
Ja du brauchst dafür einen zweiter ESP32 mit LoRa und im nächsten Teil folgt der notwendige Code.
Gruß Gerald

Marco

Marco

Hallo,
toller Artikel der Lust aufs Ausprobieren macht. Noch eine Frage. Ihr beschreibt hier das Gateway. Das “spricht” auf der einen Seite LoRa und auf der anderen Seite (via WLan) mit einem Netzwerk.
Ich bräuchte dann für die Kommunikation mittels LoRa dann noch einen zweiten ESP als Client, richtig? Könnt Ihr darüber vielleicht einen 4. Teil machen und erklären wie man dann damit ein Ende-Ende Szenario aufbaut?

Viele Grüße
Marco

Leave a comment

All comments are moderated before being published

Recommended blog posts

  1. ESP32 jetzt über den Boardverwalter installieren - AZ-Delivery
  2. Internet-Radio mit dem ESP32 - UPDATE - AZ-Delivery
  3. Arduino IDE - Programmieren für Einsteiger - Teil 1 - AZ-Delivery
  4. ESP32 - das Multitalent - AZ-Delivery