Merge pull request #2 from AresValley/forecast_screen

Forecast screen
This commit is contained in:
Alessandro Ceccato
2019-05-12 19:59:00 +02:00
committed by GitHub
12 changed files with 2560 additions and 570 deletions

3
.gitignore vendored
View File

@@ -6,6 +6,7 @@ to_do.txt
csv_info.txt
pyinstaller_cmd.txt
themes/.current_theme
*.bat
launch.bat
designer.bat
*.sh
.vscode/

View File

@@ -22,7 +22,7 @@ from PyQt5.QtCore import (QFileInfo,
pyqtSlot,)
from audio_player import AudioPlayer
from space_weather_data import SpaceWeatherData
from weatherdata import SpaceWeatherData, ForecastData
from download_window import DownloadWindow
from switchable_label import SwitchableLabelsIterable
from constants import (Constants,
@@ -32,7 +32,7 @@ from constants import (Constants,
ChecksumWhat,
Messages,
Signal,)
from themes import Theme
from themesmanager import ThemeManager
from utilities import (checksum_ok,
uncheck_and_emit,
pop_up,
@@ -80,6 +80,7 @@ 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,
@@ -138,23 +139,34 @@ class Artemis(QMainWindow, Ui_MainWindow):
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
self.space_weather_labels = (
self.space_weather_lbl_0,
self.space_weather_lbl_1,
self.space_weather_lbl_2,
self.space_weather_lbl_3,
self.space_weather_lbl_4,
self.space_weather_lbl_5,
self.space_weather_lbl_6,
self.space_weather_lbl_7,
self.space_weather_lbl_8
)
for lab in self.forecast_labels:
for lab in self.space_weather_labels:
lab.set_default_stylesheet()
self.forecast_label_container.labels = self.forecast_labels
self.theme = Theme(self)
self.space_weather_label_container.labels = self.space_weather_labels
self.space_weather_label_name_container.labels = [
self.eme_lbl,
self.ms_lbl,
self.muf_lbl,
self.hi_lbl,
self.eu50_lbl,
self.eu70_lbl,
self.eu144_lbl,
self.na_lbl,
self.aurora_lbl
]
self.theme_manager = ThemeManager(self)
# Manage frequency filters.
self.frequency_filters_btns = (
@@ -452,7 +464,7 @@ class Artemis(QMainWindow, Ui_MainWindow):
)
self.apply_remove_acf_filter_btn.clicked.connect(self.display_signals)
self.reset_acf_filters_btn.clicked.connect(self.reset_acf_filters)
self.acf_info_btn.clicked.connect(lambda : webbrowser.open(Constants.ACF_DOCS))
self.acf_info_btn.clicked.connect(lambda: webbrowser.open(Constants.ACF_DOCS))
connect_events_to_func(
events_to_connect=[self.acf_spinbox.valueChanged,
@@ -501,30 +513,56 @@ class Artemis(QMainWindow, Ui_MainWindow):
# Space weather
self.info_now_btn.clicked.connect(
lambda : webbrowser.open(Constants.FORECAST_INFO)
lambda: webbrowser.open(Constants.SPACE_WEATHER_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)
# Forecast
self.forecast_info_btn.clicked.connect(
lambda: webbrowser.open(Constants.SPACE_WEATHER_INFO)
)
self.forecast_data = ForecastData(self)
self.update_forecast_bar.clicked.connect(self.start_update_forecast)
self.update_forecast_bar.set_idle()
self.forecast_data.update_complete.connect(self.update_forecast)
# Final operations.
self.theme.initialize()
self.theme_manager.start()
self.load_db()
self.display_signals()
@pyqtSlot()
def start_update_forecast(self):
if not self.forecast_data.is_updating:
self.update_forecast_bar.set_updating()
self.forecast_data.update()
@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_forecast(self, status_ok):
self.update_forecast_bar.set_idle()
if status_ok:
self.forecast_data.update_all_labels()
elif not self.closing:
pop_up(self, title=Messages.BAD_DOWNLOAD,
text=Messages.BAD_DOWNLOAD_MSG).show()
self.forecast_data.remove_data()
@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}"
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:
@@ -579,33 +617,44 @@ class Artemis(QMainWindow, Ui_MainWindow):
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)
self.expected_noise_lbl.setText(" S0 - S1 (<-120 dBm) ")
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)
self.expected_noise_lbl.setText(" S0 - S1 (<-120 dBm) ")
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)
self.expected_noise_lbl.setText(" S1 - S2 (-115 dBm) ")
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)
self.expected_noise_lbl.setText(" S2 - S3 (-110 dBm) ")
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)
self.expected_noise_lbl.setText(" S3 - S4 (-100 dBm) ")
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)
self.expected_noise_lbl.setText(" S4 - S6 (-90 dBm) ")
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)
self.expected_noise_lbl.setText(" S6 - S9 (-80 dBm) ")
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)
self.expected_noise_lbl.setText(" S9 - S20 (>-60 dBm) ")
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)
self.expected_noise_lbl.setText(" S20 - S30 (>-60 dBm) ")
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)
self.expected_noise_lbl.setText(" S30+ (>>-60 dBm) ")
self.expected_noise_lbl.switch_on()
if a_index >= 0 and a_index < 8:
self.a_storm_labels.switch_on(self.a_quiet_lbl)
@@ -624,42 +673,31 @@ class Artemis(QMainWindow, Ui_MainWindow):
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):
for label, pixmap in zip(self.space_weather_labels, self.space_weather_data.images):
label.pixmap = pixmap
label.make_transparent()
label.apply_pixmap()
@@ -729,7 +767,9 @@ class Artemis(QMainWindow, Ui_MainWindow):
def set_initial_size(self):
"""Function to handle high resolution screens. The function sets bigger
sizes for all the relevant fixed-size widgets."""
sizes for all the relevant fixed-size widgets.
Also by default it sets the size to 3/4 of the available space
both vertically and horizontally."""
d = QDesktopWidget().availableGeometry()
w = d.width()
h = d.height()
@@ -1066,6 +1106,10 @@ class Artemis(QMainWindow, Ui_MainWindow):
def reset_modulation_filters(self):
uncheck_and_emit(self.apply_remove_modulation_filter_btn)
self.search_bar_modulation.setText('')
self.show_matching_strings(
self.modulation_list,
self.search_bar_modulation.text()
)
for i in range(self.modulation_list.count()):
if self.modulation_list.item(i).isSelected():
self.modulation_list.item(i).setSelected(False)
@@ -1074,6 +1118,10 @@ class Artemis(QMainWindow, Ui_MainWindow):
def reset_location_filters(self):
uncheck_and_emit(self.apply_remove_location_filter_btn)
self.search_bar_location.setText('')
self.show_matching_strings(
self.locations_list,
self.search_bar_location.text()
)
for i in range(self.locations_list.count()):
if self.locations_list.item(i).isSelected():
self.locations_list.item(i).setSelected(False)
@@ -1366,6 +1414,8 @@ class Artemis(QMainWindow, Ui_MainWindow):
self.download_window.close()
if self.space_weather_data.is_updating:
self.space_weather_data.shutdown_thread()
if self.forecast_data.is_updating:
self.forecast_data.shutdown_thread()
super().closeEvent(event)

1985
artemis.ui

File diff suppressed because it is too large Load Diff

View File

@@ -11,18 +11,20 @@ class ClickableProgressBar(QProgressBar):
self.__text = ''
super().__init__(parent)
def __set_text(self, text):
self.__text = text
# 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.__set_text(Constants.CLICK_TO_UPDATE_STR)
self.__text = Constants.CLICK_TO_UPDATE_STR
self.setMaximum(self.minimum() + 1)
def set_updating(self):
self.__set_text(Constants.UPDATING_STR)
# self.__set_text(Constants.UPDATING_STR)
self.__text = Constants.UPDATING_STR
self.setMaximum(self.minimum())
def mousePressEvent(self, event):

View File

@@ -2,7 +2,7 @@ from collections import namedtuple
from enum import Enum, auto
class Ftype(object):
class Ftype:
FREQ = "freq"
BAND = "band"
@@ -17,7 +17,7 @@ class ChecksumWhat(Enum):
DB = auto()
class Messages(object):
class Messages:
DB_UP_TO_DATE = "Already up to date"
DB_UP_TO_DATE_MSG = "No newer version to download."
DB_NEW_VER = "New version available"
@@ -32,7 +32,7 @@ class Messages(object):
BAD_DOWNLOAD_MSG = "Something went wrong with the downaload.\nCheck your internet connection and try again."
class Signal(object):
class Signal:
NAME = "name"
INF_FREQ = "inf_freq"
SUP_FREQ = "sup_freq"
@@ -48,7 +48,7 @@ class Signal(object):
WIKI_CLICKED = "url_clicked"
class Database(object):
class Database:
LINK_LOC = "https://aresvalley.com/Storage/Artemis/Database/data.zip"
LINK_REF = "https://aresvalley.com/Storage/Artemis/Database/data.zip.log"
NAME = "db.csv"
@@ -73,7 +73,16 @@ class Database(object):
Signal.CATEGORY_CODE,)
class Constants(object):
class ForecastColors:
WARNING_COLOR = "#F95423"
KP9_COLOR = "#FFCCCB"
KP8_COLOR = "#FFCC9A"
KP7_COLOR = "#FFFECD"
KP6_COLOR = "#CDFFCC"
KP5_COLOR = "#BEE3FE"
class Constants:
CLICK_TO_UPDATE_STR = "Click to update"
SIGIDWIKI = "https://www.sigidwiki.com/wiki/Signal_Identification_Guide"
ADD_SIGNAL_LINK = "https://www.sigidwiki.com/index.php/Special:FormEdit/Signal/?preload=Signal_Identification_Wiki:Signal_form_preload_text"
@@ -82,13 +91,14 @@ class Constants(object):
RTL_SDL_LINK = "https://www.rtl-sdr.com/"
UPDATING_STR = "Updating..."
ACF_DOCS = "https://aresvalley.com/documentation/"
FORECAST_XRAY = "https://services.swpc.noaa.gov/text/goes-xray-flux-primary.txt"
FORECAST_PROT_EL = "https://services.swpc.noaa.gov/text/goes-particle-flux-primary.txt"
FORECAST_AK_INDEX = "https://services.swpc.noaa.gov/text/wwv.txt"
FORECAST_SGAS = "https://services.swpc.noaa.gov/text/sgas.txt"
FORECAST_GEO_STORM = "https://services.swpc.noaa.gov/text/3-day-forecast.txt"
FORECAST_INFO = "https://www.swpc.noaa.gov/sites/default/files/images/NOAAscales.pdf"
FORECAST_IMGS = ["http://www.mmmonvhf.de/eme/eme.png",
FORECAST_PROBABILITIES = "https://services.swpc.noaa.gov/text/sgarf.txt"
SPACE_WEATHER_XRAY = "https://services.swpc.noaa.gov/text/goes-xray-flux-primary.txt"
SPACE_WEATHER_PROT_EL = "https://services.swpc.noaa.gov/text/goes-particle-flux-primary.txt"
SPACE_WEATHER_AK_INDEX = "https://services.swpc.noaa.gov/text/wwv.txt"
SPACE_WEATHER_SGAS = "https://services.swpc.noaa.gov/text/sgas.txt"
SPACE_WEATHER_GEO_STORM = "https://services.swpc.noaa.gov/text/3-day-forecast.txt"
SPACE_WEATHER_INFO = "https://www.swpc.noaa.gov/sites/default/files/images/NOAAscales.pdf"
SPACE_WEATHER_IMGS = ["http://www.mmmonvhf.de/eme/eme.png",
"http://www.mmmonvhf.de/ms/ms.png",
"http://www.mmmonvhf.de/es/es.png",
"http://www.mmmonvhf.de/solar/solar.png",

View File

@@ -8,14 +8,14 @@ class FixedAspectRatioLabel(QLabel):
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);
""")
self.setStyleSheet("""border-width: 1px;
border-style: solid;
border-color: black;"""
)
def make_transparent(self):
self.setText('')
self.setStyleSheet("background-color: transparent;")
self.setStyleSheet("border-width: 0px;")
def apply_pixmap(self):
if self.pixmap:

View File

@@ -12,10 +12,10 @@ class FixedAspectRatioWidget(QWidget):
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
w_pad = w - 10
if w_lbl > w_pad:
w_lbl = w_pad
h_lbl = w_pad / 5
for label in self.labels:
label.rescale(QSize(w_lbl, h_lbl))

View File

@@ -1,74 +0,0 @@
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):
status_ok = False
if self.__update_thread.status is ThreadStatus.OK:
status_ok = True
self.__parse_data()
self.update_complete.emit(status_ok)
def shutdown_thread(self):
self.__update_thread.terminate()
self.__update_thread.wait()

View File

@@ -1,20 +1,33 @@
from PyQt5.QtWidgets import QLabel
from constants import ForecastColors
class SwitchableLabel(QLabel):
class _BaseSwitchableLabel(QLabel):
def __init__(self, parent=None):
super().__init__(parent)
self.is_on = False
self.level = 0
def switch_on(self):
self.is_on = True
def switch_off(self):
self.is_on = False
class SwitchableLabel(_BaseSwitchableLabel):
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
super().switch_on()
self.__apply_colors(*self.switch_on_colors)
def switch_off(self):
self.is_on = False
super().switch_off()
self.__apply_colors(*self.switch_off_colors)
def __apply_colors(self, start, end):
@@ -26,7 +39,54 @@ class SwitchableLabel(QLabel):
)
class SwitchableLabelsIterable(object):
class SingleColorSwitchableLabel(_BaseSwitchableLabel):
def __init__(self, parent=None):
super().__init__(parent)
self.active_color = ForecastColors.WARNING_COLOR
def switch_on(self):
if self.level >= 30:
super().switch_on()
self.setStyleSheet(f"color: {self.active_color}"
# f"""background-color: {self.active_color};
# color: #000000;"""
)
def switch_off(self):
super().switch_off()
# self.setStyleSheet("""background-color: transparent;""")
self.setStyleSheet("")
class MultiColorSwitchableLabel(_BaseSwitchableLabel):
LEVEL_COLORS = {
9: ForecastColors.KP9_COLOR,
8: ForecastColors.KP8_COLOR,
7: ForecastColors.KP7_COLOR,
6: ForecastColors.KP6_COLOR,
5: ForecastColors.KP5_COLOR
}
def __init__(self, parent=None):
super().__init__(parent)
def switch_on(self):
if 5 <= self.level <= 9:
super().switch_on()
self.setStyleSheet(f"color: {self.LEVEL_COLORS[self.level]}"
# f"""background-color: {self.LEVEL_COLORS[self.level]};
# color: #000000;
# """
)
def switch_off(self):
super().switch_off()
# self.setStyleSheet("background-color: transparent;")
self.setStyleSheet("")
class SwitchableLabelsIterable:
def __init__(self, *labels):
self.labels = labels

