|
#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 <[email protected]> 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); |
|
} |
|
} |
|
} |
|
} |