1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-12-22 17:45:48 -05:00

Add Satellite Tracker feature

This commit is contained in:
Jon Beniston 2021-02-26 20:25:48 +00:00
parent 262a75beec
commit 5461facb3b
58 changed files with 8821 additions and 11 deletions

View File

@ -312,7 +312,8 @@ if (BUILD_GUI)
QuickWidgets
Positioning
Location
Charts)
Charts
TextToSpeech)
endif()
# other requirements

Binary file not shown.

After

Width:  |  Height:  |  Size: 620 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -359,6 +359,57 @@ if ((NOT LIBDSDCC_FOUND OR LIBDSDCC_EXTERNAL) AND LIBMBE_FOUND)
endif ()
endif ((NOT LIBDSDCC_FOUND OR LIBDSDCC_EXTERNAL) AND LIBMBE_FOUND)
# For APT demodulator
set(APT_LIBRARIES "${SDRANGEL_BINARY_LIB_DIR}/apt.lib" CACHE INTERNAL "")
ExternalProject_Add(apt
GIT_REPOSITORY https://github.com/srcejon/aptdec.git
GIT_TAG libaptdec
PREFIX "${EXTERNAL_BUILD_LIBRARIES}/apt"
CMAKE_ARGS ${COMMON_CMAKE_ARGS}
BUILD_BYPRODUCTS "${APT_LIBRARIES}"
INSTALL_COMMAND ""
TEST_COMMAND ""
)
ExternalProject_Get_Property(apt source_dir binary_dir)
set(APT_FOUND ON CACHE INTERNAL "")
set(APT_EXTERNAL ON CACHE INTERNAL "")
set(APT_INCLUDE_DIR "${EXTERNAL_BUILD_LIBRARIES}/apt/src/apt/src" CACHE INTERNAL "")
if (WIN32)
install(FILES "${SDRANGEL_BINARY_BIN_DIR}/apt${CMAKE_SHARED_LIBRARY_SUFFIX}" DESTINATION "${INSTALL_LIB_DIR}")
elseif (APPLE)
set(APT_LIBRARIES "${binary_dir}/libapt${CMAKE_SHARED_LIBRARY_SUFFIX}" CACHE INTERNAL "")
install(DIRECTORY "${binary_dir}/" DESTINATION "${INSTALL_LIB_DIR}"
FILES_MATCHING PATTERN "libapt*${CMAKE_SHARED_LIBRARY_SUFFIX}")
set(MACOS_EXTERNAL_LIBS_FIXUP "${MACOS_EXTERNAL_LIBS_FIXUP};${binary_dir}/")
endif ()
# For Satellite Tracker feature
# No tags for this in github - but doesn't change often
# Fails to build with CMAKE_INTERPROCEDURAL_OPTIMIZATION=ON on Windows
set(SGP4_LIBRARIES "${SDRANGEL_BINARY_LIB_DIR}/sgp4s.lib" CACHE INTERNAL "")
ExternalProject_Add(sgp4
GIT_REPOSITORY https://github.com/dnwrnr/sgp4.git
PREFIX "${EXTERNAL_BUILD_LIBRARIES}/sgp4"
CMAKE_ARGS ${COMMON_CMAKE_ARGS}
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=OFF
BUILD_BYPRODUCTS "${SGP4_LIBRARIES}"
INSTALL_COMMAND ""
TEST_COMMAND ""
)
ExternalProject_Get_Property(sgp4 source_dir binary_dir)
set(SGP4_FOUND ON CACHE INTERNAL "")
set(SGP4_EXTERNAL ON CACHE INTERNAL "")
set(SGP4_INCLUDE_DIR "${EXTERNAL_BUILD_LIBRARIES}/sgp4/src/sgp4/libsgp4" CACHE INTERNAL "")
if (WIN32)
install(FILES "${SDRANGEL_BINARY_BIN_DIR}/sgp4s${CMAKE_SHARED_LIBRARY_SUFFIX}" DESTINATION "${INSTALL_LIB_DIR}")
elseif (APPLE)
set(SGP4_LIBRARIES "${binary_dir}/libsgp4s${CMAKE_SHARED_LIBRARY_SUFFIX}" CACHE INTERNAL "")
install(DIRECTORY "${binary_dir}/" DESTINATION "${INSTALL_LIB_DIR}"
FILES_MATCHING PATTERN "libsgp4s*${CMAKE_SHARED_LIBRARY_SUFFIX}")
set(MACOS_EXTERNAL_LIBS_FIXUP "${MACOS_EXTERNAL_LIBS_FIXUP};${binary_dir}/")
endif ()
# requirements needed by many packages on windows
if (WIN32)
ExternalProject_Add(pthreads4w

View File

@ -16,4 +16,7 @@ add_subdirectory(aprs)
add_subdirectory(demodanalyzer)
add_subdirectory(rigctlserver)
add_subdirectory(simpleptt)
if (SGP4_FOUND)
add_subdirectory(satellitetracker)
endif()
add_subdirectory(startracker)

View File

@ -0,0 +1,83 @@
project(satellitetracker)
set(satellitetracker_SOURCES
satellitetracker.cpp
satellitetrackersettings.cpp
satellitetrackerplugin.cpp
satellitetrackerworker.cpp
satellitetrackerwebapiadapter.cpp
satellitetrackersgp4.cpp
)
set(satellitetracker_HEADERS
satellitetracker.h
satellitetrackersettings.h
satellitetrackerplugin.h
satellitetrackerreport.h
satellitetrackerworker.h
satellitetrackerwebapiadapter.h
satellitetrackersgp4.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
${SGP4_INCLUDE_DIR}
)
if(NOT SERVER_MODE)
set(satellitetracker_SOURCES
${satellitetracker_SOURCES}
satellitetrackergui.cpp
satellitetrackergui.ui
satellitetrackersettingsdialog.cpp
satellitetrackersettingsdialog.ui
satellitetracker.qrc
satelliteselectiondialog.cpp
satelliteselectiondialog.ui
satelliteradiocontroldialog.cpp
satelliteradiocontroldialog.ui
satellitedevicesettingsgui.cpp
)
set(satellitetracker_HEADERS
${satellitetracker_HEADERS}
satellitetrackergui.h
satellitetrackersettingsdialog.h
satelliteselectiondialog.h
satelliteradiocontroldialog.h
satellitedevicesettingsgui.h
)
set(TARGET_NAME featuresatellitetracker)
set(TARGET_LIB Qt5::Widgets Qt5::Positioning Qt5::Charts Qt5::TextToSpeech)
set(TARGET_LIB_GUI "sdrgui")
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
set(TARGET_NAME featuresatellitetrackersrv)
set(TARGET_LIB "")
set(TARGET_LIB_GUI "")
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
endif()
add_library(${TARGET_NAME} SHARED
${satellitetracker_SOURCES}
)
if(SGP4_EXTERNAL)
add_dependencies(${TARGET_NAME} sgp4)
endif()
target_link_libraries(${TARGET_NAME}
Qt5::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
${SGP4_LIBRARIES}
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
if(WIN32)
# Run deployqt for Charts and TextToSpeech etc
include(DeployQt)
windeployqt(${TARGET_NAME} ${SDRANGEL_BINARY_BIN_DIR} ${PROJECT_SOURCE_DIR}/aprs)
endif()

View File

@ -0,0 +1,197 @@
<h1>Satellite Tracker Feature Plugin</h1>
<h2>Introduction</h2>
The Satellite Tracker feature plugin can be used to:
* Track satellites, pointing antennas at them via SDRangel's Rotator Controller Features
* Control SDRangel by loading presets, starting/stopping acqusition and setting center frequenies on AOS (Acquisition of Signal) for each satellite
* Adjust channels' input frequency offset to account for Doppler shift
* Display polar and elevation/azimuth vs time plots for satellite passes
* Display the overhead position of satellites on the Map Feature, along with the ground track of the satellites
* Display a variety of information about the satellite
![Satellite Tracker feature plugin GUI](../../../doc/img/SatelliteTracker_plugin.png)
<h2>Interface</h2>
![Satellite Tracker settings](../../../doc/img/SatelliteTracker_plugin_settings.png)
<h3>1: Start/Stop plugin</h3>
This button starts or stops the satellite tracking. The plugin will only calculate satellite positions or adjust for Doppler when started.
<h3>2: Find satellite on map</h3>
Pressing this button centres the Map Feature (if open) on the target satellite.
<h3>3: Automatically select target on AOS</h3>
When checked, the target satellite will be automatically changed on any selected satellite's AOS, if it is a higher priority than the current target or if the current target satellite is not visible.
Priority is determined by the order the satellites appear in the Satellite Selection dialog.
<h3>4: Update satellite data</h3>
When clicked, the TLE (two line element) files selected in the Settings dialog are downloaded as well as the latest SatNogs satellite database, containing details of satellite's transmitter and receiver frequencies. While downloading, this button will appear green.
Satellite positions can only be predicted with limited accuracy, so without the TLEs need to be updated frequently for accurate positioning. This could be daily, weekly or monthly depending upon the individual satellite. This downloads around 1MB of data.
<h3>5: Show SDRangel Control dialog</h3>
Pressing this button displays the SDRangel Control dialog.
![SDRangel Control dialog](../../../doc/img/SatellitTracker_plugin_control.png)
This dialog determines the actions the Satellite Tracker will take when AOS or LOS occurs for a satellite. First, select a satellite from the dropdown box. Information about the satellites transmit and receive modes should appear in the field at the bottom of the dialog, if available in the SatNogs database.
To perform an action on an SDRangel device set on AOS or LOS, press the + button. This will add a row in the table, allowing you to select:
* The device set that will be controlled. This will list all currently open device sets. You can also type the name of a new device set.
* The preset to load on AOS. This allows preset device settings (E.g. centre frequency) and demodulators to be opened when the satellite becomes visible.
* Which channels Doppler correction should be applied to. The list of channels is taken from the selected preset. Check a channel to enable Doppler correct for that channel. The Doppler correction is applied to the channel's input frequency offset.
* Whether to start acquisition (i.e. start the DDR device) on AOS.
* Whether to start acquisition on LOS.
* Whether and file sinks in the preset should be started on AOS and stopped on LOS. This allows the baseband signal received from the satellite to be recorded to a file.
* Whether to override the centre frequency in the preset. This allows a single preset to be used with multiple satellites.
* A command or script to execute on AOS.
* A command or script to execute on LOS.
Multiple rows can be added, to allow independent control of multiple device sets. To remove a row, select the row by clicking the row number, then press the - button.
<h3>6: Show Satellite Selection dialog</h3>
Pressing this button displays the Satellite Selection dialog.
![Satellite Selection dialog](../../../doc/img/SatellitTracker_plugin_selection.png)
On the left hand side are a list of all available satellites, as determined by the TLE files that have been downloaded. (If none are visible, ensure the TLEs tab of the Satellite Settings dialog (8) contains at least https://db.satnogs.org/api/tle/ and then press the Update satellite data (4) button)
The list of satellites that the Satellite Tracker will track is on the right hand side.
To move satellites from side to side, either double click them, or select them and press the left or right arrows in the middle.
The Satellites to track list is ordered in priority for the auto target feature (3). The change the order, select a satellite in the list and press the up or down arrows to the right.
Satellite information at the bottom of the dialog comes from the SatNogs database: https://db.satnogs.org/
<h3>7: Set latitude and longitude from My Position</h3>
When clicked, it sets the latitude, longitude and height fields to the values from SDRangel's My Position preferences.
<h3>8: Show Settings dialog</h3>
Pressing this button displays the Settings dialog.
![Satellite tracker settings dialog](../../../doc/img/SatellitTracker_plugin_settingsdialog1.png)
On the Settings tab, you can set:
* Height above sea level in metres of the anntenna.
* The prediciton period in days. This limits the maximum number of days ahead for which satellite passes are predicted until.
* The minimum elevation in degrees from the antenna location, which a satellite much reach in order for AOS to be indicated.
* The minimum elevation in degrees from the antenna location, which a satellite much reach in order for a pass to be indicated.
* A time window for which passes must start and end between, to be displayed or acted upon. For example, for day time passes, you could set "must start after" to 8:00 and "must end before" to 18:00. For night time passes, set "must start after" to 20:00 and "must end before" to 6:00.
* The maximum azimuth angle in degrees supported by your rotator. 450 degree support is beneficial for passes that pass through 360/0 degrees, to avoid the rotator having to do a complete rotation mid pass.
* The maximum elevation angle in degrees supported by your rotator. 180 degree support is beneficial for passes that pass through 360/0 degrees, to avoid the rotator having to do a complete rotation mid pass.
* A speech warning to be given on AOS. ${name} will be subsitited with the name of the satellite, ${duration} the pass duration and ${elevation} the maximum elevation of the pass.
* A speech warning to be given on LOS. ${name} will be subsitited with the name of the satellite.
* A command/script to be executed on AOS. This applies to all satellites. It is also possible to set a per-satellite command in the SDRangel Control dialog.
* A command/script to be executed on LOS. This applies to all satellites. It is also possible to set a per-satellite command in the SDRangel Control dialog.
* The Doppler correction period in seconds, which controls how frequently Doppler correction is applied. Which channels have Doppler correction applied is set on a per-channel basis in the SDRangel Control dialog.
![Satellite tracker settings dialog](../../../doc/img/SatellitTracker_plugin_settingsdialog2.png)
On the TLEs tab, you can provide a list of URL from which satellite Two Line Element files can be downloaded from.
TLE files contain the orbital parameters for a satellite and are required in order to be able to calculate a satellites position.
![Satellite tracker settings dialog](../../../doc/img/SatellitTracker_plugin_settingsdialog3.png)
On the display tab, you can set:
* The update period in seconds, which controls how frequently satellite positions are calculated.
* The default frequency in MHz that is used for calculating Doppler and free space path loss in the Satellite Data table.
* The units used to display azimuth and elevation to the target satellite. This can be in degrees, minutes and seconds or decimal degrees.
* The number of points used for ground tracks on the map. More points result in smoother tracks, but require more processing.
* Whether times are display in the local time zone or UTC.
* Whether to draw the satellites on the map.
<h3>9: Latitude/h3>
Specifies the latitude in decimal degrees (North positive) of the antenna location.
<h3>10: Longitude</h3>
Specifies the longitude in decimal degrees (East positive) of the antenna location.
<h3>11: Time</h3>
Select the date and time at which the position of the satellite should be calculated. Select either Now, for the current time, or Custom to manually enter a date and time.
<h3>12: Target</h3>
Select the target satellite. The target satellite is the source of data for the Time to AOS, Azimuth and Elevation fields. The azimuth and elevation of the target satellite is sent to the Rotator Controller features.
<h3>13: Time to AOS</h3>
This field displays the time to AOS (Acquisition of Signal) for the target satellite. It is displayed in hours, minutes and seconds, unless the satellite is currently visible, in which case it will display "Now".
<h3>14: Azimuth</h3>
Displays the calculated azimuth (angle in degrees, clockwise from North) to the target satellite.
<h3>15: Elevation</h3>
Displays the calculated elevation (angle in degrees - 0 to horizon and 90 to zenith) to the target satellite.
<h2>Pass Charts</h2>
Pass charts can be plotted showing the azimuth and elevation of the target satellite from AOS to LOS. This can be in polar of Cartesian form:
![Satellite tracker settings dialog](../../../doc/img/SatellitTracker_plugin_passchart.png)
The arrows next to the chart combobox, allow the pass number to be selected. Pass 0 is the next pass, with higher numbered passes occuring later in time.
The amount of passes is determined by the prediction period, which can be set in the Settings dialog.
<h2>Satellite Data</h2>
The satellite data table displays calculated data about the selected satellites.
![Satellite data table](../../../doc/img/SatellitTracker_plugin_satdata.png)
The table contains:
* The satellite name.
* The azimuth in degrees to the satellite from the antenna location.
* The elevation in degress to the satellite from the antenna location.
* The time of the next AOS. If time is some days in the future, the number of days will be displayed as +days. E.g. +1 for tomorrow.
* The time of the next LOS.
* The maximum elevation in degrees that the satellite will be from the antenna location in the next pass.
* Whether the satellite will be heading South to North (up addow) or North to South (down arrow) in the next pass.
* The altitude of the satellite in kilometres.
* The range to the satellite from the antenna location in kilometers.
* The range range (i.e. speed the satellite is moving away from the antenna location) in kilometres per second.
* The Doppler shift due to the satellite's motion that would be observed on a signal at the default frequency (which can be set in the Settings dialog).
* The free space path loss to the satellite, at the default frequency.
* The one-way propagation delay to the satellite from the antenna location in milliseconds.
* The NORAD catalog identifier for the satellite.
Rows can be ordered by left clicking column headers.
Columns can be hidden by right clicking on the header and unchecking them.
<h2>Map</h2>
The Satellite Tracker feature can send the overhead position of the satellite to the Map, along with a ground track.
When using the Find feature in the Map GUI, you can search by the name of the satellite.
![SatelliteTracker map](../../../doc/img/SatelliteTracker_map.png)
<h2>Attribution</h2>
sgp4 library by Daniel Warner https://github.com/dnwrnr
SatNogs satellite database https://db.satnogs.org/
Satellite two-line elements (TLEs) are from Celestrak https://celestrak.com/
Icons are by Freepik from Flaticon https://www.flaticon.com/

View File

@ -0,0 +1,290 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include <QHBoxLayout>
#include <QSizePolicy>
#include "satellitedevicesettingsgui.h"
#include "device/deviceset.h"
#include "settings/mainsettings.h"
#include "maincore.h"
#include "util/messagequeue.h"
#include "plugin/pluginmanager.h"
#include "plugin/pluginapi.h"
SatelliteDeviceSettingsGUI::SatelliteDeviceSettingsGUI(SatelliteTrackerSettings::SatelliteDeviceSettings *devSettings,
QTableWidget *table)
{
m_devSettings = devSettings;
// Device set
m_deviceSetWidget = new QComboBox();
m_deviceSetWidget->setEditable(true);
m_deviceSetWidget->setToolTip(table->horizontalHeaderItem(SAT_DEVICE_COL_DEVICESET)->toolTip());
m_deviceSetItem = new QWidget();
layout(m_deviceSetItem, m_deviceSetWidget);
addDeviceSets();
int devSetIdx = m_deviceSetWidget->findText(devSettings->m_deviceSet);
if (devSetIdx != -1)
m_deviceSetWidget->setCurrentIndex(devSetIdx);
else
{
m_deviceSetWidget->addItem(devSettings->m_deviceSet);
m_deviceSetWidget->setCurrentIndex(m_deviceSetWidget->count() - 1);
}
// Preset
m_presetWidget = new QComboBox();
m_presetWidget->setEditable(false);
m_presetWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
m_presetWidget->setToolTip(table->horizontalHeaderItem(SAT_DEVICE_COL_PRESET)->toolTip());
m_presetItem = new QWidget();
layout(m_presetItem, m_presetWidget);
addPresets(devSettings->m_deviceSet);
const MainSettings& mainSettings = MainCore::instance()->getSettings();
if (!devSettings->m_deviceSet.isEmpty())
{
int count = mainSettings.getPresetCount();
int idx = 0;
for (int i = 0; i < count; i++)
{
const Preset *preset = mainSettings.getPreset(i);
if ( ((preset->isSourcePreset() && (devSettings->m_deviceSet[0] == "R")))
|| ((preset->isSinkPreset() && (devSettings->m_deviceSet[0] == "T")))
|| ((preset->isMIMOPreset() && (devSettings->m_deviceSet[0] == "M"))))
{
if ( (devSettings->m_presetGroup == preset->getGroup())
&& (devSettings->m_presetFrequency == preset->getCenterFrequency())
&& (devSettings->m_presetDescription == preset->getDescription()))
{
m_presetWidget->setCurrentIndex(idx);
break;
}
idx++;
}
}
}
// Doppler
m_dopplerWidget = new QComboBox();
m_dopplerWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
m_dopplerWidget->setToolTip(table->horizontalHeaderItem(SAT_DEVICE_COL_DOPPLER)->toolTip());
m_dopplerItem = new QWidget();
layout(m_dopplerItem, m_dopplerWidget);
m_dopplerWidget->setModel(&m_dopplerModel);
addChannels();
for (int i = 0; i < devSettings->m_doppler.size(); i++)
m_dopplerItems[devSettings->m_doppler[i]]->setData(Qt::Checked, Qt::CheckStateRole);
// Start on AOS
m_startOnAOSWidget = new QCheckBox();
m_startOnAOSWidget->setChecked(devSettings->m_startOnAOS);
m_startOnAOSWidget->setToolTip(table->horizontalHeaderItem(SAT_DEVICE_COL_START)->toolTip());
m_startOnAOSItem = new QWidget();
layout(m_startOnAOSItem, m_startOnAOSWidget);
// Stop on AOS
m_stopOnLOSWidget = new QCheckBox();
m_stopOnLOSWidget->setChecked(devSettings->m_stopOnLOS);
m_stopOnLOSWidget->setToolTip(table->horizontalHeaderItem(SAT_DEVICE_COL_STOP)->toolTip());
m_stopOnLOSItem = new QWidget();
layout(m_stopOnLOSItem, m_stopOnLOSWidget);
// Start file sink
m_startStopFileSinkWidget = new QCheckBox();
m_startStopFileSinkWidget->setChecked(devSettings->m_startStopFileSink);
m_startStopFileSinkWidget->setToolTip(table->horizontalHeaderItem(SAT_DEVICE_COL_START_FILE_SINK)->toolTip());
m_startStopFileSinkItem = new QWidget();
layout(m_startStopFileSinkItem, m_startStopFileSinkWidget);
// Frequency override
m_frequencyItem = new QTableWidgetItem();
m_frequencyItem->setToolTip(table->horizontalHeaderItem(SAT_DEVICE_COL_FREQUENCY)->toolTip());
if (devSettings->m_frequency != 0)
m_frequencyItem->setData(Qt::DisplayRole, QString("%1").arg(devSettings->m_frequency/1000000.0, 0, 'f', 3, QLatin1Char(' ')));
// AOS command
m_aosCommandItem = new QTableWidgetItem();
m_aosCommandItem->setText(devSettings->m_aosCommand);
m_aosCommandItem->setToolTip(table->horizontalHeaderItem(SAT_DEVICE_COL_AOS_COMMAND)->toolTip());
// LOS command
m_losCommandItem = new QTableWidgetItem();
m_losCommandItem->setText(devSettings->m_losCommand);
m_losCommandItem->setToolTip(table->horizontalHeaderItem(SAT_DEVICE_COL_LOS_COMMAND)->toolTip());
int row = table->rowCount();
table->setRowCount(row + 1);
table->setCellWidget(row, SAT_DEVICE_COL_DEVICESET, m_deviceSetItem);
table->setCellWidget(row, SAT_DEVICE_COL_PRESET, m_presetItem);
table->setCellWidget(row, SAT_DEVICE_COL_DOPPLER, m_dopplerItem);
table->setCellWidget(row, SAT_DEVICE_COL_START, m_startOnAOSItem);
table->setCellWidget(row, SAT_DEVICE_COL_STOP, m_stopOnLOSItem);
table->setCellWidget(row, SAT_DEVICE_COL_START_FILE_SINK, m_startStopFileSinkItem);
table->setItem(row, SAT_DEVICE_COL_FREQUENCY, m_frequencyItem);
table->setItem(row, SAT_DEVICE_COL_AOS_COMMAND, m_aosCommandItem);
table->setItem(row, SAT_DEVICE_COL_LOS_COMMAND, m_losCommandItem);
table->resizeColumnsToContents();
connect(m_deviceSetWidget, SIGNAL(currentTextChanged(const QString &)), this, SLOT(on_m_deviceSetWidget_currentTextChanged(const QString &)));
connect(m_presetWidget, SIGNAL(currentIndexChanged(int)), this, SLOT(on_m_presetWidget_currentIndexChanged(int)));
}
void SatelliteDeviceSettingsGUI::layout(QWidget *parent, QWidget *child)
{
QHBoxLayout* pLayout = new QHBoxLayout(parent);
pLayout->addWidget(child);
pLayout->setAlignment(Qt::AlignCenter);
pLayout->setContentsMargins(0, 0, 0, 0);
parent->setLayout(pLayout);
}
// Add available devicesets to the combobox
void SatelliteDeviceSettingsGUI::addDeviceSets()
{
MainCore *mainCore = MainCore::instance();
std::vector<DeviceSet*>& deviceSets = mainCore->getDeviceSets();
std::vector<DeviceSet*>::const_iterator it = deviceSets.begin();
for (unsigned int deviceIndex = 0; it != deviceSets.end(); ++it, deviceIndex++)
{
DSPDeviceSourceEngine *deviceSourceEngine = (*it)->m_deviceSourceEngine;
DSPDeviceSinkEngine *deviceSinkEngine = (*it)->m_deviceSinkEngine;
if (deviceSourceEngine) {
m_deviceSetWidget->addItem(QString("R%1").arg(deviceIndex), deviceIndex);
} else if (deviceSinkEngine) {
m_deviceSetWidget->addItem(QString("T%1").arg(deviceIndex), deviceIndex);
}
}
}
// Add all available presets for a deviceset to the combobox
void SatelliteDeviceSettingsGUI::addPresets(const QString& deviceSet)
{
m_presetWidget->clear();
const MainSettings& mainSettings = MainCore::instance()->getSettings();
int count = mainSettings.getPresetCount();
m_currentPresets = deviceSet[0];
for (int i = 0; i < count; i++)
{
const Preset *preset = mainSettings.getPreset(i);
if ( ((preset->isSourcePreset() && (m_currentPresets == "R")))
|| ((preset->isSinkPreset() && (m_currentPresets == "T")))
|| ((preset->isMIMOPreset() && (m_currentPresets == "M"))))
{
m_presetWidget->addItem(QString("%1: %2 - %3")
.arg(preset->getGroup())
.arg(preset->getCenterFrequency()/1000000.0, 0, 'f', 3)
.arg(preset->getDescription()));
}
}
}
const Preset* SatelliteDeviceSettingsGUI::getSelectedPreset()
{
int listIdx = m_presetWidget->currentIndex();
const MainSettings& mainSettings = MainCore::instance()->getSettings();
int count = mainSettings.getPresetCount();
int presetIdx = 0;
for (int i = 0; i < count; i++)
{
const Preset *preset = mainSettings.getPreset(i);
if ( ((preset->isSourcePreset() && (m_currentPresets == "R")))
|| ((preset->isSinkPreset() && (m_currentPresets == "T")))
|| ((preset->isMIMOPreset() && (m_currentPresets == "M"))))
{
if (listIdx == presetIdx)
return preset;
presetIdx++;
}
}
return nullptr;
}
// Add checkable list of channels from a preset to the combobox
void SatelliteDeviceSettingsGUI::addChannels()
{
m_dopplerModel.clear();
m_dopplerItems.clear();
const PluginManager *pluginManager = MainCore::instance()->getPluginManager();
const Preset* preset = getSelectedPreset();
if (preset != nullptr)
{
int channels = preset->getChannelCount();
for (int i = 0; i < channels; i++)
{
const Preset::ChannelConfig& channelConfig = preset->getChannelConfig(i);
QStandardItem *item = new QStandardItem();
item->setText(pluginManager->uriToId(channelConfig.m_channelIdURI));
item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
item->setData(Qt::Unchecked, Qt::CheckStateRole);
m_dopplerModel.appendRow(item);
m_dopplerItems.append(item);
}
}
}
// Update preset list, to match type of deviceset entered
void SatelliteDeviceSettingsGUI::on_m_deviceSetWidget_currentTextChanged(const QString &text)
{
if (!text.isEmpty())
{
if (text[0] != m_currentPresets)
addPresets(text[0]);
}
}
// Update doppler combo, to correspond to selected preset
void SatelliteDeviceSettingsGUI::on_m_presetWidget_currentIndexChanged(int index)
{
addChannels();
}
// Update devSettings with current GUI values
void SatelliteDeviceSettingsGUI::accept()
{
m_devSettings->m_deviceSet = m_deviceSetWidget->currentText();
const Preset* preset = getSelectedPreset();
if (preset != nullptr)
{
m_devSettings->m_presetGroup = preset->getGroup();
m_devSettings->m_presetFrequency = preset->getCenterFrequency();
m_devSettings->m_presetDescription = preset->getDescription();
}
else
{
m_devSettings->m_presetGroup = "";
m_devSettings->m_presetFrequency = 0;
m_devSettings->m_presetDescription = "";
}
m_devSettings->m_doppler.clear();
for (int i = 0; i < m_dopplerItems.size(); i++)
{
if (m_dopplerItems[i]->checkState() == Qt::Checked)
m_devSettings->m_doppler.append(i);
}
m_devSettings->m_startOnAOS = m_startOnAOSWidget->isChecked();
m_devSettings->m_stopOnLOS = m_stopOnLOSWidget->isChecked();
m_devSettings->m_startStopFileSink = m_startStopFileSinkWidget->isChecked();
m_devSettings->m_frequency = (quint64)(m_frequencyItem->data(Qt::DisplayRole).toDouble() * 1000000.0);
m_devSettings->m_aosCommand = m_aosCommandItem->text();
m_devSettings->m_losCommand = m_losCommandItem->text();
}

View File

@ -0,0 +1,94 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FEATURE_SATELLITEDEVICESETTINGSGUI_H
#define INCLUDE_FEATURE_SATELLITEDEVICESETTINGSGUI_H
#include <QComboBox>
#include <QCheckBox>
#include <QTableWidget>
#include <QTableWidgetItem>
#include <QStandardItemModel>
#include <QList>
#include "settings/preset.h"
#include "satellitetrackersettings.h"
class SatelliteRadioControlDialog;
class SatelliteDeviceSettingsGUI : public QObject
{
Q_OBJECT
public:
SatelliteDeviceSettingsGUI(SatelliteTrackerSettings::SatelliteDeviceSettings *devSettings,
QTableWidget *table);
void accept();
protected:
void layout(QWidget *parent, QWidget *child);
void addDeviceSets();
void addPresets(const QString& deviceSet);
void addChannels();
const Preset *getSelectedPreset();
private slots:
void on_m_deviceSetWidget_currentTextChanged(const QString &text);
void on_m_presetWidget_currentIndexChanged(int index);
protected:
friend SatelliteRadioControlDialog;
QWidget *m_deviceSetItem;
QComboBox *m_deviceSetWidget;
QWidget *m_presetItem;
QComboBox *m_presetWidget;
QWidget *m_dopplerItem;
QComboBox *m_dopplerWidget;
QWidget *m_startOnAOSItem;
QCheckBox *m_startOnAOSWidget;
QWidget *m_stopOnLOSItem;
QCheckBox *m_stopOnLOSWidget;
QWidget *m_startStopFileSinkItem;
QCheckBox *m_startStopFileSinkWidget;
QTableWidgetItem *m_frequencyItem;
QTableWidgetItem *m_aosCommandItem;
QTableWidgetItem *m_losCommandItem;
QChar m_currentPresets;
QStandardItemModel m_dopplerModel;
QList<QStandardItem *> m_dopplerItems;
SatelliteTrackerSettings::SatelliteDeviceSettings *m_devSettings;
enum SatDeviceCol {
SAT_DEVICE_COL_DEVICESET,
SAT_DEVICE_COL_PRESET,
SAT_DEVICE_COL_DOPPLER,
SAT_DEVICE_COL_START,
SAT_DEVICE_COL_STOP,
SAT_DEVICE_COL_START_FILE_SINK,
SAT_DEVICE_COL_FREQUENCY,
SAT_DEVICE_COL_AOS_COMMAND,
SAT_DEVICE_COL_LOS_COMMAND
};
};
#endif // INCLUDE_FEATURE_SATELLITEDEVICESETTINGSGUI_H

View File

@ -0,0 +1,147 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include <QComboBox>
#include <QPushButton>
#include <QCheckBox>
#include "device/deviceset.h"
#include "settings/mainsettings.h"
#include "settings/preset.h"
#include "maincore.h"
#include "util/messagequeue.h"
#include "satelliteradiocontroldialog.h"
SatelliteRadioControlDialog::SatelliteRadioControlDialog(SatelliteTrackerSettings *settings,
const QHash<QString, SatNogsSatellite *>& satellites,
QWidget* parent) :
QDialog(parent),
m_settings(settings),
m_satellites(satellites),
ui(new Ui::SatelliteRadioControlDialog)
{
ui->setupUi(this);
// Must resize before setting m_deviceSettings
resizeTable();
m_deviceSettings = m_settings->m_deviceSettings;
for (int i = 0; i < settings->m_satellites.size(); i++)
ui->satelliteSelect->addItem(settings->m_satellites[i]);
}
SatelliteRadioControlDialog::~SatelliteRadioControlDialog()
{
delete ui;
}
void SatelliteRadioControlDialog::accept()
{
for (int i = 0; i < m_devSettingsGUIs.size(); i++)
m_devSettingsGUIs[i]->accept();
QDialog::accept();
m_settings->m_deviceSettings = m_deviceSettings;
}
void SatelliteRadioControlDialog::resizeTable()
{
on_add_clicked();
ui->table->resizeColumnsToContents();
ui->table->selectRow(0);
on_remove_clicked();
ui->table->selectRow(-1);
}
void SatelliteRadioControlDialog::on_add_clicked()
{
QString name = ui->satelliteSelect->currentText();
if (!name.isEmpty())
{
SatelliteTrackerSettings::SatelliteDeviceSettings *devSettings = new SatelliteTrackerSettings::SatelliteDeviceSettings();
SatelliteDeviceSettingsGUI *devSettingsGUI = new SatelliteDeviceSettingsGUI(devSettings, ui->table);
m_devSettingsGUIs.append(devSettingsGUI);
QList<SatelliteTrackerSettings::SatelliteDeviceSettings *> *devSettingsList = m_deviceSettings.value(name);
devSettingsList->append(devSettings);
}
}
// Remove selected row
void SatelliteRadioControlDialog::on_remove_clicked()
{
// Selection mode is single, so only a single row should be returned
QModelIndexList indexList = ui->table->selectionModel()->selectedRows();
if (!indexList.isEmpty())
{
int row = indexList.at(0).row();
ui->table->removeRow(row);
delete m_devSettingsGUIs.takeAt(row);
QString name = ui->satelliteSelect->currentText();
QList<SatelliteTrackerSettings::SatelliteDeviceSettings *> *devSettingsList = m_deviceSettings.value(name);
delete devSettingsList->takeAt(row);
}
}
void SatelliteRadioControlDialog::on_satelliteSelect_currentIndexChanged(int index)
{
// Save details from current GUI elements
for (int i = 0; i < m_devSettingsGUIs.size(); i++)
m_devSettingsGUIs[i]->accept();
// Clear GUI
ui->table->setRowCount(0);
qDeleteAll(m_devSettingsGUIs);
m_devSettingsGUIs.clear();
// Create settings list for newly selected satellite, if one doesn't already exist
QString name = ui->satelliteSelect->currentText();
if (!m_deviceSettings.contains(name))
m_deviceSettings.insert(name, new QList<SatelliteTrackerSettings::SatelliteDeviceSettings *>());
// Add existing settings to GUI
QList<SatelliteTrackerSettings::SatelliteDeviceSettings *> *devSettingsList = m_deviceSettings.value(name);
for (int i = 0; i < devSettingsList->size(); i++)
{
SatelliteDeviceSettingsGUI *devSettingsGUI = new SatelliteDeviceSettingsGUI(devSettingsList->at(i), ui->table);
m_devSettingsGUIs.append(devSettingsGUI);
}
// Display modes for the satellite, to help user select appropriate presets
SatNogsSatellite *sat = m_satellites[name];
QStringList info;
for (int i = 0; i < sat->m_transmitters.size(); i++)
{
if (sat->m_transmitters[i]->m_status != "invalid")
{
QStringList mode;
mode.append(" ");
mode.append(sat->m_transmitters[i]->m_description);
if (sat->m_transmitters[i]->m_downlinkHigh > 0)
mode.append(QString("D: %1").arg(SatNogsTransmitter::getFrequencyRangeText(sat->m_transmitters[i]->m_downlinkLow, sat->m_transmitters[i]->m_downlinkHigh)));
else if (sat->m_transmitters[i]->m_downlinkLow > 0)
mode.append(QString("D: %1").arg(SatNogsTransmitter::getFrequencyText(sat->m_transmitters[i]->m_downlinkLow)));
if (sat->m_transmitters[i]->m_uplinkHigh > 0)
mode.append(QString("U: %1").arg(SatNogsTransmitter::getFrequencyRangeText(sat->m_transmitters[i]->m_uplinkLow, sat->m_transmitters[i]->m_uplinkHigh)));
else if (sat->m_transmitters[i]->m_uplinkLow > 0)
mode.append(QString("U: %1").arg(SatNogsTransmitter::getFrequencyText(sat->m_transmitters[i]->m_uplinkLow)));
info.append(mode.join(" "));
}
}
ui->satelliteModes->setText(info.join("\n"));
}

View File

@ -0,0 +1,53 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FEATURE_SATELLITERADIOCONTROLDIALOG_H
#define INCLUDE_FEATURE_SATELLITERADIOCONTROLDIALOG_H
#include <QHash>
#include "ui_SatelliteRadioControlDialog.h"
#include "satellitetrackersettings.h"
#include "satellitedevicesettingsgui.h"
#include "satnogs.h"
class SatelliteRadioControlDialog : public QDialog {
Q_OBJECT
public:
explicit SatelliteRadioControlDialog(SatelliteTrackerSettings* settings, const QHash<QString, SatNogsSatellite *>& satellites, QWidget* parent = 0);
~SatelliteRadioControlDialog();
SatelliteTrackerSettings *m_settings;
private:
void resizeTable();
private slots:
void accept();
void on_add_clicked();
void on_remove_clicked();
void on_satelliteSelect_currentIndexChanged(int index);
private:
const QHash<QString, SatNogsSatellite *>& m_satellites;
QHash<QString, QList<SatelliteTrackerSettings::SatelliteDeviceSettings *> *> m_deviceSettings; // Device settings per sateillite
QList<SatelliteDeviceSettingsGUI *> m_devSettingsGUIs; // For selected satellite
Ui::SatelliteRadioControlDialog* ui;
};
#endif // INCLUDE_FEATURE_SATELLITERADIOCONTROLDIALOG_H

View File

@ -0,0 +1,259 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SatelliteRadioControlDialog</class>
<widget class="QDialog" name="SatelliteRadioControlDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>955</width>
<height>400</height>
</rect>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="contextMenuPolicy">
<enum>Qt::PreventContextMenu</enum>
</property>
<property name="windowTitle">
<string>Satellite Radio Control</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<layout class="QGridLayout" name="gridLayout">
<item row="5" column="0">
<widget class="QTableWidget" name="table">
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<column>
<property name="text">
<string>Device set</string>
</property>
<property name="toolTip">
<string>Device set to control</string>
</property>
</column>
<column>
<property name="text">
<string>Preset to load on AOS</string>
</property>
<property name="toolTip">
<string>Preset to load to device set</string>
</property>
</column>
<column>
<property name="text">
<string>Doppler correction</string>
</property>
<property name="toolTip">
<string>Channel numbers that will have Doppler correction applied</string>
</property>
</column>
<column>
<property name="text">
<string>Start on AOS</string>
</property>
<property name="toolTip">
<string>Start acquisition on AOS</string>
</property>
</column>
<column>
<property name="text">
<string>Stop on LOS</string>
</property>
<property name="toolTip">
<string>Stop acquisition on LOS</string>
</property>
</column>
<column>
<property name="text">
<string>Start/stop file sinks</string>
</property>
<property name="toolTip">
<string>Start file sinks recording on AOS and stop recording on LOS</string>
</property>
</column>
<column>
<property name="text">
<string>Override frequency (MHz)</string>
</property>
<property name="toolTip">
<string>Override the center frequency in the preset with a value specified here in MHz.
This allows a single preset to be shared between different satellites that differ only in frequency.</string>
</property>
</column>
<column>
<property name="text">
<string>AOS command</string>
</property>
<property name="toolTip">
<string>Command to execute on AOS</string>
</property>
</column>
<column>
<property name="text">
<string>LOS command</string>
</property>
<property name="toolTip">
<string>Command to execute on LOS</string>
</property>
</column>
</widget>
</item>
<item row="8" column="0">
<widget class="QTextEdit" name="satelliteModes">
<property name="toolTip">
<string>Satellite modes from SatNOGS</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<layout class="QHBoxLayout" name="satHorizontalLayout">
<item>
<widget class="QLabel" name="satelliteSelectlabel">
<property name="text">
<string>Satellite</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="satelliteSelect">
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
</widget>
</item>
<item>
<spacer name="satHorizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="6" column="0">
<layout class="QHBoxLayout" name="buttonsHorizontalLayout">
<item>
<widget class="QPushButton" name="add">
<property name="toolTip">
<string>Add device set control</string>
</property>
<property name="text">
<string>+</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="remove">
<property name="toolTip">
<string>Remove device set control</string>
</property>
<property name="text">
<string>-</string>
</property>
</widget>
</item>
<item>
<spacer name="buttonsHorizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item row="7" column="0">
<widget class="QLabel" name="satelliteModesLabel">
<property name="text">
<string>Satellite modes</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>satelliteSelect</tabstop>
<tabstop>table</tabstop>
<tabstop>add</tabstop>
<tabstop>remove</tabstop>
<tabstop>satelliteModes</tabstop>
</tabstops>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>SatelliteRadioControlDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>SatelliteRadioControlDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,281 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QDesktopServices>
#include <OrbitalElements.h>
#include <Tle.h>
#include "satelliteselectiondialog.h"
#include "util/units.h"
SatelliteSelectionDialog::SatelliteSelectionDialog(SatelliteTrackerSettings *settings,
const QHash<QString, SatNogsSatellite *>& satellites,
QWidget* parent) :
QDialog(parent),
m_settings(settings),
m_satellites(satellites),
m_satInfo(nullptr),
ui(new Ui::SatelliteSelectionDialog)
{
ui->setupUi(this);
m_networkManager = new QNetworkAccessManager();
connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
QHashIterator<QString, SatNogsSatellite *> itr(satellites);
while (itr.hasNext())
{
itr.next();
QString name = itr.key();
SatNogsSatellite *sat = itr.value();
// Don't display decayed satellites, or those without TLEs
if ((sat->m_status == "alive") || (sat->m_status == ""))
{
if (sat->m_tle != nullptr)
{
if (settings->m_satellites.indexOf(name) == -1)
ui->availableSats->addItem(name);
}
else
qDebug() << "SatelliteSelectionDialog::SatelliteSelectionDialog: No TLE for " << name;
}
}
for (int i = 0; i < settings->m_satellites.size(); i++)
ui->selectedSats->addItem(settings->m_satellites[i]);
}
SatelliteSelectionDialog::~SatelliteSelectionDialog()
{
delete ui;
}
void SatelliteSelectionDialog::accept()
{
m_settings->m_satellites.clear();
for (int i = 0; i < ui->selectedSats->count(); i++)
m_settings->m_satellites.append(ui->selectedSats->item(i)->text());
QDialog::accept();
}
void SatelliteSelectionDialog::on_find_textChanged(const QString &text)
{
QString textTrimmed = text.trimmed();
QList<QListWidgetItem *> items = ui->availableSats->findItems(textTrimmed, Qt::MatchContains);
if (items.size() > 0)
ui->availableSats->setCurrentItem(items[0]);
else
{
// Try alternative names
QHashIterator<QString, SatNogsSatellite *> itr(m_satellites);
while (itr.hasNext())
{
itr.next();
SatNogsSatellite *sat = itr.value();
if (sat->m_names.indexOf(textTrimmed) != -1)
{
QList<QListWidgetItem *> items = ui->availableSats->findItems(sat->m_name, Qt::MatchExactly);
if (items.size() > 0)
ui->availableSats->setCurrentItem(items[0]);
break;
}
}
}
}
void SatelliteSelectionDialog::on_addSat_clicked()
{
QList<QListWidgetItem *> items = ui->availableSats->selectedItems();
for (int i = 0; i < items.size(); i++)
{
ui->selectedSats->addItem(items[i]->text());
delete items[i];
}
}
void SatelliteSelectionDialog::on_removeSat_clicked()
{
QList<QListWidgetItem *> items = ui->selectedSats->selectedItems();
for (int i = 0; i < items.size(); i++)
{
ui->availableSats->addItem(items[i]->text());
delete items[i];
}
}
void SatelliteSelectionDialog::on_moveUp_clicked()
{
QList<QListWidgetItem *> items = ui->selectedSats->selectedItems();
for (int i = 0; i < items.size(); i++)
{
int row = ui->selectedSats->row(items[i]);
if (row > 0)
{
QListWidgetItem *item = ui->selectedSats->takeItem(row);
ui->selectedSats->insertItem(row - 1, item);
ui->selectedSats->setCurrentItem(item);
}
}
}
void SatelliteSelectionDialog::on_moveDown_clicked()
{
QList<QListWidgetItem *> items = ui->selectedSats->selectedItems();
for (int i = items.size() - 1; i >= 0; i--)
{
int row = ui->selectedSats->row(items[i]);
if (row < ui->selectedSats->count() - 1)
{
QListWidgetItem *item = ui->selectedSats->takeItem(row);
ui->selectedSats->insertItem(row + 1, item);
ui->selectedSats->setCurrentItem(item);
}
}
}
void SatelliteSelectionDialog::on_availableSats_itemDoubleClicked(QListWidgetItem *item)
{
ui->selectedSats->addItem(item->text());
delete item;
}
void SatelliteSelectionDialog::on_selectedSats_itemDoubleClicked(QListWidgetItem *item)
{
ui->availableSats->addItem(item->text());
delete item;
}
void SatelliteSelectionDialog::on_availableSats_itemSelectionChanged()
{
QList<QListWidgetItem *> items = ui->availableSats->selectedItems();
if (items.size() > 0)
{
ui->selectedSats->selectionModel()->clear();
displaySatInfo(items[0]->text());
}
}
void SatelliteSelectionDialog::on_selectedSats_itemSelectionChanged()
{
QList<QListWidgetItem *> items = ui->selectedSats->selectedItems();
if (items.size() > 0)
{
ui->availableSats->selectionModel()->clear();
displaySatInfo(items[0]->text());
}
}
// Display information about the satellite from the SatNOGS database
void SatelliteSelectionDialog::displaySatInfo(const QString& name)
{
SatNogsSatellite *sat = m_satellites[name];
m_satInfo = sat;
QStringList info;
info.append(QString("Name: %1").arg(sat->m_name));
if (sat->m_names.size() > 0)
info.append(QString("Alternative names: %1").arg(sat->m_names.join(" ")));
info.append(QString("NORAD ID: %1").arg(sat->m_noradCatId));
if (sat->m_launched.isValid())
info.append(QString("Launched: %1").arg(sat->m_launched.toString()));
if (sat->m_deployed.isValid())
info.append(QString("Deployed: %1").arg(sat->m_deployed.toString()));
if (sat->m_decayed.isValid())
info.append(QString("Decayed: %1").arg(sat->m_decayed.toString()));
ui->openSatelliteWebsite->setEnabled(!sat->m_website.isEmpty());
if (!sat->m_operator.isEmpty() && sat->m_operator != "None")
info.append(QString("Operator: %1").arg(sat->m_operator));
if (!sat->m_countries.isEmpty())
info.append(QString("Countries: %1").arg(sat->m_countries));
if (sat->m_transmitters.size() > 0)
info.append("Modes:");
for (int i = 0; i < sat->m_transmitters.size(); i++)
{
if (sat->m_transmitters[i]->m_status != "invalid")
{
QStringList mode;
mode.append(" ");
mode.append(sat->m_transmitters[i]->m_description);
if (sat->m_transmitters[i]->m_downlinkHigh > 0)
mode.append(QString("D: %1").arg(SatNogsTransmitter::getFrequencyRangeText(sat->m_transmitters[i]->m_downlinkLow, sat->m_transmitters[i]->m_downlinkHigh)));
else if (sat->m_transmitters[i]->m_downlinkLow > 0)
mode.append(QString("D: %1").arg(SatNogsTransmitter::getFrequencyText(sat->m_transmitters[i]->m_downlinkLow)));
if (sat->m_transmitters[i]->m_uplinkHigh > 0)
mode.append(QString("U: %1").arg(SatNogsTransmitter::getFrequencyRangeText(sat->m_transmitters[i]->m_uplinkLow, sat->m_transmitters[i]->m_uplinkHigh)));
else if (sat->m_transmitters[i]->m_uplinkLow > 0)
mode.append(QString("U: %1").arg(SatNogsTransmitter::getFrequencyText(sat->m_transmitters[i]->m_uplinkLow)));
info.append(mode.join(" "));
}
}
if (sat->m_tle != nullptr)
{
info.append("Orbit:");
Tle tle = Tle(sat->m_tle->m_tle0.toStdString(),
sat->m_tle->m_tle1.toStdString(),
sat->m_tle->m_tle2.toStdString());
OrbitalElements ele(tle);
info.append(QString(" Period: %1 mins").arg(ele.Period()));
info.append(QString(" Inclination: %1%2").arg(Units::radiansToDegrees(ele.Inclination())).arg(QChar(0xb0)));
info.append(QString(" Eccentricity: %1").arg(ele.Eccentricity()));
}
ui->satInfo->setText(info.join("\n"));
if (!sat->m_image.isEmpty())
m_networkManager->get(QNetworkRequest(QUrl(sat->m_image)));
else
ui->satImage->setPixmap(QPixmap());
}
// Open the Satellite's webpage
void SatelliteSelectionDialog::on_openSatelliteWebsite_clicked()
{
if ((m_satInfo != nullptr) && (!m_satInfo->m_website.isEmpty()))
QDesktopServices::openUrl(QUrl(m_satInfo->m_website));
}
// Open SatNOGS observations website for the selected satellite
void SatelliteSelectionDialog::on_openSatNogsObservations_clicked()
{
if (m_satInfo != nullptr)
QDesktopServices::openUrl(QUrl(QString("https://network.satnogs.org/observations/?norad=%1").arg(m_satInfo->m_noradCatId)));
}
void SatelliteSelectionDialog::networkManagerFinished(QNetworkReply *reply)
{
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "SatelliteSelectionDialog::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
}
else
{
// Read image data and display it
QByteArray imageData = reply->readAll();
QPixmap pixmap;
if (pixmap.loadFromData(imageData))
ui->satImage->setPixmap(pixmap.scaled( ui->satImage->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation));
else
qDebug() << "SatelliteSelectionDialog::networkManagerFinished: Failed to load pixmap from image data";
}
reply->deleteLater();
}

View File

@ -0,0 +1,65 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_SATELLITESELECTIONDIALOG_H
#define INCLUDE_SATELLITESELECTIONDIALOG_H
#include <QHash>
#include <QNetworkRequest>
#include "ui_satelliteselectiondialog.h"
#include "satellitetrackersettings.h"
#include "satnogs.h"
class QNetworkAccessManager;
class QNetworkReply;
class SatelliteSelectionDialog : public QDialog {
Q_OBJECT
public:
explicit SatelliteSelectionDialog(SatelliteTrackerSettings* settings, const QHash<QString, SatNogsSatellite *>& satellites, QWidget* parent = 0);
~SatelliteSelectionDialog();
SatelliteTrackerSettings *m_settings;
private:
void displaySatInfo(const QString& name);
private slots:
void accept();
void on_find_textChanged(const QString &text);
void on_addSat_clicked();
void on_removeSat_clicked();
void on_moveUp_clicked();
void on_moveDown_clicked();
void on_availableSats_itemDoubleClicked(QListWidgetItem *item);
void on_selectedSats_itemDoubleClicked(QListWidgetItem *item);
void on_availableSats_itemSelectionChanged();
void on_selectedSats_itemSelectionChanged();
void on_openSatelliteWebsite_clicked();
void on_openSatNogsObservations_clicked();
void networkManagerFinished(QNetworkReply *reply);
private:
QNetworkAccessManager *m_networkManager;
const QHash<QString, SatNogsSatellite *>& m_satellites;
SatNogsSatellite *m_satInfo;
Ui::SatelliteSelectionDialog* ui;
};
#endif // INCLUDE_SATELLITESELECTIONDIALOG_H

View File

@ -0,0 +1,357 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SatelliteSelectionDialog</class>
<widget class="QDialog" name="SatelliteSelectionDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>696</width>
<height>561</height>
</rect>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>Select satellites to track</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="satSelectionGroup">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Satellite selection</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="availableSatsLabel">
<property name="text">
<string>Available satellites</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="selectedSatsLabel">
<property name="text">
<string>Satellites to track</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="satSelectionHorizontalLayout">
<item>
<widget class="QListWidget" name="availableSats">
<property name="toolTip">
<string>List of available satellites. Double click or press right arrow to move to selected list.</string>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="leftRightButtonsVerticalLayout">
<item>
<spacer name="verticalSpacer_7">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="addSat">
<property name="toolTip">
<string>Add satellite to selected list</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/arrow_left.png</normaloff>:/arrow_left.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeSat">
<property name="toolTip">
<string>Remove satellite from selected list</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/arrow_right.png</normaloff>:/arrow_right.png</iconset>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer_8">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="QListWidget" name="selectedSats">
<property name="toolTip">
<string>List of selected satellites. Double click or press left arrow to move to available list. Order according to priority for automatic selection on AOS.</string>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="upDownVerticalLayout">
<item>
<spacer name="verticalSpacer_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="moveUp">
<property name="toolTip">
<string>Move satellite up in list</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/arrow_up.png</normaloff>:/arrow_up.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="moveDown">
<property name="toolTip">
<string>Move satellite down in list</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/arrow_down.png</normaloff>:/arrow_down.png</iconset>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="findHorizontalLayout">
<item>
<widget class="QLabel" name="findLabel">
<property name="text">
<string>Find</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="find">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>150</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Enter name of satellite to find in the available satellites list</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="satInformationGroup">
<property name="title">
<string>Satellite information</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QTextEdit" name="satInfo">
<property name="toolTip">
<string>Information from SatNOGS about the selected satellite</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="satInfoVerticalLayout">
<item>
<widget class="QLabel" name="satImage">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Image of satellite</string>
</property>
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="webSiteButtonsHorizontalLayout">
<item>
<widget class="QPushButton" name="openSatelliteWebsite">
<property name="toolTip">
<string>Display website for the satellite</string>
</property>
<property name="text">
<string>Satellite website</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="openSatNogsObservations">
<property name="toolTip">
<string>Display SatNOGS observations of the satellite</string>
</property>
<property name="text">
<string>SatNOGS observations</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>SatelliteSelectionDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>SatelliteSelectionDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,883 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QDebug>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QBuffer>
#include "SWGFeatureSettings.h"
#include "SWGFeatureReport.h"
#include "SWGFeatureActions.h"
#include "SWGDeviceState.h"
#include "dsp/dspengine.h"
#include "util/httpdownloadmanager.h"
#include "satellitetrackerworker.h"
#include "satellitetracker.h"
MESSAGE_CLASS_DEFINITION(SatelliteTracker::MsgConfigureSatelliteTracker, Message)
MESSAGE_CLASS_DEFINITION(SatelliteTracker::MsgStartStop, Message)
MESSAGE_CLASS_DEFINITION(SatelliteTracker::MsgUpdateSatData, Message)
MESSAGE_CLASS_DEFINITION(SatelliteTracker::MsgSatData, Message)
const char* const SatelliteTracker::m_featureIdURI = "sdrangel.feature.satellitetracker";
const char* const SatelliteTracker::m_featureId = "SatelliteTracker";
SatelliteTracker::SatelliteTracker(WebAPIAdapterInterface *webAPIAdapterInterface) :
Feature(m_featureIdURI, webAPIAdapterInterface),
m_updatingSatData(false),
m_tleIndex(0),
m_firstUpdateSatData(true)
{
qDebug("SatelliteTracker::SatelliteTracker: webAPIAdapterInterface: %p", webAPIAdapterInterface);
setObjectName(m_featureId);
m_worker = new SatelliteTrackerWorker(this, webAPIAdapterInterface);
m_state = StIdle;
m_errorMessage = "SatelliteTracker error";
m_networkManager = new QNetworkAccessManager();
connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
connect(&m_dlm, &HttpDownloadManager::downloadComplete, this, &SatelliteTracker::downloadFinished);
if (!readSatData())
updateSatData();
}
SatelliteTracker::~SatelliteTracker()
{
if (m_worker->isRunning()) {
stop();
}
delete m_worker;
}
void SatelliteTracker::start()
{
qDebug("SatelliteTracker::start");
m_worker->reset();
m_worker->setMessageQueueToFeature(getInputMessageQueue());
m_worker->setMessageQueueToGUI(getMessageQueueToGUI());
bool ok = m_worker->startWork();
m_state = ok ? StRunning : StError;
m_thread.start();
m_worker->getInputMessageQueue()->push(SatelliteTrackerWorker::MsgConfigureSatelliteTrackerWorker::create(m_settings, true));
m_worker->getInputMessageQueue()->push(MsgSatData::create(m_satellites));
}
void SatelliteTracker::stop()
{
qDebug("SatelliteTracker::stop");
m_worker->stopWork();
m_state = StIdle;
m_thread.quit();
m_thread.wait();
}
bool SatelliteTracker::handleMessage(const Message& cmd)
{
if (MsgConfigureSatelliteTracker::match(cmd))
{
MsgConfigureSatelliteTracker& cfg = (MsgConfigureSatelliteTracker&) cmd;
qDebug() << "SatelliteTracker::handleMessage: MsgConfigureSatelliteTracker";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (MsgStartStop::match(cmd))
{
MsgStartStop& cfg = (MsgStartStop&) cmd;
qDebug() << "SatelliteTracker::handleMessage: MsgStartStop: start:" << cfg.getStartStop();
if (cfg.getStartStop()) {
start();
} else {
stop();
}
return true;
}
else if (MsgUpdateSatData::match(cmd))
{
// When the GUI first opens, it will make an initial request to update the sats
// In the first instance, just return the data we've read
if (m_firstUpdateSatData && (m_satellites.size() > 0))
{
if (m_guiMessageQueue)
m_guiMessageQueue->push(MsgSatData::create(m_satellites));
m_firstUpdateSatData = false;
}
else
updateSatData();
return true;
}
else
{
return false;
}
}
QByteArray SatelliteTracker::serialize() const
{
return m_settings.serialize();
}
bool SatelliteTracker::deserialize(const QByteArray& data)
{
if (m_settings.deserialize(data))
{
MsgConfigureSatelliteTracker *msg = MsgConfigureSatelliteTracker::create(m_settings, true);
m_inputMessageQueue.push(msg);
return true;
}
else
{
m_settings.resetToDefaults();
MsgConfigureSatelliteTracker *msg = MsgConfigureSatelliteTracker::create(m_settings, true);
m_inputMessageQueue.push(msg);
return false;
}
}
void SatelliteTracker::applySettings(const SatelliteTrackerSettings& settings, bool force)
{
bool tlesChanged = false;
qDebug() << "SatelliteTracker::applySettings:"
<< " m_latitude: " << settings.m_latitude
<< " m_longitude: " << settings.m_longitude
<< " m_heightAboveSeaLevel: " << settings.m_heightAboveSeaLevel
<< " m_target: " << settings.m_target
<< " m_satellites: " << settings.m_satellites
<< " m_tles: " << settings.m_tles
<< " m_dateTime: " << settings.m_dateTime
<< " m_minAOSElevation: " << settings.m_minAOSElevation
<< " m_minPassElevation: " << settings.m_minPassElevation
<< " m_azElUnits: " << settings.m_azElUnits
<< " m_groundTrackPoints: " << settings.m_groundTrackPoints
<< " m_dateFormat: " << settings.m_dateFormat
<< " m_utc: " << settings.m_utc
<< " m_updatePeriod: " << settings.m_updatePeriod
<< " m_dopplerPeriod: " << settings.m_dopplerPeriod
<< " m_defaultFrequency: " << settings.m_defaultFrequency
<< " m_drawOnMap: " << settings.m_drawOnMap
<< " m_autoTarget: " << settings.m_autoTarget
<< " m_aosSpeech: " << settings.m_aosSpeech
<< " m_losSpeech: " << settings.m_losSpeech
<< " m_aosCommand: " << settings.m_aosCommand
<< " m_losCommand: " << settings.m_losCommand
<< " m_predictionPeriod: " << settings.m_predictionPeriod
<< " m_passStartTime: " << settings.m_passStartTime
<< " m_passFinishTime: " << settings.m_passFinishTime
<< " m_deviceSettings: " << settings.m_deviceSettings
<< " m_title: " << settings.m_title
<< " m_rgbColor: " << settings.m_rgbColor
<< " m_useReverseAPI: " << settings.m_useReverseAPI
<< " m_reverseAPIAddress: " << settings.m_reverseAPIAddress
<< " m_reverseAPIPort: " << settings.m_reverseAPIPort
<< " m_reverseAPIFeatureSetIndex: " << settings.m_reverseAPIFeatureSetIndex
<< " m_reverseAPIFeatureIndex: " << settings.m_reverseAPIFeatureIndex
<< " force: " << force;
QList<QString> reverseAPIKeys;
if ((m_settings.m_latitude != settings.m_latitude) || force) {
reverseAPIKeys.append("latitude");
}
if ((m_settings.m_longitude != settings.m_longitude) || force) {
reverseAPIKeys.append("longitude");
}
if ((m_settings.m_heightAboveSeaLevel != settings.m_heightAboveSeaLevel) || force) {
reverseAPIKeys.append("heightAboveSeaLevel");
}
if ((m_settings.m_target != settings.m_target) || force) {
reverseAPIKeys.append("target");
}
if ((m_settings.m_satellites != settings.m_satellites) || force) {
reverseAPIKeys.append("satellites");
}
if ((m_settings.m_tles != settings.m_tles) || force) {
tlesChanged = true;
reverseAPIKeys.append("tles");
}
if ((m_settings.m_dateTime != settings.m_dateTime) || force) {
reverseAPIKeys.append("dateTime");
}
if ((m_settings.m_minAOSElevation != settings.m_minAOSElevation) || force) {
reverseAPIKeys.append("minAOSElevation");
}
if ((m_settings.m_minPassElevation != settings.m_minPassElevation) || force) {
reverseAPIKeys.append("minPassElevation");
}
if ((m_settings.m_azElUnits != settings.m_azElUnits) || force) {
reverseAPIKeys.append("azElUnits");
}
if ((m_settings.m_groundTrackPoints != settings.m_groundTrackPoints) || force) {
reverseAPIKeys.append("groundTrackPoints");
}
if ((m_settings.m_dateFormat != settings.m_dateFormat) || force) {
reverseAPIKeys.append("dateFormat");
}
if ((m_settings.m_utc != settings.m_utc) || force) {
reverseAPIKeys.append("utc");
}
if ((m_settings.m_updatePeriod != settings.m_updatePeriod) || force) {
reverseAPIKeys.append("updatePeriod");
}
if ((m_settings.m_dopplerPeriod != settings.m_dopplerPeriod) || force) {
reverseAPIKeys.append("dopplerPeriod");
}
if ((m_settings.m_defaultFrequency != settings.m_defaultFrequency) || force) {
reverseAPIKeys.append("defaultFrequency");
}
if ((m_settings.m_drawOnMap != settings.m_drawOnMap) || force) {
reverseAPIKeys.append("drawOnMap");
}
if ((m_settings.m_autoTarget != settings.m_autoTarget) || force) {
reverseAPIKeys.append("autoTarget");
}
if ((m_settings.m_aosSpeech != settings.m_aosSpeech) || force) {
reverseAPIKeys.append("aosSpeech");
}
if ((m_settings.m_losSpeech != settings.m_losSpeech) || force) {
reverseAPIKeys.append("losSpeech");
}
if ((m_settings.m_aosCommand != settings.m_aosCommand) || force) {
reverseAPIKeys.append("aosCommand");
}
if ((m_settings.m_losCommand != settings.m_losCommand) || force) {
reverseAPIKeys.append("losCommand");
}
if ((m_settings.m_predictionPeriod != settings.m_predictionPeriod) || force) {
reverseAPIKeys.append("predictionPeriod");
}
if ((m_settings.m_passStartTime != settings.m_passStartTime) || force) {
reverseAPIKeys.append("passStartTime");
}
if ((m_settings.m_passFinishTime != settings.m_passFinishTime) || force) {
reverseAPIKeys.append("passFinishTime");
}
if ((m_settings.m_deviceSettings != settings.m_deviceSettings) || force) {
reverseAPIKeys.append("deviceSettings");
}
if ((m_settings.m_title != settings.m_title) || force) {
reverseAPIKeys.append("title");
}
if ((m_settings.m_rgbColor != settings.m_rgbColor) || force) {
reverseAPIKeys.append("rgbColor");
}
SatelliteTrackerWorker::MsgConfigureSatelliteTrackerWorker *msg = SatelliteTrackerWorker::MsgConfigureSatelliteTrackerWorker::create(
settings, force
);
m_worker->getInputMessageQueue()->push(msg);
if (settings.m_useReverseAPI)
{
bool fullUpdate = ((m_settings.m_useReverseAPI != settings.m_useReverseAPI) && settings.m_useReverseAPI) ||
(m_settings.m_reverseAPIAddress != settings.m_reverseAPIAddress) ||
(m_settings.m_reverseAPIPort != settings.m_reverseAPIPort) ||
(m_settings.m_reverseAPIFeatureSetIndex != settings.m_reverseAPIFeatureSetIndex) ||
(m_settings.m_reverseAPIFeatureIndex != settings.m_reverseAPIFeatureIndex);
webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force);
}
m_settings = settings;
if (tlesChanged)
{
// Do we already have the TLE files, or do we need to download them?
bool existing = true;
for (int i = 0; i < m_settings.m_tles.size(); i++)
{
QFile tlesFile(tleURLToFilename(m_settings.m_tles[i]));
if (!tlesFile.exists())
{
existing = false;
break;
}
}
if (existing)
readSatData();
else
updateSatData();
}
}
int SatelliteTracker::webapiRun(bool run,
SWGSDRangel::SWGDeviceState& response,
QString& errorMessage)
{
(void) errorMessage;
getFeatureStateStr(*response.getState());
MsgStartStop *msg = MsgStartStop::create(run);
getInputMessageQueue()->push(msg);
return 202;
}
int SatelliteTracker::webapiSettingsGet(
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setSatelliteTrackerSettings(new SWGSDRangel::SWGSatelliteTrackerSettings());
response.getSatelliteTrackerSettings()->init();
webapiFormatFeatureSettings(response, m_settings);
return 200;
}
int SatelliteTracker::webapiSettingsPutPatch(
bool force,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage)
{
(void) errorMessage;
SatelliteTrackerSettings settings = m_settings;
webapiUpdateFeatureSettings(settings, featureSettingsKeys, response);
MsgConfigureSatelliteTracker *msg = MsgConfigureSatelliteTracker::create(settings, force);
m_inputMessageQueue.push(msg);
qDebug("SatelliteTracker::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureSatelliteTracker *msgToGUI = MsgConfigureSatelliteTracker::create(settings, force);
m_guiMessageQueue->push(msgToGUI);
}
webapiFormatFeatureSettings(response, settings);
return 200;
}
static QList<QString *> *convertStringListToPtrs(QStringList listIn)
{
QList<QString *> *listOut = new QList<QString *>();
for (int i = 0; i < listIn.size(); i++)
listOut->append(new QString(listIn[i]));
return listOut;
}
static QStringList convertPtrsToStringList(QList<QString *> *listIn)
{
QStringList listOut;
for (int i = 0; i < listIn->size(); i++)
listOut.append(*listIn->at(i));
return listOut;
}
void SatelliteTracker::webapiFormatFeatureSettings(
SWGSDRangel::SWGFeatureSettings& response,
const SatelliteTrackerSettings& settings)
{
response.getSatelliteTrackerSettings()->setLatitude(settings.m_latitude);
response.getSatelliteTrackerSettings()->setLongitude(settings.m_longitude);
response.getSatelliteTrackerSettings()->setHeightAboveSeaLevel(settings.m_heightAboveSeaLevel);
response.getSatelliteTrackerSettings()->setTarget(new QString(settings.m_target));
response.getSatelliteTrackerSettings()->setSatellites(convertStringListToPtrs(settings.m_satellites));
response.getSatelliteTrackerSettings()->setTles(convertStringListToPtrs(settings.m_tles));
response.getSatelliteTrackerSettings()->setDateTime(new QString(settings.m_dateTime));
response.getSatelliteTrackerSettings()->setMinAosElevation(settings.m_minAOSElevation);
response.getSatelliteTrackerSettings()->setMinPassElevation(settings.m_minPassElevation);
response.getSatelliteTrackerSettings()->setAzElUnits((int)settings.m_azElUnits);
response.getSatelliteTrackerSettings()->setGroundTrackPoints(settings.m_groundTrackPoints);
response.getSatelliteTrackerSettings()->setDateFormat(new QString(settings.m_dateFormat));
response.getSatelliteTrackerSettings()->setUtc(settings.m_utc);
response.getSatelliteTrackerSettings()->setUpdatePeriod(settings.m_updatePeriod);
response.getSatelliteTrackerSettings()->setDopplerPeriod(settings.m_dopplerPeriod);
response.getSatelliteTrackerSettings()->setDefaultFrequency(settings.m_defaultFrequency);
response.getSatelliteTrackerSettings()->setDrawOnMap(settings.m_drawOnMap);
response.getSatelliteTrackerSettings()->setAutoTarget(settings.m_autoTarget);
response.getSatelliteTrackerSettings()->setAosSpeech(new QString(settings.m_aosSpeech));
response.getSatelliteTrackerSettings()->setLosSpeech(new QString(settings.m_losSpeech));
response.getSatelliteTrackerSettings()->setAosCommand(new QString(settings.m_aosCommand));
response.getSatelliteTrackerSettings()->setLosCommand(new QString(settings.m_losCommand));
response.getSatelliteTrackerSettings()->setPredictionPeriod(settings.m_predictionPeriod);
response.getSatelliteTrackerSettings()->setPassStartTime(new QString(settings.m_passStartTime.toString()));
response.getSatelliteTrackerSettings()->setPassFinishTime(new QString(settings.m_passFinishTime.toString()));
if (response.getSatelliteTrackerSettings()->getTitle()) {
*response.getSatelliteTrackerSettings()->getTitle() = settings.m_title;
} else {
response.getSatelliteTrackerSettings()->setTitle(new QString(settings.m_title));
}
response.getSatelliteTrackerSettings()->setRgbColor(settings.m_rgbColor);
response.getSatelliteTrackerSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
if (response.getSatelliteTrackerSettings()->getReverseApiAddress()) {
*response.getSatelliteTrackerSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
} else {
response.getSatelliteTrackerSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
}
response.getSatelliteTrackerSettings()->setReverseApiPort(settings.m_reverseAPIPort);
response.getSatelliteTrackerSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIFeatureSetIndex);
response.getSatelliteTrackerSettings()->setReverseApiChannelIndex(settings.m_reverseAPIFeatureIndex);
}
void SatelliteTracker::webapiUpdateFeatureSettings(
SatelliteTrackerSettings& settings,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response)
{
if (featureSettingsKeys.contains("latitude")) {
settings.m_latitude = response.getSatelliteTrackerSettings()->getLatitude();
}
if (featureSettingsKeys.contains("longitude")) {
settings.m_longitude = response.getSatelliteTrackerSettings()->getLongitude();
}
if (featureSettingsKeys.contains("heightAboveSeaLevel")) {
settings.m_heightAboveSeaLevel = response.getSatelliteTrackerSettings()->getHeightAboveSeaLevel();
}
if (featureSettingsKeys.contains("target")) {
settings.m_target = *response.getSatelliteTrackerSettings()->getTarget();
}
if (featureSettingsKeys.contains("satellites")) {
settings.m_satellites = convertPtrsToStringList(response.getSatelliteTrackerSettings()->getSatellites());
}
if (featureSettingsKeys.contains("tles")) {
settings.m_tles = convertPtrsToStringList(response.getSatelliteTrackerSettings()->getTles());
}
if (featureSettingsKeys.contains("dateTime")) {
settings.m_dateTime = *response.getSatelliteTrackerSettings()->getDateTime();
}
if (featureSettingsKeys.contains("minAOSElevation")) {
settings.m_minAOSElevation = response.getSatelliteTrackerSettings()->getMinAosElevation();
}
if (featureSettingsKeys.contains("minPassElevation")) {
settings.m_minPassElevation = response.getSatelliteTrackerSettings()->getMinPassElevation();
}
if (featureSettingsKeys.contains("azElUnits")) {
settings.m_azElUnits = (SatelliteTrackerSettings::AzElUnits)response.getSatelliteTrackerSettings()->getAzElUnits();
}
if (featureSettingsKeys.contains("groundTrackPoints")) {
settings.m_groundTrackPoints = response.getSatelliteTrackerSettings()->getGroundTrackPoints();
}
if (featureSettingsKeys.contains("dateFormat")) {
settings.m_dateFormat = *response.getSatelliteTrackerSettings()->getDateFormat();
}
if (featureSettingsKeys.contains("utc")) {
settings.m_utc = response.getSatelliteTrackerSettings()->getUtc() != 0;
}
if (featureSettingsKeys.contains("updatePeriod")) {
settings.m_updatePeriod = response.getSatelliteTrackerSettings()->getUpdatePeriod();
}
if (featureSettingsKeys.contains("dopplerPeriod")) {
settings.m_dopplerPeriod = response.getSatelliteTrackerSettings()->getDopplerPeriod();
}
if (featureSettingsKeys.contains("defaultFrequency")) {
settings.m_defaultFrequency = response.getSatelliteTrackerSettings()->getDefaultFrequency();
}
if (featureSettingsKeys.contains("drawOnMap")) {
settings.m_drawOnMap = response.getSatelliteTrackerSettings()->getDrawOnMap() != 0;
}
if (featureSettingsKeys.contains("autoTarget")) {
settings.m_autoTarget = response.getSatelliteTrackerSettings()->getAutoTarget() != 0;
}
if (featureSettingsKeys.contains("aosSpeech")) {
settings.m_aosSpeech = *response.getSatelliteTrackerSettings()->getAosSpeech();
}
if (featureSettingsKeys.contains("losSpeech")) {
settings.m_losSpeech = *response.getSatelliteTrackerSettings()->getLosSpeech();
}
if (featureSettingsKeys.contains("aosCommand")) {
settings.m_aosCommand = *response.getSatelliteTrackerSettings()->getAosCommand();
}
if (featureSettingsKeys.contains("losCommand")) {
settings.m_losCommand = *response.getSatelliteTrackerSettings()->getLosCommand();
}
if (featureSettingsKeys.contains("predictionPeriod")) {
settings.m_predictionPeriod = response.getSatelliteTrackerSettings()->getPredictionPeriod();
}
if (featureSettingsKeys.contains("passStartTime")) {
settings.m_passStartTime = QTime::fromString(*response.getSatelliteTrackerSettings()->getPassStartTime());
}
if (featureSettingsKeys.contains("passFinishTime")) {
settings.m_passFinishTime = QTime::fromString(*response.getSatelliteTrackerSettings()->getPassFinishTime());
}
if (featureSettingsKeys.contains("title")) {
settings.m_title = *response.getSatelliteTrackerSettings()->getTitle();
}
if (featureSettingsKeys.contains("rgbColor")) {
settings.m_rgbColor = response.getSatelliteTrackerSettings()->getRgbColor();
}
if (featureSettingsKeys.contains("useReverseAPI")) {
settings.m_useReverseAPI = response.getSatelliteTrackerSettings()->getUseReverseApi() != 0;
}
if (featureSettingsKeys.contains("reverseAPIAddress")) {
settings.m_reverseAPIAddress = *response.getSatelliteTrackerSettings()->getReverseApiAddress();
}
if (featureSettingsKeys.contains("reverseAPIPort")) {
settings.m_reverseAPIPort = response.getSatelliteTrackerSettings()->getReverseApiPort();
}
if (featureSettingsKeys.contains("reverseAPIDeviceIndex")) {
settings.m_reverseAPIFeatureSetIndex = response.getSatelliteTrackerSettings()->getReverseApiDeviceIndex();
}
if (featureSettingsKeys.contains("reverseAPIChannelIndex")) {
settings.m_reverseAPIFeatureIndex = response.getSatelliteTrackerSettings()->getReverseApiChannelIndex();
}
}
void SatelliteTracker::webapiReverseSendSettings(QList<QString>& featureSettingsKeys, const SatelliteTrackerSettings& settings, bool force)
{
SWGSDRangel::SWGFeatureSettings *swgFeatureSettings = new SWGSDRangel::SWGFeatureSettings();
// swgFeatureSettings->setOriginatorFeatureIndex(getIndexInDeviceSet());
// swgFeatureSettings->setOriginatorFeatureSetIndex(getDeviceSetIndex());
swgFeatureSettings->setFeatureType(new QString("SatelliteTracker"));
swgFeatureSettings->setSatelliteTrackerSettings(new SWGSDRangel::SWGSatelliteTrackerSettings());
SWGSDRangel::SWGSatelliteTrackerSettings *swgSatelliteTrackerSettings = swgFeatureSettings->getSatelliteTrackerSettings();
// transfer data that has been modified. When force is on transfer all data except reverse API data
if (featureSettingsKeys.contains("latitude") || force) {
swgSatelliteTrackerSettings->setLatitude(settings.m_latitude);
}
if (featureSettingsKeys.contains("longitude") || force) {
swgSatelliteTrackerSettings->setLongitude(settings.m_longitude);
}
if (featureSettingsKeys.contains("heightAboveSeaLevel") || force) {
swgSatelliteTrackerSettings->setHeightAboveSeaLevel(settings.m_heightAboveSeaLevel);
}
if (featureSettingsKeys.contains("target") || force) {
swgSatelliteTrackerSettings->setTarget(new QString(settings.m_target));
}
if (featureSettingsKeys.contains("satellites") || force) {
swgSatelliteTrackerSettings->setSatellites(convertStringListToPtrs(settings.m_satellites));
}
if (featureSettingsKeys.contains("tles") || force) {
swgSatelliteTrackerSettings->setTles(convertStringListToPtrs(settings.m_satellites));
}
if (featureSettingsKeys.contains("dateTime") || force) {
swgSatelliteTrackerSettings->setDateTime(new QString(settings.m_dateTime));
}
if (featureSettingsKeys.contains("minAOSElevation") || force) {
swgSatelliteTrackerSettings->setMinAosElevation(settings.m_minAOSElevation);
}
if (featureSettingsKeys.contains("minPassElevation") || force) {
swgSatelliteTrackerSettings->setMinPassElevation(settings.m_minPassElevation);
}
if (featureSettingsKeys.contains("azElUnits") || force) {
swgSatelliteTrackerSettings->setAzElUnits((int)settings.m_azElUnits);
}
if (featureSettingsKeys.contains("groundTrackPoints") || force) {
swgSatelliteTrackerSettings->setGroundTrackPoints(settings.m_groundTrackPoints);
}
if (featureSettingsKeys.contains("dateFormat") || force) {
swgSatelliteTrackerSettings->setDateFormat(new QString(settings.m_dateFormat));
}
if (featureSettingsKeys.contains("utc") || force) {
swgSatelliteTrackerSettings->setUtc(settings.m_utc);
}
if (featureSettingsKeys.contains("updatePeriod") || force) {
swgSatelliteTrackerSettings->setUpdatePeriod(settings.m_updatePeriod);
}
if (featureSettingsKeys.contains("dopplerPeriod") || force) {
swgSatelliteTrackerSettings->setDopplerPeriod(settings.m_dopplerPeriod);
}
if (featureSettingsKeys.contains("defaultFrequency") || force) {
swgSatelliteTrackerSettings->setDefaultFrequency(settings.m_defaultFrequency);
}
if (featureSettingsKeys.contains("drawOnMap") || force) {
swgSatelliteTrackerSettings->setDrawOnMap(settings.m_drawOnMap);
}
if (featureSettingsKeys.contains("aosSpeech") || force) {
swgSatelliteTrackerSettings->setAosSpeech(new QString(settings.m_aosSpeech));
}
if (featureSettingsKeys.contains("losSpeech") || force) {
swgSatelliteTrackerSettings->setLosSpeech(new QString(settings.m_losSpeech));
}
if (featureSettingsKeys.contains("aosCommand") || force) {
swgSatelliteTrackerSettings->setAosCommand(new QString(settings.m_aosCommand));
}
if (featureSettingsKeys.contains("losCommand") || force) {
swgSatelliteTrackerSettings->setLosCommand(new QString(settings.m_losCommand));
}
if (featureSettingsKeys.contains("predictionPeriod") || force) {
swgSatelliteTrackerSettings->setPredictionPeriod(settings.m_predictionPeriod);
}
if (featureSettingsKeys.contains("passStartTime") || force) {
swgSatelliteTrackerSettings->setPassStartTime(new QString(settings.m_passStartTime.toString()));
}
if (featureSettingsKeys.contains("passFinishTime") || force) {
swgSatelliteTrackerSettings->setPassFinishTime(new QString(settings.m_passFinishTime.toString()));
}
if (featureSettingsKeys.contains("title") || force) {
swgSatelliteTrackerSettings->setTitle(new QString(settings.m_title));
}
if (featureSettingsKeys.contains("rgbColor") || force) {
swgSatelliteTrackerSettings->setRgbColor(settings.m_rgbColor);
}
QString channelSettingsURL = QString("http://%1:%2/sdrangel/featureset/%3/feature/%4/settings")
.arg(settings.m_reverseAPIAddress)
.arg(settings.m_reverseAPIPort)
.arg(settings.m_reverseAPIFeatureSetIndex)
.arg(settings.m_reverseAPIFeatureIndex);
m_networkRequest.setUrl(QUrl(channelSettingsURL));
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QBuffer *buffer = new QBuffer();
buffer->open((QBuffer::ReadWrite));
buffer->write(swgFeatureSettings->asJson().toUtf8());
buffer->seek(0);
// Always use PATCH to avoid passing reverse API settings
QNetworkReply *reply = m_networkManager->sendCustomRequest(m_networkRequest, "PATCH", buffer);
buffer->setParent(reply);
delete swgFeatureSettings;
}
void SatelliteTracker::networkManagerFinished(QNetworkReply *reply)
{
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "SatelliteTracker::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
}
else
{
QString answer = reply->readAll();
answer.chop(1); // remove last \n
qDebug("SatelliteTracker::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
}
reply->deleteLater();
}
QString SatelliteTracker::satNogsSatellitesFilename()
{
return HttpDownloadManager::downloadDir() + "/satnogs_satellites.json";
}
QString SatelliteTracker::satNogsTransmittersFilename()
{
return HttpDownloadManager::downloadDir() + "/satnogs_transmitters.json";
}
QString SatelliteTracker::satNogsTLEFilename()
{
return HttpDownloadManager::downloadDir() + "/satnogs_tle.json";
}
QString SatelliteTracker::tleURLToFilename(const QString& string)
{
if (string == "https://db.satnogs.org/api/tle/")
return satNogsTLEFilename();
QUrl url(string);
return HttpDownloadManager::downloadDir() + "/tle_" + url.fileName();
}
void SatelliteTracker::downloadFinished(const QString& filename, bool success)
{
if (success)
{
if (filename == satNogsSatellitesFilename())
{
m_dlm.download(QUrl("https://db.satnogs.org/api/transmitters/"), satNogsTransmittersFilename());
}
else if (filename == satNogsTransmittersFilename())
{
m_tleIndex = 0;
if (m_settings.m_tles.size() > 0)
m_dlm.download(QUrl(m_settings.m_tles[0]), tleURLToFilename(m_settings.m_tles[0]));
else
qWarning() << "Satellite Tracker: No TLEs";
}
else if ((m_tleIndex < m_settings.m_tles.size()) && (filename == tleURLToFilename(m_settings.m_tles[m_tleIndex])))
{
m_tleIndex++;
if (m_tleIndex < m_settings.m_tles.size())
m_dlm.download(QUrl(m_settings.m_tles[m_tleIndex]), tleURLToFilename(m_settings.m_tles[m_tleIndex]));
else
{
readSatData();
m_updatingSatData = false;
}
}
else
qDebug() << "SatelliteTracker::downloadFinished: Unexpected filename: " << filename;
}
else
m_updatingSatData = false;
}
bool SatelliteTracker::readSatData()
{
QFile satsFile(satNogsSatellitesFilename());
if (satsFile.open(QIODevice::ReadOnly))
{
if (parseSatellites(satsFile.readAll()))
{
QFile transmittersFile(satNogsTransmittersFilename());
if (transmittersFile.open(QIODevice::ReadOnly))
{
if (parseTransmitters(transmittersFile.readAll()))
{
for (int i = 0; i < m_settings.m_tles.size(); i++)
{
QFile tlesFile(tleURLToFilename(m_settings.m_tles[i]));
if (tlesFile.open(QIODevice::ReadOnly))
{
bool ok;
if (tlesFile.fileName() == satNogsTLEFilename())
{
ok = parseSatNogsTLEs(tlesFile.readAll());
}
else
ok = parseTxtTLEs(tlesFile.readAll());
if (!ok)
qDebug() << "SatelliteTracker::readSatData - failed to parse: " << tlesFile.fileName();
}
else
qDebug() << "SatelliteTracker::readSatData - failed to open: " << tlesFile.fileName();
}
qDebug() << "SatelliteTracker::readSatData - read " << m_satellites.size() << " satellites";
// Send to GUI
if (m_guiMessageQueue)
m_guiMessageQueue->push(MsgSatData::create(m_satellites));
// Send to worker
m_worker->getInputMessageQueue()->push(MsgSatData::create(m_satellites));
return true;
}
}
}
}
qDebug() << "SatelliteTracker::readSatData - Failed to read satellites";
return false;
}
bool SatelliteTracker::parseSatellites(const QByteArray& json)
{
QJsonDocument jsonResponse = QJsonDocument::fromJson(json);
if (jsonResponse.isArray())
{
m_satellites = SatNogsSatellite::createHash(jsonResponse.array());
m_satellitesId.clear();
// Create second table, hashed on ID
QHashIterator<QString, SatNogsSatellite *> i(m_satellites);
while (i.hasNext())
{
i.next();
SatNogsSatellite *sat = i.value();
m_satellitesId.insert(sat->m_noradCatId, sat);
}
return true;
}
else
return false;
}
bool SatelliteTracker::parseTransmitters(const QByteArray& json)
{
QJsonDocument jsonResponse = QJsonDocument::fromJson(json);
if (jsonResponse.isArray())
{
QList<SatNogsTransmitter *> transmitters = SatNogsTransmitter::createList(jsonResponse.array());
QHashIterator<QString, SatNogsSatellite *> i(m_satellites);
while (i.hasNext())
{
i.next();
SatNogsSatellite *sat = i.value();
sat->addTransmitters(transmitters);
}
return true;
}
else
return false;
}
bool SatelliteTracker::parseSatNogsTLEs(const QByteArray& json)
{
QJsonDocument jsonResponse = QJsonDocument::fromJson(json);
if (jsonResponse.isArray())
{
QList<SatNogsTLE *> tles = SatNogsTLE::createList(jsonResponse.array());
QHashIterator<QString, SatNogsSatellite *> i(m_satellites);
while (i.hasNext())
{
i.next();
SatNogsSatellite *sat = i.value();
sat->addTLE(tles);
}
return true;
}
else
return false;
}
bool SatelliteTracker::parseTxtTLEs(const QByteArray& txt)
{
QList<SatNogsTLE *> tles = SatNogsTLE::createList(txt);
QHashIterator<QString, SatNogsSatellite *> i(m_satellites);
while (i.hasNext())
{
i.next();
SatNogsSatellite *sat = i.value();
sat->addTLE(tles);
}
// Create satellites, that we have TLEs for, but no existing entry
for (int i = 0; i < tles.size(); i++)
{
if (!m_satellitesId.contains(tles[i]->m_noradCatId))
{
SatNogsSatellite *sat = new SatNogsSatellite(tles[i]);
m_satellites.insert(sat->m_name, sat);
m_satellitesId.insert(sat->m_noradCatId, sat);
}
}
return true;
}
void SatelliteTracker::updateSatData()
{
QMutexLocker mutexLocker(&m_mutex);
if (m_updatingSatData == false)
{
m_updatingSatData = true;
qDebug() << "SatelliteTracker::updateSatData: requesting satellites";
m_dlm.download(QUrl("https://db.satnogs.org/api/satellites/"), satNogsSatellitesFilename());
}
else
qDebug() << "SatelliteTracker::updateSatData: update in progress";
}

View File

@ -0,0 +1,200 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FEATURE_SATELLITETRACKER_H_
#define INCLUDE_FEATURE_SATELLITETRACKER_H_
#include <QThread>
#include <QNetworkRequest>
#include "feature/feature.h"
#include "util/message.h"
#include "util/httpdownloadmanager.h"
#include "satellitetrackersettings.h"
#include "satnogs.h"
class WebAPIAdapterInterface;
class SatelliteTrackerWorker;
class QNetworkAccessManager;
class QNetworkReply;
namespace SWGSDRangel {
class SWGDeviceState;
}
class SatelliteTracker : public Feature
{
Q_OBJECT
public:
class MsgConfigureSatelliteTracker : public Message {
MESSAGE_CLASS_DECLARATION
public:
const SatelliteTrackerSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureSatelliteTracker* create(const SatelliteTrackerSettings& settings, bool force) {
return new MsgConfigureSatelliteTracker(settings, force);
}
private:
SatelliteTrackerSettings m_settings;
bool m_force;
MsgConfigureSatelliteTracker(const SatelliteTrackerSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
class MsgStartStop : public Message {
MESSAGE_CLASS_DECLARATION
public:
bool getStartStop() const { return m_startStop; }
static MsgStartStop* create(bool startStop) {
return new MsgStartStop(startStop);
}
protected:
bool m_startStop;
MsgStartStop(bool startStop) :
Message(),
m_startStop(startStop)
{ }
};
class MsgUpdateSatData : public Message {
MESSAGE_CLASS_DECLARATION
public:
static MsgUpdateSatData* create() {
return new MsgUpdateSatData();
}
private:
MsgUpdateSatData() :
Message()
{ }
};
class MsgSatData : public Message {
MESSAGE_CLASS_DECLARATION
public:
QHash<QString, SatNogsSatellite *> getSatellites() { return m_satellites; }
static MsgSatData* create(QHash<QString, SatNogsSatellite *> satellites) {
return new MsgSatData(satellites);
}
private:
QHash<QString, SatNogsSatellite *> m_satellites;
MsgSatData(QHash<QString, SatNogsSatellite *> satellites) :
Message(),
m_satellites(satellites)
{ }
};
SatelliteTracker(WebAPIAdapterInterface *webAPIAdapterInterface);
virtual ~SatelliteTracker();
virtual void destroy() { delete this; }
virtual bool handleMessage(const Message& cmd);
virtual void getIdentifier(QString& id) const { id = objectName(); }
virtual void getTitle(QString& title) const { title = m_settings.m_title; }
virtual QByteArray serialize() const;
virtual bool deserialize(const QByteArray& data);
virtual int webapiRun(bool run,
SWGSDRangel::SWGDeviceState& response,
QString& errorMessage);
virtual int webapiSettingsGet(
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage);
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage);
static void webapiFormatFeatureSettings(
SWGSDRangel::SWGFeatureSettings& response,
const SatelliteTrackerSettings& settings);
static void webapiUpdateFeatureSettings(
SatelliteTrackerSettings& settings,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response);
static const char* const m_featureIdURI;
static const char* const m_featureId;
bool isUpdatingSatData() { return m_updatingSatData; }
private:
QThread m_thread;
SatelliteTrackerWorker *m_worker;
SatelliteTrackerSettings m_settings;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
HttpDownloadManager m_dlm;
bool m_updatingSatData;
int m_tleIndex;
QMutex m_mutex;
QHash<QString, SatNogsSatellite *> m_satellites; // Satellites, hashed on name
QHash<int, SatNogsSatellite *> m_satellitesId; // Same data as m_satellites, but hashed on id, rather than name
bool m_firstUpdateSatData;
void start();
void stop();
void applySettings(const SatelliteTrackerSettings& settings, bool force = false);
void webapiReverseSendSettings(QList<QString>& featureSettingsKeys, const SatelliteTrackerSettings& settings, bool force);
QString satNogsSatellitesFilename();
QString satNogsTransmittersFilename();
QString satNogsTLEFilename();
QString tleURLToFilename(const QString& string);
bool parseSatellites(const QByteArray& json);
bool parseTransmitters(const QByteArray& json);
bool parseSatNogsTLEs(const QByteArray& json);
bool parseTxtTLEs(const QByteArray& txt);
bool readSatData();
void updateSatData();
void updateSatellitesReply(QNetworkReply *reply);
void updateTransmittersReply(QNetworkReply *reply);
void updateTLEsReply(QNetworkReply *reply);
private slots:
void networkManagerFinished(QNetworkReply *reply);
void downloadFinished(const QString& filename, bool success);
};
#endif // INCLUDE_FEATURE_SATELLITETRACKER_H_

View File

@ -0,0 +1,6 @@
<RCC>
<qresource prefix="/satellitetracker/">
<file>satellitetracker/iss-32.png</file>
<file>satellitetracker/satellite-32.png</file>
</qresource>
</RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 844 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 910 B

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,154 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FEATURE_SATELLITETRACKERGUI_H_
#define INCLUDE_FEATURE_SATELLITETRACKERGUI_H_
#include <QTimer>
#include <QMenu>
#include <QtCharts>
#include <QTextToSpeech>
#include "feature/featuregui.h"
#include "util/messagequeue.h"
#include "satellitetrackersettings.h"
#include "satnogs.h"
class PluginAPI;
class FeatureUISet;
class SatelliteTracker;
struct SatelliteState;
namespace Ui {
class SatelliteTrackerGUI;
}
using namespace QtCharts;
class SatelliteTrackerGUI : public FeatureGUI {
Q_OBJECT
public:
static SatelliteTrackerGUI* create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature);
virtual void destroy();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
private:
Ui::SatelliteTrackerGUI* ui;
PluginAPI* m_pluginAPI;
FeatureUISet* m_featureUISet;
SatelliteTrackerSettings m_settings;
bool m_doApplySettings;
SatelliteTracker* m_satelliteTracker;
MessageQueue m_inputMessageQueue;
QTimer m_statusTimer;
int m_lastFeatureState;
bool m_lastUpdatingSatData;
QHash<QString, SatNogsSatellite *> m_satellites;
SatelliteState *m_targetSatState;
int m_plotPass;
QChart m_emptyChart;
QChart *m_lineChart;
QPolarChart *m_polarChart;
QDateTime m_nextTargetAOS;
QDateTime m_nextTargetLOS;
bool m_geostationarySatVisible;
QTextToSpeech *m_speech;
QMenu *menu; // Column select context menu
enum SatCol {
SAT_COL_NAME,
SAT_COL_AZ,
SAT_COL_EL,
SAT_COL_AOS,
SAT_COL_LOS,
SAT_COL_MAX_EL,
SAT_COL_DIR,
SAT_COL_ALT,
SAT_COL_RANGE,
SAT_COL_RANGE_RATE,
SAT_COL_DOPPLER,
SAT_COL_PATH_LOSS,
SAT_COL_DELAY,
SAT_COL_NORAD_ID
};
explicit SatelliteTrackerGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent = nullptr);
virtual ~SatelliteTrackerGUI();
void aos(const QString& name, int duration, int maxElevation);
void los(const QString& name);
void blockApplySettings(bool block);
void applySettings(bool force = false);
void displaySettings();
void setTarget(const QString& target);
QString convertDegreesToText(double degrees);
bool handleMessage(const Message& message);
void plotChart();
void plotAzElChart();
void plotPolarChart();
void resizeTable();
void updateTable(SatelliteState *satState);
void updateSelectedSats();
QAction *createCheckableItem(QString& text, int idx, bool checked);
void updateTimeToAOS();
QString formatDaysTime(qint64 days, QDateTime dateTime);
void leaveEvent(QEvent*);
void enterEvent(QEvent*);
private slots:
void onMenuDialogCalled(const QPoint &p);
void onWidgetRolled(QWidget* widget, bool rollDown);
void handleInputMessages();
void on_startStop_toggled(bool checked);
void on_useMyPosition_clicked(bool checked=false);
void on_latitude_valueChanged(double value);
void on_longitude_valueChanged(double value);
void on_target_currentTextChanged(const QString &text);
void on_displaySettings_clicked();
void on_radioControl_clicked();
void on_dateTimeSelect_currentTextChanged(const QString &text);
void on_dateTime_dateTimeChanged(const QDateTime &datetime);
void updateStatus();
void on_viewOnMap_clicked();
void on_updateSatData_clicked();
void on_selectSats_clicked();
void on_autoTarget_clicked(bool checked);
void on_chartSelect_currentIndexChanged(int index);
void on_nextPass_clicked();
void on_prevPass_clicked();
void on_satTable_cellDoubleClicked(int row, int column);
void satTable_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex);
void satTable_sectionResized(int logicalIndex, int oldSize, int newSize);
void columnSelectMenu(QPoint pos);
void columnSelectMenuChecked(bool checked = false);
};
#endif // INCLUDE_FEATURE_SATELLITETRACKERGUI_H_

View File

@ -0,0 +1,709 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SatelliteTrackerGUI</class>
<widget class="RollupWidget" name="SatelliteTrackerGUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>525</width>
<height>750</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>320</width>
<height>100</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>Satellite Tracker</string>
</property>
<widget class="QWidget" name="settingsContainer" native="true">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>301</width>
<height>141</height>
</rect>
</property>
<property name="windowTitle">
<string>Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>3</number>
</property>
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="3">
<widget class="QLabel" name="longitudeLabel">
<property name="text">
<string>Longitude</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="latitudeLabel">
<property name="text">
<string>Latitude</string>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QComboBox" name="dateTimeSelect">
<property name="toolTip">
<string>Date and time to use when calculating satellite's position</string>
</property>
<item>
<property name="text">
<string>Now</string>
</property>
</item>
<item>
<property name="text">
<string>Custom</string>
</property>
</item>
</widget>
</item>
<item row="4" column="4">
<widget class="QLineEdit" name="aos">
<property name="toolTip">
<string>Time to acquistion of signal (AOS)</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="timeLabel">
<property name="text">
<string>Time</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QDoubleSpinBox" name="latitude">
<property name="toolTip">
<string>Latitude in decimal degrees (North positive) of antenna location</string>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="minimum">
<double>-90.000000000000000</double>
</property>
<property name="maximum">
<double>90.000000000000000</double>
</property>
<property name="value">
<double>-90.000000000000000</double>
</property>
</widget>
</item>
<item row="6" column="3">
<widget class="QLabel" name="elevationLabel">
<property name="text">
<string>Elevation</string>
</property>
</widget>
</item>
<item row="6" column="2">
<widget class="QLineEdit" name="azimuth">
<property name="toolTip">
<string>Computed azimuth in degrees to the target satellite from the antenna's location</string>
</property>
<property name="text">
<string>360</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="targetLabel">
<property name="text">
<string>Target</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="5">
<layout class="QHBoxLayout" name="buttonLayout">
<item>
<widget class="ButtonSwitch" name="startStop">
<property name="toolTip">
<string>Start/stop tracking</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/play.png</normaloff>
<normalon>:/stop.png</normalon>:/play.png</iconset>
</property>
</widget>
</item>
<item>
<spacer name="buttonHorizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="viewOnMap">
<property name="toolTip">
<string>Find target on the map</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/gridpolar.png</normaloff>:/gridpolar.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="ButtonSwitch" name="autoTarget">
<property name="toolTip">
<string>Automatically select target satellite on AOS</string>
</property>
<property name="text">
<string>^</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/link.png</normaloff>:/link.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="updateSatData">
<property name="toolTip">
<string>Update satellite data</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/recycle.png</normaloff>:/recycle.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="radioControl">
<property name="toolTip">
<string>SDRangel control</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/sdrangel_icon.png</normaloff>:/sdrangel_icon.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="selectSats">
<property name="toolTip">
<string>Select satellites</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/gps.png</normaloff>:/gps.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="useMyPosition">
<property name="toolTip">
<string>Set latitude, longitude and height from My Position in SDRangel preferences</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/import.png</normaloff>:/import.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="displaySettings">
<property name="toolTip">
<string>Show settings dialog</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/listing.png</normaloff>:/listing.png</iconset>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="4">
<widget class="QDoubleSpinBox" name="longitude">
<property name="toolTip">
<string>Longitude in decimal degress (East positive) of antenna location</string>
</property>
<property name="decimals">
<number>6</number>
</property>
<property name="minimum">
<double>-180.000000000000000</double>
</property>
<property name="maximum">
<double>180.000000000000000</double>
</property>
<property name="value">
<double>-180.000000000000000</double>
</property>
</widget>
</item>
<item row="6" column="4">
<widget class="QLineEdit" name="elevation">
<property name="toolTip">
<string>Computed elevation in degrees to the target satellite from the antenna's location</string>
</property>
<property name="text">
<string>90</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="azimuthtLabel">
<property name="text">
<string>Azimuth</string>
</property>
</widget>
</item>
<item row="4" column="2">
<widget class="QComboBox" name="target">
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Target satellite</string>
</property>
<property name="currentIndex">
<number>-1</number>
</property>
</widget>
</item>
<item row="3" column="3" colspan="2">
<widget class="WrappingDateTimeEdit" name="dateTime">
<property name="toolTip">
<string>Date and time to use when calculating satellite's position</string>
</property>
<property name="displayFormat">
<string>dd/MM/yyyy HH:mm:ss</string>
</property>
<property name="calendarPopup">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="3">
<widget class="QLabel" name="aosLabel">
<property name="text">
<string>AOS</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="chartContainer" native="true">
<property name="geometry">
<rect>
<x>10</x>
<y>160</y>
<width>318</width>
<height>268</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>200</width>
<height>200</height>
</size>
</property>
<property name="windowTitle">
<string>Pass Chart</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,0">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QComboBox" name="chartSelect">
<property name="toolTip">
<string>Select type of chart to plot</string>
</property>
<item>
<property name="text">
<string>Polar</string>
</property>
</item>
<item>
<property name="text">
<string>Az/El vs Time</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QToolButton" name="prevPass">
<property name="toolTip">
<string>Plot previous pass</string>
</property>
<property name="text">
<string>...</string>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/arrow_right.png</normaloff>:/arrow_right.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="nextPass">
<property name="toolTip">
<string>Plot next pass</string>
</property>
<property name="text">
<string/>
</property>
<property name="icon">
<iconset resource="../../../sdrgui/resources/res.qrc">
<normaloff>:/arrow_left.png</normaloff>:/arrow_left.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="passLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>15</width>
<height>0</height>
</size>
</property>
<property name="toolTip">
<string>Pass number</string>
</property>
<property name="text">
<string>0</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QChartView" name="passChart">
<property name="minimumSize">
<size>
<width>300</width>
<height>250</height>
</size>
</property>
<property name="toolTip">
<string>Azimuth and elevation over time for satellite pass</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="dataContainer" native="true">
<property name="geometry">
<rect>
<x>10</x>
<y>440</y>
<width>431</width>
<height>291</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Satellite Data</string>
</property>
<layout class="QVBoxLayout" name="verticalLayoutTable">
<property name="spacing">
<number>2</number>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>3</number>
</property>
<item>
<widget class="QTableWidget" name="satTable">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<column>
<property name="text">
<string>Satellite</string>
</property>
<property name="toolTip">
<string>Satellite name</string>
</property>
</column>
<column>
<property name="text">
<string>Az</string>
</property>
<property name="toolTip">
<string>Azimuth in degrees to satellite from antenna location</string>
</property>
</column>
<column>
<property name="text">
<string>El</string>
</property>
<property name="toolTip">
<string>Elevation in degrees to satellite from antenna location</string>
</property>
</column>
<column>
<property name="text">
<string>AOS</string>
</property>
<property name="toolTip">
<string>Time of next AOS (Acquisition of signal)</string>
</property>
</column>
<column>
<property name="text">
<string>LOS</string>
</property>
<property name="toolTip">
<string>Time of next LOS (Loss of signal)</string>
</property>
</column>
<column>
<property name="text">
<string>Max El.</string>
</property>
<property name="toolTip">
<string>Maximum elevation in degrees of next satellite pass</string>
</property>
</column>
<column>
<property name="text">
<string>Dir</string>
</property>
<property name="toolTip">
<string>Direction of the next pass</string>
</property>
</column>
<column>
<property name="text">
<string>Alt (km)</string>
</property>
<property name="toolTip">
<string>Satellite altitude in kilometres</string>
</property>
</column>
<column>
<property name="text">
<string>Range (km)</string>
</property>
<property name="toolTip">
<string>Range to satellite in kilometres</string>
</property>
</column>
<column>
<property name="text">
<string>Range rate (km/s)</string>
</property>
<property name="toolTip">
<string>Speed of satellite towards antenna location in kilometers per second</string>
</property>
</column>
<column>
<property name="text">
<string>Doppler (Hz)</string>
</property>
<property name="toolTip">
<string>Receive Doppler shift in Hertz (At frequency set in settings)</string>
</property>
</column>
<column>
<property name="text">
<string>Path loss (dB)</string>
</property>
<property name="toolTip">
<string>Free space loss of signal in decibels (At frequency set in settings)</string>
</property>
</column>
<column>
<property name="text">
<string>Delay (ms)</string>
</property>
<property name="toolTip">
<string>Propagation delay of a signal from the antenna to the satellite in milliseconds (assuming line-of-sight)</string>
</property>
</column>
<column>
<property name="text">
<string>Norad ID</string>
</property>
<property name="toolTip">
<string>Norad catalog idenfitier for the satellite</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>RollupWidget</class>
<extends>QWidget</extends>
<header>gui/rollupwidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ButtonSwitch</class>
<extends>QToolButton</extends>
<header>gui/buttonswitch.h</header>
</customwidget>
<customwidget>
<class>QChartView</class>
<extends>QGraphicsView</extends>
<header>QtCharts</header>
</customwidget>
<customwidget>
<class>WrappingDateTimeEdit</class>
<extends>QDateTimeEdit</extends>
<header>gui/wrappingdatetimeedit.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>startStop</tabstop>
<tabstop>viewOnMap</tabstop>
<tabstop>autoTarget</tabstop>
<tabstop>updateSatData</tabstop>
<tabstop>radioControl</tabstop>
<tabstop>selectSats</tabstop>
<tabstop>useMyPosition</tabstop>
<tabstop>displaySettings</tabstop>
<tabstop>latitude</tabstop>
<tabstop>longitude</tabstop>
<tabstop>dateTimeSelect</tabstop>
<tabstop>dateTime</tabstop>
<tabstop>target</tabstop>
<tabstop>aos</tabstop>
<tabstop>azimuth</tabstop>
<tabstop>elevation</tabstop>
<tabstop>chartSelect</tabstop>
<tabstop>prevPass</tabstop>
<tabstop>nextPass</tabstop>
<tabstop>passChart</tabstop>
<tabstop>satTable</tabstop>
</tabstops>
<resources>
<include location="../../../sdrgui/resources/res.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -0,0 +1,80 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QtPlugin>
#include "plugin/pluginapi.h"
#ifndef SERVER_MODE
#include "satellitetrackergui.h"
#endif
#include "satellitetracker.h"
#include "satellitetrackerplugin.h"
#include "satellitetrackerwebapiadapter.h"
const PluginDescriptor SatelliteTrackerPlugin::m_pluginDescriptor = {
SatelliteTracker::m_featureId,
QStringLiteral("Satellite Tracker"),
QStringLiteral("6.5.6"),
QStringLiteral("(c) Jon Beniston, M7RCE and Daniel Warner (SGP4 library)"),
QStringLiteral("https://github.com/f4exb/sdrangel"),
true,
QStringLiteral("https://github.com/f4exb/sdrangel")
};
SatelliteTrackerPlugin::SatelliteTrackerPlugin(QObject* parent) :
QObject(parent),
m_pluginAPI(nullptr)
{
}
const PluginDescriptor& SatelliteTrackerPlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void SatelliteTrackerPlugin::initPlugin(PluginAPI* pluginAPI)
{
m_pluginAPI = pluginAPI;
m_pluginAPI->registerFeature(SatelliteTracker::m_featureIdURI, SatelliteTracker::m_featureId, this);
}
#ifdef SERVER_MODE
FeatureGUI* SatelliteTrackerPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const
{
(void) featureUISet;
(void) feature;
return nullptr;
}
#else
FeatureGUI* SatelliteTrackerPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const
{
return SatelliteTrackerGUI::create(m_pluginAPI, featureUISet, feature);
}
#endif
Feature* SatelliteTrackerPlugin::createFeature(WebAPIAdapterInterface* webAPIAdapterInterface) const
{
return new SatelliteTracker(webAPIAdapterInterface);
}
FeatureWebAPIAdapter* SatelliteTrackerPlugin::createFeatureWebAPIAdapter() const
{
return new SatelliteTrackerWebAPIAdapter();
}

View File

@ -0,0 +1,49 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FEATURE_SATELLITETRACKERPLUGIN_H
#define INCLUDE_FEATURE_SATELLITETRACKERPLUGIN_H
#include <QObject>
#include "plugin/plugininterface.h"
class FeatureGUI;
class WebAPIAdapterInterface;
class SatelliteTrackerPlugin : public QObject, PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID "sdrangel.feature.satellitetracker")
public:
explicit SatelliteTrackerPlugin(QObject* parent = nullptr);
const PluginDescriptor& getPluginDescriptor() const;
void initPlugin(PluginAPI* pluginAPI);
virtual FeatureGUI* createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const;
virtual Feature* createFeature(WebAPIAdapterInterface *webAPIAdapterInterface) const;
virtual FeatureWebAPIAdapter* createFeatureWebAPIAdapter() const;
private:
static const PluginDescriptor m_pluginDescriptor;
PluginAPI* m_pluginAPI;
};
#endif // INCLUDE_FEATURE_SATELLITETRACKERPLUGIN_H

View File

@ -0,0 +1,130 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FEATURE_SATELLITETRACKERREPORT_H_
#define INCLUDE_FEATURE_SATELLITETRACKERREPORT_H_
#include <QObject>
#include "util/message.h"
#include "satellitetrackersgp4.h"
class SatelliteTrackerReport : public QObject
{
Q_OBJECT
public:
// Sent from worker to GUI to give latest satellite data
class MsgReportSat : public Message {
MESSAGE_CLASS_DECLARATION
public:
SatelliteState* getSatelliteState() const { return m_satState; }
static MsgReportSat* create(SatelliteState* satState)
{
return new MsgReportSat(satState);
}
private:
SatelliteState* m_satState;
MsgReportSat(SatelliteState* satState) :
Message(),
m_satState(satState)
{
}
};
// Sent from worker to GUI to indicate AOS
class MsgReportAOS : public Message {
MESSAGE_CLASS_DECLARATION
public:
QString getName() const { return m_name; }
int getDuration() const { return m_duration; }
int getMaxElevation() const { return m_maxElevation; }
static MsgReportAOS* create(const QString& name, int duration, int maxElevation)
{
return new MsgReportAOS(name, duration, maxElevation);
}
private:
QString m_name;
int m_duration;
int m_maxElevation;
MsgReportAOS(const QString& name, int duration, int maxElevation) :
Message(),
m_name(name),
m_duration(duration),
m_maxElevation(maxElevation)
{
}
};
// Sent from worker to GUI to indicaite LOS
class MsgReportLOS : public Message {
MESSAGE_CLASS_DECLARATION
public:
QString getName() const { return m_name; }
static MsgReportLOS* create(const QString& name)
{
return new MsgReportLOS(name);
}
private:
QString m_name;
MsgReportLOS(const QString& name) :
Message(),
m_name(name)
{
}
};
// Sent from worker to GUI, to indicate target has changed
class MsgReportTarget : public Message {
MESSAGE_CLASS_DECLARATION
public:
QString getName() const { return m_name; }
static MsgReportTarget* create(const QString& name)
{
return new MsgReportTarget(name);
}
private:
QString m_name;
MsgReportTarget(const QString& name) :
Message(),
m_name(name)
{
}
};
public:
SatelliteTrackerReport() {}
~SatelliteTrackerReport() {}
};
#endif // INCLUDE_FEATURE_SATELLITETRACKERREPORT_H_

