/*
 * FILENAME: Gray_ButtonTogglerTimer.ino
 *
 * Code by: Gray Mack
 * License: MIT License
 *          https://choosealicense.com/licenses/mit/
 * Created: 10/12/2022
 * One Line Description: Demonstrate intermediate Arduino techniques using some libraries
 * Board: Arduino Uno R3 compatible such as Inventr.io Hero board
 * Select Board: Arduino UNO
 * Other Hardware: Keypad from "30 Days Lost In Space kit" or similar parts (I have no affiliation with this vendor)
 * Simulation at: https://wokwi.com/projects/345363308263506516
 * Source control at: TBD
 * Video documentation: TBD
 * I don't respond to IM. No specific support is offered though I am in a few Facebook arduino forums.
 * 
 * Detailed Description: 
 * Use a button debouncer to toggle LEDs on or off.
 * I only need 3 buttons, but I had a 16 button matrix. Thus, only using left column of buttons labled s5,s9,s13
 * Demonstrate the arduino-timer library which was the simplest timer library I could find for non repeating tasks
 * The LEDs can either be toggled by buttons or go off on their own after a few seconds
 * Using a mini speaker or piezo element is optional
 * No delays used
 *
 * Exercise 1: What would happen if a button was held down past the duration of the turn off timer?
 *             Will the LED stay on or go off? When it is finally released, would anything special happen?
 * Exercies 2: See end of code
 * Exercise 3: Add a fourth button+LED
 * Exercise 4: The tone command takes 3 arguments: pin, frequency, and optionally how long to stay on in milliseconds, 0=forever
 *             It runs in the background and does not hold up the execution regardless of the duration in constant SPEAKER_BEEP_MS
 *             Can you make the last tone stay on but then call noTone(SPEAKER_PIN) when any goes off?
 *             It will probably be annoying.
 * Exercies 5: Add the TonePitch library and using this example https://github.com/RodrigoDornelles/arduino-tone-pitch/blob/master/examples/ArduinoTone/ArduinoTone.ino
 *             play standard musical notes like NOTE_C4 instead of 800
 * Exercise 6: Add another LED and make it blink every second using the timer example https://github.com/contrem/arduino-timer/blob/master/examples/blink/blink.ino
 *             (It should not interfeer with the existing behavior.)
 *             Note the example use of a little trick: digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); // toggle the LED
 *             This works on the UNO, however may not work on other platforms such as ESP processors
 *             This is because some processors do not reliably digitalRead a pin that is in OUTPUT mode.
 *             Can you change it to use a State variable instead?
 * Exercise 7: Uncomment the block of code called "Zip sounds"
 *             Now replace the tone() in the turn on/off functions with for example StartSoundZip(RED_TONE_FREQ, +25);
 *             For led off functions, down bend the frequency with -25 and for on functions, up bend with +25
 *             Don't forget to add SoundZip.tick(); in loop() so the timer can do it's thing
 * 
 * Rev History:
 * 10/12/2022 initial code creation
 * //2022 ...
 */

// ----[ configuration  ]------------------------------------

// ----[ included libraries ]------------------------------------
#include <Bounce2.h> // https://github.com/thomasfredericks/Bounce2/ tested with version 2.71.0
#include <arduino-timer.h> // https://github.com/contrem/arduino-timer tested with version 2.3.1

// ----[ pin definitions ]------------------------------------
const int BUTTON_RED_PIN   = 2;
const int BUTTON_GREEN_PIN = 3;
const int BUTTON_BLUE_PIN  = 4;
const int LED_RED_PIN      = 6;
const int LED_GREEN_PIN    = 7;
const int LED_BLUE_PIN     = 8;
// LED_BUILTIN is already defined as pin 13 on UNO
const int SPEAKER_PIN      = A5; // analog pin can also be used as digital out, also known as pin 18

// ----[ constants ]------------------------------------
const unsigned long AUTO_LIGHT_OFF_MS = 10000L;
const bool TIMER_NO_REPEAT = false;
const int SPEAKER_BEEP_MS = 20;
const int RED_TONE_FREQ = 800;
const int GREEN_TONE_FREQ = 1000;
const int BLUE_TONE_FREQ = 1200;

// ----[ global variables ]------------------------------------
Bounce ButtonRed = Bounce();
Bounce ButtonGreen = Bounce();
Bounce ButtonBlue = Bounce();

int RedState = LOW;
int GreenState = LOW;
int BlueState = LOW;

Timer<1, millis> RedTimer;
Timer<1, millis> GreenTimer;
Timer<1, millis> BlueTimer;

// ----[ code ]------------------------------------

void setup() {
   pinMode(LED_RED_PIN, OUTPUT);
   TurnRedOff(nullptr);
   pinMode(LED_GREEN_PIN, OUTPUT);
   TurnGreenOff(nullptr);
   pinMode(LED_BLUE_PIN, OUTPUT);
   TurnBlueOff(nullptr);

   ButtonRed.attach(BUTTON_RED_PIN, INPUT_PULLUP);
   ButtonGreen.attach(BUTTON_GREEN_PIN, INPUT_PULLUP);
   ButtonBlue.attach(BUTTON_BLUE_PIN, INPUT_PULLUP);

   pinMode(SPEAKER_PIN, OUTPUT);
   digitalWrite(SPEAKER_PIN, LOW); // caution low ohm load, do not write HIGH, tone command seems ok to use
}

