Merge pull request #1 from AresValley/space_weather

Space weather
This commit is contained in:
Alessandro Ceccato
2019-04-25 20:00:35 +02:00
committed by GitHub
22 changed files with 3429 additions and 5540 deletions

4
.gitignore vendored
View File

@@ -5,9 +5,7 @@ wav_converter.py
to_do.txt
csv_info.txt
pyinstaller_cmd.txt
icons_imgs
TestData
themes/.current_theme
*.bat
*.sh
splash.jpg
.vscode/

View File

@@ -1,6 +1,5 @@
from collections import namedtuple
from functools import partial
from glob import glob
import webbrowser
import os
import sys
@@ -14,7 +13,6 @@ from PyQt5.QtWidgets import (QMainWindow,
QListWidgetItem,
QMessageBox,
QSplashScreen,
QTreeView,
QTreeWidgetItem,)
from PyQt5.QtGui import QPixmap
from PyQt5 import uic
@@ -23,10 +21,9 @@ from PyQt5.QtCore import (QFileInfo,
pyqtSlot,)
from audio_player import AudioPlayer
from double_text_button import DoubleTextButton
from space_weather_data import SpaceWeatherData
from download_window import DownloadWindow
from switchable_label import SwitchableLabelsIterable
from constants import (Constants,
Ftype,
GfdType,
@@ -35,7 +32,6 @@ from constants import (Constants,
Messages,
Signal,)
from themes import Theme
from utilities import (checksum_ok,
uncheck_and_emit,
pop_up,
@@ -43,11 +39,10 @@ from utilities import (checksum_ok,
filters_ok,
is_undef_freq,
is_undef_band,
change_unit,
format_numbers,
resource_path,)
import icon_rc
# import icon_rc
qt_creator_file = resource_path("artemis.ui")
Ui_MainWindow, _ = uic.loadUiType(qt_creator_file)
@@ -59,6 +54,7 @@ class Artemis(QMainWindow, Ui_MainWindow):
self.setupUi(self)
self.set_initial_size()
self.download_window = DownloadWindow()
self.download_window.complete.connect(self.show_downloaded_signals)
self.actionExit.triggered.connect(qApp.quit)
self.action_update_database.triggered.connect(self.ask_if_download)
self.action_check_db_ver.triggered.connect(self.check_db_ver)
@@ -66,6 +62,66 @@ class Artemis(QMainWindow, Ui_MainWindow):
self.current_signal_name = ''
self.signal_names = []
self.total_signals = 0
self.switchable_r_labels = SwitchableLabelsIterable(self.r0_now_lbl,
self.r1_now_lbl,
self.r2_now_lbl,
self.r3_now_lbl,
self.r4_now_lbl,
self.r5_now_lbl,)
self.switchable_s_labels = SwitchableLabelsIterable(self.s0_now_lbl,
self.s1_now_lbl,
self.s2_now_lbl,
self.s3_now_lbl,
self.s4_now_lbl,
self.s5_now_lbl,)
self.switchable_g_now_labels = SwitchableLabelsIterable(self.g0_now_lbl,
self.g1_now_lbl,
self.g2_now_lbl,
self.g3_now_lbl,
self.g4_now_lbl,
self.g5_now_lbl)
self.switchable_g_today_labels = SwitchableLabelsIterable(self.g0_today_lbl,
self.g1_today_lbl,
self.g2_today_lbl,
self.g3_today_lbl,
self.g4_today_lbl,
self.g5_today_lbl)
self.k_storm_labels = SwitchableLabelsIterable(self.k_ex_sev_storm_lbl,
self.k_very_sev_storm_lbl,
self.k_sev_storm_lbl,
self.k_maj_storm_lbl,
self.k_min_storm_lbl,
self.k_active_lbl,
self.k_unsettled_lbl,
self.k_quiet_lbl,
self.k_very_quiet_lbl,
self.k_inactive_lbl)
self.a_storm_labels = SwitchableLabelsIterable(self.a_sev_storm_lbl,
self.a_maj_storm_lbl,
self.a_min_storm_lbl,
self.a_active_lbl,
self.a_unsettled_lbl,
self.a_quiet_lbl)
self.forecast_labels = (self.forecast_lbl_0,
self.forecast_lbl_1,
self.forecast_lbl_2,
self.forecast_lbl_3,
self.forecast_lbl_4,
self.forecast_lbl_5,
self.forecast_lbl_6,
self.forecast_lbl_7,
self.forecast_lbl_8)
for lab in self.forecast_labels:
lab.set_default_stylesheet()
self.forecast_label_container.labels = self.forecast_labels
self.theme = Theme(self)
# Manage frequency filters.
@@ -346,13 +402,10 @@ class Artemis(QMainWindow, Ui_MainWindow):
# ##########################################################################################
# self.load_db()
# Left list widget and search bar.
self.search_bar.textChanged.connect(self.display_signals)
self.result_list.currentItemChanged.connect(self.display_specs)
self.result_list.itemDoubleClicked.connect(lambda: self.main_tab.setCurrentWidget(self.signal_properties_tab))
# self.display_signals()
self.audio_widget = AudioPlayer(self.play,
self.pause,
self.stop,
@@ -376,26 +429,186 @@ class Artemis(QMainWindow, Ui_MainWindow):
BandLabel(self.ehf_left, self.ehf, self.ehf_right),
]
# Space weather
self.info_now_btn.clicked.connect(lambda : webbrowser.open(Constants.FORECAST_INFO))
self.update_now_bar.clicked.connect(self.start_update_space_weather)
self.update_now_bar.set_idle()
self.space_weather_data = SpaceWeatherData()
self.space_weather_data.update_complete.connect(self.update_space_weather)
# Final operations.
self.theme.initialize()
self.load_db()
self.display_signals()
self.show()
@pyqtSlot()
def start_update_space_weather(self):
if not self.space_weather_data.is_updating:
self.update_now_bar.set_updating()
self.space_weather_data.update()
@pyqtSlot(bool)
def update_space_weather(self, status_ok):
self.update_now_bar.set_idle()
if status_ok:
xray_long = float(self.space_weather_data.xray[-1][7])
format_text = lambda letter, power : letter + f"{xray_long * 10**power:.1f}"
if xray_long < 1e-8 and xray_long != -1.00e+05:
self.peak_flux_lbl.setText(format_text("<A", 8))
elif xray_long >= 1e-8 and xray_long < 1e-7:
self.peak_flux_lbl.setText(format_text("A", 8))
elif xray_long >= 1e-7 and xray_long < 1e-6:
self.peak_flux_lbl.setText(format_text("B", 7))
elif xray_long >= 1e-6 and xray_long < 1e-5:
self.peak_flux_lbl.setText(format_text("C", 6))
elif xray_long >= 1e-5 and xray_long < 1e-4:
self.peak_flux_lbl.setText(format_text("M", 5))
elif xray_long >= 1e-4:
self.peak_flux_lbl.setText(format_text("X", 4))
elif xray_long == -1.00e+05:
self.peak_flux_lbl.setText("No Data")
if xray_long < 1e-5 and xray_long != -1.00e+05:
self.switchable_r_labels.switch_on(self.r0_now_lbl)
elif xray_long >= 1e-5 and xray_long < 5e-5:
self.switchable_r_labels.switch_on(self.r1_now_lbl)
elif xray_long >= 5e-5 and xray_long < 1e-4:
self.switchable_r_labels.switch_on(self.r2_now_lbl)
elif xray_long >= 1e-4 and xray_long < 1e-3:
self.switchable_r_labels.switch_on(self.r3_now_lbl)
elif xray_long >= 1e-3 and xray_long < 2e-3:
self.switchable_r_labels.switch_on(self.r4_now_lbl)
elif xray_long >= 2e-3:
self.switchable_r_labels.switch_on(self.r5_now_lbl)
elif xray_long == -1.00e+05:
self.switchable_r_labels.switch_off_all()
pro10 = float(self.space_weather_data.prot_el[-1][8])
if pro10 < 10 and pro10 != -1.00e+05:
self.switchable_s_labels.switch_on(self.s0_now_lbl)
elif pro10 >= 10 and pro10 < 100:
self.switchable_s_labels.switch_on(self.s1_now_lbl)
elif pro10 >= 100 and pro10 < 1000:
self.switchable_s_labels.switch_on(self.s2_now_lbl)
elif pro10 >= 1000 and pro10 < 10000:
self.switchable_s_labels.switch_on(self.s3_now_lbl)
elif pro10 >= 10000 and pro10 < 100000:
self.switchable_s_labels.switch_on(self.s4_now_lbl)
elif pro10 >= 100000:
self.switchable_s_labels.switch_on(self.s5_now_lbl)
elif pro10 == -1.00e+05:
self.switchable_s_labels.switch_off_all()
k_index = int(self.space_weather_data.ak_index[8][11].replace('.', ''))
self.k_index_lbl.setText(str(k_index))
a_index = int(self.space_weather_data.ak_index[7][7].replace('.', ''))
self.a_index_lbl.setText(str(a_index))
if k_index == 0:
self.switchable_g_now_labels.switch_on(self.g0_now_lbl)
self.k_storm_labels.switch_on(self.k_inactive_lbl)
elif k_index == 1:
self.switchable_g_now_labels.switch_on(self.g0_now_lbl)
self.k_storm_labels.switch_on(self.k_very_quiet_lbl)
elif k_index == 2:
self.switchable_g_now_labels.switch_on(self.g0_now_lbl)
self.k_storm_labels.switch_on(self.k_quiet_lbl)
elif k_index == 3:
self.switchable_g_now_labels.switch_on(self.g0_now_lbl)
self.k_storm_labels.switch_on(self.k_unsettled_lbl)
elif k_index == 4:
self.switchable_g_now_labels.switch_on(self.g0_now_lbl)
self.k_storm_labels.switch_on(self.k_active_lbl)
elif k_index == 5:
self.switchable_g_now_labels.switch_on(self.g1_now_lbl)
self.k_storm_labels.switch_on(self.k_min_storm_lbl)
elif k_index == 6:
self.switchable_g_now_labels.switch_on(self.g2_now_lbl)
self.k_storm_labels.switch_on(self.k_maj_storm_lbl)
elif k_index == 7:
self.switchable_g_now_labels.switch_on(self.g3_now_lbl)
self.k_storm_labels.switch_on(self.k_sev_storm_lbl)
elif k_index == 8:
self.switchable_g_now_labels.switch_on(self.g4_now_lbl)
self.k_storm_labels.switch_on(self.k_very_sev_storm_lbl)
elif k_index == 9:
self.switchable_g_now_labels.switch_on(self.g5_now_lbl)
self.k_storm_labels.switch_on(self.k_ex_sev_storm_lbl)
if a_index >= 0 and a_index < 8:
self.a_storm_labels.switch_on(self.a_quiet_lbl)
elif a_index >= 8 and a_index < 16:
self.a_storm_labels.switch_on(self.a_unsettled_lbl)
elif a_index >= 16 and a_index < 30:
self.a_storm_labels.switch_on(self.a_active_lbl)
elif a_index >= 30 and a_index < 50:
self.a_storm_labels.switch_on(self.a_min_storm_lbl)
elif a_index >= 50 and a_index < 100:
self.a_storm_labels.switch_on(self.a_maj_storm_lbl)
elif a_index >= 100 and a_index < 400:
self.a_storm_labels.switch_on(self.a_sev_storm_lbl)
index = self.space_weather_data.geo_storm[6].index("was") + 1
k_index_24_hmax = int(self.space_weather_data.geo_storm[6][index])
if k_index_24_hmax == 0:
self.switchable_g_today_labels.switch_on(self.g0_today_lbl)
self.expected_noise_lbl.setText(" S0 - S1 (<-120 dBm) ")
elif k_index_24_hmax == 1:
self.switchable_g_today_labels.switch_on(self.g0_today_lbl)
self.expected_noise_lbl.setText(" S0 - S1 (<-120 dBm) ")
elif k_index_24_hmax == 2:
self.switchable_g_today_labels.switch_on(self.g0_today_lbl)
self.expected_noise_lbl.setText(" S1 - S2 (-115 dBm) ")
elif k_index_24_hmax == 3:
self.switchable_g_today_labels.switch_on(self.g0_today_lbl)
self.expected_noise_lbl.setText(" S2 - S3 (-110 dBm) ")
elif k_index_24_hmax == 4:
self.switchable_g_today_labels.switch_on(self.g0_today_lbl)
self.expected_noise_lbl.setText(" S3 - S4 (-100 dBm) ")
elif k_index_24_hmax == 5:
self.switchable_g_today_labels.switch_on(self.g1_today_lbl)
self.expected_noise_lbl.setText(" S4 - S6 (-90 dBm) ")
elif k_index_24_hmax == 6:
self.switchable_g_today_labels.switch_on(self.g2_today_lbl)
self.expected_noise_lbl.setText(" S6 - S9 (-80 dBm) ")
elif k_index_24_hmax == 7:
self.switchable_g_today_labels.switch_on(self.g3_today_lbl)
self.expected_noise_lbl.setText(" S9 - S20 (>-60 dBm) ")
elif k_index_24_hmax == 8:
self.switchable_g_today_labels.switch_on(self.g4_today_lbl)
self.expected_noise_lbl.setText(" S20 - S30 (>-60 dBm) ")
elif k_index_24_hmax == 9:
self.switchable_g_today_labels.switch_on(self.g5_today_lbl)
self.expected_noise_lbl.setText(" S30+ (>>-60 dBm) ")
self.expected_noise_lbl.switch_on()
val = int(self.space_weather_data.ak_index[7][2].replace('.', ''))
self.sfi_lbl.setText(f"{val}")
val = int([x[4] for x in self.space_weather_data.sgas if "SSN" in x][0])
self.sn_lbl.setText(f"{val:d}")
for label, pixmap in zip(self.forecast_labels, self.space_weather_data.images):
label.pixmap = pixmap
label.make_transparent()
label.apply_pixmap()
else:
pop_up(self, title = Messages.BAD_DOWNLOAD,
text = Messages.BAD_DOWNLOAD_MSG).show()
self.space_weather_data.remove_data()
@pyqtSlot()
def go_to_gfd(self, by):
query = "/?q="
if by == GfdType.FREQ:
if by is GfdType.FREQ:
value_in_mhz = self.freq_gfd.value() * Constants.CONVERSION_FACTORS[self.unit_freq_gfd.currentText()] / Constants.CONVERSION_FACTORS["MHz"]
query += str(value_in_mhz)
elif by == GfdType.LOC:
elif by is GfdType.LOC:
query += self.gfd_line_edit.text()
try:
webbrowser.open(Constants.GFD_SITE + query.lower())
except:
pass
@pyqtSlot(QListWidgetItem)
def remove_if_unselected_modulation(self, item):
if not item.isSelected():
@@ -417,7 +630,7 @@ class Artemis(QMainWindow, Ui_MainWindow):
def show_matching_strings(self, list_elements, text):
for index in range(list_elements.count()):
item = list_elements.item(index)
if text.upper() in item.text() or item.isSelected():
if text.lower() in item.text().lower() or item.isSelected():
item.setHidden(False)
else:
item.setHidden(True)
@@ -441,10 +654,8 @@ class Artemis(QMainWindow, Ui_MainWindow):
item.child(i).setSelected(True)
def set_initial_size(self):
"""
Function to handle high resolution screens. The function sets bigger sizes
for all the relevant fixed-size widgets.
"""
"""Function to handle high resolution screens. The function sets bigger sizes
for all the relevant fixed-size widgets."""
d = QDesktopWidget().availableGeometry()
w = d.width()
h = d.height()
@@ -492,7 +703,6 @@ class Artemis(QMainWindow, Ui_MainWindow):
@pyqtSlot()
def download_db(self):
if not self.download_window.isVisible():
self.download_window.download_thread.finished.connect(self.show_downloaded_signals)
self.download_window.download_thread.start()
self.download_window.show()
@@ -536,6 +746,8 @@ class Artemis(QMainWindow, Ui_MainWindow):
text = Messages.NO_DB_AVAIL,
informative_text = Messages.DOWNLOAD_NOW_QUESTION,
is_question = True).exec()
if answer == QMessageBox.Yes:
self.download_db()
else:
try:
is_checksum_ok = checksum_ok(db, ChecksumWhat.DB)
@@ -546,7 +758,6 @@ class Artemis(QMainWindow, Ui_MainWindow):
if is_checksum_ok:
pop_up(self, title = Messages.DB_UP_TO_DATE,
text = Messages.DB_UP_TO_DATE_MSG).show()
else:
answer = pop_up(self, title = Messages.DB_NEW_VER,
text = Messages.DB_NEW_VER_MSG,
@@ -557,10 +768,9 @@ class Artemis(QMainWindow, Ui_MainWindow):
@pyqtSlot()
def show_downloaded_signals(self):
if self.download_window.everything_ok:
self.search_bar.setEnabled(True)
self.load_db()
self.display_signals()
self.search_bar.setEnabled(True)
self.load_db()
self.display_signals()
def load_db(self):
names = Database.NAMES
@@ -585,7 +795,9 @@ class Artemis(QMainWindow, Ui_MainWindow):
self.db.fillna(Constants.UNKNOWN, inplace = True)
self.db[Signal.WIKI_CLICKED] = False
self.update_status_tip(self.total_signals)
self.result_list.clear()
self.result_list.addItems(self.signal_names)
self.result_list.setCurrentItem(None)
@pyqtSlot()
def set_min_value_upper_limit(self, lower_combo_box,
@@ -599,7 +811,6 @@ class Artemis(QMainWindow, Ui_MainWindow):
lower_units = lower_combo_box.currentText()
upper_units = upper_combo_box.currentText()
lower_value = lower_spin_box.value()
upper_value = upper_spin_box.value()
inf_limit = (lower_value * Constants.CONVERSION_FACTORS[lower_units]) \
// Constants.CONVERSION_FACTORS[upper_units]
counter = 0
@@ -703,7 +914,7 @@ class Artemis(QMainWindow, Ui_MainWindow):
else:
self.result_list.item(index).setHidden(True)
# Remove selected item.
self.result_list.selectionModel().clear()
self.result_list.setCurrentItem(None)
self.update_status_tip(available_signals)
def update_status_tip(self, available_signals):
@@ -756,8 +967,15 @@ class Artemis(QMainWindow, Ui_MainWindow):
@pyqtSlot()
def reset_mode_filters(self):
uncheck_and_emit(self.apply_remove_mode_filter_btn)
parents = Constants.MODES.keys()
selected_children = []
for item in self.mode_tree_widget.selectedItems():
item.setSelected(False)
if item.text(0) in parents:
item.setSelected(False)
else:
selected_children.append(item)
for children in selected_children:
children.setSelected(False)
if self.include_unknown_modes_btn.isChecked():
self.include_unknown_modes_btn.setChecked(False)
@@ -877,7 +1095,6 @@ class Artemis(QMainWindow, Ui_MainWindow):
selected_items = [item for item in self.mode_tree_widget.selectedItems()]
selected_items_text = [i.text(0) for i in selected_items]
parents = [item for item in selected_items_text if item in Constants.MODES.keys()]
children = [item for item in selected_items_text if item not in parents]
ok = []
for item in selected_items:
if item.text(0) in parents:
@@ -1042,11 +1259,11 @@ class Artemis(QMainWindow, Ui_MainWindow):
if __name__ == '__main__':
my_app = QApplication(sys.argv)
img = QPixmap(":/icons/Artemis3.500px.png")
# img = img.scaled(600, 600, aspectRatioMode = Qt.KeepAspectRatio)
splash = QSplashScreen(img)
splash.show()
sleep(2)
w = Artemis()
splash.finish(w)
# img = QPixmap(":/icons/Artemis3.500px.png")
# splash = QSplashScreen(img)
# splash.show()
# sleep(2)
artemis = Artemis()
artemis.show()
# splash.finish(w)
sys.exit(my_app.exec_())

3131
artemis.ui

File diff suppressed because it is too large Load Diff

31
clickable_progress_bar.py Normal file
View File

@@ -0,0 +1,31 @@
from PyQt5.QtWidgets import QProgressBar
from PyQt5.QtCore import Qt, pyqtSignal
from constants import Constants
class ClickableProgressBar(QProgressBar):
clicked = pyqtSignal()
def __init__(self, parent = None):
self.__text = ''
super().__init__(parent)
def __set_text(self, text):
self.__text = text
def text(self):
return self.__text
def set_idle(self):
self.__set_text(Constants.CLICK_TO_UPDATE_STR)
self.setMaximum(self.minimum() + 1)
def set_updating(self):
self.__set_text(Constants.UPDATING_STR)
self.setMaximum(self.minimum())
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.clicked.emit()
else:
super().mousePressEvent(event)

View File

@@ -67,50 +67,70 @@ class Database(object):
Signal.CATEGORY_CODE,)
class Constants(object):
ACF_DOCS = "https://aresvalley.com/documentation/"
SEARCH_LABEL_IMG = "search_icon.png"
VOLUME_LABEL_IMG = "volume.png"
DATA_FOLDER = "Data"
SPECTRA_FOLDER = "Spectra"
SPECTRA_EXT = ".png"
AUDIO_FOLDER = "Audio"
ACTIVE = "active"
INACTIVE = "inactive"
NOT_AVAILABLE = "spectrumnotavailable.png"
NOT_SELECTED = "nosignalselected.png"
__Band = namedtuple("Band", ["lower", "upper"])
__ELF = __Band(0, 30) # Formally it is (3, 30) Hz.
__SLF = __Band(30, 300)
__ULF = __Band(300, 3000)
__VLF = __Band(3000, 30000)
__LF = __Band(30 * 10**3, 300 * 10**3)
__MF = __Band(300 * 10 ** 3, 3000 * 10**3)
__HF = __Band(3 * 10**6, 30 * 10**6)
__VHF = __Band(30 * 10**6, 300 * 10**6)
__UHF = __Band(300 * 10**6, 3000 * 10**6)
__SHF = __Band(3 * 10**9, 30 * 10**9)
__EHF = __Band(30 * 10**9, 300 * 10**9)
BANDS = (__ELF, __SLF, __ULF, __VLF, __LF, __MF, __HF, __VHF, __UHF, __SHF, __EHF)
MAX_DIGITS = 3
RANGE_SEPARATOR = ' ÷ '
GFD_SITE = "http://qrg.globaltuners.com/"
CONVERSION_FACTORS = {"Hz" : 1,
"kHz": 1000,
"MHz": 1000000,
"GHz": 1000000000}
MODES = {"FM": ("NFM", "WFM"),
"AM": (),
"CW": (),
"SK": ("FSK", "PSK", "MSK"),
"SB": ("LSB", "USB", "DSB"),
"Chirp Spread Spectrum": (),
"FHSS-TDM": (),
"RAW": (),
"SC-FDMA": (),}
APPLY = "Apply"
REMOVE = "Remove"
UNKNOWN = "N/A"
MODULATIONS = ("8VSB",
CLICK_TO_UPDATE_STR = "Click to update"
UPDATING_STR = "Updating..."
ACF_DOCS = "https://aresvalley.com/documentation/"
FORECAST_XRAY = "https://services.swpc.noaa.gov/text/goes-xray-flux-primary.txt"
FORECAST_PROT = "https://services.swpc.noaa.gov/text/goes-particle-flux-primary.txt"
FORECAST_AK_IND = "https://services.swpc.noaa.gov/text/wwv.txt"
FORECAST_SGAS = "https://services.swpc.noaa.gov/text/sgas.txt"
FORECAST_G = "https://services.swpc.noaa.gov/text/3-day-forecast.txt"
FORECAST_INFO = "https://www.swpc.noaa.gov/sites/default/files/images/NOAAscales.pdf"
FORECAST_IMG_0 = "http://www.mmmonvhf.de/eme/eme.png"
FORECAST_IMG_1 = "http://www.mmmonvhf.de/ms/ms.png"
FORECAST_IMG_2 = "http://www.mmmonvhf.de/es/es.png"
FORECAST_IMG_3 = "http://www.mmmonvhf.de/solar/solar.png"
FORECAST_IMG_4 = "http://amunters.home.xs4all.nl/eskipstatusNA.gif"
FORECAST_IMG_5 = "http://amunters.home.xs4all.nl/aurorastatus.gif"
FORECAST_IMG_6 = "http://amunters.home.xs4all.nl/eskipstatus.gif"
FORECAST_IMG_7 = "http://amunters.home.xs4all.nl/eskip50status.gif"
FORECAST_IMG_8 = "http://amunters.home.xs4all.nl/eskip70status.gif"
SEARCH_LABEL_IMG = "search_icon.png"
VOLUME_LABEL_IMG = "volume.png"
DATA_FOLDER = "Data"
SPECTRA_FOLDER = "Spectra"
SPECTRA_EXT = ".png"
AUDIO_FOLDER = "Audio"
ACTIVE = "active"
INACTIVE = "inactive"
LABEL_ON_COLOR = "on"
LABEL_OFF_COLOR = "off"
TEXT_COLOR = "text"
NOT_AVAILABLE = "spectrumnotavailable.png"
NOT_SELECTED = "nosignalselected.png"
__Band = namedtuple("Band", ["lower", "upper"])
__ELF = __Band(0, 30) # Formally it is (3, 30) Hz.
__SLF = __Band(30, 300)
__ULF = __Band(300, 3000)
__VLF = __Band(3000, 30000)
__LF = __Band(30 * 10**3, 300 * 10**3)
__MF = __Band(300 * 10 ** 3, 3000 * 10**3)
__HF = __Band(3 * 10**6, 30 * 10**6)
__VHF = __Band(30 * 10**6, 300 * 10**6)
__UHF = __Band(300 * 10**6, 3000 * 10**6)
__SHF = __Band(3 * 10**9, 30 * 10**9)
__EHF = __Band(30 * 10**9, 300 * 10**9)
BANDS = (__ELF, __SLF, __ULF, __VLF, __LF, __MF, __HF, __VHF, __UHF, __SHF, __EHF)
MAX_DIGITS = 3
RANGE_SEPARATOR = ' ÷ '
GFD_SITE = "http://qrg.globaltuners.com/"
CONVERSION_FACTORS = {"Hz" : 1,
"kHz": 1000,
"MHz": 1000000,
"GHz": 1000000000}
MODES = {"FM": ("NFM", "WFM"),
"AM": (),
"CW": (),
"SK": ("FSK", "PSK", "MSK"),
"SB": ("LSB", "USB", "DSB"),
"Chirp Spread Spectrum": (),
"FHSS-TDM": (),
"RAW": (),
"SC-FDMA": (),}
APPLY = "Apply"
REMOVE = "Remove"
UNKNOWN = "N/A"
MODULATIONS = ("8VSB",
"AFSK",
"AM",
"BFSK",
@@ -135,34 +155,34 @@ class Constants(object):
"PSK",
"QAM",
"TDMA",)
LOCATIONS = (UNKNOWN,
"Australia",
"Canada",
"Central Europe",
"China",
"Cyprus",
"Eastern Europe",
"Europe",
"Europe, japan and Asia",
"Exmouth, Australia",
"Finland",
"France",
"Germany",
"Home Base Mobile , AL",
"Hungary",
"Iran",
"Israel",
"Japan",
"LaMour, North Dakota",
"Lualualei, Hawaii",
"North America",
"North Korea",
"Poland",
"Romania",
"Ruda, Sweden",
"UK",
"United Kingdom",
"United States",
"Varberg, Sweden",
"World Wide",
"Worldwide",)
LOCATIONS = (UNKNOWN,
"Australia",
"Canada",
"Central Europe",
"China",
"Cyprus",
"Eastern Europe",
"Europe",
"Europe, japan and Asia",
"Exmouth, Australia",
"Finland",
"France",
"Germany",
"Home Base Mobile , AL",
"Hungary",
"Iran",
"Israel",
"Japan",
"LaMour, North Dakota",
"Lualualei, Hawaii",
"North America",
"North Korea",
"Poland",
"Romania",
"Ruda, Sweden",
"UK",
"United Kingdom",
"United States",
"Varberg, Sweden",
"World Wide",
"Worldwide",)

View File

@@ -1,5 +1,5 @@
from PyQt5 import uic
from PyQt5.QtCore import Qt, pyqtSlot
from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal
from PyQt5.QtWidgets import QWidget
from threads import DownloadThread, ThreadStatus
from utilities import pop_up, resource_path
@@ -8,6 +8,9 @@ from constants import Messages
Ui_Download_window, _ = uic.loadUiType(resource_path("download_db_window.ui"))
class DownloadWindow(QWidget, Ui_Download_window):
complete = pyqtSignal()
def __init__(self):
super().__init__()
self.setupUi(self)
@@ -18,7 +21,6 @@ class DownloadWindow(QWidget, Ui_Download_window):
Qt.WindowCloseButtonHint #|
# Qt.WindowStaysOnTopHint
)
self.everything_ok = True
self.no_internet_msg = pop_up(self, title = Messages.NO_CONNECTION,
text = Messages.NO_CONNECTION_MSG,
@@ -30,17 +32,8 @@ class DownloadWindow(QWidget, Ui_Download_window):
self.download_thread = DownloadThread()
self.download_thread.finished.connect(self.wait_close)
self.cancel_btn.clicked.connect(self.terminate_process)
def show_no_connection_warning(self):
self.no_internet_msg.show()
self.everything_ok = False
def show_bad_download_warning(self):
self.bad_db_download_msg.show()
self.everything_ok = False
@pyqtSlot()
def terminate_process(self):
if self.download_thread.isRunning():
@@ -50,12 +43,13 @@ class DownloadWindow(QWidget, Ui_Download_window):
@pyqtSlot()
def wait_close(self):
if self.download_thread.status == ThreadStatus.OK:
if self.download_thread.status is ThreadStatus.OK:
self.complete.emit()
self.close()
elif self.download_thread.status == ThreadStatus.NO_CONNECTION_ERR:
self.show_no_connection_warning()
elif self.download_thread.status == ThreadStatus.BAD_DOWNLOAD_ERR:
self.show_bad_download_warning()
elif self.download_thread.status is ThreadStatus.NO_CONNECTION_ERR:
self.no_internet_msg.show()
elif self.download_thread.status is ThreadStatus.BAD_DOWNLOAD_ERR:
self.bad_db_download_msg.show()
else:
self.close()

View File

@@ -0,0 +1,27 @@
from PyQt5.QtWidgets import QLabel
from PyQt5.QtCore import Qt
class FixedAspectRatioLabel(QLabel):
def __init__(self, parent = None):
super().__init__(parent)
self.pixmap = None
def set_default_stylesheet(self):
self.setStyleSheet("""
color: #ffffff;
background-color: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0,stop:0 #304352 ,stop: 1 #d7d2cc);
""")
def make_transparent(self):
self.setText('')
self.setStyleSheet("background-color: transparent;")
def apply_pixmap(self):
if self.pixmap:
self.setPixmap(
self.pixmap.scaled(
self.size(), Qt.IgnoreAspectRatio, Qt.SmoothTransformation))
def rescale(self, size):
self.resize(size)
self.apply_pixmap()

View File

@@ -0,0 +1,20 @@
from PyQt5.QtWidgets import QWidget
from PyQt5.QtCore import QSize
class FixedAspectRatioWidget(QWidget):
space = 10
def __init__(self, parent = None):
super().__init__(parent)
self.labels = []
def resizeEvent(self, event):
h, w = self.height(), self.width()
h_lbl = h / 9 - self.space
w_lbl = 5 * h_lbl
if w_lbl > w:
w_lbl = w
h_lbl = h / 9 - self.space
for label in self.labels:
label.rescale(QSize(w_lbl, h_lbl))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

File diff suppressed because it is too large Load Diff

66
space_weather_data.py Normal file
View File

@@ -0,0 +1,66 @@
from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject
from threads import UpadteSpaceWeatherThread, ThreadStatus
class SpaceWeatherData(QObject):
update_complete = pyqtSignal(bool)
def __init__(self):
super().__init__()
self.xray = ''
self.prot_el = ''
self.ak_index = ''
self.sgas = ''
self.geo_storm = ''
self.images = [QPixmap(),
QPixmap(),
QPixmap(),
QPixmap(),
QPixmap(),
QPixmap(),
QPixmap(),
QPixmap(),
QPixmap()]
self.__update_thread = UpadteSpaceWeatherThread(self)
self.__update_thread.finished.connect(self.__parse_and_emit_signal)
@property
def is_updating(self):
return self.__update_thread.isRunning()
@pyqtSlot()
def update(self):
self.__update_thread.start()
def __parse_data(self):
double_split = lambda string : [i.split() for i in string.splitlines()]
self.xray = double_split(self.xray)
self.prot_el = double_split(self.prot_el)
self.ak_index = double_split(self.ak_index)
self.sgas = double_split(self.sgas)
self.geo_storm = double_split(self.geo_storm)
def remove_data(self):
self.xray = ''
self.prot_el = ''
self.ak_index = ''
self.sgas = ''
self.geo_storm = ''
self.images = [QPixmap(),
QPixmap(),
QPixmap(),
QPixmap(),
QPixmap(),
QPixmap(),
QPixmap(),
QPixmap(),
QPixmap()]
@pyqtSlot()
def __parse_and_emit_signal(self):
if self.__update_thread.status is not ThreadStatus.OK:
status_ok = False
else:
status_ok = True
self.__parse_data()
self.update_complete.emit(status_ok)

56
switchable_label.py Normal file
View File

@@ -0,0 +1,56 @@
from PyQt5.QtWidgets import QLabel
class SwitchableLabel(QLabel):
def __init__(self, parent = None):
super().__init__(parent)
self.switch_on_colors = ()
self.switch_off_colors = ()
self.text_color = ''
self.is_on = False
def switch_on(self):
self.is_on = True
self.__apply_colors(*self.switch_on_colors)
def switch_off(self):
self.is_on = False
self.__apply_colors(*self.switch_off_colors)
def __apply_colors(self, start, end):
self.setStyleSheet(
f"""
color:{self.text_color};
background-color: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0,stop:0 {start} ,stop: 1 {end});
"""
)
class SwitchableLabelsIterable(object):
def __init__(self, *labels):
self.labels = labels
def __iter__(self):
for lab in self.labels:
yield lab
def switch_on(self, label):
for lab in self.labels:
if lab is label:
lab.switch_on()
else:
lab.switch_off()
def switch_off_all(self):
for lab in self.labels:
lab.switch_off()
def set(self, attr, value):
for lab in self.labels:
setattr(lab, attr, value)
def refresh(self):
for lab in self.labels:
if lab.is_on:
lab.switch_on()
else:
lab.switch_off()

114
themes.py
View File

@@ -1,10 +1,13 @@
from functools import partial
from itertools import chain
import os
from PyQt5.QtWidgets import QAction
import re
from PyQt5.QtWidgets import QAction, QActionGroup
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtGui import QPixmap
from constants import Constants
from utilities import pop_up, is_valid_html_color
from switchable_label import SwitchableLabelsIterable
from utilities import pop_up
class ThemeConstants(object):
FOLDER = "themes"
@@ -14,8 +17,11 @@ class ThemeConstants(object):
CURRENT = ".current_theme"
COLORS = "colors.txt"
COLOR_SEPARATOR = "="
DEFAULT_ACTIVE_COLOR = "#39eaff"
DEFAULT_ACTIVE_COLOR = "#000000"
DEFAULT_INACTIVE_COLOR = "#9f9f9f"
DEFAULT_OFF_COLORS = "#000000", "#434343"
DEFAULT_ON_COLORS = "#4b79a1", "#283e51"
DEFAULT_TEXT_COLOR = "#ffffff"
THEME_NOT_FOUND = "Theme not found"
MISSING_THEME = "Missing theme in '" + FOLDER + "' folder."
@@ -24,10 +30,26 @@ class Theme(object):
self.__parent = parent
self.__parent.active_color = ThemeConstants.DEFAULT_ACTIVE_COLOR
self.__parent.inactive_color = ThemeConstants.DEFAULT_INACTIVE_COLOR
self.__theme_path = ThemeConstants.DEFAULT
self.__theme_path = ""
self.__current_theme = ""
self.__parent.default_images_folder = os.path.join(ThemeConstants.FOLDER,
ThemeConstants.DEFAULT,
ThemeConstants.ICONS_FOLDER)
self.__forecast_labels = SwitchableLabelsIterable(*list(chain(self.__parent.switchable_r_labels,
self.__parent.switchable_s_labels,
self.__parent.switchable_g_now_labels,
self.__parent.switchable_g_today_labels,
self.__parent.k_storm_labels,
self.__parent.a_storm_labels,
[self.__parent.expected_noise_lbl])))
self.__forecast_labels.set("switch_on_colors", ThemeConstants.DEFAULT_ON_COLORS)
self.__forecast_labels.set("switch_off_colors", ThemeConstants.DEFAULT_OFF_COLORS)
self.__theme_names = {}
self.__detect_themes()
def __refresh_range_labels(self):
@@ -41,6 +63,7 @@ class Theme(object):
self.__parent.upper_band_filter_unit,
self.__parent.upper_band_confidence,
self.__parent.band_range_lbl)
self.__parent.set_band_filter_label(self.__parent.activate_low_freq_filter_btn,
self.__parent.lower_freq_spinbox,
self.__parent.lower_freq_filter_unit,
@@ -54,26 +77,33 @@ class Theme(object):
@pyqtSlot()
def __apply(self, theme_path):
self.__theme_path = theme_path
self.__change()
self.__parent.display_specs(self.__parent.result_list.currentItem(), None)
self.__refresh_range_labels()
self.__parent.audio_widget.refresh_btns_colors(self.__parent.active_color, self.__parent.inactive_color)
if self.__theme_path != self.__current_theme:
self.__change()
self.__parent.display_specs(self.__parent.result_list.currentItem(), None)
self.__refresh_range_labels()
self.__parent.audio_widget.refresh_btns_colors(self.__parent.active_color, self.__parent.inactive_color)
self.__forecast_labels.refresh()
def __pretty_name(self, bad_name):
return ' '.join(
map(lambda s: s.capitalize(),
bad_name.split('-')[1].split('_')
)
)
def __detect_themes(self):
themes = []
ag = QActionGroup(self.__parent, exclusive = True)
for theme_folder in os.listdir(ThemeConstants.FOLDER):
relative_folder = os.path.join(ThemeConstants.FOLDER, theme_folder)
if os.path.isdir(os.path.abspath(relative_folder)):
relative_folder = os.path.join(ThemeConstants.FOLDER, theme_folder)
themes.append(relative_folder)
for theme_path in themes:
theme_name = '&' + ' '.join(
map(lambda s: s.capitalize(),
os.path.basename(theme_path).split('-')[1].split('_')
)
)
new_theme = QAction(theme_name, self.__parent)
theme_name = '&' + self.__pretty_name(os.path.basename(theme_path))
new_theme = ag.addAction(QAction(theme_name, self.__parent, checkable = True))
self.__parent.menu_themes.addAction(new_theme)
self.__theme_names[theme_name.lstrip('&')] = new_theme
new_theme.triggered.connect(partial(self.__apply, theme_path))
def __change(self):
@@ -128,27 +158,53 @@ class Theme(object):
path_to_colors = os.path.join(self.__theme_path, ThemeConstants.COLORS)
active_color_ok = False
inactive_color_ok = False
valid_format = False
valid_file = False
switch_on_color_ok = False
switch_off_color_ok = False
text_color_ok = False
if os.path.exists(path_to_colors):
valid_file = True
is_valid_html_color = lambda colors : all([bool(re.match("#([a-zA-Z0-9]){6}", color)) for color in colors])
with open(path_to_colors, "r") as colors_file:
for line in colors_file:
if ThemeConstants.COLOR_SEPARATOR in line:
valid_format = True
quality, color = line.split(ThemeConstants.COLOR_SEPARATOR)
color = color.rstrip()
if quality.lower() == Constants.ACTIVE and is_valid_html_color(color):
self.__parent.active_color = color
active_color_ok = True
if quality.lower() == Constants.INACTIVE and is_valid_html_color(color):
self.__parent.inactive_color = color
inactive_color_ok = True
if ',' in color:
color = [c.rstrip().lstrip() for c in color.split(',')]
else:
color = [color]
if len(color) > 2:
break
if is_valid_html_color(color):
if quality.lower() == Constants.ACTIVE:
self.__parent.active_color = color[0]
active_color_ok = True
if quality.lower() == Constants.INACTIVE:
self.__parent.inactive_color = color[0]
inactive_color_ok = True
if len(color) == 2:
if quality.lower() == Constants.LABEL_ON_COLOR:
switch_on_color_ok = True
self.__forecast_labels.set("switch_on_colors", color)
if quality.lower() == Constants.LABEL_OFF_COLOR:
switch_off_color_ok = True
self.__forecast_labels.set("switch_off_colors", color)
if quality.lower() == Constants.TEXT_COLOR:
text_color_ok = True
self.__forecast_labels.set("text_color", color[0])
if not all([valid_file, valid_format, active_color_ok, inactive_color_ok]):
if not (active_color_ok and inactive_color_ok):
self.__parent.active_color = ThemeConstants.DEFAULT_ACTIVE_COLOR
self.__parent.inactive_color = ThemeConstants.DEFAULT_INACTIVE_COLOR
if not (switch_on_color_ok and switch_off_color_ok):
self.__forecast_labels.set("switch_on_colors", ThemeConstants.DEFAULT_ON_COLORS)
self.__forecast_labels.set("switch_off_colors", ThemeConstants.DEFAULT_OFF_COLORS)
if not text_color_ok:
self.__forecast_labels.set("text_color", ThemeConstants.DEFAULT_TEXT_COLOR)
self.__current_theme = self.__theme_path
try:
with open(os.path.join(ThemeConstants.FOLDER,
ThemeConstants.CURRENT), "w") as current_theme:
@@ -161,5 +217,9 @@ class Theme(object):
if os.path.exists(current_theme_file):
with open(current_theme_file, "r") as current_theme_path:
theme_path = current_theme_path.read()
if theme_path != ThemeConstants.DEFAULT:
self.__apply(theme_path)
theme_name = self.__pretty_name(os.path.basename(theme_path))
self.__theme_names[theme_name].setChecked(True)
self.__apply(theme_path)
else:
self.__theme_names[self.__pretty_name(ThemeConstants.DEFAULT)].setChecked(True)
self.__apply(os.path.join(ThemeConstants.FOLDER, ThemeConstants.DEFAULT))

View File

@@ -1,2 +1,5 @@
active=#4da6ff
active=#4545e5
inactive=#546E7A
off=#283048,#859398
on=#4776e6, #8e54e9
text=#ffffff

View File

@@ -433,6 +433,7 @@ TreeViewMenu (Mode)
QTreeView {
background-color: transparent;
selection-background-color: transparent;
border: 0px;
}
QTreeView::item {

View File

@@ -1,2 +1,4 @@
active=#88cc00
inactive=#546E7A
off=#304352,#d7d2cc
on=#3ca55c,#b5ac49

View File

@@ -452,6 +452,7 @@ TreeViewMenu (Mode)
QTreeView {
background-color: transparent;
selection-background-color: transparent;
border: 0px;
}
QTreeView::item {

View File

@@ -1,2 +1,4 @@
active=#6ECE12
inactive=#b3b3cc
off=#948e99,#2e1437
on=#b993d6,#8ca6db

View File

@@ -452,6 +452,7 @@ TreeViewMenu (Mode)
QTreeView {
background-color: transparent;
selection-background-color: transparent;
border: 0px;
}
QTreeView::item {

View File

@@ -8,18 +8,18 @@ from zipfile import ZipFile
from PyQt5.QtCore import QThread
from constants import Constants, Database, ChecksumWhat
from utilities import checksum_ok
import constants
class ThreadStatus(Enum):
OK = auto()
NO_CONNECTION_ERR = auto()
UNKNOWN_ERR = auto()
BAD_DOWNLOAD_ERR = auto()
UNDEFINED = auto()
class DownloadThread(QThread):
def __init__(self):
super().__init__()
self.__status = ThreadStatus.OK
self.__status = ThreadStatus.UNDEFINED
self.reason = 0
@property
@@ -33,8 +33,6 @@ class DownloadThread(QThread):
def run(self):
try:
db = urllib3.PoolManager().request('GET', Database.LINK_LOC)
# db = urllib.request.urlopen(constants.Database.LINK_LOC)
# raise urllib.error.URLError('Test')
except urllib3.exceptions.MaxRetryError: # No internet connection.
self.__status = ThreadStatus.NO_CONNECTION_ERR
return
@@ -54,8 +52,45 @@ class DownloadThread(QThread):
if os.path.exists(Constants.DATA_FOLDER):
rmtree(Constants.DATA_FOLDER)
try:
# data_folder = db.read()
with ZipFile(BytesIO(db.data)) as zipped:
zipped.extractall()
except:
self.__status = ThreadStatus.UNKNOWN_ERR
else:
self.__status = ThreadStatus.OK
class UpadteSpaceWeatherThread(QThread):
def __init__(self, space_weather_data):
super().__init__()
self.__status = ThreadStatus.UNDEFINED
self.__space_weather_data = space_weather_data
@property
def status(self):
return self.__status
def __del__(self):
self.terminate()
self.wait()
def run(self):
try:
self.__space_weather_data.xray = str(urllib3.PoolManager().request('GET', Constants.FORECAST_XRAY).data, 'utf-8')
self.__space_weather_data.prot_el = str(urllib3.PoolManager().request('GET', Constants.FORECAST_PROT).data, 'utf-8')
self.__space_weather_data.ak_index = str(urllib3.PoolManager().request('GET', Constants.FORECAST_AK_IND).data, 'utf-8')
self.__space_weather_data.sgas = str(urllib3.PoolManager().request('GET', Constants.FORECAST_SGAS).data, 'utf-8')
self.__space_weather_data.geo_storm = str(urllib3.PoolManager().request('GET', Constants.FORECAST_G).data, 'utf-8')
self.__space_weather_data.images[0].loadFromData(urllib3.PoolManager().request('GET', Constants.FORECAST_IMG_0).data)
self.__space_weather_data.images[1].loadFromData(urllib3.PoolManager().request('GET', Constants.FORECAST_IMG_1).data)
self.__space_weather_data.images[2].loadFromData(urllib3.PoolManager().request('GET', Constants.FORECAST_IMG_2).data)
self.__space_weather_data.images[3].loadFromData(urllib3.PoolManager().request('GET', Constants.FORECAST_IMG_3).data)
self.__space_weather_data.images[4].loadFromData(urllib3.PoolManager().request('GET', Constants.FORECAST_IMG_4).data)
self.__space_weather_data.images[5].loadFromData(urllib3.PoolManager().request('GET', Constants.FORECAST_IMG_5).data)
self.__space_weather_data.images[6].loadFromData(urllib3.PoolManager().request('GET', Constants.FORECAST_IMG_6).data)
self.__space_weather_data.images[7].loadFromData(urllib3.PoolManager().request('GET', Constants.FORECAST_IMG_7).data)
self.__space_weather_data.images[8].loadFromData(urllib3.PoolManager().request('GET', Constants.FORECAST_IMG_8).data)
except:
self.__status = ThreadStatus.UNKNOWN_ERR
else:
self.__status = ThreadStatus.OK

View File

@@ -1,6 +1,5 @@
from functools import partial
import hashlib
import re
import sys
import os
from pandas import read_csv
@@ -42,9 +41,9 @@ def pop_up(cls, title, text,
def checksum_ok(data, what):
code = hashlib.sha256()
code.update(data)
if what == ChecksumWhat.FOLDER:
if what is ChecksumWhat.FOLDER:
n = 0
elif what == ChecksumWhat.DB:
elif what is ChecksumWhat.DB:
n = 1
else:
raise ValueError("Wrong entry name.")
@@ -55,9 +54,6 @@ def checksum_ok(data, what):
raise
return code.hexdigest() == reference
def is_valid_html_color(color):
return bool(re.match("#([a-zA-Z0-9]){6}", color))
def connect_to(events_to_connect, fun_to_connect, fun_args):
if fun_args:
for event in events_to_connect:
@@ -101,8 +97,12 @@ def format_numbers(lower, upper):
upper = int(upper) / upper_factor
if lower.is_integer():
lower = int(lower)
else:
lower = round(lower, 2)
if upper.is_integer():
upper = int(upper)
else:
upper = round(upper, 2)
if pre_lower != pre_upper:
return f"{lower:,} {units[lower_factor]} - {upper:,} {units[upper_factor]}"
else: