Commit 0bfa9a02 authored by Mohd Bilal's avatar Mohd Bilal
Browse files

Merge branch 'master_f_gnuradio'

parents 05aafac2 c5e95933
from utils.helpers import setup_required
from extensions.mqtt import AWSMQTT as mqtt
from app.models.users import User
from app.gnuradio.flowgraphs.satellite_decoder_test import satellite_decoder_test
import config
import logging
import datetime
......@@ -57,9 +58,12 @@ class TIMGSNClient:
start = overpass['start_date_str'] + ' ' + overpass[
'start_time_str']
end = overpass['end_date_str'] + ' ' + overpass['end_time_str']
uid = overpass['uid']
schedule.append({
"uid": uid,
"contact_type": contact_type,
"norad": norad,
"satellite_frequency": overpass['satellite_frequency'],
"start": start,
"end": end,
"updated_on": updated_on,
......@@ -112,16 +116,23 @@ class TIMGSNClient:
user.load_data(user_data)
self.users.append(user)
def _setup_gnuradio(self):
self.g1 = satellite_decoder_test()
def _set_up(self):
logger.info("setting up TIMGSN client...")
self._setup_user()
self._setup_mqtt()
self._setup_gnuradio()
self._is_setup = True
def _tear_down(self):
for user in self.users:
user.terminate()
self.mqtt.teardown()
self.g1.stop()
self.g1.wait()
def stop(self):
self._tear_down()
......@@ -130,6 +141,7 @@ class TIMGSNClient:
def operate(self):
for user in self.users:
user.operate()
self.g1.start()
def start(self):
self._set_up()
......
'''
This module implements hardware specific implementations for the ground station infrastructure,
e.g, controlling the rotor, settings antenna gain etc.
Each station can have its implementations of the functions in this module.
The application then calls the right function at runtime if the file is available.
'''
import logging
import traceback
logger = logging.getLogger(__name__)
def notify_start_tracking(norad_id, gsn_name, **kwargs):
"""
This function implements a way to notify the hardware at the deployed location to start tracking.
Each station must have its own implementation of this function which is then called at runtime.
Place your function implementation under app.client.hardware in a file named: "<station_name>.py".
"<station_name>.py" should implement a function with the name `post_frame(norad_id, gsn_name, frame, **kwargs)`.
"""
try:
exec(
f'from app.client.hardware.{gsn_name} import notify_start_tracking as notify'
)
notify(norad_id, gsn_name, **kwargs)
except (ImportError, FileNotFoundError, Exception) as err:
logger.debug("Did not find file or function "
"implementing tracking notification sender. "
f"Error: {err}")
logger.debug(traceback.format_exc())
def post_frame(norad_id, gsn_name, frame, **kwargs):
"""
This function implements a way to post a frame to secondary addresses other than TIMGSN.
Each station must have its own implementation of this function which is then called at runtime.
Place your function implementation under app.client.hardware in a file named: "<station_name>.py".
"<station_name>.py" should implement a function with the name `post_frame(norad_id, gsn_name, frame, **kwargs)`.
See `jmuw_uhf.py` for example.
"""
try:
exec(f'from app.client.hardware.{gsn_name} import post_packet as post')
post(norad_id, gsn_name, frame, **kwargs)
except (ImportError, FileNotFoundError, Exception) as err:
logger.debug("Did not find file or function "
"implementing tracking notification sender. "
f"Error: {err}")
logger.debug(traceback.format_exc())
import requests
def notify_start_tracking(norad_id, gsn_name, **kwargs):
pass
def post_packet(norad_id, gsn_name, frame, **kwargs):
url_base = 'http://robotik.informatik.uni-wuerzburg.de/uwe/report_frame.php'
payload = {'noradID':norad_id,
'source': gsn_name,
'timestamp' : kwargs['timestamp'],
'frame' : frame,
'locator' : kwargs['locator'],
'longitude' : kwargs['longitude'],
'latitude' : kwargs['latitude']}
f = requests.get(url_base, params=payload)
\ No newline at end of file
from utils.threading import ThreadedModule
from utils.helpers import setup_required
from app.client import create_response
import config
import requests
import logging
import datetime
import itertools
import traceback
import time
logger = logging.getLogger(__name__)
class RequestSender(ThreadedModule):
"""
A class used to request overpasses
Attributes
----------
planning_horizon : int
Time in seconds in future for which requests are made
user: <app.client.User>
User data
"""
def __init__(self, name, *args, **kwargs):
"""
Parameters
----------
name: str
name of the thread
"""
super().__init__(name, *args, **kwargs)
self.planning_horizon = kwargs[
'planning_horizon'] if 'planning_horizon' in kwargs else 86400
self.user = None
self.url = config.endpoint_requests
self._is_setup = False
def _set_up(self):
if not self.user:
raise AttributeError("No User has been added.")
self._is_setup = True
def _tear_down(self):
logger.info(f"Shutting down thread: {self.name}")
def add_user(self, user):
self.user = user
def _loop(self):
responses = []
try:
responses = self.query_overpasses()
except:
logger.info("Failed to make requests.")
@setup_required
def query_overpasses(self):
for satellite, station in itertools.product(self.user.get_satellites(),
self.user.get_stations()):
start_time = datetime.datetime.utcnow()
end_time = start_time + datetime.timedelta(
seconds=self.planning_horizon)
data = {
'norad': satellite.norad,
'station_name': station.gsn_name,
'start_time': start_time.strftime(config.time_format),
'end_time': end_time.strftime(config.time_format)
}
response = requests.get(url=config.endpoint_overpasses,
auth=(self.user.username,
self.user.password),
data=data)
satellite_plan = [
a['contact_type']
for a in satellite.get_plan(start_time, end_time)
]
responses = self._handle_overpass_api_response(
response, satellite_plan)
# add some delay after each combination is done. This allows to
# send a proper response back to GUI
time.sleep(1)
return responses
@setup_required
def _handle_overpass_api_response(self, response, satellite_plan):
data = None
responses = []
if response.status_code == 200:
logger.info("Successfully queried overpasses.")
overpasses = response.json()['overpasses']
for overpass, request_type in itertools.zip_longest(
overpasses, satellite_plan, fillvalue="TELEMETRY"):
data = self.build_request(overpass['uid'], request_type)
res = self.send_request(data)
responses.append(res)
time.sleep(0.5)
return responses
@setup_required
def build_request(self, overpass_uid, request_type):
project_name = self.user.current_project
data = {
'project_name': project_name,
'overpass_uid': overpass_uid,
'request_type': request_type
}
return data
@setup_required
def send_request(self, data):
response = requests.post(url=self.url,
auth=(self.user.username, self.user.password),
data=data)
return self._handle_response(response)
@setup_required
def _handle_response(self, response, *args, **kwargs):
result = None
try:
result = response.json()
except:
result = response.text
if response.status_code == 200:
logger.info("Request successful.")
tracking_requests = result.get("requests")
return create_response(status=True,
data=tracking_requests,
info="Successfully created response.")
elif response.status_code == 404 or response.status_code == 403:
logger.info("Request unsuccessful.")
return create_response(status=False, data=[], info=result)
else:
logger.info("Request unsuccessful.")
return create_response(status=False, data=[], info=result)
class RequestHandler(ThreadedModule):
"""
A class to approve, reject, mark tracked, mark failed requests
"""
def __init__(self, name, *args, **kwargs):
"""
Parameters
----------
name: str
name of the thread
"""
super().__init__(name, *args, **kwargs)
self.planning_horizon = kwargs[
'planning_horizon'] if 'planning_horizon' in kwargs else 86400
self.user = None
self.url = config.endpoint_requests
self._is_setup = False
def _set_up(self):
if not self.user:
raise AttributeError("No User has been added.")
self._is_setup = True
def _tear_down(self):
pass
def add_user(self, user):
self.user = user
def create_request_url(self, request_uid):
return f"{self.url}/{request_uid}"
def _loop(self):
received_requests = self.fetch_received_requests()
responses = []
for tracking_request in received_requests:
response = self.handle_request(tracking_request)
responses.append(response)
logger.info("responded to requests.")
def handle_request(self, tracking_request: dict):
request_uid = tracking_request['uid']
overpass = tracking_request['overpass']
status = overpass['status'].upper()
start = datetime.datetime.strptime(
overpass['start_date_str'] + ' ' + overpass['start_time_str'],
config.time_format)
end = datetime.datetime.strptime(
overpass['end_date_str'] + ' ' + overpass['end_time_str'],
config.time_format)
station_name = overpass['station_name']
station = self.user.get_station(station_name)
busy = station.is_busy(start, end)
# response to the request
response = None
if status == "PENDING" and not busy:
response = self.accept(request_uid)
elif status == "ACCEPTED":
# do nothing
pass
elif status == "REJECTED" and not busy:
response = self.accept(request_uid)
elif status == "SCHEDULED":
# do nothing
pass
elif status == "FAILED":
# do nothing
pass
elif status == "DATA_UPLOADED":
# do nothing
pass
else:
# reject
response = self.reject(request_uid)
return response
@setup_required
def fetch_received_requests(self):
data = {"direction": "received"}
tracking_requests = [] # tracking requests
response = requests.get(url=self.url,
auth=(self.user.username, self.user.password),
data=data)
if response.status_code == 200:
tracking_requests = response['requests']
return tracking_requests
@setup_required
def accept(self, request_uid):
action = {'action': 'accept'}
url = self.create_request_url(request_uid)
response = requests.put(url=url,
auth=(self.user.username, self.user.password),
data=action)
return response
@setup_required
def reject(self, request_uid):
action = {'action': 'reject'}
url = self.create_request_url(request_uid)
response = requests.put(url=url,
auth=(self.user.username, self.user.password),
data=action)
return response
@setup_required
def mark_tracked(self, request_uid):
action = {'action': 'tracked'}
url = self.create_request_url(request_uid)
response = requests.put(url=url,
auth=(self.user.username, self.user.password),
data=action)
return response
@setup_required
def mark_failed(self, request_uid):
action = {'action': 'failed'}
url = self.create_request_url(request_uid)
response = requests.put(url=url,
auth=(self.user.username, self.user.password),
data=action)
return response
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright 2019 gr-fsk author.
#
# This is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this software; see the file COPYING. If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
#
import numpy
import pmt
import threading
import time
from gnuradio import gr
from skyfield.api import Topos, load
from pytz import timezone
import datetime
#Calculate doppler shift for a given speed and velocity with moving transmitter, stationary receiver
def dopplerShift(vel, f0):
c = 299792458
return f0 * ((c / (c+vel)) - 1)
class doppler_correct(gr.sync_block):
"""
Is configured with Satellite and groundstation location. Puts out doppler shift between gs and sat at system time once per second
"""
def __init__(self, sat, gs_lat, gs_long, f0, tz):
gr.sync_block.__init__(self,
name="doppler_correct_f",
in_sig=None,
out_sig=None)
self.message_port_register_out(pmt.intern('Freq_Offset'))
self.f0 = f0
self.gs = Topos(latitude_degrees = gs_lat, longitude_degrees = gs_long)
stations_url = 'http://celestrak.com/NORAD/elements/active.txt'
satellites = load.tle_file(stations_url)
by_number = {sat.model.satnum: sat for sat in satellites}
self.sat = by_number[sat]
self.dif = self.sat - self.gs
self.ts = load.timescale()
self.tz = timezone(tz)
self.verbose = False
#Check if TLE Date is less than 14 days old if not reload
t = self.ts.utc(self.tz.localize(datetime.datetime.now()))
days = t - self.sat.epoch
if abs(days) > 2:
satellites = load.tle(stations_url, reload=True)
self.sat = satellites['UWE-4']
self.time_now = datetime.datetime.utcnow()
# Start Thread for putting out Frequencies and doppler
thread = threading.Thread(target=self.loop, args=())
thread.daemon = True
thread.start()
def loop(self):
while True:
time.sleep(0.5)
t = self.ts.utc(self.tz.localize(datetime.datetime.now()))
#Calculate relative topocentric position for timespan t
topoc = self.dif.at(t)
d = topoc.position.km
#Calculate Speed relative to groundstation
d_norm = d / numpy.linalg.norm(d,axis = 0)
speed_v = numpy.multiply(d_norm, topoc.velocity.km_per_s)
speed = numpy.sum(speed_v, axis = 0)
#Compute doppler shift
dS = dopplerShift(speed * 1000, self.f0)
self.message_port_pub(pmt.intern('Freq_Offset'), pmt.from_float(-dS))
\ No newline at end of file
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: GPL-3.0
#
# GNU Radio Python Flow Graph
# Title: dd
# Author: tim
# GNU Radio version: 3.8.1.0
from gnuradio import analog
from gnuradio import blocks
from gnuradio import filter
from gnuradio.filter import firdes
from gnuradio import gr
import sys
import signal
from argparse import ArgumentParser
from gnuradio.eng_arg import eng_float, intx
from gnuradio import eng_notation
#from gnuradio import zeromq
import osmosdr
import timgsn
import time
import satellites.core
class GSN_RX(gr.top_block):
def __init__(self, file = "", **kwarg):
gr.top_block.__init__(self, "dd")
##################################################
# Variables
##################################################
self.username_env = username = "JMUW"
self.station_long = station_long = 9.974
self.station_lat = station_lat = 49.7813
self.sat_freq = sat_freq = 435.6e6
self.samp_rate = samp_rate = 200000
self.rf_gain = rf_gain = 10
self.password_env = password = "test1234"
self.overpass_uid = overpass_uid = "jmuw_uhf-39446--2021-04-22--12"
self.norad_id = norad_id = 46505#39446
self.if_gain = if_gain = 20
self.gsn_name = gsn_name = "TIMGSNTEST"
self.device_arg = device_arg = "hackrf=0"
self.bb_gain = bb_gain = 20
self.timezone = "Europe/Berlin"
self.gsn_url = "http://132.187.9.168:5000"
##################################################
# Blocks
##################################################
timgsn.loadParameters(self, file , kwarg)
#elf.zeromq_sub_source_1 = zeromq.sub_source(gr.sizeof_gr_complex, 1, 'tcp://127.0.0.1:50004', 100, False, -1)
self.satellites_satellite_decoder_0 = satellites.core.gr_satellites_flowgraph(norad = 39446, samp_rate = self.samp_rate/2, grc_block = True, iq = True, options = "")
self.osmosdr_source_0 = osmosdr.source(args="numchan=" + str(1) + " " + self.device_arg)
self.osmosdr_source_0.set_sample_rate(self.samp_rate)
self.osmosdr_source_0.set_center_freq(self.sat_freq - 50e3, 0)
self.osmosdr_source_0.set_freq_corr(0, 0)
self.osmosdr_source_0.set_gain(self.rf_gain, 0)
self.osmosdr_source_0.set_if_gain(self.if_gain, 0)
self.osmosdr_source_0.set_bb_gain(self.bb_gain, 0)
self.osmosdr_source_0.set_antenna('', 0)
self.osmosdr_source_0.set_bandwidth(0, 0)
self.osmosdr_source_0.set_dc_offset_mode(0, 0)
self.osmosdr_source_0.set_iq_balance_mode(0, 0)
self.osmosdr_source_0.set_gain_mode(False, 0)
self.low_pass_filter_0 = filter.fir_filter_ccf(
2,
firdes.low_pass(
1,
200e3,
10e3,
1e3,
firdes.WIN_HAMMING,
6.76))
#self.timgsn_doppler_correct_0 = timgsn.doppler_correct(self.norad_id, self.station_lat, self.station_long, self.sat_freq, self.timezone)
self.timgsn_TIM_packet_submit_0 = timgsn.TIM_packet_submit(self.station_long, self.station_lat, self.norad_id, self.gsn_name, self.username_env, self.password_env, self.overpass_uid, self.gsn_url)
self.blocks_multiply_xx_0_0_0 = blocks.multiply_vcc(1)
self.blocks_multiply_xx_0_0 = blocks.multiply_vcc(1)
self.analog_sig_source_x_0_0_0 = analog.sig_source_c(self.samp_rate, analog.GR_COS_WAVE, -50e3, 1, 0, 0)
self.analog_sig_source_x_0_0 = analog.sig_source_c(self.samp_rate, analog.GR_COS_WAVE, 0, 1, 0, 0)
self.analog_agc3_xx_0 = analog.agc3_cc(1e-3, 1e-4, 1.0, 1.0, 1)
self.analog_agc3_xx_0.set_max_gain(65536)
##################################################
# Connections
##################################################
#self.msg_connect((self.timgsn_doppler_correct_0, 'Freq_Offset'), (self.analog_sig_source_x_0_0, 'freq'))
self.msg_connect((self.satellites_satellite_decoder_0, 'out'), (self.timgsn_TIM_packet_submit_0, 'pckt'))
self.connect((self.analog_agc3_xx_0, 0), (self.satellites_satellite_decoder_0, 0))
self.connect((self.analog_sig_source_x_0_0, 0), (self.blocks_multiply_xx_0_0, 0))
self.connect((self.analog_sig_source_x_0_0_0, 0), (self.blocks_multiply_xx_0_0_0, 0))
self.connect((self.blocks_multiply_xx_0_0, 0), (self.blocks_multiply_xx_0_0_0, 1))
self.connect((self.blocks_multiply_xx_0_0_0, 0), (self.low_pass_filter_0, 0))
#self.connect((self.zeromq_sub_source_1, 0), (self.blocks_multiply_xx_0_0, 1))
self.connect((self.low_pass_filter_0, 0), (self.analog_agc3_xx_0, 0))
self.connect((self.osmosdr_source_0, 0), (self.blocks_multiply_xx_0_0, 1))
def get_username(self):
return self.username
def set_username(self, username):
self.username = username