Controle de acesso com NodeMCU + RFID

Este tutorial tem como objetivo mostrar o passo a passo da construção de um sistema de controle de acesso acionado por TAG’s RFID.

  • Funcionalidades:
  1. Liberar o acesso para TAG’s cadastradas;
  2. Mostrar o nome do usuário no visor LCD;
  3. Comunicar com o servidor para verificação da TAG;
  4. Manter o histórico de entrada e saída dos usuários;
  • Equipamentos necessários:
    • Hardware:
      • Placa Node MCU – Atuará como controlador do hardware e fará a comunicação com o servidor;
      • Visor LCD – Exibirá informações a respeito do acesso (liberado/negado/verificando);
      • Leitor RFID RC 522 – Atuará na leitura das TAG’s RFID;
    • Software:
      • Conta no CloudMQTT – A ferramenta atuará viabilizando a comunicação indireta entre o servidor e o equipamento de Hardware
      • Conta no Cloud9 – A ferramenta atuará como servidor da aplicação.

 

  • Infraestrutura:

arquitetura

 

Preparação:

  • Criando filas no MQTT:

Abra sua conta no CloudMQTT e crie uma nova instância. Essa instância será o host das suas filas MQTT. Com o host criado, abra os detalhes da instância, e crie os usuários que terão acesso a instância criada. Depois desça mais um pouco e crie as filas (Rules). Para este projeto, vamos precisar de fuas filas, uma para enviar informações no sentido NodeMCU -> Cloud9 e uma outra fila para fazer o sentido inverso.

As informações necessárias para a criação das filas são:

  • O usuário que terá acesso
  • O nome da fila (topic)
  • O usuário poderá Ler e/ou Escrever na fila?

Depois de ter criado as filas, é hora de inciarmos  a parte do servidor em si.

  • Desenvolvimento parte 1 (Servidor):

Após criar uma conta no Cloud9 (C9), vamos criar o banco de dados da aplicação.

Crie um arquivo .sql e nele insira o seguinte código:

-- MySQL Workbench Forward Engineering

SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL,ALLOW_INVALID_DATES';

-- -----------------------------------------------------
-- Schema ac
-- -----------------------------------------------------
DROP SCHEMA IF EXISTS `ac` ;

-- -----------------------------------------------------
-- Schema ac
-- -----------------------------------------------------
CREATE SCHEMA IF NOT EXISTS `ac` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci ;
USE `ac` ;

-- -----------------------------------------------------
-- Table `ac`.`User`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `ac`.`User` ;

CREATE TABLE IF NOT EXISTS `ac`.`User` (
`id` INT NOT NULL AUTO_INCREMENT COMMENT '',
`rfid` VARCHAR(45) NOT NULL COMMENT '',
`name` VARCHAR(45) NOT NULL COMMENT '',
PRIMARY KEY (`id`)  COMMENT '',
UNIQUE INDEX `rfid_UNIQUE` (`rfid` ASC)  COMMENT '')
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `ac`.`History`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `ac`.`History` ;

CREATE TABLE IF NOT EXISTS `ac`.`History` (
`id` INT NOT NULL AUTO_INCREMENT COMMENT '',
`idUser` INT NOT NULL COMMENT '',
`entry` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '',
`saida` VARCHAR(45) NULL DEFAULT NULL COMMENT '',
`status` CHAR NOT NULL DEFAULT 0 COMMENT '',
PRIMARY KEY (`id`)  COMMENT '',
INDEX `fk_History_User_idx` (`idUser` ASC)  COMMENT '',
CONSTRAINT `fk_History_User`
FOREIGN KEY (`idUser`)
REFERENCES `ac`.`User` (`id`)
ON DELETE NO ACTION
ON UPDATE NO ACTION)
ENGINE = InnoDB;


SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;

-- -----------------------------------------------------
-- Data for table `ac`.`User`
-- -----------------------------------------------------
START TRANSACTION;
USE `ac`;
INSERT INTO `ac`.`User` (`id`, `rfid`, `name`) VALUES (DEFAULT, 'ae43d3b5', 'Guilherme Araujo');
INSERT INTO `ac`.`User` (`id`, `rfid`, `name`) VALUES (DEFAULT, 'd798b4f5', 'Matheus Uehara');
INSERT INTO `ac`.`User` (`id`, `rfid`, `name`) VALUES (DEFAULT, 'b3f7729a', 'Francois Michell');

COMMIT;

Nesse código sql estamos criando duas tabelas, uma aonde ficará as informações do usuário (TAG e nome),

Captura de tela de 2016-07-08 15_46_28

e uma outra tabela onde ficarão as informações sobre o histórico de entrada e saida dos usuários.

Captura de tela de 2016-07-08 15_46_03

para executar o script, basta executar o seguinte comando no prompt do Cloud9 (bash):

