/*
Use this example which includes a WEB SERVER (AsyncWebServer - superiour to webserver.h)
Also has OTA, so once you have uploaded it via USB-TTL, you can disconnect it and then do further revisions via OTA.
To upload via OTA, you MUST COMPILE your updated program version:
Sketch> Export Compile Binary (Ctrl+Alt+S)
From the WEB INTERFACE of your functionning device, click update, then fetch the *.ino.bin file in the subdirectory
C:\Users\pauld\OneDrive\Documents\Arduino\esp32-Relay4\build\esp32.esp32.esp32
(Tony's: "My Documents/Arduino/YourProjectName/build/esp32.esp32.esp32/" )
The *.ino.bin should be around 800 kB... there are a few bins in that directory, PICK THE RIGHT ONE ending in *.ino.bin !!!!
IMPORTANT STUFF!!!
UPDATE YOUR SSID, PASSWORD and IP ADDRESS (this app uses a static ip!!)
if you get compile errors due to a missing library, use the IDE library manager to install the missing library.
You *MUST* use these parameters to write to the board (File menu, Tools):
Board: ESP32 Dev Module
Erase All Flash Before Sketch Upload: YES!
Flash Size: 4MB
Partition: Default
Info on the ESP32 4 Channel Relay Board
----------------------------------------
Pin Function
GPIO23 Status LED
GPIO32 Relay #1
GPIO33 Relay #2
GPIO25 Relay #3
GPIO26 Relay #4
It is POSSIBLE that the logic of LOW/HIGH signaling to the LED and RELAYs is REVERSED, if so, just update the code...
*/
// debug and version
const char *swver = "1.4"; //Added rotator stuff
bool debug = true;
// ************************************ libraires - constants and string declarations *********************************
// web stuff
#include <WiFi.h>
#include <AsyncTCP.h>
#include <WiFiClient.h>
#include <ESPAsyncWebServer.h>
#include <Update.h>
const char *ssid = "ssid";
const char *password = "nosir"
// Set your Static IP address (not needed if using DHCP)
/*Tony's
IPAddress local_IP(10, 254, 254, 22);
IPAddress gateway(10, 254, 254, 1);
IPAddress subnet(255, 255, 255, 0);
IPAddress primaryDNS(10, 254, 254, 1);
*/
IPAddress local_IP(10, 1, 1, 156);
IPAddress gateway(10, 1, 1, 1);
IPAddress subnet(255, 255, 255, 0);
IPAddress primaryDNS(4, 4, 4, 4);
AsyncWebServer server(80);
//flag to use from web update to reboot the ESP
bool shouldReboot = false;
//network time
#include <ESP32Time.h>
ESP32Time rtc;
// date and time stuff
String currDate;
String lastDate;
unsigned long currTime = 0;
unsigned long lastWifiCheckTime;
unsigned long lastLogTime;
//needed for OTA and place to keep photos.
#include "FS.h"
#include <LittleFS.h>
#define FORMAT_LITTLEFS_IF_FAILED true
String strDirectoryListing;
//Watchdog
#include <esp_task_wdt.h>
#define WDT_TIMEOUT 90
//for brownout suppression
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
//Relays
#define RELAY1 32
#define RELAY2 33
#define RELAY3 25
#define RELAY4 26
#define LED 23
volatile int relay = 0;//We'll pass one of the RELAY#s here
/*
*
* SETUP ----------------------------------------------------------------------------
*
*/
void setup(void) {
if (debug) {
Serial.begin(115200);
while (!Serial) { delay(500); }
Serial.printf("Software version: %s\n",swver);
}
if(!LittleFS.begin(FORMAT_LITTLEFS_IF_FAILED)){
if (debug) Serial.println("LittleFS Mount Failed");
delay(5000);
}
// get boot reason
String BootReasonString;
int BootReason = esp_reset_reason();
if (BootReason == ESP_RST_UNKNOWN ) BootReasonString = "Reset reason can not be determined.\n";
if (BootReason == ESP_RST_POWERON ) BootReasonString = "Reset due to power-on event.\n";
if (BootReason == ESP_RST_EXT ) BootReasonString = "Reset by external pin (not applicable for ESP32).\n";
if (BootReason == ESP_RST_SW ) BootReasonString = "Software reset via esp_restart.\n";
if (BootReason == ESP_RST_PANIC ) BootReasonString = "Software reset due to exception/panic.\n";
if (BootReason == ESP_RST_INT_WDT ) BootReasonString = "Reset (software or hardware) due to interrupt watchdog.\n";
if (BootReason == ESP_RST_TASK_WDT ) BootReasonString = "Reset due to task watchdog.\n";
if (BootReason == ESP_RST_WDT ) BootReasonString = "Reset due to other watchdogs.\n";
if (BootReason == ESP_RST_DEEPSLEEP ) BootReasonString = "Reset after exiting deep sleep mode.\n";
if (BootReason == ESP_RST_BROWNOUT ) BootReasonString = "Brownout reset (software or hardware).\n";
if (BootReason == ESP_RST_SDIO ) BootReasonString = "Reset over SDIO.\n";
appendFile(LittleFS, "/log.txt", BootReasonString.c_str());
// Relay Control
pinMode(RELAY1, OUTPUT);
digitalWrite(RELAY1, LOW);
pinMode(RELAY2, OUTPUT);
digitalWrite(RELAY2, LOW);
pinMode(RELAY3, OUTPUT);
digitalWrite(RELAY3, LOW);
pinMode(RELAY4, OUTPUT);
digitalWrite(RELAY4, LOW);
//LED CONTROL
pinMode(LED, OUTPUT);
digitalWrite(LED, LOW);
//for brownout suppression
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable detector
// set the watchdog
esp_task_wdt_init(WDT_TIMEOUT, true); //enable panic so ESP32 restarts
esp_task_wdt_add(NULL); //add current thread to WDT watch
/* //Connect using static IP
WiFi.mode(WIFI_STA);
// Configures static IP address
if (!WiFi.config(local_IP, gateway, subnet, primaryDNS)) {
Serial.println("STA Failed to configure");
}
WiFi.begin(ssid, password);
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
}
*/
//Connect with dynamic IP
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
// Print local IP address and start web server
Serial.println("");
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
server.begin();
// set the ESP32 internal RTC to the internet time via NTP
configTime(-5 * 3600, 3600, "pool.ntp.org");
struct tm timeinfo = rtc.getTimeStruct();
if (getLocalTime(&timeinfo)) {
rtc.setTimeStruct(timeinfo);
}
// time stamps initialise , be sure currTime is a valid time!!!
while (currTime < 100000) {
currTime = rtc.getEpoch(); //unix seconds since 1970
delay(1000);
}
lastWifiCheckTime = currTime;
currDate = rtc.getDate();
lastLogTime = millis();
// ************************* WEB SERVER RESPONSES *********************************************
server.on("/", HTTP_GET, handleRoot);
server.on("/getdaylog", HTTP_GET, [](AsyncWebServerRequest *request){
File filehandle = LittleFS.open("/log.txt", "r");
//size_t filesize = file.size();
String fsdata = filehandle.readString();
filehandle.close();
request->send(200, "text/plain", fsdata);
});
//list files on the SD CARD
server.on("/listdir", HTTP_GET, [](AsyncWebServerRequest *request){
strDirectoryListing = "";
listDir(LittleFS, "/", 1);
request->send(200, "text/plain", strDirectoryListing);
strDirectoryListing = "";
});
server.on("/relay1", HTTP_GET, [](AsyncWebServerRequest *request){
togglerelay(RELAY1);
request->send(200, "text/html", "<html><meta http-equiv=\"Refresh\" content=\"0; url='/'\" /></html>");
});
server.on("/relay2", HTTP_GET, [](AsyncWebServerRequest *request){
togglerelay(RELAY2);
request->send(200, "text/html", "<html><meta http-equiv=\"Refresh\" content=\"0; url='/'\" /></html>");
});
server.on("/relay3", HTTP_GET, [](AsyncWebServerRequest *request){
togglerelay(RELAY3);
request->send(200, "text/html", "<html><meta http-equiv=\"Refresh\" content=\"0; url='/'\" /></html>");
});
server.on("/relay4", HTTP_GET, [](AsyncWebServerRequest *request){
togglerelay(RELAY4);
request->send(200, "text/html", "<html><meta http-equiv=\"Refresh\" content=\"0; url='/'\" /></html>");
});
server.on("/led", HTTP_GET, [](AsyncWebServerRequest *request){
if (digitalRead(LED) == LOW) {
digitalWrite(LED, HIGH);
} else {
digitalWrite(LED, LOW);
}
request->send(200, "text/html", "<html><meta http-equiv=\"Refresh\" content=\"0; url='/'\" /></html>");
});
server.on("/allrelaysoff", HTTP_GET, [](AsyncWebServerRequest *request){
allrelaysoff();
request->send(200, "text/html", "<html><meta http-equiv=\"Refresh\" content=\"0; url='/'\" /></html>");
});
// ************************* Rotator Experiments BEGIN *********************************************
server.on("/clockwise", HTTP_GET, [](AsyncWebServerRequest *request){
//Make sure the other direction is off
if (digitalRead(RELAY2) == HIGH) {
togglerelay(RELAY2);
togglerelay(RELAY3);
delay(1000);
}
//Toggle Relay 1 before Relay 3
togglerelay(RELAY1);
togglerelay(RELAY3);
request->send(200, "text/html", "<html><meta http-equiv=\"Refresh\" content=\"0; url='/'\" /></html>");
});
server.on("/countrclockwise", HTTP_GET, [](AsyncWebServerRequest *request){
//Make sure the other direction is off
if (digitalRead(RELAY1) == HIGH) {
togglerelay(RELAY1);
togglerelay(RELAY3);
delay(1000);
}
//Toggle the direction Relay before the power Relay
togglerelay(RELAY2);
togglerelay(RELAY3);
request->send(200, "text/html", "<html><meta http-equiv=\"Refresh\" content=\"0; url='/'\" /></html>");
});
// ************************* Rotator Experiments END *********************************************
server.on("/reset", HTTP_GET, [](AsyncWebServerRequest *request){
appendFile(LittleFS, "/log.txt", "User initiated a restart.\n");
request->send(200, "text/html", "<html><meta http-equiv=\"Refresh\" content=\"10; url='/'\" /><body>Device rebooted, refreshing in 10 seconds.</body></html>");
ESP.restart();
});
// Simple Firmware Update Form
server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request){
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>");
});
server.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);
}
}
if(final){
if(Update.end(true)){
Serial.printf("Update Success: %uB\n", index+len);
} else {
Update.printError(Serial);
}
}
});
server.onNotFound(notFound);
server.begin();
} // end of setup
/*
*
* MAIN LOOP ----------------------------------------------------------------------------
*
*/
void loop(void) {
if(shouldReboot){
Serial.println("Rebooting...");
delay(2000);
ESP.restart();
}
currDate = rtc.getTime();
esp_task_wdt_reset();
// check on wifi
currTime = rtc.getEpoch();
if (lastWifiCheckTime - currTime > 30 ) {
// ok so we are at 30 seconds, so check wifi...
if ((WiFi.status() != WL_CONNECTED) ) {
appendFile(LittleFS, "/log.txt", "WiFi Lost..rebooting in 15 seconds.\n");
delay(15*1000);
ESP.restart();
}
lastWifiCheckTime = rtc.getEpoch();
}
} // end of main loop
//*********************************************************************************************************
//
// WEB URL ROUTINES
//
//*********************************************************************************************************
void handleRoot(AsyncWebServerRequest *request) {
//void handleRoot() {
char tempwebpage[2000];
int sec = millis() / 1000;
int min = sec / 60;
int hr = min / 60;
String sLed;
if (digitalRead(LED) == LOW) {
sLed = "OFF";
} else {
sLed = "ON";
}
String sRelay1;
if (digitalRead(RELAY1) == LOW) {
sRelay1 = "OPEN";
} else {
sRelay1 = "CLOSED";
}
String sRelay2;
if (digitalRead(RELAY2) == LOW) {
sRelay2 = "OPEN";
} else {
sRelay2 = "CLOSED";
}
String sRelay3;
if (digitalRead(RELAY3) == LOW) {
sRelay3 = "OPEN";
} else {
sRelay3 = "CLOSED";
}
String sRelay4;
if (digitalRead(RELAY4) == LOW) {
sRelay4 = "OPEN";
} else {
sRelay4 = "CLOSED";
}
String sClockwise;
if (digitalRead(RELAY1) == LOW) {
sClockwise = "OPEN";
} else {
sClockwise = "CLOSED";
}
String sCounterClockwise;
if (digitalRead(RELAY1) == LOW) {
sCounterClockwise = "OPEN";
} else {
sCounterClockwise = "CLOSED";
}
snprintf(tempwebpage, 2000,
"<html>\
<head>\
// <meta name='HandheldFriendly' content='True'>\
<meta name='viewport' content='width=device-width, initial-scale=1.0, user-scalable=yes'>\
// <meta http-equiv='refresh' content='5'/>\
<link rel="icon" href="data:,">\
<title>ESP32 Rotator</title>\
<style>\
body {background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; font-size: medium; Color: #000088;}\
button {height:50px; width:90px; color: blue; font-family: verdana;}// font-size: 100%;\
bigbutton {height:100px; width:200px; color: blue; font-family: verdana;}// font-size: 100%;\
</style>\
</head>\
<body>\
<h3>RELAY X4 - ver: %s</h3>\
<p>Uptime: %02d:%02d:%02d<br>\
Now: %04d-%02d-%02d %02d:%02d<br>\
CPU Core Temperature: %02.1f C<br><br>\
LED: %s<br>\
Relay 1: %s<br>\
Relay 2: %s<br>\
Relay 3: %s<br>\
Relay 4: %s<br><br>\
Clockwise: %s<br><br>\
CounterClockwise: %s<br><br>\
<button onclick=\"document.location='/relay1'\">Toggle Relay 1</button>\
<button onclick=\"document.location='/relay2'\">Toggle Relay 2</button>\
<button onclick=\"document.location='/relay3'\">Toggle Relay 3</button>\
<button onclick=\"document.location='/relay4'\">Toggle Relay 4</button><br>\
<button onclick=\"document.location='/clockwise'\" class='bigbutton'>Toggle Clockwise</button>\
<button onclick=\"document.location='/countrclockwise'\" class='bigbutton'>Toggle Counter-Clockwise</button><br>\
<button onclick=\"document.location='/led'\">Toggle LED</button>\
<button onclick=\"document.location='/allrelaysoff'\">All OFF</button><br>\
<button onclick=\"document.location='/getdaylog'\">Display Log</button>\
<button onclick=\"document.location='/listdir'\">List logs</button><br>\
<button onclick=\"document.location='/update'\">Firmware upgrade</button>\
<button onclick=\"document.location='/reset'\">Reset</button><br>\
</body></html>", swver, hr, min % 60, sec % 60, rtc.getYear(), rtc.getMonth() + 1, rtc.getDay() ,rtc.getHour(true), rtc.getMinute(),
temperatureRead(), sLed, sRelay1, sRelay2, sRelay3, sRelay4, sClockwise,sCounterClockwise);
//class="bigbutton"
// style="height:200px;width:200px"
request->send(200, "text/html", tempwebpage);
}
void notFound(AsyncWebServerRequest *request) {
request->send(404, "text/plain", "Not found");
}
//*********************************************************************************************************
//
// LittleFS subroutines
//
//*********************************************************************************************************
void appendFile(fs::FS &fs, const char * path, const char * message){
if (debug) Serial.printf("Appending to file: %s\r\n", path);
File file = fs.open(path, FILE_APPEND);
file.print(message);
file.close();
}
void renameFile(fs::FS &fs, const char * path1, const char * path2){
fs.rename(path1, path2);
}
void deleteFile(fs::FS &fs, const char * path){
fs.remove(path);
}
void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
//strDirectoryListing = "";
//Serial.printf("Listing directory: %s\r\n", dirname);
strDirectoryListing += "Listing directory: ";
strDirectoryListing += dirname;
strDirectoryListing += "\n";
File root = fs.open(dirname);
if(!root){
//Serial.println("- failed to open directory");
strDirectoryListing += "- failed to open directory\n";
return;
}
if(!root.isDirectory()){
//Serial.println(" - not a directory");
strDirectoryListing += " - not a directory\n";
return;
}
File file = root.openNextFile();
while(file){
if(file.isDirectory()){
//Serial.print(" DIR : ");
strDirectoryListing += " DIR : ";
//Serial.println(file.name());
strDirectoryListing += file.name();
strDirectoryListing += "\n";
if(levels){
listDir(fs, file.path(), levels -1);
}
} else {
//Serial.print(" FILE: ");
strDirectoryListing += " FILE: ";
//Serial.print(file.name());
strDirectoryListing += file.name();
strDirectoryListing += "\n";
//Serial.print("\tSIZE: ");
strDirectoryListing += "\tSIZE: ";
//Serial.println(file.size());
strDirectoryListing += file.size();
strDirectoryListing += "\n";
}
file = root.openNextFile();
}
}