//////////////////////////////////////////////////////////////////////
// ASM Temp Profile
// Dominik Kowalczyk
// 20.11.2024
// V1.0
//////////////////////////////////////////////////////////////////////
//  global variable definition & preprocessor def
//  enum type for Finite State Machine
typedef enum {
  TIME_PROG_1,    
  TIME_PROG_2,   
  TIME_PROG_3,    
  TIME_PROG_4,  
  TIME_PROG_5,  
} fsm_time_t;
typedef enum {
  ASM_STATE_1,    
  ASM_STATE_2,   
  ASM_STATE_3, 
  ASM_STATE_4, 
  ASM_STATE_5, 
  ASM_STATE_6,
  ASM_STATE_7,  
  ASM_STATE_8,    
} fsm_asm_t;
// debug level 0 - no debug, 1 - normal level, 2 - high level, clk level
#define DEBUG_LEVEL 1
// time grain for main loop, default time is 10ms
#define MAIN_LOOP_TIME_GRAIN_MS	10
// time interval between movement in left and right, default time is 2000ms
#define STAND_OFF_TIME 2000
// debounce time to check edge, default time is 20ms
#define DEBOUNCE_INTERVAL 20
// pinout definition for Arduino nano
// A7, A6 ONLY !!! analog input
//int Analog_In_1 = A7
//int Analog_In_2 = A6
int ASM_Left = A5;
//int Digital_Out_1 = A5;
//int Digital_Out_2 = A4;
//int Digital_Out_3 = A3;
//int Digital_Out_4 = A2;
//int Digital_Out_5 = A1;
//int Digital_Out_6 = A0;
int ASM_Right = A0;
//int Digital_In_1 = 2;
//int Digital_In_2 = 3;
//int Digital_In_3 = 4;
int ACF_Dir = 4;
//int Digital_In_4 = 5;
//int Digital_In_5 = 6;
//int Digital_In_6 = 7;
//int Digital_In_7 = 8;
//int Digital_In_8 = 9;
//int Digital_In_9 = 10;
//int Digital_In_10 = 11;
//int Digital_In_11 = 12;
// global variables
bool current_signal_level = 0, last_signal_level = 0;
bool rising_edge_flag = 0, falling_edge_flag = 0; 
bool edge_detected = 0, rising_edge_detected = 0, falling_edge_detected = 0; 
//bool aux_flag_2 = 1;
bool drive_control_debug = 1;
bool drive_control_setup = 1;
bool time_prog_debug = 1;
bool time_prog_setup = 1;
bool time_counter_flag = 1;
unsigned long previous_millis_Debounce = 0;
unsigned long previous_millis_StandOff = 0;
unsigned long previous_millis_Main_Loop = 0;
unsigned long previous_millis_Timer = 0;
unsigned long previous_millis_Run = 0;
unsigned long T_Hour = 0;
int T_Min = 0;
int T_Sec = 0;
int T_Min_Prev = 0;
int T_Sec_Prev = 0;
int T_Right = 0;
int T_Left = 0;
int T_Right_Count = 0;
int T_Left_Count = 0;
// initial state machine
fsm_time_t state_time = TIME_PROG_1;
fsm_asm_t state_asm = ASM_STATE_1;
//////////////////////////////////////////////////////////////////////
// setup definition 
void setup() {
  // UART initialization
  Serial.begin(9600);
  // pin direction definition
  pinMode(ASM_Left, OUTPUT);
  pinMode(ASM_Right, OUTPUT);
  pinMode(ACF_Dir, INPUT_PULLUP);
  // safety: set drive stop during startup
  digitalWrite(ASM_Left, LOW);
  digitalWrite(ASM_Right, LOW);
  // Log after restart
  Serial.println("Arduino Nano restart ...");
  Serial.println("Wait until ADPU restart");
  delay(10000);
        
}
//////////////////////////////////////////////////////////////////////
// function to detect input signal edge
static void Edge_Detection(void)
{ 
  
  // reading current level of input signal
  //#if ADPU_TYPE == 0
  // Case 1
  //#else
  // Case 2
  //#endif
  current_signal_level = digitalRead(ACF_Dir);
  // po restarcie przy zalaczonym przelaczniku na stan wysoki generuje sygnal !!!
  // 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();
    #if DEBUG_LEVEL == 2
      Serial.println("RISING edge flag set");
    #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();
    #if DEBUG_LEVEL == 2
      Serial.println("FALLING edge flag set");
    #endif
  }
  // 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) {
        edge_detected = 1;
        rising_edge_detected = 1;
        falling_edge_detected = 0;
        rising_edge_flag = 0;
        #if DEBUG_LEVEL == 2
          Serial.println("RISING edge detected");
        #endif
      } 
    }
  }
  // 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) {
        edge_detected = 1;
        rising_edge_detected = 0;
        falling_edge_detected = 1;
        falling_edge_flag = 0;
        #if DEBUG_LEVEL == 2
          Serial.println("FALLING edge detected");
        #endif
      } 
    }
  }
}
//////////////////////////////////////////////////////////////////////
// function to stop ASM drive
static void Drive_Stop(void)
{ 
  digitalWrite(ASM_Left, LOW);
  digitalWrite(ASM_Right, LOW); 
  #if DEBUG_LEVEL == 2
    Serial.println("Drive STOP");
  #endif
}
//////////////////////////////////////////////////////////////////////
// function to set ASM rotaton left
static void Drive_Left(void)
{ 
  digitalWrite(ASM_Left, HIGH);
  digitalWrite(ASM_Right, LOW); 
  #if DEBUG_LEVEL == 2
    Serial.println("Drive LEFT");
  #endif
}
//////////////////////////////////////////////////////////////////////
// function to set ASM rotation right
static void Drive_Right(void)
{ 
  digitalWrite(ASM_Left, LOW);
  digitalWrite(ASM_Right, HIGH); 
  #if DEBUG_LEVEL == 2
    Serial.println("Drive RIGHT");
  #endif
}
//////////////////////////////////////////////////////////////////////
// function set ASM rotation 
static void Drive_Control(void)
{ 
  // call fsm_asm
  switch (state_asm)
  {
    case ASM_STATE_1:
      // State Debug 
      #if DEBUG_LEVEL >= 1
        if (drive_control_debug) Serial.println("ASM: Initialization");
        drive_control_debug = 0;
      #endif
      // State Setup
      previous_millis_StandOff = millis();
      // State Loop
      // State Transition
      state_asm = ASM_STATE_2;
      drive_control_debug = 1;
      break;
    case ASM_STATE_2:
      // State Debug 
      #if DEBUG_LEVEL >= 1
      if (drive_control_debug) Serial.println("ASM: Wait ... 2s");
        drive_control_debug = 0;
      #endif
      // State Setup
      // State Loop
      // State Transition
      if (millis() - previous_millis_StandOff > STAND_OFF_TIME)
      {
        state_asm = ASM_STATE_3;
        drive_control_debug = 1;
      }
      break;
    case ASM_STATE_3:
      // State Debug 
      #if DEBUG_LEVEL >= 1
        if (drive_control_debug) Serial.println("ASM: Run right");
        drive_control_debug = 0;
      #endif
      // State Setup
      Drive_Right();
      previous_millis_Run = millis();
      T_Right_Count = T_Right * 1000;
      // State Loop
      // State Transition
      state_asm = ASM_STATE_4;
      drive_control_debug = 1;
      break;
    case ASM_STATE_4:
      // State Debug 
      #if DEBUG_LEVEL == 2
        if (drive_control_debug) Serial.println("ASM: Wait at rigth rotation ...");
        drive_control_debug = 0;
      #endif
      
      // State Setup
      // State Loop
      // State Transition
      if (millis() - previous_millis_Run > T_Right_Count)
      {
        state_asm = ASM_STATE_5;
        drive_control_debug = 1;
        Drive_Stop();
        previous_millis_StandOff = millis();
      }
      break;
    case ASM_STATE_5:
      // State Debug
      #if DEBUG_LEVEL >= 1
      if (drive_control_debug) Serial.println("ASM: Wait ... 2s");
        drive_control_debug = 0;
      #endif
      // State Setup
      // State Loop
      // State Transition
      if (millis() - previous_millis_StandOff > STAND_OFF_TIME)
      {
        state_asm = ASM_STATE_6;
        drive_control_debug = 1;
      }
      break;
      case ASM_STATE_6:
      // State Debug
      #if DEBUG_LEVEL >= 1
        if (drive_control_debug) Serial.println("ASM: Run left");
        drive_control_debug = 0;
      #endif
      // State Setup
      Drive_Left();
      previous_millis_Run = millis();
      T_Left_Count = T_Left * 1000;
      // State Loop
      // State Transition
      state_asm = ASM_STATE_7;
      drive_control_debug = 1;
      break;
    case ASM_STATE_7:
      // State Debug
      #if DEBUG_LEVEL == 2
        if (drive_control_debug) Serial.println("ASM: Wait at left rotation ...");
        drive_control_debug = 0;
      #endif
      // State Setup
      // State Loop
      // State Transition
      if (millis() - previous_millis_Run > T_Left_Count)
      {
        state_asm = ASM_STATE_8;
        drive_control_debug = 1; 
        Drive_Stop();
        previous_millis_StandOff = millis();
      }
      break;
    case ASM_STATE_8:
      // State Debug
      #if DEBUG_LEVEL == 2
        if (drive_control_debug) Serial.println("ASM: Current time");
        drive_control_debug = 0;
      #endif
      // State Setup
      time_counter_flag = 1;
      // State Loop
      // State Transition
      state_asm = ASM_STATE_2;
      drive_control_debug = 1; 
      break;
    default:
      // State Setup
      Serial.println("ASM: ERROR - STOP");
      Drive_Stop();
      // State Transition
      state_asm = ASM_STATE_1;
      break;
  }
}
//////////////////////////////////////////////////////////////////////
// Finite State Machine definition
static void TIME_PROG_FSM(void)
{
  switch (state_time)
  {
	  case TIME_PROG_1:
      // State Debug
    	#if DEBUG_LEVEL > 0
        if (time_prog_debug) Serial.println(">>> TIME PROG 1: 9h 20s/20s");
        time_prog_debug = 0;
      #endif
      // State Setup
      if (time_prog_setup)
      {
        time_prog_setup = 0;
        T_Right = 20;
        T_Left = 20;
      }
      
      // State Loop
      Drive_Control();
      // State Transition
      //if (T_Min >= 1)
      if (T_Hour >= 1)
      {
        state_time = TIME_PROG_5;
        time_prog_debug = 1;
        Drive_Stop();
      }
   	  break;
    case TIME_PROG_2:
      // free to use
      break;
    case TIME_PROG_3:
    // free to use
      break;
    case TIME_PROG_4:
    // free to use
      break;
    case TIME_PROG_5:
      // STOP
      #if (DEBUG_LEVEL > 0)
        if (time_prog_debug) Serial.println(">>> TIME PROG 5: PROGRAM COMPLETED");
        time_prog_debug = 0;
      #endif
      break;      
    default:
      // State Setup
      Serial.println(">>> ERROR: PROGRAM STOP");
      Drive_Stop();
      break;
	}
}
//////////////////////////////////////////////////////////////////////
// function Timer
static void TIMER_SYS(void)
{ 
  if (millis() - previous_millis_Timer > 1000)
  { 
    T_Sec ++;
    if (T_Sec > 59)
    { 
      T_Min ++;
      T_Sec = 0;
    } 
    if (T_Min > 59)
    { 
      T_Hour ++;
      T_Min = 0;
    }
    if (time_counter_flag)
    {
      Serial.println();
      Serial.print("Time: ");
      Serial.print(T_Hour);
      Serial.print(":");
      Serial.print(T_Min);
      Serial.print(":");
      Serial.println(T_Sec);
      Serial.println();
      time_counter_flag = 0;
    }
    previous_millis_Timer = millis();
  }
}
//////////////////////////////////////////////////////////////////////
// Main program here
void loop()
{  
  // call TIMER_SYS to update time
  TIMER_SYS();
  // call ASM_FSM state machine every TIME_GRAIN
  if (millis() - previous_millis_Main_Loop > MAIN_LOOP_TIME_GRAIN_MS)
  {
    // call state machine
    TIME_PROG_FSM();
    
    // set current time as previous
    previous_millis_Main_Loop = millis();
  }
}