2018-12-02 08:27:53 -07:00
|
|
|
import requests
|
|
|
|
import logging
|
2018-12-02 10:55:02 -07:00
|
|
|
import math
|
|
|
|
import random
|
|
|
|
from datetime import datetime, timedelta
|
|
|
|
import ephem
|
|
|
|
import lxml
|
|
|
|
import settings
|
2018-12-19 02:00:27 -07:00
|
|
|
from tqdm import tqdm
|
2018-12-02 08:27:53 -07:00
|
|
|
|
|
|
|
|
2018-12-02 15:00:46 -07:00
|
|
|
def get_paginated_endpoint(url, max_entries=None):
|
|
|
|
r = requests.get(url=url)
|
|
|
|
r.raise_for_status()
|
|
|
|
|
|
|
|
data = r.json()
|
|
|
|
|
|
|
|
while 'next' in r.links and (not max_entries or len(data) < max_entries):
|
|
|
|
next_page_url = r.links['next']['url']
|
|
|
|
|
|
|
|
r = requests.get(url=next_page_url)
|
|
|
|
r.raise_for_status()
|
|
|
|
|
|
|
|
data.extend(r.json())
|
|
|
|
|
|
|
|
return data
|
|
|
|
|
|
|
|
|
2018-12-02 08:27:53 -07:00
|
|
|
def get_active_transmitter_info(fmin, fmax):
|
|
|
|
# Open session
|
|
|
|
logging.info("Fetching transmitter information from DB.")
|
2018-12-02 11:09:21 -07:00
|
|
|
r = requests.get('{}/api/transmitters'.format(settings.DB_BASE_URL))
|
2018-12-02 08:27:53 -07:00
|
|
|
logging.info("Transmitters received!")
|
|
|
|
|
|
|
|
# Loop
|
|
|
|
transmitters = []
|
|
|
|
for o in r.json():
|
|
|
|
if o["downlink_low"]:
|
|
|
|
if o["alive"] and o["downlink_low"] > fmin and o["downlink_low"] <= fmax:
|
|
|
|
transmitter = {"norad_cat_id": o["norad_cat_id"],
|
|
|
|
"uuid": o["uuid"]}
|
|
|
|
transmitters.append(transmitter)
|
|
|
|
logging.info("Transmitters filtered based on ground station capability.")
|
|
|
|
return transmitters
|
2018-12-02 09:05:50 -07:00
|
|
|
|
|
|
|
|
|
|
|
def get_transmitter_stats():
|
|
|
|
logging.debug("Requesting transmitter success rates for all satellite")
|
2018-12-02 15:00:46 -07:00
|
|
|
transmitters = get_paginated_endpoint('{}/api/transmitters/'.format(settings.NETWORK_BASE_URL))
|
|
|
|
return transmitters
|
2018-12-02 10:55:02 -07:00
|
|
|
|
|
|
|
|
|
|
|
def get_scheduled_passes_from_network(ground_station, tmin, tmax):
|
|
|
|
# Get first page
|
|
|
|
client = requests.session()
|
|
|
|
|
|
|
|
# Loop
|
|
|
|
start = True
|
|
|
|
scheduledpasses = []
|
|
|
|
|
|
|
|
logging.info("Requesting scheduled passes for ground station %d" % ground_station)
|
|
|
|
while True:
|
|
|
|
if start:
|
2018-12-02 11:09:21 -07:00
|
|
|
r = client.get('{}/api/observations/?ground_station={:d}'.format(
|
2018-12-02 10:55:02 -07:00
|
|
|
settings.NETWORK_BASE_URL,
|
|
|
|
ground_station))
|
|
|
|
start = False
|
|
|
|
else:
|
|
|
|
nextpage = r.links.get("next")
|
|
|
|
r = client.get(nextpage["url"])
|
|
|
|
|
|
|
|
# r.json() is a list of dicts
|
|
|
|
for o in r.json():
|
|
|
|
satpass = {
|
|
|
|
"id": o['norad_cat_id'],
|
|
|
|
"tr": datetime.strptime(
|
|
|
|
o['start'].replace(
|
|
|
|
"Z",
|
|
|
|
""),
|
|
|
|
"%Y-%m-%dT%H:%M:%S"),
|
|
|
|
"ts": datetime.strptime(
|
|
|
|
o['end'].replace(
|
|
|
|
"Z",
|
|
|
|
""),
|
|
|
|
"%Y-%m-%dT%H:%M:%S"),
|
2018-12-19 04:39:29 -07:00
|
|
|
"scheduled": True,
|
|
|
|
"altt": o['max_altitude'],
|
|
|
|
"priority": 1,
|
|
|
|
"uuid": o['transmitter'],
|
|
|
|
"name": ''}
|
2018-12-02 10:55:02 -07:00
|
|
|
|
|
|
|
if satpass['ts'] > tmin and satpass['tr'] < tmax:
|
|
|
|
scheduledpasses.append(satpass)
|
|
|
|
if satpass['ts'] < tmin:
|
|
|
|
break
|
|
|
|
|
|
|
|
logging.info("Scheduled passes for ground station %d retrieved!" % ground_station)
|
|
|
|
return scheduledpasses
|
|
|
|
|
|
|
|
|
2018-12-29 05:34:12 -07:00
|
|
|
def overlap(satpass, scheduledpasses, wait_time_seconds):
|
|
|
|
"""Check if this pass overlaps with already scheduled passes"""
|
2018-12-02 10:55:02 -07:00
|
|
|
# No overlap
|
|
|
|
overlap = False
|
2018-12-29 05:34:12 -07:00
|
|
|
|
|
|
|
# Add wait time
|
|
|
|
tr = satpass['tr']
|
|
|
|
ts = satpass['ts'] + timedelta(seconds=wait_time_seconds)
|
|
|
|
|
2018-12-02 10:55:02 -07:00
|
|
|
# Loop over scheduled passes
|
|
|
|
for scheduledpass in scheduledpasses:
|
|
|
|
# Test pass falls within scheduled pass
|
2018-12-29 05:34:12 -07:00
|
|
|
if tr >= scheduledpass['tr'] and ts < scheduledpass['ts']:
|
2018-12-02 10:55:02 -07:00
|
|
|
overlap = True
|
|
|
|
# Scheduled pass falls within test pass
|
2018-12-29 05:34:12 -07:00
|
|
|
elif scheduledpass['tr'] >= tr and scheduledpass['ts'] < ts:
|
2018-12-02 10:55:02 -07:00
|
|
|
overlap = True
|
|
|
|
# Pass start falls within pass
|
2018-12-29 05:34:12 -07:00
|
|
|
elif tr >= scheduledpass['tr'] and tr < scheduledpass['ts']:
|
2018-12-02 10:55:02 -07:00
|
|
|
overlap = True
|
|
|
|
# Pass end falls within end
|
2018-12-29 05:34:12 -07:00
|
|
|
elif ts >= scheduledpass['tr'] and ts < scheduledpass['ts']:
|
2018-12-02 10:55:02 -07:00
|
|
|
overlap = True
|
|
|
|
if overlap:
|
|
|
|
break
|
|
|
|
|
|
|
|
return overlap
|
|
|
|
|
|
|
|
|
2018-12-29 05:34:12 -07:00
|
|
|
def ordered_scheduler(passes, scheduledpasses, wait_time_seconds):
|
2018-12-02 10:55:02 -07:00
|
|
|
"""Loop through a list of ordered passes and schedule each next one that fits"""
|
|
|
|
# Loop over passes
|
|
|
|
for satpass in passes:
|
|
|
|
# Schedule if there is no overlap with already scheduled passes
|
2018-12-29 05:34:12 -07:00
|
|
|
if not overlap(satpass, scheduledpasses, wait_time_seconds):
|
2018-12-02 10:55:02 -07:00
|
|
|
scheduledpasses.append(satpass)
|
|
|
|
|
|
|
|
return scheduledpasses
|
|
|
|
|
|
|
|
|
2018-12-29 05:34:12 -07:00
|
|
|
def random_scheduler(passes, scheduledpasses, wait_time_seconds):
|
2018-12-02 10:55:02 -07:00
|
|
|
"""Schedule passes based on random ordering"""
|
|
|
|
# Shuffle passes
|
|
|
|
random.shuffle(passes)
|
|
|
|
|
2018-12-29 05:34:12 -07:00
|
|
|
return ordered_scheduler(passes, scheduledpasses, wait_time_seconds)
|
2018-12-02 10:55:02 -07:00
|
|
|
|
|
|
|
|
|
|
|
def efficiency(passes):
|
|
|
|
|
|
|
|
# Loop over passes
|
|
|
|
start = False
|
|
|
|
for satpass in passes:
|
|
|
|
if not start:
|
|
|
|
dt = satpass['ts'] - satpass['tr']
|
|
|
|
tmin = satpass['tr']
|
|
|
|
tmax = satpass['ts']
|
|
|
|
start = True
|
|
|
|
else:
|
|
|
|
dt += satpass['ts'] - satpass['tr']
|
|
|
|
if satpass['tr'] < tmin:
|
|
|
|
tmin = satpass['tr']
|
|
|
|
if satpass['ts'] > tmax:
|
|
|
|
tmax = satpass['ts']
|
|
|
|
# Total time covered
|
|
|
|
dttot = tmax - tmin
|
|
|
|
|
|
|
|
return dt.total_seconds(), dttot.total_seconds(
|
|
|
|
), dt.total_seconds() / dttot.total_seconds()
|
|
|
|
|
|
|
|
|
|
|
|
def find_passes(satellites, observer, tmin, tmax, minimum_altitude):
|
|
|
|
# Loop over satellites
|
|
|
|
passes = []
|
|
|
|
passid = 0
|
2018-12-19 02:00:27 -07:00
|
|
|
logging.info('Finding all passes for %s satellites:' % len(satellites))
|
|
|
|
for satellite in tqdm(satellites):
|
2018-12-02 10:55:02 -07:00
|
|
|
# Set start time
|
|
|
|
observer.date = ephem.date(tmin)
|
|
|
|
|
|
|
|
# Load TLE
|
|
|
|
try:
|
|
|
|
sat_ephem = ephem.readtle(str(satellite.tle0),
|
|
|
|
str(satellite.tle1),
|
|
|
|
str(satellite.tle2))
|
|
|
|
except (ValueError, AttributeError):
|
|
|
|
continue
|
|
|
|
|
|
|
|
# Loop over passes
|
|
|
|
keep_digging = True
|
|
|
|
while keep_digging:
|
|
|
|
try:
|
|
|
|
tr, azr, tt, altt, ts, azs = observer.next_pass(sat_ephem)
|
|
|
|
except ValueError:
|
|
|
|
break # there will be sats in our list that fall below horizon, skip
|
|
|
|
except TypeError:
|
|
|
|
break # if there happens to be a non-EarthSatellite object in the list
|
|
|
|
except Exception:
|
|
|
|
break
|
|
|
|
|
|
|
|
if tr is None:
|
|
|
|
break
|
|
|
|
|
|
|
|
# using the angles module convert the sexagesimal degree into
|
|
|
|
# something more easily read by a human
|
|
|
|
try:
|
|
|
|
elevation = format(math.degrees(altt), '.0f')
|
|
|
|
azimuth_r = format(math.degrees(azr), '.0f')
|
|
|
|
azimuth_s = format(math.degrees(azs), '.0f')
|
|
|
|
except TypeError:
|
|
|
|
break
|
|
|
|
passid += 1
|
|
|
|
|
|
|
|
# show only if >= configured horizon and in next 6 hours,
|
|
|
|
# and not directly overhead (tr < ts see issue 199)
|
|
|
|
if tr < ephem.date(tmax):
|
|
|
|
if (float(elevation) >= minimum_altitude and tr < ts):
|
|
|
|
valid = True
|
|
|
|
if tr < ephem.Date(datetime.now() +
|
|
|
|
timedelta(minutes=5)):
|
|
|
|
valid = False
|
|
|
|
satpass = {'passid': passid,
|
|
|
|
'mytime': str(observer.date),
|
|
|
|
'name': str(satellite.name),
|
|
|
|
'id': str(satellite.id),
|
|
|
|
'tle1': str(satellite.tle1),
|
|
|
|
'tle2': str(satellite.tle2),
|
|
|
|
'tr': tr.datetime(), # Rise time
|
|
|
|
'azr': azimuth_r, # Rise Azimuth
|
|
|
|
'tt': tt.datetime(), # Max altitude time
|
|
|
|
'altt': elevation, # Max altitude
|
|
|
|
'ts': ts.datetime(), # Set time
|
|
|
|
'azs': azimuth_s, # Set azimuth
|
|
|
|
'valid': valid,
|
|
|
|
'uuid': satellite.transmitter,
|
|
|
|
'success_rate': satellite.success_rate,
|
|
|
|
'good_count': satellite.good_count,
|
|
|
|
'data_count': satellite.data_count,
|
|
|
|
'scheduled': False}
|
|
|
|
passes.append(satpass)
|
|
|
|
observer.date = ephem.Date(
|
|
|
|
ts).datetime() + timedelta(minutes=1)
|
|
|
|
else:
|
|
|
|
keep_digging = False
|
|
|
|
|
|
|
|
return passes
|
|
|
|
|
|
|
|
|
|
|
|
def get_groundstation_info(ground_station_id):
|
|
|
|
|
|
|
|
logging.info("Requesting information for ground station %d" % ground_station_id)
|
|
|
|
client = requests.session()
|
|
|
|
|
|
|
|
# Loop
|
|
|
|
found = False
|
2018-12-02 11:09:21 -07:00
|
|
|
r = client.get("{}/api/stations/?id={:d}".format(
|
2018-12-02 10:55:02 -07:00
|
|
|
settings.NETWORK_BASE_URL,
|
|
|
|
ground_station_id))
|
|
|
|
for o in r.json():
|
|
|
|
if o['id'] == ground_station_id:
|
2019-01-04 03:43:26 -07:00
|
|
|
if o['status'] == 'Online' or o['status'] == 'Testing':
|
|
|
|
found = True
|
|
|
|
else:
|
|
|
|
found = False
|
2018-12-02 10:55:02 -07:00
|
|
|
break
|
2019-01-04 03:43:26 -07:00
|
|
|
|
2018-12-02 10:55:02 -07:00
|
|
|
if found:
|
2018-12-16 10:28:45 -07:00
|
|
|
logging.info('Ground station information retrieved!')
|
2018-12-02 10:55:02 -07:00
|
|
|
return o
|
|
|
|
else:
|
|
|
|
logging.info('No ground station information found!')
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
|
|
def get_last_update(fname):
|
|
|
|
try:
|
|
|
|
fp = open(fname, "r")
|
|
|
|
line = fp.readline()
|
|
|
|
fp.close()
|
|
|
|
return datetime.strptime(line.strip(), "%Y-%m-%dT%H:%M:%S")
|
|
|
|
except IOError:
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
def schedule_observation(
|
|
|
|
session,
|
|
|
|
norad_cat_id,
|
|
|
|
uuid,
|
|
|
|
ground_station_id,
|
|
|
|
starttime,
|
|
|
|
endtime):
|
|
|
|
|
|
|
|
obsURL = '{}/observations/new/'.format(settings.NETWORK_BASE_URL) # Observation URL
|
|
|
|
# Get the observation/new/ page to get the CSFR token
|
|
|
|
obs = session.get(obsURL)
|
|
|
|
obs_html = lxml.html.fromstring(obs.text)
|
|
|
|
hidden_inputs = obs_html.xpath(r'//form//input[@type="hidden"]')
|
|
|
|
form = {x.attrib["name"]: x.attrib["value"] for x in hidden_inputs}
|
|
|
|
form["satellite"] = norad_cat_id
|
|
|
|
form["transmitter"] = uuid
|
|
|
|
form["start-time"] = starttime
|
|
|
|
form["end-time"] = endtime
|
|
|
|
form["0-starting_time"] = starttime
|
|
|
|
form["0-ending_time"] = endtime
|
|
|
|
form["0-station"] = ground_station_id
|
|
|
|
form["total"] = str(1)
|
|
|
|
session.post(obsURL, data=form, headers={'referer': obsURL})
|
2018-12-12 02:13:02 -07:00
|
|
|
logging.debug("Scheduled!")
|