Compare commits

...

87 Commits

Author SHA1 Message Date
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
Jordan Sherer c0c4693782 Widened the log qso dialog 2018-07-21 03:55:45 -04:00
Jordan Sherer 9223d3da40 Version bump to 0.2.0 2018-07-21 03:52:48 -04:00
Jordan Sherer 83e3f5ddbc SNR button sends a directed SNR message 2018-07-21 03:52:13 -04:00
Jordan Sherer 0f4057aa97 Make sure to clear seen beacon cache when changing bands 2018-07-21 03:45:56 -04:00
Jordan Sherer 591629e369 Disable heard list beaconing for now 2018-07-21 03:33:00 -04:00
Jordan Sherer 4a96ab3b13 Beaconing of heard list 2018-07-21 03:32:07 -04:00
Jordan Sherer e955cff24f Make sure selected call exists before using it 2018-07-21 02:31:30 -04:00
Jordan Sherer fe405cfba8 Added ability to encode power into dbm 2018-07-21 02:18:15 -04:00
Jordan Sherer 92117aa791 Added SNR directed command 2018-07-20 23:17:49 -04:00
Jordan Sherer d4c2d9a871 Log directed FT8Call messages to PSKReporter 2018-07-20 23:09:17 -04:00
Jordan Sherer b2e2b91d31 Refactored get current frequency 2018-07-20 22:17:41 -04:00
Jordan Sherer fa864c50cd Removed CQ DX from allcall 2018-07-20 16:23:48 -04:00
Jordan Sherer 1c6d1babe6 Set the QTH button to drop in the station location if provided 2018-07-20 16:11:21 -04:00
Jordan Sherer ac27d1a9b6 Brighter green for slider 2018-07-20 16:10:59 -04:00
Jordan Sherer 28eb082655 Further fix format of SNR 2018-07-20 16:04:14 -04:00
Jordan Sherer 9a945c156d Proper SNR formatting +00 +30 -09 2018-07-20 11:40:55 -04:00
Jordan Sherer 83c742f7ec Double click band activity now transfers the received message into the directed activity window and further transmissions on the offset will be added to the window automatically 2018-07-20 10:54:00 -04:00
Jordan Sherer 7a788c05c8 Added configuration options for controlling aging of the callsign and band activity windows 2018-07-20 10:13:12 -04:00
Jordan Sherer 7b409a6ff4 Fix varicode bug with empty messages 2018-07-20 09:38:34 -04:00
Jordan Sherer 87a631f5f0 Reorder directed and macro buttons 2018-07-20 09:38:10 -04:00
Jordan Sherer efd6b54ba7 Updated slider handle color 2018-07-20 09:00:36 -04:00
Jordan Sherer 553f2400e5 Updated text decoding to support more commands as well as numerical options for those commands 2018-07-19 23:14:11 -04:00
Jordan Sherer 5c84e79e5b Updated label text for qth message 2018-07-19 10:36:43 -04:00
Jordan Sherer 052b81ec8f Added 73 action and more appropriate all call responses. Added better handling of end of transmissions. 2018-07-19 10:35:00 -04:00
Jordan Sherer 7ecc550bc2 Added station qth to configuration options 2018-07-19 10:34:19 -04:00
Jordan Sherer b8267372e4 Added varicode encoding of messages
What this does is allow us to pack more than 13 characters in a single
transmission frame. Optimized using a Huffman encoding using weights of
alphabetical frequency, this will often allow us to send less than 5 bits
per character.
2018-07-19 03:44:08 -04:00
Jordan Sherer 512dffabf4 Added station message command processing 2018-07-19 02:09:19 -04:00
Jordan Sherer 50a3a56d2d Added station message to configuration 2018-07-19 00:39:31 -04:00
Jordan Sherer deb228948d Added ability to display when we receive the final transmission frame of a message 2018-07-18 16:45:27 -04:00
Jordan Sherer 58032b6ae4 Restrict low offset when not in split mode 2018-07-18 14:26:45 -04:00
Jordan Sherer 7845736c05 Smarter beacon scheduling for when editing a message or have recently transmitted 2018-07-18 09:04:58 -04:00
Jordan Sherer 77eb65d6b3 Display callsign prefix for transmitted directed messages 2018-07-16 09:14:28 -04:00
Jordan Sherer 252c21b818 Bump version to 0.1.1 2018-07-16 12:07:39 +00:00
Jordan Sherer 82c3b23e44 Merge branch 'ft8call-reorg' of https://bitbucket.org/widefido/wsjtx into ft8call-reorg 2018-07-16 11:59:06 +00:00
Jordan Sherer 5764170975 Disabled contest mode message packing which causes crashes when typing R 2018-07-16 11:58:41 +00:00
Jordan Sherer a2c85256e8 Only throttle ALLCALLs 2018-07-15 21:00:12 -04:00
17 changed files with 3082 additions and 1327 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 ();
+113 -2
View File
@@ -140,6 +140,7 @@
#include <QSettings>
#include <QAudioDeviceInfo>
#include <QAudioInput>
#include <QDebug>
#include <QDialog>
#include <QAction>
#include <QFileDialog>
@@ -160,7 +161,6 @@
#include <QColorDialog>
#include <QSerialPortInfo>
#include <QScopedPointer>
#include <QDebug>
#include "pimpl_impl.hpp"
#include "qt_helpers.hpp"
@@ -181,6 +181,8 @@
#include "MaidenheadLocatorValidator.hpp"
#include "CallsignValidator.hpp"
#include "varicode.h"
#include "ui_Configuration.h"
#include "moc_Configuration.cpp"
@@ -430,6 +432,8 @@ private:
Q_SLOT void on_add_macro_push_button_clicked (bool = false);
Q_SLOT void on_delete_macro_push_button_clicked (bool = false);
Q_SLOT void on_PTT_method_button_group_buttonClicked (int);
Q_SLOT void on_station_message_line_edit_textChanged(QString const&);
Q_SLOT void on_qth_message_line_edit_textChanged(QString const&);
Q_SLOT void on_add_macro_line_edit_editingFinished ();
Q_SLOT void delete_macro ();
void delete_selected_macros (QModelIndexList);
@@ -529,6 +533,11 @@ private:
// configuration fields that we publish
QString my_callsign_;
QString my_grid_;
QString my_station_;
int my_dBm_;
QString my_qth_;
int callsign_aging_;
int activity_aging_;
QColor color_CQ_;
QColor next_color_CQ_;
QColor color_MyCall_;
@@ -548,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_;
@@ -658,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_;}
@@ -817,6 +828,30 @@ QString Configuration::my_grid() const
return the_grid;
}
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_;
}
int Configuration::callsign_aging() const
{
return m_->callsign_aging_;
}
int Configuration::activity_aging() const
{
return m_->activity_aging_;
}
void Configuration::set_location (QString const& grid_descriptor)
{
// change the dynamic grid
@@ -955,6 +990,8 @@ Configuration::impl::impl (Configuration * self, QDir const& temp_directory,
ui_->callsign_line_edit->setValidator (new CallsignValidator {this});
ui_->grid_line_edit->setValidator (new MaidenheadLocatorValidator {this});
ui_->add_macro_line_edit->setValidator (new QRegExpValidator {message_alphabet, this});
ui_->station_message_line_edit->setValidator (new QRegExpValidator {message_alphabet, this});
ui_->qth_message_line_edit->setValidator (new QRegExpValidator {message_alphabet, this});
ui_->udp_server_port_spin_box->setMinimum (1);
ui_->udp_server_port_spin_box->setMaximum (std::numeric_limits<port_type>::max ());
@@ -1125,10 +1162,49 @@ 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_);
ui_->grid_line_edit->setText (my_grid_);
ui_->callsign_aging_spin_box->setValue(callsign_aging_);
ui_->activity_aging_spin_box->setValue(activity_aging_);
ui_->station_message_line_edit->setText (my_station_.toUpper());
ui_->qth_message_line_edit->setText (my_qth_.toUpper());
ui_->use_dynamic_grid->setChecked(use_dynamic_grid_);
ui_->labCQ->setStyleSheet(QString("background: %1").arg(color_CQ_.name()));
ui_->labMyCall->setStyleSheet(QString("background: %1").arg(color_MyCall_.name()));
@@ -1147,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_);
@@ -1247,6 +1324,11 @@ 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();
next_color_CQ_ = color_CQ_ = settings_->value("colorCQ","#66ff66").toString();
next_color_MyCall_ = color_MyCall_ = settings_->value("colorMyCall","#ff6666").toString();
next_color_TxMsg_ = color_TxMsg_ = settings_->value("colorTxMsg","#ffff00").toString();
@@ -1340,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 ();
@@ -1399,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 ();
@@ -1433,6 +1516,11 @@ 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_);
settings_->setValue("colorCQ",color_CQ_);
settings_->setValue("colorMyCall",color_MyCall_);
settings_->setValue("colorTxMsg",color_TxMsg_);
@@ -1471,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_);
@@ -1884,6 +1973,11 @@ 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();
spot_to_psk_reporter_ = ui_->psk_reporter_check_box->isChecked ();
id_interval_ = ui_->CW_id_interval_spin_box->value ();
ntrials_ = ui_->sbNtrials->value ();
@@ -1893,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 ());
@@ -2167,6 +2262,22 @@ void Configuration::impl::on_sound_output_combo_box_currentTextChanged (QString
default_audio_output_device_selected_ = QAudioDeviceInfo::defaultOutputDevice ().deviceName () == text;
}
void Configuration::impl::on_station_message_line_edit_textChanged(QString const &text)
{
QString upper = text.toUpper();
if(text != upper){
ui_->station_message_line_edit->setText (upper);
}
}
void Configuration::impl::on_qth_message_line_edit_textChanged(QString const &text)
{
QString upper = text.toUpper();
if(text != upper){
ui_->qth_message_line_edit->setText (upper);
}
}
void Configuration::impl::on_add_macro_line_edit_editingFinished ()
{
ui_->add_macro_line_edit->setText (ui_->add_macro_line_edit->text ().toUpper ());
+6
View File
@@ -96,6 +96,11 @@ 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;
QFont text_font () const;
QFont decoded_text_font () const;
qint32 id_interval () const;
@@ -108,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;
+616 -444
View File
File diff suppressed because it is too large Load Diff
+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)
+1 -1
View File
@@ -1,6 +1,6 @@
# Version number components
set (WSJTX_VERSION_MAJOR 0)
set (WSJTX_VERSION_MINOR 1)
set (WSJTX_VERSION_MINOR 3)
set (WSJTX_VERSION_PATCH 0)
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
+88 -16
View File
@@ -52,36 +52,108 @@ DecodedText::DecodedText (QString const& the_string, bool contest_mode, QString
, contest_mode_
, grid_c_string.constData ()
, 22, 6);
// We're only going to unpack standard messages for CQs && beacons...
// TODO: jsherer - this is a hack for now...
if(is_standard_){
is_standard_ = QRegularExpression("^(CQ|DE|QRZ)\\s").match(message_).hasMatch();
}
}
if(!is_standard_){
tryUnpackDirected();
}
tryUnpack();
}
void DecodedText::tryUnpackDirected(){
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;
return false;
}
QStringList parts = Varicode::unpackDirectedMessage(m);
QStringList parts = Varicode::unpackCompoundMessage(m);
if(parts.isEmpty()){
return;
if(parts.isEmpty() || parts.length() < 2){
return false;
}
if(parts.length() == 3){
// replace it with the correct unpacked (query)
message_ = QString("%1: %2%3").arg(parts.at(0), parts.at(1), parts.at(2));
} else {
// replace it with the correct unpacked (freetext)
message_ = QString(parts.join(QChar()));
}
compound_ = QString("%1/%2").arg(parts.at(0), parts.at(1));
message_ = QString("%1: ").arg(compound_);
return true;
}
directed_ = parts;
bool DecodedText::tryUnpackDirected(){
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::unpackDirectedMessage(m);
if(parts.isEmpty()){
return false;
}
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));
} 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));
} else {
// replace it with the correct unpacked (freetext)
message_ = QString(parts.join(""));
}
directed_ = parts;
return true;
}
bool DecodedText::tryUnpackData(){
QString m = message().trimmed();
// data frames calls will always be 12+ chars and contain no spaces.
if(m.length() < 12 || m.contains(' ')){
return false;
}
QString data = Varicode::unpackDataMessage(m);
if(data.isEmpty()){
return false;
}
message_ = data;
return true;
}
QStringList DecodedText::messageWords () const
+10 -2
View File
@@ -31,11 +31,18 @@ class DecodedText
{
public:
explicit DecodedText (QString const& message, bool, QString const& my_grid);
explicit DecodedText (QString const& ft8callmessage);
void tryUnpackDirected();
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_; }
@@ -84,6 +91,7 @@ private:
column_mode = 19,
column_qsoText = 22 };
QString compound_;
QStringList directed_;
QString string_;
int padding_;
+3 -1
View File
@@ -423,7 +423,9 @@ subroutine packbits(dbits,nsymd,m0,sym)
itype=1
if(bcontest) then
call to_contest_msg(msg0,msg)
!call to_contest_msg(msg0,msg)
! this causes problems with freetext ala, KN4CRD DE KN4CRD -13 R
msg=msg0
else
msg=msg0
end if
+8 -2
View File
@@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>377</width>
<height>257</height>
<width>600</width>
<height>285</height>
</rect>
</property>
<property name="sizePolicy">
@@ -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">
+947 -289
View File
File diff suppressed because it is too large Load Diff
+55 -19
View File
@@ -116,13 +116,15 @@ 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);
void addMessageText(QString text);
int logRxTxMessageText(QDateTime date, QString text, int freq, bool tx, int block=-1);
void addMessageText(QString text, bool clear=false);
void resetMessage();
void resetMessageUI();
void createMessage(QString const& text);
@@ -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();
@@ -236,10 +239,9 @@ private slots:
void on_rbFreeText_clicked(bool checked);
void on_clearAction_triggered(QObject * sender);
void on_cqMacroButton_clicked();
void on_deMacroButton_clicked();
void on_replyMacroButton_clicked();
void on_qtcMacroButton_clicked();
void on_qthMacroButton_clicked();
void on_snrMacroButton_clicked();
void buildQueryMenu(QMenu *);
void on_queryButton_pressed();
void on_macrosMacroButton_pressed();
void on_tableWidgetRXAll_cellClicked(int row, int col);
@@ -252,14 +254,17 @@ private slots:
void on_nextFreeTextMsg_currentTextChanged (QString const&);
void on_extFreeTextMsg_currentTextChanged (QString const&);
void on_extFreeTextMsgEdit_currentTextChanged (QString const&);
QStringList buildFT8MessageFrames(QString const& text);
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();
@@ -636,6 +641,7 @@ private:
struct CallDetail
{
QString call;
QString through;
QString grid;
int freq;
QDateTime utcTimestamp;
@@ -646,44 +652,51 @@ private:
{
QString from;
QString to;
QString command;
QString cmd;
int freq;
QDateTime utcTimestamp;
int snr;
QString text;
};
struct ActivityDetail
{
bool isFree;
bool isLowConfidence;
QString firstCall;
QString secondCall;
bool isCompound;
int bits;
int freq;
QString text;
QDateTime utcTimestamp;
int snr;
};
struct RXDetail
{
bool isFree;
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;
@@ -692,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;
@@ -748,6 +762,7 @@ private:
void rigFailure (QString const& reason);
void pskSetLocal ();
void pskPost(DecodedText const& decodedtext);
void pskLogReport(QString mode, int offset, int snr, QString callsign, QString grid);
void displayDialFrequency ();
void transmitDisplay (bool);
void processMessage(DecodedText const&, Qt::KeyboardModifiers = 0);
@@ -827,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[],
+575 -405
View File
File diff suppressed because it is too large Load Diff
+612 -136
View File
@@ -30,93 +30,298 @@
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
{"?", 0 }, // query snr
{"$", 1 }, // query stations heard
{"@", 2 }, // query qth
{"&", 3 }, // query station message
{"|", 4 }, // relay message
{":+", 5 }, // report +snr
{":-", 6 }, // report -snr
{":ACK", 7 }, // ack message
{":NACK", 8 }, // nack message
// ...
{" ", 31 }, // send freetext
// directed queries
{"?", 0 }, // query ack
{"@", 1 }, // query qth
{"&", 2 }, // query station message
{"$", 3 }, // query station(s) heard
{"^", 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
{" YES", 27 }, // confirm
{" 73", 28 }, // best regards, end of contact
{" RR", 29 }, // confirm message
{" AGN?", 30 }, // repeat message
{" ", 31 }, // send freetext
};
QSet<int> allowed_cmds = {0, 2, 31};
QSet<int> allowed_cmds = {0, 1, 2, 3, 4, 5, 6, 7, 23, 24, 25, 26, 27, 28, 29, 30, 31};
QRegularExpression directed_re(R"(^(?:(?<from>[A-Z0-9/]+):\s?)?(?<to>[A-Z0-9/]+)(?<cmd>([?$@&| ]|:N?ACK|:[-+])))");
QSet<int> buffered_cmds = {6, 7};
QMap<QChar, QString> huff = {
QRegularExpression directed_re("^"
"(?<to>[A-Z0-9/]+)"
"(?<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> hufftable = {
// char code weight
{' ' , "001" }, // 1300
{'E' , "000" }, // 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 = {
{ "CQ DX", nbasecall + 1 },
{ "<....>", nbasecall + 1 }, // incomplete callsign
{ "CQCQCQ", nbasecall + 2 },
{ "ALLCALL", nbasecall + 3 },
};
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
};
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();
qSort(values);
foreach(auto mw, values){
if(mw < mwatts){ continue; }
dbm = dbm2mw.key(mw);
break;
}
return dbm;
}
int dbmTomwatts(int dbm){
if(dbm2mw.contains(dbm)){
return dbm2mw[dbm];
}
auto iter = dbm2mw.lowerBound(dbm);
if(iter == dbm2mw.end()){
return dbm2mw.last();
}
return iter.value();
}
/*
* VARICODE
*/
QString Varicode::formatSNR(int snr){
if(snr < -60 || snr > 60){
return QString();
}
return QString("%1%2").arg(snr >= 0 ? "+" : "").arg(snr, snr < 0 ? 3 : 2, 10, QChar('0'));
}
QString Varicode::formatPWR(int dbm){
if(dbm < 0 || dbm > 60){
return QString();
}
int mwatts = dbmTomwatts(dbm);
if(mwatts < 1000){
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();
@@ -151,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){
@@ -164,28 +369,22 @@ 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(QMap<QChar, QString> const &huff, QVector<bool> const& bitvec, int pad){
QString text;
QString Varicode::huffDecode(QVector<bool> const& bitvec){
QString out;
QString bits = bitsToStr(bitvec);
QString bits = bitsToStr(bitvec).mid(0, bitvec.length()-pad);
// TODO: jsherer - this is naive...
while(bits.length() > 0){
bool found = false;
foreach(auto key, huff.keys()){
if(bits.startsWith(huff[key])){
if(key == huffeot){
if(key == EOT){
text.append(" ");
found = false;
break;
}
out.append(key);
text.append(key);
bits = bits.mid(huff[key].length());
found = true;
}
@@ -195,9 +394,42 @@ QString Varicode::huffDecode(QVector<bool> const& bitvec){
}
}
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;
@@ -259,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;
@@ -310,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;
@@ -356,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()){
@@ -481,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...
@@ -505,50 +794,162 @@ QString Varicode::unpackGrid(quint16 value){
return deg2grid(dlong, dlat).left(4);
}
QString Varicode::packDirectedMessage(const QString &text, const QString &callsign, int *n){
bool Varicode::isCommandAllowed(const QString &cmd){
return directed_cmds.contains(cmd) && allowed_cmds.contains(directed_cmds[cmd]);
}
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;
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);
if(packed_base == 0 || packed_fix == 0){
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){
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()){
QString from = match.captured("from");
if(from.isEmpty()){
from = callsign;
}
QString to = match.captured("to");
QString cmd = match.captured("cmd");
bool validToCallsign = basecalls.contains(to) || QRegularExpression(callsign_pattern2).match(to).hasMatch();
if(!validToCallsign || !directed_cmds.contains(cmd) || !allowed_cmds.contains(directed_cmds[cmd])){
*n = 0;
return frame;
}
auto fromBytes = from.toLocal8Bit();
auto fromCRC = CRC::Calculate(fromBytes.data(), fromBytes.length(), CRC::CRC_5_ITU());
quint8 packed_flag = 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 = fromCRC;
// [3][28][28][5],[5] = 69
auto bits = (
Varicode::intToBits(packed_flag, 3) +
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(!match.hasMatch()){
if(n) *n = 0;
return frame;
}
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){
@@ -558,26 +959,101 @@ QStringList Varicode::unpackDirectedMessage(const QString &text){
return unpacked;
}
// [3][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));
quint8 flag = Varicode::bitsToInt(Varicode::strToBits(bits.left(3)));
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 != 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();
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 = (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"]){
unpacked.append(Varicode::formatPWR(num + 30));
} else if(packed_cmd == directed_cmds[" SNR"]) {
unpacked.append(Varicode::formatSNR(num));
} else {
unpacked.append(QString("%1").arg(num));
}
}
return unpacked;
}
QString Varicode::packDataMessage(const QString &input, QString * out, int *n){
QString frame;
// [1][63],[5] = 69
quint8 is_data = 1;
auto frameBits = (
Varicode::intToBits(is_data, 1)
);
int i = 0;
// 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++;
continue;
}
break;
}
int pad = 64 - frameBits.length();
if(pad){
frameBits += Varicode::intToBits(0, pad);
}
frame = Varicode::pack64bits(Varicode::bitsToInt(frameBits)) + Varicode::pack5bits(pad % 32);
*n = i;
return frame;
}
QString Varicode::unpackDataMessage(const QString &text){
QString unpacked;
if(text.length() < 13){
return unpacked;
}
auto bits = Varicode::intToBits(Varicode::unpack64bits(text.left(12)), 64);
quint8 pad = Varicode::unpack5bits(text.right(1));
quint8 is_data = (int)bits.at(0);
if(is_data != 1){
return unpacked;
}
// pop off the is_data bit
bits.removeAt(0);
// huff decode the bits (without escapes)
unpacked = Varicode::huffDecode(hufftable, bits, pad);
// then... unescape special characters
unpacked = Varicode::huffUnescape(unpacked);
return unpacked;
}
+44 -5
View File
@@ -11,18 +11,41 @@
#include <QString>
#include <QVector>
class Varicode
{
public:
enum FrameType{
FT8 = 0, // [000]
FT8Fox = 1, // [001]
FT8Call = 2, // [010]
FT8CallLast = 3, // [011] <- used to indicate last frame in transmission
FT8CallReservedA = 4, // [100]
FT8CallReservedB = 5, // [101]
FT8CallReservedC = 6, // [110]
FT8CallReservedD = 7, // [111]
};
//Varicode();
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);
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);
@@ -31,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);
@@ -44,14 +71,26 @@ 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);
static quint16 packGrid(QString const& value);
static QString unpackGrid(quint16 value);
static QString packDirectedMessage(QString const& text, QString const& callsign, int *n);
static bool isCommandAllowed(const QString &cmd);
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, QString *out, int *n);
static QString unpackDataMessage(QString const& text);
};
#endif // VARICODE_H