Compare commits

...

31 Commits

Author SHA1 Message Date
Jordan Sherer 1629415dc1 Added spotting of messages directed to @JS8NET 2019-03-25 15:05:13 -04:00
Jordan Sherer 08cf869125 Added by.grid to SpotClient spots 2019-03-25 11:58:23 -04:00
Jordan Sherer 03bd44ae39 Updated SpotClient to send local data at least once every 5 minutes 2019-03-25 09:52:35 -04:00
Jordan Sherer 14626978c0 Updated heartbeat ack labeling 2019-03-24 22:37:18 -04:00
Jordan Sherer dd43f0db84 Changed my mind. HB ACKs should include SNR 2019-03-24 22:25:36 -04:00
Jordan Sherer 5646268faf Updated configuration reporting labeling to include JS8 Network 2019-03-24 22:19:24 -04:00
Jordan Sherer 5d21fdf1dc Added spotting to spot.js8call.com for future network data 2019-03-24 21:01:03 -04:00
Jordan Sherer 5d2ba76c17 Added explicit setting for automatically acknowledging heartbeat messages 2019-03-24 13:41:35 -04:00
Jordan Sherer 7bd86ca177 Fixed #94: swap heard graph on band change 2019-03-24 09:59:24 -04:00
Jordan Sherer 59b1a3b011 A few better tooltips 2019-03-24 09:26:22 -04:00
Jordan Sherer ce2c045458 Added click of callsign to open profile 2019-03-23 22:16:01 -04:00
Jordan Sherer 218c5b3d47 Fixed #142: callsign selected is an indicator for qso start time 2019-03-23 22:03:28 -04:00
Jordan Sherer 27ae28a889 Fixed #148: configuration tab ordering 2019-03-23 21:58:30 -04:00
Jordan Sherer 1a2596224a Bump to RC3 2019-03-23 21:48:15 -04:00
Jordan Sherer 82915540b4 Change SNR for HB ACK. Default to HB hidden 2019-03-23 21:42:51 -04:00
Jordan Sherer 73c6dd50fb Only show CQ icon for 5 minutes 2019-03-23 21:29:26 -04:00
Jordan Sherer ee48d8fd86 Added telephone icon for CQs. Added tooltip for last logged date 2019-03-23 21:19:24 -04:00
Jordan Sherer d8a16f4a42 Removed extraneous logbook debug statements 2019-03-23 11:42:31 -04:00
Jordan Sherer 5ef440faf6 Merged patch to fix #97 2019-03-21 21:14:23 -04:00
Jordan Sherer 9d9ae62526 Moved wsjtx qt creator to js8call 2019-03-21 21:01:58 -04:00
Jordan Sherer 6b9c9b4ceb Bump eol 2019-03-11 19:06:38 -04:00
Jordan Sherer 1febb18495 Added log details to the call activity menu 2019-03-10 23:48:56 -04:00
Jordan Sherer 43401c3c26 Added DIT DIT short message 2019-03-04 23:25:17 -05:00
Jordan Sherer 55928be661 Added log details column to call activity. TODO: add log details :P 2019-03-03 23:43:39 -05:00
Jordan Sherer a5f8593b3d Added NACK command for future implementation 2019-03-03 23:33:30 -05:00
Jordan Sherer 07f1594d0a Fixed #129: relays now use 'VIA' as the intermediate text for relay path computation. Fixed also relays still being ACKed while relay messaging was disabled. 2019-02-25 22:25:04 -05:00
Jordan Sherer b6dd6aadc8 Added a dummy data for testing newlines 2019-02-25 10:31:11 -05:00
Jordan Sherer b9b334c6cf Added tooltip with absolute date for age columns 2019-02-25 09:57:01 -05:00
Jordan Sherer fec45aa0ad Bump to RC2 2019-02-23 22:53:39 -05:00
Jordan Sherer 2ef4e90710 Fixed #135: query commands through relay. Changed the QUERY CALL reply format so as to not fconfuse folks with random ACKs. Cleaned up empty dB formatting in the call and band activity tables. 2019-02-23 22:49:02 -05:00
Jordan Sherer a0dc7bc013 Fixed #76: added tone output to API 2019-02-23 09:50:02 -05:00
21 changed files with 700 additions and 174 deletions
+1
View File
@@ -243,6 +243,7 @@ set (wsjtx_CXXSRCS
messagereplydialog.cpp
keyeater.cpp
APRSISClient.cpp
SpotClient.cpp
Inbox.cpp
messagewindow.cpp
mainwindow.cpp
+109 -43
View File
@@ -23,7 +23,7 @@
<string>Select tab to change configuration parameters.</string>
</property>
<property name="currentIndex">
<number>0</number>
<number>3</number>
</property>
<widget class="QWidget" name="general_tab">
<attribute name="title">
@@ -88,6 +88,16 @@
<string>Station Details</string>
</property>
<layout class="QGridLayout" name="gridLayout_13">
<item row="0" column="0">
<widget class="QLabel" name="callsign_label">
<property name="text">
<string>My Callsign:</string>
</property>
<property name="buddy">
<cstring>callsign_line_edit</cstring>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="grid_label">
<property name="text">
@@ -121,16 +131,6 @@
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="callsign_label">
<property name="text">
<string>My Callsign:</string>
</property>
<property name="buddy">
<cstring>callsign_line_edit</cstring>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="groups_label">
<property name="text">
@@ -278,7 +278,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>724</width>
<width>615</width>
<height>646</height>
</rect>
</property>
@@ -2339,7 +2339,7 @@ This is used for reverse ping analysis which is very useful
for assessing propagation and system performance.</string>
</property>
<property name="text">
<string>Enable spotting to reporting networks (PSKReporter &amp;&amp; APRS-IS)</string>
<string>Enable spotting to reporting networks (JS8 Network, PSKReporter, APRS-IS, etc)</string>
</property>
<property name="checked">
<bool>true</bool>
@@ -3983,71 +3983,137 @@ soundcard changes</string>
</widget>
<tabstops>
<tabstop>configuration_tabs</tabstop>
<tabstop>use_dynamic_grid</tabstop>
<tabstop>type_2_msg_gen_combo_box</tabstop>
<tabstop>monitor_last_used_check_box</tabstop>
<tabstop>quick_call_check_box</tabstop>
<tabstop>CW_id_after_73_check_box</tabstop>
<tabstop>enable_VHF_features_check_box</tabstop>
<tabstop>single_decode_check_box</tabstop>
<tabstop>decode_at_52s_check_box</tabstop>
<tabstop>CW_id_interval_spin_box</tabstop>
<tabstop>tabWidget_2</tabstop>
<tabstop>callsign_line_edit</tabstop>
<tabstop>grid_line_edit</tabstop>
<tabstop>groups_line_edit</tabstop>
<tabstop>avoid_allcall_checkbox</tabstop>
<tabstop>cq_message_line_edit</tabstop>
<tabstop>reply_message_line_edit</tabstop>
<tabstop>info_message_line_edit</tabstop>
<tabstop>scrollArea_2</tabstop>
<tabstop>miles_check_box</tabstop>
<tabstop>monitor_off_check_box</tabstop>
<tabstop>tx_qsy_check_box</tabstop>
<tabstop>transmit_directed_check_box</tabstop>
<tabstop>heartbeat_anywhere_check_box</tabstop>
<tabstop>heartbeat_qso_pause_check_box</tabstop>
<tabstop>spellcheck_check_box</tabstop>
<tabstop>autoreply_off_check_box</tabstop>
<tabstop>relay_disabled_check_box</tabstop>
<tabstop>auto_whitelist_line_edit</tabstop>
<tabstop>auto_blacklist_line_edit</tabstop>
<tabstop>scrollArea</tabstop>
<tabstop>callsign_aging_spin_box</tabstop>
<tabstop>activity_aging_spin_box</tabstop>
<tabstop>tx_watchdog_spin_box</tabstop>
<tabstop>rig_combo_box</tabstop>
<tabstop>CAT_poll_interval_spin_box</tabstop>
<tabstop>tabWidget_3</tabstop>
<tabstop>scrollArea_3</tabstop>
<tabstop>CAT_port_combo_box</tabstop>
<tabstop>CAT_serial_baud_combo_box</tabstop>
<tabstop>CAT_default_bit_radio_button</tabstop>
<tabstop>CAT_7_bit_radio_button</tabstop>
<tabstop>CAT_8_bit_radio_button</tabstop>
<tabstop>CAT_default_stop_bit_radio_button</tabstop>
<tabstop>CAT_one_stop_bit_radio_button</tabstop>
<tabstop>CAT_two_stop_bit_radio_button</tabstop>
<tabstop>CAT_handshake_default_radio_button</tabstop>
<tabstop>CAT_handshake_none_radio_button</tabstop>
<tabstop>CAT_handshake_xon_radio_button</tabstop>
<tabstop>CAT_handshake_hardware_radio_button</tabstop>
<tabstop>force_DTR_combo_box</tabstop>
<tabstop>force_RTS_combo_box</tabstop>
<tabstop>scrollArea_4</tabstop>
<tabstop>PTT_VOX_radio_button</tabstop>
<tabstop>PTT_CAT_radio_button</tabstop>
<tabstop>PTT_DTR_radio_button</tabstop>
<tabstop>PTT_CAT_radio_button</tabstop>
<tabstop>PTT_RTS_radio_button</tabstop>
<tabstop>PTT_port_combo_box</tabstop>
<tabstop>mode_USB_radio_button</tabstop>
<tabstop>mode_none_radio_button</tabstop>
<tabstop>mode_data_radio_button</tabstop>
<tabstop>TX_source_data_radio_button</tabstop>
<tabstop>TX_source_mic_radio_button</tabstop>
<tabstop>mode_none_radio_button</tabstop>
<tabstop>mode_USB_radio_button</tabstop>
<tabstop>mode_data_radio_button</tabstop>
<tabstop>split_none_radio_button</tabstop>
<tabstop>split_rig_radio_button</tabstop>
<tabstop>split_emulate_radio_button</tabstop>
<tabstop>split_rig_radio_button</tabstop>
<tabstop>split_none_radio_button</tabstop>
<tabstop>sbTxDelay</tabstop>
<tabstop>ptt_command_line_edit</tabstop>
<tabstop>test_CAT_push_button</tabstop>
<tabstop>test_PTT_push_button</tabstop>
<tabstop>scrollArea_5</tabstop>
<tabstop>sound_output_combo_box</tabstop>
<tabstop>sound_input_combo_box</tabstop>
<tabstop>sound_input_channel_combo_box</tabstop>
<tabstop>sound_output_combo_box</tabstop>
<tabstop>sound_output_channel_combo_box</tabstop>
<tabstop>save_path_select_push_button</tabstop>
<tabstop>azel_path_select_push_button</tabstop>
<tabstop>checkBoxPwrBandTxMemory</tabstop>
<tabstop>checkBoxPwrBandTuneMemory</tabstop>
<tabstop>add_macro_line_edit</tabstop>
<tabstop>add_macro_push_button</tabstop>
<tabstop>delete_macro_push_button</tabstop>
<tabstop>macros_list_view</tabstop>
<tabstop>prompt_to_log_check_box</tabstop>
<tabstop>scrollArea_6</tabstop>
<tabstop>opCallEntry</tabstop>
<tabstop>log_as_RTTY_check_box</tabstop>
<tabstop>udp_server_line_edit</tabstop>
<tabstop>udp_server_port_spin_box</tabstop>
<tabstop>udpEnable</tabstop>
<tabstop>accept_udp_requests_check_box</tabstop>
<tabstop>udpWindowToFront</tabstop>
<tabstop>udpWindowRestore</tabstop>
<tabstop>use_dynamic_grid</tabstop>
<tabstop>psk_reporter_check_box</tabstop>
<tabstop>aprs_server_line_edit</tabstop>
<tabstop>aprs_server_port_spin_box</tabstop>
<tabstop>aprs_passcode_line_edit</tabstop>
<tabstop>enable_n1mm_broadcast_check_box</tabstop>
<tabstop>n1mm_server_name_line_edit</tabstop>
<tabstop>n1mm_server_port_spin_box</tabstop>
<tabstop>scrollArea_7</tabstop>
<tabstop>calibration_slope_ppm_spin_box</tabstop>
<tabstop>calibration_intercept_spin_box</tabstop>
<tabstop>frequencies_table_view</tabstop>
<tabstop>auto_switch_bands_check_box</tabstop>
<tabstop>stations_table_view</tabstop>
<tabstop>sbNtrials</tabstop>
<tabstop>sbAggressive</tabstop>
<tabstop>cbTwoPass</tabstop>
<tabstop>add_macro_push_button</tabstop>
<tabstop>add_macro_line_edit</tabstop>
<tabstop>delete_macro_push_button</tabstop>
<tabstop>macros_list_view</tabstop>
<tabstop>sound_am_path_test_push_button</tabstop>
<tabstop>sound_dm_path_reset_push_button</tabstop>
<tabstop>sound_am_path_reset_push_button</tabstop>
<tabstop>sound_dm_path_select_push_button</tabstop>
<tabstop>sound_am_path_select_push_button</tabstop>
<tabstop>sound_dm_path_test_push_button</tabstop>
<tabstop>sound_cq_path_select_push_button</tabstop>
<tabstop>sound_cq_path_test_push_button</tabstop>
<tabstop>sound_cq_path_reset_push_button</tabstop>
<tabstop>font_push_button</tabstop>
<tabstop>tabWidget</tabstop>
<tabstop>scrollArea_8</tabstop>
<tabstop>tableForegroundButton</tabstop>
<tabstop>pbCQmsg</tabstop>
<tabstop>tableFontButton</tabstop>
<tabstop>pbMyCall</tabstop>
<tabstop>tableSelectedRowBackgroundButton</tabstop>
<tabstop>tableBackgroundButton</tabstop>
<tabstop>scrollArea_9</tabstop>
<tabstop>txFontButton</tabstop>
<tabstop>rxFontButton</tabstop>
<tabstop>txForegroundButton</tabstop>
<tabstop>rxBackgroundButton</tabstop>
<tabstop>rxForegroundButton</tabstop>
<tabstop>scrollArea_10</tabstop>
<tabstop>composeBackgroundButton</tabstop>
<tabstop>composeForegroundButton</tabstop>
<tabstop>composeFontButton</tabstop>
<tabstop>sbDegrade</tabstop>
<tabstop>sbBandwidth</tabstop>
<tabstop>cbx2ToneSpacing</tabstop>
<tabstop>cbx4ToneSpacing</tabstop>
<tabstop>sbNtrials</tabstop>
<tabstop>sbAggressive</tabstop>
<tabstop>cbTwoPass</tabstop>
<tabstop>cbFox</tabstop>
<tabstop>cbHound</tabstop>
</tabstops>
<resources/>
<connections>
@@ -4117,12 +4183,12 @@ soundcard changes</string>
</connection>
</connections>
<buttongroups>
<buttongroup name="CAT_data_bits_button_group"/>
<buttongroup name="TX_mode_button_group"/>
<buttongroup name="TX_audio_source_button_group"/>
<buttongroup name="CAT_stop_bits_button_group"/>
<buttongroup name="CAT_handshake_button_group"/>
<buttongroup name="split_mode_button_group"/>
<buttongroup name="PTT_method_button_group"/>
<buttongroup name="CAT_data_bits_button_group"/>
<buttongroup name="TX_audio_source_button_group"/>
<buttongroup name="CAT_handshake_button_group"/>
<buttongroup name="TX_mode_button_group"/>
</buttongroups>
</ui>
+124
View File
@@ -0,0 +1,124 @@
#include "SpotClient.h"
#include "Message.h"
#include "moc_SpotClient.cpp"
SpotClient::SpotClient(MessageClient *client, QObject *parent):
QObject(parent),
m_client { client }
{
prepare();
connect(&m_timer, &QTimer::timeout, this, &SpotClient::processSpots);
m_timer.setInterval(60 * 1000);
m_timer.setSingleShot(false);
m_timer.start();
}
void SpotClient::prepare(){
QHostInfo::lookupHost("spot.js8call.com", this, SLOT(dnsLookupResult(QHostInfo)));
}
void SpotClient::dnsLookupResult(QHostInfo info){
if (info.addresses().isEmpty()) {
qDebug() << "SpotClient Error:" << info.errorString();
return;
}
m_address = info.addresses().at(0);
qDebug() << "SpotClient Resolve:" << m_address.toString();
}
void SpotClient::setLocalStation(QString callsign, QString grid, QString info, QString version){
bool changed = false;
if(m_call != callsign){
m_call = callsign;
changed = true;
}
if(m_grid != grid){
m_grid = grid;
changed = true;
}
if(m_info != info){
m_info = info;
changed = true;
}
if(m_version != version){
m_version = version;
changed = true;
}
// send local information to network on change, or once every 5 minutes
if(changed || m_seq % 5 == 0){
enqueueLocalSpot(callsign, grid, info, version);
}
}
void SpotClient::enqueueLocalSpot(QString callsign, QString grid, QString info, QString version){
auto m = Message("RX.LOCAL", "", {
{"CALLSIGN", QVariant(callsign)},
{"GRID", QVariant(grid)},
{"INFO", QVariant(info)},
{"VERSION", QVariant(version)},
});
m_queue.enqueue(m.toJson());
}
void SpotClient::enqueueSpot(QString callsign, QString grid, int frequency, int snr){
auto m = Message("RX.SPOT", "", {
{"BY", QVariant(QMap<QString, QVariant>{
{"CALLSIGN", QVariant(m_call)},
{"GRID", QVariant(m_grid)},
})},
{"CALLSIGN", QVariant(callsign)},
{"GRID", QVariant(grid)},
{"FREQ", QVariant(frequency)},
{"SNR", QVariant(snr)},
});
m_queue.enqueue(m.toJson());
}
void SpotClient::enqueueCmd(QString cmd, QString from, QString to, QString relayPath, QString text, QString grid, QString extra, int frequency, int snr){
auto m = Message("RX.DIRECTED", "", {
{"BY", QVariant(QMap<QString, QVariant>{
{"CALLSIGN", QVariant(m_call)},
{"GRID", QVariant(m_grid)},
})},
{"CMD", QVariant(cmd)},
{"FROM", QVariant(from)},
{"TO", QVariant(to)},
{"PATH", QVariant(relayPath)},
{"TEXT", QVariant(text)},
{"GRID", QVariant(grid)},
{"EXTRA", QVariant(extra)},
{"FREQ", QVariant(frequency)},
{"SNR", QVariant(snr)},
});
m_queue.enqueue(m.toJson());
}
void SpotClient::processSpots(){
if(m_address.isNull()){
prepare();
return;
}
while(!m_queue.isEmpty()){
sendRawSpot(m_queue.dequeue());
}
m_seq++;
}
void SpotClient::sendRawSpot(QByteArray payload){
if(!m_address.isNull()){
m_client->send_raw_datagram(payload, m_address, 50000);
}
}
+41
View File
@@ -0,0 +1,41 @@
#ifndef JS8SPOTCLIENT_H
#define JS8SPOTCLIENT_H
#include "MessageClient.hpp"
#include <QObject>
#include <QHostInfo>
#include <QQueue>
#include <QTimer>
class SpotClient : public QObject
{
Q_OBJECT
public:
SpotClient(MessageClient *client, QObject *parent = nullptr);
void prepare();
void setLocalStation(QString callsign, QString grid, QString info, QString version);
void enqueueLocalSpot(QString callsign, QString grid, QString info, QString version);
void enqueueCmd(QString cmd, QString from, QString to, QString relayPath, QString text, QString grid, QString extra, int frequency, int snr);
void enqueueSpot(QString callsign, QString grid, int frequency, int snr);
void sendRawSpot(QByteArray payload);
public slots:
void processSpots();
void dnsLookupResult(QHostInfo);
private:
int m_seq;
QString m_call;
QString m_grid;
QString m_info;
QString m_version;
QHostAddress m_address;
MessageClient *m_client;
QTimer m_timer;
QQueue<QByteArray> m_queue;
};
#endif // JS8SPOTCLIENT_H
+1 -1
View File
@@ -2,5 +2,5 @@
set (WSJTX_VERSION_MAJOR 1)
set (WSJTX_VERSION_MINOR 0)
set (WSJTX_VERSION_PATCH 0)
set (WSJTX_RC 1) # release candidate number, comment out or zero for development versions
set (WSJTX_RC 3) # release candidate number, comment out or zero for development versions
set (WSJTX_VERSION_IS_RELEASE 0) # set to 1 for final release build
+4 -2
View File
@@ -81,7 +81,8 @@ SOURCES += \
jsc_checker.cpp \
Message.cpp \
Inbox.cpp \
messagewindow.cpp
messagewindow.cpp \
SpotClient.cpp
HEADERS += qt_helpers.hpp \
pimpl_h.hpp pimpl_impl.hpp \
@@ -113,7 +114,8 @@ HEADERS += qt_helpers.hpp \
jsc_checker.h \
Message.h \
Inbox.h \
messagewindow.h
messagewindow.h \
SpotClient.h
INCLUDEPATH += qmake_only
+2 -2
View File
@@ -15,7 +15,7 @@ subroutine extractmessage144(decoded,msgreceived,nhashflag,recent_calls,nrecent)
do ibyte=1,10
itmp=0
do ibit=1,8
itmp=ishft(itmp,1)+iand(1,decoded((ibyte-1)*8+ibit))
itmp=ishft(itmp,1)+iand(1_1,decoded((ibyte-1)*8+ibit))
enddo
i1Dec8BitBytes(ibyte)=itmp
enddo
@@ -31,7 +31,7 @@ subroutine extractmessage144(decoded,msgreceived,nhashflag,recent_calls,nrecent)
do ibyte=1,12
itmp=0
do ibit=1,6
itmp=ishft(itmp,1)+iand(1,decoded((ibyte-1)*6+ibit))
itmp=ishft(itmp,1)+iand(1_1,decoded((ibyte-1)*6+ibit))
enddo
i4Dec6BitWords(ibyte)=itmp
enddo
+1 -1
View File
@@ -13,7 +13,7 @@ subroutine chkcrc12a(decoded,nbadcrc)
read(cbits,1002) ncrc12 !Received CRC12
1002 format(75x,b12)
i1Dec8BitBytes(10)=iand(i1Dec8BitBytes(10),128+64+32)
i1Dec8BitBytes(10)=iand(i1Dec8BitBytes(10),transfer(128+64+32,0_1))
i1Dec8BitBytes(11)=0
icrc12=crc12(c_loc(i1Dec8BitBytes),11) !CRC12 computed from 75 msg bits
icrc12=xor(icrc12, 42) ! TODO: jsherer - could change the crc here
+2 -2
View File
@@ -19,7 +19,7 @@ subroutine extractmessage174(decoded,msgreceived,ncrcflag)
read(cbits,1002) ncrc12 !Received CRC12
1002 format(75x,b12)
i1Dec8BitBytes(10)=iand(i1Dec8BitBytes(10),128+64+32)
i1Dec8BitBytes(10)=iand(i1Dec8BitBytes(10),transfer(128+64+32,0_1))
i1Dec8BitBytes(11)=0
icrc12=crc12(c_loc(i1Dec8BitBytes),11) !CRC12 computed from 75 msg bits
icrc12=xor(icrc12, 42) ! TODO: jsherer - could change the crc here
@@ -29,7 +29,7 @@ subroutine extractmessage174(decoded,msgreceived,ncrcflag)
do ibyte=1,12
itmp=0
do ibit=1,6
itmp=ishft(itmp,1)+iand(1,decoded((ibyte-1)*6+ibit))
itmp=ishft(itmp,1)+iand(1_1,decoded((ibyte-1)*6+ibit))
enddo
i4Dec6BitWords(ibyte)=itmp
enddo
+1 -1
View File
@@ -35,7 +35,7 @@ subroutine genft8(msg,mygrid,bcontest,i3bit,msgsent,msgbits,itone)
1000 format(12b6.6,b8.8)
read(cbits,1001) i1Msg8BitBytes(1:10)
1001 format(10b8)
i1Msg8BitBytes(10)=iand(i1Msg8BitBytes(10),128+64+32)
i1Msg8BitBytes(10)=iand(i1Msg8BitBytes(10),transfer(128+64+32,0_1))
i1Msg8BitBytes(11)=0
icrc12=crc12(c_loc(i1Msg8BitBytes),11)
icrc12=xor(icrc12, 42) ! TODO: jsherer - could change the crc here
+1 -1
View File
@@ -94,7 +94,7 @@ allocate ( rxdata(N), llr(N) )
checksum = xor(checksum, 42) ! TODO: jsherer - could change the crc here
! For reference, the next 3 lines show how to check the CRC
i1Msg8BitBytes(10)=checksum/256
i1Msg8BitBytes(11)=iand (checksum,255)
i1Msg8BitBytes(11)=iand(checksum,transfer(255,0_2))
checksumok = crc12_check(c_loc (i1Msg8BitBytes), 11)
if( checksumok ) write(*,*) 'Good checksum'
+1 -1
View File
@@ -95,7 +95,7 @@ do idb = 0, 30
nhashflag=0
imsg=0
do i=1,16
imsg=ishft(imsg,1)+iand(1,decoded(17-i))
imsg=ishft(imsg,1)+iand(1_1,decoded(17-i))
enddo
nrxrpt=iand(imsg,15)
nrxhash=(imsg-nrxrpt)/16
+1 -1
View File
@@ -129,7 +129,7 @@ subroutine msk40decodeframe(c,mycall,hiscall,xsnr,bswl,nhasharray, &
imsg=0
do i=1,16
imsg=ishft(imsg,1)+iand(1,decoded(17-i))
imsg=ishft(imsg,1)+iand(1_1,decoded(17-i))
enddo
nrxrpt=iand(imsg,15)
nrxhash=(imsg-nrxrpt)/16
+15 -6
View File
@@ -101,14 +101,17 @@ void ADIF::load()
, extractField (record, "BAND")
, extractField (record, "MODE")
, extractField (record, "SUBMODE")
, extractField (record, "QSO_DATE"));
, extractField (record, "QSO_DATE")
, extractField (record, "NAME")
, extractField (record, "COMMENT")
);
}
inputFile.close ();
}
}
void ADIF::add(QString const& call, QString const& band, QString const& mode, QString const& submode, QString const& date)
void ADIF::add(QString const& call, QString const& band, QString const& mode, QString const& submode, QString const& date, QString const& name, QString const& comment)
{
QSO q;
q.call = call;
@@ -116,6 +119,9 @@ void ADIF::add(QString const& call, QString const& band, QString const& mode, QS
q.mode = mode;
q.submode = submode;
q.date = date;
q.name = name;
q.comment = comment;
if (q.call.size ())
{
_data.insert(q.call,q);
@@ -135,13 +141,18 @@ bool ADIF::match(QString const& call, QString const& band) const
if ( (band.compare(q.band,Qt::CaseInsensitive) == 0)
|| (band=="")
|| (q.band==""))
{
{
return true;
}
}
}
return false;
}
}
QList<ADIF::QSO> ADIF::find(QString const& call) const
{
return _data.values(call);
}
QList<QString> ADIF::getCallList() const
{
@@ -154,8 +165,6 @@ QList<QString> ADIF::getCallList() const
}
return p;
}
int ADIF::getCount() const
{
+8 -3
View File
@@ -21,10 +21,14 @@ class QDateTime;
class ADIF
{
public:
struct QSO;
void init(QString const& filename);
void load();
void add(QString const& call, QString const& band, QString const& mode, const QString &submode, QString const& date);
void add(QString const& call, QString const& band, QString const& mode, const QString &submode, QString const& date, const QString &name, const QString &comment);
bool match(QString const& call, QString const& band) const;
QList<ADIF::QSO> find(QString const& call) const;
QList<QString> getCallList() const;
int getCount() const;
@@ -38,12 +42,13 @@ class ADIF
, QString const& operator_call);
private:
struct QSO
{
QString call,band,mode,submode,date;
QString call,band,mode,submode,date,name,comment;
};
private:
QMultiHash<QString, QSO> _data;
QString _filename;
+39 -13
View File
@@ -59,25 +59,51 @@ void LogBook::match(/*in*/const QString call,
bool &callWorkedBefore,
bool &countryWorkedBefore) const
{
if (call.length() > 0)
{
QString currentBand = ""; // match any band
callWorkedBefore = _log.match(call,currentBand);
countryName = _countries.find(call);
if(call.isEmpty()){
return;
}
if (countryName.length() > 0) // country was found
QString currentBand = ""; // match any band
callWorkedBefore = _log.match(call, currentBand);
countryName = _countries.find(call);
if (countryName.length() > 0){ // country was found
countryWorkedBefore = _worked.getHasWorked(countryName);
else
{
countryName = "where?"; //error: prefix not found
countryWorkedBefore = false;
}
} else {
countryName = "where?"; //error: prefix not found
countryWorkedBefore = false;
}
}
void LogBook::addAsWorked(const QString call, const QString band, const QString mode, const QString submode, const QString date)
bool LogBook::findCallDetails(
/*in*/
const QString call,
/*out*/
QString &date,
QString &name,
QString &comment) const
{
_log.add(call,band,mode,submode,date);
if(call.isEmpty()){
return false;
}
auto qsos = _log.find(call);
if(qsos.isEmpty()){
return false;
}
foreach(auto qso, qsos){
if(date.isEmpty() && !qso.date.isEmpty()) date = qso.date;
if(name.isEmpty() && !qso.name.isEmpty()) name = qso.name;
if(comment.isEmpty() && !qso.comment.isEmpty()) comment = qso.comment;
}
return true;
}
void LogBook::addAsWorked(const QString call, const QString band, const QString mode, const QString submode, const QString date, const QString name, const QString comment)
{
_log.add(call,band,mode,submode,date,name,comment);
QString countryName = _countries.find(call);
if (countryName.length() > 0)
_worked.setAsWorked(countryName);
+8 -1
View File
@@ -25,7 +25,14 @@ public:
/*out*/ QString &countryName,
bool &callWorkedBefore,
bool &countryWorkedBefore) const;
void addAsWorked(const QString call, const QString band, const QString mode, const QString submode, const QString date);
bool findCallDetails(
/*in*/
const QString call,
/*out*/
QString &date,
QString &name,
QString &comment) const;
void addAsWorked(const QString call, const QString band, const QString mode, const QString submode, const QString date, const QString name, const QString comment);
private:
CountryDat _countries;
+294 -76
View File
@@ -560,6 +560,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
version (), revision (),
m_config.udp_server_name (), m_config.udp_server_port (),
this}},
m_spotClient { new SpotClient{m_messageClient, this}},
m_aprsClient {new APRSISClient{"rotate.aprs2.net", 14580, this}},
psk_Reporter {new PSK_Reporter {m_messageClient, this}},
m_i3bit {0},
@@ -569,6 +570,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
m_txFrameCountEstimate {0},
m_previousFreq {0},
m_hbPaused { false },
m_hbAutoAck { true },
m_hbHidden { false },
m_hbInterval {0},
m_cqInterval {0},
@@ -814,6 +816,15 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
});
ui->labDialFreqOffset->installEventFilter(ldmp);
// Hook up callsign label click to open preferenses
ui->labCallsign->setCursor(QCursor(Qt::PointingHandCursor));
auto clmp = new MousePressEater();
connect(clmp, &MousePressEater::mousePressed, this, [this](QObject *, QMouseEvent * e, bool *pProcessed){
openSettings(0);
if(pProcessed) *pProcessed = true;
});
ui->labCallsign->installEventFilter(clmp);
ui->bandComboBox->setVisible(false);
ui->bandComboBox->setModel (m_config.frequencies ());
ui->bandComboBox->setModelColumn (FrequencyList_v2::frequency_mhz_column);
@@ -1261,7 +1272,11 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
connect(ui->tableWidgetRXAll->horizontalHeader(), &QHeaderView::customContextMenuRequested, this, [this](QPoint const &point){
QMenu * menu = new QMenu(ui->tableWidgetRXAll);
buildBandActivitySortByMenu(menu);
QMenu * sortByMenu = menu->addMenu("Sort By...");
buildBandActivitySortByMenu(sortByMenu);
QMenu * showColumnsMenu = menu->addMenu("Show Columns...");
buildShowColumnsMenu(showColumnsMenu, "band");
menu->popup(ui->tableWidgetRXAll->horizontalHeader()->mapToGlobal(point));
});
@@ -1475,7 +1490,11 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
connect(ui->tableWidgetCalls->horizontalHeader(), &QHeaderView::customContextMenuRequested, this, [this](QPoint const &point){
QMenu * menu = new QMenu(ui->tableWidgetCalls);
buildCallActivitySortByMenu(menu);
QMenu * sortByMenu = menu->addMenu("Sort By...");
buildCallActivitySortByMenu(sortByMenu);
QMenu * showColumnsMenu = menu->addMenu("Show Columns...");
buildShowColumnsMenu(showColumnsMenu, "call");
menu->popup(ui->tableWidgetCalls->horizontalHeader()->mapToGlobal(point));
});
@@ -1604,6 +1623,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
gridButtonLayout->setColumnStretch(1, 1);
gridButtonLayout->setColumnStretch(2, 1);
spotSetLocal();
pskSetLocal();
aprsSetLocal();
@@ -1619,7 +1639,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
if (!m_valid) throw std::runtime_error {"Fatal initialization exception"};
}
QDate eol(2019, 3, 11);
QDate eol(2019, 4, 1);
void MainWindow::checkExpiryWarningMessage()
{
@@ -1755,6 +1775,8 @@ void MainWindow::initializeDummyData(){
displayTextForFreq("KN4CRD: JY1 ACK -10 \u2301 ", 800, now, false, true, true);
displayTextForFreq("KN4CRD: JY1 ACK -12 \u2301 ", 780, now.addSecs(120), false, true, true);
displayTextForFreq("HELLO\\nBRAVE\\nNEW\\nWORLD \u2301 ", 1500, now, false, true, true);
displayActivity(true);
}
@@ -1979,6 +2001,7 @@ void MainWindow::writeSettings()
m_settings->setValue ("JT65AP", ui->actionEnable_AP_JT65->isChecked ());
m_settings->setValue("SortBy", QVariant(m_sortCache));
m_settings->setValue("ShowColumns", QVariant(m_showColumnsCache));
m_settings->setValue("HBAutoAck", m_hbAutoAck);
m_settings->setValue("HBHidden", m_hbHidden);
m_settings->setValue("HBInterval", m_hbInterval);
m_settings->setValue("CQInterval", m_cqInterval);
@@ -2113,7 +2136,8 @@ void MainWindow::readSettings()
m_sortCache = m_settings->value("SortBy").toMap();
m_showColumnsCache = m_settings->value("ShowColumns").toMap();
m_hbHidden = m_settings->value("HBHidden", false).toBool();
m_hbAutoAck = m_settings->value("HBAutoAck", true).toBool();
m_hbHidden = m_settings->value("HBHidden", true).toBool();
m_hbInterval = m_settings->value("HBInterval", 0).toInt();
m_cqInterval = m_settings->value("CQInterval", 0).toInt();
@@ -2874,6 +2898,7 @@ void MainWindow::openSettings(int tab){
void MainWindow::prepareSpotting(){
if(m_config.spot_to_reporting_networks ()){
spotSetLocal();
pskSetLocal();
aprsSetLocal();
m_aprsClient->setServer(m_config.aprs_server_name(), m_config.aprs_server_port());
@@ -2969,6 +2994,9 @@ void MainWindow::on_autoButton_clicked (bool checked)
void MainWindow::on_autoReplyButton_toggled(bool checked){
resetPushButtonToggleText(ui->autoReplyButton);
// update the HB button immediately
updateRepeatButtonDisplay();
}
void MainWindow::on_monitorButton_toggled(bool checked){
@@ -4143,8 +4171,9 @@ void MainWindow::readFromStdout() //readFromStdout
// Only respond to HEARTBEATS...remember that CQ messages are "Alt" pings
if(decodedtext.isHeartbeat()){
if(decodedtext.isAlt()){
// this is a cq with a standard or compound call, ala "KN4CRD/P: CQCQCQ"
cd.cqTimestamp = DriftingDateTime::currentDateTimeUtc();
// this is a cq with a compound call, ala "KN4CRD/P: CQCQCQ"
// it is not processed elsewhere, so we need to just log it here.
logCallActivity(cd, true);
@@ -4447,6 +4476,9 @@ void MainWindow::logCallActivity(CallDetail d, bool spot){
if(!d.ackTimestamp.isValid() && old.ackTimestamp.isValid()){
d.ackTimestamp = old.ackTimestamp;
}
if(!d.cqTimestamp.isValid() && old.cqTimestamp.isValid()){
d.cqTimestamp = old.cqTimestamp;
}
m_callActivity[d.call] = d;
} else {
// create
@@ -4503,6 +4535,25 @@ QString MainWindow::lookupCallInCompoundCache(QString const &call){
return m_compoundCallCache.value(call, call);
}
void MainWindow::spotReport(int offset, int snr, QString callsign, QString grid){
if(!m_config.spot_to_reporting_networks()) return;
Frequency frequency = m_freqNominal + offset;
m_spotClient->enqueueSpot(callsign, grid, frequency, snr);
}
void MainWindow::spotCmd(CommandDetail cmd){
if(!m_config.spot_to_reporting_networks()) return;
QString cmdStr = cmd.cmd;
if(!cmdStr.trimmed().isEmpty()){
cmdStr = Varicode::lstrip(cmd.cmd);
}
m_spotClient->enqueueCmd(cmdStr, cmd.from, cmd.to, cmd.relayPath, cmd.text, cmd.grid, cmd.extra, m_freqNominal + cmd.freq, cmd.snr);
}
void MainWindow::pskLogReport(QString mode, int offset, int snr, QString callsign, QString grid){
if(!m_config.spot_to_reporting_networks()) return;
@@ -4753,12 +4804,15 @@ void MainWindow::guiUpdate()
char ft8msgbits[75 + 12]; //packed 75 bit ft8 message plus 12-bit CRC
genft8_(message, MyGrid, &bcontest, &m_i3bit, msgsent, const_cast<char *> (ft8msgbits),
const_cast<int *> (itone), 22, 6, 22);
msgibits = m_i3bit;
msgsent[22]=0;
m_currentMessage = QString::fromLatin1(msgsent);
m_currentMessage = QString::fromLatin1(msgsent).trimmed();
m_currentMessageBits = msgibits;
emitTones();
#if TEST_FOX_WAVE_GEN
if(ui->turboButton->isChecked()) {
@@ -5579,22 +5633,32 @@ void MainWindow::on_tx6_editingFinished() //tx6 edited
}
void MainWindow::cacheActivity(QString key){
m_callActivityCache[key] = m_callActivity;
m_bandActivityCache[key] = m_bandActivity;
m_rxTextCache[key] = ui->textEditRX->toHtml();
m_callActivityBandCache[key] = m_callActivity;
m_bandActivityBandCache[key] = m_bandActivity;
m_rxTextBandCache[key] = ui->textEditRX->toHtml();
m_heardGraphIncomingBandCache[key] = m_heardGraphIncoming;
m_heardGraphOutgoingBandCache[key] = m_heardGraphOutgoing;
}
void MainWindow::restoreActivity(QString key){
if(m_callActivityCache.contains(key)){
m_callActivity = m_callActivityCache[key];
if(m_callActivityBandCache.contains(key)){
m_callActivity = m_callActivityBandCache[key];
}
if(m_bandActivityCache.contains(key)){
m_bandActivity = m_bandActivityCache[key];
if(m_bandActivityBandCache.contains(key)){
m_bandActivity = m_bandActivityBandCache[key];
}
if(m_rxTextCache.contains(key)){
ui->textEditRX->setHtml(m_rxTextCache[key]);
if(m_rxTextBandCache.contains(key)){
ui->textEditRX->setHtml(m_rxTextBandCache[key]);
}
if(m_heardGraphIncomingBandCache.contains(key)){
m_heardGraphIncoming = m_heardGraphIncomingBandCache[key];
}
if(m_heardGraphOutgoingBandCache.contains(key)){
m_heardGraphOutgoing = m_heardGraphOutgoingBandCache[key];
}
displayActivity(true);
@@ -5717,7 +5781,7 @@ void MainWindow::displayTextForFreq(QString text, int freq, QDateTime date, bool
block = -1;
}
block = writeMessageTextToUI(date, text, freq, isTx, block);
block = writeMessageTextToUI(date, text.replace("\\n", "\n"), freq, isTx, block);
// never cache tx or last lines
if(isTx || isLast) {
@@ -6409,7 +6473,7 @@ void MainWindow::acceptQSO (QDateTime const& QSO_date_off, QString const& call,
, QString const& my_call, QString const& my_grid, QByteArray const& ADIF)
{
QString date = QSO_date_on.toString("yyyyMMdd");
m_logBook.addAsWorked (m_hisCall, m_config.bands ()->find (m_freqNominal), mode, submode, date);
m_logBook.addAsWorked (m_hisCall, m_config.bands ()->find (m_freqNominal), mode, submode, date, name, comments);
sendNetworkMessage("LOG.QSO", QString(ADIF), {
{"UTC.ON", QVariant(QSO_date_on.toMSecsSinceEpoch())},
@@ -6998,6 +7062,16 @@ void MainWindow::buildFrequencyMenu(QMenu *menu){
}
void MainWindow::buildHeartbeatMenu(QMenu *menu){
auto autoAckHB = menu->addAction(ui->autoReplyButton->isChecked() ? "Send Heartbeat Acknowledgments (ACK)" : "Send Heartbeat Acknowledgments (ACK) (AUTO disabled)");
autoAckHB->setEnabled(ui->autoReplyButton->isChecked());
autoAckHB->setCheckable(true);
autoAckHB->setChecked(m_hbAutoAck);
connect(autoAckHB, &QAction::triggered, this, [this, autoAckHB](){
m_hbAutoAck = autoAckHB->isChecked();
updateRepeatButtonDisplay();
});
menu->addSeparator();
if(m_hbInterval > 0){
auto startStop = menu->addAction(ui->hbMacroButton->isChecked() ? "Stop Heartbeat Timer" : "Start Heartbeat Timer");
connect(startStop, &QAction::triggered, this, [this](){ ui->hbMacroButton->toggle(); });
@@ -7117,8 +7191,13 @@ void MainWindow::sendHeartbeat(){
processTxQueue();
}
#define SEND_SNR_IN_ACK 1
void MainWindow::sendHeartbeatAck(QString to, int snr, QString extra){
#if SEND_SNR_IN_ACK
auto message = QString("%1 ACK %2 %3").arg(to).arg(Varicode::formatSNR(snr)).arg(extra).trimmed();
#else
auto message = QString("%1 ACK %2").arg(to).arg(extra).trimmed();
#endif
auto f = m_config.heartbeat_anywhere() ? -1 : findFreeFreqOffset(500, 1000, 50);
@@ -7267,11 +7346,13 @@ void MainWindow::buildShowColumnsMenu(QMenu *menu, QString tableKey){
};
if(tableKey == "call"){
columnKeys.prepend({"Worked Before Flag", "flag"});
columnKeys.prepend({"Callsign", "callsign"});
columnKeys.append({
{"Grid Locator", "grid"},
{"Distance", "distance"}
{"Distance", "distance"},
{"Worked Before", "log"},
{"Logged Name", "logName"},
{"Logged Comment", "logComment"},
});
}
@@ -7730,6 +7811,20 @@ void MainWindow::buildQueryMenu(QMenu * menu, QString call){
if(m_config.transmit_directed()) toggleTx(true);
});
auto ditDitAction = menu->addAction(QString("%1 DIT DIT - End of contact / Two bits").arg(call).trimmed());
connect(ditDitAction, &QAction::triggered, this, [this](){
QString selectedCall = callsignSelected();
if(selectedCall.isEmpty()){
return;
}
addMessageText(QString("%1 DIT DIT").arg(selectedCall), true);
if(m_config.transmit_directed()) toggleTx(true);
});
}
void MainWindow::buildRelayMenu(QMenu *menu){
@@ -7987,6 +8082,9 @@ void MainWindow::on_tableWidgetRXAll_selectionChanged(const QItemSelection &/*se
} else {
placeholderText = QString("Type your outgoing directed message to %1 here.").arg(selectedCall);
// when we select a callsign, use it as the qso start time
m_dateTimeQSOOn = DriftingDateTime::currentDateTimeUtc();
// TODO: jsherer - move this to a generic "callsign changed" signal
if(m_config.heartbeat_qso_pause()){
// don't hb if we select a callsign... (but we should keep track so if we deselect, we restore our hb)
@@ -8518,6 +8616,7 @@ void MainWindow::handle_transceiver_update (Transceiver::TransceiverState const&
}
if (m_config.spot_to_reporting_networks ()) {
spotSetLocal();
pskSetLocal();
aprsSetLocal();
}
@@ -8768,6 +8867,16 @@ bool MainWindow::shortList(QString callsign)
return b;
}
void MainWindow::spotSetLocal ()
{
auto call = m_config.my_callsign();
auto grid = m_config.my_grid();
auto info = replaceMacros(m_config.my_info(), buildMacroValues(), true);
auto ver = QString {"JS8Call v" + version() }.simplified ();
qDebug() << "SpotClient Set Local Station:" << call << grid << info << ver;
m_spotClient->setLocalStation(call, grid, info, ver);
}
void MainWindow::pskSetLocal ()
{
auto info = replaceMacros(m_config.my_info(), buildMacroValues(), true);
@@ -9078,15 +9187,16 @@ void MainWindow::updateButtonDisplay(){
}
void MainWindow::updateRepeatButtonDisplay(){
auto hbBase = m_hbAutoAck && ui->autoReplyButton->isChecked() ? "HB + ACK" : "HB";
if(ui->hbMacroButton->isChecked() && m_hbInterval > 0 && m_nextHeartbeat.isValid()){
auto secs = DriftingDateTime::currentDateTimeUtc().secsTo(m_nextHeartbeat);
if(secs > 0){
ui->hbMacroButton->setText(QString("HB (%1)").arg(secs));
ui->hbMacroButton->setText(QString("%1 (%2)").arg(hbBase).arg(secs));
} else {
ui->hbMacroButton->setText(QString("HB (now)"));
ui->hbMacroButton->setText(QString("%1 (now)").arg(hbBase));
}
} else {
ui->hbMacroButton->setText("HB");
ui->hbMacroButton->setText(hbBase);
}
if(ui->cqMacroButton->isChecked() && m_cqInterval > 0 && m_nextCQ.isValid()){
@@ -9872,6 +9982,11 @@ void MainWindow::processCommandActivity() {
continue;
}
// PROCESS @JS8NET SPOTS FOR EVERYONE
if (d.to == "@JS8NET"){
spotCmd(d);
}
// we're only responding to allcall, groupcalls, and our callsign at this point, so we'll end after logging the callsigns we've heard
if (!isAllCall && !toMe && !isGroupCall) {
continue;
@@ -9996,7 +10111,7 @@ void MainWindow::processCommandActivity() {
}
// HACK: if this is an autoreply cmd and relay path is populated and cmd is not MSG or MSG TO:, then swap out the relay path
if(Varicode::isCommandAutoreply(d.cmd) && !d.relayPath.isEmpty() && !d.cmd.startsWith(" MSG")){
if(Varicode::isCommandAutoreply(d.cmd) && !d.relayPath.isEmpty() && !d.cmd.startsWith(" MSG") && !d.cmd.startsWith(" QUERY")){
d.from = d.relayPath;
}
@@ -10076,7 +10191,7 @@ void MainWindow::processCommandActivity() {
}
// PROCESS RELAY
else if (d.cmd == ">" && !isAllCall) {
else if (d.cmd == ">" && !isAllCall && !m_config.relay_off()) {
// 1. see if there are any more hops to process
// 2. if so, forward
@@ -10088,12 +10203,12 @@ void MainWindow::processCommandActivity() {
auto match = re.match(text);
// if the text starts with a callsign, and relay is not disabled, and this is not a group callsign, then relay.
if(match.hasMatch() && !m_config.relay_off() && !isGroupCall){
if(match.hasMatch() && !isGroupCall){
// replace freetext with relayed free text
if(match.captured("type") != ">"){
text = text.replace(match.capturedStart("type"), match.capturedLength("type"), ">");
}
reply = QString("%1 DE %2").arg(text).arg(d.from);
reply = QString("%1 VIA %2").arg(text).arg(d.from);
// otherwise, as long as we're not an ACK...alert the user and either send an ACK or Message
} else if(!d.text.startsWith("ACK")) {
@@ -10132,23 +10247,35 @@ void MainWindow::processCommandActivity() {
}
// HACK: "MSG TO:" should be supported but contains a space :(
if(!relayedCmds.isEmpty() && first == " MSG"){
auto second = relayedCmds.first();
if(second == "TO:"){
first = " MSG TO:";
relayedCmds.removeFirst();
} else if(second.startsWith("TO:")){
first = " MSG TO:";
relayedCmds.replace(0, second.mid(3));
if(!relayedCmds.isEmpty()){
if(first == " MSG"){
auto second = relayedCmds.first();
if(second == "TO:"){
first = " MSG TO:";
relayedCmds.removeFirst();
} else if(second.startsWith("TO:")){
first = " MSG TO:";
relayedCmds.replace(0, second.mid(3));
}
} else if (first == " QUERY"){
auto second = relayedCmds.first();
if(second == "MSGS" || second == "MSGS?"){
first = " QUERY MSGS";
relayedCmds.removeFirst();
}
else if(second == "CALL"){
first = " QUERY CALL";
relayedCmds.removeFirst();
}
}
}
if(valid && Varicode::isCommandAutoreply(first)){
if(Varicode::isCommandAllowed(first) && Varicode::isCommandAutoreply(first)){
CommandDetail rd = {};
rd.bits = d.bits;
rd.cmd = first;
rd.freq = d.freq;
rd.from = d.from;
rd.from = d.from; // note, MSG and QUERY commands are not set with from as the relay path.
rd.relayPath = d.relayPath;
rd.text = relayedCmds.join(" "); //d.text;
rd.to = d.to;
@@ -10212,7 +10339,7 @@ void MainWindow::processCommandActivity() {
// PROCESS ACTIVE HEARTBEAT
// if we have auto reply enabled and we are heartbeating and selcall is not enabled
else if (d.cmd == " HB" && ui->autoReplyButton->isChecked() && ui->hbMacroButton->isChecked() && m_hbInterval > 0){
else if (d.cmd == " HB" && ui->autoReplyButton->isChecked() && m_hbAutoAck){
// check to see if we have a message for a station who is heartbeating
QString extra;
@@ -10272,6 +10399,14 @@ void MainWindow::processCommandActivity() {
continue;
}
// PROCESS NACKS
else if (d.cmd == " NACK" && !isAllCall){
qDebug() << "skipping incoming nack" << d.text;
// make sure this is explicit
continue;
}
// PROCESS BUFFERED CMD
else if (d.cmd == " CMD" && !isAllCall){
qDebug() << "skipping incoming command" << d.text;
@@ -10282,7 +10417,14 @@ void MainWindow::processCommandActivity() {
// PROCESS BUFFERED QUERY
else if (d.cmd == " QUERY" && !isAllCall){
auto who = d.from;
auto who = d.from; // keep in mind, this is the sender, not the original requestor if relayed
auto replyPath = d.from;
if(d.relayPath.contains(">")){
auto path = d.relayPath.split(">");
who = path.last();
replyPath = d.relayPath;
}
QStringList segs = d.text.split(" ");
if(segs.isEmpty()){
@@ -10327,8 +10469,8 @@ void MainWindow::processCommandActivity() {
inbox.set(mid, msg);
// and reply
reply = QString("%1 MSG %2 DE %3");
reply = reply.arg(who);
reply = QString("%1 MSG %2 FROM %3");
reply = reply.arg(replyPath);
reply = reply.arg(text);
reply = reply.arg(from);
}
@@ -10336,23 +10478,35 @@ void MainWindow::processCommandActivity() {
// PROCESS BUFFERED QUERY MSGS
else if (d.cmd == " QUERY MSGS" && ui->autoReplyButton->isChecked()){
auto who = d.from;
auto who = d.from; // keep in mind, this is the sender, not the original requestor if relayed
auto replyPath = d.from;
if(d.relayPath.contains(">")){
auto path = d.relayPath.split(">");
who = path.last();
replyPath = d.relayPath;
}
// if this is an allcall or a directed call, check to see if we have a stored message for user.
// we reply yes if the user would be able to retreive a stored message
auto mid = getNextMessageIdForCallsign(who);
if(mid != -1){
reply = QString("%1 YES MSG ID %2").arg(who).arg(mid);
reply = QString("%1 YES MSG ID %2").arg(replyPath).arg(mid);
}
// if this is not an allcall and we have no messages, reply no.
if(!isAllCall && reply.isEmpty()){
reply = QString("%1 NO").arg(who);
reply = QString("%1 NO").arg(replyPath);
}
}
// PROCESS BUFFERED QUERY CALL
else if (d.cmd == " QUERY CALL" && ui->autoReplyButton->isChecked()){
auto replyPath = d.from;
if(d.relayPath.contains(">")){
replyPath = d.relayPath;
}
auto who = d.text;
if(who.isEmpty()){
continue;
@@ -10372,20 +10526,20 @@ void MainWindow::processCommandActivity() {
}
if(baseCall == cd.call || baseCall == Radio::base_callsign(cd.call)){
auto r = QString("%1 ACK %2").arg(cd.call).arg(Varicode::formatSNR(cd.snr));
auto r = QString("%1 (%2)").arg(Varicode::formatSNR(cd.snr)).arg(since(cd.utcTimestamp)).trimmed();
replies.append(r);
break;
}
}
if(!replies.isEmpty()){
replies.prepend(QString("%1 YES").arg(d.from));
replies.prepend(QString("%1 YES").arg(replyPath));
}
reply = replies.join("\n");
reply = replies.join(" ");
if(!reply.isEmpty()){
if(isAllCall){
// since all pings are technically @ALLCALL, let's bump the allcall cache here...
m_txAllcallCommandCache.insert(d.from, new QDateTime(now), 25);
}
}
@@ -10585,7 +10739,7 @@ int MainWindow::getNextMessageIdForCallsign(QString callsign){
QStringList MainWindow::parseRelayPathCallsigns(QString from, QString text){
QStringList calls;
QString callDePattern = {R"(\sDE\s(?<callsign>\b(?<prefix>[A-Z0-9]{1,4}\/)?(?<base>([0-9A-Z])?([0-9A-Z])([0-9])([A-Z])?([A-Z])?([A-Z])?)(?<suffix>\/[A-Z0-9]{1,4})?)\b)"};
QString callDePattern = {R"(\sVIA\s(?<callsign>\b(?<prefix>[A-Z0-9]{1,4}\/)?(?<base>([0-9A-Z])?([0-9A-Z])([0-9])([A-Z])?([A-Z])?([A-Z])?)(?<suffix>\/[A-Z0-9]{1,4})?)\b)"};
QRegularExpression re(callDePattern);
auto iter = re.globalMatch(text);
while(iter.hasNext()){
@@ -10607,7 +10761,7 @@ void MainWindow::processAlertReplyForCommand(CommandDetail d, QString from, QStr
QString fromReplace = QString{};
foreach(auto call, calls){
fromReplace.append(" DE ");
fromReplace.append(" VIA ");
fromReplace.append(call);
}
@@ -10676,6 +10830,7 @@ void MainWindow::processSpots() {
auto dial = dialFrequency();
// Process spots to be sent...
spotSetLocal();
pskSetLocal();
aprsSetLocal();
@@ -10687,6 +10842,7 @@ void MainWindow::processSpots() {
qDebug() << "spotting call to reporting networks" << d.call << d.snr << d.freq;
spotReport(d.freq, d.snr, d.call, d.grid);
pskLogReport("JS8", d.freq, d.snr, d.call, d.grid);
aprsLogReport(d.freq, d.snr, d.call, d.grid);
@@ -10871,6 +11027,7 @@ void MainWindow::displayBandActivity() {
QList < ActivityDetail > items = m_bandActivity[offset];
if (items.length() > 0) {
QDateTime timestamp;
QStringList text;
QString age;
int snr = 0;
@@ -10922,6 +11079,7 @@ void MainWindow::displayBandActivity() {
text.append(item.text);
snr = item.snr;
age = since(item.utcTimestamp);
timestamp = item.utcTimestamp;
tdrift = item.tdrift;
}
@@ -10938,11 +11096,13 @@ void MainWindow::displayBandActivity() {
offsetItem->setData(Qt::UserRole, QVariant(offset));
ui->tableWidgetRXAll->setItem(row, col++, offsetItem);
auto ageItem = new QTableWidgetItem(QString("%1").arg(age));
auto ageItem = new QTableWidgetItem(age);
ageItem->setTextAlignment(Qt::AlignCenter | Qt::AlignVCenter);
ageItem->setToolTip(timestamp.toString());
ui->tableWidgetRXAll->setItem(row, col++, ageItem);
auto snrItem = new QTableWidgetItem(QString("%1 dB").arg(Varicode::formatSNR(snr)));
auto snrText = Varicode::formatSNR(snr);
auto snrItem = new QTableWidgetItem(snrText.isEmpty() ? "" : QString("%1 dB").arg(snrText));
snrItem->setTextAlignment(Qt::AlignCenter | Qt::AlignVCenter);
ui->tableWidgetRXAll->setItem(row, col++, snrItem);
@@ -11167,7 +11327,12 @@ void MainWindow::displayCallActivity() {
// icon flags (flag -> star -> empty)
bool hasMessage = m_rxInboxCountCache.value(d.call, 0) > 0;
bool hasAck = d.ackTimestamp.isValid();
// display telephone icon if called cq in the past 5 minutes
bool hasCQ = d.cqTimestamp.isValid() && d.cqTimestamp.secsTo(now) / 60 < 5;
// display star if they've acked a message from us
bool hasACK = d.ackTimestamp.isValid();
if (!isCallSelected && !hasMessage && callsignAging && d.utcTimestamp.secsTo(now) / 60 >= callsignAging) {
continue;
@@ -11185,32 +11350,33 @@ void MainWindow::displayCallActivity() {
QString displayCall = d.call;
#endif
QString flag;
if(m_logBook.hasWorkedBefore(d.call, "")){
// unicode checkmark
flag = "\u2713";
}
auto iconItem = new QTableWidgetItem(hasMessage ? "\u2691" : hasAck ? "\u2605" : "");
iconItem->setData(Qt::UserRole, QVariant((d.call)));
auto iconItem = new QTableWidgetItem(hasMessage ? "\u2691" : hasACK ? "\u2605" : hasCQ ? "\u260E" : "");
iconItem->setData(Qt::UserRole, QVariant(d.call));
iconItem->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
ui->tableWidgetCalls->setItem(row, col++, iconItem);
if(hasMessage || hasAck){
if(hasMessage || hasACK || hasCQ){
showIconColumn = true;
}
auto displayItem = new QTableWidgetItem(displayCall);
displayItem->setData(Qt::UserRole, QVariant(d.call));
displayItem->setToolTip(generateCallDetail(displayCall));
ui->tableWidgetCalls->setItem(row, col++, displayItem);
auto flagItem = new QTableWidgetItem(flag);
flagItem->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
ui->tableWidgetCalls->setItem(row, col++, flagItem);
#if ONLY_SHOW_HEARD_CALLSIGNS
if(d.utcTimestamp.isValid()){
ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem(QString("%1").arg(since(d.utcTimestamp))));
ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem(QString("%1 dB").arg(Varicode::formatSNR(d.snr))));
#else
if(true){
#endif
auto ageItem = new QTableWidgetItem(since(d.utcTimestamp));
ageItem->setTextAlignment(Qt::AlignCenter | Qt::AlignVCenter);
ageItem->setToolTip(d.utcTimestamp.toString());
ui->tableWidgetCalls->setItem(row, col++, ageItem);
auto snrText = Varicode::formatSNR(d.snr);
ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem(snrText.isEmpty() ? "" : QString("%1 dB").arg(snrText)));
auto offsetItem = new QTableWidgetItem(QString("%1 Hz").arg(d.freq));
offsetItem->setData(Qt::UserRole, QVariant(d.freq));
@@ -11224,7 +11390,38 @@ void MainWindow::displayCallActivity() {
auto distanceItem = new QTableWidgetItem(calculateDistance(d.grid));
distanceItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
ui->tableWidgetCalls->setItem(ui->tableWidgetCalls->rowCount() - 1, col++, distanceItem);
ui->tableWidgetCalls->setItem(row, col++, distanceItem);
QString flag;
if(m_logBook.hasWorkedBefore(d.call, "")){
// unicode checkmark
flag = "\u2713";
}
auto workedBeforeItem = new QTableWidgetItem(flag);
workedBeforeItem->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
ui->tableWidgetCalls->setItem(row, col++, workedBeforeItem);
QString logDetailDate;
QString logDetailName;
QString logDetailComment;
if(showColumn("call", "log") || showColumn("call", "logName") || showColumn("call", "logComment")){
m_logBook.findCallDetails(d.call, logDetailDate, logDetailName, logDetailComment);
}
if(!logDetailDate.isEmpty()){
auto lastLogged = QDate::fromString(logDetailDate, "yyyyMMdd");
workedBeforeItem->setToolTip(QString("Last Logged: %1").arg(lastLogged.toString()));
}
auto logNameItem = new QTableWidgetItem(logDetailName);
logNameItem->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
ui->tableWidgetCalls->setItem(row, col++, logNameItem);
auto logCommentItem = new QTableWidgetItem(logDetailComment);
logCommentItem->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
ui->tableWidgetCalls->setItem(row, col++, logCommentItem);
} else {
ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem("")); // age
@@ -11233,8 +11430,9 @@ void MainWindow::displayCallActivity() {
ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem("")); // tdrift
ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem("")); // grid
ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem("")); // distance
//ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem(""));
ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem("")); // worked before
ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem("")); // log name
ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem("")); // log comment
}
if (isCallSelected) {
@@ -11277,13 +11475,15 @@ void MainWindow::displayCallActivity() {
// Hide columns
ui->tableWidgetCalls->setColumnHidden(0, !showIconColumn);
ui->tableWidgetCalls->setColumnHidden(1, !showColumn("call", "callsign"));
ui->tableWidgetCalls->setColumnHidden(2, !showColumn("call", "flag"));
ui->tableWidgetCalls->setColumnHidden(3, !showColumn("call", "timestamp"));
ui->tableWidgetCalls->setColumnHidden(4, !showColumn("call", "snr"));
ui->tableWidgetCalls->setColumnHidden(5, !showColumn("call", "offset"));
ui->tableWidgetCalls->setColumnHidden(6, !showColumn("call", "tdrift", false));
ui->tableWidgetCalls->setColumnHidden(7, !showColumn("call", "grid", false));
ui->tableWidgetCalls->setColumnHidden(8, !showColumn("call", "distance", false));
ui->tableWidgetCalls->setColumnHidden(2, !showColumn("call", "timestamp"));
ui->tableWidgetCalls->setColumnHidden(3, !showColumn("call", "snr"));
ui->tableWidgetCalls->setColumnHidden(4, !showColumn("call", "offset"));
ui->tableWidgetCalls->setColumnHidden(5, !showColumn("call", "tdrift", false));
ui->tableWidgetCalls->setColumnHidden(6, !showColumn("call", "grid", false));
ui->tableWidgetCalls->setColumnHidden(7, !showColumn("call", "distance", false));
ui->tableWidgetCalls->setColumnHidden(8, !showColumn("call", "log"));
ui->tableWidgetCalls->setColumnHidden(9, !showColumn("call", "logName"));
ui->tableWidgetCalls->setColumnHidden(10, !showColumn("call", "logComment"));
// Resize the table columns
ui->tableWidgetCalls->resizeColumnToContents(0);
@@ -11294,6 +11494,8 @@ void MainWindow::displayCallActivity() {
ui->tableWidgetCalls->resizeColumnToContents(5);
ui->tableWidgetCalls->resizeColumnToContents(6);
ui->tableWidgetCalls->resizeColumnToContents(7);
ui->tableWidgetCalls->resizeColumnToContents(8);
ui->tableWidgetCalls->resizeColumnToContents(9);
// Reset the scroll position
ui->tableWidgetCalls->verticalScrollBar()->setValue(currentScrollPos);
@@ -11326,6 +11528,22 @@ void MainWindow::emitPTT(bool on){
});
}
void MainWindow::emitTones(){
if(!m_config.udpEnabled()){
return;
}
// emit tone numbers to network
QVariantList t;
for(int i = 0; i < NUM_FT8_SYMBOLS; i++){
t.append(QVariant((int)itone[i]));
}
sendNetworkMessage("TX.FRAME", "", {
{"TONES", t}
});
}
void MainWindow::networkMessage(Message const &message)
{
if(!m_config.udpEnabled()){
+13 -3
View File
@@ -44,6 +44,7 @@
#include "qpriorityqueue.h"
#include "varicode.h"
#include "MessageClient.hpp"
#include "SpotClient.h"
#include "APRSISClient.h"
#include "keyeater.h"
@@ -383,6 +384,7 @@ private slots:
void on_cbFirst_toggled(bool b);
void on_cbAutoSeq_toggled(bool b);
void emitPTT(bool on);
void emitTones();
void networkMessage(Message const &message);
void sendNetworkMessage(QString const &type, QString const &message);
void sendNetworkMessage(QString const &type, QString const &message, const QMap<QString, QVariant> &params);
@@ -712,6 +714,7 @@ private:
QString through;
QString grid;
int freq;
QDateTime cqTimestamp;
QDateTime ackTimestamp;
QDateTime utcTimestamp;
int snr;
@@ -826,9 +829,11 @@ private:
QMap<QString, int> m_rxInboxCountCache; // call -> count
QMap<QString, QMap<QString, CallDetail>> m_callActivityCache; // band -> call activity
QMap<QString, QMap<int, QList<ActivityDetail>>> m_bandActivityCache; // band -> band activity
QMap<QString, QString> m_rxTextCache; // band -> rx text
QMap<QString, QMap<QString, CallDetail>> m_callActivityBandCache; // band -> call activity
QMap<QString, QMap<int, QList<ActivityDetail>>> m_bandActivityBandCache; // band -> band activity
QMap<QString, QString> m_rxTextBandCache; // band -> rx text
QMap<QString, QMap<QString, QSet<QString>>> m_heardGraphOutgoingBandCache; // band -> heard in
QMap<QString, QMap<QString, QSet<QString>>> m_heardGraphIncomingBandCache; // band -> heard out
JSCChecker * m_checker;
@@ -856,6 +861,7 @@ private:
QQueue<QString> m_foxQSOinProgress; //QSOs in progress: Fox has sent a report
QQueue<qint64> m_foxRateQueue;
bool m_hbAutoAck;
bool m_hbHidden;
int m_hbInterval;
int m_cqInterval;
@@ -891,6 +897,7 @@ private:
QProgressDialog m_optimizingProgress;
MessageClient * m_messageClient;
PSK_Reporter *psk_Reporter;
SpotClient *m_spotClient;
APRSISClient * m_aprsClient;
DisplayManual m_manual;
QHash<QString, QVariant> m_pwrBandTxMemory; // Remembers power level by band
@@ -915,8 +922,11 @@ private:
bool shortList(QString callsign);
void transmit (double snr = 99.);
void rigFailure (QString const& reason);
void spotSetLocal();
void pskSetLocal ();
void aprsSetLocal ();
void spotReport(int offset, int snr, QString callsign, QString grid);
void spotCmd(CommandDetail cmd);
void pskLogReport(QString mode, int offset, int snr, QString callsign, QString grid);
void aprsLogReport(int offset, int snr, QString callsign, QString grid);
Radio::Frequency dialFrequency();
+26 -10
View File
@@ -203,7 +203,7 @@
</size>
</property>
<property name="toolTip">
<string>Current Frequency</string>
<string>Set Current Frequency</string>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
@@ -297,7 +297,7 @@ QPushButton[oob=&quot;true&quot;] {
<bool>true</bool>
</property>
<property name="toolTip">
<string>Current frequency offset</string>
<string>Set Current Frequency Offset</string>
</property>
<property name="styleSheet">
<string notr="true">QLabel {
@@ -401,6 +401,12 @@ color : white;
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="mouseTracking">
<bool>true</bool>
</property>
<property name="toolTip">
<string>Set Callsign and Grid</string>
</property>
<property name="styleSheet">
<string notr="true">QLabel {
font-family: MS Shell Dlg 2;
@@ -1242,14 +1248,6 @@ QTextEdit[transmitting=&quot;true&quot;] {
<string>Callsigns</string>
</property>
</column>
<column>
<property name="text">
<string>✓</string>
</property>
<property name="textAlignment">
<set>AlignCenter</set>
</property>
</column>
<column>
<property name="text">
<string>Age</string>
@@ -1280,6 +1278,24 @@ QTextEdit[transmitting=&quot;true&quot;] {
<string>Distance</string>
</property>
</column>
<column>
<property name="text">
<string>✓</string>
</property>
<property name="toolTip">
<string>Worked Before</string>
</property>
</column>
<column>
<property name="text">
<string>Name</string>
</property>
</column>
<column>
<property name="text">
<string>Comment</string>
</property>
</column>
</widget>
</widget>
</widget>
+8 -7
View File
@@ -50,8 +50,9 @@ QMap<QString, int> directed_cmds = {
{" SNR?", 0 }, // query snr
{"?", 0 }, // compat
//{" ", 1 }, // unused
//{" ", 2 }, // unused
{" DIT DIT", 1 }, // unused
{" NACK", 2 }, // negative acknowledge
{" HEARING?", 3 }, // query station calls heard
@@ -104,7 +105,7 @@ QMap<QString, int> directed_cmds = {
};
// commands allowed to be processed
QSet<int> allowed_cmds = {-1, 0, /*1,*/ /*2,*/ 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
QSet<int> allowed_cmds = {-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
// commands that result in an autoreply (which can be relayed)
QSet<int> autoreply_cmds = {0, 3, 4, 6, 9, 10, 11, 12, 13, 16, 30};
@@ -129,10 +130,10 @@ QMap<int, int> checksum_cmds = {
};
QString callsign_pattern = QString("(?<callsign>[@]?[A-Z0-9/]+)");
QString optional_cmd_pattern = QString("(?<cmd>\\s?(?:AGN[?]|QSL[?]|HW CPY[?]|APRS[:]|MSG TO[:]|SNR[?]|INFO[?]|GRID[?]|STATUS[?]|QUERY MSGS[?]|HEARING[?]|(?:(?:STATUS|HEARING|QUERY CALL|QUERY MSGS|QUERY|CMD|MSG|ACK|73|YES|NO|SNR|QSL|RR|SK|FB|INFO|GRID)(?=[ ]|$))|[?> ]))?");
QString optional_cmd_pattern = QString("(?<cmd>\\s?(?:AGN[?]|QSL[?]|HW CPY[?]|APRS[:]|MSG TO[:]|SNR[?]|INFO[?]|GRID[?]|STATUS[?]|QUERY MSGS[?]|HEARING[?]|(?:(?:STATUS|HEARING|QUERY CALL|QUERY MSGS|QUERY|CMD|MSG|NACK|ACK|73|YES|NO|SNR|QSL|RR|SK|FB|INFO|GRID|DIT DIT)(?=[ ]|$))|[?> ]))?");
QString optional_grid_pattern = QString("(?<grid>\\s?[A-R]{2}[0-9]{2})?");
QString optional_extended_grid_pattern = QString("^(?<grid>\\s?(?:[A-R]{2}[0-9]{2}(?:[A-X]{2}(?:[0-9]{2})?)*))?");
QString optional_num_pattern = QString("(?<num>(?<=SNR|ACK)\\s?[-+]?(?:3[01]|[0-2]?[0-9]))?");
QString optional_num_pattern = QString("(?<num>(?<=SNR|\\bACK)\\s?[-+]?(?:3[01]|[0-2]?[0-9]))?");
QRegularExpression directed_re("^" +
callsign_pattern +
@@ -1545,8 +1546,8 @@ QStringList Varicode::unpackDirectedMessage(const QString &text, quint8 *pType){
quint32 packed_to = Varicode::bitsToInt(bits.mid(31, 28));
quint8 packed_cmd = Varicode::bitsToInt(bits.mid(59, 5));
bool portable_from = (extra >> 7) & 1 == 1;
bool portable_to = (extra >> 6) & 1 == 1;
bool portable_from = ((extra >> 7) & 1) == 1;
bool portable_to = ((extra >> 6) & 1) == 1;
extra = extra % 64;
QString from = Varicode::unpackCallsign(packed_from, portable_from);