// Como uso pines analógicos necesito poner resistencias pulldown en cada fotodetector.
// El estado 0 significa que no está midiendo nada.
// El estado 1023 (o casi) significa que el sensor está midiendo algo.
// Como se usa el Timer 1 no se puede usar digitalWrite en los pines: 9 y 10.
// Display: SDA va a A4 y SCL va a A5.
// El encoder trucho que conseguí funciona teniendo siempre los pines A y B abiertos (en 1 con el internal pullup). En cada click, pasa a ambos por el cero (el orden depende del sentido de giro).
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#define pin_sensor_1 A1
#define pin_sensor_2 A2
#define pin_sensor_3 A3
#define pin_transistor_leds_IR 2
#define pin_led_indicador 3
#define pin_boton_PAUSA 4
#define pin_Encoder_SW 5
#define pin_Encoder_A 6
#define pin_Encoder_B 7
#define pin_Buzzer 8
//Crear el objeto lcd  dirección  0x27 (a veces es 0x3F) y 16 columnas x 2 filas
LiquidCrystal_I2C lcd(0x27, 16, 2);
int tercios_totales_de_vuelta = 0;
int umbral_de_deteccion = 500;  // Sale de probar con la rueda enebradora.
boolean deteccion_sensor_1 = false;
boolean deteccion_sensor_2 = false;
boolean deteccion_sensor_3 = false;
int estado_sensor_1_actual = 0;
int estado_sensor_1_actual_IR = 0;
int estado_sensor_1_actual_Ruido = 0;
int estado_sensor_1_anterior = 0;
int estado_sensor_2_actual = 0;
int estado_sensor_2_actual_IR = 0;
int estado_sensor_2_actual_Ruido = 0;
int estado_sensor_2_anterior = 0;
int estado_sensor_3_actual = 0;
int estado_sensor_3_actual_IR = 0;
int estado_sensor_3_actual_Ruido = 0;
int estado_sensor_3_anterior = 0;
volatile boolean time_to_blink = false;
boolean blink_on = false;
// Estados:
enum tipo_estado { est_Contador,
                   est_Modificador,
                   est_Pausa };
