From f10da647178b13f8153b91cca75a98e31bdeea4f Mon Sep 17 00:00:00 2001
From: f4exb <f4exb06@gmail.com>
Date: Sun, 12 Jun 2022 22:51:18 +0200
Subject: [PATCH] M17 modulator: SMS packet +

---
 modems/m17/M17Modulator.h                     | 100 ++-
 plugins/channeltx/modm17/CMakeLists.txt       |  10 +
 plugins/channeltx/modm17/m17mod.cpp           |  43 +-
 plugins/channeltx/modm17/m17mod.h             |   1 +
 plugins/channeltx/modm17/m17modbaseband.cpp   |   4 +-
 plugins/channeltx/modm17/m17modbaseband.h     |   1 +
 plugins/channeltx/modm17/m17modgui.cpp        | 228 +++++-
 plugins/channeltx/modm17/m17modgui.h          |  17 +-
 plugins/channeltx/modm17/m17modgui.ui         | 679 +++++++++++++++++-
 plugins/channeltx/modm17/m17modprocessor.cpp  |  99 +++
 plugins/channeltx/modm17/m17modprocessor.h    |  21 +-
 plugins/channeltx/modm17/m17modsettings.cpp   |  60 +-
 plugins/channeltx/modm17/m17modsettings.h     |  43 +-
 plugins/channeltx/modm17/m17modsource.cpp     | 151 ++--
 plugins/channeltx/modm17/m17modsource.h       |   7 +-
 sdrbase/resources/webapi/doc/html2/index.html |  15 +-
 .../webapi/doc/swagger/include/M17Mod.yaml    |   9 +-
 sdrgui/resources/res.qrc                      |   2 +
 sdrgui/resources/sms.png                      | Bin 0 -> 821 bytes
 sdrgui/resources/world.png                    | Bin 0 -> 7828 bytes
 .../sdrangel/api/swagger/include/M17Mod.yaml  |   9 +-
 swagger/sdrangel/code/html2/index.html        |  15 +-
 .../code/qt5/client/SWGM17ModSettings.cpp     |  72 +-
 .../code/qt5/client/SWGM17ModSettings.h       |  20 +-
 24 files changed, 1432 insertions(+), 174 deletions(-)
 create mode 100644 plugins/channeltx/modm17/m17modprocessor.cpp
 create mode 100644 sdrgui/resources/sms.png
 create mode 100644 sdrgui/resources/world.png

diff --git a/modems/m17/M17Modulator.h b/modems/m17/M17Modulator.h
index 0d4e1a5c8..12fbd16af 100644
--- a/modems/m17/M17Modulator.h
+++ b/modems/m17/M17Modulator.h
@@ -39,6 +39,7 @@ public:
     using codec_frame_t = std::array<uint8_t, 16>;
     using payload_t = std::array<uint8_t, 34>;      // Bytes in the payload of a data frame.
     using frame_t = std::array<uint8_t, 46>;        // M17 frame (without sync word).
+    using packet_t = std::array<uint8_t, 25>;       // Packet payload
 
     static constexpr std::array<uint8_t, 2> SYNC_WORD = {0x32, 0x43};
     static constexpr std::array<uint8_t, 2> LSF_SYNC_WORD = {0x55, 0xF7};
@@ -57,6 +58,31 @@ public:
         return 0;
     }
 
+    template <typename T, size_t N>
+    static std::array<int8_t, N * 4> bytes_to_symbols(const std::array<T, N>& bytes)
+    {
+        std::array<int8_t, N * 4> result;
+        size_t index = 0;
+
+        for (auto b : bytes)
+        {
+            for (size_t i = 0; i != 4; ++i)
+            {
+                result[index++] = bits_to_symbol(b >> 6);
+                b <<= 2;
+            }
+        }
+
+        return result;
+    }
+
+    static
+    void make_preamble(std::array<uint8_t, 48>& preamble_bytes)
+    {
+        // Preamble is simple... bytes -> symbols.
+        preamble_bytes.fill(0x77);
+    }
+
     /**
      * Encode each LSF segment into a Golay-encoded LICH segment bitstream.
      */
@@ -109,7 +135,7 @@ public:
     /**
      * Construct the link setup frame and split into LICH segments.
      */
-    void make_link_setup(lich_t& lich, mobilinkd::M17Modulator::frame_t lsf)
+    void make_link_setup(lich_t& lich, mobilinkd::M17Modulator::frame_t lsf_frame)
     {
         using namespace mobilinkd;
 
@@ -141,12 +167,14 @@ public:
         }
 
         auto encoded = conv_encode(lsf);
+        auto size = puncture_bytes(encoded, lsf_frame, P1);
 
-        auto size = puncture_bytes(encoded, lsf, P1);
-        assert(size == 368);
+        if (size != 368) {
+            std::cerr << "make_link_setup: incorrect size (not 368)" << size;
+        }
 
-        interleaver_.interleave(lsf);
-        randomizer_(lsf);
+        interleaver_.interleave(lsf_frame);
+        randomizer_(lsf_frame);
     }
 
     /**
@@ -168,7 +196,7 @@ public:
      * Assemble the audio frame payload by appending the frame number, encoded audio,
      * and CRC, then convolutionally coding and puncturing the data.
      */
-    payload_t make_payload(uint16_t frame_number, const codec_frame_t& payload)
+    payload_t make_audio_payload(uint16_t frame_number, const codec_frame_t& payload)
     {
         std::array<uint8_t, 20> data;   // FN, Audio, CRC = 2 + 16 + 2;
         data[0] = uint8_t((frame_number >> 8) & 0xFF);
@@ -188,7 +216,47 @@ public:
 
         payload_t punctured;
         auto size = puncture_bytes(encoded, punctured, mobilinkd::P2);
-        assert(size == 272);
+
+        if (size != 272) {
+            std::cerr << "mobilinkd::M17Modulator::make_audio_payload: incorrect size (not 272)" << size;
+        }
+
+        return punctured;
+    }
+
+    frame_t make_packet_frame(uint8_t packet_number, bool last_packet, packet_t packet, int packet_size)
+    {
+        std::array<uint8_t, 26> packet_assembly;
+        packet_assembly.fill(0);
+        std::copy(packet.begin(), packet.begin() + packet_size, packet_assembly.begin());
+
+        if (packet_number == 0) {
+            crc_.reset();
+        }
+
+        for (int i = 0; i < packet_size; i++) {
+            crc_(packet[i]);
+        }
+
+        if (last_packet)
+        {
+            packet_assembly[25] = 0x80 | (packet_size<<2);
+            packet_assembly[packet_size]   = crc_.get_bytes()[1];
+            packet_assembly[packet_size+1] = crc_.get_bytes()[0];
+        }
+        else
+        {
+            packet_assembly[25] = (packet_number<<2);
+        }
+
+        std::array<uint8_t, 2*26+1> encoded = conv_encode(packet_assembly);
+        frame_t punctured;
+        auto size = puncture_bytes(encoded, punctured, mobilinkd::P3);
+
+        if (size != 368) {
+            std::cerr << "mobilinkd::M17Modulator::make_packet_frame: incorrect size (not 368)" << size;
+        }
+
         return punctured;
     }
 
@@ -280,24 +348,6 @@ private:
         return encoded_call;
     }
 
