Many small improvements (see full commit message).

* Better preamble detection to skip most of the messages we'll likely
  not be able to decode.

* A Phase correction algorithm that improves the recognition compared
  to the previous algorithm used.

* Javascript output in debug mode, and a debug.html file that can be
  used in order to see graphically undecoded samples.

* Ability to detect cross-read messages, that are, messages that happen
  to start and end across two different reads from the device or file.

* A few bugx fixed.

* README improved.
This commit is contained in:
antirez 2013-01-26 01:07:43 +01:00
parent 9086290b03
commit c2e79d4555
5 changed files with 478 additions and 107 deletions

2
.gitignore vendored
View file

@ -2,3 +2,5 @@
dump1090 dump1090
testfiles/*.bin testfiles/*.bin
misc misc
frames.js
.*.swp

View file

@ -7,19 +7,20 @@ The main features are:
* Robust decoding of weak messages, with mode1090 many users observed * Robust decoding of weak messages, with mode1090 many users observed
improved range compared to other popular decoders. improved range compared to other popular decoders.
* Network support: TCP30003 stream (MSG5...), Raw packets, HTTP.
* Embedded HTTP server that displays the currently detected aircrafts on
Google Map.
* Single bit errors correction using the 24 bit CRC. * Single bit errors correction using the 24 bit CRC.
* Ability to decode DF11, DF17 messages. * Ability to decode DF11, DF17 messages.
* Ability to decode DF formats like DF0, DF4, DF5, DF16, DF20 and DF21 * Ability to decode DF formats like DF0, DF4, DF5, DF16, DF20 and DF21
where the checksum is xored with the ICAO address by brute forcing the where the checksum is xored with the ICAO address by brute forcing the
checksum field using recently seen ICAO addresses. checksum field using recently seen ICAO addresses.
* Decode raw IQ samples from file (using --ifile command line switch). * Decode raw IQ samples from file (using --ifile command line switch).
* Interactive mode where aircrafts currently detected are shown * Interactive command-line-interfae mode where aircrafts currently detected
as a list refreshing as more data arrives. are shown as a list refreshing as more data arrives.
* CPR coordinates decoding and track calculation from velocity. * CPR coordinates decoding and track calculation from velocity.
* TCP server streaming and recceiving raw data to/from connected clients * TCP server streaming and recceiving raw data to/from connected clients
(using --net). (using --net).
* Embedded HTTP server that displays the currently detected aircrafts on
Google Map.
Installation Installation
--- ---
@ -155,14 +156,14 @@ Then you can feed it from different data sources from the internet.
Port 30003 Port 30003
--- ---
Connected clients are served with messages in SBS1 (BaseStation) format, similar to: Connected clients are served with messages in SBS1 (BaseStation) format,
similar to:
MSG,4,,,738065,,,,,,,,420,179,,,0,,0,0,0,0 MSG,4,,,738065,,,,,,,,420,179,,,0,,0,0,0,0
MSG,3,,,738065,,,,,,,35000,,,34.81609,34.07810,,,0,0,0,0 MSG,3,,,738065,,,,,,,35000,,,34.81609,34.07810,,,0,0,0,0
This can be used to feed data to various sharing sites without the need to use another decoder. This can be used to feed data to various sharing sites without the need to use another decoder.
Antenna Antenna
--- ---
@ -217,32 +218,12 @@ An index shows the sample number, where 0 is the sample where the first
Mode S peak was found. Some additional background noise is also added Mode S peak was found. Some additional background noise is also added
before the first peak to provide some context. before the first peak to provide some context.
It is possible to display different categories of messages: To enable debug mode and check what combinations of packets you can
log, use `mode1090 --help` to obtain a list of available debug flags.
--debug 1 Displays all the messages correctly demoudulated. Debug mode includes an optional javascript output that is used to visualize
A correctly demodulated message is just one that packets using a web browser, you can use the file debug.html under the
makes sense as a Mode S message, the preamble makes 'tools' directory to load the generated frames.js file.
sense, and there are no message errors, that is,
no adiacet samples describing bits are the same
magnitude.
--debug 2 Only messages with demodulation errors are displayed,
That is, only messages where one or more adiacent
samples that should describe bits are the same
magnitude.
--debug 3 Correctly deooded messages with Bad CRC are displayed.
--debug 4 Correctly deooded messages with good CRC are displayed.
--debug 5 Preamble detection failed in some way (specified when
dumping the samples) even if the current sample level
is greater than MODES_DEBUG_NOPREAMBLE_LEVEL (set to
25 by default).
Network related debug modes:
--debug 6 Log network events (HTTP requests & others)
How this program works? How this program works?
--- ---

11
TODO
View file

@ -1,10 +1,5 @@
TODO TODO
* Extract from information from captured Mode S messages. Currently we only * Extract more information from captured Mode S messages.
decode what is trival to decode. * Improve the web interface gmap.html.
* Decode CPR encoded latitude and longitude, display it in normal and * Enhance the algorithm to reliably decode more messages.
interactive mode.
* Show nationality in interactive mode and normal mode using the
aircraft ICAO address, like: 30xxxx -> Italy.
* Actually use the fancy --debug feature in order to improve the recognition
algorithm if possibile.

View file

@ -65,12 +65,13 @@
#define MODES_UNIT_FEET 0 #define MODES_UNIT_FEET 0
#define MODES_UNIT_METERS 1 #define MODES_UNIT_METERS 1
#define MODES_DEBUG_DEMOD 1 #define MODES_DEBUG_DEMOD (1<<0)
#define MODES_DEBUG_DEMODERR 2 #define MODES_DEBUG_DEMODERR (1<<1)
#define MODES_DEBUG_BADCRC 3 #define MODES_DEBUG_BADCRC (1<<2)
#define MODES_DEBUG_GOODCRC 4 #define MODES_DEBUG_GOODCRC (1<<3)
#define MODES_DEBUG_NOPREAMBLE 5 #define MODES_DEBUG_NOPREAMBLE (1<<4)
#define MODES_DEBUG_NET 6 #define MODES_DEBUG_NET (1<<5)
#define MODES_DEBUG_JS (1<<6)
/* When debug is set to MODES_DEBUG_NOPREAMBLE, the first sample must be /* When debug is set to MODES_DEBUG_NOPREAMBLE, the first sample must be
* at least greater than a given level for us to dump the signal. */ * at least greater than a given level for us to dump the signal. */
@ -184,6 +185,7 @@ struct {
long long stat_two_bits_fix; long long stat_two_bits_fix;
long long stat_http_requests; long long stat_http_requests;
long long stat_sbs_connections; long long stat_sbs_connections;
long long stat_out_of_phase;
} Modes; } Modes;
/* The struct we use to store information about a decoded message. */ /* The struct we use to store information about a decoded message. */
@ -196,6 +198,7 @@ struct modesMessage {
uint32_t crc; /* Message CRC */ uint32_t crc; /* Message CRC */
int errorbit; /* Bit corrected. -1 if no bit corrected. */ int errorbit; /* Bit corrected. -1 if no bit corrected. */
int aa1, aa2, aa3; /* ICAO Address bytes 1 2 and 3 */ int aa1, aa2, aa3; /* ICAO Address bytes 1 2 and 3 */
int phase_corrected; /* True if phase correction was applied. */
/* DF 11 */ /* DF 11 */
int ca; /* Responder capabilities. */ int ca; /* Responder capabilities. */
@ -237,6 +240,7 @@ void modesSendSBSOutput(struct modesMessage *mm, struct aircraft *a);
void useModesMessage(struct modesMessage *mm); void useModesMessage(struct modesMessage *mm);
int fixSingleBitErrors(unsigned char *msg, int bits); int fixSingleBitErrors(unsigned char *msg, int bits);
int fixTwoBitsErrors(unsigned char *msg, int bits); int fixTwoBitsErrors(unsigned char *msg, int bits);
int modesMessageLenByType(int type);
/* ============================= Utility functions ========================== */ /* ============================= Utility functions ========================== */
@ -280,7 +284,12 @@ void modesInit(void) {
pthread_mutex_init(&Modes.data_mutex,NULL); pthread_mutex_init(&Modes.data_mutex,NULL);
pthread_cond_init(&Modes.data_cond,NULL); pthread_cond_init(&Modes.data_cond,NULL);
Modes.data_len = MODES_DATA_LEN; /* We add a full message minus a final bit to the length, so that we
* can carry the remaining part of the buffer that we can't process
* in the message detection loop, back at the start of the next data
* to process. This way we are able to also detect messages crossing
* two reads. */
Modes.data_len = MODES_DATA_LEN + (MODES_FULL_LEN-1)*4;
Modes.data_ready = 0; Modes.data_ready = 0;
/* Allocate the ICAO address cache. We use two uint32_t for every /* Allocate the ICAO address cache. We use two uint32_t for every
* entry because it's a addr / timestamp pair for every entry. */ * entry because it's a addr / timestamp pair for every entry. */
@ -293,6 +302,7 @@ void modesInit(void) {
fprintf(stderr, "Out of memory allocating data buffer.\n"); fprintf(stderr, "Out of memory allocating data buffer.\n");
exit(1); exit(1);
} }
memset(Modes.data,127,Modes.data_len);
/* Populate the I/Q -> Magnitude lookup table. It is used because /* Populate the I/Q -> Magnitude lookup table. It is used because
* sqrt or round may be expensive and may vary a lot depending on * sqrt or round may be expensive and may vary a lot depending on
@ -318,6 +328,7 @@ void modesInit(void) {
Modes.stat_two_bits_fix = 0; Modes.stat_two_bits_fix = 0;
Modes.stat_http_requests = 0; Modes.stat_http_requests = 0;
Modes.stat_sbs_connections = 0; Modes.stat_sbs_connections = 0;
Modes.stat_out_of_phase = 0;
Modes.exit = 0; Modes.exit = 0;
} }
@ -385,8 +396,12 @@ void rtlsdrCallback(unsigned char *buf, uint32_t len, void *ctx) {
MODES_NOTUSED(ctx); MODES_NOTUSED(ctx);
pthread_mutex_lock(&Modes.data_mutex); pthread_mutex_lock(&Modes.data_mutex);
if (len > Modes.data_len) len = Modes.data_len; if (len > MODES_DATA_LEN) len = MODES_DATA_LEN;
memcpy(Modes.data, buf, len); /* Move the last part of the previous buffer, that was not processed,
* on the start of the new buffer. */
memcpy(Modes.data, Modes.data+MODES_DATA_LEN, (MODES_FULL_LEN-1)*4);
/* Read the new data. */
memcpy(Modes.data+(MODES_FULL_LEN-1)*4, buf, len);
Modes.data_ready = 1; Modes.data_ready = 1;
/* Signal to the other thread that new data is ready */ /* Signal to the other thread that new data is ready */
pthread_cond_signal(&Modes.data_cond); pthread_cond_signal(&Modes.data_cond);
@ -414,8 +429,11 @@ void readDataFromFile(void) {
pthread_mutex_lock(&Modes.data_mutex); pthread_mutex_lock(&Modes.data_mutex);
} }
toread = Modes.data_len; /* Move the last part of the previous buffer, that was not processed,
p = Modes.data; * on the start of the new buffer. */
memcpy(Modes.data, Modes.data+MODES_DATA_LEN, (MODES_FULL_LEN-1)*4);
toread = MODES_DATA_LEN;
p = Modes.data+(MODES_FULL_LEN-1)*4;
while(toread) { while(toread) {
nread = read(Modes.fd, p, toread); nread = read(Modes.fd, p, toread);
if (nread <= 0) { if (nread <= 0) {
@ -444,7 +462,7 @@ void *readerThreadEntryPoint(void *arg) {
if (Modes.filename == NULL) { if (Modes.filename == NULL) {
rtlsdr_read_async(Modes.dev, rtlsdrCallback, NULL, rtlsdr_read_async(Modes.dev, rtlsdrCallback, NULL,
MODES_ASYNC_BUF_NUMBER, MODES_ASYNC_BUF_NUMBER,
Modes.data_len); MODES_DATA_LEN);
} else { } else {
readDataFromFile(); readDataFromFile();
} }
@ -491,7 +509,7 @@ void dumpMagnitudeBar(int index, int magnitude) {
* for context. */ * for context. */
void dumpMagnitudeVector(uint16_t *m, uint32_t offset) { void dumpMagnitudeVector(uint16_t *m, uint32_t offset) {
uint32_t padding = 5; /* Show 5 samples before the actual start. */ uint32_t padding = 5; /* Show a few samples before the actual start. */
uint32_t start = (offset < padding) ? 0 : offset-padding; uint32_t start = (offset < padding) ? 0 : offset-padding;
uint32_t end = offset + (MODES_PREAMBLE_US*2)+(MODES_SHORT_MSG_BITS*2) - 1; uint32_t end = offset + (MODES_PREAMBLE_US*2)+(MODES_SHORT_MSG_BITS*2) - 1;
uint32_t j; uint32_t j;
@ -501,6 +519,40 @@ void dumpMagnitudeVector(uint16_t *m, uint32_t offset) {
} }
} }
/* Produce a raw representation of the message as a Javascript file
* loadable by debug.html. */
void dumpRawMessageJS(char *descr, unsigned char *msg,
uint16_t *m, uint32_t offset, int fixable)
{
int padding = 5; /* Show a few samples before the actual start. */
int start = offset - padding;
int end = offset + (MODES_PREAMBLE_US*2)+(MODES_LONG_MSG_BITS*2) - 1;
FILE *fp;
int j, fix1 = -1, fix2 = -1;
if (fixable != -1) {
fix1 = fixable & 0xff;
if (fixable > 255) fix2 = fixable >> 8;
}
if ((fp = fopen("frames.js","a")) == NULL) {
fprintf(stderr, "Error opening frames.js: %s\n", strerror(errno));
exit(1);
}
fprintf(fp,"frames.push({\"descr\": \"%s\", \"mag\": [", descr);
for (j = start; j <= end; j++) {
fprintf(fp,"%d", j < 0 ? 0 : m[j]);
if (j != end) fprintf(fp,",");
}
fprintf(fp,"], \"fix1\": %d, \"fix2\": %d, \"bits\": %d, \"hex\": \"",
fix1, fix2, modesMessageLenByType(msg[0]>>3));
for (j = 0; j < MODES_LONG_MSG_BYTES; j++)
fprintf(fp,"\\x%02x",msg[j]);
fprintf(fp,"\"});\n");
fclose(fp);
}
/* This is a wrapper for dumpMagnitudeVector() that also show the message /* This is a wrapper for dumpMagnitudeVector() that also show the message
* in hex format with an additional description. * in hex format with an additional description.
* *
@ -508,19 +560,29 @@ void dumpMagnitudeVector(uint16_t *m, uint32_t offset) {
* msg points to the decoded message * msg points to the decoded message
* m is the original magnitude vector * m is the original magnitude vector
* offset is the offset where the message starts * offset is the offset where the message starts
*
* The function also produces the Javascript file used by debug.html to
* display packets in a graphical format if the Javascript output was
* enabled.
*/ */
void dumpRawMessage(char *descr, unsigned char *msg, void dumpRawMessage(char *descr, unsigned char *msg,
uint16_t *m, uint32_t offset) uint16_t *m, uint32_t offset)
{ {
int j; int j;
int msgtype = msg[0]>>3; int msgtype = msg[0]>>3;
int fixable = 0; int fixable = -1;
if (msgtype == 11 || msgtype == 17) { if (msgtype == 11 || msgtype == 17) {
int msgbits = (msgtype == 11) ? MODES_SHORT_MSG_BITS : int msgbits = (msgtype == 11) ? MODES_SHORT_MSG_BITS :
MODES_LONG_MSG_BITS; MODES_LONG_MSG_BITS;
if (fixSingleBitErrors(msg,msgbits) != -1) fixable = 1; fixable = fixSingleBitErrors(msg,msgbits);
else if (fixTwoBitsErrors(msg,msgbits) != -1) fixable = 2; if (fixable == -1)
fixable = fixTwoBitsErrors(msg,msgbits);
}
if (Modes.debug & MODES_DEBUG_JS) {
dumpRawMessageJS(descr, msg, m, offset, fixable);
return;
} }
printf("\n--- %s\n ", descr); printf("\n--- %s\n ", descr);
@ -640,6 +702,7 @@ int fixTwoBitsErrors(unsigned char *msg, int bits) {
int byte1 = j/8; int byte1 = j/8;
int bitmask1 = 1 << (7-(j%8)); int bitmask1 = 1 << (7-(j%8));
/* Don't check the same pairs multiple times, so i starts from j+1 */
for (i = j+1; i < bits; i++) { for (i = j+1; i < bits; i++) {
int byte2 = i/8; int byte2 = i/8;
int bitmask2 = 1 << (7-(i%8)); int bitmask2 = 1 << (7-(i%8));
@ -660,6 +723,9 @@ int fixTwoBitsErrors(unsigned char *msg, int bits) {
* the corrected sequence, and returns the error bit * the corrected sequence, and returns the error bit
* position. */ * position. */
memcpy(msg,aux,bits/8); memcpy(msg,aux,bits/8);
/* We return the two bits as a 16 bit integer by shifting
* 'i' on the left. This is possible since 'i' will always
* be non-zero because i starts from j+1. */
return j | (i<<8); return j | (i<<8);
} }
} }
@ -1041,6 +1107,7 @@ void decodeModesMessage(struct modesMessage *mm, unsigned char *msg) {
} }
} }
} }
mm->phase_corrected = 0; /* Set to 1 by the caller if needed. */
} }
/* This function gets a decoded Mode S Message and prints it on the screen /* This function gets a decoded Mode S Message and prints it on the screen
@ -1177,13 +1244,67 @@ void computeMagnitudeVector(void) {
} }
} }
/* Return -1 if the message is out of fase left-side
* Return 1 if the message is out of fase right-size
* Return 0 if the message is not particularly out of phase. */
int detectOutOfPhase(uint16_t *m) {
m += 16; /* Skip preamble. */
if (m[3] > m[2]/2) return 1;
if (m[6] > m[7]/2) return -1;
return 0;
}
/* This function does not really correct the phase of the message, it just
* applies a transformation to the first sample representing a given bit:
*
* If the previous bit was one, we amplify it a bit.
* If the previous bit was zero, we decrease it a bit.
*
* This simple transformation makes the message a bit more likely to be
* correctly decoded for out of phase messages:
*
* When messages are out of phase there is more uncertainty in
* sequences of the same bit multiple times, since 11111 will be
* transmitted as continuously altering magnitude (high, low, high, low...)
*
* However because the message is out of phase some part of the high
* is mixed in the low part, so that it is hard to distinguish if it is
* a zero or a one.
*
* However when the message is out of phase passing from 0 to 1 or from
* 1 to 0 happens in a very recognizable way, for instance in the 0 -> 1
* transition, magnitude goes low, high, high, low, and one of of the
* two middle samples the high will be *very* high as part of the previous
* or next high signal will be mixed there.
*
* Applying our simple transformation we make more likely if the current
* bit is a zero, to detect another zero. Symmetrically if it is a one
* it will be more likely to detect a one because of the transformation.
* In this way similar levels will be interpreted more likely in the
* correct way. */
void applyPhaseCorrection(uint16_t *m) {
int j;
for (j = 0; j < (MODES_LONG_MSG_BITS-1)*2; j += 2) {
if (m[j] > m[j+1]) {
/* One */
m[j+2] = (m[j+2] * 5) / 4;
} else {
/* Zero */
m[j+2] = (m[j+2] * 4) / 5;
}
}
}
/* Detect a Mode S messages inside the magnitude buffer pointed by 'm' and of /* Detect a Mode S messages inside the magnitude buffer pointed by 'm' and of
* size 'mlen' bytes. Every detected Mode S message is convert it into a * size 'mlen' bytes. Every detected Mode S message is convert it into a
* stream of bits and passed to the function to display it. */ * stream of bits and passed to the function to display it. */
void detectModeS(uint16_t *m, uint32_t mlen) { void detectModeS(uint16_t *m, uint32_t mlen) {
unsigned char bits[MODES_LONG_MSG_BITS]; unsigned char bits[MODES_LONG_MSG_BITS];
unsigned char msg[MODES_LONG_MSG_BITS/2]; unsigned char msg[MODES_LONG_MSG_BITS/2];
uint16_t aux[MODES_LONG_MSG_BITS*2];
uint32_t j; uint32_t j;
int use_correction = 0;
/* The Mode S preamble is made of impulses of 0.5 microseconds at /* The Mode S preamble is made of impulses of 0.5 microseconds at
* the following time offsets: * the following time offsets:
@ -1209,7 +1330,10 @@ void detectModeS(uint16_t *m, uint32_t mlen) {
* 9 ------------------- * 9 -------------------
*/ */
for (j = 0; j < mlen - MODES_FULL_LEN*2; j++) { for (j = 0; j < mlen - MODES_FULL_LEN*2; j++) {
int low, high, i, errors; int low, high, delta, i, errors;
int good_message = 0, out_of_phase;
if (use_correction) goto good_preamble; /* We already checked it. */
/* First check of relations between the first 10 samples /* First check of relations between the first 10 samples
* representing a valid preamble. We don't even investigate further * representing a valid preamble. We don't even investigate further
@ -1225,23 +1349,23 @@ void detectModeS(uint16_t *m, uint32_t mlen) {
m[j+8] < m[j+9] && m[j+8] < m[j+9] &&
m[j+9] > m[j+6])) m[j+9] > m[j+6]))
{ {
if (Modes.debug == MODES_DEBUG_NOPREAMBLE && if (Modes.debug & MODES_DEBUG_NOPREAMBLE &&
m[j] > MODES_DEBUG_NOPREAMBLE_LEVEL) m[j] > MODES_DEBUG_NOPREAMBLE_LEVEL)
dumpRawMessage("Unexpected ratio among first 10 samples", dumpRawMessage("Unexpected ratio among first 10 samples",
msg, m, j); msg, m, j);
continue; continue;
} }
if (Modes.aggressive) { if (!Modes.aggressive) {
/* The samples between the two spikes must be < than the average /* The samples between the two spikes must be < than the average
* of the high spikes level. */ * of the high spikes level. We don't test bits too near to
high = (m[j]+m[j+2]+m[j+7]+m[j+9])/4; * the high levels as signals can be out of phase so part of the
if (m[j+3] >= high || * energy can be in the near samples. */
m[j+4] >= high || high = (m[j]+m[j+2]+m[j+7]+m[j+9])/6;
m[j+5] >= high || if (m[j+4] >= high ||
m[j+6] >= high) m[j+5] >= high)
{ {
if (Modes.debug == MODES_DEBUG_NOPREAMBLE && if (Modes.debug & MODES_DEBUG_NOPREAMBLE &&
m[j] > MODES_DEBUG_NOPREAMBLE_LEVEL) m[j] > MODES_DEBUG_NOPREAMBLE_LEVEL)
dumpRawMessage( dumpRawMessage(
"Too high level in samples between 3 and 6", "Too high level in samples between 3 and 6",
@ -1249,16 +1373,15 @@ void detectModeS(uint16_t *m, uint32_t mlen) {
continue; continue;
} }
/* Similarly samples in the range 10-15 must be low, as it is the /* Similarly samples in the range 11-14 must be low, as it is the
* space between the preamble and real data. */ * space between the preamble and real data. Again we don't test
if (m[j+10] >= high || * bits too near to high levels, see above. */
m[j+11] >= high || if (m[j+11] >= high ||
m[j+12] >= high || m[j+12] >= high ||
m[j+13] >= high || m[j+13] >= high ||
m[j+14] >= high || m[j+14] >= high)
m[j+15] >= high)
{ {
if (Modes.debug == MODES_DEBUG_NOPREAMBLE && if (Modes.debug & MODES_DEBUG_NOPREAMBLE &&
m[j] > MODES_DEBUG_NOPREAMBLE_LEVEL) m[j] > MODES_DEBUG_NOPREAMBLE_LEVEL)
dumpRawMessage( dumpRawMessage(
"Too high level in samples between 10 and 15", "Too high level in samples between 10 and 15",
@ -1268,13 +1391,28 @@ void detectModeS(uint16_t *m, uint32_t mlen) {
} }
Modes.stat_valid_preamble++; Modes.stat_valid_preamble++;
good_preamble:
out_of_phase = detectOutOfPhase(m+j);
/* If the previous attempt with this message failed, retry using
* magnitude correction. */
if (use_correction) {
memcpy(aux,m+j+MODES_PREAMBLE_US*2,sizeof(aux));
applyPhaseCorrection(m+j);
}
/* Decode all the next 112 bits, regardless of the actual message /* Decode all the next 112 bits, regardless of the actual message
* size. We'll check the actual message type later. */ * size. We'll check the actual message type later. */
errors = 0; errors = 0;
for (i = 0; i < MODES_LONG_MSG_BITS*2; i += 2) { for (i = 0; i < MODES_LONG_MSG_BITS*2; i += 2) {
low = m[j+i+MODES_PREAMBLE_US*2]; low = m[j+i+MODES_PREAMBLE_US*2];
high = m[j+i+MODES_PREAMBLE_US*2+1]; high = m[j+i+MODES_PREAMBLE_US*2+1];
if (low == high) { delta = low-high;
if (delta < 0) delta = -delta;
if (i > 0 && delta < 256) {
bits[i/2] = bits[i/2-1];
} else if (low == high) {
/* Checking if two adiacent samples have the same magnitude /* Checking if two adiacent samples have the same magnitude
* is an effective way to detect if it's just random noise * is an effective way to detect if it's just random noise
* that was detected as a valid preamble. */ * that was detected as a valid preamble. */
@ -1288,6 +1426,10 @@ void detectModeS(uint16_t *m, uint32_t mlen) {
} }
} }
/* Restore the original message if we used magnitude correction. */
if (use_correction)
memcpy(m+j+MODES_PREAMBLE_US*2,aux,sizeof(aux));
/* Pack bits into bytes */ /* Pack bits into bytes */
for (i = 0; i < MODES_LONG_MSG_BITS; i += 8) { for (i = 0; i < MODES_LONG_MSG_BITS; i += 8) {
msg[i/8] = msg[i/8] =
@ -1306,7 +1448,7 @@ void detectModeS(uint16_t *m, uint32_t mlen) {
/* Last check, high and low bits are different enough in magnitude /* Last check, high and low bits are different enough in magnitude
* to mark this as real message and not just noise? */ * to mark this as real message and not just noise? */
int delta = 0; delta = 0;
for (i = 0; i < msglen*8*2; i += 2) { for (i = 0; i < msglen*8*2; i += 2) {
delta += abs(m[j+i+MODES_PREAMBLE_US*2]- delta += abs(m[j+i+MODES_PREAMBLE_US*2]-
m[j+i+MODES_PREAMBLE_US*2+1]); m[j+i+MODES_PREAMBLE_US*2+1]);
@ -1316,7 +1458,10 @@ void detectModeS(uint16_t *m, uint32_t mlen) {
/* Filter for an average delta of three is small enough to let almost /* Filter for an average delta of three is small enough to let almost
* every kind of message to pass, but high enough to filter some * every kind of message to pass, but high enough to filter some
* random noise. */ * random noise. */
if (delta < 10*255) continue; if (delta < 10*255) {
use_correction = 0;
continue;
}
/* If we reached this point, and error is zero, we are very likely /* If we reached this point, and error is zero, we are very likely
* with a Mode S message in our hands, but it may still be broken * with a Mode S message in our hands, but it may still be broken
@ -1326,42 +1471,65 @@ void detectModeS(uint16_t *m, uint32_t mlen) {
/* Decode the received message and update statistics */ /* Decode the received message and update statistics */
decodeModesMessage(&mm,msg); decodeModesMessage(&mm,msg);
if (errors == 0) Modes.stat_demodulated++;
if (mm.errorbit == -1) { /* Update statistics. */
if (mm.crcok) if (mm.crcok || (!out_of_phase || use_correction)) {
Modes.stat_goodcrc++; if (errors == 0) Modes.stat_demodulated++;
else if (mm.errorbit == -1) {
if (mm.crcok)
Modes.stat_goodcrc++;
else
Modes.stat_badcrc++;
} else {
Modes.stat_badcrc++; Modes.stat_badcrc++;
} else { Modes.stat_fixed++;
Modes.stat_badcrc++; if (mm.errorbit < MODES_LONG_MSG_BITS)
Modes.stat_fixed++; Modes.stat_single_bit_fix++;
if (mm.errorbit < MODES_LONG_MSG_BITS) else
Modes.stat_single_bit_fix++; Modes.stat_two_bits_fix++;
else }
Modes.stat_two_bits_fix++;
} }
/* Output debug mode info if needed. */ /* Output debug mode info if needed. */
if (Modes.debug == MODES_DEBUG_DEMOD) if (!out_of_phase || use_correction) {
dumpRawMessage("Demodulated with 0 errors", msg, m, j); if (Modes.debug & MODES_DEBUG_DEMOD)
else if (Modes.debug == MODES_DEBUG_BADCRC && dumpRawMessage("Demodulated with 0 errors", msg, m, j);
(!mm.crcok || mm.errorbit != -1)) else if (Modes.debug & MODES_DEBUG_BADCRC &&
dumpRawMessage("Decoded with bad CRC", msg, m, j); mm.msgtype == 17 &&
else if (Modes.debug == MODES_DEBUG_GOODCRC && mm.crcok && (!mm.crcok || mm.errorbit != -1))
mm.errorbit == -1) dumpRawMessage("Decoded with bad CRC", msg, m, j);
dumpRawMessage("Decoded with good CRC", msg, m, j); else if (Modes.debug & MODES_DEBUG_GOODCRC && mm.crcok &&
mm.errorbit == -1)
dumpRawMessage("Decoded with good CRC", msg, m, j);
}
/* Skip this message if we are sure it's fine. */
if (mm.crcok) {
j += (MODES_PREAMBLE_US+(msglen*8))*2;
good_message = 1;
if (use_correction)
mm.phase_corrected = 1;
}
/* Pass data to the next layer */ /* Pass data to the next layer */
useModesMessage(&mm); useModesMessage(&mm);
/* Skip this message if we are sure it's fine. */
if (mm.crcok) j += (MODES_PREAMBLE_US+(msglen*8))*2;
} else { } else {
if (Modes.debug == MODES_DEBUG_DEMODERR) { if (Modes.debug & MODES_DEBUG_DEMODERR &&
(!out_of_phase || use_correction))
{
printf("The following message has %d demod errors\n", errors); printf("The following message has %d demod errors\n", errors);
dumpRawMessage("Demodulated with errors", msg, m, j); dumpRawMessage("Demodulated with errors", msg, m, j);
} }
} }
/* Retry with phase correction if possible. */
if (!good_message && !use_correction && out_of_phase) {
j--;
use_correction = 1;
Modes.stat_out_of_phase++;
} else {
use_correction = 0;
}
} }
} }
@ -1785,7 +1953,7 @@ void modesAcceptClients(void) {
j--; /* Try again with the same listening port. */ j--; /* Try again with the same listening port. */
if (Modes.debug == MODES_DEBUG_NET) if (Modes.debug & MODES_DEBUG_NET)
printf("Created new client %d\n", fd); printf("Created new client %d\n", fd);
} }
} }
@ -1796,7 +1964,7 @@ void modesFreeClient(int fd) {
free(Modes.clients[fd]); free(Modes.clients[fd]);
Modes.clients[fd] = NULL; Modes.clients[fd] = NULL;
if (Modes.debug == MODES_DEBUG_NET) if (Modes.debug & MODES_DEBUG_NET)
printf("Closing client %d\n", fd); printf("Closing client %d\n", fd);
/* If this was our maxfd, rescan the full clients array to check what's /* If this was our maxfd, rescan the full clients array to check what's
@ -2014,7 +2182,7 @@ int handleHTTPRequest(struct client *c) {
char *p, *url, *content; char *p, *url, *content;
char *ctype; char *ctype;
if (Modes.debug == MODES_DEBUG_NET) if (Modes.debug & MODES_DEBUG_NET)
printf("\nHTTP request: %s\n", c->buf); printf("\nHTTP request: %s\n", c->buf);
/* Minimally parse the request. */ /* Minimally parse the request. */
@ -2035,7 +2203,7 @@ int handleHTTPRequest(struct client *c) {
if (!p) return 1; /* There should be a space before HTTP/... */ if (!p) return 1; /* There should be a space before HTTP/... */
*p = '\0'; *p = '\0';
if (Modes.debug == MODES_DEBUG_NET) { if (Modes.debug & MODES_DEBUG_NET) {
printf("\nHTTP keep alive: %d\n", keepalive); printf("\nHTTP keep alive: %d\n", keepalive);
printf("HTTP requested URL: %s\n\n", url); printf("HTTP requested URL: %s\n\n", url);
} }
@ -2082,7 +2250,7 @@ int handleHTTPRequest(struct client *c) {
keepalive ? "keep-alive" : "close", keepalive ? "keep-alive" : "close",
clen); clen);
if (Modes.debug == MODES_DEBUG_NET) if (Modes.debug & MODES_DEBUG_NET)
printf("HTTP Reply header:\n%s", hdr); printf("HTTP Reply header:\n%s", hdr);
/* Send header and content. */ /* Send header and content. */
@ -2206,8 +2374,16 @@ void showHelp(void) {
"--onlyaddr Show only ICAO addresses (testing purposes).\n" "--onlyaddr Show only ICAO addresses (testing purposes).\n"
"--metric Use metric units (meters, km/h, ...).\n" "--metric Use metric units (meters, km/h, ...).\n"
"--snip <level> Strip IQ file removing samples < level.\n" "--snip <level> Strip IQ file removing samples < level.\n"
"--debug <level> Debug mode, see README for more information.\n" "--debug <flags> Debug mode (verbose), see README for details.\n"
"--help Show this help.\n" "--help Show this help.\n"
"\n"
"Debug mode flags: d = Log frames decoded with errors\n"
" D = Log frames decoded with zero errors\n"
" c = Log frames with bad CRC\n"
" C = Log frames with good CRC\n"
" p = Log frames with bad preamble\n"
" n = Log network debugging info\n"
" j = Log frames to frames.js, loadable by debug.html.\n"
); );
} }
@ -2283,7 +2459,23 @@ int main(int argc, char **argv) {
} else if (!strcmp(argv[j],"--interactive-ttl")) { } else if (!strcmp(argv[j],"--interactive-ttl")) {
Modes.interactive_ttl = atoi(argv[++j]); Modes.interactive_ttl = atoi(argv[++j]);
} else if (!strcmp(argv[j],"--debug") && more) { } else if (!strcmp(argv[j],"--debug") && more) {
Modes.debug = atoi(argv[++j]); char *f = argv[++j];
while(*f) {
switch(*f) {
case 'D': Modes.debug |= MODES_DEBUG_DEMOD; break;
case 'd': Modes.debug |= MODES_DEBUG_DEMODERR; break;
case 'C': Modes.debug |= MODES_DEBUG_GOODCRC; break;
case 'c': Modes.debug |= MODES_DEBUG_BADCRC; break;
case 'p': Modes.debug |= MODES_DEBUG_NOPREAMBLE; break;
case 'n': Modes.debug |= MODES_DEBUG_NET; break;
case 'j': Modes.debug |= MODES_DEBUG_JS; break;
default:
fprintf(stderr, "Unknown debugging flag: %c\n", *f);
exit(1);
break;
}
f++;
}
} else if (!strcmp(argv[j],"--stats")) { } else if (!strcmp(argv[j],"--stats")) {
Modes.stats = 1; Modes.stats = 1;
} else if (!strcmp(argv[j],"--snip") && more) { } else if (!strcmp(argv[j],"--snip") && more) {
@ -2354,7 +2546,10 @@ int main(int argc, char **argv) {
/* If --ifile and --stats were given, print statistics. */ /* If --ifile and --stats were given, print statistics. */
if (Modes.stats && Modes.filename) { if (Modes.stats && Modes.filename) {
printf("%lld valid preambles\n", Modes.stat_valid_preamble); printf("%lld valid preambles\n", Modes.stat_valid_preamble);
printf("%lld demodulated with zero errors\n", Modes.stat_demodulated); printf("%lld demodulated again after phase correction\n",
Modes.stat_out_of_phase);
printf("%lld demodulated with zero errors\n",
Modes.stat_demodulated);
printf("%lld with good crc\n", Modes.stat_goodcrc); printf("%lld with good crc\n", Modes.stat_goodcrc);
printf("%lld with bad crc\n", Modes.stat_badcrc); printf("%lld with bad crc\n", Modes.stat_badcrc);
printf("%lld errors corrected\n", Modes.stat_fixed); printf("%lld errors corrected\n", Modes.stat_fixed);

198
tools/debug.html Normal file
View file

@ -0,0 +1,198 @@
<!DOCTYPE html>
<html>
<body>
<head>
<script>
var frames = [];
var currentFrame = 144;
var modes_checksum_table = [
0x3935ea, 0x1c9af5, 0xf1b77e, 0x78dbbf, 0xc397db, 0x9e31e9, 0xb0e2f0, 0x587178,
0x2c38bc, 0x161c5e, 0x0b0e2f, 0xfa7d13, 0x82c48d, 0xbe9842, 0x5f4c21, 0xd05c14,
0x682e0a, 0x341705, 0xe5f186, 0x72f8c3, 0xc68665, 0x9cb936, 0x4e5c9b, 0xd8d449,
0x939020, 0x49c810, 0x24e408, 0x127204, 0x093902, 0x049c81, 0xfdb444, 0x7eda22,
0x3f6d11, 0xe04c8c, 0x702646, 0x381323, 0xe3f395, 0x8e03ce, 0x4701e7, 0xdc7af7,
0x91c77f, 0xb719bb, 0xa476d9, 0xadc168, 0x56e0b4, 0x2b705a, 0x15b82d, 0xf52612,
0x7a9309, 0xc2b380, 0x6159c0, 0x30ace0, 0x185670, 0x0c2b38, 0x06159c, 0x030ace,
0x018567, 0xff38b7, 0x80665f, 0xbfc92b, 0xa01e91, 0xaff54c, 0x57faa6, 0x2bfd53,
0xea04ad, 0x8af852, 0x457c29, 0xdd4410, 0x6ea208, 0x375104, 0x1ba882, 0x0dd441,
0xf91024, 0x7c8812, 0x3e4409, 0xe0d800, 0x706c00, 0x383600, 0x1c1b00, 0x0e0d80,
0x0706c0, 0x038360, 0x01c1b0, 0x00e0d8, 0x00706c, 0x003836, 0x001c1b, 0xfff409,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000,
0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000, 0x000000
];
function modesChecksum(frame) {
var crc = 0;
var bits = frame.bits;
var offset = (bits == 112) ? 0 : (112-56);
for(var j = 0; j < bits; j++) {
var byte = j/8;
var bit = j%8;
var bitmask = 1 << (7-bit);
/* If bit is set, xor with corresponding table entry. */
if (frame.hex.charCodeAt(byte) & bitmask)
crc ^= modes_checksum_table[j+offset];
}
return crc; /* 24 bit checksum. */
}
function getFrameChecksum(frame) {
var res = "";
for (j = 0; j < frame.hex.length; j++) {
var val = frame.hex.charCodeAt(j);
var h = val.toString(16);
if (h.length == 1) h = "0"+h;
res += h;
}
return res;
}
function displayFrame(i) {
var div = document.getElementById("frame");
var msgbits = 8+112;
var frame = frames[i];
var padding = frame.mag.length - msgbits*2;
/* Remove the old representation. */
var nodes = div.childNodes.length;
for(var j = 0; j < nodes; j++) {
div.removeChild(div.firstChild);
}
/* Display the new one. */
for (var j = -padding; j < msgbits*2+padding; j++) {
var m = frame.mag[j+padding];
var type;
if (j < 0) type = "noise";
if (j >= 0 && j < 16) type = "pre";
if (j >= 16) {
if (!(j % 2)) {
var next = frame.mag[j+padding+1];
if (m > next)
type = "one";
else
type = "zero";
}
var bit = (j-16)/2;
if (bit == frame.fix1 ||
bit == frame.fix2)
type = "err";
}
var sample = document.createElement("div");
sample.setAttribute("class","sample "+type);
sample.setAttribute("title","sample "+j+" ("+m+")");
sample.style.left = ""+((j+padding)*4)+"px";
sample.style.height = ""+(m/256)+"px";
div.appendChild(sample);
}
document.getElementById("info").innerHTML =
"#"+currentFrame+" "+frame.descr+"<br>"+
"Bits:"+frame.bits+"<br>"+
"DF : "+(frame.hex.charCodeAt(0) >> 3)+"<br>"+
"fix1: "+frame.fix1+"<br>"+
"fix2: "+frame.fix2+"<br>"+
"hex : "+getFrameChecksum(frame)+"<br>"+
"crc (computed): "+modesChecksum(frame).toString(16)+"<br>";
}
function recomputeHex(frame) {
var padding = frame.mag.length - (112+8)*2;
var b = [];
var hex = "";
/* Get bits */
for (var j = 0; j < frame.bits*2; j += 2) {
var bit;
var l = frame.mag[padding+j+16];
var r = frame.mag[padding+j+1+16];
if (l > r)
bit = 1;
else
bit = 0;
b.push(bit);
}
/* Pack into bytes */
for (j = 0; j < frame.bits; j+= 8) {
hex += String.fromCharCode(
b[j]<<7 |
b[j+1]<<6 |
b[j+2]<<5 |
b[j+3]<<4 |
b[j+4]<<3 |
b[j+5]<<2 |
b[j+6]<<1 |
b[j+7]);
}
frame.hex = hex;
}
window.onload = function() {
document.getElementById("next").onclick = function() {
if (currentFrame != frames.length-1) currentFrame++;
displayFrame(currentFrame);
}
document.getElementById("prev").onclick = function() {
if (currentFrame != 0) currentFrame--;
displayFrame(currentFrame);
}
document.getElementById("ca").onclick = function() {
correctAmplitude(frames[currentFrame]);
recomputeHex(frames[currentFrame]);
displayFrame(currentFrame);
}
document.getElementById("re").onclick = function() {
recomputeHex(frames[currentFrame]);
displayFrame(currentFrame);
}
displayFrame(currentFrame);
}
</script>
<script src="frames.js"></script>
<style>
#frame {
width: 1024px;
height: 255px;
border: 1px #aaa solid;
position: relative;
}
.sample {
position: absolute;
bottom: 0px;
}
.pre {
width:4px;
background-color: orange;
}
.one {
width:4px;
background-color: #0000cc;
}
.zero {
width:4px;
background-color: #aaaaaa;
}
.err {
width:4px;
background-color: #cc6666;
}
.noise {
width:2px;
background-color: #ffffff;
border: 1px #aaa dotted;
}
</style>
</head>
<div id="frame">
</div>
<pre id="info">
</pre>
<input type="button" id="prev" value="Prev frame">
<input type="button" id="next" value="Next frame">
<input type="button" id="re" value="Recompute Hex">
</body>
</html>