/*
 * ESP32 Telegram Control Bridge — E22
 * 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):
 *   SENSE              -> 34
 *   OUT_1              -> 4
 *
 * GPIO34–35 are input only and have no pull-ups. Use them for sensors, never for LEDs.
 *
 * Project page : https://codeproven.pages.dev/project/E22/
 * Wiring       : https://codeproven.pages.dev/project-art/E22-connections.svg
 * Level        : Advanced
 * 
 * An ESP32 that reads incoming commands, acts on /on, /off and /status, and confirms each action in the thread.
 * 
 * Safety       : A chat command that moves hardware needs a local override that does not depend on the network or on Telegram being reachable.
 *
 * 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 <WiFiClientSecure.h>
#include <HTTPClient.h>
#include <ArduinoJson.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_SENSE            34
#define PIN_OUT_1            4
/* What this build calls itself on the wire and in its log. */
#define PROJECT_ID        "E22"
#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 SENSE="));  Serial.println(PIN_SENSE);
  Serial.print(F("pin OUT_1="));  Serial.println(PIN_OUT_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 = "e22-esp32-telegram-control-bridg";


/* A bot token and a chat id are the two secrets. Keep them here, out of any
   repository, and revoke the token from the Telegram app if a board is lost. */
const char *BOT_TOKEN = "000000:REPLACE_WITH_YOUR_TOKEN";
const char *CHAT_ID   = "REPLACE_WITH_YOUR_CHAT_ID";
const uint32_t POLL_MS = 5000;
const int THRESHOLD = 512;

uint32_t alertsSent = 0;
bool wasAbove = false;

void sendMessage(const String &text) {
  if (WiFi.status() != WL_CONNECTED) { Serial.println(F("# no network, message not sent")); return; }
  WiFiClientSecure client;
  client.setInsecure();                 // the certificate bundle is not in flash
  HTTPClient http;
  const String url = String("https://api.telegram.org/bot") + BOT_TOKEN + "/sendMessage";
  if (!http.begin(client, url)) { Serial.println(F("# https begin failed")); return; }
  http.addHeader("Content-Type", "application/json");

  JsonDocument doc;
  doc["chat_id"] = CHAT_ID;
  doc["text"] = text;
  doc["disable_web_page_preview"] = true;
  String body;
  serializeJson(doc, body);

  const int status = http.POST(body);
  Serial.print(F("# telegram http ")); Serial.println(status);
  if (status == 200) alertsSent++;
  http.end();
}

void setup() {
  Serial.begin(115200);
  delay(200);
  Serial.println();
  Serial.println(F("=== E22 Telegram Control Bridge — telegram bridge ==="));
  pinMode(PIN_SENSE, INPUT);
  pinMode(PIN_OUT_1, OUTPUT);

  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  for (uint8_t i = 0; i < 30 && WiFi.status() != WL_CONNECTED; i++) delay(300);

  if (String(BOT_TOKEN).indexOf("REPLACE") >= 0) {
    Serial.println(F("# set BOT_TOKEN and CHAT_ID at the top of the sketch before expecting messages"));
  } else {
    sendMessage(String(F("E22 Telegram Control Bridge online")) + " · ip " + WiFi.localIP().toString());
  }
}

void loop() {
  static uint32_t last = 0;
  if (millis() - last < POLL_MS) { delay(50); return; }
  last = millis();

  const int raw = analogRead(PIN_SENSE);
  const bool above = raw > THRESHOLD;

  Serial.print(F("READ,"));
  logKey("raw", raw, 0);
  logKey("above", above ? 1 : 0, 0);
  logKey("alerts", alertsSent, 0);
  Serial.println();

  /* Edge only: one message when a threshold is crossed, not one every sample.
     The second message says the condition has cleared, so the thread is not
     left hanging. */
  if (above && !wasAbove) {
    sendMessage(String(F("E22 threshold crossed: raw=")) + String(raw));
  } else if (!above && wasAbove) {
    sendMessage(String(F("E22 back to normal: raw=")) + String(raw));
  }
  wasAbove = above;

  /* The bridge also answers commands: /on and /off switch the output. */
    WiFiClientSecure client;
    client.setInsecure();
    HTTPClient http;
    const String url = String("https://api.telegram.org/bot") + BOT_TOKEN + "/getUpdates?timeout=0&offset=-1";
    if (http.begin(client, url) && http.GET() == 200) {
      JsonDocument doc;
      if (!deserializeJson(doc, http.getString())) {
        const String text = doc["result"][0]["message"]["text"] | "";
        if (text == "/on")  { digitalWrite(PIN_OUT_1, HIGH); sendMessage("/on — output 1 on"); }
        if (text == "/off") { digitalWrite(PIN_OUT_1, LOW);  sendMessage("/off — output 1 off"); }
        if (text == "/status") sendMessage(String("raw=") + String(raw) + " rssi=" + String(WiFi.RSSI()));
      }
      http.end();
    }
}
