/*
 * ESP32 mDNS Service Discovery — E20
 * ESP32 & ESP8266 Connectivity
 *
 * Board   : ESP32 DevKit (ESP32-WROOM-32, 30 or 38 pin)
 * MCU     : ESP32 dual core @ 240 MHz, 3.3 V logic
 * Compile : arduino-cli compile --fqbn esp32:esp32:esp32
 *
 * Pin map (also in the wiring notes and on the project page):
 *   OUT_1              -> 4
 *   IN_1               -> 34
 *
 * GPIO34–35 are input only and have no pull-ups. Use them for sensors, never for LEDs.
 *
 * Project page : https://codeproven.pages.dev/project/E20/
 * Wiring       : https://codeproven.pages.dev/project-art/E20-connections.svg
 * Level        : Intermediate
 * 
 * An ESP32 publishing an mDNS hostname and HTTP service, so the build is reachable at a stable name.
 * 
 * Safety       : mDNS is local-network only and unauthenticated. It is a convenience for your own bench, not a public address.
 *
 * This sketch is the one the project page offers for download, and it is
 * compile-checked for esp32:esp32:esp32 before publication. It is bench code: read it,
 * change the constants for your own hardware, and test with the load
 * disconnected first.
 */

#include <WiFi.h>
#include <WebServer.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            4
#define PIN_IN_1             34
/* What this build calls itself on the wire and in its log. */
#define PROJECT_ID        "E20"
#define FIRMWARE_VERSION  "1.0"
#define BOARD_LABEL       "ESP32 DevKit (ESP32-WROOM-32, 30 or 38 pin)"


/* ------------------------------------------------------------- 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 = "e20-esp32-mdns-service-discovery";


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

WebServer server(80);

String buildPage() {
  String html = F("<!doctype html><meta charset='utf-8'>"
                  "<meta name='viewport' content='width=device-width,initial-scale=1'>"
                  "<title>E20 mDNS Service Discovery</title>"
                  "<body style='font-family:monospace;background:#1c1a1a;color:#e8e6e3;padding:1.5rem;max-width:38rem;margin:auto'>"
                  "<h1 style='font-size:1.3rem'>mDNS Service Discovery</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 E20</p></body>";
  return html;
}

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

void handleData() {
  String json = "{\"id\":\"E20\",\"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("=== E20 mDNS Service Discovery — 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();
  }
}
