Merge pull request #746 from srcejon/master
Add PacketDemod, APRS, Map and Star Tracker plugins
@ -81,6 +81,7 @@ for:
|
||||
qml-module-qtlocation qml-module-qtpositioning qml-module-qtquick-window2 qml-module-qtquick-dialogs \
|
||||
qml-module-qtquick-controls qml-module-qtquick-layouts \
|
||||
libqt5serialport5-dev qtdeclarative5-dev qtpositioning5-dev qtlocation5-dev \
|
||||
libqt5charts5-dev \
|
||||
libusb-1.0-0-dev libboost-all-dev libasound2-dev libopencv-dev libopencv-imgcodecs-dev \
|
||||
libxml2-dev bison flex ffmpeg libpostproc-dev libavcodec-dev libavformat-dev \
|
||||
libopus-dev libcodec2-dev libairspy-dev libhackrf-dev \
|
||||
|
@ -308,6 +308,7 @@ if (BUILD_GUI)
|
||||
find_package(Qt5 COMPONENTS Quick)
|
||||
find_package(Qt5 COMPONENTS QuickWidgets)
|
||||
find_package(Qt5 COMPONENTS Positioning)
|
||||
find_package(Qt5 COMPONENTS Charts)
|
||||
endif()
|
||||
|
||||
# other requirements
|
||||
|
3
debian/control
vendored
@ -21,6 +21,7 @@ Build-Depends: debhelper (>= 9),
|
||||
qml-module-qtquick-controls,
|
||||
qml-module-qtquick-layouts,
|
||||
libqt5serialport5-dev,
|
||||
libqt5charts5-dev,
|
||||
qtdeclarative5-dev,
|
||||
qtpositioning5-dev,
|
||||
qtlocation5-dev,
|
||||
@ -55,7 +56,7 @@ Description: SDR/Analyzer/Generator front-end for various hardware
|
||||
Builds on Linux, Windows and Mac O/S
|
||||
Reception modes supported:
|
||||
Analog: AM, ATV, NFM, WFM, SSB, broadcast FM
|
||||
Digital: D-Star, Yaesu SF, DMR, dPMR, LoRa, ADS-B
|
||||
Digital: D-Star, Yaesu SF, DMR, dPMR, LoRa, ADS-B, Packet (AX.25/APRS)
|
||||
Analyzer: Generic channel
|
||||
Transmission modes supported:
|
||||
Analog: AM, ATV, NFM, SSB, WFM
|
||||
|
BIN
doc/img/APRS_map.png
Normal file
After Width: | Height: | Size: 318 KiB |
BIN
doc/img/APRS_plugin.png
Normal file
After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
BIN
doc/img/Map_plugin.png
Normal file
After Width: | Height: | Size: 1.4 MiB |
BIN
doc/img/PacketDemod_plugin.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
doc/img/StarTracker_map.png
Normal file
After Width: | Height: | Size: 176 KiB |
BIN
doc/img/StarTracker_plugin.png
Normal file
After Width: | Height: | Size: 24 KiB |
@ -16,6 +16,7 @@ add_subdirectory(filesink)
|
||||
add_subdirectory(freqtracker)
|
||||
add_subdirectory(demodchirpchat)
|
||||
add_subdirectory(demodvorsc)
|
||||
add_subdirectory(demodpacket)
|
||||
|
||||
if(LIBDSDCC_FOUND AND LIBMBE_FOUND)
|
||||
add_subdirectory(demoddsd)
|
||||
|
@ -34,12 +34,14 @@
|
||||
#include "SWGADSBDemodSettings.h"
|
||||
#include "SWGChannelReport.h"
|
||||
#include "SWGADSBDemodReport.h"
|
||||
#include "SWGTargetAzimuthElevation.h"
|
||||
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "dsp/devicesamplemimo.h"
|
||||
#include "device/deviceapi.h"
|
||||
#include "util/db.h"
|
||||
#include "maincore.h"
|
||||
|
||||
#include "adsbdemod.h"
|
||||
#include "adsbdemodworker.h"
|
||||
@ -471,3 +473,27 @@ void ADSBDemod::networkManagerFinished(QNetworkReply *reply)
|
||||
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
void ADSBDemod::setTarget(const QString& name, float targetAzimuth, float targetElevation)
|
||||
{
|
||||
m_targetAzimuth = targetAzimuth;
|
||||
m_targetElevation = targetElevation;
|
||||
m_targetAzElValid = true;
|
||||
|
||||
// Send to Rotator Controllers
|
||||
MessagePipes& messagePipes = MainCore::instance()->getMessagePipes();
|
||||
QList<MessageQueue*> *mapMessageQueues = messagePipes.getMessageQueues(this, "target");
|
||||
if (mapMessageQueues)
|
||||
{
|
||||
QList<MessageQueue*>::iterator it = mapMessageQueues->begin();
|
||||
|
||||
for (; it != mapMessageQueues->end(); ++it)
|
||||
{
|
||||
SWGSDRangel::SWGTargetAzimuthElevation *swgTarget = new SWGSDRangel::SWGTargetAzimuthElevation();
|
||||
swgTarget->setName(new QString(name));
|
||||
swgTarget->setAzimuth(targetAzimuth);
|
||||
swgTarget->setElevation(targetElevation);
|
||||
(*it)->push(MainCore::MsgTargetAzimuthElevation::create(this, swgTarget));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -119,12 +119,7 @@ public:
|
||||
m_basebandSink->setMessageQueueToGUI(queue);
|
||||
}
|
||||
|
||||
void setTarget(float targetAzimuth, float targetElevation)
|
||||
{
|
||||
m_targetAzimuth = targetAzimuth;
|
||||
m_targetElevation = targetElevation;
|
||||
m_targetAzElValid = true;
|
||||
}
|
||||
void setTarget(const QString& name, float targetAzimuth, float targetElevation);
|
||||
void clearTarget() { m_targetAzElValid = false; }
|
||||
|
||||
uint32_t getNumberOfDeviceStreams() const;
|
||||
|
@ -29,6 +29,8 @@
|
||||
#include <QMessageBox>
|
||||
#include <QDebug>
|
||||
|
||||
#include "SWGMapItem.h"
|
||||
|
||||
#include "ui_adsbdemodgui.h"
|
||||
#include "channel/channelwebapiutils.h"
|
||||
#include "plugin/pluginapi.h"
|
||||
@ -148,6 +150,104 @@ static Real modulus(double x, double y)
|
||||
return x - y * std::floor(x/y);
|
||||
}
|
||||
|
||||
QString Aircraft::getImage()
|
||||
{
|
||||
if (m_emitterCategory.length() > 0)
|
||||
{
|
||||
if (!m_emitterCategory.compare("Heavy"))
|
||||
return QString("aircraft_4engine.png");
|
||||
else if (!m_emitterCategory.compare("Large"))
|
||||
return QString("aircraft_2engine.png");
|
||||
else if (!m_emitterCategory.compare("Small"))
|
||||
return QString("aircraft_2enginesmall.png");
|
||||
else if (!m_emitterCategory.compare("Rotorcraft"))
|
||||
return QString("aircraft_helicopter.png");
|
||||
else if (!m_emitterCategory.compare("High performance"))
|
||||
return QString("aircraft_fighter.png");
|
||||
else if (!m_emitterCategory.compare("Light")
|
||||
|| !m_emitterCategory.compare("Ultralight")
|
||||
|| !m_emitterCategory.compare("Glider/sailplane"))
|
||||
return QString("aircraft_light.png");
|
||||
else if (!m_emitterCategory.compare("Space vehicle"))
|
||||
return QString("aircraft_space.png");
|
||||
else if (!m_emitterCategory.compare("UAV"))
|
||||
return QString("aircraft_drone.png");
|
||||
else if (!m_emitterCategory.compare("Emergency vehicle")
|
||||
|| !m_emitterCategory.compare("Service vehicle"))
|
||||
return QString("truck.png");
|
||||
else
|
||||
return QString("aircraft_2engine.png");
|
||||
}
|
||||
else
|
||||
return QString("aircraft_2engine.png");
|
||||
}
|
||||
|
||||
QString Aircraft::getText(bool all)
|
||||
{
|
||||
QStringList list;
|
||||
if (m_flight.length() > 0)
|
||||
{
|
||||
list.append(QString("Flight: %1").arg(m_flight));
|
||||
}
|
||||
else
|
||||
{
|
||||
list.append(QString("ICAO: %1").arg(m_icao, 1, 16));
|
||||
}
|
||||
if (m_showAll || m_isHighlighted || all)
|
||||
{
|
||||
if (m_aircraftInfo != nullptr)
|
||||
{
|
||||
if (m_aircraftInfo->m_model.size() > 0)
|
||||
{
|
||||
list.append(QString("Aircraft: %1").arg(m_aircraftInfo->m_model));
|
||||
}
|
||||
}
|
||||
if (m_altitudeValid)
|
||||
{
|
||||
if (m_gui->useSIUints())
|
||||
list.append(QString("Altitude: %1 (m)").arg(Units::feetToIntegerMetres(m_altitude)));
|
||||
else
|
||||
list.append(QString("Altitude: %1 (ft)").arg(m_altitude));
|
||||
}
|
||||
if (m_speedValid)
|
||||
{
|
||||
if (m_gui->useSIUints())
|
||||
list.append(QString("%1: %2 (kph)").arg(m_speedTypeNames[m_speedType]).arg(Units::knotsToIntegerKPH(m_speed)));
|
||||
else
|
||||
list.append(QString("%1: %2 (kn)").arg(m_speedTypeNames[m_speedType]).arg(m_speed));
|
||||
}
|
||||
if (m_verticalRateValid)
|
||||
{
|
||||
QString desc;
|
||||
Real rate;
|
||||
QString units;
|
||||
|
||||
if (m_gui->useSIUints())
|
||||
{
|
||||
rate = Units::feetPerMinToIntegerMetresPerSecond(m_verticalRate);
|
||||
units = QString("m/s");
|
||||
}
|
||||
else
|
||||
{
|
||||
rate = m_verticalRate;
|
||||
units = QString("ft/min");
|
||||
}
|
||||
if (m_verticalRate == 0)
|
||||
desc = "Level flight";
|
||||
else if (rate > 0)
|
||||
desc = QString("Climbing: %1 (%2)").arg(rate).arg(units);
|
||||
else
|
||||
desc = QString("Descending: %1 (%2)").arg(rate).arg(units);
|
||||
list.append(QString(desc));
|
||||
}
|
||||
if ((m_status.length() > 0) && m_status.compare("No emergency"))
|
||||
{
|
||||
list.append(m_status);
|
||||
}
|
||||
}
|
||||
return list.join("\n");
|
||||
}
|
||||
|
||||
QVariant AircraftModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
int row = index.row();
|
||||
@ -170,101 +270,12 @@ QVariant AircraftModel::data(const QModelIndex &index, int role) const
|
||||
else if (role == AircraftModel::adsbDataRole)
|
||||
{
|
||||
// Create the text to go in the bubble next to the aircraft
|
||||
QStringList list;
|
||||
if (m_aircrafts[row]->m_flight.length() > 0)
|
||||
{
|
||||
list.append(QString("Flight: %1").arg(m_aircrafts[row]->m_flight));
|
||||
}
|
||||
else
|
||||
{
|
||||
list.append(QString("ICAO: %1").arg(m_aircrafts[row]->m_icao, 1, 16));
|
||||
}
|
||||
if (m_aircrafts[row]->m_showAll || m_aircrafts[row]->m_isHighlighted)
|
||||
{
|
||||
if (m_aircrafts[row]->m_aircraftInfo != nullptr)
|
||||
{
|
||||
if (m_aircrafts[row]->m_aircraftInfo->m_model.size() > 0)
|
||||
{
|
||||
list.append(QString("Aircraft: %1").arg(m_aircrafts[row]->m_aircraftInfo->m_model));
|
||||
}
|
||||
}
|
||||
if (m_aircrafts[row]->m_altitudeValid)
|
||||
{
|
||||
if (m_aircrafts[row]->m_gui->useSIUints())
|
||||
list.append(QString("Altitude: %1 (m)").arg(Units::feetToIntegerMetres(m_aircrafts[row]->m_altitude)));
|
||||
else
|
||||
list.append(QString("Altitude: %1 (ft)").arg(m_aircrafts[row]->m_altitude));
|
||||
}
|
||||
if (m_aircrafts[row]->m_speedValid)
|
||||
{
|
||||
if (m_aircrafts[row]->m_gui->useSIUints())
|
||||
list.append(QString("%1: %2 (kph)").arg(m_aircrafts[row]->m_speedTypeNames[m_aircrafts[row]->m_speedType]).arg(Units::knotsToIntegerKPH(m_aircrafts[row]->m_speed)));
|
||||
else
|
||||
list.append(QString("%1: %2 (kn)").arg(m_aircrafts[row]->m_speedTypeNames[m_aircrafts[row]->m_speedType]).arg(m_aircrafts[row]->m_speed));
|
||||
}
|
||||
if (m_aircrafts[row]->m_verticalRateValid)
|
||||
{
|
||||
QString desc;
|
||||
Real rate;
|
||||
QString units;
|
||||
|
||||
if (m_aircrafts[row]->m_gui->useSIUints())
|
||||
{
|
||||
rate = Units::feetPerMinToIntegerMetresPerSecond(m_aircrafts[row]->m_verticalRate);
|
||||
units = QString("m/s");
|
||||
}
|
||||
else
|
||||
{
|
||||
rate = m_aircrafts[row]->m_verticalRate;
|
||||
units = QString("ft/min");
|
||||
}
|
||||
if (m_aircrafts[row]->m_verticalRate == 0)
|
||||
desc = "Level flight";
|
||||
else if (rate > 0)
|
||||
desc = QString("Climbing: %1 (%2)").arg(rate).arg(units);
|
||||
else
|
||||
desc = QString("Descending: %1 (%2)").arg(rate).arg(units);
|
||||
list.append(QString(desc));
|
||||
}
|
||||
if ((m_aircrafts[row]->m_status.length() > 0) && m_aircrafts[row]->m_status.compare("No emergency"))
|
||||
{
|
||||
list.append(m_aircrafts[row]->m_status);
|
||||
}
|
||||
}
|
||||
QString data = list.join("\n");
|
||||
return QVariant::fromValue(data);
|
||||
return QVariant::fromValue(m_aircrafts[row]->getText());
|
||||
}
|
||||
else if (role == AircraftModel::aircraftImageRole)
|
||||
{
|
||||
// Select an image to use for the aircraft
|
||||
if (m_aircrafts[row]->m_emitterCategory.length() > 0)
|
||||
{
|
||||
if (!m_aircrafts[row]->m_emitterCategory.compare("Heavy"))
|
||||
return QVariant::fromValue(QString("aircraft_4engine.png"));
|
||||
else if (!m_aircrafts[row]->m_emitterCategory.compare("Large"))
|
||||
return QVariant::fromValue(QString("aircraft_2engine.png"));
|
||||
else if (!m_aircrafts[row]->m_emitterCategory.compare("Small"))
|
||||
return QVariant::fromValue(QString("aircraft_2enginesmall.png"));
|
||||
else if (!m_aircrafts[row]->m_emitterCategory.compare("Rotorcraft"))
|
||||
return QVariant::fromValue(QString("aircraft_helicopter.png"));
|
||||
else if (!m_aircrafts[row]->m_emitterCategory.compare("High performance"))
|
||||
return QVariant::fromValue(QString("aircraft_fighter.png"));
|
||||
else if (!m_aircrafts[row]->m_emitterCategory.compare("Light")
|
||||
|| !m_aircrafts[row]->m_emitterCategory.compare("Ultralight")
|
||||
|| !m_aircrafts[row]->m_emitterCategory.compare("Glider/sailplane"))
|
||||
return QVariant::fromValue(QString("aircraft_light.png"));
|
||||
else if (!m_aircrafts[row]->m_emitterCategory.compare("Space vehicle"))
|
||||
return QVariant::fromValue(QString("aircraft_space.png"));
|
||||
else if (!m_aircrafts[row]->m_emitterCategory.compare("UAV"))
|
||||
return QVariant::fromValue(QString("aircraft_drone.png"));
|
||||
else if (!m_aircrafts[row]->m_emitterCategory.compare("Emergency vehicle")
|
||||
|| !m_aircrafts[row]->m_emitterCategory.compare("Service vehicle"))
|
||||
return QVariant::fromValue(QString("truck.png"));
|
||||
else
|
||||
return QVariant::fromValue(QString("aircraft_2engine.png"));
|
||||
}
|
||||
else
|
||||
return QVariant::fromValue(QString("aircraft_2engine.png"));
|
||||
return QVariant::fromValue(m_aircrafts[row]->getImage());
|
||||
}
|
||||
else if (role == AircraftModel::bubbleColourRole)
|
||||
{
|
||||
@ -280,7 +291,7 @@ QVariant AircraftModel::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
else if (role == AircraftModel::aircraftPathRole)
|
||||
{
|
||||
if (m_flightPaths)
|
||||
if ((m_flightPaths && m_aircrafts[row]->m_isHighlighted) || m_allFlightPaths)
|
||||
return m_aircrafts[row]->m_coordinates;
|
||||
else
|
||||
return QVariantList();
|
||||
@ -407,7 +418,7 @@ bool AirportModel::setData(const QModelIndex &index, const QVariant& value, int
|
||||
else if (idx == m_airports[row]->m_frequencies.size())
|
||||
{
|
||||
// Set airport as target
|
||||
m_gui->target(m_azimuth[row], m_elevation[row]);
|
||||
m_gui->target(m_airports[row]->m_name, m_azimuth[row], m_elevation[row]);
|
||||
emit dataChanged(index, index);
|
||||
}
|
||||
return true;
|
||||
@ -442,7 +453,29 @@ void ADSBDemodGUI::updatePosition(Aircraft *aircraft)
|
||||
aircraft->m_rangeItem->setText(QString::number(aircraft->m_range/1000.0, 'f', 1));
|
||||
aircraft->m_azElItem->setText(QString("%1/%2").arg(std::round(aircraft->m_azimuth)).arg(std::round(aircraft->m_elevation)));
|
||||
if (aircraft == m_trackAircraft)
|
||||
m_adsbDemod->setTarget(aircraft->m_azimuth, aircraft->m_elevation);
|
||||
m_adsbDemod->setTarget(aircraft->targetName(), aircraft->m_azimuth, aircraft->m_elevation);
|
||||
|
||||
// Send to Map feature
|
||||
MessagePipes& messagePipes = MainCore::instance()->getMessagePipes();
|
||||
QList<MessageQueue*> *mapMessageQueues = messagePipes.getMessageQueues(m_adsbDemod, "mapitems");
|
||||
if (mapMessageQueues)
|
||||
{
|
||||
QList<MessageQueue*>::iterator it = mapMessageQueues->begin();
|
||||
|
||||
for (; it != mapMessageQueues->end(); ++it)
|
||||
{
|
||||
SWGSDRangel::SWGMapItem *swgMapItem = new SWGSDRangel::SWGMapItem();
|
||||
swgMapItem->setName(new QString(QString("%1").arg(aircraft->m_icao, 0, 16)));
|
||||
swgMapItem->setLatitude(aircraft->m_latitude);
|
||||
swgMapItem->setLongitude(aircraft->m_longitude);
|
||||
swgMapItem->setImage(new QString(QString("qrc:///map/%1").arg(aircraft->getImage())));
|
||||
swgMapItem->setImageRotation(aircraft->m_heading);
|
||||
swgMapItem->setText(new QString(aircraft->getText(true)));
|
||||
|
||||
MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_adsbDemod, swgMapItem);
|
||||
(*it)->push(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Called when we have lat & long from local decode and we need to check if it is in a valid range (<180nm/333km)
|
||||
@ -1359,6 +1392,12 @@ void ADSBDemodGUI::on_flightPaths_clicked(bool checked)
|
||||
m_aircraftModel.setFlightPaths(checked);
|
||||
}
|
||||
|
||||
void ADSBDemodGUI::on_allFlightPaths_clicked(bool checked)
|
||||
{
|
||||
m_settings.m_allFlightPaths = checked;
|
||||
m_aircraftModel.setAllFlightPaths(checked);
|
||||
}
|
||||
|
||||
QString ADSBDemodGUI::getDataDir()
|
||||
{
|
||||
// Get directory to store app data in (aircraft & airport databases and user-definable icons)
|
||||
@ -1633,7 +1672,7 @@ void ADSBDemodGUI::updateAirports()
|
||||
}
|
||||
|
||||
// Set a static target, such as an airport
|
||||
void ADSBDemodGUI::target(float az, float el)
|
||||
void ADSBDemodGUI::target(const QString& name, float az, float el)
|
||||
{
|
||||
if (m_trackAircraft)
|
||||
{
|
||||
@ -1642,7 +1681,7 @@ void ADSBDemodGUI::target(float az, float el)
|
||||
m_aircraftModel.aircraftUpdated(m_trackAircraft);
|
||||
m_trackAircraft = nullptr;
|
||||
}
|
||||
m_adsbDemod->setTarget(az, el);
|
||||
m_adsbDemod->setTarget(name, az, el);
|
||||
}
|
||||
|
||||
void ADSBDemodGUI::targetAircraft(Aircraft *aircraft)
|
||||
@ -1658,7 +1697,7 @@ void ADSBDemodGUI::targetAircraft(Aircraft *aircraft)
|
||||
// Track this aircraft
|
||||
m_trackAircraft = aircraft;
|
||||
if (aircraft->m_positionValid)
|
||||
m_adsbDemod->setTarget(aircraft->m_azimuth, aircraft->m_elevation);
|
||||
m_adsbDemod->setTarget(aircraft->targetName(), aircraft->m_azimuth, aircraft->m_elevation);
|
||||
// Change colour of new target
|
||||
aircraft->m_isTarget = true;
|
||||
m_aircraftModel.aircraftUpdated(aircraft);
|
||||
@ -1922,6 +1961,8 @@ void ADSBDemodGUI::displaySettings()
|
||||
|
||||
ui->flightPaths->setChecked(m_settings.m_flightPaths);
|
||||
m_aircraftModel.setFlightPaths(m_settings.m_flightPaths);
|
||||
ui->allFlightPaths->setChecked(m_settings.m_allFlightPaths);
|
||||
m_aircraftModel.setAllFlightPaths(m_settings.m_allFlightPaths);
|
||||
|
||||
displayStreamIndex();
|
||||
|
||||
@ -2035,6 +2076,21 @@ void ADSBDemodGUI::tick()
|
||||
ui->adsbData->removeRow(aircraft->m_icaoItem->row());
|
||||
// Remove aircraft from hash
|
||||
i = m_aircraft.erase(i);
|
||||
// Remove from map feature
|
||||
MessagePipes& messagePipes = MainCore::instance()->getMessagePipes();
|
||||
QList<MessageQueue*> *mapMessageQueues = messagePipes.getMessageQueues(m_adsbDemod, "mapitems");
|
||||
if (mapMessageQueues)
|
||||
{
|
||||
QList<MessageQueue*>::iterator it = mapMessageQueues->begin();
|
||||
for (; it != mapMessageQueues->end(); ++it)
|
||||
{
|
||||
SWGSDRangel::SWGMapItem *swgMapItem = new SWGSDRangel::SWGMapItem();
|
||||
swgMapItem->setName(new QString(QString("%1").arg(aircraft->m_icao, 0, 16)));
|
||||
swgMapItem->setImage(new QString(""));
|
||||
MainCore::MsgMapItem *msg = MainCore::MsgMapItem::create(m_adsbDemod, swgMapItem);
|
||||
(*it)->push(msg);
|
||||
}
|
||||
}
|
||||
// And finally free its memory
|
||||
delete aircraft;
|
||||
}
|
||||
|
@ -34,6 +34,7 @@
|
||||
#include "util/azel.h"
|
||||
#include "util/movingaverage.h"
|
||||
#include "util/httpdownloadmanager.h"
|
||||
#include "maincore.h"
|
||||
|
||||
#include "adsbdemodsettings.h"
|
||||
#include "ourairportsdb.h"
|
||||
@ -207,6 +208,19 @@ struct Aircraft {
|
||||
m_correlationItem = new QTableWidgetItem();
|
||||
m_rssiItem = new QTableWidgetItem();
|
||||
}
|
||||
|
||||
QString getImage();
|
||||
QString getText(bool all=false);
|
||||
|
||||
// Name to use when selected as a target
|
||||
QString targetName()
|
||||
{
|
||||
if (!m_flight.isEmpty())
|
||||
return QString("Flight: %1").arg(m_flight);
|
||||
else
|
||||
return QString("ICAO: %1").arg(m_icao, 0, 16);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Aircraft data model used by QML map item
|
||||
@ -300,9 +314,16 @@ public:
|
||||
allAircraftUpdated();
|
||||
}
|
||||
|
||||
void setAllFlightPaths(bool allFlightPaths)
|
||||
{
|
||||
m_allFlightPaths = allFlightPaths;
|
||||
allAircraftUpdated();
|
||||
}
|
||||
|
||||
private:
|
||||
QList<Aircraft *> m_aircrafts;
|
||||
bool m_flightPaths;
|
||||
bool m_allFlightPaths;
|
||||
};
|
||||
|
||||
// Airport data model used by QML map item
|
||||
@ -445,7 +466,7 @@ public:
|
||||
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
|
||||
void highlightAircraft(Aircraft *aircraft);
|
||||
void targetAircraft(Aircraft *aircraft);
|
||||
void target(float az, float el);
|
||||
void target(const QString& name, float az, float el);
|
||||
bool setFrequency(float frequency);
|
||||
bool useSIUints() { return m_settings.m_siUnits; }
|
||||
|
||||
@ -547,6 +568,7 @@ private slots:
|
||||
void on_getOSNDB_clicked();
|
||||
void on_getAirportDB_clicked();
|
||||
void on_flightPaths_clicked(bool checked);
|
||||
void on_allFlightPaths_clicked(bool checked);
|
||||
void onWidgetRolled(QWidget* widget, bool rollDown);
|
||||
void onMenuDialogCalled(const QPoint& p);
|
||||
void handleInputMessages();
|
||||
|
@ -546,7 +546,7 @@
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="flightPaths">
|
||||
<property name="toolTip">
|
||||
<string>Display flight paths</string>
|
||||
<string>Display flight path for selected aircraft</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>^</string>
|
||||
@ -563,6 +563,26 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="allFlightPaths">
|
||||
<property name="toolTip">
|
||||
<string>Display flight paths for all aircraft</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>^</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="icons.qrc">
|
||||
<normaloff>:/icons/allflightpaths.png</normaloff>:/icons/allflightpaths.png</iconset>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ButtonSwitch" name="feed">
|
||||
<property name="toolTip">
|
||||
@ -945,17 +965,17 @@
|
||||
<extends>QWidget</extends>
|
||||
<header location="global">QtQuickWidgets/QQuickWidget</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ButtonSwitch</class>
|
||||
<extends>QToolButton</extends>
|
||||
<header>gui/buttonswitch.h</header>
|
||||
</customwidget>
|
||||
<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>LevelMeterSignalDB</class>
|
||||
<extends>QWidget</extends>
|
||||
|
@ -53,10 +53,11 @@ void ADSBDemodSettings::resetToDefaults()
|
||||
m_airportMinimumSize = AirportType::Medium;
|
||||
m_displayHeliports = false;
|
||||
m_flightPaths = true;
|
||||
m_allFlightPaths = false;
|
||||
m_siUnits = false;
|
||||
m_tableFontName = "Liberation Sans";
|
||||
m_tableFontSize = 9;
|
||||
m_displayDemodStats = true;
|
||||
m_displayDemodStats = false;
|
||||
m_correlateFullPreamble = true;
|
||||
m_demodModeS = false;
|
||||
m_deviceIndex = -1;
|
||||
@ -109,6 +110,7 @@ QByteArray ADSBDemodSettings::serialize() const
|
||||
s.writeBool(30, m_autoResizeTableColumns);
|
||||
s.writeS32(31, m_interpolatorPhaseSteps);
|
||||
s.writeFloat(32, m_interpolatorTapsPerPhase);
|
||||
s.writeBool(33, m_allFlightPaths);
|
||||
|
||||
for (int i = 0; i < ADSBDEMOD_COLUMNS; i++)
|
||||
s.writeS32(100 + i, m_columnIndexes[i]);
|
||||
@ -188,6 +190,7 @@ bool ADSBDemodSettings::deserialize(const QByteArray& data)
|
||||
d.readBool(30, &m_autoResizeTableColumns, false);
|
||||
d.readS32(31, &m_interpolatorPhaseSteps, 4);
|
||||
d.readFloat(32, &m_interpolatorTapsPerPhase, 3.5f);
|
||||
d.readBool(33, &m_allFlightPaths, false);
|
||||
|
||||
for (int i = 0; i < ADSBDEMOD_COLUMNS; i++)
|
||||
d.readS32(100 + i, &m_columnIndexes[i], i);
|
||||
|
@ -66,6 +66,7 @@ struct ADSBDemodSettings
|
||||
} m_airportMinimumSize; //!< What's the minimum size airport that should be displayed
|
||||
bool m_displayHeliports; //!< Whether to display heliports on the map
|
||||
bool m_flightPaths; //!< Whether to display flight paths
|
||||
bool m_allFlightPaths; //!< Whether to display flight paths for all aircraft
|
||||
bool m_siUnits; //!< Uses m,kph rather than ft/knts
|
||||
QString m_tableFontName; //!< Font to use for table
|
||||
int m_tableFontSize;
|
||||
|
@ -2,5 +2,6 @@
|
||||
<qresource prefix="/">
|
||||
<file>icons/aircraft.png</file>
|
||||
<file>icons/controltower.png</file>
|
||||
<file>icons/allflightpaths.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
BIN
plugins/channelrx/demodadsb/icons/allflightpaths.png
Normal file
After Width: | Height: | Size: 2.0 KiB |
58
plugins/channelrx/demodpacket/CMakeLists.txt
Normal file
@ -0,0 +1,58 @@
|
||||
project(demodpacket)
|
||||
|
||||
set(demodpacket_SOURCES
|
||||
packetdemod.cpp
|
||||
packetdemodsettings.cpp
|
||||
packetdemodbaseband.cpp
|
||||
packetdemodsink.cpp
|
||||
packetdemodplugin.cpp
|
||||
packetdemodwebapiadapter.cpp
|
||||
)
|
||||
|
||||
set(demodpacket_HEADERS
|
||||
packetdemod.h
|
||||
packetdemodsettings.h
|
||||
packetdemodbaseband.h
|
||||
packetdemodsink.h
|
||||
packetdemodplugin.h
|
||||
packetdemodwebapiadapter.h
|
||||
)
|
||||
|
||||
include_directories(
|
||||
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
|
||||
)
|
||||
|
||||
if(NOT SERVER_MODE)
|
||||
set(demodpacket_SOURCES
|
||||
${demodpacket_SOURCES}
|
||||
packetdemodgui.cpp
|
||||
packetdemodgui.ui
|
||||
)
|
||||
set(demodpacket_HEADERS
|
||||
${demodpacket_HEADERS}
|
||||
packetdemodgui.h
|
||||
)
|
||||
|
||||
set(TARGET_NAME demodpacket)
|
||||
set(TARGET_LIB "Qt5::Widgets" Qt5::Quick Qt5::QuickWidgets Qt5::Positioning)
|
||||
set(TARGET_LIB_GUI "sdrgui")
|
||||
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
|
||||
else()
|
||||
set(TARGET_NAME demodpacketsrv)
|
||||
set(TARGET_LIB "")
|
||||
set(TARGET_LIB_GUI "")
|
||||
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
|
||||
endif()
|
||||
|
||||
add_library(${TARGET_NAME} SHARED
|
||||
${demodpacket_SOURCES}
|
||||
)
|
||||
|
||||
target_link_libraries(${TARGET_NAME}
|
||||
Qt5::Core
|
||||
${TARGET_LIB}
|
||||
sdrbase
|
||||
${TARGET_LIB_GUI}
|
||||
)
|
||||
|
||||
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
|
434
plugins/channelrx/demodpacket/packetdemod.cpp
Normal file
@ -0,0 +1,434 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2015-2018 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/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "packetdemod.h"
|
||||
|
||||
#include <QTime>
|
||||
#include <QDebug>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QBuffer>
|
||||
#include <QThread>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <complex.h>
|
||||
|
||||
#include "SWGChannelSettings.h"
|
||||
#include "SWGPacketDemodSettings.h"
|
||||
#include "SWGChannelReport.h"
|
||||
#include "SWGMapItem.h"
|
||||
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "device/deviceapi.h"
|
||||
#include "feature/feature.h"
|
||||
#include "util/db.h"
|
||||
#include "maincore.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(PacketDemod::MsgConfigurePacketDemod, Message)
|
||||
|
||||
const char * const PacketDemod::m_channelIdURI = "sdrangel.channelrx.packetdemod";
|
||||
const char * const PacketDemod::m_channelId = "PacketDemod";
|
||||
|
||||
PacketDemod::PacketDemod(DeviceAPI *deviceAPI) :
|
||||
ChannelAPI(m_channelIdURI, ChannelAPI::StreamSingleSink),
|
||||
m_deviceAPI(deviceAPI),
|
||||
m_basebandSampleRate(0)
|
||||
{
|
||||
setObjectName(m_channelId);
|
||||
|
||||
m_basebandSink = new PacketDemodBaseband(this);
|
||||
m_basebandSink->setMessageQueueToChannel(getInputMessageQueue());
|
||||
m_basebandSink->moveToThread(&m_thread);
|
||||
|
||||
applySettings(m_settings, true);
|
||||
|
||||
m_deviceAPI->addChannelSink(this);
|
||||
m_deviceAPI->addChannelSinkAPI(this);
|
||||
|
||||
m_networkManager = new QNetworkAccessManager();
|
||||
connect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
|
||||
}
|
||||
|
||||
PacketDemod::~PacketDemod()
|
||||
{
|
||||
qDebug("PacketDemod::~PacketDemod");
|
||||
disconnect(m_networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(networkManagerFinished(QNetworkReply*)));
|
||||
delete m_networkManager;
|
||||
m_deviceAPI->removeChannelSinkAPI(this);
|
||||
m_deviceAPI->removeChannelSink(this);
|
||||
|
||||
if (m_basebandSink->isRunning()) {
|
||||
stop();
|
||||
}
|
||||
|
||||
delete m_basebandSink;
|
||||
}
|
||||
|
||||
uint32_t PacketDemod::getNumberOfDeviceStreams() const
|
||||
{
|
||||
return m_deviceAPI->getNbSourceStreams();
|
||||
}
|
||||
|
||||
void PacketDemod::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool firstOfBurst)
|
||||
{
|
||||
(void) firstOfBurst;
|
||||
m_basebandSink->feed(begin, end);
|
||||
}
|
||||
|
||||
void PacketDemod::start()
|
||||
{
|
||||
qDebug("PacketDemod::start");
|
||||
|
||||
m_basebandSink->reset();
|
||||
m_basebandSink->startWork();
|
||||
m_thread.start();
|
||||
|
||||
DSPSignalNotification *dspMsg = new DSPSignalNotification(m_basebandSampleRate, m_centerFrequency);
|
||||
m_basebandSink->getInputMessageQueue()->push(dspMsg);
|
||||
|
||||
PacketDemodBaseband::MsgConfigurePacketDemodBaseband *msg = PacketDemodBaseband::MsgConfigurePacketDemodBaseband::create(m_settings, true);
|
||||
m_basebandSink->getInputMessageQueue()->push(msg);
|
||||
}
|
||||
|
||||
void PacketDemod::stop()
|
||||
{
|
||||
qDebug("PacketDemod::stop");
|
||||
m_basebandSink->stopWork();
|
||||
m_thread.quit();
|
||||
m_thread.wait();
|
||||
}
|
||||
|
||||
bool PacketDemod::handleMessage(const Message& cmd)
|
||||
{
|
||||
if (MsgConfigurePacketDemod::match(cmd))
|
||||
{
|
||||
MsgConfigurePacketDemod& cfg = (MsgConfigurePacketDemod&) cmd;
|
||||
qDebug() << "PacketDemod::handleMessage: MsgConfigurePacketDemod";
|
||||
applySettings(cfg.getSettings(), cfg.getForce());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (DSPSignalNotification::match(cmd))
|
||||
{
|
||||
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
|
||||
m_basebandSampleRate = notif.getSampleRate();
|
||||
m_centerFrequency = notif.getCenterFrequency();
|
||||
// Forward to the sink
|
||||
DSPSignalNotification* rep = new DSPSignalNotification(notif); // make a copy
|
||||
qDebug() << "PacketDemod::handleMessage: DSPSignalNotification";
|
||||
m_basebandSink->getInputMessageQueue()->push(rep);
|
||||
// Forward to GUI if any
|
||||
if (m_guiMessageQueue)
|
||||
{
|
||||
rep = new DSPSignalNotification(notif);
|
||||
m_guiMessageQueue->push(rep);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MainCore::MsgPacket::match(cmd))
|
||||
{
|
||||
// Forward to GUI
|
||||
MainCore::MsgPacket& report = (MainCore::MsgPacket&)cmd;
|
||||
if (getMessageQueueToGUI())
|
||||
{
|
||||
MainCore::MsgPacket *msg = new MainCore::MsgPacket(report);
|
||||
getMessageQueueToGUI()->push(msg);
|
||||
}
|
||||
|
||||
MessagePipes& messagePipes = MainCore::instance()->getMessagePipes();
|
||||
|
||||
// Forward to APRS and other packet features
|
||||
QList<MessageQueue*> *packetMessageQueues = messagePipes.getMessageQueues(this, "packets");
|
||||
if (packetMessageQueues)
|
||||
{
|
||||
QList<MessageQueue*>::iterator it = packetMessageQueues->begin();
|
||||
for (; it != packetMessageQueues->end(); ++it)
|
||||
{
|
||||
MainCore::MsgPacket *msg = new MainCore::MsgPacket(report);
|
||||
(*it)->push(msg);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void PacketDemod::applySettings(const PacketDemodSettings& settings, bool force)
|
||||
{
|
||||
qDebug() << "PacketDemod::applySettings:"
|
||||
<< " m_streamIndex: " << settings.m_streamIndex
|
||||
<< " m_useReverseAPI: " << settings.m_useReverseAPI
|
||||
<< " m_reverseAPIAddress: " << settings.m_reverseAPIAddress
|
||||
<< " m_reverseAPIPort: " << settings.m_reverseAPIPort
|
||||
<< " m_reverseAPIDeviceIndex: " << settings.m_reverseAPIDeviceIndex
|
||||
<< " m_reverseAPIChannelIndex: " << settings.m_reverseAPIChannelIndex
|
||||
<< " force: " << force;
|
||||
|
||||
QList<QString> reverseAPIKeys;
|
||||
|
||||
if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force) {
|
||||
reverseAPIKeys.append("inputFrequencyOffset");
|
||||
}
|
||||
if ((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) {
|
||||
reverseAPIKeys.append("rfBandwidth");
|
||||
}
|
||||
if ((settings.m_fmDeviation != m_settings.m_fmDeviation) || force) {
|
||||
reverseAPIKeys.append("fmDeviation");
|
||||
}
|
||||
|
||||
if (m_settings.m_streamIndex != settings.m_streamIndex)
|
||||
{
|
||||
if (m_deviceAPI->getSampleMIMO()) // change of stream is possible for MIMO devices only
|
||||
{
|
||||
m_deviceAPI->removeChannelSinkAPI(this);
|
||||
m_deviceAPI->removeChannelSink(this, m_settings.m_streamIndex);
|
||||
m_deviceAPI->addChannelSink(this, settings.m_streamIndex);
|
||||
m_deviceAPI->addChannelSinkAPI(this);
|
||||
}
|
||||
|
||||
reverseAPIKeys.append("streamIndex");
|
||||
}
|
||||
|
||||
PacketDemodBaseband::MsgConfigurePacketDemodBaseband *msg = PacketDemodBaseband::MsgConfigurePacketDemodBaseband::create(settings, force);
|
||||
m_basebandSink->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_reverseAPIDeviceIndex != settings.m_reverseAPIDeviceIndex) ||
|
||||
(m_settings.m_reverseAPIChannelIndex != settings.m_reverseAPIChannelIndex);
|
||||
webapiReverseSendSettings(reverseAPIKeys, settings, fullUpdate || force);
|
||||
}
|
||||
|
||||
m_settings = settings;
|
||||
}
|
||||
|
||||
QByteArray PacketDemod::serialize() const
|
||||
{
|
||||
return m_settings.serialize();
|
||||
}
|
||||
|
||||
bool PacketDemod::deserialize(const QByteArray& data)
|
||||
{
|
||||
if (m_settings.deserialize(data))
|
||||
{
|
||||
MsgConfigurePacketDemod *msg = MsgConfigurePacketDemod::create(m_settings, true);
|
||||
m_inputMessageQueue.push(msg);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_settings.resetToDefaults();
|
||||
MsgConfigurePacketDemod *msg = MsgConfigurePacketDemod::create(m_settings, true);
|
||||
m_inputMessageQueue.push(msg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int PacketDemod::webapiSettingsGet(
|
||||
SWGSDRangel::SWGChannelSettings& response,
|
||||
QString& errorMessage)
|
||||
{
|
||||
(void) errorMessage;
|
||||
response.setPacketDemodSettings(new SWGSDRangel::SWGPacketDemodSettings());
|
||||
response.getPacketDemodSettings()->init();
|
||||
webapiFormatChannelSettings(response, m_settings);
|
||||
return 200;
|
||||
}
|
||||
|
||||
int PacketDemod::webapiSettingsPutPatch(
|
||||
bool force,
|
||||
const QStringList& channelSettingsKeys,
|
||||
SWGSDRangel::SWGChannelSettings& response,
|
||||
QString& errorMessage)
|
||||
{
|
||||
(void) errorMessage;
|
||||
PacketDemodSettings settings = m_settings;
|
||||
webapiUpdateChannelSettings(settings, channelSettingsKeys, response);
|
||||
|
||||
MsgConfigurePacketDemod *msg = MsgConfigurePacketDemod::create(settings, force);
|
||||
m_inputMessageQueue.push(msg);
|
||||
|
||||
qDebug("PacketDemod::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue);
|
||||
if (m_guiMessageQueue) // forward to GUI if any
|
||||
{
|
||||
MsgConfigurePacketDemod *msgToGUI = MsgConfigurePacketDemod::create(settings, force);
|
||||
m_guiMessageQueue->push(msgToGUI);
|
||||
}
|
||||
|
||||
webapiFormatChannelSettings(response, settings);
|
||||
|
||||
return 200;
|
||||
}
|
||||
|
||||
void PacketDemod::webapiUpdateChannelSettings(
|
||||
PacketDemodSettings& settings,
|
||||
const QStringList& channelSettingsKeys,
|
||||
SWGSDRangel::SWGChannelSettings& response)
|
||||
{
|
||||
if (channelSettingsKeys.contains("inputFrequencyOffset")) {
|
||||
settings.m_inputFrequencyOffset = response.getPacketDemodSettings()->getInputFrequencyOffset();
|
||||
}
|
||||
if (channelSettingsKeys.contains("fmDeviation")) {
|
||||
settings.m_fmDeviation = response.getPacketDemodSettings()->getFmDeviation();
|
||||
}
|
||||
if (channelSettingsKeys.contains("rfBandwidth")) {
|
||||
settings.m_rfBandwidth = response.getPacketDemodSettings()->getRfBandwidth();
|
||||
}
|
||||
if (channelSettingsKeys.contains("rgbColor")) {
|
||||
settings.m_rgbColor = response.getPacketDemodSettings()->getRgbColor();
|
||||
}
|
||||
if (channelSettingsKeys.contains("title")) {
|
||||
settings.m_title = *response.getPacketDemodSettings()->getTitle();
|
||||
}
|
||||
if (channelSettingsKeys.contains("streamIndex")) {
|
||||
settings.m_streamIndex = response.getPacketDemodSettings()->getStreamIndex();
|
||||
}
|
||||
if (channelSettingsKeys.contains("useReverseAPI")) {
|
||||
settings.m_useReverseAPI = response.getPacketDemodSettings()->getUseReverseApi() != 0;
|
||||
}
|
||||
if (channelSettingsKeys.contains("reverseAPIAddress")) {
|
||||
settings.m_reverseAPIAddress = *response.getPacketDemodSettings()->getReverseApiAddress();
|
||||
}
|
||||
if (channelSettingsKeys.contains("reverseAPIPort")) {
|
||||
settings.m_reverseAPIPort = response.getPacketDemodSettings()->getReverseApiPort();
|
||||
}
|
||||
if (channelSettingsKeys.contains("reverseAPIDeviceIndex")) {
|
||||
settings.m_reverseAPIDeviceIndex = response.getPacketDemodSettings()->getReverseApiDeviceIndex();
|
||||
}
|
||||
if (channelSettingsKeys.contains("reverseAPIChannelIndex")) {
|
||||
settings.m_reverseAPIChannelIndex = response.getPacketDemodSettings()->getReverseApiChannelIndex();
|
||||
}
|
||||
}
|
||||
|
||||
void PacketDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response, const PacketDemodSettings& settings)
|
||||
{
|
||||
response.getPacketDemodSettings()->setFmDeviation(settings.m_fmDeviation);
|
||||
response.getPacketDemodSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
|
||||
response.getPacketDemodSettings()->setRfBandwidth(settings.m_rfBandwidth);
|
||||
response.getPacketDemodSettings()->setRgbColor(settings.m_rgbColor);
|
||||
|
||||
if (response.getPacketDemodSettings()->getTitle()) {
|
||||
*response.getPacketDemodSettings()->getTitle() = settings.m_title;
|
||||
} else {
|
||||
response.getPacketDemodSettings()->setTitle(new QString(settings.m_title));
|
||||
}
|
||||
|
||||
response.getPacketDemodSettings()->setStreamIndex(settings.m_streamIndex);
|
||||
response.getPacketDemodSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
|
||||
|
||||
if (response.getPacketDemodSettings()->getReverseApiAddress()) {
|
||||
*response.getPacketDemodSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
|
||||
} else {
|
||||
response.getPacketDemodSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
|
||||
}
|
||||
|
||||
response.getPacketDemodSettings()->setReverseApiPort(settings.m_reverseAPIPort);
|
||||
response.getPacketDemodSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIDeviceIndex);
|
||||
response.getPacketDemodSettings()->setReverseApiChannelIndex(settings.m_reverseAPIChannelIndex);
|
||||
}
|
||||
|
||||
void PacketDemod::webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const PacketDemodSettings& settings, bool force)
|
||||
{
|
||||
SWGSDRangel::SWGChannelSettings *swgChannelSettings = new SWGSDRangel::SWGChannelSettings();
|
||||
webapiFormatChannelSettings(channelSettingsKeys, swgChannelSettings, settings, force);
|
||||
|
||||
QString channelSettingsURL = QString("http://%1:%2/sdrangel/deviceset/%3/channel/%4/settings")
|
||||
.arg(settings.m_reverseAPIAddress)
|
||||
.arg(settings.m_reverseAPIPort)
|
||||
.arg(settings.m_reverseAPIDeviceIndex)
|
||||
.arg(settings.m_reverseAPIChannelIndex);
|
||||
m_networkRequest.setUrl(QUrl(channelSettingsURL));
|
||||
m_networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QBuffer *buffer = new QBuffer();
|
||||
buffer->open((QBuffer::ReadWrite));
|
||||
buffer->write(swgChannelSettings->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 swgChannelSettings;
|
||||
}
|
||||
|
||||
void PacketDemod::webapiFormatChannelSettings(
|
||||
QList<QString>& channelSettingsKeys,
|
||||
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
|
||||
const PacketDemodSettings& settings,
|
||||
bool force
|
||||
)
|
||||
{
|
||||
swgChannelSettings->setDirection(0); // Single sink (Rx)
|
||||
swgChannelSettings->setOriginatorChannelIndex(getIndexInDeviceSet());
|
||||
swgChannelSettings->setOriginatorDeviceSetIndex(getDeviceSetIndex());
|
||||
swgChannelSettings->setChannelType(new QString("PacketDemod"));
|
||||
swgChannelSettings->setPacketDemodSettings(new SWGSDRangel::SWGPacketDemodSettings());
|
||||
SWGSDRangel::SWGPacketDemodSettings *swgPacketDemodSettings = swgChannelSettings->getPacketDemodSettings();
|
||||
|
||||
// transfer data that has been modified. When force is on transfer all data except reverse API data
|
||||
|
||||
if (channelSettingsKeys.contains("fmDeviation") || force) {
|
||||
swgPacketDemodSettings->setFmDeviation(settings.m_fmDeviation);
|
||||
}
|
||||
if (channelSettingsKeys.contains("inputFrequencyOffset") || force) {
|
||||
swgPacketDemodSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
|
||||
}
|
||||
if (channelSettingsKeys.contains("rfBandwidth") || force) {
|
||||
swgPacketDemodSettings->setRfBandwidth(settings.m_rfBandwidth);
|
||||
}
|
||||
if (channelSettingsKeys.contains("rgbColor") || force) {
|
||||
swgPacketDemodSettings->setRgbColor(settings.m_rgbColor);
|
||||
}
|
||||
if (channelSettingsKeys.contains("title") || force) {
|
||||
swgPacketDemodSettings->setTitle(new QString(settings.m_title));
|
||||
}
|
||||
if (channelSettingsKeys.contains("streamIndex") || force) {
|
||||
swgPacketDemodSettings->setStreamIndex(settings.m_streamIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void PacketDemod::networkManagerFinished(QNetworkReply *reply)
|
||||
{
|
||||
QNetworkReply::NetworkError replyError = reply->error();
|
||||
|
||||
if (replyError)
|
||||
{
|
||||
qWarning() << "PacketDemod::networkManagerFinished:"
|
||||
<< " error(" << (int) replyError
|
||||
<< "): " << replyError
|
||||
<< ": " << reply->errorString();
|
||||
}
|
||||
else
|
||||
{
|
||||
QString answer = reply->readAll();
|
||||
answer.chop(1); // remove last \n
|
||||
qDebug("PacketDemod::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
}
|
152
plugins/channelrx/demodpacket/packetdemod.h
Normal file
@ -0,0 +1,152 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2015-2018 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_PACKETDEMOD_H
|
||||
#define INCLUDE_PACKETDEMOD_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <QNetworkRequest>
|
||||
#include <QThread>
|
||||
|
||||
#include "dsp/basebandsamplesink.h"
|
||||
#include "channel/channelapi.h"
|
||||
#include "util/message.h"
|
||||
|
||||
#include "packetdemodbaseband.h"
|
||||
#include "packetdemodsettings.h"
|
||||
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
class QThread;
|
||||
class DeviceAPI;
|
||||
|
||||
class PacketDemod : public BasebandSampleSink, public ChannelAPI {
|
||||
Q_OBJECT
|
||||
public:
|
||||
class MsgConfigurePacketDemod : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
const PacketDemodSettings& getSettings() const { return m_settings; }
|
||||
bool getForce() const { return m_force; }
|
||||
|
||||
static MsgConfigurePacketDemod* create(const PacketDemodSettings& settings, bool force)
|
||||
{
|
||||
return new MsgConfigurePacketDemod(settings, force);
|
||||
}
|
||||
|
||||
private:
|
||||
PacketDemodSettings m_settings;
|
||||
bool m_force;
|
||||
|
||||
MsgConfigurePacketDemod(const PacketDemodSettings& settings, bool force) :
|
||||
Message(),
|
||||
m_settings(settings),
|
||||
m_force(force)
|
||||
{ }
|
||||
};
|
||||
|
||||
PacketDemod(DeviceAPI *deviceAPI);
|
||||
virtual ~PacketDemod();
|
||||
virtual void destroy() { delete this; }
|
||||
|
||||
using BasebandSampleSink::feed;
|
||||
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end, bool po);
|
||||
virtual void start();
|
||||
virtual void stop();
|
||||
virtual bool handleMessage(const Message& cmd);
|
||||
|
||||
virtual void getIdentifier(QString& id) { id = objectName(); }
|
||||
virtual const QString& getURI() const { return getName(); }
|
||||
virtual void getTitle(QString& title) { title = m_settings.m_title; }
|
||||
virtual qint64 getCenterFrequency() const { return 0; }
|
||||
|
||||
virtual QByteArray serialize() const;
|
||||
virtual bool deserialize(const QByteArray& data);
|
||||
|
||||
virtual int getNbSinkStreams() const { return 1; }
|
||||
virtual int getNbSourceStreams() const { return 0; }
|
||||
|
||||
virtual qint64 getStreamCenterFrequency(int streamIndex, bool sinkElseSource) const
|
||||
{
|
||||
(void) streamIndex;
|
||||
(void) sinkElseSource;
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual int webapiSettingsGet(
|
||||
SWGSDRangel::SWGChannelSettings& response,
|
||||
QString& errorMessage);
|
||||
|
||||
virtual int webapiSettingsPutPatch(
|
||||
bool force,
|
||||
const QStringList& channelSettingsKeys,
|
||||
SWGSDRangel::SWGChannelSettings& response,
|
||||
QString& errorMessage);
|
||||
|
||||
static void webapiFormatChannelSettings(
|
||||
SWGSDRangel::SWGChannelSettings& response,
|
||||
const PacketDemodSettings& settings);
|
||||
|
||||
static void webapiUpdateChannelSettings(
|
||||
PacketDemodSettings& settings,
|
||||
const QStringList& channelSettingsKeys,
|
||||
SWGSDRangel::SWGChannelSettings& response);
|
||||
|
||||
double getMagSq() const { return m_basebandSink->getMagSq(); }
|
||||
|
||||
void getMagSqLevels(double& avg, double& peak, int& nbSamples) {
|
||||
m_basebandSink->getMagSqLevels(avg, peak, nbSamples);
|
||||
}
|
||||
/* void setMessageQueueToGUI(MessageQueue* queue) override {
|
||||
BasebandSampleSink::setMessageQueueToGUI(queue);
|
||||
m_basebandSink->setMessageQueueToGUI(queue);
|
||||
}*/
|
||||
|
||||
uint32_t getNumberOfDeviceStreams() const;
|
||||
|
||||
static const char * const m_channelIdURI;
|
||||
static const char * const m_channelId;
|
||||
|
||||
private:
|
||||
DeviceAPI *m_deviceAPI;
|
||||
QThread m_thread;
|
||||
PacketDemodBaseband* m_basebandSink;
|
||||
PacketDemodSettings m_settings;
|
||||
int m_basebandSampleRate; //!< stored from device message used when starting baseband sink
|
||||
qint64 m_centerFrequency;
|
||||
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QNetworkRequest m_networkRequest;
|
||||
|
||||
void applySettings(const PacketDemodSettings& settings, bool force = false);
|
||||
void webapiReverseSendSettings(QList<QString>& channelSettingsKeys, const PacketDemodSettings& settings, bool force);
|
||||
void webapiFormatChannelSettings(
|
||||
QList<QString>& channelSettingsKeys,
|
||||
SWGSDRangel::SWGChannelSettings *swgChannelSettings,
|
||||
const PacketDemodSettings& settings,
|
||||
bool force
|
||||
);
|
||||
|
||||
private slots:
|
||||
void networkManagerFinished(QNetworkReply *reply);
|
||||
|
||||
};
|
||||
|
||||
#endif // INCLUDE_PACKETDEMOD_H
|
170
plugins/channelrx/demodpacket/packetdemodbaseband.cpp
Normal file
@ -0,0 +1,170 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "dsp/downchannelizer.h"
|
||||
|
||||
#include "packetdemodbaseband.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(PacketDemodBaseband::MsgConfigurePacketDemodBaseband, Message)
|
||||
|
||||
PacketDemodBaseband::PacketDemodBaseband(PacketDemod *packetDemod) :
|
||||
m_sink(packetDemod),
|
||||
m_running(false),
|
||||
m_mutex(QMutex::Recursive)
|
||||
{
|
||||
qDebug("PacketDemodBaseband::PacketDemodBaseband");
|
||||
|
||||
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(48000));
|
||||
m_channelizer = new DownChannelizer(&m_sink);
|
||||
}
|
||||
|
||||
PacketDemodBaseband::~PacketDemodBaseband()
|
||||
{
|
||||
m_inputMessageQueue.clear();
|
||||
|
||||
delete m_channelizer;
|
||||
}
|
||||
|
||||
void PacketDemodBaseband::reset()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
m_inputMessageQueue.clear();
|
||||
m_sampleFifo.reset();
|
||||
}
|
||||
|
||||
void PacketDemodBaseband::startWork()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
QObject::connect(
|
||||
&m_sampleFifo,
|
||||
&SampleSinkFifo::dataReady,
|
||||
this,
|
||||
&PacketDemodBaseband::handleData,
|
||||
Qt::QueuedConnection
|
||||
);
|
||||
connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
|
||||
m_running = true;
|
||||
}
|
||||
|
||||
void PacketDemodBaseband::stopWork()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
disconnect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
|
||||
QObject::disconnect(
|
||||
&m_sampleFifo,
|
||||
&SampleSinkFifo::dataReady,
|
||||
this,
|
||||
&PacketDemodBaseband::handleData
|
||||
);
|
||||
m_running = false;
|
||||
}
|
||||
|
||||
void PacketDemodBaseband::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
|
||||
{
|
||||
m_sampleFifo.write(begin, end);
|
||||
}
|
||||
|
||||
void PacketDemodBaseband::handleData()
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
|
||||
while ((m_sampleFifo.fill() > 0) && (m_inputMessageQueue.size() == 0))
|
||||
{
|
||||
SampleVector::iterator part1begin;
|
||||
SampleVector::iterator part1end;
|
||||
SampleVector::iterator part2begin;
|
||||
SampleVector::iterator part2end;
|
||||
|
||||
std::size_t count = m_sampleFifo.readBegin(m_sampleFifo.fill(), &part1begin, &part1end, &part2begin, &part2end);
|
||||
|
||||
// first part of FIFO data
|
||||
if (part1begin != part1end) {
|
||||
m_channelizer->feed(part1begin, part1end);
|
||||
}
|
||||
|
||||
// second part of FIFO data (used when block wraps around)
|
||||
if(part2begin != part2end) {
|
||||
m_channelizer->feed(part2begin, part2end);
|
||||
}
|
||||
|
||||
m_sampleFifo.readCommit((unsigned int) count);
|
||||
}
|
||||
}
|
||||
|
||||
void PacketDemodBaseband::handleInputMessages()
|
||||
{
|
||||
Message* message;
|
||||
|
||||
while ((message = m_inputMessageQueue.pop()) != nullptr)
|
||||
{
|
||||
if (handleMessage(*message)) {
|
||||
delete message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PacketDemodBaseband::handleMessage(const Message& cmd)
|
||||
{
|
||||
if (MsgConfigurePacketDemodBaseband::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
MsgConfigurePacketDemodBaseband& cfg = (MsgConfigurePacketDemodBaseband&) cmd;
|
||||
qDebug() << "PacketDemodBaseband::handleMessage: MsgConfigurePacketDemodBaseband";
|
||||
|
||||
applySettings(cfg.getSettings(), cfg.getForce());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (DSPSignalNotification::match(cmd))
|
||||
{
|
||||
QMutexLocker mutexLocker(&m_mutex);
|
||||
DSPSignalNotification& notif = (DSPSignalNotification&) cmd;
|
||||
qDebug() << "PacketDemodBaseband::handleMessage: DSPSignalNotification: basebandSampleRate: " << notif.getSampleRate();
|
||||
setBasebandSampleRate(notif.getSampleRate());
|
||||
m_sampleFifo.setSize(SampleSinkFifo::getSizePolicy(notif.getSampleRate()));
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void PacketDemodBaseband::applySettings(const PacketDemodSettings& settings, bool force)
|
||||
{
|
||||
if ((settings.m_inputFrequencyOffset != m_settings.m_inputFrequencyOffset) || force)
|
||||
{
|
||||
m_channelizer->setChannelization(PACKETDEMOD_CHANNEL_SAMPLE_RATE, settings.m_inputFrequencyOffset);
|
||||
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
|
||||
}
|
||||
|
||||
m_sink.applySettings(settings, force);
|
||||
|
||||
m_settings = settings;
|
||||
}
|
||||
|
||||
void PacketDemodBaseband::setBasebandSampleRate(int sampleRate)
|
||||
{
|
||||
m_channelizer->setBasebandSampleRate(sampleRate);
|
||||
m_sink.applyChannelSettings(m_channelizer->getChannelSampleRate(), m_channelizer->getChannelFrequencyOffset());
|
||||
}
|
94
plugins/channelrx/demodpacket/packetdemodbaseband.h
Normal file
@ -0,0 +1,94 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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_PACKETDEMODBASEBAND_H
|
||||
#define INCLUDE_PACKETDEMODBASEBAND_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QMutex>
|
||||
|
||||
#include "dsp/samplesinkfifo.h"
|
||||
#include "util/message.h"
|
||||
#include "util/messagequeue.h"
|
||||
|
||||
#include "packetdemodsink.h"
|
||||
|
||||
class DownChannelizer;
|
||||
class PacketDemod;
|
||||
|
||||
class PacketDemodBaseband : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
class MsgConfigurePacketDemodBaseband : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
const PacketDemodSettings& getSettings() const { return m_settings; }
|
||||
bool getForce() const { return m_force; }
|
||||
|
||||
static MsgConfigurePacketDemodBaseband* create(const PacketDemodSettings& settings, bool force)
|
||||
{
|
||||
return new MsgConfigurePacketDemodBaseband(settings, force);
|
||||
}
|
||||
|
||||
private:
|
||||
PacketDemodSettings m_settings;
|
||||
bool m_force;
|
||||
|
||||
MsgConfigurePacketDemodBaseband(const PacketDemodSettings& settings, bool force) :
|
||||
Message(),
|
||||
m_settings(settings),
|
||||
m_force(force)
|
||||
{ }
|
||||
};
|
||||
|
||||
PacketDemodBaseband(PacketDemod *packetDemod);
|
||||
~PacketDemodBaseband();
|
||||
void reset();
|
||||
void startWork();
|
||||
void stopWork();
|
||||
void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
|
||||
MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; } //!< Get the queue for asynchronous inbound communication
|
||||
void getMagSqLevels(double& avg, double& peak, int& nbSamples) {
|
||||
m_sink.getMagSqLevels(avg, peak, nbSamples);
|
||||
}
|
||||
void setMessageQueueToChannel(MessageQueue *messageQueue) { m_sink.setMessageQueueToChannel(messageQueue); }
|
||||
void setBasebandSampleRate(int sampleRate);
|
||||
double getMagSq() const { return m_sink.getMagSq(); }
|
||||
bool isRunning() const { return m_running; }
|
||||
|
||||
private:
|
||||
SampleSinkFifo m_sampleFifo;
|
||||
DownChannelizer *m_channelizer;
|
||||
PacketDemodSink m_sink;
|
||||
MessageQueue m_inputMessageQueue; //!< Queue for asynchronous inbound communication
|
||||
PacketDemodSettings m_settings;
|
||||
bool m_running;
|
||||
QMutex m_mutex;
|
||||
|
||||
bool handleMessage(const Message& cmd);
|
||||
void calculateOffset(PacketDemodSink *sink);
|
||||
void applySettings(const PacketDemodSettings& settings, bool force = false);
|
||||
|
||||
private slots:
|
||||
void handleInputMessages();
|
||||
void handleData(); //!< Handle data when samples have to be processed
|
||||
};
|
||||
|
||||
#endif // INCLUDE_PACKETDEMODBASEBAND_H
|
560
plugins/channelrx/demodpacket/packetdemodgui.cpp
Normal file
@ -0,0 +1,560 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2016 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/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <limits>
|
||||
#include <ctype.h>
|
||||
#include <QDockWidget>
|
||||
#include <QMainWindow>
|
||||
#include <QDebug>
|
||||
#include <QQuickItem>
|
||||
#include <QGeoLocation>
|
||||
#include <QGeoCoordinate>
|
||||
#include <QQmlContext>
|
||||
#include <QMessageBox>
|
||||
#include <QAction>
|
||||
#include <QRegExp>
|
||||
|
||||
#include "packetdemodgui.h"
|
||||
#include "util/ax25.h"
|
||||
|
||||
#include "device/deviceuiset.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/dspcommands.h"
|
||||
#include "ui_packetdemodgui.h"
|
||||
#include "plugin/pluginapi.h"
|
||||
#include "util/simpleserializer.h"
|
||||
#include "util/db.h"
|
||||
#include "util/morse.h"
|
||||
#include "util/units.h"
|
||||
#include "gui/basicchannelsettingsdialog.h"
|
||||
#include "gui/devicestreamselectiondialog.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "gui/crightclickenabler.h"
|
||||
#include "channel/channelwebapiutils.h"
|
||||
#include "maincore.h"
|
||||
|
||||
#include "packetdemod.h"
|
||||
#include "packetdemodsink.h"
|
||||
|
||||
#define PACKET_COL_FROM 0
|
||||
#define PACKET_COL_TO 1
|
||||
#define PACKET_COL_VIA 2
|
||||
#define PACKET_COL_TYPE 3
|
||||
#define PACKET_COL_PID 4
|
||||
#define PACKET_COL_DATA_ASCII 5
|
||||
#define PACKET_COL_DATA_HEX 6
|
||||
|
||||
void PacketDemodGUI::resizeTable()
|
||||
{
|
||||
// Fill table with a row of dummy data that will size the columns nicely
|
||||
// Trailing spaces are for sort arrow
|
||||
int row = ui->packets->rowCount();
|
||||
ui->packets->setRowCount(row + 1);
|
||||
ui->packets->setItem(row, PACKET_COL_FROM, new QTableWidgetItem("123456-15-"));
|
||||
ui->packets->setItem(row, PACKET_COL_TO, new QTableWidgetItem("123456-15-"));
|
||||
ui->packets->setItem(row, PACKET_COL_VIA, new QTableWidgetItem("123456-15-"));
|
||||
ui->packets->setItem(row, PACKET_COL_TYPE, new QTableWidgetItem("Type-"));
|
||||
ui->packets->setItem(row, PACKET_COL_PID, new QTableWidgetItem("PID-"));
|
||||
ui->packets->setItem(row, PACKET_COL_DATA_ASCII, new QTableWidgetItem("ABCEDGHIJKLMNOPQRSTUVWXYZ"));
|
||||
ui->packets->setItem(row, PACKET_COL_DATA_HEX, new QTableWidgetItem("ABCEDGHIJKLMNOPQRSTUVWXYZ"));
|
||||
ui->packets->resizeColumnsToContents();
|
||||
ui->packets->removeRow(row);
|
||||
}
|
||||
|
||||
// Columns in table reordered
|
||||
void PacketDemodGUI::packets_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex)
|
||||
{
|
||||
(void) oldVisualIndex;
|
||||
|
||||
m_settings.m_columnIndexes[logicalIndex] = newVisualIndex;
|
||||
}
|
||||
|
||||
// Column in table resized (when hidden size is 0)
|
||||
void PacketDemodGUI::packets_sectionResized(int logicalIndex, int oldSize, int newSize)
|
||||
{
|
||||
(void) oldSize;
|
||||
|
||||
m_settings.m_columnSizes[logicalIndex] = newSize;
|
||||
}
|
||||
|
||||
// Right click in table header - show column select menu
|
||||
void PacketDemodGUI::columnSelectMenu(QPoint pos)
|
||||
{
|
||||
menu->popup(ui->packets->horizontalHeader()->viewport()->mapToGlobal(pos));
|
||||
}
|
||||
|
||||
// Hide/show column when menu selected
|
||||
void PacketDemodGUI::columnSelectMenuChecked(bool checked)
|
||||
{
|
||||
(void) checked;
|
||||
|
||||
QAction* action = qobject_cast<QAction*>(sender());
|
||||
if (action != nullptr)
|
||||
{
|
||||
int idx = action->data().toInt(nullptr);
|
||||
ui->packets->setColumnHidden(idx, !action->isChecked());
|
||||
}
|
||||
}
|
||||
|
||||
// Create column select menu item
|
||||
QAction *PacketDemodGUI::createCheckableItem(QString &text, int idx, bool checked)
|
||||
{
|
||||
QAction *action = new QAction(text, this);
|
||||
action->setCheckable(true);
|
||||
action->setChecked(checked);
|
||||
action->setData(QVariant(idx));
|
||||
connect(action, SIGNAL(triggered()), this, SLOT(columnSelectMenuChecked()));
|
||||
return action;
|
||||
}
|
||||
|
||||
PacketDemodGUI* PacketDemodGUI::create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel)
|
||||
{
|
||||
PacketDemodGUI* gui = new PacketDemodGUI(pluginAPI, deviceUISet, rxChannel);
|
||||
return gui;
|
||||
}
|
||||
|
||||
void PacketDemodGUI::destroy()
|
||||
{
|
||||
delete this;
|
||||
}
|
||||
|
||||
void PacketDemodGUI::resetToDefaults()
|
||||
{
|
||||
m_settings.resetToDefaults();
|
||||
displaySettings();
|
||||
applySettings(true);
|
||||
}
|
||||
|
||||
QByteArray PacketDemodGUI::serialize() const
|
||||
{
|
||||
return m_settings.serialize();
|
||||
}
|
||||
|
||||
bool PacketDemodGUI::deserialize(const QByteArray& data)
|
||||
{
|
||||
if(m_settings.deserialize(data)) {
|
||||
displaySettings();
|
||||
applySettings(true);
|
||||
return true;
|
||||
} else {
|
||||
resetToDefaults();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Add row to table
|
||||
void PacketDemodGUI::packetReceived(QByteArray packet)
|
||||
{
|
||||
AX25Packet ax25;
|
||||
|
||||
if (ax25.decode(packet))
|
||||
{
|
||||
ui->packets->setSortingEnabled(false);
|
||||
int row = ui->packets->rowCount();
|
||||
ui->packets->setRowCount(row + 1);
|
||||
|
||||
QTableWidgetItem *fromItem = new QTableWidgetItem();
|
||||
QTableWidgetItem *toItem = new QTableWidgetItem();
|
||||
QTableWidgetItem *viaItem = new QTableWidgetItem();
|
||||
QTableWidgetItem *typeItem = new QTableWidgetItem();
|
||||
QTableWidgetItem *pidItem = new QTableWidgetItem();
|
||||
QTableWidgetItem *dataASCIIItem = new QTableWidgetItem();
|
||||
QTableWidgetItem *dataHexItem = new QTableWidgetItem();
|
||||
ui->packets->setItem(row, PACKET_COL_FROM, fromItem);
|
||||
ui->packets->setItem(row, PACKET_COL_TO, toItem);
|
||||
ui->packets->setItem(row, PACKET_COL_VIA, viaItem);
|
||||
ui->packets->setItem(row, PACKET_COL_TYPE, typeItem);
|
||||
ui->packets->setItem(row, PACKET_COL_PID, pidItem);
|
||||
ui->packets->setItem(row, PACKET_COL_DATA_ASCII, dataASCIIItem);
|
||||
ui->packets->setItem(row, PACKET_COL_DATA_HEX, dataHexItem);
|
||||
fromItem->setText(ax25.m_from);
|
||||
toItem->setText(ax25.m_to);
|
||||
viaItem->setText(ax25.m_via);
|
||||
typeItem->setText(ax25.m_type);
|
||||
pidItem->setText(ax25.m_pid);
|
||||
dataASCIIItem->setText(ax25.m_dataASCII);
|
||||
dataHexItem->setText(ax25.m_dataHex);
|
||||
ui->packets->setSortingEnabled(true);
|
||||
ui->packets->scrollToItem(fromItem);
|
||||
filterRow(row);
|
||||
}
|
||||
else
|
||||
qDebug() << "Unsupported AX.25 packet: " << packet;
|
||||
}
|
||||
|
||||
bool PacketDemodGUI::handleMessage(const Message& message)
|
||||
{
|
||||
if (PacketDemod::MsgConfigurePacketDemod::match(message))
|
||||
{
|
||||
qDebug("PacketDemodGUI::handleMessage: PacketDemod::MsgConfigurePacketDemod");
|
||||
const PacketDemod::MsgConfigurePacketDemod& cfg = (PacketDemod::MsgConfigurePacketDemod&) message;
|
||||
m_settings = cfg.getSettings();
|
||||
blockApplySettings(true);
|
||||
displaySettings();
|
||||
blockApplySettings(false);
|
||||
return true;
|
||||
}
|
||||
else if (DSPSignalNotification::match(message))
|
||||
{
|
||||
DSPSignalNotification& notif = (DSPSignalNotification&) message;
|
||||
m_basebandSampleRate = notif.getSampleRate();
|
||||
return true;
|
||||
}
|
||||
else if (MainCore::MsgPacket::match(message))
|
||||
{
|
||||
MainCore::MsgPacket& report = (MainCore::MsgPacket&) message;
|
||||
packetReceived(report.getPacket());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void PacketDemodGUI::handleInputMessages()
|
||||
{
|
||||
Message* message;
|
||||
|
||||
while ((message = getInputMessageQueue()->pop()) != 0)
|
||||
{
|
||||
if (handleMessage(*message))
|
||||
{
|
||||
delete message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PacketDemodGUI::channelMarkerChangedByCursor()
|
||||
{
|
||||
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
|
||||
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
|
||||
applySettings();
|
||||
}
|
||||
|
||||
void PacketDemodGUI::channelMarkerHighlightedByCursor()
|
||||
{
|
||||
setHighlighted(m_channelMarker.getHighlighted());
|
||||
}
|
||||
|
||||
void PacketDemodGUI::on_deltaFrequency_changed(qint64 value)
|
||||
{
|
||||
m_channelMarker.setCenterFrequency(value);
|
||||
m_settings.m_inputFrequencyOffset = m_channelMarker.getCenterFrequency();
|
||||
applySettings();
|
||||
}
|
||||
|
||||
void PacketDemodGUI::on_mode_currentIndexChanged(int value)
|
||||
{
|
||||
(void) value;
|
||||
QString mode = ui->mode->currentText();
|
||||
// TODO: Support 9600 FSK
|
||||
}
|
||||
|
||||
void PacketDemodGUI::on_rfBW_valueChanged(int value)
|
||||
{
|
||||
float bw = value * 100.0f;
|
||||
ui->rfBWText->setText(QString("%1k").arg(value / 10.0, 0, 'f', 1));
|
||||
m_channelMarker.setBandwidth(bw);
|
||||
m_settings.m_rfBandwidth = bw;
|
||||
applySettings();
|
||||
}
|
||||
|
||||
void PacketDemodGUI::on_fmDev_valueChanged(int value)
|
||||
{
|
||||
ui->fmDevText->setText(QString("%1k").arg(value / 10.0, 0, 'f', 1));
|
||||
m_settings.m_fmDeviation = value * 100.0;
|
||||
applySettings();
|
||||
}
|
||||
|
||||
void PacketDemodGUI::on_filterFrom_editingFinished()
|
||||
{
|
||||
m_settings.m_filterFrom = ui->filterFrom->text();
|
||||
filter();
|
||||
applySettings();
|
||||
}
|
||||
|
||||
void PacketDemodGUI::on_filterTo_editingFinished()
|
||||
{
|
||||
m_settings.m_filterTo = ui->filterTo->text();
|
||||
filter();
|
||||
applySettings();
|
||||
}
|
||||
|
||||
void PacketDemodGUI::on_filterPID_stateChanged(int state)
|
||||
{
|
||||
m_settings.m_filterPID = state==Qt::Checked ? "f0" : "";
|
||||
filter();
|
||||
applySettings();
|
||||
}
|
||||
|
||||
void PacketDemodGUI::on_clearTable_clicked()
|
||||
{
|
||||
ui->packets->setRowCount(0);
|
||||
}
|
||||
|
||||
void PacketDemodGUI::filterRow(int row)
|
||||
{
|
||||
bool hidden = false;
|
||||
if (m_settings.m_filterFrom != "")
|
||||
{
|
||||
QRegExp re(m_settings.m_filterFrom);
|
||||
QTableWidgetItem *fromItem = ui->packets->item(row, PACKET_COL_FROM);
|
||||
if (!re.exactMatch(fromItem->text()))
|
||||
hidden = true;
|
||||
}
|
||||
if (m_settings.m_filterTo != "")
|
||||
{
|
||||
QRegExp re(m_settings.m_filterTo);
|
||||
QTableWidgetItem *toItem = ui->packets->item(row, PACKET_COL_TO);
|
||||
if (!re.exactMatch(toItem->text()))
|
||||
hidden = true;
|
||||
}
|
||||
if (m_settings.m_filterPID != "")
|
||||
{
|
||||
QTableWidgetItem *pidItem = ui->packets->item(row, PACKET_COL_PID);
|
||||
if (pidItem->text() != m_settings.m_filterPID)
|
||||
hidden = true;
|
||||
}
|
||||
ui->packets->setRowHidden(row, hidden);
|
||||
}
|
||||
|
||||
void PacketDemodGUI::filter()
|
||||
{
|
||||
for (int i = 0; i < ui->packets->rowCount(); i++)
|
||||
{
|
||||
filterRow(i);
|
||||
}
|
||||
}
|
||||
|
||||
void PacketDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown)
|
||||
{
|
||||
(void) widget;
|
||||
(void) rollDown;
|
||||
}
|
||||
|
||||
void PacketDemodGUI::onMenuDialogCalled(const QPoint &p)
|
||||
{
|
||||
if (m_contextMenuType == ContextMenuChannelSettings)
|
||||
{
|
||||
BasicChannelSettingsDialog dialog(&m_channelMarker, this);
|
||||
dialog.setUseReverseAPI(m_settings.m_useReverseAPI);
|
||||
dialog.setReverseAPIAddress(m_settings.m_reverseAPIAddress);
|
||||
dialog.setReverseAPIPort(m_settings.m_reverseAPIPort);
|
||||
dialog.setReverseAPIDeviceIndex(m_settings.m_reverseAPIDeviceIndex);
|
||||
dialog.setReverseAPIChannelIndex(m_settings.m_reverseAPIChannelIndex);
|
||||
dialog.move(p);
|
||||
dialog.exec();
|
||||
|
||||
m_settings.m_rgbColor = m_channelMarker.getColor().rgb();
|
||||
m_settings.m_title = m_channelMarker.getTitle();
|
||||
m_settings.m_useReverseAPI = dialog.useReverseAPI();
|
||||
m_settings.m_reverseAPIAddress = dialog.getReverseAPIAddress();
|
||||
m_settings.m_reverseAPIPort = dialog.getReverseAPIPort();
|
||||
m_settings.m_reverseAPIDeviceIndex = dialog.getReverseAPIDeviceIndex();
|
||||
m_settings.m_reverseAPIChannelIndex = dialog.getReverseAPIChannelIndex();
|
||||
|
||||
setWindowTitle(m_settings.m_title);
|
||||
setTitleColor(m_settings.m_rgbColor);
|
||||
|
||||
applySettings();
|
||||
}
|
||||
else if ((m_contextMenuType == ContextMenuStreamSettings) && (m_deviceUISet->m_deviceMIMOEngine))
|
||||
{
|
||||
DeviceStreamSelectionDialog dialog(this);
|
||||
dialog.setNumberOfStreams(m_packetDemod->getNumberOfDeviceStreams());
|
||||
dialog.setStreamIndex(m_settings.m_streamIndex);
|
||||
dialog.move(p);
|
||||
dialog.exec();
|
||||
|
||||
m_settings.m_streamIndex = dialog.getSelectedStreamIndex();
|
||||
m_channelMarker.clearStreamIndexes();
|
||||
m_channelMarker.addStreamIndex(m_settings.m_streamIndex);
|
||||
displayStreamIndex();
|
||||
applySettings();
|
||||
}
|
||||
|
||||
resetContextMenuType();
|
||||
}
|
||||
|
||||
PacketDemodGUI::PacketDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent) :
|
||||
ChannelGUI(parent),
|
||||
ui(new Ui::PacketDemodGUI),
|
||||
m_pluginAPI(pluginAPI),
|
||||
m_deviceUISet(deviceUISet),
|
||||
m_channelMarker(this),
|
||||
m_doApplySettings(true),
|
||||
m_tickCount(0)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
setAttribute(Qt::WA_DeleteOnClose, true);
|
||||
connect(this, SIGNAL(widgetRolled(QWidget*,bool)), this, SLOT(onWidgetRolled(QWidget*,bool)));
|
||||
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)), this, SLOT(onMenuDialogCalled(const QPoint &)));
|
||||
|
||||
m_packetDemod = reinterpret_cast<PacketDemod*>(rxChannel);
|
||||
m_packetDemod->setMessageQueueToGUI(getInputMessageQueue());
|
||||
|
||||
connect(&MainCore::instance()->getMasterTimer(), SIGNAL(timeout()), this, SLOT(tick())); // 50 ms
|
||||
|
||||
ui->deltaFrequencyLabel->setText(QString("%1f").arg(QChar(0x94, 0x03)));
|
||||
ui->deltaFrequency->setColorMapper(ColorMapper(ColorMapper::GrayGold));
|
||||
ui->deltaFrequency->setValueRange(false, 7, -9999999, 9999999);
|
||||
ui->channelPowerMeter->setColorTheme(LevelMeterSignalDB::ColorGreenAndBlue);
|
||||
|
||||
m_channelMarker.blockSignals(true);
|
||||
m_channelMarker.setColor(Qt::yellow);
|
||||
m_channelMarker.setBandwidth(m_settings.m_rfBandwidth);
|
||||
m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset);
|
||||
m_channelMarker.setTitle("Packet Demodulator");
|
||||
m_channelMarker.blockSignals(false);
|
||||
m_channelMarker.setVisible(true); // activate signal on the last setting only
|
||||
|
||||
setTitleColor(m_channelMarker.getColor());
|
||||
m_settings.setChannelMarker(&m_channelMarker);
|
||||
|
||||
m_deviceUISet->addChannelMarker(&m_channelMarker);
|
||||
m_deviceUISet->addRollupWidget(this);
|
||||
|
||||
connect(&m_channelMarker, SIGNAL(changedByCursor()), this, SLOT(channelMarkerChangedByCursor()));
|
||||
connect(&m_channelMarker, SIGNAL(highlightedByCursor()), this, SLOT(channelMarkerHighlightedByCursor()));
|
||||
connect(getInputMessageQueue(), SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
|
||||
|
||||
// Resize the table using dummy data
|
||||
resizeTable();
|
||||
// Allow user to reorder columns
|
||||
ui->packets->horizontalHeader()->setSectionsMovable(true);
|
||||
// Allow user to sort table by clicking on headers
|
||||
ui->packets->setSortingEnabled(true);
|
||||
// Add context menu to allow hiding/showing of columns
|
||||
menu = new QMenu(ui->packets);
|
||||
for (int i = 0; i < ui->packets->horizontalHeader()->count(); i++)
|
||||
{
|
||||
QString text = ui->packets->horizontalHeaderItem(i)->text();
|
||||
menu->addAction(createCheckableItem(text, i, true));
|
||||
}
|
||||
ui->packets->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(ui->packets->horizontalHeader(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(columnSelectMenu(QPoint)));
|
||||
// Get signals when columns change
|
||||
connect(ui->packets->horizontalHeader(), SIGNAL(sectionMoved(int, int, int)), SLOT(packets_sectionMoved(int, int, int)));
|
||||
connect(ui->packets->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), SLOT(packets_sectionResized(int, int, int)));
|
||||
|
||||
displaySettings();
|
||||
applySettings(true);
|
||||
}
|
||||
|
||||
PacketDemodGUI::~PacketDemodGUI()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void PacketDemodGUI::blockApplySettings(bool block)
|
||||
{
|
||||
m_doApplySettings = !block;
|
||||
}
|
||||
|
||||
void PacketDemodGUI::applySettings(bool force)
|
||||
{
|
||||
if (m_doApplySettings)
|
||||
{
|
||||
PacketDemod::MsgConfigurePacketDemod* message = PacketDemod::MsgConfigurePacketDemod::create( m_settings, force);
|
||||
m_packetDemod->getInputMessageQueue()->push(message);
|
||||
}
|
||||
}
|
||||
|
||||
void PacketDemodGUI::displaySettings()
|
||||
{
|
||||
m_channelMarker.blockSignals(true);
|
||||
m_channelMarker.setBandwidth(m_settings.m_rfBandwidth);
|
||||
m_channelMarker.setCenterFrequency(m_settings.m_inputFrequencyOffset);
|
||||
m_channelMarker.setTitle(m_settings.m_title);
|
||||
m_channelMarker.blockSignals(false);
|
||||
m_channelMarker.setColor(m_settings.m_rgbColor); // activate signal on the last setting only
|
||||
|
||||
setTitleColor(m_settings.m_rgbColor);
|
||||
setWindowTitle(m_channelMarker.getTitle());
|
||||
|
||||
blockApplySettings(true);
|
||||
|
||||
ui->deltaFrequency->setValue(m_channelMarker.getCenterFrequency());
|
||||
|
||||
ui->rfBWText->setText(QString("%1k").arg(m_settings.m_rfBandwidth / 1000.0, 0, 'f', 1));
|
||||
ui->rfBW->setValue(m_settings.m_rfBandwidth / 100.0);
|
||||
|
||||
ui->fmDevText->setText(QString("%1k").arg(m_settings.m_fmDeviation / 1000.0, 0, 'f', 1));
|
||||
ui->fmDev->setValue(m_settings.m_fmDeviation / 100.0);
|
||||
|
||||
displayStreamIndex();
|
||||
|
||||
ui->filterFrom->setText(m_settings.m_filterFrom);
|
||||
ui->filterTo->setText(m_settings.m_filterTo);
|
||||
ui->filterPID->setChecked(m_settings.m_filterPID == "f0");
|
||||
|
||||
// Order and size columns
|
||||
QHeaderView *header = ui->packets->horizontalHeader();
|
||||
for (int i = 0; i < PACKETDEMOD_COLUMNS; i++)
|
||||
{
|
||||
bool hidden = m_settings.m_columnSizes[i] == 0;
|
||||
header->setSectionHidden(i, hidden);
|
||||
menu->actions().at(i)->setChecked(!hidden);
|
||||
if (m_settings.m_columnSizes[i] > 0)
|
||||
ui->packets->setColumnWidth(i, m_settings.m_columnSizes[i]);
|
||||
header->moveSection(header->visualIndex(i), m_settings.m_columnIndexes[i]);
|
||||
}
|
||||
|
||||
filter();
|
||||
|
||||
blockApplySettings(false);
|
||||
}
|
||||
|
||||
void PacketDemodGUI::displayStreamIndex()
|
||||
{
|
||||
if (m_deviceUISet->m_deviceMIMOEngine) {
|
||||
setStreamIndicator(tr("%1").arg(m_settings.m_streamIndex));
|
||||
} else {
|
||||
setStreamIndicator("S"); // single channel indicator
|
||||
}
|
||||
}
|
||||
|
||||
void PacketDemodGUI::leaveEvent(QEvent*)
|
||||
{
|
||||
m_channelMarker.setHighlighted(false);
|
||||
}
|
||||
|
||||
void PacketDemodGUI::enterEvent(QEvent*)
|
||||
{
|
||||
m_channelMarker.setHighlighted(true);
|
||||
}
|
||||
|
||||
void PacketDemodGUI::tick()
|
||||
{
|
||||
double magsqAvg, magsqPeak;
|
||||
int nbMagsqSamples;
|
||||
m_packetDemod->getMagSqLevels(magsqAvg, magsqPeak, nbMagsqSamples);
|
||||
double powDbAvg = CalcDb::dbPower(magsqAvg);
|
||||
double powDbPeak = CalcDb::dbPower(magsqPeak);
|
||||
|
||||
ui->channelPowerMeter->levelChanged(
|
||||
(100.0f + powDbAvg) / 100.0f,
|
||||
(100.0f + powDbPeak) / 100.0f,
|
||||
nbMagsqSamples);
|
||||
|
||||
if (m_tickCount % 4 == 0) {
|
||||
ui->channelPower->setText(QString::number(powDbAvg, 'f', 1));
|
||||
}
|
||||
|
||||
m_tickCount++;
|
||||
}
|
119
plugins/channelrx/demodpacket/packetdemodgui.h
Normal file
@ -0,0 +1,119 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2016 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_PACKETDEMODGUI_H
|
||||
#define INCLUDE_PACKETDEMODGUI_H
|
||||
|
||||
#include <QIcon>
|
||||
#include <QAbstractListModel>
|
||||
#include <QModelIndex>
|
||||
#include <QProgressDialog>
|
||||
#include <QGeoLocation>
|
||||
#include <QGeoCoordinate>
|
||||
#include <QTableWidgetItem>
|
||||
#include <QPushButton>
|
||||
#include <QToolButton>
|
||||
#include <QHBoxLayout>
|
||||
#include <QMenu>
|
||||
|
||||
#include "channel/channelgui.h"
|
||||
#include "dsp/channelmarker.h"
|
||||
#include "dsp/movingaverage.h"
|
||||
#include "util/messagequeue.h"
|
||||
#include "packetdemodsettings.h"
|
||||
|
||||
class PluginAPI;
|
||||
class DeviceUISet;
|
||||
class BasebandSampleSink;
|
||||
class PacketDemod;
|
||||
class PacketDemodGUI;
|
||||
|
||||
namespace Ui {
|
||||
class PacketDemodGUI;
|
||||
}
|
||||
class PacketDemodGUI;
|
||||
|
||||
class PacketDemodGUI : public ChannelGUI {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static PacketDemodGUI* create(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel);
|
||||
virtual void destroy();
|
||||
|
||||
void resetToDefaults();
|
||||
QByteArray serialize() const;
|
||||
bool deserialize(const QByteArray& data);
|
||||
virtual MessageQueue *getInputMessageQueue() { return &m_inputMessageQueue; }
|
||||
|
||||
public slots:
|
||||
void channelMarkerChangedByCursor();
|
||||
void channelMarkerHighlightedByCursor();
|
||||
|
||||
private:
|
||||
Ui::PacketDemodGUI* ui;
|
||||
PluginAPI* m_pluginAPI;
|
||||
DeviceUISet* m_deviceUISet;
|
||||
ChannelMarker m_channelMarker;
|
||||
PacketDemodSettings m_settings;
|
||||
bool m_doApplySettings;
|
||||
|
||||
PacketDemod* m_packetDemod;
|
||||
int m_basebandSampleRate;
|
||||
uint32_t m_tickCount;
|
||||
MessageQueue m_inputMessageQueue;
|
||||
|
||||
QMenu *menu; // Column select context menu
|
||||
|
||||
explicit PacketDemodGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel, QWidget* parent = 0);
|
||||
virtual ~PacketDemodGUI();
|
||||
|
||||
void blockApplySettings(bool block);
|
||||
void applySettings(bool force = false);
|
||||
void displaySettings();
|
||||
void displayStreamIndex();
|
||||
void packetReceived(QByteArray packet);
|
||||
bool handleMessage(const Message& message);
|
||||
|
||||
void leaveEvent(QEvent*);
|
||||
void enterEvent(QEvent*);
|
||||
|
||||
void resizeTable();
|
||||
QAction *createCheckableItem(QString& text, int idx, bool checked);
|
||||
|
||||
private slots:
|
||||
void on_deltaFrequency_changed(qint64 value);
|
||||
void on_mode_currentIndexChanged(int value);
|
||||
void on_rfBW_valueChanged(int index);
|
||||
void on_fmDev_valueChanged(int value);
|
||||
void on_filterFrom_editingFinished();
|
||||
void on_filterTo_editingFinished();
|
||||
void on_filterPID_stateChanged(int state);
|
||||
void on_clearTable_clicked();
|
||||
void filterRow(int row);
|
||||
void filter();
|
||||
void packets_sectionMoved(int logicalIndex, int oldVisualIndex, int newVisualIndex);
|
||||
void packets_sectionResized(int logicalIndex, int oldSize, int newSize);
|
||||
void columnSelectMenu(QPoint pos);
|
||||
void columnSelectMenuChecked(bool checked = false);
|
||||
void onWidgetRolled(QWidget* widget, bool rollDown);
|
||||
void onMenuDialogCalled(const QPoint& p);
|
||||
void handleInputMessages();
|
||||
void tick();
|
||||
};
|
||||
|
||||
#endif // INCLUDE_PACKETDEMODGUI_H
|
579
plugins/channelrx/demodpacket/packetdemodgui.ui
Normal file
@ -0,0 +1,579 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>PacketDemodGUI</class>
|
||||
<widget class="RollupWidget" name="PacketDemodGUI">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>398</width>
|
||||
<height>519</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>352</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Liberation Sans</family>
|
||||
<pointsize>9</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Packet Demodulator</string>
|
||||
</property>
|
||||
<property name="statusTip">
|
||||
<string>Packet Demodulator</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="settingsContainer" native="true">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>390</width>
|
||||
<height>101</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>350</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</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="QHBoxLayout" name="powLayout">
|
||||
<property name="topMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="deltaFrequencyLabel">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Df</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="ValueDialZ" name="deltaFrequency" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>32</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Liberation Mono</family>
|
||||
<pointsize>12</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="cursor">
|
||||
<cursorShape>PointingHandCursor</cursorShape>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::StrongFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Demod shift frequency from center in Hz</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="deltaUnits">
|
||||
<property name="text">
|
||||
<string>Hz </string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="channelPowerLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="channelPower">
|
||||
<property name="toolTip">
|
||||
<string>Channel power</string>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::RightToLeft</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>0.0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="channelPowerUnits">
|
||||
<property name="text">
|
||||
<string> dB</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="buttonLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="channelPowerMeterUnits">
|
||||
<property name="text">
|
||||
<string>dB</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="LevelMeterSignalDB" name="channelPowerMeter" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Liberation Mono</family>
|
||||
<pointsize>8</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Level meter (dB) top trace: average, bottom trace: instantaneous peak, tip: peak hold</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QComboBox" name="mode">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Baud rate and modulation</string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>1200 AFSK</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<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="Line" name="line_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="rfBWLabel">
|
||||
<property name="text">
|
||||
<string>BW</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="rfBW">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>RF bandwidth</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>400</number>
|
||||
</property>
|
||||
<property name="pageStep">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="rfBWText">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>10.0k</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="fmDevLabel">
|
||||
<property name="text">
|
||||
<string>Dev</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSlider" name="fmDev">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Minimum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Frequency deviation</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>60</number>
|
||||
</property>
|
||||
<property name="pageStep">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>50</number>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="fmDevText">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>30</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>5.0k</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="filterFromLabel">
|
||||
<property name="text">
|
||||
<string>From</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="filterFrom">
|
||||
<property name="toolTip">
|
||||
<string>Display only packets where the source address (From) matches the specified regular expression</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="filterToLabel">
|
||||
<property name="text">
|
||||
<string>To:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="filterTo">
|
||||
<property name="toolTip">
|
||||
<string>Display only packets where the destination address (To) matches the specified regular expression</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="filterPID">
|
||||
<property name="toolTip">
|
||||
<string>Check to display only packets with PID set to No L3 (f0). This is typically used for APRS and BBS packets.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>PID No L3</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_3">
|
||||
<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="clearTable">
|
||||
<property name="toolTip">
|
||||
<string>Clear packets from table</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../sdrgui/resources/res.qrc">
|
||||
<normaloff>:/bin.png</normaloff>:/bin.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="dataContainer" native="true">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>110</y>
|
||||
<width>391</width>
|
||||
<height>261</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Received Packets</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayoutTable">
|
||||
<property name="spacing">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTableWidget" name="packets">
|
||||
<property name="toolTip">
|
||||
<string>Received packets</string>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>From</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Source callsign/address</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>To</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Destination callsign/address</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Via</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Repeater addresses</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Type</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>AX.25 frame type</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>PID</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Layer 3 protocol ID</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Data (ASCII)</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Packet data as ASCII</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Data (Hex)</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Packet data as hex</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>RollupWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/rollupwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>LevelMeterSignalDB</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/levelmeter.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ValueDialZ</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>gui/valuedialz.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>packets</tabstop>
|
||||
</tabstops>
|
||||
<resources>
|
||||
<include location="../../../sdrgui/resources/res.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
</ui>
|
92
plugins/channelrx/demodpacket/packetdemodplugin.cpp
Normal file
@ -0,0 +1,92 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2016 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/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QtPlugin>
|
||||
#include "plugin/pluginapi.h"
|
||||
|
||||
#ifndef SERVER_MODE
|
||||
#include "packetdemodgui.h"
|
||||
#endif
|
||||
#include "packetdemod.h"
|
||||
#include "packetdemodwebapiadapter.h"
|
||||
#include "packetdemodplugin.h"
|
||||
|
||||
const PluginDescriptor PacketDemodPlugin::m_pluginDescriptor = {
|
||||
PacketDemod::m_channelId,
|
||||
QStringLiteral("Packet Demodulator"),
|
||||
QStringLiteral("6.4.0"),
|
||||
QStringLiteral("(c) Jon Beniston, M7RCE"),
|
||||
QStringLiteral("https://github.com/f4exb/sdrangel"),
|
||||
true,
|
||||
QStringLiteral("https://github.com/f4exb/sdrangel")
|
||||
};
|
||||
|
||||
PacketDemodPlugin::PacketDemodPlugin(QObject* parent) :
|
||||
QObject(parent),
|
||||
m_pluginAPI(0)
|
||||
{
|
||||
}
|
||||
|
||||
const PluginDescriptor& PacketDemodPlugin::getPluginDescriptor() const
|
||||
{
|
||||
return m_pluginDescriptor;
|
||||
}
|
||||
|
||||
void PacketDemodPlugin::initPlugin(PluginAPI* pluginAPI)
|
||||
{
|
||||
m_pluginAPI = pluginAPI;
|
||||
|
||||
m_pluginAPI->registerRxChannel(PacketDemod::m_channelIdURI, PacketDemod::m_channelId, this);
|
||||
}
|
||||
|
||||
void PacketDemodPlugin::createRxChannel(DeviceAPI *deviceAPI, BasebandSampleSink **bs, ChannelAPI **cs) const
|
||||
{
|
||||
if (bs || cs)
|
||||
{
|
||||
PacketDemod *instance = new PacketDemod(deviceAPI);
|
||||
|
||||
if (bs) {
|
||||
*bs = instance;
|
||||
}
|
||||
|
||||
if (cs) {
|
||||
*cs = instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef SERVER_MODE
|
||||
ChannelGUI* PacketDemodPlugin::createRxChannelGUI(
|
||||
DeviceUISet *deviceUISet,
|
||||
BasebandSampleSink *rxChannel) const
|
||||
{
|
||||
(void) deviceUISet;
|
||||
(void) rxChannel;
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
ChannelGUI* PacketDemodPlugin::createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const
|
||||
{
|
||||
return PacketDemodGUI::create(m_pluginAPI, deviceUISet, rxChannel);
|
||||
}
|
||||
#endif
|
||||
|
||||
ChannelWebAPIAdapter* PacketDemodPlugin::createChannelWebAPIAdapter() const
|
||||
{
|
||||
return new PacketDemodWebAPIAdapter();
|
||||
}
|
49
plugins/channelrx/demodpacket/packetdemodplugin.h
Normal file
@ -0,0 +1,49 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2016 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_PACKETDEMODPLUGIN_H
|
||||
#define INCLUDE_PACKETDEMODPLUGIN_H
|
||||
|
||||
#include <QObject>
|
||||
#include "plugin/plugininterface.h"
|
||||
|
||||
class DeviceUISet;
|
||||
class BasebandSampleSink;
|
||||
|
||||
class PacketDemodPlugin : public QObject, PluginInterface {
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(PluginInterface)
|
||||
Q_PLUGIN_METADATA(IID "sdrangel.channel.packetdemod")
|
||||
|
||||
public:
|
||||
explicit PacketDemodPlugin(QObject* parent = NULL);
|
||||
|
||||
const PluginDescriptor& getPluginDescriptor() const;
|
||||
void initPlugin(PluginAPI* pluginAPI);
|
||||
|
||||
virtual void createRxChannel(DeviceAPI *deviceAPI, BasebandSampleSink **bs, ChannelAPI **cs) const;
|
||||
virtual ChannelGUI* createRxChannelGUI(DeviceUISet *deviceUISet, BasebandSampleSink *rxChannel) const;
|
||||
virtual ChannelWebAPIAdapter* createChannelWebAPIAdapter() const;
|
||||
|
||||
private:
|
||||
static const PluginDescriptor m_pluginDescriptor;
|
||||
|
||||
PluginAPI* m_pluginAPI;
|
||||
};
|
||||
|
||||
#endif // INCLUDE_PACKETDEMODPLUGIN_H
|
145
plugins/channelrx/demodpacket/packetdemodsettings.cpp
Normal file
@ -0,0 +1,145 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2015 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/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QColor>
|
||||
|
||||
#include "dsp/dspengine.h"
|
||||
#include "util/simpleserializer.h"
|
||||
#include "settings/serializable.h"
|
||||
#include "packetdemodsettings.h"
|
||||
|
||||
PacketDemodSettings::PacketDemodSettings() :
|
||||
m_channelMarker(0)
|
||||
{
|
||||
resetToDefaults();
|
||||
}
|
||||
|
||||
void PacketDemodSettings::resetToDefaults()
|
||||
{
|
||||
m_inputFrequencyOffset = 0;
|
||||
m_baud = 1200;
|
||||
m_rfBandwidth = 12500.0f;
|
||||
m_fmDeviation = 2500.0f;
|
||||
m_filterFrom = "";
|
||||
m_filterTo = "";
|
||||
m_filterPID = "";
|
||||
|
||||
m_rgbColor = QColor(255, 255, 0).rgb();
|
||||
m_title = "Packet Demodulator";
|
||||
m_streamIndex = 0;
|
||||
m_useReverseAPI = false;
|
||||
m_reverseAPIAddress = "127.0.0.1";
|
||||
m_reverseAPIPort = 8888;
|
||||
m_reverseAPIDeviceIndex = 0;
|
||||
m_reverseAPIChannelIndex = 0;
|
||||
|
||||
for (int i = 0; i < PACKETDEMOD_COLUMNS; i++)
|
||||
{
|
||||
m_columnIndexes[i] = i;
|
||||
m_columnSizes[i] = -1; // Autosize
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray PacketDemodSettings::serialize() const
|
||||
{
|
||||
SimpleSerializer s(1);
|
||||
s.writeS32(1, m_inputFrequencyOffset);
|
||||
s.writeS32(2, m_streamIndex);
|
||||
s.writeString(4, m_filterFrom);
|
||||
s.writeString(4, m_filterTo);
|
||||
s.writeString(5, m_filterPID);
|
||||
|
||||
if (m_channelMarker) {
|
||||
s.writeBlob(6, m_channelMarker->serialize());
|
||||
}
|
||||
|
||||
s.writeU32(7, m_rgbColor);
|
||||
s.writeString(9, m_title);
|
||||
s.writeBool(14, m_useReverseAPI);
|
||||
s.writeString(15, m_reverseAPIAddress);
|
||||
s.writeU32(16, m_reverseAPIPort);
|
||||
s.writeU32(17, m_reverseAPIDeviceIndex);
|
||||
s.writeU32(18, m_reverseAPIChannelIndex);
|
||||
|
||||
for (int i = 0; i < PACKETDEMOD_COLUMNS; i++)
|
||||
s.writeS32(100 + i, m_columnIndexes[i]);
|
||||
for (int i = 0; i < PACKETDEMOD_COLUMNS; i++)
|
||||
s.writeS32(200 + i, m_columnSizes[i]);
|
||||
|
||||
return s.final();
|
||||
}
|
||||
|
||||
bool PacketDemodSettings::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.readS32(1, &m_inputFrequencyOffset, 0);
|
||||
d.readS32(2, &m_streamIndex, 0);
|
||||
d.readString(3, &m_filterFrom, "");
|
||||
d.readString(4, &m_filterTo, "");
|
||||
d.readString(5, &m_filterPID, "");
|
||||
d.readBlob(6, &bytetmp);
|
||||
|
||||
if (m_channelMarker) {
|
||||
m_channelMarker->deserialize(bytetmp);
|
||||
}
|
||||
|
||||
d.readU32(7, &m_rgbColor);
|
||||
d.readString(9, &m_title, "Packet Demodulator");
|
||||
d.readBool(14, &m_useReverseAPI, false);
|
||||
d.readString(15, &m_reverseAPIAddress, "127.0.0.1");
|
||||
d.readU32(16, &utmp, 0);
|
||||
|
||||
if ((utmp > 1023) && (utmp < 65535)) {
|
||||
m_reverseAPIPort = utmp;
|
||||
} else {
|
||||
m_reverseAPIPort = 8888;
|
||||
}
|
||||
|
||||
d.readU32(17, &utmp, 0);
|
||||
m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp;
|
||||
d.readU32(18, &utmp, 0);
|
||||
m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp;
|
||||
|
||||
for (int i = 0; i < PACKETDEMOD_COLUMNS; i++)
|
||||
d.readS32(100 + i, &m_columnIndexes[i], i);
|
||||
for (int i = 0; i < PACKETDEMOD_COLUMNS; i++)
|
||||
d.readS32(200 + i, &m_columnSizes[i], -1);
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
resetToDefaults();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
61
plugins/channelrx/demodpacket/packetdemodsettings.h
Normal file
@ -0,0 +1,61 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2017 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_PACKETDEMODSETTINGS_H
|
||||
#define INCLUDE_PACKETDEMODSETTINGS_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QHash>
|
||||
|
||||
class Serializable;
|
||||
|
||||
// Number of columns in the table
|
||||
#define PACKETDEMOD_COLUMNS 7
|
||||
|
||||
struct PacketDemodSettings
|
||||
{
|
||||
qint32 m_inputFrequencyOffset;
|
||||
qint32 m_baud;
|
||||
Real m_rfBandwidth;
|
||||
Real m_fmDeviation;
|
||||
QString m_filterFrom;
|
||||
QString m_filterTo;
|
||||
QString m_filterPID;
|
||||
|
||||
quint32 m_rgbColor;
|
||||
QString m_title;
|
||||
Serializable *m_channelMarker;
|
||||
QString m_audioDeviceName;
|
||||
int m_streamIndex; //!< MIMO channel. Not relevant when connected to SI (single Rx).
|
||||
bool m_useReverseAPI;
|
||||
QString m_reverseAPIAddress;
|
||||
uint16_t m_reverseAPIPort;
|
||||
uint16_t m_reverseAPIDeviceIndex;
|
||||
uint16_t m_reverseAPIChannelIndex;
|
||||
|
||||
int m_columnIndexes[PACKETDEMOD_COLUMNS];//!< How the columns are ordered in the table
|
||||
int m_columnSizes[PACKETDEMOD_COLUMNS]; //!< Size of the columns in the table
|
||||
|
||||
PacketDemodSettings();
|
||||
void resetToDefaults();
|
||||
void setChannelMarker(Serializable *channelMarker) { m_channelMarker = channelMarker; }
|
||||
QByteArray serialize() const;
|
||||
bool deserialize(const QByteArray& data);
|
||||
};
|
||||
|
||||
#endif /* INCLUDE_PACKETDEMODSETTINGS_H */
|
306
plugins/channelrx/demodpacket/packetdemodsink.cpp
Normal file
@ -0,0 +1,306 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include <complex.h>
|
||||
|
||||
#include "dsp/dspengine.h"
|
||||
#include "dsp/dspengine.h"
|
||||
#include "util/db.h"
|
||||
#include "util/stepfunctions.h"
|
||||
#include "pipes/pipeendpoint.h"
|
||||
#include "maincore.h"
|
||||
|
||||
#include "packetdemod.h"
|
||||
#include "packetdemodsink.h"
|
||||
|
||||
PacketDemodSink::PacketDemodSink(PacketDemod *packetDemod) :
|
||||
m_packetDemod(packetDemod),
|
||||
m_channelSampleRate(PACKETDEMOD_CHANNEL_SAMPLE_RATE),
|
||||
m_channelFrequencyOffset(0),
|
||||
m_magsqSum(0.0f),
|
||||
m_magsqPeak(0.0f),
|
||||
m_magsqCount(0),
|
||||
m_messageQueueToChannel(nullptr),
|
||||
m_f1(nullptr),
|
||||
m_f0(nullptr),
|
||||
m_corrBuf(nullptr),
|
||||
m_corrIdx(0),
|
||||
m_corrCnt(0)
|
||||
{
|
||||
m_magsq = 0.0;
|
||||
|
||||
applySettings(m_settings, true);
|
||||
applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
|
||||
}
|
||||
|
||||
PacketDemodSink::~PacketDemodSink()
|
||||
{
|
||||
}
|
||||
|
||||
void PacketDemodSink::feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end)
|
||||
{
|
||||
Complex ci;
|
||||
|
||||
for (SampleVector::const_iterator it = begin; it != end; ++it)
|
||||
{
|
||||
Complex c(it->real(), it->imag());
|
||||
c *= m_nco.nextIQ();
|
||||
|
||||
if (m_interpolatorDistance < 1.0f) // interpolate
|
||||
{
|
||||
while (!m_interpolator.interpolate(&m_interpolatorDistanceRemain, c, &ci))
|
||||
{
|
||||
processOneSample(ci);
|
||||
m_interpolatorDistanceRemain += m_interpolatorDistance;
|
||||
}
|
||||
}
|
||||
else // decimate
|
||||
{
|
||||
if (m_interpolator.decimate(&m_interpolatorDistanceRemain, c, &ci))
|
||||
{
|
||||
processOneSample(ci);
|
||||
m_interpolatorDistanceRemain += m_interpolatorDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PacketDemodSink::processOneSample(Complex &ci)
|
||||
{
|
||||
Complex ca;
|
||||
|
||||
// FM demodulation
|
||||
double magsqRaw;
|
||||
Real deviation;
|
||||
Real fmDemod = m_phaseDiscri.phaseDiscriminatorDelta(ci, magsqRaw, deviation);
|
||||
|
||||
// Calculate average and peak levels for level meter
|
||||
Real magsq = magsqRaw / (SDR_RX_SCALED*SDR_RX_SCALED);
|
||||
m_movingAverage(magsq);
|
||||
m_magsq = m_movingAverage.asDouble();
|
||||
m_magsqSum += magsq;
|
||||
if (magsq > m_magsqPeak)
|
||||
{
|
||||
m_magsqPeak = magsq;
|
||||
}
|
||||
m_magsqCount++;
|
||||
|
||||
m_corrBuf[m_corrIdx] = fmDemod;
|
||||
if (m_corrCnt >= m_correlationLength && magsq > 1e-7)
|
||||
{
|
||||
// Correlate with 1200 + 2200 baud complex exponentials
|
||||
Complex corrF0 = 0.0f;
|
||||
Complex corrF1 = 0.0f;
|
||||
for (int i = 0; i < m_correlationLength; i++)
|
||||
{
|
||||
int j = m_corrIdx - i;
|
||||
if (j < 0)
|
||||
j += m_correlationLength;
|
||||
corrF0 += m_f0[i] * m_corrBuf[j];
|
||||
corrF1 += m_f1[i] * m_corrBuf[j];
|
||||
}
|
||||
m_corrCnt--; // Avoid overflow in increment below
|
||||
|
||||
// Low pass filter, to minimize changes above the baud rate
|
||||
Real f0Filt = m_lowpassF0.filter(std::abs(corrF0));
|
||||
Real f1Filt = m_lowpassF1.filter(std::abs(corrF1));
|
||||
|
||||
// Determine which is the closest match and then quantise to 1 or -1
|
||||
// FIXME: We should try to account for the fact that higher frequencies can have preemphasis
|
||||
float diff = f1Filt - f0Filt;
|
||||
int sample = diff >= 0.0f ? 1 : 0;
|
||||
|
||||
// Look for edge
|
||||
if (sample != m_samplePrev)
|
||||
{
|
||||
m_syncCount = PACKETDEMOD_CHANNEL_SAMPLE_RATE/m_settings.m_baud/2;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_syncCount--;
|
||||
if (m_syncCount <= 0)
|
||||
{
|
||||
// HDLC deframing
|
||||
|
||||
// Should be in the middle of the symbol
|
||||
// NRZI decoding
|
||||
int bit;
|
||||
if (sample != m_symbolPrev)
|
||||
bit = 0;
|
||||
else
|
||||
bit = 1;
|
||||
m_symbolPrev = sample;
|
||||
|
||||
// Store in shift reg
|
||||
m_bits |= bit << m_bitCount;
|
||||
m_bitCount++;
|
||||
|
||||
if (bit == 1)
|
||||
{
|
||||
m_onesCount++;
|
||||
// Shouldn't ever get 7 1s in a row
|
||||
if ((m_onesCount == 7) && m_gotSOP)
|
||||
{
|
||||
m_gotSOP = false;
|
||||
m_byteCount = 0;
|
||||
}
|
||||
}
|
||||
else if (bit == 0)
|
||||
{
|
||||
if (m_onesCount == 5)
|
||||
{
|
||||
// Remove bit-stuffing (5 1s followed by a 0)
|
||||
m_bitCount--;
|
||||
}
|
||||
else if (m_onesCount == 6)
|
||||
{
|
||||
// Start/end of packet
|
||||
if ((m_bitCount == 8) && (m_bits == 0x7e) && (m_byteCount > 0))
|
||||
{
|
||||
// End of packet
|
||||
// Check CRC is valid
|
||||
m_crc.init();
|
||||
m_crc.calculate(m_bytes, m_byteCount - 2);
|
||||
uint16_t calcCrc = m_crc.get();
|
||||
uint16_t rxCrc = m_bytes[m_byteCount-2] | (m_bytes[m_byteCount-1] << 8);
|
||||
if (calcCrc == rxCrc)
|
||||
{
|
||||
QByteArray rxPacket((char *)m_bytes, m_byteCount);
|
||||
qDebug() << "RX: " << rxPacket.toHex();
|
||||
if (getMessageQueueToChannel())
|
||||
{
|
||||
MainCore::MsgPacket *msg = MainCore::MsgPacket::create(m_packetDemod, rxPacket, QDateTime::currentDateTime()); // FIXME pointer
|
||||
getMessageQueueToChannel()->push(msg);
|
||||
}
|
||||
}
|
||||
else
|
||||
qDebug() << QString("CRC mismatch: %1 %2").arg(calcCrc, 4, 16, QLatin1Char('0')).arg(rxCrc, 4, 16, QLatin1Char('0'));
|
||||
// Reset state to start receiving next packet
|
||||
m_gotSOP = false;
|
||||
m_bits = 0;
|
||||
m_bitCount = 0;
|
||||
m_byteCount = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Start of packet
|
||||
m_gotSOP = true;
|
||||
m_bits = 0;
|
||||
m_bitCount = 0;
|
||||
m_byteCount = 0;
|
||||
}
|
||||
}
|
||||
m_onesCount = 0;
|
||||
}
|
||||
|
||||
if (m_gotSOP)
|
||||
{
|
||||
if (m_bitCount == 8)
|
||||
{
|
||||
if (m_byteCount >= 512)
|
||||
{
|
||||
// Too many bytes
|
||||
m_gotSOP = false;
|
||||
m_byteCount = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_bytes[m_byteCount] = m_bits;
|
||||
m_byteCount++;
|
||||
}
|
||||
m_bits = 0;
|
||||
m_bitCount = 0;
|
||||
}
|
||||
}
|
||||
m_syncCount = PACKETDEMOD_CHANNEL_SAMPLE_RATE/m_settings.m_baud;
|
||||
}
|
||||
}
|
||||
m_samplePrev = sample;
|
||||
}
|
||||
m_corrIdx = (m_corrIdx + 1) % m_correlationLength;
|
||||
m_corrCnt++;
|
||||
}
|
||||
|
||||
void PacketDemodSink::applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force)
|
||||
{
|
||||
qDebug() << "PacketDemodSink::applyChannelSettings:"
|
||||
<< " channelSampleRate: " << channelSampleRate
|
||||
<< " channelFrequencyOffset: " << channelFrequencyOffset;
|
||||
|
||||
if ((m_channelFrequencyOffset != channelFrequencyOffset) ||
|
||||
(m_channelSampleRate != channelSampleRate) || force)
|
||||
{
|
||||
m_nco.setFreq(-channelFrequencyOffset, channelSampleRate);
|
||||
}
|
||||
|
||||
if ((m_channelSampleRate != channelSampleRate) || force)
|
||||
{
|
||||
m_interpolator.create(16, channelSampleRate, PACKETDEMOD_CHANNEL_BANDWIDTH);
|
||||
m_interpolatorDistanceRemain = 0;
|
||||
m_interpolatorDistance = (Real) channelSampleRate / (Real) PACKETDEMOD_CHANNEL_SAMPLE_RATE;
|
||||
}
|
||||
|
||||
m_channelSampleRate = channelSampleRate;
|
||||
m_channelFrequencyOffset = channelFrequencyOffset;
|
||||
}
|
||||
|
||||
void PacketDemodSink::applySettings(const PacketDemodSettings& settings, bool force)
|
||||
{
|
||||
qDebug() << "PacketDemodSink::applySettings:"
|
||||
<< " force: " << force;
|
||||
|
||||
if (force)
|
||||
{
|
||||
m_lowpass.create(301, PACKETDEMOD_CHANNEL_SAMPLE_RATE, settings.m_rfBandwidth / 2.0f);
|
||||
m_phaseDiscri.setFMScaling(PACKETDEMOD_CHANNEL_SAMPLE_RATE / (2.0f * settings.m_fmDeviation));
|
||||
|
||||
delete m_f1;
|
||||
delete m_f0;
|
||||
delete m_corrBuf;
|
||||
m_correlationLength = PACKETDEMOD_CHANNEL_SAMPLE_RATE/settings.m_baud;
|
||||
m_f1 = new Complex[m_correlationLength]();
|
||||
m_f0 = new Complex[m_correlationLength]();
|
||||
m_corrBuf = new Complex[m_correlationLength]();
|
||||
m_corrIdx = 0;
|
||||
m_corrCnt = 0;
|
||||
Real f0 = 0.0f;
|
||||
Real f1 = 0.0f;
|
||||
for (int i = 0; i < m_correlationLength; i++)
|
||||
{
|
||||
m_f0[i] = Complex(cos(f0), sin(f0));
|
||||
m_f1[i] = Complex(cos(f1), sin(f1));
|
||||
f0 += 2.0f*(Real)M_PI*2200.0f/PACKETDEMOD_CHANNEL_SAMPLE_RATE;
|
||||
f1 += 2.0f*(Real)M_PI*1200.0f/PACKETDEMOD_CHANNEL_SAMPLE_RATE;
|
||||
}
|
||||
|
||||
m_lowpassF1.create(301, PACKETDEMOD_CHANNEL_SAMPLE_RATE, settings.m_baud * 1.1f);
|
||||
m_lowpassF0.create(301, PACKETDEMOD_CHANNEL_SAMPLE_RATE, settings.m_baud * 1.1f);
|
||||
m_samplePrev = 0;
|
||||
m_syncCount = 0;
|
||||
m_symbolPrev = 0;
|
||||
m_bits = 0;
|
||||
m_bitCount = 0;
|
||||
m_onesCount = 0;
|
||||
m_gotSOP = false;
|
||||
m_byteCount = 0;
|
||||
}
|
||||
|
||||
m_settings = settings;
|
||||
}
|
135
plugins/channelrx/demodpacket/packetdemodsink.h
Normal file
@ -0,0 +1,135 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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_PACKETDEMODSINK_H
|
||||
#define INCLUDE_PACKETDEMODSINK_H
|
||||
|
||||
#include "dsp/channelsamplesink.h"
|
||||
#include "dsp/phasediscri.h"
|
||||
#include "dsp/nco.h"
|
||||
#include "dsp/interpolator.h"
|
||||
#include "dsp/firfilter.h"
|
||||
#include "util/movingaverage.h"
|
||||
#include "util/doublebufferfifo.h"
|
||||
#include "util/messagequeue.h"
|
||||
#include "util/crc.h"
|
||||
|
||||
#include "packetdemodsettings.h"
|
||||
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
|
||||
#define PACKETDEMOD_CHANNEL_BANDWIDTH 9600
|
||||
// Must be integer multiple of m_baud=1200
|
||||
#define PACKETDEMOD_CHANNEL_SAMPLE_RATE 38400
|
||||
|
||||
class PacketDemod;
|
||||
|
||||
class PacketDemodSink : public ChannelSampleSink {
|
||||
public:
|
||||
PacketDemodSink(PacketDemod *packetDemod);
|
||||
~PacketDemodSink();
|
||||
|
||||
virtual void feed(const SampleVector::const_iterator& begin, const SampleVector::const_iterator& end);
|
||||
|
||||
void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false);
|
||||
void applySettings(const PacketDemodSettings& settings, bool force = false);
|
||||
void setMessageQueueToChannel(MessageQueue *messageQueue) { m_messageQueueToChannel = messageQueue; }
|
||||
|
||||
double getMagSq() const { return m_magsq; }
|
||||
|
||||
void getMagSqLevels(double& avg, double& peak, int& nbSamples)
|
||||
{
|
||||
if (m_magsqCount > 0)
|
||||
{
|
||||
m_magsq = m_magsqSum / m_magsqCount;
|
||||
m_magSqLevelStore.m_magsq = m_magsq;
|
||||
m_magSqLevelStore.m_magsqPeak = m_magsqPeak;
|
||||
}
|
||||
|
||||
avg = m_magSqLevelStore.m_magsq;
|
||||
peak = m_magSqLevelStore.m_magsqPeak;
|
||||
nbSamples = m_magsqCount == 0 ? 1 : m_magsqCount;
|
||||
|
||||
m_magsqSum = 0.0f;
|
||||
m_magsqPeak = 0.0f;
|
||||
m_magsqCount = 0;
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
struct MagSqLevelsStore
|
||||
{
|
||||
MagSqLevelsStore() :
|
||||
m_magsq(1e-12),
|
||||
m_magsqPeak(1e-12)
|
||||
{}
|
||||
double m_magsq;
|
||||
double m_magsqPeak;
|
||||
};
|
||||
|
||||
PacketDemod *m_packetDemod;
|
||||
PacketDemodSettings m_settings;
|
||||
int m_channelSampleRate;
|
||||
int m_channelFrequencyOffset;
|
||||
|
||||
NCO m_nco;
|
||||
Interpolator m_interpolator;
|
||||
Real m_interpolatorDistance;
|
||||
Real m_interpolatorDistanceRemain;
|
||||
|
||||
double m_magsq;
|
||||
double m_magsqSum;
|
||||
double m_magsqPeak;
|
||||
int m_magsqCount;
|
||||
MagSqLevelsStore m_magSqLevelStore;
|
||||
|
||||
MessageQueue *m_messageQueueToChannel;
|
||||
|
||||
MovingAverageUtil<Real, double, 16> m_movingAverage;
|
||||
|
||||
Lowpass<Complex> m_lowpass;
|
||||
PhaseDiscriminators m_phaseDiscri;
|
||||
|
||||
int m_correlationLength;
|
||||
Complex *m_f1;
|
||||
Complex *m_f0;
|
||||
Complex *m_corrBuf;
|
||||
int m_corrIdx;
|
||||
int m_corrCnt;
|
||||
|
||||
Lowpass<Real> m_lowpassF1;
|
||||
Lowpass<Real> m_lowpassF0;
|
||||
|
||||
int m_samplePrev;
|
||||
int m_syncCount;
|
||||
int m_symbolPrev;
|
||||
unsigned char m_bits;
|
||||
int m_bitCount;
|
||||
int m_onesCount;
|
||||
bool m_gotSOP;
|
||||
unsigned char m_bytes[512]; // Info field can be 256 bytes
|
||||
int m_byteCount;
|
||||
crc16x25 m_crc;
|
||||
|
||||
void processOneSample(Complex &ci);
|
||||
MessageQueue *getMessageQueueToChannel() { return m_messageQueueToChannel; }
|
||||
};
|
||||
|
||||
#endif // INCLUDE_PACKETDEMODSINK_H
|
52
plugins/channelrx/demodpacket/packetdemodwebapiadapter.cpp
Normal file
@ -0,0 +1,52 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "SWGChannelSettings.h"
|
||||
#include "packetdemod.h"
|
||||
#include "packetdemodwebapiadapter.h"
|
||||
|
||||
PacketDemodWebAPIAdapter::PacketDemodWebAPIAdapter()
|
||||
{}
|
||||
|
||||
PacketDemodWebAPIAdapter::~PacketDemodWebAPIAdapter()
|
||||
{}
|
||||
|
||||
int PacketDemodWebAPIAdapter::webapiSettingsGet(
|
||||
SWGSDRangel::SWGChannelSettings& response,
|
||||
QString& errorMessage)
|
||||
{
|
||||
(void) errorMessage;
|
||||
response.setPacketDemodSettings(new SWGSDRangel::SWGPacketDemodSettings());
|
||||
response.getPacketDemodSettings()->init();
|
||||
PacketDemod::webapiFormatChannelSettings(response, m_settings);
|
||||
|
||||
return 200;
|
||||
}
|
||||
|
||||
int PacketDemodWebAPIAdapter::webapiSettingsPutPatch(
|
||||
bool force,
|
||||
const QStringList& channelSettingsKeys,
|
||||
SWGSDRangel::SWGChannelSettings& response,
|
||||
QString& errorMessage)
|
||||
{
|
||||
(void) force;
|
||||
(void) errorMessage;
|
||||
PacketDemod::webapiUpdateChannelSettings(m_settings, channelSettingsKeys, response);
|
||||
|
||||
return 200;
|
||||
}
|
50
plugins/channelrx/demodpacket/packetdemodwebapiadapter.h
Normal file
@ -0,0 +1,50 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2019 Edouard Griffiths, F4EXB. //
|
||||
// Copyright (C) 2020 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_PACKETDEMOD_WEBAPIADAPTER_H
|
||||
#define INCLUDE_PACKETDEMOD_WEBAPIADAPTER_H
|
||||
|
||||
#include "channel/channelwebapiadapter.h"
|
||||
#include "packetdemodsettings.h"
|
||||
|
||||
/**
|
||||
* Standalone API adapter only for the settings
|
||||
*/
|
||||
class PacketDemodWebAPIAdapter : public ChannelWebAPIAdapter {
|
||||
public:
|
||||
PacketDemodWebAPIAdapter();
|
||||
virtual ~PacketDemodWebAPIAdapter();
|
||||
|
||||
virtual QByteArray serialize() const { return m_settings.serialize(); }
|
||||
virtual bool deserialize(const QByteArray& data) { return m_settings.deserialize(data); }
|
||||
|
||||
virtual int webapiSettingsGet(
|
||||
SWGSDRangel::SWGChannelSettings& response,
|
||||
QString& errorMessage);
|
||||
|
||||
virtual int webapiSettingsPutPatch(
|
||||
bool force,
|
||||
const QStringList& channelSettingsKeys,
|
||||
SWGSDRangel::SWGChannelSettings& response,
|
||||
QString& errorMessage);
|
||||
|
||||
private:
|
||||
PacketDemodSettings m_settings;
|
||||
};
|
||||
|
||||
#endif // INCLUDE_PACKETDEMOD_WEBAPIADAPTER_H
|
63
plugins/channelrx/demodpacket/readme.md
Normal file
@ -0,0 +1,63 @@
|
||||
<h1>Packet radio demodulator plugin</h1>
|
||||
|
||||
<h2>Introduction</h2>
|
||||
|
||||
This plugin can be used to demodulate packet radio (APRS/AX.25) data packets. Received packets can be sent to the APRS Feature for decoding and display.
|
||||
|
||||
<h2>Interface</h2>
|
||||
|
||||
![Packet Demodulator plugin GUI](../../../doc/img/PacketDemod_plugin.png)
|
||||
|
||||
<h3>1: Frequency shift from center frequency of reception</h3>
|
||||
|
||||
Use the wheels to adjust the frequency shift in Hz from the center frequency of reception. Left click on a digit sets the cursor position at this digit. Right click on a digit sets all digits on the right to zero. This effectively floors value at the digit position. Wheels are moved with the mousewheel while pointing at the wheel or by selecting the wheel with the left mouse click and using the keyboard arrows. Pressing shift simultaneously moves digit by 5 and pressing control moves it by 2.
|
||||
|
||||
<h3>2: Channel power</h3>
|
||||
|
||||
Average total power in dB relative to a +/- 1.0 amplitude signal received in the pass band.
|
||||
|
||||
<h3>3: Level meter in dB</h3>
|
||||
|
||||
- top bar (green): average value
|
||||
- bottom bar (blue green): instantaneous peak value
|
||||
- tip vertical bar (bright green): peak hold value
|
||||
|
||||
<h3>4: Modulation</h3>
|
||||
|
||||
This specifies the baud rate and modulation that is used for the packet transmission. Currently 1200 baud AFSK is supported.
|
||||
|
||||
<h3>5: RF Bandwidth</h3>
|
||||
|
||||
This specifies the bandwidth of a LPF that is applied to the input signal to limit the RF bandwidth.
|
||||
|
||||
<h3>6: Frequency deviation</h3>
|
||||
|
||||
Adjusts the expected frequency deviation in 0.1 kHz steps from 1 to 6 kHz. Typical values are 2.5 kHz and 5 kHz.
|
||||
|
||||
<h3>7: Filter Packets From</h3>
|
||||
|
||||
Entering a regular expression in the From field displays only packets where the source address, displayed in the From column, matches the regular expression.
|
||||
|
||||
<h3>8: Filter Packets To</h3>
|
||||
|
||||
Entering a regular expression in the To field displays only packets where the destination address, displayed in the To column, matches the regular expression.
|
||||
|
||||
<h3>9: Filter PID No L3</h3>
|
||||
|
||||
Checking this option displays only packets where the PID (Protocol ID) field is 0xf0 (no L3). This value is used by APRS and BBS data packets, and helps to filter out control packets.
|
||||
|
||||
<h3>10: Clear Packets from table</h3>
|
||||
|
||||
Pressing this button clears all packets from the table.
|
||||
|
||||
<h3>Received Packets Table</h3>
|
||||
|
||||
The received packets table displays the contexts of the packets that have been received. Only packets with valid CRCs are displayed.
|
||||
|
||||
* From - The source address / callsign of the sender of the packet.
|
||||
* To - The destination address.
|
||||
* Via - List of addresses of repeaters the packet has passed through or directed via.
|
||||
* Type - The AX.25 frame type.
|
||||
* PID - Protocol Identifier.
|
||||
* Data (ASCII) - The AX.25 information field displayed as ASCII.
|
||||
* Data (Hex) - The AX.25 information field displayed as hexidecimal.
|
@ -473,10 +473,10 @@ void VORDemod::webapiFormatChannelSettings(
|
||||
swgVORDemodSettings->setStreamIndex(settings.m_streamIndex);
|
||||
}
|
||||
if (channelSettingsKeys.contains("identThreshold") || force) {
|
||||
swgVORDemodSettings->setAudioMute(settings.m_identThreshold);
|
||||
swgVORDemodSettings->setIdentThreshold(settings.m_identThreshold);
|
||||
}
|
||||
if (channelSettingsKeys.contains("magDecAdjust") || force) {
|
||||
swgVORDemodSettings->setAudioMute(settings.m_magDecAdjust ? 1 : 0);
|
||||
swgVORDemodSettings->setMagDecAdjust(settings.m_magDecAdjust ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -384,8 +384,8 @@ static bool calcIntersectionPoint(float lat1, float lon1, float bearing1, float
|
||||
double lat3Rad = asin(sinLat1*cos(delta13)+cosLat1*sin(delta13)*cos(theta13));
|
||||
double lon3Rad = lon1Rad + atan2(sin(theta13)*sin(delta13)*cosLat1, cos(delta13)-sinLat1*sin(lat3Rad));
|
||||
|
||||
intersectLat = Units::radiansToDegress(lat3Rad);
|
||||
intersectLon = Units::radiansToDegress(lon3Rad);
|
||||
intersectLat = Units::radiansToDegrees(lat3Rad);
|
||||
intersectLon = Units::radiansToDegrees(lon3Rad);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -222,7 +222,7 @@ void VORDemodSink::processOneSample(Complex &ci)
|
||||
if (m_varGoertzel.size() == VORDEMOD_CHANNEL_SAMPLE_RATE - 1)
|
||||
{
|
||||
m_varGoertzel.goertzel(mag);
|
||||
varPhase = Units::radiansToDegress(m_varGoertzel.phase());
|
||||
varPhase = Units::radiansToDegrees(m_varGoertzel.phase());
|
||||
varMag = m_varGoertzel.mag();
|
||||
m_varGoertzel.reset();
|
||||
}
|
||||
@ -245,7 +245,7 @@ void VORDemodSink::processOneSample(Complex &ci)
|
||||
if (m_refGoertzel.size() == VORDEMOD_CHANNEL_SAMPLE_RATE - 1)
|
||||
{
|
||||
m_refGoertzel.goertzel(phi);
|
||||
float phaseDeg = Units::radiansToDegress(m_refGoertzel.phase());
|
||||
float phaseDeg = Units::radiansToDegrees(m_refGoertzel.phase());
|
||||
double refMag = m_refGoertzel.mag();
|
||||
int groupDelay = (301-1)/2;
|
||||
float filterPhaseShift = 360.0*30.0*groupDelay/VORDEMOD_CHANNEL_SAMPLE_RATE;
|
||||
|
@ -211,7 +211,7 @@ void VORDemodSCSink::processOneSample(Complex &ci)
|
||||
if (m_varGoertzel.size() == VORDemodSCSettings::VORDEMOD_CHANNEL_SAMPLE_RATE - 1)
|
||||
{
|
||||
m_varGoertzel.goertzel(mag);
|
||||
varPhase = Units::radiansToDegress(m_varGoertzel.phase());
|
||||
varPhase = Units::radiansToDegrees(m_varGoertzel.phase());
|
||||
varMag = m_varGoertzel.mag();
|
||||
m_varGoertzel.reset();
|
||||
}
|
||||
@ -234,7 +234,7 @@ void VORDemodSCSink::processOneSample(Complex &ci)
|
||||
if (m_refGoertzel.size() == VORDemodSCSettings::VORDEMOD_CHANNEL_SAMPLE_RATE - 1)
|
||||
{
|
||||
m_refGoertzel.goertzel(phi);
|
||||
float phaseDeg = Units::radiansToDegress(m_refGoertzel.phase());
|
||||
float phaseDeg = Units::radiansToDegrees(m_refGoertzel.phase());
|
||||
double refMag = m_refGoertzel.mag();
|
||||
int groupDelay = (301-1)/2;
|
||||
float filterPhaseShift = 360.0*30.0*groupDelay/VORDemodSCSettings::VORDEMOD_CHANNEL_SAMPLE_RATE;
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "util/crc.h"
|
||||
#include "util/messagequeue.h"
|
||||
#include "maincore.h"
|
||||
#include "channel/channelapi.h"
|
||||
|
||||
PacketModSource::PacketModSource() :
|
||||
m_channelSampleRate(48000),
|
||||
@ -424,33 +425,56 @@ void PacketModSource::applyChannelSettings(int channelSampleRate, int channelFre
|
||||
}
|
||||
}
|
||||
|
||||
static bool ax25_ssid(QByteArray& b, int i, int len, uint8_t& ssid)
|
||||
{
|
||||
if (b[i] == '-')
|
||||
{
|
||||
if (len > i + 1)
|
||||
{
|
||||
ssid = b[i+1] - '0';
|
||||
if ((len > i + 2) && isdigit(b[i+2])) {
|
||||
ssid = (ssid*10) + (b[i+2] - '0');
|
||||
}
|
||||
if (ssid >= 16)
|
||||
{
|
||||
qDebug() << "ax25_address: SSID greater than 15 not supported";
|
||||
ssid = ssid & 0xf;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "ax25_address: SSID number missing";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
static uint8_t *ax25_address(uint8_t *p, QString address, uint8_t crrl)
|
||||
{
|
||||
int len;
|
||||
int i;
|
||||
QByteArray b;
|
||||
int ssid;
|
||||
uint8_t ssid = 0;
|
||||
bool hyphenSeen = false;
|
||||
|
||||
len = address.length();
|
||||
b = address.toUtf8();
|
||||
ssid = 0;
|
||||
for (i = 0; i < 6; i++)
|
||||
{
|
||||
if ((i < len) && (ssid == 0))
|
||||
if ((i < len) && !hyphenSeen)
|
||||
{
|
||||
if (b[i] == '-')
|
||||
{
|
||||
if (len > i + 1)
|
||||
{
|
||||
ssid = b[i+1] - '0';
|
||||
if ((len > i + 2) && isdigit(b[i+2])) {
|
||||
ssid = (ssid*10) + (b[i+1] - '0');
|
||||
}
|
||||
if (ssid >= 16)
|
||||
qDebug() << "ax25_address: SSID greater than 15 not supported";
|
||||
}
|
||||
else
|
||||
qDebug() << "ax25_address: SSID number missing";
|
||||
ax25_ssid(b, i, len, ssid);
|
||||
hyphenSeen = true;
|
||||
*p++ = ' ' << 1;
|
||||
}
|
||||
else
|
||||
@ -463,6 +487,10 @@ static uint8_t *ax25_address(uint8_t *p, QString address, uint8_t crrl)
|
||||
*p++ = ' ' << 1;
|
||||
}
|
||||
}
|
||||
if (b[i] == '-')
|
||||
{
|
||||
ax25_ssid(b, i, len, ssid);
|
||||
}
|
||||
*p++ = crrl | (ssid << 1);
|
||||
|
||||
return p;
|
||||
@ -534,6 +562,7 @@ void PacketModSource::addTXPacket(QString callsign, QString to, QString via, QSt
|
||||
{
|
||||
uint8_t packet[AX25_MAX_BYTES];
|
||||
uint8_t *crc_start;
|
||||
uint8_t *packet_end;
|
||||
uint8_t *p;
|
||||
crc16x25 crc;
|
||||
uint16_t crcValue;
|
||||
@ -565,6 +594,7 @@ void PacketModSource::addTXPacket(QString callsign, QString to, QString via, QSt
|
||||
crcValue = crc.get();
|
||||
*p++ = crcValue & 0xff;
|
||||
*p++ = (crcValue >> 8);
|
||||
packet_end = p;
|
||||
// Flag
|
||||
for (int i = 0; i < std::min(m_settings.m_ax25PostFlags, AX25_MAX_FLAGS); i++)
|
||||
*p++ = AX25_FLAG;
|
||||
@ -582,8 +612,17 @@ void PacketModSource::addTXPacket(QString callsign, QString to, QString via, QSt
|
||||
for (int j = 0; j < 8; j++)
|
||||
{
|
||||
int tx_bit = (packet[i] >> j) & 1;
|
||||
// Stuff 0 if last 5 bits are 1s
|
||||
if ((packet[i] != AX25_FLAG) && (m_last5Bits == 0x1f))
|
||||
// Stuff 0 if last 5 bits are 1s, unless transmitting flag
|
||||
// Except for special case of when last 5 bits of CRC are 1s
|
||||
if ( ( (packet[i] != AX25_FLAG)
|
||||
|| ( (&packet[i] >= crc_start)
|
||||
&& ( (&packet[i] < packet_end)
|
||||
|| ((&packet[i] == packet_end) && (j == 0))
|
||||
)
|
||||
)
|
||||
)
|
||||
&& (m_last5Bits == 0x1f)
|
||||
)
|
||||
addBit(0);
|
||||
addBit(tx_bit);
|
||||
}
|
||||
|
@ -5,10 +5,13 @@ if (Qt5SerialPort_FOUND)
|
||||
endif()
|
||||
|
||||
if (Qt5Quick_FOUND AND Qt5QuickWidgets_FOUND AND Qt5Positioning_FOUND)
|
||||
add_subdirectory(map)
|
||||
add_subdirectory(vorlocalizer)
|
||||
endif()
|
||||
|
||||
add_subdirectory(afc)
|
||||
add_subdirectory(aprs)
|
||||
add_subdirectory(demodanalyzer)
|
||||
add_subdirectory(rigctlserver)
|
||||
add_subdirectory(simpleptt)
|
||||
add_subdirectory(startracker)
|
||||
|
@ -135,8 +135,7 @@ bool AFC::handleMessage(const Message& cmd)
|
||||
qDebug() << "AFC::handleMessage: MessagePipesCommon::MsgReportChannelDeleted";
|
||||
MessagePipesCommon::MsgReportChannelDeleted& report = (MessagePipesCommon::MsgReportChannelDeleted&) cmd;
|
||||
const MessagePipesCommon::ChannelRegistrationKey& channelKey = report.getChannelRegistrationKey();
|
||||
const ChannelAPI *channel = channelKey.m_key;
|
||||
MainCore::instance()->getMessagePipes().unregisterChannelToFeature(channel, this, "settings");
|
||||
MainCore::instance()->getMessagePipes().unregisterChannelToFeature(channelKey.m_key, this, "settings");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
67
plugins/feature/aprs/CMakeLists.txt
Normal file
@ -0,0 +1,67 @@
|
||||
project(aprs)
|
||||
|
||||
set(aprs_SOURCES
|
||||
aprs.cpp
|
||||
aprssettings.cpp
|
||||
aprsplugin.cpp
|
||||
aprsworker.cpp
|
||||
aprswebapiadapter.cpp
|
||||
)
|
||||
|
||||
set(aprs_HEADERS
|
||||
aprs.h
|
||||
aprssettings.h
|
||||
aprsplugin.h
|
||||
aprsreport.h
|
||||
aprsworker.h
|
||||
aprswebapiadapter.h
|
||||
)
|
||||
|
||||
include_directories(
|
||||
${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
|
||||
)
|
||||
|
||||
if(NOT SERVER_MODE)
|
||||
set(aprs_SOURCES
|
||||
${aprs_SOURCES}
|
||||
aprsgui.cpp
|
||||
aprsgui.ui
|
||||
aprssettingsdialog.cpp
|
||||
aprssettingsdialog.ui
|
||||
aprs.qrc
|
||||
)
|
||||
set(aprs_HEADERS
|
||||
${aprs_HEADERS}
|
||||
aprsgui.h
|
||||
aprssettingsdialog.h
|
||||
)
|
||||
|
||||
set(TARGET_NAME aprs)
|
||||
set(TARGET_LIB "Qt5::Widgets" Qt5::Charts)
|
||||
set(TARGET_LIB_GUI "sdrgui")
|
||||
set(INSTALL_FOLDER ${INSTALL_PLUGINS_DIR})
|
||||
else()
|
||||
set(TARGET_NAME aprssrv)
|
||||
set(TARGET_LIB "")
|
||||
set(TARGET_LIB_GUI "")
|
||||
set(INSTALL_FOLDER ${INSTALL_PLUGINSSRV_DIR})
|
||||
endif()
|
||||
|
||||
add_library(${TARGET_NAME} SHARED
|
||||
${aprs_SOURCES}
|
||||
)
|
||||
|
||||
target_link_libraries(${TARGET_NAME}
|
||||
Qt5::Core
|
||||
${TARGET_LIB}
|
||||
sdrbase
|
||||
${TARGET_LIB_GUI}
|
||||
)
|
||||
|
||||
install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
|
||||
|
||||
if(WIN32)
|
||||
# Run deployqt for Charts etc
|
||||
include(DeployQt)
|
||||
windeployqt(${TARGET_NAME} ${SDRANGEL_BINARY_BIN_DIR} ${PROJECT_SOURCE_DIR}/aprs)
|
||||
endif()
|
415
plugins/feature/aprs/aprs.cpp
Normal file
@ -0,0 +1,415 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// Copyright (C) 2021 Jon Beniston, M7RCE //
|
||||
// Copyright (C) 2020 Edouard Griffiths, F4EXB //
|
||||
// //
|
||||
// This program is free software; you can redistribute it and/or modify //
|
||||
// it under the terms of the GNU General Public License as published by //
|
||||
// the Free Software Foundation as version 3 of the License, or //
|
||||
// (at your option) any later version. //
|
||||
// //
|
||||
// This program is distributed in the hope that it will be useful, //
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of //
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
|
||||
// GNU General Public License V3 for more details. //
|
||||
// //
|
||||
// You should have received a copy of the GNU General Public License //
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>. //
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <QDebug>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QBuffer>
|
||||
|
||||
#include "SWGFeatureSettings.h"
|
||||
#include "SWGFeatureReport.h"
|
||||
#include "SWGFeatureActions.h"
|
||||
#include "SWGDeviceState.h"
|
||||
|
||||
#include "dsp/dspengine.h"
|
||||
|
||||
#include "device/deviceset.h"
|
||||
#include "channel/channelapi.h"
|
||||
#include "maincore.h"
|
||||
#include "aprsworker.h"
|
||||
#include "aprs.h"
|
||||
|
||||
MESSAGE_CLASS_DEFINITION(APRS::MsgConfigureAPRS, Message)
|
||||
MESSAGE_CLASS_DEFINITION(APRS::MsgReportWorker, Message)
|
||||
|
||||
const char* const APRS::m_featureIdURI = "sdrangel.feature.aprs";
|
||||
const char* const APRS::m_featureId = "APRS";
|
||||
|
||||
APRS::APRS(WebAPIAdapterInterface *webAPIAdapterInterface) :
|
||||
Feature(m_featureIdURI, webAPIAdapterInterface)
|
||||
{
|
||||
qDebug("APRS::APRS: webAPIAdapterInterface: %p", webAPIAdapterInterface);
|
||||
setObjectName(m_featureId);
|
||||
m_worker = new APRSWorker(this, webAPIAdapterInterface);
|
||||
m_state = StIdle;
|
||||
m_errorMessage = "APRS error";
|
||||
connect(&m_updatePipesTimer, SIGNAL(timeout()), this, SLOT(updatePipes()));
|
||||
m_updatePipesTimer.start(1000);
|
||||
}
|
||||
|
||||
APRS::~APRS()
|
||||
{
|
||||
if (m_worker->isRunning()) {
|
||||
stop();
|
||||
}
|
||||
|
||||
delete m_worker;
|
||||
}
|
||||
|
||||
void APRS::start()
|
||||
{
|
||||
qDebug("APRS::start");
|
||||
m_worker->reset();
|
||||
m_worker->setMessageQueueToFeature(getInputMessageQueue());
|
||||
m_worker->setMessageQueueToGUI(getMessageQueueToGUI());
|
||||
bool ok = m_worker->startWork();
|
||||
m_state = ok ? StIdle : StError;
|
||||
m_thread.start();
|
||||
|
||||
APRSWorker::MsgConfigureAPRSWorker *msg = APRSWorker::MsgConfigureAPRSWorker::create(m_settings, true);
|
||||
m_worker->getInputMessageQueue()->push(msg);
|
||||
}
|
||||
|
||||
void APRS::stop()
|
||||
{
|
||||
qDebug("APRS::stop");
|
||||
m_worker->stopWork();
|
||||
m_state = StIdle;
|
||||
m_thread.quit();
|
||||
m_thread.wait();
|
||||
}
|
||||
|
||||
bool APRS::handleMessage(const Message& cmd)
|
||||
{
|
||||
if (MsgConfigureAPRS::match(cmd))
|
||||
{
|
||||
MsgConfigureAPRS& cfg = (MsgConfigureAPRS&) cmd;
|
||||
qDebug() << "APRS::handleMessage: MsgConfigureAPRS";
|
||||
applySettings(cfg.getSettings(), cfg.getForce());
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (MsgReportWorker::match(cmd))
|
||||
{
|
||||
MsgReportWorker& report = (MsgReportWorker&) cmd;
|
||||
if (report.getMessage() == "Connected")
|
||||
m_state = StRunning;
|
||||
else if (report.getMessage() == "Disconnected")
|
||||
m_state = StIdle;
|
||||
else
|
||||
{
|
||||
m_state = StError;
|
||||
m_errorMessage = report.getMessage();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (MainCore::MsgPacket::match(cmd))
|
||||
{
|
||||
MainCore::MsgPacket& report = (MainCore::MsgPacket&) cmd;
|
||||
if (getMessageQueueToGUI())
|
||||
{
|
||||
MainCore::MsgPacket *copy = new MainCore::MsgPacket(report);
|
||||
getMessageQueueToGUI()->push(copy);
|
||||
}
|
||||
if (m_state == StRunning)
|
||||
{
|
||||
MainCore::MsgPacket *copy = new MainCore::MsgPacket(report);
|
||||
m_worker->getInputMessageQueue()->push(copy);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void APRS::updatePipes()
|
||||
{
|
||||
QList<AvailablePipeSource> availablePipes = updateAvailablePipeSources("packets", APRSSettings::m_pipeTypes, APRSSettings::m_pipeURIs, this);
|
||||
|
||||
if (availablePipes != m_availablePipes)
|
||||
{
|
||||
m_availablePipes = availablePipes;
|
||||
if (getMessageQueueToGUI())
|
||||
{
|
||||
MsgReportPipes *msgToGUI = MsgReportPipes::create();
|
||||
QList<AvailablePipeSource>& msgAvailablePipes = msgToGUI->getAvailablePipes();
|
||||
msgAvailablePipes.append(availablePipes);
|
||||
getMessageQueueToGUI()->push(msgToGUI);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray APRS::serialize() const
|
||||
{
|
||||
return m_settings.serialize();
|
||||
}
|
||||
|
||||
bool APRS::deserialize(const QByteArray& data)
|
||||
{
|
||||
if (m_settings.deserialize(data))
|
||||
{
|
||||
MsgConfigureAPRS *msg = MsgConfigureAPRS::create(m_settings, true);
|
||||
m_inputMessageQueue.push(msg);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_settings.resetToDefaults();
|
||||
MsgConfigureAPRS *msg = MsgConfigureAPRS::create(m_settings, true);
|
||||
m_inputMessageQueue.push(msg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void APRS::applySettings(const APRSSettings& settings, bool force)
|
||||
{
|
||||
qDebug() << "APRS::applySettings:"
|
||||
<< " m_igateEnabled: " << settings.m_igateEnabled
|
||||
<< " 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_igateEnabled != settings.m_igateEnabled) || force)
|
||||
{
|
||||
if (settings.m_igateEnabled)
|
||||
start();
|
||||
else
|
||||
stop();
|
||||
reverseAPIKeys.append("igateEnabled");
|
||||
}
|
||||
|
||||
if ((m_settings.m_title != settings.m_title) || force) {
|
||||
reverseAPIKeys.append("title");
|
||||
}
|
||||
if ((m_settings.m_rgbColor != settings.m_rgbColor) || force) {
|
||||
reverseAPIKeys.append("rgbColor");
|
||||
}
|
||||
|
||||
APRSWorker::MsgConfigureAPRSWorker *msg = APRSWorker::MsgConfigureAPRSWorker::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 APRS::webapiRun(bool run,
|
||||
SWGSDRangel::SWGDeviceState& response,
|
||||
QString& errorMessage)
|
||||
{
|
||||
(void) run;
|
||||
(void) response;
|
||||
(void) errorMessage;
|
||||
//getFeatureStateStr(*response.getState());
|
||||
//MsgStartStopIGate *msg = MsgStartStopIGate::create(run);
|
||||
//getInputMessageQueue()->push(msg);
|
||||
return 202;
|
||||
}
|
||||
|
||||
int APRS::webapiSettingsGet(
|
||||
SWGSDRangel::SWGFeatureSettings& response,
|
||||
QString& errorMessage)
|
||||
{
|
||||
(void) errorMessage;
|
||||
response.setAprsSettings(new SWGSDRangel::SWGAPRSSettings());
|
||||
response.getAprsSettings()->init();
|
||||
webapiFormatFeatureSettings(response, m_settings);
|
||||
return 200;
|
||||
}
|
||||
|
||||
int APRS::webapiSettingsPutPatch(
|
||||
bool force,
|
||||
const QStringList& featureSettingsKeys,
|
||||
SWGSDRangel::SWGFeatureSettings& response,
|
||||
QString& errorMessage)
|
||||
{
|
||||
(void) errorMessage;
|
||||
APRSSettings settings = m_settings;
|
||||
webapiUpdateFeatureSettings(settings, featureSettingsKeys, response);
|
||||
|
||||
MsgConfigureAPRS *msg = MsgConfigureAPRS::create(settings, force);
|
||||
m_inputMessageQueue.push(msg);
|
||||
|
||||
qDebug("APRS::webapiSettingsPutPatch: forward to GUI: %p", m_guiMessageQueue);
|
||||
if (m_guiMessageQueue) // forward to GUI if any
|
||||
{
|
||||
MsgConfigureAPRS *msgToGUI = MsgConfigureAPRS::create(settings, force);
|
||||
m_guiMessageQueue->push(msgToGUI);
|
||||
}
|
||||
|
||||
webapiFormatFeatureSettings(response, settings);
|
||||
|
||||
return 200;
|
||||
}
|
||||
|
||||
void APRS::webapiFormatFeatureSettings(
|
||||
SWGSDRangel::SWGFeatureSettings& response,
|
||||
const APRSSettings& settings)
|
||||
{
|
||||
response.getAprsSettings()->setIgateServer(new QString(settings.m_igateServer));
|
||||
response.getAprsSettings()->setIgatePort(settings.m_igatePort);
|
||||
response.getAprsSettings()->setIgateCallsign(new QString(settings.m_igateCallsign));
|
||||
response.getAprsSettings()->setIgatePasscode(new QString(settings.m_igatePasscode));
|
||||
response.getAprsSettings()->setIgateFilter(new QString(settings.m_igateFilter));
|
||||
response.getAprsSettings()->setIgateEnabled(settings.m_igateEnabled ? 1 : 0);
|
||||
|
||||
if (response.getAprsSettings()->getTitle()) {
|
||||
*response.getAprsSettings()->getTitle() = settings.m_title;
|
||||
} else {
|
||||
response.getAprsSettings()->setTitle(new QString(settings.m_title));
|
||||
}
|
||||
|
||||
response.getAprsSettings()->setRgbColor(settings.m_rgbColor);
|
||||
response.getAprsSettings()->setUseReverseApi(settings.m_useReverseAPI ? 1 : 0);
|
||||
|
||||
if (response.getAprsSettings()->getReverseApiAddress()) {
|
||||
*response.getAprsSettings()->getReverseApiAddress() = settings.m_reverseAPIAddress;
|
||||
} else {
|
||||
response.getAprsSettings()->setReverseApiAddress(new QString(settings.m_reverseAPIAddress));
|
||||
}
|
||||
|
||||
response.getAprsSettings()->setReverseApiPort(settings.m_reverseAPIPort);
|
||||
response.getAprsSettings()->setReverseApiDeviceIndex(settings.m_reverseAPIFeatureSetIndex);
|
||||
response.getAprsSettings()->setReverseApiChannelIndex(settings.m_reverseAPIFeatureIndex);
|
||||
}
|
||||
|
||||
void APRS::webapiUpdateFeatureSettings(
|
||||
APRSSettings& settings,
|
||||
const QStringList& featureSettingsKeys,
|
||||
SWGSDRangel::SWGFeatureSettings& response)
|
||||
{
|
||||
if (featureSettingsKeys.contains("igateServer")) {
|
||||
settings.m_igateServer = *response.getAprsSettings()->getIgateServer();
|
||||
}
|
||||
if (featureSettingsKeys.contains("igatePort")) {
|
||||
settings.m_igatePort = response.getAprsSettings()->getIgatePort();
|
||||
}
|
||||
if (featureSettingsKeys.contains("igateCallsign")) {
|
||||
settings.m_igateCallsign = *response.getAprsSettings()->getIgateCallsign();
|
||||
}
|
||||
if (featureSettingsKeys.contains("igatePasscode")) {
|
||||
settings.m_igatePasscode = *response.getAprsSettings()->getIgatePasscode();
|
||||
}
|
||||
if (featureSettingsKeys.contains("igateFilter")) {
|
||||
settings.m_igateFilter = *response.getAprsSettings()->getIgateFilter();
|
||||
}
|
||||
if (featureSettingsKeys.contains("title")) {
|
||||
settings.m_title = *response.getAprsSettings()->getTitle();
|
||||
}
|
||||
if (featureSettingsKeys.contains("rgbColor")) {
|
||||
settings.m_rgbColor = response.getAprsSettings()->getRgbColor();
|
||||
}
|
||||
if (featureSettingsKeys.contains("useReverseAPI")) {
|
||||
settings.m_useReverseAPI = response.getAprsSettings()->getUseReverseApi() != 0;
|
||||
}
|
||||
if (featureSettingsKeys.contains("reverseAPIAddress")) {
|
||||
settings.m_reverseAPIAddress = *response.getAprsSettings()->getReverseApiAddress();
|
||||
}
|
||||
if (featureSettingsKeys.contains("reverseAPIPort")) {
|
||||
settings.m_reverseAPIPort = response.getAprsSettings()->getReverseApiPort();
|
||||
}
|
||||
if (featureSettingsKeys.contains("reverseAPIDeviceIndex")) {
|
||||
settings.m_reverseAPIFeatureSetIndex = response.getAprsSettings()->getReverseApiDeviceIndex();
|
||||
}
|
||||
if (featureSettingsKeys.contains("reverseAPIChannelIndex")) {
|
||||
settings.m_reverseAPIFeatureIndex = response.getAprsSettings()->getReverseApiChannelIndex();
|
||||
}
|
||||
}
|
||||
|
||||
void APRS::webapiReverseSendSettings(QList<QString>& featureSettingsKeys, const APRSSettings& settings, bool force)
|
||||
{
|
||||
SWGSDRangel::SWGFeatureSettings *swgFeatureSettings = new SWGSDRangel::SWGFeatureSettings();
|
||||
// swgFeatureSettings->setOriginatorFeatureIndex(getIndexInDeviceSet());
|
||||
// swgFeatureSettings->setOriginatorFeatureSetIndex(getDeviceSetIndex());
|
||||
swgFeatureSettings->setFeatureType(new QString("APRS"));
|
||||
swgFeatureSettings->setAprsSettings(new SWGSDRangel::SWGAPRSSettings());
|
||||
SWGSDRangel::SWGAPRSSettings *swgAPRSSettings = swgFeatureSettings->getAprsSettings();
|
||||
|
||||
// transfer data that has been modified. When force is on transfer all data except reverse API data
|
||||
|
||||
if (featureSettingsKeys.contains("igateServer") || force) {
|
||||
swgAPRSSettings->setIgateServer(new QString(settings.m_igateServer));
|
||||
}
|
||||
if (featureSettingsKeys.contains("igatePort") || force) {
|
||||
swgAPRSSettings->setIgatePort(settings.m_igatePort);
|
||||
}
|
||||
if (featureSettingsKeys.contains("igateCallsign") || force) {
|
||||
swgAPRSSettings->setIgateCallsign(new QString(settings.m_igateCallsign));
|
||||
}
|
||||
if (featureSettingsKeys.contains("igatePasscode") || force) {
|
||||
swgAPRSSettings->setIgatePasscode(new QString(settings.m_igatePasscode));
|
||||
}
|
||||
if (featureSettingsKeys.contains("igateFilter") || force) {
|
||||
swgAPRSSettings->setIgateFilter(new QString(settings.m_igateFilter));
|
||||
}
|
||||
if (featureSettingsKeys.contains("title") || force) {
|
||||
swgAPRSSettings->setTitle(new QString(settings.m_title));
|
||||
}
|
||||
if (featureSettingsKeys.contains("rgbColor") || force) {
|
||||
swgAPRSSettings->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 APRS::networkManagerFinished(QNetworkReply *reply)
|
||||
{
|
||||
QNetworkReply::NetworkError replyError = reply->error();
|
||||
|
||||
if (replyError)
|
||||
{
|
||||
qWarning() << "APRS::networkManagerFinished:"
|
||||
<< " error(" << (int) replyError
|
||||
<< "): " << replyError
|
||||
<< ": " << reply->errorString();
|
||||
}
|
||||
else
|
||||
{
|
||||
QString answer = reply->readAll();
|
||||
answer.chop(1); // remove last \n
|
||||
qDebug("APRS::networkManagerFinished: reply:\n%s", answer.toStdString().c_str());
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
}
|
143
plugins/feature/aprs/aprs.h
Normal file
@ -0,0 +1,143 @@
|
||||
///////////////////////////////////////////////////////////////////////////////////
|
||||
// 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_APRS_H_
|
||||
#define INCLUDE_FEATURE_APRS_H_
|
||||
|
||||
#include <QThread>
|
||||
#include <QHash>
|
||||
#include <QNetworkRequest>
|
||||
#include <QTimer>
|
||||
|
||||
#include "feature/feature.h"
|
||||
#include "util/message.h"
|
||||
|
||||
#include "aprssettings.h"
|
||||
|
||||
class WebAPIAdapterInterface;
|
||||
class APRSWorker;
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
|
||||
namespace SWGSDRangel {
|
||||
class SWGDeviceState;
|
||||
}
|
||||
|
||||
class APRS : public Feature
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
class MsgConfigureAPRS : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
const APRSSettings& getSettings() const { return m_settings; }
|
||||
bool getForce() const { return m_force; }
|
||||
|
||||
static MsgConfigureAPRS* create(const APRSSettings& settings, bool force) {
|
||||
return new MsgConfigureAPRS(settings, force);
|
||||
}
|
||||
|
||||
private:
|
||||
APRSSettings m_settings;
|
||||
bool m_force;
|
||||
|
||||
MsgConfigureAPRS(const APRSSettings& settings, bool force) :
|
||||
Message(),
|
||||
m_settings(settings),
|
||||
m_force(force)
|
||||
{ }
|
||||
};
|
||||
|
||||
class MsgReportWorker : public Message {
|
||||
MESSAGE_CLASS_DECLARATION
|
||||
|
||||
public:
|
||||
QString getMessage() { return m_message; }
|
||||
|
||||
static MsgReportWorker* create(QString message) {
|
||||
return new MsgReportWorker(message);
|
||||
}
|
||||
|
||||
private:
|
||||
QString m_message;
|
||||
|
||||
MsgReportWorker(QString message) :
|
||||
Message(),
|
||||
m_message(message)
|
||||
{}
|
||||
};
|
||||
|
||||
APRS(WebAPIAdapterInterface *webAPIAdapterInterface);
|
||||
virtual ~APRS();
|
||||
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 APRSSettings& settings);
|
||||
|
||||
static void webapiUpdateFeatureSettings(
|
||||
APRSSettings& settings,
|
||||
const QStringList& featureSettingsKeys,
|
||||
SWGSDRangel::SWGFeatureSettings& response);
|
||||
|
||||
static const char* const m_featureIdURI;
|
||||
static const char* const m_featureId;
|
||||
|
||||
private:
|
||||
QThread m_thread;
|
||||
APRSWorker *m_worker;
|
||||
APRSSettings m_settings;
|
||||
QList<PipeEndPoint::AvailablePipeSource> m_availablePipes;
|
||||
QTimer m_updatePipesTimer;
|
||||
|
||||
QNetworkAccessManager *m_networkManager;
|
||||
QNetworkRequest m_networkRequest;
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
void applySettings(const APRSSettings& settings, bool force = false);
|
||||
void webapiReverseSendSettings(QList<QString>& featureSettingsKeys, const APRSSettings& settings, bool force);
|
||||
|
||||
private slots:
|
||||
void updatePipes();
|
||||
void networkManagerFinished(QNetworkReply *reply);
|
||||
};
|
||||
|
||||
#endif // INCLUDE_FEATURE_APRS_H_
|
196
plugins/feature/aprs/aprs.qrc
Normal file
@ -0,0 +1,196 @@
|
||||
<RCC>
|
||||
<qresource prefix="/aprs/">
|
||||
<file>aprs/aprs-symbols-24-0-00.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-01.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-02.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-03.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-04.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-05.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-06.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-07.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-08.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-09.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-10.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-11.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-12.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-13.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-14.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-15.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-16.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-17.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-18.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-19.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-20.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-21.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-22.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-23.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-24.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-25.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-26.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-27.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-28.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-29.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-30.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-31.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-32.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-33.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-34.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-35.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-36.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-37.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-38.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-39.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-40.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-41.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-42.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-43.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-44.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-45.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-46.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-47.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-48.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-49.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-50.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-51.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-52.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-53.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-54.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-55.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-56.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-57.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-58.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-59.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-60.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-61.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-62.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-63.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-64.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-65.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-66.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-67.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-68.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-69.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-70.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-71.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-72.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-73.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-74.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-75.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-76.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-77.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-78.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-79.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-80.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-81.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-82.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-83.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-84.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-85.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-86.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-87.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-88.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-89.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-90.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-91.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-92.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-93.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-94.png</file>
|
||||
<file>aprs/aprs-symbols-24-0-95.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-00.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-01.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-02.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-03.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-04.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-05.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-06.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-07.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-08.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-09.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-10.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-11.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-12.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-13.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-14.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-15.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-16.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-17.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-18.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-19.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-20.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-21.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-22.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-23.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-24.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-25.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-26.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-27.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-28.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-29.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-30.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-31.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-32.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-33.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-34.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-35.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-36.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-37.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-38.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-39.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-40.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-41.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-42.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-43.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-44.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-45.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-46.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-47.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-48.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-49.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-50.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-51.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-52.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-53.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-54.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-55.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-56.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-57.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-58.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-59.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-60.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-61.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-62.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-63.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-64.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-65.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-66.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-67.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-68.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-69.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-70.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-71.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-72.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-73.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-74.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-75.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-76.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-77.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-78.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-79.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-80.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-81.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-82.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-83.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-84.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-85.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-86.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-87.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-88.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-89.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-90.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-91.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-92.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-93.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-94.png</file>
|
||||
<file>aprs/aprs-symbols-24-1-95.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
6
plugins/feature/aprs/aprs/README.txt
Normal file
@ -0,0 +1,6 @@
|
||||
APRS images are from: https://github.com/hessu/aprs-symbols/tree/master/png
|
||||
|
||||
To split in to individual files, using ImageMagick:
|
||||
|
||||
convert aprs-symbols-24-0.png -crop 24x24 aprs-symbols-24-0-%02d.png
|
||||
convert aprs-symbols-24-1.png -crop 24x24 aprs-symbols-24-1-%02d.png
|
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-00.png
Normal file
After Width: | Height: | Size: 617 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-01.png
Normal file
After Width: | Height: | Size: 523 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-02.png
Normal file
After Width: | Height: | Size: 661 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-03.png
Normal file
After Width: | Height: | Size: 544 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-04.png
Normal file
After Width: | Height: | Size: 586 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-05.png
Normal file
After Width: | Height: | Size: 566 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-06.png
Normal file
After Width: | Height: | Size: 867 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-07.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-08.png
Normal file
After Width: | Height: | Size: 535 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-09.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-10.png
Normal file
After Width: | Height: | Size: 372 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-11.png
Normal file
After Width: | Height: | Size: 544 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-12.png
Normal file
After Width: | Height: | Size: 511 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-13.png
Normal file
After Width: | Height: | Size: 533 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-14.png
Normal file
After Width: | Height: | Size: 623 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-15.png
Normal file
After Width: | Height: | Size: 500 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-16.png
Normal file
After Width: | Height: | Size: 461 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-17.png
Normal file
After Width: | Height: | Size: 531 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-18.png
Normal file
After Width: | Height: | Size: 538 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-19.png
Normal file
After Width: | Height: | Size: 517 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-20.png
Normal file
After Width: | Height: | Size: 525 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-21.png
Normal file
After Width: | Height: | Size: 536 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-22.png
Normal file
After Width: | Height: | Size: 509 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-23.png
Normal file
After Width: | Height: | Size: 536 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-24.png
Normal file
After Width: | Height: | Size: 531 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-25.png
Normal file
After Width: | Height: | Size: 847 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-26.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-27.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-28.png
Normal file
After Width: | Height: | Size: 747 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-29.png
Normal file
After Width: | Height: | Size: 855 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-30.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-31.png
Normal file
After Width: | Height: | Size: 764 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-32.png
Normal file
After Width: | Height: | Size: 379 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-33.png
Normal file
After Width: | Height: | Size: 528 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-34.png
Normal file
After Width: | Height: | Size: 725 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-35.png
Normal file
After Width: | Height: | Size: 535 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-36.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-37.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-38.png
Normal file
After Width: | Height: | Size: 380 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-39.png
Normal file
After Width: | Height: | Size: 473 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-40.png
Normal file
After Width: | Height: | Size: 713 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-41.png
Normal file
After Width: | Height: | Size: 509 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-42.png
Normal file
After Width: | Height: | Size: 688 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-43.png
Normal file
After Width: | Height: | Size: 771 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-44.png
Normal file
After Width: | Height: | Size: 691 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-45.png
Normal file
After Width: | Height: | Size: 560 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-46.png
Normal file
After Width: | Height: | Size: 821 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-47.png
Normal file
After Width: | Height: | Size: 730 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-48.png
Normal file
After Width: | Height: | Size: 716 B |
BIN
plugins/feature/aprs/aprs/aprs-symbols-24-0-49.png
Normal file
After Width: | Height: | Size: 534 B |