Hola comunidad,
Primero que todo les presento disculpas (especialmente a @Zion Lion) porque esto lo debí haber hecho hace mucho tiempo. En esa época @Zion Lion estaba trabajando en un Cultivo con Arduino + LED y construyó el EasyGrowWeed. Con las partes que él me suministró más otras que compré y usando su idea original escribí un programa desde cero. Es un sistema embebido automático de control para cultivos, diseñado para ser implementado en Arduino (Mega en este caso). El programa está escrito en full c++ y utiliza librerías MUY actualizadas que incluso no existían hace unos años cuando todo comenzó.
Estas son las partes utilizadas:
- fuente suicheada S-40-12 12v 3.2A 38w
- regulador protoboard mb102
- arduino mega 2560
- 16x2 LCD keypad shield
- reloj de tiempo real DS3232
- sensor de humedad en suelo FC-28
- sensor de temperatura y humedad en aire DHT22
- módulo réle de 2 canales opto-aislado 5v 10A
- mini bomba de agua 12v 3.6W
(agradecimientos a @Zion Lion por su aporte)
Este es el diagrama de conexiones (realizado en Fritzing).
Este es el código:
Resumen de funcionamiento
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.
Las ideas para la siguiente versión son:
- Comunicarse con el sistema por internet utilizando el ethernet shield,
- Personalizar en tiempo real el foto-periodo y los valores de referencia de sustrato húmedo y seco.
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..
Agradecimientos a todos por la comprensión. La verdad tuve un bloqueo que me impidió hacer esto antes, así que lo siento mucho.
Espero estarle cumpliendo a la comunidad y a @Zion Lion.
Saludos. Buenos humos.
Links de utilidad:
Arduino
fritzing
GitHub alexokaban
Primero que todo les presento disculpas (especialmente a @Zion Lion) porque esto lo debí haber hecho hace mucho tiempo. En esa época @Zion Lion estaba trabajando en un Cultivo con Arduino + LED y construyó el EasyGrowWeed. Con las partes que él me suministró más otras que compré y usando su idea original escribí un programa desde cero. Es un sistema embebido automático de control para cultivos, diseñado para ser implementado en Arduino (Mega en este caso). El programa está escrito en full c++ y utiliza librerías MUY actualizadas que incluso no existían hace unos años cuando todo comenzó.
Estas son las partes utilizadas:
- fuente suicheada S-40-12 12v 3.2A 38w
- regulador protoboard mb102
- arduino mega 2560
- 16x2 LCD keypad shield
- reloj de tiempo real DS3232
- sensor de humedad en suelo FC-28
- sensor de temperatura y humedad en aire DHT22
- módulo réle de 2 canales opto-aislado 5v 10A
- mini bomba de agua 12v 3.6W
(agradecimientos a @Zion Lion por su aporte)
Este es el diagrama de conexiones (realizado en Fritzing).
Este es el código:
Código:
/* nombre clave: sweet_love v0.11
sistema de control manual y automático para cultivos
escrito por: alexokaban (alexokaban@gmail.com)
idea original (EasyGrowWeed) y soporte de partes: Zion Lion
agradecimiento especial a Zion Lion por el envío de partes
código libre para el uso de la comunidad.
julio de 2019 */
#include <DHT.h> // https://github.com/alexokaban/DHT-sensor-library
#include <DHT_U.h>
#include <Adafruit_Sensor.h> // https://github.com/alexokaban/Adafruit_Sensor
#include <DS3232RTC.h> // https://github.com/alexokaban/DS3232RTC
#include <MD_UISwitch.h> // https://github.com/alexokaban/MD_UISwitch
#include <SoftTimer.h> // https://github.com/alexokaban/arduino-softtimer
#include <hd44780.h> // https://github.com/alexokaban/hd44780
#include <hd44780ioClass/hd44780_pinIO.h> // Arduino pin i/o class header
#include <Streaming.h> // http://arduiniana.org/libraries/streaming/
// LCD pin setup
const int rs=8, en=9, db4=4, db5=5, db6=6, db7=7;
hd44780_pinIO lcd(rs, en, db4, db5, db6, db7);
int LCD_select; // variable que indica una pantalla de 5 posibles
// 1: tiempo/fecha, 2: sensores, 3 y 4: control manual, 5 y 6: control automático, 7: foto-periodo
const int LCD_date_time = 1;
const int LCD_sensors = 2;
const int LCD_ctrl_lamp = 3;
const int LCD_ctrl_plump = 4;
const int LCD_auto_lamp = 5;
const int LCD_auto_plump = 6;
const int LCD_lamp_cycle = 7;
// LCD geometry
const int LCD_COLS = 16; // columnas
const int LCD_ROWS = 2; // 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
// variables for the key_pad
const uint8_t ANALOG_SWITCH_PIN = A0; // switches connected to this pin
MD_UISwitch_Analog::uiAnalogKeys_t kt[] = {
{ 10, 10, 'R' }, // Right
{ 130, 15, 'U' }, // Up
{ 305, 15, 'D' }, // Down
{ 475, 15, 'L' }, // Left
{ 720, 15, 'S' }, // Select
};
MD_UISwitch_Analog keypad(ANALOG_SWITCH_PIN, kt, ARRAY_SIZE(kt));
// variables for controls and sensors
const int ctrl_relay_lamp = 50; // pin de salida digital al relé que controla la lampára
const int ctrl_relay_plump = 52; // pin de salida al relé que controla la bomba de agua
volatile bool ctrl_lamp_ON = false; // bandera de estado para el control manual de la lampára
volatile bool ctrl_plump_ON = false; // bandera de estado de para el control manual la bomba de agua
volatile bool auto_lamp_ON = false; // bandera de estado para el control automático de la lámpara
volatile bool auto_plump_ON = false; // bandera de estado para el control automático de la bomba de agua
volatile bool ctrl_lamp_cycle = false; // bandera que indica el foto-periodo FALSE = 18/6 y TRUE = 12/12
// for soil moisture sensor HL28
#define HL28_pin A2 // analog pin to connect the soil moisture sensor
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
const int HL28_dry_soil = 500; // constante de sustrato seco para activar la bomba de agua
const int HL28_wet_soil = 200; // constante de sustrato humedo para detener la bomba de agua
int HL28_plump_count; // contador para realizar lecturas de humedad de acuerdo al estado de la bomba de agua
bool HL28_first_reading = false; // bandera para realizar la 1ra lectura del sensor HL-28
// for temperature & humidity sensor DHT22
#define DHT_pin A1 // entrada pin análogo al sensor DHT22
#define DHT_type DHT22
DHT dht(DHT_pin, DHT_type);
volatile float DHT_airhumidity; // variables for saving sensors's values
volatile float DHT_temperature;
// variables para el RTC DS3232
#define SQW_pin 19 // connect this pin to DS3231 INT/SQW pin. entrada que recibe la señal de interrupción para las alarmas del RTC
volatile boolean RTC_alarm_call_ON = false;
const byte alarm1_hour = 16; // hora de inicio del fotoperiodo (fija)
const byte alarm1_minute = 7;
const byte alarm1_second = 0;
const byte alarm1_day = 0;
volatile byte alarm2_hour = 16; // hora de finalización del fotoperiodo (variable)
volatile byte alarm2_minute = 8;
volatile byte alarm2_second = 0;
volatile byte alarm2_day = 0;
// -- define method signature
void callBack1(Task* me);
void callBack2(Task* me);
void callBack3(Task* me);
void callBack4(Task* me);
void callBack5(Task* me);
// define las tareas y cada cuantos milisegundos se realizan
Task lcd_show(100, callBack1); // mostrar una pantalla del estado del sistema en el LCD
Task key_check(10, callBack2); // revisar si una tecla ha sido presionada
Task DHT_check(3000, callBack3); // realizar una lectura del sensor DHT22
Task HL28_short_check(1000, callBack4); // lecturas del sensor HL-28 con intervalo corto
Task HL28_long_check(60000, callBack5); // ... con intervalo largo
void setup() {
// inicializa controles
pinMode(ctrl_relay_lamp, OUTPUT); // initialize digital pin 50 as an relay output.
digitalWrite(ctrl_relay_lamp, HIGH); // inicializa la lámpara apagada (lógica inversa)
pinMode(ctrl_relay_plump, OUTPUT); // initialize digital pin 52 as an relay output.
digitalWrite(ctrl_relay_plump, HIGH); // inicializa la bomba apagada
keypad.begin(); // inicializa la clase MD_UISwitch_Analog
// inicializa sensores
pinMode(HL28_pin, INPUT);
dht.begin(); // inicializa el objeto dht
// inicializa y configura el RTC DS3232 y sus dos alarmas
// esta porción de código debe ser aplicada una sola vez para cargar el tiempo en el RTC
/*time_t t = compileTime(); // change the tm structure into time_t
RTC.set(t);*/
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, 0, 0, 6, 0); // inicializa ALARM_1 a las 6am de todos los días (0)
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, 0, 0, 18, 0); // ALARM_2 a las 6pm
RTC.setAlarm(ALM2_MATCH_HOURS, alarm2_second, alarm2_minute, alarm2_hour, alarm2_day);
RTC.alarm(ALARM_2);
RTC.alarmInterrupt(ALARM_2, true);
// 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
pinMode(SQW_pin, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(SQW_pin), RTC_alarm_call, FALLING);
// inicializa el LCD
lcd.begin(LCD_COLS, LCD_ROWS);
lcd.createChar(0, degree_char); // crea caracter de grado
lcd.createChar(1, arrow_char); // .. flecha
LCD_select = LCD_date_time; // 1ra pantalla de tiempo/fecha
SoftTimer.add(&lcd_show); // agrega las tareas definidas a la librería SoftTimer
SoftTimer.add(&key_check);
SoftTimer.add(&DHT_check);
SoftTimer.add(&HL28_short_check);
SoftTimer.add(&HL28_long_check);
Serial.begin(115200); // para pruebas
printDateTime(RTC.get());
Serial << " --> Current RTC time\n";
}
// utility function for digital clock display: prints preceding colon and leading 0
void print_Digits(int digits) {
if (digits < 10)
lcd.print('0');
lcd.print(digits);
}
void callBack1(Task* me) { // muestra una de cuatro pantallas posibles en el LCD
/* como este programa no tiene la función de blucle infinito loop(), entonces
la interrupción generada por el RTC al activarsen las alarmas se procesa
aquí al principio de la tarea que actualiza la pantalla del LCD*/
if(RTC_alarm_call_ON == true && auto_lamp_ON == true ) {
if (RTC.alarm(ALARM_1)) { // si al ALARM_1 se activa indica el inicio del ciclo
printDateTime( RTC.get() );
Serial << " --> Alarm 1\n";
digitalWrite(ctrl_relay_lamp, LOW); // ... entonces prende la lámpara
ctrl_lamp_ON = true;
}
if (RTC.alarm(ALARM_2)) { // si al ALARM_2 se activa indica el final del ciclo
printDateTime( RTC.get() );
Serial << " --> Alarm 2\n";
digitalWrite(ctrl_relay_lamp, HIGH); // ... entonces apaga la lámpara
ctrl_lamp_ON = false;
}
RTC_alarm_call_ON = false; // limpia la bandera de llamado por interrupción de las alarmas del RTC
}
lcd.clear(); // limpia la pantalla del LCD
switch(LCD_select) { // analiza cuál pantalla ha sido seleccionada para mostrar en el LCD
case LCD_date_time: // imprime en pantalla tiempo / fecha
lcd.setCursor(0,0);
lcd.print("DATE: ");
lcd.setCursor(6,0);
print_Digits(day());
lcd.print('/');
print_Digits(month());
lcd.print('/');
lcd.print(year());
lcd.setCursor(0,1);
lcd.print("TIME: ");
lcd.setCursor(6,1);
print_Digits(hour());
lcd.print(':');
print_Digits(minute());
lcd.print(':');
print_Digits(second());
break;
case LCD_sensors: // imprime en pantalla las lecturas de los sensores DHT22 y HL-28
lcd.setCursor(0,0);
lcd.print("T:");
lcd.setCursor(2,0);
lcd.print(DHT_temperature);
lcd.write(0);
lcd.setCursor(0,1);
lcd.print("A:");
lcd.setCursor(2,1);
lcd.print(DHT_airhumidity);
lcd.print("%");
lcd.setCursor(9,1);
lcd.print("S:");
lcd.setCursor(12,1);
lcd.print(HL28_maped_value);
lcd.print("%");
break;
case LCD_ctrl_lamp: // imprime en pantalla el estado de la lámpara y la bomba de agua y permite prenderlas / apagarlas
lcd.setCursor(0,0);
lcd.write(1);
lcd.setCursor(1,0);
lcd.print(" ");
LCD_ctrl_show();
break;
case LCD_ctrl_plump:
lcd.setCursor(0,1);
lcd.write(1);
lcd.setCursor(1,1);
lcd.print(" ");
LCD_ctrl_show();
break;
case LCD_auto_lamp: // imprime en pantalla el estado de los controles automáticos y permite activarlos / desactivarlos
lcd.setCursor(0,0);
lcd.write(1);
lcd.setCursor(1,0);
lcd.print(" ");
LCD_ctrl_show();
break;
case LCD_auto_plump:
lcd.setCursor(0,1);
lcd.write(1);
lcd.setCursor(1,1);
lcd.print(" ");
LCD_ctrl_show();
break;
case LCD_lamp_cycle:
lcd.setCursor(0,0);
lcd.print("FOTO-PERIODO: ");
lcd.setCursor(0,1);
lcd.write(1);
lcd.setCursor(1,1);
lcd.print(" ");
LCD_ctrl_show();
}
}
void LCD_ctrl_show(void) { // función para mostrar el estado de los controles
switch(LCD_select) {
case LCD_ctrl_lamp:
LCD_ctrl_manual();
break;
case LCD_ctrl_plump:
LCD_ctrl_manual();
break;
case LCD_auto_lamp:
LCD_ctrl_auto();
break;
case LCD_auto_plump:
LCD_ctrl_auto();
break;
case LCD_lamp_cycle:
lcd.setCursor(2,1);
if (ctrl_lamp_cycle == false) {
lcd.print("VEGETAV. 18/6");}
else {
lcd.print("MADURAC. 12/12");}
break;
}
}
void LCD_ctrl_manual(void) {
lcd.setCursor(2,0);
lcd.print("LAMP: ");
lcd.setCursor(8,0);
if (ctrl_lamp_ON == false) {
lcd.print("OFF");}
if (ctrl_lamp_ON == true) {
lcd.print("ON");}
lcd.setCursor(2,1);
lcd.print("BOMB: ");
lcd.setCursor(8,1);
if (ctrl_plump_ON == false) {
lcd.print("OFF");}
if (ctrl_plump_ON == true) {
lcd.print("ON");}
}
void LCD_ctrl_auto(void) {
lcd.setCursor(2,0);
lcd.print("LAMP.AUTO: ");
lcd.setCursor(13,0);
if (auto_lamp_ON == false) {
lcd.print("OFF");}
if (auto_lamp_ON == true) {
lcd.print("ON");}
lcd.setCursor(2,1);
lcd.print("BOMB.AUTO: ");
lcd.setCursor(13,1);
if (auto_plump_ON == false) {
lcd.print("OFF");}
if (auto_plump_ON == true) {
lcd.print("ON");}
}
void callBack2(Task* me) { // revisa si ha sido presionada una tecla para navegar en el LCD
MD_UISwitch::keyResult_t k = keypad.read(); // lee el estado del teclado
if (k == MD_UISwitch::KEY_DOWN) { // si hay un evento de presionar tecla
switch(keypad.getKey()) { // analiza cuál tecla ha sido presionada
// utiliza las teclas arriba y abajo para cambiar las posibles pantallas en el LCD
case 'U': // arriba
if (LCD_select == LCD_date_time) { // si es la 1ra pantalla
LCD_select = LCD_lamp_cycle;} // entonces cambia a la última pantalla
else {
LCD_select--;} // sino entonces retrocede una pantalla
break;
case 'D': // abajo
if (LCD_select == LCD_lamp_cycle) { // si es la última pantalla
LCD_select = LCD_date_time;} // entonces nuestra la primera pantalla
else {
LCD_select++;} // sino entonces avanza una pantalla
break;
// utiliza la tecla select para prender / apagar la lámpara y la bomba de agua
case 'S': // select
switch (LCD_select) {
case LCD_ctrl_lamp:
if (ctrl_lamp_ON == true ){ // si la lámpara está encendida: la apaga
digitalWrite(ctrl_relay_lamp, HIGH);
ctrl_lamp_ON = false;
}
else {
digitalWrite(ctrl_relay_lamp, LOW); // sino entonces la prende
ctrl_lamp_ON = true;
}
break;
case LCD_ctrl_plump:
if (ctrl_plump_ON == true ) { // si la bomba de agua está encendida: la apaga
digitalWrite(ctrl_relay_plump, HIGH);
ctrl_plump_ON = false;
}
else {
digitalWrite(ctrl_relay_plump, LOW); // sino entonces la prende
ctrl_plump_ON = true;
}
break;
case LCD_auto_lamp:
if (auto_lamp_ON == true) { // si el control automtico de la lámpara está activado: lo activa
auto_lamp_ON = false;
}
else {
auto_lamp_ON = true; // sino entonces lo activa
}
break;
case LCD_auto_plump:
if (auto_plump_ON == true) { // si el control automtico de la bomba de agua está activado: lo activa
auto_plump_ON = false;
}
else {
auto_plump_ON = true; // sino entonces lo activa
}
break;
}
break;
// utiliza las tecla derecha e izquierda para cambiar el foto-periodo
case 'R': // derecha
if (LCD_select == LCD_lamp_cycle) {
// ctrl_lamp_cycle es una bandera que indica el foto-periodo FALSE = 18/6 y TRUE = 12/12
if (ctrl_lamp_cycle == false){ // si el foto-periodo era 18/6
ctrl_lamp_cycle = true; // ... entonces lo cambia a 12/12
//alarm2_hour = 18; // configura la ALARM_2 para finalizar el ciclo a las 6pm
alarm2_hour = 16;
alarm2_minute = 9;
}
else { // si el foto-periodo era 12/12
ctrl_lamp_cycle = false; // ... entonces los cambia a 18/6
//alarm2_hour = 0; // configura la ALARM_2 para finalizar el ciclo a las 12am
alarm2_hour = 16;
alarm2_minute = 8;
}
RTC_alarm2_set();
}
break;
case 'L': // izquierda
if (LCD_select == LCD_lamp_cycle) {
// ctrl_lamp_cycle es una bandera que indica el foto-periodo FALSE = 18/6 y TRUE = 12/12
if (ctrl_lamp_cycle == false){ // si el foto-periodo era 18/6
ctrl_lamp_cycle = true; // ... entonces lo cambia a 12/12
//alarm2_hour = 18; // configura la ALARM_2 para finalizar el ciclo a las 6pm
alarm2_hour = 16;
alarm2_minute = 9;
}
else { // si el foto-periodo era 12/12
ctrl_lamp_cycle = false; // ... entonces los cambia a 18/6
//alarm2_hour = 0; // configura la ALARM_2 para finalizar el ciclo a las 12am
alarm2_hour = 16;
alarm2_hour = 8;
}
RTC_alarm2_set();
}
break;
}
}
}
void callBack3(Task* me) { // realiza lecturas en el sensor DHT22
DHT_airhumidity = dht.readHumidity(); // lee y almacena el valor de la humedad en el aire
DHT_temperature = dht.readTemperature(); // ... de la temperatura
}
void callBack4(Task* me) { // realiza una lectura de intervalo corto en el sensor HL-28
if (ctrl_plump_ON == true){ // sólo realiza lectura de intervalo corto mientras la bomba de agua está prendida
HL28_reading(); //
HL28_compare();
}
}
void callBack5(Task* me) { // realiza una lectura de intervalo largo en el sensor HL-28
if (ctrl_plump_ON == false){ // sólo realiza lectura de intervalo largo mientras la bomba de agua está apagada
HL28_reading();
HL28_compare();
}
}
void HL28_reading(void){ // función para realizar una lectura de humedad en el sustrato y mapear el valor
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, 550, 179, 0, 100); // ubica este valor leído entre un mínimo y un máximo
/*
1016 sonda en sustrato completamente seco (sin planta)
179 sonda en sustrato completamente húmedo (sin planta) -> 100% (la bomba se desactiva al llegar a 200))
550 sonda en sustrato seco con planta -> 0% (la bomba se activa al llegar a 500)
58 -> 100% (la bomba se desactiva al llegar a 200))
1023 sonda al aire
450 sonda en paño humedo
0 sonda en agua*/
// para pruebas
/*Serial.println(HL28_analog_value); //lectura analógica
//Serial.print(HL28_maped_value); //lectura mapeada
Serial.println("%");
Serial.print("\n");*/
}
void HL28_compare(void){ // función para comparar el valor analogo obtenido con las constantes de sustrato definidas al inicio
if (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 > HL28_dry_soil){ // si el valor es mayor que sustrato seco, entonces prender bomba de agua
digitalWrite(ctrl_relay_plump, LOW);
ctrl_plump_ON = true;
}
if (HL28_analog_value < HL28_wet_soil){ // si el valor es menor que sustrato humedo, entonces apagar bomba de agua
digitalWrite(ctrl_relay_plump, HIGH);
ctrl_plump_ON = false;
}
}
}
void RTC_alarm_call(void) {
RTC_alarm_call_ON = true;
}
void RTC_alarm2_set(void) {
RTC.setAlarm(ALM2_MATCH_HOURS, alarm2_second, alarm2_minute, alarm2_hour, alarm2_day);
RTC.alarm(ALARM_2);
RTC.alarmInterrupt(ALARM_2, true);
}
void printDateTime(time_t t) {
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
}
Resumen de funcionamiento
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.
Las ideas para la siguiente versión son:
- Comunicarse con el sistema por internet utilizando el ethernet shield,
- Personalizar en tiempo real el foto-periodo y los valores de referencia de sustrato húmedo y seco.
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..
Agradecimientos a todos por la comprensión. La verdad tuve un bloqueo que me impidió hacer esto antes, así que lo siento mucho.
Espero estarle cumpliendo a la comunidad y a @Zion Lion.
Saludos. Buenos humos.
Links de utilidad:
Arduino
fritzing
GitHub alexokaban