mirror of https://github.com/mfkiwl/krakensdr_pr
parent
38fbf69d7c
commit
efaaf39f79
31 changed files with 8704 additions and 2 deletions
@ -1,2 +1,94 @@ |
||||
# krakensdr_pr |
||||
Passive Radar Code for the KrakenSDR |
||||
# Kraken SDR DoA DSP |
||||
This software is intended to demonstrate the direction of arrival (DoA) estimation capabilities of the KrakenSDR and other RTL-SDR based coherent receiver systems which use the compatible data acquisition system - HeIMDALL DAQ Firmware. |
||||
<br> |
||||
<br> |
||||
The complete application is broken down into two main modules in terms of implementation, into the DAQ Subsystem and to the DSP Subsystem. These two modules can operate together either remotely through Ethernet connection or locally on the same host using shared-memory. |
||||
|
||||
Running these two subsystems on separate processing units can grant higher throughput and stability, while running on the same processing unit makes the entire system more compact. |
||||
|
||||
## Installation |
||||
|
||||
1. Install the prerequisites |
||||
|
||||
``` bash |
||||
sudo apt update |
||||
sudo apt install php-cli |
||||
``` |
||||
|
||||
2. Install Heimdall DAQ |
||||
|
||||
If not done already, first, follow the instructions at https://github.com/krakenrf/heimdall_daq_fw/tree/development to install the Heimdall DAQ Firmware. |
||||
|
||||
3. Set up Miniconda environment |
||||
|
||||
You will have created a Miniconda environment during the Heimdall DAQ install phase. |
||||
|
||||
Please run the installs in this order as we need to ensure a specific version of dash is installed. |
||||
|
||||
``` bash |
||||
conda activate kraken |
||||
|
||||
conda install quart |
||||
conda install pandas |
||||
conda install orjson |
||||
conda install matplotlib |
||||
|
||||
pip3 install dash_bootstrap_components |
||||
pip3 install quart_compress |
||||
pip3 install dash_devices |
||||
pip3 install pyargus |
||||
|
||||
conda install dash==1.20.0 |
||||
``` |
||||
|
||||
4. Install the krakensdr_doa software |
||||
|
||||
```bash |
||||
cd ~/krakensdr |
||||
git clone https://github.com/krakenrf/krakensdr_doa |
||||
cd krakensdr_doa |
||||
git checkout clientside_graphs |
||||
``` |
||||
|
||||
Copy the the *krakensdr_doa/util/kraken_doa_start.sh* and the *krakensdr_doa/util/kraken_doa_stop.sh* scripts into the krakensdr root folder of the project. |
||||
```bash |
||||
cd ~/krakensdr |
||||
cp krakensdr_doa/util/kraken_doa_start.sh . |
||||
cp krakensdr_doa/util/kraken_doa_stop.sh . |
||||
``` |
||||
|
||||
## Running |
||||
|
||||
### Local operation (Recommended) |
||||
|
||||
```bash |
||||
./kraken_doa_start.sh |
||||
``` |
||||
|
||||
Please be patient on the first run, at it can take 1-2 minutes for the JIT numba compiler to compile the numba optimized functions, and during this compilation time it may appear that the software has gotten stuck. On subsqeuent runs this loading time will be much faster as it will read from cache. |
||||
|
||||
### Remote operation |
||||
|
||||
1. Start the DAQ Subsystem either remotely. (Make sure that the *daq_chain_config.ini* contains the proper configuration) |
||||
(See:https://github.com/krakenrf/heimdall_daq_fw/Documentation) |
||||
2. Set the IP address of the DAQ Subsystem in the settings.json, *default_ip* field. |
||||
3. Start the DoA DSP software by typing: |
||||
`./gui_run.sh` |
||||
4. To stop the server and the DSP processing chain run the following script: |
||||
`./kill.sh` |
||||
|
||||
<p1> After starting the script a web based server opens at port number 8051, which then can be accessed by typing "KRAKEN_IP:8050/" in the address bar of any web browser. You can find the IP address of the KrakenSDR Pi4 wither via your routers WiFi management page, or by typing "ip addr" into the terminal. You can also use the hostname of the Pi4 in place of the IP address, but this only works on local networks, and not the internet, or mobile hotspot networks. </p1> |
||||
|
||||
 |
||||
|
||||
## Upcoming Features and Known Bugs |
||||
|
||||
1. [FEATURE] Currently squelch works by selecting the strongest signal that is active and above the set threshold within the active bandwidth. The next steps will be to allow users to create multiple channels within the active bandwidth, each with their own squelch. This will allow users to track multiple signals at once, and ignore unwated signals within the bandwidth at the same time. |
||||
|
||||
2. [FEATURE] It would be better if the KrakenSDR controls, spectrum and/or DOA graphs could be accessible from the same page. Future work will look to integrate the controls in a sidebar. |
||||
|
||||
3. [FEATURE] Some users would like to monitor the spectrum, and manually click on an active signal to DF that particular signal. We will be looking at a way to implement this. |
||||
|
||||
4. [BUG] Sometimes the DOA graphs will not load properly and refreshing the page is required. A fix is being investigated. |
||||
|
||||
This software was 95% developed by Tamas Peto, and makes use of his pyAPRIL and pyARGUS libraries. See his website at www.tamaspeto.com |
||||
|
@ -0,0 +1,215 @@ |
||||
/*Globlal Properties*/ |
||||
/*Tags*/ |
||||
body { |
||||
background-color: #000000; |
||||
color:#ffffff; |
||||
margin: 0px; |
||||
padding: 0px; |
||||
} |
||||
*{ |
||||
font-size:18px; |
||||
font-family: 'Montserrat', sans-serif; |
||||
} |
||||
h1{ |
||||
font-size:22px; |
||||
} |
||||
a{ |
||||
font-size: 22px; |
||||
} |
||||
input[type="checkbox"]{ |
||||
width:20px; |
||||
height:20px; |
||||
background:white; |
||||
border-radius:5px; |
||||
border:2px solid #555; |
||||
vertical-align: text-bottom; |
||||
} |
||||
.Select-control { |
||||
background-color:#000000; |
||||
} |
||||
.Select-value { |
||||
background-color: rgb(255, 102, 0); |
||||
color:#000000; |
||||
} |
||||
.Select-menu-outer { |
||||
background-color: rgb(0, 0, 0); |
||||
} |
||||
|
||||
input, select{ |
||||
width: 100%; |
||||
} |
||||
iframe{ |
||||
background-color: transparent; |
||||
border: 0px none transparent; |
||||
padding: 0px; |
||||
overflow: hidden; |
||||
} |
||||
/*Classes*/ |
||||
.header{ |
||||
background-color: #000000; |
||||
width: 100%; |
||||
text-align: center; |
||||
padding: 10px; |
||||
} |
||||
.header a{ |
||||
color: white; |
||||
text-transform: uppercase; |
||||
text-decoration: none; |
||||
padding: 5px; |
||||
line-height: 40px; |
||||
border: 0.1em solid #ffffff; |
||||
border-radius:0.05em; |
||||
margin: 5px; |
||||
} |
||||
.header_active, .header a:hover{ |
||||
background-color: #ff6600c0; |
||||
} |
||||
.ctr_toolbar{ |
||||
background-color: #000000; |
||||
height: 50px; |
||||
width: 100%; |
||||
text-align: center; |
||||
} |
||||
.ctr_toolbar_item { |
||||
text-align: center; |
||||
display : inline-block; |
||||
position : relative; |
||||
margin : 0 auto; |
||||
padding : 0px; |
||||
float : center; |
||||
} |
||||
.doa_check{ |
||||
text-align: left; |
||||
} |
||||
.btn{ |
||||
height: 40px; |
||||
cursor: pointer; |
||||
background-color: #141414; |
||||
border: 0.1em solid #000000; |
||||
border-radius:0.12em; |
||||
color: white; |
||||
font-size:20px; |
||||
text-decoration: none; |
||||
text-align: center; |
||||
width: 100%; |
||||
transition: all 0.2s; |
||||
} |
||||
.btn:hover{ |
||||
color: rgb(0, 0, 0); |
||||
background-color: #ff6600; |
||||
} |
||||
.btn a{ |
||||
color: white; |
||||
text-decoration: none; |
||||
position: relative; |
||||
top: 15%; |
||||
} |
||||
.btn_start{ |
||||
height: 40px; |
||||
cursor: pointer; |
||||
background-color: #02c93d; |
||||
border: 0.1em solid #000000; |
||||
border-radius:0.12em; |
||||
color: rgb(0, 0, 0); |
||||
font-size:20px; |
||||
text-decoration: none; |
||||
text-align: center; |
||||
width: 100%; |
||||
transition: all 0.2s; |
||||
} |
||||
.btn_start:hover{ |
||||
color: rgb(0, 0, 0); |
||||
background-color: #ff6600; |
||||
} |
||||
.btn_stop{ |
||||
height: 40px; |
||||
cursor: pointer; |
||||
background-color: #c40404; |
||||
border: 0.1em solid #000000; |
||||
border-radius:0.12em; |
||||
color: rgb(0, 0, 0); |
||||
font-size:20px; |
||||
text-decoration: none; |
||||
text-align: center; |
||||
width: 100%; |
||||
} |
||||
.btn_stop:hover{ |
||||
color: rgb(0, 0, 0); |
||||
background-color: #ff6600; |
||||
} |
||||
.btn_save_cfg{ |
||||
height: 40px; |
||||
cursor: pointer; |
||||
background-color: #b5aef5; |
||||
border: 0.1em solid #000000; |
||||
border-radius:0.12em; |
||||
color: rgb(0, 0, 0); |
||||
font-size:20px; |
||||
text-decoration: none; |
||||
text-align: center; |
||||
width: 100%; |
||||
} |
||||
.btn_save_cfg:hover{ |
||||
color: rgb(154, 233, 102); |
||||
background-color: #ff6600; |
||||
} |
||||
.tooltip{ |
||||
color: rgb(0, 0, 0); |
||||
background-color: #ffffff; |
||||
opacity: 0.95; |
||||
border-radius:0.2em; |
||||
width: 300px; |
||||
} |
||||
.card{ |
||||
background-color: #000000; |
||||
border: 0.1em solid #ffffff; |
||||
border-radius:0.2em; |
||||
width: 400px; |
||||
max-width: 500px; |
||||
padding: 20px; |
||||
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.46); |
||||
margin: 10px; |
||||
float: left; |
||||
} |
||||
.monitor_card{ |
||||
background-color: #000000; |
||||
border: 0.1em solid #ffffff; |
||||
border-radius:0.2em; |
||||
width: 95%; |
||||
height: 800px; |
||||
overflow:scroll; |
||||
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.46); |
||||
margin: auto; |
||||
} |
||||
|
||||
.field{ |
||||
width: 400px; |
||||
display: block; |
||||
margin: 10px auto; |
||||
padding: 0px; |
||||
} |
||||
.field-label{ |
||||
width: 250px; |
||||
display: inline-block; |
||||
vertical-align: top; |
||||
} |
||||
.field-body{ |
||||
width: 144px; |
||||
display: inline-block; |
||||
} |
||||
|
||||
/*Mobile Properties*/ |
||||
@media screen and (max-width: 1072px) { |
||||
.card{ |
||||
width: calc( 100% - 30px ); |
||||
padding: 5px; |
||||
margin: 20px auto; |
||||
float: none; |
||||
} |
||||
} |
||||
|
||||
@media screen and (max-width: 500px) { |
||||
.field, .field-label, .field-body, input{ |
||||
width: calc( 100% - 15px ); |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,210 @@ |
||||
import dash_html_components as html |
||||
import dash_bootstrap_components as dbc |
||||
|
||||
dsp_config_tooltips = html.Div([ |
||||
# Antenna arrangement selection |
||||
dbc.Tooltip([ |
||||
html.P("ULA - Uniform Linear Array"), |
||||
html.P("Antenna elements placed on a line with having equal distances between each other"), |
||||
html.P("UCA - Uniform Circular Array"), |
||||
html.P("Antenna elements are placed on circle equaly distributed on 360°")], |
||||
target="label_ant_arrangement", |
||||
placement="bottom", |
||||
className="tooltip" |
||||
), |
||||
# Antenna Spacing |
||||
# dbc.Tooltip([ |
||||
# html.P("When ULA is selected: Spacing between antenna elements"), |
||||
# html.P("When UCA is selected: Radius of the circle on which the elements are placed")], |
||||
# target="label_ant_spacing", |
||||
# placement="bottom", |
||||
# className="tooltip" |
||||
# ), |
||||
# Enable F-B averaging |
||||
dbc.Tooltip([ |
||||
html.P("Forward-backward averegaing improves the performance of DoA estimation in multipath environment"), |
||||
html.P("(Available only for ULA antenna systems)")], |
||||
target="label_en_fb_avg", |
||||
placement="bottom", |
||||
className="tooltip" |
||||
), |
||||
]) |
||||
|
||||
daq_ini_config_tooltips = html.Div([ |
||||
# DAQ buffer size |
||||
dbc.Tooltip([ |
||||
html.P("Buffer size of the realtek driver")], |
||||
target="label_daq_buffer_size", |
||||
placement="bottom", |
||||
className="tooltip" |
||||
), |
||||
# Sampling frequency |
||||
dbc.Tooltip([ |
||||
html.P("Raw - ADC sampling frequency of the realtek chip")], |
||||
target="label_sample_rate", |
||||
placement="bottom", |
||||
className="tooltip" |
||||
), |
||||
# Enable noise source control |
||||
dbc.Tooltip([ |
||||
html.P("Enables the utilization of the built-in noise source for calibration")], |
||||
target="label_en_noise_source_ctr", |
||||
placement="bottom", |
||||
className="tooltip" |
||||
), |
||||
# Enable squelch mode |
||||
dbc.Tooltip([ |
||||
html.P("Enable DAQ-side squelch to capture burst like signals - NOTE DISABLED IN THIS VERSION, THIS VERSION USES DSP SIDE SQUELCH ONLY")], |
||||
target="label_en_squelch", |
||||
placement="bottom", |
||||
className="tooltip" |
||||
), |
||||
# Squelch threshold |
||||
dbc.Tooltip([ |
||||
html.P("Amplitude threshold used for the squelch feature."), |
||||
html.P("Should take values on range: 0...1"), |
||||
html.P("When set to zero the squelch is bypassed")], |
||||
target="label_squelch_init_threshold", |
||||
placement="bottom", |
||||
className="tooltip" |
||||
), |
||||
# CPI size |
||||
dbc.Tooltip([ |
||||
html.P("Length of the Coherent Processing Interval (CPI) after decimation")], |
||||
target="label_cpi_size", |
||||
placement="bottom", |
||||
className="tooltip" |
||||
), |
||||
# Decimation raito |
||||
dbc.Tooltip([ |
||||
html.P("Decimation factor")], |
||||
target="label_decimation_ratio", |
||||
placement="bottom", |
||||
className="tooltip" |
||||
), |
||||
# FIR relative bandwidth |
||||
dbc.Tooltip([ |
||||
html.P("Anti-aliasing filter bandwith after decimation"), |
||||
html.P("Should take values on range: (0, 1]"), |
||||
html.P("E.g.: ADC sampling frequency: 1 MHz (IQ!) , Decimation ratio: 2, FIR relative bandwith:0.25"), |
||||
html.P("Resulting passband bandwidth: 125 kHz ")], |
||||
target="label_fir_relative_bw", |
||||
placement="bottom", |
||||
className="tooltip" |
||||
), |
||||
# FIR tap size |
||||
dbc.Tooltip([ |
||||
html.P("Anti-aliasing FIR filter tap size - Do not set too large, or CPU utilization will be 100%"), |
||||
html.P("Should be greater than the decimation ratio")], |
||||
target="label_fir_tap_size", |
||||
placement="bottom", |
||||
className="tooltip" |
||||
), |
||||
# FIR tap size |
||||
dbc.Tooltip([ |
||||
html.P("Window function type for designing the anti-aliasing FIR filter"), |
||||
html.P("https://en.wikipedia.org/wiki/Window_function")], |
||||
target="label_fir_window", |
||||
placement="bottom", |
||||
className="tooltip" |
||||
), |
||||
# Enable filter reset |
||||
dbc.Tooltip([ |
||||
html.P("If enabled, the memory of the anti-aliasing FIR filter is reseted at the begining of every new CPI")], |
||||
target="label_en_filter_reset", |
||||
placement="bottom", |
||||
className="tooltip" |
||||
), |
||||
# Correlation size |
||||
dbc.Tooltip([ |
||||
html.P("Number of samples used for the calibration procedure (sample delay and IQ compensation)")], |
||||
target="label_correlation_size", |
||||
placement="bottom", |
||||
className="tooltip" |
||||
), |
||||
# Standard channel index |
||||
dbc.Tooltip([ |
||||
html.P("The selected channel is used as a reference for the IQ compensation")], |
||||
target="label_std_ch_index", |
||||
placement="bottom", |
||||
className="tooltip" |
||||
), |
||||
# Enable IQ calibration |
||||
dbc.Tooltip([ |
||||
html.P("Enables to compensate the amplitude and phase differences of the receiver channels")], |
||||
target="label_en_iq_calibration", |
||||
placement="bottom", |
||||
className="tooltip" |
||||
), |
||||
# Gain lock interval |
||||
dbc.Tooltip([ |
||||
html.P("Minimum number of stable frames before terminating the gain tuning procedure")], |
||||
target="label_gain_lock_interval", |
||||
placement="bottom", |
||||
className="tooltip" |
||||
), |
||||
# Require track lock intervention |
||||
dbc.Tooltip([ |
||||
html.P("When enabled the DAQ firmware waits for manual intervention during the calibraiton procedure"), |
||||
html.P("Should be used only for hardave version 1.0")], |
||||
target="label_require_track_lock", |
||||
placement="bottom", |
||||
className="tooltip" |
||||
), |
||||
# Amplitude calibraiton mode |
||||
dbc.Tooltip([ |
||||
html.P("Amplitude difference compensation method applied as part of the IQ compensation"), |
||||
html.P("default: Amplitude differences are estimated by calculating the cross-correlations of the channels"), |
||||
html.P("disabled: Amplitude differences are not compensated"), |
||||
html.P("channel_power: Ampltiude compensation is set in a way to achieve equal channel powers")], |
||||
target="label_amplitude_calibration_mode", |
||||
placement="bottom", |
||||
className="tooltip" |
||||
), |
||||
dbc.Tooltip([ |
||||
html.P("When periodic calibration track mode is selected the firmware regularly turn on the noise source for a short burst to\ |
||||
check whether the IQ calibration is still valid or not. In case the calibrated state is lost, the firmware automatically\ |
||||
initiates a reclaibration procedure")], |
||||
target="label_calibration_track_mode", |
||||
placement="bottom", |
||||
className="tooltip" |
||||
), |
||||
|
||||
# Calibration frame interval |
||||
dbc.Tooltip([ |
||||
html.P("Number of data frames between two consecutive calibration burst. Used when periodic calibration mode is selected")], |
||||
target="label_calibration_frame_interval", |
||||
placement="bottom", |
||||
className="tooltip" |
||||
), |
||||
# Calibration frame burst size |
||||
dbc.Tooltip([ |
||||
html.P("Number of calibration frames generated in the periodic calibration mode")], |
||||
target="label_calibration_frame_burst_size", |
||||
placement="bottom", |
||||
className="tooltip" |
||||
), |
||||
# Amplitude tolerance |
||||
dbc.Tooltip([ |
||||
html.P("Maximum allowed amplitude difference between the receiver channels")], |
||||
target="label_amplitude_tolerance", |
||||
placement="bottom", |
||||
className="tooltip" |
||||
), |
||||
# Phase tolerance |
||||
dbc.Tooltip([ |
||||
html.P("Maximum allowed phase difference between the receiver channels")], |
||||
target="label_phase_tolerance", |
||||
placement="bottom", |
||||
className="tooltip" |
||||
), |
||||
# Maximum sync fails |
||||
dbc.Tooltip([ |
||||
html.P("Maximum allowed consecutive IQ difference check failures before initiating a recalibration")], |
||||
target="label_max_sync_fails", |
||||
placement="bottom", |
||||
className="tooltip" |
||||
), |
||||
]) |
||||
|
||||
|
@ -0,0 +1,102 @@ |
||||
import json |
||||
import os |
||||
|
||||
""" |
||||
Handles the DoA DSP settings |
||||
|
||||
Project: Kraken DoA DSP |
||||
Author : Tamas Peto |
||||
""" |
||||
|
||||
root_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) |
||||
settings_file_path = os.path.join(root_path,"settings.json") |
||||
|
||||
settings_found =False |
||||
if os.path.exists(settings_file_path): |
||||
settings_found = True |
||||
with open(settings_file_path, 'r') as myfile: |
||||
settings=json.loads(myfile.read()) |
||||
|
||||
# DAQ Configuration |
||||
center_freq = settings.get("center_freq", 100.0) |
||||
uniform_gain = settings.get("uniform_gain", 1.4) |
||||
data_interface = settings.get("data_interface", "eth") |
||||
default_ip = settings.get("default_ip", "0.0.0.0") |
||||
|
||||
# PR Paramaters |
||||
en_pr = settings.get("en_pr", 0) |
||||
clutter_cancel_algo = settings.get("clutter_cancel_algo", "Wiener MRE") |
||||
max_bistatic_range = settings.get("max_bistatic_range", 128) |
||||
max_doppler = settings.get("max_doppler", 256) |
||||
en_pr_persist = settings.get("en_pr_persist", 1) |
||||
pr_persist_decay = settings.get("pr_persist_decay", 0.99) |
||||
pr_dynrange_min = settings.get("pr_dynrange_min", -20) |
||||
pr_dynrange_max = settings.get("pr_dynrange_max", 100) |
||||
#ant_arrangement = settings.get("ant_arrangement", "ULA") |
||||
#ant_spacing = settings.get("ant_spacing", 0.5) |
||||
#doa_method = settings.get("doa_method", "MUSIC") |
||||
#en_fbavg = settings.get("en_fbavg", 0) |
||||
#compass_offset = settings.get("compass_offset", 0) |
||||
#doa_fig_type = settings.get("doa_fig_type", "Linear plot") |
||||
|
||||
# DSP misc |
||||
#en_squelch = settings.get("en_squelch", 0) |
||||
#squelch_threshold_dB = settings.get("squelch_threshold_dB", 0.0) |
||||
|
||||
# Web Interface |
||||
en_hw_check = settings.get("en_hw_check", 0) |
||||
en_advanced_daq_cfg = settings.get("en_advanced_daq_cfg", 0) |
||||
logging_level = settings.get("logging_level", 0) |
||||
disable_tooltips = settings.get("disable_tooltips", 0) |
||||
|
||||
# Check and correct if needed |
||||
#if not ant_arrangement in ["ULA", "UCA"]: |
||||
# ant_arrangement="ULA" |
||||
|
||||
#doa_method_dict = {"Bartlett":0, "Capon":1, "MEM":2, "MUSIC":3} |
||||
#if not doa_method in doa_method_dict: |
||||
# doa_method = "MUSIC" |
||||
|
||||
#doa_fig_type_dict = {"Linear plot":0, "Polar plot":1, "Compass":2} |
||||
#if not doa_fig_type in doa_fig_type_dict: |
||||
# doa_gfig_type="Linear plot" |
||||
|
||||
def write(data = None): |
||||
if data is None: |
||||
data = {} |
||||
|
||||
# DAQ Configuration |
||||
data["center_freq"] = center_freq |
||||
data["uniform_gain"] = uniform_gain |
||||
data["data_interface"] = data_interface |
||||
data["default_ip"] = default_ip |
||||
|
||||
# DOA Estimation |
||||
data["en_pr"] = en_pr |
||||
data["clutter_cancel_algo"] = clutter_cancel_algo |
||||
data["max_bistatic_range"] = max_bistatic_range |
||||
data["max_doppler"] = max_doppler |
||||
data["en_pr_persist"] = en_pr_persist |
||||
data["pr_persist_decay"] = pr_persist_decay |
||||
data["pr_dynrange_min"] = pr_dynrange_min |
||||
data["pr_dynrange_max"] = pr_dynrange_max |
||||
|
||||
#data["ant_arrangement"] = ant_arrangement |
||||
#data["ant_spacing"] = ant_spacing |
||||
#data["doa_method"] = doa_method |
||||
#data["en_fbavg"] = en_fbavg |
||||
#data["compass_offset"] = compass_offset |
||||
#data["doa_fig_tpye"] = doa_fig_type |
||||
|
||||
# DSP misc |
||||
#data["en_squelch"] = en_squelch |
||||
#data["squelch_threshold_dB"] = squelch_threshold_dB |
||||
|
||||
# Web Interface |
||||
data["en_hw_check"] = en_hw_check |
||||
data["en_advanced_daq_cfg"] = en_advanced_daq_cfg |
||||
data["logging_level"] = logging_level |
||||
data["disable_tooltips"] = disable_tooltips |
||||
|
||||
with open(settings_file_path, 'w') as outfile: |
||||
json.dump(data, outfile) |
@ -0,0 +1,152 @@ |
||||
from struct import pack,unpack |
||||
import logging |
||||
import sys |
||||
""" |
||||
Desctiption: IQ Frame header definition |
||||
For header field description check the corresponding documentation |
||||
Total length: 1024 byte |
||||
Project: HeIMDALL RTL |
||||
Author: Tamás Pető |
||||
Status: Finished |
||||
Version history: |
||||
1 : Initial version (2019 04 23) |
||||
2 : Fixed 1024 byte length (2019 07 25) |
||||
3 : Noise source state (2019 10 01) |
||||
4 : IQ sync flag (2019 10 21) |
||||
5 : Sync state (2019 11 10) |
||||
6 : Unix Epoch timestamp (2019 12 17) |
||||
6a: Frame type defines (2020 03 19) |
||||
7 : Sync word (2020 05 03) |
||||
""" |
||||
class IQHeader(): |
||||
|
||||
FRAME_TYPE_DATA = 0 |
||||
FRAME_TYPE_DUMMY = 1 |
||||
FRAME_TYPE_RAMP = 2 |
||||
FRAME_TYPE_CAL = 3 |
||||
FRAME_TYPE_TRIGW = 4 |
||||
|
||||
SYNC_WORD = 0x2bf7b95a |
||||
|
||||
def __init__(self): |
||||
|
||||
self.logger = logging.getLogger(__name__) |
||||
self.header_size = 1024 # size in bytes |
||||
self.reserved_bytes = 192 |
||||
|
||||
self.sync_word=self.SYNC_WORD # uint32_t |
||||
self.frame_type=0 # uint32_t |
||||
self.hardware_id="" # char [16] |
||||
self.unit_id=0 # uint32_t |
||||
self.active_ant_chs=0 # uint32_t |
||||
self.ioo_type=0 # uint32_t |
||||
self.rf_center_freq=0 # uint64_t |
||||
self.adc_sampling_freq=0 # uint64_t |
||||
self.sampling_freq=0 # uint64_t |
||||
self.cpi_length=0 # uint32_t |
||||
self.time_stamp=0 # uint64_t |
||||
self.daq_block_index=0 # uint32_t |
||||
self.cpi_index=0 # uint32_t |
||||
self.ext_integration_cntr=0 # uint64_t |
||||
self.data_type=0 # uint32_t |
||||
self.sample_bit_depth=0 # uint32_t |
||||
self.adc_overdrive_flags=0 # uint32_t |
||||
self.if_gains=[0]*32 # uint32_t x 32 |
||||
self.delay_sync_flag=0 # uint32_t |
||||
self.iq_sync_flag=0 # uint32_t |
||||
self.sync_state=0 # uint32_t |
||||
self.noise_source_state=0 # uint32_t |
||||
self.reserved=[0]*self.reserved_bytes# uint32_t x reserverd_bytes |
||||
self.header_version=0 # uint32_t |
||||
|
||||
def decode_header(self, iq_header_byte_array): |
||||
""" |
||||
Unpack,decode and store the content of the iq header |
||||
""" |
||||
iq_header_list = unpack("II16sIIIQQQIQIIQIII"+"I"*32+"IIII"+"I"*self.reserved_bytes+"I", iq_header_byte_array) |
||||
|
||||
self.sync_word = iq_header_list[0] |
||||
self.frame_type = iq_header_list[1] |
||||
self.hardware_id = iq_header_list[2].decode() |
||||
self.unit_id = iq_header_list[3] |
||||
self.active_ant_chs = iq_header_list[4] |
||||
self.ioo_type = iq_header_list[5] |
||||
self.rf_center_freq = iq_header_list[6] |
||||
self.adc_sampling_freq = iq_header_list[7] |
||||
self.sampling_freq = iq_header_list[8] |
||||
self.cpi_length = iq_header_list[9] |
||||
self.time_stamp = iq_header_list[10] |
||||
self.daq_block_index = iq_header_list[11] |
||||
self.cpi_index = iq_header_list[12] |
||||
self.ext_integration_cntr = iq_header_list[13] |
||||
self.data_type = iq_header_list[14] |
||||
self.sample_bit_depth = iq_header_list[15] |
||||
self.adc_overdrive_flags = iq_header_list[16] |
||||
self.if_gains = iq_header_list[17:49] |
||||
self.delay_sync_flag = iq_header_list[49] |
||||
self.iq_sync_flag = iq_header_list[50] |
||||
self.sync_state = iq_header_list[51] |
||||
self.noise_source_state = iq_header_list[52] |
||||
self.header_version = iq_header_list[52+self.reserved_bytes+1] |
||||
|
||||
def encode_header(self): |
||||
""" |
||||
Pack the iq header information into a byte array |
||||
""" |
||||
iq_header_byte_array=pack("II", self.sync_word, self.frame_type) |
||||
iq_header_byte_array+=self.hardware_id.encode()+bytearray(16-len(self.hardware_id.encode())) |
||||
iq_header_byte_array+=pack("IIIQQQIQIIQIII", |
||||
self.unit_id, self.active_ant_chs, self.ioo_type, self.rf_center_freq, self.adc_sampling_freq, |
||||
self.sampling_freq, self.cpi_length, self.time_stamp, self.daq_block_index, self.cpi_index, |
||||
self.ext_integration_cntr, self.data_type, self.sample_bit_depth, self.adc_overdrive_flags) |
||||
for m in range(32): |
||||
iq_header_byte_array+=pack("I", self.if_gains[m]) |
||||
|
||||
iq_header_byte_array+=pack("I", self.delay_sync_flag) |
||||
iq_header_byte_array+=pack("I", self.iq_sync_flag) |
||||
iq_header_byte_array+=pack("I", self.sync_state) |
||||
iq_header_byte_array+=pack("I", self.noise_source_state) |
||||
|
||||
for m in range(self.reserved_bytes): |
||||
iq_header_byte_array+=pack("I",0) |
||||
|
||||
iq_header_byte_array+=pack("I", self.header_version) |
||||
return iq_header_byte_array |
||||
|
||||
def dump_header(self): |
||||
""" |
||||
Prints out the content of the header in human readable format |
||||
""" |
||||
self.logger.info("Sync word: {:d}".format(self.sync_word)) |
||||
self.logger.info("Header version: {:d}".format(self.header_version)) |
||||
self.logger.info("Frame type: {:d}".format(self.frame_type)) |
||||
self.logger.info("Hardware ID: {:16}".format(self.hardware_id)) |
||||
self.logger.info("Unit ID: {:d}".format(self.unit_id)) |
||||
self.logger.info("Active antenna channels: {:d}".format(self.active_ant_chs)) |
||||
self.logger.info("Illuminator type: {:d}".format(self.ioo_type)) |
||||
self.logger.info("RF center frequency: {:.2f} MHz".format(self.rf_center_freq/10**6)) |
||||
self.logger.info("ADC sampling frequency: {:.2f} MHz".format(self.adc_sampling_freq/10**6)) |
||||
self.logger.info("IQ sampling frequency {:.2f} MHz".format(self.sampling_freq/10**6)) |
||||
self.logger.info("CPI length: {:d}".format(self.cpi_length)) |
||||
self.logger.info("Unix Epoch timestamp: {:d}".format(self.time_stamp)) |
||||
self.logger.info("DAQ block index: {:d}".format(self.daq_block_index)) |
||||
self.logger.info("CPI index: {:d}".format(self.cpi_index)) |
||||
self.logger.info("Extended integration counter {:d}".format(self.ext_integration_cntr)) |
||||
self.logger.info("Data type: {:d}".format(self.data_type)) |
||||
self.logger.info("Sample bit depth: {:d}".format(self.sample_bit_depth)) |
||||
self.logger.info("ADC overdrive flags: {:d}".format(self.adc_overdrive_flags)) |
||||
for m in range(32): |
||||
self.logger.info("Ch: {:d} IF gain: {:.1f} dB".format(m, self.if_gains[m]/10)) |
||||
self.logger.info("Delay sync flag: {:d}".format(self.delay_sync_flag)) |
||||
self.logger.info("IQ sync flag: {:d}".format(self.iq_sync_flag)) |
||||
self.logger.info("Sync state: {:d}".format(self.sync_state)) |
||||
self.logger.info("Noise source state: {:d}".format(self.noise_source_state)) |
||||
|
||||
def check_sync_word(self): |
||||
""" |
||||
Check the sync word of the header |
||||
""" |
||||
if self.sync_word != self.SYNC_WORD: |
||||
return -1 |
||||
else: |
||||
return 0 |
@ -0,0 +1,375 @@ |
||||
# KrakenSDR Receiver |
||||
|
||||
# Copyright (C) 2018-2021 Carl Laufer, Tamás Pető |
||||
# |
||||
# |
||||
# This program 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 of the License, or |
||||
# any later version. |
||||
# |
||||
# This program 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 program. If not, see <https://www.gnu.org/licenses/>. |
||||
# |
||||
|
||||
# -*- coding: utf-8 -*- |
||||
|
||||
# Import built-in modules |
||||
import sys |
||||
import os |
||||
import time |
||||
from struct import pack, unpack |
||||
import socket |
||||
import _thread |
||||
from threading import Lock |
||||
import queue |
||||
import logging |
||||
#import copy |
||||
|
||||
# Import third party modules |
||||
import numpy as np |
||||
from scipy import signal |
||||
from iq_header import IQHeader |
||||
from shmemIface import inShmemIface |
||||
class ReceiverRTLSDR(): |
||||
|
||||
def __init__(self, data_que, data_interface = "eth", logging_level=10): |
||||
""" |
||||
Parameter: |
||||
---------- |
||||
:param: data_que: Que to communicate with the UI (web iface/Qt GUI) |
||||
:param: data_interface: This field is configured by the GUI during instantiation. |
||||
Valid values are the followings: |
||||
"eth" : The module will receiver IQ frames through an Ethernet connection |
||||
"shmem": The module will receiver IQ frames through a shared memory interface |
||||
:type : data_interface: string |
||||
""" |
||||
self.logger = logging.getLogger(__name__) |
||||
self.logger.setLevel(logging_level) |
||||
|
||||
# DAQ parameters |
||||
# These values are used by default to configure the DAQ through the configuration interface |
||||
# Values are configured externally upon configuration request |
||||
self.daq_center_freq = 100 # MHz |
||||
self.daq_rx_gain = 0 # [dB] |
||||
self.daq_squelch_th_dB = 0 |
||||
|
||||
# UI interface |
||||
self.data_que = data_que |
||||
|
||||
# IQ data interface |
||||
self.data_interface = data_interface |
||||
|
||||
# -> Ethernet |
||||
self.receiver_connection_status = False |
||||
self.port = 5000 |
||||
self.rec_ip_addr = "127.0.0.1" # Configured by the GUI prior to connection request |
||||
self.socket_inst = socket.socket() |
||||
self.receiverBufferSize = 2 ** 18 # Size of the Ethernet receiver buffer measured in bytes |
||||
|
||||
# -> Shared memory |
||||
root_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) |
||||
daq_path = os.path.join(os.path.dirname(root_path),"heimdall_daq_fw") |
||||
self.daq_shmem_control_path = os.path.join(os.path.join(daq_path,"Firmware"),"_data_control/") |
||||
self.init_data_iface() |
||||
|
||||
# Control interface |
||||
self.ctr_iface_socket = socket.socket() |
||||
self.ctr_iface_port = 5001 |
||||
self.ctr_iface_thread_lock = Lock() # Used to synchronize the operation of the ctr_iface thread |
||||
|
||||
self.iq_frame_bytes = None |
||||
self.iq_samples = None |
||||
self.iq_header = IQHeader() |
||||
self.M = 0 # Number of receiver channels, updated after establishing connection |
||||
|
||||
def init_data_iface(self): |
||||
if self.data_interface == "shmem": |
||||
# Open shared memory interface to capture the DAQ firmware output |
||||
self.in_shmem_iface = inShmemIface("delay_sync_iq", self.daq_shmem_control_path) |
||||
if not self.in_shmem_iface.init_ok: |
||||
self.logger.critical("Shared memory initialization failed") |
||||
self.in_shmem_iface.destory_sm_buffer() |
||||
return -1 |
||||
return 0 |
||||
|
||||
|
||||
def eth_connect(self): |
||||
""" |
||||
Compatible only with DAQ firmwares that has the IQ streaming mode. |
||||
HeIMDALL DAQ Firmware version: 1.0 or later |
||||
""" |
||||
try: |
||||
if not self.receiver_connection_status: |
||||
if self.data_interface == "eth": |
||||
# Establlish IQ data interface connection |
||||
self.socket_inst.connect((self.rec_ip_addr, self.port)) |
||||
self.socket_inst.sendall(str.encode('streaming')) |
||||
test_iq = self.receive_iq_frame() |
||||
|
||||
self.M = self.iq_header.active_ant_chs |
||||
|
||||
# Establish control interface connection |
||||
self.ctr_iface_socket.connect((self.rec_ip_addr, self.ctr_iface_port)) |
||||
self.receiver_connection_status = True |
||||
self.ctr_iface_init() |
||||
self.logger.info("CTR INIT Center freq: {0}".format(self.daq_center_freq)) |
||||
self.set_center_freq(self.daq_center_freq) |
||||
self.set_if_gain(self.daq_rx_gain) |
||||
self.set_squelch_threshold(self.daq_squelch_th_dB) |
||||
except: |
||||
errorMsg = sys.exc_info()[0] |
||||
self.logger.error("Error message: "+str(errorMsg)) |
||||
self.receiver_connection_status = False |
||||
self.logger.error("Unexpected error: {0}".format(sys.exc_info()[0])) |
||||
|
||||
# Re-instantiating sockets |
||||
self.socket_inst = socket.socket() |
||||
self.ctr_iface_socket = socket.socket() |
||||
return -1 |
||||
|
||||
self.logger.info("Connection established") |
||||
que_data_packet = [] |
||||
que_data_packet.append(['conn-ok',]) |
||||
self.data_que.put(que_data_packet) |
||||
|
||||
def eth_close(self): |
||||
""" |
||||
Close Ethernet conenctions including the IQ data and the control interfaces |
||||
""" |
||||
try: |
||||
if self.receiver_connection_status: |
||||
if self.data_interface == "eth": |
||||
self.socket_inst.sendall(str.encode('q')) # Send exit message |
||||
self.socket_inst.close() |
||||
self.socket_inst = socket.socket() # Re-instantiating socket |
||||
|
||||
# Close control interface connection |
||||
exit_message_bytes=("EXIT".encode()+bytearray(124)) |
||||
self.ctr_iface_socket.send(exit_message_bytes) |
||||
self.ctr_iface_socket.close() |
||||
self.ctr_iface_socket = socket.socket() |
||||
|
||||
self.receiver_connection_status = False |
||||
que_data_packet = [] |
||||
que_data_packet.append(['disconn-ok',]) |
||||
self.data_que.put(que_data_packet) |
||||
except: |
||||
errorMsg = sys.exc_info()[0] |
||||
self.logger.error("Error message: {0}".format(errorMsg)) |
||||
return -1 |
||||
|
||||
if self.data_interface == "shmem": |
||||
self.in_shmem_iface.destory_sm_buffer() |
||||
|
||||
return 0 |
||||
|
||||
def get_iq_online(self): |
||||
""" |
||||
This function obtains a new IQ data frame through the Ethernet IQ data or the shared memory interface |
||||
""" |
||||
|
||||
# Check connection |
||||
if not self.receiver_connection_status: |
||||
fail = self.eth_connect() |
||||
if fail: |
||||
return -1 |
||||
|
||||
if self.data_interface == "eth": |
||||
self.socket_inst.sendall(str.encode("IQDownload")) # Send iq request command |
||||
self.iq_samples = self.receive_iq_frame() |
||||
|
||||
elif self.data_interface == "shmem": |
||||
active_buff_index = self.in_shmem_iface.wait_buff_free() |
||||
if active_buff_index < 0 or active_buff_index > 1: |
||||
self.logger.info("Terminating.., signal: {:d}".format(active_buff_index)) |
||||
return -1 |
||||
|
||||
buffer = self.in_shmem_iface.buffers[active_buff_index] |
||||
iq_header_bytes = buffer[0:1024].tobytes() |
||||
self.iq_header.decode_header(iq_header_bytes) |
||||
|
||||
# Inititalization from header - Set channel numbers |
||||
if self.M == 0: |
||||
self.M = self.iq_header.active_ant_chs |
||||
|
||||
incoming_payload_size = self.iq_header.cpi_length*self.iq_header.active_ant_chs*2*int(self.iq_header.sample_bit_depth/8) |
||||
if incoming_payload_size > 0: |
||||
iq_samples_in = (buffer[1024:1024 + incoming_payload_size].view(dtype=np.complex64))\ |
||||
.reshape(self.iq_header.active_ant_chs, self.iq_header.cpi_length) |
||||
self.iq_samples = iq_samples_in.copy() # Must be .copy |
||||
|
||||
self.in_shmem_iface.send_ctr_buff_ready(active_buff_index) |
||||
|
||||
def receive_iq_frame(self): |
||||
""" |
||||
Called by the get_iq_online function. Receives IQ samples over the establed Ethernet connection |
||||
""" |
||||
total_received_bytes = 0 |
||||
recv_bytes_count = 0 |
||||
iq_header_bytes = bytearray(self.iq_header.header_size) # allocate array |
||||
view = memoryview(iq_header_bytes) # Get buffer |
||||
|
||||
self.logger.debug("Starting IQ header reception") |
||||
|
||||
while total_received_bytes < self.iq_header.header_size: |
||||
# Receive into buffer |
||||
recv_bytes_count = self.socket_inst.recv_into(view, self.iq_header.header_size-total_received_bytes) |
||||
view = view[recv_bytes_count:] # reset memory region |
||||
total_received_bytes += recv_bytes_count |
||||
|
||||
self.iq_header.decode_header(iq_header_bytes) |
||||
# Uncomment to check the content of the IQ header |
||||
#self.iq_header.dump_header() |
||||
|
||||
incoming_payload_size = self.iq_header.cpi_length*self.iq_header.active_ant_chs*2*int(self.iq_header.sample_bit_depth/8) |
||||
if incoming_payload_size > 0: |
||||
# Calculate total bytes to receive from the iq header data |
||||
total_bytes_to_receive = incoming_payload_size |
||||
receiver_buffer_size = 2**18 |
||||
|
||||
self.logger.debug("Total bytes to receive: {:d}".format(total_bytes_to_receive)) |
||||
|
||||
total_received_bytes = 0 |
||||
recv_bytes_count = 0 |
||||
iq_data_bytes = bytearray(total_bytes_to_receive + receiver_buffer_size) # allocate array |
||||
view = memoryview(iq_data_bytes) # Get buffer |
||||
|
||||
while total_received_bytes < total_bytes_to_receive: |
||||
# Receive into buffer |
||||
recv_bytes_count = self.socket_inst.recv_into(view, receiver_buffer_size) |
||||
view = view[recv_bytes_count:] # reset memory region |
||||
total_received_bytes += recv_bytes_count |
||||
|
||||
self.logger.debug(" IQ data succesfully received") |
||||
|
||||
# Convert raw bytes to Complex float64 IQ samples |
||||
self.iq_samples = np.frombuffer(iq_data_bytes[0:total_bytes_to_receive], dtype=np.complex64).reshape(self.iq_header.active_ant_chs, self.iq_header.cpi_length) |
||||
|
||||
self.iq_frame_bytes = bytearray()+iq_header_bytes+iq_data_bytes |
||||
return self.iq_samples |
||||
else: |
||||
return 0 |
||||
|
||||
def set_squelch_threshold(self, threshold_dB): |
||||
""" |
||||
Configures the threshold level of the squelch module in the DAQ FW through the control interface |
||||
""" |
||||
if self.receiver_connection_status: # Check connection |
||||
self.daq_squelch_th_dB = threshold_dB |
||||
if threshold_dB == -80: threshold = 0 |
||||
else: threshold = 10**(threshold_dB/20) |
||||
|
||||
# Assembling message |
||||
cmd="STHU" |
||||
th_bytes=pack("f",threshold) |
||||
msg_bytes=(cmd.encode()+th_bytes+bytearray(120)) |
||||
try: |
||||
_thread.start_new_thread(self.ctr_iface_communication, (msg_bytes,)) |
||||
except: |
||||
errorMsg = sys.exc_info()[0] |
||||
self.logger.error("Unable to start communication thread") |
||||
self.logger.error("Error message: {:s}".format(errorMsg)) |
||||
|
||||
def ctr_iface_init(self): |
||||
""" |
||||
Initialize connection with the DAQ FW through the control interface |
||||
""" |
||||
if self.receiver_connection_status: # Check connection |
||||
# Assembling message |
||||
cmd="INIT" |
||||
msg_bytes=(cmd.encode()+bytearray(124)) |
||||
try: |
||||
_thread.start_new_thread(self.ctr_iface_communication, (msg_bytes,)) |
||||
except: |
||||
errorMsg = sys.exc_info()[0] |
||||
self.logger.error("Unable to start communication thread") |
||||
self.logger.error("Error message: {:s}".format(errorMsg)) |
||||
|
||||
def ctr_iface_communication(self, msg_bytes): |
||||
""" |
||||
Handles communication on the control interface with the DAQ FW |
||||
|
||||
Parameters: |
||||
----------- |
||||
|
||||
:param: msg: Message bytes, that will be sent ont the control interface |
||||
:type: msg: Byte array |
||||
""" |
||||
self.ctr_iface_thread_lock.acquire() |
||||
self.logger.debug("Sending control message") |
||||
self.ctr_iface_socket.send(msg_bytes) |
||||
|
||||
# Waiting for the command to take effect |
||||
reply_msg_bytes = self.ctr_iface_socket.recv(128) |
||||
|
||||
self.logger.debug("Control interface communication finished") |
||||
self.ctr_iface_thread_lock.release() |
||||
|
||||
status = reply_msg_bytes[0:4].decode() |
||||
if status == "FNSD": |
||||
self.logger.info("Reconfiguration succesfully finished") |
||||
que_data_packet = [] |
||||
que_data_packet.append(['config-ok',]) |
||||
self.data_que.put(que_data_packet) |
||||
|
||||
else: |
||||
self.logger.error("Failed to set the requested parameter, reply: {0}".format(status)) |
||||
|
||||
def set_center_freq(self, center_freq): |
||||
""" |
||||
Configures the RF center frequency of the receiver through the control interface |
||||
|
||||
Paramters: |
||||
---------- |
||||
:param: center_freq: Required center frequency to set [Hz] |
||||
:type: center_freq: float |
||||
""" |
||||
if self.receiver_connection_status: # Check connection |
||||
self.daq_center_freq = int(center_freq) |
||||
# Set center frequency |
||||
cmd="FREQ" |
||||
freq_bytes=pack("Q",int(center_freq)) |
||||
msg_bytes=(cmd.encode()+freq_bytes+bytearray(116)) |
||||
try: |
||||
_thread.start_new_thread(self.ctr_iface_communication, (msg_bytes,)) |
||||
except: |
||||
errorMsg = sys.exc_info()[0] |
||||
self.logger.error("Unable to start communication thread") |
||||
self.logger.error("Error message: {:s}".format(errorMsg)) |
||||
|
||||
def set_if_gain(self, gain): |
||||
""" |
||||
Configures the IF gain of the receiver through the control interface |
||||
|
||||
Paramters: |
||||
---------- |
||||
:param: gain: IF gain value [dB] |
||||
:type: gain: int |
||||
""" |
||||
if self.receiver_connection_status: # Check connection |
||||
self.daq_rx_gain = gain |
||||
|
||||
# Set center frequency |
||||
cmd="GAIN" |
||||
gain_list=[297, 37] #[int(gain*10)]*self.M |
||||
gain_bytes=pack("I"*self.M, *gain_list) |
||||
msg_bytes=(cmd.encode()+gain_bytes+bytearray(128-(self.M+1)*4)) |
||||
try: |
||||
_thread.start_new_thread(self.ctr_iface_communication, (msg_bytes,)) |
||||
except: |
||||
errorMsg = sys.exc_info()[0] |
||||
self.logger.error("Unable to start communication thread") |
||||
self.logger.error("Error message: {:s}".format(errorMsg)) |
||||
|
||||
def close(self): |
||||
""" |
||||
Disconnet the receiver module and the DAQ FW |
||||
""" |
||||
self.eth_close() |
||||
|
@ -0,0 +1,195 @@ |
||||
""" |
||||
HeIMDALL DAQ Firmware |
||||
Python based shared memory interface implementations |
||||
|
||||
Author: Tamás Pető |
||||
License: GNU GPL V3 |
||||
|
||||
This program 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 of the License, or |
||||
any later version. |
||||
|
||||
This program 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 program. If not, see <https://www.gnu.org/licenses/>. |
||||
""" |
||||
import logging |
||||
from struct import pack, unpack |
||||
from multiprocessing import shared_memory |
||||
import numpy as np |
||||
import os |
||||
|
||||
A_BUFF_READY = 1 |
||||
B_BUFF_READY = 2 |
||||
INIT_READY = 10 |
||||
TERMINATE = 255 |
||||
class outShmemIface(): |
||||
|
||||
|
||||
def __init__(self, shmem_name, shmem_size, drop_mode = False): |
||||
|
||||
self.init_ok = True |
||||
logging.basicConfig(level=logging.INFO) |
||||
self.logger = logging.getLogger(__name__) |
||||
self.drop_mode = drop_mode |
||||
self.dropped_frame_cntr = 0 |
||||
|
||||
self.shmem_name = shmem_name |
||||
self.buffer_free = [True, True] |
||||
|
||||
self.memories = [] |
||||
self.buffers = [] |
||||
|
||||
# Try to remove shared memories if already exist |
||||
try: |
||||
shmem_A = shared_memory.SharedMemory(name=shmem_name+'_A',create=False, size=shmem_size) |
||||
shmem_A.close() |
||||
#shmem_A.unkink() |
||||
except FileNotFoundError as err: |
||||
self.logger.warning("Shared memory not exist") |
||||
try: |
||||
shmem_B = shared_memory.SharedMemory(name=shmem_name+'_B',create=False, size=shmem_size) |
||||
shmem_B.close() |
||||
#shmem_B.unkink() |
||||
except FileNotFoundError as err: |
||||
self.logger.warning("Shared memory not exist") |
||||
|
||||
# Create the shared memories |
||||
self.memories.append(shared_memory.SharedMemory(name=shmem_name+'_A',create=True, size=shmem_size)) |
||||
self.memories.append(shared_memory.SharedMemory(name=shmem_name+'_B',create=True, size=shmem_size)) |
||||
self.buffers.append(np.ndarray((shmem_size,), dtype=np.uint8, buffer=self.memories[0].buf)) |
||||
self.buffers.append(np.ndarray((shmem_size,), dtype=np.uint8, buffer=self.memories[1].buf)) |
||||
|
||||
# Opening control FIFOs |
||||
if self.drop_mode: |
||||
bw_fifo_flags = os.O_RDONLY | os.O_NONBLOCK |
||||
else: |
||||
bw_fifo_flags = os.O_RDONLY |
||||
try: |
||||
self.fw_ctr_fifo = os.open('_data_control/'+'fw_'+shmem_name, os.O_WRONLY) |
||||
self.bw_ctr_fifo = os.open('_data_control/'+'bw_'+shmem_name, bw_fifo_flags) |
||||
except OSError as err: |
||||
self.logger.critical("OS error: {0}".format(err)) |
||||
self.logger.critical("Failed to open control fifos") |
||||
self.bw_ctr_fifo = None |
||||
self.fw_ctr_fifo = None |
||||
self.init_ok = False |
||||
|
||||
# Send init ready signal |
||||
if self.init_ok: |
||||
os.write(self.fw_ctr_fifo, pack('B',INIT_READY)) |
||||
|
||||
def send_ctr_buff_ready(self, active_buffer_index): |
||||
# Send buffer ready signal on the forward FIFO |
||||
if active_buffer_index == 0: |
||||
os.write(self.fw_ctr_fifo, pack('B',A_BUFF_READY)) |
||||
elif active_buffer_index == 1: |
||||
os.write(self.fw_ctr_fifo, pack('B',B_BUFF_READY)) |
||||
|
||||
# Deassert buffer free flag |
||||
self.buffer_free[active_buffer_index] = False |
||||
|
||||
def send_ctr_terminate(self): |
||||
os.write(self.fw_ctr_fifo, pack('B',TERMINATE)) |
||||
self.logger.info("Terminate signal sent") |
||||
|
||||
def destory_sm_buffer(self): |
||||
for memory in self.memories: |
||||
memory.close() |
||||
memory.unlink() |
||||
|
||||
if self.fw_ctr_fifo is not None: |
||||
os.close(self.fw_ctr_fifo) |
||||
|
||||
if self.bw_ctr_fifo is not None: |
||||
os.close(self.bw_ctr_fifo) |
||||
|
||||
def wait_buff_free(self): |
||||
if self.buffer_free[0]: |
||||
return 0 |
||||
elif self.buffer_free[1]: |
||||
return 1 |
||||
else: |
||||
try: |
||||
buffer = os.read(self.bw_ctr_fifo, 1) |
||||
signal = unpack('B', buffer )[0] |
||||
|
||||
if signal == A_BUFF_READY: |
||||
self.buffer_free[0] = True |
||||
return 0 |
||||
if signal == B_BUFF_READY: |
||||
self.buffer_free[1] = True |
||||
return 1 |
||||
except BlockingIOError as err: |
||||
self.dropped_frame_cntr +=1 |
||||
self.logger.warning("Dropping frame.. Total: [{:d}] ".format(self.dropped_frame_cntr)) |
||||
return 3 |
||||
return -1 |
||||
|
||||
|
||||
class inShmemIface(): |
||||
|
||||
def __init__(self, shmem_name, ctr_fifo_path="_data_control/"): |
||||
|
||||
self.init_ok = True |
||||
logging.basicConfig(level=logging.INFO) |
||||
self.logger = logging.getLogger(__name__) |
||||
self.drop_mode = False |
||||
|
||||
self.shmem_name = shmem_name |
||||
|
||||
self.memories = [] |
||||
self.buffers = [] |
||||
try: |
||||
self.fw_ctr_fifo = os.open(ctr_fifo_path+'fw_'+shmem_name, os.O_RDONLY) |
||||
self.bw_ctr_fifo = os.open(ctr_fifo_path+'bw_'+shmem_name, os.O_WRONLY) |
||||
except OSError as err: |
||||
self.logger.critical("OS error: {0}".format(err)) |
||||
self.logger.critical("Failed to open control fifos") |
||||
self.bw_ctr_fifo = None |
||||
self.fw_ctr_fifo = None |
||||
self.init_ok = False |
||||
|
||||
if self.fw_ctr_fifo is not None: |
||||
if unpack('B', os.read(self.fw_ctr_fifo, 1))[0] == INIT_READY: |
||||
self.memories.append(shared_memory.SharedMemory(name=shmem_name+'_A')) |
||||
self.memories.append(shared_memory.SharedMemory(name=shmem_name+'_B')) |
||||
self.buffers.append(np.ndarray((self.memories[0].size,), |
||||
dtype=np.uint8, |
||||
buffer=self.memories[0].buf)) |
||||
self.buffers.append(np.ndarray((self.memories[1].size,), |
||||
dtype=np.uint8, |
||||
buffer=self.memories[1].buf)) |
||||
else: |
||||
self.init_ok = False |
||||
|
||||
def send_ctr_buff_ready(self, active_buffer_index): |
||||
if active_buffer_index == 0: |
||||
os.write(self.bw_ctr_fifo, pack('B',A_BUFF_READY)) |
||||
elif active_buffer_index == 1: |
||||
os.write(self.bw_ctr_fifo, pack('B',B_BUFF_READY)) |
||||
|
||||
def destory_sm_buffer(self): |
||||
for memory in self.memories: |
||||
memory.close() |
||||
|
||||
if self.fw_ctr_fifo is not None: |
||||
os.close(self.fw_ctr_fifo) |
||||
|
||||
if self.bw_ctr_fifo is not None: |
||||
os.close(self.bw_ctr_fifo) |
||||
|
||||
def wait_buff_free(self): |
||||
signal = unpack('B', os.read(self.fw_ctr_fifo, 1))[0] |
||||
if signal == A_BUFF_READY: |
||||
return 0 |
||||
elif signal == B_BUFF_READY: |
||||
return 1 |
||||
elif signal == TERMINATE: |
||||
return TERMINATE |
||||
return -1 |
@ -0,0 +1,713 @@ |
||||
# KrakenSDR Signal Processor |
||||
# |
||||
# Copyright (C) 2018-2021 Carl Laufer, Tamás Pető |
||||