/*
 * ESP8266 Bench Status Page — E28
 * 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):
 *   OUT_1              -> 5
 *   IN_1               -> A0
 *
 * 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/E28/
 * Wiring       : https://codeproven.pages.dev/project-art/E28-connections.svg
 * Level        : Beginner
 * 
 * An ESP8266 serving uptime, signal strength and one sensor reading, as the smallest thing worth putting on your network.
 * 
 * Safety       : This is an unauthenticated page on your network. It is for a bench, not for a build you leave in a public space.
 *
 * 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>

/* ------------------------------------------------------------ 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_OUT_1            5
#define PIN_IN_1             A0
/* What this build calls itself on the wire and in its log. */
#define PROJECT_ID        "E28"
#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 OUT_1="));  Serial.println(PIN_OUT_1);
  Serial.print(F("pin IN_1="));  Serial.println(PIN_IN_1);
  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 = "e28-esp8266-bench-status-page";


const uint8_t PIN_OUT1 = PIN_OUT_1;
const uint8_t PIN_IN1  = PIN_IN_1;
bool OutputState[1] = { false };
const int PWM_MAX = 1023;
const uint32_t WIFI_RETRY_MS = 15000;

ESP8266WebServer server(80);

String buildPage() {
  String html = F("<!doctype html><meta charset='utf-8'>"
                  "<meta name='viewport' content='width=device-width,initial-scale=1'>"
                  "<title>E28 Bench Status Page</title>"
                  "<body style='font-family:monospace;background:#1c1a1a;color:#e8e6e3;padding:1.5rem;max-width:38rem;margin:auto'>"
                  "<h1 style='font-size:1.3rem'>Bench Status Page</h1>");
  html += "<p>rssi=" + String(WiFi.RSSI()) + " dBm · uptime=" + String(millis() / 1000) + " s</p>";
    html += "<p>input 1=" + String(analogRead(PIN_IN1)) + "</p>";

  for (uint8_t i = 0; i < 1; i++) {
    html += "<p>output " + String(i + 1) + ": ";
    html += OutputState[i] ? "ON " : "OFF ";
    html += "<a href='/out" + String(i + 1) + "?on=1'>on</a> ";
    html += "<a href='/out" + String(i + 1) + "?on=0'>off</a></p>";
  }
  html += "<p><a href='/data'>json</a></p>";
  html += "<p style='color:#9aa0a6;font-size:.85rem'>CodeProven E28</p></body>";
  return html;
}

void handleRoot() { server.send(200, "text/html", buildPage()); }

void handleData() {
  String json = "{\"id\":\"E28\",\"uptime\":" + String(millis() / 1000) + ",\"rssi\":" + String(WiFi.RSSI());
  json += ",\"in1\":" + String(analogRead(PIN_IN1));
  json += "}";
  server.send(200, "application/json", json);
}

void connectWifi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  Serial.print(F("# connecting"));
  for (uint8_t attempt = 0; attempt < 30 && WiFi.status() != WL_CONNECTED; attempt++) {
    delay(300);
    Serial.print('.');
  }
  Serial.println();
  if (WiFi.status() == WL_CONNECTED) {
    Serial.print(F("# http://")); Serial.println(WiFi.localIP());
  } else {
    Serial.println(F("# no network — set WIFI_SSID and WIFI_PASS at the top"));
  }
}

void setup() {
  Serial.begin(115200);
  delay(200);
  Serial.println();
  Serial.println(F("=== E28 Bench Status Page — control page ==="));
  for (uint8_t i = 0; i < 1; i++) {
    pinMode((&PIN_OUT1)[i], OUTPUT);
    digitalWrite((&PIN_OUT1)[i], OutputState[i] ? HIGH : LOW);
  }
  pinMode(PIN_IN1, INPUT);

  server.on("/", handleRoot);
  server.on("/data", handleData);
  server.on("/out1", []() {
    const bool on = server.arg("on") == "1";
    digitalWrite(PIN_OUT1, on ? HIGH : LOW);
    OutputState[0] = on;
    server.send(200, "text/plain", on ? "on" : "off");
  });
  server.onNotFound([]() { server.send(404, "text/plain", "not found"); });

  connectWifi();
  server.begin();
  Serial.println(F("# server listening on port 80"));
}

void loop() {
  server.handleClient();

  /* Reconnect rather than going quiet: a controller that drops off the network
     is indistinguishable from a controller that has crashed. */
  static uint32_t lastCheck = 0;
  if (millis() - lastCheck > WIFI_RETRY_MS) {
    lastCheck = millis();
    if (WiFi.status() != WL_CONNECTED) {
      Serial.println(F("# link lost, reconnecting"));
      connectWifi();
    }
  }

  static uint32_t lastLog = 0;
  if (millis() - lastLog >= 5000) {
    lastLog = millis();
    Serial.print(F("NODE,"));
    logKey("rssi", WiFi.RSSI(), 0);
    logKey("heap", ESP.getFreeHeap() / 1024.0f, 1);
    logKey("uptime", millis() / 1000.0f, 0);
    Serial.println();
  }
}
