1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-04 16:01:14 -05:00

Add StarTracker feature

This commit is contained in:
Jon Beniston 2021-01-13 20:51:38 +00:00
parent 79497e56e4
commit 4d04ee1c31
39 changed files with 4729 additions and 0 deletions

View File

@ -10,6 +10,8 @@ if (Qt5Quick_FOUND AND Qt5QuickWidgets_FOUND AND Qt5Positioning_FOUND)
endif() endif()
add_subdirectory(afc) add_subdirectory(afc)
add_subdirectory(aprs)
add_subdirectory(demodanalyzer) add_subdirectory(demodanalyzer)
add_subdirectory(rigctlserver) add_subdirectory(rigctlserver)
add_subdirectory(simpleptt) add_subdirectory(simpleptt)
add_subdirectory(startracker)

View File

@ -0,0 +1,61 @@
project(startracker)
set(startracker_SOURCES
startracker.cpp
startrackersettings.cpp
startrackerplugin.cpp
startrackerworker.cpp
startrackerwebapiadapter.cpp
)
set(startracker_HEADERS
startracker.h
startrackersettings.h
startrackerplugin.h
startrackerreport.h
startrackerworker.h
startrackerwebapiadapter.h
)
include_directories(
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
)
if(NOT SERVER_MODE)
set(startracker_SOURCES
${startracker_SOURCES}
startrackergui.cpp
startrackergui.ui
startrackersettingsdialog.cpp
startrackersettingsdialog.ui
startracker.qrc
)
set(startracker_HEADERS
${startracker_HEADERS}
startrackergui.h
startrackersettingsdialog.h
)
set(TARGET_NAME featurestartracker)
set(TARGET_LIB Qt5::Widgets Qt5::Charts)
set(TARGET_LIB_GUI "sdrgui")
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
else()
set(TARGET_NAME featurestartrackersrv)
set(TARGET_LIB "")
set(TARGET_LIB_GUI "")
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
endif()
add_library(${TARGET_NAME} SHARED
${startracker_SOURCES}
)
target_link_libraries(${TARGET_NAME}
Qt5::Core
${TARGET_LIB}
sdrbase
${TARGET_LIB_GUI}
)
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})

View File

