#include <WiFi.h>
#include <time.h>
#include <U8g2lib.h>
#include <Keypad.h>
#include <Preferences.h>
#define DISPLAY_WOKWI // comente para usar o SSD1315
int ultimoMinutoDisparo = -1;
#ifdef DISPLAY_WOKWI
// simulação
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);
#else
// SSD1315 SPI real (4 fios)
U8G2_SSD1306_128X64_NONAME_F_SW_SPI u8g2(
U8G2_R0,
/* clock=*/ 8, // SCK
/* data=*/ 9, // SDA / MOSI
/* cs=*/ U8X8_PIN_NONE,
/* dc=*/ U8X8_PIN_NONE,
/* reset=*/ U8X8_PIN_NONE
);
#endif
// =====================
// PINOS
// =====================
#define PIN_RELE 18
// =====================
// WIFI
// =====================
const char* ssid = "Wokwi-GUEST";
const char* password = "";
// =====================
// NTP (UTC-3)
// =====================
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = -3 * 3600;
const int daylightOffset_sec = 0;
// =====================
// TECLADO 4x4
// =====================
const byte ROWS = 4;
const byte COLS = 4;
char keys[ROWS][COLS] = {
{'1','2','3','A'},
{'4','5','6','B'},
{'7','8','9','C'},
{'*','0','#','D'}
};
byte rowPins[ROWS] = {33, 34, 35, 36};
byte colPins[COLS] = {37, 38, 39, 40};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
// =====================
// NVS
// =====================
Preferences prefs;
// =====================
// CONFIG
// =====================
int horaIrriga = 9;
int minutoIrriga = 0;
int duracaoMin = 15;
// =====================
// CONTROLE
// =====================
bool irrigando = false;
bool modoManual = false;
unsigned long tInicioIrrigacao = 0;
// =====================
// UI
// =====================
enum Tela {
TELA_HOME,
TELA_MENU,
TELA_SET_HORA,
TELA_SET_DURACAO
};
Tela telaAtual = TELA_HOME;
String buffer = "";
// =====================
// ANIMAÇÃO
// =====================
unsigned long tAnim = 0;
int animFrame = 0;
// Bitmap simples de gota (8x8)
const uint8_t gota[] PROGMEM = {
0b00110000,
0b01111000,
0b11111100,
0b11111100,
0b01111000,
0b00110000,
0b00000000,
0b00000000
};
// =====================
// RELÉ
// =====================
void ligarIrrigacao() {
digitalWrite(PIN_RELE, HIGH);
}
void desligarIrrigacao() {
digitalWrite(PIN_RELE, LOW);
}
// =====================
// NVS
// =====================
void salvarConfig() {
prefs.begin("irrigacao", false);
prefs.putInt("hora", horaIrriga);
prefs.putInt("min", minutoIrriga);
prefs.putInt("dur", duracaoMin);
prefs.end();
}
void carregarConfig() {
prefs.begin("irrigacao", true);
horaIrriga = prefs.getInt("hora", 9);
minutoIrriga = prefs.getInt("min", 0);
duracaoMin = prefs.getInt("dur", 15);
prefs.end();
}
// =====================
// WIFI + NTP
// =====================
void conectarWiFi() {
WiFi.begin(ssid, password);
unsigned long t0 = millis();
while (WiFi.status() != WL_CONNECTED && millis() - t0 < 12000) {
delay(300);
}
}
void iniciarNTP() {
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
}
bool pegarHora(tm &t) {
return getLocalTime(&t);
}
// =====================
// LOGOMARCA + TELAS
// =====================
const uint8_t logo_ifce[] PROGMEM = {
// 'ita', 128x64px
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0xe7, 0xcf, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0xf7, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0xf7, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0xf7, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0xf7, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0xf7, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0xf7, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0xe7, 0xcf, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0xf7, 0xc0, 0x04, 0x9b, 0xfd, 0x7a, 0x7f, 0x78, 0x77, 0x79, 0xde, 0x32, 0x00, 0x00,
0x00, 0x03, 0xf7, 0xe0, 0x05, 0xdb, 0xfd, 0x7a, 0x7f, 0xf8, 0x77, 0x7d, 0xde, 0x72, 0x00, 0x00,
0x00, 0x03, 0xf7, 0xe0, 0x05, 0xda, 0x19, 0x32, 0x64, 0xc8, 0x44, 0x6d, 0x9b, 0x72, 0x00, 0x00,
0x00, 0x03, 0xf7, 0xe0, 0x05, 0xfb, 0x19, 0x32, 0x64, 0xcc, 0x77, 0x6d, 0xde, 0x7a, 0x00, 0x00,
0x00, 0x03, 0xf7, 0xe0, 0x05, 0xb9, 0x99, 0x32, 0x64, 0xcc, 0x77, 0x6d, 0xde, 0x7a, 0x00, 0x00,
0x00, 0x03, 0xf7, 0xe0, 0x05, 0xb8, 0xd9, 0x32, 0x64, 0xc8, 0x44, 0x6d, 0x9e, 0xfa, 0x00, 0x00,
0x00, 0x03, 0xf7, 0xe0, 0x05, 0xbf, 0xd9, 0x33, 0xc4, 0xf8, 0x47, 0x7d, 0xdb, 0xcb, 0x80, 0x00,
0x00, 0x03, 0xf7, 0xe0, 0x04, 0x9b, 0x99, 0x31, 0xc4, 0x78, 0x47, 0x79, 0xdb, 0xcb, 0xc0, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0xf7, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0xf7, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0xf7, 0xff, 0x84, 0x44, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0xf7, 0xff, 0x84, 0x65, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0xf7, 0xff, 0x84, 0xef, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0xf7, 0xff, 0x84, 0xef, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0xf7, 0xef, 0x87, 0x6f, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0xe7, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0xf7, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0xf7, 0xe0, 0x03, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0xf7, 0xe0, 0x04, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0xf7, 0xe0, 0x04, 0x6f, 0xf5, 0xe7, 0x6e, 0x76, 0xdc, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0xf7, 0xe0, 0x04, 0x2a, 0xd5, 0x84, 0x3a, 0x59, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0xf7, 0xe0, 0x04, 0xea, 0xd5, 0x64, 0x7a, 0x59, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0xf7, 0xc0, 0x06, 0xea, 0xd7, 0x65, 0x7e, 0x76, 0xd4, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x08, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x08, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
void splashScreen() {
u8g2.clearBuffer();
// Bitmap full screen 128x64
u8g2.drawBitmap(
0, // x
0, // y
16, // largura em BYTES (128 / 8 = 16)
64, // altura em pixels
logo_ifce
);
u8g2.setFont(u8g2_font_5x7_tf);
u8g2.drawStr(12, 62, "Desenvolvido por LaMMA");
u8g2.sendBuffer();
delay(2500);
}
void header(const char* titulo) {
u8g2.setFont(u8g2_font_6x12_tf);
u8g2.drawStr(0, 10, titulo);
u8g2.drawLine(0, 12, 127, 12);
}
void animacaoIrrigacao() {
if (!irrigando) return;
if (millis() - tAnim > 300) {
animFrame = !animFrame;
tAnim = millis();
}
int y = animFrame ? 18 : 22;
u8g2.drawXBMP(104, y, 8, 8, gota);
}
void mostrarHome(tm *t) {
u8g2.clearBuffer();
header("IRRIGASYSTEM IFCE");
// Status
if (irrigando) u8g2.drawStr(106, 10, "ON");
else u8g2.drawStr(106, 10, "OFF");
// Hora
if (t) {
char hora[10];
sprintf(hora, "%02d:%02d:%02d", t->tm_hour, t->tm_min, t->tm_sec);
u8g2.setFont(u8g2_font_logisoso18_tf);
u8g2.drawStr(0, 38, hora);
}
// Linha inferior
u8g2.setFont(u8g2_font_6x12_tf);
char buf[24];
if (modoManual) u8g2.drawStr(92, 40, "MANUAL");
sprintf(buf, "Programado para %02d:%02d", horaIrriga, minutoIrriga);
u8g2.drawStr(0, 52, buf);
sprintf(buf, "Duracao %dmin", duracaoMin);
u8g2.drawStr(0, 64, buf);
animacaoIrrigacao();
u8g2.sendBuffer();
}
// =====================
// SETUP
// =====================
void setup() {
pinMode(PIN_RELE, OUTPUT);
desligarIrrigacao();
u8g2.begin();
carregarConfig();
splashScreen();
conectarWiFi();
iniciarNTP();
}
// =====================
// FUNÇÃO MOSTRAR MENU
// =====================
void mostrarMenu() {
u8g2.clearBuffer();
header("MENU");
u8g2.setFont(u8g2_font_6x12_tf);
u8g2.drawStr(0, 28, "1 - Ajustar Hora");
u8g2.drawStr(0, 42, "2 - Duracao");
u8g2.drawStr(0, 56, "* - Voltar");
u8g2.sendBuffer();
}
void mostrarSetHora() {
u8g2.clearBuffer();
header("AJUSTAR HORA");
u8g2.setFont(u8g2_font_6x12_tf);
u8g2.drawStr(0, 30, "Digite HHMM:");
u8g2.drawStr(0, 45, buffer.c_str());
u8g2.setFont(u8g2_font_5x7_tf);
u8g2.drawStr(0, 56, "Clique # para salvar");
u8g2.setFont(u8g2_font_5x7_tf);
u8g2.drawStr(0, 64, "ou clique * para voltar");
u8g2.sendBuffer();
}
void mostrarSetDuracao() {
u8g2.clearBuffer();
header("DURACAO");
u8g2.setFont(u8g2_font_6x12_tf);
u8g2.drawStr(0, 30, "Minutos:");
u8g2.drawStr(0, 45, buffer.c_str());
u8g2.setFont(u8g2_font_5x7_tf);
u8g2.drawStr(0, 56, "Clique # para salvar");
u8g2.setFont(u8g2_font_5x7_tf);
u8g2.drawStr(0, 64, "ou clique * para voltar");
u8g2.sendBuffer();
}
// =====================
// LOOP
// =====================
void loop() {
tm timeinfo;
bool temHora = false;
if (WiFi.status() == WL_CONNECTED) {
temHora = pegarHora(timeinfo);
}
char tecla = keypad.getKey();
if (telaAtual == TELA_HOME) {
if (tecla == 'A') telaAtual = TELA_MENU;
if (tecla == 'B') {
modoManual = !modoManual;
if (modoManual) {
ligarIrrigacao();
irrigando = true;
tInicioIrrigacao = millis();
} else {
desligarIrrigacao();
irrigando = false;
}
}
mostrarHome(temHora ? &timeinfo : nullptr);
}
else if (telaAtual == TELA_MENU) {
mostrarMenu();
if (tecla == '1') {
buffer = "";
telaAtual = TELA_SET_HORA;
}
else if (tecla == '2') {
buffer = "";
telaAtual = TELA_SET_DURACAO;
}
else if (tecla == '*') {
telaAtual = TELA_HOME;
}
}
else if (telaAtual == TELA_SET_HORA) {
mostrarSetHora();
if (tecla >= '0' && tecla <= '9' && buffer.length() < 4) {
buffer += tecla;
}
if (tecla == '*') {
buffer = "";
telaAtual = TELA_MENU;
}
if (tecla == '#') {
if (buffer.length() == 4) {
int h = buffer.substring(0, 2).toInt();
int m = buffer.substring(2, 4).toInt();
if (h >= 0 && h < 24 && m >= 0 && m < 60) {
horaIrriga = h;
minutoIrriga = m;
salvarConfig();
}
}
buffer = "";
telaAtual = TELA_MENU;
}
}
else if (telaAtual == TELA_SET_DURACAO) {
mostrarSetDuracao();
if (tecla >= '0' && tecla <= '9' && buffer.length() < 3) {
buffer += tecla;
}
if (tecla == '*') {
buffer = "";
telaAtual = TELA_MENU;
}
if (tecla == '#') {
int d = buffer.toInt();
if (d >= 1 && d <= 180) {
duracaoMin = d;
salvarConfig();
}
buffer = "";
telaAtual = TELA_MENU;
}
}
// ⏱ Controle de tempo da irrigação
if (irrigando && millis() - tInicioIrrigacao > duracaoMin * 60000UL) {
desligarIrrigacao();
irrigando = false;
modoManual = false;
}
// ⏰ Irrigação automática por horário
if (temHora && !irrigando && !modoManual) {
if (timeinfo.tm_hour == horaIrriga &&
timeinfo.tm_min == minutoIrriga &&
timeinfo.tm_min != ultimoMinutoDisparo) {
ligarIrrigacao();
irrigando = true;
tInicioIrrigacao = millis();
ultimoMinutoDisparo = timeinfo.tm_min;
}
}
delay(150);
}
Loading
esp32-s2-devkitm-1
esp32-s2-devkitm-1