스카이필드와 함께 지상역 패스 스케줄 작성하기
Ansys STK 일몰 준비 완료: 스카이필드를 사용하여 아마추어 라디오 또는 큐브샛 지상국의 SGP4 통과 예측을 계산합니다.TLE 페치, 엘레베이션 마스크, 도플러 및 iCalendar 익스포트를 포함한 전체 Python 워크스루.
목차
- 로컬 패스 스케줄러가 필요한 이유
- 설치```bash
- 1단계: 현재 TLE 가져오기
- 3단계: 상승/절정/설정 이벤트 찾기`Satellite.find_events()`함수는 이벤트의 단순 목록 (0 = 상승, 1 = 절정, 2 = 세트) 을 반환합니다.세 배로 처리하세요.```python
- 4단계: 패스 상에서의 도플러 시프트
- 5단계: 각 패스의 연결 예산
- 6단계: 패스가 캘린더에 표시되도록 iCalendar를 내보냅니다.
- 7단계: 크론에서 실행`~/bin/station-passes.py`안에 스크립트를 드롭하고 하루에 한 번 크론하십시오:```cron
- STK Cloud를 하나씩 교체하기
- 더 읽어보기
로컬 패스 스케줄러가 필요한 이유
SatNOGS 방송국, AO-91/ISS/NOAA를 추적하는 아마추어 라디오 쉑 또는 초기 단계의 큐브샛 그라운드 구간을 운영하는 경우 자동 패스 예측이 필요합니다.상용 도구 (STK, NOVA) 는 무용지물입니다.무료 웹 예측 변수 (AMSAT, Heavens-Above) 는 스크립트를 제대로 작성하지 못합니다.STK 클라우드가 2026년 3월에 출시되는 지금, 가장 깨끗한 무료 대안은 파이펫의 최신 파이썬 후속 버전인 스카이필드 입니다.
이 게시물에서는 새 TLE를 가져오고, 위성 목록의 다음 24시간 패스를 계산하고, 최소 고도를 기준으로 필터링하고, 다운링크 주파수에 대해 도플러를 계산하고, 캘린더 앱에 패스가 표시되도록 iCalendar를 내보내는 등 약 80줄의 Python으로 완전한 지상국 스케줄러를 살펴봅니다.
설치pip install skyfield requests icalendar
그게 다예요.스카이필드는 최초 실행 시 자체 에페메리드 (DE421, IERS) 를 가져옵니다. 약 17MB가 로컬에 캐시됩니다.
pip install skyfield requests icalendar1단계: 현재 TLE 가져오기
TLE가 드리프트 (drift) — 에포크 후 7일 이내에 사용할 수 있습니다.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')이름을 기준으로 대상을 선택하세요 (Celestrak 파일에서는 대소문자를 구분합니다).targets = [
by_name['AO-91 (FOX-1B)'],
by_name['ISS (ZARYA)'],
by_name['NOAA 19'],
by_name['GreenCube (IO-117)'],
]## 2단계: 그라운드 스테이션 정의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 passeselevation_m파라미터를 사용하세요. 이 파라미터는 낮은 고도각에서의 경사 범위에 중대한 영향을 미칩니다.
3단계: 상승/절정/설정 이벤트 찾기Satellite.find_events()함수는 이벤트의 단순 목록 (0 = 상승, 1 = 절정, 2 = 세트) 을 반환합니다.세 배로 처리하세요.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')
t0지평선 위에 이미 있는 위성은 무시하십시오 (이벤트 목록은 절정/설정 쌍으로 시작됨).
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')4단계: 패스 상에서의 도플러 시프트
아마추어 UHF (435MHz) 의 경우 패스 한 번으로 캐리어가 ±10kHz 정도 이동할 수 있으므로 수신기가 추적해야 합니다.레인지레이트로 순시 도플러 계산하기:
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')일반적인 6분 AO-91 오버헤드 패스의 경우 약 +10kHz (상승) 에서 0 (제니스) 을 거쳐 -10kHz (세트) 까지의 도플러 스위프를 볼 수 있습니다.라디오의 메인 VFO에서 이 내용을 추적하면 신호가 손실되지 않습니다.
5단계: 각 패스의 연결 예산
범위가 기울어지면 rftools 여유 공간 경로 손실 계산기 에 입력하거나 인라인으로 계산하세요.
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 dBITU-R 전파 모델을 사용한 전체 몬테카를로 링크 예산을 확인하려면 위성 링크 버짓 분석기 를 사용하고 시나리오 URL을 스테이션 로그북과 공유하십시오.
6단계: 패스가 캘린더에 표시되도록 iCalendar를 내보냅니다.
이렇게 하면 스케줄러가 사람에게 유용하게 쓰일 수 있습니다.
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')passes.ics버전을 애플 캘린더/구글 캘린더/아웃룩으로 가져오면 모든 패스 5분 전에 푸시 알림을 받을 수 있습니다.
7단계: 크론에서 실행~/bin/station-passes.py안에 스크립트를 드롭하고 하루에 한 번 크론하십시오:# Refresh pass schedule nightly at 03:00 local
0 3 * * * /usr/bin/python3 ~/bin/station-passes.py > ~/passes.ics 2>> ~/station.log
curl --upload-file과 함께 webdav 캘린더에 연결하면 휴대폰이 모든 패스를 자동으로 볼 수 있습니다.
# Refresh pass schedule nightly at 03:00 local
0 3 * * * /usr/bin/python3 ~/bin/station-passes.py > ~/passes.ics 2>> ~/station.logSTK Cloud를 하나씩 교체하기
아마추어/Cubesat/소규모 커머셜 운영의 경우 통합된 툴킷이 STK Cloud의 일상적인 드라이버 기능을 대체합니다.
| STK 클라우드 기능 | 무료 교체 |
|---|---|
| 패스 예측 | 스카이필드 |
| 링크 버짓 | rftools 위성 링크 버짓 애널라이저 |
| 도플러/레인지 커브 | 스카이필드 레인지-레이트 |
| 엘레베이션 마스크 + 터레인 | 스카이필드+로컬 DEM (SRTM) |
| 3D 시각화 | Cesium.js |
| 결합 스크리닝 | 소크라테스 |
더 읽어보기
- 스카이필드 문서
- 셀레스트락 TLE 카탈로그
- rftools 새틀라이트 패스 프리딕터 — 가장 일반적인 아마추어 위성을 위한 이 스케줄러의 호스팅 버전
- STK 클라우드에서 마이그레이션하기: 무료 대안 — 전체 STK 워크플로우 교체에 관한 관련 게시물
- UHF 큐브샛 링크 버젯 크기 조정 — 엔드 투 엔드 작업 예제
관련 기사
Scripting Satellite Link Budgets with ITU-Rpy (Python Examples)
STK-free link-budget automation: sweep frequency, rain availability, and elevation in pure Python using the ITU-Rpy reference implementation of P.618/P.676/P.840. Companion to the rftools Satellite Link Budget Analyzer.
2026년 4월 30일
Satellite CommunicationsSizing a 9600-baud UHF Downlink for a 3U CubeSat: Full Walkthrough
End-to-end link budget for an amateur-band 3U cubesat: EIRP, ground-station G/T, ITU-R propagation losses, and Monte Carlo availability. Uses the Amateur CubeSat preset.
2026년 4월 29일
Satellite CommunicationsMigrating from STK Cloud: Free Alternatives for Link Budget and Orbit Analysis
Ansys is sunsetting STK Cloud in March 2026. Here are the free open-source replacements for the two things it did best — ITU-R link budgets and orbital pass prediction.
2026년 4월 29일