// 1-channel LoRa Gateway for ESP8266 // Copyright (c) 2016, 2017, 2018, 2019 Maarten Westenberg version for ESP8266 // Version 6.1.5 // Date: 2019-12-20 // // based on work done by many people and making use of several libraries. // // All rights reserved. This program and the accompanying materials // are made available under the terms of the MIT License // which accompanies this distribution, and is available at // https://opensource.org/licenses/mit-license.php // // NO WARRANTY OF ANY KIND IS PROVIDED // // Author: Maarten Westenberg (mw12554@hotmail.com) // // This file contains the webserver code for the ESP Single Channel Gateway. // Note: // The ESP Webserver works with Strings to display HTML content. // Care must be taken that not all data is output to the webserver in one string // as this will use a LOT of memory and possibly kill the heap (cause system // crash or other unreliable behaviour. // Instead, output of the various tables on the webpage should be displayed in // chunks so that strings are limited in size. // Be aware that using no strings but only sendContent() calls has its own // disadvantage that these calls take a lot of time and cause the page to be // displayed like an old typewriter. // So, the trick is to make chucks that are sent to the website by using // a response String but not make those Strings too big. // // Also, selecting too many options for Statistics, display, Hopping channels // etc makes the gateway more sluggish and may impact the available memory and // thus its performance and reliability. It's up to the user to select wisely! // // -------------------------------------------------------------------------------- // PRINT IP // Output the 4-byte IP address for easy printing. // As this function is also used by _otaServer.ino do not put in #define // -------------------------------------------------------------------------------- static void printIP(IPAddress ipa, const char sep, String& response) { response+=(String)ipa[0]; response+=sep; response+=(String)ipa[1]; response+=sep; response+=(String)ipa[2]; response+=sep; response+=(String)ipa[3]; } // // The remainder of the file ONLY works is A_SERVER=1 is set. // #if A_SERVER==1 // ================================================================================ // WEBSERVER DECLARATIONS // ================================================================================ // None at the moment // ================================================================================ // WEBSERVER FUNCTIONS // ================================================================================ // -------------------------------------------------------------------------------- // Used by all functions requiring user confirmation // Displays a menu by user and two buttons "OK" and "CANCEL" // The function returns true for OK and false for CANCEL // Usage: Call this function ONCE during startup to declare and init // the ynDialog JavaScript function, and call the function // from the program when needed. // Paramters of the JavaScript function: // s: Th strig contining the question to be answered // o: The OK tab for the webpage where to go to // c: The Cancel string (optional) // -------------------------------------------------------------------------------- boolean YesNo() { boolean ret = false; String response = ""; response += ""; server.sendContent(response); // Put something like this in the ESP program // response += ""; return(ret); } // -------------------------------------------------------------------------------- // WWWFILE // This function will open a pop-up in the browser and then // display the contents of a file in that window // Output is sent to server.sendContent() // Note: The output is not in a variable, its size would be too large // Parameters: // fn; String with filename // Returns: // // -------------------------------------------------------------------------------- void wwwFile(String fn) { if (!SPIFFS.exists(fn)) { # if _MONITOR>=1 mPrint("wwwFile:: ERROR: SPIFFS file not found="); # endif return; } // _MONITOR # if _MONITOR>=2 else { mPrint("wwwFile:: SPIFFS File existist= " + String(fn)); } # endif //_MONITOR # if _MONITOR>=1 File f = SPIFFS.open(fn, "r"); // Open the file for reading int j; for (j=0; j // -------------------------------------------------------------------------------- static void wwwButtons() { String response = ""; String mode = (gwayConfig.expert ? "Basic Mode" : "Expert Mode"); String moni = (gwayConfig.monitor ? "Hide Monitor" : "Show Monitor"); String seen = (gwayConfig.seen ? "Hide Last Seen" : "Show Last Seen"); YesNo(); // Init the Yes/No function buttonDocu(); response += ""; response += ""; response += ""; # if _MONITOR>=1 response += ""; # endif response += ""; server.sendContent(response); // Send to the screen } // -------------------------------------------------------------------------------- // SET ESP8266/ESP32 WEB SERVER VARIABLES // // This funtion implements the WiFi Webserver (very simple one). The purpose // of this server is to receive simple admin commands, and execute these // results which are sent back to the web client. // Commands: DEBUG, ADDRESS, IP, CONFIG, GETTIME, SETTIME // The webpage is completely built response and then printed on screen. // // Parameters: // cmd: Contains a character array with the command to execute // arg: Contains the parameter value of that command // Returns: // // -------------------------------------------------------------------------------- static void setVariables(const char *cmd, const char *arg) { // DEBUG settings; These can be used as a single argument if (strcmp(cmd, "DEBUG")==0) { // Set debug level 0-2 if (atoi(arg) == 1) { debug = (debug+1)%4; } else if (atoi(arg) == -1) { debug = (debug+3)%4; } writeGwayCfg(CONFIGFILE); // Save configuration to file } if (strcmp(cmd, "CAD")==0) { // Set -cad on=1 or off=0 _cad=(bool)atoi(arg); writeGwayCfg(CONFIGFILE); // Save configuration to file } if (strcmp(cmd, "HOP")==0) { // Set -hop on=1 or off=0 _hop=(bool)atoi(arg); if (! _hop) { ifreq=0; setFreq(freqs[ifreq].upFreq); rxLoraModem(); sf = SF7; cadScanner(); } writeGwayCfg(CONFIGFILE); // Save configuration to file } // DELAY, write txDelay for transmissions // if (strcmp(cmd, "DELAY")==0) { // Set delay usecs gwayConfig.txDelay+=atoi(arg)*1000; writeGwayCfg(CONFIGFILE); // Save configuration to file } // TRUSTED, write node trusted value // if (strcmp(cmd, "TRUSTED")==0) { // Set delay usecs gwayConfig.trusted=atoi(arg); if (atoi(arg) == 1) { gwayConfig.trusted = (gwayConfig.trusted +1)%4; } else if (atoi(arg) == -1) { gwayConfig.trusted = (gwayConfig.trusted -1)%4; } writeGwayCfg(CONFIGFILE); // Save configuration to file } // SF; Handle Spreading Factor Settings // if (strcmp(cmd, "SF")==0) { uint8_t sfi = sf; if (atoi(arg) == 1) { if (sf>=SF12) sf=SF7; else sf= (sf_t)((int)sf+1); } else if (atoi(arg) == -1) { if (sf<=SF7) sf=SF12; else sf= (sf_t)((int)sf-1); } rxLoraModem(); // Reset the radio with the new spreading factor writeGwayCfg(CONFIGFILE); // Save configuration to file } // FREQ; Handle Frequency Settings // if (strcmp(cmd, "FREQ")==0) { uint8_t nf = sizeof(freqs)/ sizeof(freqs[0]); // Number of frequency elements in array // Compute frequency index if (atoi(arg) == 1) { if (ifreq==(nf-1)) ifreq=0; else ifreq++; } else if (atoi(arg) == -1) { Serial.println("down"); if (ifreq==0) ifreq=(nf-1); else ifreq--; } setFreq(freqs[ifreq].upFreq); rxLoraModem(); // Reset the radio with the new frequency writeGwayCfg(CONFIGFILE); // Save configuration to file } //if (strcmp(cmd, "GETTIME")==0) { Serial.println(F("gettime tbd")); } // Get the local time // //if (strcmp(cmd, "SETTIME")==0) { Serial.println(F("settime tbd")); } // Set the local time // // Help // if (strcmp(cmd, "HELP")==0) { Serial.println(F("Display Help Topics")); } // Node // #if GATEWAYNODE==1 if (strcmp(cmd, "NODE")==0) { // Set node on=1 or off=0 gwayConfig.isNode =(bool)atoi(arg); writeGwayCfg(CONFIGFILE); // Save configuration to file } // File Counter// if (strcmp(cmd, "FCNT")==0) { frameCount=0; rxLoraModem(); // Reset the radio with the new frequency writeGwayCfg(CONFIGFILE); } #endif // WiFi Manager // #if _WIFIMANAGER==1 if (strcmp(cmd, "NEWSSID")==0) { WiFiManager wifiManager; strcpy(wpa[0].login,""); strcpy(wpa[0].passw,""); WiFi.disconnect(); wifiManager.autoConnect(AP_NAME, AP_PASSWD ); } #endif // Update the software (from User Interface) #if A_OTA==1 if (strcmp(cmd, "UPDATE")==0) { if (atoi(arg) == 1) { updateOtaa(); writeGwayCfg(CONFIGFILE); } } #endif #if A_REFRESH==1 if (strcmp(cmd, "REFR")==0) { // Set refresh on=1 or off=0 gwayConfig.refresh =(bool)atoi(arg); writeGwayCfg(CONFIGFILE); // Save configuration to file } #endif } // -------------------------------------------------------------------------------- // OPEN WEB PAGE // This is the init function for opening the webpage // // -------------------------------------------------------------------------------- static void openWebPage() { ++gwayConfig.views; // increment number of views #if A_REFRESH==1 //server.client().stop(); // Experimental, stop webserver in case something is still running! #endif String response=""; server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate"); server.sendHeader("Pragma", "no-cache"); server.sendHeader("Expires", "-1"); // init webserver, fill the webpage // NOTE: The page is renewed every _WWW_INTERVAL seconds, please adjust in configGway.h // server.setContentLength(CONTENT_LENGTH_UNKNOWN); server.send(200, "text/html", ""); String tt=""; printIP((IPAddress)WiFi.localIP(),'.',tt); // Time with separator #if A_REFRESH==1 if (gwayConfig.refresh) { response += String() + "1ch Gateway " + String(tt) + ""; } else { response += String("1ch Gateway " + String(tt) + ""); } #else response += String("1ch Gateway " + String(tt) + ""); #endif response += ""; response += ""; response += ""; response +="

