Skip to content
RFrftools.io
Satellite Communications30 de abril de 20265 min de lectura

Creación de un cronograma de pases para estaciones terrestres con Skyfield

Preparado para el Ansys STK al atardecer: calcula las predicciones de pases del SGP4 para tu estación terrestre de radio amateur o cubesat con Skyfield.

Contenido

Por qué necesitas un programador de pases local

Si tienes una emisora SatNogs, una choza de radioaficionados que rastrean el AO-91/ISS/NOAA o un segmento terrestre con cubesat en las primeras etapas, necesitas hacer predicciones de pases automatizadas. Las herramientas comerciales (STK, NOVA) son exageradas. Los predictores web gratuitos (AMSAT, Heavens-Above) no se escriben bien. Con STK Cloud a finales de marzo de 2026, la alternativa gratuita más limpia es Skyfield, el sucesor moderno de PyEphem en Python.

Esta publicación describe un programador completo de estaciones terrenas en unas 80 líneas de Python: busca TLE nuevos, calcula las próximas 24 horas de pases para una lista de satélites, filtra por elevación mínima, calcula el Doppler para la frecuencia de enlace descendente y emite iCalendar para que los pases aparezcan en tu aplicación de calendario.

Instalar

pip install skyfield requests icalendar

Eso es todo. Skyfield extrae sus propias efemérides (DE421, IERS) en la primera ejecución: unos 17 MB en caché localmente.

Paso 1: Obtenga los TLE actuales

Los TLE están a la deriva: úsalos dentro de los 7 días de la época. Extrae el conjunto de elementos actual de Celestrak:

from skyfield.api import load

stations_url = 'https://celestrak.org/NORAD/elements/gp.php?GROUP=amateur&FORMAT=tle'
sats = load.tle_file(stations_url)
by_name = {s.name: s for s in sats}
print(f'Loaded {len(sats)} amateur-radio satellites')

Elija sus objetivos por nombre (distinguen mayúsculas de minúsculas en el archivo Celestrak):

targets = [
    by_name['AO-91 (FOX-1B)'],
    by_name['ISS (ZARYA)'],
    by_name['NOAA 19'],
    by_name['GreenCube (IO-117)'],
]

Paso 2: Defina su estación terrestre

from skyfield.api import wgs84, load

ts = load.timescale()
my_station = wgs84.latlon(40.0150, -105.2705, elevation_m=1624)  # Boulder, CO
min_elevation_deg = 10  # horizon mask to skip low passes

Utilice el parámetro elevation_m: afecta materialmente al rango de inclinación en ángulos de elevación bajos.

Paso 3: Encuentra eventos de ascenso, culminación o descenso

Satellite.find_events() devuelve una lista plana de eventos (0 = ascenso, 1 = culminación, 2 = conjunto). Procésalos en triples:
from datetime import timedelta

t0 = ts.now()
t1 = ts.from_datetime(t0.utc_datetime() + timedelta(hours=24))

for sat in targets:
    t, events = sat.find_events(my_station, t0, t1,
                                altitude_degrees=min_elevation_deg)
    # Group into rise/peak/set triples
    triples = zip(t[0::3], t[1::3], t[2::3])
    for rise, culm, setting in triples:
        # Peak elevation
        topo = (sat - my_station).at(culm)
        alt, az, dist = topo.altaz()
        print(f'{sat.name:30s}  {rise.utc_iso():20s}  peak {alt.degrees:4.1f}°  slant {dist.km:4.0f} km')

Ignora los satélites que ya están por encima del horizonte en el punto t0(la lista de eventos comienza con un par de culminación y conjunto).

Paso 4: cambio Doppler durante una pasada

En el caso de los aficionados a la frecuencia UHF (435 MHz), un pase puede desplazar el operador en ± 10 kHz; el receptor debe rastrear. Calcula el Doppler instantáneo a partir de la velocidad de rango:

import numpy as np

FREQ_HZ = 435_800_000  # AO-91 downlink
C = 299_792_458.0      # m/s

def doppler_shift(sat, observer, t):
    """Positive when satellite approaching (received frequency is higher)."""
    # Numerical range rate from 1s apart
    dt = 1.0  # seconds
    t_next = ts.from_datetime(t.utc_datetime() + timedelta(seconds=dt))
    r0 = (sat - observer).at(t).distance().m
    r1 = (sat - observer).at(t_next).distance().m
    range_rate = (r1 - r0) / dt  # m/s, positive = moving away
    return -FREQ_HZ * range_rate / C  # Hz, positive = approaching

# Sample across a pass
for pct in [0, 25, 50, 75, 100]:
    t_sample = ts.from_datetime(
        rise.utc_datetime() + (setting.utc_datetime() - rise.utc_datetime()) * pct / 100
    )
    d = doppler_shift(sat, my_station, t_sample)
    print(f'  {pct:3d}% pass:  doppler = {d:+8.0f} Hz')

En una pasada aérea típica del AO-91 de 6 minutos, verás un barrido Doppler desde aproximadamente +10 kHz (subida) pasando por 0 (cenit) hasta −10 kHz (conjunto). Síguelo en el VFO principal de la radio para no perder la señal.

Paso 5: Vincula el presupuesto de cada pase

Cuando tengas el rango de inclinación, introdúcelo en la calculadora de pérdidas por trayectoria en espacios libres de rftools o calcula en línea:

def fspl_db(distance_m, freq_hz):
    return 20 * np.log10(4 * np.pi * distance_m * freq_hz / C)

# At zenith of an AO-91 pass (~800 km slant at 40° elevation from Boulder)
fspl = fspl_db(800_000, FREQ_HZ)
print(f'FSPL = {fspl:.1f} dB')  # ~143 dB

Para obtener un presupuesto completo de enlaces de Montecarlo con los modelos de propagación de la UIT-R, utilice el Satellite Link Budget Analyzer y comparta la URL del escenario con el libro de registro de la estación.

Paso 6: Emite iCalendar para que los pases aparezcan en tu calendario

Aquí es donde el planificador se vuelve útil para los humanos:

from icalendar import Calendar, Event
from datetime import datetime
import pytz

cal = Calendar()
cal.add('prodid', '-//rftools ground station//')
cal.add('version', '2.0')

for sat in targets:
    t, events = sat.find_events(my_station, t0, t1, altitude_degrees=10)
    triples = zip(t[0::3], t[1::3], t[2::3])
    for rise, culm, setting in triples:
        topo = (sat - my_station).at(culm)
        alt, az, _ = topo.altaz()
        ev = Event()
        ev.add('summary', f'{sat.name} (peak {alt.degrees:.0f}°)')
        ev.add('dtstart', rise.utc_datetime())
        ev.add('dtend', setting.utc_datetime())
        ev.add('description', f'Culminate at {culm.utc_iso()}, AZ {az.degrees:.0f}°')
        cal.add_component(ev)

with open('passes.ics', 'wb') as f:
    f.write(cal.to_ical())
print('Wrote passes.ics — import into Google Calendar / Apple Calendar / Outlook')

Importa passes.ics a Apple Calendar, Google Calendar o Outlook y recibirás notificaciones push 5 minutos antes de cada pase.

Paso 7: ejecútalo en cron

Coloca el script en ~/bin/station-passes.py y córtalo una vez al día:

# Refresh pass schedule nightly at 03:00 local
0 3 * * * /usr/bin/python3 ~/bin/station-passes.py > ~/passes.ics 2>> ~/station.log

Combínalo con curl --upload-file a un calendario webdav y tu teléfono verá todos los pases automáticamente.

Sustituir a STK Cloud, pieza por pieza

Para pequeñas operaciones comerciales de aficionados, cubesats y pequeñas, el conjunto de herramientas combinado reemplaza las funciones diarias de STK Cloud:

Función STK CloudReemplazo gratuito
Predicción de pasesSkyfield
Presupuesto de enlaceAnalizador presupuestario rftools Satellite Link
Doppler/curvas de rangoVelocidad de alcance de Skyfield
Máscaras de elevación y terrenoSkyfield + DEM local (SRTM)
Visualización 3DCesium.js
Detección de conjuncionesSOCRATES
Para estudios comerciales o programas institucionales de diseño de misiones, igual querrás usar el STK de escritorio o el GMAT de la NASA. Para todo lo que no sea eso, incluidas las revisiones de diseño de AMSAT, la gama Skyfield + rftools es completa.

Lecturas adicionales

Preguntas Frecuentes

Para la predicción de pases, las curvas Doppler, los plazos de elevación y las descargas programadas, sí. Skyfield implementa el SGP4 con efemérides validadas con las del JPL Horizons, y iguala las predicciones de STK sobre el tiempo de caducidad en unos segundos, en un horizonte de 30 días. No reemplaza la visualización 3D, los informes integrados ni el análisis conjunto de STK; para ello, utilice Cesium.js o SOCRATES.
Dentro de los 7 días de la época para una precisión de posición inferior a 1 km para los satélites LEO. Los TLE de más de 14 días de antigüedad pueden desviarse más de 10 km en una trayectoria larga, lo que hace que los tiempos de pase varíen más de 30 segundos, lo suficiente como para perder una ventana de pase de 6 minutos. Obtén TLEs nuevos de Celestrak antes de cada ejecución de programación.
Tres causas probables: (1) las coordenadas de tu estación terrestre están desfasadas decenas de metros (compruébalo con el GPS), (2) estás utilizando una época de TLE más antigua que el predictor de AMSAT, (3) no estás teniendo en cuenta la elevación de la estación sobre el nivel del mar (utilízalo elevation_m en wgs84.latlon). Se esperan diferencias de menos de 10 segundos y no afectan a las operaciones prácticas.
Numéricamente: calcule el rango de inclinación en el tiempo t y t+1 s, divídalo para obtener la velocidad de rango en m/s, luego doppler_shift_hz = -frequency_hz * range_rate / c (positivo cuando el satélite se acerca). Skyfield no tiene una función Doppler integrada porque depende de la frecuencia de los enlaces descendentes/ascendentes; calcúlala tú mismo. Un pase LEO típico de 435 MHz recorre ±10 kHz durante unos 6 minutos.
Para GEO (geoestacionario) sí, pero la precisión del SGP4/TLE se degrada por encima de ~ 6000 km. Utilice un solucionador de elementos keplerianos o efemérides completas (DE440) para GEO. Para misiones en el espacio profundo, usa las efemérides basadas en núcleos de Skyfield con los núcleos SPICE de la NASA. Para las órbitas lunares, carga las efemérides del DE440 o del jup365 a través de skyfield.api.load ('de440.bsp').

Artículos Relacionados