Arbeiten mit dem Cayenne Dashboard - Gateway als Access-Point und Netzwerk-Client(Teil 5) - AZ-Delivery

In Teil 3 haben wir ein Gateway gebaut, das als Client in einem WLAN-Netzwerk arbeitet. Es war daher notwendig die entsprechenden Zugangsdaten in Konstanten im Programmspeicher zu hinterlegen. Da wir das Gateway auch für ESP-Now nutzbar machen wollen, wäre es praktisch wenn das Gateway auch als Access-Point arbeiten könnte.

Nun der ESP32 kann das. Wenn wir den WiFi-Mode auf WIFI_AP_STA stzen arbeitet der ESP sowohl als Access-Point als auch als Station. Allerdings kann nur ein Kanal verwendet werden. Die Verbindung ins Routernetzwerk hat bei der Kanalauswahl Priorität. Das heißt der Access-Point benutzt immer den selben Kanal wie die Verbindung ins Routernetzwerk.

Wir wollen nun diesen Doppelmode dazu nutzen unser Gateway über einen Browser zu konfigurieren. Die nötigen Zugangsdaten wie SSID und Passwort aber auch die Zugangsdaten zu Cayenne einfach auf Leerstrings. Wird das Gateway gestartet so kann es keine Verbindung ins Routernetzwerk aufbauen, da die Zugangsdaten fehlen. Nachdem der Verbindungsversuch ins Timeout gegangen ist, wird der Access-Point mit der SSID MQTTGateway gestartet. Die IP-Adresse des Gateways ist in diesem Netz immer 192.168.4.1

Um nun das Gateway zu konfigurieren melden wir einen Computer oder Smartphone an diesem Netzwerk an (kein Passwort) und starten einen Browser mit der Adresse http://192.168.4.1 Wir sollten dann die folgende Konfigurationsseite sehen.

Wenn die Zugangsdaten gespeichert werden, versucht das Gateway eine Verbindung mit dem Routernetz herzustellen. Ist der Verbindungsversuch erfolgreich, zeigt das Display die IP Adresse an mit der das Gateway im Routernetz erreicht werden kann. 

Nachdem eine Verbindung zum Routernetz besteht erhält man im Browser immer die Liste der registrierten Geräte. Über den Pfad /conf kann man auf die Konfigurationsseite zugreifen und die Zugangsdaten ändern. 

Sketch:

 

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


//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 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=\"10\" 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;
  uint8_t dienst; //0=LoRa, 1=ESP-Now
  uint8_t id[6];
  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];

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

//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 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 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
    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.
      Serial.println("Cayenne verbinden");
      Cayenne.begin(txtUser, txtPwd, txtId);
    //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();
  }
  //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");
  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++;
  }
  
}
//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; 
  }
  
}

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 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());
  WiFi.begin(txtSSID, txtPassword);
  //Verbindung ins Routernetz wird hergestellt
  uint8_t timeout = 0;
  while ((WiFi.status() != WL_CONNECTED) && (timeout<10)) {
    timeout++;
    delay(1000);
  }
  //wir warten maximal 10 Sekunden bis die Verbindung steht
  //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");
  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());
    Cayenne.begin(txtUser, txtPwd, txtId);
    Serial.println("Cayenne Verbindung hergestellt");
    //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();
  Serial.println("*********************************************");


}


void loop() {
  anzeige();
  if (WiFi.status() == WL_CONNECTED) {
    //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;
  }

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

 

 Viel Spaß beim Testen.

Esp-32Projekte für fortgeschrittene

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