Compare commits
80 Commits
v3.2.3
...
revised-ne
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c0b1b7e04 | ||
|
|
c17c1fdbea | ||
|
|
8138cf8e38 | ||
|
|
0b416f0a2e | ||
|
|
1d0c459402 | ||
|
|
0f898ff6f5 | ||
|
|
c58d85c6a2 | ||
|
|
f73034c35c | ||
|
|
77c43813a0 | ||
|
|
b01bd99ecf | ||
|
|
146a5d1605 | ||
|
|
ea245c853b | ||
|
|
e57e9bf8e4 | ||
|
|
70ed158b02 | ||
|
|
1d795b688e | ||
|
|
52c4fbcce9 | ||
|
|
79891899ce | ||
|
|
dde223d2ac | ||
|
|
43ddee410a | ||
|
|
ecbf10ebb5 | ||
|
|
5d3cdb7abb | ||
|
|
5b8670814b | ||
|
|
19acf11b1a | ||
|
|
39056d1d91 | ||
|
|
436c54b733 | ||
|
|
b48a42dcc8 | ||
|
|
e00e21c46a | ||
|
|
2aa821ee65 | ||
|
|
5ab0c39aa9 | ||
|
|
f09bbd3311 | ||
|
|
91589018a3 | ||
|
|
d41c9e1ed6 | ||
|
|
0cbd9a7a0b | ||
|
|
7db68ab2ad | ||
|
|
c7c53b5a68 | ||
|
|
9d2443b0f0 | ||
|
|
2c3ffd5e66 | ||
|
|
485eccb373 | ||
|
|
faf9a5293a | ||
|
|
e58cf4d206 | ||
|
|
1b48405c14 | ||
|
|
c8fcb2c3e0 | ||
|
|
ab8403c16e | ||
|
|
7929c23ac5 | ||
|
|
707cc001cf | ||
|
|
4594237c09 | ||
|
|
10607c88ea | ||
|
|
f61527ed70 | ||
|
|
4e7ebcc2f5 | ||
|
|
16e2668fe9 | ||
|
|
cc607fdd28 | ||
|
|
f5565a6b5e | ||
|
|
04bc4cce5f | ||
|
|
c6afcc0e75 | ||
|
|
3fdbdbfae4 | ||
|
|
359e5c9076 | ||
|
|
7a89b64ca8 | ||
|
|
e2a48e7a54 | ||
|
|
2a70e42a59 | ||
|
|
f0db05c96e | ||
|
|
528c816508 | ||
|
|
acc44c93b3 | ||
|
|
0a5fefa214 | ||
|
|
c658e072f7 | ||
|
|
81f9e958d6 | ||
|
|
b41ad0e8f2 | ||
|
|
ed8be2d274 | ||
|
|
1cbf01f307 | ||
|
|
93a946e2a9 | ||
|
|
fef8b71c46 | ||
|
|
adf7421c94 | ||
|
|
047d5c8ffd | ||
|
|
b6d7f39efc | ||
|
|
cc0efafc92 | ||
|
|
6ace19d75c | ||
|
|
d2e0ece9eb | ||
|
|
d1716621b0 | ||
|
|
cd5d31b404 | ||
|
|
5a1db2c0d4 | ||
|
|
e4fdf1d564 |
32
.github/workflows/linux.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
name: Nuitka - Linux
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
artemis_version:
|
||||||
|
description: 'Version'
|
||||||
|
required: true
|
||||||
|
default: '4.0.0'
|
||||||
|
type: string
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-linux-x86_64:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
steps:
|
||||||
|
- name: Check out code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- name: Build and package
|
||||||
|
run: sh ./building/Linux/build_linux.sh
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: Artemis-Linux-x86_64-${{github.event.inputs.artemis_version}}
|
||||||
|
path: ./app.dist/
|
||||||
32
.github/workflows/macOS.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
name: Nuitka - macOS
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
artemis_version:
|
||||||
|
description: 'Version'
|
||||||
|
required: true
|
||||||
|
default: '4.0.0'
|
||||||
|
type: string
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-macos-x86_64:
|
||||||
|
runs-on: macos-11
|
||||||
|
steps:
|
||||||
|
- name: Check out code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- name: Build and package
|
||||||
|
run: sh ./building/macOS/build_macos.sh
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: Artemis-macOS-x86_64-${{github.event.inputs.artemis_version}}
|
||||||
|
path: ./app.dist/
|
||||||
79
.github/workflows/windows-packaging.yml
vendored
@@ -1,79 +0,0 @@
|
|||||||
name: Auto Packaging - Windows
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
windows-packaging:
|
|
||||||
runs-on: windows-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Use Python 3.10
|
|
||||||
uses: actions/setup-python@v4
|
|
||||||
with:
|
|
||||||
python-version: '3.10'
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: pip install -r ./requirements/requirements.txt
|
|
||||||
|
|
||||||
- name: Install PyInstaller
|
|
||||||
run: |
|
|
||||||
pip install pyinstaller
|
|
||||||
|
|
||||||
- name: Build Artemis main executables and
|
|
||||||
run: |
|
|
||||||
CD spec_files\Windows
|
|
||||||
|
|
||||||
ECHO "Building Artemis executable..."
|
|
||||||
MKDIR output
|
|
||||||
pyinstaller artemis.spec
|
|
||||||
MOVE dist\Artemis.exe .\output\Artemis.exe
|
|
||||||
RMDIR -recurse -force dist
|
|
||||||
RMDIR -recurse -force build
|
|
||||||
|
|
||||||
ECHO "Building updater..."
|
|
||||||
pyinstaller updater.spec
|
|
||||||
MOVE dist\_ArtemisUpdater.exe .\output\_ArtemisUpdater.exe
|
|
||||||
RMDIR -recurse -force dist
|
|
||||||
RMDIR -recurse -force build
|
|
||||||
|
|
||||||
CD output
|
|
||||||
MKDIR Artemis
|
|
||||||
XCOPY /y Artemis.exe Artemis\
|
|
||||||
XCOPY /e /k /y ..\..\..\src\themes Artemis\themes\ /EXCLUDE:..\excluded_files.txt
|
|
||||||
XCOPY /y _ArtemisUpdater.exe Artemis\
|
|
||||||
|
|
||||||
ECHO "Compress files themes+Artemis.exe -> Artemis.zip"
|
|
||||||
$compress = @{
|
|
||||||
Path = ".\Artemis.exe", "..\..\..\src\themes"
|
|
||||||
CompressionLevel = "Optimal"
|
|
||||||
DestinationPath = ".\Artemis_win.zip"
|
|
||||||
}
|
|
||||||
Compress-Archive @compress
|
|
||||||
|
|
||||||
$compress = @{
|
|
||||||
Path = ".\_ArtemisUpdater.exe"
|
|
||||||
CompressionLevel = "Optimal"
|
|
||||||
DestinationPath = ".\_ArtemisUpdater_win.zip"
|
|
||||||
}
|
|
||||||
Compress-Archive @compress
|
|
||||||
|
|
||||||
ECHO "Compress all files for website download"
|
|
||||||
$compress = @{
|
|
||||||
Path = "Artemis"
|
|
||||||
CompressionLevel = "Optimal"
|
|
||||||
DestinationPath = ".\ArtemisWebsite_win.zip"
|
|
||||||
}
|
|
||||||
Compress-Archive @compress
|
|
||||||
|
|
||||||
python ..\..\__get_hash_code.py Artemis_win.zip _ArtemisUpdater_win.zip ArtemisWebsite_win.zip > checksum.txt
|
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
|
||||||
with:
|
|
||||||
name: Artemis_Windows
|
|
||||||
path: |
|
|
||||||
.\spec_files\Windows\output\Artemis_win.zip
|
|
||||||
.\spec_files\Windows\output\_ArtemisUpdater_win.zip
|
|
||||||
.\spec_files\Windows\output\ArtemisWebsite_win.zip
|
|
||||||
.\spec_files\Windows\output\checksum.txt
|
|
||||||
35
.github/workflows/windows.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
name: Nuitka - Windows
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
artemis_version:
|
||||||
|
description: 'Version'
|
||||||
|
required: true
|
||||||
|
default: '4.0.0'
|
||||||
|
type: string
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-windows-x86_64:
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.11'
|
||||||
|
|
||||||
|
- name: Build and package
|
||||||
|
run: ./building/Windows/build_windows.ps1
|
||||||
|
shell: powershell
|
||||||
|
|
||||||
|
- name: Building installer (ISCC)
|
||||||
|
run: iscc ./building/Windows/windows_installer.iss
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: Artemis-Windows-x86_64-${{github.event.inputs.artemis_version}}
|
||||||
|
path: ./building/artemis.exe
|
||||||
20
.gitignore
vendored
@@ -1,11 +1,11 @@
|
|||||||
__PYCache__
|
**/__pycache__/
|
||||||
Data
|
|
||||||
src/themes/__current_theme
|
|
||||||
designer.bat
|
|
||||||
launch.bat
|
|
||||||
.vscode/
|
.vscode/
|
||||||
.code-workspace
|
*.pyproject.user
|
||||||
spec_files/**/output
|
data/
|
||||||
*.txt
|
.flake8
|
||||||
*.json
|
*.qtds
|
||||||
info.log
|
artemis_rc.py
|
||||||
|
site
|
||||||
|
Generated
|
||||||
|
app.build
|
||||||
|
app.dist
|
||||||
|
|||||||
58
CHANGELOG.md
@@ -1,7 +1,53 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) and the format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
||||||
|
|
||||||
The first release is [3.0.0] because this is actually the third major version (completely rewritten) of the software.
|
> [!NOTE]
|
||||||
|
> This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) and the format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Possibility to navigate Artemis just with the keyboard [#50](https://github.com/AresValley/Artemis/issues/50)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Improved readability of labels for filter ranges for frequency, bandwidth, and ACF
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Added a database load check to avoid (non critical) exceptions when applying filters without having loaded a database.
|
||||||
|
|
||||||
|
## [4.0.3] - 2024-06-10
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Optimized final package size (reduced by 30% to 50%) by explicitly including necessary plugins/DLLs and excluding unnecessary ones with Nuitka [#47](https://github.com/AresValley/Artemis/issues/47)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- When the links/urls in the description field of a signal are clicked, they open the default browser [#46](https://github.com/AresValley/Artemis/issues/46)
|
||||||
|
- Fixed an error occurig on Linux where configuration file path are not properly resolved during startup with the binary version of the program (they are if running from source) [#48](https://github.com/AresValley/Artemis/issues/48)
|
||||||
|
|
||||||
|
## [4.0.1] - 2024-06-9
|
||||||
|
### Added
|
||||||
|
- Database format has been changed from .csv to a proper relational DB (sqlite) which is much easier handled thanks to the native library shipped with python
|
||||||
|
- Possibility to create an arbitrary number of new databases for storing new custom signals
|
||||||
|
- Every signal allows an arbitrary number of parameters
|
||||||
|
- All signal parameters (such as frequency, modulation, location, etc.) are now followed by a description
|
||||||
|
- Databases can be exported/imported for easy sharing
|
||||||
|
- Possibility to store and view all type of documents related to a signal entry
|
||||||
|
- Filtration process is now much more efficient due to usage of SQL queries
|
||||||
|
- D-Region Absorption Predictions (DRAP) and Aurora OVATION model are now present in the Space Weather window
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Updated GUI libray from PyQt5 to PySide6. Artemis 4 now relies on the QtQuick framework.
|
||||||
|
- SigID standard database is now hosted on GitHub (the server is much faster) along with the website parser
|
||||||
|
- Undefined value for frequency and bandwidth is now deprecated
|
||||||
|
- Drastically reduced the number of third party libraries
|
||||||
|
- The signals filtering page has been simplified to be more immediate and user friendly
|
||||||
|
- Space weather page has been greatly improved and now relies on Poseidon daemon (hosted on aresvalley.com)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Artemis can be execunted inside standard pretected folder (such as Program Files) without using elevated privileges
|
||||||
|
|
||||||
|
## [3.2.4] - 2022-09-30
|
||||||
|
### Fixed
|
||||||
|
- Fixed crash on opening the Rx/Tx Condition tab
|
||||||
|
|
||||||
## [3.2.3] - 2022-09-29
|
## [3.2.3] - 2022-09-29
|
||||||
### Added
|
### Added
|
||||||
@@ -26,7 +72,6 @@ The first release is [3.0.0] because this is actually the third major version (c
|
|||||||
|
|
||||||
|
|
||||||
## [3.2.0] - 2019-12-14
|
## [3.2.0] - 2019-12-14
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- The default font can be changed ([#14](https://github.com/AresValley/Artemis/pull/14)).
|
- The default font can be changed ([#14](https://github.com/AresValley/Artemis/pull/14)).
|
||||||
- Move `Themes` into `Settings`.
|
- Move `Themes` into `Settings`.
|
||||||
@@ -64,7 +109,12 @@ First release.
|
|||||||
|
|
||||||
|
|
||||||
<!-- Links definitions -->
|
<!-- Links definitions -->
|
||||||
[Unreleased]: https://github.com/AresValley/Artemis/compare/v3.2.1...HEAD
|
[Unreleased]: https://github.com/AresValley/Artemis/compare/v4.0.3...HEAD
|
||||||
|
[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
|
||||||
|
[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.2]: https://github.com/AresValley/Artemis/compare/v3.2.1...v3.2.2
|
||||||
[3.2.1]: https://github.com/AresValley/Artemis/compare/v3.2.0...v3.2.1
|
[3.2.1]: https://github.com/AresValley/Artemis/compare/v3.2.0...v3.2.1
|
||||||
[3.2.0]: https://github.com/AresValley/Artemis/compare/v3.1.0...v3.2.0
|
[3.2.0]: https://github.com/AresValley/Artemis/compare/v3.1.0...v3.2.0
|
||||||
[3.1.0]: https://github.com/AresValley/Artemis/compare/v3.0.1...v3.1.0
|
[3.1.0]: https://github.com/AresValley/Artemis/compare/v3.0.1...v3.1.0
|
||||||
|
|||||||
171
README.md
@@ -1,160 +1,28 @@
|
|||||||
<img src="documentation/ArtemisLogoSmall.png" align="right" />
|
<div align="center">
|
||||||
|
<img src="docs/assets/logo_large_black.svg" alt="Logo" width="400">
|
||||||
|
</div>
|
||||||
|
|
||||||
# ARTEMIS 3   
|
<div align="center">
|
||||||
|
|
||||||
*Radio Signals Recognition Manual*
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
## ARTEMIS. In a nutshell.
|
</div>
|
||||||
|
|
||||||
In short, ARTEMIS is a signals hunter software and a useful aid for radio listeners! The analysis of real-time spectra (from your SDR, for instance) is made simple: you can take advantage using one of the largest RF signal database (with over 370 records). Compare several signals properties (such as frequency, bandwidth, modulation, etc.) and verify what you are searching for through a waterfall/audio sample. A collection of filters allows you to narrow your search, making the identification of unknown signals, odd buzzes or weird noises way easier.
|
**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.
|
||||||
|
|
||||||
## Table of contents
|
<div align="center">
|
||||||
|
<img src="docs/assets/artemis_preview.webp">
|
||||||
|
</div>
|
||||||
|
|
||||||
- [Run the software](#Run-the-software)
|
## Documentation
|
||||||
- [Run from binary](#Run-from-binary)
|
To learn more about Artemis go to the [complete documentation](https://AresValley.github.io/Artemis).
|
||||||
- [Run from source code](#Run-from-source-code)
|
|
||||||
- [Compile from source code](#Compile-from-source-code)
|
|
||||||
- [Database](#database)
|
|
||||||
- [Syntax](#syntax)
|
|
||||||
- [Multiple Items fields (Location, Modulation)](#multiple-items-fields-location-modulation)
|
|
||||||
- [Themes](#themes)
|
|
||||||
- [License](#license)
|
|
||||||
- [Thanks](#thanks)
|
|
||||||
|
|
||||||
## Run the software
|
## Contributors
|
||||||
|
* **Marco Dalla Tiezza** - *Artemis I-II-IV developer, DB parsing, Website*
|
||||||
Artemis 3 is entirely written in Python, so if you already have Python 3.7.0+ installed in your system, you can directly run the main script. Otherwise you can download a binary executable (see below).
|
|
||||||
|
|
||||||
### Run from binary
|
|
||||||
**If you don't know what you want or you are not sure where to look, this is for you.**
|
|
||||||
|
|
||||||
Basically, this is the easiest, smooth, and clean way to run Artemis 3. A Python installation is not required.
|
|
||||||
For more information, follow [the main page of Artemis 3](https://aresvalley.com/artemis/) (detailed documentation at the end of the main page).
|
|
||||||
|
|
||||||
**Requirements:**
|
|
||||||
- Windows 7/8/8.1/10
|
|
||||||
- Linux: Ubuntu 18.04+, Mint 19+, Fedora 28+ and many other. **You need at least version 2.27 of the GLIBC system library** ([details](https://github.com/AresValley/Artemis/tree/master/spec_files))
|
|
||||||
- macOS
|
|
||||||
|
|
||||||
### Run from source code
|
|
||||||
Run the software from the source code with the Python interpreter is the simplest and natural way to run Artemis 3.
|
|
||||||
|
|
||||||
**Requirements:**
|
|
||||||
- Python (ver. 3.7.0+)
|
|
||||||
- Python libraries (in `requirements/requirements.txt`)
|
|
||||||
|
|
||||||
1. Download and install Python (ver. 3.7.0+) from the official [website](https://www.python.org/downloads/). Be sure to select the flag `Add Python 3.x to PATH` during the first part of the installation.
|
|
||||||
|
|
||||||
2. Install the necessary Python libraries with PIP. Open a console in Artemis/requirements folder and type:
|
|
||||||
|
|
||||||
```
|
|
||||||
pip install -r requirements.txt --user
|
|
||||||
```
|
|
||||||
|
|
||||||
3. After that launch the software in the Artemis folder with:
|
|
||||||
|
|
||||||
```
|
|
||||||
python3 artemis.py
|
|
||||||
```
|
|
||||||
|
|
||||||
### Compile from source code
|
|
||||||
If you want to compile Artemis yourself from the source code follow the instructions in the [spec_files/README](spec_files/README.md) file.
|
|
||||||
|
|
||||||
## Database
|
|
||||||
|
|
||||||
The database (db.csv) is directly extracted from sigidwiki.com with a DB parser and reworked to a standard format defined as follow. Artemis DB is a human-readable csv file where the delimiter is the character `*` (Asterisk, Unicode: U+002A). The new entry (separation between signals) is the End Of Line (EOL) escape sequence `\n`. Every signal is directly connected to spectra and audio sample stored in **Spectra** and **Audio** folders, respectively. Every signal is composed of 12 columns:
|
|
||||||
|
|
||||||
| Column | Description | Unit of Measurement | Multiple Items | Type|
|
|
||||||
| :-: | :-: | :-: | :-: | :-: |
|
|
||||||
| 1 | Signal name | - | - | string |
|
|
||||||
| 2 | Freq. Lower Limit | Hz | - | integer |
|
|
||||||
| 3 | Freq. Upper Limit | Hz | - | integer |
|
|
||||||
| 4 | Mode | - | - | string |
|
|
||||||
| 5 | Band. Lower Limit | Hz | - | integer |
|
|
||||||
| 6 | Band. Upper Limit | Hz | - | integer |
|
|
||||||
| 7 | Location | - | ✔ | string |
|
|
||||||
| 8 | sigidwiki URL | - | - | string |
|
|
||||||
| 9 | Description | - | - | string |
|
|
||||||
| 10 | Modulation | - | ✔ | string |
|
|
||||||
| 11 | ID Code | - | - | integer |
|
|
||||||
| 12 | Auto-correlation function | ms | ✔ | string |
|
|
||||||
|
|
||||||
### Syntax
|
|
||||||
|
|
||||||
1. **Signal Name**: The name of the signal. A simple string that describes in short the analyzed signal. Special characters are allowed (except the main delimiter `*`).
|
|
||||||
2. **Frequency (Lower Limit)**: The frequency lower bound expressed in Hertz.
|
|
||||||
3. **Frequency (Upper Limit)**: The same as above but this express the frequency upper bound of the received signal.
|
|
||||||
|
|
||||||
* In the case of a single frequency transmitter/service the **Freq. Lower Limit** and the **Freq. Upper Limit** must be coincident (same value)
|
|
||||||
* Transmission with different protocols must be added in two or more distinct entry. **DO NOT USE** the same signal page to add different transmission protocols (with different frequencies, bandwidth, modes,...). For example, NOAA-19 satellite transimit images and data with two different protocols:
|
|
||||||
|
|
||||||
* APT (Analog): 137.1000 MHz - 137.3125 MHz
|
|
||||||
* HRPT (Digital): 1698 MHz - 1707 MHz
|
|
||||||
|
|
||||||
Add two separate entry (APT and HRPT) with the correct Lower and Upper bound frequency. **DO NOT ADD** a single signal entry with a Freq. Lower Bound of 137.100 MHz and the Upper Bound of 1707 MHz.
|
|
||||||
|
|
||||||
4. **Mode**: This field reports the way how a signals has been decoded during the reception.
|
|
||||||
5. **Bandwidth (Lower Limit)**: As reported above for frequency (points 2 and 3). Also here the value is reported in Hz.
|
|
||||||
6. **Bandwidth (Upper Limit)**: As reported above for frequency (points 2 and 3).
|
|
||||||
7. **Location**: This is the location where the signal is distributed/received. Avoid the usage of the precise location of the TX station or very small town (very rare). It's a good habit to use nations/continents or special location (worldwide).
|
|
||||||
8. **sigidiwki URL**: The sigidwiki URL of the selected signal. This is a direct connection to the online database where further details of the signal are collected.
|
|
||||||
9. **Description**: The short description is used to explain the purpose of the signal and some other useful details.
|
|
||||||
10. **Modulation**: The modulation is the way how the information have been encoded into the main signal (carrier). Several modification of the properties (Amplitude, Frequecy, ...) are possible and a tx station could transmit with different modulations.
|
|
||||||
11. **ID Code**: The category code, known as ID Code, is a sequence of 0/1 and its main purpose is to assign the signal to their families/categories. It's formed by 17 digits:
|
|
||||||
|
|
||||||
|1|2|3|4|5|6|7|8|9|10|
|
|
||||||
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|
|
||||||
| Military | Radar | Active | Inactive | HAM | Commercial | Aviation | Marine | Analogue | Digital|
|
|
||||||
|
|
||||||
|11|12|13|14|15|16|17|
|
|
||||||
|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|
|
||||||
| Trunked | Utility | Sat | Navigation | Interfering | Number Stations | Time Signal |
|
|
||||||
|
|
||||||
12. **Auto-correlation funtion (ACF)**: The ACF is an awesome discriminator when the signal is composed of redundant pattern that continouosly repeats. Unfortunately, for this reason, ACF is not always available. The time value is reported in **ms** and, in some cases, could have multiple values for a single signal. An extended description with an example signal analysis is available here: https://aresvalley.com/documentation/
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
... ID Code * ACF1 Description - ACF1 Value (in ms) ; ACF2 Description - ACF2 Value (in ms) ; ...
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example 1 (D-STAR):**
|
|
||||||
|
|
||||||
```
|
|
||||||
... ID Code * Superframe - 420 ; Frame - 20
|
|
||||||
```
|
|
||||||
**Example 2 (EDACS):**
|
|
||||||
|
|
||||||
```
|
|
||||||
... ID Code * edacs48 - 60 ; edacs96 - 30
|
|
||||||
```
|
|
||||||
|
|
||||||
**Special case:** variable ACFs are allowed and a brief explanation can be reported instead of ACF Value. A nice example is the * [SSTV](https://www.sigidwiki.com/wiki/Slow-Scan_Television_(SSTV) "SSTV") transmission where the ACF is directly related to the number of lines per minute.
|
|
||||||
|
|
||||||
### Multiple Items fields (Location, Modulation)
|
|
||||||
The necessity to manage a multiple Location/Modulation search pushed us to implement a fictitious 'secondary delimiter' chosen to be the `;` character. For instance:
|
|
||||||
|
|
||||||
```
|
|
||||||
... Band. Upper Limit * Location 1 ; Location 2 ; ... * sigidwiki URL ...
|
|
||||||
```
|
|
||||||
or
|
|
||||||
```
|
|
||||||
... Description * Modulation 1 ; Modulation 2 ; ... * ID Code ...
|
|
||||||
```
|
|
||||||
|
|
||||||
## Themes
|
|
||||||
The only folder with the pre-built package is the `themes` one. In this way the themes are fully customizable and you can add your own. New themes (in the `themes` folder) will appear automatically in the main menu and the last used theme will be saved as the favorite one (a restart of Artemis will use the last used theme).
|
|
||||||
|
|
||||||
Some of the available themes were adapted from https://github.com/GTRONICK/QSS.
|
|
||||||
|
|
||||||
## License
|
|
||||||
This program (ARTEMIS 3, 2014-2022) is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License along with this program. If not, see: www.gnu.org/licenses
|
|
||||||
|
|
||||||
## Thanks
|
|
||||||
* **Marco Dalla Tiezza** - *Artemis I-II developer, DB parsing, Website*
|
|
||||||
* [**Alessandro Ceccato**](https://github.com/alessandro90 "GitHub profile") - *Artemis III lead developer*
|
* [**Alessandro Ceccato**](https://github.com/alessandro90 "GitHub profile") - *Artemis III lead developer*
|
||||||
* **Paolo Romani (IZ1MLL)** - *Lead β Tester, RF specialist*
|
* **Paolo Romani (IZ1MLL)** - *Lead β Tester, RF specialist*
|
||||||
* **Carl Colena** - *Sigidwiki admin, β Tester, Signals expert*
|
* **Carl Colena** - *Sigidwiki admin, β Tester, Signals expert*
|
||||||
@@ -162,3 +30,6 @@ You should have received a copy of the GNU General Public License along with thi
|
|||||||
* [**Eric Wiessner (KI7POL)**](https://github.com/WheezyE "GitHub profile") - *ARM port (Raspberry Pi3B+ and Pi4B)*
|
* [**Eric Wiessner (KI7POL)**](https://github.com/WheezyE "GitHub profile") - *ARM port (Raspberry Pi3B+ and Pi4B)*
|
||||||
* [**Pierpaolo Pravatto**](https://github.com/ppravatto "GitHub profile") - *Wiki page, β Tester*
|
* [**Pierpaolo Pravatto**](https://github.com/ppravatto "GitHub profile") - *Wiki page, β Tester*
|
||||||
* [**Francesco Capostagno**](https://github.com/fcapostagno "GitHub profile"), **Luca**, **Pietro** - *β Tester*
|
* [**Francesco Capostagno**](https://github.com/fcapostagno "GitHub profile"), **Luca**, **Pietro** - *β Tester*
|
||||||
|
|
||||||
|
## License
|
||||||
|
Artemis is licensed under the [**GPL-3**](https://github.com/AresValley/Artemis/blob/master/LICENSE) license.
|
||||||
|
|||||||
28
app.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import sys
|
||||||
|
|
||||||
|
from PySide6.QtCore import QCoreApplication
|
||||||
|
from PySide6.QtGui import QGuiApplication, QIcon
|
||||||
|
|
||||||
|
from artemis.utils.constants import Constants
|
||||||
|
from artemis.utils.ui_utils import set_ui
|
||||||
|
from artemis.ui.artemis import UIArtemis
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
set_ui()
|
||||||
|
|
||||||
|
QCoreApplication.setOrganizationName(Constants.ORGANIZATION_NAME)
|
||||||
|
QCoreApplication.setOrganizationDomain(Constants.ORGANIZATION_DOMAIN)
|
||||||
|
QCoreApplication.setApplicationName(Constants.APPLICATION_NAME)
|
||||||
|
|
||||||
|
app = QGuiApplication(sys.argv)
|
||||||
|
|
||||||
|
icon_file_path = (':/images/artemis_icon.ico')
|
||||||
|
app.setWindowIcon(QIcon(icon_file_path))
|
||||||
|
|
||||||
|
UIArtemis()
|
||||||
|
sys.exit(app.exec())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"folders": [
|
|
||||||
{
|
|
||||||
"path": "."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
50
artemis.qmlproject
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import QmlProject
|
||||||
|
|
||||||
|
Project {
|
||||||
|
|
||||||
|
QmlFiles {
|
||||||
|
directory: "ui"
|
||||||
|
}
|
||||||
|
|
||||||
|
QmlFiles {
|
||||||
|
directory: "ui/components"
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageFiles {
|
||||||
|
directory: "images"
|
||||||
|
}
|
||||||
|
|
||||||
|
Files {
|
||||||
|
directory: "config"
|
||||||
|
filter: "*.conf"
|
||||||
|
files: ["qtquickcontrols2.conf"]
|
||||||
|
}
|
||||||
|
|
||||||
|
Environment {
|
||||||
|
QT_QUICK_CONTROLS_CONF: "qtquickcontrols2.conf"
|
||||||
|
QT_AUTO_SCREEN_SCALE_FACTOR: "1"
|
||||||
|
QML_COMPAT_RESOLVE_URLS_ON_ASSIGNMENT: "1"
|
||||||
|
QT_LOGGING_RULES: "qt.qml.connections=false"
|
||||||
|
QT_ENABLE_HIGHDPI_SCALING: "0"
|
||||||
|
/* Useful for debugging
|
||||||
|
QSG_VISUALIZE=batches
|
||||||
|
QSG_VISUALIZE=clip
|
||||||
|
QSG_VISUALIZE=changes
|
||||||
|
QSG_VISUALIZE=overdraw
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
qt6Project: true
|
||||||
|
|
||||||
|
qdsVersion: "4.5"
|
||||||
|
|
||||||
|
quickVersion: "6.7"
|
||||||
|
|
||||||
|
/* If any modules the project imports require widgets (e.g. QtCharts), widgetApp must be true */
|
||||||
|
widgetApp: true
|
||||||
|
|
||||||
|
multilanguageSupport: true
|
||||||
|
supportedLanguages: ["en"]
|
||||||
|
primaryLanguage: "en"
|
||||||
|
|
||||||
|
}
|
||||||
52
artemis.qrc
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<RCC>
|
||||||
|
<qresource prefix="/">
|
||||||
|
<file>images/artemis_icon.ico</file>
|
||||||
|
<file>images/artemis_icon.svg</file>
|
||||||
|
|
||||||
|
<file>images/icons/dialog_error.svg</file>
|
||||||
|
<file>images/icons/dialog_info.svg</file>
|
||||||
|
<file>images/icons/dialog_quest.svg</file>
|
||||||
|
<file>images/icons/dialog_warn.svg</file>
|
||||||
|
|
||||||
|
<file>images/icons/player_pause.svg</file>
|
||||||
|
<file>images/icons/player_play.svg</file>
|
||||||
|
<file>images/icons/player_stop.svg</file>
|
||||||
|
<file>images/icons/player_loop.svg</file>
|
||||||
|
<file>images/icons/player_mute.svg</file>
|
||||||
|
<file>images/icons/save.svg</file>
|
||||||
|
<file>images/icons/delete.svg</file>
|
||||||
|
<file>images/icons/add.svg</file>
|
||||||
|
<file>images/icons/rename.svg</file>
|
||||||
|
<file>images/icons/load.svg</file>
|
||||||
|
<file>images/icons/open.svg</file>
|
||||||
|
<file>images/icons/browser.svg</file>
|
||||||
|
<file>images/icons/documents.svg</file>
|
||||||
|
<file>images/icons/abort.svg</file>
|
||||||
|
<file>images/spectrum_not_available.svg</file>
|
||||||
|
<file>images/artemis_not_available.svg</file>
|
||||||
|
|
||||||
|
<file>ui/Artemis.qml</file>
|
||||||
|
<file>ui/DbManager.qml</file>
|
||||||
|
<file>ui/DialogMessage.qml</file>
|
||||||
|
<file>ui/Downloader.qml</file>
|
||||||
|
<file>ui/Preferences.qml</file>
|
||||||
|
<file>ui/SpaceWeather.qml</file>
|
||||||
|
<file>ui/DocumentsManager.qml</file>
|
||||||
|
<file>ui/SignalEditor.qml</file>
|
||||||
|
<file>ui/CategoryEditor.qml</file>
|
||||||
|
|
||||||
|
<file>ui/FilterPage.qml</file>
|
||||||
|
<file>ui/SignalPage.qml</file>
|
||||||
|
<file>ui/SpaceWeatherCurrentPage.qml</file>
|
||||||
|
<file>ui/SpaceWeatherForecastPage.qml</file>
|
||||||
|
<file>ui/SpaceWeatherDRAPPage.qml</file>
|
||||||
|
<file>ui/SpaceWeatherAuroraPage.qml</file>
|
||||||
|
|
||||||
|
<file>ui/About.qml</file>
|
||||||
|
|
||||||
|
<file>ui/components/KIndexLight.qml</file>
|
||||||
|
<file>ui/components/AIndexLight.qml</file>
|
||||||
|
<file>ui/components/AudioPlayer.qml</file>
|
||||||
|
<file>ui/components/BandBar.qml</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
||||||
0
artemis/resources.py
Normal file
373
artemis/ui/artemis.py
Normal file
@@ -0,0 +1,373 @@
|
|||||||
|
import uuid
|
||||||
|
|
||||||
|
from PySide6.QtQml import QQmlApplicationEngine
|
||||||
|
from PySide6.QtCore import QObject, Slot, Signal
|
||||||
|
|
||||||
|
from artemis.utils.constants import Constants, Messages
|
||||||
|
from artemis.utils.sys_utils import open_directory, make_tar, unpack_tar
|
||||||
|
from artemis.utils.sql_utils import ArtemisDatabase, ArtemisSignal
|
||||||
|
from artemis.utils.update_utils import UpdateManager
|
||||||
|
from artemis.utils.generic_utils import generate_filter_query
|
||||||
|
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.ui.preferences import UIPreferences
|
||||||
|
from artemis.ui.dbmanager import UIdbmanager
|
||||||
|
from artemis.ui.signaleditor import UIsignaleditor
|
||||||
|
from artemis.ui.downloader import UIDownloader
|
||||||
|
from artemis.ui.spaceweather import UIspaceweather
|
||||||
|
from artemis.ui.documentsmanager import UIdocumentsmanager
|
||||||
|
from artemis.ui.categoryeditor import UIcategoryeditor
|
||||||
|
|
||||||
|
import artemis.resources
|
||||||
|
|
||||||
|
|
||||||
|
class UIArtemis(QObject):
|
||||||
|
# Python > QML Signals
|
||||||
|
close_ui = Signal()
|
||||||
|
populate_sig_list = Signal(list)
|
||||||
|
populate_sig_details = Signal(list)
|
||||||
|
populate_filter_modulation = Signal(list)
|
||||||
|
|
||||||
|
clear_list = Signal()
|
||||||
|
clear_signal_page = Signal()
|
||||||
|
clear_filter_page = Signal()
|
||||||
|
lock_audio_player = Signal()
|
||||||
|
lock_menu = Signal(bool)
|
||||||
|
|
||||||
|
show_dialog_popup = Signal(str, str, str)
|
||||||
|
show_dialog_download_db = Signal(str, str, str)
|
||||||
|
show_dialog_update_artemis = Signal(str, str, str, bool)
|
||||||
|
update_info_bar = Signal(str, str)
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
# Main UI initialization
|
||||||
|
self._engine = QQmlApplicationEngine()
|
||||||
|
self._engine.rootContext().setContextProperty('APPLICATION_VERSION', Constants.APPLICATION_VERSION)
|
||||||
|
self._engine.rootContext().setContextProperty('PYTHON_VERSION', Constants.PYTHON_VERSION)
|
||||||
|
self._engine.rootContext().setContextProperty('QT_VERSION', Constants.QT_VERSION)
|
||||||
|
self._engine.load('qrc:/ui/Artemis.qml')
|
||||||
|
self._window = self._engine.rootObjects()[0]
|
||||||
|
|
||||||
|
self._window_filter = self._window.findChild(QObject, "filterPageObj")
|
||||||
|
self._window_signal = self._window.findChild(QObject, "signalPageObj")
|
||||||
|
|
||||||
|
self.loaded_db = None
|
||||||
|
|
||||||
|
self._connect()
|
||||||
|
|
||||||
|
# Creating istances for other windows
|
||||||
|
self.preferences = UIPreferences(self)
|
||||||
|
self.dbmanager = UIdbmanager(self)
|
||||||
|
self.spaceweather = UIspaceweather(self)
|
||||||
|
self.docmanager = UIdocumentsmanager(self)
|
||||||
|
self.sigeditor = UIsignaleditor(self)
|
||||||
|
self.cateditor = UIcategoryeditor(self)
|
||||||
|
self.downloader = UIDownloader(self)
|
||||||
|
|
||||||
|
self.update_manager = UpdateManager(self)
|
||||||
|
|
||||||
|
self.autoload_db()
|
||||||
|
|
||||||
|
|
||||||
|
def _connect(self):
|
||||||
|
# QML > Python connections
|
||||||
|
self._window.showDBmanager.connect(self.show_dbmanager_ui)
|
||||||
|
self._window.loadSignal.connect(self.load_sig)
|
||||||
|
self._window.showPref.connect(self.show_pref_ui)
|
||||||
|
self._window.openSigEditor.connect(self.open_sig_editor)
|
||||||
|
self._window.checkForUpdate.connect(self.check_for_update)
|
||||||
|
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.openDbDirectory.connect(self.open_db_directory)
|
||||||
|
self._window.showCatManager.connect(self.open_cat_manager)
|
||||||
|
|
||||||
|
self._window.newDb.connect(self.new_db)
|
||||||
|
self._window.exportDb.connect(self.export_db)
|
||||||
|
self._window.importDb.connect(self.import_db)
|
||||||
|
|
||||||
|
self._window_filter.applyFilter.connect(self.apply_filter)
|
||||||
|
self._window_filter.sendBottomAlert.connect(self.bottom_info_bar)
|
||||||
|
|
||||||
|
self._window_signal.openDocManager.connect(self.show_documentsmanager_ui)
|
||||||
|
self._window_signal.openSigEditor.connect(self.open_sig_editor)
|
||||||
|
self._window_signal.deleteCatTag.connect(self.delete_cat_tag)
|
||||||
|
self._window_signal.addCatTag.connect(self.add_cat_tag)
|
||||||
|
|
||||||
|
# Python > QML connections
|
||||||
|
self.close_ui.connect(self._window.close)
|
||||||
|
self.populate_sig_list.connect(self._window.populateList)
|
||||||
|
self.clear_list.connect(self._window.clearList)
|
||||||
|
self.update_info_bar.connect(self._window.bottomInfoBar)
|
||||||
|
self.show_dialog_popup.connect(self._window.openGeneralDialog)
|
||||||
|
self.show_dialog_download_db.connect(self._window.openDialogDownloadDb)
|
||||||
|
self.show_dialog_update_artemis.connect(self._window.openDialogUpdateArtemis)
|
||||||
|
self.lock_menu.connect(self._window.lockMenu)
|
||||||
|
|
||||||
|
self.populate_sig_details.connect(self._window_signal.populateSignalParam)
|
||||||
|
self.lock_audio_player.connect(self._window_signal.lockPlayer)
|
||||||
|
self.clear_signal_page.connect(self._window_signal.resetAll)
|
||||||
|
|
||||||
|
self.clear_filter_page.connect(self._window_filter.resetAll)
|
||||||
|
self.populate_filter_modulation.connect(self._window_filter.loadLists)
|
||||||
|
|
||||||
|
|
||||||
|
def load_db(self, db_dir_name):
|
||||||
|
""" Load the DB and populate the signals list
|
||||||
|
|
||||||
|
Args:
|
||||||
|
db_dir_name (str): folder name in the data folder
|
||||||
|
"""
|
||||||
|
# Loading DB
|
||||||
|
self.loaded_db = ArtemisDatabase(db_dir_name)
|
||||||
|
self.loaded_db.load()
|
||||||
|
# Clearing UI
|
||||||
|
self.lock_menu.emit(False)
|
||||||
|
self.clear_signal_page.emit()
|
||||||
|
self.clear_filter_page.emit()
|
||||||
|
# Populating UI
|
||||||
|
self.load_filter_lists()
|
||||||
|
self.populate_sig_list.emit(self.loaded_db.all_signals)
|
||||||
|
# Updating status bar
|
||||||
|
total_signals = len(self.loaded_db.all_signals)
|
||||||
|
self.bottom_info_bar("Database loaded with {} signals".format(total_signals), "info")
|
||||||
|
|
||||||
|
|
||||||
|
@Slot(int)
|
||||||
|
def load_sig(self, sig_id):
|
||||||
|
""" Load the selected signal and populate the SignalPage
|
||||||
|
|
||||||
|
Args:
|
||||||
|
sig_id (int): SIG_ID of the signal to be loaded
|
||||||
|
"""
|
||||||
|
self.loaded_sig = ArtemisSignal(self.loaded_db)
|
||||||
|
self.loaded_sig.load(sig_id)
|
||||||
|
sig_dic = self.loaded_sig.generate_dic()
|
||||||
|
|
||||||
|
self.populate_sig_details.emit([sig_dic])
|
||||||
|
|
||||||
|
|
||||||
|
def load_filter_lists(self):
|
||||||
|
""" Populates the 3 listviews in the FilterPage
|
||||||
|
"""
|
||||||
|
self.populate_filter_modulation.emit([{
|
||||||
|
'modulation': self.loaded_db.all_modulation,
|
||||||
|
'location': self.loaded_db.all_location,
|
||||||
|
'category': self.loaded_db.all_category_labels
|
||||||
|
}])
|
||||||
|
|
||||||
|
|
||||||
|
@Slot(dict)
|
||||||
|
def apply_filter(self, filter_status):
|
||||||
|
""" Update the signal list according to the selected filters in the FilterPage.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filter_status (dic): dictionary containing the active filters with all
|
||||||
|
the details to generate a search query
|
||||||
|
"""
|
||||||
|
filter_status = filter_status.toVariant()
|
||||||
|
if self.loaded_db is not None:
|
||||||
|
if filter_status != {}:
|
||||||
|
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)
|
||||||
|
|
||||||
|
total_signals = len(self.loaded_db.all_signals)
|
||||||
|
self.bottom_info_bar("FILTERS ACTIVE: {} signals found".format(total_signals), "warning")
|
||||||
|
else:
|
||||||
|
self.load_db(self.loaded_db.db_dir_name)
|
||||||
|
|
||||||
|
|
||||||
|
def show_pref_ui(self):
|
||||||
|
""" Load the preference windows
|
||||||
|
"""
|
||||||
|
self.preferences.load_preferences_ui()
|
||||||
|
|
||||||
|
|
||||||
|
def show_dbmanager_ui(self):
|
||||||
|
""" Load the DB manager windows
|
||||||
|
"""
|
||||||
|
self.dbmanager.load_dbmanager_ui()
|
||||||
|
|
||||||
|
|
||||||
|
@Slot(str, list, bool)
|
||||||
|
def open_sig_editor(self, type, sig_param, is_new):
|
||||||
|
""" Open the signal editor windows
|
||||||
|
Called when the user want to add, edit or delete the signal or its parametes.
|
||||||
|
"""
|
||||||
|
self.sigeditor.load_signaleditor_ui(type, sig_param, is_new)
|
||||||
|
|
||||||
|
|
||||||
|
def show_space_weather_ui(self):
|
||||||
|
""" Open the space weather windows
|
||||||
|
"""
|
||||||
|
self.spaceweather.load_spaceweather_ui()
|
||||||
|
|
||||||
|
|
||||||
|
def show_documentsmanager_ui(self):
|
||||||
|
""" Open the documents manager windows
|
||||||
|
"""
|
||||||
|
self.docmanager.load_documentsmanager_ui()
|
||||||
|
|
||||||
|
|
||||||
|
def check_for_update(self):
|
||||||
|
""" User manual check for updates updates
|
||||||
|
"""
|
||||||
|
self.update_manager.check_updates(True)
|
||||||
|
|
||||||
|
|
||||||
|
def dialog_download_db(self, message_type, title, message):
|
||||||
|
""" Dialog popup for DB download confirmation
|
||||||
|
"""
|
||||||
|
self.show_dialog_download_db.emit(message_type, title, message)
|
||||||
|
|
||||||
|
|
||||||
|
def dialog_update_artemis(self, message_type, title, message, auto=False):
|
||||||
|
""" Dialog popup for Artemis download confirmation
|
||||||
|
"""
|
||||||
|
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):
|
||||||
|
""" Open the local folder of the loaded DB
|
||||||
|
"""
|
||||||
|
open_directory(self.loaded_db.db_dir)
|
||||||
|
|
||||||
|
|
||||||
|
@Slot(str)
|
||||||
|
def new_db(self, name):
|
||||||
|
""" Create a new local DB
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): name of the new DB, hardcoded in sql info table
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
new_db = ArtemisDatabase(str(uuid.uuid4()))
|
||||||
|
new_db.create(name)
|
||||||
|
self.load_db(new_db.db_dir_name)
|
||||||
|
self.dialog_popup(
|
||||||
|
Messages.DIALOG_TYPE_INFO,
|
||||||
|
Messages.GENERIC_SUCCESS,
|
||||||
|
Messages.DB_CREATION_SUCCESS_MSG
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
self.dialog_popup(
|
||||||
|
Messages.DIALOG_TYPE_ERROR,
|
||||||
|
Messages.GENERIC_ERROR,
|
||||||
|
Messages.GENERIC_ERROR_MSG.format(e)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@Slot(str)
|
||||||
|
def export_db(self, save_path):
|
||||||
|
""" Export the load DB in a tar file. Does not use compression
|
||||||
|
|
||||||
|
Args:
|
||||||
|
save_path (str): destination path of the generated .tar file
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
dest_path = normalize_dialog_path(save_path)
|
||||||
|
make_tar(dest_path, self.loaded_db.db_dir)
|
||||||
|
self.dialog_popup(
|
||||||
|
Messages.DIALOG_TYPE_INFO,
|
||||||
|
Messages.GENERIC_SUCCESS,
|
||||||
|
Messages.EXPORTING_SUCCESS_MSG
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
self.dialog_popup(
|
||||||
|
Messages.DIALOG_TYPE_ERROR,
|
||||||
|
Messages.GENERIC_ERROR,
|
||||||
|
Messages.GENERIC_ERROR_MSG.format(e)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@Slot(str)
|
||||||
|
def import_db(self, tar_path):
|
||||||
|
""" Import a new DB in the Artemis data folder
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tar_path (str): Path of the archive to be imported
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
origin_path = normalize_dialog_path(tar_path)
|
||||||
|
save_path = DATA_DIR / str(uuid.uuid4())
|
||||||
|
unpack_tar(origin_path, save_path)
|
||||||
|
self.dialog_popup(
|
||||||
|
Messages.DIALOG_TYPE_INFO,
|
||||||
|
Messages.GENERIC_SUCCESS,
|
||||||
|
Messages.IMPORTING_SUCCESS_MSG
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
self.dialog_popup(
|
||||||
|
Messages.DIALOG_TYPE_ERROR,
|
||||||
|
Messages.GENERIC_ERROR,
|
||||||
|
Messages.GENERIC_ERROR_MSG.format(e)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@Slot(int)
|
||||||
|
def add_cat_tag(self, clb_id):
|
||||||
|
self.loaded_sig.insert_category(clb_id)
|
||||||
|
self.load_db(self.loaded_db.db_dir_name)
|
||||||
|
|
||||||
|
|
||||||
|
@Slot(int)
|
||||||
|
def delete_cat_tag(self, cat_id):
|
||||||
|
self.loaded_sig.delete_category(cat_id)
|
||||||
|
self.load_db(self.loaded_db.db_dir_name)
|
||||||
|
|
||||||
|
|
||||||
|
def open_cat_manager(self):
|
||||||
|
""" Open the category manager windows
|
||||||
|
"""
|
||||||
|
self.cateditor.load_cateditor_ui()
|
||||||
|
|
||||||
|
|
||||||
|
def autoload_db(self):
|
||||||
|
sig_id_path = DATA_DIR / 'SigID' / Constants.SQL_NAME
|
||||||
|
autoload = CONFIGURE_QT.value("Database", "autoload", 0)
|
||||||
|
if sig_id_path.exists() and int(autoload):
|
||||||
|
self.load_db('SigID')
|
||||||
|
|
||||||
|
|
||||||
|
def dialog_popup(self, message_type, title, message):
|
||||||
|
""" Opens a general dialog popup
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message_type (str): 'info', 'question', 'warn', 'error'
|
||||||
|
title (str): header of the dialoog
|
||||||
|
message (sstr): description inside the dialog
|
||||||
|
"""
|
||||||
|
self.show_dialog_popup.emit(message_type, title, message)
|
||||||
|
|
||||||
|
|
||||||
|
@Slot(str, str)
|
||||||
|
def bottom_info_bar(self, message, message_type):
|
||||||
|
""" Manage the footer info bar
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message (str): text to be shown in the info bar
|
||||||
|
message_type (str): 'info', 'warning'
|
||||||
|
"""
|
||||||
|
self.update_info_bar.emit(message, message_type)
|
||||||
67
artemis/ui/categoryeditor.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
from PySide6.QtQml import QQmlApplicationEngine
|
||||||
|
from PySide6.QtCore import QObject, Signal, Slot
|
||||||
|
|
||||||
|
from artemis.utils.path_utils import *
|
||||||
|
from artemis.utils.generic_utils import *
|
||||||
|
|
||||||
|
|
||||||
|
class UIcategoryeditor(QObject):
|
||||||
|
# Python > QML Signals
|
||||||
|
show_ui = Signal()
|
||||||
|
load = Signal(list)
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self._parent = parent
|
||||||
|
|
||||||
|
self._engine = QQmlApplicationEngine()
|
||||||
|
self._engine.load('qrc:/ui/CategoryEditor.qml')
|
||||||
|
self._window = self._engine.rootObjects()[0]
|
||||||
|
|
||||||
|
self._connect()
|
||||||
|
|
||||||
|
|
||||||
|
def _connect(self):
|
||||||
|
# QML > Python connections
|
||||||
|
self._window.saveParam.connect(self.save)
|
||||||
|
self._window.deleteParam.connect(self.delete)
|
||||||
|
|
||||||
|
# Python > QML connections
|
||||||
|
self.show_ui.connect(self._window.show)
|
||||||
|
self.load.connect(self._window.loadList)
|
||||||
|
|
||||||
|
|
||||||
|
def load_cateditor_ui(self):
|
||||||
|
""" Load the list with existing category tags and show the UI
|
||||||
|
"""
|
||||||
|
all_cat = self._parent.loaded_db.all_category_labels
|
||||||
|
self.load.emit(all_cat)
|
||||||
|
self.show_ui.emit()
|
||||||
|
|
||||||
|
|
||||||
|
@Slot(list, bool)
|
||||||
|
def save(self, data, is_new):
|
||||||
|
""" Save new category tag or update the existing ones.
|
||||||
|
"""
|
||||||
|
data = data.toVariant()
|
||||||
|
|
||||||
|
if is_new:
|
||||||
|
self._parent.loaded_db.insert_category_label(data[0])
|
||||||
|
else:
|
||||||
|
self._parent.loaded_db.update_category_label(data[1], data[0])
|
||||||
|
|
||||||
|
self._parent.load_db(self._parent.loaded_db.db_dir_name)
|
||||||
|
self.load_cateditor_ui()
|
||||||
|
|
||||||
|
|
||||||
|
@Slot(int)
|
||||||
|
def delete(self, clb_id):
|
||||||
|
""" Delete a database category tag.
|
||||||
|
All the entries in the documents table are automatically beign deleted due to
|
||||||
|
foreign-key cascade propagation
|
||||||
|
"""
|
||||||
|
self._parent.loaded_db.delete_category_label(clb_id)
|
||||||
|
self._parent.load_db(self._parent.loaded_db.db_dir_name)
|
||||||
|
self.load_cateditor_ui()
|
||||||
127
artemis/ui/dbmanager.py
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from PySide6.QtQml import QQmlApplicationEngine
|
||||||
|
from PySide6.QtCore import QObject, Signal, Slot
|
||||||
|
|
||||||
|
from artemis.utils.path_utils import DATA_DIR
|
||||||
|
from artemis.utils.generic_utils import *
|
||||||
|
from artemis.utils.sql_utils import ArtemisDatabase
|
||||||
|
from artemis.utils.constants import Constants
|
||||||
|
from artemis.utils.sys_utils import delete_dir
|
||||||
|
|
||||||
|
|
||||||
|
class UIdbmanager(QObject):
|
||||||
|
# Python > QML Signals
|
||||||
|
show_ui = Signal()
|
||||||
|
close_ui = Signal()
|
||||||
|
populate_db_list = Signal(list)
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self._parent = parent
|
||||||
|
|
||||||
|
self._engine = QQmlApplicationEngine()
|
||||||
|
self._engine.load('qrc:/ui/DbManager.qml')
|
||||||
|
self._window = self._engine.rootObjects()[0]
|
||||||
|
|
||||||
|
self._connect()
|
||||||
|
|
||||||
|
|
||||||
|
def _connect(self):
|
||||||
|
# QML > Python connections
|
||||||
|
self._window.loadDB.connect(self.load_db)
|
||||||
|
self._window.deleteDB.connect(self.delete_db)
|
||||||
|
self._window.renameDB.connect(self.rename_db)
|
||||||
|
|
||||||
|
# Python > QML connections
|
||||||
|
self.show_ui.connect(self._window.show)
|
||||||
|
self.close_ui.connect(self._window.close)
|
||||||
|
self.populate_db_list.connect(self._window.loadList)
|
||||||
|
|
||||||
|
|
||||||
|
def load_dbmanager_ui(self):
|
||||||
|
self.load_local_db_list()
|
||||||
|
self.show_ui.emit()
|
||||||
|
|
||||||
|
|
||||||
|
def load_local_db_list(self):
|
||||||
|
""" Scan for all the valid DBs in the data folder and show them on the list
|
||||||
|
"""
|
||||||
|
valid_db_list = self.scan_db_dir()
|
||||||
|
self.populate_db_list.emit(valid_db_list)
|
||||||
|
|
||||||
|
|
||||||
|
def load_db(self, db_dir_name):
|
||||||
|
""" Load the selected DB (from the DB Manager list) in the main artemis window
|
||||||
|
"""
|
||||||
|
self._parent.load_db(db_dir_name)
|
||||||
|
self.close_ui.emit()
|
||||||
|
|
||||||
|
|
||||||
|
@Slot(str)
|
||||||
|
def delete_db(self, db_dir_name):
|
||||||
|
""" Delete the DB folder.
|
||||||
|
Clear the main UI if the database to be deleted is the selected one
|
||||||
|
"""
|
||||||
|
if self._parent.loaded_db is not None:
|
||||||
|
if self._parent.loaded_db.db_dir_name == db_dir_name:
|
||||||
|
self._parent.lock_menu.emit(True)
|
||||||
|
self._parent.clear_list.emit()
|
||||||
|
self._parent.clear_signal_page.emit()
|
||||||
|
delete_dir(DATA_DIR / db_dir_name)
|
||||||
|
self.load_local_db_list()
|
||||||
|
|
||||||
|
|
||||||
|
@Slot(str, str)
|
||||||
|
def rename_db(self, db_dir_name, new_name):
|
||||||
|
""" Rename db in the data folder
|
||||||
|
"""
|
||||||
|
database = ArtemisDatabase(db_dir_name)
|
||||||
|
database.rename(new_name)
|
||||||
|
self.load_local_db_list()
|
||||||
|
|
||||||
|
|
||||||
|
def scan_db_dir(self):
|
||||||
|
""" Scans the data directory for valid databases and
|
||||||
|
return a dictionary containing only the valid ones with a summary
|
||||||
|
"""
|
||||||
|
valid_db_list = []
|
||||||
|
db_dirs = next(os.walk(DATA_DIR))[1]
|
||||||
|
|
||||||
|
for db_dir_name in db_dirs:
|
||||||
|
if self._valid_db(db_dir_name):
|
||||||
|
database = ArtemisDatabase(db_dir_name)
|
||||||
|
database.load()
|
||||||
|
valid_db_list.append(
|
||||||
|
{
|
||||||
|
'name': database.name,
|
||||||
|
'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
|
||||||
|
|
||||||
|
|
||||||
|
def _valid_db(self, db_dir_name):
|
||||||
|
""" Checks if db_dir_name is a valid db dir containing a `data.sqlite` file.
|
||||||
|
Db must be valid as well and should be properly initialized and loaded with
|
||||||
|
no errors.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
db_dir_name (str): name of the db folder
|
||||||
|
"""
|
||||||
|
if os.path.exists(DATA_DIR / db_dir_name / Constants.SQL_NAME):
|
||||||
|
try:
|
||||||
|
database = ArtemisDatabase(db_dir_name)
|
||||||
|
database.load()
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
return False # Invalid or corrupted DB
|
||||||
|
else:
|
||||||
|
return False # The dir is not containing a data.sqlite file
|
||||||
128
artemis/ui/documentsmanager.py
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
from PySide6.QtQml import QQmlApplicationEngine
|
||||||
|
from PySide6.QtCore import QObject, Signal, Slot
|
||||||
|
|
||||||
|
from artemis.utils.path_utils import *
|
||||||
|
from artemis.utils.generic_utils import *
|
||||||
|
from artemis.utils.sys_utils import *
|
||||||
|
|
||||||
|
|
||||||
|
class UIdocumentsmanager(QObject):
|
||||||
|
# Python > QML Signals
|
||||||
|
show_ui = Signal()
|
||||||
|
close_ui = Signal()
|
||||||
|
populate_documents_list = Signal(list)
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self._parent = parent
|
||||||
|
|
||||||
|
self._engine = QQmlApplicationEngine()
|
||||||
|
self._engine.load('qrc:/ui/DocumentsManager.qml')
|
||||||
|
self._window = self._engine.rootObjects()[0]
|
||||||
|
|
||||||
|
self._connect()
|
||||||
|
|
||||||
|
|
||||||
|
def _connect(self):
|
||||||
|
# QML > Python connections
|
||||||
|
self._window.saveNewDoc.connect(self.save_new_doc)
|
||||||
|
self._window.deleteDoc.connect(self.delete_doc)
|
||||||
|
self._window.updateDoc.connect(self.update_doc)
|
||||||
|
self._window.openDoc.connect(self.open_doc)
|
||||||
|
|
||||||
|
|
||||||
|
# Python > QML connections
|
||||||
|
self.show_ui.connect(self._window.show)
|
||||||
|
self.close_ui.connect(self._window.close)
|
||||||
|
self.populate_documents_list.connect(self._window.loadList)
|
||||||
|
|
||||||
|
|
||||||
|
def load_documentsmanager_ui(self):
|
||||||
|
self.load_documents_list()
|
||||||
|
self.show_ui.emit()
|
||||||
|
|
||||||
|
|
||||||
|
def load_documents_list(self):
|
||||||
|
""" Load the documents of the selected signal and populate the documents list
|
||||||
|
"""
|
||||||
|
self._parent.loaded_sig.select_documents()
|
||||||
|
all_documents = self._parent.loaded_sig.documents
|
||||||
|
|
||||||
|
keys = (
|
||||||
|
'doc_id',
|
||||||
|
'extension',
|
||||||
|
'name',
|
||||||
|
'description',
|
||||||
|
'type',
|
||||||
|
'preview'
|
||||||
|
)
|
||||||
|
|
||||||
|
doc_lst = [dict(zip(keys, values)) for values in all_documents]
|
||||||
|
self.populate_documents_list.emit(doc_lst)
|
||||||
|
|
||||||
|
|
||||||
|
@Slot(list)
|
||||||
|
def save_new_doc(self, doc_lst):
|
||||||
|
""" Save the new document (identified by the DOC_ID = -1) and reload the document list.
|
||||||
|
doc_param contains all the details of the new documents.
|
||||||
|
"""
|
||||||
|
doc_param = doc_lst.toVariant()
|
||||||
|
file_extension = os.path.splitext(doc_param[0])[1][1:]
|
||||||
|
|
||||||
|
doc_id = self._parent.loaded_sig.insert_document([
|
||||||
|
-1,
|
||||||
|
file_extension,
|
||||||
|
doc_param[1],
|
||||||
|
doc_param[2],
|
||||||
|
doc_param[3],
|
||||||
|
0
|
||||||
|
])
|
||||||
|
|
||||||
|
local_file_name = '{}.{}'.format(str(doc_id), file_extension)
|
||||||
|
origin_path = normalize_dialog_path(doc_param[0])
|
||||||
|
copy_file(origin_path, self._parent.loaded_db.media_dir / local_file_name)
|
||||||
|
self.load_documents_list()
|
||||||
|
|
||||||
|
|
||||||
|
@Slot(list)
|
||||||
|
def update_doc(self, doc_lst):
|
||||||
|
""" Update the details of the existent document
|
||||||
|
"""
|
||||||
|
doc_list = doc_lst.toVariant()
|
||||||
|
for doc in doc_list:
|
||||||
|
self._parent.loaded_sig.update_documents(doc[0], doc[1], doc[2], doc[3], doc[4])
|
||||||
|
self.load_documents_list()
|
||||||
|
|
||||||
|
|
||||||
|
@Slot(str, str)
|
||||||
|
def open_doc(self, doc_id, extension):
|
||||||
|
""" Open the selected document with the proper system application (if any)
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
open_file(self._parent.loaded_db.media_dir / '{}.{}'.format(doc_id, extension))
|
||||||
|
except Exception as e:
|
||||||
|
self.close_ui.emit()
|
||||||
|
self._parent.dialog_popup(
|
||||||
|
Messages.DIALOG_TYPE_ERROR,
|
||||||
|
Messages.GENERIC_ERROR,
|
||||||
|
str(e)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@Slot(str, str, str, bool)
|
||||||
|
def delete_doc(self, doc_id, doc_extension, doc_type, doc_preview):
|
||||||
|
""" Delete the selected document
|
||||||
|
"""
|
||||||
|
doc_file_name = '{}.{}'.format(doc_id, doc_extension)
|
||||||
|
doc_file_path = self._parent.loaded_db.media_dir / doc_file_name
|
||||||
|
|
||||||
|
self._parent.loaded_sig.delete_document(doc_id)
|
||||||
|
|
||||||
|
if doc_preview:
|
||||||
|
if doc_type == 'Audio':
|
||||||
|
self._parent.lock_audio_player.emit()
|
||||||
|
|
||||||
|
delete_file(doc_file_path)
|
||||||
|
self.load_documents_list()
|
||||||
167
artemis/ui/downloader.py
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
import requests
|
||||||
|
|
||||||
|
from PySide6.QtQml import QQmlApplicationEngine
|
||||||
|
from PySide6.QtCore import QObject, Slot, Signal, QUrl, QSaveFile, QDir, QIODevice
|
||||||
|
from PySide6.QtNetwork import QNetworkReply, QNetworkRequest, QNetworkAccessManager
|
||||||
|
|
||||||
|
from artemis.utils.constants import Messages
|
||||||
|
|
||||||
|
|
||||||
|
class UIDownloader(QObject):
|
||||||
|
# Python > QML Signals
|
||||||
|
show_ui = Signal()
|
||||||
|
close_ui = Signal()
|
||||||
|
update_progress_bar = Signal(int, int)
|
||||||
|
set_indeterminate_bar = Signal()
|
||||||
|
update_status = Signal(str)
|
||||||
|
finished = Signal()
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self._parent = parent
|
||||||
|
|
||||||
|
self._engine = QQmlApplicationEngine()
|
||||||
|
self._engine.load('qrc:/ui/Downloader.qml')
|
||||||
|
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()
|
||||||
|
|
||||||
|
|
||||||
|
def _connect(self):
|
||||||
|
# QML > Python connections
|
||||||
|
self._window.onAbort.connect(self.on_abort)
|
||||||
|
|
||||||
|
# Python > QML connections
|
||||||
|
self.show_ui.connect(self._window.show)
|
||||||
|
self.close_ui.connect(self._window.close)
|
||||||
|
self.update_progress_bar.connect(self._window.updateProgressBar)
|
||||||
|
self.set_indeterminate_bar.connect(self._window.setIndeterminateBar)
|
||||||
|
self.update_status.connect(self._window.updateStatus)
|
||||||
|
|
||||||
|
|
||||||
|
def on_start(self, url, save_path):
|
||||||
|
""" Start the download process using the specified URL
|
||||||
|
|
||||||
|
Args:
|
||||||
|
url (str): url from where download the file
|
||||||
|
save_path (str): path where to save the downloaded file
|
||||||
|
"""
|
||||||
|
self._clear_ui()
|
||||||
|
self.show_ui.emit()
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
if self.file.open(QIODevice.WriteOnly):
|
||||||
|
# Start a GET HTTP request
|
||||||
|
self.manager = QNetworkAccessManager(self)
|
||||||
|
self.reply = self.manager.get(QNetworkRequest(self.file_url))
|
||||||
|
self.reply.downloadProgress.connect(self.on_progress)
|
||||||
|
self.reply.finished.connect(self.on_finished)
|
||||||
|
self.reply.readyRead.connect(self.on_ready_read)
|
||||||
|
self.reply.errorOccurred.connect(self.on_error)
|
||||||
|
else:
|
||||||
|
self.close_ui.emit()
|
||||||
|
self.show_popup_error(self.file.errorString())
|
||||||
|
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def on_abort(self):
|
||||||
|
""" Stop the download when user presses the abort button
|
||||||
|
"""
|
||||||
|
if self.reply:
|
||||||
|
self.reply.abort()
|
||||||
|
|
||||||
|
if self.file:
|
||||||
|
self.file.cancelWriting()
|
||||||
|
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def on_ready_read(self):
|
||||||
|
""" Write available bytes to the file
|
||||||
|
"""
|
||||||
|
if self.reply:
|
||||||
|
if self.reply.error() == QNetworkReply.NoError:
|
||||||
|
self.file.write(self.reply.readAll())
|
||||||
|
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def on_finished(self):
|
||||||
|
""" Finalize the download process and, if no errors
|
||||||
|
occurs, emits the finished signal usefull for
|
||||||
|
a callback
|
||||||
|
"""
|
||||||
|
if self.reply:
|
||||||
|
self.reply.deleteLater()
|
||||||
|
|
||||||
|
if self.file:
|
||||||
|
self.file.commit()
|
||||||
|
|
||||||
|
if self.reply.error() == QNetworkReply.NoError:
|
||||||
|
self.finished.emit()
|
||||||
|
|
||||||
|
self.close_ui.emit()
|
||||||
|
|
||||||
|
|
||||||
|
@Slot(int, int)
|
||||||
|
def on_progress(self, bytesReceived: int):
|
||||||
|
""" Update progress bar and status label
|
||||||
|
"""
|
||||||
|
if self.file_size is not None:
|
||||||
|
self.update_status.emit("{:.1f} Mb / {:.1f} Mb".format(bytesReceived/10**6, self.file_size/10**6))
|
||||||
|
self.update_progress_bar.emit(bytesReceived, self.file_size)
|
||||||
|
else:
|
||||||
|
self.update_status.emit("{:.1f} Mb".format(bytesReceived/10**6))
|
||||||
|
|
||||||
|
|
||||||
|
@Slot(QNetworkReply.NetworkError)
|
||||||
|
def on_error(self, code: QNetworkReply.NetworkError):
|
||||||
|
""" Show a message if an error happen during download
|
||||||
|
"""
|
||||||
|
if self.reply:
|
||||||
|
self.close_ui.emit()
|
||||||
|
self.show_popup_error(
|
||||||
|
self.reply.errorString()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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):
|
||||||
|
self._parent.dialog_popup(
|
||||||
|
Messages.DIALOG_TYPE_ERROR,
|
||||||
|
Messages.GENERIC_ERROR,
|
||||||
|
Messages.GENERIC_ERROR_MSG.format(error_msg)
|
||||||
|
)
|
||||||
67
artemis/ui/preferences.py
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
from PySide6.QtQml import QQmlApplicationEngine
|
||||||
|
from PySide6.QtCore import QObject, Slot, Signal
|
||||||
|
|
||||||
|
from artemis.utils.config_utils import *
|
||||||
|
|
||||||
|
|
||||||
|
class UIPreferences(QObject):
|
||||||
|
# Python > QML Signals
|
||||||
|
show_ui = Signal()
|
||||||
|
load_material_accent = Signal(str)
|
||||||
|
load_material_theme = Signal(str)
|
||||||
|
load_autoload = Signal(int)
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self._parent = parent
|
||||||
|
|
||||||
|
self._engine = QQmlApplicationEngine()
|
||||||
|
self._engine.load('qrc:/ui/Preferences.qml')
|
||||||
|
self._window = self._engine.rootObjects()[0]
|
||||||
|
|
||||||
|
self._connect()
|
||||||
|
|
||||||
|
|
||||||
|
def _connect(self):
|
||||||
|
# QML > Python connections
|
||||||
|
self._window.saveMaterialAccent.connect(self.save_material_accent)
|
||||||
|
self._window.saveMaterialTheme.connect(self.save_material_theme)
|
||||||
|
self._window.saveAutoload.connect(self.save_autoload)
|
||||||
|
|
||||||
|
# Python > QML connections
|
||||||
|
self.show_ui.connect(self._window.show)
|
||||||
|
self.load_material_accent.connect(self._window.loadMaterialAccent)
|
||||||
|
self.load_material_theme.connect(self._window.loadMaterialTheme)
|
||||||
|
self.load_autoload.connect(self._window.loadAutoload)
|
||||||
|
|
||||||
|
|
||||||
|
def load_preferences_ui(self):
|
||||||
|
""" Loading all the initial preferences from the conf file to the UI
|
||||||
|
"""
|
||||||
|
self.load_material_accent.emit(CONFIGURE_QT.value("Material", "Accent", "Green"))
|
||||||
|
self.load_material_theme.emit(CONFIGURE_QT.value("Material", "Theme", "System"))
|
||||||
|
self.load_autoload.emit(int(CONFIGURE_QT.value("Database", "autoload", 0)))
|
||||||
|
self.show_ui.emit()
|
||||||
|
|
||||||
|
|
||||||
|
@Slot(str)
|
||||||
|
def save_material_accent(self, material_accent):
|
||||||
|
""" Saving material accent setting
|
||||||
|
"""
|
||||||
|
CONFIGURE_QT.set("Material", "Accent", material_accent)
|
||||||
|
|
||||||
|
|
||||||
|
@Slot(str)
|
||||||
|
def save_material_theme(self, material_theme):
|
||||||
|
""" Saving material theme setting
|
||||||
|
"""
|
||||||
|
CONFIGURE_QT.set("Material", "Theme", material_theme)
|
||||||
|
|
||||||
|
|
||||||
|
@Slot(int)
|
||||||
|
def save_autoload(self, autoload):
|
||||||
|
""" Saving autoload setting
|
||||||
|
"""
|
||||||
|
CONFIGURE_QT.set("Database", "autoload", str(autoload))
|
||||||
126
artemis/ui/signaleditor.py
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
from PySide6.QtQml import QQmlApplicationEngine
|
||||||
|
from PySide6.QtCore import QObject, Signal, Slot
|
||||||
|
|
||||||
|
from artemis.utils.path_utils import *
|
||||||
|
from artemis.utils.generic_utils import *
|
||||||
|
from artemis.utils.sql_utils import ArtemisSignal
|
||||||
|
from artemis.utils.sys_utils import delete_file
|
||||||
|
|
||||||
|
|
||||||
|
class UIsignaleditor(QObject):
|
||||||
|
# Python > QML Signals
|
||||||
|
show_ui = Signal()
|
||||||
|
load = Signal(str, list, bool)
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self._parent = parent
|
||||||
|
|
||||||
|
self._engine = QQmlApplicationEngine()
|
||||||
|
self._engine.load('qrc:/ui/SignalEditor.qml')
|
||||||
|
self._window = self._engine.rootObjects()[0]
|
||||||
|
|
||||||
|
self._connect()
|
||||||
|
|
||||||
|
|
||||||
|
def _connect(self):
|
||||||
|
# QML > Python connections
|
||||||
|
self._window.saveParam.connect(self.save)
|
||||||
|
self._window.deleteParam.connect(self.delete)
|
||||||
|
|
||||||
|
# Python > QML connections
|
||||||
|
self.show_ui.connect(self._window.show)
|
||||||
|
self.load.connect(self._window.load)
|
||||||
|
|
||||||
|
|
||||||
|
def load_signaleditor_ui(self, param_type, sig_param, is_new):
|
||||||
|
""" Load all the details of the selected signal
|
||||||
|
|
||||||
|
Args:
|
||||||
|
param_type (str): Signal, Frequency, Bandwidth, Modulation, Mode,
|
||||||
|
ACF, Location
|
||||||
|
sig_param (list): a list formed as [id, value, description]
|
||||||
|
is_new (bool): If true, the windows open in an empty state ready to
|
||||||
|
be compiled by the user. If false, the windows will open all the
|
||||||
|
current parameter for the loaded signal, for editing or deleting purposes.
|
||||||
|
"""
|
||||||
|
if param_type == 'Signal' and not is_new:
|
||||||
|
sig_param = [
|
||||||
|
self._parent.loaded_sig.sig_id,
|
||||||
|
self._parent.loaded_sig.name,
|
||||||
|
self._parent.loaded_sig.description
|
||||||
|
]
|
||||||
|
self.load.emit(param_type, sig_param, is_new)
|
||||||
|
self.show_ui.emit()
|
||||||
|
|
||||||
|
|
||||||
|
@Slot(str, list, bool)
|
||||||
|
def save(self, param_type, data, is_new):
|
||||||
|
""" Save new signal parameters or update the existing ones.
|
||||||
|
"""
|
||||||
|
data = data.toVariant()
|
||||||
|
|
||||||
|
if is_new:
|
||||||
|
if param_type == 'Signal':
|
||||||
|
self._parent.loaded_sig = ArtemisSignal(self._parent.loaded_db)
|
||||||
|
self._parent.loaded_sig.insert_signal(data[1], data[2])
|
||||||
|
elif param_type == 'Frequency':
|
||||||
|
self._parent.loaded_sig.insert_frequency(int(data[1]), data[2])
|
||||||
|
elif param_type == 'Bandwidth':
|
||||||
|
self._parent.loaded_sig.insert_bandwidth(int(data[1]), data[2])
|
||||||
|
elif param_type == 'Modulation':
|
||||||
|
self._parent.loaded_sig.insert_modulation(data[1], data[2])
|
||||||
|
elif param_type == 'Mode':
|
||||||
|
self._parent.loaded_sig.insert_mode(data[1], data[2])
|
||||||
|
elif param_type == 'ACF':
|
||||||
|
self._parent.loaded_sig.insert_acf(data[1], data[2])
|
||||||
|
elif param_type == 'Location':
|
||||||
|
self._parent.loaded_sig.insert_location(data[1], data[2])
|
||||||
|
else:
|
||||||
|
if param_type == 'Signal':
|
||||||
|
self._parent.loaded_sig.update_signal(data[0], data[1], data[2])
|
||||||
|
elif param_type == 'Frequency':
|
||||||
|
self._parent.loaded_sig.update_frequency(data[0], int(data[1]), data[2])
|
||||||
|
elif param_type == 'Bandwidth':
|
||||||
|
self._parent.loaded_sig.update_bandwidth(data[0], int(data[1]), data[2])
|
||||||
|
elif param_type == 'Modulation':
|
||||||
|
self._parent.loaded_sig.update_modulation(data[0], data[1], data[2])
|
||||||
|
elif param_type == 'Mode':
|
||||||
|
self._parent.loaded_sig.update_mode(data[0], data[1], data[2])
|
||||||
|
elif param_type == 'ACF':
|
||||||
|
self._parent.loaded_sig.update_acf(data[0], data[1], data[2])
|
||||||
|
elif param_type == 'Location':
|
||||||
|
self._parent.loaded_sig.update_location(data[0], data[1], data[2])
|
||||||
|
|
||||||
|
self._parent.load_db(self._parent.loaded_db.db_dir_name)
|
||||||
|
|
||||||
|
|
||||||
|
@Slot(str, int)
|
||||||
|
def delete(self, param_type, id):
|
||||||
|
""" Delete a signal parameter or the signal itself (with all the parameters and documents).
|
||||||
|
All the entries in the documents table are automatically beign deleted due to
|
||||||
|
foreign-key cascade propagation
|
||||||
|
"""
|
||||||
|
if param_type == 'Signal':
|
||||||
|
self._parent.loaded_sig.delete_signal()
|
||||||
|
self._parent.lock_audio_player.emit()
|
||||||
|
for doc in self._parent.loaded_sig.documents:
|
||||||
|
doc_file_name = '{}.{}'.format(str(doc[0]), doc[1])
|
||||||
|
doc_file_path = self._parent.loaded_db.media_dir / doc_file_name
|
||||||
|
delete_file(doc_file_path)
|
||||||
|
elif param_type == 'Frequency':
|
||||||
|
self._parent.loaded_sig.delete_frequency(id)
|
||||||
|
elif param_type == 'Bandwidth':
|
||||||
|
self._parent.loaded_sig.delete_bandwidth(id)
|
||||||
|
elif param_type == 'Modulation':
|
||||||
|
self._parent.loaded_sig.delete_modulation(id)
|
||||||
|
elif param_type == 'Mode':
|
||||||
|
self._parent.loaded_sig.delete_mode(id)
|
||||||
|
elif param_type == 'ACF':
|
||||||
|
self._parent.loaded_sig.delete_acf(id)
|
||||||
|
elif param_type == 'Location':
|
||||||
|
self._parent.loaded_sig.delete_location(id)
|
||||||
|
|
||||||
|
self._parent.load_db(self._parent.loaded_db.db_dir_name)
|
||||||
73
artemis/ui/spaceweather.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
from PySide6.QtQml import QQmlApplicationEngine
|
||||||
|
from PySide6.QtCore import QObject, Signal
|
||||||
|
|
||||||
|
from artemis.utils.path_utils import *
|
||||||
|
from artemis.utils.generic_utils import *
|
||||||
|
|
||||||
|
from artemis.utils.constants import Constants
|
||||||
|
|
||||||
|
|
||||||
|
class UIspaceweather(QObject):
|
||||||
|
# Python > QML Signals
|
||||||
|
show_ui = Signal()
|
||||||
|
load_poseidon_report = Signal(dict)
|
||||||
|
load_poseidon_forecast_report = Signal(dict)
|
||||||
|
load_poseidon_drap_report = Signal(dict)
|
||||||
|
load_aurora_report = Signal()
|
||||||
|
update_bottom_bar = Signal(str)
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self._parent = parent
|
||||||
|
|
||||||
|
self._engine = QQmlApplicationEngine()
|
||||||
|
self._engine.load('qrc:/ui/SpaceWeather.qml')
|
||||||
|
self._window = self._engine.rootObjects()[0]
|
||||||
|
|
||||||
|
self._window_current = self._window.findChild(QObject, "spaceWeatherCurrentObj")
|
||||||
|
self._window_forecast = self._window.findChild(QObject, "spaceWeatherForecastObj")
|
||||||
|
self._window_drap = self._window.findChild(QObject, "spaceWeatherDRAPObj")
|
||||||
|
self._window_aurora = self._window.findChild(QObject, "spaceWeatherAuroraObj")
|
||||||
|
|
||||||
|
self._connect()
|
||||||
|
|
||||||
|
|
||||||
|
def _connect(self):
|
||||||
|
# QML > Python connections
|
||||||
|
|
||||||
|
# Python > QML connections
|
||||||
|
self.show_ui.connect(self._window.show)
|
||||||
|
self.update_bottom_bar.connect(self._window.updateBottomBar)
|
||||||
|
self.load_poseidon_report.connect(self._window_current.loadReport)
|
||||||
|
self.load_poseidon_forecast_report.connect(self._window_forecast.loadForecastReport)
|
||||||
|
self.load_poseidon_drap_report.connect(self._window_drap.loadDrapReport)
|
||||||
|
self.load_aurora_report.connect(self._window_aurora.loadAuroraReport)
|
||||||
|
|
||||||
|
|
||||||
|
def load_spaceweather_ui(self):
|
||||||
|
""" Before opening the windows, poseidon report (data.json) is read online
|
||||||
|
"""
|
||||||
|
self.download_poseidon_report()
|
||||||
|
|
||||||
|
|
||||||
|
def download_poseidon_report(self):
|
||||||
|
update_manager = self._parent.update_manager
|
||||||
|
poseidon_data = update_manager.fetch_remote_json(
|
||||||
|
Constants.POSEIDON_REPORT_URL,
|
||||||
|
True
|
||||||
|
)
|
||||||
|
if poseidon_data:
|
||||||
|
self.load_poseidon_report.emit(poseidon_data)
|
||||||
|
self.load_poseidon_forecast_report.emit(poseidon_data)
|
||||||
|
self.load_poseidon_drap_report.emit(poseidon_data)
|
||||||
|
self.load_aurora_report.emit()
|
||||||
|
|
||||||
|
self.update_bottom_bar.emit(
|
||||||
|
'Loaded Poseidon report issued on {} at {} UTC'.format(
|
||||||
|
poseidon_data['JSON_INFO']['utc_date'],
|
||||||
|
poseidon_data['JSON_INFO']['utc_time']
|
||||||
|
)
|
||||||
|
)
|
||||||
|
self.show_ui.emit()
|
||||||
75
artemis/utils/config_utils.py
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
from configparser import ConfigParser
|
||||||
|
|
||||||
|
from artemis.utils.path_utils import PREFERENCES_DIR, BASE_DIR
|
||||||
|
from artemis.utils.sys_utils import copy_file
|
||||||
|
|
||||||
|
|
||||||
|
class Config(ConfigParser):
|
||||||
|
""" Custom configuration class derived from ConfigParser.
|
||||||
|
Used to get value, set, save and remove any configuration from the conf file
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, config_file_path, space_around_delimiters=False):
|
||||||
|
super().__init__()
|
||||||
|
self._config_file_path = config_file_path
|
||||||
|
self.read(self._config_file_path)
|
||||||
|
self._space_around_delimiters = space_around_delimiters
|
||||||
|
|
||||||
|
def value(self, section, option, default_value):
|
||||||
|
value = super().get(section, option, fallback=default_value)
|
||||||
|
return value
|
||||||
|
|
||||||
|
def set(self, section, option, value=None):
|
||||||
|
if not self.has_section(section):
|
||||||
|
self.add_section(section)
|
||||||
|
super().set(section, option, value)
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def remove(self, section, option):
|
||||||
|
super().remove_option(section, option)
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
with open(self._config_file_path, 'w') as f:
|
||||||
|
self.write(f, space_around_delimiters=self._space_around_delimiters)
|
||||||
|
|
||||||
|
|
||||||
|
def merge_config_files(old_config_path, template_config_path):
|
||||||
|
""" Merge two configuration files: if the old one lacks some
|
||||||
|
sections or options from a comparison with a template,
|
||||||
|
this function will add what is missing to the old conf file
|
||||||
|
"""
|
||||||
|
old_config = ConfigParser()
|
||||||
|
old_config.read(old_config_path)
|
||||||
|
|
||||||
|
new_config = ConfigParser()
|
||||||
|
new_config.read(template_config_path)
|
||||||
|
|
||||||
|
for section in new_config.sections():
|
||||||
|
if not old_config.has_section(section):
|
||||||
|
old_config.add_section(section)
|
||||||
|
for option in new_config.options(section):
|
||||||
|
if not old_config.has_option(section, option):
|
||||||
|
old_config.set(section, option, new_config.get(section, option))
|
||||||
|
|
||||||
|
with open(old_config_path, 'w') as f:
|
||||||
|
old_config.write(f)
|
||||||
|
|
||||||
|
|
||||||
|
def check_conf_file():
|
||||||
|
""" Check the integrity of the used conf file.
|
||||||
|
If it is not present it will add a copy to the PREF_DIR
|
||||||
|
and if it is different in structure (different section/options)
|
||||||
|
it will merge the conf file with the new template one
|
||||||
|
"""
|
||||||
|
active_conf = (PREFERENCES_DIR / 'qtquickcontrols2.conf').resolve()
|
||||||
|
template_conf = (BASE_DIR / 'config' / 'qtquickcontrols2.conf').resolve()
|
||||||
|
|
||||||
|
if not active_conf.exists():
|
||||||
|
copy_file(template_conf, active_conf)
|
||||||
|
else:
|
||||||
|
merge_config_files(active_conf, template_conf)
|
||||||
|
|
||||||
|
|
||||||
|
check_conf_file()
|
||||||
|
CONFIGURE_QT = Config((PREFERENCES_DIR / 'qtquickcontrols2.conf').resolve().as_posix())
|
||||||
493
artemis/utils/constants.py
Normal file
@@ -0,0 +1,493 @@
|
|||||||
|
import locale
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from PySide6.QtCore import qVersion
|
||||||
|
|
||||||
|
|
||||||
|
class Constants():
|
||||||
|
""" Container class for several constants of the software """
|
||||||
|
|
||||||
|
APPLICATION_NAME = 'Artemis'
|
||||||
|
ORGANIZATION_NAME = 'AresValley'
|
||||||
|
ORGANIZATION_DOMAIN = 'aresvalley.com'
|
||||||
|
APPLICATION_VERSION = '4.0.3'
|
||||||
|
|
||||||
|
SQL_NAME = 'data.sqlite'
|
||||||
|
|
||||||
|
LATEST_VERSION_URL = 'https://raw.githubusercontent.com/AresValley/Artemis/master/config/release-info.json'
|
||||||
|
POSEIDON_REPORT_URL = 'https://www.aresvalley.com/poseidon_engine/data.json'
|
||||||
|
|
||||||
|
DEFAULT_ENCODING = 'utf-8'
|
||||||
|
SYSTEM_LANGUAGE = 'en_US' # locale.getdefaultlocale()[0]
|
||||||
|
PYTHON_VERSION = '.'.join(str(v) for v in sys.version_info[:3])
|
||||||
|
QT_VERSION = qVersion()
|
||||||
|
|
||||||
|
|
||||||
|
class Messages:
|
||||||
|
""" Container class for messages to be displayed """
|
||||||
|
# Type
|
||||||
|
DIALOG_TYPE_INFO = 'info'
|
||||||
|
DIALOG_TYPE_QUEST = 'question'
|
||||||
|
DIALOG_TYPE_WARN = 'warn'
|
||||||
|
DIALOG_TYPE_ERROR = 'error'
|
||||||
|
|
||||||
|
# Titles
|
||||||
|
GENERIC_SUCCESS = "Success!"
|
||||||
|
GENERIC_ERROR = "Something went wrong!"
|
||||||
|
NO_DB_DETECTED = "No SigID database detected..."
|
||||||
|
NO_CONNECTION = "Connection Error!"
|
||||||
|
UP_TO_DATE = "You're up to date!"
|
||||||
|
DB_NEW_VER = "New SigID DB version available!"
|
||||||
|
ART_NEW_VER = "New Artemis version available!"
|
||||||
|
DB_CORRUPTED = "Database Corruption Detected"
|
||||||
|
|
||||||
|
# Messages
|
||||||
|
DB_CREATION_SUCCESS_MSG = "The new database has been created succesfully."
|
||||||
|
GENERIC_ERROR_MSG = "An error occurred during the process. Details: {}"
|
||||||
|
IMPORTING_SUCCESS_MSG = "Database importing has been succesfully completed!"
|
||||||
|
EXPORTING_SUCCESS_MSG = "Database exporting has been succesfully completed!"
|
||||||
|
FILE_NOT_FOUND_ERR_MSG = "The file you are trying to access cannot be located. This may be because the file has been moved or deleted."
|
||||||
|
NO_DB_DETECTED_MSG = "Do you want to download it now?"
|
||||||
|
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."
|
||||||
|
DB_NEW_VER_MSG = "A new version of the database ({}) is available for download. Download now?"
|
||||||
|
ART_NEW_VER_MANUAL_MSG = "A new version of Artemis ({}) is available for download. Check GitHub page now?"
|
||||||
|
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():
|
||||||
|
""" Container class for all the sqlite queries """
|
||||||
|
|
||||||
|
############################## SELECT
|
||||||
|
|
||||||
|
SELECT_ALL_SIGNALS = "SELECT SIG_ID, NAME, DESCRIPTION FROM signals ORDER BY NAME ASC"
|
||||||
|
|
||||||
|
SELECT_ALL_MODULATION = "SELECT DISTINCT VALUE FROM modulation ORDER BY VALUE ASC"
|
||||||
|
|
||||||
|
SELECT_ALL_LOCATION = "SELECT DISTINCT VALUE FROM location ORDER BY VALUE ASC"
|
||||||
|
|
||||||
|
SELECT_SIG_ID = "SELECT SIG_ID, NAME, DESCRIPTION FROM signals WHERE SIG_ID IN ({}) ORDER BY NAME ASC"
|
||||||
|
|
||||||
|
SELECT_ALL_CAT_LABELS = "SELECT CLB_ID, VALUE FROM category_label ORDER BY VALUE ASC"
|
||||||
|
|
||||||
|
SELECT_INFO = """
|
||||||
|
SELECT
|
||||||
|
NAME,
|
||||||
|
DATE,
|
||||||
|
VERSION,
|
||||||
|
EDITABLE
|
||||||
|
FROM info
|
||||||
|
"""
|
||||||
|
|
||||||
|
SELECT_SIGNAL = """
|
||||||
|
SELECT
|
||||||
|
NAME,
|
||||||
|
DESCRIPTION,
|
||||||
|
URL
|
||||||
|
FROM signals WHERE SIG_ID = ?
|
||||||
|
"""
|
||||||
|
|
||||||
|
SELECT_CATEGORY = """
|
||||||
|
SELECT
|
||||||
|
category.CAT_ID,
|
||||||
|
category_label.VALUE
|
||||||
|
FROM category
|
||||||
|
INNER JOIN category_label ON category.CLB_ID = category_label.CLB_ID
|
||||||
|
WHERE SIG_ID = ?
|
||||||
|
"""
|
||||||
|
|
||||||
|
SELECT_DOCUMENTS = """
|
||||||
|
SELECT
|
||||||
|
DOC_ID,
|
||||||
|
EXTENSION,
|
||||||
|
NAME,
|
||||||
|
DESCRIPTION,
|
||||||
|
TYPE,
|
||||||
|
PREVIEW
|
||||||
|
FROM documents WHERE SIG_ID = ?
|
||||||
|
ORDER BY TYPE ASC
|
||||||
|
"""
|
||||||
|
|
||||||
|
SELECT_FREQUENCY = """
|
||||||
|
SELECT
|
||||||
|
FREQ_ID,
|
||||||
|
VALUE,
|
||||||
|
DESCRIPTION
|
||||||
|
FROM frequency WHERE SIG_ID = ?
|
||||||
|
"""
|
||||||
|
|
||||||
|
SELECT_BANDWIDTH = """
|
||||||
|
SELECT
|
||||||
|
BAND_ID,
|
||||||
|
VALUE,
|
||||||
|
DESCRIPTION
|
||||||
|
FROM bandwidth WHERE SIG_ID = ?
|
||||||
|
"""
|
||||||
|
|
||||||
|
SELECT_MODULATION = """
|
||||||
|
SELECT
|
||||||
|
MDL_ID,
|
||||||
|
VALUE,
|
||||||
|
DESCRIPTION
|
||||||
|
FROM modulation WHERE SIG_ID = ?
|
||||||
|
"""
|
||||||
|
|
||||||
|
SELECT_MODE= """
|
||||||
|
SELECT
|
||||||
|
MOD_ID,
|
||||||
|
VALUE,
|
||||||
|
DESCRIPTION
|
||||||
|
FROM mode WHERE SIG_ID = ?
|
||||||
|
"""
|
||||||
|
|
||||||
|
SELECT_LOCATION = """
|
||||||
|
SELECT
|
||||||
|
LOC_ID,
|
||||||
|
VALUE,
|
||||||
|
DESCRIPTION
|
||||||
|
FROM location WHERE SIG_ID = ?
|
||||||
|
"""
|
||||||
|
|
||||||
|
SELECT_ACF = """
|
||||||
|
SELECT
|
||||||
|
ACF_ID,
|
||||||
|
VALUE,
|
||||||
|
DESCRIPTION
|
||||||
|
FROM acf WHERE SIG_ID = ?
|
||||||
|
"""
|
||||||
|
|
||||||
|
SELECT_STAT_DOCS = """
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM documents
|
||||||
|
"""
|
||||||
|
|
||||||
|
SELECT_STAT_IMAGES = """
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM documents
|
||||||
|
WHERE type IS 'Image'
|
||||||
|
"""
|
||||||
|
|
||||||
|
SELECT_STAT_AUDIO = """
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM documents
|
||||||
|
WHERE type IS 'Audio'
|
||||||
|
"""
|
||||||
|
|
||||||
|
############################## CREATE
|
||||||
|
|
||||||
|
CREATE_SIGNALS = """
|
||||||
|
CREATE TABLE signals (
|
||||||
|
SIG_ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
NAME TEXT,
|
||||||
|
DESCRIPTION TEXT,
|
||||||
|
URL TEXT
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
CREATE_CATEGORY = """
|
||||||
|
CREATE TABLE category (
|
||||||
|
CAT_ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
SIG_ID INTEGER,
|
||||||
|
CLB_ID INTEGER,
|
||||||
|
FOREIGN KEY (SIG_ID) REFERENCES signals (SIG_ID) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
FOREIGN KEY (CLB_ID) REFERENCES category_label (CLB_ID) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
CREATE_CATEGORY_LABELS = """
|
||||||
|
CREATE TABLE category_label (
|
||||||
|
CLB_ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
VALUE TEXT
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
CREATE_DOCUMENTS = """
|
||||||
|
CREATE TABLE documents (
|
||||||
|
DOC_ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
SIG_ID INTEGER REFERENCES signals (SIG_ID) ON DELETE CASCADE,
|
||||||
|
EXTENSION TEXT,
|
||||||
|
NAME TEXT,
|
||||||
|
DESCRIPTION TEXT,
|
||||||
|
TYPE TEXT,
|
||||||
|
PREVIEW INTEGER
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
CREATE_FREQUENCY = """
|
||||||
|
CREATE TABLE frequency (
|
||||||
|
FREQ_ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
SIG_ID INTEGER REFERENCES signals (SIG_ID) ON DELETE CASCADE,
|
||||||
|
VALUE INTEGER,
|
||||||
|
DESCRIPTION TEXT
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
CREATE_BANDWIDTH = """
|
||||||
|
CREATE TABLE bandwidth (
|
||||||
|
BAND_ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
SIG_ID INTEGER REFERENCES signals (SIG_ID) ON DELETE CASCADE,
|
||||||
|
VALUE INTEGER,
|
||||||
|
DESCRIPTION TEXT
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
CREATE_MODULATION = """
|
||||||
|
CREATE TABLE modulation (
|
||||||
|
MDL_ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
SIG_ID INTEGER REFERENCES signals (SIG_ID) ON DELETE CASCADE,
|
||||||
|
VALUE TEXT,
|
||||||
|
DESCRIPTION TEXT
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
CREATE_MODE = """
|
||||||
|
CREATE TABLE mode (
|
||||||
|
MOD_ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
SIG_ID INTEGER,
|
||||||
|
VALUE TEXT,
|
||||||
|
DESCRIPTION TEXT,
|
||||||
|
FOREIGN KEY (SIG_ID) REFERENCES signals (SIG_ID) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
CREATE_LOCATION = """
|
||||||
|
CREATE TABLE location (
|
||||||
|
LOC_ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
SIG_ID INTEGER,
|
||||||
|
VALUE TEXT,
|
||||||
|
DESCRIPTION TEXT,
|
||||||
|
FOREIGN KEY (SIG_ID) REFERENCES signals (SIG_ID) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
CREATE_ACF = """
|
||||||
|
CREATE TABLE acf (
|
||||||
|
ACF_ID INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
SIG_ID INTEGER REFERENCES signals (SIG_ID) ON DELETE CASCADE,
|
||||||
|
VALUE FLOAT,
|
||||||
|
DESCRIPTION TEXT
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
CREATE_INFO = """
|
||||||
|
CREATE TABLE info (
|
||||||
|
NAME TEXT,
|
||||||
|
DATE TEXT,
|
||||||
|
VERSION INTEGER,
|
||||||
|
EDITABLE INTEGER
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
|
||||||
|
CREATE_VIEW_FREQ = """
|
||||||
|
CREATE VIEW FREQ_RANGE AS
|
||||||
|
SELECT SIG_ID,
|
||||||
|
MIN(VALUE) AS MIN_VALUE,
|
||||||
|
MAX(VALUE) AS MAX_VALUE
|
||||||
|
FROM frequency
|
||||||
|
GROUP BY SIG_ID
|
||||||
|
"""
|
||||||
|
|
||||||
|
CREATE_VIEW_BAND = """
|
||||||
|
CREATE VIEW BAND_RANGE AS
|
||||||
|
SELECT SIG_ID,
|
||||||
|
MIN(VALUE) AS MIN_VALUE,
|
||||||
|
MAX(VALUE) AS MAX_VALUE
|
||||||
|
FROM bandwidth
|
||||||
|
GROUP BY SIG_ID
|
||||||
|
"""
|
||||||
|
|
||||||
|
############################## INSERT
|
||||||
|
|
||||||
|
INSERT_SIGNAL = """
|
||||||
|
INSERT INTO signals (
|
||||||
|
NAME,
|
||||||
|
DESCRIPTION
|
||||||
|
) VALUES (?,?)
|
||||||
|
"""
|
||||||
|
|
||||||
|
INSERT_CATEGORY = """
|
||||||
|
INSERT INTO category (
|
||||||
|
SIG_ID,
|
||||||
|
CLB_ID
|
||||||
|
) VALUES (?,?)
|
||||||
|
"""
|
||||||
|
|
||||||
|
INSERT_CATEGORY_LABEL = """
|
||||||
|
INSERT INTO category_label (
|
||||||
|
VALUE
|
||||||
|
) VALUES (?)
|
||||||
|
"""
|
||||||
|
|
||||||
|
INSERT_INFO = """
|
||||||
|
INSERT INTO info (
|
||||||
|
NAME,
|
||||||
|
DATE,
|
||||||
|
VERSION,
|
||||||
|
EDITABLE
|
||||||
|
) VALUES (?,?,?,?)
|
||||||
|
"""
|
||||||
|
|
||||||
|
INSERT_DOCUMENTS = """
|
||||||
|
INSERT INTO documents (
|
||||||
|
SIG_ID,
|
||||||
|
EXTENSION,
|
||||||
|
NAME,
|
||||||
|
DESCRIPTION,
|
||||||
|
TYPE,
|
||||||
|
PREVIEW
|
||||||
|
) VALUES (?,?,?,?,?,?)
|
||||||
|
"""
|
||||||
|
|
||||||
|
INSERT_FREQUENCY = """
|
||||||
|
INSERT INTO frequency (
|
||||||
|
SIG_ID,
|
||||||
|
VALUE,
|
||||||
|
DESCRIPTION
|
||||||
|
) VALUES (?,?,?)
|
||||||
|
"""
|
||||||
|
|
||||||
|
INSERT_BANDWIDTH = """
|
||||||
|
INSERT INTO bandwidth (
|
||||||
|
SIG_ID,
|
||||||
|
VALUE,
|
||||||
|
DESCRIPTION
|
||||||
|
) VALUES (?,?,?)
|
||||||
|
"""
|
||||||
|
|
||||||
|
INSERT_MODULATION = """
|
||||||
|
INSERT INTO modulation (
|
||||||
|
SIG_ID,
|
||||||
|
VALUE,
|
||||||
|
DESCRIPTION
|
||||||
|
) VALUES (?,?,?)
|
||||||
|
"""
|
||||||
|
|
||||||
|
INSERT_MODE = """
|
||||||
|
INSERT INTO mode (
|
||||||
|
SIG_ID,
|
||||||
|
VALUE,
|
||||||
|
DESCRIPTION
|
||||||
|
) VALUES (?,?,?)
|
||||||
|
"""
|
||||||
|
|
||||||
|
INSERT_LOCATION = """
|
||||||
|
INSERT INTO location (
|
||||||
|
SIG_ID,
|
||||||
|
VALUE,
|
||||||
|
DESCRIPTION
|
||||||
|
) VALUES (?,?,?)
|
||||||
|
"""
|
||||||
|
|
||||||
|
INSERT_ACF = """
|
||||||
|
INSERT INTO acf (
|
||||||
|
SIG_ID,
|
||||||
|
VALUE,
|
||||||
|
DESCRIPTION
|
||||||
|
) VALUES (?,?,?)
|
||||||
|
"""
|
||||||
|
|
||||||
|
############################## UPDATE
|
||||||
|
|
||||||
|
RENAME_DB = "UPDATE info SET NAME = ?"
|
||||||
|
|
||||||
|
UPDATE_SIGNAL = """
|
||||||
|
UPDATE signals SET
|
||||||
|
NAME = ?,
|
||||||
|
DESCRIPTION = ?
|
||||||
|
WHERE SIG_ID = ?
|
||||||
|
"""
|
||||||
|
|
||||||
|
UPDATE_CATEGORY_LABEL = """
|
||||||
|
UPDATE category_label SET
|
||||||
|
VALUE = ?
|
||||||
|
WHERE CLB_ID = ?
|
||||||
|
"""
|
||||||
|
|
||||||
|
UPDATE_FREQUENCY = """
|
||||||
|
UPDATE frequency SET
|
||||||
|
VALUE = ?,
|
||||||
|
DESCRIPTION = ?
|
||||||
|
WHERE FREQ_ID = ?
|
||||||
|
"""
|
||||||
|
|
||||||
|
UPDATE_BANDWIDTH = """
|
||||||
|
UPDATE bandwidth SET
|
||||||
|
VALUE = ?,
|
||||||
|
DESCRIPTION = ?
|
||||||
|
WHERE BAND_ID = ?
|
||||||
|
"""
|
||||||
|
|
||||||
|
UPDATE_ACF = """
|
||||||
|
UPDATE acf SET
|
||||||
|
VALUE = ?,
|
||||||
|
DESCRIPTION = ?
|
||||||
|
WHERE ACF_ID = ?
|
||||||
|
"""
|
||||||
|
|
||||||
|
UPDATE_MODE = """
|
||||||
|
UPDATE mode SET
|
||||||
|
VALUE = ?,
|
||||||
|
DESCRIPTION = ?
|
||||||
|
WHERE MOD_ID = ?
|
||||||
|
"""
|
||||||
|
|
||||||
|
UPDATE_LOCATION = """
|
||||||
|
UPDATE location SET
|
||||||
|
VALUE = ?,
|
||||||
|
DESCRIPTION = ?
|
||||||
|
WHERE LOC_ID = ?
|
||||||
|
"""
|
||||||
|
|
||||||
|
UPDATE_MODULATION = """
|
||||||
|
UPDATE modulation SET
|
||||||
|
VALUE = ?,
|
||||||
|
DESCRIPTION = ?
|
||||||
|
WHERE MDL_ID = ?
|
||||||
|
"""
|
||||||
|
|
||||||
|
UPDATE_DOCUMENTS = """
|
||||||
|
UPDATE documents SET
|
||||||
|
NAME = ?,
|
||||||
|
DESCRIPTION = ?,
|
||||||
|
TYPE = ?,
|
||||||
|
PREVIEW = ?
|
||||||
|
WHERE DOC_ID = ?
|
||||||
|
"""
|
||||||
|
|
||||||
|
############################## DELETE
|
||||||
|
|
||||||
|
DELETE_SIGNAL = "DELETE FROM signals WHERE SIG_ID = ?"
|
||||||
|
|
||||||
|
DELETE_DOCUMENT = "DELETE FROM documents WHERE DOC_ID = ?"
|
||||||
|
|
||||||
|
DELETE_FREQUENCY = "DELETE FROM frequency WHERE FREQ_ID = ?"
|
||||||
|
|
||||||
|
DELETE_BANDWIDTH = "DELETE FROM bandwidth WHERE BAND_ID = ?"
|
||||||
|
|
||||||
|
DELETE_MODULATION = "DELETE FROM modulation WHERE MDL_ID = ?"
|
||||||
|
|
||||||
|
DELETE_MODE = "DELETE FROM mode WHERE MOD_ID = ?"
|
||||||
|
|
||||||
|
DELETE_LOCATION = "DELETE FROM location WHERE LOC_ID = ?"
|
||||||
|
|
||||||
|
DELETE_ACF = "DELETE FROM acf WHERE ACF_ID = ?"
|
||||||
|
|
||||||
|
DELETE_CATEGORY = "DELETE FROM category WHERE CAT_ID = ?"
|
||||||
|
|
||||||
|
DELETE_CATEGORY_LABEL = "DELETE FROM category_label WHERE CLB_ID = ?"
|
||||||
|
|
||||||
|
############################## FILTER QUERY
|
||||||
|
|
||||||
|
FILTER_FREQ = "SELECT SIG_ID FROM FREQ_RANGE WHERE ({} >= MIN_VALUE) AND ({} <= MAX_VALUE)"
|
||||||
|
|
||||||
|
FILTER_BAND = "SELECT SIG_ID FROM BAND_RANGE WHERE ({} >= MIN_VALUE) AND ({} <= MAX_VALUE)"
|
||||||
|
|
||||||
|
FILTER_ACF = "SELECT SIG_ID FROM acf WHERE ({} >= VALUE) AND ({} <= VALUE)"
|
||||||
|
|
||||||
|
FILTER_MODULATION = "SELECT SIG_ID FROM modulation WHERE VALUE IN ({})"
|
||||||
|
|
||||||
|
FILTER_LOCATION = "SELECT SIG_ID FROM location WHERE VALUE IN ({})"
|
||||||
|
|
||||||
|
FILTER_CATEGORY = "SELECT SIG_ID FROM category WHERE CLB_ID IN ({})"
|
||||||
77
artemis/utils/generic_utils.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
from artemis.utils.constants import Query
|
||||||
|
|
||||||
|
|
||||||
|
def format_frequency(freq_hz):
|
||||||
|
""" Return frequency in a human-readable format
|
||||||
|
|
||||||
|
Args:
|
||||||
|
freq_hz (int): frequency in Hz
|
||||||
|
"""
|
||||||
|
scale = _change_unit_freq(freq_hz)
|
||||||
|
formatted_freq = f'{freq_hz / scale[0]} {scale[1]}'
|
||||||
|
return formatted_freq
|
||||||
|
|
||||||
|
|
||||||
|
def _change_unit_freq(freq_hz):
|
||||||
|
""" Return a scale factor and unit based on the number of digits in the frequency
|
||||||
|
|
||||||
|
Args:
|
||||||
|
freq_hz (int): frequency in Hz
|
||||||
|
"""
|
||||||
|
digits = len(str(freq_hz))
|
||||||
|
|
||||||
|
if digits < 4:
|
||||||
|
return 1, 'Hz'
|
||||||
|
elif digits < 7:
|
||||||
|
return 10**3, 'kHz'
|
||||||
|
elif digits < 10:
|
||||||
|
return 10**6, 'MHz'
|
||||||
|
else:
|
||||||
|
return 10**9, 'GHz'
|
||||||
|
|
||||||
|
|
||||||
|
def generate_filter_query(filer_status):
|
||||||
|
""" Returns the sql query according to the selected filter parameters
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filer_status (dic): dictionary containing a summary of the active
|
||||||
|
filtering options with the related parametes.
|
||||||
|
"""
|
||||||
|
query = []
|
||||||
|
|
||||||
|
for key, val in filer_status.items():
|
||||||
|
|
||||||
|
if key == 'frequency':
|
||||||
|
query.append(Query.FILTER_FREQ.format(
|
||||||
|
val['upper_band'],
|
||||||
|
val['lower_band']
|
||||||
|
))
|
||||||
|
|
||||||
|
elif key == 'bandwidth':
|
||||||
|
query.append(Query.FILTER_BAND.format(
|
||||||
|
val['upper_band'],
|
||||||
|
val['lower_band']
|
||||||
|
))
|
||||||
|
|
||||||
|
elif key == 'acf':
|
||||||
|
query.append(Query.FILTER_ACF.format(
|
||||||
|
val['upper_band'],
|
||||||
|
val['lower_band']
|
||||||
|
))
|
||||||
|
|
||||||
|
elif key == 'modulation':
|
||||||
|
query.append(Query.FILTER_MODULATION.format(
|
||||||
|
', '.join(f"'{mod}'" for mod in val)
|
||||||
|
))
|
||||||
|
|
||||||
|
elif key == 'location':
|
||||||
|
query.append(Query.FILTER_LOCATION.format(
|
||||||
|
', '.join(f"'{loc}'" for loc in val)
|
||||||
|
))
|
||||||
|
|
||||||
|
elif key == 'category':
|
||||||
|
query.append(Query.FILTER_CATEGORY.format(
|
||||||
|
', '.join(f"{cat}" for cat in val)
|
||||||
|
))
|
||||||
|
|
||||||
|
return ' INTERSECT '.join(query)
|
||||||
59
artemis/utils/path_utils.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from artemis.utils.constants import Constants
|
||||||
|
from artemis.utils.sys_utils import is_windows, is_linux, is_macos
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_dialog_path(path):
|
||||||
|
if is_windows():
|
||||||
|
norm_path = path.replace('file:///', '')
|
||||||
|
elif is_linux() or is_macos():
|
||||||
|
norm_path = path.replace('file:///', '/')
|
||||||
|
return norm_path
|
||||||
|
|
||||||
|
|
||||||
|
def _app_dir():
|
||||||
|
if is_macos():
|
||||||
|
app_dir_path = Path.home() / 'Library' / 'Application Support' / Constants.ORGANIZATION_NAME / Constants.APPLICATION_NAME
|
||||||
|
elif is_windows():
|
||||||
|
app_dir_path = Path.home() / 'AppData' / 'Local' / Constants.ORGANIZATION_NAME / Constants.APPLICATION_NAME
|
||||||
|
elif is_linux():
|
||||||
|
app_dir_path = Path.home() / '.local' / 'share' / Constants.ORGANIZATION_NAME / Constants.APPLICATION_NAME
|
||||||
|
else:
|
||||||
|
app_dir_path = BASE_DIR.resolve()
|
||||||
|
|
||||||
|
if not app_dir_path.exists():
|
||||||
|
app_dir_path.mkdir(parents=True)
|
||||||
|
|
||||||
|
return app_dir_path
|
||||||
|
|
||||||
|
|
||||||
|
def _data_dir():
|
||||||
|
data_dir_path = APP_DIR / 'data'
|
||||||
|
if not data_dir_path.exists():
|
||||||
|
data_dir_path.mkdir(parents=True)
|
||||||
|
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():
|
||||||
|
preference_dir_path = APP_DIR / 'config'
|
||||||
|
if not preference_dir_path.exists():
|
||||||
|
preference_dir_path.mkdir(parents=True)
|
||||||
|
return preference_dir_path
|
||||||
|
|
||||||
|
|
||||||
|
BASE_DIR = Path(os.path.dirname(__file__)) / '../..'
|
||||||
|
APP_DIR = _app_dir()
|
||||||
|
DATA_DIR = _data_dir()
|
||||||
|
TMP_DIR = _tmp_dir()
|
||||||
|
PREFERENCES_DIR = _preference_dir()
|
||||||
417
artemis/utils/sql_utils.py
Normal file
@@ -0,0 +1,417 @@
|
|||||||
|
import os
|
||||||
|
import sqlite3
|
||||||
|
|
||||||
|
from PySide6.QtCore import QUrl
|
||||||
|
from operator import itemgetter
|
||||||
|
from datetime import datetime
|
||||||
|
from contextlib import closing
|
||||||
|
|
||||||
|
from artemis.utils.constants import Query, Constants
|
||||||
|
from artemis.utils.path_utils import DATA_DIR
|
||||||
|
from artemis.utils.generic_utils import format_frequency
|
||||||
|
|
||||||
|
|
||||||
|
class Database():
|
||||||
|
""" General superclass for SQLite DB manipulation.
|
||||||
|
Foreign keys are activated (otherwise disabled by default for compatibility purposes)
|
||||||
|
"""
|
||||||
|
def __init__(self, sql_path):
|
||||||
|
self.sql_path = sql_path
|
||||||
|
|
||||||
|
def execute(self, query, parameters=None, last_rowid=False):
|
||||||
|
""" Open a connection, execute the given query with optional parameters and close the connection.
|
||||||
|
In the case of a SELECT query, returns the results as a fetchall().
|
||||||
|
If last_rowid == True, this function returns a tuple with the result of the fetchall() and
|
||||||
|
the latest modified row id of the current connection.
|
||||||
|
"""
|
||||||
|
with closing(sqlite3.connect(self.sql_path, check_same_thread=False)) as conn:
|
||||||
|
conn.execute('PRAGMA foreign_keys = ON;')
|
||||||
|
|
||||||
|
curs = conn.cursor()
|
||||||
|
|
||||||
|
if parameters:
|
||||||
|
curs.execute(query, parameters)
|
||||||
|
else:
|
||||||
|
curs.execute(query)
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
if last_rowid:
|
||||||
|
result = (curs.fetchall(), curs.lastrowid)
|
||||||
|
else:
|
||||||
|
result = curs.fetchall()
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
################################## MARK: >>> DATABASE <<<
|
||||||
|
|
||||||
|
class ArtemisDatabase(Database):
|
||||||
|
""" General CRUD class for SQLite DB manipulation.
|
||||||
|
Foreign keys are activated (otherwise disabled by default for compatibility purposes)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, db_dir_name):
|
||||||
|
self.db_dir_name = db_dir_name
|
||||||
|
self.db_dir = DATA_DIR / db_dir_name
|
||||||
|
self.sql_path = self.db_dir / Constants.SQL_NAME
|
||||||
|
self.media_dir = self.db_dir / 'media'
|
||||||
|
super().__init__(self.sql_path)
|
||||||
|
|
||||||
|
self.name = None
|
||||||
|
self.date = None
|
||||||
|
self.version = None
|
||||||
|
self.editable = None
|
||||||
|
|
||||||
|
self.all_signals = None
|
||||||
|
self.all_modulation = None
|
||||||
|
self.all_location = None
|
||||||
|
self.all_category_labels = None
|
||||||
|
|
||||||
|
self.filtered_signals = None
|
||||||
|
|
||||||
|
self.stats = {}
|
||||||
|
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
self._select_info()
|
||||||
|
self._select_all()
|
||||||
|
self._select_all_modulation()
|
||||||
|
self._select_all_location()
|
||||||
|
self._select_all_category_labels()
|
||||||
|
self._select_stats()
|
||||||
|
|
||||||
|
|
||||||
|
def _select_info(self):
|
||||||
|
""" Load the DB meta INFO from the table 'info'
|
||||||
|
"""
|
||||||
|
result = self.execute(Query.SELECT_INFO)[0]
|
||||||
|
self.name = result[0]
|
||||||
|
self.date = result[1]
|
||||||
|
self.version = result[2]
|
||||||
|
self.editable = result[3]
|
||||||
|
|
||||||
|
|
||||||
|
def _select_all(self):
|
||||||
|
""" Load a list of tuple for all signals. Each tuple (representing a signal)
|
||||||
|
contains the SIG_ID and the NAME of the signal
|
||||||
|
"""
|
||||||
|
self.all_signals = self.execute(Query.SELECT_ALL_SIGNALS)
|
||||||
|
keys = ('SIG_ID', 'name', 'description')
|
||||||
|
result = [dict(zip(keys, values)) for values in self.all_signals]
|
||||||
|
self.all_signals = result
|
||||||
|
|
||||||
|
|
||||||
|
def _select_all_modulation(self):
|
||||||
|
self.all_modulation = self.execute(Query.SELECT_ALL_MODULATION)
|
||||||
|
self.all_modulation = [{'value': item[0]} for item in self.all_modulation]
|
||||||
|
|
||||||
|
|
||||||
|
def _select_all_location(self):
|
||||||
|
self.all_location = self.execute(Query.SELECT_ALL_LOCATION)
|
||||||
|
self.all_location = [{'value': item[0]} for item in self.all_location]
|
||||||
|
|
||||||
|
|
||||||
|
def _select_all_category_labels(self):
|
||||||
|
self.all_category_labels = self.execute(Query.SELECT_ALL_CAT_LABELS)
|
||||||
|
self.all_category_labels = [{'clb_id': item[0], 'value': item[1]} for item in self.all_category_labels]
|
||||||
|
|
||||||
|
|
||||||
|
def _select_stats(self):
|
||||||
|
tot_docs = self.execute(Query.SELECT_STAT_DOCS)[0][0]
|
||||||
|
tot_images = self.execute(Query.SELECT_STAT_IMAGES)[0][0]
|
||||||
|
tot_audio = self.execute(Query.SELECT_STAT_AUDIO)[0][0]
|
||||||
|
|
||||||
|
self.stats['documents'] = tot_docs
|
||||||
|
self.stats['images'] = tot_images
|
||||||
|
self.stats['audio'] = tot_audio
|
||||||
|
self.stats['signals'] = len(self.all_signals)
|
||||||
|
|
||||||
|
|
||||||
|
def select_by_filter(self, filter_query):
|
||||||
|
matching_sig_ids = self.execute(filter_query)
|
||||||
|
sig_ids = ",".join(str(num[0]) for num in matching_sig_ids)
|
||||||
|
|
||||||
|
self.all_signals = self.execute(Query.SELECT_SIG_ID.format(sig_ids))
|
||||||
|
keys = ('SIG_ID', 'name', 'description')
|
||||||
|
result = [dict(zip(keys, values)) for values in self.all_signals]
|
||||||
|
self.all_signals = result
|
||||||
|
|
||||||
|
|
||||||
|
def create(self, name):
|
||||||
|
""" Create new db in the data folder.
|
||||||
|
The name of folder containing the new db has a unique id as name (db_dir_name).
|
||||||
|
"""
|
||||||
|
meta = [name, datetime.now(), 0, 0]
|
||||||
|
os.makedirs(self.db_dir)
|
||||||
|
os.makedirs(self.media_dir)
|
||||||
|
|
||||||
|
self.execute(Query.CREATE_INFO)
|
||||||
|
self.execute(Query.INSERT_INFO, meta)
|
||||||
|
self.execute(Query.CREATE_SIGNALS)
|
||||||
|
self.execute(Query.CREATE_CATEGORY)
|
||||||
|
self.execute(Query.CREATE_CATEGORY_LABELS)
|
||||||
|
self.execute(Query.CREATE_FREQUENCY)
|
||||||
|
self.execute(Query.CREATE_BANDWIDTH)
|
||||||
|
self.execute(Query.CREATE_MODULATION)
|
||||||
|
self.execute(Query.CREATE_MODE)
|
||||||
|
self.execute(Query.CREATE_LOCATION)
|
||||||
|
self.execute(Query.CREATE_ACF)
|
||||||
|
self.execute(Query.CREATE_DOCUMENTS)
|
||||||
|
|
||||||
|
self.execute(Query.CREATE_VIEW_FREQ)
|
||||||
|
self.execute(Query.CREATE_VIEW_BAND)
|
||||||
|
|
||||||
|
|
||||||
|
def rename(self, name):
|
||||||
|
self.execute(Query.RENAME_DB, [name])
|
||||||
|
|
||||||
|
|
||||||
|
def insert_category_label(self, value):
|
||||||
|
self.execute(Query.INSERT_CATEGORY_LABEL, [value])
|
||||||
|
|
||||||
|
|
||||||
|
def update_category_label(self, clb_id, value):
|
||||||
|
self.execute(Query.UPDATE_CATEGORY_LABEL, [value, clb_id])
|
||||||
|
|
||||||
|
|
||||||
|
def delete_category_label(self, clb_id):
|
||||||
|
self.execute(Query.DELETE_CATEGORY_LABEL, [clb_id])
|
||||||
|
|
||||||
|
################################## MARK: >>> SIGNAL <<<
|
||||||
|
|
||||||
|
class ArtemisSignal():
|
||||||
|
""" Main class of the object signal
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, loaded_db):
|
||||||
|
self.db = loaded_db
|
||||||
|
|
||||||
|
self.sig_id = None
|
||||||
|
self.name = None
|
||||||
|
self.description = None
|
||||||
|
self.url = None
|
||||||
|
self.category = None
|
||||||
|
self.frequency = None
|
||||||
|
self.bandwidth = None
|
||||||
|
self.modulation = None
|
||||||
|
self.mode = None
|
||||||
|
self.location = None
|
||||||
|
self.acf = None
|
||||||
|
|
||||||
|
self.documents = None
|
||||||
|
self.spectrum_path = None
|
||||||
|
self.audio_path = None
|
||||||
|
|
||||||
|
|
||||||
|
def load(self, sig_id):
|
||||||
|
self.sig_id = sig_id
|
||||||
|
self._select_signals()
|
||||||
|
self._select_category()
|
||||||
|
self._select_frequency()
|
||||||
|
self._select_bandwidth()
|
||||||
|
self._select_modulation()
|
||||||
|
self._select_mode()
|
||||||
|
self._select_location()
|
||||||
|
self._select_acf()
|
||||||
|
self.select_documents()
|
||||||
|
|
||||||
|
|
||||||
|
def generate_dic(self):
|
||||||
|
dic = {
|
||||||
|
'name': self.name,
|
||||||
|
'description': self.description,
|
||||||
|
'url': self.url,
|
||||||
|
'category': self.category,
|
||||||
|
'frequency': self.frequency,
|
||||||
|
'bandwidth': self.bandwidth,
|
||||||
|
'modulation': self.modulation,
|
||||||
|
'mode': self.mode,
|
||||||
|
'location': self.location,
|
||||||
|
'acf': self.acf,
|
||||||
|
'spectrum_path': self.spectrum_path,
|
||||||
|
'audio_path': self.audio_path,
|
||||||
|
'all_category': self.db.all_category_labels
|
||||||
|
}
|
||||||
|
return dic
|
||||||
|
|
||||||
|
|
||||||
|
################################## MARK: SELECT Methods
|
||||||
|
|
||||||
|
|
||||||
|
def _select_signals(self):
|
||||||
|
signal = self.db.execute(Query.SELECT_SIGNAL, [self.sig_id])[0]
|
||||||
|
self.name = signal[0]
|
||||||
|
self.description = signal[1]
|
||||||
|
self.url = signal[2]
|
||||||
|
|
||||||
|
|
||||||
|
def _select_category(self):
|
||||||
|
self.category = self.db.execute(Query.SELECT_CATEGORY, [self.sig_id])
|
||||||
|
self.category = [list(x) for x in self.category]
|
||||||
|
|
||||||
|
|
||||||
|
def _select_frequency(self):
|
||||||
|
result = self.db.execute(Query.SELECT_FREQUENCY, [self.sig_id])
|
||||||
|
sorted_list = sorted(result, key=itemgetter(1))
|
||||||
|
self.frequency = [list(x) + [format_frequency(x[1])] for x in sorted_list]
|
||||||
|
|
||||||
|
|
||||||
|
def _select_bandwidth(self):
|
||||||
|
result = self.db.execute(Query.SELECT_BANDWIDTH, [self.sig_id])
|
||||||
|
sorted_list = sorted(result, key=itemgetter(1))
|
||||||
|
self.bandwidth = [list(x) + [format_frequency(x[1])] for x in sorted_list]
|
||||||
|
|
||||||
|
|
||||||
|
def _select_acf(self):
|
||||||
|
self.acf = self.db.execute(Query.SELECT_ACF, [self.sig_id])
|
||||||
|
self.acf = [list(x) for x in self.acf]
|
||||||
|
|
||||||
|
|
||||||
|
def _select_modulation(self):
|
||||||
|
self.modulation = self.db.execute(Query.SELECT_MODULATION, [self.sig_id])
|
||||||
|
self.modulation = [list(x) for x in self.modulation]
|
||||||
|
|
||||||
|
|
||||||
|
def _select_mode(self):
|
||||||
|
self.mode = self.db.execute(Query.SELECT_MODE, [self.sig_id])
|
||||||
|
self.mode = [list(x) for x in self.mode]
|
||||||
|
|
||||||
|
|
||||||
|
def _select_location(self):
|
||||||
|
self.location = self.db.execute(Query.SELECT_LOCATION, [self.sig_id])
|
||||||
|
self.location = [list(x) for x in self.location]
|
||||||
|
|
||||||
|
|
||||||
|
def select_documents(self):
|
||||||
|
self.documents = self.db.execute(Query.SELECT_DOCUMENTS, [self.sig_id])
|
||||||
|
|
||||||
|
default_spectrum = [doc for doc in self.documents if doc[4] == 'Image' and doc[5] == 1]
|
||||||
|
default_audio = [doc for doc in self.documents if doc[4] == 'Audio' and doc[5] == 1]
|
||||||
|
|
||||||
|
if default_spectrum != []:
|
||||||
|
default_spectrum_filename = '{}.{}'.format(str(default_spectrum[0][0]), default_spectrum[0][1])
|
||||||
|
self.spectrum_path = self.db.media_dir / default_spectrum_filename
|
||||||
|
self.spectrum_path = QUrl.fromLocalFile(self.spectrum_path.resolve())
|
||||||
|
else:
|
||||||
|
self.spectrum_path = 'qrc:///images/spectrum_not_available.svg'
|
||||||
|
|
||||||
|
if default_audio != []:
|
||||||
|
default_audio_filename = '{}.{}'.format(str(default_audio[0][0]), default_audio[0][1])
|
||||||
|
self.audio_path = self.db.media_dir / default_audio_filename
|
||||||
|
self.audio_path = QUrl.fromLocalFile(self.audio_path.resolve())
|
||||||
|
else:
|
||||||
|
self.audio_path = ''
|
||||||
|
|
||||||
|
|
||||||
|
################################## MARK: UPDATE Methods
|
||||||
|
|
||||||
|
|
||||||
|
def update_signal(self, sig_id, value, description):
|
||||||
|
self.db.execute(Query.UPDATE_SIGNAL, [value, description, sig_id])
|
||||||
|
|
||||||
|
|
||||||
|
def update_frequency(self, freq_id, value, description):
|
||||||
|
self.db.execute(Query.UPDATE_FREQUENCY, [value, description, freq_id])
|
||||||
|
|
||||||
|
|
||||||
|
def update_bandwidth(self, band_id, value, description):
|
||||||
|
self.db.execute(Query.UPDATE_BANDWIDTH, [value, description, band_id])
|
||||||
|
|
||||||
|
|
||||||
|
def update_modulation(self, modu_id, value, description):
|
||||||
|
self.db.execute(Query.UPDATE_MODULATION, [value, description, modu_id])
|
||||||
|
|
||||||
|
|
||||||
|
def update_mode(self, mode_id, value, description):
|
||||||
|
self.db.execute(Query.UPDATE_MODE, [value, description, mode_id])
|
||||||
|
|
||||||
|
|
||||||
|
def update_acf(self, acf_id, value, description):
|
||||||
|
self.db.execute(Query.UPDATE_ACF, [value, description, acf_id])
|
||||||
|
|
||||||
|
|
||||||
|
def update_location(self, loc_id, value, description):
|
||||||
|
self.db.execute(Query.UPDATE_LOCATION, [value, description, loc_id])
|
||||||
|
|
||||||
|
|
||||||
|
def update_documents(self, doc_id, name, description, type, is_preview):
|
||||||
|
self.db.execute(Query.UPDATE_DOCUMENTS, [name, description, type, is_preview, doc_id])
|
||||||
|
|
||||||
|
|
||||||
|
################################## MARK: INSERT Methods
|
||||||
|
|
||||||
|
|
||||||
|
def insert_signal(self, value, description):
|
||||||
|
self.db.execute(Query.INSERT_SIGNAL, [value, description])
|
||||||
|
|
||||||
|
|
||||||
|
def insert_frequency(self, value, description):
|
||||||
|
self.db.execute(Query.INSERT_FREQUENCY, [self.sig_id, value, description])
|
||||||
|
|
||||||
|
|
||||||
|
def insert_bandwidth(self, value, description):
|
||||||
|
self.db.execute(Query.INSERT_BANDWIDTH, [self.sig_id,value, description])
|
||||||
|
|
||||||
|
|
||||||
|
def insert_modulation(self, value, description):
|
||||||
|
self.db.execute(Query.INSERT_MODULATION, [self.sig_id,value, description])
|
||||||
|
|
||||||
|
|
||||||
|
def insert_mode(self, value, description):
|
||||||
|
self.db.execute(Query.INSERT_MODE, [self.sig_id,value, description])
|
||||||
|
|
||||||
|
|
||||||
|
def insert_acf(self, value, description):
|
||||||
|
self.db.execute(Query.INSERT_ACF, [self.sig_id,value, description])
|
||||||
|
|
||||||
|
|
||||||
|
def insert_location(self, value, description):
|
||||||
|
self.db.execute(Query.INSERT_LOCATION, [self.sig_id,value, description])
|
||||||
|
|
||||||
|
|
||||||
|
def insert_category(self, clb_id):
|
||||||
|
self.db.execute(Query.INSERT_CATEGORY, [self.sig_id, clb_id])
|
||||||
|
|
||||||
|
|
||||||
|
def insert_document(self, doc_lst):
|
||||||
|
row_id = self.db.execute(Query.INSERT_DOCUMENTS, [self.sig_id] + doc_lst[1:], True)[1]
|
||||||
|
return row_id
|
||||||
|
|
||||||
|
|
||||||
|
################################## MARK: DELETE Methods
|
||||||
|
|
||||||
|
|
||||||
|
def delete_signal(self):
|
||||||
|
self.db.execute(Query.DELETE_SIGNAL, [self.sig_id])
|
||||||
|
|
||||||
|
|
||||||
|
def delete_frequency(self, freq_id):
|
||||||
|
self.db.execute(Query.DELETE_FREQUENCY, [freq_id])
|
||||||
|
|
||||||
|
|
||||||
|
def delete_bandwidth(self, band_id):
|
||||||
|
self.db.execute(Query.DELETE_BANDWIDTH, [band_id])
|
||||||
|
|
||||||
|
|
||||||
|
def delete_modulation(self, modu_id):
|
||||||
|
self.db.execute(Query.DELETE_MODULATION, [modu_id])
|
||||||
|
|
||||||
|
|
||||||
|
def delete_mode(self, mode_id):
|
||||||
|
self.db.execute(Query.DELETE_MODE, [mode_id])
|
||||||
|
|
||||||
|
|
||||||
|
def delete_acf(self, acf_id):
|
||||||
|
self.db.execute(Query.DELETE_ACF, [acf_id])
|
||||||
|
|
||||||
|
|
||||||
|
def delete_location(self, loc_id):
|
||||||
|
self.db.execute(Query.DELETE_LOCATION, [loc_id])
|
||||||
|
|
||||||
|
|
||||||
|
def delete_document(self, doc_id):
|
||||||
|
self.db.execute(Query.DELETE_DOCUMENT, [doc_id])
|
||||||
|
|
||||||
|
|
||||||
|
def delete_category(self, cat_id):
|
||||||
|
self.db.execute(Query.DELETE_CATEGORY, [cat_id])
|
||||||
100
artemis/utils/sys_utils.py
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import subprocess
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
from shutil import rmtree, copyfile, make_archive, unpack_archive
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from artemis.utils.constants import Messages
|
||||||
|
|
||||||
|
|
||||||
|
def is_windows():
|
||||||
|
return platform.system() == 'Windows'
|
||||||
|
|
||||||
|
def is_macos():
|
||||||
|
return platform.system() == 'Darwin'
|
||||||
|
|
||||||
|
def is_linux():
|
||||||
|
return platform.system() == 'Linux'
|
||||||
|
|
||||||
|
|
||||||
|
def open_file(file_path, timeout=3):
|
||||||
|
try:
|
||||||
|
if is_windows():
|
||||||
|
os.startfile(file_path)
|
||||||
|
elif is_macos():
|
||||||
|
subprocess.call(['open', file_path], timeout=timeout)
|
||||||
|
elif is_linux():
|
||||||
|
subprocess.call(['xdg-open', file_path], timeout=timeout)
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
except FileNotFoundError:
|
||||||
|
raise Exception(Messages.FILE_NOT_FOUND_ERR_MSG)
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(Messages.GENERIC_ERROR_MSG.format(e))
|
||||||
|
|
||||||
|
|
||||||
|
def open_directory(directory, timeout=3):
|
||||||
|
if is_windows():
|
||||||
|
subprocess.call(['explorer', str(Path(directory))], timeout=timeout)
|
||||||
|
elif is_macos():
|
||||||
|
subprocess.call(['open', directory], timeout=timeout)
|
||||||
|
elif is_linux():
|
||||||
|
subprocess.call(['xdg-open', directory], timeout=timeout)
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def copy_file(src_file_path, dst_file_path):
|
||||||
|
copyfile(src_file_path, dst_file_path)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_dir(dir_path):
|
||||||
|
if os.path.exists(dir_path):
|
||||||
|
rmtree(dir_path)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_file(file_path):
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
os.remove(file_path)
|
||||||
|
|
||||||
|
|
||||||
|
def make_tar(save_path, origin_path):
|
||||||
|
""" Create a tar archive from a folder
|
||||||
|
|
||||||
|
Args:
|
||||||
|
save_path: destination path where new tar is saved
|
||||||
|
origin_path: directory path of the folder to be archived
|
||||||
|
"""
|
||||||
|
make_archive(save_path, 'tar', origin_path.resolve().as_posix())
|
||||||
|
|
||||||
|
|
||||||
|
def unpack_tar(tar_path, destination_path):
|
||||||
|
""" Unpack a tar archive in a folder
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tar_path: path of the tar to be unpacked
|
||||||
|
destination_path: path where the tar is extracted
|
||||||
|
"""
|
||||||
|
unpack_archive(tar_path, destination_path, 'tar')
|
||||||
|
|
||||||
|
|
||||||
|
def match_hash(data, reference_hash):
|
||||||
|
""" Check whether the checksum of 'data' match the reference one.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data (str): Path of the file to be checked
|
||||||
|
reference_hash (str): Reference SHA-256 hash
|
||||||
|
"""
|
||||||
|
if reference_hash is None:
|
||||||
|
raise ValueError("ERROR: Invalid hash code.")
|
||||||
|
|
||||||
|
code = hashlib.sha256()
|
||||||
|
b = bytearray(128*1024)
|
||||||
|
mv = memoryview(b)
|
||||||
|
with open(data, 'rb', buffering=0) as f:
|
||||||
|
while n := f.readinto(mv):
|
||||||
|
code.update(mv[:n])
|
||||||
|
|
||||||
|
return code.hexdigest() == reference_hash
|
||||||
21
artemis/utils/ui_utils.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
from artemis.utils.sys_utils import is_windows, is_linux, is_macos
|
||||||
|
from artemis.utils.config_utils import CONFIGURE_QT
|
||||||
|
|
||||||
|
|
||||||
|
def set_ui():
|
||||||
|
os.environ['QT_QUICK_CONTROLS_STYLE'] = CONFIGURE_QT.value('Controls', 'style', 'Material')
|
||||||
|
os.environ['QT_QUICK_CONTROLS_MATERIAL_VARIANT'] = CONFIGURE_QT.value('Material', 'variant', 'Dense')
|
||||||
|
os.environ['QT_QUICK_CONTROLS_MATERIAL_THEME'] = CONFIGURE_QT.value('Material', 'theme', 'System')
|
||||||
|
os.environ['QT_QUICK_CONTROLS_MATERIAL_ACCENT'] = CONFIGURE_QT.value('Material', 'accent', 'Green')
|
||||||
|
|
||||||
|
if is_windows():
|
||||||
|
os.environ['QSG_RHI_BACKEND'] = 'opengl'
|
||||||
|
|
||||||
|
if is_linux():
|
||||||
|
os.environ['GDK_BACKEND'] = 'x11'
|
||||||
|
os.environ['QT_QPA_PLATFORM'] = 'xcb'
|
||||||
|
|
||||||
|
os.environ['QT_ENABLE_GLYPH_CACHE_WORKAROUND'] = '1'
|
||||||
|
os.environ['QML_USE_GLYPHCACHE_WORKAROUND'] = '1'
|
||||||
234
artemis/utils/update_utils.py
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
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, delete_file, delete_dir, 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.sigid_db_path = DATA_DIR / 'SigID' / Constants.SQL_NAME
|
||||||
|
|
||||||
|
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._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']
|
||||||
|
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 _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 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):
|
||||||
|
delete_dir(DATA_DIR / 'SigID')
|
||||||
|
unpack_tar(latest_db_tar_path, DATA_DIR / 'SigID')
|
||||||
|
self._parent.load_db('SigID')
|
||||||
|
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
|
||||||
|
)
|
||||||
30
building/Linux/build_linux.sh
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
echo "Building Linux target ..."
|
||||||
|
|
||||||
|
echo "Installing requirements ..."
|
||||||
|
pip install -r requirements.txt
|
||||||
|
pip install nuitka==2.3
|
||||||
|
|
||||||
|
echo "Building with Nuitka ..."
|
||||||
|
python -m nuitka app.py \
|
||||||
|
--standalone \
|
||||||
|
--show-modules \
|
||||||
|
--assume-yes-for-downloads \
|
||||||
|
--enable-plugin=pyside6 \
|
||||||
|
--noinclude-dlls=libQt6Charts* \
|
||||||
|
--noinclude-dlls=libQt6Quick3D* \
|
||||||
|
--noinclude-dlls=libQt6Sensors* \
|
||||||
|
--noinclude-dlls=libQt6Test* \
|
||||||
|
--noinclude-dlls=libQt6WebEngine* \
|
||||||
|
--include-qt-plugins=styles \
|
||||||
|
--include-qt-plugins=qml \
|
||||||
|
--include-qt-plugins=multimedia \
|
||||||
|
--include-data-files=./artemis/resources.py=./artemis/resources.py \
|
||||||
|
--include-data-files=./config/qtquickcontrols2.conf=./config/qtquickcontrols2.conf \
|
||||||
|
--include-data-files=./building/Linux/create_shortcut.sh=./create_shortcut.sh \
|
||||||
|
--include-data-files=./images/artemis_icon.svg=./images/artemis_icon.svg \
|
||||||
|
--force-stderr-spec="{TEMP}/artemis.err.log" \
|
||||||
|
--force-stdout-spec="{TEMP}/artemis.out.log"
|
||||||
|
|
||||||
|
echo "Building Linux target finished."
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
clear
|
clear
|
||||||
echo "
|
echo "
|
||||||
===================================
|
===================================
|
||||||
Artemis 3 Shortcut Creator
|
Artemis 4 Shortcut Creator
|
||||||
LINUX
|
LINUX
|
||||||
===================================
|
===================================
|
||||||
"
|
"
|
||||||
@@ -15,7 +15,7 @@ sudo chmod 700 $DIR
|
|||||||
# Generation of shortcut
|
# Generation of shortcut
|
||||||
file=/home/$USER/.local/share/applications/artemis.desktop
|
file=/home/$USER/.local/share/applications/artemis.desktop
|
||||||
if [ -e "$file" ]; then
|
if [ -e "$file" ]; then
|
||||||
echo "A shortcut of Artemis 3 is already present:"
|
echo "A shortcut of Artemis 4 is already present:"
|
||||||
echo ""
|
echo ""
|
||||||
echo "R) Remove the shortcut and the icon file"
|
echo "R) Remove the shortcut and the icon file"
|
||||||
echo "U) Update the shortcut"
|
echo "U) Update the shortcut"
|
||||||
@@ -26,16 +26,16 @@ if [ -e "$file" ]; then
|
|||||||
echo "[Desktop Entry]" >> /home/$USER/.local/share/applications/artemis.desktop
|
echo "[Desktop Entry]" >> /home/$USER/.local/share/applications/artemis.desktop
|
||||||
echo "Name=Artemis" >> /home/$USER/.local/share/applications/artemis.desktop
|
echo "Name=Artemis" >> /home/$USER/.local/share/applications/artemis.desktop
|
||||||
echo "Type=Application" >> /home/$USER/.local/share/applications/artemis.desktop
|
echo "Type=Application" >> /home/$USER/.local/share/applications/artemis.desktop
|
||||||
echo "StartupWMClass=artemis3" >> /home/$USER/.local/share/applications/artemis.desktop
|
echo "StartupWMClass=artemis" >> /home/$USER/.local/share/applications/artemis.desktop
|
||||||
echo "Exec=sh -c \"cd $DIR && ./Artemis\" " >> /home/$USER/.local/share/applications/artemis.desktop
|
echo "Exec=sh -c \"cd $DIR && ./app.bin\" " >> /home/$USER/.local/share/applications/artemis.desktop
|
||||||
echo "Terminal=false" >> /home/$USER/.local/share/applications/artemis.desktop
|
echo "Terminal=false" >> /home/$USER/.local/share/applications/artemis.desktop
|
||||||
echo "Icon=artemis3" >> /home/$USER/.local/share/applications/artemis.desktop
|
echo "Icon=artemis_icon" >> /home/$USER/.local/share/applications/artemis.desktop
|
||||||
sudo cp ./artemis3.svg /usr/share/icons/
|
sudo cp ./images/artemis_icon.svg /usr/share/icons/
|
||||||
echo "Link Updated!"
|
echo "Link Updated!"
|
||||||
;;
|
;;
|
||||||
r|R)
|
r|R)
|
||||||
sudo rm /home/$USER/.local/share/applications/artemis.desktop
|
sudo rm /home/$USER/.local/share/applications/artemis.desktop
|
||||||
sudo rm /usr/share/icons/artemis3.svg
|
sudo rm /usr/share/icons/artemis_icon.svg
|
||||||
echo "Link and icon removed!"
|
echo "Link and icon removed!"
|
||||||
;;
|
;;
|
||||||
*) echo "Sorry! Invalid option $REPLY";;
|
*) echo "Sorry! Invalid option $REPLY";;
|
||||||
@@ -44,14 +44,14 @@ else
|
|||||||
echo "[Desktop Entry]" >> /home/$USER/.local/share/applications/artemis.desktop
|
echo "[Desktop Entry]" >> /home/$USER/.local/share/applications/artemis.desktop
|
||||||
echo "Name=Artemis" >> /home/$USER/.local/share/applications/artemis.desktop
|
echo "Name=Artemis" >> /home/$USER/.local/share/applications/artemis.desktop
|
||||||
echo "Type=Application" >> /home/$USER/.local/share/applications/artemis.desktop
|
echo "Type=Application" >> /home/$USER/.local/share/applications/artemis.desktop
|
||||||
echo "StartupWMClass=artemis3" >> /home/$USER/.local/share/applications/artemis.desktop
|
echo "StartupWMClass=artemis" >> /home/$USER/.local/share/applications/artemis.desktop
|
||||||
echo "Exec=sh -c \"cd $DIR && ./Artemis\" " >> /home/$USER/.local/share/applications/artemis.desktop
|
echo "Exec=sh -c \"cd $DIR && ./app.bin\" " >> /home/$USER/.local/share/applications/artemis.desktop
|
||||||
echo "Terminal=false" >> /home/$USER/.local/share/applications/artemis.desktop
|
echo "Terminal=false" >> /home/$USER/.local/share/applications/artemis.desktop
|
||||||
echo "Icon=artemis3" >> /home/$USER/.local/share/applications/artemis.desktop
|
echo "Icon=artemis_icon" >> /home/$USER/.local/share/applications/artemis.desktop
|
||||||
sudo cp ./artemis3.svg /usr/share/icons/
|
sudo cp ./images/artemis_icon.svg /usr/share/icons/
|
||||||
echo "
|
echo "
|
||||||
Link copied in: /home/$USER/.local/share/applications/artemis.desktop
|
Link copied in: /home/$USER/.local/share/applications/artemis.desktop
|
||||||
Icon copied in: /usr/share/icons/artemis3.svg
|
Icon copied in: /usr/share/icons/artemis_icon.svg
|
||||||
"
|
"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
35
building/Windows/build_windows.ps1
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
Write-Output "Building Windows target"
|
||||||
|
|
||||||
|
Write-Output "Installing requirements ..."
|
||||||
|
pip install -r requirements.txt
|
||||||
|
pip install nuitka==2.3
|
||||||
|
|
||||||
|
Write-Output "Building with Nuitka ..."
|
||||||
|
python -m nuitka app.py `
|
||||||
|
--standalone `
|
||||||
|
--show-modules `
|
||||||
|
--assume-yes-for-downloads `
|
||||||
|
--windows-console-mode=disable `
|
||||||
|
--enable-plugin=pyside6 `
|
||||||
|
--noinclude-dlls="Qt6Charts*" `
|
||||||
|
--noinclude-dlls="Qt6Quick3D*" `
|
||||||
|
--noinclude-dlls="Qt6Sensors*" `
|
||||||
|
--noinclude-dlls="Qt6Test*" `
|
||||||
|
--noinclude-dlls="Qt6WebEngine*" `
|
||||||
|
--include-qt-plugins=styles `
|
||||||
|
--include-qt-plugins=qml `
|
||||||
|
--include-qt-plugins=multimedia `
|
||||||
|
--include-data-files=.\artemis\resources.py=.\artemis\resources.py `
|
||||||
|
--include-data-files=.\config\qtquickcontrols2.conf=.\config\qtquickcontrols2.conf `
|
||||||
|
--force-stderr-spec="{TEMP}\artemis.err.log" `
|
||||||
|
--force-stdout-spec="{TEMP}\artemis.out.log" `
|
||||||
|
--windows-company-name=Aresvalley.com `
|
||||||
|
--windows-product-name=Artemis `
|
||||||
|
--windows-file-version=4.0.3 `
|
||||||
|
--windows-product-version=4.0.3 `
|
||||||
|
--windows-file-description=Artemis `
|
||||||
|
--windows-icon-from-ico=images\artemis_icon.ico
|
||||||
|
|
||||||
|
Rename-Item -Path app.dist\app.exe -NewName artemis.exe
|
||||||
|
|
||||||
|
Write-Output "Building Windows target finished."
|
||||||
48
building/Windows/windows_installer.iss
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#define MyAppName "Artemis"
|
||||||
|
#define MyAppVersion "4.0.3"
|
||||||
|
#define MyAppPublisher "AresValley"
|
||||||
|
#define MyAppURL "https://www.aresvalley.com/"
|
||||||
|
#define MyAppExeName "artemis.exe"
|
||||||
|
|
||||||
|
[Setup]
|
||||||
|
AppName={#MyAppName}
|
||||||
|
AppId={{2A04F9B8-0EF1-4D46-A335-E484DA6CE8A7}
|
||||||
|
AppVersion={#MyAppVersion}
|
||||||
|
AppPublisher={#MyAppPublisher}
|
||||||
|
AppPublisherURL={#MyAppURL}
|
||||||
|
AppSupportURL={#MyAppURL}
|
||||||
|
AppUpdatesURL={#MyAppURL}
|
||||||
|
DefaultDirName={autopf}\{#MyAppName}
|
||||||
|
DisableProgramGroupPage=yes
|
||||||
|
LicenseFile=..\..\LICENSE
|
||||||
|
PrivilegesRequiredOverridesAllowed=dialog
|
||||||
|
OutputDir=..\
|
||||||
|
OutputBaseFilename=Artemis
|
||||||
|
SetupIconFile=..\..\images\installer_icon.ico
|
||||||
|
Compression=lzma2/ultra64
|
||||||
|
SolidCompression=yes
|
||||||
|
VersionInfoVersion={#MyAppVersion}
|
||||||
|
ArchitecturesAllowed=x64
|
||||||
|
UninstallDisplayIcon={app}\{#MyAppExeName}
|
||||||
|
WizardStyle=modern
|
||||||
|
|
||||||
|
[Languages]
|
||||||
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||||
|
|
||||||
|
[Tasks]
|
||||||
|
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||||
|
|
||||||
|
[Files]
|
||||||
|
Source: "..\..\app.dist\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
|
||||||
|
Source: "..\..\app.dist\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
||||||
|
|
||||||
|
[Icons]
|
||||||
|
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
|
||||||
|
Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
|
||||||
|
|
||||||
|
[Run]
|
||||||
|
Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
|
||||||
|
|
||||||
|
[UninstallDelete]
|
||||||
|
Type: filesandordirs; Name: "{localappdata}\{#MyAppPublisher}\{#MyAppName}\cache"
|
||||||
|
Type: filesandordirs; Name: "{localappdata}\{#MyAppPublisher}\{#MyAppName}\config"
|
||||||
28
building/macOS/build_macos.sh
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
echo "Building maacOS target ..."
|
||||||
|
|
||||||
|
echo "Installing requirements ..."
|
||||||
|
pip install -r requirements.txt
|
||||||
|
pip install nuitka==2.3 imageio
|
||||||
|
|
||||||
|
echo "Building with Nuitka ..."
|
||||||
|
python -m nuitka app.py \
|
||||||
|
--standalone \
|
||||||
|
--follow-imports \
|
||||||
|
--show-modules \
|
||||||
|
--assume-yes-for-downloads \
|
||||||
|
--enable-plugin=pyside6 \
|
||||||
|
--include-qt-plugins=sensible,styles,qml,multimedia \
|
||||||
|
--include-data-files=./artemis/resources.py=./artemis/resources.py \
|
||||||
|
--include-data-files=./config/qtquickcontrols2.conf=./config/qtquickcontrols2.conf \
|
||||||
|
--include-data-files=./building/Linux/create_shortcut.sh=./create_shortcut.sh \
|
||||||
|
--macos-create-app-bundle \
|
||||||
|
--macos-app-icon=images/artemis_icon.ico \
|
||||||
|
--macos-signed-app-name=com.AresValley.Artemis \
|
||||||
|
--macos-app-name=Artemis \
|
||||||
|
--macos-app-mode=gui \
|
||||||
|
--macos-sign-identity=ad-hoc \
|
||||||
|
--macos-app-version=4.0.3
|
||||||
|
|
||||||
|
echo "Building Linux target finished."
|
||||||
10
config/qtquickcontrols2.conf
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[Controls]
|
||||||
|
style=Material
|
||||||
|
|
||||||
|
[Material]
|
||||||
|
variant=Dense
|
||||||
|
theme=System
|
||||||
|
accent=Green
|
||||||
|
|
||||||
|
[Database]
|
||||||
|
autoload=0
|
||||||
20
config/release-info.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"sigID_DB": {
|
||||||
|
"version": 61,
|
||||||
|
"url": "https://github.com/AresValley/Artemis-DB/releases/download/v61/v61.tar",
|
||||||
|
"sha256_hash": "c65d2ab65e9420cd7789190c100abef2f1575ea15489776c2d97b5b09cdc8410",
|
||||||
|
"total_bytes": 244449280
|
||||||
|
},
|
||||||
|
"windows": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"url": "https://github.com/AresValley/Artemis/releases/download/v4.0.3/Artemis-Windows-x86_64-4.0.3.exe"
|
||||||
|
},
|
||||||
|
"linux": {
|
||||||
|
"version": "4.0.3",
|
||||||
|
"url": "https://github.com/AresValley/Artemis/releases/download/v4.0.3/Artemis-Linux-x86_64-4.0.3.zip"
|
||||||
|
},
|
||||||
|
"mac": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"url": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
65
devel/env_script/start_linux.sh
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||||
|
|
||||||
|
if [[ "$(pwd)" =~ " " ]]; then echo This script relies on Miniconda which can not be silently installed under a path with spaces. && exit; fi
|
||||||
|
|
||||||
|
OS_ARCH=$(uname -m)
|
||||||
|
case "${OS_ARCH}" in
|
||||||
|
x86_64*) OS_ARCH="x86_64";;
|
||||||
|
arm64*) OS_ARCH="aarch64";;
|
||||||
|
*) echo "Unknown system architecture: $OS_ARCH! This script runs only on x86_64 or arm64" && exit
|
||||||
|
esac
|
||||||
|
|
||||||
|
# config
|
||||||
|
INSTALL_DIR="$(pwd)/installer_files"
|
||||||
|
CONDA_ROOT_PREFIX="$(pwd)/installer_files/conda"
|
||||||
|
INSTALL_ENV_DIR="$(pwd)/installer_files/env"
|
||||||
|
MINICONDA_DOWNLOAD_URL="https://repo.anaconda.com/miniconda/Miniconda3-py310_23.1.0-1-Linux-${OS_ARCH}.sh"
|
||||||
|
conda_exists="F"
|
||||||
|
|
||||||
|
# figure out whether git and conda needs to be installed
|
||||||
|
if "$CONDA_ROOT_PREFIX/bin/conda" --version &>/dev/null; then conda_exists="T"; fi
|
||||||
|
|
||||||
|
# (if necessary) install git and conda into a contained environment
|
||||||
|
# download miniconda
|
||||||
|
if [ "$conda_exists" == "F" ]; then
|
||||||
|
echo "Downloading Miniconda from $MINICONDA_DOWNLOAD_URL to $INSTALL_DIR/miniconda_installer.sh"
|
||||||
|
|
||||||
|
mkdir -p "$INSTALL_DIR"
|
||||||
|
curl -Lk "$MINICONDA_DOWNLOAD_URL" > "$INSTALL_DIR/miniconda_installer.sh"
|
||||||
|
|
||||||
|
chmod u+x "$INSTALL_DIR/miniconda_installer.sh"
|
||||||
|
bash "$INSTALL_DIR/miniconda_installer.sh" -b -p $CONDA_ROOT_PREFIX
|
||||||
|
|
||||||
|
# test the conda binary
|
||||||
|
echo "Miniconda version:"
|
||||||
|
"$CONDA_ROOT_PREFIX/bin/conda" --version
|
||||||
|
fi
|
||||||
|
|
||||||
|
# create the installer env
|
||||||
|
if [ ! -e "$INSTALL_ENV_DIR" ]; then
|
||||||
|
"$CONDA_ROOT_PREFIX/bin/conda" create -y -k --prefix "$INSTALL_ENV_DIR" python=3.10
|
||||||
|
fi
|
||||||
|
|
||||||
|
# check if conda environment was actually created
|
||||||
|
if [ ! -e "$INSTALL_ENV_DIR/bin/python" ]; then
|
||||||
|
echo "Conda environment is empty."
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
# activate installer env
|
||||||
|
source "$CONDA_ROOT_PREFIX/etc/profile.d/conda.sh" # otherwise conda complains about 'shell not initialized' (needed when running in a script)
|
||||||
|
conda activate "$INSTALL_ENV_DIR"
|
||||||
|
|
||||||
|
if [ ! -e "./Artemis" ]; then
|
||||||
|
git clone https://github.com/AresValley/Artemis.git
|
||||||
|
pip install -r Artemis/requirements/requirements.txt --user
|
||||||
|
fi
|
||||||
|
|
||||||
|
# setup installer env
|
||||||
|
cd ./Artemis/src/
|
||||||
|
python artemis.py
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Done!"
|
||||||
56
devel/env_script/start_windows.bat
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
@echo off
|
||||||
|
|
||||||
|
cd /D "%~dp0"
|
||||||
|
|
||||||
|
echo "%CD%"| findstr /C:" " >nul && echo This script relies on Miniconda which can not be silently installed under a path with spaces. && goto end
|
||||||
|
|
||||||
|
set PATH=%PATH%;%SystemRoot%\system32
|
||||||
|
|
||||||
|
@rem config
|
||||||
|
set INSTALL_DIR=%cd%\installer_files
|
||||||
|
set CONDA_ROOT_PREFIX=%cd%\installer_files\conda
|
||||||
|
set INSTALL_ENV_DIR=%cd%\installer_files\env
|
||||||
|
set MINICONDA_DOWNLOAD_URL=https://repo.anaconda.com/miniconda/Miniconda3-py310_23.1.0-1-Windows-x86_64.exe
|
||||||
|
set conda_exists=F
|
||||||
|
|
||||||
|
@rem figure out whether git and conda needs to be installed
|
||||||
|
call "%CONDA_ROOT_PREFIX%\_conda.exe" --version >nul 2>&1
|
||||||
|
if "%ERRORLEVEL%" EQU "0" set conda_exists=T
|
||||||
|
|
||||||
|
@rem (if necessary) install git and conda into a contained environment
|
||||||
|
@rem download conda
|
||||||
|
if "%conda_exists%" == "F" (
|
||||||
|
echo Downloading Miniconda from %MINICONDA_DOWNLOAD_URL% to %INSTALL_DIR%\miniconda_installer.exe
|
||||||
|
|
||||||
|
mkdir "%INSTALL_DIR%"
|
||||||
|
call curl -Lk "%MINICONDA_DOWNLOAD_URL%" > "%INSTALL_DIR%\miniconda_installer.exe" || ( echo. && echo Miniconda failed to download. && goto end )
|
||||||
|
|
||||||
|
echo Installing Miniconda to %CONDA_ROOT_PREFIX%
|
||||||
|
start /wait "" "%INSTALL_DIR%\miniconda_installer.exe" /InstallationType=JustMe /NoShortcuts=1 /AddToPath=0 /RegisterPython=0 /NoRegistry=1 /S /D=%CONDA_ROOT_PREFIX%
|
||||||
|
|
||||||
|
@rem test the conda binary
|
||||||
|
echo Miniconda version:
|
||||||
|
call "%CONDA_ROOT_PREFIX%\_conda.exe" --version || ( echo. && echo Miniconda not found. && goto end )
|
||||||
|
)
|
||||||
|
|
||||||
|
@rem create the installer env
|
||||||
|
if not exist "%INSTALL_ENV_DIR%" (
|
||||||
|
echo Packages to install: %PACKAGES_TO_INSTALL%
|
||||||
|
call "%CONDA_ROOT_PREFIX%\_conda.exe" create --no-shortcuts -y -k --prefix "%INSTALL_ENV_DIR%" python=3.10 || ( echo. && echo Conda environment creation failed. && goto end )
|
||||||
|
call pip install PySide6==6.5.0 requests
|
||||||
|
)
|
||||||
|
|
||||||
|
@rem check if conda environment was actually created
|
||||||
|
if not exist "%INSTALL_ENV_DIR%\python.exe" ( echo. && echo Conda environment is empty. && goto end )
|
||||||
|
|
||||||
|
@rem activate installer env
|
||||||
|
call "%CONDA_ROOT_PREFIX%\condabin\conda.bat" activate "%INSTALL_ENV_DIR%" || ( echo. && echo Miniconda hook not found. && goto end )
|
||||||
|
|
||||||
|
@rem setup installer env
|
||||||
|
call python app.py
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo Done!
|
||||||
|
|
||||||
|
:end
|
||||||
|
pause
|
||||||
71
docs/acf_analysis.md
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# Autocorrelation Function (ACF)
|
||||||
|
|
||||||
|
## Theoretical Introduction
|
||||||
|
|
||||||
|
Correlation functions are valuable mathematical tools utilized across various scientific disciplines, including engineering, physics, and chemistry. The cross-correlation function, commonly defined as a sliding inner (dot) product, quantifies the similarity between two signals as a function of a temporal shift applied to one of them:
|
||||||
|
|
||||||
|
$$
|
||||||
|
\left[ k*l \right]\equiv \int_{-\infty}^{+\infty}k^{*}(t)l(t+\tau)dt
|
||||||
|
$$
|
||||||
|
|
||||||
|
where $k$ and $l$ are two general non-discrete functions without discontinuity, $k^{*}$ is the complex conjugate of $k$ and $\tau$ is the lag (time delay for signal analysis). This representation is closely related to the convolution theorem by the following relationship:
|
||||||
|
|
||||||
|
$$
|
||||||
|
k(t)*l(t)\equiv k^{*}(-t)*l(t)
|
||||||
|
$$
|
||||||
|
|
||||||
|
In our case, the signal must be compared with itself to identify periodic patterns or detect anomalous high degrees of similarity within a given time interval. To achieve this objective, we can employ the Autocorrelation Function (ACF), which is essentially the cross-correlation of a signal with itself, where $k(t)=l(t)$. The following equation defines the ACF, derived from a straightforward modification of the initial equation:
|
||||||
|
|
||||||
|
$$
|
||||||
|
\left[ k*k \right](\tau)\equiv \int_{-\infty}^{+\infty}k^{*}(t)k(t+\tau)dt
|
||||||
|
$$
|
||||||
|
|
||||||
|
Within a discrete data set composed by $Y(t)$ records we can easily calculate autocorrelation from:
|
||||||
|
|
||||||
|
$$
|
||||||
|
\rho_{\tau}=\frac{\sum_{i=1}^{N-\tau}(Y_i-\bar{Y})(Y_{i+\tau}-\bar{Y})}{\sum_{i=1}^{N}(Y_i-\bar{Y})^2}
|
||||||
|
$$
|
||||||
|
|
||||||
|
where $Y_{i+τ}$ is a lagged data by $τ$ of $Y_i$ and $\bar{Y}$ is the average value of the original data set. Entire denominator is used to keep the normalization condition, indeed ρτ can assume a value between -1 (exact anticorrelation) and +1 (exact correlation). A complete correlation can be only achieved with a perfect overlap of the analyzed function with it self; the simple way to verify that is when $τ=0$
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Example: ACF Analysis
|
||||||
|
### STANAG 4285
|
||||||
|
|
||||||
|
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 A-3](assets/acf_3.png)
|
||||||
|
* [STANAG 4285 A-4](assets/acf_4.png)
|
||||||
|
* [STANAG 4285 A-5](assets/acf_5.png)
|
||||||
|
* [STANAG 4285 A-7](assets/acf_6.png)
|
||||||
|
|
||||||
|
!!! note "Summary"
|
||||||
|
* 1 x 80 bits of synchronization pattern
|
||||||
|
* 4 x 32 bits of data
|
||||||
|
* 3 x 16 bits of reference
|
||||||
|
|
||||||
|
Therefore, a full sequence will be composed as the sum of all symbols reported above: 256 symbols recursively transmitted at the ratio of 2400 bauds means a redundancy of 106.66 ms.
|
||||||
|
To analyze an unknown signal I strongly recommend **Signal Analyzer (SA)**. SA is actually one of the most valuable and priceless software available by Sergey Makarov (Макаров Сергей Михайлович, sadly passed away on 29 May 2012) and it used to perform several tests on data sample coded as .wav or .mp3 file. The signal waterfall is reported below:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
When an Autocorrelation function is applied on selected data frame (see above), the result shows a clear recursive pattern with a gap of **106.244 ms** between every peak as expected. An ACF refinement can be done into **WaveForm** windows where we can also notice an identical structure as reported by the specifications
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
!!! Success "Summary"
|
||||||
|
1. Synchronization pattern is composed by $2400*33.013E-3=79.2\simeq 80$ symbols
|
||||||
|
2. Data group contain $2400*13.348E-3=32.0=32$ symbols
|
||||||
|
3. Reference sequence is formed by $2400*6.674E-3=16.02\simeq 16$ symbols
|
||||||
BIN
docs/assets/acf_1.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
docs/assets/acf_2.png
Normal file
|
After Width: | Height: | Size: 164 KiB |
BIN
docs/assets/acf_3.png
Normal file
|
After Width: | Height: | Size: 261 KiB |
BIN
docs/assets/acf_4.png
Normal file
|
After Width: | Height: | Size: 216 KiB |
BIN
docs/assets/acf_5.png
Normal file
|
After Width: | Height: | Size: 140 KiB |
BIN
docs/assets/acf_6.png
Normal file
|
After Width: | Height: | Size: 144 KiB |
BIN
docs/assets/acf_7.png
Normal file
|
After Width: | Height: | Size: 720 KiB |
BIN
docs/assets/acf_8.png
Normal file
|
After Width: | Height: | Size: 121 KiB |
BIN
docs/assets/acf_9.png
Normal file
|
After Width: | Height: | Size: 624 KiB |
BIN
docs/assets/artemis_icon.ico
Normal file
|
After Width: | Height: | Size: 193 KiB |
BIN
docs/assets/artemis_preview.webp
Normal file
|
After Width: | Height: | Size: 209 KiB |
1
docs/assets/logo_large_black.svg
Normal file
|
After Width: | Height: | Size: 21 KiB |
1
docs/assets/logo_large_white.svg
Normal file
|
After Width: | Height: | Size: 21 KiB |
1
docs/assets/logo_small_black.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="222.267" height="371.88" viewBox="0 0 166.7 278.91" fill="#231f20" xmlns:v="https://vecta.io/nano"><path d="M0 140.2L83.35 0l83.35 140.2-25.74 43.28-16.55-27.43c2.98-5.36 6.25-10.54 9.33-15.84L83.35 55.44 32.96 140.2l9.34 15.79-16.52 27.47L0 140.2zm37.52 62.49l45.83-76.19 45.83 76.18-45.83 76.23-45.83-76.22z"/></svg>
|
||||||
|
After Width: | Height: | Size: 365 B |
1
docs/assets/logo_small_white.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="222.267" height="371.88" viewBox="0 0 166.7 278.91" fill="#231f20" xmlns:v="https://vecta.io/nano"><path d="M0 140.2L83.35 0l83.35 140.2-25.74 43.28-16.55-27.43c2.98-5.36 6.25-10.54 9.33-15.84L83.35 55.44 32.96 140.2l9.34 15.79-16.52 27.47L0 140.2zm37.52 62.49l45.83-76.19 45.83 76.18-45.83 76.23-45.83-76.22z" fill="#fff"/></svg>
|
||||||
|
After Width: | Height: | Size: 377 B |
BIN
docs/assets/main_window.webp
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
docs/assets/sql_schema.webp
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
docs/assets/stanag_4285.wav
Normal file
BIN
docs/assets/sw_1.webp
Normal file
|
After Width: | Height: | Size: 113 KiB |
BIN
docs/assets/sw_2.webp
Normal file
|
After Width: | Height: | Size: 137 KiB |
BIN
docs/assets/sw_3.webp
Normal file
|
After Width: | Height: | Size: 171 KiB |
BIN
docs/assets/sw_4.webp
Normal file
|
After Width: | Height: | Size: 154 KiB |
63
docs/basic_operations.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
#
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 1. Main Menu
|
||||||
|
|
||||||
|
### File
|
||||||
|
* **New Database:** Create a new database.
|
||||||
|
* **Load Database:** 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.
|
||||||
|
* **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
|
||||||
|
* **New:** Add a new signal to the database.
|
||||||
|
* **Edit:** Edit the current/selected signal from the loaded database.
|
||||||
|
|
||||||
|
### Space Weather
|
||||||
|
* **Check Report:** Open the main [Space Weather window](space_weather/current.md) and retrieve all the live data from Poseidon Crawler.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
### Filter by Name/Description
|
||||||
|
On top of the list, there is a field for filtering signals by name or any keyword inside the description of the signal: this filter has the highest priority among all the filters.
|
||||||
|
|
||||||
|
## 3. Signal Menu
|
||||||
|
Here you can swithc between the main **signal** window and the **filter** page.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
* **Add/Rename Tag:** To add a new tag open the [Tags Editor](#1-main-menu) in the main menu.
|
||||||
|
|
||||||
|
## 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).
|
||||||
|
|
||||||
|
## 6. Edit Parameter
|
||||||
|
Click on the parameter badge to open the Signal Editor windows. From here, you can edit or delete the corresponding parameter.
|
||||||
|
|
||||||
|
!!! tip "Parameter Description"
|
||||||
|
All the parameters have a description field: if some text is added, it will appear when the corresponding parameter badge is hovered with the mouse pointer.
|
||||||
|
|
||||||
|
## 7. Description :simple-markdown:{ title="Markdown Supported" }
|
||||||
|
This is the description of the signal and can be edited from the [Main Menu](#1-main-menu) (`Signal/Edit...`)
|
||||||
|
|
||||||
|
!!! tip "Markdown Supported"
|
||||||
|
The Description field can render **Markdown**, a simple markup language for creating rich text using plain text. Headers, emphasis, lists, links, code blocks, and many more features for advanced text formatting. [Markdown Basic Syntax :simple-markdown:](https://www.markdownguide.org/basic-syntax/)
|
||||||
|
|
||||||
|
## 8. Audio Sample
|
||||||
|
This is a player where an audio sample of the signal can be played. To associate an audio file to be shown as the **main** audio sample, check in the [extra menu](#10-extra) below the signal spectrum.
|
||||||
|
|
||||||
|
## 9. Image Sample
|
||||||
|
This is an image box that commonly contains the signal spectrum/waterfall. To associate an image file to be shown as the **main** image sample, check in the [extra menu](#10-extra) below the signal spectrum.
|
||||||
|
|
||||||
|
## 10. Extra
|
||||||
|
|
||||||
|
:material-earth: This button is only available for the standard SigID wiki database and connects the local signal to its counterpart on the [sigidwiki website](https://www.sigidwiki.com/).
|
||||||
|
|
||||||
|
:material-file-document-multiple: This will open the **Documents Manager**. From here you can add any file (audio, image, pdf, etc.) to the signal entry. It is also possible to mark only one image and one audio to be shown on the main signal window.
|
||||||
51
docs/build_package.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Build Package
|
||||||
|
Building a distributable package with an executable for Artemis creates a practical solution for end-users, as they can run the application without needing to interact with the terminal and they can easily share the application as a standalone package.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
* Python (3.11 or higher)
|
||||||
|
|
||||||
|
!!! info
|
||||||
|
We assume that Python is already installed on the system and the Artemis source code has been downloaded and extracted. If these prerequisites are not met, please follow steps 1 to 3 in the [run from source section](run_from_source.md).
|
||||||
|
|
||||||
|
!!! warning "Cross-Compilation"
|
||||||
|
An operating system that matches the target OS must be used to generate standalone packages, as Nuitka does not support cross-compilation. For example, you cannot build binaries on Windows that work on Linux or macOS.
|
||||||
|
|
||||||
|
## :simple-windows: Windows
|
||||||
|
|
||||||
|
### Procedure
|
||||||
|
|
||||||
|
1. Open a PowerShell terminal in the main Artemis folder and execute the following command to start the build process:
|
||||||
|
|
||||||
|
```
|
||||||
|
.\building\Windows\build_windows.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Wait for the build process to complete. This may take a few minutes depending on your system's performance. Once the process finishes, check the `artemis.dist/` directory: it will contain the standalone software with the `artemis.exe` executable.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## :simple-linux: Linux
|
||||||
|
|
||||||
|
### Procedure
|
||||||
|
|
||||||
|
1. Open a terminal in the main Artemis folder and execute the following command to start the build process:
|
||||||
|
```
|
||||||
|
. ./building/Linux/build_linux.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Wait for the build process to complete. This may take a few minutes depending on your system's performance. Once the process finishes, check the `artemis.dist/` directory: it will contain the standalone software with the `app.bin` executable.
|
||||||
|
3. If you wish to create a shortcut, follows the procedure in the [installation section](installation.md/#create-a-shortcut)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## :simple-apple: Mac OS
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
The support for the macOS compiled version of the program is temporarily limited due to a lack of machines for extensive testing. Feel free to contribute by reporting any issues you encounter by [opening an Issue](https://github.com/AresValley/Artemis/issues).
|
||||||
|
|
||||||
|
### Procedure
|
||||||
|
|
||||||
|
1. Open a terminal in the main Artemis folder and execute the following command to start the build process:
|
||||||
|
```
|
||||||
|
. ./building/macOS/build_macos.sh
|
||||||
|
```
|
||||||
37
docs/contribute.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Contribute
|
||||||
|
Artemis is an open-source project, and every contribution, no matter how small, is valuable and greatly appreciated. Don't worry about getting everything perfect; we are happy to work with you on your contribution and help you along the way. This guide will help you get started by outlining various ways you can contribute.
|
||||||
|
|
||||||
|
<div class="grid cards" markdown>
|
||||||
|
|
||||||
|
- :material-bug: __Spot a bug?__
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Please open an issue (or pull request) and let us know the problem you faced (or you're working on)
|
||||||
|
|
||||||
|
[:octicons-arrow-right-24: Open an Issue](https://github.com/AresValley/Artemis/issues)
|
||||||
|
|
||||||
|
- :material-source-fork: __Fork the repository__
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Create a copy of the codebase from which you can modify and submit pull requests.
|
||||||
|
|
||||||
|
|
||||||
|
[:octicons-arrow-right-24: Fork the repo](https://github.com/AresValley/Artemis)
|
||||||
|
|
||||||
|
- :material-lightbulb-on: __Ideas?__
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Idea for a new feature? Open an issue on the project's GitHub repository to describe your proposal.
|
||||||
|
|
||||||
|
[:octicons-arrow-right-24: Open an Issue](https://github.com/AresValley/Artemis/issues)
|
||||||
|
|
||||||
|
- :material-heart: __Spreading the word!__
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Do you like Artemis? Remember to share it with your network and your friends!
|
||||||
|
|
||||||
|
</div>
|
||||||
14
docs/credits.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# License & Credits
|
||||||
|
|
||||||
|
## License
|
||||||
|
Artemis is maintained by Marco Dalla Tiezza and released under the [GPLv3](https://github.com/AresValley/Artemis/blob/master/LICENSE) license.
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
* **Marco Dalla Tiezza** - *Artemis I-II-IV developer, DB parsing, Website*
|
||||||
|
* [**Alessandro Ceccato**](https://github.com/alessandro90 "GitHub profile") - *Artemis III lead developer*
|
||||||
|
* **Paolo Romani (IZ1MLL)** - *Lead β Tester, RF specialist*
|
||||||
|
* **Carl Colena** - *Sigidwiki admin, β Tester, Signals expert*
|
||||||
|
* [**Marco Bortoli**](https://github.com/marbort "GitHub profile") - *macOS deployment, β Tester*
|
||||||
|
* [**Eric Wiessner (KI7POL)**](https://github.com/WheezyE "GitHub profile") - *ARM port (Raspberry Pi3B+ and Pi4B)*
|
||||||
|
* [**Pierpaolo Pravatto**](https://github.com/ppravatto "GitHub profile") - *Wiki page, β Tester*
|
||||||
|
* [**Francesco Capostagno**](https://github.com/fcapostagno "GitHub profile"), **Luca**, **Pietro** - *β Tester*
|
||||||
25
docs/database/db_acf.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# ACF
|
||||||
|
The table contains 4 columns explained below.
|
||||||
|
|
||||||
|
!!! example
|
||||||
|
A technical explanation on how autocorrelation function works along with a practical example is reported [HERE](../acf_analysis.md)
|
||||||
|
|
||||||
|
## ACF_ID
|
||||||
|
`INTEGER` :material-key-outline:{ title="Primary key" } :material-upload-outline:{ title="Auto-increment" }
|
||||||
|
|
||||||
|
This is a unique identification number for each entry that is assigned during the creation of a new ACF. It is auto-incrementing and is not replaced in the event of deletion.
|
||||||
|
|
||||||
|
## SIG_ID
|
||||||
|
`INTEGER` :material-axis-arrow:{ title="Foreign key" }
|
||||||
|
|
||||||
|
This is a direct reference to the specific signal associated with the ACF. It links to the primary key of the [Signals](db_signals.md) table that holds detailed information about the signals.
|
||||||
|
|
||||||
|
## VALUE
|
||||||
|
`FLOAT`
|
||||||
|
|
||||||
|
The autocorrelation time expressed in ms.
|
||||||
|
|
||||||
|
## DESCRIPTION
|
||||||
|
`TEXT`
|
||||||
|
|
||||||
|
The short description is used to explain the details about the autocorrelation value, e.g. `Frame`, `Superframe`, etc.
|
||||||
22
docs/database/db_bandwidth.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Bandwidth
|
||||||
|
The table contains 4 columns explained below.
|
||||||
|
|
||||||
|
## BAND_ID
|
||||||
|
`INTEGER` :material-key-outline:{ title="Primary key" } :material-upload-outline:{ title="Auto-increment" }
|
||||||
|
|
||||||
|
This is a unique identification number for each entry that is assigned during the creation of a new bandwidth. It is auto-incrementing and is not replaced in the event of deletion.
|
||||||
|
|
||||||
|
## SIG_ID
|
||||||
|
`INTEGER` :material-axis-arrow:{ title="Foreign key" }
|
||||||
|
|
||||||
|
This is a direct reference to the specific signal associated with the bandwidth. It links to the primary key of the [Signals](db_signals.md) table that holds detailed information about the signals.
|
||||||
|
|
||||||
|
## VALUE
|
||||||
|
`INTEGER`
|
||||||
|
|
||||||
|
The bandwidth in Hz expressed as an integer.
|
||||||
|
|
||||||
|
## DESCRIPTION
|
||||||
|
`TEXT`
|
||||||
|
|
||||||
|
The short description is used to explain the purpose of the bandwidth and any other useful details.
|
||||||
12
docs/database/db_cat_label.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Category Label
|
||||||
|
This table contains only the name of the category/tag. The table contains 2 columns explained below.
|
||||||
|
|
||||||
|
## CLB_ID
|
||||||
|
`INTEGER` :material-key-outline:{ title="Primary key" } :material-upload-outline:{ title="Auto-increment" }
|
||||||
|
|
||||||
|
This is a unique identification number for each entry that is assigned during the creation of a new category tag. It is auto-incrementing and is not replaced in the event of deletion.
|
||||||
|
|
||||||
|
## VALUE
|
||||||
|
`TEXT`
|
||||||
|
|
||||||
|
The name of the category/tag expressed as a string.
|
||||||
17
docs/database/db_category.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Category
|
||||||
|
The primary function of this table is to facilitate the classification of the signal by assigning it to its appropriate family or category. Can be used with any tag. The table contains 3 columns explained below.
|
||||||
|
|
||||||
|
## CAT_ID
|
||||||
|
`INTEGER` :material-key-outline:{ title="Primary key" } :material-upload-outline:{ title="Auto-increment" }
|
||||||
|
|
||||||
|
This is a unique identification number for each entry that is assigned during the creation of a new category/tag. It is auto-incrementing and is not replaced in the event of deletion.
|
||||||
|
|
||||||
|
## SIG_ID
|
||||||
|
`INTEGER` :material-axis-arrow:{ title="Foreign key" }
|
||||||
|
|
||||||
|
This is a direct reference to the specific signal associated with the category. It links to the primary key of the [Signals](db_signals.md) table that holds detailed information about the signals.
|
||||||
|
|
||||||
|
## CLB_ID
|
||||||
|
`INTEGER` :material-axis-arrow:{ title="Foreign key" }
|
||||||
|
|
||||||
|
This is a direct reference to the specific **category label** associated with the category. It links to the primary key of the [Category Label](db_cat_label.md) table that holds the name of the category.
|
||||||
22
docs/database/db_frequency.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Frequency
|
||||||
|
The table contains 4 columns explained below.
|
||||||
|
|
||||||
|
## FREQ_ID
|
||||||
|
`INTEGER` :material-key-outline:{ title="Primary key" } :material-upload-outline:{ title="Auto-increment" }
|
||||||
|
|
||||||
|
This is a unique identification number for each entry that is assigned during the creation of a new frequency. It is auto-incrementing and is not replaced in the event of deletion.
|
||||||
|
|
||||||
|
## SIG_ID
|
||||||
|
`INTEGER` :material-axis-arrow:{ title="Foreign key" }
|
||||||
|
|
||||||
|
This is a direct reference to the specific signal associated with the frequency. It links to the primary key of the [Signals](db_signals.md) table that holds detailed information about the signals.
|
||||||
|
|
||||||
|
## VALUE
|
||||||
|
`INTEGER`
|
||||||
|
|
||||||
|
The freqeuncy in Hz expressed as an integer.
|
||||||
|
|
||||||
|
## DESCRIPTION
|
||||||
|
`TEXT`
|
||||||
|
|
||||||
|
The short description is used to explain the purpose of the bandwidth and any other useful details.
|
||||||
28
docs/database/db_info.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Info
|
||||||
|
This is the database meta table and contains 4 columns explained below.
|
||||||
|
|
||||||
|
## NAME
|
||||||
|
`TEXT`
|
||||||
|
|
||||||
|
This is the name of the database.
|
||||||
|
|
||||||
|
## DATA
|
||||||
|
`TEXT`
|
||||||
|
|
||||||
|
The creation date when the database has been initialised.
|
||||||
|
|
||||||
|
## VERSION
|
||||||
|
`INTEGER`
|
||||||
|
|
||||||
|
A simple integer to denote the database version.
|
||||||
|
|
||||||
|
## EDITABLE
|
||||||
|
`INTEGER`
|
||||||
|
|
||||||
|
This field should serve as a writing protection on the database.
|
||||||
|
|
||||||
|
* **0**: read-only database
|
||||||
|
* **1**: database can be edited with no restrictions
|
||||||
|
|
||||||
|
!!! example "Experimental"
|
||||||
|
This feature is experimental and not yet implemented.
|
||||||
22
docs/database/db_location.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Location
|
||||||
|
This is the location where the signal is distributed/received. Avoid the usage of the precise location of the TX station or very small town (very rare). It's a good habit to use **nations/continents** or special location (like `Worldwide`). The table contains 4 columns explained below.
|
||||||
|
|
||||||
|
## LOC_ID
|
||||||
|
`INTEGER` :material-key-outline:{ title="Primary key" } :material-upload-outline:{ title="Auto-increment" }
|
||||||
|
|
||||||
|
This is a unique identification number for each entry that is assigned during the creation of a new location. It is auto-incrementing and is not replaced in the event of deletion.
|
||||||
|
|
||||||
|
## SIG_ID
|
||||||
|
`INTEGER` :material-axis-arrow:{ title="Foreign key" }
|
||||||
|
|
||||||
|
This is a direct reference to the specific signal associated with the location. It links to the primary key of the [Signals](db_signals.md) table that holds detailed information about the signals.
|
||||||
|
|
||||||
|
## VALUE
|
||||||
|
`TEXT`
|
||||||
|
|
||||||
|
The location expressed as a string.
|
||||||
|
|
||||||
|
## DESCRIPTION
|
||||||
|
`TEXT`
|
||||||
|
|
||||||
|
The short description is used to explain further details about the location.
|
||||||
22
docs/database/db_mode.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Mode
|
||||||
|
This field reports the way how a signals has been decoded during the reception. The table contains 4 columns explained below.
|
||||||
|
|
||||||
|
## MOD_ID
|
||||||
|
`INTEGER` :material-key-outline:{ title="Primary key" } :material-upload-outline:{ title="Auto-increment" }
|
||||||
|
|
||||||
|
This is a unique identification number for each entry that is assigned during the creation of a new mode. It is auto-incrementing and is not replaced in the event of deletion.
|
||||||
|
|
||||||
|
## SIG_ID
|
||||||
|
`INTEGER` :material-axis-arrow:{ title="Foreign key" }
|
||||||
|
|
||||||
|
This is a direct reference to the specific signal associated with the modulation. It links to the primary key of the [Signals](db_signals.md) table that holds detailed information about the signals.
|
||||||
|
|
||||||
|
## VALUE
|
||||||
|
`TEXT`
|
||||||
|
|
||||||
|
The mode expressed as a string.
|
||||||
|
|
||||||
|
## DESCRIPTION
|
||||||
|
`TEXT`
|
||||||
|
|
||||||
|
The short description is used to explain the purpose of the mode and any other useful details.
|
||||||
22
docs/database/db_modulation.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Modulation
|
||||||
|
Modulation refers to the method by which information is encoded into the main signal (carrier). This process involves altering various properties of the carrier signal, such as amplitude, frequency, or phase. Multiple modulation techniques can be employed, and a TX station has the capability to utilize different modulation schemes. The table contains 4 columns explained below.
|
||||||
|
|
||||||
|
## MDL_ID
|
||||||
|
`INTEGER` :material-key-outline:{ title="Primary key" } :material-upload-outline:{ title="Auto-increment" }
|
||||||
|
|
||||||
|
This is a unique identification number for each entry that is assigned during the creation of a new modulation. It is auto-incrementing and is not replaced in the event of deletion.
|
||||||
|
|
||||||
|
## SIG_ID
|
||||||
|
`INTEGER` :material-axis-arrow:{ title="Foreign key" }
|
||||||
|
|
||||||
|
This is a direct reference to the specific signal associated with the modulation. It links to the primary key of the [Signals](db_signals.md) table that holds detailed information about the signals.
|
||||||
|
|
||||||
|
## VALUE
|
||||||
|
`TEXT`
|
||||||
|
|
||||||
|
The modulation expressed as a string.
|
||||||
|
|
||||||
|
## DESCRIPTION
|
||||||
|
`TEXT`
|
||||||
|
|
||||||
|
The short description is used to explain the purpose of the modulation and any other useful details.
|
||||||
8
docs/database/db_overview.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Database
|
||||||
|
|
||||||
|
With the release of Artemis 4, we have made a significant upgrade in our data management system by transitioning from a CSV file to a full relational SQL database. This change brings a multitude of advantages that enhance the efficiency, scalability, and reliability of our system. In the following sections, we will explore, table by table, the structure of the new database.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
28
docs/database/db_signals.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Signals
|
||||||
|
This is the main table and contains 4 columns explained below.
|
||||||
|
|
||||||
|
## SIG_ID
|
||||||
|
`INTEGER` :material-key-outline:{ title="Primary key" } :material-upload-outline:{ title="Auto-increment" }
|
||||||
|
|
||||||
|
This is a unique identification number for each entry that is assigned during the creation of a signal. It is auto-incrementing and is not replaced in the event of signal deletion.
|
||||||
|
|
||||||
|
## NAME
|
||||||
|
`TEXT`
|
||||||
|
|
||||||
|
The name of the signal. A simple string that describes in short the analyzed signal. Special characters are allowed.
|
||||||
|
|
||||||
|
## DESCRIPTION
|
||||||
|
`TEXT`
|
||||||
|
|
||||||
|
The short description is used to explain the purpose of the signal and some other useful details.
|
||||||
|
|
||||||
|
!!! tip
|
||||||
|
The DESCRIPTION field supports **Markdown**, a simple markup language for creating rich text using plain text. Headers, emphasis, lists, links, code blocks and many more features for advanced text formtting. [Markdown Basic Syntax :simple-markdown:](https://www.markdownguide.org/basic-syntax/)
|
||||||
|
|
||||||
|
## URL
|
||||||
|
`TEXT`
|
||||||
|
|
||||||
|
The sigidwiki (SigID) URL of the selected signal. This is a direct connection to the online database where further details of the signal are collected.
|
||||||
|
|
||||||
|
!!! info
|
||||||
|
**Internal Use Only** This field is for the SigID database and not intended for user viewing or editing. Personal URLs can be stored in the signal description.
|
||||||
54
docs/database/sigid.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# SigID Wiki Database
|
||||||
|
|
||||||
|
<div align="center" markdown>
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Artemis serves as a valuable resource for both personal signal collection and leveraging a vast repository of pre-identified signals. This software application allows users to curate their own collections, but its true strength lies in its integration with a comprehensive database of known signals. This database is directly sourced from the [Signal Identification Wiki](https://www.sigidwiki.com/wiki/Signal_Identification_Guide), an open-source resource collaboratively maintained by a global community of radio enthusiasts.
|
||||||
|
|
||||||
|
!!! tip "Database Revision"
|
||||||
|
For quality control purposes, the database undergoes a rigorous review process before integration into Artemis. This review adheres to established [guidelines](https://github.com/AresValley/Artemis-DB), ensuring the accuracy and completeness of the information presented to users. The specifics of this review process are outlined in the following section.
|
||||||
|
|
||||||
|
## Modulation
|
||||||
|
A good practise (reported also on ) is to write the primary type of modulation (if known) and not all the possible variants. A practical example is reported on [Signal Identification Wiki](https://www.sigidwiki.com/wiki/Signal_Identification_Guide): there is no need to write **8-PSK** or **QPSK**, **PSK** is enough. The Artemis SigID database is provided without any modulation variants included. The recognized modulations are listed below:
|
||||||
|
|
||||||
|
### Analog
|
||||||
|
* **AM:** Amplitude Modulation
|
||||||
|
* **FM:** Frequency Modulation
|
||||||
|
* **PM:** Phase Modulation
|
||||||
|
* **LSB:** Lower Sideband
|
||||||
|
* **USB:** Upper Sideband
|
||||||
|
* **VSB:** Vestigial Sideband
|
||||||
|
* **CW:** Continuous Wave
|
||||||
|
|
||||||
|
### Digital
|
||||||
|
* **QAM:** Quadrature Amplitude Modulation
|
||||||
|
* **PSK:** Phase-Shift Keying
|
||||||
|
* **FSK:** Frequency-Shift Keying
|
||||||
|
* **ASK:** Amplitude-Shift Keying
|
||||||
|
* **MSK:** Minimum-Shift Keying
|
||||||
|
* **IFK:** Incremental Frequency Keying
|
||||||
|
* **OOK:** On-Off Keying
|
||||||
|
* **FDM:** Frequency-Division Multiplexing
|
||||||
|
* **BOC:** Binary Offset Carrier Modulation
|
||||||
|
* **CDMA:** Code Division Multiple Access
|
||||||
|
* **TDMA:** Time Division Multiple Access
|
||||||
|
* **FBMC:** Filter Bank Multi Carrier
|
||||||
|
* **UFMC:** Universal Filtered Multi Carrier
|
||||||
|
* **PCM:** Pulse Code Modulation
|
||||||
|
* **PPM:** Pulse Position Modulation
|
||||||
|
* **FMCW:** Frequency-Modulated Continuous Wave
|
||||||
|
* **Pulse:** Pulse
|
||||||
|
|
||||||
|
### Spread Spectrum
|
||||||
|
* **CSS:** Chirp Spread Spectrum
|
||||||
|
* **DSSS:** Direct Sequence Spread Spectrum
|
||||||
|
* **FHSS:** Frequency Hopping Spread Spectrum
|
||||||
|
* **THSS:** Time Hopping Spread Spectrum
|
||||||
|
|
||||||
|
## Locations
|
||||||
|
Locations are either countries or special token (`Worldwide`, `Europe`, etc.) . Precise location of the TX station, towns and cities are converted to their respective countries.
|
||||||
18
docs/faq.md
Normal 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:
|
||||||
|
|
||||||
|
- [O’Shea, 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.
|
||||||
14
docs/index.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
title: Documentation
|
||||||
|
---
|
||||||
|
#
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p align="center" markdown>
|
||||||
|
[Artemis Homepage](https://www.aresvalley.com){ .md-button }
|
||||||
|
</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.
|
||||||
53
docs/installation.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# Installation
|
||||||
|
|
||||||
|
**Requirements:**
|
||||||
|
|
||||||
|
* **Windows** 8 or later
|
||||||
|
* **Linux** Ubuntu 20.04+ / Mint 20+ / Fedora 32+ and many other
|
||||||
|
* **macOS** 11+ (Big Sur or later)
|
||||||
|
|
||||||
|
## :simple-windows: Windows
|
||||||
|
1. Download the installer `Artemis-Windows-x86_64-4.x.x.exe` in the Assets menu from the [:material-download: LATEST RELEASE](https://github.com/AresValley/Artemis/releases) and follow the guided procedure to complete the installation process.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## :simple-linux: Linux
|
||||||
|
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:
|
||||||
|
|
||||||
|
``` 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
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create a Shortcut
|
||||||
|
|
||||||
|
1. To create a direct shortcut (in the main menu) launch the bash script in the terminal with the command:
|
||||||
|
|
||||||
|
```
|
||||||
|
. create_shortcut.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This script will:
|
||||||
|
|
||||||
|
- Set the correct read/write privileges of the Artemis folder
|
||||||
|
- Create the artemis.desktop file (shortcut) in /home/$USER/.local/share/applications
|
||||||
|
- Move the Artemis icon file to /usr/share/icons
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## :simple-apple: Mac OS
|
||||||
|
!!! warning
|
||||||
|
The macOS support is temporarily limited!
|
||||||
|
|
||||||
|
The support for the macOS compiled version of the program is temporarily limited due to a lack of machines for extensive testing. To use Artemis on a macOS device, you have the following options:
|
||||||
|
|
||||||
|
* **Run the program directly from the source:** Follow the instructions provided in [this chapter](run_from_source.md) to launch the program from the source code.
|
||||||
|
* **Compile the Artemis 4 binaries on your machine:** In this case, you can contribute by reporting any issues you encounter by [opening an Issue](https://github.com/AresValley/Artemis/issues).
|
||||||
|
* **Use the last available compiled version (3.2.1):** Although this version is no longer officially supported, it remains available for use: [:material-download: Artemis-3.2.1.dmg](https://www.aresvalley.com/?sdm_process_download=1&download_id=377).
|
||||||
|
|
||||||
10
docs/javascripts/katex.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
document$.subscribe(({ body }) => {
|
||||||
|
renderMathInElement(body, {
|
||||||
|
delimiters: [
|
||||||
|
{ left: "$$", right: "$$", display: true },
|
||||||
|
{ left: "$", right: "$", display: false },
|
||||||
|
{ left: "\\(", right: "\\)", display: false },
|
||||||
|
{ left: "\\[", right: "\\]", display: true }
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
44
docs/run_from_source.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Run from source code
|
||||||
|
Running Artemis directly from the source code using the Python interpreter is considered the most reliable and least problematic method. This approach ensures maximum compatibility and reduces the likelihood of encountering runtime issues. However, it is also the less practical option, as it requires the use of the terminal for the execution.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
* Python (3.11 or higher)
|
||||||
|
|
||||||
|
## Procedure
|
||||||
|
|
||||||
|
1. Download and install Python (3.11 or higher) from the official [website](https://www.python.org/downloads/). Be sure to select the flag `Add Python 3.x to PATH` during the first part of the installation.
|
||||||
|
|
||||||
|
2. Download Artemis source code from the [latest release](https://github.com/AresValley/Artemis/releases) in the GitHub repository.
|
||||||
|
|
||||||
|
3. Extract the downloaded archive.
|
||||||
|
|
||||||
|
4. Open the terminal in Artemis folder and install the required Python libraries with PIP:
|
||||||
|
```
|
||||||
|
pip install -r requirements.txt --user
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Launch Artemis:
|
||||||
|
```
|
||||||
|
python app.py
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! example "Note for Developers"
|
||||||
|
Whenever modifications are made to any **.qml** file or any assets (such as images, icons, etc.), it is essential to recompile the **resource.py** file to ensure that the changes are reflected in the application. To achieve this, execute the following command:
|
||||||
|
```
|
||||||
|
pyside6-rcc ./artemis.qrc -o artemis/resources.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Folders Structure
|
||||||
|
Artemis can be safely executed and/or installed in any folder (even protected ones, such as `Program Files (x86)` in Windows) because Artemis performs read-only operations in the `BASE_DIR` folder from where it runs. All the reading-writing operations (such as database ops, logging, etc.) are performed in standard folders as follow:
|
||||||
|
|
||||||
|
### :simple-windows: Windows
|
||||||
|
* Data, Cache, Configurations: `$USER\AppData\Local\AresValley\Artemis`
|
||||||
|
* Logs: `$USER\AppData\Local\Temp`
|
||||||
|
|
||||||
|
### :simple-linux: Linux
|
||||||
|
* Data, Cache, Configurations: `~/.local/share/AresValley/Artemis`
|
||||||
|
* Logs: `/tmp`
|
||||||
|
|
||||||
|
### :simple-apple: Mac OS
|
||||||
|
* Data, Cache, Configurations: `~/Library/Application Support/AresValley/Artemis`
|
||||||
|
* Logs: `/tmp`
|
||||||
9
docs/space_weather/aurora.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
This short-term aurora forecast predicts its location and intensity over the next 30 to 90 minutes, based on the [OVATION model](https://www.swpc.noaa.gov/products/aurora-30-minute-forecast). The lead time of the forecast corresponds to the duration it takes for the solar wind to travel from the L1 observation point to Earth.
|
||||||
|
|
||||||
|
Auroras indicate current geomagnetic storm conditions and provide situational awareness for various technologies: for example, they directly affect HF radio communication and GPS/GNSS satellite navigation and are related to ground-induced currents impacting electric power transmission.
|
||||||
|
|
||||||
|
For many people, the aurora is a beautiful nighttime phenomenon that is worth traveling to arctic regions just to observe. It is the only way for most people to actually experience space weather.
|
||||||
225
docs/space_weather/current.md
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
#
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 1. Kp Index
|
||||||
|
The **K index** is a number (from 0 to 9) that shows how much Earth's magnetic field is disturbed. A K index of 1 means things are calm, while a K index of 5 or higher indicates a geomagnetic storm. These disturbances are measured with magnetometers that track changes in Earth's magnetic field every three hours. The K itself comes from a German word "Kennziffer" meaning "characteristic digit". To get a big picture of what's happening around the world, an official planetary **Kp index** is calculated. This is done by averaging the K indices from a special network of 13 geomagnetic observatories located around the globe at mid-latitudes.
|
||||||
|
|
||||||
|
|Index|Activity Level|High Latitudes|Low Latitudes|Possible Source|
|
||||||
|
|-|-|-|-|-|
|
||||||
|
|**Kp 0**|Inactive|Weak & slow aurora possible|Aurora extremely unlikely|Small influx of particles due to some reconnections mostly at the magnetotail|
|
||||||
|
|**Kp 1**|Very Quiet|Weak & slow aurora likely|Aurora very unlikely|Vide supra|
|
||||||
|
|**Kp 2**|Quiet|Moderate auroral display|Aurora unlikely|Vide supra|
|
||||||
|
|**Kp 3**|Unsettled|Active auroral display, sporadic substorm possible|Weak aurora display possible|Coronal hole sending fast winds or remains after days of storming, enhanced solar wind|
|
||||||
|
|**Kp 4**|Active|Active auroral display, multiple sporadic substorms possible|Weak Aurora Display Possible|Vide supra|
|
||||||
|
|**Kp 5**|Minor Storm (G1)|Very active auroral display, multiple substorms likely|Aurora display likely|Coronal hole sending fast winds or coronal mass ejection (CME), enhanced solar wind|
|
||||||
|
|**Kp 6**|Moderate Storm (G2)|Strong auroral display, longer substorms|Active auroral display very likely|Vide supra|
|
||||||
|
|**Kp 7**|Strong Storm (G3)|Very strong auroral display|Strong auroral display extremely likely|Large CMEs caused by solar storms or flares, very enhanced solar wind with strong shock wave|
|
||||||
|
|**Kp 8**|Severe Storm (G4)|Extremely strong aurora, long periods of substorming|Strong auroral display extremely likely|Vide supra|
|
||||||
|
|**Kp 9**|Extreme Storm (G5)|Extremely strong aurora, long periods of substorming|Very strong auroral display, overhead aurora possible|Super CMEs, Carrington-class events, devastating solar wind with extreme shock waves|
|
||||||
|
|
||||||
|
## 2. Ap Index
|
||||||
|
The **A index** represents the three-hourly equivalent amplitude of geomagnetic activity at a specific magnetometer station, derived from the station-specific K index. Due to the quasi-logarithmic nature of the K-scale in relation to magnetometer fluctuations, directly averaging a set of K indices is not really meaningful. Instead each K is converted back into a linear scale. The **Ap index** is determined by averaging the eight daily Ap values (3-hour) and using the same stations grid explained for the Kp index in the previous section. This provides a measure of geomagnetic activity for a specific day. Days with higher levels of geomagnetic activity correspond to higher daily Ap values.
|
||||||
|
|
||||||
|
## 3. NOAA Space Weather Scale
|
||||||
|
|
||||||
|
### Geomagnetic Storm
|
||||||
|
|
||||||
|
??? danger "G5 (Extreme)"
|
||||||
|
* **Physical measure:** Kp = 9
|
||||||
|
* **Average Frequency:** 4 days per cycle (11 years)
|
||||||
|
|
||||||
|
**Power systems:** Widespread voltage control problems and protective system problems can occur, some grid systems may experience complete collapse or blackouts. Transformers may experience damage.
|
||||||
|
|
||||||
|
**Spacecraft operations:** May experience extensive surface charging, problems with orientation, uplink/downlink and tracking satellites.
|
||||||
|
|
||||||
|
**Other systems:** Pipeline currents can reach hundreds of amps, HF (high frequency) radio propagation may be impossible in many areas for one to two days, satellite navigation may be degraded for days, low-frequency radio navigation can be out for hours, and aurora has been seen as low as Florida and southern Texas (typically 40° geomagnetic lat.).
|
||||||
|
|
||||||
|
??? danger "G4 (Severe)"
|
||||||
|
* **Physical measure:** Kp = 8 to 9-
|
||||||
|
* **Average Frequency:** 60 days per cycle (11 years)
|
||||||
|
|
||||||
|
**Power systems:** Possible widespread voltage control problems and some protective systems will mistakenly trip out key assets from the grid.
|
||||||
|
|
||||||
|
**Spacecraft operations:** May experience surface charging and tracking problems, corrections may be needed for orientation problems.
|
||||||
|
|
||||||
|
**Other systems:** Induced pipeline currents affect preventive measures, HF radio propagation sporadic, satellite navigation degraded for hours, low-frequency radio navigation disrupted, and aurora has been seen as low as Alabama and northern California (typically 45° geomagnetic lat.).
|
||||||
|
|
||||||
|
??? warning "G3 (Strong)"
|
||||||
|
* **Physical measure:** Kp = 7
|
||||||
|
* **Average Frequency:** 130 days per cycle (11 years)
|
||||||
|
|
||||||
|
**Power systems:** Voltage corrections may be required, false alarms triggered on some protection devices.
|
||||||
|
|
||||||
|
**Spacecraft operations:** Surface charging may occur on satellite components, drag may increase on low-Earth-orbit satellites, and corrections may be needed for orientation problems.
|
||||||
|
|
||||||
|
**Other systems:** Intermittent satellite navigation and low-frequency radio navigation problems may occur, HF radio may be intermittent, and aurora has been seen as low as Illinois and Oregon (typically 50° geomagnetic lat.).
|
||||||
|
|
||||||
|
??? warning "G2 (Moderate)"
|
||||||
|
* **Physical measure:** Kp = 6
|
||||||
|
* **Average Frequency:** 360 days per cycle (11 years)
|
||||||
|
|
||||||
|
**Power systems:** High-latitude power systems may experience voltage alarms, long-duration storms may cause transformer damage.
|
||||||
|
|
||||||
|
**Spacecraft operations:** Corrective actions to orientation may be required by ground control; possible changes in drag affect orbit predictions.
|
||||||
|
|
||||||
|
**Other systems:** HF radio propagation can fade at higher latitudes, and aurora has been seen as low as New York and Idaho (typically 55° geomagnetic lat.).
|
||||||
|
|
||||||
|
??? info "G1 (Minor)"
|
||||||
|
* **Physical measure:** Kp = 5
|
||||||
|
* **Average Frequency:** 900 days per cycle (11 years)
|
||||||
|
|
||||||
|
**Power systems:** Weak power grid fluctuations can occur.
|
||||||
|
|
||||||
|
**Spacecraft operations:** Minor impact on satellite operations possible.
|
||||||
|
|
||||||
|
**Other systems:** Migratory animals are affected at this and higher levels; aurora is commonly visible at high latitudes (northern Michigan and Maine).
|
||||||
|
|
||||||
|
### Solar Radiation Storms
|
||||||
|
|
||||||
|
??? danger "S5 (Extreme)"
|
||||||
|
* **Physical measure:** Flux level of $\ge 10 MeV$ particles = $10^5$
|
||||||
|
* **Average Frequency:** < 1 event per cycle (11 years)
|
||||||
|
|
||||||
|
**Biological:** Unavoidable high radiation hazard to astronauts on EVA (extra-vehicular activity); passengers and crew in high-flying aircraft at high latitudes may be exposed to radiation risk.
|
||||||
|
|
||||||
|
**Satellite operations:** Satellites may be rendered useless, memory impacts can cause loss of control, may cause serious noise in image data, star-trackers may be unable to locate sources; permanent damage to solar panels possible.
|
||||||
|
|
||||||
|
**Other systems:** Complete blackout of HF (high frequency) communications possible through the polar regions, and position errors make navigation operations extremely difficult.
|
||||||
|
|
||||||
|
??? danger "S4 (Severe)"
|
||||||
|
* **Physical measure:** Flux level of $\ge 10 MeV$ particles = $10^4$
|
||||||
|
* **Average Frequency:** 3 events per cycle (11 years)
|
||||||
|
|
||||||
|
**Biological:** Unavoidable radiation hazard to astronauts on EVA; passengers and crew in high-flying aircraft at high latitudes may be exposed to radiation risk.
|
||||||
|
|
||||||
|
**Satellite operations:** May experience memory device problems and noise on imaging systems; star-tracker problems may cause orientation problems, and solar panel efficiency can be degraded.
|
||||||
|
|
||||||
|
**Other systems:** Blackout of HF radio communications through the polar regions and increased navigation errors over several days are likely.
|
||||||
|
|
||||||
|
??? warning "S3 (Strong)"
|
||||||
|
* **Physical measure:** Flux level of $\ge 10 MeV$ particles = $10^3$
|
||||||
|
* **Average Frequency:** 10 events per cycle (11 years)
|
||||||
|
|
||||||
|
**Biological:** Radiation hazard avoidance recommended for astronauts on EVA; passengers and crew in high-flying aircraft at high latitudes may be exposed to radiation risk.
|
||||||
|
|
||||||
|
**Satellite operations:** Single-event upsets, noise in imaging systems, and slight reduction of efficiency in solar panel are likely.
|
||||||
|
|
||||||
|
**Other systems:** Degraded HF radio propagation through the polar regions and navigation position errors likely.
|
||||||
|
|
||||||
|
??? warning "S2 (Moderate)"
|
||||||
|
* **Physical measure:** Flux level of $\ge 10 MeV$ particles = $10^2$
|
||||||
|
* **Average Frequency:** 25 events per cycle (11 years)
|
||||||
|
|
||||||
|
**Biological:** Passengers and crew in high-flying aircraft at high latitudes may be exposed to elevated radiation risk.
|
||||||
|
|
||||||
|
**Satellite operations:** Infrequent single-event upsets possible.
|
||||||
|
|
||||||
|
**Other systems:** Small effects on HF propagation through the polar regions and navigation at polar cap locations possibly affected.
|
||||||
|
|
||||||
|
??? info "S1 (Minor)"
|
||||||
|
* **Physical measure:** Flux level of $\ge 10 MeV$ particles = $10$
|
||||||
|
* **Average Frequency:** 50 events per cycle (11 years)
|
||||||
|
|
||||||
|
**Biological:** None.
|
||||||
|
|
||||||
|
**Satellite operations:** None.
|
||||||
|
|
||||||
|
**Other systems:** Minor impacts on HF radio in the polar regions.
|
||||||
|
|
||||||
|
### Radio Blackouts
|
||||||
|
|
||||||
|
??? danger "R5 (Extreme)"
|
||||||
|
* **Physical measure:*** X20 ($2e^{-3} Wm^{-2}$)
|
||||||
|
* **Average Frequency:** < 1 days per cycle (11 years)
|
||||||
|
|
||||||
|
**HF Radio:** Complete HF (high frequency**) radio blackout on the entire sunlit side of the Earth lasting for a
|
||||||
|
number of hours. This results in no HF radio contact with mariners and en route aviators in this sector.
|
||||||
|
|
||||||
|
**Navigation:** Low-frequency navigation signals used by maritime and general aviation systems experience outages
|
||||||
|
on the sunlit side of the Earth for many hours, causing loss in positioning. Increased satellite navigation errors in
|
||||||
|
positioning for several hours on the sunlit side of Earth, which may spread into the night side.
|
||||||
|
|
||||||
|
*GOES X-ray peak brightness by class and by flux (measured in the 0.1-0.8 nm range, in W·m-2)
|
||||||
|
|
||||||
|
??? danger "R4 (Severe)"
|
||||||
|
* **Physical measure:*** X10 ($10^{-3} Wm^{-2}$)
|
||||||
|
* **Average Frequency:** 8 days per cycle (11 years)
|
||||||
|
|
||||||
|
**HF Radio:** HF radio communication blackout on most of the sunlit side of Earth for one to two hours. HF radio
|
||||||
|
contact lost during this time.
|
||||||
|
|
||||||
|
**Navigation:** Outages of low-frequency navigation signals cause increased error in positioning for one to two
|
||||||
|
hours. Minor disruptions of satellite navigation possible on the sunlit side of Earth.
|
||||||
|
|
||||||
|
*GOES X-ray peak brightness by class and by flux (measured in the 0.1-0.8 nm range, in W·m-2)
|
||||||
|
|
||||||
|
??? warning "R3 (Strong)"
|
||||||
|
* **Physical measure:*** X1 ($10^{-4} Wm^{-2}$)
|
||||||
|
* **Average Frequency:** 140 days per cycle (11 years)
|
||||||
|
|
||||||
|
**HF Radio:** Wide area blackout of HF radio communication, loss of radio contact for about an hour on sunlit side
|
||||||
|
of Earth.
|
||||||
|
|
||||||
|
**Navigation:** Low-frequency navigation signals degraded for about an hour.
|
||||||
|
|
||||||
|
*GOES X-ray peak brightness by class and by flux (measured in the 0.1-0.8 nm range, in W·m-2)
|
||||||
|
|
||||||
|
??? warning "R2 (Moderate)"
|
||||||
|
* **Physical measure:*** M5 ($5e^{-5} Wm^{-2}$)
|
||||||
|
* **Average Frequency:** 300 days per cycle (11 years)
|
||||||
|
|
||||||
|
**HF Radio:** Limited blackout of HF radio communication on sunlit side of the Earth, loss of radio contact for tens
|
||||||
|
of minutes.
|
||||||
|
|
||||||
|
**Navigation:** Degradation of low-frequency navigation signals for tens of minutes.
|
||||||
|
|
||||||
|
*GOES X-ray peak brightness by class and by flux (measured in the 0.1-0.8 nm range, in W·m-2)
|
||||||
|
|
||||||
|
??? info "R1 (Minor)"
|
||||||
|
* **Physical measure:*** M1 ($10^{-5} Wm^{-2}$)
|
||||||
|
* **Average Frequency:** 950 days per cycle (11 years)
|
||||||
|
|
||||||
|
**HF Radio:** Weak or minor degradation of HF radio communication on sunlit side of the Earth, occasional loss of
|
||||||
|
radio contact.
|
||||||
|
|
||||||
|
**Navigation:** Low-frequency navigation signals degraded for brief intervals.
|
||||||
|
|
||||||
|
*GOES X-ray peak brightness by class and by flux (measured in the 0.1-0.8 nm range, in W·m-2)
|
||||||
|
|
||||||
|
## 4. X-Ray Solar Activity
|
||||||
|
This is a summary of the **X-Ray Flare Class**. Large solar X-ray flares can change the Earth’s ionosphere, which blocks high-frequency (HF) radio transmissions on the sunlit side of the Earth. Solar flares are also associated with Coronal Mass Ejections (CMEs) which can ultimately lead to geomagnetic storms. SWPC sends out space weather alerts at the M5 level. Some large flares are accompanied by strong radio bursts that may interfere with other radio frequencies and cause problems for satellite communication and radio navigation (GPS).
|
||||||
|
|
||||||
|
| Class | Peak Strength (W/m<sup>2</sup>) | Effects on Earth |
|
||||||
|
|-------|---------------------------------|--------------------------------------------------------------|
|
||||||
|
| B | I < 10<sup>-6</sup> | Too small to harm Earth |
|
||||||
|
| C | 10<sup>-6</sup> ≤ I < 10<sup>-5</sup> | Small with few noticeable consequences |
|
||||||
|
| M | 10<sup>-5</sup> ≤ I < 10<sup>-4</sup> | Brief radio blackouts in polar regions, minor radiation storms |
|
||||||
|
| X | I ≥ 10<sup>-4</sup> | Planet-wide radio blackouts, long-lasting radiation storms |
|
||||||
|
|
||||||
|
## 5. RF Propagation
|
||||||
|
|
||||||
|
### Maximum Usable Frequency
|
||||||
|
In radio transmission, the maximum usable frequency (MUF) is the highest frequency that can be effectively used for communication between two locations on Earth by reflecting off the ionosphere (via skywave or skip) at a given time, regardless of the transmitter's power. This measurement is particularly valuable for shortwave transmissions.
|
||||||
|
|
||||||
|
### Earth-Moon-Earth
|
||||||
|
Earth–Moon–Earth communication (EME), commonly referred to as Moon bounce, is a radio communication method in which radio waves are transmitted from an Earth-based station, reflected off the Moon's surface, and then received back on Earth. The value gives the probability of a succesfull connection.
|
||||||
|
|
||||||
|
### Meteor Scatter
|
||||||
|
Meteor burst communications (MBC), also known as meteor scatter (MS) communications, is a radio propagation technique that uses the ionized trails created by meteors entering the atmosphere to establish brief communication links between radio stations up to 2,250 kilometers (1,400 miles) apart. This can involve either forward-scatter or back-scatter of the radio waves. Like EME, the value gives the probability of a succesfull connection.
|
||||||
|
|
||||||
|
### Sporadic-E
|
||||||
|
**Report of the latest E-skip spots on 50, 70 & 144 MHz by [DXrobot](https://dxrobot.gooddx.net/)**
|
||||||
|
|
||||||
|
Sporadic E (Es or SpE) is a rare type of radio propagation that uses a lower part of the Earth's ionosphere, which typically doesn't refract radio waves. It reflects signals off small "clouds" in the E region at altitudes of 95-150 km (50-100 miles). Unlike the regular F region skywave propagation, which depends on daily cycles of ionized layers from UV light, Sporadic E uses transient ionized patches. This allows for occasional long-distance VHF communication, usually during the six weeks around the summer solstice, beyond the normal line-of-sight range.
|
||||||
|
|
||||||
|
### Aurora Spots
|
||||||
|
**Report of the latest aurora spots on 50, 70 & 144 MHz by [DXrobot](https://dxrobot.gooddx.net/)**
|
||||||
|
|
||||||
|
Auroral propagation, or auroral backscatter, is a form of radio propagation that occurs during an auroral event, affecting VHF and UHF communications. Increased ionization in the E layer of the ionosphere reflects signals at much higher frequencies than usual, enabling communication up to 1000 MHz, though 500 MHz is more common. Signals are directed towards the auroral region and reflected back, but they are often distorted due to particle movement (the signal is roughly Doppler shifted of 1 kHz at around 150 MHz as the electrons stream down).
|
||||||
|
|
||||||
|
### Expected HF Noise
|
||||||
|
This is just the expected noise in HF based on the current Space Weather conditions.
|
||||||
|
|
||||||
|
## 6. Report Age
|
||||||
|
Poseidon Daemon is in charge of parse all the necessary data used for the Space Weather module. The data of the last generated report is written here.
|
||||||
7
docs/space_weather/drap.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The D-Region Absorption Product (DRAP) evaluates the effects of solar X-ray flux and solar energetic particle (SEP) events on HF radio communication. Long-distance communications using high frequency (HF) radio waves (3 - 30 MHz) rely on signal reflection in the ionosphere. Typically, radio waves reflect near the peak of the F2 layer (~300 km altitude), but during their journey to and from this peak, the signals experience attenuation due to absorption by the intervening ionosphere.
|
||||||
|
|
||||||
|
The [D-Region Absorption Prediction model](https://www.swpc.noaa.gov/products/d-region-absorption-predictions-d-rap) provides guidance to understand the degradation and blackouts of HF radio communications that can result from these conditions.
|
||||||
15
docs/space_weather/forecasts.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 1. Forecast Summary
|
||||||
|
Geomagnetic activity, Solar Radiation Storms and Radio Blackouts probable events (in the next 3 days) are described in this section.
|
||||||
|
|
||||||
|
## 2. 3-Day Kp Index
|
||||||
|
This is a 3 day projection of the [Kp index](current.md).
|
||||||
|
|
||||||
|
## 3. Events Probability
|
||||||
|
The probability (in percentage) of different events that can take place and generate some important conditions for RF propagation. All the event are explained [here](current.md).
|
||||||
|
|
||||||
|
!!! info
|
||||||
|
Geomagnetic Activity has two percentual value per day: the left one refers to high-latitude location and the right one is for middle-latitude locations.
|
||||||
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 8.9 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 6.7 KiB |
BIN
images/artemis_icon.ico
Normal file
|
After Width: | Height: | Size: 193 KiB |
1
images/artemis_icon.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="276.328" height="277.267" xmlns:v="https://vecta.io/nano"><path d="M37.321 277.266c-7.249-.467-14.32-2.937-20.241-7.072-2.011-1.404-3.587-2.728-5.393-4.532-4.488-4.481-7.778-9.819-9.717-15.761-1.004-3.078-1.597-6.062-1.878-9.455-.121-1.465-.121-202.054 0-203.52.588-7.105 2.758-13.395 6.618-19.187C10.987 11.32 17.25 6.137 24.305 3.176 28.898 1.249 33.349.265 38.385.061c2.072-.084 197.531-.082 199.544.002 5.149.214 9.683 1.233 14.304 3.215 8.516 3.652 15.673 10.409 19.836 18.728 2.594 5.184 3.951 10.485 4.195 16.392.085 2.056.085 198.52 0 200.576-.072 1.731-.194 3.044-.423 4.544-2.125 13.903-11.448 25.667-24.529 30.953a42.96 42.96 0 0 1-6.279 1.939c-1.436.317-2.637.507-4.96.782l-202.752.073zm119.17-58.109l18.281-30.512c-.034-.118-36.584-60.9-36.622-60.902-.019-.001-8.28 13.71-18.357 30.468l-18.322 30.47.409.682 18.284 30.41 17.903 29.776c.016.026.049.048.075.048s8.282-13.698 18.348-30.44zm-57.79-56.886l6.577-11.008c0-.036-1.674-2.882-3.72-6.324l-3.72-6.258 20.142-33.884 20.215-33.806c.108.114 40.164 67.515 40.198 67.639.017.061-1.532 2.73-3.694 6.368l-3.665 6.359 13.163 21.867c.053.056 2.715-4.374 10.357-17.234l10.286-17.309-33.326-56.062-33.367-56.062c-.023 0-14.94 25.063-33.149 55.696L71.67 138.316l-.218.366 10.292 17.313 10.336 17.266c.024-.026 3.004-4.971 6.621-10.989z"/><path d="M81.991 155.817c-5.546-9.292-9.97-17.19-9.832-17.551s10.416-17.742 22.838-38.624l32.832-55.198c5.635-9.477 10.392-17.062 10.571-16.856.407.47 62.641 105.173 64.594 108.673l1.432 2.567-9.918 16.703c-5.455 9.187-10.087 16.651-10.293 16.587-.455-.141-12.65-20.17-12.65-20.777 0-.236 1.618-3.187 3.595-6.557l3.595-6.129-19.228-32.361-20.268-34.07-1.039-1.709-1.549 2.475c-.852 1.361-10.01 16.69-20.35 34.064L97.52 138.642l3.567 6.048c1.962 3.327 3.567 6.276 3.567 6.554s-2.831 5.222-6.29 10.986l-6.29 10.48zm38.078 63.227l-17.968-30.37c0-.396 35.265-59.406 35.99-60.223.176-.198 35.744 59.144 35.937 59.958.131.552-35.404 60.465-35.862 60.465-.071 0-8.215-13.424-18.097-29.831z" fill="#fff"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
1
images/artemis_not_available.svg
Normal file
|
After Width: | Height: | Size: 20 KiB |
1
images/icons/abort.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="m480-424 116 116q11 11 28 11t28-11q11-11 11-28t-11-28L536-480l116-116q11-11 11-28t-11-28q-11-11-28-11t-28 11L480-536 364-652q-11-11-28-11t-28 11q-11 11-11 28t11 28l116 116-116 116q-11 11-11 28t11 28q11 11 28 11t28-11l116-116Zm0 344q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 538 B |
1
images/icons/add.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M280-160v-441q0-33 24-56t57-23h439q33 0 56.5 23.5T880-600v287q0 16-6 30.5T857-257L703-103q-11 11-25.5 17T647-80H360q-33 0-56.5-23.5T280-160ZM81-710q-6-33 13-59.5t52-32.5l434-77q33-6 59.5 13t32.5 52l10 54H360q-66 0-113 47t-47 113v382q-16-9-27.5-24T158-276L81-710Zm459 370v80q0 17 11.5 28.5T580-220q17 0 28.5-11.5T620-260v-80h80q17 0 28.5-11.5T740-380q0-17-11.5-28.5T700-420h-80v-80q0-17-11.5-28.5T580-540q-17 0-28.5 11.5T540-500v80h-80q-17 0-28.5 11.5T420-380q0 17 11.5 28.5T460-340h80Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 610 B |
1
images/icons/browser.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M838-65 720-183v89h-80v-226h226v80h-90l118 118-56 57ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 20-2 40t-6 40h-82q5-20 7.5-40t2.5-40q0-20-2.5-40t-7.5-40H654q3 20 4.5 40t1.5 40q0 20-1.5 40t-4.5 40h-80q3-20 4.5-40t1.5-40q0-20-1.5-40t-4.5-40H386q-3 20-4.5 40t-1.5 40q0 20 1.5 40t4.5 40h134v80H404q12 43 31 82.5t45 75.5q20 0 40-2.5t40-4.5v82q-20 2-40 4.5T480-80ZM170-400h136q-3-20-4.5-40t-1.5-40q0-20 1.5-40t4.5-40H170q-5 20-7.5 40t-2.5 40q0 20 2.5 40t7.5 40Zm34-240h118q9-37 22.5-72.5T376-782q-55 18-99 54.5T204-640Zm172 462q-18-34-31.5-69.5T322-320H204q29 51 73 87.5t99 54.5Zm28-462h152q-12-43-31-82.5T480-798q-26 36-45 75.5T404-640Zm234 0h118q-29-51-73-87.5T584-782q18 34 31.5 69.5T638-640Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 917 B |
1
images/icons/delete.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M280-120q-33 0-56.5-23.5T200-200v-520q-17 0-28.5-11.5T160-760q0-17 11.5-28.5T200-800h160q0-17 11.5-28.5T400-840h160q17 0 28.5 11.5T600-800h160q17 0 28.5 11.5T800-760q0 17-11.5 28.5T760-720v520q0 33-23.5 56.5T680-120H280Zm200-284 76 76q11 11 28 11t28-11q11-11 11-28t-11-28l-76-76 76-76q11-11 11-28t-11-28q-11-11-28-11t-28 11l-76 76-76-76q-11-11-28-11t-28 11q-11 11-11 28t11 28l76 76-76 76q-11 11-11 28t11 28q11 11 28 11t28-11l76-76Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 556 B |
1
images/icons/dialog_error.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#f44336"><path d="M363-120q-16 0-30.5-6T307-143L143-307q-11-11-17-25.5t-6-30.5v-234q0-16 6-30.5t17-25.5l164-164q11-11 25.5-17t30.5-6h234q16 0 30.5 6t25.5 17l164 164q11 11 17 25.5t6 30.5v234q0 16-6 30.5T817-307L653-143q-11 11-25.5 17t-30.5 6H363Zm117-304 86 86q11 11 28 11t28-11q11-11 11-28t-11-28l-86-86 86-86q11-11 11-28t-11-28q-11-11-28-11t-28 11l-86 86-86-86q-11-11-28-11t-28 11q-11 11-11 28t11 28l86 86-86 86q-11 11-11 28t11 28q11 11 28 11t28-11l86-86Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 563 B |
1
images/icons/dialog_info.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#2196f3"><path d="M480-280q17 0 28.5-11.5T520-320v-160q0-17-11.5-28.5T480-520q-17 0-28.5 11.5T440-480v160q0 17 11.5 28.5T480-280Zm0-320q17 0 28.5-11.5T520-640q0-17-11.5-28.5T480-680q-17 0-28.5 11.5T440-640q0 17 11.5 28.5T480-600Zm0 520q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 524 B |
1
images/icons/dialog_quest.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#9c27b0"><path d="M478-240q21 0 35.5-14.5T528-290q0-21-14.5-35.5T478-340q-21 0-35.5 14.5T428-290q0 21 14.5 35.5T478-240Zm2 160q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm4-572q25 0 43.5 16t18.5 40q0 22-13.5 39T502-525q-23 20-40.5 44T444-427q0 14 10.5 23.5T479-394q15 0 25.5-10t13.5-25q4-21 18-37.5t30-31.5q23-22 39.5-48t16.5-58q0-51-41.5-83.5T484-720q-38 0-72.5 16T359-655q-7 12-4.5 25.5T368-609q14 8 29 5t25-17q11-15 27.5-23t34.5-8Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 679 B |
1
images/icons/dialog_warn.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#ffc107"><path d="M109-120q-11 0-20-5.5T75-140q-5-9-5.5-19.5T75-180l370-640q6-10 15.5-15t19.5-5q10 0 19.5 5t15.5 15l370 640q6 10 5.5 20.5T885-140q-5 9-14 14.5t-20 5.5H109Zm371-120q17 0 28.5-11.5T520-280q0-17-11.5-28.5T480-320q-17 0-28.5 11.5T440-280q0 17 11.5 28.5T480-240Zm0-120q17 0 28.5-11.5T520-400v-120q0-17-11.5-28.5T480-560q-17 0-28.5 11.5T440-520v120q0 17 11.5 28.5T480-360Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 489 B |