mysql -uroot -proot < ac.sql

sintaxe do comando: mysql -uUSER -pPASSWD < NOMEARQUIVO.sql

Depois, abra o terminal do C9 e instale o paho-mqtt com o seguinte comando:

pip install paho-mqtt

Após instalar o paho-mqtt crie um arquivo .py e utilize o código:

# FAZENDO OS IMPORTS NECESSARIOS PARA A APLICACAO
import os, urlparse
import paho.mqtt.client as mqtt
import pymysql
import cgitb
from datetime import datetime


# CONEXÃO COM O BANCO - DATABASE, USUÁRIO, SENHA E HOST
conn = pymysql.connect(
    db='ac',
    user='root',
    passwd='root',
    host='localhost')
c = conn.cursor()

cgitb.enable()

# CODIGO DE CONSULTA AO BANCO
    
# VERIFICA SE O RFID PASSADO EXISTE NO BANCO
# SE SIM, RETORNA UMA LISTA CONTENDO NOME E ID DO USUARIO CADASTRADO 
# NAQUELE RFID
def consulta(num):
    retorno = {}
    retorno["userId"] = 0
    retorno["userName"] = ""
    sql = "SELECT id,name FROM User WHERE rfid = '%s'" % (num)
    
    c.execute(sql)
    
    r = c.fetchall()

    if len(r) > 0:
        retorno["userId"] = int(r[0][0])
        retorno["userName"] = r[0][1] + ""
        
    return retorno


# VERIFICA SE DADO USUÁRIO POSSUI REGISTRO ABERTO ASSOCIADO A SEU RFID
# CASO NÃO HAJA, O HORARIO E REGISTRADO E TEM SEU SATUS DEFINIDO COMO ABERTO (1).
# CASO HAJA, O HORARIO E REGISTRADO E O STATUS DEFINIDO COMO FECHADO (0)
def registro(userData):
    try:
        sql_consulta = "SELECT id FROM History WHERE idUser = %i AND status = 1;"/
        % (userData["userId"])
        c.execute(sql_consulta)
        r = c.fetchall()
        if len(r) > 0:
            timestamp = datetime.now()
            id_hist = r[0][0]
            sql_update = "UPDATE `History` SET `status` = 0, `saida` = '%s' WHERE /
            id = %i;" % (timestamp,id_hist)
            #print sql_update
            c.execute(sql_update)
            conn.commit()
            return "SAINDO/" + userData["userName"]
        else:
            sql_insert = "INSERT INTO History (idUser,status) VALUES (%i,1);"/
            % (userData["userId"])
            c.execute(sql_insert)
            conn.commit()
            return "ENTRANDO/" + userData["userName"]
            
    except:
        return "ERRO";
    
# SOBREESCREVEMOS O COMPORTAMENTO DE ALGUMAS
# FUNCOES PROPRIAS DO MQTT

# EXECUTADA QUANDO UMA NOVA CONEXAO E FEITA
def on_connect(mosq, obj, rc):
    print("rc: " + str(rc))

# EXECUTADA QUANDO UMA NOVA MENSAGEM E LIDA NA FILA
# PUBLICA NA FILA DE RESPOSTA SE O ACESSO FOI/NAO FOI LIBERADO
# + O NOME DO CADASTRADO PARA EXIBICAO NO LCD
def on_message(mosq, obj, msg):
    print(msg.topic + " " + str(msg.qos) + " " + str(msg.payload))

    cons = consulta(str(msg.payload))
    
    if(cons["userName"] != ""):
        retorno = registro(cons)
        print(retorno)
        mqttc.publish("retorno", retorno)
    else:
        mqttc.publish("retorno", "FALSE")
        
# EXECUTADO A CADA PUBLICACAO
def on_publish(mosq, obj, mid):
    print("Publish: " + str(mid))

# EXECUTADO A CADA FILA QUE UM SUBSCRIBE E DADO
def on_subscribe(mosq, obj, mid, granted_qos):
    print("Subscribed: " + str(mid) + " " + str(granted_qos))

# EXECUTADO EM CADA ESCRITA NO LOG
def on_log(mosq, obj, level, string):
    print(string)

# CRIACAO DO OBJETO DO TIPO mqtt.Client
mqttc = mqtt.Client()

# SOBRESCRITA DOS METODOS NATIVOS DO MQTT
mqttc.on_message = on_message
mqttc.on_connect = on_connect
mqttc.on_publish = on_publish
mqttc.on_subscribe = on_subscribe

# URL DO CLOUDMQTT E DA INSTANCIA AONDE AS FILAS ESTAO
# A URL DA INSTANCIA E COMPOSTA POR: mqtt://m12.cloudmqtt.com: + PORTA
# PORTA PODE SER ENCONTRADO NAS INFORMACOES DA INSTANCIA
url_str = os.environ.get('m12.cloudmqtt.com', 'mqtt://m12.cloudmqtt.com:PORTA')
url = urlparse.urlparse(url_str)

# ATRIBUICAO DO USUARIO COM ACESSO AS FILAS
#os parametros do username_pw_set são os dados usuário e senha do MQTT
mqttc.username_pw_set("USUARIO", "SENHA")
mqttc.connect(url.hostname, url.port)

# SUBSCRIBE NA FILA ACESSO
mqttc.subscribe("acesso", 0)

# LOOP ENQUANTO UM ERRO NAO FOR ENCONTRADO O NOSSO SERVIDOR ESTARÁ OUVINDO A FILA
# ACESSO E ESCREVENDO AS RESPOSTAS NA FILA RETORNO
rc = 0
while rc == 0:
    rc = mqttc.loop()
print("rc: " + str(rc))

Pronto, o servidor foi finalizado.

Com o arquivo criado temos nosso código do servidor pronto, antes de dar run no nosso arquivo lembre-se de iniciar o apache.

  • Desenvolvimento parte 2 (Hardware)

Baixe e instale a IDE do arduino em: https://www.arduino.cc/en/Main/Software

Com a IDE instalada siga os seguintes passos:
Ferramentas > Placa > Gerenciador de Placa > Pesquisar por ESP8266 e instalar

Ferramentas > Placa > selecionar ESP8266Modules

Arquivos > Preferencias > URLs adicionais e gerenciadores de placa : http://arduino.esp8266.com/package_esp8266com_index.json (Update 03/11/17: http://arduino.esp8266.com/stable/package_esp8266com_index.json)

Todos os links de bibliotecas do Github existentes no código devem ser baixados como .ZIP

Após baixar bibliotecas vá em Sketch > Incluir Biblioteca > Adicionar Biblioteca .ZIP *

*Realizar este procedimento para todas as bibliotecas.

Reiniciar o Arduino

Começando o código:

//Biblioteca wifi do nodeMCUESP8266
#include <ESP8266WiFi.h>

//https://www.arduino.cc/en/Reference/SPI
#include <SPI.h>

//Biblioteca do RFID 
//https://github.com/miguelbalboa/rfid
#include <MFRC522.h>

//https://www.arduino.cc/en/Reference/Wire
#include <Wire.h> 

//Biblioteca do display LCD
//https://github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library
#include <LiquidCrystal_I2C.h>

//Biblioteca do clientMQTT
//http://pubsubclient.knolleary.net/api.html
//https://github.com/knolleary/pubsubclient
#include <PubSubClient.h>
//Pinos do RFID
#define RST_PIN  15 // RST-PIN für RC522 - RFID - SPI - Modul GPIO15 
#define SS_PIN  2  // SDA-PIN für RC522 - RFID - SPI - Modul GPIO2 

//Criando WIFIClient
WiFiClient espClient;

//Criando o clientMQTT com o wificlient
PubSubClient client(espClient);

//Criando LCD com posições dos pinos,tamanho da linha, tamanho coluna.
LiquidCrystal_I2C lcd(0x27,16,2);

//Criando RFID nas posições dos pinos.
MFRC522 mfrc522(SS_PIN, RST_PIN);

//WIFI parametros.
const char* ssid = "NOME_DA_REDE_WIFI";
const char* password = "SENHA";

//MQTT parametros.
const char* mqtt_server = "m12.cloudmqtt.com";
const int mqtt_port = PORTA_MQTT;

void setup() {
  //Definindo porta de saida do Serial
  Serial.begin(9600);
  
  //Chamando método de conexão WIFI
  setup_wifi();
  
  //Definindo server mqtt do Client
  client.setServer(mqtt_server, mqtt_port);
  
  //Definindo método callback que irá receber os callbacks do client criado.
  client.setCallback(callback);
  
  // Init SPI bus
  SPI.begin();           
  
  //Iniciando PCD do RFID
  mfrc522.PCD_Init();
  
  // sda, scl With sda cable connected to D2 and scl cable connected to D1.
  Wire.begin(4, 5); 

  //Iniciando LCD
  lcd.begin();  

  printLCD("Passe o Cartao!");  
}

//Método de conexão com rede WIFI
void setup_wifi() {
  delay(10);
  Serial.println();
  Serial.print("Conectando com ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi conectado");
  Serial.println("Endereco IP : ");
  Serial.println(WiFi.localIP());
}

