[hamradio-commits] [dump1090] 01/389: Initial commit of Dump1090, a simple Mode S decoder.
Matthew Ernisse
mernisse-guest at moszumanska.debian.org
Wed Nov 5 00:19:33 UTC 2014
This is an automated email from the git hooks/post-receive script.
mernisse-guest pushed a commit to branch master
in repository dump1090.
commit 7ca5a4b3a40293343be92f4b9840259935340597
Author: antirez <antirez at gmail.com>
Date: Sat Jan 5 13:52:25 2013 +0100
Initial commit of Dump1090, a simple Mode S decoder.
---
.gitignore | 3 +
Makefile | 18 +
README | 207 ++++++++
TODO | 10 +
mode1090.c | 1340 ++++++++++++++++++++++++++++++++++++++++++++++++++
testfiles/modes1.bin | 13 +
6 files changed, 1591 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f0464da
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+*.o
+mode1090
+testfiles/test*.bin
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..b6ece6f
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,18 @@
+LIBUSB_INC_PATH=/usr/local/Cellar/libusb/1.0.9/include/libusb-1.0
+LIBUSB_LIB_PATH=/usr/local/Cellar/libusb/1.0.9/lib
+LIBRTLSDR_INC_PATH=/usr/local/Cellar/rtlsdr/HEAD/include
+LIBRTLSDR_LIB_PATH=/usr/local/Cellar/rtlsdr/HEAD/lib
+LIBS=-lusb-1.0 -lrtlsdr -lpthread -lm
+CC=gcc
+PROGNAME=mode1090
+
+all: mode1090
+
+mode1090.o: mode1090.c
+ $(CC) -O2 -g -Wall -W -I$(LIBUSB_INC_PATH) -I$(LIBRTLSDR_INC_PATH) mode1090.c -c -o mode1090.o
+
+mode1090: mode1090.o
+ $(CC) -g -L$(LIBUSB_LIB_PATH) -L$(LIBRTLSDR_LIB_PATH) -o mode1090 mode1090.o $(LIBS)
+
+clean:
+ rm -f *.o mode1090
diff --git a/README b/README
new file mode 100644
index 0000000..3c3fd62
--- /dev/null
+++ b/README
@@ -0,0 +1,207 @@
+Dump1090 README
+===
+
+Dump 1090 is a Mode S decoder specifically designed for RTLSDR devices.
+
+The main features are:
+
+* Robust decoding of weak messages.
+* 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.
+
+Installation
+---
+
+Edit the Makefile and set the following variables according to your system:
+
+LIBUSB_INC_PATH=/usr/local/Cellar/libusb/1.0.9/include/libusb-1.0
+LIBUSB_LIB_PATH=/usr/local/Cellar/libusb/1.0.9/lib
+LIBRTLSDR_INC_PATH=/usr/local/Cellar/rtlsdr/HEAD/include
+LIBRTLSDR_LIB_PATH=/usr/local/Cellar/rtlsdr/HEAD/lib
+
+Then save the modified Makefile and type "make".
+
+Normal usage
+---
+
+To capture traffic directly from your RTL device and show the captured traffic
+on standard output, just run the program without options at all:
+
+ ./dump1090
+
+To just output hexadecimal messages:
+
+ ./dump1090 --raw
+
+To run the program in interactive mode:
+
+ ./dump1090 --interactive
+
+In iteractive mode it is possible to have a less information dense but more
+"arcade style" output, where the screen is refreshed every second displaying
+all the recently seen aircrafts with some additional information such as
+altitude and flight number, extracted from the received Mode S packets.
+
+Using files as source of data
+---
+
+To decode data from file, use:
+
+ ./dump1090 --ifile /path/to/binfile
+
+The binary file should be created using rtl_sdr like this (or with any other
+program that is able to output 8-bit unsigned IQ samples at 2Mhz sample rate).
+
+ rtl_sdr -f 1090000000 -s 2000000 -g 50 output.bin
+
+In the example rtl_sdr a gain of 50 is used, simply you should use the highest
+gain availabe for your tuner. This is not needed when calling Dump1090 itself
+as it is able to select the highest gain supported automatically.
+
+It is possible to feed the program with data via standard input using
+the --ifile option with "-" as argument.
+
+Additional options
+---
+
+Dump1090 can be called with other command line options to set a different
+gain, frequency, and so forth. For a list of options use:
+
+ ./dump1090 --help
+
+Everything is not documented here should be obvious, and for most users calling
+it without arguments at all is the best thing to do.
+
+Reliability
+---
+
+By default Dump1090 tries to fix single bit errors using the checksum.
+Basically the program will try to flip every bit of the message and check if
+the checksum of the resulting message matches.
+
+This is indeed able to fix errors and works reliably in my experience,
+however if you are interested in very reliable data I suggest to use
+the --no-fix command line switch in order to disable error fixing.
+
+Performances and sensibility of detection
+---
+
+In my limited experience Dump1090 was able to decode a big number of messages
+even in conditions where I encountered problems using other programs, however
+no formal test was performed so I can't really claim that this program is
+better or worse compared to other similar programs.
+
+If you can capture traffic that Dump1090 is not able to decode properly, drop
+me an email with a download link. I may try to improve the detection during
+my free time (this is just an hobby project).
+
+Antenna
+---
+
+Mode S messages are transmitted in the 1090 Mhz frequency. If you have a decent
+antenna you'll be able to pick up signals from aircrafts pretty far from your
+position, especially if you are outdoor and in a position with a good sky view.
+
+You can easily build a very cheap antenna following the istructions at:
+
+ http://antirez.com/news/46
+
+With this trivial antenna I was able to pick up signals of aircrafts 200+ Km
+away from me.
+
+Debug mode
+---
+
+The Debug mode is a visual help to improve the detection algorithm or to
+understand why the program is not working for a given input.
+
+In this mode messages are displayed in an ASCII-art style graphical
+representation, where the individial magnitude bars sampled at 2Mhz are
+displayed.
+
+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:
+
+ --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).
+
+How this program works?
+---
+
+The code is very documented and written in order to be easy to understand.
+For the diligent programmer with a Mode S specification on his hands it
+should be trivial to understand how it works.
+
+The algorithms I used were obtained basically looking at many messages
+as displayed using a trow-away SDL program, and trying to model the algorithm
+based on how the messages look graphically.
+
+How to test the program?
+---
+
+If you have an RTLSDR device and you happen to be in an area where there
+are aircrafts flying over your head, just run the program and check for signals.
+
+However if you don't have an RTLSDR device, or if in your area the presence
+of aircrafts is very limited, you may want to try the sample file distributed
+with the Dump1090 distribution under the "testfiles" directory.
+
+Just run it like this:
+
+ ./dump1090 --ifile testfiles/modes1.bin
+
+What is --strip mode?
+---
+
+It is just a simple filter that will get raw IQ 8 bit samples in input
+and will output a file missing all the parts of the file where I and Q
+are lower than the specified <level> for more than 32 samples.
+
+Use it like this:
+
+ cat big.bin | ./mode1090 --snip 25 > small.bin
+
+I used it in order to create a small test file to include inside this
+program source code distribution.
+
+Contributing
+---
+
+Mode1090 was written during some free time during xmas 2012, it is an hobby
+project so I'll be able to address issues and improve it only during
+free time, however you are incouraged to send pull requests in order to
+improve the program. A good starting point can be the TODO list included in
+the source distribution.
+
+Credits
+---
+
+Dump1090 was written by Salvatore Sanfilippo <antirez at gmail.com> and is
+released under the BSD three clause license.
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..b86ceb4
--- /dev/null
+++ b/TODO
@@ -0,0 +1,10 @@
+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.
diff --git a/mode1090.c b/mode1090.c
new file mode 100644
index 0000000..e261a5f
--- /dev/null
+++ b/mode1090.c
@@ -0,0 +1,1340 @@
+/* Mode1090, a Mode S messages decoder for RTLSDR devices.
+ *
+ * Copyright (C) 2012 by Salvatore Sanfilippo <antirez at gmail.com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <stdint.h>
+#include <errno.h>
+#include <unistd.h>
+#include <math.h>
+#include <sys/time.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include "rtl-sdr.h"
+
+#define MODES_DEFAULT_RATE 2000000
+#define MODES_DEFAULT_FREQ 1090000000
+#define MODES_DEFAULT_WIDTH 1000
+#define MODES_DEFAULT_HEIGHT 700
+#define MODES_ASYNC_BUF_NUMBER 12
+#define MODES_DATA_LEN (16*16384) /* 256k */
+#define MODES_AUTO_GAIN -100 /* Use automatic gain. */
+#define MODES_MAX_GAIN 999999 /* Use max available gain. */
+
+#define MODES_PREAMBLE_US 8 /* microseconds */
+#define MODES_LONG_MSG_BITS 112
+#define MODES_SHORT_MSG_BITS 56
+#define MODES_FULL_LEN (MODES_PREAMBLE_US+MODES_LONG_MSG_BITS)
+#define MODES_LONG_MSG_BYTES (112/8)
+#define MODES_SHORT_MSG_BYTES (56/8)
+
+#define MODES_ICAO_CACHE_LEN 32
+#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
+
+/* 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. */
+#define MODES_DEBUG_NOPREAMBLE_LEVEL 25
+
+#define MODES_INTERACTIVE_REFRESH_TIME 250 /* Milliseconds */
+
+#define MODES_NOTUSED(V) ((void) V)
+
+/* Structure used to describe an aircraft in iteractive mode. */
+struct aircraft {
+ uint32_t addr; /* ICAO address */
+ char hexaddr[7]; /* Printable ICAO address */
+ char flight[9]; /* Flight number */
+ int altitude; /* Altitude */
+ int speed; /* Velocity computed from EW and NS components. */
+ time_t seen; /* Time at which the last packet was received. */
+ long messages; /* Number of Mode S messages received. */
+ struct aircraft *next; /* Next aircraft in our linked list. */
+};
+
+/* Program global state. */
+struct {
+ /* Internal state */
+ pthread_t reader_thread;
+ pthread_mutex_t data_mutex; /* Mutex to synchronize buffer access. */
+ pthread_cond_t data_cond; /* Conditional variable associated. */
+ unsigned char *data; /* Raw IQ samples buffer */
+ unsigned char *magnitude; /* Magnitude vector */
+ uint32_t data_len; /* Buffer length. */
+ int fd; /* --ifile option file descriptor. */
+ int data_ready; /* Data ready to be processed. */
+ uint32_t icao_cache[MODES_ICAO_CACHE_LEN];/* Recently seen ICAO addresses */
+ int icao_cache_idx; /* icao_cache circular buf idx. */
+ unsigned char *maglut; /* I/Q -> Magnitude lookup table. */
+
+ /* RTLSDR */
+ int dev_index;
+ int gain;
+ int enable_agc;
+ rtlsdr_dev_t *dev;
+ int freq;
+
+ /* Configuration */
+ char *filename; /* Input form file, --ifile option. */
+ int fix_errors; /* Single bit error correction if true. */
+ int check_crc; /* Only display messages with good CRC. */
+ int raw; /* Raw output format */
+ int debug; /* Debugging mode */
+ int interactive; /* Interactive mode */
+
+ /* Interactive mode */
+ struct aircraft *aircrafts;
+ long long interactive_last_update; /* Last screen update in milliseconds */
+} Modes;
+
+/* The struct we use to store information about a decoded message. */
+struct modesMessage {
+ /* Generic fields */
+ unsigned char msg[MODES_LONG_MSG_BYTES]; /* Binary message. */
+ int msgbits; /* Number of bits in message */
+ int msgtype; /* Downlink format # */
+ int crcok; /* True if CRC was valid */
+ 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 */
+
+ /* DF 11 */
+ int ca; /* Responder capabilities. */
+
+ /* DF 17 */
+ int metype; /* Extended squitter message type. */
+ int mesub; /* Extended squitter message subtype. */
+ int heading_is_valid;
+ int heading;
+ int aircraft_type;
+ int fflag; /* Odd or Even CPR message? */
+ int tflag; /* UTC synchronized? */
+ int raw_latitude; /* Non decoded latitude */
+ int raw_longitude; /* Non decoded longitude */
+ char flight[9]; /* 8 chars flight number. */
+ int ew_dir; /* E/W direction. */
+ int ew_velocity; /* E/W velocity. */
+ int ns_dir; /* N/S direction. */
+ int ns_velocity; /* N/S velocity. */
+ int vert_rate_source; /* Vertical rate source. */
+ int vert_rate_sign; /* Vertical rate sign. */
+ int vert_rate; /* Vertical rate. */
+ int velocity; /* Computed from EW and NS velocity. */
+
+ /* DF4, DF5, DF20, DF21 */
+ int fs; /* Flight status for DF4,5,20,21 */
+ int dr; /* Request extraction of downlink request. */
+ int um; /* Request extraction of downlink request. */
+ int identity; /* 13 bits identity (Squawk). */
+
+ /* Fields used by multiple message types. */
+ int altitude, unit;
+};
+
+void interactiveShowData(void);
+void interactiveReceiveData(struct modesMessage *mm);
+
+/* ============================= Utility functions ========================== */
+
+static long long mstime(void) {
+ struct timeval tv;
+ long long mst;
+
+ gettimeofday(&tv, NULL);
+ mst = ((long long)tv.tv_sec)*1000;
+ mst += tv.tv_usec/1000;
+ return mst;
+}
+
+/* =============================== Initialization =========================== */
+
+void modesInitConfig(void) {
+ Modes.gain = MODES_MAX_GAIN;
+ Modes.dev_index = 0;
+ Modes.enable_agc = 0;
+ Modes.freq = MODES_DEFAULT_FREQ;
+ Modes.filename = NULL;
+ Modes.fix_errors = 1;
+ Modes.check_crc = 1;
+ Modes.raw = 0;
+ Modes.debug = 0;
+ Modes.interactive = 0;
+}
+
+void modesInit(void) {
+ int i, q;
+
+ pthread_mutex_init(&Modes.data_mutex,NULL);
+ pthread_cond_init(&Modes.data_cond,NULL);
+ Modes.data_len = MODES_DATA_LEN;
+ Modes.data_ready = 0;
+ memset(Modes.icao_cache,0,sizeof(Modes.icao_cache));
+ Modes.icao_cache_idx = 0;
+ Modes.aircrafts = NULL;
+ Modes.interactive_last_update = 0;
+ if ((Modes.data = malloc(Modes.data_len)) == NULL ||
+ (Modes.magnitude = malloc(Modes.data_len)) == NULL) {
+ fprintf(stderr, "Out of memory allocating data buffer.\n");
+ exit(1);
+ }
+
+ /* Populate the I/Q -> Magnitude lookup table. It is used because
+ * sqrt or round may be expensive and may vary a lot depending on
+ * the libc used.
+ *
+ * We scale to 0-255 range multiplying by 1.4 in order to ensure that
+ * every different I/Q pair will result in a different magnitude value,
+ * not losing any resolution. */
+ Modes.maglut = malloc(129*129);
+ for (i = 0; i <= 128; i++) {
+ for (q = 0; q <= 128; q++) {
+ Modes.maglut[i*129+q] = round(sqrt(i*i+q*q)*1.4);
+ }
+ }
+}
+
+/* =============================== RTLSDR handling ========================== */
+
+void modesInitRTLSDR(void) {
+ int j;
+ int device_count;
+ int ppm_error = 0;
+ char vendor[256], product[256], serial[256];
+
+ device_count = rtlsdr_get_device_count();
+ if (!device_count) {
+ fprintf(stderr, "No supported RTLSDR devices found.\n");
+ exit(1);
+ }
+
+ fprintf(stderr, "Found %d device(s):\n", device_count);
+ for (j = 0; j < device_count; j++) {
+ rtlsdr_get_device_usb_strings(j, vendor, product, serial);
+ fprintf(stderr, "%d: %s, %s, SN: %s %s\n", j, vendor, product, serial,
+ (j == Modes.dev_index) ? "(currently selected)" : "");
+ }
+
+ if (rtlsdr_open(&Modes.dev, Modes.dev_index) < 0) {
+ fprintf(stderr, "Error opening the RTLSDR device: %s\n",
+ strerror(errno));
+ exit(1);
+ }
+
+ /* Set gain, frequency, sample rate, and reset the device. */
+ rtlsdr_set_tuner_gain_mode(Modes.dev,
+ (Modes.gain == MODES_AUTO_GAIN) ? 0 : 1);
+ if (Modes.gain != MODES_AUTO_GAIN) {
+ if (Modes.gain == MODES_MAX_GAIN) {
+ /* Find the maximum gain available. */
+ int numgains;
+ int gains[100];
+
+ numgains = rtlsdr_get_tuner_gains(Modes.dev, gains);
+ Modes.gain = gains[numgains-1];
+ fprintf(stderr, "Max available gain is: %.2f\n", Modes.gain/10.0);
+ }
+ rtlsdr_set_tuner_gain(Modes.dev, Modes.gain);
+ fprintf(stderr, "Setting gain to: %.2f\n", Modes.gain/10.0);
+ } else {
+ fprintf(stderr, "Using automatic gain control.\n");
+ }
+ rtlsdr_set_freq_correction(Modes.dev, ppm_error);
+ if (Modes.enable_agc) rtlsdr_set_agc_mode(Modes.dev, 1);
+ rtlsdr_set_center_freq(Modes.dev, Modes.freq);
+ rtlsdr_set_sample_rate(Modes.dev, MODES_DEFAULT_RATE);
+ rtlsdr_reset_buffer(Modes.dev);
+ fprintf(stderr, "Gain reported by device: %.2f\n",
+ rtlsdr_get_tuner_gain(Modes.dev)/10.0);
+}
+
+/* We use a thread reading data in background, while the main thread
+ * handles decoding and visualization of data to the user.
+ *
+ * The reading thread calls the RTLSDR API to read data asynchronously, and
+ * uses a callback to populate the data buffer.
+ * A Mutex is used to avoid races with the decoding thread. */
+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);
+ Modes.data_ready = 1;
+ pthread_cond_signal(&Modes.data_cond);
+ pthread_mutex_unlock(&Modes.data_mutex);
+}
+
+/* This is used when --ifile is specified in order to read data from file
+ * instead of using an RTLSDR device. */
+void readDataFromFile(void) {
+ while(1) {
+ ssize_t nread, toread;
+ unsigned char *p;
+
+ pthread_mutex_lock(&Modes.data_mutex);
+ toread = Modes.data_len;
+ p = Modes.data;
+ while(toread) {
+ nread = read(Modes.fd, p, toread);
+ if (nread <= 0) exit(0);
+ p += nread;
+ toread -= nread;
+ }
+ Modes.data_ready = 1;
+ pthread_cond_signal(&Modes.data_cond);
+ pthread_mutex_unlock(&Modes.data_mutex);
+ }
+}
+
+/* We read data using a thread, so the main thread only handles decoding
+ * without caring about data acquisition. */
+void *readerThreadEntryPoint(void *arg) {
+ MODES_NOTUSED(arg);
+
+ if (Modes.filename == NULL) {
+ rtlsdr_read_async(Modes.dev, rtlsdrCallback, NULL,
+ MODES_ASYNC_BUF_NUMBER,
+ Modes.data_len);
+ } else {
+ readDataFromFile();
+ }
+ return NULL;
+}
+
+/* ============================== Debugging ================================= */
+
+/* Helper function for dumpMagnitudeVector().
+ * It prints a single bar used to display raw signals.
+ *
+ * Since every magnitude sample is between 0-255, the function uses
+ * up to 63 characters for every bar. Every character represents
+ * a length of 4, 3, 2, 1, specifically:
+ *
+ * "O" is 4
+ * "o" is 3
+ * "-" is 2
+ * "." is 1
+ */
+void dumpMagnitudeBar(int index, int magnitude) {
+ char *set = ".-o";
+ char buf[256];
+ int div = magnitude / 4;
+ int rem = magnitude % 4;
+
+ memset(buf,'O',div);
+ buf[div] = set[rem];
+ buf[div+1] = '\0';
+
+ if (index >= 0)
+ printf("[%.3d] |%-69s %d\n", index, buf, magnitude);
+ else
+ printf("[%.2d] |%-69s %d\n", index, buf, magnitude);
+}
+
+/* Display an ASCII-art alike graphical representation of the undecoded
+ * message as a magnitude signal.
+ *
+ * The message starts at the specified offset in the "m" buffer.
+ * The function will display enough data to cover a short 56 bit message.
+ *
+ * If possible a few samples before the start of the messsage are included
+ * for context. */
+
+void dumpMagnitudeVector(unsigned char *m, uint32_t offset) {
+ uint32_t padding = 5; /* Show 5 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;
+
+ for (j = start; j <= end; j++)
+ dumpMagnitudeBar(j-offset, m[j]);
+}
+
+/* This is a wrapper for dumpMagnitudeVector() that also show the message
+ * in hex format with an additional description.
+ *
+ * descr is the additional message to show to describe the dump.
+ * msg points to the decoded message
+ * m is the original magnitude vector
+ * offset is the offset where the message starts
+ */
+void dumpRawMessage(char *descr, unsigned char *msg,
+ unsigned char *m, uint32_t offset)
+{
+ int j;
+
+ printf("\n--- %s\n ", descr);
+ for (j = 0; j < MODES_LONG_MSG_BYTES; j++) {
+ printf("%02x",msg[j]);
+ if (j == MODES_SHORT_MSG_BYTES-1) printf(" ... ");
+ }
+ printf("\n");
+ dumpMagnitudeVector(m,offset);
+ printf("---\n\n");
+}
+
+/* ===================== Mode S detection and decoding ===================== */
+
+/* Parity table for MODE S Messages.
+ * The table contains 112 elements, every element corresponds to a bit set
+ * in the message, starting from the first bit of actual data after the
+ * preamble.
+ *
+ * For messages of 112 bit, the whole table is used.
+ * For messages of 56 bits only the last 56 elements are used.
+ *
+ * The algorithm is as simple as xoring all the elements in this table
+ * for which the corresponding bit on the message is set to 1.
+ *
+ * The latest 24 elements in this table are set to 0 as the checksum at the
+ * end of the message should not affect the computation.
+ *
+ * Note: this function can be used with DF11 and DF17, other modes have
+ * the CRC xored with the sender address as they are reply to interrogations,
+ * but a casual listener can't split the address from the checksum.
+ */
+uint32_t modes_checksum_table[112] = {
+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
+};
+
+uint32_t modesChecksum(unsigned char *msg, int bits) {
+ uint32_t crc = 0;
+ int offset = (bits == 112) ? 0 : (112-56);
+ int j;
+
+ for(j = 0; j < bits; j++) {
+ int byte = j/8;
+ int bit = j%8;
+ int bitmask = 1 << (7-bit);
+
+ /* If bit is set, xor with corresponding table entry. */
+ if (msg[byte] & bitmask)
+ crc ^= modes_checksum_table[j+offset];
+ }
+ return crc; /* 24 bit checksum. */
+}
+
+/* Given the Downlink Format (DF) of the message, return the message length
+ * in bits. */
+int modesMessageLenByType(int type) {
+ if (type == 16 || type == 17 ||
+ type == 19 || type == 20 ||
+ type == 21)
+ return MODES_LONG_MSG_BITS;
+ else
+ return MODES_SHORT_MSG_BITS;
+}
+
+/* Try to fix single bit errors using the checksum. On success modifies
+ * the original buffer with the fixed version, and returns the position
+ * of the error bit. Otherwise if fixing failed -1 is returned. */
+int fixSingleBitErrors(unsigned char *msg, int bits) {
+ int j;
+ unsigned char aux[MODES_LONG_MSG_BITS/8];
+
+ for (j = 0; j < bits; j++) {
+ int byte = j/8;
+ int bitmask = 1 << (7-(j%8));
+ uint32_t crc1, crc2;
+
+ memcpy(aux,msg,bits/8);
+ aux[byte] ^= bitmask; /* Flip j-th bit. */
+
+ crc1 = ((uint32_t)aux[(bits/8)-3] << 16) |
+ ((uint32_t)aux[(bits/8)-2] << 8) |
+ (uint32_t)aux[(bits/8)-1];
+ crc2 = modesChecksum(aux,bits);
+
+ if (crc1 == crc2) {
+ /* The error is fixed. Overwrite the original buffer with
+ * the corrected sequence, and returns the error bit
+ * position. */
+ memcpy(msg,aux,bits/8);
+ return j;
+ }
+ }
+ return -1;
+}
+
+/* Add the specified entry to the list of recently seen ICAO addresses.
+ * We use the array as a circular buffer. */
+void addRecentlySeenICAOAddr(uint32_t addr) {
+ Modes.icao_cache[Modes.icao_cache_idx] = addr;
+ Modes.icao_cache_idx = (Modes.icao_cache_idx+1) % MODES_ICAO_CACHE_LEN;
+}
+
+/* If the message type appears to be about a DF that has the checksum xored
+ * with the ICAO address, try to brute force it using a list of recently
+ * seen ICAO addresses.
+ *
+ * Do this in a brute-force fashion xoring the addresses we know with the
+ * message checksum, and latest testing if the message verifies.
+ *
+ * On success the input buffer is modified to remove the xored checksum
+ * from the packet, so that the last three bytes will contain the
+ * plain ICAO address.
+ *
+ * If the function successfully recovers a message with a correct checksum
+ * it returns 1. Otherwise 0 is returned. */
+int bruteForceAP(unsigned char *msg, int msgbits) {
+ int j;
+ unsigned char aux[MODES_LONG_MSG_BITS/8];
+ int msgtype = msg[0]>>3;
+
+ if (msgtype == 0 || /* Short air surveillance */
+ msgtype == 4 || /* Surveillance, altitude reply */
+ msgtype == 5 || /* Surveillance, identity reply */
+ msgtype == 16 || /* Long Air-Air survillance */
+ msgtype == 20 || /* Comm-A, altitude request */
+ msgtype == 21 || /* Comm-A, identity request */
+ msgtype == 24) /* Comm-C ELM */
+ {
+ for (j = 0; j < MODES_ICAO_CACHE_LEN; j++) {
+ uint32_t addr = Modes.icao_cache[j];
+ uint32_t crc1, crc2;
+ int lastbyte = (msgbits/8)-1;
+
+ if (addr == 0) continue; /* Empty field. */
+ memcpy(aux,msg,msgbits/8);
+ /* Xor with the address, so that if we picked the right address
+ * what remains is just the checksum. */
+ aux[lastbyte] ^= addr & 0xff;
+ aux[lastbyte-1] ^= (addr >> 8) & 0xff;
+ aux[lastbyte-2] ^= (addr >> 16) & 0xff;
+ crc1 = aux[lastbyte-2] << 16 |
+ aux[lastbyte-1] << 8 |
+ aux[lastbyte];
+ crc2 = modesChecksum(aux,msgbits);
+ if (crc1 == crc2) {
+ /* Restore the address as last three bytes. */
+ msg[lastbyte] = addr & 0xff;
+ msg[lastbyte-1] = (addr >> 8) & 0xff;
+ msg[lastbyte-2] = (addr >> 16) & 0xff;
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+/* Decode the 13 bit AC altitude field (in DF 20 and others).
+ * Returns the altitude, and set 'unit' to either MODES_UNIT_METERS
+ * or MDOES_UNIT_FEETS. */
+int decodeAC13Field(unsigned char *msg, int *unit) {
+ int m_bit = msg[3] & (1<<6);
+ int q_bit = msg[3] & (1<<4);
+
+ if (!m_bit) {
+ *unit = MODES_UNIT_FEET;
+ if (q_bit) {
+ /* N is the 11 bit integer resulting from the removal of bit
+ * Q and M */
+ int n = ((msg[2]&31)<<6) |
+ ((msg[3]&0x80)>>2) |
+ ((msg[3]&0x20)>>1) |
+ (msg[3]&15);
+ /* The final altitude is due to the resulting number multiplied
+ * by 25, minus 1000. */
+ return n*25-1000;
+ } else {
+ /* TODO: Implement altitude where Q=0 and M=0 */
+ }
+ } else {
+ *unit = MODES_UNIT_METERS;
+ /* TODO: Implement altitude when meter unit is selected. */
+ }
+ return 0;
+}
+
+/* Decode the 12 bit AC altitude field (in DF 17 and others).
+ * Returns the altitude or 0 if it can't be decoded. */
+int decodeAC12Field(unsigned char *msg, int *unit) {
+ int q_bit = msg[5] & 1;
+
+ if (q_bit) {
+ /* N is the 11 bit integer resulting from the removal of bit
+ * Q */
+ *unit = MODES_UNIT_FEET;
+ int n = ((msg[5]>>1)<<4) | ((msg[6]&0xF0) >> 4);
+ /* The final altitude is due to the resulting number multiplied
+ * by 25, minus 1000. */
+ return n*25-1000;
+ } else {
+ return 0;
+ }
+}
+
+/* Capability table. */
+char *ca_str[8] = {
+ /* 0 */ "Level 1 (Survillance Only)",
+ /* 1 */ "Level 2 (DF0,4,5,11)",
+ /* 2 */ "Level 3 (DF0,4,5,11,20,21)",
+ /* 3 */ "Level 4 (DF0,4,5,11,20,21,24)",
+ /* 4 */ "Level 2+3+4 (DF0,4,5,11,20,21,24,code7 - is on ground)",
+ /* 5 */ "Level 2+3+4 (DF0,4,5,11,20,21,24,code7 - is on airborne)",
+ /* 6 */ "Level 2+3+4 (DF0,4,5,11,20,21,24,code7)",
+ /* 7 */ "Level 7 ???"
+};
+
+/* Flight status table. */
+char *fs_str[8] = {
+ /* 0 */ "Normal, Airborne",
+ /* 1 */ "Normal, On the ground",
+ /* 2 */ "ALERT, Airborne",
+ /* 3 */ "ALERT, On the ground",
+ /* 4 */ "ALERT & Special Position Identification. Airborne or Ground",
+ /* 5 */ "Special Position Identification. Airborne or Ground",
+ /* 6 */ "Value 6 is not assigned",
+ /* 7 */ "Value 7 is not assigned"
+};
+
+/* ME message type to description table. */
+char *me_str[] = {
+};
+
+char *getMEDescription(int metype, int mesub) {
+ char *mename = "Unknown";
+
+ if (metype >= 1 && metype <= 4)
+ mename = "Aircraft Identification and Category";
+ else if (metype >= 5 && metype <= 8)
+ mename = "Surface Position";
+ else if (metype >= 9 && metype <= 18)
+ mename = "Airborne Position (Baro Altitude)";
+ else if (metype == 19 && mesub >=1 && mesub <= 4)
+ mename = "Airborne Velocity";
+ else if (metype >= 20 && metype <= 22)
+ mename = "Airborne Position (GNSS Height)";
+ else if (metype == 23 && mesub == 0)
+ mename = "Test Message";
+ else if (metype == 24 && mesub == 1)
+ mename = "Surface System Status";
+ else if (metype == 28 && mesub == 1)
+ mename = "Extended Squitter Aircraft Status (Emergency)";
+ else if (metype == 28 && mesub == 2)
+ mename = "Extended Squitter Aircraft Status (1090ES TCAS RA)";
+ else if (metype == 29 && (mesub == 0 || mesub == 1))
+ mename = "Target State and Status Message";
+ else if (metype == 31 && (mesub == 0 || mesub == 1))
+ mename = "Aircraft Operational Status Message";
+ return mename;
+}
+
+/* Decode a raw Mode S message demodulated as a stream of bytes by
+ * detectModeS(), and split it into fields populating a modesMessage
+ * structure. */
+void decodeModesMessage(struct modesMessage *mm, unsigned char *msg) {
+ uint32_t crc2; /* Computed CRC, used to verify the message CRC. */
+ char *ais_charset = "?ABCDEFGHIJKLMNOPQRSTUVWXYZ????? ???????????????0123456789??????";
+
+ /* Work on our local copy */
+ memcpy(mm->msg,msg,MODES_LONG_MSG_BYTES);
+ msg = mm->msg;
+
+ /* Get the message type ASAP as other operations depend on this */
+ mm->msgtype = msg[0]>>3; /* Downlink Format */
+ mm->msgbits = modesMessageLenByType(mm->msgtype);
+
+ /* CRC is always the last three bytes. */
+ mm->crc = ((uint32_t)msg[(mm->msgbits/8)-3] << 16) |
+ ((uint32_t)msg[(mm->msgbits/8)-2] << 8) |
+ (uint32_t)msg[(mm->msgbits/8)-1];
+ crc2 = modesChecksum(msg,mm->msgbits);
+
+ /* Check CRC and fix single bit errors using the CRC when
+ * possible (DF 11 and 17). */
+ mm->errorbit = -1; /* No error */
+ mm->crcok = (mm->crc == crc2);
+
+ if (!mm->crcok && Modes.fix_errors &&
+ (mm->msgtype == 11 || mm->msgtype == 17))
+ {
+ if ((mm->errorbit = fixSingleBitErrors(msg,mm->msgbits)) != -1) {
+ mm->crc = modesChecksum(msg,mm->msgbits);
+ mm->crcok = 1;
+ }
+ }
+
+ /* Note that most of the other computation happens *after* we fix
+ * the single bit errors, otherwise we would need to recompute the
+ * fields again. */
+ mm->ca = msg[0] & 7; /* Responder capabilities. */
+
+ /* ICAO address */
+ mm->aa1 = msg[1];
+ mm->aa2 = msg[2];
+ mm->aa3 = msg[3];
+
+ /* DF 17 type (assuming this is a DF17, otherwise not used) */
+ mm->metype = msg[4] >> 3; /* Extended squitter message type. */
+ mm->mesub = msg[4] & 7; /* Extended squitter message subtype. */
+
+ /* Fields for DF4,5,20,21 */
+ mm->fs = msg[0] & 7; /* Flight status for DF4,5,20,21 */
+ mm->dr = msg[1] >> 3 & 31; /* Request extraction of downlink request. */
+ mm->um = ((msg[1] & 7)<<3)| /* Request extraction of downlink request. */
+ msg[2]>>5;
+ mm->identity = (msg[2]&31 << 8) | msg[3]; /* 13 bits identity. */
+
+ /* Check if we can check the checksum for the Downlink Formats where
+ * the checksum is xored with the aircraft ICAO address. We try to
+ * brute force it using a list of recently seen aircraft addresses. */
+ if (mm->msgtype != 11 && mm->msgtype != 17) {
+ /* Return to the caller now if we can't resolve the API field and
+ * we need to check the checksum. */
+ if (bruteForceAP(msg,mm->msgbits)) {
+ /* We recovered the message!
+ * Populate the AA fields with the right information. */
+ mm->aa3 = msg[mm->msgbits/8-1];
+ mm->aa2 = msg[mm->msgbits/8-2];
+ mm->aa1 = msg[mm->msgbits/8-3];
+ mm->crcok = 1;
+ } else {
+ mm->crcok = 0;
+ }
+ }
+ /* If this is DF 11 or DF 17 and the checksum was ok,
+ * we can add this address to the list of recently seen
+ * addresses. */
+ if (mm->crcok && mm->errorbit == -1) {
+ uint32_t addr = (mm->aa1 << 16) | (mm->aa2 << 8) | mm->aa3;
+ addRecentlySeenICAOAddr(addr);
+ }
+
+ /* Decode 13 bit altitude for DF0, DF4, DF16, DF20 */
+ if (mm->msgtype == 0 || mm->msgtype == 4 ||
+ mm->msgtype == 16 || mm->msgtype == 20) {
+ mm->altitude = decodeAC13Field(msg, &mm->unit);
+ }
+
+ /* Decode extended squitter specific stuff. */
+ if (mm->msgtype == 17) {
+ /* Decode the extended squitter message. */
+
+ if (mm->metype >= 1 && mm->metype <= 4) {
+ /* Aircraft Identification and Category */
+ mm->aircraft_type = mm->metype-1;
+ mm->flight[0] = ais_charset[msg[5]>>2];
+ mm->flight[1] = ais_charset[((msg[5]&3)<<4)|(msg[6]>>4)];
+ mm->flight[2] = ais_charset[((msg[6]&15)<<2)|(msg[7]>>6)];
+ mm->flight[3] = ais_charset[msg[7]&63];
+ mm->flight[4] = ais_charset[msg[8]>>2];
+ mm->flight[5] = ais_charset[((msg[8]&3)<<4)|(msg[9]>>4)];
+ mm->flight[6] = ais_charset[((msg[9]&15)<<2)|(msg[10]>>6)];
+ mm->flight[7] = ais_charset[msg[10]&63];
+ mm->flight[8] = '\0';
+ } else if (mm->metype >= 9 && mm->metype <= 18) {
+ /* Airborne position Message */
+ mm->fflag = msg[6] & (1<<2);
+ mm->tflag = msg[6] & (1<<3);
+ mm->altitude = decodeAC12Field(msg,&mm->unit);
+ mm->raw_latitude = ((msg[6] & 3) << 15) |
+ (msg[7] << 7) |
+ (msg[8] >> 1);
+ mm->raw_longitude = ((msg[8]&1) << 16) |
+ (msg[9] << 8) |
+ msg[10];
+ } else if (mm->metype == 19 && mm->mesub >= 1 && mm->mesub <= 4) {
+ /* Airborne Velocity Message */
+ if (mm->mesub == 1 || mm->mesub == 2) {
+ mm->ew_dir = (msg[5]&4) >> 2;
+ mm->ew_velocity = ((msg[5]&3) << 8) | msg[6];
+ mm->ns_dir = (msg[7]&0x80) >> 7;
+ mm->ns_velocity = ((msg[7]&0x7f) << 3) | ((msg[8]&0xe0) >> 5);
+ mm->vert_rate_source = (msg[8]&0x10) >> 4;
+ mm->vert_rate_sign = (msg[8]&0x8) >> 5;
+ mm->vert_rate = ((msg[8]&7) << 6) | ((msg[9]&0xfc) >> 2);
+ mm->velocity = sqrt(mm->ns_velocity*mm->ns_velocity+
+ mm->ew_velocity*mm->ew_velocity);
+ } else if (mm->mesub == 3 || mm->mesub == 4) {
+ mm->heading_is_valid = msg[5] & (1<<2);
+ mm->heading = (360.0/128) * (((msg[5] & 3) << 5) |
+ (msg[6] >> 3));
+ }
+ }
+ }
+}
+
+/* This function gets a decoded Mode S Message and prints it on the screen
+ * in a human readable format. */
+void displayModesMessage(struct modesMessage *mm) {
+ int j;
+
+ /* Show the raw message. */
+ for (j = 0; j < mm->msgbits/8; j++) printf("%02x", mm->msg[j]);
+ printf("\n");
+
+ if (Modes.raw) return; /* Enough for --raw mode */
+
+ printf("CRC: %06x (%s)\n", (int)mm->crc, mm->crcok ? "ok" : "wrong");
+ if (mm->errorbit != -1)
+ printf("Single bit error fixed, bit %d\n", mm->errorbit);
+
+ if (mm->msgtype == 0) {
+ /* DF 0 */
+ printf("DF 0: Short Air-Air Surveillance.\n");
+ printf(" Altitude : %d %s\n", mm->altitude,
+ (mm->unit == MODES_UNIT_METERS) ? "meters" : "feet");
+ printf(" ICAO Address : %02x%02x%02x\n", mm->aa1, mm->aa2, mm->aa3);
+ } else if (mm->msgtype == 4 || mm->msgtype == 20) {
+ printf("DF %d: %s, Altitude Reply.\n", mm->msgtype,
+ (mm->msgtype == 4) ? "Surveillance" : "Comm-B");
+ printf(" Flight Status : %s\n", fs_str[mm->fs]);
+ printf(" DR : %d\n", mm->dr);
+ printf(" UM : %d\n", mm->um);
+ printf(" Altitude : %d %s\n", mm->altitude,
+ (mm->unit == MODES_UNIT_METERS) ? "meters" : "feet");
+ printf(" ICAO Address : %02x%02x%02x\n", mm->aa1, mm->aa2, mm->aa3);
+
+ if (mm->msgtype == 20) {
+ /* TODO: 56 bits DF20 MB additional field. */
+ }
+ } else if (mm->msgtype == 5 || mm->msgtype == 21) {
+ printf("DF %d: %s, Identity Reply.\n", mm->msgtype,
+ (mm->msgtype == 5) ? "Surveillance" : "Comm-B");
+ printf(" Flight Status : %s\n", fs_str[mm->fs]);
+ printf(" DR : %d\n", mm->dr);
+ printf(" UM : %d\n", mm->um);
+ printf(" Squawk : %o (octal)\n", mm->identity);
+ printf(" ICAO Address : %02x%02x%02x\n", mm->aa1, mm->aa2, mm->aa3);
+
+ if (mm->msgtype == 21) {
+ /* TODO: 56 bits DF21 MB additional field. */
+ }
+ } else if (mm->msgtype == 11) {
+ /* DF 11 */
+ printf("DF 11: All Call Reply.\n");
+ printf(" Capability : %s\n", ca_str[mm->ca]);
+ printf(" ICAO Address: %02x%02x%02x\n", mm->aa1, mm->aa2, mm->aa3);
+ } else if (mm->msgtype == 17) {
+ /* DF 17 */
+ printf("DF 17: ADS-B message.\n");
+ printf(" Capability : %d (%s)\n", mm->ca, ca_str[mm->ca]);
+ printf(" ICAO Address : %02x%02x%02x\n", mm->aa1, mm->aa2, mm->aa3);
+ printf(" Extended Squitter Type: %d\n", mm->metype);
+ printf(" Extended Squitter Sub : %d\n", mm->mesub);
+ printf(" Extended Squitter Name: %s\n",
+ getMEDescription(mm->metype,mm->mesub));
+
+ /* Decode the extended squitter message. */
+ if (mm->metype >= 1 && mm->metype <= 4) {
+ /* Aircraft identification. */
+ char *ac_type_str[4] = {
+ "Aircraft Type D",
+ "Aircraft Type C",
+ "Aircraft Type B",
+ "Aircraft Type A"
+ };
+
+ printf(" Aircraft Type : %s\n", ac_type_str[mm->aircraft_type]);
+ printf(" Identification : %s\n", mm->flight);
+ } else if (mm->metype >= 9 && mm->metype <= 18) {
+ printf(" F flag : %s\n", mm->fflag ? "odd" : "even");
+ printf(" T flag : %s\n", mm->tflag ? "UTC" : "non-UTC");
+ printf(" Altitude : %d feet\n", mm->altitude);
+ printf(" Latitude : %d (not decoded)\n", mm->raw_latitude);
+ printf(" Longitude: %d (not decoded)\n", mm->raw_longitude);
+ } else if (mm->metype == 19 && mm->mesub >= 1 && mm->mesub <= 4) {
+ if (mm->mesub == 1 || mm->mesub == 2) {
+ /* Velocity */
+ printf(" EW direction : %d\n", mm->ew_dir);
+ printf(" EW velocity : %d\n", mm->ew_velocity);
+ printf(" NS direction : %d\n", mm->ns_dir);
+ printf(" NS velocity : %d\n", mm->ns_velocity);
+ printf(" Vertical rate src : %d\n", mm->vert_rate_source);
+ printf(" Vertical rate sign: %d\n", mm->vert_rate_sign);
+ printf(" Vertical rate : %d\n", mm->vert_rate);
+ } else if (mm->mesub == 3 || mm->mesub == 4) {
+ printf(" Heading status: %d", mm->heading_is_valid);
+ printf(" Heading: %d", mm->heading);
+ }
+ } else {
+ printf(" Unrecognized ME type: %d subtype: %d\n",
+ mm->metype, mm->mesub);
+ }
+ } else {
+ if (Modes.check_crc)
+ printf("DF %d with good CRC received "
+ "(decoding still not implemented).\n",
+ mm->msgtype);
+ }
+}
+
+/* Turn I/Q samples pointed by Modes.data into the magnitude vector
+ * pointed by Modes.magnitude. */
+void computeMagnitudeVector(void) {
+ unsigned char *m = Modes.magnitude, *p = Modes.data;
+ uint32_t j;
+
+ /* Compute the magnitudo vector. It's just SQRT(I^2 + Q^2), but
+ * we rescale to the 0-255 range to exploit the full resolution. */
+ for (j = 0; j < Modes.data_len; j += 2) {
+ int i = p[j]-127;
+ int q = p[j+1]-127;
+
+ if (i < 0) i = -i;
+ if (q < 0) q = -q;
+ m[j/2] = Modes.maglut[i*129+q];
+ }
+}
+
+/* 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(unsigned char *m, uint32_t mlen) {
+ unsigned char bits[MODES_LONG_MSG_BITS];
+ unsigned char msg[MODES_LONG_MSG_BITS/2];
+ uint32_t j;
+
+ /* The Mode S preamble is made of impulses of 0.5 microseconds at
+ * the following time offsets:
+ *
+ * 0 - 0.5 usec: first impulse.
+ * 1.0 - 1.5 usec: second impulse.
+ * 3.5 - 4 usec: third impulse.
+ * 4.5 - 5 usec: last impulse.
+ *
+ * Since we are sampling at 2 Mhz every sample in our magnitude vector
+ * is 0.5 usec, so the preamble will look like this, assuming there is
+ * an impulse at offset 0 in the array:
+ *
+ * 0 -----------------
+ * 1 -
+ * 2 ------------------
+ * 3 --
+ * 4 -
+ * 5 --
+ * 6 -
+ * 7 ------------------
+ * 8 --
+ * 9 -------------------
+ */
+ for (j = 0; j < mlen - MODES_FULL_LEN*2; j++) {
+ int low, high, i, errors;
+
+ /* First check of relations between the first 10 samples
+ * representing a valid preamble. We don't even investigate further
+ * if this simple test is not passed. */
+ if (!(m[j] > m[j+1] &&
+ m[j+1] < m[j+2] &&
+ m[j+2] > m[j+3] &&
+ m[j+3] < m[j] &&
+ m[j+4] < m[j] &&
+ m[j+5] < m[j] &&
+ m[j+6] < m[j] &&
+ m[j+7] > m[j+8] &&
+ m[j+8] < m[j+9] &&
+ m[j+9] > m[j+6]))
+ {
+ if (Modes.debug == MODES_DEBUG_NOPREAMBLE &&
+ m[j] > MODES_DEBUG_NOPREAMBLE_LEVEL)
+ dumpRawMessage("Unexpected ratio among first 10 samples",
+ msg, m, j);
+ continue;
+ }
+
+ /* 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)
+ {
+ if (Modes.debug == MODES_DEBUG_NOPREAMBLE &&
+ m[j] > MODES_DEBUG_NOPREAMBLE_LEVEL)
+ dumpRawMessage("Too high level in samples between 3 and 6",
+ msg, m, j);
+ 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 ||
+ m[j+12] >= high ||
+ m[j+13] >= high ||
+ m[j+14] >= high ||
+ m[j+15] >= high)
+ {
+ if (Modes.debug == MODES_DEBUG_NOPREAMBLE &&
+ m[j] > MODES_DEBUG_NOPREAMBLE_LEVEL)
+ dumpRawMessage("Too high level in samples between 10 and 15",
+ msg, m, j);
+ continue;
+ }
+
+ /* 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) {
+ /* 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. */
+ bits[i/2] = 2; /* error */
+ if (i < MODES_SHORT_MSG_BITS*2) errors++;
+ } else if (low > high) {
+ bits[i/2] = 1;
+ } else {
+ /* (low < high) for exclusion */
+ bits[i/2] = 0;
+ }
+ }
+
+ /* Pack bits into bytes */
+ for (i = 0; i < MODES_LONG_MSG_BITS; i += 8) {
+ msg[i/8] =
+ bits[i]<<7 |
+ bits[i+1]<<6 |
+ bits[i+2]<<5 |
+ bits[i+3]<<4 |
+ bits[i+4]<<3 |
+ bits[i+5]<<2 |
+ bits[i+6]<<1 |
+ bits[i+7];
+ }
+
+ int msgtype = msg[0]>>3;
+ int msglen = modesMessageLenByType(msgtype)/8;
+
+ /* Last check, high and low bits are different enough in magnitude
+ * to mark this as real message and not just noise? */
+ int 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]);
+ }
+ delta /= msglen*4;
+
+ /* A avg delta of three is small enough to let almost every kind of
+ * message to pass, but high enough to filter some random noise,
+ * especially when --no-crc-check is used. */
+ if (delta < 3) 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
+ * and CRC may not be correct. This is handled by the next layer. */
+ if (errors == 0) {
+ struct modesMessage mm;
+
+ decodeModesMessage(&mm,msg);
+
+ if (Modes.debug == MODES_DEBUG_DEMOD)
+ dumpRawMessage("Demodulated with 0 errors", msg, m, j);
+ else if (Modes.debug == MODES_DEBUG_BADCRC && !mm.crcok)
+ dumpRawMessage("Decoded with bad CRC", msg, m, j);
+ else if (Modes.debug == MODES_DEBUG_GOODCRC && mm.crcok &&
+ mm.errorbit == -1)
+ dumpRawMessage("Decoded with good CRC", msg, m, j);
+
+ if (Modes.check_crc == 0 || mm.crcok) {
+ if (Modes.interactive) {
+ interactiveReceiveData(&mm);
+ } else {
+ displayModesMessage(&mm);
+ if (!Modes.raw) printf("\n");
+ }
+ }
+
+ /* Skip this message. Inside it we can find only fake premables. */
+ j += (MODES_PREAMBLE_US+(msglen*8))*2;
+ } else {
+ if (Modes.debug == MODES_DEBUG_DEMODERR) {
+ printf("The following message has %d demod errors\n", errors);
+ dumpRawMessage("Demodulated with errors", msg, m, j);
+ }
+ }
+ }
+}
+
+/* ========================= Interactive mode =============================== */
+
+/* Return a new aircraft structure for the interactive mode linked list
+ * of aircrafts. */
+struct aircraft *interactiveCreateAircraft(uint32_t addr) {
+ struct aircraft *a = malloc(sizeof(*a));
+
+ a->addr = addr;
+ snprintf(a->hexaddr,sizeof(a->hexaddr),"%06x",(int)addr);
+ a->flight[0] = '\0';
+ a->altitude = 0;
+ a->seen = time(NULL);
+ a->messages = 0;
+ a->next = NULL;
+ return a;
+}
+
+/* Return the aircraft with the specified address, or NULL if no aircraft
+ * exists with this address. */
+struct aircraft *interactiveFindAircraft(uint32_t addr) {
+ struct aircraft *a = Modes.aircrafts;
+
+ while(a) {
+ if (a->addr == addr) return a;
+ a = a->next;
+ }
+ return NULL;
+}
+
+/* Receive new messages and populate the interactive mode with more info. */
+void interactiveReceiveData(struct modesMessage *mm) {
+ uint32_t addr;
+ struct aircraft *a, *aux;
+
+ if (Modes.check_crc && mm->crcok == 0) return;
+ addr = (mm->aa1 << 16) | (mm->aa2 << 8) | mm->aa3;
+
+ /* Loookup our aircraft or create a new one. */
+ a = interactiveFindAircraft(addr);
+ if (!a) {
+ a = interactiveCreateAircraft(addr);
+ a->next = Modes.aircrafts;
+ Modes.aircrafts = a;
+ } else {
+ /* If it is an already known aircraft, move it on head
+ * so we keep aircrafts ordered by received message time.
+ *
+ * However move it on head only if at least one second elapsed
+ * since the aircraft that is currently on head sent a message,
+ * othewise with multiple aircrafts at the same time we have an
+ * useless shuffle of positions on the screen. */
+ if (Modes.aircrafts != a && (time(NULL) - a->seen) >= 1) {
+ aux = Modes.aircrafts;
+ while(aux->next != a) aux = aux->next;
+ /* Now we are a node before the aircraft to remove. */
+ aux->next = aux->next->next; /* removed. */
+ /* Add on head */
+ a->next = Modes.aircrafts;
+ Modes.aircrafts = a;
+ }
+ }
+
+ a->seen = time(NULL);
+ a->messages++;
+
+ if (mm->msgtype == 0 || mm->msgtype == 4 || mm->msgtype == 20) {
+ a->altitude = mm->altitude;
+ } else if (mm->msgtype == 17) {
+ if (mm->metype >= 1 && mm->metype <= 4) {
+ memcpy(a->flight, mm->flight, sizeof(a->flight));
+ } else if (mm->metype >= 9 && mm->metype <= 18) {
+ a->altitude = mm->altitude;
+ } else if (mm->metype == 19) {
+ if (mm->mesub == 1 || mm->mesub == 2) {
+ a->speed = mm->velocity;
+ }
+ }
+ }
+}
+
+/* Show the currently captured interactive data on screen. */
+void interactiveShowData(void) {
+ struct aircraft *a = Modes.aircrafts;
+ time_t now = time(NULL);
+ char progress[4];
+ int count = 0;
+
+ memset(progress,' ',3);
+ progress[time(NULL)%3] = '.';
+ progress[3] = '\0';
+
+ printf("\x1b[H\x1b[2J"); /* Clear the screen */
+ printf("Hex Flight Altitude Speed Messages Seen %s\n",
+ progress);
+ printf("----------------------------------------------------\n");
+ while(a && count < 15) {
+ printf("%-6s %-8s %-9d %-9d %-9ld %d sec ago\n",
+ a->hexaddr, a->flight, a->altitude, a->speed, a->messages,
+ (int)(now - a->seen));
+ a = a->next;
+ count++;
+ }
+}
+
+/* ============================== Snip mode ================================= */
+
+/* Get raw IQ samples and filter everything is < than the specified level
+ * for more than 256 samples in order to reduce example file size. */
+void snipMode(int level) {
+ int i, q;
+ long long c = 0;
+
+ while ((i = getchar()) != EOF && (q = getchar()) != EOF) {
+ if (abs(i-127) < level && abs(q-127) < level) {
+ c++;
+ if (c > MODES_PREAMBLE_US*4) continue;
+ } else {
+ c = 0;
+ }
+ putchar(i);
+ putchar(q);
+ }
+}
+
+/* ================================ Main ==================================== */
+
+void showHelp(void) {
+ printf(
+"--device-index <index> Select RTL device (default: 0).\n"
+"--gain <db> Set gain (default: max gain. Use 0 for auto-gain).\n"
+"--enable-agc <db> Enable the Automatic Gain Control (default: off).\n"
+"--freq <hz> Set frequency (default: 1090 Mhz).\n"
+"--ifile <filename> Read data from file (use '-' for stdin).\n"
+"--interactive Interactive mode refreshing data on screen.\n"
+"--raw Show only messages hex values.\n"
+"--no-fix Disalbe single-bits error correction using CRC.\n"
+"--no-crc-check Disalbe messages with broken CRC.\n"
+"--snip <level> Strip IQ file removing samples < level.\n"
+"--help Show this help.\n"
+ );
+}
+
+int main(int argc, char **argv) {
+ int j;
+
+ /* Set sane defaults. */
+ modesInitConfig();
+
+ /* Parse the command line options */
+ for (j = 1; j < argc; j++) {
+ int more = j+1 < argc; /* There are more arguments. */
+
+ if (!strcmp(argv[j],"--device-index") && more) {
+ Modes.dev_index = atoi(argv[++j]);
+ } else if (!strcmp(argv[j],"--gain") && more) {
+ Modes.gain = atof(argv[++j])*10; /* Gain is in tens of DBs */
+ } else if (!strcmp(argv[j],"--enable-agc")) {
+ Modes.enable_agc++;
+ } else if (!strcmp(argv[j],"--freq") && more) {
+ Modes.freq = strtoll(argv[++j],NULL,10);
+ } else if (!strcmp(argv[j],"--ifile") && more) {
+ Modes.filename = strdup(argv[++j]);
+ } else if (!strcmp(argv[j],"--no-fix")) {
+ Modes.fix_errors = 0;
+ } else if (!strcmp(argv[j],"--no-crc-check")) {
+ Modes.check_crc = 0;
+ } else if (!strcmp(argv[j],"--raw")) {
+ Modes.raw = 1;
+ } else if (!strcmp(argv[j],"--interactive")) {
+ Modes.interactive = 1;
+ } else if (!strcmp(argv[j],"--debug") && more) {
+ Modes.debug = atoi(argv[++j]);
+ } else if (!strcmp(argv[j],"--snip") && more) {
+ snipMode(atoi(argv[++j]));
+ exit(0);
+ } else if (!strcmp(argv[j],"--help")) {
+ showHelp();
+ exit(0);
+ } else {
+ fprintf(stderr,
+ "Unknown or not enough arguments for option '%s'.\n\n",
+ argv[j]);
+ showHelp();
+ exit(1);
+ }
+ }
+
+ modesInit();
+ if (Modes.filename == NULL) {
+ modesInitRTLSDR();
+ } else {
+ if (Modes.filename[0] == '-' && Modes.filename[1] == '\0') {
+ Modes.fd = STDIN_FILENO;
+ } else if ((Modes.fd = open(Modes.filename,O_RDONLY)) == -1) {
+ perror("Opening data file");
+ exit(1);
+ }
+ }
+
+ /* Create the thread that will read the data from the device. */
+ pthread_create(&Modes.reader_thread, NULL, readerThreadEntryPoint, NULL);
+
+ pthread_mutex_lock(&Modes.data_mutex);
+ while(1) {
+ if (!Modes.data_ready) {
+ pthread_cond_wait(&Modes.data_cond,&Modes.data_mutex);
+ continue;
+ }
+ computeMagnitudeVector();
+
+ /* If we are reading data from the RTLSDR device, process data
+ * after releasing the lock, so that the capturing thread can
+ * read data while we perform computationally expensive stuff
+ * at the same time. (This should only be useful with very
+ * slow processors).
+ *
+ * Instead if we are reading from file, process data before
+ * releasing the lock, in order to avoid missing parts of the file
+ * to process.
+ */
+ if (Modes.filename) detectModeS(Modes.magnitude, Modes.data_len/2);
+ Modes.data_ready = 0;
+ pthread_mutex_unlock(&Modes.data_mutex);
+ if (!Modes.filename) detectModeS(Modes.magnitude, Modes.data_len/2);
+
+ pthread_mutex_lock(&Modes.data_mutex);
+
+ /* Refresh screen when in interactive mode. */
+ if (Modes.interactive &&
+ (mstime() - Modes.interactive_last_update) >
+ MODES_INTERACTIVE_REFRESH_TIME)
+ {
+ interactiveShowData();
+ Modes.interactive_last_update = mstime();
+ }
+ }
+
+ rtlsdr_close(Modes.dev);
+ return 0;
+}
diff --git a/testfiles/modes1.bin b/testfiles/modes1.bin
new file mode 100644
index 0000000..62f7f97
--- /dev/null
+++ b/testfiles/modes1.bin
@@ -0,0 +1,13 @@
+�~}}�~�~����~�~�������~~~�~~~���~�~����~�~}�~�}��x�n{�qzw{t|syvu|��u|v�x�p�|�x�{��������������������~�|�{~�f�t�x�v�~}�����}���������~}��{�w�z�t�v�r}y{rzyvuy{qzy~f�p�}x�y�~������������|����|�~�t�s��y�g~s}xwr��{ygvr~x�o��y�q�|�~~�������������������}��~���}��}pxp��}~�����v���}}�������}������~~~���������}�l�s��~�|���}}�����~��xf~}~�hp�{e~~���~�~���~|����||��~�~��~~~~~�wh}|���~���~�}�~�c��y�l��� [...]
�X�q�m�h���ɦ�����ט�}�e�Qu��_� �8zK{(dWY4We*H��Ws �8�[�.Îvi�d��ʔ��ӧ�����͞�x�x�h�Ep��J�.�`n g7^VK:Pl5WM�*{Y�#��s]�W�|�}���vn������̞�y�x�s�"�I�Q�0{N�+h[M&��R^*Y��|}}~~~���~���������������������~�~����$�b�
}_�~{~}%Mfs
y\���{����~�m聜y�䐛̿������{�_�p�;�e�"�\~�l? ms�Lex{ �l�)�d��yR�o���}~�䐗������|��i�t�B�g�*�^�
�Z[$q]6;fi!Y hj��|
�f�F�l�g�y����ݑ�˾��y}�}�_�4�o�)�`�xzbQ'ju�~c`|
�]�+��x�|t끡���Ę�ݨ���y��L�n�/� i��jucB.is
c]v
�]��|+�5�}�{������̟�t|��y�s�� [...]
+�h�.�i�K�{�}���Ȕ�֨�����o�N�n�;�f�#~l}a>u{xu'Vaz!�e���y�z�\�������ϔ�ѫ����q�x�P�n�}��wr�yR0%p{|y^ �h�*�f�B�w�|�tᄜ�ӎ�����ߨ���v��N�3�j�*�`�&|a[1kd>Een%dby$�o�x�9�u�V�p������َ����|���m�v�@�$�h�&�bp"wn|y9=gp&df|�
+�u�z7�W�}����~����Ȍ���ሞ|�X�t�3��k�|l~yP*ot��}}�~����}���������������~��~~���~�~�~~|~}�~��>�JmKd>��wy��=sKwJ�;��}z����~t����um�����������d�m�V�X�I�H~Kw3��gI"Ec��6sBU� �[�\�ZŇpt�����sr���������so��\�\�K�J�E�BkGkDUWVQHjAgS| �H��tK�]�s�r�������������ˑi|�o�f�e��R�G�/��e;^Ibc
+,?n��5�F�R�J�f�9�w�o�ȑ������������q�_r��L�L�U� s?��^?XL\f @Ov?�?�Q�?��u^�U���u��������ɝox���X�Xt��D�L�F�@kLg at e]Be��7{E�M�F�^�V�q�j����������������x�mk��R�W�H�E|G{=bN\E`b2=q��9�C�^��f�m�p�k�����ٯ���Ѓj��b�b�O�L�H�AoJk>i]ReHo/r�;�I�^�U�p�n����Ó�������Ζoy�|�J�Ov��B�H{H{:sZ6OU��||}����������������������~~}~�~~~}~~~�}|��~����������yy��z��x�x�j�ex��}�~|~��jWee��RgdvU�]�_�5�q�j�r�}�}�����zv��̣�~t��v�t�m�=�k�X�X�xWT6fo��Pjb{^*�f��v_�s�y�z�����������������u�V�g}��R� [...]
��o�0�r���^�r�}������Ԏ�Ӣ���~��R��u�,�p~rynN2ol3Sju%{qw{�.�n�M�s��||�����~~����~~~�����~�~~����}��~~~������~{�{��z��|~��Ι��~�|{��e�@��P�5��bNeJ��{~��~~���}�������}~���~����~�{x��k�Q{��}�~�}�����~��~��~���~����������~�~��Kz��W�9��|��~�������~��{|��ů��}}���~~�|}��[�=~��|���{���<yTv��y~��~~�~��~��~~��~�}����}~��}~�~�qbe5jn>C�~y��{`�2�l�?��{~�~��||����ѩy��|�d�j�g�G�b�2|b~-shR-��ms
I&j�{\�/�r�+�O�z�u�{w�����u��Փ�~�_�Y{��_�;�`�+tbe-jjG at cv4]`�(�n��7��tx�c͆��ׅ��緾��ܞs��s�h�v�&�4~��hv t/ngD3��hwa.sf�&��zq�=�`�~|�����ʗ�����Ж�~�jz��e�@�o��-saj.lhJ?js&\�z^�-�l�8�|}~��~~�����������~���~~�~��~��}}�~~��x~��n�J~��}���~�}����~��~~�����~�͂�~z��~{��r�U��������|{��Y]VS��z|�~�~~�~~�������������~~�����~��~�����t|���}y��F�]y��z~�M<vh��+_vuy�)�y}�}�����~~����������~��~������a5zh��{�~~�~� [...]
+iq}(�e��|@�H�z���t���ҍ�{~��w�{�M�q�6�k�)�g}�T(yk5H@ry(|e}4�f�H�j��zs�{��Є�|w�~~~~��������~�~��~~~��{������~��������|u���~}��|��z�|��v�)��wmg*��flCM��~�~�~��~��~�~�~~����}~��u�7�}}�~����~�~��~~��~��~�~~~����~�}��]{��f�9��~����~������~���~��~�~~�����ņ�{{�������{x��k�B}��}����z~��IhLf��z|�}}~�������~�~��������~�~~~~�~���~��3�i� �}~|�~ms7Jkz!o{{}|~~��}|�y���~���������ړ���l�t [...]
+S]y� �g�%�d��uX�����x~�嗗���}~��`�r�7�a�
�W�~UT mY.9^gW kd��x�b�E�j�f�����旡t|鹟���e�p|��/� �h�}R��P
mmNVu�{� �k��x:�N�|��셪�ٓ���w��}�|�P�j�,�[��WmwVD*g\%J[hoW~�}x}~}~�}~~~~~}�~�~���������������~���~}���Ex/{Q�&��{x��qv�yЎ���|t�����~s��z�bj��T�E�N�)�]f d:\TI=Nl2ZQ��~S��Mko�؉�������شqt���o�Vp��T�6�`� t!��cES;Rc8ONy+oN�+�W�;�i�X�{����vn������֚���g�`�c�5q��E�)z^Y J.��NX6WM}(vO�,�[�?�m�`Є��֘��Ϧ�ö��ܓ�~�[�Mt��O�0�b� n1kTF'��^b M �zB�4�^�F�r�iځ����Ʀ�����ў�{� [...]
+e6��xV�D�j�W�z�u�������wy���ȉz��|�I�\�`�7���Q};kabAblETiwp?�\�=�k�J��wv�����������ȭs���{�w�?�J��S�@x\s:hcZE_m at Zjzy5��|b�0�[�y�oą���y{����Ɩx��z�v�j�W�`�B�i�o4��oa(0LYWx=q_�9�g�B��ss�mǁ���������q}��dž�|�J�]�`�F�a�-��xbD
JK�VsAk\�9�e�F�m�\�x�{���ux�����������y�hz��m��E�^z/��ua.)OW]s<ib�.��x]�M�u�^�x�����sw������œ�w�p�v�.�N�\�;~ct1���zz�~}|~~~~~~��~~�~����������~�~��<�\fLmQ��zw�}9uXyL�O��{x�|~�xx����xp���������{�f�u�R�e�E�U}F}G��`G.'\h�~7uTyJ�
�f�X�]��pw� [...]
?dq>|QyN�I��wY�W�|�|y���됝����n~ˀ��j�5�l�D�XG~D��_E++[h�8wVwF�Q�Y�Z�k�`�y���������������~q��W�i�J�Y}E�QgHrQ^\
3Vn�|5�T�R��h�c�j�}n�ˉ����ږ������r�{�X�k�N�L~�x3�V_PlPNbZ`Rvy\~H�P�_�Z��rx���zz���Ƭ�x����e�l{��R��Q��p;yX\U[S��{y~~~}~}�~}~��~�������~�����������~~����~�;��yoa>��}~�����������~��~���}�Ā�z��~~~��}mm<�|~}~}�{��{Z�O��|{��~������~�����~���~~��|��~����}�|��~�����~Cqfy��{��|�^�{�|~�~|����u|��Ďr��v�jx���~��������� [...]
�\��nk�{�������os���������i�h�U�R�H�EzGw:n[/HW��:gFtH�;�]�
�d�l�i�����ui�����������t�ch��J�I�\� r=��^>\NKeB^Zx }>��uC�T�m�e����ƌ�����luŚ���{�\�Wq��E�F�Y� sOcK`FWd7U��It �C��zQ�/�p�x���zg�����������{�{�g�On��T� {<��h<dIT_NTJq;o[� �T�V�O�p�h���������po�����|Á�g�d�T�I�^� s<��`AXKbj :MsI~-��yE�Q�m�O�{����¢����������}g��_�]�Q�H�K�9y[F `TQ\IWPw.w��M��T��sc�j���{s���꼡sx���}�fn��T�T�R�6y��z~������������~�~�~�~~�~~~~��~�������bd��_t]}��}������~~��~~��}|��v [...]
+��n�?�y�{�{䂒���၀��ۤ���u�{�E�-�~�n�!�fk&rdC1qoF
`}~n� �o�+�?��}���~����֓�ԫ��ဉ�~�:�$�z�v�swmH'~xty"Xnz��l�-�w�x�a�|����и��ߊ���]�B�}�q�'�g~$wbY+nh8Cfq%ec}%�c�0�i�K�r�s�}���ꄁ����|�U�v�&��|�qr|o@ &*x}zwm�k�*�g�H�s�l�~��ڇ��χ������{�`�q�@�j�"�t�{_Isp.Cxxv{|m��$�s�M�y�|��ㄌ}}����}����������~�}���}�������������~�~��A�n~�
�n~��{�~�������~���������uz����vx��~��}��|�&�o}�{}~{��^w(u��{~��~~�~�~����~�}��������������� [...]
|ps>l[��GKesIl
se��z@�l�_�r�v�~�zw���������}�}�l�C�g{��7�`}F`lk��ASetGp
+~k�D�b�\�m��rtč퐛|x��ܼ�����{p��^�l�R��l�>vW��V at jnPaYa��}7�g�U�j�i�g�}x�Ï����������o�w�V�g�I�`�C�\hErbQTciLkh`��~@�+�s��uo�}����閑yy�������u�px��U�#�j�>�_kGs^TRggGiatE�V��}D�5�t�l�y���������wt��}�p�kr��F�d�L�
ylfDg]��CX_u��{~��������������~�����~~�~���~�}}|}~���~~����M^��~|��|^�Z��qk�y�n����}w���~~����}z����������~~~�~����~~�����~w���~x����~���������~~���������~������������\��rx����x���~�~��~~����������wyCb|�~�������xu�t��x~������x���|v��~�~~~~��~��}~���~���}�~��~�~����������Z��z~���{x����st����o}�y�vv��}����?~p���}~����������}����������~~�~��~x���{x���~��������~���~�~ [...]
gl)?bomm�y��z�x�T�z����눌��⾗����t�w�H�u�u��e� ] xyzp-3aq_]{�e�
�
�p�U�z��鉡�ܓ������y�V�l�2�a��ctQ vv~x. Qv�r|�h��+�}�x�z�ᑝ�ȕ������|�}�C�q�� |s�{b @pi(7swr|w^�
�h�%�8���z�������~��������~�|�z�9�l�� ~r���|�~��~}~�~��~���~�}�������~������~�~�d�C�l��x��}��䑟�ğ�vz���~�}���&�`�
�Wm
vWD+g_'K_nn �g��{�#�s�h�y��接zy�М�����|�a�ly��"�_�
�Wj
tV?-hb%G Ne|�Y�)�]��vI�b���{{�ꓛ���Ζ�눫xs��H��f����\l> km��I\s~U�"�Y�:�g�`�s��慨������߲��r|�t�E�j��� �^��Wf
4 hm��H `c��Y�0�`��tS�p���yz�䗗wr��}�{�}~���~�~���}��~�|��~����� [...]
�c��od�{�����������ns�����lq��N�Z�R� |I��d.kTMRU[AjLo;�H�D�O�X�a�p�n���wo�ȟ����������s�`�Sn��4�K�Kn bR��UG1[t<s8��=��i�a�pĄj�ŵ���uu�������v�t�V�`�L�>{��)tLlP&^eBYOh=�GF�B��qP�i�y�~��r��~�����~~�����}�~~���~�~~�~�����}~�~�~��O�E��pU�i��t}��~��~����~~~����~��}{��saQ>��|~����{��xP�^��v|����~���|�Ȫ��x}��~��������������������~����~~~�������\�{�~�݈���ɺ�}��ڂx��x�Rz���~{���Np>��}y����~����~~����~�����~��~x��~�hy~��~��~}~ [...]
�v�}{G�b�}��܍�y{��뿒�ی�z{��W�,�n����zj\-nd>Gil(ed��} �i�6�8�w�l�|��ԉ��ƕ�y�ܝ���t�vy�����~���~����������~��~�~~~���������~[Ccd��/`b�}(�p�}Eɀ�z�{�}~��x|��ɘzy��~�~������~�������}��{w�hт����~�~�~����~�������~~���������tՁ���ʁ|~���~����~��~~����~���~-�h��}z����z��Ѓ~}�~��~��}u��k�Xu��~���}��~�~���~�����~�����~~~}��~~��;Mhw%l~�|��{l�F�s�dځ�~~�~���}�����x~z��9�4�d�,av(s`S3mm/ [...]
+AW��2r9yS� �F��qZ�_���zĝ��ű������x�|�l�*�Fv��3�AoEr5g[ UYGl)f��C� �H��v]�N�u�����ui���������u�w�l�#�X�C�?|H|'��dJ
AW��Fr �8��tG�L�l�R�w�����tj�������r�t�j�$�By��H� |FgJP4��O] NHuD�*��yP�#�d�y�rǑ���wm���̵��|Ƅ�a�e�O�L�K�+��j6g<c`N`Es't��H� �L��mi�pń�����������Ԛf~�l�h�f��>|��3�6oY5 JJ��Lb ]4��|K��O��si�r���uo���������}ͅ�s�9�_�I�E�F�7oMb,��XT 18l�}6�=�R�A�f�Y�{�~���tp����vu��}�}�~~~~~}~�~���~�~~}~}�}~��~|~~z|��~�~���E}7yT�-��zy�rw�rȏ���~u���~u��x�ii��R�M�L�1^k l [...]
+?<��Wo,ii� �-��wd�R�y�nҋ��ҙ��ġ�ȫ��؆�w�P�Hy��Y�3|[y'vk=J at an'U��a} �+��|j�8�a�}���|r���¡�ǫ��؇�x�L�S�^�:�_�{�xeB
?8��er ]
�|[�5�o�7�c�t���ї��Ý�ǫ��ن�z�N�H{��h� �.y^b"��nh26]^y~��d��EɅvy�vՂ����ŗ�зzy���y�l�j�P�d�({�}Vt+ul7
ED`n$\��c�.��~o�E�l�u���͖�����̠�~��o�S�p��2�_z w�vg)=K]r0cb����h�#�OӁt|��ׇ�������ɨ��ԉ�p�c�f�E�e�#}�wXk0gfL=as0Xh} ~"��i�&�U�|�tӈ��ڂ}���}�}}�}�~}��}~������~��~�������~�������DkHjM�5��v��vf�e����ˀq���~�r}��̀i��a�c�Q�J�\� �MnL_5��W[?<q��J� [...]
gfD�S��vN�h�q�q��������������s|�}�Y�u�U�Yw��B�o[��UHlcDc[eNy
+�Y��|R�:�p��s|Ń�����������𢚅�r�z�[�k�M�_�D�YmFxX]S]Y��EdlfE�R��vQ�i�t�s�����������pyȎ���n�v�b�0�a��7�[tOOqlQYVe��8y[J�Y�[�8�q��vx��xv��ݿ��t~���e�u�[�]v��=�^rG{X]Pm[J`]fNtxf�H�Q��wX�W�y�����������������p~�p�K�fy��J�}[��c<n^]]<_r��{}�~��~�������~������~�}�}��~~~}��~���������GSjp?q_y��z��}J�l�k�p�~~��|x��{~����t~Ȃ���g�u�Q�'�l�;�^��c:?(ip��7euj��z;�h�Y�N�y�ā�~t���ϗ�x~Α���t�Z�p{��>�h�=�`k at y_TLpcDabp<}`|C��m��x^�u�����������������u��_�t�O��n�9�\��[:qiNX
Jg}�2�d� [...]
kL��g](:Ik��U{�K��{`�?�o�|�~�q���خ�rz�����z�]�m�b�Mw��N�E}�{~~~�}~�~~��~~������~~���}}�~~�~��������w|�}x��[�d�V�Mz�z��|}��U^Nd��Nw�S��vS�`�q�j������������������y�~�f�k�V�]�O�RwP}LdViTUdZ_NwRsP�O�X�S�i�H�s����}p���՜�����n�s�r�i�0�V|��C�TkRmQhe'0`lOsBv�~E�R�f�4�l�x�t�����zq��ھ��u}���k�gx��]� �P��yDzSaXfWUfXbQ{PrP�O�Z�V�i�f�|�v�����������������l�n�i�%�a�P�B��kFpV[aY\_r e\~O�K�\�X�n�k��q�������ੜr������m�~�g�g�V�W�O�NpQuN`\cXSlUdNMwS�Q�_�Y�o�i���|��������� [...]
+�]�Y�S��||��~���~����~�~������}~~�~�~~~~���������~����}�w}���}w~z���|y�{�|}���{~���~��~�d�|��||�����y}����|~�~�����|��M�Q~�{~~���~��~�}�~���}��~�~~��~~����~��}��e�|��ˁ��������Ɉ�}��~|��t�2}v�yd%R2{}zy,R'k}�r�7�o�M�q�d�{��dž���������Ɨ���x�y�]�o�H�j�:�fy9xhZ>ur9G)Wz�o|3�l�C�o�W�w�q���ƅ������~��͊�}�h�S�~�v�9�n�'u"�}ztPEmt>ahx8}j�<�k�K�q�c�z��ɂ��ѷ˄���נۈ�}��V�H�p�?�j�3�xyuZ<ppCVjv9qg~7�n�6�=��|�pڈ܀�������ϩښ�~���c�Q�q�D�i�:�iq3~w [...]
\ No newline at end of file
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-hamradio/dump1090.git
More information about the pkg-hamradio-commits
mailing list