/*
 * ESP8266 Config Portal — E02
 * ESP32 & ESP8266 Connectivity
 *
 * Board   : ESP8266 (NodeMCU v3 / Wemos D1 mini)
 * MCU     : ESP8266EX @ 80 MHz, 3.3 V logic
 * Compile : arduino-cli compile --fqbn esp8266:esp8266:nodemcuv2
 *
 * Pin map (also in the wiring notes and on the project page):
 *   STATUS_LED         -> 5
 *   BUTTON             -> 12
 *
 * Only A0 is analog, and it accepts 3.3 V maximum — add a divider for a 5 V sensor.
 *
 * Project page : https://codeproven.pages.dev/project/E02/
 * Wiring       : https://codeproven.pages.dev/project-art/E02-connections.svg
 * Level        : Intermediate
 * 
 * An ESP8266 that hosts its own setup page, stores network credentials, and reports its address once connected.
 * 
 * Safety       : Only A0 is analog and it accepts 3.3 V — do not feed it 5 V. The board draws up to 300 mA briefly on transmit.
 *
 * This sketch is the one the project page offers for download, and it is
 * compile-checked for esp8266:esp8266:nodemcuv2 before publication. It is bench code: read it,
 * change the constants for your own hardware, and test with the load
 * disconnected first.
 */

#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <DNSServer.h>
#include <ESP8266mDNS.h>

/* ------------------------------------------------------------ pin map -----
   Written by the generator from the board's own pin pools. The same numbers
   are in the connection diagram and the project page. */
#define PIN_STATUS_LED       5
#define PIN_BUTTON           12
/* What this build calls itself on the wire and in its log. */
#define PROJECT_ID        "E02"
#define FIRMWARE_VERSION  "1.0"
#define BOARD_LABEL       "ESP8266 (NodeMCU v3 / Wemos D1 mini)"


/* ------------------------------------------------------------- self-check --
   Runs once at boot. If a wiring fault is present, it is reported here rather
   than discovered later as a mystery reset. */
void startupReport() {
  Serial.println();
  Serial.println(F("--- build self-check ---"));
  Serial.print(F("project="));   Serial.println(F(PROJECT_ID));
  Serial.print(F("firmware="));  Serial.println(F(FIRMWARE_VERSION));
  Serial.print(F("board="));     Serial.println(F(BOARD_LABEL));
  Serial.print(F("pin STATUS_LED="));  Serial.println(PIN_STATUS_LED);
  Serial.print(F("pin BUTTON="));  Serial.println(PIN_BUTTON);
  Serial.println(F("--- end self-check ---"));
  Serial.println();
}
/* ---------------------------------------------------------------- serial ----
   One telemetry shape across every connected build: key=value pairs, comma
   separated, so any log from any project reads the same way. */
void logKey(const char *key, float value, int places = 2) {
  Serial.print(key); Serial.print('='); Serial.print(value, places); Serial.print(',');
}
void logStr(const char *key, const char *value) {
  Serial.print(key); Serial.print('='); Serial.print(value); Serial.print(',');
}


/* ------------------------------------------------------------------ setup --
   Leave WIFI_SSID empty to get a config portal on first boot: the board comes
   up as its own access point, you enter your network on the page, and the
   credentials are stored in flash. No credentials are compiled into the sketch,
   so a downloaded file never carries someone else's network. */
const char *WIFI_SSID = "";
const char *WIFI_PASS = "";
const char *DEVICE_NAME = "e02-esp8266-config-portal";


/* PIN_STATUS_LED and PIN_BUTTON come from the generator's pin map, so the
   constant names and the macros would collide if the sketch redefined them. */
const uint32_t PORTAL_TIMEOUT_MS = 600000;      // give up and retry station mode
const uint32_t BUTTON_HOLD_MS = 5000;           // hold to wipe the saved network

ESP8266WebServer server(80);
DNSServer dns;
String savedSsid, savedPass;
bool portalActive = false;
uint32_t portalStartedAt = 0;

const char *PORTAL_PAGE = R"HTML(<!doctype html><html><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>ESP8266 Config Portal</title></head><body style="font-family:monospace;background:#1c1a1a;color:#e8e6e3;max-width:34rem;margin:2rem auto;padding:0 1rem">
<h1 style="font-size:1.4rem">Network setup</h1>
<form method="POST" action="/save"><p><label>Network<br><input name="ssid" style="width:100%;padding:.5rem" required></label></p>
<p><label>Password<br><input name="pass" type="password" style="width:100%;padding:.5rem"></label></p>
<p><button style="background:#ff6b35;border:0;color:#1c1a1a;padding:.6rem 1rem;font-family:inherit">Save and connect</button></p></form>
<p style="color:#9aa0a6;font-size:.85rem">E02 · CodeProven</p></body></html>)HTML";

