satnogs-auto-scheduler/schedule_single_station.py

327 lines
11 KiB
Python
Raw Normal View History

2018-08-22 14:35:35 -06:00
#!/usr/bin/env python
2018-12-02 06:53:56 -07:00
from __future__ import division
2018-08-22 14:35:35 -06:00
import requests
import ephem
from datetime import datetime, timedelta
2018-11-18 14:03:16 -07:00
from satellite_tle import fetch_tles
import os
import lxml.html
2018-11-26 09:56:30 -07:00
import argparse
2018-12-02 06:53:56 -07:00
import logging
2018-12-02 10:55:02 -07:00
from utils import get_active_transmitter_info, get_transmitter_stats, \
get_groundstation_info, get_last_update, get_scheduled_passes_from_network, ordered_scheduler, \
efficiency, find_passes, schedule_observation
import settings
2018-12-02 06:53:56 -07:00
_LOG_LEVEL_STRINGS = ['CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG']
2018-11-30 07:40:18 -07:00
2018-12-02 10:55:02 -07:00
2018-11-18 14:03:16 -07:00
class twolineelement:
"""TLE class"""
2018-08-22 14:35:35 -06:00
def __init__(self, tle0, tle1, tle2):
2018-11-18 14:03:16 -07:00
"""Define a TLE"""
2018-08-22 14:35:35 -06:00
self.tle0 = tle0
self.tle1 = tle1
self.tle2 = tle2
2018-11-30 07:40:18 -07:00
if tle0[:2] == "0 ":
2018-08-22 14:35:35 -06:00
self.name = tle0[2:]
else:
self.name = tle0
2018-11-30 07:40:18 -07:00
if tle1.split(" ")[1] == "":
2018-11-26 09:56:30 -07:00
self.id = int(tle1.split(" ")[2][:4])
else:
self.id = int(tle1.split(" ")[1][:5])
2018-11-18 14:03:16 -07:00
class satellite:
"""Satellite class"""
2018-11-19 15:45:54 -07:00
def __init__(self, tle, transmitter, success_rate, good_count, data_count):
2018-11-18 14:03:16 -07:00
"""Define a satellite"""
self.tle0 = tle.tle0
self.tle1 = tle.tle1
self.tle2 = tle.tle2
self.id = tle.id
2018-12-02 12:33:06 -07:00
self.name = tle.name.strip()
2018-11-18 14:03:16 -07:00
self.transmitter = transmitter
2018-11-19 15:45:54 -07:00
self.success_rate = success_rate
self.good_count = good_count
self.data_count = data_count
2018-08-22 14:35:35 -06:00
2018-12-02 12:33:06 -07:00
def __repr__(self):
return "%s %s %d %d %d %s" % (self.id, self.transmitter, self.success_rate,
self.good_count, self.data_count, self.name)
2018-11-30 07:40:18 -07:00
2018-12-02 06:53:56 -07:00
def _log_level_string_to_int(log_level_string):
if log_level_string not in _LOG_LEVEL_STRINGS:
message = 'invalid choice: {0} (choose from {1})'.format(log_level_string,
_LOG_LEVEL_STRINGS)
raise argparse.ArgumentTypeError(message)
log_level_int = getattr(logging, log_level_string, logging.INFO)
# check the logging log_level_choices have not changed from our expected values
assert isinstance(log_level_int, int)
return log_level_int
2018-11-15 13:08:48 -07:00
if __name__ == "__main__":
2018-11-26 09:56:30 -07:00
# Parse arguments
2018-11-30 07:40:18 -07:00
parser = argparse.ArgumentParser(
description="Automatically schedule observations on a SatNOGS station.")
2018-11-26 09:56:30 -07:00
parser.add_argument("-s", "--station", help="Ground station ID", type=int)
2018-11-30 07:40:18 -07:00
parser.add_argument(
"-t",
"--starttime",
help="Start time (YYYY-MM-DD HH:MM:SS) [default: now]",
default=datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S"))
parser.add_argument(
"-d",
"--duration",
help="Duration to schedule [hours]",
type=int,
default=1)
2018-11-26 09:56:30 -07:00
parser.add_argument("-u", "--username", help="SatNOGS username")
parser.add_argument("-p", "--password", help="SatNOGS password")
2018-11-30 07:40:18 -07:00
parser.add_argument(
"-n",
"--dryrun",
help="Dry run (do not schedule passes)",
action="store_true")
2018-12-02 06:53:56 -07:00
parser.add_argument("-l", "--log-level",
default="INFO",
dest="log_level",
type=_log_level_string_to_int,
nargs="?",
help="Set the logging output level. {0}".format(_LOG_LEVEL_STRINGS))
2018-11-26 09:56:30 -07:00
args = parser.parse_args()
2018-11-30 07:40:18 -07:00
2018-12-02 06:53:56 -07:00
# Setting logging level
numeric_level = args.log_level
if not isinstance(numeric_level, int):
2018-12-02 10:55:02 -07:00
raise ValueError("Invalid log level")
2018-12-02 06:53:56 -07:00
logging.basicConfig(level=numeric_level,
2018-12-02 10:55:02 -07:00
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s")
2018-12-02 06:53:56 -07:00
2018-11-15 13:08:48 -07:00
# Settings
2018-11-26 09:56:30 -07:00
ground_station_id = args.station
length_hours = args.duration
2018-11-18 14:03:16 -07:00
cache_dir = "/tmp/cache"
2018-11-26 09:56:30 -07:00
username = args.username
password = args.password
schedule = not args.dryrun
2018-11-18 14:03:16 -07:00
# Set time range
2018-11-26 09:56:30 -07:00
tnow = datetime.strptime(args.starttime, "%Y-%m-%dT%H:%M:%S")
2018-11-18 14:03:16 -07:00
tmin = tnow
2018-11-30 07:40:18 -07:00
tmax = tnow + timedelta(hours=length_hours)
2018-11-18 14:03:16 -07:00
2018-11-15 13:08:48 -07:00
# Get ground station information
ground_station = get_groundstation_info(ground_station_id)
2018-11-18 14:03:16 -07:00
# Create cache
if not os.path.isdir(cache_dir):
os.mkdir(cache_dir)
# Get last update
2018-11-30 07:40:18 -07:00
tlast = get_last_update(
os.path.join(
cache_dir,
"last_update_%d.txt" %
ground_station_id))
2018-11-18 14:03:16 -07:00
2018-11-19 15:45:54 -07:00
# Update logic
update = False
2018-12-03 03:02:32 -07:00
if tlast is None or (tnow - tlast).total_seconds() > settings.CACHE_AGE * 3600:
2018-11-19 15:45:54 -07:00
update = True
2018-11-30 07:40:18 -07:00
if not os.path.isfile(
os.path.join(
cache_dir,
"transmitters_%d.txt" %
ground_station_id)):
2018-11-19 15:45:54 -07:00
update = True
2018-11-30 07:40:18 -07:00
if not os.path.isfile(
os.path.join(
cache_dir,
"tles_%d.txt" %
ground_station_id)):
2018-11-19 15:45:54 -07:00
update = True
2018-11-30 07:40:18 -07:00
2018-11-19 15:45:54 -07:00
# Update
if update:
2018-12-02 06:53:56 -07:00
logging.info('Updating transmitters and TLEs for station')
2018-11-18 14:03:16 -07:00
# Store current time
2018-11-30 07:40:18 -07:00
with open(os.path.join(cache_dir, "last_update_%d.txt" % ground_station_id), "w") as fp:
fp.write(tnow.strftime("%Y-%m-%dT%H:%M:%S") + "\n")
2018-11-18 14:03:16 -07:00
# Get active transmitters in frequency range of each antenna
transmitters = {}
2018-11-18 14:03:16 -07:00
for antenna in ground_station['antenna']:
for transmitter in get_active_transmitter_info(antenna["frequency"],
antenna["frequency_max"]):
transmitters[transmitter['uuid']] = transmitter
# Get NORAD IDs
norad_cat_ids = sorted(
set([transmitter["norad_cat_id"] for transmitter in transmitters.values()]))
2018-11-18 14:03:16 -07:00
# Store transmitters
2018-11-30 07:40:18 -07:00
fp = open(
os.path.join(
cache_dir,
"transmitters_%d.txt" %
ground_station_id),
"w")
2018-12-02 06:53:56 -07:00
logging.info("Requesting transmitter success rates.")
transmitters_stats = get_transmitter_stats()
for transmitter in transmitters_stats:
if not transmitter['uuid'] in transmitters.keys():
continue
2018-11-30 07:40:18 -07:00
fp.write(
"%05d %s %d %d %d\n" %
(transmitter["norad_cat_id"],
transmitter["uuid"],
transmitter["success_rate"],
transmitter["good_count"],
transmitter["data_count"]))
2018-12-02 06:53:56 -07:00
logging.info("Transmitter success rates received!")
2018-11-18 14:03:16 -07:00
fp.close()
2018-11-30 07:40:18 -07:00
2018-11-18 14:03:16 -07:00
# Get TLEs
tles = fetch_tles(norad_cat_ids)
# Store TLEs
2018-11-30 07:40:18 -07:00
fp = open(
os.path.join(
cache_dir,
"tles_%d.txt" %
ground_station_id),
"w")
2018-11-18 14:03:16 -07:00
for norad_cat_id, (source, tle) in tles.items():
2018-11-30 07:40:18 -07:00
fp.write("%s\n%s\n%s\n" % (tle[0], tle[1], tle[2]))
2018-11-18 14:03:16 -07:00
fp.close()
2018-11-30 07:40:18 -07:00
2018-11-15 13:08:48 -07:00
# Set observer
observer = ephem.Observer()
observer.lon = str(ground_station['lng'])
observer.lat = str(ground_station['lat'])
observer.elevation = ground_station['altitude']
minimum_altitude = ground_station['min_horizon']
2018-11-18 14:03:16 -07:00
# Read tles
2018-11-30 07:40:18 -07:00
with open(os.path.join(cache_dir, "tles_%d.txt" % ground_station_id), "r") as f:
2018-11-15 13:08:48 -07:00
lines = f.readlines()
2018-11-30 07:40:18 -07:00
tles = [twolineelement(lines[i], lines[i + 1], lines[i + 2])
2018-11-18 14:03:16 -07:00
for i in range(0, len(lines), 3)]
2018-11-15 13:08:48 -07:00
2018-12-02 12:33:06 -07:00
# Read transmitters
2018-11-18 14:03:16 -07:00
satellites = []
2018-11-30 07:40:18 -07:00
with open(os.path.join(cache_dir, "transmitters_%d.txt" % ground_station_id), "r") as f:
2018-11-18 14:03:16 -07:00
lines = f.readlines()
for line in lines:
2018-11-19 15:45:54 -07:00
item = line.split()
2018-11-30 07:40:18 -07:00
norad_cat_id, uuid, success_rate, good_count, data_count = int(
item[0]), item[1], float(item[2]) / 100.0, int(item[3]), int(item[4])
2018-11-18 14:03:16 -07:00
for tle in tles:
if tle.id == norad_cat_id:
2018-11-30 07:40:18 -07:00
satellites.append(
satellite(
tle,
uuid,
success_rate,
good_count,
data_count))
2018-11-15 13:08:48 -07:00
# Find passes
passes = find_passes(satellites, observer, tmin, tmax, minimum_altitude)
2018-08-22 14:35:35 -06:00
# Priorities
2018-11-19 15:45:54 -07:00
priorities = {}
2018-11-30 07:40:18 -07:00
2018-08-22 14:35:35 -06:00
# List of scheduled passes
2018-11-30 07:40:18 -07:00
scheduledpasses = get_scheduled_passes_from_network(
ground_station_id, tmin, tmax)
2018-12-02 06:53:56 -07:00
logging.info(
2018-12-02 10:55:02 -07:00
"Found %d scheduled passes between %s and %s on ground station %d" %
2018-11-30 07:40:18 -07:00
(len(scheduledpasses), tmin, tmax, ground_station_id))
2018-08-22 14:35:35 -06:00
# Get passes of priority objects
prioritypasses = []
normalpasses = []
for satpass in passes:
# Get user defined priorities
if satpass['id'] in priorities:
satpass['priority'] = priorities[satpass['id']]
prioritypasses.append(satpass)
else:
2018-11-30 07:40:18 -07:00
satpass['priority'] = (
float(satpass['altt']) / 90.0) * satpass['success_rate']
2018-08-22 14:35:35 -06:00
normalpasses.append(satpass)
2018-11-30 07:40:18 -07:00
2018-08-22 14:35:35 -06:00
# Priority scheduler
2018-11-30 07:40:18 -07:00
prioritypasses = sorted(
prioritypasses,
key=lambda satpass: -
satpass['priority'])
2018-08-22 14:35:35 -06:00
scheduledpasses = ordered_scheduler(prioritypasses, scheduledpasses)
# Random scheduler
2018-11-30 07:40:18 -07:00
normalpasses = sorted(
normalpasses,
key=lambda satpass: -
satpass['priority'])
2018-08-22 14:35:35 -06:00
scheduledpasses = ordered_scheduler(normalpasses, scheduledpasses)
dt, dttot, eff = efficiency(scheduledpasses)
2018-12-02 06:53:56 -07:00
logging.info(
2018-11-30 07:40:18 -07:00
"%d passes scheduled out of %d, %.0f s out of %.0f s at %.3f%% efficiency" %
(len(scheduledpasses), len(passes), dt, dttot, 100 * eff))
2018-08-22 14:35:35 -06:00
# Find unique objects
satids = sorted(set([satpass['id'] for satpass in passes]))
schedule_needed = False
2018-08-22 14:35:35 -06:00
for satpass in sorted(scheduledpasses, key=lambda satpass: satpass['tr']):
2018-11-30 07:40:18 -07:00
if not satpass['scheduled']:
schedule_needed = True
2018-12-02 06:53:56 -07:00
logging.info(
2018-11-30 07:40:18 -07:00
"%05d %s %s %3.0f %4.3f %s %s" %
(int(
satpass['id']),
satpass['tr'].strftime("%Y-%m-%dT%H:%M:%S"),
satpass['ts'].strftime("%Y-%m-%dT%H:%M:%S"),
float(
satpass['altt']),
satpass['priority'],
satpass['uuid'],
satpass['name'].rstrip()))
2018-08-22 14:35:35 -06:00
# Login and schedule passes
if schedule and schedule_needed:
loginUrl = '{}/accounts/login/'.format(settings.NETWORK_BASE_URL) # login URL
session = requests.session()
login = session.get(loginUrl) # Get login page for CSFR token
login_html = lxml.html.fromstring(login.text)
login_hidden_inputs = login_html.xpath(
r'//form//input[@type="hidden"]') # Get CSFR token
form = {x.attrib["name"]: x.attrib["value"] for x in login_hidden_inputs}
form["login"] = username
form["password"] = password
session.post(loginUrl, data=form, headers={'referer': loginUrl}) # Login
for satpass in sorted(scheduledpasses, key=lambda satpass: satpass['tr']):
if not satpass['scheduled']:
schedule_observation(session,
2018-11-18 14:03:16 -07:00
int(satpass['id']),
satpass['uuid'],
ground_station_id,
2018-11-30 07:40:18 -07:00
satpass['tr'].strftime("%Y-%m-%d %H:%M:%S") + ".000",
satpass['ts'].strftime("%Y-%m-%d %H:%M:%S") + ".000")