View File

@ -0,0 +1,300 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <QColor>
#include <QDataStream>
#include "util/simpleserializer.h"
#include "settings/serializable.h"
#include "satellitetrackersettings.h"
#define DEAFULT_TARGET "ISS"
#define DEFAULT_TLES {"https://db.satnogs.org/api/tle/", "https://www.amsat.org/tle/current/nasabare.txt", "https://www.celestrak.com/NORAD/elements/goes.txt"}
#define DEFAULT_DATE_FORMAT "yyyy/MM/dd"
#define DEFAULT_AOS_SPEECH "${name} is visible for ${duration} minutes. Max elevation, ${elevation} degrees."
#define DEFAULT_LOS_SPEECH "${name} is no longer visible."
SatelliteTrackerSettings::SatelliteTrackerSettings()
{
resetToDefaults();
}
void SatelliteTrackerSettings::resetToDefaults()
{
m_latitude = 0.0;
m_longitude = 0.0;
m_heightAboveSeaLevel = 0.0;
m_target = DEAFULT_TARGET;
m_satellites = {QString(DEAFULT_TARGET)};
m_tles = DEFAULT_TLES;
m_dateTime = "";
m_minAOSElevation = 5;
m_minPassElevation = 15;
m_rotatorMaxAzimuth = 450;
m_rotatorMaxElevation = 180;
m_azElUnits = DM;
m_groundTrackPoints = 100;
m_dateFormat = DEFAULT_DATE_FORMAT;
m_utc = false;
m_updatePeriod = 1.0f;
m_dopplerPeriod = 10.0f;
m_defaultFrequency = 100000000.0f;
m_drawOnMap = true;
m_autoTarget = true;
m_aosSpeech = DEFAULT_AOS_SPEECH;
m_losSpeech = DEFAULT_LOS_SPEECH;
m_aosCommand = "";
m_losCommand = "";
m_predictionPeriod = 5;
m_passStartTime = QTime(0,0);
m_passFinishTime = QTime(23,59,59);
m_title = "Satellite Tracker";
m_rgbColor = QColor(225, 25, 99).rgb();
m_useReverseAPI = false;
m_reverseAPIAddress = "127.0.0.1";
m_reverseAPIPort = 8888;
m_reverseAPIFeatureSetIndex = 0;
m_reverseAPIFeatureIndex = 0;
for (int i = 0; i < SAT_COL_COLUMNS; i++)
{
m_columnIndexes[i] = i;
m_columnSizes[i] = -1; // Autosize
}
}
QByteArray SatelliteTrackerSettings::serialize() const
{
SimpleSerializer s(1);
s.writeDouble(1, m_latitude);
s.writeDouble(2, m_longitude);
s.writeDouble(3, m_heightAboveSeaLevel);
s.writeString(4, m_target);
s.writeBlob(5, serializeStringList(m_satellites));
s.writeBlob(6, serializeStringList(m_tles));
s.writeString(7, m_dateTime);
s.writeS32(8, m_minAOSElevation);
s.writeS32(9, m_minPassElevation);
s.writeS32(10, m_rotatorMaxAzimuth);
s.writeS32(11, m_rotatorMaxElevation);
s.writeS32(12, m_azElUnits);
s.writeS32(13, m_groundTrackPoints);
s.writeString(14, m_dateFormat);
s.writeBool(15, m_utc);
s.writeFloat(16, m_updatePeriod);
s.writeFloat(17, m_dopplerPeriod);
s.writeS32(18, m_predictionPeriod);
s.writeString(19, m_passStartTime.toString());
s.writeString(20, m_passFinishTime.toString());
s.writeFloat(21, m_defaultFrequency);
s.writeBool(22, m_drawOnMap);
s.writeBool(23, m_autoTarget);
s.writeString(24, m_aosSpeech);
s.writeString(25, m_losSpeech);
s.writeString(26, m_aosCommand);
s.writeString(27, m_losCommand);
s.writeBlob(28, serializeDeviceSettings(m_deviceSettings));
s.writeString(29, m_title);
s.writeU32(30, m_rgbColor);
s.writeBool(31, m_useReverseAPI);
s.writeString(32, m_reverseAPIAddress);
s.writeU32(33, m_reverseAPIPort);
s.writeU32(34, m_reverseAPIFeatureSetIndex);
s.writeU32(35, m_reverseAPIFeatureIndex);
for (int i = 0; i < SAT_COL_COLUMNS; i++)
s.writeS32(100 + i, m_columnIndexes[i]);
for (int i = 0; i < SAT_COL_COLUMNS; i++)
s.writeS32(200 + i, m_columnSizes[i]);
return s.final();
}
bool SatelliteTrackerSettings::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if (!d.isValid())
{
resetToDefaults();
return false;
}
if (d.getVersion() == 1)
{
QByteArray bytetmp;
uint32_t utmp;
QString strtmp;
QByteArray blob;
d.readDouble(1, &m_latitude, 0.0);
d.readDouble(2, &m_longitude, 0.0);
d.readDouble(3, &m_heightAboveSeaLevel, 0.0);
d.readString(4, &m_target, DEAFULT_TARGET);
d.readBlob(5, &blob);
deserializeStringList(blob, m_satellites);
d.readBlob(6, &blob);
deserializeStringList(blob, m_tles);
d.readString(7, &m_dateTime, "");
d.readS32(8, &m_minAOSElevation, 5);
d.readS32(9, &m_minPassElevation, 15);
d.readS32(10, &m_rotatorMaxAzimuth, 450);
d.readS32(11, &m_rotatorMaxElevation, 180);
d.readS32(12, (qint32 *)&m_azElUnits, DM);
d.readS32(13, &m_groundTrackPoints, 100);
d.readString(14, &m_dateFormat, DEFAULT_DATE_FORMAT);
d.readBool(15, &m_utc, false);
d.readFloat(16, &m_updatePeriod, 1.0f);
d.readFloat(17, &m_dopplerPeriod, 10.0f);
d.readS32(18, &m_predictionPeriod, 5);
d.readString(19, &strtmp, "00:00:00");
m_passStartTime = QTime::fromString(strtmp);
d.readString(20, &strtmp, "23:59:59");
m_passFinishTime = QTime::fromString(strtmp);
d.readFloat(21, &m_defaultFrequency, 100000000.0f);
d.readBool(22, &m_drawOnMap, true);
d.readBool(23, &m_autoTarget, true);
d.readString(24, &m_aosSpeech, DEFAULT_AOS_SPEECH);
d.readString(25, &m_aosCommand, DEFAULT_LOS_SPEECH);
d.readString(26, &m_aosCommand, "");
d.readString(27, &m_losCommand, "");
d.readBlob(28, &blob);
deserializeDeviceSettings(blob, m_deviceSettings);
d.readString(29, &m_title, "Satellite Tracker");
d.readU32(30, &m_rgbColor, QColor(225, 25, 99).rgb());
d.readBool(31, &m_useReverseAPI, false);
d.readString(32, &m_reverseAPIAddress, "127.0.0.1");
d.readU32(33, &utmp, 0);
if ((utmp > 1023) && (utmp < 65535)) {
m_reverseAPIPort = utmp;
} else {
m_reverseAPIPort = 8888;
}
d.readU32(34, &utmp, 0);
m_reverseAPIFeatureSetIndex = utmp > 99 ? 99 : utmp;
d.readU32(35, &utmp, 0);
m_reverseAPIFeatureIndex = utmp > 99 ? 99 : utmp;
for (int i = 0; i < SAT_COL_COLUMNS; i++)
d.readS32(100 + i, &m_columnIndexes[i], i);
for (int i = 0; i < SAT_COL_COLUMNS; i++)
d.readS32(200 + i, &m_columnSizes[i], -1);
return true;
}
else
{
resetToDefaults();
return false;
}
}
QByteArray SatelliteTrackerSettings::serializeStringList(const QList<QString>& strings) const
{
QByteArray data;
QDataStream *stream = new QDataStream(&data, QIODevice::WriteOnly);
(*stream) << strings;
delete stream;
return data;
}
void SatelliteTrackerSettings::deserializeStringList(const QByteArray& data, QList<QString>& strings)
{
QDataStream *stream = new QDataStream(data);
(*stream) >> strings;
delete stream;
}
QDataStream& operator<<(QDataStream& out, const QList<SatelliteTrackerSettings::SatelliteDeviceSettings *> *list)
{
out << *list;
return out;
}
QDataStream& operator>>(QDataStream& in, QList<SatelliteTrackerSettings::SatelliteDeviceSettings *>*& list)
{
list = new QList<SatelliteTrackerSettings::SatelliteDeviceSettings *>();
in >> *list;
return in;
}
QDataStream& operator<<(QDataStream& out, const SatelliteTrackerSettings::SatelliteDeviceSettings* settings)
{
out << settings->m_deviceSet;
out << settings->m_presetGroup;
out << settings->m_presetFrequency;
out << settings->m_presetDescription;
out << settings->m_doppler;
out << settings->m_startOnAOS;
out << settings->m_stopOnLOS;
out << settings->m_startStopFileSink;
out << settings->m_frequency;
out << settings->m_aosCommand;
out << settings->m_losCommand;
return out;
}
QDataStream& operator>>(QDataStream& in, SatelliteTrackerSettings::SatelliteDeviceSettings*& settings)
{
settings = new SatelliteTrackerSettings::SatelliteDeviceSettings();
in >> settings->m_deviceSet;
in >> settings->m_presetGroup;
in >> settings->m_presetFrequency;
in >> settings->m_presetDescription;
in >> settings->m_doppler;
in >> settings->m_startOnAOS;
in >> settings->m_stopOnLOS;
in >> settings->m_startStopFileSink;
in >> settings->m_frequency;
in >> settings->m_aosCommand;
in >> settings->m_losCommand;
return in;
}
QByteArray SatelliteTrackerSettings::serializeDeviceSettings(QHash<QString, QList<SatelliteDeviceSettings *> *> deviceSettings) const
{
QByteArray data;
QDataStream *stream = new QDataStream(&data, QIODevice::WriteOnly);
(*stream) << deviceSettings;
delete stream;
return data;
}
void SatelliteTrackerSettings::deserializeDeviceSettings(const QByteArray& data, QHash<QString, QList<SatelliteDeviceSettings *> *>& deviceSettings)
{
QDataStream *stream = new QDataStream(data);
(*stream) >> deviceSettings;
delete stream;
}
SatelliteTrackerSettings::SatelliteDeviceSettings::SatelliteDeviceSettings()
{
m_deviceSet = "R0";
m_presetFrequency = 0;
m_startOnAOS = true;
m_stopOnLOS = true;
m_startStopFileSink = true;
m_frequency = 0;
m_aosCommand = "";
m_losCommand = "";
}

