
/*
  Project Name: LED Table
  Description: This program controls the LED strip for table lighting. It can receive information
  via ESP-NOW and makes the LED strip light up in the corresponding color and mode.
  Author: Lena Hagenauer
  Date: 05 January 2025
  Version: 2.0
  License: MIT License
*/

// Include libraries

// neoPixel
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h>
#endif
#define PIN        D6
#define NUMPIXELS 300
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

// Color Information
char colorType[8];
uint32_t color;

// WIFI & ESPNOW
#include <ESP8266WiFi.h>
#include <espnow.h>

uint8_t channel = 1;


#define MAX_CHANNEL 13  // 11 in North America or 13 in Europe

unsigned long currentMillis = millis();
unsigned long lastTime = 0;  
unsigned long timerDelay = 2000;  // send readings timer

uint8_t broadcastAddressX[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

uint8_t clientMacAddress[6];

enum PairingStatus {PAIR_REQUEST, PAIR_REQUESTED, PAIR_PAIRED, };

PairingStatus pairingStatus = PAIR_REQUEST;

enum MessageType {PAIRING, DATA, LIGHT,};
MessageType messageType;

// Updates DHT readings every 10 seconds
//const long interval = 10000;
unsigned long previousMillis = 0;    // will store last time DHT was updated


//Structure to send light
typedef struct struct_light {
  uint8_t msgType;
  char colorType[8];
  uint32_t color;
} struct_light;

// Structure for pairing message
typedef struct struct_pairing {      
  uint8_t msgType;
  uint8_t id;
  uint8_t macAddr[6];
  uint8_t channel;
  char name[64];
} struct_pairing;

// Variables definitions
struct_light incomingReadings;
struct_pairing pairingData;

#define BOARD_ID 1
unsigned long start;
int state = 0;


// Callback when data is sent
void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus) {
  Serial.print("Last Packet Send Status: ");
  if (sendStatus == 0){
    Serial.println("Delivery success");
  }
  else{
    Serial.println("Delivery fail");
  }
}

// Function to print MAC address
void printMAC(const uint8_t * mac_addr){
  char macStr[18];
  snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
           mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
  Serial.print(macStr);
}

// Callback when data is received
void OnDataRecv(uint8_t * mac, uint8_t *incomingData, uint8_t len) {
  Serial.print("Size of message : ");
  Serial.print(len);
  Serial.print(" from ");
  printMAC(mac);
  Serial.println();
  uint8_t type = incomingData[0];

  switch (type) {
  case LIGHT:  
   
    memcpy(&incomingReadings, incomingData, sizeof(incomingReadings));
    Serial.print(len);
    Serial.print(" Data bytes received from: ");
    printMAC(mac);
    Serial.println();

    strcpy(colorType, incomingReadings.colorType);
    Serial.println(state);
    
    color = incomingReadings.color;
    Serial.println(color);
 
    break;

  case PAIRING:
    memcpy(&pairingData, incomingData, sizeof(pairingData));
    if (pairingData.id == 0) {                // the message comes from server
      Serial.print("Pairing done for ");
      printMAC(pairingData.macAddr);
      Serial.print(" on channel " );
      Serial.print(pairingData.channel);    // channel used by the server
      Serial.print(" in ");
      Serial.print(millis()-start);
      Serial.println("ms");
      //esp_now_del_peer(pairingData.macAddr);
      //esp_now_del_peer(mac);
      esp_now_add_peer(pairingData.macAddr, ESP_NOW_ROLE_COMBO, pairingData.channel, NULL, 0); // add the server to the peer list 
      pairingStatus = PAIR_PAIRED ;            // set the pairing status
    }
    break;
  }  
}

// Function to read and get MAC address
void readGetMacAddress(){
  String val = WiFi.macAddress();
  Serial.println(val);
  char* endPtr; 
  clientMacAddress[0] = strtol(val.c_str(), &endPtr, 16); // read the first starting at the beginning of the buffer. this initializes endPtr as a pointer to the ':' after the first number 
  for (int i = 1;  (*endPtr) && (i < 6); i++) {
    clientMacAddress[i] = strtol(endPtr + 1, &endPtr, 16); // using +1 for the pointer as we want to skip the ':'
  }

  for (int i = 0; i < 6; i++) {
    Serial.print(clientMacAddress[i], HEX);
    if (i != 5) Serial.print(F(":"));
  }
}

// Function to handle auto pairing
PairingStatus autoPairing(){
  switch(pairingStatus) {
  case PAIR_REQUEST:
    Serial.print("Pairing request on channel "  );
    Serial.println(channel);
  
    // clean esp now
    esp_now_deinit();
    WiFi.mode(WIFI_STA);
    // set WiFi channel   
    wifi_promiscuous_enable(1);
    wifi_set_channel(channel);
    wifi_promiscuous_enable(0);
    //WiFi.printDiag(Serial);
    WiFi.disconnect();

    // Init ESP-NOW
    if (esp_now_init() != 0) {
      Serial.println("Error initializing ESP-NOW");
    }
    esp_now_set_self_role(ESP_NOW_ROLE_COMBO);
    // set callback routines
    esp_now_register_send_cb(OnDataSent);
    esp_now_register_recv_cb(OnDataRecv);
    
    // set pairing data to send to the server
    pairingData.id = BOARD_ID;     
    pairingData.channel = channel;
    pairingData.macAddr[0] = clientMacAddress[0];
    pairingData.macAddr[1] = clientMacAddress[1];
    pairingData.macAddr[2] = clientMacAddress[2];
    pairingData.macAddr[3] = clientMacAddress[3];
    pairingData.macAddr[4] = clientMacAddress[4];
    pairingData.macAddr[5] = clientMacAddress[5];
    strcpy(pairingData.name, "");
 
    previousMillis = millis();
    // add peer and send request
    Serial.println(esp_now_send(broadcastAddressX, (uint8_t *) &pairingData, sizeof(pairingData)));
    pairingStatus = PAIR_REQUESTED;
    break;

  case PAIR_REQUESTED:
    // time out to allow receiving response from server
    currentMillis = millis();
    if(currentMillis - previousMillis > 1000) {
      previousMillis = currentMillis;
      // time out expired,  try next channel
      channel ++;
      if (channel > MAX_CHANNEL) {
        channel = 0;
      }
      pairingStatus = PAIR_REQUEST; 
    }
    break;

  case PAIR_PAIRED:
    //Serial.println("Paired!");
    break;
  }
  return pairingStatus;
} 

// Return RGB values
uint8_t red(uint32_t c) {
  return (c >> 16);
}
uint8_t green(uint32_t c) {
  return (c >> 8);
}
uint8_t blue(uint32_t c) {
  return (c);
}

// Function to turn LED Stripe off
void off(){
  pixels.clear();
  pixels.show();
}

// Function to turn LED Stripe on
void on(int r, int g, int b){
  for(int i=0; i<NUMPIXELS; i++) {
 
    pixels.setPixelColor(i, r, g, b);
  }
  pixels.show();
}

// Function to turn LED Stripe on in defined Color and Fade it
void fade(int c, int st, int end){
  
 int wait = 20;
  for(int j=st; j<end; j++){
    for(int i=0; i<NUMPIXELS; i++) {
 
      pixels.setPixelColor(i, pixels.ColorHSV(c*256, 255, j));
    }
    pixels.show();
    delay(wait);

  }

  for(int j=st; j>=end; j--){
    for(int i=0; i<NUMPIXELS; i++) {

      pixels.setPixelColor(i, pixels.ColorHSV(c*256, 255, j));

    }

    pixels.show();
    delay(wait);

  }
}
// Function to turn LED Stripe on in defined Color and wafe it
void wafe(int r, int g, int b){

  for(int i=0; i<NUMPIXELS; i++) {
    pixels.setPixelColor(i, r, g, b);
  }

  for(int i = 50; i<200; i++){
    pixels.setBrightness(i);
    delay(5);
    pixels.show();
  }
  
  for(int i = 200; i>50; i--){
    pixels.setBrightness(i);
    delay(5);
    pixels.show();
  }

}

// Function to turn LED Stripe on in green tones
void forest(){

  int h = 80; // grün
  int v = 100;

    int rand = random(10);
    for(int i = 0; i<NUMPIXELS; i++){
      pixels.setPixelColor(i, pixels.ColorHSV((h+random(-10,10))*256, 255, v-rand*2));
      i=i+rand;
    }

  delay(500);
  pixels.show();
}

// Function to turn LED Stripe on in red tones
void fight(){
  int r = 255;
  int g = 2;
  int b = 2;

  for(int i=0; i<NUMPIXELS; i++) {
    pixels.setPixelColor(i, r, g, b);
  }

  for(int i = 50; i<200; i++){
    pixels.setBrightness(i);
    delay(5);
    pixels.show();
  }
  
  for(int i = 200; i>50; i--){
    pixels.setBrightness(i);
    delay(5);
    pixels.show();
  }
}
// Function to turn LED Stripe on in brown tones
void tavern(){
  int h = 24; // orange
    int v = 100;

    int rand = random(10);
    for(int i = 0; i<NUMPIXELS; i++){
      pixels.setPixelColor(i, pixels.ColorHSV((h+random(-20,30))*256, 255, v-rand*3));
      i=i+rand;
    }

  delay(500);
  pixels.show();
}
// Function to turn LED Stripe on in blue tones
void water(){
  int h = 160; //blau
  for(int i = 0; i<NUMPIXELS; i++){
    pixels.setPixelColor(i, pixels.ColorHSV(h*256, 255, 100));
  }

  //delay(500);
  pixels.show();
}





//show rainbow
void rainbow(int wait) {
  for(long firstPixelHue = 0; firstPixelHue < 65536; firstPixelHue += 256) {

    pixels.rainbow(firstPixelHue,2);
    pixels.show();
    delay(wait);  

  }
}



void setup() {

  // Init Serial Monitor
  Serial.begin(74880);

  //Init neoPixel
  pixels.begin();

  // Set device as a Wi-Fi Station
  WiFi.mode(WIFI_STA);
  readGetMacAddress();

  //Serial.println(WiFi.macAddress());
  WiFi.disconnect();

  // Init ESP-NOW
  if (esp_now_init() != 0) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }

  // Set ESP-NOW Role
  esp_now_set_self_role(ESP_NOW_ROLE_COMBO);
  
  // Register for a callback function that will be called when data is received
  esp_now_register_recv_cb(OnDataRecv);
  esp_now_register_send_cb(OnDataSent);

  pairingData.id = 1;

}

 
void loop() {

  if(state == 0){
    if(autoPairing() == PAIR_PAIRED ){
      state = 1;
      //rainbow(10);
      String s = "rainbow";
       s.toCharArray(colorType, 8);
    }
  } else{
    Serial.println(colorType);
    Serial.println(color);
    if(strcmp(colorType, "off") == 0){ 
      off();
    }else if(strcmp(colorType, "on") == 0){
      on(red(color),green(color),blue(color));
    }else if(strcmp(colorType, "wave") == 0){
      wafe(red(color),green(color),blue(color));
    }else if(strcmp(colorType, "fade") == 0){
      // To do
    }else if(strcmp(colorType, "forest") == 0){
      forest();
    }else if(strcmp(colorType, "fight") == 0){
      fight();
    }else if(strcmp(colorType, "tavern") == 0){
      tavern();
    }else if(strcmp(colorType, "water") == 0){
      water();
    }else if(strcmp(colorType, "rainbow") == 0){
      rainbow(10);
    }
  }
}