// ENZO FERNANDES RAMOS RM:563705
// FELIPE CERAZI RM:562746
// GUSTAVO PEAGUDA RM:562923
// LORENZO ANDOLFATTO COQUE RM:563385
#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <U8g2lib.h>
// ===================== CONFIG WiFi/MQTT (FIWARE) =====================
const char* default_SSID = "Wokwi-GUEST";
const char* default_PASSWORD = "";
const char* default_BROKER_MQTT = "102.37.18.193"; // seu broker na Azure
const int default_BROKER_PORT = 1883;
const char* default_ID_MQTT = "fiware_score_001";
const char* deviceId = "score001"; // ID do "device" no IoT Agent
// Tópicos Ultralight 2.0
const char* default_TOPICO_SUBSCRIBE = "/TEF/score001/cmd"; // comandos
const char* default_TOPICO_PUBLISH = "/TEF/score001/attrs"; // atributos (estado)
// Variáveis mutáveis (se quiser tornar editável em runtime)
char* SSID = const_cast<char*>(default_SSID);
char* PASSWORD = const_cast<char*>(default_PASSWORD);
char* BROKER_MQTT = const_cast<char*>(default_BROKER_MQTT);
int BROKER_PORT = default_BROKER_PORT;
char* TOPICO_SUBSCRIBE= const_cast<char*>(default_TOPICO_SUBSCRIBE);
char* TOPICO_PUBLISH = const_cast<char*>(default_TOPICO_PUBLISH);
char* ID_MQTT = const_cast<char*>(default_ID_MQTT);
// ===================== HARDWARE: PINOS =====================
#define PIN_VERMELHO 0
#define PIN_AMARELO 16
#define PIN_GOL_A 4
#define PIN_GOL_B 17
#define PIN_CRONOMETRO 5
// ===================== DISPLAY SH1107 (128x128) =====================
U8G2_SH1107_128X128_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
// ===================== ESTADO DO JOGO =====================
volatile int golsA = 0;
volatile int golsB = 0;
volatile int cartaoAmarelo = 0;
volatile int cartaoVermelho = 0;
volatile bool cronometroAtivo = true;
// Botões - estados anteriores
int lastBtnVermelho = HIGH;
int lastBtnAmarelo = HIGH;
int lastBtnGolA = HIGH;
int lastBtnGolB = HIGH;
int lastBtnCron = HIGH;
// Long press
unsigned long pressStartV = 0;
unsigned long pressStartA = 0;
unsigned long pressStartGA = 0;
unsigned long pressStartGB = 0;
unsigned long pressStartC = 0;
bool longHandledV = false;
bool longHandledA = false;
bool longHandledGA = false;
bool longHandledGB = false;
bool longHandledC = false;
const unsigned long LONG_PRESS_MS = 3000;
// Cronômetro
const unsigned long LIMITE_MS = 25UL * 60UL * 1000UL; // 25:00
unsigned long cronometroBase = 0; // acumulado ao pausar
unsigned long cronometroStart = 0; // millis() do último start
// Draw throttle e publish throttle
const unsigned long DRAW_INTERVAL_MS = 100; // 10 FPS no OLED
const unsigned long PUBLISH_INTERVAL_MS= 2000; // envia estado a cada 2s
unsigned long nextDrawAt = 0;
unsigned long nextPublishAt = 0;
// ===================== WiFi/MQTT =====================
WiFiClient espClient;
PubSubClient MQTT(espClient);
// --------------------- Helpers de tempo ---------------------
unsigned long tempoCronometroMs() {
unsigned long t = cronometroBase;
if (cronometroAtivo) t += (millis() - cronometroStart);
if (t >= LIMITE_MS) t = LIMITE_MS;
return t;
}
void iniciarCronometro() {
if (!cronometroAtivo) {
cronometroStart = millis();
cronometroAtivo = true;
}
}
void pausarCronometro() {
if (cronometroAtivo) {
cronometroBase = tempoCronometroMs();
cronometroAtivo = false;
}
}
void alternarCronometro() {
if (cronometroAtivo) pausarCronometro();
else iniciarCronometro();
}
void reiniciarCronometro() {
cronometroBase = 0;
cronometroStart = millis();
cronometroAtivo = false; // volta 00:00 pausado
}
// --------------------- Desenho no OLED ---------------------
void desenharCronometro() {
unsigned long t = tempoCronometroMs();
// Trava no limite
if (t >= LIMITE_MS && cronometroAtivo) {
pausarCronometro();
}
unsigned long totalSeg = t / 1000UL;
unsigned int mm = totalSeg / 60U;
unsigned int ss = totalSeg % 60U;
char buf[8];
snprintf(buf, sizeof(buf), "%02u:%02u", mm, ss);
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_6x12_tf);
u8g2.drawStr(0, 12, "CRONOMETRO");
u8g2.setFont(u8g2_font_logisoso32_tf);
int16_t w = u8g2.getUTF8Width(buf);
int16_t x = (128 - w) / 2;
int16_t y = 80;
u8g2.drawUTF8(x, y, buf);
u8g2.setFont(u8g2_font_6x12_tf);
u8g2.drawStr(0, 124, cronometroAtivo ? "Rodando" : "Pausado");
u8g2.drawStr(74, 124, "Limite: 25:00");
u8g2.sendBuffer();
}
// --------------------- MQTT: publish estado ---------------------
String formatTempoMMSS() {
unsigned long t = tempoCronometroMs();
unsigned long totalSeg = t / 1000UL;
unsigned int mm = totalSeg / 60U;
unsigned int ss = totalSeg % 60U;
char buf[8];
snprintf(buf, sizeof(buf), "%02u:%02u", mm, ss);
return String(buf);
}
void publishEstado(bool force = false) {
static int last_gA=-1, last_gB=-1, last_y=-1, last_r=-1, last_run=-1;
static String last_t = "";
int run = cronometroAtivo ? 1 : 0;
String t = formatTempoMMSS();
// Reenvia a cada período ou quando mudou algo
bool changed = (golsA != last_gA) || (golsB != last_gB) ||
(cartaoAmarelo != last_y) || (cartaoVermelho != last_r) ||
(run != last_run) || (t != last_t);
if (!changed && !force && millis() < nextPublishAt) return;
String payload = "gA|" + String(golsA) +
"#gB|" + String(golsB) +
"#y|" + String(cartaoAmarelo) +
"#r|" + String(cartaoVermelho) +
"#run|"+ String(run) +
"#t|" + t +
"#online|1";
MQTT.publish(TOPICO_PUBLISH, payload.c_str());
last_gA = golsA; last_gB = golsB;
last_y = cartaoAmarelo; last_r = cartaoVermelho;
last_run = run; last_t = t;
nextPublishAt = millis() + PUBLISH_INTERVAL_MS;
}
// --------------------- MQTT: callback de comandos ---------------------
void mqtt_callback(char* topic, byte* payload, unsigned int length) {
String msg;
msg.reserve(length + 8);
for (unsigned int i = 0; i < length; i++) msg += (char)payload[i];
// Espera padrão: "score001@<cmd>|"
String prefix = String(deviceId) + "@";
auto isCmd = [&](const char* cmd) {
String expect = prefix + String(cmd) + "|";
return msg.equals(expect);
};
bool acted = false;
if (isCmd("ga_add")) { golsA++; acted = true; }
if (isCmd("ga_sub")) { if (golsA>0) golsA--; acted = true; }
if (isCmd("gb_add")) { golsB++; acted = true; }
if (isCmd("gb_sub")) { if (golsB>0) golsB--; acted = true; }
if (isCmd("y_add")) { cartaoAmarelo++; acted = true; }
if (isCmd("y_sub")) { if (cartaoAmarelo>0) cartaoAmarelo--; acted = true; }
if (isCmd("r_add")) { cartaoVermelho++; acted = true; }
if (isCmd("r_sub")) { if (cartaoVermelho>0) cartaoVermelho--; acted = true; }
if (isCmd("timer_toggle")) { alternarCronometro(); acted = true; }
if (isCmd("timer_reset")) { reiniciarCronometro(); acted = true; }
if (acted) {
publishEstado(true); // publica imediatamente após comando
}
}
// --------------------- Conexões WiFi/MQTT ---------------------
void connectWiFiBlocking() {
if (WiFi.status() == WL_CONNECTED) return;
WiFi.mode(WIFI_STA);
WiFi.begin(SSID, PASSWORD);
Serial.print("Conectando WiFi: "); Serial.println(SSID);
while (WiFi.status() != WL_CONNECTED) {
delay(150);
Serial.print(".");
}
Serial.println("\nWiFi OK. IP: " + WiFi.localIP().toString());
}
unsigned long nextMqttAttempt = 0;
void ensureMQTT() {
if (MQTT.connected()) return;
unsigned long now = millis();
if (now < nextMqttAttempt) return;
Serial.print("MQTT -> "); Serial.print(BROKER_MQTT); Serial.print(":"); Serial.println(BROKER_PORT);
if (MQTT.connect(ID_MQTT)) {
Serial.println("MQTT conectado.");
MQTT.subscribe(TOPICO_SUBSCRIBE);
publishEstado(true); // anuncia estado ao conectar
} else {
Serial.println("Falha MQTT, tentando de novo em 2s.");
nextMqttAttempt = now + 2000;
}
}
// ===================== SETUP =====================
void setup() {
Serial.begin(115200);
// Entradas com pull-up
pinMode(PIN_VERMELHO, INPUT_PULLUP);
pinMode(PIN_AMARELO, INPUT_PULLUP);
pinMode(PIN_GOL_A, INPUT_PULLUP);
pinMode(PIN_GOL_B, INPUT_PULLUP);
pinMode(PIN_CRONOMETRO, INPUT_PULLUP);
// I2C (OLED) rápido
Wire.begin(2, 15); // SDA=2, SCL=15 conforme seu diagrama
Wire.setClock(400000); // 400 kHz
u8g2.begin();
// WiFi/MQTT
connectWiFiBlocking();
MQTT.setServer(BROKER_MQTT, BROKER_PORT);
MQTT.setCallback(mqtt_callback);
nextDrawAt = millis();
nextPublishAt = millis();
desenharCronometro();
publishEstado(true);
}
// ===================== LOOP =====================
void loop() {
unsigned long now = millis();
// -------- conexões --------
if (WiFi.status() != WL_CONNECTED) connectWiFiBlocking();
ensureMQTT();
MQTT.loop();
// -------- Botão Vermelho (cartão vermelho) --------
int stateV = digitalRead(PIN_VERMELHO);
if (stateV == LOW && lastBtnVermelho == HIGH) {
pressStartV = now; longHandledV = false;
}
if (stateV == LOW && lastBtnVermelho == LOW && !longHandledV) {
if (now - pressStartV >= LONG_PRESS_MS) {
if (cartaoVermelho > 0) cartaoVermelho--;
longHandledV = true;
publishEstado(true);
}
}
if (stateV == HIGH && lastBtnVermelho == LOW) {
if (!longHandledV) { cartaoVermelho++; publishEstado(true); }
}
lastBtnVermelho = stateV;
// -------- Botão Amarelo --------
int stateA = digitalRead(PIN_AMARELO);
if (stateA == LOW && lastBtnAmarelo == HIGH) {
pressStartA = now; longHandledA = false;
}
if (stateA == LOW && lastBtnAmarelo == LOW && !longHandledA) {
if (now - pressStartA >= LONG_PRESS_MS) {
if (cartaoAmarelo > 0) cartaoAmarelo--;
longHandledA = true;
publishEstado(true);
}
}
if (stateA == HIGH && lastBtnAmarelo == LOW) {
if (!longHandledA) { cartaoAmarelo++; publishEstado(true); }
}
lastBtnAmarelo = stateA;
// -------- Botão Gol A --------
int stateGA = digitalRead(PIN_GOL_A);
if (stateGA == LOW && lastBtnGolA == HIGH) {
pressStartGA = now; longHandledGA = false;
}
if (stateGA == LOW && lastBtnGolA == LOW && !longHandledGA) {
if (now - pressStartGA >= LONG_PRESS_MS) {
if (golsA > 0) golsA--;
longHandledGA = true;
publishEstado(true);
}
}
if (stateGA == HIGH && lastBtnGolA == LOW) {
if (!longHandledGA) { golsA++; publishEstado(true); }
}
lastBtnGolA = stateGA;
// -------- Botão Gol B --------
int stateGB = digitalRead(PIN_GOL_B);
if (stateGB == LOW && lastBtnGolB == HIGH) {
pressStartGB = now; longHandledGB = false;
}
if (stateGB == LOW && lastBtnGolB == LOW && !longHandledGB) {
if (now - pressStartGB >= LONG_PRESS_MS) {
if (golsB > 0) golsB--;
longHandledGB = true;
publishEstado(true);
}
}
if (stateGB == HIGH && lastBtnGolB == LOW) {
if (!longHandledGB) { golsB++; publishEstado(true); }
}
lastBtnGolB = stateGB;
// -------- Botão Cronômetro --------
int stateC = digitalRead(PIN_CRONOMETRO);
if (stateC == LOW && lastBtnCron == HIGH) {
pressStartC = now; longHandledC = false;
}
if (stateC == LOW && lastBtnCron == LOW && !longHandledC) {
if (now - pressStartC >= LONG_PRESS_MS) {
reiniciarCronometro();
longHandledC = true;
publishEstado(true);
}
}
if (stateC == HIGH && lastBtnCron == LOW) {
if (!longHandledC) {
alternarCronometro();
// se já estava no limite, impede ultrapassar
if (tempoCronometroMs() >= LIMITE_MS) pausarCronometro();
publishEstado(true);
}
}
lastBtnCron = stateC;
// -------- OLED (10 FPS) --------
if (now >= nextDrawAt) {
desenharCronometro();
nextDrawAt = now + DRAW_INTERVAL_MS;
}
// -------- Publish periódico mesmo sem mudanças --------
if (now >= nextPublishAt) {
publishEstado(false);
}
}
Loading
grove-oled-sh1107
grove-oled-sh1107