/*
   parking-access-s01 is demo application of parking access.
   https://programmersqtcpp.blogspot.com/2022/03/applicazione-accesso-parcheggio-entry.html
   
   Copyright (C) 2023 Maurilio Pizzurro 

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 1, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this library.  If not, see <http://www.gnu.org/licenses/>.
*/

/* 
    Pulsante verde per simulare la barriera IR
    Pulsante giallo per simulare il contatto chiave
*/


#include <LiquidCrystal.h>
#include <Servo.h>
#include "genFSM.h"  // (1) include genFSM

Servo bar0;
Servo bar1;

GenericFSM bar0State;  // (2) Crea istanza del nuovo gestore di stati
GenericFSM bar1State;  // (2) Crea istanza del nuovo gestore di stati
LiquidCrystal lcd(12, 11, 10, 9, 8, 7);

/* SERVO_UP servo in posizione verticale */
#define SERVO_UP      180
/* SERVO_DOWN servo in posizione orizzontale */
#define SERVO_DOWN    90

/* S_XXX stati della macchina a stati g_state */
#define S_START        0
#define S_INOPENING    1
#define S_ISUP         2
#define S_INCLOSING    3

#define IRCMD_PLAY       168

uint8_t   g_servoPos0    = SERVO_DOWN;
uint8_t   g_servoPos1    = SERVO_DOWN;
//uint32_t  g_saveMillis  = 0;

/* PIN_IR_BARRIER contatto pulito relay IR RX */
const byte PIN_IR_BARRIER0   = 4;
const byte PIN_IR_BARRIER1   = A4;
/* PIN_KEY contatto pulito comando chiave */
const byte PIN_KEY0          = 6;
const byte PIN_KEY1          = A5;


void setup()
{
    Serial.begin(115200);
    pinMode(PIN_IR_BARRIER0, INPUT_PULLUP);
    pinMode(PIN_IR_BARRIER1, INPUT_PULLUP);
    pinMode(PIN_KEY0, INPUT_PULLUP);
    pinMode(PIN_KEY1, INPUT_PULLUP);
    pinMode(LED_BUILTIN, OUTPUT);
    pinMode(A0, OUTPUT);
    pinMode(A3, OUTPUT);
    pinMode(A1, OUTPUT);
    
    lcd.begin(16, 2);
    lcd.setCursor(0, 1);
    lcd.print((char)223);
    lcd.setCursor(6,1);
    lcd.print("Tms: ");
    
    Serial.print("Press play or\nyellow button\n");
    bar0.attach(5);
    bar0.write(g_servoPos0);
    lcdPrintPos(g_servoPos0);
    bar1.attach(A2);
    bar1.write(g_servoPos1);
    
    lcdPrintTimeout(0);
    bar0State.setState(S_START); // (3) inizializzazione opzionale
}

/* lcdPrintPos() visualizza su lcd la posizione angolare del servo*/
void lcdPrintPos(uint8_t pos) {
    char posBuff[4];
    posBuff[ 4 - 1 ] = '\0';
    sprintf(posBuff, "%#3u", pos);
    lcd.setCursor(1, 1);
    lcd.print(posBuff);
}

/* lcdPrintState() visualizza su lcd lo stato corrente */
void lcdPrintState(uint8_t n) {
    char *s[] = {"S_START    "
               , "S_INOPENING"
               , "S_ISUP     "
               , "S_INCLOSING"};
   
    lcd.setCursor(0, 0);
    lcd.print("RUN ");
    lcd.setCursor(5, 0);
    lcd.print(s[n]);
}

/* flashingLight() accendo/spegne il lampeggiante */
void flashingLight(bool tf) {
    #define FL_TIMING 200
    static uint32_t timer = 0;
    static uint8_t timing = 0;
    if (!tf) {
        digitalWrite(LED_BUILTIN, LOW);
        digitalWrite(A0, LOW);
        timer = 0;
        timing = 0;
        return;
    }
      
    if (millis() - timer > timing) {
        bool yelIsOn = !digitalRead(A0);
        timer = millis();
        timing = FL_TIMING;
        digitalWrite(LED_BUILTIN, !yelIsOn);
        digitalWrite(A0, yelIsOn);
    }
}

