1 // ---------------------------------------------------------------------
2 //
3 // navtex.cxx
4 //
5 // Copyright (C) 2011-2016
6 // Remi Chateauneu, F4ECW
7 // Rik van Riel, AB1KW, <riel@surriel.com>
8 //
9 // This file is part of fldigi. Adapted from code contained in JNX
10 // source code distribution.
11 // JNX Copyright (C) Paul Lutus
12 // http://www.arachnoid.com/JNX/index.html
13 //
14 // fldigi is free software; you can redistribute it and/or modify
15 // it under the terms of the GNU General Public License as published by
16 // the Free Software Foundation; either version 3 of the License, or
17 // (at your option) any later version.
18 //
19 // fldigi is distributed in the hope that it will be useful,
20 // but WITHOUT ANY WARRANTY; without even the implied warranty of
21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 // GNU General Public License for more details.
23 //
24 // You should have received a copy of the GNU General Public License
25 // along with this program. If not, see <http://www.gnu.org/licenses/>.
26 // ---------------------------------------------------------------------
27
28 // ---------------------------------------------------------------------
29 // Sync using multicorrelator, instead of null crossings
30 // Rik van Riel, AB1KW, <riel@surriel.com>
31 //
32 // Null crossings are somewhat noisy, and the code to keep the navtex
33 // decoder in sync with the incoming signal using null crossings was
34 // rather fragile.
35 //
36 // Use a multicorrelator instead, which relies on the averaged magnitude
37 // of the signal accumulator to sync the decoder with the incoming signal.
38 //
39 // When debugging the code, the multicorrelator mostly corrects the
40 // modem forward in time, which can be explained by the fact that a
41 // bit takes 110.25 samples, while the code uses 110. When the NAVTEX
42 // transmitter is running at exactly 100 baud, one can expect to see
43 // the decoder get adjusted 25 times a second, to make up for the
44 // difference between 11000 and 11025.
45 //
46 // When multiple signals are on the air simultaneously, the null crossing
47 // code would often lose track of the signal. The multicorrelator seems
48 // to be more stable in this situation, though of course when both signals
49 // are close in strength things do not get decoded right.
50 //
51 // The signal sampling spread of 1/6 of the width of a bit was set through
52 // trial and error. A larger spread increases the signal difference between
53 // early, prompt, and late samples, but reduces the accumulator value seen
54 // by the demodulator. A smaller spread increases the accumulator value seen,
55 // but makes it harder to lock on in noisy conditions.
56 // ---------------------------------------------------------------------
57
58 // ---------------------------------------------------------------------
59 // low pass mark & space individually
60 // Rik van Riel, AB1KW, <riel@surriel.com>
61 //
62 // Putting individual low pass filters on mark and space seems to
63 // result in an improved ability to overcome pulse noise, and decode
64 // weaker navtex signals.
65 //
66 // I have not found any signal where the performance of the codec
67 // got worse with this change.
68 // ---------------------------------------------------------------------
69
70 // ---------------------------------------------------------------------
71 // Correct display metric
72 // Rik van Riel, AB1KW, <riel@surriel.com>
73 //
74 // The NAVTEX display_metric() function was buggy, in that decayavg
75 // returns the decayed value, but does not store it. It always put
76 // the current value in the metric, and kept avg_ratio at 0.0.
77 //
78 // This resulted in a somewhat chaotic, and not very useful metric
79 // display. Copy over the S/N calculation from the RTTY code, because
80 // that code seems to work well.
81
82 // Also print the S/N in Status2, like the RTTY code and other modes
83 // do.
84
85 // Copying over the RTTY S/N code wholesale might still not be
86 // enough, since the NAVTEX wave form appears to be somewhat
87 // different from RTTY. However, at least we have something
88 // now, and the metric used for squelch seems to work again.
89 // ---------------------------------------------------------------------
90
91 // ---------------------------------------------------------------------
92 // Correct display metric
93 // Rik van Riel, AB1KW, <riel@surriel.com>
94 //
95 // Widen afc filter for 'jump 90 Hz' code
96 //
97 // When the NAVTEX code spots a power imbalance of more than a factor
98 // 5 between mark and space, it will shift the frequency by 90 Hz.
99 // This is reported to help with some signals.
100 //
101 // However, it breaks with some other signals, which have a different
102 // spectral distribution between mark and space, with a spectrum looking
103 // something like this:
104 //
105 // *
106 // *
107 // *
108 // **
109 // ****** ***
110 // ******** ******
111 // ********** ********
112 // ********************************************
113 // **********************************************
114 //
115 // In this spectrum, mark & space have a similar amount of energy,
116 // but that is only apparent when the comparison between them is
117 // done on a wider sample than 10 Hz.
118 //
119 // Sampling 30 Hz instead seems to result in a more stable AFC.
120 // ---------------------------------------------------------------------
121
122 // ---------------------------------------------------------------------
123 // use exact bit length
124 // Rik van Riel, AB1KW, <riel@surriel.com>
125 //
126 // With a baud rate of 100 and a sample rate of 11025, the number
127 // of bits per sample is 110.25. Approximating this with 110 bits
128 // per sample results in the decoder continuously chasing after the
129 // signal, and losing it more easily during transient noise or
130 // interference events.
131 //
132 // Simply changing the variable type from int to double makes life
133 // a little easier on the bit tracking code.
134 //
135 // The accumulator does not seem to care that it gets an extra sample
136 // every 4 bit periods.
137 // ---------------------------------------------------------------------
138
139 // ---------------------------------------------------------------------
140 // improvements to the multi correlator
141 // Rik van Riel, AB1KW, <riel@surriel.com>
142 //
143 // While the multi correlator for bit sync was a nice improvement over
144 // the null crossing tracking, it did lose sync too easily in the presence
145 // of transient noise or interference, and was full of magic adjustments.
146 //
147 // Replace the magic adjustments with a calculation, which makes the multi
148 // correlator able to ride out transient noise or interference, and then
149 // make a larger adjustment all at once (if needed).
150 // ---------------------------------------------------------------------
151
152 // ---------------------------------------------------------------------
153 // use same mark/space detector as RTTY modem
154 // Rik van Riel, AB1KW, <riel@surriel.com>
155 //
156 // Switch the NAVTEX modem over to the same mark/spac decoder, with W7AY's
157 // automatic threshold correction algorithm, like the RTTY modem uses.
158 //
159 // The noise subtraction is a little different than in the RTTY modem;
160 // the algorithm used in W7AY's code seems to work a little better with
161 // the noise present at 518 kHz, when compared to the algorithm used in
162 // the RTTY modem.
163 //
164 // I have compared this detector to a correlation detector; the latter
165 // appears to be a little more sensitive, which includes higher
166 // sensitivity to noise. With a 250 Hz filter on the radio, the
167 // correlation detector might be a little bit better, while with the
168 // filter on the radio opened up to 4kHz wide, this detector appears
169 // to be more robust.
170 //
171 // On signals with a large mark/space power imbalance, or where the power
172 // distribution in one of the two throws off the automatic frequency
173 // correction, this decoder is able to handle signals that neither of
174 // the alternatives tested does.
175 // ---------------------------------------------------------------------
176
177 #include <math.h>
178 #include <stdlib.h>
179 #include <stdio.h>
180 #include <ctype.h>
181 #include <memory.h>
182 #include <assert.h>
183
184 #include <list>
185 #include <vector>
186 #include <string>
187 #include <queue>
188 #include <deque>
189 #include <map>
190 #include <iostream>
191 #include <sstream>
192 #include <fstream>
193 #include <stdexcept>
194
195 #include "config.h"
196 #include "configuration.h"
197 #include "fl_digi.h"
198 #include "debug.h"
199 #include "gettext.h"
200 #include "navtex.h"
201 #include "logbook.h"
202 #include "coordinate.h"
203 #include "misc.h"
204 #include "status.h"
205 #include "strutil.h"
206 #include "kmlserver.h"
207 #include "record_loader.h"
208 #include "fftfilt.h"
209
210 #include "strutil.h"
211
212 #include "FL/fl_ask.H"
213
214 pthread_mutex_t navtex_filter_mutex = PTHREAD_MUTEX_INITIALIZER;
215
216 /// This models a line of the file defining Navtex stations.
217 class NavtexRecord
218 {
219 std::string m_country ;
220 std::string m_country_code ;
221 double m_frequency ;
222 char m_origin ;
223 std::string m_callsign ;
224 std::string m_name ;
225 CoordinateT::Pair m_coordinates ;
226
227 std::string m_locator ;
228
229 /// Reads a CSV file.
230 static const char m_delim = ';';
231 public:
NavtexRecord()232 NavtexRecord()
233 : m_frequency(0.0)
234 , m_origin('?')
235 , m_name( _("Unknown station") ) {}
236
origin(void) const237 char origin(void) const { return m_origin; };
coordinates() const238 const CoordinateT::Pair & coordinates() const { return m_coordinates; }
frequency(void) const239 double frequency(void) const { return m_frequency; };
country() const240 const std::string & country() const { return m_country; }
name() const241 const std::string & name() const { return m_name; }
callsign() const242 const std::string & callsign() const { return m_callsign; }
243
244 /// Example: Azores;AZR;490.0;J;CTH;Horta;38 32 N;28 38 W;II;PP
operator >>(std::istream & istrm,NavtexRecord & rec)245 friend std::istream & operator>>( std::istream & istrm, NavtexRecord & rec )
246 {
247 std::string input_str ;
248 if( ! std::getline( istrm, input_str ) ) return istrm ;
249 std::stringstream str_strm( input_str );
250
251 if( read_until_delim( m_delim, str_strm, rec.m_country )
252 && read_until_delim( m_delim, str_strm /* Country code */ )
253 && read_until_delim( m_delim, str_strm, rec.m_frequency )
254 && read_until_delim( m_delim, str_strm, rec.m_origin )
255 && read_until_delim( m_delim, str_strm, rec.m_callsign )
256 && read_until_delim( m_delim, str_strm, rec.m_name )
257 && read_until_delim( m_delim, str_strm, rec.m_coordinates.latitude() )
258 && read_until_delim( m_delim, str_strm, rec.m_coordinates.longitude() )
259 && read_until_delim( m_delim, str_strm /* Zone */ )
260 && read_until_delim( m_delim, str_strm /* Language */ )
261
262 && ( rec.m_coordinates.latitude().is_lon() == false )
263 && ( rec.m_coordinates.longitude().is_lon() == true )
264 )
265 {
266 return istrm ;
267 }
268
269 istrm.setstate(std::ios::eofbit);
270 return istrm ;
271 }
272 };
273
274 /// Navtex catalog of stations is used when logging to ADIF file: It gives the station name, callsign etc...
275 class NavtexCatalog : public RecordLoader< NavtexCatalog >
276 {
277 // TODO: Consider a multimap<char, NavtexRecord>
278 typedef std::deque< NavtexRecord > CatalogType ;
279 CatalogType m_catalog ;
280
281 /// Frequency more or less 1 %: 485-494 kHz, 512-523 kHz etc...
freq_close(double freqA,double freqB)282 static bool freq_close( double freqA, double freqB )
283 {
284 static const double freq_ratio = 1.01 ;
285 return ( freqA < freqB * freq_ratio ) || ( freqA * freq_ratio > freqB );
286 }
287
288 /// Tells if this is a reasonable Navtex frequency.
freq_acceptable(double freq)289 static bool freq_acceptable( double freq )
290 {
291 return freq_close( freq, 490.0 )
292 || freq_close( freq, 518.0 )
293 || freq_close( freq, 4209.5 );
294 }
295
Clear()296 void Clear() {
297 m_catalog.clear();
298 }
299
ReadRecord(std::istream & istrm)300 bool ReadRecord( std::istream & istrm ) {
301 NavtexRecord tmp ;
302 istrm >> tmp ;
303 if( istrm || istrm.eof() ) {
304 m_catalog.push_back( tmp );
305 return true ;
306 }
307 return false ;
308 }
309
310 /// Minimal edit distance (Levenshtein) between the pattern and any token of the string.
DistToStationName(const std::string & msg,const std::string & pattern)311 static double DistToStationName( const std::string & msg, const std::string & pattern ) {
312 std::stringstream strm( msg );
313 /// Any big number is OK, if bigger than any string length.
314 double currDist = 1.7976931348623157e+308; // DBL_MAX ;
315 typedef std::istream_iterator<std::string> StrmIterStr ;
316 for( StrmIterStr itStrm( strm ); itStrm != StrmIterStr(); ++itStrm ) {
317 const std::string tmp = *itStrm ;
318 currDist = std::min( currDist, (double)levenshtein( tmp, pattern ) );
319 }
320 return currDist ;
321 }
322
323 public:
base_filename() const324 std::string base_filename() const
325 {
326 return "NAVTEX_Stations.csv";
327 }
328
Description() const329 const char * Description() const
330 {
331 return _("Navtex stations");
332 }
333
334 /// Usual frequencies are 490, 518 or 4209 kiloHertz.
FindStation(long long freq_ll,char origin,const std::string & maidenhead,const std::string & msg)335 const NavtexRecord * FindStation(
336 long long freq_ll,
337 char origin,
338 const std::string & maidenhead,
339 const std::string & msg)
340 {
341 if( maidenhead.empty() ) return NULL;
342
343 if( m_catalog.empty() ) {
344 int nbRecs = LoadAndRegister();
345
346 static bool error_signaled = false ;
347
348 if( nbRecs <= 0 ) {
349 LOG_WARN("Error reading Navtex stations file");
350 if(error_signaled == false) {
351 fl_alert("Cannot read Navtex file %s", storage_filename().first.c_str() );
352 error_signaled = true ;
353 return NULL;
354 }
355 }
356 error_signaled = false ;
357 }
358
359 const CoordinateT::Pair coo( maidenhead );
360
361 /// Possible Navtex stations stored by closer first.
362 typedef std::multimap< double, CatalogType::const_iterator > SolutionType ;
363
364 SolutionType solDistKm;
365
366 double freq = freq_ll / 1000.0 ; // As kiloHertz in the data file.
367
368 bool okFreq = freq_acceptable( freq );
369
370 //LOG_INFO("Operator Maidenhead=%s lon=%lf lat=%lf okFreq=%d Origin=%c",
371 // maidenhead.c_str(), coo.longitude().angle(), coo.latitude().angle(), okFreq, origin );
372
373 for( CatalogType::const_iterator it = m_catalog.begin(), en = m_catalog.end(); it != en ; ++it )
374 {
375 /// The origin letters must be identical.
376 if( origin != it->origin() ) continue ;
377
378 /// The two frequencies must be close more or less 10%.
379 bool freqClose = freq_close( freq, it->frequency() );
380 if( okFreq && ! freqClose ) continue ;
381
382 /// Solutions are stored smallest distance first.
383 double dist = coo.distance( it->coordinates() );
384 solDistKm.insert( SolutionType::value_type( dist, it ) );
385 }
386
387 /// No station found.
388 if( solDistKm.empty() ) return NULL;
389
390 /// Only one station, no ambiguity.
391 SolutionType::iterator begSolKm = solDistKm.begin();
392 if( solDistKm.size() == 1 ) return & ( *begSolKm->second );
393
394 SolutionType solStrDist ;
395 // Maybe some station names appear but not others. This can be for example "Maltaradio", "Cullercoat", "Limnos" etc...
396 for( SolutionType::iterator itSolKm = begSolKm, endSolKm = solDistKm.end(); itSolKm != endSolKm; ++itSolKm ) {
397 std::stringstream strm ;
398 strm << itSolKm->second->coordinates();
399 // LOG_INFO("Name=%s Dist=%lf %s", itSolKm->second->name().c_str(), itSolKm->first, strm.str().c_str() );
400 // The message is in uppercase anyway, so no need to convert.
401 double str_dist = DistToStationName( msg, ucasestr( itSolKm->second->name() ) );
402
403 solStrDist.insert( SolutionType::value_type( str_dist, itSolKm->second ) );
404 }
405
406 // There are at least two elements, so we can do this.
407 SolutionType::iterator begSolStr = solStrDist.begin();
408 SolutionType::iterator nxtSolStr = begSolStr;
409 ++nxtSolStr ;
410
411 // The first message only contains a string very similar to a radio station.
412 if( (begSolStr->first < 2) && ( nxtSolStr->first > 2 ) ) {
413 //LOG_INFO("Levenshtein beg=%lf beg_name=%s next=%lf next_name=%s",
414 // begSolStr->first, begSolStr->second->name().c_str(),
415 // nxtSolStr->first, nxtSolStr->second->name().c_str() );
416 return & (*begSolStr->second) ;
417 }
418
419 // There are at least two elements, and more than one station name, or none of them,
420 // is contained in the message.
421
422 // Just returns the closest element.
423 return & ( *begSolKm->second );
424
425 // Now we could search for a coordinate in the message, and we will keep the station which is the closest
426 // to this coordinate. We wish to do that anyway in order to map things in KML.
427 // Possible formats are -(This is experimental):
428 // 67-04.0N 032-25.7E
429 // 47-29'30N 003-16'00W
430 // 6930.1N 01729.9E
431 // 48-21'45N 004-31'45W
432 // 58-37N 003-32W
433 // 314408N 341742E
434 // 42-42N 005-10E
435 // 54-02.3N 004-45.8E
436 // 55-20.76N 014-45.27E
437 // 55-31.1 N 012-44.7 E
438 // 5330.4N 01051.5W
439 // 43 45.0 N - 015 44.8 E
440 // 34-33.7N 012-28.7E
441 // 51 10.55 N - 001 51.02 E
442 // 51.21.67N 002.13.29E
443 // 73 NORTH 14 EAST
444 // 58-01.20N 005-27.08W
445 // 50.56N 007.00,5W
446 // 5630,1N- 00501,6E
447 // LAT. 41.06N - LONG 012.57E
448 // 42 40 01 N - 018 05 10 E
449 // 40 25 31N - 18 15 30E
450 // 40-32.2N 000-33.5E
451 // 58-01.2 NORTH 005-27.1 WEST
452 // 39-07,7N 026-39,2E
453 //
454 // ESTIMATED LIMIT OF ALL KNOWN ICE:
455 // 4649N 5411W TO 4530N 5400W TO
456 // 4400N 4900W TO 4545N 4530W TO
457 // 4715N 4530W TO 5000N 4715W TO
458 // 5530N 5115W TO 5700N 5545W.
459 //
460
461
462 }
463 }; // NavtexCatalog
464
465 static const unsigned char code_to_ltrs[128] = {
466 //0 1 2 3 4 5 6 7 8 9 a b c d e f
467 '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', // 0
468 '_', '_', '_', '_', '_', '_', '_', 'J', '_', '_', '_', 'F', '_', 'C', 'K', '_', // 1
469 '_', '_', '_', '_', '_', '_', '_', 'W', '_', '_', '_', 'Y', '_', 'P', 'Q', '_', // 2
470 '_', '_', '_', '_', '_', 'G', '_', '_', '_', 'M', 'X', '_', 'V', '_', '_', '_', // 3
471 '_', '_', '_', '_', '_', '_', '_', 'A', '_', '_', '_', 'S', '_', 'I', 'U', '_', // 4
472 '_', '_', '_', 'D', '_', 'R', 'E', '_', '_', 'N', '_', '_', ' ', '_', '_', '_', // 5
473 '_', '_', '_', 'Z', '_', 'L', '_', '_', '_', 'H', '_', '_', '\n', '_', '_', '_', // 6
474 '_', 'O', 'B', '_', 'T', '_', '_', '_', '\r', '_', '_', '_', '_', '_', '_', '_' // 7
475 };
476
477 static const unsigned char code_to_figs[128] = {
478 //0 1 2 3 4 5 6 7 8 9 a b c d e f
479 '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', // 0
480 '_', '_', '_', '_', '_', '_', '_', '\'', '_', '_', '_', '!', '_', ':', '(', '_', // 1
481 '_', '_', '_', '_', '_', '_', '_', '2', '_', '_', '_', '6', '_', '0', '1', '_', // 2
482 '_', '_', '_', '_', '_', '&', '_', '_', '_', '.', '/', '_', ';', '_', '_', '_', // 3
483 '_', '_', '_', '_', '_', '_', '_', '-', '_', '_', '_', '\07', '_', '8', '7', '_', // 4
484 '_', '_', '_', '$', '_', '4', '3', '_', '_', ',', '_', '_', ' ', '_', '_', '_', // 5
485 '_', '_', '_', '"', '_', ')', '_', '_', '_', '#', '_', '_', '\n', '_', '_', '_', // 6
486 '_', '9', '?', '_', '5', '_', '_', '_', '\r', '_', '_', '_', '_', '_', '_', '_' // 7
487 };
488
489 static const int code_ltrs = 0x5a;
490 static const int code_figs = 0x36;
491 static const int code_alpha = 0x0f;
492 static const int code_beta = 0x33;
493 static const int code_char32 = 0x6a;
494 static const int code_rep = 0x66;
495 static const int char_bell = 0x07;
496
497 class CCIR476 {
498
499 unsigned char m_ltrs_to_code[128];
500 unsigned char m_figs_to_code[128];
501 bool m_valid_codes[128];
502 public:
CCIR476()503 CCIR476() {
504 memset( m_ltrs_to_code, 0, 128 );
505 memset( m_figs_to_code, 0, 128 );
506 for( size_t i = 0; i < 128; ++i ) m_valid_codes[i] = false ;
507 for (int code = 0; code < 128; code++) {
508 // Valid codes have four bits set only. This leaves three bits for error detection.
509 // TODO: If a code is invalid, we could take the closest value in terms of bits.
510 if (check_bits(code)) {
511 m_valid_codes[code] = true;
512 unsigned char figv = code_to_figs[code];
513 unsigned char ltrv = code_to_ltrs[code];
514 if ( figv != '_') {
515 m_figs_to_code[figv] = code;
516 }
517 if ( ltrv != '_') {
518 m_ltrs_to_code[ltrv] = code;
519 }
520 }
521 }
522 }
523
char_to_code(std::string & str,int ch,bool & ex_shift) const524 void char_to_code(std::string & str, int ch, bool & ex_shift) const {
525 ch = toupper(ch);
526 // avoid unnecessary shifts
527 if (ex_shift && m_figs_to_code[ch] != '\0') {
528 str.push_back( m_figs_to_code[ch] );
529 }
530 else if (!ex_shift && m_ltrs_to_code[ch] != '\0') {
531 str.push_back( m_ltrs_to_code[ch] );
532 }
533 else if (m_figs_to_code[ch] != '\0') {
534 ex_shift = true;
535 str.push_back( code_figs );
536 str.push_back( m_figs_to_code[ch] );
537 }
538 else if (m_ltrs_to_code[ch] != '\0') {
539 ex_shift = false;
540 str.push_back( code_ltrs );
541 str.push_back( m_ltrs_to_code[ch] );
542 }
543 }
544
code_to_char(int code,bool shift) const545 int code_to_char(int code, bool shift) const {
546 const unsigned char * target = (shift) ? code_to_figs : code_to_ltrs;
547 if (target[code] != '_') {
548 return target[code];
549 }
550 // default: return negated code
551 return -code;
552 }
553
bytes_to_code(int * pos)554 int bytes_to_code(int *pos) {
555 int code = 0;
556 int i;
557
558 for (i = 0; i < 7; i++)
559 code |= ((pos[i] > 0) << i);
560 return code;
561 }
562
bytes_to_char(int * pos,int shift)563 int bytes_to_char(int *pos, int shift) {
564 int code = bytes_to_code(pos);
565 return code_to_char(code, shift);
566 }
567
568 // http://graphics.stanford.edu/~seander/bithacks.html#CountBitsSetNaive
569 /// Counting set bits, Brian Kernighan's way
check_bits(int v)570 static bool check_bits(int v) {
571 int bc = 0;
572 while (v != 0) {
573 bc++;
574 v &= v - 1;
575 }
576 //printf("check_bits %d %d %c\n", bc, (int)code_to_ltrs[v], code_to_ltrs[v] );
577 return bc == 4;
578 }
579
580 // Is there a valid character in the next 7 ints?
valid_char_at(int * pos)581 bool valid_char_at(int *pos) {
582 int count = 0;
583 int i;
584
585 for (i = 0; i < 7; i++)
586 if (pos[i] > 0)
587 count++;
588
589 return (count == 4);
590 }
591
592 };
593
594 /// This is temporary, to manipulate a multi-line string.
595 static const char * new_line = "\n";
596
597 // Coordinates samples:
598 // 52-08.5N 003-18.0E
599 // 51-03.93N 001-09.17E
600 // 50-40.2N 001-03.7W
601 class ccir_message : public std::string {
602 static const size_t header_len = 10 ;
603 static const size_t trunc_len = 5 ;
604
605 // Header structure is:
606 // ZCZCabcd message text NNNN
607 // a : Origin of the station.
608 // b : Message type.
609 // cd : Message number from this station.
610 char m_origin ;
611 char m_subject ;
612 int m_number ;
613 public:
msg_type(void) const614 const char * msg_type(void) const
615 {
616 switch(m_subject) {
617 case 'A' : return _("Navigational warning");
618 case 'B' : return _("Meteorological warning");
619 case 'C' : return _("Ice report");
620 case 'D' : return _("Search & rescue information, pirate warnings");
621 case 'E' : return _("Meteorological forecast");
622 case 'F' : return _("Pilot service message");
623 case 'G' : return _("AIS message");
624 case 'H' : return _("LORAN message");
625 case 'I' : return _("Not used");
626 case 'J' : return _("SATNAV messages");
627 case 'K' : return _("Other electronic navaid messages");
628 case 'L' : return _("Navigational warnings");
629 case 'T' : return _("Test transmissions (UK only)");
630 case 'V' : return _("Notice to fishermen (U.S. only)");
631 case 'W' : return _("Environmental (U.S. only)");
632 case 'X' : return _("Special services - allocation by IMO NAVTEX Panel");
633 case 'Y' : return _("Special services - allocation by IMO NAVTEX Panel");
634 case 'Z' : return _("No message on hand");
635 default : return _("Invalid navtex subject");
636 }
637 }
638 private:
639 /// Remove non-Ascii chars, replace new-line by special character etc....
cleanup()640 void cleanup() {
641 /// It would be possible to do the change in place, because the new string
642 /// it shorter than the current one, but at the expense of clarity.
643 bool wasDelim = false, wasSpace = false, chrSeen = false ;
644 std::string newStr ;
645 for( iterator it = begin(); it != end(); ++it ) {
646 switch( *it ) {
647 case '\n':
648 case '\r': wasDelim = true ;
649 break ;
650 case ' ' :
651 case '\t': wasSpace = true ;
652 break ;
653 default : if( chrSeen ) {
654 if( wasDelim ) {
655 newStr.append(new_line);
656 } else if( wasSpace ) {
657 newStr.push_back(' ');
658 }
659 }
660 wasDelim = false ;
661 wasSpace = false ;
662 chrSeen = true ;
663 newStr.push_back( *it );
664 }
665 }
666 swap( newStr );
667 }
668
init_members()669 void init_members() {
670 m_origin = '?';
671 m_subject = '?';
672 m_number = 0 ;
673 }
674 public:
ccir_message()675 ccir_message() {
676 init_members();
677 }
678
ccir_message(const std::string & s,char origin,char subject,int number)679 ccir_message( const std::string & s, char origin, char subject, int number )
680 : std::string(s)
681 , m_origin(origin)
682 , m_subject(subject)
683 , m_number(number) {
684 cleanup();
685 }
686
reset_msg()687 void reset_msg() {
688 clear();
689 init_members();
690 }
691
692 typedef std::pair<bool, ccir_message> detect_result ;
detect_header()693 detect_result detect_header() {
694 size_t qlen = size();
695
696 if (qlen >= header_len) {
697 const char * comp = & (*this)[ qlen - header_len ];
698 if(
699 (comp[0] == 'Z') &&
700 (comp[1] == 'C') &&
701 (comp[2] == 'Z') &&
702 (comp[3] == 'C') &&
703 (comp[4] == ' ') &&
704 isalnum(comp[5]) &&
705 isalnum(comp[6]) &&
706 isdigit(comp[7]) &&
707 isdigit(comp[8]) &&
708 // (comp[9] == '\r') )
709 (strchr( "\n\r", comp[9] ) ) ) {
710
711 /// This returns the garbage before the valid header.
712 // Garbage because the trailer could not be read, but maybe header OK.
713 ccir_message msg_cut(
714 substr( 0, size() - header_len ),
715 m_origin,
716 m_subject,
717 m_number );
718 m_origin = comp[5];
719 m_subject = comp[6];
720 m_number = ( comp[7] - '0' ) * 10 + ( comp[8] - '0' );
721 // Remove the beginning useless chars.
722 /// TODO: Read broken headers such as "ZCZC EA0?"
723 clear();
724 return detect_result( true, msg_cut );
725 }
726 }
727 return detect_result( false, ccir_message() ); ;
728 }
729
detect_end()730 bool detect_end() {
731 // Should be "\r\nNNNN\r\n" theoretically, but tolerates shorter strings.
732 static const size_t slen = 4 ;
733 static const char stop_valid[slen + 1] = "NNNN";
734 size_t qlen = size();
735 if (qlen < slen) {
736 return false;
737 }
738 std::string comp = substr(qlen - slen, slen);
739 bool end_seen = comp == stop_valid;
740 if( end_seen ) {
741 erase( qlen - slen, slen );
742 LOG_INFO("\n%s", c_str());
743 }
744 return end_seen ;
745 }
746
display(const std::string & alt_string)747 void display( const std::string & alt_string ) {
748 std::string::operator=( alt_string );
749 cleanup();
750
751 long long currFreq = wf->rfcarrier();
752
753 if( ! progdefaults.NVTX_AdifLog && ! progdefaults.NVTX_KmlLog ) {
754 return ;
755 }
756
757 const NavtexRecord * ptrNavRec = NavtexCatalog::InstCatalog().FindStation(currFreq, m_origin, progdefaults.myLocator, *this );
758 if( ptrNavRec != NULL ) {
759 LOG_INFO("Locator=%s Origin=%c freq=%d name=%s lon=%lf lat=%lf",
760 progdefaults.myLocator.c_str(),
761 m_origin,
762 static_cast<int>(currFreq),
763 ptrNavRec->name().c_str(),
764 ptrNavRec->coordinates().longitude().angle(),
765 ptrNavRec->coordinates().latitude().angle() );
766 } else {
767 LOG_INFO("Locator=%s Origin=%c freq=%d Navtex station not found",
768 progdefaults.myLocator.c_str(),
769 m_origin,
770 static_cast<int>(currFreq) );
771 }
772
773 if( progdefaults.NVTX_AdifLog ) {
774 /// For updating the logbook with received messages.
775 QsoHelper qso(MODE_NAVTEX);
776
777 if( ptrNavRec ) {
778 qso.Push(QTH, ptrNavRec->country() );
779 qso.Push(CALL, ptrNavRec->callsign() );
780 qso.Push(COUNTRY, ptrNavRec->country() );
781 qso.Push(GRIDSQUARE, ptrNavRec->coordinates().locator() );
782 qso.Push(NAME, ptrNavRec->name() );
783 /// If the header is clean, the message type is removed from the string.
784 // In this context, this field cannot be used.
785 qso.Push(XCHG1, msg_type() );
786 qso.Push(SRX, strformat( "%d", m_number ) );
787 } else {
788 qso.Push(NAME, std::string("Station_") + m_origin );
789 }
790
791 // Sequence of Chars and line-breaks, ASCII CR (code 13) + ASCII LF (code 10)
792 qso.Push(NOTES, strreplace( *this, new_line, ADIF_EOL ) );
793 }
794
795 // Adds a placemark to the navtex KML file.
796 if( progdefaults.NVTX_KmlLog ) {
797 if( ptrNavRec ) {
798 KmlServer::CustomDataT custData ;
799 custData.Push( "Callsign", ptrNavRec->callsign() );
800 custData.Push( "Country", ptrNavRec->country() );
801 custData.Push( "Locator", ptrNavRec->coordinates().locator() );
802 custData.Push( "Message number", m_number );
803 custData.Push( "Frequency", currFreq );
804
805 custData.Push( "Mode", mode_info[MODE_NAVTEX].adif_name );
806 custData.Push( "Message", *this );
807
808 KmlServer::GetInstance()->Broadcast(
809 "Navtex",
810 0,
811 ptrNavRec->coordinates(),
812 0,
813 ptrNavRec->name(),
814 "navtex_station",
815 substr( 0, 20 ) + "...",
816 custData );
817 }
818
819 // TODO: Parse the message to extract coordinates.
820 }
821 } // display
822 }; // ccir_message
823
824 static const int deviation_f = 85;
825
826 static const double dflt_center_freq = 1000.0 ;
827
828 class navtex ;
829
830 /// Implements PIMPL idiom.
831 class navtex_implementation {
832
833 enum State {
834 SYNC_SETUP, SYNC, READ_DATA
835 };
836
state_to_str(State s)837 static const char * state_to_str( State s ) {
838 switch( s ) {
839 case SYNC_SETUP: return "SYNC_SETUP";
840 case SYNC : return "SYNC";
841 case READ_DATA : return "READ_DATA";
842 default : return "Unknown" ;
843 }
844 }
845
846 bool m_only_sitor_b ;
847 int m_message_counter ;
848
849 static const size_t m_tx_block_len = 1024 ;
850 /// Between -1 and 1.
851 double m_tx_buf[m_tx_block_len];
852 size_t m_tx_counter ;
853
854 navtex * m_ptr_navtex ;
855
856 pthread_mutex_t m_mutex_tx ;
857 typedef std::list<std::string> TxMsgQueueT ;
858 TxMsgQueueT m_tx_msg_queue ;
859
860 double m_metric ;
861
862 CCIR476 m_ccir476;
863 typedef std::list<int> sync_chrs_type ;
864 ccir_message m_curr_msg ;
865
866 double m_message_time ;
867 double m_early_accumulator ;
868 double m_prompt_accumulator ;
869 double m_late_accumulator ;
870 double m_mark_f, m_space_f;
871 double m_time_sec;
872
873 double m_baud_rate ;
874 int m_sample_rate ;
875 int m_averaged_mark_state;
876 int m_bit_duration ;
877 fftfilt *m_mark_lowpass;
878 fftfilt *m_space_lowpass;
879 double m_mark_phase;
880 double m_space_phase;
881 double m_bit_sample_count;
882 State m_state;
883 int m_sample_count;
884 double m_next_early_event;
885 double m_next_prompt_event;
886 double m_next_late_event;
887 double m_average_early_signal;
888 double m_average_prompt_signal;
889 double m_average_late_signal;
890 std::vector<int> m_bit_values;
891 int m_bit_cursor;
892 bool m_shift ;
893 bool m_pulse_edge_event;
894 int m_error_count;
895 bool m_alpha_phase ;
896 bool m_header_found ;
897 char snrmsg[80];
898 // filter method related
899 double m_center_frequency_f ;
900
901 navtex_implementation( const navtex_implementation & );
902 navtex_implementation();
903 navtex_implementation & operator=( const navtex_implementation & );
904 public:
navtex_implementation(int the_sample_rate,bool only_sitor_b,navtex * ptr_navtex)905 navtex_implementation(int the_sample_rate, bool only_sitor_b, navtex * ptr_navtex ) {
906 pthread_mutex_init( &m_mutex_tx, NULL );
907 m_ptr_navtex = ptr_navtex ;
908 m_only_sitor_b = only_sitor_b ;
909 m_message_counter = 1 ;
910 m_metric = 0.0 ;
911 m_time_sec = 0.0 ;
912 m_state = SYNC_SETUP;
913 m_message_time = 0.0 ;
914 m_early_accumulator = 0;
915 m_prompt_accumulator = 0;
916 m_late_accumulator = 0;
917 m_sample_rate = the_sample_rate;
918 m_bit_duration = 0;
919 m_shift = false;
920 m_alpha_phase = false;
921 m_header_found = false;
922 m_center_frequency_f = dflt_center_freq;
923 // this value must never be zero and bigger than 10.
924 m_baud_rate = 100;
925 double m_bit_duration_seconds = 1.0 / m_baud_rate;
926 m_bit_sample_count = m_sample_rate * m_bit_duration_seconds;
927 // A narrower spread between signals allows the modem to
928 // center on the pulses better, but a wider spread makes
929 // more robust under noisy conditions. 1/5 seems to work.
930 m_next_early_event = 0;
931 m_next_prompt_event = m_bit_sample_count / 5;
932 m_next_late_event = m_bit_sample_count * 2 / 5;
933 m_average_early_signal = 0;
934 m_average_prompt_signal = 0;
935 m_average_late_signal = 0;
936 m_error_count = 0;
937 m_sample_count = 0;
938 // keep 1 second worth of bit values for decoding
939 m_bit_values.resize(m_baud_rate);
940 m_bit_cursor = 0;
941
942 m_mark_lowpass = 0;
943 m_space_lowpass = 0;
944
945 set_filter_values();
946 configure_filters();
947 }
~navtex_implementation()948 ~navtex_implementation() {
949 pthread_mutex_destroy( &m_mutex_tx );
950 }
951 private:
952
set_filter_values()953 void set_filter_values() {
954 m_mark_f = m_center_frequency_f + deviation_f;
955 m_space_f = m_center_frequency_f - deviation_f;
956 m_mark_phase = 0;
957 m_space_phase = 0;
958 }
959
configure_filters()960 void configure_filters() {
961 // do not allow filters to be changed during signal processing!
962 guard_lock filter_guard(&navtex_filter_mutex);
963
964 const int filtlen = 512;
965 if (m_mark_lowpass) delete m_mark_lowpass;
966 m_mark_lowpass = new fftfilt(m_baud_rate/m_sample_rate, filtlen);
967 m_mark_lowpass->rtty_filter(m_baud_rate/m_sample_rate);
968
969 if (m_space_lowpass) delete m_space_lowpass;
970 m_space_lowpass = new fftfilt(m_baud_rate/m_sample_rate, filtlen);
971 m_space_lowpass->rtty_filter(m_baud_rate/m_sample_rate);
972 }
973
set_state(State s)974 void set_state(State s) {
975 if (s != m_state) {
976 m_state = s;
977 set_label_from_state();
978 }
979 }
980
981 /// The parameter is appended at the message end.
flush_message(const std::string & extra_info)982 void flush_message(const std::string & extra_info)
983 {
984 if( m_header_found )
985 {
986 m_header_found = false;
987 display_message( m_curr_msg, m_curr_msg + extra_info );
988 }
989 else
990 {
991 display_message( m_curr_msg, "[Lost header]:" + m_curr_msg + extra_info );
992 }
993 m_curr_msg.reset_msg();
994 m_message_time = m_time_sec;
995 }
996
997 /// Checks that we have no waited too long, and if so, flushes the message with a specific terminator.
process_timeout()998 void process_timeout() {
999 /// No messaging in SitorB
1000 if ( m_only_sitor_b ) {
1001 return ;
1002 }
1003 bool timeOut = m_time_sec - m_message_time > 600 ;
1004 if ( ! timeOut ) return ;
1005 LOG_INFO("Timeout: time_sec=%lf, message_time=%lf", m_time_sec, m_message_time );
1006
1007 // TODO: Headerless messages could be dropped if shorter than X chars.
1008 flush_message(":<TIMEOUT>");
1009 }
1010
process_messages(int c)1011 void process_messages(int c) {
1012 m_curr_msg.push_back((char) c);
1013
1014 /// No header nor trailer for plain SitorB.
1015 if ( m_only_sitor_b ) {
1016 m_header_found = true;
1017 m_message_time = m_time_sec;
1018 return;
1019 }
1020
1021 ccir_message::detect_result msg_cut = m_curr_msg.detect_header();
1022 if ( msg_cut.first ) {
1023 /// Maybe the message was already valid.
1024 if( m_header_found )
1025 {
1026 display_message( msg_cut.second, msg_cut.second + ":[Lost trailer]" );
1027 }
1028 else
1029 {
1030 /// Maybe only non-significant chars.
1031 if( ! msg_cut.second.empty() )
1032 {
1033 display_message( msg_cut.second, "[Lost header]:" + msg_cut.second + ":[Lost trailer]" );
1034 }
1035 }
1036 m_header_found = true;
1037 m_message_time = m_time_sec;
1038
1039 } else { // valid message state
1040 if ( m_curr_msg.detect_end() ) {
1041 flush_message("");
1042 }
1043 }
1044 }
1045
1046 // The rep character is transmitted 5 characters (35 bits) ahead of
1047 // the alpha character.
fec_offset(int offset)1048 int fec_offset(int offset) {
1049 return offset - 35;
1050 }
1051
1052 // Flip the sign of the smallest (least certain) bit in a character;
1053 // hopefully this will result in the right valid character.
flip_smallest_bit(int * pos)1054 void flip_smallest_bit(int *pos) {
1055 int min_zero = INT_MIN, min_one = INT_MAX;
1056 int min_zero_pos = -1, min_one_pos = -1;
1057 int count_zero = 0, count_one = 1;
1058 int val, i;
1059
1060 for (i = 0; i < 7; i++) {
1061 val = pos[i];
1062 if (val < 0) {
1063 count_zero++;
1064 if (val > min_zero) {
1065 min_zero = val;
1066 min_zero_pos = i;
1067 }
1068 } else {
1069 count_one++;
1070 if (val < min_one) {
1071 min_one = val;
1072 min_one_pos = i;
1073 }
1074 }
1075 }
1076
1077 // A valid character has 3 zeroes and 4 ones, if we have
1078 // 5 ones or 4 zeroes, flipping the smallest one would make
1079 // this character valid.
1080 if (count_zero == 4)
1081 pos[min_zero_pos] = -pos[min_zero_pos];
1082 else if (count_one == 5)
1083 pos[min_one_pos] = -pos[min_one_pos];
1084 }
1085
1086 // Try to find a position in the bit stream with:
1087 // - the largest number of valid characters, and
1088 // - with rep (duplicate) characters in the right locations
1089 // This way the code can sync up with an incoming signal after
1090 // the initial alpha/rep synchronisation
1091 //
1092 // http://www.arachnoid.com/JNX/index.html
1093 // "NAUTICAL" becomes:
1094 // rep alpha rep alpha N alpha A alpha U N T A I U C T A I L C blank A blank L
find_alpha_characters(void)1095 int find_alpha_characters(void) {
1096 int best_offset = 0;
1097 int best_score = 0;
1098 int offset, i;
1099
1100 // With 7 bits per character, and interleaved rep & alpha
1101 // characters, the first alpha character with a corresponding
1102 // rep in the stream can be in any of 14 locations
1103 for (offset = 35; offset < (35 + 14); offset++) {
1104 int score = 0;
1105 int reps = 0;
1106 int limit = m_bit_values.size() - 7;
1107
1108 // Search for the largest sequence of valid characters
1109 for (i = offset; i < limit; i += 7) {
1110 if (m_ccir476.valid_char_at(&m_bit_values[i])) {
1111 int ri = fec_offset(i);
1112 int code = m_ccir476.bytes_to_code(&m_bit_values[i]);
1113 int rep = m_ccir476.bytes_to_code(&m_bit_values[ri]);
1114
1115 // This character is valid
1116 score++;
1117
1118 // Does it match its rep?
1119 if (code == rep) {
1120 // This offset is wrong, rep
1121 // and alpha are spaced odd
1122 if (code == code_alpha ||
1123 code == code_rep) {
1124 score = 0;
1125 continue;
1126 }
1127 reps++;
1128 } else if (code == code_alpha) {
1129 // Is there a matching rep to
1130 // this alpha?
1131 int ri = i - 7;
1132 int rep = m_ccir476.bytes_to_code(&m_bit_values[ri]);
1133 if (rep == code_rep) {
1134 reps++;
1135 }
1136 }
1137 }
1138 }
1139
1140 // the most valid characters, with at least 3 FEC reps
1141 if (reps >= 3 && score + reps > best_score) {
1142 best_score = score + reps;
1143 best_offset = offset;
1144 }
1145 }
1146
1147 // m_bit_values fits 14 characters; if there are at least
1148 // 9 good ones, tell the caller where they start
1149 if (best_score > 8)
1150 return best_offset;
1151 else
1152 return -1;
1153 }
1154
1155 // Turns accumulator values (estimates of whether a bit is 1 or 0)
1156 // into navtex messages
handle_bit_value(int accumulator)1157 void handle_bit_value(int accumulator) {
1158 int buffersize = m_bit_values.size();
1159 int i, offset = 0;
1160
1161 // Store the received value in the bit stream
1162 for (i = 0; i < buffersize - 1; i++) {
1163 m_bit_values[i] = m_bit_values[i+1];
1164 }
1165 m_bit_values[buffersize - 1] = accumulator;
1166 if (m_bit_cursor > 0)
1167 m_bit_cursor--;
1168
1169 // Find the most likely location where the message starts
1170 if (m_state == SYNC) {
1171 offset = find_alpha_characters();
1172 if (offset >= 0) {
1173 set_state(READ_DATA);
1174 m_bit_cursor = offset;
1175 m_alpha_phase = true;
1176 } else
1177 set_state(SYNC_SETUP);
1178 }
1179
1180 // Process 7-bit characters as they come in,
1181 // skipping rep (duplicate) characters
1182 if (m_state == READ_DATA) {
1183 if (m_bit_cursor < buffersize - 7) {
1184 if (m_alpha_phase) {
1185 int ret = process_bytes(m_bit_cursor);
1186 m_error_count -= ret;
1187 if (m_error_count > 5)
1188 set_state(SYNC_SETUP);
1189 if (m_error_count < 0)
1190 m_error_count = 0;
1191 }
1192 m_alpha_phase = !m_alpha_phase;
1193 m_bit_cursor += 7;
1194 }
1195 }
1196 }
1197
1198 // Turn a series of 7 bit confidence values into a character
1199 //
1200 // 1 on successful decode of the alpha character
1201 // 0 on unmodified FEC replacement
1202 // -1 on soft failure (FEC calculation)
1203 // -2 on hard failure
process_bytes(int m_bit_cursor)1204 int process_bytes(int m_bit_cursor) {
1205 int code = m_ccir476.bytes_to_code(&m_bit_values[m_bit_cursor]);
1206 int success = 0;
1207
1208 if (m_ccir476.check_bits(code)) {
1209 LOG_DEBUG("valid code : %x (%c)", code, m_ccir476.code_to_char(code, m_shift));
1210 success = 1;
1211 goto decode;
1212 }
1213
1214 if (fec_offset(m_bit_cursor) < 0)
1215 return -1;
1216
1217 // The alpha (primary) character received was not correct.
1218 // Try the rep (duplicate) copy of the character, and some
1219 // permutations to see if the correct character can be found.
1220 {
1221 int i, calc, avg[7];
1222 // Rep is 5 characters before alpha.
1223 int reppos = fec_offset(m_bit_cursor);
1224 int rep = m_ccir476.bytes_to_code(&m_bit_values[reppos]);
1225 if (CCIR476::check_bits(rep)) {
1226 // Current code is probably code_alpha.
1227 // Skip decoding to avoid switching phase.
1228 if (rep == code_rep)
1229 return 0;
1230 LOG_DEBUG("FEC replacement: %x -> %x (%c)", code, rep, m_ccir476.code_to_char(rep, m_shift));
1231 code = rep;
1232 goto decode;
1233 }
1234
1235 // Neither alpha or rep are valid. Check whether
1236 // the average of the two is a valid character.
1237 for (i = 0; i < 7; i++) {
1238 int a = m_bit_values[m_bit_cursor + i];
1239 int r = m_bit_values[reppos + i];
1240 avg[i] = a + r;
1241 }
1242
1243 calc = m_ccir476.bytes_to_code(avg);
1244 if (CCIR476::check_bits(calc)) {
1245 LOG_DEBUG("FEC calculation: %x & %x -> %x (%c)", code, rep, calc, m_ccir476.code_to_char(calc, m_shift));
1246 code = calc;
1247 success = -1;
1248 goto decode;
1249 }
1250
1251 // Flip the lowest confidence bit in alpha.
1252 flip_smallest_bit(&m_bit_values[m_bit_cursor]);
1253 calc = m_ccir476.bytes_to_code(&m_bit_values[m_bit_cursor]);
1254 if (CCIR476::check_bits(calc)) {
1255 LOG_DEBUG("FEC calculation: %x & %x -> %x (%c)", code, rep, calc, m_ccir476.code_to_char(calc, m_shift));
1256 code = calc;
1257 success = -1;
1258 goto decode;
1259 }
1260
1261 // Flip the lowest confidence bit in rep.
1262 flip_smallest_bit(&m_bit_values[reppos]);
1263 calc = m_ccir476.bytes_to_code(&m_bit_values[reppos]);
1264 if (CCIR476::check_bits(calc)) {
1265 LOG_DEBUG("FEC calculation: %x & %x -> %x (%c)", code, rep, calc, m_ccir476.code_to_char(calc, m_shift));
1266 code = calc;
1267 success = -1;
1268 goto decode;
1269 }
1270
1271 // Try flipping the bit with the lowest confidence
1272 // in the average of alpha & rep.
1273 flip_smallest_bit(avg);
1274 calc = m_ccir476.bytes_to_code(avg);
1275 if (CCIR476::check_bits(calc)) {
1276 LOG_DEBUG("FEC calculation: %x & %x -> %x (%c)", code, rep, calc, m_ccir476.code_to_char(calc, m_shift));
1277 code = calc;
1278 success = -1;
1279 goto decode;
1280 }
1281
1282 LOG_DEBUG("decode fail %x, %x", code, rep);
1283 return -2;
1284 }
1285
1286 decode:
1287 process_char(code);
1288 return success;
1289 }
1290
process_char(int chr)1291 bool process_char(int chr) {
1292 static int last_char = 0;
1293 switch (chr) {
1294 case code_rep:
1295 // This code should run in alpha phase, but
1296 // it just received two rep characters. Fix
1297 // the rep/alpha phase, so FEC works again.
1298 if (last_char == code_rep) {
1299 LOG_DEBUG("fixing rep/alpha sync");
1300 m_alpha_phase = false;
1301 }
1302 break;
1303 case code_alpha:
1304 break;
1305 case code_beta:
1306 break;
1307 case code_char32:
1308 break;
1309 case code_ltrs:
1310 m_shift = false;
1311 break;
1312 case code_figs:
1313 m_shift = true;
1314 break;
1315 default:
1316 chr = m_ccir476.code_to_char(chr, m_shift);
1317 if (chr < 0) {
1318 LOG_INFO(_("Missed this code: %x"), abs(chr));
1319 } else {
1320 filter_print(chr);
1321 process_messages(chr);
1322 }
1323 break;
1324 } // switch
1325
1326 last_char = chr;
1327 return true;
1328 }
1329
filter_print(int c)1330 void filter_print(int c) {
1331 if (c == char_bell) {
1332 /// TODO: It should be a beep, but French navtex displays a quote.
1333 put_rx_char('\'');
1334 } else if (c != -1 && c != '\r' && c != code_alpha && c != code_rep) {
1335 put_rx_char(c);
1336 }
1337 }
1338
compute_metric(void)1339 void compute_metric(void)
1340 {
1341 static double sigpwr = 0.0 ;
1342 static double noisepwr = 0.0;
1343 double delta = m_baud_rate/8.0;
1344 double np = wf->powerDensity(m_center_frequency_f, delta) * 3000 / delta;
1345 double sp =
1346 wf->powerDensity(m_mark_f, delta) +
1347 wf->powerDensity(m_space_f, delta) + 1e-10;
1348 double snr;
1349
1350 sigpwr = decayavg ( sigpwr, sp, sp > sigpwr ? 2 : 8);
1351 noisepwr = decayavg ( noisepwr, np, 16 );
1352 snr = 10*log10(sigpwr / noisepwr);
1353
1354 snprintf(snrmsg, sizeof(snrmsg), "s/n %3.0f dB", snr);
1355 put_Status2(snrmsg);
1356 m_metric = CLAMP((3000 / delta) * (sigpwr/noisepwr), 0.0, 100.0);
1357 m_ptr_navtex->display_metric(m_metric);
1358
1359 }
1360
process_afc()1361 void process_afc() {
1362 if( progStatus.afconoff == false ) return ;
1363 static size_t cnt_upd = 0 ;
1364 static const size_t delay_upd = 50 ;
1365 ++cnt_upd ;
1366
1367 /// AFC from time to time.
1368 if( ( cnt_upd % delay_upd ) != 0 ) {
1369 return ;
1370 }
1371 static int cnt_read_data = 0 ;
1372 /// This centers the carrier where the activity is the strongest.
1373 static const int bw[][2] = {
1374 { -deviation_f - 10, -deviation_f + 5 },
1375 { deviation_f - 5, deviation_f + 10 } };
1376 // find mid frequency of power spectra in mark/space frequency interval
1377 double max_carrier = wf->powerDensityMaximum( 2, bw );
1378
1379 /// Do not change the frequency too quickly if an image is received.
1380 double next_carr = 0.0 ;
1381
1382 State lingering_state ;
1383 if( m_state == READ_DATA ) {
1384 /// Proportional to the number of lines between each AFC update.
1385 cnt_read_data = delay_upd / 20 ;
1386 lingering_state = READ_DATA ;
1387 } else {
1388 if( cnt_read_data ) {
1389 --cnt_read_data ;
1390 lingering_state = READ_DATA ;
1391 } else {
1392 lingering_state = m_state ;
1393 /// Maybe this is the phasing signal, so we recenter.
1394 double pwr_left = wf->powerDensity ( max_carrier - deviation_f, 30 );
1395 double pwr_right = wf->powerDensity( max_carrier + deviation_f, 30 );
1396 static const double ratio_left_right = 5.0 ;
1397 if( pwr_left > ratio_left_right * pwr_right ) {
1398 max_carrier -= deviation_f ;
1399 } else if ( ratio_left_right * pwr_left < pwr_right ) {
1400 max_carrier += deviation_f ;
1401 }
1402 }
1403 }
1404 switch( lingering_state ) {
1405 case SYNC_SETUP:
1406 next_carr = max_carrier ;
1407 break;
1408 case SYNC:
1409 next_carr = decayavg( m_center_frequency_f, max_carrier, 1 );
1410 break;
1411 case READ_DATA:
1412 // It will stay stable for a couple of calls.
1413 if( max_carrier < m_center_frequency_f )
1414 next_carr = std::max( max_carrier, m_center_frequency_f - 3.0 );
1415 else if( max_carrier > m_center_frequency_f )
1416 next_carr = std::min( max_carrier, m_center_frequency_f + 3.0 );
1417 else next_carr = max_carrier ;
1418 break;
1419 default:
1420 LOG_ERROR("Should not happen: lingering_state=%d", (int)lingering_state );
1421 break ;
1422 }
1423
1424 LOG_DEBUG("m_center_frequency_f=%f max_carrier=%f next_carr=%f cnt_read_data=%d",
1425 (double)m_center_frequency_f, max_carrier, next_carr, cnt_read_data );
1426 double delta = fabs( m_center_frequency_f - next_carr );
1427 if( delta > 1.0 ) { // Hertz.
1428 m_ptr_navtex->set_freq(next_carr);
1429 }
1430 }
1431
1432 // The signal is sampled at three points: early, prompt, and late.
1433 // The prompt event is where the signal is decoded, while early and
1434 // late are only used to adjust the time of the sampling to match
1435 // the incoming signal.
1436 //
1437 // The early event happens 1/5 bit period before the prompt event,
1438 // and the late event 1/5 bit period later. If the incoming signal
1439 // peaks early, it means the decoder is late. That is, if the early
1440 // signal is "too large", decoding should to happen earlier.
1441 //
1442 // Attempt to center the signal so the accumulator is at its
1443 // maximum deviation at the prompt event. If the bit is decoded
1444 // too early or too late, the code is more sensitive to noise,
1445 // and less likely to decode the signal correctly.
process_multicorrelator()1446 void process_multicorrelator() {
1447 // Adjust the sampling period once every 8 bit periods.
1448 if (m_sample_count % (int)(m_bit_sample_count * 8))
1449 return;
1450
1451 // Calculate the slope between early and late signals
1452 // to align the logic sampling with the received signal
1453 double slope = m_average_late_signal - m_average_early_signal;
1454
1455 if (m_average_prompt_signal * 1.05 < m_average_early_signal &&
1456 m_average_prompt_signal * 1.05 < m_average_late_signal) {
1457 // At a signal minimum. Get out quickly.
1458 if (m_average_early_signal > m_average_late_signal) {
1459 // move prompt to where early is
1460 slope = m_next_early_event - m_next_prompt_event;
1461 slope = fmod(slope - m_bit_sample_count, m_bit_sample_count);
1462 m_average_late_signal = m_average_prompt_signal;
1463 m_average_prompt_signal = m_average_early_signal;
1464 } else {
1465 // move prompt to where late is
1466 slope = m_next_late_event - m_next_prompt_event;
1467 slope = fmod(slope + m_bit_sample_count, m_bit_sample_count);
1468 m_average_early_signal = m_average_prompt_signal;
1469 m_average_prompt_signal = m_average_late_signal;
1470 }
1471 } else
1472 slope /= 1024;
1473
1474 if (slope) {
1475 m_next_early_event += slope;
1476 m_next_prompt_event += slope;
1477 m_next_late_event += slope;
1478 LOG_DEBUG("adjusting by %1.2f, early %1.1f, prompt %1.1f, late %1.1f", slope, m_average_early_signal, m_average_prompt_signal, m_average_late_signal);
1479 }
1480 }
1481
1482 /* A NAVTEX message is built on SITOR collective B-mode and consists of:
1483 * a phasing signal of at least ten seconds
1484 * the four characters "ZCZC" that identify the end of phasing
1485 * a single space
1486 * four characters B1, B2, B3 and B4:
1487 * B1 is an alpha character identifying the station,
1488 * B2 is an alpha character used to identify the subject of the message.
1489 * B3 and B4 are two-digit numerics identifying individual messages
1490 * a carriage return and a line feed
1491 * the information
1492 * the four characters "NNNN" to identify the end of information
1493 * a carriage return and two line feeds
1494 * either
1495 * 5 or more seconds of phasing signal and another message starting with "ZCZC" or
1496 * an end of emission idle signal alpha for at least 2 seconds. */
1497 public:
process_data(const double * data,int nb_samples)1498 void process_data(const double * data, int nb_samples) {
1499 cmplx z, zmark, zspace, *zp_mark, *zp_space;
1500
1501 process_afc();
1502 process_timeout();
1503 // prevent user waterfall interaction from changing filters!
1504 guard_lock g( &navtex_filter_mutex );
1505
1506 for( int i =0; i < nb_samples; ++i ) {
1507 int n_out;
1508
1509 m_time_sec = m_sample_count / m_sample_rate ;
1510
1511 double dv = 32767 * data[i];
1512 z = cmplx(dv, dv);
1513
1514 zmark = mixer(m_mark_phase, m_mark_f, z);
1515 m_mark_lowpass->run(zmark, &zp_mark);
1516
1517 zspace = mixer(m_space_phase, m_space_f, z);
1518 n_out = m_space_lowpass->run(zspace, &zp_space);
1519
1520 if (n_out)
1521 process_fft_output(zp_mark, zp_space, n_out);
1522 }
1523 }
1524
1525 private:
mixer(double & phase,double f,cmplx in)1526 cmplx mixer(double &phase, double f, cmplx in)
1527 {
1528 cmplx z = cmplx( cos(phase), sin(phase)) * in;
1529
1530 phase -= TWOPI * f / m_sample_rate;
1531 if (phase < -TWOPI) phase += TWOPI;
1532
1533 return z;
1534 }
1535
1536 // noise average decays fast down, slow up
noise_decay(double avg,double value)1537 double noise_decay(double avg, double value) {
1538 int divisor;
1539 if (value < avg)
1540 divisor = m_bit_sample_count / 4;
1541 else
1542 divisor = m_bit_sample_count * 48;
1543 return decayavg(avg, value, divisor);
1544 }
1545
1546 // envelope average decays fast up, slow down
envelope_decay(double avg,double value)1547 double envelope_decay(double avg, double value) {
1548 int divisor;
1549 if (value > avg)
1550 divisor = m_bit_sample_count / 4;
1551 else
1552 divisor = m_bit_sample_count * 16;
1553 return decayavg(avg, value, divisor);
1554 }
1555
process_fft_output(cmplx * zp_mark,cmplx * zp_space,int samples)1556 void process_fft_output(cmplx *zp_mark, cmplx *zp_space, int samples) {
1557 // envelope & noise levels for mark & space, respectively
1558 static double mark_env = 0, space_env = 0;
1559 static double mark_noise = 0, space_noise = 0;
1560
1561 for (int i = 0; i < samples; i++) {
1562 double mark_abs = abs(zp_mark[i]);
1563 double space_abs = abs(zp_space[i]);
1564
1565 process_multicorrelator();
1566
1567 // determine noise floor & envelope for mark & space
1568 mark_env = envelope_decay(mark_env, mark_abs);
1569 mark_noise = noise_decay(mark_noise, mark_abs);
1570
1571 space_env = envelope_decay(space_env, space_abs);
1572 space_noise = noise_decay(space_noise, space_abs);
1573
1574 double noise_floor = (space_noise + mark_noise) / 2;
1575
1576 // clip mark & space to envelope & floor
1577 mark_abs = min(mark_abs, mark_env);
1578 mark_abs = max(mark_abs, noise_floor);
1579
1580 space_abs = min(space_abs, space_env);
1581 space_abs = max(space_abs, noise_floor);
1582
1583 // mark-space discriminator with automatic threshold
1584 // correction, see:
1585 // http://www.w7ay.net/site/Technical/ATC/
1586 double logic_level =
1587 (mark_abs - noise_floor) * (mark_env - noise_floor) -
1588 (space_abs - noise_floor) * (space_env - noise_floor) -
1589 0.5 * ( (mark_env - noise_floor) * (mark_env - noise_floor) -
1590 (space_env - noise_floor) * (space_env - noise_floor));
1591
1592 // Using the logarithm of the logic_level tells the
1593 // bit synchronization and character decoding which
1594 // samples were decoded well, and which poorly.
1595 // This helps fish signals out of the noise.
1596 int mark_state = log(1 + abs(logic_level));
1597 if (logic_level < 0)
1598 mark_state = -mark_state;
1599 m_early_accumulator += mark_state;
1600 m_prompt_accumulator += mark_state;
1601 m_late_accumulator += mark_state;
1602
1603 // An average of the magnitude of the accumulator
1604 // is taken at the sample point, as well as a quarter
1605 // bit before and after. This allows the code to see
1606 // the best time to sample the signal without relying
1607 // on (noisy) null crossings.
1608 if (m_sample_count >= m_next_early_event) {
1609 m_average_early_signal = decayavg(
1610 m_average_early_signal,
1611 fabs(m_early_accumulator), 64);
1612 m_next_early_event += m_bit_sample_count;
1613 m_early_accumulator = 0;
1614 }
1615
1616 if (m_sample_count >= m_next_late_event) {
1617 m_average_late_signal = decayavg(
1618 m_average_late_signal,
1619 fabs(m_late_accumulator), 64);
1620 m_next_late_event += m_bit_sample_count;
1621 m_late_accumulator = 0;
1622 }
1623
1624 // the end of a signal pulse
1625 // the accumulator should be at maximum deviation
1626 m_pulse_edge_event = m_sample_count >= m_next_prompt_event;
1627 if (m_pulse_edge_event) {
1628 m_average_prompt_signal = decayavg(
1629 m_average_prompt_signal,
1630 fabs(m_prompt_accumulator), 64);
1631 m_next_prompt_event += m_bit_sample_count;
1632 m_averaged_mark_state = m_prompt_accumulator;
1633 if (m_ptr_navtex->get_reverse())
1634 m_averaged_mark_state = -m_averaged_mark_state;
1635 m_prompt_accumulator = 0;
1636 }
1637
1638 switch (m_state) {
1639 case SYNC_SETUP:
1640 m_error_count = 0;
1641 m_shift = false;
1642 set_state(SYNC);
1643 break;
1644 case SYNC:
1645 case READ_DATA:
1646 if (m_pulse_edge_event)
1647 handle_bit_value(m_averaged_mark_state);
1648 }
1649
1650 m_sample_count++;
1651 }
1652 compute_metric();
1653 }
1654
1655 /// This updates the window label according to the state.
set_label_from_state(void) const1656 void set_label_from_state(void) const
1657 {
1658 put_status( state_to_str(m_state) );
1659 }
1660
1661 private:
1662 /// Each received message is pushed in this queue, so it can be read by XML/RPC.
1663 syncobj m_sync_rx ;
1664 std::queue< std::string > m_received_messages ;
1665
display_message(ccir_message & ccir_msg,const std::string & alt_string)1666 void display_message( ccir_message & ccir_msg, const std::string & alt_string ) {
1667 if( ccir_msg.size() >= (size_t)progdefaults.NVTX_MinSizLoggedMsg )
1668 {
1669 try
1670 {
1671 ccir_msg.display(alt_string);
1672 put_received_message( alt_string );
1673 } catch( const std::exception & exc ) {
1674 LOG_WARN("Caught %s", exc.what() );
1675 }
1676 }
1677 else
1678 {
1679 LOG_INFO("Do not log short message:%s", ccir_msg.c_str() );
1680 }
1681 }
1682
1683 /// Called by the engine each time a message is saved.
put_received_message(const std::string & message)1684 void put_received_message( const std::string &message )
1685 {
1686 guard_lock g( m_sync_rx.mtxp() );
1687 LOG_INFO("%s", message.c_str() );
1688 m_received_messages.push( message );
1689 m_sync_rx.signal();
1690 }
1691
1692 public:
1693 /// Returns a received message, by chronological order.
get_received_message(double max_seconds)1694 std::string get_received_message( double max_seconds )
1695 {
1696 guard_lock g( m_sync_rx.mtxp() );
1697
1698 LOG_DEBUG("Delay=%f", max_seconds );
1699 if( m_received_messages.empty() )
1700 {
1701 if( ! m_sync_rx.wait(max_seconds) ) return "Timeout";
1702 }
1703 std::string message = m_received_messages.front();
1704 m_received_messages.pop();
1705 return message ;
1706 }
1707
1708 // http://www.arachnoid.com/JNX/index.html
1709 // "NAUTICAL" becomes:
1710 // rep alpha rep alpha N alpha A alpha U N T A I U C T A I L C blank A blank L
create_fec(const std::string & str) const1711 std::string create_fec( const std::string & str ) const
1712 {
1713 std::string res ;
1714 const size_t sz = str.size();
1715
1716 static const size_t offset = 2 ;
1717 for( size_t i = 0 ; i < offset ; ++i ) {
1718 res.push_back( code_rep );
1719 res.push_back( code_alpha );
1720 }
1721
1722 for ( size_t i = 0; i < sz; ++i ) {
1723 res.push_back( str[i] );
1724 res.push_back( i >= offset ? str[ i - offset ] : code_alpha );
1725 }
1726
1727 for( size_t i = 0 ; i < offset ; ++i ) {
1728 res.push_back( code_char32 );
1729 res.push_back( str[ sz - offset + i ] );
1730 }
1731 return res;
1732 }
1733
1734 /// Note path std::string can contain null characters. TODO: Beware of the extra copy constructor.
encode(const std::string & str) const1735 std::string encode( const std::string & str ) const
1736 {
1737 std::string res ;
1738 bool shift = false ;
1739 for ( size_t i = 0, sz = str.size(); i < sz; ++ i ) {
1740 m_ccir476.char_to_code(res, str[i], shift );
1741 }
1742 return res;
1743 }
1744
tx_flush()1745 void tx_flush()
1746 {
1747 if( m_tx_counter != 0 ) {
1748 m_ptr_navtex->ModulateXmtr( m_tx_buf, m_tx_counter );
1749 m_tx_counter = 0 ;
1750 }
1751 }
1752
1753 /// Input value must be between -1 and 1
add_sample(double sam)1754 void add_sample( double sam )
1755 {
1756 m_tx_buf[ m_tx_counter++ ] = sam ;
1757
1758 if( m_tx_counter == m_tx_block_len ) {
1759 tx_flush();
1760 }
1761 }
1762
1763 // REMI : Note change to send_sine
send_sine(double seconds,double freq)1764 void send_sine( double seconds, double freq )
1765 {
1766 static double phase = 0;
1767 int nb_samples = seconds * m_ptr_navtex->get_samplerate();
1768 double max_level = 0.9;//0.99 ; // Between -1.0 and 1.0
1769 double ratio = 2.0 * M_PI * (double)freq / (double)m_ptr_navtex->get_samplerate() ;
1770 for (int i = 0; i < nb_samples ; ++i )
1771 {
1772 add_sample( max_level * sin( phase += ratio));//i * ratio ) );
1773 if (phase > 2.0 * M_PI) phase -= 2.0*M_PI;
1774 }
1775 }
1776
send_phasing(int seconds)1777 void send_phasing( int seconds )
1778 {
1779 send_sine( seconds, m_center_frequency_f );
1780 }
1781
send_bit(bool bit)1782 void send_bit( bool bit )
1783 {
1784 send_sine( 1.0 / (double)m_baud_rate, bit ? m_mark_f : m_space_f );
1785 }
1786
send_string(const std::string & msg)1787 void send_string( const std::string & msg )
1788 {
1789 std::string encod = encode( msg );
1790 std::string sevenbits = create_fec( encod );
1791
1792 for( size_t i = 0, sz = sevenbits.size(); i < sz; i++ )
1793 {
1794 char tmp_stat[64];
1795 snprintf( tmp_stat, sizeof(tmp_stat), "Transmission %d%%", (int)( 100.0 * ( i + 1.0 ) / sz ) );
1796 put_status( tmp_stat );
1797
1798 char c = sevenbits[i];
1799 for( size_t j = 0; j < 7 ; ++j, c >>= 1 )
1800 {
1801 send_bit( c & 1 );
1802 }
1803 }
1804 }
send_message(const std::string & msg,bool is_first,bool is_last)1805 void send_message( const std::string & msg, bool is_first, bool is_last )
1806 {
1807 put_status( "Transmission" );
1808 m_tx_counter = 0 ;
1809
1810 if( m_only_sitor_b )
1811 {
1812 send_string( msg );
1813 }
1814 else
1815 {
1816 put_status( "Phasing" );
1817 send_phasing( is_first ? 10.0 : 5.0 );
1818 char preamble[64];
1819 const char origin = 'Z' ; // Never seen this value.
1820 const char subject = 'I' ; // This code is not used.
1821 snprintf( preamble, sizeof(preamble), "ZCZC %c%c%02d\r\n", origin, subject, m_message_counter );
1822 m_message_counter = ( m_message_counter + 1 ) % 100 ;
1823
1824 /// The extra cr-nl before NNNN is not in the specification but clarify things.
1825 std::string full_msg = preamble + msg + "\r\nNNNN\r\n\n";
1826 send_string( full_msg );
1827
1828 // 5 or more seconds of phasing signal and another message starting with "ZCZC" or
1829 // an end of emission idle signal alpha for at least 2 seconds. */
1830 if( is_last ) {
1831 put_status( "Trailer" );
1832 send_phasing(2.0);
1833 }
1834 }
1835 tx_flush();
1836 put_status( "" );
1837 }
1838
append_message_to_send(const std::string & msg)1839 void append_message_to_send( const std::string & msg )
1840 {
1841 guard_lock g( &m_mutex_tx );
1842 m_tx_msg_queue.push_back( msg );
1843 }
1844
transmit_message_async(const std::string & msg)1845 void transmit_message_async( const std::string & msg )
1846 {
1847 LOG_INFO("%s", msg.c_str() );
1848
1849 append_message_to_send( msg );
1850
1851 bool is_first = true ;
1852 for(;;)
1853 {
1854 guard_lock g( &m_mutex_tx );
1855
1856 TxMsgQueueT::iterator it = m_tx_msg_queue.begin(), en = m_tx_msg_queue.end();
1857 if( it == en ) break ;
1858 TxMsgQueueT::iterator it_next = it ;
1859 ++it_next ;
1860 bool is_last = it_next == en ;
1861 send_message( *it, is_first, is_last );
1862 is_first = false ;
1863 m_tx_msg_queue.erase(it);
1864 }
1865 }
1866
process_tx()1867 void process_tx()
1868 {
1869 std::string msg ;
1870
1871 for(;;)
1872 {
1873 int c = get_tx_char();
1874 if( c == GET_TX_CHAR_NODATA ) {
1875 break ;
1876 }
1877 msg.push_back( c );
1878 }
1879
1880 for( size_t i = 0 ; i < msg.size(); ++ i)
1881 {
1882 put_echo_char( msg[i] );
1883 }
1884
1885 transmit_message_async(msg);
1886 }
1887
set_carrier(double freq)1888 void set_carrier( double freq )
1889 {
1890 m_center_frequency_f = freq;
1891 set_filter_values();
1892 }
1893
1894 }; // navtex_implementation
1895
1896 #ifdef NAVTEX_COMMAND_LINE
1897 /// For testing purpose, this file can be compiled and run separately as a command-line program.
main(int n,const char ** v)1898 int main(int n, const char ** v )
1899 {
1900 printf("%s\n", v[1] );
1901 FILE * f = fl_fopen( v[1], "r" );
1902 fseek( f, 0, SEEK_END );
1903 long l = ftell( f );
1904 printf("l=%ld\n", l);
1905 char * buf = new char[l];
1906 fseek( f, 0, SEEK_SET );
1907 size_t lr = fread( buf, 1, l, f );
1908 if( lr - l ) {
1909 printf("Err reading\n");
1910 exit(EXIT_FAILURE);
1911 };
1912
1913 navtex_implementation nv(11025) ;
1914 double * tmp = new double[l/2];
1915 const short * shrt = (const short *)buf;
1916 for( int i = 0; i < l/2; i++ )
1917 tmp[i] = ( (double)shrt[i] ) / 32767.0;
1918 nv.process_data( tmp, l / 2 );
1919 return 0 ;
1920 }
1921 #endif // NAVTEX_COMMAND_LINE
1922
navtex(trx_mode md)1923 navtex::navtex (trx_mode md)
1924 {
1925 modem::cap |= CAP_AFC | CAP_REV;
1926 navtex::mode = md;
1927 modem::samplerate = 11025;
1928 modem::bandwidth = 2 * deviation_f ;
1929 modem::reverse = false ;
1930 bool only_sitor_b = false ;
1931 switch( md )
1932 {
1933 case MODE_NAVTEX : only_sitor_b = false ;
1934 break;
1935 case MODE_SITORB : only_sitor_b = true ;
1936 break;
1937 default : LOG_ERROR("Unknown mode");
1938 }
1939 m_impl = new navtex_implementation( modem::samplerate, only_sitor_b, this );
1940 }
1941
~navtex()1942 navtex::~navtex()
1943 {
1944 if( m_impl )
1945 {
1946 delete m_impl ;
1947 }
1948 }
rx_init()1949 void navtex::rx_init()
1950 {
1951 put_MODEstatus(modem::mode);
1952 }
1953
restart()1954 void navtex::restart()
1955 {
1956 }
1957
rx_process(const double * buf,int len)1958 int navtex::rx_process(const double *buf, int len)
1959 {
1960 m_impl->process_data( buf, len );
1961 return 0;
1962 }
1963
tx_init()1964 void navtex::tx_init()
1965 {
1966 videoText(); // In trx/modem.cxx
1967 }
1968
tx_process()1969 int navtex::tx_process()
1970 {
1971 modem::tx_process();
1972
1973 m_impl->process_tx();
1974
1975 return -1;
1976 }
1977
set_freq(double freq)1978 void navtex::set_freq( double freq )
1979 {
1980 modem::set_freq( freq );
1981 m_impl->set_carrier( freq );
1982 }
1983
1984 /// This returns the next received message.
get_message(int max_seconds)1985 std::string navtex::get_message(int max_seconds)
1986 {
1987 return m_impl->get_received_message(max_seconds);
1988 }
1989
send_message(const std::string & msg)1990 std::string navtex::send_message(const std::string &msg)
1991 {
1992 m_impl->append_message_to_send(msg);
1993 start_tx(); // If this is not done.
1994 return "";
1995 }
1996
1997