docs: documentar deploy automático + firmware ESP32
Some checks failed
Deploy DummyBot / Build & Deploy to ${{ env.DEPLOY_HOST }} (push) Has been cancelled
Some checks failed
Deploy DummyBot / Build & Deploy to ${{ env.DEPLOY_HOST }} (push) Has been cancelled
This commit is contained in:
parent
cebbe513da
commit
3a80d5e76b
200
README.md
200
README.md
@ -1,158 +1,76 @@
|
||||
# DummyBot Backend
|
||||
# DummyBot
|
||||
|
||||
API REST y UI web para **DummyBot** — un bot físico de tareas para personas con TDAH, basado en ESP32 + ST7789 + caritas kawaii.
|
||||
Bot físico de tareas para personas con TDAH, basado en ESP32 + OLED + 3 botones.
|
||||
|
||||
Stack: **Laravel 12 · PHP 8.4 · MySQL 8 · Sanctum · Tailwind (CDN) + Alpine.js (CDN)**.
|
||||
- **Stack backend**: Laravel 12 · PHP 8.4 · MySQL 8 · Sanctum · Tailwind (CDN) + Alpine.js (CDN)
|
||||
- **Stack firmware**: ESP32 (Arduino framework) · OLED SSD1306 128x64 · TTP223 buttons · vibrador
|
||||
- **Deploy**: automático vía webhook de Gitea en push a `main`
|
||||
|
||||
---
|
||||
## Estructura
|
||||
|
||||
## 🚀 Setup rápido
|
||||
```
|
||||
dummybot/
|
||||
├── app/ # Laravel: controllers, models
|
||||
├── bootstrap/ # Laravel: bootstrap + middleware config
|
||||
├── config/ # Laravel: app, db, sanctum, etc.
|
||||
├── database/ # Migraciones y seeders
|
||||
├── docs/ # Documentación técnica
|
||||
│ ├── DEPLOY.md # Pipeline de deploy automático
|
||||
│ └── FIRMWARE.md # Hardware, pines, flasheo
|
||||
├── firmware/ # PlatformIO + ESP32 Arduino
|
||||
│ ├── platformio.ini
|
||||
│ ├── partitions.csv
|
||||
│ └── src/main.cpp # Firmware v6.1
|
||||
├── resources/ # Blade views + CSS
|
||||
│ ├── css/
|
||||
│ ├── js/
|
||||
│ └── views/
|
||||
│ ├── layouts/base.blade.php
|
||||
│ └── pages/
|
||||
│ ├── app.blade.php
|
||||
│ ├── auth.blade.php
|
||||
│ ├── home.blade.php
|
||||
│ └── settings.blade.php
|
||||
├── routes/
|
||||
│ ├── api.php
|
||||
│ └── web.php
|
||||
├── deploy-runner.sh # Script que ejecuta el webhook
|
||||
├── docker-compose.yml
|
||||
├── Dockerfile
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## Documentación adicional
|
||||
|
||||
- [`docs/DEPLOY.md`](docs/DEPLOY.md) — Pipeline de deploy automático
|
||||
- [`docs/FIRMWARE.md`](docs/FIRMWARE.md) — Hardware, cableado, flasheo del ESP32
|
||||
- [`firmware/README.md`](firmware/README.md) — Detalles de la build PlatformIO
|
||||
|
||||
## Quick start (desarrollo local)
|
||||
|
||||
```bash
|
||||
# 1. Crear BD
|
||||
mysql -u root -p
|
||||
> CREATE DATABASE dummybot CHARACTER SET utf8mb4;
|
||||
> CREATE USER 'dummybot'@'localhost' IDENTIFIED BY 'TU_PASSWORD';
|
||||
> GRANT ALL ON dummybot.* TO 'dummybot'@'localhost';
|
||||
|
||||
# 2. Configurar
|
||||
# Backend
|
||||
composer install
|
||||
cp .env.example .env
|
||||
# Edita DB_PASSWORD, APP_URL, etc.
|
||||
|
||||
# 3. Instalar + migrar
|
||||
composer install --no-dev --optimize-autoloader
|
||||
php artisan key:generate
|
||||
php artisan migrate
|
||||
|
||||
# 4. Arrancar
|
||||
php artisan serve --host=0.0.0.0 --port=8000
|
||||
|
||||
# Firmware
|
||||
cd firmware
|
||||
pio run -t upload --upload-port /dev/ttyUSB0
|
||||
```
|
||||
|
||||
Abre `http://localhost:8000/`. Crea una cuenta, ve a **Ajustes → Dispositivos** y crea tu primer DummyBot para obtener un token.
|
||||
## Despliegue
|
||||
|
||||
## 🐳 Docker
|
||||
|
||||
```bash
|
||||
AUTO_MIGRATE=1 docker compose up -d --build
|
||||
```
|
||||
|
||||
Esto levanta `dummybot_app` (PHP-FPM), `dummybot_web` (nginx) y `dummybot_db` (MySQL 8). El puerto 80 queda interno (el proxy externo ya está gestionado en producción).
|
||||
|
||||
## 🧠 Modelo de datos
|
||||
|
||||
| Tabla | Descripción |
|
||||
|------------|-------------|
|
||||
| `users` | Usuarios (auth vía Sanctum) |
|
||||
| `tasks` | Tareas (`title`, `description`, `priority`, `estimate_min`, `deadline`, `recurrence`, `done`, `snooze_until`, `done_at`, `project_id`, `user_id`) |
|
||||
| `projects` | Agrupa tareas (color + icono) |
|
||||
| `tags` | Etiquetas transversales (muchas a muchas con tasks) |
|
||||
| `devices` | Cada ESP32 con su propio `token` y `last_seen_at` |
|
||||
|
||||
## 🔌 API REST
|
||||
|
||||
Todas las rutas devuelven JSON. Auth vía `Authorization: Bearer <token>`.
|
||||
|
||||
### Auth (web)
|
||||
```
|
||||
POST /api/auth/register {name, email, password, password_confirmation}
|
||||
POST /api/auth/login {email, password}
|
||||
POST /api/auth/logout (auth)
|
||||
GET /api/auth/me (auth)
|
||||
```
|
||||
|
||||
### Tareas (web, con sesión o token Sanctum)
|
||||
```
|
||||
GET /api/tasks Listar (con project + tags)
|
||||
POST /api/tasks Crear
|
||||
PUT /api/tasks/{id} Editar
|
||||
DELETE /api/tasks/{id} Borrar
|
||||
POST /api/tasks/{id}/done Marcar como hecha (genera recurrente si aplica)
|
||||
POST /api/tasks/{id}/snooze Posponer {minutes: N}
|
||||
POST /api/tasks/{id}/restore Reabrir
|
||||
GET /api/tasks/stats {pending, today, done_today, overdue, total_done}
|
||||
```
|
||||
|
||||
### Proyectos
|
||||
```
|
||||
GET /api/projects
|
||||
POST /api/projects {name, color, icon}
|
||||
PUT /api/projects/{id}
|
||||
DELETE /api/projects/{id}
|
||||
```
|
||||
|
||||
### Tags
|
||||
```
|
||||
GET /api/tags
|
||||
POST /api/tags {name, color}
|
||||
DELETE /api/tags/{id}
|
||||
```
|
||||
|
||||
### Dispositivos (DummyBots)
|
||||
```
|
||||
GET /api/devices Tus DummyBots
|
||||
POST /api/devices {name, hardware?, firmware_version?} → devuelve token (solo 1 vez)
|
||||
POST /api/devices/{id}/rotate Invalida y crea nuevo token
|
||||
DELETE /api/devices/{id}
|
||||
```
|
||||
|
||||
### Para el ESP32 (auth.device, con token del dispositivo)
|
||||
```
|
||||
GET /api/device/tasks/pending Polling cada 30s
|
||||
POST /api/device/tasks Crear tarea (desde bot, ej. comandos)
|
||||
POST /api/device/tasks/{id}/done
|
||||
POST /api/device/tasks/{id}/snooze
|
||||
DELETE /api/device/tasks/{id}
|
||||
POST /api/device/heartbeat Ping (refresca last_seen_at)
|
||||
```
|
||||
|
||||
## 🎨 Frontend (Blade + Alpine)
|
||||
|
||||
- `pages/home.blade.php` — landing
|
||||
- `pages/login.blade.php` / `pages/register.blade.php` — auth
|
||||
- `pages/app.blade.php` — **app principal** (lista tareas, filtros, atajos teclado)
|
||||
- `pages/settings.blade.php` — gestión de dispositivos, proyectos, tags
|
||||
|
||||
### Atajos de teclado (en `/app`)
|
||||
|
||||
- `n` → nueva tarea
|
||||
- `/` → buscador
|
||||
- `Esc` → cerrar modal
|
||||
- `1` / `2` / `3` / `4` → filtro Todas / Pendientes / Hoy / Hechas
|
||||
- `s` → snooze 5 min (sobre tarea enfocada)
|
||||
|
||||
### TDAH-friendly
|
||||
|
||||
- **Sin gamificación tóxica** (sin puntos, sin streaks).
|
||||
- **No judgemental**: "sin tareas" muestra un emoji festivo, no un mensaje triste.
|
||||
- **Dark mode por defecto** con gradientes suaves.
|
||||
- **Atajos de teclado** para no tener que tocar el ratón.
|
||||
- **Filtros contextuales** (Hoy, Vencidas, Por proyecto) para evitar el agobio del "todo".
|
||||
|
||||
## 🔌 Webhooks (TODO)
|
||||
|
||||
Pendiente: hook de salida cuando se crea/marca una tarea. Pensado para integrarse con Notion, Zapier, n8n, etc.
|
||||
|
||||
## 📋 Importar / Exportar (TODO)
|
||||
|
||||
Endpoints para CSV/JSON de tareas.
|
||||
|
||||
## 🚀 Despliegue en producción
|
||||
|
||||
Ya está dockerizado. El repo está en `https://git.anabolictrack.com/javiservices/dummybot`.
|
||||
|
||||
```bash
|
||||
# En el server
|
||||
cd /root/dummybot
|
||||
git pull
|
||||
AUTO_MIGRATE=1 docker compose up -d --build
|
||||
```
|
||||
|
||||
Para que el proxy-manager lo exponga en HTTPS, añadir host en `http://157.180.77.232:81` apuntando al contenedor `dummybot_web:80`.
|
||||
|
||||
## Licencia
|
||||
|
||||
MIT
|
||||
Ver [`docs/DEPLOY.md`](docs/DEPLOY.md). Push a `main` → deploy automático en
|
||||
~30 segundos.
|
||||
|
||||
## API
|
||||
|
||||
Ver `resources/views/pages/settings.blade.php` (sección "API") o el README
|
||||
original.
|
||||
|
||||
## Hardware
|
||||
|
||||
Ver [`docs/FIRMWARE.md`](docs/FIRMWARE.md) para cableado y pinout completo.
|
||||
|
||||
244
docs/DEPLOY.md
Normal file
244
docs/DEPLOY.md
Normal file
@ -0,0 +1,244 @@
|
||||
# Despliegue automático
|
||||
|
||||
DummyBot se despliega automáticamente a producción cuando se hace push a la rama
|
||||
`main` del repositorio.
|
||||
|
||||
```
|
||||
push a main
|
||||
→ Gitea dispara webhook "deploy"
|
||||
→ POST http://dummybot_expose:80/webhook/deploy (red Docker interna)
|
||||
→ Laravel valida HMAC-SHA256 (X-Hub-Signature-256)
|
||||
→ exec('nohup deploy-runner.sh &')
|
||||
→ git pull + composer install + npm ci + npm run build
|
||||
→ php artisan migrate --force
|
||||
→ php artisan optimize
|
||||
→ Healthcheck GET /up
|
||||
→ Log en storage/logs/deploy-YYYYMMDD-HHMMSS.log
|
||||
```
|
||||
|
||||
Tiempo total: ~25-35 segundos desde push hasta server actualizado.
|
||||
|
||||
---
|
||||
|
||||
## Arquitectura
|
||||
|
||||
```
|
||||
┌─────────────────────┐
|
||||
│ git.anabolictrack │ Gitea 1.22.6
|
||||
│ (rootless, 3000) │ - Repos: dummybot, dummybot-firmware
|
||||
└──────────┬──────────┘
|
||||
│ webhook push event
|
||||
│ POST http://dummybot_expose:80/webhook/deploy
|
||||
▼
|
||||
┌─────────────────────┐
|
||||
│ 157.180.77.232:80 │ nginx-proxy-manager (172.20.0.2)
|
||||
└──────────┬──────────┘
|
||||
│ red "proxy" (172.20.0.0/16)
|
||||
▼
|
||||
┌─────────────────────┐ ┌──────────────────────┐
|
||||
│ dummybot_expose │───▶│ dummybot_web (nginx) │
|
||||
│ socat :80→web:80 │ │ /var/www/public │
|
||||
│ (172.20.0.8) │ └──────────┬───────────┘
|
||||
└─────────────────────┘ │
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ dummybot_app (php-fpm)│
|
||||
│ php artisan │
|
||||
│ /var/www │
|
||||
└──────────┬───────────┘
|
||||
│
|
||||
┌──────────▼───────────┐
|
||||
│ dummybot_db (mysql) │
|
||||
└──────────────────────┘
|
||||
```
|
||||
|
||||
**Red Docker `proxy`** (172.20.0.0/16): comparten gitea, dummybot_web,
|
||||
dummybot_expose, nginx-proxy-manager. Gitea hace POST al webhook usando
|
||||
`http://dummybot_expose:80/webhook/deploy` — resolución DNS por nombre de
|
||||
container, no atraviesa UFW ni la interfaz pública.
|
||||
|
||||
**Volúmenes clave**:
|
||||
- `/root/dummybot:/var/www` (en el container) — el repo clonado, mismo path
|
||||
en host y container.
|
||||
- `/root/dummybot.env` — backup del `.env` real (no se commitea).
|
||||
- `/root/dummybot/storage/logs/` — logs de deploy.
|
||||
|
||||
---
|
||||
|
||||
## Webhook de Gitea
|
||||
|
||||
**Endpoint**: `POST /webhook/deploy`
|
||||
**Auth**: HMAC-SHA256 con secreto compartido en header `X-Hub-Signature-256`.
|
||||
**Trigger**: push a rama `main`.
|
||||
**Branch filter**: `main` (configurado en Gitea).
|
||||
|
||||
**Validación en Laravel** (`app/Http/Controllers/WebhookController.php`):
|
||||
1. Lee `DEPLOY_WEBHOOK_SECRET` del `.env`.
|
||||
2. Calcula `hash_hmac('sha256', body, secret)` y compara con header.
|
||||
3. Decodifica payload, comprueba `ref == "refs/heads/main"`.
|
||||
4. Lanza `nohup bash deploy-runner.sh > log 2>&1 &`.
|
||||
5. Responde 202 con path al log.
|
||||
|
||||
**Excluido de CSRF** en `bootstrap/app.php`:
|
||||
```php
|
||||
$middleware->validateCsrfTokens(except: ['webhook/*']);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuración inicial del server
|
||||
|
||||
### 1. SSH key para acceso de emergencia
|
||||
|
||||
```bash
|
||||
ssh-keygen -t ed25519 -C "deploy-key" -f ~/.ssh/dummybot_deploy -N ""
|
||||
ssh-copy-id -i ~/.ssh/dummybot_deploy.pub root@157.180.77.232
|
||||
```
|
||||
|
||||
### 2. Respaldo del .env
|
||||
|
||||
```bash
|
||||
# En el server, una vez esté configurado
|
||||
cp /root/dummybot/.env /root/dummybot.env
|
||||
```
|
||||
|
||||
El deploy restaura `.env` desde `/root/dummybot.env` en cada push.
|
||||
|
||||
### 3. Generar secreto del webhook
|
||||
|
||||
```bash
|
||||
openssl rand -hex 32 # → "75f02729eecd38c9..."
|
||||
```
|
||||
|
||||
Guardarlo en dos sitios:
|
||||
- `/root/dummybot/.env`: `DEPLOY_WEBHOOK_SECRET=<secret>`
|
||||
- Configuración del webhook en Gitea (al crearlo).
|
||||
|
||||
### 4. Configurar Gitea para permitir webhooks a hosts internos
|
||||
|
||||
Gitea 1.22 tiene whitelist de hosts (`webhook.ALLOWED_HOST_LIST`). Por defecto
|
||||
está vacía y rechaza cualquier host. Editar `/etc/gitea/app.ini`:
|
||||
|
||||
```ini
|
||||
[webhook]
|
||||
ALLOWED_HOST_LIST = *
|
||||
SKIP_TLS_VERIFY = false
|
||||
```
|
||||
|
||||
Y reiniciar: `docker restart gitea`.
|
||||
|
||||
### 5. Crear el webhook en Gitea
|
||||
|
||||
```bash
|
||||
SECRET=$(cat /root/dummybot-webhook-secret)
|
||||
|
||||
curl -X POST "https://git.anabolictrack.com/api/v1/repos/javiservices/dummybot/hooks" \
|
||||
-u "javiservices:<token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"type\": \"gitea\",
|
||||
\"config\": {
|
||||
\"url\": \"http://dummybot_expose:80/webhook/deploy\",
|
||||
\"content_type\": \"json\",
|
||||
\"secret\": \"$SECRET\"
|
||||
},
|
||||
\"events\": [\"push\"],
|
||||
\"active\": true,
|
||||
\"branch_filter\": \"main\"
|
||||
}"
|
||||
```
|
||||
|
||||
**Importante**: la URL debe usar el **nombre del container** (`dummybot_expose`)
|
||||
no la IP pública. Si el container se renombra, hay que actualizar el webhook.
|
||||
|
||||
### 6. Primer deploy manual
|
||||
|
||||
Como el endpoint `/webhook/deploy` no existe hasta que se despliega por primera
|
||||
vez, hay que hacerlo manualmente:
|
||||
|
||||
```bash
|
||||
ssh root@157.180.77.232
|
||||
cd /root/dummybot
|
||||
git fetch origin main
|
||||
git reset --hard origin/main
|
||||
cp /root/dummybot.env .env
|
||||
docker compose exec -T app php artisan migrate --force
|
||||
docker compose exec -T app php artisan config:clear route:clear view:clear optimize
|
||||
docker compose up -d --remove-orphans
|
||||
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:18080/up # debe ser 200
|
||||
```
|
||||
|
||||
Tras este primer deploy, los siguientes son automáticos.
|
||||
|
||||
---
|
||||
|
||||
## Verificación
|
||||
|
||||
### Probar manualmente (sin push)
|
||||
|
||||
```bash
|
||||
SECRET=$(cat /root/dummybot-webhook-secret)
|
||||
BODY='{"ref":"refs/heads/main","pusher":{"name":"manual"}}'
|
||||
SIG="sha256=$(echo -n "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')"
|
||||
|
||||
curl -X POST "http://157.180.77.232:18080/webhook/deploy" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Hub-Signature-256: $SIG" \
|
||||
-d "$BODY"
|
||||
```
|
||||
|
||||
Respuesta esperada: `{"ok":true,"status":"deploy started","log":"deploy-..."}` con HTTP 202.
|
||||
|
||||
### Ver logs
|
||||
|
||||
```bash
|
||||
# Logs de deploy
|
||||
ls -lat /root/dummybot/storage/logs/deploy-*.log | head
|
||||
|
||||
# Log de un deploy concreto
|
||||
tail -50 /root/dummybot/storage/logs/deploy-20260618-102042.log
|
||||
|
||||
# Laravel general
|
||||
tail -f /root/dummybot/storage/logs/laravel.log
|
||||
```
|
||||
|
||||
### Probar webhook desde Gitea
|
||||
|
||||
```bash
|
||||
curl -X POST "https://git.anabolictrack.com/api/v1/repos/javiservices/dummybot/hooks/1/tests" \
|
||||
-u "javiservices:<token>"
|
||||
```
|
||||
|
||||
HTTP 204 = OK. Si hay error de entrega, ver logs del container gitea:
|
||||
`docker logs gitea --tail 50`.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Síntoma | Causa | Solución |
|
||||
|---------|-------|----------|
|
||||
| Push no dispara deploy | `webhook.ALLOWED_HOST_LIST` no configurado | Editar `/etc/gitea/app.ini`, reiniciar gitea |
|
||||
| HTTP 419 al webhook | CSRF no excluido | Revisar `bootstrap/app.php` |
|
||||
| HTTP 401 al webhook | Firma inválida | Verificar `DEPLOY_WEBHOOK_SECRET` en server y Gitea |
|
||||
| Webhook devuelve "skipped: branch != main" | Bug `substr($ref, 10)` debe ser `11` | Actualizar `WebhookController.php` |
|
||||
| Deploy log dice "Deploy OK" pero server tiene código viejo | `php artisan optimize` no recarga | Reiniciar container: `docker compose restart app` |
|
||||
| `docker compose up` falla con "container name already in use" | Container huérfano de un deploy anterior | `docker rm -f dummybot_expose` y reintentar |
|
||||
|
||||
---
|
||||
|
||||
## Por qué webhook y no Actions
|
||||
|
||||
Gitea 1.22.6 tiene Actions parcial:
|
||||
- ✓ Registro de runners funciona
|
||||
- ✓ API de secrets/variables funciona
|
||||
- ✗ API de dispatch devuelve 404
|
||||
- ✗ Ejecución automática de workflows no se dispara
|
||||
|
||||
Gitea Actions se estabilizó en 1.23+. El webhook de Gitea es la solución
|
||||
estándar pre-Actions y funciona en cualquier versión.
|
||||
|
||||
Si en el futuro se actualiza Gitea, el workflow `.gitea/workflows/deploy.yml`
|
||||
ya está listo y se puede activar configurando el secreto `DEPLOY_SSH_KEY` en
|
||||
Gitea (la SSH key pública ya está en el server en
|
||||
`/root/.ssh/authorized_keys`).
|
||||
189
docs/FIRMWARE.md
Normal file
189
docs/FIRMWARE.md
Normal file
@ -0,0 +1,189 @@
|
||||
# Firmware del ESP32
|
||||
|
||||
Firmware para el bot físico DummyBot. ESP32 + OLED SSD1306 128x64 + WiFi
|
||||
+ polling backend.
|
||||
|
||||
## Hardware
|
||||
|
||||
- **ESP32-D0WD-V3** (DevKitC / WROOM-32, 4 MB flash)
|
||||
- **Pantalla OLED SSD1306 0.96"** (128x64, I2C)
|
||||
- **3 botones** (NEXT / DONE / SNOOZE)
|
||||
- **1 motor vibrador 1027** (3V DC, MOSFET en GPIO 25)
|
||||
|
||||
### Pinout
|
||||
|
||||
| Función | GPIO | Notas |
|
||||
|------------------|-------|--------------------------------------|
|
||||
| OLED SDA | 21 | I2C data |
|
||||
| OLED SCL | 22 | I2C clock |
|
||||
| OLED addr | 0x3C | I2C address |
|
||||
| Botón NEXT | 26 | INPUT_PULLDOWN, flanco ascendente |
|
||||
| Botón DONE | 27 | INPUT_PULLDOWN, flanco ascendente |
|
||||
| Botón SNOOZE | 14 | INPUT_PULLDOWN, flanco ascendente |
|
||||
| Vibrador | 25 | OUTPUT, MOSFET gate |
|
||||
| BOOT (reset cfg) | 0 | INPUT_PULLUP, mantener 5s al arrancar |
|
||||
|
||||
### Cableado OLED
|
||||
|
||||
```
|
||||
ESP32 (WROOM-32) OLED SSD1306 0.96"
|
||||
───────────────────── ─────────────────────
|
||||
3V3 ───────▶ VDD
|
||||
GND ───────▶ GND
|
||||
GPIO 22 ───────▶ SCK (I2C SCL)
|
||||
GPIO 21 ───────▶ SDA (I2C SDA)
|
||||
```
|
||||
|
||||
### Cableado botones
|
||||
|
||||
Los botones se conectan entre GND y el GPIO correspondiente. Con
|
||||
`INPUT_PULLDOWN` interno del ESP32, el GPIO lee LOW en reposo y HIGH al
|
||||
pulsar. El firmware detecta el flanco ascendente con debounce de 200 ms.
|
||||
|
||||
```
|
||||
ESP32 Botón (entre pin y GND)
|
||||
───── ─────
|
||||
GPIO 26 ──────▶ Siguiente
|
||||
GPIO 27 ──────▶ Hecha
|
||||
GPIO 14 ──────▶ Snooze 5 min
|
||||
GND ──────▶ Común
|
||||
```
|
||||
|
||||
### Cableado vibrador
|
||||
|
||||
```
|
||||
ESP32 MOSFET (AO3400 / 2N7000) Vibrador 1027
|
||||
───── ────── ─────
|
||||
GPIO 25 ──[100Ω]──▶ Gate
|
||||
│
|
||||
├──▶ Source ──▶ GND
|
||||
│
|
||||
└──▶ Drain ──▶ Vibrador ──▶ 3V3
|
||||
```
|
||||
|
||||
Si solo se dispone de NPN (BC547 / 2N2222 / S8050), esquema alternativo:
|
||||
motor entre 3V3 y colector, resistencia 1kΩ en base.
|
||||
|
||||
## Build flags (platformio.ini)
|
||||
|
||||
```ini
|
||||
build_flags =
|
||||
-DCORE_DEBUG_LEVEL=3
|
||||
-DUSER_SETUP_LOADED=1
|
||||
-DDEFAULT_WIFI_SSID=\"PHARMACO\"
|
||||
-DDEFAULT_WIFI_PASS=\"arnoldgym2010\"
|
||||
-DBACKEND_URL=\"http://157.180.77.232:18080\"
|
||||
-DDEFAULT_DEVICE_TOKEN=\"<token-del-bot>\"
|
||||
```
|
||||
|
||||
## Build y flasheo
|
||||
|
||||
```bash
|
||||
# Compilar
|
||||
pio run
|
||||
|
||||
# Flashear
|
||||
pio run -t upload --upload-port /dev/ttyUSB0
|
||||
|
||||
# Monitor serie
|
||||
pio device monitor --port /dev/ttyUSB0 --baud 115200
|
||||
```
|
||||
|
||||
## Configuración en runtime (captive portal)
|
||||
|
||||
Si no hay config WiFi válida en NVS, el bot abre un AP `DummyBot-Setup`
|
||||
(pass `dummybot`) en `192.168.4.1`. Desde el móvil, configurar WiFi y token
|
||||
del dispositivo. El bot se reinicia automáticamente y conecta.
|
||||
|
||||
Para resetear la config: mantener pulsado el botón **BOOT (GPIO 0)** durante
|
||||
5 segundos al arrancar.
|
||||
|
||||
## Comandos serie (debug)
|
||||
|
||||
Conectar a `115200 baud`:
|
||||
|
||||
| Comando | Acción |
|
||||
|---------|-------------------------------------------------|
|
||||
| `S` | Imprime estado (WiFi, IP, tarea actual) |
|
||||
| `B0` | Simula pulsación botón NEXT (GPIO 26) |
|
||||
| `B1` | Simula pulsación botón DONE (GPIO 27) |
|
||||
| `B2` | Simula pulsación botón SNOOZE (GPIO 14) |
|
||||
| `P` | Lee estado actual de los 3 pines de botón |
|
||||
| `A` | Lectura continua del ADC en GPIO 34 (60s) |
|
||||
| `C` | Calibración guiada de umbrales ADC (3 botones) |
|
||||
| `W` | Watch: monitor rápido de cambios en pines |
|
||||
|
||||
## Arquitectura del firmware
|
||||
|
||||
```cpp
|
||||
// Namespace principales
|
||||
namespace Config { void load(); void save(); void clear(); }
|
||||
namespace UI { showBoot, showConnecting, showIdle, showTask, showPortal }
|
||||
namespace Vib { buzz(), tick() — patrón 2-buzz al recibir tarea nueva }
|
||||
namespace Api { pollPending(), markDone(), snooze() }
|
||||
namespace Input { tick() — polling 3 botones con debounce por flanco }
|
||||
namespace Portal { start(), stop(), tick() — AP + DNS + WebServer }
|
||||
namespace Wifi { wifiStartSTA(), wifiTick() — máquina de estados }
|
||||
```
|
||||
|
||||
### Máquina de estados WiFi
|
||||
|
||||
```
|
||||
BOOT → CONNECTING (timeout 20s)
|
||||
├─ OK → CONNECTED (poll cada 30s)
|
||||
└─ FAIL → PORTAL (AP DummyBot-Setup)
|
||||
|
||||
CONNECTED
|
||||
└─ WiFi perdido → CONNECTING
|
||||
|
||||
PORTAL
|
||||
└─ ESP.restart() al guardar config
|
||||
```
|
||||
|
||||
### Flujo de polling
|
||||
|
||||
```
|
||||
CONNECTED
|
||||
→ Api::pollPending() cada 30s
|
||||
→ GET /api/device/tasks/pending
|
||||
→ Si tiene tareas: muestra primera en OLED
|
||||
→ Si cambia id: buzz (vibrador)
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### WiFi no conecta
|
||||
|
||||
- Verificar SSID y password (son case-sensitive).
|
||||
- Si el router está en 5 GHz, el ESP32 solo soporta 2.4 GHz.
|
||||
- Si la red bloquea nuevos dispositivos (MAC filtering), añadir la MAC del
|
||||
ESP32 (visible en el log de `WiFi.status()`).
|
||||
|
||||
### Botones no responden
|
||||
|
||||
- Verificar que el botón hace contacto entre el GPIO y GND.
|
||||
- Comandos serie `P` muestra el estado actual de los pines. Pulsar y ver
|
||||
si cambia de 0 a 1.
|
||||
- Si el botón usa active-low (HIGH en reposo, LOW al pulsar), cambiar a
|
||||
`INPUT_PULLUP` y la lógica de flanco a descendente.
|
||||
|
||||
### OLED no enciende
|
||||
|
||||
- Verificar 3V3 en VDD del OLED.
|
||||
- Verificar SDA (GPIO 21) y SCL (GPIO 22) — pines fijos I2C del ESP32.
|
||||
- I2C addr por defecto 0x3C. Si la OLED es 0x3D, cambiar `OLED_ADDR`.
|
||||
|
||||
### API no responde
|
||||
|
||||
- Verificar que el bot tiene acceso a internet (ping al backend desde el
|
||||
ESP32 con el comando serie `S`).
|
||||
- Si el backend usa HTTPS con cert auto-firmado, hay que añadir el cert al
|
||||
firmware (más complejo) o usar HTTP plano.
|
||||
|
||||
## Versiones
|
||||
|
||||
- **v6.1** (actual): producción, sin botones (pendiente nueva placa).
|
||||
Funcionalidad core: WiFi, OLED, polling, portal cautivo.
|
||||
- **v6.0** (legacy): intentona con ADC para matriz de botones, no funcionó
|
||||
en hardware del usuario.
|
||||
- **v5** (legacy): SPIFFS en lugar de NVS, varios bugs en defaults WiFi.
|
||||
Loading…
Reference in New Issue
Block a user