diff --git a/.gitignore b/.gitignore index 34aef8c..bee4738 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ *.iq8s *.pyc -*.log +*.log* diff --git a/ADSB_Encoder.py b/ADSB_Encoder.py index e2a6cce..f62df2f 100755 --- a/ADSB_Encoder.py +++ b/ADSB_Encoder.py @@ -10,6 +10,7 @@ import configparser import logging import logging.config import os +import csv ############################################################### @@ -35,9 +36,16 @@ import os def auto_int(x): """Parses HEX into for argParser""" return int(x, 0) + +def auto_bool(x): + if x.lower() in ('yes', 'true', 't', 'y', '1'): + return True + elif x.lower() in ('no', 'false', 'f', 'n', '0'): + return False + else: + raise argparse.ArgumentTypeError('Boolean value expected.') def argParser(): - #TODO add some contraint checking description = 'This tool will generate ADS-B data in a form that a hackRF can broadcast. In addition to providing the information at the command the defaults can be changed in the config.cfg file and the the loggin config changed in logging.cfg.' parser = argparse.ArgumentParser(description=description) parser.add_argument('-i', '--icao', action='store', type=auto_int, dest='icao', default=cfg.get('plane', 'icao'), help='The ICAO number for the plane in hex. Ensure the ICAO is prefixed with \'0x\' to ensure this is parsed as a hex number. Default: %(default)s') @@ -49,11 +57,84 @@ def argParser(): parser.add_argument('--ss', '--surveillancestatus', action='store', type=int, dest='surveillancestatus', default=cfg.getint('plane', 'surveillancestatus'), help='The surveillance status. (Think this is always 0 from ADSB messages. More info would be appreciate). Default: %(default)s') parser.add_argument('--nicsb', '--nicsupplementb', action='store', type=int, dest='nicsupplementb', default=cfg.getint('plane', 'nicsupplementb'), help='The NIC supplement-B.(Think this is always 0 from ADSB messages. More info would be appreciate). Default: %(default)s') parser.add_argument('--time', action='store', type=int, dest='time', default=cfg.getint('plane', 'time'), help='The time. (Think this is always 0 from ADSB messages. More info would be appreciate). Default: %(default)s') - parser.add_argument('-s', '--surface', action='store', default=cfg.getboolean('plane', 'surface'), type=bool, dest='surface', help='If the plane is on the ground or not. Default: %(default)s') + parser.add_argument('-s', '--surface', action='store', default=cfg.getboolean('plane', 'surface'), type=auto_bool, dest='surface', help='If the plane is on the ground or not. Default: %(default)s') parser.add_argument('-o', '--out', '--output', action='store', type=str, default=cfg.get('general', 'outputfilename'), dest='outputfilename', help='The iq8s output filename. This is the file which you will feed into the hackRF. Default: %(default)s') parser.add_argument('-r', '--repeats', action='store', dest='repeats', type=int, default=cfg.getint('general', 'repeats'), help='How many repeats of the data to perform. Default: %(default)s') - + parser.add_argument('--csv', '--csvfile', '--in', '--input', action='store', type=str, default=cfg.get('general', 'csvfile'), dest='csvfile', help='Import a CSV file with the plane data in it. Default: %(default)s') + # TODO Make it so it can do a static checksum + # TODO Get pause between messages + # TODO Get pause between repeats + # TODO Do a pause function return parser.parse_args() + +def singlePlane(arguments): + logger.info('Processing default and command line options for a single plane') + logger.info('Repeating the message %s times' % (arguments.repeats)) + samples = bytearray() + for i in range(0, arguments.repeats): + modes = ModeS() + (df17_even, df17_odd) = modes.df17_pos_rep_encode(arguments.capability, arguments.icao, arguments.typecode, arguments.surveillancestatus, arguments.nicsupplementb, arguments.altitude, arguments.time, arguments.latitude, arguments.longitude, arguments.surface) + + ppm = PPM() + df17_array = ppm.frame_1090es_ppm_modulate(df17_even, df17_odd) + + hackrf = HackRF() + samples_array = hackrf.hackrf_raw_IQ_format(df17_array) + samples = samples+samples_array + return samples + +def manyPlanes(arguments): + logger.info('Processing CSV file') + logger.info('Repeating the message %s times' % (arguments.repeats)) + samples = bytearray() + print(arguments.repeats) + for i in range(0, arguments.repeats): + with open(arguments.csvfile, newline='') as csvfile: + reader = csv.DictReader(csvfile, delimiter=',') + for row in reader: + if not 'icao' in row.keys(): + row['icao'] = arguments.icao + row['icao'] = int(row['icao'], 0) + if not 'latitude' in row.keys(): + row['latitude'] = arguments.latitude + if not 'longitude' in row.keys(): + row['longitude'] = arguments.longitude + if not 'altitude' in row.keys(): + row['altitude'] = arguments.altitude + if not 'capability' in row.keys(): + row['capability'] = arguments.capability + if not 'typecode' in row.keys(): + row['typecode'] = arguments.typecode + if not 'surveillancestatus' in row.keys(): + row['surveillancestatus'] = arguments.surveillancestatus + if not 'nicsupplementb' in row.keys(): + row['nicsupplementb'] = arguments.nicsupplementb + if not 'time' in row.keys(): + row['time'] = arguments.time + if not 'surface' in row.keys(): + row['surface'] = arguments.surface + logger.debug('Row from CSV: %s' % (row)) + print(row) + modes = ModeS() + (df17_even, df17_odd) = modes.df17_pos_rep_encode(row['capability'], row['icao'], row['typecode'], row['surveillancestatus'], row['nicsupplementb'], row['altitude'], row['time'], row['latitude'], row['longitude'], row['surface']) + + ppm = PPM() + df17_array = ppm.frame_1090es_ppm_modulate(df17_even, df17_odd) + + hackrf = HackRF() + samples_array = hackrf.hackrf_raw_IQ_format(df17_array) + samples = samples+samples_array + return samples + +def writeOutputFile(filename, data): + SamplesFile = open('tmp.iq8s', 'wb') + SamplesFile.write(data) + SamplesFile.close() + os.system('sync') + os.system('rm %s' % (filename)) + os.system("dd if=tmp.iq8s of=%s bs=4k seek=63" % (filename)) # TODO redirect output to /dev/null + os.system('sync') + os.system('rm tmp.iq8s') if __name__ == "__main__": global cfg @@ -67,24 +148,12 @@ if __name__ == "__main__": logger = logging.getLogger(__name__) logger.info('Starting ADSB Encoder') logger.debug('The arguments: %s' % (arguments)) + data = None + if arguments.csvfile == '': + data = singlePlane(arguments) + else: + data = manyPlanes(arguments) + writeOutputFile(arguments.outputfilename, data) - logger.info('Repeating the message %s times' % (arguments.repeats)) - SamplesFile = open('tmp.iq8s', 'wb') - for i in range(0, arguments.repeats): - modes = ModeS() - (df17_even, df17_odd) = modes.df17_pos_rep_encode(arguments.capability, arguments.icao, arguments.typecode, arguments.surveillancestatus, arguments.nicsupplementb, arguments.altitude, arguments.time, arguments.latitude, arguments.longitude, arguments.surface) - ppm = PPM() - df17_array = ppm.frame_1090es_ppm_modulate(df17_even, df17_odd) - - hackrf = HackRF() - samples_array = hackrf.hackrf_raw_IQ_format(df17_array) - - - SamplesFile.write(samples_array) - SamplesFile.close() - os.system('sync') - os.system("dd if=tmp.iq8s of=%s bs=4k seek=63" % (arguments.outputfilename)) # TODO redirect output to /dev/null - os.system('sync') - os.system('rm tmp.iq8s') diff --git a/README.md b/README.md index 48e4fd9..392f0ab 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +Firstly if you use this code or are doing anything with ADS-B broadcast, I would be interested in hearing what you are up to. Get in touch on @nzkarit on twitter or adsb (AT) karit [dot] nz + # "ADS-B Out" add-on for SoftRF-Emu, Stratux, etc... This repository contains "ADS-B Out" encoder for Tx-capable SDR hardware. @@ -11,59 +13,73 @@ The source code is published for academic purpose only. ## Instructions 1. Execute *ADSB_Encoder.py* all the options have defaults so none are needed to generate with defaults. Running help will show you the optiosn you can change: ``` -$ ADSB_Encoder.py +$ ./ADSB_Encoder.py -$ ADSB_Encoder.py -h -Usage: ADSB_Encoder.py [options] +$ ./ADSB_Encoder.py -h +usage: ADSB_Encoder.py [-h] [-i ICAO] [--lat LATITUDE] [--lon LONGITUDE] + [-a ALTITUDE] [--ca CAPABILITY] [--tc TYPECODE] + [--ss SURVEILLANCESTATUS] [--nicsb NICSUPPLEMENTB] + [--time TIME] [-s SURFACE] [-o OUTPUTFILENAME] + [-r REPEATS] [--csv CSVFILE] -Options: +This tool will generate ADS-B data in a form that a hackRF can broadcast. In +addition to providing the information at the command the defaults can be +changed in the config.cfg file and the the loggin config changed in +logging.cfg. + +optional arguments: -h, --help show this help message and exit - -i ICAO, --icao=ICAO The ICAO number for the plane in hex. Ensure the ICAO + -i ICAO, --icao ICAO The ICAO number for the plane in hex. Ensure the ICAO is prefixed with '0x' to ensure this is parsed as a hex number. Default: 0xABCDEF - --lat=LATITUDE, --latitude=LATITUDE + --lat LATITUDE, --latitude LATITUDE Latitude for the plane in decminal degrees. Default: 12.34 - --lon=LONGITUDE, --long=LONGITUDE, --longitude=LONGITUDE + --lon LONGITUDE, --long LONGITUDE, --longitude LONGITUDE Longitude for the place in decminal degrees. Default: 56.78 - -a ALTITUDE, --alt=ALTITUDE, --altitude=ALTITUDE + -a ALTITUDE, --alt ALTITUDE, --altitude ALTITUDE Altitude in decminal feet. Default: 9876.5 - --ca=CAPABILITY, --capability=CAPABILITY + --ca CAPABILITY, --capability CAPABILITY The capability. (Think this is always 5 from ADSB - messages. More info would be appreciate). Default: 5 - --tc=TYPECODE, --typecode=TYPECODE + messages. More info would be appreciate). Default: 5 + --tc TYPECODE, --typecode TYPECODE The type for the ADSB messsage. See https://adsb- decode-guide.readthedocs.io/en/latest/content/introduc tion.html#ads-b-message-types for more information. Default: 11 - --ss=SURVEILLANCESTATUS, --surveillancestatus=SURVEILLANCESTATUS + --ss SURVEILLANCESTATUS, --surveillancestatus SURVEILLANCESTATUS The surveillance status. (Think this is always 0 from ADSB messages. More info would be appreciate). Default: 0 - --nicsb=NICSUPPLEMENTB, --nicsupplementb=NICSUPPLEMENTB - The NIC supplement-B.(Think this is always 0 from - ADSB messages. More info would be appreciate). - Default: 0 - --time=TIME The time. (Think this is always 0 from ADSB messages. - More info would be appreciate). Default: 0 - -s SURFACE, --surface=SURFACE + --nicsb NICSUPPLEMENTB, --nicsupplementb NICSUPPLEMENTB + The NIC supplement-B.(Think this is always 0 from ADSB + messages. More info would be appreciate). Default: 0 + --time TIME The time. (Think this is always 0 from ADSB messages. + More info would be appreciate). Default: 0 + -s SURFACE, --surface SURFACE If the plane is on the ground or not. Default: False - -o OUTPUTFILENAME, --out=OUTPUTFILENAME, --output=OUTPUTFILENAME + -o OUTPUTFILENAME, --out OUTPUTFILENAME, --output OUTPUTFILENAME The iq8s output filename. This is the file which you will feed into the hackRF. Default: Samples_256K.iq8s + -r REPEATS, --repeats REPEATS + How many repeats of the data to perform. Default: 1 + --csv CSVFILE, --csvfile CSVFILE, --in CSVFILE, --input CSVFILE + Import a CSV file with the plane data in it. Default: + ``` 2. Transmit the signal into air: ``` -$ hackrf_transfer -t Samples_256K.iq8s -f 868000000 -s 2000000 -x 10 +$ hackrf_transfer -t Samples_256K.iq8s -f 915000000 -s 2000000 -x 10 call hackrf_sample_rate_set(2000000 Hz/2.000 MHz) call hackrf_baseband_filter_bandwidth_set(1750000 Hz/1.750 MHz) -call hackrf_set_freq(868000000 Hz/868.000 MHz) +call hackrf_set_freq(915000000 Hz/915.000 MHz) Stop with Ctrl-C + 3.9 MiB / 1.000 sec = 3.9 MiB/second 0.5 MiB / 1.000 sec = 0.5 MiB/second User cancel, exiting... -Total time: 1.00038 s +Total time: 2.00039 s hackrf_stop_tx() done hackrf_close() done hackrf_exit() done @@ -77,7 +93,7 @@ $ * -x is the gain ## Validation ``` -$ sudo dump1090 --net --freq 868000000 +$ sudo ./dump1090 --net --freq 915000000 ... ``` ![](https://github.com/lyusupov/ADSB-Out/raw/master/documents/images/dump1090.JPG) diff --git a/config.cfg b/config.cfg index 3d0adc0..7882308 100644 --- a/config.cfg +++ b/config.cfg @@ -1,6 +1,7 @@ [general] outputfilename = Samples_256K.iq8s repeats = 1 +csvfile = [plane] icao = 0xABCDEF diff --git a/example.csv b/example.csv new file mode 100644 index 0000000..6b4e5f4 --- /dev/null +++ b/example.csv @@ -0,0 +1,3 @@ +icao,altitude +0x123456,50000 +0xABCDEF,1000