View File

@ -0,0 +1,100 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FEATURE_SATELLITETRACKERSETTINGS_H_
#define INCLUDE_FEATURE_SATELLITETRACKERSETTINGS_H_
#include <QByteArray>
#include <QString>
#include <QHash>
#include <QList>
#include <QTime>
class Serializable;
#define SAT_COL_COLUMNS 14
struct SatelliteTrackerSettings
{
struct SatelliteDeviceSettings
{
QString m_deviceSet; //!< R0, T1...
QString m_presetGroup; //!< Preset to load to device set
quint64 m_presetFrequency;
QString m_presetDescription;
QList<int> m_doppler; //!< Which channels to apply Doppler correction to, if any
bool m_startOnAOS; //!< Start acquistion on AOS
bool m_stopOnLOS; //!< Stop acquistion on LOS
bool m_startStopFileSink; //!< Start&stop file sinks recording on AOS/LOS
quint64 m_frequency; //!< Optional center frequency to set (in Hz), to override preset value
QString m_aosCommand; //!< Command/script to execute on AOS
QString m_losCommand; //!< Command/script to execute on LOS
SatelliteDeviceSettings();
};
double m_latitude; //!< Antenna location, degrees
double m_longitude;
double m_heightAboveSeaLevel; //!< In metres
QString m_target; //!< Target satellite
QList<QString> m_satellites; //!< Selected satellites
QList<QString> m_tles; //!< TLE URLs
QString m_dateTime; //!< Date/time for observation, or "" for now (UTC or local as per m_utc)
int m_minAOSElevation; //!< Minimum elevation for AOS
int m_minPassElevation; //!< Minimum elevation for a pass
int m_rotatorMaxAzimuth; //!< Maximum rotator azimuth 360/450
int m_rotatorMaxElevation; //!< Maximum rotator elevation 90/180
enum AzElUnits {DMS, DM, D, Decimal} m_azElUnits;
int m_groundTrackPoints; //!< Number of points in ground tracks
QString m_dateFormat; //!< Format used for displaying dates in the GUI
bool m_utc; //!< Set/display times as UTC rather than local
float m_updatePeriod; //!< How long in seconds between updates of satellite's position
float m_dopplerPeriod; //!< How long in seconds between Doppler corrections
int m_predictionPeriod; //!< How many days ahead to predict passes in
QTime m_passStartTime; //!< Time after which pass must start
QTime m_passFinishTime; //!< Time before which pass must finish
float m_defaultFrequency; //!< Frequency used for Doppler & path loss calculation in satellite table
bool m_drawOnMap;
bool m_autoTarget; //!< Automatically select target on AOS
QString m_aosSpeech; //!< Text to say on AOS
QString m_losSpeech; //!< Text to say on LOS
QString m_aosCommand; //!< Command/script to execute on AOS
QString m_losCommand; //!< Command/script to execute on LOS
QHash<QString, QList<SatelliteDeviceSettings *> *> m_deviceSettings; //!< Settings for each device set for each satellite
int m_columnIndexes[SAT_COL_COLUMNS];//!< How the columns are ordered in the table
int m_columnSizes[SAT_COL_COLUMNS]; //!< Size of the coumns in the table
QString m_title;
quint32 m_rgbColor;
bool m_useReverseAPI;
QString m_reverseAPIAddress;
uint16_t m_reverseAPIPort;
uint16_t m_reverseAPIFeatureSetIndex;
uint16_t m_reverseAPIFeatureIndex;
SatelliteTrackerSettings();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
QByteArray serializeStringList(const QList<QString>& strings) const;
void deserializeStringList(const QByteArray& data, QList<QString>& strings);
QByteArray serializeDeviceSettings(QHash<QString, QList<SatelliteDeviceSettings *> *> deviceSettings) const;
void deserializeDeviceSettings(const QByteArray& data, QHash<QString, QList<SatelliteDeviceSettings *> *>& deviceSettings);
};
#endif // INCLUDE_FEATURE_SATELLITETRACKERSETTINGS_H_

View File

@ -0,0 +1,97 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "satellitetrackersettingsdialog.h"
#include <QDebug>
SatelliteTrackerSettingsDialog::SatelliteTrackerSettingsDialog(SatelliteTrackerSettings *settings,
QWidget* parent) :
QDialog(parent),
m_settings(settings),
ui(new Ui::SatelliteTrackerSettingsDialog)
{
ui->setupUi(this);
ui->height->setValue(settings->m_heightAboveSeaLevel);
ui->predictionPeriod->setValue(settings->m_predictionPeriod);
ui->passStartTime->setTime(settings->m_passStartTime);
ui->passFinishTime->setTime(settings->m_passFinishTime);
ui->minimumAOSElevation->setValue(settings->m_minAOSElevation);
ui->minimumPassElevation->setValue(settings->m_minPassElevation);
ui->aosSpeech->setText(settings->m_aosSpeech);
ui->losSpeech->setText(settings->m_losSpeech);
ui->aosCommand->setText(settings->m_aosCommand);
ui->losCommand->setText(settings->m_losCommand);
ui->updatePeriod->setValue(settings->m_updatePeriod);
ui->dopplerPeriod->setValue(settings->m_dopplerPeriod);
ui->defaultFrequency->setValue(settings->m_defaultFrequency / 1000000.0);
ui->azElUnits->setCurrentIndex((int)settings->m_azElUnits);
ui->groundTrackPoints->setValue(settings->m_groundTrackPoints);
ui->dateFormat->setText(settings->m_dateFormat);
ui->utc->setChecked(settings->m_utc);
ui->drawOnMap->setChecked(settings->m_drawOnMap);
for (int i = 0; i < settings->m_tles.size(); i++)
{
QListWidgetItem *item = new QListWidgetItem(settings->m_tles[i]);
item->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEditable|Qt::ItemIsEnabled);
ui->tles->addItem(item);
}
}
SatelliteTrackerSettingsDialog::~SatelliteTrackerSettingsDialog()
{
delete ui;
}
void SatelliteTrackerSettingsDialog::on_addTle_clicked()
{
QListWidgetItem *item = new QListWidgetItem("http://");
item->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEditable|Qt::ItemIsEnabled);
ui->tles->addItem(item);
}
void SatelliteTrackerSettingsDialog::on_removeTle_clicked()
{
QList<QListWidgetItem *> items = ui->tles->selectedItems();
for (int i = 0; i < items.size(); i++)
delete items[i];
}
void SatelliteTrackerSettingsDialog::accept()
{
m_settings->m_heightAboveSeaLevel = ui->height->value();
m_settings->m_predictionPeriod = ui->predictionPeriod->value();
m_settings->m_passStartTime = ui->passStartTime->time();
m_settings->m_passFinishTime = ui->passFinishTime->time();
m_settings->m_minAOSElevation = ui->minimumAOSElevation->value();
m_settings->m_minPassElevation = ui->minimumPassElevation->value();
m_settings->m_aosSpeech = ui->aosSpeech->text();
m_settings->m_losSpeech = ui->losSpeech->text();
m_settings->m_aosCommand = ui->aosCommand->text();
m_settings->m_losCommand = ui->losCommand->text();
m_settings->m_updatePeriod = (float)ui->updatePeriod->value();
m_settings->m_dopplerPeriod = (float)ui->dopplerPeriod->value();
m_settings->m_defaultFrequency = (float)(ui->defaultFrequency->value() * 1000000.0);
m_settings->m_azElUnits = (SatelliteTrackerSettings::AzElUnits)ui->azElUnits->currentIndex();
m_settings->m_groundTrackPoints = ui->groundTrackPoints->value();
m_settings->m_dateFormat = ui->dateFormat->text();
m_settings->m_utc = ui->utc->isChecked();
m_settings->m_drawOnMap = ui->drawOnMap->isChecked();
m_settings->m_tles.clear();
for (int i = 0; i < ui->tles->count(); i++)
m_settings->m_tles.append(ui->tles->item(i)->text());
QDialog::accept();
}

View File

@ -0,0 +1,42 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_SATELLITETRACKERSETTINGSDIALOG_H
#define INCLUDE_SATELLITETRACKERSETTINGSDIALOG_H
#include "ui_satellitetrackersettingsdialog.h"
#include "satellitetrackersettings.h"
class SatelliteTrackerSettingsDialog : public QDialog {
Q_OBJECT
public:
explicit SatelliteTrackerSettingsDialog(SatelliteTrackerSettings* settings, QWidget* parent = 0);
~SatelliteTrackerSettingsDialog();
SatelliteTrackerSettings *m_settings;
private slots:
void on_addTle_clicked();
void on_removeTle_clicked();
void accept();
private:
Ui::SatelliteTrackerSettingsDialog* ui;
};
#endif // INCLUDE_SATELLITETRACKERSETTINGSDIALOG_H

View File

