Control de acceso y aforo personalizable

Autores: Carlos Jiménez López, Miguel Carmona Garrido y Marcos Meca Merino

Explicación del proyecto

Nuestro proyecto se trata de un control de acceso. Con tarjetas o chapas que se pasan por un sensor RFID, el control dejaría pasar o no, en función de si se trata de una acreditación válida, y mostrando en una matriz de leds una flecha o una cruz cuando corresponda. Además, este control de acceso lleva la cuenta del aforo que hay, y posee un aforo máximo que no dejará que se supere. Para establecer el aforo máximo, se ha desarrollado una aplicación muy sencilla que permite hacer dos cosas:

  1. Si se introduce el usuario y contraseña de un operario, este podrá cambiar el aforo máximo, siempre y cuando el nuevo aforo máximo sea mayor que el aforo que hay en el momento.
  2. Si se trata de un usuario normal, este podrá pulsar un botón en la aplicación que hará que se muestre por la pantalla LCD del control el aforo actual y el aforo máximo.

Componentes utilizados

En cuanto a los componentes utilizados, la mayoría de sensores ya los teníamos ya que disponíamos de un kit de Arduino bastante completo. Por otro lado, se fabricaron más cables macho-hembra ya que no tuvimos suficientes. Por último, nos hicimos con 2 llaveros de acceso más, que nos prestaron ya que no se utilizaban.

Componente/ProductoCoste
Pantalla LCD0€
Potenciómetro0€
Sensor ultrasónico x20€ (uno de ellos prestado)
Módulo RFID0€
Matriz de leds0€
Módulo Bluetooth8,49€
Cables macho-hembra0€
Cables macho-macho0€
Llaveros de acceso x3 y tarjeta0€
Caja2,50€
Bridas0,80€
Cinta de doble cara0,90€
TOTAL:12,69€

Esquema del hardware

Simulación del circuito realizada en Tinkercad

Problemas encontrados

1. Falta de puertos

Cuando nos dispusimos a contar los pines que nos harían falta para poder montar todos los sensores que planteamos en un comienzo, nos dimos cuenta de que no teníamos suficientes con la placa. Al comienzo nos planteamos ir quitando sensores y recortando el máximo número de puertos posibles. Mientras tanto, pensamos en comprar multiplexores, ya que otro grupo los necesitaba también y pensamos en comprarlos en conjunto. Finalmente logramos que no nos hicieran falta, y terminamos ocupando todos los puertos de la placa. Esto provocó que tuviéramos que utilizar los puertos analógicos como digitales. Con una búsqueda en internet pudimos saber cómo hacerlo. Por otro lado, los puertos 0 y 1, dedicados a las comunicaciones en el Arduino, también tuvimos que ocuparlos, por lo que perdimos la conexión con el monitor serial. Como ya hemos mencionado anteriormente, esto se solucionó utilizando la pantalla LCD que habíamos configurado previamente como salida para las comprobaciones necesarias. Cabe destacar que el único inconveniente relacionado con ocupar estos puertos fue que, cada vez que se necesita subir código a la placa, se ha de desconectar lo que esté en estos puertos, ya que si no se producirá un error y este no se subirá.

2. Falta de cables

En cuanto a los cables, donde pudimos ver que nos quedamos escasos fue en los cables macho-hembra. Gracias al padre de uno de los integrantes del grupo, pudo hacernos más cables, lo que nos ahorró tener que comprarlos.

3. Problema con ultrasonidos

A la hora de configurar el segundo ultrasonidos e integrarlo en los avances que ya llevábamos del proyecto, los resultados que obteníamos de ambos no nos cuadraban con los que esperábamos. Estuvimos un tiempo intentando averiguar qué les ocurría, cambiando los ultrasonidos por los de otros grupos que nos prestaron, cambiando los cables, puertos, código, etc., hasta que finalmente nos dimos cuenta que lo que estaba ocurriendo era que estaban interfiriendo el uno en el otro, por lo que probamos a separarlos y así fue.

4. Desarrollo de la aplicación

Al comienzo del desarrollo de la aplicación, utilizamos la plataforma “App Inventor”, pero debido a su sencillez (por su programación en bloques, que no permitía gran personalización) y a la complejidad de realizar un diseño que nos agradase, la desechamos y comenzamos a trabajar en “Android Studio”. Sin embargo, nos supuso gran esfuerzo, sobre todo a la hora de programar la parte de transferencias por Bluetooth. Al no encontrar información suficiente para su desarrollo decidimos volver a “App Inventor”, donde finalmente conseguimos un resultado más o menos satisfactorio.

5. Caja impresa en 3d

Al comienzo del proyecto, cuando estábamos planteandonos qué hacer, ya pensamos en diseñar e imprimir la caja en la que colocaríamos nuestro proyecto, ya que en principio disponíamos de los medios para hacerlo y nos pareció una idea interesante. Finalmente, una vez diseñamos la caja y nos disponíamos a imprimirla, la impresora comenzó a fallar (el extrusor no dejaba pasar correctamente el PLA), y no nos permitió imprimir las piezas que queríamos, debido principalmente a su tiempo de impresión (14 horas la caja y 5 horas la tapa). Debido a esto, optamos por comprar una caja en una tienda local, y posteriormente, con un cutter, hacer los agujeros necesarios para encajar todos los componentes.

Vídeo demostración

Código

A contnuación se muestra el código. Este se encuentra suficientemente comentado como para entender cada línea del mismo.

//Librerias

//MATRIZ
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>

// RFID
#include <MFRC522.h>

// BLUETOOTH
#include <SoftwareSerial.h>

// LCD
#include <LiquidCrystal.h>

////////////////////////////////////////////////////////////////////////////////
// MATRIZ
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 1
#define CS_PIN 3

// Software SPI:
 #define DATA_PIN 2
 #define CLK_PIN 4
 MD_Parola myDisplay = MD_Parola(HARDWARE_TYPE, DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES);

// Flecha
const uint8_t F_ARROW = 1;
const uint8_t W_ARROW = 10;
const uint8_t PROGMEM arrow[F_ARROW * W_ARROW] =
{
  0x18, 0x3c, 0x7e, 0xff, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c
};

// Cruz
const uint8_t F_CROSS = 1;
const uint8_t W_CROSS = 10;
const uint8_t PROGMEM cross[F_CROSS * W_CROSS] =
{
  0x81, 0xc3, 0xe7, 0x7e, 0x3c, 0x3c, 0x7e, 0xe7, 0xc3, 0x81
};

///////////////////////////////////////////////////////////////////////////////////

// ULTRASONIDOS
const int EchoPinEntrar = 5;
const int TriggerPinEntrar = 6;
int entrar;

const int EchoPinSalir = 0;
const int TriggerPinSalir = 1;
int salir;


unsigned long tiempoEntrar;
unsigned long tiempoSalir;
unsigned long pausaAcceso;

/////////////////////////////////////////////////////////////////////////////////

// RFID
#define RST_PIN	9     //Pin 9 para el reset del RC522
#define SS_PIN	10    //Pin 10 para el SS (SDA) del RC522
MFRC522 mfrc522(SS_PIN, RST_PIN); ///Creamos el objeto para el RC522

byte ActualUID[4]; //almacenará el código del Tag leído
byte Usuario1[4]= {0x70, 0x5B, 0xDE, 0x87} ; //código del usuario 1
byte Usuario2[4]= {0x40, 0xD3, 0x56, 0x7E} ; //código del usuario 2

unsigned long timingMATRIZ;

/////////////////////////////////////////////////////////////////////////////////

// BLUETOOTH
SoftwareSerial BT(7,8);    // Definimos los pines TX y RX del Arduino conectados al Bluetooth
String datoleido;
unsigned long pausaBT;

/////////////////////////////////////////////////////////////////////////////////

// LCD

//Crear el objeto LCD con los números correspondientes (rs, en, d4, d5, d6, d7)
LiquidCrystal lcd(19, 18, 17, 16, 15, 14);

/////////////////////////////////////////////////////////////////////////////////

// AFORO Y ACCESO
int aforoMax;
int aforoActual;
bool entra;
bool sale;

void setup() {

  //Serial.begin(9600);
  SPI.begin();


  // MATRIZ
  myDisplay.begin();
  myDisplay.setIntensity(0);
  myDisplay.displayClear();

  //Cruz
  myDisplay.setSpriteData(cross, W_CROSS, F_CROSS, cross, W_CROSS, F_CROSS);
  myDisplay.displayText("", PA_CENTER, 50, 0, PA_SPRITE, PA_SPRITE);
  


  // ULTRASONIDOS
  pinMode(TriggerPinEntrar, OUTPUT);
  pinMode(EchoPinEntrar, INPUT);
  pinMode(TriggerPinSalir, OUTPUT);
  pinMode(EchoPinSalir, INPUT);


  // RFID
  mfrc522.PCD_Init(); // Iniciamos el MFRC522

  // BLUETOOTH
  BT.begin(38400);
  datoleido="";
  pausaBT=0;

  // LCD
  // Usar pines analogicos como digitales
  pinMode(A0, OUTPUT); // configura ‘14’ como salida
  pinMode(A1, OUTPUT); // configura ‘15’ como salida
  pinMode(A2, OUTPUT); // configura ‘16’ como salida
  pinMode(A3, OUTPUT); // configura ‘17’ como salida
  pinMode(A4, OUTPUT); // configura ‘18’ como salida
  pinMode(A5, OUTPUT); // configura ‘19’ como salida

  // Inicializar el LCD con el número de  columnas y filas del LCD
  lcd.begin(16, 2);

  // Aforo y acceso
  pausaAcceso=0;
  aforoMax=5;
  aforoActual=0;
  entra=false;
  sale=false;


}


void loop() {
  
  // En el siguiente if se activa la cruz para mostrarla después en el caso de que:
  //     - Se intentó acceder, se accedió y han pasado 4 segundos o más desde que se activó la flecha.
  //     - Se intentó acceder pero no se completó el acceso (solo se activó un sensor de los dos). En este caso la flecha no se activa.
  //
  // Además, también nos ermite inicializar otros parámetros como el momento de activación de la matriz, el mensaje de identificación del LCD,
  // los tiempos de entrada y salida, la pausa del acceso, la pausa de bluetooth y los permiso de entrada y salida.

  //Comprobamos si han pasado más de 4 segundos desde que se activó la flecha por última vez (acceso concedido) y más de 2 segundos desde el último intento de acceso
  //(para cubrir los accesos incompletos)
  if ((millis() - timingMATRIZ > 4000) && (millis() - pausaAcceso > 2000) && (millis() - pausaBT > 2000)){
    
    //Establecemos la cruz para mostrarla posteriormente
    myDisplay.setSpriteData(cross, W_CROSS, F_CROSS, cross, W_CROSS, F_CROSS);
    myDisplay.displayText("", PA_CENTER, 50, 0, PA_SPRITE, PA_SPRITE);

    // Reiniciamos el momento en el que se activa la matriz
    //timingMATRIZ=millis();

    // Escribimos el mensaje en el LCD.
    lcd.setCursor(0,0);
    lcd.clear();
    lcd.print("  IDENTIFICATE");
    //lcd.print(aforoActual);
    lcd.setCursor(0,1);
    lcd.print("   POR  FAVOR");
    //lcd.print(aforoMax);

    // Inicializamos otros valores
    tiempoEntrar=0;
    tiempoSalir=0;
    pausaAcceso=millis();
    pausaBT=millis();
    entra=false;
    sale=false;
  }

  //Mostramos por la matriz lo que esté activado en el momento (flecha o cruz)
  if (myDisplay.displayAnimate()) {
    myDisplay.displayReset();
  }
  

  

  
  // A continuación llevamos a cabo el propio método de identificación. Para ellos, se leen del lector RFID las tarjetas que se acerquen a este, y se comprueba si
  // se tratan de identificaciones válidas. Pueden ocurrir tres casos:
  //
  //    - La tarjeta/llavero sea válida: se mostrará por pantalla que se ha concedido el acceso. Se activarán ambas variables booleana "entra" y "sale" que nos permitirá hacer la comprobación
  //      de si el usuario ha pasado (entrar o salir) o no.
  //
  //    - La tarjeta/llavero no sea válida: se mostrará por pantalla que se ha denegado el acceso.
  //
  //    - El aforo esté completo, solo si la tarjeta/llavero es válida: se mostrará por pantalla que el aforo está completo, y solo se podrá salir.


  // Revisamos si hay nuevas tarjetas  presentes
	if (mfrc522.PICC_IsNewCardPresent()){
    // Seleccionamos una tarjeta
    if (mfrc522.PICC_ReadCardSerial()){
                                                                                                                                //Serial.print(F("Card UID:"));
      // Recorremos el UID de la tarjeta leida
      for (byte i = 0; i < mfrc522.uid.size; i++) {
        //lcd.clear();
        //lcd.setCursor(0,1);
        //lcd.print(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " ");
        //lcd.print(mfrc522.uid.uidByte[i], HEX);

        //delay(3000);
        // Se guarda el UID completo de la tarjeta/llavero
        ActualUID[i]=mfrc522.uid.uidByte[i];
      }

                                                                                                                              //Serial.print("     ");                 
      // Comparamos los UID para determinar si es uno de nuestros usuarios
      if(compareArray(ActualUID,Usuario1)){
                                                                                                                              //Serial.println("Acceso concedido...");
        // Mostramos por pantalla el mensaje correspondiente
        lcd.setCursor(0,0);
        lcd.clear();
        lcd.print("Acceso concedido");
        lcd.setCursor(0,1);
        lcd.print(" Adelante  Ivan ");

        // Activamos la flecha para ser mostrada
        myDisplay.setSpriteData(arrow, W_ARROW, F_ARROW, arrow, W_ARROW, F_ARROW);
        myDisplay.displayText("", PA_CENTER, 50, 0, PA_SPRITE);

        // Mostramos por la matriz lo que esté activado en el momento (en este caso la flecha)
        if (myDisplay.displayAnimate()) {
          myDisplay.displayReset();
        }

        // Guarda el momento en el que se activa la flecha
        timingMATRIZ=millis();

        // Establecemos el acceso a true, lo que nos permitirá comprobar posteriormente si el usuario a pasado o no.
        if(aforoActual<aforoMax){
          entra = true;
          sale = true;
        }else{
          entra=false;
          sale=true;
          lcd.clear();
          lcd.setCursor(0,0);
          lcd.print(" AFORO COMPLETO ");
          lcd.setCursor(0,1);
          lcd.print("  SOLO  SALIDA  ");
        }

      }else if(compareArray(ActualUID,Usuario2)){
                                                                                                                              //Serial.println("Acceso concedido...");
        // Mostramos por pantalla el mensaje correspondiente
        lcd.setCursor(0,0);
        lcd.clear();
        lcd.print("Acceso concedido");
        lcd.setCursor(0,1);
        lcd.print("Adelante S.Stark");

        // Activamos la flecha para ser mostrada
        myDisplay.setSpriteData(arrow, W_ARROW, F_ARROW, arrow, W_ARROW, F_ARROW);
        myDisplay.displayText("", PA_CENTER, 50, 0, PA_SPRITE);

        // Mostramos por la matriz lo que esté activado en el momento (en este caso la flecha)
        if (myDisplay.displayAnimate()) {
          myDisplay.displayReset();
        }

        // Guarda el momento en el que se activa la flecha
        timingMATRIZ=millis();

        // Establecemos el acceso a true, lo que nos permitirá comprobar posteriormente si el usuario a pasado o no.
        if(aforoActual<aforoMax){
          entra = true;
          sale = true;
        }else{
          entra=false;
          sale=true;
          lcd.clear();
          lcd.setCursor(0,0);
          lcd.print(" AFORO COMPLETO ");
          lcd.setCursor(0,1);
          lcd.print("  SOLO  SALIDA  ");
        }
        

      }else{
        // El aceso ha sido denegado
        // Mostramos por pantalla el mensaje correspondiente
        lcd.clear();
        lcd.print("Acceso  denegado");

        // Guarda el momento en el que se ha denegado el acceso
        timingMATRIZ=millis();

        // Se reestablecen las variables entra y sale
        entra=false;
        sale=false;
      }

      // Terminamos la lectura de la tarjeta tarjeta
      mfrc522.PICC_HaltA();
      
    }
    
  }



  // El siguiente código comprueba, una vez se ha concedido el acceso, si el usuario ha pasado o no. Pueden ocurrir 3 casos:
  //
  //    - Se ha concedido el acceso y el usuario ha entrado: se sumará 1 al aforo actual.
  //
  //    - Se ha concedido el acceso y el usuario ha salido: se restará 1 al aforo actual.
  //
  //    - Se ha concedido el acceso pero el usuario no ha pasado (ni salido ni entrado): no se modificará el aforo actual.
  //      Este caso se puede dar por 2 razones: no se ha activado ningún ultrasonidos o solo se ha activado uno de los ultrasonidos.


  // Comprobamos que han pasado más de 2 segundos desde el último intento de acceso, para así evitar que detecte más accesos de los que debería
  if(millis()-pausaAcceso>2000){

    // Comprobamos si el sensor de entrada se ha activado y guardamos el momento en el que se activó
    entrar = ping(TriggerPinEntrar, EchoPinEntrar);
    if(entrar<20){
      tiempoEntrar=millis();
    }

    // Comprobamos si el sensor de salida se ha activado y guardamos el momento en el que se activó
    salir = ping(TriggerPinSalir, EchoPinSalir);
    if(salir<20){
      tiempoSalir=millis();
    }

    // Si el tiempo de salida es menor que el de entrada, el usuario entró
    if(tiempoSalir<tiempoEntrar && tiempoSalir != 0 && entra == true){
      lcd.clear();
      lcd.setCursor(0,0);
      lcd.print("Entra");

      // Reestablecemos los tiempos de entrada y salida a 0;
      tiempoEntrar=0;
      tiempoSalir=0;
      pausaAcceso=millis();

      // Sumamos 1 al aforo actual
      aforoActual=aforoActual+1;

      // Se reestablecen "entra" y "sale" a false
      entra = false;
      sale = false;
    }

    // Si el tiempo de entrada es menor que el de salida, el usuario salió
    if(tiempoEntrar<tiempoSalir && tiempoEntrar != 0 && sale == true){
      lcd.clear();
      lcd.setCursor(0,0);
      lcd.print("Sale");

      // Reestablecemos los tiempos de entrada y salida a 0;
      tiempoEntrar=0;
      tiempoSalir=0;
      //pausaAcceso=millis();

      // En función de lo que valga el aforo actual, se queda en 0 o se resta 1.
      // Aunque el primer caso no se debería dar (ya que suponemos que no debería haber nadie dentro cuando se inicialice el control de acceso) aún así se evalúa.
      if(aforoActual<1){
        aforoActual=0;
      }else{
        aforoActual=aforoActual-1;
      }

      // Si el usuario salió, se reestablece "acceso" a false
      sale = false;
      entra = false;
      
    }
  }
  


  // Por último, se implementa la comunicación entre el módulo bluetooth y la aplicación desarrollada
  // Al evaluar los datos que se reciben por bluetooth, se pueden dar 2 casos:
  //
  //    - Un usuario autorizado (con nombre y contraseña) cambia el aforo máximo: en este caso, se comprobará si el aforo actual es inferior al nuevo aforo máximo. Si es así, no se cambiará,
  //      si no que se ha de bajar el aforo actual saliendo suficiente gente o establecer un aforo máximo válido (superior al actual)
  //
  //    - Un usuario cliente (guest en la aplicación) solicita la información del aforo actual y máximo
  //
  if(BT.available()>0){
    // Se guarda un único carácter leído por el módulo bluetooth
    char c = BT.read();

    // Se concatena en un array cada carácter leído, en total 2, formando un string con el dato completo
    datoleido+=c;
    
    // Cambio de aforo máximo por usuario autorizado
    if(datoleido.length()==2 && datoleido != "CO"){

      // Se guarda en data el dato leido en tipo entero
      int data = datoleido.toInt();

      // Comprobamos de que el nuevo aforo máximo es mayor al aforo actual
      if(data > aforoActual){
        lcd.clear();
        lcd.setCursor(0,0);
        lcd.print(" AFORO CAMBIADO ");
        lcd.setCursor(0,1);
        lcd.print("      A " + String(data) + "      ");
        aforoMax=data;

      }else{
        lcd.clear();
        lcd.setCursor(0,0);
        lcd.print(" AFORO INFERIOR ");
        lcd.setCursor(0,1);
        lcd.print(" AL ACTUAL (" + String(aforoActual) + ") ");
      }

      // Se reestablecen las variables pausaBT y datoleido
      pausaBT=millis();
      datoleido="";
    }
    
    // Solicitud de aforos actual y máximo por usuario guest
    // El dato que se lee está especificado en la aplicación
    if(datoleido.length()==4 && datoleido == "CODE"){
      lcd.clear();
      lcd.setCursor(0,0);
      lcd.print("AFORO ACTUAL: " + String(aforoActual));
      lcd.setCursor(0,1);
      lcd.print("AFORO MAXIMO: " + String(aforoMax));
      
      // Se reestablecen las variables pausaBT y datoleido
      pausaBT=millis();
      datoleido="";
    }
    
  }

}

// A continuación se programan las funciones de apoyo

// Funcion ping de ultrasonidos
int ping(int TriggerPin, int EchoPin) {
   long duration, distanceCm;
   
   digitalWrite(TriggerPin, LOW);
   delayMicroseconds(4);
   digitalWrite(TriggerPin, HIGH);
   delayMicroseconds(10);
   digitalWrite(TriggerPin, LOW);
   
   duration = pulseIn(EchoPin, HIGH);
   
   distanceCm = duration * 10 / 292/ 2;
   return distanceCm;
}

// Funcion compara arrays
boolean compareArray(byte array1[],byte array2[]){
  if(array1[0] != array2[0])return(false);
  if(array1[1] != array2[1])return(false);
  if(array1[2] != array2[2])return(false);
  if(array1[3] != array2[3])return(false);
  return(true);
}

También te podría gustar...

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *