M17: implemented APRS

This commit is contained in:
f4exb 2022-07-03 10:06:12 +02:00
parent b69275949a
commit 3cf3938757
17 changed files with 647 additions and 11 deletions

View File

@ -45,6 +45,7 @@
MESSAGE_CLASS_DEFINITION(M17Demod::MsgConfigureM17Demod, Message)
MESSAGE_CLASS_DEFINITION(M17Demod::MsgReportSMS, Message)
MESSAGE_CLASS_DEFINITION(M17Demod::MsgReportAPRS, Message)
const char* const M17Demod::m_channelIdURI = "sdrangel.channel.m17demod";
const char* const M17Demod::m_channelId = "M17Demod";
@ -185,6 +186,27 @@ bool M17Demod::handleMessage(const Message& cmd)
return true;
}
else if (MsgReportAPRS::match(cmd))
{
MsgReportAPRS& report = (MsgReportAPRS&) cmd;
// Forward to GUI if any
if (getMessageQueueToGUI()) {
getMessageQueueToGUI()->push(new MsgReportAPRS(report));
}
// Forward to APRS and other packet features
QList<ObjectPipe*> packetsPipes;
MainCore::instance()->getMessagePipes().getMessagePipes(this, "packets", packetsPipes);
for (const auto& pipe : packetsPipes)
{
MessageQueue *messageQueue = qobject_cast<MessageQueue*>(pipe->m_element);
MainCore::MsgPacket *msg = MainCore::MsgPacket::create(this, report.getPacket(), QDateTime::currentDateTime());
messageQueue->push(msg);
}
return true;
}
else
{
return false;

View File

@ -85,6 +85,67 @@ public:
{ }
};
class MsgReportAPRS : public Message {
MESSAGE_CLASS_DECLARATION
public:
const QString& getSource() const { return m_source; }
const QString& getDest() const { return m_dest; }
const QString& getFrom() const { return m_from; }
const QString& getTo() const { return m_to; }
const QString& getVia() const { return m_via; }
const QString& getType() const { return m_type; }
const QString& getPID() const { return m_pid; }
const QString& getData() const { return m_data; }
QByteArray& getPacket() { return m_packet; }
static MsgReportAPRS* create(
const QString& source,
const QString& dest,
const QString& from,
const QString& to,
const QString& via,
const QString& type,
const QString& pid,
const QString& data
)
{
return new MsgReportAPRS(source, dest, from, to, via, type, pid, data);
}
private:
QString m_source;
QString m_dest;
QString m_from;
QString m_to;
QString m_via;
QString m_type;
QString m_pid;
QString m_data;
QByteArray m_packet;
MsgReportAPRS(
const QString& source,
const QString& dest,
const QString& from,
const QString& to,
const QString& via,
const QString& type,
const QString& pid,
const QString& data
) :
Message(),
m_source(source),
m_dest(dest),
m_from(from),
m_to(to),
m_via(via),
m_type(type),
m_pid(pid),
m_data(data)
{ }
};
M17Demod(DeviceAPI *deviceAPI);
virtual ~M17Demod();
virtual void destroy() { delete this; }

View File

@ -20,6 +20,8 @@
#include <QMainWindow>
#include <QDebug>
#include <QScrollBar>
#include <QMessageBox>
#include <QFileDialog>
#include <complex>
@ -30,6 +32,7 @@
#include "plugin/pluginapi.h"
#include "util/simpleserializer.h"
#include "util/db.h"
#include "util/csv.h"
#include "gui/basicchannelsettingsdialog.h"
#include "gui/devicestreamselectiondialog.h"
#include "gui/crightclickenabler.h"
@ -131,6 +134,43 @@ bool M17DemodGUI::handleMessage(const Message& message)
return true;
}
else if (M17Demod::MsgReportAPRS::match(message))
{
const M17Demod::MsgReportAPRS& report = (M17Demod::MsgReportAPRS&) message;
// Is scroll bar at bottom
QScrollBar *sb = ui->aprsPackets->verticalScrollBar();
bool scrollToBottom = sb->value() == sb->maximum();
ui->aprsPackets->setSortingEnabled(false);
int row = ui->aprsPackets->rowCount();
ui->aprsPackets->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();
ui->aprsPackets->setItem(row, 0, fromItem);
ui->aprsPackets->setItem(row, 1, toItem);
ui->aprsPackets->setItem(row, 2, viaItem);
ui->aprsPackets->setItem(row, 3, typeItem);
ui->aprsPackets->setItem(row, 4, pidItem);
ui->aprsPackets->setItem(row, 5, dataASCIIItem);
fromItem->setText(report.getFrom());
toItem->setText(report.getTo());
viaItem->setText(report.getVia());
typeItem->setText(report.getType());
pidItem->setText(report.getPID());
dataASCIIItem->setText(report.getData());
ui->aprsPackets->setSortingEnabled(true);
if (scrollToBottom) {
ui->aprsPackets->scrollToBottom();
}
return true;
}
else
{
return false;
@ -239,6 +279,11 @@ void M17DemodGUI::on_highPassFilter_toggled(bool checked)
applySettings();
}
void M17DemodGUI::on_aprsClearTable_clicked()
{
ui->aprsPackets->setRowCount(0);
}
void M17DemodGUI::onWidgetRolled(QWidget* widget, bool rollDown)
{
(void) widget;
@ -655,6 +700,7 @@ void M17DemodGUI::makeUIConnections()
QObject::connect(ui->highPassFilter, &ButtonSwitch::toggled, this, &M17DemodGUI::on_highPassFilter_toggled);
QObject::connect(ui->audioMute, &QToolButton::toggled, this, &M17DemodGUI::on_audioMute_toggled);
QObject::connect(ui->viewStatusLog, &QPushButton::clicked, this, &M17DemodGUI::on_viewStatusLog_clicked);
QObject::connect(ui->aprsClearTable, &QPushButton::clicked, this, &M17DemodGUI::on_aprsClearTable_clicked);
}
void M17DemodGUI::updateAbsoluteCenterFrequency()

View File

@ -111,6 +111,7 @@ private:
void makeUIConnections();
void updateAbsoluteCenterFrequency();
QString getStatus(int status, bool streamElsePacket, int packetProtocol);
void packetReceived(QByteArray packet);
void leaveEvent(QEvent*);
void enterEvent(QEvent*);
@ -129,6 +130,7 @@ private slots:
void on_squelch_valueChanged(int value);
void on_highPassFilter_toggled(bool checked);
void on_audioMute_toggled(bool checked);
void on_aprsClearTable_clicked();
void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p);
void on_viewStatusLog_clicked();

View File

@ -732,7 +732,7 @@
<enum>QTabWidget::East</enum>
</property>
<property name="currentIndex">
<number>0</number>
<number>2</number>
</property>
<widget class="QWidget" name="settingsTab">
<property name="toolTip">
@ -1549,6 +1549,90 @@
<attribute name="toolTip">
<string>APRS</string>
</attribute>
<widget class="QTableWidget" name="aprsPackets">
<property name="geometry">
<rect>
<x>0</x>
<y>24</y>
<width>480</width>
<height>186</height>
</rect>
</property>
<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>
</widget>
<widget class="QPushButton" name="aprsClearTable">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>24</width>
<height>21</height>
</rect>
</property>
<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>
</widget>
</widget>
</item>

View File

@ -23,6 +23,7 @@
#include <QDebug>
#include "audio/audiofifo.h"
#include "util/ax25.h"
#include "m17/ax25_frame.h"
#include "m17demod.h"
@ -148,10 +149,6 @@ void M17DemodProcessor::diagnostic_callback(
oss << buffer;
qDebug() << "M17DemodProcessor::diagnostic_callback: " << oss.str().c_str();
}
if (status == 0) { // unlocked
m_this->resetInfo();
}
}
bool M17DemodProcessor::decode_lich(mobilinkd::M17FrameDecoder::lich_buffer_t const& lich)
@ -361,9 +358,9 @@ bool M17DemodProcessor::decode_packet(mobilinkd::M17FrameDecoder::packet_buffer_
oss << *it;
}
qDebug() << "M17DemodProcessor::decode_packet: "
<< " From:" << getSrcCall()
<< " To:" << getDestcCall()
qDebug() << "M17DemodProcessor::decode SMS_packet: "
<< " Src:" << getSrcCall()
<< " Dest:" << getDestcCall()
<< " SMS:" << oss.str().c_str();
if (m_demodInputMessageQueue)
@ -376,6 +373,40 @@ bool M17DemodProcessor::decode_packet(mobilinkd::M17FrameDecoder::packet_buffer_
m_demodInputMessageQueue->push(msg);
}
}
else if (m_stdPacketProtocol == StdPacketAPRS)
{
AX25Packet ax25;
QByteArray packet = QByteArray(reinterpret_cast<const char*>(&m_currentPacket[1]), m_currentPacket.size()-3);
if (ax25.decode(packet))
{
qDebug() << "M17DemodProcessor::decode APRS_packet: "
<< " Src:" << getSrcCall()
<< " Dest:" << getDestcCall()
<< " From:" << ax25.m_from
<< " To: " << ax25.m_to
<< " Via: " << ax25.m_via
<< " Type: " << ax25.m_type
<< " PID: " << ax25.m_pid
<< " Data: " << ax25.m_dataASCII;
if (m_demodInputMessageQueue)
{
M17Demod::MsgReportAPRS *msg = M17Demod::MsgReportAPRS::create(
getSrcCall(),
getDestcCall(),
ax25.m_from,
ax25.m_to,
ax25.m_via,
ax25.m_type,
ax25.m_pid,
ax25.m_dataASCII
);
msg->getPacket() = packet;
m_demodInputMessageQueue->push(msg);
}
}
}
return true;
}

