diff --git a/doc/img/ChirpChatDemod_plugin.png b/doc/img/ChirpChatDemod_plugin.png index b005db7b2..ac354518b 100644 Binary files a/doc/img/ChirpChatDemod_plugin.png and b/doc/img/ChirpChatDemod_plugin.png differ diff --git a/doc/img/ChirpChatDemod_plugin.xcf b/doc/img/ChirpChatDemod_plugin.xcf index e504be01f..fd8f6f1b8 100644 Binary files a/doc/img/ChirpChatDemod_plugin.xcf and b/doc/img/ChirpChatDemod_plugin.xcf differ diff --git a/doc/img/ChirpChatMod_plugin.png b/doc/img/ChirpChatMod_plugin.png index 2665e4873..bf6665ff2 100644 Binary files a/doc/img/ChirpChatMod_plugin.png and b/doc/img/ChirpChatMod_plugin.png differ diff --git a/doc/img/ChirpChatMod_plugin.xcf b/doc/img/ChirpChatMod_plugin.xcf index 7baf945be..65144d6be 100644 Binary files a/doc/img/ChirpChatMod_plugin.xcf and b/doc/img/ChirpChatMod_plugin.xcf differ diff --git a/ft8/ft8.cpp b/ft8/ft8.cpp index 7f50613ee..85894e3a8 100644 --- a/ft8/ft8.cpp +++ b/ft8/ft8.cpp @@ -2634,7 +2634,7 @@ int FT8::decode(const float ll174[], int a174[], FT8Params& _params, int use_osd // apply (174, 91) generator mastrix to obtain the 83 parity bits // append the 83 bits to the 91 bits message + crc to obbain the 174 bit payload // -void FT8::encode(int a174[], int s77[]) +void FT8::encode(int a174[], const int s77[]) { int a91[91]; // msg + CRC std::fill(a91, a91 + 91, 0); diff --git a/ft8/ft8.h b/ft8/ft8.h index 2e84f3ac3..fb1b9533c 100644 --- a/ft8/ft8.h +++ b/ft8/ft8.h @@ -273,7 +273,7 @@ public: // adds the 14 bit CRC to obtain 91 bits // apply (174, 91) generator mastrix to obtain the 83 parity bits // append the 83 bits to the 91 bits message e+ crc to obtain the 174 bit payload - static void encode(int a174[], int s77[]); + static void encode(int a174[], const int s77[]); // // set ones and zero symbol indexes diff --git a/ft8/libldpc.cpp b/ft8/libldpc.cpp index 388651ec1..822c4b626 100644 --- a/ft8/libldpc.cpp +++ b/ft8/libldpc.cpp @@ -396,7 +396,7 @@ void LDPC::ft8_crc(int msg1[], int msglen, int out[14]) } } - for (int i = 0; i < msglen + 14; i++) + for (int i = 0; i < 14; i++) { out[i] = msg[msglen + i]; } diff --git a/plugins/channelrx/demodchirpchat/chirpchatdemod.cpp b/plugins/channelrx/demodchirpchat/chirpchatdemod.cpp index 38ad95c8f..f00ee2ea4 100644 --- a/plugins/channelrx/demodchirpchat/chirpchatdemod.cpp +++ b/plugins/channelrx/demodchirpchat/chirpchatdemod.cpp @@ -432,6 +432,14 @@ void ChirpChatDemod::applySettings(const ChirpChatDemodSettings& settings, bool << " m_sendViaUDP: " << settings.m_sendViaUDP << " m_udpAddress: " << settings.m_udpAddress << " m_udpPort: " << settings.m_udpPort + << " m_decodeActive: " << settings.m_decodeActive + << " m_eomSquelchTenths: " << settings.m_eomSquelchTenths + << " m_nbSymbolsMax: " << settings.m_nbSymbolsMax + << " m_preambleChirps: " << settings.m_preambleChirps + << " m_streamIndex: " << settings.m_streamIndex + << " m_useReverseAPI: " << settings.m_useReverseAPI + << " m_fftWindow: " << settings.m_fftWindow + << " m_invertRamps: " << settings.m_invertRamps << " m_rgbColor: " << settings.m_rgbColor << " m_title: " << settings.m_title << " force: " << force; @@ -533,6 +541,9 @@ void ChirpChatDemod::applySettings(const ChirpChatDemodSettings& settings, bool if ((settings.m_autoNbSymbolsMax != m_settings.m_autoNbSymbolsMax) || force) { reverseAPIKeys.append("autoNbSymbolsMax"); } + if ((settings.m_invertRamps != m_settings.m_invertRamps) || force) { + reverseAPIKeys.append("invertRamps"); + } if ((settings.m_udpAddress != m_settings.m_udpAddress) || force) { @@ -694,6 +705,9 @@ void ChirpChatDemod::webapiUpdateChannelSettings( uint16_t port = response.getChirpChatDemodSettings()->getUdpPort(); settings.m_udpPort = port < 1024 ? 1024 : port; } + if (channelSettingsKeys.contains("invertRamps")) { + settings.m_invertRamps = response.getChirpChatDemodSettings()->getInvertRamps() != 0; + } if (channelSettingsKeys.contains("rgbColor")) { settings.m_rgbColor = response.getChirpChatDemodSettings()->getRgbColor(); } @@ -757,6 +771,7 @@ void ChirpChatDemod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings response.getChirpChatDemodSettings()->setHasCrc(settings.m_hasCRC ? 1 : 0); response.getChirpChatDemodSettings()->setHasHeader(settings.m_hasHeader ? 1 : 0); response.getChirpChatDemodSettings()->setSendViaUdp(settings.m_sendViaUDP ? 1 : 0); + response.getChirpChatDemodSettings()->setInvertRamps(settings.m_invertRamps ? 1 : 0); if (response.getChirpChatDemodSettings()->getUdpAddress()) { *response.getChirpChatDemodSettings()->getUdpAddress() = settings.m_udpAddress; @@ -978,6 +993,9 @@ void ChirpChatDemod::webapiFormatChannelSettings( if (channelSettingsKeys.contains("updPort") || force) { swgChirpChatDemodSettings->setUdpPort(settings.m_udpPort); } + if (channelSettingsKeys.contains("invertRamps") || force) { + swgChirpChatDemodSettings->setInvertRamps(settings.m_invertRamps ? 1 : 0); + } if (channelSettingsKeys.contains("rgbColor") || force) { swgChirpChatDemodSettings->setRgbColor(settings.m_rgbColor); } diff --git a/plugins/channelrx/demodchirpchat/chirpchatdemodgui.cpp b/plugins/channelrx/demodchirpchat/chirpchatdemodgui.cpp index ceaada06e..0d4ce30a6 100644 --- a/plugins/channelrx/demodchirpchat/chirpchatdemodgui.cpp +++ b/plugins/channelrx/demodchirpchat/chirpchatdemodgui.cpp @@ -338,6 +338,12 @@ void ChirpChatDemodGUI::on_udpPort_editingFinished() applySettings(); } +void ChirpChatDemodGUI::on_invertRamps_stateChanged(int state) +{ + m_settings.m_invertRamps = (state == Qt::Checked); + applySettings(); +} + void ChirpChatDemodGUI::onWidgetRolled(QWidget* widget, bool rollDown) { (void) widget; @@ -529,6 +535,7 @@ void ChirpChatDemodGUI::displaySettings() } ui->messageLengthAuto->setChecked(m_settings.m_autoNbSymbolsMax); + ui->invertRamps->setChecked(m_settings.m_invertRamps); displaySquelch(); updateIndexLabel(); @@ -854,6 +861,7 @@ void ChirpChatDemodGUI::makeUIConnections() QObject::connect(ui->udpSend, &QCheckBox::stateChanged, this, &ChirpChatDemodGUI::on_udpSend_stateChanged); QObject::connect(ui->udpAddress, &QLineEdit::editingFinished, this, &ChirpChatDemodGUI::on_udpAddress_editingFinished); QObject::connect(ui->udpPort, &QLineEdit::editingFinished, this, &ChirpChatDemodGUI::on_udpPort_editingFinished); + QObject::connect(ui->invertRamps, &QCheckBox::stateChanged, this, &ChirpChatDemodGUI::on_invertRamps_stateChanged); } void ChirpChatDemodGUI::updateAbsoluteCenterFrequency() diff --git a/plugins/channelrx/demodchirpchat/chirpchatdemodgui.h b/plugins/channelrx/demodchirpchat/chirpchatdemodgui.h index 277ae430c..0d3830158 100644 --- a/plugins/channelrx/demodchirpchat/chirpchatdemodgui.h +++ b/plugins/channelrx/demodchirpchat/chirpchatdemodgui.h @@ -82,6 +82,7 @@ private slots: void on_udpSend_stateChanged(int state); void on_udpAddress_editingFinished(); void on_udpPort_editingFinished(); + void on_invertRamps_stateChanged(int state); void onWidgetRolled(QWidget* widget, bool rollDown); void onMenuDialogCalled(const QPoint& p); void channelMarkerHighlightedByCursor(); diff --git a/plugins/channelrx/demodchirpchat/chirpchatdemodgui.ui b/plugins/channelrx/demodchirpchat/chirpchatdemodgui.ui index d750cc76d..17534c6d3 100644 --- a/plugins/channelrx/demodchirpchat/chirpchatdemodgui.ui +++ b/plugins/channelrx/demodchirpchat/chirpchatdemodgui.ui @@ -526,6 +526,23 @@ + + + + Qt::Vertical + + + + + + + Invert preamble, SFD and payload ramps + + + Inv + + + diff --git a/plugins/channelrx/demodchirpchat/chirpchatdemodsettings.cpp b/plugins/channelrx/demodchirpchat/chirpchatdemodsettings.cpp index 0c6a95dc5..fec2de46b 100644 --- a/plugins/channelrx/demodchirpchat/chirpchatdemodsettings.cpp +++ b/plugins/channelrx/demodchirpchat/chirpchatdemodsettings.cpp @@ -80,6 +80,7 @@ void ChirpChatDemodSettings::resetToDefaults() m_hasCRC = true; m_hasHeader = true; m_sendViaUDP = false; + m_invertRamps = false; m_udpAddress = "127.0.0.1"; m_udpPort = 9999; m_rgbColor = QColor(255, 0, 255).rgb(); @@ -121,6 +122,7 @@ QByteArray ChirpChatDemodSettings::serialize() const s.writeBool(15, m_hasHeader); s.writeU32(17, m_preambleChirps); s.writeS32(18, (int) m_fftWindow); + s.writeBool(19, m_invertRamps); s.writeBool(20, m_useReverseAPI); s.writeString(21, m_reverseAPIAddress); s.writeU32(22, m_reverseAPIPort); @@ -188,6 +190,7 @@ bool ChirpChatDemodSettings::deserialize(const QByteArray& data) d.readU32(17, &m_preambleChirps, 8); d.readS32(18, &tmp, (int) FFTWindow::Rectangle); m_fftWindow = (FFTWindow::Function) tmp; + d.readBool(19, &m_invertRamps, false); d.readBool(20, &m_useReverseAPI, false); d.readString(21, &m_reverseAPIAddress, "127.0.0.1"); d.readU32(22, &utmp, 0); @@ -232,6 +235,113 @@ bool ChirpChatDemodSettings::deserialize(const QByteArray& data) } } +void ChirpChatDemodSettings::applySettings(const QStringList& settingsKeys, const ChirpChatDemodSettings& settings) +{ + if (settingsKeys.contains("inputFrequencyOffset")) + m_inputFrequencyOffset = settings.m_inputFrequencyOffset; + if (settingsKeys.contains("bandwidthIndex")) + m_bandwidthIndex = settings.m_bandwidthIndex; + if (settingsKeys.contains("spreadFactor")) + m_spreadFactor = settings.m_spreadFactor; + if (settingsKeys.contains("deBits")) + m_deBits = settings.m_deBits; + if (settingsKeys.contains("fftWindow")) + m_fftWindow = settings.m_fftWindow; + if (settingsKeys.contains("codingScheme")) + m_codingScheme = settings.m_codingScheme; + if (settingsKeys.contains("decodeActive")) + m_decodeActive = settings.m_decodeActive; + if (settingsKeys.contains("eomSquelchTenths")) + m_eomSquelchTenths = settings.m_eomSquelchTenths; + if (settingsKeys.contains("nbSymbolsMax")) + m_nbSymbolsMax = settings.m_nbSymbolsMax; + if (settingsKeys.contains("preambleChirps")) + m_preambleChirps = settings.m_preambleChirps; + if (settingsKeys.contains("nbParityBits")) + m_nbParityBits = settings.m_nbParityBits; + if (settingsKeys.contains("packetLength")) + m_packetLength = settings.m_packetLength; + if (settingsKeys.contains("hasCRC")) + m_hasCRC = settings.m_hasCRC; + if (settingsKeys.contains("hasHeader")) + m_hasHeader = settings.m_hasHeader; + if (settingsKeys.contains("sendViaUDP")) + m_sendViaUDP = settings.m_sendViaUDP; + if (settingsKeys.contains("invertRamps")) + m_invertRamps = settings.m_invertRamps; + if (settingsKeys.contains("udpAddress")) + m_udpAddress = settings.m_udpAddress; + if (settingsKeys.contains("udpPort")) + m_udpPort = settings.m_udpPort; + if (settingsKeys.contains("useReverseAPI")) + m_useReverseAPI = settings.m_useReverseAPI; + if (settingsKeys.contains("reverseAPIAddress")) + m_reverseAPIAddress = settings.m_reverseAPIAddress; + if (settingsKeys.contains("reverseAPIPort")) + m_reverseAPIPort = settings.m_reverseAPIPort; + if (settingsKeys.contains("reverseAPIDeviceIndex")) + m_reverseAPIDeviceIndex = settings.m_reverseAPIDeviceIndex; + if (settingsKeys.contains("reverseAPIChannelIndex")) + m_reverseAPIChannelIndex = settings.m_reverseAPIChannelIndex; + if (settingsKeys.contains("streamIndex")) + m_streamIndex = settings.m_streamIndex; +} + +QString ChirpChatDemodSettings::getDebugString(const QStringList& settingsKeys, bool force) const +{ + QString debug; + + if (force || settingsKeys.contains("inputFrequencyOffset")) + debug += QString("InputFrequencyOffset: %1 ").arg(m_inputFrequencyOffset); + if (force || settingsKeys.contains("bandwidthIndex")) + debug += QString("BandwidthIndex: %1 ").arg(m_bandwidthIndex); + if (force || settingsKeys.contains("spreadFactor")) + debug += QString("SpreadFactor: %1 ").arg(m_spreadFactor); + if (force || settingsKeys.contains("deBits")) + debug += QString("DEBits: %1 ").arg(m_deBits); + if (force || settingsKeys.contains("fftWindow")) + debug += QString("FFTWindow: %1 ").arg((int) m_fftWindow); + if (force || settingsKeys.contains("codingScheme")) + debug += QString("CodingScheme: %1 ").arg((int) m_codingScheme); + if (force || settingsKeys.contains("decodeActive")) + debug += QString("DecodeActive: %1 ").arg(m_decodeActive); + if (force || settingsKeys.contains("eomSquelchTenths")) + debug += QString("EOMSquelchTenths: %1 ").arg(m_eomSquelchTenths); + if (force || settingsKeys.contains("nbSymbolsMax")) + debug += QString("NbSymbolsMax: %1 ").arg(m_nbSymbolsMax); + if (force || settingsKeys.contains("preambleChirps")) + debug += QString("PreambleChirps: %1 ").arg(m_preambleChirps); + if (force || settingsKeys.contains("nbParityBits")) + debug += QString("NbParityBits: %1 ").arg(m_nbParityBits); + if (force || settingsKeys.contains("packetLength")) + debug += QString("PacketLength: %1 ").arg(m_packetLength); + if (force || settingsKeys.contains("hasCRC")) + debug += QString("HasCRC: %1 ").arg(m_hasCRC); + if (force || settingsKeys.contains("hasHeader")) + debug += QString("HasHeader: %1 ").arg(m_hasHeader); + if (force || settingsKeys.contains("sendViaUDP")) + debug += QString("SendViaUDP: %1 ").arg(m_sendViaUDP); + if (force || settingsKeys.contains("invertRamps")) + debug += QString("InvertRamps: %1 ").arg(m_invertRamps); + if (force || settingsKeys.contains("udpAddress")) + debug += QString("UDPAddress: %1 ").arg(m_udpAddress); + if (force || settingsKeys.contains("udpPort")) + debug += QString("UDPPort: %1 ").arg(m_udpPort); + if (force || settingsKeys.contains("useReverseAPI")) + debug += QString("UseReverseAPI: %1 ").arg(m_useReverseAPI); + if (force || settingsKeys.contains("reverseAPIAddress")) + debug += QString("ReverseAPIAddress: %1 ").arg(m_reverseAPIAddress); + if (force || settingsKeys.contains("reverseAPIPort")) + debug += QString("ReverseAPIPort: %1 ").arg(m_reverseAPIPort); + if (force || settingsKeys.contains("reverseAPIDeviceIndex")) + debug += QString("ReverseAPIDeviceIndex: %1 ").arg(m_reverseAPIDeviceIndex); + if (force || settingsKeys.contains("reverseAPIChannelIndex")) + debug += QString("ReverseAPIChannelIndex: %1 ").arg(m_reverseAPIChannelIndex); + if (force || settingsKeys.contains("streamIndex")) + debug += QString("StreamIndex: %1 ").arg(m_streamIndex); + return debug; +} + unsigned int ChirpChatDemodSettings::getNbSFDFourths() const { switch (m_codingScheme) diff --git a/plugins/channelrx/demodchirpchat/chirpchatdemodsettings.h b/plugins/channelrx/demodchirpchat/chirpchatdemodsettings.h index 2f21bc48d..ea5c6fbff 100644 --- a/plugins/channelrx/demodchirpchat/chirpchatdemodsettings.h +++ b/plugins/channelrx/demodchirpchat/chirpchatdemodsettings.h @@ -65,6 +65,7 @@ struct ChirpChatDemodSettings bool m_hasCRC; //!< Payload has CRC (LoRa) bool m_hasHeader; //!< Header present before actual payload (LoRa) bool m_sendViaUDP; //!< Send decoded message via UDP + bool m_invertRamps; //!< Invert chirp ramps vs standard LoRa (up/down/up is standard) QString m_udpAddress; //!< UDP address where to send message uint16_t m_udpPort; //!< UDP port where to send message uint32_t m_rgbColor; @@ -96,6 +97,8 @@ struct ChirpChatDemodSettings bool hasSyncWord() const; //!< Only LoRa has a syncword (for the moment) QByteArray serialize() const; bool deserialize(const QByteArray& data); + void applySettings(const QStringList& settingsKeys, const ChirpChatDemodSettings& settings); + QString getDebugString(const QStringList& settingsKeys, bool force=false) const; }; diff --git a/plugins/channelrx/demodchirpchat/chirpchatdemodsink.cpp b/plugins/channelrx/demodchirpchat/chirpchatdemodsink.cpp index 0c7c1ab85..2a56df3cf 100644 --- a/plugins/channelrx/demodchirpchat/chirpchatdemodsink.cpp +++ b/plugins/channelrx/demodchirpchat/chirpchatdemodsink.cpp @@ -158,7 +158,7 @@ void ChirpChatDemodSink::processSample(const Complex& ci) } else if (m_state == ChirpChatStateDetectPreamble) // look for preamble { - m_fft->in()[m_fftCounter++] = ci * m_downChirps[m_chirp]; // de-chirp the up ramp + m_fft->in()[m_fftCounter++] = ci * (m_settings.m_invertRamps ? m_upChirps[m_chirp] : m_downChirps[m_chirp]); // de-chirp the preamble ramp if (m_fftCounter == m_fftLength) { @@ -178,6 +178,11 @@ void ChirpChatDemodSink::processSample(const Complex& ci) m_fftInterpolation ) / m_fftInterpolation; + // When ramps are inverted, FFT output interpretation is reversed + if (m_settings.m_invertRamps) { + imax = (m_nbSymbols - imax) % m_nbSymbols; + } + if (m_magsqQueue.size() > m_settings.m_preambleChirps) { m_magsqQueue.pop(); } @@ -246,8 +251,8 @@ void ChirpChatDemodSink::processSample(const Complex& ci) } else if (m_state == ChirpChatStatePreamble) // preamble found look for SFD start { - m_fft->in()[m_fftCounter] = ci * m_downChirps[m_chirp]; // de-chirp the up ramp - m_fftSFD->in()[m_fftCounter] = ci * m_upChirps[m_chirp]; // de-chirp the down ramp + m_fft->in()[m_fftCounter] = ci * (m_settings.m_invertRamps ? m_upChirps[m_chirp] : m_downChirps[m_chirp]); // de-chirp the preamble ramp + m_fftSFD->in()[m_fftCounter] = ci * (m_settings.m_invertRamps ? m_downChirps[m_chirp] : m_upChirps[m_chirp]); // de-chirp the SFD ramp m_fftCounter++; if (m_fftCounter == m_fftLength) @@ -284,6 +289,11 @@ void ChirpChatDemodSink::processSample(const Complex& ci) m_fftInterpolation ) / m_fftInterpolation; + // When ramps are inverted, FFT output interpretation is reversed + if (m_settings.m_invertRamps) { + imax = (m_nbSymbols - imax) % m_nbSymbols; + } + m_preambleHistory[m_chirpCount] = imax; m_chirpCount++; double preDrop = magsqPre - magsqSFD; @@ -374,7 +384,7 @@ void ChirpChatDemodSink::processSample(const Complex& ci) } else if (m_state == ChirpChatStateReadPayload) { - m_fft->in()[m_fftCounter] = ci * m_downChirps[m_chirp]; // de-chirp the up ramp + m_fft->in()[m_fftCounter] = ci * (m_settings.m_invertRamps ? m_upChirps[m_chirp] : m_downChirps[m_chirp]); // de-chirp the payload ramp m_fftCounter++; if (m_fftCounter == m_fftLength) @@ -438,6 +448,11 @@ void ChirpChatDemodSink::processSample(const Complex& ci) ); } + // When ramps are inverted, FFT output interpretation is reversed + if (m_settings.m_invertRamps) { + imax = (m_nbSymbols * m_fftInterpolation - imax) % (m_nbSymbols * m_fftInterpolation); + } + symbol = evalSymbol(imax) % m_nbSymbolsEff; m_decodeMsg->pushBackSymbol(symbol); } diff --git a/plugins/channelrx/demodchirpchat/readme.md b/plugins/channelrx/demodchirpchat/readme.md index f1e8a2004..dc51f1f14 100644 --- a/plugins/channelrx/demodchirpchat/readme.md +++ b/plugins/channelrx/demodchirpchat/readme.md @@ -120,6 +120,12 @@ In practice it is difficult to make correct decodes if only one FFT bin is used This is the number of chirps expected in the preamble and has to be agreed between the transmitter and receiver. +

15: Invert chirp ramps

+ +The LoRa standard is up-chirps for the preamble, down-chirps for the SFD and up-chirps for the payload. + +When you check this option it inverts the direction of the chirps thus becoming down-chirps for the preamble, up-chirps for the SFD and down-chirps for the payload. +

A: Payload controls and indicators

![ChirpChat Demodulator payload controls](../../../doc/img/ChirpChatDemod_payload.png) @@ -165,6 +171,8 @@ LoRa mode only. This is the number of parity bits in the Hamming code used in th When a header is expected this control is disabled because the value used is the one found in the header. +In FT8 mode there is no FEC as FEC is handled within the FT payload (with LDPC) +

A.9: Payload CRC presence

LoRa mode: Use this checkbox to tell if you expect a 2 byte CRC at the end of the payload. FT mode: there is always a CRC. @@ -284,3 +292,43 @@ Controls are the usual controls of spectrum displays with the following restrict - The window type is non operating because the FFT window is chosen by (7) - The FFT size can be changed however it is set to 2SF where SF is the spread factor and thus displays correctly + + +

Common LoRa settings

+ +CR is the code rate and translates to FEC according to the following table + +| CR | FEC | +| --- | --- | +| 4/5 | 1 | +| 4/6 | 2 | +| 4/7 | 3 | +| 4/8 | 4 | + +When DE is on set DE to 2 else 0 + +

Generic

+ +| Use Case | SF | CR | BW (kHz) | DE Enabled? | Notes | +| --------------------- | --------- | ------- | -------- | ----------- | ----------------------------------------------------------- | +| High rate/short range | SF7 | 4/5 | 500 | Off | Fastest, urban/close devices | +| Balanced/general IoT | SF7–SF9 | 4/5–4/6 | 125–250 | Off | TTN default, good for gateways | +| Long range/rural | SF10–SF12 | 4/6–4/8 | 125 | On | Max sensitivity (-137 dBm), slow airtime | +| Extreme range | SF12 | 4/8 | 125 | On | Best for weak signals, long packets | + +

Meshtastic

+ +

Quick facts

+ + - Uses 0x2B sync byte + - In Europe the default channel is centered on 869.525 MHz + +

Presets table

+ +| Preset | SF | CR | BW (kHz) | DE? | Use Case | +| ------------------- | -- | ---- | -------- | --- | ---------------------------------------------------- | +| LONG_FAST (default) | 11 | 4/8 | 250 | On | Long range, moderate speed | +| MEDIUM_SLOW | 10 | 4/7 | 250 | On | Balanced range/reliability​ | +| SHORT_FAST | 9 | 4/7 | 250 | Off | Urban/short hops, faster​ | +| SHORT_TURBO | 7 | 4/5 | 500 | Off | Max speed, shortest range (region-limited)​ | +| LONG_SLOW | 12 | 4/8 | 125 | On | Extreme range, slowest meshtastic​ | diff --git a/plugins/channeltx/modchirpchat/chirpchatmod.cpp b/plugins/channeltx/modchirpchat/chirpchatmod.cpp index a8d74f9cf..0093ed49b 100644 --- a/plugins/channeltx/modchirpchat/chirpchatmod.cpp +++ b/plugins/channeltx/modchirpchat/chirpchatmod.cpp @@ -186,6 +186,21 @@ void ChirpChatMod::applySettings(const ChirpChatModSettings& settings, bool forc << " m_73message: " << settings.m_73Message << " m_qsoTextMessage: " << settings.m_qsoTextMessage << " m_textMessage: " << settings.m_textMessage + << " m_bytesMessage: " << settings.m_bytesMessage.toHex() + << " m_spreadFactor: " << settings.m_spreadFactor + << " m_deBits: " << settings.m_deBits + << " m_codingScheme: " << settings.m_codingScheme + << " m_nbParityBits: " << settings.m_nbParityBits + << " m_hasCRC: " << settings.m_hasCRC + << " m_hasHeader: " << settings.m_hasHeader + << " m_messageType: " << settings.m_messageType + << " m_preambleChirps: " << settings.m_preambleChirps + << " m_quietMillis: " << settings.m_quietMillis + << " m_messageRepeat: " << settings.m_messageRepeat + << " m_udpEnabled: " << settings.m_udpEnabled + << " m_udpAddress: " << settings.m_udpAddress + << " m_udpPort: " << settings.m_udpPort + << " m_invertRamps: " << settings.m_invertRamps << " m_useReverseAPI: " << settings.m_useReverseAPI << " m_reverseAPIAddress: " << settings.m_reverseAPIAddress << " m_reverseAPIAddress: " << settings.m_reverseAPIPort @@ -216,6 +231,17 @@ void ChirpChatMod::applySettings(const ChirpChatModSettings& settings, bool forc m_encoder.setNbSymbolBits(settings.m_spreadFactor, settings.m_deBits); } + if ((settings.m_spreadFactor != m_settings.m_spreadFactor) + || (settings.m_bandwidthIndex != m_settings.m_bandwidthIndex) || force) + { + if (getMessageQueueToGUI()) + { + m_currentPayloadTime = (m_symbols.size()*(1<push(rpt); + } + } + if ((settings.m_codingScheme != m_settings.m_codingScheme) || force) { reverseAPIKeys.append("codingScheme"); @@ -273,9 +299,17 @@ void ChirpChatMod::applySettings(const ChirpChatModSettings& settings, bool forc if ((settings.m_bytesMessage != m_settings.m_bytesMessage) || force) { reverseAPIKeys.append("bytesMessage"); } + if ((settings.m_preambleChirps != m_settings.m_preambleChirps) || force) { + reverseAPIKeys.append("preambleChirps"); + } + if ((settings.m_quietMillis != m_settings.m_quietMillis) || force) { + reverseAPIKeys.append("quietMillis"); + } + if ((settings.m_invertRamps != m_settings.m_invertRamps) || force) { + reverseAPIKeys.append("invertRamps"); + } ChirpChatModBaseband::MsgConfigureChirpChatModPayload *payloadMsg = nullptr; - std::vector symbols; if ((settings.m_messageType == ChirpChatModSettings::MessageNone) && ((settings.m_messageType != m_settings.m_messageType) || force)) @@ -294,18 +328,19 @@ void ChirpChatMod::applySettings(const ChirpChatModSettings& settings, bool forc || (settings.m_textMessage != m_settings.m_textMessage) || (settings.m_bytesMessage != m_settings.m_bytesMessage) || force) { - m_encoder.encode(settings, symbols); - payloadMsg = ChirpChatModBaseband::MsgConfigureChirpChatModPayload::create(symbols); + m_symbols.clear(); + m_encoder.encode(settings, m_symbols); + payloadMsg = ChirpChatModBaseband::MsgConfigureChirpChatModPayload::create(m_symbols); } if (payloadMsg) { m_basebandSource->getInputMessageQueue()->push(payloadMsg); - m_currentPayloadTime = (symbols.size()*(1<push(rpt); } } @@ -552,6 +587,9 @@ void ChirpChatMod::webapiUpdateChannelSettings( if (channelSettingsKeys.contains("udpPort")) { settings.m_udpPort = response.getChirpChatModSettings()->getUdpPort(); } + if (channelSettingsKeys.contains("invertRamps")) { + settings.m_invertRamps = response.getChirpChatModSettings()->getInvertRamps(); + } if (channelSettingsKeys.contains("rgbColor")) { settings.m_rgbColor = response.getChirpChatModSettings()->getRgbColor(); } @@ -703,6 +741,7 @@ void ChirpChatMod::webapiFormatChannelSettings(SWGSDRangel::SWGChannelSettings& response.getChirpChatModSettings()->setUdpEnabled(settings.m_udpEnabled); response.getChirpChatModSettings()->setUdpAddress(new QString(settings.m_udpAddress)); response.getChirpChatModSettings()->setUdpPort(settings.m_udpPort); + response.getChirpChatModSettings()->setInvertRamps(settings.m_invertRamps ? 1 : 0); response.getChirpChatModSettings()->setRgbColor(settings.m_rgbColor); @@ -935,6 +974,9 @@ void ChirpChatMod::webapiFormatChannelSettings( if (channelSettingsKeys.contains("udpPort") || force) { swgChirpChatModSettings->setUdpPort(settings.m_udpPort); } + if (channelSettingsKeys.contains("invertRamps") || force) { + swgChirpChatModSettings->setInvertRamps(settings.m_invertRamps ? 1 : 0); + } if (channelSettingsKeys.contains("rgbColor") || force) { swgChirpChatModSettings->setRgbColor(settings.m_rgbColor); diff --git a/plugins/channeltx/modchirpchat/chirpchatmod.h b/plugins/channeltx/modchirpchat/chirpchatmod.h index e2b990183..37a4615ce 100644 --- a/plugins/channeltx/modchirpchat/chirpchatmod.h +++ b/plugins/channeltx/modchirpchat/chirpchatmod.h @@ -167,6 +167,7 @@ private: ChirpChatModEncoder m_encoder; // TODO: check if it needs to be on its own thread ChirpChatModSettings m_settings; float m_currentPayloadTime; + std::vector m_symbols; SampleVector m_sampleBuffer; QRecursiveMutex m_settingsMutex; diff --git a/plugins/channeltx/modchirpchat/chirpchatmodgui.cpp b/plugins/channeltx/modchirpchat/chirpchatmodgui.cpp index deb422120..07e2f0e07 100644 --- a/plugins/channeltx/modchirpchat/chirpchatmodgui.cpp +++ b/plugins/channeltx/modchirpchat/chirpchatmodgui.cpp @@ -359,6 +359,12 @@ void ChirpChatModGUI::on_udpPort_editingFinished() applySettings(); } +void ChirpChatModGUI::on_invertRamps_stateChanged(int state) +{ + m_settings.m_invertRamps = (state == Qt::Checked); + applySettings(); +} + void ChirpChatModGUI::onWidgetRolled(QWidget* widget, bool rollDown) { (void) widget; @@ -541,6 +547,7 @@ void ChirpChatModGUI::displaySettings() ui->udpEnabled->setChecked(m_settings.m_udpEnabled); ui->udpAddress->setText(m_settings.m_udpAddress); ui->udpPort->setText(QString::number(m_settings.m_udpPort)); + ui->invertRamps->setChecked(m_settings.m_invertRamps); getRollupContents()->restoreState(m_rollupState); updateAbsoluteCenterFrequency(); blockApplySettings(false); @@ -658,6 +665,7 @@ void ChirpChatModGUI::makeUIConnections() QObject::connect(ui->udpEnabled, &QCheckBox::clicked, this, &ChirpChatModGUI::on_udpEnabled_clicked); QObject::connect(ui->udpAddress, &QLineEdit::editingFinished, this, &ChirpChatModGUI::on_udpAddress_editingFinished); QObject::connect(ui->udpPort, &QLineEdit::editingFinished, this, &ChirpChatModGUI::on_udpPort_editingFinished); + QObject::connect(ui->invertRamps, &QCheckBox::stateChanged, this, &ChirpChatModGUI::on_invertRamps_stateChanged); } void ChirpChatModGUI::updateAbsoluteCenterFrequency() diff --git a/plugins/channeltx/modchirpchat/chirpchatmodgui.h b/plugins/channeltx/modchirpchat/chirpchatmodgui.h index 8e32a1022..8fecb7762 100644 --- a/plugins/channeltx/modchirpchat/chirpchatmodgui.h +++ b/plugins/channeltx/modchirpchat/chirpchatmodgui.h @@ -123,6 +123,7 @@ private slots: void on_udpEnabled_clicked(bool checked); void on_udpAddress_editingFinished(); void on_udpPort_editingFinished(); + void on_invertRamps_stateChanged(int state); void onWidgetRolled(QWidget* widget, bool rollDown); void onMenuDialogCalled(const QPoint& p); void tick(); diff --git a/plugins/channeltx/modchirpchat/chirpchatmodgui.ui b/plugins/channeltx/modchirpchat/chirpchatmodgui.ui index 333f0bba8..ebaee5883 100644 --- a/plugins/channeltx/modchirpchat/chirpchatmodgui.ui +++ b/plugins/channeltx/modchirpchat/chirpchatmodgui.ui @@ -215,6 +215,23 @@
+ + + + Qt::Vertical + + + + + + + Invert preamble, SFD and payload ramps + + + Inv + + + diff --git a/plugins/channeltx/modchirpchat/chirpchatmodsettings.cpp b/plugins/channeltx/modchirpchat/chirpchatmodsettings.cpp index 3b8a95a68..6e641172f 100644 --- a/plugins/channeltx/modchirpchat/chirpchatmodsettings.cpp +++ b/plugins/channeltx/modchirpchat/chirpchatmodsettings.cpp @@ -88,6 +88,7 @@ void ChirpChatModSettings::resetToDefaults() m_udpEnabled = false; m_udpAddress = "127.0.0.1"; m_udpPort = 9998; + m_invertRamps = false; m_rgbColor = QColor(255, 0, 255).rgb(); m_title = "ChirpChat Modulator"; m_streamIndex = 0; @@ -169,11 +170,7 @@ QByteArray ChirpChatModSettings::serialize() const s.writeU32(9, m_syncWord); s.writeU32(10, m_preambleChirps); s.writeS32(11, m_quietMillis); - s.writeBool(12, m_useReverseAPI); - s.writeString(13, m_reverseAPIAddress); - s.writeU32(14, m_reverseAPIPort); - s.writeU32(15, m_reverseAPIDeviceIndex); - s.writeU32(16, m_reverseAPIChannelIndex); + s.writeBool(12, m_invertRamps); s.writeString(20, m_beaconMessage); s.writeString(21, m_cqMessage); s.writeString(22, m_replyMessage); @@ -250,19 +247,7 @@ bool ChirpChatModSettings::deserialize(const QByteArray& data) d.readU32(10, &m_preambleChirps, 8); d.readS32(11, &m_quietMillis, 1000); d.readBool(11, &m_useReverseAPI, false); - d.readString(12, &m_reverseAPIAddress, "127.0.0.1"); - d.readU32(13, &utmp, 0); - - if ((utmp > 1023) && (utmp < 65535)) { - m_reverseAPIPort = utmp; - } else { - m_reverseAPIPort = 8888; - } - - d.readU32(14, &utmp, 0); - m_reverseAPIDeviceIndex = utmp > 99 ? 99 : utmp; - d.readU32(15, &utmp, 0); - m_reverseAPIChannelIndex = utmp > 99 ? 99 : utmp; + d.readBool(12, &m_invertRamps, false); d.readString(20, &m_beaconMessage, "VVV DE %1 %2"); d.readString(21, &m_cqMessage, "CQ DE %1 %2"); d.readString(22, &m_replyMessage, "%2 %1 %3"); @@ -327,3 +312,181 @@ bool ChirpChatModSettings::deserialize(const QByteArray& data) return false; } } + +void ChirpChatModSettings::applySettings(const QStringList& settingsKeys, const ChirpChatModSettings& settings) +{ + if (settingsKeys.contains("inputFrequencyOffset")) + m_inputFrequencyOffset = settings.m_inputFrequencyOffset; + if (settingsKeys.contains("bandwidthIndex")) + m_bandwidthIndex = settings.m_bandwidthIndex; + if (settingsKeys.contains("spreadFactor")) + m_spreadFactor = settings.m_spreadFactor; + if (settingsKeys.contains("deBits")) + m_deBits = settings.m_deBits; + if (settingsKeys.contains("codingScheme")) + m_codingScheme = settings.m_codingScheme; + if (settingsKeys.contains("preambleChirps")) + m_preambleChirps = settings.m_preambleChirps; + if (settingsKeys.contains("quietMillis")) + m_quietMillis = settings.m_quietMillis; + if (settingsKeys.contains("invertRamps")) + m_invertRamps = settings.m_invertRamps; + if (settingsKeys.contains("syncWord")) + m_syncWord = settings.m_syncWord; + if (settingsKeys.contains("channelMute")) + m_channelMute = settings.m_channelMute; + if (settingsKeys.contains("title")) + m_title = settings.m_title; + if (settingsKeys.contains("udpEnabled")) + m_udpEnabled = settings.m_udpEnabled; + if (settingsKeys.contains("udpAddress")) + m_udpAddress = settings.m_udpAddress; + if (settingsKeys.contains("udpPort")) + m_udpPort = settings.m_udpPort; + if (settingsKeys.contains("streamIndex")) + m_streamIndex = settings.m_streamIndex; + if (settingsKeys.contains("useReverseAPI")) + m_useReverseAPI = settings.m_useReverseAPI; + if (settingsKeys.contains("reverseAPIAddress")) + m_reverseAPIAddress = settings.m_reverseAPIAddress; + if (settingsKeys.contains("reverseAPIPort")) + m_reverseAPIPort = settings.m_reverseAPIPort; + if (settingsKeys.contains("reverseAPIDeviceIndex")) + m_reverseAPIDeviceIndex = settings.m_reverseAPIDeviceIndex; + if (settingsKeys.contains("reverseAPIChannelIndex")) + m_reverseAPIChannelIndex = settings.m_reverseAPIChannelIndex; + if (settingsKeys.contains("workspaceIndex")) + m_workspaceIndex = settings.m_workspaceIndex; + if (settingsKeys.contains("geometryBytes")) + m_geometryBytes = settings.m_geometryBytes; + if (settingsKeys.contains("hidden")) + m_hidden = settings.m_hidden; + if (settingsKeys.contains("channelMarker") && m_channelMarker && settings.m_channelMarker) + m_channelMarker->deserialize(settings.m_channelMarker->serialize()); + if (settingsKeys.contains("rollupState") && m_rollupState && settings.m_rollupState) + m_rollupState->deserialize(settings.m_rollupState->serialize()); + if (settingsKeys.contains("beaconMessage")) + m_beaconMessage = settings.m_beaconMessage; + if (settingsKeys.contains("cqMessage")) + m_cqMessage = settings.m_cqMessage; + if (settingsKeys.contains("replyMessage")) + m_replyMessage = settings.m_replyMessage; + if (settingsKeys.contains("reportMessage")) + m_reportMessage = settings.m_reportMessage; + if (settingsKeys.contains("replyReportMessage")) + m_replyReportMessage = settings.m_replyReportMessage; + if (settingsKeys.contains("rrrMessage")) + m_rrrMessage = settings.m_rrrMessage; + if (settingsKeys.contains("73Message")) + m_73Message = settings.m_73Message; + if (settingsKeys.contains("qsoTextMessage")) + m_qsoTextMessage = settings.m_qsoTextMessage; + if (settingsKeys.contains("textMessage")) + m_textMessage = settings.m_textMessage; + if (settingsKeys.contains("bytesMessage")) + m_bytesMessage = settings.m_bytesMessage; + if (settingsKeys.contains("messageType")) + m_messageType = settings.m_messageType; + if (settingsKeys.contains("nbParityBits")) + m_nbParityBits = settings.m_nbParityBits; + if (settingsKeys.contains("hasCRC")) + m_hasCRC = settings.m_hasCRC; + if (settingsKeys.contains("hasHeader")) + m_hasHeader = settings.m_hasHeader; + if (settingsKeys.contains("myCall")) + m_myCall = settings.m_myCall; + if (settingsKeys.contains("urCall")) + m_urCall = settings.m_urCall; + if (settingsKeys.contains("myLoc")) + m_myLoc = settings.m_myLoc; + if (settingsKeys.contains("myRpt")) + m_myRpt = settings.m_myRpt; + if (settingsKeys.contains("messageRepeat")) + m_messageRepeat = settings.m_messageRepeat; +} + +QString ChirpChatModSettings::getDebugString(const QStringList& settingsKeys, bool force) const +{ + QString debug; + if (settingsKeys.contains("inputFrequencyOffset") || force) + debug += QString("Input Frequency Offset: %1\n").arg(m_inputFrequencyOffset); + if (settingsKeys.contains("bandwidthIndex") || force) + debug += QString("Bandwidth Index: %1\n").arg(m_bandwidthIndex); + if (settingsKeys.contains("spreadFactor") || force) + debug += QString("Spread Factor: %1\n").arg(m_spreadFactor); + if (settingsKeys.contains("deBits") || force) + debug += QString("DE Bits: %1\n").arg(m_deBits); + if (settingsKeys.contains("codingScheme") || force) + debug += QString("Coding Scheme: %1\n").arg(m_codingScheme); + if (settingsKeys.contains("preambleChirps") || force) + debug += QString("Preamble Chirps: %1\n").arg(m_preambleChirps); + if (settingsKeys.contains("quietMillis") || force) + debug += QString("Quiet Millis: %1\n").arg(m_quietMillis); + if (settingsKeys.contains("invertRamps") || force) + debug += QString("Invert Ramps: %1\n").arg(m_invertRamps); + if (settingsKeys.contains("syncWord") || force) + debug += QString("Sync Word: %1\n").arg(m_syncWord); + if (settingsKeys.contains("channelMute") || force) + debug += QString("Channel Mute: %1\n").arg(m_channelMute); + if (settingsKeys.contains("title") || force) + debug += QString("Title: %1\n").arg(m_title); + if (settingsKeys.contains("udpEnabled") || force) + debug += QString("UDP Enabled: %1\n").arg(m_udpEnabled); + if (settingsKeys.contains("udpAddress") || force) + debug += QString("UDP Address: %1\n").arg(m_udpAddress); + if (settingsKeys.contains("udpPort") || force) + debug += QString("UDP Port: %1\n").arg(m_udpPort); + if (settingsKeys.contains("streamIndex") || force) + debug += QString("Stream Index: %1\n").arg(m_streamIndex); + if (settingsKeys.contains("useReverseAPI") || force) + debug += QString("Use Reverse API: %1\n").arg(m_useReverseAPI); + if (settingsKeys.contains("reverseAPIAddress") || force) + debug += QString("Reverse API Address: %1\n").arg(m_reverseAPIAddress); + if (settingsKeys.contains("reverseAPIPort") || force) + debug += QString("Reverse API Port: %1\n").arg(m_reverseAPIPort); + if (settingsKeys.contains("reverseAPIDeviceIndex") || force) + debug += QString("Reverse API Device Index: %1\n").arg(m_reverseAPIDeviceIndex); + if (settingsKeys.contains("reverseAPIChannelIndex") || force) + debug += QString("Reverse API Channel Index: %1\n").arg(m_reverseAPIChannelIndex); + if (settingsKeys.contains("workspaceIndex") || force) + debug += QString("Workspace Index: %1\n").arg(m_workspaceIndex); + if (settingsKeys.contains("hidden") || force) + debug += QString("Hidden: %1\n").arg(m_hidden); + if (settingsKeys.contains("beaconMessage") || force) + debug += QString("Beacon Message: %1\n").arg(m_beaconMessage); + if (settingsKeys.contains("cqMessage") || force) + debug += QString("CQ Message: %1\n").arg(m_cqMessage); + if (settingsKeys.contains("replyMessage") || force) + debug += QString("Reply Message: %1\n").arg(m_replyMessage); + if (settingsKeys.contains("reportMessage") || force) + debug += QString("Report Message: %1\n").arg(m_reportMessage); + if (settingsKeys.contains("replyReportMessage") || force) + debug += QString("Reply Report Message: %1\n").arg(m_replyReportMessage); + if (settingsKeys.contains("rrrMessage") || force) + debug += QString("RRR Message: %1\n").arg(m_rrrMessage); + if (settingsKeys.contains("73Message") || force) + debug += QString("73 Message: %1\n").arg(m_73Message); + if (settingsKeys.contains("qsoTextMessage") || force) + debug += QString("QSO Text Message: %1\n").arg(m_qsoTextMessage); + if (settingsKeys.contains("textMessage") || force) + debug += QString("Text Message: %1\n").arg(m_textMessage); + if (settingsKeys.contains("messageType") || force) + debug += QString("Message Type: %1\n").arg(m_messageType); + if (settingsKeys.contains("nbParityBits") || force) + debug += QString("Number of Parity Bits: %1\n").arg(m_nbParityBits); + if (settingsKeys.contains("hasCRC") || force) + debug += QString("Has CRC: %1\n").arg(m_hasCRC); + if (settingsKeys.contains("hasHeader") || force) + debug += QString("Has Header: %1\n").arg(m_hasHeader); + if (settingsKeys.contains("myCall") || force) + debug += QString("My Call: %1\n").arg(m_myCall); + if (settingsKeys.contains("urCall") || force) + debug += QString("UR Call: %1\n").arg(m_urCall); + if (settingsKeys.contains("myLoc") || force) + debug += QString("My Loc: %1\n").arg(m_myLoc); + if (settingsKeys.contains("myRpt") || force) + debug += QString("My Rpt: %1\n").arg(m_myRpt); + if (settingsKeys.contains("messageRepeat") || force) + debug += QString("Message Repeat: %1\n").arg(m_messageRepeat); + return debug; +} diff --git a/plugins/channeltx/modchirpchat/chirpchatmodsettings.h b/plugins/channeltx/modchirpchat/chirpchatmodsettings.h index a5c4de741..bd90f81fb 100644 --- a/plugins/channeltx/modchirpchat/chirpchatmodsettings.h +++ b/plugins/channeltx/modchirpchat/chirpchatmodsettings.h @@ -84,6 +84,7 @@ struct ChirpChatModSettings bool m_udpEnabled; QString m_udpAddress; uint16_t m_udpPort; + bool m_invertRamps; //!< Invert chirp ramps vs standard LoRa (up/down/up is standard) uint32_t m_rgbColor; QString m_title; int m_streamIndex; @@ -113,6 +114,8 @@ struct ChirpChatModSettings void setRollupState(Serializable *rollupState) { m_rollupState = rollupState; } QByteArray serialize() const; bool deserialize(const QByteArray& data); + void applySettings(const QStringList& settingsKeys, const ChirpChatModSettings& settings); + QString getDebugString(const QStringList& settingsKeys, bool force=false) const; }; diff --git a/plugins/channeltx/modchirpchat/chirpchatmodsource.cpp b/plugins/channeltx/modchirpchat/chirpchatmodsource.cpp index 900f1b4e1..ef304f2d3 100644 --- a/plugins/channeltx/modchirpchat/chirpchatmodsource.cpp +++ b/plugins/channeltx/modchirpchat/chirpchatmodsource.cpp @@ -197,7 +197,7 @@ void ChirpChatModSource::modulateSample() } else if (m_state == ChirpChatStatePreamble) { - m_modPhasor += m_phaseIncrements[m_chirp]; // up chirps + m_modPhasor += (m_settings.m_invertRamps ? -1 : 1) * m_phaseIncrements[m_chirp]; // preamble chirps m_modSample = Complex(std::polar(0.891235351562 * SDR_TX_SCALED, m_modPhasor)); m_fftCounter++; @@ -228,7 +228,7 @@ void ChirpChatModSource::modulateSample() } else if (m_state == ChirpChatStateSyncWord) { - m_modPhasor += m_phaseIncrements[m_chirp]; // up chirps + m_modPhasor += (m_settings.m_invertRamps ? -1 : 1) * m_phaseIncrements[m_chirp]; // sync chirps same orientation as preamble m_modSample = Complex(std::polar(0.891235351562 * SDR_TX_SCALED, m_modPhasor)); m_fftCounter++; @@ -251,7 +251,7 @@ void ChirpChatModSource::modulateSample() } else if (m_state == ChirpChatStateSFD) { - m_modPhasor -= m_phaseIncrements[m_chirp]; // down chirps + m_modPhasor -= (m_settings.m_invertRamps ? -1 : 1) * m_phaseIncrements[m_chirp]; // SFD chirps m_modSample = Complex(std::polar(0.891235351562 * SDR_TX_SCALED, m_modPhasor)); m_fftCounter++; m_sampleCounter++; @@ -280,7 +280,7 @@ void ChirpChatModSource::modulateSample() } else if (m_state == ChirpChatStatePayload) { - m_modPhasor += m_phaseIncrements[m_chirp]; // up chirps + m_modPhasor += (m_settings.m_invertRamps ? -1 : 1) * m_phaseIncrements[m_chirp]; // payload chirps m_modSample = Complex(std::polar(0.891235351562 * SDR_TX_SCALED, m_modPhasor)); m_fftCounter++; diff --git a/plugins/channeltx/modchirpchat/readme.md b/plugins/channeltx/modchirpchat/readme.md index f66ec95e0..39365a3ec 100644 --- a/plugins/channeltx/modchirpchat/readme.md +++ b/plugins/channeltx/modchirpchat/readme.md @@ -72,6 +72,12 @@ Thus available bandwidths are: The ChirpChat signal is oversampled by four therefore it needs a baseband of at least four times the bandwidth. This drives the maximum value on the slider automatically. +

16: Invert chirp ramps

+ +The LoRa standard is up-chirps for the preamble, down-chirps for the SFD and up-chirps for the payload. + +When you check this option it inverts the direction of the chirps thus becoming down-chirps for the preamble, up-chirps for the SFD and down-chirps for the payload. +

5: Spread Factor

This is the Spread Factor parameter of the ChirpChat signal. This is the log2 of the possible frequency shifts used over the bandwidth (3). The number of symbols is 2SF-DE where SF is the spread factor and DE the Distance Enhancement factor (6). @@ -107,7 +113,7 @@ To populate messages you can specify your callsign (10.5), the other party calls - **LoRa**: LoRa compatible - **ASCII**: 7 bit plain ASCII without FEC and CRC. Requires exactly 7 bit effective samples thus SF-DE = 7 where SF is the spreading factor (5) and DE the distance enhancement factor (6) - **TTY**: 5 bit Baudot (Teletype) without FEC and CRC. Requires exactly 5 bit effective samples thus SF-DE = 5 where SF is the spreading factor (5) and DE the distance enhancement factor (6) - - **FT**: FT8/FT4 coding is applied using data in (10.5) to (10.8) to encode the 174 bit message payload with CRC and FEC as per FT8/FT4 protocol using a type 1 (standard) type of message. Note that the report (10.8) must comply with the FT rule (coded "-35" to "+99" with a leading 0 for the number) and would usually represent the integer part of the S/N ratio in the ChirpChat demodulator receiver. Calls should not be prefixed nor suffixed and the first 4 characters of the locator must represent a valid 4 character grid square. Plain text messages (13 characters) are also supported with the 0.0 type of message using the text entered in the message box (11). These 174 bits are packed into (SF - DE) bits symbols padded with zero bits if necessary. For the details of the FT protocol see: https://wsjt.sourceforge.io/FT4_FT8_QEX.pdf + - **FT**: FT8/FT4 coding is applied using data in (10.5) to (10.8) to encode the 174 bit message payload with CRC and FEC as per FT8/FT4 protocol using a type 1 (standard) type of message. Note that the report (10.8) must comply with the FT rule (coded "-35" to "+99" with a leading 0 for the number) and would usually represent the integer part of the S/N ratio in the ChirpChat demodulator receiver. Calls should not be prefixed nor suffixed and the first 4 characters of the locator must represent a valid 4 character grid square. Plain text messages (13 characters) are also supported with the 0.0 type of message using the text entered in the message box (11). These 174 bits are packed into (SF - DE) bits symbols padded with zero bits if necessary. For the details of the FT protocol see: https://wsjt.sourceforge.io/FT4_FT8_QEX.pdf For example for SF=9 and DE=3 we have 6 bits per symbols so the 174 bits are packed in exactly 29 symbols this should appear in the message length ML (13)

10.2: Number of FEC parity bits (LoRa)

@@ -229,9 +235,9 @@ This window lets you edit the message selected in (10.9). You can use `%n` place Use this line editor to specify the hex string used as the bytes message. -

13: Symbol time

+

13: Symbol time and message length

-This is the duration of a symbol or chirp in milliseconds +This is the duration of a symbol or chirp in milliseconds followed by the message length in the number of symbols

14: Payload time

diff --git a/sdrbase/resources/webapi/doc/html2/index.html b/sdrbase/resources/webapi/doc/html2/index.html index 56d6b80e5..44f7bff42 100644 --- a/sdrbase/resources/webapi/doc/html2/index.html +++ b/sdrbase/resources/webapi/doc/html2/index.html @@ -4457,7 +4457,11 @@ margin-bottom: 20px; }, "udpPort" : { "type" : "integer", - "description" : "UDP destination properties" + "description" : "UDP destination port" + }, + "invertRamps" : { + "type" : "integer", + "description" : "Invert chirp ramps\n * 0 - Normal chirp ramps (upchirps for preamble and payload)\n * 1 - Inverted chirp ramps (downchirps for preamble and payload)\n" }, "rgbColor" : { "type" : "integer" @@ -4657,6 +4661,10 @@ margin-bottom: 20px; "type" : "integer", "description" : "UDP port to listen for messages to transmit on" }, + "invertRamps" : { + "type" : "integer", + "description" : "Invert chirp ramps\n * 0 - Normal chirp ramps (upchirps for preamble and payload)\n * 1 - Inverted chirp ramps (downchirps for preamble and payload)\n" + }, "rgbColor" : { "type" : "integer" }, @@ -59832,7 +59840,7 @@ except ApiException as e:
- Generated 2026-01-10T11:16:10.140+01:00 + Generated 2026-01-24T10:44:52.632+01:00
diff --git a/sdrbase/resources/webapi/doc/swagger/include/ChirpChatDemod.yaml b/sdrbase/resources/webapi/doc/swagger/include/ChirpChatDemod.yaml index 8e6d36487..101ae8d26 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/ChirpChatDemod.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/ChirpChatDemod.yaml @@ -100,8 +100,14 @@ ChirpChatDemodSettings: description: UDP destination udpAddress type: string udpPort: - description: UDP destination properties + description: UDP destination port type: integer + invertRamps: + type: integer + description: > + Invert chirp ramps + * 0 - Normal chirp ramps (upchirps for preamble and payload) + * 1 - Inverted chirp ramps (downchirps for preamble and payload) rgbColor: type: integer title: diff --git a/sdrbase/resources/webapi/doc/swagger/include/ChirpChatMod.yaml b/sdrbase/resources/webapi/doc/swagger/include/ChirpChatMod.yaml index 3e734f63e..ced9b5310 100644 --- a/sdrbase/resources/webapi/doc/swagger/include/ChirpChatMod.yaml +++ b/sdrbase/resources/webapi/doc/swagger/include/ChirpChatMod.yaml @@ -139,6 +139,12 @@ ChirpChatModSettings: udpPort: description: UDP port to listen for messages to transmit on type: integer + invertRamps: + type: integer + description: > + Invert chirp ramps + * 0 - Normal chirp ramps (upchirps for preamble and payload) + * 1 - Inverted chirp ramps (downchirps for preamble and payload) rgbColor: type: integer title: diff --git a/swagger/sdrangel/api/swagger/include/ChirpChatDemod.yaml b/swagger/sdrangel/api/swagger/include/ChirpChatDemod.yaml index 5cf7a58f9..b9eb085a4 100644 --- a/swagger/sdrangel/api/swagger/include/ChirpChatDemod.yaml +++ b/swagger/sdrangel/api/swagger/include/ChirpChatDemod.yaml @@ -100,8 +100,14 @@ ChirpChatDemodSettings: description: UDP destination udpAddress type: string udpPort: - description: UDP destination properties + description: UDP destination port type: integer + invertRamps: + type: integer + description: > + Invert chirp ramps + * 0 - Normal chirp ramps (upchirps for preamble and payload) + * 1 - Inverted chirp ramps (downchirps for preamble and payload) rgbColor: type: integer title: diff --git a/swagger/sdrangel/api/swagger/include/ChirpChatMod.yaml b/swagger/sdrangel/api/swagger/include/ChirpChatMod.yaml index 1568e40e1..58d14202a 100644 --- a/swagger/sdrangel/api/swagger/include/ChirpChatMod.yaml +++ b/swagger/sdrangel/api/swagger/include/ChirpChatMod.yaml @@ -139,6 +139,12 @@ ChirpChatModSettings: udpPort: description: UDP port to listen for messages to transmit on type: integer + invertRamps: + type: integer + description: > + Invert chirp ramps + * 0 - Normal chirp ramps (upchirps for preamble and payload) + * 1 - Inverted chirp ramps (downchirps for preamble and payload) rgbColor: type: integer title: diff --git a/swagger/sdrangel/code/html2/index.html b/swagger/sdrangel/code/html2/index.html index 56d6b80e5..44f7bff42 100644 --- a/swagger/sdrangel/code/html2/index.html +++ b/swagger/sdrangel/code/html2/index.html @@ -4457,7 +4457,11 @@ margin-bottom: 20px; }, "udpPort" : { "type" : "integer", - "description" : "UDP destination properties" + "description" : "UDP destination port" + }, + "invertRamps" : { + "type" : "integer", + "description" : "Invert chirp ramps\n * 0 - Normal chirp ramps (upchirps for preamble and payload)\n * 1 - Inverted chirp ramps (downchirps for preamble and payload)\n" }, "rgbColor" : { "type" : "integer" @@ -4657,6 +4661,10 @@ margin-bottom: 20px; "type" : "integer", "description" : "UDP port to listen for messages to transmit on" }, + "invertRamps" : { + "type" : "integer", + "description" : "Invert chirp ramps\n * 0 - Normal chirp ramps (upchirps for preamble and payload)\n * 1 - Inverted chirp ramps (downchirps for preamble and payload)\n" + }, "rgbColor" : { "type" : "integer" }, @@ -59832,7 +59840,7 @@ except ApiException as e:
- Generated 2026-01-10T11:16:10.140+01:00 + Generated 2026-01-24T10:44:52.632+01:00
diff --git a/swagger/sdrangel/code/qt5/client/SWGChirpChatDemodSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGChirpChatDemodSettings.cpp index 7a1934b10..25102d235 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChirpChatDemodSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChirpChatDemodSettings.cpp @@ -66,6 +66,8 @@ SWGChirpChatDemodSettings::SWGChirpChatDemodSettings() { m_udp_address_isSet = false; udp_port = 0; m_udp_port_isSet = false; + invert_ramps = 0; + m_invert_ramps_isSet = false; rgb_color = 0; m_rgb_color_isSet = false; title = nullptr; @@ -134,6 +136,8 @@ SWGChirpChatDemodSettings::init() { m_udp_address_isSet = false; udp_port = 0; m_udp_port_isSet = false; + invert_ramps = 0; + m_invert_ramps_isSet = false; rgb_color = 0; m_rgb_color_isSet = false; title = new QString(""); @@ -182,6 +186,7 @@ SWGChirpChatDemodSettings::cleanup() { } + if(title != nullptr) { delete title; } @@ -253,6 +258,8 @@ SWGChirpChatDemodSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&udp_port, pJson["udpPort"], "qint32", ""); + ::SWGSDRangel::setValue(&invert_ramps, pJson["invertRamps"], "qint32", ""); + ::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", ""); ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); @@ -348,6 +355,9 @@ SWGChirpChatDemodSettings::asJsonObject() { if(m_udp_port_isSet){ obj->insert("udpPort", QJsonValue(udp_port)); } + if(m_invert_ramps_isSet){ + obj->insert("invertRamps", QJsonValue(invert_ramps)); + } if(m_rgb_color_isSet){ obj->insert("rgbColor", QJsonValue(rgb_color)); } @@ -575,6 +585,16 @@ SWGChirpChatDemodSettings::setUdpPort(qint32 udp_port) { this->m_udp_port_isSet = true; } +qint32 +SWGChirpChatDemodSettings::getInvertRamps() { + return invert_ramps; +} +void +SWGChirpChatDemodSettings::setInvertRamps(qint32 invert_ramps) { + this->invert_ramps = invert_ramps; + this->m_invert_ramps_isSet = true; +} + qint32 SWGChirpChatDemodSettings::getRgbColor() { return rgb_color; @@ -747,6 +767,9 @@ SWGChirpChatDemodSettings::isSet(){ if(m_udp_port_isSet){ isObjectUpdated = true; break; } + if(m_invert_ramps_isSet){ + isObjectUpdated = true; break; + } if(m_rgb_color_isSet){ isObjectUpdated = true; break; } diff --git a/swagger/sdrangel/code/qt5/client/SWGChirpChatDemodSettings.h b/swagger/sdrangel/code/qt5/client/SWGChirpChatDemodSettings.h index 0a5dab394..1409247be 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChirpChatDemodSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChirpChatDemodSettings.h @@ -102,6 +102,9 @@ public: qint32 getUdpPort(); void setUdpPort(qint32 udp_port); + qint32 getInvertRamps(); + void setInvertRamps(qint32 invert_ramps); + qint32 getRgbColor(); void setRgbColor(qint32 rgb_color); @@ -196,6 +199,9 @@ private: qint32 udp_port; bool m_udp_port_isSet; + qint32 invert_ramps; + bool m_invert_ramps_isSet; + qint32 rgb_color; bool m_rgb_color_isSet; diff --git a/swagger/sdrangel/code/qt5/client/SWGChirpChatModSettings.cpp b/swagger/sdrangel/code/qt5/client/SWGChirpChatModSettings.cpp index ba524de3a..9f95558fa 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChirpChatModSettings.cpp +++ b/swagger/sdrangel/code/qt5/client/SWGChirpChatModSettings.cpp @@ -90,6 +90,8 @@ SWGChirpChatModSettings::SWGChirpChatModSettings() { m_udp_address_isSet = false; udp_port = 0; m_udp_port_isSet = false; + invert_ramps = 0; + m_invert_ramps_isSet = false; rgb_color = 0; m_rgb_color_isSet = false; title = nullptr; @@ -180,6 +182,8 @@ SWGChirpChatModSettings::init() { m_udp_address_isSet = false; udp_port = 0; m_udp_port_isSet = false; + invert_ramps = 0; + m_invert_ramps_isSet = false; rgb_color = 0; m_rgb_color_isSet = false; title = new QString(""); @@ -270,6 +274,7 @@ SWGChirpChatModSettings::cleanup() { } + if(title != nullptr) { delete title; } @@ -362,6 +367,8 @@ SWGChirpChatModSettings::fromJsonObject(QJsonObject &pJson) { ::SWGSDRangel::setValue(&udp_port, pJson["udpPort"], "qint32", ""); + ::SWGSDRangel::setValue(&invert_ramps, pJson["invertRamps"], "qint32", ""); + ::SWGSDRangel::setValue(&rgb_color, pJson["rgbColor"], "qint32", ""); ::SWGSDRangel::setValue(&title, pJson["title"], "QString", "QString"); @@ -491,6 +498,9 @@ SWGChirpChatModSettings::asJsonObject() { if(m_udp_port_isSet){ obj->insert("udpPort", QJsonValue(udp_port)); } + if(m_invert_ramps_isSet){ + obj->insert("invertRamps", QJsonValue(invert_ramps)); + } if(m_rgb_color_isSet){ obj->insert("rgbColor", QJsonValue(rgb_color)); } @@ -835,6 +845,16 @@ SWGChirpChatModSettings::setUdpPort(qint32 udp_port) { this->m_udp_port_isSet = true; } +qint32 +SWGChirpChatModSettings::getInvertRamps() { + return invert_ramps; +} +void +SWGChirpChatModSettings::setInvertRamps(qint32 invert_ramps) { + this->invert_ramps = invert_ramps; + this->m_invert_ramps_isSet = true; +} + qint32 SWGChirpChatModSettings::getRgbColor() { return rgb_color; @@ -1033,6 +1053,9 @@ SWGChirpChatModSettings::isSet(){ if(m_udp_port_isSet){ isObjectUpdated = true; break; } + if(m_invert_ramps_isSet){ + isObjectUpdated = true; break; + } if(m_rgb_color_isSet){ isObjectUpdated = true; break; } diff --git a/swagger/sdrangel/code/qt5/client/SWGChirpChatModSettings.h b/swagger/sdrangel/code/qt5/client/SWGChirpChatModSettings.h index 15723a767..b63ee354b 100644 --- a/swagger/sdrangel/code/qt5/client/SWGChirpChatModSettings.h +++ b/swagger/sdrangel/code/qt5/client/SWGChirpChatModSettings.h @@ -138,6 +138,9 @@ public: qint32 getUdpPort(); void setUdpPort(qint32 udp_port); + qint32 getInvertRamps(); + void setInvertRamps(qint32 invert_ramps); + qint32 getRgbColor(); void setRgbColor(qint32 rgb_color); @@ -265,6 +268,9 @@ private: qint32 udp_port; bool m_udp_port_isSet; + qint32 invert_ramps; + bool m_invert_ramps_isSet; + qint32 rgb_color; bool m_rgb_color_isSet;