U12: Logging, Diagnósticos y Reportes de Misión¶
Por qué el logging es una habilidad de producción¶
Un sistema robótico sin logging adecuado es intransigente: cuando algo falla en producción (y fallará), no tienes datos para diagnosticar. Un buen sistema de logging te permite:
- Depurar reproduciendo exactamente qué pasó con los datos de la misión
- Auditar - demostrar que el robot realizó las inspecciones correctamente
- Optimizar - identificar qué partes de la ruta consumen más batería
- Reportar - generar documentación automática de las misiones
Dos sistemas de logging en ROS 2¶
1. rclpy.logging - para logs del nodo¶
Escribe al terminal y al sistema de logging de ROS 2:
self.get_logger().debug("Dato de depuración detallado")
self.get_logger().info("Estado normal del sistema")
self.get_logger().warn("Situación anómala pero recuperable")
self.get_logger().error("Error que afecta la misión")
self.get_logger().fatal("Error crítico - el nodo debe terminar")
Cuándo usar cada nivel:
- DEBUG: datos del sensor, velocidades publicadas - demasiado para producción
- INFO: inicio/fin de estados, llegada a POIs, cambios de misión
- WARN: batería baja, ArUco no detectado, timeout (recuperable)
- ERROR: fallo de navegación, servicio no disponible (no recuperable en el ciclo)
- FATAL: hardware inaccesible, fallo de seguridad
Filtrar por nivel:
# Solo INFO y superior (por defecto)
ros2 run mi_pkg mi_nodo
# Solo DEBUG y superior (modo diagnóstico)
ros2 run mi_pkg mi_nodo --ros-args --log-level debug
2. logging de Python - para archivos de log¶
Para escribir a archivo además del terminal:
import logging
logger = logging.getLogger("mission")
logger.setLevel(logging.DEBUG)
# Handler de archivo
fh = logging.FileHandler("/home/student/ros2_ws/src/logs/mission.log")
fh.setFormatter(logging.Formatter("%(asctime)s [%(levelname)s] %(message)s"))
logger.addHandler(fh)
# Usar exactamente como rclpy:
logger.info("Esto va al archivo Y al terminal")
Registrar datos de misión en CSV¶
El formato CSV es adecuado para datos de series temporales porque: - Es legible por humanos y por cualquier herramienta (Excel, pandas, Python) - Permite análisis offline sin ROS instalado - Puede crecer indefinidamente sin problemas de memoria
import csv, time
from pathlib import Path
csv_path = Path("/home/student/ros2_ws/src/logs/mission.csv")
with open(csv_path, "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=[
"timestamp_s", "x", "y", "yaw_deg", "battery_pct"
])
writer.writeheader()
# En cada ciclo de muestreo:
writer.writerow({
"timestamp_s": time.time(),
"x": self.x,
"y": self.y,
"yaw_deg": math.degrees(self.yaw),
"battery_pct": self.battery_pct,
})
f.flush() # ← importante: escribe al disco inmediatamente
Generar reportes JSON¶
Al finalizar una misión, genera un resumen estructurado:
import json
from datetime import datetime
reporte = {
"meta": {
"fecha": datetime.now().isoformat(),
"duracion_s": elapsed,
"operador": os.environ.get("STUDENT_USER", "unknown"),
},
"resumen": {
"pois_visitados": len(resultados),
"aruco_detectados": sum(1 for r in resultados if r["detectado"]),
"bateria_inicial": batt_inicio,
"bateria_final": batt_final,
"distancia_m": distancia_total,
},
"estaciones": resultados,
}
with open("report.json", "w") as f:
json.dump(reporte, f, indent=2, ensure_ascii=False)
rosbag2 - grabar y reproducir misiones completas¶
rosbag2 graba todos los topics en un archivo y te permite reproducirlos
después, como si el robot estuviera corriendo de nuevo:
# Grabar (durante una misión)
ros2 bag record -o mision_2025_06_01 /odom /scan /battery_state /camera/image_raw
# Listar el contenido
ros2 bag info mision_2025_06_01/
# Reproducir (para depuración o análisis)
ros2 bag play mision_2025_06_01/ --rate 2.0 # 2x velocidad
Usa el bag grabado para: - Depurar el detector ArUco con imágenes reales de la misión - Verificar que la odometría coincide con la trayectoria esperada - Reproducir el escenario que causó un fallo
Configuración del sandbox¶
cd ~/ros2_ws
colcon build --packages-select unitree_u12_logging --symlink-install
source install/setup.bash
# Terminal 1: iniciar el logger
ros2 run unitree_u12_logging unitree_logger.py
# Terminal 2: correr cualquier nodo de misión en paralelo
ros2 run unitree_u9_inspection unitree_inspection.py
# Terminal 3: seguir los logs en tiempo real
tail -f ~/ros2_ws/src/logs/mission_*.log
Al terminar (Ctrl+C en el logger), se generarán automáticamente:
- ~/ros2_ws/src/logs/mission_<timestamp>.csv
- ~/ros2_ws/src/logs/report_<timestamp>.json
Analizar los datos con pandas (fuera de ROS)¶
import pandas as pd
import matplotlib.pyplot as plt
df = pd.read_csv("mission_20250601_142315.csv")
df["timestamp"] = pd.to_datetime(df["timestamp_s"], unit="s")
df = df.set_index("timestamp")
# Trayectoria
plt.figure(figsize=(8, 8))
plt.plot(df["x"], df["y"], "b-", linewidth=0.5)
plt.scatter(df["x"].iloc[0], df["y"].iloc[0], color="green", s=100, label="inicio")
plt.scatter(df["x"].iloc[-1], df["y"].iloc[-1], color="red", s=100, label="fin")
plt.xlabel("x (m)")
plt.ylabel("y (m)")
plt.title("Trayectoria de la misión")
plt.legend()
plt.savefig("trajectory.png")
# Batería en el tiempo
df["battery_pct"].plot(title="Batería durante la misión")
plt.ylabel("%")
plt.savefig("battery_history.png")
Ejercicios¶
Ejercicio 1 - Logging con niveles correctos
Revisa el código de unitree_inspection.py e identifica al menos
3 lugares donde el nivel de log podría mejorarse (ej: cambiar INFO
a DEBUG para datos de sensor frecuentes).
Ejercicio 2 - CSV de trayectoria
Corre unitree_logger.py mientras unitree_waypoint_nav.py completa
una misión. Abre el CSV generado y calcula manualmente la distancia
total recorrida sumando las distancias entre puntos consecutivos.
Ejercicio 3 - Grabar y reproducir con rosbag2
- Graba una misión completa con
ros2 bag record -o mi_bag /odom /battery_state - Reproduce el bag:
ros2 bag play mi_bag/ - Corre
unitree_logger.pymientras se reproduce - Compara el CSV generado con el del ejercicio 2
Ejercicio 4 - Reporte HTML
Modifica generar_reporte_final() en unitree_logger.py para que
además del JSON genere un archivo report.html con una tabla legible
y el historial de batería en texto. Utiliza f-strings de Python
para generar el HTML (no necesitas ninguna librería extra).