Arbeiten mit dem Cayenne Dashboard - Verbessertes Gateway mit ESP-Now (Teil 6) - AZ-Delivery

Nach weiteren Tests ist es mir gelungen das MQTT Gateway so zu erweitern, dass es Geräte mit ESP-Now unterstützen kann. Das ermöglicht den Einsatz der sehr preiswerten Boards auf Basis vom ESP8266. Die Reichweite ist dann allerdings auf den Bereich des lokalen WLAN Netzwerks begrenzt. Es gibt noch eine weitere Einschränkung. ESPNow arbeitet nur mit dem WLAN Kanal 1. Es ist daher notwendig den Router für das lokale Netzwerk (z.B. Fritzbox) fix auf Kanal 1 einzustellen.

Da ich bisher die ESP-Now Verbindung nur vom Gerät zum Gateway zum arbeiten brachte, kann das ESP-Now Gerät nur Daten von Sensoren an das Gateway liefern aber keine Befehle vom Gateway erhalten. Ich werde jedoch versuchen dieses Problem zu lösen und in diesem Blog zu posten.

Der Code enthält auch noch einige Verbesserungen und den Fix für einen Fehler beim Speichern der Geräteliste, der dann auftrat wenn mehr als ein Gerät registriert war.

 

/* 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"
#include <esp_now.h>

//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

//Pin für das Display-Reset
#define DISPLRESET 16

//
#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
#define MAXKANAL 8 //maximale Anzahl an Kanälen pro Gerät

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

#define APPWD "123456789"
#define APCHANNEL 0

#define DEBUG 0

const String gwversion = "1.0";

//Bausteine für den Web-Server
const PROGMEM 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>"
"<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>";

const PROGMEM char HTML_HEADER_END[] = 
"</head>"
"<body><div style='margin-left:30px;'>";

const PROGMEM char HTML_SCRIPT[] =
"<script language=\"javascript\">"
"function reload() {"
"document.location=\"http://%s\";}"
"</script>";

const PROGMEM char HTML_END_RELOAD[] =
"</div><script language=\"javascript\">setTimeout(reload, 10000);</script></body>"
"</html>";
const PROGMEM char HTML_END[] =
"</body></html>";
const PROGMEM 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 PROGMEM char HTML_TAB_END[] =
"</table>";
const PROGMEM char HTML_NEWDEVICE[] =
"<div style=\"margin-top:20px;\">%s Name: <input type=\"text\" style=\"width:200px\" name=\"devname\" maxlength=\"20\" value=\"\"> <button name=\"registrieren\" value=\"%s\">Registrieren</button></div>";
const PROGMEM 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>";
const PROGMEM char HTML_CONFIG[] = 
"<form method=\"post\"><h1>Zugangsdaten</h1><table>"
"<tr><td>WLAN SSID</td><td><input type=\"text\" name=\"ssid\" value=\"%s\" size=50 maxlen=30/></td></tr>"
"<tr><td>WLAN Passwort</td><td><input type=\"text\" name=\"pwd\" value=\"%s\" size=50 maxlen=30/></td></tr>"
"<tr><td>Cayenne Benutzername</td><td><input type=\"text\" name=\"mquser\" value=\"%s\" size=50 maxlen=40/></td></tr>"
"<tr><td>Cayenne Passwort</td><td><input type=\"text\" name=\"mqpwd\" value=\"%s\" size=50 maxlen=50/></td></tr>"
"<tr><td>Cayenne Client Id</td><td><input type=\"text\" name=\"mqid\" value=\"%s\" size=50 maxlen=40/></td></tr>"
"<tr><td>&nbsp;</td><td><button name=\"save\" value=>Speichern</button></td></tr>"
"</table></form></body></html>";

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

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

//Globale Variable
//Zugangsdaten diese können über den Web-Server eingegeben werden
String wlanssid = "Lechner LAN";
String wlanpwd = "Guadalquivir2711";
String mqttuser = "";
String mqttpwd = "";
String mqttid = "";

//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];

//MQTT Status
int mqtt_con = 0;
//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 (WiFi.status() == WL_CONNECTED) {
    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;
}

//Funktion liefert Teil eines Datenbuffers im  format xx, xx, xx .... als String
String getData(uint8_t buf[], uint8_t start, uint8_t ende)
{
  String stdata;
  char tmp[4];
  sprintf(tmp,"%02x",buf[start]);
  stdata=tmp;
  for (uint8_t j = start+1; j<ende; j++) {
    sprintf(tmp,",%02x",buf[j]);
    stdata = stdata += tmp ;
  }
  return stdata;
}

//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('\n');
    if (devices[i].aktiv) {
      f.print(devices[i].dienst);f.print('\n');
      f.print(getId(devices[i].id));f.print('\n');
      f.print(devices[i].name);f.print('\n');
      f.print(devices[i].last);f.print('\n');
    } else {
      f.printf("0\n00:00:00:00:00:00\n-\n-\n");
    }
  }
}

//Funktion zum Speichern der Zugangsdaten
void schreibeZugang(const char *fn) {
  File f = SPIFFS.open(fn, FILE_WRITE);
  if (!f) {
    Serial.println(F("ERROR: SPIFFS Kann Zugangsdaten nicht speichern"));
    return;
  }
  f.print("WLANSSID=");f.print(wlanssid);f.print('\n');
  f.print("WLANPWD=");f.print(wlanpwd);f.print('\n');
  f.print("MQTTUSER=");f.print(mqttuser);f.print('\n');
  f.print("MQTTPWD=");f.print(mqttpwd);f.print('\n');
  f.print("MQTTID=");f.print(mqttid);f.print('\n');
  
}

//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;
  }
#ifdef DEBUG
  Serial.println("Lese Geräteliste");
#endif
  while (f.available() && (i<MAXDEVICE)) {
    Serial.printf("Lese Gerät %i",i);
    tmp = f.readStringUntil('\n');
    devices[i].aktiv = (tmp == "1");
    tmp = f.readStringUntil('\n');
    devices[i].dienst = tmp.toInt();
    tmp = f.readStringUntil('\n');
    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('\n');
    devices[i].name = tmp;
    tmp = f.readStringUntil('\n');
    devices[i].last = tmp;
#ifdef DEBUG
  Serial.print("Gerät "+getId(devices[i].id)+ " Name " + devices[i].name);
  Serial.printf(" Dienst %i Aktiv %i\r\n",devices[i].dienst,devices[i].aktiv);
#endif
    i++;
  }
  
}
//Funktion zum Lesen der Zugangsdaten
void leseZugang(const char *fn) {
  uint8_t i = 0;
  String key;
  String val;
  char hex[3];
  if (!SPIFFS.exists(fn)) {
    //existiert noch nicht dann erzeugen
    schreibeZugang(fn);
    return;
  }
  File f = SPIFFS.open(fn, "r");
  if (!f) {
    Serial.println(F("ERROR:: SPIFFS Kann Zugangsdaten nicht öffnen"));
    return;
  }
  while (f.available() && (i<MAXDEVICE)) {
    key = f.readStringUntil('=');
    val = f.readStringUntil('\n');
    if (key == "WLANSSID") wlanssid = val;
    if (key == "WLANPWD") wlanpwd = val; 
    if (key == "MQTTUSER") mqttuser = val; 
    if (key == "MQTTPWD") mqttpwd = val; 
    if (key == "MQTTID") mqttid = val; 
  }
  
}


//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;
  }
}

//Die Konfigurationsseite wird vom Web-Server angezeigt
void handleConfig(){
  char htmlbuf[1024];
  boolean restart = false;
  int index;

  //wurde der Speicherknopf gedrückt ?
  if (server.hasArg("save")) {
    //Daten aus dem POST request
    wlanssid = server.arg("ssid");
    //wenn die SSID ein Leerzeichen enthält erhalten wir ein "+"
    //das muss für die Anmeldung wieder in ein Leerzeichen gewandelt werden
    wlanssid.replace("+"," ");
    wlanpwd = server.arg("pwd");
    mqttuser = server.arg("mquser");
    mqttpwd = server.arg("mqpwd");
    mqttid = server.arg("mqid");
    Serial.println("Neue Konfiguration:");
    Serial.print("SSID: ");Serial.println(wlanssid);
    Serial.print("Passwort: ");Serial.println(wlanpwd);
    Serial.print("User: ");Serial.println(mqttuser);
    Serial.print("Passwort: ");Serial.println(mqttpwd);
    Serial.print("ID: ");Serial.println(mqttid);
    //Die neue Konfiguration in SPIFFS speichern
    schreibeZugang("/zugang.txt");
    //wir merken uns dass die WiFi Verbindung neu gestartet werden muss
    //zuerst muss aber der web server die HTML Seite liefern
    restart = true;
  }
  //Ausgabe der Konfigurationsseite
  //wir bilden Zeiger auf den internen speicher der Zugangsstrings
  //um diese für sprintf und zum Starten der WLAN und Cayenne Verbindung zu nutzen
  char* txtSSID = const_cast<char*>(wlanssid.c_str());
  char* txtPassword = const_cast<char*>(wlanpwd.c_str());   
  char* txtUser = const_cast<char*>(mqttuser.c_str());
  char* txtPwd = const_cast<char*>(mqttpwd.c_str());
  char* txtId = const_cast<char*>(mqttid.c_str());
  //Aktuelle HTML Seite an Browser senden
  server.setContentLength(CONTENT_LENGTH_UNKNOWN);
  //Header
  server.send(200, "text/html",HTML_HEADER);
  server.sendContent(HTML_HEADER_END);
  //Das Formular mit den Eingabefeldern wird mit den aktuellen Werten befüllt
  sprintf(htmlbuf,HTML_CONFIG,txtSSID,txtPassword,txtUser,txtPwd,txtId);
  //und an den Browsewr gesendet
  server.sendContent(htmlbuf);
  server.sendContent(HTML_END);
  if (restart) {
    //War das restart flag gesetzt muss die WiFi Verbindung getrennt und neu
    //aufgebaut werden
    mqtt_con = 0;
    Serial.println("Neustart");
    uint8_t timeout = 0;
    Serial.println("Verbindung trennen");
    WiFi.disconnect();
    while ((WiFi.status() == WL_CONNECTED) && (timeout < 10))
    {
      delay(1000);
      timeout++;
    }
    Serial.println("Neu verbinden");
    WiFi.begin(txtSSID,txtPassword);
    while ((WiFi.status() != WL_CONNECTED) && (timeout < 10))
    {
      delay(1000);
      timeout++;
    }
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
    if (WiFi.status() == WL_CONNECTED) {
      //war der Neustrart erfolgreich muss auch die Verbindung zu Cayenne neu aufgebaut werden.
      if ((mqttuser != "")&&(mqttpwd != "") && (mqttid != "")) {
        Serial.println("Cayenne verbinden");
        Cayenne.begin(txtUser, txtPwd, txtId);
        mqtt_con = 1;
      }
    //Uhr mit Zeitserver synchronisieren
    configTime(GMT_OFFSET_SEC, DAYLIGHT_OFFSET_SEC, NTP_SERVER);
    //Aktuelle Uhrzeit ausgeben
    Serial.println(getLocalTime());

    }
  }
}

//Die reset Seite wurde vom WebServer abgefragt
void handleReset() {
  //wir setzen die Zugangsdaten zurück
   wlanssid= "";
   wlanpwd = "";
   mqttuser = "";
   mqttpwd="";
   mqttid="";
   //und zeigen die Konfigurationsdaten an
   //erst wenn auf der Konfigurationsseite der Button
   //Speichern geklickt wird, werden die Zugangsdaten 
   //auch im SPIFFS gelöscht.
   handleConfig();
}

//Die Seite mit der Geräteliste wurde vom Webserver abgefragt
void handleWLANRequest(){
  char htmlbuf[512];
  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();
    Serial.println("Konfiguration wird gespeichert");
    schreibeKonfiguration("/konfiguration.csv");
  }
  //Aktuelle HTML Seite an Browser senden
  server.setContentLength(CONTENT_LENGTH_UNKNOWN);
  //Header
  server.send(200, "text/html",HTML_HEADER);
  //IP Adresse für reload script
  WiFi.localIP().toString().toCharArray(tmp1,20);
  sprintf(htmlbuf,HTML_SCRIPT,tmp1);
  server.sendContent(htmlbuf);
  server.sendContent(HTML_HEADER_END);
  //Formular Anfang
  server.sendContent("<div class=\"titel\">MQTT - Gateway</div><form method=\"post\">");
  //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_RELOAD);
}

//Service Funktions des Web Servers für das Root Verzeichnis
void handleRoot() {
  if (WiFi.status() != WL_CONNECTED) {
    //wenn wir keine Verbindung ins Routernetz haben
    //wird die Konfigurationsseite angezeigt, sodass die Zugangsdaten eingegeben werden können
    handleConfig();
  } else {
    handleWLANRequest();
  }
}


//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 "+gwversion);
  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();
}

//Empfangene Daten in den Nachrichtenbuffer speichern
//Rückgabewert die Gerätenummer oder -1 falls nicht registriert
uint8_t processData(uint8_t buf[], int buflen) {
  int devnr;
  int index;
  uint8_t devid[6];
  uint8_t channel;
  uint8_t typ;
  uint8_t len;
  uint8_t i;
  boolean output;
  index = 0;
  while ((index < 6) && (index < buflen)) {
    devid[index] = buf[index];
    index++;
  }
#ifdef DEBUG
  Serial.print("Geräte Id = ");Serial.println(getId(devid));
#endif  
  //nachschauen ob das Gerät registriert ist
  devnr = findDevice(devid);
  if (devnr >= 0)  {
#ifdef DEBUG
    Serial.println(getLocalTime());
    Serial.print("Geräte Nummer = ");Serial.println(devnr);
#endif  
    //wenn ja setzen wir den Zeitstempel für die letzte Meldung und
    //lesen die Daten
    devices[devnr].last = getLocalTime();
    //schreibeKonfiguration("/konfiguration.csv");
    //nun die Daten lesen
    while (index < buflen) {
      channel = buf[index++]+devnr*MAXKANAL;
      if (index == buflen) break;
      typ = buf[index++];
      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;
      i = 0;
      while ((i<len) && (index < buflen)) {
        if (!output) messages[channel].daten[i] = buf[index];
        i++; index++;
      }
#ifdef DEBUG
      Serial.printf("Kanal %i Typ  %i Daten: ",channel,typ);Serial.println(getData(messages[channel].daten,0,len));
#endif  
    }
    return devnr;
  } else {
    for (uint8_t i = 0; i<6; i++) unbekannt[i] = devid[i];
    neuesGeraet = true;
    return -1;
  }
  
}

uint8_t antwortBilden(uint8_t buf[], uint8_t devnr) {
  // wir prüfen ob wir Output Daten für das aktuelle LoRa-Gerät haben
  int index = 6; //erste sechs bytes sin die Geräte Id
  int devbase = devnr*MAXKANAL;
#ifdef DEBUG
  Serial.printf("Aktivatoren für Gerät %i Kanal %i bis %i\r\n",devnr,devbase,devbase+8);
#endif
  for (int i = devbase; i<devbase+8; i++) {
    //je nach typ Digital oder Analogdaten
    switch (messages[i].typ) {
        case LPP_DIGITAL_OUTPUT : buf[index++]= i-devbase;
          buf[index++]=messages[i].typ;
          buf[index++]=messages[i].daten[0];
#ifdef DEBUG
          Serial.println("Digital Ausgang");
#endif
          break;
        case LPP_ANALOG_OUTPUT :   buf[index++]= i-devbase;
          buf[index++]=messages[i].typ;
          buf[index++]=messages[i].daten[0];
          buf[index++]=messages[i].daten[1];
#ifdef DEBUG
          Serial.println("Analog Ausgang");
#endif
          break;
    }
  }
  return index;
}

//Eine Nachricht von einem LoRa Client verarbeiten
void readLoRa() {
  uint8_t buf[256];
  int ix;
  int devnr;
  uint8_t len;
  uint8_t byt;
  //Daten holen falls vorhanden
  int packetSize = LoRa.parsePacket();
  //haben wir Daten erhalten ?
  if (packetSize > 0) {
#ifdef DEBUG
    Serial.printf("%i Bytes von LoRa empfangen\r\n",packetSize);
#endif
    while ((ix < packetSize) && (ix < 256)) {
      byt= LoRa.read();
//      Serial.printf("%2x ",byt);
      buf[ix++] = byt;
    }
//    Serial.println();
#ifdef DEBUG
    Serial.println(getData(buf,0,packetSize));
#endif
    devnr = processData(buf, packetSize);
    if (devnr >=0) {
      //Status aktualisieren
      loraCnt++;
      loraLast = getLocalTime();
    } else {
      neuesGeraetTyp = 0; //LoRa Gerät
    }
    //Teil zwei Antwort an das LoRa Gerät senden
    //delay(500);
    //in den ertsen sechs bytes des Buffers steht bereits die GeräteId
    len = 6;
    //falls wir ein registriertes Gerät haben senden wir auch Daten mit
    if (devnr >= 0) len = antwortBilden(buf, devnr);
#ifdef DEBUG
    Serial.printf("Sende an Gerät %i %i bytes\r\n",devnr,len);
    Serial.println(getData(buf,0,len));
#endif
    LoRa.beginPacket();
    LoRa.write(buf,len);
    int lstatus = LoRa.endPacket();
#ifdef DEBUG
    Serial.print("Sendestatus = ");
    Serial.println(lstatus);
#endif

  }
}

// callback für ESP Now
void readESPNow(const uint8_t *mac_addr, const uint8_t *r_data, int data_len) {
  uint8_t data[70];
  uint8_t devnr;
  uint8_t len;
  
#ifdef DEBUG
  Serial.printf("%i Bytes von ESP-Now empfangen\r\n",data_len);
#endif
  memcpy(&data, r_data, sizeof(data));
  devnr = processData(data,data_len);
  if (devnr >=0) {
    //Status aktualisieren
    nowCnt++;
    nowLast = getLocalTime();
  } else {
    neuesGeraetTyp = 1; //ESP Now Gerät
  }
  delay(100);
  //Teil zwei Antwort an das ESP-Now Gerät senden
  //in den ertsen sechs bytes des Buffers steht bereits die GeräteId
  len = 6;
  //falls wir ein registriertes Gerät haben senden wir auch Daten mit
  if (devnr >= 0) len = antwortBilden(data, devnr);
#ifdef DEBUG
  Serial.printf("Sende an Gerät %i %i bytes\r\n",devnr,len);
  Serial.println(getData(data,0,len));
#endif
  esp_now_send(data, data, len); 
#ifdef DEBUG
  Serial.println("Ende");
#endif  
}


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

  // OLED Display initialisieren
  pinMode(DISPLRESET,OUTPUT);
  digitalWrite(DISPLRESET, LOW);
  delay(50); 
  digitalWrite(DISPLRESET, 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 system
  if (SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED)) Serial.println(F("SPIFFS geladen"));
  //Konfiguration und Zugangsdaten  einlesen
  leseKonfiguration("/konfiguration.csv");
  leseZugang("/zugang.txt");
  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);

  //Ausgabe der gelesenen Zugangsdaten zur Kontrolle
  Serial.print("SSID: ");Serial.println(wlanssid);
  Serial.print("Passwort: ");Serial.println(wlanpwd);
  Serial.print("User: ");Serial.println(mqttuser);
  Serial.print("Passwort: ");Serial.println(mqttpwd);
  Serial.print("ID: ");Serial.println(mqttid);
  //Mit dem WLAN und MQTT Server verbinden
  Serial.println("WLAN verbinden");
  //wir benutzen den ESP32 als Access Poin aber auch als Client im Routernetz
  WiFi.mode(WIFI_AP_STA);
  //wir benötigen Zeiger auf den Zeichenspeicher innerhalb der Strings
  char* txtSSID = const_cast<char*>(wlanssid.c_str());
  char* txtPassword = const_cast<char*>(wlanpwd.c_str());   
  char* txtUser = const_cast<char*>(mqttuser.c_str());
  char* txtPwd = const_cast<char*>(mqttpwd.c_str());
  char* txtId = const_cast<char*>(mqttid.c_str());
  //Unabhängig von der Verbindung ins Routernetz starten wir den AccessPoint
  //das ermöglicht die Konfiguration über einen Browser, wenn wir diesen 
  //am AccessPoint anmelden
  WiFi.softAP("MQTTGateway",APPWD,APCHANNEL,0);
  //Verbindung ins Routernetz wird hergestellt
  WiFi.begin(txtSSID, txtPassword);
  uint8_t timeout = 0;
  while ((WiFi.status() != WL_CONNECTED) && (timeout<10)) {
    timeout++;
    delay(1000);
  }
  //wir warten maximal 10 Sekunden bis die Verbindung steht
  if (WiFi.status() == WL_CONNECTED) {
    //War die Verbindung ins Routernetz erfolgreich, starten wir MQTT zu Cayenne
    //und synchronisieren die interne Uhr mit dem Time-Server
    Serial.print("IP address: ");
    Serial.println(WiFi.localIP());
    if ((mqttid != "") && (mqttuser != "") && (mqttpwd != "")) {
      Serial.println("State MQTT");
      Cayenne.begin(txtUser, txtPwd, txtId);
      Serial.println("Cayenne Verbindung hergestellt");
      mqtt_con = 1;
    }
    //Uhr mit Zeitserver synchronisieren
    configTime(GMT_OFFSET_SEC, DAYLIGHT_OFFSET_SEC, NTP_SERVER);
    //Aktuelle Uhrzeit ausgeben
    Serial.println(getLocalTime());
  }
  //Web Server initialisieren
  server.on("/", handleRoot);
  server.on("/conf",handleConfig);
  server.on("/reset",handleReset);
  server.begin();
  if (esp_now_init() == ESP_OK) Serial.println("ESP-Now initialisiert!");
  esp_now_register_recv_cb(readESPNow);
  Serial.println("*********************************************");


}


void loop() {
  anzeige();
  if (WiFi.status() == WL_CONNECTED) {
    //LoRa Interface auf Daten prüfen
    readLoRa();
    //mit Cayenne MQTT Server kommunizieren
    if (mqtt_con == 1) Cayenne.loop(1);
  }
  //Web Server bedienen
  server.handleClient();
  delay(100);

}

//Daten aus dem Nachrichtenbuffer an den MQTT Server senden
CAYENNE_OUT_DEFAULT()
{
  boolean output = false;
  boolean sentData = false;
  float val;
#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 Kanal %i Typ %i\n",i,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 : val = (messages[i].daten[0]*256 + messages[i].daten[1]);Cayenne.virtualWrite(i,val/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 : val = (messages[i].daten[0]*256 + messages[i].daten[1]); Cayenne.celsiusWrite(i,val/10); break;
          case LPP_RELATIVE_HUMIDITY : val=messages[i].daten[0];Cayenne.virtualWrite(i,val/2,TYPE_RELATIVE_HUMIDITY,UNIT_PERCENT); break;
          case LPP_ACCELEROMETER : val = (messages[i].daten[0]*256 + messages[i].daten[1]);Cayenne.virtualWrite(i,val/1000,"gx","g"); break;
          case LPP_BAROMETRIC_PRESSURE : val = (messages[i].daten[0]*256 + messages[i].daten[1]);Cayenne.hectoPascalWrite(i,val/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();
  }

}

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;
  }

  CAYENNE_LOG("Channel %u, value %s", request.channel, getValue.asString());
  //Process message here. If there is an error set an error message using getValue.setError(), e.g getValue.setError("Error message");
  
}

Das Display zeigt jetzt auch eine Versionsnummer an. Weiter Informationen zu diesem Gateway findet Ihr in den anderen Teilen dieser Serie.

 

Grundlagen software

2 comments

Roland

Roland

Ich habe den Fehler scheinbar gefunden. Ich habe das exakt gleiche Board von Heltec 2×.
Beim Ersten Board funktioniert das WLAN einfach nicht. Beim zweiten geht alles Problemlos.
Ich habe auf der Platine nichts gelötet (Pins) und habe so auch keinen Kurzschluss auf der Platine mit der Antenne verursacht. Schaut so aus als hätte die Platine einen Defekt. Ich habe jetzt den Heltec ESP32LORA als Client für LORA verwendet das funktioniert.
Optisch habe ich jetzt nichts gefunden was die WLAN Antenne stören könnte.

Roland

Roland

Jetzt spiele ich mich mit den Projekten von 1 bis 7
Das Hochladen und Kompilieren funktioniert bei allen Projekten.
Ich schaffe es aber nicht die WLAN Einstellungen zu ändern. Im Seriellen Monitor sieht man
LoRa TRX
LoRa Initial OK!
SSID: Lechner LAN
Passwort: Guadalquivir2711
User:
Passwort:
ID:
WLAN verbinden
ESP-Now initialisiert!

Wie in der Beschreibung erwähnt startet aber der Access-Point „MQTTGateway“ nicht.
Was mache ich da falsch?

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