martes, 29 de septiembre de 2015

Login en Arduino + ENC28J60


Este tutorial te ayudara a agregar conectividad a tu Arduino, tanto para control como para monitoreo en Internet utilizando una interfaz Ethernet. Es decir se transformara el Arduino en un pequeño servidor Web y además se realizara un login para añadir algo de seguridad en el acceso a dicho servidor.
Para demostrar el control se encendera y apagara un led en una salida digital y se podrá variar la intensidad de otro led en una salida pwm, y para el monitoreo se mostrara la humedad y temperatura leídos desde el Arduino, junto al tiempo de ejecución y la memoria RAM disponible.

Material utilizado:

  • Arduino UNO
  • Modulo Ethernet ENC28J60
  • 2 Leds
  • 2 Resistencias de 220Ω
  • Lector de temperatura y humedad DHT11
  • Cables dupont para realizar las conexiones
  • Cable ethernet y un hub o modem para las pruebas.
Módulo ENC28J60
La principal ventaja de este modulo es su precio que ronda los 100 pesos mexicanos en Mercadolibre. Pero tiene la dificultad de que no hay mucha información al respecto. Se ha estado trabajando con este módulo desde hace algunos años, pero la libreria que se venia trabajando(Ethershield, https://github.com/jonoxer/etherShield) ha quedado descontinuada y no funciona en las ultimas versiones del editor de Arduino.
Funciona como un dispositivo esclavo y no cuenta con memoria propia para el buffer de escritura.

Conexión
La forma de conectar este módulo al Arduino se muestra en el siguiente esquema:
Fig 1: Conexión del módulo ENC28J60

Después de realizar la conexión, si todo se hizo correctamente, el led en el módulo ENC28J60 se debe encender al conectar el Arduino a la PC. Además al conectar el cable Ethernet, los leds en el conector Ethernet deberían encenderse, debido al tráfico en la red.

A continuación conectamos los leds y el lector DHT11 de la siguiente forma: 

Fig2: Conexión de los leds y el sensor DHT11



Programación
Para utilizar el módulo ethernet utilizaremos la libreria Ethercard que se puede descargar en este repositorio:
También se utilizo la librería DHT para el sensor de temperatura y humedad, esta librería se puede descargar de: 
Antes de explicar el código, debo indicar que la parte del login se realizo sin el uso de php, es decir se realizo el proceso de login utilizando solo código en Arduino, esto debido a que la librería utilizada es nueva y no tiene muchas funciones aún, por lo cual no es un método muy seguro, pero sirvió para nuestro propósito.

Incluimos las 2 librerías
#include <EtherCard.h>
#include "DHT.h"


A continuación seleccionamos el PIN del arduino donde se ha conectado el sensor DHT y el modelo de sensor.
#define DHTPIN 3
#define DHTTYPE DHT11


Declaramos el buffer ethernet, la mac e ip, estas dos últimas deben ser únicas en la red LAN.
static BufferFiller bfill;
DHT dht(DHTPIN, DHTTYPE);
byte Ethernet::buffer[1100];
// ethernet interface mac address
static byte mymac[] = { 0x74,0x69,0x69,0x2D,0x30,0x31 };
// ethernet interface ip address
static byte myip[] = { 192,168,3,200 };


Declaramos algunas variables que guardaran información, ledstatus tiene el estado del led que esta conectado a la salida digital, logged se utiliza para saber si hubo un login correcto, user, pass y pwm guardaran los valores introducidos por el usuario en la página web y _USER y _PASS son el usuario y contraseña para acceder al servidor.

byte ledstatus = LOW;

boolean logged = false;

char user[6];

char pass[6];
char pwm[4];
/* Usuario y contraseña para acceder a la página de control y monitoreo */
char _USER[6]="user";
char _PASS[6]="pass";


La siguiente función calcula el espacio libre en la memoria del Arduino:
static int freeRam () {
  extern int __heap_start, *__brkval;
  int v;
  return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}
En el setup de arduino declaramos las dos salidas donde se conectaran los leds, el 5 es pwm, se inicializa la comunicación serial utilizada para realizar debug y se inicializa la comunicación ethernet con las funciones ether.begin() y ether.staticSetup.
void setup(){
  pinMode(2,OUTPUT);
  pinMode(5,OUTPUT);
  Serial.begin(9600);
  ether.begin(sizeof Ethernet::buffer, mymac);
  ether.staticSetup(myip);
  dht.begin();
}
Las funciones homePage y login crean el código html que será mostrado en el servidor.
PSTR devuelve una cadena en el formato requerido para mostrarlo en el servidor, y emit_p lo guarda en el buffer. El tercer parámetro de PSTR es el valor la etiquita content, que en html5 indica cada cuanto tiempo se volverá a cargar la página, en este caso 5 segundos y led es el estado del led actual.
Para la lectura del valor del Led pwm, se crea un formulario(<form>) y al campo de texto en el que se guardara el valor se le debe poner el atributo name, que es la llave con la que se envía el Request al servidor, al enviar el formulario, esta llave se utiliza para obtener el valor ingresado en el Arduino, como se verá más adelante.
Por ultimo mostramos la temperatura, humedad, tiempo de ejecución y memoria disponible, utilizando de nuevo la función PSTR para dar el formato html e ingresar los valores leídos del arduino, y esto se guarda en el buffer.
static void homePage(BufferFiller& buf, byte led) {
    buf.emit_p(PSTR("$F\r\n"
        "<meta http-equiv='refresh' content='$D'/>"
        "<title>Arduino Server</title>"
        "<body><p>Estatus del led2: $D</br>"
        "<a href='?led=0'>Apagar</a> </br>"
        "<a href='?led=1'>Encender</a><br></p>"), okHeader, 5, led);
    /*LED PWM*/
    buf.emit_p(PSTR("<form>"
        "<input type=text name=pwm>"
        "<input type=submit value=Ok>"
        "</form>"
    ));
    int temp = dht.readTemperature();
    int hum = dht.readHumidity();
    buf.emit_p(PSTR("Temperatura: $D</br>Humedad: $D</br>"),temp,hum);
 
    // Cronometrar el tiempo de ejecucion del programa
    long t = millis() / 1000;
    word h = t / 3600;
    byte m = (t / 60) % 60;
    byte s = t % 60;
    buf.emit_p(PSTR("Tiempo corriendo $D$D:$D$D:$D$D"), h/10, h%10, m/10, m%10, s/10, s%10);
    buf.emit_p(PSTR(" ($D bytes free)</body>"), freeRam());
}

La función login(crea el html del login) esta basada en lo anteriormente explicado en la lectura del valor del led pwm(atributo name en el formulario), además incluye algunos css.
static void login(BufferFiller& buf){
  buf.emit_p(PSTR(
        "<html><head>"
        "<meta charset=utf-8/>"
        "<title>ArduStat</title>"
        "<style type=text/css>"
        "body {"
          "background: #DCDDDF;"
        "}"
        "form:after {"
          "content: \".\";"
          "display: block;"
          "clear: both;"
        "}"
        ".container { margin: 25px auto; width: 900px; }"
        "#content {"
          "background: #f9f9f9;"
          "border: 1px solid #c4c6ca;"
          "margin: 0 auto;"
          "padding: 25px 0 0;"
          "position: relative;"
          "text-align: center;"
          "text-shadow: 0 1px 0 #fff;"
          "width: 400px;"
        "}"
        "#content h1 {"
          "color: #7E7E7E;"
        "}"
        "#content form input[type=\"submit\"] {"
        "margin: 20px 0 35px 15px;"
        "}"
        "</style>"
        "</head>"
        "<body>"
        "<div class=container>"
        "<section id=content>"
            "<form>"
              "<h1>Login</h1>"
              "<div>"
                "<input type=text placeholder=Username id=username name=username maxlength=5 required>"
              "</div>"
              "<div>"
                "<input type=password placeholder=Password id=password name=password maxlength=5 required>"
              "</div>"
              "<div>"
                "<input type=submit value=Login>"
              "</div>"
            "</form>"
            "</section>"
           "</div>"
        "</body></html>"));

}


por ultimo en la función loop es donde el servidor(Arduino) decide que página mostrar.
Primero se revisa si se recibió alguna solicitud.
word len = ether.packetReceive();
word pos = ether.packetLoop(len);

En caso de que se halla recibido, este se guarda:
bfill = ether.tcpOffset();
char* data = (char *) Ethernet::buffer + pos;

La función ether.findKeyVal devuelve los valores ingresados por el usuario, estos se buscan a partir del atributo name como se había mencionado anteriormente. Para el caso de la contraseña y el usuario se verifica si son los correctos, en caso de que lo sean se dan los permisos para acceder al servidor. Para el pwm solo se guarda el valor para su futuro uso.
if(ether.findKeyVal(data + 6, user , sizeof user , "username")>0)
          if(ether.findKeyVal(data + 6, pass , sizeof pass , "password")>0)
            if(strncmp(user,_USER,5)==0 &&strncmp(pass,_PASS,5)==0){
              logged=true;
              this_time=true;
            }
ether.findKeyVal(data+6, pwm , sizeof pwm , "pwm");

Después se decide que página se mostrará según la solicitud recibida, en caso de que no se haya realizado el login, se mostrara la página de inicio de sesión , si ya se realizo el login hay 3 tipos de solicitudes, que se apague el led(led=0), que se encienda(led=1) o que se modifique el valor pwm(pwm=valorpwm), en caso de que se reciba otro tipo de solicitud se muestra una página de no Autorización.

if(strncmp("GET / ", data, 6)==0 or logged==false)
  login(bfill);
else if (this_time==true){
  homePage(bfill, ledstatus);
}
else if (strncmp("GET /?led=0", data, 11) == 0) {
  homePage(bfill, ledstatus);//configPage(data, bfill);
  if(ledstatus == HIGH) {
    digitalWrite(2, LOW);
    ledstatus = LOW;
  }
}
else if (strncmp("GET /?led=1", data, 11) == 0) {
    homePage(bfill, ledstatus);
    if(ledstatus == LOW) {
      digitalWrite(2, HIGH);
      ledstatus = HIGH;
    }
}
else if (strncmp("GET /?pwm", data, 9) == 0) {
    homePage(bfill, ledstatus);
    Serial.println(pwm);
    analogWrite(5, atoi(pwm));
}
else{
    bfill.emit_p(PSTR(
      "HTTP/1.0 401 Invalido\r\n"
      "Content-Type: text/html\r\n"
      "\r\n"
      "<h1>401 Unauthorized</h1>"));
}

Por último se envía la página:
ether.httpServerReply(bfill.position());
Resultados








3 comentarios: