// Source : http://tronixstuff.com/2013/11/19/arduino-tutorials-chapter-15-rfid/
// with just a mod to print the tag nb before "Accepted"/"Rejected"

#include <SoftwareSerial.h>
#include <Servo.h>
#include <EEPROM.h>

//Pin definitions
#define RED_LED     12
#define GREEN_LED   13
#define RFID_RX     2
#define RFID_TX     3
#define PROG_BUTTON 4
#define SERVO_CTRL  5
#define DOOR_BUTTON 6

// Others parameters
#define SERVO_SPEED 2
#define SERVO_PWR_TIME    500 //time to stop powering servomotor after the end of move
#define DOOR_OPENED_TIME  3000
#define EMPTY_TAG     {0,0,0,0,0,0,0,0,0,0,0,0,0,0}
#define UNWRITTEN_TAG {255,255,255,255,255,255,255,255,255,255,255,255,255,255}

//Globals declarations

SoftwareSerial RFID(RFID_RX, RFID_TX); // RX and TX
Servo door_servo;

int data1 = 0;
int tag_ok = -1;
byte servo_pos = 180;
bool door_state = 0;    // door opened ?
bool door_move = false;     // door moving ?
long door_timer = 0;    // timer to close the door
long servo_timer = 0;   //timer to stop powering servomotor
long red_led_timer = 0;

//byte tag1[14] = {2,52,54,48,48,57,49,48,57,56,48,53,69,3};
byte newtag[14] = EMPTY_TAG; // used for read comparisons

const byte emptytag[14] = EMPTY_TAG;
const byte unwrittentag[14] = UNWRITTEN_TAG;

byte * readeepromtag(short tagnb=0) {
  // Read the n-th RFID tag in EEPROM
  
  //First check if the # of tag is out of the capacity of EEPROM
  //Return an null tag if so.
  if ((tagnb+1)*14 > EEPROM.length()) {
    Serial.print(F("Error: Tag Nb to high : "));
    Serial.println(tagnb);
    byte tag[14] = EMPTY_TAG;
    return tag;
  }
  byte tag[14] = EMPTY_TAG;
  for (int i=0; i<14; i++)
  {
    tag[i]=EEPROM.read(i + tagnb*14);
    //delay(1);   //small delay to avoid misreading
  }
  //Serial.print("Tag nb ");
  //Serial.print(tagnb);
  //Serial.print(" : ");
  //for (int z = 0; z < 14 ; z++) {
  //    Serial.print(tag[z]);
  //    Serial.print(",");
  //}
  //Serial.println("");
  
  delay(2);   //small delay to avoid misreading
  return tag;
}

int maxeepromtags() {
  // Return maximum number of tag that can be stored in EEPROM
  return EEPROM.length() / 14;
}

boolean comparetag(byte taga[14], byte tagb[14]) {
  // Compare two RFID tag
  int x = 0;
  for (int i = 0 ; i < 14 ; i++)
  {
    if (taga[i] == tagb[i]) x++;
  }
  return (x == 14);
}

int findtag(byte searchtag[14]) {
  //Find a given tag in EEPROM
  
  byte *tag;
    
  for (int i=0; i < maxeepromtags(); i++)
  {
    tag = readeepromtag(i);
    if (comparetag(tag, searchtag)) return i;
  }
  return -1;
}

void tagOK() {
  if (digitalRead(PROG_BUTTON))
  {
    for (int z = 0; z < 14 ; z++) {
      Serial.print(newtag[z]);
      Serial.print(".");
    }
    Serial.println(F(" : Accepted"));
    if (!door_state) {
      Serial.println("Ouverture...");
      door_state = 1;
      //door_timer = millis() + 1000;
    }
  }
  else
  {
    delTag(newtag);
  }
}

void tagNoOK() {
  if (digitalRead(PROG_BUTTON)) {
    for (int z = 0; z < 14 ; z++) {
      Serial.print(newtag[z]);
      Serial.print(".");
    }
    Serial.println(F(" : Rejected"));
    digitalWrite(RED_LED, HIGH);
    //Light the red LED for 1s
    red_led_timer = millis() + 1000;
  }
  else {
    addNewTag(newtag);
  }
}

void readRFID() { 
  tag_ok = 0;
  delay(100); // time for the data to come in from the serial buffer.
  
  //Check up to 3 times if rejected to avoid mistake
  for (byte i=0; i<3; i++) {
    // read tag numbers
    for (byte z = 0 ; z < 14 ; z++) // read the rest of the tag
    {
      data1 = RFID.read();
      newtag[z] = data1;
    }
    RFID.flush(); // stops multiple reads

    // do the tag exist in EEPROM
    if (findtag(newtag) >= 0) tag_ok=1;
    if (tag_ok>0) break;
  }

  //Check if the tag is not an error before doing anything
  if (!comparetag(newtag, unwrittentag)) {
    // now do something based on tag type
    if (tag_ok > 0) tagOK();
    else tagNoOK();
  }
  
  // empty the data cache
  while (RFID.available()) {
    RFID.read();
  }
}

void disableServo() {
  //Disable servomotor once they stop moving
  if (!door_move and servo_timer < millis() and door_servo.attached()) {
    door_servo.detach();
    Serial.println(F("Servo disabled"));
  }
}

void updateDoor() {
  //Check if the need to move and move it 

  //Apply door status
  if (door_state) {
    digitalWrite(GREEN_LED, HIGH);
    digitalWrite(RED_LED, LOW);
    if (servo_pos >= 180) {
      //if door has to be open and already open, disable servomotor
      disableServo();
      if (door_move) {
        Serial.println(F("Door : opened"));
        door_timer = millis() + DOOR_OPENED_TIME;
        servo_timer = millis() + SERVO_PWR_TIME;
        door_move = false;
      }
    }
    else {
     //Keep opening the door
     door_move = true;
     servo_pos += SERVO_SPEED;
     door_servo.attach(SERVO_CTRL);
     door_servo.write(servo_pos);
     //Serial.print(F("Door : opening "));
     //Serial.println(servo_pos);
    }
    //Check opened-door timer
    if (millis() < door_timer)
    {
      //Serial.print(F("Door timer :"));
      //Serial.println(door_timer - millis());
    }
    else if (!door_move)
    {
      door_state = 0;
    }
  }
  else {
    digitalWrite(GREEN_LED, LOW);
    if (servo_pos <= 0) {
      //if door has to be open and already open, disable servomotor
      disableServo();
      if (door_move) {
        Serial.println(F("Door : closed"));
        digitalWrite(RED_LED, LOW);
        servo_timer = millis() + SERVO_PWR_TIME;
        door_move = false;
      }
    }
    else {
     //Keep closing the door
     door_move = true;
     servo_pos -= SERVO_SPEED;
     door_servo.attach(SERVO_CTRL);
     door_servo.write(servo_pos);
     //Serial.print(F("Door : closing "));
     //Serial.println(servo_pos);
    }
  }
}

void updateRedLED() {
  //Check the redLED timer and turn ON or OFF the LED
  if (millis() < red_led_timer) {
    digitalWrite(RED_LED, HIGH);
//    Serial.print(F("Red LED timer : "));
//    Serial.println(red_led_timer - millis());
  }
  else digitalWrite(RED_LED, LOW);
}

void addNewTag(byte tag[14]) {
  Serial.print(F("Adding new tag to EEPROM : "));
  for (int z = 0; z < 14 ; z++) Serial.print(tag[z]);
  Serial.println("");

  //Find first empty or unwritten space in EEPROM
  int tagnb = findtag(emptytag);
  if (tagnb < 0){
    Serial.println(F("No empty space found."));
    tagnb = findtag(unwrittentag);
    if (tagnb < 0) {
      Serial.println(F("No unwritten space found either. Can't save more tag."));
      for (int i=0; i<4; i++) {
        digitalWrite(RED_LED, HIGH);
        delay(500);
        digitalWrite(RED_LED, LOW);
        delay(500);
      }
      return -1;
    }
  }

  Serial.print(F("New tag number : "));
  Serial.println(tagnb);
  Serial.print(F("Writing..."));
  for (int z = 0; z < 14 ; z++) {
    EEPROM.write(z+(tagnb*14), tag[z]);
  }
  Serial.print(F("  Checking..."));
  int tmp = findtag(tag);
  if (tmp != -1 and tmp == tagnb) {
    Serial.println("OK");
    for (int i=0; i<3; i++) {
      digitalWrite(GREEN_LED, HIGH);
      delay(500);
      digitalWrite(GREEN_LED, LOW);
      delay(500);
    }
  }
  else {
    Serial.println("Failed");
    for (int i=0; i<5; i++) {
      digitalWrite(RED_LED, HIGH);
      delay(500);
      digitalWrite(RED_LED, LOW);
      delay(500);
    }
  }
}

void delTag(byte tag[14]) {
  Serial.print(F("Deleting tag from EEPROM : "));
  for (int z = 0; z < 14 ; z++) {
    Serial.print(tag[z]);
  }
  Serial.println("");

  int tagnb = findtag(tag);
  Serial.print("Tag number ");
  Serial.println(tagnb);

  Serial.print(F("Writing zeros..."));
  for (int z = 0; z < 14 ; z++) {
    EEPROM.write(z+(tagnb*14), 0);
  }
  Serial.print(F("  Checking..."));
  int tmptag = readeepromtag(tagnb);
  if (comparetag(tmptag, emptytag)) {
    Serial.println("OK");
    digitalWrite(GREEN_LED, HIGH);
    delay(500);
    digitalWrite(GREEN_LED, LOW);
    delay(500);
  }
  else {
    Serial.println("Failed");
    for (int i=0; i<5; i++) {
      digitalWrite(RED_LED, HIGH);
      delay(500);
      digitalWrite(RED_LED, LOW);
      delay(500);
    }
  }
}

void setup() {
// start serial to PC 
  Serial.begin(115200);
 
  // start serial to RFID reader
  RFID.begin(9600);
  // empty the data cache
  while (RFID.available()) {
    RFID.read();
  }
  
  // for status LEDs
  pinMode(GREEN_LED, OUTPUT);
  pinMode(RED_LED, OUTPUT);

  //Mode for button and end-stop
  pinMode(PROG_BUTTON, INPUT_PULLUP);
  pinMode(DOOR_BUTTON, INPUT_PULLUP);

  Serial.print(F("EEPROM length : "));
  Serial.print(EEPROM.length());
  Serial.print(F(" bytes ("));
  Serial.print(maxeepromtags());
  Serial.println(F(" tags)"));
  Serial.println(findtag(newtag));
  Serial.println(findtag(unwrittentag));

}

void loop() {
  tag_ok = -1;

  if (RFID.available() > 0 and !door_state ) {
    readRFID();
  }

  if (!digitalRead(DOOR_BUTTON)) {
    Serial.println("Ouverture par l'utilisateur.");
    door_state = 1;
  }
  updateDoor();
  updateRedLED();
  
  delay(10);
}
