diff --git a/plugins/channelrx/demodm17/m17demod.cpp b/plugins/channelrx/demodm17/m17demod.cpp index 2972127e6..501708391 100644 --- a/plugins/channelrx/demodm17/m17demod.cpp +++ b/plugins/channelrx/demodm17/m17demod.cpp @@ -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 packetsPipes; + MainCore::instance()->getMessagePipes().getMessagePipes(this, "packets", packetsPipes); + + for (const auto& pipe : packetsPipes) + { + MessageQueue *messageQueue = qobject_cast(pipe->m_element); + MainCore::MsgPacket *msg = MainCore::MsgPacket::create(this, report.getPacket(), QDateTime::currentDateTime()); + messageQueue->push(msg); + } + + return true; + } else { return false; diff --git a/plugins/channelrx/demodm17/m17demod.h b/plugins/channelrx/demodm17/m17demod.h index 9ccec1e09..be2ebd26b 100644 --- a/plugins/channelrx/demodm17/m17demod.h +++ b/plugins/channelrx/demodm17/m17demod.h @@ -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; } diff --git a/plugins/channelrx/demodm17/m17demodgui.cpp b/plugins/channelrx/demodm17/m17demodgui.cpp index 3e2efbb27..807674aa8 100644 --- a/plugins/channelrx/demodm17/m17demodgui.cpp +++ b/plugins/channelrx/demodm17/m17demodgui.cpp @@ -20,6 +20,8 @@ #include #include #include +#include +#include #include @@ -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() diff --git a/plugins/channelrx/demodm17/m17demodgui.h b/plugins/channelrx/demodm17/m17demodgui.h index 1e7f916b0..dcf7242d8 100644 --- a/plugins/channelrx/demodm17/m17demodgui.h +++ b/plugins/channelrx/demodm17/m17demodgui.h @@ -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(); diff --git a/plugins/channelrx/demodm17/m17demodgui.ui b/plugins/channelrx/demodm17/m17demodgui.ui index b04054974..7625d83bb 100644 --- a/plugins/channelrx/demodm17/m17demodgui.ui +++ b/plugins/channelrx/demodm17/m17demodgui.ui @@ -732,7 +732,7 @@ QTabWidget::East - 0 + 2 @@ -1549,6 +1549,90 @@ APRS + + + + 0 + 24 + 480 + 186 + + + + Received packets + + + QAbstractItemView::NoEditTriggers + + + + From + + + Source callsign/address + + + + + To + + + Destination callsign/address + + + + + Via + + + Repeater addresses + + + + + Type + + + AX.25 frame type + + + + + PID + + + Layer 3 protocol ID + + + + + Data (ASCII) + + + Packet data as ASCII + + + + + + + 0 + 0 + 24 + 21 + + + + Clear packets from table + + + + + + + :/bin.png:/bin.png + + diff --git a/plugins/channelrx/demodm17/m17demodprocessor.cpp b/plugins/channelrx/demodm17/m17demodprocessor.cpp index 8e9ca506a..aecc8f7c4 100644 --- a/plugins/channelrx/demodm17/m17demodprocessor.cpp +++ b/plugins/channelrx/demodm17/m17demodprocessor.cpp @@ -23,6 +23,7 @@ #include #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(&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; } diff --git a/plugins/channelrx/demodm17/m17demodsettings.h b/plugins/channelrx/demodm17/m17demodsettings.h index 79710297c..98a78abd2 100644 --- a/plugins/channelrx/demodm17/m17demodsettings.h +++ b/plugins/channelrx/demodm17/m17demodsettings.h @@ -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; diff --git a/plugins/channelrx/demodm17/m17demodsink.cpp b/plugins/channelrx/demodm17/m17demodsink.cpp index dea62c74e..ac0e63048 100644 --- a/plugins/channelrx/demodm17/m17demodsink.cpp +++ b/plugins/channelrx/demodm17/m17demodsink.cpp @@ -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 } } diff --git a/plugins/channeltx/modm17/CMakeLists.txt b/plugins/channeltx/modm17/CMakeLists.txt index 932ed8359..08a086509 100644 --- a/plugins/channeltx/modm17/CMakeLists.txt +++ b/plugins/channeltx/modm17/CMakeLists.txt @@ -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 diff --git a/plugins/channeltx/modm17/m17modax25.cpp b/plugins/channeltx/modm17/m17modax25.cpp new file mode 100644 index 000000000..b0ec8d122 --- /dev/null +++ b/plugins/channeltx/modm17/m17modax25.cpp @@ -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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#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 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(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; + } +} diff --git a/plugins/channeltx/modm17/m17modax25.h b/plugins/channeltx/modm17/m17modax25.h new file mode 100644 index 000000000..624f6c81b --- /dev/null +++ b/plugins/channeltx/modm17/m17modax25.h @@ -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 . // +/////////////////////////////////////////////////////////////////////////////////// + +#ifndef PLUGINS_CHANNELTX_MODM17_M17MODAX25_H_ +#define PLUGINS_CHANNELTX_MODM17_M17MODAX25_H_ + +#include +#include + +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_ + diff --git a/plugins/channeltx/modm17/m17modgui.cpp b/plugins/channeltx/modm17/m17modgui.cpp index ea1289102..13d0edbad 100644 --- a/plugins/channeltx/modm17/m17modgui.cpp +++ b/plugins/channeltx/modm17/m17modgui.cpp @@ -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::of(&QSpinBox::valueChanged), this, &M17ModGUI::on_can_valueChanged); diff --git a/plugins/channeltx/modm17/m17modgui.h b/plugins/channeltx/modm17/m17modgui.h index bd5922eeb..b16fe0933 100644 --- a/plugins/channeltx/modm17/m17modgui.h +++ b/plugins/channeltx/modm17/m17modgui.h @@ -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); diff --git a/plugins/channeltx/modm17/m17modprocessor.cpp b/plugins/channeltx/modm17/m17modprocessor.cpp index a72ac627f..f221bf698 100644 --- a/plugins/channeltx/modm17/m17modprocessor.cpp +++ b/plugins/channeltx/modm17/m17modprocessor.cpp @@ -18,10 +18,13 @@ #include #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 sync_word, const st std::array 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); +} diff --git a/plugins/channeltx/modm17/m17modprocessor.h b/plugins/channeltx/modm17/m17modprocessor.h index 528380e83..641105ef3 100644 --- a/plugins/channeltx/modm17/m17modprocessor.h +++ b/plugins/channeltx/modm17/m17modprocessor.h @@ -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 sync_word, const std::array& frame); + QString formatAPRSPosition(); private slots: void handleInputMessages(); diff --git a/plugins/channeltx/modm17/m17modsource.cpp b/plugins/channeltx/modm17/m17modsource.cpp index 80a7b4640..a0259b4ec 100644 --- a/plugins/channeltx/modm17/m17modsource.cpp +++ b/plugins/channeltx/modm17/m17modsource.cpp @@ -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); + } } diff --git a/plugins/feature/aprs/aprssettings.cpp b/plugins/feature/aprs/aprssettings.cpp index b9b2036af..f9d9a7192 100644 --- a/plugins/feature/aprs/aprssettings.cpp +++ b/plugins/feature/aprs/aprssettings.cpp @@ -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 = {