View File

@ -43,6 +43,8 @@ struct M17DemodSettings
int m_traceStroke; // [0..255]
int m_traceDecay; // [0..255]
QString m_audioDeviceName;
QString m_aprsLogFilename;
bool m_aprsLogEnabled;
int m_streamIndex; //!< MIMO channel. Not relevant when connected to SI (single Rx).
bool m_useReverseAPI;
QString m_reverseAPIAddress;

View File

@ -174,7 +174,10 @@ void M17DemodSink::feed(const SampleVector::const_iterator& begin, const SampleV
if (m_squelchWasOpen)
{
m_m17DemodProcessor.resetInfo();
if (m_m17DemodProcessor.getStreamElsePacket()) { // if packet kepp last values
m_m17DemodProcessor.resetInfo();
}
m_m17DemodProcessor.setDCDOff(); // indicate loss of carrier
}
}

View File

@ -7,6 +7,7 @@ set(modm17_SOURCES
m17modprocessor.cpp
m17modfifo.cpp
m17moddecimator.cpp
m17modax25.cpp
m17modplugin.cpp
m17modsettings.cpp
m17modwebapiadapter.cpp
@ -19,6 +20,7 @@ set(modm17_HEADERS
m17modprocessor.h
m17modfifo.h
m17moddecimator.h
m17modax25.h
m17modplugin.h
m17modsettings.h
m17modwebapiadapter.h

View File

@ -0,0 +1,148 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 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 <QString>
#include <array>
#include "util/crc.h"
#include "m17modax25.h"
M17ModAX25::M17ModAX25() :
m_ax25Control(3),
m_ax25PID(AX25_NO_L3)
{}
M17ModAX25::~M17ModAX25()
{}
QByteArray M17ModAX25::makePacket(const QString& callsign, const QString& to, const QString& via, const QString& data)
{
std::array<uint8_t, AX25_MAX_BYTES> packet;
uint8_t *crc_start;
uint8_t *p;
crc16x25 crc;
uint16_t crcValue;
int len;
int packet_length;
// Create AX.25 packet
p = packet.data();
// Unique preamble flag
// *p++ = AX25_FLAG;
crc_start = p;
// Dest
p = ax25_address(p, to, 0xe0);
// From
p = ax25_address(p, callsign, 0x60);
// Via
p = ax25_address(p, via, 0x61);
// Control
*p++ = m_ax25Control;
// PID
*p++ = m_ax25PID;
// Data
len = data.length();
memcpy(p, data.toUtf8(), len);
p += len;
// CRC (do not include flags)
crc.calculate(crc_start, p-crc_start);
crcValue = crc.get();
*p++ = crcValue & 0xff;
*p++ = (crcValue >> 8);
// Unique postamble flag
// *p++ = AX25_FLAG;
packet_length = p-&packet[0];
return QByteArray(reinterpret_cast<const char*>(packet.data()), packet_length);
}
uint8_t *M17ModAX25::ax25_address(uint8_t *p, QString address, uint8_t crrl)
{
int len;
int i;
QByteArray b;
uint8_t ssid = 0;
bool hyphenSeen = false;
len = address.length();
b = address.toUtf8();
ssid = 0;
for (i = 0; i < 6; i++)
{
if ((i < len) && !hyphenSeen)
{
if (b[i] == '-')
{
ax25_ssid(b, i, len, ssid);
hyphenSeen = true;
*p++ = ' ' << 1;
}
else
{
*p++ = b[i] << 1;
}
}
else
{
*p++ = ' ' << 1;
}
}
if (b[i] == '-') {
ax25_ssid(b, i, len, ssid);
}
*p++ = crrl | (ssid << 1);
return p;
}
bool M17ModAX25::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("M17ModAX25::ax25_ssid: ax25_address: SSID greater than 15 not supported");
ssid = ssid & 0xf;
return false;
}
else
{
return true;
}
}
else
{
qDebug("M17ModAX25::ax25_ssid: ax25_address: SSID number missing");
return false;
}
}
else
{
return false;
}
}

