28 Commits

Author SHA1 Message Date
Marco Dalla Tiezza
36648e08cc Updated message on preference window, bump Artemis version to 4.0.5 2024-06-15 12:09:14 +02:00
Marco Dalla Tiezza
d61caa1ada Removed unused imports, added date parser for db info data 2024-06-15 12:01:33 +02:00
Marco Dalla Tiezza
6fdd36b548 Code optimized: removed an additional db loading 2024-06-15 11:44:01 +02:00
Marco Dalla Tiezza
6213e8b1ca Added creation date and version of the db on db manager window 2024-06-15 00:55:05 +02:00
Marco Dalla Tiezza
d0bfbe40d7 New logic for local sigid search 2024-06-15 00:40:00 +02:00
Marco Dalla Tiezza
ebda950c87 Updated docs 2024-06-14 21:33:42 +02:00
Marco Dalla Tiezza
c803a72489 Updated changelog and resources (after merge) 2024-06-14 01:18:48 +02:00
Marco Dalla Tiezza
70758ea8bd Merge pull request #51 from AresValley/revised-networking
Revised network manager, update manager and downloader
2024-06-13 23:08:42 +00:00
Marco Dalla Tiezza
6c0b1b7e04 Merge branch 'master' into revised-networking 2024-06-13 23:07:30 +00:00
Marco Dalla Tiezza
c17c1fdbea Updated resources 2024-06-14 01:01:53 +02:00
Marco Dalla Tiezza
8138cf8e38 Docs update 2 2024-06-14 00:14:04 +02:00
Marco Dalla Tiezza
0b416f0a2e Docs update 2024-06-13 23:55:41 +02:00
Marco Dalla Tiezza
1d0c459402 Fixed a potential issue where the downloader window closes but the downloader instance keeps running 2024-06-13 19:19:21 +02:00
Marco Dalla Tiezza
0f898ff6f5 Introduced temporary folder 2024-06-13 16:16:36 +02:00
Marco Dalla Tiezza
c58d85c6a2 Fixed bug of space_weather introduced with the new update_manager 2024-06-13 15:52:47 +02:00
Marco Dalla Tiezza
f73034c35c Preparatory update for the new update manager 2024-06-13 15:46:56 +02:00
Marco Dalla Tiezza
77c43813a0 Final ieration of the new downloader and update_manager, implemented automatic updates only for windows 2024-06-13 15:46:25 +02:00
Marco Dalla Tiezza
b01bd99ecf Preparatory update for the new update manager 2024-06-13 14:57:36 +02:00
Marco Dalla Tiezza
146a5d1605 First implementation of the auto update for windows 2024-06-13 14:40:07 +02:00
Marco Dalla Tiezza
ea245c853b Updated changelog and documentation 2024-06-13 12:20:12 +02:00
Marco Dalla Tiezza
e57e9bf8e4 Artemis can be fully navigated (and so the signal list) just with the keyboard, fixed #50 2024-06-13 11:42:24 +02:00
Marco Dalla Tiezza
70ed158b02 Updated resources, fixed typo 2024-06-13 02:06:45 +02:00
Marco Dalla Tiezza
1d795b688e File size request from HTTP header, handled the indeterminate size case 2024-06-13 01:54:59 +02:00
Marco Dalla Tiezza
52c4fbcce9 Downloader has been generalized, now network_utils is update_utils 2024-06-13 01:05:53 +02:00
Marco Dalla Tiezza
79891899ce Improved label readability, handled non critical exception about filtering without a loaded DB 2024-06-12 23:16:48 +02:00
Marco Dalla Tiezza
dde223d2ac Updated documentation 2024-06-12 21:20:18 +02:00
Marco Dalla Tiezza
43ddee410a Removed unnecessay arguments (now default) for nuitka 2024-06-11 18:37:50 +02:00
Marco Dalla Tiezza
ecbf10ebb5 Rolling out v4.0.3 2024-06-10 22:20:37 +02:00
33 changed files with 1318 additions and 994 deletions

View File