//Método de conexão com servidor MQTT
void conectMqtt() {
  while (!client.connected()) {    
    Serial.print("Conectando ao MQTT ...");    
    
    //Parametros são nodeMCUClient, usuárioMQTT, senhaMQTT
    if (client.connect("ESP8266Client","USUARIO_MQTT","SENHA_MQTT")) {
      Serial.println("Conectado");
      //Inscrevendo-se no tópico retorno.
      client.subscribe("retorno");
    } else {
      Serial.print("Falha, rc=");      
      Serial.print(client.state());      
      Serial.println(" Tentando novamente em 5 segundos");      
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

//Método que foi definido para receber os retornos dos tópicos que demos subscribe,
// neste caso apenas o tópico 'retorno'
//Parametros: NomedoTópico, mensagem , tamanho da mensagem
void callback(char* topic, byte* payload, unsigned int length) {
  Serial.println();
  Serial.print("Messagem recebida [");
  Serial.print(topic);
  Serial.print("]: ");
  String mensagem = "";
  //Conversão da mensagem recebidade de byte pra String
  for (int i = 0; i < length; i++) {
    mensagem += (char)payload[i];
  }
  Serial.println(mensagem);
  Serial.println();
  
  //Chamada ao método que controla o acesso
  verificaAcesso(mensagem);
}

//Método de controle de acesso.
void verificaAcesso(String mensagem){
  //Estrutura da mensagem recebida 
  // ENTRANDO/Nome_do_dono_do_cartao
  // SAINDO/Nome_do_dono_do_cartao

  // Retorno FALSE é dado sempre que o id do cartão não existe no servidor 
  //ou ocorreu alguma falha de validação
  // FALSE 

  if ( mensagem.substring(0,8) == "ENTRANDO" ){
    printLCD("BEM VINDO");
    delay(1000);
    printLCD(mensagem.substring(9));
    delay(1000);
  }else if (mensagem.substring(0,6) == "SAINDO" ){
    printLCD("ATE MAIS");
    delay(1000);
    printLCD(mensagem.substring(7));
    delay(1000);
  }else{
    printLCD("Acesso negado!");
    delay(1000);
  }  
  delay(1000);
  printLCD("Passe o Cartao!");
  return;
}

//Método utilitário para print no display LCD 
//Nele variamos apenas a mensagem da segunda linha
void printLCD(String mensagem){
    lcd.clear();
    lcd.setCursor(0,0);
    lcd.print("Infra-Hardware");
    lcd.setCursor(0,1);
    lcd.print(mensagem);
}

//Método de envio do id do cartão lido pra fila acesso
void sendMessage(MFRC522 mfrc522){
  printLCD("Lendo Cartao");
  char rfidstr[15];
  char s[100];
  for (int i = 0; i < mfrc522.uid.size; i++){
    
    //Conversão de byte pra Hexadecimal
    sprintf(s,"%x",mfrc522.uid.uidByte[i]);    
    
    //Concatenando para o array de char que será enviado
    strcat( &rfidstr[i] , s);
  }
  Serial.print("Card ID : ");
  Serial.print(rfidstr);

  //Publicando na fila acesso o id do cartão lido
  client.publish("acesso", rfidstr);  
  
  Serial.println();
  printLCD("Verificando...");

  return;
}

void loop() {  
  //Verificando Status do ClientMQTT
  if (!client.connected()) {
    conectMqtt();
  }
  client.loop();

  //Verificando existencia do card no leitor
  if ( ! mfrc522.PICC_IsNewCardPresent()) {
    delay(1000);
    return;
  }

  //Verificando Leitura do card
  if ( ! mfrc522.PICC_ReadCardSerial()) {
    delay(1000);
    return;
  }

  //Enviando mensagem
  sendMessage(mfrc522);
    
}

Após tudo concluido, inicie o código no Cloud9 dando RUN no arquivo .py criado para iniciar o servidor e depois execute o código no NodeMCU pra ver a magia acontecer!

15 comentários em “Controle de acesso com NodeMCU + RFID

  1. Bom dia! Glauco é possivel postar a ligação dos pinos do leito RFID, da forma que liguei não funcionou, apesar de ter visto o artigo citado acima, ainda to com duvidas na ligação.

    Curtir

  2. Boa noite Glauco, olha amigo, não consegui criar a conta no C9 porque ele pede para usar cartão de credito e n tenho, tem outra opção de criar conta la, ou fazer um turtorial criando o database + scrpyt e broker no windows?

    Curtir

    1. Olá Maurício. Realmente, desde que a Amazon comprou o C9 precisa do cartão. Se você deseja fazer um controle de acesso com todos os serviços locais, pode usar um broker mais simples como o mosquitto. O mysql você consegue instalar no windows sem maiores problemas. E o script, por ser python, você consegue rodar também. Então acho que dá pra fazer tranquilo.

      Curtir

  3. Olá, também estou com dúvida na criação do banco de dados no c9. Poderia dizer como fazer isso? Visto que eu já cadastrei meu cartão e nunca trabalhei com essa IDE.

    Curtir

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google

Você está comentando utilizando sua conta Google. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s