Skip to content

Instantly share code, notes, and snippets.

@gandrewstone
Created March 19, 2022 20:24
Show Gist options
  • Save gandrewstone/f2e087c814b19e9d53221888cf61af5a to your computer and use it in GitHub Desktop.
Save gandrewstone/f2e087c814b19e9d53221888cf61af5a to your computer and use it in GitHub Desktop.
Simple ESP32 web page motor control
#include <Arduino_DebugUtils.h>
#include <EEPROM.h>
#define WIFI 1
const char compileDate[] = __DATE__ " " __TIME__;
/*
Andrew Stone 2021
A simple web server that lets you blink an LED via the web.
This sketch will print the IP address of your WiFi Shield (once connected)
to the Serial monitor. From there, you can open that address in a web browser
to turn on and off the LED on pin 5.
If the IP address of your shield is yourAddress:
http://yourAddress/H turns the LED on
http://yourAddress/L turns it off
This example is written for a network using WPA encryption. For
WEP or WPA, change the Wifi.begin() call accordingly.
Circuit:
WiFi shield attached
LED attached to pin 5
created for arduino 25 Nov 2012
by Tom Igoe
ported for sparkfun esp32
31.01.2017 by Jan Hendrik Berlin
Based on Modifications Copyright (c) 2017-2019 Martin F. Falatic
Based on public domain code created 19 Nov 2016 by Chris Osborn <fozztexx@fozztexx.com> xttp://insentricity.com
*/
/*
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include <WiFi.h>
#include <ESPmDNS.h>
#define COUNT_OF(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x])))))
const int CtrlPort = 80;
const int CtrlMaxClients = 2;
const String ssid = "your net";
const String password = "your pw";
// Change this to configure what the minimum signal strength you are willing to connect to is
const int MinRssi = -72;
const int OnboardLed = 2;
const int MotorControlPin = 1;
const int RedLed = 3;
const int GreenLen = 4;
const int BlueLed = 5;
const int YellowLed = 18;
const int WhiteLed = 19;
#pragma GCC diagnostic pop
WiFiServer server(CtrlPort, CtrlMaxClients);
WiFiClient clients[CtrlMaxClients];
int64_t lastDelay = 0;
typedef struct
{
unsigned int magic;
char boardName[32];
} ConfigData;
ConfigData configData;
const char* BoardName()
{
if (configData.magic != 7228)
return "winder";
else
return configData.boardName;
}
void NoConnectionAnimation()
{
digitalWrite(YellowLed, 1);
vTaskDelay(500 / portTICK_PERIOD_MS);
digitalWrite(YellowLed, 0);
vTaskDelay(500 / portTICK_PERIOD_MS);
}
void someOtherTask(void * parameter)
{
}
void setup()
{
pinMode(MotorControlPin, OUTPUT);
digitalWrite(MotorControlPin, LOW); // Disable
pinMode(YellowLed, OUTPUT);
pinMode(RedLed, OUTPUT);
Serial.begin(115200);
Serial.print("\n\nJust Give Me A Button! | G. Andrew Stone\nBuild ");
Serial.println(compileDate);
EEPROM.begin(sizeof(ConfigData));
ReadEEPROM();
Debug.setDebugLevel(DBG_VERBOSE);
Debug.timestampOn();
Debug.setDebugOutputStream(&Serial);
pinMode(OnboardLed, OUTPUT);
digitalWrite(OnboardLed, HIGH);
dumpSysInfo();
if (configData.magic != 7228)
{
strcpy(configData.boardName, BoardName());
WriteEEPROM();
Debug.print(DBG_INFO, "Overwrote EEPROM with defaults");
}
}
int cmdCount = 0;
bool onboardLedState = 0;
void toggleOnboardLed()
{
onboardLedState = !onboardLedState;
digitalWrite(OnboardLed, onboardLedState);
}
unsigned int parseUint(String& s, int& pos)
{
if (pos < 0) return 0; // return 0 if position is illegal
if (pos >= s.length()) {
pos = -pos; // Too far
return 0;
}
int end = pos;
while (end < s.length() && (s[end] >= '0' && s[end] <= '9')) end++;
String p = s.substring(pos, end + 1);
int ret = p.toInt();
pos = end;
if (pos >= s.length()) pos = -pos; // Too far
return ret;
}
void replyPrefix(WiFiClient& client)
{
// HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
// and a content-type so the client knows what's coming, then a blank line:
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println();
client.println("<html>");
}
void replyWithHome(WiFiClient& client)
{
replyPrefix(client);
client.println("<body><big><big><big><center>\n");
client.print("<a href=\"/redh\">RED ON</a><br><br>");
client.print("<a href=\"/redl\">RED OFF</a><br><br>");
client.print("<a href=\"/mh\">MOTOR ON</a><br><br>");
client.print("<a href=\"/ml\">MOTOR OFF</a><br><br>");
client.println("</big></big></big></center></body>");
client.println("</html>\n");
}
void handleHttpLine(String& line, WiFiClient& client)
{
Debug.print(DBG_INFO, "handle %s", line.c_str());
// Check to see if the client request was "GET /H" or "GET /L":
if (line.startsWith("GET /redh"))
{
Debug.print(DBG_INFO, "high");
digitalWrite(RedLed, HIGH); // GET /H turns the LED on
}
else if (line.startsWith("GET /redl"))
{
Debug.print(DBG_INFO, "low");
digitalWrite(RedLed, LOW); // GET /L turns the LED off
}
if (line.startsWith("GET /mh"))
{
Debug.print(DBG_INFO, "motor high");
digitalWrite(MotorControlPin, HIGH); // GET /H turns the LED on
replyWithHome(client);
}
else if (line.startsWith("GET /ml"))
{
Debug.print(DBG_INFO, "motor low");
digitalWrite(MotorControlPin, LOW); // GET /L turns the LED off
replyWithHome(client);
}
else if (line.startsWith("GET /"))
{
Debug.print(DBG_INFO, "home");
replyWithHome(client);
}
}
int64_t micros64()
{
static int64_t rolls = 0;
static uint32_t lastmicros = 0;
uint32_t n = micros();
//uint32_t p = n;
n += 0xFC000000UL;
//Debug.print(DBG_INFO, "micros64 n:%lu -> %lu lastmicros: %lld", p, n, lastmicros);
if (n < lastmicros)
{
rolls += 0x100000000UL;
// Debug.print(DBG_INFO, "Rolled 0x%llx", rolls);
}
lastmicros = n;
int64_t now = n;
return (now + rolls);
}
unsigned char readWait(WiFiClient& client)
{
uint32_t start = millis();
while (!client.available())
{
if (!client.connected())
{
Debug.print(DBG_INFO, "Client disconnect in readWait()");
return 0;
}
vTaskDelay(1 / portTICK_PERIOD_MS);
}
return client.read();
}
size_t readWait(WiFiClient& client, uint8_t* buf, size_t total)
{
size_t got = 0;
while (got < total)
{
while ((!client.available()) && client.connected())
{
vTaskDelay(1 / portTICK_PERIOD_MS);
}
if (!client.connected())
{
Debug.print(DBG_INFO, "Client disconnect in readWait(...)");
return 0;
}
int amtRead = client.read(buf + got, total - got);
if (amtRead < 0)
{
Debug.print(DBG_INFO, "Read failure in readWait(...)");
client.stop();
return got;
}
got += amtRead;
}
return got;
}
boolean fillNewClient()
{
//Debug.print(DBG_INFO, "look for new client");
WiFiClient c = server.available();
if (c)
{
Debug.print(DBG_INFO, "found new client");
for (int cidx = 0; cidx < CtrlMaxClients; cidx++)
{
if (clients[cidx] && (clients[cidx].fd() == c.fd())) return false; // Not something new
}
for (int cidx = 0; cidx < CtrlMaxClients; cidx++)
{
if (!clients[cidx])
{
IPAddress ip = c.remoteIP();
Debug.print(DBG_INFO, "New client in slot %d fd %d coming from %d.%d.%d.%d", cidx, c.fd(), ip[0], ip[1], ip[2], ip[3] );
clients[cidx] = c;
//clients[cidx].setNoDelay(true);
lastDelay = micros64();
return true;
}
}
}
return false;
}
unsigned long lastFill = 0;
void loop()
{
LoopTask(0);
//vTaskDelay(10000 / portTICK_PERIOD_MS);
}
void LoopTask(void * parameter)
{
while (1)
{
//Debug.print(DBG_INFO, "Loop");
WiFiConnector();
int emptySlots = CtrlMaxClients;
int somethingHappened = 0;
//Debug.print(DBG_INFO, "stats: numFills: %d, last fill: %llu avg fill clock ticks: %llu", fastLEDnumFills, fastLEDlastFillDuration, fastLEDfillDuration/fastLEDnumFills);
for (int cidx = 0; cidx < CtrlMaxClients; cidx++)
{
//Debug.print(DBG_INFO, "client Loop %d", cidx);
if (clients[cidx] && clients[cidx].connected()) // Fill an empty slot with a new client
{
//Debug.print(DBG_INFO, "client is connected");
emptySlots--;
WiFiClient& client = clients[cidx];
String currentLine = ""; // make a String to hold incoming data from the client
if (client.available())
Debug.print(DBG_INFO, "client %d is alive, %s data", client.fd(), client.available() ? "has" : "no");
while (client.available() && client.connected()) // if there's bytes to read from the client,
{
somethingHappened++;
char c = readWait(client); // read a byte, then
Serial.write(c); // print it out the serial monitor
if (c == '\n') // if the byte is a newline character
{
// if the current line is blank, you got two newline characters in a row.
// that's the end of the client HTTP request, so send a response:
if (currentLine.length() == 0)
{
Debug.print(DBG_INFO, "main page");
// HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
// and a content-type so the client knows what's coming, then a blank line:
client.println("HTTP/1.1 200 OK");
client.println("Content-type:text/html");
client.println();
// the content of the HTTP response follows the header:
client.print("Click <a href=\"/H\">here</a> to turn the LED on pin 5 on.<br>");
client.print("Click <a href=\"/L\">here</a> to turn the LED on pin 5 off.<br>");
// The HTTP response ends with another blank line:
client.println();
// break out of the while loop:
break;
}
else // if you got a newline, then clear currentLine:
{
Debug.print(DBG_INFO, currentLine.c_str());
handleHttpLine(currentLine, client);
currentLine = "";
}
}
if (c != '\r') // if you got anything else but a carriage return character,
{
currentLine += c; // add it to the end of the currentLine
}
}
if (!client.connected())
{
Debug.print(DBG_INFO, "Client %d idx %d Disconnected.", client.fd(), cidx);
}
}
}
unsigned long now = millis();
if (somethingHappened == 0) // &&(now > lastFill + MIN_CNXN_FILL_INTERVAL))
{
lastFill = now;
if (fillNewClient()) somethingHappened++;
}
if (!somethingHappened)
{
if (emptySlots == CtrlMaxClients)
{
NoConnectionAnimation();
}
else
{
vTaskDelay(1 / portTICK_PERIOD_MS);
}
}
}
}
void dumpSysInfo()
{
esp_chip_info_t sysinfo;
esp_chip_info(&sysinfo);
Serial.print("ESP32 Model: ");
Serial.print((int)sysinfo.model);
Serial.print("; Features: 0x");
Serial.print((int)sysinfo.features, HEX);
Serial.print("; Cores: ");
Serial.print((int)sysinfo.cores);
Serial.print("; Revision: r");
Serial.println((int)sysinfo.revision);
}
void logWiFis()
{
// Set WiFi to station mode and disconnect from an AP if it was previously connected
WiFi.mode(WIFI_STA);
WiFi.disconnect();
delay(100);
int n = WiFi.scanNetworks();
Serial.println("scan done");
if (n == 0) {
Serial.println("no networks found");
} else {
Serial.print(n);
Serial.println(" networks found");
for (int i = 0; i < n; ++i) {
// Print SSID and RSSI for each network found
Serial.print(i + 1);
Serial.print(": ");
Serial.print(WiFi.SSID(i));
Serial.print(" (");
Serial.print(WiFi.RSSI(i));
Serial.print(")");
Serial.println((WiFi.encryptionType(i) == WIFI_AUTH_OPEN)?" ":"*");
delay(10);
}
}
}
// Prints what WiFis I can hear. Returns the strongest open network, unless the preferred is available and above the minimum.
// DISCONNECTS from WIFI!!!
String AnalyzeWiFis(String preferred = String())
{
// Set WiFi to station mode and disconnect from an AP if it was previously connected
WiFi.mode(WIFI_STA);
WiFi.disconnect();
delay(100);
int n = WiFi.scanNetworks();
Serial.println("scan done");
if (n == 0)
{
Serial.println("no networks found");
}
else
{
Serial.print(n);
Serial.println(" networks found");
int preferredIdx = -1;
int maxRssiIdx = -1;
for (int i = 0; i < n; ++i)
{
if (WiFi.SSID(i) == preferred) preferredIdx = i;
// Find the strongest open network
if (WiFi.encryptionType(i) == WIFI_AUTH_OPEN)
if ((maxRssiIdx == -1) || (WiFi.RSSI(maxRssiIdx) < WiFi.RSSI(i)))
maxRssiIdx = i;
// Print SSID and RSSI for each network found
Serial.print(i);
Serial.print(": ");
Serial.print(WiFi.SSID(i));
Serial.print(" (");
Serial.print(WiFi.RSSI(i));
Serial.print(")");
Serial.println((WiFi.encryptionType(i) == WIFI_AUTH_OPEN) ? " " : "*");
}
// If our preferred is available and strong enough, return it.
if ((preferredIdx >= 0) && WiFi.RSSI(preferredIdx) >= MinRssi) return preferred;
// Otherwise return the strongest open connection
if (maxRssiIdx != -1)
return WiFi.SSID(maxRssiIdx);
}
return String();
}
void WiFiConnector()
{
if (WiFi.status() != WL_CONNECTED)
{
Serial.println(WiFi.status());
Serial.print("I am ");
Serial.println(BoardName());
String connectTo = AnalyzeWiFis(String(ssid));
if (connectTo != "")
{
if ((connectTo == ssid) && (password != "")) WiFi.begin(ssid.c_str(), password.c_str());
else WiFi.begin(connectTo.c_str());
Serial.print("Connecting to ");
Serial.println(connectTo);
size_t tries = 0;
while ((WiFi.status() != WL_CONNECTED) && (tries < 100))
{
digitalWrite(OnboardLed, tries & 1);
delay(100);
Serial.print(".");
if (tries & 127 == 0) Serial.print("\n");
tries++;
}
if (WiFi.status() == WL_CONNECTED)
{
Serial.println("");
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
AdvertiseServices(BoardName());
// server.setNoDelay(true);
server.begin();
}
else
Serial.println("WiFi cannot connect");
}
else
{
Debug.print(DBG_INFO, "No WiFi candidates");
}
}
}
uint16_t GetDeviceId()
{
#if defined(ARDUINO_ARCH_ESP32)
return ESP.getEfuseMac();
#else
return ESP.getChipId();
#endif
}
const void WriteEEPROM()
{
configData.magic = 7228;
unsigned char* ptr = (unsigned char*) &configData;
for (int i = 0; i < sizeof(ConfigData); i++)
{
EEPROM.write(i, *(ptr + i));
}
EEPROM.commit();
}
const void ReadEEPROM()
{
unsigned char* ptr = (unsigned char*) &configData;
for (int i = 0; i < sizeof(ConfigData); i++)
{
*(ptr + i) = EEPROM.read(i);
}
}
void AdvertiseServices(const char *MyName)
{
if (MDNS.begin(MyName))
{
Serial.println(F("mDNS responder started"));
Serial.print(F("I am: "));
Serial.println(MyName);
// Add service to MDNS-SD
MDNS.addService("oshwswftw", "tcp", CtrlPort);
}
else
{
while (1)
{
Serial.println(F("Error setting up MDNS responder"));
delay(1000);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment