From 690daadf508e1c7543b340aec6ba6ebf3a687e77 Mon Sep 17 00:00:00 2001 From: Stelios Bounanos Date: Thu, 20 Nov 2008 15:13:27 +0000 Subject: [PATCH] Add spotting * Add hooks to the logger and received text handlers * Add support for N1DQ's PSK Automatic Propagation Reporter --- src/Makefile.am | 4 + src/dialogs/Viewer.cxx | 4 + src/dialogs/confdialog.cxx | 132 +++++- src/dialogs/confdialog.fl | 98 +++- src/dialogs/fl_digi.cxx | 83 +++- src/include/confdialog.h | 14 +- src/include/configuration.h | 10 +- src/include/debug.h | 1 + src/include/fl_digi.h | 3 + src/include/pixmaps.h | 1 + src/include/pskrep.h | 8 + src/include/spot.h | 48 ++ src/include/status.h | 4 +- src/include/util.h | 5 + src/logger/logger.cxx | 6 + src/main.cxx | 6 + src/misc/configuration.cxx | 1 + src/misc/pixmaps.cxx | 26 ++ src/misc/status.cxx | 12 +- src/misc/util.cxx | 14 + src/psk/viewpsk.cxx | 4 +- src/spot/pskrep.cxx | 906 ++++++++++++++++++++++++++++++++++++ src/spot/spot.cxx | 203 ++++++++ 23 files changed, 1551 insertions(+), 42 deletions(-) create mode 100644 src/include/pskrep.h create mode 100644 src/include/spot.h create mode 100644 src/spot/pskrep.cxx create mode 100644 src/spot/spot.cxx diff --git a/src/Makefile.am b/src/Makefile.am index 50b2e9d6..aa5e3045 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -212,6 +212,7 @@ fldigi_SOURCES += \ include/pskeval.h \ include/ptt.h \ include/pixmaps.h \ + include/pskrep.h \ include/qrunner.h \ include/lookupcall.h \ include/qrzlib.h \ @@ -230,6 +231,7 @@ fldigi_SOURCES += \ include/socket.h \ include/sound.h \ include/soundconf.h \ + include/spot.h \ include/stacktrace.h \ include/status.h \ include/testmodem.h \ @@ -302,6 +304,8 @@ fldigi_SOURCES += \ soundcard/mixer.cxx \ soundcard/sound.cxx \ soundcard/soundconf.cxx \ + spot/pskrep.cxx \ + spot/spot.cxx \ throb/throb.cxx \ trx/modem.cxx \ trx/trx.cxx \ diff --git a/src/dialogs/Viewer.cxx b/src/dialogs/Viewer.cxx index 4e8ed3f7..ad16ad1c 100644 --- a/src/dialogs/Viewer.cxx +++ b/src/dialogs/Viewer.cxx @@ -10,6 +10,7 @@ #include "waterfall.h" #include "fl_digi.h" #include "re.h" +#include "spot.h" #include #include @@ -350,6 +351,9 @@ void viewer_redraw() void viewaddchr(int ch, int freq, char c) { if (!dlgViewer) return; + if (progStatus.spot_recv) + spot_recv(c, ch, freq); + if (rfc != wf->rfcarrier() || usb != wf->USB()) viewer_redraw(); static string nuline; diff --git a/src/dialogs/confdialog.cxx b/src/dialogs/confdialog.cxx index 0286d20a..56aa12c0 100644 --- a/src/dialogs/confdialog.cxx +++ b/src/dialogs/confdialog.cxx @@ -1,4 +1,4 @@ -// generated by Fast Light User Interface Designer (fluid) version 1.0108 +// generated by Fast Light User Interface Designer (fluid) version 1.0109 #include "confdialog.h" #include @@ -13,6 +13,7 @@ #include "icons.h" #include "Viewer.h" extern void initViewer(); +#include "pskrep.h" Fl_Double_Window *dlgConfig; void set_qrz_buttons(Fl_Button* b) { @@ -156,6 +157,65 @@ static void cb_btnStartAtSweetSpot(Fl_Check_Button* o, void*) { progdefaults.changed = true; } +Fl_Group *tabSpot=(Fl_Group *)0; + +Fl_Check_Button *btnPSKRepAuto=(Fl_Check_Button *)0; + +static void cb_btnPSKRepAuto(Fl_Check_Button* o, void*) { + progdefaults.pskrep_auto = o->value(); +btnPSKRepInit->labelcolor(FL_RED); +btnPSKRepInit->redraw_label(); +progdefaults.changed = true; +} + +Fl_Check_Button *btnPSKRepLog=(Fl_Check_Button *)0; + +static void cb_btnPSKRepLog(Fl_Check_Button* o, void*) { + progdefaults.pskrep_log = o->value(); +btnPSKRepInit->labelcolor(FL_RED); +btnPSKRepInit->redraw_label(); +progdefaults.changed = true; +} + +Fl_Input *inpPSKRepHost=(Fl_Input *)0; + +static void cb_inpPSKRepHost(Fl_Input* o, void*) { + progdefaults.pskrep_host = o->value(); +btnPSKRepInit->labelcolor(FL_RED); +btnPSKRepInit->redraw_label(); +progdefaults.changed = true; +} + +Fl_Input *inpPSKRepPort=(Fl_Input *)0; + +static void cb_inpPSKRepPort(Fl_Input* o, void*) { + progdefaults.pskrep_port = o->value(); +btnPSKRepInit->labelcolor(FL_RED); +btnPSKRepInit->redraw_label(); +progdefaults.changed = true; +} + +Fl_Button *btnPSKRepInit=(Fl_Button *)0; + +static void cb_btnPSKRepInit(Fl_Button* o, void*) { + pskrep_stop(); +if (!pskrep_start()) + boxPSKRepMsg->copy_label(pskrep_error()); +else { + boxPSKRepMsg->label(0); + o->labelcolor(FL_FOREGROUND_COLOR); +}; +} + +Fl_Box *boxPSKRepMsg=(Fl_Box *)0; + +Fl_Check_Button *btnPSKRepQRG=(Fl_Check_Button *)0; + +static void cb_btnPSKRepQRG(Fl_Check_Button* o, void*) { + progdefaults.pskrep_qrg = o->value(); +progdefaults.changed = true; +} + Fl_Group *tabModems=(Fl_Group *)0; Fl_Tabs *tabsModems=(Fl_Tabs *)0; @@ -734,6 +794,13 @@ static void cb_inpMyQth(Fl_Input* o, void*) { progdefaults.changed = true; } +Fl_Input *inpMyAntenna=(Fl_Input *)0; + +static void cb_inpMyAntenna(Fl_Input* o, void*) { + progdefaults.myAntenna = o->value(); +progdefaults.changed = true; +} + Fl_Input *inpMyLocator=(Fl_Input *)0; static void cb_inpMyLocator(Fl_Input* o, void*) { @@ -1802,7 +1869,7 @@ static const char szStopBits[] = "1|1.5|2"; static const char szOliviaTones[] = "2|4|8|16|32|64|128|256"; static const char szOliviaBandwidth[] = "125|250|500|1000|2000"; static const char szBaudRates[] = "300|600|1200|2400|4800|9600|19200|38400|57600|115200|230400|460800"; - { Fl_Double_Window* o = new Fl_Double_Window(400, 255, "fldigi - config"); + { Fl_Double_Window* o = new Fl_Double_Window(400, 254, "fldigi - config"); w = o; o->tooltip("Leading/Trailing Risetimes (msec)"); o->color(FL_DARK2); @@ -1902,7 +1969,6 @@ static const char szBaudRates[] = "300|600|1200|2400|4800|9600|19200|38400|57600 { tabMisc = new Fl_Group(0, 25, 400, 195, "Misc"); tabMisc->color((Fl_Color)51); tabMisc->selection_color((Fl_Color)51); - tabMisc->hide(); { tabsMisc = new Fl_Tabs(0, 25, 400, 195); tabsMisc->selection_color((Fl_Color)10); { tabCPUspeed = new Fl_Group(0, 50, 400, 170, "CPU"); @@ -1940,6 +2006,7 @@ static const char szBaudRates[] = "300|600|1200|2400|4800|9600|19200|38400|57600 tabMacros->end(); } // Fl_Group* tabMacros { tabSweetSpot = new Fl_Group(0, 50, 400, 170, "Sweet Spot"); + tabSweetSpot->hide(); { Fl_Group* o = new Fl_Group(5, 60, 390, 75); o->box(FL_ENGRAVED_FRAME); o->align(FL_ALIGN_TOP_LEFT|FL_ALIGN_INSIDE); @@ -1977,6 +2044,46 @@ static const char szBaudRates[] = "300|600|1200|2400|4800|9600|19200|38400|57600 } // Fl_Group* o tabSweetSpot->end(); } // Fl_Group* tabSweetSpot + { tabSpot = new Fl_Group(0, 50, 400, 170, "Spotting"); + { Fl_Group* o = new Fl_Group(2, 57, 395, 160, "PSK Reporter"); + o->box(FL_ENGRAVED_FRAME); + o->align(FL_ALIGN_TOP_LEFT|FL_ALIGN_INSIDE); + { btnPSKRepAuto = new Fl_Check_Button(15, 80, 324, 20, "Automatically spot callsigns in decoded text"); + btnPSKRepAuto->down_box(FL_DOWN_BOX); + btnPSKRepAuto->callback((Fl_Callback*)cb_btnPSKRepAuto); + btnPSKRepAuto->value(progdefaults.pskrep_auto); + } // Fl_Check_Button* btnPSKRepAuto + { btnPSKRepLog = new Fl_Check_Button(15, 104, 327, 20, "Send reception report when logging a QSO"); + btnPSKRepLog->down_box(FL_DOWN_BOX); + btnPSKRepLog->callback((Fl_Callback*)cb_btnPSKRepLog); + btnPSKRepLog->value(progdefaults.pskrep_log); + } // Fl_Check_Button* btnPSKRepLog + { inpPSKRepHost = new Fl_Input(56, 156, 220, 20, "Host:"); + inpPSKRepHost->callback((Fl_Callback*)cb_inpPSKRepHost); + inpPSKRepHost->when(FL_WHEN_CHANGED); + inpPSKRepHost->value(progdefaults.pskrep_host.c_str()); + } // Fl_Input* inpPSKRepHost + { inpPSKRepPort = new Fl_Input(323, 156, 60, 20, "Port:"); + inpPSKRepPort->callback((Fl_Callback*)cb_inpPSKRepPort); + inpPSKRepPort->when(FL_WHEN_CHANGED); + inpPSKRepPort->value(progdefaults.pskrep_port.c_str()); + } // Fl_Input* inpPSKRepPort + { btnPSKRepInit = new Fl_Button(303, 186, 80, 24, "Initialize"); + btnPSKRepInit->callback((Fl_Callback*)cb_btnPSKRepInit); + } // Fl_Button* btnPSKRepInit + { boxPSKRepMsg = new Fl_Box(56, 186, 220, 24, ""); + boxPSKRepMsg->labelfont(2); + boxPSKRepMsg->label(0); + } // Fl_Box* boxPSKRepMsg + { btnPSKRepQRG = new Fl_Check_Button(15, 128, 357, 20, "Report QRG (enable only if you have rig control!)"); + btnPSKRepQRG->down_box(FL_DOWN_BOX); + btnPSKRepQRG->callback((Fl_Callback*)cb_btnPSKRepQRG); + btnPSKRepQRG->value(progdefaults.pskrep_qrg); + } // Fl_Check_Button* btnPSKRepQRG + o->end(); + } // Fl_Group* o + tabSpot->end(); + } // Fl_Group* tabSpot tabsMisc->end(); } // Fl_Tabs* tabsMisc tabMisc->end(); @@ -2108,7 +2215,6 @@ static const char szBaudRates[] = "300|600|1200|2400|4800|9600|19200|38400|57600 cntCWdash2dot->type(1); cntCWdash2dot->minimum(2.5); cntCWdash2dot->maximum(4); - cntCWdash2dot->step(0.1); cntCWdash2dot->value(3); cntCWdash2dot->callback((Fl_Callback*)cb_cntCWdash2dot); cntCWdash2dot->align(FL_ALIGN_LEFT); @@ -2118,7 +2224,6 @@ static const char szBaudRates[] = "300|600|1200|2400|4800|9600|19200|38400|57600 cntCWrisetime->type(1); cntCWrisetime->minimum(0); cntCWrisetime->maximum(15); - cntCWrisetime->step(0.1); cntCWrisetime->value(4); cntCWrisetime->callback((Fl_Callback*)cb_cntCWrisetime); cntCWrisetime->align(FL_ALIGN_LEFT); @@ -2182,7 +2287,6 @@ static const char szBaudRates[] = "300|600|1200|2400|4800|9600|19200|38400|57600 valDominoEX_BW->type(1); valDominoEX_BW->minimum(1); valDominoEX_BW->maximum(2); - valDominoEX_BW->step(0.1); valDominoEX_BW->value(1.5); valDominoEX_BW->callback((Fl_Callback*)cb_valDominoEX_BW); o->value(progdefaults.DOMINOEX_BW); @@ -2213,7 +2317,6 @@ static const char szBaudRates[] = "300|600|1200|2400|4800|9600|19200|38400|57600 { Fl_Value_Slider* o = valDomCWI = new Fl_Value_Slider(125, 179, 260, 21, "CWI threshold:"); valDomCWI->tooltip("CWI detection and suppression"); valDomCWI->type(1); - valDomCWI->step(0.01); valDomCWI->textsize(14); valDomCWI->callback((Fl_Callback*)cb_valDomCWI); valDomCWI->align(FL_ALIGN_LEFT); @@ -2608,7 +2711,6 @@ static const char szBaudRates[] = "300|600|1200|2400|4800|9600|19200|38400|57600 valTHOR_BW->type(1); valTHOR_BW->minimum(1); valTHOR_BW->maximum(2); - valTHOR_BW->step(0.1); valTHOR_BW->value(1.5); valTHOR_BW->callback((Fl_Callback*)cb_valTHOR_BW); o->value(progdefaults.THOR_BW); @@ -2639,7 +2741,6 @@ static const char szBaudRates[] = "300|600|1200|2400|4800|9600|19200|38400|57600 { Fl_Value_Slider* o = valThorCWI = new Fl_Value_Slider(120, 174, 260, 21, "CWI threshold:"); valThorCWI->tooltip("CWI detection and suppression"); valThorCWI->type(1); - valThorCWI->step(0.01); valThorCWI->textsize(14); valThorCWI->callback((Fl_Callback*)cb_valThorCWI); valThorCWI->align(FL_ALIGN_LEFT); @@ -2656,15 +2757,19 @@ static const char szBaudRates[] = "300|600|1200|2400|4800|9600|19200|38400|57600 tabOperator->selection_color((Fl_Color)51); tabOperator->callback((Fl_Callback*)cb_tabOperator); tabOperator->when(FL_WHEN_CHANGED); - { inpMyCallsign = new Fl_Input(78, 36, 85, 24, "Callsign:"); + tabOperator->hide(); + { inpMyCallsign = new Fl_Input(78, 36, 100, 24, "Callsign:"); inpMyCallsign->callback((Fl_Callback*)cb_inpMyCallsign); } // Fl_Input* inpMyCallsign - { inpMyName = new Fl_Input(78, 62, 120, 24, "Name:"); + { inpMyName = new Fl_Input(270, 36, 120, 24, "Name:"); inpMyName->callback((Fl_Callback*)cb_inpMyName); } // Fl_Input* inpMyName - { inpMyQth = new Fl_Input(78, 89, 312, 24, "Qth:"); + { inpMyQth = new Fl_Input(78, 62, 312, 24, "Qth:"); inpMyQth->callback((Fl_Callback*)cb_inpMyQth); } // Fl_Input* inpMyQth + { inpMyAntenna = new Fl_Input(78, 89, 312, 24, "Antenna:"); + inpMyAntenna->callback((Fl_Callback*)cb_inpMyAntenna); + } // Fl_Input* inpMyAntenna { inpMyLocator = new Fl_Input(78, 116, 85, 24, "Locator:"); inpMyLocator->callback((Fl_Callback*)cb_inpMyLocator); } // Fl_Input* inpMyLocator @@ -2696,7 +2801,7 @@ static const char szBaudRates[] = "300|600|1200|2400|4800|9600|19200|38400|57600 } // Fl_Group* o tabOperator->end(); } // Fl_Group* tabOperator - { tabQRZ = new Fl_Group(0, 25, 400, 195, "Qrz"); + { tabQRZ = new Fl_Group(0, 25, 400, 195, "QRZ"); tabQRZ->color((Fl_Color)51); tabQRZ->selection_color((Fl_Color)51); tabQRZ->hide(); @@ -3209,7 +3314,6 @@ l with your sound hardware."); valPCMvolume->type(5); valPCMvolume->color((Fl_Color)26); valPCMvolume->selection_color((Fl_Color)1); - valPCMvolume->step(0.01); valPCMvolume->value(0.8); valPCMvolume->textsize(14); valPCMvolume->callback((Fl_Callback*)cb_valPCMvolume); diff --git a/src/dialogs/confdialog.fl b/src/dialogs/confdialog.fl index 1005fc6f..958935b9 100644 --- a/src/dialogs/confdialog.fl +++ b/src/dialogs/confdialog.fl @@ -1,5 +1,5 @@ # data file for the Fltk User Interface Designer (fluid) -version 1.0108 +version 1.0109 header_name {.h} code_name {.cxx} decl {\#include } {} @@ -35,6 +35,9 @@ decl {\#include "Viewer.h"} {} decl {extern void initViewer();} {} +decl {\#include "pskrep.h"} {global +} + decl {Fl_Double_Window *dlgConfig;} {public } @@ -59,8 +62,8 @@ static const char szOliviaBandwidth[] = "125|250|500|1000|2000"; static const char szBaudRates[] = "300|600|1200|2400|4800|9600|19200|38400|57600|115200|230400|460800";} {} code {} {} Fl_Window {} { - label {fldigi - config} open - tooltip {Leading/Trailing Risetimes (msec)} xywh {606 597 400 255} type Double color 45 selection_color 51 labelsize 18 align 80 visible + label {fldigi - config} open selected + tooltip {Leading/Trailing Risetimes (msec)} xywh {951 749 400 254} type Double color 45 selection_color 51 labelsize 18 align 80 visible } { Fl_Tabs tabsConfigure {open xywh {0 0 405 221} color 47 selection_color 9 @@ -154,7 +157,7 @@ progdefaults.changed = true;} } Fl_Group tabMisc { label Misc open - xywh {0 25 400 195} color 51 selection_color 51 hide + xywh {0 25 400 195} color 51 selection_color 51 } { Fl_Tabs tabsMisc {open xywh {0 25 400 195} selection_color 10 @@ -201,7 +204,7 @@ progdefaults.changed = true;} } Fl_Group tabSweetSpot { label {Sweet Spot} open - xywh {0 50 400 170} + xywh {0 50 400 170} hide } { Fl_Group {} {open xywh {5 60 390 75} box ENGRAVED_FRAME align 21 @@ -236,6 +239,75 @@ progdefaults.changed = true;} } } } + Fl_Group tabSpot { + label Spotting open + xywh {0 50 400 170} + } { + Fl_Group {} { + label {PSK Reporter} open + xywh {2 57 395 160} box ENGRAVED_FRAME align 21 + } { + Fl_Check_Button btnPSKRepAuto { + label {Automatically spot callsigns in decoded text} + callback {progdefaults.pskrep_auto = o->value(); +btnPSKRepInit->labelcolor(FL_RED); +btnPSKRepInit->redraw_label(); +progdefaults.changed = true;} + xywh {15 80 324 20} down_box DOWN_BOX + code0 {btnPSKRepAuto->value(progdefaults.pskrep_auto);} + } + Fl_Check_Button btnPSKRepLog { + label {Send reception report when logging a QSO} + callback {progdefaults.pskrep_log = o->value(); +btnPSKRepInit->labelcolor(FL_RED); +btnPSKRepInit->redraw_label(); +progdefaults.changed = true;} + xywh {15 104 327 20} down_box DOWN_BOX + code0 {btnPSKRepLog->value(progdefaults.pskrep_log);} + } + Fl_Input inpPSKRepHost { + label {Host:} + callback {progdefaults.pskrep_host = o->value(); +btnPSKRepInit->labelcolor(FL_RED); +btnPSKRepInit->redraw_label(); +progdefaults.changed = true;} + xywh {56 156 220 20} when 1 + code0 {inpPSKRepHost->value(progdefaults.pskrep_host.c_str());} + } + Fl_Input inpPSKRepPort { + label {Port:} + callback {progdefaults.pskrep_port = o->value(); +btnPSKRepInit->labelcolor(FL_RED); +btnPSKRepInit->redraw_label(); +progdefaults.changed = true;} + xywh {323 156 60 20} when 1 + code0 {inpPSKRepPort->value(progdefaults.pskrep_port.c_str());} + } + Fl_Button btnPSKRepInit { + label Initialize + callback {pskrep_stop(); +if (!pskrep_start()) + boxPSKRepMsg->copy_label(pskrep_error()); +else { + boxPSKRepMsg->label(0); + o->labelcolor(FL_FOREGROUND_COLOR); +}} + xywh {303 186 80 24} + } + Fl_Box boxPSKRepMsg { + label {} + xywh {56 186 220 24} labelfont 2 + code0 {boxPSKRepMsg->label(0);} + } + Fl_Check_Button btnPSKRepQRG { + label {Report QRG (enable only if you have rig control!)} + callback {progdefaults.pskrep_qrg = o->value(); +progdefaults.changed = true;} + xywh {15 128 357 20} down_box DOWN_BOX + code0 {btnPSKRepQRG->value(progdefaults.pskrep_qrg);} + } + } + } } } Fl_Group tabModems { @@ -894,8 +966,8 @@ progdefaults.changed = true;} } Fl_Group tabOperator { label Oper - callback {progdefaults.changed = true;} open selected - xywh {0 25 400 195} color 51 selection_color 51 when 1 + callback {progdefaults.changed = true;} open + xywh {0 25 400 195} color 51 selection_color 51 when 1 hide } { Fl_Input inpMyCallsign { label {Callsign:} @@ -912,17 +984,23 @@ txtSecondary->value(progdefaults.secText.c_str()); progdefaults.myCall = o->value(); update_main_title(); progdefaults.changed = true;} - xywh {78 36 85 24} + xywh {78 36 100 24} } Fl_Input inpMyName { label {Name:} callback {progdefaults.myName = o->value(); progdefaults.changed = true;} - xywh {78 62 120 24} + xywh {270 36 120 24} } Fl_Input inpMyQth { label {Qth:} callback {progdefaults.myQth = o->value(); +progdefaults.changed = true;} + xywh {78 62 312 24} + } + Fl_Input inpMyAntenna { + label {Antenna:} + callback {progdefaults.myAntenna = o->value(); progdefaults.changed = true;} xywh {78 89 312 24} } @@ -957,7 +1035,7 @@ progdefaults.changed = true;} } } Fl_Group tabQRZ { - label Qrz open + label QRZ open xywh {0 25 400 195} color 51 selection_color 51 hide } { Fl_Round_Button btnQRZnotavailable { diff --git a/src/dialogs/fl_digi.cxx b/src/dialogs/fl_digi.cxx index a505d2b4..d5609a27 100644 --- a/src/dialogs/fl_digi.cxx +++ b/src/dialogs/fl_digi.cxx @@ -111,6 +111,7 @@ #include "debug.h" #include "re.h" #include "network.h" +#include "spot.h" Fl_Double_Window *fl_digi_main=(Fl_Double_Window *)0; Fl_Help_Dialog *help_dialog = (Fl_Help_Dialog *)0; @@ -120,9 +121,9 @@ MixerBase* mixer = 0; //bool useCheckButtons; - -Fl_Light_Button *btnTune = (Fl_Light_Button *)0; +Fl_Light_Button *btnAutoSpot = (Fl_Light_Button *)0; Fl_Light_Button *btnRSID = (Fl_Light_Button *)0; +Fl_Light_Button *btnTune = (Fl_Light_Button *)0; Fl_Tile_check *TiledGroup = 0; FTextView *ReceiveText = 0; @@ -805,6 +806,11 @@ void cb_mnuPlayback(Fl_Widget *w, void *d) m->flags &= ~FL_MENU_VALUE; playval = false; } + else if (btnAutoSpot->value()) { + put_status("Spotting disabled", 3.0); + btnAutoSpot->value(0); + btnAutoSpot->do_callback(); + } } #endif // USE_SNDFILE @@ -854,6 +860,11 @@ void cb_mnuVisitURL(Fl_Widget*, void* arg) restoreFocus(); } +void cb_mnuVisitPSKRep(Fl_Widget*, void*) +{ + cb_mnuVisitURL(0, (void*)string("http://pskreporter.info/pskmap?").append(progdefaults.myCall).c_str()); +} + void html_help( const string &Html) { if (!help_dialog) @@ -1085,6 +1096,11 @@ void cbRSID(Fl_Widget *w, void *) { restoreFocus(); } +void cbAutoSpot(Fl_Widget* w, void*) +{ + progStatus.spot_recv = static_cast(w)->value(); +} + void toggleRSID() { btnRSID->value(0); @@ -1185,8 +1201,21 @@ void qsoSave_cb(Fl_Widget *b, void *) void cb_QRZ(Fl_Widget *b, void *) { - CALLSIGNquery(); - oktoclear = false; + if (!*inpCall->value()) + return; + + switch (Fl::event_button()) { + case FL_LEFT_MOUSE: + CALLSIGNquery(); + oktoclear = false; + break; + case FL_RIGHT_MOUSE: + if (quick_choice(string("Spot \"").append(inpCall->value()).append("\"?").c_str(), false)) + spot_manual(inpCall->value(), inpLoc->value()); + break; + default: + break; + } } void status_cb(Fl_Widget *b, void *arg) @@ -1497,7 +1526,7 @@ Fl_Menu_Item menu_[] = { { make_icon_label("Rig Control", multimedia_player_icon), 0, (Fl_Callback*)cb_mnuRig, 0, 0, _FL_MULTI_LABEL, 0, 14, 0}, {0,0,0,0,0,0,0,0,0}, -{" ", 0, 0, 0, FL_MENU_INACTIVE, FL_NORMAL_LABEL, 0, 14, 0}, +{" ", 0, 0, 0, FL_MENU_INACTIVE, FL_NORMAL_LABEL, 0, 14, 0}, {"Help", 0, 0, 0, FL_SUBMENU, FL_NORMAL_LABEL, 0, 14, 0}, #ifndef NDEBUG // settle the gmfsk vs fldigi argument once and for all @@ -1505,7 +1534,8 @@ Fl_Menu_Item menu_[] = { #endif { make_icon_label("Beginners' Guide", start_here_icon), 0, cb_mnuBeginnersURL, 0, 0, _FL_MULTI_LABEL, 0, 14, 0}, { make_icon_label("Online documentation...", help_browser_icon), 0, cb_mnuVisitURL, (void *)PACKAGE_DOCS, 0, _FL_MULTI_LABEL, 0, 14, 0}, -{ make_icon_label("Fldigi web site...", net_icon), 0, cb_mnuVisitURL, (void *)PACKAGE_HOME, FL_MENU_DIVIDER, _FL_MULTI_LABEL, 0, 14, 0}, +{ make_icon_label("Fldigi web site...", net_icon), 0, cb_mnuVisitURL, (void *)PACKAGE_HOME, 0, _FL_MULTI_LABEL, 0, 14, 0}, +{ make_icon_label("Reception reports...", pskr_icon), 0, cb_mnuVisitPSKRep, 0, FL_MENU_DIVIDER, _FL_MULTI_LABEL, 0, 14, 0}, { make_icon_label("Command line options", utilities_terminal_icon), 0, cb_mnuCmdLineHelp, 0, 0, _FL_MULTI_LABEL, 0, 14, 0}, { make_icon_label("Audio device info", audio_card_icon), 0, cb_mnuAudioInfo, 0, 0, _FL_MULTI_LABEL, 0, 14, 0}, { make_icon_label("Build info", executable_icon), 0, cb_mnuBuildInfo, 0, 0, _FL_MULTI_LABEL, 0, 14, 0}, @@ -1752,6 +1782,22 @@ void show_bw(const string& sWidth) REQ_SYNC(&Fl_ComboBox::put_value, opBW, sWidth.c_str()); } +void show_spot(bool v) +{ + if (v) { + mnu->size(btnAutoSpot->x(), mnu->h()); + btnAutoSpot->value(progStatus.spot_recv); + btnAutoSpot->show(); + } + else { + btnAutoSpot->hide(); + btnAutoSpot->value(v); + btnAutoSpot->do_callback(); + mnu->size(btnRSID->x(), mnu->h()); + } + mnu->redraw(); +} + void create_fl_digi_main() { int pad = 1; //wSpace; @@ -1790,6 +1836,11 @@ void create_fl_digi_main() { Fl_Tooltip::size(FL_NORMAL_SIZE); Fl_Tooltip::enable(progdefaults.tooltips); + btnAutoSpot = new Fl_Light_Button(WNOM - 200 - pad, 0, 50, Hmenu, "Spot"); + btnAutoSpot->selection_color(FL_GREEN); + btnAutoSpot->callback(cbAutoSpot, 0); + btnAutoSpot->hide(); + btnRSID = new Fl_Light_Button(WNOM - 150 - pad, 0, 50, Hmenu, "RSID"); btnRSID->selection_color(FL_GREEN); btnRSID->callback(cbRSID, 0); @@ -2438,11 +2489,11 @@ void put_rx_char(unsigned int data) { static unsigned int last = 0; const char **asc = ascii; + trx_mode mode = active_modem->get_mode(); if (mailclient || mailserver || arqmode) asc = ascii2; - if (active_modem->get_mode() == MODE_RTTY || - active_modem->get_mode() == MODE_CW) + if (mode == MODE_RTTY || mode == MODE_CW) asc = ascii; int style = FTextBase::RECV; @@ -2465,7 +2516,15 @@ void put_rx_char(unsigned int data) WriteARQ(data); - string s = iscntrl(data) ? ascii2[data & 0x7F] : string(1, data); + string s; + if (iscntrl(data)) + s = ascii2[data & 0x7F]; + else { + s += data; + bool viewer = (mode >= MODE_PSK_FIRST && mode <= MODE_PSK_LAST && dlgViewer && dlgViewer->visible()); + if (progStatus.spot_recv && !viewer) + spot_recv(data); + } if (Maillogfile) Maillogfile->log_to_file(cLogfile::LOG_RX, s); @@ -2879,3 +2938,9 @@ void qsy(long long rfc, long long fmid) else active_modem->set_freq(fmid); } + +bool quick_choice(const char* title, bool sel) +{ + Fl_Menu_Item m[] = { { "Confirm" }, { "Cancel" }, { 0 } }; + return m->popup(Fl::event_x(), Fl::event_y(), title, m + !sel) == m; +} diff --git a/src/include/confdialog.h b/src/include/confdialog.h index 97485174..9149f54c 100644 --- a/src/include/confdialog.h +++ b/src/include/confdialog.h @@ -1,4 +1,4 @@ -// generated by Fast Light User Interface Designer (fluid) version 1.0108 +// generated by Fast Light User Interface Designer (fluid) version 1.0109 #ifndef confdialog_h #define confdialog_h @@ -39,6 +39,16 @@ extern Fl_Value_Input *valCWsweetspot; extern Fl_Value_Input *valRTTYsweetspot; extern Fl_Value_Input *valPSKsweetspot; extern Fl_Check_Button *btnStartAtSweetSpot; +extern Fl_Group *tabSpot; +extern Fl_Check_Button *btnPSKRepAuto; +extern Fl_Check_Button *btnPSKRepLog; +extern Fl_Input *inpPSKRepHost; +extern Fl_Input *inpPSKRepPort; +#include +extern Fl_Button *btnPSKRepInit; +#include +extern Fl_Box *boxPSKRepMsg; +extern Fl_Check_Button *btnPSKRepQRG; extern Fl_Group *tabModems; extern Fl_Tabs *tabsModems; extern Fl_Group *tabCW; @@ -126,6 +136,7 @@ extern Fl_Group *tabOperator; extern Fl_Input *inpMyCallsign; extern Fl_Input *inpMyName; extern Fl_Input *inpMyQth; +extern Fl_Input *inpMyAntenna; extern Fl_Input *inpMyLocator; extern Fl_Check_Button *btnUseLeadingZeros; extern Fl_Value_Input *nbrContestStart; @@ -139,7 +150,6 @@ extern Fl_Input *inpQRZusername; extern Fl_Input *inpQRZuserpassword; extern Fl_Round_Button *btnQRZsub; extern Fl_Round_Button *btnHamcall; -#include extern Fl_Button *btnQRZpasswordShow; extern Fl_Group *tabRig; extern Fl_Round_Button *btnPTT[5]; diff --git a/src/include/configuration.h b/src/include/configuration.h index 90e6f371..bda68f08 100644 --- a/src/include/configuration.h +++ b/src/include/configuration.h @@ -211,6 +211,7 @@ ELEM_(std::string, myName, "MYNAME", "") \ ELEM_(std::string, myLocator, "MYLOC", "") \ ELEM_(std::string, secText, "SECONDARYTEXT", "") \ + ELEM_(std::string, myAntenna, "MYANTENNA", "") \ /* Sound card */ \ ELEM_(int, btnAudioIOis, "AUDIOIO", SND_IDX_PORT) \ ELEM_(std::string, OSSdevice, "OSSDEVICE", "") \ @@ -300,7 +301,14 @@ ELEM_(int, rx_msgid, "", 9876) \ ELEM_(int, tx_msgid, "", 6789) \ ELEM_(std::string, arq_address, "", "127.0.0.1") \ - ELEM_(std::string, arq_port, "", "3122") + ELEM_(std::string, arq_port, "", "3122") \ +/* PSK reporter */ \ + ELEM_(bool, pskrep_auto, "PSKREPAUTO", false) \ + ELEM_(bool, pskrep_log, "PSKREPLOG", false) \ + ELEM_(bool, pskrep_qrg, "PSKREPQRG", false) \ + ELEM_(std::string, pskrep_host, "PSKREPHOST", "report.psk.gladstonefamily.net") \ + ELEM_(std::string, pskrep_port, "PSKREPPORT", "4739") + // declare the struct #define ELEM_DECLARE_CONFIGURATION(type_, var_, tag_, ...) type_ var_; diff --git a/src/include/debug.h b/src/include/debug.h index 3b7e3fd2..5ac68ac2 100644 --- a/src/include/debug.h +++ b/src/include/debug.h @@ -23,6 +23,7 @@ #ifndef _DEBUG_H_ #define _DEBUG_H_ +#include "util.h" class debug { diff --git a/src/include/fl_digi.h b/src/include/fl_digi.h index d3777536..071d127c 100644 --- a/src/include/fl_digi.h +++ b/src/include/fl_digi.h @@ -155,6 +155,7 @@ extern void set_AFCrange(double val); extern void show_frequency(long long); extern void show_mode(const string& mode); extern void show_bw(const string& sWidth); +extern void show_spot(bool v); extern void put_WARNstatus(double); @@ -196,4 +197,6 @@ Fl_Color adjust_color(Fl_Color fg, Fl_Color bg); void qsy(long long rfc, long long fmid = -1LL); +bool quick_choice(const char* title = 0, bool sel = true); + #endif diff --git a/src/include/pixmaps.h b/src/include/pixmaps.h index a68df339..d3ae7da8 100644 --- a/src/include/pixmaps.h +++ b/src/include/pixmaps.h @@ -53,5 +53,6 @@ extern const char *tx_icon[]; extern const char *fldigi_icon[]; extern const char *waterfall_icon[]; extern const char *dice_icon[]; +extern const char *pskr_icon[]; #endif // PIXMAPS_H_ diff --git a/src/include/pskrep.h b/src/include/pskrep.h new file mode 100644 index 00000000..5c500f4f --- /dev/null +++ b/src/include/pskrep.h @@ -0,0 +1,8 @@ +#ifndef PSKREP_H_ +#define PSKREP_H_ + +bool pskrep_start(void); +void pskrep_stop(); +const char* pskrep_error(void); + +#endif // PSKREP_H_ diff --git a/src/include/spot.h b/src/include/spot.h new file mode 100644 index 00000000..f974241b --- /dev/null +++ b/src/include/spot.h @@ -0,0 +1,48 @@ +// ---------------------------------------------------------------------------- +// spot.h +// +// Copyright (C) 2008 +// Stelios Bounanos, M0GLD +// +// This file is part of fldigi. +// +// fldigi is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 3 of the License, or +// (at your option) any later version. +// +// fldigi is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ---------------------------------------------------------------------------- + +#ifndef SPOT_H_ +#define SPOT_H_ + +#include +#include +#include "globals.h" + +typedef void (*spot_recv_cb_t)(int afreq, const char* str, const regmatch_t* sub, size_t len, void* data); +typedef void (*spot_log_cb_t)(const char* call, const char* loc, long long freq, + trx_mode mode, time_t rtime, void* data); + +void spot_recv(char c, int decoder = -1, int afreq = 0); +void spot_log(const char* callsign, const char* locator = "", long long freq = 0LL, + trx_mode mode = NUM_MODES, time_t rtime = -1L); +void spot_manual(const char* callsign, const char* locator = "", + long long freq = 0LL, trx_mode mode = NUM_MODES, time_t rtime = -1L); + +void spot_register_log(spot_log_cb_t lcb, void* ldata); +void spot_register_manual(spot_log_cb_t mcb, void* mdata); +void spot_register_recv(spot_recv_cb_t rcb, void* rdata, const char* re, int reflags); + +void spot_unregister_log(spot_log_cb_t lcb, void* ldata); +void spot_unregister_manual(spot_log_cb_t mcb, void* mdata); +void spot_unregister_recv(spot_recv_cb_t rcb, void* rdata); + +#endif // SPOT_H_ diff --git a/src/include/status.h b/src/include/status.h index c6b1f65f..c3036559 100644 --- a/src/include/status.h +++ b/src/include/status.h @@ -1,8 +1,6 @@ #ifndef _status_H #define _status_H -#include -#include #include #include "globals.h" @@ -40,6 +38,8 @@ struct status { int scopeW; int scopeH; string LastMacroFile; + bool spot_recv; + bool spot_log; bool bLastStateRead; diff --git a/src/include/util.h b/src/include/util.h index ba491bf7..c7f17687 100644 --- a/src/include/util.h +++ b/src/include/util.h @@ -120,6 +120,11 @@ void restore_signals(void); } // extern "C" #endif +#ifdef __cplusplus +uint32_t simple_hash_data(const unsigned char* buf, unsigned len, uint32_t code = 0); +uint32_t simple_hash_str(const unsigned char* str, uint32_t code = 0); +#endif + #if !defined(NDEBUG) && defined(deprecated__) && defined(__GNUC__) #include #include diff --git a/src/logger/logger.cxx b/src/logger/logger.cxx index 739c344a..81fb344c 100644 --- a/src/logger/logger.cxx +++ b/src/logger/logger.cxx @@ -37,6 +37,9 @@ #include "main.h" #include "modem.h" #include "debug.h" +#include "configuration.h" +#include "status.h" +#include "spot.h" #include @@ -146,6 +149,9 @@ char LOG_MSEPARATOR[2] = {1,0}; int submit_log(void) { + if (progStatus.spot_log) + spot_log(inpCall->value(), inpLoc->value()); + char logdate[32], logtime[32], adifdate[32]; #ifndef __CYGWIN__ int msqid, len; diff --git a/src/main.cxx b/src/main.cxx index 3c9adf28..b37fb3bd 100644 --- a/src/main.cxx +++ b/src/main.cxx @@ -64,6 +64,7 @@ #include "fileselect.h" #include "timeops.h" #include "debug.h" +#include "pskrep.h" #if USE_HAMLIB #include "rigclass.h" @@ -285,6 +286,9 @@ int main(int argc, char ** argv) XML_RPC_Server::start(progdefaults.xmlrpc_address.c_str(), progdefaults.xmlrpc_port.c_str()); #endif + if (!pskrep_start()) + LOG_ERROR("Could not start PSK reporter: %s", pskrep_error()); + int ret = Fl::run(); arq_close(); @@ -293,6 +297,8 @@ int main(int argc, char ** argv) XML_RPC_Server::stop(); #endif + pskrep_stop(); + for (int i = 0; i < NUM_QRUNNER_THREADS; i++) { cbq[i]->detach(); delete cbq[i]; diff --git a/src/misc/configuration.cxx b/src/misc/configuration.cxx index e4ea6f82..7b5186e1 100644 --- a/src/misc/configuration.cxx +++ b/src/misc/configuration.cxx @@ -349,6 +349,7 @@ int configuration::setDefaults() inpMyName->value(myName.c_str()); inpMyQth->value(myQth.c_str()); inpMyLocator->value(myLocator.c_str()); + inpMyAntenna->value(myAntenna.c_str()); UseLeadingZeros = btnUseLeadingZeros->value(); ContestStart = (int)nbrContestStart->value(); ContestDigits = (int)nbrContestDigits->value(); diff --git a/src/misc/pixmaps.cxx b/src/misc/pixmaps.cxx index a28cef40..dfbcb096 100644 --- a/src/misc/pixmaps.cxx +++ b/src/misc/pixmaps.cxx @@ -4743,6 +4743,32 @@ const char *tx_icon[] = { " .. ", " "}; +// pskreporter.info "favicon" + +/* XPM */ +const char *pskr_icon[] = { +"16 16 3 1", +" c None", +". c #FF0000", +"+ c #FFFF00", +" .. ", +" ... ", +" ..... ", +" .....+.. ", +". ...++++... ", +".. ..+++++. .. ", +"....++++++..+. ", +"..+++++++++++.. ", +"..+++++++.++++. ", +"....+++++..++.. ", +".. ..+++++.... ", +". ..+++++.. ", +" ...+... ", +" .... ", +" .. ", +" . "}; + + /* XPM */ const char *fldigi_icon[] = { "48 48 215 2", diff --git a/src/misc/status.cxx b/src/misc/status.cxx index 4552f985..52f262bb 100644 --- a/src/misc/status.cxx +++ b/src/misc/status.cxx @@ -67,6 +67,8 @@ status progStatus = { 50, // int scopeW; 50, // int scopeH; "macros.mdf", // string LastMacroFile; + false, // bool spot_recv + false, // bool spot_log false // bool bLastStateRead; }; @@ -155,8 +157,11 @@ void status::saveLastState() spref.set("scope_y", scopeY); spref.set("scope_w", scopeW); spref.set("scope_h", scopeH); - + spref.set("last_macro_file", LastMacroFile.c_str()); + + spref.set("spot_recv", spot_recv); + spref.set("spot_log", spot_recv); } void status::loadLastState() @@ -216,11 +221,14 @@ void status::loadLastState() spref.get("scope_y", scopeY, scopeY); spref.get("scope_w", scopeW, scopeW); spref.get("scope_h", scopeH, scopeH); - + char *defbuffer; spref.get("last_macro_file", defbuffer, "macros.mdf"); LastMacroFile = defbuffer; if (defbuffer) free(defbuffer); + + spref.get("spot_recv", i, i); spot_recv = i; + spref.get("spot_log", i, i); spot_log = i; } void status::initLastState() diff --git a/src/misc/util.cxx b/src/misc/util.cxx index b382f6b0..fd22c948 100644 --- a/src/misc/util.cxx +++ b/src/misc/util.cxx @@ -109,3 +109,17 @@ void restore_signals(void) nsig = 0; pthread_mutex_unlock(&sigmutex); } + +uint32_t simple_hash_data(const unsigned char* buf, size_t len, uint32_t code) +{ + for (size_t i = 0; i < len; i++) + code = ((code << 4) | (code >> (32 - 4))) ^ (uint32_t)buf[i]; + + return code; +} +uint32_t simple_hash_str(const unsigned char* str, uint32_t code) +{ + while (*str) + code = ((code << 4) | (code >> (32 - 4))) ^ (uint32_t)*str++; + return code; +} diff --git a/src/psk/viewpsk.cxx b/src/psk/viewpsk.cxx index 0e39b076..32a2a0ec 100644 --- a/src/psk/viewpsk.cxx +++ b/src/psk/viewpsk.cxx @@ -157,8 +157,8 @@ void viewpsk::rx_bit(int ch, int bit) if ((shreg[ch] & 3) == 0) { c = psk_varicode_decode(shreg[ch] >> 2); shreg[ch] = 0; - if (c == '\n') c = ' '; - if (c >= ' ' && c <= 'z') { + if (c == '\n' || c == '\r') c = ' '; + if (isprint(c)) { REQ(&viewaddchr, ch, (int)frequency[ch], c); timeout[ch] = now + progdefaults.VIEWERtimeout; } diff --git a/src/spot/pskrep.cxx b/src/spot/pskrep.cxx new file mode 100644 index 00000000..c89a9d32 --- /dev/null +++ b/src/spot/pskrep.cxx @@ -0,0 +1,906 @@ +// ---------------------------------------------------------------------------- +// pskrep.cxx +// +// Copyright (C) 2008 +// Stelios Bounanos, M0GLD +// +// This is a client for N1DQ's PSK Automatic Propagation Reporter +// (see http://pskreporter.info/). Philip Gladstone, N1DQ, is +// thanked for his helpful explanation of the protocol. +// +// +// This file is part of fldigi. +// +// fldigi is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 3 of the License, or +// (at your option) any later version. +// +// fldigi is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ---------------------------------------------------------------------------- + +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "socket.h" +#include "re.h" +#include "debug.h" +#include "util.h" +#include "trx.h" +#include "waterfall.h" +#include "configuration.h" +#include "rigsupport.h" +#include "spot.h" + +#include "pskrep.h" + +// ------------------------------------------------------------------------------------------------- + +// The regular expression that matches the spotter's buffer when it calls us. +// It must define at least two capturing groups, the second of which is the +// spotted callsign. +#define CALLSIGN_RE "[[:alnum:]]?[[:alpha:]/]+[[:digit:]]+[[:alnum:]/]+" +#define PSKREP_RE "(de|cq|qrz)[^[:alnum:]/\n]+" "(" CALLSIGN_RE ")" " +(.* +)?\\2[^[:alnum:]]+$" + +// Try to flush the report queue every SEND_INTERVAL seconds. +#define SEND_INTERVAL 300 + +// Ignore reports that are less than DUP_INTERVAL seconds older than +// a previously sent report for the same callsign and frequency band. +// Sent reports are also garbage-collected after DUP_INTERVAL seconds. +#define DUP_INTERVAL 3600 + +// The first TEMPLATE_THRESHOLD packets will contain the long templates; +// the next TEMPLATE_THRESHOLD packets will include the short templates +#define TEMPLATE_THRESHOLD 3 +// Resend short templates every TEMPLATE_INTERVAL seconds +#define TEMPLATE_INTERVAL 1800 + +// Maximum send size +#define DGRAM_MAX (1500-14-24-8) + +#define PSKREP_QUEUE_FILE "pskrqueue.txt" +#define PSKREP_ID_FILE "pskrkey.txt" + +// ------------------------------------------------------------------------------------------------- + +using namespace std; + +enum status_t { STATUS_NEW, STATUS_PENDING, STATUS_SENT }; +enum rtype_t { PSKREP_AUTO = 1, PSKREP_LOG = 2, PSKREP_MANUAL = 3 }; + +struct rcpt_report_t +{ + rcpt_report_t(trx_mode m = 0, long long f = 0, time_t t = 0, + rtype_t p = PSKREP_AUTO, string loc = "") + : mode(m), freq(f), rtime(t), rtype(p), + status(STATUS_NEW), locator(loc) { } + + trx_mode mode; + long long freq; + time_t rtime; + rtype_t rtype; + + status_t status; + + string locator; +}; + +enum band_t { + BAND_LMW, BAND_160M, BAND_80M, BAND_75M, BAND_60M, BAND_40M, BAND_30M, BAND_20M, + BAND_17M, BAND_15M, BAND_12M, BAND_10M, BAND_6M, BAND_4M, BAND_2M, BAND_125CM, + BAND_70CM, BAND_33CM, BAND_23CM, BAND_13CM, BAND_9CM, BAND_OTHER +}; + +// A band_map_t holds a list of reception reports (for a particular callsign and band) +typedef deque band_map_t; +// A call_map_t holds reception reports for a particular callsign +typedef map call_map_t; +// A container of this type holds all reception reports, sorted by callsign and band +typedef map queue_t; + +class pskrep_sender +{ +public: + pskrep_sender(const string& call, const string& loc, const string& ant, + const string& host_, const string& port_, + const string& long_id_, const string& short_id_); + ~pskrep_sender(); + + bool append(const string& callsign, const band_map_t::value_type& r); + bool send(void); + +private: + void write_station_info(void); + void write_preamble(void); + + string recv_callsign, recv_locator, recv_antenna; + string host, port; + string long_id, short_id; + + static const unsigned char long_station_info_template[]; + static const unsigned char short_station_info_template[]; + static const unsigned char rcpt_record_template[]; + vector long_station_info; + vector short_station_info; + + uint32_t identifier; + uint32_t sequence_number; + + unsigned template_count; + time_t last_template; + + Socket* send_socket; + unsigned char* dgram; + size_t dgram_size; + size_t report_offset; + + void create_socket(void); + pthread_t resolver_thread; + static void* resolver(void* obj); + + static const char hexsym[]; + static const string& printhex(const unsigned char* s, size_t len); + + static size_t pad(size_t len, size_t mult); +}; + + +class pskrep +{ +public: + pskrep(const string& call, const string& loc, const string& ant, + const string& host, const string& port, + const string& long_id, const string& short_id, + bool reg_auto, bool reg_log, bool reg_manual); + ~pskrep(); + + static void recv(int afreq, const char* str, const regmatch_t* calls, size_t len, void* obj); + static void log(const char* call, const char* loc, long long freq, trx_mode mode, time_t rtime, void* obj); + static void manual(const char* call, const char* loc, long long freq, trx_mode mode, time_t rtime, void* obj); + bool progress(void); + + static band_t band(long long freq_hz); + static fre_t locator_re; +private: + + void append(string call, const char* loc, long long freq, trx_mode mode, time_t rtime, rtype_t rtype); + void gc(void); + + void load_queue(void); + void save_queue(void); + + static bool not_sent(const band_map_t::value_type& r) + { + return r.status != STATUS_SENT; + } + + queue_t queue; + pskrep_sender sender; +}; + +fre_t pskrep::locator_re("[a-r]{2}[0-9]{2}[a-x]{2}", REG_EXTENDED | REG_NOSUB | REG_ICASE); + +#define SHORT_ID_SIZE 4 +#define LONG_ID_SIZE 8 + +// ------------------------------------------------------------------------------------------------- + +static string error_string; + +static bool pskrep_check(void) +{ + struct { + const string* var; + const char* msg; + } check[] = { + { &progdefaults.myCall, "callsign" }, + { &progdefaults.myLocator, "locator" }, + { &progdefaults.myAntenna, "antenna info" }, + }; + for (size_t i = 0; i < sizeof(check)/sizeof(*check); i++) { + if (check[i].var->empty()) { + error_string.assign("Error: missing ").append(check[i].msg); + return false; + } + } + if (!pskrep::locator_re.match(progdefaults.myLocator.c_str())) { + error_string = "Error: bad IARU locator"; + return false; + } + + return true; +} + +const char* pskrep_error(void) +{ + return error_string.c_str(); +} + +static void pskrep_progress(void* obj) +{ + if (reinterpret_cast(obj)->progress()) + Fl::add_timeout(SEND_INTERVAL, pskrep_progress, obj); + else + pskrep_stop(); +} + +static void pskrep_make_id(string& id, size_t len) +{ + id.resize(len); + + ifstream f("/dev/urandom"); + if (f) { + for (size_t i = 0; i < len; i++) + while ((id[i] = f.get()) != EOF && !isgraph(id[i])); + f.close(); + } + else { + unsigned seed = time(NULL); + if (!progdefaults.myCall.empty()) + seed ^= simple_hash_str((const unsigned char*)progdefaults.myCall.c_str()); + srandom(seed); + for (size_t i = 0; i < len; i++) + while (!isgraph(id[i] = random() % 0x7F)); + } +} + +static pskrep* pskr = 0; + +bool pskrep_start(void) +{ + if (pskr) + return true; + else if (!pskrep_check()) + return false; + + // get identifier + string fname = HomeDir; + fname.append(PSKREP_ID_FILE); + ifstream in(fname.c_str()); + string long_id, short_id; + if (in) + in >> long_id >> short_id; + if (!in || in.eof()) { + in.close(); + pskrep_make_id(long_id, LONG_ID_SIZE); + pskrep_make_id(short_id, SHORT_ID_SIZE); + + ofstream out(fname.c_str()); + if (out) + out << long_id << ' ' << short_id << '\n'; + else + LOG_ERROR("Could not write identifiers (\"%s\", \"%s\") to %s", + long_id.c_str(), short_id.c_str(), fname.c_str()); + } + + pskr = new pskrep(progdefaults.myCall, progdefaults.myLocator, progdefaults.myAntenna, + progdefaults.pskrep_host, progdefaults.pskrep_port, long_id, short_id, + progdefaults.pskrep_auto, progdefaults.pskrep_log, true); + Fl::add_timeout(SEND_INTERVAL, pskrep_progress, pskr); + + return true; +} + +void pskrep_stop(void) +{ + Fl::remove_timeout(pskrep_progress, pskr); + + delete pskr; + pskr = 0; +} + +// ------------------------------------------------------------------------------------------------- + +pskrep::pskrep(const string& call, const string& loc, const string& ant, + const string& host, const string& port, + const string& long_id, const string& short_id, + bool reg_auto, bool reg_log, bool reg_manual) + : sender(call, loc, ant, host, port, long_id, short_id) +{ + if (reg_auto) + spot_register_recv(pskrep::recv, this, PSKREP_RE, REG_EXTENDED | REG_ICASE); + if (reg_log) + spot_register_log(pskrep::log, this); + if (reg_manual) + spot_register_manual(pskrep::manual, this); + load_queue(); +} + +pskrep::~pskrep() +{ + spot_unregister_recv(pskrep::recv, this); + spot_unregister_log(pskrep::log, this); + spot_unregister_manual(pskrep::manual, this); + save_queue(); +} + +// This function is called by spot_recv() when its buffer matches our PSKREP_RE +void pskrep::recv(int afreq, const char* str, const regmatch_t* calls, size_t len, void* obj) +{ + if (unlikely(calls[2].rm_so == -1 || calls[2].rm_eo == -1)) + return; + + string call(str, calls[2].rm_so, calls[2].rm_eo - calls[2].rm_so); + long long freq = afreq; + if (!wf->USB()) + freq = -freq; + freq += wf->rfcarrier(); + LOG_DEBUG("Spotted \"%s\" in buffer \"%s\"", call.c_str(), str); + + reinterpret_cast(obj)->append(call.c_str(), "", freq, + active_modem->get_mode(), time(NULL), PSKREP_AUTO); +} + +// This function is called by spot_log() +void pskrep::log(const char* call, const char* loc, long long freq, trx_mode mode, time_t rtime, void* obj) +{ + reinterpret_cast(obj)->append(call, loc, freq, mode, rtime, PSKREP_LOG); +} + +// This function is called by spot_manual() +void pskrep::manual(const char* call, const char* loc, long long freq, trx_mode mode, time_t rtime, void* obj) +{ + reinterpret_cast(obj)->append(call, loc, freq, mode, rtime, PSKREP_MANUAL); +} + +void pskrep::append(string call, const char* loc, long long freq, trx_mode mode, time_t rtime, rtype_t rtype) +{ + if (unlikely(call.empty())) + return; + transform(call.begin(), call.end(), call.begin(), static_cast(toupper)); + + if (!progdefaults.pskrep_qrg) + freq = 0LL; + + if (*loc && !locator_re.match(loc)) + loc = ""; + + band_map_t& bandq = queue[call][band(freq)]; + if (bandq.empty() || rtime - bandq.back().rtime >= DUP_INTERVAL) { // add new + bandq.push_back(rcpt_report_t(mode, freq, rtime, rtype, loc)); + LOG_INFO("Added (call=\"%s\", loc=\"%s\", mode=\"%s\", freq=%lld, time=%ld, type=%u)", + call.c_str(), loc, mode_info[mode].adif_name, freq, rtime, rtype); + save_queue(); + } + else if (!bandq.empty()) { + band_map_t::value_type& r = bandq.back(); + if (r.status != STATUS_SENT && *loc && r.locator != loc) { // update last + r.locator = loc; + r.rtype = rtype; + LOG_INFO("Updated (call=\"%s\", loc=\"%s\", mode=\"%s\", freq=%lld, time=%ld, type=%u)", + call.c_str(), loc, mode_info[r.mode].adif_name, r.freq, r.rtime, rtype); + save_queue(); + } + } +} + +// Handle queued reports +bool pskrep::progress(void) +{ + if (queue.empty()) + return true; + + unsigned nrep = 0; + bool sender_full = false; + for (queue_t::iterator i = queue.begin(); i != queue.end(); ++i) { + for (call_map_t::iterator j = i->second.begin(); j != i->second.end(); ++j) { + for (band_map_t::iterator k = j->second.begin(); k != j->second.end(); ++k) { + switch (k->status) { + case STATUS_NEW: + if ((sender_full = !sender.append(i->first, *k))) + goto send_reports; + k->status = STATUS_PENDING; + nrep++; + break; + case STATUS_PENDING: // sent in last cycle + k->status = STATUS_SENT; + default: + break; + } + } + } + } + +send_reports: + LOG_INFO("Found %u new report(s)", nrep); + if (nrep) { + if (!sender.send()) { + LOG_ERROR("Sender failed, disabling pskreporter"); + return false; + } + return progress(); + } + + gc(); + save_queue(); + return true; +} + +// Delete sent reports that are older than DUP_INTERVAL seconds +void pskrep::gc(void) +{ + time_t threshold = time(NULL) - DUP_INTERVAL; + unsigned rm = 0; + + for (queue_t::iterator i = queue.begin(); i != queue.end() ; ) { + for (call_map_t::iterator j = i->second.begin(); j != i->second.end() ; ) { + band_map_t& b = j->second; + band_map_t::iterator k = find_if(b.begin(), b.end(), not_sent); + if (k != b.begin() && k == b.end()) + --k; + rm += k - b.begin(); + k = b.erase(b.begin(), k); + + if (k != b.end() && k->status == STATUS_SENT && k->rtime <= threshold) { + b.erase(k); + rm++; + } + + if (b.empty()) + i->second.erase(j++); + else + ++j; + } + if (i->second.empty()) + queue.erase(i++); + else + ++i; + } + + LOG_DEBUG("Removed %zu sent report(s)", rm); +} + +static ostream& operator<<(ostream& out, const rcpt_report_t& r); +static istream& operator>>(istream& in, rcpt_report_t& r); +static ostream& operator<<(ostream& out, const queue_t& q); +static istream& operator>>(istream& in, queue_t& q); + +void pskrep::save_queue(void) +{ + string fname = HomeDir; + fname.append(PSKREP_QUEUE_FILE); + ofstream out(fname.c_str()); + + if (out) + out << queue; + else + LOG_ERROR("Could not write %s", fname.c_str()); +} + +void pskrep::load_queue(void) +{ + string fname = HomeDir; + fname.append(PSKREP_QUEUE_FILE); + ifstream in(fname.c_str()); + if (!in) + return; + + in >> queue; + // restore pending reports as new + for (queue_t::iterator i = queue.begin(); i != queue.end(); ++i) + for (call_map_t::iterator j = i->second.begin(); j != i->second.end(); ++j) + for (band_map_t::iterator k = j->second.begin(); k != j->second.end(); ++k) + if (k->status == STATUS_PENDING) + k->status = STATUS_NEW; +} + +band_t pskrep::band(long long freq_hz) +{ + switch (freq_hz / 1000000LL) { + case 0: return BAND_LMW; + case 1: return BAND_160M; + case 3: return BAND_80M; + case 4: return BAND_75M; + case 5: return BAND_60M; + case 7: return BAND_40M; + case 10: return BAND_30M; + case 14: return BAND_20M; + case 18: return BAND_17M; + case 21: return BAND_15M; + case 24: return BAND_12M; + case 28 ... 29: return BAND_10M; + case 50 ... 54: return BAND_6M; + case 70 ... 71: return BAND_4M; + case 144 ... 148: return BAND_2M; + case 222 ... 225: return BAND_125CM; + case 420 ... 450: return BAND_70CM; + case 902 ... 928: return BAND_33CM; + case 1240 ... 1325: return BAND_23CM; + case 2300 ... 2450: return BAND_13CM; + case 3300 ... 3500: return BAND_9CM; + } + + return BAND_OTHER; +} + +// ------------------------------------------------------------------------------------------------- + +// Text fields must be <= 254 bytes +#define MAX_TEXT_SIZE 254 +// Records must be padded to a multiple of 4 +#define PAD 4 + +pskrep_sender::pskrep_sender(const string& call, const string& loc, const string& ant, + const string& host_, const string& port_, + const string& long_id_, const string& short_id_) + : recv_callsign(call, 0, MAX_TEXT_SIZE), recv_locator(loc, 0, MAX_TEXT_SIZE), + recv_antenna(ant, 0, MAX_TEXT_SIZE), + host(host_, 0, MAX_TEXT_SIZE), port(port_, 0, MAX_TEXT_SIZE), + long_id(long_id_, 0, LONG_ID_SIZE), short_id(short_id_, 0, SHORT_ID_SIZE), + sequence_number(0), template_count(2 * TEMPLATE_THRESHOLD), last_template(0), + send_socket(0), dgram_size(0), report_offset(0) +{ + create_socket(); + dgram = new unsigned char[DGRAM_MAX]; + write_station_info(); +} + +pskrep_sender::~pskrep_sender() +{ + delete send_socket; + delete [] dgram; +} + +// fldigi uses 0x0219 as the long station info template id (bytes 4,5) +const unsigned char pskrep_sender::long_station_info_template[] = { + 0x00, 0x03, 0x00, 0x34, 0x02, 0x19, 0x00, 0x05, 0x00, 0x00, + 0x80, 0x02, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F, // receiverCallsign + 0x80, 0x04, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F, // receiverLocator + 0x80, 0x0C, 0x00, 0x08, 0x00, 0x00, 0x76, 0x8F, // persistentIdentifier + 0x80, 0x08, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F, // decoderSoftware + 0x80, 0x09, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F, // anntennaInformation + 0x00, 0x00 +}; + +// fldigi uses 0x0218 as the short station info template id (bytes 4,5) +const unsigned char pskrep_sender::short_station_info_template[] = { + 0x00, 0x03, 0x00, 0x24, 0x02, 0x18, 0x00, 0x03, 0x00, 0x00, + 0x80, 0x02, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F, // receiverCallsign + 0x80, 0x04, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F, // receiverLocator + 0x80, 0x0C, 0x00, 0x08, 0x00, 0x00, 0x76, 0x8F, // persistentIdentifier + 0x00, 0x00 +}; + +void pskrep_sender::write_station_info(void) +{ + char prog_info[MAX_TEXT_SIZE]; + size_t prog_len; + + prog_len = snprintf(prog_info, sizeof(prog_info), "%s", PACKAGE_TARNAME "-" PACKAGE_VERSION); + prog_len = MIN(prog_len, sizeof(prog_info)); + struct utsname u; + if (uname(&u) != -1) { + prog_len += snprintf(prog_info+prog_len, sizeof(prog_info)-prog_len, "/%s-%s", u.sysname, u.machine); + prog_len = MIN(prog_len, sizeof(prog_info)); + } + + size_t call_len = recv_callsign.length(), + loc_len = recv_locator.length(), + ant_len = recv_antenna.length(); + + size_t long_len, short_len; + // Long station info length + long_len = 4 + // 4-byte header + 1 + call_len + // 1-byte call length + call string length + 1 + loc_len + // 1-byte loc length + loc string length + 8 + // 8-byte identifier + 1 + prog_len + // 1-byte prog length + prog string length + 1 + ant_len; // 1-byte ant length + ant string length + long_len = pad(long_len, PAD); + // Short station info length + short_len = 4 + // 4-byte header + 1 + call_len + // 1-byte call length + call string length + 1 + loc_len + // 1-byte loc length + loc string length + 8; // 8-byte identifier + short_len = pad(short_len, PAD); + + long_station_info.resize(long_len); + short_station_info.resize(short_len); + unsigned char* p; + size_t npad; + + // Write the long station info + p = &long_station_info[0]; + // header + memcpy(p, long_station_info_template + 4, 2); p += 2; + *reinterpret_cast(p) = htons(long_len); p += sizeof(uint16_t); + // call + *p++ = call_len; memcpy(p, recv_callsign.data(), call_len); p += call_len; + // locator + *p++ = loc_len; memcpy(p, recv_locator.data(), loc_len); p += loc_len; + // identifier + memcpy(p, long_id.data(), LONG_ID_SIZE); p += LONG_ID_SIZE; + // program + *p++ = prog_len; memcpy(p, prog_info, prog_len); p += prog_len; + // antenna + *p++ = ant_len; memcpy(p, recv_antenna.data(), ant_len); p += ant_len; + // pad + npad = &long_station_info[0] + long_len - p; + if (npad) + memset(p, 0, npad); + LOG_DEBUG("long_station_info=\"%s\"", printhex(&long_station_info[0], long_len).c_str()); + + // Write the short station info + p = &short_station_info[0]; + // header + memcpy(p, short_station_info_template + 4, 2); p += 2; + *reinterpret_cast(p) = htons(short_len); p += sizeof(uint16_t); + // call + *p++ = call_len; memcpy(p, recv_callsign.data(), call_len); p += call_len; + // locator + *p++ = loc_len; memcpy(p, recv_locator.data(), loc_len); p += loc_len; + // identifier + memcpy(p, long_id.data(), LONG_ID_SIZE); p += LONG_ID_SIZE; + // pad + npad = &short_station_info[0] + short_len - p; + if (npad) + memset(p, 0, npad); + LOG_DEBUG("short_station_info=\"%s\"", printhex(&short_station_info[0], short_len).c_str()); +} + +// fldigi uses 0x022C as the reception record template id (bytes 4,5) +const unsigned char pskrep_sender::rcpt_record_template[] = { + 0x00, 0x02, 0x00, 0x34, 0x02, 0x2C, 0x00, 0x06, + 0x80, 0x01, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F, // senderCallsign + 0x00, 0x96, 0x00, 0x04, // flowStartSeconds + 0x80, 0x05, 0x00, 0x04, 0x00, 0x00, 0x76, 0x8F, // frequency + 0x80, 0x0A, 0xFF, 0xFF, 0x00, 0x00, 0x76, 0x8F, // mode (adif string) + 0x80, 0x03, 0xff, 0xff, 0x00, 0x00, 0x76, 0x8F, // senderLocator (if known) + 0x80, 0x0B, 0x00, 0x01, 0x00, 0x00, 0x76, 0x8F // flags (informationSource) +}; + +void pskrep_sender::write_preamble(void) +{ + time_t now = time(NULL); + unsigned char* p = dgram; + + // header + *p++ = 0x00; *p++ = 0x0A; /* length written later */ p += 2; + /* time written later */ p += sizeof(uint32_t); + *reinterpret_cast(p) = htonl(sequence_number); p += sizeof(uint32_t); + memcpy(p, short_id.data(), SHORT_ID_SIZE); p += SHORT_ID_SIZE; + + const unsigned char* station_info_template; + size_t tlen; + vector* station_info; + + if (template_count == 0 && now - last_template >= TEMPLATE_INTERVAL) + template_count = TEMPLATE_THRESHOLD; + if (template_count > TEMPLATE_THRESHOLD) { + station_info_template = long_station_info_template; + tlen = sizeof(long_station_info_template); + station_info = &long_station_info; + } + else if (template_count >= 0) { + station_info_template = short_station_info_template; + tlen = sizeof(short_station_info_template); + station_info = &short_station_info; + } + if (template_count > 0) { + memcpy(p, rcpt_record_template, sizeof(rcpt_record_template)); p += sizeof(rcpt_record_template); + memcpy(p, station_info_template, tlen); p += tlen; + template_count--; + last_template = now; + } + + // station info record + memcpy(p, &(*station_info)[0], station_info->size()); p += station_info->size(); + + report_offset = p - dgram; + // write report record header + memcpy(p, rcpt_record_template + 4, 2); p += 2; /* length written later */ p += sizeof(uint16_t); + + dgram_size = p - dgram; +} + +bool pskrep_sender::append(const string& callsign, const band_map_t::value_type& r) +{ + if (dgram_size == 0) + write_preamble(); + + size_t call_len = callsign.length(); + call_len = MIN(MAX_TEXT_SIZE, call_len); + + const char* mode = mode_info[r.mode].adif_name; + size_t mode_len = strlen(mode); + mode_len = MIN(MAX_TEXT_SIZE, mode_len); + + size_t loc_len = MIN(MAX_TEXT_SIZE, r.locator.length()); + + // call_len + call + time + freq + mode_len + mode + loc_len + loc + info + size_t rlen = 1 + call_len + 4 + 4 + 1 + mode_len + 1 + loc_len + 1; + + if (pad(rlen, PAD) + dgram_size > DGRAM_MAX) // datagram full + return false; + + + LOG_INFO("Appending report (call=%s mode=%s freq=%lld time=%ld type=%u)", + callsign.c_str(), mode_info[r.mode].adif_name, r.freq, r.rtime, r.rtype); + + unsigned char* start = dgram + dgram_size; + unsigned char* p = start; + // call + *p++ = call_len; memcpy(p, callsign.data(), call_len); p += call_len; + // 4-byte reception time + *reinterpret_cast(p) = htonl(r.rtime); p += sizeof(uint32_t); + // 4-byte freq + *reinterpret_cast(p) = htonl(r.freq); p += sizeof(uint32_t); + // mode + *p++ = mode_len; memcpy(p, mode, mode_len); p += mode_len; + // locator + *p++ = loc_len; memcpy(p, r.locator.data(), loc_len); p += loc_len; + // info source + *p++ = r.rtype; + + LOG_DEBUG(" \"%s\"", printhex(start, p - start).c_str()); + + dgram_size += rlen; + return true; +} + +void* pskrep_sender::resolver(void* obj) +{ + pskrep_sender* s = reinterpret_cast(obj); + try { + s->send_socket = new Socket(Address(s->host.c_str(), s->port.c_str(), "udp")); + s->send_socket->connect(); + } + catch (const SocketException& e) { + LOG_ERROR("Could not resolve %s: %s", s->host.c_str(), e.what()); + } + + return NULL; +} + +void pskrep_sender::create_socket(void) +{ + if (pthread_create(&resolver_thread, NULL, resolver, this) != 0) { + LOG_PERROR("pthread_create"); + return; + } +} + +bool pskrep_sender::send(void) +{ + if (!send_socket) + return false; + + // empty dgram or no reports (shouldn't happen) + if (dgram_size == 0 || dgram_size == report_offset + 4) { + LOG_DEBUG("Not sending empty dgram: %zu %zu", dgram_size, report_offset); + return false; + } + + // Finish writing the report record: + // do we need padding? + size_t npad = (dgram_size - report_offset) % PAD; + if (npad) { + npad = PAD - npad; + memset(dgram + dgram_size, 0x0, npad); + dgram_size += npad; + } + // write length + *reinterpret_cast(dgram + report_offset + 2) = htons(dgram_size - report_offset); + + // finish writing the datagram + *reinterpret_cast(dgram + 2) = htons(dgram_size); + *reinterpret_cast(dgram + 4) = htonl(time(NULL)); + + bool ret; + LOG_DEBUG("Sending datagram (%zu): \"%s\"", dgram_size, printhex(dgram, dgram_size).c_str()); + try { + if ((size_t)send_socket->send(dgram, dgram_size) != dgram_size) + throw SocketException("short write"); + ret = true; + } + catch (const SocketException& e) { + LOG_ERROR("Could not send datagram to %s port %s: %s", host.c_str(), port.c_str(), e.what()); + ret = false; + } + + // increment this regardless of any errors + sequence_number++; + dgram_size = 0; + + return ret; +} + +// Pad len to a multiple of mult +size_t pskrep_sender::pad(size_t len, size_t mult) +{ + size_t r = len % mult; + return r ? len + mult - r : len; +} + +const char pskrep_sender::hexsym[] = "0123456789ABCDEF"; +const string& pskrep_sender::printhex(const unsigned char* s, size_t len) +{ + static string hex; + if (unlikely(len == 0)) + return hex.erase(); + + hex.resize(len * 3 - 1); + string::iterator i = hex.begin(); + size_t j; + for (j = 0; j < len-1; j++) { + *i++ = hexsym[s[j] >> 4]; + *i++ = hexsym[s[j] & 0xF]; + *i++ = ' '; + } + *i++ = hexsym[s[j] >> 4]; + *i = hexsym[s[j] & 0xF]; + + return hex; +} + +// ------------------------------------------------------------------------------------------------- + +static istream& operator>>(istream& in, rtype_t& t) +{ + int i; + in >> i; + t = static_cast(i); + return in; +} +static istream& operator>>(istream& in, status_t& t) +{ + int i; + in >> i; + t = static_cast(i); + return in; +} +static istream& operator>>(istream& in, rcpt_report_t& r) +{ + in >> r.mode >> r.freq >> r.rtime >> r.rtype >> r.status >> r.locator; + if (*r.locator.c_str() == '?') r.locator.clear(); + return in; +} + +static ostream& operator<<(ostream& out, const rcpt_report_t& r) +{ + return out << r.mode << ' ' << r.freq << ' ' << r.rtime << ' ' + << r.rtype << ' ' << r.status << ' ' << (r.locator.empty() ? "?" : r.locator); +} +static ostream& operator<<(ostream& out, const queue_t& q) +{ + for (queue_t::const_iterator i = q.begin(); i != q.end(); ++i) + for (call_map_t::const_iterator j = i->second.begin(); j != i->second.end(); ++j) + for (band_map_t::const_iterator k = j->second.begin(); k != j->second.end(); ++k) + out << *k << " " << j->first << " " << i->first << '\n'; + + return out; +} +static istream& operator>>(istream& in, queue_t& q) +{ + rcpt_report_t rep; + int band; + string call; + + while (in >> rep >> band >> call) + q[call][static_cast(band)].push_back(rep); + + return in; +} diff --git a/src/spot/spot.cxx b/src/spot/spot.cxx new file mode 100644 index 00000000..0834a1a4 --- /dev/null +++ b/src/spot/spot.cxx @@ -0,0 +1,203 @@ +// ---------------------------------------------------------------------------- +// spot.cxx +// +// Copyright (C) 2008 +// Stelios Bounanos, M0GLD +// +// This file is part of fldigi. +// +// fldigi is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 3 of the License, or +// (at your option) any later version. +// +// fldigi is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// ---------------------------------------------------------------------------- + +#include + +#include +#include + +#include "trx.h" +#include "globals.h" +#include "re.h" +#include "fl_digi.h" +#include "debug.h" +#include "spot.h" + +#define SEARCHLEN 32 +#define DECBUFSIZE 8 * SEARCHLEN + + +using namespace std; + +struct callback_t { + void* data; + + spot_log_cb_t lcb; + + spot_log_cb_t mcb; + + spot_recv_cb_t rcb; + fre_t* re; +}; + +static map buffers; +typedef vector cblist_t; + + +static cblist_t cblist; + +void spot_recv(char c, int decoder, int afreq) +{ + static trx_mode last_mode = NUM_MODES + 1; + + switch (decoder) { + case -1: // mode without multiple decoders + decoder = active_modem->get_mode(); + if (last_mode > NUM_MODES) + last_mode = decoder; + else if (last_mode != decoder) { + buffers.clear(); + last_mode = decoder; + } + break; + default: + if (last_mode > NUM_MODES) + last_mode = active_modem->get_mode(); + else if (last_mode != active_modem->get_mode()) { + buffers.clear(); + last_mode = active_modem->get_mode(); + } + break; + } + if (afreq == 0) + afreq = active_modem->get_freq(); + + string& buf = buffers[decoder]; + buf.reserve(DECBUFSIZE); + + buf += c; + string::size_type n = buf.length(); + if (n == DECBUFSIZE) + buf.erase(0, DECBUFSIZE - SEARCHLEN); + const char* search = buf.c_str() + (n > SEARCHLEN ? n - SEARCHLEN : 0); + + for (cblist_t::iterator i = cblist.begin(); i != cblist.end(); ++i) { + if (i->rcb && unlikely(i->re->match(search))) { + const vector& m = i->re->suboff(); + if (m.empty()) + i->rcb(afreq, search, NULL, 0, i->data); + else + i->rcb(afreq, search, &m[0], m.size(), i->data); + } + } +} + +static void get_log_details(long long& freq, trx_mode& mode, time_t& rtime) +{ + if (mode == NUM_MODES) + mode = active_modem->get_mode(); + if (mode >= MODE_WWV) + return; + + if (freq == 0LL) + freq = active_modem->get_freq(); + if (!wf->USB()) + freq = -freq; + freq += wf->rfcarrier(); + + if (rtime == -1L) + rtime = time(NULL); +} + +void spot_log(const char* callsign, const char* locator, long long freq, trx_mode mode, time_t rtime) +{ + get_log_details(freq, mode, rtime); + for (cblist_t::const_iterator i = cblist.begin(); i != cblist.end(); ++i) + if (i->lcb) + i->lcb(callsign, locator, freq, mode, rtime, i->data); +} + +void spot_manual(const char* callsign, const char* locator, long long freq, trx_mode mode, time_t rtime) +{ + get_log_details(freq, mode, rtime); + for (cblist_t::const_iterator i = cblist.begin(); i != cblist.end(); ++i) + if (i->mcb) + i->mcb(callsign, locator, freq, mode, rtime, i->data); +} + +// +// A callback of type spot_log_cb_t is registered with a data argument. +// The callback is invoked every time a QSO is logged. +// +void spot_register_log(spot_log_cb_t lcb, void* ldata) +{ + callback_t c = { ldata, lcb, 0, 0, 0 }; + cblist.push_back(c); +} +// +// A callback of type spot_log_cb_t is registered with a data argument. +// The callback is invoked every time the user manually spots a callsign. +// +void spot_register_manual(spot_log_cb_t mcb, void* mdata) +{ + callback_t c = { mdata, 0, mcb, 0, 0 }; + cblist.push_back(c); +} + +// +// A callback of type spot_recv_cb_t is registered with a regular +// expression (RE, RE_flags). If the RE matches the spotter's search +// buffer, the callback is invoked with offsets into the search buffer +// indicating substring matches, if the RE defines any, and with its +// data argument. The offset format is described in regexec(3). The +// buffer and offsets are only valid during that particular invocation. +// Clients should use anchoring to avoid repeated calls. +// +void spot_register_recv(spot_recv_cb_t rcb, void* rdata, const char* re, int reflags) +{ + callback_t c = { rdata, 0, 0, rcb, new fre_t(re, reflags) }; + cblist.push_back(c); + show_spot(true); +} + +void spot_unregister_log(spot_log_cb_t lcb, void* ldata) +{ + for (cblist_t::reverse_iterator ri = cblist.rbegin(); ri != cblist.rend(); ++ri) { + if (lcb == ri->lcb && ldata == ri->data) { + cblist.erase((++ri).base()); + break; + } + } +} +void spot_unregister_manual(spot_log_cb_t mcb, void* mdata) +{ + for (cblist_t::reverse_iterator ri = cblist.rbegin(); ri != cblist.rend(); ++ri) { + if (mcb == ri->mcb && mdata == ri->data) { + cblist.erase((++ri).base()); + break; + } + } +} +void spot_unregister_recv(spot_recv_cb_t rcb, void* rdata) +{ + cblist_t::reverse_iterator ri; + for (ri = cblist.rbegin(); ri != cblist.rend(); ++ri) { + if (rcb == ri->rcb && rdata == ri->data) { + cblist.erase((++ri).base()); + break; + } + } + + for (ri = cblist.rbegin(); ri != cblist.rend(); ++ri) + if (ri->rcb) break; + show_spot(ri != cblist.rend()); +}