View File

@ -0,0 +1,50 @@
///////////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2022 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 PLUGINS_CHANNELTX_MODM17_M17MODAX25_H_
#define PLUGINS_CHANNELTX_MODM17_M17MODAX25_H_
#include <QByteArray>
#include <cstdint>
class QString;
class M17ModAX25
{
public:
M17ModAX25();
~M17ModAX25();
void setAX25Control(int ax25Control) { m_ax25Control = ax25Control; }
void setAX25PID(int ax25PID) { m_ax25PID = ax25PID; }
QByteArray makePacket(const QString& callsign, const QString& to, const QString& via, const QString& data);
static const int AX25_MAX_FLAGS = 1024;
static const int AX25_MAX_BYTES = (2*AX25_MAX_FLAGS+1+28+2+256+2+1);
static const int AX25_MAX_BITS = (AX25_MAX_BYTES*2);
static const uint8_t AX25_FLAG = 0x7e;
static const uint8_t AX25_NO_L3 = 0xf0;
private:
int m_ax25Control;
int m_ax25PID;
static uint8_t *ax25_address(uint8_t *p, QString address, uint8_t crrl);
static bool ax25_ssid(QByteArray& b, int i, int len, uint8_t& ssid);
};
#endif // PLUGINS_CHANNELTX_MODM17_M17MODAX25_H_

View File