/* See Exercise 7
// Zip sounds - instead of beep, make pitch bend zip tones
int StartSoundFrequency;
int CurrentSoundFrequency;
int SoundDirection;
Timer<1, millis> SoundZip; // a timer to bend the note up or down
bool SoundZipTone(void *)
{
  CurrentSoundFrequency += SoundDirection; // a positive or negative number
  if(   CurrentSoundFrequency > StartSoundFrequency+500 
     || CurrentSoundFrequency < StartSoundFrequency-500
     || CurrentSoundFrequency > 10000 
     || CurrentSoundFrequency < 0)
  {
    noTone(SPEAKER_PIN);
    return false; // stop
  }
  else{
    tone(SPEAKER_PIN, CurrentSoundFrequency);
    return true;
  }
}
void StartSoundZip(int StartFreq, int StartDirection)
{
  SoundZip.cancel();
  StartSoundFrequency = StartFreq;
  CurrentSoundFrequency = StartFreq;
  SoundDirection = StartDirection;
  SoundZip.every(5, SoundZipTone);
}
// end of block Zip sounds
*/

// Because this can called by the timer library, 
// it is required to take some object pointer (which is not needed for my use) and return a bool
// to call it manually use: TurnRedOff(nullptr)
bool TurnRedOff(void *)
{
  RedState = LOW;
  digitalWrite(LED_RED_PIN, RedState);
  tone(SPEAKER_PIN,RED_TONE_FREQ,SPEAKER_BEEP_MS); // speaker beep
  RedTimer.cancel(); // if manually turned off then we don't need the timer to turn it off later, no harm if the timer itself called this
  return TIMER_NO_REPEAT; // instruction to the timer library
}

void TurnRedOn()
{
  RedState = HIGH;
  digitalWrite(LED_RED_PIN, RedState);
  tone(SPEAKER_PIN,RED_TONE_FREQ,SPEAKER_BEEP_MS);
  RedTimer.in(AUTO_LIGHT_OFF_MS, TurnRedOff);
}

bool TurnGreenOff(void *)
{
  GreenState = LOW;
  digitalWrite(LED_GREEN_PIN, GreenState);
  tone(SPEAKER_PIN,GREEN_TONE_FREQ,SPEAKER_BEEP_MS);
  GreenTimer.cancel();
  return TIMER_NO_REPEAT;
}

void TurnGreenOn()
{
  GreenState = HIGH;
  digitalWrite(LED_GREEN_PIN, GreenState);
  tone(SPEAKER_PIN,GREEN_TONE_FREQ,SPEAKER_BEEP_MS);
  GreenTimer.in(AUTO_LIGHT_OFF_MS, TurnGreenOff);
}

bool TurnBlueOff(void *)
{
  BlueState = LOW;
  digitalWrite(LED_BLUE_PIN, BlueState);
  tone(SPEAKER_PIN,BLUE_TONE_FREQ,SPEAKER_BEEP_MS);
  BlueTimer.cancel();
  return TIMER_NO_REPEAT;
}

void TurnBlueOn()
{
  BlueState = HIGH;
  digitalWrite(LED_BLUE_PIN, BlueState);
  tone(SPEAKER_PIN,BLUE_TONE_FREQ,SPEAKER_BEEP_MS);
  BlueTimer.in(AUTO_LIGHT_OFF_MS, TurnBlueOff);
}

void loop() {
  ButtonRed.update();
  if(ButtonRed.fell())
  {
    if(RedState == LOW)
      TurnRedOn();
    else
      TurnRedOff(nullptr);
  }

  ButtonGreen.update();
  if(ButtonGreen.fell())
  {
    if(GreenState == LOW)
      TurnGreenOn();
    else
      TurnGreenOff(nullptr);
  }

  ButtonBlue.update();
  if(ButtonBlue.fell())
  {
    if(BlueState == LOW)
      TurnBlueOn();
    else
      TurnBlueOff(nullptr);
  }

  RedTimer.tick();
  GreenTimer.tick();
  BlueTimer.tick();

  //If we dig down in arduino source, we see HIGH == 1 == true and LOW == 0 == false
  //This is pretty fundamental in many arduino toolchains and would certainly never change 
  //So this will make builtin led be true/HIGH if any LED is true/HIGH
  digitalWrite(LED_BUILTIN, RedState || GreenState || BlueState); // logical or
  // Exercise 2: explain what would happen if this were changed to any of the following
  //digitalWrite(LED_BUILTIN, RedState | GreenState | BlueState); // bitwise or
  //digitalWrite(LED_BUILTIN, RedState & GreenState & BlueState); // bitwise and
  //digitalWrite(LED_BUILTIN, RedState ^ GreenState ^ BlueState); // bitwise xor
  //digitalWrite(LED_BUILTIN, (RedState & GreenState) | (GreenState & BlueState));

}