Source code for satnogsclient.observer.worker

from __future__ import absolute_import, division, print_function

import logging
import math
import threading
import time
from datetime import datetime, timedelta

import ephem
import Hamlib
import pytz

from satnogsclient import settings
from satnogsclient.observer.orbital import pinpoint
from satnogsclient.rig import Rig
from satnogsclient.rotator import Rotator

LOGGER = logging.getLogger(__name__)


[docs]class Worker(object): """Class to facilitate as a worker for rotctl/rigctl.""" # sleep time of loop (in seconds) _sleep_time = 0.1 # loop flag _stay_alive = False # end when this timestamp is reached _observation_end = None # frequency of original signal _frequency = None _azimuth = None _altitude = None observer_dict = {} satellite_dict = {} def __init__(self, ip, port, time_to_stop=None, frequency=None, sleep_time=None): """Initialize worker class.""" self._ip = ip self._port = port if frequency: self._frequency = frequency if time_to_stop: self._observation_end = time_to_stop if sleep_time: self._sleep_time = sleep_time self.track = None @property def is_alive(self): """Returns if tracking loop is alive or not.""" return self._stay_alive @is_alive.setter def is_alive(self, value): """Sets value if tracking loop is alive or not.""" self._stay_alive = value
[docs] def trackobject(self, observer_dict, satellite_dict): """ Sets tracking object. Can also be called while tracking to manipulate observation. """ self.observer_dict = observer_dict self.satellite_dict = satellite_dict
[docs] def trackstart(self): """ Starts the thread that communicates tracking info to remote socket. Stops by calling trackstop() """ self.is_alive = True LOGGER.info('Tracking initiated') if not all([self.observer_dict, self.satellite_dict]): raise ValueError('Satellite or observer dictionary not defined.') self.track = threading.Thread(target=self._communicate_tracking_info) self.track.daemon = True self.track.start() return self.is_alive
[docs] def send_to_socket(self, pin, sock): # Needs to be implemented in freq/track workers implicitly raise NotImplementedError
[docs] def _communicate_tracking_info(self): raise NotImplementedError
[docs] def trackstop(self): """ Sets object flag to false and stops the tracking thread. """ self.is_alive = False self.track.join() LOGGER.info('Tracking stopped.')
[docs]class WorkerTrack(Worker): _midpoint = None _flip = False
[docs] def _communicate_tracking_info(self): """ Runs as a daemon thread, communicating tracking info to remote socket. Uses observer and satellite objects set by trackobject(). Will exit when observation_end timestamp is reached. """ rotator = Rotator(settings.SATNOGS_ROT_MODEL, settings.SATNOGS_ROT_BAUD, settings.SATNOGS_ROT_PORT) rotator.open() # track satellite while self.is_alive: pin = pinpoint(self.observer_dict, self.satellite_dict) if pin['ok']: self.send_to_socket(pin, rotator) time.sleep(self._sleep_time) rotator.close()
[docs] @staticmethod def find_midpoint(observer_dict, satellite_dict, start): # Workaround for https://github.com/brandon-rhodes/pyephem/issues/105 # pylint: disable=assigning-non-slot # Disable until pylint 2.4 is released, see # https://github.com/PyCQA/pylint/issues/2807 start -= timedelta(minutes=1) observer = ephem.Observer() observer.lon = str(observer_dict['lon']) observer.lat = str(observer_dict['lat']) observer.elevation = observer_dict['elev'] observer.date = ephem.Date(start) satellite = ephem.readtle(str(satellite_dict['tle0']), str(satellite_dict['tle1']), str(satellite_dict['tle2'])) timestamp_max = pytz.utc.localize(ephem.Date(observer.next_pass(satellite)[2]).datetime()) pin = pinpoint(observer_dict, satellite_dict, timestamp_max) azi_max = pin['az'].conjugate() * 180 / math.pi alt_max = pin['alt'].conjugate() * 180 / math.pi return (azi_max, alt_max, timestamp_max)
[docs] @staticmethod def normalize_angle(num, lower=0, upper=360): res = num if num > upper or num == lower: num = lower + abs(num + upper) % (abs(lower) + abs(upper)) if num < lower or num == upper: num = upper - abs(num - lower) % (abs(lower) + abs(upper)) res = lower if num == upper else num return res
[docs] @staticmethod def flip_coordinates(azi, alt, timestamp, midpoint): midpoint_azi, midpoint_alt, midpoint_timestamp = midpoint if timestamp >= midpoint_timestamp: azi = midpoint_azi + (midpoint_azi - azi) alt = midpoint_alt + (midpoint_alt - alt) return (WorkerTrack.normalize_angle(azi), WorkerTrack.normalize_angle(alt)) return (azi, alt)
[docs] def trackobject(self, observer_dict, satellite_dict): super(WorkerTrack, self).trackobject(observer_dict, satellite_dict) if settings.SATNOGS_ROT_FLIP and settings.SATNOGS_ROT_FLIP_ANGLE: self._midpoint = WorkerTrack.find_midpoint(observer_dict, satellite_dict, datetime.now(pytz.utc)) LOGGER.info('Antenna midpoint: AZ%.2f EL%.2f %s', *self._midpoint) self._flip = (self._midpoint[1] >= settings.SATNOGS_ROT_FLIP_ANGLE) LOGGER.info('Antenna flip: %s', self._flip)
[docs] def send_to_socket(self, pin, sock): # Read az/alt of sat and convert to degrees azi = pin['az'].conjugate() * 180 / math.pi alt = pin['alt'].conjugate() * 180 / math.pi if self._flip: azi, alt = WorkerTrack.flip_coordinates(azi, alt, datetime.now(pytz.utc), self._midpoint) self._azimuth = azi self._altitude = alt # read current position of rotator in degrees (cur_azi, cur_alt) = sock.position # if the need to move exceeds threshold, then do it # Take the 360 modulus of the azimuth position, to handle rotators that report # positions in overwind regions as values outside the range 0-360. if (abs(azi - cur_azi % 360.0) > settings.SATNOGS_ROT_THRESHOLD or abs(alt - cur_alt) > settings.SATNOGS_ROT_THRESHOLD): msg = (azi, alt) LOGGER.debug('Rotctld msg: %s', msg) sock.position = msg
[docs]class WorkerFreq(Worker):
[docs] def _communicate_tracking_info(self): """ Runs as a daemon thread, communicating tracking info to remote socket. Uses observer and satellite objects set by trackobject(). Will exit when observation_end timestamp is reached. """ rig = Rig(Hamlib.RIG_MODEL_NETRIGCTL, '{}:{}'.format(self._ip, self._port)) rig.open() # track satellite while self.is_alive: pin = pinpoint(self.observer_dict, self.satellite_dict) if pin['ok']: self.send_to_socket(pin, rig) time.sleep(self._sleep_time) rig.close()
[docs] def send_to_socket(self, pin, sock): doppler_calc_freq = self._frequency * (1 - (pin['rng_vlct'] / ephem.c)) msg = int(doppler_calc_freq) LOGGER.debug('Initial frequency: %s', self._frequency) LOGGER.debug('Rigctld msg: %s', msg) sock.frequency = msg