// https://wokwi.com/projects/409770554156515329
// https://forum.arduino.cc/t/toggle-switch-state/1303593

//================================================^================================================
//
//  https://forum.arduino.cc/t/toggle-switch-state/1303593
//
//
//
//  Version    YY/MM/DD    Comments
//  =======    ========    ========================================================================
//  1.00       24/09/20    Running code
//  1.10       24/09/21    Minor changes, added comments, baud ate changed to 115200
//  1.20       24/09/21    Added IR code, added LDR code
//  1.30       24/09/22    Added IR keypad button controls for LarryD's remote, delete these as needed
//  1.40       24/09/22    A switch operation is now reversed
//  1.50       24/09/22    Modified so any change in the toggle switch state operates the servo
//
//
//  Notes:
//


#include <IRremote.hpp>
#include <Servo.h>


//================================================
#define LEDon              HIGH   //PIN---[220R]---A[LED]K---GND
#define LEDoff             LOW

#define PRESSED            LOW    //+5V---[Internal 50k]---PIN---[Switch]---GND
#define RELEASED           HIGH

#define CLOSED             LOW    //+5V---[Internal 50k]---PIN---[Switch]---GND
#define OPENED             HIGH

#define ENABLED            true
#define DISABLED           false

#define MAXIMUM            180
#define MINIMUM            0

//                          millis() / micros()   B a s e d   T I M E R S
//================================================^================================================
//To keep the sketch tidy, you can put this structure in a different tab in the IDE
//
//These TIMER objects are non-blocking
struct makeTIMER
{
#define MILLIS             0
#define MICROS             1

#define ENABLED            true
#define DISABLED           false

#define YES                true
#define NO                 false

#define STILLtiming        0
#define EXPIRED            1
#define TIMERdisabled      2

  //these are the bare minimum "members" needed when defining a TIMER
  byte                     TimerType;      //what kind of TIMER is this? MILLIS/MICROS
  unsigned long            Time;           //when the TIMER started
  unsigned long            Interval;       //delay time which we are looking for
  bool                     TimerFlag;      //is the TIMER enabled ? ENABLED/DISABLED
  bool                     Restart;        //do we restart this TIMER   ? YES/NO

  //================================================
  //condition returned: STILLtiming (0), EXPIRED (1) or TIMERdisabled (2)
  //function to check the state of our TIMER  ex: if(myTimer.checkTIMER() == EXPIRED);
  byte checkTIMER()
  {
    //========================
    //is this TIMER enabled ?
    if (TimerFlag == ENABLED)
    {
      //========================
      //has this TIMER expired ?
      if (getTime() - Time >= Interval)
      {
        //========================
        //should this TIMER restart again?
        if (Restart == YES)
        {
          //restart this TIMER
          Time = getTime();
        }

        //this TIMER has expired
        return EXPIRED;
      }

      //========================
      else
      {
        //this TIMER has not expired
        return STILLtiming;
      }

    } //END of   if (TimerFlag == ENABLED)

    //========================
    else
    {
      //this TIMER is disabled
      return TIMERdisabled;
    }

  } //END of   checkTime()

  //================================================
  //function to enable and restart this TIMER  ex: myTimer.enableRestartTIMER();
  void enableRestartTIMER()
  {
    TimerFlag = ENABLED;

    //restart this TIMER
    Time = getTime();

  } //END of   enableRestartTIMER()

  //================================================
  //function to disable this TIMER  ex: myTimer.disableTIMER();
  void disableTIMER()
  {
    TimerFlag = DISABLED;

  } //END of    disableTIMER()

  //================================================
  //function to restart this TIMER  ex: myTimer.restartTIMER();
  void restartTIMER()
  {
    Time = getTime();

  } //END of    restartTIMER()

  //================================================
  //function to force this TIMER to expire ex: myTimer.expireTimer();
  void expireTimer()
  {
    //force this TIMER to expire
    Time = getTime() - Interval;

  } //END of   expireTimer()

  //================================================
  //function to set the Interval for this TIMER ex: myTimer.setInterval(100);
  void setInterval(unsigned long value)
  {
    //set the Interval
    Interval = value;

  } //END of   setInterval()

  //================================================
  //function to return the current time
  unsigned long getTime()
  {
    //return the time             i.e. millis() or micros()
    //========================
    if (TimerType == MILLIS)
    {
      return millis();
    }

    //========================
    else
    {
      return micros();
    }

  } //END of   getTime()

}; //END of   struct makeTIMER