ESP Gateway Config

"; response +="

"; response +="Version: "; response+=VERSION; response +="
ESP alive since "; // STARTED ON stringTime(startTime, response); response +=", Uptime: "; // UPTIME uint32_t secs = millis()/1000; uint16_t days = secs / 86400; // Determine number of days uint8_t _hour = hour(secs); uint8_t _minute = minute(secs); uint8_t _second = second(secs); response += String(days) + "-"; if (_hour < 10) response += "0"; response += String(_hour) + ":"; if (_minute < 10) response += "0"; response += String(_minute) + ":"; if (_second < 10) response += "0"; response += String(_second); response +="
Current time "; // CURRENT TIME stringTime(now(), response); response +="
"; response +="

"; server.sendContent(response); } // -------------------------------------------------------------------------------- // H2 Gateway Settings // // Display the configuration and settings data. This is an interactive setting // allowing the user to set CAD, HOP, Debug and several other operating parameters // // -------------------------------------------------------------------------------- static void gatewaySettings() { String response=""; String bg=""; response +="

Gateway Settings

"; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; bg = " background-color: "; bg += ( _cad ? "LightGreen" : "orange" ); response +=""; response +=""; response +=""; response +=""; bg = " background-color: "; bg += ( _hop ? "LightGreen" : "orange" ); response +=""; response +=""; response +=""; response +=""; response +=""; } else { response += sf; response +=""; response +=""; } response +=""; // Channel response +=""; response +=""; } else { response += String(ifreq); response +=""; response +=""; response +=""; } response +=""; // Trusted options, when TRUSTED_NODE is set #ifdef _TRUSTED_NODES response +=""; response +=""; response +=""; response +=""; #endif // Debugging options, only when _DUSB is set, otherwise no // serial activity #if _DUSB>=1 || _MONITOR>=1 response +=""; response +=""; response +=""; response +=""; // Debug Pattern response +=""; bg = ( (pdebug & P_SCAN) ? "LightGreen" : "orange" ); response +=""; bg = ( (pdebug & P_CAD) ? "LightGreen" : "orange" ); response +=""; bg = ( (pdebug & P_RX) ? "LightGreen" : "orange" ); response +=""; bg = ( (pdebug & P_TX) ? "LightGreen" : "orange" ); response +=""; response += ""; // Use a second Line response +=""; bg = ( (pdebug & P_PRE) ? "LightGreen" : "orange" ); response +=""; bg = ( (pdebug & P_MAIN) ? "LightGreen" : "orange" ); response +=""; bg = ( (pdebug & P_GUI) ? "LightGreen" : "orange" ); response +=""; bg = ( (pdebug & P_RADIO) ? "LightGreen" : "orange" ); response +=""; response +=""; #endif // USB Debug, Serial Debugging response +=""; //response +=""; //response +=""; response +=""; #if GATEWAYNODE==1 response +=""; response +=""; response +=""; bg = " background-color: "; bg += ( (gwayConfig.isNode == 1) ? "LightGreen" : "orange" ); response +=""; response +=""; response +=""; response +=""; #endif /// WWW Refresh #if A_REFRESH==1 bg = " background-color: "; bg += ( (gwayConfig.refresh == 1) ? "LightGreen" : "orange" ); response +=""; response +=""; response +=""; response +=""; #endif // Time Correction DELAY if (gwayConfig.expert) { response +=""; response +=""; response +=""; response +=""; } // Reset Accesspoint #if _WIFIMANAGER==1 response +=""; #endif // Update Firmware all statistics response +=""; response +=""; // Format the Filesystem response +=""; response +=String() + ""; response +=""; // Reset all statistics #if _STATISTICS >= 1 response +=""; response +=String() + ""; response +=""; // Reset response +=""; response +=String() + ""; response +=""; #endif response +="
SettingValueSet
CAD"; response += ( _cad ? "ON" : "OFF" ); response +="
HOP"; response += ( _hop ? "ON" : "OFF" ); response +="
SF Setting"; if (_cad) { response += "AUTO
Channel"; if (_hop) { response += "AUTO
Trusted Nodes"; response +=gwayConfig.trusted; response +="
Debug Level"; response +=debug; response +="
Debug pattern"; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +="
"; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +="
Usb Debug"; response += _DUSB; response +="
Framecounter Internal Sensor"; response +=frameCount; response +=""; response +="
Gateway Node"; response += ( (gwayConfig.isNode == true) ? "ON" : "OFF" ); response +="
WWW Refresh"; response += ( (gwayConfig.refresh == 1) ? "ON" : "OFF" ); response +="
Time Correction (uSec)"; response += gwayConfig.txDelay; response +="
"; response +="Click here to reset accesspoint
"; response +="
Update Firmware
Format SPIFFS"+""+"
Statistics"+statc.resets+"
Boots and Resets"+gwayConfig.boots+"
"; server.sendContent(response); } // -------------------------------------------------------------------------------- // H2 Package Statistics // // This section display a matrix on the screen where everay channel and spreading // factor is displayed. // -------------------------------------------------------------------------------- static void statisticsData() { String response=""; // Header response +="

