/* =========================================================================================
 * Project: ENC_v2j.ino
 * Build:   2026-03-02 
 * SERVER platform: ESP32 CYD (Cheap Yellow Display) - ESP32-2432S028R
 * CLIENT platform: ESP32-C3 Super Mini.
 * Alternative client OLED: SSD1306: U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, -1, OLED_SCL, OLED_SDA); 
 * =========================================================================================
 * CLIENT IMPLEMENTED FEATURES
 * 1. Startup Splash Screen: 3s branding animation with versioning.
 * 2. Dedicated SPI for Touch: Prevents bus clashing on pins 25, 39, 32, 33.
 * 3. 3-Page Navigation: Dashboard, RSSI Diagnostics, and System MAC Info.
 * 4. SNTP Watchdog: 10-minute auto-restart for clock synchronization.
 * 5. Signal Bar UI: Color-coded dBm diagnostics with 4-bar graphics.
 * 6. Optimized Layout: Hard-left alignment to prevent ID10 text wrapping.
 * 7. Audio Feedback: High-freq "chirp" for discovery and touch.
 * =========================================================================================
 * A.I. MUST REVIEW BEFORE CODING
 * 1. Comment Delimiters: REMOVE all citations or tags.
 * 2. Function Separators: PRESERVE all //------------------------------
 * 3. Verbosity: Serial Monitor must be HIGHLY VERBOSE.
 * 4. Variable Consistency: Use NODE_ID and FILENAME constants.
 * =====================================================================
 * Critical Requirements for Client-Server Sync
 * To ensure the Client and Server communicate successfully, four specific factors must align:
 * MAC Address Match: The Client must send data specifically to the Server's 6-byte Hardware MAC address.
 * WiFi Channel Lock: Both devices must be forced onto the same WiFi channel (Channel 1) to prevent the radios from "missing" each other while scanning.
 * Peer Registration: The Server must register the Client as a "Peer" before it can send the handshake back.
 * Software Handshake: The Server must manually send a return packet to the Client so the Client can trigger its OnDataRecv callback and capture the RSSI (signal bars).
 * =====================================================================
 * Name:  ENC_v2j.ino
 * SYNC REQUIREMENT: Target Server MAC must be C8:2E:18:F8:D0:6C.
 * SYNC REQUIREMENT: Radio must be locked to Channel 1.
 */

#include <Wire.h>
#include <Adafruit_AHT10.h>
#include <U8g2lib.h>
#include <esp_now.h>
#include <WiFi.h>
#include <esp_wifi.h>

//----------------------------------------------------------------------
// --- Global Settings ---
const int NODE_ID = 6; 
const char *FILENAME = "ENC_v2j.ino";
String clientVersion = "v2j";
//----------------------------------------------------------------------

const int ldrPin = 3;  
const int pirPin = 2;  
const int ledPin = 1;  

#define OLED_SDA 8
#define OLED_SCL 9

U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /*reset=*/-1, /*clock=*/OLED_SCL, /*data=*/OLED_SDA);
// Alternative SSD1306: U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, -1, OLED_SCL, OLED_SDA); 

Adafruit_AHT10 aht;

uint8_t broadcastAddress[] = { 0xC8, 0x2E, 0x18, 0xF8, 0xD0, 0x6C };
esp_now_peer_info_t peerInfo;
static volatile int8_t gLastRssi = 127; 

int AHT10_Tc = 0;
int AHT10_Hu = 0;
volatile bool motionDetected = false; 
bool pirLatched = false;
unsigned long elapsedMillis = 0;
unsigned long timerValue = 60000; 

typedef struct struct_message {
  int id;
  int Tc;
  int Hu;
  int Lx; 
} struct_message;
struct_message AHT10Readings;

//----------------------------------------------------------------------

void IRAM_ATTR catchesMotion() {
  motionDetected = true; 
}

//----------------------------------------------------------------------

static void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  Serial.printf("Action: ESP-NOW Status for ID %d: %s\n", NODE_ID, status == ESP_NOW_SEND_SUCCESS ? "SUCCESS" : "FAIL");
}

static void OnDataRecv(const esp_now_recv_info_t *info, const uint8_t *data, int len) {
  if (info->rx_ctrl) {
    gLastRssi = info->rx_ctrl->rssi;
    Serial.printf(">>> HANDSHAKE RECEIVED: %d dBm from master\n", gLastRssi);
  }
}

//----------------------------------------------------------------------

