mirror of https://github.com/wb2osz/direwolf.git
AIS refinements.
This commit is contained in:
parent
b99f9f33c2
commit
74a5c34a94
189
src/ais.c
189
src/ais.c
|
@ -50,6 +50,42 @@
|
|||
#include "textcolor.h"
|
||||
#include "ais.h"
|
||||
|
||||
// Lengths, in bits, for the AIS message types.
|
||||
|
||||
#define NUM_TYPES 27
|
||||
static const struct {
|
||||
short min;
|
||||
short max;
|
||||
} valid_len[NUM_TYPES+1] = {
|
||||
{ -1, -1 }, // 0 not used
|
||||
{ 168, 168 }, // 1
|
||||
{ 168, 168 }, // 2
|
||||
{ 168, 168 }, // 3
|
||||
{ 168, 168 }, // 4
|
||||
{ 424, 424 }, // 5
|
||||
{ 72, 1008 }, // 6 multipurpose
|
||||
{ 72, 168 }, // 7 increments of 32 bits
|
||||
{ 168, 1008 }, // 8 multipurpose
|
||||
{ 168, 168 }, // 9
|
||||
{ 72, 72 }, // 10
|
||||
{ 168, 168 }, // 11
|
||||
{ 72, 1008 }, // 12
|
||||
{ 72, 168 }, // 13 increments of 32 bits
|
||||
{ 40, 1008 }, // 14
|
||||
{ 88, 160 }, // 15
|
||||
{ 96, 114 }, // 16 96 or 114, not range
|
||||
{ 80, 816 }, // 17
|
||||
{ 168, 168 }, // 18
|
||||
{ 312, 312 }, // 19
|
||||
{ 72, 160 }, // 20
|
||||
{ 272, 360 }, // 21
|
||||
{ 168, 168 }, // 22
|
||||
{ 160, 160 }, // 23
|
||||
{ 160, 168 }, // 24
|
||||
{ 40, 168 }, // 25
|
||||
{ 60, 1064 }, // 26
|
||||
{ 96, 168 } // 27 96 or 168, not range
|
||||
};
|
||||
|
||||
/*-------------------------------------------------------------------
|
||||
*
|
||||
|
@ -113,22 +149,53 @@ static double get_field_latlon (unsigned char *base, unsigned int start, unsigne
|
|||
// 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);
|
||||
|
||||
// Message type 27 uses lower resolution, 17 & 18 bits rather than 27 & 28.
|
||||
// It encodes minutes/10 rather than normal minutes/10000.
|
||||
}
|
||||
|
||||
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);
|
||||
// For aircraft it is knots, not deciknots.
|
||||
return ((float)get_field(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);
|
||||
return ((float)get_field(base, start, len) * 0.1);
|
||||
}
|
||||
|
||||
static int get_field_ascii (unsigned char *base, unsigned int start, unsigned int len)
|
||||
{
|
||||
assert (len == 6);
|
||||
int ch = get_field(base, start, len);
|
||||
if (ch < 32) ch += 64;
|
||||
return (ch);
|
||||
}
|
||||
|
||||
static void get_field_string (unsigned char *base, unsigned int start, unsigned int len, char *result)
|
||||
{
|
||||
assert (len % 6 == 0);
|
||||
int nc = len / 6; // Number of characters.
|
||||
// Caller better provide space for at least this +1.
|
||||
// No bounds checking here.
|
||||
for (int i = 0; i < nc; i++) {
|
||||
result[i] = get_field_ascii (base, start + i * 6, 6);
|
||||
}
|
||||
result[nc] = '\0';
|
||||
// Officially it should be terminated/padded with @ but we also see trailing spaces.
|
||||
char *p = strchr(result, '@');
|
||||
if (p != NULL) *p = '\0';
|
||||
for (int k = strlen(result) - 1; k >= 0 && result[k] == ' '; k--) {
|
||||
result[k] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*-------------------------------------------------------------------
|
||||
*
|
||||
|
@ -238,18 +305,22 @@ void ais_to_nmea (unsigned char *ais, int ais_len, char *nmea, int nmea_size)
|
|||
* mssi 9 digit identifier.
|
||||
* odlat latitude.
|
||||
* odlon longitude.
|
||||
* ofknots speed.
|
||||
* ofknots speed, knots.
|
||||
* ofcourse direction of travel.
|
||||
* ofalt_m altitude, meters.
|
||||
* symtab APRS symbol table.
|
||||
* symbol APRS symbol code.
|
||||
*
|
||||
* Returns: 0 for success, -1 for error.
|
||||
*
|
||||
*--------------------------------------------------------------------*/
|
||||
|
||||
// Maximum NMEA sentence length is 82 according to some people.
|
||||
// Maximum NMEA sentence length is 82, including CR/LF.
|
||||
// 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)
|
||||
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, float *ofalt_m, char *symtab, char *symbol, char *comment, int comment_size)
|
||||
{
|
||||
char stemp[NMEA_MAX_LEN]; /* Make copy because parsing is destructive. */
|
||||
|
||||
|
@ -258,6 +329,7 @@ int ais_parse (char *sentence, int quiet, char *descr, int descr_size, char *mss
|
|||
*odlon = G_UNKNOWN;
|
||||
*ofknots = G_UNKNOWN;
|
||||
*ofcourse = G_UNKNOWN;
|
||||
*ofalt_m = G_UNKNOWN;
|
||||
|
||||
strlcpy (stemp, sentence, sizeof(stemp));
|
||||
|
||||
|
@ -349,10 +421,15 @@ int ais_parse (char *sentence, int quiet, char *descr, int descr_size, char *mss
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// Extract the fields of interest from a few message types.
|
||||
// Don't get too carried away.
|
||||
|
||||
int type = get_field(ais, 0, 6);
|
||||
|
||||
if (type >= 1 && type <= 27) {
|
||||
snprintf (mssi, mssi_size, "%d", get_field(ais, 8, 30));
|
||||
}
|
||||
switch (type) {
|
||||
|
||||
case 1: // Position Report Class A
|
||||
|
@ -360,7 +437,8 @@ int ais_parse (char *sentence, int quiet, char *descr, int descr_size, char *mss
|
|||
case 3:
|
||||
|
||||
snprintf (descr, descr_size, "AIS %d: Position Report Class A", type);
|
||||
snprintf (mssi, mssi_size, "%d", get_field(ais, 8, 30));
|
||||
*symtab = '/';
|
||||
*symbol = 's'; // Power boat (ship) side view
|
||||
*odlon = get_field_latlon(ais, 61, 28);
|
||||
*odlat = get_field_latlon(ais, 89, 27);
|
||||
*ofknots = get_field_speed(ais, 50, 10);
|
||||
|
@ -370,7 +448,8 @@ int ais_parse (char *sentence, int quiet, char *descr, int descr_size, char *mss
|
|||
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));
|
||||
*symtab = '\\';
|
||||
*symbol = 'L'; // Lighthouse
|
||||
//year = get_field(ais, 38, 14);
|
||||
//month = get_field(ais, 52, 4);
|
||||
//day = get_field(ais, 56, 5);
|
||||
|
@ -381,10 +460,47 @@ int ais_parse (char *sentence, int quiet, char *descr, int descr_size, char *mss
|
|||
*odlat = get_field_latlon(ais, 107, 27);
|
||||
break;
|
||||
|
||||
case 5: // Static and Voyage Related Data
|
||||
|
||||
snprintf (descr, descr_size, "AIS %d: Static and Voyage Related Data", type);
|
||||
*symtab = '/';
|
||||
*symbol = 's'; // Power boat (ship) side view
|
||||
{
|
||||
char callsign[12];
|
||||
char shipname[24];
|
||||
char destination[24];
|
||||
get_field_string(ais, 70, 42, callsign);
|
||||
get_field_string(ais, 112, 120, shipname);
|
||||
get_field_string(ais, 302, 120, destination);
|
||||
|
||||
if (strlen(destination) > 0) {
|
||||
snprintf (comment, comment_size, "%s, %s, dest. %s", shipname, callsign, destination);
|
||||
}
|
||||
else {
|
||||
snprintf (comment, comment_size, "%s, %s", shipname, callsign);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 9: // Standard SAR Aircraft Position Report
|
||||
|
||||
snprintf (descr, descr_size, "AIS %d: SAR Aircraft Position Report", type);
|
||||
*symtab = '/';
|
||||
*symbol = '\''; // Small AIRCRAFT
|
||||
*ofalt_m = get_field(ais, 38, 12); // meters, 4095 means not available
|
||||
*odlon = get_field_latlon(ais, 61, 28);
|
||||
*odlat = get_field_latlon(ais, 89, 27);
|
||||
*ofknots = get_field_speed(ais, 50, 10) * 10.0; // plane is knots, not knots/10
|
||||
*ofcourse = get_field_course(ais, 116, 12);
|
||||
break;
|
||||
|
||||
case 18: // Standard Class B CS Position Report
|
||||
// As an oversimplification, Class A is commercial, B is recreational.
|
||||
|
||||
snprintf (descr, descr_size, "AIS %d: Standard Class B CS Position Report", type);
|
||||
snprintf (mssi, mssi_size, "%d", get_field(ais, 8, 30));
|
||||
*symtab = '/';
|
||||
*symbol = 'Y'; // YACHT (sail)
|
||||
*odlon = get_field_latlon(ais, 57, 28);
|
||||
*odlat = get_field_latlon(ais, 85, 27);
|
||||
break;
|
||||
|
@ -392,11 +508,23 @@ int ais_parse (char *sentence, int quiet, char *descr, int descr_size, char *mss
|
|||
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));
|
||||
*symtab = '/';
|
||||
*symbol = 'Y'; // YACHT (sail)
|
||||
*odlon = get_field_latlon(ais, 57, 28);
|
||||
*odlat = get_field_latlon(ais, 85, 27);
|
||||
break;
|
||||
|
||||
case 27: // Long Range AIS Broadcast message
|
||||
|
||||
snprintf (descr, descr_size, "AIS %d: Long Range AIS Broadcast message", type);
|
||||
*symtab = '\\';
|
||||
*symbol = 's'; // OVERLAY SHIP/boat (top view)
|
||||
*odlon = get_field_latlon(ais, 44, 18) * 1000; // Note: minutes/10 rather than usual /10000.
|
||||
*odlat = get_field_latlon(ais, 62, 17) * 1000;
|
||||
*ofknots = get_field_speed(ais, 79, 6) * 10; // Note: knots, not deciknots.
|
||||
*ofcourse = get_field_course(ais, 85, 9) * 10; // Note: degrees, not decidegrees.
|
||||
break;
|
||||
|
||||
default:
|
||||
snprintf (descr, descr_size, "AIS message type %d", type);
|
||||
break;
|
||||
|
@ -406,4 +534,45 @@ int ais_parse (char *sentence, int quiet, char *descr, int descr_size, char *mss
|
|||
|
||||
} /* end ais_parse */
|
||||
|
||||
// end ais.c
|
||||
|
||||
|
||||
/*-------------------------------------------------------------------
|
||||
*
|
||||
* Name: ais_check_length
|
||||
*
|
||||
* Purpose: Verify frame length against expected.
|
||||
*
|
||||
* Inputs: type Message type, 1 - 27.
|
||||
*
|
||||
* length Number of data octets in in frame.
|
||||
*
|
||||
* Returns: -1 Invalid message type.
|
||||
* 0 Good length.
|
||||
* 1 Unexpected lenth.
|
||||
*
|
||||
*--------------------------------------------------------------------*/
|
||||
|
||||
int ais_check_length (int type, int length)
|
||||
{
|
||||
if (type >= 1 && type <= NUM_TYPES) {
|
||||
int b = length * 8;
|
||||
if (b >= valid_len[type].min && b <= valid_len[type].max) {
|
||||
return (0); // Good.
|
||||
}
|
||||
else {
|
||||
//text_color_set (DW_COLOR_ERROR);
|
||||
//dw_printf("AIS ERROR: type %d, has %d bits when %d to %d expected.\n",
|
||||
// type, b, valid_len[type].min, valid_len[type].max);
|
||||
return (1); // Length out of range.
|
||||
}
|
||||
}
|
||||
else {
|
||||
//text_color_set (DW_COLOR_ERROR);
|
||||
//dw_printf("AIS ERROR: message type %d is invalid.\n", type);
|
||||
return (-1); // Invalid type.
|
||||
}
|
||||
|
||||
} // end ais_check_length
|
||||
|
||||
|
||||
// end ais.c
|
||||
|
|
|
@ -2,4 +2,7 @@
|
|||
|
||||
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);
|
||||
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, float *ofalt_m, char *symtab, char *symbol, char *comment, int comment_size);
|
||||
|
||||
int ais_check_length (int type, int length);
|
||||
|
|
|
@ -2335,13 +2335,17 @@ static void aprs_user_defined (decode_aprs_t *A, char *info, int ilen)
|
|||
else if (info[0] == '{' && info[1] == USER_DEF_USER_ID && info[2] == USER_DEF_TYPE_AIS) {
|
||||
double lat, lon;
|
||||
float knots, course;
|
||||
float alt_meters;
|
||||
|
||||
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);
|
||||
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, &alt_meters, &(A->g_symbol_table), &(A->g_symbol_code),
|
||||
A->g_comment, sizeof(A->g_comment));
|
||||
|
||||
A->g_lat = lat;
|
||||
A->g_lon = lon;
|
||||
A->g_speed_mph = DW_KNOTS_TO_MPH(knots);
|
||||
A->g_course = course;
|
||||
A->g_altitude_ft = DW_METERS_TO_FEET(alt_meters);
|
||||
strcpy (A->g_mfr, "");
|
||||
}
|
||||
else if (strncmp(info, "{{", 2) == 0) {
|
||||
|
|
19
src/demod.c
19
src/demod.c
|
@ -622,6 +622,22 @@ int demod_init (struct audio_s *pa)
|
|||
default: /* Not AFSK */
|
||||
{
|
||||
|
||||
// For AIS we will accept only a good CRC without any fixup attempts.
|
||||
// Even with that, there are still a lot of CRC false matches with random noise.
|
||||
|
||||
if (save_audio_config_p->achan[chan].modem_type == MODEM_AIS) {
|
||||
if (save_audio_config_p->achan[chan].fix_bits != RETRY_NONE) {
|
||||
text_color_set(DW_COLOR_INFO);
|
||||
dw_printf ("Channel %d: FIX_BITS option has been turned off for AIS.\n", chan);
|
||||
save_audio_config_p->achan[chan].fix_bits = RETRY_NONE;
|
||||
}
|
||||
if (save_audio_config_p->achan[chan].passall != 0) {
|
||||
text_color_set(DW_COLOR_INFO);
|
||||
dw_printf ("Channel %d: PASSALL option has been turned off for AIS.\n", chan);
|
||||
save_audio_config_p->achan[chan].passall = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(save_audio_config_p->achan[chan].profiles, "") == 0) {
|
||||
|
||||
/* Apply default if not set earlier. */
|
||||
|
@ -632,13 +648,10 @@ int demod_init (struct audio_s *pa)
|
|||
/* We want higher performance to be the default. */
|
||||
/* "MODEM 9600 -" can be used on very slow CPU if necessary. */
|
||||
|
||||
//#ifndef __arm__
|
||||
strlcpy (save_audio_config_p->achan[chan].profiles, "+", sizeof(save_audio_config_p->achan[chan].profiles));
|
||||
//#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
#ifdef TUNE_ZEROSTUFF
|
||||
zerostuff = TUNE_ZEROSTUFF;
|
||||
#endif
|
||||
|
|
|
@ -104,7 +104,7 @@
|
|||
#include "demod_9600.h" /* for descramble() */
|
||||
#include "audio.h" /* for struct audio_s */
|
||||
//#include "ax25_pad.h" /* for AX25_MAX_ADDR_LEN */
|
||||
|
||||
#include "ais.h"
|
||||
|
||||
//#define DEBUG 1
|
||||
//#define DEBUGx 1
|
||||
|
@ -766,11 +766,14 @@ static int try_decode (rrbb_t block, int chan, int subchan, int slice, alevel_t
|
|||
|
||||
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 */
|
||||
// Sanity check for AIS.
|
||||
if (ais_check_length((H.frame_buf[0] >> 2) & 0x3f, H.frame_len - 2) == 0) {
|
||||
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 {
|
||||
return 0; /* did not pass sanity check */
|
||||
}
|
||||
}
|
||||
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)) {
|
||||
|
|
Loading…
Reference in New Issue