Package Statistics

"; response +=""; response +=""; # if _STATISTICS == 3 response +=""; response +=""; response +=""; # endif //_STATISTICS==3 response +=""; response +=""; response +=""; // // Table rows // response +=""; # if _STATISTICS == 3 response +=""; response +=""; response +=""; # endif response += ""; response +=""; response +=""; # if _STATISTICS == 3 response +=""; response +=""; response +=""; # endif //_STATISTICS==3 response +=""; response +=""; response +=""; #if _STATISTICS == 3 response +=""; response +=""; response +=""; # endif //_STATISTICS==3 response +=""; response +=""; // Provide a table with all the SF data including percentage of messsages #if _STATISTICS == 2 response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; # endif //_STATISTICS==2 # if _STATISTICS == 3 response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; # endif //_STATISTICS==3 response +="
CounterC 0C 1C 2PkgsPkgs/hr
Packages Downlink" + String(statc.msg_down_0) + "" + String(statc.msg_down_1) + "" + String(statc.msg_down_2) + "" + String(statc.msg_down) + "
Packages Uplink Total" + String(statc.msg_ttl_0) + "" + String(statc.msg_ttl_1) + "" + String(statc.msg_ttl_2) + "" + String(statc.msg_ttl) + "" + String((statc.msg_ttl*3600)/(now() - startTime)) + "
Packages Uplink OK " + String(statc.msg_ok_0) + "" + String(statc.msg_ok_1) + "" + String(statc.msg_ok_2) + "" + String(statc.msg_ok) + "
SF7 rcvd"; response +=statc.sf7; response +=""; response += String(statc.msg_ttl>0 ? 100*statc.sf7/statc.msg_ttl : 0)+" %"; response +="
SF8 rcvd"; response +=statc.sf8; response +=""; response += String(statc.msg_ttl>0 ? 100*statc.sf8/statc.msg_ttl : 0)+" %"; response +="
SF9 rcvd"; response +=statc.sf9; response +=""; response += String(statc.msg_ttl>0 ? 100*statc.sf9/statc.msg_ttl : 0)+" %"; response +="
SF10 rcvd"; response +=statc.sf10; response +=""; response += String(statc.msg_ttl>0 ? 100*statc.sf10/statc.msg_ttl : 0)+" %"; response +="
SF11 rcvd"; response +=statc.sf11; response +=""; response += String(statc.msg_ttl>0 ? 100*statc.sf11/statc.msg_ttl : 0)+" %"; response +="
SF12 rcvd"; response +=statc.sf12; response +=""; response += String(statc.msg_ttl>0 ? 100*statc.sf12/statc.msg_ttl : 0)+" %"; response +="
SF7 rcvd"; response +=statc.sf7_0; response +=""; response +=statc.sf7_1; response +=""; response +=statc.sf7_2; response +=""; response +=statc.sf7; response +=""; response += String(statc.msg_ttl>0 ? 100*statc.sf7/statc.msg_ttl : 0)+" %"; response +="
SF8 rcvd"; response +=statc.sf8_0; response +=""; response +=statc.sf8_1; response +=""; response +=statc.sf8_2; response +=""; response +=statc.sf8; response +=""; response += String(statc.msg_ttl>0 ? 100*statc.sf8/statc.msg_ttl : 0)+" %"; response +="
SF9 rcvd"; response +=statc.sf9_0; response +=""; response +=statc.sf9_1; response +=""; response +=statc.sf9_2; response +=""; response +=statc.sf9; response +=""; response += String(statc.msg_ttl>0 ? 100*statc.sf9/statc.msg_ttl : 0)+" %"; response +="
SF10 rcvd"; response +=statc.sf10_0; response +=""; response +=statc.sf10_1; response +=""; response +=statc.sf10_2; response +=""; response +=statc.sf10; response +=""; response += String(statc.msg_ttl>0 ? 100*statc.sf10/statc.msg_ttl : 0)+" %"; response +="
SF11 rcvd"; response +=statc.sf11_0; response +=""; response +=statc.sf11_1; response +=""; response +=statc.sf11_2; response +=""; response +=statc.sf11; response +=""; response += String(statc.msg_ttl>0 ? 100*statc.sf11/statc.msg_ttl : 0)+" %"; response +="
SF12 rcvd"; response +=statc.sf12_0; response +=""; response +=statc.sf12_1; response +=""; response +=statc.sf12_1; response +=""; response +=statc.sf12; response +=""; response += String(statc.msg_ttl>0 ? 100*statc.sf12/statc.msg_ttl : 0)+" %"; response +="
"; server.sendContent(response); } // -------------------------------------------------------------------------------- // Message History // If enabled, display the sensor messageHistory on the current webserver Page. // In this GUI section a number of statr[x] records are displayed such as: // // Time, The time the sensor message was received // Node, the DevAddr or even Node name for Trusted nodes, // Data (Localserver), when _LOCALSERVER is enabled contains decoded data // C, Channel frequency on which the sensor was received // Freq, The frequency of the channel // SF, Spreading Factor // pRSSI, Packet RSSI // // Parameters: // - // Returns: // - // // As we make the TRUSTED_NODE a dynamic parameter, it can be set/unset in the user // interface. It will allow the user to only see known nodes (with a name) in the // list or also nodes with only an address (Unknown nodes). // As a result, the size of the list will NOT be as large when only known nodes are // selected, as in can be deselcted in the GUI and we have only so much space on // th screen. // -------------------------------------------------------------------------------- static void messageHistory() { #if _STATISTICS >= 1 String response=""; // PRINT HEADERS response += "

