1 // =====================================================================
2 //
3 // FD_logger.cxx
4 //
5 // interface to tcpip application fdserver.tcl
6 // fdserver is a multiple client tcpip server
7 //
8 // Copyright (C) 2016
9 // Dave Freese, W1HKJ
10 //
11 // This file is part of fldigi.
12 //
13 // Fldigi is free software: you can redistribute it and/or modify
14 // it under the terms of the GNU General Public License as published by
15 // the Free Software Foundation, either version 3 of the License, or
16 // (at your option) any later version.
17 //
18 // Fldigi is distributed in the hope that it will be useful,
19 // but WITHOUT ANY WARRANTY; without even the implied warranty of
20 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 // GNU General Public License for more details.
22 //
23 // You should have received a copy of the GNU General Public License
24 // along with fldigi. If not, see <http://www.gnu.org/licenses/>.
25 // =====================================================================
26
27 #include <iostream>
28 #include <cmath>
29 #include <cstring>
30 #include <vector>
31 #include <list>
32 #include <stdlib.h>
33
34 #include <FL/Fl_Text_Display.H>
35 #include <FL/Fl_Text_Buffer.H>
36
37 #include "fl_digi.h"
38 #include "rigsupport.h"
39 #include "modem.h"
40 #include "trx.h"
41 #include "configuration.h"
42 #include "main.h"
43 #include "waterfall.h"
44 #include "macros.h"
45 #include "qrunner.h"
46 #include "debug.h"
47 #include "status.h"
48 #include "icons.h"
49
50 #include "logsupport.h"
51 #include "fd_logger.h"
52 #include "fd_view.h"
53 #include "flmisc.h"
54
55 #include "confdialog.h"
56
57 LOG_FILE_SOURCE(debug::LOG_FD);
58
59 using namespace std;
60
61 //forward declarations of local functions
62 void FD_start();
63
64 //======================================================================
65 // Socket FD i/o used on all platforms
66 //======================================================================
67
68 pthread_t FD_thread;
69 pthread_t FD_rx_socket_thread;
70 pthread_mutex_t FD_mutex = PTHREAD_MUTEX_INITIALIZER;
71
72 Socket *FD_socket = 0;
73
74 bool FD_connected = false;
75 bool FD_logged_on = false;
76 bool FD_enabled = false;
77 bool FD_exit = false;
78
79 string FD_ip_addr = "";
80 string FD_ip_port = "";
81
82 string FD_rxbuffer;
83
84 //======================================================================
85 // data report from fdserver.tcl
86 // LOGON w1hkj 40,DIG
87 // LOGON_OK W1HKJ 1A 5
88 // SCORE 35
89 // WORKED ... all on a single line
90 // {160 CW 1} {160 DIG 0} {160 PHONE 0}
91 // {80 CW 0} {80 DIG 0} {80 PHONE 0}
92 // {40 CW 1} {40 DIG 0} {40 PHONE 0}
93 // {20 CW 0} {20 DIG 1} {20 PHONE 0}
94 // {17 CW 0} {17 DIG 0} {17 PHONE 1}
95 // {15 CW 0} {15 DIG 0} {15 PHONE 1}
96 // {10 CW 0} {10 DIG 0} {10 PHONE 0}
97 // {2 CW 0} {2 DIG 0} {2 PHONE 0}
98 // {440 CW 0} {440 DIG 0} {440 PHONE 0}
99 //======================================================================
100
101 //======================================================================
102 //
103 //======================================================================
post(Fl_Input2 * w,const char * s)104 void post(Fl_Input2 *w, const char * s)
105 {
106 w->value(s);
107 }
108
109 //======================================================================
110 //
111 //======================================================================
view(Fl_Output * w,const char * s)112 void view(Fl_Output *w, const char * s)
113 {
114 w->value(s);
115 }
116
117 //======================================================================
118 //
119 //======================================================================
120 static string toparse;
121
parse_logon_ok(string s)122 void parse_logon_ok(string s)
123 {
124 size_t p = 0;
125 static string call, clss, mult, sect;
126 call.clear(); clss.clear(); sect.clear();
127 s.erase(0, 9);
128 call = s;
129 call.erase(call.find(" "));
130 p = s.find(" ");
131 if (p != string::npos) {
132 s.erase(0, p+1);
133 clss = s;
134 p = clss.find(" ");
135 clss.erase(p);
136 p = s.find(" ");
137 if (p != string::npos) {
138 s.erase(0,p+1);
139 mult = s;
140 p = mult.find(" ");
141 mult.erase(p);
142 p = s.find(" ");
143 if (p != string::npos) {
144 s.erase(0,p+1);
145 sect = s;
146 p = sect.find("\r");
147 if (p != string::npos) sect.erase(p);
148 p = sect.find("\n");
149 if (p != string::npos) sect.erase(p);
150 }
151 }
152 }
153 progdefaults.my_FD_call = call;
154 REQ(&post, inp_my_FD_call, call.c_str());
155 REQ(&view, view_FD_call, call.c_str());
156 progdefaults.my_FD_class = clss;
157 REQ(&post, inp_my_FD_class, clss.c_str());
158 REQ(&view, view_FD_class, clss.c_str());
159 progdefaults.my_FD_section = sect;
160 REQ(&post, inp_my_FD_section, sect.c_str());
161 REQ(&view, view_FD_section, sect.c_str());
162 progdefaults.my_FD_mult = mult;
163 REQ(&view, view_FD_mult, mult.c_str());
164 }
165
166 //======================================================================
167 //
168 //======================================================================
parse_score(string s)169 void parse_score(string s)
170 {
171 static string sscore;
172 size_t p = s.find("\r");
173 if (p != string::npos) s.erase(p);
174 p = s.find("\n");
175 if (p != string::npos) s.erase(p);
176 p = s.find(" ");
177 if (p != string::npos) s.erase(0, p+1);
178 sscore = s;
179 REQ(&view, view_FD_score, sscore.c_str());
180 }
181
182 //======================================================================
183 //
184 //======================================================================
parse_entry(string needle,Fl_Output * view1,Fl_Output * view2)185 void parse_entry( string needle, Fl_Output *view1, Fl_Output *view2 )
186 {
187 size_t p1 = toparse.find(needle);
188 if (p1 == string::npos) return;
189 p1 += needle.length();
190 size_t p2 = toparse.find(" ", p1);
191 size_t p3 = toparse.find("}", p1);
192 if (p3 == string::npos) return;
193 string num = "", op = "";
194 num = toparse.substr(p1, p2 - p1);
195 op = toparse.substr(p2+1, p3 - (p2+1));
196
197 view1->value(num.c_str());
198 view2->value(op.c_str());
199 }
200
201 //======================================================================
202 //
203 //======================================================================
parse_worked()204 void parse_worked()
205 {
206 // CW contacts
207 parse_entry("{160 CW ", view_FD_CW[0], view_FD_CW_OP[0]);
208 parse_entry("{80 CW ", view_FD_CW[1], view_FD_CW_OP[1]);
209 parse_entry("{40 CW ", view_FD_CW[2], view_FD_CW_OP[2]);
210 parse_entry("{20 CW ", view_FD_CW[3], view_FD_CW_OP[3]);
211 parse_entry("{17 CW ", view_FD_CW[4], view_FD_CW_OP[4]);
212 parse_entry("{15 CW ", view_FD_CW[5], view_FD_CW_OP[5]);
213 parse_entry("{12 CW ", view_FD_CW[6], view_FD_CW_OP[6]);
214 parse_entry("{10 CW ", view_FD_CW[7], view_FD_CW_OP[7]);
215 parse_entry("{6 CW ", view_FD_CW[8], view_FD_CW_OP[8]);
216 parse_entry("{2 CW ", view_FD_CW[9], view_FD_CW_OP[9]);
217 parse_entry("{220 CW ", view_FD_CW[10], view_FD_CW_OP[10]);
218 parse_entry("{440 CW ", view_FD_CW[11], view_FD_CW_OP[11]);
219
220 // DIG contacts
221 parse_entry("{160 DIG ", view_FD_DIG[0], view_FD_DIG_OP[0]);
222 parse_entry("{80 DIG ", view_FD_DIG[1], view_FD_DIG_OP[1]);
223 parse_entry("{40 DIG ", view_FD_DIG[2], view_FD_DIG_OP[2]);
224 parse_entry("{20 DIG ", view_FD_DIG[3], view_FD_DIG_OP[3]);
225 parse_entry("{17 DIG ", view_FD_DIG[4], view_FD_DIG_OP[4]);
226 parse_entry("{15 DIG ", view_FD_DIG[5], view_FD_DIG_OP[5]);
227 parse_entry("{12 DIG ", view_FD_DIG[6], view_FD_DIG_OP[6]);
228 parse_entry("{10 DIG ", view_FD_DIG[7], view_FD_DIG_OP[7]);
229 parse_entry("{6 DIG ", view_FD_DIG[8], view_FD_DIG_OP[8]);
230 parse_entry("{2 DIG ", view_FD_DIG[9], view_FD_DIG_OP[9]);
231 parse_entry("{220 DIG ", view_FD_DIG[10], view_FD_DIG_OP[10]);
232 parse_entry("{440 DIG ", view_FD_DIG[11], view_FD_DIG_OP[11]);
233
234 // PHONE contacts
235 parse_entry("{160 PHONE ", view_FD_PHONE[0], view_FD_PHONE_OP[0]);
236 parse_entry("{80 PHONE ", view_FD_PHONE[1], view_FD_PHONE_OP[1]);
237 parse_entry("{40 PHONE ", view_FD_PHONE[2], view_FD_PHONE_OP[2]);
238 parse_entry("{20 PHONE ", view_FD_PHONE[3], view_FD_PHONE_OP[3]);
239 parse_entry("{17 PHONE ", view_FD_PHONE[4], view_FD_PHONE_OP[4]);
240 parse_entry("{15 PHONE ", view_FD_PHONE[5], view_FD_PHONE_OP[5]);
241 parse_entry("{12 PHONE ", view_FD_PHONE[6], view_FD_PHONE_OP[6]);
242 parse_entry("{10 PHONE ", view_FD_PHONE[7], view_FD_PHONE_OP[7]);
243 parse_entry("{6 PHONE ", view_FD_PHONE[8], view_FD_PHONE_OP[8]);
244 parse_entry("{2 PHONE ", view_FD_PHONE[9], view_FD_PHONE_OP[9]);
245 parse_entry("{220 PHONE ", view_FD_PHONE[10], view_FD_PHONE_OP[10]);
246 parse_entry("{440 PHONE ", view_FD_PHONE[11], view_FD_PHONE_OP[11]);
247
248 }
249
clear_fd_viewer()250 void clear_fd_viewer() {
251 for (int i = 0; i < 12; i++) {
252 view_FD_CW[i]->value("");
253 view_FD_CW_OP[i]->value("");
254 view_FD_DIG[i]->value("");
255 view_FD_DIG_OP[i]->value("");
256 view_FD_PHONE[i]->value("");
257 view_FD_PHONE_OP[i]->value("");
258 }
259 view_FD_call->value("");
260 view_FD_class->value("");
261 view_FD_section->value("");
262 view_FD_mult->value("");
263 box_fdserver_connected->color((Fl_Color)31);
264 box_fdserver_connected->redraw();
265 }
266
267 //======================================================================
268 //
269 //======================================================================
parse_FD_stream(string data)270 void parse_FD_stream(string data)
271 {
272 size_t p = 0;
273 if (data.empty()) return;
274
275 //std::cout << "RX Stream:\n" << data << std::endl;
276
277 if (data.find("QUIT") != string::npos) {
278 //std::cout << "Quit\n";
279 FD_disconnect();
280 btn_fd_connect->value(0);
281 return;
282 }
283 if (data.find("LOGON_DENIED") != string::npos) {
284 //std::cout << "Logon denied\n";
285 FD_logged_on = false;
286 LOG_ERROR("FD logon DENIED");
287 btn_fd_connect->value(0);
288 return;
289 }
290 if (data.find("LOGOFF_OK") != string::npos) {
291 //std::cout << "Log off OK\n";
292 FD_logged_on = false;
293 return;
294 }
295 if (data.find("LOGON_OK") != string::npos) {
296 //std::cout << "Logon OK\n";
297 parse_logon_ok(data.substr(p));
298 FD_logged_on = true;
299 box_fdserver_connected->color((Fl_Color)2);
300 box_fdserver_connected->redraw();
301 }
302 if ( (p = data.find("SCORE") ) != string::npos) {
303 //std::cout << "SCORE\n";
304 parse_score(data.substr(p));
305 }
306 if ( (p = data.find("WORKED"))!= string::npos) {
307 //std::cout << "WORKED\n";
308 toparse = data.substr(p);
309 REQ(parse_worked);
310 return;
311 }
312 if ( data.find("NODUP") != string::npos) {
313 //std::cout << "Not a duplicate\n";
314 return;
315 }
316 else if (data.find("DUP") != string::npos) {
317 //std::cout << "Duplicate\n";
318 }
319 else if (data.find("REJECT") != string::npos) {
320 //std::cout << "Reject\n";
321 }
322 else if (data.find("ACCEPT") != string::npos) {
323 //std::cout << "Accept\n";
324 }
325 }
326
327 //======================================================================
328 //
329 //======================================================================
FD_write(string s)330 void FD_write(string s)
331 {
332 FD_socket->send(s.append("\n"));
333 }
334
335 //======================================================================
336 //
337 //======================================================================
FD_get_record(string call)338 void FD_get_record(string call)
339 {
340 if(!FD_socket) return;
341 if (!FD_connected) return;
342 }
343
344 //======================================================================
345 //
346 //======================================================================
FD_add_record()347 void FD_add_record()
348 {
349 if(!FD_socket) return;
350 if (!FD_connected) return;
351 guard_lock send_lock(&FD_mutex);
352 string cmd = "ADD ";
353 cmd.append(inpCall->value()).append(" ");
354 cmd.append(inpSection->value()).append(" ");
355 cmd.append(inpClass->value());
356 FD_write(cmd);
357 }
358
359 //======================================================================
360 //
361 //======================================================================
362 static string fd_band;
363 static string fd_mode;
364
FD_opmode()365 static string FD_opmode()
366 {
367 if (!active_modem) {
368 return "DIG";
369 }
370 if (active_modem->get_mode() == MODE_CW)
371 return "CW";
372 else if (active_modem->get_mode() < MODE_SSB)
373 return "DIG";
374 else if (active_modem->get_mode() == MODE_SSB)
375 return "PHONE";
376 return "";
377 }
378
379 //======================================================================
380 //
381 //======================================================================
FD_opband()382 static string FD_opband()
383 {
384 if (!active_modem) return "40";
385
386 float freq = qsoFreqDisp->value();
387 freq /= 1e6;
388
389 if (freq >= 1.8 && freq < 3.5) return "160";
390 if (freq >= 3.5 && freq <= 7.0) return "80";
391 if (freq >= 7.0 && freq <= 7.5) return "40";
392 if (freq >= 14.0 && freq < 18.0) return "20";
393 if (freq >= 18.0 && freq < 21.0) return "17";
394 if (freq >= 21.0 && freq < 24.0) return "15";
395 if (freq >= 24.0 && freq < 28.0) return "12";
396 if (freq >= 28.0 && freq < 50.0) return "10";
397 if (freq >= 50.0 && freq < 70.0) return "6";
398 if (freq >= 144.0 && freq < 222.0) return "2";
399 if (freq >= 222.0 && freq < 420.0) return "222";
400 if (freq >= 420.0 && freq < 444.0) return "440";
401 return "";
402 }
403
404 //======================================================================
405 //
406 //======================================================================
FD_dupcheck()407 int FD_dupcheck()
408 {
409 if(!FD_socket) return 0;
410 if (!FD_connected) return 0;
411
412 string response;
413 string cmd = "DUPCHECK ";
414 cmd.append(inpCall->value());
415 try {
416 guard_lock send_lock(&FD_mutex);
417 FD_write(cmd);
418 MilliSleep(50);
419 FD_socket->recv(response);
420 if (response.empty())
421 return 0;
422 if (response.find("NODUP") != string::npos)
423 return 0;
424 if (response.find("DUP") != string::npos)
425 return 1;
426 return 0;
427 } catch (const SocketException& e) {
428 LOG_ERROR("Error %d, %s", e.error(), e.what());
429 FD_exit = true;
430 }
431 return 0;
432 }
433
434 //======================================================================
435 //
436 //======================================================================
FD_logon()437 void FD_logon()
438 {
439 string ucasecall = progdefaults.fd_op_call;
440 string buffer;
441 string cmd = "LOGON ";
442
443 if (ucasecall.empty()) return;
444 for (size_t n = 0; n < ucasecall.length(); n++)
445 ucasecall[n] = toupper(ucasecall[n]);
446
447 fd_band = FD_opband();
448 fd_mode = FD_opmode();
449 if (fd_band.empty() || fd_mode.empty()) return;
450
451 cmd.append(ucasecall).append(" ");
452 cmd.append(fd_band).append(",");
453 cmd.append(fd_mode);
454
455 try {
456 guard_lock send_lock(&FD_mutex);
457 //std::cout << "Log On: " << cmd << std::endl;
458 FD_write(cmd);
459 FD_socket->recv(buffer);
460
461 parse_FD_stream(buffer);
462
463 LOG_INFO("Logged on to fdserver");
464 FD_logged_on = true;
465
466 } catch (const SocketException& e) {
467 LOG_ERROR("Error %d, %s", e.error(), e.what());
468 }
469 }
470
471 //======================================================================
472 //
473 //======================================================================
FD_logoff()474 void FD_logoff()
475 {
476 if (!FD_socket) return;
477
478 guard_lock send_lock(&FD_mutex);
479 try {
480 string buffer;
481 FD_write("LOGOFF");
482 MilliSleep(100);
483 FD_socket->recv(buffer);
484 parse_FD_stream(buffer);
485
486 FD_disconnect();
487 LOG_INFO("Logged off FD server");
488 } catch (const SocketException& e) {
489 LOG_ERROR("Error %d, %s", e.error(), e.what());
490 }
491 }
492
493 //======================================================================
494 //
495 //======================================================================
FD_band_check()496 void FD_band_check() {
497 if (fd_band != FD_opband()) {
498 FD_logoff();
499 MilliSleep(50);
500 FD_start();
501 FD_logon();
502 }
503 }
504
505 //======================================================================
506 //
507 //======================================================================
FD_mode_check()508 void FD_mode_check() {
509 if (fd_mode != FD_opmode()) {
510 FD_logoff();
511 MilliSleep(50);
512 FD_start();
513 FD_logon();
514 }
515 }
516
517 //======================================================================
518 //
519 //======================================================================
FD_rcv_data()520 void FD_rcv_data()
521 {
522 string tempbuff;
523 try {
524 guard_lock send_lock(&FD_mutex);
525 FD_socket->recv(tempbuff);
526 if (tempbuff.empty())
527 return;
528 parse_FD_stream(tempbuff);
529 } catch (const SocketException& e) {
530 LOG_ERROR("Error %d, %s", e.error(), e.what());
531 FD_exit = true;
532 }
533 }
534
535 //======================================================================
536 //
537 //======================================================================
538 static int fd_looptime = 1000; // in milliseconds
FD_start()539 void FD_start()
540 {
541 guard_lock send_lock(&FD_mutex);
542
543 FD_ip_addr = progdefaults.fd_tcpip_addr;
544 FD_ip_port = progdefaults.fd_tcpip_port;
545
546 try {
547 FD_socket = new Socket(
548 Address( FD_ip_addr.c_str(),
549 FD_ip_port.c_str(),
550 "tcp") );
551 FD_socket->set_timeout(0.01);
552 FD_socket->connect();
553 FD_socket->set_nonblocking(true);
554
555 LOG_INFO( "Connected to fdserver %s:%s",
556 FD_ip_addr.c_str(), FD_ip_port.c_str() );
557
558 fd_looptime = 100;
559 FD_connected = true;
560 }
561 catch (const SocketException& e) {
562 // LOG_ERROR("%s", e.what() );
563 delete FD_socket;
564 FD_socket = 0;
565 FD_connected = false;
566 }
567 }
568
569 //======================================================================
570 //
571 //======================================================================
FD_restart()572 void FD_restart()
573 {
574 guard_lock send_lock(&FD_mutex);
575
576 FD_ip_addr = progdefaults.fd_tcpip_addr;
577 FD_ip_port = progdefaults.fd_tcpip_port;
578
579 try {
580 FD_socket->shut_down();
581 FD_socket->close();
582 delete FD_socket;
583 FD_connected = false;
584 FD_socket = new Socket(
585 Address( FD_ip_addr.c_str(),
586 FD_ip_port.c_str(),
587 "tcp") );
588 FD_socket->set_timeout(0.01);
589 FD_socket->connect();
590 FD_socket->set_nonblocking(true);
591 fd_looptime = 100;
592
593 LOG_INFO( "Connected to fdserver %s:%s",
594 FD_ip_addr.c_str(), FD_ip_port.c_str() );
595 }
596 catch (const SocketException& e) {
597 // LOG_ERROR("%s", e.what() );
598 delete FD_socket;
599 FD_socket = 0;
600 FD_connected = false;
601 }
602 }
603
604 //======================================================================
605 // Disconnect from FD tcpip server
606 //======================================================================
FD_disconnect()607 void FD_disconnect()
608 {
609 if (!FD_socket) return;
610
611 // send disconnect string
612 FD_socket->shut_down();
613 FD_socket->close();
614 delete FD_socket;
615 FD_socket = 0;
616 FD_connected = false;
617 FD_logged_on = false;
618 REQ(clear_fd_viewer);
619 LOG_INFO("Disconnected from fdserver");
620 }
621
622 ////////////////////////////////////////////////
623 // NEED TO DETECT WHEN SERVER IS CLOSED OR FAILS
624 ////////////////////////////////////////////////
625
626 //======================================================================
627 // Thread loop
628 //======================================================================
629
630 static notify_dialog *fd_alert_window = 0;
631
ui_feedback()632 void ui_feedback()
633 {
634 btn_fd_connect->value(0);
635
636 if (!fd_alert_window) fd_alert_window = new notify_dialog;
637
638 fd_alert_window->notify(_("Check FD server, connect failed"), 15.0);
639 show_notifier(fd_alert_window);
640 }
641
FD_loop(void * args)642 void *FD_loop(void *args)
643 {
644 SET_THREAD_ID(FD_TID);
645
646 int try_count = 10;
647 while(1) {
648 for (int i = 0; i < fd_looptime/50; i++) {
649 MilliSleep(50);
650 if (FD_exit) break;
651 }
652
653 if (!FD_connected && progdefaults.connect_to_fdserver) {
654 if (try_count--)
655 FD_start();
656 else {
657 progdefaults.connect_to_fdserver = false;
658 REQ(ui_feedback);
659 try_count = 10;
660 }
661 }
662
663 if ( FD_connected &&
664 ((FD_ip_addr != progdefaults.fd_tcpip_addr) ||
665 (FD_ip_port != progdefaults.fd_tcpip_port)) )
666 FD_restart();
667
668 if (FD_exit) break;
669
670 if (FD_connected && !progdefaults.connect_to_fdserver) {
671 if (FD_logged_on) FD_logoff();
672 FD_disconnect();
673 } else if ( FD_connected &&
674 !FD_logged_on &&
675 progdefaults.connect_to_fdserver) {
676 FD_logon();
677 } else if (FD_connected && FD_logged_on) {
678 FD_rcv_data();
679 FD_mode_check();
680 FD_band_check();
681 }
682 }
683
684 if (FD_logged_on && FD_socket)
685 FD_logoff(); // calls FD_disconnect()
686 else
687 FD_disconnect();
688
689 // exit the FD thread
690 SET_THREAD_CANCEL();
691 return NULL;
692 }
693
694 //======================================================================
695 //
696 //======================================================================
FD_init(void)697 void FD_init(void)
698 {
699 FD_enabled = false;
700 FD_exit = false;
701
702 if (pthread_create(&FD_thread, NULL, FD_loop, NULL) < 0) {
703 LOG_ERROR("%s", "pthread_create failed");
704 return;
705 }
706
707 LOG_INFO("%s", "fdserver thread started");
708
709 FD_enabled = true;
710 }
711
712 //======================================================================
713 //
714 //======================================================================
FD_close(void)715 void FD_close(void)
716 {
717 if (!FD_enabled) return;
718
719 FD_exit = true;
720 pthread_join(FD_thread, NULL);
721 FD_enabled = false;
722
723 LOG_INFO("%s", "fdserver thread terminated. ");
724
725 if (FD_socket) {
726 FD_socket->shut_down();
727 FD_socket->close();
728 }
729
730 }
731
732