diff --git a/src/main.cpp b/src/main.cpp index be1619e..5bc8327 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,8 +1,10 @@ // ============================================================= -// DummyBot — firmware ESP32 v6.1 (producción sin botones) +// DummyBot — firmware ESP32 v6.2 (botones invertidos + sin vibrador) // OLED SSD1306 128x64 + WiFi STA + polling backend -// Sin input físico: botones deshabilitados hasta nueva placa. -// Mantiene: WiFi config + portal cautivo + API + OLED + vibrador. +// Botones: GPIO 25 = PLAY (DONE), 26 = V+ (NEXT), 27 = V- (SNOOZE) +// Lógica: INPUT_PULLUP → HIGH reposo, LOW al pulsar +// KCOM del módulo de botones → GND +// GPIO 25 (era vibrador) ahora es un botón → vibrador desactivado // ============================================================= #include @@ -17,14 +19,16 @@ #include // ============== HARDWARE ============== -static constexpr uint8_t PIN_VIBRATOR = 25; -static constexpr uint8_t PIN_BOOT = 0; -static constexpr int8_t OLED_SDA = 21; -static constexpr int8_t OLED_SCL = 22; -static constexpr uint8_t OLED_ADDR = 0x3C; -static constexpr int8_t OLED_RST = -1; -static constexpr uint8_t SCREEN_W = 128; -static constexpr uint8_t SCREEN_H = 64; +static constexpr uint8_t PIN_BTN_PLAY = 25; // PLAY (DONE: marcar como hecha) +static constexpr uint8_t PIN_BTN_NEXT = 26; // V+ (NEXT: siguiente tarea) +static constexpr uint8_t PIN_BTN_SNOO = 27; // V- (SNOOZE: posponer 5 min) +static constexpr uint8_t PIN_BOOT = 0; +static constexpr int8_t OLED_SDA = 21; +static constexpr int8_t OLED_SCL = 22; +static constexpr uint8_t OLED_ADDR = 0x3C; +static constexpr int8_t OLED_RST = -1; +static constexpr uint8_t SCREEN_W = 128; +static constexpr uint8_t SCREEN_H = 64; // ============== CONFIG COMPILETIME ============== #ifndef DEFAULT_WIFI_SSID @@ -37,7 +41,7 @@ static constexpr uint8_t SCREEN_H = 64; #define BACKEND_URL "http://157.180.77.232:18080" #endif #ifndef DEFAULT_DEVICE_TOKEN -#define DEFAULT_DEVICE_TOKEN "" +#define DEFAULT_DEVICE_TOKEN "UrTQ0byozhEJxZQSC7LFmDq8liH3OnEBaFryUlU4yHaKcq2c" #endif static constexpr const char* AP_NAME = "DummyBot-Setup"; @@ -47,8 +51,8 @@ static constexpr const char* NVS_NS = "dummybot"; static constexpr uint32_t POLL_MS = 30000; static constexpr uint32_t API_TIMEOUT_MS = 5000; static constexpr uint32_t WIFI_TIMEOUT_MS = 20000; -static constexpr uint32_t VIB_PATTERN_MS = 1500; -static constexpr uint32_t VIB_BUZZ_MS = 400; +static constexpr uint32_t DEBOUNCE_MS = 200; +static constexpr uint8_t SNOOZE_MINUTES = 5; Adafruit_SSD1306 display(SCREEN_W, SCREEN_H, &Wire, OLED_RST); Preferences nvs; @@ -134,13 +138,12 @@ void showConnecting(const String& ssid, uint8_t dots) { display.display(); } -void showIdle(bool wifiOk) { +void showIdle() { display.clearDisplay(); display.setTextSize(1); display.setTextColor(SSD1306_WHITE); display.setCursor(0, 0); - display.print(F("DummyBot ")); - display.print(wifiOk ? F("[W]") : F("[!]")); + display.print(F("DummyBot [W]")); display.drawLine(0, 10, 127, 10, SSD1306_WHITE); display.setTextSize(2); display.setCursor(8, 22); @@ -176,6 +179,22 @@ void showTask(const String& title, const String& priority, int32_t dueSec) { display.display(); } +void showToast(const char* msg, bool ok) { + display.clearDisplay(); + display.setTextSize(1); + display.setTextColor(SSD1306_WHITE); + display.setCursor(0, 0); + display.print(F("DummyBot")); + display.drawLine(0, 10, 127, 10, SSD1306_WHITE); + display.setTextSize(2); + display.setCursor(8, 22); + display.print(msg); + display.setTextSize(1); + display.setCursor(0, 56); + display.print(ok ? F("[OK]") : F("[ERROR]")); + display.display(); +} + void showPortal(uint8_t clients) { display.clearDisplay(); display.setTextSize(1); @@ -195,23 +214,6 @@ void showPortal(uint8_t clients) { } } // namespace UI -// ============================================================= -// VIBRADOR -// ============================================================= -namespace Vib { -bool active = false; -uint32_t startMs = 0; - -void buzz() { active = true; startMs = millis(); digitalWrite(PIN_VIBRATOR, HIGH); } -void stop() { active = false; digitalWrite(PIN_VIBRATOR, LOW); } -void tick() { - if (!active) return; - uint32_t e = millis() - startMs; - if (e >= VIB_PATTERN_MS) { stop(); return; } - digitalWrite(PIN_VIBRATOR, (e % (VIB_BUZZ_MS + 200)) < VIB_BUZZ_MS ? HIGH : LOW); -} -} // namespace Vib - // ============================================================= // TAREAS // ============================================================= @@ -225,6 +227,9 @@ struct Task { static Task currentTask; static bool hasTask = false; static int32_t lastSeenId = 0; +static uint32_t toastUntil = 0; +static const char* toastMsg = nullptr; +static bool toastOk = true; // ============================================================= // API @@ -268,12 +273,87 @@ bool pollPending() { currentTask.priority = t0["priority"] | "med"; currentTask.nextDueSec = t0["next_due_in"] | 0; hasTask = true; - if ((int32_t)currentTask.id != lastSeenId && lastSeenId != 0) Vib::buzz(); + if ((int32_t)currentTask.id != lastSeenId && lastSeenId != 0) { + // Nueva tarea detectada — pequeño feedback visual + UI::showToast("Nueva!", true); + toastUntil = millis() + 800; + } lastSeenId = currentTask.id; return true; } + +bool markDone() { + if (!hasTask) return false; + char path[64]; + snprintf(path, sizeof(path), "/api/device/tasks/%d/done", currentTask.id); + bool ok = httpJson("POST", path, "{}", nullptr); + if (ok) { lastSeenId = 0; pollPending(); } + return ok; +} + +bool snooze() { + if (!hasTask) return false; + char path[64]; + snprintf(path, sizeof(path), "/api/device/tasks/%d/snooze", currentTask.id); + char body[32]; + snprintf(body, sizeof(body), "{\"minutes\":%u}", SNOOZE_MINUTES); + bool ok = httpJson("POST", path, body, nullptr); + if (ok) { lastSeenId = 0; pollPending(); } + return ok; +} } // namespace Api +// ============================================================= +// INPUT — botones con INPUT_PULLUP, lógica invertida +// HIGH en reposo, LOW al pulsar, KCOM del módulo a GND +// ============================================================= +namespace Input { +uint32_t lastMs[3] = {0, 0, 0}; +bool lastState[3] = {true, true, true}; // pull-up: idle=HIGH +constexpr uint8_t PINS[3] = { PIN_BTN_NEXT, PIN_BTN_PLAY, PIN_BTN_SNOO }; + +// Acción común +void press(uint8_t i) { + switch (i) { + case 0: + Serial.println("[BTN] NEXT (V+)"); + Api::pollPending(); + UI::showToast("Siguiente", true); + toastUntil = millis() + 1200; + break; + case 1: { + Serial.println("[BTN] DONE (PLAY)"); + bool ok = Api::markDone(); + UI::showToast(ok ? "Hecha!" : "Error", ok); + toastUntil = millis() + 1200; + break; + } + case 2: { + Serial.println("[BTN] SNOOZE (V-)"); + bool ok = Api::snooze(); + UI::showToast(ok ? "Snooze 5m" : "Error", ok); + toastUntil = millis() + 1200; + break; + } + } + lastMs[i] = millis(); +} + +void tick() { + uint32_t now = millis(); + for (uint8_t i = 0; i < 3; i++) { + bool now_low = (digitalRead(PINS[i]) == LOW); // pulsado = LOW + // Flanco descendente: idle HIGH → pulsado LOW + if (now_low && lastState[i] && (now - lastMs[i] > DEBOUNCE_MS)) { + press(i); + } else if (!now_low && !lastState[i]) { + // soltado + } + lastState[i] = now_low; + } +} +} // namespace Input + // ============================================================= // PORTAL // ============================================================= @@ -303,7 +383,6 @@ button.sec{background:transparent;border:1px solid #3f3f46;margin-top:.5rem} .result{margin-top:1rem;padding:.6rem;border-radius:8px;font-size:.85rem;display:none} .ok{background:#16a34a22;color:#86efac;border:1px solid #16a34a55} .err{background:#dc262622;color:#fca5a5;border:1px solid #dc262655} -.emoji{font-size:2rem;margin-bottom:.3rem}

DummyBot

Configura WiFi y token del bot

@@ -320,7 +399,6 @@ button.sec{background:transparent;border:1px solid #3f3f46;margin-top:.5rem}