// rgbLed.ino
//
// Version 1, 10 August 2021, by Koepel
// Version 2, 12 August 2021, by Koepel
//    Changed a few variable names.
//    Removed Serial.begin.
//    More comments.
//
// Click on the buttons to change the status of the project.
// Instead of showing the status on a LCD display, a soft pulsating RGB led
// shows the status.
// For example:
//   Green pulse with 1 Hz   : The project is running fine and busy
//   Red fast pulsating led  : Error state
//   Blue slow pulsating led : Waiting for input
//
// The RGB led is a Common Cathode type (common leg to GND).
// The Arduino pins to the led must be PWM pins.
//

const int rgbPins[] = { 11, 10, 9};  // PWM pins for Red, Green and Blue colors of RGB led.
const int buttonPins[] = { 4, 3, 2}; // input pins for Red, Green and Blue button.

float newStatus = 50.0;              // the new status from 0.0 to 100.0, 50 = Green
float filteredStatus = 50.0;         // low pas filtered status of the project from 0.0 to 100.0

float x = -0.5;                      // from -0.5 to 0.5, value on the time axis of the pulse

// A millis-timer of 40ms is used to update the PWM value for the RGB led
// The PWM signal itself is about 500Hz for a Arduino Uno
unsigned long previousMillis;
const unsigned long interval = 40;   // 40 ms interval for 25 Hz


void setup() 
{
  for( int i=0; i<3; i++)
    pinMode( rgbPins[i], OUTPUT);

  for( int i=0; i<3; i++)
    pinMode( buttonPins[i], INPUT_PULLUP);
}


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

  // The buttons do not need any debounce in this specific sketch.
  // They can be pressed long or short or multiple times.
  if( digitalRead(buttonPins[0]) == LOW)  // Red button, active LOW
    newStatus = 100.0;
  if( digitalRead(buttonPins[1]) == LOW)  // Green button, active LOW
    newStatus = 50.0;
  if( digitalRead(buttonPins[2]) == LOW)  // Blue button, active LOW
    newStatus = 0.0;

  // Millis timer to set the update rate of the PWM for RGB led,
  // and to provide a constant interval to calculate the pulse.
  if( currentMillis - previousMillis >= interval)
  {
    previousMillis = currentMillis;

    // Low pass filter.
    // Lower percentage for slower filter, higher percentage for faster filter
    const float pct = 1.8;     // percentage of new value added to filter
    filteredStatus = (( 1.0 - (pct / 100.0)) * filteredStatus) + (pct / 100.0 * newStatus);  

    float r, g, b;     // values 0...1
    float f;           // frequency of the pulse in Hz

    // Convert status (0...100) to hue and frequency
    //   0   = blue  = 0.5 Hz
    //   50  = green = 1.0 Hz
    //   100 = red   = 3.0 Hz
    if( filteredStatus < 50.0)
    {
      r = 0.0;
      g = filteredStatus / 50.0;
      b = 1.0 - g;
      f = 0.5 + (g / 2);      // 0.5 to 1.0 for lower range
    }
    else
    {
      r = (filteredStatus - 50.0) / 50.0;
      g = 1.0 - r;
      b = 0.0;
      f = 1.0 + (2 * r);     // 1.0 to 3.0 for higher range
    }

    // The standard deviation is:
    //    y = e ^ ( -0.5 * x * x )
    // This sketch uses:
    //    y = expf ( -s * squaref ( x ) )  // s = steepness, -50 is normal, -150 is steep
    // The 'y' will be used for the amplitude of the r, g, b values.
    float y = expf( -50.0 * squaref( x));

    // Increment 'x' for the time axis.
    // Keep 'x' between -0.5 and 0.5
    x += f * float( interval) / 1000.0;  // divide interval by 1000 because it is in milliseconds
    if( x >= 0.5)
      x -= 1.0;

    // Multiply each color by the amplitude of the pulse.
    // The minimal value is set to 1, because turning the led completely off 
    // didn't look good.
    int pwmR = (int) round( 1.0 + (r * y * 254.0));
    int pwmG = (int) round( 1.0 + (g * y * 254.0));
    int pwmB = (int) round( 1.0 + (b * y * 254.0));

    // Write the new PWM value to the PWM pins for the Red, Green and Blue led colors.
    analogWrite( rgbPins[0], pwmR);
    analogWrite( rgbPins[1], pwmG);
    analogWrite( rgbPins[2], pwmB);
  }
}