/* flashingLight() accendo/spegne il lampeggiante */
void flashingLight1(bool tf) {
    #define FL_TIMING 200
    static uint32_t timer = 0;
    static uint8_t timing = 0;
    if (!tf) {
        digitalWrite(A3, LOW);
        digitalWrite(A1, LOW);
        timer = 0;
        timing = 0;
        return;
    }
      
    if (millis() - timer > timing) {
        bool yelIsOn = !digitalRead(A1);
        timer = millis();
        timing = FL_TIMING;
        digitalWrite(A3, !yelIsOn);
        digitalWrite(A1, yelIsOn);
    }
}

/* lcdPrintTimeout() visualizza su lcd il timeout residuo */
void lcdPrintTimeout(uint16_t t) {
    char timeBuff[5];
    timeBuff[ 5 - 1 ] = '\0';
    sprintf(timeBuff, "%#4u", t);
    lcd.setCursor(11, 1);
    lcd.print(timeBuff);
}

void loop() {
    flashingLight(bar0State.getState());  // (4)
    flashingLight1(bar1State.getState());  // (4)

    uint8_t command0 = 0;
    uint8_t command1 = 0;

    if (digitalRead(PIN_KEY0) == LOW) {
        command0 = IRCMD_PLAY;
        
    } else if (digitalRead(PIN_KEY1) == LOW) {
        command1 = IRCMD_PLAY;
    } 
   
    bar0State.run();    // (5)
    bar1State.run();    // (5)
            
    switch (bar0State.getState()) {  // (6)
        
        case S_START:
            /* ENTRY STATE */
            if (bar0State.onEnter()) { // (7)
                lcdPrintState(bar0State.getState());
                Serial.println(F("Enter to S_START"));
            }
                        
            /* RUN STATE */
            if (command0 == IRCMD_PLAY) {
                //g_state = S_INOPENING;   
                bar0State.setState(S_INOPENING);  // (8)
            } 
            
            /* EXIT STATE */
            if (bar0State.onExit()) {  // (9)
                Serial.println(F("Exit from S_START"));
            }         
            break;

        case S_INOPENING:
            /* ENTRY STATE */
            if (bar0State.onEnter()) {  // (10)
                lcdPrintState(bar0State.getState());
                Serial.println(F("Enter to S_INOPENING"));
            }
            
            /* RUN STATE */          
            if (bar0State.isElapsed(25)) { // (11)
                //g_saveMillis = millis();(
                bar0State.setTimer();      // (12)
                
                if (g_servoPos0 <= SERVO_UP) {
                    g_servoPos0++;
                    bar0.write(g_servoPos0);
                    lcdPrintPos(g_servoPos0);
                } 
                if (g_servoPos0 == SERVO_UP) {
                    /* CHANGE STATE */
                    bar0State.setState(S_ISUP);  // (13)
                    //g_saveMillis = millis();
                }
            }
            
            /* EXIT STATE */
            if (bar0State.onExit()) {
                Serial.println(F("Exit from S_INOPENING"));
            } 
            break;
            
        case S_ISUP:
            /* ENTRY STATE */
            if (bar0State.onEnter()) {
                lcdPrintState(bar0State.getState());
                Serial.println(F("Enter to S_ISUP"));
            }
            
            /* RUN STATE */
            if (bar0State.isElapsed(2000)) {
                /* CHANGE STATE */
                bar0State.setState(S_INCLOSING);
                lcdPrintTimeout(0);
            } else {
                uint32_t dt = millis() - bar0State.getTimer();
                lcdPrintTimeout(2000 - dt);
            }

            /* CHECK IR BARRIER */
            if (digitalRead(PIN_IR_BARRIER0) == LOW) {
                /* RESTART TIMER */
                //g_saveMillis = millis();
                bar0State.setTimer();
            }
            
            /* EXIT STATE */
            if (bar0State.onExit()) {
                Serial.println(F("Exit from S_ISUP"));
            }
            break;

        case S_INCLOSING:
            /* ENTRY STATE */
            if (bar0State.onEnter()) {
                lcdPrintState(bar0State.getState());
                Serial.println(F("Enter to S_INCLOSING"));
            }
            
            /* RUN STATE */
            if (bar0State.isElapsed(35)) {
                //g_saveMillis = millis();
                bar0State.setTimer();
                  
                if (g_servoPos0 >= SERVO_DOWN) {
                    g_servoPos0--;
                    bar0.write(g_servoPos0);
                    lcdPrintPos(g_servoPos0);
                } 
                if (g_servoPos0 == SERVO_DOWN) {
                    /* CHANGE STATE */
                    bar0State.setState(S_START);
                }
            }
            /* CHECK IR BARRIER */
            if (digitalRead(PIN_IR_BARRIER0) == LOW) {
                /* CHANGE STATE */
                bar0State.setState(S_INOPENING);
            }
            
            /* EXIT STATE */
            if (bar0State.onExit()) {
                Serial.println(F("Exit from S_INCLOSING")); 
            }
            break;
    } // end switch
    // seconda barra
    switch (bar1State.getState()) {  // (6)
        
        case S_START:
            /* ENTRY STATE */
            if (bar1State.onEnter()) { // (7)
                
                Serial.println(F("\tbar1, Enter to S_START"));
            }
                        
            /* RUN STATE */
            if (command1 == IRCMD_PLAY) {
                //g_state = S_INOPENING;   
                bar1State.setState(S_INOPENING);  // (8)
            } 
            
            /* EXIT STATE */
            if (bar1State.onExit()) {  // (9)
                Serial.println(F("\tbar1, Exit from S_START"));
            }         
            break;

        case S_INOPENING:
            /* ENTRY STATE */
            if (bar1State.onEnter()) {  // (10)
                
                Serial.println(F("\tbar1, Enter to S_INOPENING"));
            }
            
            /* RUN STATE */          
            if (bar1State.isElapsed(25)) { // (11)
                //g_saveMillis = millis();(
                bar1State.setTimer();      // (12)
                
                if (g_servoPos1 <= SERVO_UP) {
                    g_servoPos1++;
                    bar1.write(g_servoPos1);
                    
                } 
                if (g_servoPos1 == SERVO_UP) {
                    /* CHANGE STATE */
                    bar1State.setState(S_ISUP);  // (13)
                    //g_saveMillis = millis();
                }
            }
            
            /* EXIT STATE */
            if (bar1State.onExit()) {
                Serial.println(F("\tbar1, Exit from S_INOPENING"));
            } 
            break;
            
        case S_ISUP:
            /* ENTRY STATE */
            if (bar1State.onEnter()) {
                
                Serial.println(F("\tbar1, Enter to S_ISUP"));
            }
            
            /* RUN STATE */
            if (bar1State.isElapsed(2000)) {
                /* CHANGE STATE */
                bar1State.setState(S_INCLOSING);
                
            } 

            /* CHECK IR BARRIER */
            if (digitalRead(PIN_IR_BARRIER1) == LOW) {
                /* RESTART TIMER */
                //g_saveMillis = millis();
                bar1State.setTimer();
            }
            
            /* EXIT STATE */
            if (bar1State.onExit()) {
                Serial.println(F("\tbar1, Exit from S_ISUP"));
            }
            break;

        case S_INCLOSING:
            /* ENTRY STATE */
            if (bar1State.onEnter()) {
                
                Serial.println(F("\tbar1, Enter to S_INCLOSING"));
            }
            
            /* RUN STATE */
            if (bar1State.isElapsed(35)) {
                //g_saveMillis = millis();
                bar1State.setTimer();
                  
                if (g_servoPos1 >= SERVO_DOWN) {
                    g_servoPos1--;
                    bar1.write(g_servoPos1);
                    
                } 
                if (g_servoPos1 == SERVO_DOWN) {
                    /* CHANGE STATE */
                    bar1State.setState(S_START);
                }
            }
            /* CHECK IR BARRIER */
            if (digitalRead(PIN_IR_BARRIER1) == LOW) {
                /* CHANGE STATE */
                bar1State.setState(S_INOPENING);
            }
            
            /* EXIT STATE */
            if (bar1State.onExit()) {
                Serial.println(F("\tbar1, Exit from S_INCLOSING")); 
            }
            break;
    } // end switch
} // end loop()