//                            D e f i n e   a l l   a r e   T I M E R S
//================================================^================================================
/*example
  //========================
  makeTIMER toggleLED =
  {
     MILLIS/MICROS, 0, 500ul, ENABLED/DISABLED, YES/NO  //.TimerType, .Time, .Interval, .TimerFlag, .Restart
  };

  TIMER functions we can access:
  toggleLED.checkTIMER();
  toggleLED.enableRestartTIMER();
  toggleLED.disableTIMER();
  toggleLED.expireTimer();
  toggleLED.setInterval(100ul);
*/

//========================
makeTIMER heartbeatTIMER =
{
  MILLIS, 0, 250ul, ENABLED, YES       //.TimerType, .Time, .Interval, .TimerFlag, .Restart
};

//========================
makeTIMER switchesTIMER =
{
  MILLIS, 0, 50ul, ENABLED, YES        //.TimerType, .Time, .Interval, .TimerFlag, .Restart
};

//========================
makeTIMER CHECKirTIMER =
{
  MICROS, 0, 1000ul, ENABLED, YES      //.TimerType, .Time, .Interval, .TimerFlag, .Restart
};

//========================
makeTIMER servoTIMER =
{
  MILLIS, 0, 10ul, DISABLED, YES      //.TimerType, .Time, .Interval, .TimerFlag, .Restart
};


//                                            G P I O s
//================================================^================================================
//
const byte photoPin               = A0;

const byte IRpin                  = 2;
const byte toggleSwitchPin        = 5;

const byte servoPin               = 9;
const byte heartbeatLED           = 13;


//                                        V A R I A B L E S
//================================================^================================================
//
Servo servo;

bool photoSensor                  = DISABLED;  //LDR

byte lastButtonState              = RELEASED;

int currentServoPosition          = 0;
int destinationServoPosition      = MINIMUM;


//                                           s e t u p ( )
//================================================^================================================
void setup()
{
  Serial.begin(115200);

  IrReceiver.begin(IRpin);

  pinMode(toggleSwitchPin, INPUT_PULLUP);

  digitalWrite(heartbeatLED, LEDoff);
  pinMode(heartbeatLED, OUTPUT);

  //========================
  //connect the servo, it moves to 90°
  servo.attach(servoPin);
  delay(1000);

  currentServoPosition = 90;
  destinationServoPosition = MINIMUM;

  Serial.print("Servo position = ");
  Serial.println(currentServoPosition);

  //enabled the TIMER to allow the servo to step
  servoTIMER.enableRestartTIMER();

} //END of   setup()


//                                            l o o p ( )
//================================================^================================================
void loop()
{
  //========================================================================  T I M E R  heartbeatLED
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //is it time to toggle the heartbeat LED ?
  if (heartbeatTIMER.checkTIMER() == EXPIRED)
  {
    //toggle the heartbeat LED
    digitalWrite(heartbeatLED, digitalRead(heartbeatLED) == HIGH ? LOW : HIGH);
  }

  //========================================================================  T I M E R  switches
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //is it time to check our switches ?
  if (switchesTIMER.checkTIMER() == EXPIRED)
  {
    checkSwitches();
  }

  //========================================================================  T I M E R  CHECKir
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //is it time to check for an IR code ?
  if (CHECKirTIMER.checkTIMER() == EXPIRED)
  {
    checkIRcode();
  }

  //========================================================================  T I M E R  servo
  //condition returned: STILLtiming, EXPIRED or TIMERdisabled
  //if this TIMER is enabled, is it time to move the servo 1 step ?
  if (servoTIMER.checkTIMER() == EXPIRED)
  {
    //go CCW to maximum position ?
    if (destinationServoPosition == MAXIMUM && currentServoPosition <= destinationServoPosition)
    {
      //move one step CCW
      servo.write(currentServoPosition);

      Serial.print("CCW, servo position = ");
      Serial.println(currentServoPosition);

      Serial.print("Destination = ");
      Serial.println(destinationServoPosition);

      //get ready for the next step
      currentServoPosition++;

      //don't go over 180
      if (currentServoPosition > destinationServoPosition)
      {
        //re-adjust
        currentServoPosition = 180;

        Serial.println("We are at maximum, LDR disabled");

        //we have arrived at our destination, disable this TIMER
        servoTIMER.disableTIMER();

        //cancel an LDR pending control
        photoSensor = DISABLED;
      }
    }

    //go CW to minimum position ?
    else if (destinationServoPosition == MINIMUM && currentServoPosition >= destinationServoPosition)
    {
      //move one step CW
      servo.write(currentServoPosition);

      Serial.print("CW, servo position = ");
      Serial.println(currentServoPosition);

      Serial.print("Destination = ");
      Serial.println(destinationServoPosition);

      //get ready for the next step
      currentServoPosition--;

      //don't go under 0
      if (currentServoPosition < 0)
      {
        //re-adjust
        currentServoPosition = 0;

        Serial.println("We are at minimum, LDR disabled");

        //we have arrived at our destination, disable this TIMER
        servoTIMER.disableTIMER();

        //cancel an LDR pending control
        photoSensor = DISABLED;
      }
    }
  }


  //================================================
  //other non blocking code goes here
  //================================================


} //END of   loop()