@@ -5,6 +5,25 @@
## [Unreleased] ## [Unreleased]
## [4.0.5] - 2024-06-15
### Added
- Possibility to navigate Artemis just with the keyboard [#50](https://github.com/AresValley/Artemis/issues/50)
- **Windows:** automatic updates have been implemented. When a software update is available, Artemis will download the new version and install the updates automatically
- Multiple sigID databases can be conserved. In the case of autoload, the latest local version will be loaded
- Added creation date and DB version in DB manager window
### Changed
- Improved readability of labels for filter ranges for frequency, bandwidth, and ACF
- Improved Update manager and Downloader functionalities
- OS dependent temporary folders are now used for database download and artemis updates
- The logic for searching the last sigID database has changed, now the discriminant is no longer the folder name but is reported as a signature in the database itself (-1 in the editable field, see documentation)
- Old sigID databases are not deleted anymore when a new version is downloaded. This is to avoid removing databases with user changes or additions
### Fixed
- Added a database load check to avoid (non critical) exceptions when applying filters without having loaded a database.
- Fixed a potential issue involing the forcibly closure of downloader window but the downloader instance keeps running
- With the new logic in the latest sigID database search, manually imported sigID databases are officially recognized as proper ones
## [4.0.3] - 2024-06-10 ## [4.0.3] - 2024-06-10
@@ -101,8 +120,9 @@ First release.
<!-- Links definitions --> <!-- Links definitions -->
[Unreleased]: https://github.com/AresValley/Artemis/compare/v4.0.3...HEAD [Unreleased]: https://github.com/AresValley/Artemis/compare/v4.0.5...HEAD
[4.0.1]: https://github.com/AresValley/Artemis/compare/v4.0.1...v4.0.3 [4.0.5]: https://github.com/AresValley/Artemis/compare/v4.0.3...v4.0.5
[4.0.3]: https://github.com/AresValley/Artemis/compare/v4.0.1...v4.0.3
[4.0.1]: https://github.com/AresValley/Artemis/compare/v3.2.4...v4.0.1 [4.0.1]: https://github.com/AresValley/Artemis/compare/v3.2.4...v4.0.1
[3.2.4]: https://github.com/AresValley/Artemis/compare/v3.2.1...v3.2.4 [3.2.4]: https://github.com/AresValley/Artemis/compare/v3.2.1...v3.2.4
[3.2.3]: https://github.com/AresValley/Artemis/compare/v3.2.2...v3.2.3 [3.2.3]: https://github.com/AresValley/Artemis/compare/v3.2.2...v3.2.3

File diff suppressed because it is too large Load Diff

View File

@@ -6,10 +6,10 @@ from PySide6.QtCore import QObject, Slot, Signal
from artemis.utils.constants import Constants, Messages from artemis.utils.constants import Constants, Messages
from artemis.utils.sys_utils import open_directory, make_tar, unpack_tar from artemis.utils.sys_utils import open_directory, make_tar, unpack_tar
from artemis.utils.sql_utils import ArtemisDatabase, ArtemisSignal from artemis.utils.sql_utils import ArtemisDatabase, ArtemisSignal
from artemis.utils.path_utils import DATA_DIR from artemis.utils.update_utils import UpdateManager
from artemis.utils.network_utils import NetworkManager
from artemis.utils.generic_utils import generate_filter_query from artemis.utils.generic_utils import generate_filter_query
from artemis.utils.path_utils import normalize_dialog_path from artemis.utils.path_utils import normalize_dialog_path
from artemis.utils.path_utils import DATA_DIR
from artemis.utils.config_utils import CONFIGURE_QT from artemis.utils.config_utils import CONFIGURE_QT
from artemis.ui.preferences import UIPreferences from artemis.ui.preferences import UIPreferences
@@ -25,6 +25,7 @@ import artemis.resources
class UIArtemis(QObject): class UIArtemis(QObject):
# Python > QML Signals # Python > QML Signals
close_ui = Signal()
populate_sig_list = Signal(list) populate_sig_list = Signal(list)
populate_sig_details = Signal(list) populate_sig_details = Signal(list)
populate_filter_modulation = Signal(list) populate_filter_modulation = Signal(list)
@@ -37,7 +38,7 @@ class UIArtemis(QObject):
show_dialog_popup = Signal(str, str, str) show_dialog_popup = Signal(str, str, str)
show_dialog_download_db = Signal(str, str, str) show_dialog_download_db = Signal(str, str, str)
show_dialog_download_art = Signal(str, str, str) show_dialog_update_artemis = Signal(str, str, str, bool)
update_info_bar = Signal(str, str) update_info_bar = Signal(str, str)
@@ -62,13 +63,13 @@ class UIArtemis(QObject):
# Creating istances for other windows # Creating istances for other windows
self.preferences = UIPreferences(self) self.preferences = UIPreferences(self)
self.dbmanager = UIdbmanager(self) self.dbmanager = UIdbmanager(self)
self.downloader = UIDownloader(self)
self.spaceweather = UIspaceweather(self) self.spaceweather = UIspaceweather(self)
self.docmanager = UIdocumentsmanager(self) self.docmanager = UIdocumentsmanager(self)
self.sigeditor = UIsignaleditor(self) self.sigeditor = UIsignaleditor(self)
self.cateditor = UIcategoryeditor(self) self.cateditor = UIcategoryeditor(self)
self.downloader = UIDownloader(self)
self.network_manager = NetworkManager(self) self.update_manager = UpdateManager(self)
self.autoload_db() self.autoload_db()
@@ -79,8 +80,9 @@ class UIArtemis(QObject):
self._window.loadSignal.connect(self.load_sig) self._window.loadSignal.connect(self.load_sig)
self._window.showPref.connect(self.show_pref_ui) self._window.showPref.connect(self.show_pref_ui)
self._window.openSigEditor.connect(self.open_sig_editor) self._window.openSigEditor.connect(self.open_sig_editor)
self._window.startDownloader.connect(self.start_download_db) self._window.checkForUpdate.connect(self.check_for_update)
self._window.checkDbUpdates.connect(self.check_update_db) self._window.updateDb.connect(self.update_db)
self._window.updateArtemis.connect(self.update_artemis)
self._window.showSpaceWeather.connect(self.show_space_weather_ui) self._window.showSpaceWeather.connect(self.show_space_weather_ui)
self._window.openDbDirectory.connect(self.open_db_directory) self._window.openDbDirectory.connect(self.open_db_directory)
self._window.showCatManager.connect(self.open_cat_manager) self._window.showCatManager.connect(self.open_cat_manager)
@@ -98,12 +100,13 @@ class UIArtemis(QObject):
self._window_signal.addCatTag.connect(self.add_cat_tag) self._window_signal.addCatTag.connect(self.add_cat_tag)
# Python > QML connections # Python > QML connections
self.close_ui.connect(self._window.close)
self.populate_sig_list.connect(self._window.populateList) self.populate_sig_list.connect(self._window.populateList)
self.clear_list.connect(self._window.clearList) self.clear_list.connect(self._window.clearList)
self.update_info_bar.connect(self._window.bottomInfoBar) self.update_info_bar.connect(self._window.bottomInfoBar)
self.show_dialog_popup.connect(self._window.openGeneralDialog) self.show_dialog_popup.connect(self._window.openGeneralDialog)
self.show_dialog_download_db.connect(self._window.openDialogDownloadDb) self.show_dialog_download_db.connect(self._window.openDialogDownloadDb)
self.show_dialog_download_art.connect(self._window.openDialogDownloadArtemis) self.show_dialog_update_artemis.connect(self._window.openDialogUpdateArtemis)
self.lock_menu.connect(self._window.lockMenu) self.lock_menu.connect(self._window.lockMenu)
self.populate_sig_details.connect(self._window_signal.populateSignalParam) self.populate_sig_details.connect(self._window_signal.populateSignalParam)
@@ -168,17 +171,18 @@ class UIArtemis(QObject):
the details to generate a search query the details to generate a search query
""" """
filter_status = filter_status.toVariant() filter_status = filter_status.toVariant()
if filter_status != {}: if self.loaded_db is not None:
filter_query = generate_filter_query(filter_status) if filter_status != {}:
self.loaded_db.select_by_filter(filter_query) filter_query = generate_filter_query(filter_status)
self.loaded_db.select_by_filter(filter_query)
self.clear_signal_page.emit()
self.populate_sig_list.emit(self.loaded_db.all_signals) self.clear_signal_page.emit()
self.populate_sig_list.emit(self.loaded_db.all_signals)
total_signals = len(self.loaded_db.all_signals) total_signals = len(self.loaded_db.all_signals)
self.bottom_info_bar("FILTERS ACTIVE: {} signals found".format(total_signals), "warning") self.bottom_info_bar("FILTERS ACTIVE: {} signals found".format(total_signals), "warning")
else: else:
self.load_db(self.loaded_db.db_dir_name) self.load_db(self.loaded_db.db_dir_name)
def show_pref_ui(self): def show_pref_ui(self):
@@ -213,18 +217,10 @@ class UIArtemis(QObject):
self.docmanager.load_documentsmanager_ui() self.docmanager.load_documentsmanager_ui()
def check_update_db(self): def check_for_update(self):
""" User manual check for updates db updates """ User manual check for updates updates
""" """
self.network_manager.show_popup = True self.update_manager.check_updates(True)
self.network_manager.check_updates()
def start_download_db(self):
""" Show the downloader and start the download of the sigid db
"""
self.downloader.show_ui.emit()
self.downloader.on_start()
def dialog_download_db(self, message_type, title, message): def dialog_download_db(self, message_type, title, message):
@@ -233,10 +229,24 @@ class UIArtemis(QObject):
self.show_dialog_download_db.emit(message_type, title, message) self.show_dialog_download_db.emit(message_type, title, message)
def dialog_download_artemis(self, message_type, title, message): def dialog_update_artemis(self, message_type, title, message, auto=False):
""" Dialog popup for artemis download confirmation """ Dialog popup for Artemis download confirmation
""" """
self.show_dialog_download_art.emit(message_type, title, message) self.show_dialog_update_artemis.emit(message_type, title, message, auto)
@Slot()
def update_db(self):
""" Start the download of the sigID DB
"""
self.update_manager.download_db()
@Slot()
def update_artemis(self):
""" Start the download of Artemis
"""
self.update_manager.download_artemis()
def open_db_directory(self): def open_db_directory(self):
@@ -335,10 +345,13 @@ class UIArtemis(QObject):
def autoload_db(self): def autoload_db(self):
sig_id_path = DATA_DIR / 'SigID' / Constants.SQL_NAME """ This will autoload the latest local sigID DB, if present
according to the user settings
"""
sig_id_db = self.dbmanager.get_latest_local_sigid_db()
autoload = CONFIGURE_QT.value("Database", "autoload", 0) autoload = CONFIGURE_QT.value("Database", "autoload", 0)
if sig_id_path.exists() and int(autoload): if sig_id_db is not None and int(autoload):
self.load_db('SigID') self.load_db(sig_id_db.db_dir_name)
def dialog_popup(self, message_type, title, message): def dialog_popup(self, message_type, title, message):

View File

@@ -6,7 +6,6 @@ from PySide6.QtCore import QObject, Signal, Slot
from artemis.utils.path_utils import DATA_DIR from artemis.utils.path_utils import DATA_DIR
from artemis.utils.generic_utils import * from artemis.utils.generic_utils import *
from artemis.utils.sql_utils import ArtemisDatabase from artemis.utils.sql_utils import ArtemisDatabase
from artemis.utils.constants import Constants
from artemis.utils.sys_utils import delete_dir from artemis.utils.sys_utils import delete_dir
@@ -49,8 +48,24 @@ class UIdbmanager(QObject):
def load_local_db_list(self): def load_local_db_list(self):
""" Scan for all the valid DBs in the data folder and show them on the list """ Scan for all the valid DBs in the data folder and show them on the list
""" """
db_param = []
valid_db_list = self.scan_db_dir() valid_db_list = self.scan_db_dir()
self.populate_db_list.emit(valid_db_list)
for db in valid_db_list:
db_param.append(
{
'name': db.name,
'version': db.version,
'date': parse_date(db.date),
'db_dir_name': db.db_dir_name,
'documents_n': db.stats['documents'],
'signals_n': db.stats['signals'],
'images_n': db.stats['images'],
'audio_n': db.stats['audio']
}
)
self.populate_db_list.emit(db_param)
def load_db(self, db_dir_name): def load_db(self, db_dir_name):
@@ -85,43 +100,32 @@ class UIdbmanager(QObject):
def scan_db_dir(self): def scan_db_dir(self):
""" Scans the data directory for valid databases and """ Scans the data directory for valid databases and
return a dictionary containing only the valid ones with a summary return a dictionary containing only the valid ones.
Returns a list of objects (dbs)
""" """
valid_db_list = [] valid_db_list = []
db_dirs = next(os.walk(DATA_DIR))[1] db_dirs = next(os.walk(DATA_DIR))[1]
for db_dir_name in db_dirs: for db_dir_name in db_dirs:
if self._valid_db(db_dir_name): try:
database = ArtemisDatabase(db_dir_name) database = ArtemisDatabase(db_dir_name)
database.load() database.load()
valid_db_list.append( valid_db_list.append(database)
{ except:
'name': database.name, continue
'db_dir_name': database.db_dir_name,
'documents_n': database.stats['documents'],
'signals_n': database.stats['signals'],
'images_n': database.stats['images'],
'audio_n': database.stats['audio']
}
)
return valid_db_list return valid_db_list
def _valid_db(self, db_dir_name): def get_latest_local_sigid_db(self):
""" Checks if db_dir_name is a valid db dir containing a `data.sqlite` file. """ Return the newest valid local sigID database.
Db must be valid as well and should be properly initialized and loaded with Returns None if no valid sigID database is found.
no errors.
Args:
db_dir_name (str): name of the db folder
""" """
if os.path.exists(DATA_DIR / db_dir_name / Constants.SQL_NAME): valid_dbs = self._parent.dbmanager.scan_db_dir()
try: sig_id_dbs = [db for db in valid_dbs if db.editable == -1]
database = ArtemisDatabase(db_dir_name)
database.load() if len(sig_id_dbs) != 0:
return True sig_id_latest = max(sig_id_dbs, key=lambda x: x.version)
except: return sig_id_latest
return False # Invalid or corrupted DB
else: else:
return False # The dir is not containing a data.sqlite file return None

View File

@@ -1,11 +1,10 @@
import requests
from PySide6.QtQml import QQmlApplicationEngine from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtCore import QObject, Slot, Signal, QUrl, QSaveFile, QDir, QIODevice from PySide6.QtCore import QObject, Slot, Signal, QUrl, QSaveFile, QDir, QIODevice
from PySide6.QtNetwork import QNetworkReply, QNetworkRequest, QNetworkAccessManager from PySide6.QtNetwork import QNetworkReply, QNetworkRequest, QNetworkAccessManager
from artemis.utils.config_utils import *
from artemis.utils.sys_utils import delete_file, delete_dir, match_hash, unpack_tar
from artemis.utils.constants import Messages from artemis.utils.constants import Messages
from artemis.utils.path_utils import DATA_DIR
class UIDownloader(QObject): class UIDownloader(QObject):
@@ -13,7 +12,9 @@ class UIDownloader(QObject):
show_ui = Signal() show_ui = Signal()
close_ui = Signal() close_ui = Signal()
update_progress_bar = Signal(int, int) update_progress_bar = Signal(int, int)
set_indeterminate_bar = Signal()
update_status = Signal(str) update_status = Signal(str)
finished = Signal()
def __init__(self, parent): def __init__(self, parent):
@@ -25,6 +26,13 @@ class UIDownloader(QObject):
self._engine.load('qrc:/ui/Downloader.qml') self._engine.load('qrc:/ui/Downloader.qml')
self._window = self._engine.rootObjects()[0] self._window = self._engine.rootObjects()[0]
self.file_url = None
self.file_size = None
self.dest_file = None
self.file = None
self.manager = None
self.reply = None
self._connect() self._connect()
@@ -36,50 +44,54 @@ class UIDownloader(QObject):
self.show_ui.connect(self._window.show) self.show_ui.connect(self._window.show)
self.close_ui.connect(self._window.close) self.close_ui.connect(self._window.close)
self.update_progress_bar.connect(self._window.updateProgressBar) self.update_progress_bar.connect(self._window.updateProgressBar)
self.set_indeterminate_bar.connect(self._window.setIndeterminateBar)
self.update_status.connect(self._window.updateStatus) self.update_status.connect(self._window.updateStatus)
@Slot() def on_start(self, url, save_path):
def on_start(self): """ Start the download process using the specified URL
""" Start the download of the DB taking the needed url and size from
the attributes of the UpdatesController class Args:
url (str): url from where download the file
save_path (str): path where to save the downloaded file
""" """
url_file = QUrl(self._parent.network_manager.remote_db_url) self._clear_ui()
dest_path = QDir(DATA_DIR) self.show_ui.emit()
self.dest_file = dest_path.filePath(url_file.fileName())
self.file_url = QUrl(url)
self.file_size = self._get_filesize(url)
dest_path = QDir(save_path)
self.dest_file = dest_path.filePath(self.file_url.fileName())
self.file = QSaveFile(self.dest_file) self.file = QSaveFile(self.dest_file)
if self.file.open(QIODevice.WriteOnly): if self.file.open(QIODevice.WriteOnly):
# Start a GET HTTP request # Start a GET HTTP request
self.manager = QNetworkAccessManager(self) self.manager = QNetworkAccessManager(self)
self.reply = self.manager.get(QNetworkRequest(url_file)) self.reply = self.manager.get(QNetworkRequest(self.file_url))
self.reply.downloadProgress.connect(self.on_progress) self.reply.downloadProgress.connect(self.on_progress)
self.reply.finished.connect(self.on_finished) self.reply.finished.connect(self.on_finished)
self.reply.readyRead.connect(self.on_ready_read) self.reply.readyRead.connect(self.on_ready_read)
self.reply.errorOccurred.connect(self.on_error) self.reply.errorOccurred.connect(self.on_error)
else: else:
self.close_ui.emit() self.close_ui.emit()
self.show_popup_error( self.show_popup_error(self.file.errorString())
self.file.errorString()
)
@Slot() @Slot()
def on_abort(self): def on_abort(self):
""" Stop the download when user press abort button """ """ Stop the download when user presses the abort button
"""
if self.reply: if self.reply:
self.reply.abort() self.reply.abort()
self.update_progress_bar.emit(0, 0)
if self.file: if self.file:
self.file.cancelWriting() self.file.cancelWriting()
self.close_ui.emit()
@Slot() @Slot()
def on_ready_read(self): def on_ready_read(self):
""" Get available bytes and store them into the file """ """ Write available bytes to the file
"""
if self.reply: if self.reply:
if self.reply.error() == QNetworkReply.NoError: if self.reply.error() == QNetworkReply.NoError:
self.file.write(self.reply.readAll()) self.file.write(self.reply.readAll())
@@ -87,33 +99,31 @@ class UIDownloader(QObject):
@Slot() @Slot()
def on_finished(self): def on_finished(self):
""" Delete reply, close the file, check the hash for integrity, """ Finalize the download process and, if no errors
extract the database and delete the downloaded zip occurs, emits the finished signal usefull for
a callback
""" """
if self.reply: if self.reply:
self.reply.deleteLater() self.reply.deleteLater()
if self.file: if self.file:
self.file.commit() self.file.commit()
if self.reply.error() == QNetworkReply.NoError:
self.finished.emit()
self.update_status.emit("Checking DB integrity (SHA-256)") self.close_ui.emit()
if match_hash(self.dest_file, self._parent.network_manager.remote_db_hash):
self.update_status.emit("Unpacking archive...")
delete_dir(DATA_DIR / 'SigID')
unpack_tar(self.dest_file, DATA_DIR / 'SigID')
delete_file(self.dest_file)
self._parent.load_db('SigID')
self.close_ui.emit()
@Slot(int, int) @Slot(int, int)
def on_progress(self, bytesReceived: int): def on_progress(self, bytesReceived: int):
""" Update progress bar and label """ Update progress bar and status label
""" """
total_bytes = self._parent.network_manager.remote_db_size if self.file_size is not None:
self.update_status.emit("{:.1f} Mb / {:.1f} Mb".format(bytesReceived/10**6, total_bytes/10**6)) self.update_status.emit("{:.1f} Mb / {:.1f} Mb".format(bytesReceived/10**6, self.file_size/10**6))
self.update_progress_bar.emit(bytesReceived, total_bytes) self.update_progress_bar.emit(bytesReceived, self.file_size)
else:
self.update_status.emit("{:.1f} Mb".format(bytesReceived/10**6))
@Slot(QNetworkReply.NetworkError) @Slot(QNetworkReply.NetworkError)
@@ -127,6 +137,28 @@ class UIDownloader(QObject):
) )
def _get_filesize(self, url):
""" Get the file size by sending a HEAD request to the URL.
If the Content-Length in HTTP headers is missing, returns None
and set the progress_bar as 'indeterminate' like a 'busy indicator'
Args:
url (str): URL to check the file size
"""
try:
response = requests.get(url, stream=True)
size = int(response.headers.get('content-length'))
return size
except:
self.set_indeterminate_bar.emit()
return None
def _clear_ui(self):
self.update_progress_bar.emit(0, 0)
self.update_status.emit('')
def show_popup_error(self, error_msg): def show_popup_error(self, error_msg):
self._parent.dialog_popup( self._parent.dialog_popup(
Messages.DIALOG_TYPE_ERROR, Messages.DIALOG_TYPE_ERROR,

View File

@@ -53,10 +53,10 @@ class UIspaceweather(QObject):
def download_poseidon_report(self): def download_poseidon_report(self):
network_manager = self._parent.network_manager update_manager = self._parent.update_manager
network_manager.show_popup = True poseidon_data = update_manager.fetch_remote_json(
poseidon_data = network_manager.fetch_remote_json( Constants.POSEIDON_REPORT_URL,
Constants.POSEIDON_REPORT_URL True
) )
if poseidon_data: if poseidon_data:
self.load_poseidon_report.emit(poseidon_data) self.load_poseidon_report.emit(poseidon_data)

View File

@@ -10,7 +10,7 @@ class Constants():
APPLICATION_NAME = 'Artemis' APPLICATION_NAME = 'Artemis'
ORGANIZATION_NAME = 'AresValley' ORGANIZATION_NAME = 'AresValley'
ORGANIZATION_DOMAIN = 'aresvalley.com' ORGANIZATION_DOMAIN = 'aresvalley.com'
APPLICATION_VERSION = '4.0.3' APPLICATION_VERSION = '4.0.5'
SQL_NAME = 'data.sqlite' SQL_NAME = 'data.sqlite'
@@ -39,6 +39,7 @@ class Messages:
UP_TO_DATE = "You're up to date!" UP_TO_DATE = "You're up to date!"
DB_NEW_VER = "New SigID DB version available!" DB_NEW_VER = "New SigID DB version available!"
ART_NEW_VER = "New Artemis version available!" ART_NEW_VER = "New Artemis version available!"
DB_CORRUPTED = "Database Corruption Detected"
# Messages # Messages
DB_CREATION_SUCCESS_MSG = "The new database has been created succesfully." DB_CREATION_SUCCESS_MSG = "The new database has been created succesfully."
@@ -50,8 +51,10 @@ class Messages:
NO_CONNECTION_MSG = "Unable to check for updates. It appears that there is a problem with your internet connection. Please check your network settings and try again later. {}" NO_CONNECTION_MSG = "Unable to check for updates. It appears that there is a problem with your internet connection. Please check your network settings and try again later. {}"
UP_TO_DATE_MSG = "The latest version of Artemis and SigID wiki is installed on your computer." UP_TO_DATE_MSG = "The latest version of Artemis and SigID wiki is installed on your computer."
DB_NEW_VER_MSG = "A new version of the database ({}) is available for download. Download now?" DB_NEW_VER_MSG = "A new version of the database ({}) is available for download. Download now?"
ART_NEW_VER_MSG = "A new version of Artemis ({}) is available for download. Check GitHub page now?" ART_NEW_VER_MANUAL_MSG = "A new version of Artemis ({}) is available for download. Check GitHub page now?"
DOWNLOAD_CORRUPTED_MSG = "Downloaded data corrupted or invalid. Please retry." ART_NEW_VER_AUTO_MSG = "A new version of Artemis ({}) is available for download. Update Artemis now?"
DB_CORRUPTED_MSG = "Downloaded data corrupted or invalid. Please retry."
DB_DOWNLOAD_SUCCESS_MSG = "The database has been successfully downloaded and is now being loaded."
class Query(): class Query():

View File

@@ -1,3 +1,5 @@
from datetime import datetime
from artemis.utils.constants import Query from artemis.utils.constants import Query
@@ -75,3 +77,17 @@ def generate_filter_query(filer_status):
)) ))
return ' INTERSECT '.join(query) return ' INTERSECT '.join(query)
def parse_date(date_str):
""" Parses a date string in "%Y-%m-%d %H:%M:%S.%f" format and returns
the date in "YYYY-MM-DD" format. If parsing fails, returns the original string.
Args:
date_str (str): The date string to parse.
"""
try:
form_date = datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S.%f")
return str(form_date.date())
except ValueError:
return date_str

View File

@@ -1,135 +0,0 @@
import os
import requests
from packaging.version import Version
from artemis.utils.constants import Constants, Messages
from artemis.utils.sql_utils import ArtemisDatabase
from artemis.utils.sys_utils import is_windows, is_linux, is_macos
from artemis.utils.path_utils import DATA_DIR
class NetworkManager:
""" Class that checks for DB or software updates
"""
def __init__(self, parent):
self._parent = parent
self.sigid_db_path = DATA_DIR / 'SigID' / Constants.SQL_NAME
self.show_popup = False
self.db_update = None
self.art_update = None
self.remote_db_url = None
self.remote_db_hash = None
self.remote_db_version = None
self.remote_db_size = None
self.remote_art_version = None
self.check_updates()
def check_updates(self):
""" Checks if a new DB update is available.
Args:
popup (bool, optional): Suppress the "already up-to-date" message on startup.
Defaults to False.
"""
latest_json = self.fetch_remote_json(Constants.LATEST_VERSION_URL)
if latest_json:
local_db = self.load_local_db()
remote_db = latest_json['sigID_DB']
self.remote_db_version = remote_db['version']
self.remote_db_url = remote_db['url']
self.remote_db_hash = remote_db['sha256_hash']
self.remote_db_size = remote_db['total_bytes']
if is_windows():
self.remote_art_version = latest_json['windows']['version']
elif is_linux():
self.remote_art_version = latest_json['linux']['version']
elif is_macos():
self.remote_art_version = latest_json['mac']['version']
if Version(self.remote_art_version) > Version(Constants.APPLICATION_VERSION):
self.art_update = True
else:
self.art_update = False
if self.art_update:
self.show_popup_art_update()
else:
if local_db:
if self.remote_db_version > local_db.version:
self.show_popup_db_update()
elif self.show_popup:
self.show_popup_up_to_date()
else:
self.show_popup_initial_db_download()
def fetch_remote_json(self, url):
""" Fetches the remote json from a url
"""
try:
response = requests.get(url)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
if self.show_popup:
self._parent.dialog_popup(
Messages.DIALOG_TYPE_ERROR,
Messages.NO_CONNECTION,
Messages.NO_CONNECTION_MSG.format(e)
)
return None
def load_local_db(self):
""" Loads the local database if exists
"""
if os.path.exists(self.sigid_db_path):
local_db = ArtemisDatabase('SigID')
local_db.load()
return local_db
return None
def show_popup_db_update(self):
"""Prompts the user to download the updated version of the database."""
self._parent.dialog_download_db(
Messages.DIALOG_TYPE_WARN,
Messages.DB_NEW_VER,
Messages.DB_NEW_VER_MSG.format(self.remote_db_version)
)
def show_popup_art_update(self):
"""Prompts the user to download the updated version of the database."""
self._parent.dialog_download_artemis(
Messages.DIALOG_TYPE_WARN,
Messages.ART_NEW_VER,
Messages.ART_NEW_VER_MSG.format(self.remote_art_version)
)
def show_popup_up_to_date(self):
"""Notifies the user that the database is up to date."""
self._parent.dialog_popup(
Messages.DIALOG_TYPE_INFO,
Messages.UP_TO_DATE,
Messages.UP_TO_DATE_MSG
)
def show_popup_initial_db_download(self):
"""Prompts the user to download the database for the first time."""
self._parent.dialog_download_db(
Messages.DIALOG_TYPE_QUEST,
Messages.NO_DB_DETECTED,
Messages.NO_DB_DETECTED_MSG
)

View File

@@ -36,6 +36,15 @@ def _data_dir():
return data_dir_path return data_dir_path
def _tmp_dir():
if is_windows():
tmp_dir_path = Path.home() / 'AppData' / 'Local' / 'Temp'
else:
tmp_dir_path = Path('/tmp')
return tmp_dir_path
def _preference_dir(): def _preference_dir():
preference_dir_path = APP_DIR / 'config' preference_dir_path = APP_DIR / 'config'
if not preference_dir_path.exists(): if not preference_dir_path.exists():
@@ -46,4 +55,5 @@ def _preference_dir():
BASE_DIR = Path(os.path.dirname(__file__)) / '../..' BASE_DIR = Path(os.path.dirname(__file__)) / '../..'
APP_DIR = _app_dir() APP_DIR = _app_dir()
DATA_DIR = _data_dir() DATA_DIR = _data_dir()
TMP_DIR = _tmp_dir()
PREFERENCES_DIR = _preference_dir() PREFERENCES_DIR = _preference_dir()

View File

@@ -7,8 +7,8 @@ from datetime import datetime
from contextlib import closing from contextlib import closing
from artemis.utils.constants import Query, Constants from artemis.utils.constants import Query, Constants
from artemis.utils.path_utils import DATA_DIR
from artemis.utils.generic_utils import format_frequency from artemis.utils.generic_utils import format_frequency
from artemis.utils.path_utils import DATA_DIR
class Database(): class Database():
@@ -141,7 +141,7 @@ class ArtemisDatabase(Database):
""" Create new db in the data folder. """ Create new db in the data folder.
The name of folder containing the new db has a unique id as name (db_dir_name). The name of folder containing the new db has a unique id as name (db_dir_name).
""" """
meta = [name, datetime.now(), 0, 0] meta = [name, datetime.now(), 1, 1]
os.makedirs(self.db_dir) os.makedirs(self.db_dir)
os.makedirs(self.media_dir) os.makedirs(self.media_dir)

