//////////////////////////////////////////////////////////////////////
// Lamp Driver
// 06.10.2024
// V1.0
//////////////////////////////////////////////////////////////////////
//  global variable definition & preprocessor def
//  enum type for Finite State Machine
typedef enum {
  FSM_REEDSW_SIGNAL_DETECTION,   
  FSM_LIGHT_ON,    
	FSM_LIGHT_OFF,  
} light_fsm_t;
// debug level 0 - no debug, 1 - normal level, 2 - high level
#define DEBUG_LEVEL 1
// time grain for main loop, default time is 10ms
#define MAIN_LOOP_TIME_GRAIN_MS	10
// debounce time to check edge, default time is 20ms
#define DEBOUNCE_INTERVAL 20
// pinout definition for Arduino nano
int RELAY_Output = A0;
int MODESW_Input = 10;
int REEDSW_Input = 8;
// global variables
// reed switch variables
bool current_signal_level = 0, last_signal_level = 0;
bool rising_edge_flag = 0, falling_edge_flag = 0;
bool rising_edge_detected = 0, falling_edge_detected = 0;
// common variables
unsigned long previous_millis_Debounce = 0;
unsigned long previous_millis_Main_Loop = 0;
bool aux_flag = 1;
// initial state
light_fsm_t state = FSM_REEDSW_SIGNAL_DETECTION;
//////////////////////////////////////////////////////////////////////
// setup definition 
void setup() {
  Serial.begin(9600);
  pinMode(RELAY_Output, OUTPUT);
  pinMode(MODESW_Input, INPUT_PULLUP);
  pinMode(REEDSW_Input, INPUT_PULLUP);
  
  //safety: set drive stop during startup
  digitalWrite(RELAY_Output, LOW);
  
  // Log after restart
  Serial.println("Controller restart ...");
        
}
//////////////////////////////////////////////////////////////////////
// function to detect REED switch signal
static void Reedsw_Signal_Detection(void)
{ 
  current_signal_level = digitalRead(REEDSW_Input);
  // rising edge detection
  if ((current_signal_level == HIGH) && (last_signal_level == LOW)) {
    rising_edge_flag = 1;
    falling_edge_flag = 0;
    last_signal_level = current_signal_level;
    previous_millis_Debounce = millis();
  }
  // debounce time to check HIGH level is stable
  if (rising_edge_flag == HIGH) {
    if (millis() - previous_millis_Debounce > DEBOUNCE_INTERVAL) {
      if (current_signal_level == HIGH) {
        rising_edge_detected = 1;
        falling_edge_detected = 0;
        rising_edge_flag = 0;
        falling_edge_flag = 0;
        #if DEBUG_LEVEL > 1
          Serial.println("RISING edge detected");
        #endif
      } 
    }
  }
  // falling edge detection
  if ((current_signal_level == LOW) && (last_signal_level == HIGH)) {
    rising_edge_flag = 0;
    falling_edge_flag = 1;
    last_signal_level = current_signal_level;
    previous_millis_Debounce = millis();
  }
  // debounce time to check LOW level is stable
  if (falling_edge_flag == HIGH) {
    if (millis() - previous_millis_Debounce > DEBOUNCE_INTERVAL) {
      if (current_signal_level == LOW) {
        rising_edge_detected = 0;
        falling_edge_detected = 1;
        rising_edge_flag = 0;
        falling_edge_flag = 0;
        #if DEBUG_LEVEL > 1
          Serial.println("FALLING edge detected");
        #endif
      } 
    }
  }
}
//////////////////////////////////////////////////////////////////////
// function to Light turn On
static void Light_On(void)
{ 
  digitalWrite(RELAY_Output, HIGH);
  #if DEBUG_LEVEL > 1
    Serial.println("Turn light ON");
  #endif
}
//////////////////////////////////////////////////////////////////////
// function to Light turn Off
static void Light_Off(void)
{ 
  digitalWrite(RELAY_Output, LOW);
  #if DEBUG_LEVEL > 1
    Serial.println("Turn light OFF");
  #endif
}
//////////////////////////////////////////////////////////////////////
// Light Control Finite State Machine definition
static void LIGHT_FSM(void)
{
  switch (state)
  {
	  case FSM_REEDSW_SIGNAL_DETECTION:
      //state FSM_REEDSW_SIGNAL_DETECTION
      //call every 10ms according TIME_GRAIN
      #if (DEBUG_LEVEL > 0)
        if (aux_flag) Serial.println("State: REEDSW SIGNAL DETECTION"); aux_flag = 0; 
      #endif
      
      Reedsw_Signal_Detection();
      // Forced LIGHT_ON mode
      if (digitalRead(MODESW_Input) == 0) {
        state = FSM_LIGHT_ON;
      // Auto LIGHT ON/OFF mode  
      } else {
        if (rising_edge_detected == 1) {
          if (digitalRead(RELAY_Output) == 0) {
            state = FSM_LIGHT_ON;
            rising_edge_detected = 0;
            aux_flag = 1;
          } else {
            state = FSM_LIGHT_OFF;
            rising_edge_detected = 0;
            aux_flag = 1;
          }
        } 
      }
      break;
   
    case FSM_LIGHT_ON:
      //state FSM_LIGHT_ON
      #if (DEBUG_LEVEL > 0)
        if (aux_flag) Serial.println("State: LIGHT ON");
      #endif
      Light_On();
      state = FSM_REEDSW_SIGNAL_DETECTION;
      break;
    case FSM_LIGHT_OFF:
      //state FSM_LIGHT_OFF
      #if (DEBUG_LEVEL > 0)
        if(aux_flag) Serial.println("State: LIGHT OFF");
      #endif
      Light_Off();
      state = FSM_REEDSW_SIGNAL_DETECTION;
      break; 
    default:
      //IN CASE OF ERROR
      #if (DEBUG_LEVEL > 0)
        if(aux_flag) Serial.println("State: DEFAULT in case of error");
      #endif
      Light_Off();
      state = FSM_REEDSW_SIGNAL_DETECTION;
      break;
	}
}
//////////////////////////////////////////////////////////////////////
// Main program here
void loop()
{  
  // call LIGHT_FSM state machine every TIME_GRAIN
  if (millis() - previous_millis_Main_Loop > MAIN_LOOP_TIME_GRAIN_MS)
  {
    // call state machine
    LIGHT_FSM();
    // set current time as previous
    previous_millis_Main_Loop = millis();
  }
}