# --------------------------------------------------------------
# LABORATORIO 9
# Control de Servo por Temperatura (DHT22) + Alarma de Gas (MQ-2 + Buzzer)
# --------------------------------------------------------------
import time
import socket
import network
import machine
import framebuf
from utime import sleep
from secrets import secrets
from ssd1306 import SSD1306_I2C
import dht
import select
import ure
import ujson
# ===================== CONFIGURACIÓN DE PINES =====================
# OLED SSD1306
i2c = machine.I2C(0, scl=machine.Pin(5), sda=machine.Pin(4), freq=2000000)
oled = SSD1306_I2C(128, 64, i2c)
# Servo en GP0
servo = machine.PWM(machine.Pin(0))
servo.freq(50)
# DHT22 en GP26
dht22 = dht.DHT22(machine.Pin(26))
# MQ-2 (analógico) → conectado al ADC0 (GP26 también se puede, pero mejor usamos GP27)
mq2 = machine.ADC(27) # GP27 como entrada analógica
BUZZER = machine.Pin(15, machine.Pin.OUT) # Buzzer activo en GP15
# ===================== FUNCIÓN SERVO =====================
def mover_servo(angulo):
angulo = max(0, min(180, angulo))
duty = int((angulo / 180.0 * 2 + 0.5) / 20 * 65535)
servo.duty_u16(duty)
# ===================== LOGO (tu logo original) =====================
# === Mensaje de inicio ===
oled.fill(0)
oled.fill_rect(0, 0, 52, 64, 1)
oled.line(18, 13, 25, 13, 0)
oled.line(17, 14, 27, 14, 0)
oled.line(29, 15, 16, 15, 0)
oled.line(29, 15, 29, 37, 0)
oled.line(30, 16, 30, 36, 0)
oled.line(31, 16, 31, 35, 0)
oled.line(32, 15, 32, 34, 0)
oled.line(33, 14, 33, 17, 0)
oled.line(34, 14, 34, 16, 0)
oled.line(35, 13, 35, 15, 0)
oled.line(36, 13, 36, 14, 0)
oled.line(37, 13, 42, 13, 0)
oled.line(40, 14, 43, 14, 0)
oled.line(41, 15, 42, 15, 0)
oled.line(37, 15, 39, 15, 0)
oled.line(36, 16, 41, 16, 0)
oled.line(35, 17, 36, 17, 0)
oled.line(38, 17, 40, 17, 0)
oled.fill_rect(37, 18, 2, 3, 0)
oled.fill_rect(26, 19, 11, 2, 0)
oled.fill_rect(33, 21, 4, 12, 0)
oled.fill_rect(24, 16, 5, 4, 0)
oled.line(23, 17, 23, 19, 0)
oled.line(22, 18, 22, 20, 0)
oled.line(21, 19, 21, 21, 0)
oled.line(20, 20, 20, 21, 0)
oled.line(19, 18, 19, 22, 0)
oled.line(18, 19, 18, 22, 0)
oled.line(17, 20, 17, 23, 0)
oled.line(18, 16, 15, 16, 0)
oled.line(17, 17, 14, 17, 0)
oled.line(37, 22, 37, 44, 0)
oled.line(38, 23, 38, 44, 0)
oled.line(39, 24, 39, 44, 0)
oled.line(40, 24, 40, 45, 0)
oled.line(41, 25, 41, 45, 0)
oled.line(42, 26, 42, 45, 0)
oled.line(43, 26, 43, 46, 0)
oled.line(44, 27, 44, 59, 0)
oled.line(45, 27, 45, 59, 0)
oled.line(46, 28, 46, 58, 0)
oled.line(47, 29, 47, 57, 0)
oled.line(48, 30, 48, 57, 0)
oled.line(51, 32, 51, 54, 0)
oled.line(49, 31, 49, 56, 0)
oled.line(50, 31, 50, 55, 0)
oled.fill_rect(37, 48, 7, 12, 0)
oled.line(43, 60, 36, 60, 0)
oled.line(41, 61, 35, 61, 0)
oled.line(40, 62, 34, 62, 0)
oled.line(30, 45, 32, 45, 0)
oled.line(31, 46, 38, 46, 0)
oled.line(32, 47, 41, 47, 0)
oled.line(33, 48, 36, 48, 0)
oled.fill_rect(34, 49, 3, 2, 0)
oled.line(35, 51, 36, 51, 0)
oled.pixel(36, 52, 0)
oled.fill_rect(34, 33, 3, 11, 0)
oled.line(33, 35, 33, 43, 0)
oled.line(32, 36, 32, 42, 0)
oled.line(31, 37, 31, 42, 0)
oled.line(30, 38, 30, 42, 0)
oled.line(29, 39, 29, 42, 0)
oled.line(28, 40, 28, 42, 0)
oled.line(27, 41, 27, 42, 0)
oled.pixel(26, 42, 0)
oled.line(23, 21, 28, 21, 0)
oled.line(22, 22, 28, 22, 0)
oled.line(20, 23, 28, 23, 0)
oled.line(19, 24, 20, 24, 0)
oled.line(26, 24, 28, 24, 0)
oled.fill_rect(27, 25, 2, 13, 0)
oled.fill_rect(26, 27, 1, 10, 0)
oled.fill_rect(25, 26, 1, 4, 0)
oled.fill_rect(24, 25, 1, 4, 0)
oled.fill_rect(22, 25, 2, 3, 0)
oled.fill_rect(20, 26, 2, 2, 0)
oled.fill_rect(21, 31, 5, 5, 0)
oled.line(18, 32, 20, 32, 0)
oled.line(19, 31, 19, 33, 0)
oled.line(20, 29, 20, 34, 0)
oled.fill_rect(21, 29, 2, 2, 0)
oled.line(23, 30, 23, 42, 0)
oled.fill_rect(15, 18, 2, 6, 0)
oled.fill_rect(9, 26, 9, 6, 0)
oled.fill_rect(3, 25, 4, 3, 0)
oled.fill_rect(11, 42, 12, 2, 0)
oled.fill_rect(13, 18, 2, 2, 0)
oled.fill_rect(12, 19, 2, 2, 0)
oled.fill_rect(10, 20, 2, 2, 0)
oled.fill_rect(9, 21, 2, 2, 0)
oled.fill_rect(8, 22, 2, 2, 0)
oled.fill_rect(5, 23, 4, 2, 0)
oled.fill_rect(5, 26, 4, 3, 0)
oled.line(10, 25, 16, 25, 0)
oled.line(11, 24, 14, 24, 0)
oled.line(12, 23, 14, 23, 0)
oled.line(13, 22, 14, 22, 0)
oled.pixel(14, 21, 0)
oled.pixel(4, 24, 0)
oled.pixel(22, 16, 0)
oled.line(18, 27, 18, 30, 0)
oled.line(7, 29, 8, 29, 0)
oled.line(25, 37, 25, 40, 0)
oled.line(24, 41, 24, 36, 0)
oled.line(22, 36, 22, 41, 0)
oled.line(21, 37, 21, 41, 0)
oled.line(20, 36, 20, 37, 0)
oled.pixel(19, 35, 0)
oled.line(17, 44, 12, 44, 0)
oled.line(15, 45, 13, 45, 0)
oled.line(15, 46, 14, 46, 0)
oled.pixel(15, 47, 0)
oled.line(20, 41, 18, 41, 0)
oled.line(20, 40, 19, 40, 0)
oled.pixel(20, 39, 0)
oled.line(17, 40, 13, 40, 0)
oled.line(14, 38, 14, 39, 0)
oled.pixel(13, 39, 0)
oled.pixel(18, 39, 0)
oled.line(17, 33, 12, 33, 0)
oled.line(3, 30, 4, 30, 0)
oled.line(4, 31, 6, 33, 0)
oled.line(11, 32, 8, 35, 0)
oled.line(6, 31, 9, 34, 0)
oled.line(8, 34, 10, 32, 0)
oled.fill_rect(7, 31, 3, 3, 0)
oled.line(11, 34, 9, 36, 0)
oled.line(12, 37, 10, 37, 0)
oled.fill_rect(10, 35, 2, 2, 0)
oled.text("SISTEMA ",62,0)
oled.text("CONTROL ",63,10)
oled.text("AMBIENTAL ",56,20)
# Dibujar las formas
oled.fill_rect(89, 38, 11, 3, 1)
oled.fill_rect(102, 38, 10, 3, 1)
oled.line(99, 59, 99, 41, 1)
oled.line(98, 58, 98, 41, 1)
oled.line(97, 57, 97, 41, 1)
oled.line(96, 56, 96, 54, 1)
oled.line(95, 55, 95, 53, 1)
oled.line(94, 55, 94, 49, 1)
oled.line(95, 50, 95, 49, 1)
oled.line(93, 54, 93, 50, 1)
oled.line(92, 53, 92, 50, 1)
oled.line(91, 54, 90, 55, 1)
oled.line(91, 53, 89, 55, 1)
oled.line(91, 52, 88, 55, 1)
oled.line(91, 41, 91, 51, 1)
oled.line(90, 51, 90, 41, 1)
oled.line(89, 50, 89, 41, 1)
oled.line(110, 42, 110, 41, 1)
oled.line(109, 44, 109, 41, 1)
oled.line(108, 46, 108, 41, 1)
oled.line(107, 48, 107, 42, 1)
oled.line(106, 50, 106, 44, 1)
oled.line(105, 56, 105, 47, 1)
oled.line(104, 57, 104, 49, 1)
oled.line(103, 58, 103, 51, 1)
oled.line(102, 59, 102, 53, 1)
oled.line(106, 55, 106, 52, 1)
oled.line(107, 54, 107, 51, 1)
oled.line(108, 53, 108, 50, 1)
oled.line(109, 52, 109, 49, 1)
oled.line(110, 51, 110, 48, 1)
oled.line(111, 50, 111, 47, 1)
oled.line(91, 36, 91, 32, 1)
oled.line(92, 36, 92, 33, 1)
oled.line(93, 36, 93, 34, 1)
oled.line(109, 36, 109, 32, 1)
oled.line(108, 36, 108, 33, 1)
oled.line(107, 36, 107, 34, 1)
oled.line(99, 34, 101, 34, 1)
oled.pixel(100, 33, 1)
oled.fill_rect(94, 35, 13, 2, 1)
oled.text("*** ",60,43)
oled.show()
time.sleep(3)
# ===================== OLED: solo temperatura y ángulo =====================
def mostrar_oled(temp, hum, angulo):
oled.fill(0)
oled.text("Control por Temp.", 0, 0)
oled.text(f"Temp: {temp:.1f} C", 0, 15)
oled.text(f"Humedad: {hum:.1f}%", 0, 26)
oled.text(f"Angulo: {angulo} deg", 0, 37)
bar = int((angulo / 180) * 90)
oled.fill_rect(14, 52, bar, 8, 1)
oled.rect(14, 52, 90, 8, 1)
oled.text("0", 0, 52); oled.text("180", 103, 52)
oled.show()
# ===================== DETECCIÓN DE GAS (MQ-2) =====================
UMBRAL_GAS = 25000 # Ajusta según tu entorno
ALERTA_ACTIVA = False
manual_servo = None # Para control manual vía web
angulo_anterior = -1 # global
mq2 = machine.ADC(27) # GP27 como entrada analógica
def leer_gas():
valor = mq2.read_u16() # devuelve 0 a 65535
return valor
def controlar_buzzer(nivel_gas):
global ALERTA_ACTIVA
if nivel_gas > UMBRAL_GAS:
if not ALERTA_ACTIVA:
BUZZER.value(1)
ALERTA_ACTIVA = True
else:
BUZZER.value(0)
ALERTA_ACTIVA = False
# ===================== PÁGINA WEB (muestra TODO: temp + gas) =====================
def web_page(temp, hum, angulo, gas_nivel, alerta):
estado = "PELIGRO: GAS DETECTADO!" if alerta else "Ambiente seguro"
color_alerta = "#ff0066" if alerta else "#00ff00"
return f"""<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Lab 9 - Temp + Gas</title>
<style>
body {{margin:0;padding:0;background:#120028;color:#fff;font-family:Arial;display:flex;justify-content:center;align-items:center;min-height:100vh;}}
.c {{background:rgba(20,0,50,0.9);padding:1rem;border-radius:15px;width:90%;max-width:400px;text-align:center;}}
h1 {{color:#ff33aa;}}
.dato {{font-size:1.5rem;margin:5px 0;color:#ff99ff;}}
.barra {{height:15px;background:#222;border-radius:10px;overflow:hidden;margin:10px auto;width:80%;border:2px solid #ff33aa;}}
.fill {{height:100%;background:linear-gradient(90deg,#ff0066,#ff33ff);width:{(angulo/90)*100}%;transition:0.5s;}}
.gas {{font-size:1.2rem;color:{color_alerta};font-weight:bold;}}
</style>
</head>
<body>
<div class="c">
<h1>Laboratorio 9</h1>
<div class="dato">Temp: <span id="t">{temp:.1f}</span>°C</div>
<div class="dato">Humedad: <span id="h">{hum:.1f}</span>%</div>
<div class="dato">Ángulo Servo: <span id="a">{angulo}</span>°</div>
<div class="barra"><div class="fill" id="fill"></div></div>
<div class="gas" id="gas">Gas: {estado}</div>
<small>Nivel crudo MQ-2: <span id="raw">{gas_nivel}</span></small>
<h3>Control manual del servo (0°-90°)</h3>
<input type="range" id="manualServo" min="0" max="90" value="{angulo}" oninput="updateServo(this.value)">
<span id="servoVal">{angulo}</span>°
<div class="barra" style="margin-top:5px;">
<div id="fillManual" class="fill" style="width:{(angulo/90)*100}%;"></div>
</div>
<script>
function updateServo(val){{
val = Number(val);
document.getElementById('servoVal').textContent = val;
document.getElementById('fillManual').style.width = (val/90*100)+'%';
fetch('/servo?valor=' + val);
}}
function act(){{
fetch('/datos').then(r=>r.json()).then(d=>{{
document.getElementById('t').textContent = d.temp.toFixed(1);
document.getElementById('h').textContent = d.hum.toFixed(1);
document.getElementById('a').textContent = d.angulo;
document.getElementById('fill').style.width = (d.angulo/90*100)+'%';
document.getElementById('gas').textContent = d.alerta ? "¡PELIGRO: GAS DETECTADO!" : "Ambiente seguro";
document.getElementById('gas').style.color = d.alerta ? "#ff0066" : "#00ff00";
document.getElementById('raw').textContent = d.gas;
}});
}}
setInterval(act, 2500);
act();
</script>
</div>
</body></html>
"""
# ===================== CONEXIÓN WIFI =====================
ssid = secrets['ssid']
password = secrets['password']
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)
while not wlan.isconnected(): sleep(0.5)
print("Conectado! IP:", wlan.ifconfig()[0])
# ===================== INICIO =====================
temp = hum = angulo = gas_nivel = 0
alerta_gas = False
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', 80))
s.listen(5)
# ===================== BUCLE PRINCIPAL =====================
poll = select.poll()
poll.register(s, select.POLLIN)
while True:
try:
# --- Servidor web ---
conn, addr = s.accept()
request = conn.recv(1024).decode()
if '/datos' in request:
datos = {
"temp": temp,
"hum": hum,
"angulo": angulo if manual_servo is None else manual_servo,
"gas": gas_nivel,
"alerta": alerta_gas
}
conn.send('HTTP/1.1 200 OK\nContent-Type: application/json\nConnection: close\n\n')
conn.sendall(ujson.dumps(datos))
elif '/servo' in request:
m = ure.search(r'valor=(\d+)', request)
if m:
manual_val = int(m.group(1))
manual_servo = max(0, min(90, manual_val))
mover_servo(manual_servo)
conn.send('HTTP/1.1 200 OK\nContent-Type: text/plain\nConnection: close\n\n')
conn.sendall('OK')
else:
html = web_page(temp, hum, angulo if manual_servo is None else manual_servo, gas_nivel, alerta_gas)
conn.send('HTTP/1.1 200 OK\nContent-Type: text/html\nConnection: close\n\n')
conn.sendall(html)
conn.close()
except Exception as e:
print("Error web:", e)
# --- LECTURA DE SENSORES ---
try:
dht22.measure()
temp = dht22.temperature()
hum = dht22.humidity()
except:
pass
gas_nivel = leer_gas()
controlar_buzzer(gas_nivel)
alerta_gas = gas_nivel > UMBRAL_GAS
# --- Movimiento del servo ---
# angulo_anterior debe declararse FUERA del while, no dentro
# así que colócalo antes del while: angulo_anterior = -1
if alerta_gas:
nuevo_angulo = 90
elif manual_servo is not None: # Control manual web → prioridad alta
nuevo_angulo = manual_servo
else: # Control automático por temperatura
nuevo_angulo = int((temp - 15) / 30 * 90)
nuevo_angulo = max(0, min(nuevo_angulo, 90))
# Mover servo solo si hay cambio real
if nuevo_angulo != angulo_anterior:
mover_servo(nuevo_angulo)
angulo_anterior = nuevo_angulo
# --- OLED ---
mostrar_oled(temp, hum, angulo_anterior)
sleep(0.05)