void setup() {
  Serial.begin(115200);
  delay(1500);
  
  WiFi.disconnect(true);
  WiFi.mode(WIFI_OFF);
  delay(100);
  WiFi.mode(WIFI_STA);
  delay(200);

  Serial.println("\n//--------------------------------------");
  Serial.println(">>> CLIENT SYNC DIAGNOSTICS <<<");
  Serial.printf("FILE: %s | ID: %d\n", FILENAME, NODE_ID);
  Serial.print("CLIENT MAC: "); Serial.println(WiFi.macAddress());
  Serial.println("//--------------------------------------");

  pinMode(ledPin, OUTPUT);
  pinMode(pirPin, INPUT_PULLUP); 
  attachInterrupt(digitalPinToInterrupt(pirPin), catchesMotion, RISING);

  aht.begin();
  u8g2.begin();
  u8g2.setContrast(150);

  esp_wifi_set_channel(1, WIFI_SECOND_CHAN_NONE);

  if (esp_now_init() == ESP_OK) {
    esp_now_register_send_cb(OnDataSent);
    esp_now_register_recv_cb(OnDataRecv);
    
    memset(&peerInfo, 0, sizeof(peerInfo));
    memcpy(peerInfo.peer_addr, broadcastAddress, 6);
    peerInfo.channel = 1; 
    peerInfo.encrypt = false;
    esp_now_add_peer(&peerInfo);
  }
}

//----------------------------------------------------------------------

void loop() {
  unsigned long now = millis();

  if (now - elapsedMillis >= timerValue) {
    elapsedMillis = now;
    Tasks();
  }

  if (motionDetected && !pirLatched) {
    pirLatched = true; 
    motionDetected = false; 
    flashLedOnce();
    Tasks();
  } 
  
  if (digitalRead(pirPin) == LOW && pirLatched) {
    pirLatched = false;
  }
}

//----------------------------------------------------------------------

void Tasks() {
  sensors_event_t humidity, temp;
  if (aht.getEvent(&humidity, &temp)) { 
    AHT10Readings.id = NODE_ID; 
    AHT10Readings.Tc = (int)temp.temperature; 
    AHT10Readings.Hu = (int)humidity.relative_humidity; 
    AHT10Readings.Lx = analogRead(ldrPin); 
    AHT10_Tc = AHT10Readings.Tc;
    AHT10_Hu = AHT10Readings.Hu;

    displayOutgoingLarge(); 
    esp_now_send(broadcastAddress, (uint8_t *)&AHT10Readings, sizeof(AHT10Readings));
    
    delay(1000); 
    displayOutgoing(); 
  }
}

//----------------------------------------------------------------------

void displayOutgoingLarge() {
  // Screen 1: ID, Filename, and Channel Status
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_fub20_tf); 
  char idStr[10];
  sprintf(idStr, "ID: %d", NODE_ID);
  int idWidth = u8g2.getStrWidth(idStr);
  u8g2.setCursor((128 - idWidth) / 2, 28);
  u8g2.print(idStr);
  
  u8g2.setFont(u8g2_font_doomalpha04_tr);
  int verWidth = u8g2.getStrWidth(FILENAME);
  u8g2.setCursor((128 - verWidth) / 2, 48);
  u8g2.print(FILENAME); 

  // ADDED: Channel Status Indicator
  u8g2.drawStr(50, 62, "CH1");
  
  u8g2.sendBuffer();
  delay(1500);

  // Screen 2: Huge Temperature
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_inb53_mr); 
  u8g2.setCursor(0, 60);
  u8g2.print(AHT10_Tc);
  u8g2.setFont(u8g2_font_inb46_mf); 
  u8g2.print("c");
  u8g2.sendBuffer();
  delay(1200);

  // Screen 3: Huge Humidity
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_inb53_mr);
  u8g2.setCursor(0, 60);
  u8g2.print(AHT10_Hu);
  u8g2.setFont(u8g2_font_inb46_mf); 
  u8g2.print("%");
  u8g2.sendBuffer();
  delay(1200);
}

//----------------------------------------------------------------------

void displayOutgoing() {
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_doomalpha04_tr);
  u8g2.setCursor(2, 10); u8g2.print("-OUTGOING STATS-");
  u8g2.setCursor(0, 25); u8g2.printf("ID: %d", NODE_ID);
  u8g2.setCursor(45, 25); u8g2.printf("Tc:%d", AHT10_Tc);
  u8g2.setCursor(90, 25); u8g2.printf("Hu:%d", AHT10_Hu);
  
  String lightDesc = (AHT10Readings.Lx < 500) ? "Bright" : (AHT10Readings.Lx < 1500) ? "Normal" : "Dark";
  u8g2.setCursor(0, 40); u8g2.print("Light: "); u8g2.print(lightDesc);

  u8g2.setCursor(0, 55); u8g2.print("RSSI:");
  if (gLastRssi < 0) {
    u8g2.print(gLastRssi); u8g2.print(" dBm");
    int bars = (gLastRssi > -60) ? 4 : (gLastRssi > -70) ? 3 : (gLastRssi > -80) ? 2 : 1;
    for (int i = 1; i <= 4; i++) {
      int barH = i * 4;
      if (i <= bars) u8g2.drawBox(90 + (i * 7), 64 - barH, 5, barH);
      else u8g2.drawFrame(90 + (i * 7), 64 - barH, 5, barH);
    }
  } else {
    u8g2.print("-- dBm");
  }
  u8g2.sendBuffer();
  delay(3500);
  u8g2.clearBuffer(); u8g2.sendBuffer();
}

//----------------------------------------------------------------------

void flashLedOnce() { digitalWrite(ledPin, HIGH); delay(150); digitalWrite(ledPin, LOW); }
//----------------------------------------------------------------------