//
// HackerBox 0104 firmware for Alien Robot Badge
//
// Listens to neighboring units on ESP-NOW to trigger display modes
// Also periodically broadcasts ESP-NOW trigger packets 
//
// Hold top button during boot to lock into random cycling
//   attract mode (no wireless trigger) - reset to exit.
//
// ESP-NOW wireless functionality inspired by Rui Santos' example here:
// https://randomnerdtutorials.com/esp-now-two-way-communication-esp32/
//

#include <esp_now.h>
#include <WiFi.h>
#include <FastLED.h>

#define LEDpin          1
#define buzzerPin       4
#define topButtonPin    6
#define botButtonPin    7
#define NUM_LEDS       32

CRGB leds[NUM_LEDS];

typedef struct pkt_struct {
    uint32_t tag;
    uint8_t display_mode;
    uint8_t arg[10];
} struct_message;

pkt_struct rx_packet;
pkt_struct tx_packet;
esp_now_peer_info_t peerInfo;

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

unsigned long lastTime = 0;  
unsigned long tx_timer = 10000;  // send wireless trigger every 10 seconds

void setup() { 
  rx_packet.display_mode = 255;  //255 means "no new packet"
  pinMode(buzzerPin, OUTPUT);
  pinMode(topButtonPin, INPUT_PULLDOWN);
  pinMode(botButtonPin, INPUT_PULLDOWN);
  FastLED.addLeds<WS2812B, LEDpin, GRB>(leds, NUM_LEDS);
  FastLED.setBrightness(90);

  Serial.begin(115200);
  WiFi.mode(WIFI_STA);

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

  esp_now_register_send_cb(PacketSent);

  // Register peer
  memcpy(peerInfo.peer_addr, broadcastAddress, 6);
  peerInfo.channel = 0;  
  peerInfo.encrypt = false;
  
  // Add peer        
  if (esp_now_add_peer(&peerInfo) != ESP_OK){
    Serial.println("Failed to add peer");
    return;
  }
 
  // Register for a callback function that will be called when data is received
  esp_now_register_recv_cb(esp_now_recv_cb_t(PacketReceived));

  if(digitalRead(topButtonPin)){
    attract_mode();  
  }
}

void attract_mode(){
  int rnd, r, g, b;
  while(1){ // lock into attract mode - reset to exit
    rnd = random(3);
    switch(rnd){
      case 0:
        r = 255;
        g = 0;
        b = 0;
        break;
      case 1:
        r = 0;
        g = 255;
        b = 0;
        break;
      case 2:
        r = 0;
        g = 0;
        b = 255;
        break;
    }
    RunDisplayMode(random(20), r, g, b);
    delay(4000);
  }
}

void loop()
{
  // Default mode for our local display
  eyes_smile(0,255,0);  //green smile

  // Periodically broadcast display trigger to nearby units
  // This broadcasts a trigger for RED EYE_ROLL (Mode=5)
  // But set it to whatever mode you want your badge to send to others
  if ((millis() - lastTime) > tx_timer) {
    tx_packet.tag = 0x13374AC5;  //elite hacks :)
    tx_packet.display_mode = 5;
    tx_packet.arg[0] = 255;  
    tx_packet.arg[1] = 0;
    tx_packet.arg[2] = 0;
    esp_now_send(broadcastAddress, (uint8_t *) &tx_packet, sizeof(tx_packet));
    lastTime = millis();
  }

  if (rx_packet.display_mode != 255){ // is there a new packet?
    uint8_t r = rx_packet.arg[0];
    uint8_t g = rx_packet.arg[1];
    uint8_t b = rx_packet.arg[2];
    RunDisplayMode(rx_packet.display_mode, r, g, b);
    delay(1000);
    rx_packet.display_mode = 255; // return to no_packet state
  }
}

void eyes_open(uint8_t red, uint8_t green, uint8_t blue) { 
  int count = 16; //quantity of pixels to turn on
  int pixels[] = {1,2,4,7,8,11,13,14,17,18,20,23,24,27,29,30};

  fill_solid(leds, NUM_LEDS, 0);
  for (int i = 0; i < count; i++){
      leds[(pixels[i])] = CRGB(red,green,blue);
  }
  FastLED.show();
}