View File

@@ -0,0 +1,222 @@
import uuid
import requests
from packaging.version import Version
from artemis.utils.constants import Constants, Messages
from artemis.utils.sys_utils import is_windows, is_linux, is_macos, delete_file, match_hash, unpack_tar, open_file
from artemis.utils.path_utils import DATA_DIR, TMP_DIR
class UpdateManager:
""" Class used to manage DB and software updates
"""
def __init__(self, parent):
self._parent = parent
self.db_update = None
self.art_update = None
self.remote_db_url = None
self.remote_db_hash = None
self.remote_db_version = None
self.remote_db_size = None
self.remote_db_file_name = None
self.remote_artemis_version = None
self.remote_artemis_url = None
self.remote_artemis_file_name = None
self.check_updates()
def check_updates(self, show_popup=False):
""" Checks if a software or DB update is available.
Prioritize Artemis updates over the DB one.
Args:
show_popup (bool, optional):
If False, suppress the "already up-to-date" message on startup.
Defaults to False. True is usefull when the user manual check for
updates.
"""
latest_json = self.fetch_remote_json(Constants.LATEST_VERSION_URL, show_popup)
if latest_json:
local_db = self._parent.dbmanager.get_latest_local_sigid_db()
remote_db = latest_json['sigID_DB']
self.remote_db_version = remote_db['version']
self.remote_db_url = remote_db['url']
self.remote_db_hash = remote_db['sha256_hash']
self.remote_db_size = remote_db['total_bytes']
self.remote_db_file_name = self.remote_db_url.split('/')[-1]
if is_windows():
self.remote_artemis_version = latest_json['windows']['version']
self.remote_artemis_url = latest_json['windows']['url']
elif is_linux():
self.remote_artemis_version = latest_json['linux']['version']
self.remote_artemis_url = latest_json['linux']['url']
elif is_macos():
self.remote_artemis_version = latest_json['mac']['version']
self.remote_artemis_url = latest_json['mac']['url']
self.remote_artemis_file_name = self.remote_artemis_url.split('/')[-1]
if Version(self.remote_artemis_version) > Version(Constants.APPLICATION_VERSION):
self.art_update = True
else:
self.art_update = False
if self.art_update:
self._show_popup_art_update()
else:
if local_db:
if self.remote_db_version > local_db.version:
self._show_popup_db_update()
elif show_popup:
self._show_popup_up_to_date()
else:
self._show_popup_initial_db_download()
def fetch_remote_json(self, url, show_popup=False):
""" Fetches the remote json from a url
Args:
show_popup (bool, optional): If false, suppress any error message
Defaults to False (to avoid error if the program is used offline)
"""
try:
response = requests.get(url)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
if show_popup:
self._parent.dialog_popup(
Messages.DIALOG_TYPE_ERROR,
Messages.NO_CONNECTION,
Messages.NO_CONNECTION_MSG.format(e)
)
return None
def download_db(self):
""" Open the downloader and download the sigID database in the
TMP_DIR folder. After a succesfull download the callback function
from the downloader is post_download_db
"""
self._parent.downloader.finished.connect(self.post_download_db)
self._parent.downloader.on_start(
self.remote_db_url,
TMP_DIR
)
def post_download_db(self):
""" After a succesfull DB download, this function check the hash
for possible corrupted data, delete old sigID DB and extract
the new one
"""
latest_db_tar_path = TMP_DIR / self.remote_db_file_name
if match_hash(latest_db_tar_path, self.remote_db_hash):
db_dir_name = str(uuid.uuid4())
unpack_tar(latest_db_tar_path, DATA_DIR / db_dir_name)
self._parent.load_db(db_dir_name)
self._show_popup_db_download_complete()
else:
self._show_popup_db_hash_failed()
delete_file(latest_db_tar_path)
def download_artemis(self):
""" Open the downloader and download Artemis in the
TMP_DIR folder. After a succesfull download the callback function
from the downloader is post_download_artemis
"""
self._parent.downloader.finished.connect(self.post_download_artemis)
self._parent.downloader.on_start(
self.remote_artemis_url,
TMP_DIR
)
def post_download_artemis(self):
""" After a succesfull Artemis download, this open the installer
and close the application
"""
if is_windows():
open_file(TMP_DIR / self.remote_artemis_file_name)
self._parent.close_ui.emit()
def _show_popup_db_update(self):
""" Prompts the user to download the updated version of the database
"""
self._parent.dialog_download_db(
Messages.DIALOG_TYPE_WARN,
Messages.DB_NEW_VER,
Messages.DB_NEW_VER_MSG.format(self.remote_db_version)
)
def _show_popup_art_update(self):
""" Alerts the user of a new version of Artemis.
Windows - asks to download with automatic update
Linux, macOS - redirects to GitHub page
"""
if is_windows():
self._parent.dialog_update_artemis(
Messages.DIALOG_TYPE_QUEST,
Messages.ART_NEW_VER,
Messages.ART_NEW_VER_AUTO_MSG.format(self.remote_artemis_version),
True
)
else:
self._parent.dialog_update_artemis(
Messages.DIALOG_TYPE_QUEST,
Messages.ART_NEW_VER,
Messages.ART_NEW_VER_MANUAL_MSG.format(self.remote_artemis_version),
False
)
def _show_popup_up_to_date(self):
""" Notifies the user that the database is up to date
"""
self._parent.dialog_popup(
Messages.DIALOG_TYPE_INFO,
Messages.UP_TO_DATE,
Messages.UP_TO_DATE_MSG
)
def _show_popup_initial_db_download(self):
""" Prompts the user to download the database for the first time
"""
self._parent.dialog_download_db(
Messages.DIALOG_TYPE_QUEST,
Messages.NO_DB_DETECTED,
Messages.NO_DB_DETECTED_MSG
)
def _show_popup_db_download_complete(self):
""" DB has been succesfully downloaded
"""
self._parent.dialog_popup(
Messages.DIALOG_TYPE_INFO,
Messages.GENERIC_SUCCESS,
Messages.DB_DOWNLOAD_SUCCESS_MSG
)
def _show_popup_db_hash_failed(self):
""" Notify the user after detection of a corrupted database
"""
self._parent.dialog_popup(
Messages.DIALOG_TYPE_ERROR,
Messages.DB_CORRUPTED,
Messages.DB_CORRUPTED_MSG
)

View File

@@ -9,7 +9,6 @@ pip install nuitka==2.3
echo "Building with Nuitka ..." echo "Building with Nuitka ..."
python -m nuitka app.py \ python -m nuitka app.py \
--standalone \ --standalone \
--follow-imports \
--show-modules \ --show-modules \
--assume-yes-for-downloads \ --assume-yes-for-downloads \
--enable-plugin=pyside6 \ --enable-plugin=pyside6 \
@@ -18,7 +17,6 @@ python -m nuitka app.py \
--noinclude-dlls=libQt6Sensors* \ --noinclude-dlls=libQt6Sensors* \
--noinclude-dlls=libQt6Test* \ --noinclude-dlls=libQt6Test* \
--noinclude-dlls=libQt6WebEngine* \ --noinclude-dlls=libQt6WebEngine* \
--include-qt-plugins=sensible \
--include-qt-plugins=styles \ --include-qt-plugins=styles \
--include-qt-plugins=qml \ --include-qt-plugins=qml \
--include-qt-plugins=multimedia \ --include-qt-plugins=multimedia \
@@ -29,6 +27,4 @@ python -m nuitka app.py \
--force-stderr-spec="{TEMP}/artemis.err.log" \ --force-stderr-spec="{TEMP}/artemis.err.log" \
--force-stdout-spec="{TEMP}/artemis.out.log" --force-stdout-spec="{TEMP}/artemis.out.log"
chmod 755 ./app.dist/app.bin
echo "Building Linux target finished." echo "Building Linux target finished."

View File

@@ -7,7 +7,6 @@ pip install nuitka==2.3
Write-Output "Building with Nuitka ..." Write-Output "Building with Nuitka ..."
python -m nuitka app.py ` python -m nuitka app.py `
--standalone ` --standalone `
--follow-imports `
--show-modules ` --show-modules `
--assume-yes-for-downloads ` --assume-yes-for-downloads `
--windows-console-mode=disable ` --windows-console-mode=disable `
@@ -17,7 +16,6 @@ python -m nuitka app.py `
--noinclude-dlls="Qt6Sensors*" ` --noinclude-dlls="Qt6Sensors*" `
--noinclude-dlls="Qt6Test*" ` --noinclude-dlls="Qt6Test*" `
--noinclude-dlls="Qt6WebEngine*" ` --noinclude-dlls="Qt6WebEngine*" `
--include-qt-plugins=sensible `
--include-qt-plugins=styles ` --include-qt-plugins=styles `
--include-qt-plugins=qml ` --include-qt-plugins=qml `
--include-qt-plugins=multimedia ` --include-qt-plugins=multimedia `
@@ -27,8 +25,8 @@ python -m nuitka app.py `
--force-stdout-spec="{TEMP}\artemis.out.log" ` --force-stdout-spec="{TEMP}\artemis.out.log" `
--windows-company-name=Aresvalley.com ` --windows-company-name=Aresvalley.com `
--windows-product-name=Artemis ` --windows-product-name=Artemis `
--windows-file-version=4.0.3 ` --windows-file-version=4.0.5 `
--windows-product-version=4.0.3 ` --windows-product-version=4.0.5 `
--windows-file-description=Artemis ` --windows-file-description=Artemis `
--windows-icon-from-ico=images\artemis_icon.ico --windows-icon-from-ico=images\artemis_icon.ico

View File

@@ -1,5 +1,5 @@
#define MyAppName "Artemis" #define MyAppName "Artemis"
#define MyAppVersion "4.0.3" #define MyAppVersion "4.0.5"
#define MyAppPublisher "AresValley" #define MyAppPublisher "AresValley"
#define MyAppURL "https://www.aresvalley.com/" #define MyAppURL "https://www.aresvalley.com/"
#define MyAppExeName "artemis.exe" #define MyAppExeName "artemis.exe"

View File

@@ -23,6 +23,6 @@ python -m nuitka app.py \
--macos-app-name=Artemis \ --macos-app-name=Artemis \
--macos-app-mode=gui \ --macos-app-mode=gui \
--macos-sign-identity=ad-hoc \ --macos-sign-identity=ad-hoc \
--macos-app-version=4.0.3 --macos-app-version=4.0.5
echo "Building Linux target finished." echo "Building Linux target finished."

View File

@@ -2,16 +2,19 @@
"sigID_DB": { "sigID_DB": {
"version": 61, "version": 61,
"url": "https://github.com/AresValley/Artemis-DB/releases/download/v61/v61.tar", "url": "https://github.com/AresValley/Artemis-DB/releases/download/v61/v61.tar",
"sha256_hash": "c65d2ab65e9420cd7789190c100abef2f1575ea15489776c2d97b5b09cdc8410", "sha256_hash": "da4d0f56924940f90b86349bf7a22a478bcf08e969d8de2bcb34e2939e74c885",
"total_bytes": 244449280 "total_bytes": 244449280
}, },
"windows": { "windows": {
"version": "4.0.1" "version": "4.0.3",
"url": "https://github.com/AresValley/Artemis/releases/download/v4.0.3/Artemis-Windows-x86_64-4.0.3.exe"
}, },
"linux": { "linux": {
"version": "4.0.1" "version": "4.0.3",
"url": "https://github.com/AresValley/Artemis/releases/download/v4.0.3/Artemis-Linux-x86_64-4.0.3.zip"
}, },
"mac": { "mac": {
"version": "4.0.0" "version": "4.0.0",
"url": ""
} }
} }

View File

@@ -33,7 +33,16 @@ where $Y_{i+τ}$ is a lagged data by $τ$ of $Y_i$ and $\bar{Y}$ is the average
## Example: ACF Analysis ## Example: ACF Analysis
### STANAG 4285 ### STANAG 4285
The first example will be a NATO standard known as STANAG 4285. You can download a .wav sample from HERE. We start to collect some information about the structure of this signal: a good starting point is the official declassified NATO document dated 1989. The main frame structure can be found at **Annex A-3/5** with a graphical view at **Annex A-7**, also reported below: The first example will be a NATO standard known as STANAG 4285. You can download a .wav sample from [:material-download: HERE](assets/stanag_4285.wav).
<div align="center">
<audio controls>
<source src="https://raw.githubusercontent.com/AresValley/Artemis/master/docs/assets/stanag_4285.wav" type="audio/wav">
Your browser does not support the audio player.
</audio>
</div>
We start to collect some information about the structure of this signal: a good starting point is the official declassified NATO document dated 1989. The main frame structure can be found at **Annex A-3/5** with a graphical view at **Annex A-7**, also reported below:
* [STANAG 4285 (i)](assets/acf_2.png) * [STANAG 4285 (i)](assets/acf_2.png)
* [STANAG 4285 A-3](assets/acf_3.png) * [STANAG 4285 A-3](assets/acf_3.png)

BIN
docs/assets/stanag_4285.wav Normal file

Binary file not shown.

View File

@@ -5,21 +5,58 @@
## 1. Main Menu ## 1. Main Menu
### File ### File
* **New Database:** Create a new database.
* **Load Database:** Open the Database Manager windows in order to open, rename, or delete a database. * **New Database**
* **Import Database:** Import an Artemis database with a standard .tar format.
* **Export Database:** Export the loaded database with a standard .tar format. Create a new database.
* **Edit Tags:** Open the tags editor window. From here, you can add, rename, or delete tags. The tags can be added to a signal from the [tags menu](#4-tags)
* **Open Database Folder:** Shows the folder of the currently loaded database in the explorer. * **Load Database**
* **Preferences:** Open the program settings window.
* **Exit:** This will close the application. Open the Database Manager windows in order to open, rename, or delete a database.
* **Import Database**
Import an Artemis database with a standard .tar format.
!!! tip "Offline Importing of SigID Database"
Sometimes it may happen that a computer does not have network access and unfortunately Artemis cannot download the SigID database. To solve this you can:
1. Download the .tar database [:material-download: HERE](https://github.com/AresValley/Artemis-DB/releases) from a PC with an internet access
2. Import the downloaded .tar on the target PC (without internet access) using the **Import Database** function
* **Export Database**
Export the loaded database with a standard .tar format.
* **Edit Tags**
Open the tags editor window. From here, you can add, rename, or delete tags. The tags can be added to a signal from the [tags menu](#4-tags)
* **Open Database Folder**
Shows the folder of the currently loaded database in the explorer.
* **Preferences**
Open the program settings window.
* **Exit**
This will close the application.
### Signal ### Signal
* **New:** Add a new signal to the database. * **New**
* **Edit:** Edit the current/selected signal from the loaded database.
Add a new signal to the database.
* **Edit**
Edit the current/selected signal from the loaded database.
### Space Weather ### Space Weather
* **Check Report:** Open the main [Space Weather window](space_weather/current.md) and retrieve all the live data from Poseidon Crawler.
* **Check Report**
Open the main [Space Weather window](space_weather/current.md) and retrieve all the live data from Poseidon Crawler.
## 2. Signal List ## 2. Signal List
This is the signal list where all the database entries are shown. When a signal is selected, it will load on the right panel. This is the signal list where all the database entries are shown. When a signal is selected, it will load on the right panel.
@@ -31,9 +68,18 @@ On top of the list, there is a field for filtering signals by name or any keywor
Here you can swithc between the main **signal** window and the **filter** page. Here you can swithc between the main **signal** window and the **filter** page.
## 4. Tags ## 4. Tags
* **Associate Tag:** Custom tags can be associated to the selected signal with the :octicons-plus-circle-16: icon
* **Remove Tag:** In order to remove a tag, just click on its badge. * **Associate Tag**
* **Add/Rename Tag:** To add a new tag open the [Tags Editor](#1-main-menu) in the main menu.
Custom tags can be associated to the selected signal with the :octicons-plus-circle-16: icon
* **Remove Tag**
In order to remove a tag, just click on its badge.
* **Add/Rename Tag**
To add a new tag open the [Tags Editor](#1-main-menu) in the main menu.
## 5. Add Parameter ## 5. Add Parameter
Click on the labels to add the corresponding parameter to the signal (e.g. click on **Frequency** to add a new frequency). Click on the labels to add the corresponding parameter to the signal (e.g. click on **Frequency** to add a new frequency).

View File

@@ -21,8 +21,6 @@ A simple integer to denote the database version.
This field should serve as a writing protection on the database. This field should serve as a writing protection on the database.
* **-1**: reserved to sigID database. This is the primary way to distinguish a valid sigID database
* **0**: read-only database * **0**: read-only database
* **1**: database can be edited with no restrictions * **1**: database can be edited with no restrictions
!!! example "Experimental"
This feature is experimental and not yet implemented.

18
docs/faq.md Normal file
View File

@@ -0,0 +1,18 @@
# Frequently Asked Questions
### What about an automatic signal recognition feature?
This question has been asked countless times, and for good reason: in many fields, machine learning (ML) has disrupted the way we solve problems, and when applied to the right field, you can get outstanding results. **So why not on signals?**
Well, the story dates back to at least 2017 when I started discussing it with several people on the rtl-sdr blog: at first, it seemed promising, but like many things, all that glitters is not gold. Let us proceed in order:
#### ML / Deep Learning approach
A machine learning/neural network approach would not be complex (from a technical standpoint) if there were not the problem of the dataframe completeness. To be effective, neural models need to be trained on a large number of entries. The precise number can vary and strongly depends on the nature of data used for training, but commonly, numbers can be tens of thousands of entries or even more, for example. This would not be a significant concern in the case of RF signals because the various encodings that differentiate can be created artificially using for example [Fldigi](http://www.w1hkj.com/). To make these synthetic signals more like a real one, noise and artificial distortions can be added. However, this approach allows us to have a spectrum that ranges only over civilian, non-proprietary, and non-military signals (a little bit more than 150 out of 500, in the best-case scenario). Many efforts have been made in this field and some excellent results have been reported below:
- [OShea, Corgan, and Clancy](https://arxiv.org/pdf/1602.04105) - Training Size: 900000 signals, 11 modulations
- [Stefan Scholl](https://arxiv.org/pdf/1906.04459) - Training Size: 120000 signals, 18 modulations
- [Stefan Scholl](https://panoradio-sdr.de/automatic-identification-of-160-shortwave-rf-signals-with-deep-learning/) - Training Size: 1.2 million signals, 143 modulations tested in HF
Therefore, The main point is to have a good quality data frame to train the model; this is not always an easy task for the above reasons. Subsequently, as Stefan Scholl pointed out on his blog, similar modulations (MFSK-32 vs Olivia 16/500) can significantly decrease the effectiveness of discrimination. The quality of reception can also influence the result as well:  high SNR cannot always be an ideal point since the reception can be disrupted by interference or fading.
#### Classical Audio Analysis
More classical methods, such as similarity recognition between audio samples (such as using Mel-frequency cepstral coefficients, for example), could be effective, albeit marginally, if applied to the 500 signals audio sample library from sigID wiki. With the same signal encoding, the content of the signal alone can affect the similarity index and, thus, the method's effectiveness. Listening to a signal with the same modulation but encoding different information can significantly decrease the accuracy of signal recognition.

View File

@@ -1,15 +1,14 @@
--- ---
title: Documentation title: Documentation
--- ---
# #
<p align="center" markdown> <div align="center">
![Logo](assets/logo_large_black.svg#only-light){width="400"} <iframe width="560" height="315" src="https://www.youtube.com/embed/W_8Y_4FvoHI?si=0hBqRnxnzCUWmTxK" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
![Logo](assets/logo_large_white.svg#only-dark){width="400"} </div>
</p>
**Artemis** is a software designed to assist **radio frequency (RF) signal identification and storage**. It simplifies real-time spectrum analysis by leveraging one of the most extensive and community-driven databases, containing nearly **500 recognized signals**. This comprehensive software solution allows users to collect RF signals with specific parameters such as frequency, bandwidth, modulation, etc. Users can also store spectrum waterfalls, audio samples, and all types of documents for future reference. Artemis provides a robust platform to manage a wide range of RF data with precision and ease.
<p align="center" markdown> <p align="center" markdown>
[Artemis Homepage](https://www.aresvalley.com){ .md-button } [Artemis Homepage](https://www.aresvalley.com){ .md-button }
</p> </p>
**Artemis** is a software designed to assist **radio frequency (RF) signal identification and storage**. It simplifies real-time spectrum analysis by leveraging one of the most extensive and community-driven databases, containing nearly **500 recognized signals**. This comprehensive software solution allows users to collect RF signals with specific parameters such as frequency, bandwidth, modulation, etc. Users can also store spectrum waterfalls, audio samples, and all types of documents for future reference. Artemis provides a robust platform to manage a wide range of RF data with precision and ease.

View File

@@ -12,8 +12,14 @@
--- ---
## :simple-linux: Linux ## :simple-linux: Linux
1. Download `Artemis-Linux-x86_64-4.x.x.tar` in the Assets menu from the [:material-download: LATEST RELEASE](https://github.com/AresValley/Artemis/releases) and extract the tarball archive in a folder of your choice. 1. On Linux, the xcb plugin is utilized to supply the essential functionality required for Qt GUI and Qt Widgets to operate on [X11](https://doc.qt.io/qt-6/linux-requirements.html). On some Linux distributions the required dependencies are already met, but in many cases, you will need to install them. To install the dependencies use:
2. Before running `app.bin`, be sure to have the executable permissions to the binary file with:
``` bash title="Debian-based distro (Ubuntu, Mint, Pop! OS, Kali, ...)"
sudo apt install libxcb-cursor0
```
2. Download `Artemis-Linux-x86_64-4.x.x.tar` in the Assets menu from the [:material-download: LATEST RELEASE](https://github.com/AresValley/Artemis/releases) and extract the tarball archive in a folder of your choice.
3. Before running `app.bin`, be sure to have the executable permissions to the binary file with:
``` ```
chmod 700 app.bin chmod 700 app.bin

View File

@@ -73,8 +73,9 @@ nav:
- Forecasts: 'space_weather/forecasts.md' - Forecasts: 'space_weather/forecasts.md'
- DRAP: 'space_weather/drap.md' - DRAP: 'space_weather/drap.md'
- Aurora: 'space_weather/aurora.md' - Aurora: 'space_weather/aurora.md'
- Example: - Signal Analysis:
- ACF Analysis: 'acf_analysis.md' - Autocorrelation (ACF): 'acf_analysis.md'
- FAQ: 'faq.md'
- Contribute: 'contribute.md' - Contribute: 'contribute.md'
- Changelog: https://github.com/AresValley/Artemis/blob/master/CHANGELOG.md - Changelog: https://github.com/AresValley/Artemis/blob/master/CHANGELOG.md
- License & Credits: 'credits.md' - License & Credits: 'credits.md'

View File

@@ -31,8 +31,9 @@ Window {
signal showCatManager() signal showCatManager()
signal openSigEditor(string type, var sig_param, bool is_new) signal openSigEditor(string type, var sig_param, bool is_new)
signal showSpaceWeather() signal showSpaceWeather()
signal checkDbUpdates() signal checkForUpdate()
signal startDownloader() signal updateDb()
signal updateArtemis()
signal openDbDirectory() signal openDbDirectory()
signal newDb(string name) signal newDb(string name)
signal exportDb(string path) signal exportDb(string path)
@@ -121,11 +122,12 @@ Window {
dialogDownloadDb.open() dialogDownloadDb.open()
} }
function openDialogDownloadArtemis(messageType, title, message) { function openDialogUpdateArtemis(messageType, title, message, auto) {
dialogDownloadArtemis.messageType = messageType dialogUpdateArtemis.messageType = messageType
dialogDownloadArtemis.title = title dialogUpdateArtemis.title = title
dialogDownloadArtemis.message = message dialogUpdateArtemis.message = message
dialogDownloadArtemis.open() dialogUpdateArtemis.autoUpdate = auto
dialogUpdateArtemis.open()
} }
DialogMessage { DialogMessage {
@@ -135,18 +137,24 @@ Window {
standardButtons: Dialog.Cancel | Dialog.Yes standardButtons: Dialog.Cancel | Dialog.Yes
onAccepted: { onAccepted: {
startDownloader() updateDb()
} }
} }
DialogMessage { DialogMessage {
id: dialogDownloadArtemis id: dialogUpdateArtemis
modal: true modal: true
property bool autoUpdate
standardButtons: Dialog.Cancel | Dialog.Yes standardButtons: Dialog.Cancel | Dialog.Yes
onAccepted: { onAccepted: {
Qt.openUrlExternally("https://github.com/AresValley/Artemis") if (autoUpdate) {
updateArtemis();
} else {
Qt.openUrlExternally("https://github.com/AresValley/Artemis");
}
} }
} }
@@ -218,6 +226,11 @@ Window {
leftPadding: 10 leftPadding: 10
bottomPadding: 10 bottomPadding: 10
focus: true
Keys.onDownPressed: listView.incrementCurrentIndex()
Keys.onUpPressed: listView.decrementCurrentIndex()
header: MenuBar { header: MenuBar {
id: topBar id: topBar
@@ -315,7 +328,7 @@ Window {
MenuItem { MenuItem {
text: "Check for Updates" text: "Check for Updates"
onClicked: {checkDbUpdates()} onClicked: {checkForUpdate()}
} }
MenuSeparator {} MenuSeparator {}
@@ -389,7 +402,6 @@ Window {
Layout.fillHeight: true Layout.fillHeight: true
highlightMoveDuration: 0 highlightMoveDuration: 0
clip: true clip: true
focus: true
ScrollBar.vertical: bar ScrollBar.vertical: bar
highlight: Rectangle { color: Material.accent; radius: 5 } highlight: Rectangle { color: Material.accent; radius: 5 }
onCurrentIndexChanged: { itemChangedList() } onCurrentIndexChanged: { itemChangedList() }

View File

@@ -33,6 +33,8 @@ Window {
if (selected_db !== undefined) { if (selected_db !== undefined) {
lockMenu(false) lockMenu(false)
titleLabel.text = myModel.get(listView.currentIndex).name titleLabel.text = myModel.get(listView.currentIndex).name
versionLabel.text = 'VERSION ' + myModel.get(listView.currentIndex).version
dateLabel.text = myModel.get(listView.currentIndex).date
totDocsLabel.text = myModel.get(listView.currentIndex).documents_n totDocsLabel.text = myModel.get(listView.currentIndex).documents_n
totSignalsLabel.text = myModel.get(listView.currentIndex).signals_n totSignalsLabel.text = myModel.get(listView.currentIndex).signals_n
totImagesLabel.text = myModel.get(listView.currentIndex).images_n totImagesLabel.text = myModel.get(listView.currentIndex).images_n
@@ -44,6 +46,8 @@ Window {
function clearAll() { function clearAll() {
titleLabel.text = 'N/A' titleLabel.text = 'N/A'
versionLabel.text = ''
dateLabel.text = ''
totDocsLabel.text = '' totDocsLabel.text = ''
totSignalsLabel.text = '' totSignalsLabel.text = ''
totImagesLabel.text = '' totImagesLabel.text = ''
@@ -175,11 +179,16 @@ Window {
Label { Label {
id: titleLabel id: titleLabel
Layout.bottomMargin: 20
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
font.pointSize: 15 font.pointSize: 15
font.bold: true font.bold: true
} }
Label {
id: versionLabel
Layout.bottomMargin: 20
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
font.pointSize: 9
}
GridLayout { GridLayout {
columnSpacing: 25 columnSpacing: 25
columns: 2 columns: 2
@@ -230,7 +239,20 @@ Window {
id: totAudioLabel id: totAudioLabel
text: qsTr("0") text: qsTr("0")
font.pointSize: 12 font.pointSize: 12
} }
Label {
Layout.topMargin: 20
text: qsTr("DB Created:")
font.pointSize: 12
}
Label {
id: dateLabel
Layout.topMargin: 20
text: qsTr("")
font.pointSize: 12
}
} }
Item { Item {

View File

@@ -372,7 +372,6 @@ Window {
Layout.fillHeight: true Layout.fillHeight: true
highlightMoveDuration: 0 highlightMoveDuration: 0
clip: true clip: true
focus: true
ScrollBar.vertical: bar ScrollBar.vertical: bar
highlight: Rectangle { color: Material.accent; radius: 5 } highlight: Rectangle { color: Material.accent; radius: 5 }
onCurrentIndexChanged: { itemChanged() } onCurrentIndexChanged: { itemChanged() }

View File

@@ -24,11 +24,19 @@ Window {
signal onAbort() signal onAbort()
onClosing: {
onAbort()
}
function updateProgressBar(bytesReceived, bytesTotal) { function updateProgressBar(bytesReceived, bytesTotal) {
progressBar.value = bytesReceived progressBar.value = bytesReceived
progressBar.to = bytesTotal progressBar.to = bytesTotal
} }
function setIndeterminateBar() {
progressBar.indeterminate = true
}
function updateStatus(arg) { function updateStatus(arg) {
progressLabel.text = arg progressLabel.text = arg
} }
@@ -51,6 +59,7 @@ Window {
Layout.rightMargin: 20 Layout.rightMargin: 20
Layout.leftMargin: 20 Layout.leftMargin: 20
Layout.fillWidth: true Layout.fillWidth: true
indeterminate: false
value: 0 value: 0
to: 0 to: 0
} }
@@ -61,10 +70,11 @@ Window {
} }
Button { Button {
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
text: qsTr("Abort") text: qsTr("Abort")
icon.source: "qrc:/images/icons/abort.svg" icon.source: "qrc:/images/icons/abort.svg"
display: AbstractButton.TextBesideIcon display: AbstractButton.TextBesideIcon
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom flat: true
onClicked: { onAbort() } onClicked: { onAbort() }
} }
} }

View File

@@ -301,11 +301,12 @@ Page {
Label { Label {
id: summaryFreq id: summaryFreq
Layout.fillWidth: true
Layout.fillHeight: true
color: Material.color(Material.Green) color: Material.color(Material.Green)
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
Layout.fillWidth: true font.pointSize: 16
Layout.fillHeight: true
} }
} }
} }
@@ -390,11 +391,12 @@ Page {
} }
Label { Label {
id: summaryBand id: summaryBand
Layout.fillWidth: true
Layout.fillHeight: true
color: Material.color(Material.Green) color: Material.color(Material.Green)
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
Layout.fillWidth: true font.pointSize: 16
Layout.fillHeight: true
} }
} }
} }
@@ -465,11 +467,12 @@ Page {
} }
Label { Label {
id: summaryACF id: summaryACF
Layout.fillWidth: true
Layout.fillHeight: true
color: Material.color(Material.Green) color: Material.color(Material.Green)
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
Layout.fillWidth: true font.pointSize: 16
Layout.fillHeight: true
} }
} }
} }
@@ -503,7 +506,6 @@ Page {
Layout.fillHeight: true Layout.fillHeight: true
highlightMoveDuration: 0 highlightMoveDuration: 0
clip: true clip: true
focus: true
ScrollBar.vertical: ScrollBar { ScrollBar.vertical: ScrollBar {
active: true active: true
} }
@@ -555,7 +557,6 @@ Page {
Layout.fillWidth: true Layout.fillWidth: true
highlightMoveDuration: 0 highlightMoveDuration: 0
clip: true clip: true
focus: true
ScrollBar.vertical: ScrollBar { ScrollBar.vertical: ScrollBar {
active: true active: true
} }
@@ -607,7 +608,6 @@ Page {
Layout.fillWidth: true Layout.fillWidth: true
highlightMoveDuration: 0 highlightMoveDuration: 0
clip: true clip: true
focus: true
ScrollBar.vertical: ScrollBar { ScrollBar.vertical: ScrollBar {
active: true active: true
} }

View File

@@ -146,7 +146,7 @@ Window {
Layout.fillWidth: true Layout.fillWidth: true
Label { Label {
text: "Auto-load SigID Database on Startup" text: "Auto-load SigID database on startup (latest version)"
font.pixelSize: 12 font.pixelSize: 12
clip: true clip: true
Layout.fillWidth: true Layout.fillWidth: true