import network
import socket
from utime import sleep
from machine import Pin, PWM, I2C
import ujson
import framebuf
import time
# OLED SSD1306
from ssd1306 import SSD1306_I2C
# Configuración WiFi
from secrets import secrets
ssid = secrets['ssid']
password = secrets['password']
# Configuración del servo
servo_pin = Pin(0)
servo = PWM(servo_pin)
servo.freq(50)
# Configuración de la pantalla OLED (GPIO 4 SDA, GPIO 5 SCL)
i2c = I2C(0, scl=Pin(5), sda=Pin(4))
oled = SSD1306_I2C(128, 64, i2c)
print("Dispositivos I2C detectados:", [hex(addr) for addr in i2c.scan()])
OLED_ADDR = 0x3C
WIDTH = 128
HEIGHT = 64
try:
oled = SSD1306_I2C(WIDTH, HEIGHT, i2c, addr=OLED_ADDR)
print("Pantalla OLED inicializada correctamente")
except Exception as e:
print("Error al inicializar la pantalla:", e)
imagen_bytes = bytearray([
0xff, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf0, 0x0f, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf0, 0x0f, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf3, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf3, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf3, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xff,
0xf0, 0x0f, 0xfe, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x3f, 0x7f, 0xff, 0xff, 0xff, 0xfb, 0xff,
0xf0, 0x0f, 0xfc, 0x00, 0x00, 0x00, 0x01, 0xff, 0x00, 0x3f, 0x7a, 0x4c, 0x71, 0xbb, 0x3b, 0xff,
0xf3, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x01, 0xff, 0x80, 0x1f, 0x0d, 0xb7, 0xb6, 0xba, 0xdb, 0xff,
0xf3, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x03, 0xff, 0x80, 0x0f, 0x7f, 0xf6, 0x36, 0xba, 0x1b, 0xff,
0xf3, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x80, 0x0f, 0x7b, 0xf5, 0xb6, 0xba, 0xfb, 0xff,
0xf0, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x80, 0x07, 0x7b, 0xf5, 0xb6, 0xd2, 0xfb, 0xff,
0xf0, 0x0f, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x80, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x03, 0xff, 0x80, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x01, 0xff, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x01, 0x7b, 0xff, 0xff, 0xfb, 0xff, 0xff,
0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x33, 0xff, 0xff, 0xfb, 0xff, 0xff,
0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x3b, 0x18, 0x63, 0x8a, 0x73, 0xff,
0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x6a, 0xeb, 0x7d, 0x7b, 0xed, 0xff,
0xf1, 0xf1, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0x4a, 0x0b, 0xe1, 0x7b, 0xed, 0xff,
0xf1, 0xf1, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0x5a, 0xfb, 0xfd, 0x7b, 0xed, 0xff,
0xf0, 0xe1, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0x7b, 0x0b, 0x61, 0x8b, 0xe1, 0xff,
0xf0, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf0, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf2, 0x49, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf2, 0x49, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf2, 0x09, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf3, 0x19, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0x6f, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf3, 0x19, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0x6d, 0x64, 0xb5, 0xd6, 0x5f, 0xff,
0xf3, 0xf9, 0x80, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0x1b, 0xad, 0xfe, 0xfa, 0xdf, 0xff,
0xf3, 0xf9, 0x80, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0x6b, 0xad, 0xf6, 0xfa, 0xff, 0xff,
0xf3, 0xf9, 0x80, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x6f, 0xad, 0xfe, 0xfa, 0xdf, 0xff,
0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0x74, 0x6d, 0xf0, 0xfb, 0x3f, 0xff,
0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf0, 0x3f, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf0, 0x1f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf3, 0x8f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf3, 0xcf, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf3, 0xcf, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf3, 0x8f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf0, 0x1f, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf0, 0x1f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf3, 0x8f, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf3, 0xcf, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf3, 0xc7, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf3, 0xe7, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xf3, 0xe3, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
])
def mostrar_imagen():
try:
fb = framebuf.FrameBuffer(imagen_bytes, WIDTH, HEIGHT, framebuf.MONO_HLSB)
oled.fill(0)
oled.blit(fb, 0, 0)
oled.show()
print("Imagen mostrada correctamente")
except Exception as e:
print(f"Error al mostrar la imagen: {e}")
oled.fill(0)
oled.text("Error imagen", 10, HEIGHT//2 - 8)
oled.show()
def mostrar_contador(valor):
oled.fill(0)
texto = "{:04d}".format(valor)
x = (WIDTH - len(texto) * 8) // 2
y = (HEIGHT - 8) // 2
oled.text(texto, x, y)
oled.show()
contador = 0
print("Mostrando imagen en la pantalla...")
mostrar_imagen()
print("Iniciando bucle principal")
print("Mostrando imagen en la pantalla...")
mostrar_imagen()
time.sleep(4)
# Variables globales
current_angle = 90
angle_history = []
MAX_HISTORY = 30
ANGLE_STEP = 1 # Paso más pequeño para cambio continuo
# Función para actualizar OLED
def update_oled(angle):
oled.fill(0)
oled.text("Angulo Servo:", 0, 20)
oled.text(str(angle) + " grados", 0, 40)
oled.show()
def set_servo_angle(angle):
global current_angle
current_angle = max(0, min(180, angle))
duty = int((current_angle / 180) * 8000 + 1000)
servo.duty_u16(duty)
angle_history.append(current_angle)
if len(angle_history) > MAX_HISTORY:
angle_history.pop(0)
update_oled(current_angle) # Mostrar en pantalla OLED
set_servo_angle(current_angle)
print("Conectando a WiFi...")
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)
while not wlan.isconnected():
sleep(1)
print("Conectado con IP:", wlan.ifconfig()[0])
# Función web_page() sin cambios
def web_page():
chart_labels = ",".join(str(i) for i in range(len(angle_history)))
chart_data = ",".join(str(v) for v in angle_history)
html = f"""<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Servo Web Control</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
body {{
font-family: Arial;
background: #f0f0f0;
text-align: center;
padding: 20px;
}}
h1 {{
color: #333;
margin-bottom: 30px;
}}
.control-panel {{
background: white;
border-radius: 10px;
padding: 20px;
margin: 20px auto;
max-width: 500px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}}
.button-container {{
display: flex;
justify-content: center;
gap: 20px;
margin: 30px 0;
}}
.btn {{
padding: 15px 30px;
font-size: 18px;
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
user-select: none;
}}
.btn:active {{
transform: scale(0.95);
}}
.btn-down {{
background-color: #ff4444;
color: white;
}}
.btn-up {{
background-color: #44aa44;
color: white;
}}
.angle-display {{
font-size: 24px;
font-weight: bold;
margin: 20px 0;
color: #333;
}}
.chart-container {{
width: 90%;
max-width: 600px;
margin: 30px auto;
background: white;
border-radius: 10px;
padding: 20px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}}
.preset-buttons {{
display: flex;
justify-content: center;
gap: 10px;
margin: 20px 0;
flex-wrap: wrap;
}}
.preset-btn {{
padding: 10px 15px;
background-color: #337ab7;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}}
</style>
</head>
<body>
<h1>Control de Servomotor</h1>
<div class="control-panel">
<div class="angle-display">
Ángulo actual: <span id="current-angle">{current_angle}</span>°
</div>
<div class="button-container">
<button class="btn btn-down"
onmousedown="startAdjust(-1)"
onmouseup="stopAdjust()"
ontouchstart="startAdjust(-1)"
ontouchend="stopAdjust()">
⬇️ Bajar
</button>
<button class="btn btn-up"
onmousedown="startAdjust(1)"
onmouseup="stopAdjust()"
ontouchstart="startAdjust(1)"
ontouchend="stopAdjust()">
⬆️ Subir
</button>
</div>
<div class="preset-buttons">
<button class="preset-btn" onclick="setPresetAngle(0)">0°</button>
<button class="preset-btn" onclick="setPresetAngle(45)">45°</button>
<button class="preset-btn" onclick="setPresetAngle(90)">90°</button>
<button class="preset-btn" onclick="setPresetAngle(135)">135°</button>
<button class="preset-btn" onclick="setPresetAngle(180)">180°</button>
</div>
</div>
<div class="chart-container">
<h3>Historial de ángulos</h3>
<canvas id="angleChart"></canvas>
</div>
<script>
let current = {current_angle};
let adjustInterval = null;
let isAdjusting = false;
// Función para ajuste continuo
function startAdjust(direction) {{
if (isAdjusting) return;
isAdjusting = true;
adjustInterval = setInterval(() => {{
current = Math.min(180, Math.max(0, current + direction * {ANGLE_STEP}));
document.getElementById('current-angle').textContent = current;
fetch(`/set_angle?angle=${{current}}`);
}}, 50); // Ajusta cada 50ms
}}
function stopAdjust() {{
if (adjustInterval) {{
clearInterval(adjustInterval);
adjustInterval = null;
isAdjusting = false;
}}
}}
// Función para ángulos predefinidos
function setPresetAngle(angle) {{
current = angle;
document.getElementById('current-angle').textContent = current;
fetch(`/set_angle?angle=${{current}}`);
}}
// Configuración del gráfico
const ctx = document.getElementById('angleChart').getContext('2d');
const chart = new Chart(ctx, {{
type: 'line',
data: {{
labels: [{chart_labels}],
datasets: [{{
label: 'Ángulo Servo',
data: [{chart_data}],
borderColor: 'rgba(75, 192, 192, 1)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
tension: 0.1,
borderWidth: 2
}}]
}},
options: {{
responsive: true,
scales: {{
y: {{
min: 0,
max: 180,
ticks: {{
stepSize: 30
}}
}},
x: {{
display: false
}}
}},
animation: {{
duration: 0
}}
}}
}});
// Actualización periódica
setInterval(() => {{
fetch('/get_data')
.then(res => res.json())
.then(data => {{
if (!isAdjusting) {{
current = data.current_angle;
document.getElementById('current-angle').textContent = current;
}}
chart.data.labels = Array.from({{length: data.angles.length}}, (_, i) => i);
chart.data.datasets[0].data = data.angles;
chart.update();
}});
}}, 1000);
</script>
</body>
</html>"""
return html
# Servidor web
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', 80))
s.listen(5)
print("Servidor web iniciado...")
while True:
try:
conn, addr = s.accept()
request = conn.recv(1024).decode()
if '/set_angle?' in request:
angle_start = request.find('/set_angle?angle=') + len('/set_angle?angle=')
angle_end = request.find(' ', angle_start)
angle = int(request[angle_start:angle_end])
set_servo_angle(angle)
conn.send('HTTP/1.1 200 OK\n')
conn.send('Content-Type: application/json\n')
conn.send('Connection: close\n\n')
conn.sendall(ujson.dumps({'status': 'success', 'angle': angle}))
elif '/get_data' in request:
conn.send('HTTP/1.1 200 OK\n')
conn.send('Content-Type: application/json\n')
conn.send('Connection: close\n\n')
conn.sendall(ujson.dumps({
'current_angle': current_angle,
'angles': angle_history
}))
else:
response = web_page()
conn.send('HTTP/1.1 200 OK\n')
conn.send('Content-Type: text/html\n')
conn.send('Connection: close\n\n')
conn.sendall(response)
conn.close()
except Exception as e:
print('Error en servidor:', e)
try:
conn.close()
except:
pass