@ -0,0 +1,576 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SatelliteTrackerSettingsDialog</class>
<widget class="QDialog" name="SatelliteTrackerSettingsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>487</width>
<height>543</height>
</rect>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>Satellite Tracker Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="passesTab">
<attribute name="title">
<string>Passes</string>
</attribute>
<layout class="QFormLayout" name="formLayout_2">
<item row="0" column="0">
<widget class="QLabel" name="heightLabel">
<property name="text">
<string>Antenna height (m ASL)</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QSpinBox" name="height">
<property name="toolTip">
<string>Height of antenna location above sea level in metres</string>
</property>
<property name="minimum">
<number>-1000</number>
</property>
<property name="maximum">
<number>20000</number>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="predictionPeriod">
<property name="toolTip">
<string>Number of days ahead for which passes should be predicted in</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>365</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="minimumAOSElevationLabel">
<property name="text">
<string>Minimum elevation for AOS (°)</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="minimumAOSElevation">
<property name="toolTip">
<string>Enter a minimum elevation in degrees for which AOS (Acquisition of Signal) will be indicated</string>
</property>
<property name="minimum">
<number>-90</number>
</property>
<property name="maximum">
<number>90</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="minimumPassElevationLabel">
<property name="text">
<string>Minimum elevation for pass (°)</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="minimumPassElevation">
<property name="toolTip">
<string>Enter a minimum elevation in degrees a satellite must reach in a pass</string>
</property>
<property name="maximum">
<number>90</number>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="passStartTimeLabel">
<property name="text">
<string>Passes must start after</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QTimeEdit" name="passStartTime"/>
</item>
<item row="5" column="0">
<widget class="QLabel" name="passFinishTimeLabel">
<property name="text">
<string>Passes must finish before</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QTimeEdit" name="passFinishTime">
<property name="time">
<time>
<hour>23</hour>
<minute>59</minute>
<second>59</second>
</time>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="rotatorMaximumAzimuthLabel">
<property name="text">
<string>Rotator maximum azimuth (°)</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QSpinBox" name="rotatorMaximumAzimuth">
<property name="toolTip">
<string>Maximum azimuth angle of rotator in degrees</string>
</property>
<property name="minimum">
<number>360</number>
</property>
<property name="maximum">
<number>720</number>
</property>
<property name="singleStep">
<number>90</number>
</property>
<property name="value">
<number>450</number>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="rotatorMaximumElevationLabel">
<property name="text">
<string>Rotator maximum elevation (°)</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QSpinBox" name="rotatorMaximumElevation">
<property name="toolTip">
<string>Maximum elevation angle of rotator in degrees</string>
</property>
<property name="maximum">
<number>180</number>
</property>
<property name="singleStep">
<number>90</number>
</property>
<property name="value">
<number>180</number>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="aosSpeechLabel">
<property name="text">
<string>AOS speech warning</string>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QLineEdit" name="aosSpeech">
<property name="toolTip">
<string>Text to say when a satellite signal is acquired</string>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="losSpeechLabel">
<property name="text">
<string>LOS speech warning</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QLineEdit" name="losSpeech">
<property name="toolTip">
<string>Text to say when a satellite signal is lost</string>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="aosCommandLabel">
<property name="text">
<string>AOS command</string>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QLineEdit" name="aosCommand">
<property name="toolTip">
<string>Program / script to execute on AOS</string>
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QLabel" name="losCommandLabel">
<property name="text">
<string>LOS command</string>
</property>
</widget>
</item>
<item row="11" column="1">
<widget class="QLineEdit" name="losCommand">
<property name="toolTip">
<string>Program / script to execute on LOS</string>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="dopplerPeriodLabel">
<property name="text">
<string>Doppler period (s)</string>
</property>
</widget>
</item>
<item row="12" column="1">
<widget class="QDoubleSpinBox" name="dopplerPeriod">
<property name="toolTip">
<string>Enter the time in seconds between each Doppler correction</string>
</property>
<property name="minimum">
<double>0.010000000000000</double>
</property>
<property name="maximum">
<double>100.000000000000000</double>
</property>
<property name="value">
<double>10.000000000000000</double>
</property>
</widget>
</item>
<item row="13" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0">
<widget class="QLabel" name="predictionPeriodLabel">
<property name="text">
<string>Prediction period (days)</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tleTab">
<attribute name="title">
<string>TLEs</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QListWidget" name="tles">
<property name="toolTip">
<string>Satellite Two Line Element (TLE) sources</string>
</property>
<property name="editTriggers">
<set>QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked</set>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="ButtonHorizontalLayout">
<item>
<widget class="QPushButton" name="addTle">
<property name="toolTip">
<string>Add TLE</string>
</property>
<property name="text">
<string>+</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeTle">
<property name="toolTip">
<string>Remove selected TLE</string>
</property>
<property name="text">
<string>-</string>
</property>
</widget>
</item>
<item>
<spacer name="buttonHorizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="displayTab">
<attribute name="title">
<string>Display</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_2">
<item row="2" column="1">
<widget class="QDoubleSpinBox" name="defaultFrequency">
<property name="toolTip">
<string>Frequency used for Doppler and free space path loss calculations in the satellite table</string>
</property>
<property name="decimals">
<number>3</number>
</property>
<property name="minimum">
<double>1.000000000000000</double>
</property>
<property name="maximum">
<double>50000.000000000000000</double>
</property>
<property name="singleStep">
<double>100.000000000000000</double>
</property>
<property name="value">
<double>100.000000000000000</double>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLineEdit" name="dateFormat">
<property name="toolTip">
<string>Format for dates displayed in the GUI</string>
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QCheckBox" name="drawOnMap">
<property name="toolTip">
<string>When checked satellite positions are sent to the map</string>
</property>
<property name="text">
<string>Draw satellites on map</string>
</property>
</widget>
</item>
<item row="12" column="0">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="0">
<widget class="QLabel" name="updatePeriodLabel">
<property name="text">
<string>Update period (s)</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QDoubleSpinBox" name="updatePeriod">
<property name="toolTip">
<string>Enter the time in seconds between each calculation of the target's position</string>
</property>
<property name="maximum">
<double>3600.000000000000000</double>
</property>
<property name="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="defaultFrequencyLabel">
<property name="text">
<string>Default frequency (MHz)</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="azElUnits">
<property name="toolTip">
<string>Units used for displaying azimuth and elevation. Either degrees, minutes and seconds or decimal degrees.</string>
</property>
<property name="editable">
<bool>false</bool>
</property>
<item>
<property name="text">
<string>° ' &quot;</string>
</property>
</item>
<item>
<property name="text">
<string>° '</string>
</property>
</item>
<item>
<property name="text">
<string>°</string>
</property>
</item>
<item>
<property name="text">
<string>Decimal</string>
</property>
</item>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="azElUnitsLabel">
<property name="text">
<string>Azimuth and elevation units</string>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QCheckBox" name="utc">
<property name="toolTip">
<string>When checked times are dispayed using UTC rather than the local time zone</string>
</property>
<property name="text">
<string>Display times in UTC</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="dateFormatLabel">
<property name="text">
<string>Date format</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="groundTrackPointsLabel">
<property name="text">
<string>Ground track points</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="groundTrackPoints">
<property name="toolTip">
<string>Number of points in ground tracks (more points result in smoother curves)</string>
</property>
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>360</number>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>tabWidget</tabstop>
<tabstop>height</tabstop>
<tabstop>predictionPeriod</tabstop>
<tabstop>minimumAOSElevation</tabstop>
<tabstop>minimumPassElevation</tabstop>
<tabstop>passStartTime</tabstop>
<tabstop>passFinishTime</tabstop>
<tabstop>rotatorMaximumAzimuth</tabstop>
<tabstop>rotatorMaximumElevation</tabstop>
<tabstop>aosSpeech</tabstop>
<tabstop>losSpeech</tabstop>
<tabstop>aosCommand</tabstop>
<tabstop>losCommand</tabstop>
<tabstop>dopplerPeriod</tabstop>
<tabstop>tles</tabstop>
<tabstop>addTle</tabstop>
<tabstop>removeTle</tabstop>
<tabstop>updatePeriod</tabstop>
<tabstop>defaultFrequency</tabstop>
<tabstop>azElUnits</tabstop>
<tabstop>groundTrackPoints</tabstop>
<tabstop>dateFormat</tabstop>
<tabstop>utc</tabstop>
<tabstop>drawOnMap</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>SatelliteTrackerSettingsDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>SatelliteTrackerSettingsDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,497 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// Copyright (C) 2013 Daniel Warner <contact@danrw.com> //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <cmath>
#include <CoordTopocentric.h>
#include <CoordGeodetic.h>
#include <Observer.h>
#include <SGP4.h>
#include "util/units.h"
#include "satellitetrackersgp4.h"
// Convert QGP4 DateTime to Qt QDataTime
static QDateTime dateTimeToQDateTime(DateTime dt)
{
QDateTime qdt(QDate(dt.Year(), dt.Month(), dt.Day()), QTime(dt.Hour(), dt.Minute(), dt.Second(), (int)(dt.Microsecond()/1000.0)), Qt::UTC);
return qdt;
}
// Convert Qt QDataTime to QGP4 DateTime
static DateTime qDateTimeToDateTime(QDateTime qdt)
{
QDateTime utc = qdt.toUTC();
QDate date = utc.date();
QTime time = utc.time();
DateTime dt(date.year(), date.month(), date.day(), time.hour(), time.minute(), time.second());
return dt;
}
// Get ground track
// Throws SatelliteException, DecayedException and TleException
void getGroundTrack(QDateTime dateTime,
const QString& tle0, const QString& tle1, const QString& tle2,
int steps, bool forward,
QList<QGeoCoordinate *>& coordinates)
{
Tle tle = Tle(tle0.toStdString(), tle1.toStdString(), tle2.toStdString());
SGP4 sgp4(tle);
OrbitalElements ele(tle);
double periodMins;
double timeStep;
// Note map doesn't support paths wrapping around Earth
DateTime currentTime = qDateTimeToDateTime(dateTime);
DateTime endTime;
if (forward)
{
periodMins = ele.Period() * 0.9;
endTime = currentTime.AddMinutes(periodMins);
timeStep = periodMins / (steps * 0.9);
}
else
{
periodMins = ele.Period() * 0.4;
endTime = currentTime.AddMinutes(-periodMins);
timeStep = -periodMins / (steps * 0.4);
}
coordinates.clear();
while (forward && (currentTime < endTime) || !forward && (currentTime > endTime))
{
// Calculate satellite position
Eci eci = sgp4.FindPosition(currentTime);
// Convert satellite position to geodetic coordinates (lat and long)
CoordGeodetic geo = eci.ToGeodetic();
QGeoCoordinate *coord = new QGeoCoordinate(Units::radiansToDegrees(geo.latitude),
Units::radiansToDegrees(geo.longitude),
geo.altitude * 1000.0);
coordinates.append(coord);
// Map is stretched at poles, so use finer steps
if (std::abs(Units::radiansToDegrees(geo.latitude)) >= 70)
currentTime = currentTime.AddMinutes(timeStep/4);
else
currentTime = currentTime.AddMinutes(timeStep);
}
}
// Find azimuth and elevation points during a pass
void getPassAzEl(QLineSeries* azimuth, QLineSeries* elevation, QLineSeries* polar,
const QString& tle0, const QString& tle1, const QString& tle2,
double latitude, double longitude, double altitude,
QDateTime& aos, QDateTime& los)
{
try
{
Tle tle = Tle(tle0.toStdString(), tle1.toStdString(), tle2.toStdString());
SGP4 sgp4(tle);
Observer obs(latitude, longitude, altitude);
DateTime aosTime = qDateTimeToDateTime(aos);
DateTime losTime = qDateTimeToDateTime(los);
DateTime currentTime(aosTime);
int steps = 20;
double timeStep = (losTime - aosTime).TotalSeconds() / steps;
while (currentTime <= losTime)
{
// Calculate satellite position
Eci eci = sgp4.FindPosition(currentTime);
// Calculate angle to satellite from antenna
CoordTopocentric topo = obs.GetLookAngle(eci);
// Save azimuth and elevation in series
QDateTime qdt = dateTimeToQDateTime(currentTime);
if (azimuth != nullptr)
azimuth->append(qdt.toMSecsSinceEpoch(), Units::radiansToDegrees(topo.azimuth));
if (elevation != nullptr)
elevation->append(qdt.toMSecsSinceEpoch(), Units::radiansToDegrees(topo.elevation));
if (polar != nullptr)
polar->append(Units::radiansToDegrees(topo.azimuth), 90.0-Units::radiansToDegrees(topo.elevation));
currentTime = currentTime.AddSeconds(timeStep);
}
}
catch (SatelliteException se)
{
qDebug() << se.what();
}
catch (DecayedException de)
{
qDebug() << de.what();
}
catch (TleException tlee)
{
qDebug() << tlee.what();
}
}
// Get whether a pass passes through 0 degreees
bool getPassesThrough0Deg(const QString& tle0, const QString& tle1, const QString& tle2,
double latitude, double longitude, double altitude,
QDateTime& aos, QDateTime& los)
{
try
{
Tle tle = Tle(tle0.toStdString(), tle1.toStdString(), tle2.toStdString());
SGP4 sgp4(tle);
Observer obs(latitude, longitude, altitude);
DateTime aosTime = qDateTimeToDateTime(aos);
DateTime losTime = qDateTimeToDateTime(los);
DateTime currentTime(aosTime);
int steps = 20;
double timeStep = (losTime - aosTime).TotalSeconds() / steps;
double prevAz;
for (int i = 0; i < steps; i++)
{
// Calculate satellite position
Eci eci = sgp4.FindPosition(currentTime);
// Calculate angle to satellite from antenna
CoordTopocentric topo = obs.GetLookAngle(eci);
double az = Units::radiansToDegrees(topo.azimuth);
if (i == 0)
prevAz = az;
// Does it cross 0 degrees?
if (((prevAz > 270.0) && (az < 90.0)) || ((prevAz < 90.0) && (az >= 270.0)))
return true;
prevAz = az;
currentTime = currentTime.AddSeconds(timeStep);
}
}
catch (SatelliteException se)
{
qDebug() << se.what();
}
catch (DecayedException de)
{
qDebug() << de.what();
}
catch (TleException tlee)
{
qDebug() << tlee.what();
}
return false;
}
// Find maximum elevation in a pass
static double findMaxElevation(Observer& obs1, SGP4& sgp4, const DateTime& aos, const DateTime& los)
{
Observer obs(obs1.GetLocation());
bool running;
double timeStep = (los - aos).TotalSeconds() / 9.0;
DateTime currentTime(aos);
DateTime time1(aos);
DateTime time2(los);
double maxElevation;
do
{
running = true;
maxElevation = -INFINITY;
while (running && (currentTime < time2))
{
Eci eci = sgp4.FindPosition(currentTime);
CoordTopocentric topo = obs.GetLookAngle(eci);
if (topo.elevation > maxElevation)
{
maxElevation = topo.elevation;
currentTime = currentTime.AddSeconds(timeStep);
if (currentTime > time2)
currentTime = time2;
}
else
running = false;
}
time1 = currentTime.AddSeconds(-2.0 * timeStep);
time2 = currentTime;
currentTime = time1;
timeStep = (time2 - time1).TotalSeconds() / 9.0;
}
while (timeStep > 1.0);
return Units::radiansToDegrees(maxElevation);
}
// Find the time at which the satellite crossed the minimum elevation required for AOS or LOS
static DateTime findCrossingPoint(Observer& obs, SGP4& sgp4, const DateTime& initialTime1, const DateTime& initialTime2, double minElevation, bool findingAOS)
{
bool running;
int cnt;
DateTime time1(initialTime1);
DateTime time2(initialTime2);
DateTime middleTime;
running = true;
cnt = 0;
while (running && (cnt++ < 16))
{
middleTime = time1.AddSeconds((time2 - time1).TotalSeconds() / 2.0);
Eci eci = sgp4.FindPosition(middleTime);
CoordTopocentric topo = obs.GetLookAngle(eci);
if (topo.elevation > minElevation)
{
if (findingAOS)
time2 = middleTime;
else
time1 = middleTime;
}
else
{
if (findingAOS)
time1 = middleTime;
else
time2 = middleTime;
}
if ((time2 - time1).TotalSeconds() < 1.0)
{
running = false;
int us = middleTime.Microsecond();
middleTime = middleTime.AddMicroseconds(-us);
middleTime = middleTime.AddSeconds(findingAOS ? 1 : -1);
}
}
running = true;
cnt = 0;
while (running && (cnt++ < 6))
{
Eci eci = sgp4.FindPosition(middleTime);
CoordTopocentric topo = obs.GetLookAngle(eci);
if (topo.elevation > minElevation)
middleTime = middleTime.AddSeconds(findingAOS ? -1 : 1);
else
running = false;
}
return middleTime;
}
// Find when AOS occured, by stepping backwards
static DateTime findAOSBackwards(Observer& obs, SGP4& sgp4, DateTime& startTime,
int predictionPeriod, double minElevation, bool& aosUnknown)
{
DateTime previousTime(startTime);
DateTime currentTime(startTime);
DateTime endTime(startTime.AddDays(-predictionPeriod));
while (currentTime >= endTime)
{
Eci eci = sgp4.FindPosition(currentTime);
CoordTopocentric topo = obs.GetLookAngle(eci);
if (topo.elevation < minElevation)
{
aosUnknown = false;
return findCrossingPoint(obs, sgp4, currentTime, previousTime, minElevation, true);
}
previousTime = currentTime;
currentTime = currentTime - TimeSpan(0, 0, 180);
}
aosUnknown = true;
return currentTime;
}
bool inPassWindow(DateTime dateTime, QTime passStartTime, QTime passEndTime, bool utc)
{
// Don't compare seconds as not currently settable in GUI
QDateTime qdt = dateTimeToQDateTime(dateTime);
if (!utc)
qdt = qdt.toLocalTime();
QTime qt(qdt.time().hour(), qdt.time().minute());
passStartTime = QTime(passStartTime.hour(), passStartTime.minute());
passEndTime = QTime(passEndTime.hour(), passEndTime.minute());
// If passEndTime is before passStartTime, then we allow overnight passes
if (passEndTime > passStartTime)
{
return (qt >= passStartTime) && (qt <= passEndTime);
}
else
{
return (qt <= passEndTime) || (qt >= passStartTime);
}
}
// Create a list of satellite passes, between the given start and end times, that exceed the specified minimum elevation
// We return an uninitalised QDateTime if AOS or LOS is outside of predictionPeriod
static QList<SatellitePass *> createPassList(Observer& obs, SGP4& sgp4, DateTime& startTime,
int predictionPeriod, double minAOSElevation, double minPassElevationDeg,
QTime passStartTime, QTime passEndTime, bool utc,
int noOfPasses)
{
QList<SatellitePass *> passes;
bool aos = false;
bool aosUnknown = true;
double aosAz;
double losAz;
DateTime previousTime(startTime);
DateTime currentTime(startTime);
DateTime endTime(startTime.AddDays(predictionPeriod));
DateTime aosTime;
DateTime losTime;
while (currentTime < endTime)
{
bool endOfPass = false;
Eci eci = sgp4.FindPosition(currentTime);
CoordTopocentric topo = obs.GetLookAngle(eci);
if (!aos && (topo.elevation > minAOSElevation))
{
if (startTime == currentTime)
{
// AOS is before startTime
aosTime = findAOSBackwards(obs, sgp4, startTime, predictionPeriod, minAOSElevation, aosUnknown);
}
else
{
aosTime = findCrossingPoint(obs, sgp4, previousTime, currentTime, minAOSElevation, true);
aosUnknown = false;
}
aos = true;
eci = sgp4.FindPosition(aosTime);
topo = obs.GetLookAngle(eci);
aosAz = Units::radiansToDegrees(topo.azimuth);
}
else if (aos && (topo.elevation < minAOSElevation))
{
aos = false;
endOfPass = true;
losTime = findCrossingPoint(obs, sgp4, previousTime, currentTime, minAOSElevation, false);
eci = sgp4.FindPosition(losTime);
topo = obs.GetLookAngle(eci);
losAz = Units::radiansToDegrees(topo.azimuth);
double maxElevationDeg = findMaxElevation(obs, sgp4, aosTime, losTime);
if ((maxElevationDeg >= minPassElevationDeg)
&& inPassWindow(aosTime, passStartTime, passEndTime, utc)
&& inPassWindow(losTime, passStartTime, passEndTime, utc))
{
SatellitePass *pass = new SatellitePass;
pass->m_aos = aosUnknown ? QDateTime() : dateTimeToQDateTime(aosTime);
pass->m_los = dateTimeToQDateTime(losTime);
pass->m_maxElevation = maxElevationDeg;
pass->m_aosAzimuth = aosAz;
pass->m_losAzimuth = losAz;
pass->m_northToSouth = std::min(360.0-aosAz, aosAz-0.0) < std::min(360.0-losAz, losAz-0.0);
passes.append(pass);
noOfPasses--;
if (noOfPasses <= 0)
return passes;
}
}
previousTime = currentTime;
if (endOfPass)
currentTime = currentTime + TimeSpan(0, 30, 0); // 30 minutes - no orbit likely to be that fast
else
currentTime = currentTime + TimeSpan(0, 0, 180);
if (currentTime > endTime)
currentTime = endTime;
}
if (aos)
{
// Pass still in progress at end time
Eci eci = sgp4.FindPosition(currentTime);
CoordTopocentric topo = obs.GetLookAngle(eci);
losAz = Units::radiansToDegrees(topo.azimuth);
double maxElevationDeg = findMaxElevation(obs, sgp4, aosTime, losTime);
if ((maxElevationDeg >= minPassElevationDeg)
&& inPassWindow(aosTime, passStartTime, passEndTime, utc)
&& inPassWindow(losTime, passStartTime, passEndTime, utc))
{
SatellitePass *pass = new SatellitePass;
pass->m_aos = aosUnknown ? QDateTime() : dateTimeToQDateTime(aosTime);
pass->m_los = QDateTime();
pass->m_aosAzimuth = aosAz;
pass->m_losAzimuth = losAz;
pass->m_maxElevation = maxElevationDeg;
pass->m_northToSouth = std::min(360.0-aosAz, aosAz-0.0) < std::min(360.0-losAz, losAz-0.0);
passes.append(pass);
}
}
return passes;
}
void getSatelliteState(QDateTime dateTime,
const QString& tle0, const QString& tle1, const QString& tle2,
double latitude, double longitude, double altitude,
int predictionPeriod, int minAOSElevationDeg, int minPassElevationDeg,
QTime passStartTime, QTime passFinishTime, bool utc,
int noOfPasses, int groundTrackSteps, SatelliteState *satState)
{
try {
Tle tle = Tle(tle0.toStdString(), tle1.toStdString(), tle2.toStdString());
SGP4 sgp4(tle);
Observer obs(latitude, longitude, altitude);
DateTime dt = qDateTimeToDateTime(dateTime);
// Calculate satellite position
Eci eci = sgp4.FindPosition(dt);
// Calculate angle to satellite from antenna
CoordTopocentric topo = obs.GetLookAngle(eci);
// Convert satellite position to geodetic coordinates (lat and long)
CoordGeodetic geo = eci.ToGeodetic();
satState->m_latitude = Units::radiansToDegrees(geo.latitude);
satState->m_longitude = Units::radiansToDegrees(geo.longitude);
satState->m_altitude = geo.altitude;
satState->m_azimuth = Units::radiansToDegrees(topo.azimuth);
satState->m_elevation = Units::radiansToDegrees(topo.elevation);
satState->m_range = topo.range;
satState->m_rangeRate = topo.range_rate;
OrbitalElements ele(tle);
satState->m_speed = eci.Velocity().Magnitude();
satState->m_period = ele.Period();
if (noOfPasses > 0)
{
qDeleteAll(satState->m_passes);
satState->m_passes = createPassList(obs, sgp4, dt, predictionPeriod,
Units::degreesToRadians((double)minAOSElevationDeg),
minPassElevationDeg,
passStartTime, passFinishTime, utc,
noOfPasses);
}
getGroundTrack(dateTime, tle0, tle1, tle2, groundTrackSteps, false, satState->m_groundTrack);
getGroundTrack(dateTime, tle0, tle1, tle2, groundTrackSteps, true, satState->m_predictedGroundTrack);
}
catch (SatelliteException se)
{
qDebug() << "getSatelliteState: " << satState->m_name << ": " << se.what();
}
catch (DecayedException de)
{
qDebug() << "getSatelliteState: " << satState->m_name << ": " << de.what();
}
catch (TleException tlee)
{
qDebug() << "getSatelliteState: " << satState->m_name << ": " << tlee.what();
}
}

View File

@ -0,0 +1,73 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FEATURE_SATELLITETRACKERSGP4_H_
#define INCLUDE_FEATURE_SATELLITETRACKERSGP4_H_
#include <QList>
#include <QDateTime>
#include <QLineSeries>
#include <QGeoCoordinate>
using namespace QtCharts;
struct SatellitePass {
QDateTime m_aos;
QDateTime m_los;
double m_maxElevation; // Degrees
double m_aosAzimuth; // Degrees
double m_losAzimuth; // Degrees
bool m_northToSouth;
};
struct SatelliteState {
QString m_name;
double m_latitude; // Degrees
double m_longitude; // Degrees
double m_altitude; // km
double m_azimuth; // Degrees
double m_elevation; // Degrees
double m_range; // km
double m_rangeRate; // km/s
double m_speed;
double m_period;
QList<SatellitePass *> m_passes;
QList<QGeoCoordinate *> m_groundTrack;
QList<QGeoCoordinate *> m_predictedGroundTrack;
};
void getGroundTrack(QDateTime dateTime,
const QString& tle0, const QString& tle1, const QString& tle2,
int steps, QList<QGeoCoordinate>& coordinates);
void getSatelliteState(QDateTime dateTime,
const QString& tle0, const QString& tle1, const QString& tle2,
double latitude, double longitude, double altitude,
int predictionPeriod, int minAOSElevationDeg, int minPassElevationDeg,
QTime passStartTime, QTime passFinishTime, bool utc,
int noOfPasses, int groundTrackSteps, SatelliteState *satState);
void getPassAzEl(QLineSeries *azimuth, QLineSeries *elevation, QLineSeries *polar,
const QString& tle0, const QString& tle1, const QString& tle2,
double latitude, double longitude, double altitude,
QDateTime& aos, QDateTime& los);
bool getPassesThrough0Deg(const QString& tle0, const QString& tle1, const QString& tle2,
double latitude, double longitude, double altitude,
QDateTime& aos, QDateTime& los);
#endif // INCLUDE_FEATURE_SATELLITETRACKERSGP4_H_

View File

@ -0,0 +1,52 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// Copyright (C) 2020 Edouard Griffiths, F4EXB. //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "SWGFeatureSettings.h"
#include "satellitetracker.h"
#include "satellitetrackerwebapiadapter.h"
SatelliteTrackerWebAPIAdapter::SatelliteTrackerWebAPIAdapter()
{}
SatelliteTrackerWebAPIAdapter::~SatelliteTrackerWebAPIAdapter()
{}
int SatelliteTrackerWebAPIAdapter::webapiSettingsGet(
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setSatelliteTrackerSettings(new SWGSDRangel::SWGSatelliteTrackerSettings());
response.getSatelliteTrackerSettings()->init();
SatelliteTracker::webapiFormatFeatureSettings(response, m_settings);
return 200;
}
int SatelliteTrackerWebAPIAdapter::webapiSettingsPutPatch(
bool force,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage)
{
(void) force; // no action
(void) errorMessage;
SatelliteTracker::webapiUpdateFeatureSettings(m_settings, featureSettingsKeys, response);
return 200;
}

View File

@ -0,0 +1,50 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// Copyright (C) 2020 Edouard Griffiths, F4EXB. //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_SATELLITETRACKER_WEBAPIADAPTER_H
#define INCLUDE_SATELLITETRACKER_WEBAPIADAPTER_H
#include "feature/featurewebapiadapter.h"
#include "satellitetrackersettings.h"
/**
* Standalone API adapter only for the settings
*/
class SatelliteTrackerWebAPIAdapter : public FeatureWebAPIAdapter {
public:
SatelliteTrackerWebAPIAdapter();
virtual ~SatelliteTrackerWebAPIAdapter();
virtual QByteArray serialize() const { return m_settings.serialize(); }
virtual bool deserialize(const QByteArray& data) { return m_settings.deserialize(data); }
virtual int webapiSettingsGet(
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage);
virtual int webapiSettingsPutPatch(
bool force,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage);
private:
SatelliteTrackerSettings m_settings;
};
#endif // INCLUDE_SATELLITETRACKER_WEBAPIADAPTER_H

View File

@ -0,0 +1,826 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include <cmath>
#include <QDebug>
#include <QAbstractSocket>
#include <QTcpServer>
#include <QTcpSocket>
#include <QEventLoop>
#include <QTimer>
#include <QDateTime>
#include "SWGTargetAzimuthElevation.h"
#include "SWGMapItem.h"
#include "webapi/webapiadapterinterface.h"
#include "webapi/webapiutils.h"
#include "util/units.h"
#include "device/deviceset.h"
#include "device/deviceapi.h"
#include "channel/channelwebapiutils.h"
#include "maincore.h"
#include "satellitetracker.h"
#include "satellitetrackerworker.h"
#include "satellitetrackerreport.h"
#include "satellitetrackersgp4.h"
MESSAGE_CLASS_DEFINITION(SatelliteTrackerWorker::MsgConfigureSatelliteTrackerWorker, Message)
MESSAGE_CLASS_DEFINITION(SatelliteTrackerReport::MsgReportSat, Message)
MESSAGE_CLASS_DEFINITION(SatelliteTrackerReport::MsgReportAOS, Message)
MESSAGE_CLASS_DEFINITION(SatelliteTrackerReport::MsgReportLOS, Message)
MESSAGE_CLASS_DEFINITION(SatelliteTrackerReport::MsgReportTarget, Message)
SatelliteTrackerWorker::SatelliteTrackerWorker(SatelliteTracker* satelliteTracker, WebAPIAdapterInterface *webAPIAdapterInterface) :
m_satelliteTracker(satelliteTracker),
m_webAPIAdapterInterface(webAPIAdapterInterface),
m_msgQueueToFeature(nullptr),
m_msgQueueToGUI(nullptr),
m_running(false),
m_mutex(QMutex::Recursive),
m_recalculatePasses(true),
m_flipRotation(false),
m_extendedAzRotation(false)
{
connect(&m_pollTimer, SIGNAL(timeout()), this, SLOT(update()));
}
SatelliteTrackerWorker::~SatelliteTrackerWorker()
{
m_inputMessageQueue.clear();
}
void SatelliteTrackerWorker::reset()
{
QMutexLocker mutexLocker(&m_mutex);
m_inputMessageQueue.clear();
}
bool SatelliteTrackerWorker::startWork()
{
QMutexLocker mutexLocker(&m_mutex);
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
m_pollTimer.start((int)round(m_settings.m_updatePeriod*1000.0));
// Resume doppler timers
QHashIterator<QString, SatWorkerState *> itr(m_workerState);
while (itr.hasNext())
{
itr.next();
SatWorkerState *satWorkerState = itr.value();
if (satWorkerState->m_dopplerTimer.interval() > 0)
satWorkerState->m_dopplerTimer.start();
}
m_recalculatePasses = true;
m_running = true;
return m_running;
}
void SatelliteTrackerWorker::stopWork()
{
QMutexLocker mutexLocker(&m_mutex);
disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
m_pollTimer.stop();
// Stop doppler timers
QHashIterator<QString, SatWorkerState *> itr(m_workerState);
while (itr.hasNext())
{
itr.next();
itr.value()->m_dopplerTimer.stop();
}
m_running = false;
}
void SatelliteTrackerWorker::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
if (handleMessage(*message)) {
delete message;
}
}
}
bool SatelliteTrackerWorker::handleMessage(const Message& message)
{
if (MsgConfigureSatelliteTrackerWorker::match(message))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigureSatelliteTrackerWorker& cfg = (MsgConfigureSatelliteTrackerWorker&) message;
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (SatelliteTracker::MsgSatData::match(message))
{
SatelliteTracker::MsgSatData& satData = (SatelliteTracker::MsgSatData&) message;
m_satellites = satData.getSatellites();
m_recalculatePasses = true;
return true;
}
else
{
return false;
}
}
void SatelliteTrackerWorker::applySettings(const SatelliteTrackerSettings& settings, bool force)
{
qDebug() << "SatelliteTrackerWorker::applySettings:"
<< " m_target: " << settings.m_target
<< " m_satellites: " << settings.m_satellites
<< " m_dateTime: " << settings.m_dateTime
<< " m_utc: " << settings.m_utc
<< " m_updatePeriod: " << settings.m_updatePeriod
<< " force: " << force;
if ((m_settings.m_target != settings.m_target)
|| (m_settings.m_latitude != settings.m_latitude)
|| (m_settings.m_longitude != settings.m_longitude)
|| (m_settings.m_heightAboveSeaLevel != settings.m_heightAboveSeaLevel)
|| (m_settings.m_dateTime != settings.m_dateTime)
|| (m_settings.m_utc != settings.m_utc)
|| (m_settings.m_groundTrackPoints != settings.m_groundTrackPoints)
|| (m_settings.m_minAOSElevation != settings.m_minAOSElevation)
|| (m_settings.m_minPassElevation != settings.m_minPassElevation)
|| (m_settings.m_predictionPeriod != settings.m_predictionPeriod)
|| (m_settings.m_passStartTime != settings.m_passStartTime)
|| (m_settings.m_passFinishTime != settings.m_passFinishTime)
|| (!m_settings.m_drawOnMap && settings.m_drawOnMap)
|| force)
{
// Recalculate immediately
m_recalculatePasses = true;
QTimer::singleShot(1, this, &SatelliteTrackerWorker::update);
m_pollTimer.start((int)round(settings.m_updatePeriod*1000.0));
}
else if ((m_settings.m_updatePeriod != settings.m_updatePeriod) || force)
{
m_pollTimer.start((int)round(settings.m_updatePeriod*1000.0));
}
if (!settings.m_drawOnMap && m_settings.m_drawOnMap)
{
QHashIterator<QString, SatWorkerState *> itr(m_workerState);
while (itr.hasNext())
{
itr.next();
removeFromMap(itr.key());
}
}
// Remove satellites no longer needed
QMutableHashIterator<QString, SatWorkerState *> itr(m_workerState);
while (itr.hasNext())
{
itr.next();
if (settings.m_satellites.indexOf(itr.key()) == -1)
itr.remove();
}
// Add new satellites
for (int i = 0; i < settings.m_satellites.size(); i++)
{
if (!m_workerState.contains(settings.m_satellites[i]))
{
SatWorkerState *satWorkerState = new SatWorkerState(settings.m_satellites[i]);
m_workerState.insert(settings.m_satellites[i], satWorkerState);
connect(&satWorkerState->m_aosTimer, &QTimer::timeout, [this, satWorkerState]() {
aos(satWorkerState);
});
connect(&satWorkerState->m_losTimer, &QTimer::timeout, [this, satWorkerState]() {
los(satWorkerState);
});
m_recalculatePasses = true;
}
}
m_settings = settings;
}
void SatelliteTrackerWorker::removeFromMap(QString id)
{
MessagePipes& messagePipes = MainCore::instance()->getMessagePipes();
QList<MessageQueue*> *mapMessageQueues = messagePipes.getMessageQueues(m_satelliteTracker, "mapitems");
if (mapMessageQueues)
sendToMap(mapMessageQueues, id, "", "", 0.0, 0.0, 0.0, 0.0, nullptr, nullptr);
}
void SatelliteTrackerWorker::sendToMap(QList<MessageQueue*> *mapMessageQueues,
QString name, QString image, QString text,
double lat, double lon, double altitude, double rotation,
QList<QGeoCoordinate *> *track, QList<QGeoCoordinate *> *predictedTrack)
{
QList<MessageQueue*>::iterator it = mapMessageQueues->begin();
for (; it != mapMessageQueues->end(); ++it)
{
SWGSDRangel::SWGMapItem *swgMapItem = new SWGSDRangel::SWGMapItem();
swgMapItem->setName(new QString(name));
swgMapItem->setLatitude(lat);
swgMapItem->setLongitude(lon);
swgMapItem->setAltitude(altitude);
swgMapItem->setImage(new QString(image));
swgMapItem->setImageRotation(rotation);
swgMapItem->setText(new QString(text));
swgMapItem->setImageMinZoom(0);
if (track != nullptr)
{
QList<SWGSDRangel::SWGMapCoordinate *> *mapTrack = new QList<SWGSDRangel::SWGMapCoordinate *>();
for (int i = 0; i < track->size(); i++)
{
SWGSDRangel::SWGMapCoordinate* p = new SWGSDRangel::SWGMapCoordinate();
QGeoCoordinate *c = track->at(i);
p->setLatitude(c->latitude());
p->setLongitude(c->longitude());
p->setAltitude(c->altitude());
mapTrack->append(p);
}
swgMapItem->setTrack(mapTrack);
}
if (predictedTrack != nullptr)
{
QList<SWGSDRangel::SWGMapCoordinate *> *mapTrack = new QList<SWGSDRangel::SWGMapCoordinate *>();
for (int i = 0; i < predictedTrack->size(); i++)
{
SWGSDRangel::SWGMapCoordinate* p = new SWGSDRangel::SWGMapCoordinate();
QGeoCoordinate *c = predictedTrack->at(i);
p->setLatitude(c->latitude());
p->setLongitude(c->longitude());
p->setAltitude(c->altitude());
mapTrack->append(p);
}
swgMapItem->setPredictedTrack(mapTrack);
}
MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_satelliteTracker, swgMapItem);
(*it)->push(msg);
}
}
void SatelliteTrackerWorker::update()
{
// Get date and time to calculate position at
QDateTime qdt;
if (m_settings.m_dateTime == "")
qdt = QDateTime::currentDateTimeUtc();
else if (m_settings.m_utc)
qdt = QDateTime::fromString(m_settings.m_dateTime, Qt::ISODateWithMs);
else
qdt = QDateTime::fromString(m_settings.m_dateTime, Qt::ISODateWithMs).toUTC();
QHashIterator<QString, SatWorkerState *> itr(m_workerState);
while (itr.hasNext())
{
itr.next();
SatWorkerState *satWorkerState = itr.value();
QString name = satWorkerState->m_name;
if (m_satellites.contains(name))
{
SatNogsSatellite *sat = m_satellites.value(name);
if (sat->m_tle != nullptr)
{
// Calculate position, AOS/LOS and other details for satellite
int noOfPasses;
bool recalcAsPastLOS = (satWorkerState->m_satState.m_passes.size() > 0) && (satWorkerState->m_satState.m_passes[0]->m_los < qdt);
if (m_recalculatePasses || recalcAsPastLOS)
noOfPasses = (name == m_settings.m_target) ? 99 : 1;
else
noOfPasses = 0;
getSatelliteState(qdt, sat->m_tle->m_tle0, sat->m_tle->m_tle1, sat->m_tle->m_tle2,
m_settings.m_latitude, m_settings.m_longitude, m_settings.m_heightAboveSeaLevel/1000.0,
m_settings.m_predictionPeriod, m_settings.m_minAOSElevation, m_settings.m_minPassElevation,
m_settings.m_passStartTime, m_settings.m_passFinishTime, m_settings.m_utc,
noOfPasses, m_settings.m_groundTrackPoints, &satWorkerState->m_satState);
// Update AOS/LOS (only set timers if using real time)
if ((m_settings.m_dateTime == "") && (satWorkerState->m_satState.m_passes.size() > 0))
{
/*int min = 8;
QDateTime p1a = QDateTime(QDateTime::currentDateTime().date(), QTime(16, min, 0));
QDateTime p1s = QDateTime(QDateTime::currentDateTime().date(), QTime(16, min, 30));
QDateTime p2a = QDateTime(QDateTime::currentDateTime().date(), QTime(16, min+1, 0));
QDateTime p2s = QDateTime(QDateTime::currentDateTime().date(), QTime(16, min+1, 30));
if (qdt > p1a)
{
satWorkerState->m_satState.m_passes[0]->m_aos = p2a;
satWorkerState->m_satState.m_passes[0]->m_los = p2s;
}
else
{
satWorkerState->m_satState.m_passes[0]->m_aos = p1a;
satWorkerState->m_satState.m_passes[0]->m_los = p1s;
} */
/* if (name == "NOAA 18")
{
satWorkerState->m_satState.m_passes[0]->m_aos = QDateTime(QDateTime::currentDateTime().date(), QTime(11, 10, 0));
satWorkerState->m_satState.m_passes[0]->m_los = QDateTime(QDateTime::currentDateTime().date(), QTime(11, 10, 30));
} */
/*if (name == "ISS")
{
if (m_settings.m_minAOSElevation == 5)
{
qDebug() << "*********** seting first AOS";
satWorkerState->m_satState.m_passes[0]->m_aos = QDateTime(QDateTime::currentDateTime().date(), QTime(14, 10, 0));
satWorkerState->m_satState.m_passes[0]->m_los = QDateTime(QDateTime::currentDateTime().date(), QTime(14, 10, 30));
}
else
{
qDebug() << "*********** seting second AOS";
satWorkerState->m_satState.m_passes[0]->m_aos = QDateTime(QDateTime::currentDateTime().date(), QTime(14, 11, 0));
satWorkerState->m_satState.m_passes[0]->m_los = QDateTime(QDateTime::currentDateTime().date(), QTime(14, 11, 30));
}
}*/
// Do we have a new AOS?
if ((satWorkerState->m_aos != satWorkerState->m_satState.m_passes[0]->m_aos) || (satWorkerState->m_los != satWorkerState->m_satState.m_passes[0]->m_los))
{
qDebug() << "New AOS: " << name << " new: " << satWorkerState->m_satState.m_passes[0]->m_aos << " old: " << satWorkerState->m_aos;
qDebug() << "New LOS: " << name << " new: " << satWorkerState->m_satState.m_passes[0]->m_los << " old: " << satWorkerState->m_los;
satWorkerState->m_aos = satWorkerState->m_satState.m_passes[0]->m_aos;
satWorkerState->m_los = satWorkerState->m_satState.m_passes[0]->m_los;
if (satWorkerState->m_aos.isValid())
{
if (satWorkerState->m_aos > qdt)
{
satWorkerState->m_aosTimer.setInterval(satWorkerState->m_aos.toMSecsSinceEpoch() - qdt.toMSecsSinceEpoch());
satWorkerState->m_aosTimer.setSingleShot(true);
satWorkerState->m_aosTimer.start();
}
else if (qdt < satWorkerState->m_los)
aos(satWorkerState);
if (satWorkerState->m_los.isValid() && (m_settings.m_target == satWorkerState->m_name))
calculateRotation(satWorkerState);
}
if (satWorkerState->m_los.isValid() && (satWorkerState->m_los > qdt))
{
if (satWorkerState->m_losTimer.isActive() && (satWorkerState->m_losTimer.remainingTime() == 0))
{
qDebug() << "****** m_losTimer.remainingTime: " << satWorkerState->m_losTimer.remainingTime();
qDebug() << "****** m_losTimer.active: " << satWorkerState->m_losTimer.isActive();
// LOS hasn't been called yet - do so, before we reset timer
los(satWorkerState);
}
satWorkerState->m_losTimer.setInterval(satWorkerState->m_los.toMSecsSinceEpoch() - qdt.toMSecsSinceEpoch());
satWorkerState->m_losTimer.setSingleShot(true);
satWorkerState->m_losTimer.start();
}
}
}
else
{
satWorkerState->m_aos = QDateTime();
satWorkerState->m_los = QDateTime();
satWorkerState->m_aosTimer.stop();
satWorkerState->m_losTimer.stop();
}
// Send Az/El of target to Rotator Controllers, if elevation above horizon
if ((name == m_settings.m_target) && (satWorkerState->m_satState.m_elevation >= 0))
{
double azimuth = satWorkerState->m_satState.m_azimuth;
double elevation = satWorkerState->m_satState.m_elevation;
if (m_extendedAzRotation)
{
if (azimuth < 180.0)
azimuth += 360.0;
}
else if (m_flipRotation)
{
azimuth = std::fmod(azimuth + 180.0, 360.0);
elevation = 180.0 - elevation;
}
MessagePipes& messagePipes = MainCore::instance()->getMessagePipes();
QList<MessageQueue*> *rotatorMessageQueues = messagePipes.getMessageQueues(m_satelliteTracker, "target");
if (rotatorMessageQueues)
{
QList<MessageQueue*>::iterator it = rotatorMessageQueues->begin();
for (; it != rotatorMessageQueues->end(); ++it)
{
SWGSDRangel::SWGTargetAzimuthElevation *swgTarget = new SWGSDRangel::SWGTargetAzimuthElevation();
swgTarget->setName(new QString(m_settings.m_target));
swgTarget->setAzimuth(azimuth);
swgTarget->setElevation(elevation);
(*it)->push(MainCore::MsgTargetAzimuthElevation::create(m_satelliteTracker, swgTarget));
}
}
}
// Send to Map
if (m_settings.m_drawOnMap)
{
MessagePipes& messagePipes = MainCore::instance()->getMessagePipes();
QList<MessageQueue*> *mapMessageQueues = messagePipes.getMessageQueues(m_satelliteTracker, "mapitems");
if (mapMessageQueues)
{
QString image;
if (sat->m_name == "ISS")
image = "qrc:///satellitetracker/satellitetracker/iss-32.png";
else
image = "qrc:///satellitetracker/satellitetracker/satellite-32.png";
QString text = QString("Name: %1\nAltitude: %2 km\nRange: %3 km\nRange rate: %4 km/s\nSpeed: %5 km/h\nPeriod: %6 mins")
.arg(sat->m_name)
.arg((int)round(satWorkerState->m_satState.m_altitude))
.arg((int)round(satWorkerState->m_satState.m_range))
.arg(satWorkerState->m_satState.m_rangeRate, 0, 'f', 1)
.arg(Units::kmpsToIntegerKPH(satWorkerState->m_satState.m_speed))
.arg((int)round(satWorkerState->m_satState.m_period));
if (satWorkerState->m_satState.m_passes.size() > 0)
{
if ((qdt >= satWorkerState->m_satState.m_passes[0]->m_aos) && (qdt <= satWorkerState->m_satState.m_passes[0]->m_los))
text = text.append("\nSatellite is visible");
else
text = text.append("\nAOS in: %1 mins").arg((int)round((satWorkerState->m_satState.m_passes[0]->m_aos.toSecsSinceEpoch() - qdt.toSecsSinceEpoch())/60.0));
text = QString("%1\nAOS: %2\nLOS: %3\nMax El: %4%5")
.arg(text)
.arg(satWorkerState->m_satState.m_passes[0]->m_aos.toString(m_settings.m_dateFormat + " hh:mm"))
.arg(satWorkerState->m_satState.m_passes[0]->m_los.toString(m_settings.m_dateFormat + " hh:mm"))
.arg((int)round(satWorkerState->m_satState.m_passes[0]->m_maxElevation))
.arg(QChar(0xb0));
}
sendToMap(mapMessageQueues, sat->m_name, image, text,
satWorkerState->m_satState.m_latitude, satWorkerState->m_satState.m_longitude,
satWorkerState->m_satState.m_altitude * 1000.0, 0,
&satWorkerState->m_satState.m_groundTrack, &satWorkerState->m_satState.m_predictedGroundTrack);
}
}
// Send to GUI
if (getMessageQueueToGUI())
getMessageQueueToGUI()->push(SatelliteTrackerReport::MsgReportSat::create(new SatelliteState(satWorkerState->m_satState)));
}
else
qDebug() << "SatelliteTrackerWorker::update: No TLE for " << sat->m_name << ". Can't compute position.";
}
}
m_recalculatePasses = false;
}
void SatelliteTrackerWorker::aos(SatWorkerState *satWorkerState)
{
qDebug() << "SatelliteTrackerWorker::aos " << satWorkerState->m_name;
// Indicate AOS to GUI
if (getMessageQueueToGUI())
{
int durationMins = (int)round((satWorkerState->m_los.toSecsSinceEpoch() - satWorkerState->m_aos.toSecsSinceEpoch())/60.0);
int maxElevation = 0;
if (satWorkerState->m_satState.m_passes.size() > 0)
maxElevation = satWorkerState->m_satState.m_passes[0]->m_maxElevation;
getMessageQueueToGUI()->push(SatelliteTrackerReport::MsgReportAOS::create(satWorkerState->m_name, durationMins, maxElevation));
}
// Update target
if (m_settings.m_autoTarget && (satWorkerState->m_name != m_settings.m_target))
{
// Only switch if higher priority (earlier in list) or other target not in AOS
SatWorkerState *targetSatWorkerState = m_workerState.value(m_settings.m_target);
int currentTargetIdx = m_settings.m_satellites.indexOf(m_settings.m_target);
int newTargetIdx = m_settings.m_satellites.indexOf(satWorkerState->m_name);
if ((newTargetIdx < currentTargetIdx) || !targetSatWorkerState->hasAOS())
{
// Stop doppler correction for current target
if (m_workerState.contains(m_settings.m_target))
m_workerState.value(m_settings.m_target)->m_dopplerTimer.stop();
qDebug() << "SatelliteTrackerWorker::aos - autoTarget setting " << satWorkerState->m_name;
m_settings.m_target = satWorkerState->m_name;
// Update GUI with new target
if (getMessageQueueToGUI())
getMessageQueueToGUI()->push(SatelliteTrackerReport::MsgReportTarget::create(satWorkerState->m_name));
}
}
// TODO: Detect if different device sets are used and support multiple sats simultaneously
if (m_settings.m_target == satWorkerState->m_name)
applyDeviceAOSSettings(satWorkerState->m_name);
}
// Determine if we need to flip rotator or use extended azimuth to avoid 360/0 discontinuity
void SatelliteTrackerWorker::calculateRotation(SatWorkerState *satWorkerState)
{
m_flipRotation = false;
m_extendedAzRotation = false;
if (satWorkerState->m_satState.m_passes.size() > 0)
{
SatNogsSatellite *sat = m_satellites.value(satWorkerState->m_name);
bool passes0 = getPassesThrough0Deg(sat->m_tle->m_tle0, sat->m_tle->m_tle1, sat->m_tle->m_tle2,
m_settings.m_latitude, m_settings.m_longitude, m_settings.m_heightAboveSeaLevel/1000.0,
satWorkerState->m_satState.m_passes[0]->m_aos, satWorkerState->m_satState.m_passes[0]->m_los);
if (passes0)
{
double aosAz = satWorkerState->m_satState.m_passes[0]->m_aosAzimuth;
double losAz = satWorkerState->m_satState.m_passes[0]->m_losAzimuth;
double minAz = std::min(aosAz, losAz);
if ((m_settings.m_rotatorMaxAzimuth - 360.0) > minAz)
m_extendedAzRotation = true;
else if (m_settings.m_rotatorMaxElevation == 180.0)
m_flipRotation = true;
}
}
}
void SatelliteTrackerWorker::applyDeviceAOSSettings(const QString& name)
{
// Execute global program/script
if (!m_settings.m_aosCommand.isEmpty())
{
qDebug() << "SatelliteTrackerWorker::aos: executing command: " << m_settings.m_aosCommand;
QProcess::startDetached(m_settings.m_aosCommand);
}
// Update device set
if (m_settings.m_deviceSettings.contains(name))
{
QList<SatelliteTrackerSettings::SatelliteDeviceSettings *> *m_deviceSettingsList = m_settings.m_deviceSettings.value(name);
MainCore *mainCore = MainCore::instance();
// Load presets
for (int i = 0; i < m_deviceSettingsList->size(); i++)
{
SatelliteTrackerSettings::SatelliteDeviceSettings *devSettings = m_deviceSettingsList->at(i);
if (!devSettings->m_presetGroup.isEmpty() && !devSettings->m_deviceSet.isEmpty())
{
const MainSettings& mainSettings = mainCore->getSettings();
QString presetType = QString(devSettings->m_deviceSet[0]);
const Preset* preset = mainSettings.getPreset(devSettings->m_presetGroup, devSettings->m_presetFrequency, devSettings->m_presetDescription, presetType);
if (preset != nullptr)
{
qDebug() << "SatelliteTrackerWorker::aos: Loading preset " << preset->getDescription() << " to " << devSettings->m_deviceSet[0];
int deviceSetIndex = devSettings->m_deviceSet.mid(1).toInt();
std::vector<DeviceSet*>& deviceSets = mainCore->getDeviceSets();
if (deviceSetIndex < deviceSets.size())
{
MainCore::MsgLoadPreset *msg = MainCore::MsgLoadPreset::create(preset, deviceSetIndex);
mainCore->getMainMessageQueue()->push(msg);
}
else
qWarning() << "SatelliteTrackerWorker::aos: device set " << devSettings->m_deviceSet << " does not exist";
}
else
qWarning() << "SatelliteTrackerWorker::aos: Unable to get preset: " << devSettings->m_presetGroup << " " << devSettings->m_presetFrequency << " " << devSettings->m_presetDescription;
}
}
// Wait a little bit for presets to load before performing other steps
QTimer::singleShot(1000, [this, mainCore, name, m_deviceSettingsList]()
{
for (int i = 0; i < m_deviceSettingsList->size(); i++)
{
SatelliteTrackerSettings::SatelliteDeviceSettings *devSettings = m_deviceSettingsList->at(i);
// Override frequency
if (devSettings->m_frequency != 0)
{
qDebug() << "SatelliteTrackerWorker::aos: setting frequency to: " << devSettings->m_frequency;
int deviceSetIndex = devSettings->m_deviceSet.mid(1).toInt();
ChannelWebAPIUtils::setCenterFrequency(deviceSetIndex, devSettings->m_frequency);
}
// Execute per satellite program/script
if (!devSettings->m_aosCommand.isEmpty())
{
qDebug() << "SatelliteTrackerWorker::aos: executing command: " << devSettings->m_aosCommand;
QProcess::startDetached(devSettings->m_aosCommand);
}
}
// Start acquisition - Need to use WebAPI, in order for GUI to correctly reflect being started
for (int i = 0; i < m_deviceSettingsList->size(); i++)
{
SatelliteTrackerSettings::SatelliteDeviceSettings *devSettings = m_deviceSettingsList->at(i);
if (devSettings->m_startOnAOS)
{
qDebug() << "SatelliteTrackerWorker::aos: starting acqusition";
int deviceSetIndex = devSettings->m_deviceSet.mid(1).toInt();
ChannelWebAPIUtils::run(deviceSetIndex);
}
}
for (int i = 0; i < m_deviceSettingsList->size(); i++)
{
SatelliteTrackerSettings::SatelliteDeviceSettings *devSettings = m_deviceSettingsList->at(i);
// Start file sinks (See issue #782 - currently must occur after starting acqusition)
if (devSettings->m_startStopFileSink)
{
qDebug() << "SatelliteTrackerWorker::aos: starting file sinks";
int deviceSetIndex = devSettings->m_deviceSet.mid(1).toInt();
ChannelWebAPIUtils::startStopFileSinks(deviceSetIndex, true);
}
}
// Send AOS message to channels
SatWorkerState *satWorkerState = m_workerState.value(name);
ChannelWebAPIUtils::satelliteAOS(name, satWorkerState->m_satState.m_passes[0]->m_northToSouth);
// Start Doppler correction, if needed
satWorkerState->m_initFrequencyOffset.clear();
bool requiresDoppler = false;
for (int i = 0; i < m_deviceSettingsList->size(); i++)
{
SatelliteTrackerSettings::SatelliteDeviceSettings *devSettings = m_deviceSettingsList->at(i);
if (devSettings->m_doppler.size() > 0)
{
requiresDoppler = true;
for (int j = 0; j < devSettings->m_doppler.size(); j++)
{
int offset;
int deviceSetIndex = devSettings->m_deviceSet.mid(1).toInt();
if (ChannelWebAPIUtils::getFrequencyOffset(deviceSetIndex, devSettings->m_doppler[j], offset))
{
satWorkerState->m_initFrequencyOffset.append(offset);
qDebug() << "SatelliteTrackerWorker::applyDeviceAOSSettings: Initial frequency offset: " << offset;
}
else
{
qDebug() << "SatelliteTrackerWorker::applyDeviceAOSSettings: Failed to get initial frequency offset";
satWorkerState->m_initFrequencyOffset.append(0);
}
}
}
}
if (requiresDoppler)
{
satWorkerState->m_dopplerTimer.setInterval(m_settings.m_dopplerPeriod);
satWorkerState->m_dopplerTimer.start();
connect(&satWorkerState->m_dopplerTimer, &QTimer::timeout, [this, satWorkerState]() {
doppler(satWorkerState);
});
}
});
}
else
{
// Send AOS message to channels
SatWorkerState *satWorkerState = m_workerState.value(name);
ChannelWebAPIUtils::satelliteAOS(name, satWorkerState->m_satState.m_passes[0]->m_northToSouth);
}
}
void SatelliteTrackerWorker::doppler(SatWorkerState *satWorkerState)
{
qDebug() << "SatelliteTrackerWorker::doppler " << satWorkerState->m_name;
QList<SatelliteTrackerSettings::SatelliteDeviceSettings *> *m_deviceSettingsList = m_settings.m_deviceSettings.value(satWorkerState->m_name);
if (m_deviceSettingsList != nullptr)
{
for (int i = 0; i < m_deviceSettingsList->size(); i++)
{
SatelliteTrackerSettings::SatelliteDeviceSettings *devSettings = m_deviceSettingsList->at(i);
if (devSettings->m_doppler.size() > 0)
{
// Get center frequency for this device
int deviceSetIndex = devSettings->m_deviceSet.mid(1).toInt();
double centerFrequency;
if (ChannelWebAPIUtils::getCenterFrequency(deviceSetIndex, centerFrequency))
{
// Calculate frequency delta due to Doppler
double c = 299792458.0;
double deltaF = centerFrequency * satWorkerState->m_satState.m_rangeRate * 1000.0 / c;
for (int j = 0; j < devSettings->m_doppler.size(); j++)
{
// For receive, we subtract, transmit we add
int offset;
if (devSettings->m_deviceSet[0] == "R")
offset = satWorkerState->m_initFrequencyOffset[i] - (int)round(deltaF);
else
offset = satWorkerState->m_initFrequencyOffset[i] + (int)round(deltaF);
if (!ChannelWebAPIUtils::setFrequencyOffset(deviceSetIndex, devSettings->m_doppler[j], offset))
qDebug() << "SatelliteTrackerWorker::doppler: Failed to set frequency offset";
}
}
else
qDebug() << "SatelliteTrackerWorker::doppler: couldn't get centre frequency for " << devSettings->m_deviceSet;
}
}
}
}
void SatelliteTrackerWorker::los(SatWorkerState *satWorkerState)
{
qDebug() << "SatelliteTrackerWorker::los " << satWorkerState->m_name;
// Indicate LOS to GUI
if (getMessageQueueToGUI())
getMessageQueueToGUI()->push(SatelliteTrackerReport::MsgReportLOS::create(satWorkerState->m_name));
// Stop Doppler timer, and set interval to 0, so we don't restart it in start()
satWorkerState->m_dopplerTimer.stop();
satWorkerState->m_dopplerTimer.setInterval(0);
if (m_settings.m_target == satWorkerState->m_name)
{
// Execute program/script
if (!m_settings.m_losCommand.isEmpty())
{
qDebug() << "SatelliteTrackerWorker::los: executing command: " << m_settings.m_losCommand;
QProcess::startDetached(m_settings.m_losCommand);
}
if (m_settings.m_deviceSettings.contains(satWorkerState->m_name))
{
QList<SatelliteTrackerSettings::SatelliteDeviceSettings *> *m_deviceSettingsList = m_settings.m_deviceSettings.value(satWorkerState->m_name);
// Stop file sinks
for (int i = 0; i < m_deviceSettingsList->size(); i++)
{
SatelliteTrackerSettings::SatelliteDeviceSettings *devSettings = m_deviceSettingsList->at(i);
if (devSettings->m_startStopFileSink)
{
qDebug() << "SatelliteTrackerWorker::los: stopping file sinks";
int deviceSetIndex = devSettings->m_deviceSet.mid(1).toInt();
ChannelWebAPIUtils::startStopFileSinks(deviceSetIndex, false);
}
}
// Send LOS message to channels
ChannelWebAPIUtils::satelliteLOS(satWorkerState->m_name);
// Stop acquisition
for (int i = 0; i < m_deviceSettingsList->size(); i++)
{
SatelliteTrackerSettings::SatelliteDeviceSettings *devSettings = m_deviceSettingsList->at(i);
if (devSettings->m_stopOnLOS)
{
int deviceSetIndex = devSettings->m_deviceSet.mid(1).toInt();
ChannelWebAPIUtils::stop(deviceSetIndex);
}
}
// Execute per satellite program/script
// Do after stopping acquisition, so files are closed by file sink
for (int i = 0; i < m_deviceSettingsList->size(); i++)
{
SatelliteTrackerSettings::SatelliteDeviceSettings *devSettings = m_deviceSettingsList->at(i);
if (!devSettings->m_losCommand.isEmpty())
{
qDebug() << "SatelliteTrackerWorker::los: executing command: " << devSettings->m_losCommand;
QProcess::startDetached(devSettings->m_losCommand);
}
}
}
}
// Is another lower-priority satellite with AOS available to switch to?
if (m_settings.m_autoTarget)
{
for (int i = m_settings.m_satellites.indexOf(m_settings.m_target) + 1; i < m_settings.m_satellites.size(); i++)
{
if (m_workerState.contains(m_settings.m_satellites[i]))
{
SatWorkerState *newSatWorkerState = m_workerState.value(m_settings.m_satellites[i]);
if (newSatWorkerState->hasAOS())
{
qDebug() << "SatelliteTrackerWorker::los - autoTarget setting " << m_settings.m_satellites[i];
m_settings.m_target = m_settings.m_satellites[i];
// Update GUI with new target
if (getMessageQueueToGUI())
getMessageQueueToGUI()->push(SatelliteTrackerReport::MsgReportTarget::create(m_settings.m_target));
// Apply device settings
applyDeviceAOSSettings(m_settings.m_target);
break;
}
}
}
}
m_recalculatePasses = true;
}

View File

