Go Down

Topic: Encendido de una máquina online (Sistema de riego) + NTP + DDNS (Read 884 times) previous topic - next topic

canary

Hola, me llamo J. González y soy ingeniero informático por la Universidad de Las Palmas de Gran Canaria.

Estoy realizando un Sistema de riego, aunque este código sirve para encender cualquier máquina mediante un relé por un tiempo determinado.

Las librerías utilizadas fueron:
https://github.com/arduino-libraries/NTPClient
https://github.com/sirleech/Webduino
https://www.arduino.cc/en/Reference/Ethernet
https://www.arduino.cc/en/Reference/SPI

Para hacer uso de este código de manera online sin preocuparme de saber de la dirección IP del arduino, es necesario un servicio DDNS.

Existen algunos como www.noip.com, pero, o bien son de pago, o son más costosos de configurar.

freedns.afraid.org es la solución perfecta, pues puede utilizarse desde el propio arduino para actualizarse. Así que si vas a utilizar este código, lo mejor es que te registres allí, crees un subdominio (mooo.com está bien y es fácil de recordar), vayas al apartado Dynamic DNS, y copies la URL de Direct URL.

El resultado es algo así:
http://freedns.afraid.org/dynamic/update.php?XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Ahora te recomiendo que vayas a la web www.base64encode.org y codifiques un usuario:contraseña a tu gusto. Recuerda poner los ':'

Este es el código:
Code: [Select]
#include "SPI.h"
#include "Ethernet.h"
#include <EthernetUdp.h>
#include <NTPClient.h>
#include "WebServer.h"

static uint8_t mac[] = { 0x56, 0xBC, 0xBE, 0xEF, 0x50, 0xF4 };
static uint8_t ip[] = { 192, 168, 1, 177 };

EthernetUDP Udp;
NTPClient timeClient(Udp, "es.pool.ntp.org", 3600, 60000); //<- Servidor español. Actualiza cada hora (3600). Además a la hora dada añade 1 hora más (60000).

#define PREFIX ""
WebServer webserver(PREFIX, 80);

#define Machine_PIN 4

char toggle = 0;

unsigned long tiempo = 0;
unsigned long ip_renew_time = 0;

IPAddress DDNS(50,23,197,94); // <- freedns.afraid.org

EthernetClient ddnsClient;

void defaultCmd(WebServer &server, WebServer::ConnectionType type, char *, bool) {
  server.httpSuccess();
  if (type != WebServer::HEAD)
  {
    P(helloEsp) = "<h1>Servicio de regad&iacuteo privado</h1><h2>Si ha llegado aqu&iacute por favor abandone la p&aacutegina.</h2>";
    server.printP(helloEsp);
    P(helloEng) = "<h1>Private irrigation service</h1><h2>Please leave the page.</h2><a href=\"private.html\">Continuar al servicio</a>";
    server.printP(helloEng);
  }
}

void machineCmd(WebServer &server, WebServer::ConnectionType type, char *url_tail, bool tail_complete) {
  if (server.checkCredentials("dXNlcjp1c2Vy")) { //<- IMPORTANTE CAMBIAR ESTO; "dXNlcjp1c2Vy" significa "user:user" en base 64. En esta web https://www.base64encode.org/ puedes crear un "user:password" diferente.
    if (type == WebServer::POST) {
      bool repeat;
      char name[16], value[16];
      do {
        repeat = server.readPOSTparam(name, 16, value, 16);

        if (strcmp(name, "machine") == 0) {
          tiempo = timeClient.getEpochTime() + strtoul(value, NULL, 10);
        }
      } while (repeat);

      server.httpSeeOther("private.html");
      return;
    }
    
    server.httpSuccess();

    if (type == WebServer::GET) {
      printEstado(server, type);
      P(form) =
        "<html><title>Sistema de Regad&iacuteo</title>"
        "<body><center>"
        "<h2>Introduzca el tiempo que desea regar:</h2>"
        "<form action='private.html' method='POST'>"
        "<p><button name='machine' value='0' style='margin:auto;background-color: #FA5858;color: snow;padding: 10px;border: 1px solid #FF0000;width:250px;'> APAGAR </button></p>"
        "<p><button name='machine' value='60' style='margin:auto;background-color: #84B1FF;color: snow;padding: 10px;border: 1px solid #3F7CFF;width:250px;'> 1 minuto </button></p>"
        "<p><button name='machine' value='600' style='margin:auto;background-color: #84B1FF;color: snow;padding: 10px;border: 1px solid #3F7CFF;width:250px;'> 10 minutos</button></p>"
        "<p><button name='machine' value='1800' style='margin:auto;background-color: #84B1FF;color: snow;padding: 10px;border: 1px solid #3F7CFF;width:250px;'> 30 minutos </button></p>"
        "<p><button name='machine' value='2700' style='margin:auto;background-color: #84B1FF;color: snow;padding: 10px;border: 1px solid #3F7CFF;width:250px;'> 45 minutos </button></p>"
        "<p><button name='machine' value='3600' style='margin:auto;background-color: #84B1FF;color: snow;padding: 10px;border: 1px solid #3F7CFF;width:250px;'> 1 hora </button></p>"
        "<p><button name='machine' value='5400' style='margin:auto;background-color: #84B1FF;color: snow;padding: 10px;border: 1px solid #3F7CFF;width:250px;'> 1 hora y media </button></p>"
        "<p><button name='machine' value='7200' style='margin:auto;background-color: #84B1FF;color: snow;padding: 10px;border: 1px solid #3F7CFF;width:250px;'> 2 horas </button></p>"
        "</form></body></center></html>";
      server.printP(form);
    }
  }
  else {
    server.httpUnauthorized();
  }
}

