/*
   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>

#define PIN_RECEIVER 2   // Signal Pin of IR receiver

IRrecv receiver(PIN_RECEIVER);
Servo RCservo;

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

// g_state è la variabile di stato
uint8_t   g_state       = S_START;
uint8_t   g_oldState    = 255;
uint8_t   g_servoPos    = SERVO_DOWN;
uint32_t  g_saveMillis  = 0;
bool      g_onEntryState    = false;

/* 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);
    
}

/* 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(g_state);

    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;
    } 
    /* Gestione automatica degli entry state */
    if (g_oldState != g_state) {
        g_oldState = g_state;
        g_onEntryState = true;
        // visualizza lo stato corrente sul display
        lcdPrintState(g_state);
    } else {
        g_onEntryState = false;
    }
        
    switch (g_state) {
        
        case S_START:
            /* ENTRY STATE */
            if (g_onEntryState) {
                Serial.println(F("Enter to S_START"));
            }
            
            /* RUN STATE */
            if (command == IRCMD_PLAY) {
                g_state = S_INOPENING;     
            } 
            
            /* EXIT STATE */
            if (g_state != g_oldState) {
                Serial.println(F("Exit from S_START"));
            }         
            break;

        case S_INOPENING:
            /* ENTRY STATE */
            if (g_onEntryState) {
                Serial.println(F("Enter to S_INOPENING"));
            }
            
            /* RUN STATE */          
            if (millis() - g_saveMillis > 25) {
                g_saveMillis = millis();
                
                if (g_servoPos <= SERVO_UP) {
                    g_servoPos++;
                    RCservo.write(g_servoPos);
                    lcdPrintPos(g_servoPos);
                } 
                if (g_servoPos == SERVO_UP) {
                    /* CHANGE STATE */
                    g_state = S_ISUP;
                    g_saveMillis = millis();
                }
            }
            
            /* EXIT STATE */
            if (g_state != g_oldState) {
                Serial.println(F("Exit from S_INOPENING"));
            } 
            break;

        case S_ISUP:
            /* ENTRY STATE */
            if (g_onEntryState) {
                Serial.println(F("Enter to S_ISUP"));
            }
            
            /* RUN STATE */
            if (millis() - g_saveMillis > 2000) {
                /* CHANGE STATE */
                g_state = S_INCLOSING;
                lcdPrintTimeout(0);
            } else {
                uint32_t dt = millis() - g_saveMillis;
                lcdPrintTimeout(2000 - dt);
            }

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

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