#include <Servo.h>

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

#define lcdaddress 0x27
LiquidCrystal_I2C lcd(lcdaddress, 20, 4); 

// Settings slagboom
Servo slagboom1;
Servo slagboom2;
#define servoSturing1 10    //SERVO bediening slagboom  (PWM pin)
#define servoSturing2 9     //SERVO bediening slagboom  (PWM pin)

#define servo1Min 90        // servo 1 minimum (closed)
#define servo2Min 90        // servo 2 minimum (closed)

#define servo1Max 180       // servo 1 maximum (opened)
#define servo2Max 180       // servo 2 maximum (opened)

#define slagboomsnelheid 10 // 5 = fast, 25 = medium, 50 = slow (maximale waarde is 255)
byte myServoPosition[2] = { 80,  80} ;  // stores current servo positions

boolean  OpentDeSlagBoom = true;
boolean  SluitDeSlagBoom = false;


unsigned long IntervalRodeledsSlagbomenSluiten; 
unsigned long IntervalSlagbomenOpenenWitteleds; 

unsigned long WachttijdRodeledsSlagbomenSluiten = 1200; // tijd die moet verlopen tussen beginnen knipperen van de rode leds en het in beweging komen van de slagbomen
unsigned long WachttijdSlagbomenOpenenWitteleds = 750; // tijd die moet verlopen tussen hetopens staan van de slagbomen en het beginnen knipperen van de witte leds

unsigned long servoTimer;


// Instellingen leds
#define LedAan 0
#define LedUit 1

boolean  KnippertDeWitteLed = true;
boolean  KnippertDeRodeLed = false;

byte RodeLedPin[2] = { 5 , 7 };
byte WitteLedPin[1] = { 3 };

unsigned int KnippertijdWitteLed[2] = {250 , 350};
unsigned int KnippertijdRodeLed[2] = {200 , 300};

unsigned long WhiteTimer = 0;
unsigned long RedTimer = 0;


// Instellingen sensoren
byte sensorPin[2] = { 2 , 4 };
uint16_t btndbc[2] = {0xAAAA , 0xAAAA};
byte ButtonState[2] = { 2 , 2 };
byte DisplayButtonState[2] = { 2 , 2 };
byte ButtonPreviousState[2] = { 2  , 2 };


// Instellingen geluidssignaal
#define mp3 11          // mp3 speler reset voor belgeluid


byte OverwegStatus = 0;
byte HuidigeUitgevoerdeOverwegStatus = 0;
byte GewensteOverwegStatus = 0;
byte SystemStep = 0;
byte SystemStepold = 0;


void debounce(byte Sensornr)
{
  btndbc[Sensornr]=(btndbc[Sensornr]<<1) | digitalRead(sensorPin[Sensornr]) | 0xe000;
  if (btndbc[Sensornr] == 0xFFFF) ButtonState[Sensornr] = 1;
  if (btndbc[Sensornr] == 0xE000) ButtonState[Sensornr] = 0;

  if (DisplayButtonState[Sensornr] != ButtonState[Sensornr])
  {
    lcd.setCursor(8+2*Sensornr, 3);
    lcd.print(ButtonState[Sensornr]);
    DisplayButtonState[Sensornr] = ButtonState[Sensornr];
  }
}


void openSlagboom()
{
  if (myServoPosition[0] < servo1Max) myServoPosition[0]++;
  slagboom1.write(myServoPosition[0]);
  
  if (myServoPosition[1] < servo2Max) myServoPosition[1]++;
  slagboom2.write(myServoPosition[1]);
  
  servoTimer = millis() + slagboomsnelheid;
}


void sluitSlagboom()
{
  if (myServoPosition[0] > servo1Min) myServoPosition[0]--;
  slagboom1.write(myServoPosition[0]);
  
  if (myServoPosition[1] > servo2Min) myServoPosition[1]--;
  slagboom2.write(myServoPosition[1]);
  
  servoTimer = millis() + slagboomsnelheid;
}


void KnipperLicht ( byte Pin )
{
  digitalWrite(Pin , !digitalRead(Pin));
}

void KnipperenWitteLed()
{
  KnipperLicht ( WitteLedPin[0] );
  WhiteTimer = millis() + KnippertijdWitteLed[digitalRead(WitteLedPin[0])] ;
  lcd.setCursor( 10 , 0 );
  if (digitalRead(WitteLedPin[0]) == 1) lcd.print("uit       "); else lcd.print("aan      ");
}


void KnipperenRodeLeds()
{
  static int LedNr = 0;

  KnipperLicht ( RodeLedPin[LedNr] );
  if (digitalRead(RodeLedPin[LedNr]) != 0) LedNr = 1 - LedNr ; // led uit dan veranderen van knipperende led
  RedTimer = millis() + KnippertijdRodeLed[digitalRead(RodeLedPin[LedNr])];

  lcd.setCursor( 9 , 1 );
  if ((digitalRead(RodeLedPin[0]) == 1) && (digitalRead(RodeLedPin[1]) == 1)) lcd.print("uit        "); else lcd.print("aan        ");
}


byte OverwegBeheer( byte GewensteOverwegStatus)
{
  static unsigned long MyTime;

  // lijnen 139 - 148 te verwijderen indien logging weg moet
  static byte GewensteOverwegStatusold = 0;
if ((GewensteOverwegStatusold != GewensteOverwegStatus) || (HuidigeUitgevoerdeOverwegStatus != OverwegStatus))
{
  Serial.print ("Overwegbeheer GewensteOverwegStatus: ");
  Serial.print (GewensteOverwegStatus);
  Serial.print ("  OverwegStatus: ");
  Serial.println (OverwegStatus);
  GewensteOverwegStatusold = GewensteOverwegStatus;
}
  
  HuidigeUitgevoerdeOverwegStatus = OverwegStatus;

  switch (OverwegStatus)
  {
    case 0:
         // Initialisatie: Alle lichten uit, slagboom open
         digitalWrite(WitteLedPin[0] , HIGH);
         digitalWrite(RodeLedPin[0] , HIGH);
         digitalWrite(RodeLedPin[1] , HIGH);
         KnippertDeWitteLed = false;
         lcd.setCursor( 10 , 0 );
         lcd.print("gestopt   ");

         KnippertDeRodeLed = true;

         OpentDeSlagBoom = true;
         lcd.setCursor( 9 , 2 );
         lcd.print("openen     ");

         SluitDeSlagBoom = false;

         if (GewensteOverwegStatus > OverwegStatus) OverwegStatus = 10;
         break;

    case 10:  
         // Wachten tot slagboom open is
         if ((myServoPosition[0] == servo1Max) && (myServoPosition[1] == servo2Max) && (GewensteOverwegStatus > OverwegStatus)) OverwegStatus = 15; 
         break;

    case 15:
         // Bericht weergeven
         lcd.setCursor( 9 , 2 );
         lcd.print("is geopend ");

         Serial.println ("Slagboom is geopend.");
         Serial.println ("Druk op 1 van de knoppen en wacht.");
         OverwegStatus = 20; 
         break;

    case 20:  
         // wit knipperlicht
         KnippertDeWitteLed = true;
         KnippertDeRodeLed = false;
         OpentDeSlagBoom = true;
         SluitDeSlagBoom = false;
         OverwegStatus = 30;
         break;

    case 30:  // wachten tot de witte led gedoofd is 
         if ((digitalRead(WitteLedPin[0]) == LedUit) && (GewensteOverwegStatus > OverwegStatus))
         {
           KnippertDeWitteLed = false; // witte led mag niet meer knipperen
           lcd.setCursor( 10 , 0 );
           lcd.print("gestopt   ");
           lcd.setCursor( 9 , 1 );
           lcd.print("uit       ");


           Serial.println ("Knipperen witte led gestopt.");
           MyTime =  WhiteTimer ;      // even wachten
           OverwegStatus = 40;
         }

         if ((digitalRead(RodeLedPin[0]) == LedUit) && (digitalRead(RodeLedPin[1]) == LedUit) && (GewensteOverwegStatus < OverwegStatus))
         {
           // wachten totdat de rode led gedoofd is
           KnippertDeRodeLed = false; // Rode led mag niet meer knipperen
           lcd.setCursor( 9 , 1 );
           lcd.print("gestopt   ");
           lcd.setCursor( 10 , 0 );
           lcd.print("uit       ");

           Serial.println ("Knipperen rode led gestopt.");
           MyTime =  RedTimer ;       // even wachten
           OverwegStatus = 40;
         }

         break;

    case 40:
         if (GewensteOverwegStatus > OverwegStatus)
         {
           if (millis() > MyTime) {OverwegStatus = 50;}
         }

         if (GewensteOverwegStatus < OverwegStatus)
         {
           if (millis() > MyTime) {OverwegStatus = 50;}
         }
        break;


    case 50:  // Rode knipperlichten aan en instellen wchttijd slagbomen
         if (GewensteOverwegStatus > OverwegStatus)
         {
            KnippertDeRodeLed = true;
            Serial.println ("Knipperen rode leds gestart.");
            OverwegStatus = 60;
         }

         if (GewensteOverwegStatus < OverwegStatus)
         {
           KnippertDeWitteLed = true;

           Serial.println ("Knipperen witte led gestart.");
           OverwegStatus = 20;          
         }
         break;

    case 60:
         if (GewensteOverwegStatus > OverwegStatus)
         {
           // instellen wchttijd tussen aangaan rode leds en naar beneden gaan slagbomen
           lcd.setCursor( 9 , 2 );
           lcd.print("zal sluiten");
  
           Serial.println ("Wachten op sluiten slagbomen.");
           IntervalRodeledsSlagbomenSluiten = millis() + WachttijdRodeledsSlagbomenSluiten;
           OverwegStatus = 80;
         }

         if (GewensteOverwegStatus < OverwegStatus)
         {
           IntervalSlagbomenOpenenWitteleds = millis() + WachttijdSlagbomenOpenenWitteleds;
           OverwegStatus = 80;
         }

         break;

    case 80:    
          // wachttijd voor  het inbeweging zetten van de slagbomen laten voorbijgaan
         if ((millis()> IntervalRodeledsSlagbomenSluiten) && (GewensteOverwegStatus > OverwegStatus))
         {
           OverwegStatus = 90;
         }

         if ((millis()> IntervalSlagbomenOpenenWitteleds) && (GewensteOverwegStatus < OverwegStatus))
         {
           OverwegStatus = 30;
         }

         break;

    case 90:
         if (GewensteOverwegStatus > OverwegStatus)
         {
           SluitDeSlagBoom = false;
           OpentDeSlagBoom = false;
           lcd.setCursor( 9 , 2 );
           lcd.print("sluit      ");

           Serial.println ("slagbomen worden gesloten.");
           SluitDeSlagBoom = true;

           OverwegStatus = 100;
         }

         if (GewensteOverwegStatus < OverwegStatus)
         {
           SluitDeSlagBoom = false;
           OpentDeSlagBoom = false;
           lcd.setCursor( 9 , 2 );
           lcd.print("is geopend ");

           Serial.println ("Slagbomen zijn geopend.");
           OverwegStatus = 60;
         }
         break;

    case 100:  // slagbomen in beweging zetten
         if (GewensteOverwegStatus > OverwegStatus)
         {
            SluitDeSlagBoom = true; // Deze 2 lijnen moeten erbij, anders kan het programma in bepaalde TESTomstandigheden vastlopen
            OpentDeSlagBoom = false;
            lcd.setCursor( 9 , 2 );
            lcd.print("sluit      ");

            if ((myServoPosition[0] == servo1Min) && (myServoPosition[1] == servo2Min)) OverwegStatus = 110; 
         }

         if (GewensteOverwegStatus < OverwegStatus)
         {
            SluitDeSlagBoom = false; // Deze 2 lijnen moeten erbij, anders kan het programma in bepaalde TESTomstandigheden vastlopen
            OpentDeSlagBoom = true;
            lcd.setCursor( 9 , 2 );
            lcd.print("opent      ");

            if ((myServoPosition[0] == servo1Max) && (myServoPosition[1] == servo2Max)) OverwegStatus = 90;
         }
         break;

    case 110:
         if (GewensteOverwegStatus > OverwegStatus)
         {
           SluitDeSlagBoom = false;
           OpentDeSlagBoom = false;
           lcd.setCursor( 9 , 2 );
           lcd.print("is gesloten");

           Serial.println ("Slagbomen zijn gesloten."); 
           Serial.println ("Druk op de andere knop. Pas bij loslaten verlaat de trein de overweg en zullen de slagbomen openen.");
           OverwegStatus = 120;
         }

         if (GewensteOverwegStatus < OverwegStatus)
         {
           SluitDeSlagBoom = false;
           lcd.setCursor( 9 , 2 );
           lcd.print("opent      ");
 
           Serial.println ("Slagbomen worden geopend.");
           OpentDeSlagBoom = true;
           OverwegStatus = 100;
         }
         break;

    case 120:  // slagbomen zijn naar beneden
         if (GewensteOverwegStatus < OverwegStatus) OverwegStatus = 110;
         break;
  }
  return (HuidigeUitgevoerdeOverwegStatus);

}


void SensorHandler()
{
  // lijnen 378 - 383 te verwijderen indien logging weg moet
  if (SystemStepold != SystemStep)
  {
     Serial.print ("SystemStep: ");
     Serial.println (SystemStep);
  }

  SystemStepold = SystemStep;

  switch (SystemStep)
  {
    case 0:
         ButtonPreviousState[0] = ButtonState[0];
         ButtonPreviousState[1] = ButtonState[1];

         if ((ButtonState[0] == LOW) && (ButtonState[1] == LOW)) SystemStep = 10;
         break;

    case 10:
         if ((ButtonState[0] != ButtonPreviousState[0]) && (ButtonState[0] == HIGH)) { SystemStep = 20; ButtonPreviousState[0] = ButtonState[0]; }
         if ((ButtonState[1] != ButtonPreviousState[1]) && (ButtonState[1] == HIGH)) { SystemStep = 30; ButtonPreviousState[1] = ButtonState[1];}

         break;

    case 20:
         GewensteOverwegStatus = 120;
         // Vorige sensor die slagbomen liet neergaan moet eerst terug vrijgegeven worden
         if ((ButtonState[0] != ButtonPreviousState[0]) && (ButtonState[0] == LOW)) { SystemStep = 40; ButtonPreviousState[0] = ButtonState[0]; ButtonPreviousState[1] = ButtonState[1];}
         break;

    case 30:
         GewensteOverwegStatus = 120;
         // Vorige sensor die slagbomen liet neergaan moet eerst terug vrijgegeven worden
         if ((ButtonState[1] != ButtonPreviousState[1]) && (ButtonState[1] == LOW)) { SystemStep = 50; ButtonPreviousState[1] = ButtonState[1]; ButtonPreviousState[0] = ButtonState[0];}
         break;

    case 40:
         if ((ButtonState[1] != ButtonPreviousState[1]) && (ButtonState[1] == HIGH)) { SystemStep = 60; ButtonPreviousState[1] = ButtonState[1]; ButtonPreviousState[0] = ButtonState[0];}
         break;

    case 50:
         if ((ButtonState[0] != ButtonPreviousState[0]) && (ButtonState[0] == HIGH)) { SystemStep = 70; ButtonPreviousState[0] = ButtonState[0]; ButtonPreviousState[1] = ButtonState[1];}
         break;

    case 60:
         if ((ButtonState[1] != ButtonPreviousState[1]) && (ButtonState[1] == LOW)) { SystemStep = 80; ButtonPreviousState[1] = ButtonState[1]; ButtonPreviousState[0]  = ButtonState[0];}
         break;

    case 70:
         if ((ButtonState[0] != ButtonPreviousState[0]) && (ButtonState[0] ==LOW)) { SystemStep = 90; ButtonPreviousState[0] = ButtonState[0]; ButtonPreviousState[1] = ButtonState[1];}
         break;

    case 80:
         GewensteOverwegStatus = 20;
         SystemStep = 0;
         break;

    case 90:
         GewensteOverwegStatus = 20;
         SystemStep = 0;
         break;

    default:
         Serial.println ("Error in sensorhandler voor ");
         Serial.println (SystemStepold);
  }
}


void setup() 
{
  Serial.begin ( 115200 );                 // starts the serial communication
  while ( !Serial ) {} ;

// Display settings
  lcd.init();                  
  lcd.init();
  lcd.backlight();
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Witte led           ");
  lcd.setCursor(0, 1);
  lcd.print("Rode led gestopt    ");
  lcd.setCursor(0, 2);
  lcd.print("Slagboom opent    ");
  lcd.setCursor(0, 3);
  lcd.print("Sensor:             ");

  slagboom1.attach(servoSturing1);     
  slagboom2.attach(servoSturing2); 

//  pinMode(servoSturing1, OUTPUT); // Onnodig wordt bij de attach Servo al gedaan
//  pinMode(servoSturing2, OUTPUT);

  pinMode(WitteLedPin[0], OUTPUT);
  pinMode(RodeLedPin[0], OUTPUT);
  pinMode(RodeLedPin[1], OUTPUT);

  pinMode(sensorPin[0], INPUT);
  pinMode(sensorPin[1], INPUT);

  while (ButtonState[0] > 1) { debounce(0); };
  ButtonPreviousState[0] = ButtonState[0];

  while (ButtonState[1] > 1) { debounce(1); };
  ButtonPreviousState[1] = ButtonState[1];

  GewensteOverwegStatus = 20;
  SystemStep = 10;

  lcd.setCursor(8, 3);
  lcd.print(ButtonState[0]);
  lcd.setCursor(10, 3);
  lcd.print(ButtonState[1]);

  Serial.println("Einde setup");
}


void loop() 
{
  // Knipperen van de leds
  if ((KnippertDeWitteLed) && (millis() > WhiteTimer)) KnipperenWitteLed();
  if ((KnippertDeRodeLed) && (millis() > RedTimer)) KnipperenRodeLeds();

  // Opvragen sensoren en behandeling van de sensorsignalen
  debounce(0);
  debounce(1);
  if ((ButtonState[0] != ButtonPreviousState[0]) || (ButtonState[1] != ButtonPreviousState[1]) || (SystemStepold != SystemStep)) SensorHandler();

  // Beheer van de overweg
  if (GewensteOverwegStatus != HuidigeUitgevoerdeOverwegStatus) OverwegBeheer( GewensteOverwegStatus );

  // Aansturen van de slagbomen
  if ((OpentDeSlagBoom) && (servoTimer < millis())) openSlagboom();
  if ((SluitDeSlagBoom) && (servoTimer < millis())) sluitSlagboom();
 
}