// estado_actual = tipo_estado::menu_inicial;
tipo_estado estado_actual = est_Contador;
tipo_estado estado_anterior = est_Contador;
void setup() {
  Serial.begin(9600);
  pinMode(pin_led_indicador, OUTPUT);
  pinMode(pin_transistor_leds_IR, OUTPUT);
  digitalWrite(pin_led_indicador, HIGH);
  digitalWrite(pin_transistor_leds_IR, LOW);
  pinMode(pin_boton_PAUSA, INPUT_PULLUP);
  pinMode(pin_Encoder_SW, INPUT_PULLUP);
  pinMode(pin_Encoder_A, INPUT);
  pinMode(pin_Encoder_B, INPUT);
  pinMode(pin_Buzzer, OUTPUT);
  // Inicializar el LCD
  lcd.init();
  //Encender la luz de fondo.
  lcd.backlight();
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("     N: ");
  muestra_vueltas(tercios_totales_de_vuelta, 0);
  setupTimer1();
}
void loop() {
  // Verifica estados:
  if (digitalRead(pin_boton_PAUSA) == LOW) {  // Si el botón PAUSA está oprimido.
    estado_actual = est_Pausa;
    delay(50);                                      // Sirve como debounce.
    Estado_Pausa();                                 // Entra en estado Pausa.
  }
  else if (1) {  // Si el botón del encoder está oprimido.
    estado_actual = est_Modificador;
    delay(50);
    while (digitalRead(pin_Encoder_SW) == LOW) {}  // Espera mientras el botón se mantenga pulsado. Cuando se suelta el botón se pasa al estado modificador. Tal vez, si se aprieta demasiado rápido, esta linea trae problemas.
    delay(50);
    Estado_Modificador();  // Entra en estado Modificador.
  }
  else {
    estado_actual = est_Contador;
    Estado_Contador();  // Entra en estado Contador.
  }
}
void setupTimer1() {
}
ISR(TIMER1_COMPA_vect) {
  // Debe ser lo más corta posible, solo para "levantar alguna bandera" y listo.
  // Al principio intenté prender y apagar el display desde acá y se me colgaba.
  // Se ejecuta cada vez que se dispara la condición del timer 1.
  TCNT1 = 0;
  time_to_blink = true;
}
void Estado_Pausa() {
  // Entra en este estado cuando se detecta que el botón PAUSA está presionado.
  // Sale de la función cuando se suelta el botón PAUSA.
  while (digitalRead(pin_boton_PAUSA) == LOW) {
    if (time_to_blink) {
      time_to_blink = false;
      lcd.setBacklight(blink_on);  // Se podría usar lcd.noDisplay(), que apaga toda la pantalla.
      digitalWrite(pin_led_indicador, blink_on);
      blink_on = !blink_on;  // Si era verdadero, lo cambia por falso y al revés.
    }
  }
  lcd.setBacklight(HIGH);
  digitalWrite(pin_led_indicador, HIGH);
}
void Estado_Modificador() {
  // Entra en este estado cuando se detecta que el botón del encoder está presionado.
  // Sale de la función cuando se vuelve a presionar el botón del encoder.
  // Al salir, siempre cambia el contador por el valor ingresado, así que si no se quiere modificar, hay que volver a elegir el mismo que tenía antes.
  uint8_t encoder_A_actual;  // uint8_t es equivalente a byte y es lo que está definido como argumento en la función digitalWrite().
  uint8_t encoder_A_anterior;
  int tercios_totales_de_vuelta_a_modificar;
uint8_t encoder_B_actual;
  encoder_A_anterior = digitalRead(pin_Encoder_A);
  tercios_totales_de_vuelta_a_modificar = tercios_totales_de_vuelta;
  lcd.setCursor(0, 1);
  lcd.print(" Change: ");
  while (1) {
    encoder_A_actual = digitalRead(pin_Encoder_A);
encoder_B_actual = digitalRead(pin_Encoder_B);
Serial.print("encoder_A_anterior: ");
Serial.println(encoder_A_anterior);
Serial.print("encoder_A_actual: ");
Serial.println(encoder_A_actual);
Serial.print("Encoder_B_actual: ");
Serial.println(encoder_B_actual);
    if (encoder_A_actual != encoder_A_anterior) {            // Si se rotó la perilla. Creo que en cada click el A pasa a cero y, luego a 1, por lo que solo hay que contabilizar uno de los dos estados.
			encoder_A_anterior = encoder_A_actual;
Serial.print("encoder_A_anterior: ");
Serial.println(encoder_A_anterior);
			if (encoder_A_actual == 1) {            // Si se rotó la perilla. Creo que en cada click el A pasa a cero y, luego a 1, por lo que solo hay que contabilizar uno de los dos estados.
      	if (encoder_B_actual != encoder_A_actual) {  // Se rotó en sentido horaio.
        	tercios_totales_de_vuelta_a_modificar++;
      	} else {
        	tercios_totales_de_vuelta_a_modificar--;
      	}
Serial.print("tercios_totales_de_vuelta_a_modificar:");
Serial.println(tercios_totales_de_vuelta_a_modificar);
  
      	muestra_vueltas(tercios_totales_de_vuelta_a_modificar, 1);
			}
    }
//    else if (encoder_A_actual != encoder_A_anterior && encoder_A_actual == 1) {
//      encoder_A_anterior = encoder_A_actual;
//    }
    delay(50);  // Es el debouncer más simple que existe.
Serial.println(" ");
  }
  while (0) {}  // Espera mientras el botón se mantenga pulsado. Cuando se suelta el botón sale del estado modificador. Tal vez, si se aprieta demasiado rápido, esta linea trae problemas.
  delay(50);
  tercios_totales_de_vuelta = tercios_totales_de_vuelta_a_modificar;
  muestra_vueltas(tercios_totales_de_vuelta, 0);
  lcd.setCursor(0, 1);
  lcd.print("                ");
  digitalWrite(pin_led_indicador, HIGH);
}
void Estado_Contador() {
  // Entra en este estado cada vez que no está pulsado el botón del encoder o de pausa.
  // Realiza un par de mediciones (con y sin LED IR) y dsevuelve el control del procesador.
  // Esta técnica, sacada de https://youtu.be/-jA5W2S5PeE, permite eliminar el ruido IR de fondo.
  // Además, los leds IR solo están prendidos cuando se mide el primer paso, y si no se está en este estado, permanecen apagados.
  digitalWrite(pin_transistor_leds_IR, HIGH);
  delay(1);
  estado_sensor_1_actual_IR = analogRead(pin_sensor_1);
  estado_sensor_2_actual_IR = analogRead(pin_sensor_2);
  estado_sensor_3_actual_IR = analogRead(pin_sensor_3);
  digitalWrite(pin_transistor_leds_IR, LOW);
  delay(1);
  estado_sensor_1_actual_Ruido = analogRead(pin_sensor_1);
  estado_sensor_2_actual_Ruido = analogRead(pin_sensor_2);
  estado_sensor_3_actual_Ruido = analogRead(pin_sensor_3);
  estado_sensor_1_actual = estado_sensor_1_actual_IR - estado_sensor_1_actual_Ruido;
  estado_sensor_2_actual = estado_sensor_2_actual_IR - estado_sensor_2_actual_Ruido;
  estado_sensor_3_actual = estado_sensor_3_actual_IR - estado_sensor_3_actual_Ruido;
  /*
  // Esto es para medir cuánto vale la señal filtrada con la rueda y elegir el umbral.
  Serial.print("IR:"); // No tiene que haber espacios después del texto para que se pueda usar el Serial Plotter.
  Serial.print(estado_sensor_1_actual_IR);
  Serial.print(",");
  Serial.print("Ruido:");
  Serial.print(estado_sensor_1_actual_Ruido);
  Serial.print(",");
  Serial.print("Filtrado:");
  Serial.println(estado_sensor_1_actual);
*/
  if (estado_sensor_1_actual > umbral_de_deteccion && estado_sensor_1_anterior < umbral_de_deteccion) {
    // ACÁ OCURRE LA DETECCIÓN.
    deteccion_sensor_1 = true;
    if (deteccion_sensor_3 == true) {
      Actualiza_tercios_totales(true);
      deteccion_sensor_3 = false;
    } else if (deteccion_sensor_2 == true) {
      Actualiza_tercios_totales(false);
      deteccion_sensor_2 = false;
    }
  } else if (estado_sensor_2_actual > umbral_de_deteccion && estado_sensor_2_anterior < umbral_de_deteccion) {
    // ACÁ OCURRE LA DETECCIÓN.
    deteccion_sensor_2 = true;
    if (deteccion_sensor_1 == true) {
      Actualiza_tercios_totales(true);
      deteccion_sensor_1 = false;
    } else if (deteccion_sensor_3 == true) {
      Actualiza_tercios_totales(false);
      deteccion_sensor_3 = false;
    }
  } else if (estado_sensor_3_actual > umbral_de_deteccion && estado_sensor_3_anterior < umbral_de_deteccion) {
    // ACÁ OCURRE LA DETECCIÓN.
    deteccion_sensor_3 = true;
    if (deteccion_sensor_2 == true) {
      Actualiza_tercios_totales(true);
      deteccion_sensor_2 = false;
    } else if (deteccion_sensor_1 == true) {
      Actualiza_tercios_totales(false);
      deteccion_sensor_1 = false;
    }
  }
  estado_sensor_1_anterior = estado_sensor_1_actual;
  estado_sensor_2_anterior = estado_sensor_2_actual;
  estado_sensor_3_anterior = estado_sensor_3_actual;
  digitalWrite(pin_led_indicador, LOW);  // se puede mejorar con algún contador para que se mantenga encendido más tiempo.
  delay(100);                            // Para que no esté todo el tiempo encendiendo y apagando los leds.
}
void muestra_vueltas(int tercios_de_vuelta, int fila) {
  // fila indica la fila del display que debe ser cambiada con el dato de las vueltas.
  int vueltas;
  byte fraccion_de_vuelta;
  char char_texto_linea_display[16];
  vueltas = tercios_de_vuelta / 3;
  fraccion_de_vuelta = tercios_de_vuelta % 3;  // Toma valores 0, 1 o 2.
  if (fila == 0) {
    sprintf(char_texto_linea_display, "      N: %03d %d/3", vueltas, fraccion_de_vuelta); // Muchos dicen que usar sprintf es lento, pero acá no me importa.
  }
  else {
    sprintf(char_texto_linea_display, " Change: %03d %d/3", vueltas, fraccion_de_vuelta); // Muchos dicen que usar sprintf es lento, pero acá no me importa.
  }
  lcd.setCursor(0, fila);
  lcd.print(char_texto_linea_display);
Serial.print("Vueltas: ");
Serial.print(char_texto_linea_display);
Serial.print("  ");
Serial.print(fraccion_de_vuelta);
Serial.println("/3");
}
void Actualiza_tercios_totales(boolean incrementa) {
  if (incrementa) {
    tercios_totales_de_vuelta++;
  }
  else {
    tercios_totales_de_vuelta--;
  }
  muestra_vueltas(tercios_totales_de_vuelta, 0);
  if ((tercios_totales_de_vuelta % 3) == 0) {
    tone(pin_Buzzer, 330, 50);
  }
  else {
    tone(pin_Buzzer, 165, 50);
  }
//  digitalWrite(pin_led_indicador, HIGH);  // se puede mejorar con algún contador para que se mantenga encendido más tiempo.
}