import yaml
from datetime import datetime
import linkpredict as lp
# Load values
with open('config.yaml', 'r') as f:
config = yaml.safe_load(f)
# Spacecraft
CLUSTER = "CL1"
HPM = False
TDA_MODE = "7H"
RANGING = False
TLE1 = "1 0U 00045A 21001.00000000 .00000000 00000+0 00000+0 0 00194"
TLE2 = "2 0 136.3574 344.5433 5626026 193.4040 130.7573 0.44220344 32391"
# Groundstation
GS ='VIL1'
GS_LOCATION = config['STATION_LOCATION'][GS]
from functools import lru_cache
from skyfield.api import EarthSatellite, load, wgs84, utc
from skyfield.positionlib import ICRF
class MyGeometry(lp.Geometry):
def __init__(self, gs_lat, gs_lon, gs_alt, sat_name, sat_tle1, sat_tle2):
self.ts = load.timescale()
self.groundstation = wgs84.latlon(gs_lat, gs_lon, gs_alt)
self.satellite = EarthSatellite(sat_tle1, sat_tle2, sat_name, self.ts)
self.sat_antenna = ICRF([0, 0, 1]) # ecliptic north
self.slant = self.satellite - self.groundstation
@lru_cache
def _get_diff_vector(self, time):
timepoint = self.ts.from_datetime(time.replace(tzinfo=utc))
return self.slant.at(timepoint)
def get_slant_range(self, time):
vector = self._get_diff_vector(time)
return vector.distance().m
def get_tx_antenna_angle(self, time):
vector = self._get_diff_vector(time)
return vector.separation_from(self.sat_antenna).degrees
def get_rx_antenna_angle(self, time):
return 0
def get_elevation(self, time):
vector = self._get_diff_vector(time)
alt, az, distance = vector.altaz()
return alt.degrees
def get_azimuth(self, time):
vector = self._get_diff_vector(time)
alt, az, distance = vector.altaz()
return az.degrees
# Transmitter
transmitter = lp.Transmitter(
amplifier_power=config["CLUSTER_HPM_POWER"] if HPM else config["CLUSTER_LPM_POWER"],
devices=[lp.Device(gain=-config["CLUSTER_CIRCUIT_LOSS"])])
transmit_antenna = lp.MeasuredAntenna(
measured_gains=config['CLUSTER_ANTENNA_GAIN'][CLUSTER],
linear_polarized=False, symetric=True)
# Path
geometry = MyGeometry(*GS_LOCATION, CLUSTER, TLE1, TLE2)
# Channel
tda_mode = config["CLUSTER_TM_TDA"][TDA_MODE]
tda_mode_loss = tda_mode["modulation_loss"][1 if RANGING else 0]
modulation = lp.DigitalModulation(
symbol_rate=tda_mode["symbol_rate"], esno_ratio_threshold=0.5,
modulation_loss=config["CLUSTER_DEMOD_LOSS"] + tda_mode_loss)
channel = lp.Channel(frequency=config["CLUSTER_FREQ"][CLUSTER], modulation=modulation)
# Receiver
def station_g_t(time):
elevation = geometry.get_elevation(time)
noise5deg = config["STATION_TEMP"]["5DEG"][GS]
noise30deg = config["STATION_TEMP"]["30DEG"][GS]
if elevation < 5:
noise_db = noise5deg
elif elevation > 30:
noise_db = noise30deg
else:
noise_db = (1 / (5 * elevation)) * (
noise30deg * (6 * elevation - 30) + noise5deg * (30 - elevation))
gain = config["STATION_GAINS"][GS] - config["STATION_POINTING_LOSS"][GS]
return gain - noise_db
# Link
link = lp.Link.from_g_t_figure(
channel=channel,
geometry=geometry,
transmitter=transmitter,
transmit_antenna=transmit_antenna,
rx_g_t_figure=station_g_t,
is_rx_linear_polarized=False,
rx_antenna_pointing_loss=0)
%%time
from lib.plotting import create_cluster_link_budget_plot
start = datetime(2021, 1, 2, 3, 0)
end = datetime(2021, 1, 2, 12, 0)
create_cluster_link_budget_plot(link, start, end)
CPU times: user 2.82 s, sys: 1.31 s, total: 4.12 s Wall time: 1.62 s
result = link.calculate_link_budget(start, end)
result[0]
{<LinkBudgetKeys.time [datetime]>: datetime.datetime(2021, 1, 2, 3, 0), <LinkBudgetKeys.tx_amplifier_power [dBW]>: 3.7, <LinkBudgetKeys.tx_circuit_loss [dB]>: 2.7, <LinkBudgetKeys.tx_antenna_angle [deg]>: 52.234733136220065, <LinkBudgetKeys.tx_antenna_gain [dBi]>: 2.0, <LinkBudgetKeys.tx_antenna_pointing_loss [dB]>: 2.9556186224229366, <LinkBudgetKeys.eirp [dBW]>: 3.0, <LinkBudgetKeys.medium_loss [dB]>: 0, <LinkBudgetKeys.slant_range [m]>: 66359257.24144621, <LinkBudgetKeys.power_flux_density [dBW/m^2]>: -164.43012896672897, <LinkBudgetKeys.power_flux_density_limit [dBW/m^2]>: None, <LinkBudgetKeys.free_space_path_loss [dB]>: 195.95259545461747, <LinkBudgetKeys.total_path_loss [dB]>: 195.95259545461747, <LinkBudgetKeys.received_isotropic_signal_level [dB]>: -195.9082140770404, <LinkBudgetKeys.rx_antenna_pointing_loss [dB]>: 0, <LinkBudgetKeys.g_t_figure_of_merit [dB/K]>: 29.189999999999998, <LinkBudgetKeys.cno_ratio [dB-Hz]>: 60.51095309617725, <LinkBudgetKeys.modulation_loss [dB]>: 1.37, <LinkBudgetKeys.cno_ratio_threshold [dB-Hz]>: None, <LinkBudgetKeys.symbol_rate [sym/s]>: 596814.56048, <LinkBudgetKeys.esno_ratio [dB]>: 2.7525589954490286, <LinkBudgetKeys.esno_ratio_threshold [dB]>: 0.5, <LinkBudgetKeys.esno_ratio_margin [dB]>: 2.2525589954490286, <LinkBudgetKeys.bit_rate [bit/s]>: 596814.56048, <LinkBudgetKeys.ebno_ratio [dB]>: 2.7525589954490286, <LinkBudgetKeys.ebno_ratio_threshold [dB]>: None, <LinkBudgetKeys.coding_gain [dB]>: None}