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 messagereplydialog.cpp
keyeater.cpp keyeater.cpp
APRSISClient.cpp APRSISClient.cpp
SpotClient.cpp
Inbox.cpp Inbox.cpp
messagewindow.cpp messagewindow.cpp
mainwindow.cpp mainwindow.cpp
+109 -43
View File
@@ -23,7 +23,7 @@
<string>Select tab to change configuration parameters.</string> <string>Select tab to change configuration parameters.</string>
</property> </property>
<property name="currentIndex"> <property name="currentIndex">
<number>0</number> <number>3</number>
</property> </property>
<widget class="QWidget" name="general_tab"> <widget class="QWidget" name="general_tab">
<attribute name="title"> <attribute name="title">
@@ -88,6 +88,16 @@
<string>Station Details</string> <string>Station Details</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_13"> <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"> <item row="1" column="0">
<widget class="QLabel" name="grid_label"> <widget class="QLabel" name="grid_label">
<property name="text"> <property name="text">
@@ -121,16 +131,6 @@
</property> </property>
</widget> </widget>
</item> </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"> <item row="3" column="0">
<widget class="QLabel" name="groups_label"> <widget class="QLabel" name="groups_label">
<property name="text"> <property name="text">
@@ -278,7 +278,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>724</width> <width>615</width>
<height>646</height> <height>646</height>
</rect> </rect>
</property> </property>
@@ -2339,7 +2339,7 @@ This is used for reverse ping analysis which is very useful
for assessing propagation and system performance.</string> for assessing propagation and system performance.</string>
</property> </property>
<property name="text"> <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>
<property name="checked"> <property name="checked">
<bool>true</bool> <bool>true</bool>
@@ -3983,71 +3983,137 @@ soundcard changes</string>
</widget> </widget>
<tabstops> <tabstops>
<tabstop>configuration_tabs</tabstop> <tabstop>configuration_tabs</tabstop>
<tabstop>use_dynamic_grid</tabstop> <tabstop>tabWidget_2</tabstop>
<tabstop>type_2_msg_gen_combo_box</tabstop> <tabstop>callsign_line_edit</tabstop>
<tabstop>monitor_last_used_check_box</tabstop> <tabstop>grid_line_edit</tabstop>
<tabstop>quick_call_check_box</tabstop> <tabstop>groups_line_edit</tabstop>
<tabstop>CW_id_after_73_check_box</tabstop> <tabstop>avoid_allcall_checkbox</tabstop>
<tabstop>enable_VHF_features_check_box</tabstop> <tabstop>cq_message_line_edit</tabstop>
<tabstop>single_decode_check_box</tabstop> <tabstop>reply_message_line_edit</tabstop>
<tabstop>decode_at_52s_check_box</tabstop> <tabstop>info_message_line_edit</tabstop>
<tabstop>CW_id_interval_spin_box</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>rig_combo_box</tabstop>
<tabstop>CAT_poll_interval_spin_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_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_7_bit_radio_button</tabstop>
<tabstop>CAT_8_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_one_stop_bit_radio_button</tabstop>
<tabstop>CAT_two_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_none_radio_button</tabstop>
<tabstop>CAT_handshake_xon_radio_button</tabstop> <tabstop>CAT_handshake_xon_radio_button</tabstop>
<tabstop>CAT_handshake_hardware_radio_button</tabstop> <tabstop>CAT_handshake_hardware_radio_button</tabstop>
<tabstop>force_DTR_combo_box</tabstop> <tabstop>force_DTR_combo_box</tabstop>
<tabstop>force_RTS_combo_box</tabstop> <tabstop>force_RTS_combo_box</tabstop>
<tabstop>scrollArea_4</tabstop>
<tabstop>PTT_VOX_radio_button</tabstop> <tabstop>PTT_VOX_radio_button</tabstop>
<tabstop>PTT_CAT_radio_button</tabstop>
<tabstop>PTT_DTR_radio_button</tabstop> <tabstop>PTT_DTR_radio_button</tabstop>
<tabstop>PTT_CAT_radio_button</tabstop>
<tabstop>PTT_RTS_radio_button</tabstop> <tabstop>PTT_RTS_radio_button</tabstop>
<tabstop>PTT_port_combo_box</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_data_radio_button</tabstop>
<tabstop>TX_source_mic_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_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_CAT_push_button</tabstop>
<tabstop>test_PTT_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_combo_box</tabstop>
<tabstop>sound_input_channel_combo_box</tabstop> <tabstop>sound_input_channel_combo_box</tabstop>
<tabstop>sound_output_combo_box</tabstop>
<tabstop>sound_output_channel_combo_box</tabstop> <tabstop>sound_output_channel_combo_box</tabstop>
<tabstop>save_path_select_push_button</tabstop> <tabstop>save_path_select_push_button</tabstop>
<tabstop>azel_path_select_push_button</tabstop> <tabstop>azel_path_select_push_button</tabstop>
<tabstop>checkBoxPwrBandTxMemory</tabstop> <tabstop>checkBoxPwrBandTxMemory</tabstop>
<tabstop>checkBoxPwrBandTuneMemory</tabstop> <tabstop>checkBoxPwrBandTuneMemory</tabstop>
<tabstop>add_macro_line_edit</tabstop> <tabstop>scrollArea_6</tabstop>
<tabstop>add_macro_push_button</tabstop> <tabstop>opCallEntry</tabstop>
<tabstop>delete_macro_push_button</tabstop> <tabstop>log_as_RTTY_check_box</tabstop>
<tabstop>macros_list_view</tabstop>
<tabstop>prompt_to_log_check_box</tabstop>
<tabstop>udp_server_line_edit</tabstop> <tabstop>udp_server_line_edit</tabstop>
<tabstop>udp_server_port_spin_box</tabstop> <tabstop>udp_server_port_spin_box</tabstop>
<tabstop>udpEnable</tabstop>
<tabstop>accept_udp_requests_check_box</tabstop> <tabstop>accept_udp_requests_check_box</tabstop>
<tabstop>udpWindowToFront</tabstop> <tabstop>use_dynamic_grid</tabstop>
<tabstop>udpWindowRestore</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_slope_ppm_spin_box</tabstop>
<tabstop>calibration_intercept_spin_box</tabstop> <tabstop>calibration_intercept_spin_box</tabstop>
<tabstop>frequencies_table_view</tabstop> <tabstop>frequencies_table_view</tabstop>
<tabstop>auto_switch_bands_check_box</tabstop>
<tabstop>stations_table_view</tabstop> <tabstop>stations_table_view</tabstop>
<tabstop>sbNtrials</tabstop> <tabstop>add_macro_push_button</tabstop>
<tabstop>sbAggressive</tabstop> <tabstop>add_macro_line_edit</tabstop>
<tabstop>cbTwoPass</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>sbDegrade</tabstop>
<tabstop>sbBandwidth</tabstop> <tabstop>sbBandwidth</tabstop>
<tabstop>cbx2ToneSpacing</tabstop> <tabstop>cbx2ToneSpacing</tabstop>
<tabstop>cbx4ToneSpacing</tabstop> <tabstop>cbx4ToneSpacing</tabstop>
<tabstop>sbNtrials</tabstop>
<tabstop>sbAggressive</tabstop>
<tabstop>cbTwoPass</tabstop>
<tabstop>cbFox</tabstop>
<tabstop>cbHound</tabstop>
</tabstops> </tabstops>
<resources/> <resources/>
<connections> <connections>
@@ -4117,12 +4183,12 @@ soundcard changes</string>
</connection> </connection>
</connections> </connections>
<buttongroups> <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_stop_bits_button_group"/>
<buttongroup name="CAT_handshake_button_group"/>
<buttongroup name="split_mode_button_group"/> <buttongroup name="split_mode_button_group"/>
<buttongroup name="PTT_method_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> </buttongroups>
</ui> </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_MAJOR 1)
set (WSJTX_VERSION_MINOR 0) set (WSJTX_VERSION_MINOR 0)
set (WSJTX_VERSION_PATCH 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 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 \ jsc_checker.cpp \
Message.cpp \ Message.cpp \
Inbox.cpp \ Inbox.cpp \
messagewindow.cpp messagewindow.cpp \
SpotClient.cpp
HEADERS += qt_helpers.hpp \ HEADERS += qt_helpers.hpp \
pimpl_h.hpp pimpl_impl.hpp \ pimpl_h.hpp pimpl_impl.hpp \
@@ -113,7 +114,8 @@ HEADERS += qt_helpers.hpp \
jsc_checker.h \ jsc_checker.h \
Message.h \ Message.h \
Inbox.h \ Inbox.h \
messagewindow.h messagewindow.h \
SpotClient.h
INCLUDEPATH += qmake_only INCLUDEPATH += qmake_only
+2 -2
View File
@@ -15,7 +15,7 @@ subroutine extractmessage144(decoded,msgreceived,nhashflag,recent_calls,nrecent)
do ibyte=1,10 do ibyte=1,10
itmp=0 itmp=0
do ibit=1,8 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 enddo
i1Dec8BitBytes(ibyte)=itmp i1Dec8BitBytes(ibyte)=itmp
enddo enddo
@@ -31,7 +31,7 @@ subroutine extractmessage144(decoded,msgreceived,nhashflag,recent_calls,nrecent)
do ibyte=1,12 do ibyte=1,12
itmp=0 itmp=0
do ibit=1,6 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 enddo
i4Dec6BitWords(ibyte)=itmp i4Dec6BitWords(ibyte)=itmp
enddo enddo
+1 -1
View File
@@ -13,7 +13,7 @@ subroutine chkcrc12a(decoded,nbadcrc)
read(cbits,1002) ncrc12 !Received CRC12 read(cbits,1002) ncrc12 !Received CRC12
1002 format(75x,b12) 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 i1Dec8BitBytes(11)=0
icrc12=crc12(c_loc(i1Dec8BitBytes),11) !CRC12 computed from 75 msg bits icrc12=crc12(c_loc(i1Dec8BitBytes),11) !CRC12 computed from 75 msg bits
icrc12=xor(icrc12, 42) ! TODO: jsherer - could change the crc here 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 read(cbits,1002) ncrc12 !Received CRC12
1002 format(75x,b12) 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 i1Dec8BitBytes(11)=0
icrc12=crc12(c_loc(i1Dec8BitBytes),11) !CRC12 computed from 75 msg bits icrc12=crc12(c_loc(i1Dec8BitBytes),11) !CRC12 computed from 75 msg bits
icrc12=xor(icrc12, 42) ! TODO: jsherer - could change the crc here icrc12=xor(icrc12, 42) ! TODO: jsherer - could change the crc here
@@ -29,7 +29,7 @@ subroutine extractmessage174(decoded,msgreceived,ncrcflag)
do ibyte=1,12 do ibyte=1,12
itmp=0 itmp=0
do ibit=1,6 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 enddo
i4Dec6BitWords(ibyte)=itmp i4Dec6BitWords(ibyte)=itmp
enddo enddo
+1 -1
View File
@@ -35,7 +35,7 @@ subroutine genft8(msg,mygrid,bcontest,i3bit,msgsent,msgbits,itone)
1000 format(12b6.6,b8.8) 1000 format(12b6.6,b8.8)
read(cbits,1001) i1Msg8BitBytes(1:10) read(cbits,1001) i1Msg8BitBytes(1:10)
1001 format(10b8) 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 i1Msg8BitBytes(11)=0
icrc12=crc12(c_loc(i1Msg8BitBytes),11) icrc12=crc12(c_loc(i1Msg8BitBytes),11)
icrc12=xor(icrc12, 42) ! TODO: jsherer - could change the crc here 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 checksum = xor(checksum, 42) ! TODO: jsherer - could change the crc here
! For reference, the next 3 lines show how to check the CRC ! For reference, the next 3 lines show how to check the CRC
i1Msg8BitBytes(10)=checksum/256 i1Msg8BitBytes(10)=checksum/256
i1Msg8BitBytes(11)=iand (checksum,255) i1Msg8BitBytes(11)=iand(checksum,transfer(255,0_2))
checksumok = crc12_check(c_loc (i1Msg8BitBytes), 11) checksumok = crc12_check(c_loc (i1Msg8BitBytes), 11)
if( checksumok ) write(*,*) 'Good checksum' if( checksumok ) write(*,*) 'Good checksum'
+1 -1
View File
@@ -95,7 +95,7 @@ do idb = 0, 30
nhashflag=0 nhashflag=0
imsg=0 imsg=0
do i=1,16 do i=1,16
imsg=ishft(imsg,1)+iand(1,decoded(17-i)) imsg=ishft(imsg,1)+iand(1_1,decoded(17-i))
enddo enddo
nrxrpt=iand(imsg,15) nrxrpt=iand(imsg,15)
nrxhash=(imsg-nrxrpt)/16 nrxhash=(imsg-nrxrpt)/16
+1 -1
View File
@@ -129,7 +129,7 @@ subroutine msk40decodeframe(c,mycall,hiscall,xsnr,bswl,nhasharray, &
imsg=0 imsg=0
do i=1,16 do i=1,16
imsg=ishft(imsg,1)+iand(1,decoded(17-i)) imsg=ishft(imsg,1)+iand(1_1,decoded(17-i))
enddo enddo
nrxrpt=iand(imsg,15) nrxrpt=iand(imsg,15)
nrxhash=(imsg-nrxrpt)/16 nrxhash=(imsg-nrxrpt)/16
+15 -6
View File
@@ -101,14 +101,17 @@ void ADIF::load()
, extractField (record, "BAND") , extractField (record, "BAND")
, extractField (record, "MODE") , extractField (record, "MODE")
, extractField (record, "SUBMODE") , extractField (record, "SUBMODE")
, extractField (record, "QSO_DATE")); , extractField (record, "QSO_DATE")
, extractField (record, "NAME")
, extractField (record, "COMMENT")
);
} }
inputFile.close (); 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; QSO q;
q.call = call; q.call = call;
@@ -116,6 +119,9 @@ void ADIF::add(QString const& call, QString const& band, QString const& mode, QS
q.mode = mode; q.mode = mode;
q.submode = submode; q.submode = submode;
q.date = date; q.date = date;
q.name = name;
q.comment = comment;
if (q.call.size ()) if (q.call.size ())
{ {
_data.insert(q.call,q); _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) if ( (band.compare(q.band,Qt::CaseInsensitive) == 0)
|| (band=="") || (band=="")
|| (q.band=="")) || (q.band==""))
{ {
return true; return true;
} }
} }
} }
return false; return false;
} }
QList<ADIF::QSO> ADIF::find(QString const& call) const
{
return _data.values(call);
}
QList<QString> ADIF::getCallList() const QList<QString> ADIF::getCallList() const
{ {
@@ -154,8 +165,6 @@ QList<QString> ADIF::getCallList() const
} }
return p; return p;
} }
int ADIF::getCount() const int ADIF::getCount() const
{ {
+8 -3
View File
@@ -21,10 +21,14 @@ class QDateTime;
class ADIF class ADIF
{ {
public: public:
struct QSO;
void init(QString const& filename); void init(QString const& filename);
void load(); 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; bool match(QString const& call, QString const& band) const;
QList<ADIF::QSO> find(QString const& call) const;
QList<QString> getCallList() const; QList<QString> getCallList() const;
int getCount() const; int getCount() const;
@@ -38,12 +42,13 @@ class ADIF
, QString const& operator_call); , QString const& operator_call);
private:
struct QSO struct QSO
{ {
QString call,band,mode,submode,date; QString call,band,mode,submode,date,name,comment;
}; };
private:
QMultiHash<QString, QSO> _data; QMultiHash<QString, QSO> _data;
QString _filename; QString _filename;
+39 -13
View File
@@ -59,25 +59,51 @@ void LogBook::match(/*in*/const QString call,
bool &callWorkedBefore, bool &callWorkedBefore,
bool &countryWorkedBefore) const bool &countryWorkedBefore) const
{ {
if (call.length() > 0) if(call.isEmpty()){
{ return;
QString currentBand = ""; // match any band }
callWorkedBefore = _log.match(call,currentBand);
countryName = _countries.find(call);
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); countryWorkedBefore = _worked.getHasWorked(countryName);
else } else {
{ countryName = "where?"; //error: prefix not found
countryName = "where?"; //error: prefix not found countryWorkedBefore = false;
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); QString countryName = _countries.find(call);
if (countryName.length() > 0) if (countryName.length() > 0)
_worked.setAsWorked(countryName); _worked.setAsWorked(countryName);
+8 -1
View File
@@ -25,7 +25,14 @@ public:
/*out*/ QString &countryName, /*out*/ QString &countryName,
bool &callWorkedBefore, bool &callWorkedBefore,
bool &countryWorkedBefore) const; 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: private:
CountryDat _countries; CountryDat _countries;
+294 -76
View File
@@ -560,6 +560,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
version (), revision (), version (), revision (),
m_config.udp_server_name (), m_config.udp_server_port (), m_config.udp_server_name (), m_config.udp_server_port (),
this}}, this}},
m_spotClient { new SpotClient{m_messageClient, this}},
m_aprsClient {new APRSISClient{"rotate.aprs2.net", 14580, this}}, m_aprsClient {new APRSISClient{"rotate.aprs2.net", 14580, this}},
psk_Reporter {new PSK_Reporter {m_messageClient, this}}, psk_Reporter {new PSK_Reporter {m_messageClient, this}},
m_i3bit {0}, m_i3bit {0},
@@ -569,6 +570,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
m_txFrameCountEstimate {0}, m_txFrameCountEstimate {0},
m_previousFreq {0}, m_previousFreq {0},
m_hbPaused { false }, m_hbPaused { false },
m_hbAutoAck { true },
m_hbHidden { false }, m_hbHidden { false },
m_hbInterval {0}, m_hbInterval {0},
m_cqInterval {0}, m_cqInterval {0},
@@ -814,6 +816,15 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
}); });
ui->labDialFreqOffset->installEventFilter(ldmp); 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->setVisible(false);
ui->bandComboBox->setModel (m_config.frequencies ()); ui->bandComboBox->setModel (m_config.frequencies ());
ui->bandComboBox->setModelColumn (FrequencyList_v2::frequency_mhz_column); 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){ connect(ui->tableWidgetRXAll->horizontalHeader(), &QHeaderView::customContextMenuRequested, this, [this](QPoint const &point){
QMenu * menu = new QMenu(ui->tableWidgetRXAll); 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)); 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){ connect(ui->tableWidgetCalls->horizontalHeader(), &QHeaderView::customContextMenuRequested, this, [this](QPoint const &point){
QMenu * menu = new QMenu(ui->tableWidgetCalls); 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)); 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(1, 1);
gridButtonLayout->setColumnStretch(2, 1); gridButtonLayout->setColumnStretch(2, 1);
spotSetLocal();
pskSetLocal(); pskSetLocal();
aprsSetLocal(); aprsSetLocal();
@@ -1619,7 +1639,7 @@ MainWindow::MainWindow(QDir const& temp_directory, bool multiple,
if (!m_valid) throw std::runtime_error {"Fatal initialization exception"}; if (!m_valid) throw std::runtime_error {"Fatal initialization exception"};
} }
QDate eol(2019, 3, 11); QDate eol(2019, 4, 1);
void MainWindow::checkExpiryWarningMessage() 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 -10 \u2301 ", 800, now, false, true, true);
displayTextForFreq("KN4CRD: JY1 ACK -12 \u2301 ", 780, now.addSecs(120), 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); displayActivity(true);
} }
@@ -1979,6 +2001,7 @@ void MainWindow::writeSettings()
m_settings->setValue ("JT65AP", ui->actionEnable_AP_JT65->isChecked ()); m_settings->setValue ("JT65AP", ui->actionEnable_AP_JT65->isChecked ());
m_settings->setValue("SortBy", QVariant(m_sortCache)); m_settings->setValue("SortBy", QVariant(m_sortCache));
m_settings->setValue("ShowColumns", QVariant(m_showColumnsCache)); m_settings->setValue("ShowColumns", QVariant(m_showColumnsCache));
m_settings->setValue("HBAutoAck", m_hbAutoAck);
m_settings->setValue("HBHidden", m_hbHidden); m_settings->setValue("HBHidden", m_hbHidden);
m_settings->setValue("HBInterval", m_hbInterval); m_settings->setValue("HBInterval", m_hbInterval);
m_settings->setValue("CQInterval", m_cqInterval); m_settings->setValue("CQInterval", m_cqInterval);
@@ -2113,7 +2136,8 @@ void MainWindow::readSettings()
m_sortCache = m_settings->value("SortBy").toMap(); m_sortCache = m_settings->value("SortBy").toMap();
m_showColumnsCache = m_settings->value("ShowColumns").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_hbInterval = m_settings->value("HBInterval", 0).toInt();
m_cqInterval = m_settings->value("CQInterval", 0).toInt(); m_cqInterval = m_settings->value("CQInterval", 0).toInt();
@@ -2874,6 +2898,7 @@ void MainWindow::openSettings(int tab){
void MainWindow::prepareSpotting(){ void MainWindow::prepareSpotting(){
if(m_config.spot_to_reporting_networks ()){ if(m_config.spot_to_reporting_networks ()){
spotSetLocal();
pskSetLocal(); pskSetLocal();
aprsSetLocal(); aprsSetLocal();
m_aprsClient->setServer(m_config.aprs_server_name(), m_config.aprs_server_port()); 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){ void MainWindow::on_autoReplyButton_toggled(bool checked){
resetPushButtonToggleText(ui->autoReplyButton); resetPushButtonToggleText(ui->autoReplyButton);
// update the HB button immediately
updateRepeatButtonDisplay();
} }
void MainWindow::on_monitorButton_toggled(bool checked){ 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 // Only respond to HEARTBEATS...remember that CQ messages are "Alt" pings
if(decodedtext.isHeartbeat()){ if(decodedtext.isHeartbeat()){
if(decodedtext.isAlt()){ 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. // it is not processed elsewhere, so we need to just log it here.
logCallActivity(cd, true); logCallActivity(cd, true);
@@ -4447,6 +4476,9 @@ void MainWindow::logCallActivity(CallDetail d, bool spot){
if(!d.ackTimestamp.isValid() && old.ackTimestamp.isValid()){ if(!d.ackTimestamp.isValid() && old.ackTimestamp.isValid()){
d.ackTimestamp = old.ackTimestamp; d.ackTimestamp = old.ackTimestamp;
} }
if(!d.cqTimestamp.isValid() && old.cqTimestamp.isValid()){
d.cqTimestamp = old.cqTimestamp;
}
m_callActivity[d.call] = d; m_callActivity[d.call] = d;
} else { } else {
// create // create
@@ -4503,6 +4535,25 @@ QString MainWindow::lookupCallInCompoundCache(QString const &call){
return m_compoundCallCache.value(call, 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){ void MainWindow::pskLogReport(QString mode, int offset, int snr, QString callsign, QString grid){
if(!m_config.spot_to_reporting_networks()) return; 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 char ft8msgbits[75 + 12]; //packed 75 bit ft8 message plus 12-bit CRC
genft8_(message, MyGrid, &bcontest, &m_i3bit, msgsent, const_cast<char *> (ft8msgbits), genft8_(message, MyGrid, &bcontest, &m_i3bit, msgsent, const_cast<char *> (ft8msgbits),
const_cast<int *> (itone), 22, 6, 22); const_cast<int *> (itone), 22, 6, 22);
msgibits = m_i3bit; msgibits = m_i3bit;
msgsent[22]=0; msgsent[22]=0;
m_currentMessage = QString::fromLatin1(msgsent); m_currentMessage = QString::fromLatin1(msgsent).trimmed();
m_currentMessageBits = msgibits; m_currentMessageBits = msgibits;
emitTones();
#if TEST_FOX_WAVE_GEN #if TEST_FOX_WAVE_GEN
if(ui->turboButton->isChecked()) { if(ui->turboButton->isChecked()) {
@@ -5579,22 +5633,32 @@ void MainWindow::on_tx6_editingFinished() //tx6 edited
} }
void MainWindow::cacheActivity(QString key){ void MainWindow::cacheActivity(QString key){
m_callActivityCache[key] = m_callActivity; m_callActivityBandCache[key] = m_callActivity;
m_bandActivityCache[key] = m_bandActivity; m_bandActivityBandCache[key] = m_bandActivity;
m_rxTextCache[key] = ui->textEditRX->toHtml(); m_rxTextBandCache[key] = ui->textEditRX->toHtml();
m_heardGraphIncomingBandCache[key] = m_heardGraphIncoming;
m_heardGraphOutgoingBandCache[key] = m_heardGraphOutgoing;
} }
void MainWindow::restoreActivity(QString key){ void MainWindow::restoreActivity(QString key){
if(m_callActivityCache.contains(key)){ if(m_callActivityBandCache.contains(key)){
m_callActivity = m_callActivityCache[key]; m_callActivity = m_callActivityBandCache[key];
} }
if(m_bandActivityCache.contains(key)){ if(m_bandActivityBandCache.contains(key)){
m_bandActivity = m_bandActivityCache[key]; m_bandActivity = m_bandActivityBandCache[key];
} }
if(m_rxTextCache.contains(key)){ if(m_rxTextBandCache.contains(key)){
ui->textEditRX->setHtml(m_rxTextCache[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); displayActivity(true);
@@ -5717,7 +5781,7 @@ void MainWindow::displayTextForFreq(QString text, int freq, QDateTime date, bool
block = -1; 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 // never cache tx or last lines
if(isTx || isLast) { 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 const& my_call, QString const& my_grid, QByteArray const& ADIF)
{ {
QString date = QSO_date_on.toString("yyyyMMdd"); 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), { sendNetworkMessage("LOG.QSO", QString(ADIF), {
{"UTC.ON", QVariant(QSO_date_on.toMSecsSinceEpoch())}, {"UTC.ON", QVariant(QSO_date_on.toMSecsSinceEpoch())},
@@ -6998,6 +7062,16 @@ void MainWindow::buildFrequencyMenu(QMenu *menu){
} }
void MainWindow::buildHeartbeatMenu(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){ if(m_hbInterval > 0){
auto startStop = menu->addAction(ui->hbMacroButton->isChecked() ? "Stop Heartbeat Timer" : "Start Heartbeat Timer"); auto startStop = menu->addAction(ui->hbMacroButton->isChecked() ? "Stop Heartbeat Timer" : "Start Heartbeat Timer");
connect(startStop, &QAction::triggered, this, [this](){ ui->hbMacroButton->toggle(); }); connect(startStop, &QAction::triggered, this, [this](){ ui->hbMacroButton->toggle(); });
@@ -7117,8 +7191,13 @@ void MainWindow::sendHeartbeat(){
processTxQueue(); processTxQueue();
} }
#define SEND_SNR_IN_ACK 1
void MainWindow::sendHeartbeatAck(QString to, int snr, QString extra){ 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(); 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); 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"){ if(tableKey == "call"){
columnKeys.prepend({"Worked Before Flag", "flag"});
columnKeys.prepend({"Callsign", "callsign"}); columnKeys.prepend({"Callsign", "callsign"});
columnKeys.append({ columnKeys.append({
{"Grid Locator", "grid"}, {"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); 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){ void MainWindow::buildRelayMenu(QMenu *menu){
@@ -7987,6 +8082,9 @@ void MainWindow::on_tableWidgetRXAll_selectionChanged(const QItemSelection &/*se
} else { } else {
placeholderText = QString("Type your outgoing directed message to %1 here.").arg(selectedCall); 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 // TODO: jsherer - move this to a generic "callsign changed" signal
if(m_config.heartbeat_qso_pause()){ 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) // 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 ()) { if (m_config.spot_to_reporting_networks ()) {
spotSetLocal();
pskSetLocal(); pskSetLocal();
aprsSetLocal(); aprsSetLocal();
} }
@@ -8768,6 +8867,16 @@ bool MainWindow::shortList(QString callsign)
return b; 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 () void MainWindow::pskSetLocal ()
{ {
auto info = replaceMacros(m_config.my_info(), buildMacroValues(), true); auto info = replaceMacros(m_config.my_info(), buildMacroValues(), true);
@@ -9078,15 +9187,16 @@ void MainWindow::updateButtonDisplay(){
} }
void MainWindow::updateRepeatButtonDisplay(){ void MainWindow::updateRepeatButtonDisplay(){
auto hbBase = m_hbAutoAck && ui->autoReplyButton->isChecked() ? "HB + ACK" : "HB";
if(ui->hbMacroButton->isChecked() && m_hbInterval > 0 && m_nextHeartbeat.isValid()){ if(ui->hbMacroButton->isChecked() && m_hbInterval > 0 && m_nextHeartbeat.isValid()){
auto secs = DriftingDateTime::currentDateTimeUtc().secsTo(m_nextHeartbeat); auto secs = DriftingDateTime::currentDateTimeUtc().secsTo(m_nextHeartbeat);
if(secs > 0){ if(secs > 0){
ui->hbMacroButton->setText(QString("HB (%1)").arg(secs)); ui->hbMacroButton->setText(QString("%1 (%2)").arg(hbBase).arg(secs));
} else { } else {
ui->hbMacroButton->setText(QString("HB (now)")); ui->hbMacroButton->setText(QString("%1 (now)").arg(hbBase));
} }
} else { } else {
ui->hbMacroButton->setText("HB"); ui->hbMacroButton->setText(hbBase);
} }
if(ui->cqMacroButton->isChecked() && m_cqInterval > 0 && m_nextCQ.isValid()){ if(ui->cqMacroButton->isChecked() && m_cqInterval > 0 && m_nextCQ.isValid()){
@@ -9872,6 +9982,11 @@ void MainWindow::processCommandActivity() {
continue; 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 // 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) { if (!isAllCall && !toMe && !isGroupCall) {
continue; 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 // 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; d.from = d.relayPath;
} }
@@ -10076,7 +10191,7 @@ void MainWindow::processCommandActivity() {
} }
// PROCESS RELAY // 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 // 1. see if there are any more hops to process
// 2. if so, forward // 2. if so, forward
@@ -10088,12 +10203,12 @@ void MainWindow::processCommandActivity() {
auto match = re.match(text); 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 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 // replace freetext with relayed free text
if(match.captured("type") != ">"){ if(match.captured("type") != ">"){
text = text.replace(match.capturedStart("type"), match.capturedLength("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 // 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")) { } else if(!d.text.startsWith("ACK")) {
@@ -10132,23 +10247,35 @@ void MainWindow::processCommandActivity() {
} }
// HACK: "MSG TO:" should be supported but contains a space :( // HACK: "MSG TO:" should be supported but contains a space :(
if(!relayedCmds.isEmpty() && first == " MSG"){ if(!relayedCmds.isEmpty()){
auto second = relayedCmds.first(); if(first == " MSG"){
if(second == "TO:"){ auto second = relayedCmds.first();
first = " MSG TO:"; if(second == "TO:"){
relayedCmds.removeFirst(); first = " MSG TO:";
} else if(second.startsWith("TO:")){ relayedCmds.removeFirst();
first = " MSG TO:"; } else if(second.startsWith("TO:")){
relayedCmds.replace(0, second.mid(3)); 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 = {}; CommandDetail rd = {};
rd.bits = d.bits; rd.bits = d.bits;
rd.cmd = first; rd.cmd = first;
rd.freq = d.freq; 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.relayPath = d.relayPath;
rd.text = relayedCmds.join(" "); //d.text; rd.text = relayedCmds.join(" "); //d.text;
rd.to = d.to; rd.to = d.to;
@@ -10212,7 +10339,7 @@ void MainWindow::processCommandActivity() {
// PROCESS ACTIVE HEARTBEAT // PROCESS ACTIVE HEARTBEAT
// if we have auto reply enabled and we are heartbeating and selcall is not enabled // 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 // check to see if we have a message for a station who is heartbeating
QString extra; QString extra;
@@ -10272,6 +10399,14 @@ void MainWindow::processCommandActivity() {
continue; continue;
} }
// PROCESS NACKS
else if (d.cmd == " NACK" && !isAllCall){
qDebug() << "skipping incoming nack" << d.text;
// make sure this is explicit
continue;
}
// PROCESS BUFFERED CMD // PROCESS BUFFERED CMD
else if (d.cmd == " CMD" && !isAllCall){ else if (d.cmd == " CMD" && !isAllCall){
qDebug() << "skipping incoming command" << d.text; qDebug() << "skipping incoming command" << d.text;
@@ -10282,7 +10417,14 @@ void MainWindow::processCommandActivity() {
// PROCESS BUFFERED QUERY // PROCESS BUFFERED QUERY
else if (d.cmd == " QUERY" && !isAllCall){ 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(" "); QStringList segs = d.text.split(" ");
if(segs.isEmpty()){ if(segs.isEmpty()){
@@ -10327,8 +10469,8 @@ void MainWindow::processCommandActivity() {
inbox.set(mid, msg); inbox.set(mid, msg);
// and reply // and reply
reply = QString("%1 MSG %2 DE %3"); reply = QString("%1 MSG %2 FROM %3");
reply = reply.arg(who); reply = reply.arg(replyPath);
reply = reply.arg(text); reply = reply.arg(text);
reply = reply.arg(from); reply = reply.arg(from);
} }
@@ -10336,23 +10478,35 @@ void MainWindow::processCommandActivity() {
// PROCESS BUFFERED QUERY MSGS // PROCESS BUFFERED QUERY MSGS
else if (d.cmd == " QUERY MSGS" && ui->autoReplyButton->isChecked()){ 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. // 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 // we reply yes if the user would be able to retreive a stored message
auto mid = getNextMessageIdForCallsign(who); auto mid = getNextMessageIdForCallsign(who);
if(mid != -1){ 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 this is not an allcall and we have no messages, reply no.
if(!isAllCall && reply.isEmpty()){ if(!isAllCall && reply.isEmpty()){
reply = QString("%1 NO").arg(who); reply = QString("%1 NO").arg(replyPath);
} }
} }
// PROCESS BUFFERED QUERY CALL // PROCESS BUFFERED QUERY CALL
else if (d.cmd == " QUERY CALL" && ui->autoReplyButton->isChecked()){ else if (d.cmd == " QUERY CALL" && ui->autoReplyButton->isChecked()){
auto replyPath = d.from;
if(d.relayPath.contains(">")){
replyPath = d.relayPath;
}
auto who = d.text; auto who = d.text;
if(who.isEmpty()){ if(who.isEmpty()){
continue; continue;
@@ -10372,20 +10526,20 @@ void MainWindow::processCommandActivity() {
} }
if(baseCall == cd.call || baseCall == Radio::base_callsign(cd.call)){ 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); replies.append(r);
break;
} }
} }
if(!replies.isEmpty()){ 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(!reply.isEmpty()){
if(isAllCall){ if(isAllCall){
// since all pings are technically @ALLCALL, let's bump the allcall cache here...
m_txAllcallCommandCache.insert(d.from, new QDateTime(now), 25); 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 MainWindow::parseRelayPathCallsigns(QString from, QString text){
QStringList calls; 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); QRegularExpression re(callDePattern);
auto iter = re.globalMatch(text); auto iter = re.globalMatch(text);
while(iter.hasNext()){ while(iter.hasNext()){
@@ -10607,7 +10761,7 @@ void MainWindow::processAlertReplyForCommand(CommandDetail d, QString from, QStr
QString fromReplace = QString{}; QString fromReplace = QString{};
foreach(auto call, calls){ foreach(auto call, calls){
fromReplace.append(" DE "); fromReplace.append(" VIA ");
fromReplace.append(call); fromReplace.append(call);
} }
@@ -10676,6 +10830,7 @@ void MainWindow::processSpots() {
auto dial = dialFrequency(); auto dial = dialFrequency();
// Process spots to be sent... // Process spots to be sent...
spotSetLocal();
pskSetLocal(); pskSetLocal();
aprsSetLocal(); aprsSetLocal();
@@ -10687,6 +10842,7 @@ void MainWindow::processSpots() {
qDebug() << "spotting call to reporting networks" << d.call << d.snr << d.freq; 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); pskLogReport("JS8", d.freq, d.snr, d.call, d.grid);
aprsLogReport(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]; QList < ActivityDetail > items = m_bandActivity[offset];
if (items.length() > 0) { if (items.length() > 0) {
QDateTime timestamp;
QStringList text; QStringList text;
QString age; QString age;
int snr = 0; int snr = 0;
@@ -10922,6 +11079,7 @@ void MainWindow::displayBandActivity() {
text.append(item.text); text.append(item.text);
snr = item.snr; snr = item.snr;
age = since(item.utcTimestamp); age = since(item.utcTimestamp);
timestamp = item.utcTimestamp;
tdrift = item.tdrift; tdrift = item.tdrift;
} }
@@ -10938,11 +11096,13 @@ void MainWindow::displayBandActivity() {
offsetItem->setData(Qt::UserRole, QVariant(offset)); offsetItem->setData(Qt::UserRole, QVariant(offset));
ui->tableWidgetRXAll->setItem(row, col++, offsetItem); 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->setTextAlignment(Qt::AlignCenter | Qt::AlignVCenter);
ageItem->setToolTip(timestamp.toString());
ui->tableWidgetRXAll->setItem(row, col++, ageItem); 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); snrItem->setTextAlignment(Qt::AlignCenter | Qt::AlignVCenter);
ui->tableWidgetRXAll->setItem(row, col++, snrItem); ui->tableWidgetRXAll->setItem(row, col++, snrItem);
@@ -11167,7 +11327,12 @@ void MainWindow::displayCallActivity() {
// icon flags (flag -> star -> empty) // icon flags (flag -> star -> empty)
bool hasMessage = m_rxInboxCountCache.value(d.call, 0) > 0; 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) { if (!isCallSelected && !hasMessage && callsignAging && d.utcTimestamp.secsTo(now) / 60 >= callsignAging) {
continue; continue;
@@ -11185,32 +11350,33 @@ void MainWindow::displayCallActivity() {
QString displayCall = d.call; QString displayCall = d.call;
#endif #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); iconItem->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
ui->tableWidgetCalls->setItem(row, col++, iconItem); ui->tableWidgetCalls->setItem(row, col++, iconItem);
if(hasMessage || hasAck){ if(hasMessage || hasACK || hasCQ){
showIconColumn = true; showIconColumn = true;
} }
auto displayItem = new QTableWidgetItem(displayCall); auto displayItem = new QTableWidgetItem(displayCall);
displayItem->setData(Qt::UserRole, QVariant(d.call)); displayItem->setData(Qt::UserRole, QVariant(d.call));
displayItem->setToolTip(generateCallDetail(displayCall)); displayItem->setToolTip(generateCallDetail(displayCall));
ui->tableWidgetCalls->setItem(row, col++, displayItem); ui->tableWidgetCalls->setItem(row, col++, displayItem);
auto flagItem = new QTableWidgetItem(flag); #if ONLY_SHOW_HEARD_CALLSIGNS
flagItem->setTextAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
ui->tableWidgetCalls->setItem(row, col++, flagItem);
if(d.utcTimestamp.isValid()){ if(d.utcTimestamp.isValid()){
ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem(QString("%1").arg(since(d.utcTimestamp)))); #else
ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem(QString("%1 dB").arg(Varicode::formatSNR(d.snr)))); 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)); auto offsetItem = new QTableWidgetItem(QString("%1 Hz").arg(d.freq));
offsetItem->setData(Qt::UserRole, QVariant(d.freq)); offsetItem->setData(Qt::UserRole, QVariant(d.freq));
@@ -11224,7 +11390,38 @@ void MainWindow::displayCallActivity() {
auto distanceItem = new QTableWidgetItem(calculateDistance(d.grid)); auto distanceItem = new QTableWidgetItem(calculateDistance(d.grid));
distanceItem->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter); 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 { } else {
ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem("")); // age 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("")); // tdrift
ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem("")); // grid ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem("")); // grid
ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem("")); // distance ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem("")); // distance
ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem("")); // worked before
//ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem("")); ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem("")); // log name
ui->tableWidgetCalls->setItem(row, col++, new QTableWidgetItem("")); // log comment
} }
if (isCallSelected) { if (isCallSelected) {
@@ -11277,13 +11475,15 @@ void MainWindow::displayCallActivity() {
// Hide columns // Hide columns
ui->tableWidgetCalls->setColumnHidden(0, !showIconColumn); ui->tableWidgetCalls->setColumnHidden(0, !showIconColumn);
ui->tableWidgetCalls->setColumnHidden(1, !showColumn("call", "callsign")); ui->tableWidgetCalls->setColumnHidden(1, !showColumn("call", "callsign"));
ui->tableWidgetCalls->setColumnHidden(2, !showColumn("call", "flag")); ui->tableWidgetCalls->setColumnHidden(2, !showColumn("call", "timestamp"));
ui->tableWidgetCalls->setColumnHidden(3, !showColumn("call", "timestamp")); ui->tableWidgetCalls->setColumnHidden(3, !showColumn("call", "snr"));
ui->tableWidgetCalls->setColumnHidden(4, !showColumn("call", "snr")); ui->tableWidgetCalls->setColumnHidden(4, !showColumn("call", "offset"));
ui->tableWidgetCalls->setColumnHidden(5, !showColumn("call", "offset")); ui->tableWidgetCalls->setColumnHidden(5, !showColumn("call", "tdrift", false));
ui->tableWidgetCalls->setColumnHidden(6, !showColumn("call", "tdrift", false)); ui->tableWidgetCalls->setColumnHidden(6, !showColumn("call", "grid", false));
ui->tableWidgetCalls->setColumnHidden(7, !showColumn("call", "grid", false)); ui->tableWidgetCalls->setColumnHidden(7, !showColumn("call", "distance", false));
ui->tableWidgetCalls->setColumnHidden(8, !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 // Resize the table columns
ui->tableWidgetCalls->resizeColumnToContents(0); ui->tableWidgetCalls->resizeColumnToContents(0);
@@ -11294,6 +11494,8 @@ void MainWindow::displayCallActivity() {
ui->tableWidgetCalls->resizeColumnToContents(5); ui->tableWidgetCalls->resizeColumnToContents(5);
ui->tableWidgetCalls->resizeColumnToContents(6); ui->tableWidgetCalls->resizeColumnToContents(6);
ui->tableWidgetCalls->resizeColumnToContents(7); ui->tableWidgetCalls->resizeColumnToContents(7);
ui->tableWidgetCalls->resizeColumnToContents(8);
ui->tableWidgetCalls->resizeColumnToContents(9);
// Reset the scroll position // Reset the scroll position
ui->tableWidgetCalls->verticalScrollBar()->setValue(currentScrollPos); 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) void MainWindow::networkMessage(Message const &message)
{ {
if(!m_config.udpEnabled()){ if(!m_config.udpEnabled()){
+13 -3
View File
@@ -44,6 +44,7 @@
#include "qpriorityqueue.h" #include "qpriorityqueue.h"
#include "varicode.h" #include "varicode.h"
#include "MessageClient.hpp" #include "MessageClient.hpp"
#include "SpotClient.h"
#include "APRSISClient.h" #include "APRSISClient.h"
#include "keyeater.h" #include "keyeater.h"
@@ -383,6 +384,7 @@ private slots:
void on_cbFirst_toggled(bool b); void on_cbFirst_toggled(bool b);
void on_cbAutoSeq_toggled(bool b); void on_cbAutoSeq_toggled(bool b);
void emitPTT(bool on); void emitPTT(bool on);
void emitTones();
void networkMessage(Message const &message); void networkMessage(Message const &message);
void sendNetworkMessage(QString const &type, QString const &message); void sendNetworkMessage(QString const &type, QString const &message);
void sendNetworkMessage(QString const &type, QString const &message, const QMap<QString, QVariant> &params); void sendNetworkMessage(QString const &type, QString const &message, const QMap<QString, QVariant> &params);
@@ -712,6 +714,7 @@ private:
QString through; QString through;
QString grid; QString grid;
int freq; int freq;
QDateTime cqTimestamp;
QDateTime ackTimestamp; QDateTime ackTimestamp;
QDateTime utcTimestamp; QDateTime utcTimestamp;
int snr; int snr;
@@ -826,9 +829,11 @@ private:
QMap<QString, int> m_rxInboxCountCache; // call -> count QMap<QString, int> m_rxInboxCountCache; // call -> count
QMap<QString, QMap<QString, CallDetail>> m_callActivityCache; // band -> call activity QMap<QString, QMap<QString, CallDetail>> m_callActivityBandCache; // band -> call activity
QMap<QString, QMap<int, QList<ActivityDetail>>> m_bandActivityCache; // band -> band activity QMap<QString, QMap<int, QList<ActivityDetail>>> m_bandActivityBandCache; // band -> band activity
QMap<QString, QString> m_rxTextCache; // band -> rx text 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; JSCChecker * m_checker;
@@ -856,6 +861,7 @@ private:
QQueue<QString> m_foxQSOinProgress; //QSOs in progress: Fox has sent a report QQueue<QString> m_foxQSOinProgress; //QSOs in progress: Fox has sent a report
QQueue<qint64> m_foxRateQueue; QQueue<qint64> m_foxRateQueue;
bool m_hbAutoAck;
bool m_hbHidden; bool m_hbHidden;
int m_hbInterval; int m_hbInterval;
int m_cqInterval; int m_cqInterval;
@@ -891,6 +897,7 @@ private:
QProgressDialog m_optimizingProgress; QProgressDialog m_optimizingProgress;
MessageClient * m_messageClient; MessageClient * m_messageClient;
PSK_Reporter *psk_Reporter; PSK_Reporter *psk_Reporter;
SpotClient *m_spotClient;
APRSISClient * m_aprsClient; APRSISClient * m_aprsClient;
DisplayManual m_manual; DisplayManual m_manual;
QHash<QString, QVariant> m_pwrBandTxMemory; // Remembers power level by band QHash<QString, QVariant> m_pwrBandTxMemory; // Remembers power level by band
@@ -915,8 +922,11 @@ private:
bool shortList(QString callsign); bool shortList(QString callsign);
void transmit (double snr = 99.); void transmit (double snr = 99.);
void rigFailure (QString const& reason); void rigFailure (QString const& reason);
void spotSetLocal();
void pskSetLocal (); void pskSetLocal ();
void aprsSetLocal (); 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 pskLogReport(QString mode, int offset, int snr, QString callsign, QString grid);
void aprsLogReport(int offset, int snr, QString callsign, QString grid); void aprsLogReport(int offset, int snr, QString callsign, QString grid);
Radio::Frequency dialFrequency(); Radio::Frequency dialFrequency();
+26 -10
View File
@@ -203,7 +203,7 @@
</size> </size>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Current Frequency</string> <string>Set Current Frequency</string>
</property> </property>
<property name="frameShape"> <property name="frameShape">
<enum>QFrame::NoFrame</enum> <enum>QFrame::NoFrame</enum>
@@ -297,7 +297,7 @@ QPushButton[oob=&quot;true&quot;] {
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Current frequency offset</string> <string>Set Current Frequency Offset</string>
</property> </property>
<property name="styleSheet"> <property name="styleSheet">
<string notr="true">QLabel { <string notr="true">QLabel {
@@ -401,6 +401,12 @@ color : white;
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="mouseTracking">
<bool>true</bool>
</property>
<property name="toolTip">
<string>Set Callsign and Grid</string>
</property>
<property name="styleSheet"> <property name="styleSheet">
<string notr="true">QLabel { <string notr="true">QLabel {
font-family: MS Shell Dlg 2; font-family: MS Shell Dlg 2;
@@ -1242,14 +1248,6 @@ QTextEdit[transmitting=&quot;true&quot;] {
<string>Callsigns</string> <string>Callsigns</string>
</property> </property>
</column> </column>
<column>
<property name="text">
<string>✓</string>
</property>
<property name="textAlignment">
<set>AlignCenter</set>
</property>
</column>
<column> <column>
<property name="text"> <property name="text">
<string>Age</string> <string>Age</string>
@@ -1280,6 +1278,24 @@ QTextEdit[transmitting=&quot;true&quot;] {
<string>Distance</string> <string>Distance</string>
</property> </property>
</column> </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> </widget>
</widget> </widget>
+8 -7
View File
@@ -50,8 +50,9 @@ QMap<QString, int> directed_cmds = {
{" SNR?", 0 }, // query snr {" SNR?", 0 }, // query snr
{"?", 0 }, // compat {"?", 0 }, // compat
//{" ", 1 }, // unused {" DIT DIT", 1 }, // unused
//{" ", 2 }, // unused
{" NACK", 2 }, // negative acknowledge
{" HEARING?", 3 }, // query station calls heard {" HEARING?", 3 }, // query station calls heard
@@ -104,7 +105,7 @@ QMap<QString, int> directed_cmds = {
}; };
// commands allowed to be processed // 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) // 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}; 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 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_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_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("^" + QRegularExpression directed_re("^" +
callsign_pattern + callsign_pattern +
@@ -1545,8 +1546,8 @@ QStringList Varicode::unpackDirectedMessage(const QString &text, quint8 *pType){
quint32 packed_to = Varicode::bitsToInt(bits.mid(31, 28)); quint32 packed_to = Varicode::bitsToInt(bits.mid(31, 28));
quint8 packed_cmd = Varicode::bitsToInt(bits.mid(59, 5)); quint8 packed_cmd = Varicode::bitsToInt(bits.mid(59, 5));
bool portable_from = (extra >> 7) & 1 == 1; bool portable_from = ((extra >> 7) & 1) == 1;
bool portable_to = (extra >> 6) & 1 == 1; bool portable_to = ((extra >> 6) & 1) == 1;
extra = extra % 64; extra = extra % 64;
QString from = Varicode::unpackCallsign(packed_from, portable_from); QString from = Varicode::unpackCallsign(packed_from, portable_from);