1
0
mirror of https://github.com/f4exb/sdrangel.git synced 2024-11-25 09:18:54 -05:00

M17 modulator: SMS packet +

This commit is contained in:
f4exb 2022-06-12 22:51:18 +02:00
parent dd2233f763
commit f10da64717
24 changed files with 1432 additions and 174 deletions

View File

@ -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)
{

View File

@ -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})

View File

@ -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();
}

View File

@ -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;

View File

@ -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());

View File

@ -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:
/**

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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"/>

View File

@ -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);
}

View File

@ -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();

View File

@ -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

View File

@ -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;

View File

@ -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,13 +144,22 @@ 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);
}
if (carrier)
{
calculateLevel(t);
t1 = m_lowpass.filter(t) * 1.2f;
@ -159,6 +172,12 @@ void M17ModSource::modulateSample()
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_demodBuffer[m_demodBufferFill] = t1 * std::numeric_limits<int16_t>::max();
++m_demodBufferFill;
@ -186,14 +205,18 @@ void M17ModSource::modulateSample()
}
}
void M17ModSource::pullAF(Real& sample)
void M17ModSource::pullAF(Real& sample, bool& carrier)
{
switch (m_settings.m_modAFInput)
carrier = true;
if (m_settings.m_m17Mode == M17ModSettings::M17ModeFMTone)
{
case M17ModSettings::M17ModInputTone:
sample = m_toneNco.next();
break;
case M17ModSettings::M17ModInputFile:
}
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())
@ -221,8 +244,9 @@ void M17ModSource::pullAF(Real& sample)
{
sample = 0.0f;
}
break;
case M17ModSettings::M17ModInputAudio:
}
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;
@ -234,13 +258,19 @@ void M17ModSource::pullAF(Real& sample)
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;
}
else
{
sample = 0.0f;
}
}
}
void M17ModSource::pullM17(Real& sample, bool& carrier)
{
// TODO
carrier = false;
sample = 0.0f;
}
void M17ModSource::pushFeedback(Real 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);
}
}

View File

@ -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);

View File

@ -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>

View File

@ -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

View File

@ -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>

BIN
sdrgui/resources/sms.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 821 B

BIN
sdrgui/resources/world.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -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

View File

@ -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>

View File

@ -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){

View File

@ -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;