Message History

"; response += ""; response += ""; response += ""; response += ""; #if _LOCALSERVER==1 response += ""; #endif response += ""; response += ""; response += ""; response += ""; #if RSSI==1 if (debug > 1) { response += ""; } #endif response += ""; server.sendContent(response); // PRINT NODE CONTENT for (int i=0; i"; response += String() + ""; #if _LOCALSERVER==1 response += String() + ""; response += String() + ""; response += String() + ""; response += String() + ""; #if RSSI==1 if (debug >= 2) { response += String() + ""; } #endif response += ""; server.sendContent(response); } server.sendContent("
TimeNodeDataCFreqSFpRSSIRSSI
"; // Tmst stringTime((statr[i].tmst), response); // XXX Change tmst not to be millis() dependent response += ""; // Node #ifdef _TRUSTED_NODES // DO nothing with TRUSTED NODES switch (gwayConfig.trusted) { case 0: printHEX((char *)(& (statr[i].node)),' ',response); break; case 1: if (SerialName((char *)(& (statr[i].node)), response) < 0) { printHEX((char *)(& (statr[i].node)),' ',response); }; break; case 2: if (SerialName((char *)(& (statr[i].node)), response) < 0) { continue; }; break; case 3: // Value and we do not print unless also defined for LOCAL_SERVER default: # if _MONITOR>=1 mPrint("Unknow value for gwayConfig.trusted"); # endif //_MONITOR break; } #else // _TRUSTED_NODES printHEX((char *)(& (statr[i].node)),' ',response); #endif // _TRUSTED_NODES response += ""; // Data for (int j=0; j" + statr[i].ch + "" + freqs[statr[i].ch].upFreq + "" + statr[i].sf + "" + statr[i].prssi + "" + statr[i].rssi + "
"); #endif } // -------------------------------------------------------------------------------- // H2 NODE SEEN HISTORY // If enabled, display the sensor last Seen history. // This setting ,pves togetjer with the "expert" mode. // If that mode is enabled than the node seen intory is displayed // // Parameters: // - // Returns: // - // -------------------------------------------------------------------------------- static void nodeHistory() { #if _SEENMAX >= 1 if (gwayConfig.seen) { // First draw the headers String response=""; response += "