@ -0,0 +1,144 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_FEATURE_SATELLITETRACKERWORKER_H_
#define INCLUDE_FEATURE_SATELLITETRACKERWORKER_H_
#include <QObject>
#include <QTimer>
#include <QAbstractSocket>
#include "util/message.h"
#include "util/messagequeue.h"
#include "util/astronomy.h"
#include "satellitetrackersettings.h"
#include "satellitetrackersgp4.h"
#include "satnogs.h"
class WebAPIAdapterInterface;
class QTcpServer;
class QTcpSocket;
class SatelliteTracker;
class SatelliteTrackerWorker;
class QDateTime;
class QGeoCoordinate;
class SatWorkerState
{
public:
SatWorkerState(QString name) :
m_name(name)
{
m_satState.m_name = name;
}
bool hasAOS()
{
QDateTime currentTime = QDateTime::currentDateTime();
return (m_aos <= currentTime) && (m_los > currentTime);
}
protected:
QString m_name; // Name of the satellite
QDateTime m_aos; // Time of next AOS
QDateTime m_los; // Time of next LOS
QTimer m_aosTimer;
QTimer m_losTimer;
QTimer m_dopplerTimer;
QList<int> m_initFrequencyOffset;
SatelliteState m_satState;
friend SatelliteTrackerWorker;
};
class SatelliteTrackerWorker : public QObject
{
Q_OBJECT
public:
class MsgConfigureSatelliteTrackerWorker : public Message {
MESSAGE_CLASS_DECLARATION
public:
const SatelliteTrackerSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureSatelliteTrackerWorker* create(const SatelliteTrackerSettings& settings, bool force)
{
return new MsgConfigureSatelliteTrackerWorker(settings, force);
}
private:
SatelliteTrackerSettings m_settings;
bool m_force;
MsgConfigureSatelliteTrackerWorker(const SatelliteTrackerSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
SatelliteTrackerWorker(SatelliteTracker* satelliteTracker, WebAPIAdapterInterface *webAPIAdapterInterface);
~SatelliteTrackerWorker();
void reset();
bool startWork();
void stopWork();
bool isRunning() const { return m_running; }
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
void setMessageQueueToFeature(MessageQueue *messageQueue) { m_msgQueueToFeature = messageQueue; }
void setMessageQueueToGUI(MessageQueue *messageQueue) { m_msgQueueToGUI = messageQueue; }
private:
SatelliteTracker* m_satelliteTracker;
WebAPIAdapterInterface *m_webAPIAdapterInterface;
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
MessageQueue *m_msgQueueToFeature; //!< Queue to report channel change to main feature object
MessageQueue *m_msgQueueToGUI;
SatelliteTrackerSettings m_settings;
bool m_running;
QMutex m_mutex;
QTimer m_pollTimer;
QHash<QString, SatNogsSatellite *> m_satellites;
QHash<QString, SatWorkerState *> m_workerState;
bool m_recalculatePasses; //!< Recalculate passes as something has changed
bool m_flipRotation; //!< Use 180 elevation to avoid 360/0 degree discontinutiy
bool m_extendedAzRotation; //!< Use 450+ degree azimuth to avoid 360/0 degree discontinuity
bool handleMessage(const Message& cmd);
void applySettings(const SatelliteTrackerSettings& settings, bool force = false);
MessageQueue *getMessageQueueToGUI() { return m_msgQueueToGUI; }
void removeFromMap(QString id);
void sendToMap(QList<MessageQueue*> *mapMessageQueues, QString id, QString image, QString text,
double lat, double lon, double altitude, double rotation,
QList<QGeoCoordinate *> *track = nullptr, QList<QGeoCoordinate *> *predictedTrack = nullptr);
void applyDeviceAOSSettings(const QString& name);
void startStopSinks(bool start);
void calculateRotation(SatWorkerState *satWorkerState);
private slots:
void handleInputMessages();
void update();
void aos(SatWorkerState *satWorkerState);
void los(SatWorkerState *satWorkerState);
void doppler(SatWorkerState *satWorkerState);
};
#endif // INCLUDE_FEATURE_SATELLITETRACKERWORKER_H_

View File

@ -0,0 +1,263 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef INCLUDE_SATNOGS_H_
#define INCLUDE_SATNOGS_H_
#include <QString>
#include <QDateTime>
#include <QHash>
#include <QJsonArray>
#include <QJsonObject>
struct SatNogsTransmitter {
int m_noradCatId; // To link to which satellite this is for
QString m_description; // E.g. GMSK9k6 G3RUH AX.25 TLM
bool m_alive;
QString m_type; // "Transmitter", "Trasceiver" or "Transponder"
qint64 m_uplinkLow;
qint64 m_uplinkHigh;
qint64 m_downlinkLow;
qint64 m_downlinkHigh;
QString m_mode; // E.g. "GMSK", "CW", "AFSK", "BPSK", "USB", etc
int m_baud;
QString m_status; // "active", "inactive", "invalid"
QString m_service; // "Amateur", "Earth Exploration", "Maritime", "Meteorological", "Mobile", "Space Research"
SatNogsTransmitter(const QJsonObject& obj)
{
m_noradCatId = obj["norad_cat_id"].toInt();
m_description = obj["description"].toString();
m_alive = obj["alive"].toBool();
m_type = obj["type"].toString();
m_uplinkLow = (qint64)obj["uplink_low"].toDouble();
m_uplinkHigh = (qint64)obj["uplink_high"].toDouble();
m_downlinkLow = (qint64)obj["downlink_low"].toDouble();
m_downlinkHigh = (qint64)obj["downlink_high"].toDouble();
m_mode = obj["mode"].toString();
m_baud = obj["baud"].toInt();
m_status = obj["status"].toString();
m_service = obj["service"].toString();
}
static QList<SatNogsTransmitter *> createList(QJsonArray array)
{
QList<SatNogsTransmitter *> list;
for (int i = 0; i < array.size(); i++)
{
QJsonValue value = array.at(i);
if (value.isObject())
list.append(new SatNogsTransmitter(value.toObject()));
}
return list;
}
static QString getFrequencyText(quint64 frequency)
{
if (frequency > 1000000000)
return QString("%1 GHz").arg(frequency/1000000000.0, 0, ',', 6);
else if (frequency > 1000000)
return QString("%1 MHz").arg(frequency/1000000.0, 0, ',', 3);
else
return QString("%1 kHz").arg(frequency/1000.0, 0, ',', 3);
}
static QString getFrequencyRangeText(quint64 low, quint64 high)
{
if (high > 1000000000)
return QString("%1-%2 GHz").arg(low/1000000000.0, 0, ',', 6).arg(high/1000000000.0, 0, ',', 6);
else if (high > 1000000)
return QString("%1-%2 MHz").arg(low/1000000.0, 0, ',', 3).arg(high/1000000.0, 0, ',', 3);
else
return QString("%1-%2 kHz").arg(low/1000.0, 0, ',', 3).arg(high/1000.0, 0, ',', 3);
}
};
struct SatNogsTLE {
int m_noradCatId; // To link to which satellite this is for
QString m_tle0;
QString m_tle1;
QString m_tle2;
QDateTime m_updated;
SatNogsTLE(const QJsonObject &obj)
{
m_noradCatId = obj["norad_cat_id"].toInt();
m_tle0 = obj["tle0"].toString();
m_tle1 = obj["tle1"].toString();
m_tle2 = obj["tle2"].toString();
m_updated = QDateTime::fromString(obj["updated"].toString(), Qt::ISODateWithMs);
}
SatNogsTLE(const QString& tle0, const QString& tle1, const QString& tle2)
{
m_noradCatId = tle2.mid(2, 5).toInt();
m_tle0 = tle0;
m_tle1 = tle1;
m_tle2 = tle2;
}
static QList<SatNogsTLE *> createList(QJsonArray array)
{
QList<SatNogsTLE *> list;
for (int i = 0; i < array.size(); i++)
{
QJsonValue value = array.at(i);
if (value.isObject())
list.append(new SatNogsTLE(value.toObject()));
}
return list;
}
static QList<SatNogsTLE *> createList(const QByteArray& array)
{
QList<SatNogsTLE *> list;
QList<QByteArray> lines = array.split('\n');
for (int i = 0; i < lines.size(); i += 3)
{
if (i + 3 < lines.size())
{
QString tle0(lines[i]);
QString tle1(lines[i+1]);
QString tle2(lines[i+2]);
list.append(new SatNogsTLE(tle0.trimmed(), tle1.trimmed(), tle2.trimmed()));
}
}
return list;
}
};
struct SatNogsSatellite {
int m_noradCatId;
QString m_name;
QStringList m_names; // Alterantive names - JSON "AO-10\r\nOSCAR-10"
QString m_image; // URL to image of satellie - JSON example: "https://db-satnogs.freetls.fastly.net/media/satellites/sigma.jpg"
QString m_status; // "alive" "re-entered" "dead" "future" or "" for TLE only sats
QDateTime m_decayed; // Date of decay. JSON "2018-05-19T00:00:00Z"
QDateTime m_launched;
QDateTime m_deployed;
QString m_website;
QString m_operator; // "None" or "European Space Agency",
QString m_countries; // "US" or "ES"
QList<SatNogsTransmitter *> m_transmitters;
SatNogsTLE *m_tle;
SatNogsSatellite(const QJsonObject& obj)
{
m_noradCatId = obj["norad_cat_id"].toInt();
m_name = obj["name"].toString();
m_names = obj["names"].toString().split("\r\n");
if ((m_names.size() == 1) && m_names[0].isEmpty())
m_names = QStringList();
m_image = obj["image"].toString();
m_status = obj["status"].toString();
if (!obj["decayed"].isNull())
m_decayed = QDateTime::fromString(obj["decayed"].toString(), Qt::ISODate);
if (!obj["launched"].isNull())
m_launched = QDateTime::fromString(obj["launched"].toString(), Qt::ISODate);
if (!obj["deployed"].isNull())
m_deployed = QDateTime::fromString(obj["deployed"].toString(), Qt::ISODate);
m_website = obj["website"].toString();
m_operator = obj["operator"].toString();
m_countries = obj["countries"].toString();
m_tle = nullptr;
}
SatNogsSatellite(SatNogsTLE *tle)
{
// Extract names from TLE
// tle0 is of the form:
// MOZHAYETS 4 (RS-22)
// GOES 9 [-]
QRegExp re("([A-Za-z0-9\\- ]+)([\\(]([A-Z0-9\\- ]+)[\\)])?");
if (re.indexIn(tle->m_tle0) != -1)
{
QStringList groups = re.capturedTexts();
m_name = groups[1].trimmed();
if ((groups.size() >= 4) && (groups[3] != "-") && !groups[3].isEmpty())
m_names = QStringList({groups[3].trimmed()});
m_noradCatId = tle->m_tle2.mid(2, 5).toInt();
m_tle = tle;
}
}
QString toString()
{
QStringList list;
list.append(QString("Name: %1").arg(m_name));
list.append(QString("NORAD ID: %1").arg(m_noradCatId));
if (m_tle != nullptr)
{
list.append(QString("TLE0: %1").arg(m_tle->m_tle0));
list.append(QString("TLE1: %1").arg(m_tle->m_tle1));
list.append(QString("TLE2: %1").arg(m_tle->m_tle2));
}
for (int i = 0; i < m_transmitters.size(); i++)
{
list.append(QString("Mode: %1 Freq: %2").arg(m_transmitters[i]->m_mode).arg(m_transmitters[i]->m_downlinkLow));
}
return list.join("\n");
}
void addTransmitters(const QList<SatNogsTransmitter *>& transmitters)
{
for (int i = 0; i < transmitters.size(); i++)
{
SatNogsTransmitter *tx = transmitters[i];
if (tx->m_noradCatId == m_noradCatId)
m_transmitters.append(tx);
}
}
void addTLE(const QList<SatNogsTLE *>& tles)
{
for (int i = 0; i < tles.size(); i++)
{
SatNogsTLE *tle = tles[i];
if (tle->m_noradCatId == m_noradCatId)
m_tle = tle;
}
}
// Create a hash table of satellites from the JSON object
static QHash<QString, SatNogsSatellite *> createHash(QJsonArray array)
{
QHash<QString, SatNogsSatellite *> hash;
for (int i = 0; i < array.size(); i++)
{
QJsonValue value = array.at(i);
if (value.isObject())
{
SatNogsSatellite *sat = new SatNogsSatellite(value.toObject());
hash.insert(sat->m_name, sat);
}
}
return hash;
}
};
#endif // INCLUDE_SATNOGS_H_

View File

@ -25,20 +25,24 @@
#include "SWGDeviceSettings.h"
#include "SWGChannelSettings.h"
#include "SWGDeviceSet.h"
#include "SWGChannelActions.h"
#include "SWGFileSinkActions.h"
#include "maincore.h"
#include "device/deviceset.h"
#include "device/deviceapi.h"
#include "channel/channelutils.h"
#include "dsp/devicesamplesource.h"
#include "dsp/devicesamplesink.h"
#include "dsp/devicesamplemimo.h"
#include "webapi/webapiadapterinterface.h"
#include "webapi/webapiutils.h"
// Get device center frequency
bool ChannelWebAPIUtils::getCenterFrequency(unsigned int deviceIndex, double &frequencyInHz)
{
SWGSDRangel::SWGDeviceSettings deviceSettingsResponse;
SWGSDRangel::SWGErrorResponse errorResponse;
QString errorResponse;
int httpRC;
DeviceSet *deviceSet;
@ -52,21 +56,21 @@ bool ChannelWebAPIUtils::getCenterFrequency(unsigned int deviceIndex, double &fr
deviceSettingsResponse.setDeviceHwType(new QString(deviceSet->m_deviceAPI->getHardwareId()));
deviceSettingsResponse.setDirection(0);
DeviceSampleSource *source = deviceSet->m_deviceAPI->getSampleSource();
httpRC = source->webapiSettingsGet(deviceSettingsResponse, *errorResponse.getMessage());
httpRC = source->webapiSettingsGet(deviceSettingsResponse, errorResponse);
}
else if (deviceSet->m_deviceSinkEngine)
{
deviceSettingsResponse.setDeviceHwType(new QString(deviceSet->m_deviceAPI->getHardwareId()));
deviceSettingsResponse.setDirection(1);
DeviceSampleSink *sink = deviceSet->m_deviceAPI->getSampleSink();
httpRC = sink->webapiSettingsGet(deviceSettingsResponse, *errorResponse.getMessage());
httpRC = sink->webapiSettingsGet(deviceSettingsResponse, errorResponse);
}
else if (deviceSet->m_deviceMIMOEngine)
{
deviceSettingsResponse.setDeviceHwType(new QString(deviceSet->m_deviceAPI->getHardwareId()));
deviceSettingsResponse.setDirection(2);
DeviceSampleMIMO *mimo = deviceSet->m_deviceAPI->getSampleMIMO();
httpRC = mimo->webapiSettingsGet(deviceSettingsResponse, *errorResponse.getMessage());
httpRC = mimo->webapiSettingsGet(deviceSettingsResponse, errorResponse);
}
else
{
@ -83,7 +87,7 @@ bool ChannelWebAPIUtils::getCenterFrequency(unsigned int deviceIndex, double &fr
if (httpRC/100 != 2)
{
qWarning("ChannelWebAPIUtils::getCenterFrequency: get device frequency error %d: %s",
httpRC, qPrintable(*errorResponse.getMessage()));
httpRC, qPrintable(errorResponse));
return false;
}
@ -91,10 +95,11 @@ bool ChannelWebAPIUtils::getCenterFrequency(unsigned int deviceIndex, double &fr
return WebAPIUtils::getSubObjectDouble(*jsonObj, "centerFrequency", frequencyInHz);
}
// Set device center frequency
bool ChannelWebAPIUtils::setCenterFrequency(unsigned int deviceIndex, double frequencyInHz)
{
SWGSDRangel::SWGDeviceSettings deviceSettingsResponse;
SWGSDRangel::SWGErrorResponse errorResponse;
QString errorResponse;
int httpRC;
DeviceSet *deviceSet;
@ -108,21 +113,21 @@ bool ChannelWebAPIUtils::setCenterFrequency(unsigned int deviceIndex, double fre
deviceSettingsResponse.setDeviceHwType(new QString(deviceSet->m_deviceAPI->getHardwareId()));
deviceSettingsResponse.setDirection(0);
DeviceSampleSource *source = deviceSet->m_deviceAPI->getSampleSource();
httpRC = source->webapiSettingsGet(deviceSettingsResponse, *errorResponse.getMessage());
httpRC = source->webapiSettingsGet(deviceSettingsResponse, errorResponse);
}
else if (deviceSet->m_deviceSinkEngine)
{
deviceSettingsResponse.setDeviceHwType(new QString(deviceSet->m_deviceAPI->getHardwareId()));
deviceSettingsResponse.setDirection(1);
DeviceSampleSink *sink = deviceSet->m_deviceAPI->getSampleSink();
httpRC = sink->webapiSettingsGet(deviceSettingsResponse, *errorResponse.getMessage());
httpRC = sink->webapiSettingsGet(deviceSettingsResponse, errorResponse);
}
else if (deviceSet->m_deviceMIMOEngine)
{
deviceSettingsResponse.setDeviceHwType(new QString(deviceSet->m_deviceAPI->getHardwareId()));
deviceSettingsResponse.setDirection(2);
DeviceSampleMIMO *mimo = deviceSet->m_deviceAPI->getSampleMIMO();
httpRC = mimo->webapiSettingsGet(deviceSettingsResponse, *errorResponse.getMessage());
httpRC = mimo->webapiSettingsGet(deviceSettingsResponse, errorResponse);
}
else
{
@ -139,7 +144,7 @@ bool ChannelWebAPIUtils::setCenterFrequency(unsigned int deviceIndex, double fre
if (httpRC/100 != 2)
{
qWarning("ChannelWebAPIUtils::setCenterFrequency: get device frequency error %d: %s",
httpRC, qPrintable(*errorResponse.getMessage()));
httpRC, qPrintable(errorResponse));
return false;
}
@ -178,3 +183,282 @@ bool ChannelWebAPIUtils::setCenterFrequency(unsigned int deviceIndex, double fre
return true;
}
// Start acquisition
bool ChannelWebAPIUtils::run(unsigned int deviceIndex, int subsystemIndex)
{
SWGSDRangel::SWGDeviceState runResponse;
QString errorResponse;
int httpRC;
DeviceSet *deviceSet;
std::vector<DeviceSet*> deviceSets = MainCore::instance()->getDeviceSets();
if (deviceIndex < deviceSets.size())
{
runResponse.setState(new QString());
deviceSet = deviceSets[deviceIndex];
if (deviceSet->m_deviceSourceEngine)
{
DeviceSampleSource *source = deviceSet->m_deviceAPI->getSampleSource();
httpRC = source->webapiRun(1, runResponse, errorResponse);
}
else if (deviceSet->m_deviceSinkEngine)
{
DeviceSampleSink *sink = deviceSet->m_deviceAPI->getSampleSink();
httpRC = sink->webapiRun(1, runResponse, errorResponse);
}
else if (deviceSet->m_deviceMIMOEngine)
{
DeviceSampleMIMO *mimo = deviceSet->m_deviceAPI->getSampleMIMO();
httpRC = mimo->webapiRun(1, subsystemIndex, runResponse, errorResponse);
}
else
{
qDebug() << "ChannelWebAPIUtils::run - unknown device " << deviceIndex;
return false;
}
}
else
{
qDebug() << "ChannelWebAPIUtils::run - no device " << deviceIndex;
return false;
}
if (httpRC/100 != 2)
{
qWarning("ChannelWebAPIUtils::run: run error %d: %s",
httpRC, qPrintable(errorResponse));
return false;
}
return true;
}
// Stop acquisition
bool ChannelWebAPIUtils::stop(unsigned int deviceIndex, int subsystemIndex)
{
SWGSDRangel::SWGDeviceState runResponse;
QString errorResponse;
int httpRC;
DeviceSet *deviceSet;
std::vector<DeviceSet*> deviceSets = MainCore::instance()->getDeviceSets();
if (deviceIndex < deviceSets.size())
{
runResponse.setState(new QString());
deviceSet = deviceSets[deviceIndex];
if (deviceSet->m_deviceSourceEngine)
{
DeviceSampleSource *source = deviceSet->m_deviceAPI->getSampleSource();
httpRC = source->webapiRun(0, runResponse, errorResponse);
}
else if (deviceSet->m_deviceSinkEngine)
{
DeviceSampleSink *sink = deviceSet->m_deviceAPI->getSampleSink();
httpRC = sink->webapiRun(0, runResponse, errorResponse);
}
else if (deviceSet->m_deviceMIMOEngine)
{
DeviceSampleMIMO *mimo = deviceSet->m_deviceAPI->getSampleMIMO();
httpRC = mimo->webapiRun(0, subsystemIndex, runResponse, errorResponse);
}
else
{
qDebug() << "ChannelWebAPIUtils::stop - unknown device " << deviceIndex;
return false;
}
}
else
{
qDebug() << "ChannelWebAPIUtils::stop - no device " << deviceIndex;
return false;
}
if (httpRC/100 != 2)
{
qWarning("ChannelWebAPIUtils::stop: run error %d: %s",
httpRC, qPrintable(errorResponse));
return false;
}
return true;
}
// Get input frequency offset for a channel
bool ChannelWebAPIUtils::getFrequencyOffset(unsigned int deviceIndex, int channelIndex, int& offset)
{
SWGSDRangel::SWGChannelSettings channelSettingsResponse;
QString errorResponse;
int httpRC;
QJsonObject *jsonObj;
double offsetD;
ChannelAPI *channel = MainCore::instance()->getChannel(deviceIndex, channelIndex);
if (channel != nullptr)
{
httpRC = channel->webapiSettingsGet(channelSettingsResponse, errorResponse);
if (httpRC/100 != 2)
{
qWarning("ChannelWebAPIUtils::getFrequencyOffset: get channel settings error %d: %s",
httpRC, qPrintable(errorResponse));
return false;
}
jsonObj = channelSettingsResponse.asJsonObject();
if (WebAPIUtils::getSubObjectDouble(*jsonObj, "inputFrequencyOffset", offsetD))
{
offset = (int)offsetD;
return true;
}
}
return false;
}
// Set input frequency offset for a channel
bool ChannelWebAPIUtils::setFrequencyOffset(unsigned int deviceIndex, int channelIndex, int offset)
{
SWGSDRangel::SWGChannelSettings channelSettingsResponse;
QString errorResponse;
int httpRC;
QJsonObject *jsonObj;
ChannelAPI *channel = MainCore::instance()->getChannel(deviceIndex, channelIndex);
if (channel != nullptr)
{
httpRC = channel->webapiSettingsGet(channelSettingsResponse, errorResponse);
if (httpRC/100 != 2)
{
qWarning("ChannelWebAPIUtils::setFrequencyOffset: get channel settings error %d: %s",
httpRC, qPrintable(errorResponse));
return false;
}
jsonObj = channelSettingsResponse.asJsonObject();
if (WebAPIUtils::setSubObjectDouble(*jsonObj, "inputFrequencyOffset", (double)offset))
{
QStringList keys;
keys.append("inputFrequencyOffset");
channelSettingsResponse.init();
channelSettingsResponse.fromJsonObject(*jsonObj);
httpRC = channel->webapiSettingsPutPatch(false, keys, channelSettingsResponse, errorResponse);
if (httpRC/100 != 2)
{
qWarning("ChannelWebAPIUtils::setFrequencyOffset: patch channel settings error %d: %s",
httpRC, qPrintable(errorResponse));
return false;
}
return true;
}
}
return false;
}
// Start or stop all file sinks in a given device set
bool ChannelWebAPIUtils::startStopFileSinks(unsigned int deviceIndex, bool start)
{
MainCore *mainCore = MainCore::instance();
ChannelAPI *channel;
int channelIndex = 0;
while(nullptr != (channel = mainCore->getChannel(deviceIndex, channelIndex)))
{
if (ChannelUtils::compareChannelURIs(channel->getURI(), "sdrangel.channel.filesink"))
{
QStringList channelActionKeys = {"record"};
SWGSDRangel::SWGChannelActions channelActions;
SWGSDRangel::SWGFileSinkActions *fileSinkAction = new SWGSDRangel::SWGFileSinkActions();
QString errorResponse;
int httpRC;
fileSinkAction->setRecord(start);
channelActions.setFileSinkActions(fileSinkAction);
httpRC = channel->webapiActionsPost(channelActionKeys, channelActions, errorResponse);
if (httpRC/100 != 2)
{
qWarning("ChannelWebAPIUtils::startStopFileSinks: webapiActionsPost error %d: %s",
httpRC, qPrintable(errorResponse));
return false;
}
}
channelIndex++;
}
return true;
}
// Send AOS actions to all channels that support it
bool ChannelWebAPIUtils::satelliteAOS(const QString name, bool northToSouthPass)
{
MainCore *mainCore = MainCore::instance();
std::vector<DeviceSet*> deviceSets = mainCore->getDeviceSets();
for (unsigned int deviceIndex = 0; deviceIndex < deviceSets.size(); deviceIndex++)
{
ChannelAPI *channel;
int channelIndex = 0;
while(nullptr != (channel = mainCore->getChannel(deviceIndex, channelIndex)))
{
if (ChannelUtils::compareChannelURIs(channel->getURI(), "sdrangel.channel.aptdemod"))
{
QStringList channelActionKeys = {"aos"};
SWGSDRangel::SWGChannelActions channelActions;
SWGSDRangel::SWGAPTDemodActions *aptDemodAction = new SWGSDRangel::SWGAPTDemodActions();
SWGSDRangel::SWGAPTDemodActions_aos *aosAction = new SWGSDRangel::SWGAPTDemodActions_aos();
QString errorResponse;
int httpRC;
aosAction->setSatelliteName(new QString(name));
aosAction->setNorthToSouthPass(northToSouthPass);
aptDemodAction->setAos(aosAction);
channelActions.setAptDemodActions(aptDemodAction);
httpRC = channel->webapiActionsPost(channelActionKeys, channelActions, errorResponse);
if (httpRC/100 != 2)
{
qWarning("ChannelWebAPIUtils::satelliteAOS: webapiActionsPost error %d: %s",
httpRC, qPrintable(errorResponse));
return false;
}
}
channelIndex++;
}
}
return true;
}
// Send LOS actions to all channels that support it
bool ChannelWebAPIUtils::satelliteLOS(const QString name)
{
MainCore *mainCore = MainCore::instance();
std::vector<DeviceSet*> deviceSets = mainCore->getDeviceSets();
for (unsigned int deviceIndex = 0; deviceIndex < deviceSets.size(); deviceIndex++)
{
ChannelAPI *channel;
int channelIndex = 0;
while(nullptr != (channel = mainCore->getChannel(deviceIndex, channelIndex)))
{
if (ChannelUtils::compareChannelURIs(channel->getURI(), "sdrangel.channel.aptdemod"))
{
QStringList channelActionKeys = {"los"};
SWGSDRangel::SWGChannelActions channelActions;
SWGSDRangel::SWGAPTDemodActions *aptDemodAction = new SWGSDRangel::SWGAPTDemodActions();
SWGSDRangel::SWGAPTDemodActions_los *losAction = new SWGSDRangel::SWGAPTDemodActions_los();
QString errorResponse;
int httpRC;
losAction->setSatelliteName(new QString(name));
aptDemodAction->setLos(losAction);
channelActions.setAptDemodActions(aptDemodAction);
httpRC = channel->webapiActionsPost(channelActionKeys, channelActions, errorResponse);
if (httpRC/100 != 2)
{
qWarning("ChannelWebAPIUtils::satelliteLOS: webapiActionsPost error %d: %s",
httpRC, qPrintable(errorResponse));
return false;
}
}
channelIndex++;
}
}
return true;
}

View File

@ -18,6 +18,8 @@
#ifndef SDRBASE_CHANNEL_CHANNELWEBAPIUTILS_H_
#define SDRBASE_CHANNEL_CHANNELWEBAPIUTILS_H_
#include <QString>
#include "export.h"
class SDRBASE_API ChannelWebAPIUtils
@ -25,6 +27,13 @@ class SDRBASE_API ChannelWebAPIUtils
public:
static bool getCenterFrequency(unsigned int deviceIndex, double &frequencyInHz);
static bool setCenterFrequency(unsigned int deviceIndex, double frequencyInHz);
static bool run(unsigned int deviceIndex, int subsystemIndex=0);
static bool stop(unsigned int deviceIndex, int subsystemIndex=0);
static bool getFrequencyOffset(unsigned int deviceIndex, int channelIndex, int& offset);
static bool setFrequencyOffset(unsigned int deviceIndex, int channelIndex, int offset);
static bool startStopFileSinks(unsigned int deviceIndex, bool start);
static bool satelliteAOS(const QString name, bool northToSouthPass);
static bool satelliteLOS(const QString name);
};
#endif // SDRBASE_CHANNEL_CHANNELWEBAPIUTILS_H_

View File

@ -338,3 +338,28 @@ const PluginInterface *PluginManager::getFeaturePluginInterface(const QString& f
return nullptr;
}
QString PluginManager::uriToId(const QString& uri) const
{
for (int i = 0; i < m_rxChannelRegistrations.size(); i++)
{
if (m_rxChannelRegistrations[i].m_channelIdURI == uri)
return m_rxChannelRegistrations[i].m_channelId;
}
for (int i = 0; i < m_txChannelRegistrations.size(); i++)
{
if (m_txChannelRegistrations[i].m_channelIdURI == uri)
return m_txChannelRegistrations[i].m_channelId;
}
for (int i = 0; i < m_mimoChannelRegistrations.size(); i++)
{
if (m_mimoChannelRegistrations[i].m_channelIdURI == uri)
return m_mimoChannelRegistrations[i].m_channelId;
}
for (int i = 0; i < m_featureRegistrations.size(); i++)
{
if (m_featureRegistrations[i].m_featureIdURI == uri)
return m_featureRegistrations[i].m_featureId;
}
return uri;
}

View File

@ -89,6 +89,9 @@ public:
const PluginInterface *getDevicePluginInterface(const QString& deviceId) const;
const PluginInterface *getFeaturePluginInterface(const QString& featureIdURI) const;
// Map channel/feature URI to short form Id
QString uriToId(const QString& uri) const;
static const QString& getFileInputDeviceId() { return m_fileInputDeviceTypeID; }
static const QString& getTestMIMODeviceId() { return m_testMIMODeviceTypeID; }
static const QString& getFileOutputDeviceId() { return m_fileOutputDeviceTypeID; }

View File

@ -61,6 +61,16 @@ public:
return (int)std::round(knotsToKPH(knots));
}
static float kmpsToKPH(float kps)
{
return kps * (60.0 * 60.0);
}
static int kmpsToIntegerKPH(float kps)
{
return (int)std::round(kmpsToKPH(kps));
}
static float feetPerMinToMetresPerSecond(float fpm)
{
return fpm * 0.00508f;

View File

@ -55,6 +55,7 @@ set(sdrgui_SOURCES
gui/samplingdevicedialog.cpp
gui/samplingdevicesdock.cpp
gui/scaleengine.cpp
gui/scaledimage.cpp
gui/sdrangelsplash.cpp
gui/tickedslider.cpp
gui/transverterbutton.cpp
@ -64,6 +65,7 @@ set(sdrgui_SOURCES
gui/valuedial.cpp
gui/valuedialz.cpp
gui/wsspectrumsettingsdialog.cpp
gui/wrappingdatetimeedit.cpp
dsp/scopevis.cpp
dsp/scopevisxy.cpp
@ -141,6 +143,7 @@ set(sdrgui_HEADERS
gui/samplingdevicedialog.h
gui/samplingdevicesdock.h
gui/scaleengine.h
gui/scaledimage.h
gui/sdrangelsplash.h
gui/tickedslider.h
gui/transverterbutton.h
@ -150,6 +153,7 @@ set(sdrgui_HEADERS
gui/valuedial.h
gui/valuedialz.h
gui/wsspectrumsettingsdialog.h
gui/wrappingdatetimeedit.h
dsp/scopevis.h
dsp/scopevisxy.h

View File

@ -0,0 +1,41 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "scaledimage.h"
ScaledImage::ScaledImage(QWidget *parent) :
QLabel(parent)
{
}
void ScaledImage::setPixmap(const QPixmap& pixmap)
{
setPixmap(pixmap, size());
}
void ScaledImage::setPixmap(const QPixmap& pixmap, const QSize& size)
{
m_pixmap = pixmap;
m_pixmapScaled = pixmap.scaled(size, Qt::KeepAspectRatio);
QLabel::setPixmap(m_pixmapScaled);
}
void ScaledImage::resizeEvent(QResizeEvent *event)
{
QLabel::resizeEvent(event);
setPixmap(m_pixmap, event->size());
}

47
sdrgui/gui/scaledimage.h Normal file
View File

@ -0,0 +1,47 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef SDRGUI_GUI_SCALEDIMAGE_H
#define SDRGUI_GUI_SCALEDIMAGE_H
#include <QLabel>
#include <QPixmap>
#include <QSize>
#include <QResizeEvent>
#include "export.h"
// Similar to displaying a pixmap with QLabel, except we preserve the aspect ratio
class SDRGUI_API ScaledImage : public QLabel {
public:
explicit ScaledImage(QWidget *parent = nullptr);
void setPixmap(const QPixmap& pixmap);
void setPixmap(const QPixmap& pixmap, const QSize& size);
protected:
virtual void resizeEvent(QResizeEvent *event);
private:
QPixmap m_pixmap;
QPixmap m_pixmapScaled;
};
#endif // SDRGUI_GUI_SCALEDIMAGE_H

View File

@ -0,0 +1,71 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#include "wrappingdatetimeedit.h"
WrappingDateTimeEdit::WrappingDateTimeEdit(QWidget *parent) :
QDateTimeEdit(parent)
{
setWrapping(true);
}
void WrappingDateTimeEdit::stepBy(int steps)
{
if (currentSection() == QDateTimeEdit::MonthSection)
setDate(date().addMonths(steps));
else if (currentSection() == QDateTimeEdit::DaySection)
setDate(date().addDays(steps));
else if (currentSection() == QDateTimeEdit::HourSection)
{
QTime t = time();
int h = t.hour();
setTime(time().addSecs(steps*3600));
if ((h < -steps) && (steps < 0))
setDate(date().addDays(-1));
else if ((h + steps > 23) && (steps > 0))
setDate(date().addDays(1));
}
else if (currentSection() == QDateTimeEdit::MinuteSection)
{
QTime t = time();
int h = t.hour();
int m = t.minute();
setTime(time().addSecs(steps*60));
if ((m < -steps) && (steps < 0) && (h == 0))
setDate(date().addDays(-1));
else if ((m + steps > 59) && (steps > 0) && (h == 23))
setDate(date().addDays(1));
}
else if (currentSection() == QDateTimeEdit::SecondSection)
{
QTime t = time();
int h = t.hour();
int m = t.minute();
int s = t.second();
setTime(time().addSecs(steps));
if ((s < -steps) && (steps < 0) && (h == 0) && (m == 0))
setDate(date().addDays(-1));
else if ((s + steps > 59) && (steps > 0) && (h == 23) && (m == 59))
setDate(date().addDays(1));
}
}

View File

@ -0,0 +1,35 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// //
// This program is free software; you can redistribute it and/or modify //
// it under the terms of the GNU General Public License as published by //
// the Free Software Foundation as 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 V3 for more details. //
// //
// You should have received a copy of the GNU General Public License //
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
///////////////////////////////////////////////////////////////////////////////////
#ifndef SDRGUI_GUI_WRAPPINGDATETIMEEDIT_H
#define SDRGUI_GUI_WRAPPINGDATETIMEEDIT_H
#include <QDateTimeEdit>
#include "export.h"
// Same as QDateTimeEdit, except allows minutes to wrap to hours and hours to
// days when scrolling up or down
class SDRGUI_API WrappingDateTimeEdit : public QDateTimeEdit {
public:
explicit WrappingDateTimeEdit(QWidget *parent = nullptr);
void stepBy(int steps) override;
};
#endif // SDRGUI_GUI_WRAPPINGDATETIMEEDIT_H

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@ -2,6 +2,7 @@
<qresource prefix="/">
<file>info.png</file>
<file>arrow_left.png</file>
<file>arrow_right.png</file>
<file>star.png</file>
<file>swap.png</file>
<file>gridpolar.png</file>