@ -347,6 +347,36 @@ void M17ModGUI::on_smsText_editingFinished()
applySettings();
}
void M17ModGUI::on_aprsFromText_editingFinished()
{
m_settings.m_aprsCallsign = ui->aprsFromText->text();
applySettings();
}
void M17ModGUI::on_aprsTo_currentTextChanged(const QString &text)
{
m_settings.m_aprsTo = text;
applySettings();
}
void M17ModGUI::on_aprsVia_currentTextChanged(const QString &text)
{
m_settings.m_aprsVia = text;
applySettings();
}
void M17ModGUI::on_aprsData_editingFinished()
{
m_settings.m_aprsData = ui->aprsData->text();
applySettings();
}
void M17ModGUI::on_aprsInsertPosition_toggled(bool checked)
{
m_settings.m_aprsInsertPosition = checked;
applySettings();
}
void M17ModGUI::configureFileName()
{
qDebug() << "M17ModGUI::configureFileName: " << m_fileName.toStdString().c_str();
@ -556,6 +586,7 @@ void M17ModGUI::displaySettings()
ui->aprsData->setText(m_settings.m_aprsData);
ui->aprsTo->lineEdit()->setText(m_settings.m_aprsTo);
ui->aprsVia->lineEdit()->setText(m_settings.m_aprsVia);
ui->aprsInsertPosition->setChecked(m_settings.m_aprsInsertPosition);
getRollupContents()->restoreState(m_rollupState);
updateAbsoluteCenterFrequency();
@ -764,7 +795,13 @@ void M17ModGUI::makeUIConnections()
QObject::connect(ui->sendPacket, &QPushButton::clicked, this, &M17ModGUI::on_sendPacket_clicked);
QObject::connect(ui->loopPacket, &ButtonSwitch::toggled, this, &M17ModGUI::on_loopPacket_toggled);
QObject::connect(ui->loopPacketInterval, &QDial::valueChanged, this, &M17ModGUI::on_loopPacketInterval_valueChanged);
QObject::connect(ui->packetDataWidget, &QTabWidget::currentChanged, this, &M17ModGUI::on_packetDataWidget_currentChanged);
QObject::connect(ui->smsText, &CustomTextEdit::editingFinished, this, &M17ModGUI::on_smsText_editingFinished);
QObject::connect(ui->aprsFromText, &QLineEdit::editingFinished, this, &M17ModGUI::on_aprsFromText_editingFinished);
QObject::connect(ui->aprsTo, &QComboBox::currentTextChanged, this, &M17ModGUI::on_aprsTo_currentTextChanged);
QObject::connect(ui->aprsVia, &QComboBox::currentTextChanged, this, &M17ModGUI::on_aprsVia_currentTextChanged);
QObject::connect(ui->aprsData, &QLineEdit::editingFinished, this, &M17ModGUI::on_aprsData_editingFinished);
QObject::connect(ui->aprsInsertPosition, &ButtonSwitch::toggled, this, &M17ModGUI::on_aprsInsertPosition_toggled);
QObject::connect(ui->source, &QLineEdit::editingFinished, this, &M17ModGUI::on_source_editingFinished);
QObject::connect(ui->destination, &QLineEdit::editingFinished, this, &M17ModGUI::on_destination_editingFinished);
QObject::connect(ui->can, QOverload<int>::of(&QSpinBox::valueChanged), this, &M17ModGUI::on_can_valueChanged);

View File

@ -142,8 +142,15 @@ private slots:
void on_destination_editingFinished();
void on_insertPosition_toggled(bool checked);
void on_can_valueChanged(int value);
void on_smsText_editingFinished();
void on_aprsFromText_editingFinished();
void on_aprsTo_currentTextChanged(const QString &text);
void on_aprsVia_currentTextChanged(const QString &text);
void on_aprsData_editingFinished();
void on_aprsInsertPosition_toggled(bool checked);
void onWidgetRolled(QWidget* widget, bool rollDown);
void onMenuDialogCalled(const QPoint& p);

View File

@ -18,10 +18,13 @@
#include <codec2/codec2.h>
#include "m17/M17Modulator.h"
#include "maincore.h"
#include "m17modax25.h"
#include "m17modprocessor.h"
MESSAGE_CLASS_DEFINITION(M17ModProcessor::MsgSendSMS, Message)
MESSAGE_CLASS_DEFINITION(M17ModProcessor::MsgSendAPRS, Message)
MESSAGE_CLASS_DEFINITION(M17ModProcessor::MsgSendAudioFrame, Message)
MESSAGE_CLASS_DEFINITION(M17ModProcessor::MsgStartAudio, Message)
MESSAGE_CLASS_DEFINITION(M17ModProcessor::MsgStopAudio, Message)
@ -57,6 +60,24 @@ bool M17ModProcessor::handleMessage(const Message& cmd)
// test(notif.getSourceCall(), notif.getDestCall());
return true;
}
else if (MsgSendAPRS::match(cmd))
{
const MsgSendAPRS& notif = (const MsgSendAPRS&) cmd;
M17ModAX25 modAX25;
QString strData;
if (notif.getInsertPosition()) {
strData += "!" + formatAPRSPosition();
} else {
strData = notif.getData();
}
QByteArray packetBytes = modAX25.makePacket(notif.getCall(), notif.getTo(), notif.getVia(), strData);
packetBytes.prepend(0x02); // APRS standard type
packetBytes.truncate(798); // Maximum packet size is 798 payload + 2 bytes CRC = 800 bytes (32*25)
processPacket(notif.getSourceCall(), notif.getDestCall(), notif.getCAN(), packetBytes);
return true;
}
else if (MsgSendAudioFrame::match(cmd))
{
MsgSendAudioFrame& notif = (MsgSendAudioFrame&) cmd;
@ -248,3 +269,47 @@ void M17ModProcessor::output_baseband(std::array<uint8_t, 2> sync_word, const st
std::array<int16_t, 1920> baseband = m_m17Modulator.symbols_to_baseband(temp); // 1920 48 kS/s int16_t samples
m_basebandFifo.write(baseband.data(), 1920);
}
QString M17ModProcessor::formatAPRSPosition()
{
float latitude = MainCore::instance()->getSettings().getLatitude();
float longitude = MainCore::instance()->getSettings().getLongitude();
int latDeg, latMin, latFrac, latNorth;
int longDeg, longMin, longFrac, longEast;
// Convert decimal latitude to degrees, min and hundreths of a minute
latNorth = latitude >= 0.0f;
latitude = abs(latitude);
latDeg = (int) latitude;
latitude -= (float) latDeg;
latitude *= 60.0f;
latMin = (int) latitude;
latitude -= (float) latMin;
latitude *= 100.0f;
latFrac = round(latitude);
// Convert decimal longitude
longEast = longitude >= 0.0f;
longitude = abs(longitude);
longDeg = (int) longitude;
longitude -= (float) longDeg;
longitude *= 60.0f;
longMin = (int) longitude;
longitude -= (float) longMin;
longitude *= 100.0f;
longFrac = round(longitude);
// Insert position with house symbol (-) in to data field
QString latStr = QString("%1%2.%3%4")
.arg(latDeg, 2, 10, QChar('0'))
.arg(latMin, 2, 10, QChar('0'))
.arg(latFrac, 2, 10, QChar('0'))
.arg(latNorth ? 'N' : 'S');
QString longStr = QString("%1%2.%3%4")
.arg(longDeg, 3, 10, QChar('0'))
.arg(longMin, 2, 10, QChar('0'))
.arg(longFrac, 2, 10, QChar('0'))
.arg(longEast ? 'E' : 'W');
return QString("%1/%2-").arg(latStr).arg(longStr);
}

View File

@ -60,6 +60,65 @@ public:
{ }
};
class MsgSendAPRS : public Message {
MESSAGE_CLASS_DECLARATION
public:
const QString& getSourceCall() const { return m_sourceCall; }
const QString& getDestCall() const { return m_destCall; }
uint8_t getCAN() const { return m_can; }
const QString& getCall() const { return m_call; }
const QString& getTo() const { return m_to; }
const QString& getVia() const { return m_via; }
const QString& getData() const { return m_data; }
bool getInsertPosition() const { return m_insertPosition; }
static MsgSendAPRS* create(
const QString& sourceCall,
const QString& destCall,
uint8_t can,
const QString& call,
const QString& to,
const QString& via,
const QString& data,
bool insertPosition
)
{
return new MsgSendAPRS(sourceCall, destCall, can, call, to, via, data, insertPosition);
}
private:
QString m_sourceCall;
QString m_destCall;
uint8_t m_can;
QString m_call;
QString m_to;
QString m_via;
QString m_data;
bool m_insertPosition;
MsgSendAPRS(
const QString& sourceCall,
const QString& destCall,
uint8_t can,
const QString& call,
const QString& to,
const QString& via,
const QString& data,
bool insertPosition
) :
Message(),
m_sourceCall(sourceCall),
m_destCall(destCall),
m_can(can),
m_call(call),
m_to(to),
m_via(via),
m_data(data),
m_insertPosition(insertPosition)
{ }
};
class MsgSendAudioFrame : public Message {
MESSAGE_CLASS_DECLARATION
@ -154,6 +213,7 @@ private:
void send_preamble();
void send_eot();
void output_baseband(std::array<uint8_t, 2> sync_word, const std::array<int8_t, 368>& frame);
QString formatAPRSPosition();
private slots:
void handleInputMessages();

View File

@ -577,4 +577,18 @@ void M17ModSource::sendPacket()
);
m_processor->getInputMessageQueue()->push(msg);
}
else if (m_settings.m_packetType == M17ModSettings::PacketType::PacketAPRS)
{
M17ModProcessor::MsgSendAPRS *msg = M17ModProcessor::MsgSendAPRS::create(
m_settings.m_sourceCall,
m_settings.m_destCall,
m_settings.m_can,
m_settings.m_aprsCallsign,
m_settings.m_aprsTo,
m_settings.m_aprsVia,
m_settings.m_aprsData,
m_settings.m_aprsInsertPosition
);
m_processor->getInputMessageQueue()->push(msg);
}
}

View File

@ -25,12 +25,14 @@
const QStringList APRSSettings::m_pipeTypes = {
QStringLiteral("PacketDemod"),
QStringLiteral("ChirpChatDemod")
QStringLiteral("ChirpChatDemod"),
QStringLiteral("M17Demod")
};
const QStringList APRSSettings::m_pipeURIs = {
QStringLiteral("sdrangel.channel.packetdemod"),
QStringLiteral("sdrangel.channel.chirpchatdemod")
QStringLiteral("sdrangel.channel.chirpchatdemod"),
QStringLiteral("sdrangel.channel.m17demod")
};
const QStringList APRSSettings::m_altitudeUnitNames = {