/*
 TEAMS BUTTONS
 =============

 Firmware for a set of NUM_BUTTONS push buttons with NeoPixel RGB LEDs,
 designed to work with the Teams Buttons Handler Python script
 (teams_button_handler.py) as a physical controller for Microsoft Teams.

 Roger Meier, 03/2024

 HARDWARE
 --------
 Board:      Teensy 2.0 (should work with most 5V Arduinos/Teensies)
 Buttons:    Connected to pins 0–3, active LOW with internal pull-ups
 NeoPixels:  Data line on pin 10 (one pixel per button)
             NeoPixels prefer a 5V data signal but work at 3.3V as well.
 Note:       NUM_BUTTONS, LONG_PRESS, and WATCHDOG_TIMEOUT can all be
             adjusted in the OPTIONS section below.

 DEPENDENCIES
 ------------
 - Bounce2          https://github.com/thomasfredericks/Bounce2
 - elapsedMillis    https://github.com/pfeerick/elapsedMillis/wiki
 - Adafruit NeoPixel https://github.com/adafruit/Adafruit_NeoPixel

 BUTTON ASSIGNMENTS
 ------------------
   Button 0:  Meeting  — short press: leave call       / long press: toggle recording
   Button 1:  Audio    — short press: toggle mute      / long press: send "like" reaction
   Button 2:  Video    — short press: toggle video     / long press: toggle background blur
   Button 3:  Hand     — short press: raise/lower hand / long press: send "applause" reaction
 Note: when not paired to Teams, any short press triggers a pairing attempt.
       Long-pressing button 0 while unpaired connects/disconnects the WebSocket.

 SERIAL PROTOCOL
 ---------------
 Outgoing (Arduino -> Host), one byte per button event:
   Short press (on release):  byte = button index (0 to NUM_BUTTONS-1)
   Long press  (after 2 sec): byte = button index + 128
   Example: button 1 short press = 0x01, button 1 long press = 0x81

 Incoming (Host -> Arduino), fixed-length packet of 3*NUM_BUTTONS+1 bytes:
   Byte 0:          command type
   Remaining bytes: 3 bytes (R, G, B) per NeoPixel, in button order
   Command type 0:  set NeoPixel colors
   Additional command types can be added as needed.

 WATCHDOG
 --------
 If no serial data is received for WATCHDOG_TIMEOUT milliseconds (default:
 60 seconds), the NeoPixels display a scrolling rainbow idle pattern. Normal
 LED control resumes automatically as soon as the next packet arrives.

*/

// IOs:
// ----
//  0:  Meeting Button
//  1:  Audio Button
//  2:  Video Button
//  3:  Hand Button
//  4:  
//  5:  
//  6:  
//  7:  
//  8:  
//  9:  
// 10:  Neopixels
// 11:  LED  (on-board LED on Teensy 2.0)
// 12:  
// 13:  
// 14:  
// 15:  
// 16:  
// 17:  
// 18:  
// 19:  
// 20:  
// 21:  
// 22:  
// 23: 

#include "Bounce2.h"            // https://github.com/thomasfredericks/Bounce2
#include <elapsedMillis.h>      // https://github.com/pfeerick/elapsedMillis/wiki
#include <Adafruit_NeoPixel.h>  // https://github.com/adafruit/Adafruit_NeoPixel

// OPTIONS
// -------
#define NUM_BUTTONS  4
#define LONG_PRESS  2000 // 2 second
#define WATCHDOG_TIMEOUT  60000 // 1 minute

// DEFINITIONS AND INITIALIZATIONS
// -------------------------------

// Built-in LED
#define LED  11

// Neopixels
#define PIXELS_PIN  10
Adafruit_NeoPixel pixels(NUM_BUTTONS, PIXELS_PIN, NEO_GRB + NEO_KHZ800);
uint8_t but_colors[3*NUM_BUTTONS]; // 3 bytes (red/green/blue values) for each neopixel
#define PATTERN_UPDATE_PERIOD  30
elapsedMillis sincePatternUpdate = 0;
long firstPixelHue = 0;

// Serial port
#define BAUDRATE  9600
#define SERIAL_TIMEOUT  3000  // timeout for incoming data
elapsedMillis sinceSerialRX = 0;

