// 3 way LX controller
// 1, 2 or 3 independant tracks, bidirectional
// Robert Parnell January 2021
// updated and tested 22/3/22
// work-in progress 6/4/22 - 4th output added for each group (see below)
// simulated electronically OK, to be checked in real world 6/4/22
//
// Version 7/1/21
// working as of 8/1/21
// Version 6/4/22 to be tested fully

// Inputs:
// LX1:   LX2:    LX3:
// A2     A5      A1      "up" approach/departure
// A3     A6      A0      LX itself
// A4     A7      D13     "down" approach/departure
//
//
// ################ 6/4/22
//
// Outputs:
// LX1: LX2:  LX3:
// D1   D5    D9   group enable. Acitve LOW when crossing activated
// D2   D6    D10   Lamp 1 - active HIGH
// D3   D7    D11   Lamp 2 - active HIGH
// D4   D8    D12   constant 50% duty cycle output
//
// Nano - old bootloader
int flrate1 = 670; //set LED flash rate. 50% duty cycle.
int flrate2 = 625;
int flrate3 = 580;

int LX1osc= 4;
int LX2osc= 8;
int LX3osc= 12; // constant LED osciallating outputs

int LX1val = LOW;
int LX2val = LOW;
int LX3val = LOW; // 22/3/22

class Flasher
{
    // Class Member Variables
    // These are initialized at startup
    int ledPin;      // the number of the LED pin
    
    long OnTime;     // milliseconds of on-time
    long OffTime;    // milliseconds of off-time

    // These maintain the current state
    int ledState;             		// ledState used to set the LED
    unsigned long previousMillis;  	// will store last time LED was updated

    // Constructor - creates a Flasher
    // and initializes the member variables and state
  public:
    Flasher(int pin, long on, long off)
    {
      ledPin = pin;
      
      pinMode(ledPin, OUTPUT);
     
      OnTime = on;
      OffTime = off;

      ledState = LOW;
      previousMillis = 0;
    }

    void Update()
    {
      // check to see if it's time to change the state of the LED
      unsigned long currentMillis = millis();

      if ((ledState == HIGH) && (currentMillis - previousMillis >= OnTime))
      {
        ledState = LOW;  // Turn it off
        previousMillis = currentMillis;  // Remember the time
        digitalWrite(ledPin, ledState);  // Update the actual LED
        
      }
      else if ((ledState == LOW) && (currentMillis - previousMillis >= OffTime))
      {
        ledState = HIGH;  // turn it on
        previousMillis = currentMillis;   // Remember the time
        digitalWrite(ledPin, ledState);	  // Update the actual LED
                
      }
    }
};

// Digital output #, on time, off time
Flasher led1(LX1osc, flrate1, flrate1); // 22/3/22 changed number to LX1osc variable
Flasher led2(LX2osc, flrate2, flrate2);
Flasher led3(LX3osc, flrate3, flrate3);


//Inputs:

int blockA1 = A2;
int blockA2 = A3;
int blockA3 = A4;

int blockB1 = A5;
int blockB2 = A6;
int blockB3 = A7;

int blockC1 = A1; // pins physically grouped together
int blockC2 = A0; //
int blockC3 = 13; //digitalinput D13

//Outputs:

int Astatus = 1; // output "enable" pins for each crossing
int Bstatus = 5;
int Cstatus = 9;

//pinMode(ledPin, OUTPUT);
//int LX1 = 4;
//int LX2 = 7;
//int LX3 = 10;


//Variables:
int A1val = 1000;
int A2val = 1000;
int A3val = 1000;
int B1val = 1000;
int B2val = 1000;
int B3val = 1000;
int C1val = 1000;
int C2val = 1000;
int C3val = 1000;

int A1occ = 0; // block occupancy status
int A2occ = 0; // 0 = unoccupied
int A3occ = 0; // 1 = occupied
int B1occ = 0;
int B2occ = 0;
int B3occ = 0;
int C1occ = 0;
int C2occ = 0;
int C3occ = 0;

int A1counter = 0;
int A2counter = 0;
int A3counter = 0;
int B1counter = 0;
int B2counter = 0;
int B3counter = 0;
int C1counter = 0;
int C2counter = 0;
int C3counter = 0;
int debouncetime = 17; // multiples of 10ms for debounce timer for a block input


int trackA = 0; //1 = road occupied
int trackB = 0;
int trackC = 0;
int trackADN = 0; // down: true 1, false = 0
int trackAUP = 0;
int trackBDN = 0;
int trackBUP = 0;
int trackCDN = 0;
int trackCUP = 0;

// combined LED output pins
int LX1a = 2; // 6/4/22
int LX1b = 3; // lamp A and B outputs for LX1
int LX2a = 6; // 
int LX2b = 7;
int LX3a = 10;
int LX3b = 11;



void setup() {


  pinMode (blockA1, INPUT);
  pinMode (blockA2, INPUT);
  pinMode (blockA3, INPUT);
  pinMode (blockB1, INPUT);
  pinMode (blockB2, INPUT);
  pinMode (blockB3, INPUT);
  pinMode (blockC1, INPUT);
  pinMode (blockC2, INPUT);
  pinMode (blockC3, INPUT);

  pinMode (Astatus, OUTPUT);
  pinMode (Bstatus, OUTPUT);
  pinMode (Cstatus, OUTPUT);
  
  pinMode (LX1a, OUTPUT); // individual lamp outputs
  pinMode (LX2a, OUTPUT);
  pinMode (LX3a, OUTPUT);
  pinMode (LX1b, OUTPUT); 
  pinMode (LX2b, OUTPUT);
  pinMode (LX3b, OUTPUT);
}

void loop() {

  led1.Update();
  led2.Update();
  led3.Update();
   readinputs();

  if ((A1val > 300) && (A2val > 300) && (A3val > 300)) {
    trackA = 0;
    trackADN = 0;
    trackAUP = 0;
    AlightsOFF();
    
  }
 
  if ((B1val > 300) && (B2val > 300) && (B3val > 300)) {

    trackB = 0;
    trackBDN = 0;
    trackBUP = 0;
    BlightsOFF();
    
  }
 
  if ((C1val > 300) && (C2val > 300) && (C3val > 300)) {

    trackC = 0;
    trackCDN = 0;
    trackCUP = 0;
    ClightsOFF();
    

  }
 

  if ((A1val < 300) || (A2val < 300) || (A3val < 300) || (B1val < 300) || (B2val < 300) || (B3val < 300) || (C1val < 300) || (C2val < 300) || (C3val < 300)) {

    train();
  }
  // if all inputs are inactive, so go back to the start and check again
  //
  //
  // END OF MAINLOOP //
  //
  //
}

void train() {
  // once in this void, can't get out until all trains have cleared their respective crossings
TrainLoop:
  led1.Update();
  led2.Update();
  led3.Update();
 
  //a train has activated one or more block inputs. Work out which one, see if up or down.
  // track A
  if (A1val < 300 ) {
    A1occ=1;
    A1counter = 0;
  }
  if (A2val < 300) {
    A2occ=1;
    A2counter = 0;
  }
  if (A3val < 300) {
    A3occ=1;
    A3counter = 0;
  }
  if (B1val < 300 ) {
    B1occ=1;
    B1counter= 0;
  }
  if (B2val < 300) {
    B2occ=1;
    B2counter = 0;
  }
  if (B3val < 300) {
    B3occ=1;
    B3counter = 0;
  }
  if (C1val < 300 ) {
    C1occ=1;
    C1counter = 0;
  }
  if (C2val < 300) {
    C2occ=1;
    C2counter = 0;
  }
  if (C3val < 300) {
    C3occ=1;
    C3counter = 0;
  }
  
  //
  // debounce counters
  //
  // AAAAAAAAAAAAAAAAAAAAAA
  //
  if ((A1occ == 1) && (A1val > 300)){
      A1counter = A1counter + 1;
  }
    // block has been occupied and is now unoccupied. Start counting for debounce
    if ((A1occ == 1) && (A1val > 300) && (A1counter >= debouncetime)){
    A1occ = 0;
   }
  // block has now become unoccupied and debounce timer has reached the maximum, declare block unoccupied
  if ((A2occ == 1) && (A2val > 300)){
    A2counter = A2counter + 1;
  }
  // block has been occupied and is now unoccupied. Start counting for debounce
    if ((A2occ == 1) && (A2val > 300) && (A2counter >= debouncetime)){
    A2occ = 0;
  }
    if ((A3occ == 1) && (A3val > 300)){
    A3counter = A3counter + 1;
  }
  // block has been occupied and is now unoccupied. Start counting for debounce
    if ((A3occ == 1) && (A3val > 300) && (A3counter >= debouncetime)){
    A3occ = 0;
  }
  
  //
  // debounce counters
  //
  // BBBBBBBBBBBBBBBBB
  //
  if ((B1occ == 1) && (B1val > 300)){
      B1counter = B1counter + 1;
  }
    // block has been occupied and is now unoccupied. Start counting for debounce
    if ((B1occ == 1) && (B1val > 300) && (B1counter >= debouncetime)){
    B1occ = 0;
    }
  // block has now become unoccupied and debounce timer has reached the maximum, declare block unoccupied
  if ((B2occ == 1) && (B2val > 300)){
    B2counter = B2counter + 1;
  }
  // block has been occupied and is now unoccupied. Start counting for debounce
  
  if ((B2occ == 1) && (B2val > 300) && (B2counter >= debouncetime)){
    B2occ = 0;
  }
  
  if ((B3occ == 1) && (B3val > 300)){
    B3counter = B3counter + 1;
  }
  // block has been occupied and is now unoccupied. Start counting for debounce
  
  if ((B3occ == 1) && (B3val > 300) && (B3counter >= debouncetime)){
    B3occ = 0;
  }
  
  //
  // debounce counters
  //
  // CCCCCCCCCCCCCCCCC
  //
  if ((C1occ == 1) && (C1val > 300)){
      C1counter = C1counter + 1;
  }
    // block has been occupied and is now unoccupied. Start counting for debounce
    if ((C1occ == 1) && (C1val > 300) && (C1counter >= debouncetime)){
    C1occ = 0;
     }
  // block has now become unoccupied and debounce timer has reached the maximum, declare block unoccupied
  if ((C2occ == 1) && (C2val > 300)){
    C2counter = C2counter + 1;
  }
  // block has been occupied and is now unoccupied. Start counting for debounce
    if ((C2occ == 1) && (C2val > 300) && (C2counter >= debouncetime)){
    C2occ = 0;
  }
    if ((C3occ == 1) && (C3val > 300)){
    C3counter = C3counter + 1;
  }
  // block has been occupied and is now unoccupied. Start counting for debounce
    if ((C3occ == 1) && (C3val > 300) && (C3counter >= debouncetime)){
    C3occ = 0;
  }
  
  //
  // Up and down train logic
  //
  if ((A1occ == 1) && (trackAUP == 0)) {
    trackADN = 1; //set as down train
    trackA = 1; //turn the lights on
          }
  if ((A1occ ==1) && (trackAUP == 1)) {
    trackADN = 0; //don't set as down train
    trackA = 0;  //ignore block input as train is departing
      }
  if ((A3occ == 1) && (trackADN == 0)) {
    trackAUP = 1; //set as up train
    trackA = 1; //turn the lights on
      }
  if ((A3occ == 1) && (trackADN == 1)) {
    trackAUP = 0; //don't set as up train
    trackA = 0; //ignore block input as train is departing
      }
  if (A2occ == 1) {
    trackA = 1; //this is the road crossing block. Must flash lights in any case if active.
      }
      if ((A1occ == 0) && (A2occ == 0) && (A3occ == 0)){
        trackA = 0; // turn lights off since that track is free of trains
      }
  if (trackA == 1) {
    AlightsON();
  }
  if (trackA == 0) {
    AlightsOFF();
  }
  //
  // track B
  if ((B1occ == 1) && (trackBUP == 0)) {
    trackBDN = 1; //set as down train
    trackB = 1; //turn the lights on
      }
  if ((B1occ ==1) && (trackBUP == 1)) {
    trackBDN = 0; //don't set as down train
    trackB = 0;  //ignore block input as train is departing
      }
  if ((B3occ ==1) && (trackBDN == 0)) {
    trackBUP = 1; //set as up train
    trackB = 1; //turn the lights on
      }
  if ((B3occ == 1) && (trackBDN == 1)) {
    trackBUP = 0; //don't set as up train
    trackB = 0; //ignore block input as train is departing
      }
  if (B2occ == 1) {
    trackB = 1; //this is the road crossing block. Must flash lights in any case if active.
    
  }
   if ((B1occ == 0) && (B2occ == 0) && (B3occ == 0)){
        trackB = 0; // turn lights off since that track is free of trains
      }
  if (trackB == 1) {
    BlightsON();
  }
  if (trackB == 0) {
    BlightsOFF();
  }
  //
  // track C
  if ((C1occ == 1) && (trackCUP == 0)) {
    trackCDN = 1; //set as down train
    trackC = 1; //turn the lights on
     }
  if ((C1occ == 1) && (trackCUP == 1)) {
    trackCDN = 0; //don't set as down train
    trackC = 0;  //ignore block input as train is departing
      }
  if ((C3occ ==1 ) && (trackCDN == 0)) {
    trackCUP = 1; //set as up train
    trackC = 1; //turn the lights on
     }
  if ((C3occ == 1) && (trackCDN == 1)) {
    trackCUP = 0; //don't set as up train
    trackC = 0; //ignore block input as train is departing
     }
  if (C2occ ==1)  {
    trackC = 1; //this is the road crossing block. Must flash lights in any case if active.
    }
     if ((C1occ == 0) && (C2occ == 0) && (C3occ == 0)){
        trackC = 0; // turn lights off since that track is free of trains
      }
  if (trackC == 1) {
    ClightsON();
  }
  if (trackC == 0) {
    ClightsOFF();
  }
  
 // delay (10); // 10ms delay as part of debounce
 // 8/1/21 - calculations seem to be enough to cause delay, so extra delay not required.
  
  readinputs(); // now check if anything has changed state
  // if a block has become inactive, count through debounce timer to ensure that is
  // has actually cleared the block completely
  // if occupied, counter is reset to zero.
  // if counter is zero, then the block is occupied. If the counter is between zero and maximum, then debounce count is in progress
    // counter has been reset because of a train. Check if it is now unoccupied and increment counter if so
   

  if ((A1occ == 0) && (A2occ == 0) && (A3occ==0) && (B1occ == 0)&& (B2occ == 0)&& (B3occ == 0)&& (C1occ == 0)&& (C2occ == 0)&& (C3occ == 0)){
    goto notrain; 
  }
 

// snip end 7/1/21
  goto TrainLoop;

notrain:
  delay (1); // do nothing
  //
  // END OF "train" VOID
  //
}

void readinputs() {
  A1val = analogRead(blockA1);
  A2val = analogRead(blockA2);
  A3val = analogRead(blockA3);
  B1val = analogRead(blockB1);
  B2val = analogRead(blockB2);
  B3val = analogRead(blockB3);
  C1val = analogRead(blockC1);
  C2val = analogRead(blockC2);
  C3val = digitalRead(blockC3);

  if (C3val == 0) {
    C3val = 0; //since C3 is on a digitalinput
  }
  if (C3val == 1) {
    C3val = 1000;
  }
}

void AlightsOFF() {
  digitalWrite (Astatus, HIGH); // LX1 group "enable " output
  digitalWrite (LX1a, LOW); //    LX1a lamp
  digitalWrite (LX1b, LOW); //    LX1b lamp
}
void BlightsOFF() {

  digitalWrite (Bstatus, HIGH);
  digitalWrite (LX2a, LOW); // 
  digitalWrite (LX2b, LOW); //
}
void ClightsOFF() {

  digitalWrite (Cstatus, HIGH);
  digitalWrite (LX3a, LOW); // 
  digitalWrite (LX3b, LOW); //
}
void AlightsON() {
  digitalWrite (Astatus, LOW);
    LX1val=digitalRead (LX1osc); // 
  if (LX1val == HIGH){
     digitalWrite (LX1a, LOW); // 
     digitalWrite (LX1b, HIGH);
  }
  if (LX1val == LOW) {
  digitalWrite (LX1a, HIGH); // 
  digitalWrite (LX1b, LOW);
  
  }
  
}
void BlightsON() {

  digitalWrite (Bstatus, LOW);
  LX2val=digitalRead (LX2osc); // read constant LX 50% duty cycle output
  if (LX2val == HIGH){          // if it is high, one lamp on, the other off
    digitalWrite (LX2a, LOW); // 
   digitalWrite (LX2b, HIGH); // 
  }
  if (LX2val == LOW) {
    digitalWrite (LX2b, LOW); //  if it is low, the lamps swap state
   digitalWrite (LX2a, HIGH); //
  }
}
void ClightsON() {

  digitalWrite (Cstatus, LOW);
  LX3val=digitalRead (LX3osc); // 22/3/22
  if (LX3val == HIGH) {
    digitalWrite (LX3a, LOW); // 22/3/22
    digitalWrite (LX3b, HIGH); // 22/3/22
  }
  
  if (LX3val == LOW) {
     digitalWrite (LX3b, LOW); // 22/3/22
    digitalWrite (LX3a, HIGH); // 22/3/22
  }
  

}