void printEstado(WebServer &server, WebServer::ConnectionType type) {
  P(estado) = "<html><meta http-equiv=refresh content=30><center><h1>";
  server.printP(estado);
  server.print(timeClient.getFormattedTime());
  P(estado2) ="<br>EN ESTE MOMENTO LA BOMBA DEL AGUA EST&Aacute ";
  server.printP(estado2);
  if (digitalRead(Machine_PIN)) {
    P(estado) = "<font color=#2EFE2E>ENCENDIDA</font> durante ";
    server.printP(estado);
    server.print((tiempo-timeClient.getEpochTime())/60);
    P(estado3) = " minutos</h1></center></html>";
    server.printP(estado3);
  }
  else {
    P(estado) = "<font color=#FF0000>APAGADA</font></h1></center></html>";
    server.printP(estado);
  }
}

void ddnsRenew(){
  //http://nubedideas.blogspot.com.es/2015/02/termostato-calefaccion-con-arduino_13.html
  String respuesta="";
  timeClient.update();
  
  if (ddnsClient.connect(DDNS, 80)) {
    ddnsClient.println("GET /dynamic/update.php?XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX HTTP/1.1");
    ddnsClient.println("Host: freedns.afraid.org");
    ddnsClient.println("Accept: text/html");
    ddnsClient.println("User-Agent: Mozilla/5.0 (X11; Linux x86_64)");
    ddnsClient.println("");
    ip_renew_time = timeClient.getEpochTime() + 3600;
  }
  
else{
    ip_renew_time = timeClient.getEpochTime() + 300;
  }
  
  delay(2000);
  ddnsClient.stop();
}


void setup() {
  pinMode(Machine_PIN, OUTPUT);
  digitalWrite(Machine_PIN, LOW);
  
  Ethernet.begin(mac, ip);
  
  timeClient.begin();
  
  ddnsRenew();
  
  webserver.setDefaultCommand(&defaultCmd);
  webserver.addCommand("index.html", &defaultCmd);
  webserver.addCommand("private.html", &machineCmd);
  
  webserver.begin();
}

void loop() {
  webserver.processConnection();
  if ((++toggle & 1) && (timeClient.getEpochTime() < tiempo)){
    digitalWrite(Machine_PIN, HIGH);
  }
  else{
    digitalWrite(Machine_PIN, LOW);
  }
  if (timeClient.getEpochTime() > ip_renew_time){
      ddnsRenew();
  }
}


Bueno, lo último que me falta es el código AJAX para actualizar en modo pr0 y poner el circuito una vez lo haga.

noter

Hola.
Respecto de tu primera pregunta, puedes agregar una etiqueta META tal y como se describe aquí, o si quieres algo más dinámico podrías recurrir a AJAX o alguna de las librerías que lo implementan (por ejemplo JQUERY). De esa forma sólo cargarías la página una vez, y luego se actualizarían sólo los campos necesarios, disminuyendo también el trasiego de datos.
Respecto de la segunda, deberías estudiarte el ejemplo blinkWhitoutDelay del propio IDE y aprender a trabajar con millis (si me dieran un euro cada vez que se ha puesto esto mismo en este foro, ya sería millonario :)). Básicamente millis es un contador de milisegundos que se inicia al arrancar el arduino. Si tomamos ese contador en los momentos A y B, y luego restamos B-A, obtenemos el número de milisegundos transcurridos entre ambos momentos. Basta con mirar regularmente en el loop si ha transcurrido el tiempo establecido desde que se tomó la primera marca.

canary

¡Gracias compañero!

La verdad es que no he andado quieto, y descubrí las 2 soluciones que me dijiste echando horas a buscar (este foro y stackoverflow son impresionantes  :D). Al principio no entendí si me iban a funcionar o no, pero a base de mirar lo pude adaptar.

Aunque por ahora no me entero mucho del AJAX o JQUERY, y prefiero ir paso a paso, pensaré en cómo poder usarlo :3

Con respecto a lo del tiempo, encontré una librería que de manera simple llama a un servidor NPT (en mi caso usé uno español) y saco de ahí la hora.

Cuando vaya logrando algunos hitos más, iré posteando el código :3

De nuevo, gracias :D

canary

He cambiado el título del post y el código.

Ahora hace NTP y DDNS.

Me falta saber cómo hacer lo del AJAX (porque sinceramente de eso ni idea).

surbyte

#4
Jul 06, 2016, 04:46 pm Last Edit: Jul 06, 2016, 04:47 pm by surbyte
Lo ideal sería ya que estas en un foro proyectos que delimites el tuyo, indicando
que vas a controlar : generalmente son válvulas 24VAC de modo que eso implica usar RELES y por ende considerar un módulo de reles de 2/4/8.
que vas medir, humedad de la tierra (todo un tema de debate, porque el sensor que suelen vender se degrada a los 3 meses asi que cuidado con este tema y a considerar sensores mas caros pero que aseguran durabilidad y al final no son caros, si comparas que necesitas 4 de los comunes en 1 año.
etc, etc etc.

Asi podemos hablar y/o aconsejarte de hardware y de software.
Pensar en un sistema de riego en NTP antes de comenzar es como mucho.. yo diría que vayas resolviendo EL SISTEMA DE RIEGO y usa un RTC DS3231 y luego lo ajustas por NTP cada 12/24hs como hago yo con algo parecido.

Igualmente entiendo tu preocupación por la interfaz Web pero hay muchas opciones resueltas hoy. Luego te paso algunas.

BUeno con tu cambio de título mi comentario quedó fuera de lugar, pero sigue siendo un Proyecto con el objetivo de Riego.

canary

Pensé en lo de los medidores de humedad, pero no me convencían por lo que dijiste.

Tampoco quería regar todos los días porque ahora mismo no tengo nada plantado y porque aquí el clima es de lo más curioso jejeje.

Simplemente se me ocurrió que en lugar de acercarme hasta el lugar (con lo cual tardo 40 minutos) lo activo el tiempo que desee según a cuántas cosas les tengo que dar agua.

Como suelo ir con normalidad, si planto algo en un terreno le pongo las mangueras necesarias para que le llegue agua, y si no nada. La opción del reloj fue algo que me planteé, pero no me convencía por lo que puse antes.

Un saludo y gracias :3

surbyte

Si no tienes internet que pasa? No hay NTP. 
Igualmente sigue funcionando pero si se corta la luz y luego vuelve no tendrás hora pq no puede sincronizarla con el NTP.
Entonces requieres un RTC como el que te dije. Un DS3231 que seguirá funcionando si se corta internet y seguirá actuando y controlando todo cuando regrese la energía.
Y cuando se reestablezca Internet podrá sincronizar la hora pero ni siquiera hace falta con un DS3231 en pocos dias.


canary

He pensado en lo que has dicho y he hecho algunas pruebas.

Quité primero el timeClient.update() y así comprobé qué ocurre si no está el servidor disponible (porque no haya internet pero sí intranet).

La hora que pone por defecto es 01:00:00, pero sigue aumentando cada segundo, por lo que puede seguir siendo usado como reloj (para la bomba de riego) como mismo sirve la función milis().

Pero me di cuenta de algo, y es que intento renovar la IP en el servicio DDNS y si no hubiera internet realmente tendría que esperar una hora para ponerlo operativo (en el peor de los casos, que acabara de volver internet justo después de hacer la comprobación).

Para no comprometer tanto el servicio, lo hago cada 5 minutos hasta que el resultado sea satisfactorio, y además el NTP se actualiza cada vez que intente actualizar el ddns, porque únicamente lo actualizaba en el setup() y es mejor actualizarlo periódicamente.

Code: [Select]
void ddnsRenew(){
  //http://nubedideas.blogspot.com.es/2015/02/termostato-calefaccion-con-arduino_13.html
  String respuesta="";
  timeClient.update();
 
  if (ddnsClient.connect(DDNS, 80)) {
    ddnsClient.println("GET /dynamic/update.php?M1dYaDlKSFlnSWt4blZ1c0I2S2NYcEVTOjE2MDY4MzE1 HTTP/1.1");
    ddnsClient.println("Host: freedns.afraid.org");
    ddnsClient.println("Accept: text/html");
    ddnsClient.println("User-Agent: Mozilla/5.0 (X11; Linux x86_64)");
    ddnsClient.println("");
    ip_renew_time = timeClient.getEpochTime() + 3600;
  }
 
else{
    ip_renew_time = timeClient.getEpochTime() + 300;
  }
 
  delay(2000);
  ddnsClient.stop();
}


void setup() {
  pinMode(Machine_PIN, OUTPUT);
  digitalWrite(Machine_PIN, LOW);
 
  Ethernet.begin(mac, ip);
 
  timeClient.begin();
 
  ddnsRenew();
 
  webserver.setDefaultCommand(&defaultCmd);
  webserver.addCommand("index.html", &defaultCmd);
  webserver.addCommand("private.html", &machineCmd);
 
  webserver.begin();
}

surbyte

NO estoy de acuerdo con una sincronización tan repetida aunque no afecta en nada.
Yo haría otra cosa.
Si intento sincronizar y no puedo, levanto un flag que diga SincronizacionPendiente.
Cuando se reestablezca Internet entonces simplemente como el flag esta levantado atiendo la petición, sincronizo y lo bajo.
Con cada loop lo estoy comprobando y siempre será mas rápido que tus 5min pero solo es una idea y ambas funcionan.

canary

¿Y cómo harías para mostrar una variable (por ejemplo la hora actual) con AJAX?

Llevo 2 días mirando código de mil personas diferentes (de arduino.cc y externo), haciendo pruebas a ver si me funciona, y nada.

No sé cómo coger una variable o función y mostrar su resultado con AJAX.

surbyte


canary

surbyte lo vi, pero no entiendo bien los lenguajes web.

Gracias, al menos así sé que ahí está mi solución  :)

surbyte


canary

Bien, tras un par de semanas de vacaciones me puse de nuevo con el proyecto.

Por ahora he logrado hacer lo que quería con la web (bootstrap, javascript, y ahora sirve para varias máquinas a la vez, y muestra cuánto tiempo le queda a cada una encendida).

Cambié muchas cosas del proyecto como por ejemplo las librerías utilizadas (ya no utilizo webduino), para poder hacer cosas más interesantes.

El único problema es que ahora no consigo que me funcione el código de autentificación que estaba utilizando:
Código de autentificación.

Al añadir las líneas de la autentificación la página web se queda congelada y salta un mensaje del navegador diciendo que no se ha podido encontrar la página solicitada.

¿A qué se puede deber? ¿Hay alguna solución alternativa?

El código lo he dejado en un txt y también la web porque pasan de los 9000 caracteres ;)

Otra pregunta, cómo puedo hacer algo así:
Code: [Select]
for (int i = 1; i < 6; i++) {
    String aux = "&t" + i;
    char auxBUF[3];
    aux.toCharArray(auxBUF, 3);
    if (StrContains(HTTP_req, auxBUF)) {
      if (StrContains(HTTP_req, "=X")) {
        tiempo[i - 1] = 0;
      }
      if (StrContains(HTTP_req, "=900")) {
        tiempo[i - 1] = timeClient.getEpochTime() + 900;
      }
      if (StrContains(HTTP_req, "=1800")) {
        tiempo[i - 1] = timeClient.getEpochTime() + 1800;
      }
      if (StrContains(HTTP_req, "=2700")) {
        tiempo[i - 1] = timeClient.getEpochTime() + 2700;
      }
      if (StrContains(HTTP_req, "=3600")) {
        tiempo[i - 1] = timeClient.getEpochTime() + 3600;
      }
    }
  }


Quiero eliminar las líneas de código que son repetitivas, pero no sé cómo hacerlo.

Un saludo :3

jolguin

Hola Canary: disculpa que haya pasado tiempo pero me interesa tu proyecto. Ya lo tienes operativo? Pudiste avanzar y solucionar tus inconvenientes? me gustaria conocer como los resolviste.

Go Up