NXDN: Tweak to Frame Sync, restore other sync patterns based on polarity; Fixes random frame skips on NXDN96 trunking;

NXDN: Initial GPS Report Commit;

NXDN: Don't assign GI, TG/RID in VCALL_ASSGN; Leads to duplicate entries in Event History, particularly when private call;

NMEA: Sentence Checker with start, end, checksum, and supplimentary string checks P25p1 Tier 1 Location Service and Also observed on old Type-D system;

LRRP: Fix LRRP.exe style reports to use date/time strings with colons and slashes;

NXDN: Tweak to DCALL to check for a selective retry when blk_num is not equal to pf_num and invalidate, we don't have the memory of the last PDU attempt (and may have successfully received it anyways, even if the radio did not);
This commit is contained in:
lwvmobile
2026-03-20 11:10:15 -04:00
parent a6bdfa913f
commit 56f6f6d78e
6 changed files with 308 additions and 71 deletions

View File

@@ -1409,6 +1409,7 @@ void harris_lptt (dsd_opts * opts, dsd_state * state, uint8_t * input, uint32_t
void nxdn_gps_report(dsd_opts * opts, dsd_state * state, uint8_t * input, uint32_t src);
void utf16_to_text (dsd_state * state, uint8_t wr, uint16_t len, uint8_t * input);
void utf8_to_text (dsd_state * state, uint8_t wr, uint16_t len, uint8_t * input);
uint8_t nmea_sentence_checker(dsd_opts * opts, dsd_state * state, uint8_t * input, uint8_t slot, int len);
//"DMR STEREO"
void dmrBSBootstrap (dsd_opts * opts, dsd_state * state);

View File

@@ -995,8 +995,9 @@ void dmr_locn (dsd_opts * opts, dsd_state * state, uint16_t len, uint8_t * DMR_P
//write to LRRP file
if (opts->lrrp_file_output == 1)
{
char * timestr = getTimeC();
char * datestr = getDateS();
char * timestr = getTimeC();
//open file by name that is supplied in the ncurses terminal, or cli
FILE * pFile; //file pointer

View File

@@ -1212,8 +1212,11 @@ getFrameSync (dsd_opts * opts, dsd_state * state)
{
strncpy (synctest10, (synctest_p - 9), 10); //FSW only
if (
(strcmp (synctest10, "3131331131") == 0 ) //this seems to be the most common 'correct' pattern on Type-C
|| (strcmp (synctest10, "3331331131") == 0 ) //this seems to be the most common 'correct' pattern on Conventional
(strcmp (synctest10, "3131331131") == 0 )
|| (strcmp (synctest10, "3331331131") == 0 )
|| (strcmp (synctest10, "3131331111") == 0 )
// || (strcmp (synctest10, "3331331111") == 0 )
// || (strcmp (synctest10, "3131311131") == 0 )
)
{
@@ -1239,8 +1242,11 @@ getFrameSync (dsd_opts * opts, dsd_state * state)
{
strncpy (synctest10, (synctest_p - 9), 10); //FSW only
if (
(strcmp (synctest10, "1313113313") == 0 ) //this seems to be the most common 'correct' pattern on Type-C
|| (strcmp (synctest10, "1113113313") == 0 ) //this seems to be the most common 'correct' pattern on Conventional
(strcmp (synctest10, "1313113313") == 0 )
|| (strcmp (synctest10, "1113113313") == 0 )
|| (strcmp (synctest10, "1313113333") == 0 )
// || (strcmp (synctest10, "1113113333") == 0 )
// || (strcmp (synctest10, "1313133313") == 0 )
)
{

View File

@@ -129,8 +129,8 @@ void lip_protocol_decoder (dsd_opts * opts, dsd_state * state, uint8_t * input)
if (opts->lrrp_file_output == 1)
{
char * datestr = getDate();
char * timestr = getTime();
char * datestr = getDateS();
char * timestr = getTimeC();
//open file by name that is supplied in the ncurses terminal, or cli
pFile = fopen (opts->lrrp_out_file, "a");
@@ -247,8 +247,8 @@ void nmea_iec_61162_1 (dsd_opts * opts, dsd_state * state, uint8_t * input, uint
if (opts->lrrp_file_output == 1)
{
char * datestr = getDate();
char * timestr = getTime();
char * datestr = getDateS();
char * timestr = getTimeC();
int s = (int)fkph; //rounded interger format for the log report
int a = 0;
@@ -406,8 +406,8 @@ void harris_lptt (dsd_opts * opts, dsd_state * state, uint8_t * input, uint32_t
if (opts->lrrp_file_output == 1 && src != 0 && gps_quality != 3)
{
char * datestr = getDate();
char * timestr = getTime();
char * datestr = getDateS();
char * timestr = getTimeC();
//rounded interger formats for the log report
int s = (int)fkph;
@@ -536,8 +536,8 @@ void dmr_embedded_gps (dsd_opts * opts, dsd_state * state, uint8_t lc_bits[])
if (opts->lrrp_file_output == 1)
{
char * datestr = getDate();
char * timestr = getTime();
char * datestr = getDateS();
char * timestr = getTimeC();
//open file by name that is supplied in the ncurses terminal, or cli
pFile = fopen (opts->lrrp_out_file, "a");
@@ -644,8 +644,9 @@ void apx_embedded_gps (dsd_opts * opts, dsd_state * state, uint8_t lc_bits[])
FILE * pFile; //file pointer
if (opts->lrrp_file_output == 1)
{
char * datestr = getDate();
char * timestr = getTime();
char * datestr = getDateS();
char * timestr = getTimeC();
//open file by name that is supplied in the ncurses terminal, or cli
pFile = fopen (opts->lrrp_out_file, "a");
@@ -719,11 +720,235 @@ void decode_ars(dsd_opts * opts, dsd_state * state, uint8_t * input, int len)
UNUSED(len);
}
//WIP: Based on Denny's always brilliant reverse-engineering of GPS reports
void nxdn_gps_report(dsd_opts * opts, dsd_state * state, uint8_t * input, uint32_t src)
{
//TODO: This
UNUSED(opts);
UNUSED(state);
UNUSED(input);
UNUSED(src);
// Basic fields
// uint8_t quality = (uint8_t)convert_bits_into_output(input+8, 4); //can't seem to work these first two out correctly
// uint8_t num_sat = (uint8_t)convert_bits_into_output(input+12, 4); //can't seem to work these first two out correctly
int16_t elevation = (int16_t)convert_bits_into_output(input+56, 16);
// Speed & Heading (in tenths)
uint16_t speed_raw = (uint16_t)convert_bits_into_output(input+74, 14);
uint16_t heading_raw = (uint16_t)convert_bits_into_output(input+92, 12);
double speed = speed_raw / 10.0; //knots or k/h
double heading = heading_raw / 10.0;
//Date
uint16_t year = (uint16_t)(convert_bits_into_output(input+136,7) + 2000);
uint8_t month = (uint8_t) convert_bits_into_output(input+143,4);
uint8_t day = (uint8_t)(convert_bits_into_output(input+147,5) + 1);
// Longitude (DDMM.mmmm format)
uint16_t lon_degmin = (uint16_t)convert_bits_into_output(input+152, 16);
uint16_t lon_frac = (uint16_t)convert_bits_into_output(input+16, 15);
uint8_t lon_hem = (uint8_t) convert_bits_into_output(input+183, 1);
double lon_minutes = (lon_degmin % 100) + (lon_frac / 10000.0);
double lon_decimal = (lon_degmin / 100) + (lon_minutes / 60.0);
double longitude = (lon_hem == 0) ? lon_decimal : -lon_decimal;
// Latitude (DDMM.mmmm format)
uint16_t lat_degmin = (uint16_t)convert_bits_into_output(input+184,16);
uint16_t lat_frac = (uint16_t)convert_bits_into_output(input+200,15);
uint8_t lat_hem = (uint8_t) convert_bits_into_output(input+215,1);
double lat_minutes = (lat_degmin % 100) + (lat_frac / 10000.0);
double lat_decimal = (lat_degmin / 100) + (lat_minutes / 60.0);
double latitude = (lat_hem == 0) ? lat_decimal : -lat_decimal;
//Time
// uint8_t hour = (uint8_t) convert_bits_into_output(input+247,5);
// uint8_t minute = (uint8_t) convert_bits_into_output(input+252,6);
// uint8_t second = (uint8_t) convert_bits_into_output(input+258,6); //this seems to be zero, recheck time
char deg_glyph[4];
sprintf (deg_glyph, "%s", "°");
//Report
fprintf (stderr, "\n GPS: (%f%s, %f%s) ", latitude, deg_glyph, longitude, deg_glyph);
fprintf (stderr, "Speed: %lf k/h; ", speed);
fprintf (stderr, "COG: %lf; ", heading);
fprintf (stderr, "Elevation: %i; ", elevation);
// fprintf (stderr, "Quality: %d / %d; ", quality, num_sat);
fprintf (stderr, "Date: %04d/%02d/%02d ", year, month, day);
// fprintf (stderr, "Time: %02d:%02d:%02d ", hour, minute, second);
//save to LRRP report for mapping/logging
if (opts->lrrp_file_output == 1 && src != 0) //&& quality?
{
char * datestr = getDateS();
char * timestr = getTimeC();
//rounded interger formats for the log report
int s = (int)speed;
int a = (int)heading;
//open file by name that is supplied in the ncurses terminal, or cli
FILE * pFile; //file pointer
pFile = fopen (opts->lrrp_out_file, "a");
fprintf (pFile, "%s\t", datestr );
fprintf (pFile, "%s\t", timestr );
fprintf (pFile, "%08d\t", src);
fprintf (pFile, "%.6lf\t", latitude);
fprintf (pFile, "%.6lf\t", longitude);
fprintf (pFile, "%d\t ", s);
fprintf (pFile, "%d\t ", a);
fprintf (pFile, "\n");
fclose (pFile);
if (timestr != NULL)
{
free (timestr);
timestr = NULL;
}
if (datestr != NULL)
{
free (datestr);
datestr = NULL;
}
}
//reset source and target from PDU storage
state->dmr_lrrp_source[0] = 0;
state->dmr_lrrp_target[0] = 0;
}
uint8_t nmea_sentence_checker(dsd_opts * opts, dsd_state * state, uint8_t * input, uint8_t slot, int len)
{
//Check Start and End, and Validity w/ Checksum
int i;
uint8_t start_value = (uint8_t)ConvertBitIntoBytes(input, 8);
uint8_t end_value = 0;
uint8_t checksum_calc = 0;
uint8_t checksum_ext = 0;
char checksum_str[3];
memset(checksum_str, 0, sizeof(checksum_str));
uint8_t valid = 0;
if (start_value == '$' || start_value == '!')
{
//debug
// fprintf (stderr, "Start Okay! ");
for (i = 1; i < len; i++)
{
uint8_t value = (uint8_t)ConvertBitIntoBytes(input+(i*8), 8);
if (value == '*')
{
end_value = value;
checksum_str[0] = (uint8_t)ConvertBitIntoBytes(input+(i*8)+8, 8);
checksum_str[1] = (uint8_t)ConvertBitIntoBytes(input+(i*8)+16, 8);
checksum_str[2] = 0;
sscanf (checksum_str, "%hhX", &checksum_ext);
break;
}
else if (value >= 0x20 && value < 0x7F)
{
checksum_calc ^= value;
continue;
}
else break; //anything else that isn't ascii range values
}
//debug
// if (end_value == '*')
// fprintf (stderr, "Checksum Present! ");
if (checksum_ext == checksum_calc)
{
// fprintf (stderr, "Checksum Okay! (%02X / %02X)", checksum_calc, checksum_ext);
valid = 1;
}
else
{
// fprintf (stderr, "Checksum Err! (%02X / %02X) ", checksum_calc, checksum_ext);
valid = 0;
}
}
//dump, if valid
memset(state->event_history_s[slot].Event_History_Items[0].text_message, 0, sizeof(state->event_history_s[slot].Event_History_Items[0].text_message));
if (valid)
{
uint8_t ascii = 0;
uint16_t crlf = 0;
for (i = 0; i < len; i++)
{
ascii = (uint8_t)ConvertBitIntoBytes(input+(i*8), 8);
crlf = (uint16_t)ConvertBitIntoBytes(input+(i*8), 16);
if (ascii >= 0x20 && ascii < 0x7F)
{
fprintf (stderr, "%c", ascii);
state->event_history_s[slot].Event_History_Items[0].text_message[i] = ascii;
}
else if (crlf == 0x0D0A) //NMEA Sentences end with 0x0D 0x0A (CR/LF)
{
fprintf (stderr, "%s", " ");
state->event_history_s[slot].Event_History_Items[0].text_message[i+0] = 0x20; //Space
state->event_history_s[slot].Event_History_Items[0].text_message[i+1] = 0x20; //Space
i+=2;
break;
}
else break; //anything else that isn't ascii range values
}
//check for supplimentary string i.e., $PP25
ascii = (uint8_t)ConvertBitIntoBytes(input+(i*8), 8);
if (crlf == 0x0D0A && ascii == '$')
{
for (i = i; i < len; i++)
{
ascii = (uint8_t)ConvertBitIntoBytes(input+(i*8), 8);
crlf = (uint16_t)ConvertBitIntoBytes(input+(i*8), 16);
if (ascii >= 0x20 && ascii < 0x7F)
{
fprintf (stderr, "%c", ascii);
state->event_history_s[slot].Event_History_Items[0].text_message[i] = ascii;
}
else if (crlf == 0x0D0A) //Supplimentary Sentences end with 0x0D 0x0A (CR/LF)
{
state->event_history_s[slot].Event_History_Items[0].text_message[i] = 0x20;
break;
}
else break; //anything else that isn't ascii range values
}
}
//debug
// fprintf(stderr, "\n %s", state->event_history_s[slot].Event_History_Items[0].text_message);
//dump to event history
state->event_history_s[slot].Event_History_Items[0].text_message[i] = '\0';
uint32_t source = state->dmr_lrrp_source[slot];
uint32_t target = state->dmr_lrrp_target[slot];
char comp_string[500]; memset (comp_string, 0, sizeof(comp_string));
sprintf (comp_string, "NMEA SRC: %d; TGT: %d;", source, target);
watchdog_event_datacall (opts, state, source, target, comp_string, slot);
}
else
{
if (start_value != '$' && start_value != '!')
fprintf (stderr, " Not an NMEA Sentence Structure;");
else if ( (start_value == '$' || start_value == '!') && end_value != '*')
fprintf (stderr, " Possible NMEA Sentence, Missing Ending *;");
else if ( start_value != '$' && start_value != '!' && end_value == '*')
fprintf (stderr, " Possible NMEA Sentence, Missing Starting $ or !;");
else if (checksum_calc != checksum_ext)
fprintf (stderr, " NMEA Checksum Error (%02X / %02X);", checksum_calc, checksum_ext);
}
//TODO: Convert to LRRP data and report / GPS on Event History
state->dmr_lrrp_source[slot] = 0;
state->dmr_lrrp_target[slot] = 0;
return valid;
}

View File

@@ -186,6 +186,10 @@ void NXDN_Elements_Content_decode(dsd_opts * opts, dsd_state * state, uint8_t Cr
//reset header validity
state->data_header_valid[0] = 0;
//set source and target
state->dmr_lrrp_source[0] = 0;
state->dmr_lrrp_target[0] = 0;
//reset encryption variables (for pdu)
state->payload_algid = 0;
state->payload_keyid = 0;
@@ -220,6 +224,10 @@ void NXDN_Elements_Content_decode(dsd_opts * opts, dsd_state * state, uint8_t Cr
//reset header validity
state->data_header_valid[0] = 0;
//set source and target
state->dmr_lrrp_source[0] = 0;
state->dmr_lrrp_target[0] = 0;
//reset encryption variables (for pdu)
state->payload_algid = 0;
state->payload_keyid = 0;
@@ -712,23 +720,6 @@ void NXDN_decode_VCALL_ASSGN(dsd_opts * opts, dsd_state * state, uint8_t * Messa
state->p25_vc_freq[0] = state->p25_vc_freq[1] = freq;
opts->p25_is_tuned = 1; //set to 1 to set as currently tuned so we don't keep tuning nonstop
//set rid and tg when we actually tune to it
//only assign rid if not spare and not reserved (happens on private calls, unsure of its significance)
if ( (VoiceCallOption & 0xF) < 4) //ideally, only want 0, 2, or 3
state->nxdn_last_rid = SourceUnitID;
state->nxdn_last_tg = DestinationID;
sprintf (state->nxdn_call_type, "%s", NXDN_Call_Type_To_Str(CallType));
if (CallType == 3)
{
state->gi[0] = 1; //Private Call
//unassign these, sometimes, when trunking, these may be reversed by the time listened,
//and this will plant an extra private call in the event_history
state->nxdn_last_rid = 0;
state->nxdn_last_tg = 0;
}
else state->gi[0] = 0; //Group Call
//Call String for Per Call WAV File
sprintf (state->call_string[0], "%s", NXDN_Call_Type_To_Str(CallType));
if (CCOption & 0x80) strcat (state->call_string[0], " Emergency");
@@ -760,23 +751,6 @@ void NXDN_decode_VCALL_ASSGN(dsd_opts * opts, dsd_state * state, uint8_t * Messa
state->p25_vc_freq[0] = state->p25_vc_freq[1] = freq;
opts->p25_is_tuned = 1;
//set rid and tg when we actually tune to it
//only assign rid if not spare and not reserved (happens on private calls, unsure of its significance)
if ( (VoiceCallOption & 0xF) < 4) //ideally, only want 0, 2, or 3
state->nxdn_last_rid = SourceUnitID;
state->nxdn_last_tg = DestinationID;
sprintf (state->nxdn_call_type, "%s", NXDN_Call_Type_To_Str(CallType));
if (CallType == 3)
{
state->gi[0] = 1; //Private Call
//unassign these, sometimes, when trunking, these may be reversed by the time listened,
//and this will plant an extra private call in the event_history
state->nxdn_last_rid = 0;
state->nxdn_last_tg = 0;
}
else state->gi[0] = 0; //Group Call
//Call String for Per Call WAV File
sprintf (state->call_string[0], "%s", NXDN_Call_Type_To_Str(CallType));
if (CCOption & 0x80) strcat (state->call_string[0], " Emergency");
@@ -2586,6 +2560,10 @@ void nxdn_sdcall_header(dsd_opts * opts, dsd_state * state, uint8_t * Message)
//set this is a valid header
state->data_header_valid[0] = 1;
//set source and target
state->dmr_lrrp_source[0] = source;
state->dmr_lrrp_target[0] = target;
}
@@ -2659,7 +2637,7 @@ void nxdn_dcall_header(dsd_opts * opts, dsd_state * state, uint8_t * Message)
NXDN_Data_Call_Option_To_Str(dcall_opt, duplex_string, dcall_string);
fprintf (stderr, "\n %s", KCYN);
fprintf (stderr, "Data Call Header (%04X) ", pkt_info);
fprintf (stderr, "Data Call Header (%06X) ", pkt_info);
if (idas == 0)
fprintf (stderr, "Source: %d; Target: %d; ", source, target);
else
@@ -2732,6 +2710,10 @@ void nxdn_dcall_header(dsd_opts * opts, dsd_state * state, uint8_t * Message)
//set this is a valid header
state->data_header_valid[0] = 1;
//set source and target
state->dmr_lrrp_source[0] = source;
state->dmr_lrrp_target[0] = target;
}
@@ -2739,8 +2721,9 @@ int nxdn_dcall_data(dsd_opts * opts, dsd_state * state, int type, uint8_t * Mess
{
UNUSED(opts);
//packet frame number and block number, observation shows these seem to be the same value (observed Type-D)
//but the manual does not quite explain these values indicate super packet of up to 16 big packets, and 1 packet is up to 16 blocks?
//if this is a full packet delivery, both of these numbers match typically,
//but if this is a selective retry resending blocks, the second number is the
//number of the retry block, and the first number is the remaining blocks
uint8_t pf_num = (uint8_t)ConvertBitIntoBytes(Message+8, 4);
uint8_t blk_num = (uint8_t)ConvertBitIntoBytes(Message+12, 4);
@@ -2783,6 +2766,18 @@ int nxdn_dcall_data(dsd_opts * opts, dsd_state * state, int type, uint8_t * Mess
return -1;
}
//Part 1-B Basic Operation Ver.2.0 page 34
//if this is a retry, we don't have memory of the other pieces, so just nuke it
//although, I suppose its possible that, for example, there were originally 4 blocks,
//but the retry is going to retry 3,3,2,0 blocks all sequentially, or similar,
//and this would potentially miss it, but the CRC32 will determine success then.
if (pf_num != blk_num)
{
fprintf (stderr, "Partial Selective Retry, Previous Delivery Not Retained in Memory; ");
state->data_header_valid[0] = 0;
return -1;
}
if (total_bytes < 4) //this shouldn't even happen in code
{
fprintf (stderr, "Total Bytes After Padding Less Than Required; ");
@@ -2797,7 +2792,7 @@ int nxdn_dcall_data(dsd_opts * opts, dsd_state * state, int type, uint8_t * Mess
memcpy(state->dmr_pdu_sf[0]+ptr, Message+16, byte_len*8*sizeof(uint8_t));
//Finished Assembly
if (blk_num == 0)
if (pf_num == 0)
{
//KS creation and application, if applicable
@@ -2933,18 +2928,13 @@ int nxdn_dcall_data(dsd_opts * opts, dsd_state * state, int type, uint8_t * Mess
if (crc_ext == crc_chk)
{
uint8_t opcode = (uint8_t)ConvertBitIntoBytes(state->dmr_pdu_sf[0], 8);
uint8_t nmea = (uint8_t)ConvertBitIntoBytes(state->dmr_pdu_sf[0]+8, 8);
uint32_t reverse = (uint8_t)ConvertBitIntoBytes(state->dmr_pdu_sf[0], 24); //observed 3 bytes of zeroes
if (opcode == 0x06)
if (opcode == 0x06 && (nmea == '$' || nmea == '!')) //may only need to check one of the other
{
//String Data Dump if first byte is 0x06 (NMEA sentences observed here)
//Check NMEA Sentence
fprintf (stderr, "\n ");
for (int i = 1; i < total_bytes; i++)
{
uint8_t ascii = (uint8_t)ConvertBitIntoBytes(state->dmr_pdu_sf[0]+(i*8), 8);
if (ascii >= 0x20 && ascii < 0x7F)
fprintf (stderr, "%c", ascii);
else break;
}
nmea_sentence_checker(opts, state, state->dmr_pdu_sf[0]+8, 0, total_bytes);
}
else if (reverse == 0)
{
@@ -2962,7 +2952,7 @@ int nxdn_dcall_data(dsd_opts * opts, dsd_state * state, int type, uint8_t * Mess
//Debug Data Dump
fprintf (stderr, "\n REV: ");
for (int i = 0; i < total_bytes-4-4+1; i++) //sans the CRC and the leading zeroes
for (int i = 0; i < total_bytes-7; i++) //sans the CRC and the leading zeroes
fprintf (stderr, "%02X", reverse_bytes[i]);
uint8_t reverse_byte_bits[total_bytes*8];
@@ -2971,7 +2961,7 @@ int nxdn_dcall_data(dsd_opts * opts, dsd_state * state, int type, uint8_t * Mess
//GPS report on 0xFFFC? or is that just any odd Kenwood message?
if ((uint16_t)convert_bits_into_output(reverse_byte_bits, 16) == 0xFFFC)
nxdn_gps_report(opts, state, reverse_byte_bits+16, state->nxdn_last_rid); //src is probably reset on cac
nxdn_gps_report(opts, state, reverse_byte_bits+16, state->dmr_lrrp_source[0]);
}
else
@@ -3000,6 +2990,10 @@ int nxdn_dcall_data(dsd_opts * opts, dsd_state * state, int type, uint8_t * Mess
//reset header validity
state->data_header_valid[0] = 0;
//set source and target
state->dmr_lrrp_source[0] = 0;
state->dmr_lrrp_target[0] = 0;
//reset encryption variables
state->payload_algid = 0;
state->payload_keyid = 0;

View File

@@ -457,8 +457,18 @@ void p25_decode_pdu_data(dsd_opts * opts, dsd_state * state, uint8_t * input, in
if (sap == 0 || sap == 4) //User Data or Packet Data (both are UDP typically, same format dmr UDP/IP data)
decode_ip_pdu (opts, state, len+1, input+ptr);
else if (sap == 48) //Tier 1 Location Service (or does it depend on the io bit?)
utf8_to_text(state, 1, len-ptr+1, input+ptr); //TODO, read initial string, i.e., $GPRMC and properly decode
//NMEA Tier 1 Location Reporting w/ Supplimentary $PP25 string
else if (sap == 48)
{
len += 12; //not sure how we ended up off by up to 12 on this, above negation too much?
state->dmr_lrrp_source[state->currentslot] = state->lastsrc;
state->dmr_lrrp_source[state->currentslot] = state->lasttg;
uint8_t input_bits[(len+1)*8];
memset(input_bits, 0, sizeof(input_bits));
unpack_byte_array_into_bit_array(input, input_bits, len);
fprintf (stderr, "\n ");
nmea_sentence_checker(opts, state, input_bits+ptr*8, state->currentslot, len*8);
}
// else //default catch all (debug only)
// {