Dans Partie 3 nous avons construit une passerelle qui fonctionne en tant que client sur un réseau Wi-Fi. Il était donc nécessaire de stocker les données d’accès correspondantes en constantes dans la mémoire du programme. Étant donné que nous voulons également rendre la passerelle utilisable pour ESP-Now, il serait commode si la passerelle pouvait également fonctionner comme un point d’accès.
Maintenant, l’ESP32 peut le faire. Lorsque nous nous asseyons le mode WiFi sur WIFI_AP_STA l’ESP fonctionne à la fois comme un point d’accès et une station. Cependant, un seul canal peut être utilisé. La connexion au réseau de routeurs est une priorité pour la sélection des canaux. Cela signifie que le point d’accès utilise toujours le même canal que la connexion au réseau de routeurs.
Nous voulons maintenant utiliser ce mode double pour configurer notre passerelle via un navigateur. Les données d’accès nécessaires telles que SSID et mot de passe ainsi que les données d’accès à Cayenne simplement sur les chaînes vides. Si la passerelle est démarré, elle ne peut pas se connecter au réseau de routeurs parce que les informations d’identification sont manquantes. Une fois la tentative de connexion effectuée, le point d’accès est commencé avec le SSID MQTTGateway. L’adresse IP de la passerelle est toujours 192.168.4.1 dans ce réseau
Pour configurer la passerelle, nous connectons un ordinateur ou un smartphone sur ce réseau (pas de mot de passe) et commençons un navigateur avec l’adresse http://192.168.4.1 Nous devrions ensuite voir la page de configuration suivante.
Lorsque les informations d’identification sont enregistrées, la passerelle tente de se connecter au réseau de routeurs. Si la tentative de connexion est réussie, l’affichage affiche l’adresse IP avec laquelle la passerelle peut être atteinte dans le réseau de routeurs.
Après une connexion au réseau de routeurs, vous obtenez toujours la liste des appareils enregistrés dans le navigateur. Vous pouvez accéder à la page de configuration et modifier les données d’accès via le chemin /conf.
dessin:
/- La passerelle MQTT forme une interface entre les appareils LoRa ou les appareils ESP Nowe et les tableaux de bord De Cayenne MQTT. Il fonctionne sur ESP32 avec LoRa et OLED display La configuration est effectuée par le navigateur */ #include <Spi.H (en)> #include <Lora.H (en)> #include "SSD1306.h" #include<Arduino.H (en)> #include <CayenneMQTTESP32.H (en)> #include <CayenneLPP CayenneLPP.H (en)> #include <Wifi.H (en)> #include <Web.H (en)> #include <Temps.H (en)> #include "FS.h" #include "SPIFFS.h" Serveur NTP pour la synchronisation du temps #define NTP_SERVER "de.pool.ntp.org" #define GMT_OFFSET_SEC 3600 #define DAYLIGHT_OFFSET_SEC 0 Épingles pour la puce LoRa #define Ss 18 #define Tvd 14 #define DI0 26 Fréquence de la puce LoRa #define Bande 433175000 // #define MAXCHANNELS (EN) 256 nombre maximum de canaux gérés #define MAXDEVICE (EN) 32 nombre maximum d’appareils gérés MAXCHANNELS/MAXDEVICE - 8 résultats dans le nombre maximum de canaux par appareil Format Flash Filesystem si ce n’est déjà fait #define FORMAT_SPIFFS_IF_FAILED Vrai #define Debug 1 Blocs de construction pour le serveur web Const PROGMEM Char Char HTML_HEADER[] = "Lt;! DOCTYPE HTML -GT;" "Lt;html-gt;" "Lt;head’gt;" "Lt;meta name - "viewport" contenu 'largeur 'largeur de l’appareil, échelle initiale '1.0, échelle maximale '1.0, utilisateur-évolutif '0'gt;"gt;" contenu /html de type «lt;meta http-equivMD»"type contenu";; charset-UTF-8"gt;" "Lt;title’gt;MQTT Gateway’lt;/title’gt;" "Lt;style’gt;" "corps - couleur de fond: #d2f3eb; police-famille: Arial, Helvetica, Sans-Serif; Couleur: #000000;font-taille:12pt; }" "th fond-couleur: #b6c0db; couleur: #050ed2;font-weight:lighter;font-size:10pt; "table, e, td "border: 1px noir solide;" ".title .font-size:18pt;font-weight:bold;text-align:center; " "Lt;/style’gt;"; Const PROGMEM Char Char HTML_HEADER_END[] = "Lt;/tête-gt;" "lt;body’lt;lt;div style’margin-left:30px;' -gt;"; Const PROGMEM Char Char HTML_SCRIPT[] = "Lt;script language"javascript"gt;" "rechargement de la fonction() "document.location"http://%s";" "Lt;/script’gt;"; Const PROGMEM Char Char HTML_END_RELOAD[] = "'lt;/div’gt;lt;lt;'lt;'lt;"javascript" 'gt;setTimeout(reload, 10000);'lt;/script 'gt;'lt;/body 'gt;" "Lt;/html’gt;"; Const PROGMEM Char Char HTML_END[] = "Lt;/corps 'lt;/html’gt;"; Const PROGMEM Char Char HTML_TAB_GERAETE[] = "Lt;table style""width:100%""lt;tr’gt;lt;lt;lt;th style"width:20%"""""'gt;ID’lt;/th 'lt;'lt;'lth style'"width:10%""gt;No.lt;/th’gt;" "Lt;th style""largeur:20%"gt;Channels’lt;/th’lt;'lt;lt;th style'"largeur:20%"gt;Name’lt;/th’gt;" "Lt;th style""largeur:20%"gt;Recent Data’lt;/th’lt;'lt;'lt;lt;th style'"width:10%""gt;Action’lt;/th 'gt;/tr’gt;"; Const PROGMEM Char Char HTML_TAB_END[] = "Lt;/table’gt;"; Const PROGMEM Char Char HTML_NEWDEVICE[] = "Lt;div style""margin-top:20px;"'gt;%s Nom: 'lt;input type"texte" style '"largeur:200px" nom "devname"" maxlength""10" valeur ""gt; Nom de bouton de lt;"register" valeur "%s"-gt;Register’lt;/button’lt;/div’gt;"; Const PROGMEM Char Char HTML_TAB_ZEILE[] = "lt;tr’gt;lt;td;td’td’t’lt;/td 'lt;td;td;td;%i’lt;/td 'lt;'lt;td;td’td’gt;%i 'lt;/td’lt;td;td;td;%s’lt ;//td’lt;lt;td;td’td’td’t’lt;/td 'lt;td;td;lt;lt;button name '"delete" value'"%i"gt;Delete’lt;/button’lt;/td’lt;/td’lt;/tr’gt;"; Const PROGMEM Char Char HTML_CONFIG[] = "Lt;form method"post"gt;lt;h1 'gt;Access data’lt;/h1'lt;'lt;'lt;com’gt;table’gt;" "Lt;tr’gt;lt;td;WLAN SSID-lt;/td’lt;td;td;td;td;lt;'lt;'lt;'lt;'lt;'lt"text""ssid" value'"%s" size'50 maxlen'30/'gt;'lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;'lt;/td’lt;'lt;td;td;td;td;tt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt; "Lt;tr’gt;lt;td;Td’gt;WLAN Password’lt;/td’lt;td;td;td;lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt; "lt;tr’gt;lt;td;td’gt;Cayenne Username’lt;/td’lt;td;td;td;'lt;'lt;'lt;'lt;'lt;'lt;'lt"text"""name"mquser" value'"%s" size'50 maxlen'40/'lt;'lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;'lt;'lt;'tt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;' "lt;tr’gt;lt;td;td’gt;Cayenne Password’lt;/td’lt;td;td;td;lt;lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;/td’lt;"tt;'lt;'lt;'tt;td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;/td’lt;tt;tt;tt;tt;tt;tt;tt;tt;tt;tt;tt "'lt;tr’gt;lt;td;td’gt;Cayenne Client Id’lt;/td’lt;td;td;td;lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'tt;'lt;'lt;'lt;tt;'lt;'lt;'tt;'tt;'lt;tt;tt;'tt;'lt;'lt;'lt;tt;tt;tt;tt;tt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;'lt;' "lt;tr’t;lt;td'''''''''''''''''est-à-lt'' et n’en a pas pour rien, la valeur de l’épargne et de l’achat de l’et;/let;/td’lt;/td’lt;/tr’gt;" "Lt;/table 'gt;'lt;/form’lt;/body 'lt;/html’gt;"; Structures Mémoire tampon d’actualités Struct MSG_BUF { uint8_t Type; uint8_t Nouveau; uint8_t Données[10]; }; Définition de l’appareil Struct Appareil { uint8_t Active; uint8_t Service; 0-LoRa, 1-ESP-Maintenant uint8_t Id[6]; String Nom; String Dernière; }; Variable globale Accéder aux données, celles-ci peuvent être saisies via le serveur web String wlanssid = "Lechner LAN"; String wlanpwd = "Guadalquivir2711"; String mqttuser (en) = ""; String mqttpwd = ""; String mqttid (en) = ""; Instance de serveur Web Web Serveur(80); Affichage OLED SSD1306 Affichage(0x3c, 4, 15); Tampon pour la mise en cache des messages par canal MSG_BUF Messages[MAXCHANNELS (EN)]; Liste des appareils définis Appareil Dispositifs[MAXDEVICE (EN)]; Id d’un appareil non enregistré uint8_t Inconnu[6]; Drapeau toujours vrai quand un nouvel appareil est détecté Boolean nouveauGeraet = Faux; Type de nouvel appareil 0-L’Ra 1 'ESPNow uint8_t nouveauGeraetType = 0; Compteurs et activités Statut pour l’affichage uint32_t loraCnt = 0; Nombre de messages LoRa reçus String loraLast = ""; Date et heure du dernier message LoRa reçu uint32_t maintenantCnt = 0; Nombre de messages ESP Now reçus String maintenantLast = ""; Date et heure du dernier message LoRa reçu uint32_t cayCnt = 0; Nombre de messages MQTT envoyés String cayLast = ""; Date et heure du dernier message MQTT envoyé Fonction retourne date et heure dans le format yyyy-mm-dd hh:mm:ss comme une chaîne String getLocalTime (en)() { Char Char sttime sttime[20] = ""; Struct Tm timeinfo (en anglais); Si (Wifi.Statut() == WL_CONNECTED) { Si(!getLocalTime (en)(&timeinfo (en anglais))){ Série.println("Échec à obtenir du temps"); Retour sttime sttime; } Strftime Strftime Strftime Strf(sttime sttime, Sizeof(sttime sttime), "%Y-%m-%d %H:%M:%S", &timeinfo (en anglais)); } Retour sttime sttime; } Funktion liefert eine 6-Byte Geräte-Id im format xx:xx:xx:xx:xx:xx als String String Getid Getid(uint8_t Id[6]) { String Stid; Char Char Tmp[4]; Sprintf(Tmp,"%02x",Id[0]); Stid=Tmp; Pour (uint8_t J = 1; J<6; J++) { Sprintf(Tmp,":%02x",Id[J]); Stid = Stid += Tmp ; } Retour Stid; } prépare le tampon de message définit tous les messages sur fait Vide initMessageBuffer() { Pour (Int Ⅰ. = 0;Ⅰ.<MAXCHANNELS (EN);Ⅰ.++) Messages[Ⅰ.].Nouveau = 0; } Fonction pour enregistrer la configuration Vide writeConfiguration(Const Char Char *Fn) { Fichier Q = SPIFFS SPIFFS.Ouvert(Fn, FILE_WRITE); Si (!Q) { Série.println(Q("ERREUR: SPIFFS ne peut pas enregistrer la configuration")); Retour; } Pour (uint8_t Ⅰ. = 0; Ⅰ.<MAXDEVICE (EN); Ⅰ.++) { Q.Imprimer(Dispositifs[Ⅰ.].Active);Q.Imprimer(","); Q.Imprimer(Dispositifs[Ⅰ.].Service);Q.Imprimer(","); Q.Imprimer(Getid Getid(Dispositifs[Ⅰ.].Id));Q.Imprimer(","); Q.Imprimer(Dispositifs[Ⅰ.].Nom);Q.Imprimer(","); Q.println(Dispositifs[Ⅰ.].Dernière); } } Fonction pour stocker les données d’accès Vide writingAccess(Const Char Char *Fn) { Fichier Q = SPIFFS SPIFFS.Ouvert(Fn, FILE_WRITE); Si (!Q) { Série.println(Q("ERROR: SPIFFS ne peut pas stocker les informations d’identification")); Retour; } Q.Imprimer("WLANSSID");Q.Imprimer(wlanssid);Q.Imprimer('n'); Q.Imprimer("WLANPWD");Q.Imprimer(wlanpwd);Q.Imprimer('n'); Q.Imprimer("MQTTUSER");Q.Imprimer(mqttuser (en));Q.Imprimer('n'); Q.Imprimer("MQTTPWD");Q.Imprimer(mqttpwd);Q.Imprimer('n'); Q.Imprimer("MQTTID");Q.Imprimer(mqttid (en));Q.Imprimer('n'); } Fonction d’enregistrement d’un nouvel appareil Vide geraetRegister (geraetRegister)() { uint8_t Ⅰ. = 0; entrée sans recherche Tandis que ((Ⅰ.<MAXDEVICE (EN)) && Dispositifs[Ⅰ.].Active) Ⅰ.++; il n’y a pas de nouvelle entrée, nous ne faisons rien Si (Ⅰ. < MAXDEVICE (EN)) { autrement enregistrer le nom geraet - nom inscrit ou inconnu si aucun n’a été entré Si (Serveur.hasArg("devname")) { Dispositifs[Ⅰ.].Nom = Serveur.Mauvais("devname"); } Autre { Dispositifs[Ⅰ.].Nom = "inconnu"; } Pour (uint8_t J = 0; J<6; J++) Dispositifs[Ⅰ.].Id[J]=Inconnu[J]; Dispositifs[Ⅰ.].Active = 1; Dispositifs[Ⅰ.].Service= nouveauGeraetType; Dispositifs[Ⅰ.].Dernière = ""; writeConfiguration("/configuration.csv"); nouveauGeraet = Faux; } } La page de configuration est affichée par le serveur web Vide poignéeConfig(){ Char Char htmlbuf[1024]; Boolean Redémarrer = Faux; Int Index; le bouton mémoire a-t-il été appuyé ? Si (Serveur.hasArg("sauver")) { Données de la demande POST wlanssid = Serveur.Mauvais("ssid"); si le SSID contient un espace, nous recevrons un cela doit être changé en un espace pour l’enregistrement wlanssid.Remplacer("+"," "); wlanpwd = Serveur.Mauvais("pwd"); mqttuser (en) = Serveur.Mauvais("mquser"); mqttpwd = Serveur.Mauvais("mqpwd"); mqttid (en) = Serveur.Mauvais("mqid"); Série.println("Nouvelle configuration:"); Série.Imprimer("SSID: ");Série.println(wlanssid); Série.Imprimer("Mot de passe: ");Série.println(wlanpwd); Série.Imprimer("Utilisateur: ");Série.println(mqttuser (en)); Série.Imprimer("Mot de passe: ");Série.println(mqttpwd); Série.Imprimer("ID: ");Série.println(mqttid (en)); Enregistrer la nouvelle configuration dans SPIFFS writingAccess("/access.txt"); nous nous souvenons que la connexion WiFi doit être redémarrée mais d’abord le serveur web doit livrer la page HTML Redémarrer = Vrai; } Sortie de la page de configuration nous formons des pointeurs à la mémoire interne des chaînes d’accès de les utiliser pour sprintf et de commencer la connexion Wi-Fi et Cayenne Char Char* txtSSID = const_cast<Char Char*>(wlanssid.c_str()); Char Char* txtPassword (txtPassword) = const_cast<Char Char*>(wlanpwd.c_str()); Char Char* txtUser (en) = const_cast<Char Char*>(mqttuser (en).c_str()); Char Char* txtPwd = const_cast<Char Char*>(mqttpwd.c_str()); Char Char* txtId = const_cast<Char Char*>(mqttid (en).c_str()); Envoyer la page HTML actuelle au navigateur Serveur.setContentLength(CONTENT_LENGTH_UNKNOWN); En-tête Serveur.Envoyer(200, "texte/html",HTML_HEADER); Serveur.envoyerContent(HTML_HEADER_END); Le formulaire avec les champs d’entrée est rempli des valeurs actuelles Sprintf(htmlbuf,HTML_CONFIG,txtSSID,txtPassword (txtPassword),txtUser (en),txtPwd,txtId); et envoyé à la Browsewr Serveur.envoyerContent(htmlbuf); Serveur.envoyerContent(HTML_END); Si (Redémarrer) { Est-ce que l’ensemble du drapeau de redémarrage doit déconnecter la connexion WiFi et se reconnecter à construire Série.println("Redémarrer"); uint8_t Timeout = 0; Série.println("Déconnecter"); Wifi.Débrancher(); Tandis que ((Wifi.Statut() == WL_CONNECTED) && (Timeout < 10)) { Retard(1000); Timeout++; } Série.println("Reconnect"); Wifi.Commencer(txtSSID,txtPassword (txtPassword)); Tandis que ((Wifi.Statut() != WL_CONNECTED) && (Timeout < 10)) { Retard(1000); Timeout++; } Série.Imprimer("Adresse IP: "); Série.println(Wifi.localIP()); Si (Wifi.Statut() == WL_CONNECTED) { le Neustrart a été un succès, la connexion à Cayenne doit également être reconstruite. Série.println("Connexion Cayenne"); Cayenne.Commencer(txtUser (en), txtPwd, txtId); Synchroniser l’horloge avec le serveur de temps configTime(GMT_OFFSET_SEC, DAYLIGHT_OFFSET_SEC, NTP_SERVER); Temps courant de sortie Série.println(getLocalTime (en)()); } } } La page de réinitialisation a été interrogée par le serveur web Vide poignéeReset() { nous réinitialiser les données d’accès wlanssid= ""; wlanpwd = ""; mqttuser (en) = ""; mqttpwd=""; mqttid (en)=""; et afficher les données de configuration que lorsque le bouton de la page de configuration est enregistrer est cliqué, les données d’accès est également supprimé dans SPIFFS. poignéeConfig(); } La page avec la liste de l’appareil a été interrogée par le serveur web Vide poignéeWLANRequest(){ Char Char htmlbuf[512]; Char Char tmp1[20]; Char Char tmp2[20]; Char Char tmp3[20]; Int Index; le bouton de suppression a-t-il cliqué ? Si (Serveur.hasArg("supprimer")) { Index = Serveur.Mauvais("supprimer").Toint(); #ifdef DEGUG DEGUG Série.Printf("Supprimer l’appareil %i ",Index); Série.println(Dispositifs[Index].Nom); #endif Dispositifs[Index].Active=0; writeConfiguration("/configuration.csv"); } le bouton Registre a-t-il été cliqué ? Si (Serveur.hasArg("registre")) { geraetRegister (geraetRegister)(); } Envoyer la page HTML actuelle au navigateur Serveur.setContentLength(CONTENT_LENGTH_UNKNOWN); En-tête Serveur.Envoyer(200, "texte/html",HTML_HEADER); Adresse IP pour recharger le script Wifi.localIP().Tostring().Tochararray Tochararray(tmp1,20); Sprintf(htmlbuf,HTML_SCRIPT,tmp1); Serveur.envoyerContent(htmlbuf); Serveur.envoyerContent(HTML_HEADER_END); Début de la forme Serveur.envoyerContent("Lt;div class"title"gt;MQTT - Gateway-lt;/div’lt;lt;form method'"post"gt;"); Tableau d’appareils actifs Serveur.envoyerContent(HTML_TAB_GERAETE); Pour (uint8_t Ⅰ. = 0; Ⅰ.<MAXDEVICE (EN); Ⅰ.++) { Si (Dispositifs[Ⅰ.].Active == 1) { Getid Getid(Dispositifs[Ⅰ.].Id).Tochararray Tochararray(tmp1,20); Dispositifs[Ⅰ.].Nom.Tochararray Tochararray(tmp2,20); Dispositifs[Ⅰ.].Dernière.Tochararray Tochararray(tmp3,20); Sprintf(htmlbuf,HTML_TAB_ZEILE,tmp1,Ⅰ.,Ⅰ.*8,Ⅰ.*8+7,tmp2,tmp3,Ⅰ.); Serveur.envoyerContent(htmlbuf); } } Serveur.envoyerContent(HTML_TAB_END); Si un nouvel appareil est trouvé, son ID et un champ d’entrée pour le nom de la et un bouton pour enregistrer le nouvel appareil s’affiche Si (nouveauGeraet) { Getid Getid(Inconnu).Tochararray Tochararray(tmp1,20); Sprintf(htmlbuf,HTML_NEWDEVICE,tmp1,tmp1); Serveur.envoyerContent(htmlbuf); } Serveur.envoyerContent(HTML_END_RELOAD); } Fonction de service serveur Web pour l’annuaire racine Vide handleRoot() { Si (Wifi.Statut() != WL_CONNECTED) { si nous n’avons pas de connexion au réseau de routeurs la page de configuration est affichée afin que les données d’accès puissent être saisies poignéeConfig(); } Autre { poignéeWLANRequest(); } } Fonction pour trouver un appareil dans la liste des appareils Indice de rendement de l’appareil ou -1 s’il n’a pas été trouvé Int findDevice(uint8_t Dev[6]) { uint8_t J; uint8_t Ⅰ. = 0; Boolean Trouvé = Faux; Jeu { J = 0; Si (Dispositifs[Ⅰ.].Active == 0) { Ⅰ.++; } Autre { Tandis que ((J < 6) && (Dev[J] == Dispositifs[Ⅰ.].Id[J])) {J++;} Trouvé = (J == 6); Si (!Trouvé) Ⅰ.++; } } Tandis que ((Ⅰ.<MAXDEVICE (EN)) && (!Trouvé)); Si (Trouvé) {Retour Ⅰ.;} Autre {Retour -1;} } Fonction pour afficher l’état sur l’écran OLED Vide Affichage() { Affichage.Clair(); Affichage.Cordon(0,0,"Porte d’entrée MQTT"); Affichage.Cordon(0,10,getLocalTime (en)()); Affichage.Cordon(0,20,Wifi.localIP().Tostring()); Affichage.Cordon(0,34,"MQTT: "); Affichage.Cordon(60,34,String(cayCnt)); Affichage.Cordon(0,44,"LoRa: "); Affichage.Cordon(60,44,String(loraCnt)); Affichage.Cordon(0,54,"MAINTENANT: "); Affichage.Cordon(60,54,String(maintenantCnt)); Affichage.Affichage(); } Traiter un message d’un client LoRa Vide lireLoRa() { Int devnr (en); uint8_t Daniel[6]; uint8_t Canal; uint8_t Type; uint8_t Len; uint8_t Dat; Boolean Sortie; Obtenez des données s’il est disponible Int packetSize = Lora.parsePacket (en)(); avons-nous reçu des données ? Si (packetSize > 5) { #ifdef Debug Série.println(getLocalTime (en)()); Série.Imprimer(" RX "); Série.Imprimer(packetSize); Série.println("Octets"); Série.Imprimer("Id de l’appareil"); #endif première lecture de l’id appareil Pour (uint8_t Ⅰ.=0; Ⅰ.<6;Ⅰ.++){ Daniel[Ⅰ.]=Lora.Lire(); #ifdef Debug Série.Printf("-%02x",Daniel[Ⅰ.]); #endif } #ifdef Debug Série.println(); #endif Calculer l’emballage résiduel packetSize -= 6; vérifier si l’appareil est enregistré devnr (en) = findDevice(Daniel); Si (devnr (en) >= 0) { si oui, nous définissons le délai pour le dernier message et lire les données Dispositifs[devnr (en)].Dernière = getLocalTime (en)(); writeConfiguration("/configuration.csv"); Tandis que (packetSize > 0) { Numéro de canal ' numéro d’appareil ' 16 ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' Canal = Lora.Lire() + devnr (en)*16; #ifdef Debug Série.Printf("Canal: %02x",Canal); #endif type de canal Type = Lora.Lire(); #ifdef Debug Série.Printf("Type: %02x",Type); #endif déterminer la longueur du paquet de données et si le canal est un actionneur Sortie = Faux; Interrupteur(Type) { Cas LPP_DIGITAL_INPUT : Len = LPP_DIGITAL_INPUT_SIZE - 2; Pause; Cas LPP_DIGITAL_OUTPUT : Len = LPP_DIGITAL_OUTPUT_SIZE - 2; Sortie = Vrai; Pause; Cas LPP_ANALOG_INPUT : Len = LPP_ANALOG_INPUT_SIZE - 2; Pause; Cas LPP_ANALOG_OUTPUT : Len = LPP_ANALOG_OUTPUT_SIZE - 2; Sortie = Vrai; Pause; Cas LPP_LUMINOSITY : Len = LPP_LUMINOSITY_SIZE - 2; Pause; Cas LPP_PRESENCE : Len = LPP_PRESENCE_SIZE - 2; Pause; Cas LPP_TEMPERATURE : Len = LPP_TEMPERATURE_SIZE - 2; Pause; Cas LPP_RELATIVE_HUMIDITY : Len = LPP_RELATIVE_HUMIDITY_SIZE - 2; Pause; Cas LPP_ACCELEROMETER : Len = LPP_ACCELEROMETER_SIZE - 2; Pause; Cas LPP_BAROMETRIC_PRESSURE : Len = LPP_BAROMETRIC_PRESSURE_SIZE - 2; Pause; Cas LPP_GYROMETER : Len = LPP_GYROMETER_SIZE - 2; Pause; Cas LPP_GPS : Len = LPP_GPS_SIZE - 2; Pause; Par défaut: Len = 0; } si le canal n’est pas un actionneur, nous réinitialisons le mémoire tampon de message à 1 de sorte que les données sont envoyées au serveur MQTT à la prochaine occasion Si (!Sortie) Messages[Canal].Nouveau =1; Messages[Canal].Type = Type; Paquet restant 2 de moins parce que le canal et le type ont été lus packetSize -= 2; #ifdef Debug Série.Imprimer("Données:"); #endif maintenant nous lisons les données reçues avec la longueur déterminée Pour (uint8_t Ⅰ.=0; Ⅰ.<Len; Ⅰ.++) { Dat = Lora.Lire(); pour les actionneurs, nous ne nous souvenons d’aucune donnée Si (! Sortie) Messages[Canal].Données[Ⅰ.] = Dat; #ifdef Debug Série.Printf("-%02x",Dat); #endif Réduire le reste du paquet d’un packetSize --; } #ifdef Debug Série.println(); #endif } Mise à jour de l’état loraCnt++; loraLast = getLocalTime (en)(); Affichage(); } Autre { L’appareil n’est pas enregistré nous nous souvenons de l’id de l’appareil pour l’afficher pour l’enregistrement Pour (uint8_t Ⅰ. = 0; Ⅰ.<6; Ⅰ.++) Inconnu[Ⅰ.] = Daniel[Ⅰ.]; nouveauGeraet = Vrai; nouveauGeraetType = 0; Dispositif LoRa } Partie deux Envoyer la réponse à l’appareil LoRa Retard(100); Lora.beginPacket(); au début, l’id de l’appareil Lora.Écrire(Daniel,6); nous vérifions si nous avons des données de sortie pour l’appareil LoRa actuel Int devbase = devnr (en)*16; Pour (Int Ⅰ. = devbase; Ⅰ.<devbase+8; Ⅰ.++) { selon le type de données numériques ou analogiques Interrupteur (Messages[Ⅰ.].Type) { Cas LPP_DIGITAL_OUTPUT : Lora.Écrire(Ⅰ.-devbase); Lora.Écrire(Messages[Ⅰ.].Type); Lora.Écrire(Messages[Ⅰ.].Données,1); #ifdef Debug Série.println("Sortie numérique"); #endif Pause; Cas LPP_ANALOG_OUTPUT : Lora.Écrire(Ⅰ.-devbase); Lora.Écrire(Messages[Ⅰ.].Type); Lora.Écrire(Messages[Ⅰ.].Données,2); #ifdef Debug Série.println("Sortie analogique"); #endif Pause; } } Int lstatus lstatus = Lora.endPacket(); #ifdef Debug Série.Imprimer("Envoyer le statut "); Série.println(lstatus lstatus); #endif } } Fonction pour lire la configuration Vide lireConfiguration(Const Char Char *Fn) { uint8_t Ⅰ. = 0; String Tmp; Char Char Hexagonale[3]; Si (!SPIFFS SPIFFS.Existe(Fn)) { n’existe pas encore, puis générer writeConfiguration(Fn); Retour; } Fichier Q = SPIFFS SPIFFS.Ouvert(Fn, "r"); Si (!Q) { Série.println(Q("ERREUR:: SPIFFS ne peut pas ouvrir la configuration")); Retour; } Tandis que (Q.Disponible() && (Ⅰ.<MAXDEVICE (EN))) { Tmp = Q.lireStringUntil(','); Dispositifs[Ⅰ.].Active = (Tmp == "1"); Tmp = Q.lireStringUntil(','); Dispositifs[Ⅰ.].Service = Tmp.Toint(); Tmp = Q.lireStringUntil(','); Pour (uint8_t J=0; J<6; J++){ Hexagonale[0]=Tmp[J*3]; Hexagonale[1]=Tmp[J*3+1]; Hexagonale[2]=0; Dispositifs[Ⅰ.].Id[J]= (Octet) strtol(Hexagonale,Null,16); } Tmp = Q.lireStringUntil(','); Dispositifs[Ⅰ.].Nom = Tmp; Tmp = Q.lireStringUntil(','); Dispositifs[Ⅰ.].Dernière = Tmp; Ⅰ.++; } } Fonction pour la lecture des données d’accès Vide lireAccess(Const Char Char *Fn) { uint8_t Ⅰ. = 0; String Clé; String Val; Char Char Hexagonale[3]; Si (!SPIFFS SPIFFS.Existe(Fn)) { n’existe pas encore, puis générer writingAccess(Fn); Retour; } Fichier Q = SPIFFS SPIFFS.Ouvert(Fn, "r"); Si (!Q) { Série.println(Q("ERROR:: SPIFFS ne peut pas ouvrir les informations d’identification")); Retour; } Tandis que (Q.Disponible() && (Ⅰ.<MAXDEVICE (EN))) { Clé = Q.lireStringUntil('='); Val = Q.lireStringUntil('n'); Si (Clé == "WLANSSID") wlanssid = Val; Si (Clé == "WLANPWD") wlanpwd = Val; Si (Clé == "MQTTUSER") mqttuser (en) = Val; Si (Clé == "MQTTPWD") mqttpwd = Val; Si (Clé == "MQTTID") mqttid (en) = Val; } } Vide Configuration() { Initialiser le stockage de l’appareil Pour (uint8_t Ⅰ. =0; Ⅰ.<MAXDEVICE (EN); Ⅰ.++) Dispositifs[Ⅰ.].Active = 0; OLED Display Initialize pinMode(16,Sortie); digitalWrite (en)(16, Faible); Retard(50); digitalWrite (en)(16, Haute); Affichage.Init(); Affichage.flipScreenVertically(); Affichage.setFont(ArialMT_Plain_10); Affichage.setTextAlignment(TEXT_ALIGN_LEFT); Démarrer l’interface sérielle Série.Commencer(115200); Tandis que (!Série); Série.println("Démarrer"); Système de fichiers Flash Si (SPIFFS SPIFFS.Commencer(FORMAT_SPIFFS_IF_FAILED)) Série.println(Q("SPIFFS chargé")); Lire dans les données de configuration et d’accès lireConfiguration("/configuration.csv"); lireAccess("/access.txt"); initMessageBuffer(); Initialiser SPI et LoRa Spi.Commencer(5,19,27,18); Lora.setPins setPins setPins setPin(Ss,Tvd,DI0); Série.println("LoRa TRX"); Si (!Lora.Commencer(Bande)) { Série.println("Démarrer LoRa a échoué!"); Tandis que (1); } Lora.enableCrc(); Série.println("LoRa initial OK!"); Retard(2000); Sortie des données d’accès lus pour le contrôle Série.Imprimer("SSID: ");Série.println(wlanssid); Série.Imprimer("Mot de passe: ");Série.println(wlanpwd); Série.Imprimer("Utilisateur: ");Série.println(mqttuser (en)); Série.Imprimer("Mot de passe: ");Série.println(mqttpwd); Série.Imprimer("ID: ");Série.println(mqttid (en)); Connectez-vous au serveur Wi-Fi et MQTT Série.println("Connect Wi-Fi"); nous utilisons l’ESP32 comme poin d’accès, mais aussi comme un client dans le réseau de routeurs Wifi.Mode(WIFI_AP_STA); nous avons besoin de pointeurs à la mémoire de caractère dans les cordes Char Char* txtSSID = const_cast<Char Char*>(wlanssid.c_str()); Char Char* txtPassword (txtPassword) = const_cast<Char Char*>(wlanpwd.c_str()); Char Char* txtUser (en) = const_cast<Char Char*>(mqttuser (en).c_str()); Char Char* txtPwd = const_cast<Char Char*>(mqttpwd.c_str()); Char Char* txtId = const_cast<Char Char*>(mqttid (en).c_str()); Wifi.Commencer(txtSSID, txtPassword (txtPassword)); Connexion au réseau de routeurs uint8_t Timeout = 0; Tandis que ((Wifi.Statut() != WL_CONNECTED) && (Timeout<10)) { Timeout++; Retard(1000); } nous attendons un maximum de 10 secondes jusqu’à ce que la connexion soit en place Quelle que soit la connexion au réseau de routeurs, nous commençons l’AccessPoint cela permet la configuration via un navigateur, si nous utilisons ce Connectez-vous à AccessPoint Wifi.softAP (softAP)("MQTTGateway"); Si (Wifi.Statut() == WL_CONNECTED) { Si la connexion au réseau de routeurs a été un succès, nous commençons MQTT à Cayenne et synchroniser l’horloge interne avec le serveur de temps Série.Imprimer("Adresse IP: "); Série.println(Wifi.localIP()); Cayenne.Commencer(txtUser (en), txtPwd, txtId); Série.println("Cayenne Connection Made"); Synchroniser l’horloge avec le serveur de temps configTime(GMT_OFFSET_SEC, DAYLIGHT_OFFSET_SEC, NTP_SERVER); Temps courant de sortie Série.println(getLocalTime (en)()); } Initialiser le serveur Web Serveur.Sur("/", handleRoot); Serveur.Sur("/conf",poignéeConfig); Serveur.Sur("/réinitialisation",poignéeReset); Serveur.Commencer(); Série.println("*********************************************"); } Vide Boucle() { Affichage(); Si (Wifi.Statut() == WL_CONNECTED) { Vérifiez LoRa Interface pour les données lireLoRa(); communiquer avec Cayenne MQTT Server Cayenne.Boucle(1); } Serveur Web de service Serveur.handleClient(); } Envoyer des données à partir du tampon de message au serveur MQTT CAYENNE_OUT_DEFAULT() { Boolean Sortie = Faux; Boolean sentData = Faux; #ifdef Debug Série.println(getLocalTime (en)()); Série.println("Cayenne envoyer"); #endif Pour (Int Ⅰ. = 0; Ⅰ.<MAXCHANNELS (EN); Ⅰ.++) { envoyer uniquement de nouveaux messages Si (Messages[Ⅰ.].Nouveau == 1) { #ifdef Debug Série.Printf("Envoyer MQTT Type %i’n",Messages[Ⅰ.].Type); #endif envoyer des données selon le type Interrupteur (Messages[Ⅰ.].Type) { Cas LPP_DIGITAL_INPUT : Cayenne.digitalSensorWrite(Ⅰ.,Messages[Ⅰ.].Données[0]); Pause; Cas LPP_DIGITAL_OUTPUT : Sortie = Vrai; Pause; cas LPP_ANALOG_INPUT : Cayenne.virtualWrite (i,(messages[i].daten[0] -256 - messages[i].data[1])/100", analog_sensor", UNIT_UNDEFINED); pause; pause; Cas LPP_ANALOG_OUTPUT : Sortie = Vrai; Pause; Cas LPP_LUMINOSITY : Cayenne.luxWrite (luxWrite)(Ⅰ,Messages[Ⅰ].Daten[0]*256 + Messages[Ⅰ].Daten[1]); Pause; Cas LPP_PRESENCE : Cayenne.digitalSensorWrite(Ⅰ,Messages[Ⅰ].Daten[0]); Pause; Cas LPP_TEMPERATURE : Cayenne.CelsiusWrite (en)(Ⅰ,(Messages[Ⅰ].Daten[0]*256 + Messages[Ⅰ].Daten[1])/10); Pause; Cas LPP_RELATIVE_HUMIDITY : Cayenne.virtualWrite (en)(Ⅰ,Messages[Ⅰ].Daten[0]/2,TYPE_RELATIVE_HUMIDITY,UNIT_PERCENT); Pause; Cas LPP_ACCELEROMETER : Cayenne.virtualWrite (en)(Ⅰ,(Messages[Ⅰ].Daten[0]*256 + Messages[Ⅰ].Daten[1])/1000,"gx","g"); Pause; Cas LPP_BAROMETRIC_PRESSURE : Cayenne.hectoPascalWrite(Ⅰ,(Messages[Ⅰ].Daten[0]*256 + Messages[Ⅰ].Daten[1])/10); Pause; cas LPP_GYROMETER : len LPP_GYROMETER_SIZE - 2; pause; cas LPP_GPS : len LPP_GPS_SIZE - 2; pause; } Si (!Sortie) { Messages[Ⅰ].Neu = 0; sentData = Vrai; } } } Si (sentData) { Statut aktualisieren cayCnt++; cayLast = getLocalTime (en)(); Anzeige(); } } CAYENNE_IN_DEFAULT() { uint8_t * Pdata; Int Val; Int Ch = Demande.Canal; #ifdef Debug Série.println("Cayenne recive"); Série.Printf("MQTT Daten f’r Kanal %i -%s’n",Ch,Getvalue.asString()); #endif Interrupteur (Messages[Ch].Typ) { Cas LPP_DIGITAL_OUTPUT : Messages[Ch].Daten[0] = Getvalue.asInt(); Messages[Ch].Neu = 1; Pause; Cas LPP_ANALOG_OUTPUT : Val = Rond(Getvalue.asDouble()*100); Messages[Ch].Daten[0] = Val / 256; Messages[Ch].Daten[1] = Val % 256; Messages[Ch].Neu = 1; Pause; } CAYENNE_LOG("Channel %u, valeur %s", Demande.Canal, Getvalue.asString()); Message de processus ici. S’il y a une erreur définissez un message d’erreur à l’aide de getValue.setError(), par exemple getValue.setError (« message d’erreur »); }
Viel Spa beim Testen.