/*
  On Air Lamp, Version 1.0.0
  Copyright (c) 2024, Heise Medien GmbH & Co. KG / c’t
  Beschreibung des Programms in:
    Hajo Schulz
    Achtung, Aufnahme
    Bastelprojekt: „On Air“-Lampe mit WLAN
    c’t 25/2024, S. 90
    https://www.heise.de/select/ct/archiv/2024/25/seite-090
*/

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

// ---
// Configurable settings
// ---
const char* WifiSsid = "Ihre WLAN-SSID";
const char* WifiPwd = "Ihr WLAN-Passwort";
const char* ServerName = "onairlamp";

// ---
// GPIO pins
// ---
// RGB LED on D5 - D7
const int RgbRedPin = D6;
const int RgbGreenPin = D7;
const int RgbBluePin = D5;
// Optocoupler on D1
const int OptoPin = D1;
// LSR on A0;
const int LsrPin = A0;

// ---
// Helpers for RGB LED
// ---
enum RgbColor {
  BLACK  = 0,
  RED    = 1,
  GREEN  = 2,
  YELLOW = 3,
  BLUE   = 4,
  PURPLE = 5,
  CYAN   = 6,
  WHITE  = 7
};

void setRgb(RgbColor color)
{
  digitalWrite(RgbRedPin, color & 1 ? HIGH : LOW);
  digitalWrite(RgbGreenPin, color & 2 ? HIGH : LOW);
  digitalWrite(RgbBluePin, color & 4 ? HIGH : LOW);
}

// ---
// The lamp
// ---
int lampState = 0;
// If you want the lamp to pulsate when "On Air" instead of shining
// continuously, just swap the names of the following two functions.
void lampOn()
{
  digitalWrite(OptoPin, HIGH);
  delay(100);
  digitalWrite(OptoPin, LOW);
  delay(200);
}
void lampOff()
{
  digitalWrite(OptoPin, HIGH);
  delay(100);
  digitalWrite(OptoPin, LOW);
  delay(200);
  digitalWrite(OptoPin, HIGH);
  delay(100);
  digitalWrite(OptoPin, LOW);
  delay(200);
}
void switchLamp(int desiredState = -1)
{
  if(desiredState == -1) {
    desiredState = (lampState == 1 ? 0 : 1);
  }
  if(desiredState != lampState) {
    if(desiredState == 1) {
      lampOn();
    }
    else {
      lampOff();
    }
    lampState = desiredState;
  }
}

// ---
// The web server
// ---
ESP8266WebServer server(80);
bool usingMDNS = false;

String prepareAnswer()
{
  String htmlPage;
  htmlPage.reserve(1024);               // prevent RAM fragmentation
  htmlPage = "<!DOCTYPE HTML>\r\n"
             "<html>\r\n"
             "<head>\r\n"
             "<meta charset=\"utf-8\">\r\n"
             "<title>On-Air-Lampe</title>\r\n"
             "</head>\r\n"
             "<body>\r\n"
             "<h1>On-Air-Lampe</h1>\r\n"
             "<p>Die Lampe ist <span id=\"lampstate\" style=\"font-weight:bold;\">";
  htmlPage += (lampState == 1 ? "an" : "aus");
  htmlPage += "</span>.</p>\r\n"
              "</body></html>\r\n";
  return htmlPage;
}

void handleLamp(int request)
{
  setRgb(YELLOW);
  if(request == 0 || request == 1) {
    switchLamp(request);
  }
  String answer = prepareAnswer();
  server.send(200, "text/html; charset=utf-8", answer.c_str());
  setRgb(GREEN);
}

void handleRoot()
{
  handleLamp(-1);
}

void handleOn()
{
  handleLamp(1);
}

void handleOff()
{
  handleLamp(0);
}

void handleNotFound()
{
  setRgb(RED);
  server.send(404, "text/plain", "404 not found\r\n");
  setRgb(GREEN);
}

// ---
// Initialize everything
// --
void setup(void)
{
  pinMode(RgbRedPin, OUTPUT);
  pinMode(RgbGreenPin, OUTPUT);
  pinMode(RgbBluePin, OUTPUT);
  pinMode(OptoPin, OUTPUT);
  pinMode(LsrPin, INPUT);
  setRgb(BLACK);
  digitalWrite(OptoPin, 0);

  Serial.begin(115200);
  
  WiFi.mode(WIFI_STA);
  WiFi.hostname(ServerName);
  Serial.println("");
  Serial.println("Connecting ...");
  bool blinkFlag = true;
  int retryCount = 0;
  bool failFlag = true;
  wl_status_t ws = WiFi.status();
  // Wait for connection
  while (ws != WL_CONNECTED) {
    if(failFlag) {
      WiFi.begin(WifiSsid, WifiPwd);
      failFlag = false;
    }
    Serial.print(".");
    setRgb(blinkFlag ? YELLOW : BLACK);
    blinkFlag = !blinkFlag;
    delay(500);
    ws = WiFi.status();
    if(ws == WL_CONNECT_FAILED) {
      failFlag = true;
      setRgb(RED);
      delay(500);
      if(++retryCount > 5) {
        // We've done our best. Stopping
        Serial.println("");
        Serial.println("Connection failed permanently.");
        while(1) {
          setRgb(blinkFlag ? RED : BLACK);
          blinkFlag = !blinkFlag;
          delay(500);
        }
      }
      Serial.println("");
      Serial.println("Connection failed. Retrying ...");
    }
  }
  setRgb(GREEN);
  Serial.println("");
  Serial.print("Connected to "); Serial.println(WifiSsid);
  Serial.print("IP address: "); Serial.println(WiFi.localIP());

  usingMDNS = MDNS.begin(ServerName);
  if (usingMDNS) {
    MDNS.addService("http", "tcp", 80);
    Serial.print("MDNS responder started; Server URL: http://");
    Serial.print(ServerName); Serial.println(".local");
  }

  server.on("/", handleRoot);
  server.on("/on", handleOn);
  server.on("/off", handleOff);
  server.onNotFound(handleNotFound);

  server.begin();
  Serial.println("HTTP server started");
}

// ---
// The main loop
// ---
void loop(void)
{
  server.handleClient();
  if(usingMDNS) {
    MDNS.update();
  }
}