@ -0,0 +1,158 @@
<h1>Star Tracker Feature Plugin</h1>
<h2>Introduction</h2>
The Star Tracker feature plugin is for use in radio astronomy and EME (Earth-Moon-Earth) communication.
It calculates the azimuth and elevation of celestial objects and can send them to the Rotator Controller or other plugins to point an antenna at that object.
The overhead position of the Sun, Moon and selected star can be displayed on the Map Feature.
The plugin can communicate with Stellarium, allowing Stellarium to control SDRangel as though it was a telescope and for the direction the antenna is pointing to be displayed in Stellarium.
<h2>Interface</h2>
![Star Tracker feature plugin GUI](../../../doc/img/StarTracker_plugin.png)
<h3>1: Start/Stop plugin</h3>
This button starts or stops the plugin. The plugin will only calculate azimuth and elevation or communicate with Stellarium when started.
<h3>2: Find target on map</h3>
Pressing this button centres the Map Feature (if open) on the current target.
<h3>3: 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>4: Show settings dialog</h3>
Pressing this button displays a settings dialog, that allows you to set:
* The epoch used when entering RA and Dec. This can be either J2000 (which is used for most catalogues) or JNOW which is the current date and time.
* The units used for the display of the calculated azimuth and elevation. This can be either degrees, minutes and seconds or decimal degrees.
* Whether to correct for atmospheric refaction. You can choose either no correction, the Saemundsson algorithm, typically used for optical astronomy or the more accurate Positional Astronomy Library calculation, which can be used for >250MHz radio frequencies or light. Note that there is only a very minor difference between the two.
* Air pressure in millibars for use in refraction correction.
* Air temperature in degrees Celsius for use in refraction correction.
* Relative humidity in % for use in refraction correction.
* Height above sea level in metres for use in refraction correction.
* Temperature lapse rate in Kelvin per kilometer for use in refraction correction.
* Radio frequency being observed in MHz for use in refraction correction.
* The update period in seconds, which controls how frequently azimuth and elevation are re-calculated.
* The IP port number the Stellarium server listens on.
* Whether to start a Stellarium telescope server.
* Whether to draw the Sun in the map.
* Whether to draw the Moon on the map.
* Whether to draw the target star (or galaxy) on the map.
<h3>5: Latitude/h3>
Specifies the latitude in decimal degrees of the observation point (antenna location).
<h3>6: Longitude</h3>
Specifies the longitude in decimal degrees of the observation point (antenna location).
<h3>7: Time</h3>
Select the date and time at which the position of the target should be calculated. Select either Now, for the current time, or Custom to manually enter a date and time.
<h3>8: Target</h3>
Select a target object to track from the list.
To manually enter RA (right ascension) and Dec (declination) of an unlisted target, select Custom.
To allow Stellarium to set the RA and Dec, select Custom, and ensure the Stellarium Server option is checked in the Star Tracker Settings dialog.
| Target | Type | Details | Flux density (Jy) |
|------------------|-------------------|------------------------------------------------|---------------------------------------------
| Sun | Star | Targets our Sun | 10k-10M (50MHz), 500k-10M (1.4GHz) |
| Moon | Moon | Targets our Moon | 2 (50MHz), 1000 (1.4GHz) |
| PSR B0329+54 | Pulsar | Strongest in Northern hemisphere (J0332+5434) | 1.8 (50MHz), 1.5 (400MHz), 0.2 (1.4GHz) |
| PSR B0833-45 | Pulsar | Strongest in Southern hemisphere (J0835-4510) | 5.4 (150MHz), 5.0 (400MHz), 1.0 (1.4GHz) |
| Sagittarius A | Galatic centre | First detected source of extrasolar radio | ~0.5 (<1GHz) for Sgr A* |
| Cassiopeia A | Supernova | Brightest extrasolar radio source | 27k (50MHz), 10k (150MHz), 1768 (1.4GHz) |
| Cygnus A | Galaxy | First radio galaxy | 22k (50MHz), 11k (150MHz), 1579 (1.4GHz) |
| Taurus A (M1) | Supernova/Pulsar | Crab Nebular | 2008 (50MHz), 1368 (150MHz), 829 (1.4GHz) |
| Virgo A (M87) | Galaxy | | 2635 (50MHz), 1209 (150MHz), 212 (1.4GHz) |
| Custom | | Manually enter RA and Dec | |
References:
* ATNF Pulsar Catalogue - https://www.atnf.csiro.au/research/pulsar/psrcat/
* Cassiopeia A, Cygnus A, Taurus A, and Virgo A at ultra-low radio frequencies - https://research.chalmers.se/publication/516438/file/516438_Fulltext.pdf
* Repeating Jansky - https://www.gb.nrao.edu/~fghigo/JanskyAntenna/RepeatingJansky_memo10.pdf
<h3>9: Right Ascension</h3>
When target is set to Custom, you can specify the right ascension in hours of the target object. This can be specified as a decimal (E.g. 12.23, from 0 to 24) or in hours, minutes and seconds (E.g. 12h05m10.2s or 12 05 10.2). Whether the epoch is J2000 or JNOW can be set in the Star Tracker Settings dialog.
<h3>10: Declination</h3>
When target is set to Custom, you can specify the declination in degrees of the target object. This can be specified as a decimal (E.g. 34.6, from -90.0 to 90.0) or in degrees, minutes and seconds (E.g. 34d12m5.6s, 34d12'5.6" 34 12 5.6). Whether the epoch is J2000 or JNOW can be set in the Star Tracker Settings dialog.
<h3>11: Azimuth</h3>
Displays the calculated azimuth (angle in degrees, clockwise from North) to the object.
<h3>12: Elevation</h3>
Displays the calculated elevation (angle in degrees - 0 to horizon and 90 to zenith) to the object.
<h3>13: Elevation vs Time Plot<h3>
In order to assit in determining whether and when observations of the target object may be possible, an elevation vs time plot is drawn for the 24 hours encompassing the selected date and time.
Some objects may not be visible from a particular latitude for the specified time, in which case, the grahp title will indicate the object is not visible on that particular date.
<h2>Map</h2>
The Star Tracker feature can send the overhead position of the Sun, Moon and target Star to the Map. These can be enabled individually in the settings dialog. The Moon should be displayed with an approximate phase. Stars (or galaxies) are displayed as an image of a pulsar.
When using the Find feature in the Map GUI, you can search for "Sun", "Moon" or "Star".
<h2>Stellarium Interface</h2>
In Star Tracker:
* Set target to Custom
* Press Show settings dialog and ensure Stellarium server is checked
* Press Start
Then in Stellarium:
* Enable Telescope Control plugin and restart
* Press the telescope button in the main toolbar
* Press "Configure telescopes..."
* Press "Add a new telescope"
* Set "Telescope controlled by" to "External softare or a remote computer"
* Set "Name" to "SDRangel" (Optional)
* Set "Coordinate system" to "J2000 (default)"
* Press OK
* Press Connect
* Enter Right Ascension/Declination or press "Current object" to get RA/Dec of currently selected object
* Press "Slew" to send the RA/Dec to Star Tracker
Star Tracker will continually send the RA/Dec of its target to Stellarium and this should be displayed in Stellarium with a crosshair/reticle and the label SDRangel (or whatever name you entered for the telescope).
To see the rough field of view of your antenna, open the Ocular configuration window and under Eyepieces, add a new eyepiece with name SDRangel.
Set aFOV to the half-power beam width of your antenna, focal length to 100 and field stop to 0.
Then select the SDRangel telescope reticle and press Ocular view.
![StarTracker map](../../../doc/img/StarTracker_map.png)
<h2>Attribution</h2>
Icons are by Adnen Kadri and Erik Madsen, from the Noun Project Noun Project: https://thenounproject.com/
Icons are by Freepik from Flaticon https://www.flaticon.com/
<h2>API</h2>
Full details of the API can be found in the Swagger documentation. Here is a quick example of how to set the target to the Moon at the current time:
curl -X PATCH "http://127.0.0.1:8091/sdrangel/featureset/0/feature/0/settings" -d '{"featureType": "StarTracker", "StarTrackerSettings": { "target": "Moon", "dateTime": "" }}'
Or to a custom RA and declination on a given date and time:
curl -X PATCH "http://127.0.0.1:8091/sdrangel/featureset/0/feature/0/settings" -d '{"featureType": "StarTracker", "StarTrackerSettings": { "target": "Custom", "ra": "03h32m59.35s", "dec": "54d34m45.05s", "dateTime": "1921-04-15T10:17:05" }}'
To start tracking:
curl -X POST "http://127.0.0.1:8091/sdrangel/featureset/0/feature/0/run"

View File

@ -0,0 +1,502 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "SWGSimplePTTReport.h"
#include "SWGDeviceState.h"
#include "dsp/dspengine.h"
#include "startrackerworker.h"
#include "startracker.h"
MESSAGE_CLASS_DEFINITION(StarTracker::MsgConfigureStarTracker, Message)
MESSAGE_CLASS_DEFINITION(StarTracker::MsgStartStop, Message)
const char* const StarTracker::m_featureIdURI = "sdrangel.feature.startracker";
const char* const StarTracker::m_featureId = "StarTracker";
StarTracker::StarTracker(WebAPIAdapterInterface *webAPIAdapterInterface) :
Feature(m_featureIdURI, webAPIAdapterInterface),
m_ptt(false)
{
qDebug("StarTracker::StarTracker: webAPIAdapterInterface: %p", webAPIAdapterInterface);
setObjectName(m_featureId);
m_worker = new StarTrackerWorker(this, webAPIAdapterInterface);
m_state = StIdle;
m_errorMessage = "StarTracker error";
}
StarTracker::~StarTracker()
{
if (m_worker->isRunning()) {
stop();
}
delete m_worker;
}
void StarTracker::start()
{
qDebug("StarTracker::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();
StarTrackerWorker::MsgConfigureStarTrackerWorker *msg = StarTrackerWorker::MsgConfigureStarTrackerWorker::create(m_settings, true);
m_worker->getInputMessageQueue()->push(msg);
}
void StarTracker::stop()
{
qDebug("StarTracker::stop");
m_worker->stopWork();
m_state = StIdle;
m_thread.quit();
m_thread.wait();
}
bool StarTracker::handleMessage(const Message& cmd)
{
if (MsgConfigureStarTracker::match(cmd))
{
MsgConfigureStarTracker& cfg = (MsgConfigureStarTracker&) cmd;
qDebug() << "StarTracker::handleMessage: MsgConfigureStarTracker";
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else if (MsgStartStop::match(cmd))
{
MsgStartStop& cfg = (MsgStartStop&) cmd;
qDebug() << "StarTracker::handleMessage: MsgStartStop: start:" << cfg.getStartStop();
if (cfg.getStartStop()) {
start();
} else {
stop();
}
return true;
}
else
{
return false;
}
}
QByteArray StarTracker::serialize() const
{
return m_settings.serialize();
}
bool StarTracker::deserialize(const QByteArray& data)
{
if (m_settings.deserialize(data))
{
MsgConfigureStarTracker *msg = MsgConfigureStarTracker::create(m_settings, true);
m_inputMessageQueue.push(msg);
return true;
}
else
{
m_settings.resetToDefaults();
MsgConfigureStarTracker *msg = MsgConfigureStarTracker::create(m_settings, true);
m_inputMessageQueue.push(msg);
return false;
}
}
void StarTracker::applySettings(const StarTrackerSettings& settings, bool force)
{
qDebug() << "StarTracker::applySettings:"
<< " m_target: " << settings.m_target
<< " m_ra: " << settings.m_ra
<< " m_dec: " << settings.m_dec
<< " m_latitude: " << settings.m_latitude
<< " m_longitude: " << settings.m_longitude
<< " m_serverPort: " << settings.m_serverPort
<< " m_enableServer: " << settings.m_enableServer
<< " 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_target != settings.m_target) || force) {
reverseAPIKeys.append("target");
}
if ((m_settings.m_ra != settings.m_ra) || force) {
reverseAPIKeys.append("ra");
}
if ((m_settings.m_dec != settings.m_dec) || force) {
reverseAPIKeys.append("dec");
}
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_dateTime != settings.m_dateTime) || force) {
reverseAPIKeys.append("dateTime");
}
if ((m_settings.m_refraction != settings.m_refraction) || force) {
reverseAPIKeys.append("refraction");
}
if ((m_settings.m_pressure != settings.m_pressure) || force) {
reverseAPIKeys.append("pressure");
}
if ((m_settings.m_temperature != settings.m_temperature) || force) {
reverseAPIKeys.append("temperature");
}
if ((m_settings.m_humidity != settings.m_humidity) || force) {
reverseAPIKeys.append("humidity");
}
if ((m_settings.m_heightAboveSeaLevel != settings.m_heightAboveSeaLevel) || force) {
reverseAPIKeys.append("heightAboveSeaLevel");
}
if ((m_settings.m_temperatureLapseRate != settings.m_temperatureLapseRate) || force) {
reverseAPIKeys.append("temperatureLapseRate");
}
if ((m_settings.m_frequency != settings.m_frequency) || force) {
reverseAPIKeys.append("frequency");
}
if ((m_settings.m_serverPort != settings.m_serverPort) || force) {
reverseAPIKeys.append("stellariumPort");
}
if ((m_settings.m_enableServer != settings.m_enableServer) || force) {
reverseAPIKeys.append("stellariumServerEnabled");
}
if ((m_settings.m_updatePeriod != settings.m_updatePeriod) || force) {
reverseAPIKeys.append("updatePeriod");
}
if ((m_settings.m_jnow != settings.m_jnow) || force) {
reverseAPIKeys.append("epoch");
}
if ((m_settings.m_title != settings.m_title) || force) {
reverseAPIKeys.append("title");
}
if ((m_settings.m_rgbColor != settings.m_rgbColor) || force) {
reverseAPIKeys.append("rgbColor");
}
StarTrackerWorker::MsgConfigureStarTrackerWorker *msg = StarTrackerWorker::MsgConfigureStarTrackerWorker::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;
}
int StarTracker::webapiRun(bool run,
SWGSDRangel::SWGDeviceState& response,
QString& errorMessage)
{
(void) errorMessage;
getFeatureStateStr(*response.getState());
MsgStartStop *msg = MsgStartStop::create(run);
getInputMessageQueue()->push(msg);
return 202;
}
int StarTracker::webapiSettingsGet(
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setStarTrackerSettings(new SWGSDRangel::SWGStarTrackerSettings());
response.getStarTrackerSettings()->init();
webapiFormatFeatureSettings(response, m_settings);
return 200;
}
int StarTracker::webapiSettingsPutPatch(
bool force,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage)
{
(void) errorMessage;
StarTrackerSettings settings = m_settings;
webapiUpdateFeatureSettings(settings, featureSettingsKeys, response);
MsgConfigureStarTracker *msg = MsgConfigureStarTracker::create(settings, force);
m_inputMessageQueue.push(msg);
qDebug("StarTracker::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue);
if (m_guiMessageQueue) // forward to GUI if any
{
MsgConfigureStarTracker *msgToGUI = MsgConfigureStarTracker::create(settings, force);
m_guiMessageQueue->push(msgToGUI);
}
webapiFormatFeatureSettings(response, settings);
return 200;
}
void StarTracker::webapiFormatFeatureSettings(
SWGSDRangel::SWGFeatureSettings& response,
const StarTrackerSettings& settings)
{
response.getStarTrackerSettings()->setTarget(new QString(settings.m_target));
response.getStarTrackerSettings()->setRa(new QString(settings.m_ra));
response.getStarTrackerSettings()->setDec(new QString(settings.m_dec));
response.getStarTrackerSettings()->setLatitude(settings.m_latitude);
response.getStarTrackerSettings()->setLongitude(settings.m_longitude);
response.getStarTrackerSettings()->setDateTime(new QString(settings.m_dateTime));
response.getStarTrackerSettings()->setRefraction(new QString(settings.m_refraction));
response.getStarTrackerSettings()->setPressure(settings.m_pressure);
response.getStarTrackerSettings()->setTemperature(settings.m_temperature);
response.getStarTrackerSettings()->setHumidity(settings.m_humidity);
response.getStarTrackerSettings()->setHeightAboveSeaLevel(settings.m_heightAboveSeaLevel);
response.getStarTrackerSettings()->setTemperatureLapseRate(settings.m_temperatureLapseRate);
response.getStarTrackerSettings()->setFrequency(settings.m_frequency/1000000.0);
response.getStarTrackerSettings()->setStellariumServerEnabled(settings.m_enableServer ? 1 : 0);
response.getStarTrackerSettings()->setStellariumPort(settings.m_serverPort);
response.getStarTrackerSettings()->setUpdatePeriod(settings.m_updatePeriod);
response.getStarTrackerSettings()->setEpoch(settings.m_jnow ? new QString("JNOW") : new QString("J2000"));
if (response.getStarTrackerSettings()->getTitle()) {
*response.getStarTrackerSettings()->getTitle() = settings.m_title;
} else {
response.getStarTrackerSettings()->setTitle(new QString(settings.m_title));
}
response.getStarTrackerSettings()->setRgbColor(settings.m_rgbColor);
response.getStarTrackerSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
if (response.getStarTrackerSettings()->getReverseApiAddress()) {
*response.getStarTrackerSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
} else {
response.getStarTrackerSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
}
response.getStarTrackerSettings()->setReverseApiPort(settings.m_reverseAPIPort);
response.getStarTrackerSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIFeatureSetIndex);
response.getStarTrackerSettings()->setReverseApiChannelIndex(settings.m_reverseAPIFeatureIndex);
}
void StarTracker::webapiUpdateFeatureSettings(
StarTrackerSettings& settings,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response)
{
if (featureSettingsKeys.contains("target")) {
settings.m_target = *response.getStarTrackerSettings()->getTarget();
}
if (featureSettingsKeys.contains("ra")) {
settings.m_ra = *response.getStarTrackerSettings()->getRa();
}
if (featureSettingsKeys.contains("dec")) {
settings.m_dec = *response.getStarTrackerSettings()->getDec();
}
if (featureSettingsKeys.contains("latitude")) {
settings.m_latitude = response.getStarTrackerSettings()->getLatitude();
}
if (featureSettingsKeys.contains("longitude")) {
settings.m_longitude = response.getStarTrackerSettings()->getLongitude();
}
if (featureSettingsKeys.contains("dateTime")) {
settings.m_dateTime = *response.getStarTrackerSettings()->getDateTime();
}
if (featureSettingsKeys.contains("pressure")) {
settings.m_pressure = response.getStarTrackerSettings()->getPressure();
}
if (featureSettingsKeys.contains("temperature")) {
settings.m_temperature = response.getStarTrackerSettings()->getTemperature();
}
if (featureSettingsKeys.contains("humidity")) {
settings.m_humidity = response.getStarTrackerSettings()->getHumidity();
}
if (featureSettingsKeys.contains("heightAboveSeaLevel")) {
settings.m_heightAboveSeaLevel = response.getStarTrackerSettings()->getHeightAboveSeaLevel();
}
if (featureSettingsKeys.contains("temperatureLapseRate")) {
settings.m_temperatureLapseRate = response.getStarTrackerSettings()->getTemperatureLapseRate();
}
if (featureSettingsKeys.contains("frequency")) {
settings.m_frequency = response.getStarTrackerSettings()->getFrequency() * 100000.0;
}
if (featureSettingsKeys.contains("stellariumServerEnabled")) {
settings.m_enableServer = response.getStarTrackerSettings()->getStellariumServerEnabled() == 1;
}
if (featureSettingsKeys.contains("stellariumPort")) {
settings.m_serverPort = response.getStarTrackerSettings()->getStellariumPort();
}
if (featureSettingsKeys.contains("updatePeriod")) {
settings.m_updatePeriod = response.getStarTrackerSettings()->getUpdatePeriod();
}
if (featureSettingsKeys.contains("epoch")) {
settings.m_jnow = *response.getStarTrackerSettings()->getEpoch() == "JNOW";
}
if (featureSettingsKeys.contains("title")) {
settings.m_title = *response.getStarTrackerSettings()->getTitle();
}
if (featureSettingsKeys.contains("rgbColor")) {
settings.m_rgbColor = response.getStarTrackerSettings()->getRgbColor();
}
if (featureSettingsKeys.contains("useReverseAPI")) {
settings.m_useReverseAPI = response.getStarTrackerSettings()->getUseReverseApi() != 0;
}
if (featureSettingsKeys.contains("reverseAPIAddress")) {
settings.m_reverseAPIAddress = *response.getStarTrackerSettings()->getReverseApiAddress();
}
if (featureSettingsKeys.contains("reverseAPIPort")) {
settings.m_reverseAPIPort = response.getStarTrackerSettings()->getReverseApiPort();
}
if (featureSettingsKeys.contains("reverseAPIDeviceIndex")) {
settings.m_reverseAPIFeatureSetIndex = response.getStarTrackerSettings()->getReverseApiDeviceIndex();
}
if (featureSettingsKeys.contains("reverseAPIChannelIndex")) {
settings.m_reverseAPIFeatureIndex = response.getStarTrackerSettings()->getReverseApiChannelIndex();
}
}
void StarTracker::webapiReverseSendSettings(QList<QString>& featureSettingsKeys, const StarTrackerSettings& settings, bool force)
{
SWGSDRangel::SWGFeatureSettings *swgFeatureSettings = new SWGSDRangel::SWGFeatureSettings();
// swgFeatureSettings->setOriginatorFeatureIndex(getIndexInDeviceSet());
// swgFeatureSettings->setOriginatorFeatureSetIndex(getDeviceSetIndex());
swgFeatureSettings->setFeatureType(new QString("StarTracker"));
swgFeatureSettings->setStarTrackerSettings(new SWGSDRangel::SWGStarTrackerSettings());
SWGSDRangel::SWGStarTrackerSettings *swgStarTrackerSettings = swgFeatureSettings->getStarTrackerSettings();
// transfer data that has been modified. When force is on transfer all data except reverse API data
if (featureSettingsKeys.contains("target") || force) {
swgStarTrackerSettings->setTarget(new QString(settings.m_target));
}
if (featureSettingsKeys.contains("ra") || force) {
swgStarTrackerSettings->setRa(new QString(settings.m_ra));
}
if (featureSettingsKeys.contains("dec") || force) {
swgStarTrackerSettings->setDec(new QString(settings.m_dec));
}
if (featureSettingsKeys.contains("latitude") || force) {
swgStarTrackerSettings->setLatitude(settings.m_latitude);
}
if (featureSettingsKeys.contains("longitude") || force) {
swgStarTrackerSettings->setLongitude(settings.m_longitude);
}
if (featureSettingsKeys.contains("dateTime") || force) {
swgStarTrackerSettings->setDateTime(new QString(settings.m_dateTime));
}
if (featureSettingsKeys.contains("pressure") || force) {
swgStarTrackerSettings->setPressure(settings.m_pressure);
}
if (featureSettingsKeys.contains("temperature") || force) {
swgStarTrackerSettings->setTemperature(settings.m_temperature);
}
if (featureSettingsKeys.contains("humidity") || force) {
swgStarTrackerSettings->setHumidity(settings.m_humidity);
}
if (featureSettingsKeys.contains("heightAboveSeaLevel") || force) {
swgStarTrackerSettings->setHeightAboveSeaLevel(settings.m_heightAboveSeaLevel);
}
if (featureSettingsKeys.contains("temperatureLapseRate") || force) {
swgStarTrackerSettings->setTemperatureLapseRate(settings.m_temperatureLapseRate);
}
if (featureSettingsKeys.contains("frequency") || force) {
swgStarTrackerSettings->setFrequency(settings.m_frequency / 1000000.0);
}
if (featureSettingsKeys.contains("stellariumServerEnabled") || force) {
swgStarTrackerSettings->setStellariumServerEnabled(settings.m_enableServer ? 1 : 0);
}
if (featureSettingsKeys.contains("stellariumPort") || force) {
swgStarTrackerSettings->setStellariumPort(settings.m_serverPort);
}
if (featureSettingsKeys.contains("updatePeriod") || force) {
swgStarTrackerSettings->setUpdatePeriod(settings.m_updatePeriod);
}
if (featureSettingsKeys.contains("epoch") || force) {
swgStarTrackerSettings->setEpoch(settings.m_jnow ? new QString("JNOW") : new QString("J2000"));
}
if (featureSettingsKeys.contains("title") || force) {
swgStarTrackerSettings->setTitle(new QString(settings.m_title));
}
if (featureSettingsKeys.contains("rgbColor") || force) {
swgStarTrackerSettings->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 StarTracker::networkManagerFinished(QNetworkReply *reply)
{
QNetworkReply::NetworkError replyError = reply->error();
if (replyError)
{
qWarning() << "StarTracker::networkManagerFinished:"
<< " error(" << (int) replyError
<< "): " << replyError
<< ": " << reply->errorString();
}
else
{
QString answer = reply->readAll();
answer.chop(1); // remove last \n
qDebug("StarTracker::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
}
reply->deleteLater();
}

View File

@ -0,0 +1,139 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_STARTRACKER_H_
#define INCLUDE_FEATURE_STARTRACKER_H_
#include <QThread>
#include <QNetworkRequest>
#include "feature/feature.h"
#include "util/message.h"
#include "startrackersettings.h"
class WebAPIAdapterInterface;
class StarTrackerWorker;
class QNetworkAccessManager;
class QNetworkReply;
namespace SWGSDRangel {
class SWGDeviceState;
}
class StarTracker : public Feature
{
Q_OBJECT
public:
class MsgConfigureStarTracker : public Message {
MESSAGE_CLASS_DECLARATION
public:
const StarTrackerSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureStarTracker* create(const StarTrackerSettings& settings, bool force) {
return new MsgConfigureStarTracker(settings, force);
}
private:
StarTrackerSettings m_settings;
bool m_force;
MsgConfigureStarTracker(const StarTrackerSettings& 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)
{ }
};
StarTracker(WebAPIAdapterInterface *webAPIAdapterInterface);
virtual ~StarTracker();
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 StarTrackerSettings& settings);
static void webapiUpdateFeatureSettings(
StarTrackerSettings& settings,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response);
static const char* const m_featureIdURI;
static const char* const m_featureId;
private:
QThread m_thread;
StarTrackerWorker *m_worker;
StarTrackerSettings m_settings;
bool m_ptt;
QNetworkAccessManager *m_networkManager;
QNetworkRequest m_networkRequest;
void start();
void stop();
void applySettings(const StarTrackerSettings& settings, bool force = false);
void webapiReverseSendSettings(QList<QString>& featureSettingsKeys, const StarTrackerSettings& settings, bool force);
private slots:
void networkManagerFinished(QNetworkReply *reply);
};
#endif // INCLUDE_FEATURE_STARTRACKER_H_

View File

@ -0,0 +1,17 @@
<RCC>
<qresource prefix="/startracker/">
<file>startracker/moon-flat-32.png</file>
<file>startracker/moon-full-32.png</file>
<file>startracker/moon-new-32.png</file>
<file>startracker/moon-old-32.png</file>
<file>startracker/moon-waning-crescent-32.png</file>
<file>startracker/moon-waning-gibbous-32.png</file>
<file>startracker/moon-third-quarter-32.png</file>
<file>startracker/moon-waxing-crescent-32.png</file>
<file>startracker/moon-waxing-gibbous-32.png</file>
<file>startracker/moon-first-quarter-32.png</file>
<file>startracker/moon-young-32.png</file>
<file>startracker/pulsar-32.png</file>
<file>startracker/sun-40.png</file>
</qresource>
</RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 607 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 713 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,582 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 <QMessageBox>
#include <QLineEdit>
#include <QRegExp>
#include <QtCharts/QChartView>
#include <QtCharts/QLineSeries>
#include <QtCharts/QDateTimeAxis>
#include <QtCharts/QValueAxis>
#include "feature/featureuiset.h"
#include "feature/featurewebapiutils.h"
#include "gui/basicfeaturesettingsdialog.h"
#include "mainwindow.h"
#include "device/deviceuiset.h"
#include "util/units.h"
#include "util/astronomy.h"
#include "ui_startrackergui.h"
#include "startracker.h"
#include "startrackergui.h"
#include "startrackerreport.h"
#include "startrackersettingsdialog.h"
StarTrackerGUI* StarTrackerGUI::create(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature)
{
StarTrackerGUI* gui = new StarTrackerGUI(pluginAPI, featureUISet, feature);
return gui;
}
void StarTrackerGUI::destroy()
{
delete this;
}
void StarTrackerGUI::resetToDefaults()
{
m_settings.resetToDefaults();
displaySettings();
applySettings(true);
}
QByteArray StarTrackerGUI::serialize() const
{
return m_settings.serialize();
}
bool StarTrackerGUI::deserialize(const QByteArray& data)
{
if (m_settings.deserialize(data))
{
displaySettings();
applySettings(true);
return true;
}
else
{
resetToDefaults();
return false;
}
}
QString StarTrackerGUI::convertDegreesToText(double degrees)
{
if (m_settings.m_azElUnits == StarTrackerSettings::DMS)
return Units::decimalDegreesToDegreeMinutesAndSeconds(degrees);
else if (m_settings.m_azElUnits == StarTrackerSettings::DM)
return Units::decimalDegreesToDegreesAndMinutes(degrees);
else if (m_settings.m_azElUnits == StarTrackerSettings::D)
return Units::decimalDegreesToDegrees(degrees);
else
return QString("%1").arg(degrees, 0, 'f', 2);
}
bool StarTrackerGUI::handleMessage(const Message& message)
{
if (StarTracker::MsgConfigureStarTracker::match(message))
{
qDebug("StarTrackerGUI::handleMessage: StarTracker::MsgConfigureStarTracker");
const StarTracker::MsgConfigureStarTracker& cfg = (StarTracker::MsgConfigureStarTracker&) message;
m_settings = cfg.getSettings();
blockApplySettings(true);
displaySettings();
blockApplySettings(false);
return true;
}
else if (StarTrackerReport::MsgReportAzAl::match(message))
{
StarTrackerReport::MsgReportAzAl& azAl = (StarTrackerReport::MsgReportAzAl&) message;
ui->azimuth->setText(convertDegreesToText(azAl.getAzimuth()));
ui->elevation->setText(convertDegreesToText(azAl.getElevation()));
return true;
}
else if (StarTrackerReport::MsgReportRADec::match(message))
{
StarTrackerReport::MsgReportRADec& raDec = (StarTrackerReport::MsgReportRADec&) message;
m_settings.m_ra = Units::decimalHoursToHoursMinutesAndSeconds(raDec.getRA());
m_settings.m_dec = Units::decimalDegreesToDegreeMinutesAndSeconds(raDec.getDec());
ui->rightAscension->setText(m_settings.m_ra);
ui->declination->setText(m_settings.m_dec);
return true;
}
return false;
}
void StarTrackerGUI::handleInputMessages()
{
Message* message;
while ((message = getInputMessageQueue()->pop()))
{
if (handleMessage(*message)) {
delete message;
}
}
}
void StarTrackerGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{
(void) widget;
(void) rollDown;
}
StarTrackerGUI::StarTrackerGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent) :
FeatureGUI(parent),
ui(new Ui::StarTrackerGUI),
m_pluginAPI(pluginAPI),
m_featureUISet(featureUISet),
m_doApplySettings(true),
m_lastFeatureState(0)
{
ui->setupUi(this);
setAttribute(Qt::WA_DeleteOnClose, true);
setChannelWidget(false);
connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
m_starTracker = reinterpret_cast<StarTracker*>(feature);
m_starTracker->setMessageQueueToGUI(&m_inputMessageQueue);
m_featureUISet->addRollupWidget(this);
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
connect(&m_statusTimer, SIGNAL(timeout()), this, SLOT(updateStatus()));
m_statusTimer.start(1000);
// Intialise chart
m_chart.legend()->hide();
ui->elevationChart->setChart(&m_chart);
ui->elevationChart->setRenderHint(QPainter::Antialiasing);
m_chart.addAxis(&m_chartXAxis, Qt::AlignBottom);
m_chart.addAxis(&m_chartYAxis, Qt::AlignLeft);
ui->dateTime->setDateTime(QDateTime::currentDateTime());
displaySettings();
applySettings(true);
// Use My Position from preferences, if none set
if ((m_settings.m_latitude == 0.0) && (m_settings.m_longitude == 0.0))
on_useMyPosition_clicked();
/*
printf("saemundsson=[");
for (int i = 0; i <= 90; i+= 5)
printf("%f ", Astronomy::refractionSaemundsson(i, m_settings.m_pressure, m_settings.m_temperature));
printf("];\n");
printf("palRadio=[");
for (int i = 0; i <= 90; i+= 5)
printf("%f ", Astronomy::refractionPAL(i, m_settings.m_pressure, m_settings.m_temperature, m_settings.m_humidity,
100000000, m_settings.m_latitude, m_settings.m_heightAboveSeaLevel,
m_settings.m_temperatureLapseRate));
printf("];\n");
printf("palLight=[");
for (int i = 0; i <= 90; i+= 5)
printf("%f ",Astronomy::refractionPAL(i, m_settings.m_pressure, m_settings.m_temperature, m_settings.m_humidity,
7.5e14, m_settings.m_latitude, m_settings.m_heightAboveSeaLevel,
m_settings.m_temperatureLapseRate));
printf("];\n");
*/
}
StarTrackerGUI::~StarTrackerGUI()
{
delete ui;
}
void StarTrackerGUI::blockApplySettings(bool block)
{
m_doApplySettings = !block;
}
void StarTrackerGUI::displaySettings()
{
setTitleColor(m_settings.m_rgbColor);
setWindowTitle(m_settings.m_title);
blockApplySettings(true);
ui->latitude->setValue(m_settings.m_latitude);
ui->longitude->setValue(m_settings.m_longitude);
ui->target->setCurrentIndex(ui->target->findText(m_settings.m_target));
if (m_settings.m_target == "Custom")
{
ui->rightAscension->setText(m_settings.m_ra);
ui->declination->setText(m_settings.m_dec);
}
if (m_settings.m_dateTime == "")
{
ui->dateTimeSelect->setCurrentIndex(0);
ui->dateTime->setVisible(false);
}
else
{
ui->dateTime->setDateTime(QDateTime::fromString(m_settings.m_dateTime, Qt::ISODateWithMs));
ui->dateTime->setVisible(true);
ui->dateTimeSelect->setCurrentIndex(1);
}
updateForTarget();
plotChart();
blockApplySettings(false);
}
void StarTrackerGUI::leaveEvent(QEvent*)
{
}
void StarTrackerGUI::enterEvent(QEvent*)
{
}
void StarTrackerGUI::onMenuDialogCalled(const QPoint &p)
{
if (m_contextMenuType == ContextMenuChannelSettings)
{
BasicFeatureSettingsDialog dialog(this);
dialog.setTitle(m_settings.m_title);
dialog.setColor(m_settings.m_rgbColor);
dialog.setUseReverseAPI(m_settings.m_useReverseAPI);
dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress);
dialog.setReverseAPIPort(m_settings.m_reverseAPIPort);
dialog.setReverseAPIFeatureSetIndex(m_settings.m_reverseAPIFeatureSetIndex);
dialog.setReverseAPIFeatureIndex(m_settings.m_reverseAPIFeatureIndex);
dialog.move(p);
dialog.exec();
m_settings.m_rgbColor = dialog.getColor().rgb();
m_settings.m_title = dialog.getTitle();
m_settings.m_useReverseAPI = dialog.useReverseAPI();
m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
m_settings.m_reverseAPIFeatureSetIndex = dialog.getReverseAPIFeatureSetIndex();
m_settings.m_reverseAPIFeatureIndex = dialog.getReverseAPIFeatureIndex();
setWindowTitle(m_settings.m_title);
setTitleColor(m_settings.m_rgbColor);
applySettings();
}
resetContextMenuType();
}
void StarTrackerGUI::on_startStop_toggled(bool checked)
{
if (m_doApplySettings)
{
StarTracker::MsgStartStop *message = StarTracker::MsgStartStop::create(checked);
m_starTracker->getInputMessageQueue()->push(message);
}
}
void StarTrackerGUI::on_latitude_valueChanged(double value)
{
m_settings.m_latitude = value;
applySettings();
plotChart();
}
void StarTrackerGUI::on_longitude_valueChanged(double value)
{
m_settings.m_longitude = value;
applySettings();
plotChart();
}
void StarTrackerGUI::on_rightAscension_editingFinished()
{
m_settings.m_ra = ui->rightAscension->text();
applySettings();
plotChart();
}
void StarTrackerGUI::on_declination_editingFinished()
{
m_settings.m_dec = ui->declination->text();
applySettings();
plotChart();
}
void StarTrackerGUI::updateForTarget()
{
if (m_settings.m_target == "Sun")
{
ui->rightAscension->setReadOnly(true);
ui->declination->setReadOnly(true);
ui->rightAscension->setText("");
ui->declination->setText("");
}
else if (m_settings.m_target == "Moon")
{
ui->rightAscension->setReadOnly(true);
ui->declination->setReadOnly(true);
ui->rightAscension->setText("");
ui->declination->setText("");
}
else if (m_settings.m_target == "Custom")
{
ui->rightAscension->setReadOnly(false);
ui->declination->setReadOnly(false);
}
else
{
ui->rightAscension->setReadOnly(true);
ui->declination->setReadOnly(true);
if (m_settings.m_target == "PSR B0329+54")
{
ui->rightAscension->setText("03h32m59.35s");
ui->declination->setText(QString("54%0134'45.05\"").arg(QChar(0xb0)));
}
else if (m_settings.m_target == "PSR B0833-45")
{
ui->rightAscension->setText("08h35m20.66s");
ui->declination->setText(QString("-45%0110'35.15\"").arg(QChar(0xb0)));
}
else if (m_settings.m_target == "Sagittarius A")
{
ui->rightAscension->setText("17h45m40.04s");
ui->declination->setText(QString("-29%0100'28.17\"").arg(QChar(0xb0)));
}
else if (m_settings.m_target == "Cassiopeia A")
{
ui->rightAscension->setText("23h23m24s");
ui->declination->setText(QString("58%0148'54\"").arg(QChar(0xb0)));
}
else if (m_settings.m_target == "Cygnus A")
{
ui->rightAscension->setText("19h59m28.36s");
ui->declination->setText(QString("40%0144'02.1\"").arg(QChar(0xb0)));
}
else if (m_settings.m_target == "Taurus A (M1)")
{
ui->rightAscension->setText("05h34m31.94s");
ui->declination->setText(QString("22%0100'52.2\"").arg(QChar(0xb0)));
}
else if (m_settings.m_target == "Virgo A (M87)")
{
ui->rightAscension->setText("12h30m49.42s");
ui->declination->setText(QString("12%0123'28.04\"").arg(QChar(0xb0)));
}
on_rightAscension_editingFinished();
on_declination_editingFinished();
}
// Clear as no longer valid when target has changed
ui->azimuth->setText("");
ui->elevation->setText("");
}
void StarTrackerGUI::on_target_currentTextChanged(const QString &text)
{
m_settings.m_target = text;
applySettings();
updateForTarget();
plotChart();
}
void StarTrackerGUI::updateLST()
{
QDateTime dt;
if (m_settings.m_dateTime.isEmpty())
dt = QDateTime::currentDateTime();
else
dt = QDateTime::fromString(m_settings.m_dateTime, Qt::ISODateWithMs);
double lst = Astronomy::localSiderealTime(dt, m_settings.m_longitude);
ui->lst->setText(Units::decimalHoursToHoursMinutesAndSeconds(lst/15.0, 0));
}
void StarTrackerGUI::updateStatus()
{
int state = m_starTracker->getState();
if (m_lastFeatureState != state)
{
// We set checked state of start/stop button, in case it was changed via API
bool oldState;
switch (state)
{
case Feature::StNotStarted:
ui->startStop->setStyleSheet("QToolButton { background:rgb(79,79,79); }");
break;
case Feature::StIdle:
oldState = ui->startStop->blockSignals(true);
ui->startStop->setChecked(false);
ui->startStop->blockSignals(oldState);
ui->startStop->setStyleSheet("QToolButton { background-color : blue; }");
break;
case Feature::StRunning:
oldState = ui->startStop->blockSignals(true);
ui->startStop->setChecked(true);
ui->startStop->blockSignals(oldState);
ui->startStop->setStyleSheet("QToolButton { background-color : green; }");
break;
case Feature::StError:
ui->startStop->setStyleSheet("QToolButton { background-color : red; }");
QMessageBox::information(this, tr("Message"), m_starTracker->getErrorMessage());
break;
default:
break;
}
m_lastFeatureState = state;
}
updateLST();
}
void StarTrackerGUI::applySettings(bool force)
{
if (m_doApplySettings)
{
StarTracker::MsgConfigureStarTracker* message = StarTracker::MsgConfigureStarTracker::create(m_settings, force);
m_starTracker->getInputMessageQueue()->push(message);
}
}
void StarTrackerGUI::on_useMyPosition_clicked(bool checked)
{
double stationLatitude = MainCore::instance()->getSettings().getLatitude();
double stationLongitude = MainCore::instance()->getSettings().getLongitude();
double stationAltitude = MainCore::instance()->getSettings().getAltitude();
ui->latitude->setValue(stationLatitude);
ui->longitude->setValue(stationLongitude);
m_settings.m_heightAboveSeaLevel = stationAltitude;
applySettings();
plotChart();
}
// Show settings dialog
void StarTrackerGUI::on_displaySettings_clicked()
{
StarTrackerSettingsDialog dialog(&m_settings);
if (dialog.exec() == QDialog::Accepted)
{
applySettings();
}
}
void StarTrackerGUI::on_dateTimeSelect_currentTextChanged(const QString &text)
{
if (text == "Now")
{
m_settings.m_dateTime = "";
ui->dateTime->setVisible(false);
}
else
{
m_settings.m_dateTime = ui->dateTime->dateTime().toString(Qt::ISODateWithMs);
ui->dateTime->setVisible(true);
}
applySettings();
plotChart();
}
void StarTrackerGUI::on_dateTime_dateTimeChanged(const QDateTime &datetime)
{
if (ui->dateTimeSelect->currentIndex() == 1)
{
m_settings.m_dateTime = ui->dateTime->dateTime().toString(Qt::ISODateWithMs);
applySettings();
plotChart();
}
}
// Plot target elevation angle over the day
void StarTrackerGUI::plotChart()
{
m_chart.removeAllSeries();
double maxElevation = -90.0;
QLineSeries *series = new QLineSeries();
QDateTime dt;
if (m_settings.m_dateTime.isEmpty())
dt = QDateTime::currentDateTime();
else
dt = QDateTime::fromString(m_settings.m_dateTime, Qt::ISODateWithMs);
dt.setTime(QTime(0,0));
QDateTime startTime = dt;
QDateTime endTime = dt;
for (int hour = 0; hour <= 24; hour++)
{
AzAlt aa;
RADec rd;
// Calculate elevation of desired object
if (m_settings.m_target == "Sun")
Astronomy::sunPosition(aa, rd, m_settings.m_latitude, m_settings.m_longitude, dt);
else if (m_settings.m_target == "Moon")
Astronomy::moonPosition(aa, rd, m_settings.m_latitude, m_settings.m_longitude, dt);
else
{
rd.ra = Astronomy::raToDecimal(m_settings.m_ra);
rd.dec = Astronomy::decToDecimal(m_settings.m_dec);
aa = Astronomy::raDecToAzAlt(rd, m_settings.m_latitude, m_settings.m_longitude, dt, !m_settings.m_jnow);
}
if (aa.alt > maxElevation)
maxElevation = aa.alt;
// Adjust for refraction
if (m_settings.m_refraction == "Positional Astronomy Library")
{
aa.alt += Astronomy::refractionPAL(aa.alt, m_settings.m_pressure, m_settings.m_temperature, m_settings.m_humidity,
m_settings.m_frequency, m_settings.m_latitude, m_settings.m_heightAboveSeaLevel,
m_settings.m_temperatureLapseRate);
if (aa.alt > 90.0)
aa.alt = 90.0f;
}
else if (m_settings.m_refraction == "Saemundsson")
{
aa.alt += Astronomy::refractionSaemundsson(aa.alt, m_settings.m_pressure, m_settings.m_temperature);
if (aa.alt > 90.0)
aa.alt = 90.0f;
}
series->append(dt.toMSecsSinceEpoch(), aa.alt);
endTime = dt;
dt = dt.addSecs(60*60); // addSecs accounts for daylight savings jumps
}
if (maxElevation < 0)
m_chart.setTitle("Not visible from this latitude");
else
m_chart.setTitle("");
m_chart.addSeries(series);
series->attachAxis(&m_chartXAxis);
series->attachAxis(&m_chartYAxis);
m_chartXAxis.setTitleText(QString("%1 %2").arg(startTime.date().toString()).arg(startTime.timeZoneAbbreviation()));
m_chartXAxis.setFormat("hh");
m_chartXAxis.setTickCount(7);
m_chartXAxis.setRange(startTime, endTime);
m_chartYAxis.setRange(0.0, 90.0);
m_chartYAxis.setTitleText(QString("Elevation (%1)").arg(QChar(0xb0)));
}
// Find target on the Map
void StarTrackerGUI::on_viewOnMap_clicked()
{
QString target = m_settings.m_target == "Sun" || m_settings.m_target == "Moon" ? m_settings.m_target : "Star";
FeatureWebAPIUtils::mapFind(target);
}

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_STARTRACKERGUI_H_
#define INCLUDE_FEATURE_STARTRACKERGUI_H_
#include <QTimer>
#include <QtCharts>
#include "feature/featuregui.h"
#include "util/messagequeue.h"
#include "startrackersettings.h"
class PluginAPI;
class FeatureUISet;
class StarTracker;
namespace Ui {
class StarTrackerGUI;
}
using namespace QtCharts;
class StarTrackerGUI : public FeatureGUI {
Q_OBJECT
public:
static StarTrackerGUI* 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::StarTrackerGUI* ui;
PluginAPI* m_pluginAPI;
FeatureUISet* m_featureUISet;
StarTrackerSettings m_settings;
bool m_doApplySettings;
StarTracker* m_starTracker;
MessageQueue m_inputMessageQueue;
QTimer m_statusTimer;
int m_lastFeatureState;
QChart m_chart;
QDateTimeAxis m_chartXAxis;
QValueAxis m_chartYAxis;
explicit StarTrackerGUI(PluginAPI* pluginAPI, FeatureUISet *featureUISet, Feature *feature, QWidget* parent = nullptr);
virtual ~StarTrackerGUI();
void blockApplySettings(bool block);
void applySettings(bool force = false);
void displaySettings();
void updateForTarget();
QString convertDegreesToText(double degrees);
bool handleMessage(const Message& message);
void updateLST();
void plotChart();
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_rightAscension_editingFinished();
void on_declination_editingFinished();
void on_target_currentTextChanged(const QString &text);
void on_displaySettings_clicked();
void on_dateTimeSelect_currentTextChanged(const QString &text);
void on_dateTime_dateTimeChanged(const QDateTime &datetime);
void updateStatus();
void on_viewOnMap_clicked();
};
#endif // INCLUDE_FEATURE_STARTRACKERGUI_H_