//                                     c h e c k I R c o d e ( )
//================================================^================================================
void checkIRcode()
{
  //========================
  if (IrReceiver.decode())
  {
    static unsigned long rawData;

    //is this a repeat code ?
    if (IrReceiver.decodedIRData.decodedRawData == 0)
    {
      IrReceiver.resume();

      return;
    }

    //get the new IR code
    rawData = IrReceiver.decodedIRData.decodedRawData;

//    Serial.print("\nIR Receive code = ");
//    Serial.println(rawData, HEX);

    //============ LDR ON
    //when we are not at maximum, check for IR code to enable LDR


//... changed! so it is keys 7, 8 and 9
//    if (destinationServoPosition == MINIMUM && (rawData == 0xB847FF00 || rawData == 0xBA45FF00 || rawData == 0xBF40FF00))

//... say more aboiut this
    if (destinationServoPosition == MINIMUM && (rawData == 0xBD42FF00 || rawData == 0xB54AFF00 || rawData == 0xAD52FF00))
    {
      if (photoSensor == ENABLED) Serial.println("LDR still ENABLED");
      else {
        photoSensor = ENABLED;
        Serial.println("LDR got ENABLED");
      }
    }

    //============ go to 180° ?
    //Check for other IR codes (servo control)
    else if (destinationServoPosition == MINIMUM && (rawData == 0xBC43FF00 || rawData == 0xF609FF00 || rawData == 0xB946FF00))
    {
      destinationServoPosition = MAXIMUM;

      //enabled the TIMER to allow the servo to step
      servoTIMER.enableRestartTIMER();
    }

    //============ go to 0° ?
    else if (destinationServoPosition == MAXIMUM && (rawData == 0xBB44FF00 || rawData == 0xF807FF00 || rawData == 0xEA15FF00))
    {
      destinationServoPosition = MINIMUM;

      //enabled the TIMER to allow the servo to step
      servoTIMER.enableRestartTIMER();
    }

    IrReceiver.resume();

  } //END of   if (receiver.decode())

} //END of   checkIRcode()


//                                   c h e c k S w i t c h e s ( )
//================================================^================================================
void checkSwitches()
{
  byte pinState;
  int LDRvalue;

  //========================================================================  photoSensor
  //are we allowed to read the LDR ?
  if (photoSensor == ENABLED)
  {
    LDRvalue = analogRead(photoPin);

    //have we reached the trigger point ?
    if (LDRvalue > 300)
    {
      //we have now reached the trigger point, disable the LDR
      photoSensor = DISABLED;

      destinationServoPosition = MAXIMUM;

      //enabled the TIMER to allow the servo to step
      servoTIMER.enableRestartTIMER();
    }
  }

  //========================================================================  toggleSwitchPin
  pinState = digitalRead(toggleSwitchPin);

  //===================================
  //has this switch changed state ?
  if (lastButtonState != pinState)
  {
    //update to this new state
    lastButtonState = pinState;

    //========================
    //the switch has changed state
    //cancel any LDR pending control
    photoSensor = DISABLED;

    Serial.println("\nSwitch is released");

    //============
    //toggle the servo destination ?
    if (destinationServoPosition == MINIMUM)
    {
      destinationServoPosition = MAXIMUM;
    }

    //============
    //toggle the servo destination ?
    else if (destinationServoPosition == MAXIMUM)
    {
      destinationServoPosition = MINIMUM;
    }

    //allow the servo to step
    servoTIMER.enableRestartTIMER();

  } //END of  toggleSwitchPin

  //========================================================================  Next Switch

} //END of   checkSwitches()


//================================================^================================================