-    template <typename T, size_t N>
-    static std::array<int8_t, N * 4> bytes_to_symbols(const std::array<T, N>& bytes)
-    {
-        std::array<int8_t, N * 4> result;
-        size_t index = 0;
-
-        for (auto b : bytes)
-        {
-            for (size_t i = 0; i != 4; ++i)
-            {
-                result[index++] = bits_to_symbol(b >> 6);
-                b <<= 2;
-            }
-        }
-
-        return result;
-    }
-
     template <typename T, size_t N>
     static std::array<T, N * 2 + 1> conv_encode(std::array<T, N> data)
     {
diff --git a/plugins/channeltx/modm17/CMakeLists.txt b/plugins/channeltx/modm17/CMakeLists.txt
index bf11c0d73..82d0a789a 100644
--- a/plugins/channeltx/modm17/CMakeLists.txt
+++ b/plugins/channeltx/modm17/CMakeLists.txt
@@ -4,6 +4,7 @@ set(modm17_SOURCES
     m17mod.cpp
     m17modbaseband.cpp
     m17modsource.cpp
+    m17modprocessor.cpp
 	m17modplugin.cpp
     m17modsettings.cpp
     m17modwebapiadapter.cpp
@@ -13,6 +14,7 @@ set(modm17_HEADERS
 	m17mod.h
     m17modbaseband.h
     m17modsource.h
+    m17modprocessor.h
 	m17modplugin.h
     m17modsettings.h
     m17modwebapiadapter.h
@@ -20,6 +22,8 @@ set(modm17_HEADERS
 
 include_directories(
     ${CMAKE_SOURCE_DIR}/swagger/sdrangel/code/qt5/client
+    ${CODEC2_INCLUDE_DIR}
+    ${CMAKE_SOURCE_DIR}/modems
 )
 
 if(NOT SERVER_MODE)
@@ -48,12 +52,18 @@ add_library(${TARGET_NAME} SHARED
 	${modm17_SOURCES}
 )
 
+if(CODEC2_EXTERNAL)
+    add_dependencies(${TARGET_NAME} codec2)
+endif()
+
 target_link_libraries(${TARGET_NAME}
     Qt5::Core
     ${TARGET_LIB}
 	sdrbase
 	${TARGET_LIB_GUI}
     swagger
+    ${CODEC2_LIBRARIES}
+    modems
 )
 
 install(TARGETS ${TARGET_NAME} DESTINATION ${INSTALL_FOLDER})
diff --git a/plugins/channeltx/modm17/m17mod.cpp b/plugins/channeltx/modm17/m17mod.cpp
index c79ddd73f..47fc16675 100644
--- a/plugins/channeltx/modm17/m17mod.cpp
+++ b/plugins/channeltx/modm17/m17mod.cpp
@@ -294,7 +294,9 @@ void M17Mod::applySettings(const M17ModSettings& settings, bool force)
             << " m_toneFrequency: " << settings.m_toneFrequency
             << " m_channelMute: " << settings.m_channelMute
             << " m_playLoop: " << settings.m_playLoop
-            << " m_modAFInput " << settings.m_modAFInput
+            << " m_m17Mode " << settings.m_m17Mode
+            << " m_audioType " << settings.m_audioType
+            << " m_packetType " << settings.m_packetType
             << " m_audioDeviceName: " << settings.m_audioDeviceName
             << " m_useReverseAPI: " << settings.m_useReverseAPI
             << " m_reverseAPIAddress: " << settings.m_reverseAPIAddress
@@ -320,8 +322,14 @@ void M17Mod::applySettings(const M17ModSettings& settings, bool force)
     if ((settings.m_playLoop != m_settings.m_playLoop) || force) {
         reverseAPIKeys.append("playLoop");
     }
-    if ((settings.m_modAFInput != m_settings.m_modAFInput) || force) {
-        reverseAPIKeys.append("modAFInput");
+    if ((settings.m_audioType != m_settings.m_audioType) || force) {
+        reverseAPIKeys.append("audioType");
+    }
+    if ((settings.m_packetType != m_settings.m_packetType) || force) {
+        reverseAPIKeys.append("packetType");
+    }
+    if ((settings.m_m17Mode != m_settings.m_m17Mode) || force) {
+        reverseAPIKeys.append("m17Mode");
     }
     if((settings.m_rfBandwidth != m_settings.m_rfBandwidth) || force) {
         reverseAPIKeys.append("rfBandwidth");
@@ -470,8 +478,14 @@ void M17Mod::webapiUpdateChannelSettings(
     if (channelSettingsKeys.contains("inputFrequencyOffset")) {
         settings.m_inputFrequencyOffset = response.getM17ModSettings()->getInputFrequencyOffset();
     }
-    if (channelSettingsKeys.contains("modAFInput")) {
-        settings.m_modAFInput = (M17ModSettings::M17ModInputAF) response.getM17ModSettings()->getModAfInput();
+    if (channelSettingsKeys.contains("m17Mode")) {
+        settings.m_m17Mode = (M17ModSettings::M17Mode) response.getM17ModSettings()->getM17Mode();
+    }
+    if (channelSettingsKeys.contains("audioType")) {
+        settings.m_audioType = (M17ModSettings::AudioType) response.getM17ModSettings()->getAudioType();
+    }
+    if (channelSettingsKeys.contains("packetType")) {
+        settings.m_packetType = (M17ModSettings::PacketType) response.getM17ModSettings()->getPacketType();
     }
     if (channelSettingsKeys.contains("playLoop")) {
         settings.m_playLoop = response.getM17ModSettings()->getPlayLoop() != 0;
@@ -533,7 +547,9 @@ void M17Mod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& respon
     response.getM17ModSettings()->setChannelMute(settings.m_channelMute ? 1 : 0);
     response.getM17ModSettings()->setFmDeviation(settings.m_fmDeviation);
     response.getM17ModSettings()->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
-    response.getM17ModSettings()->setModAfInput((int) settings.m_modAFInput);
+    response.getM17ModSettings()->setM17Mode((int) settings.m_m17Mode);
+    response.getM17ModSettings()->setAudioType((int) settings.m_audioType);
+    response.getM17ModSettings()->setPacketType((int) settings.m_packetType);
     response.getM17ModSettings()->setPlayLoop(settings.m_playLoop ? 1 : 0);
     response.getM17ModSettings()->setRfBandwidth(settings.m_rfBandwidth);
     response.getM17ModSettings()->setRgbColor(settings.m_rgbColor);
@@ -673,8 +689,14 @@ void M17Mod::webapiFormatChannelSettings(
     if (channelSettingsKeys.contains("inputFrequencyOffset") || force) {
         swgM17ModSettings->setInputFrequencyOffset(settings.m_inputFrequencyOffset);
     }
-    if (channelSettingsKeys.contains("modAFInput") || force) {
-        swgM17ModSettings->setModAfInput((int) settings.m_modAFInput);
+    if (channelSettingsKeys.contains("m17Mode") || force) {
+        swgM17ModSettings->setM17Mode((int) settings.m_m17Mode);
+    }
+    if (channelSettingsKeys.contains("audioType") || force) {
+        swgM17ModSettings->setAudioType((int) settings.m_audioType);
+    }
+    if (channelSettingsKeys.contains("packetType") || force) {
+        swgM17ModSettings->setPacketType((int) settings.m_packetType);
     }
     if (channelSettingsKeys.contains("audioDeviceName") || force) {
         swgM17ModSettings->setAudioDeviceName(new QString(settings.m_audioDeviceName));
@@ -764,3 +786,8 @@ int M17Mod::getFeedbackAudioSampleRate() const
 {
     return m_basebandSource->getFeedbackAudioSampleRate();
 }
+
+void M17Mod::sendPacket()
+{
+    m_basebandSource->sendPacket();
+}
diff --git a/plugins/channeltx/modm17/m17mod.h b/plugins/channeltx/modm17/m17mod.h
index 0f68030e3..a2924332b 100644
--- a/plugins/channeltx/modm17/m17mod.h
+++ b/plugins/channeltx/modm17/m17mod.h
@@ -233,6 +233,7 @@ public:
     uint32_t getNumberOfDeviceStreams() const;
     int getAudioSampleRate() const;
     int getFeedbackAudioSampleRate() const;
+    void sendPacket();
 
     static const char* const m_channelIdURI;
     static const char* const m_channelId;
diff --git a/plugins/channeltx/modm17/m17modbaseband.cpp b/plugins/channeltx/modm17/m17modbaseband.cpp
index 685900f17..6962fc9ea 100644
--- a/plugins/channeltx/modm17/m17modbaseband.cpp
+++ b/plugins/channeltx/modm17/m17modbaseband.cpp
@@ -196,12 +196,12 @@ void M17ModBaseband::applySettings(const M17ModSettings& settings, bool force)
         }
     }
 
-    if ((settings.m_modAFInput != m_settings.m_modAFInput) || force)
+    if ((settings.m_audioType != m_settings.m_audioType) || force)
     {
         AudioDeviceManager *audioDeviceManager = DSPEngine::instance()->getAudioDeviceManager();
         int audioDeviceIndex = audioDeviceManager->getInputDeviceIndex(settings.m_audioDeviceName);
 
-        if (settings.m_modAFInput == M17ModSettings::M17ModInputAudio) {
+        if (settings.m_audioType == M17ModSettings::AudioInput) {
             audioDeviceManager->addAudioSource(getAudioFifo(), getInputMessageQueue(), audioDeviceIndex);
         } else {
             audioDeviceManager->removeAudioSource(getAudioFifo());
diff --git a/plugins/channeltx/modm17/m17modbaseband.h b/plugins/channeltx/modm17/m17modbaseband.h
index 43724cf29..9e8f2d129 100644
--- a/plugins/channeltx/modm17/m17modbaseband.h
+++ b/plugins/channeltx/modm17/m17modbaseband.h
@@ -70,6 +70,7 @@ public:
     AudioFifo *getAudioFifo() { return m_source.getAudioFifo(); }
     AudioFifo *getFeedbackAudioFifo() { return m_source.getFeedbackAudioFifo(); }
     void setChannel(ChannelAPI *channel);
+    void sendPacket() { m_source.sendPacket(); }
 
 signals:
 	/**
diff --git a/plugins/channeltx/modm17/m17modgui.cpp b/plugins/channeltx/modm17/m17modgui.cpp
index 7c7dea247..cd0492cd1 100644
--- a/plugins/channeltx/modm17/m17modgui.cpp
+++ b/plugins/channeltx/modm17/m17modgui.cpp
@@ -182,6 +182,22 @@ void M17ModGUI::on_toneFrequency_valueChanged(int value)
     applySettings();
 }
 
+void M17ModGUI::on_fmAudio_toggled(bool checked)
+{
+    m_fmAudioMode = checked;
+
+    if ((checked) && (m_settings.m_m17Mode == M17ModSettings::M17Mode::M17ModeM17Audio))
+    {
+        m_settings.m_m17Mode = M17ModSettings::M17Mode::M17ModeFMAudio;
+        applySettings();
+    }
+    else if ((!checked) && (m_settings.m_m17Mode == M17ModSettings::M17Mode::M17ModeFMAudio))
+    {
+        m_settings.m_m17Mode = M17ModSettings::M17Mode::M17ModeM17Audio;
+        applySettings();
+    }
+}
+
 void M17ModGUI::on_channelMute_toggled(bool checked)
 {
     m_settings.m_channelMute = checked;
@@ -196,9 +212,13 @@ void M17ModGUI::on_playLoop_toggled(bool checked)
 
 void M17ModGUI::on_play_toggled(bool checked)
 {
-    ui->tone->setEnabled(!checked); // release other source inputs
-    ui->mic->setEnabled(!checked);
-    m_settings.m_modAFInput = checked ? M17ModSettings::M17ModInputFile : M17ModSettings::M17ModInputNone;
+    m_settings.m_audioType = checked ? M17ModSettings::AudioFile : M17ModSettings::AudioNone;
+    m_settings.m_m17Mode = checked ?
+        m_fmAudioMode ?
+            M17ModSettings::M17Mode::M17ModeFMAudio
+            : M17ModSettings::M17Mode::M17ModeM17Audio
+        : M17ModSettings::M17ModeNone;
+    displayModes();
     applySettings();
     ui->navTimeSlider->setEnabled(!checked);
     m_enableNavTime = !checked;
@@ -206,17 +226,20 @@ void M17ModGUI::on_play_toggled(bool checked)
 
 void M17ModGUI::on_tone_toggled(bool checked)
 {
-    ui->play->setEnabled(!checked); // release other source inputs
-    ui->mic->setEnabled(!checked);
-    m_settings.m_modAFInput = checked ? M17ModSettings::M17ModInputTone : M17ModSettings::M17ModInputNone;
+    m_settings.m_m17Mode = checked ? M17ModSettings::M17ModeFMTone : M17ModSettings::M17ModeNone;
+    displayModes();
     applySettings();
 }
 
 void M17ModGUI::on_mic_toggled(bool checked)
 {
-    ui->play->setEnabled(!checked); // release other source inputs
-    ui->tone->setEnabled(!checked); // release other source inputs
-    m_settings.m_modAFInput = checked ? M17ModSettings::M17ModInputAudio : M17ModSettings::M17ModInputNone;
+    m_settings.m_audioType = checked ? M17ModSettings::AudioInput : M17ModSettings::AudioNone;
+    m_settings.m_m17Mode = checked ?
+        m_fmAudioMode ?
+            M17ModSettings::M17Mode::M17ModeFMAudio
+            : M17ModSettings::M17Mode::M17ModeM17Audio
+        : M17ModSettings::M17ModeNone;
+    displayModes();
     applySettings();
 }
 
@@ -261,6 +284,66 @@ void M17ModGUI::on_showFileDialog_clicked(bool checked)
     }
 }
 
+void M17ModGUI::on_packetMode_toggled(bool checked)
+{
+    m_settings.m_m17Mode = checked ? M17ModSettings::M17ModeM17Packet : M17ModSettings::M17ModeNone;
+    displayModes();
+    applySettings();
+}
+
+void M17ModGUI::on_sendPacket_clicked(bool)
+{
+    m_m17Mod->sendPacket();
+}
+
+void M17ModGUI::on_loopPacket_toggled(bool checked)
+{
+    (void) checked;
+    // TODO
+}
+
+void M17ModGUI::on_loopPacketInterval_valueChanged(int value)
+{
+    (void) value;
+    // TODO
+}
+
+void M17ModGUI::on_packetDataWidget_currentChanged(int index)
+{
+    m_settings.m_packetType = indexToPacketType(index);
+    applySettings();
+}
+
+void M17ModGUI::on_source_editingFinished()
+{
+    m_settings.m_sourceCall = ui->source->text();
+    applySettings();
+}
+
+void M17ModGUI::on_destination_editingFinished()
+{
+    m_settings.m_destCall = ui->destination->text();
+    applySettings();
+}
+
+void M17ModGUI::on_insertPosition_toggled(bool checked)
+{
+    m_settings.m_insertPosition = checked;
+    applySettings();
+}
+
+void M17ModGUI::on_can_valueChanged(int value)
+{
+    m_settings.m_can = value;
+    applySettings();
+}
+
+void M17ModGUI::on_smsText_editingFinished()
+{
+    m_settings.m_smsText = ui->smsText->toPlainText();
+    applySettings();
+}
+
 void M17ModGUI::configureFileName()
 {
     qDebug() << "M17ModGUI::configureFileName: " << m_fileName.toStdString().c_str();
@@ -333,6 +416,7 @@ M17ModGUI::M17ModGUI(PluginAPI* pluginAPI, DeviceUISet *deviceUISet, BasebandSam
     m_deviceCenterFrequency(0),
     m_basebandSampleRate(1),
 	m_doApplySettings(true),
+    m_fmAudioMode(false),
     m_recordLength(0),
     m_recordSampleRate(48000),
     m_samplesCount(0),
@@ -447,23 +531,101 @@ void M17ModGUI::displaySettings()
     ui->channelMute->setChecked(m_settings.m_channelMute);
     ui->playLoop->setChecked(m_settings.m_playLoop);
 
-    ui->tone->setEnabled((m_settings.m_modAFInput == M17ModSettings::M17ModInputAF::M17ModInputTone) || (m_settings.m_modAFInput == M17ModSettings::M17ModInputAF::M17ModInputNone));
-    ui->mic->setEnabled((m_settings.m_modAFInput == M17ModSettings::M17ModInputAF::M17ModInputAudio) || (m_settings.m_modAFInput == M17ModSettings::M17ModInputAF::M17ModInputNone));
-    ui->play->setEnabled((m_settings.m_modAFInput == M17ModSettings::M17ModInputAF::M17ModInputFile) || (m_settings.m_modAFInput == M17ModSettings::M17ModInputAF::M17ModInputNone));
-
-    ui->tone->setChecked(m_settings.m_modAFInput == M17ModSettings::M17ModInputAF::M17ModInputTone);
-    ui->mic->setChecked(m_settings.m_modAFInput == M17ModSettings::M17ModInputAF::M17ModInputAudio);
-    ui->play->setChecked(m_settings.m_modAFInput == M17ModSettings::M17ModInputAF::M17ModInputFile);
+    displayModes();
+    ui->fmAudio->setChecked(m_fmAudioMode);
+    ui->packetDataWidget->setCurrentIndex(packetTypeToIndex(m_settings.m_packetType));
 
     ui->feedbackEnable->setChecked(m_settings.m_feedbackAudioEnable);
     ui->feedbackVolume->setValue(roundf(m_settings.m_feedbackVolumeFactor * 100.0));
     ui->feedbackVolumeText->setText(QString("%1").arg(m_settings.m_feedbackVolumeFactor, 0, 'f', 2));
 
+    ui->source->setText(m_settings.m_sourceCall);
+    ui->destination->setText(m_settings.m_destCall);
+    ui->insertPosition->setChecked(m_settings.m_insertPosition);
+    ui->can->setValue(m_settings.m_can);
+
+    ui->smsText->setText(m_settings.m_smsText);
+
+    ui->aprsFromText->setText(m_settings.m_aprsCallsign);
+    ui->aprsData->setText(m_settings.m_aprsData);
+    ui->aprsTo->lineEdit()->setText(m_settings.m_aprsTo);
+    ui->aprsVia->lineEdit()->setText(m_settings.m_aprsVia);
+
     getRollupContents()->restoreState(m_rollupState);
     updateAbsoluteCenterFrequency();
     blockApplySettings(false);
 }
 
+void M17ModGUI::displayModes()
+{
+    qDebug("M17ModGUI::displayModes: m_m17Mode: %d m_audioType: %d",
+        (int) m_settings.m_m17Mode, (int) m_settings.m_audioType);
+
+    if (m_settings.m_m17Mode ==  M17ModSettings::M17Mode::M17ModeM17Packet)
+    {
+        ui->packetMode->setChecked(true);
+        ui->packetMode->setEnabled(true);
+        ui->tone->setChecked(false);
+        ui->mic->setChecked(false);
+        ui->play->setChecked(false);
+        ui->tone->setEnabled(false);
+        ui->mic->setEnabled(false);
+        ui->play->setEnabled(false);
+    }
+    else if (m_settings.m_m17Mode ==  M17ModSettings::M17Mode::M17ModeFMTone)
+    {
+        ui->tone->setChecked(true);
+        ui->tone->setEnabled(true);
+        ui->packetMode->setChecked(false);
+        ui->mic->setChecked(false);
+        ui->play->setChecked(false);
+        ui->packetMode->setEnabled(false);
+        ui->mic->setEnabled(false);
+        ui->play->setEnabled(false);
+    }
+    else if ((m_settings.m_m17Mode ==  M17ModSettings::M17Mode::M17ModeFMAudio) ||
+        (m_settings.m_m17Mode ==  M17ModSettings::M17Mode::M17ModeM17Audio))
+    {
+        ui->tone->setChecked(false);
+        ui->packetMode->setChecked(false);
+        ui->tone->setEnabled(false);
+        ui->packetMode->setEnabled(false);
+
+        if (m_settings.m_audioType == M17ModSettings::AudioType::AudioInput)
+        {
+            ui->mic->setChecked(true);
+            ui->mic->setEnabled(true);
+            ui->play->setChecked(false);
+            ui->play->setEnabled(false);
+        }
+        else if (m_settings.m_audioType == M17ModSettings::AudioType::AudioFile)
+        {
+            ui->play->setChecked(true);
+            ui->play->setEnabled(true);
+            ui->mic->setChecked(false);
+            ui->mic->setEnabled(false);
+        }
+        else if (m_settings.m_audioType == M17ModSettings::AudioType::AudioNone)
+        {
+            ui->mic->setChecked(false);
+            ui->play->setChecked(false);
+            ui->mic->setEnabled(true);
+            ui->play->setEnabled(true);
+        }
+    }
+    else if (m_settings.m_m17Mode == M17ModSettings::M17Mode::M17ModeNone)
+    {
+        ui->packetMode->setChecked(false);
+        ui->tone->setChecked(false);
+        ui->mic->setChecked(false);
+        ui->play->setChecked(false);
+        ui->packetMode->setEnabled(true);
+        ui->tone->setEnabled(true);
+        ui->mic->setEnabled(true);
+        ui->play->setEnabled(true);
+    }
+}
+
 void M17ModGUI::leaveEvent(QEvent* event)
 {
 	m_channelMarker.setHighlighted(false);
@@ -534,7 +696,7 @@ void M17ModGUI::tick()
         m_feedbackAudioSampleRate = feedbackAudioSampleRate;
     }
 
-    if (((++m_tickCount & 0xf) == 0) && (m_settings.m_modAFInput == M17ModSettings::M17ModInputFile))
+    if (((++m_tickCount & 0xf) == 0) && (m_settings.m_audioType == M17ModSettings::AudioFile))
     {
         M17Mod::MsgConfigureFileSourceStreamTiming* message = M17Mod::MsgConfigureFileSourceStreamTiming::create();
         m_m17Mod->getInputMessageQueue()->push(message);
@@ -591,9 +753,41 @@ void M17ModGUI::makeUIConnections()
     QObject::connect(ui->showFileDialog, &QPushButton::clicked, this, &M17ModGUI::on_showFileDialog_clicked);
     QObject::connect(ui->feedbackEnable, &QToolButton::toggled, this, &M17ModGUI::on_feedbackEnable_toggled);
     QObject::connect(ui->feedbackVolume, &QDial::valueChanged, this, &M17ModGUI::on_feedbackVolume_valueChanged);
+    QObject::connect(ui->fmAudio, &ButtonSwitch::toggled, this, &M17ModGUI::on_fmAudio_toggled);
+    QObject::connect(ui->packetMode, &ButtonSwitch::toggled, this, &M17ModGUI::on_packetMode_toggled);
+    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->smsText, &CustomTextEdit::editingFinished, this, &M17ModGUI::on_smsText_editingFinished);
 }
 
 void M17ModGUI::updateAbsoluteCenterFrequency()
 {
     setStatusFrequency(m_deviceCenterFrequency + m_settings.m_inputFrequencyOffset);
 }
+
+M17ModSettings::PacketType M17ModGUI::indexToPacketType(int index)
+{
+    switch(index)
+    {
+        case 0:
+            return M17ModSettings::PacketType::PacketSMS;
+        case 1:
+            return M17ModSettings::PacketType::PacketAPRS;
+        default:
+            return M17ModSettings::PacketType::PacketNone;
+    }
+}
+
+int M17ModGUI::packetTypeToIndex(M17ModSettings::PacketType type)
+{
+    switch(type)
+    {
+        case M17ModSettings::PacketType::PacketSMS:
+            return 0;
+        case M17ModSettings::PacketType::PacketAPRS:
+            return 1;
+        default:
+            return -1;
+    }
+}
diff --git a/plugins/channeltx/modm17/m17modgui.h b/plugins/channeltx/modm17/m17modgui.h
index a32b8a4bf..04b0e1cff 100644
--- a/plugins/channeltx/modm17/m17modgui.h
+++ b/plugins/channeltx/modm17/m17modgui.h
@@ -76,6 +76,7 @@ private:
     qint64 m_deviceCenterFrequency;
     int m_basebandSampleRate;
     bool m_doApplySettings;
+    bool m_fmAudioMode;
 
     M17Mod* m_m17Mod;
     MovingAverageUtil<double, double, 20> m_channelPowerDbAvg;
@@ -88,7 +89,6 @@ private:
     int m_feedbackAudioSampleRate;
     std::size_t m_tickCount;
     bool m_enableNavTime;
-    M17ModSettings::M17ModInputAF m_modAFInput;
     MessageQueue m_inputMessageQueue;
     QRegExpValidator m_dcsCodeValidator;
 
@@ -98,11 +98,14 @@ private:
     void blockApplySettings(bool block);
     void applySettings(bool force = false);
     void displaySettings();
+    void displayModes();
     void updateWithStreamData();
     void updateWithStreamTime();
     bool handleMessage(const Message& message);
     void makeUIConnections();
     void updateAbsoluteCenterFrequency();
+    M17ModSettings::PacketType indexToPacketType(int index);
+    int packetTypeToIndex(M17ModSettings::PacketType type);
 
     void leaveEvent(QEvent*);
     void enterEvent(QEvent*);
@@ -115,6 +118,7 @@ private slots:
     void on_rfBW_valueChanged(int value);
     void on_fmDev_valueChanged(int value);
     void on_toneFrequency_valueChanged(int value);
+    void on_fmAudio_toggled(bool checked);
     void on_volume_valueChanged(int value);
     void on_channelMute_toggled(bool checked);
     void on_tone_toggled(bool checked);
@@ -128,6 +132,17 @@ private slots:
     void on_feedbackEnable_toggled(bool checked);
     void on_feedbackVolume_valueChanged(int value);
 
+    void on_packetMode_toggled(bool checked);
+    void on_sendPacket_clicked(bool checked);
+    void on_loopPacket_toggled(bool checked);
+    void on_loopPacketInterval_valueChanged(int value);
+    void on_packetDataWidget_currentChanged(int index);
+    void on_source_editingFinished();
+    void on_destination_editingFinished();
+    void on_insertPosition_toggled(bool checked);
+    void on_can_valueChanged(int value);
+    void on_smsText_editingFinished();
+
     void onWidgetRolled(QWidget* widget, bool rollDown);
     void onMenuDialogCalled(const QPoint& p);
 
diff --git a/plugins/channeltx/modm17/m17modgui.ui b/plugins/channeltx/modm17/m17modgui.ui
index 9d9241ebd..e7a3adeaa 100644
--- a/plugins/channeltx/modm17/m17modgui.ui
+++ b/plugins/channeltx/modm17/m17modgui.ui
@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>360</width>
-    <height>278</height>
+    <height>568</height>
    </rect>
   </property>
   <property name="sizePolicy">
@@ -40,13 +40,13 @@
   <property name="windowTitle">
    <string>M17 Modulator</string>
   </property>
-  <widget class="QWidget" name="settingsContainer" native="true">
+  <widget class="QWidget" name="aSettingsContainer" native="true">
    <property name="geometry">
     <rect>
      <x>0</x>
      <y>0</y>
      <width>358</width>
-     <height>271</height>
+     <height>105</height>
     </rect>
    </property>
    <property name="minimumSize">
@@ -286,13 +286,6 @@
       </item>
      </layout>
     </item>
-    <item>
-     <widget class="Line" name="line_5">
-      <property name="orientation">
-       <enum>Qt::Horizontal</enum>
-      </property>
-     </widget>
-    </item>
     <item>
      <layout class="QHBoxLayout" name="volumeLayout">
       <item>
@@ -364,12 +357,42 @@
       </item>
      </layout>
     </item>
+   </layout>
+  </widget>
+  <widget class="QWidget" name="bAudioContainer" native="true">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>107</y>
+     <width>361</width>
+     <height>140</height>
+    </rect>
+   </property>
+   <property name="windowTitle">
+    <string>Audio</string>
+   </property>
+   <layout class="QVBoxLayout" name="verticalLayout_2">
+    <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="recordFileSelectLayout">
       <item>
        <widget class="ButtonSwitch" name="tone">
         <property name="toolTip">
-         <string>FM tone modulation</string>
+         <string>Analog FM tone modulation</string>
         </property>
         <property name="text">
          <string>...</string>
@@ -424,6 +447,22 @@
         </property>
        </widget>
       </item>
+      <item>
+       <widget class="ButtonSwitch" name="fmAudio">
+        <property name="maximumSize">
+         <size>
+          <width>35</width>
+          <height>16777215</height>
+         </size>
+        </property>
+        <property name="toolTip">
+         <string>Modulate audio as analog FM (for testing)</string>
+        </property>
+        <property name="text">
+         <string>FM</string>
+        </property>
+       </widget>
+      </item>
       <item>
        <widget class="ButtonSwitch" name="mic">
         <property name="toolTip">
@@ -532,13 +571,6 @@
       </item>
      </layout>
     </item>
-    <item>
-     <widget class="Line" name="line_4">
-      <property name="orientation">
-       <enum>Qt::Horizontal</enum>
-      </property>
-     </widget>
-    </item>
     <item>
      <layout class="QHBoxLayout" name="fileNameLayout">
       <item>
@@ -704,6 +736,606 @@
     </item>
    </layout>
   </widget>
+  <widget class="QWidget" name="cPacketContainer" native="true">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>247</y>
+     <width>358</width>
+     <height>193</height>
+    </rect>
+   </property>
+   <property name="windowTitle">
+    <string>Packet</string>
+   </property>
+   <layout class="QVBoxLayout" name="verticalLayout_3">
+    <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="horizontalLayout">
+      <item>
+       <widget class="ButtonSwitch" name="packetMode">
+        <property name="toolTip">
+         <string>Packet mode</string>
+        </property>
+        <property name="text">
+         <string>PKT</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QPushButton" name="sendPacket">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="toolTip">
+         <string>Send packet</string>
+        </property>
+        <property name="text">
+         <string/>
+        </property>
+        <property name="icon">
+         <iconset resource="../../../sdrgui/resources/res.qrc">
+          <normaloff>:/stream.png</normaloff>:/stream.png</iconset>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="ButtonSwitch" name="loopPacket">
+        <property name="toolTip">
+         <string>Send packets in a loop</string>
+        </property>
+        <property name="text">
+         <string/>
+        </property>
+        <property name="icon">
+         <iconset resource="../../../sdrgui/resources/res.qrc">
+          <normaloff>:/playloop.png</normaloff>:/playloop.png</iconset>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QDial" name="loopPacketInterval">
+        <property name="maximumSize">
+         <size>
+          <width>24</width>
+          <height>24</height>
+         </size>
+        </property>
+        <property name="toolTip">
+         <string>Interval between packets (s)</string>
+        </property>
+        <property name="minimum">
+         <number>1</number>
+        </property>
+        <property name="maximum">
+         <number>600</number>
+        </property>
+        <property name="pageStep">
+         <number>1</number>
+        </property>
+        <property name="value">
+         <number>60</number>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLabel" name="loopPacketIntervalText">
+        <property name="toolTip">
+         <string>Interval between packets (s)</string>
+        </property>
+        <property name="text">
+         <string>600</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <spacer name="horizontalSpacer_4">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>40</width>
+          <height>20</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+     </layout>
+    </item>
+    <item>
+     <widget class="QTabWidget" name="packetDataWidget">
+      <property name="toolTip">
+       <string>Packet data</string>
+      </property>
+      <property name="tabPosition">
+       <enum>QTabWidget::East</enum>
+      </property>
+      <property name="currentIndex">
+       <number>0</number>
+      </property>
+      <widget class="QWidget" name="pktSMS">
+       <property name="toolTip">
+        <string>SMS data</string>
+       </property>
+       <attribute name="icon">
+        <iconset resource="../../../sdrgui/resources/res.qrc">
+         <normaloff>:/sms.png</normaloff>:/sms.png</iconset>
+       </attribute>
+       <attribute name="title">
+        <string/>
+       </attribute>
+       <attribute name="toolTip">
+        <string>SMS</string>
+       </attribute>
+       <widget class="CustomTextEdit" name="smsText">
+        <property name="geometry">
+         <rect>
+          <x>0</x>
+          <y>10</y>
+          <width>307</width>
+          <height>120</height>
+         </rect>
+        </property>
+        <property name="toolTip">
+         <string>SMS text</string>
+        </property>
+       </widget>
+      </widget>
+      <widget class="QWidget" name="pktAPRS">
+       <property name="toolTip">
+        <string>APRS data</string>
+       </property>
+       <attribute name="icon">
+        <iconset resource="../../../sdrgui/resources/res.qrc">
+         <normaloff>:/world.png</normaloff>:/world.png</iconset>
+       </attribute>
+       <attribute name="title">
+        <string/>
+       </attribute>
+       <attribute name="toolTip">
+        <string>APRS</string>
+       </attribute>
+       <widget class="QWidget" name="horizontalLayoutWidget">
+        <property name="geometry">
+         <rect>
+          <x>0</x>
+          <y>40</y>
+          <width>301</width>
+          <height>34</height>
+         </rect>
+        </property>
+        <layout class="QHBoxLayout" name="toLayout">
+         <item>
+          <widget class="QLabel" name="aprsToLabel">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="text">
+            <string>To</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QComboBox" name="aprsTo">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="minimumSize">
+            <size>
+             <width>100</width>
+             <height>0</height>
+            </size>
+           </property>
+           <property name="toolTip">
+            <string>Enter destination</string>
+           </property>
+           <property name="editable">
+            <bool>true</bool>
+           </property>
+           <property name="currentText">
+            <string>APRS</string>
+           </property>
+           <property name="currentIndex">
+            <number>0</number>
+           </property>
+           <item>
+            <property name="text">
+             <string>APRS</string>
+            </property>
+           </item>
+           <item>
+            <property name="text">
+             <string>APZ</string>
+            </property>
+           </item>
+           <item>
+            <property name="text">
+             <string>CQ</string>
+            </property>
+           </item>
+           <item>
+            <property name="text">
+             <string>BEACON</string>
+            </property>
+           </item>
+           <item>
+            <property name="text">
+             <string>CALLSIGN-SSID</string>
+            </property>
+           </item>
+          </widget>
+         </item>
+         <item>
+          <widget class="QLabel" name="aprsViaLabel">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="text">
+            <string>Via</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QComboBox" name="aprsVia">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="toolTip">
+            <string>Enter routing</string>
+           </property>
+           <property name="editable">
+            <bool>true</bool>
+           </property>
+           <item>
+            <property name="text">
+             <string>WIDE2-2</string>
+            </property>
+           </item>
+           <item>
+            <property name="text">
+             <string>ARISS</string>
+            </property>
+           </item>
+          </widget>
+         </item>
+         <item>
+          <spacer name="horizontalSpacer_5">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>40</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+        </layout>
+       </widget>
+       <widget class="QWidget" name="horizontalLayoutWidget_2">
+        <property name="geometry">
+         <rect>
+          <x>0</x>
+          <y>80</y>
+          <width>301</width>
+          <height>34</height>
+         </rect>
+        </property>
+        <layout class="QHBoxLayout" name="aprsDataLayout">
+         <item>
+          <widget class="QLabel" name="aprsDataLabel">
+           <property name="layoutDirection">
+            <enum>Qt::LeftToRight</enum>
+           </property>
+           <property name="text">
+            <string>Data</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QLineEdit" name="aprsData">
+           <property name="toolTip">
+            <string>Enter data to transmit.
+            </string>
+           </property>
+           <property name="text">
+            <string>&gt;Using SDRangel</string>
+           </property>
+           <property name="maxLength">
+            <number>256</number>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </widget>
+       <widget class="QWidget" name="horizontalLayoutWidget_3">
+        <property name="geometry">
+         <rect>
+          <x>0</x>
+          <y>0</y>
+          <width>301</width>
+          <height>34</height>
+         </rect>
+        </property>
+        <layout class="QHBoxLayout" name="fromLayout">
+         <item>
+          <widget class="QLabel" name="label_2">
+           <property name="text">
+            <string>From</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QLineEdit" name="aprsFromText">
+           <property name="toolTip">
+            <string>Enter your amateur radio callsign and optionally a SSID. E.g. M7RCE or M7RCE-1</string>
+           </property>
+           <property name="text">
+            <string>MYCALL</string>
+           </property>
+           <property name="maxLength">
+            <number>10</number>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="ButtonSwitch" name="aprsInsertPosition">
+           <property name="toolTip">
+            <string>Insert position (latitude and longitude)</string>
+           </property>
+           <property name="text">
+            <string>...</string>
+           </property>
+           <property name="icon">
+            <iconset>
+             <normalon>:/gps.png</normalon>
+            </iconset>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <spacer name="aprsFromLabel">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>40</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+        </layout>
+       </widget>
+      </widget>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QWidget" name="dDigitalContainer" native="true">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>442</y>
+     <width>358</width>
+     <height>120</height>
+    </rect>
+   </property>
+   <property name="windowTitle">
+    <string>Digital</string>
+   </property>
+   <layout class="QVBoxLayout" name="verticalLayout_4" stretch="0">
+    <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="horizontalLayout_2">
+      <item>
+       <widget class="TVScreen" name="screenTV" native="true">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="minimumSize">
+         <size>
+          <width>100</width>
+          <height>100</height>
+         </size>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QFrame" name="frame">
+        <property name="frameShape">
+         <enum>QFrame::StyledPanel</enum>
+        </property>
+        <property name="frameShadow">
+         <enum>QFrame::Raised</enum>
+        </property>
+        <widget class="QComboBox" name="baudRate">
+         <property name="geometry">
+          <rect>
+           <x>10</x>
+           <y>10</y>
+           <width>60</width>
+           <height>24</height>
+          </rect>
+         </property>
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="minimumSize">
+          <size>
+           <width>35</width>
+           <height>0</height>
+          </size>
+         </property>
+         <property name="maximumSize">
+          <size>
+           <width>16777215</width>
+           <height>16777215</height>
+          </size>
+         </property>
+         <property name="toolTip">
+          <string>Baud rate: 2.4k: NXDN48, dPMR 4.8k: DMR, D-Star, YSF, NXDN96</string>
+         </property>
+         <item>
+          <property name="text">
+           <string>4.8k</string>
+          </property>
+         </item>
+        </widget>
+        <widget class="QLabel" name="sourceLabel">
+         <property name="geometry">
+          <rect>
+           <x>10</x>
+           <y>40</y>
+           <width>21</width>
+           <height>28</height>
+          </rect>
+         </property>
+         <property name="text">
+          <string>Src</string>
+         </property>
+        </widget>
+        <widget class="QLineEdit" name="source">
+         <property name="geometry">
+          <rect>
+           <x>40</x>
+           <y>40</y>
+           <width>100</width>
+           <height>24</height>
+          </rect>
+         </property>
+        </widget>
+        <widget class="QLabel" name="destinationLabel">
+         <property name="geometry">
+          <rect>
+           <x>10</x>
+           <y>70</y>
+           <width>21</width>
+           <height>28</height>
+          </rect>
+         </property>
+         <property name="text">
+          <string>Dst</string>
+         </property>
+        </widget>
+        <widget class="QLineEdit" name="destination">
+         <property name="geometry">
+          <rect>
+           <x>40</x>
+           <y>70</y>
+           <width>100</width>
+           <height>24</height>
+          </rect>
+         </property>
+        </widget>
+        <widget class="ButtonSwitch" name="insertPosition">
+         <property name="geometry">
+          <rect>
+           <x>150</x>
+           <y>40</y>
+           <width>24</width>
+           <height>24</height>
+          </rect>
+         </property>
+         <property name="toolTip">
+          <string>Insert position (latitude and longitude)</string>
+         </property>
+         <property name="text">
+          <string>...</string>
+         </property>
+         <property name="icon">
+          <iconset>
+           <normalon>:/gps.png</normalon>
+          </iconset>
+         </property>
+        </widget>
+        <widget class="QLabel" name="destinationLabel_2">
+         <property name="geometry">
+          <rect>
+           <x>150</x>
+           <y>70</y>
+           <width>30</width>
+           <height>28</height>
+          </rect>
+         </property>
+         <property name="text">
+          <string>CAN</string>
+         </property>
+        </widget>
+        <widget class="QSpinBox" name="can">
+         <property name="geometry">
+          <rect>
+           <x>180</x>
+           <y>70</y>
+           <width>56</width>
+           <height>28</height>
+          </rect>
+         </property>
+         <property name="maximum">
+          <number>255</number>
+         </property>
+         <property name="value">
+          <number>10</number>
+         </property>
+        </widget>
+       </widget>
+      </item>
+     </layout>
+    </item>
+   </layout>
+  </widget>
  </widget>
  <customwidgets>
   <customwidget>
@@ -723,12 +1355,23 @@
    <extends>QToolButton</extends>
    <header>gui/buttonswitch.h</header>
   </customwidget>
+  <customwidget>
+   <class>TVScreen</class>
+   <extends>QWidget</extends>
+   <header>gui/tvscreen.h</header>
+   <container>1</container>
+  </customwidget>
   <customwidget>
    <class>LevelMeterVU</class>
    <extends>QWidget</extends>
    <header>gui/levelmeter.h</header>
    <container>1</container>
   </customwidget>
+  <customwidget>
+   <class>CustomTextEdit</class>
+   <extends>QTextEdit</extends>
+   <header>gui/customtextedit.h</header>
+  </customwidget>
  </customwidgets>
  <resources>
   <include location="../../../sdrgui/resources/res.qrc"/>
diff --git a/plugins/channeltx/modm17/m17modprocessor.cpp b/plugins/channeltx/modm17/m17modprocessor.cpp
new file mode 100644
index 000000000..5933106a9
--- /dev/null
+++ b/plugins/channeltx/modm17/m17modprocessor.cpp
@@ -0,0 +1,99 @@
+///////////////////////////////////////////////////////////////////////////////////
+// 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 "m17/M17Modulator.h"
+
+#include "m17modprocessor.h"
+
+M17ModProcessor::M17ModProcessor()
+{
+    m_basebandFifo.setSampleSize(sizeof(int16_t), 48000);
+    connect(&m_inputMessageQueue, SIGNAL(messageEnqueued()), this, SLOT(handleInputMessages()));
+}
+
+M17ModProcessor::~M17ModProcessor()
+{}
+
+bool M17ModProcessor::handleMessage(const Message& cmd)
+{
+    if (MsgSendSMS::match(cmd))
+    {
+        const MsgSendSMS& notif = (const MsgSendSMS&) cmd;
+        QByteArray packetBytes = notif.getSMSText().toUtf8();
+        packetBytes.prepend(0x05); // SMS standard type
+        packetBytes.truncate(798); // Maximum packet size is 798 payload + 2 bytes CRC = 800 bytes (32*25)
+        processPacket(notif.getSourceCall(), notif.getDestCall(), packetBytes);
+        return true;
+    }
+
+    return false;
+}
+
+
+void M17ModProcessor::handleInputMessages()
+{
+	Message* message;
+
+	while ((message = m_inputMessageQueue.pop()) != nullptr)
+	{
+		if (handleMessage(*message)) {
+			delete message;
+		}
+	}
+}
+
+void M17ModProcessor::processPacket(const QString& sourceCall, const QString& destCall, const QByteArray& packetBytes)
+{
+    mobilinkd::M17Modulator modulator(sourceCall.toStdString(), destCall.toStdString());
+    // preamble
+    std::array<uint8_t, 48> preamble_bytes;
+    mobilinkd::M17Modulator::make_preamble(preamble_bytes);
+    std::array<int8_t, 48 * 4> fullframe_symbols = mobilinkd::M17Modulator::bytes_to_symbols(preamble_bytes);
+    std::array<int16_t, 1920> baseband = mobilinkd::M17Modulator::symbols_to_baseband(fullframe_symbols);
+    m_basebandFifo.write((const quint8*) baseband.data(), 1920);
+    // LSF
+    mobilinkd::M17Modulator::lich_t lichSegments; // Not used for packet
+    mobilinkd::M17Modulator::frame_t frame;
+    modulator.make_link_setup(lichSegments, frame);
+    std::array<int8_t, 46 * 4> frame_symbols = mobilinkd::M17Modulator::bytes_to_symbols(frame);
+    std::copy(mobilinkd::M17Modulator::LSF_SYNC_WORD.begin(), mobilinkd::M17Modulator::LSF_SYNC_WORD.end(), fullframe_symbols.begin());
+    std::copy(frame_symbols.begin(), frame_symbols.end(), fullframe_symbols.begin()+2);
+    baseband = mobilinkd::M17Modulator::symbols_to_baseband(fullframe_symbols);
+    m_basebandFifo.write((const quint8*) baseband.data(), 1920);
+    // Packets
+    std::copy(mobilinkd::M17Modulator::DATA_SYNC_WORD.begin(), mobilinkd::M17Modulator::DATA_SYNC_WORD.end(), fullframe_symbols.begin());
+    mobilinkd::M17Modulator::packet_t packet;
+    int remainderCount = packetBytes.size();
+    int packetCount = 0;
+
+    while (remainderCount > 25)
+    {
+        std::copy(packetBytes.begin() + (packetCount*25), packetBytes.begin() + ((packetCount+1)*25), packet.begin());
+        frame = modulator.make_packet_frame(packetCount, false, packet, 25);
+        std::copy(frame_symbols.begin(), frame_symbols.end(), fullframe_symbols.begin()+2);
+        baseband = mobilinkd::M17Modulator::symbols_to_baseband(fullframe_symbols);
+        m_basebandFifo.write((const quint8*) baseband.data(), 1920);
+        remainderCount -= 25;
+        packetCount++;
+    }
+
+    std::copy(packetBytes.begin() + (packetCount*25), packetBytes.begin() + (packetCount*25) + remainderCount, packet.begin());
+    frame = modulator.make_packet_frame(packetCount, true, packet, remainderCount);
+    std::copy(frame_symbols.begin(), frame_symbols.end(), fullframe_symbols.begin()+2);
+    baseband = mobilinkd::M17Modulator::symbols_to_baseband(fullframe_symbols);
+    m_basebandFifo.write((const quint8*) baseband.data(), 1920);
+}
diff --git a/plugins/channeltx/modm17/m17modprocessor.h b/plugins/channeltx/modm17/m17modprocessor.h
index 891fc0390..8a2ba7f32 100644
--- a/plugins/channeltx/modm17/m17modprocessor.h
+++ b/plugins/channeltx/modm17/m17modprocessor.h
@@ -29,22 +29,28 @@ class M17ModProcessor : public QObject
 {
     Q_OBJECT
 public:
-    class MsgSendPacket : public Message {
+    class MsgSendSMS : public Message {
         MESSAGE_CLASS_DECLARATION
 
     public:
-        const QByteArray& getPacket() const { return m_packet; }
+        const QString& getSourceCall() const { return m_sourceCall; }
+        const QString& getDestCall() const { return m_destCall; }
+        const QString& getSMSText() const { return m_smsText; }
 
-        static MsgSendPacket* create(const QByteArray& packet) {
-            return new MsgSendPacket(packet);
+        static MsgSendSMS* create(const QString& sourceCall, const QString& destCall, const QString& smsText) {
+            return new MsgSendSMS(sourceCall, destCall, smsText);
         }
 
     private:
-        QByteArray m_packet;
+        QString m_sourceCall;
+        QString m_destCall;
+        QString m_smsText;
 
-        MsgSendPacket(const QByteArray& bytes) :
+        MsgSendSMS(const QString& sourceCall, const QString& destCall, const QString& smsText) :
             Message(),
-            m_packet(bytes)
+            m_sourceCall(sourceCall),
+            m_destCall(destCall),
+            m_smsText(smsText)
         { }
     };
 
@@ -59,6 +65,7 @@ private:
     AudioFifo m_basebandFifo; //!< Samples are 16 bit integer baseband 48 kS/s samples
 
     bool handleMessage(const Message& cmd);
+    void processPacket(const QString& sourceCall, const QString& destCall, const QByteArray& packetBytes);
 
 private slots:
     void handleInputMessages();
diff --git a/plugins/channeltx/modm17/m17modsettings.cpp b/plugins/channeltx/modm17/m17modsettings.cpp
index 2eea4748d..b5062e2e2 100644
--- a/plugins/channeltx/modm17/m17modsettings.cpp
+++ b/plugins/channeltx/modm17/m17modsettings.cpp
@@ -42,7 +42,9 @@ void M17ModSettings::resetToDefaults()
     m_playLoop = false;
     m_rgbColor = QColor(255, 0, 255).rgb();
     m_title = "M17 Modulator";
-    m_modAFInput = M17ModInputAF::M17ModInputNone;
+    m_m17Mode = M17Mode::M17ModeNone;
+    m_audioType = AudioType::AudioNone;
+    m_packetType = PacketType::PacketNone;
     m_audioDeviceName = AudioDeviceManager::m_defaultDeviceName;
     m_feedbackAudioDeviceName = AudioDeviceManager::m_defaultDeviceName;
     m_feedbackVolumeFactor = 0.5f;
@@ -55,6 +57,16 @@ void M17ModSettings::resetToDefaults()
     m_reverseAPIChannelIndex = 0;
     m_workspaceIndex = 0;
     m_hidden = false;
+    m_sourceCall = "";
+    m_destCall = "";
+    m_insertPosition = false;
+    m_can = 10;
+    m_smsText = "";
+    m_aprsCallsign = "MYCALL";
+    m_aprsTo = "APRS";
+    m_aprsVia = "WIDE2-2";
+    m_aprsData = ">Using SDRangel";
+    m_aprsInsertPosition = 0;
 }
 
 QByteArray M17ModSettings::serialize() const
@@ -67,13 +79,15 @@ QByteArray M17ModSettings::serialize() const
     s.writeU32(5, m_rgbColor);
     s.writeReal(6, m_toneFrequency);
     s.writeReal(7, m_volumeFactor);
+    s.writeS32(8, (int) m_m17Mode);
+    s.writeS32(9, (int) m_audioType);
+    s.writeS32(10, (int) m_packetType);
 
     if (m_channelMarker) {
         s.writeBlob(11, m_channelMarker->serialize());
     }
 
     s.writeString(12, m_title);
-    s.writeS32(13, (int) m_modAFInput);
     s.writeString(14, m_audioDeviceName);
     s.writeBool(15, m_useReverseAPI);
     s.writeString(16, m_reverseAPIAddress);
@@ -93,6 +107,19 @@ QByteArray M17ModSettings::serialize() const
     s.writeBlob(29, m_geometryBytes);
     s.writeBool(30, m_hidden);
 
+    s.writeString(40, m_sourceCall);
+    s.writeString(41, m_destCall);
+    s.writeBool(42, m_insertPosition);
+    s.writeU32(43, m_can);
+
+    s.writeString(50, m_smsText);
+
+    s.writeString(60, m_aprsCallsign);
+    s.writeString(61, m_aprsTo);
+    s.writeString(62, m_aprsVia);
+    s.writeString(63, m_aprsData);
+    s.writeBool(64, m_aprsInsertPosition);
+
     return s.final();
 }
 
@@ -119,7 +146,13 @@ bool M17ModSettings::deserialize(const QByteArray& data)
         d.readU32(5, &m_rgbColor);
         d.readReal(6, &m_toneFrequency, 1000.0);
         d.readReal(7, &m_volumeFactor, 1.0);
-        d.readBlob(8, &bytetmp);
+        d.readS32(8, &tmp, 0);
+        m_m17Mode = tmp < 0 ? M17ModeNone : tmp > (int) M17ModeM17BERT ? M17ModeM17BERT : (M17Mode) tmp;
+        d.readS32(9, &tmp, 0);
+        m_audioType = tmp < 0 ? AudioNone : tmp > (int) AudioInput ? AudioInput : (AudioType) tmp;
+        m_packetType = tmp < 0 ? PacketNone : tmp > (int) PacketSMS ? PacketSMS : (PacketType) tmp;
+
+        d.readBlob(11, &bytetmp);
 
         if (m_channelMarker)
         {
@@ -129,13 +162,6 @@ bool M17ModSettings::deserialize(const QByteArray& data)
 
         d.readString(12, &m_title, "M17 Modulator");
 
-        d.readS32(13, &tmp, 0);
-        if ((tmp < 0) || (tmp > (int) M17ModInputAF::M17ModInputTone)) {
-            m_modAFInput = M17ModInputNone;
-        } else {
-            m_modAFInput = (M17ModInputAF) tmp;
-        }
-
         d.readString(14, &m_audioDeviceName, AudioDeviceManager::m_defaultDeviceName);
         d.readBool(15, &m_useReverseAPI, false);
         d.readString(16, &m_reverseAPIAddress, "127.0.0.1");
@@ -167,6 +193,20 @@ bool M17ModSettings::deserialize(const QByteArray& data)
         d.readBlob(29, &m_geometryBytes);
         d.readBool(30, &m_hidden, false);
 
+        d.readString(40, &m_sourceCall, "");
+        d.readString(41, &m_destCall, "");
+        d.readBool(42, &m_insertPosition, false);
+        d.readU32(43, &utmp);
+        m_can = utmp < 255 ? utmp : 255;
+
+        d.readString(50, &m_smsText, "");
+
+        d.readString(60, &m_aprsCallsign, "MYCALL");
+        d.readString(61, &m_aprsTo, "");
+        d.readString(62, &m_aprsVia, "");
+        d.readString(63, &m_aprsData, "");
+        d.readBool(64, &m_aprsInsertPosition, false);
+
         return true;
     }
     else
diff --git a/plugins/channeltx/modm17/m17modsettings.h b/plugins/channeltx/modm17/m17modsettings.h
index d0509620e..bb0ede7d2 100644
--- a/plugins/channeltx/modm17/m17modsettings.h
+++ b/plugins/channeltx/modm17/m17modsettings.h
@@ -26,12 +26,28 @@ class Serializable;
 
 struct M17ModSettings
 {
-    enum M17ModInputAF
+    enum M17Mode
     {
-        M17ModInputNone,
-        M17ModInputFile,
-        M17ModInputAudio,
-        M17ModInputTone
+        M17ModeNone,
+        M17ModeFMTone,
+        M17ModeFMAudio,
+        M17ModeM17Audio,
+        M17ModeM17Packet,
+        M17ModeM17BERT
+    };
+
+    enum AudioType
+    {
+        AudioNone,
+        AudioFile,
+        AudioInput
+    };
+
+    enum PacketType
+    {
+        PacketNone,
+        PacketSMS,
+        PacketAPRS
     };
 
     qint64 m_inputFrequencyOffset;
@@ -43,7 +59,9 @@ struct M17ModSettings
     bool m_playLoop;
     quint32 m_rgbColor;
     QString m_title;
-    M17ModInputAF m_modAFInput;
+    M17Mode m_m17Mode;
+    AudioType m_audioType;
+    PacketType m_packetType;
     QString m_audioDeviceName;         //!< This is the audio device you get the audio samples from
     QString m_feedbackAudioDeviceName; //!< This is the audio device you send the audio samples to for audio feedback
     float m_feedbackVolumeFactor;
@@ -58,6 +76,19 @@ struct M17ModSettings
     QByteArray m_geometryBytes;
     bool m_hidden;
 
+    QString m_sourceCall;
+    QString m_destCall;
+    bool m_insertPosition;
+    uint8_t m_can;
+
+    QString m_smsText;
+
+    QString m_aprsCallsign;
+    QString m_aprsTo;
+    QString m_aprsVia;
+    QString m_aprsData;
+    bool m_aprsInsertPosition;
+
     Serializable *m_channelMarker;
     Serializable *m_rollupState;
 
diff --git a/plugins/channeltx/modm17/m17modsource.cpp b/plugins/channeltx/modm17/m17modsource.cpp
index b124fe143..a52ad489c 100644
--- a/plugins/channeltx/modm17/m17modsource.cpp
+++ b/plugins/channeltx/modm17/m17modsource.cpp
@@ -21,6 +21,7 @@
 #include "util/messagequeue.h"
 #include "maincore.h"
 
+#include "m17modprocessor.h"
 #include "m17modsource.h"
 
 const int M17ModSource::m_levelNbSamples = 480; // every 10ms
@@ -53,12 +54,15 @@ M17ModSource::M17ModSource() :
 
 	m_magsq = 0.0;
 
+    m_processor = new M17ModProcessor();
+
     applySettings(m_settings, true);
     applyChannelSettings(m_channelSampleRate, m_channelFrequencyOffset, true);
 }
 
 M17ModSource::~M17ModSource()
 {
+    delete m_processor;
 }
 
 void M17ModSource::pull(SampleVector::iterator begin, unsigned int nbSamples)
@@ -140,25 +144,40 @@ void M17ModSource::pullAudio(unsigned int nbSamplesAudio)
 void M17ModSource::modulateSample()
 {
 	Real t1, t;
+    bool carrier;
 
-    pullAF(t);
+    if ((m_settings.m_m17Mode == M17ModSettings::M17ModeFMTone) || (m_settings.m_m17Mode == M17ModSettings::M17ModeFMAudio)) {
+        pullAF(t, carrier);
+    } else if (m_settings.m_m17Mode != M17ModSettings::M17ModeNone) {
+        pullM17(t, carrier);
+    } else {
+        t = 0;
+    }
 
     if (m_settings.m_feedbackAudioEnable) {
         pushFeedback(t * m_settings.m_feedbackVolumeFactor * 16384.0f);
     }
 
-    calculateLevel(t);
-    t1 = m_lowpass.filter(t) * 1.2f;
+    if (carrier)
+    {
+        calculateLevel(t);
+        t1 = m_lowpass.filter(t) * 1.2f;
 
-    m_modPhasor += (m_settings.m_fmDeviation / (float) m_audioSampleRate) * t1;
+        m_modPhasor += (m_settings.m_fmDeviation / (float) m_audioSampleRate) * t1;
 
-    // limit phasor range to ]-pi,pi]
-    if (m_modPhasor > M_PI) {
-        m_modPhasor -= (2.0f * M_PI);
+        // limit phasor range to ]-pi,pi]
+        if (m_modPhasor > M_PI) {
+            m_modPhasor -= (2.0f * M_PI);
+        }
+
+        m_modSample.real(cos(m_modPhasor) * 0.891235351562f * SDR_TX_SCALEF); // -1 dB
+        m_modSample.imag(sin(m_modPhasor) * 0.891235351562f * SDR_TX_SCALEF);
+    }
+    else
+    {
+        m_modSample.real(0.0f);
+        m_modSample.imag(0.0f);
     }
-
-    m_modSample.real(cos(m_modPhasor) * 0.891235351562f * SDR_TX_SCALEF); // -1 dB
-    m_modSample.imag(sin(m_modPhasor) * 0.891235351562f * SDR_TX_SCALEF);
 
     m_demodBuffer[m_demodBufferFill] = t1 * std::numeric_limits<int16_t>::max();
     ++m_demodBufferFill;
@@ -186,63 +205,74 @@ void M17ModSource::modulateSample()
     }
 }
 
-void M17ModSource::pullAF(Real& sample)
+void M17ModSource::pullAF(Real& sample, bool& carrier)
 {
-    switch (m_settings.m_modAFInput)
-    {
-    case M17ModSettings::M17ModInputTone:
-        sample = m_toneNco.next();
-        break;
-    case M17ModSettings::M17ModInputFile:
-        // sox f4exb_call.wav --encoding float --endian little f4exb_call.raw
-        // ffplay -f f32le -ar 48k -ac 1 f4exb_call.raw
-        if (m_ifstream && m_ifstream->is_open())
-        {
-            if (m_ifstream->eof())
-            {
-            	if (m_settings.m_playLoop)
-            	{
-                    m_ifstream->clear();
-                    m_ifstream->seekg(0, std::ios::beg);
-            	}
-            }
+    carrier = true;
 
-            if (m_ifstream->eof())
+    if (m_settings.m_m17Mode == M17ModSettings::M17ModeFMTone)
+    {
+        sample = m_toneNco.next();
+    }
+    else if (m_settings.m_m17Mode == M17ModSettings::M17ModeFMAudio)
+    {
+        if (m_settings.m_audioType == M17ModSettings::AudioFile)
+        {
+            // sox f4exb_call.wav --encoding float --endian little f4exb_call.raw
+            // ffplay -f f32le -ar 48k -ac 1 f4exb_call.raw
+            if (m_ifstream && m_ifstream->is_open())
             {
-            	sample = 0.0f;
+                if (m_ifstream->eof())
+                {
+                    if (m_settings.m_playLoop)
+                    {
+                        m_ifstream->clear();
+                        m_ifstream->seekg(0, std::ios::beg);
+                    }
+                }
+
+                if (m_ifstream->eof())
+                {
+                    sample = 0.0f;
+                }
+                else
+                {
+                    m_ifstream->read(reinterpret_cast<char*>(&sample), sizeof(Real));
+                    sample *= m_settings.m_volumeFactor;
+                }
             }
             else
             {
-            	m_ifstream->read(reinterpret_cast<char*>(&sample), sizeof(Real));
-            	sample *= m_settings.m_volumeFactor;
+                sample = 0.0f;
+            }
+        }
+        else if (m_settings.m_audioType == M17ModSettings::AudioInput)
+        {
+            if (m_audioBufferFill < m_audioBuffer.size())
+            {
+                sample = ((m_audioBuffer[m_audioBufferFill].l + m_audioBuffer[m_audioBufferFill].r) / 65536.0f) * m_settings.m_volumeFactor;
+                m_audioBufferFill++;
+            }
+            else
+            {
+                unsigned int size = m_audioBuffer.size();
+                qDebug("NFMModSource::pullAF: starve audio samples: size: %u", size);
+                sample = ((m_audioBuffer[size-1].l + m_audioBuffer[size-1].r) / 65536.0f) * m_settings.m_volumeFactor;
             }
         }
         else
         {
             sample = 0.0f;
         }
-        break;
-    case M17ModSettings::M17ModInputAudio:
-        if (m_audioBufferFill < m_audioBuffer.size())
-        {
-            sample = ((m_audioBuffer[m_audioBufferFill].l + m_audioBuffer[m_audioBufferFill].r) / 65536.0f) * m_settings.m_volumeFactor;
-            m_audioBufferFill++;
-        }
-        else
-        {
-            unsigned int size = m_audioBuffer.size();
-            qDebug("NFMModSource::pullAF: starve audio samples: size: %u", size);
-            sample = ((m_audioBuffer[size-1].l + m_audioBuffer[size-1].r) / 65536.0f) * m_settings.m_volumeFactor;
-        }
-
-        break;
-    case M17ModSettings::M17ModInputNone:
-    default:
-        sample = 0.0f;
-        break;
     }
 }
 
+void M17ModSource::pullM17(Real& sample, bool& carrier)
+{
+    // TODO
+    carrier = false;
+    sample = 0.0f;
+}
+
 void M17ModSource::pushFeedback(Real sample)
 {
     Complex c(sample, sample);
@@ -368,9 +398,9 @@ void M17ModSource::applySettings(const M17ModSettings& settings, bool force)
         m_toneNco.setFreq(settings.m_toneFrequency, m_audioSampleRate);
     }
 
-    if ((settings.m_modAFInput != m_settings.m_modAFInput) || force)
+    if ((settings.m_audioType != m_settings.m_audioType) || force)
     {
-        if (settings.m_modAFInput == M17ModSettings::M17ModInputAudio) {
+        if (settings.m_audioType == M17ModSettings::AudioInput) {
             connect(&m_audioFifo, SIGNAL(dataReady()), this, SLOT(handleAudio()));
         } else {
             disconnect(&m_audioFifo, SIGNAL(dataReady()), this, SLOT(handleAudio()));
@@ -416,3 +446,16 @@ void M17ModSource::handleAudio()
         }
     }
 }
+
+void M17ModSource::sendPacket()
+{
+    if (m_settings.m_packetType == M17ModSettings::PacketType::PacketSMS)
+    {
+        M17ModProcessor::MsgSendSMS *msg = M17ModProcessor::MsgSendSMS::create(
+            m_settings.m_sourceCall,
+            m_settings.m_destCall,
+            m_settings.m_smsText
+        );
+        m_processor->getInputMessageQueue()->push(msg);
+    }
+}
diff --git a/plugins/channeltx/modm17/m17modsource.h b/plugins/channeltx/modm17/m17modsource.h
index 6b852b4b1..6890edc5b 100644
--- a/plugins/channeltx/modm17/m17modsource.h
+++ b/plugins/channeltx/modm17/m17modsource.h
@@ -37,6 +37,7 @@
 #include "m17modsettings.h"
 
 class ChannelAPI;
+class M17ModProcessor;
 
 class M17ModSource : public QObject, public ChannelSampleSource
 {
@@ -67,6 +68,8 @@ public:
     void applySettings(const M17ModSettings& settings, bool force = false);
     void applyChannelSettings(int channelSampleRate, int channelFrequencyOffset, bool force = false);
 
+    void sendPacket();
+
 private:
     int m_channelSampleRate;
     int m_channelFrequencyOffset;
@@ -115,6 +118,7 @@ private:
     Real m_levelSum;
 
     std::ifstream *m_ifstream;
+    M17ModProcessor *m_processor;
 
     QMutex m_mutex;
 
@@ -122,7 +126,8 @@ private:
     static const float m_preemphasis;
 
     void processOneSample(Complex& ci);
-    void pullAF(Real& sample);
+    void pullAF(Real& sample, bool& carrier);
+    void pullM17(Real& sample, bool& carrier);
     void pullAudio(unsigned int nbSamples);
     void pushFeedback(Real sample);
     void calculateLevel(Real& sample);
diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html
index c77a093ed..0099b9d15 100644
--- a/sdrbase/resources/webapi/doc/html2/index.html
+++ b/sdrbase/resources/webapi/doc/html2/index.html
@@ -8390,8 +8390,17 @@ margin-bottom: 20px;
     "audioDeviceName" : {
       "type" : "string"
     },
-    "modAFInput" : {
-      "type" : "integer"
+    "m17Mode" : {
+      "type" : "integer",
+      "description" : "M17Mode"
+    },
+    "audioType" : {
+      "type" : "integer",
+      "description" : "AudioType"
+    },
+    "packetType" : {
+      "type" : "integer",
+      "description" : "PacketType"
     },
     "streamIndex" : {
       "type" : "integer",
@@ -56380,7 +56389,7 @@ except ApiException as e:
           </div>
           <div id="generator">
             <div class="content">
-              Generated 2022-06-09T22:25:54.513+02:00
+              Generated 2022-06-10T22:26:56.056+02:00
             </div>
           </div>
       </div>
diff --git a/sdrbase/resources/webapi/doc/swagger/include/M17Mod.yaml b/sdrbase/resources/webapi/doc/swagger/include/M17Mod.yaml
index 5cb05b2ce..8a87ee14a 100644
--- a/sdrbase/resources/webapi/doc/swagger/include/M17Mod.yaml
+++ b/sdrbase/resources/webapi/doc/swagger/include/M17Mod.yaml
@@ -26,8 +26,15 @@ M17ModSettings:
       type: string
     audioDeviceName:
       type: string
-    modAFInput:
+    m17Mode:
       type: integer
+      description: M17Mode
+    audioType:
+      type: integer
+      description: AudioType
+    packetType:
+      type: integer
+      description: PacketType
     streamIndex:
       description: MIMO channel. Not relevant when connected to SI (single Rx).
       type: integer
diff --git a/sdrgui/resources/res.qrc b/sdrgui/resources/res.qrc
index c62dcd600..90fe1ae90 100644
--- a/sdrgui/resources/res.qrc
+++ b/sdrgui/resources/res.qrc
@@ -8,6 +8,8 @@
     <file>bell_fill.png</file>
     <file>bell_gradient.png</file>
     <file>bell_line.png</file>
+    <file>world.png</file>
+    <file>sms.png</file>
     <file>ruler.png</file>
     <file>sort.png</file>
     <file>audio_mic.png</file>
diff --git a/sdrgui/resources/sms.png b/sdrgui/resources/sms.png
new file mode 100644
index 0000000000000000000000000000000000000000..0f9312a9a501d84171fb0c65d0a7d3a86839bae4
GIT binary patch
literal 821
zcmV-51Iqk~P)<h;3K|Lk000e1NJLTq000;O000;W1^@s6;CDUv0004mX+uL$Nkc;*
zaB^>EX>4Tx04R}tkv&MmKpe$iQ?;TM2Rn#3WT;LS#ELj-6^c+H)C#RSm|XfHG-*gu
zTpR`0f`cE6RR<SmT^(EnLGS~_(aA~CMN0f%QfLw5!Ery{-Fw`<1B7~+X;#NLpy{@m
zPA0@`Zbb~eq6@tUB96GsEMr!ZQt%yL_XzOyF2=L`&;2?2)V#%jfJi*U4AUlFC!X50
z4bJ<-5mu5_;&b9LlP*a7$aTfzH_k<u1)do(GwC_v2(egfW2KE*$<&A^iKD8fQ@)V(
zSmnIMSu0mr^Pc>L;k>@Q#C4j(NMZqtkRU=q6&onSMvPXS6botEPx$zUT)#vvg<Kn8
z<d{bV8f4cG{s+IiwF>bGFDaY=I$s>;V+0880*#vEd>=bb;{*sk16O*>U#SDrpQP7X
zTJ#9$-v%zOTbi;5T<!paPljyDt`wvx6bium8GTa@7`O#`R=wVu`#607GSpS-1~@nb
zMvIia=JW3E_TK(I)9mjDpQCcQA8<vX00006VoOIv00000008+zyMF)x010qNS#tmY
z4c7nw4c7reD4Tcy000McNliru<^~G`I0#RQsty1E0WwKMK~zY`?Uk`g15pq~&qNU=
zHVSF1EEUBfHa3clmAztVp_PTbU?W1lK=1=B{fPu^tb!<hfR%!s7>x1Qyui8;Ld*&l
zURd6~yX>5Kv%^l1q%HyrKro!1fqF!=$pk;MSlz2r0Fg4k1?s>B!)qQm1WqF27<D^Q
z_X`M)T1xTG0ZIkb{F@@;GrKSG%lZfW44}@aH|lD}tEgA%X2vV44RvSG6MO(~>FTNj
zw1L;0*8<*q0Y-bY+7qyr^Im}UoYw+2{=4Y!11vJ2I+E|AX1d>1$pIS?@sM5|1Ga#P
zK8%&LK4z)49)n{qJzr9r0}PyNb&~9_TAc-^({?Tmp97!*9HnyBfXhB7=tM-bUmdF{
zlh?G~smn!vs4k$Ro~q*%+zHjfv(5p^MP>a3q2WXn`<?dz00000NkvXXu0mjfc+F$Q

literal 0
HcmV?d00001

diff --git a/sdrgui/resources/world.png b/sdrgui/resources/world.png
new file mode 100644
index 0000000000000000000000000000000000000000..4572120e78f18e936c7931b2d4a709bc2ed5a2c7
GIT binary patch
literal 7828
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
D&}sz4

literal 0
HcmV?d00001

diff --git a/swagger/sdrangel/api/swagger/include/M17Mod.yaml b/swagger/sdrangel/api/swagger/include/M17Mod.yaml
index 42a65efa8..162071947 100644
--- a/swagger/sdrangel/api/swagger/include/M17Mod.yaml
+++ b/swagger/sdrangel/api/swagger/include/M17Mod.yaml
@@ -26,8 +26,15 @@ M17ModSettings:
       type: string
     audioDeviceName:
       type: string
-    modAFInput:
+    m17Mode:
       type: integer
+      description: M17Mode
+    audioType:
+      type: integer
+      description: AudioType
+    packetType:
+      type: integer
+      description: PacketType
     streamIndex:
       description: MIMO channel. Not relevant when connected to SI (single Rx).
       type: integer
diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html
index c77a093ed..0099b9d15 100644
--- a/swagger/sdrangel/code/html2/index.html
+++ b/swagger/sdrangel/code/html2/index.html
@@ -8390,8 +8390,17 @@ margin-bottom: 20px;
     "audioDeviceName" : {
       "type" : "string"
     },
-    "modAFInput" : {
-      "type" : "integer"
+    "m17Mode" : {
+      "type" : "integer",
+      "description" : "M17Mode"
+    },
+    "audioType" : {
+      "type" : "integer",
+      "description" : "AudioType"
+    },
+    "packetType" : {
+      "type" : "integer",
+      "description" : "PacketType"
     },
     "streamIndex" : {
       "type" : "integer",
@@ -56380,7 +56389,7 @@ except ApiException as e:
           </div>
           <div id="generator">
             <div class="content">
-              Generated 2022-06-09T22:25:54.513+02:00
+              Generated 2022-06-10T22:26:56.056+02:00
             </div>
           </div>
       </div>
diff --git a/swagger/sdrangel/code/qt5/client/SWGM17ModSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGM17ModSettings.cpp
index 1ff287761..db04df885 100644
--- a/swagger/sdrangel/code/qt5/client/SWGM17ModSettings.cpp
+++ b/swagger/sdrangel/code/qt5/client/SWGM17ModSettings.cpp
@@ -48,8 +48,12 @@ SWGM17ModSettings::SWGM17ModSettings() {
     m_title_isSet = false;
     audio_device_name = nullptr;
     m_audio_device_name_isSet = false;
-    mod_af_input = 0;
-    m_mod_af_input_isSet = false;
+    m17_mode = 0;
+    m_m17_mode_isSet = false;
+    audio_type = 0;
+    m_audio_type_isSet = false;
+    packet_type = 0;
+    m_packet_type_isSet = false;
     stream_index = 0;
     m_stream_index_isSet = false;
     use_reverse_api = 0;
@@ -94,8 +98,12 @@ SWGM17ModSettings::init() {
     m_title_isSet = false;
     audio_device_name = new QString("");
     m_audio_device_name_isSet = false;
-    mod_af_input = 0;
-    m_mod_af_input_isSet = false;
+    m17_mode = 0;
+    m_m17_mode_isSet = false;
+    audio_type = 0;
+    m_audio_type_isSet = false;
+    packet_type = 0;
+    m_packet_type_isSet = false;
     stream_index = 0;
     m_stream_index_isSet = false;
     use_reverse_api = 0;
@@ -133,6 +141,8 @@ SWGM17ModSettings::cleanup() {
 
 
 
+
+
     if(reverse_api_address != nullptr) { 
         delete reverse_api_address;
     }
@@ -178,7 +188,11 @@ SWGM17ModSettings::fromJsonObject(QJsonObject &pJson) {
     
     ::SWGSDRangel::setValue(&audio_device_name, pJson["audioDeviceName"], "QString", "QString");
     
-    ::SWGSDRangel::setValue(&mod_af_input, pJson["modAFInput"], "qint32", "");
+    ::SWGSDRangel::setValue(&m17_mode, pJson["m17Mode"], "qint32", "");
+    
+    ::SWGSDRangel::setValue(&audio_type, pJson["audioType"], "qint32", "");
+    
+    ::SWGSDRangel::setValue(&packet_type, pJson["packetType"], "qint32", "");
     
     ::SWGSDRangel::setValue(&stream_index, pJson["streamIndex"], "qint32", "");
     
@@ -242,8 +256,14 @@ SWGM17ModSettings::asJsonObject() {
     if(audio_device_name != nullptr && *audio_device_name != QString("")){
         toJsonValue(QString("audioDeviceName"), audio_device_name, obj, QString("QString"));
     }
-    if(m_mod_af_input_isSet){
-        obj->insert("modAFInput", QJsonValue(mod_af_input));
+    if(m_m17_mode_isSet){
+        obj->insert("m17Mode", QJsonValue(m17_mode));
+    }
+    if(m_audio_type_isSet){
+        obj->insert("audioType", QJsonValue(audio_type));
+    }
+    if(m_packet_type_isSet){
+        obj->insert("packetType", QJsonValue(packet_type));
     }
     if(m_stream_index_isSet){
         obj->insert("streamIndex", QJsonValue(stream_index));
@@ -374,13 +394,33 @@ SWGM17ModSettings::setAudioDeviceName(QString* audio_device_name) {
 }
 
 qint32
-SWGM17ModSettings::getModAfInput() {
-    return mod_af_input;
+SWGM17ModSettings::getM17Mode() {
+    return m17_mode;
 }
 void
-SWGM17ModSettings::setModAfInput(qint32 mod_af_input) {
-    this->mod_af_input = mod_af_input;
-    this->m_mod_af_input_isSet = true;
+SWGM17ModSettings::setM17Mode(qint32 m17_mode) {
+    this->m17_mode = m17_mode;
+    this->m_m17_mode_isSet = true;
+}
+
+qint32
+SWGM17ModSettings::getAudioType() {
+    return audio_type;
+}
+void
+SWGM17ModSettings::setAudioType(qint32 audio_type) {
+    this->audio_type = audio_type;
+    this->m_audio_type_isSet = true;
+}
+
+qint32
+SWGM17ModSettings::getPacketType() {
+    return packet_type;
+}
+void
+SWGM17ModSettings::setPacketType(qint32 packet_type) {
+    this->packet_type = packet_type;
+    this->m_packet_type_isSet = true;
 }
 
 qint32
@@ -498,7 +538,13 @@ SWGM17ModSettings::isSet(){
         if(audio_device_name && *audio_device_name != QString("")){
             isObjectUpdated = true; break;
         }
-        if(m_mod_af_input_isSet){
+        if(m_m17_mode_isSet){
+            isObjectUpdated = true; break;
+        }
+        if(m_audio_type_isSet){
+            isObjectUpdated = true; break;
+        }
+        if(m_packet_type_isSet){
             isObjectUpdated = true; break;
         }
         if(m_stream_index_isSet){
diff --git a/swagger/sdrangel/code/qt5/client/SWGM17ModSettings.h b/swagger/sdrangel/code/qt5/client/SWGM17ModSettings.h
index 2226d218b..794e6b639 100644
--- a/swagger/sdrangel/code/qt5/client/SWGM17ModSettings.h
+++ b/swagger/sdrangel/code/qt5/client/SWGM17ModSettings.h
@@ -74,8 +74,14 @@ public:
     QString* getAudioDeviceName();
     void setAudioDeviceName(QString* audio_device_name);
 
-    qint32 getModAfInput();
-    void setModAfInput(qint32 mod_af_input);
+    qint32 getM17Mode();
+    void setM17Mode(qint32 m17_mode);
+
+    qint32 getAudioType();
+    void setAudioType(qint32 audio_type);
+
+    qint32 getPacketType();
+    void setPacketType(qint32 packet_type);
 
     qint32 getStreamIndex();
     void setStreamIndex(qint32 stream_index);
@@ -135,8 +141,14 @@ private:
     QString* audio_device_name;
     bool m_audio_device_name_isSet;
 
-    qint32 mod_af_input;
-    bool m_mod_af_input_isSet;
+    qint32 m17_mode;
+    bool m_m17_mode_isSet;
+
+    qint32 audio_type;
+    bool m_audio_type_isSet;
+
+    qint32 packet_type;
+    bool m_packet_type_isSet;
 
     qint32 stream_index;
     bool m_stream_index_isSet;