Skip to content

Instantly share code, notes, and snippets.

@Tech500
Last active February 7, 2022 21:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Tech500/276fdfe78615541034d2b7bdfbfdda8d to your computer and use it in GitHub Desktop.
Save Tech500/276fdfe78615541034d2b7bdfbfdda8d to your computer and use it in GitHub Desktop.
ESP-NOW ESP32_Receiver.ino --working
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// "Version 2.0 'ESP32Receiver.ino' 02/02/2022 15:00 EST" Developed by William M. Lucid, tech500
//
// Developing AsyncWebServer 11/07/2019; modifying with fileRead and not found function. Adding wifi log of reconnects.
//
// Portion of NTP time code was developed from code provided by schufti --of ESP8266Community
//
// listFiles and readFile functions by martinayotte of ESP8266 Community Forum. readFile function modified by RichardS of ESP8266 Community Forum for ESP32.
//
// Time keeping functions uses NTP Time.
//
// GPS and rain gauge code developed by Muhammad Haroon. Thank you Muhammad.
//
// Previous projects: https://github.com/tech500
//
// Project is Open-Source, requires one BME280 breakout board, a NEO m8n GPS Module, and a "HiLetgo ESP-WROOM-32 ESP32 ESP-32S Development Board"
//
// http://weather-3.ddns.net/Weather Project web page --Servered from ESP32.
//
// https://observations-weather.000webhostapp.com/index.php --Servered from "Free" Domain Hosting service.
//
// Note: Uses esp32 core by ESP32 Community version 1.0.4 from "Arduino IDE, Board Manager" Arduino IDE; use Board: "Node32s" for the "HiLetGo" ESP32 Board.
//
//
///////////////////////////////////////////////////////////////////////////////////////////////////////
// ********************************************************************************
// ********************************************************************************
//
// See library downloads for each library license.
//
// ********************************************************************************
// ********************************************************************************
#include <esp_now.h>
#include "EEPROM.h" //Part of version 1.0.4 ESP32 Board Manager install
#include <WiFi.h> //Part of version 1.0.4 ESP32 Board Manager install
#include <HTTPClient.h> //Part of version 1.0.4 ESP32 Board Manager install
#include <AsyncTCP.h> //https://github.com/me-no-dev/AsyncTCP
#include <ESPAsyncWebServer.h> //https://github.com/me-no-dev/ESPAsyncWebServer
#include <Arduino_JSON.h>
#include <ESPmDNS.h> //Part of version 1.0.4 ESP32 Board Manager install
#include <ESP8266FtpServer.h> //https://github.com/nailbuster/esp8266FTPServer -->Needed for ftp transfers
#include <HTTPClient.h> //Part of version 1.0.4 ESP32 Board Manager install ----> Used for Domain Web Interace
#include <WiFiUdp.h> //1.0.4 ESP32 Board Manager install
#include <sys/time.h> // struct timeval --> Needed to sync time
#include <time.h> // time() ctime() --> Needed to sync time
#include "FS.h"
#include "SPIFFS.h"
#include "Update.h" //1.0.4 ESP32 Board Manager install
#include <ThingSpeak.h> //https://github.com/mathworks/thingspeak-arduino . Get it using the Library Manager
#include <Wire.h> //Part of version 1.0.4 ESP32 Board Manager install -----> Used for I2C protocol
#include <Ticker.h> //Part of version 1.0.4 ESP32 Board Manager install -----> Used for watchdog ISR
//#include <LiquidCrystal_I2C.h> //https://github.com/esp8266/Basic/tree/master/libraries/LiquidCrystal optional
#include "variableInput.h" //Packaged with project download. Provides editing options; without having to search 2000+ lines of code.
// Replace with your network details
//const char* host;
// Replace with your network credentials (STATION)
const char* ssidStation = "R2D2";
const char* passwordStation = "sissy4357";
// ACCESS POINT credentials
const char* ssidAP = "ESP32-Access-Point";
const char* passwordAP = "123456789";
// Wi-Fi channel for the access point (must match the sender channel)
#define CHAN_AP 1
// Replace with your network details
//const char* ssid;
//const char* password;
#import "index1.h" //Weather HTML; do not remove
#import "index2.h" //SdBrowse HTML; do not remove
#import "index3.h" //Graphs HTML; do not remove
#import "index4.h" //Restarts server; do not remove
IPAddress ipREMOTE;
///Are we currently connected?
boolean connected = false;
///////////////////////////////////////////////
WiFiUDP udp;
// local port to listen for UDP packets
//const int udpPort = 1337;
char incomingPacket[255];
char replyPacket[] = "Hi there! Got the message :-)";
//const char * udpAddress1;
//const char * udpAddress2;
// Structure example to receive data
// Must match the sender structure
typedef struct struct_message {
int id;
float temp;
float heat;
float hum;
float dew;
float press;
unsigned long int readingId;
} struct_message;
struct_message incomingReadings;
//struct_message incomingReadings;
float BOARD_ID;
float temp;
float heatid;
float humd;
float dewpt;
float baropress;
unsigned long int readingId = incomingReadings.readingId;
JSONVar board;
//AsyncWebServer server(80);
//AsyncEventSource events("/events");
////////////////////////// AsyncWebServer ////////////
AsyncWebServer serverAsync(PORT);
AsyncWebSocket ws("/ws"); // access at ws://[esp ip]/ws
AsyncEventSource events("/events"); // event source (Server-Sent events)
//////////////////////////////////////////////////////
// callback function that will be executed when data is received
void OnDataRecv(const uint8_t * mac_addr, const uint8_t *incomingData, int len) {
// Copies the sender mac address to a string
char macStr[18];
Serial.println("");
Serial.print("Packet received from: ");
snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
Serial.println(macStr);
memcpy(&incomingReadings, incomingData, sizeof(incomingReadings));
board["id"] = incomingReadings.id;
board["temperature"] = incomingReadings.temp;
board["heatindex"] = incomingReadings.heat;
board["humidity"] = incomingReadings.hum;
board["dewpoint"] = incomingReadings.dew;
board["pressure"] = incomingReadings.press;
board["readingId"] = (String)incomingReadings.readingId;
String jsonString = JSON.stringify(board);
events.send(jsonString.c_str(), "new_readings", millis());
Serial.printf("Board ID %u: %u bytes\n", incomingReadings.id, len);
Serial.printf("t value: %4.2f \n", incomingReadings.temp);
Serial.printf("i value: %4.2f \n", incomingReadings.heat);
Serial.printf("h value: %4.2f \n", incomingReadings.hum);
Serial.printf("d value: %4.2f \n", incomingReadings.dew);
Serial.printf("p value: %4.2f \n", incomingReadings.press);
Serial.printf("readingId value: %d \n", incomingReadings.readingId);
Serial.println();
}
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
<title>ESP-NOW DASHBOARD</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
<link rel="icon" href="data:,">
<style>
html {font-family: Arial; display: inline-block; text-align: center;}
p { font-size: 1.2rem;}
body { margin: 0;}
.topnav { overflow: hidden; background-color: #2f4468; color: white; font-size: 1.7rem; }
.content { padding: 20px; }
.card { background-color: white; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); }
.cards { max-width: 700px; margin: 0 auto; display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.reading { font-size: 2.8rem; }
.packet { color: #bebebe; }
</style>
</head>
<body>
<div class="topnav">
<h3>ESP-NOW DASHBOARD</h3>
</div>
<div class="content">
<div class="cards">
<div class="card temperature">
<h4><i class="fas fa-thermometer-half"></i> BOARD #1 - TEMPERATURE</h4><p><span class="reading"><span id="t1"></span> &deg;F</span></p><p class="packet">Reading ID: <span id="rt1"></span></p>
</div>
<div class="card heatindex">
<h4><i class="fas fa-thermometer-half"></i> BOARD #1- HEATINDEX</h4><p><span class="reading"><span id="i1"></span> &deg;F</span></p><p class="packet">Reading ID: <span id="ri1"></span></p>
<div>
<div class="card humidity">
<h4><i class="fas fa-tint"></i> BOARD #1 - HUMIDITY</h4><p><span class="reading"><span id="h1"></span> &percnt;</span></p><p class="packet">Reading ID: <span id="rh1"></span></p>
</div>
<div class="card dewpoint">
<h4><i class="fas fa-tint"></i> BOARD #1 - DEWPOINT</h4><p><span class="reading"><span id="d1"></span> &deg;F</span></p><p class="packet">Reading ID: <span id="rd1"></span></p>
</div>
<div class="card pressure">
<h4><i class="fas fa-tint"></i> BOARD #1 - PRESSURE</h4><p><span class="reading"><span id="p1"></span> inHg</span></p><p class="packet">Reading ID: <span id="rp1"></span></p>
</div>
</div>
</div>
<script>
if (!!window.EventSource) {
var source = new EventSource('/events');
source.addEventListener('open', function(e) {
console.log("Events Connected");
}, false);
source.addEventListener('error', function(e) {
if (e.target.readyState != EventSource.OPEN) {
console.log("Events Disconnected");
}
}, false);
source.addEventListener('message', function(e) {
console.log("message", e.data);
}, false);
source.addEventListener('new_readings', function(e) {
console.log("new_readings", e.data);
var obj = JSON.parse(e.data);
console.log(obj);
document.getElementById("t"+obj.id).innerHTML = obj.temperature.toFixed(2);
document.getElementById("i"+obj.id).innerHTML = obj.heatindex.toFixed(2);
document.getElementById("h"+obj.id).innerHTML = obj.humidity.toFixed(2);
document.getElementById("d"+obj.id).innerHTML = obj.dewpoint.toFixed(2);
document.getElementById("p"+obj.id).innerHTML = obj.pressure.toFixed(2);
document.getElementById("rt"+obj.id).innerHTML = obj.readingId;
document.getElementById("ri"+obj.id).innerHTML = obj.readingId;
document.getElementById("rh"+obj.id).innerHTML = obj.readingId;
document.getElementById("rd"+obj.id).innerHTML = obj.readingId;
document.getElementById("rp"+obj.id).innerHTML = obj.readingId;
}, false);
}
</script>
</body>
</html>)rawliteral";
#define TZ "EST+5EDT,M3.2.0/2,M11.1.0/2"
////////////////////////////////////////////////
WiFiClient client;
////////////////////////// Web Server /////////////////
//WiFiServer server(PORT);
///////////////////////////////////////////////////////
////////////////////////// FTP Server /////////////////
FtpServer ftpSrv;
///////////////////////////////////////////////////////
////////////////// OTA Support ////////////////////////////////////
//const char* http_username = "admin";
//const char* http_password = "reddogs";
//flag to use from web update to reboot the ESP
bool shouldReboot = false;
int logon;
void onRequest(AsyncWebServerRequest *request)
{
//Handle Unknown Request
request->send(404);
}
void onBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total)
{
//Handle body
}
void onUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final)
{
//Handle upload
}
void onEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len)
{
//Handle WebSocket event
}
//////////////////////////////// End OEA Support //////////////////////
static const uint32_t GPSBaud = 9600; // Ublox GPS default Baud Rate is 9600
const double Home_LAT = 88.888888; // Your Home Latitude --edit with your data
const double Home_LNG = 88.888888; // Your Home Longitude --edit with your data
const char* WiFi_hostname = "esp32";
Ticker secondTick;
portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED;
volatile int watchdogCounter;
int totalwatchdogCounter;
void IRAM_ATTR ISRwatchdog()
{
portENTER_CRITICAL_ISR(&mux);
watchdogCounter++;
delay(100);
portEXIT_CRITICAL_ISR(&mux);
}
int DOW, MONTH, DATE, YEAR, HOUR, MINUTE, SECOND;
char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
char strftime_buf[64];
String dtStamp(strftime_buf);
String lastUpdate;
unsigned long delayTime;
/*
const byte gpsone; //gps millis()
const byte boot; //watchdog millis()
const byte watch; //boot millis()
const byte rain; //rain gauge millis()
const byte sender; //ESP32 sender
*/
/* Two "independant" timed events */
const long eventTime_1 = 5000; //gps
const long eventTime_2 = 1000; //boot
const long eventTime_3 = 45 * 1000;
const long eventTime_4 = 5 * 60 * 1000;
const long eventTime_5 = 60 * 1000; //in ms
/* When did they start the race? */
unsigned long previousTime_1 = 0;
unsigned long previousTime_2 = 0;
unsigned long previousTime_3 = 0;
unsigned long previousTime_4 = 0;
unsigned long previousTime_5 = 0;
int lc = 0;
time_t tnow = 0;
int count = 0;
int i;
int error = 0;
int flag = 0;
int wait = 0;
int started; //Used to tell if Server has started
//use I2Cscanner to find LCD display address, in this case 3F //https://github.com/todbot/arduino-i2c-scanner/
//LiquidCrystal_I2C lcd(0x3F,16,2); // set the LCD address to 0x3F for a 16 chars and 2 line display
//#define sonalert 9 // pin for Piezo buzzer
#define online 19 //pin for online LED indicator
float pastPressure; //Previous pressure reading used to find pressure change difference.
float milliBars; //Barometric pressure in millibars
float difference; //change in barometric pressure drop; greater than .020 inches of mercury.
//long int id = 1; //Increments record number
char* filelist[12];
String logging;
char *filename;
String fn;
char str[] = {0};
String fileRead;
char MyBuffer[17];
String PATH;
//String publicIP; //in-place of xxx.xxx.xxx.xxx put your Public IP address inside quotes
//define LISTEN_PORT; // in-place of yyyy put your listening port number
// The HTTP protocol uses port 80 by default.
/*
This is the ThingSpeak channel number for the MathwWorks weather station
https://thingspeak.com/channels/YourChannelNumber. It senses a number of things and puts them in the eight
field of the channel:
Field 1 - Temperature (Degrees C )
Field 2 - Humidity (%RH)
Field 3 - Barometric Pressure (hpa)
Field 4 - Rain Last 5 Minutes (mm)
*/
/* You only need to format SPIFFS the first time you run a
test or else use the SPIFFS plugin to create a partition
https://github.com/me-no-dev/arduino-esp32fs-plugin */
//Hardware pin definitions
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// digital I/O pins
//Calibrate rain bucket here
//Rectangle raingauge from Sparkfun.com weather sensors
//float rain_bucket_mm = 0.011*25.4;//Each dump is 0.011" of water
//DAVISNET Rain Collector 2
//float rain_bucket_mm = 0.01*25.4;//Each dump is 0.01" of water //Convert inch to millmeter (0.01 * 25.4)
// volatiles are subject to modification by IRQs
//volatile unsigned long raintime, rainlast, raininterval, rain, Rainindtime, Rainindlast; // For Rain
//int addr=0;
#define eeprom_size 512
String eepromstring = "0.00";
//for loop
//int i;
unsigned long lastSecond, last5Minutes;
float lastPulseCount;
int currentPulseCount;
float rain5min;
float rainFall;
float rainHour;
float rainDay;
float daysRain;
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//Interrupt routines (these are called by the hardware interrupts, not by the main code)
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
#define FIVEMINUTES (300*1000L)
#define REEDPIN 34 //was 32 Touch pin
#define REEDINTERRUPT 0
volatile int pulseCount_ISR = 0;
void IRAM_ATTR reedSwitch_ISR()
{
static unsigned long lastReedSwitchTime;
// debounce for a quarter second = max. 4 counts per second
if (labs(millis() - lastReedSwitchTime) > 250)
{
portENTER_CRITICAL_ISR(&mux);
pulseCount_ISR++;
lastReedSwitchTime = millis();
portEXIT_CRITICAL_ISR(&mux);
}
}
void setup(void)
{
Serial.begin(9600);
// Set the device as a Station and Soft Access Point simultaneously
WiFi.mode(WIFI_AP_STA);
WiFi.setSleep(WIFI_PS_NONE); //Added 4/18/2021
started = 0; //Server started
while (!Serial);
Serial.println("");
Serial.println("Version 2.0 'ESP32_Receiver.ino' 02/02/2022 15:00 EST");
Serial.println("Please wait; for network connection...");
Serial.println("");
wifi_Start();
Wire.begin(21, 22);
pinMode(online, OUTPUT); //Set pinMode to OUTPUT for online LED
///////////////////////// FTP /////////////////////////////////
//FTP Setup, ensure SPIFFS is started before ftp;
////////////////////////////////////////////////////////////////
#ifdef ESP32 //esp32 we send true to format spiffs if cannot mount
if (SPIFFS.begin(true))
{
#elif defined ESP8266
if (SPIFFS.begin())
#endif
Serial.println("SPIFFS opened!");
Serial.println("");
ftpSrv.begin(ftpUser, ftpPassword); //username, password for ftp. set ports in ESP8266FtpServer.h (default 21, 50009 for PASV)
}
/////////////////////// End FTP//////////////////////////////
// Init ESP-NOW
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
// Once ESPNow is successfully Init, we will register for recv CB to
// get recv packer info
esp_now_register_recv_cb(OnDataRecv);
serverAsync.on("/", HTTP_GET, [](AsyncWebServerRequest * request)
{
request->send_P(200, "text/html", index_html);
});
events.onConnect([](AsyncEventSourceClient * client) {
if (client->lastId())
{
Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
}
// send event with message "hello!", id current millis
// and set reconnect delay to 1 second
client->send("hello!", NULL, millis(), 10000);
});
serverAsync.addHandler(&events);
serverAsync.on("/favicon.ico", HTTP_GET, [](AsyncWebServerRequest * request)
{
PATH = "/FAVICON";
//accessLog();
if (! flag == 1)
{
request->send(SPIFFS, "/favicon.png", "image/png");
}
//end();
});
/*
serverAsync.on("/", HTTP_GET, [](AsyncWebServerRequest * request)
{
PATH = "/";
accessLog();
ipREMOTE = request->client()->remoteIP();
if (! flag == 1)
{
request->send_P(200, PSTR("text/html"), HTML1, processor1);
}
end();
});
*/
serverAsync.on("/Weather", HTTP_GET, [](AsyncWebServerRequest * request)
{
PATH = "/Weather";
accessLog();
ipREMOTE = request->client()->remoteIP();
if (! flag == 1)
{
request->send_P(200, PSTR("text/html"), HTML1, processor1);
}
end();
});
serverAsync.on("/SdBrowse", HTTP_GET, [](AsyncWebServerRequest * request)
{
PATH = "/SdBrowse";
accessLog();
if (! flag == 1)
{
request->send_P(200, PSTR("text/html"), HTML2, processor2);
}
end();
});
serverAsync.on("/Graphs", HTTP_GET, [](AsyncWebServerRequest * request)
{
PATH = "/Graphs";
accessLog();
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", HTML3, processor3);
response->addHeader("Server", "ESP Async Web Server");
if (! flag == 1)
{
request->send(response);
}
end();
});
serverAsync.on("/RESTART", HTTP_GET, [](AsyncWebServerRequest * request)
{
PATH = "/RESTART";
accessLog();
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", HTML4, processor4);
response->addHeader("Server", "ESP Async Web Server");
if (! flag == 1)
{
request->send(response);
}
Serial2.flush();
end();
ESP.restart();
});
///////////////////// OTA Support //////////////////////
//attach.AsyncWebSocket
ws.onEvent(onEvent);
serverAsync.addHandler(&ws);
// attach AsyncEventSource
serverAsync.addHandler(&events);
// respond to GET requests on URL /heap
serverAsync.on("/heap", HTTP_GET, [](AsyncWebServerRequest * request)
{
request->send(200, "text/plain", String(ESP.getFreeHeap()));
});
// upload a file to /upload
serverAsync.on("/upload", HTTP_POST, [](AsyncWebServerRequest * request)
{
request->send(200);
}, onUpload);
// send a file when /index is requested
serverAsync.on("/index", HTTP_ANY, [](AsyncWebServerRequest * request)
{
request->send(SPIFFS, "/index.htm");
});
// HTTP basic authentication
serverAsync.on("/login", HTTP_GET, [](AsyncWebServerRequest * request)
{
PATH = "/login";
accessLog();
if (!request->authenticate(http_username, http_password))
return request->requestAuthentication();
request->send(200, "text/plain", "Login Success; upload firmware!");
logon = 1;
end();
});
// Simple Firmware Update Form
serverAsync.on("/update", HTTP_GET, [](AsyncWebServerRequest * request)
{
PATH = "/update";
accessLog();
if (logon == 1)
{
request->send(200, "text/html", "<form method='POST' action='/update' enctype='multipart/form-data'><input type='file' name='update'><input type='submit' value='Update'></form>");
logon = 0;
end();
}
else
{
request->send(404); //Sends 404 File Not Found
logon = 0;
end();
}
});
serverAsync.on("/update", HTTP_POST, [](AsyncWebServerRequest * request)
{
shouldReboot = !Update.hasError();
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", shouldReboot ? "OK" : "FAIL");
response->addHeader("Connection", "close");
request->send(response);
}, [](AsyncWebServerRequest * request, String filename, size_t index, uint8_t *data, size_t len, bool final)
{
if (!index)
{
Serial.printf("Update Start: %s\n", filename.c_str());
//Update.runAsync(true);
if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000))
{
Update.printError(Serial);
}
}
if (!Update.hasError())
{
if (Update.write(data, len) != len)
{
Update.printError(Serial);
end();
}
}
if (final)
{
if (Update.end(true))
{
Serial.printf("Update Success: %uB\n", index + len);
end();
}
else
{
Update.printError(Serial);
}
}
});
// attach filesystem root at URL /fs
serverAsync.serveStatic("/fs", SPIFFS, "/");
// Catch-All Handlers
// Any request that can not find a Handler that canHandle it
// ends in the callbacks below.
serverAsync.onNotFound(onRequest);
serverAsync.onFileUpload(onUpload);
serverAsync.onRequestBody(onBody);
//serverAsync.begin();
///////////////////////// End OTA Support /////////////////////////////
serverAsync.onNotFound(notFound);
Serial2.end();
//Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2);
secondTick.attach(1, ISRwatchdog); //watchdog ISR triggers every 1 second
configTime(0, 0, udpAddress1, udpAddress2);
setenv("TZ", "EST+5EDT,M3.2.0/2,M11.1.0/2", 3); // this sets TZ to Indianapolis, Indiana
tzset();
/*
Serial.print("wait for first valid timestamp ");
while (time(nullptr) < 100000ul)
{
Serial.print(".");
delay(5000);
}
*/
pinMode(REEDPIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(REEDPIN), reedSwitch_ISR, FALLING);
//SPIFFS.format();
//lcdDisplay(); // LCD 1602 Display function --used for inital display
ThingSpeak.begin(client);
//delay(30 * 1000); //Used to test reconnect WiFi routine. Will produce one entry for each disconnect in "WIFI.TXT."
//WiFi.disconnect(); //Used to test reconnect WiFi routine. Will produce one entry for eac disconnect in "WIFI.TXT."
//delay(50 * 1000); //Uncomment to test watchdog
//Serial.println("Delay elapsed");
started = 1;
}
void loop()
{
/* Updates frequently */
//unsigned long currentTime = millis();
static unsigned long lastEventTime = millis();
static const unsigned long EVENT_INTERVAL_MS = 5000;
if ((millis() - lastEventTime) > EVENT_INTERVAL_MS) {
events.send("ping",NULL,millis());
lastEventTime = millis();
}
//udp only send data when connected
if (connected)
{
//Send a packet
udp.beginPacket(udpAddress1, udpPort);
udp.printf("Seconds since boot: %u", millis() / 1000);
udp.endPacket();
}
if (WiFi.status() != WL_CONNECTED)
{
wifi_Start();
delay(10 * 1000); //wait 10 seconds before writing
getDateTime();
//Open a "WIFI.TXT" for appended writing. Client access ip address logged.
File logFile = SPIFFS.open("/WIFI.TXT", "a");
if (!logFile)
{
Serial.println("File: '/WIFI.TXT' failed to open");
}
else
{
logFile.print("Reconnected WiFi: ");
logFile.println(dtStamp);
started = 1;
}
}
delay(1);
if (started == 1)
{
delay(2000);
getDateTime();
digitalWrite(online, HIGH); //Indicates when Server is "Ready" for Browser Requests.
delay(3000);
digitalWrite(online, LOW);
// Open a "log.txt" for appended writing
File log = SPIFFS.open("/SERVER.TXT", "a");
if (!log)
{
Serial.println("file 'SERVER.TXT' open failed");
}
log.print("Restarted Server: ");
log.print(" ");
log.print(dtStamp);
log.println("");
log.close();
//getWeatherData();
//previousTime_1 = currentTime;
started = 0;
}
if (watchdogCounter > 45)
{
totalwatchdogCounter++;
Serial.println("watchdog Triggered");
Serial.print("Watchdog Event has occurred. Total number: ");
Serial.println(watchdogCounter / 80);
// Open a "log.txt" for appended writing
File log = SPIFFS.open("/WATCHDOG.TXT", "a");
if (!log)
{
Serial.println("file 'WATCHDOG.TXT' open failed");
}
getDateTime();
log.print("Watchdog Restart: ");
log.print(" ");
log.print(dtStamp);
log.println("");
log.close();
Serial.println("Watchdog Restart " + dtStamp);
WiFi.disconnect();
ESP.restart();
}
watchdogCounter = 0; //Resets the watchdogCount
///////////////////////////////////////////////////// FTP ///////////////////////////////////
for (int x = 1; x < 5000; x++)
{
ftpSrv.handleFTP();
}
///////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////// OTA Support ///////////////////////
if (shouldReboot)
{
Serial.println("Rebooting...");
delay(100);
ESP.restart();
}
/*
static char temp[128];
sprintf(temp, "Seconds since boot: %u", (currentTime - previousTime_2)); // / 1000); //time since boot
events.send(temp, "time"); //send event "time"
previousTime_2 = currentTime;
*/
//////////////////// End OTA Support /////////////////////////
// each second read and reset pulseCount_ISR
//if (millis() - lastSecond >= 1000)
/*
if ( currentTime - previousTime_3 >= eventTime_3) { //rain gauge interupt
lastSecond += 1000;
portENTER_CRITICAL(&mux);
currentPulseCount += pulseCount_ISR; // add to current counter
pulseCount_ISR = 0; // reset ISR counter
rainFall = currentPulseCount * .047; //Amout of rain in one bucket dump.
portEXIT_CRITICAL(&mux);
previousTime_3 = currentTime;
}
*/
for (int x = 1; x < 5000; x++)
{
ftpSrv.handleFTP();
}
getDateTime();
//Executes 15 Minute routine and one Five Minute Rountine.
if ((MINUTE % 5 == 0) && (SECOND == 0))
{
delay(1000);
Serial.println("");
Serial.println("Five minute routine");
Serial.println(dtStamp);
delayTime = 1000;
temp = incomingReadings.temp;
heatid = incomingReadings.heat;
humd = incomingReadings.hum;
dewpt = incomingReadings.dew;
baropress = incomingReadings.press;
lastUpdate = dtStamp; //store dtstamp for use on dynamic web page
updateDifference(); //Get Barometric Pressure difference
logtoSD(); //Output to LittleFS --Log to LittleFS on 15 minute interval.
delay(10); //Be sure there is enough LittleFS write time
pastPressure = baropress; //Store last 15 MINUTE, baropress
webInterface();
speak();
}
if ((HOUR == 23) && (MINUTE == 58) && (SECOND == 58)) //24 Hour; clear ALL.
{
fileStore();
}
}
String processor1(const String& var)
{
//index1.h
if (var == F("LASTUPDATE"))
return lastUpdate;
if (var == F("TEMP"))
return String(temp, 1);
if (var == F("HEAT"))
return String(heatid, 1);
if (var == F("HUM"))
return String(humd);
if (var == F("DEW"))
return String(dewpt);
if (var == F("PRESSURE"))
return String((baropress), 3);
if (var == F("DIF"))
return String((difference), 3);
if (var == F("RAINDAY"))
return String(rainDay);
if (var == F("RAINHOUR"))
return String(rainHour);
if (var == F("RAINFALL"))
return String(rainFall);
if (var == F("DTSTAMP"))
return dtStamp;
if (var == F("LINK"))
return linkAddress;
if (var == F("CLIENTIP"))
return ipREMOTE.toString().c_str();
return String();
}
String processor2(const String& var)
{
//index2.h
String str;
File root = SPIFFS.open("/");
File file = root.openNextFile();
while (file)
{
if (strncmp(file.name(), "/LOG", 4) == 0)
{
str += "<a href=\"";
str += file.name();
str += "\">";
str += file.name();
str += "</a>";
str += " ";
str += file.size();
str += "<br>\r\n";
}
file = root.openNextFile();
}
root.close();
root.rewindDirectory();
if (var == F("URLLINK"))
return str;
if (var == F("LINK"))
return linkAddress;
if (var == F("FILENAME"))
return file.name();
return String();
}
String processor3(const String& var)
{
//index3.h
if (var == F("LINK"))
return linkAddress;
return String();
}
String processor4(const String& var)
{
//index4.h
if (var == F("LINK"))
return linkAddress;
return String();
}
void accessLog()
{
digitalWrite(online, HIGH); //turn on online LED indicator
getDateTime();
String ip1String = "10.0.0.146"; //Host ip address
String ip2String = ipREMOTE.toString().c_str(); //client remote IP address
Serial.println("");
Serial.println("Client connected: " + dtStamp);
Serial.print("Client IP: ");
Serial.println(ip2String);
Serial.print("Path: ");
Serial.println(PATH);
Serial.println(F("Processing request"));
//Open a "access.txt" for appended writing. Client access ip address logged.
File logFile = SPIFFS.open(Restricted, FILE_APPEND);
if (!logFile)
{
Serial.println("File 'ACCESS.TXT'failed to open");
}
if ((ip1String == ip2String) || (ip2String == "0.0.0.0") || (ip2String == "(IP unset)"))
{
//Serial.println("HOST IP Address match");
logFile.close();
}
else
{
Serial.println("Log client ip address");
logFile.print("Accessed: ");
logFile.print(dtStamp);
logFile.print(" -- Client IP: ");
logFile.print(ip2String);
logFile.print(" -- ");
logFile.print("Path: ");
logFile.print(PATH);
logFile.println("");
logFile.close();
}
}
void beep(unsigned char delayms)
{
// wait for a delayms ms
// digitalWrite(sonalert, HIGH);
// delayTime = 3000;
// digitalWrite(sonalert, LOW);
}
//----------------------------- EEPROM -------------- Muhammad Haroon --------------------------------
void eepromSet(String name, String value)
{
Serial.println("eepromSet");
String list = eepromDelete(name);
String nameValue = "&" + name + "=" + value;
//Serial.println(list);
//Serial.println(nameValue);
list += nameValue;
for (int i = 0; i < list.length(); ++i)
{
EEPROM.write(i, list.charAt(i));
}
EEPROM.commit();
Serial.print(name);
Serial.print(":");
Serial.println(value);
delayTime = 1000;
}
String eepromDelete(String name)
{
Serial.println("eepromDelete");
int nameOfValue;
String currentName = "";
String currentValue = "";
int foundIt = 0;
char letter;
String newList = "";
for (int i = 0; i < 512; ++i)
{
letter = char(EEPROM.read(i));
if (letter == '\n')
{
if (foundIt == 1)
{
}
else if (newList.length() > 0)
{
newList += "=" + currentValue;
}
break;
}
else if (letter == '&')
{
nameOfValue = 0;
currentName = "";
if (foundIt == 1)
{
foundIt = 0;
}
else if (newList.length() > 0)
{
newList += "=" + currentValue;
}
}
else if (letter == '=')
{
if (currentName == name)
{
foundIt = 1;
}
else
{
foundIt = 0;
newList += "&" + currentName;
}
nameOfValue = 1;
currentValue = "";
}
else
{
if (nameOfValue == 0)
{
currentName += letter;
}
else
{
currentValue += letter;
}
}
}
for (int i = 0; i < 512; ++i)
{
EEPROM.write(i, '\n');
}
EEPROM.commit();
for (int i = 0; i < newList.length(); ++i)
{
EEPROM.write(i, newList.charAt(i));
}
EEPROM.commit();
Serial.println(name);
Serial.println(newList);
return newList;
}
void eepromClear()
{
Serial.println("eepromClear");
for (int i = 0; i < 512; ++i)
{
EEPROM.write(i, '\n');
}
}
String eepromList()
{
Serial.println("eepromList");
char letter;
String list = "";
for (int i = 0; i < 512; ++i)
{
letter = char(EEPROM.read(i));
if (letter == '\n')
{
break;
}
else
{
list += letter;
}
}
Serial.println(list);
return list;
}
String eepromGet(String name)
{
Serial.println("eepromGet");
int nameOfValue;
String currentName = "";
String currentValue = "";
int foundIt = 0;
String value = "";
char letter;
for (int i = 0; i < 512; ++i)
{
letter = char(EEPROM.read(i));
if (letter == '\n')
{
if (foundIt == 1)
{
value = currentValue;
}
break;
}
else if (letter == '&')
{
nameOfValue = 0;
currentName = "";
if (foundIt == 1)
{
value = currentValue;
break;
}
}
else if (letter == '=')
{
if (currentName == name)
{
foundIt = 1;
}
else
{
}
nameOfValue = 1;
currentValue = "";
}
else
{
if (nameOfValue == 0)
{
currentName += letter;
}
else
{
currentValue += letter;
}
}
}
Serial.print(name);
Serial.print(":");
Serial.println(value);
return value;
}
void seteeprom()
{
eepromstring = String(rainDay, 2);
eepromSet("rainDay", eepromstring);
rain5min = 0;
eepromstring = String(rainHour, 2);
eepromSet("rainHour", eepromstring);
eepromstring = String(rain5min, 2);
eepromSet("rain5min", eepromstring);
//END - STORE RAINCOUNT IN EEPROM
}
//------------------------------- end EEPROM --------- Muhammad Haroon -------------------------------------
void end()
{
delay(1000);
digitalWrite(online, LOW); //turn-off online LED indicator
getDateTime();
Serial.println("Client closed: " + dtStamp);
}
void fileStore() //If Midnight, rename "LOGXXYYZZ.TXT" to ("log" + month + day + ".txt") and create new, empty "LOGXXYYZZ.TXT"
{
int temp;
String Date;
String Month;
temp = (DATE);
if (temp < 10)
{
Date = ("0" + (String)temp);
}
else
{
Date = (String)temp;
}
temp = (MONTH);
if (temp < 10)
{
Month = ("0" + (String)temp);
}
else
{
Month = (String)temp;
}
String logname; //file format /LOGxxyyzzzz.txt
logname = "/LOG";
logname += Date; ////logname += Clock.getMonth(Century);
logname += Month; ///logname += Clock.getDate();
logname += YEAR;
logname += ".TXT";
//Open file for appended writing
File log = SPIFFS.open(logname.c_str(), "a");
if (!log)
{
Serial.println("file open failed");
}
}
//////////////////////////////////
//Get Date and Time
//////////////////////////////////
String getDateTime()
{
struct tm *ti;
tnow = time(nullptr) + 1;
//strftime(strftime_buf, sizeof(strftime_buf), "%c", localtime(&tnow));
ti = localtime(&tnow);
DOW = ti->tm_wday;
YEAR = ti->tm_year + 1900;
MONTH = ti->tm_mon + 1;
DATE = ti->tm_mday;
HOUR = ti->tm_hour;
MINUTE = ti->tm_min;
SECOND = ti->tm_sec;
strftime(strftime_buf, sizeof(strftime_buf), "%a , %m/%d/%Y , %H:%M:%S %Z", localtime(&tnow));
dtStamp = strftime_buf;
return (dtStamp);
}
//////////////////////////////////////////////////////
//Pressure difference for fifthteen minute interval
/////////////////////////////////////////////////////
float updateDifference() //Pressure difference for fifthteen minute interval
{
//Function to find difference in Barometric Pressure
//First loop pass pastPressure and currentPressure are equal resulting in an incorrect difference result. Output "...Processing"
//Future loop passes difference results are correct
difference = baropress - pastPressure; //This will be pressure from this pass thru loop, pressure1 will be new pressure reading next loop pass
if (difference == baropress)
{
difference = 0;
}
return (difference); //Barometric pressure change in inches of Mecury
}
////////////////////
//Write to SPIFSS
///////////////////
void logtoSD() //Output to SPIFFS every fifthteen minutes
{
getDateTime();
int tempy;
String Date;
String Month;
tempy = (DATE);
if (tempy < 10)
{
Date = ("0" + (String)tempy);
}
else
{
Date = (String)tempy;
}
tempy = (MONTH);
if (tempy < 10)
{
Month = ("0" + (String)tempy);
}
else
{
Month = (String)tempy;
}
String logname;
logname = "/LOG";
logname += Date; ////logname += Clock.getMonth(Century);
logname += Month; ///logname += Clock.getDate();
logname += YEAR;
logname += ".TXT";
// Open a "log.txt" for appended writing
//File log = SPIFFS.open(logname.c_str(), FILE_APPEND);
File log = SPIFFS.open(logname.c_str(), FILE_APPEND);
if (!log)
{
Serial.println("file 'LOG.TXT' open failed");
}
delay(500);
//log.print(id);
//log.print(" , ");
log.print(lastUpdate);
log.print(" , ");
log.print("Hum: ");
log.print(humd, 1);
log.print(" % , ");
log.print("Temp: ");
log.print(temp, 1);
log.print(" F. , ");
log.print("Press: ");
log.print(baropress, 3);
log.print(" inHg. ");
log.print(" , ");
if (pastPressure == baropress)
{
log.print("0.000");
log.print(" Difference ");
log.print(" ,");
}
else
{
log.print((difference), 3);
log.print(" Difference ");
log.print(", ");
}
log.print(" Day ");
log.print(rainDay, 2);
log.print(" ,");
log.print(" Hour ");
log.print(rainHour, 2);
log.print(" , ");
log.print(" Five Minute ");
log.print(rain5min, 2);
log.print(" , ");
log.println();
//Increment Record ID number
//id++;
Serial.println("");
Serial.println("Data written to " + logname + " " + dtStamp);
log.close();
pastPressure = baropress;
if (abs(difference) >= .020) //After testing and observations of Data; raised from .010 to .020 inches of Mecury
{
// Open a "Differ.txt" for appended writing --records Barometric Pressure change difference and time stamps
File diffFile = SPIFFS.open("DIFFER.TXT", FILE_APPEND);
if (!diffFile)
{
Serial.println("file 'DIFFER.TXT' open failed");
}
Serial.println("");
Serial.print("Difference greater than .020 inches of Mecury , ");
Serial.print(difference, 3);
Serial.print(" ,");
Serial.print(dtStamp);
diffFile.println("");
diffFile.print("Difference greater than .020 inches of Mecury, ");
diffFile.print(difference, 3);
diffFile.print(" ,");
diffFile.print(dtStamp);
diffFile.close();
beep(50); //Duration of Sonalert tone
}
}
////////////////////////////////////////////////////////////////
//readFile --AsyncWebServer version with much help from Pavel
////////////////////////////////////////////////////////////////
void notFound(AsyncWebServerRequest *request)
{
digitalWrite(online, HIGH); //turn-on online LED indicator
if (! request->url().endsWith(F(".TXT")))
{
request->send(404);
}
else
{
if (request->url().endsWith(F(".TXT")))
{
//.endsWith(F(".txt")))
// here comes some mambo-jambo to extract the filename from request->url()
int fnsstart = request->url().lastIndexOf('/');
fn = request->url().substring(fnsstart);
PATH = fn;
accessLog();
Serial.print("File: ");
Serial.println(fn);
File webFile = SPIFFS.open(fn);
Serial.print("File size: ");
Serial.println(webFile.size());
if (!webFile)
{
Serial.println("File: " + fn + " failed to open");
Serial.println("\n");
}
else if (webFile.size() == 0)
{
webFile.close();
}
else
{
//request->send(SPIFFS, fn, String(), true); //Download file
request->send(SPIFFS, fn, String(), false); //Display file
webFile.close();
}
fn = "";
end();
}
}
digitalWrite(online, LOW); //turn-off online LED indicator
}
////////////////////////////////////////
//ThingSpeak.com --Graphing and iftmes
///////////////////////////////////////
void speak()
{
char t_buffered1[14];
dtostrf(temp, 7, 1, t_buffered1);
char t_buffered2[14];
dtostrf(humd, 7, 1, t_buffered2);
char t_buffered3[14];
dtostrf(baropress, 7, 1, t_buffered3);
char t_buffered4[14];
dtostrf(dewpt, 7, 1, t_buffered4);
// Write to ThingSpeak. There are up to 8 fields in a channel, allowing you to store up to 8 different
// pieces of information in a channel. Here, we write to field 1.
// Write to ThingSpeak. There are up to 8 fields in a channel, allowing you to store up to 8 different
// pieces of information in a channel. Here, we write to field 1.
ThingSpeak.setField(1, t_buffered1); //Temperature
ThingSpeak.setField(2, t_buffered2); //Humidity
ThingSpeak.setField(3, t_buffered3); //Barometric Pressure
ThingSpeak.setField(4, t_buffered4); //Dew Point F.
// Write the fields that you've set all at once.
//ThingSpeak.writeFields(myChannelNumber, myWriteAPIKey);
getDateTime();
Serial.println("");
Serial.println("Sent data to Thingspeak.com " + dtStamp + "\n");
}
//////////////////////////////////////////////////////////////////////////////////
//Hosted Domain, web page -code sends data for Dynamic web page every 15 Minutes
/////////////////////////////////////////////////////////////////////////////////
void webInterface()
{
/*
char fahr[7];// Buffer big enough for 9-character float
dtostrf(incomingReadings.temp, 6, 1, fahr); // Leave room for too large numbers!
char heatindex[7];// Buffer big enough for 9-character float
dtostrf(incomingReadings.heat, 6, 1,heatindex); // Leave room for too large numbers!
char humidity[7]; // Buffer big enough for 9-character float
dtostrf(incomingReadings.hum, 6, 1, humidity); // Leave room for too large numbers!
char dewpoint[7]; // Buffer big enough for 9-character float
dtostrf(incomingReadings.dew, 6, 1, dewpoint); // Leave room for too large numbers!
char barometric[9]; // Buffer big enough for 7-character float
dtostrf(incomingReadings.press, 8, 3, barometric); // Leave room for too large numbers!
char diff[9]; // Buffer big enough for 7-character float
dtostrf(difference, 8, 3, diff); // Leave room for too large numbers!
char rain5[10]; // Buffer big enough for 9-character float
dtostrf(rain5min, 6, 3, rain5); // Leave room for too large numbers!
char rain60[10]; // Buffer big enough for 9-character float
dtostrf(rainHour, 6, 3, rain60); // Leave room for too large numbers!
char rain24[10]; // Buffer big enough for 9-character float
dtostrf(rainDay, 6, 3, rain24); // Leave room for too large numbers!
*/
char fahr[7];// Buffer big enough for 9-character float
dtostrf(temp, 6, 1, fahr); // Leave room for too large numbers!
char heatindex[7];// Buffer big enough for 9-character float
dtostrf(heatid, 6, 1,heatindex); // Leave room for too large numbers!
char humidity[7]; // Buffer big enough for 9-character float
dtostrf(humd, 6, 1, humidity); // Leave room for too large numbers!
char dewpoint[7]; // Buffer big enough for 9-character float
dtostrf(dewpt, 6, 1, dewpoint); // Leave room for too large numbers!
char barometric[9]; // Buffer big enough for 7-character float
dtostrf(baropress, 8, 3, barometric); // Leave room for too large numbers!
char diff[9]; // Buffer big enough for 7-character float
dtostrf(difference, 8, 3, diff); // Leave room for too large numbers!
char rain5[10]; // Buffer big enough for 9-character float
dtostrf(rain5min, 6, 3, rain5); // Leave room for too large numbers!
char rain60[10]; // Buffer big enough for 9-character float
dtostrf(rainHour, 6, 3, rain60); // Leave room for too large numbers!
char rain24[10]; // Buffer big enough for 9-character float
dtostrf(rainDay, 6, 3, rain24); // Leave room for too large numbers!
String data = "&last=" + (String)lastUpdate
+ "&fahr=" + fahr
+ "&heatindex=" + heatindex
+ "&humidity=" + humidity
+ "&dewpoint=" + dewpoint
+ "&barometric=" + barometric
+ "&diff=" + diff
+ "&rain5=" + rain5
+ "&rain60=" + rain60
+ "&rain24=" + rain24;
if (WiFi.status() == WL_CONNECTED)
{
//Check WiFi connection status
HTTPClient http; //Declare object of class HTTPClient
http.begin(sendData); //Specify request destination
http.addHeader("Content-Type", "application/x-www-form-urlencoded"); //Specify content-type header
int httpCode = http.POST(data); //Send the request
String payload = http.getString(); //Get the response payload
if (httpCode == 200)
{
Serial.print("");
Serial.print("HttpCode: ");
Serial.print(httpCode); //Print HTTP return code
Serial.print(" Data echoed back from Hosted website " );
Serial.println("");
Serial.println(payload); //Print payload response
http.end(); //Close HTTPClient http
}
else
{
Serial.print("");
Serial.print("HttpCode: ");
Serial.print(httpCode); //Print HTTP return code
Serial.print(" Domain website data update failed. ");
Serial.println("");
http.end(); //Close HTTPClient http
}
}
else
{
Serial.println("Error in WiFi connection");
// listen();
}
}
/////////////////////////////////////
//wiFi Start-up and connection code
////////////////////////////////////
void wifi_Start()
{
//WiFi.disconnect();
//delayTime = (10 * 1000);
//WiFi.mode(WIFI_OFF);
WiFi.mode(WIFI_AP_STA);
Serial.println();
Serial.print("MAC: ");
Serial.println(WiFi.macAddress());
// We start by connecting to WiFi Station
Serial.print("Connecting to ");
Serial.println(ssidStation);
WiFi.begin(ssidStation, passwordStation);
delay(1000);
//setting the static addresses in function "wifi_Start
IPAddress ip;
IPAddress gateway;
IPAddress subnet;
IPAddress dns;
WiFi.config(ip, gateway, subnet, dns);
WiFi.begin(ssidStation, passwordStation);
Serial.println("Web server running. Waiting for the ESP32 IP...");
// Printing the ESP IP address
Serial.print("Server IP: ");
Serial.println(WiFi.localIP());
Serial.print("Port: ");
Serial.println(LISTEN_PORT);
Serial.print("Wi-Fi Channel: ");
Serial.println(WiFi.channel());
Serial.println("\n");
delayTime = 500;
WiFi.waitForConnectResult();
serverAsync.begin();
// Set device as an access point
WiFi.softAP(ssidAP, passwordAP, CHAN_AP, false);
// Init ESP-NOW
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
// Once ESPNow is successfully Init, we will register for recv CB to
// get recv packer info
esp_now_register_recv_cb(OnDataRecv);
Serial.printf("Connection result: %d\n", WiFi.waitForConnectResult());
if (WiFi.waitForConnectResult() != 3)
{
delay(3000);
wifi_Start();
}
}
@Tech500
Copy link
Author

Tech500 commented Feb 5, 2022

Log file and webserver are working correctly. Dashboard has two issues; repeating temperature and humidity reading values and no reading id.
Dashboard

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment