Controlador de Irrigação Inteligente com Arduino – Rega Automática com Sensores de Clima e Luz
Componentes e Materiais
- Arduino Nano R3 – 1 unidade
- Expansor de I/O – 1 unidade
- Kit Expansor de I/O – 1 unidade
Aplicativos e Serviços Online
- Arduino IDE
Sobre o Projeto
Crie um Controlador de Irrigação Inteligente com Arduino
Irrigue seu jardim de forma inteligente com ciclos de rega dinâmicos. Pare a rega se estiver chovendo ou se choveu desde a última rega. Use o sensor de luz para detectar o nascer do sol e ajustar automaticamente os horários de início. Pare a rega se estiver muito frio.
Lista de Recursos
- Sensor de temperatura externa
- Sensor de chuva externo
- Sensor de luz externo
- Relógio em tempo real (RTC) com backup de bateria para agendamento semanal
- Armazenamento não volátil – nunca perca uma rega por falta de energia
- Detecção de nascer do sol
- Rega inteligente economiza na conta de água
- Rega antes do nascer do sol para melhor absorção
- Para rega quando está muito frio
- Reduz crescimento de fungos
- Controle de agendamento fácil

Peças Necessárias para Construir o Controlador
- Expansor de I/O
- 2x Conversor 1-Wire para I2C
- Junção 1-Wire
- Conector Óptico
- Divisor
- Arduino Nano
- Módulo Relé 4 Canais 5V DC
- Sensor de Luz TSL2561
- Sensor de Temperatura à Prova d'Água DS18B20
- Sensor de Nível de Água Infravermelho Óptico
- RTC DS3231 AT24C32 IIC de Precisão
- Display OLED I2C SSD1306 128x64
- Caixa Plástica à Prova d'Água 200x120x75mm
- Caixa Plástica à Prova d'Água 100x68x50mm
- Prensa-cabo IP68 PG7 de Nylon
- Prensa-cabo IP68 PG11 de Nylon
- Jack Keystone RJ11 de Parafuso
- Fio RJ11 4C4P 50ft
- Fio RJ11 4C4P 6ft
- Fio Header 2.54mm
- 2x Chave Micro Push Button SPST Momentânea 2 Pinos
- Fonte de Alimentação 12VDC 1A
Diagrama de Fiação

Display OLED

Pressione o botão Menu para exibir o menu e continue pressionando para percorrer as opções. O menu some automaticamente após 30 segundos de inatividade. Pressione o botão Select para executar a função desejada.
Por Que Usar o Expansor de I/O?
- Design mais simples
- Componentes prontos (off-the-shelf)
- Sem necessidade de escrever driver 1-Wire
- Sem driver DS3231 RTC
- Sem driver EEPROM
- Sem driver de display OLED
- Sem fontes para display ocupando espaço no Arduino
- Sem driver de sensor de temperatura
- Sem driver de sensor de chuva óptico
- Economiza espaço de código no Arduino; apenas 12710 bytes (39%)
- Código escrito em apenas três dias
- Fácil fiação com cabo telefônico RJ11 padrão
- Sem problemas de comprimento de cabo do sensor
- Mais barato que sistemas comerciais similares
- Fácil adaptar a requisitos individuais
- Fonte de alimentação única
Construa o Sistema
Conecte o Arduino Nano ao Expansor de I/O e programe com o código abaixo. O header de 6 pinos é a porta serial de depuração e não é necessário na instalação final.

Certifique-se de alterar os endereços ONEWIRE_TO_I2C_ROM1 e ONEWIRE_TO_I2C_ROM2 para corresponder aos endereços dos seus conversores 1-Wire para I2C.
Código Principal
/* Sketch do Expansor de I/O otimizado
* Sistema de Irrigação v1.1
*/
#include <math.h>
#include <time.h>
#include <util/crc16.h>
#include <avr/wdt.h>
#include <SoftwareSerial.h>
#include "IOExpander.h"
#define FAHRENHEIT
#define INIT_BOARD "g5w1;g11w1;g11d0,75;g12w1;g12d0,75;rsf"
#define ONEWIRE_TO_I2C_ROM1 "i4scc"
#define ONEWIRE_TO_I2C_ROM2 "i6s8f"
#define ONEWIRE_TEMPERATURE "t6s0300"
#define RTC_SENSOR "s4te"
#define I2C_EEPROM "s4tf"
#define I2C_OLED "s4t10"
#define I2C_LIGHT "s3t9;sc0"
#define OPTICAL_SENSOR "g5a"
#define BUTTON1 "g11d"
#define BUTTON2 "g12d"
#define WATER_TIME_BEFORE_SUNRISE 60
#define SUNRISE_LUX 100
#define RAIN_DETECT_LEVEL 4.0
#define DO_NOT_WATER_TEMP 4.4444 // 40°F
#define MAX_ZONES 4
#define HOUR_IN_DAY 24L
#define MIN_IN_HOUR 60L
#define SEC_IN_MIN 60L
#define SEC_IN_HOUR (MIN_IN_HOUR * SEC_IN_MIN)
#define SEC_IN_DAY (HOUR_IN_DAY * SEC_IN_HOUR)
#define DAYS_IN_WEEK 7
#define SEC_IN_WEEK (SEC_IN_DAY * DAYS_IN_WEEK)
#define SUN 0x01
#define MON 0x02
#define TUE 0x04
#define WED 0x08
#define THR 0x10
#define FRI 0x20
#define SAT 0x40
#define EVERYDAY (SUN | MON | TUE | WED | THR | FRI | SAT)
#define SUNRISE 0x80
#define MENU_OPTIONS 9
#define MENU_TIME 30
#define OFF 0
#define ON 1
#define STATE_ON_OFF 0x01
//#define SERIAL_DEBUG
#ifdef SERIAL_DEBUG
SoftwareSerial swSerial(8,7);
#endif
char weekday[][4] = {"SUN","MON","TUE","WED","THU","FRI","SAT"};
char menu[][13] = {"Next","Water","Reset","Clock Min +","Clock Min -","Clock Hour +","Clock Hour -","Sunrise","ON/OFF"};
enum { MENU_NEXT, MENU_WATER, MENU_RESET, MENU_CLOCK_MIN_PLUS, MENU_CLOCK_MIN_MINUS, MENU_CLOCK_HOUR_PLUS, MENU_CLOCK_HOUR_MINUS, MENU_SUNRISE, MENU_ON_OFF };
typedef struct { char description[16]; uint8_t relay; } ZONE;
typedef struct { uint8_t zone; uint8_t days; int8_t hour; int8_t min; uint8_t duration; } SCHEDULE;
typedef struct { time_t sunrise_time; time_t last_water_time; uint8_t water_schedule; uint8_t water_duration; uint8_t rain[MAX_ZONES]; uint8_t state; uint8_t crc; } NVRAM;
enum { ZONE1, ZONE2, ZONE3, ZONE4 };
enum { RELAY1 = 1, RELAY2, RELAY3, RELAY4 };
ZONE zone[] = {
{"Front Right", RELAY1},
{"Front Left", RELAY2},
{"Bushes", RELAY3},
{"Left Side", RELAY4},
};
SCHEDULE schedule[] = {
{ZONE1, SUNRISE | EVERYDAY, -1, 0, 4},
{ZONE2, EVERYDAY, 6, 15, 5},
{ZONE3, EVERYDAY, 6, 0, 10},
{ZONE4, EVERYDAY, 6, 10, 6},
};
NVRAM nvram;
bool update_nvram = false;
uint8_t crc8(uint8_t* data, uint16_t length) {
uint8_t crc = 0;
while (length--) crc = _crc8_ccitt_update(crc, *data++);
return crc;
}
int led = 13;
bool init_oled = true;
bool update_oled = true;
bool init_board = true;
#ifdef FAHRENHEIT
#define C2F(temp) CelsiusToFahrenheit(temp)
float CelsiusToFahrenheit(float celsius) { return ((celsius * 9) / 5) + 32; }
#else
#define C2F(temp) (temp)
#endif
void SerialPrint(const char* str, float decimal, char error) {
Serial.print(str);
if (error) Serial.print(F("NA")); else Serial.print(decimal, 1);
}
time_t NextScheduleTime(time_t last_time, uint8_t* next_schedule) {
time_t next_time = -1, clk_time;
uint8_t i; tm clk; uint8_t wday;
for (i = 0; i < sizeof(schedule)/sizeof(SCHEDULE); i++) {
if (schedule[i].days & SUNRISE) {
clk_time = nvram.sunrise_time + schedule[i].hour * SEC_IN_HOUR + schedule[i].min * SEC_IN_MIN;
localtime_r(&clk_time, &clk);
} else {
localtime_r(&last_time, &clk);
clk.tm_hour = schedule[i].hour; clk.tm_min = schedule[i].min; clk.tm_sec = 0;
clk_time = mktime(&clk);
}
wday = clk.tm_wday;
while (clk_time <= last_time || !(schedule[i].days & (1 << wday))) {
clk_time += SEC_IN_DAY;
if (++wday > 6) wday = 0;
if (wday == clk.tm_wday) break;
}
if (clk_time < next_time) { next_time = clk_time; *next_schedule = i; }
}
return next_time;
}
void StartScheduleTime(time_t start_time, uint8_t start_schedule) {
uint8_t i;
nvram.last_water_time = start_time;
nvram.water_schedule = start_schedule;
nvram.water_duration = schedule[start_schedule].duration + 1;
update_nvram = true;
i = schedule[start_schedule].zone;
if (i < MAX_ZONES && nvram.rain[i] > 0) {
if (nvram.rain[i] > nvram.water_duration) nvram.water_duration = 0;
else nvram.water_duration -= nvram.rain[i];
nvram.rain[i] = 0;
}
}
void WaterScheduleTime() {
uint8_t i;
nvram.water_duration--;
update_nvram = true;
i = schedule[nvram.water_schedule].zone;
if (i < MAX_ZONES) {
Serial.print("r"); Serial.print(zone[i].relay);
if (nvram.water_duration > 0) Serial.println("o"); else Serial.println("f");
SerialReadUntilDone();
}
}
void setup() {
Serial.begin(115200);
#ifdef SERIAL_DEBUG
swSerial.begin(115200);
#endif
pinMode(led, OUTPUT);
wdt_enable(WDTO_8S);
}
void loop() {
static tm rtc; tm clk, sunrise_clk; time_t rtc_time, clk_time;
static time_t next_time; static uint8_t last_sec, last_min;
bool error_rtc, error_light, error_temp;
static long lux = 0; static float temp, rain;
static uint8_t sunrise_counter = MIN_IN_HOUR; static bool check_sunrise = false;
uint8_t i; static bool read_nvram = true; static time_t water_time; static uint8_t water_schedule;
uint8_t sz, wday; long n; bool button1, button2;
static int8_t menu_select = -1; static time_t menu_time = 0;
Serial.println();
if (SerialReadUntilDone()) {
if (init_board) { SerialCmdDone(INIT_BOARD); init_board = false; }
if (init_oled) {
if (SerialCmdNoError(ONEWIRE_TO_I2C_ROM1)) { SerialCmdDone(I2C_OLED ";si;sc;sd"); init_oled = false; }
}
if (SerialCmdDone(RTC_SENSOR)) {
error_rtc = !SerialReadTime(&rtc);
if (!error_rtc) { clk = rtc; rtc_time = mktime(&clk); localtime_r(&rtc_time, &rtc); }
if (read_nvram) {
if (SerialCmdNoError(I2C_EEPROM)) {
SerialReadEEPROM((uint8_t*)&nvram, 0, sizeof(nvram));
if (nvram.crc != crc8((uint8_t*)&nvram, sizeof(nvram)-sizeof(uint8_t))) {
memset(&nvram, 0, sizeof(nvram));
clk = rtc; clk.tm_hour = 6; clk.tm_min = 0; clk.tm_sec = 0;
nvram.sunrise_time = mktime(&clk);
if (nvram.sunrise_time < rtc_time) nvram.sunrise_time += SEC_IN_DAY;
update_nvram = true;
}
if (rtc_time - nvram.last_water_time > SEC_IN_WEEK) nvram.last_water_time = rtc_time - SEC_IN_WEEK;
if (rtc_time > nvram.sunrise_time) {
localtime_r(&nvram.sunrise_time, &sunrise_clk);
clk = rtc; clk.tm_hour = sunrise_clk.tm_hour; clk.tm_min = sunrise_clk.tm_min; clk.tm_sec = sunrise_clk.tm_sec;
nvram.sunrise_time = mktime(&clk);
if (nvram.sunrise_time < rtc_time) nvram.sunrise_time += SEC_IN_DAY;
}
if (nvram.water_duration) { nvram.water_duration++; water_time = nvram.last_water_time; }
else { clk_time = nvram.last_water_time ? nvram.last_water_time : rtc_time; water_time = NextScheduleTime(clk_time, &water_schedule); }
read_nvram = false;
}
}
}
if (rtc.tm_min != last_min) {
error_temp = !SerialCmdNoError(ONEWIRE_TEMPERATURE); if (!error_temp) SerialCmdDone("tt");
error_light = !SerialCmdNoError(ONEWIRE_TO_I2C_ROM2 ";oo0");
if (!error_light) { SerialCmdDone(I2C_LIGHT); SerialCmd("sr"); SerialReadInt(&lux); SerialReadUntilDone(); }
if (SerialCmd(OPTICAL_SENSOR)) { SerialReadFloat(&rain); SerialReadUntilDone(); }
error_temp = !SerialCmdNoError(ONEWIRE_TEMPERATURE);
if (!error_temp) { SerialCmd("tr"); SerialReadFloat(&temp); SerialReadUntilDone(); }
if (lux < SUNRISE_LUX) { if (sunrise_counter > 0) sunrise_counter--; else check_sunrise = true; }
else { if (sunrise_counter < MIN_IN_HOUR) { sunrise_counter++; if (check_sunrise && sunrise_counter == MIN_IN_HOUR) { nvram.sunrise_time = rtc_time + (SEC_IN_DAY - SEC_IN_HOUR); check_sunrise = false; update_nvram = true; } } }
if (rain <= RAIN_DETECT_LEVEL) { for (i=0;i= MENU_OPTIONS) menu_select = 0; } menu_time = rtc_time; update_oled = true; }
if (menu_select >= 0) {
button2 = SerialReadButton(BUTTON2);
if (button2) {
clk_time = rtc_time;
switch(menu_select) {
case MENU_NEXT: case MENU_RESET:
if (nvram.water_duration) { nvram.water_duration = 1; WaterScheduleTime(); }
water_time = NextScheduleTime((menu_select == MENU_NEXT) ? water_time : rtc_time, &water_schedule); break;
case MENU_WATER: StartScheduleTime(water_time, water_schedule); WaterScheduleTime(); break;
case MENU_CLOCK_MIN_PLUS: clk_time += SEC_IN_MIN; break;
case MENU_CLOCK_MIN_MINUS: clk_time -= SEC_IN_MIN; break;
case MENU_CLOCK_HOUR_PLUS: clk_time += SEC_IN_HOUR; break;
case MENU_CLOCK_HOUR_MINUS: clk_time -= SEC_IN_HOUR; break;
case MENU_ON_OFF: nvram.state ^= STATE_ON_OFF; update_nvram = true; break;
}
if (clk_time != rtc_time) { if (SerialCmdDone(RTC_SENSOR)) { localtime_r(&clk_time, &clk); SerialWriteTime(&clk); rtc_time = clk_time; } }
menu_time = rtc_time; update_oled = true;
}
}
if (menu_select >= 0 && rtc_time - menu_time > MENU_TIME) { menu_select = -1; update_oled = true; }
if (update_oled) {
if (SerialCmdNoError(ONEWIRE_TO_I2C_ROM1)) {
Serial.print("st10;so1;sc;sf0;sa0;sd0,0,\"");
if (nvram.water_duration) Serial.print(nvram.water_duration);
else { if ((nvram.state & STATE_ON_OFF) == OFF) Serial.print("OFF"); else if (rain <= RAIN_DETECT_LEVEL) Serial.print("Rain"); else if (temp <= DO_NOT_WATER_TEMP) Serial.print("Cold"); else Serial.print("v1.1"); }
Serial.print("\";sf2;sa1;sd75,0,\"");
if (menu_select == 7) { clk_time = nvram.sunrise_time; localtime_r(&clk_time, &clk); } else clk = rtc;
Serial.print(clk.tm_hour - ((clk.tm_hour>12)?12:0)); Serial.print(":"); if (clk.tm_min < 10) Serial.print("0"); Serial.print(clk.tm_min); Serial.println("\""); SerialReadUntilDone();
Serial.print("sf1;sa0;sd79,8,\""); Serial.print((clk.tm_hour>12)?"PM":"AM"); Serial.print("\";sf0;sa1;sd127,1,\""); Serial.print(weekday[clk.tm_wday]); Serial.print("\";sd127,13,\""); Serial.print(clk.tm_mon+1); Serial.print("/"); Serial.print(clk.tm_mday); Serial.println("\""); SerialReadUntilDone();
Serial.print("sf0;sa0;sd1,36,\""); i = schedule[water_schedule].zone; if (i < MAX_ZONES) Serial.print(zone[i].description); localtime_r(&water_time, &clk);
if (water_time - rtc_time > SEC_IN_DAY) { Serial.print("\";sa1;sd126,36,\""); Serial.print(clk.tm_mon+1); Serial.print("/"); Serial.print(clk.tm_mday); Serial.print(" "); Serial.print(clk.tm_hour-((clk.tm_hour>12)?12:0)); Serial.print(":"); if (clk.tm_min<10) Serial.print("0"); Serial.print(clk.tm_min); Serial.print(" "); }
else { Serial.print("\";sf1;sa1;sd111,30,\""); Serial.print(clk.tm_hour-((clk.tm_hour>12)?12:0)); Serial.print(":"); if (clk.tm_min<10) Serial.print("0"); Serial.print(clk.tm_min); Serial.print("\";sf0;sd126,36,\""); }
Serial.print((clk.tm_hour>12)?"PM":"AM"); if (nvram.water_duration) Serial.print("\";so2;sc0,29,128,19\""); Serial.println(); SerialReadUntilDone();
if (menu_select == -1) { SerialPrint("\";so1;sa2;sd63,52,\"", C2F(temp), error_temp); if (!error_temp) Serial.print("\",248,\"" #ifdef FAHRENHEIT "F" #else "C" #endif ); Serial.print(" / "); Serial.print(lux); }
else { Serial.print("\";so0;sc0,51,128,14;sf0;sa2;sd63,52,\""); if (menu_select == MENU_ON_OFF) Serial.print((nvram.state & STATE_ON_OFF) ? "OFF" : "ON"); else Serial.print(menu[menu_select]); }
Serial.println("\";sd\""); SerialReadUntilDone(); update_oled = false;
} else init_oled = true;
}
if (update_nvram) { if (SerialCmdNoError(I2C_EEPROM)) { nvram.crc = crc8((uint8_t*)&nvram, sizeof(nvram)-sizeof(uint8_t)); SerialWriteEEPROM((uint8_t*)&nvram, 0, sizeof(nvram)); update_nvram = false; } }
delay(50);
} else { digitalWrite(led, HIGH); delay(500); digitalWrite(led, LOW); delay(500); init_board = true; init_oled = true; }
wdt_reset();
}
Nota: Se usar a porta USB para programar o Arduino Nano, desconecte-o do Expansor de I/O, pois ambos usam a mesma porta serial. Para depuração, use a porta ICSP para programar o ATmega328P. Para habilitar a porta de depuração de software, descomente SERIAL_DEBUG.
O divisor deve ser configurado para isolar a linha de dados do sensor infravermelho óptico da linha do sensor remoto 1-Wire. Solde um resistor zero ohm 0603 em R2.

Fure um orifício de 7/16" na caixa pequena e 11/16" na maior no lado direito para os prensa-cabos PG7 e PG11. Use uma ferramenta rotativa para alargar até ajustar. O PG7 alimenta os sensores remotos e o PG11 os fios de 12VDC, 24VAC, manifold e cabo RJ11.

Conecte as chaves micro push button SPST ao terminal de parafuso RJ11. Use tubo termorretrátil para isolar os contatos.

Conecte todos os fios e monte tudo na caixa grande. O cabo RJ11 de 50ft para sensores remotos deve passar pelo prensa-cabo PG11 sem necessidade de corte.

Fure um orifício de 9/16" no topo da caixa pequena para o sensor de água infravermelho óptico. A caixa pequena é apertada, mas se os componentes forem dispostos na orientação recomendada, cabem. Encurtar os fios RJ11 ajuda. Após montar, adicione cola marítima na arruela do prensa-cabo antes de apertar a porca para melhor vedação.

Instale a caixa de sensores remotos do lado externo, elevada no lado leste da casa, com o sensor óptico de água e sensor de luz apontados para o céu sem obstruções.

Fure orifícios de 1/4" no topo, meio e base da caixa grande para os botões. Use ferramenta rotativa para ajustar.

Teste o sistema e verifique se tudo funciona. Para testar relés e sensores, desconecte o Arduino do Expansor e conecte diretamente ao computador para controle manual. Após verificar, monte tudo na caixa usando fita dupla face e espuma de embalagem para fixar as placas, e aproveite os benefícios e economia do seu Controlador de Irrigação Inteligente.
Vídeo em Operação
Atualização 12/09/2019
Lançada v1.1 que corrigiu problema de inicialização se o sistema ficasse sem energia por vários dias.
Atualização 02/10/2019
Ao conectar o 1-Wire para I2C ao DS3231 e depois ao OLED SSD1306, haverá três pull-ups diferentes nas linhas SDA e SCL (conforme imagem abaixo circulado). Isso resulta em pull-up efetivo de 4.7k / 3 = 1.56k, muito forte, causando corrupções aleatórias na tela.

Como o DS3231 usa um resistor pack compartilhado, remova os outros pull-ups:
- 1-Wire para I2C: R3 e R4.
- OLED SSD1306: R6 e R7.
Código
- Controlador de Irrigação Inteligente
Controlador de Irrigação Inteligente C/C++
Use seu Arduino para irrigar seu jardim ou quintal de forma inteligente.
/* IO Expander sketch optimized * Irrigation System v1.1 * */
#include <math.h> #include <time.h> #include <util/crc16.h> #include <avr/wdt.h> #include <SoftwareSerial.h> #include "IOExpander.h"
#define FAHRENHEIT
#define INIT_BOARD "g5w1;g11w1;g11d0,75;g12w1;g12d0,75;rsf"
#define ONEWIRE_TO_I2C_ROM1 "i4scc"
#define ONEWIRE_TO_I2C_ROM2 "i6s8f"
#define ONEWIRE_TEMPERATURE "t6s0300"
#define RTC_SENSOR "s4te"
#define I2C_EEPROM "s4tf"
#define I2C_OLED "s4t10"
#define I2C_LIGHT "s3t9;sc0"
#define OPTICAL_SENSOR "g5a"
#define BUTTON1 "g11d"
#define BUTTON2 "g12d"
#define WATER_TIME_BEFORE_SUNRISE 60
#define SUNRISE_LUX 100
#define RAIN_DETECT_LEVEL 4.0
#define DO_NOT_WATER_TEMP 4.4444 // 40F
#define MAX_ZONES 4
#define HOUR_IN_DAY 24L
#define MIN_IN_HOUR 60L
#define SEC_IN_MIN 60L
#define SEC_IN_HOUR (MIN_IN_HOUR * SEC_IN_MIN)
#define SEC_IN_DAY (HOUR_IN_DAY * SEC_IN_HOUR)
#define DAYS_IN_WEEK 7
#define SEC_IN_WEEK (SEC_IN_DAY * DAYS_IN_WEEK)
#define SUN 0x01
#define MON 0x02
#define TUE 0x04
#define WED 0x08
#define THR 0x10
#define FRI 0x20
#define SAT 0x40
#define EVERYDAY (SUN | MON | TUE | WED | THR | FRI | SAT)
#define SUNRISE 0x80
#define MENU_OPTIONS 9
#define MENU_TIME 30
#define OFF 0
#define ON 1
#define STATE_ON_OFF 0x01
//#define SERIAL_DEBUG
#ifdef SERIAL_DEBUG
SoftwareSerial swSerial(8,7);
#endif
char weekday[][4] = {"SUN","MON","TUE","WED","THU","FRI","SAT"};
char menu[][13] = {"Next","Water","Reset","Clock Min +","Clock Min -","Clock Hour +","Clock Hour -","Sunrise","ON/OFF"};
enum { MENU_NEXT, MENU_WATER, MENU_RESET, MENU_CLOCK_MIN_PLUS, MENU_CLOCK_MIN_MINUS, MENU_CLOCK_HOUR_PLUS, MENU_CLOCK_HOUR_MINUS, MENU_SUNRISE, MENU_ON_OFF };
typedef struct { char description[16]; uint8_t relay; } ZONE;
typedef struct { uint8_t zone; uint8_t days; int8_t hour; int8_t min; uint8_t duration; } SCHEDULE;
typedef struct { time_t sunrise_time; time_t last_water_time; uint8_t water_schedule; uint8_t water_duration; uint8_t rain[MAX_ZONES]; uint8_t state; uint8_t crc; } NVRAM;
enum { ZONE1, ZONE2, ZONE3, ZONE4 };
enum { RELAY1 = 1, RELAY2, RELAY3, RELAY4 };
ZONE zone[] = { {"Front Right", RELAY1}, {"Front Left", RELAY2}, {"Bushes", RELAY3}, {"Left Side", RELAY4} };
SCHEDULE schedule[] = { {ZONE1, SUNRISE | EVERYDAY, -1, 0, 4}, {ZONE2, EVERYDAY, 6, 15, 5}, {ZONE3, EVERYDAY, 6, 0, 10}, {ZONE4, EVERYDAY, 6, 10, 6} };
NVRAM nvram; bool update_nvram = false;
uint8_t crc8(uint8_t* data, uint16_t length) { uint8_t crc = 0; while (length--) crc = _crc8_ccitt_update(crc, *data++); return crc; }
int led = 13; bool init_oled = true; bool update_oled = true; bool init_board = true;
#ifdef FAHRENHEIT
#define C2F(temp) CelsiusToFahrenheit(temp)
float CelsiusToFahrenheit(float celsius) { return ((celsius * 9) / 5) + 32; }
#else
#define C2F(temp) (temp)
#endif
void SerialPrint(const char* str, float decimal, char error) { Serial.print(str); if (error) Serial.print(F("NA")); else Serial.print(decimal, 1); }
time_t NextScheduleTime(time_t last_time, uint8_t* next_schedule) { time_t next_time = -1, clk_time; uint8_t i; tm clk; uint8_t wday; for (i = 0; i < sizeof(schedule)/sizeof(SCHEDULE); i++) { if (schedule[i].days & SUNRISE) { clk_time = nvram.sunrise_time + schedule[i].hour * SEC_IN_HOUR + schedule[i].min * SEC_IN_MIN; localtime_r(&clk_time, &clk); } else { localtime_r(&last_time, &clk); clk.tm_hour = schedule[i].hour; clk.tm_min = schedule[i].min; clk.tm_sec = 0; clk_time = mktime(&clk); } wday = clk.tm_wday; while (clk_time <= last_time || !(schedule[i].days & (1 << wday))) { clk_time += SEC_IN_DAY; if (++wday > 6) wday = 0; if (wday == clk.tm_wday) break; } if (clk_time < next_time) { next_time = clk_time; *next_schedule = i; } } return next_time; }
void StartScheduleTime(time_t start_time, uint8_t start_schedule) { uint8_t i; nvram.last_water_time = start_time; nvram.water_schedule = start_schedule; nvram.water_duration = schedule[start_schedule].duration+1; update_nvram = true; i = schedule[start_schedule].zone; if (i < MAX_ZONES && nvram.rain[i] > 0) { if (nvram.rain[i] > nvram.water_duration) nvram.water_duration = 0; else nvram.water_duration -= nvram.rain[i]; nvram.rain[i] = 0; } }
void WaterScheduleTime() { uint8_t i; nvram.water_duration--; update_nvram = true; i = schedule[nvram.water_schedule].zone; if (i < MAX_ZONES) { Serial.print("r"); Serial.print(zone[i].relay); if (nvram.water_duration > 0) Serial.println("o"); else Serial.println("f"); SerialReadUntilDone(); } }
void setup() { Serial.begin(115200); #ifdef SERIAL_DEBUG swSerial.begin(115200); #endif pinMode(led, OUTPUT); wdt_enable(WDTO_8S); }
void loop() { static tm rtc; tm clk, sunrise_clk; time_t rtc_time, clk_time; static time_t next_time; static uint8_t last_sec, last_min; bool error_rtc, error_light, error_temp; static long lux = 0; static float temp, rain; static uint8_t sunrise_counter = MIN_IN_HOUR; static bool check_sunrise = false; uint8_t i; static bool read_nvram = true; static time_t water_time; static uint8_t water_schedule; uint8_t sz, wday; long n; bool button1, button2; static int8_t menu_select = -1; static time_t menu_time = 0; Serial.println(); if (SerialReadUntilDone()) { if (init_board) { SerialCmdDone(INIT_BOARD); init_board = false; } if (init_oled) { if (SerialCmdNoError(ONEWIRE_TO_I2C_ROM1)) { SerialCmdDone(I2C_OLED ";si;sc;sd"); init_oled = false; } } if (SerialCmdDone(RTC_SENSOR)) { error_rtc = !SerialReadTime(&rtc); if (!error_rtc) { clk = rtc; rtc_time = mktime(&clk); localtime_r(&rtc_time, &rtc); } if (read_nvram) { if (SerialCmdNoError(I2C_EEPROM)) { SerialReadEEPROM((uint8_t*)&nvram, 0, sizeof(nvram)); if (nvram.crc != crc8((uint8_t*)&nvram, sizeof(nvram)-sizeof(uint8_t))) { memset(&nvram, 0, sizeof(nvram)); clk = rtc; clk.tm_hour = 6; clk.tm_min = 0; clk.tm_sec = 0; nvram.sunrise_time = mktime(&clk); if (nvram.sunrise_time < rtc_time) nvram.sunrise_time += SEC_IN_DAY; update_nvram = true; } if (rtc_time - nvram.last_water_time > SEC_IN_WEEK) nvram.last_water_time = rtc_time - SEC_IN_WEEK; if (rtc_time > nvram.sunrise_time) { localtime_r(&nvram.sunrise_time, &sunrise_clk); clk = rtc; clk.tm_hour = sunrise_clk.tm_hour; clk.tm_min = sunrise_clk.tm_min; clk.tm_sec = sunrise_clk.tm_sec; nvram.sunrise_time = mktime(&clk); if (nvram.sunrise_time < rtc_time) nvram.sunrise_time += SEC_IN_DAY; } if (nvram.water_duration) { nvram.water_duration++; water_time = nvram.last_water_time; } else { clk_time = nvram.last_water_time ? nvram.last_water_time : rtc_time; water_time = NextScheduleTime(clk_time, &water_schedule); } read_nvram = false; } } } if (rtc.tm_min != last_min) { error_temp = !SerialCmdNoError(ONEWIRE_TEMPERATURE); if (!error_temp) SerialCmdDone("tt"); error_light = !SerialCmdNoError(ONEWIRE_TO_I2C_ROM2 ";oo0"); if (!error_light) { SerialCmdDone(I2C_LIGHT); SerialCmd("sr"); SerialReadInt(&lux); SerialReadUntilDone(); } if (SerialCmd(OPTICAL_SENSOR)) { SerialReadFloat(&rain); SerialReadUntilDone(); } error_temp = !SerialCmdNoError(ONEWIRE_TEMPERATURE); if (!error_temp) { SerialCmd("tr"); SerialReadFloat(&temp); SerialReadUntilDone(); } if (lux < SUNRISE_LUX) { if (sunrise_counter > 0) sunrise_counter--; else check_sunrise = true; } else { if (sunrise_counter < MIN_IN_HOUR) { sunrise_counter++; if (check_sunrise && sunrise_counter == MIN_IN_HOUR) { nvram.sunrise_time = rtc_time + (SEC_IN_DAY - SEC_IN_HOUR); check_sunrise = false; update_nvram = true; } } } if (rain <= RAIN_DETECT_LEVEL) { for (i = 0; i < MAX_ZONES; i++) if (nvram.rain[i] < 255) nvram.rain[i]++; update_nvram = true; } if (menu_select == -1 && !nvram.water_duration) { while (water_time + (schedule[water_schedule].duration * SEC_IN_MIN) < rtc_time) water_time = NextScheduleTime(water_time, &water_schedule); if (water_time <= rtc_time) { StartScheduleTime(water_time, water_schedule); if (temp <= DO_NOT_WATER_TEMP || (nvram.state & STATE_ON_OFF) == OFF) nvram.water_duration = 0; } } if (nvram.water_duration) { WaterScheduleTime(); if (!nvram.water_duration) water_time = NextScheduleTime(water_time, &water_schedule); } last_min = rtc.tm_min; update_oled = true; } button1 = SerialReadButton(BUTTON1); if (button1) { if (menu_select == -1) menu_select = 0; else { if (++menu_select >= MENU_OPTIONS) menu_select = 0; } menu_time = rtc_time; update_oled = true; } if (menu_select >= 0) { button2 = SerialReadButton(BUTTON2); if (button2) { clk_time = rtc_time; switch(menu_select) { case MENU_NEXT: case MENU_RESET: if (nvram.water_duration) { nvram.water_duration = 1; WaterScheduleTime(); } water_time = NextScheduleTime((menu_select == MENU_NEXT) ? water_time : rtc_time, &water_schedule); break; case MENU_WATER: StartScheduleTime(water_time, water_schedule); WaterScheduleTime(); break; case MENU_CLOCK_MIN_PLUS: clk_time += SEC_IN_MIN; break; case MENU_CLOCK_MIN_MINUS: clk_time -= SEC_IN_MIN; break; case MENU_CLOCK_HOUR_PLUS: clk_time += SEC_IN_HOUR; break; case MENU_CLOCK_HOUR_MINUS: clk_time -= SEC_IN_HOUR; break; case MENU_ON_OFF: nvram.state ^= STATE_ON_OFF; update_nvram = true; break; } if (clk_time != rtc_time) { if (SerialCmdDone(RTC_SENSOR)) { localtime_r(&clk_time, &clk); SerialWriteTime(&clk); rtc_time = clk_time; } } menu_time = rtc_time; update_oled = true; } } if (menu_select >= 0 && rtc_time - menu_time > MENU_TIME) { menu_select = -1; update_oled = true; } if (update_oled) { if (SerialCmdNoError(ONEWIRE_TO_I2C_ROM1)) { Serial.print("st10;so1;sc;sf0;sa0;sd0,0,\""); if (nvram.water_duration) Serial.print(nvram.water_duration); else { if ((nvram.state & STATE_ON_OFF) == OFF) Serial.print("OFF"); else if (rain <= RAIN_DETECT_LEVEL) Serial.print("Rain"); else if (temp <= DO_NOT_WATER_TEMP) Serial.print("Cold"); else Serial.print("v1.1"); } Serial.print("\";sf2;sa1;sd75,0,\""); if (menu_select == 7) { clk_time = nvram.sunrise_time; localtime_r(&clk_time, &clk); } else clk = rtc; Serial.print(clk.tm_hour-((clk.tm_hour>12)?12:0)); Serial.print(":"); if (clk.tm_min < 10) Serial.print("0"); Serial.print(clk.tm_min); Serial.println("\""); SerialReadUntilDone(); Serial.print("sf1;sa0;sd79,8,\""); Serial.print((clk.tm_hour>12)?"PM":"AM"); Serial.print("\";sf0;sa1;sd127,1,\""); Serial.print(weekday[clk.tm_wday]); Serial.print("\";sd127,13,\""); Serial.print(clk.tm_mon+1); Serial.print("/"); Serial.print(clk.tm_mday); Serial.println("\""); SerialReadUntilDone(); Serial.print("sf0;sa0;sd1,36,\""); i = schedule[water_schedule].zone; if (i < MAX_ZONES) Serial.print(zone[i].description); localtime_r(&water_time, &clk); if (water_time - rtc_time > SEC_IN_DAY) { Serial.print("\";sa1;sd126,36,\""); Serial.print(clk.tm_mon+1); Serial.print("/"); Serial.print(clk.tm_mday); Serial.print(" "); Serial.print(clk.tm_hour-((clk.tm_hour>12)?12:0)); Serial.print(":"); if (clk.tm_min < 10) Serial.print("0"); Serial.print(clk.tm_min); Serial.print(" "); } else { Serial.print("\";sf1;sa1;sd111,30,\""); Serial.print(clk.tm_hour-((clk.tm_hour>12)?12:0)); Serial.print(":"); if (clk.tm_min < 10) Serial.print("0"); Serial.print(clk.tm_min); Serial.print("\";sf0;sd126,36,\""); } Serial.print((clk.tm_hour>12)?"PM":"AM"); if (nvram.water_duration) Serial.print("\";so2;sc0,29,128,19\""); Serial.println(); SerialReadUntilDone(); if (menu_select == -1) { SerialPrint("\";so1;sa2;sd63,52,\"", C2F(temp), error_temp); if (!error_temp) Serial.print("\",248,\"" #ifdef FAHRENHEIT "F" #else "C" #endif ); Serial.print(" / "); Serial.print(lux); } else { Serial.print("\";so0;sc0,51,128,14;sf0;sa2;sd63,52,\""); if (menu_select == MENU_ON_OFF) Serial.print((nvram.state & STATE_ON_OFF) ? "OFF" : "ON"); else Serial.print(menu[menu_select]); } Serial.println("\";sd\""); SerialReadUntilDone(); update_oled = false; } else init_oled = true; } if (update_nvram) { if (SerialCmdNoError(I2C_EEPROM)) { nvram.crc = crc8((uint8_t*)&nvram, sizeof(nvram)-sizeof(uint8_t)); SerialWriteEEPROM((uint8_t*)&nvram, 0, sizeof(nvram)); update_nvram = false; } } delay(50); } else { digitalWrite(led, HIGH); delay(500); digitalWrite(led, LOW); delay(500); init_board = true; init_oled = true; } wdt_reset(); }
Esquemático
Irrigue seu quintal ou jardim de forma inteligente.

Processo de manufatura
- Construindo Sistemas de Smart Farming Open-Source: Capacitando Agricultores com IoT Acessível e Interoperável
- ECM3532: Primeira Placa de Sensor IA Integrada Ultra-Baixo Consumo da Eta Compute Acelera Desenvolvimento Edge
- Construa um Controlador Doméstico Inteligente 433 MHz com Sensorflare e Raspberry Pi
- Construa Monitor de Temperatura Doméstico com Raspberry Pi, MCP9808, InfluxDB e Grafana
- Lixeira Inteligente: Solução IoT Celular para Gestão Automatizada de Resíduos
- Bartender Inteligente: Construa sua Própria Estação de Coquetéis com Raspberry Pi
- Sensor de Emoção & EEG: Hardware de Baixo Custo para Detecção de Emoções em Tempo Real
- Controlador de Irrigação Inteligente com Windows 10 IoT, Raspberry Pi e Sensores de Umidade XBee
- Sensor de Deformação Ultra-Sensível e Durável Revoluciona Têxteis Inteligentes
- Sensor Ultra-Fino Revolucionário Melhora Funcionalidade de Lentes de Contato Inteligentes