void eyes_closed(uint8_t red, uint8_t green, uint8_t blue) { 
  int count = 8; //quantity of pixels to turn on
  int pixels[] = {1,7,8,14,17,23,24,30};
  fill_solid(leds, NUM_LEDS, 0);
  for (int i = 0; i < count; i++){
      leds[(pixels[i])] = CRGB(red,green,blue);
  }
  FastLED.show();
}

void eyes_leftwink(uint8_t red, uint8_t green, uint8_t blue) { 
  int count = 12; //quantity of pixels to turn on
  int pixels[] = {1,7,8,14,17,18,20,23,24,27,29,30};

  fill_solid(leds, NUM_LEDS, 0);
  for (int i = 0; i < count; i++){
      leds[(pixels[i])] = CRGB(red,green,blue);
  }
  FastLED.show();
}

void eyes_rightwink(uint8_t red, uint8_t green, uint8_t blue) { 
  int count = 12; //quantity of pixels to turn on
  int pixels[] = {1,2,4,7,8,11,13,14,17,23,24,30};

  fill_solid(leds, NUM_LEDS, 0);
  for (int i = 0; i < count; i++){
      leds[(pixels[i])] = CRGB(red,green,blue);
  }
  FastLED.show();
}

void eyes_roll(uint8_t red, uint8_t green, uint8_t blue) { 
  int count = 8; //quantity of pixels to turn on
  int pixels[] = {1,2,4,11,13,14,8,7};
  for (int i = 0; i < count; i++){
      fill_solid(leds, NUM_LEDS, 0);
      leds[(pixels[i])] = CRGB(red,green,blue);
      leds[(pixels[i])+16] = CRGB(red,green,blue);
      FastLED.show();
      tone(buzzerPin, 600, 35);
      delay(50);
  }
}

void eyes_upperleft(uint8_t red, uint8_t green, uint8_t blue) { 
  int count = 8; //quantity of pixels to turn on
  int pixels[] = {2,3,4,5,18,19,20,21};
  
  fill_solid(leds, NUM_LEDS, 0);
  for (int i = 0; i < count; i++){
      leds[(pixels[i])] = CRGB(red,green,blue);
  }
  FastLED.show();
}

void eyes_uppercenter(uint8_t red, uint8_t green, uint8_t blue) { 
  int count = 8; //quantity of pixels to turn on
  int pixels[] = {4,5,10,11,20,21,26,27};
  
  fill_solid(leds, NUM_LEDS, 0);
  for (int i = 0; i < count; i++){
      leds[(pixels[i])] = CRGB(red,green,blue);
  }
  FastLED.show();
}

void eyes_upperright(uint8_t red, uint8_t green, uint8_t blue) { 
  int count = 8; //quantity of pixels to turn on
  int pixels[] = {10,11,12,13,26,27,28,29};

  fill_solid(leds, NUM_LEDS, 0);
  for (int i = 0; i < count; i++){
      leds[(pixels[i])] = CRGB(red,green,blue);
  }
  FastLED.show();
}

void eyes_brows(uint8_t red, uint8_t green, uint8_t blue) { 
  int count = 4; //quantity of pixels to turn on
  int pixels[] = {2,4,27,29};

  fill_solid(leds, NUM_LEDS, 0);
  for (int i = 0; i < count; i++){
      leds[(pixels[i])] = CRGB(red,green,blue);
  }
  FastLED.show();
}

void eyes_blink(uint8_t red, uint8_t green, uint8_t blue) { 
  eyes_open(red, green, blue);
  delay(300);

  eyes_closed(red, green, blue);
  tone(buzzerPin, 1000, 200);
  delay(150);

  eyes_open(red, green, blue);
  delay(300);
}

void eyes_shifty(uint8_t red, uint8_t green, uint8_t blue) { 
  eyes_upperleft(red, green, blue);
  tone(buzzerPin, 1500, 200);
  delay(300);

  eyes_uppercenter(red, green, blue);
  tone(buzzerPin, 1500, 200);
  delay(300);

  eyes_upperright(red, green, blue);
  tone(buzzerPin, 1500, 200);
  delay(300);

  eyes_uppercenter(red, green, blue);
  tone(buzzerPin, 1500, 200);
  delay(300);

  eyes_upperleft(red, green, blue);
  tone(buzzerPin, 1500, 200);
  delay(300);
}

void eyes_allrandcolors() {
  fill_solid(leds, NUM_LEDS, 0);
  fadeToBlackBy(leds, NUM_LEDS, 10);
  for(int i=0; i<150; i++) {
    int pos = random16(NUM_LEDS);
    leds[pos] = CHSV(random8(255), 200, 255);
    FastLED.show();  
    FastLED.delay(20);
  }
}

void eyes_circlerandcolors() {
  fill_solid(leds, NUM_LEDS, 0);
  for(int i=0; i<100; i++) {
    int count = 16; //quantity of pixels to turn on
    int pixels[] = {1,2,4,7,8,11,13,14,17,18,20,23,24,27,29,30};
    leds[pixels[random8(count)]] = CHSV(random8(255), 200, 255); 
    FastLED.show();  
    FastLED.delay(40);
  }
}

void rick_roll_audio(){
  int melody[] = { 466, 466, 415, 415, 698, 698, 622, 466, 466, 415,
  415, 622, 622, 554, 523, 466, 554, 554, 554, 554, 554, 622, 523, 
  466, 415, 415, 415, 622, 554};
   
  int rhythmn[]  = { 1, 1, 1, 1, 3, 3, 6, 1, 1, 1, 1, 3, 3, 3, 1, 2,
  1, 1, 1, 1, 3, 3, 3, 1, 2, 2, 2, 4, 8};

  volatile int beatlength = 100; 
  float beatseparationconstant = 0.3;
  boolean play = true;
  int b = 0; 
  int c = 0;
  while (play){
    int notelength;

    notelength = beatlength * rhythmn[b];
    if (melody[b] > 0) {
      tone(buzzerPin, melody[b], notelength);
      c++;
    }
    b++;
    if (b >= sizeof(melody) / sizeof(int)) {
      play=false; 
    }
    delay(notelength);
    noTone(buzzerPin);
    delay(notelength * beatseparationconstant);
  }
}

void eyes_IOI(uint8_t red, uint8_t green, uint8_t blue) { 
  //Enemy of Art3mis, the sixer fixer! 
  int count = 16; //quantity of pixels to turn on
  int pixels[] = {0,1,2,3,9,10,12,15,16,19,21,22,28,29,30,31};

  fill_solid(leds, NUM_LEDS, 0);
  for (int i = 0; i < count; i++){
      leds[(pixels[i])] = CRGB(red,green,blue);
  }
  FastLED.show();
}

void eyes_HI(uint8_t red, uint8_t green, uint8_t blue) { 
  int count = 14; //quantity of pixels to turn on
  int pixels[] = {4,5,6,7,10,13,16,17,18,19,24,25,26,27};

  fill_solid(leds, NUM_LEDS, 0);
  for (int i = 0; i < count; i++){
      leds[(pixels[i])] = CRGB(red,green,blue);
  }
  FastLED.show();
}

void eyes_glider(uint8_t red, uint8_t green, uint8_t blue) { 
  int count = 7; //quantity of pixels to turn on
  int pixels[] = {8,15,16,23,22,18,12};

  fill_solid(leds, NUM_LEDS, 0);
  for (int i = 0; i < count; i++){
      leds[(pixels[i])] = CRGB(red,green,blue);
  }
  FastLED.show();
}

void eyes_smile(uint8_t red, uint8_t green, uint8_t blue) { 
  int count = 8; //quantity of pixels to turn on
  int pixels[] = {11,20,6,8,15,16,23,25};

  fill_solid(leds, NUM_LEDS, 0);
  for (int i = 0; i < count; i++){
      leds[(pixels[i])] = CRGB(red,green,blue);
  }
  FastLED.show();
}

void eyes_triange_wave(uint8_t red, uint8_t green, uint8_t blue) { 
  int count = 8;
  int px0[] = {0,6,10,12,18,22,24,30};
  int px1up[] = {1,5,11,13,17,23,25,29};
  int px1dn[] = {1,7,9,13,19,21,25,31};
  int px2up[] = {2,4,10,14,16,22,26,28};
  int px2dn[] = {2,6,8,14,18,20,26,29};
  int px3[] = {3,5,9,15,17,21,27,29};

  for(int r=0; r<10; r++){
    fill_solid(leds, NUM_LEDS, 0);
    for (int i = 0; i < count; i++){
      leds[(px0[i])] = CRGB(red,green,blue);
    }
    FastLED.show();
    delay(50);
        fill_solid(leds, NUM_LEDS, 0);
    for (int i = 0; i < count; i++){
      leds[(px1up[i])] = CRGB(red,green,blue);
    }
    FastLED.show();
    delay(50);
        fill_solid(leds, NUM_LEDS, 0);
    for (int i = 0; i < count; i++){
      leds[(px2up[i])] = CRGB(red,green,blue);
    }
    FastLED.show();
    delay(50);
        fill_solid(leds, NUM_LEDS, 0);
    for (int i = 0; i < count; i++){
      leds[(px3[i])] = CRGB(red,green,blue);
    }
    FastLED.show();
    delay(50);
        fill_solid(leds, NUM_LEDS, 0);
    for (int i = 0; i < count; i++){
      leds[(px2dn[i])] = CRGB(red,green,blue);
    }
    FastLED.show();
    delay(50);
        fill_solid(leds, NUM_LEDS, 0);
    for (int i = 0; i < count; i++){
      leds[(px1dn[i])] = CRGB(red,green,blue);
    }
    FastLED.show();
    delay(50);
  }
}

void eyes_sweeper() 
{
  uint8_t gHue = 0;
  for(int n=0; n<600; n++){
    fadeToBlackBy( leds, NUM_LEDS, 10);
    int pos = beatsin16( 25, 0, NUM_LEDS-1 );
    leds[pos] += CHSV( gHue, 255, 192);
    FastLED.show();
    delay(10);
    gHue++;
  }
}

void PacketReceived(const uint8_t * mac, const uint8_t *incomingData, int len) {
  memcpy(&rx_packet, incomingData, sizeof(rx_packet));
  if(rx_packet.tag == 0x13374AC5) { //check tag to verify it's one of ours
    tone(buzzerPin, 4000, 10); // quick peep to tell us packet was received
  } else {
    rx_packet.display_mode = 255; // remain in "no_packet" state
  }
}

void PacketSent(const uint8_t *mac_addr, esp_now_send_status_t status) {
  Serial.print("\r\nLast Packet Send Status:\t");
  Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}

void RunDisplayMode(uint8_t mode, uint8_t r, uint8_t g, uint8_t b){
  switch(mode){
    case 0:
      eyes_open(r,g,b);
      delay(3000);
      break;
    case 1:
      eyes_closed(r,g,b);
      delay(3000);
      break;
    case 2:      
      eyes_leftwink(r,g,b);
      delay(3000);
      break;
    case 3:      
      eyes_rightwink(r,g,b);
      delay(3000);
      break;
    case 4:      
      eyes_brows(r,g,b);
      delay(3000);
      break;
    case 5:      
      eyes_roll(r,g,b);
      delay(3000);
      break;
    case 6:      
      eyes_upperleft(r,g,b);
      delay(3000);
      break;
    case 7:      
      eyes_uppercenter(r,g,b);
      delay(3000);
      break;
    case 8:      
      eyes_upperright(r,g,b);
      delay(3000);
      break;
    case 9:      
      eyes_blink(r,g,b);
      delay(3000);
      break;
    case 10:      
      eyes_shifty(r,g,b);
      delay(3000);
      break;
    case 11:      
      eyes_circlerandcolors();
      break;
    case 12:      
      eyes_allrandcolors();
      break;
    case 13:      
      rick_roll_audio();
      break;
    case 14:      
      eyes_IOI(r,g,b);
      delay(3000);
      break;
    case 15:      
      eyes_HI(r,g,b);
      delay(3000);
      break;
    case 16:      
      eyes_glider(r,g,b);
      delay(3000);
      break;
    case 17:      
      eyes_smile(r,g,b);
      delay(3000);
      break;
    case 18:      
      eyes_triange_wave(r,g,b);
      break;
    case 19:      
      eyes_sweeper();
      break;
  }
}