Node Last Seen History

"; response += ""; response += ""; response += ""; response += ""; response += ""; //#if _LOCALSERVER==1 // response += ""; //#endif response += ""; response += ""; response += ""; server.sendContent(response); // Now start the contents int i; for (i=0; i<_SEENMAX; i++) { if (listSeen[i].idSeen == 0) break; response = ""; response += String() + ""; response += String() + ""; response += String() + ""; // Counter response += String() + ""; // Channel response += String() + ""; // SF server.sendContent(response); } server.sendContent("
TimeNodePkgsDataCSF
"; // Tmst stringTime((listSeen[i].timSeen), response); response += ""; // Node #ifdef _TRUSTED_NODES // DO nothing with TRUSTED NODES switch (gwayConfig.trusted) { case 0: printHEX((char *)(& (listSeen[i].idSeen)),' ',response); break; case 1: if (SerialName((char *)(& (listSeen[i].idSeen)), response) < 0) { printHEX((char *)(& (listSeen[i].idSeen)),' ',response); }; break; case 2: if (SerialName((char *)(& (listSeen[i].idSeen)), response) < 0) { continue; }; break; case 3: // Value 3 and we do not print unless also defined for LOCAL_SERVER default: # if _MONITOR>=1 mPrint("Unknow value for gwayConfig.trusted"); # endif //_MONITOR break; } #else printHEX((char *)(& (listSeen[i].idSeen)),' ',response); #endif // _TRUSTED_NODES response += "" + listSeen[i].cntSeen + "" + listSeen[i].chnSeen + "" + listSeen[i].sfSeen + "
"); } #endif //_SEENMAX } // nodeHistory() // -------------------------------------------------------------------------------- // MONITOR DATA // This function will print monitor data for the gateway based on the settings in // _MONITOR in file configGway.h // Only when the _MONITOR is positive then the function will be executed. // If less then nothing will be displayed. // XXX We have to make the function such that when printed, the webpage refreshes. // -------------------------------------------------------------------------------- int monitorData() { # if _MONITOR>=1 if (gwayConfig.monitor) { String response=""; response +="

Monitoring Console

"; response +=""; response +=""; response +=""; response +=""; for (int i=0; i<_MONITOR; i++) { if (monitor[i].txt == "") { break; } // DISPLAY line response +=""; } response +="
Monitor Console
" ; response += String(monitor[i].txt); response += "
"; server.sendContent(response); } # endif } // -------------------------------------------------------------------------------- // WIFI CONFIG // wifiConfig() displays the most important Wifi parameters gathered // // -------------------------------------------------------------------------------- static void wifiConfig() { if (gwayConfig.expert) { String response=""; response +="

WiFi Config

"; response +=""; response +=""; response +=""; #else response +=wifi_station_get_hostname(); response+=""; #endif response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; #ifdef _THINGSERVER response +=""; response +=""; #endif response +="
ParameterValue
WiFi host"; #if ESP32_ARCH==1 response +=WiFi.getHostname(); response+="
WiFi SSID"; response +=WiFi.SSID(); response+="
IP Address"; printIP((IPAddress)WiFi.localIP(),'.',response); response +="
IP Gateway"; printIP((IPAddress)WiFi.gatewayIP(),'.',response); response +="
NTP Server"; response+=NTP_TIMESERVER; response+="
LoRa Router"; response+=_TTNSERVER; response+="
LoRa Router IP"; printIP((IPAddress)ttnServer,'.',response); response +="
LoRa Router 2"; response+=_THINGSERVER; response += String() + ":" + _THINGPORT + "
LoRa Router 2 IP"; printIP((IPAddress)thingServer,'.',response); response +="
"; server.sendContent(response); } // gwayConfig.expert } // wifiConfig // -------------------------------------------------------------------------------- // H2 systemStatus // systemStatus is additional and only available in the expert mode. // It provides a number of system specific data such as heap size etc. // -------------------------------------------------------------------------------- static void systemStatus() { if (gwayConfig.expert) { String response=""; response +="