// Buttons
int but_pin[4] = {0,1,2,3}; // Initialize button pin numbers
Bounce2::Button but[NUM_BUTTONS]; // Instantiate button objects
// Button timer
elapsedMillis sincePress[NUM_BUTTONS];
bool is_pressed[NUM_BUTTONS];
bool is_long_press[NUM_BUTTONS];

// Watchdog timer
elapsedMillis sinceWatchdog = WATCHDOG_TIMEOUT; // enable idle pattern from the start
bool is_idle = true;



// ========================================================================================
// SETUP
// ========================================================================================

void setup() {
  // Start Serial
  Serial.begin(BAUDRATE);
  
  // Setup LED
  pinMode(LED, OUTPUT);
  
  // Initialize Buttons
  for(int i=0; i<NUM_BUTTONS; i++) {
    but[i] = Bounce2::Button();
    but[i].attach(but_pin[i], INPUT_PULLUP);
    but[i].interval(5); // debounce interval
    but[i].setPressedState(LOW); // pressed is LOW
    is_pressed[i] = false;
    is_long_press[i] = false;
  }

  // Clear the serial input buffer
  while(Serial.available()){
    Serial.read();
  }

  // Initialize Neopixels
  pixels.begin();
  pixels.clear();
  pixels.show();

}


// ========================================================================================
// MAIN LOOP
// ========================================================================================

void loop() {

  // WATCHDOG
  // --------

  if(Serial.available()) {
    // We have received something
    // Reset the watchdog timer
    sinceWatchdog = 0;
    if (is_idle){
      // clear all pixels
      pixels.clear();
      pixels.show();
    }
    is_idle = false;
  }
  if (sinceWatchdog >= WATCHDOG_TIMEOUT) {
    // We're idling
    is_idle = true;
    if (sincePatternUpdate > PATTERN_UPDATE_PERIOD){
      //rainbow();
      pixels.rainbow(firstPixelHue);
      pixels.show();
      firstPixelHue += 256;
      sincePatternUpdate = 0;
    }
  }


  // GET SERIAL DATA
  // ---------------

  if (Serial.available() == 0){
    // Reset the timeout timer
    sinceSerialRX = 0;
  }

  else if (Serial.available() < 3*NUM_BUTTONS+1){
    // We have not received the correct number of bytes yet
    if(sinceSerialRX > SERIAL_TIMEOUT){
      // Timeout! Clear the receive buffer
      while(Serial.available()){
        Serial.read();
      }
      sinceSerialRX = 0;
    }
  }

  else if (Serial.available() >= 3*NUM_BUTTONS+1) {
    // We have received the correct number of bytes
    char cmd = Serial.read();
    switch (cmd) {
      case 0: // Set LED colors
        // read the color values
        for(int i=0; i<3*NUM_BUTTONS; i++){
          but_colors[i] = Serial.read();
        }
        // Update the LEDs
        pixels.clear();
        for(int i=0; i<NUM_BUTTONS; i++) {
          pixels.setPixelColor(i, pixels.Color(but_colors[3*i], but_colors[3*i+1], but_colors[3*i+2]));
        }
        pixels.show();
        break;
      default: // unknown command
        // discard the data
        for(int i=0; i<3*NUM_BUTTONS; i++){
          Serial.read();
        }
        break;
    }
    sinceSerialRX = 0;
  }


  // CHECK THE BUTTONS
  // -----------------

  // Cycle through all buttons
  for(int i=0; i<NUM_BUTTONS; i++){
    // Update the current button
    but[i].update();

    // See if it has recently been released
    if(but[i].released()){
      if(!is_long_press[i]){ // This was a short press
        // Send serial code
        Serial.print(char(i));
      }
      // Reset the long press status
      is_long_press[i] = false;
      is_pressed[i] = false;
      
      continue; // We're done with this button
    }

    // See if it's currently being pressed
    if(but[i].isPressed()){
      // Update press timer if needed
      if(!is_pressed[i]){
        is_pressed[i] = true;
        sincePress[i] = 0;
      }
    }

    // See it's been long-pressed
    if(is_pressed[i] && (sincePress[i] > LONG_PRESS)){
      // The button has been pressed for longer than LONG_PRESS
      // send the long press code
      is_long_press[i] = true;
      is_pressed[i] = false;
      // Send serial code
      Serial.print(char(i+128));
    }

  }

}