Compare commits

..

56 Commits

Author SHA1 Message Date
Jordan Sherer eca184bac6 Version bump 2018-07-27 02:27:50 -04:00
Jordan Sherer 6ad2417804 Remove station from heard list 2018-07-27 02:22:48 -04:00
Jordan Sherer 21e87d8b6f QSY on right click of heard list 2018-07-27 01:43:25 -04:00
Jordan Sherer 52a5650a74 Restore original functionality regarding stations heard 2018-07-27 01:36:55 -04:00
Jordan Sherer 2158722ebc Fixed bug in checksuming of buffered messages 2018-07-27 01:24:38 -04:00
Jordan Sherer bf11d66f60 Dont reset the beacon at a 1 minute interval 2018-07-26 20:51:02 -04:00
Jordan Sherer 371aa1e20c Fixed callsign parsing expression 2018-07-26 20:46:08 -04:00
Jordan Sherer 8348f61a94 Bump date 2018-07-26 17:31:21 -04:00
Jordan Sherer bf28918096 Write current messages to ALL.TXT 2018-07-26 16:30:19 -04:00
Jordan Sherer 571aa6446d Added message alerts 2018-07-26 15:57:19 -04:00
Jordan Sherer 4290dd6e2f Extended charset via escapes. DE added to retransmits 2018-07-26 14:31:28 -04:00
Jordan Sherer de66664635 Added extended alphabet for special characters 2018-07-26 12:47:03 -04:00
Jordan Sherer 9e9c996813 Refactor message buffering for relay 2018-07-25 22:49:19 -04:00
Jordan Sherer f67ea3803d Changed to a better callsign validator expression 2018-07-25 20:24:22 -04:00
Jordan Sherer fa00e0dfd6 Added callsign label to main window 2018-07-25 20:13:23 -04:00
Jordan Sherer ceaa76c497 Experimental single-hop relay working 2018-07-25 17:15:59 -04:00
Jordan Sherer 9c9a5c2d8b Added power reply. Added ack reply 2018-07-25 16:51:47 -04:00
Jordan Sherer d611d83bb9 Fixed J1Y callsign bug. Added power reporting command 2018-07-25 14:46:21 -04:00
Jordan Sherer 253b60217f Added station power to configuration 2018-07-25 11:30:44 -04:00
Jordan Sherer 39a536bb91 Experimental foundation of all or nothing checksummed messages 2018-07-25 09:06:47 -04:00
Jordan Sherer 115a9d65f7 Send data frames after directed. Fix bug with enter key double sending 2018-07-24 23:10:47 -04:00
Jordan Sherer 796920cb6b Better EOT. Fixed bug in enter key press 2018-07-24 22:32:24 -04:00
Jordan Sherer c1c7d85195 UI Tweaks. Remove frequency restriction (causing problems). And keep track of the last message sent for later. 2018-07-24 21:04:04 -04:00
Jordan Sherer c5a6f76b1e Bump version to v0.3.0 2018-07-24 20:42:55 -04:00
Jordan Sherer 5b198351be Send message on enter key press 2018-07-24 17:45:23 -04:00
Jordan Sherer 246d53201c Updated compound call parsing and aliasing 2018-07-24 17:31:06 -04:00
Jordan Sherer 999a239e67 Remove reference to FT8Free 2018-07-24 16:47:14 -04:00
Jordan Sherer f091cb28ef Make sure log window is large enough 2018-07-24 16:47:05 -04:00
Jordan Sherer f415b0c94f Added autoreply button in conjuntion with beacon button 2018-07-24 16:46:04 -04:00
Jordan Sherer 0bf2afa5f8 Proper handling of directed messages for compound calls using an alias scheme 2018-07-24 15:19:02 -04:00
Jordan Sherer 706a9b1ebd Allow stations heard query 2018-07-24 11:11:04 -04:00
Jordan Sherer 4cec8b80a3 Check offsets above and below 5Hz 2018-07-24 09:42:07 -04:00
Jordan Sherer 9e68b8c402 Clear compound call cache on activity clear 2018-07-24 09:23:44 -04:00
Jordan Sherer 478ba82df7 Removed split rig check ignore for split operation 2018-07-24 09:08:53 -04:00
Jordan Sherer 7aef92dd68 Updated call activity once receiving compound callsign 2018-07-24 03:07:36 -04:00
Jordan Sherer 01249bd115 Support compound callsigns 2018-07-24 02:53:01 -04:00
Jordan Sherer 9bee00c5dd Remove directed buttons and put them in the menu 2018-07-24 02:52:12 -04:00
Jordan Sherer b9b274f2d6 Disallow callsigns with both prefixes and suffixes 2018-07-24 02:51:44 -04:00
Jordan Sherer 4a17062487 Added packing of compound callsigns into a dedicated message 2018-07-24 02:47:14 -04:00
Jordan Sherer 1c73ce2c90 Removed extra non-ft8 binaries 2018-07-23 23:56:51 -04:00
Jordan Sherer 07a29c7f1d Refactoring directed frame packing 2018-07-23 17:20:03 -04:00
Jordan Sherer f7a941406c Typing in a callsign not in your heard list recognizes it as a callsign selected 2018-07-23 17:19:26 -04:00
Jordan Sherer adecb88c29 Added callsign prefix packing 2018-07-23 15:28:36 -04:00
Jordan Sherer 857e19ed94 Updated callsign handling
Reducing compound callsigns to their basecall for most commands except CQ and DE (beacon)
2018-07-23 11:45:41 -04:00
Jordan Sherer f47224979e Refactor store freq. Rename to bacon. 2018-07-23 09:09:55 -04:00
Jordan Sherer c0833aa753 Added checksum generation and ACK 2018-07-23 08:51:29 -04:00
Jordan Sherer 20d931a9ca Moved macro button...again. 2018-07-23 08:49:58 -04:00
Jordan Sherer 1b2f8a1c6c Restore audio frequency after responding to allcall or transmitting beacon 2018-07-21 17:50:33 -04:00
Jordan Sherer 95e75741ed Fix bug in scrolling of the main window 2018-07-21 17:32:35 -04:00
Jordan Sherer dc75c08081 Configuration and MainWindow UI tweaks 2018-07-21 17:18:35 -04:00
Jordan Sherer 8c204e317b Fixed beacon postponement 2018-07-21 17:06:17 -04:00
Jordan Sherer a672668c3a Only respond to allcalls once per beacon interval 2018-07-21 16:57:42 -04:00
Jordan Sherer 8638b53e45 Added band activity age in the table 2018-07-21 16:55:04 -04:00
Jordan Sherer 394a6d045f Beacon interval default to 15 min 2018-07-21 16:10:13 -04:00
Jordan Sherer 29bbedcc8f Make sure standard messages are displayed with a space between them. 2018-07-21 15:57:01 -04:00
Jordan Sherer d66b4ffb37 Disable QTC QTH buttons if those messages are empty 2018-07-21 12:37:11 -04:00
16 changed files with 1920 additions and 825 deletions
+1
View File
@@ -10,6 +10,7 @@ auto CallsignValidator::validate (QString& input, int& pos) const -> State
{
auto match = re_.match (input, 0, QRegularExpression::PartialPreferCompleteMatch);
input = input.toUpper ();
if (input.count(QLatin1Char('/')) > 1) return Invalid;
if (match.hasMatch ()) return Acceptable;
if (!input.size () || match.hasPartialMatch ()) return Intermediate;
pos = input.size ();
+52 -1
View File
@@ -181,6 +181,8 @@
#include "MaidenheadLocatorValidator.hpp"
#include "CallsignValidator.hpp"
#include "varicode.h"
#include "ui_Configuration.h"
#include "moc_Configuration.cpp"
@@ -532,6 +534,7 @@ private:
QString my_callsign_;
QString my_grid_;
QString my_station_;
int my_dBm_;
QString my_qth_;
int callsign_aging_;
int activity_aging_;
@@ -554,6 +557,7 @@ private:
bool id_after_73_;
bool tx_QSY_allowed_;
bool spot_to_psk_reporter_;
bool autoreply_off_at_startup_;
bool monitor_off_at_startup_;
bool monitor_last_used_;
bool log_as_RTTY_;
@@ -664,6 +668,7 @@ void Configuration::set_spot_to_psk_reporter (bool spot)
}
}
bool Configuration::autoreply_off_at_startup () const {return m_->autoreply_off_at_startup_;}
bool Configuration::monitor_off_at_startup () const {return m_->monitor_off_at_startup_;}
bool Configuration::monitor_last_used () const {return m_->rig_is_dummy_ || m_->monitor_last_used_;}
bool Configuration::log_as_RTTY () const {return m_->log_as_RTTY_;}
@@ -828,6 +833,10 @@ QString Configuration::my_station() const
return m_->my_station_;
}
int Configuration::my_dBm() const {
return m_->my_dBm_;
}
QString Configuration::my_qth() const
{
return m_->my_qth_;
@@ -1153,6 +1162,41 @@ void Configuration::impl::initialize_models ()
{
pal.setColor (QPalette::Base, Qt::white);
}
QMap<int, int> dbm2mw = {
{0 , 1},
{3 , 2},
{7 , 5},
{10 , 10},
{13 , 20},
{17 , 50},
{20 , 100},
{23 , 200},
{27 , 500},
{30 , 1000}, // 1W
{33 , 2000}, // 2W
{37 , 5000}, // 5W
{40 , 10000}, // 10W
{43 , 20000}, // 20W
{47 , 50000}, // 50W
{50 , 100000}, // 100W
{53 , 200000}, // 200W
{57 , 500000}, // 500W
{60 , 1000000}, // 1000W
};
ui_->station_power_combo_box->clear();
ui_->station_power_combo_box->addItem(QString(""), -1);
foreach(auto dbm, dbm2mw.keys()){
ui_->station_power_combo_box->addItem(QString("%1 (%2 dBm)").arg(Varicode::formatPWR(dbm)).arg(dbm), dbm);
if(dbm == my_dBm_){
ui_->station_power_combo_box->setCurrentIndex(ui_->station_power_combo_box->count()-1);
}
}
ui_->callsign_line_edit->setPalette (pal);
ui_->grid_line_edit->setPalette (pal);
ui_->callsign_line_edit->setText (my_callsign_);
@@ -1179,6 +1223,7 @@ void Configuration::impl::initialize_models ()
ui_->CW_id_after_73_check_box->setChecked (id_after_73_);
ui_->tx_QSY_check_box->setChecked (tx_QSY_allowed_);
ui_->psk_reporter_check_box->setChecked (spot_to_psk_reporter_);
ui_->autoreply_off_check_box->setChecked (autoreply_off_at_startup_);
ui_->monitor_off_check_box->setChecked (monitor_off_at_startup_);
ui_->monitor_last_used_check_box->setChecked (monitor_last_used_);
ui_->log_as_RTTY_check_box->setChecked (log_as_RTTY_);
@@ -1280,6 +1325,7 @@ void Configuration::impl::read_settings ()
my_callsign_ = settings_->value ("MyCall", QString {}).toString ();
my_grid_ = settings_->value ("MyGrid", QString {}).toString ();
my_station_ = settings_->value("MyStation", QString {}).toString();
my_dBm_ = settings_->value("MyPower", -1).toInt();
callsign_aging_ = settings_->value ("CallsignAging", 0).toInt ();
activity_aging_ = settings_->value ("ActivityAging", 2).toInt ();
my_qth_ = settings_->value("MyQTH", QString {}).toString();
@@ -1376,6 +1422,7 @@ void Configuration::impl::read_settings ()
type_2_msg_gen_ = settings_->value ("Type2MsgGen", QVariant::fromValue (Configuration::type_2_msg_3_full)).value<Configuration::Type2MsgGen> ();
autoreply_off_at_startup_ = settings_->value ("AutoreplyOFF", false).toBool ();
monitor_off_at_startup_ = settings_->value ("MonitorOFF", false).toBool ();
monitor_last_used_ = settings_->value ("MonitorLastUsed", false).toBool ();
spot_to_psk_reporter_ = settings_->value ("PSKReporter", false).toBool ();
@@ -1435,7 +1482,7 @@ void Configuration::impl::read_settings ()
miles_ = settings_->value ("Miles", false).toBool ();
quick_call_ = settings_->value ("QuickCall", false).toBool ();
disable_TX_on_73_ = settings_->value ("73TxDisable", false).toBool ();
beacon_ = settings_->value ("TxBeacon", 5).toInt ();
beacon_ = settings_->value ("TxBeacon", 15).toInt ();
watchdog_ = settings_->value ("TxWatchdog", 0).toInt ();
TX_messages_ = settings_->value ("Tx2QSO", true).toBool ();
enable_VHF_features_ = settings_->value("VHFUHF",false).toBool ();
@@ -1470,6 +1517,7 @@ void Configuration::impl::write_settings ()
settings_->setValue ("MyCall", my_callsign_);
settings_->setValue ("MyGrid", my_grid_);
settings_->setValue ("MyStation", my_station_);
settings_->setValue ("MyPower", my_dBm_);
settings_->setValue ("MyQTH", my_qth_);
settings_->setValue ("CallsignAging", callsign_aging_);
settings_->setValue ("ActivityAging", activity_aging_);
@@ -1511,6 +1559,7 @@ void Configuration::impl::write_settings ()
settings_->setValue ("AudioInputChannel", AudioDevice::toString (audio_input_channel_));
settings_->setValue ("AudioOutputChannel", AudioDevice::toString (audio_output_channel_));
settings_->setValue ("Type2MsgGen", QVariant::fromValue (type_2_msg_gen_));
settings_->setValue ("AutoreplyOFF", autoreply_off_at_startup_);
settings_->setValue ("MonitorOFF", monitor_off_at_startup_);
settings_->setValue ("MonitorLastUsed", monitor_last_used_);
settings_->setValue ("PSKReporter", spot_to_psk_reporter_);
@@ -1925,6 +1974,7 @@ void Configuration::impl::accept ()
my_callsign_ = ui_->callsign_line_edit->text ();
my_grid_ = ui_->grid_line_edit->text ();
my_station_ = ui_->station_message_line_edit->text().toUpper();
my_dBm_ = ui_->station_power_combo_box->currentData().toInt();
my_qth_ = ui_->qth_message_line_edit->text().toUpper();
callsign_aging_ = ui_->callsign_aging_spin_box->value();
activity_aging_ = ui_->activity_aging_spin_box->value();
@@ -1937,6 +1987,7 @@ void Configuration::impl::accept ()
RxBandwidth_ = ui_->sbBandwidth->value ();
id_after_73_ = ui_->CW_id_after_73_check_box->isChecked ();
tx_QSY_allowed_ = ui_->tx_QSY_check_box->isChecked ();
autoreply_off_at_startup_ = ui_->autoreply_off_check_box->isChecked ();
monitor_off_at_startup_ = ui_->monitor_off_check_box->isChecked ();
monitor_last_used_ = ui_->monitor_last_used_check_box->isChecked ();
type_2_msg_gen_ = static_cast<Type2MsgGen> (ui_->type_2_msg_gen_combo_box->currentIndex ());
+2
View File
@@ -97,6 +97,7 @@ public:
QString my_callsign () const;
QString my_grid () const;
QString my_station () const;
int my_dBm() const;
int activity_aging() const;
int callsign_aging() const;
QString my_qth () const;
@@ -112,6 +113,7 @@ public:
bool tx_QSY_allowed () const;
bool spot_to_psk_reporter () const;
void set_spot_to_psk_reporter (bool);
bool autoreply_off_at_startup () const;
bool monitor_off_at_startup () const;
bool monitor_last_used () const;
bool log_as_RTTY () const;
+29 -9
View File
@@ -202,6 +202,16 @@
<item row="1" column="1">
<widget class="QLineEdit" name="station_message_line_edit"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Station Power:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="station_power_combo_box"/>
</item>
</layout>
</widget>
</item>
@@ -314,6 +324,13 @@
<string>Behavior</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_8">
<item>
<widget class="QCheckBox" name="autoreply_off_check_box">
<property name="text">
<string>Autoreply off at startup</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="monitor_off_check_box">
<property name="toolTip">
@@ -329,6 +346,9 @@
</item>
<item>
<widget class="QCheckBox" name="monitor_last_used_check_box">
<property name="visible">
<bool>false</bool>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Check this if you wish to automatically return to the last monitored frequency when monitor is enabled, leave it unchecked if you wish to have the current rig frequency maintained.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
@@ -465,7 +485,7 @@
<string/>
</property>
<property name="minimum">
<number>1</number>
<number>5</number>
</property>
<property name="maximum">
<number>60</number>
@@ -474,7 +494,7 @@
<number>1</number>
</property>
<property name="value">
<number>5</number>
<number>15</number>
</property>
</widget>
</item>
@@ -2580,13 +2600,13 @@ Right click for insert and delete options.</string>
<item>
<widget class="QPushButton" name="font_push_button">
<property name="enabled">
<bool>false</bool>
<bool>true</bool>
</property>
<property name="toolTip">
<string>Set the font characteristics for the application.</string>
</property>
<property name="text">
<string>Font...</string>
<string>UI Font...</string>
</property>
</widget>
</item>
@@ -2599,7 +2619,7 @@ Right click for insert and delete options.</string>
<string>Set the font characteristics for the Band Activity and Rx Frequency areas.</string>
</property>
<property name="text">
<string>Decoded Text Font...</string>
<string>Message Text Font...</string>
</property>
</widget>
</item>
@@ -3045,12 +3065,12 @@ soundcard changes</string>
</connection>
</connections>
<buttongroups>
<buttongroup name="TX_mode_button_group"/>
<buttongroup name="split_mode_button_group"/>
<buttongroup name="CAT_stop_bits_button_group"/>
<buttongroup name="CAT_handshake_button_group"/>
<buttongroup name="PTT_method_button_group"/>
<buttongroup name="CAT_data_bits_button_group"/>
<buttongroup name="CAT_handshake_button_group"/>
<buttongroup name="TX_mode_button_group"/>
<buttongroup name="CAT_stop_bits_button_group"/>
<buttongroup name="PTT_method_button_group"/>
<buttongroup name="TX_audio_source_button_group"/>
</buttongroups>
</ui>
+3 -3
View File
@@ -804,9 +804,9 @@ void HamlibTransceiver::do_tx_frequency (Frequency tx, MODE mode, bool no_ignore
if (UNK != mode)
{
auto new_mode = map_mode (mode);
// TRACE_CAT ("HamlibTransceiver", "rig_set_split_freq_mode freq = " << tx
// << " mode = " << rig_strrmode (new_mode));
// error_check (rig_set_split_freq_mode (rig_.data (), RIG_VFO_CURR, tx, new_mode, RIG_PASSBAND_NOCHANGE), tr ("setting split TX frequency and mode"));
TRACE_CAT ("HamlibTransceiver", "rig_set_split_freq_mode freq = " << tx
<< " mode = " << rig_strrmode (new_mode));
error_check (rig_set_split_freq_mode (rig_.data (), RIG_VFO_CURR, tx, new_mode, RIG_PASSBAND_NOCHANGE), tr ("setting split TX frequency and mode"));
}
else
{
-1
View File
@@ -24,7 +24,6 @@ namespace
"QRA64",
"FreqCal",
"FT8",
"FT8Free"
};
std::size_t constexpr mode_names_size = sizeof (mode_names) / sizeof (mode_names[0]);
}
-1
View File
@@ -50,7 +50,6 @@ public:
QRA64,
FreqCal,
FT8,
FT8Free,
MODES_END_SENTINAL_AND_COUNT // this must be last
};
Q_ENUM (Mode)
+2 -2
View File
@@ -1,6 +1,6 @@
# Version number components
set (WSJTX_VERSION_MAJOR 0)
set (WSJTX_VERSION_MINOR 2)
set (WSJTX_VERSION_PATCH 0)
set (WSJTX_VERSION_MINOR 3)
set (WSJTX_VERSION_PATCH 1)
set (WSJTX_RC 0) # release candidate number, comment out or zero for development versions
set (WSJTX_VERSION_IS_RELEASE 0) # set to 1 for final release build
+48 -11
View File
@@ -60,16 +60,53 @@ DecodedText::DecodedText (QString const& the_string, bool contest_mode, QString
}
}
if(!is_standard_){
bool unpacked = false;
tryUnpack();
}
if(!unpacked){
unpacked = tryUnpackDirected();
}
if(!unpacked){
unpacked = tryUnpackData();
}
DecodedText::DecodedText (QString const& ft8callmessage){
message_ = ft8callmessage;
is_standard_ = false;
tryUnpack();
}
bool DecodedText::tryUnpack(){
if(is_standard_){
return false;
}
bool unpacked = false;
if(!unpacked){
unpacked = tryUnpackCompound();
}
if(!unpacked){
unpacked = tryUnpackDirected();
}
if(!unpacked){
unpacked = tryUnpackData();
}
return unpacked;
}
bool DecodedText::tryUnpackCompound(){
QString m = message().trimmed();
// directed calls will always be 12+ chars and contain no spaces.
if(m.length() < 12 || m.contains(' ')){
return false;
}
QStringList parts = Varicode::unpackCompoundMessage(m);
if(parts.isEmpty() || parts.length() < 2){
return false;
}
compound_ = QString("%1/%2").arg(parts.at(0), parts.at(1));
message_ = QString("%1: ").arg(compound_);
return true;
}
bool DecodedText::tryUnpackDirected(){
@@ -88,13 +125,13 @@ bool DecodedText::tryUnpackDirected(){
if(parts.length() == 3){
// replace it with the correct unpacked (directed)
message_ = QString("%1: %2%3").arg(parts.at(0), parts.at(1), parts.at(2));
message_ = QString("%1: %2%3 ").arg(parts.at(0), parts.at(1), parts.at(2));
} else if(parts.length() == 4){
// replace it with the correct unpacked (directed numeric)
message_ = QString("%1: %2%3 %4").arg(parts.at(0), parts.at(1), parts.at(2), parts.at(3));
message_ = QString("%1: %2%3 %4 ").arg(parts.at(0), parts.at(1), parts.at(2), parts.at(3));
} else {
// replace it with the correct unpacked (freetext)
message_ = QString(parts.join(QChar()));
message_ = QString(parts.join(""));
}
directed_ = parts;
+8 -1
View File
@@ -31,12 +31,18 @@ class DecodedText
{
public:
explicit DecodedText (QString const& message, bool, QString const& my_grid);
explicit DecodedText (QString const& ft8callmessage);
bool tryUnpack();
bool tryUnpackCompound();
bool tryUnpackDirected();
bool tryUnpackData();
QString compoundCall() const { return compound_; }
bool isCompoundMessage() const { return !compound_.isEmpty(); }
QStringList directedMessage() const { return directed_; }
bool isDirectedMessage() const { return !directed_.isEmpty(); }
bool isDirectedMessage() const { return !directed_.isEmpty() && directed_.length() > 2; }
QString string() const { return string_; }
QString message() const { return message_; }
@@ -85,6 +91,7 @@ private:
column_mode = 19,
column_qsoText = 22 };
QString compound_;
QStringList directed_;
QString string_;
int padding_;
+6
View File
@@ -16,6 +16,12 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>600</width>
<height>0</height>
</size>
</property>
<layout class="QVBoxLayout" name="verticalLayout_11">
<item>
<widget class="QLabel" name="label">
+686 -223
View File
File diff suppressed because it is too large Load Diff
+45 -17
View File
@@ -116,12 +116,14 @@ public slots:
void readFromStdout();
void p1ReadFromStdout();
void setXIT(int n, Frequency base = 0u);
void setFreqForRestore(int freq, bool shouldRestore);
void setFreq4(int rxFreq, int txFreq);
void msgAvgDecode2();
void fastPick(int x0, int x1, int y);
QString lookupCallInCompoundCache(QString const &call);
void clearActivity();
int logRxTxMessageText(QDateTime date, bool isFree, QString text, int freq, bool tx, int block=-1);
int logRxTxMessageText(QDateTime date, QString text, int freq, bool tx, int block=-1);
void addMessageText(QString text, bool clear=false);
void resetMessage();
void resetMessageUI();
@@ -197,6 +199,7 @@ private slots:
void on_txb5_doubleClicked ();
void on_txb6_clicked();
void on_startTxButton_toggled(bool checked);
void toggleTx(bool start);
void splitAndSendNextMessage();
void on_rbNextFreeTextMsg_toggled (bool status);
void on_lookupButton_clicked();
@@ -237,9 +240,7 @@ private slots:
void on_clearAction_triggered(QObject * sender);
void on_cqMacroButton_clicked();
void on_qtcMacroButton_clicked();
void on_replyMacroButton_clicked();
void on_qthMacroButton_clicked();
void on_snrMacroButton_clicked();
void buildQueryMenu(QMenu *);
void on_queryButton_pressed();
void on_macrosMacroButton_pressed();
@@ -254,14 +255,16 @@ private slots:
void on_extFreeTextMsg_currentTextChanged (QString const&);
void on_extFreeTextMsgEdit_currentTextChanged (QString const&);
int currentFreq();
int countFT8MessageFrames(QString const& text);
QPair<QStringList, QStringList> buildFT8MessageFrames(QString const& text);
QString parseFT8Message(QString input, bool *isFree);
bool prepareNextMessageFrame();
bool isFreqOffsetFree(int f, int bw);
int findFreeFreqOffset(int fmin, int fmax, int bw);
void scheduleBeacon(bool first=false);
void setBeaconTimer(QDateTime timestamp);
void prepareBeacon();
void scheduleBacon(bool first=false);
void setBaconTimer(QDateTime timestamp);
void pauseBacon();
void prepareBacon();
QString calculateDistance(QString const& grid);
void on_rptSpinBox_valueChanged(int n);
void killFile();
@@ -638,6 +641,7 @@ private:
struct CallDetail
{
QString call;
QString through;
QString grid;
int freq;
QDateTime utcTimestamp;
@@ -652,45 +656,47 @@ private:
int freq;
QDateTime utcTimestamp;
int snr;
QString text;
};
struct ActivityDetail
{
bool isFree;
bool isLowConfidence;
bool isCompound;
int bits;
QString firstCall;
QString secondCall;
int freq;
QString text;
QDateTime utcTimestamp;
int snr;
};
struct RXDetail
{
bool isFree;
bool isLowConfidence;
int bits;
int freq;
QString text;
QDateTime utcTimestamp;
struct MessageBuffer {
CommandDetail cmd;
QList<ActivityDetail> msgs;
};
bool m_rxDirty;
int m_txFrameCount;
QString m_lastTxMessage;
QDateTime m_lastTxTime;
QQueue<QString> m_txFrameQueue;
QQueue<RXDetail> m_rxFrameQueue;
QQueue<ActivityDetail> m_rxFrameQueue;
QQueue<CommandDetail> m_rxCommandQueue;
QMap<QString, QString> m_compoundCallCache; // base callsign -> compound callsign
QCache<QString, QDateTime> m_txAllcallCommandCache; // callsign -> last tx
QCache<int, QDateTime> m_rxRecentCache; // freq -> last rx
QCache<int, QDateTime> m_rxDirectedCache; // freq -> last directed rx
QCache<QString, int> m_rxCallCache; // call -> last freq seen
QMap<int, int> m_rxFrameBlockNumbers; // freq -> block
QMap<int, QList<ActivityDetail>> m_bandActivity; // freq -> [(text, last timestamp), ...]
QMap<int, MessageBuffer> m_messageBuffer; // freq -> (cmd, [frames, ...])
QMap<QString, CallDetail> m_callActivity; // call -> (last freq, last timestamp)
QSet<QString> m_callSeenBeacon; // call
int m_previousFreq;
bool m_shouldRestoreFreq;
QMap<QString,FoxQSO> m_foxQSO;
QMap<QString,QString> m_loggedByFox;
@@ -699,6 +705,7 @@ private:
QQueue<QString> m_foxRR73Queue;
QQueue<qint64> m_foxRateQueue;
bool m_nextBeaconPaused = false;
QDateTime m_nextBeacon;
QDateTime m_dateTimeQSOOn;
QDateTime m_dateTimeLastTX;
@@ -835,6 +842,27 @@ protected:
}
};
class EnterKeyPressEater : public QObject
{
Q_OBJECT
protected:
bool eventFilter(QObject *obj, QEvent *event){
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if(keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return){
emit this->enterKeyPressed(keyEvent, obj);
return true;
}
}
// standard event processing
return QObject::eventFilter(obj, event);
}
public:
Q_SIGNAL void enterKeyPressed(QKeyEvent *evt, QObject *obj);
};
extern int killbyname(const char* progName);
extern void getDev(int* numDevices,char hostAPI_DeviceName[][50],
int minChan[], int maxChan[],
+554 -388
View File
File diff suppressed because it is too large Load Diff
+457 -163
View File
@@ -30,22 +30,29 @@
const int nalphabet = 41;
QString alphabet = {"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-./?"};
QString grid_pattern = {R"((?<grid>[A-R]{2}[0-9]{2})+)"};
QString callsign_pattern1 = {R"((?<callsign>[A-Z0-9/]{2,}))"};
QString callsign_pattern2 = {R"((?<callsign>(\d|[A-Z])+\/?((\d|[A-Z]){3,})(\/(\d|[A-Z])+)?(\/(\d|[A-Z])+)?))"};
QString callsign_pattern3 = {R"(([0-9A-Z ])([0-9A-Z])([0-9])([A-Z ])([A-Z ])([A-Z ]))"};
QString orig_compound_callsign_pattern = {R"((?<callsign>(\d|[A-Z])+\/?((\d|[A-Z]){2,})(\/(\d|[A-Z])+)?(\/(\d|[A-Z])+)?))"};
QString compound_callsign_pattern = {R"((?<callsign>\b([A-Z0-9]{1,4}\/)?([0-9A-Z])?([0-9A-Z])([0-9])([A-Z])?([A-Z])?([A-Z])?(\/[A-Z0-9]{1,4})?)\b)"};
QString pack_callsign_pattern = {R"(([0-9A-Z ])([0-9A-Z])([0-9])([A-Z ])([A-Z ])([A-Z ]))"};
QString callsign_alphabet = {"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ "};
QMap<QString, int> directed_cmds = {
// any changes here need to be made also in the directed regular xpression for parsing
// directed queries
{"?", 0 }, // query snr
{"?", 0 }, // query ack
{"@", 1 }, // query qth
{"&", 2 }, // query station message
{"$", 3 }, // query station(s) heard
{"|", 4 }, // relay message
{"^", 4 }, // query snr
{"%", 5 }, // query pwr
{"|", 6 }, // relay message?
{"!", 7 }, // alert message?
// {"=", 8 }, // unused? (can we even use equals?)
// {"/", 9 }, // unused? (can we even use stroke?)
// directed responses
{" ACK", 23 }, // acknowledged
{" PWR", 24 }, // power level
{" SNR", 25 }, // seen a station at the provided snr
{" NO", 26 }, // negative confirm
@@ -56,71 +63,127 @@ QMap<QString, int> directed_cmds = {
{" ", 31 }, // send freetext
};
QSet<int> allowed_cmds = {0, 1, 2, 24, 25, 26, 27, 28, 29, 30, 31};
QSet<int> allowed_cmds = {0, 1, 2, 3, 4, 5, 6, 7, 23, 24, 25, 26, 27, 28, 29, 30, 31};
QSet<int> buffered_cmds = {6, 7};
QRegularExpression directed_re("^"
"(?<to>[A-Z0-9/]+)"
"(?<cmd>\\s?(?:AGN[?]|RR|73|YES|NO|SNR|PWR|[?$@&| ]))"
"(?<cmd>\\s?(?:AGN[?]|RR|73|YES|NO|SNR|PWR|ACK|[?@&$^%|! ]))"
"(?<pwr>\\s?\\d+\\s?[KM]?W)?"
"(?<num>\\s?[-+]?(?:3[01]|[0-2]?[0-9]))?"
);
QMap<QChar, QString> huff = {
QMap<QChar, QString> hufftable = {
// char code weight
{' ' , "000" }, // 1300
{'E' , "001" }, // 1270.2
{'T' , "1100" }, // 905.6
{'A' , "1010" }, // 816.7
{'O' , "0111" }, // 750.7
{'I' , "0101" }, // 696.6
{'N' , "0100" }, // 674.9
{'S' , "11111" }, // 632.7
{'H' , "11110" }, // 609.4
{'R' , "11101" }, // 598.7
{'D' , "10111" }, // 425.3
{'L' , "10110" }, // 402.5
{'C' , "111001" }, // 278.2
{'U' , "111000" }, // 275.8
{'M' , "110111" }, // 240.6
{'W' , "110110" }, // 236.0
{'F' , "110100" }, // 222.8
{'G' , "100111" }, // 201.5
{'Q' , "100110" }, // 200
{'Y' , "011010" }, // 197.4
{'P' , "011001" }, // 192.9
{'B' , "011000" }, // 149.2
{'!' , "0110111" }, // 100
{'.' , "1000000" }, // 100
{'0' , "1000001" }, // 100
{'1' , "1000010" }, // 100
{'2' , "1000011" }, // 100
{'3' , "1000100" }, // 100
{'4' , "1000101" }, // 100
{'5' , "1000110" }, // 100
{'6' , "1000111" }, // 100
{'7' , "1001000" }, // 100
{'8' , "1001001" }, // 100
{'9' , "1001010" }, // 100
{'?' , "1001011" }, // 100
{'^' , "1101010" }, // 100 <- shift
{'V' , "0110110" }, // 97.8
{'K' , "11010111" }, // 77.2
{'J' , "1101011010" }, // 15.3
{'X' , "1101011001" }, // 15.0
{'Z' , "11010110110" }, // 7.4
{':' , "11010110000" }, // 5
{'+' , "110101100011" }, // 5
{'-' , "110101101110" }, // 5
{'/' , "110101101111" }, // 5
{'\x04' , "110101100010" }, // 1 <- eot
{ ' ' , "000" }, // 1300
{ 'E' , "001" }, // 1270.2
{ 'T' , "1100" }, // 905.6
{ 'A' , "1010" }, // 816.7
{ 'O' , "0111" }, // 750.7
{ 'I' , "0101" }, // 696.6
{ 'N' , "0100" }, // 674.9
{ 'S' , "11111" }, // 632.7
{ 'H' , "11110" }, // 609.4
{ 'R' , "11101" }, // 598.7
{ 'D' , "10111" }, // 425.3
{ 'L' , "10110" }, // 402.5
{ 'C' , "111001" }, // 278.2
{ 'U' , "111000" }, // 275.8
{ 'M' , "110111" }, // 240.6
{ 'W' , "110110" }, // 236.0
{ 'F' , "110100" }, // 222.8
{ 'G' , "100111" }, // 201.5
{ 'Q' , "100110" }, // 200
{ 'Y' , "011010" }, // 197.4
{ 'P' , "011001" }, // 192.9
{ 'B' , "011000" }, // 149.2
{ '\\' , "0110111" }, // 100 <- escape
{ '.' , "1000000" }, // 100
{ '0' , "1000001" }, // 100
{ '1' , "1000010" }, // 100
{ '2' , "1000011" }, // 100
{ '3' , "1000100" }, // 100
{ '4' , "1000101" }, // 100
{ '5' , "1000110" }, // 100
{ '6' , "1000111" }, // 100
{ '7' , "1001000" }, // 100
{ '8' , "1001001" }, // 100
{ '9' , "1001010" }, // 100
{ '?' , "1001011" }, // 100
{ '/' , "1101010" }, // 100
{ 'V' , "0110110" }, // 97.8
{ 'K' , "11010111" }, // 77.2
{ 'J' , "1101011010" }, // 15.3
{ 'X' , "1101011001" }, // 15.0
{ 'Z' , "11010110110" }, // 7.4
{ ':' , "11010110000" }, // 5
{ '+' , "110101100011" }, // 5
{ '-' , "110101101110" }, // 5
{ '!' , "110101101111" }, // 5
{ '\x04' , "110101100010" }, // 1 <- eot
/*
A-Z 0-9 Space \\ ? / : - + !
special chars that are escaped will be added here too...
*/
};
QChar huffeot = '\x04';
/*
original: Space \\ ? / : - + !
needed: ^,&@#$%'"()<>|*[]{}=;_~`
*/
QMap<QString, QChar> huffescapes = {
{ "\\ ", '^' },
{ "\\E", ',' },
{ "\\T", '&' },
{ "\\A", '@' },
{ "\\O", '#' },
{ "\\I", '$' },
{ "\\N", '%' },
{ "\\S", '\'' },
{ "\\H", '\"' },
{ "\\R", '(' },
{ "\\D", ')' },
{ "\\L", '<' },
{ "\\C", '>' },
{ "\\U", '|' },
{ "\\M", '*' },
{ "\\W", '[' },
{ "\\F", ']' },
{ "\\G", '{' },
{ "\\Q", '}' },
{ "\\Y", '=' },
{ "\\P", ';' },
{ "\\B", '_' },
{ "\\.", '~' },
{ "\\0", '`' },
#if 0
// reserved <= 14 bits
{ "\\1", '' },
{ "\\2", '' },
{ "\\3", '' },
{ "\\4", '' },
{ "\\5", '' },
{ "\\6", '' },
{ "\\7", '' },
{ "\\8", '' },
{ "\\9", '' },
{ "\\?", '' },
{ "\\/", '' },
{ "\\V", '' },
#endif
};
QChar ESC = '\\'; // Escape char
QChar EOT = '\x04'; // EOT char
quint32 nbasecall = 37 * 36 * 10 * 27 * 27 * 27;
QMap<QString, quint32> basecalls = {
{ "CQCQCQ", nbasecall + 1 },
{ "<....>", nbasecall + 1 }, // incomplete callsign
{ "CQCQCQ", nbasecall + 2 },
{ "ALLCALL", nbasecall + 3 },
};
@@ -146,6 +209,35 @@ QMap<int, int> dbm2mw = {
{60 , 1000000}, // 1000W
};
QMap<QChar, QString> initializeEscapes(QMap<QChar, QString> huff, QMap<QString, QChar> escapes){
QMap<QChar, QString> newhuff(huff);
foreach(auto escapeString, escapes.keys()){
auto ch = escapes[escapeString];
auto encoded = Varicode::huffEncode(huff, escapeString);
auto bits = Varicode::bitsListToBits(encoded);
newhuff[ch] = Varicode::bitsToStr(bits);
}
#if PRINT_VARICODE_ALPHABET
auto keys = newhuff.keys();
qSort(keys.begin(), keys.end(), [newhuff](QChar a, QChar b){
return newhuff[a].length() < newhuff[b].length();
});
foreach(auto ch, keys){
qDebug() << ch << newhuff[ch] << newhuff[ch].length();
}
#endif
return newhuff;
}
QMap<QChar, QString> hufftableescaped = initializeEscapes(hufftable, huffescapes);
/*
* UTILITIES
*/
int mwattsToDbm(int mwatts){
int dbm = 0;
auto values = dbm2mw.values();
@@ -170,6 +262,10 @@ int dbmTomwatts(int dbm){
return iter.value();
}
/*
* VARICODE
*/
QString Varicode::formatSNR(int snr){
if(snr < -60 || snr > 60){
return QString();
@@ -185,15 +281,47 @@ QString Varicode::formatPWR(int dbm){
int mwatts = dbmTomwatts(dbm);
if(mwatts < 1000){
return QString("%1MW").arg(mwatts);
return QString("%1mW").arg(mwatts);
}
return QString("%1W").arg(mwatts/1000);
}
QString Varicode::checksum16(QString const &input){
auto fromBytes = input.toLocal8Bit();
auto crc = CRC::Calculate(fromBytes.data(), fromBytes.length(), CRC::CRC_16_KERMIT());
auto checksum = Varicode::pack16bits(crc);
if(checksum.length() < 3){
checksum += QString(" ").repeated(3-checksum.length());
}
return checksum;
}
bool Varicode::checksum16Valid(QString const &checksum, QString const &input){
auto fromBytes = input.toLocal8Bit();
auto crc = CRC::Calculate(fromBytes.data(), fromBytes.length(), CRC::CRC_16_KERMIT());
return Varicode::pack16bits(crc) == checksum;
}
QString Varicode::checksum32(QString const &input){
auto fromBytes = input.toLocal8Bit();
auto crc = CRC::Calculate(fromBytes.data(), fromBytes.length(), CRC::CRC_32_BZIP2());
auto checksum = Varicode::pack32bits(crc);
if(checksum.length() < 6){
checksum += QString(" ").repeated(6-checksum.length());
}
return checksum;
}
bool Varicode::checksum32Valid(QString const &checksum, QString const &input){
auto fromBytes = input.toLocal8Bit();
auto crc = CRC::Calculate(fromBytes.data(), fromBytes.length(), CRC::CRC_32_BZIP2());
return Varicode::pack32bits(crc) == checksum;
}
QStringList Varicode::parseCallsigns(QString const &input){
QStringList callsigns;
QRegularExpression re(callsign_pattern2);
QRegularExpression re(compound_callsign_pattern);
QRegularExpressionMatchIterator iter = re.globalMatch(input);
while(iter.hasNext()){
QRegularExpressionMatch match = iter.next();
@@ -228,7 +356,7 @@ QStringList Varicode::parseGrids(const QString &input){
return grids;
}
QList<QVector<bool>> Varicode::huffEncode(QString const& text){
QList<QVector<bool>> Varicode::huffEncode(QMap<QChar, QString> const &huff, QString const& text){
QList<QVector<bool>> out;
foreach(auto ch, text){
@@ -241,16 +369,8 @@ QList<QVector<bool>> Varicode::huffEncode(QString const& text){
return out;
}
QVector<bool> Varicode::huffFlatten(QList<QVector<bool>> &list){
QVector<bool> out;
foreach(auto vec, list){
out += vec;
}
return out;
}
QString Varicode::huffDecode(QVector<bool> const& bitvec, int pad){
QString out;
QString Varicode::huffDecode(QMap<QChar, QString> const &huff, QVector<bool> const& bitvec, int pad){
QString text;
QString bits = bitsToStr(bitvec).mid(0, bitvec.length()-pad);
@@ -259,12 +379,12 @@ QString Varicode::huffDecode(QVector<bool> const& bitvec, int pad){
bool found = false;
foreach(auto key, huff.keys()){
if(bits.startsWith(huff[key])){
if(key == huffeot){
out.append(" ");
if(key == EOT){
text.append(" ");
found = false;
break;
}
out.append(key);
text.append(key);
bits = bits.mid(huff[key].length());
found = true;
}
@@ -274,9 +394,42 @@ QString Varicode::huffDecode(QVector<bool> const& bitvec, int pad){
}
}
return out;
return text;
}
QString Varicode::huffUnescape(QString const &input){
QString text = input;
// unescape alternate alphabet
foreach(auto escaped, huffescapes.keys()){
text = text.replace(escaped, huffescapes[escaped]);
}
return text;
}
QString Varicode::huffEscape(QString const &input){
QString text = input;
// escape alternate alphabet
foreach(auto unescaped, huffescapes.values()){
text = text.replace(unescaped, huffescapes.key(unescaped));
}
return text;
}
QSet<QChar> Varicode::huffValidChars(){
return QSet<QChar>::fromList(hufftableescaped.keys());
}
bool Varicode::huffShouldEscape(QString const &input){
foreach(auto ch, huffescapes.values()){
if(input.contains(ch)){
return true;
}
}
return false;
}
// convert char* array of 0 bytes and 1 bytes to bool vector
QVector<bool> Varicode::bytesToBits(char *bitvec, int n){
QVector<bool> bits;
@@ -338,29 +491,54 @@ quint64 Varicode::bitsToInt(QVector<bool>::ConstIterator start, int n){
return v;
}
QVector<bool> Varicode::bitsListToBits(QList<QVector<bool>> &list){
QVector<bool> out;
foreach(auto vec, list){
out += vec;
}
return out;
}
quint8 Varicode::unpack5bits(QString const& value){
return alphabet.indexOf(value.at(0));
}
// pack a 5-bit value from 0 to 31 into a single character
QString Varicode::pack5bits(quint8 packed){
return alphabet.at(packed % nalphabet);
return alphabet.at(packed % 32);
}
quint8 Varicode::unpack6bits(QString const& value){
return alphabet.indexOf(value.at(0));
}
// pack a 6-bit value from 0 to 40 into a single character
QString Varicode::pack6bits(quint8 packed){
return alphabet.at(packed % 41);
}
quint16 Varicode::unpack16bits(QString const& value){
int a = alphabet.indexOf(value.at(0));
int b = alphabet.indexOf(value.at(1));
int c = alphabet.indexOf(value.at(2));
return (nalphabet*nalphabet) * a + nalphabet*b + c;
int unpacked = (nalphabet * nalphabet) * a + nalphabet * b + c;
if(unpacked > (1<<16)-1){
// BASE-41 can produce a value larger than 16 bits... ala "???" == 70643
return 0;
}
return unpacked & ((1<<16)-1);
}
// pack a 16-bit value into a three character sequence
QString Varicode::pack16bits(quint16 packed){
QString out;
quint16 tmp = packed / (nalphabet*nalphabet);
quint16 tmp = packed / (nalphabet * nalphabet);
out.append(alphabet.at(tmp));
tmp = (packed - (tmp * (nalphabet*nalphabet))) / nalphabet;
tmp = (packed - (tmp * (nalphabet * nalphabet))) / nalphabet;
out.append(alphabet.at(tmp));
tmp = packed % nalphabet;
@@ -389,6 +567,37 @@ QString Varicode::pack64bits(quint64 packed){
return pack32bits(a) + pack32bits(b);
}
// //
// --- //
// //
// pack a 4-digit alpha-numeric callsign prefix/suffix into a 22 bit value
quint32 Varicode::packCallsignPrefixSuffix(QString const& value){
quint8 mask6 = (1<<6)-1;
QString prefix = QString(value).replace(QRegExp("[^A-Z0-9]"), "");
if(prefix.length() < 4){
prefix = prefix + QString(".").repeated(4-prefix.length());
}
// [16][6] = 22 bits
auto left = prefix.left(3);
auto right = prefix.right(1); // guaranteed to be in our alphabet...
return ((quint32)Varicode::unpack16bits(left) << 6) | (Varicode::unpack6bits(right) & mask6);
}
QString Varicode::unpackCallsignPrefixSuffix(quint32 packed){
quint32 mask22 = ((1<<16)-1) << 6;
quint32 mask6 = ((1<<6)-1);
quint16 a = (packed & mask22) >> 6;
quint16 b = packed & mask6 ;
return QString(Varicode::pack16bits(a) + Varicode::pack6bits(b)).replace(".", "");
}
// pack a callsign into a 28-bit value
quint32 Varicode::packCallsign(QString const& value){
quint32 packed = 0;
@@ -435,7 +644,7 @@ quint32 Varicode::packCallsign(QString const& value){
}
QString matched;
QRegularExpression m(callsign_pattern3);
QRegularExpression m(pack_callsign_pattern);
foreach(auto permutation, permutations){
auto match = m.match(permutation);
if(match.hasMatch()){
@@ -560,6 +769,7 @@ QPair<float, float> grid2deg(QString const &grid){
return longLat;
}
// pack a 4-digit maidenhead grid locator into a 15-bit value
quint16 Varicode::packGrid(QString const& grid){
// TODO: validate grid...
@@ -588,89 +798,168 @@ bool Varicode::isCommandAllowed(const QString &cmd){
return directed_cmds.contains(cmd) && allowed_cmds.contains(directed_cmds[cmd]);
}
QString Varicode::packDirectedMessage(const QString &text, const QString &callsign, int *n){
bool Varicode::isCommandBuffered(const QString &cmd){
return directed_cmds.contains(cmd) && buffered_cmds.contains(directed_cmds[cmd]);
}
QString Varicode::packCompoundMessage(const QString &baseCallsign, const QString &fix, bool isPrefix, quint16 num){
QString frame;
auto match = directed_re.match(text);
if(match.hasMatch()){
QString from = callsign;
QString to = match.captured("to");
QString cmd = match.captured("cmd");
QString num = match.captured("num").trimmed();
QString pwr = match.captured("pwr").trimmed();
quint8 packed_is_data = 0;
quint8 packed_is_compound = 1;
quint8 packed_is_prefix = (int)isPrefix;
quint32 packed_base = Varicode::packCallsign(baseCallsign);
quint32 packed_fix = Varicode::packCallsignPrefixSuffix(fix);
int inum = -31;
bool hasnum = false;
if(!num.isEmpty()){
inum = qMax(-30, qMin(num.toInt(&hasnum, 10), 30));
}
// if we are packing a PWR command, pack pwr into dbm
int ipwr = -31;
if(!pwr.isEmpty() && cmd.trimmed() == "PWR"){
int factor = 1000;
if(pwr.endsWith("KW")){
factor = 1000000;
}
else if(pwr.endsWith("MW")){
factor = 1;
}
ipwr = pwr.replace(QRegExp("[KM]?W"), "").toInt() * factor;
inum = mwattsToDbm(ipwr) - 30;
}
if(to == callsign){
*n = 0;
return frame;
}
bool validToCallsign = basecalls.contains(to) || QRegularExpression(callsign_pattern2).match(to).hasMatch();
if(!validToCallsign || !Varicode::isCommandAllowed(cmd)){
*n = 0;
return frame;
}
// TODO: jsherer - we don't need this CRC... the FT8 msg already has a 12 bit CRC...
//auto fromBytes = from.toLocal8Bit();
//auto fromCRC = CRC::Calculate(fromBytes.data(), fromBytes.length(), CRC::CRC_5_ITU());
quint8 packed_is_data = 0;
quint8 packed_flag = inum < 0 ? 1 : 0;
quint32 packed_from = Varicode::packCallsign(from);
quint32 packed_to = Varicode::packCallsign(to);
if(packed_from == 0 || packed_to == 0){
*n = 0;
return frame;
}
quint8 packed_cmd = directed_cmds[cmd];
quint8 packed_extra = qAbs(inum);
// [1][2][28][28][5],[5] = 69
auto bits = (
Varicode::intToBits(packed_is_data, 1) +
Varicode::intToBits(packed_flag, 2) +
Varicode::intToBits(packed_from, 28) +
Varicode::intToBits(packed_to, 28) +
Varicode::intToBits(packed_cmd & 31, 5)
);
frame = Varicode::pack64bits(Varicode::bitsToInt(bits)) + Varicode::pack5bits(packed_extra & 31);
*n = match.captured(0).length();
if(packed_base == 0 || packed_fix == 0){
return frame;
}
return frame;
quint16 mask11 = ((1<<11)-1)<<5;
quint8 mask5 = (1<<5)-1;
quint16 packed_11 = (num & mask11) >> 5;
quint8 packed_5 = num & mask5;
// [1][1][1][28][22][11],[5] = 69
auto bits = (
Varicode::intToBits(packed_is_data, 1) +
Varicode::intToBits(packed_is_compound, 1) +
Varicode::intToBits(packed_is_prefix, 1) +
Varicode::intToBits(packed_base, 28) +
Varicode::intToBits(packed_fix, 22) +
Varicode::intToBits(packed_11, 11)
);
return Varicode::pack64bits(Varicode::bitsToInt(bits)) + Varicode::pack5bits(packed_5 % 32);
}
QStringList Varicode::unpackCompoundMessage(const QString &text){
QStringList unpacked;
if(text.length() < 13 || text.contains(" ")){
return unpacked;
}
// [1][1][1][28][22][11],[5] = 69
auto bits = Varicode::bitsToStr(Varicode::intToBits(Varicode::unpack64bits(text.left(12)), 64));
quint8 packed_5 = Varicode::unpack5bits(text.right(1));
quint8 is_data = Varicode::bitsToInt(Varicode::strToBits(bits.left(1)));
if(is_data != 0){
return unpacked;
}
quint8 is_compound = Varicode::bitsToInt(Varicode::strToBits(bits.mid(1,1)));
if(is_compound != 1){
return unpacked;
}
quint8 is_prefix = Varicode::bitsToInt(Varicode::strToBits(bits.mid(2,1)));
quint32 packed_base = Varicode::bitsToInt(Varicode::strToBits(bits.mid(3, 28)));
quint32 packed_fix = Varicode::bitsToInt(Varicode::strToBits(bits.mid(31, 22)));
quint8 packed_11 = Varicode::bitsToInt(Varicode::strToBits(bits.mid(53, 11)));
QString base = Varicode::unpackCallsign(packed_base).trimmed();
QString fix = Varicode::unpackCallsignPrefixSuffix(packed_fix);
quint16 num = (packed_11 << 5) | packed_5;
if(is_prefix){
unpacked.append(fix);
}
unpacked.append(base);
if(!is_prefix){
unpacked.append(fix);
}
unpacked.append(QString("%1").arg(num));
return unpacked;
}
QString Varicode::packDirectedMessage(const QString &text, const QString &baseCallsign, QString * pCmd, int *n){
QString frame;
auto match = directed_re.match(text);
if(!match.hasMatch()){
if(n) *n = 0;
return frame;
}
QString from = baseCallsign;
QString to = match.captured("to");
QString cmd = match.captured("cmd");
QString num = match.captured("num").trimmed();
QString pwr = match.captured("pwr").trimmed().toUpper();
// validate callsign
bool validToCallsign = (to != baseCallsign) && (basecalls.contains(to) || QRegularExpression(compound_callsign_pattern).match(to).hasMatch());
if(!validToCallsign){
if(n) *n = 0;
return frame;
}
// validate command
if(!Varicode::isCommandAllowed(cmd)){
if(n) *n = 0;
return frame;
}
// packing general number...
int inum = -31;
bool hasnum = false;
if(!num.isEmpty()){
inum = qMax(-30, qMin(num.toInt(&hasnum, 10), 30));
}
// if we are packing a PWR command, pack pwr as dbm
int ipwr = -31;
if(!pwr.isEmpty() && cmd.trimmed() == "PWR"){
int factor = 1000;
if(pwr.endsWith("KW")){
factor = 1000000;
}
else if(pwr.endsWith("MW")){
factor = 1;
}
ipwr = pwr.replace(QRegExp("[KM]?W", Qt::CaseInsensitive), "").toInt() * factor;
inum = mwattsToDbm(ipwr) - 30;
}
quint8 packed_is_data = 0;
quint8 packed_is_compound = 0;
quint8 packed_num_flag = inum < 0 ? 1 : 0;
quint32 packed_from = Varicode::packCallsign(from);
quint32 packed_to = Varicode::packCallsign(to);
if(packed_from == 0 || packed_to == 0){
if(n) *n = 0;
return frame;
}
quint8 packed_cmd = directed_cmds[cmd];
quint8 packed_extra = qAbs(inum);
// [1][1][1][28][28][5],[5] = 69
auto bits = (
Varicode::intToBits(packed_is_data, 1) +
Varicode::intToBits(packed_is_compound, 1) +
Varicode::intToBits(packed_num_flag, 1) +
Varicode::intToBits(packed_from, 28) +
Varicode::intToBits(packed_to, 28) +
Varicode::intToBits(packed_cmd % 32, 5)
);
if(pCmd) *pCmd = cmd;
if(n) *n = match.captured(0).length();
return Varicode::pack64bits(Varicode::bitsToInt(bits)) + Varicode::pack5bits(packed_extra % 32);
}
QStringList Varicode::unpackDirectedMessage(const QString &text){
QStringList unpacked;
if(text.length() < 13){
if(text.length() < 13 || text.contains(" ")){
return unpacked;
}
// [1][2][28][28][5],[5] = 69
// [1][1][1][28][28][5],[5] = 69
auto bits = Varicode::bitsToStr(Varicode::intToBits(Varicode::unpack64bits(text.left(12)), 64));
quint8 extra = Varicode::unpack5bits(text.right(1));
@@ -678,25 +967,24 @@ QStringList Varicode::unpackDirectedMessage(const QString &text){
if(is_data != 0){
return unpacked;
}
quint8 flag = Varicode::bitsToInt(Varicode::strToBits(bits.mid(1,2)));
quint8 is_compound = Varicode::bitsToInt(Varicode::strToBits(bits.mid(1,1)));
if(is_compound != 0){
return unpacked;
}
quint8 num_flag = Varicode::bitsToInt(Varicode::strToBits(bits.mid(2,1)));
quint32 packed_from = Varicode::bitsToInt(Varicode::strToBits(bits.mid(3, 28)));
quint32 packed_to = Varicode::bitsToInt(Varicode::strToBits(bits.mid(31, 28)));
quint8 packed_cmd = Varicode::bitsToInt(Varicode::strToBits(bits.mid(59, 5)));
QString from = Varicode::unpackCallsign(packed_from).trimmed();
// TODO: jsherer - we don't need this CRC... the FT8 msg already has a 12 bit CRC...
//auto fromBytes = from.toLocal8Bit();
//auto fromCRC = CRC::Calculate(fromBytes.data(), fromBytes.length(), CRC::CRC_5_ITU());
//if(fromCRC != extra){
// return unpacked;
//}
QString to = Varicode::unpackCallsign(packed_to).trimmed();
QString cmd = directed_cmds.key(packed_cmd % 32);
unpacked.append(from);
unpacked.append(Varicode::unpackCallsign(packed_to).trimmed());
unpacked.append(directed_cmds.key(packed_cmd & 31));
unpacked.append(to);
unpacked.append(cmd);
int num = (flag ? -1 : 1) * extra;
int num = (num_flag ? -1 : 1) * extra;
if(num != -31){
// TODO: jsherer - should we decide which format to use on the command, or something else?
if(packed_cmd == directed_cmds[" PWR"]){
@@ -711,7 +999,7 @@ QStringList Varicode::unpackDirectedMessage(const QString &text){
return unpacked;
}
QString Varicode::packDataMessage(const QString &text, int *n){
QString Varicode::packDataMessage(const QString &input, QString * out, int *n){
QString frame;
// [1][63],[5] = 69
@@ -721,7 +1009,9 @@ QString Varicode::packDataMessage(const QString &text, int *n){
);
int i = 0;
foreach(auto charBits, Varicode::huffEncode(text)){
// we use the escaped table here, so they the escapes and the characters are packed together...
foreach(auto charBits, Varicode::huffEncode(hufftableescaped, input)){
if(frameBits.length() + charBits.length() < 63){
frameBits += charBits;
i++;
@@ -735,7 +1025,7 @@ QString Varicode::packDataMessage(const QString &text, int *n){
frameBits += Varicode::intToBits(0, pad);
}
frame = Varicode::pack64bits(Varicode::bitsToInt(frameBits)) + Varicode::pack5bits(pad & 31);
frame = Varicode::pack64bits(Varicode::bitsToInt(frameBits)) + Varicode::pack5bits(pad % 32);
*n = i;
return frame;
@@ -744,7 +1034,7 @@ QString Varicode::packDataMessage(const QString &text, int *n){
QString Varicode::unpackDataMessage(const QString &text){
QString unpacked;
if(text.length() < 13){
if(text.length() < 13 || text.contains(" ")){
return unpacked;
}
@@ -759,7 +1049,11 @@ QString Varicode::unpackDataMessage(const QString &text){
// pop off the is_data bit
bits.removeAt(0);
unpacked = Varicode::huffDecode(bits, pad);
// huff decode the bits (without escapes)
unpacked = Varicode::huffDecode(hufftable, bits, pad);
// then... unescape special characters
unpacked = Varicode::huffUnescape(unpacked);
return unpacked;
}
+27 -5
View File
@@ -30,12 +30,22 @@ public:
static QString formatSNR(int snr);
static QString formatPWR(int dbm);
static QString checksum16(QString const &input);
static bool checksum16Valid(QString const &checksum, QString const &input);
static QString checksum32(QString const &input);
static bool checksum32Valid(QString const &checksum, QString const &input);
static QStringList parseCallsigns(QString const &input);
static QStringList parseGrids(QString const &input);
static QList<QVector<bool>> huffEncode(QString const& text);
static QVector<bool> huffFlatten(QList<QVector<bool>> &list);
static QString huffDecode(QVector<bool> const& bitvec, int pad=0);
static QList<QVector<bool>> huffEncode(const QMap<QChar, QString> &huff, QString const& text);
static QString huffDecode(const QMap<QChar, QString> &huff, QVector<bool> const& bitvec, int pad=0);
static QString huffUnescape(QString const &input);
static QString huffEscape(QString const &input);
static QSet<QChar> huffValidChars();
static bool huffShouldEscape(QString const &input);
static QVector<bool> bytesToBits(char * bitvec, int n);
static QVector<bool> strToBits(QString const& bitvec);
@@ -44,10 +54,14 @@ public:
static QVector<bool> intToBits(quint64 value, int expected=0);
static quint64 bitsToInt(QVector<bool> const value);
static quint64 bitsToInt(QVector<bool>::ConstIterator start, int n);
static QVector<bool> bitsListToBits(QList<QVector<bool>> &list);
static quint8 unpack5bits(QString const& value);
static QString pack5bits(quint8 packed);
static quint8 unpack6bits(QString const& value);
static QString pack6bits(quint8 packed);
static quint16 unpack16bits(QString const& value);
static QString pack16bits(quint16 packed);
@@ -57,6 +71,9 @@ public:
static quint64 unpack64bits(QString const& value);
static QString pack64bits(quint64 packed);
static quint32 packCallsignPrefixSuffix(QString const& value);
static QString unpackCallsignPrefixSuffix(quint32 packed);
static quint32 packCallsign(QString const& value);
static QString unpackCallsign(quint32 value);
@@ -64,10 +81,15 @@ public:
static QString unpackGrid(quint16 value);
static bool isCommandAllowed(const QString &cmd);
static QString packDirectedMessage(QString const& text, QString const& callsign, int *n);
static bool isCommandBuffered(const QString &cmd);
static QString packCompoundMessage(const QString &baseCallsign, const QString &fix, bool isPrefix, quint16 num);
static QStringList unpackCompoundMessage(const QString &text);
static QString packDirectedMessage(QString const& text, QString const& callsign, QString * pCmd, int *n);
static QStringList unpackDirectedMessage(QString const& text);
static QString packDataMessage(QString const& text, int *n);
static QString packDataMessage(QString const& text, QString *out, int *n);
static QString unpackDataMessage(QString const& text);
};