
BermejaSat #9: Caja Negra en Tierra — Data Logging Automático en CSV con Python
Introducción
En el desarrollo de una misión aerospacial, recibir telemetría visual en pantalla es solo la mitad del trabajo. Si el ordenador de la Estación Terrena sufre una desconexión accidental, la batería del portátil se agota o la gráfica de control se cierra en pleno vuelo, los datos científicos capturados hasta ese instante se perderían para siempre.
Para garantizar la integridad del experimento de acuerdo con los estándares de la competición CanSat de la ESA, hemos actualizado el software de nuestra Estación Terrena en Python. Ahora actúa como una Caja Negra en tierra, almacenando automáticamente cada paquete de datos recibido en el disco duro dentro de un archivo de valores separados por comas (.csv).
🛠️ Conceptos Clave de la “Caja Negra”
Para que el almacenamiento de datos sea fiable en entornos de pruebas, el script de Python incorpora tres decisiones de diseño fundamentales:
- Nombres de archivo dinámicos (Timestamping): Cada vez que se ejecuta la Estación Terrena, el programa lee la hora del sistema y crea un archivo único (por ejemplo,
telemetria_bermejasat_20260913_164500.csv). Esto evita sobrescribir registros de vuelos o pruebas anteriores. - Volcado directo a disco (
flush()): Por defecto, los sistemas operativos guardan las escrituras en una memoria intermedia (búfer) antes de escribirlas físicamente en el disco duro. Al forzar la ordenarchivo_csv.flush()tras recibir cada paquete, garantizamos que los datos se graben en el almacenamiento físico en el mismo milisegundo en que llegan. - Registro con doble marca temporal: Guardamos tanto el tiempo relativo de misión en segundos como la marca de tiempo de alta precisión del ordenador (
HH:MM:SS.mmm).
💻 Código Completo de la Estación Terrena (Python)
Guarda este script en tu ordenador como estacion_terrena_csv.py. Asegúrate de tener instaladas las librerías pyserial y matplotlib (pip install pyserial matplotlib).
Python
import serial
import time
import csv
from datetime import datetime
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
# --- CONFIGURACIÓN DE CONEXIÓN Y ARCHIVO ---
PUERTO_SERIE = 'COM3' # Cambia 'COM3' por el puerto asignado a tu placa (ej: /dev/ttyUSB0 en Linux/Mac)
BAUD_RATE = 115200
# Generar nombre único para el archivo CSV usando fecha y hora actual
timestamp_inicio = datetime.now().strftime("%Y%m%d_%H%M%S")
NOMBRE_ARCHIVO_CSV = f"telemetria_bermejasat_{timestamp_inicio}.csv"
# Crear y preparar el archivo CSV con la cabecera de la misión
try:
archivo_csv = open(NOMBRE_ARCHIVO_CSV, mode='w', newline='', encoding='utf-8')
escritor_csv = csv.writer(archivo_csv)
# Encabezados oficiales de la trama BermejaSat
escritor_csv.writerow(['Hora_PC', 'ID_Paquete', 'Altitud_m', 'Presion_hPa', 'Temperatura_C', 'RSSI_dBm'])
archivo_csv.flush()
print(f"[OK] Caja negra iniciada. Registrando en: {NOMBRE_ARCHIVO_CSV}")
except Exception as e:
print(f"[ERROR] No se pudo crear el archivo CSV: {e}")
exit()
# Abrir el puerto serie de comunicación USB
try:
ser = serial.Serial(PUERTO_SERIE, BAUD_RATE, timeout=1)
time.sleep(2) # Espera de seguridad para estabilizar la línea
print(f"[OK] Estación Terrena conectada al puerto {PUERTO_SERIE}")
except Exception as e:
print(f"[ERROR] No se pudo abrir el puerto serie {PUERTO_SERIE}: {e}")
archivo_csv.close()
exit()
# Listas históricas para el renderizado gráfico
tiempos = []
altitudes = []
presiones = []
rssis = []
tiempo_inicio_mision = time.time()
# Configuración de la interfaz gráfica
fig, (ax_alt, ax_pres, ax_rssi) = plt.subplots(3, 1, figsize=(10, 8))
fig.canvas.manager.set_window_title(f'BermejaSat Ground Station — Grabando en: {NOMBRE_ARCHIVO_CSV}')
def actualizar_graficas(frame):
if ser.in_waiting > 0:
try:
linea = ser.readline().decode('utf-8').strip()
datos = linea.split(',')
# Validación de trama completa (5 parámetros)
if len(datos) == 5:
pkt_id = int(datos[0])
alt = float(datos[1])
pres = float(datos[2])
temp = float(datos[3])
rssi = int(datos[4])
t_mision = round(time.time() - tiempo_inicio_mision, 1)
hora_pc = datetime.now().strftime("%H:%M:%S.%f")[:-3]
# 1. ESCRIBIR EN EL ARCHIVO CSV (REGISTRO FÍSICO EN DISCO)
escritor_csv.writerow([hora_pc, pkt_id, alt, pres, temp, rssi])
archivo_csv.flush() # Volcado inmediato para evitar pérdida de datos
# 2. ACTUALIZAR VISTA EN TIEMPO REAL
tiempos.append(t_mision)
altitudes.append(alt)
presiones.append(pres)
rssis.append(rssi)
# Mostrar los últimos 30 datos recibidos
t_plot = tiempos[-30:]
alt_plot = altitudes[-30:]
pres_plot = presiones[-30:]
rssi_plot = rssis[-30:]
# Gráfica de Altitud
ax_alt.clear()
ax_alt.plot(t_plot, alt_plot, color='#d9534f', linewidth=2, marker='o', markersize=3)
ax_alt.set_title(f'Perfil de Altitud: {alt:.1f} m | Paquete #{pkt_id}', fontsize=10, fontweight='bold')
ax_alt.set_ylabel('Altitud (m)')
ax_alt.grid(True, linestyle='--', alpha=0.5)
# Gráfica de Presión Barométrica
ax_pres.clear()
ax_pres.plot(t_plot, pres_plot, color='#0275d8', linewidth=2, marker='s', markersize=3)
ax_pres.set_title(f'Presión Barométrica: {pres:.1f} hPa', fontsize=10, fontweight='bold')
ax_pres.set_ylabel('Presión (hPa)')
ax_pres.grid(True, linestyle='--', alpha=0.5)
# Gráfica de Cobertura LoRa (RSSI)
ax_rssi.clear()
ax_rssi.plot(t_plot, rssi_plot, color='#5cb85c', linewidth=2)
ax_rssi.set_title(f'Potencia de Señal Radio: {rssi} dBm', fontsize=10, fontweight='bold')
ax_rssi.set_ylabel('RSSI (dBm)')
ax_rssi.set_xlabel('Tiempo de Misión (s)')
ax_rssi.grid(True, linestyle='--', alpha=0.5)
plt.tight_layout()
except (ValueError, UnicodeDecodeError):
pass # Filtrar tramas incompletas o erróneas de transmisión
# Refresco gráfico dinámico
ani = FuncAnimation(fig, actualizar_graficas, interval=500, cache_frame_data=False)
plt.tight_layout()
try:
plt.show()
finally:
# Garantizar el cierre seguro del puerto y archivo al salir
ser.close()
archivo_csv.close()
print(f"\n[OK] Sesión finalizada. Telemetría guardada con éxito en: {NOMBRE_ARCHIVO_CSV}")
📊 Análisis Posterior de Datos
Una vez concluida la prueba de vuelo, el archivo .csv generado se puede importar directamente en herramientas como Excel, Google Sheets, Calc o MATLAB para realizar:
- Cálculo de la velocidad media de descenso ($\text{m/s}$).
- Gráficas comparativas entre presión atmosférica y altura.
- Evaluación del alcance de radio (RSSI) en función del tiempo.
Conclusión Pedagógica
Integrar la funcionalidad de Data Logging en la Estación Terrena enseña al alumnado la importancia del almacenamiento de datos redundante y la persistencia de información en proyectos críticos. El flujo de telemetría de BermejaSat no solo se visualiza en directo, sino que queda registrado para su posterior análisis científico y presentación en la memoria técnica del proyecto CanSat.
Etiqueta:BermejaSat, cansat, CSV, Data Logging, ESA, Estación Terrena, ies monterroso, montesteam, pySerial, python, Telemetría