View File

@@ -10,7 +10,7 @@ from switchable_label import SwitchableLabelsIterable
from utilities import pop_up
class ThemeConstants(object):
class ThemeConstants:
FOLDER = "themes"
EXTENSION = ".qss"
ICONS_FOLDER = "icons"
@@ -26,7 +26,7 @@ class ThemeConstants(object):
THEME_NOT_FOUND = "Theme not found"
MISSING_THEME = "Missing theme in '" + FOLDER + "' folder."
class Theme(object):
class ThemeManager:
def __init__(self, parent):
self.__parent = parent
self.__parent.active_color = ThemeConstants.DEFAULT_ACTIVE_COLOR
@@ -41,7 +41,7 @@ class Theme(object):
ThemeConstants.ICONS_FOLDER
)
self.__forecast_labels = SwitchableLabelsIterable(
self.__space_weather_labels = SwitchableLabelsIterable(
*list(
chain(
self.__parent.switchable_r_labels,
@@ -55,11 +55,11 @@ class Theme(object):
)
)
self.__forecast_labels.set(
self.__space_weather_labels.set(
"switch_on_colors",
ThemeConstants.DEFAULT_ON_COLORS
)
self.__forecast_labels.set(
self.__space_weather_labels.set(
"switch_off_colors", ThemeConstants.DEFAULT_OFF_COLORS
)
@@ -106,7 +106,7 @@ class Theme(object):
self.__parent.active_color,
self.__parent.inactive_color
)
self.__forecast_labels.refresh()
self.__space_weather_labels.refresh()
def __pretty_name(self, bad_name):
return ' '.join(
@@ -257,20 +257,20 @@ class Theme(object):
inactive_color_ok = True
if quality.lower() == Constants.TEXT_COLOR:
text_color_ok = True
self.__forecast_labels.set(
self.__space_weather_labels.set(
"text_color",
color
)
if color_len == 2:
if quality.lower() == Constants.LABEL_ON_COLOR:
switch_on_color_ok = True
self.__forecast_labels.set(
self.__space_weather_labels.set(
"switch_on_colors",
color
)
if quality.lower() == Constants.LABEL_OFF_COLOR:
switch_off_color_ok = True
self.__forecast_labels.set(
self.__space_weather_labels.set(
"switch_off_colors",
color
)
@@ -280,17 +280,17 @@ class Theme(object):
self.__parent.inactive_color = ThemeConstants.DEFAULT_INACTIVE_COLOR
if not (switch_on_color_ok and switch_off_color_ok):
self.__forecast_labels.set(
self.__space_weather_labels.set(
"switch_on_colors",
ThemeConstants.DEFAULT_ON_COLORS
)
self.__forecast_labels.set(
self.__space_weather_labels.set(
"switch_off_colors",
ThemeConstants.DEFAULT_OFF_COLORS
)
if not text_color_ok:
self.__forecast_labels.set(
self.__space_weather_labels.set(
"text_color",
ThemeConstants.DEFAULT_TEXT_COLOR
)
@@ -305,7 +305,7 @@ class Theme(object):
except Exception:
pass
def initialize(self):
def start(self):
current_theme_file = os.path.join(
ThemeConstants.FOLDER,
ThemeConstants.CURRENT

View File

@@ -19,7 +19,7 @@ class ThreadStatus(Enum):
UNDEFINED = auto()
class _BaseDownloadThread(QThread):
class BaseDownloadThread(QThread):
def __init__(self, parent=None):
super().__init__(parent)
self.status = ThreadStatus.UNDEFINED
@@ -27,10 +27,9 @@ class _BaseDownloadThread(QThread):
def __del__(self):
self.terminate()
self.wait()
super().__del__()
class DownloadThread(_BaseDownloadThread):
class DownloadThread(BaseDownloadThread):
def __init__(self):
super().__init__()
self.reason = 0
@@ -66,7 +65,13 @@ class DownloadThread(_BaseDownloadThread):
self.status = ThreadStatus.OK
class UpadteSpaceWeatherThread(_BaseDownloadThread):
class _AsyncDownloader:
async def _download_resource(self, session, link):
resp = await session.get(link)
return await resp.read()
class UpdateSpaceWeatherThread(BaseDownloadThread, _AsyncDownloader):
__properties = ("xray", "prot_el", "ak_index", "sgas", "geo_storm")
@@ -74,20 +79,16 @@ class UpadteSpaceWeatherThread(_BaseDownloadThread):
super().__init__()
self.__space_weather_data = space_weather_data
async def __download_resource(self, session, link):
resp = await session.get(link)
return await resp.read()
async def __download_property(self, session, property_name):
link = getattr(Constants, "FORECAST_" + property_name.upper())
data = await self.__download_resource(session, link)
link = getattr(Constants, "SPACE_WEATHER_" + property_name.upper())
data = await self._download_resource(session, link)
setattr(self.__space_weather_data, property_name, str(data, 'utf-8'))
async def __download_image(self, session, n):
im = await self.__download_resource(session, Constants.FORECAST_IMGS[n])
im = await self._download_resource(session, Constants.SPACE_WEATHER_IMGS[n])
self.__space_weather_data.images[n].loadFromData(im)
async def __download_resources(self, *links):
async def _download_resources(self):
session = aiohttp.ClientSession()
try:
t = []
@@ -96,7 +97,7 @@ class UpadteSpaceWeatherThread(_BaseDownloadThread):
asyncio.create_task(self.__download_property(session, p))
)
tot_images = range(len(Constants.FORECAST_IMGS))
tot_images = range(len(Constants.SPACE_WEATHER_IMGS))
t1 = []
for im_number in tot_images:
t1.append(
@@ -112,4 +113,53 @@ class UpadteSpaceWeatherThread(_BaseDownloadThread):
def run(self):
self.status = ThreadStatus.UNDEFINED
asyncio.run(self.__download_resources())
asyncio.run(self._download_resources())
class UpdateForecastThread(BaseDownloadThread, _AsyncDownloader):
class _PropertyName(Enum):
FORECAST = auto()
PROBABILITIES = auto()
def __init__(self, parent):
super().__init__()
self.parent = parent
async def __download_property(self, session, link, prop_name):
resp = await self._download_resource(session, link)
resp = str(resp, 'utf-8')
if prop_name is self._PropertyName.FORECAST:
self.parent.forecast = resp
if prop_name is self._PropertyName.PROBABILITIES:
self.parent.probabilities = resp
async def _download_resources(self):
session = aiohttp.ClientSession()
try:
await asyncio.gather(
asyncio.create_task(
self.__download_property(
session,
Constants.SPACE_WEATHER_GEO_STORM,
self._PropertyName.FORECAST
)
),
asyncio.create_task(
self.__download_property(
session,
Constants.FORECAST_PROBABILITIES,
self._PropertyName.PROBABILITIES
)
)
)
except Exception:
self.status = ThreadStatus.UNKNOWN_ERR
else:
self.status = ThreadStatus.OK
finally:
await session.close()
def run(self):
self.status = ThreadStatus.UNDEFINED
asyncio.run(self._download_resources())

316
weatherdata.py Normal file
View File

@@ -0,0 +1,316 @@
import re
from PyQt5.QtGui import QPixmap
from PyQt5.QtCore import pyqtSlot, pyqtSignal, QObject
from threads import (BaseDownloadThread,
UpdateSpaceWeatherThread,
ThreadStatus,
UpdateForecastThread)
from constants import Constants
from switchable_label import MultiColorSwitchableLabel
class _BaseWeatherData(QObject):
update_complete = pyqtSignal(bool)
def __init__(self):
super().__init__()
self._update_thread = BaseDownloadThread()
@property
def is_updating(self):
return self._update_thread.isRunning()
def update(self):
self._update_thread.start()
def _parse_data(self):
pass
@pyqtSlot()
def _parse_and_emit_signal(self):
status_ok = False
if self._update_thread.status is ThreadStatus.OK:
status_ok = True
self._parse_data()
self.update_complete.emit(status_ok)
def _double_split(self, string):
return [i.split() for i in string.splitlines()]
def shutdown_thread(self):
self._update_thread.terminate()
self._update_thread.wait()
class SpaceWeatherData(_BaseWeatherData):
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 = UpdateSpaceWeatherThread(self)
self._update_thread.finished.connect(self._parse_and_emit_signal)
def _parse_data(self):
self.xray = self._double_split(self.xray)
self.prot_el = self._double_split(self.prot_el)
self.ak_index = self._double_split(self.ak_index)
self.sgas = self._double_split(self.sgas)
self.geo_storm = self._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()
]
class ForecastData(_BaseWeatherData):
ROW_KEYWORDS = {
"solar_row": "S1 or greater",
"event_row": "III. Event probabilities",
"rb_now_row": "R1-R2",
"ga_now_row": "Geomagnetic Activity Probabilities",
"kp_index_row": "NOAA Kp index breakdown"
}
def __init__(self, parent):
super().__init__()
self.forecast = ''
self.probabilities = ''
self.__labels_table = []
self.__solar_row = None
self.__event_row = None
self.__rb_now_row = None
self.__ga_now_row = None
self.__kp_index_row = None
self._update_thread = UpdateForecastThread(self)
self._update_thread.finished.connect(self._parse_and_emit_signal)
self.today_lbl = parent.today_lbl
self.today_p1_lbl = parent.today_p1_lbl
self.today_p2_lbl = parent.today_p2_lbl
self.__today_lbls = []
self.__today_p1_lbls = []
self.__today_p2_lbls = []
self.__all_lbls = []
flags = ['', 'p1_', 'p2_']
for flag in flags:
title_lbl = getattr(self, "today_" + flag + "lbl")
title_lbl.setText("-")
for index in range(20):
label = getattr(
parent,
"forecast_today_" + flag + str(index) + "_lbl"
)
label.setText(Constants.UNKNOWN)
if flag == flags[0]:
self.__today_lbls.append(label)
if flag == flags[1]:
self.__today_p1_lbls.append(label)
if flag == flags[2]:
self.__today_p2_lbls.append(label)
self.__all_lbls = [
self.__today_lbls,
self.__today_p1_lbls,
self.__today_p2_lbls
]
def _parse_data(self):
self.forecast = self.forecast.splitlines()
# Remove possible '(G\d)' from the kp_index table
self.probabilities = re.sub(
'(G\d)', lambda obj: '', self.probabilities
)
self.probabilities = self.probabilities.splitlines()
def __split_lists(self):
self.forecast = [i.split() for i in self.forecast]
self.probabilities = [i.split() for i in self.probabilities]
def __find_row_with(self, data, text):
for i, row in enumerate(data):
if text in row:
return i
return None
def __get_rows(self):
self.__solar_row = self.__find_row_with(
self.forecast,
self.ROW_KEYWORDS["solar_row"]
)
self.__event_row = self.__find_row_with(
self.probabilities,
self.ROW_KEYWORDS["event_row"]
)
self.__rb_now_row = self.__find_row_with(
self.forecast,
self.ROW_KEYWORDS["rb_now_row"]
)
self.__ga_now_row = self.__find_row_with(
self.probabilities,
self.ROW_KEYWORDS["ga_now_row"]
)
self.__kp_index_row = self.__find_row_with(
self.forecast,
self.ROW_KEYWORDS["kp_index_row"]
)
is_none = lambda x: x is None
if any([
is_none(self.__solar_row),
is_none(self.__event_row),
is_none(self.__rb_now_row),
is_none(self.__ga_now_row),
is_none(self.__kp_index_row)
]):
raise Exception('Missing Rows')
def __set_dates(self):
month = self.forecast[self.__solar_row - 1][0]
today = self.forecast[self.__solar_row - 1][1]
today_p1 = self.forecast[self.__solar_row - 1][3]
today_p2 = self.forecast[self.__solar_row - 1][5]
self.today_lbl.setText(month + ' ' + today)
self.today_p1_lbl.setText(month + ' ' + today_p1)
self.today_p2_lbl.setText(month + ' ' + today_p2)
def __make_labels_table(self):
get_first_split = lambda x: x.split("/")[0]
get_second_split = lambda x: x.split("/")[1]
get_third_split = lambda x: x.split("/")[2]
self.__labels_table = [
[
[self.forecast, self.__solar_row, 3, None],
[self.probabilities, self.__event_row + 1, 2, get_first_split],
[self.probabilities, self.__event_row + 2, 2, get_first_split],
[self.probabilities, self.__event_row + 3, 1, get_first_split],
[self.forecast, self.__rb_now_row, 1, None],
[self.forecast, self.__rb_now_row + 1, 3, None],
[self.probabilities, self.__ga_now_row + 2, 1, get_first_split],
[self.probabilities, self.__ga_now_row + 3, 2, get_first_split],
[self.probabilities, self.__ga_now_row + 4, 2, get_first_split],
[self.probabilities, self.__ga_now_row + 6, 1, get_first_split],
[self.probabilities, self.__ga_now_row + 7, 2, get_first_split],
[self.probabilities, self.__ga_now_row + 8, 2, get_first_split],
[self.forecast, self.__kp_index_row + 3, 1, None],
[self.forecast, self.__kp_index_row + 4, 1, None],
[self.forecast, self.__kp_index_row + 5, 1, None],
[self.forecast, self.__kp_index_row + 6, 1, None],
[self.forecast, self.__kp_index_row + 7, 1, None],
[self.forecast, self.__kp_index_row + 8, 1, None],
[self.forecast, self.__kp_index_row + 9, 1, None],
[self.forecast, self.__kp_index_row + 10, 1, None]
],
[
[self.forecast, self.__solar_row, 4, None],
[self.probabilities, self.__event_row + 1, 2, get_second_split],
[self.probabilities, self.__event_row + 2, 2, get_second_split],
[self.probabilities, self.__event_row + 3, 1, get_second_split],
[self.forecast, self.__rb_now_row, 2, None],
[self.forecast, self.__rb_now_row + 1, 4, None],
[self.probabilities, self.__ga_now_row + 2, 1, get_second_split],
[self.probabilities, self.__ga_now_row + 3, 2, get_second_split],
[self.probabilities, self.__ga_now_row + 4, 2, get_second_split],
[self.probabilities, self.__ga_now_row + 6, 1, get_second_split],
[self.probabilities, self.__ga_now_row + 7, 2, get_second_split],
[self.probabilities, self.__ga_now_row + 8, 2, get_second_split],
[self.forecast, self.__kp_index_row + 3, 2, None],
[self.forecast, self.__kp_index_row + 4, 2, None],
[self.forecast, self.__kp_index_row + 5, 2, None],
[self.forecast, self.__kp_index_row + 6, 2, None],
[self.forecast, self.__kp_index_row + 7, 2, None],
[self.forecast, self.__kp_index_row + 8, 2, None],
[self.forecast, self.__kp_index_row + 9, 2, None],
[self.forecast, self.__kp_index_row + 10, 2, None]
],
[
[self.forecast, self.__solar_row, 5, None],
[self.probabilities, self.__event_row + 1, 2, get_third_split],
[self.probabilities, self.__event_row + 2, 2, get_third_split],
[self.probabilities, self.__event_row + 3, 1, get_third_split],
[self.forecast, self.__rb_now_row, 3, None],
[self.forecast, self.__rb_now_row + 1, 5, None],
[self.probabilities, self.__ga_now_row + 2, 1, get_third_split],
[self.probabilities, self.__ga_now_row + 3, 2, get_third_split],
[self.probabilities, self.__ga_now_row + 4, 2, get_third_split],
[self.probabilities, self.__ga_now_row + 6, 1, get_third_split],
[self.probabilities, self.__ga_now_row + 7, 2, get_third_split],
[self.probabilities, self.__ga_now_row + 8, 2, get_third_split],
[self.forecast, self.__kp_index_row + 3, 3, None],
[self.forecast, self.__kp_index_row + 4, 3, None],
[self.forecast, self.__kp_index_row + 5, 3, None],
[self.forecast, self.__kp_index_row + 6, 3, None],
[self.forecast, self.__kp_index_row + 7, 3, None],
[self.forecast, self.__kp_index_row + 8, 3, None],
[self.forecast, self.__kp_index_row + 9, 3, None],
[self.forecast, self.__kp_index_row + 10, 3, None]
]
]
def __get_lbl_value(self, data, row, col, f = None):
val = data[row][col]
if f is not None:
val = f(val)
val = val.lstrip('0').rstrip('%')
return val
def __is_integer(self, s):
try:
int(s)
except Exception:
return False
else:
return True
def __set_labels_values(self):
for lbl_list, table in zip(self.__all_lbls, self.__labels_table):
for lbl, row in zip(lbl_list, table):
lbl.switch_off()
value = self.__get_lbl_value(*row)
if self.__is_integer(value):
lbl.level = int(value)
if not isinstance(lbl, MultiColorSwitchableLabel):
value += '%'
lbl.setText(value)
lbl.switch_on()
def update_all_labels(self):
try:
self.__get_rows()
self.__split_lists()
self.__make_labels_table()
self.__set_dates()
self.__set_labels_values()
except Exception:
pass
def remove_data(self):
self.forecast = ''
self.probabilities = ''