1 // ----------------------------------------------------------------------------
2 // fsq.cxx -- fsq modem
3 //
4 // Copyright (C) 2015
5 // Dave Freese, W1HKJ
6 //
7 // This file is part of fldigi.
8 //
9 // Fldigi is free software: you can redistribute it and/or modify
10 // it under the terms of the GNU General Public License as published by
11 // the Free Software Foundation, either version 3 of the License, or
12 // (at your option) any later version.
13 //
14 // Fldigi is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 // GNU General Public License for more details.
18 //
19 // You should have received a copy of the GNU General Public License
20 // along with fldigi. If not, see <http://www.gnu.org/licenses/>.
21 // ----------------------------------------------------------------------------
22
23 #include <config.h>
24
25 #include <stdlib.h>
26 #include <iostream>
27 #include <fstream>
28 #include <string>
29 #include <sstream>
30 #include <iomanip>
31 #include <cmath>
32 #include <vector>
33 #include <libgen.h>
34
35 #include <FL/filename.H>
36 #include "progress.h"
37 #include "fsq.h"
38 #include "complex.h"
39 #include "fl_digi.h"
40 #include "misc.h"
41 #include "fileselect.h"
42 #include "threads.h"
43 #include "debug.h"
44 #include "re.h"
45
46 #include "configuration.h"
47 #include "qrunner.h"
48 #include "fl_digi.h"
49 #include "status.h"
50 #include "main.h"
51 #include "icons.h"
52 #include "ascii.h"
53 #include "timeops.h"
54 #include "flmisc.h"
55
56 #include "test_signal.h"
57
58 using namespace std;
59
60 #include "fsq_varicode.cxx"
61
62 void clear_xmt_arrays();
63
64 #define SQLFILT_SIZE 64
65
66 #define NIT std::string::npos
67 #define txcenterfreq 1500.0
68
69 int fsq::symlen = 4096; // nominal symbol length; 3 baud
70
71 static const char *FSQBOL = " \n";
72 static const char *FSQEOL = "\n ";
73 static const char *FSQEOT = " \b ";
74 static const char *fsq_lf = "\n";
75 static const char *fsq_bot = "<bot>";
76 static const char *fsq_eol = "<eol>";
77 static const char *fsq_eot = "<eot>";
78
79 static std::string fsq_string;
80 static std::string fsq_delayed_string;
81
82 static bool enable_audit_log = false;
83 static bool enable_heard_log = false;
84
85 #include "fsq-pic.cxx"
86
87 static notify_dialog *alert_window = 0;
88
post_alert(std::string s1,double timeout=0.0,std::string s2="")89 void post_alert(std::string s1, double timeout = 0.0, std::string s2 = "")
90 {
91 if (active_modem && (active_modem->get_mode() == MODE_FSQ) && !s2.empty()) {
92 active_modem->send_ack(s2);
93 }
94
95 if (!alert_window) alert_window = new notify_dialog;
96 alert_window->notify(s1.c_str(), timeout);
97 REQ(show_notifier, alert_window);
98
99 display_fsq_mon_text(s1, FTextBase::ALTR);
100
101 }
102
103 // nibbles table used for fast conversion from tone difference to symbol
104
sz2utf8(std::string s)105 static std::string sz2utf8(std::string s)
106 {
107 char dest[4*s.length() + 1]; // if every char were utf8
108 int numbytes = fl_utf8froma(dest, sizeof(dest) - 1, s.c_str(), s.length());
109 if (numbytes > 0) return dest;
110 return s;
111 }
112
113 //static int nibbles[199];
init_nibbles()114 void fsq::init_nibbles()
115 {
116 int nibble = 0;
117 for (int i = 0; i < 199; i++) {
118 nibble = floor(0.5 + (i - 99)/3.0);
119 // allow for wrap-around (33 tones for 32 tone differences)
120 if (nibble < 0) nibble += 33;
121 if (nibble > 32) nibble -= 33;
122 // adjust for +1 symbol at the transmitter
123 nibble--;
124 nibbles[i] = nibble;
125 }
126 }
127
128 // note:
129 // display_fsq_rx_text and
130 // display_fsq_mon_text
131 // use a REQ(...) access to the UI
132 // it is not necessary to indirectly call either write_rx_mon_char or
133 // write_mon_tx_char using the REQ access mechanism
134
write_rx_mon_char(int ch)135 void write_rx_mon_char(int ch)
136 {
137 int ach = ch & 0xFF;
138 if (!progdefaults.fsq_directed) {
139 display_fsq_rx_text(fsq_ascii[ach], FTextBase::FSQ_UND) ;
140 if (ach == '\n')
141 display_fsq_rx_text(fsq_lf, FTextBase::FSQ_UND);
142 }
143 display_fsq_mon_text(fsq_ascii[ach], FTextBase::RECV);
144 if (ach == '\n')
145 display_fsq_mon_text(fsq_lf, FTextBase::RECV);
146 }
147
write_mon_tx_char(int ch)148 void write_mon_tx_char(int ch)
149 {
150 int ach = ch & 0xFF;
151
152 display_fsq_mon_text(fsq_ascii[ach], FTextBase::FSQ_TX);
153 if (ach == '\n')
154 display_fsq_mon_text(fsq_lf, FTextBase::FSQ_TX);
155 }
156
printit(double speed,int bandwidth,int symlen,int bksize,int peak_hits,int tone)157 void printit(double speed, int bandwidth, int symlen, int bksize, int peak_hits, int tone)
158 {
159 std::ostringstream it;
160 it << "\nSpeed.......... " << speed << "\nBandwidth...... " << bandwidth;
161 it << "\nSymbol length.. " << symlen << "\nBlock size..... " << bksize;
162 it << "\nMinimum Hits... " << peak_hits << "\nBasetone....... " << tone << "\n";
163 display_fsq_mon_text(it.str(), FTextBase::ALTR);
164 }
165
fsq(trx_mode md)166 fsq::fsq(trx_mode md) : modem()
167 {
168 modem::set_freq(1500); // default Rx/Tx center frequency
169
170 mode = md;
171 samplerate = SR;
172 fft = new g_fft<double>(FFTSIZE);
173 snfilt = new Cmovavg(SQLFILT_SIZE);
174
175 noisefilt = new Cmovavg(32);
176 sigfilt = new Cmovavg(8);
177
178 // baudfilt = new Cmovavg(3);
179 movavg_size = progdefaults.fsq_movavg;
180 if (movavg_size < 1) movavg_size = progdefaults.fsq_movavg = 1;
181 if (movavg_size > MOVAVGLIMIT) movavg_size = progdefaults.fsq_movavg = MOVAVGLIMIT;
182 for (int i = 0; i < NUMBINS; i++) binfilt[i] = new Cmovavg(movavg_size);
183 spacing = 3;
184 txphase = 0;
185 basetone = 333;
186
187 picfilter = new C_FIR_filter();
188 picfilter->init_lowpass(257, 1, 500.0 / samplerate);
189 phase = 0;
190 phidiff = 2.0 * M_PI * frequency / samplerate;
191 prevz = cmplx(0,0);
192
193 bkptr = 0;
194 peak_counter = 0;
195 peak = last_peak = 0;
196 max = 0;
197 curr_nibble = prev_nibble = 0;
198 s2n = 0;
199 ch_sqlch_open = false;
200 memset(rx_stream, 0, sizeof(rx_stream));
201 rx_text.clear();
202
203 for (int i = 0; i < BLOCK_SIZE; i++)
204 a_blackman[i] = blackman(1.0 * i / BLOCK_SIZE);
205
206 fsq_tx_image = false;
207
208 init_nibbles();
209
210 start_aging();
211
212 show_mode();
213
214 restart();
215 toggle_logs();
216 }
217
~fsq()218 fsq::~fsq()
219 {
220 delete fft;
221 delete snfilt;
222
223 delete sigfilt;
224 delete noisefilt;
225
226 for (int i = 0; i < NUMBINS; i++)
227 delete binfilt[i];
228 // delete baudfilt;
229 delete picfilter;
230 REQ(close_fsqMonitor);
231 stop_sounder();
232 stop_aging();
233
234 if (enable_audit_log)
235 audit_log.close();
236 if (enable_heard_log)
237 heard_log.close();
238 };
239
tx_init()240 void fsq::tx_init()
241 {
242 tone = prevtone = 0;
243 txphase = 0;
244 send_bot = true;
245 mycall = progdefaults.myCall;
246 if (progdefaults.fsq_lowercase)
247 for (size_t n = 0; n < mycall.length(); n++) mycall[n] = tolower(mycall[n]);
248 videoText();
249 }
250
rx_init()251 void fsq::rx_init()
252 {
253 set_freq(frequency);
254 bandwidth = 33 * spacing * samplerate / FSQ_SYMLEN;
255 bkptr = 0;
256 peak_counter = 0;
257 peak = last_peak = 0;
258 max = 0;
259 curr_nibble = prev_nibble = 0;
260 s2n = 0;
261 ch_sqlch_open = false;
262 memset(rx_stream, 0, sizeof(rx_stream));
263
264 rx_text.clear();
265 for (int i = 0; i < NUMBINS; i++) {
266 tones[i] = 0.0;
267 binfilt[i]->reset();
268 }
269
270 pixel = 0;
271 amplitude = 0;
272 phase = 0;
273 prevz = cmplx(0,0);
274 image_counter = 0;
275 RXspp = 10; // 10 samples per pixel
276 state = TEXT;
277 }
278
init()279 void fsq::init()
280 {
281 modem::init();
282
283 sounder_interval = progdefaults.fsq_sounder;
284 start_sounder(sounder_interval);
285
286 rx_init();
287 }
288
set_freq(double f)289 void fsq::set_freq(double f)
290 {
291 frequency = f;
292 modem::set_freq(frequency);
293 basetone = ceil(1.0*(frequency - bandwidth / 2) * FSQ_SYMLEN / samplerate);
294 tx_basetone = ceil((get_txfreq() - bandwidth / 2) * FSQ_SYMLEN / samplerate );
295 int incr = basetone % spacing;
296 basetone -= incr;
297 tx_basetone -= incr;
298 }
299
show_mode()300 void fsq::show_mode()
301 {
302 if (speed == 1.5 )
303 put_MODEstatus("FSQ-1.5");
304 else if (speed == 2.0)
305 put_MODEstatus("FSQ-2");
306 else if (speed == 3.0)
307 put_MODEstatus("FSQ-3");
308 else if (speed == 4.5)
309 put_MODEstatus("FSQ-4.5");
310 else
311 put_MODEstatus("FSQ-6");
312 }
313
adjust_for_speed()314 void fsq::adjust_for_speed()
315 {
316 speed = progdefaults.fsqbaud;
317
318 if( speed == 1.5 ) {
319 symlen = 8192;
320 } else if (speed == 2.0) {
321 symlen = 6144;
322 } else if (speed == 3.0) {
323 symlen = 4096;
324 } else if (speed == 4.5) {
325 symlen = 3072;
326 } else { // speed == 6
327 symlen = 2048;
328 }
329 show_mode();
330 }
331
toggle_logs()332 void fsq::toggle_logs()
333 {
334 if (enable_heard_log != progdefaults.fsq_enable_heard_log) {
335 enable_heard_log = progdefaults.fsq_enable_heard_log;
336 if (heard_log.is_open()) heard_log.close();
337 }
338
339 if (enable_audit_log != progdefaults.fsq_enable_audit_log) {
340 enable_audit_log = progdefaults.fsq_enable_audit_log;
341 if (audit_log.is_open()) audit_log.close();
342 }
343
344 if (enable_heard_log) {
345 heard_log_fname = progdefaults.fsq_heard_log;
346 std::string sheard = TempDir;
347 sheard.append(heard_log_fname);
348 heard_log.open(sheard.c_str(), ios::app);
349
350 heard_log << "==================================================\n";
351 heard_log << "Heard log: " << zdate() << ", " << ztime() << "\n";
352 heard_log << "==================================================\n";
353 }
354
355 if (enable_audit_log) {
356 audit_log_fname = progdefaults.fsq_audit_log;
357 std::string saudit = TempDir;
358 saudit.append(audit_log_fname);
359 audit_log.close();
360 audit_log.open(saudit.c_str(), ios::app);
361
362 audit_log << "==================================================\n";
363 audit_log << "Audit log: " << zdate() << ", " << ztime() << "\n";
364 audit_log << "==================================================\n";
365 }
366 }
367
restart()368 void fsq::restart()
369 {
370 set_freq(frequency);
371
372 peak_hits = progdefaults.fsqhits;
373 adjust_for_speed();
374
375 mycall = progdefaults.myCall;
376 if (progdefaults.fsq_lowercase)
377 for (size_t n = 0; n < mycall.length(); n++) mycall[n] = tolower(mycall[n]);
378
379 movavg_size = progdefaults.fsq_movavg;
380 if (movavg_size < 1) movavg_size = progdefaults.fsq_movavg = 1;
381 if (movavg_size > MOVAVGLIMIT) movavg_size = progdefaults.fsq_movavg = MOVAVGLIMIT;
382
383 for (int i = 0; i < NUMBINS; i++) binfilt[i]->setLength(movavg_size);
384
385 printit(speed, bandwidth, symlen, SHIFT_SIZE, peak_hits, basetone);
386
387 }
388
valid_char(int ch)389 bool fsq::valid_char(int ch)
390 {
391 if ( ch == 10 || ch == 163 || ch == 176 ||
392 ch == 177 || ch == 215 || ch == 247 ||
393 (ch > 31 && ch < 128))
394 return true;
395 return false;
396 }
397
398 //=====================================================================
399 // receive processing
400 //=====================================================================
401
fsq_squelch_open()402 bool fsq::fsq_squelch_open()
403 {
404 return ch_sqlch_open || metric >= progStatus.sldrSquelchValue;
405 }
406
407 static string triggers = " !#$%&'()*+,-.;<=>?@[\\]^_{|}~";
408 static string allcall = "allcall";
409 static string cqcqcq = "cqcqcq";
410
411 static fre_t call("([[:alnum:]]?[[:alpha:]/]+[[:digit:]]+[[:alnum:]/]+)", REG_EXTENDED);
412
413 // test for valid callsign
414 // returns:
415 // 0 - not a callsign
416 // 1 - mycall
417 // 2 - allcall
418 // 4 - cqcqcq
419 // 8 - any other valid call
420
valid_callsign(std::string s)421 int fsq::valid_callsign(std::string s)
422 {
423 if (s.length() < 3) return 0;
424 if (s.length() > 20) return 0;
425
426 if (s == allcall) return 2;
427 if (s == cqcqcq) return 4;
428 if (s == mycall) return 1;
429 if (s.find("Heard") != string::npos) return 0;
430
431 static char sz[21];
432 memset(sz, 0, 21);
433 strcpy(sz, s.c_str());
434 bool matches = call.match(sz);
435 return (matches ? 8 : 0);
436 }
437
parse_rx_text()438 void fsq::parse_rx_text()
439 {
440 char ztbuf[20];
441 struct timeval tv;
442 gettimeofday(&tv, NULL);
443 struct tm tm;
444 time_t t_temp;
445 t_temp=(time_t)tv.tv_sec;
446 gmtime_r(&t_temp, &tm);
447 strftime(ztbuf, sizeof(ztbuf), "%Y%m%d,%H%M%S", &tm);
448
449 toprint.clear();
450
451 if (rx_text.empty()) return;
452 if (rx_text.length() > 65536) {
453 rx_text.clear();
454 return;
455 }
456
457 state = TEXT;
458 size_t p = rx_text.find(':');
459 if (p == 0) {
460 rx_text.erase(0,1);
461 return;
462 }
463 if (p == std::string::npos ||
464 rx_text.length() < p + 2) {
465 return;
466 }
467
468 std::string rxcrc = rx_text.substr(p+1,2);
469
470 station_calling.clear();
471
472 int max = p+1;
473 if (max > 20) max = 20;
474 std::string substr;
475 for (int i = 1; i < max; i++) {
476 if (rx_text[p-i] <= ' ' || rx_text[p-i] > 'z') {
477 rx_text.erase(0, p+1);
478 return;
479 }
480 substr = rx_text.substr(p-i, i);
481 if ((crc.sval(substr) == rxcrc) && valid_callsign(substr)) {
482 station_calling = substr;
483 break;
484 }
485 }
486
487 if (station_calling == mycall) { // do not display any of own rx stream
488 LOG_ERROR("Station calling is mycall: %s", station_calling.c_str());
489 rx_text.erase(0, p+3);
490 return;
491 }
492 if (!station_calling.empty()) {
493 REQ(add_to_heard_list, station_calling, szestimate);
494 if (enable_heard_log) {
495 std::string sheard = ztbuf;
496 sheard.append(",").append(station_calling);
497 sheard.append(",").append(szestimate).append("\n");
498 heard_log << sheard;
499 heard_log.flush();
500 }
501 }
502
503 // remove station_calling, colon and checksum
504 rx_text.erase(0, p+3);
505
506 // extract all directed callsigns
507 // look for 'allcall', 'cqcqcq' or mycall
508
509 bool all = false;
510 bool directed = false;
511
512 // test next word in string
513 size_t tr_pos = 0;
514 char tr = rx_text[tr_pos];
515 size_t trigger = triggers.find(tr);
516
517 // strip any leading spaces before either text or first directed callsign
518
519 while (rx_text.length() > 1 &&
520 triggers.find(rx_text[0]) != std::string::npos)
521 rx_text.erase(0,1);
522
523 // find first word
524 while ( tr_pos < rx_text.length()
525 && ((trigger = triggers.find(rx_text[tr_pos])) == std::string::npos) ) {
526 tr_pos++;
527 }
528
529 while (trigger != std::string::npos && tr_pos < rx_text.length()) {
530 int word_is = valid_callsign(rx_text.substr(0, tr_pos));
531
532 if (word_is == 0) {
533 rx_text.insert(0," ");
534 break; // not a callsign
535 }
536 if (word_is == 1) {
537 directed = true; // mycall
538 }
539 // test for cqcqcq and allcall
540 else if (word_is != 8)
541 all = true;
542
543 rx_text.erase(0, tr_pos);
544
545 while (rx_text.length() > 1 &&
546 (rx_text[0] == ' ' && rx_text[1] == ' '))
547 rx_text.erase(0,1);
548
549 if (rx_text[0] != ' ') break;
550 rx_text.erase(0, 1);
551
552 tr_pos = 0;
553 tr = rx_text[tr_pos];
554 trigger = triggers.find(tr);
555 while ( tr_pos < rx_text.length() && (trigger == std::string::npos) ) {
556 tr_pos++;
557 tr = rx_text[tr_pos];
558 trigger = triggers.find(tr);
559 }
560 }
561
562 if ( (all == false) && (directed == false)) {
563 rx_text.clear();
564 return;
565 }
566
567 // remove eot if present
568 if (rx_text.length() > 3) rx_text.erase(rx_text.length() - 3);
569
570 toprint.assign(station_calling).append(":");
571
572 // test for trigger
573 tr = rx_text[0];
574 trigger = triggers.find(tr);
575
576 if (trigger == NIT) {
577 tr = ' '; // force to be text line
578 rx_text.insert(0, " ");
579 }
580
581 // if asleep suppress all but the * trigger
582
583 if (btn_SELCAL->value() == 0) {
584 if (tr == '*') parse_star();
585 rx_text.clear();
586 return;
587 }
588
589 // now process own call triggers
590 if (directed) {
591 switch (tr) {
592 case ' ': parse_space(false); break;
593 case '?': parse_qmark(); break;
594 case '*': parse_star(); break;
595 case '+': parse_plus(); break;
596 case '-': break;//parse_minus(); break;
597 case ';': parse_relay(); break;
598 case '!': parse_repeat(); break;
599 case '~': parse_delayed_repeat(); break;
600 case '#': parse_pound(); break;
601 case '$': parse_dollar(); break;
602 case '@': parse_at(); break;
603 case '&': parse_amp(); break;
604 case '^': parse_carat(); break;
605 case '%': parse_pcnt(); break;
606 case '|': parse_vline(); break;
607 case '>': parse_greater(); break;
608 case '<': parse_less(); break;
609 case '[': parse_relayed(); break;
610 }
611 }
612
613 // if allcall; only respond to the ' ', '*', '#', '%', and '[' triggers
614 else {
615 switch (tr) {
616 case ' ': parse_space(true); break;
617 case '*': parse_star(); break;
618 case '#': parse_pound(); break;
619 case '%': parse_pcnt(); break;
620 case '[': parse_relayed(); break;
621 }
622 }
623
624 rx_text.clear();
625 }
626
parse_space(bool all)627 void fsq::parse_space(bool all)
628 {
629 display_fsq_rx_text(toprint.append(rx_text).append("\n"), FTextBase::FSQ_DIR);
630 }
631
parse_qmark(std::string relay)632 void fsq::parse_qmark(std::string relay)
633 {
634 std::string response = station_calling;
635 if (!relay.empty()) response.append(";").append(relay);
636 response.append(" snr=").append(szestimate);
637 reply(response);
638 display_fsq_rx_text(toprint.append(rx_text).append("\n"), FTextBase::FSQ_DIR);
639 }
640
parse_dollar(std::string relay)641 void fsq::parse_dollar(std::string relay)
642 {
643 std::string response = station_calling;
644 if (!relay.empty()) response.append(";").append(relay);
645 response.append(" Heard:\n");
646 response.append(heard_list());
647 reply(response);
648 display_fsq_rx_text(toprint.append(rx_text).append("\n"), FTextBase::FSQ_DIR);
649 }
650
parse_star()651 void fsq::parse_star()
652 {
653 REQ(enableSELCAL);
654 reply(std::string(station_calling).append(" ack"));
655 }
656
657 // immediate repeat of msg
parse_repeat()658 void fsq::parse_repeat()
659 {
660 std::string response;
661 rx_text.erase(0, 1);
662 if (rx_text[0] != ' ') rx_text.insert(0, " ");
663 response.assign(" ");
664 response.append(rx_text);
665 reply(response);
666 display_fsq_rx_text(toprint.append(rx_text).append("\n"), FTextBase::FSQ_DIR);
667 }
668
669 // delayed repeat of msg
parse_delayed_repeat()670 void fsq::parse_delayed_repeat()
671 {
672 std::string response;
673 rx_text.erase(0, 1);
674 if (rx_text[0] != ' ') rx_text.insert(0, " ");
675 response.assign(" ");
676 response.append(rx_text);
677 delayed_reply(response, 15);
678 display_fsq_rx_text(toprint.append(rx_text).append("\n"), FTextBase::FSQ_DIR);
679 }
680
681 // extended relay of msg
682 // k2a sees : k1a:; k3a hi
683 // k2a sends: k2a:22k3a[k1a] hi
parse_relay()684 void fsq::parse_relay()
685 {
686 std::string send_txt = rx_text;
687 send_txt.erase(0,1); // remove ';'
688 if (send_txt.empty()) return;
689 while (send_txt[0] == ' ' && !send_txt.empty())
690 send_txt.erase(0,1); // remove leading spaces
691 // find trigger
692 size_t p = 0;
693 while ((triggers.find(send_txt[p]) == NIT) && p < send_txt.length()) p++;
694 std::string response = string("[").append(station_calling).append("]");
695 send_txt.insert(p, response);
696 if ((p = send_txt.find('^')) != NIT) send_txt.insert(p, "^");
697 display_fsq_rx_text(toprint.append(rx_text).append("\n"), FTextBase::FSQ_DIR);
698 reply(send_txt);
699 }
700
701 // k3a sees : k2a: [k1a]@
702 // or : k2a:[k1a]@
parse_relayed()703 void fsq::parse_relayed()
704 {
705 std::string relayed = "";
706
707 size_t p1 = rx_text.find('[');
708 if (p1 == NIT) {
709 LOG_ERROR("%s", "missing open bracket");
710 return;
711 }
712 rx_text.erase(0,p1 + 1);
713 if (rx_text.empty()) return;
714
715 size_t p2 = rx_text.find(']');
716 if (p2 == NIT) {
717 LOG_ERROR("%s", "missing end bracket");
718 return;
719 }
720 relayed = rx_text.substr(0, p2);
721 rx_text.erase(0, p2 + 1);
722 if (rx_text.empty()) return;
723
724 if (triggers.find(rx_text[0]) == NIT) {
725 LOG_ERROR("%s", "invalid relay trigger");
726 return;
727 }
728 // execute trigger
729 switch (rx_text[0]) {
730 case ' ' : {
731 std::string response = station_calling;
732 response.append(";").append(relayed).append(rx_text);
733 display_fsq_rx_text(toprint.append(response).append("\n"), FTextBase::FSQ_DIR);
734 } break;
735 case '$' : parse_dollar(relayed); break;
736 case '&' : parse_amp(relayed); break;
737 case '?' : parse_qmark(relayed); break;
738 case '@' : parse_at(relayed); break;
739 case '^' : parse_carat(relayed); break;
740 case '|' : parse_vline(relayed); break;
741 case '#' : parse_pound(relayed); break;
742 case '<' : parse_less(relayed); break;
743 case '>' : parse_greater(relayed); break;
744 default : break;
745 }
746 }
747
748 // rx_text[0] will be '#'
749
parse_pound(std::string relay)750 void fsq::parse_pound(std::string relay)
751 {
752 size_t p1 = NIT, p2 = NIT;
753 std::string fname = "";
754 p1 = rx_text.find('[');
755 if (p1 != NIT) {
756 p2 = rx_text.find(']', p1);
757 if (p2 != NIT) {
758 fname = rx_text.substr(p1 + 1, p2 - p1 - 1);
759 fname = fl_filename_name(fname.c_str());
760 } else p2 = 0;
761 } else p2 = 0;
762 if (fname.empty()) {
763 if (!relay.empty()) fname = relay;
764 else fname = station_calling;
765 fname.append(".txt");
766 }
767 if (fname.find(".txt") == std::string::npos) fname.append(".txt");
768 if (rx_text[rx_text.length() -1] != '\n') rx_text.append("\n");
769
770 size_t p3 = NIT;
771 while( (p3 = fname.find("/")) != NIT) fname[p3] = '.';
772 while( (p3 = fname.find("\\")) != NIT) fname[p3] = '.';
773
774 std::ofstream rxfile;
775 fname.insert(0, TempDir);
776 if (progdefaults.always_append) {
777 rxfile.open(fname.c_str(), ios::app);
778 } else {
779 rxfile.open(fname.c_str(), ios::out);
780 }
781 if (!rxfile) return;
782 if (progdefaults.add_fsq_msg_dt) {
783 rxfile << "Received: " << zdate() << ", " << ztime() << "\n";
784 rxfile << rx_text.substr(p2+1) << "\n";
785 } else
786 rxfile << rx_text.substr(p2+1);
787
788 rxfile.close();
789
790 display_fsq_rx_text(toprint.append(rx_text).append("\n"), FTextBase::FSQ_DIR);
791
792 std::string response = station_calling;
793 if (!relay.empty()) response.append(";").append(relay);
794 response.append(" ack");
795 reply(response);
796 }
797
parse_plus(std::string relay)798 void fsq::parse_plus(std::string relay)
799 {
800 size_t p1 = NIT, p2 = NIT;
801 std::string fname = "";
802 p1 = rx_text.find('[');
803 if (p1 != NIT) {
804 p2 = rx_text.find(']', p1);
805 if (p2 != NIT) {
806 fname = rx_text.substr(p1 + 1, p2 - p1 - 1);
807 fname = fl_filename_name(fname.c_str());
808 } else p2 = 0;
809 }
810 if (fname.empty()) {
811 if (!relay.empty()) fname = relay;
812 else fname = station_calling;
813 fname.append(".txt");
814 }
815
816 std::ifstream txfile;
817 bool append = (fname == station_calling);
818 std::string pathname = TempDir;
819 if (append) {
820 pathname.append(fname).append(".txt");
821 txfile.open(pathname.c_str());
822 } else {
823 pathname.append(fname);
824 txfile.open(pathname.c_str());
825 }
826 if (!txfile) {
827 reply(std::string(station_calling).append(" not found"));
828 return;
829 }
830 stringstream outtext(station_calling);
831 outtext << " [" << fname << "]\n";
832 char ch = txfile.get();
833 while (!txfile.eof()) {
834 outtext << ch;
835 ch = txfile.get();
836 }
837 txfile.close();
838
839 std::string response = station_calling;
840 if (!relay.empty()) response.append(";").append(relay);
841 response.append(outtext.str());
842 reply(response);
843 }
844
parse_minus()845 void fsq::parse_minus()
846 {
847 display_fsq_rx_text(toprint.append(rx_text).append(" nia\n"), FTextBase::FSQ_DIR);
848 reply(std::string(station_calling).append(" not supported"));
849 }
850
parse_at(std::string relay)851 void fsq::parse_at(std::string relay)
852 {
853 std::string response = station_calling;
854 if (!relay.empty()) response.append(";").append(relay);
855 response.append(" ").append(progdefaults.myQth);
856 reply(response);
857 display_fsq_rx_text(toprint.append(rx_text).append("\n"), FTextBase::FSQ_DIR);
858 }
859
parse_amp(std::string relay)860 void fsq::parse_amp(std::string relay)
861 {
862 std::string response = station_calling;
863 if (!relay.empty()) response.append(";").append(relay);
864 response.append(" ").append(progdefaults.fsqQTCtext);
865 reply(response);
866 display_fsq_rx_text(toprint.append(rx_text).append("\n"), FTextBase::FSQ_DIR);
867 }
868
parse_carat(std::string relay)869 void fsq::parse_carat(std::string relay)
870 {
871 std::string response = station_calling;
872 if (!relay.empty()) response.append(";").append(relay);
873 response.append(" fldigi ").append(PACKAGE_VERSION);
874 reply(response);
875 display_fsq_rx_text(toprint.append(rx_text).append("\n"), FTextBase::FSQ_DIR);
876 }
877
parse_pcnt()878 void fsq::parse_pcnt()
879 {
880 switch (rx_text[2]) {
881 case 'L' :
882 image_mode = 0; picW = 320; picH = 240;
883 break;
884 case 'S' :
885 image_mode = 1; picW = 160; picH = 120;
886 break;
887 case 'F' :
888 image_mode = 2; picW = 640; picH = 480;
889 break;
890 case 'V' :
891 image_mode = 3; picW = 640; picH = 480;
892 break;
893 case 'P' :
894 image_mode = 4; picW = 240; picH = 300;
895 break;
896 case 'p' :
897 image_mode = 5; picW = 240; picH = 300;
898 break;
899 case 'M' :
900 image_mode = 6; picW = 120; picH = 150;
901 break;
902 case 'm' :
903 image_mode = 7; picW = 120; picH = 150;
904 break;
905
906 }
907 REQ( fsq_showRxViewer, picW, picH, rx_text[2] );
908
909 image_counter = 0;
910
911 picf = 0;
912 row = col = rgb = 0;
913 state = IMAGE;
914
915 display_fsq_rx_text(toprint.append(rx_text).append("\n"), FTextBase::FSQ_DIR);
916 }
917
parse_vline(std::string relay)918 void fsq::parse_vline(std::string relay)
919 {
920 display_fsq_rx_text(toprint.append(rx_text).append("\n"), FTextBase::FSQ_DIR);
921
922 std::string alert = "Message received from ";
923 if (relay.empty()) alert.append(station_calling);
924 else alert.append(relay);
925 alert.append("\n").append("at: ").append(zshowtime()).append("\n");
926 alert.append(rx_text.substr(1));
927
928 REQ(post_alert, alert, progdefaults.fsq_notify_time_out, relay);
929 }
930
send_ack(std::string relay)931 void fsq::send_ack(std::string relay)
932 {
933 std::string response = station_calling;
934 if (!relay.empty()) response.append(";").append(relay);
935 response.append(" ack");
936 reply(response);
937 }
938
939
parse_greater(std::string relay)940 void fsq::parse_greater(std::string relay)
941 {
942 std::string response;
943 response.assign(station_calling);
944 if (!relay.empty()) response.append(";").append(relay);
945
946 double spd = progdefaults.fsqbaud;
947 if (spd == 1.5 ) {
948 spd = 2.0;
949 response.append(" 2.0 baud");
950 } else if (spd == 2.0) {
951 spd = 3.0;
952 response.append(" 3.0 baud");
953 } else if (spd == 3.0) {
954 spd = 4.5;
955 response.append(" 4.5 baud");
956 } else if (spd == 4.5) {
957 spd = 6.0;
958 response.append(" 6.0 baud");
959 } else if (spd == 6.0) {
960 response.append(" 6.0 baud");
961 }
962 progdefaults.fsqbaud = spd;
963 adjust_for_speed();
964 reply(response);
965 display_fsq_rx_text(toprint.append(rx_text).append("\n"), FTextBase::FSQ_DIR);
966 }
967
parse_less(std::string relay)968 void fsq::parse_less(std::string relay)
969 {
970 std::string response;
971 response.assign(station_calling);
972 if (!relay.empty()) response.append(";").append(relay);
973
974 double spd = progdefaults.fsqbaud;
975 if (spd == 2.0) {
976 spd = 1.5;
977 response.append(" 1.5 baud");
978 } else if (spd == 3.0) {
979 spd = 2.0;
980 response.append(" 2.0 baud");
981 } else if (spd == 4.5) {
982 spd = 3.0;
983 response.append(" 3.0 baud");
984 } else if (spd == 6.0) {
985 spd = 4.5;
986 response.append(" 4.5 baud");
987 }
988 progdefaults.fsqbaud = spd;
989 adjust_for_speed();
990 reply(response);
991 display_fsq_rx_text(toprint.append(rx_text).append("\n"), FTextBase::FSQ_DIR);
992 }
993
lf_check(int ch)994 void fsq::lf_check(int ch)
995 {
996 static char lfpair[3] = "01";
997 static char bstrng[4] = "012";
998
999 lfpair[0] = lfpair[1];
1000 lfpair[1] = 0xFF & ch;
1001
1002 bstrng[0] = bstrng[1];
1003 bstrng[1] = bstrng[2];
1004 bstrng[2] = 0xFF & ch;
1005
1006 b_bot = b_eol = b_eot = false;
1007 if (bstrng[0] == FSQEOT[0] // find SP SP BS SP
1008 && bstrng[1] == FSQEOT[1]
1009 && bstrng[2] == FSQEOT[2]
1010 ) {
1011 b_eot = true;
1012 } else if (lfpair[0] == FSQBOL[0] && lfpair[1] == FSQBOL[1]) {
1013 b_bot = true;
1014 } else if (lfpair[0] == FSQEOL[0] && lfpair[1] == FSQEOL[1]) {
1015 b_eol = true;
1016 }
1017 }
1018
process_symbol(int sym)1019 void fsq::process_symbol(int sym)
1020 {
1021 int nibble = 0;
1022 int curr_ch = -1;
1023
1024 symbol = sym;
1025
1026 nibble = symbol - prev_symbol;
1027 if (nibble < -99 || nibble > 99) {
1028 prev_symbol = symbol;
1029 return;
1030 }
1031 nibble = nibbles[nibble + 99];
1032
1033 // -1 is our idle symbol, indicating we already have our symbol
1034 if (nibble >= 0) { // process nibble
1035 curr_nibble = nibble;
1036
1037 // single-nibble characters
1038 if ((prev_nibble < 29) & (curr_nibble < 29)) {
1039 curr_ch = wsq_varidecode[prev_nibble];
1040
1041 // double-nibble characters
1042 } else if ( (prev_nibble < 29) &&
1043 (curr_nibble > 28) &&
1044 (curr_nibble < 32)) {
1045 curr_ch = wsq_varidecode[prev_nibble * 32 + curr_nibble];
1046 }
1047 if (curr_ch > 0) {
1048 if (enable_audit_log) {
1049 audit_log << fsq_ascii[curr_ch];// & 0xFF];
1050 if (curr_ch == '\n') audit_log << '\n';
1051 audit_log.flush();
1052 }
1053 lf_check(curr_ch);
1054
1055 if (b_bot) {
1056 ch_sqlch_open = true;
1057 rx_text.clear();
1058 }
1059
1060 if (fsq_squelch_open()) {
1061 write_rx_mon_char(curr_ch);
1062 if (b_bot)
1063 {
1064 char ztbuf[20];
1065 struct timeval tv;
1066 gettimeofday(&tv,NULL);
1067 struct tm tm;
1068 time_t t_temp;
1069 t_temp=(time_t)tv.tv_sec;
1070 gmtime_r(&t_temp, &tm);
1071 strftime(ztbuf,sizeof(ztbuf),"%m/%d %H:%M:%S ",&tm);
1072 display_fsq_mon_text( ztbuf, FTextBase::CTRL);
1073 display_fsq_mon_text( fsq_bot, FTextBase::CTRL);
1074 }
1075 if (b_eol) {
1076 display_fsq_mon_text( fsq_eol, FTextBase::CTRL);
1077 noisefilt->reset();
1078 noisefilt->run(1);
1079 sigfilt->reset();
1080 sigfilt->run(1);
1081 snprintf(szestimate, sizeof(szestimate), "%.0f db", s2n );
1082 }
1083 if (b_eot) {
1084 snprintf(szestimate, sizeof(szestimate), "%.0f db", s2n );
1085 noisefilt->reset();
1086 noisefilt->run(1);
1087 sigfilt->reset();
1088 sigfilt->run(1);
1089 display_fsq_mon_text( fsq_eot, FTextBase::CTRL);
1090 }
1091 }
1092
1093 if ( valid_char(curr_ch) || b_eol || b_eot ) {
1094 if (rx_text.length() > 32768) rx_text.clear();
1095 if ( fsq_squelch_open() || !progStatus.sqlonoff ) {
1096 rx_text += curr_ch;
1097 if (b_eot) {
1098 parse_rx_text();
1099 if (state == TEXT)
1100 ch_sqlch_open = false;
1101 }
1102 }
1103 }
1104 if (fsq_squelch_open() && (b_eot || b_eol)) {
1105 ch_sqlch_open = false;
1106 }
1107 }
1108 prev_nibble = curr_nibble;
1109 }
1110
1111 prev_symbol = symbol;
1112 }
1113
1114 // find the maximum bin
1115 // 908 Hz and 1351 Hz respectively for original center frequency of 1145 Hz
1116 // 1280 to 1720 for a 1500 Hz center frequency
1117
process_tones()1118 void fsq::process_tones()
1119 {
1120 max = 0;
1121 peak = NUMBINS / 2;
1122
1123 // examine FFT bin contents over bandwidth +/- ~ 50 Hz
1124 int firstbin = frequency * FSQ_SYMLEN / samplerate - NUMBINS / 2;
1125
1126 double sigval = 0;
1127
1128 double min = 3.0e8;
1129 int minbin = NUMBINS / 2;
1130
1131 for (int i = 0; i < NUMBINS; ++i) {
1132 val = norm(fft_data[i + firstbin]);
1133 // looking for maximum signal
1134 tones[i] = binfilt[i]->run(val);
1135 if (tones[i] > max) {
1136 max = tones[i];
1137 peak = i;
1138 }
1139 // looking for minimum signal in a 3 bin sequence
1140 if (tones[i] < min) {
1141 min = tones[i];
1142 minbin = i;
1143 }
1144 }
1145
1146 sigval = tones[(peak-1) < 0 ? (NUMBINS - 1) : (peak - 1)] +
1147 tones[peak] +
1148 tones[(peak+1) == NUMBINS ? 0 : (peak + 1)];
1149
1150 min = tones[(minbin-1) < 0 ? (NUMBINS - 1) : (minbin - 1)] +
1151 tones[minbin] +
1152 tones[(minbin+1) == NUMBINS ? 0 : (minbin + 1)];
1153
1154 if (min == 0) min = 1e-10;
1155
1156 s2n = 10 * log10( snfilt->run(sigval/min)) - 34.0 + movavg_size / 4;
1157
1158 if (s2n <= 0) metric = 2 * (25 + s2n);
1159 if (s2n > 0) metric = 50 * ( 1 + s2n / 45);
1160 metric = clamp(metric, 0, 100);
1161
1162 display_metric(metric);
1163
1164 if (metric < progStatus.sldrSquelchValue && ch_sqlch_open)
1165 ch_sqlch_open = false;
1166
1167 // requires consecutive hits
1168 if (peak == prev_peak) {
1169 peak_counter++;
1170 } else {
1171 peak_counter = 0;
1172 }
1173
1174 if ((peak_counter >= peak_hits) &&
1175 (peak != last_peak) &&
1176 (fsq_squelch_open() || !progStatus.sqlonoff)) {
1177 process_symbol(peak);
1178 peak_counter = 0;
1179 last_peak = peak;
1180 }
1181
1182 prev_peak = peak;
1183 }
1184
recvpic(double smpl)1185 void fsq::recvpic(double smpl)
1186 {
1187 phase -= phidiff;
1188 if (phase < 0) phase += 2.0 * M_PI;
1189
1190 cmplx z = smpl * cmplx( cos(phase), sin(phase ) );
1191 picfilter->run( z, currz);
1192 pixel += arg(conj(prevz) * currz);// * samplerate / TWOPI;
1193 amplitude += norm(currz);
1194 prevz = currz;
1195
1196 if (image_counter <= -RXspp) {
1197 pixel = 0;
1198 amplitude = 0;
1199 image_counter++;
1200 return;
1201 }
1202
1203 if ((image_counter++ % RXspp) == 0) {
1204
1205 amplitude /= RXspp;
1206 pixel /= RXspp;
1207 pixel *= (samplerate / TWOPI);
1208 byte = pixel / 1.5 + 128;
1209 byte = (int)CLAMP( byte, 0.0, 255.0);
1210
1211 // FSQCAL sends blue-green-red
1212 static int RGB[] = {2, 1, 0};
1213
1214 if (image_mode == 2 || image_mode == 5 || image_mode == 7) { // grey scale
1215 pixelnbr = 3 * (col + row * picW);
1216 REQ(fsq_updateRxPic, byte, pixelnbr);
1217 REQ(fsq_updateRxPic, byte, pixelnbr + 1);
1218 REQ(fsq_updateRxPic, byte, pixelnbr + 2);
1219 if (++ col == picW) {
1220 col = 0;
1221 row++;
1222 if (row >= picH) {
1223 state = TEXT;
1224 REQ(fsq_enableshift);
1225 metric = 0;
1226 }
1227 }
1228 } else {
1229 pixelnbr = RGB[rgb] + 3 * (col + row * picW);
1230 REQ(fsq_updateRxPic, byte, pixelnbr);
1231 if (++col == picW) {
1232 col = 0;
1233 if (++rgb == 3) {
1234 rgb = 0;
1235 row ++;
1236 }
1237 }
1238 if (row >= picH) {
1239 state = TEXT;
1240 REQ(fsq_enableshift);
1241 metric = 0;
1242 }
1243 }
1244
1245 pixel = 0;
1246 amplitude = 0;
1247 }
1248 }
1249
rx_process(const double * buf,int len)1250 int fsq::rx_process(const double *buf, int len)
1251 {
1252 if (peak_hits != progdefaults.fsqhits) restart();
1253 if (movavg_size != progdefaults.fsq_movavg) restart();
1254 if (speed != progdefaults.fsqbaud) restart();
1255
1256 if (enable_heard_log != progdefaults.fsq_enable_heard_log ||
1257 enable_audit_log != progdefaults.fsq_enable_audit_log)
1258 toggle_logs();
1259
1260 if (sounder_interval != progdefaults.fsq_sounder) {
1261 sounder_interval = progdefaults.fsq_sounder;
1262 start_sounder(sounder_interval);
1263 }
1264
1265 if (bkptr < 0) bkptr = 0;
1266 if (bkptr >= SHIFT_SIZE) bkptr = 0;
1267
1268 if (len > 512) {
1269 LOG_ERROR("fsq rx stream overrun %d", len);
1270 }
1271
1272 if (progStatus.fsq_rx_abort) {
1273 state = TEXT;
1274 progStatus.fsq_rx_abort = false;
1275 REQ(fsq_clear_rximage);
1276 }
1277
1278 while (len) {
1279 if (state == IMAGE) {
1280 recvpic(*buf);
1281 len--;
1282 buf++;
1283 } else {
1284 rx_stream[BLOCK_SIZE + bkptr] = *buf;
1285 len--;
1286 buf++;
1287 bkptr++;
1288
1289 if (bkptr == SHIFT_SIZE) {
1290 bkptr = 0;
1291 memmove(rx_stream, // to
1292 &rx_stream[SHIFT_SIZE], // from
1293 BLOCK_SIZE*sizeof(*rx_stream)); // # bytes
1294 // fft_data gets overwritten each time with a fixed number of
1295 // elements. Do we need to zero it out?
1296 //memset(fft_data, 0, sizeof(fft_data));
1297 for (int i = 0; i < BLOCK_SIZE; i++) {
1298 double d = rx_stream[i] * a_blackman[i];
1299 fft_data[i] = cmplx(d, d);
1300 }
1301 fft->ComplexFFT(fft_data);
1302 process_tones();
1303 }
1304 }
1305 }
1306 return 0;
1307 }
1308
1309 //=====================================================================
1310 // transmit processing
1311 //=====================================================================
1312
1313 // implement the symbol counter using a new thread whose thread loop
1314 // time is equal to a symbol length 4096/12000 = 341 milliseconds
1315 // symbol loop decrements symbol counts and send_symbol increments them
1316 // flush_buffer will then awake for the symbol count to be zero
1317 // have not observed a remaining count > 1 so this might be an over kill!
1318 // typical awake period is 90 msec
1319
flush_buffer()1320 void fsq::flush_buffer()
1321 {
1322 for (int i = 0; i < 64; i++) outbuf[i] = 0;
1323 ModulateXmtr(outbuf, 64);
1324 return;
1325 }
1326
1327 #include "confdialog.h"
1328
send_tone(int tone)1329 void fsq::send_tone(int tone)
1330 {
1331 double phaseincr;
1332 double freq;
1333
1334 if (speed != progdefaults.fsqbaud) restart();
1335
1336 freq = (tx_basetone + tone * spacing) * samplerate / FSQ_SYMLEN;
1337 if (test_signal_window && test_signal_window->visible() && btnOffsetOn->value())
1338 freq += ctrl_freq_offset->value();
1339
1340 phaseincr = 2.0 * M_PI * freq / samplerate;
1341 prevtone = tone;
1342
1343 int send_symlen = symlen;
1344 if (fsq_tx_image) send_symlen = 4096; // must use 3 baud symlen for image xfrs
1345
1346 for (int i = 0; i < send_symlen; i++) {
1347 outbuf[i] = cos(txphase);
1348 txphase -= phaseincr;
1349 if (txphase < 0) txphase += TWOPI;
1350 }
1351 ModulateXmtr(outbuf, send_symlen);
1352 }
1353
send_symbol(int sym)1354 void fsq::send_symbol(int sym)
1355 {
1356
1357 tone = (prevtone + sym + 1) % 33;
1358 send_tone(tone);
1359 }
1360
send_idle()1361 void fsq::send_idle()
1362 {
1363 send_symbol(28);
1364 send_symbol(30);
1365 }
1366
1367 static bool send_eot = false;
1368
send_char(int ch)1369 void fsq::send_char(int ch)
1370 {
1371 if (!ch) return send_idle();
1372
1373 int sym1 = fsq_varicode[ch][0];
1374 int sym2 = fsq_varicode[ch][1];
1375
1376 send_symbol(sym1);
1377 if (sym2 > 28)
1378 send_symbol(sym2);
1379
1380 if (valid_char(ch) && !(send_bot || send_eot))
1381 put_echo_char(ch);
1382
1383 write_mon_tx_char(ch);
1384 }
1385
send_image()1386 void fsq::send_image()
1387 {
1388 int W = 640, H = 480; // grey scale transfer (FAX)
1389 bool color = true;
1390 float freq, phaseincr;
1391 float radians = 2.0 * M_PI / samplerate;
1392
1393 if (!fsqpicTxWin || !fsqpicTxWin->visible()) {
1394 return;
1395 }
1396
1397 switch (selfsqpicSize->value()) {
1398 case 0 : W = 160; H = 120; break;
1399 case 1 : W = 320; H = 240; break;
1400 case 2 : W = 640; H = 480; color = false; break;
1401 case 3 : W = 640; H = 480; break;
1402 case 4 : W = 240; H = 300; break;
1403 case 5 : W = 240; H = 300; color = false; break;
1404 case 6 : W = 120; H = 150; break;
1405 case 7 : W = 120; H = 150; color = false; break;
1406 }
1407
1408 REQ(fsq_clear_tximage);
1409
1410 stop_deadman();
1411
1412 freq = frequency - 200;
1413 #define PHASE_CORR 200
1414 phaseincr = radians * freq;
1415 for (int n = 0; n < PHASE_CORR; n++) {
1416 outbuf[n] = cos(txphase);
1417 txphase -= phaseincr;
1418 if (txphase < 0) txphase += TWOPI;
1419 }
1420 ModulateXmtr(outbuf, 10);
1421
1422 if (color == false) { // grey scale image
1423 for (int row = 0; row < H; row++) {
1424 memset(outbuf, 0, 10 * sizeof(*outbuf));
1425 for (int col = 0; col < W; col++) {
1426 if (stopflag) return;
1427 tx_pixelnbr = col + row * W;
1428 tx_pixel = 0.3 * fsqpic_TxGetPixel(tx_pixelnbr, 0) + // red
1429 0.6 * fsqpic_TxGetPixel(tx_pixelnbr, 1) + // green
1430 0.1 * fsqpic_TxGetPixel(tx_pixelnbr, 2); // blue
1431 REQ(fsq_updateTxPic, tx_pixel, tx_pixelnbr*3 + 0);
1432 REQ(fsq_updateTxPic, tx_pixel, tx_pixelnbr*3 + 1);
1433 REQ(fsq_updateTxPic, tx_pixel, tx_pixelnbr*3 + 2);
1434 freq = frequency - 200 + tx_pixel * 1.5;
1435 phaseincr = radians * freq;
1436 for (int n = 0; n < 10; n++) {
1437 outbuf[n] = cos(txphase);
1438 txphase -= phaseincr;
1439 if (txphase < 0) txphase += TWOPI;
1440 }
1441 ModulateXmtr(outbuf, 10);
1442 Fl::awake();
1443 }
1444 }
1445 } else {
1446 for (int row = 0; row < H; row++) {
1447 for (int color = 2; color >= 0; color--) {
1448 memset(outbuf, 0, 10 * sizeof(*outbuf));
1449 for (int col = 0; col < W; col++) {
1450 if (stopflag) return;
1451 tx_pixelnbr = col + row * W;
1452 tx_pixel = fsqpic_TxGetPixel(tx_pixelnbr, color);
1453 REQ(fsq_updateTxPic, tx_pixel, tx_pixelnbr*3 + color);
1454 freq = frequency - 200 + tx_pixel * 1.5;
1455 phaseincr = radians * freq;
1456 for (int n = 0; n < 10; n++) {
1457 outbuf[n] = cos(txphase);
1458 txphase -= phaseincr;
1459 if (txphase < 0) txphase += TWOPI;
1460 }
1461 ModulateXmtr(outbuf, 10);
1462 }
1463 Fl::awake();
1464 }
1465 }
1466 }
1467 start_deadman();
1468 }
1469
send_string(std::string s)1470 void fsq::send_string(std::string s)
1471 {
1472 for (size_t n = 0; n < s.length(); n++)
1473 send_char(s[n]);
1474 if ((s == FSQEOT || s == FSQEOL) && fsq_tx_image) send_image();
1475 }
1476
fsq_send_image(std::string s)1477 void fsq::fsq_send_image(std::string s) {
1478 fsq_tx_image = true;
1479 fsq_string = std::string("IMAGE:").append(s);
1480 write_fsq_que(fsq_string);
1481 fsq_xmt(s);
1482 }
1483
tx_process()1484 int fsq::tx_process()
1485 {
1486 modem::tx_process();
1487
1488 if (send_bot) {
1489 std::string send;
1490 send.assign(" ").append(FSQBOL).append(mycall).append(":");
1491 if (progdefaults.fsq_directed)
1492 send.append(crc.sval(mycall));
1493 send_string(send);
1494 send_bot = false;
1495 }
1496 int c = get_tx_char();
1497
1498 if (c == GET_TX_CHAR_ETX || c == -1) { // end of text or empty tx buffer
1499 send_eot = true;
1500 if (progdefaults.fsq_directed)
1501 send_string(std::string(FSQEOT));
1502 else
1503 send_string(std::string(FSQEOL));
1504 send_eot = false;
1505 put_echo_char('\n');
1506 if (c == -1) REQ(&FTextTX::clear, TransmitText);
1507 flush_buffer();
1508 stopflag = false;
1509 fsq_tx_image = false;
1510 return -1;
1511 }
1512 if ( stopflag ) { // aborts transmission
1513 static std::string aborted = " !ABORTED!\n";
1514 for (size_t n = 0; n < aborted.length(); n++)
1515 put_echo_char(aborted[n]);
1516 fsq_tx_image = false;
1517 stopflag = false;
1518 clear_xmt_arrays();
1519 return -1;
1520 }
1521 send_char(c);
1522 return 0;
1523 }
1524
1525 //==============================================================================
1526 // delayed transmit
1527 //==============================================================================
1528
1529 static pthread_mutex_t fsq_tx_mutex = PTHREAD_MUTEX_INITIALIZER;
1530 static float xmt_repeat_try = 6.0;
1531 static string tx_text_queue = "";
1532
1533 static vector<string> commands;
1534 #define NUMCOMMANDS 10
1535 static size_t next = 0;
1536
clear_xmt_arrays()1537 void clear_xmt_arrays()
1538 {
1539 commands.erase(commands.begin(), commands.begin());
1540 tx_text_queue.clear();
1541 fsq_tx_text->clear();
1542 }
1543
fsq_xmtdelay()1544 double fsq_xmtdelay() // in seconds
1545 {
1546 #define MIN_DELAY 50
1547 #define MAX_DELAY 500
1548 srand((int)clock());
1549 double scaled = (double)rand()/RAND_MAX;
1550 double delay = ((MAX_DELAY - MIN_DELAY + 1 ) * scaled + MIN_DELAY) / 1000.0;
1551 if (delay < 0.05) delay = 0.05;
1552 if (delay > 0.5) delay = 0.5;
1553 return delay;
1554 }
1555
fsq_repeat_last_command()1556 void fsq_repeat_last_command()
1557 {
1558 using ::next; // disambiguate from std::next
1559 if (commands.empty()) return;
1560 fsq_tx_text->clear();
1561 fsq_tx_text->addstr(sz2utf8(commands[next].c_str()));
1562 next++;
1563 if (next == commands.size()) {
1564 next = 0;
1565 }
1566 }
1567
get_fsq_tx_char(void)1568 int get_fsq_tx_char(void)
1569 {
1570 guard_lock tx_proc_lock(&fsq_tx_mutex);
1571
1572 if (tx_text_queue.empty()) return (GET_TX_CHAR_NODATA);
1573
1574 int c = tx_text_queue[0];
1575 tx_text_queue.erase(0,1);
1576
1577 if (c == GET_TX_CHAR_ETX) {
1578 return c;
1579 }
1580 if (c == -1)
1581 return(GET_TX_CHAR_NODATA);
1582
1583 if (c == '^') {
1584 c = tx_text_queue[0];
1585 tx_text_queue.erase(0,1);
1586
1587 if (c == -1) return(GET_TX_CHAR_NODATA);
1588
1589 switch (c) {
1590 case 'r':
1591 return(GET_TX_CHAR_ETX);
1592 break;
1593 case 'R':
1594 return(GET_TX_CHAR_ETX);
1595 break;
1596 default: ;
1597 }
1598 }
1599 start_deadman();
1600 return(c);
1601 }
1602
try_transmit(void *)1603 void try_transmit(void *)
1604 {
1605 if (active_modem != fsq_modem) return;
1606
1607 if (!active_modem->fsq_squelch_open() && trx_state == STATE_RX) {
1608 ::next = 0;
1609 fsq_que_clear();
1610 //LOG_WARN("%s", "start_tx()");
1611 start_tx();
1612 return;
1613 }
1614
1615 if (xmt_repeat_try > 0) {
1616 xmt_repeat_try -= 0.5;
1617 static char szwaiting[50];
1618 snprintf(szwaiting, sizeof(szwaiting), "Waiting %4.2f", xmt_repeat_try);
1619 fsq_que_clear();
1620 write_fsq_que(std::string(szwaiting).append("\n").append(fsq_string));
1621 //LOG_WARN("%s", szwaiting);
1622 Fl::repeat_timeout(0.5, try_transmit);
1623 return;
1624 } else {
1625 static const char szsquelch[50] = "Squelch open. Transmit timed out!";
1626 display_fsq_rx_text(string("\n").append(szsquelch).append("\n").c_str(), FTextBase::ALTR);
1627 tx_text_queue.clear();
1628 fsq_que_clear();
1629 if (active_modem->fsq_tx_image) active_modem->fsq_tx_image = false;
1630 //LOG_WARN("%s", szsquelch);
1631 return;
1632 }
1633 return;
1634 }
1635
_fsq_xmt(string s)1636 inline void _fsq_xmt(string s)
1637 {
1638 tx_text_queue.clear();
1639 if (commands.size() > NUMCOMMANDS)
1640 commands.pop_back();
1641
1642 commands.insert(commands.begin(), 1, s);
1643 s.append("^r");
1644 tx_text_queue = s;
1645
1646 xmt_repeat_try = progdefaults.fsq_time_out;
1647 Fl::add_timeout(0.5 + fsq_xmtdelay(), try_transmit);
1648 }
1649
fsq_xmt_mt(void * cs=(void *)0)1650 void fsq_xmt_mt(void *cs = (void *)0)
1651 {
1652 guard_lock tx_proc_lock(&fsq_tx_mutex);
1653
1654 if (active_modem != fsq_modem) return;
1655 if (!cs) return;
1656
1657 string s;
1658 s.assign((char *) cs);
1659 delete (char *) cs;
1660
1661 if(!s.empty()) {
1662 _fsq_xmt(s);
1663 }
1664 }
1665
fsq_xmt(string s)1666 void fsq_xmt(string s)
1667 {
1668 guard_lock tx_proc_lock(&fsq_tx_mutex);
1669
1670 if (active_modem != fsq_modem) return;
1671
1672 if(!s.empty()) {
1673 _fsq_xmt(s);
1674 }
1675 }
1676
fsq_transmit(void * a=(void *)0)1677 void fsq_transmit(void *a = (void *)0)
1678 {
1679 guard_lock tx_proc_lock(&fsq_tx_mutex);
1680
1681 if (active_modem != fsq_modem) return;
1682
1683 if (!tx_text_queue.empty()) {
1684 size_t p = tx_text_queue.find("^r");
1685 tx_text_queue.erase(p);
1686 tx_text_queue += ' ';
1687 int nxt = fsq_tx_text->nextChar();
1688 while (nxt != -1) {
1689 tx_text_queue += nxt;
1690 nxt = fsq_tx_text->nextChar();
1691 }
1692 commands.erase(commands.begin(), commands.begin());
1693 commands.insert(commands.begin(), 1, tx_text_queue);
1694 tx_text_queue.append("^r");
1695 fsq_tx_text->clear();
1696 return;
1697 //LOG_WARN("A: %s", tx_text_queue.c_str());
1698 }
1699
1700 int nxt = fsq_tx_text->nextChar();
1701 while (nxt != -1) {
1702 tx_text_queue += nxt;
1703 nxt = fsq_tx_text->nextChar();
1704 }
1705 if (commands.size() > NUMCOMMANDS)
1706 commands.pop_back();
1707 commands.insert(commands.begin(), 1, tx_text_queue);
1708 tx_text_queue.append("^r");
1709 fsq_tx_text->clear();
1710 //LOG_WARN("B: %s", tx_text_queue.c_str());
1711
1712 xmt_repeat_try = progdefaults.fsq_time_out;
1713 Fl::add_timeout(0.5 + fsq_xmtdelay(), try_transmit);
1714 }
1715
timed_xmt(void *)1716 void timed_xmt(void *)
1717 {
1718 //LOG_WARN("%s", fsq_delayed_string.c_str());
1719 fsq_xmt(fsq_delayed_string);
1720 }
1721
1722 static float secs = 0;
1723
fsq_add_tx_timeout(void * a=0)1724 void fsq_add_tx_timeout(void *a = 0)
1725 {
1726 Fl::add_timeout(secs, timed_xmt);
1727 }
1728
reply(std::string s)1729 void fsq::reply(std::string s)
1730 {
1731 fsq_string = std::string("SEND: ").append(s);
1732 write_fsq_que(fsq_string);
1733 char *cs = (char *)0;
1734 cs = new char[s.size() + 1];
1735 if(!cs) return;
1736 cs[s.size()] = 0;
1737 memcpy(cs, s.c_str(), s.size());
1738 Fl::awake(fsq_xmt_mt, (void *) cs);
1739 }
1740
delayed_reply(std::string s,int delay)1741 void fsq::delayed_reply(std::string s, int delay)
1742 {
1743 fsq_string = std::string("DELAYED SEND: ").append(s);
1744 write_fsq_que(fsq_string);
1745 fsq_delayed_string = s;
1746 secs = delay;
1747 Fl::awake(fsq_add_tx_timeout, 0);
1748 //LOG_WARN("%s : %d", s.c_str(), delay);
1749 }
1750
1751 //==============================================================================
1752 // Heard list aging
1753 //==============================================================================
aging(void * who)1754 void aging(void *who)
1755 {
1756 fsq *me = (fsq *)who;
1757 if (me != active_modem) return;
1758 age_heard_list();
1759 Fl::repeat_timeout(60.0, aging, me);
1760 }
1761
fsq_start_aging(void * who)1762 void fsq_start_aging(void *who)
1763 {
1764 fsq *me = (fsq *)who;
1765 Fl::remove_timeout(aging);
1766 Fl::add_timeout(60.0, aging, me);
1767 }
1768
start_aging()1769 void fsq::start_aging()
1770 {
1771 Fl::awake(fsq_start_aging, this);
1772 }
1773
fsq_stop_aging(void *)1774 void fsq_stop_aging(void *)
1775 {
1776 Fl::remove_timeout(aging);
1777 }
1778
stop_aging()1779 void fsq::stop_aging()
1780 {
1781 Fl::awake(fsq_stop_aging);
1782 }
1783
1784 //==============================================================================
1785 // Sounder support
1786 //==============================================================================
1787 static int sounder_tries = 10;
1788 static double sounder_secs = 60;
1789
sounder(void *)1790 void sounder(void *)
1791 {
1792 if (active_modem != fsq_modem) return;
1793
1794 if (trx_state == STATE_TX) {
1795 Fl::repeat_timeout(active_modem->fsq_xmtdelay(), timed_xmt);
1796 return;
1797 }
1798 if (active_modem->fsq_squelch_open()) {
1799 if (--sounder_tries < 0) {
1800 display_fsq_rx_text("\nSounder timed out!\n", FTextBase::ALTR);
1801 sounder_tries = 10;
1802 Fl::repeat_timeout(sounder_secs, sounder);
1803 return;
1804 }
1805 Fl::repeat_timeout(10, sounder); // retry in 10 seconds
1806 return;
1807 }
1808 sounder_tries = 10;
1809 std::string xmtstr = FSQBOL;
1810 xmtstr.append(active_modem->fsq_mycall()).append(":").append(FSQEOT);
1811 int numsymbols = xmtstr.length();
1812
1813 int xmtsecs = (int)(1.0 * numsymbols * (fsq::symlen / 4096.0) / SR);
1814
1815 if (fsq_tx_text->eot()) {
1816 std::string stime = ztime();
1817 stime.erase(4);
1818 stime.insert(2,":");
1819 std::string sndx = "Sounded @ ";
1820 sndx.append(stime);
1821 display_fsq_rx_text(sndx, FTextBase::ALTR);
1822
1823 fsq_xmt(" ");
1824 }
1825
1826 Fl::repeat_timeout(sounder_secs - xmtsecs, sounder);
1827 }
1828
fsq_start_sounder()1829 void fsq_start_sounder()
1830 {
1831 if (active_modem != fsq_modem) return;
1832 Fl::remove_timeout(sounder);
1833 Fl::add_timeout(sounder_secs, sounder);
1834 }
1835
fsq_stop_sounder()1836 void fsq_stop_sounder()
1837 {
1838 Fl::remove_timeout(sounder);
1839 }
1840
stop_sounder()1841 void fsq::stop_sounder()
1842 {
1843 REQ(fsq_stop_sounder);
1844 }
1845
start_sounder(int interval)1846 void fsq::start_sounder(int interval)
1847 {
1848 if (interval == 0) {
1849 REQ(fsq_stop_sounder);
1850 return;
1851 }
1852 switch (interval) {
1853 case 0: return;
1854 case 1: sounder_secs = 60; break; // 1 minute
1855 case 2: sounder_secs = 600; break; // 10 minutes
1856 case 3: sounder_secs = 1800; break; // 30 minutes
1857 case 4: sounder_secs = 3600; break; // 60 minutes
1858 default: sounder_secs = 600;
1859 }
1860 REQ(fsq_start_sounder);
1861 }
1862
1863 #include "bitmaps.cxx"
1864