Skip to content

Instantly share code, notes, and snippets.

Revisions

  1. wdmtech created this gist Oct 7, 2020.
    366 changes: 366 additions & 0 deletions Configurable WiFi Station that polls a URL.cpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,366 @@
    // Import required libraries
    #include <Arduino.h>
    #include <ESP8266WiFi.h>
    #include <ESP8266HTTPClient.h>

    #include <Hash.h>
    #include <ESPAsyncTCP.h>
    #include <ESPAsyncWebServer.h>
    #include <EEPROM.h>

    HTTPClient client;

    // Set up password-less WiFi hotspot
    const char *ap_ssid = "CheerBird";
    const char *ap_password = "";

    // WiFi connection credentials will be set by the user
    String sta_ssid = "";
    String sta_password = "";

    // Write string to flash storage
    void EEPROM_ESP8266_WRITE(String buffer, int N)
    {
    EEPROM.begin(512);
    delay(10);
    for (int L = 0; L < 32; ++L)
    {
    EEPROM.write(N + L, buffer[L]);
    }
    EEPROM.commit();
    }

    // Read string from flash storage
    String EEPROM_ESP8266_READ(int min, int max)
    {
    EEPROM.begin(512);
    delay(10);
    String buffer;
    for (int L = min; L < max; ++L)
    if (isAlphaNumeric(EEPROM.read(L)))
    buffer += char(EEPROM.read(L));
    return buffer;
    }

    // Clear flash storage
    void EEPROM_ESP8266_CLEAR()
    {
    EEPROM.begin(512);
    // Write a 0 to all 512 bytes of the EEPROM
    for (int i = 0; i < 512; i++)
    {
    EEPROM.write(i, 0);
    }
    EEPROM.end();
    }

    // Pin for flash reset button
    const int resetFlashInterruptPin = D4; //D4 (gpio4)
    int resetFlashInterruptPinState = 0;

    //variable interruptCounter will be used in the interrupt service routine. Thus, it needs to be declared as "volatile"
    volatile boolean interruptFlag = 0;

    // Create AsyncWebServer object on port 80
    AsyncWebServer server(80);

    const char index_html[] PROGMEM = R"rawliteral(
    <!DOCTYPE HTML><html lang="en-US">
    <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🐦</text></svg>">
    <style>
    html {
    font-family: Arial;
    display: inline-block;
    margin: 0px auto;
    text-align: center;
    }
    h1 { font-size: 9.0rem; }
    p { font-size: 1.0rem; }
    .units { font-size: 1.2rem; }
    .dht-labels{
    font-size: 1.5rem;
    vertical-align:middle;
    padding-bottom: 15px;
    }
    </style>
    </head>
    <body>
    <h1 id="title" style="color: %COLOR%; text-shadow: 0px 0px 13px rgba(150, 150, 150, 1);">CheerBird 🐦</h1>
    <h2 id="connectivity"></h2>
    <form action="/save" id="saveCredentialsForm">
    <label>
    Network name
    <input type="text" id="ssid" name="ssid" maxlength="32" value="">
    </label>
    <label>
    Password
    <input type="password" id="password" name="password" maxlength="32" value="">
    </label>
    <input type="submit" value="Save">
    </form>
    <div>
    <h3>Reset</h3>
    <button onclick="reset(event)">Reset your CheerBird to default settings</button>
    </div>
    </body>
    <script>
    window.addEventListener( "load", function () {
    function sendCredentials() {
    const XHR = new XMLHttpRequest();
    // Bind the FormData object and the form element
    const FD = new FormData( form );
    // Define what happens on successful data submission
    XHR.addEventListener( "load", function(event) {
    console.log( event.target.responseText );
    } );
    // Define what happens in case of error
    XHR.addEventListener( "error", function( event ) {
    console.log( 'Oops! Something went wrong.' );
    } );
    // Set up our request
    XHR.open( "POST", "/save-credentials" );
    // The data sent is what the user provided in the form
    XHR.send( FD );
    }
    // Access the form element...
    const form = document.getElementById( "saveCredentialsForm" );
    // ...and take over its submit event.
    form.addEventListener( "submit", function ( event ) {
    event.preventDefault();
    sendCredentials();
    } );
    } );
    setInterval(function ( ) {
    var xhttp = new XMLHttpRequest();
    xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
    document.getElementById("connectivity").innerHTML = "Connected to " + this.responseText;
    } else {
    document.getElementById("connectivity").innerHTML = "Not connected to WiFi";
    }
    };
    xhttp.open("GET", "/connectivity", true);
    xhttp.send();
    }, 10000 ) ;
    function reset(event) {
    console.log(event)
    var XHR = new XMLHttpRequest();
    XHR.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
    }
    };
    XHR.open("GET", "/reset", true);
    XHR.send();
    }
    </script>
    </html>
    )rawliteral";

    // Replaces placeholders in HTML via %VARNAME%
    String processor(const String &var)
    {
    return String();
    }

    void ICACHE_RAM_ATTR clearEEPROMInterrupt()
    {
    EEPROM_ESP8266_WRITE("", 0);
    EEPROM_ESP8266_WRITE("", 32);
    ESP.restart();
    }

    void setup()
    {
    // Serial port for debugging purposes
    Serial.begin(115200);
    // Serial.setDebugOutput(true);

    WiFi.mode(WIFI_AP_STA);

    pinMode(resetFlashInterruptPin, INPUT_PULLDOWN_16);
    attachInterrupt(digitalPinToInterrupt(resetFlashInterruptPin), clearEEPROMInterrupt, FALLING);

    Serial.println();
    Serial.println("EEPROM");
    Serial.println("ssid: " + EEPROM_ESP8266_READ(0, 32));
    Serial.println("password: " + EEPROM_ESP8266_READ(32, 64));
    Serial.println("/EEPROM");
    Serial.println();

    for (uint8_t t = 4; t > 0; t--)
    {
    Serial.printf("[SETUP] WAIT %d...\n", t);
    Serial.flush();
    delay(1000);
    }

    Serial.print("Setting AP (Access Point)…");

    // Remove the password parameter, if you want the AP (Access Point) to be open
    // WiFi.softAPConfig(Ip, Ip, NMask);
    WiFi.softAP(ap_ssid, ap_password);

    Serial.println("Wait 100 ms for AP_START...");
    delay(100);
    IPAddress softIP = WiFi.softAPIP();

    Serial.print("AP IP address: ");
    Serial.println(softIP);

    // Get credentials from EEPROM if they exist
    if (!EEPROM_ESP8266_READ(0, 32).isEmpty() && !EEPROM_ESP8266_READ(32, 64).isEmpty())
    {
    sta_ssid = EEPROM_ESP8266_READ(0, 32);
    sta_password = EEPROM_ESP8266_READ(32, 64);
    // Forget the saved WiFi credentials, we will use the NEW ones provided by the user
    WiFi.disconnect();
    WiFi.begin(sta_ssid, sta_password);

    Serial.println();
    Serial.print("Connecting to ");
    Serial.println(sta_ssid);

    while (WiFi.waitForConnectResult() != WL_CONNECTED)
    {
    delay(500);
    if (WL_NO_SSID_AVAIL)
    {
    Serial.println("Bad SSID");
    break;
    };
    if (WL_CONNECT_FAILED)
    {
    Serial.println("Bad password");
    break;
    };
    Serial.print(".");
    }

    Serial.println("");
    Serial.print("Connected to WiFi network with IP Address: ");
    Serial.println(WiFi.localIP());
    }

    // Route for root / web page
    server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send_P(200, "text/html", index_html, processor);
    });

    server.on("/save-credentials", HTTP_POST, [](AsyncWebServerRequest *request) {
    // Save WiFi creds
    // request->send_P(200, "text/plain", String(t).c_str());
    int args = request->args();
    for (int i = 0; i < args; i++)
    {
    Serial.printf("ARG[%s]: %s\n", request->argName(i).c_str(), request->arg(i).c_str());
    }

    if (request->hasParam("ssid", true) && request->hasParam("password", true))
    {

    AsyncWebParameter *ssid = request->getParam("ssid", true);
    AsyncWebParameter *password = request->getParam("password", true);

    String ssidString = ssid->value();
    String passwordString = password->value();

    if (!ssidString.isEmpty() && !passwordString.isEmpty())
    {
    EEPROM_ESP8266_WRITE(ssidString, 0); //Primero de 0 al 32, del 32 al 64, etc
    EEPROM_ESP8266_WRITE(passwordString, 32); //SAVE
    request->send_P(200, "text/plain", String("Credentials saved, your CheerBird will restart and connect to the network details you just provided").c_str());
    }
    else
    {
    request->send_P(200, "text/plain", String("Invalid credentials").c_str());
    }
    }
    });

    server.on("/reset", HTTP_GET, [](AsyncWebServerRequest *request) {
    // Reset device to factory settings
    EEPROM_ESP8266_CLEAR();
    request->send_P(200, "text/plain", String("Your CheerBird will now restart").c_str());
    delay(5000);
    ESP.reset();
    });

    server.on("/connectivity", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send_P(200, "text/plain", String(sta_ssid).c_str());
    });

    // Start server
    server.begin();
    }

    void loop()
    {
    // wait for WiFi connection and continuously poll a URL
    if (WiFi.status() == WL_CONNECTED)
    {

    if (displayMode == "mode-cheerlights")
    {

    HTTPClient http;

    Serial.print("[HTTP] begin...\n");
    // this URL just returns a color hex code (see https://cheerlights.com)
    client.begin("http://api.thingspeak.com/channels/1417/field/2/last.txt"); //HTTP

    Serial.print("[HTTP] GET...\n");
    // start connection and send HTTP header
    int httpCode = client.GET();

    // httpCode will be negative on error
    if (httpCode > 0)
    {
    // HTTP header has been send and Server response header has been handled
    Serial.printf("[HTTP] GET... code: %d\n", httpCode);

    // good response
    if (httpCode == HTTP_CODE_OK)
    {
    String payload = client.getString();
    Serial.print("hex: ");
    Serial.println(payload);
    // Do stuff, e.g. change color of RGB LED
    }
    }
    else
    {
    Serial.printf("[HTTP] GET... failed, error: %s\n", client.errorToString(httpCode).c_str());
    }

    client.end();
    }
    }

    delay(15000);
    }