void startPortal() {
  WiFi.mode(WIFI_AP);
  WiFi.softAP(DEVICE_NAME, NULL);
  dns.start(53, "*", WiFi.softAPIP());
  portalActive = true;
  portalStartedAt = millis();
  Serial.print(F("# portal open at http://"));
  Serial.println(WiFi.softAPIP());
}

bool connectStation() {
  if (savedSsid.isEmpty()) return false;
  WiFi.mode(WIFI_STA);
  WiFi.begin(savedSsid.c_str(), savedPass.c_str());
  Serial.print(F("# connecting to ")); Serial.println(savedSsid);
  for (uint8_t attempt = 0; attempt < 40 && WiFi.status() != WL_CONNECTED; attempt++) {
    delay(250);
    digitalWrite(PIN_STATUS_LED, !digitalRead(PIN_STATUS_LED));
  }
  digitalWrite(PIN_STATUS_LED, LOW);
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println(F("# connect failed"));
    return false;
  }
  Serial.print(F("# connected, address "));
  Serial.println(WiFi.localIP());
  return true;
}

void handleRoot() {
  String html = F("<!doctype html><meta charset='utf-8'><title>E02</title>"
                  "<body style='font-family:monospace;background:#1c1a1a;color:#e8e6e3;padding:2rem'>"
                  "<h1 style='font-size:1.3rem'>Config Portal</h1>");
  html += "<p>address=" + WiFi.localIP().toString() + "</p>";
  html += "<p>network=" + WiFi.SSID() + " rssi=" + String(WiFi.RSSI()) + " dBm</p>";
  html += "<p>uptime=" + String(millis() / 1000) + " s</p>";
  html += "<p style='color:#9aa0a6'>POST /reset to clear the saved network</p></body>";
  server.send(200, "text/html", html);
}

void handleSave() {
  savedSsid = server.arg("ssid");
  savedPass = server.arg("pass");
#if defined(ESP32)
  Preferences prefs;
  prefs.begin("wifi", false);
  prefs.putString("ssid", savedSsid);
  prefs.putString("pass", savedPass);
  prefs.end();
#endif
  server.send(200, "text/html", F("<meta charset='utf-8'><body style='font-family:monospace;background:#1c1a1a;color:#e8e6e3;padding:2rem'><p>Saved. Rebooting into station mode.</p>"));
  delay(600);
  ESP.restart();
}

void handleReset() {
#if defined(ESP32)
  Preferences prefs;
  prefs.begin("wifi", false);
  prefs.clear();
  prefs.end();
#endif
  savedSsid = "";
  server.send(200, "text/plain", "cleared");
  delay(400);
  ESP.restart();
}

void setup() {
  Serial.begin(115200);
  delay(200);
  Serial.println();
  Serial.println(F("=== E02 Config Portal — config portal ==="));
  pinMode(PIN_STATUS_LED, OUTPUT);
  pinMode(PIN_BUTTON, INPUT_PULLUP);

#if defined(ESP32)
  Preferences prefs;
  prefs.begin("wifi", true);
  savedSsid = prefs.getString("ssid", "");
  savedPass = prefs.getString("pass", "");
  prefs.end();
#endif

  server.on("/", handleRoot);
  server.on("/save", HTTP_POST, handleSave);
  server.on("/reset", HTTP_POST, handleReset);
  /* Captive portals: every unknown path answers with the setup page, which is
     what makes the phone's "sign in to network" sheet appear. */
  server.onNotFound([]() {
    if (portalActive) server.send(200, "text/html", PORTAL_PAGE);
    else server.send(404, "text/plain", "not found");
  });

  if (connectStation()) {
    server.begin();
    Serial.println(F("# web server on port 80"));
  } else {
    startPortal();
    server.begin();
  }
}

void loop() {
  server.handleClient();
  if (portalActive) {
    dns.processNextRequest();
    if (millis() - portalStartedAt > PORTAL_TIMEOUT_MS) {
      Serial.println(F("# portal timed out, retrying the saved network"));
      portalActive = false;
      if (!connectStation()) startPortal();
    }
  }

  /* Hold the button to forget the network — the way out of a bad password. */
  static uint32_t heldSince = 0;
  if (digitalRead(PIN_BUTTON) == LOW) {
    if (!heldSince) heldSince = millis();
    else if (millis() - heldSince > BUTTON_HOLD_MS) {
      Serial.println(F("# button held: clearing the saved network"));
      handleReset();
    }
  } else heldSince = 0;

  static uint32_t last = 0;
  if (millis() - last >= 5000) {
    last = millis();
    Serial.print(F("NET,"));
    logStr("mode", portalActive ? "portal" : "station");
    logKey("rssi", WiFi.RSSI(), 0);
    logKey("uptime", millis() / 1000.0f, 0);
    Serial.println();
  }
}
