Skip to content
RFrftools.io
Satellite Communications30 de abril de 202610 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. Tutorial completo de Python con extracción de TLE, máscaras de elevación, Doppler y exportación de iCalendar.

Contenido

Por qué necesitas un programador de pases local

Si diriges 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, es decir, unos 17 MB en caché localmente.

Paso 1: recupera 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')
Elige tus objetivos por su 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ámetroelevation_m: afecta considerablemente al rango de inclinación en ángulos de elevación bajos.

Paso 3: Encuentra eventos de ascenso, culminación o descensoSatellite.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 elt0(la lista de eventos comienza con un par de puntos culminantes y conjuntos).

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 (ajuste). 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 los enlaces de Montecarlo con los modelos de propagación del UIT-R, utilice el Satellite Link Budget Analyzer y comparta la URL del escenario con el cuaderno de bitácora 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 el artículo 12§ 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.pyy 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 concurl --upload-filea 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 CloudSustitución gratuita
Predicción de pasesSkyfield
Link budgetrftools Satellite Link Budget Analyzer
Doppler/curvas de rangoVelocidad de alcance de Skyfield
Máscaras de elevación más terrenoSkyfield + DEM local (SRTM)
Visualización 3DCesium.js
Detección conjuntaSÓCRATES
Para estudios profesionales o programas institucionales relacionados con el 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

Artículos Relacionados