
Proyecto PALE BLUE DOT — Entrada 1: 1990, el año en que la Tierra se vio dos veces
El 14 de febrero de 1990, a 6.000 millones de kilómetros de casa, la sonda Voyager 1 giró sus cámaras hacia atrás y fotografió la Tierra. El resultado fue un píxel. Menos de un píxel, en realidad: una mota luminosa suspendida en un rayo de luz dispersa. Carl Sagan la llamó pale blue dot.
Diez meses después, en diciembre de ese mismo año, otra sonda volvió a mirar a la Tierra. Pero esta vez no fue un gesto poético. Fue un experimento.
La nave Galileo iba camino de Júpiter y necesitaba una asistencia gravitatoria terrestre. Sagan propuso aprovechar ese sobrevuelo para hacer algo que nunca se había hecho: apuntar todos los instrumentos científicos a la Tierra y preguntarse si, sin saber lo que estaban mirando, serían capaces de concluir que ahí abajo hay vida.
1990 fue, por tanto, el año en que la Tierra se retrató dos veces. Una como poema. Otra como sujeto experimental.
Este proyecto repite el segundo retrato, desde la Estación Espacial Internacional, con una Raspberry Pi.
1. Por qué un detector tiene que ser ciego
Imagina que construyes un algoritmo para detectar vida en exoplanetas. ¿Cómo sabes si funciona? No puedes probarlo en Marte, porque no sabemos si hay vida. No puedes probarlo en un exoplaneta, porque tampoco. Solo existe un único planeta del universo del que sabemos con certeza que está habitado, y estamos encima de él.
De ahí la idea de Sagan: la Tierra es el único control positivo disponible para toda la astrobiología. Si tu detector no encuentra vida en la Tierra, tu detector está mal.
Pero hay una condición: el detector tiene que ser ciego. No puede saber que mira la Tierra, ni puede tener conocimiento previo metido en el código. Si programas if planeta == "Tierra": print("hay vida"), no has hecho ciencia, has hecho trampa. El programa debe recibir píxeles y lecturas de sensores de un mundo anónimo, y llegar a una conclusión solo desde primeros principios.
Eso es PALE BLUE DOT: un detector de biofirmas que no sabe dónde está.
Y el resultado de Galileo, adelantándonos un poco, fue notable. La sonda encontró oxígeno gaseoso abundante, un pigmento superficial ampliamente distribuido con un borde de absorción marcado en la zona roja del espectro visible, y metano atmosférico en desequilibrio termodinámico extremo. En conjunto: fuertemente sugestivo de vida. El experimento funcionó. Pero hay un detalle en ese resultado, un cuarto hallazgo, que reservamos para la última entrada porque cambia el significado de todo lo demás.
2. La restricción que define la ingeniería: diez minutos
El reglamento del Astro Pi Challenge de la ESA es tajante. El programa debe llamarse main.py, escribirse en Python 3.13, funcionar sin errores bajo el Astro Pi Replay Tool, no usar redes, guardar sus datos únicamente en la carpeta donde reside el propio archivo, y vigilar su tiempo de ejecución y detenerse antes de que pasen 10 minutos.
Diez minutos. Eso es aproximadamente un decimoquinto de órbita: unos 4.500 kilómetros de recorrido sobre la superficie. Nuestro detector tiene que caracterizar un planeta entero en ese tiempo, sin intervención humana, sin poder conectarse a nada y sin poder fallar.
En una misión real esto se llama envolvente de vuelo: el conjunto de restricciones dentro del cual la nave tiene que operar. Y la regla de oro de la ingeniería espacial es que la envolvente se construye primero, antes que la ciencia. Un experimento brillante que se cuelga en el minuto 7 no produce ningún dato; un experimento modesto que termina limpiamente produce todos los suyos.
Por eso la versión 0.1 de nuestro main.py no mide absolutamente nada. Solo sabe contar el tiempo, escribir en disco y morirse ordenadamente.
3. El código: main.py versión 0.1 — Envolvente de vuelo
python
#!/usr/bin/env python3
"""
PALE BLUE DOT - A blind biosignature detector
Version 0.1 - Flight envelope
This version contains no science yet. It establishes the mission clock,
the safe output paths and the phase scheduler that every later version
will plug into.
Team: MonteSTEAM - IES Monterroso
"""
import csv
import logging
import sys
from datetime import datetime, timedelta
from pathlib import Path
# --- Flight envelope constants -------------------------------------------
# The ESA rulebook allows a maximum run time of 10 minutes. We target 9:30
# so that an unexpectedly slow final write can never overrun the limit.
MISSION_DURATION = timedelta(minutes=9, seconds=30)
# All output must live next to main.py. No absolute paths, no new folders.
BASE_FOLDER = Path(__file__).parent.resolve()
TELEMETRY_FILE = BASE_FOLDER / "telemetry.csv"
EVENT_LOG_FILE = BASE_FOLDER / "events.log"
# Fraction of the mission budget allocated to each phase.
# Phases are stubs in v0.1; each one is filled in in a later entry.
MISSION_PHASES = [
("SURFACE_SIGNATURE", 0.40),
("ATMOSPHERIC_SIGNATURE", 0.20),
("MAGNETIC_SIGNATURE", 0.20),
("VERDICT", 0.20),
]
def configure_logging():
"""Send human-readable events to a .log file next to main.py."""
logging.basicConfig(
filename=str(EVENT_LOG_FILE),
filemode="w",
level=logging.INFO,
format="%(asctime)s | %(levelname)s | %(message)s",
datefmt="%Y-%m-%dT%H:%M:%S",
)
def open_telemetry_file():
"""Create the telemetry CSV and write its header row."""
handle = open(TELEMETRY_FILE, "w", newline="", encoding="utf-8")
writer = csv.writer(handle)
writer.writerow(["timestamp_utc", "elapsed_s", "phase", "measurement", "value"])
return handle, writer
def run_phase(phase_name, phase_deadline, writer):
"""
Placeholder for a science phase.
Every real phase added in later versions must respect two rules:
it never runs past phase_deadline, and it never raises.
"""
logging.info("Phase %s started", phase_name)
writer.writerow([
datetime.now().isoformat(timespec="seconds"),
0,
phase_name,
"status",
"stub",
])
logging.info("Phase %s finished", phase_name)
def main():
configure_logging()
logging.info("PALE BLUE DOT v0.1 - mission start")
start_time = datetime.now()
hard_deadline = start_time + MISSION_DURATION
handle, writer = open_telemetry_file()
try:
for phase_name, budget_fraction in MISSION_PHASES:
if datetime.now() >= hard_deadline:
logging.warning("Mission clock exhausted, skipping %s", phase_name)
break
phase_deadline = min(
datetime.now() + MISSION_DURATION * budget_fraction,
hard_deadline,
)
try:
run_phase(phase_name, phase_deadline, writer)
except Exception as error:
# A failing phase must never abort the whole mission.
logging.error("Phase %s failed: %s", phase_name, error)
handle.flush()
finally:
handle.close()
elapsed = (datetime.now() - start_time).total_seconds()
logging.info("Mission ended after %.1f s", elapsed)
if __name__ == "__main__":
try:
main()
except Exception as fatal_error:
# Last line of defence: the program must never crash on the ISS.
logging.critical("Unhandled error: %s", fatal_error)
sys.exit(0)
Cuatro decisiones de diseño merecen explicación, porque son las que sostendrán todo lo demás:
El margen de 30 segundos. No apuntamos a 10:00 sino a 9:30. El último volcado a disco puede tardar más de lo previsto y sobrepasar el límite significa descalificación. En vuelo espacial, los márgenes no son pesimismo: son presupuesto.
Path(__file__).parent. El reglamento prohíbe rutas absolutas y crear directorios nuevos. Esta línea garantiza que el programa escriba donde debe, sin importar desde dónde se lance.
El try/except dentro del bucle de fases. Si la fase magnética falla, las otras tres deben continuar. Una misión que devuelve tres cuartas partes de sus datos es un éxito parcial; una que devuelve cero es un fracaso total.
El except final que llama a sys.exit(0). Un programa que termina con un error no gestionado incumple el checklist. Esta es la red de seguridad: pase lo que pase, salimos limpiamente.
4. Lo que la v0.1 todavía no cumple
Seamos honestos con el checklist. Esta versión aún no sería aceptada por Mission Control, porque el reglamento exige que el programa use al menos un sensor del Sense HAT o la cámara, y la v0.1 no toca ninguno. Eso llega en la siguiente entrada.
| Versión | Entrada | Qué añade |
|---|---|---|
| v0.1 | 1 | Envolvente de vuelo: reloj, rutas, planificador |
| v0.2 | 2-3 | Sensores y cámara, primer dato real |
| v0.3-v0.7 | 4-8 | Las cuatro fases científicas |
| v0.8 | 9 | El veredicto y su umbral |
| v0.9 | 10 | Dry run completo y checklist ESA |
| v1.0 | 11-12 | Versión de entrega |
Próxima entrada
Construiremos el laboratorio: Astro Pi Replay, el emulador del Sense HAT, y la primera lectura real de un mundo del que fingiremos no saber nada.
2026-09-19T14:01:22 | INFO | PALE BLUE DOT v0.1 – mission start
2026-09-19T14:01:22 | INFO | Phase SURFACE_SIGNATURE started
2026-09-19T14:01:22 | INFO | Phase SURFACE_SIGNATURE finished
2026-09-19T14:01:22 | INFO | Phase ATMOSPHERIC_SIGNATURE started
2026-09-19T14:01:22 | INFO | Phase ATMOSPHERIC_SIGNATURE finished
2026-09-19T14:01:22 | INFO | Phase MAGNETIC_SIGNATURE started
2026-09-19T14:01:22 | INFO | Phase MAGNETIC_SIGNATURE finished
2026-09-19T14:01:22 | INFO | Phase VERDICT started
2026-09-19T14:01:22 | INFO | Phase VERDICT finished
2026-09-19T14:01:22 | INFO | Mission ended after 0.0 s




