Hola!
Decidí crear un nuevo tema pues hay mucha información y material nuevo. Primero un breve resumen: Este proyecto comenzó en el 2015, cuando @Zion Lion presentó a la comunidad un proyecto llamado EasyGrowWeed. @Zion Lion me envió desde España (hasta Colombia) todas las partes para que yo le ayudara en su desarrollo, desafortunadamente caí en un bloqueo creativo (perdón Zion!!) y pasaron unos años hasta que en el 2019 presenté un proyecto llamado sweet_love v0.11, el cual estaba inspirado en el trabajo de @Zion Lion pero estaba escrito desde cero, usando librerías más actualizadas. Se trata de un sistema embebido de control para el cuidado de cultivos diseñado para ser implementado en el Arduino Mega 2560. Luego, cuando estaba incluyendo el feature de acceso al sistema a través de una red, me di cuenta que dicha versión tenía muchas limitaciones, especialmente porque el flujo del programa estaba basado en una librería llamada SoftTimer.h. Entonces tomé la decisión de escribir nuevamente el programa desde cero y utilizar una técnica llamada "máquinas de estados", la cual permite realizar diferentes tareas de manera "simultanea".
En el camino y debido a fallas en el sistema tuve que reemplazar el Arduino Mega chino por un Arduino italiano original, el cual recomiendo 100%. También reemplacé el módulo LCD por uno I2C y uno de los relés mecánicos (el que controla la lámpara) por un módulo relé de estado sólido SSR (este cambio también es MUY importante). Omití la fuente switcheada + regulador de protoboard que alimentaban todo el sistema y los reemplacé por un adaptador de voltaje de 9v + un regulador LM7805 (con disipador de calor!). Otro cambio es que el módulo de relés mecánicos ahora lleva su propio adaptador de voltaje de 5v pues si se conecta a los 5v del LM7805 "jala" mucha corriente y se nota en el contraste del LCD. El Arduino lo alimento por el conector USB.
También tuve que reemplazar el módulo de relés y el sensor de humedad por partes nuevas pues las viejas se dañaron.
Entonces la lista de partes es la siguiente:
Arduino MEGA 2560 Rev3 Original de Italia
Regulador de voltaje LM7805 (no encontré imagen con disipador!!)
Módulo LCD 4x20 I2C
Reloj de tiempo real RTC DS3232
Sensor de humedad en suelo FC-28
Sensor de temperatura y humedad en aire DHT22
(Omití la imagen porque sólo puedo publicar 10 imágenes!!)
Módulo de relés mecánicos opto-aislado 5v 10A
Módulo de relé de estado solido SSR
Mini bomba de agua 12v 3.6W
(Omití la imagen porque sólo puedo publicar 10 imágenes!!)
Este es el diagrama eléctrico actualizado realizado en Fritzing. Ya incluye el LCD I2C, el relé de estado sólido, la alimentación independiente del relé mecánico, el bus de I2C con resistencias de 10k a +V y todas las conexiones al Arduino Mega para que funcione correctamente con el programa.
Aquí dejo un link con una imagen de mejor calidad.
Así luce el montaje de sweet_love v0.29. Foto tomada el mismo día de la publicación del tema:
Recomendación:
Si piensan hacer el montaje de este proyecto usando cables viejos, hay que revisar que las puntas estén 100% limpias. En mi caso, el montaje presentaba intermitencia y fallas, así que opté por desarmar todo, armarlo de nuevo y limpiar las puntas de los cables con un bisturí pues tenían oxido o simplemente estaban opacas. La diferencia en el funcionamiento se nota MUCHO, por ejm en el contraste del LCD.
Este es el código:
NOTA: Para cargar la fecha y hora en el RTC por primera vez, se debe programar el Arduino quitando el comando de comentario "//"de la línea que dice: //RTC.set(compileTime()); y luego se debe programar de nuevo el Arduino incluyendo el comando "//".
Resumen de funcionamiento (copy/paste del tema anterior!):
El sistema controla el riego y la iluminación del cultivo. Muestra en la pantalla del LCD la siguiente información: tiempo, fecha, temperatura, humedad en aire y en sustrato, estado de los controles manuales y automáticos, así como el fotoperiodo. Puede ser configurado para realizar control manual y automático. Para el control automático del riego, utiliza la medida del sensor de humedad en el sustrato y activa el riego mediante una bomba de agua. Para el control automático de la luz, configura las alarmas del reloj de tiempo real con el foto-periodo especificado sea de crecimiento 18/6 (por defecto) o 12/12 de maduración. Los controles automáticos vienen desactivados por defecto.
Reemplacé la sonda del sensor de humedad que viene de fabrica por una artesanal hecha con tornillos gruesos y piezas de ferretearía, de modo que si se daña por corrosión podría ser reemplazada fácilmente. De todas maneras en esta parte tuve en cuenta las dificultades presentadas anteriormente (a Zion y otros usuarios) debido a la corrosión en la sonda, entonces para este código implementé un algoritmo que usa dicha sonda y realiza una lectura de humedad sólo una vez cada minuto pero con una mayor frecuencia al momento de usar la bomba de agua para lograr detener su uso a tiempo. La corrosión la causa la lectura como tal así que reducir la frecuencia con que se hace debería extender la vida de la sonda..
Riego y lámpara
El riego lo implementé usando una sonda plástica de las que venden en ferretería. Ubicándola en forma de círculo alrededor del tallo de la planta, perforando pequeños agujeros hacia abajo y poniendo un tapón al final de la sonda. La lámpara que usé para las pruebas fue una lámpara fluorescente de 110v. No sirve mucho para cultivos de cannabis pero creo que sirve para probar el sistema. El relé de estado solido soportaría hasta 240VAC @2A.
¡ACTUALIZACIÓN!
Como lo prometí, en esta nueva versión es posible acceder al sistema utilizando un Ethernet Shield (donación de @Zion_Lion, gracias!!).
Así luce el servidor web accediendo desde un celular:
La dirección IP por defecto del servidor web es 192.168.1.115. Esta dirección debe ser acorde a la subred que haya en el lugar, si hay una subred distinta entonces hay que cambiar la dirección IP desde el código (en la línea: IPAddress ip(192, 168, 1, 115);). Para acceder directamente el Ethernet Shield simplemente se conecta al router con un cable UTP, para acceder a través de Internet se tendría que abrir el puerto 80 del router!! (esto aún no lo he probado).
Vídeos en Youtube!
El año pasado grabé material para una futura publicación. Aquí dejo los links!
Saludos!!
Decidí crear un nuevo tema pues hay mucha información y material nuevo. Primero un breve resumen: Este proyecto comenzó en el 2015, cuando @Zion Lion presentó a la comunidad un proyecto llamado EasyGrowWeed. @Zion Lion me envió desde España (hasta Colombia) todas las partes para que yo le ayudara en su desarrollo, desafortunadamente caí en un bloqueo creativo (perdón Zion!!) y pasaron unos años hasta que en el 2019 presenté un proyecto llamado sweet_love v0.11, el cual estaba inspirado en el trabajo de @Zion Lion pero estaba escrito desde cero, usando librerías más actualizadas. Se trata de un sistema embebido de control para el cuidado de cultivos diseñado para ser implementado en el Arduino Mega 2560. Luego, cuando estaba incluyendo el feature de acceso al sistema a través de una red, me di cuenta que dicha versión tenía muchas limitaciones, especialmente porque el flujo del programa estaba basado en una librería llamada SoftTimer.h. Entonces tomé la decisión de escribir nuevamente el programa desde cero y utilizar una técnica llamada "máquinas de estados", la cual permite realizar diferentes tareas de manera "simultanea".
En el camino y debido a fallas en el sistema tuve que reemplazar el Arduino Mega chino por un Arduino italiano original, el cual recomiendo 100%. También reemplacé el módulo LCD por uno I2C y uno de los relés mecánicos (el que controla la lámpara) por un módulo relé de estado sólido SSR (este cambio también es MUY importante). Omití la fuente switcheada + regulador de protoboard que alimentaban todo el sistema y los reemplacé por un adaptador de voltaje de 9v + un regulador LM7805 (con disipador de calor!). Otro cambio es que el módulo de relés mecánicos ahora lleva su propio adaptador de voltaje de 5v pues si se conecta a los 5v del LM7805 "jala" mucha corriente y se nota en el contraste del LCD. El Arduino lo alimento por el conector USB.
También tuve que reemplazar el módulo de relés y el sensor de humedad por partes nuevas pues las viejas se dañaron.
Entonces la lista de partes es la siguiente:
Arduino MEGA 2560 Rev3 Original de Italia
Regulador de voltaje LM7805 (no encontré imagen con disipador!!)
Módulo LCD 4x20 I2C
Reloj de tiempo real RTC DS3232
Sensor de humedad en suelo FC-28
Sensor de temperatura y humedad en aire DHT22
(Omití la imagen porque sólo puedo publicar 10 imágenes!!)
Módulo de relés mecánicos opto-aislado 5v 10A
Módulo de relé de estado solido SSR
Mini bomba de agua 12v 3.6W
(Omití la imagen porque sólo puedo publicar 10 imágenes!!)
Este es el diagrama eléctrico actualizado realizado en Fritzing. Ya incluye el LCD I2C, el relé de estado sólido, la alimentación independiente del relé mecánico, el bus de I2C con resistencias de 10k a +V y todas las conexiones al Arduino Mega para que funcione correctamente con el programa.
Aquí dejo un link con una imagen de mejor calidad.
Así luce el montaje de sweet_love v0.29. Foto tomada el mismo día de la publicación del tema:
Recomendación:
Si piensan hacer el montaje de este proyecto usando cables viejos, hay que revisar que las puntas estén 100% limpias. En mi caso, el montaje presentaba intermitencia y fallas, así que opté por desarmar todo, armarlo de nuevo y limpiar las puntas de los cables con un bisturí pues tenían oxido o simplemente estaban opacas. La diferencia en el funcionamiento se nota MUCHO, por ejm en el contraste del LCD.
Este es el código:
C++:
/* PROYECTO: Sistema de control para el cuidado de cultivos
NOMBRE: sweet_love v0.2935
CARACTERISTICAS:
- Diseñado para el Board Arduino Mega 2560
- Push Buttons en el Loop
- LCD por I2C en state machine
- Reloj RTC DS3232 en LCD
- Sensores DHT22 y HL28 en ctrl state machine
- Control manual de relays para lámpara y bomba
- Control automático de bomba y lámpara (foto-periodo)
- Configuración del foto-periodo: vegetativo o floración
- Servidor web en Loop:
- Lectura de sensores (boton para actualizar)
- Acceso al control manual y automático
- Configuración del foto-periodo
Escrito hasta Diciembre de 2023
Por Alejandro Bermúdez Ospina
(alejandro.bermudez.ospina@gmail.com)*/
//========================================
// ----------LIBRARIES--------------
#include <ezButton.h> // https://github.com/alexokaban/button
#include <Wire.h>
#include <hd44780.h> // https://github.com/alexokaban/hd44780
#include <hd44780ioClass/hd44780_pinIO.h> // Arduino pin i/o class header
#include <hd44780ioClass/hd44780_I2Cexp.h> // i2c expander i/o class header
#include <DS3232RTC.h> // https://github.com/alexokaban/DS3232RTC
#include <DHT.h> // https://github.com/alexokaban/DHT-sensor-library
#include <DHT_U.h>
#include <Streaming.h>
#include <Ethernet.h>
#include <SPI.h>
// --------CONSTANTS (won't change)---------------
ezButton button1(2); // create ezButton object that attach to pin 6;
ezButton button2(3); // create ezButton object that attach to pin 7;
ezButton button3(5); // create ezButton object that attach to pin 8;
#define HL28_pin A0 // analog pin to connect the soil moisture sensor
#define DHT_pin A1 // entrada pin análogo al sensor DHT22
#define SQW_pin 19 // señal de interrupción para las alarmas del RTC - // connect this pin to DS3231 INT/SQW pin.
const int lamp = 8; // pin de salida digital al relé que controla la lampára
const int plump = 9; // pin de salida al relé que controla la bomba de agua
#define DHT_type DHT22
DHT dht(DHT_pin, DHT_type);
hd44780_I2Cexp lcd;
// LCD geometry
const int LCD_COLS = 20; // columnas
const int LCD_ROWS = 4; // filas
uint8_t degree_char[8] = {0x18, 0x18, 0x00, 0x07, 0x08, 0x08, 0x08, 0x07}; // caracter de grado
uint8_t arrow_char[8] = {0x00, 0x00, 0x04, 0x06, 0x1F, 0x06, 0x04, 0x00}; // flecha
uint8_t mark_char[8] = {0x00, 0x00, 0x01, 0x02, 0x14, 0x08, 0x00, 0x00}; // marca
const int lcd_interval = 1000; // tiempo de actualización
const int DHT_interval = 10000; // tiempo entre lecturas
const int HL28_dry_soil = 950; // constante de sustrato seco para activar la bomba de agua
const int HL28_wet_soil = 430; // constante de sustrato humedo para detener la bomba de agua
const byte alarm1_hour = 6; // hora de inicio del fotoperiodo (fija)
const byte alarm1_minute = 0;
const byte alarm1_second = 0;
const byte alarm1_day = 0; //
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0x03 };//dirección MAC de la Ethernet Shield que está con una etiqueta debajo la placa
IPAddress ip(192, 168, 1, 115); //Asignamos la IP privada al ethernet shield
EthernetServer server(80); //Creamos un servidor Web con el puerto 80 que es el puerto HTTP por defecto
//------------ VARIABLES (will change)---------------------
// máquina de estados para el LCD
typedef enum { STATE_NONE,
STATE_LCD_START,
STATE_LCD_SENSORS,
STATE_LCD_C_MANUAL_LAMP,
STATE_LCD_C_MANUAL_PLUMP,
STATE_LCD_C_AUTO_LAMP,
STATE_LCD_C_AUTO_PLUMP,
STATE_LCD_FOTO_P_VEG,
STATE_LCD_FOTO_P_FLO,
STATE_LCD_TELE_CONNECT,
STATE_LCD_AUTO_TELE,
} states_lcd;
states_lcd state_lcd = STATE_LCD_START; // current state-machine state
// máquina de estados para los controles y sensores
typedef enum { STATE_CTRL_NONE,
STATE_CTRL_DHT_READ,
STATE_CTRL_HL28_SREAD,
STATE_CTRL_HL28_LREAD,
STATE_CTRL_LAMP_OFF,
STATE_CTRL_LAMP_ON,
STATE_CTRL_PLUMP_OFF,
STATE_CTRL_PLUMP_ON
} states_ctrl;
// current state-machine state
states_ctrl state_ctrl = STATE_CTRL_NONE;
//máquina de estados para el último comando ejecutado
typedef enum { LAST_CMD_NONE,
LAST_CMD_PHOTO_STARTS,
LAST_CMD_PHOTO_ENDS,
LAST_CMD_HUM_LOW_PLUMP_ON,
LAST_CMD_HUM_HIGH_PLUMP_OFF,
} last_cmds;
volatile last_cmds last_cmd = LAST_CMD_NONE; // current state-machine state
bool tele_lastcall_f = false;
// for temperature & humidity sensor DHT22
volatile float DHT_airhumidity; // variables for saving sensors's values
volatile float DHT_temperature;
// for soil moisture sensor HL28
volatile bool HL28_get = false; // flag to get the reading of moisture in the soil
volatile int HL28_maped_value; // variable para guardar el valor mapeado
volatile int HL28_analog_value; // variable para guardar el valor leído por el sensor HL-28
volatile bool HL28_pump_on = false; // al prenderse la bomba cambia el valor de HL28_interval
// for controls and sensors
volatile bool c_manual_lamp_ON = false; // bandera de estado para el control manual de la lampára
volatile bool c_manual_plump_ON = false; // bandera de estado de para el control manual la bomba de agua
volatile bool c_auto_lamp_ON = false; // bandera de estado para el control automático de la lámpara
volatile bool c_auto_plump_ON = false; // bandera de estado para el control automático de la bomba de agua
volatile bool foto_p_veg = false; // bandera que indica el foto-periodo true = vegetativo, false = florecimiento
// para los push buttons
int button_pressed = 0;
// for the timming
int msg_delay = 0; // contador de segundos para mensaje en 4ta línea del LCD
volatile bool msg_delay_on = false; // bandera que indica mensaje en la 4ta linea del LCD
int HL28_interval = 20000; // tiempo entre lecturas largas del HL28
unsigned long currentMillis = 0; // stores the value of millis() in each iteration of loop()
unsigned long previous_lcd_millis = 0; // will store last times
unsigned long previous_HL28_millis = 0;
unsigned long previous_DHT_millis = 0;
// para el RTC DS3232
volatile boolean RTC_alarm_call_ON = false;
volatile byte alarm2_hour = 18; // hora de finalización del fotoperiodo (variable), por defecto florecimiento
volatile byte alarm2_minute = 0;
volatile byte alarm2_second = 0;
volatile byte alarm2_day = 0;
void setup() {
delay(100);
// inicializa puerto serial, push buttons y pin 13 (built in LED)
Serial.begin(9600);
printDateTime(RTC.get());
Serial << " --> Current RTC time\n";
button1.setDebounceTime(50); // set debounce time to 50 milliseconds
button2.setDebounceTime(50); // set debounce time to 50 milliseconds
button3.setDebounceTime(50); // set debounce time to 50 milliseconds
pinMode(13, OUTPUT); //nn
digitalWrite(13, LOW);
// inicializa el LCD
Wire.begin();
lcd.begin(LCD_COLS, LCD_ROWS);
lcd.backlight(); // turn on backlight
lcd.createChar(0, degree_char); // crea caracter de grado
lcd.createChar(1, arrow_char); // .. flecha
lcd.createChar(2, mark_char); // .. marca
// inicializa controles
pinMode(lamp, OUTPUT); // initialize digital pin 50 as an relay output.
digitalWrite(lamp, HIGH); // inicializa la lámpara apagada, -> LÓGICA INVERSA
pinMode(plump, OUTPUT); // initialize digital pin 52 as an relay output.
digitalWrite(plump, LOW); // inicializa la bomba apagada
// inicializa sensores
pinMode(HL28_pin, INPUT);
pinMode(DHT_pin, INPUT);
dht.begin(); // inicializa el objeto dht
sensor_update();
// inicializa RTC y alarmas
//RTC.set(compileTime()); // ejecutar una sola vez para cargar el tiempo en el RTC
// setSyncProvider() causes the Time library to synchronize with the
// external RTC by calling RTC.get() every five minutes by default.
setSyncProvider(RTC.get); // the function to get the time from the RTC
RTC.squareWave(SQWAVE_NONE); // desactiva la onda cuadrada del RTC
// las alarmas 1 y 2 del RTC se utilizan para controlar el fotoperiodo. ALARM_1 marca el inicio del ciclo y ALARM_2 el final
// sintaxis: setAlarm(ALARM_TYPES_t alarmType, byte seconds, byte minutes, byte hours, byte daydate)
RTC.setAlarm(ALM1_MATCH_HOURS, alarm1_second, alarm1_minute, alarm1_hour, alarm1_day);
RTC.alarm(ALARM_1);
RTC.alarmInterrupt(ALARM_1, true);
RTC.setAlarm(ALM2_MATCH_HOURS, alarm2_second, alarm2_minute, alarm2_hour, alarm2_day);
RTC.alarm(ALARM_2);
RTC.alarmInterrupt(ALARM_2, true);
pinMode(SQW_pin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(SQW_pin), RTC_alarm_call, FALLING);
process_lcd_machine();
Ethernet.begin(mac, ip);// Inicializamos la comunicación Ethernet y el servidor
server.begin();
Serial.print("server is at ");
Serial.println(Ethernet.localIP());//Mostramos en el monitor serie la ip del ethernet shield
}
void loop() {
button1.loop(); // MUST call the loop() function first
button2.loop(); // MUST call the loop() function first
button3.loop(); // MUST call the loop() function first
read_buttons(); //
process_buttons(); //
currentMillis = millis();
lcd_check(); //
HL28_check(); //
DHT_check(); //
auto_lamp_check(); //
lcd_last_cmd(last_cmd); //
internet_task(); //
}
void sensor_update() { // función para actualizar los sensores de húmedad y temperatura
HL28_analog_value = analogRead(HL28_pin); // realiza una lectura análoga del pin conectado al sensor HL-28
HL28_maped_value = map(HL28_analog_value, HL28_wet_soil, HL28_dry_soil, 100, 0); // ubica este valor leído entre un mínimo y un máximo
DHT_airhumidity = dht.readHumidity(); // lee y almacena el valor de la humedad en el aire
DHT_temperature = dht.readTemperature(); // ... de la temperatura
}
void read_buttons() {
int btn1State = button1.getState();
int btn2State = button2.getState();
int btn3State = button3.getState();
if (button1.isPressed()) {
button_pressed = 1;
last_cmd = LAST_CMD_NONE;
}
if (button2.isPressed()) {
button_pressed = 2;
last_cmd = LAST_CMD_NONE;
}
if (button3.isPressed()) {
button_pressed = 3;
last_cmd = LAST_CMD_NONE;
}
}
void process_buttons() {
switch (button_pressed) {
case 1: // pantalla
button_pressed = 0;
lcd.setCursor(0, 3);
lcd.print("> BUTTON PRESSED: 1");
switch (state_lcd) {
case STATE_LCD_START:
state_lcd = STATE_LCD_SENSORS;
break;
case STATE_LCD_SENSORS:
state_lcd = STATE_LCD_C_MANUAL_LAMP;
break;
case STATE_LCD_C_MANUAL_LAMP:
state_lcd = STATE_LCD_C_MANUAL_PLUMP;
break;
case STATE_LCD_C_MANUAL_PLUMP:
state_lcd = STATE_LCD_C_AUTO_LAMP;
break;
case STATE_LCD_C_AUTO_LAMP:
state_lcd = STATE_LCD_C_AUTO_PLUMP;
break;
case STATE_LCD_C_AUTO_PLUMP:
state_lcd = STATE_LCD_FOTO_P_VEG;
break;
case STATE_LCD_FOTO_P_VEG:
state_lcd = STATE_LCD_FOTO_P_FLO;
break;
case STATE_LCD_FOTO_P_FLO:
state_lcd = STATE_LCD_START;
break;
}
break;
case 2:
button_pressed = 0;
lcd.setCursor(0, 3);
lcd.print("> BUTTON PRESSED: 2");
switch (state_lcd) {
case STATE_LCD_START:
state_lcd = STATE_LCD_FOTO_P_FLO;
break;
case STATE_LCD_SENSORS:
state_lcd = STATE_LCD_START;
break;
case STATE_LCD_C_MANUAL_LAMP:
state_lcd = STATE_LCD_SENSORS;
break;
case STATE_LCD_C_MANUAL_PLUMP:
state_lcd = STATE_LCD_C_MANUAL_LAMP;
break;
case STATE_LCD_C_AUTO_LAMP:
state_lcd = STATE_LCD_C_MANUAL_PLUMP;
break;
case STATE_LCD_C_AUTO_PLUMP:
state_lcd = STATE_LCD_C_AUTO_LAMP;
break;
case STATE_LCD_FOTO_P_VEG:
state_lcd = STATE_LCD_C_AUTO_PLUMP;
break;
case STATE_LCD_FOTO_P_FLO:
state_lcd = STATE_LCD_FOTO_P_VEG;
break;
}
break;
case 3:
button_pressed = 0;
lcd.setCursor(0, 3);
lcd.print("> BUTTON PRESSED: 3");
switch (state_lcd) {
case STATE_LCD_C_MANUAL_LAMP:
if (c_manual_lamp_ON == false) {
digitalWrite(lamp, LOW);
c_manual_lamp_ON = true;
}
else {
digitalWrite(lamp, HIGH);
c_manual_lamp_ON = false;
}
break;
case STATE_LCD_C_MANUAL_PLUMP:
if (c_manual_plump_ON == false) {
digitalWrite(plump, HIGH);
c_manual_plump_ON = true;
HL28_interval = 1000;
}
else {
digitalWrite(plump, LOW);
c_manual_plump_ON = false;
HL28_interval = 20000;
}
break;
case STATE_LCD_C_AUTO_LAMP:
if (c_auto_lamp_ON == true) {
c_auto_lamp_ON = false;
}
else {
c_auto_lamp_ON = true;
auto_lamp_first();
}
break;
case STATE_LCD_C_AUTO_PLUMP:
if (c_auto_plump_ON == true) {
c_auto_plump_ON = false;
}
else {
c_auto_plump_ON = true;
state_ctrl = STATE_CTRL_HL28_SREAD;
process_ctrl_machine();
}
break;
case STATE_LCD_FOTO_P_VEG:
foto_p_veg = true;
alarm2_hour = 24;
if (c_auto_lamp_ON == true) {
auto_lamp_first();
}
break;
case STATE_LCD_FOTO_P_FLO:
foto_p_veg = false;
alarm2_hour = 18;
if (c_auto_lamp_ON == true) {
auto_lamp_first();
}
break;
}
break;
}
}
void lcd_check() {
if (currentMillis - previous_lcd_millis >= lcd_interval) {
previous_lcd_millis = currentMillis;
process_lcd_machine();
msg_delay_check();
}
}
void process_lcd_machine() {
switch (state_lcd) {
case STATE_LCD_START:
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" sweet_love v0.29 ");
lcd.setCursor(5, 1);
print_Digits(day());
lcd.print('/');
print_Digits(month());
lcd.print('/');
lcd.print(year());
lcd.setCursor(6, 2);
print_Digits(hour());
lcd.print(':');
print_Digits(minute());
lcd.print(':');
print_Digits(second());
break;
case STATE_LCD_SENSORS:
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Temperatura");
lcd.setCursor(12, 0);
lcd.print(DHT_temperature);
lcd.write(0);
lcd.setCursor(0, 1);
lcd.print("Humedad aire");
lcd.setCursor(13, 1);
lcd.print(DHT_airhumidity);
lcd.print("%");
lcd.setCursor(0, 2);
lcd.print("Hum. sustrato");
lcd.setCursor(14, 2);
lcd.print(HL28_maped_value);
lcd.print("%");
break;
case STATE_LCD_C_MANUAL_LAMP:
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" CONTROL MANUAL ");
lcd.setCursor(0, 1);
lcd.write(1);
lcd.setCursor(2, 1);
if (c_manual_lamp_ON == false) {
//if (lamp == LOW) {
lcd.print("Prender lampara ");
}
if (c_manual_lamp_ON == true) {
//if (lamp == HIGH) {
lcd.print("Apagar lampara ");
}
lcd.setCursor(2, 2);
if (c_manual_plump_ON == false) {
//if (plump == LOW) {
lcd.print("Prender bomba ");
}
if (c_manual_plump_ON == true) {
//if (plump == HIGH) {
lcd.print("Apagar bomba ");
}
break;
case STATE_LCD_C_MANUAL_PLUMP:
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" CONTROL MANUAL ");
lcd.setCursor(2, 1);
if (c_manual_lamp_ON == false) {
lcd.print("Prender lampara ");
}
if (c_manual_lamp_ON == true) {
lcd.print("Apagar lampara ");
}
lcd.setCursor(0, 2);
lcd.write(1);
lcd.setCursor(2, 2);
if (c_manual_plump_ON == false) {
lcd.print("Prender bomba ");
}
if (c_manual_plump_ON == true) {
lcd.print("Apagar bomba ");
}
break;
case STATE_LCD_C_AUTO_LAMP:
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" CONTROL AUTOMATICO ");
lcd.setCursor(0, 1);
lcd.write(1);
lcd.setCursor(2, 1);
if (c_auto_lamp_ON == true) {
lcd.print("Auto-lampara ON ");
}
if (c_auto_lamp_ON == false) {
lcd.print("Auto-lampara OFF ");
}
lcd.setCursor(2, 2);
if (c_auto_plump_ON == true) {
lcd.print("Auto-bomba ON ");
}
if (c_auto_plump_ON == false) {
lcd.print("Auto-bomba OFF ");
}
break;
case STATE_LCD_C_AUTO_PLUMP:
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" CONTROL AUTOMATICO ");
lcd.setCursor(0, 2);
lcd.write(1);
lcd.setCursor(2, 1);
if (c_auto_lamp_ON == true) {
lcd.print("Auto-lampara ON ");
}
if (c_auto_lamp_ON == false) {
lcd.print("Auto-lampara OFF ");
}
lcd.setCursor(2, 2);
if (c_auto_plump_ON == true) {
lcd.print("Auto-bomba ON ");
}
if (c_auto_plump_ON == false) {
lcd.print("Auto-bomba OFF ");
}
break;
case STATE_LCD_FOTO_P_VEG:
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" FOTO-PERIODO ");
lcd.setCursor(0, 1);
lcd.write(1);
lcd.setCursor(2, 1);
lcd.print("Vegetativo 18/6 ");
lcd.setCursor(2, 2);
lcd.print("Florecim. 12/12 ");
if (foto_p_veg == true) {
lcd.setCursor(18, 1);
}
else {
lcd.setCursor(18, 2);
}
lcd.write(2);
break;
case STATE_LCD_FOTO_P_FLO:
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(" FOTO-PERIODO ");
lcd.setCursor(0, 2);
lcd.write(1);
lcd.setCursor(2, 1);
lcd.print("Vegetativo 18/6 ");
lcd.setCursor(2, 2);
lcd.print("Florecim. 12/12 ");
if (foto_p_veg == true) {
lcd.setCursor(18, 1);
}
else {
lcd.setCursor(18, 2);
}
lcd.write(2);
break;
}
}
void process_ctrl_machine() { // procesa la máquina de estados que controla la lectura de sensores
switch (state_ctrl) {
case STATE_CTRL_HL28_SREAD: // lectura con intervalo corto de humedad en sustrato
HL28_analog_value = analogRead(HL28_pin); // realiza una lectura análoga del pin conectado al sensor HL-28
HL28_maped_value = map(HL28_analog_value, HL28_wet_soil, HL28_dry_soil, 100, 0); // ubica este valor leído entre un mínimo y un máximo
state_ctrl = STATE_CTRL_NONE;
// para pruebas
/*Serial.println(HL28_analog_value); //lectura analógica
Serial.print(HL28_maped_value); //lectura mapeada
Serial.println("%");
Serial.print("\n");*/
// función para comparar el valor analogo obtenido con las constantes de sustrato definidas al inicio
if (c_auto_plump_ON == true) { // sólo realiza la comparación si el control automático de la bomba de agua está activado
if ((HL28_analog_value + 32) > HL28_dry_soil) { // si el valor (+5% para evitar el caso de que nunca llegue a 0% de humedad) es mayor que sustrato seco, entonces prender bomba de agua
//if (HL28_analog_value > HL28_dry_soil) { // si el valor es mayor que sustrato seco, entonces prender bomba de agua
digitalWrite(plump, HIGH);
c_manual_plump_ON = true;
last_cmd = LAST_CMD_HUM_LOW_PLUMP_ON;
//if (HL28_pump_on == false ) {
HL28_interval = 1000;
}
else if ((HL28_analog_value - 32) < HL28_wet_soil) { // si el valor (-5% para evitar el caso de que nunca llegue a 100% de humedad) es mayor que sustrato seco, entonces prender bomba de agua
//if (HL28_analog_value < HL28_wet_soil) { // si el valor es menor que sustrato humedo, entonces apagar bomba de agua
digitalWrite(plump, LOW);
c_manual_plump_ON = false;
last_cmd = LAST_CMD_HUM_HIGH_PLUMP_OFF;
HL28_interval = 20000;
}
}
break;
case STATE_CTRL_DHT_READ: // lectura de humedad en el aire y temperatura
DHT_airhumidity = dht.readHumidity(); // lee y almacena el valor de la humedad en el aire
DHT_temperature = dht.readTemperature(); // ... de la temperatura
state_ctrl = STATE_CTRL_NONE;
break;
}
}
void HL28_check() {
if (currentMillis - previous_HL28_millis >= HL28_interval) {
previous_HL28_millis = currentMillis;
state_ctrl = STATE_CTRL_HL28_SREAD;
process_ctrl_machine();
}
}
void DHT_check() {
if (currentMillis - previous_DHT_millis >= DHT_interval) {
previous_DHT_millis = currentMillis;
state_ctrl = STATE_CTRL_DHT_READ;
process_ctrl_machine();
}
}
void auto_lamp_check() {
if (RTC_alarm_call_ON == true && c_auto_lamp_ON == true ) {
if (RTC.alarm(ALARM_1)) { // si la ALARM_1 se activa indica el inicio del ciclo
printDateTime( RTC.get() );
Serial << " --> Alarm 1\n";
digitalWrite(lamp, LOW); // ... entonces prende la lámpara
c_manual_lamp_ON = true;
last_cmd = LAST_CMD_PHOTO_STARTS;
}
if (RTC.alarm(ALARM_2)) { // si la ALARM_2 se activa indica el final del ciclo
printDateTime( RTC.get() );
Serial << " --> Alarm 2\n";
digitalWrite(lamp, HIGH); // ... entonces apaga la lámpara
c_manual_lamp_ON = false;
last_cmd = LAST_CMD_PHOTO_ENDS;
}
RTC_alarm_call_ON = false; // limpia la bandera de llamado por interrupción de las alarmas del RTC
}
}
void lcd_last_cmd(last_cmds last_cmd) {
lcd.setCursor(0, 3);
switch (last_cmd) {
case LAST_CMD_PHOTO_STARTS:
lcd.print("> PHOTOPERIOD STARTS");
msg_delay_on = true;
break;
case LAST_CMD_PHOTO_ENDS:
lcd.print("> PHOTOPERIOD ENDS ");
msg_delay_on = true;
break;
case LAST_CMD_HUM_HIGH_PLUMP_OFF:
lcd.print("> HUM HIGH PLUMP OFF");
break;
case LAST_CMD_HUM_LOW_PLUMP_ON:
lcd.print("> HUM LOW PLUMP ON ");
break;
}
last_cmd = LAST_CMD_NONE;
}
void internet_task() { // crea un cliente web accesible a tráves de la red. muestra la página HTML de sweet_love, lee y ejecuta peticiones web
EthernetClient client = server.available(); //Creamos un cliente Web
//Cuando detecte un cliente a través de una petición HTTP
if (client) {
Serial.println("new client");
boolean currentLineIsBlank = true; //Una petición HTTP acaba con una línea en blanco
String comando = ""; //cadena de caracteres vacía para guardar el estado del la lampara
while (client.connected()) {//mientras el cliente este conectado y disponible
if (client.available()) {
char c = client.read();//Leemos la petición HTTP cada caracter individualmente
Serial.write(c);
comando.concat(c);//vamos agregando cada caracter de cada peticion para obtener la orden completa
int poscomando = comando.indexOf("CMD="); //buscamos en el texto donde empieza el comando a ejecutar palabra LAM
if (comando.substring(poscomando) == "CMD=LAMP_ON") //Si en la posicion poscomando hay "LAMP=ON"
{
digitalWrite(lamp, LOW); //encendemos la bombilla
c_manual_lamp_ON = true;
}
if (comando.substring(poscomando) == "CMD=LAMP_OFF") //Si en la posicion poscomando hay "LAMP=OFF"
{
digitalWrite(lamp, HIGH); //apagamos bombilla
c_manual_lamp_ON = false;
}
if (comando.substring(poscomando) == "CMD=PLUMP_ON") //Si en la posicion poscomando hay "PLUMP=ON"
{
digitalWrite(plump, HIGH); //encendemos la bomba rele activo
c_manual_plump_ON = true;
HL28_interval = 1000;
}
if (comando.substring(poscomando) == "CMD=PLUMP_OFF") //Si en la posicion poscomando hay "PLUMP=OFF"
{
digitalWrite(plump, LOW); //apagamos bomba rele desactivo
c_manual_plump_ON = false;
HL28_interval = 20000;
}
if (comando.substring(poscomando) == "CMD=AUTO_LAMP_ON")
{
c_auto_lamp_ON = true;
auto_lamp_first();
}
if (comando.substring(poscomando) == "CMD=AUTO_LAMP_OFF")
{
c_auto_lamp_ON = false;
}
if (comando.substring(poscomando) == "CMD=AUTO_PLUMP_ON")
{
c_auto_plump_ON = true;
state_ctrl = STATE_CTRL_HL28_SREAD;
process_ctrl_machine();
}
if (comando.substring(poscomando) == "CMD=AUTO_PLUMP_OFF")
{
c_auto_plump_ON = false;
}
if (comando.substring(poscomando) == "CMD=FOTO_P_VEG")
{
foto_p_veg = true;
if (c_auto_lamp_ON == true) {
auto_lamp_first();
}
}
if (comando.substring(poscomando) == "CMD=FOTO_P_FLO")
{
foto_p_veg = false;
if (c_auto_lamp_ON == true) {
auto_lamp_first();
}
}
if (comando.substring(poscomando) == "CMD=SENSOR_UPDATE") //Si en la posicion poscomando hay "LAM=OFF"
{
sensor_update();
}
if (c == '\n' && currentLineIsBlank) {//comprobamos que ha acabado la petición con una linea en blanco
client.println("HTTP/1.1 200 OK");//Enviamos la respuesta de la peticion al cliente
client.println("Content-Type: text/html");
client.println();
if (digitalRead(lamp) == 1) {
c_manual_lamp_ON = false;
}
if (digitalRead(lamp) == 0) {
c_manual_lamp_ON = true;
}
if (digitalRead(plump) == 1) {
c_manual_plump_ON = true;
}
if (digitalRead(plump) == 0) {
c_manual_plump_ON = false;
}
sensor_update();
client.println("<html>");//servimos la web a mostrar en HTML
client.println("<head>");
String html_1 = "<meta name=\"HandheldFriendly\" content=\"True\"> <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=yes\">";
client.println(html_1);
client.println("</head>");
client.println("<body>");
String html_2 = "<p style=\"text-align:center\"><strong><span style=\"font-family:Courier New,Courier,monospace\"><em><span style=\"font-size:28px\">sweet_love v0.29</span></em></span></strong></p> <p style=\"text-align:center\"><strong><span style=\"font-family:Courier New,Courier,monospace\"><em>Sistema de control para cultivos</em></span></strong></p> <p> </p> ";
client.print(html_2);
client.print("<p>SENSORES <button type=\"button\" onclick=\"location.href='./?CMD=SENSOR_UPDATE\'\">ACTUALIZAR</button> </p> <ul> <li> ");
client.print(DHT_temperature);
client.print("ºC ⇒ TEMPERATURA</li> <li>");
client.print(DHT_airhumidity);
client.print("% ⇒ HÚMEDAD EN EL AIRE</li> <li>");
client.print(HL28_maped_value);
client.print("% ⇒ HÚMEDAD EN EL SUSTRATO</li> </ul> <p> </p>");
client.println("<p>CONTROL MANUAL</p> <ul>");
if (c_manual_lamp_ON == false) { //si la lampara esta apagada
client.println("<li> <radio onClick=location.href='./?CMD=LAMP_ON\'>");//cuando se pinche envia el comando lam=on
client.print("<input type=\"radio\"name=\"c_manual_lamp\"value=\"1\"> ENCENDER LAMPARA </li>");//muestra el texto encender
}
if (c_manual_lamp_ON == true) { //si esta encendida muestra texto apagar
client.println("<li> <radio onClick=location.href='./?CMD=LAMP_OFF\'>");//cuando se pinche da orden de apagar
client.print("<input type=\"radio\"name=\"c_manual_lamp\"value=\"1\"> APAGAR LAMPARA </li>");//muestra texto apagar
}
if (c_manual_plump_ON == false) { //si la bomba esta apagada
client.println("<li> <radio onClick=location.href='./?CMD=PLUMP_ON\'>");//cuando se pinche envia el comando lam=on
//client.print("<input type=\"radio\"name=\"c_manual_lamp\"value=\"1\"> ENCENDER BOMBA </li> </ul> <p> </p>");//muestra el texto encender
client.print("<input type=\"radio\"name=\"c_manual_plump\"value=\"1\"> ENCENDER BOMBA </li> </ul> <p> </p>");//muestra el texto encender
}
if (c_manual_plump_ON == true) { //si esta encendida muestra texto apagar
client.println("<li> <radio onClick=location.href='./?CMD=PLUMP_OFF\'>");//cuando se pinche da orden de apagar
//client.print("<input type=\"radio\"name=\"c_manual_lamp\"value=\"1\"> APAGAR BOMBA </li> </ul> <p> </p>");//muestra texto apagar
client.print("<input type=\"radio\"name=\"c_manual_plump\"value=\"1\"> APAGAR BOMBA </li> </ul> <p> </p>");//muestra texto apagar
}
client.println("<p>CONTROL AUTOMÁTICO</p> <ul>");
if (c_auto_lamp_ON == false) { //si auto lamp esta apagada
client.println("<li> <radio onClick=location.href='./?CMD=AUTO_LAMP_ON\'>");
client.print("<input type=\"radio\"name=\"c_auto_lamp\"value=\"1\"> ACTIVAR AUTO LÁMPARA</li>");//muestra el texto encender
}
if (c_auto_lamp_ON == true) {
client.println("<li> <radio onClick=location.href='./?CMD=AUTO_LAMP_OFF\'>");//cuando se pinche da orden de apagar
client.print("<input type=\"radio\"name=\"c_auto_lamp\"value=\"1\"> DESACTIVAR AUTO LÁMPARA</li>");//muestra texto apagar
}
if (c_auto_plump_ON == false) { //si auto bomba esta apagada
client.println("<li> <radio onClick=location.href='./?CMD=AUTO_PLUMP_ON\'>");
client.print("<input type=\"radio\"name=\"c_auto_plump\"value=\"1\"> ACTIVAR AUTO BOMBA </li> </ul> <p> </p>");//muestra el texto encender
}
if (c_auto_plump_ON == true) { //si esta activada muestra texto desactivar
client.println("<li> <radio onClick=location.href='./?CMD=AUTO_PLUMP_OFF\'>");
client.print("<input type=\"radio\"name=\"c_auto_plump\"value=\"1\"> DESACTIVAR AUTO BOMBA </li> </ul> <p> </p>");//muestra texto apagar
}
client.println("<p>FOTO PERIODO</p> <ul>");
if (foto_p_veg == true) {
client.print("<li> <input checked=\"checked\" name=\"web_foto_p_veg\" type=\"checkbox\" /> VEGETATIVO 18/6 HORAS</li>");
client.println("<li> <radio onClick=location.href='./?CMD=FOTO_P_FLO\'>");
client.print("<input name=\"web_foto_p_veg\" type=\"checkbox\" /> FLORECIMIENTO 12/12 HORAS</li>");
}
else {
client.println("<li> <radio onClick=location.href='./?CMD=FOTO_P_VEG\'>");
client.print("<input name=\"web_foto_p_veg\" type=\"checkbox\" /> VEGETATIVO 18/6 HORAS</li>");
client.print("<li> <input checked=\"checked\" name=\"web_foto_p_flo\" type=\"checkbox\" /> FLORECIMIENTO 12/12 HORAS</li> </ul> <p> </p>");
}
String html_3 = "<p style=\"text-align:right\"><strong><em>Creado en Diciembre de 2023 </em></strong></p> <p style=\"text-align:right\"><strong><em>Por: Alejandro Bermudez Ospina</em></strong></p> <p style=\"text-align:right\"><strong><em>(alejandro.bermudez.ospinan@gmail.com)</em></strong></p>";
client.print(html_3);
client.println("</body>");
client.println("</html>");
break;
}
//final de respuesta
if (c == '\n') {
currentLineIsBlank = true;
}
else if (c != '\r') {
currentLineIsBlank = false;
}
}
}
delay(1);//Esperamos a que reciba la respuesta
client.stop();//Termina la conexión
}
}
void auto_lamp_first () { //compara el tiempo actual y retorna 1 de 3 posibles valores
//si es de 00 a 06 apaga la lamp(1), si es 06 a 18 la prende(2), de 18 a 24 si es veg prendida(3) y si es flo apagada
time_t t_now = RTC.get();
time_t t_0 = tmConvert_t(year(t_now), month(t_now), day(t_now), 0, 0, 0);
time_t t_6 = tmConvert_t(year(t_now), month(t_now), day(t_now), 6, 0, 0);
time_t t_18 = tmConvert_t(year(t_now), month(t_now), day(t_now), 18, 0, 0);
time_t t_24 = tmConvert_t(year(t_now), month(t_now), day(t_now), 23, 59, 59);
if (t_now >= t_0 && t_now < t_6) {
digitalWrite(lamp, HIGH);
c_manual_lamp_ON = false;
}
if (t_now >= t_6 && t_now < t_18) {
digitalWrite(lamp, LOW);
c_manual_lamp_ON = true;
}
if (t_now >= t_18 && t_now <= t_24) {
if (foto_p_veg == true) {
digitalWrite(lamp, LOW);
c_manual_lamp_ON = true;
}
else {
digitalWrite(lamp, HIGH);
c_manual_lamp_ON = false;
}
}
}
time_t tmConvert_t(int YYYY, byte MM, byte DD, byte hh, byte mm, byte ss)
{
tmElements_t tmSet;
tmSet.Year = YYYY - 1970;
tmSet.Month = MM;
tmSet.Day = DD;
tmSet.Hour = hh;
tmSet.Minute = mm;
tmSet.Second = ss;
return makeTime(tmSet); //convert to time_t
}
void RTC_alarm_call(void) {
RTC_alarm_call_ON = true;
Serial.println("RTC alarm called");
}
void print_Digits(int digits) {
if (digits < 10)
lcd.print('0');
lcd.print(digits);
}
void printDateTime(time_t t) { // funciones de manejo de fecha y hora
Serial << ((day(t) < 10) ? "0" : "") << _DEC(day(t));
Serial << monthShortStr(month(t)) << _DEC(year(t)) << ' ';
Serial << ((hour(t) < 10) ? "0" : "") << _DEC(hour(t)) << ':';
Serial << ((minute(t) < 10) ? "0" : "") << _DEC(minute(t)) << ':';
Serial << ((second(t) < 10) ? "0" : "") << _DEC(second(t));
}
time_t compileTime() { // function to return the compile date and time as a time_t value
const time_t FUDGE(10); //fudge factor to allow for upload time, etc. (seconds, YMMV)
const char *compDate = __DATE__, *compTime = __TIME__, *months = "JanFebMarAprMayJunJulAugSepOctNovDec";
char compMon[3], *m;
strncpy(compMon, compDate, 3);
compMon[3] = '\0';
m = strstr(months, compMon);
tmElements_t tm;
tm.Month = ((m - months) / 3 + 1);
tm.Day = atoi(compDate + 4);
tm.Year = atoi(compDate + 7) - 1970;
tm.Hour = atoi(compTime);
tm.Minute = atoi(compTime + 3);
tm.Second = atoi(compTime + 6);
time_t t = makeTime(tm);
return t + FUDGE; //add fudge factor to allow for compile time
}
void msg_delay_check() {
if (msg_delay_on == true) {
msg_delay++;
if (msg_delay == 60) {
last_cmd = LAST_CMD_NONE;
lcd.setCursor(0, 3);
lcd.print("> ");
msg_delay = 0;
msg_delay_on = false;
}
}
}
Resumen de funcionamiento (copy/paste del tema anterior!):
El sistema controla el riego y la iluminación del cultivo. Muestra en la pantalla del LCD la siguiente información: tiempo, fecha, temperatura, humedad en aire y en sustrato, estado de los controles manuales y automáticos, así como el fotoperiodo. Puede ser configurado para realizar control manual y automático. Para el control automático del riego, utiliza la medida del sensor de humedad en el sustrato y activa el riego mediante una bomba de agua. Para el control automático de la luz, configura las alarmas del reloj de tiempo real con el foto-periodo especificado sea de crecimiento 18/6 (por defecto) o 12/12 de maduración. Los controles automáticos vienen desactivados por defecto.
Reemplacé la sonda del sensor de humedad que viene de fabrica por una artesanal hecha con tornillos gruesos y piezas de ferretearía, de modo que si se daña por corrosión podría ser reemplazada fácilmente. De todas maneras en esta parte tuve en cuenta las dificultades presentadas anteriormente (a Zion y otros usuarios) debido a la corrosión en la sonda, entonces para este código implementé un algoritmo que usa dicha sonda y realiza una lectura de humedad sólo una vez cada minuto pero con una mayor frecuencia al momento de usar la bomba de agua para lograr detener su uso a tiempo. La corrosión la causa la lectura como tal así que reducir la frecuencia con que se hace debería extender la vida de la sonda..
Riego y lámpara
El riego lo implementé usando una sonda plástica de las que venden en ferretería. Ubicándola en forma de círculo alrededor del tallo de la planta, perforando pequeños agujeros hacia abajo y poniendo un tapón al final de la sonda. La lámpara que usé para las pruebas fue una lámpara fluorescente de 110v. No sirve mucho para cultivos de cannabis pero creo que sirve para probar el sistema. El relé de estado solido soportaría hasta 240VAC @2A.
¡ACTUALIZACIÓN!
Como lo prometí, en esta nueva versión es posible acceder al sistema utilizando un Ethernet Shield (donación de @Zion_Lion, gracias!!).
Así luce el servidor web accediendo desde un celular:
La dirección IP por defecto del servidor web es 192.168.1.115. Esta dirección debe ser acorde a la subred que haya en el lugar, si hay una subred distinta entonces hay que cambiar la dirección IP desde el código (en la línea: IPAddress ip(192, 168, 1, 115);). Para acceder directamente el Ethernet Shield simplemente se conecta al router con un cable UTP, para acceder a través de Internet se tendría que abrir el puerto 80 del router!! (esto aún no lo he probado).
Vídeos en Youtube!
El año pasado grabé material para una futura publicación. Aquí dejo los links!
Saludos!!
Última edición: