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
testfiles/*.bin
misc
frames.js
.*.swp

View file

@ -7,19 +7,20 @@ The main features are:
* Robust decoding of weak messages, with mode1090 many users observed
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.
* Ability to decode DF11, DF17 messages.
* 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
checksum field using recently seen ICAO addresses.
* Decode raw IQ samples from file (using --ifile command line switch).
* Interactive mode where aircrafts currently detected are shown
as a list refreshing as more data arrives.
* Interactive command-line-interfae mode where aircrafts currently detected
are shown as a list refreshing as more data arrives.
* CPR coordinates decoding and track calculation from velocity.
* TCP server streaming and recceiving raw data to/from connected clients
(using --net).
* Embedded HTTP server that displays the currently detected aircrafts on
Google Map.
Installation
---
@ -155,14 +156,14 @@ Then you can feed it from different data sources from the internet.
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,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.
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
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.
A correctly demodulated message is just one that
makes sense as a Mode S message, the preamble makes
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)
Debug mode includes an optional javascript output that is used to visualize
packets using a web browser, you can use the file debug.html under the
'tools' directory to load the generated frames.js file.
How this program works?
---

11
TODO
View file

@ -1,10 +1,5 @@
TODO
* Extract from information from captured Mode S messages. Currently we only
decode what is trival to decode.
* Decode CPR encoded latitude and longitude, display it in normal and
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.
* Extract more information from captured Mode S messages.
* Improve the web interface gmap.html.
* Enhance the algorithm to reliably decode more messages.

View file

@ -65,12 +65,13 @@
#define MODES_UNIT_FEET 0
#define MODES_UNIT_METERS 1
#define MODES_DEBUG_DEMOD 1
#define MODES_DEBUG_DEMODERR 2
#define MODES_DEBUG_BADCRC 3
#define MODES_DEBUG_GOODCRC 4
#define MODES_DEBUG_NOPREAMBLE 5
#define MODES_DEBUG_NET 6
#define MODES_DEBUG_DEMOD (1<<0)
#define MODES_DEBUG_DEMODERR (1<<1)
#define MODES_DEBUG_BADCRC (1<<2)
#define MODES_DEBUG_GOODCRC (1<<3)
#define MODES_DEBUG_NOPREAMBLE (1<<4)
#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
* 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_http_requests;
long long stat_sbs_connections;
long long stat_out_of_phase;
} Modes;
/* The struct we use to store information about a decoded message. */
@ -196,6 +198,7 @@ struct modesMessage {
uint32_t crc; /* Message CRC */
int errorbit; /* Bit corrected. -1 if no bit corrected. */
int aa1, aa2, aa3; /* ICAO Address bytes 1 2 and 3 */
int phase_corrected; /* True if phase correction was applied. */
/* DF 11 */
int ca; /* Responder capabilities. */
@ -237,6 +240,7 @@ void modesSendSBSOutput(struct modesMessage *mm, struct aircraft *a);
void useModesMessage(struct modesMessage *mm);
int fixSingleBitErrors(unsigned char *msg, int bits);
int fixTwoBitsErrors(unsigned char *msg, int bits);
int modesMessageLenByType(int type);
/* ============================= Utility functions ========================== */
@ -280,7 +284,12 @@ void modesInit(void) {
pthread_mutex_init(&Modes.data_mutex,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;
/* Allocate the ICAO address cache. We use two uint32_t for every
* 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");
exit(1);
}
memset(Modes.data,127,Modes.data_len);
/* Populate the I/Q -> Magnitude lookup table. It is used because
* 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_http_requests = 0;
Modes.stat_sbs_connections = 0;
Modes.stat_out_of_phase = 0;
Modes.exit = 0;
}
@ -385,8 +396,12 @@ void rtlsdrCallback(unsigned char *buf, uint32_t len, void *ctx) {
MODES_NOTUSED(ctx);
pthread_mutex_lock(&Modes.data_mutex);
if (len > Modes.data_len) len = Modes.data_len;
memcpy(Modes.data, buf, len);
if (len > MODES_DATA_LEN) len = MODES_DATA_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;
/* Signal to the other thread that new data is ready */
pthread_cond_signal(&Modes.data_cond);
@ -414,8 +429,11 @@ void readDataFromFile(void) {
pthread_mutex_lock(&Modes.data_mutex);
}
toread = Modes.data_len;
p = Modes.data;
/* 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);
toread = MODES_DATA_LEN;
p = Modes.data+(MODES_FULL_LEN-1)*4;
while(toread) {
nread = read(Modes.fd, p, toread);
if (nread <= 0) {
@ -444,7 +462,7 @@ void *readerThreadEntryPoint(void *arg) {
if (Modes.filename == NULL) {
rtlsdr_read_async(Modes.dev, rtlsdrCallback, NULL,
MODES_ASYNC_BUF_NUMBER,
Modes.data_len);
MODES_DATA_LEN);
} else {
readDataFromFile();
}
@ -491,7 +509,7 @@ void dumpMagnitudeBar(int index, int magnitude) {
* for context. */
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 end = offset + (MODES_PREAMBLE_US*2)+(MODES_SHORT_MSG_BITS*2) - 1;
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
* 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
* m is the original magnitude vector
* 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,
uint16_t *m, uint32_t offset)
{
int j;
int msgtype = msg[0]>>3;
int fixable = 0;
int fixable = -1;
if (msgtype == 11 || msgtype == 17) {
int msgbits = (msgtype == 11) ? MODES_SHORT_MSG_BITS :
MODES_LONG_MSG_BITS;
if (fixSingleBitErrors(msg,msgbits) != -1) fixable = 1;
else if (fixTwoBitsErrors(msg,msgbits) != -1) fixable = 2;
fixable = fixSingleBitErrors(msg,msgbits);
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);
@ -640,6 +702,7 @@ int fixTwoBitsErrors(unsigned char *msg, int bits) {
int byte1 = 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++) {
int byte2 = 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
* position. */
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);
}
}
@ -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
@ -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
* size 'mlen' bytes. Every detected Mode S message is convert it into a
* stream of bits and passed to the function to display it. */
void detectModeS(uint16_t *m, uint32_t mlen) {
unsigned char bits[MODES_LONG_MSG_BITS];
unsigned char msg[MODES_LONG_MSG_BITS/2];
uint16_t aux[MODES_LONG_MSG_BITS*2];
uint32_t j;
int use_correction = 0;
/* The Mode S preamble is made of impulses of 0.5 microseconds at
* the following time offsets:
@ -1209,7 +1330,10 @@ void detectModeS(uint16_t *m, uint32_t mlen) {
* 9 -------------------
*/
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
* 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+9] > m[j+6]))
{
if (Modes.debug == MODES_DEBUG_NOPREAMBLE &&
if (Modes.debug & MODES_DEBUG_NOPREAMBLE &&
m[j] > MODES_DEBUG_NOPREAMBLE_LEVEL)
dumpRawMessage("Unexpected ratio among first 10 samples",
msg, m, j);
continue;
}
if (Modes.aggressive) {
if (!Modes.aggressive) {
/* The samples between the two spikes must be < than the average
* of the high spikes level. */
high = (m[j]+m[j+2]+m[j+7]+m[j+9])/4;
if (m[j+3] >= high ||
m[j+4] >= high ||
m[j+5] >= high ||
m[j+6] >= high)
* of the high spikes level. We don't test bits too near to
* the high levels as signals can be out of phase so part of the
* energy can be in the near samples. */
high = (m[j]+m[j+2]+m[j+7]+m[j+9])/6;
if (m[j+4] >= high ||
m[j+5] >= high)
{
if (Modes.debug == MODES_DEBUG_NOPREAMBLE &&
if (Modes.debug & MODES_DEBUG_NOPREAMBLE &&
m[j] > MODES_DEBUG_NOPREAMBLE_LEVEL)
dumpRawMessage(
"Too high level in samples between 3 and 6",
@ -1249,16 +1373,15 @@ void detectModeS(uint16_t *m, uint32_t mlen) {
continue;
}
/* Similarly samples in the range 10-15 must be low, as it is the
* space between the preamble and real data. */
if (m[j+10] >= high ||
m[j+11] >= high ||
/* Similarly samples in the range 11-14 must be low, as it is the
* space between the preamble and real data. Again we don't test
* bits too near to high levels, see above. */
if (m[j+11] >= high ||
m[j+12] >= high ||
m[j+13] >= high ||
m[j+14] >= high ||
m[j+15] >= high)
m[j+14] >= high)
{
if (Modes.debug == MODES_DEBUG_NOPREAMBLE &&
if (Modes.debug & MODES_DEBUG_NOPREAMBLE &&
m[j] > MODES_DEBUG_NOPREAMBLE_LEVEL)
dumpRawMessage(
"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++;
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
* size. We'll check the actual message type later. */
errors = 0;
for (i = 0; i < MODES_LONG_MSG_BITS*2; i += 2) {
low = m[j+i+MODES_PREAMBLE_US*2];
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
* is an effective way to detect if it's just random noise
* 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 */
for (i = 0; i < MODES_LONG_MSG_BITS; 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
* to mark this as real message and not just noise? */
int delta = 0;
delta = 0;
for (i = 0; i < msglen*8*2; i += 2) {
delta += abs(m[j+i+MODES_PREAMBLE_US*2]-
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
* every kind of message to pass, but high enough to filter some
* 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
* with a Mode S message in our hands, but it may still be broken
@ -1326,6 +1471,9 @@ void detectModeS(uint16_t *m, uint32_t mlen) {
/* Decode the received message and update statistics */
decodeModesMessage(&mm,msg);
/* Update statistics. */
if (mm.crcok || (!out_of_phase || use_correction)) {
if (errors == 0) Modes.stat_demodulated++;
if (mm.errorbit == -1) {
if (mm.crcok)
@ -1340,28 +1488,48 @@ void detectModeS(uint16_t *m, uint32_t mlen) {
else
Modes.stat_two_bits_fix++;
}
}
/* Output debug mode info if needed. */
if (Modes.debug == MODES_DEBUG_DEMOD)
if (!out_of_phase || use_correction) {
if (Modes.debug & MODES_DEBUG_DEMOD)
dumpRawMessage("Demodulated with 0 errors", msg, m, j);
else if (Modes.debug == MODES_DEBUG_BADCRC &&
else if (Modes.debug & MODES_DEBUG_BADCRC &&
mm.msgtype == 17 &&
(!mm.crcok || mm.errorbit != -1))
dumpRawMessage("Decoded with bad CRC", msg, m, j);
else if (Modes.debug == MODES_DEBUG_GOODCRC && mm.crcok &&
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 */
useModesMessage(&mm);
/* Skip this message if we are sure it's fine. */
if (mm.crcok) j += (MODES_PREAMBLE_US+(msglen*8))*2;
} 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);
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. */
if (Modes.debug == MODES_DEBUG_NET)
if (Modes.debug & MODES_DEBUG_NET)
printf("Created new client %d\n", fd);
}
}
@ -1796,7 +1964,7 @@ void modesFreeClient(int fd) {
free(Modes.clients[fd]);
Modes.clients[fd] = NULL;
if (Modes.debug == MODES_DEBUG_NET)
if (Modes.debug & MODES_DEBUG_NET)
printf("Closing client %d\n", fd);
/* 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 *ctype;
if (Modes.debug == MODES_DEBUG_NET)
if (Modes.debug & MODES_DEBUG_NET)
printf("\nHTTP request: %s\n", c->buf);
/* Minimally parse the request. */
@ -2035,7 +2203,7 @@ int handleHTTPRequest(struct client *c) {
if (!p) return 1; /* There should be a space before HTTP/... */
*p = '\0';
if (Modes.debug == MODES_DEBUG_NET) {
if (Modes.debug & MODES_DEBUG_NET) {
printf("\nHTTP keep alive: %d\n", keepalive);
printf("HTTP requested URL: %s\n\n", url);
}
@ -2082,7 +2250,7 @@ int handleHTTPRequest(struct client *c) {
keepalive ? "keep-alive" : "close",
clen);
if (Modes.debug == MODES_DEBUG_NET)
if (Modes.debug & MODES_DEBUG_NET)
printf("HTTP Reply header:\n%s", hdr);
/* Send header and content. */
@ -2206,8 +2374,16 @@ void showHelp(void) {
"--onlyaddr Show only ICAO addresses (testing purposes).\n"
"--metric Use metric units (meters, km/h, ...).\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"
"\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")) {
Modes.interactive_ttl = atoi(argv[++j]);
} 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")) {
Modes.stats = 1;
} 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 (Modes.stats && Modes.filename) {
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 bad crc\n", Modes.stat_badcrc);
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>