mirror of https://github.com/wb2osz/direwolf.git
AIS reception.
This commit is contained in:
parent
65d8d265cd
commit
0dc7cba1c5
|
@ -10,13 +10,15 @@
|
||||||
|
|
||||||
- Rather than trying to keep a bunch of different platform specific Makefiles in sync, "cmake" is now used for greater portability and easier maintenance.
|
- Rather than trying to keep a bunch of different platform specific Makefiles in sync, "cmake" is now used for greater portability and easier maintenance.
|
||||||
|
|
||||||
|
- README.md has a quick summary of the process. More details in the User Guide.
|
||||||
|
|
||||||
|
|
||||||
### New Features: ###
|
### New Features: ###
|
||||||
|
|
||||||
|
|
||||||
- "-X" option enables FX.25 transmission. FX.25 reception is always enabled so you don't need to do anything special.
|
- "-X" option enables FX.25 transmission. FX.25 reception is always enabled so you don't need to do anything special. "What is FX.25?" you might ask. It is forward error correction (FEC) added in a way that is completely compatible with an ordinary AX.25 frame. See new document ***AX25\_plus\_FEC\_equals\_FX25.pdf*** for details.
|
||||||
|
|
||||||
|
- Receive AIS location data from ships. Enable by using "-B AIS" command line option or "MODEM AIS" in the configuration file. AIS NMEA sentences are encapsulated in APRS user-defined data with a "{DA" prefix. This uses 9600 bps so you need to use wide band audio, not what comes out of the speaker.
|
||||||
|
|
||||||
- "-t" option now accepts more values to accommodate inconsistent handling of text color control codes by different terminal emulators. The default, 1, should work with most modern terminal types. If the colors are not right, try "-t 9" to see the result of the different choices and pick the best one. If none of them look right, file a bug report and specify: operating system version (e.g. Raspbian Buster), terminal emulator type and version (e.g. LXTerminal 0.3.2). Include a screen capture.
|
- "-t" option now accepts more values to accommodate inconsistent handling of text color control codes by different terminal emulators. The default, 1, should work with most modern terminal types. If the colors are not right, try "-t 9" to see the result of the different choices and pick the best one. If none of them look right, file a bug report and specify: operating system version (e.g. Raspbian Buster), terminal emulator type and version (e.g. LXTerminal 0.3.2). Include a screen capture.
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ endif()
|
||||||
# direwolf
|
# direwolf
|
||||||
list(APPEND direwolf_SOURCES
|
list(APPEND direwolf_SOURCES
|
||||||
direwolf.c
|
direwolf.c
|
||||||
|
ais.c
|
||||||
aprs_tt.c
|
aprs_tt.c
|
||||||
audio_stats.c
|
audio_stats.c
|
||||||
ax25_link.c
|
ax25_link.c
|
||||||
|
@ -49,6 +50,7 @@ list(APPEND direwolf_SOURCES
|
||||||
fx25_init.c
|
fx25_init.c
|
||||||
fx25_rec.c
|
fx25_rec.c
|
||||||
fx25_send.c
|
fx25_send.c
|
||||||
|
fx25_auto.c
|
||||||
gen_tone.c
|
gen_tone.c
|
||||||
hdlc_rec.c
|
hdlc_rec.c
|
||||||
hdlc_rec2.c
|
hdlc_rec2.c
|
||||||
|
@ -136,6 +138,7 @@ endif()
|
||||||
# decode_aprs
|
# decode_aprs
|
||||||
list(APPEND decode_aprs_SOURCES
|
list(APPEND decode_aprs_SOURCES
|
||||||
decode_aprs.c
|
decode_aprs.c
|
||||||
|
ais.c
|
||||||
kiss_frame.c
|
kiss_frame.c
|
||||||
ax25_pad.c
|
ax25_pad.c
|
||||||
dwgpsnmea.c
|
dwgpsnmea.c
|
||||||
|
@ -287,6 +290,7 @@ target_link_libraries(gen_packets
|
||||||
# atest
|
# atest
|
||||||
list(APPEND atest_SOURCES
|
list(APPEND atest_SOURCES
|
||||||
atest.c
|
atest.c
|
||||||
|
ais.c
|
||||||
demod.c
|
demod.c
|
||||||
demod_afsk.c
|
demod_afsk.c
|
||||||
demod_psk.c
|
demod_psk.c
|
||||||
|
|
|
@ -0,0 +1,409 @@
|
||||||
|
|
||||||
|
// This file is part of Dire Wolf, an amateur radio packet TNC.
|
||||||
|
//
|
||||||
|
// Copyright (C) 2020 John Langner, WB2OSZ
|
||||||
|
//
|
||||||
|
// This program 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 2 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
/********************************************************************************
|
||||||
|
*
|
||||||
|
* File: ais.c
|
||||||
|
*
|
||||||
|
* Purpose: Functions for processing received AIS transmissions and
|
||||||
|
* converting to NMEA sentence representation.
|
||||||
|
*
|
||||||
|
* References: AIVDM/AIVDO protocol decoding by Eric S. Raymond
|
||||||
|
* https://gpsd.gitlab.io/gpsd/AIVDM.html
|
||||||
|
*
|
||||||
|
* Sample recording with about 100 messages. Test with "atest -B AIS xxx.wav"
|
||||||
|
* https://github.com/freerange/ais-on-sdr/wiki/example-data/long-beach-160-messages.wav
|
||||||
|
*
|
||||||
|
* Useful on-line decoder for AIS NMEA sentences.
|
||||||
|
* https://www.aggsoft.com/ais-decoder.htm
|
||||||
|
*
|
||||||
|
* Future? Add an interface to feed AIS data into aprs.fi.
|
||||||
|
* https://aprs.fi/page/ais_feeding
|
||||||
|
*
|
||||||
|
*******************************************************************************/
|
||||||
|
|
||||||
|
#include "direwolf.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "textcolor.h"
|
||||||
|
#include "ais.h"
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Functions to get and set element of a bit vector.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
static const unsigned char mask[8] = { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 };
|
||||||
|
|
||||||
|
static inline unsigned int get_bit (unsigned char *base, unsigned int offset)
|
||||||
|
{
|
||||||
|
return ( (base[offset >> 3] & mask[offset & 0x7]) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void set_bit (unsigned char *base, unsigned int offset, int val)
|
||||||
|
{
|
||||||
|
if (val) {
|
||||||
|
base[offset >> 3] |= mask[offset & 0x7];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
base[offset >> 3] &= ~ mask[offset & 0x7];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Extract a variable length field from a bit vector.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
static unsigned int get_field (unsigned char *base, unsigned int start, unsigned int len)
|
||||||
|
{
|
||||||
|
unsigned int result = 0;
|
||||||
|
for (int k = 0; k < len; k++) {
|
||||||
|
result <<= 1;
|
||||||
|
result |= get_bit (base, start + k);
|
||||||
|
}
|
||||||
|
return (result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_field (unsigned char *base, unsigned int start, unsigned int len, unsigned int val)
|
||||||
|
{
|
||||||
|
for (int k = 0; k < len; k++) {
|
||||||
|
set_bit (base, start + k, (val >> (len - 1 - k) ) & 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int get_field_signed (unsigned char *base, unsigned int start, unsigned int len)
|
||||||
|
{
|
||||||
|
int result = (int) get_field(base, start, len);
|
||||||
|
// Sign extend.
|
||||||
|
result <<= (32 - len);
|
||||||
|
result >>= (32 - len);
|
||||||
|
return (result);
|
||||||
|
}
|
||||||
|
|
||||||
|
static double get_field_latlon (unsigned char *base, unsigned int start, unsigned int len)
|
||||||
|
{
|
||||||
|
// Latitude of 0x3412140 (91 deg) means not available.
|
||||||
|
// Longitude of 0x6791AC0 (181 deg) means not available.
|
||||||
|
return ((double)get_field_signed(base, start, len) / 600000.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static float get_field_speed (unsigned char *base, unsigned int start, unsigned int len)
|
||||||
|
{
|
||||||
|
// Raw 1023 means not available.
|
||||||
|
// Multiply by 0.1 to get knots.
|
||||||
|
return ((float)get_field_signed(base, start, len) * 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static float get_field_course (unsigned char *base, unsigned int start, unsigned int len)
|
||||||
|
{
|
||||||
|
// Raw 3600 means not available.
|
||||||
|
// Multiply by 0.1 to get degrees
|
||||||
|
return ((float)get_field_signed(base, start, len) * 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Convert between 6 bit values and printable characters used in
|
||||||
|
* in the AIS NMEA sentences.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
// Characters '0' thru 'W' become values 0 thru 39.
|
||||||
|
// Characters '`' thru 'w' become values 40 thru 63.
|
||||||
|
|
||||||
|
static int char_to_sextet (char ch)
|
||||||
|
{
|
||||||
|
if (ch >= '0' && ch <= 'W') {
|
||||||
|
return (ch - '0');
|
||||||
|
}
|
||||||
|
else if (ch >= '`' && ch <= 'w') {
|
||||||
|
return (ch - '`' + 40);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Invalid character \"%c\" found in AIS NMEA sentence payload.\n", ch);
|
||||||
|
return (0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Values 0 thru 39 become characters '0' thru 'W'.
|
||||||
|
// Values 40 thru 63 become characters '`' thru 'w'.
|
||||||
|
// This is known as "Payload Armoring."
|
||||||
|
|
||||||
|
static int sextet_to_char (int val)
|
||||||
|
{
|
||||||
|
if (val >= 0 && val <= 39) {
|
||||||
|
return ('0' + val);
|
||||||
|
}
|
||||||
|
else if (val >= 40 && val <= 63) {
|
||||||
|
return ('`' + val - 40);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Invalid 6 bit value %d from AIS HDLC payload.\n", val);
|
||||||
|
return ('0');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Convert AIS binary block (from HDLC frame) to NMEA sentence.
|
||||||
|
*
|
||||||
|
* In: Pointer to AIS binary block and number of bytes.
|
||||||
|
* Out: NMEA sentence. Provide size to avoid string overflow.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
void ais_to_nmea (unsigned char *ais, int ais_len, char *nmea, int nmea_size)
|
||||||
|
{
|
||||||
|
char payload[256];
|
||||||
|
// Number of resulting characters for payload.
|
||||||
|
int ns = (ais_len * 8 + 5) / 6;
|
||||||
|
if (ns+1 > sizeof(payload)) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("AIS HDLC payload of %d bytes is too large.\n", ais_len);
|
||||||
|
ns = sizeof(payload) - 1;
|
||||||
|
}
|
||||||
|
for (int k = 0; k < ns; k++) {
|
||||||
|
payload[k] = sextet_to_char(get_field(ais, k*6, 6));
|
||||||
|
}
|
||||||
|
payload[ns] = '\0';
|
||||||
|
|
||||||
|
strlcpy (nmea, "!AIVDM,1,1,,A,", nmea_size);
|
||||||
|
strlcat (nmea, payload, nmea_size);
|
||||||
|
|
||||||
|
// If the number of bytes in is not a multiple of 3, this does not
|
||||||
|
// produce a whole number of characters out. Extra padding bits were
|
||||||
|
// added to get the last character. Include this number so the
|
||||||
|
// decoding application can drop this number of bits from the end.
|
||||||
|
// At least, I think that is the way it should work.
|
||||||
|
// The examples all have 0.
|
||||||
|
char pad_bits[8];
|
||||||
|
snprintf (pad_bits, sizeof(pad_bits), ",%d", ns * 6 - ais_len * 8);
|
||||||
|
strlcat (nmea, pad_bits, nmea_size);
|
||||||
|
|
||||||
|
// Finally the NMEA style checksum.
|
||||||
|
int cs = 0;
|
||||||
|
for (char *p = nmea + 1; *p != '\0'; p++) {
|
||||||
|
cs ^= *p;
|
||||||
|
}
|
||||||
|
char checksum[8];
|
||||||
|
snprintf (checksum, sizeof(checksum), "*%02X", cs & 0x7f);
|
||||||
|
strlcat (nmea, checksum, nmea_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Name: ais_parse
|
||||||
|
*
|
||||||
|
* Purpose: Parse AIS sentence and extract interesing parts.
|
||||||
|
*
|
||||||
|
* Inputs: sentence NMEA sentence.
|
||||||
|
*
|
||||||
|
* quiet Suppress printing of error messages.
|
||||||
|
*
|
||||||
|
* Outputs: descr Description of AIS message type.
|
||||||
|
* mssi 9 digit identifier.
|
||||||
|
* odlat latitude.
|
||||||
|
* odlon longitude.
|
||||||
|
* ofknots speed.
|
||||||
|
* ofcourse direction of travel.
|
||||||
|
*
|
||||||
|
* Returns: 0 for success, -1 for error.
|
||||||
|
*
|
||||||
|
*--------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
// Maximum NMEA sentence length is 82 according to some people.
|
||||||
|
// Make buffer considerably larger to be safe.
|
||||||
|
#define NMEA_MAX_LEN 240
|
||||||
|
|
||||||
|
int ais_parse (char *sentence, int quiet, char *descr, int descr_size, char *mssi, int mssi_size, double *odlat, double *odlon, float *ofknots, float *ofcourse)
|
||||||
|
{
|
||||||
|
char stemp[NMEA_MAX_LEN]; /* Make copy because parsing is destructive. */
|
||||||
|
|
||||||
|
strlcpy (mssi, "?", mssi_size);
|
||||||
|
*odlat = G_UNKNOWN;
|
||||||
|
*odlon = G_UNKNOWN;
|
||||||
|
*ofknots = G_UNKNOWN;
|
||||||
|
*ofcourse = G_UNKNOWN;
|
||||||
|
|
||||||
|
strlcpy (stemp, sentence, sizeof(stemp));
|
||||||
|
|
||||||
|
// Verify and remove checksum.
|
||||||
|
|
||||||
|
unsigned char cs = 0;
|
||||||
|
char *p;
|
||||||
|
|
||||||
|
for (p = stemp+1; *p != '*' && *p != '\0'; p++) {
|
||||||
|
cs ^= *p;
|
||||||
|
}
|
||||||
|
|
||||||
|
p = strchr (stemp, '*');
|
||||||
|
if (p == NULL) {
|
||||||
|
if ( ! quiet) {
|
||||||
|
text_color_set (DW_COLOR_INFO);
|
||||||
|
dw_printf("Missing AIS sentence checksum.\n");
|
||||||
|
}
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
if (cs != strtoul(p+1, NULL, 16)) {
|
||||||
|
if ( ! quiet) {
|
||||||
|
text_color_set (DW_COLOR_ERROR);
|
||||||
|
dw_printf("AIS sentence checksum error. Expected %02x but found %s.\n", cs, p+1);
|
||||||
|
}
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
*p = '\0'; // Remove the checksum.
|
||||||
|
|
||||||
|
// Extract the comma separated fields.
|
||||||
|
|
||||||
|
char *next;
|
||||||
|
|
||||||
|
char *talker; /* Expecting !AIVDM */
|
||||||
|
char *frag_count; /* ignored */
|
||||||
|
char *frag_num; /* ignored */
|
||||||
|
char *msg_id; /* ignored */
|
||||||
|
char *radio_chan; /* ignored */
|
||||||
|
char *payload; /* Encoded as 6 bits per character. */
|
||||||
|
char *fill_bits; /* Number of bits to discard. */
|
||||||
|
|
||||||
|
next = stemp;
|
||||||
|
talker = strsep(&next, ",");
|
||||||
|
frag_count = strsep(&next, ",");
|
||||||
|
frag_num = strsep(&next, ",");
|
||||||
|
msg_id = strsep(&next, ",");
|
||||||
|
radio_chan = strsep(&next, ",");
|
||||||
|
payload = strsep(&next, ",");
|
||||||
|
fill_bits = strsep(&next, ",");
|
||||||
|
|
||||||
|
/* Suppress the 'set but not used' compiler warnings. */
|
||||||
|
/* Alternatively, we might use __attribute__((unused)) */
|
||||||
|
|
||||||
|
(void)(talker);
|
||||||
|
(void)(frag_count);
|
||||||
|
(void)(frag_num);
|
||||||
|
(void)(msg_id);
|
||||||
|
(void)(radio_chan);
|
||||||
|
|
||||||
|
if (payload == NULL || strlen(payload) == 0) {
|
||||||
|
if ( ! quiet) {
|
||||||
|
text_color_set (DW_COLOR_ERROR);
|
||||||
|
dw_printf("Payload is missing from AIS sentence.\n");
|
||||||
|
}
|
||||||
|
return (-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert character representation to bit vector.
|
||||||
|
|
||||||
|
unsigned char ais[256];
|
||||||
|
memset (ais, 0, sizeof(ais));
|
||||||
|
|
||||||
|
int plen = strlen(payload);
|
||||||
|
|
||||||
|
for (int k = 0; k < plen; k++) {
|
||||||
|
set_field (ais, k*6, 6, char_to_sextet(payload[k]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify number of filler bits.
|
||||||
|
|
||||||
|
int nfill = atoi(fill_bits);
|
||||||
|
int nbytes = (plen * 6) / 8;
|
||||||
|
|
||||||
|
if (nfill != plen * 6 - nbytes * 8) {
|
||||||
|
if ( ! quiet) {
|
||||||
|
text_color_set (DW_COLOR_ERROR);
|
||||||
|
dw_printf("Number of filler bits is %d when %d is expected.\n",
|
||||||
|
nfill, plen * 6 - nbytes * 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract the fields of interest from a few message types.
|
||||||
|
// Don't get too carried away.
|
||||||
|
|
||||||
|
int type = get_field(ais, 0, 6);
|
||||||
|
switch (type) {
|
||||||
|
|
||||||
|
case 1: // Position Report Class A
|
||||||
|
case 2:
|
||||||
|
case 3:
|
||||||
|
|
||||||
|
snprintf (descr, descr_size, "AIS %d: Position Report Class A", type);
|
||||||
|
snprintf (mssi, mssi_size, "%d", get_field(ais, 8, 30));
|
||||||
|
*odlon = get_field_latlon(ais, 61, 28);
|
||||||
|
*odlat = get_field_latlon(ais, 89, 27);
|
||||||
|
*ofknots = get_field_speed(ais, 50, 10);
|
||||||
|
*ofcourse = get_field_course(ais, 116, 12);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 4: // Base Station Report
|
||||||
|
|
||||||
|
snprintf (descr, descr_size, "AIS %d: Base Station Report", type);
|
||||||
|
snprintf (mssi, mssi_size, "%d", get_field(ais, 8, 30));
|
||||||
|
//year = get_field(ais, 38, 14);
|
||||||
|
//month = get_field(ais, 52, 4);
|
||||||
|
//day = get_field(ais, 56, 5);
|
||||||
|
//hour = get_field(ais, 61, 5);
|
||||||
|
//minute = get_field(ais, 66, 6);
|
||||||
|
//second = get_field(ais, 72, 6);
|
||||||
|
*odlon = get_field_latlon(ais, 79, 28);
|
||||||
|
*odlat = get_field_latlon(ais, 107, 27);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 18: // Standard Class B CS Position Report
|
||||||
|
|
||||||
|
snprintf (descr, descr_size, "AIS %d: Standard Class B CS Position Report", type);
|
||||||
|
snprintf (mssi, mssi_size, "%d", get_field(ais, 8, 30));
|
||||||
|
*odlon = get_field_latlon(ais, 57, 28);
|
||||||
|
*odlat = get_field_latlon(ais, 85, 27);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 19: // Extended Class B CS Position Report
|
||||||
|
|
||||||
|
snprintf (descr, descr_size, "AIS %d: Extended Class B CS Position Report", type);
|
||||||
|
snprintf (mssi, mssi_size, "%d", get_field(ais, 8, 30));
|
||||||
|
*odlon = get_field_latlon(ais, 57, 28);
|
||||||
|
*odlat = get_field_latlon(ais, 85, 27);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
snprintf (descr, descr_size, "AIS message type %d", type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (0);
|
||||||
|
|
||||||
|
} /* end ais_parse */
|
||||||
|
|
||||||
|
// end ais.c
|
|
@ -0,0 +1,5 @@
|
||||||
|
|
||||||
|
|
||||||
|
void ais_to_nmea (unsigned char *ais, int ais_len, char *nema, int nema_size);
|
||||||
|
|
||||||
|
int ais_parse (char *sentence, int quiet, char *descr, int descr_size, char *mssi, int mssi_size, double *odlat, double *odlon, float *ofknots, float *ofcourse);
|
16
src/atest.c
16
src/atest.c
|
@ -270,7 +270,13 @@ int main (int argc, char *argv[])
|
||||||
|
|
||||||
case 'B': /* -B for data Bit rate */
|
case 'B': /* -B for data Bit rate */
|
||||||
/* Also implies modem type based on speed. */
|
/* Also implies modem type based on speed. */
|
||||||
B_opt = atoi(optarg);
|
/* Special case "AIS" rather than number. */
|
||||||
|
if (strcasecmp(optarg, "AIS") == 0) {
|
||||||
|
B_opt = 12345; // See special case below.
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
B_opt = atoi(optarg);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'g': /* -G Force G3RUH regardless of speed. */
|
case 'g': /* -G Force G3RUH regardless of speed. */
|
||||||
|
@ -447,6 +453,13 @@ int main (int argc, char *argv[])
|
||||||
my_audio_config.achan[0].space_freq = 0;
|
my_audio_config.achan[0].space_freq = 0;
|
||||||
strlcpy (my_audio_config.achan[0].profiles, "", sizeof(my_audio_config.achan[0].profiles));
|
strlcpy (my_audio_config.achan[0].profiles, "", sizeof(my_audio_config.achan[0].profiles));
|
||||||
}
|
}
|
||||||
|
else if (my_audio_config.achan[0].baud == 12345) {
|
||||||
|
my_audio_config.achan[0].modem_type = MODEM_AIS;
|
||||||
|
my_audio_config.achan[0].baud = 9600;
|
||||||
|
my_audio_config.achan[0].mark_freq = 0;
|
||||||
|
my_audio_config.achan[0].space_freq = 0;
|
||||||
|
strlcpy (my_audio_config.achan[0].profiles, " ", sizeof(my_audio_config.achan[0].profiles)); // avoid getting default later.
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
my_audio_config.achan[0].modem_type = MODEM_SCRAMBLE;
|
my_audio_config.achan[0].modem_type = MODEM_SCRAMBLE;
|
||||||
my_audio_config.achan[0].mark_freq = 0;
|
my_audio_config.achan[0].mark_freq = 0;
|
||||||
|
@ -890,6 +903,7 @@ static void usage (void) {
|
||||||
dw_printf (" 2400 bps uses QPSK based on V.26 standard.\n");
|
dw_printf (" 2400 bps uses QPSK based on V.26 standard.\n");
|
||||||
dw_printf (" 4800 bps uses 8PSK based on V.27 standard.\n");
|
dw_printf (" 4800 bps uses 8PSK based on V.27 standard.\n");
|
||||||
dw_printf (" 9600 bps and up uses K9NG/G3RUH standard.\n");
|
dw_printf (" 9600 bps and up uses K9NG/G3RUH standard.\n");
|
||||||
|
dw_printf (" AIS for ship Automatic Identification System.\n");
|
||||||
dw_printf ("\n");
|
dw_printf ("\n");
|
||||||
dw_printf (" -g Use G3RUH modem rather rather than default for data rate.\n");
|
dw_printf (" -g Use G3RUH modem rather rather than default for data rate.\n");
|
||||||
dw_printf (" -j 2400 bps QPSK compatible with direwolf <= 1.5.\n");
|
dw_printf (" -j 2400 bps QPSK compatible with direwolf <= 1.5.\n");
|
||||||
|
|
|
@ -112,6 +112,13 @@ struct audio_s {
|
||||||
/* Initially this applies to all channels. */
|
/* Initially this applies to all channels. */
|
||||||
/* This should probably be per channel. One step at a time. */
|
/* This should probably be per channel. One step at a time. */
|
||||||
|
|
||||||
|
int fx25_auto_enable; /* Turn on FX.25 for current connected mode session */
|
||||||
|
/* under poor conditions. */
|
||||||
|
/* Set to 0 to disable feature. */
|
||||||
|
/* I put it here, rather than with the rest of the link layer */
|
||||||
|
/* parameters because it is really a part of the HDLC layer */
|
||||||
|
/* and is part of the KISS TNC functionality rather than our data link layer. */
|
||||||
|
|
||||||
char timestamp_format[40]; /* -T option */
|
char timestamp_format[40]; /* -T option */
|
||||||
/* Precede received & transmitted frames with timestamp. */
|
/* Precede received & transmitted frames with timestamp. */
|
||||||
/* Command line option uses "strftime" format string. */
|
/* Command line option uses "strftime" format string. */
|
||||||
|
@ -141,7 +148,7 @@ struct audio_s {
|
||||||
/* Could all be the same or different. */
|
/* Could all be the same or different. */
|
||||||
|
|
||||||
|
|
||||||
enum modem_t { MODEM_AFSK, MODEM_BASEBAND, MODEM_SCRAMBLE, MODEM_QPSK, MODEM_8PSK, MODEM_OFF, MODEM_16_QAM, MODEM_64_QAM } modem_type;
|
enum modem_t { MODEM_AFSK, MODEM_BASEBAND, MODEM_SCRAMBLE, MODEM_QPSK, MODEM_8PSK, MODEM_OFF, MODEM_16_QAM, MODEM_64_QAM, MODEM_AIS } modem_type;
|
||||||
|
|
||||||
/* Usual AFSK. */
|
/* Usual AFSK. */
|
||||||
/* Baseband signal. Not used yet. */
|
/* Baseband signal. Not used yet. */
|
||||||
|
|
71
src/config.c
71
src/config.c
|
@ -799,6 +799,8 @@ void config_init (char *fname, struct audio_s *p_audio_config,
|
||||||
p_audio_config->achan[channel].fulldup = DEFAULT_FULLDUP;
|
p_audio_config->achan[channel].fulldup = DEFAULT_FULLDUP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p_audio_config->fx25_auto_enable = AX25_N2_RETRY_DEFAULT / 2;
|
||||||
|
|
||||||
/* First channel should always be valid. */
|
/* First channel should always be valid. */
|
||||||
/* If there is no ADEVICE, it uses default device in mono. */
|
/* If there is no ADEVICE, it uses default device in mono. */
|
||||||
|
|
||||||
|
@ -1274,7 +1276,12 @@ void config_init (char *fname, struct audio_s *p_audio_config,
|
||||||
dw_printf ("Line %d: Missing data transmission speed for MODEM command.\n", line);
|
dw_printf ("Line %d: Missing data transmission speed for MODEM command.\n", line);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
n = atoi(t);
|
if (strcasecmp(t,"AIS") == 0) {
|
||||||
|
n = 12345; // See special case later.
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
n = atoi(t);
|
||||||
|
}
|
||||||
if (n >= MIN_BAUD && n <= MAX_BAUD) {
|
if (n >= MIN_BAUD && n <= MAX_BAUD) {
|
||||||
p_audio_config->achan[channel].baud = n;
|
p_audio_config->achan[channel].baud = n;
|
||||||
if (n != 300 && n != 1200 && n != 2400 && n != 4800 && n != 9600 && n != 19200) {
|
if (n != 300 && n != 1200 && n != 2400 && n != 4800 && n != 9600 && n != 19200) {
|
||||||
|
@ -1316,6 +1323,11 @@ void config_init (char *fname, struct audio_s *p_audio_config,
|
||||||
p_audio_config->achan[channel].mark_freq = 0;
|
p_audio_config->achan[channel].mark_freq = 0;
|
||||||
p_audio_config->achan[channel].space_freq = 0;
|
p_audio_config->achan[channel].space_freq = 0;
|
||||||
}
|
}
|
||||||
|
else if (p_audio_config->achan[channel].baud == 12345) {
|
||||||
|
p_audio_config->achan[channel].modem_type = MODEM_AIS;
|
||||||
|
p_audio_config->achan[channel].mark_freq = 0;
|
||||||
|
p_audio_config->achan[channel].space_freq = 0;
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
p_audio_config->achan[channel].modem_type = MODEM_SCRAMBLE;
|
p_audio_config->achan[channel].modem_type = MODEM_SCRAMBLE;
|
||||||
p_audio_config->achan[channel].mark_freq = 0;
|
p_audio_config->achan[channel].mark_freq = 0;
|
||||||
|
@ -2190,6 +2202,63 @@ void config_init (char *fname, struct audio_s *p_audio_config,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FX25TX n - Enable FX.25 transmission. Default off.
|
||||||
|
* 0 = off, 1 = auto mode, others are suggestions for testing
|
||||||
|
* or special cases. 16, 32, 64 is number of parity bytes to add.
|
||||||
|
* Also set by "-X n" command line option.
|
||||||
|
* Current a global setting. Could be per channel someday.
|
||||||
|
*/
|
||||||
|
|
||||||
|
else if (strcasecmp(t, "FX25TX") == 0) {
|
||||||
|
int n;
|
||||||
|
t = split(NULL,0);
|
||||||
|
if (t == NULL) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Line %d: Missing FEC mode for FX25TX command.\n", line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
n = atoi(t);
|
||||||
|
if (n >= 0 && n < 200) {
|
||||||
|
p_audio_config->fx25_xmit_enable = n;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
p_audio_config->fx25_xmit_enable = 1;
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Line %d: Unreasonable value for FX.25 transmission mode. Using %d.\n",
|
||||||
|
line, p_audio_config->fx25_xmit_enable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FX25AUTO n - Enable Automatic use of FX.25 for connected mode.
|
||||||
|
* Automatically enable, for that session only, when an identical
|
||||||
|
* frame is sent more than this number of times.
|
||||||
|
* Default 5 based on half of default RETRY.
|
||||||
|
* 0 to disable feature.
|
||||||
|
* Current a global setting. Could be per channel someday.
|
||||||
|
*/
|
||||||
|
|
||||||
|
else if (strcasecmp(t, "FX25AUTO") == 0) {
|
||||||
|
int n;
|
||||||
|
t = split(NULL,0);
|
||||||
|
if (t == NULL) {
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Line %d: Missing count for FX25AUTO command.\n", line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
n = atoi(t);
|
||||||
|
if (n >= 0 && n < 20) {
|
||||||
|
p_audio_config->fx25_auto_enable = n;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
p_audio_config->fx25_auto_enable = AX25_N2_RETRY_DEFAULT / 2;
|
||||||
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
dw_printf ("Line %d: Unreasonable count for connected mode automatic FX.25. Using %d.\n",
|
||||||
|
line, p_audio_config->fx25_auto_enable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ==================== APRS Digipeater parameters ====================
|
* ==================== APRS Digipeater parameters ====================
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -54,6 +54,7 @@
|
||||||
#include "dwgpsnmea.h"
|
#include "dwgpsnmea.h"
|
||||||
#include "decode_aprs.h"
|
#include "decode_aprs.h"
|
||||||
#include "telemetry.h"
|
#include "telemetry.h"
|
||||||
|
#include "ais.h"
|
||||||
|
|
||||||
|
|
||||||
#define TRUE 1
|
#define TRUE 1
|
||||||
|
@ -109,6 +110,7 @@ static void aprs_status_report (decode_aprs_t *A, char *, int);
|
||||||
static void aprs_general_query (decode_aprs_t *A, char *, int, int quiet);
|
static void aprs_general_query (decode_aprs_t *A, char *, int, int quiet);
|
||||||
static void aprs_directed_station_query (decode_aprs_t *A, char *addressee, char *query, int quiet);
|
static void aprs_directed_station_query (decode_aprs_t *A, char *addressee, char *query, int quiet);
|
||||||
static void aprs_telemetry (decode_aprs_t *A, char *info, int info_len, int quiet);
|
static void aprs_telemetry (decode_aprs_t *A, char *info, int info_len, int quiet);
|
||||||
|
static void aprs_user_defined (decode_aprs_t *A, char *, int);
|
||||||
static void aprs_raw_touch_tone (decode_aprs_t *A, char *, int);
|
static void aprs_raw_touch_tone (decode_aprs_t *A, char *, int);
|
||||||
static void aprs_morse_code (decode_aprs_t *A, char *, int);
|
static void aprs_morse_code (decode_aprs_t *A, char *, int);
|
||||||
static void aprs_positionless_weather_report (decode_aprs_t *A, unsigned char *, int);
|
static void aprs_positionless_weather_report (decode_aprs_t *A, unsigned char *, int);
|
||||||
|
@ -232,6 +234,22 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Application might be in the destination field for most message types.
|
||||||
|
* MIC-E format has part of location in the destination field.
|
||||||
|
*/
|
||||||
|
|
||||||
|
switch (*pinfo) { /* "DTI" data type identifier. */
|
||||||
|
|
||||||
|
case '\'': /* Old Mic-E Data */
|
||||||
|
case '`': /* Current Mic-E Data */
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
decode_tocall (A, dest);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
switch (*pinfo) { /* "DTI" data type identifier. */
|
switch (*pinfo) { /* "DTI" data type identifier. */
|
||||||
|
|
||||||
case '!': /* Position without timestamp (no APRS messaging). */
|
case '!': /* Position without timestamp (no APRS messaging). */
|
||||||
|
@ -323,20 +341,8 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '{': /* user defined data */
|
case '{': /* user defined data */
|
||||||
/* http://www.aprs.org/aprs11/expfmts.txt */
|
|
||||||
|
|
||||||
if (strncmp((char*)pinfo, "{tt", 3) == 0) {
|
aprs_user_defined (A, (char*)pinfo, info_len);
|
||||||
aprs_raw_touch_tone (A, (char*)pinfo, info_len);
|
|
||||||
}
|
|
||||||
else if (strncmp((char*)pinfo, "{mc", 3) == 0) {
|
|
||||||
aprs_morse_code (A, (char*)pinfo, info_len);
|
|
||||||
}
|
|
||||||
else if (strncmp((char*)pinfo, "{{", 2) == 0) {
|
|
||||||
snprintf (A->g_msg_type, sizeof(A->g_msg_type), "User-Defined Experimental");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
//aprs_user_defined (A, pinfo, info_len);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 't': /* Raw touch tone data - NOT PART OF STANDARD */
|
case 't': /* Raw touch tone data - NOT PART OF STANDARD */
|
||||||
|
@ -382,22 +388,6 @@ void decode_aprs (decode_aprs_t *A, packet_t pp, int quiet)
|
||||||
symbols_from_dest_or_src (*pinfo, A->g_src, dest, &A->g_symbol_table, &A->g_symbol_code);
|
symbols_from_dest_or_src (*pinfo, A->g_src, dest, &A->g_symbol_table, &A->g_symbol_code);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Application might be in the destination field for most message types.
|
|
||||||
* MIC-E format has part of location in the destination field.
|
|
||||||
*/
|
|
||||||
|
|
||||||
switch (*pinfo) { /* "DTI" data type identifier. */
|
|
||||||
|
|
||||||
case '\'': /* Old Mic-E Data */
|
|
||||||
case '`': /* Current Mic-E Data */
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
decode_tocall (A, dest);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
} /* end decode_aprs */
|
} /* end decode_aprs */
|
||||||
|
|
||||||
|
|
||||||
|
@ -2320,6 +2310,50 @@ static void aprs_telemetry (decode_aprs_t *A, char *info, int ilen, int quiet)
|
||||||
} /* end aprs_telemetry */
|
} /* end aprs_telemetry */
|
||||||
|
|
||||||
|
|
||||||
|
/*------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* Function: aprs_user_defined
|
||||||
|
*
|
||||||
|
* Purpose: Decode user defined data.
|
||||||
|
*
|
||||||
|
* Inputs: info - Pointer to Information field.
|
||||||
|
* ilen - Information field length.
|
||||||
|
*
|
||||||
|
* Description: APRS Protocol Specification, Chapter 18
|
||||||
|
* User IDs allocated here: http://www.aprs.org/aprs11/expfmts.txt
|
||||||
|
*
|
||||||
|
*------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
static void aprs_user_defined (decode_aprs_t *A, char *info, int ilen)
|
||||||
|
{
|
||||||
|
if (strncmp(info, "{tt", 3) == 0) { // Historical. Should probably use DT.
|
||||||
|
aprs_raw_touch_tone (A, info, ilen);
|
||||||
|
}
|
||||||
|
else if (strncmp(info, "{mc", 3) == 0) { // Historical. Should probably use DM.
|
||||||
|
aprs_morse_code (A, info, ilen);
|
||||||
|
}
|
||||||
|
else if (info[0] == '{' && info[1] == USER_DEF_USER_ID && info[2] == USER_DEF_TYPE_AIS) {
|
||||||
|
double lat, lon;
|
||||||
|
float knots, course;
|
||||||
|
|
||||||
|
ais_parse (info+3, 0, A->g_msg_type, sizeof(A->g_msg_type), A->g_name, sizeof(A->g_name), &lat, &lon, &knots, &course);
|
||||||
|
|
||||||
|
A->g_lat = lat;
|
||||||
|
A->g_lon = lon;
|
||||||
|
A->g_speed_mph = DW_KNOTS_TO_MPH(knots);
|
||||||
|
A->g_course = course;
|
||||||
|
strcpy (A->g_mfr, "");
|
||||||
|
}
|
||||||
|
else if (strncmp(info, "{{", 2) == 0) {
|
||||||
|
snprintf (A->g_msg_type, sizeof(A->g_msg_type), "User-Defined Experimental");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
snprintf (A->g_msg_type, sizeof(A->g_msg_type), "User-Defined Data");
|
||||||
|
}
|
||||||
|
|
||||||
|
} /* end aprs_user_defined */
|
||||||
|
|
||||||
|
|
||||||
/*------------------------------------------------------------------
|
/*------------------------------------------------------------------
|
||||||
*
|
*
|
||||||
* Function: aprs_raw_touch_tone
|
* Function: aprs_raw_touch_tone
|
||||||
|
|
|
@ -113,7 +113,9 @@ static inline float agc (float in, float fast_attack, float slow_decay, float *p
|
||||||
*
|
*
|
||||||
* Purpose: Initialize the 9600 (or higher) baud demodulator.
|
* Purpose: Initialize the 9600 (or higher) baud demodulator.
|
||||||
*
|
*
|
||||||
* Inputs: samples_per_sec - Number of samples per second.
|
* Inputs: modem_type - Determines whether scrambling is used.
|
||||||
|
*
|
||||||
|
* samples_per_sec - Number of samples per second.
|
||||||
* Might be upsampled in hopes of
|
* Might be upsampled in hopes of
|
||||||
* reducing the PLL jitter.
|
* reducing the PLL jitter.
|
||||||
*
|
*
|
||||||
|
@ -125,12 +127,13 @@ static inline float agc (float in, float fast_attack, float slow_decay, float *p
|
||||||
*
|
*
|
||||||
*----------------------------------------------------------------*/
|
*----------------------------------------------------------------*/
|
||||||
|
|
||||||
void demod_9600_init (int samples_per_sec, int baud, struct demodulator_state_s *D)
|
void demod_9600_init (enum modem_t modem_type, int samples_per_sec, int baud, struct demodulator_state_s *D)
|
||||||
{
|
{
|
||||||
float fc;
|
float fc;
|
||||||
int j;
|
int j;
|
||||||
|
|
||||||
memset (D, 0, sizeof(struct demodulator_state_s));
|
memset (D, 0, sizeof(struct demodulator_state_s));
|
||||||
|
D->modem_type = modem_type;
|
||||||
D->num_slicers = 1;
|
D->num_slicers = 1;
|
||||||
|
|
||||||
// Multiple profiles in future?
|
// Multiple profiles in future?
|
||||||
|
@ -512,7 +515,7 @@ inline static void nudge_pll (int chan, int subchan, int slice, float demod_out_
|
||||||
|
|
||||||
/* Overflow. Was large positive, wrapped around, now large negative. */
|
/* Overflow. Was large positive, wrapped around, now large negative. */
|
||||||
|
|
||||||
hdlc_rec_bit (chan, subchan, slice, demod_out_f > 0, 1, D->slicer[slice].lfsr);
|
hdlc_rec_bit (chan, subchan, slice, demod_out_f > 0, D->modem_type == MODEM_SCRAMBLE, D->slicer[slice].lfsr);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
#include "fsk_demod_state.h"
|
#include "fsk_demod_state.h"
|
||||||
|
|
||||||
|
|
||||||
void demod_9600_init (int samples_per_sec, int baud, struct demodulator_state_s *D);
|
void demod_9600_init (enum modem_t modem_type, int samples_per_sec, int baud, struct demodulator_state_s *D);
|
||||||
|
|
||||||
void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D);
|
void demod_9600_process_sample (int chan, int sam, struct demodulator_state_s *D);
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
//
|
//
|
||||||
// This file is part of Dire Wolf, an amateur radio packet TNC.
|
// This file is part of Dire Wolf, an amateur radio packet TNC.
|
||||||
//
|
//
|
||||||
// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019 John Langner, WB2OSZ
|
// Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2019, 2020 John Langner, WB2OSZ
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// This program is free software: you can redistribute it and/or modify
|
||||||
// it under the terms of the GNU General Public License as published by
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
@ -284,7 +284,7 @@ int main (int argc, char *argv[])
|
||||||
text_color_init(t_opt);
|
text_color_init(t_opt);
|
||||||
text_color_set(DW_COLOR_INFO);
|
text_color_set(DW_COLOR_INFO);
|
||||||
//dw_printf ("Dire Wolf version %d.%d (%s) Beta Test 4\n", MAJOR_VERSION, MINOR_VERSION, __DATE__);
|
//dw_printf ("Dire Wolf version %d.%d (%s) Beta Test 4\n", MAJOR_VERSION, MINOR_VERSION, __DATE__);
|
||||||
dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "D", __DATE__);
|
dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "E", __DATE__);
|
||||||
//dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION);
|
//dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION);
|
||||||
|
|
||||||
|
|
||||||
|
@ -416,8 +416,14 @@ int main (int argc, char *argv[])
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
case 'B': /* -B baud rate and modem properties. */
|
case 'B': /* -B baud rate and modem properties. */
|
||||||
|
/* Also implies modem type based on speed. */
|
||||||
B_opt = atoi(optarg);
|
/* Special case "AIS" rather than number. */
|
||||||
|
if (strcasecmp(optarg, "AIS") == 0) {
|
||||||
|
B_opt = 12345; // See special case below.
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
B_opt = atoi(optarg);
|
||||||
|
}
|
||||||
if (B_opt < MIN_BAUD || B_opt > MAX_BAUD) {
|
if (B_opt < MIN_BAUD || B_opt > MAX_BAUD) {
|
||||||
text_color_set(DW_COLOR_ERROR);
|
text_color_set(DW_COLOR_ERROR);
|
||||||
dw_printf ("Use a more reasonable data baud rate in range of %d - %d.\n", MIN_BAUD, MAX_BAUD);
|
dw_printf ("Use a more reasonable data baud rate in range of %d - %d.\n", MIN_BAUD, MAX_BAUD);
|
||||||
|
@ -716,6 +722,12 @@ int main (int argc, char *argv[])
|
||||||
dw_printf ("Bit rate should be standard 4800 rather than specified %d.\n", audio_config.achan[0].baud);
|
dw_printf ("Bit rate should be standard 4800 rather than specified %d.\n", audio_config.achan[0].baud);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (audio_config.achan[0].baud == 12345) {
|
||||||
|
audio_config.achan[0].modem_type = MODEM_AIS;
|
||||||
|
audio_config.achan[0].baud = 9600;
|
||||||
|
audio_config.achan[0].mark_freq = 0;
|
||||||
|
audio_config.achan[0].space_freq = 0;
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
audio_config.achan[0].modem_type = MODEM_SCRAMBLE;
|
audio_config.achan[0].modem_type = MODEM_SCRAMBLE;
|
||||||
audio_config.achan[0].mark_freq = 0;
|
audio_config.achan[0].mark_freq = 0;
|
||||||
|
@ -1367,6 +1379,7 @@ static void usage (char **argv)
|
||||||
dw_printf (" 2400 bps uses QPSK based on V.26 standard.\n");
|
dw_printf (" 2400 bps uses QPSK based on V.26 standard.\n");
|
||||||
dw_printf (" 4800 bps uses 8PSK based on V.27 standard.\n");
|
dw_printf (" 4800 bps uses 8PSK based on V.27 standard.\n");
|
||||||
dw_printf (" 9600 bps and up uses K9NG/G3RUH standard.\n");
|
dw_printf (" 9600 bps and up uses K9NG/G3RUH standard.\n");
|
||||||
|
dw_printf (" AIS for ship Automatic Identification System.\n");
|
||||||
dw_printf (" -g Force G3RUH modem regardless of speed.\n");
|
dw_printf (" -g Force G3RUH modem regardless of speed.\n");
|
||||||
dw_printf (" -j 2400 bps QPSK compatible with direwolf <= 1.5.\n");
|
dw_printf (" -j 2400 bps QPSK compatible with direwolf <= 1.5.\n");
|
||||||
dw_printf (" -J 2400 bps QPSK compatible with MFJ-2400.\n");
|
dw_printf (" -J 2400 bps QPSK compatible with MFJ-2400.\n");
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/********************************************************************************
|
/********************************************************************************
|
||||||
*
|
*
|
||||||
* File: hdlc_rec2.c
|
* File: hdlc_rec2.c
|
||||||
|
@ -763,7 +764,15 @@ static int try_decode (rrbb_t block, int chan, int subchan, int slice, alevel_t
|
||||||
|
|
||||||
expected_fcs = fcs_calc (H.frame_buf, H.frame_len - 2);
|
expected_fcs = fcs_calc (H.frame_buf, H.frame_len - 2);
|
||||||
|
|
||||||
if (actual_fcs == expected_fcs &&
|
if (actual_fcs == expected_fcs && save_audio_config_p->achan[chan].modem_type == MODEM_AIS) {
|
||||||
|
|
||||||
|
// Sanity check for AIS does not seem feasible.
|
||||||
|
// Could possibly check length if we knew all the valid possibilities.
|
||||||
|
|
||||||
|
multi_modem_process_rec_frame (chan, subchan, slice, H.frame_buf, H.frame_len - 2, alevel, retry_conf.retry, 0); /* len-2 to remove FCS. */
|
||||||
|
return 1; /* success */
|
||||||
|
}
|
||||||
|
else if (actual_fcs == expected_fcs &&
|
||||||
sanity_check (H.frame_buf, H.frame_len - 2, retry_conf.retry, save_audio_config_p->achan[chan].sanity_test)) {
|
sanity_check (H.frame_buf, H.frame_len - 2, retry_conf.retry, save_audio_config_p->achan[chan].sanity_test)) {
|
||||||
|
|
||||||
// TODO: Shouldn't be necessary to pass chan, subchan, alevel into
|
// TODO: Shouldn't be necessary to pass chan, subchan, alevel into
|
||||||
|
|
|
@ -101,6 +101,9 @@
|
||||||
#include "hdlc_rec2.h"
|
#include "hdlc_rec2.h"
|
||||||
#include "dlq.h"
|
#include "dlq.h"
|
||||||
#include "fx25.h"
|
#include "fx25.h"
|
||||||
|
#include "version.h"
|
||||||
|
#include "ais.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Properties of the radio channels.
|
// Properties of the radio channels.
|
||||||
|
@ -319,7 +322,21 @@ void multi_modem_process_rec_frame (int chan, int subchan, int slice, unsigned c
|
||||||
assert (subchan >= 0 && subchan < MAX_SUBCHANS);
|
assert (subchan >= 0 && subchan < MAX_SUBCHANS);
|
||||||
assert (slice >= 0 && slice < MAX_SUBCHANS);
|
assert (slice >= 0 && slice < MAX_SUBCHANS);
|
||||||
|
|
||||||
pp = ax25_from_frame (fbuf, flen, alevel);
|
// Special encapsulation for AIS so it can be treated normally pretty much everywhere else.
|
||||||
|
|
||||||
|
if (save_audio_config_p->achan[chan].modem_type == MODEM_AIS) {
|
||||||
|
char nmea[256];
|
||||||
|
ais_to_nmea (fbuf, flen, nmea, sizeof(nmea));
|
||||||
|
|
||||||
|
char monfmt[276];
|
||||||
|
snprintf (monfmt, sizeof(monfmt), "AIS>%s%1d%1d:{%c%c%s", APP_TOCALL, MAJOR_VERSION, MINOR_VERSION, USER_DEF_USER_ID, USER_DEF_TYPE_AIS, nmea);
|
||||||
|
pp = ax25_from_text (monfmt, 1);
|
||||||
|
|
||||||
|
// alevel gets in there somehow making me question why it is passed thru here.
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pp = ax25_from_frame (fbuf, flen, alevel);
|
||||||
|
}
|
||||||
|
|
||||||
if (pp == NULL) {
|
if (pp == NULL) {
|
||||||
text_color_set(DW_COLOR_ERROR);
|
text_color_set(DW_COLOR_ERROR);
|
||||||
|
|
|
@ -1,8 +1,20 @@
|
||||||
|
|
||||||
/* Dire Wolf version 1.6 */
|
/* Dire Wolf version 1.6 */
|
||||||
|
|
||||||
|
// Put in destination field to identify the equipment used.
|
||||||
|
|
||||||
#define APP_TOCALL "APDW" // Assigned by WB4APR in tocalls.txt
|
#define APP_TOCALL "APDW" // Assigned by WB4APR in tocalls.txt
|
||||||
|
|
||||||
|
// This now comes from compile command line options.
|
||||||
|
|
||||||
//#define MAJOR_VERSION 1
|
//#define MAJOR_VERSION 1
|
||||||
//#define MINOR_VERSION 6
|
//#define MINOR_VERSION 6
|
||||||
//#define EXTRA_VERSION "Beta Test"
|
//#define EXTRA_VERSION "Beta Test"
|
||||||
|
|
||||||
|
|
||||||
|
// For user-defined data format.
|
||||||
|
// APRS protocol spec Chapter 18 and http://www.aprs.org/aprs11/expfmts.txt
|
||||||
|
|
||||||
|
#define USER_DEF_USER_ID 'D' // user id D for direwolf
|
||||||
|
|
||||||
|
#define USER_DEF_TYPE_AIS 'A' // data type A for AIS NMEA sentence
|
||||||
|
|
|
@ -105,6 +105,7 @@ endif()
|
||||||
# Unit test for demodulators
|
# Unit test for demodulators
|
||||||
list(APPEND atest9_SOURCES
|
list(APPEND atest9_SOURCES
|
||||||
${CUSTOM_SRC_DIR}/atest.c
|
${CUSTOM_SRC_DIR}/atest.c
|
||||||
|
${CUSTOM_SRC_DIR}/ais.c
|
||||||
${CUSTOM_SRC_DIR}/demod.c
|
${CUSTOM_SRC_DIR}/demod.c
|
||||||
${CUSTOM_SRC_DIR}/dsp.c
|
${CUSTOM_SRC_DIR}/dsp.c
|
||||||
${CUSTOM_SRC_DIR}/demod_afsk.c
|
${CUSTOM_SRC_DIR}/demod_afsk.c
|
||||||
|
@ -160,6 +161,7 @@ endif()
|
||||||
# Unit test for inner digipeater algorithm
|
# Unit test for inner digipeater algorithm
|
||||||
list(APPEND dtest_SOURCES
|
list(APPEND dtest_SOURCES
|
||||||
${CUSTOM_SRC_DIR}/digipeater.c
|
${CUSTOM_SRC_DIR}/digipeater.c
|
||||||
|
${CUSTOM_SRC_DIR}/ais.c
|
||||||
${CUSTOM_SRC_DIR}/dedupe.c
|
${CUSTOM_SRC_DIR}/dedupe.c
|
||||||
${CUSTOM_SRC_DIR}/pfilter.c
|
${CUSTOM_SRC_DIR}/pfilter.c
|
||||||
${CUSTOM_SRC_DIR}/ax25_pad.c
|
${CUSTOM_SRC_DIR}/ax25_pad.c
|
||||||
|
@ -247,6 +249,7 @@ target_link_libraries(tttexttest
|
||||||
# Unit test for Packet Filtering.
|
# Unit test for Packet Filtering.
|
||||||
list(APPEND pftest_SOURCES
|
list(APPEND pftest_SOURCES
|
||||||
${CUSTOM_SRC_DIR}/pfilter.c
|
${CUSTOM_SRC_DIR}/pfilter.c
|
||||||
|
${CUSTOM_SRC_DIR}/ais.c
|
||||||
${CUSTOM_SRC_DIR}/ax25_pad.c
|
${CUSTOM_SRC_DIR}/ax25_pad.c
|
||||||
${CUSTOM_SRC_DIR}/textcolor.c
|
${CUSTOM_SRC_DIR}/textcolor.c
|
||||||
${CUSTOM_SRC_DIR}/fcs_calc.c
|
${CUSTOM_SRC_DIR}/fcs_calc.c
|
||||||
|
@ -499,6 +502,7 @@ if(OPTIONAL_TEST)
|
||||||
# Unit test for IGate
|
# Unit test for IGate
|
||||||
list(APPEND itest_SOURCES
|
list(APPEND itest_SOURCES
|
||||||
${CUSTOM_SRC_DIR}/igate.c
|
${CUSTOM_SRC_DIR}/igate.c
|
||||||
|
${CUSTOM_SRC_DIR}/ais.c
|
||||||
${CUSTOM_SRC_DIR}/ax25_pad.c
|
${CUSTOM_SRC_DIR}/ax25_pad.c
|
||||||
${CUSTOM_SRC_DIR}/fcs_calc.c
|
${CUSTOM_SRC_DIR}/fcs_calc.c
|
||||||
${CUSTOM_SRC_DIR}/mheard.c
|
${CUSTOM_SRC_DIR}/mheard.c
|
||||||
|
@ -544,6 +548,7 @@ if(OPTIONAL_TEST)
|
||||||
# For demodulator tweaking experiments.
|
# For demodulator tweaking experiments.
|
||||||
list(APPEND testagc_SOURCES
|
list(APPEND testagc_SOURCES
|
||||||
${CUSTOM_SRC_DIR}/atest.c
|
${CUSTOM_SRC_DIR}/atest.c
|
||||||
|
${CUSTOM_SRC_DIR}/ais.c
|
||||||
${CUSTOM_SRC_DIR}/demod.c
|
${CUSTOM_SRC_DIR}/demod.c
|
||||||
${CUSTOM_SRC_DIR}/dsp.c
|
${CUSTOM_SRC_DIR}/dsp.c
|
||||||
${CUSTOM_SRC_DIR}/demod_afsk.c
|
${CUSTOM_SRC_DIR}/demod_afsk.c
|
||||||
|
@ -592,6 +597,7 @@ if(OPTIONAL_TEST)
|
||||||
# Send GPS location to KISS TNC each second.
|
# Send GPS location to KISS TNC each second.
|
||||||
list(APPEND walk96_SOURCES
|
list(APPEND walk96_SOURCES
|
||||||
${CUSTOM_SRC_DIR}/walk96.c
|
${CUSTOM_SRC_DIR}/walk96.c
|
||||||
|
${CUSTOM_SRC_DIR}/ais.c
|
||||||
${CUSTOM_SRC_DIR}/dwgps.c
|
${CUSTOM_SRC_DIR}/dwgps.c
|
||||||
${CUSTOM_SRC_DIR}/dwgpsnmea.c
|
${CUSTOM_SRC_DIR}/dwgpsnmea.c
|
||||||
${CUSTOM_SRC_DIR}/dwgpsd.c
|
${CUSTOM_SRC_DIR}/dwgpsd.c
|
||||||
|
|
Loading…
Reference in New Issue