System Status

"; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; // XXX We Shoudl find an ESP32 alternative #if !defined ESP32_ARCH response +=""; response +=""; response+=""; response +=""; #endif response +=""; #if _STATISTICS >= 1 response +=""; response +=""; #endif response +="
ParameterValueSet
Gateway ID"; if (MAC_array[0]< 0x10) response +='0'; response +=String(MAC_array[0],HEX); // The MAC array is always returned in lowercase if (MAC_array[1]< 0x10) response +='0'; response +=String(MAC_array[1],HEX); if (MAC_array[2]< 0x10) response +='0'; response +=String(MAC_array[2],HEX); response +="FFFF"; if (MAC_array[3]< 0x10) response +='0'; response +=String(MAC_array[3],HEX); if (MAC_array[4]< 0x10) response +='0'; response +=String(MAC_array[4],HEX); if (MAC_array[5]< 0x10) response +='0'; response +=String(MAC_array[5],HEX); response+="
Free heap"; response+=ESP.getFreeHeap(); response+="
ESP speed"; response+=ESP.getCpuFreqMHz(); response +="
ESP Chip ID"; response+=ESP.getChipId(); response+="
OLED"; response+=OLED; response+="
WiFi Setups"; response+=gwayConfig.wifis; response+="
WWW Views"; response+=gwayConfig.views; response+="
"; server.sendContent(response); } // gwayConfig.expert } // systemStatus // -------------------------------------------------------------------------------- // H2 System State and Interrupt // Display interrupt data, but only for debug >= 2 // // -------------------------------------------------------------------------------- static void interruptData() { if (gwayConfig.expert) { uint8_t flags = readRegister(REG_IRQ_FLAGS); uint8_t mask = readRegister(REG_IRQ_FLAGS_MASK); String response=""; response +="

System State and Interrupt

"; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +=""; response +="
ParameterValueSet
_state"; switch (_state) { // See loraModem.h case S_INIT: response +="INIT"; break; case S_SCAN: response +="SCAN"; break; case S_CAD: response +="CAD"; break; case S_RX: response +="RX"; break; case S_TX: response +="TX"; break; default: response +="unknown"; break; } response +="
_STRICT_1CH" ; response += String() + _STRICT_1CH; response +="
flags (8 bits)0x"; if (flags <16) response += "0"; response +=String(flags,HEX); response+="
mask (8 bits)0x"; if (mask <16) response += "0"; response +=String(mask,HEX); response+="
Re-entrant cntr"; response += String() + gwayConfig.reents; response +="
ntp call cntr"; response += String() + gwayConfig.ntps; response+="
ntpErr cntr"; response += String() + gwayConfig.ntpErr; response +=""; stringTime(gwayConfig.ntpErrTime, response); response +="
"; server.sendContent(response); }// if gwayConfig.expert } // interruptData // -------------------------------------------------------------------------------- // setupWWW is the main function for webserver functions/ // SetupWWW function called by main setup() program to setup webserver // It does actually not much more than installing all the callback handlers // for messages sent to the webserver // // Implemented is an interface like: // http:///= // // -------------------------------------------------------------------------------- void setupWWW() { server.begin(); // Start the webserver // ----------------- // BUTTONS, define what should happen with the buttons we press on the homepage server.on("/", []() { sendWebPage("",""); // Send the webPage string server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); server.on("/HELP", []() { sendWebPage("HELP",""); // Send the webPage string server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); // Format the filesystem server.on("/FORMAT", []() { Serial.print(F("FORMAT ...")); SPIFFS.format(); // Normally disabled. Enable only when SPIFFS corrupt initConfig(&gwayConfig); writeConfig( CONFIGFILE, &gwayConfig); writeSeen( _SEENFILE, listSeen); // Write the last time record is seen # if _MONITOR>=1 if ((debug>=1) && (pdebug & P_GUI )) { mPrint("Format DONE"); } # endif //_MONITOR server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); // Reset the statistics server.on("/RESET", []() { Serial.println(F("RESET")); startTime= now() - 1; // Reset all timers too statc.msg_ttl = 0; // Reset package statistics statc.msg_ok = 0; statc.msg_down = 0; #if _STATISTICS >= 3 statc.msg_ttl_0 = 0; statc.msg_ttl_1 = 0; statc.msg_ttl_2 = 0; statc.msg_ok_0 = 0; statc.msg_ok_1 = 0; statc.msg_ok_2 = 0; statc.msg_down_0 = 0; statc.msg_down_1 = 0; statc.msg_down_2 = 0; #endif #if _STATISTICS >= 1 for (int i=0; i= 2 statc.sf7 = 0; statc.sf8 = 0; statc.sf9 = 0; statc.sf10= 0; statc.sf11= 0; statc.sf12= 0; statc.resets= 0; writeGwayCfg(CONFIGFILE); #if _STATISTICS >= 3 statc.sf7_0 = 0; statc.sf7_1 = 0; statc.sf7_2 = 0; statc.sf8_0 = 0; statc.sf8_1 = 0; statc.sf8_2 = 0; statc.sf9_0 = 0; statc.sf9_1 = 0; statc.sf9_2 = 0; statc.sf10_0= 0; statc.sf10_1= 0; statc.sf10_2= 0; statc.sf11_0= 0; statc.sf11_1= 0; statc.sf11_2= 0; statc.sf12_0= 0; statc.sf12_1= 0; statc.sf12_2= 0; #endif #endif #endif server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); // Reset the boot counter server.on("/BOOT", []() { #if _STATISTICS >= 2 gwayConfig.boots = 0; gwayConfig.wifis = 0; gwayConfig.views = 0; gwayConfig.ntpErr = 0; // NTP errors gwayConfig.ntpErrTime = 0; // NTP last error time gwayConfig.ntps = 0; // Number of NTP calls #endif gwayConfig.reents = 0; // Re-entrance writeGwayCfg(CONFIGFILE); #if _MONITOR>=1 if ((debug>=2) && (pdebug & P_MAIN)) { mPrint("BOOT, config written"); } #endif server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); // Reboot the Gateway // NOTE: There is no button for this code yet server.on("/REBOOT", []() { sendWebPage("",""); // Send the webPage string server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); ESP.restart(); }); server.on("/NEWSSID", []() { sendWebPage("NEWSSID",""); // Send the webPage string server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); // Set debug parameter server.on("/DEBUG=-1", []() { // Set debug level 0-2. Note: +3 is same as -1 debug = (debug+3)%4; writeGwayCfg(CONFIGFILE); // Save configuration to file # if _DUSB>=1 || _MONITOR>=1 if ((debug>=1) && (pdebug & P_MAIN)) { mPrint("DEBUG -1: config written"); } # endif // _DUSB _MONITOR server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); server.on("/DEBUG=1", []() { debug = (debug+1)%4; writeGwayCfg(CONFIGFILE); // Save configuration to file # if _DUSB>=1 || _MONITOR>=1 if ((debug>=1) && (pdebug & P_MAIN)) { mPrint("DEBUG +1: config written"); } # endif // _DUSB _MONITOR server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); // Set PDEBUG parameter // server.on("/PDEBUG=SCAN", []() { // Set debug level 0x01 pdebug ^= P_SCAN; writeGwayCfg(CONFIGFILE); // Save configuration to file server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); server.on("/PDEBUG=CAD", []() { // Set debug level 0x02 pdebug ^= P_CAD; writeGwayCfg(CONFIGFILE); // Save configuration to file server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); server.on("/PDEBUG=RX", []() { // Set debug level 0x04 pdebug ^= P_RX; writeGwayCfg(CONFIGFILE); // Save configuration to file server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); server.on("/PDEBUG=TX", []() { // Set debug level 0x08 pdebug ^= P_TX; writeGwayCfg(CONFIGFILE); // Save configuration to file server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); server.on("/PDEBUG=PRE", []() { // Set debug level 0-2 pdebug ^= P_PRE; writeGwayCfg(CONFIGFILE); // Save configuration to file server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); server.on("/PDEBUG=MAIN", []() { // Set debug level 0-2 pdebug ^= P_MAIN; writeGwayCfg(CONFIGFILE); // Save configuration to file server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); server.on("/PDEBUG=GUI", []() { // Set debug level 0-2 pdebug ^= P_GUI; writeGwayCfg(CONFIGFILE); // Save configuration to file server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); server.on("/PDEBUG=RADIO", []() { // Set debug level 0-2 pdebug ^= P_RADIO; writeGwayCfg(CONFIGFILE); // Save configuration to file server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); // Set delay in microseconds server.on("/DELAY=1", []() { gwayConfig.txDelay+=5000; writeGwayCfg(CONFIGFILE); // Save configuration to file # if _MONITOR>=1 if ((debug>=1) && (pdebug & P_MAIN)) { mPrint("DELAY +, config written"); } # endif server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); server.on("/DELAY=-1", []() { gwayConfig.txDelay-=5000; writeGwayCfg(CONFIGFILE); // Save configuration to file # if _MONITOR>=1 if ((debug>=1) && (pdebug & P_MAIN)) { mPrint("DELAY -, config written"); } # endif server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); // Set Trusted Node Parameter server.on("/TRUSTED=1", []() { gwayConfig.trusted = (gwayConfig.trusted +1)%4; writeGwayCfg(CONFIGFILE); // Save configuration to file # if _MONITOR>=2 mPrint("TRUSTED +, config written"); # endif //_MONITOR server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); server.on("/TRUSTED=-1", []() { gwayConfig.trusted = (gwayConfig.trusted -1)%4; writeGwayCfg(CONFIGFILE); // Save configuration to file #if _DUSB>=2 Serial.println(F("TRUSTED +, config written")); #endif server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); // Spreading Factor setting server.on("/SF=1", []() { if (sf>=SF12) sf=SF7; else sf= (sf_t)((int)sf+1); server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); server.on("/SF=-1", []() { if (sf<=SF7) sf=SF12; else sf= (sf_t)((int)sf-1); server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); // Set Frequency of the GateWay node server.on("/FREQ=1", []() { uint8_t nf = sizeof(freqs)/sizeof(freqs[0]); // Number of elements in array #if _DUSB>=2 Serial.print("FREQ==1:: For freq[0] sizeof vector="); Serial.print(sizeof(freqs[0])); Serial.println(); #endif if (ifreq==(nf-1)) ifreq=0; else ifreq++; server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); server.on("/FREQ=-1", []() { uint8_t nf = sizeof(freqs)/sizeof(freqs[0]); // Number of elements in array if (ifreq==0) ifreq=(nf-1); else ifreq--; server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); // Set CAD function off/on server.on("/CAD=1", []() { _cad=(bool)1; writeGwayCfg(CONFIGFILE); // Save configuration to file server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); server.on("/CAD=0", []() { _cad=(bool)0; writeGwayCfg(CONFIGFILE); // Save configuration to file server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); // GatewayNode server.on("/NODE=1", []() { #if GATEWAYNODE==1 gwayConfig.isNode =(bool)1; writeGwayCfg(CONFIGFILE); // Save configuration to file #endif server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); server.on("/NODE=0", []() { #if GATEWAYNODE==1 gwayConfig.isNode =(bool)0; writeGwayCfg(CONFIGFILE); // Save configuration to file #endif server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); #if GATEWAYNODE==1 // Framecounter of the Gateway node server.on("/FCNT", []() { frameCount=0; rxLoraModem(); // Reset the radio with the new frequency writeGwayCfg(CONFIGFILE); //sendWebPage("",""); // Send the webPage string server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); #endif // WWW Page refresh function server.on("/REFR=1", []() { // WWW page auto refresh ON #if A_REFRESH==1 gwayConfig.refresh =1; writeGwayCfg(CONFIGFILE); // Save configuration to file #endif server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); server.on("/REFR=0", []() { // WWW page auto refresh OFF #if A_REFRESH==1 gwayConfig.refresh =0; writeGwayCfg(CONFIGFILE); // Save configuration to file #endif server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); // Switch off/on the HOP functions server.on("/HOP=1", []() { _hop=true; server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); server.on("/HOP=0", []() { _hop=false; ifreq=0; setFreq(freqs[ifreq].upFreq); rxLoraModem(); server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); #if !defined ESP32_ARCH // Change speed to 160 MHz server.on("/SPEED=80", []() { system_update_cpu_freq(80); server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); server.on("/SPEED=160", []() { system_update_cpu_freq(160); server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); #endif // Display Documentation pages server.on("/DOCU", []() { server.sendHeader("Location", String("/"), true); buttonDocu(); server.send ( 302, "text/plain", ""); }); // Display LOGging information server.on("/LOG", []() { server.sendHeader("Location", String("/"), true); #if _DUSB>=2 Serial.println(F("LOG button")); #endif buttonLog(); server.send ( 302, "text/plain", ""); }); // Display Expert mode or Simple mode server.on("/EXPERT", []() { server.sendHeader("Location", String("/"), true); gwayConfig.expert = bool(1 - (int) gwayConfig.expert) ; server.send ( 302, "text/plain", ""); }); #if _MONITOR>=1 // Display Monitor Console or not server.on("/MONITOR", []() { server.sendHeader("Location", String("/"), true); gwayConfig.monitor = bool(1 - (int) gwayConfig.monitor) ; server.send ( 302, "text/plain", ""); }); #endif // Display the SEEN statistics server.on("/SEEN", []() { server.sendHeader("Location", String("/"), true); gwayConfig.seen = bool(1 - (int) gwayConfig.seen) ; server.send ( 302, "text/plain", ""); }); // Update the sketch. Not yet implemented server.on("/UPDATE=1", []() { #if A_OTA==1 updateOtaa(); #endif server.sendHeader("Location", String("/"), true); server.send ( 302, "text/plain", ""); }); // ----------- // This section from version 4.0.7 defines what PART of the // webpage is shown based on the buttons pressed by the user // Maybe not all information should be put on the screen since it // may take too much time to serve all information before a next // package interrupt arrives at the gateway # if _DUSB>=1 Serial.print(F("WWW Server started on port ")); Serial.println(A_SERVERPORT); # endif //_DUSB return; } // setupWWW // -------------------------------------------------------------------------------- // SEND WEB PAGE() // Call the webserver and send the standard content and the content that is // passed by the parameter. Each time a variable is changed, this function is // called to display the webpage again/ // // NOTE: This is the only place where yield() or delay() calls are used. // // -------------------------------------------------------------------------------- void sendWebPage(const char *cmd, const char *arg) { openWebPage(); yield(); // Do the initial website setup wwwButtons(); // Display buttons such as Documentation, Mode, Logfiles setVariables(cmd,arg); yield(); // Read Webserver commands from line statisticsData(); yield(); // Node statistics messageHistory(); yield(); // Display the sensor history, message statistics nodeHistory(); yield(); // Display the lastSeen array monitorData(); yield(); // Console gatewaySettings(); yield(); // Display web configuration wifiConfig(); yield(); // WiFi specific parameters systemStatus(); yield(); // System statistics such as heap etc. interruptData(); yield(); // Display interrupts only when debug >= 2 websiteFooter(); yield(); server.client().stop(); } // -------------------------------------------------------------------------------- // websiteFooter // // Thi function displays the last messages without header on the webpage and then // closes the webpage. // -------------------------------------------------------------------------------- static void websiteFooter() { // Close the client connection to server server.sendContent(String() + "

Click here to explain Help and REST options


"); server.sendContent(String() + ""); server.sendContent(""); yield(); } #endif // A_SERVER==1