feat: firmware v6.3 multi-pulsación con 1 botón
Cambio importante: el módulo de botones chino tiene los pads cortocircuitados a KCOM (que está a GND), lo que fuerza GPIO 26 y 27 a LOW constantemente y dispara acciones sin tocarlos. Solución temporal: solo GPIO 25 funciona como botón. Multi-pulsación para 3 acciones: - 1 tap = DONE - 2 taps = NEXT (re-poll) - 3 taps = SNOOZE 5 min - mantener 1s+ = sleep/wake pantalla Cuando llegue la placa nueva con 3 pulsadores mecánicos de 6mm, se restaura la lógica de 3 botones en GPIO 25/26/27.
This commit is contained in:
parent
dce87fbc11
commit
2e79aca367
163
src/main.cpp
163
src/main.cpp
@ -1,10 +1,20 @@
|
|||||||
// =============================================================
|
// =============================================================
|
||||||
// DummyBot — firmware ESP32 v6.2 (botones invertidos + sin vibrador)
|
// DummyBot — firmware ESP32 v6.3 (1 botón con multi-pulsación)
|
||||||
// OLED SSD1306 128x64 + WiFi STA + polling backend
|
// OLED SSD1306 128x64 + WiFi STA + polling backend
|
||||||
// Botones: GPIO 25 = PLAY (DONE), 26 = V+ (NEXT), 27 = V- (SNOOZE)
|
//
|
||||||
// Lógica: INPUT_PULLUP → HIGH reposo, LOW al pulsar
|
// Cambio importante v6.3: el módulo de botones chino estaba
|
||||||
// KCOM del módulo de botones → GND
|
// cortocircuitado (GPIO 26 y 27 quedaban forzados a LOW, lo
|
||||||
// GPIO 25 (era vibrador) ahora es un botón → vibrador desactivado
|
// que disparaba acciones constantemente sin tocar nada).
|
||||||
|
//
|
||||||
|
// Solución temporal: solo GPIO 25 funciona como botón físico.
|
||||||
|
// Multi-pulsación para hacer 3 acciones:
|
||||||
|
// · 1 tap = DONE (marcar como hecha)
|
||||||
|
// · 2 taps = NEXT (siguiente tarea, re-poll)
|
||||||
|
// · 3 taps = SNOOZE 5 min
|
||||||
|
// · mantener >1s = dormir/despertar pantalla
|
||||||
|
//
|
||||||
|
// Cuando llegue la placa nueva con 3 pulsadores mecánicos,
|
||||||
|
// se restaurará la lógica de 3 botones en GPIO 25/26/27.
|
||||||
// =============================================================
|
// =============================================================
|
||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
@ -19,9 +29,7 @@
|
|||||||
#include <Preferences.h>
|
#include <Preferences.h>
|
||||||
|
|
||||||
// ============== HARDWARE ==============
|
// ============== HARDWARE ==============
|
||||||
static constexpr uint8_t PIN_BTN_PLAY = 25; // PLAY (DONE: marcar como hecha)
|
static constexpr uint8_t PIN_BTN_PLAY = 25; // único botón físico
|
||||||
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 uint8_t PIN_BOOT = 0;
|
||||||
static constexpr int8_t OLED_SDA = 21;
|
static constexpr int8_t OLED_SDA = 21;
|
||||||
static constexpr int8_t OLED_SCL = 22;
|
static constexpr int8_t OLED_SCL = 22;
|
||||||
@ -304,52 +312,131 @@ bool snooze() {
|
|||||||
} // namespace Api
|
} // namespace Api
|
||||||
|
|
||||||
// =============================================================
|
// =============================================================
|
||||||
// INPUT — botones con INPUT_PULLUP, lógica invertida
|
// INPUT — 1 botón con multi-pulsación (sustituto temporal)
|
||||||
// HIGH en reposo, LOW al pulsar, KCOM del módulo a GND
|
//
|
||||||
|
// GPIO 25 (PLAY) con INPUT_PULLUP, lógica invertida.
|
||||||
|
// Estado: HIGH en reposo, LOW al pulsar.
|
||||||
|
//
|
||||||
|
// Patrones:
|
||||||
|
// - 1 tap = DONE (marca tarea como hecha)
|
||||||
|
// - 2 taps = NEXT (forzar re-polling)
|
||||||
|
// - 3 taps = SNOOZE (posponer 5 min)
|
||||||
|
// - mantener 1s+ = SLEEP (apagar pantalla; tocas otra vez para despertar)
|
||||||
|
//
|
||||||
|
// Ventana entre taps: 500 ms
|
||||||
|
// Timeout multi-tap: 700 ms
|
||||||
|
// Debounce por tap: 150 ms
|
||||||
// =============================================================
|
// =============================================================
|
||||||
namespace Input {
|
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
|
constexpr uint32_t TAP_DEBOUNCE_MS = 150;
|
||||||
void press(uint8_t i) {
|
constexpr uint32_t MULTI_GAP_MS = 500; // máx entre taps
|
||||||
switch (i) {
|
constexpr uint32_t MULTI_TIMEOUT_MS = 700; // ventana para consolidar
|
||||||
case 0:
|
constexpr uint32_t LONG_PRESS_MS = 1000; // hold = sleep
|
||||||
Serial.println("[BTN] NEXT (V+)");
|
|
||||||
Api::pollPending();
|
bool lastState = true; // pull-up: idle HIGH
|
||||||
UI::showToast("Siguiente", true);
|
uint32_t pressStartMs = 0; // momento de pulsación actual
|
||||||
toastUntil = millis() + 1200;
|
bool isPressed = false; // estamos dentro de un LOW
|
||||||
break;
|
uint8_t tapCount = 0; // taps acumulados
|
||||||
|
uint32_t lastTapMs = 0; // último tap registrado
|
||||||
|
|
||||||
|
bool sleeping = false; // pantalla apagada por sleep
|
||||||
|
|
||||||
|
void showAction(int action) {
|
||||||
|
switch (action) {
|
||||||
case 1: {
|
case 1: {
|
||||||
Serial.println("[BTN] DONE (PLAY)");
|
Serial.println("[BTN] DONE (1 tap)");
|
||||||
bool ok = Api::markDone();
|
bool ok = Api::markDone();
|
||||||
UI::showToast(ok ? "Hecha!" : "Error", ok);
|
UI::showToast(ok ? "Hecha!" : "Error", ok);
|
||||||
toastUntil = millis() + 1200;
|
toastUntil = millis() + 1200;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 2: {
|
case 2: {
|
||||||
Serial.println("[BTN] SNOOZE (V-)");
|
Serial.println("[BTN] NEXT (2 taps)");
|
||||||
|
Api::pollPending();
|
||||||
|
UI::showToast("Siguiente", true);
|
||||||
|
toastUntil = millis() + 1200;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 3: {
|
||||||
|
Serial.println("[BTN] SNOOZE (3 taps)");
|
||||||
bool ok = Api::snooze();
|
bool ok = Api::snooze();
|
||||||
UI::showToast(ok ? "Snooze 5m" : "Error", ok);
|
UI::showToast(ok ? "Snooze 5m" : "Error", ok);
|
||||||
toastUntil = millis() + 1200;
|
toastUntil = millis() + 1200;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lastMs[i] = millis();
|
}
|
||||||
|
|
||||||
|
void toggleSleep() {
|
||||||
|
sleeping = !sleeping;
|
||||||
|
if (sleeping) {
|
||||||
|
display.ssd1306_command(SSD1306_DISPLAYOFF);
|
||||||
|
Serial.println("[BTN] SLEEP (long press)");
|
||||||
|
} else {
|
||||||
|
display.ssd1306_command(SSD1306_DISPLAYON);
|
||||||
|
Serial.println("[BTN] WAKE");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void tick() {
|
void tick() {
|
||||||
uint32_t now = millis();
|
if (sleeping) {
|
||||||
for (uint8_t i = 0; i < 3; i++) {
|
// Para despertar: cualquier toque cuenta
|
||||||
bool now_low = (digitalRead(PINS[i]) == LOW); // pulsado = LOW
|
bool now_low = (digitalRead(PIN_BTN_PLAY) == LOW);
|
||||||
// Flanco descendente: idle HIGH → pulsado LOW
|
if (now_low && !lastState) {
|
||||||
if (now_low && lastState[i] && (now - lastMs[i] > DEBOUNCE_MS)) {
|
// No-op: solo se detecta el flanco
|
||||||
press(i);
|
|
||||||
} else if (!now_low && !lastState[i]) {
|
|
||||||
// soltado
|
|
||||||
}
|
}
|
||||||
lastState[i] = now_low;
|
if (!now_low && lastState) {
|
||||||
|
// Soltado tras un toque en sleep → despertar
|
||||||
|
toggleSleep();
|
||||||
|
// Resetear estado del multi-tap
|
||||||
|
tapCount = 0;
|
||||||
|
lastTapMs = 0;
|
||||||
|
lastState = now_low;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastState = now_low;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t now = millis();
|
||||||
|
bool now_low = (digitalRead(PIN_BTN_PLAY) == LOW);
|
||||||
|
|
||||||
|
// Flanco descendente (HIGH → LOW): nuevo tap
|
||||||
|
if (now_low && lastState) {
|
||||||
|
isPressed = true;
|
||||||
|
pressStartMs = now;
|
||||||
|
}
|
||||||
|
// Flanco ascendente (LOW → HIGH): tap soltado
|
||||||
|
else if (!now_low && isPressed) {
|
||||||
|
uint32_t held = now - pressStartMs;
|
||||||
|
isPressed = false;
|
||||||
|
|
||||||
|
if (held >= LONG_PRESS_MS) {
|
||||||
|
// Long press → toggle sleep
|
||||||
|
toggleSleep();
|
||||||
|
tapCount = 0;
|
||||||
|
lastTapMs = 0;
|
||||||
|
} else {
|
||||||
|
// Tap corto: incrementar contador
|
||||||
|
tapCount++;
|
||||||
|
lastTapMs = now;
|
||||||
|
Serial.printf("[BTN] tap #%u (held %ums)\n", tapCount, (unsigned)held);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastState = now_low;
|
||||||
|
|
||||||
|
// Consolidar multi-tap cuando se cumple timeout
|
||||||
|
if (tapCount > 0 && !isPressed && (now - lastTapMs) > MULTI_TIMEOUT_MS) {
|
||||||
|
if (tapCount <= 3) {
|
||||||
|
showAction(tapCount);
|
||||||
|
} else {
|
||||||
|
Serial.printf("[BTN] %u taps → siguiente\n", tapCount);
|
||||||
|
showAction(2); // 4+ taps = next
|
||||||
|
}
|
||||||
|
tapCount = 0;
|
||||||
|
lastTapMs = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} // namespace Input
|
} // namespace Input
|
||||||
@ -644,12 +731,10 @@ void wifiTick() {
|
|||||||
void setup() {
|
void setup() {
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
delay(300);
|
delay(300);
|
||||||
Serial.println("\n=== DummyBot v6.2 (botones invertidos) ===");
|
Serial.println("\n=== DummyBot v6.3 (1 botón + multi-pulsación) ===");
|
||||||
|
|
||||||
// Botones con INPUT_PULLUP, lógica invertida (LOW al pulsar)
|
// Único botón en GPIO 25 con INPUT_PULLUP, lógica invertida
|
||||||
pinMode(PIN_BTN_NEXT, INPUT_PULLUP); // V+
|
pinMode(PIN_BTN_PLAY, INPUT_PULLUP);
|
||||||
pinMode(PIN_BTN_PLAY, INPUT_PULLUP); // PLAY (DONE)
|
|
||||||
pinMode(PIN_BTN_SNOO, INPUT_PULLUP); // V- (SNOOZE)
|
|
||||||
|
|
||||||
Wire.begin(OLED_SDA, OLED_SCL);
|
Wire.begin(OLED_SDA, OLED_SCL);
|
||||||
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
|
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user