View File

@ -0,0 +1,503 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>StarTrackerGUI</class>
<widget class="RollupWidget" name="StarTrackerGUI">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>337</width>
<height>568</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>Star Tracker</string>
</property>
<widget class="QWidget" name="settingsContainer" native="true">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>301</width>
<height>201</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="1">
<widget class="QDoubleSpinBox" name="latitude">
<property name="toolTip">
<string>Latitude in decimal degrees (North positive) of observation point / 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="1">
<widget class="QLineEdit" name="rightAscension">
<property name="toolTip">
<string>Right Ascension of the target object.
This can be specified as a decimal (E.g. 12.23) or in hours, minutes and seconds (E.g. 12h05m10.2s or 12 05 10.2)</string>
</property>
<property name="text">
<string>23h59m59.59s</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLineEdit" name="azimuth">
<property name="toolTip">
<string>Computed azimuth in degrees to the target from the observation point</string>
</property>
<property name="text">
<string>360</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="timeLabel">
<property name="text">
<string>Time</string>
</property>
</widget>
</item>
<item row="6" column="3">
<widget class="QLineEdit" name="declination">
<property name="toolTip">
<string>Declination of the target object
This can be specified as a decimal (E.g. 34.23) or in degrees, minutes and seconds (E.g. 34d12m10.2s, 34d12'10.2&quot; 34 12 10.2)</string>
</property>
<property name="text">
<string>-90d59'59.59&quot;</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="targetLabel">
<property name="text">
<string>Target</string>
</property>
</widget>
</item>
<item row="3" column="2" colspan="2">
<widget class="QDateTimeEdit" name="dateTime">
<property name="toolTip">
<string>Date and time to use when calculating target'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="2" column="0">
<widget class="QLabel" name="latitudeLabel">
<property name="text">
<string>Latitude</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="dateTimeSelect">
<item>
<property name="text">
<string>Now</string>
</property>
</item>
<item>
<property name="text">
<string>Custom</string>
</property>
</item>
</widget>
</item>
<item row="7" column="2">
<widget class="QLabel" name="elevationLabel">
<property name="text">
<string>Elevation</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="rightAscensionLabel">
<property name="text">
<string>RA</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="4">
<layout class="QHBoxLayout" name="buttonLayout">
<item>
<widget class="ButtonSwitch" name="startStop">
<property name="toolTip">
<string>start/stop acquisition</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="horizontalSpacer_5">
<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="QPushButton" 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="QPushButton" 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="QPushButton" 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="5" column="1" colspan="3">
<layout class="QHBoxLayout" name="targetLayout">
<item>
<widget class="QComboBox" name="target">
<property name="minimumSize">
<size>
<width>110</width>
<height>0</height>
</size>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<item>
<property name="text">
<string>Sun</string>
</property>
</item>
<item>
<property name="text">
<string>Moon</string>
</property>
</item>
<item>
<property name="text">
<string>PSR B0329+54</string>
</property>
</item>
<item>
<property name="text">
<string>PSR B0833-45</string>
</property>
</item>
<item>
<property name="text">
<string>Sagittarius A</string>
</property>
</item>
<item>
<property name="text">
<string>Cassiopeia A</string>
</property>
</item>
<item>
<property name="text">
<string>Cygnus A</string>
</property>
</item>
<item>
<property name="text">
<string>Taurus A (M1)</string>
</property>
</item>
<item>
<property name="text">
<string>Virgo A (M87)</string>
</property>
</item>
<item>
<property name="text">
<string>Custom</string>
</property>
</item>
</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>
<item row="2" column="3">
<widget class="QDoubleSpinBox" name="longitude">
<property name="toolTip">
<string>Longitude in decimal degress (East positive) of observation point / 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="2">
<widget class="QLabel" name="declinationLabel">
<property name="text">
<string>Dec</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLabel" name="longitudeLabel">
<property name="text">
<string>Longitude</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="azimuthtLabel">
<property name="text">
<string>Azimuth</string>
</property>
</widget>
</item>
<item row="7" column="3">
<widget class="QLineEdit" name="elevation">
<property name="toolTip">
<string>Computed elevation in degrees to the target from the observation point</string>
</property>
<property name="text">
<string>90</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="lstLabel">
<property name="text">
<string>LST</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="lst">
<property name="toolTip">
<string>Local sidereal time for selected date, time and longitude</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="chartContainer" native="true">
<property name="geometry">
<rect>
<x>10</x>
<y>220</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>Elevation vs Time</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="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>
<widget class="QChartView" name="elevationChart">
<property name="minimumSize">
<size>
<width>300</width>
<height>250</height>
</size>
</property>
</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>
</customwidgets>
<tabstops>
<tabstop>startStop</tabstop>
<tabstop>useMyPosition</tabstop>
<tabstop>displaySettings</tabstop>
<tabstop>latitude</tabstop>
<tabstop>longitude</tabstop>
<tabstop>target</tabstop>
<tabstop>rightAscension</tabstop>
<tabstop>declination</tabstop>
<tabstop>azimuth</tabstop>
<tabstop>elevation</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 "startrackergui.h"
#endif
#include "startracker.h"
#include "startrackerplugin.h"
#include "startrackerwebapiadapter.h"
const PluginDescriptor StarTrackerPlugin::m_pluginDescriptor = {
StarTracker::m_featureId,
QStringLiteral("Star Tracker"),
QStringLiteral("6.4.0"),
QStringLiteral("(c) Jon Beniston, M7RCE"),
QStringLiteral("https://github.com/f4exb/sdrangel"),
true,
QStringLiteral("https://github.com/f4exb/sdrangel")
};
StarTrackerPlugin::StarTrackerPlugin(QObject* parent) :
QObject(parent),
m_pluginAPI(nullptr)
{
}
const PluginDescriptor& StarTrackerPlugin::getPluginDescriptor() const
{
return m_pluginDescriptor;
}
void StarTrackerPlugin::initPlugin(PluginAPI* pluginAPI)
{
m_pluginAPI = pluginAPI;
m_pluginAPI->registerFeature(StarTracker::m_featureIdURI, StarTracker::m_featureId, this);
}
#ifdef SERVER_MODE
FeatureGUI* StarTrackerPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const
{
(void) featureUISet;
(void) feature;
return nullptr;
}
#else
FeatureGUI* StarTrackerPlugin::createFeatureGUI(FeatureUISet *featureUISet, Feature *feature) const
{
return StarTrackerGUI::create(m_pluginAPI, featureUISet, feature);
}
#endif
Feature* StarTrackerPlugin::createFeature(WebAPIAdapterInterface* webAPIAdapterInterface) const
{
return new StarTracker(webAPIAdapterInterface);
}
FeatureWebAPIAdapter* StarTrackerPlugin::createFeatureWebAPIAdapter() const
{
return new StarTrackerWebAPIAdapter();
}

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_STARTRACKERPLUGIN_H
#define INCLUDE_FEATURE_STARTRACKERPLUGIN_H
#include <QObject>
#include "plugin/plugininterface.h"
class FeatureGUI;
class WebAPIAdapterInterface;
class StarTrackerPlugin : public QObject, PluginInterface {
Q_OBJECT
Q_INTERFACES(PluginInterface)
Q_PLUGIN_METADATA(IID "sdrangel.feature.startracker")
public:
explicit StarTrackerPlugin(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_STARTRACKERPLUGIN_H

View File

@ -0,0 +1,83 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2019 Edouard Griffiths, F4EXB //
// 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_STARTRACKERREPORT_H_
#define INCLUDE_FEATURE_STARTRACKERREPORT_H_
#include <QObject>
#include "util/message.h"
class StarTrackerReport : public QObject
{
Q_OBJECT
public:
class MsgReportAzAl : public Message {
MESSAGE_CLASS_DECLARATION
public:
double getAzimuth() const { return m_azimuth; }
double getElevation() const { return m_elevation; }
static MsgReportAzAl* create(double azimuth, double elevation)
{
return new MsgReportAzAl(azimuth, elevation);
}
private:
double m_azimuth;
double m_elevation;
MsgReportAzAl(double azimuth, double elevation) :
Message(),
m_azimuth(azimuth),
m_elevation(elevation)
{
}
};
class MsgReportRADec : public Message {
MESSAGE_CLASS_DECLARATION
public:
double getRA() const { return m_ra; }
double getDec() const { return m_dec; }
static MsgReportRADec* create(double ra, double dec)
{
return new MsgReportRADec(ra, dec);
}
private:
double m_ra;
double m_dec;
MsgReportRADec(double ra, double dec) :
Message(),
m_ra(ra),
m_dec(dec)
{
}
};
public:
StarTrackerReport() {}
~StarTrackerReport() {}
};
#endif // INCLUDE_FEATURE_STARTRACKERREPORT_H_

View File

@ -0,0 +1,166 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "util/simpleserializer.h"
#include "settings/serializable.h"
#include "startrackersettings.h"
StarTrackerSettings::StarTrackerSettings()
{
resetToDefaults();
}
void StarTrackerSettings::resetToDefaults()
{
m_ra = "";
m_dec = "";
m_latitude = 0.0;
m_longitude = 0.0;
m_target = "Sun";
m_dateTime = "";
m_refraction = "Positional Astronomy Library";
m_pressure = 1010;
m_temperature = 10;
m_humidity = 80.0;
m_heightAboveSeaLevel = 0.0;
m_temperatureLapseRate = 6.49;
m_frequency = 435000000;
m_enableServer = true;
m_serverPort = 10001;
m_azElUnits = DM;
m_updatePeriod = 1.0;
m_jnow = false;
m_drawSunOnMap = true;
m_drawMoonOnMap = true;
m_drawStarOnMap = true;
m_title = "Star 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;
}
QByteArray StarTrackerSettings::serialize() const
{
SimpleSerializer s(1);
s.writeString(1, m_ra);
s.writeString(2, m_dec);
s.writeDouble(3, m_latitude);
s.writeDouble(4, m_longitude);
s.writeString(5, m_target);
s.writeString(6, m_dateTime);
s.writeU32(7, m_enableServer);
s.writeU32(8, m_serverPort);
s.writeS32(9, m_azElUnits);
s.writeFloat(10, m_updatePeriod);
s.writeBool(11, m_jnow);
s.writeString(12, m_refraction);
s.writeDouble(13, m_pressure);
s.writeDouble(14, m_temperature);
s.writeDouble(15, m_humidity);
s.writeDouble(16, m_heightAboveSeaLevel);
s.writeDouble(17, m_temperatureLapseRate);
s.writeDouble(18, m_frequency);
s.writeBool(19, m_drawSunOnMap);
s.writeBool(20, m_drawMoonOnMap);
s.writeBool(21, m_drawStarOnMap);
s.writeString(22, m_title);
s.writeU32(23, m_rgbColor);
s.writeBool(24, m_useReverseAPI);
s.writeString(25, m_reverseAPIAddress);
s.writeU32(26, m_reverseAPIPort);
s.writeU32(27, m_reverseAPIFeatureSetIndex);
s.writeU32(28, m_reverseAPIFeatureIndex);
return s.final();
}
bool StarTrackerSettings::deserialize(const QByteArray& data)
{
SimpleDeserializer d(data);
if (!d.isValid())
{
resetToDefaults();
return false;
}
if (d.getVersion() == 1)
{
QByteArray bytetmp;
uint32_t utmp;
QString strtmp;
d.readString(1, &m_ra, "");
d.readString(2, &m_dec, "");
d.readDouble(3, &m_latitude, 0.0);
d.readDouble(4, &m_longitude, 0.0);
d.readString(5, &m_target, "Sun");
d.readString(6, &m_dateTime, "");
d.readBool(7, &m_enableServer, true);
d.readU32(8, &utmp, 0);
if ((utmp > 1023) && (utmp < 65535)) {
m_serverPort = utmp;
} else {
m_serverPort = 10001;
}
d.readS32(9, (qint32 *)&m_azElUnits, DM);
d.readFloat(10, &m_updatePeriod, 1.0f);
d.readBool(11, &m_jnow, false);
d.readString(12, &m_refraction, "Positional Astronomy Library");
d.readDouble(13, &m_pressure, 1010);
d.readDouble(14, &m_temperature, 10);
d.readDouble(15, &m_humidity, 10);
d.readDouble(16, &m_heightAboveSeaLevel, 80);
d.readDouble(17, &m_temperatureLapseRate, 6.49);
d.readDouble(18, &m_frequency, 435000000.0);
d.readBool(19, &m_drawSunOnMap, true);
d.readBool(20, &m_drawMoonOnMap, true);
d.readBool(21, &m_drawStarOnMap, true);
d.readString(22, &m_title, "Star Tracker");
d.readU32(23, &m_rgbColor, QColor(225, 25, 99).rgb());
d.readBool(24, &m_useReverseAPI, false);
d.readString(25, &m_reverseAPIAddress, "127.0.0.1");
d.readU32(26, &utmp, 0);
if ((utmp > 1023) && (utmp < 65535)) {
m_reverseAPIPort = utmp;
} else {
m_reverseAPIPort = 8888;
}
d.readU32(27, &utmp, 0);
m_reverseAPIFeatureSetIndex = utmp > 99 ? 99 : utmp;
d.readU32(28, &utmp, 0);
m_reverseAPIFeatureIndex = utmp > 99 ? 99 : utmp;
return true;
}
else
{
resetToDefaults();
return false;
}
}

View File

@ -0,0 +1,67 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_STARTRACKERSETTINGS_H_
#define INCLUDE_FEATURE_STARTRACKERSETTINGS_H_
#include <QByteArray>
#include <QString>
#include "util/message.h"
class Serializable;
struct StarTrackerSettings
{
QString m_ra;
QString m_dec;
double m_latitude;
double m_longitude;
QString m_target; // Sun, Moon, Custom
QString m_dateTime; // Date/time for observation, or "" for now
QString m_refraction; // Refraction correction. "None", "Saemundsson" or "Positional Astronomy Library"
double m_pressure; // Air pressure in millibars
double m_temperature; // Air temperature in C
double m_humidity; // Humidity in %
double m_heightAboveSeaLevel; // In metres
double m_temperatureLapseRate; // In K/km
double m_frequency; // Observation frequency in Hz
uint16_t m_serverPort;
bool m_enableServer; // Enable Stellarium server
enum AzElUnits {DMS, DM, D, Decimal} m_azElUnits;
float m_updatePeriod;
bool m_jnow; // Use JNOW epoch rather than J2000
bool m_drawSunOnMap;
bool m_drawMoonOnMap;
bool m_drawStarOnMap;
QString m_title;
quint32 m_rgbColor;
bool m_useReverseAPI;
QString m_reverseAPIAddress;
uint16_t m_reverseAPIPort;
uint16_t m_reverseAPIFeatureSetIndex;
uint16_t m_reverseAPIFeatureIndex;
StarTrackerSettings();
void resetToDefaults();
QByteArray serialize() const;
bool deserialize(const QByteArray& data);
};
#endif // INCLUDE_FEATURE_STARTRACKERSETTINGS_H_

View File

@ -0,0 +1,68 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "startrackersettingsdialog.h"
#include <QDebug>
StarTrackerSettingsDialog::StarTrackerSettingsDialog(StarTrackerSettings *settings,
QWidget* parent) :
QDialog(parent),
ui(new Ui::StarTrackerSettingsDialog),
m_settings(settings)
{
ui->setupUi(this);
ui->epoch->setCurrentIndex(settings->m_jnow ? 1 : 0);
ui->azElUnits->setCurrentIndex((int)settings->m_azElUnits);
ui->updatePeriod->setValue(settings->m_updatePeriod);
ui->serverPort->setValue(settings->m_serverPort);
ui->enableServer->setChecked(settings->m_enableServer);
ui->refraction->setCurrentIndex(ui->refraction->findText(settings->m_refraction));
ui->pressure->setValue(settings->m_pressure);
ui->temperature->setValue(settings->m_temperature);
ui->humidity->setValue(settings->m_humidity);
ui->height->setValue(settings->m_heightAboveSeaLevel);
ui->temperatureLapseRate->setValue(settings->m_temperatureLapseRate);
ui->frequency->setValue(settings->m_frequency/1000000.0);
ui->drawSunOnMap->setChecked(settings->m_drawSunOnMap);
ui->drawMoonOnMap->setChecked(settings->m_drawMoonOnMap);
ui->drawStarOnMap->setChecked(settings->m_drawStarOnMap);
}
StarTrackerSettingsDialog::~StarTrackerSettingsDialog()
{
delete ui;
}
void StarTrackerSettingsDialog::accept()
{
m_settings->m_jnow = ui->epoch->currentIndex() == 1;
m_settings->m_azElUnits = (StarTrackerSettings::AzElUnits)ui->azElUnits->currentIndex();
m_settings->m_updatePeriod = ui->updatePeriod->value();
m_settings->m_serverPort = (uint16_t)ui->serverPort->value();
m_settings->m_enableServer = ui->enableServer->isChecked();
m_settings->m_refraction = ui->refraction->currentText();
m_settings->m_pressure = ui->pressure->value();
m_settings->m_temperature = ui->temperature->value();
m_settings->m_humidity = ui->humidity->value();
m_settings->m_heightAboveSeaLevel = ui->height->value();
m_settings->m_temperatureLapseRate = ui->temperatureLapseRate->value();
m_settings->m_frequency = ui->frequency->value() * 1000000.0;
m_settings->m_drawSunOnMap = ui->drawSunOnMap->isChecked();
m_settings->m_drawMoonOnMap = ui->drawMoonOnMap->isChecked();
m_settings->m_drawStarOnMap = ui->drawStarOnMap->isChecked();
QDialog::accept();
}

View File

@ -0,0 +1,40 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_STARTRACKERSETTINGSDIALOG_H
#define INCLUDE_STARTRACKERSETTINGSDIALOG_H
#include "ui_startrackersettingsdialog.h"
#include "startrackersettings.h"
class StarTrackerSettingsDialog : public QDialog {
Q_OBJECT
public:
explicit StarTrackerSettingsDialog(StarTrackerSettings* settings, QWidget* parent = 0);
~StarTrackerSettingsDialog();
StarTrackerSettings *m_settings;
private slots:
void accept();
private:
Ui::StarTrackerSettingsDialog* ui;
};
#endif // INCLUDE_STARTRACKERSETTINGSDIALOG_H

View File

@ -0,0 +1,382 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>StarTrackerSettingsDialog</class>
<widget class="QDialog" name="StarTrackerSettingsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>351</width>
<height>468</height>
</rect>
</property>
<property name="font">
<font>
<family>Liberation Sans</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="windowTitle">
<string>Star Tracker Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="groupBox">
<layout class="QGridLayout" name="gridLayout">
<item row="6" column="0">
<widget class="QLabel" name="heightLabel">
<property name="text">
<string>Height above sea level (m)</string>
</property>
</widget>
</item>
<item row="16" column="1">
<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="15" column="0">
<widget class="QCheckBox" name="drawStarOnMap">
<property name="text">
<string>Draw target star on map</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="temperatureLabel">
<property name="text">
<string>Air temperature (C)</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="refractionLabel">
<property name="text">
<string>Refraction correction</string>
</property>
</widget>
</item>
<item row="11" column="1">
<widget class="QSpinBox" name="serverPort">
<property name="toolTip">
<string>Stellarium telescope server IP port number</string>
</property>
<property name="minimum">
<number>1024</number>
</property>
<property name="maximum">
<number>65535</number>
</property>
<property name="value">
<number>10001</number>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QSpinBox" name="frequency">
<property name="toolTip">
<string>Radio frequency being observed</string>
</property>
<property name="maximum">
<number>1000000000</number>
</property>
<property name="value">
<number>435</number>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="pressureLabel">
<property name="text">
<string>Air pressure (mb)</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QDoubleSpinBox" name="pressure">
<property name="toolTip">
<string>Air pressure in millibars, for use in atmospheric refraction correction</string>
</property>
<property name="maximum">
<double>2000.000000000000000</double>
</property>
<property name="singleStep">
<double>1.000000000000000</double>
</property>
<property name="value">
<double>1010.000000000000000</double>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="epochLabel">
<property name="text">
<string>Epoch for RA &amp; Dec</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="temperature">
<property name="toolTip">
<string>Air temperature in degrees Celsius, for use in atmospheric refraction correction</string>
</property>
<property name="minimum">
<number>-100</number>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>10</number>
</property>
</widget>
</item>
<item row="13" column="0">
<widget class="QCheckBox" name="drawSunOnMap">
<property name="text">
<string>Draw Sun on map</string>
</property>
</widget>
</item>
<item row="14" column="0">
<widget class="QCheckBox" name="drawMoonOnMap">
<property name="text">
<string>Draw Moon on map</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QSpinBox" name="humidity">
<property name="toolTip">
<string>Relative humidity in %</string>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="value">
<number>80</number>
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QLabel" name="serverPortLabel">
<property name="text">
<string>Stellarium server port</string>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="updatePeriodLabel">
<property name="text">
<string>Update period (s)</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="humidityLabel">
<property name="text">
<string>Humidity (%)</string>
</property>
</widget>
</item>
<item row="9" 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="value">
<double>1.000000000000000</double>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QSpinBox" name="height">
<property name="toolTip">
<string>Height of observation/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="8" column="0">
<widget class="QLabel" name="frequencyLabel">
<property name="text">
<string>Frequency (MHz)</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="epoch">
<property name="toolTip">
<string>Epoch for custom right ascension and declination</string>
</property>
<item>
<property name="text">
<string>J2000</string>
</property>
</item>
<item>
<property name="text">
<string>JNOW</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="azElUnitsLabel">
<property name="text">
<string>Azimuth and elevation units</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="refraction">
<property name="toolTip">
<string>Atmospheric refraction correction</string>
</property>
<property name="currentIndex">
<number>0</number>
</property>
<item>
<property name="text">
<string>None</string>
</property>
</item>
<item>
<property name="text">
<string>Saemundsson</string>
</property>
</item>
<item>
<property name="text">
<string>Positional Astronomy Library</string>
</property>
</item>
</widget>
</item>
<item row="12" column="0">
<widget class="QCheckBox" name="enableServer">
<property name="toolTip">
<string>Enable Stellarium server which allows RA and Dec to be sent to and from Stellarium</string>
</property>
<property name="text">
<string>Stellarium server</string>
</property>
</widget>
</item>
<item row="1" 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="7" column="0">
<widget class="QLabel" name="temperatureLapseRateLabel">
<property name="toolTip">
<string>Temperature lapse rate (K/m)</string>
</property>
<property name="text">
<string>Temperature lapse rate (K/km)</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QDoubleSpinBox" name="temperatureLapseRate">
<property name="decimals">
<number>3</number>
</property>
<property name="maximum">
<double>100.000000000000000</double>
</property>
<property name="value">
<double>6.490000000000000</double>
</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>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>StarTrackerSettingsDialog</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>StarTrackerSettingsDialog</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,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 "startracker.h"
#include "startrackerwebapiadapter.h"
StarTrackerWebAPIAdapter::StarTrackerWebAPIAdapter()
{}
StarTrackerWebAPIAdapter::~StarTrackerWebAPIAdapter()
{}
int StarTrackerWebAPIAdapter::webapiSettingsGet(
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage)
{
(void) errorMessage;
response.setSimplePttSettings(new SWGSDRangel::SWGSimplePTTSettings());
response.getSimplePttSettings()->init();
StarTracker::webapiFormatFeatureSettings(response, m_settings);
return 200;
}
int StarTrackerWebAPIAdapter::webapiSettingsPutPatch(
bool force,
const QStringList& featureSettingsKeys,
SWGSDRangel::SWGFeatureSettings& response,
QString& errorMessage)
{
(void) force; // no action
(void) errorMessage;
StarTracker::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_STARTRACKER_WEBAPIADAPTER_H
#define INCLUDE_STARTRACKER_WEBAPIADAPTER_H
#include "feature/featurewebapiadapter.h"
#include "startrackersettings.h"
/**
* Standalone API adapter only for the settings
*/
class StarTrackerWebAPIAdapter : public FeatureWebAPIAdapter {
public:
StarTrackerWebAPIAdapter();
virtual ~StarTrackerWebAPIAdapter();
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:
StarTrackerSettings m_settings;
};
#endif // INCLUDE_STARTRACKER_WEBAPIADAPTER_H

View File

@ -0,0 +1,551 @@
///////////////////////////////////////////////////////////////////////////////////
// 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 "maincore.h"
#include "startracker.h"
#include "startrackerworker.h"
#include "startrackerreport.h"
MESSAGE_CLASS_DEFINITION(StarTrackerWorker::MsgConfigureStarTrackerWorker, Message)
MESSAGE_CLASS_DEFINITION(StarTrackerReport::MsgReportAzAl, Message)
MESSAGE_CLASS_DEFINITION(StarTrackerReport::MsgReportRADec, Message)
StarTrackerWorker::StarTrackerWorker(StarTracker* starTracker, WebAPIAdapterInterface *webAPIAdapterInterface) :
m_starTracker(starTracker),
m_webAPIAdapterInterface(webAPIAdapterInterface),
m_msgQueueToFeature(nullptr),
m_msgQueueToGUI(nullptr),
m_running(false),
m_mutex(QMutex::Recursive),
m_tcpServer(nullptr),
m_clientConnection(nullptr)
{
connect(&m_pollTimer, SIGNAL(timeout()), this, SLOT(update()));
}
StarTrackerWorker::~StarTrackerWorker()
{
m_inputMessageQueue.clear();
}
void StarTrackerWorker::reset()
{
QMutexLocker mutexLocker(&m_mutex);
m_inputMessageQueue.clear();
}
bool StarTrackerWorker::startWork()
{
QMutexLocker mutexLocker(&m_mutex);
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
m_pollTimer.start((int)round(m_settings.m_updatePeriod*1000.0));
m_running = true;
return m_running;
}
void StarTrackerWorker::stopWork()
{
QMutexLocker mutexLocker(&m_mutex);
disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
restartServer(false, 0);
m_pollTimer.stop();
m_running = false;
}
void StarTrackerWorker::handleInputMessages()
{
Message* message;
while ((message = m_inputMessageQueue.pop()) != nullptr)
{
if (handleMessage(*message)) {
delete message;
}
}
}
bool StarTrackerWorker::handleMessage(const Message& cmd)
{
if (MsgConfigureStarTrackerWorker::match(cmd))
{
QMutexLocker mutexLocker(&m_mutex);
MsgConfigureStarTrackerWorker& cfg = (MsgConfigureStarTrackerWorker&) cmd;
applySettings(cfg.getSettings(), cfg.getForce());
return true;
}
else
{
return false;
}
}
void StarTrackerWorker::applySettings(const StarTrackerSettings& settings, bool force)
{
qDebug() << "StarTrackerWorker::applySettings:"
<< " m_target: " << settings.m_target
<< " m_ra: " << settings.m_ra
<< " m_dec: " << settings.m_dec
<< " m_time: " << settings.m_dateTime
<< " m_enableServer: " << settings.m_enableServer
<< " m_serverPort: " << settings.m_serverPort
<< " 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_dateTime != settings.m_dateTime)
|| (m_settings.m_refraction != settings.m_refraction)
|| (m_settings.m_pressure != settings.m_pressure)
|| (m_settings.m_temperature != settings.m_temperature)
|| (m_settings.m_ra != settings.m_ra)
|| (m_settings.m_dec != settings.m_dec) || force)
{
// Recalculate immediately
QTimer::singleShot(1, this, &StarTrackerWorker::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_drawSunOnMap && m_settings.m_drawSunOnMap)
removeFromMap("Sun");
if (!settings.m_drawMoonOnMap && m_settings.m_drawMoonOnMap)
removeFromMap("Moon");
if ((!settings.m_drawStarOnMap && m_settings.m_drawStarOnMap)
|| (((settings.m_target == "Sun") || (settings.m_target == "Moon"))
&& ((m_settings.m_target != "Sun") && (m_settings.m_target != "Moon"))))
removeFromMap("Star");
if ((settings.m_serverPort != m_settings.m_serverPort) ||
(settings.m_enableServer != m_settings.m_enableServer) || force)
{
restartServer(settings.m_enableServer, settings.m_serverPort);
}
m_settings = settings;
}
void StarTrackerWorker::restartServer(bool enabled, uint32_t port)
{
if (m_tcpServer)
{
if (m_clientConnection)
{
m_clientConnection->close();
delete m_clientConnection;
m_clientConnection = nullptr;
}
disconnect(m_tcpServer, &QTcpServer::newConnection, this, &StarTrackerWorker::acceptConnection);
m_tcpServer->close();
delete m_tcpServer;
m_tcpServer = nullptr;
}
if (enabled)
{
qDebug() << "StarTrackerWorker::restartServer: server enabled on port " << port;
m_tcpServer = new QTcpServer(this);
if (!m_tcpServer->listen(QHostAddress::Any, port)) {
qWarning("Star Tracker failed to listen on port %u. Check it is not already in use.", port);
} else {
connect(m_tcpServer, &QTcpServer::newConnection, this, &StarTrackerWorker::acceptConnection);
}
}
}
void StarTrackerWorker::acceptConnection()
{
QMutexLocker mutexLocker(&m_mutex);
m_clientConnection = m_tcpServer->nextPendingConnection();
if (!m_clientConnection) {
return;
}
connect(m_clientConnection, &QIODevice::readyRead, this, &StarTrackerWorker::readStellariumCommand);
connect(m_clientConnection, SIGNAL(disconnected()), this, SLOT(disconnected()));
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
connect(m_clientConnection, QOverload<QAbstractSocket::SocketError>::of(&QAbstractSocket::error), this, &StarTrackerWorker::errorOccurred);
#else
connect(m_clientConnection, &QAbstractSocket::errorOccurred, this, &StarTrackerWorker::errorOccurred);
#endif
qDebug() << "StarTrackerWorker::acceptConnection: client connected";
}
void StarTrackerWorker::disconnected()
{
QMutexLocker mutexLocker(&m_mutex);
qDebug() << "StarTrackerWorker::disconnected";
m_clientConnection->deleteLater();
m_clientConnection = nullptr;
/*if (m_msgQueueToFeature)
{
StarTrackerWorker::MsgReportWorker *msg = StarTrackerWorker::MsgReportWorker::create("Disconnected");
m_msgQueueToFeature->push(msg);
}*/
}
void StarTrackerWorker::errorOccurred(QAbstractSocket::SocketError socketError)
{
qDebug() << "StarTrackerWorker::errorOccurred: " << socketError;
/*if (m_msgQueueToFeature)
{
StarTrackerWorker::MsgReportWorker *msg = StarTrackerWorker::MsgReportWorker::create(m_socket.errorString() + " " + socketError);
m_msgQueueToFeature->push(msg);
}*/
}
// Get RA & Dec from Stellarium
// Protocol described here:
// http://svn.code.sf.net/p/stellarium/code/trunk/telescope_server/stellarium_telescope_protocol.txt
void StarTrackerWorker::readStellariumCommand()
{
QMutexLocker mutexLocker(&m_mutex);
unsigned char buf[64];
qint64 len;
len = m_clientConnection->read((char *)buf, sizeof(buf));
if (len != -1)
{
int msg_len;
int msg_type;
unsigned char *msg;
// Extract length and message type
msg_len = buf[0] | (buf[1] << 8);
msg_type = buf[2] | (buf[3] << 8);
msg = &buf[4];
if (msg_type == 0) // MessageGoto
{
unsigned ra;
int dec;
if (msg_len == 20)
{
// Skip time
msg += 8;
// Extract RA LSB first
ra = msg[0] | (msg[1] << 8) | (msg[2] << 16) | (msg[3] << 24);
msg += 4;
// Extract DEC LSB first
dec = msg[0] | (msg[1] << 8) | (msg[2] << 16) | (msg[3] << 24);
msg += 4;
// Convert from integer to floating point
double raDeg = ra*(24.0/4294967296.0); // Convert to decimal hours
double decDeg = dec*(360.0/4294967296.0); // Convert to decimal degrees
// Set as current target
m_settings.m_ra = Units::decimalHoursToHoursMinutesAndSeconds(raDeg);
m_settings.m_dec = Units::decimalDegreesToDegreeMinutesAndSeconds(decDeg);
qDebug() << "StarTrackerWorker: New target from Stellarum: " << m_settings.m_ra << " " << m_settings.m_dec;
// Forward to GUI for display
if (getMessageQueueToGUI())
getMessageQueueToGUI()->push(StarTrackerReport::MsgReportRADec::create(raDeg, decDeg));
}
else
{
qDebug() << "StarTrackerWorker: Unexpected number of bytes received (" << len << ") for message type: " << msg_type;
}
}
else
{
qDebug() << "StarTrackerWorker: Unsupported Stellarium message type: " << msg_type;
}
}
}
// Send our target to Stellarium (J2000 epoch)
void StarTrackerWorker::writeStellariumTarget(double ra, double dec)
{
QMutexLocker mutexLocker(&m_mutex);
if (m_clientConnection != nullptr)
{
unsigned char buf[24];
// Length
buf[0] = sizeof(buf);
buf[1] = 0;
// Type (MessageCurrentPosition)
buf[2] = 0;
buf[3] = 0;
// Time (unused)
buf[4] = 0;
buf[5] = 0;
buf[6] = 0;
buf[7] = 0;
buf[8] = 0;
buf[9] = 0;
buf[10] = 0;
buf[11] = 0;
// RA
unsigned raInt = ra * (4294967296.0/24.0);
buf[12] = raInt & 0xff;
buf[13] = (raInt >> 8) & 0xff;
buf[14] = (raInt >> 16) & 0xff;
buf[15] = (raInt >> 24) & 0xff;
// Dec
int decInt = dec * (4294967296.0/360.0);
buf[16] = decInt & 0xff;
buf[17] = (decInt >> 8) & 0xff;
buf[18] = (decInt >> 16) & 0xff;
buf[19] = (decInt >> 24) & 0xff;
// Status (OK)
buf[20] = 0;
buf[21] = 0;
buf[22] = 0;
buf[23] = 0;
m_clientConnection->write((char *)buf, sizeof(buf));
}
}
void StarTrackerWorker::updateRaDec(RADec rd, QDateTime dt)
{
RADec rdJ2000;
double jd;
jd = Astronomy::julianDate(dt);
// Precess to J2000
rdJ2000 = Astronomy::precess(rd, jd, Astronomy::jd_j2000());
// Send to Stellarium
writeStellariumTarget(rdJ2000.ra, rdJ2000.dec);
// Send to GUI
if (m_settings.m_target == "Sun" || m_settings.m_target == "Moon")
{
if (getMessageQueueToGUI())
{
if (m_settings.m_jnow)
getMessageQueueToGUI()->push(StarTrackerReport::MsgReportRADec::create(rd.ra, rd.dec));
else
getMessageQueueToGUI()->push(StarTrackerReport::MsgReportRADec::create(rdJ2000.ra, rdJ2000.dec));
}
}
}
void StarTrackerWorker::removeFromMap(QString id)
{
MessagePipes& messagePipes = MainCore::instance()->getMessagePipes();
QList<MessageQueue*> *mapMessageQueues = messagePipes.getMessageQueues(m_starTracker, "mapitems");
if (mapMessageQueues)
{
sendToMap(mapMessageQueues, id, "", "", 0.0, 0.0);
}
}
void StarTrackerWorker::sendToMap(QList<MessageQueue*> *mapMessageQueues, QString name, QString image, QString text, double lat, double lon, double rotation)
{
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->setImage(new QString(image));
swgMapItem->setImageRotation(rotation);
swgMapItem->setText(new QString(text));
swgMapItem->setImageFixedSize(1);
MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_starTracker, swgMapItem);
(*it)->push(msg);
}
}
QString moonPhase(double sunLongitude, double moonLongitude, double observationLatitude, double &rotation)
{
double difference = sunLongitude - moonLongitude;
if (difference < -180.0)
difference += 360.0;
else if (difference > 180.0)
difference -= 360.0;
if (difference >= 0.0)
rotation = observationLatitude - 90.0;
else
rotation = 90.0 - observationLatitude;
// These probably shouldn't be divided equally
if (difference < -157.5)
return "full";
else if (difference < -112.5)
return "waxing-gibbous";
else if (difference < -67.5)
return "first-quarter";
else if (difference < -22.5)
return "waxing-crescent";
else if (difference < 22.5)
return "new";
else if (difference < 67.5)
return "waning-crescent";
else if (difference < 112.5)
return "third-quarter";
else if (difference < 157.5)
return "waning-gibbous";
else if (difference < 202.5)
return "full";
else
return "full";
}
void StarTrackerWorker::update()
{
AzAlt aa, sunAA, moonAA;
RADec rd, sunRD, moonRD;
QDateTime dt;
// Get date and time to calculate position at
if (m_settings.m_dateTime == "")
dt = QDateTime::currentDateTime();
else
dt = QDateTime::fromString(m_settings.m_dateTime, Qt::ISODateWithMs);
// Calculate position
if ((m_settings.m_target == "Sun") || (m_settings.m_drawSunOnMap))
Astronomy::sunPosition(sunAA, sunRD, m_settings.m_latitude, m_settings.m_longitude, dt);
if ((m_settings.m_target == "Moon") || (m_settings.m_drawMoonOnMap))
Astronomy::moonPosition(moonAA, moonRD, m_settings.m_latitude, m_settings.m_longitude, dt);
if (m_settings.m_target == "Sun")
{
rd = sunRD;
aa = sunAA;
}
else if (m_settings.m_target == "Moon")
{
rd = moonRD;
aa = moonAA;
}
else
{
// Convert RA/Dec to Alt/Az
rd.ra = Astronomy::raToDecimal(m_settings.m_ra);
rd.dec = Astronomy::decToDecimal(m_settings.m_dec);
aa = Astronomy::raDecToAzAlt(rd, m_settings.m_latitude, m_settings.m_longitude, dt, !m_settings.m_jnow);
}
updateRaDec(rd, dt);
// Adjust for refraction
if (m_settings.m_refraction == "Positional Astronomy Library")
{
aa.alt += Astronomy::refractionPAL(aa.alt, m_settings.m_pressure, m_settings.m_temperature, m_settings.m_humidity,
m_settings.m_frequency, m_settings.m_latitude, m_settings.m_heightAboveSeaLevel,
m_settings.m_temperatureLapseRate);
if (aa.alt > 90.0)
aa.alt = 90.0f;
}
else if (m_settings.m_refraction == "Saemundsson")
{
aa.alt += Astronomy::refractionSaemundsson(aa.alt, m_settings.m_pressure, m_settings.m_temperature);
if (aa.alt > 90.0)
aa.alt = 90.0f;
}
// Send to GUI
if (getMessageQueueToGUI())
{
StarTrackerReport::MsgReportAzAl *msg = StarTrackerReport::MsgReportAzAl::create(aa.az, aa.alt);
getMessageQueueToGUI()->push(msg);
}
// Send Az/El to Rotator Controllers
MessagePipes& messagePipes = MainCore::instance()->getMessagePipes();
QList<MessageQueue*> *mapMessageQueues = messagePipes.getMessageQueues(m_starTracker, "target");
if (mapMessageQueues)
{
QList<MessageQueue*>::iterator it = mapMessageQueues->begin();
for (; it != mapMessageQueues->end(); ++it)
{
SWGSDRangel::SWGTargetAzimuthElevation *swgTarget = new SWGSDRangel::SWGTargetAzimuthElevation();
swgTarget->setName(new QString(m_settings.m_target));
swgTarget->setAzimuth(aa.az);
swgTarget->setElevation(aa.alt);
(*it)->push(MainCore::MsgTargetAzimuthElevation::create(m_starTracker, swgTarget));
}
}
// Send to Map
if (m_settings.m_drawSunOnMap || m_settings.m_drawMoonOnMap || m_settings.m_drawStarOnMap)
{
mapMessageQueues = messagePipes.getMessageQueues(m_starTracker, "mapitems");
if (mapMessageQueues)
{
// Different between GMST(Lst at Greenwich) and RA
double lst = Astronomy::localSiderealTime(dt, 0.0);
double sunLongitude;
double sunLatitude;
if (m_settings.m_drawSunOnMap || m_settings.m_drawMoonOnMap)
{
sunLongitude = Astronomy::lstAndRAToLongitude(lst, sunRD.ra);
sunLatitude = sunRD.dec;
sendToMap(mapMessageQueues, "Sun", "qrc:///startracker/startracker/sun-40.png", "Sun", sunLatitude, sunLongitude);
}
if (m_settings.m_drawMoonOnMap)
{
double moonLongitude = Astronomy::lstAndRAToLongitude(lst, moonRD.ra);
double moonLatitude = moonRD.dec;
double moonRotation;
QString phase = moonPhase(sunLongitude, moonLongitude, m_settings.m_latitude, moonRotation);
sendToMap(mapMessageQueues, "Moon", QString("qrc:///startracker/startracker/moon-%1-32").arg(phase), "Moon",
moonLatitude, moonLongitude, moonRotation);
}
if ((m_settings.m_drawStarOnMap) && (m_settings.m_target != "Sun") && (m_settings.m_target != "Moon"))
{
double starLongitude = Astronomy::lstAndRAToLongitude(lst, rd.ra);
double starLatitude = rd.dec;
QString text = m_settings.m_target == "Custom" ? "Star" : m_settings.m_target;
sendToMap(mapMessageQueues, "Star", "qrc:///startracker/startracker/pulsar-32.png", text, starLatitude, starLongitude);
}
}
}
}

View File

@ -0,0 +1,109 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_STARTRACKERWORKER_H_
#define INCLUDE_FEATURE_STARTRACKERWORKER_H_
#include <QObject>
#include <QTimer>
#include <QAbstractSocket>
#include "util/message.h"
#include "util/messagequeue.h"
#include "util/astronomy.h"
#include "startrackersettings.h"
class WebAPIAdapterInterface;
class QTcpServer;
class QTcpSocket;
class StarTracker;
class QDateTime;
class StarTrackerWorker : public QObject
{
Q_OBJECT
public:
class MsgConfigureStarTrackerWorker : public Message {
MESSAGE_CLASS_DECLARATION
public:
const StarTrackerSettings& getSettings() const { return m_settings; }
bool getForce() const { return m_force; }
static MsgConfigureStarTrackerWorker* create(const StarTrackerSettings& settings, bool force)
{
return new MsgConfigureStarTrackerWorker(settings, force);
}
private:
StarTrackerSettings m_settings;
bool m_force;
MsgConfigureStarTrackerWorker(const StarTrackerSettings& settings, bool force) :
Message(),
m_settings(settings),
m_force(force)
{ }
};
StarTrackerWorker(StarTracker* starTracker, WebAPIAdapterInterface *webAPIAdapterInterface);
~StarTrackerWorker();
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:
StarTracker* m_starTracker;
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;
StarTrackerSettings m_settings;
bool m_running;
QMutex m_mutex;
QTimer m_pollTimer;
QTcpServer *m_tcpServer;
QTcpSocket *m_clientConnection;
bool handleMessage(const Message& cmd);
void applySettings(const StarTrackerSettings& settings, bool force = false);
void restartServer(bool enabled, uint32_t port);
MessageQueue *getMessageQueueToGUI() { return m_msgQueueToGUI; }
void setAzimuth(int azimuth);
void setAzimuthElevation(int azimuth, int elevation);
void updateRaDec(RADec rd, QDateTime dt);
void writeStellariumTarget(double ra, double dec);
void removeFromMap(QString id);
void sendToMap(QList<MessageQueue*> *mapMessageQueues, QString id, QString image, QString text, double lat, double lon, double rotation=0.0);
private slots:
void handleInputMessages();
void update();
void acceptConnection();
void disconnected();
void errorOccurred(QAbstractSocket::SocketError socketError);
void readStellariumCommand();
};
#endif // INCLUDE_FEATURE_STARTRACKERWORKER_H_

View File

@ -176,6 +176,8 @@ set(sdrbase_SOURCES
settings/mainsettings.cpp settings/mainsettings.cpp
util/ax25.cpp util/ax25.cpp
util/aprs.cpp
util/astronomy.cpp
util/azel.cpp util/azel.cpp
util/crc.cpp util/crc.cpp
util/CRC64.cpp util/CRC64.cpp
@ -366,6 +368,8 @@ set(sdrbase_HEADERS
settings/mainsettings.h settings/mainsettings.h
util/ax25.h util/ax25.h
util/aprs.h
util/astronomy.h
util/azel.h util/azel.h
util/CRC64.h util/CRC64.h
util/csv.h util/csv.h

896
sdrbase/util/astronomy.cpp Normal file
View File

@ -0,0 +1,896 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2021 Jon Beniston, M7RCE //
// Copyright (C) 2004 Rutherford Appleton Laboratory //
// Copyright (C) 2012 Science and Technology Facilities Council. //
// //
// 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 <QRegExp>
#include <QDateTime>
#include <QDebug>
#include "util/units.h"
#include "astronomy.h"
// Function prototypes
static void palRefz(double zu, double refa, double refb, double *zr);
static void palRefco (double hm, double tdk, double pmb, double rh,
double wl, double phi, double tlr, double eps,
double *refa, double *refb);
static void palRefro( double zobs, double hm, double tdk, double pmb,
double rh, double wl, double phi, double tlr,
double eps, double * ref);
// Calculate Julian date (days since January 1, 4713 BC) from Gregorian calendar date
double Astronomy::julianDate(int year, int month, int day, int hours, int minutes, int seconds)
{
int julian_day;
double julian_date;
// From: https://en.wikipedia.org/wiki/Julian_day
julian_day = (1461 * (year + 4800 + (month - 14)/12))/4 +(367 * (month - 2 - 12 * ((month - 14)/12)))/12 - (3 * ((year + 4900 + (month - 14)/12)/100))/4 + day - 32075;
julian_date = julian_day + (hours/24.0 - 0.5) + minutes/(24.0*60.0) + seconds/(24.0*60.0*60.0);
return julian_date;
}
// Calculate Julian date
double Astronomy::julianDate(QDateTime dt)
{
QDateTime utc = dt.toUTC();
QDate date = utc.date();
QTime time = utc.time();
return julianDate(date.year(), date.month(), date.day(), time.hour(), time.minute(), time.second());
}
// Get Julian date of J2000 Epoch
double Astronomy::jd_j2000(void)
{
static double j2000 = 0.0;
if (j2000 == 0.0) {
j2000 = julianDate(2000, 1, 1, 12, 0, 0);
}
return j2000;
}
// Get Julian date of B1950 Epoch
double Astronomy::jd_b1950(void)
{
static double b1950 = 0.0;
if (b1950 == 0.0) {
b1950 = julianDate(1949, 12, 31, 22, 9, 0);
}
return b1950;
}
// Get Julian date of current time Epoch
double Astronomy::jd_now(void)
{
time_t system_time;
struct tm *utc_time;
// Get current time in seconds since Unix Epoch (1970)
time(&system_time);
// Convert to UTC (GMT)
utc_time = gmtime(&system_time);
// Convert to Julian date
return julianDate(utc_time->tm_year + 1900, utc_time->tm_mon + 1, utc_time->tm_mday,
utc_time->tm_hour, utc_time->tm_min, utc_time->tm_sec);
}
// Precess a RA/DEC between two given Epochs
RADec Astronomy::precess(RADec rd_in, double jd_from, double jd_to)
{
RADec rd_out;
double x, y, z;
double xp, yp, zp;
double ra_rad, dec_rad;
double rot[3][3]; // [row][col]
double ra_deg;
double days_per_century = 36524.219878;
double t0 = (jd_from - jd_b1950())/days_per_century; // Tropical centuries since B1950.0
double t = (jd_to - jd_from)/days_per_century; // Tropical centuries from starting epoch to ending epoch
// From: https://www.cloudynights.com/topic/561254-ra-dec-epoch-conversion/
rot[0][0] = 1.0 - ((29696.0 + 26.0*t0)*t*t - 13.0*t*t*t)*.00000001;
rot[1][0] = ((2234941.0 + 1355.0*t0)*t - 676.0*t*t + 221.0*t*t*t)*.00000001;
rot[2][0] = ((971690.0 - 414.0*t0)*t + 207.0*t*t + 96.0*t*t*t)*.00000001;
rot[0][1] = -rot[1][0];
rot[1][1] = 1.0 - ((24975.0 + 30.0*t0)*t*t - 15.0*t*t*t)*.00000001;
rot[2][1] = -((10858.0 + 2.0*t0)*t*t)*.00000001;
rot[0][2] = -rot[2][0];
rot[1][2] = rot[2][1];
rot[2][2] = 1.0 - ((4721.0 - 4.0*t0)*t*t)*.00000001;
// Hours to degrees
ra_deg = rd_in.ra*(360.0/24.0);
// Convert to rectangular coordinates
ra_rad = Units::degreesToRadians(ra_deg);
dec_rad = Units::degreesToRadians(rd_in.dec);
x = cos(ra_rad) * cos(dec_rad);
y = sin(ra_rad) * cos(dec_rad);
z = sin(dec_rad);
// Rotate
xp = rot[0][0]*x + rot[0][1]*y + rot[0][2]*z;
yp = rot[1][0]*x + rot[1][1]*y + rot[1][2]*z;
zp = rot[2][0]*x + rot[2][1]*y + rot[2][2]*z;
// Convert back to spherical coordinates
rd_out.ra = Units::radiansToDegrees(atan(yp/xp));
if (xp < 0.0) {
rd_out.ra += 180.0;
} else if ((yp < 0) && (xp > 0)) {
rd_out.ra += 360.0;
}
rd_out.dec = Units::radiansToDegrees(asin(zp));
// Degrees to hours
rd_out.ra /= (360.0/24.0);
return rd_out;
}
// Calculate local mean sidereal time (LMST) in degrees
double Astronomy::localSiderealTime(QDateTime dateTime, double longitude)
{
double jd = julianDate(dateTime);
double d = (jd - jd_j2000()); // Days since J2000 epoch (including fraction)
double f = fmod(jd, 1.0); // Fractional part is decimal days
double ut = (f+0.5)*24.0; // Universal time in decimal hours
// https://astronomy.stackexchange.com/questions/24859/local-sidereal-time
// 100.46 is offset for GMST at 0h UT on 1 Jan 2000
// 0.985647 is number of degrees per day over 360 the Earth rotates in one solar day
// Approx to 0.3 arcseconds
return fmod(100.46 + 0.985647 * d + longitude + (360/24) * ut, 360.0);
}
// Convert from J2000 right ascension (decimal hours) and declination (decimal degrees) to altitude and azimuth, for location (decimal degrees) and time
AzAlt Astronomy::raDecToAzAlt(RADec rd, double latitude, double longitude, QDateTime dt, bool j2000)
{
AzAlt aa;
double ra_deg; // Right ascension in degrees
double lst_deg; // Local sidereal time in degrees
double ha; // Hour angle
double a, az;
double dec_rad, lat_rad, ha_rad, alt_rad; // Corresponding variables as radians
double jd = julianDate(dt);
// Precess RA/DEC from J2000 Epoch to desired (typically current) Epoch
if (j2000)
rd = precess(rd, jd_j2000(), jd);
// Calculate local mean sidereal time (LMST) in degrees
// https://astronomy.stackexchange.com/questions/24859/local-sidereal-time
// 100.46 is offset for GMST at 0h UT on 1 Jan 2000
// 0.985647 is number of degrees per day over 360 the Earth rotates in one solar day
// Approx to 0.3 arcseconds
lst_deg = Astronomy::localSiderealTime(dt, longitude);
// Convert right ascension from hours to degrees
ra_deg = rd.ra * (360.0/24.0);
// Calculate hour angle
ha = fmod(lst_deg - ra_deg, 360.0);
// Convert degrees to radians
dec_rad = Units::degreesToRadians(rd.dec);
lat_rad = Units::degreesToRadians(latitude);
ha_rad = Units::degreesToRadians(ha);
// Calculate altitude and azimuth - no correction for atmospheric refraction
// From: http://www.convertalot.com/celestial_horizon_co-ordinates_calculator.html
alt_rad = asin(sin(dec_rad)*sin(lat_rad) + cos(dec_rad)*cos(lat_rad)*cos(ha_rad));
a = Units::radiansToDegrees(acos((sin(dec_rad)-sin(alt_rad)*sin(lat_rad)) / (cos(alt_rad)*cos(lat_rad))));
if (sin(ha_rad) < 0.0) {
az = a;
} else {
az = 360.0 - a;
}
aa.alt = Units::radiansToDegrees(alt_rad);
aa.az = az;
return aa;
}
// Needs to work for negative a
double Astronomy::modulo(double a, double b)
{
return a - b * floor(a/b);
}
// Calculate azimuth and altitude angles to the sun from the given latitude and longitude at the given time
// Refer to:
// https://en.wikipedia.org/wiki/Position_of_the_Sun
// https://www.aa.quae.nl/en/reken/zonpositie.html
// Said to be accurate to .01 degree (36") for dates between 1950 and 2050
// although we use slightly more accurate constants and an extra term in the equation
// of centre from the second link
// For an alternative, see: http://www.psa.es/sdg/sunpos.htm
// Most accurate algorithm is supposedly: https://midcdmz.nrel.gov/spa/
void Astronomy::sunPosition(AzAlt& aa, RADec& rd, double latitude, double longitude, QDateTime dt)
{
double jd = julianDate(dt);
double n = (jd - jd_j2000()); // Days since J2000 epoch (including fraction)
double l = 280.461 + 0.9856474 * n; // Mean longitude of the Sun, corrected for the abberation of light
double g = 357.5291 + 0.98560028 * n; // Mean anomaly of the Sun - how far around orbit from perihlion, in degrees
l = modulo(l, 360.0);
g = modulo(g, 360.0);
double gr = Units::degreesToRadians(g);
double la = l + 1.9148 * sin(gr) + 0.0200 * sin(2.0*gr) + 0.0003 * sin(3.0*gr); // Ecliptic longitude (Ecliptic latitude b set to 0)
// Convert la, b=0, which give the position of the Sun in the ecliptic coordinate sytem, to
// equatorial coordinates
double e = 23.4393 - 3.563E-7 * n; // Obliquity of the ecliptic - tilt of Earth's axis of rotation
double er = Units::degreesToRadians(e);
double lr = Units::degreesToRadians(la);
double a = atan2(cos(er) * sin(lr), cos(lr)); // Right ascension, radians
double d = asin(sin(er) * sin(lr)); // Declination, radians
rd.ra = modulo(Units::radiansToDegrees(a), 360.0) / (360.0/24.0); // Convert to hours
rd.dec = Units::radiansToDegrees(d); // Convert to degrees
aa = raDecToAzAlt(rd, latitude, longitude, dt, false);
}
double Astronomy::moonDays(QDateTime dt)
{
QDateTime utc = dt.toUTC();
QDate date = utc.date();
QTime time = utc.time();
int y = date.year();
int m = date.month();
int D = date.day();
int d = 367 * y - 7 * (y + (m+9)/12) / 4 - 3 * ((y + (m-9)/7) / 100 + 1) / 4 + 275*m/9 + D - 730515;
return d + time.hour()/24.0 + time.minute()/(24.0*60.0) + time.second()/(24.0*60.0*60.0);
}
// Calculate azimuth and altitude angles to the moon from the given latitude and longitude at the given time
// Refer to: https://stjarnhimlen.se/comp/ppcomp.html
// Accurate to 4 arcminute
void Astronomy::moonPosition(AzAlt& aa, RADec& rd, double latitude, double longitude, QDateTime dt)
{
double d = moonDays(dt);
double ecl = Units::degreesToRadians(23.4393 - 3.563E-7 * d); // Obliquity of the ecliptic - tilt of Earth's axis of rotation
// Orbital elements for the Sun
double Ns = 0.0;
double is = 0.0;
double ws = Units::degreesToRadians(282.9404 + 4.70935E-5 * d);
double as = 1.0; // (AU)
double es = 0.016709 - 1.151E-9 * d; // ecs
double Ms = Units::degreesToRadians(356.0470 + 0.9856002585 * d);
// Orbital elements for the Moon
double Nm = Units::degreesToRadians(125.1228 - 0.0529538083 * d); // longitude of the ascending node
double im = Units::degreesToRadians(5.1454); // inclination to the ecliptic (plane of the Earth's orbit)
double wm = Units::degreesToRadians(318.0634 + 0.1643573223 * d); // argument of perihelion
double am = 60.2666; // (Earth radii) semi-major axis, or mean distance from Sun
double em = 0.054900; // ecm // eccentricity (0=circle, 0-1=ellipse, 1=parabola)
double Mm = Units::degreesToRadians(115.3654 + 13.0649929509 * d); // mean anomaly (0 at perihelion; increases uniformly with time), degrees
double Em = Mm + em * sin(Mm) * (1.0 + em * cos(Mm)); // Eccentric anomaly in radians
double xv = am * (cos(Em) - em);
double yv = am * (sqrt(1.0 - em*em) * sin(Em));
double vm = atan2(yv, xv); // True anomaly
double rm = sqrt(xv*xv + yv*yv); // Distance
// Compute position in space (for the Moon, this is geocentric)
double xh = rm * (cos(Nm) * cos(vm+wm) - sin(Nm) * sin(vm+wm) * cos(im));
double yh = rm * (sin(Nm) * cos(vm+wm) + cos(Nm) * sin(vm+wm) * cos(im));
double zh = rm * (sin(vm+wm) * sin(im));
// Convert to ecliptic longitude and latitude
double lonecl = atan2(yh, xh);
double latecl = atan2(zh, sqrt(xh*xh+yh*yh));
// Perturbations of the Moon
double Ls = Ms + ws; // Mean Longitude of the Sun (Ns=0)
double Lm = Mm + wm + Nm; // Mean longitude of the Moon
double D = Lm - Ls; // Mean elongation of the Moon
double F = Lm - Nm; // Argument of latitude for the Moon
double dlon;
dlon = -1.274 * sin(Mm - 2*D); // (the Evection)
dlon += +0.658 * sin(2*D); // (the Variation)
dlon += -0.186 * sin(Ms); // (the Yearly Equation)
dlon += -0.059 * sin(2*Mm - 2*D);
dlon += -0.057 * sin(Mm - 2*D + Ms);
dlon += +0.053 * sin(Mm + 2*D);
dlon += +0.046 * sin(2*D - Ms);
dlon += +0.041 * sin(Mm - Ms);
dlon += -0.035 * sin(D); // (the Parallactic Equation)
dlon += -0.031 * sin(Mm + Ms);
dlon += -0.015 * sin(2*F - 2*D);
dlon += +0.011 * sin(Mm - 4*D);
double dlat;
dlat = -0.173 * sin(F - 2*D);
dlat += -0.055 * sin(Mm - F - 2*D);
dlat += -0.046 * sin(Mm + F - 2*D);
dlat += +0.033 * sin(F + 2*D);
dlat += +0.017 * sin(2*Mm + F);
lonecl += Units::degreesToRadians(dlon);
latecl += Units::degreesToRadians(dlat);
rm += -0.58 * cos(Mm - 2*D);
rm += -0.46 * cos(2*D);
// Convert perturbed
xh = rm * cos(lonecl) * cos(latecl);
yh = rm * sin(lonecl) * cos(latecl);
zh = rm * sin(latecl);
// Convert to geocentric coordinates (already the case for the Moon)
double xg = xh;
double yg = yh;
double zg = zh;
// Convert to equatorial cordinates
double xe = xg;
double ye = yg * cos(ecl) - zg * sin(ecl);
double ze = yg * sin(ecl) + zg * cos(ecl);
// Compute right ascension and declination
double ra = atan2(ye, xe);
double dec = atan2(ze, sqrt(xe*xe+ye*ye));
rd.ra = modulo(Units::radiansToDegrees(ra), 360.0) / (360.0/24.0); // Convert to hours
rd.dec = Units::radiansToDegrees(dec); // Convert to degrees
// Convert from geocentric to topocentric
double mpar = asin(1/rm);
double lat = Units::degreesToRadians(latitude);
double gclat = Units::degreesToRadians(latitude - 0.1924 * sin(2.0 * lat));
double rho = 0.99833 + 0.00167 * cos(2*lat);
QTime time = dt.toUTC().time();
double UT = time.hour() + time.minute()/60.0 + time.second()/(60.0*60.0);
double GMST0 = Units::radiansToDegrees(Ls)/15.0 + 12; // In hours
double GMST = GMST0 + UT;
double LST = GMST + longitude/15.0;
double HA = Units::degreesToRadians(LST*15.0 - Units::radiansToDegrees(ra)); // Hour angle in radians
double g = atan(tan(gclat) / cos(HA));
double topRA = ra - mpar * rho * cos(gclat) * sin(HA) / cos(dec);
double topDec;
if (g != 0.0)
topDec = dec - mpar * rho * sin(gclat) * sin(g - dec) / sin(g);
else
topDec = dec - mpar * rho * sin(-dec) * cos(HA);
rd.ra = modulo(Units::radiansToDegrees(topRA), 360.0) / (360.0/24.0); // Convert to hours
rd.dec = Units::radiansToDegrees(topDec); // Convert to degrees
aa = raDecToAzAlt(rd, latitude, longitude, dt, false);
}
// Calculated adjustment to altitude angle from true to apparent due to atmospheric refraction using
// Saemundsson's formula (which is used by Stellarium and is primarily just for
// optical wavelengths)
// See: https://en.wikipedia.org/wiki/Atmospheric_refraction#Calculating_refraction
// Alt is in degrees. 90 = Zenith gives a factor of 0.
// Pressure in millibars
// Temperature in Celsuis
// We divide by 60.0 to get a value in degrees (as original formula is in arcminutes)
double Astronomy::refractionSaemundsson(double alt, double pressure, double temperature)
{
double pt = (pressure/1010.0) * (283.0/(273.0+temperature));
return (1.02 / tan(Units::degreesToRadians(alt+10.3/(alt+5.11))) + 0.0019279) / 60.0;
}
// Calculated adjustment to altitude angle from true to apparent due to atmospheric refraction using
// code from Starlink Positional Astronomy Library. This is more accurate for
// radio than Saemundsson's formula, but also more complex.
// See: https://github.com/Starlink/pal
// Alt is in degrees. 90 = Zenith gives a factor of 0.
// Pressure in millibars
// Temperature in Celsuis
// Humdity in %
// Frequency in Hertz
// Latitude in decimal degrees
// HeightAboveSeaLevel in metres
// Temperature lapse rate in K/km
double Astronomy::refractionPAL(double alt, double pressure, double temperature, double humidity, double frequency,
double latitude, double heightAboveSeaLevel, double temperatureLapseRate)
{
double tdk = Units::celsiusToKelvin(temperature); // Ambient temperature at the observer (K)
double wl = (299792458.0/frequency)*1000000.0; // Wavelength in micrometres
double rh = humidity/100.0; // Relative humidity in range 0-1
double phi = Units::degreesToRadians(latitude); // Latitude of the observer (radian, astronomical)
double tlr = temperatureLapseRate/1000.0; // Temperature lapse rate in the troposphere (K/metre)
double refa, refb;
double z = 90.0-alt;
double zu = Units::degreesToRadians(z);
double zr;
palRefco(heightAboveSeaLevel, tdk, pressure, rh,
wl, phi, tlr, 1E-10,
&refa, &refb);
palRefz(zu, refa, refb, &zr);
return z-Units::radiansToDegrees(zr);
}
double Astronomy::raToDecimal(const QString& value)
{
QRegExp decimal("^([0-9]+(\\.[0-9]+)?)");
QRegExp hms("^([0-9]+)[ h]([0-9]+)[ m]([0-9]+(\\.[0-9]+)?)s?");
if (decimal.exactMatch(value))
return decimal.capturedTexts()[0].toDouble();
else if (hms.exactMatch(value))
{
return Units::hoursMinutesSecondsToDecimal(
hms.capturedTexts()[1].toDouble(),
hms.capturedTexts()[2].toDouble(),
hms.capturedTexts()[3].toDouble());
}
return 0.0;
}
double Astronomy::decToDecimal(const QString& value)
{
QRegExp decimal("^(-?[0-9]+(\\.[0-9]+)?)");
QRegExp dms(QString("^(-?[0-9]+)[ %1d]([0-9]+)[ 'm]([0-9]+(\\.[0-9]+)?)[\"s]?").arg(QChar(0xb0)));
if (decimal.exactMatch(value))
return decimal.capturedTexts()[0].toDouble();
else if (dms.exactMatch(value))
{
return Units::degreesMinutesSecondsToDecimal(
dms.capturedTexts()[1].toDouble(),
dms.capturedTexts()[2].toDouble(),
dms.capturedTexts()[3].toDouble());
}
return 0.0;
}
double Astronomy::lstAndRAToLongitude(double lst, double raHours)
{
double longitude = lst - (raHours * 15.0); // Convert hours to degrees
if (longitude < -180.0)
longitude += 360.0;
else if (longitude > 180.0)
longitude -= 360.0;
return -longitude; // East positive
}
// The following functions are from Starlink Positional Astronomy Library
// https://github.com/Starlink/pal
/* Pi */
static const double PAL__DPI = 3.1415926535897932384626433832795028841971693993751;
/* 2Pi */
static const double PAL__D2PI = 6.2831853071795864769252867665590057683943387987502;
/* pi/180: degrees to radians */
static const double PAL__DD2R = 0.017453292519943295769236907684886127134428718885417;
/* Radians to degrees */
static const double PAL__DR2D = 57.295779513082320876798154814105170332405472466564;
/* DMAX(A,B) - return maximum value - evaluates arguments multiple times */
#define DMAX(A,B) ((A) > (B) ? (A) : (B) )
/* DMIN(A,B) - return minimum value - evaluates arguments multiple times */
#define DMIN(A,B) ((A) < (B) ? (A) : (B) )
// Normalize angle into range +/- pi
double palDrange( double angle ) {
double result = fmod( angle, PAL__D2PI );
if( result > PAL__DPI ) {
result -= PAL__D2PI;
} else if( result < -PAL__DPI ) {
result += PAL__D2PI;
}
return result;
}
// Calculate stratosphere parameters
static void pal1Atms ( double rt, double tt, double dnt, double gamal,
double r, double * dn, double * rdndr ) {
double b;
double w;
b = gamal / tt;
w = (dnt - 1.0) * exp( -b * (r-rt) );
*dn = 1.0 + w;
*rdndr = -r * b * w;
}
// Calculate troposphere parameters
static void pal1Atmt ( double r0, double t0, double alpha, double gamm2,
double delm2, double c1, double c2, double c3, double c4,
double c5, double c6, double r,
double *t, double *dn, double *rdndr ) {
double tt0;
double tt0gm2;
double tt0dm2;
*t = DMAX( DMIN( t0 - alpha*(r-r0), 320.0), 100.0 );
tt0 = *t / t0;
tt0gm2 = pow( tt0, gamm2 );
tt0dm2 = pow( tt0, delm2 );
*dn = 1.0 + ( c1 * tt0gm2 - ( c2 - c5 / *t ) * tt0dm2 ) * tt0;
*rdndr = r * ( -c3 * tt0gm2 + ( c4 - c6 / tt0 ) * tt0dm2 );
}
// Adjust unrefracted zenith distance
static void palRefz ( double zu, double refa, double refb, double *zr ) {
/* Constants */
/* Largest usable ZD (deg) */
const double D93 = 93.0;
/* ZD at which one model hands over to the other (radians) */
const double Z83 = 83.0 * PAL__DD2R;
/* coefficients for high ZD model (used beyond ZD 83 deg) */
const double C1 = +0.55445;
const double C2 = -0.01133;
const double C3 = +0.00202;
const double C4 = +0.28385;
const double C5 = +0.02390;
/* High-ZD-model prefiction (deg) for that point */
const double REF83 = (C1+C2*7.0+C3*49.0)/(1.0+C4*7.0+C5*49.0);
double zu1,zl,s,c,t,tsq,tcu,ref,e,e2;
/* perform calculations for zu or 83 deg, whichever is smaller */
zu1 = DMIN(zu,Z83);
/* functions of ZD */
zl = zu1;
s = sin(zl);
c = cos(zl);
t = s/c;
tsq = t*t;
tcu = t*tsq;
/* refracted zd (mathematically to better than 1 mas at 70 deg) */
zl = zl-(refa*t+refb*tcu)/(1.0+(refa+3.0*refb*tsq)/(c*c));
/* further iteration */
s = sin(zl);
c = cos(zl);
t = s/c;
tsq = t*t;
tcu = t*tsq;
ref = zu1-zl+
(zl-zu1+refa*t+refb*tcu)/(1.0+(refa+3.0*refb*tsq)/(c*c));
/* special handling for large zu */
if (zu > zu1) {
e = 90.0-DMIN(D93,zu*PAL__DR2D);
e2 = e*e;
ref = (ref/REF83)*(C1+C2*e+C3*e2)/(1.0+C4*e+C5*e2);
}
/* return refracted zd */
*zr = zu-ref;
}
// Determine constants in atmospheric refraction model
static void palRefco ( double hm, double tdk, double pmb, double rh,
double wl, double phi, double tlr, double eps,
double *refa, double *refb ) {
double r1, r2;
/* Sample zenith distances: arctan(1) and arctan(4) */
const double ATN1 = 0.7853981633974483;
const double ATN4 = 1.325817663668033;
/* Determine refraction for the two sample zenith distances */
palRefro(ATN1,hm,tdk,pmb,rh,wl,phi,tlr,eps,&r1);
palRefro(ATN4,hm,tdk,pmb,rh,wl,phi,tlr,eps,&r2);
/* Solve for refraction constants */
*refa = (64.0*r1-r2)/60.0;
*refb = (r2-4.0*r1)/60.0;
}
// Atmospheric refraction for radio and optical/IR wavelengths
static void palRefro( double zobs, double hm, double tdk, double pmb,
double rh, double wl, double phi, double tlr,
double eps, double * ref ) {
/*
* Fixed parameters
*/
/* 93 degrees in radians */
const double D93 = 1.623156204;
/* Universal gas constant */
const double GCR = 8314.32;
/* Molecular weight of dry air */
const double DMD = 28.9644;
/* Molecular weight of water vapour */
const double DMW = 18.0152;
/* Mean Earth radius (metre) */
const double S = 6378120.;
/* Exponent of temperature dependence of water vapour pressure */
const double DELTA = 18.36;
/* Height of tropopause (metre) */
const double HT = 11000.;
/* Upper limit for refractive effects (metre) */
const double HS = 80000.;
/* Numerical integration: maximum number of strips. */
const int ISMAX=16384l;
/* Local variables */
int is, k, n, i, j;
int optic, loop; /* booleans */
double zobs1,zobs2,hmok,tdkok,pmbok,rhok,wlok,alpha,
tol,wlsq,gb,a,gamal,gamma,gamm2,delm2,
tdc,psat,pwo,w,
c1,c2,c3,c4,c5,c6,r0,tempo,dn0,rdndr0,sk0,f0,
rt,tt,dnt,rdndrt,sine,zt,ft,dnts,rdndrp,zts,fts,
rs,dns,rdndrs,zs,fs,refold,z0,zrange,fb,ff,fo,fe,
h,r,sz,rg,dr,tg,dn,rdndr,t,f,refp,reft;
/* The refraction integrand */
#define refi(DN,RDNDR) RDNDR/(DN+RDNDR)
/* Transform ZOBS into the normal range. */
zobs1 = palDrange(zobs);
zobs2 = DMIN(fabs(zobs1),D93);
/* keep other arguments within safe bounds. */
hmok = DMIN(DMAX(hm,-1e3),HS);
tdkok = DMIN(DMAX(tdk,100.0),500.0);
pmbok = DMIN(DMAX(pmb,0.0),10000.0);
rhok = DMIN(DMAX(rh,0.0),1.0);
wlok = DMAX(wl,0.1);
alpha = DMIN(DMAX(fabs(tlr),0.001),0.01);
/* tolerance for iteration. */
tol = DMIN(DMAX(fabs(eps),1e-12),0.1)/2.0;
/* decide whether optical/ir or radio case - switch at 100 microns. */
optic = wlok < 100.0;
/* set up model atmosphere parameters defined at the observer. */
wlsq = wlok*wlok;
gb = 9.784*(1.0-0.0026*cos(phi+phi)-0.00000028*hmok);
if (optic) {
a = (287.6155+(1.62887+0.01360/wlsq)/wlsq) * 273.15e-6/1013.25;
} else {
a = 77.6890e-6;
}
gamal = (gb*DMD)/GCR;
gamma = gamal/alpha;
gamm2 = gamma-2.0;
delm2 = DELTA-2.0;
tdc = tdkok-273.15;
psat = pow(10.0,(0.7859+0.03477*tdc)/(1.0+0.00412*tdc)) *
(1.0+pmbok*(4.5e-6+6.0e-10*tdc*tdc));
if (pmbok > 0.0) {
pwo = rhok*psat/(1.0-(1.0-rhok)*psat/pmbok);
} else {
pwo = 0.0;
}
w = pwo*(1.0-DMW/DMD)*gamma/(DELTA-gamma);
c1 = a*(pmbok+w)/tdkok;
if (optic) {
c2 = (a*w+11.2684e-6*pwo)/tdkok;
} else {
c2 = (a*w+6.3938e-6*pwo)/tdkok;
}
c3 = (gamma-1.0)*alpha*c1/tdkok;
c4 = (DELTA-1.0)*alpha*c2/tdkok;
if (optic) {
c5 = 0.0;
c6 = 0.0;
} else {
c5 = 375463e-6*pwo/tdkok;
c6 = c5*delm2*alpha/(tdkok*tdkok);
}
/* conditions at the observer. */
r0 = S+hmok;
pal1Atmt(r0,tdkok,alpha,gamm2,delm2,c1,c2,c3,c4,c5,c6,
r0,&tempo,&dn0,&rdndr0);
sk0 = dn0*r0*sin(zobs2);
f0 = refi(dn0,rdndr0);
/* conditions in the troposphere at the tropopause. */
rt = S+DMAX(HT,hmok);
pal1Atmt(r0,tdkok,alpha,gamm2,delm2,c1,c2,c3,c4,c5,c6,
rt,&tt,&dnt,&rdndrt);
sine = sk0/(rt*dnt);
zt = atan2(sine,sqrt(DMAX(1.0-sine*sine,0.0)));
ft = refi(dnt,rdndrt);
/* conditions in the stratosphere at the tropopause. */
pal1Atms(rt,tt,dnt,gamal,rt,&dnts,&rdndrp);
sine = sk0/(rt*dnts);
zts = atan2(sine,sqrt(DMAX(1.0-sine*sine,0.0)));
fts = refi(dnts,rdndrp);
/* conditions at the stratosphere limit. */
rs = S+HS;
pal1Atms(rt,tt,dnt,gamal,rs,&dns,&rdndrs);
sine = sk0/(rs*dns);
zs = atan2(sine,sqrt(DMAX(1.0-sine*sine,0.0)));
fs = refi(dns,rdndrs);
/* variable initialization to avoid compiler warning. */
reft = 0.0;
/* integrate the refraction integral in two parts; first in the
* troposphere (k=1), then in the stratosphere (k=2). */
for (k=1; k<=2; k++) {
/* initialize previous refraction to ensure at least two iterations. */
refold = 1.0;
/* start off with 8 strips. */
is = 8;
/* start z, z range, and start and end values. */
if (k==1) {
z0 = zobs2;
zrange = zt-z0;
fb = f0;
ff = ft;
} else {
z0 = zts;
zrange = zs-z0;
fb = fts;
ff = fs;
}
/* sums of odd and even values. */
fo = 0.0;
fe = 0.0;
/* first time through the loop we have to do every point. */
n = 1;
/* start of iteration loop (terminates at specified precision). */
loop = 1;
while (loop) {
/* strip width. */
h = zrange/((double)is);
/* initialize distance from earth centre for quadrature pass. */
if (k == 1) {
r = r0;
} else {
r = rt;
}
/* one pass (no need to compute evens after first time). */
for (i=1; i<is; i+=n) {
/* sine of observed zenith distance. */
sz = sin(z0+h*(double)(i));
/* find r (to the nearest metre, maximum four iterations). */
if (sz > 1e-20) {
w = sk0/sz;
rg = r;
dr = 1.0e6;
j = 0;
while ( fabs(dr) > 1.0 && j < 4 ) {
j++;
if (k==1) {
pal1Atmt(r0,tdkok,alpha,gamm2,delm2,
c1,c2,c3,c4,c5,c6,rg,&tg,&dn,&rdndr);
} else {
pal1Atms(rt,tt,dnt,gamal,rg,&dn,&rdndr);
}
dr = (rg*dn-w)/(dn+rdndr);
rg = rg-dr;
}
r = rg;
}
/* find the refractive index and integrand at r. */
if (k==1) {
pal1Atmt(r0,tdkok,alpha,gamm2,delm2,
c1,c2,c3,c4,c5,c6,r,&t,&dn,&rdndr);
} else {
pal1Atms(rt,tt,dnt,gamal,r,&dn,&rdndr);
}
f = refi(dn,rdndr);
/* accumulate odd and (first time only) even values. */
if (n==1 && i%2 == 0) {
fe += f;
} else {
fo += f;
}
}
/* evaluate the integrand using simpson's rule. */
refp = h*(fb+4.0*fo+2.0*fe+ff)/3.0;
/* has the required precision been achieved (or can't be)? */
if (fabs(refp-refold) > tol && is < ISMAX) {
/* no: prepare for next iteration.*/
/* save current value for convergence test. */
refold = refp;
/* double the number of strips. */
is += is;
/* sum of all current values = sum of next pass's even values. */
fe += fo;
/* prepare for new odd values. */
fo = 0.0;
/* skip even values next time. */
n = 2;
} else {
/* yes: save troposphere component and terminate the loop. */
if (k==1) reft = refp;
loop = 0;
}
}
}
/* result. */
*ref = reft+refp;
if (zobs1 < 0.0) *ref = -(*ref);
}

68
sdrbase/util/astronomy.h Normal file
View File

@ -0,0 +1,68 @@
///////////////////////////////////////////////////////////////////////////////////
// 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_ASTRONOMY_H
#define INCLUDE_ASTRONOMY_H
#include "export.h"
class QDateTime;
// Right ascension and declination
struct SDRBASE_API RADec {
double ra;
double dec;
};
// Azimuth and Altitude
struct SDRBASE_API AzAlt {
double az;
double alt;
};
class SDRBASE_API Astronomy {
public:
static double julianDate(int year, int month, int day, int hours, int minutes, int seconds);
static double julianDate(QDateTime dt);
static double jd_j2000(void);
static double jd_b1950(void);
static double jd_now(void);
static RADec precess(RADec rd_in, double jd_from, double jd_to);
static AzAlt raDecToAzAlt(RADec rd, double latitude, double longitude, QDateTime dt, bool j2000=true);
static double localSiderealTime(QDateTime dateTime, double longitude);
static void sunPosition(AzAlt& aa, RADec& rd, double latitude, double longitude, QDateTime dt);
static double moonDays(QDateTime dt);
static void moonPosition(AzAlt& aa, RADec& rd, double latitude, double longitude, QDateTime dt);
static double refractionSaemundsson(double alt, double pressure, double temperature);
static double refractionPAL(double alt, double pressure, double temperature, double humidity, double frequency, double latitude, double heightAboveSeaLevel, double temperatureLapseRate);
static double raToDecimal(const QString& value);
static double decToDecimal(const QString& value);
static double lstAndRAToLongitude(double lst, double raHours);
protected:
static double modulo(double a, double b);
};
#endif // INCLUDE_ASTRONOMY_H