mirror of
https://github.com/marcogll/s23_time-attend-v1.git
synced 2026-01-13 13:15:16 +00:00
first commit
This commit is contained in:
31
.gitignore
vendored
Normal file
31
.gitignore
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
# ==========================================
|
||||
# Ignora credenciales y ajustes locales
|
||||
# ==========================================
|
||||
secrets.h
|
||||
.env
|
||||
|
||||
# ==========================================
|
||||
# Configuración de IDEs / SO
|
||||
# ==========================================
|
||||
.vscode/
|
||||
.idea/
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# ==========================================
|
||||
# Salida de compilación de Arduino / C++
|
||||
# ==========================================
|
||||
/build/
|
||||
*.o
|
||||
*.a
|
||||
*.elf
|
||||
*.bin
|
||||
|
||||
# ==========================================
|
||||
# Archivos temporales y logs
|
||||
# ==========================================
|
||||
*.log
|
||||
*.tmp
|
||||
*.swp
|
||||
*.orig
|
||||
backup_*
|
||||
231
README.md
Normal file
231
README.md
Normal file
@@ -0,0 +1,231 @@
|
||||
# 🕒 Checador Inteligente NFC IoT (ESP8266 · MVP)
|
||||
|
||||
Este repositorio contiene el **Producto Mínimo Viable (MVP)** de un sistema de control de asistencia basado en **NFC** e **IoT**, diseñado para entornos reales de operación (oficinas, talleres, sucursales).
|
||||
|
||||
El sistema utiliza un **NodeMCU ESP8266** para leer tarjetas NFC de empleados, obtener la hora exacta desde un servidor en la nube y enviar los registros de asistencia a un **Webhook HTTP** para su posterior procesamiento (bases de datos, bots, dashboards o sistemas de RRHH).
|
||||
|
||||
---
|
||||
|
||||
## ⚡️ Guía Rápida (TL;DR)
|
||||
|
||||
1. Instala dependencias (ESP8266 core + librerías listadas más abajo) en Arduino IDE.
|
||||
2. Duplica `secrets.h.example` → `secrets.h` y rellena Wi‑Fi, URLs y zona horaria.
|
||||
3. Carga el sketch `soul23_time_attendance.ino` en tu NodeMCU 1.0 (ESP‑12E).
|
||||
4. Cablea RC522 y OLED siguiendo el **pinout seguro** descrito en este documento.
|
||||
5. Graba tarjetas NFC con **JSON válido** usando NFC Tools.
|
||||
6. Abre el monitor serie (115200) para verificar conexión, hora y peticiones HTTP.
|
||||
|
||||
---
|
||||
|
||||
## 📁 Estructura del Repositorio
|
||||
|
||||
```
|
||||
soul23_time_attendance/
|
||||
├── soul23_time_attendance.ino
|
||||
├── README.md
|
||||
├── secrets.h.example
|
||||
├── secrets.h
|
||||
└── test/
|
||||
```
|
||||
|
||||
> Solo es necesario editar `secrets.h` para credenciales y constantes sensibles.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Funcionalidades
|
||||
|
||||
* **Lectura NFC/RFID** (Mifare / NTAG 13.56 MHz)
|
||||
* **Decodificación NDEF en JSON**
|
||||
* **Sincronización de hora en la nube** (sin RTC físico)
|
||||
* **Feedback visual en OLED**
|
||||
* **Integración vía Webhook HTTP (POST JSON)**
|
||||
|
||||
---
|
||||
|
||||
## 🧠 Arquitectura del Firmware
|
||||
|
||||
| Módulo | Función |
|
||||
| ---------------------- | -------------------------------------------------- |
|
||||
| `setup()` | Inicializa Wi‑Fi, OLED, SPI, NFC y sincroniza hora |
|
||||
| `loop()` | Muestra hora, refresca estado y espera tarjeta |
|
||||
| `processTag()` | Lee UID + JSON NFC y dispara envío |
|
||||
| `syncTimeWithServer()` | Obtiene `unixtime` desde servidor |
|
||||
| `sendToWebhook()` | Envía payload JSON vía POST |
|
||||
|
||||
---
|
||||
|
||||
## 🔬 Flujo de Operación
|
||||
|
||||
1. **Arranque:** conexión Wi‑Fi + sincronización de hora.
|
||||
2. **Espera:** muestra reloj y mensaje *ACERQUE TARJETA*.
|
||||
3. **Lectura NFC:** UID + payload JSON.
|
||||
4. **Validación:** deserialización segura.
|
||||
5. **Envío:** POST al webhook.
|
||||
6. **Feedback:** confirmación visual/sonora.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Hardware Requerido
|
||||
|
||||
| Componente | Descripción |
|
||||
| --------------- | ----------------- |
|
||||
| NodeMCU ESP8266 | MCU con Wi‑Fi |
|
||||
| RC522 | Lector NFC SPI |
|
||||
| OLED 0.96" | SSD1306 I2C |
|
||||
| LED | Indicador visual |
|
||||
| Buzzer | Indicador audible |
|
||||
| Tags NFC | NTAG213 / Mifare |
|
||||
|
||||
---
|
||||
|
||||
## 🔌 Pinout Seguro (Producción)
|
||||
|
||||
### RC522 (SPI)
|
||||
|
||||
| RC522 | NodeMCU | GPIO | Color Cable | Nota |
|
||||
| ----- | ------- | ------ | ----------- | ---------------------------------- |
|
||||
| SDA | D8 | GPIO15 | Azul | Pull‑down interno (seguro como SS) |
|
||||
| SCK | D5 | GPIO14 | Amarillo | SPI |
|
||||
| MOSI | D7 | GPIO13 | Morado | SPI |
|
||||
| MISO | D6 | GPIO12 | Blanco | SPI |
|
||||
| IRQ | NC | — | Naranja | No conectado |
|
||||
| GND | GND | — | Verde | Tierra común |
|
||||
| RST | D0 | GPIO16 | Negro | Seguro para reset |
|
||||
| 3.3V | 3.3V | — | Rojo | ⚠️ No usar 5V |
|
||||
|
||||
### OLED (I2C)
|
||||
|
||||
| OLED | NodeMCU | Color Cable |
|
||||
| ---- | ------- | ----------- |
|
||||
| SDA | D2 | Azul |
|
||||
| SCL | D1 | Naranja |
|
||||
| VCC | 3.3V | Rojo |
|
||||
| GND | GND | Negro |
|
||||
|
||||
### LED y Buzzer
|
||||
|
||||
| Componente | NodeMCU | GPIO | Uso |
|
||||
| ---------- | ------- | ----- | --------------- |
|
||||
| LED | D3 | GPIO0 | Estado visual |
|
||||
| Buzzer | D4 | GPIO2 | Feedback sonoro |
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Pull‑Up Resistors y Pines de Boot (Explicación Clave)
|
||||
|
||||
El **ESP8266 tiene pines críticos durante el arranque**. Si alguno está en el nivel incorrecto, el microcontrolador entra en **modo programación** o **falla el boot**.
|
||||
|
||||
### GPIO 0 (D3) – LED
|
||||
|
||||
* **Requisito:** debe estar en **HIGH** durante el boot.
|
||||
* **Riesgo:** un LED mal cableado puede forzar LOW.
|
||||
* **Solución aplicada:**
|
||||
|
||||
* El firmware inicializa primero:
|
||||
|
||||
```cpp
|
||||
pinMode(LED_PIN, INPUT_PULLUP);
|
||||
```
|
||||
* Luego se configura como salida cuando el sistema ya arrancó.
|
||||
* **Resultado:** se evita entrar en modo flash accidentalmente.
|
||||
|
||||
### GPIO 2 (D4) – Buzzer
|
||||
|
||||
* Tiene **pull‑up interno por defecto**.
|
||||
* **Problema común:** un buzzer pasivo conectado durante la carga puede generar interferencia.
|
||||
* **Buenas prácticas:**
|
||||
|
||||
* Inicializar el buzzer **al final del `setup()`**.
|
||||
* Desconectar GND del buzzer durante la carga del firmware.
|
||||
* Opcional: resistencia serie de **220 Ω**.
|
||||
|
||||
### GPIO 15 (D8)
|
||||
|
||||
* Tiene **pull‑down interno**.
|
||||
* Ideal para **SS (SDA) del RC522**.
|
||||
* **Nunca usar** para señales que requieran HIGH en boot.
|
||||
|
||||
### GPIO 16 (D0)
|
||||
|
||||
* No tiene pull‑up/pull‑down.
|
||||
* Seguro para RST del RC522.
|
||||
* No soporta PWM.
|
||||
|
||||
---
|
||||
|
||||
## 💳 Formato del Payload NFC (Entrada)
|
||||
|
||||
El tag NFC debe contener un **registro NDEF de texto** con JSON válido.
|
||||
|
||||
Ejemplo recomendado:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Juan",
|
||||
"num_emp": "XXXX250117",
|
||||
"sucursal": "sucursal_1",
|
||||
"telegram_id": "123456789"
|
||||
}
|
||||
```
|
||||
|
||||
Campos adicionales son ignorados de forma segura si no se usan.
|
||||
|
||||
---
|
||||
|
||||
## 📡 Payload Enviado al Webhook (Salida)
|
||||
|
||||
Ejemplo real del POST generado por el dispositivo:
|
||||
|
||||
```json
|
||||
{
|
||||
"card_uid": "04A1B2C3",
|
||||
"name": "Juan",
|
||||
"num_emp": "XXXX250117",
|
||||
"sucursal": "sucursal_1",
|
||||
"telegram_id": "123456789",
|
||||
"timestamp": 1765913424,
|
||||
"device": "Checador_Oficina_1"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Configuración (`secrets.h`)
|
||||
|
||||
```cpp
|
||||
const char* ssid = "TU_WIFI";
|
||||
const char* password = "TU_PASSWORD";
|
||||
|
||||
const char* webhookUrl = "https://api.tudominio.com/webhook";
|
||||
const char* timeServerUrl = "https://soul23.cloud/time-server";
|
||||
|
||||
#define DEVICE_NAME "Checador_Oficina_1"
|
||||
#define TIMEZONE_OFFSET -21600
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Troubleshooting Rápido
|
||||
|
||||
* **No arranca:** revisar GPIO0 y GPIO2.
|
||||
* **No lee NFC:** revisar 3.3 V y RST.
|
||||
* **No carga firmware:** desconectar buzzer temporalmente.
|
||||
* **JSON inválido:** validar comillas rectas y formato.
|
||||
|
||||
---
|
||||
|
||||
## 📌 Estado
|
||||
|
||||
**MVP funcional**, listo para pilotos, dashboards y automatización RH.
|
||||
|
||||
---
|
||||
|
||||
## 👤 Autor
|
||||
|
||||
**Marco** — Inventor · Automatización · IoT
|
||||
|
||||
---
|
||||
|
||||
## 📄 Licencia
|
||||
|
||||
MIT
|
||||
251
compile_and_upload.sh
Executable file
251
compile_and_upload.sh
Executable file
@@ -0,0 +1,251 @@
|
||||
#!/bin/bash
|
||||
# Script para compilar, cargar y revisar el monitor serial del checador NFC
|
||||
|
||||
# Colores para output
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
RED='\033[0;31m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m' # No Color
|
||||
BOLD='\033[1m'
|
||||
|
||||
# Configuración
|
||||
# Obtener el directorio del script para hacerlo portable
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SKETCH_DIR="$SCRIPT_DIR"
|
||||
SKETCH_NAME="soul23_time_attendance"
|
||||
FQBN="esp8266:esp8266:nodemcuv2" # NodeMCU 1.0 (ESP-12E Module)
|
||||
PORT="/dev/ttyUSB0"
|
||||
BAUD_RATE="115200"
|
||||
|
||||
# Función para mostrar barra de progreso
|
||||
show_progress() {
|
||||
local current=$1
|
||||
local total=$2
|
||||
local width=50
|
||||
local percent=$((current * 100 / total))
|
||||
local filled=$((current * width / total))
|
||||
local empty=$((width - filled))
|
||||
|
||||
printf "\r${CYAN}["
|
||||
printf "%${filled}s" | tr ' ' '█'
|
||||
printf "%${empty}s" | tr ' ' '░'
|
||||
printf "] ${percent}%%${NC}"
|
||||
}
|
||||
|
||||
# Función para verificar estado del dispositivo
|
||||
check_device_status() {
|
||||
local port=$1
|
||||
local status=""
|
||||
local details=""
|
||||
|
||||
if [ -e "$port" ]; then
|
||||
# Verificar si el puerto está en la lista de arduino-cli
|
||||
local board_list=$(arduino-cli board list 2>/dev/null)
|
||||
if echo "$board_list" | grep -q "$port"; then
|
||||
local board_info=$(echo "$board_list" | grep "$port")
|
||||
local protocol=$(echo "$board_info" | awk '{print $2}')
|
||||
local board_type=$(echo "$board_info" | awk '{print $3}')
|
||||
|
||||
if echo "$board_info" | grep -q "Unknown"; then
|
||||
status="${YELLOW}⚠️ Conectado${NC}"
|
||||
details="(Placa no identificada - Protocolo: $protocol)"
|
||||
else
|
||||
local board_name=$(echo "$board_info" | awk '{for(i=4;i<=NF;i++) printf $i" "; print ""}')
|
||||
status="${GREEN}✅ Conectado${NC}"
|
||||
details="($board_name)"
|
||||
fi
|
||||
else
|
||||
status="${YELLOW}⚠️ Puerto existe${NC}"
|
||||
details="(No detectado por arduino-cli - puede requerir permisos)"
|
||||
fi
|
||||
else
|
||||
status="${RED}❌ No conectado${NC}"
|
||||
details="(Puerto $port no encontrado)"
|
||||
fi
|
||||
|
||||
echo -e "$status $details"
|
||||
}
|
||||
|
||||
# Función para detectar puerto automáticamente
|
||||
detect_port() {
|
||||
local detected_port=$(arduino-cli board list | grep -E "ttyUSB|ttyACM" | head -1 | awk '{print $1}')
|
||||
if [ -n "$detected_port" ]; then
|
||||
echo "$detected_port"
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
# Encabezado
|
||||
clear
|
||||
echo -e "${BOLD}${BLUE}╔══════════════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${BOLD}${BLUE}║ Checador NFC - Compilación y Carga Automática ║${NC}"
|
||||
echo -e "${BOLD}${BLUE}╚══════════════════════════════════════════════════════════╝${NC}\n"
|
||||
|
||||
# Paso 0: Verificar secrets.h
|
||||
echo -e "${BOLD}${CYAN}[0/4]${NC} Verificando configuración..."
|
||||
if [ ! -f "$SKETCH_DIR/secrets.h" ]; then
|
||||
echo -e "${RED}❌ Error: No se encuentra secrets.h${NC}"
|
||||
echo -e "${YELLOW}💡 Crea secrets.h desde secrets.h.example y configura tus credenciales${NC}"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${GREEN}✅ secrets.h encontrado${NC}\n"
|
||||
|
||||
# Paso 1: Verificar y detectar dispositivo
|
||||
echo -e "${BOLD}${CYAN}[1/4]${NC} Verificando conexión del dispositivo..."
|
||||
echo -e " Puerto configurado: ${BOLD}$PORT${NC}"
|
||||
|
||||
# Verificar puerto configurado
|
||||
echo -e " Estado: $(check_device_status "$PORT")"
|
||||
|
||||
if [ ! -e "$PORT" ] || ! arduino-cli board list 2>/dev/null | grep -q "$PORT"; then
|
||||
echo -e "\n${YELLOW}🔍 Buscando dispositivos disponibles...${NC}"
|
||||
|
||||
# Intentar detectar automáticamente
|
||||
auto_port=$(detect_port)
|
||||
if [ -n "$auto_port" ]; then
|
||||
echo -e "${GREEN} ✓ Dispositivo detectado en: $auto_port${NC}"
|
||||
read -p " ¿Usar este puerto? (s/n): " -n 1 -r
|
||||
echo
|
||||
if [[ $REPLY =~ ^[SsYy]$ ]]; then
|
||||
PORT="$auto_port"
|
||||
echo -e " Estado: $(check_device_status "$PORT")"
|
||||
else
|
||||
echo -e "${RED}❌ Por favor, conecta el dispositivo y vuelve a intentar${NC}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo -e "${RED} ✗ No se encontraron dispositivos${NC}"
|
||||
echo -e "\n${YELLOW}Puertos disponibles:${NC}"
|
||||
arduino-cli board list
|
||||
echo -e "\n${YELLOW}Por favor, conecta el dispositivo o modifica PORT en el script${NC}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo -e " Puerto seleccionado: ${BOLD}$PORT${NC}\n"
|
||||
|
||||
# Paso 2: Compilar
|
||||
echo -e "${BOLD}${CYAN}[2/4]${NC} Compilando el sketch..."
|
||||
echo -e " FQBN: ${BOLD}$FQBN${NC}"
|
||||
echo -e " Sketch: ${BOLD}$SKETCH_NAME.ino${NC}\n"
|
||||
|
||||
# Compilar con barra de progreso animada
|
||||
compile_output=$(mktemp)
|
||||
(
|
||||
arduino-cli compile --fqbn "$FQBN" "$SKETCH_DIR/$SKETCH_NAME.ino" > "$compile_output" 2>&1
|
||||
echo $? > "${compile_output}.exit"
|
||||
) &
|
||||
compile_pid=$!
|
||||
|
||||
# Mostrar barra de progreso animada mientras compila
|
||||
spinner="|/-\\"
|
||||
spinner_idx=0
|
||||
while kill -0 $compile_pid 2>/dev/null; do
|
||||
spinner_char=${spinner:$spinner_idx:1}
|
||||
printf "\r ${CYAN}[${spinner_char}]${NC} Compilando... ${CYAN}%s${NC}" "$spinner_char"
|
||||
spinner_idx=$(( (spinner_idx + 1) % 4 ))
|
||||
sleep 0.2
|
||||
done
|
||||
|
||||
wait $compile_pid
|
||||
compile_result=$(cat "${compile_output}.exit" 2>/dev/null || echo "1")
|
||||
rm -f "${compile_output}.exit"
|
||||
|
||||
printf "\r ${GREEN}[✓]${NC} Compilación completada \n"
|
||||
|
||||
# Mostrar errores si los hay
|
||||
if [ $compile_result -ne 0 ]; then
|
||||
echo -e "\n${RED}❌ Error en la compilación:${NC}"
|
||||
cat "$compile_output" | grep -i "error\|warning" | head -10
|
||||
rm -f "$compile_output"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Mostrar resumen de compilación
|
||||
if [ -s "$compile_output" ]; then
|
||||
size_info=$(grep -i "sketch uses\|global variables" "$compile_output" | head -2)
|
||||
if [ -n "$size_info" ]; then
|
||||
echo -e " ${BLUE}ℹ️${NC} $(echo "$size_info" | tr '\n' ' ')"
|
||||
fi
|
||||
fi
|
||||
rm -f "$compile_output"
|
||||
|
||||
echo -e "${GREEN}✅ Compilación exitosa${NC}\n"
|
||||
|
||||
# Paso 3: Cargar al dispositivo
|
||||
echo -e "${BOLD}${CYAN}[3/4]${NC} Cargando firmware al dispositivo..."
|
||||
echo -e " Puerto: ${BOLD}$PORT${NC}"
|
||||
|
||||
# Verificar nuevamente que el dispositivo está conectado
|
||||
if [ ! -e "$PORT" ]; then
|
||||
echo -e "${RED}❌ Error: El dispositivo se desconectó${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verificar estado antes de cargar
|
||||
echo -e " Estado: $(check_device_status "$PORT")\n"
|
||||
|
||||
# Cargar con indicador de progreso
|
||||
upload_output=$(mktemp)
|
||||
(
|
||||
arduino-cli upload -p "$PORT" --fqbn "$FQBN" "$SKETCH_DIR/$SKETCH_NAME.ino" > "$upload_output" 2>&1
|
||||
echo $? > "${upload_output}.exit"
|
||||
) &
|
||||
upload_pid=$!
|
||||
|
||||
# Mostrar spinner animado durante la carga
|
||||
spinner="|/-\\"
|
||||
spinner_idx=0
|
||||
stage=0
|
||||
stages=("Iniciando" "Escribiendo" "Verificando" "Completando")
|
||||
while kill -0 $upload_pid 2>/dev/null; do
|
||||
spinner_char=${spinner:$spinner_idx:1}
|
||||
# Cambiar etapa cada 2 segundos aproximadamente
|
||||
if [ $((spinner_idx % 10)) -eq 0 ] && [ $stage -lt ${#stages[@]} ]; then
|
||||
stage=$((stage + 1))
|
||||
fi
|
||||
if [ $stage -ge ${#stages[@]} ]; then
|
||||
stage=$((stage % ${#stages[@]}))
|
||||
fi
|
||||
printf "\r ${CYAN}[${spinner_char}]${NC} ${stages[$stage]}... ${CYAN}%s${NC}" "$spinner_char"
|
||||
spinner_idx=$(( (spinner_idx + 1) % 4 ))
|
||||
sleep 0.2
|
||||
done
|
||||
|
||||
wait $upload_pid
|
||||
upload_result=$(cat "${upload_output}.exit" 2>/dev/null || echo "1")
|
||||
rm -f "${upload_output}.exit"
|
||||
|
||||
printf "\r ${GREEN}[✓]${NC} Carga completada \n"
|
||||
|
||||
if [ $upload_result -ne 0 ]; then
|
||||
echo -e "\n${RED}❌ Error al cargar el firmware:${NC}"
|
||||
cat "$upload_output" | grep -i "error\|failed\|timeout" | head -5
|
||||
rm -f "$upload_output"
|
||||
echo -e "\n${YELLOW}💡 Verifica que:${NC}"
|
||||
echo -e " • El dispositivo esté conectado"
|
||||
echo -e " • No haya otro programa usando el puerto"
|
||||
echo -e " • Presiona el botón RESET del ESP8266 si es necesario"
|
||||
echo -e " • Mantén presionado BOOT mientras cargas si es necesario"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -f "$upload_output"
|
||||
echo -e "${GREEN}✅ Firmware cargado exitosamente${NC}\n"
|
||||
|
||||
# Paso 4: Monitor serial
|
||||
echo -e "${BOLD}${CYAN}[4/4]${NC} Abriendo monitor serial..."
|
||||
echo -e " Puerto: ${BOLD}$PORT${NC}"
|
||||
echo -e " Baud rate: ${BOLD}$BAUD_RATE${NC}"
|
||||
echo -e "\n${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
||||
echo -e "${YELLOW} Monitor Serial (Presiona ${BOLD}Ctrl+C${NC}${YELLOW} para salir)${NC}"
|
||||
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"
|
||||
|
||||
# Pequeña pausa antes de abrir el monitor
|
||||
sleep 1
|
||||
|
||||
# Abrir monitor serial
|
||||
arduino-cli monitor -p "$PORT" --config baudrate="$BAUD_RATE"
|
||||
332
compiler_info.md
Normal file
332
compiler_info.md
Normal file
@@ -0,0 +1,332 @@
|
||||
# 🔧 Guía de Compilación y Carga del Firmware
|
||||
|
||||
Esta guía te ayudará a compilar, cargar y monitorear el firmware del checador NFC en tu ESP8266 NodeMCU.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Método Rápido (Script Automático)
|
||||
|
||||
El proyecto incluye un script automatizado que realiza todo el proceso en un solo comando:
|
||||
|
||||
```bash
|
||||
./compile_and_upload.sh
|
||||
```
|
||||
|
||||
### ✨ Características del Script
|
||||
|
||||
#### 1. **Verificación de Estado del Dispositivo** 🔌
|
||||
- ✅ Detecta automáticamente si el ESP8266 está conectado
|
||||
- ✅ Muestra información detallada del puerto y tipo de placa
|
||||
- ✅ Detecta automáticamente el puerto si el configurado no está disponible
|
||||
- ✅ Verifica el estado antes de cada operación crítica
|
||||
|
||||
#### 2. **Barras de Progreso y Spinners** 📊
|
||||
- ✅ Spinner animado durante la compilación
|
||||
- ✅ Indicadores de progreso durante la carga del firmware
|
||||
- ✅ Etapas visuales: "Iniciando", "Escribiendo", "Verificando", "Completando"
|
||||
- ✅ Feedback visual claro en cada paso
|
||||
|
||||
#### 3. **Interfaz Mejorada** 🎨
|
||||
- ✅ Encabezado visual con bordes
|
||||
- ✅ Colores diferenciados para cada tipo de mensaje
|
||||
- ✅ Indicadores de progreso paso a paso [1/4], [2/4], etc.
|
||||
- ✅ Mensajes de error más descriptivos con sugerencias
|
||||
|
||||
#### 4. **Detección Automática** 🔍
|
||||
- ✅ Busca automáticamente dispositivos disponibles si el puerto configurado no funciona
|
||||
- ✅ Pregunta al usuario si desea usar el puerto detectado
|
||||
- ✅ Muestra todos los puertos disponibles si no encuentra ninguno
|
||||
|
||||
### 📋 Ejemplo de Salida del Script
|
||||
|
||||
```
|
||||
╔══════════════════════════════════════════════════════════╗
|
||||
║ Checador NFC - Compilación y Carga Automática ║
|
||||
╚══════════════════════════════════════════════════════════╝
|
||||
|
||||
[0/4] Verificando configuración...
|
||||
✅ secrets.h encontrado
|
||||
|
||||
[1/4] Verificando conexión del dispositivo...
|
||||
Puerto configurado: /dev/ttyUSB0
|
||||
Estado: ✅ Conectado (NodeMCU 1.0)
|
||||
Puerto seleccionado: /dev/ttyUSB0
|
||||
|
||||
[2/4] Compilando el sketch...
|
||||
FQBN: esp8266:esp8266:nodemcuv2
|
||||
Sketch: soul23_time_attendance.ino
|
||||
[|] Compilando... | ← Spinner animado
|
||||
[✓] Compilación completada
|
||||
✅ Compilación exitosa
|
||||
|
||||
[3/4] Cargando firmware al dispositivo...
|
||||
Puerto: /dev/ttyUSB0
|
||||
Estado: ✅ Conectado (NodeMCU 1.0)
|
||||
[|] Escribiendo... | ← Spinner con etapas
|
||||
[✓] Carga completada
|
||||
✅ Firmware cargado exitosamente
|
||||
|
||||
[4/4] Abriendo monitor serial...
|
||||
Puerto: /dev/ttyUSB0
|
||||
Baud rate: 115200
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
Monitor Serial (Presiona Ctrl+C para salir)
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
```
|
||||
|
||||
### 🛠️ Personalización del Script
|
||||
|
||||
Puedes modificar estas variables al inicio del script `compile_and_upload.sh`:
|
||||
|
||||
```bash
|
||||
PORT="/dev/ttyUSB0" # Puerto del dispositivo
|
||||
FQBN="esp8266:esp8266:nodemcuv2" # Tipo de placa
|
||||
BAUD_RATE="115200" # Velocidad del monitor serial
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 Comandos Individuales
|
||||
|
||||
Si prefieres ejecutar los comandos manualmente o necesitas más control:
|
||||
|
||||
### 1. Verificar puertos disponibles
|
||||
|
||||
```bash
|
||||
arduino-cli board list
|
||||
```
|
||||
|
||||
### 2. Compilar el sketch
|
||||
|
||||
```bash
|
||||
arduino-cli compile --fqbn esp8266:esp8266:nodemcuv2 soul23_time_attendance.ino
|
||||
```
|
||||
|
||||
**Nota:** El FQBN `esp8266:esp8266:nodemcuv2` es para NodeMCU 1.0 (ESP-12E Module).
|
||||
|
||||
### 3. Cargar al dispositivo
|
||||
|
||||
```bash
|
||||
arduino-cli upload -p /dev/ttyUSB0 --fqbn esp8266:esp8266:nodemcuv2 soul23_time_attendance.ino
|
||||
```
|
||||
|
||||
**⚠️ Importante:** Reemplaza `/dev/ttyUSB0` con el puerto correcto de tu dispositivo (verifica con `arduino-cli board list`)
|
||||
|
||||
### 4. Abrir monitor serial
|
||||
|
||||
```bash
|
||||
arduino-cli monitor -p /dev/ttyUSB0 --config baudrate=115200
|
||||
```
|
||||
|
||||
**Para salir del monitor:** Presiona `Ctrl+C`
|
||||
|
||||
### 5. Compilar sin cargar (solo verificar errores)
|
||||
|
||||
```bash
|
||||
arduino-cli compile --fqbn esp8266:esp8266:nodemcuv2 soul23_time_attendance.ino
|
||||
```
|
||||
|
||||
### 6. Ver información detallada de compilación
|
||||
|
||||
```bash
|
||||
arduino-cli compile --fqbn esp8266:esp8266:nodemcuv2 --verbose soul23_time_attendance.ino
|
||||
```
|
||||
|
||||
### 7. Limpiar archivos de compilación
|
||||
|
||||
```bash
|
||||
rm -rf build/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 Instalación de Dependencias
|
||||
|
||||
### Instalar Core de ESP8266
|
||||
|
||||
```bash
|
||||
arduino-cli core install esp8266:esp8266
|
||||
```
|
||||
|
||||
### Instalar Librerías Necesarias
|
||||
|
||||
```bash
|
||||
# Librerías principales
|
||||
arduino-cli lib install "MFRC522"
|
||||
arduino-cli lib install "Adafruit SSD1306"
|
||||
arduino-cli lib install "Adafruit GFX Library"
|
||||
arduino-cli lib install "ArduinoJson"
|
||||
arduino-cli lib install "Time"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 Verificar Instalación
|
||||
|
||||
### Ver librerías instaladas
|
||||
|
||||
```bash
|
||||
arduino-cli lib list
|
||||
```
|
||||
|
||||
### Ver cores instalados
|
||||
|
||||
```bash
|
||||
arduino-cli core list
|
||||
```
|
||||
|
||||
### Verificar FQBN correcto
|
||||
|
||||
```bash
|
||||
arduino-cli board listall | grep -i nodemcu
|
||||
```
|
||||
|
||||
Salida esperada:
|
||||
```
|
||||
NodeMCU 0.9 (ESP-12 Module) esp8266:esp8266:nodemcu
|
||||
NodeMCU 1.0 (ESP-12E Module) esp8266:esp8266:nodemcuv2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 Solución de Problemas
|
||||
|
||||
### Error: "No se encuentra secrets.h"
|
||||
|
||||
```bash
|
||||
cp secrets.h.example secrets.h
|
||||
# Luego edita secrets.h con tus credenciales
|
||||
```
|
||||
|
||||
### Error: "Puerto no encontrado"
|
||||
|
||||
1. Verifica que el ESP8266 esté conectado:
|
||||
```bash
|
||||
arduino-cli board list
|
||||
```
|
||||
|
||||
2. Si no aparece, verifica permisos:
|
||||
```bash
|
||||
sudo usermod -a -G dialout $USER
|
||||
```
|
||||
Luego reinicia sesión o ejecuta:
|
||||
```bash
|
||||
newgrp dialout
|
||||
```
|
||||
|
||||
### Error: "FQBN no válido"
|
||||
|
||||
Verifica el modelo exacto de tu NodeMCU y ajusta el FQBN:
|
||||
- **NodeMCU 1.0 (ESP-12E):** `esp8266:esp8266:nodemcuv2`
|
||||
- **NodeMCU 0.9:** `esp8266:esp8266:nodemcu`
|
||||
|
||||
### Monitor serial no muestra datos
|
||||
|
||||
1. Verifica el baud rate (debe ser **115200**)
|
||||
2. Presiona el botón **RESET** del ESP8266
|
||||
3. Verifica que el puerto sea correcto
|
||||
4. Asegúrate de que no haya otro programa usando el puerto serial
|
||||
|
||||
### Error durante la carga del firmware
|
||||
|
||||
1. **Desconecta el buzzer** temporalmente (desconecta el GND) durante la carga
|
||||
2. Verifica que no haya otro programa usando el puerto
|
||||
3. Presiona el botón **RESET** del ESP8266 antes de cargar
|
||||
4. Si es necesario, mantén presionado el botón **BOOT** mientras cargas
|
||||
|
||||
### El script no encuentra el dispositivo
|
||||
|
||||
1. El script buscará automáticamente puertos disponibles
|
||||
2. Si encuentra uno, te preguntará si deseas usarlo
|
||||
3. Si no encuentra ninguno, verifica:
|
||||
- Que el cable USB esté bien conectado
|
||||
- Que el driver USB-Serial esté instalado
|
||||
- Que tengas permisos para acceder al puerto
|
||||
|
||||
---
|
||||
|
||||
## 💡 Tips y Mejores Prácticas
|
||||
|
||||
### Uso del Script Automático
|
||||
|
||||
- El script detecta automáticamente el puerto si el configurado no está disponible
|
||||
- Los spinners muestran que el proceso está en ejecución
|
||||
- Los mensajes de error incluyen sugerencias para solucionarlos
|
||||
- El estado del dispositivo se verifica antes de cada operación crítica
|
||||
|
||||
### Desarrollo y Depuración
|
||||
|
||||
- **Mantén el monitor serial abierto** mientras pruebas para ver los mensajes de debug
|
||||
- El código imprime información útil en Serial a 115200 baud
|
||||
- Si cambias el código, solo necesitas recompilar y cargar (no necesitas cerrar el monitor)
|
||||
- Para depuración, busca mensajes que empiezan con `--- Checador IOT (Public Version) ---`
|
||||
|
||||
### Flujo de Trabajo Recomendado
|
||||
|
||||
1. **Primera vez:**
|
||||
```bash
|
||||
# Instalar dependencias
|
||||
arduino-cli core install esp8266:esp8266
|
||||
arduino-cli lib install "MFRC522" "Adafruit SSD1306" "Adafruit GFX Library" "ArduinoJson" "Time"
|
||||
|
||||
# Configurar secrets.h
|
||||
cp secrets.h.example secrets.h
|
||||
# Editar secrets.h con tus credenciales
|
||||
|
||||
# Compilar y cargar
|
||||
./compile_and_upload.sh
|
||||
```
|
||||
|
||||
2. **Desarrollo iterativo:**
|
||||
```bash
|
||||
# Solo necesitas ejecutar el script
|
||||
./compile_and_upload.sh
|
||||
```
|
||||
|
||||
3. **Solo verificar compilación:**
|
||||
```bash
|
||||
arduino-cli compile --fqbn esp8266:esp8266:nodemcuv2 soul23_time_attendance.ino
|
||||
```
|
||||
|
||||
### Optimización
|
||||
|
||||
- Si tienes problemas de espacio, puedes limpiar archivos de compilación:
|
||||
```bash
|
||||
rm -rf build/
|
||||
```
|
||||
- Para compilaciones más rápidas, el script ya optimiza el proceso automáticamente
|
||||
|
||||
---
|
||||
|
||||
## 📝 Resumen de Comandos Rápidos
|
||||
|
||||
```bash
|
||||
# Método rápido (recomendado)
|
||||
./compile_and_upload.sh
|
||||
|
||||
# Comandos individuales
|
||||
arduino-cli board list # Ver puertos
|
||||
arduino-cli compile --fqbn esp8266:esp8266:nodemcuv2 soul23_time_attendance.ino
|
||||
arduino-cli upload -p /dev/ttyUSB0 --fqbn esp8266:esp8266:nodemcuv2 soul23_time_attendance.ino
|
||||
arduino-cli monitor -p /dev/ttyUSB0 --config baudrate=115200
|
||||
|
||||
# Verificación
|
||||
arduino-cli lib list # Ver librerías
|
||||
arduino-cli core list # Ver cores
|
||||
arduino-cli board listall | grep -i nodemcu # Ver placas disponibles
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 Próximos Pasos
|
||||
|
||||
Una vez que hayas compilado y cargado el firmware exitosamente:
|
||||
|
||||
1. ✅ Verifica que el dispositivo se conecte a WiFi
|
||||
2. ✅ Revisa el monitor serial para confirmar la sincronización de tiempo
|
||||
3. ✅ Prueba acercando una tarjeta NFC para verificar la lectura
|
||||
4. ✅ Verifica que los registros se envíen correctamente al webhook
|
||||
|
||||
Para más información sobre el hardware y la configuración, consulta el [README.md](README.md).
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
2
kicad/attendance_1/attendance_1.kicad_pcb
Normal file
2
kicad/attendance_1/attendance_1.kicad_pcb
Normal file
@@ -0,0 +1,2 @@
|
||||
(kicad_pcb (version 20241229) (generator "pcbnew") (generator_version "9.0")
|
||||
)
|
||||
98
kicad/attendance_1/attendance_1.kicad_prl
Normal file
98
kicad/attendance_1/attendance_1.kicad_prl
Normal file
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"board": {
|
||||
"active_layer": 0,
|
||||
"active_layer_preset": "",
|
||||
"auto_track_width": true,
|
||||
"hidden_netclasses": [],
|
||||
"hidden_nets": [],
|
||||
"high_contrast_mode": 0,
|
||||
"net_color_mode": 1,
|
||||
"opacity": {
|
||||
"images": 0.6,
|
||||
"pads": 1.0,
|
||||
"shapes": 1.0,
|
||||
"tracks": 1.0,
|
||||
"vias": 1.0,
|
||||
"zones": 0.6
|
||||
},
|
||||
"selection_filter": {
|
||||
"dimensions": true,
|
||||
"footprints": true,
|
||||
"graphics": true,
|
||||
"keepouts": true,
|
||||
"lockedItems": false,
|
||||
"otherItems": true,
|
||||
"pads": true,
|
||||
"text": true,
|
||||
"tracks": true,
|
||||
"vias": true,
|
||||
"zones": true
|
||||
},
|
||||
"visible_items": [
|
||||
"vias",
|
||||
"footprint_text",
|
||||
"footprint_anchors",
|
||||
"ratsnest",
|
||||
"grid",
|
||||
"footprints_front",
|
||||
"footprints_back",
|
||||
"footprint_values",
|
||||
"footprint_references",
|
||||
"tracks",
|
||||
"drc_errors",
|
||||
"drawing_sheet",
|
||||
"bitmaps",
|
||||
"pads",
|
||||
"zones",
|
||||
"drc_warnings",
|
||||
"drc_exclusions",
|
||||
"locked_item_shadows",
|
||||
"conflict_shadows",
|
||||
"shapes"
|
||||
],
|
||||
"visible_layers": "ffffffff_ffffffff_ffffffff_ffffffff",
|
||||
"zone_display_mode": 0
|
||||
},
|
||||
"git": {
|
||||
"repo_type": "",
|
||||
"repo_username": "",
|
||||
"ssh_key": ""
|
||||
},
|
||||
"meta": {
|
||||
"filename": "attendance_1.kicad_prl",
|
||||
"version": 5
|
||||
},
|
||||
"net_inspector_panel": {
|
||||
"col_hidden": [],
|
||||
"col_order": [],
|
||||
"col_widths": [],
|
||||
"custom_group_rules": [],
|
||||
"expanded_rows": [],
|
||||
"filter_by_net_name": true,
|
||||
"filter_by_netclass": true,
|
||||
"filter_text": "",
|
||||
"group_by_constraint": false,
|
||||
"group_by_netclass": false,
|
||||
"show_unconnected_nets": false,
|
||||
"show_zero_pad_nets": false,
|
||||
"sort_ascending": true,
|
||||
"sorting_column": -1
|
||||
},
|
||||
"open_jobsets": [],
|
||||
"project": {
|
||||
"files": []
|
||||
},
|
||||
"schematic": {
|
||||
"selection_filter": {
|
||||
"graphics": true,
|
||||
"images": true,
|
||||
"labels": true,
|
||||
"lockedItems": false,
|
||||
"otherItems": true,
|
||||
"pins": true,
|
||||
"symbols": true,
|
||||
"text": true,
|
||||
"wires": true
|
||||
}
|
||||
}
|
||||
}
|
||||
418
kicad/attendance_1/attendance_1.kicad_pro
Normal file
418
kicad/attendance_1/attendance_1.kicad_pro
Normal file
@@ -0,0 +1,418 @@
|
||||
{
|
||||
"board": {
|
||||
"3dviewports": [],
|
||||
"design_settings": {
|
||||
"defaults": {},
|
||||
"diff_pair_dimensions": [],
|
||||
"drc_exclusions": [],
|
||||
"rules": {},
|
||||
"track_widths": [],
|
||||
"via_dimensions": []
|
||||
},
|
||||
"ipc2581": {
|
||||
"dist": "",
|
||||
"distpn": "",
|
||||
"internal_id": "",
|
||||
"mfg": "",
|
||||
"mpn": ""
|
||||
},
|
||||
"layer_pairs": [],
|
||||
"layer_presets": [],
|
||||
"viewports": []
|
||||
},
|
||||
"boards": [],
|
||||
"cvpcb": {
|
||||
"equivalence_files": []
|
||||
},
|
||||
"erc": {
|
||||
"erc_exclusions": [],
|
||||
"meta": {
|
||||
"version": 0
|
||||
},
|
||||
"pin_map": [
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
0,
|
||||
2,
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
0,
|
||||
2
|
||||
],
|
||||
[
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2
|
||||
]
|
||||
],
|
||||
"rule_severities": {
|
||||
"bus_definition_conflict": "error",
|
||||
"bus_entry_needed": "error",
|
||||
"bus_to_bus_conflict": "error",
|
||||
"bus_to_net_conflict": "error",
|
||||
"different_unit_footprint": "error",
|
||||
"different_unit_net": "error",
|
||||
"duplicate_reference": "error",
|
||||
"duplicate_sheet_names": "error",
|
||||
"endpoint_off_grid": "warning",
|
||||
"extra_units": "error",
|
||||
"footprint_filter": "ignore",
|
||||
"footprint_link_issues": "warning",
|
||||
"four_way_junction": "ignore",
|
||||
"global_label_dangling": "warning",
|
||||
"hier_label_mismatch": "error",
|
||||
"label_dangling": "error",
|
||||
"label_multiple_wires": "warning",
|
||||
"lib_symbol_issues": "warning",
|
||||
"lib_symbol_mismatch": "warning",
|
||||
"missing_bidi_pin": "warning",
|
||||
"missing_input_pin": "warning",
|
||||
"missing_power_pin": "error",
|
||||
"missing_unit": "warning",
|
||||
"multiple_net_names": "warning",
|
||||
"net_not_bus_member": "warning",
|
||||
"no_connect_connected": "warning",
|
||||
"no_connect_dangling": "warning",
|
||||
"pin_not_connected": "error",
|
||||
"pin_not_driven": "error",
|
||||
"pin_to_pin": "warning",
|
||||
"power_pin_not_driven": "error",
|
||||
"same_local_global_label": "warning",
|
||||
"similar_label_and_power": "warning",
|
||||
"similar_labels": "warning",
|
||||
"similar_power": "warning",
|
||||
"simulation_model_issue": "ignore",
|
||||
"single_global_label": "ignore",
|
||||
"unannotated": "error",
|
||||
"unconnected_wire_endpoint": "warning",
|
||||
"undefined_netclass": "error",
|
||||
"unit_value_mismatch": "error",
|
||||
"unresolved_variable": "error",
|
||||
"wire_dangling": "error"
|
||||
}
|
||||
},
|
||||
"libraries": {
|
||||
"pinned_footprint_libs": [],
|
||||
"pinned_symbol_libs": []
|
||||
},
|
||||
"meta": {
|
||||
"filename": "attendance_1.kicad_pro",
|
||||
"version": 3
|
||||
},
|
||||
"net_settings": {
|
||||
"classes": [
|
||||
{
|
||||
"bus_width": 12,
|
||||
"clearance": 0.2,
|
||||
"diff_pair_gap": 0.25,
|
||||
"diff_pair_via_gap": 0.25,
|
||||
"diff_pair_width": 0.2,
|
||||
"line_style": 0,
|
||||
"microvia_diameter": 0.3,
|
||||
"microvia_drill": 0.1,
|
||||
"name": "Default",
|
||||
"pcb_color": "rgba(0, 0, 0, 0.000)",
|
||||
"priority": 2147483647,
|
||||
"schematic_color": "rgba(0, 0, 0, 0.000)",
|
||||
"track_width": 0.2,
|
||||
"via_diameter": 0.6,
|
||||
"via_drill": 0.3,
|
||||
"wire_width": 6
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"version": 4
|
||||
},
|
||||
"net_colors": null,
|
||||
"netclass_assignments": null,
|
||||
"netclass_patterns": []
|
||||
},
|
||||
"pcbnew": {
|
||||
"last_paths": {
|
||||
"gencad": "",
|
||||
"idf": "",
|
||||
"netlist": "",
|
||||
"plot": "",
|
||||
"pos_files": "",
|
||||
"specctra_dsn": "",
|
||||
"step": "",
|
||||
"svg": "",
|
||||
"vrml": ""
|
||||
},
|
||||
"page_layout_descr_file": ""
|
||||
},
|
||||
"schematic": {
|
||||
"annotate_start_num": 0,
|
||||
"bom_export_filename": "${PROJECTNAME}.csv",
|
||||
"bom_fmt_presets": [],
|
||||
"bom_fmt_settings": {
|
||||
"field_delimiter": ",",
|
||||
"keep_line_breaks": false,
|
||||
"keep_tabs": false,
|
||||
"name": "CSV",
|
||||
"ref_delimiter": ",",
|
||||
"ref_range_delimiter": "",
|
||||
"string_delimiter": "\""
|
||||
},
|
||||
"bom_presets": [],
|
||||
"bom_settings": {
|
||||
"exclude_dnp": false,
|
||||
"fields_ordered": [
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Reference",
|
||||
"name": "Reference",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Qty",
|
||||
"name": "${QUANTITY}",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "Value",
|
||||
"name": "Value",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "DNP",
|
||||
"name": "${DNP}",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "Exclude from BOM",
|
||||
"name": "${EXCLUDE_FROM_BOM}",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "Exclude from Board",
|
||||
"name": "${EXCLUDE_FROM_BOARD}",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": true,
|
||||
"label": "Footprint",
|
||||
"name": "Footprint",
|
||||
"show": true
|
||||
},
|
||||
{
|
||||
"group_by": false,
|
||||
"label": "Datasheet",
|
||||
"name": "Datasheet",
|
||||
"show": true
|
||||
}
|
||||
],
|
||||
"filter_string": "",
|
||||
"group_symbols": true,
|
||||
"include_excluded_from_bom": true,
|
||||
"name": "Default Editing",
|
||||
"sort_asc": true,
|
||||
"sort_field": "Reference"
|
||||
},
|
||||
"connection_grid_size": 50.0,
|
||||
"drawing": {
|
||||
"dashed_lines_dash_length_ratio": 12.0,
|
||||
"dashed_lines_gap_length_ratio": 3.0,
|
||||
"default_line_thickness": 6.0,
|
||||
"default_text_size": 50.0,
|
||||
"field_names": [],
|
||||
"intersheets_ref_own_page": false,
|
||||
"intersheets_ref_prefix": "",
|
||||
"intersheets_ref_short": false,
|
||||
"intersheets_ref_show": false,
|
||||
"intersheets_ref_suffix": "",
|
||||
"junction_size_choice": 3,
|
||||
"label_size_ratio": 0.375,
|
||||
"operating_point_overlay_i_precision": 3,
|
||||
"operating_point_overlay_i_range": "~A",
|
||||
"operating_point_overlay_v_precision": 3,
|
||||
"operating_point_overlay_v_range": "~V",
|
||||
"overbar_offset_ratio": 1.23,
|
||||
"pin_symbol_size": 25.0,
|
||||
"text_offset_ratio": 0.15
|
||||
},
|
||||
"legacy_lib_dir": "",
|
||||
"legacy_lib_list": [],
|
||||
"meta": {
|
||||
"version": 1
|
||||
},
|
||||
"net_format_name": "",
|
||||
"page_layout_descr_file": "",
|
||||
"plot_directory": "",
|
||||
"space_save_all_events": true,
|
||||
"spice_current_sheet_as_root": false,
|
||||
"spice_external_command": "spice \"%I\"",
|
||||
"spice_model_current_sheet_as_root": true,
|
||||
"spice_save_all_currents": false,
|
||||
"spice_save_all_dissipations": false,
|
||||
"spice_save_all_voltages": false,
|
||||
"subpart_first_id": 65,
|
||||
"subpart_id_separator": 0
|
||||
},
|
||||
"sheets": [
|
||||
[
|
||||
"82b3a318-584c-487b-a2ab-9cfe2954db04",
|
||||
"Root"
|
||||
]
|
||||
],
|
||||
"text_variables": {}
|
||||
}
|
||||
1086
kicad/attendance_1/attendance_1.kicad_sch
Normal file
1086
kicad/attendance_1/attendance_1.kicad_sch
Normal file
File diff suppressed because it is too large
Load Diff
4
kicad/attendance_1/sym-lib-table
Normal file
4
kicad/attendance_1/sym-lib-table
Normal file
@@ -0,0 +1,4 @@
|
||||
(sym_lib_table
|
||||
(version 7)
|
||||
(lib (name "ESP8266")(type "Legacy")(uri "/home/marco/kicad/libraries/kicad-ESP8266/ESP8266.lib")(options "")(descr ""))
|
||||
)
|
||||
25
secrets.h.example
Normal file
25
secrets.h.example
Normal file
@@ -0,0 +1,25 @@
|
||||
// Por favor, renombra este archivo a "secrets.h" y rellena tus datos.
|
||||
// Este archivo NO se subirá a Git, protegiendo tu información.
|
||||
|
||||
#ifndef SECRETS_H
|
||||
#define SECRETS_H
|
||||
|
||||
// ==========================================
|
||||
// ⚙️ CONFIGURACIÓN DE CREDENCIALES
|
||||
// ==========================================
|
||||
|
||||
// Red WiFi
|
||||
const char* ssid = "TU_WIFI_SSID";
|
||||
const char* password = "TU_WIFI_PASSWORD";
|
||||
|
||||
// Webhook y Time Server
|
||||
const char* webhookUrl = "URL_DE_TU_WEBHOOK";
|
||||
const char* timeServerUrl = "https://worldtimeapi.org/api/ip"; // Servidor público de ejemplo
|
||||
|
||||
// Nombre del dispositivo para la cabecera User-Agent (Identifica el origen)
|
||||
const char* DEVICE_NAME = "Checador_Oficina_1";
|
||||
|
||||
// Zona Horaria (Ej: Monterrey UTC-6 -> -6 * 3600 = -21600)
|
||||
const long timeZoneOffset = -21600;
|
||||
|
||||
#endif // SECRETS_H
|
||||
541
soul23_time_attendance.ino
Normal file
541
soul23_time_attendance.ino
Normal file
@@ -0,0 +1,541 @@
|
||||
#include <SPI.h>
|
||||
#include <MFRC522.h>
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESP8266HTTPClient.h>
|
||||
#include <WiFiClientSecure.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <Wire.h>
|
||||
#include <Adafruit_GFX.h>
|
||||
#include <Adafruit_SSD1306.h>
|
||||
#include <TimeLib.h> // Requiere instalar: arduino-cli lib install "Time"
|
||||
|
||||
#include "secrets.h"
|
||||
|
||||
// Intervalo de resincronización del reloj (1 hora)
|
||||
const unsigned long syncInterval = 3600000;
|
||||
|
||||
// ==========================================
|
||||
// 🔌 PINES SEGUROS (NO INTERFIEREN CON BOOT)
|
||||
// ==========================================
|
||||
#define SS_PIN D8 // GPIO 15
|
||||
#define RST_PIN D0 // GPIO 16
|
||||
#define OLED_SDA D2
|
||||
#define OLED_SCL D1
|
||||
#define LED_PIN D3 // LED de estatus (GPIO 0) - Inicializado con pull-up para evitar problemas de boot
|
||||
#define BUZZER_PIN D4 // Buzzer (GPIO 2) - ⚠️ IMPORTANTE: Inicializado MUY TARDE en setup() para evitar problemas de boot
|
||||
|
||||
MFRC522 mfrc522(SS_PIN, RST_PIN);
|
||||
|
||||
#define SCREEN_WIDTH 128
|
||||
#define SCREEN_HEIGHT 64
|
||||
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
|
||||
|
||||
// Variables de control
|
||||
unsigned long lastSyncTime = 0;
|
||||
int lastMinuteDisplayed = -1;
|
||||
bool hasError = false;
|
||||
unsigned long lastBlinkTime = 0;
|
||||
bool ledState = LOW;
|
||||
|
||||
// Control del Watchdog del Lector NFC
|
||||
const unsigned long nfcCheckInterval = 15000; // 15 segundos (Revisión frecuente para auto-reparación rápida)
|
||||
unsigned long lastNfcCheck = 0;
|
||||
|
||||
|
||||
// ==========================================
|
||||
// 📝 DECLARACIONES DE FUNCIONES
|
||||
// ==========================================
|
||||
void checkAndResetNFC();
|
||||
void showStatus(String line1, String line2);
|
||||
void syncTimeWithServer();
|
||||
bool sendToWebhook(String uid, String name, String num_emp, String sucursal);
|
||||
void showClockScreen();
|
||||
String getFormattedTime();
|
||||
bool readCardData(byte *data, byte *length);
|
||||
void processTag();
|
||||
String generateUUID(byte length);
|
||||
String getISO8601DateTime(time_t t);
|
||||
String getFormattedDate();
|
||||
void beep(int times, int duration);
|
||||
void alarmSound();
|
||||
void silenceBuzzer();
|
||||
void updateStatusLED();
|
||||
void scanI2C();
|
||||
|
||||
// ==========================================
|
||||
// 🚀 SETUP
|
||||
// ==========================================
|
||||
void setup() {
|
||||
Serial.println("\n--- Iniciando setup ---");
|
||||
// ⚠️ CRÍTICO: Inicializar GPIO 2 (BUZZER) PRIMERO para evitar ruido durante boot
|
||||
// GPIO 2 tiene pull-up interno que puede causar problemas con buzzer pasivo
|
||||
// Configurarlo como INPUT sin pull-up/pull-down para evitar interferencia
|
||||
pinMode(BUZZER_PIN, INPUT); // INPUT sin pull para evitar ruido durante boot
|
||||
delay(10); // Pequeña pausa para estabilizar
|
||||
|
||||
// ⚠️ INICIALIZAR GPIO 0 (LED) CON PULL-UP para evitar problemas de boot
|
||||
// Si GPIO 0 está LOW durante boot, el ESP8266 entra en modo programación
|
||||
pinMode(LED_PIN, INPUT_PULLUP); // Primero como INPUT con pull-up interno
|
||||
delay(10); // Pequeña pausa para estabilizar
|
||||
pinMode(LED_PIN, OUTPUT); // Luego como OUTPUT
|
||||
digitalWrite(LED_PIN, LOW); // Apagado inicial
|
||||
|
||||
Serial.begin(115200);
|
||||
delay(100);
|
||||
Serial.println("\n\n--- Checador IOT (Public Version) ---");
|
||||
|
||||
randomSeed(analogRead(A0));
|
||||
|
||||
// 1. Inicializar los buses de comunicación primero
|
||||
// ⚠️ NO inicializar el buzzer aquí para evitar problemas de boot
|
||||
// El pin D4 (GPIO 2) tiene pull-up interno, pero el buzzer puede causar problemas si está conectado
|
||||
SPI.begin();
|
||||
Wire.begin(OLED_SDA, OLED_SCL);
|
||||
|
||||
// 2. Luego, inicializar los dispositivos periféricos
|
||||
Serial.println("Escanenado dispositivos I2C...");
|
||||
scanI2C(); // Escanear dispositivos I2C conectados
|
||||
mfrc522.PCD_Init();
|
||||
mfrc522.PCD_DumpVersionToSerial(); // Imprime datos del lector NFC para depuración
|
||||
|
||||
|
||||
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
|
||||
Serial.println(F("Intentando dirección OLED 0x3D..."));
|
||||
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3D)) {
|
||||
Serial.println(F("Error: No se encuentra OLED"));
|
||||
sos(); // Error crítico, S.O.S.
|
||||
}
|
||||
}
|
||||
display.clearDisplay();
|
||||
display.setTextColor(WHITE);
|
||||
// display.setContrast(255); // Comentado: Método no soportado
|
||||
display.dim(false); // Configurar brillo máximo (equivalente a alto contraste)
|
||||
Serial.println("OLED inicializado y configurado.");
|
||||
|
||||
showStatus("CONECTANDO", "WIFI...");
|
||||
WiFi.begin(ssid, password);
|
||||
|
||||
int timeout = 0;
|
||||
while (WiFi.status() != WL_CONNECTED && timeout < 30) {
|
||||
delay(500); Serial.print("."); timeout++;
|
||||
}
|
||||
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
showStatus("WIFI", "OK");
|
||||
delay(1000); // Pausa para estabilizar la red
|
||||
} else {
|
||||
showStatus("ERROR", "WIFI");
|
||||
sos(); // Error crítico, S.O.S.
|
||||
}
|
||||
|
||||
syncTimeWithServer();
|
||||
Serial.println("Sistema Listo.");
|
||||
|
||||
// ⚠️ CRÍTICO: Configurar buzzer como OUTPUT SOLO AL FINAL, después de que TODO esté listo
|
||||
// El pin ya está configurado como INPUT al inicio para evitar ruido durante boot
|
||||
// Ahora lo cambiamos a OUTPUT y lo ponemos en LOW para silenciarlo
|
||||
delay(100); // Pausa adicional para asegurar que todo esté estable
|
||||
pinMode(BUZZER_PIN, OUTPUT);
|
||||
digitalWrite(BUZZER_PIN, LOW); // Buzzer apagado inicial - CRÍTICO para evitar ruido
|
||||
delay(50); // Pausa más larga para estabilizar
|
||||
// Asegurar múltiples veces que el buzzer esté silenciado
|
||||
digitalWrite(BUZZER_PIN, LOW);
|
||||
delay(10);
|
||||
digitalWrite(BUZZER_PIN, LOW);
|
||||
|
||||
blinkLed(5, 200); // Boot OK - Parpadeo de 5 veces
|
||||
digitalWrite(LED_PIN, LOW); // LED apagado en estado de reposo
|
||||
silenceBuzzer(); // Asegurar que el buzzer esté silenciado
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 🔄 LOOP PRINCIPAL
|
||||
// ==========================================
|
||||
void loop() {
|
||||
Serial.println("Loop ejecutándose..."); // Debug: confirmar que loop se ejecuta
|
||||
checkAndResetNFC(); // Comprobar estado del lector NFC
|
||||
|
||||
if (timeStatus() == timeNotSet || (millis() - lastSyncTime > syncInterval)) {
|
||||
syncTimeWithServer();
|
||||
}
|
||||
|
||||
if (minute() != lastMinuteDisplayed) {
|
||||
showClockScreen();
|
||||
lastMinuteDisplayed = minute();
|
||||
}
|
||||
|
||||
if (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
|
||||
processTag();
|
||||
lastMinuteDisplayed = -1;
|
||||
}
|
||||
|
||||
// Asegurar que el buzzer esté silenciado en cada iteración
|
||||
silenceBuzzer();
|
||||
|
||||
delay(50);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 📋 LÓGICA DE PROCESAMIENTO
|
||||
// ==========================================
|
||||
void processTag() {
|
||||
String uid = "";
|
||||
for (byte i = 0; i < mfrc522.uid.size; i++) {
|
||||
uid += (mfrc522.uid.uidByte[i] < 0x10 ? "0" : "");
|
||||
uid += String(mfrc522.uid.uidByte[i], HEX);
|
||||
}
|
||||
uid.toUpperCase();
|
||||
|
||||
byte buffer[128];
|
||||
byte bufferSize = sizeof(buffer);
|
||||
|
||||
if (readCardData(buffer, &bufferSize)) {
|
||||
int jsonStart = -1;
|
||||
for (int i = 0; i < bufferSize; i++) { if (buffer[i] == '{') { jsonStart = i; break; } }
|
||||
|
||||
if (jsonStart != -1) {
|
||||
DynamicJsonDocument docCard(512);
|
||||
DeserializationError error = deserializeJson(docCard, (const char*)(buffer + jsonStart));
|
||||
|
||||
if (error) {
|
||||
showStatus("ERROR", "JSON ILEGIBLE");
|
||||
signalTemporaryError();
|
||||
} else {
|
||||
String name = docCard["name"];
|
||||
String num_emp = docCard["num_emp"];
|
||||
String sucursal = docCard["sucursal"];
|
||||
String telegram_id = docCard["telegram_id"];
|
||||
|
||||
sendToWebhook(uid, name, num_emp, sucursal, telegram_id);
|
||||
}
|
||||
} else {
|
||||
showStatus("ERROR", "SIN JSON");
|
||||
signalTemporaryError();
|
||||
}
|
||||
} else {
|
||||
showStatus("ERROR", "LECTURA FALLO");
|
||||
signalTemporaryError();
|
||||
}
|
||||
|
||||
mfrc522.PICC_HaltA();
|
||||
mfrc522.PCD_StopCrypto1();
|
||||
// El delay y el apagado del LED se manejan ahora en las funciones de señalización
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 🚨 WATCHDOG DEL LECTOR NFC
|
||||
// ==========================================
|
||||
void checkAndResetNFC() {
|
||||
// Solo ejecutar si ha pasado el intervalo de tiempo definido
|
||||
if (millis() - lastNfcCheck > nfcCheckInterval) {
|
||||
Serial.println("NFC Watchdog: Verificando lector...");
|
||||
|
||||
// Verificación más segura leyendo la versión del firmware
|
||||
byte v = mfrc522.PCD_ReadRegister(MFRC522::VersionReg);
|
||||
|
||||
// 0x00 o 0xFF indican error de comunicación
|
||||
if (v == 0x00 || v == 0xFF) {
|
||||
Serial.println(F("NFC Watchdog: Lector no responde. Intentando Soft Reset..."));
|
||||
mfrc522.PCD_Init();
|
||||
delay(50);
|
||||
|
||||
// Verificar si revivió
|
||||
v = mfrc522.PCD_ReadRegister(MFRC522::VersionReg);
|
||||
if (v == 0x00 || v == 0xFF) {
|
||||
Serial.println(F("NFC Watchdog: Soft Reset falló. Intentando HARD RESET..."));
|
||||
|
||||
// Hard Reset usando el pin RST
|
||||
pinMode(RST_PIN, OUTPUT);
|
||||
digitalWrite(RST_PIN, LOW);
|
||||
delay(100); // Mantener en bajo un momento
|
||||
digitalWrite(RST_PIN, HIGH);
|
||||
delay(50); // Esperar a que arranque
|
||||
|
||||
mfrc522.PCD_Init(); // Re-inicializar registros
|
||||
|
||||
v = mfrc522.PCD_ReadRegister(MFRC522::VersionReg);
|
||||
if (v == 0x00 || v == 0xFF) {
|
||||
Serial.println(F("NFC Watchdog: ¡FALLO CRÍTICO! Hard Reset tampoco funcionó."));
|
||||
} else {
|
||||
Serial.println(F("NFC Watchdog: Hard Reset exitoso."));
|
||||
}
|
||||
} else {
|
||||
Serial.println(F("NFC Watchdog: Soft Reset exitoso."));
|
||||
}
|
||||
} else {
|
||||
Serial.print(F("NFC Watchdog: Lector OK (v=0x"));
|
||||
Serial.print(v, HEX);
|
||||
Serial.println(F("). Refrescando configuración..."));
|
||||
// Re-inicializar de todos modos para asegurar configuración correcta (Gain, etc.)
|
||||
mfrc522.PCD_Init();
|
||||
}
|
||||
|
||||
// Actualizar el tiempo de la última comprobación
|
||||
lastNfcCheck = millis();
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// ☁️ RED Y LECTURA DE TARJETA
|
||||
// ==========================================
|
||||
bool readCardData(byte *data, byte *length) {
|
||||
MFRC522::MIFARE_Key key;
|
||||
byte nfcForumKey[6] = {0xD3, 0xF7, 0xD3, 0xF7, 0xD3, 0xF7};
|
||||
memcpy(key.keyByte, nfcForumKey, 6);
|
||||
|
||||
MFRC522::StatusCode status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, 4, &key, &(mfrc522.uid));
|
||||
if (status != MFRC522::STATUS_OK) { return false; }
|
||||
|
||||
delay(5);
|
||||
|
||||
byte readBlock[18];
|
||||
byte index = 0;
|
||||
for (byte block = 4; block < 12; block++) {
|
||||
if (block % 4 == 3) {
|
||||
status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, block + 1, &key, &(mfrc522.uid));
|
||||
if (status != MFRC522::STATUS_OK) break;
|
||||
continue;
|
||||
}
|
||||
byte size = sizeof(readBlock);
|
||||
status = mfrc522.MIFARE_Read(block, readBlock, &size);
|
||||
if (status != MFRC522::STATUS_OK) break;
|
||||
memcpy(&data[index], readBlock, 16);
|
||||
index += 16;
|
||||
}
|
||||
*length = index;
|
||||
return index > 0;
|
||||
}
|
||||
|
||||
void syncTimeWithServer() {
|
||||
if (WiFi.status() != WL_CONNECTED) return;
|
||||
std::unique_ptr<WiFiClientSecure> client(new WiFiClientSecure);
|
||||
client->setInsecure();
|
||||
HTTPClient http;
|
||||
if (http.begin(*client, timeServerUrl)) {
|
||||
http.setTimeout(10000); // Timeout de 10 segundos para evitar resets por watchdog
|
||||
http.setUserAgent(DEVICE_NAME);
|
||||
int httpCode = http.GET();
|
||||
if (httpCode == HTTP_CODE_OK) {
|
||||
DynamicJsonDocument doc(1024);
|
||||
deserializeJson(doc, http.getString());
|
||||
// Ajuste para el servidor de ejemplo worldtimeapi.org
|
||||
setTime(doc["unixtime"].as<unsigned long>() + timeZoneOffset);
|
||||
lastSyncTime = millis();
|
||||
}
|
||||
http.end();
|
||||
}
|
||||
}
|
||||
|
||||
bool sendToWebhook(String uid, String name, String num_emp, String sucursal, String telegram_id) {
|
||||
if (timeStatus() == timeNotSet) {
|
||||
showStatus("ERROR", "SIN HORA");
|
||||
signalTemporaryError();
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<WiFiClientSecure> client(new WiFiClientSecure);
|
||||
client->setInsecure();
|
||||
HTTPClient http;
|
||||
|
||||
unsigned long utcTimestamp = now() - timeZoneOffset;
|
||||
String uuid = generateUUID(11);
|
||||
String date = getFormattedDate();
|
||||
String datetime_utc = getISO8601DateTime(utcTimestamp);
|
||||
|
||||
showStatus("ENVIANDO", "REGISTRO...");
|
||||
|
||||
if (http.begin(*client, webhookUrl)) {
|
||||
http.setTimeout(10000); // Timeout de 10 segundos para evitar resets por watchdog
|
||||
http.setUserAgent(DEVICE_NAME);
|
||||
http.addHeader("Content-Type", "application/json");
|
||||
|
||||
DynamicJsonDocument doc(1024);
|
||||
JsonArray array = doc.to<JsonArray>();
|
||||
|
||||
JsonObject obj = array.createNestedObject();
|
||||
obj["uuid"] = uuid;
|
||||
obj["timestamp"] = utcTimestamp;
|
||||
obj["datetime_utc"] = datetime_utc;
|
||||
obj["date"] = date;
|
||||
obj["num_empleado"] = num_emp;
|
||||
obj["name"] = name;
|
||||
obj["branch"] = sucursal;
|
||||
obj["telegram_id"] = telegram_id;
|
||||
|
||||
String jsonPayload;
|
||||
serializeJson(doc, jsonPayload);
|
||||
|
||||
int httpResponseCode = http.POST(jsonPayload);
|
||||
if (httpResponseCode >= 200 && httpResponseCode < 300) {
|
||||
showStatus("HOLA :)", name);
|
||||
http.end();
|
||||
blinkLed(5, 200); // Éxito - Parpadeo de 5 veces (mismo comportamiento que boot)
|
||||
return true;
|
||||
} else {
|
||||
showStatus("ERROR", "AL ENVIAR");
|
||||
http.end();
|
||||
signalTemporaryError();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
signalTemporaryError();
|
||||
return false;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 🔊 BUZZER Y LEDS
|
||||
// ==========================================
|
||||
|
||||
void signalTemporaryError() {
|
||||
alarmSound();
|
||||
digitalWrite(LED_PIN, HIGH);
|
||||
delay(5000);
|
||||
digitalWrite(LED_PIN, LOW);
|
||||
}
|
||||
|
||||
void blinkLed(int times, int duration) {
|
||||
for (int i = 0; i < times; i++) {
|
||||
digitalWrite(LED_PIN, HIGH);
|
||||
delay(duration);
|
||||
digitalWrite(LED_PIN, LOW);
|
||||
if (i < times - 1) delay(duration);
|
||||
}
|
||||
}
|
||||
|
||||
void morseDot() {
|
||||
digitalWrite(LED_PIN, HIGH);
|
||||
delay(250);
|
||||
digitalWrite(LED_PIN, LOW);
|
||||
delay(250);
|
||||
}
|
||||
|
||||
void morseDash() {
|
||||
digitalWrite(LED_PIN, HIGH);
|
||||
delay(750);
|
||||
digitalWrite(LED_PIN, LOW);
|
||||
delay(250);
|
||||
}
|
||||
|
||||
void sos() {
|
||||
while(true) { // Bucle infinito para S.O.S.
|
||||
// SOS con la luz: ... --- ... (3 segundos)
|
||||
morseDot(); morseDot(); morseDot();
|
||||
delay(500);
|
||||
morseDash(); morseDash(); morseDash();
|
||||
delay(500);
|
||||
morseDot(); morseDot(); morseDot();
|
||||
delay(3000); // 3 segundos de SOS
|
||||
delay(10000); // Esperar 10 segundos antes de repetir
|
||||
}
|
||||
}
|
||||
|
||||
void beep(int times, int duration) {
|
||||
// Asegurar que el pin esté configurado (por si se llama antes del setup completo)
|
||||
static bool buzzerInitialized = false;
|
||||
if (!buzzerInitialized) {
|
||||
pinMode(BUZZER_PIN, OUTPUT);
|
||||
digitalWrite(BUZZER_PIN, LOW);
|
||||
buzzerInitialized = true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < times; i++) {
|
||||
digitalWrite(BUZZER_PIN, HIGH);
|
||||
delay(duration);
|
||||
digitalWrite(BUZZER_PIN, LOW);
|
||||
if (i < times - 1) delay(duration / 2);
|
||||
}
|
||||
|
||||
// Asegurar que el buzzer quede silenciado después de cada beep
|
||||
digitalWrite(BUZZER_PIN, LOW);
|
||||
delay(10); // Pequeña pausa para estabilizar
|
||||
}
|
||||
|
||||
void alarmSound() {
|
||||
beep(3, 150); // 3 beeps cortos y rápidos para el error
|
||||
silenceBuzzer(); // Asegurar que quede silenciado después del error
|
||||
}
|
||||
|
||||
void silenceBuzzer() {
|
||||
// Función para asegurar que el buzzer esté completamente silenciado
|
||||
// Útil para evitar ruido o interferencia electromagnética
|
||||
// Asegurar que el pin esté configurado como OUTPUT y en LOW
|
||||
pinMode(BUZZER_PIN, OUTPUT);
|
||||
digitalWrite(BUZZER_PIN, LOW);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 🖥️ PANTALLA Y UTILIDADES
|
||||
// ==========================================
|
||||
String generateUUID(byte length) {
|
||||
String randomString = "";
|
||||
const char charset[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
for (byte i = 0; i < length; i++) {
|
||||
randomString += charset[random(sizeof(charset) - 1)];
|
||||
}
|
||||
return randomString;
|
||||
}
|
||||
|
||||
String getISO8601DateTime(time_t t) {
|
||||
char buff[21];
|
||||
sprintf(buff, "%04d-%02d-%02dT%02d:%02d:%02dZ",
|
||||
year(t), month(t), day(t), hour(t), minute(t), second(t));
|
||||
return String(buff);
|
||||
}
|
||||
|
||||
String getFormattedDate() {
|
||||
String dateStr = String(year()) + "-";
|
||||
if (month() < 10) dateStr += "0"; dateStr += String(month());
|
||||
dateStr += "-";
|
||||
if (day() < 10) dateStr += "0"; dateStr += String(day());
|
||||
return dateStr;
|
||||
}
|
||||
|
||||
String getFormattedTime() {
|
||||
String timeStr = "";
|
||||
int h = hour(); int m = minute(); String ampm = "AM";
|
||||
if (h >= 12) { ampm = "PM"; if (h > 12) h -= 12; }
|
||||
if (h == 0) h = 12;
|
||||
if (h < 10) timeStr += " "; timeStr += String(h); timeStr += ":";
|
||||
if (m < 10) timeStr += "0"; timeStr += String(m); timeStr += " " + ampm;
|
||||
return timeStr;
|
||||
}
|
||||
|
||||
void showClockScreen() {
|
||||
display.clearDisplay();
|
||||
display.setTextSize(1); display.setCursor(0, 0); display.println(F("CHECADOR"));
|
||||
display.setTextSize(2); display.setCursor(10, 25);
|
||||
if (timeStatus() != timeNotSet) { display.println(getFormattedTime()); }
|
||||
else { display.println("--:--"); }
|
||||
display.setTextSize(1); display.setCursor(0, 55); display.println(F("ACERQUE TARJETA..."));
|
||||
display.display();
|
||||
}
|
||||
|
||||
void showStatus(String line1, String line2) {
|
||||
display.clearDisplay();
|
||||
display.setTextSize(2); display.setCursor(0, 10); display.println(line1);
|
||||
if(line2.length() > 8) display.setTextSize(1); else display.setTextSize(2);
|
||||
display.setCursor(0, 35); display.println(line2);
|
||||
display.display();
|
||||
}
|
||||
|
||||
void scanI2C() {
|
||||
byte error, address;
|
||||
int nDevices = 0;
|
||||
Serial.println("Dispositivos I2C encontrados:");
|
||||
for (address = 1; address < 127; address++) {
|
||||
Wire.beginTransmission(address);
|
||||
error = Wire.endTransmission();
|
||||
if (error == 0) {
|
||||
Serial.print("Encontrado en 0x");
|
||||
if (address < 16) Serial.print("0");
|
||||
Serial.println(address, HEX);
|
||||
nDevices++;
|
||||
} else if (error == 4) {
|
||||
Serial.print("Error desconocido en 0x");
|
||||
if (address < 16) Serial.print("0");
|
||||
Serial.println(address, HEX);
|
||||
}
|
||||
}
|
||||
if (nDevices == 0) Serial.println("Ningún dispositivo I2C encontrado");
|
||||
else Serial.println("Escaneo terminado");
|
||||
}
|
||||
Reference in New Issue
Block a user