Debug, before prod test 001
This commit is contained in:
parent
1e96d05660
commit
9ec98b52b6
4 changed files with 557 additions and 210 deletions
756
main.py
756
main.py
|
|
@ -1,258 +1,600 @@
|
|||
# MicroPython ESP32 - Guardián Fénix
|
||||
# Sensores: BME680 + ADXL345
|
||||
# Servidor web para mostrar datos y registros
|
||||
# ---------------------------------------
|
||||
# MicroPython ESP32 - Guardián Fénix FINAL
|
||||
# Enviromental(BME680) + ADXL345
|
||||
# I am Guardian Fenix
|
||||
# Samuel Alexader
|
||||
# ---------------------------------------
|
||||
|
||||
import machine, time, ujson, network, socket, math
|
||||
from machine import Pin, I2C
|
||||
import bme680 # Asegúrate de tener la librería BME680 para MicroPython
|
||||
import network, socket, time,struct, math, ujson, gc
|
||||
from machine import Pin, I2C, PWM,UART
|
||||
import adxl345
|
||||
import neopixel
|
||||
|
||||
# -------------------
|
||||
# CONFIGURACIÓN SENSORES
|
||||
# -------------------
|
||||
i2c = I2C(scl=Pin(5), sda=Pin(4))
|
||||
|
||||
# BME680
|
||||
bme = bme680.BME680(i2c)
|
||||
# =========================
|
||||
# DFPLAYER (UART2)
|
||||
# =========================
|
||||
DF_UART_ID = 2
|
||||
DF_TX_PIN = 21 # ESP32 TX -> DFPlayer RX
|
||||
DF_RX_PIN = 20 # ESP32 RX <- DFPlayer TX (optional, but harmless)
|
||||
DF_BUSY_PIN = 47 # DFPlayer BUSY -> ESP32 GPIO (usually LOW when playing)
|
||||
DF_BAUD = 9600
|
||||
|
||||
# ADXL345
|
||||
acc = adxl345.ADXL345(i2c)
|
||||
_START = 0x7E
|
||||
_VER = 0xFF
|
||||
_LEN = 0x06
|
||||
_ACK = 0x00
|
||||
_END = 0xEF
|
||||
|
||||
# -------------------
|
||||
# CONFIGURACIÓN WIFI (HOTSPOT)
|
||||
# -------------------
|
||||
ssid = "GuardianFenix"
|
||||
password = "12345678"
|
||||
CMD_SET_VOLUME = 0x06
|
||||
CMD_PLAY_TRACK = 0x03
|
||||
CMD_PLAY_FOLDER = 0x0F
|
||||
CMD_STOP = 0x16
|
||||
CMD_RESET = 0x0C
|
||||
|
||||
def _checksum(cmd, p1, p2):
|
||||
s = _VER + _LEN + cmd + _ACK + p1 + p2
|
||||
c = (0 - s) & 0xFFFF
|
||||
return (c >> 8) & 0xFF, c & 0xFF
|
||||
|
||||
class DFPlayer:
|
||||
def __init__(self, uart: UART, busy_pin=None):
|
||||
self.uart = uart
|
||||
self.busy = Pin(busy_pin, Pin.IN, Pin.PULL_UP) if busy_pin is not None else None
|
||||
time.sleep_ms(800)
|
||||
|
||||
def _send(self, cmd, param=0):
|
||||
p1 = (param >> 8) & 0xFF
|
||||
p2 = param & 0xFF
|
||||
ch, cl = _checksum(cmd, p1, p2)
|
||||
frame = bytes([_START, _VER, _LEN, cmd, _ACK, p1, p2, ch, cl, _END])
|
||||
self.uart.write(frame)
|
||||
|
||||
def reset(self):
|
||||
self._send(CMD_RESET, 0)
|
||||
time.sleep_ms(1200)
|
||||
|
||||
def volume(self, level):
|
||||
level = max(0, min(30, int(level)))
|
||||
self._send(CMD_SET_VOLUME, level)
|
||||
|
||||
def stop(self):
|
||||
self._send(CMD_STOP, 0)
|
||||
|
||||
def play_track(self, track_num):
|
||||
self._send(CMD_PLAY_TRACK, int(track_num))
|
||||
|
||||
def play_folder_file(self, folder, file):
|
||||
folder = max(1, min(99, int(folder)))
|
||||
file = max(1, min(255, int(file)))
|
||||
self._send(CMD_PLAY_FOLDER, (folder << 8) | file)
|
||||
|
||||
def is_playing(self):
|
||||
if self.busy is None:
|
||||
return None
|
||||
# Typical DFPlayer: BUSY LOW = playing, HIGH = idle
|
||||
return self.busy.value() == 0
|
||||
|
||||
def wait_done(self, timeout_ms=20000):
|
||||
"""Wait for BUSY to go LOW (start) then HIGH (done)."""
|
||||
if self.busy is None:
|
||||
return False
|
||||
|
||||
t0 = time.ticks_ms()
|
||||
|
||||
# wait for playback to begin (BUSY goes LOW)
|
||||
while self.busy.value() == 1:
|
||||
if time.ticks_diff(time.ticks_ms(), t0) > 1000:
|
||||
return False
|
||||
time.sleep_ms(5)
|
||||
|
||||
# wait for playback to finish (BUSY goes HIGH)
|
||||
while self.busy.value() == 0:
|
||||
if time.ticks_diff(time.ticks_ms(), t0) > timeout_ms:
|
||||
return False
|
||||
time.sleep_ms(10)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
df_uart = UART(
|
||||
DF_UART_ID,
|
||||
baudrate=DF_BAUD,
|
||||
bits=8,
|
||||
parity=None,
|
||||
stop=1,
|
||||
tx=Pin(DF_TX_PIN),
|
||||
rx=Pin(DF_RX_PIN),
|
||||
timeout=100
|
||||
)
|
||||
player = DFPlayer(df_uart, busy_pin=DF_BUSY_PIN)
|
||||
|
||||
# =========================
|
||||
# MODBUS RTU (RS485 UART1)
|
||||
# =========================
|
||||
MB_UART_ID = 1
|
||||
MB_TX_PIN = 45
|
||||
MB_RX_PIN = 0
|
||||
MB_BAUD = 9600
|
||||
|
||||
SLAVE_ID = 10
|
||||
|
||||
# RS485 direction pin (DE/RE tied together)
|
||||
RS485_DIR_PIN = 18
|
||||
rs485_dir = Pin(RS485_DIR_PIN, Pin.OUT)
|
||||
rs485_dir.value(0) # receive mode
|
||||
|
||||
mb_uart = UART(
|
||||
MB_UART_ID,
|
||||
baudrate=MB_BAUD,
|
||||
bits=8,
|
||||
parity=None,
|
||||
stop=1,
|
||||
tx=Pin(MB_TX_PIN),
|
||||
rx=Pin(MB_RX_PIN),
|
||||
timeout=200
|
||||
)
|
||||
|
||||
def crc16(data):
|
||||
crc = 0xFFFF
|
||||
for b in data:
|
||||
crc ^= b
|
||||
for _ in range(8):
|
||||
crc = (crc >> 1) ^ 0xA001 if (crc & 1) else (crc >> 1)
|
||||
return crc
|
||||
|
||||
def read_holding(start_reg, count):
|
||||
frame = bytearray(6)
|
||||
frame[0] = SLAVE_ID
|
||||
frame[1] = 3 # FC03
|
||||
frame[2] = start_reg >> 8
|
||||
frame[3] = start_reg & 0xFF
|
||||
frame[4] = count >> 8
|
||||
frame[5] = count & 0xFF
|
||||
|
||||
frame += struct.pack("<H", crc16(frame))
|
||||
|
||||
# ---- transmit ----
|
||||
rs485_dir.value(1)
|
||||
time.sleep_us(200)
|
||||
|
||||
mb_uart.write(frame)
|
||||
mb_uart.flush()
|
||||
|
||||
time.sleep_us(200)
|
||||
rs485_dir.value(0)
|
||||
|
||||
# ---- receive ----
|
||||
time.sleep_ms(100)
|
||||
resp = mb_uart.read()
|
||||
|
||||
if not resp or len(resp) < 5:
|
||||
return None
|
||||
|
||||
# basic sanity
|
||||
if resp[0] != SLAVE_ID or resp[1] != 3:
|
||||
return None
|
||||
|
||||
byte_count = resp[2]
|
||||
if len(resp) < (3 + byte_count + 2):
|
||||
return None
|
||||
|
||||
regs = []
|
||||
for i in range(0, byte_count, 2):
|
||||
regs.append((resp[3+i] << 8) | resp[4+i])
|
||||
return regs
|
||||
|
||||
|
||||
# ----------------------
|
||||
# NeoPixel LED
|
||||
# ----------------------
|
||||
PIN_LEDS = 39
|
||||
NUM_LEDS = 90
|
||||
np = neopixel.NeoPixel(Pin(PIN_LEDS), NUM_LEDS)
|
||||
|
||||
def npfill(color):
|
||||
for i in range(NUM_LEDS):
|
||||
np[i] = color
|
||||
np.write()
|
||||
|
||||
|
||||
# ----------------------
|
||||
# I2C
|
||||
# ----------------------
|
||||
i2c_adxl345 = I2C(scl=Pin(15), sda=Pin(16), freq=100000)
|
||||
|
||||
# ----------------------
|
||||
# Sensores
|
||||
# ----------------------
|
||||
acc = adxl345.ADXL345(i2c_adxl345)
|
||||
|
||||
|
||||
# ----------------------
|
||||
# WiFi Hotspot
|
||||
# ----------------------
|
||||
ap = network.WLAN(network.AP_IF)
|
||||
ap.active(True)
|
||||
ap.config(essid=ssid, password=password)
|
||||
ap.config(essid="GuardianFenix", password="12345678", authmode=3)
|
||||
while not ap.active():
|
||||
time.sleep(0.5)
|
||||
print("✅ Hotspot activo, IP:", ap.ifconfig()[0])
|
||||
|
||||
print("Hotspot activo:", ssid)
|
||||
# ----------------------
|
||||
# Variables
|
||||
# ----------------------
|
||||
registros = []
|
||||
MAX_REG = 50
|
||||
alarma_activa = False
|
||||
alerta_sensor = ""
|
||||
|
||||
# -------------------
|
||||
# VARIABLES
|
||||
# -------------------
|
||||
registros = [] # Lista de registros
|
||||
aceptar_vib = False
|
||||
alerta_activa = False
|
||||
|
||||
# -------------------
|
||||
# FUNCIONES DE SENSORES
|
||||
# -------------------
|
||||
def leer_bme680():
|
||||
bme.measure(gas=False)
|
||||
temp = bme.temperature()
|
||||
hum = bme.humidity()
|
||||
pres = bme.pressure()
|
||||
return {"temp": round(temp,1), "hum": round(hum,1), "pres": round(pres,1)}
|
||||
|
||||
def leer_adxl345():
|
||||
x, y, z = acc.read_g() # m/s²
|
||||
vib = abs((x*2 + y*2 + z*2) * 2)
|
||||
estado = "Estable"
|
||||
global alerta_activa
|
||||
if vib > 6:
|
||||
estado = "Inestable"
|
||||
alerta_activa = True
|
||||
# ----------------------
|
||||
# Función para actualizar LED según sensor
|
||||
# ----------------------
|
||||
def actualizar_led_alerta(sensor):
|
||||
if sensor == 'vib':
|
||||
npfill((255, 191, 0)) # naranja
|
||||
elif sensor == 'temp':
|
||||
npfill((255, 0, 0)) # rojo
|
||||
elif sensor == 'hum':
|
||||
npfill((0, 0, 255)) # azul
|
||||
elif sensor == 'pres':
|
||||
npfill( (0, 255, 0)) # verde
|
||||
elif sensor == 'IAQ':
|
||||
npfill( (128, 0, 128)) # morado
|
||||
else:
|
||||
alerta_activa = False
|
||||
return {"vib": round(vib,2), "estado": estado}
|
||||
npfill((0, 10, 0)) # apagado suave
|
||||
|
||||
# -------------------
|
||||
# REGISTRO AUTOMÁTICO
|
||||
# -------------------
|
||||
def registrar():
|
||||
datos = leer_bme680()
|
||||
vib = leer_adxl345()
|
||||
datos["vib"] = vib["vib"]
|
||||
datos["estado"] = vib["estado"]
|
||||
datos["hora"] = time.localtime()[3:6] # HH:MM:SS
|
||||
registros.append(datos)
|
||||
# Mantener solo últimos 100 registros
|
||||
if len(registros) > 100:
|
||||
# ----------------------
|
||||
# Lectura de sensores
|
||||
# ----------------------
|
||||
def leer_sensores():
|
||||
global alarma_activa, alerta_sensor
|
||||
try:
|
||||
# Initiate variables
|
||||
temp = 0
|
||||
hum = 0
|
||||
pres = 0
|
||||
IAQ = 0
|
||||
# Read from remote sensor (bme680) via modbus
|
||||
regs = read_holding(0, 8)
|
||||
if regs:
|
||||
# Informacion del sensor ambiental
|
||||
temp = struct.unpack(">h", struct.pack(">H", regs[4]))[0] / 100
|
||||
hum = regs[5] / 100
|
||||
pres = regs[6] / 10
|
||||
# Indice de calidad del aire
|
||||
IAQ = regs[7]
|
||||
except:
|
||||
temp = hum = pres = 0
|
||||
|
||||
try:
|
||||
ax, ay, az = acc.read_g()
|
||||
vib = round(math.sqrt(ax*ax + ay*ay + az*az), 2)
|
||||
except:
|
||||
ax = ay = az = vib = 0
|
||||
|
||||
estado = "Normal"
|
||||
alerta_sensor = ""
|
||||
player.volume(20)
|
||||
# time.sleep_ms(50)
|
||||
# Alertas menos sensibles según estadísticas normales
|
||||
if vib > 1.6:
|
||||
estado = "ALERTA"
|
||||
alerta_sensor = "Vibración"
|
||||
elif temp > 26.7 or temp < 22:
|
||||
estado = "ALERTA"
|
||||
alerta_sensor = "Temperatura"
|
||||
elif hum < 30 or hum > 60:
|
||||
estado = "ALERTA"
|
||||
alerta_sensor = "Humedad"
|
||||
elif pres < 950 or pres > 1050:
|
||||
estado = "ALERTA"
|
||||
alerta_sensor = "Presión"
|
||||
elif IAQ > 100:
|
||||
estado = "ALERTA"
|
||||
alerta_sensor = "IAQ"
|
||||
|
||||
_Volume = 15
|
||||
# LED y ...
|
||||
if estado == "ALERTA":
|
||||
alarma_activa = True
|
||||
if alerta_sensor == "Vibración":
|
||||
actualizar_led_alerta('vib')
|
||||
player.volume(_Volume)
|
||||
time.sleep_ms(50)
|
||||
player.play_folder_file(2, 105)
|
||||
ok = player.wait_done(timeout_ms=25000)
|
||||
# time.sleep(1)
|
||||
elif alerta_sensor == "Temperatura":
|
||||
actualizar_led_alerta('temp')
|
||||
player.volume(_Volume)
|
||||
time.sleep_ms(50)
|
||||
player.play_folder_file(2, 105)
|
||||
ok = player.wait_done(timeout_ms=25000)
|
||||
# time.sleep(1)
|
||||
elif alerta_sensor == "Humedad":
|
||||
actualizar_led_alerta('hum')
|
||||
player.volume(_Volume)
|
||||
time.sleep_ms(50)
|
||||
player.play_folder_file(2, 105)
|
||||
ok = player.wait_done(timeout_ms=25000)
|
||||
# time.sleep(1)
|
||||
elif alerta_sensor == "Presión":
|
||||
actualizar_led_alerta('pres')
|
||||
player.volume(_Volume)
|
||||
time.sleep_ms(50)
|
||||
player.play_folder_file(2, 105)
|
||||
ok = player.wait_done(timeout_ms=25000)
|
||||
# time.sleep(1)
|
||||
elif alerta_sensor == "IAQ":
|
||||
actualizar_led_alerta('IAQ')
|
||||
player.volume(_Volume)
|
||||
time.sleep_ms(50)
|
||||
player.play_folder_file(2, 105)
|
||||
ok = player.wait_done(timeout_ms=25000)
|
||||
# time.sleep(1)
|
||||
else:
|
||||
if not alarma_activa:
|
||||
actualizar_led_alerta("normal")
|
||||
|
||||
dato = {
|
||||
"temp": temp,
|
||||
"hum": hum,
|
||||
"pres": pres,
|
||||
"IAQ": IAQ,
|
||||
"vib": vib,
|
||||
"ax": round(ax,2),
|
||||
"ay": round(ay,2),
|
||||
"az": round(az,2),
|
||||
"estado": estado,
|
||||
"alarma": alarma_activa,
|
||||
"alerta_sensor": alerta_sensor
|
||||
}
|
||||
|
||||
registros.append(dato)
|
||||
if len(registros) > MAX_REG:
|
||||
registros.pop(0)
|
||||
return datos
|
||||
|
||||
# -------------------
|
||||
# SERVIDOR WEB
|
||||
# -------------------
|
||||
# ----------------------
|
||||
# HTML con Canvas y alerta ambiental
|
||||
# ----------------------
|
||||
def web_page():
|
||||
return """<!doctype html>
|
||||
return """<!DOCTYPE html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Guardián Fénix</title>
|
||||
<style>
|
||||
body{font-family:Arial,sans-serif;background:#fdf2f2;color:#333;margin:16px}
|
||||
h1{margin:8px 0 16px 0}
|
||||
.card{background:#fff;padding:16px;margin:12px 0;border-radius:12px;box-shadow:0 4px 10px rgba(0,0,0,.12);}
|
||||
button{background:#c62828;color:#fff;border:none;padding:10px 14px;border-radius:8px;cursor:pointer;margin:6px 6px 0 0;}
|
||||
.row{display:flex;flex-wrap:wrap;gap:12px}
|
||||
.stat{min-width:160px}
|
||||
.big{font-size:28px;font-weight:700}
|
||||
table{width:100%;border-collapse:collapse;font-size:.9em;margin-top:10px}
|
||||
th,td{border:1px solid #ccc;padding:6px;text-align:center}
|
||||
th{background:#f3d6d6}
|
||||
.mono{font-family:ui-monospace,Menlo,Consolas,monospace}
|
||||
</style>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Guardián Fénix</title>
|
||||
<style>
|
||||
body{font-family:Arial,sans-serif;margin:10px;background:#fdf2f2;color:#333}
|
||||
h1{text-align:center;color:#8b0000}
|
||||
.nav{display:flex;gap:8px;margin-bottom:12px}
|
||||
.nav button{flex:1;padding:8px;border-radius:6px;background:#c62828;color:#fff;border:none;cursor:pointer;}
|
||||
.card{display:none;background:#fff;padding:12px;margin-bottom:12px;border-radius:10px;box-shadow:0 4px 10px rgba(0,0,0,.12);}
|
||||
.row{display:flex;gap:12px;flex-wrap:wrap}
|
||||
.stat{min-width:120px}
|
||||
.alerta{display:none;background:#c62828;color:#fff;padding:12px;margin-top:10px;text-align:center;font-weight:bold;border-radius:6px;}
|
||||
canvas{width:100%;height:200px;margin-top:10px;border:1px solid #ccc;}
|
||||
table{width:100%;border-collapse:collapse;margin-top:8px}
|
||||
th,td{border:1px solid #ccc;padding:4px;text-align:center}
|
||||
th{background:#f3d6d6}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🛡️ Guardián Fénix 🔥</h1>
|
||||
|
||||
<div class="card">
|
||||
<h2>🌿 Sensor Ambiental (BME680)</h2>
|
||||
<div class="row">
|
||||
<div class="stat"><div>Temp</div><div class="big"><span id="temp">--</span> °C</div></div>
|
||||
<div class="stat"><div>Hum</div><div class="big"><span id="hum">--</span> %</div></div>
|
||||
<div class="stat"><div>Pres</div><div class="big"><span id="pres">--</span> hPa</div></div>
|
||||
<div class="stat"><div>Gas</div><div class="big"><span id="gas">--</span> Ω</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<h1>🛡️ Guardián Fénix 🔥</h1>
|
||||
|
||||
<div class="card">
|
||||
<h2>📳 Vibración (ADXL345)</h2>
|
||||
<div class="row">
|
||||
<div class="stat"><div>Shake</div><div class="big"><span id="shake">--</span></div></div>
|
||||
<div class="stat mono">ax=<span id="ax">--</span>g</div>
|
||||
<div class="stat mono">ay=<span id="ay">--</span>g</div>
|
||||
<div class="stat mono">az=<span id="az">--</span>g</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav">
|
||||
<button onclick="mostrar('sensores')">Sensores</button>
|
||||
<button onclick="mostrar('graficas')">Gráficas</button>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>📝 Registros</h2>
|
||||
<button id="btnNow">Leer ahora</button>
|
||||
<button id="btnLog">Guardar registro</button>
|
||||
<button id="btnClear">Borrar último</button>
|
||||
<div id="sensores" class="card">
|
||||
<h2>🌿 Sensor Ambiental</h2>
|
||||
<div class="row">
|
||||
<div class="stat">Temp: <span id="temp">--</span> °C</div>
|
||||
<div class="stat">Hum: <span id="hum">--</span> %</div>
|
||||
<div class="stat">Pres: <span id="pres">--</span> hPa</div>
|
||||
<div class="stat">Alerta: <button id="btnAlerta">--</button></div>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th><th>Hora</th><th>Temp</th><th>Hum</th><th>Pres</th><th>Gas</th><th>Shake</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tabla"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<h2>📳 Vibración</h2>
|
||||
<div class="row">
|
||||
<div class="stat">Shake: <span id="shake">--</span></div>
|
||||
<div class="stat">ax: <span id="ax">--</span></div>
|
||||
<div class="stat">ay: <span id="ay">--</span></div>
|
||||
<div class="stat">az: <span id="az">--</span></div>
|
||||
<div class="stat">Estado: <span id="estado">--</span></div>
|
||||
</div>
|
||||
|
||||
<div id="alarma" class="alerta">
|
||||
🚨 ALERTA! <button onclick="detenerAlarma()">DETENER</button>
|
||||
<audio id="alarmaAudio" loop>
|
||||
<source src="data:audio/wav;base64,UklGRigAAABXQVZFZm10IBAAAAABAAEA..." type="audio/wav">
|
||||
</audio>
|
||||
</div>
|
||||
|
||||
<h2>📝 Registros manuales</h2>
|
||||
<button onclick="guardar()">Guardar registro</button>
|
||||
<button onclick="borrarUltimo()">Borrar último</button>
|
||||
|
||||
<table>
|
||||
<thead><tr><th>#</th><th>Hora</th><th>Temp</th><th>Hum</th><th>Pres</th><th>Shake</th><th>Estado</th></tr></thead>
|
||||
<tbody id="tabla"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="graficas" class="card">
|
||||
<h2>📊 Gráficas Canvas</h2>
|
||||
<h3>Temperatura (°C)</h3>
|
||||
<canvas id="gTemp"></canvas>
|
||||
<h3>Humedad (%)</h3>
|
||||
<canvas id="gHum"></canvas>
|
||||
<h3>Presión (hPa)</h3>
|
||||
<canvas id="gPres"></canvas>
|
||||
<h3>Vibración (g)</h3>
|
||||
<canvas id="gVib"></canvas>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const el = (id)=>document.getElementById(id);
|
||||
let manualData = [];
|
||||
let liveData = [];
|
||||
|
||||
function setText(id, v) {
|
||||
el(id).textContent = (v===null || v===undefined) ? "--" : v;
|
||||
}
|
||||
function mostrar(id){
|
||||
document.querySelectorAll('.card').forEach(c=>c.style.display='none');
|
||||
document.getElementById(id).style.display='block';
|
||||
}
|
||||
|
||||
function drawTable(list) {
|
||||
const tb = el("tabla");
|
||||
tb.innerHTML = "";
|
||||
const last = list.slice(-20); // show last 20
|
||||
last.forEach((r, i) => {
|
||||
const hora = Array.isArray(r.hora) ? `${r.hora[0]}:${r.hora[1]}:${r.hora[2]}` : "--";
|
||||
tb.innerHTML += `
|
||||
<tr>
|
||||
<td>${list.length - last.length + i + 1}</td>
|
||||
<td>${hora}</td>
|
||||
<td>${r.temp ?? "--"}</td>
|
||||
<td>${r.hum ?? "--"}</td>
|
||||
<td>${r.pres ?? "--"}</td>
|
||||
<td>${r.gas ?? "--"}</td>
|
||||
<td>${r.shake ?? r.vib ?? "--"}</td>
|
||||
</tr>`;
|
||||
});
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
try {
|
||||
const r = await fetch("/data");
|
||||
async function refresh(){
|
||||
try{
|
||||
const r = await fetch('/data');
|
||||
const d = await r.json();
|
||||
|
||||
// If /data returns a single object:
|
||||
if (!Array.isArray(d)) {
|
||||
setText("temp", d.temp ?? d.t_c);
|
||||
setText("hum", d.hum ?? d.h_pct);
|
||||
setText("pres", d.pres ?? d.p_hpa);
|
||||
setText("gas", d.gas ?? d.gas_ohms);
|
||||
|
||||
setText("ax", d.ax);
|
||||
setText("ay", d.ay);
|
||||
setText("az", d.az);
|
||||
setText("shake", d.shake ?? d.vib);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// If /data returns the whole registros list:
|
||||
drawTable(d);
|
||||
if(!Array.isArray(d)) return;
|
||||
const last = d[d.length-1];
|
||||
if (last) {
|
||||
setText("temp", last.temp);
|
||||
setText("hum", last.hum);
|
||||
setText("pres", last.pres);
|
||||
setText("gas", last.gas);
|
||||
setText("shake", last.shake ?? last.vib);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("refresh error", e);
|
||||
}
|
||||
if(last){
|
||||
document.getElementById("temp").textContent = last.temp;
|
||||
document.getElementById("hum").textContent = last.hum;
|
||||
document.getElementById("pres").textContent = last.pres;
|
||||
document.getElementById("shake").textContent = last.vib;
|
||||
document.getElementById("ax").textContent = last.ax;
|
||||
document.getElementById("ay").textContent = last.ay;
|
||||
document.getElementById("az").textContent = last.az;
|
||||
document.getElementById("estado").textContent = last.estado;
|
||||
checkAlarma(last.estado, last.alarma, last.alerta_sensor);
|
||||
}
|
||||
liveData = d.slice(-50);
|
||||
dibujarGraficas();
|
||||
}catch(e){console.log(e);}
|
||||
}
|
||||
|
||||
// Buttons call your endpoints AND then refresh screen
|
||||
el("btnNow").onclick = async () => { await fetch("/leer"); await refresh(); };
|
||||
el("btnLog").onclick = async () => { await fetch("/guardar"); await refresh(); };
|
||||
el("btnClear").onclick = async () => { await fetch("/borrar"); await refresh(); };
|
||||
function guardar(){
|
||||
manualData.push({
|
||||
hora: new Date().toLocaleTimeString(),
|
||||
temp: document.getElementById("temp").textContent,
|
||||
hum: document.getElementById("hum").textContent,
|
||||
pres: document.getElementById("pres").textContent,
|
||||
vib: document.getElementById("shake").textContent,
|
||||
estado: document.getElementById("estado").textContent
|
||||
});
|
||||
cargarTabla();
|
||||
}
|
||||
|
||||
setInterval(refresh, 1000);
|
||||
refresh();
|
||||
function cargarTabla(){
|
||||
const tb = document.getElementById("tabla");
|
||||
tb.innerHTML = "";
|
||||
manualData.forEach((r,i)=>{
|
||||
tb.innerHTML += `<tr><td>${i+1}</td><td>${r.hora}</td><td>${r.temp}</td><td>${r.hum}</td><td>${r.pres}</td><td>${r.vib}</td><td>${r.estado}</td></tr>`;
|
||||
});
|
||||
}
|
||||
|
||||
function borrarUltimo(){
|
||||
manualData.pop();
|
||||
cargarTabla();
|
||||
}
|
||||
|
||||
function checkAlarma(estado, alarma, sensor){
|
||||
const alertaDiv = document.getElementById("alarma");
|
||||
const audio = document.getElementById("alarmaAudio");
|
||||
const btn = document.getElementById("btnAlerta");
|
||||
|
||||
if(estado=="ALERTA" || alarma){
|
||||
alertaDiv.style.display="block";
|
||||
audio.play();
|
||||
|
||||
// Cambiar color del botón según sensor ambiental
|
||||
if(sensor=="Temperatura") btn.style.background="#d32f2f"; // rojo
|
||||
else if(sensor=="Humedad") btn.style.background="#1976d2"; // azul
|
||||
else if(sensor=="Presión") btn.style.background="#388e3c"; // verde
|
||||
else btn.style.background="#888"; // gris
|
||||
} else {
|
||||
alertaDiv.style.display="none";
|
||||
audio.pause();
|
||||
audio.currentTime = 0;
|
||||
btn.style.background="#888";
|
||||
}
|
||||
btn.textContent = sensor || "--";
|
||||
}
|
||||
|
||||
function detenerAlarma(){
|
||||
document.getElementById("alarma").style.display="none";
|
||||
const audio = document.getElementById("alarmaAudio");
|
||||
audio.pause();
|
||||
audio.currentTime = 0;
|
||||
fetch('/reset');
|
||||
}
|
||||
|
||||
function dibujarGrafica(canvasId, valores, color){
|
||||
const c = document.getElementById(canvasId);
|
||||
const ctx = c.getContext("2d");
|
||||
ctx.clearRect(0,0,c.width,c.height);
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = color;
|
||||
ctx.lineWidth = 2;
|
||||
valores.forEach((v,i)=>{
|
||||
let x = i*(c.width/valores.length);
|
||||
let y = c.height - (v / Math.max(...valores.concat([1])) * c.height);
|
||||
if(i==0) ctx.moveTo(x,y);
|
||||
else ctx.lineTo(x,y);
|
||||
});
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
function dibujarGraficas(){
|
||||
if(liveData.length==0) return;
|
||||
dibujarGrafica("gTemp", liveData.map(x=>x.temp), "#d32f2f");
|
||||
dibujarGrafica("gHum", liveData.map(x=>x.hum), "#1976d2");
|
||||
dibujarGrafica("gPres", liveData.map(x=>x.pres), "#388e3c");
|
||||
dibujarGrafica("gVib", liveData.map(x=>x.vib), "#f57c00");
|
||||
}
|
||||
|
||||
mostrar('sensores');
|
||||
setInterval(refresh,2000);
|
||||
refresh();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
# -------------------
|
||||
# SERVIDOR
|
||||
# -------------------
|
||||
addr = socket.getaddrinfo('192.168.4.1',80)[0][-1]
|
||||
s = socket.socket()
|
||||
# ----------------------
|
||||
# Servidor socket
|
||||
# ----------------------
|
||||
addr = socket.getaddrinfo("0.0.0.0", 80)[0][-1]
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
s.bind(addr)
|
||||
s.listen(5)
|
||||
print('Servidor escuchando en', addr)
|
||||
s.listen(1)
|
||||
print("🌐 Servidor listo")
|
||||
|
||||
def handle_client(cl):
|
||||
global alarma_activa
|
||||
try:
|
||||
req = cl.recv(1024)
|
||||
req = str(req)
|
||||
if 'GET /data' in req:
|
||||
res = ujson.dumps(registros)
|
||||
cl.send('HTTP/1.0 200 OK\r\nContent-Type: application/json\r\n\r\n')
|
||||
cl.send(res)
|
||||
elif 'GET /leer' in req:
|
||||
d = registrar()
|
||||
cl.send('HTTP/1.0 200 OK\r\nContent-Type: application/json\r\n\r\n')
|
||||
cl.send(ujson.dumps(d))
|
||||
elif 'GET /guardar' in req:
|
||||
d = registrar()
|
||||
cl.send('HTTP/1.0 200 OK\r\nContent-Type: application/json\r\n\r\n')
|
||||
cl.send(ujson.dumps(d))
|
||||
elif 'GET /borrar' in req:
|
||||
if registros:
|
||||
registros.pop()
|
||||
cl.send('HTTP/1.0 200 OK\r\nContent-Type: application/json\r\n\r\n')
|
||||
req = cl.recv(1024).decode()
|
||||
if "GET /data" in req:
|
||||
cl.send("HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n")
|
||||
cl.send(ujson.dumps(registros))
|
||||
elif 'GET /toggle' in req:
|
||||
global aceptar_vib
|
||||
aceptar_vib = not aceptar_vib
|
||||
cl.send('HTTP/1.0 200 OK\r\nContent-Type: application/json\r\n\r\n')
|
||||
cl.send(ujson.dumps({"aceptar_vib":aceptar_vib}))
|
||||
elif "GET /reset" in req:
|
||||
alarma_activa = False
|
||||
actualizar_led_alerta("normal")
|
||||
cl.send("HTTP/1.1 200 OK\r\n\r\nOK")
|
||||
else:
|
||||
cl.send('HTTP/1.0 200 OK\r\nContent-Type: text/html\r\n\r\n')
|
||||
cl.send("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n")
|
||||
cl.send(web_page())
|
||||
except Exception as e:
|
||||
print("Error:", e)
|
||||
print("❌ Error:", e)
|
||||
cl.close()
|
||||
|
||||
# ----------------------
|
||||
# Loop principal
|
||||
# ----------------------
|
||||
last_read = 0
|
||||
while True:
|
||||
registrar()
|
||||
if time.ticks_ms() - last_read > 0.5:
|
||||
leer_sensores()
|
||||
last_read = time.ticks_ms()
|
||||
print("Free: ", gc.mem_free())
|
||||
print("Allocated: ", gc.mem_alloc())
|
||||
try:
|
||||
cl, addr = s.accept()
|
||||
handle_client(cl)
|
||||
except Exception as e:
|
||||
print("❌ Socket error:", e)
|
||||
BIN
uC/main_uC/.DS_Store
vendored
BIN
uC/main_uC/.DS_Store
vendored
Binary file not shown.
0
uC/main_uC/Team/main.py
Normal file
0
uC/main_uC/Team/main.py
Normal file
5
uC/main_uC/readm.txt
Normal file
5
uC/main_uC/readm.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
DFPlayer Structure
|
||||
Folder 1 = English
|
||||
|
||||
Folder 2 = Spanish
|
||||
1 =
|
||||
Loading…
Reference in a new issue