Funciona todo sin modulo_imu

This commit is contained in:
2026-05-21 09:51:42 -05:00
parent 026820f205
commit 19dc4bcf90
19 changed files with 1857 additions and 0 deletions
+192
View File
@@ -0,0 +1,192 @@
import serial
import math
class ChasisESP32:
"""
Interfaz UART con el ESP32.
Protocolo de TX (RPi → ESP32):
M:s1,f1,s2,f2,s3,f3,s4,f4\\n
s = signo → -1 | 0 | 1
f = fracción de VEL_MAX → 0.000 – 1.000
S:\\n → Stop inmediato
C:\\n → Iniciar autocalibración
Protocolo de RX (ESP32 → RPi):
T:t1,t2,t3,t4\\n → Odometría acumulada (ticks con signo)
K:v1,v2,v3,v4\\n → VEL_MAX_TICKS calibrada por motor (ticks/s)
D:...\\n → Mensajes de debug del ESP32
"""
# ── Geometría ──────────────────────────────────────────────────────────────
TICKS_POR_VUELTA = 20.0
RADIO_RUEDA = 0.03 # metros
ANCHO_EJE = 0.11 # metros (distancia entre ruedas del mismo eje)
# VEL_MAX_TICKS se actualiza cuando el ESP32 envía la trama K:
# Valor inicial conservador: 200 ticks/s
# M1=FL, M2=FR, M3=RL, M4=RR
_VEL_MAX_TICKS = [200.0, 200.0, 200.0, 200.0]
def __init__(self, puerto: str = '/dev/ttyAMA0', baudrate: int = 921600):
self.METROS_POR_TICK = (2.0 * math.pi * self.RADIO_RUEDA) / self.TICKS_POR_VUELTA
try:
self.puerto = serial.Serial(puerto, baudrate, timeout=0.05)
print(f"✅ Conexión UART activa en {puerto}")
except Exception as e:
print(f"❌ Error UART: {e}")
self.puerto = None
self.x, self.y, self.theta = 0.0, 0.0, 0.0
self.ticks_anteriores = [0, 0, 0, 0]
self.primera_lectura = True
# ── Velocidad máxima (promedio por lado) ───────────────────────────────────
@property
def vel_max_izq(self) -> float:
"""Ticks/s máximos del lado izquierdo (promedio M1 y M3)."""
return (self._VEL_MAX_TICKS[0] + self._VEL_MAX_TICKS[2]) / 2.0
@property
def vel_max_der(self) -> float:
"""Ticks/s máximos del lado derecho (promedio M2 y M4)."""
return (self._VEL_MAX_TICKS[1] + self._VEL_MAX_TICKS[3]) / 2.0
# ── Lectura de odometría y tramas de control ───────────────────────────────
def leer_odometria(self) -> bool:
"""
Lee una línea del ESP32 y la procesa.
Retorna True si se actualizó la odometría.
"""
if not (self.puerto and self.puerto.in_waiting > 0):
return False
try:
linea = self.puerto.readline().decode('utf-8', errors='ignore').strip()
except Exception:
return False
# ── T: odometría ──────────────────────────────────────────────────────
if linea.startswith("T:"):
partes = linea[2:].split(',')
if len(partes) != 4:
return False
try:
ticks_actuales = [int(p) for p in partes]
except ValueError:
return False
if self.primera_lectura:
self.ticks_anteriores = ticks_actuales
self.primera_lectura = False
return True
delta = [ticks_actuales[i] - self.ticks_anteriores[i] for i in range(4)]
self.ticks_anteriores = ticks_actuales
# Desplazamiento lineal de cada lado (metros)
# M1(FL) y M3(RL) → izquierda
# M2(FR) y M4(RR) → derecha
d_izq = ((delta[0] + delta[2]) / 2.0) * self.METROS_POR_TICK
d_der = ((delta[1] + delta[3]) / 2.0) * self.METROS_POR_TICK
d_centro = (d_izq + d_der) / 2.0
d_theta = (d_der - d_izq) / self.ANCHO_EJE
self.x += d_centro * math.cos(self.theta + d_theta / 2.0)
self.y += d_centro * math.sin(self.theta + d_theta / 2.0)
self.theta += d_theta
return True
# ── K: VEL_MAX calibrada por el ESP32 ─────────────────────────────────
elif linea.startswith("K:"):
partes = linea[2:].split(',')
if len(partes) == 4:
try:
nuevos = [float(p) for p in partes]
# Validar que los valores sean razonables (10–2000 ticks/s)
if all(10.0 < v < 2000.0 for v in nuevos):
self._VEL_MAX_TICKS = nuevos
print(
f"\n✅ VEL_MAX actualizada → "
f"M1={nuevos[0]:.1f} M2={nuevos[1]:.1f} "
f"M3={nuevos[2]:.1f} M4={nuevos[3]:.1f} ticks/s"
)
else:
print(f"\n⚠️ VEL_MAX recibida fuera de rango: {nuevos} — ignorada")
except ValueError:
pass
# ── D: debug del ESP32 ────────────────────────────────────────────────
elif linea.startswith("D:"):
print(f"\r[ESP32] {linea} ", end="")
return False
# ── Envío de velocidad diferencial ─────────────────────────────────────────
def enviar_velocidad(self, v: float, w: float) -> None:
"""
Convierte velocidad lineal (m/s) y angular (rad/s) al protocolo M:.
Cinemática diferencial:
v_izq = v - w * (ANCHO_EJE / 2)
v_der = v + w * (ANCHO_EJE / 2)
Cada rueda se envía con:
s = signo (-1, 0, 1)
f = |vel_rueda_ticks| / VEL_MAX_TICKS del motor (0.0 – 1.0)
Trama resultante:
M:s1,f1,s2,f2,s3,f3,s4,f4\\n
"""
if not self.puerto:
return
# Velocidades en m/s para cada lado
v_izq_ms = v - w * (self.ANCHO_EJE / 2.0)
v_der_ms = v + w * (self.ANCHO_EJE / 2.0)
# Convertir a ticks/s
v_izq_ticks = v_izq_ms / self.METROS_POR_TICK
v_der_ticks = v_der_ms / self.METROS_POR_TICK
# Velocidades por motor (izq = M1, M3 / der = M2, M4)
vel_motores = [v_izq_ticks, v_der_ticks, v_izq_ticks, v_der_ticks]
# Umbral mínimo de movimiento: 0.5 ticks/s ≈ movimiento nulo
UMBRAL = 0.5
partes = []
todos_cero = True
for i, vel in enumerate(vel_motores):
if abs(vel) < UMBRAL:
s, f = 0, 0.0
else:
s = 1 if vel > 0 else -1
# Normalizar respecto a la VEL_MAX de cada motor específico
f = min(abs(vel) / self._VEL_MAX_TICKS[i], 1.0)
todos_cero = False
partes.append(f"{s},{f:.3f}")
if todos_cero:
cmd = "S:\n"
else:
cmd = "M:" + ",".join(partes) + "\n"
print(f"\r[UART TX] → {cmd.strip():<55}", end="", flush=True)
try:
self.puerto.write(cmd.encode('utf-8'))
except Exception as e:
print(f"\n❌ Error al escribir UART: {e}")
# ── Calibración ───────────────────────────────────────────────────────────
def calibrar_pid(self) -> None:
"""Solicita autocalibración al ESP32. La respuesta K: actualizará VEL_MAX."""
if self.puerto:
try:
self.puerto.write(b"C:\n")
print("\n⚙️ Solicitud de calibración enviada al ESP32.")
except Exception as e:
print(f"\n❌ Error al enviar calibración: {e}")