/*
   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 <IRremote.h>
#include <LiquidCrystal.h>
#include <Servo.h>
#include "genFSM.h"  // (1) include genFSM

#define PIN_RECEIVER 2   // Signal Pin of IR receiver

IRrecv receiver(PIN_RECEIVER);
Servo RCservo;

GenericFSM mainState;  // (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_servoPos    = SERVO_DOWN;
//uint32_t  g_saveMillis  = 0;

/* PIN_IR_BARRIER contatto pulito relay IR RX */
const byte PIN_IR_BARRIER   = 4;
/* PIN_KEY contatto pulito comando chiave */
const byte PIN_KEY          = 6;


void setup()
{
    Serial.begin(115200);
    pinMode(PIN_IR_BARRIER, INPUT_PULLUP);
    pinMode(PIN_KEY, INPUT_PULLUP);
    pinMode(LED_BUILTIN, OUTPUT);
    pinMode(A0, OUTPUT);

    receiver.enableIRIn(); // Start the receiver

    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");
    RCservo.attach(5);
    RCservo.write(g_servoPos);
    lcdPrintPos(g_servoPos);
    
    lcdPrintTimeout(0);
    mainState.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);
    }
}

/* 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(mainState.getState());  // (4)

    uint8_t command = 0;

    if (receiver.decode()) {
        command = receiver.decodedIRData.command;
        receiver.resume();  // Receive the next value
    } else if (digitalRead(PIN_KEY) == LOW) {
        command = IRCMD_PLAY;
    } 
   
    mainState.run();    // (5)
    /*if (mainState.onEnter()) {
        lcdPrintState(mainState.getState());
    }*/
        
    switch (mainState.getState()) {  // (6)
        
        case S_START:
            /* ENTRY STATE */
            if (mainState.onEnter()) { // (7)
                lcdPrintState(mainState.getState());
                Serial.println(F("Enter to S_START"));
            }
                        
            /* RUN STATE */
            if (command == IRCMD_PLAY) {
                //g_state = S_INOPENING;   
                mainState.setState(S_INOPENING);  // (8)
            } 
            
            /* EXIT STATE */
            if (mainState.onExit()) {  // (9)
                Serial.println(F("Exit from S_START"));
            }         
            break;

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

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

        case S_INCLOSING:
            /* ENTRY STATE */
            if (mainState.onEnter()) {
                lcdPrintState(mainState.getState());
                Serial.println(F("Enter to S_INCLOSING"));
            }
            
            /* RUN STATE */
            if (mainState.isElapsed(35)) {
                //g_saveMillis = millis();
                mainState.setTimer();
                  
                if (g_servoPos >= SERVO_DOWN) {
                    g_servoPos--;
                    RCservo.write(g_servoPos);
                    lcdPrintPos(g_servoPos);
                } 
                if (g_servoPos == SERVO_DOWN) {
                    /* CHANGE STATE */
                    mainState.setState(S_START);
                }
            }
            /* CHECK IR BARRIER */
            if (digitalRead(PIN_IR_BARRIER) == LOW) {
                /* CHANGE STATE */
                mainState.setState(S_INOPENING);
            }
            
            /* EXIT STATE */
            if (mainState.onExit()) {
                Serial.println(F("Exit from S_INCLOSING")); 
            }
            break;
    } // end switch
} // end loop()