kopia lustrzana https://github.com/jamescoxon/dl-fldigi
2354 wiersze
54 KiB
C++
2354 wiersze
54 KiB
C++
// ---------------------------------------------------------------------
|
|
// pkt.cxx -- 1200/300/2400 baud AX.25
|
|
//
|
|
//
|
|
// This file is a proposed part of fldigi. Adapted very liberally from
|
|
// rtty.cxx, with many thanks to John Hansen, W2FS, who wrote
|
|
// 'dcc.doc' and 'dcc2.doc', GNU Octave, GNU Radio Companion, and finally
|
|
// Bartek Kania (bk.gnarf.org) whose 'aprs.c' expository coding style helped
|
|
// shape this implementation.
|
|
//
|
|
// Copyright (C) 2010
|
|
// Dave Freese, W1HKJ
|
|
// Chris Sylvain, KB3CS
|
|
//
|
|
// fldigi is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation; either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// fldigi is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with fldigi; if not, write to the
|
|
//
|
|
// Free Software Foundation, Inc.
|
|
// 51 Franklin Street, Fifth Floor
|
|
// Boston, MA 02110-1301 USA.
|
|
//
|
|
// ---------------------------------------------------------------------
|
|
|
|
|
|
#include <config.h>
|
|
#include <iostream>
|
|
using namespace std;
|
|
|
|
#include "pkt.h"
|
|
|
|
#include "fl_digi.h"
|
|
#include "misc.h"
|
|
#include "confdialog.h"
|
|
#include "configuration.h"
|
|
#include "status.h"
|
|
|
|
#include "timeops.h"
|
|
|
|
static char msg1[20];
|
|
static char msg2[20];
|
|
|
|
/***********************************************************************
|
|
* these are redefined as static members of the class
|
|
* in the pkt1200.h file
|
|
* increased number of elements in the PKTBITS to prepare for when
|
|
* HF/VHF/DOUBLE selection is available
|
|
* removed leading _ in names. Leading underscore is reserved in Linux
|
|
* for the OS and library code.
|
|
***********************************************************************/
|
|
/*
|
|
1200 -> tones 1200 mark and 2200 Hz space (1700 Hz center)
|
|
300 -> tones 1600 space and 1800 Hz mark (1700 Hz center)
|
|
2400 -> tones 297.5 mark and 2102.5 Hz space (1200 Hz center)
|
|
*/
|
|
const double pkt::CENTER[] = {1700, 1700, 1200};
|
|
const double pkt::SHIFT[] = {1000, 200, 1805};
|
|
const int pkt::BAUD[] = {1200, 300, 2400};
|
|
const int pkt::BITS[] = {8, 8, 8};
|
|
|
|
PKT_MicE_field pkt::MicE_table[][12][5] = {
|
|
{
|
|
{ Zero, Zero, South, P0, East },
|
|
{ One, Zero, South, P0, East },
|
|
{ Two, Zero, South, P0, East },
|
|
{ Three, Zero, South, P0, East },
|
|
{ Four, Zero, South, P0, East },
|
|
{ Five, Zero, South, P0, East },
|
|
{ Six, Zero, South, P0, East },
|
|
{ Seven, Zero, South, P0, East },
|
|
{ Eight, Zero, South, P0, East },
|
|
{ Nine, Zero, South, P0, East },
|
|
{ Invalid, Null, Null, Null, Null },
|
|
{ Invalid, Null, Null, Null, Null }
|
|
},
|
|
{ // ['A'..'K'] + 'L'
|
|
{ Zero, One, Null, Null, Null }, // custom A/B/C msg codes
|
|
{ One, One, Null, Null, Null },
|
|
{ Two, One, Null, Null, Null },
|
|
{ Three, One, Null, Null, Null },
|
|
{ Four, One, Null, Null, Null },
|
|
{ Five, One, Null, Null, Null },
|
|
{ Six, One, Null, Null, Null },
|
|
{ Seven, One, Null, Null, Null },
|
|
{ Eight, One, Null, Null, Null },
|
|
{ Nine, One, Null, Null, Null },
|
|
{ Space, One, Null, Null, Null },
|
|
{ Space, Zero, South, P0, East }
|
|
},
|
|
{ // ['P'..'Z']
|
|
{ Zero, One, North, P100, West }, // standard A/B/C msg codes
|
|
{ One, One, North, P100, West },
|
|
{ Two, One, North, P100, West },
|
|
{ Three, One, North, P100, West },
|
|
{ Four, One, North, P100, West },
|
|
{ Five, One, North, P100, West },
|
|
{ Six, One, North, P100, West },
|
|
{ Seven, One, North, P100, West },
|
|
{ Eight, One, North, P100, West },
|
|
{ Nine, One, North, P100, West },
|
|
{ Space, One, North, P100, West },
|
|
{ Invalid, Null, Null, Null, Null }
|
|
} };
|
|
|
|
PKT_PHG_table pkt::PHG_table[] = {
|
|
{ "Omni", 4 },
|
|
{ "NE", 2 },
|
|
{ "E", 1 },
|
|
{ "SE", 2 },
|
|
{ "S", 1 },
|
|
{ "SW", 2 },
|
|
{ "W", 1 },
|
|
{ "NW", 2 },
|
|
{ "N", 1 }
|
|
};
|
|
|
|
void pkt::tx_init(SoundBase *sc)
|
|
{
|
|
scard = sc;
|
|
|
|
int scale_factor = (pkt_baud > 1200 ? 2 : 1); // baud rate proportional
|
|
|
|
// start each new transmission with MARK tone
|
|
pretone = PKT_MarkBits * scale_factor;
|
|
// number of flags to begin frame
|
|
preamble = PKT_StartFlags * scale_factor;
|
|
// number of flags to end frame
|
|
postamble = PKT_EndFlags * scale_factor;
|
|
|
|
if (!lo_tone) lo_tone = new NCO();
|
|
if (!hi_tone) hi_tone = new NCO();
|
|
|
|
lo_tone->init(pkt_ctrfreq-(pkt_shift/2), 0, PKT_SampleRate);
|
|
hi_tone->init(pkt_ctrfreq+(pkt_shift/2), 0, PKT_SampleRate);
|
|
|
|
tx_cbuf = &txbuf[0];
|
|
nr_ones = 0;
|
|
currbit = nostuff = did_pkt_head = false;
|
|
|
|
videoText();
|
|
}
|
|
|
|
void pkt::rx_init()
|
|
{
|
|
rxstate = PKT_RX_STATE_STOP;
|
|
scounter = 0;
|
|
|
|
cbuf = &rxbuf[0]; // init rx buf ptr
|
|
}
|
|
|
|
void pkt::set_freq(double f)
|
|
{
|
|
// fixed transmit frequency modem
|
|
if (abs(f - 1700) > 9) {
|
|
if (!modem::freqlocked()) {
|
|
modem::set_freq(pkt_ctrfreq);
|
|
modem::freqlock = true;
|
|
}
|
|
modem::set_freq(f);
|
|
nco_lo->init(f-(pkt_shift/2), 0, PKT_SampleRate);
|
|
nco_hi->init(f+(pkt_shift/2), 0, PKT_SampleRate);
|
|
nco_mid->init(f, 0, PKT_SampleRate);
|
|
}
|
|
else {
|
|
if (modem::freqlocked())
|
|
modem::freqlock = false;
|
|
modem::set_freq(pkt_ctrfreq);
|
|
nco_lo->init(pkt_ctrfreq-(pkt_shift/2), 0, PKT_SampleRate);
|
|
nco_hi->init(pkt_ctrfreq+(pkt_shift/2), 0, PKT_SampleRate);
|
|
nco_mid->init(pkt_ctrfreq, 0, PKT_SampleRate);
|
|
}
|
|
}
|
|
|
|
void pkt::init()
|
|
{
|
|
modem::init();
|
|
set_freq(pkt_ctrfreq); // use pkt default center freq. don't use PSKsweetspot.
|
|
|
|
rx_init();
|
|
|
|
snprintf(msg1, sizeof(msg1), "%4i / %-4.0f", pkt_baud, pkt_shift);
|
|
put_Status1(msg1);
|
|
put_MODEstatus(mode);
|
|
|
|
if (progdefaults.PKT_PreferXhairScope)
|
|
set_scope_mode(Digiscope::XHAIRS);
|
|
else
|
|
set_scope_mode(Digiscope::RTTY);
|
|
|
|
lo_signal_gain = pow(10, progdefaults.PKT_LOSIG_RXGAIN / 10);
|
|
hi_signal_gain = pow(10, progdefaults.PKT_HISIG_RXGAIN / 10);
|
|
|
|
lo_txgain = pow(10, progdefaults.PKT_LOSIG_TXGAIN / 10);
|
|
hi_txgain = pow(10, progdefaults.PKT_HISIG_TXGAIN / 10);
|
|
|
|
if (hi_txgain > 1.0 || lo_txgain > 1.0) {
|
|
// renormalize output levels
|
|
// [ modem output recording depends on gain =< 1.0 ]
|
|
double inv;
|
|
|
|
if (hi_txgain > lo_txgain)
|
|
inv = 1.0 / hi_txgain;
|
|
else
|
|
inv = 1.0 / lo_txgain;
|
|
|
|
lo_txgain *= inv;
|
|
hi_txgain *= inv;
|
|
}
|
|
|
|
// leave 10% headroom
|
|
lo_txgain *= 0.9;
|
|
hi_txgain *= 0.9;
|
|
|
|
#ifndef NDEBUG
|
|
static bool do_once = true;
|
|
|
|
/* from TAPR AX.25 v2.2 Specification:
|
|
2.2.8 Order of Bit Transmission
|
|
With the exception of the FCS field, all fields of an AX.25 frame shall
|
|
be sent with each octet's least-significant bit first. The FCS shall be
|
|
sent most-significant bit first.
|
|
*/
|
|
// it seems they meant "byte first" in the last sentence above. sigh.
|
|
|
|
/* useful tools for identifying CRCs
|
|
SRP16 CRC16 http://home.netsurf.de/wolfgang.ehrhardt/crchash_en.html
|
|
*/
|
|
|
|
if (!do_once) return;
|
|
|
|
#undef CRCDEBUG
|
|
#ifdef CRCDEBUG
|
|
// first two: http://ecee.colorado.edu/~newhallw/TechDepot/AX25CRC/CRC_for_AX25.pdf
|
|
rxbuf[0] = 'A';
|
|
rxbuf[1] = 'B';
|
|
rxbuf[2] = 'C';
|
|
rxbuf[3] = 0x9f;
|
|
rxbuf[4] = 0x2f; // FCS 0x9F2F (CRC16-X25) 0xF9F4 byte-wise reflected
|
|
checkFCS(&rxbuf[3]);
|
|
|
|
/* http://www.lammertbies.nl/comm/info/crc-calculation.html
|
|
|
|
8c4ccc2cac6cec1c9c7609 -> 0x1D0F (each msg byte is flipped)
|
|
3132333435363738399067 -> 0x0F2E
|
|
*/
|
|
|
|
rxbuf[0] = '1';
|
|
rxbuf[1] = '2';
|
|
rxbuf[2] = '3';
|
|
rxbuf[3] = '4';
|
|
rxbuf[4] = '5';
|
|
rxbuf[5] = '6';
|
|
rxbuf[6] = '7';
|
|
rxbuf[7] = '8';
|
|
rxbuf[8] = '9';
|
|
rxbuf[9] = 0x90;
|
|
rxbuf[10] = 0x6e; // FCS 0x906E (CRC16-X25) 0x0976 bytewise reflected
|
|
checkFCS(&rxbuf[9]);
|
|
|
|
// http://www.aerospacesoftware.com/winhexcom.html
|
|
rxbuf[0] = 0x11;
|
|
rxbuf[1] = 0x22;
|
|
rxbuf[2] = 0x33;
|
|
rxbuf[3] = 0x44;
|
|
rxbuf[4] = 0x55;
|
|
rxbuf[5] = 0x66;
|
|
rxbuf[6] = 0x77;
|
|
rxbuf[7] = 0x88;
|
|
rxbuf[8] = 0x99;
|
|
rxbuf[9] = 0x14;
|
|
rxbuf[10] = 0x99; // FCS 0x1499 (CRC16-X25) 0x2899 bytewise reflected
|
|
checkFCS(&rxbuf[9]);
|
|
|
|
// next two: http://www.lammertbies.nl/forum/viewtopic.php?t=607
|
|
rxbuf[0] = 0x10; // 7e 08 91 87 44 7e <- 0x4487 HDLC
|
|
rxbuf[1] = 0x89; // == 7e 10 89 e1 22 7e
|
|
rxbuf[2] = 0x83;
|
|
rxbuf[3] = 0x1f; // CRC16-X25 0x831F reflected is 0xC1F8
|
|
checkFCS(&rxbuf[2]);
|
|
rxbuf[2] = 0x1f;
|
|
rxbuf[3] = 0x83; // set FCS (lo,hi)
|
|
rxbuf[4] = 0x0f;
|
|
rxbuf[5] = 0x47; // magic 0x0F47
|
|
checkFCS(&rxbuf[4]);
|
|
|
|
rxbuf[0] = 0x10; // 7e 08 b1 85 65 7e <- 0x6585 HDLC
|
|
rxbuf[1] = 0x8d; // == 7e 10 8d a1 a6 7e
|
|
rxbuf[2] = 0xc5;
|
|
rxbuf[3] = 0x3b; // CRC16-X25 0xC53B reflected Bwise is 0xA3DC
|
|
checkFCS(&rxbuf[2]);
|
|
rxbuf[2] = 0x3b;
|
|
rxbuf[3] = 0xc5; // set FCS (lo,hi)
|
|
rxbuf[4] = 0x0f;
|
|
rxbuf[5] = 0x47; // magic 0x0F47
|
|
checkFCS(&rxbuf[4]);
|
|
|
|
// http://cs.nju.edu.cn/yangxc/dcc_teach/fcs-calc.pdf
|
|
/* rxbuf[0] = 0x72;
|
|
rxbuf[1] = 0xd3;
|
|
rxbuf[2] = 0x4f;
|
|
rxbuf[3] = 0x3c;
|
|
rxbuf[4] = 0x30; // FCS reflected Bwise is 0x3C30
|
|
*/
|
|
rxbuf = { 0x72, 0xd3, 0x4f, 0x3c, 0x0c };
|
|
checkFCS(&rxbuf[3]);
|
|
|
|
rxbuf[0] = 0x01; //DF test
|
|
rxbuf[1] = 0x02;
|
|
rxbuf[2] = 0x03;
|
|
rxbuf[3] = 0x04;
|
|
rxbuf[4] = 0x40;
|
|
rxbuf[5] = 0x80;
|
|
rxbuf[6] = 0x61; // (was) 0xb77b
|
|
rxbuf[7] = 0xee; // 0x61EE is reflected Bw 0x8677
|
|
checkFCS(&rxbuf[6]);
|
|
|
|
// http://www.avrfreaks.net/index.php?name=PNphpBB2&file=printview&t=75742&start=0
|
|
// (looks like an anonymized AX.25 packet)
|
|
rxbuf[0] = 0x80;
|
|
rxbuf[1] = 0x80;
|
|
rxbuf[2] = 0x80;
|
|
rxbuf[3] = 0x80;
|
|
rxbuf[4] = 0x80;
|
|
rxbuf[5] = 0x80;
|
|
rxbuf[6] = 0xf2;
|
|
rxbuf[7] = 0x9e; // addr char 0x9e>>1 = 0x4f == 'O'
|
|
rxbuf[8] = 0x9e;
|
|
rxbuf[9] = 0x9e;
|
|
rxbuf[10] = 0x9e;
|
|
rxbuf[11] = 0x9e;
|
|
rxbuf[12] = 0x9e;
|
|
rxbuf[13] = 0x61;
|
|
rxbuf[14] = 0x03; // ax.25 flag
|
|
rxbuf[15] = 0xf0; // ax.25 flag
|
|
rxbuf[16] = 0x0b;
|
|
rxbuf[17] = 0x58; // FCS is 0x0B58 (hi,lo) msb->lsb 0xd01a Bwise refl.
|
|
checkFCS(&rxbuf[16]);
|
|
rxbuf[16] = 0x58;
|
|
rxbuf[17] = 0x0b; // set FCS (lo,hi) msb->lsb
|
|
rxbuf[18] = 0x0f;
|
|
rxbuf[19] = 0x47; // magic value 0x0F47 revflipped is 0xF0E2
|
|
// bitflip(0x0F47) = 0xE2F0
|
|
// ~(0xE2F0) = 0x1D0F
|
|
// CRC-CCITT magic value is 0x1D0F
|
|
checkFCS(&rxbuf[18]);
|
|
#endif // CRCDEBUG
|
|
|
|
if (debug::level == debug::DEBUG_LEVEL) {
|
|
// same anonymized packet from above plus some trash before 1st flag
|
|
rxbuf[0] = 0x66; rxbuf[1] = 0x77; rxbuf[2] = 0x7e; rxbuf[3] = 0x7e;
|
|
rxbuf[4] = 0x80; rxbuf[5] = 0x80; rxbuf[6] = 0x80; rxbuf[7] = 0x80;
|
|
rxbuf[8] = 0x80; rxbuf[9] = 0x80; rxbuf[10] = 0xf2; rxbuf[11] = 0x9e;
|
|
rxbuf[12] = 0x9e; rxbuf[13] = 0x9e; rxbuf[14] = 0x9e; rxbuf[15] = 0x9e;
|
|
rxbuf[16] = 0x9e; rxbuf[17] = 0x61; rxbuf[18] = 0x03; rxbuf[19] = 0xf0;
|
|
rxbuf[20] = 0x58; rxbuf[21] = 0x0b; rxbuf[22] = 0x7e; rxbuf[23] = 0x00;
|
|
|
|
unsigned char *cp, b;
|
|
|
|
for (cp = &rxbuf[0]; *cp ; cp++) {
|
|
for (b = 0; b < 8; b++)
|
|
if (*cp & (1 << b))
|
|
rx(true);
|
|
else
|
|
rx(false);
|
|
}
|
|
|
|
// "Sun May 24 14:56:03 2009" http://bk.gnarf.org/radio/aprs-20090524-1.txt
|
|
rxbuf[0] = 0x7e; rxbuf[1] = 0x82; rxbuf[2] = 0xa0; rxbuf[3] = 0xaa;
|
|
rxbuf[4] = 0x64; rxbuf[5] = 0x6a; rxbuf[6] = 0x9c; rxbuf[7] = 0xe0;
|
|
rxbuf[8] = 0xa6; rxbuf[9] = 0x9a; rxbuf[10] = 0x6e; rxbuf[11] = 0x8e;
|
|
rxbuf[12] = 0xb2; rxbuf[13] = 0xa8; rxbuf[14] = 0x60; rxbuf[15] = 0xae;
|
|
rxbuf[16] = 0x92; rxbuf[17] = 0x88; rxbuf[18] = 0x8a; rxbuf[19] = 0x66;
|
|
rxbuf[20] = 0x40; rxbuf[21] = 0x65; rxbuf[22] = 0x03; rxbuf[23] = 0xf0;
|
|
rxbuf[24] = 0x3d; rxbuf[25] = 0x35; rxbuf[26] = 0x35; rxbuf[27] = 0x34;
|
|
rxbuf[28] = 0x39; rxbuf[29] = 0x2e; rxbuf[30] = 0x32; rxbuf[31] = 0x36;
|
|
rxbuf[32] = 0x4e; rxbuf[33] = 0x2f; rxbuf[34] = 0x30; rxbuf[35] = 0x31;
|
|
rxbuf[36] = 0x33; rxbuf[37] = 0x30; rxbuf[38] = 0x37; rxbuf[39] = 0x2e;
|
|
rxbuf[40] = 0x30; rxbuf[41] = 0x33; rxbuf[42] = 0x45; rxbuf[43] = 0x2f;
|
|
rxbuf[44] = 0x2a; rxbuf[45] = 0x2a; rxbuf[46] = 0x4b; rxbuf[47] = 0x45;
|
|
rxbuf[48] = 0x56; rxbuf[49] = 0x4c; rxbuf[50] = 0x41; rxbuf[51] = 0x4e;
|
|
rxbuf[52] = 0x47; rxbuf[53] = 0x45; rxbuf[54] = 0x20; rxbuf[55] = 0x48;
|
|
rxbuf[56] = 0x45; rxbuf[57] = 0x4c; rxbuf[58] = 0x49; rxbuf[59] = 0x50;
|
|
rxbuf[60] = 0x4f; rxbuf[61] = 0x52; rxbuf[62] = 0x54; rxbuf[63] = 0x2a;
|
|
rxbuf[64] = 0x2a; rxbuf[65] = 0x20; rxbuf[66] = 0x7b; rxbuf[67] = 0x55;
|
|
rxbuf[68] = 0x49; rxbuf[69] = 0x56; rxbuf[70] = 0x33; rxbuf[71] = 0x32;
|
|
rxbuf[72] = 0x4e; rxbuf[73] = 0x7d; rxbuf[74] = 0x0d; rxbuf[75] = 0xe8;
|
|
rxbuf[76] = 0x03;
|
|
|
|
checkFCS(&rxbuf[75]);
|
|
do_put_rx_char(&rxbuf[75]);
|
|
}
|
|
|
|
#undef NCODEBUG
|
|
#ifdef NCODEBUG
|
|
NCO *osc = new NCO();
|
|
osc->init(1200, 0, PKT_SampleRate);
|
|
for(int i = 0; i < 30; i++) {
|
|
cmplx z = osc->cmplx_sample();
|
|
fprintf(stderr," %f %f\n", z.re, z.im);
|
|
}
|
|
|
|
osc->init(2200, 0, PKT_SampleRate);
|
|
for(int i = 0; i < 30; i++) {
|
|
cmplx z = osc->cmplx_sample();
|
|
fprintf(stderr," %f %f\n", z.re, z.im);
|
|
}
|
|
|
|
delete osc;
|
|
#endif // NCODEBUG
|
|
|
|
do_once = false;
|
|
|
|
#endif // NDEBUG
|
|
}
|
|
|
|
pkt::~pkt()
|
|
{
|
|
if (nco_lo) delete nco_lo;
|
|
if (nco_hi) delete nco_hi;
|
|
if (nco_mid) delete nco_mid;
|
|
|
|
if (idle_signal_buf) delete idle_signal_buf;
|
|
|
|
if (lo_signal_buf) delete lo_signal_buf;
|
|
if (hi_signal_buf) delete hi_signal_buf;
|
|
if (mid_signal_buf) delete mid_signal_buf;
|
|
|
|
if (signal_buf) delete signal_buf;
|
|
|
|
if (pipe) delete [] pipe;
|
|
if (dsppipe) delete [] dsppipe;
|
|
|
|
if (lo_tone) delete lo_tone;
|
|
if (hi_tone) delete hi_tone;
|
|
}
|
|
|
|
void pkt::set_pkt_modem_params(int i)
|
|
{
|
|
pkt_baud = BAUD[i];
|
|
pkt_shift = SHIFT[i];
|
|
pkt_nbits = BITS[i];
|
|
pkt_ctrfreq = CENTER[i];
|
|
|
|
/**************************************************************
|
|
SYMBOLLEN is the number of samples in one data bit (aka one symbol)
|
|
at the current baud rate
|
|
**************************************************************/
|
|
|
|
symbollen = (int) floor((double)PKT_SampleRate / pkt_baud + 0.5);
|
|
|
|
pkt_startlen = 4 * symbollen;
|
|
pkt_detectlen = PKT_DetectLen * symbollen;
|
|
pkt_syncdisplen = PKT_SyncDispLen * symbollen;
|
|
pkt_idlelen = PKT_IdleLen * symbollen;
|
|
pkt_startlen = 2 * pkt_idlelen;
|
|
|
|
fragmentsize = symbollen; // modem::fragmentsize -> see modem.h
|
|
|
|
// http://users.encs.concordia.ca/~n_goswam/advsg00/advsgtxt/c10digtx_b1_r00.htm
|
|
// BW = Baud + Shift * K .. K ::= 1.2
|
|
pkt_BW = pkt_baud + pkt_shift * 1.2;
|
|
}
|
|
|
|
void pkt::restart()
|
|
{
|
|
if (select_val != progdefaults.PKT_BAUD_SELECT) {
|
|
select_val = progdefaults.PKT_BAUD_SELECT;
|
|
set_pkt_modem_params(select_val);
|
|
}
|
|
|
|
snprintf(msg1, sizeof(msg1), "%4i / %-4.0f", pkt_baud, pkt_shift);
|
|
put_Status1(msg1);
|
|
put_MODEstatus(mode);
|
|
|
|
if (!nco_lo) nco_lo = new NCO();
|
|
if (!nco_hi) nco_hi = new NCO();
|
|
if (!nco_mid) nco_mid = new NCO();
|
|
|
|
nco_lo->init(pkt_ctrfreq-(pkt_shift/2), 0, PKT_SampleRate);
|
|
nco_hi->init(pkt_ctrfreq+(pkt_shift/2), 0, PKT_SampleRate);
|
|
nco_mid->init(pkt_ctrfreq, 0, PKT_SampleRate);
|
|
|
|
set_freq(pkt_ctrfreq);
|
|
|
|
set_bandwidth(pkt_shift); // waterfall tuning box
|
|
|
|
wf->redraw_marker();
|
|
|
|
if (!idle_signal_buf)
|
|
idle_signal_buf = new double [PKT_IdleLen*PKT_MaxSymbolLen];
|
|
|
|
idle_signal_pwr = idle_signal_buf_ptr = 0;
|
|
for (int i = 0; i < pkt_idlelen; i++)
|
|
idle_signal_buf[i] = 0;
|
|
|
|
if (!lo_signal_buf) lo_signal_buf = new cmplx [PKT_MaxSymbolLen];
|
|
if (!hi_signal_buf) hi_signal_buf = new cmplx [PKT_MaxSymbolLen];
|
|
if (!mid_signal_buf) mid_signal_buf = new cmplx [PKT_MaxSymbolLen];
|
|
|
|
if (!signal_buf)
|
|
signal_buf = new double [PKT_DetectLen*PKT_MaxSymbolLen];
|
|
|
|
signal_pwr = signal_buf_ptr = 0;
|
|
signal_gain = 1.0; // 5.0
|
|
|
|
for(int i = 0; i < pkt_detectlen; i++)
|
|
signal_buf[i] = 0;
|
|
|
|
lo_signal_energy = hi_signal_energy =
|
|
mid_signal_energy = cmplx(0, 0);
|
|
|
|
yt_avg = correlate_buf_ptr = 0;
|
|
|
|
for(int i = 0; i < symbollen; i++)
|
|
lo_signal_buf[i] = hi_signal_buf[i] =
|
|
mid_signal_buf[i] = cmplx(0, 0);
|
|
|
|
if (!pipe)
|
|
pipe = new double [PKT_SyncDispLen*PKT_MaxSymbolLen];
|
|
if (!dsppipe)
|
|
dsppipe = new double [PKT_SyncDispLen*PKT_MaxSymbolLen];
|
|
|
|
QIptr = pipeptr = 0;
|
|
|
|
// 1024 = 2 * SCBLOCKSIZE ( == MAX_ZLEN )
|
|
for (int i = 0; i < MAX_ZLEN; i++)
|
|
QI[i] = cmplx(0,0);
|
|
|
|
metric = 0.0;
|
|
signal_power = noise_power = power_ratio = snr_avg = 1;
|
|
|
|
clear_zdata = true;
|
|
}
|
|
|
|
pkt::pkt(trx_mode md)
|
|
{
|
|
cap |= CAP_REV;
|
|
cap &= ~CAP_AFC; // modem::cap
|
|
|
|
mode = md; // modem::mode
|
|
|
|
samplerate = PKT_SampleRate; // modem::samplerate
|
|
|
|
nco_lo = nco_hi = nco_mid = (NCO *)0;
|
|
|
|
idle_signal_buf = (double *)0;
|
|
|
|
lo_signal_buf = hi_signal_buf = mid_signal_buf = (cmplx *)0;
|
|
|
|
signal_buf = (double *)0;
|
|
|
|
pipe = dsppipe = (double *)0;
|
|
|
|
select_val = -1; // force modem param init
|
|
|
|
restart();
|
|
|
|
lo_tone = hi_tone = (NCO *)0;
|
|
tx_char_count = MAXOCTETS-3; // leave room for FCS and end-flag
|
|
|
|
// init_MicE_table();
|
|
// init_PHG_table();
|
|
}
|
|
|
|
void pkt::update_syncscope()
|
|
{
|
|
int j, len = pkt_syncdisplen;
|
|
|
|
for (int i = 0; i < len; i++) {
|
|
j = pipeptr - i;
|
|
if (j < 0) j += len;
|
|
dsppipe[i] = pipe[j];
|
|
}
|
|
set_scope(dsppipe, len, false);
|
|
}
|
|
|
|
void pkt::clear_syncscope()
|
|
{
|
|
set_scope(0, 0, false);
|
|
}
|
|
|
|
/*
|
|
cmplx pkt::mixer(cmplx in)
|
|
{
|
|
cmplx z;
|
|
z.re = cos(phaseacc);
|
|
z.im = sin(phaseacc);
|
|
z = z * in;
|
|
|
|
phaseacc -= TWOPI * frequency / samplerate;
|
|
if (phaseacc > M_PI)
|
|
phaseacc -= TWOPI;
|
|
else if (phaseacc < M_PI)
|
|
phaseacc += TWOPI;
|
|
|
|
return z;
|
|
}
|
|
*/
|
|
|
|
unsigned char pkt::bitreverse(unsigned char in, int n)
|
|
{
|
|
unsigned char out = 0;
|
|
|
|
for (int i = 0; i < n; i++)
|
|
out = (out << 1) | ((in >> i) & 1);
|
|
|
|
return out;
|
|
}
|
|
|
|
unsigned int pkt::computeFCS(unsigned char *head, unsigned char *tail)
|
|
{
|
|
unsigned char *c, b, tc;
|
|
|
|
// CRC AX.25 Generator mask
|
|
// == bitflip(poly(x**16 + x**12 + x**5 + 1))
|
|
// == bitflip(0x1021) == bitflip(0001000000100001)
|
|
// == 1000010000001000 == 0x8408
|
|
|
|
// http://www.ross.net/crc/download/crc_v3.txt
|
|
|
|
unsigned int fcsv = 0xFFFF;
|
|
|
|
// as in Ross except: shift right instead of left and reflected mask
|
|
// (mirror image because AX.25 is lsb->msb order)
|
|
|
|
for(c = head; c < tail; c++) {
|
|
|
|
fcsv ^= *c;
|
|
|
|
for(b = 0; b < 8; b++) {
|
|
|
|
if (fcsv & 0x0001)
|
|
fcsv = (fcsv >> 1) ^ 0x8408;
|
|
else
|
|
fcsv >>= 1;
|
|
|
|
fcsv &= 0xFFFF;
|
|
}
|
|
}
|
|
fcsv ^= 0xFFFF;
|
|
// fcsv is now (lo,hi)
|
|
|
|
tc = (fcsv & 0xFF00) >> 8;
|
|
fcsv = ((fcsv & 0x00FF) << 8) | tc;
|
|
// fcsv is now (hi,lo)
|
|
|
|
return fcsv;
|
|
}
|
|
|
|
// compare AX.25 Frame CheckSum (FCS) value to computed value
|
|
|
|
bool pkt::checkFCS(unsigned char *cp)
|
|
{
|
|
// HDLC frame must be at least one byte plus FCS
|
|
// AX.25 frame must be at least MINOCTETS plus FCS
|
|
if (cp < &rxbuf[MINOCTETS-1]) return false;
|
|
|
|
/* http://www.tapr.org/pub_ax25.html
|
|
|
|
transmitted AX.25 FCS is big endian (high byte first)
|
|
and bit-wise reversed for each byte versus the rest of the frame.
|
|
|
|
because we traverse each byte from lsb->msb
|
|
while receiving or transmitting
|
|
-- and --
|
|
the FCS is transmitted msb->lsb per specification, therefore:
|
|
|
|
>> the FCS must be in the buffer big endian
|
|
>> with a byte-wise bit-reversed ("reflected") order.
|
|
|
|
... really.
|
|
*/
|
|
/* with more investigating I learn:
|
|
|
|
AX.25 FCS is big endian and bit-wise lsb->msb like all the rest
|
|
of the octets.
|
|
|
|
so therefore: the FCS goes in the buffer big endian order.
|
|
|
|
... just that and no more.
|
|
*/
|
|
|
|
unsigned int fcsv_rcvd = (unsigned int)(cp[0] << 8) | cp[1];
|
|
unsigned int fcsv = computeFCS(&rxbuf[1], cp); // begin after leading flag
|
|
|
|
LOG_DEBUG("FCS computed %04X %s received %04X", fcsv,
|
|
(fcsv_rcvd == fcsv ? "==" : "<>"), fcsv_rcvd);
|
|
|
|
if (fcsv_rcvd == fcsv)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
inline void put_rx_const(const char s[])
|
|
{
|
|
unsigned char *p = (unsigned char *) &s[0];
|
|
for( ; *p; p++) put_rx_char(*p);
|
|
}
|
|
|
|
inline void put_rx_hex(unsigned char c)
|
|
{
|
|
char v[3];
|
|
|
|
snprintf(&v[0], 3, "%02x", c);
|
|
|
|
put_rx_char(v[0]);
|
|
put_rx_char(v[1]);
|
|
}
|
|
|
|
void pkt::expand_Cmp(unsigned char *cpI)
|
|
{
|
|
// APRS Spec 1.0.1 Chapter 9 - Compressed Position Report format
|
|
|
|
unsigned char *cp, tc, cc;
|
|
unsigned char Cmpbuf[96], *bp = &Cmpbuf[0];
|
|
unsigned char *tbp = bp;
|
|
|
|
double Lat, Lon, td;
|
|
bool sign;
|
|
|
|
cp = cpI+1; // skip past Symbol Table ID char
|
|
|
|
// Latitude as base91 number
|
|
tc = *cp++ - 33;
|
|
Lat = tc * 91 * 91 * 91; // fourth digit ==> x * 91^3
|
|
tc = *cp++ - 33;
|
|
Lat += tc * 91 * 91; // third digit ==> x * 91^2
|
|
tc = *cp++ - 33;
|
|
Lat += tc * 91; // second digit ==> x * 91^1
|
|
tc = *cp++ - 33;
|
|
Lat += tc; // units digit ==> x * 91^0
|
|
|
|
Lat = 90.0 - Lat / 380926.0; // - ==> S, + ==> N
|
|
|
|
// Longitude as base91 number
|
|
tc = *cp++ - 33;
|
|
Lon = tc * 91 * 91 * 91; // 4th digit
|
|
tc = *cp++ - 33;
|
|
Lon += tc * 91 * 91; // 3rd digit
|
|
tc = *cp++ - 33;
|
|
Lon += tc * 91; // 2nd digit
|
|
tc = *cp++ - 33;
|
|
Lon += tc; // units digit
|
|
|
|
Lon = -180.0 + Lon / 190463.0; // - ==> W, + ==> E
|
|
|
|
if (Lat < 0) {
|
|
sign = 1; // has sign (is negative)
|
|
Lat *= -1;
|
|
}
|
|
else sign = 0;
|
|
|
|
td = Lat - floor(Lat);
|
|
cc = snprintf((char *)bp, 3, "%2.f", (Lat - td)); // DD
|
|
bp += cc;
|
|
cc = snprintf((char *)bp, 6, "%05.2f", td*60); // MM.MM
|
|
bp += cc;
|
|
|
|
if (sign) *bp++ = 'S';
|
|
else *bp++ = 'N';
|
|
|
|
*bp++ = ' ';
|
|
|
|
if (Lon < 0) {
|
|
sign = 1;
|
|
Lon *= -1;
|
|
}
|
|
else sign = 0;
|
|
|
|
td = Lon - floor(Lon);
|
|
cc = snprintf((char *)bp, 4, "%03.f", (Lon - td)); // DDD
|
|
bp += cc;
|
|
cc = snprintf((char *)bp, 6, "%5.2f", td*60); // MM.MM
|
|
bp += cc;
|
|
|
|
if (sign) *bp++ = 'W';
|
|
else *bp++ = 'E';
|
|
|
|
cp += 1; // skip past Symbol Code char
|
|
|
|
if (*cp != ' ') { // still more
|
|
if ((*(cp + 2) & 0x18) == 0x10) { // NMEA source = GGA sentence
|
|
// compressed Altitude uses chars in the same range
|
|
// as CSE/SPD but the Compression Type ID takes precedence
|
|
// when it indicates the NMEA source is a GGA sentence.
|
|
// so check on this one first and CSE/SPD last.
|
|
double Altitude;
|
|
|
|
tc = *cp++ - 33; // 2nd digit
|
|
Altitude = tc * 91;
|
|
tc = *cp++ - 33;
|
|
Altitude += tc;
|
|
|
|
// this compressed posit field is not very useful as spec'ed,
|
|
// since it cannot produce a possible negative altitude!
|
|
// the NMEA GGA sentence is perfectly capable of providing
|
|
// a negative altitude value. Mic-E gets this right.
|
|
|
|
// Since the example given in the APRS 1.0.1 Spec uses a value
|
|
// in excess of 10000, this field should be re-spec'ed as a
|
|
// value in meters relative to 10km below mean sea level (just
|
|
// as done in Mic-E).
|
|
Altitude = pow(1.002, Altitude);
|
|
|
|
if (progdefaults.PKT_unitsSI)
|
|
cc = snprintf((char *)bp, 11, " %-.1fm", Altitude*0.3048);
|
|
else // units per Spec
|
|
cc = snprintf((char *)bp, 12, " %-.1fft", Altitude);
|
|
bp += cc;
|
|
}
|
|
else if (*cp == '{') { // pre-calculated radio range
|
|
double Range;
|
|
|
|
cp += 1; // skip past ID char
|
|
|
|
tc = *cp++ - 33; // range
|
|
Range = pow(1.08, (double)tc) * 2;
|
|
|
|
if (progdefaults.PKT_unitsSI)
|
|
cc = snprintf((char *)bp, 24, " Est. Range = %-.1fkm", Range*1.609);
|
|
else // units per Spec
|
|
cc = snprintf((char *)bp, 24, " Est. Range = %-.1fmi", Range);
|
|
bp += cc;
|
|
}
|
|
else if (*cp >= '!' && *cp <= 'z') { // compressed CSE/SPD
|
|
int Speed;
|
|
|
|
tc = *cp++ - 33; // course
|
|
cc = snprintf((char *)bp, 8, " %03ddeg", tc*4);
|
|
bp += cc;
|
|
|
|
tc = *cp++ - 33; // speed
|
|
Speed = (int)floor(pow(1.08, (double)tc) - 1); // 1.08^tc - 1 kts
|
|
|
|
if (progdefaults.PKT_unitsSI)
|
|
cc = snprintf((char *)bp, 8, " %03dkph", (int)floor(Speed*1.852+0.5));
|
|
else if (progdefaults.PKT_unitsEnglish)
|
|
cc = snprintf((char *)bp, 8, " %03dmph", (int)floor(Speed*1.151+0.5));
|
|
else // units per Spec
|
|
cc = snprintf((char *)bp, 8, " %03dkts", Speed);
|
|
bp += cc;
|
|
}
|
|
}
|
|
if (progdefaults.PKT_RXTimestamp)
|
|
put_rx_const(" ");
|
|
|
|
put_rx_const(" [Cmp] ");
|
|
|
|
for(; tbp < bp; tbp++) put_rx_char(*tbp);
|
|
|
|
put_rx_char('\r');
|
|
|
|
if (debug::level >= debug::VERBOSE_LEVEL) {
|
|
cp = cpI+12; // skip to Compression Type ID char
|
|
|
|
if (*(cp - 2) != ' ') { // Cmp Type ID is valid
|
|
tbp = bp = &Cmpbuf[0];
|
|
|
|
tc = *cp - 33; // T
|
|
|
|
cc = snprintf((char *)bp, 4, "%02x:", tc);
|
|
bp += cc;
|
|
|
|
strcpy((char *)bp, " GPS Fix = ");
|
|
bp += 11;
|
|
|
|
if ((tc & 0x20) == 0x20) {
|
|
strcpy((char *)bp, "old");
|
|
bp += 3;
|
|
}
|
|
else {
|
|
strcpy((char *)bp, "current");
|
|
bp += 7;
|
|
}
|
|
|
|
strcpy((char *)bp, ", NMEA Source = ");
|
|
bp += 16;
|
|
|
|
switch (tc & 0x18) {
|
|
case 0x00:
|
|
strcpy((char *)bp, "other");
|
|
bp += 5;
|
|
break;
|
|
case 0x08:
|
|
strcpy((char *)bp, "GLL");
|
|
bp += 3;
|
|
break;
|
|
case 0x10:
|
|
strcpy((char *)bp, "GGA");
|
|
bp += 3;
|
|
break;
|
|
case 0x18:
|
|
strcpy((char *)bp, "RMC");
|
|
bp += 3;
|
|
break;
|
|
default:
|
|
strcpy((char *)bp, "\?\?");
|
|
bp += 2;
|
|
break;
|
|
}
|
|
|
|
strcpy((char *)bp, ", Cmp Origin = ");
|
|
bp += 15;
|
|
|
|
switch (tc & 0x07) {
|
|
case 0x00:
|
|
strcpy((char *)bp, "Compressed");
|
|
bp += 10;
|
|
break;
|
|
case 0x01:
|
|
strcpy((char *)bp, "TNC BText");
|
|
bp += 9;
|
|
break;
|
|
case 0x02:
|
|
strcpy((char *)bp, "Software (DOS/Mac/Win/+SA)");
|
|
bp += 26;
|
|
break;
|
|
case 0x03:
|
|
strcpy((char *)bp, "[tbd]");
|
|
bp += 5;
|
|
break;
|
|
case 0x04:
|
|
strcpy((char *)bp, "KPC3");
|
|
bp += 4;
|
|
break;
|
|
case 0x05:
|
|
strcpy((char *)bp, "Pico");
|
|
bp += 4;
|
|
break;
|
|
case 0x06:
|
|
strcpy((char *)bp, "Other tracker [tbd]");
|
|
bp += 19;
|
|
break;
|
|
case 0x07:
|
|
strcpy((char *)bp, "Digipeater conversion");
|
|
bp += 21;
|
|
break;
|
|
default:
|
|
strcpy((char *)bp, "\?\?");
|
|
bp += 2;
|
|
break;
|
|
}
|
|
|
|
if (progdefaults.PKT_RXTimestamp)
|
|
put_rx_const(" ");
|
|
|
|
put_rx_const(" [CmpType] ");
|
|
|
|
for(; tbp < bp; tbp++) put_rx_char(*tbp);
|
|
|
|
put_rx_char('\r');
|
|
}
|
|
}
|
|
}
|
|
|
|
void pkt::expand_PHG(unsigned char *cpI)
|
|
{
|
|
// APRS Spec 1.0.1 Chapter 6 - Time and Position format
|
|
// APRS Spec 1.0.1 Chapter 7 - PHG Extension format
|
|
|
|
bool hasPHG = false;
|
|
unsigned char *cp, tc, cc;
|
|
unsigned char PHGbuf[64], *bp = &PHGbuf[0];
|
|
unsigned char *tbp = bp;
|
|
|
|
switch (*cpI) {
|
|
case '!':
|
|
case '=': // simplest posits
|
|
|
|
cp = cpI+1; // skip past posit ID char
|
|
|
|
if (*cp != '/') { // posit not compressed
|
|
cp += 19; // skip past posit data
|
|
}
|
|
else { // posit is compressed
|
|
cp += 1; // skip past compressed posit ID char
|
|
cp += 12; // skip past compressed posit data
|
|
}
|
|
|
|
if (strncmp((const char *)cp, "PHG", 3) == 0) { // strings match
|
|
unsigned char ndigits;
|
|
int power, height;
|
|
double gain, range;
|
|
|
|
cp += 3; // skip past Data Extension ID chars
|
|
|
|
// get span of chars in cp which are only digits
|
|
ndigits = strspn((const char *)cp, "0123456789");
|
|
|
|
switch (ndigits) {
|
|
//case 1: H might be larger than '9'. code below will work.
|
|
// must also check that P.GD are all '0'-'9'
|
|
//break;
|
|
case 4: // APRS Spec 1.0.1 Chapter 7 page 28
|
|
case 5: // PHGR proposed for APRS Spec 1.2
|
|
hasPHG = true;
|
|
|
|
tc = *cp++ - '0'; // P
|
|
power = tc * tc; // tc^2
|
|
cc = snprintf((char *)bp, 5, "%dW,", power);
|
|
bp += cc;
|
|
|
|
tc = *cp++ - '0'; // H
|
|
*bp++ = ' ';
|
|
if (tc < 30) { // constrain Height to signed 32bit value
|
|
height = 10 * (1 << tc); // 10 * 2^tc
|
|
|
|
if (progdefaults.PKT_unitsSI)
|
|
cc = snprintf((char *)bp, 11, "%dm", (int)floor(height*0.3048+0.5));
|
|
else // units per Spec
|
|
cc = snprintf((char *)bp, 12, "%dft", height);
|
|
bp += cc;
|
|
}
|
|
else {
|
|
height = 0;
|
|
strcpy((char *)bp, "-\?\?-");
|
|
bp += 4;
|
|
}
|
|
strcpy((char *)bp, " HAAT,");
|
|
bp += 6;
|
|
|
|
tc = *cp++; // G
|
|
gain = pow(10, ((double)(tc - '0') / 10));
|
|
cc = snprintf((char *)bp, 6, " %cdB,", tc);
|
|
bp += cc;
|
|
|
|
tc = *cp++ - '0'; // D
|
|
*bp++ = ' ';
|
|
if (tc < 9) {
|
|
strcpy((char *)bp, PHG_table[tc].s);
|
|
bp += PHG_table[tc].l;
|
|
}
|
|
else {
|
|
strcpy((char *)bp, "-\?\?-");
|
|
bp += 4;
|
|
}
|
|
*bp++ = ',';
|
|
|
|
range = sqrt(2 * height * sqrt(((double)power / 10) * (gain / 2)));
|
|
if (progdefaults.PKT_unitsSI)
|
|
cc = snprintf((char *)bp, 24, " Est. Range = %-.1fkm", range*1.609);
|
|
else // units per Spec
|
|
cc = snprintf((char *)bp, 24, " Est. Range = %-.1fmi", range);
|
|
bp += cc;
|
|
|
|
if (ndigits == 5 && *(cp + 1) == '/') {
|
|
// PHGR: http://www.aprs.org/aprs12/probes.txt
|
|
// '1'-'9' and 'A'-'Z' are actually permissible.
|
|
// does anyone send 10 ('A') or more beacons per hour?
|
|
strcpy((char *)bp, ", ");
|
|
bp += 2;
|
|
|
|
tc = *cp++; // R
|
|
cc = snprintf((char *)bp, 14, "%c beacons/hr", tc);
|
|
bp += cc;
|
|
}
|
|
break;
|
|
default: // switch(ndigits)
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
default: // switch(*cpI)
|
|
break;
|
|
}
|
|
|
|
if (hasPHG) {
|
|
if (progdefaults.PKT_RXTimestamp)
|
|
put_rx_const(" ");
|
|
|
|
put_rx_const(" [PHG] ");
|
|
|
|
for(; tbp < bp; tbp++) put_rx_char(*tbp);
|
|
|
|
put_rx_char('\r');
|
|
}
|
|
}
|
|
|
|
void pkt::expand_MicE(unsigned char *cpI, unsigned char *cpE)
|
|
{
|
|
// APRS Spec 1.0.1 Chapter 10 - Mic-E Data format
|
|
|
|
bool isMicE = true;
|
|
bool msgstd = false, msgcustom = false;
|
|
|
|
// decoding starts at first AX.25 dest addr
|
|
unsigned char *cp = &rxbuf[1], tc, cc;
|
|
unsigned char MicEbuf[64], *bp = &MicEbuf[0];
|
|
unsigned char *tbp = bp;
|
|
unsigned int msgABC = 0;
|
|
PKT_MicE_field Lat = North, LonOffset = Zero, Lon = West;
|
|
|
|
for (int i = 0; i < 3; i++) {
|
|
// remember: AX.25 dest addr chars are shifted left by one
|
|
tc = *cp++ >> 1;
|
|
|
|
switch (tc & 0xF0) {
|
|
case 0x30: // MicE_table[0]
|
|
cc = tc - '0';
|
|
if (cc < 10) {
|
|
*bp++ = MicE_table[0][cc][0];
|
|
}
|
|
else isMicE = false;
|
|
break;
|
|
case 0x40: // MicE_table[1]
|
|
cc = tc - 'A';
|
|
if (cc < 12) {
|
|
bool t = MicE_table[1][cc][1]-'0';
|
|
if (t) {
|
|
msgABC |= t << (2-i);
|
|
msgcustom = true;
|
|
}
|
|
else msgABC &= ~(1 << (2-i));
|
|
*bp++ = MicE_table[1][cc][0];
|
|
}
|
|
else isMicE = false;
|
|
break;
|
|
case 0x50: // MicE_table[2]
|
|
cc = tc - 'P';
|
|
if (cc < 11) {
|
|
msgABC |= (MicE_table[2][cc][1]-'0') << (2-i);
|
|
msgstd = true;
|
|
*bp++ = MicE_table[2][cc][0];
|
|
}
|
|
else isMicE = false;
|
|
break;
|
|
default: // Invalid
|
|
isMicE = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (int i = 3; i < 6; i++) {
|
|
// remember: AX.25 dest addr chars are shifted left by one
|
|
tc = *cp++ >> 1;
|
|
|
|
switch (i) {
|
|
case 3:
|
|
switch (tc & 0xF0) {
|
|
case 0x30: // MicE_table[0]
|
|
cc = tc - '0';
|
|
if (cc < 10) {
|
|
Lat = MicE_table[0][cc][2];
|
|
*bp++ = MicE_table[0][cc][0];
|
|
}
|
|
else isMicE = false;
|
|
break;
|
|
case 0x40: // MicE_table[1]
|
|
cc = tc - 'A';
|
|
if (cc == 11) {
|
|
Lat = MicE_table[1][cc][2];
|
|
*bp++ = MicE_table[1][cc][0];
|
|
}
|
|
else isMicE = false;
|
|
break;
|
|
case 0x50: // MicE_table[2]
|
|
cc = tc - 'P';
|
|
if (cc < 11) {
|
|
Lat = MicE_table[2][cc][2];
|
|
*bp++ = MicE_table[2][cc][0];
|
|
}
|
|
else isMicE = false;
|
|
break;
|
|
default: // Invalid
|
|
isMicE = false;
|
|
break;
|
|
}
|
|
break;
|
|
case 4:
|
|
switch (tc & 0xF0) {
|
|
case 0x30: // MicE_table[0]
|
|
cc = tc - '0';
|
|
if (cc < 10) {
|
|
LonOffset = MicE_table[0][cc][3];
|
|
*bp++ = MicE_table[0][cc][0];
|
|
}
|
|
else isMicE = false;
|
|
break;
|
|
case 0x40: // MicE_table[1]
|
|
cc = tc - 'A';
|
|
if (cc == 11) {
|
|
LonOffset = MicE_table[1][cc][3];
|
|
*bp++ = MicE_table[1][cc][0];
|
|
}
|
|
else isMicE = false;
|
|
break;
|
|
case 0x50: // MicE_table[2]
|
|
cc = tc - 'P';
|
|
if (cc < 11) {
|
|
LonOffset = MicE_table[2][cc][3];
|
|
*bp++ = MicE_table[2][cc][0];
|
|
}
|
|
else isMicE = false;
|
|
break;
|
|
default: // Invalid
|
|
isMicE = false;
|
|
break;
|
|
}
|
|
break;
|
|
case 5:
|
|
switch (tc & 0xF0) {
|
|
case 0x30: // MicE_table[0]
|
|
cc = tc - '0';
|
|
if (cc < 10) {
|
|
Lon = MicE_table[0][cc][4];
|
|
*bp++ = MicE_table[0][cc][0];
|
|
}
|
|
else isMicE = false;
|
|
break;
|
|
case 0x40: // MicE_table[1]
|
|
cc = tc - 'A';
|
|
if (cc == 11) {
|
|
Lon = MicE_table[1][cc][4];
|
|
*bp++ = MicE_table[1][cc][0];
|
|
}
|
|
else isMicE = false;
|
|
break;
|
|
case 0x50: // MicE_table[2]
|
|
cc = tc - 'P';
|
|
if (cc < 11) {
|
|
Lon = MicE_table[2][cc][4];
|
|
*bp++ = MicE_table[2][cc][0];
|
|
}
|
|
else isMicE = false;
|
|
break;
|
|
default: // Invalid
|
|
isMicE = false;
|
|
break;
|
|
}
|
|
break;
|
|
default: // Invalid
|
|
isMicE = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (isMicE) {
|
|
int Speed = 0, Course = 0;
|
|
|
|
if (progdefaults.PKT_RXTimestamp)
|
|
put_rx_const(" ");
|
|
|
|
put_rx_const(" [Mic-E] ");
|
|
|
|
if (msgstd && msgcustom)
|
|
put_rx_const("Unknown? ");
|
|
else if (msgcustom) {
|
|
put_rx_const("Custom-");
|
|
put_rx_char((7 - msgABC)+'0');
|
|
put_rx_const(". ");
|
|
}
|
|
else {
|
|
switch (msgABC) { // APRS Spec 1.0.1 Chapter 10 page 45
|
|
case 0:
|
|
put_rx_const("Emergency");
|
|
break;
|
|
case 1:
|
|
put_rx_const("Priority");
|
|
break;
|
|
case 2:
|
|
put_rx_const("Special");
|
|
break;
|
|
case 3:
|
|
put_rx_const("Committed");
|
|
break;
|
|
case 4:
|
|
put_rx_const("Returning");
|
|
break;
|
|
case 5:
|
|
put_rx_const("In Service");
|
|
break;
|
|
case 6:
|
|
put_rx_const("En Route");
|
|
break;
|
|
case 7:
|
|
put_rx_const("Off Duty");
|
|
break;
|
|
default:
|
|
put_rx_const("-\?\?-");
|
|
break;
|
|
}
|
|
if (msgABC) put_rx_char('.');
|
|
else put_rx_char('!'); // Emergency!
|
|
|
|
put_rx_char(' ');
|
|
}
|
|
|
|
for (; tbp < bp; tbp++) {
|
|
put_rx_char(*tbp);
|
|
if (tbp == (bp - 3)) put_rx_char('.');
|
|
}
|
|
|
|
if (Lat == North) put_rx_char('N');
|
|
else if (Lat == South) put_rx_char('S');
|
|
else put_rx_char('\?');
|
|
|
|
put_rx_char(' ');
|
|
|
|
cp = cpI+1; // one past the Data Type ID char
|
|
|
|
// decode Lon degrees - APRS Spec 1.0.1 Chapter 10 page 48
|
|
tc = *cp++ - 28;
|
|
if (LonOffset == P100) tc += 100;
|
|
if (tc > 179 && tc < 190) tc -= 80;
|
|
else if (tc > 189 && tc < 200) tc -= 190;
|
|
|
|
cc = snprintf((char *)bp, 4, "%03d", tc);
|
|
bp += cc;
|
|
|
|
// decode Lon minutes
|
|
tc = *cp++ - 28;
|
|
if (tc > 59) tc -= 60;
|
|
|
|
cc = snprintf((char *)bp, 3, "%02d", tc);
|
|
bp += cc;
|
|
|
|
// decode Lon hundredths of a minute
|
|
tc = *cp++ - 28;
|
|
|
|
cc = snprintf((char *)bp, 3, "%02d", tc);
|
|
bp += cc;
|
|
|
|
for (; tbp < bp; tbp++) {
|
|
put_rx_char(*tbp);
|
|
if (tbp == (bp - 3)) put_rx_char('.');
|
|
}
|
|
|
|
if (Lon == East) put_rx_char('E');
|
|
else if (Lon == West) put_rx_char('W');
|
|
else put_rx_char('\?');
|
|
|
|
// decode Speed and Course - APRS Spec 1.0.1 Chapter 10 page 52
|
|
tc = *cp++ - 28; // speed: hundreds and tens
|
|
|
|
if (tc > 79) tc -= 80;
|
|
Speed = tc * 10;
|
|
|
|
tc = *cp++ - 28; // speed: units and course: hundreds
|
|
|
|
Course = (tc % 10); // remainder from dividing by 10
|
|
tc -= Course; tc /= 10; // tc is now quotient from dividing by 10
|
|
Speed += tc;
|
|
|
|
if (Course > 3) Course -= 4;
|
|
Course *= 100;
|
|
|
|
tc = *cp++ - 28; // course: tens and units
|
|
|
|
Course += tc;
|
|
|
|
if (progdefaults.PKT_unitsSI)
|
|
cc = snprintf((char *)bp, 8, " %03dkph", (int)floor(Speed*1.852+0.5));
|
|
else if (progdefaults.PKT_unitsEnglish)
|
|
cc = snprintf((char *)bp, 8, " %03dmph", (int)floor(Speed*1.151+0.5));
|
|
else // units per Spec
|
|
cc = snprintf((char *)bp, 8, " %03dkts", Speed);
|
|
bp += cc;
|
|
|
|
cc = snprintf((char *)bp, 8, " %03ddeg", Course);
|
|
bp += cc;
|
|
|
|
for (; tbp < bp; tbp++) {
|
|
put_rx_char(*tbp);
|
|
}
|
|
|
|
cp += 2; // skip past Symbol and Symbol Table ID chars
|
|
|
|
if (cp <= cpE) { // still more
|
|
|
|
if (*cp == '>') {
|
|
cp += 1;
|
|
put_rx_const(" TH-D7");
|
|
}
|
|
else if (*cp == ']' && *cpE == '=') {
|
|
cp += 1;
|
|
cpE -= 1;
|
|
put_rx_const(" TM-D710");
|
|
}
|
|
else if (*cp == ']') {
|
|
cp += 1;
|
|
put_rx_const(" TM-D700");
|
|
}
|
|
else if (*cp == '\'' && *(cpE - 1) == '|' && *cpE == '3') {
|
|
cp += 1;
|
|
cpE -= 2;
|
|
put_rx_const(" TT3");
|
|
}
|
|
else if (*cp == '\'' && *(cpE - 1) == '|' && *cpE == '4') {
|
|
cp += 1;
|
|
cpE -= 2;
|
|
put_rx_const(" TT4");
|
|
}
|
|
else if (*cp == '`' && *(cpE - 1) == '_' && *cpE == ' ') {
|
|
cp += 1;
|
|
cpE -= 2;
|
|
put_rx_const(" VX-8");
|
|
}
|
|
else if (*cp == '`' && *(cpE - 1) == '_' && *cpE == '#') {
|
|
cp += 1;
|
|
cpE -= 2;
|
|
put_rx_const(" VX-8D/G"); // VX-8G for certain. guessing.
|
|
}
|
|
else if (*cp == '`' && *(cpE - 1) == '_' && *cpE == '\"') {
|
|
cp += 1;
|
|
cpE -= 2;
|
|
put_rx_const(" FTM-350");
|
|
}
|
|
else if ((*cp == '\'' || *cp == '`') && *(cp + 4) == '}') {
|
|
cp += 1;
|
|
// tracker? rig? ID codes are somewhat ad hoc.
|
|
put_rx_const(" MFR\?");
|
|
}
|
|
|
|
if (cp < cpE) {
|
|
if (*(cp + 3) == '}') { // station altitude as base91 number
|
|
int Altitude = 0;
|
|
|
|
tc = *cp++ - 33; // third digit ==> x * 91^2
|
|
Altitude = tc * 91 * 91;
|
|
tc = *cp++ - 33; // second digit ==> x * 91^1 ==> x * 91
|
|
Altitude += tc * 91;
|
|
tc = *cp++ - 33; // unit digit ==> x * 91^0 ==> x * 1
|
|
Altitude += tc;
|
|
|
|
Altitude -= 10000; // remove offset from datum
|
|
|
|
*bp++ = ' ';
|
|
if (Altitude >= 0) *bp++ = '+';
|
|
|
|
if (progdefaults.PKT_unitsEnglish)
|
|
cc = snprintf((char *)bp, 12, "%dft", (int)floor(Altitude*3.281+0.5));
|
|
else // units per Spec
|
|
cc = snprintf((char *)bp, 11, "%dm", Altitude);
|
|
bp += cc;
|
|
|
|
for (; tbp < bp; tbp++) {
|
|
put_rx_char(*tbp);
|
|
}
|
|
|
|
cp += 1; // skip past '}'
|
|
}
|
|
}
|
|
|
|
if (cp < cpE) put_rx_char(' ');
|
|
|
|
for (; cp <= cpE; cp++) put_rx_char(*cp);
|
|
}
|
|
|
|
put_rx_char('\r');
|
|
}
|
|
}
|
|
|
|
void pkt::do_put_rx_char(unsigned char *cp)
|
|
{
|
|
int i, j;
|
|
unsigned char c;
|
|
bool isMicE = false;
|
|
unsigned char *cpInfo;
|
|
|
|
for (i = 8; i < 14; i++) { // src callsign is second in AX.25 frame
|
|
c = rxbuf[i] >> 1;
|
|
if (c != ' ') put_rx_char(c); // skip past padding (if any)
|
|
}
|
|
|
|
// bit 7 = command/response bit
|
|
// bits 6,5 = 1
|
|
// bits 4-1 = src SSID
|
|
// bit 0 = last callsign flag
|
|
c = (rxbuf[14] & 0x7f) >> 1;
|
|
|
|
if (c > 0x30) {
|
|
put_rx_char('-');
|
|
if (c < 0x3a)
|
|
put_rx_char(c);
|
|
else {
|
|
put_rx_char('1');
|
|
put_rx_char(c-0x0a);
|
|
}
|
|
}
|
|
|
|
put_rx_char('>');
|
|
|
|
for (i = 1; i < 7; i++) { // dest callsign is first in AX.25 frame
|
|
c = rxbuf[i] >> 1;
|
|
if (c != ' ') put_rx_char(c);
|
|
}
|
|
|
|
c = (rxbuf[7] & 0x7f) >> 1;
|
|
if (c > 0x30) {
|
|
put_rx_char('-');
|
|
if (c < 0x3a)
|
|
put_rx_char(c);
|
|
else {
|
|
put_rx_char('1');
|
|
put_rx_char(c-0x0a);
|
|
}
|
|
}
|
|
|
|
j=8;
|
|
if ((rxbuf[14] & 0x01) != 1) { // check last callsign flag
|
|
do {
|
|
put_rx_char(',');
|
|
|
|
j += 7;
|
|
for (i = j; i < (j+6); i++) {
|
|
c = rxbuf[i] >> 1;
|
|
if (c != ' ') put_rx_char(c);
|
|
}
|
|
|
|
c = (rxbuf[j+6] & 0x7f) >> 1;
|
|
if (c > 0x30) {
|
|
put_rx_char('-');
|
|
if (c < 0x3a)
|
|
put_rx_char(c);
|
|
else {
|
|
put_rx_char('1');
|
|
put_rx_char(c-0x0a);
|
|
}
|
|
}
|
|
|
|
} while ((rxbuf[j+6] & 0x01) != 1);
|
|
|
|
if (rxbuf[j+6] & 0x80) // packet gets no more hops
|
|
put_rx_char('*');
|
|
}
|
|
|
|
if (debug::level < debug::VERBOSE_LEVEL) {
|
|
// skip past CTRL and PID to INFO bytes when I_FRAME
|
|
// puts buffer pointer in FCS when U_FRAME and S_FRAME
|
|
// (save CTRL byte for possible MicE decoding)
|
|
j += 7;
|
|
c = rxbuf[j];
|
|
j += 2;
|
|
}
|
|
else { // show more frame info when .ge. VERBOSE debug level
|
|
j += 7;
|
|
put_rx_char(';');
|
|
|
|
c = rxbuf[j]; // CTRL present in all frames
|
|
|
|
if ((c & 0x01) == 0) { // I_FRAME
|
|
unsigned char p = rxbuf[j+1]; // PID present only in I_FRAME
|
|
|
|
if (debug::level == debug::DEBUG_LEVEL) {
|
|
put_rx_hex(c);
|
|
put_rx_char(' ');
|
|
put_rx_hex(p);
|
|
put_rx_char(';');
|
|
}
|
|
|
|
put_rx_const("I/");
|
|
|
|
put_rx_hex( (c & 0xE0) >> 5 ); // AX.25 v2.2 para 2.3.2.1
|
|
if (c & 0x10) put_rx_char('*'); // P/F bit
|
|
else put_rx_char('.');
|
|
put_rx_hex( (c & 0x0E) >> 1 );
|
|
|
|
put_rx_char('/');
|
|
|
|
switch (p) { // AX.25 v2.2 para 2.2.4
|
|
case 0x01:
|
|
put_rx_const("X.25PLP");
|
|
break;
|
|
case 0x06:
|
|
put_rx_const("C-TCPIP");
|
|
break;
|
|
case 0x07:
|
|
put_rx_const("U-TCPIP");
|
|
break;
|
|
case 0x08:
|
|
put_rx_const("FRAG");
|
|
break;
|
|
case 0xC3:
|
|
put_rx_const("TEXNET");
|
|
break;
|
|
case 0xC4:
|
|
put_rx_const("LQP");
|
|
break;
|
|
case 0xCA:
|
|
put_rx_const("ATALK");
|
|
break;
|
|
case 0xCB:
|
|
put_rx_const("ATALK-ARP");
|
|
break;
|
|
case 0xCC:
|
|
put_rx_const("ARPA-IP");
|
|
break;
|
|
case 0xCD:
|
|
put_rx_const("ARPA-AR");
|
|
break;
|
|
case 0xCE:
|
|
put_rx_const("FLEXNET");
|
|
break;
|
|
case 0xCF:
|
|
put_rx_const("NET/ROM");
|
|
break;
|
|
case 0xF0:
|
|
put_rx_const("NO-L3");
|
|
break;
|
|
|
|
case 0xFF:
|
|
put_rx_const("L3ESC=");
|
|
put_rx_hex(rxbuf[++j]);
|
|
break;
|
|
|
|
default:
|
|
if ((p & 0x30) == 0x10) put_rx_const("L3V1");
|
|
else if ((p & 0x30) == 0x20) put_rx_const("L3V2");
|
|
else put_rx_const("L3-RSVD");
|
|
|
|
put_rx_char('=');
|
|
put_rx_hex(p);
|
|
break;
|
|
}
|
|
}
|
|
else if ((c & 0x03) == 0x01) { // S_FRAME
|
|
|
|
if (debug::level == debug::DEBUG_LEVEL) {
|
|
put_rx_hex(c);
|
|
put_rx_char(';');
|
|
}
|
|
|
|
put_rx_const("S/");
|
|
|
|
put_rx_hex( (c & 0xE0) >> 5 );
|
|
if (c & 0x10) put_rx_char('*');
|
|
else put_rx_char('.');
|
|
put_rx_char('/');
|
|
|
|
switch (c & 0x0C) { // AX.25 v2.2 para 2.3.4.2
|
|
case 0x00:
|
|
put_rx_const("RR");
|
|
break;
|
|
case 0x04:
|
|
put_rx_const("RNR");
|
|
break;
|
|
case 0x08:
|
|
put_rx_const("REJ");
|
|
break;
|
|
case 0x0C:
|
|
default:
|
|
put_rx_const("UNK");
|
|
break;
|
|
}
|
|
}
|
|
else if ((c & 0x03) == 0x03) { // U_FRAME
|
|
|
|
if (debug::level == debug::DEBUG_LEVEL) {
|
|
put_rx_hex(c);
|
|
put_rx_char(';');
|
|
}
|
|
|
|
put_rx_char('U');
|
|
|
|
if (c & 0x10) put_rx_char('*');
|
|
else put_rx_char('.');
|
|
|
|
switch (c & 0xEC) { // AX.25 v2.2 para 2.3.4.3
|
|
case 0x00:
|
|
put_rx_const("UI");
|
|
break;
|
|
case 0x0E:
|
|
put_rx_const("DM");
|
|
break;
|
|
case 0x1E:
|
|
put_rx_const("SABM");
|
|
break;
|
|
case 0x20:
|
|
put_rx_const("DISC");
|
|
break;
|
|
case 0x30:
|
|
put_rx_const("UA");
|
|
break;
|
|
case 0x81:
|
|
put_rx_const("FRMR");
|
|
break;
|
|
default:
|
|
put_rx_const("UNK");
|
|
break;
|
|
}
|
|
}
|
|
j+=2;
|
|
}
|
|
put_rx_char(':');
|
|
|
|
// ptr to first info field char
|
|
cpInfo = &rxbuf[j];
|
|
|
|
if ((c & 0x03) == 0x03 && (c & 0xEC) == 0x00
|
|
&& (*cpInfo == '\'' || *cpInfo == '`'
|
|
|| *cpInfo == 0x1C || *cpInfo == 0x1D)
|
|
&& (cp - cpInfo) > 7) {
|
|
/*
|
|
Mic-E must have at least 8 info chars + Data Type ID char
|
|
cp - (cpInfo - 1) > 8 ==> cp - cpInfo > 7
|
|
*/
|
|
// this is very probably a Mic-E encoded packet
|
|
isMicE = true;
|
|
}
|
|
|
|
// offset between last info char (not FCS) and bufhead
|
|
i = (cp - &rxbuf[0]); // (cp - &rxbuf[1]) + 1 ==> (cp - &rxbuf[0])
|
|
|
|
while (j < i) put_rx_char(rxbuf[j++]);
|
|
|
|
if (*(cp-1) != '\r')
|
|
put_rx_char('\r'); // <cr> only for packets not ending with <cr>
|
|
|
|
// cp points to FCS, so (cp-X) is last info field char
|
|
if ((progdefaults.PKT_expandMicE || debug::level >= debug::VERBOSE_LEVEL)
|
|
&& isMicE)
|
|
expand_MicE(cpInfo, (*(cp-1) == '\r' ? cp-2 : cp-1));
|
|
|
|
// need to deal with posits having timestamps ('/' and '@' leading char)
|
|
if (*cpInfo == '!' || *cpInfo == '=') {
|
|
if ((progdefaults.PKT_expandCmp || debug::level >= debug::VERBOSE_LEVEL)
|
|
&& (*(cpInfo + 1) == '/' || *(cpInfo + 1) == '\\'))
|
|
// compressed posit
|
|
expand_Cmp(cpInfo+1);
|
|
|
|
if (progdefaults.PKT_expandPHG
|
|
|| debug::level >= debug::VERBOSE_LEVEL) // look for PHG data
|
|
expand_PHG(cpInfo);
|
|
}
|
|
|
|
if (*(cp-1) == '\r')
|
|
put_rx_char('\r'); // for packets ending with <cr>: show it on-screen
|
|
}
|
|
|
|
|
|
void pkt::rx(bool bit)
|
|
{
|
|
static unsigned char c = 0, bcounter = 0;
|
|
|
|
c >>= 1;
|
|
++bcounter;
|
|
|
|
if (bit == false) {
|
|
c &= ~(1 << (pkt_nbits-1)); // bits are sent lsb first
|
|
|
|
if (seq_ones == 6) { // flag byte found
|
|
bcounter = 0;
|
|
|
|
if (cbuf >= &rxbuf[MINOCTETS]) { // flag at end of frame
|
|
|
|
*cbuf = PKT_Flag; // == 0x7e
|
|
|
|
if (debug::level == debug::DEBUG_LEVEL)
|
|
fprintf(stderr,"7e\n");
|
|
|
|
// monitor Metric and Squelch Level
|
|
if ( !progStatus.sqlonoff ||
|
|
metric >= progStatus.sldrSquelchValue ) { // lazy eval
|
|
/*
|
|
// check FCS at end of frame
|
|
// if FCS is OK - packet is in rxbuffer,
|
|
// put_rx_char() for each byte in rxbuffer
|
|
*/
|
|
if (checkFCS((cbuf - 2)) == true) {
|
|
if (progdefaults.PKT_RXTimestamp) {
|
|
unsigned char ts[16], *tc = &ts[0];
|
|
time_t t = time(NULL);
|
|
struct tm stm;
|
|
|
|
(void)gmtime_r(&t, &stm);
|
|
snprintf((char *)ts, sizeof(ts),
|
|
"[%02d:%02d:%02d] ",
|
|
stm.tm_hour, stm.tm_min, stm.tm_sec);
|
|
|
|
while (*tc) put_rx_char(*tc++);
|
|
}
|
|
do_put_rx_char((cbuf - 2));
|
|
}
|
|
}
|
|
|
|
cbuf = &rxbuf[0]; // reset after first end frame flag
|
|
}
|
|
else {
|
|
// packet too short if cbuf < &rxbuf[MINOCTETS]
|
|
|
|
// put only one beginning flag into buffer
|
|
rxbuf[0] = PKT_Flag;
|
|
cbuf = &rxbuf[1];
|
|
|
|
if (debug::level == debug::DEBUG_LEVEL)
|
|
fprintf(stderr,"7e ");
|
|
}
|
|
}
|
|
else if (seq_ones == 5) { // need bit un-stuffing
|
|
c <<= 1; // shift c back to skip stuffed bit
|
|
--bcounter;
|
|
}
|
|
|
|
seq_ones = 0;
|
|
}
|
|
else {
|
|
c |= (1 << (pkt_nbits-1)); // bits are sent lsb first
|
|
++seq_ones;
|
|
|
|
//if (seq_ones > 6) { // something is wrong
|
|
//}
|
|
}
|
|
|
|
if (bcounter == pkt_nbits) {
|
|
bcounter = 0;
|
|
if (cbuf < &rxbuf[MAXOCTETS]) {
|
|
*cbuf++ = c;
|
|
|
|
if (debug::level == debug::DEBUG_LEVEL)
|
|
fprintf(stderr,"%02x ",c);
|
|
}
|
|
else
|
|
// else complain: cbuf is at MAXOCTETS
|
|
LOG_WARN("Long input packet, %d octets!",(int)(cbuf - &rxbuf[0]));
|
|
}
|
|
|
|
/*
|
|
// when flag found: keep collecting flags (0x7e) until !(0x7e) found
|
|
// (first non-0x7e begins DATA)
|
|
|
|
// keep collecting bits in DATA, 8 at-a-time, until 0x7e found
|
|
// while collecting bits, perform bit-unstuffing so we place in
|
|
// databuffer exactly 8 bits at-a-time
|
|
// (at times more than 8 bits in to 8 bits out)
|
|
// first trailing 0x7e ends DATA and begins STOP
|
|
*/
|
|
}
|
|
|
|
void pkt::Metric()
|
|
{
|
|
double snr = 0;
|
|
double pr = signal_power / noise_power;
|
|
|
|
power_ratio = decayavg(power_ratio, pr, pr-power_ratio > 0 ? 2 : 8);
|
|
snr = 10*log10( power_ratio );
|
|
|
|
snprintf(msg2, sizeof(msg2), "s/n %3.0f dB", snr-snr_avg);
|
|
put_Status2(msg2);
|
|
|
|
metric = CLAMP(power_ratio, 0.0, 100.0);
|
|
display_metric(metric);
|
|
|
|
if (metric < 5.0)
|
|
snr_avg = decayavg(snr_avg, snr, 8);
|
|
}
|
|
|
|
void pkt::idle_signal_power(double sample)
|
|
{
|
|
// average of signal energy over PKT_IdleLen duration
|
|
|
|
sample *= sample;
|
|
|
|
idle_signal_pwr += sample;
|
|
idle_signal_pwr -= idle_signal_buf[idle_signal_buf_ptr];
|
|
idle_signal_buf[idle_signal_buf_ptr] = sample;
|
|
|
|
++idle_signal_buf_ptr %= pkt_idlelen; // circular buffer
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
double pkt::corr_power(cmplx z)
|
|
#else
|
|
inline double pkt::corr_power(cmplx z)
|
|
#endif
|
|
{
|
|
// scaled magnitude
|
|
double power = norm(z);
|
|
power /= 2 * symbollen;
|
|
|
|
return power;
|
|
}
|
|
|
|
void pkt::correlate(double sample)
|
|
{
|
|
cmplx yl, yh, yt;
|
|
|
|
yl = nco_lo->cmplx_sample();
|
|
yl *= sample;
|
|
|
|
lo_signal_energy += yl;
|
|
lo_signal_energy -= lo_signal_buf[correlate_buf_ptr];
|
|
lo_signal_buf[correlate_buf_ptr] = yl;
|
|
lo_signal_corr = lo_signal_gain * corr_power(lo_signal_energy);
|
|
|
|
yh = nco_hi->cmplx_sample();
|
|
yh *= sample;
|
|
|
|
hi_signal_energy += yh;
|
|
hi_signal_energy -= hi_signal_buf[correlate_buf_ptr];
|
|
hi_signal_buf[correlate_buf_ptr] = yh;
|
|
hi_signal_corr = hi_signal_gain * corr_power(hi_signal_energy);
|
|
|
|
yt = nco_mid->cmplx_sample();
|
|
yt *= sample;
|
|
|
|
mid_signal_energy += yt;
|
|
mid_signal_energy -= mid_signal_buf[correlate_buf_ptr];
|
|
mid_signal_buf[correlate_buf_ptr] = yt;
|
|
//mid_signal_corr = corr_power(mid_signal_energy);
|
|
|
|
++correlate_buf_ptr %= symbollen; // SymbolLen correlation window
|
|
|
|
yt = mid_signal_energy;
|
|
|
|
if (abs(lo_signal_corr - hi_signal_corr) < 0.2) { // mid-symbol or noise
|
|
pipe[pipeptr] = 0.0;
|
|
|
|
QI[QIptr] = cmplx(yt.imag(), yt.real());
|
|
}
|
|
else if (lo_signal_corr > hi_signal_corr) {
|
|
pipe[pipeptr] = 0.5;
|
|
|
|
QI[QIptr] = cmplx (0.125 * yt.imag(), yt.real());
|
|
}
|
|
else {
|
|
pipe[pipeptr] = -0.5;
|
|
|
|
QI[QIptr] = cmplx( yt.real(), 0.125 * yt.imag());
|
|
}
|
|
++pipeptr %= pkt_syncdisplen;
|
|
|
|
yt_avg = decayavg(yt_avg, abs(yt), symbollen/2);
|
|
|
|
if (yt_avg > 0) QI[QIptr] = QI[QIptr] / yt_avg;
|
|
|
|
++QIptr %= MAX_ZLEN;
|
|
}
|
|
|
|
void pkt::detect_signal()
|
|
{
|
|
double sig_corr = lo_signal_corr + hi_signal_corr;// - mid_signal_corr;
|
|
|
|
signal_pwr += sig_corr;
|
|
signal_pwr -= signal_buf[signal_buf_ptr];
|
|
signal_buf[signal_buf_ptr] = sig_corr;
|
|
|
|
++signal_buf_ptr %= pkt_detectlen; // circular buffer
|
|
|
|
signal_power = signal_pwr / pkt_detectlen * signal_gain;
|
|
noise_power = idle_signal_pwr / pkt_idlelen;
|
|
|
|
// allow lazy eval
|
|
if (signal_power > PKT_MinSignalPwr && signal_power > noise_power) {
|
|
// lo+hi freq signals are present
|
|
detect_drop = pkt_detectlen; // reset signal drop counter
|
|
|
|
if (rxstate == PKT_RX_STATE_DROP) {
|
|
rxstate = PKT_RX_STATE_START;
|
|
|
|
// init STATE_START
|
|
signal_gain = 1.0; // 5.0
|
|
scounter = pkt_detectlen; //(was) 9 * symbollen;//11 instead of 9?
|
|
lo_sync = 100.0;
|
|
hi_sync = -100.0;
|
|
}
|
|
}
|
|
else if (--detect_drop < 1 && rxstate == PKT_RX_STATE_DATA) { // lazy eval
|
|
// give up on signals - gone,gone.
|
|
rxstate = PKT_RX_STATE_STOP;
|
|
scounter = 0; // (scounter is signed int)
|
|
|
|
if (debug::level == debug::DEBUG_LEVEL)
|
|
fprintf(stderr,"\n");
|
|
}
|
|
}
|
|
|
|
void pkt::do_sync()
|
|
{
|
|
double power_delta = lo_signal_corr - hi_signal_corr;
|
|
|
|
lo_sync_ptr--; hi_sync_ptr--;
|
|
|
|
if (lo_sync > power_delta && power_delta < 0) {
|
|
lo_sync_ptr = 0;
|
|
lo_sync = power_delta;
|
|
}
|
|
|
|
if (hi_sync < power_delta && power_delta > 0) {
|
|
hi_sync_ptr = 0;
|
|
hi_sync = power_delta;
|
|
}
|
|
|
|
if (--scounter < 1) {
|
|
int offset;
|
|
|
|
if (fabs(hi_sync) > fabs(lo_sync))
|
|
offset = -hi_sync_ptr;
|
|
else
|
|
offset = -lo_sync_ptr;
|
|
|
|
offset %= symbollen; // clamp offset to +/- one symbol
|
|
|
|
mid_symbol = symbollen - offset;
|
|
prev_symbol = pll_symbol = hi_signal_corr < lo_signal_corr;
|
|
|
|
rxstate = PKT_RX_STATE_DATA;
|
|
seq_ones = 0; // reset once on transition to STATE_DATA
|
|
cbuf = &rxbuf[0]; // and reset packet buffer ptr
|
|
}
|
|
}
|
|
|
|
void pkt::rx_data()
|
|
{
|
|
bool tbit = hi_signal_corr < lo_signal_corr; // detect current symbol
|
|
|
|
mid_symbol -= 1;
|
|
int imid_symbol = (int) floor(mid_symbol+0.5);
|
|
|
|
// hard-decision symbol decoding. done at mid-symbol (imid_symbol < 1).
|
|
//
|
|
// might do instead a soft-decision by cumulative voting
|
|
// of more than one mid-symbol sample? symbollen-2 samples max?
|
|
//
|
|
if (imid_symbol < 1) { // counting down to middle of next symbol
|
|
|
|
bool bit = prev_symbol == tbit; // detect symbol change
|
|
|
|
prev_symbol = pll_symbol = tbit;
|
|
mid_symbol += symbollen; // advance to middle of next symbol
|
|
|
|
// remember to check value of reverse here to determine symbol
|
|
rx(reverse ? !bit : bit);
|
|
}
|
|
else
|
|
if (tbit != pll_symbol) { // update PLL
|
|
if (mid_symbol < ((double)symbollen/2))
|
|
mid_symbol += PKT_PLLAdjVal;
|
|
else
|
|
mid_symbol -= PKT_PLLAdjVal;
|
|
|
|
pll_symbol = tbit;
|
|
}
|
|
}
|
|
|
|
int pkt::rx_process(const double *buf, int len)
|
|
{
|
|
double x_n;
|
|
|
|
if (select_val != progdefaults.PKT_BAUD_SELECT) {
|
|
// need to re-init modem
|
|
restart();
|
|
}
|
|
|
|
Metric();
|
|
|
|
while (len-- > 0) {
|
|
|
|
x_n = *buf++;
|
|
|
|
switch (rxstate) {
|
|
|
|
case PKT_RX_STATE_STOP:
|
|
default:
|
|
rxstate = PKT_RX_STATE_IDLE;
|
|
// fallthrough with next sample (x_(n+1))
|
|
|
|
case PKT_RX_STATE_IDLE:
|
|
idle_signal_power( x_n );
|
|
|
|
if (--scounter < 1) {
|
|
rxstate = PKT_RX_STATE_DROP;
|
|
|
|
scounter = pkt_idlelen;
|
|
signal_gain = 1.0;
|
|
|
|
signal_pwr = signal_buf_ptr = 0;
|
|
|
|
for(int i = 0; i < pkt_detectlen; i++)
|
|
signal_buf[i] = 0;
|
|
|
|
lo_signal_energy = hi_signal_energy =
|
|
mid_signal_energy = cmplx(0, 0);
|
|
|
|
yt_avg = correlate_buf_ptr = 0;
|
|
|
|
for(int i = 0; i < symbollen; i++)
|
|
lo_signal_buf[i] = hi_signal_buf[i] =
|
|
mid_signal_buf[i] = cmplx(0, 0);
|
|
}
|
|
break;
|
|
|
|
case PKT_RX_STATE_DROP:
|
|
idle_signal_power( x_n );
|
|
correlate( x_n );
|
|
detect_signal();
|
|
break;
|
|
|
|
case PKT_RX_STATE_START:
|
|
correlate( x_n );
|
|
do_sync();
|
|
break;
|
|
|
|
case PKT_RX_STATE_DATA:
|
|
correlate( x_n );
|
|
rx_data();
|
|
detect_signal();
|
|
break;
|
|
}
|
|
} // while(len)
|
|
|
|
if (metric < progStatus.sldrSquelchValue && progStatus.sqlonoff) {
|
|
if (clear_zdata) {
|
|
for (int i = 0; i < MAX_ZLEN; i++)
|
|
QI[i] = cmplx(0,0);
|
|
QIptr = 0;
|
|
set_zdata(QI, MAX_ZLEN);
|
|
clear_zdata = false;
|
|
clear_syncscope();
|
|
}
|
|
} else {
|
|
clear_zdata = true;
|
|
update_syncscope();
|
|
set_zdata(QI, MAX_ZLEN);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//=====================================================================
|
|
// PKT1200 transmit
|
|
//=====================================================================
|
|
|
|
void pkt::send_symbol(bool bit)
|
|
{
|
|
if (reverse) bit = !bit;
|
|
|
|
for (int i = 0; i < symbollen; i++) {
|
|
if (bit)
|
|
outbuf[i] = hi_tone->sample() * hi_txgain; // modem::outbuf[]
|
|
else
|
|
outbuf[i] = lo_tone->sample() * lo_txgain; // modem::outbuf[]
|
|
}
|
|
|
|
// adjust nco phase accumulator to pick up where the other nco left off
|
|
if (bit)
|
|
lo_tone->setphaseacc(hi_tone->getphaseacc());
|
|
else
|
|
hi_tone->setphaseacc(lo_tone->getphaseacc());
|
|
|
|
ModulateXmtr(outbuf, symbollen); // ignore retv?
|
|
}
|
|
|
|
/*
|
|
void pkt::send_stop()
|
|
{
|
|
double freq;
|
|
bool invert = reverse;
|
|
|
|
if (invert)
|
|
freq = get_txfreq_woffset() - pkt_shift / 2.0;
|
|
else
|
|
freq = get_txfreq_woffset() + pkt_shift / 2.0;
|
|
|
|
// stoplen = 0;
|
|
// stoplen ::= (int) ((1.0 * 12000 / 1200) + 0.5) = 10
|
|
|
|
for (int i = 0; i < stoplen; i++) {
|
|
outbuf[i] = nco(freq);
|
|
if (invert)
|
|
FSKbuf[i] = 0.0 * FSKnco();
|
|
else
|
|
FSKbuf[i] = FSKnco();
|
|
}
|
|
if (progdefaults.PseudoFSK)
|
|
ModulateStereo(outbuf, FSKbuf, stoplen);
|
|
else
|
|
ModulateXmtr(outbuf, stoplen);
|
|
}
|
|
*/
|
|
|
|
void pkt::send_char(unsigned char c)
|
|
{
|
|
// if nostuff == 1 then { do _no_ bit stuffing }
|
|
// else { keep track of no-transition (one bits) and stuff as needed }
|
|
|
|
for (int i = 0; i < pkt_nbits; i++) {
|
|
|
|
if ((c & 0x01) == 0) {
|
|
currbit = !currbit; // transition on zero bits
|
|
send_symbol(currbit);
|
|
|
|
nr_ones = 0;
|
|
}
|
|
else {
|
|
send_symbol(currbit);
|
|
|
|
if (++nr_ones == 5 && !nostuff) {
|
|
currbit = !currbit; // stuff zero bit
|
|
send_symbol(currbit);
|
|
|
|
nr_ones = 0;
|
|
}
|
|
}
|
|
|
|
c >>= 1; // lsb to msb bit order
|
|
}
|
|
}
|
|
|
|
string upcase(string s)
|
|
{
|
|
// this function is also defined in arq::upcase
|
|
// how to make a fldigi::upcase and use it here and in flarq?
|
|
// maybe add it to misc/strutil.cxx ?
|
|
|
|
for (size_t i = 0; i < s.length(); i++)
|
|
s[i] = toupper(s[i]);
|
|
return s;
|
|
}
|
|
|
|
void pkt::send_msg(unsigned char c)
|
|
{
|
|
// form TX pane incoming chars into a valid UI AX.25 packet
|
|
// in KISS mode, we do not create a packet header.
|
|
if (!did_pkt_head) {
|
|
int i, j;
|
|
// the following is split into two because putting it all together
|
|
// as "upcase(progdefaults.myCall).c_str()" would compile but not
|
|
// work correctly. Callsign decoded as "******-9" (wrong!).
|
|
string ts = upcase(progdefaults.myCall);
|
|
const char *src = ts.c_str(), // MYCALL
|
|
dest2[] = "WIDE2 "; // digi
|
|
char dest[7];
|
|
|
|
snprintf(dest, 7, "APL%1d%02d", FLDIGI_VERSION_MAJOR,
|
|
FLDIGI_VERSION_MINOR); // dest[] = "APL321"
|
|
|
|
for (i = 7, j = 0; i < 13; i++, j++) { // put src callsign in frame
|
|
if (src[j] == 0) break;
|
|
txbuf[i] = src[j] << 1;
|
|
}
|
|
while (j++ < 6) {
|
|
txbuf[i++] = ' ' << 1; // padding
|
|
}
|
|
|
|
txbuf[13] = '9' << 1; // src + SSID ==> MYCALL-9
|
|
|
|
for (i = 0, j = 0; i < 6; i++, j++ ) { // put dest callsign in frame
|
|
txbuf[i] = dest[j] << 1;
|
|
}
|
|
txbuf[6] = '1' << 1; // dest[] + SSID ==> APL321-1
|
|
|
|
if (pkt_baud >= 1200) {
|
|
for (i = 14, j = 0; i < 20; i++, j++) { // put digi callsign in frame
|
|
txbuf[i] = dest2[j] << 1;
|
|
}
|
|
txbuf[20] = '1' << 1; // dest2[] + SSID ==> WIDE1-1
|
|
txbuf[20] |= 0x01; // add last callsign flag
|
|
|
|
tx_cbuf = &txbuf[21];
|
|
}
|
|
else { // use no digi when baud < 1200. limited bandwidth!
|
|
txbuf[6] |= 0x01; // add last callsign flag
|
|
|
|
tx_cbuf = &txbuf[14];
|
|
}
|
|
|
|
*tx_cbuf++ = 0x03; // add CTRL val for U_FRAME type UI
|
|
*tx_cbuf++ = 0xf0;
|
|
|
|
unsigned char *tc = &txbuf[0];
|
|
|
|
while (tc < tx_cbuf) {
|
|
send_char(*tc++);
|
|
tx_char_count--; // must count header chars in pkt length
|
|
}
|
|
|
|
did_pkt_head = true;
|
|
}
|
|
|
|
send_char(c);
|
|
*tx_cbuf++ = c;
|
|
}
|
|
|
|
|
|
int pkt::tx_process()
|
|
{
|
|
if (pretone > 0) {
|
|
while (pretone-- > 0) {
|
|
send_symbol(0); // Mark (low) tone
|
|
}
|
|
}
|
|
|
|
if (preamble > 0) {
|
|
nostuff = 1; // turn off bit stuffing here
|
|
while (preamble-- > 0) {
|
|
send_char(PKT_Flag);
|
|
}
|
|
nostuff = 0; // send_char() is 8-bit clean
|
|
tx_char_count--; // count only last flag char
|
|
}
|
|
|
|
static int c = 0;
|
|
if (tx_char_count > 0)
|
|
c = get_tx_char();
|
|
|
|
// TX buffer empty
|
|
// if (c == 0x03 || stopflag) {
|
|
if (c == GET_TX_CHAR_ETX || stopflag || tx_char_count == 0) {
|
|
if (!stopflag) {
|
|
// compute FCS and add it to the frame via *tx_cbuf
|
|
unsigned int fcs = computeFCS(&txbuf[0], tx_cbuf);
|
|
// (B == Byte, b == bit)
|
|
unsigned char tc = (fcs & 0xFF00) >> 8; // msB
|
|
|
|
*tx_cbuf++ = tc;
|
|
send_char(tc);
|
|
|
|
tc = (unsigned char)(fcs & 0x00FF); // lsB
|
|
|
|
*tx_cbuf++ = tc;
|
|
send_char(tc);
|
|
|
|
nostuff = 1; // turn off bit stuffing
|
|
while (postamble-- > 0) {
|
|
send_char(PKT_Flag);
|
|
}
|
|
nostuff = 0;
|
|
|
|
stopflag = true;
|
|
return 0;
|
|
}
|
|
|
|
stopflag = false;
|
|
tx_char_count = MAXOCTETS-3; // leave room for FCS and end-flag
|
|
tx_cbuf = &txbuf[0];
|
|
currbit = 0;
|
|
did_pkt_head = false;
|
|
|
|
cwid();
|
|
return -1;
|
|
}
|
|
|
|
if (tx_char_count-- > 0)
|
|
send_msg((unsigned char)c);
|
|
else {
|
|
// else complain: maybe auto-segment?
|
|
LOG_WARN("Long output packet, %d octets!",(int)(tx_cbuf - &txbuf[0]));
|
|
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|