├── auto_rx ├── log │ └── log_files_go_here.txt ├── autorx │ ├── static │ │ ├── img │ │ │ ├── balloon-red.png │ │ │ ├── antenna-green.png │ │ │ ├── balloon-blue.png │ │ │ ├── balloon-green.png │ │ │ ├── balloon-purple.png │ │ │ ├── parachute-blue.png │ │ │ ├── parachute-red.png │ │ │ ├── parachute-green.png │ │ │ └── parachute-purple.png │ │ ├── css │ │ │ ├── images │ │ │ │ ├── layers.png │ │ │ │ ├── layers-2x.png │ │ │ │ ├── marker-icon.png │ │ │ │ ├── marker-icon-2x.png │ │ │ │ └── marker-shadow.png │ │ │ ├── autorx.css │ │ │ └── c3.min.css │ │ └── js │ │ │ ├── scan_chart.js │ │ │ └── utils.js │ ├── __init__.py │ ├── gps.py │ ├── email_notification.py │ ├── ozimux.py │ ├── logger.py │ └── config.py ├── auto_rx.service ├── auto_rx.sh ├── build.sh ├── README.md ├── utils │ ├── plot_rtl_power.py │ ├── snr_test.py │ ├── receiver_stats.py │ └── log_to_kml.py └── station.cfg.example ├── c34 ├── dft_c34 ├── c50.txt └── c34.txt ├── iq ├── dfmIQ.wav ├── iq_demod.pdf └── sdriq.txt ├── m10 ├── M10V05.jpg ├── M10v05-BSL.png ├── M10v05-JTAG.png ├── m10_20150206.jpg ├── M10v05-JTAG_BSL.jpg ├── README.md ├── m10c_part.c └── m10_msp430.txt ├── meisei ├── mrs_pre2 └── meisei-rs.txt ├── rs92 ├── nga18670.Z ├── rs92-almanac_gps_outage.jpg ├── pos2kml.pl ├── pos2gpx.pl ├── pos2aprs.pl ├── pos2nmea.pl ├── README.md ├── rs92.txt └── almanac.sem.week0843.061440.txt ├── imet ├── imet1ab_multi.jpg ├── imet1rs-binary.pdf ├── wav │ └── 20150505_403MHz.wav └── imet1ab.txt ├── rs41 ├── wav │ ├── 20140717_402MHz.wav │ └── rs41pre_20150802.wav └── README.md ├── rs_module ├── rs_datum.h ├── rs_rs41.h ├── rs_demod.h ├── rs_rs92.h ├── rs_bch_ecc.h ├── README.md ├── rs_datum.c ├── rs_data.h ├── rs_demod.c ├── rs_main41.c └── rs_main92.c ├── .gitignore ├── demod ├── demod.h ├── demod_dft.h └── demod.c ├── lms6 └── lms6.txt ├── mk2a ├── mk2a.txt └── Mk2a_9600.bytes ├── README.md ├── dropsonde └── rd94.txt ├── dfm └── dfm06.txt ├── scan └── rtlsdr_scan.pl └── ecc └── ecc-rs_vaisala.c /auto_rx/log/log_files_go_here.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /c34/dft_c34: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/c34/dft_c34 -------------------------------------------------------------------------------- /iq/dfmIQ.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/iq/dfmIQ.wav -------------------------------------------------------------------------------- /m10/M10V05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/m10/M10V05.jpg -------------------------------------------------------------------------------- /iq/iq_demod.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/iq/iq_demod.pdf -------------------------------------------------------------------------------- /meisei/mrs_pre2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/meisei/mrs_pre2 -------------------------------------------------------------------------------- /rs92/nga18670.Z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/rs92/nga18670.Z -------------------------------------------------------------------------------- /m10/M10v05-BSL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/m10/M10v05-BSL.png -------------------------------------------------------------------------------- /m10/M10v05-JTAG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/m10/M10v05-JTAG.png -------------------------------------------------------------------------------- /m10/m10_20150206.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/m10/m10_20150206.jpg -------------------------------------------------------------------------------- /imet/imet1ab_multi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/imet/imet1ab_multi.jpg -------------------------------------------------------------------------------- /imet/imet1rs-binary.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/imet/imet1rs-binary.pdf -------------------------------------------------------------------------------- /m10/M10v05-JTAG_BSL.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/m10/M10v05-JTAG_BSL.jpg -------------------------------------------------------------------------------- /imet/wav/20150505_403MHz.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/imet/wav/20150505_403MHz.wav -------------------------------------------------------------------------------- /rs41/wav/20140717_402MHz.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/rs41/wav/20140717_402MHz.wav -------------------------------------------------------------------------------- /rs41/wav/rs41pre_20150802.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/rs41/wav/rs41pre_20150802.wav -------------------------------------------------------------------------------- /rs92/rs92-almanac_gps_outage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/rs92/rs92-almanac_gps_outage.jpg -------------------------------------------------------------------------------- /auto_rx/autorx/static/img/balloon-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/auto_rx/autorx/static/img/balloon-red.png -------------------------------------------------------------------------------- /auto_rx/autorx/static/css/images/layers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/auto_rx/autorx/static/css/images/layers.png -------------------------------------------------------------------------------- /auto_rx/autorx/static/img/antenna-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/auto_rx/autorx/static/img/antenna-green.png -------------------------------------------------------------------------------- /auto_rx/autorx/static/img/balloon-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/auto_rx/autorx/static/img/balloon-blue.png -------------------------------------------------------------------------------- /auto_rx/autorx/static/img/balloon-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/auto_rx/autorx/static/img/balloon-green.png -------------------------------------------------------------------------------- /auto_rx/autorx/static/img/balloon-purple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/auto_rx/autorx/static/img/balloon-purple.png -------------------------------------------------------------------------------- /auto_rx/autorx/static/img/parachute-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/auto_rx/autorx/static/img/parachute-blue.png -------------------------------------------------------------------------------- /auto_rx/autorx/static/img/parachute-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/auto_rx/autorx/static/img/parachute-red.png -------------------------------------------------------------------------------- /auto_rx/autorx/static/css/images/layers-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/auto_rx/autorx/static/css/images/layers-2x.png -------------------------------------------------------------------------------- /auto_rx/autorx/static/img/parachute-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/auto_rx/autorx/static/img/parachute-green.png -------------------------------------------------------------------------------- /auto_rx/autorx/static/img/parachute-purple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/auto_rx/autorx/static/img/parachute-purple.png -------------------------------------------------------------------------------- /auto_rx/autorx/static/css/images/marker-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/auto_rx/autorx/static/css/images/marker-icon.png -------------------------------------------------------------------------------- /auto_rx/autorx/static/css/images/marker-icon-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/auto_rx/autorx/static/css/images/marker-icon-2x.png -------------------------------------------------------------------------------- /auto_rx/autorx/static/css/images/marker-shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixpunk/radiosonde_auto_rx/master/auto_rx/autorx/static/css/images/marker-shadow.png -------------------------------------------------------------------------------- /rs_module/rs_datum.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef RS_DATUM_H 3 | #define RS_DATUM_H 4 | 5 | 6 | char weekday[7][4]; 7 | 8 | void Gps2Date(rs_data_t *); 9 | 10 | 11 | #endif /* RS_DATUM_H */ 12 | 13 | -------------------------------------------------------------------------------- /rs_module/rs_rs41.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef RS_RS41_H 3 | #define RS_RS41_H 4 | 5 | 6 | int rs41_process(void *, int, int); 7 | //int rs41_xbits2byte(void *, char *); 8 | 9 | int init_rs41data(rs_data_t *); 10 | int free_rs41data(rs_data_t *); 11 | 12 | 13 | #endif /* RS_RS41_H */ 14 | 15 | -------------------------------------------------------------------------------- /rs_module/rs_demod.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef RS_DEMOD_H 3 | #define RS_DEMOD_H 4 | 5 | 6 | int read_wav_header(FILE *, rs_data_t *); 7 | int read_bits_fsk(FILE *, int *, int *, int); 8 | 9 | void inc_bufpos(rs_data_t *); 10 | int compare(rs_data_t *); 11 | 12 | 13 | #endif /* RS_DEMOD_H */ 14 | 15 | -------------------------------------------------------------------------------- /rs_module/rs_rs92.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef RS_RS92_H 3 | #define RS_RS92_H 4 | 5 | 6 | int rs92_process(void *, int, int); 7 | //int rs92_bits2byte(void *, char *); 8 | 9 | int init_rs92data(rs_data_t *, int, char *); 10 | int free_rs92data(rs_data_t *); 11 | 12 | 13 | #endif /* RS_RS92_H */ 14 | 15 | -------------------------------------------------------------------------------- /rs_module/rs_bch_ecc.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef RS_BCH_ECC_H 3 | #define RS_BCH_ECC_H 4 | 5 | 6 | int rs_init_RS255(void); 7 | int rs_init_BCH64(void); 8 | int rs_encode(ui8_t *cw); 9 | int rs_decode(ui8_t *cw, ui8_t *, ui8_t *); 10 | int rs_decode_bch_gf2t2(ui8_t *cw, ui8_t *, ui8_t *); 11 | 12 | 13 | #endif /* RS_BCH_ECC_H */ 14 | 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | auto_rx/log/* 3 | auto_rx/log_power.csv 4 | auto_rx/reset_usb 5 | auto_rx/rs41ecc 6 | auto_rx/rs41mod 7 | auto_rx/rs92ecc 8 | auto_rx/rs92mod 9 | auto_rx/rs_detect 10 | auto_rx/station.cfg 11 | rs41/rs41ecc 12 | rs92/rs92ecc 13 | rs_module/rs41mod 14 | rs_module/rs92mod 15 | scan/reset_usb 16 | scan/rs_detect 17 | *.pyc 18 | *.o 19 | -------------------------------------------------------------------------------- /auto_rx/auto_rx.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=auto_rx 3 | After=syslog.target 4 | 5 | [Service] 6 | ExecStart=/usr/bin/python /home/pi/radiosonde_auto_rx/auto_rx/auto_rx.py -t 0 7 | Restart=always 8 | RestartSec=3 9 | WorkingDirectory=/home/pi/radiosonde_auto_rx/auto_rx/ 10 | User=pi 11 | SyslogIdentifier=auto_rx 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | 16 | 17 | -------------------------------------------------------------------------------- /demod/demod.h: -------------------------------------------------------------------------------- 1 | 2 | float read_wav_header(FILE*, float); 3 | int f32buf_sample(FILE*, int, int); 4 | int read_sbit(FILE*, int, int*, int, int, int, int); 5 | 6 | int getmaxCorr(float*, unsigned int*, int); 7 | int headcmp(int, char*, int, unsigned int); 8 | float get_bufvar(int); 9 | 10 | int init_buffers(char*, int, int); 11 | int free_buffers(void); 12 | 13 | unsigned int get_sample(void); 14 | 15 | -------------------------------------------------------------------------------- /demod/demod_dft.h: -------------------------------------------------------------------------------- 1 | 2 | float read_wav_header(FILE*, float); 3 | int f32buf_sample(FILE*, int, int); 4 | int read_sbit(FILE*, int, int*, int, int, int, int); 5 | 6 | int getCorrDFT(int, int, unsigned int, float *, unsigned int *); 7 | int headcmp(int, char*, int, unsigned int, int, int); 8 | float get_bufvar(int); 9 | float get_bufmu(int); 10 | 11 | int init_buffers(char*, int, int); 12 | int free_buffers(void); 13 | 14 | unsigned int get_sample(void); 15 | 16 | -------------------------------------------------------------------------------- /rs_module/README.md: -------------------------------------------------------------------------------- 1 | 2 | ##### separate modules 3 | 4 | preliminary/test version (rs41, rs92) 5 | 6 | compile: 7 | 8 | ``` 9 | 10 | gcc -c rs_datum.c 11 | gcc -c rs_demod.c 12 | gcc -c rs_bch_ecc.c 13 | 14 | gcc -c rs_rs41.c 15 | gcc -c rs_rs92.c 16 | 17 | 18 | gcc -c rs_main41.c 19 | gcc rs_main41.o rs_rs41.o rs_bch_ecc.o rs_demod.o rs_datum.o -lm -o rs41mod 20 | 21 | 22 | gcc -c rs_main92.c 23 | gcc rs_main92.o rs_rs92.o rs_bch_ecc.o rs_demod.o rs_datum.o -lm -o rs92mod 24 | 25 | ``` 26 | 27 | 28 | -------------------------------------------------------------------------------- /auto_rx/auto_rx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Radiosonde Auto-RX Script 3 | # 4 | # 2017-04 Mark Jessop 5 | # 6 | # NOTE: If running this from crontab, make sure to set the appropriate PATH env-vars, 7 | # else utilities like rtl_power and rtl_fm won't be found. 8 | # 9 | # WARNING - THIS IS DEPRECATED - PLEASE USE THE SYSTEMD SERVICE 10 | # 11 | 12 | # change into appropriate directory 13 | cd /home/pi/radiosonde_auto_rx/auto_rx/ 14 | 15 | # Clean up old files 16 | rm log_power*.csv 17 | 18 | # Start auto_rx process with a 3 hour timeout. 19 | python auto_rx.py -t 180 2>error.log 20 | -------------------------------------------------------------------------------- /auto_rx/autorx/static/css/autorx.css: -------------------------------------------------------------------------------- 1 | #scan_chart .c3-circles-Spectra { 2 | display: none; 3 | } 4 | #scan_chart .c3-circles-Threshold{ 5 | display: none; 6 | } 7 | #scan_chart .c3-line-Threshold { 8 | stroke-dasharray: 5,5; 9 | } 10 | .table-fixed thead { 11 | width: 97%; 12 | } 13 | .table-fixed tbody { 14 | height: 400px; 15 | overflow-y: auto; 16 | width: 100%; 17 | } 18 | .table-fixed thead, .table-fixed tbody, .table-fixed tr, .table-fixed td, .table-fixed th { 19 | display: block; 20 | } 21 | .table-fixed tbody td, .table-fixed thead > tr> th { 22 | float: left; 23 | border-bottom-width: 0; 24 | } -------------------------------------------------------------------------------- /auto_rx/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Auto Sonde Decoder build script. 4 | # 5 | 6 | # Build rs_detect. 7 | echo "Building rs_detect" 8 | cd ../scan/ 9 | gcc rs_detect.c -lm -o rs_detect 10 | 11 | echo "Building RS92/RS41/DFM Demodulators" 12 | cd ../demod/ 13 | gcc -c demod.c 14 | gcc -c demod_dft.c 15 | gcc rs92dm_dft.c demod_dft.o -lm -o rs92ecc -I../ecc/ -I../rs92 16 | gcc rs41dm_dft.c demod_dft.o -lm -o rs41ecc -I../ecc/ -I../rs41 17 | gcc dfm09dm_dft.c demod_dft.o -lm -o dfm09ecc -I../ecc/ -I../dfm 18 | 19 | # Copy all necessary files into this directory. 20 | echo "Copying files into auto_rx directory." 21 | cd ../auto_rx/ 22 | cp ../scan/rs_detect . 23 | cp ../demod/rs92ecc . 24 | cp ../demod/rs41ecc . 25 | cp ../demod/dfm09ecc . 26 | 27 | echo "Done!" 28 | -------------------------------------------------------------------------------- /c34/c50.txt: -------------------------------------------------------------------------------- 1 | 2 | Modulation AFSK 2400 baud 3 | 3000 Hz : bit 1 4 | 4800 Hz : bit 0 5 | 6 | 7 | bits : little endian -> byte 8 | bytes: big endian -> int-value 9 | 10 | 8N1: 0 bbbbbbbb 1 11 | 12 | Datenpaket besteht aus 8 byte: 13 | header 2 byte 00FF 14 | pck_id 1 byte N 15 | data 4 byte val (big endian) 16 | chksum 2 byte Fletcher16 (hier: byte2 1's-complement am Ende) 17 | 18 | 19 | Telemetrie pck_id N: 20 | 0x14: date: 0x0003F815 = 260117 -> 2017-01-26 21 | 0x15: time: 0x0001C41D = 124735 -> 11:57:41 22 | 0x16: lat : 0x1BC1678B = 465659787 -> 46+5659787/6000000=46.943298° 23 | 0x17: lon : 0x03D54F9F = 64311199 -> 6+4311199/6000000=6.7185332° 24 | 0x18: alt : 0x0003252E = 206126 -> 206126/10 = 20612.6m 25 | 26 | 27 | GPS-lat/lon wie NMEA mit Faktor 1e5 28 | 29 | 30 | Checksum: Fletcher16 31 | 32 | PTU-Data: float32? 33 | 34 | 35 | -------------------------------------------------------------------------------- /m10/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Radiosonde M10 3 | 4 | Tools for decoding M10 radiosonde signals. 5 | 6 | ### Files 7 | 8 | * `m10x.c` - M10 decoder 9 | 10 | ##### Compile 11 | `gcc m10x.c -lm -o m10x` 12 | 13 | ##### Usage 14 | `./m10x [options] `
15 | * ``: FM-demodulated signal, recorded as wav audio file
16 | * `options`:
17 | `-r`: output raw data
18 | `-v`, `-vv`: additional data/info (velocities, SN, checksum)
19 | `-c`: colored output
20 | 21 | 22 | ##### Examples 23 | * `./m10x -v 20150701_402MHz.wav`
24 | `./m10x -vv -c 20150701_402MHz.wav`
25 | `./m10x -r -c 20150701_402MHz.wav`
26 | `sox 20150701_402MHz.wav -t wav - lowpass 6000 2>/dev/null | ./m10x -vv -c`
27 | 28 | ##### 29 |
30 | 31 | 32 | * `pilotsonde/m12.c` - Pilotsonde 33 | 34 | ##### Compile 35 | `gcc m12.c -lm -o m12` 36 | 37 | -------------------------------------------------------------------------------- /rs_module/rs_datum.c: -------------------------------------------------------------------------------- 1 | 2 | #include "rs_data.h" 3 | #include "rs_datum.h" 4 | 5 | char weekday[7][4] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; 6 | //char weekday[7][3] = { "So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"}; 7 | 8 | /* 9 | * Convert GPS Week and Seconds to Modified Julian Day. 10 | * - Adapted from sci.astro FAQ. 11 | * - Ignores UTC leap seconds. 12 | */ 13 | //void Gps2Date(long GpsWeek, long GpsSeconds, int *Year, int *Month, int *Day) { 14 | void Gps2Date(rs_data_t *rs_data) { 15 | 16 | long GpsSeconds, GpsDays, Mjd; 17 | long J, C, Y, M; 18 | 19 | GpsSeconds = (rs_data->GPS).msec / 1000; 20 | GpsDays = (rs_data->GPS).week * 7 + (GpsSeconds / 86400); 21 | Mjd = 44244 + GpsDays; 22 | 23 | J = Mjd + 2468570; 24 | C = 4 * J / 146097; 25 | J = J - (146097 * C + 3) / 4; 26 | Y = 4000 * (J + 1) / 1461001; 27 | J = J - 1461 * Y / 4 + 31; 28 | M = 80 * J / 2447; 29 | rs_data->day = J - 2447 * M / 80; 30 | J = M / 11; 31 | rs_data->month = M + 2 - (12 * J); 32 | rs_data->year = 100 * (C - 49) + Y + J; 33 | } 34 | 35 | 36 | -------------------------------------------------------------------------------- /imet/imet1ab.txt: -------------------------------------------------------------------------------- 1 | 2 | iMet-1-AB 3 | 4 | GPS Chip: 5 | Trimble Lassen iQ 6 | ublox AMY-5M 7 | 8 | AFSK 2400 baud: 9 | 10 | 1200Hz-Schwingung: Pause bzw. In/Out 11 | 1x 2400Hz-Schwingung: bit 0 12 | 2x 4800Hz-Schwingung: bit 1 13 | 14 | 8 bit begrenzt durch In/Out 15 | 16 | 204 byte pro Frame 17 | Beginn: 18 | 69 69 69 69 69 19 | Ende: 20 | 96 96 96 96 96 96 96 96 96 21 | 22 | double B60B60 = 0xB60B60; // 2^32/360 = 0xB60B60.xxx 23 | pos 0x8A 4byte: GPSTOW: sek (Trimble Lassen iQ), ms (ublox AMY-5M) 24 | pos 0x8E 4byte: GPSlat: /B60B60 25 | pos 0x92 4byte: GPSlon: /B60B60 26 | pos 0x96 4byte: GPSalt: Hoehe in mm 27 | 28 | //Velocity East-North-Up (ENU) 29 | pos 0x84 2byte: GPSvO /2e2 // vielleicht 1.94e2 (m/s -> knots)? 30 | pos 0x86 2byte: GPSvN /2e2 31 | pos 0x88 2byte: GPSvV /2e2 32 | 33 | 34 | Frame: , 35 | =0x10, =0x03; =0xb9 36 | (.. 69) 10 b9 01 .. .. 10 03 cs (96 ..) 37 | 8bit-xor-checksum: 38 | xorsum(), 39 | wobei 0x10 innerhalb doppelt gesendet werden, 40 | zweimal zaehlen, wobei 0x10^0x10=0x00 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /rs92/pos2kml.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use strict; 3 | use warnings; 4 | 5 | my $filename = $ARGV[0]; 6 | my $fh; 7 | if (defined $filename) { 8 | open($fh, "<", $filename) or die "Could not open $filename: $!"; 9 | } 10 | else { 11 | $fh = *STDIN; 12 | } 13 | 14 | print "\n"; 15 | print "\n"; 16 | 17 | my $line; 18 | my $hms; 19 | my $lat; my $lon; my $alt; 20 | 21 | print " \n"; 22 | print " \n"; 23 | print " \n"; 24 | print " absolute\n"; 25 | print " \n"; 26 | print " "; 27 | while ($line = <$fh>) { 28 | if ($line =~ /(\d\d:\d\d:\d\d).*\ +lat:\ *(-?\d*\.\d*)\ +lon:\ *(-?\d*\.\d*)\ +alt:\ *(-?\d*\.\d*).*/) { 29 | $hms = $1; 30 | $lat = $2; 31 | $lon = $3; 32 | $alt = $4; 33 | print " $lon,$lat,$alt"; 34 | } 35 | } 36 | print "\n"; 37 | print " \n"; 38 | print " \n"; 39 | print " \n"; 40 | print " \n"; 41 | 42 | print "\n"; 43 | 44 | -------------------------------------------------------------------------------- /auto_rx/autorx/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # radiosonde_auto_rx 4 | # 5 | # Copyright (C) 2018 Mark Jessop 6 | # Released under GNU GPL v3 or later 7 | # 8 | __version__ = "20180808" 9 | 10 | # Global Variables 11 | 12 | # RTLSDR Usage Register - This dictionary holds information about each SDR and its currently running Decoder / Scanner 13 | # Key = SDR device index / ID 14 | # 'device_idx': { 15 | # 'in_use' (bool) : True if the SDR is currently in-use by a decoder or scanner. 16 | # 'task' (class) : If this SDR is in use, a reference to the task. 17 | # 'bias' (bool) : True if the bias-tee should be enabled on this SDR, False otherwise. 18 | # 'ppm' (int) : The PPM offset for this SDR. 19 | # 'gain' (float) : The gain setting to use with this SDR. A setting of -1 turns on hardware AGC. 20 | # } 21 | # 22 | # 23 | sdr_list = {} 24 | 25 | # Currently running task register. 26 | # Keys will either be 'SCAN' (only one scanner shall be running at a time), or a sonde frequency in MHz. 27 | # Each element contains: 28 | # 'task' : (class) Reference to the currently running task. 29 | # 'device_idx' (str): The allocated SDR. 30 | # 31 | task_list = {} 32 | -------------------------------------------------------------------------------- /rs41/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Radiosonde RS41 3 | 4 | #### Files 5 | 6 | * `rs41ecc.c`, `RS/ecc/bch_ecc.c` 7 | 8 | #### Compile 9 | (copy `bch_ecc.c`)
10 | `gcc rs41ecc.c -lm -o rs41ecc` 11 | 12 | #### Usage 13 | `./rs41ecc [options] `
14 | * ``: FM-demodulated signal, recorded as wav audio file 15 | * `options`:
16 | `-i`: invert signal/polarity
17 | `-b`: alternative demod
18 | `-r`: output raw data
19 | `-v, -vx, -vv`: additional data/aux/info
20 | `--ecc`: Reed-Solomon error correction
21 | `--crc`: CRC blocks: 0-OK, 1-NO
22 | `--sat`: additional Sat data
23 | 24 | `./rs41gps -h`: list more options 25 | 26 | #### Examples 27 | FSK-demodulation is kept very simple. If the signal quality is low and (default) zero-crossing-demod is used, 28 | a lowpass filter is recommended: 29 | * `sox 20170116_12Z.wav -t wav - lowpass 2800 2>/dev/null | ./rs41ecc --ecc --crc -vx` 30 | 31 | If timing/sync is not an issue, integrating the bit-samples (option `-b`) is better for error correction: 32 | * `./rs41ecc -b --ecc --crc -vx 20170116_12Z.wav` 33 | 34 | If the signal is inverted 35 | (depends on sdr-software and/or audio-card/settings), try option `-i`. 36 | 37 | (cf. /RS/rs92) 38 | 39 | -------------------------------------------------------------------------------- /auto_rx/autorx/static/js/scan_chart.js: -------------------------------------------------------------------------------- 1 | // Scan Result Chart Setup 2 | 3 | var scan_chart_spectra; 4 | var scan_chart_peaks; 5 | var scan_chart_threshold; 6 | var scan_chart_obj; 7 | 8 | function setup_scan_chart(){ 9 | scan_chart_spectra = { 10 | xs: { 11 | 'Spectra': 'x_spectra' 12 | }, 13 | columns: [ 14 | ['x_spectra',autorx_config.min_freq, autorx_config.max_freq], 15 | ['Spectra',0,0] 16 | ], 17 | type:'line' 18 | }; 19 | 20 | scan_chart_peaks = { 21 | xs: { 22 | 'Peaks': 'x_peaks' 23 | }, 24 | columns: [ 25 | ['x_peaks',0], 26 | ['Peaks',0] 27 | ], 28 | type:'scatter' 29 | }; 30 | 31 | scan_chart_threshold = { 32 | xs:{ 33 | 'Threshold': 'x_thresh' 34 | }, 35 | columns:[ 36 | ['x_thresh',autorx_config.min_freq, autorx_config.max_freq], 37 | ['Threshold',autorx_config.snr_threshold,autorx_config.snr_threshold] 38 | ], 39 | type:'line' 40 | }; 41 | 42 | scan_chart_obj = c3.generate({ 43 | bindto: '#scan_chart', 44 | data: scan_chart_spectra, 45 | axis:{ 46 | x:{ 47 | tick:{ 48 | format: function (x) { return x.toFixed(3); } 49 | }, 50 | label:"Frequency (MHz)" 51 | }, 52 | y:{ 53 | label:"Power (dB - Uncalibrated)" 54 | } 55 | }, 56 | point:{r:10} 57 | }); 58 | } -------------------------------------------------------------------------------- /iq/sdriq.txt: -------------------------------------------------------------------------------- 1 | 2 | IQ-wav 3 | 2 channels: Re, Im 4 | 16-bit-signed oder 8-bit-unsigned (centerpoint 128) 5 | 6 | sdr# : 8bit unsigned, 16bit signed 7 | rtl_sdr: 8bit unsigned (wie aus rtlsdr-stick) 8 | rtl_fm : 16bit signed 9 | 10 | 11 | sdr# < rev1381: 12 | rtlsdrIQ = sdr#-QI: swap IQ 13 | In sdr# swapIQ fuer rtl_sdr-IQ einschalten. 14 | (entsprechend hat auch audio umgekehrte Polaritaet) 15 | 16 | 17 | rtl_sdr: 8 bit 18 | ./rtl_sdr.exe -M -raw -f 403.5M -s 1.4M rtlsdr_1400k.raw 19 | sox -r 1400000 -e unsigned -b 8 -c 2 rtlsdr_1400k.raw rtlsdr_1400k.wav 20 | 21 | rtl_fm: 16 bit 22 | ./rtl_fm.exe -M raw -f 403.5M -s 100000 rtlfm_100k.raw 23 | sox -r 100000 -e signed -b 16 -c 2 rtlfm_100k.raw rtlfm_100k.wav 24 | oder: 25 | ./rtl_fm.exe -M raw -f 403.5M -s 100000 -E wav rtlfm2_100k.wav 26 | 27 | Wird raw-Datei durch sox mit anderer sample_rate in wav umgewandelt, 28 | als die raw-Datei erstellt wurde, wird Signal entsprechend gedehnt oder 29 | gestaucht. 30 | 31 | 32 | Baseband verkleinern -> resample w/ sox: 33 | sox sdrIQ_250k.wav -r 100k sdrIQ_100k.wav 34 | 35 | 36 | sdr# DC-Frequenz im Dateinamen (sonst 0 Hz): 37 | z.B. 38 | SDRSharp_20150303_122423Z_403000kHz_IQ.wav 39 | wird in sdr# um die Frequenz 403000 kHz angezeigt. 40 | 41 | 42 | Translation, d.h. Verschieben um f0: 43 | w(t) = z(t)*exp(t * i*2pi*f0) <-> W(f) = Z(f-f0) 44 | 45 | swap I<->Q, d.h. Spiegelung im freq-Raum: 46 | w = Im(z) + I*Re(z) = exp(i*pi/2) * conj(z) = i*conj(z) 47 | Spiegelung: w = conj(z) 48 | 49 | -------------------------------------------------------------------------------- /rs92/pos2gpx.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use strict; 3 | use warnings; 4 | 5 | my $filename = $ARGV[0]; 6 | my $fh; 7 | if (defined $filename) { 8 | open($fh, "<", $filename) or die "Could not open $filename: $!"; 9 | } 10 | else { 11 | $fh = *STDIN; 12 | } 13 | 14 | print "\n"; 15 | print "\n"; 16 | 17 | my $line; 18 | my $date; 19 | my $hms; 20 | my $lat; my $lon; my $alt; 21 | 22 | print "\n"; 23 | print "\n"; 24 | 25 | while ($line = <$fh>) { 26 | if ($line =~ /(\d\d:\d\d:\d\d\.?\d?\d?\d?).*\ +lat:\ *(-?\d*\.\d*)\ +lon:\ *(-?\d*\.\d*)\ +alt:\ *(-?\d*\.\d*).*/) { 27 | 28 | $hms = $1; 29 | $lat = $2; 30 | $lon = $3; 31 | $alt = $4; 32 | 33 | $date = ""; 34 | if ($line =~ /(\d\d\d\d-\d\d-\d\d).*/) { $date = sprintf ("%sT", $1); } 35 | #if ($line =~ /(\d\d\d\d)-(\d\d)-(\d\d).*/) { $date = sprintf ("%04d-%02d-%02dT", $1, $2, $3); } 36 | 37 | print " \n"; 38 | print " $alt<\/ele>\n"; 39 | printf(" \n", $date, $hms); 40 | print " \n"; 41 | } 42 | } 43 | 44 | print "\n"; 45 | print "\n"; 46 | 47 | print "\n"; 48 | 49 | -------------------------------------------------------------------------------- /auto_rx/README.md: -------------------------------------------------------------------------------- 1 | ### Automatic Radiosonde RX Station Extensions ### 2 | This fork of [rs1279's RS](https://github.com/rs1729/RS) codebase provides a set of utilities ('auto_rx') to allow automatic reception and uploading of [Radiosonde](https://en.wikipedia.org/wiki/Radiosonde) positions to multiple services, including: 3 | * The [Habitat High-Altitude Balloon Tracker](https://tracker.habhub.org) 4 | * APRS-IS (for display on sites such as [aprs.fi](https://aprs.fi) 5 | * [OziPlotter](https://github.com/projecthorus/oziplotter), for mobile radiosonde chasing. 6 | 7 | Currently we support the following radiosonde types: 8 | * Vaisala RS92SGP 9 | * Vaisala RS41SGP 10 | * Graw DFM06 & DFM09 11 | 12 | Support for other radiosondes may be added as required (send us sondes to test with!) 13 | 14 | This software performs the following steps: 15 | 1. Use rtl_power to scan across a user-defined frequency range, and detect peaks in the spectrum. 16 | 2. For each detected peak frequency, run the rs_detect utility, which determines if a radiosonde signal is present, and what type it is. 17 | 3. If a radiosonde signal is found, start demodulating it, and upload data to various internet services. 18 | 4. If no peaks are found, or if no packets are heard from the radiosonde in a given amount of time (2 minutes by default), go back to step 1. 19 | 20 | By running auto_rx continuously, not just at known radiosonde launch times, you may see other radiosonde launches (military or otherwise) that would otherwise go un-noticed. 21 | 22 | Refer to the wiki for [installation and setup instructions](https://github.com/projecthorus/radiosonde_auto_rx/wiki). 23 | -------------------------------------------------------------------------------- /c34/c34.txt: -------------------------------------------------------------------------------- 1 | 2 | Modulation AFSK 2400 baud 3 | 3000 Hz : bit 1 4 | 4800 Hz : bit 0 5 | 6 | 7 | bits : little endian -> byte 8 | bytes: big endian -> int-value 9 | 10 | bytes getrennt durch 4 bit 1110: 11 | 8N1: 0 bbbbbbbb 1(11)0 bb... , (11)-idle 12 | 13 | Datenpaket besteht aus 8 byte: 14 | header 2 byte 00FF 15 | pck_id 1 byte N 16 | data 4 byte val (big endian) 17 | chksum 2 byte Fletcher16 (hier: byte2 1's-complement am Ende) 18 | 19 | 20 | Telemetrie pck_id N: 21 | 0x14: date: 0x00027173 = 160115 -> 2015-01-16 22 | 0x15: time: 0x0001E73F = 124735 -> 12:47:35 23 | 0x16: lat : 0x02D39C9B = 47422619 -> 47+422619/600000=47.704365° 24 | 0x17: lon : 0x006CB603 = 7124483 -> 7+124483/600000=7.2074717° 25 | 0x18: alt : 0x0004AB21 = 305953 -> 305953/10 = 30595.3m 26 | 27 | 28 | GPS-lat/lon wie NMEA mit Faktor 1e4 29 | 30 | 31 | Checksum: 32 | 2 byte, Summe ueber byte[0]=pck_id,byte[1]=data[0],...,byte[4]=data[3] 33 | byte1: (sum_i byte[i]) & 0xFF 34 | byte2: (-1 - sum_i byte[i]*(5-i)) & 0xFF = ~(sum_i byte[i]*(5-i)) & 0xFF 35 | 36 | FrameID 0x15 (time): 37 | 00FF 15 0001E5FE F9CB 38 | 00FF 15 0001E5FF FACA <- vorletztes byte +1 39 | 00FF 15 0001E600 FCC7 -> byte2: -1-2*1 Differenz 40 | 00FF 15 0001E601 FDC6 41 | 00FF 15 0001E602 FEC5 42 | 00FF 15 0001E603 FFC4 43 | 00FF 15 0001E604 00C3 44 | 00FF 15 0001E605 01C2 45 | 00FF 15 0001E606 02C1 46 | 47 | -> Fletcher16: 48 | 5*byte[0]+4*byte[1]+3*byte[2]+2*byte[3]+1*byte[4] 49 | = byte[0] + (byte[0]+byte[1]) + (byte[0]+byte[1]+byte[2]) + (byte[0]+byte[1]+byte[2]+byte[3]) + (byte[0]+byte[1]+byte[2]+byte[3]+byte[4]) 50 | 51 | 52 | PTU-Data: float32? 53 | 54 | 55 | -------------------------------------------------------------------------------- /lms6/lms6.txt: -------------------------------------------------------------------------------- 1 | 2 | Lockheed Martin Sippican LMS6 3 | (403 MHz) 4 | 5 | FSK 4800 baud 6 | NRZ-S, convolutional code R=1/2, K=8: 0xA9=10010101, 0x44=00100010 7 | 8 | bits: little endian 9 | bytes: big endian 10 | 11 | 12 | 13 | 14 | sync/header: 15 | .. pp pp pp 24 54 00 00 00 (7A .. ..) 16 | wobei pp sich wiederholendes (sync-)byte, z.B. 0xCA oder 0x30 17 | 18 | Dann beginnt Frame mit 3 byte Sonde-SN, 0x7A.... = 80..... , 19 | dann Frame-Counter, GPS. 20 | 21 | 22 | https://www.youtube.com/watch?v=-0Ydq9Ole48 23 | 24 | subframe: 0x7A .. 25 | 26 | | SN/ID |Count|TOW/ms | |Lat/B60B60 |Lon/B60B60 | Alt/mm |vE/mm |vN/mm |vU/mm | 27 | 7A 9A 4A 1C 5C 0C B4 6D 12 FF E7 0D B6 24 2D CA 2D FF 52 36 C2 01 1E E6 68 00 B1 1B 00 11 33 00 11 E4 .. 28 | 7A 9A 4A 1C 5D 0C B4 70 FC FF EC CA 81 24 2D CB CF FF 52 54 CF 01 1E F7 3A 00 B1 D4 00 0F 7F 00 10 B9 .. 29 | 7A 9A 4A 1C 5E 0C B4 74 E3 FF E3 5E 52 24 2D CC B9 FF 52 71 EB 01 1F 0A 7F 00 AC 27 00 08 92 00 13 14 .. 30 | 7A 9A 4A 1C 5F 0C B4 78 C9 FF E9 34 63 24 2D CD 1F FF 52 8D D7 01 1F 18 8A 00 A5 2F 00 03 CF 00 0D E9 .. 31 | 7A 9A 4A 1C 60 0C B4 7C B5 FF DF A8 9C 24 2D CD 58 FF 52 A9 7E 01 1F 27 0D 00 A3 A2 00 02 1C 00 0E 70 .. 32 | 33 | SN: 0x7A9A4A = 8034890 34 | 35 | [ 7260] Di (11:12:31.0) lat: 50.876534° lon: -0.954636° alt: 18802.28m vH: 45.6m/s D: 84.5° vV: 4.6m/s 36 | [ 7261] Di (11:12:32.0) lat: 50.876569° lon: -0.953991° alt: 18806.59m vH: 45.7m/s D: 85.0° vV: 4.3m/s 37 | [ 7262] Di (11:12:33.0) lat: 50.876588° lon: -0.953366° alt: 18811.52m vH: 44.1m/s D: 87.1° vV: 4.9m/s 38 | [ 7263] Di (11:12:34.0) lat: 50.876597° lon: -0.952767° alt: 18815.11m vH: 42.3m/s D: 88.7° vV: 3.6m/s 39 | [ 7264] Di (11:12:35.0) lat: 50.876602° lon: -0.952174° alt: 18818.83m vH: 41.9m/s D: 89.3° vV: 3.7m/s 40 | 41 | -------------------------------------------------------------------------------- /rs_module/rs_data.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef RS_DATA_H 3 | #define RS_DATA_H 4 | 5 | 6 | typedef unsigned char ui8_t; 7 | typedef unsigned short ui16_t; 8 | typedef unsigned int ui32_t; 9 | typedef short i16_t; 10 | typedef int i32_t; 11 | 12 | 13 | typedef struct { 14 | int week; int msec; 15 | double lat; double lon; double alt; 16 | double vN; double vE; double vU; 17 | double vH; double vD; 18 | } GPS_t; 19 | 20 | typedef struct { 21 | double P; 22 | double T; 23 | double H1; 24 | double H2; 25 | } PTU_t; 26 | 27 | typedef struct { 28 | 29 | char SN[12]; 30 | int frnr; 31 | int freq; 32 | int year; int month; int day; 33 | int wday; 34 | int hr; int min; float sec; 35 | 36 | GPS_t GPS; 37 | PTU_t PTU; 38 | 39 | ui32_t crc; 40 | int ecc; 41 | 42 | int header_ofs; 43 | int header_len; 44 | int bufpos; 45 | char *buf; 46 | char *header; 47 | 48 | int baud; 49 | int bits; 50 | float samples_per_bit; 51 | 52 | char *frame_rawbits; 53 | char *frame_bits; 54 | ui8_t *frame_bytes; 55 | ui32_t frame_start; 56 | ui32_t pos; 57 | ui32_t pos_min; 58 | ui32_t frame_len; 59 | 60 | int (*bits2byte)(void *, char *); 61 | int (*rs_process)(void *, int, int); 62 | int input; 63 | 64 | void *addData; 65 | 66 | } rs_data_t; 67 | 68 | typedef struct { 69 | ui32_t tow; 70 | int prn[12]; 71 | double pseudorange[12]; 72 | double doppler[12]; 73 | ui8_t status[12]; 74 | double pos_ecef[3]; 75 | double vel_ecef[3]; 76 | ui8_t Nfix; 77 | double pDOP; 78 | double sAcc; 79 | } sat_t; 80 | 81 | typedef struct { 82 | char SN[12]; 83 | ui8_t bytes[0x33][16+1]; 84 | sat_t sat; 85 | } addData_Vaisala_t; 86 | 87 | typedef struct { 88 | int typ; 89 | int msglen; 90 | int msgpos; 91 | int parpos; 92 | int hdrlen; 93 | int frmlen; 94 | } rs_ecccfg_t; 95 | 96 | 97 | #define ERROR_MALLOC -1 98 | 99 | 100 | 101 | #endif /* RS_DATA_H */ 102 | 103 | -------------------------------------------------------------------------------- /auto_rx/autorx/static/css/c3.min.css: -------------------------------------------------------------------------------- 1 | .c3 svg{font:10px sans-serif;-webkit-tap-highlight-color:transparent}.c3 line,.c3 path{fill:none;stroke:#000}.c3 text{-webkit-user-select:none;-moz-user-select:none;user-select:none}.c3-bars path,.c3-event-rect,.c3-legend-item-tile,.c3-xgrid-focus,.c3-ygrid{shape-rendering:crispEdges}.c3-chart-arc path{stroke:#fff}.c3-chart-arc rect{stroke:#fff;stroke-width:1}.c3-chart-arc text{fill:#fff;font-size:13px}.c3-grid line{stroke:#aaa}.c3-grid text{fill:#aaa}.c3-xgrid,.c3-ygrid{stroke-dasharray:3 3}.c3-text.c3-empty{fill:grey;font-size:2em}.c3-line{stroke-width:1px}.c3-circle._expanded_{stroke-width:1px;stroke:#fff}.c3-selected-circle{fill:#fff;stroke-width:2px}.c3-bar{stroke-width:0}.c3-bar._expanded_{fill-opacity:1;fill-opacity:.75}.c3-target.c3-focused{opacity:1}.c3-target.c3-focused path.c3-line,.c3-target.c3-focused path.c3-step{stroke-width:2px}.c3-target.c3-defocused{opacity:.3!important}.c3-region{fill:#4682b4;fill-opacity:.1}.c3-brush .extent{fill-opacity:.1}.c3-legend-item{font-size:12px}.c3-legend-item-hidden{opacity:.15}.c3-legend-background{opacity:.75;fill:#fff;stroke:#d3d3d3;stroke-width:1}.c3-title{font:14px sans-serif}.c3-tooltip-container{z-index:10}.c3-tooltip{border-collapse:collapse;border-spacing:0;background-color:#fff;empty-cells:show;-webkit-box-shadow:7px 7px 12px -9px #777;-moz-box-shadow:7px 7px 12px -9px #777;box-shadow:7px 7px 12px -9px #777;opacity:.9}.c3-tooltip tr{border:1px solid #ccc}.c3-tooltip th{background-color:#aaa;font-size:14px;padding:2px 5px;text-align:left;color:#fff}.c3-tooltip td{font-size:13px;padding:3px 6px;background-color:#fff;border-left:1px dotted #999}.c3-tooltip td>span{display:inline-block;width:10px;height:10px;margin-right:6px}.c3-tooltip td.value{text-align:right}.c3-area{stroke-width:0;opacity:.2}.c3-chart-arcs-title{dominant-baseline:middle;font-size:1.3em}.c3-chart-arcs .c3-chart-arcs-background{fill:#e0e0e0;stroke:#fff}.c3-chart-arcs .c3-chart-arcs-gauge-unit{fill:#000;font-size:16px}.c3-chart-arcs .c3-chart-arcs-gauge-max{fill:#777}.c3-chart-arcs .c3-chart-arcs-gauge-min{fill:#777}.c3-chart-arc .c3-gauge-value{fill:#000}.c3-chart-arc.c3-target g path{opacity:1}.c3-chart-arc.c3-target.c3-focused g path{opacity:1} -------------------------------------------------------------------------------- /auto_rx/utils/plot_rtl_power.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # auto_rx debug utils - Plot an rtl_power output file. 4 | # 5 | # Usage: python plot_rtl_power.py log_power.csv 6 | # Requires Numpy & Matplotlib 7 | # 8 | import matplotlib.pyplot as plt 9 | import numpy as np 10 | from StringIO import StringIO 11 | import sys 12 | 13 | # Need to keep this in sync with auto_rx.py as we're not set up to do relative imports yet. 14 | def read_rtl_power(filename): 15 | """ Read in frequency samples from a single-shot log file produced by rtl_power """ 16 | 17 | # Output buffers. 18 | freq = np.array([]) 19 | power = np.array([]) 20 | 21 | freq_step = 0 22 | 23 | 24 | # Open file. 25 | f = open(filename,'r') 26 | 27 | # rtl_power log files are csv's, with the first 6 fields in each line describing the time and frequency scan parameters 28 | # for the remaining fields, which contain the power samples. 29 | 30 | for line in f: 31 | # Split line into fields. 32 | fields = line.split(',') 33 | 34 | if len(fields) < 6: 35 | logging.error("Invalid number of samples in input file - corrupt?") 36 | raise Exception("Invalid number of samples in input file - corrupt?") 37 | 38 | start_date = fields[0] 39 | start_time = fields[1] 40 | start_freq = float(fields[2]) 41 | stop_freq = float(fields[3]) 42 | freq_step = float(fields[4]) 43 | n_samples = int(fields[5]) 44 | 45 | #freq_range = np.arange(start_freq,stop_freq,freq_step) 46 | samples = np.loadtxt(StringIO(",".join(fields[6:])),delimiter=',') 47 | freq_range = np.linspace(start_freq,stop_freq,len(samples)) 48 | 49 | # Add frequency range and samples to output buffers. 50 | freq = np.append(freq, freq_range) 51 | power = np.append(power, samples) 52 | 53 | f.close() 54 | 55 | # Sanitize power values, to remove the nan's that rtl_power puts in there occasionally. 56 | power = np.nan_to_num(power) 57 | 58 | return (freq, power, freq_step) 59 | 60 | 61 | if __name__ == '__main__': 62 | filename = sys.argv[1] 63 | 64 | (freq, power, freq_step) = read_rtl_power(filename) 65 | 66 | plt.plot(freq/1e6, power) 67 | plt.xlabel("Frequency (MHz)") 68 | plt.ylabel("Power (dB?)") 69 | plt.title("rtl_power output: %s" % filename) 70 | plt.show() 71 | 72 | -------------------------------------------------------------------------------- /mk2a/mk2a.txt: -------------------------------------------------------------------------------- 1 | 2 | Sippican MkIIa 3 | (1680 MHz) 4 | 5 | FSK 9600 baud, 8N1 6 | bits: little endian 7 | bytes: big endian 8 | 9 | 2x2 subframes pro Sekunde (2 Subframes + Wiederholung) 10 | 11 | sync: 12 | ... 0 01010011 10 01010011 10 01010011 10 01010011 1 13 | CA CA CA CA 14 | (moeglicherweise auch anderes sync-byte moeglich) 15 | 16 | head: 17 | 0 00100100 10 01001010 1 18 | 24 52 19 | 20 | Beispiel: 21 | http://www.hfunderground.com/board/index.php/topic,11020.msg33291.html#msg33291 22 | 23 | subframe1: 0x24 0x52 0x4D 24 | subframe2: 0x24 0x52 0x54 25 | 26 | subframe2: 27 | |ID? |Count| |TOW/ms | |Lat/B60B60 |Lon/B60B60 | Alt/mm |vE/mm |vN/mm |vU/mm | 28 | 24 52 54 32 EC 64 05 D7 00 A2 19 94 37 BB FF EE 84 92 21 41 60 82 B8 57 DA EF 00 1B FD 3E 00 07 BD FF F9 A0 00 0E E8 00 .. 29 | 24 52 54 31 EC 64 05 D8 00 A2 19 94 3B A2 FF E5 18 43 21 41 5F 68 B8 57 D9 C6 00 1C 09 67 FF F9 0F FF F5 E4 00 0D 38 00 .. 30 | 24 52 54 32 EC 64 05 D8 00 A2 19 94 3B A2 FF E5 18 43 21 41 5F 68 B8 57 D9 C6 00 1C 09 67 FF F9 0F FF F5 E4 00 0D 38 00 .. 31 | 24 52 54 31 EC 64 05 D9 00 A2 19 94 3F 8D FF EA CE 9F 21 41 5C D7 B8 57 D8 CA 00 1C 1D 62 FF F9 ED FF E8 62 00 13 2E 00 .. 32 | 24 52 54 32 EC 64 05 D9 00 A2 19 94 3F 8D FF EA CE 9F 21 41 5C D7 B8 57 D8 CA 00 1C 1D 62 FF F9 ED FF E8 62 00 13 2E 00 .. 33 | 24 52 54 31 EC 64 05 DA 00 A2 19 94 43 74 FF E1 62 52 21 41 59 18 B8 57 DA 6B 00 1C 32 15 00 0A 22 FF DD 15 00 13 1B 00 .. 34 | 24 52 54 32 EC 64 05 DA 00 A2 19 94 43 74 FF E1 62 52 21 41 59 18 B8 57 DA 6B 00 1C 32 15 00 0A 22 FF DD 15 00 13 1B 00 .. 35 | 36 | [ 1495] Do (23:12:24.0) lat: 46.765379° lon: -100.767403° alt: 1834.30m vH: 2.6m/s D: 129.5° vV: 3.8m/s 37 | [ 1496] Do (23:12:25.0) lat: 46.765356° lon: -100.767428° alt: 1837.41m vH: 3.1m/s D: 214.5° vV: 3.4m/s 38 | [ 1496] Do (23:12:25.0) lat: 46.765356° lon: -100.767428° alt: 1837.41m vH: 3.1m/s D: 214.5° vV: 3.4m/s 39 | [ 1497] Do (23:12:26.0) lat: 46.765301° lon: -100.767449° alt: 1842.53m vH: 6.2m/s D: 194.4° vV: 4.9m/s 40 | [ 1497] Do (23:12:26.0) lat: 46.765301° lon: -100.767449° alt: 1842.53m vH: 6.2m/s D: 194.4° vV: 4.9m/s 41 | [ 1498] Do (23:12:27.0) lat: 46.765220° lon: -100.767414° alt: 1847.83m vH: 9.3m/s D: 163.8° vV: 4.9m/s 42 | [ 1498] Do (23:12:27.0) lat: 46.765220° lon: -100.767414° alt: 1847.83m vH: 9.3m/s D: 163.8° vV: 4.9m/s 43 | 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Automatic Radiosonde Receiver Utilities 2 | This fork of [rs1279's RS](https://github.com/rs1729/RS) codebase provides a set of utilities ('auto_rx') to allow automatic reception and uploading of [Radiosonde](https://en.wikipedia.org/wiki/Radiosonde) positions to multiple services, including: 3 | * The [Habitat High-Altitude Balloon Tracker](https://tracker.habhub.org) 4 | * **Please note the HabHub Tracker now filters out radiosondes by default.** To view the radiosondes again, clear the search field at the top-left of the tracker of all text, and press enter. Alternatively, use our front-end to HabHub at: [https://sondehub.org/](https://sondehub.org/) 5 | * APRS-IS (for display on sites such as [aprs.fi](https://aprs.fi) 6 | * [OziPlotter](https://github.com/projecthorus/oziplotter), for mobile radiosonde chasing. 7 | 8 | There is also a web interface provided (defaults to port 5000), allowing display of station status and basic tracking of the sonde position. 9 | 10 | Currently we support the following radiosonde types: 11 | * Vaisala RS92SGP 12 | * Vaisala RS41SGP 13 | * Graw DFM06/DFM09 14 | 15 | Support for other radiosondes may be added as required (please send us sondes to test with!) 16 | 17 | This software performs the following steps: 18 | 1. Use rtl_power to scan across a user-defined frequency range, and detect peaks in the spectrum. 19 | 2. For each detected peak frequency, run the rs_detect utility, which determines if a radiosonde signal is present, and what type it is. 20 | 3. If a radiosonde signal is found, start demodulating it, and upload data to various internet services. 21 | 4. If no peaks are found, or if no packets are heard from the radiosonde in a given amount of time (2 minutes by default), go back to step 1. 22 | 23 | The latest version can make use of multiple RTLSDRs to allow for tracking of many radiosondes simultaneously. The number of simultaneous radiosondes you can track is limited only by the number of RTLSDRs you have setup! 24 | 25 | Refer to the wiki for the [latest updates, and installation/setup instructions](https://github.com/projecthorus/radiosonde_auto_rx/wiki). 26 | 27 | #### Please note this software is under constant development. Please update (git pull) regularly! 28 | 29 | ### Contacts 30 | * [Mark Jessop](https://github.com/darksidelemm) - vk5qi@rfhead.net 31 | * [Michael Wheeler](https://github.com/TheSkorm) - git@mwheeler.org 32 | 33 | You can often find us in the #highaltitude IRC Channel on [Freenode](https://webchat.freenode.net/). 34 | -------------------------------------------------------------------------------- /auto_rx/autorx/gps.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Radiosonde Auto RX Tools - GPS Ephemeris / Almanac Grabber 4 | # 5 | # 2017-04 Mark Jessop 6 | # 7 | import ftplib 8 | import requests 9 | import datetime 10 | import logging 11 | import os 12 | 13 | def get_ephemeris(destination="ephemeris.dat"): 14 | ''' Download the latest GPS ephemeris file from the CDDIS's FTP server ''' 15 | try: 16 | logging.debug("GPS Grabber - Connecting to GSFC FTP Server...") 17 | ftp = ftplib.FTP("cddis.gsfc.nasa.gov", timeout=10) 18 | ftp.login("anonymous","anonymous") 19 | ftp.cwd("gnss/data/daily/%s/brdc/" % datetime.datetime.utcnow().strftime("%Y")) 20 | file_list= ftp.nlst() 21 | 22 | # We expect the latest files to be the last in the list. 23 | download_file = None 24 | file_suffix = datetime.datetime.utcnow().strftime("%yn.Z") 25 | 26 | if file_suffix in file_list[-1]: 27 | download_file = file_list[-1] 28 | elif file_suffix in file_list[-2]: 29 | download_file = file_list[-2] 30 | else: 31 | logging.error("GPS Grabber - Could not find appropriate ephemeris file.") 32 | return None 33 | 34 | logging.debug("GPS Grabber - Downloading ephemeris data file: %s" % download_file) 35 | 36 | # Download file. 37 | f_eph = open(destination+".Z",'wb') 38 | ftp.retrbinary("RETR %s" % download_file, f_eph.write) 39 | f_eph.close() 40 | ftp.close() 41 | 42 | # Unzip file. 43 | os.system("gunzip -q -f ./%s" % (destination+".Z")) 44 | 45 | logging.info("GPS Grabber - Ephemeris downloaded to %s successfuly!" % destination) 46 | 47 | return destination 48 | except Exception as e: 49 | logging.error("GPS Grabber - Could not download ephemeris file. - %s" % str(e)) 50 | return None 51 | 52 | def get_almanac(destination="almanac.txt", timeout=20): 53 | ''' Download the latest GPS almanac file from the US Coast Guard website. ''' 54 | try: 55 | _r = requests.get("https://www.navcen.uscg.gov/?pageName=currentAlmanac&format=sem", timeout=timeout) 56 | data = _r.text 57 | if "CURRENT.ALM" in data: 58 | f = open(destination,'w') 59 | f.write(data) 60 | f.close() 61 | logging.info("GPS Grabber - Almanac downloaded to %s successfuly!" % destination) 62 | return destination 63 | else: 64 | logging.error("GPS Grabber - Downloaded file is not a GPS almanac.") 65 | return None 66 | except Exception as e: 67 | logging.error("GPS Grabber - Failed to download almanac data - " % str(e)) 68 | return None 69 | 70 | 71 | if __name__ == "__main__": 72 | logging.basicConfig(level=logging.DEBUG) 73 | get_almanac() 74 | get_ephemeris() 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /rs92/pos2aprs.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | ## aprs-output provided by daniestevez 3 | use strict; 4 | use warnings; 5 | 6 | 7 | my $filename = undef; 8 | my $date = undef; 9 | 10 | my $mycallsign; 11 | my $passcode; 12 | my $comment; 13 | 14 | while (@ARGV) { 15 | $mycallsign = shift @ARGV; 16 | $passcode = shift @ARGV; 17 | $comment = shift @ARGV; 18 | $filename = shift @ARGV; 19 | } 20 | 21 | my $fpi; 22 | 23 | if (defined $filename) { 24 | open($fpi, "<", $filename) or die "Could not open $filename: $!"; 25 | } 26 | else { 27 | $fpi = *STDIN; 28 | } 29 | 30 | my $fpo = *STDOUT; 31 | 32 | 33 | my $line; 34 | 35 | my $hms; 36 | my $lat; my $lon; my $alt; 37 | my $sign; 38 | my $NS; my $EW; 39 | my $str; 40 | 41 | my $speed = 0.00; 42 | my $course = 0.00; 43 | 44 | my $callsign; 45 | 46 | my $temp; 47 | 48 | print $fpo "user $mycallsign pass $passcode vers \"RS decoder\"\n"; 49 | 50 | while ($line = <$fpi>) { 51 | 52 | print STDERR $line; ## entweder: alle Zeilen ausgeben 53 | 54 | if ($line =~ /(\d\d):(\d\d):(\d\d\.?\d?\d?\d?).*\ +lat:\ *(-?\d*)(\.\d*)\ +lon:\ *(-?\d*)(\.\d*)\ +alt:\ *(-?\d*\.\d*).*/) { 55 | 56 | #print STDERR $line; ## oder: nur Zeile mit Koordinaten ausgeben 57 | 58 | $hms = $1*10000+$2*100+$3; 59 | 60 | if ($4 < 0) { $NS="S"; $sign *= -1; } 61 | else { $NS="N"; $sign = 1} 62 | $lat = $sign*$4*100+$5*60; 63 | 64 | if ($6 < 0) { $EW="W"; $sign = -1; } 65 | else { $EW="E"; $sign = 1; } 66 | $lon = $sign*$6*100+$7*60; 67 | 68 | $alt = $8*3.28084; ## m -> feet 69 | 70 | if ($line =~ /(\d\d\d\d)-(\d\d)-(\d\d).*/) { 71 | $date = $3*10000+$2*100+($1%100); 72 | } 73 | 74 | if ($line =~ /vH:\ *(\d+\.\d+)\ +D:\ *(\d+\.\d+).*/) { 75 | $speed = $1*3.6/1.852; ## m/s -> knots 76 | $course = $2; 77 | } 78 | 79 | if ($line =~ /\(([\w]+)\)/) { 80 | $callsign = $1; 81 | } 82 | 83 | if ($line =~ /T=(-?[\d.]+)C/) { 84 | $temp = " T=$1C"; 85 | } 86 | else { 87 | $temp = ""; 88 | } 89 | 90 | $str = sprintf("$mycallsign>APRS,TCPIP*:;%-9s*%06dh%07.2f$NS/%08.2f${EW}O%03d/%03d/A=%06d$comment$temp", $callsign, $hms, $lat, $lon, $course, $speed, $alt); 91 | print $fpo "$str\n"; 92 | 93 | } 94 | #elsif ($line =~ / # xdata = (.*)/) { ## nicht, wenn (oben) alle Zeilen ausgeben werden 95 | # if ($1) { 96 | # print STDERR $line; 97 | # } 98 | #} 99 | } 100 | 101 | close $fpi; 102 | close $fpo; 103 | 104 | -------------------------------------------------------------------------------- /dropsonde/rd94.txt: -------------------------------------------------------------------------------- 1 | 2 | Dropsonde RD94 3 | 4 | GPS: ublox TIM-5H 5 | 6 | 7 | FSK 4800 bit/sec 8 | Manchester, 8N1: 240 bytes/sec 9 | 10 | 2 frames/sec 11 | Frame: 120 byte 12 | 13 | 14 | pro Frame 2 Geschwindigkeitsmessungen, d.h. 15 | GPS-Pos, PTU: 2 Hz 16 | GPS-Vel: 4 Hz 17 | 18 | Frame besteht aus header plus 5 Bloecke mit 16bit-Checksumme. 19 | Checksumme: Fletcher16 mod 256 20 | 21 | PTU data: float32 (exp|sgn|mantissa) 22 | GPS: ublox5 NAV-SOL 0x01 0x06 23 | 24 | 25 | header | cnt chk0 | P T U1 U2 chk1 | iTOW fTOW? week ecef-X ecef-Y ecef-Z pAcc vX1 vY1 vZ1 sAcc1 pDOP sat chk2 | vX2 vY2 vZ2 sAcc2 sat chk3 | ID "A5" bat Ti chk4 26 | 1acffc1d 01 4002 4385 873f3e9f 84b8747c 8419e1c0 8407b540 00 8dc5 b6960a0d 4c6a0200 5b07 03dd b0891417 8284de00 9bd4251e 23010000 3bf6ffff 53050000 25000000 3d000000 ac00 08 18d8 03 66f6ffff 46050000 16000000 3c000000 08 0203 07df0266 4135 069201 c21e 831a2ff0 a80305227006 4126 # check: 00000_00 27 | 1acffc1d 01 4102 4487 873f6622 84b83b34 841a7ac0 84088040 00 1da0 aa980a0d ea6b0200 5b07 03dd d0841417 2787de00 9fd4251e 23010000 46f6ffff 47050000 26000000 3b000000 ac00 08 7245 03 52f6ffff 3c050000 26000000 3f000000 08 f7cc 07df0266 4135 069201 071f 83199130 880305216d06 046b # check: 00000_00 28 | 1acffc1d 01 4202 4589 873fad73 84b7f7a3 841b8040 84093a40 00 21f9 9e9a0a0d 876d0200 5b07 03dd f27f1417 ca89de00 a4d4251e 23010000 50f6ffff 59050000 13000000 3e000000 ac00 08 daac 03 5af6ffff 59050000 1a000000 3d000000 08 0e57 07df0266 4135 069201 c21e 83195a70 900305227106 d467 # check: 00000_00 29 | 1acffc1d 01 4302 468b 873fe5a9 84b7be8b 841bc200 84099f00 00 65cf 929c0a0d 256f0200 5b07 03dd 0a7b1417 788cde00 a9d4251e 24010000 4cf6ffff 61050000 28000000 3e000000 ac00 08 548f 03 4af6ffff 46050000 2b000000 41000000 08 00fd 07df0266 4135 069201 fd1e 83192650 ab0305246d06 d44b # check: 00000_00 30 | 31 | [ 576] Di 2016-02-09 12:46:37.750 (W 1883) lat: 52.72925° lon: 2.15676° alt: 7041.68m sats: 8 vH: 24.5m/s D: 36.5° vV: -14.5m/s P=382.49hPa T=-46.11°C H1=38.47% H2=33.93% (132055654) Ti=19.27°C Bat=7.87V # check: 00000_00 32 | [ 577] Di 2016-02-09 12:46:38.250 (W 1883) lat: 52.72933° lon: 2.15687° alt: 7034.31m sats: 8 vH: 24.4m/s D: 36.3° vV: -14.5m/s P=382.80hPa T=-46.06°C H1=38.62% H2=34.13% (132055654) Ti=19.20°C Bat=7.94V # check: 00000_00 33 | [ 578] Di 2016-02-09 12:46:38.750 (W 1883) lat: 52.72942° lon: 2.15697° alt: 7026.96m sats: 8 vH: 24.3m/s D: 37.0° vV: -14.5m/s P=383.36hPa T=-45.99°C H1=38.88% H2=34.31% (132055654) Ti=19.17°C Bat=7.87V # check: 00000_00 34 | [ 579] Di 2016-02-09 12:46:39.250 (W 1883) lat: 52.72951° lon: 2.15708° alt: 7019.56m sats: 8 vH: 24.5m/s D: 36.9° vV: -14.4m/s P=383.79hPa T=-45.94°C H1=38.94% H2=34.41% (132055654) Ti=19.14°C Bat=7.93V # check: 00000_00 35 | 36 | -------------------------------------------------------------------------------- /dfm/dfm06.txt: -------------------------------------------------------------------------------- 1 | DFM-06: 2 | 3 | 4 | GPS Chip: 5 | Navman/Telit 6 | 7 | Modulation: FSK 8 | raw: 2500 bit/s. 9 | Manchester codiert -> 1250 baud 10 | 11 | Frame: 280 bit, davon 16 bit Header 0x45CF, dann 12 | 3 Bloecke (CFG,DAT1,DAT2) mit (56,104,104) bits 13 | Interleaved: 7x8, 13x8, 13x8 14 | erweiterter systematischer [8,4]-Hamming-Code, erste 4 bit sind Daten 15 | Big Endian 16 | 17 | 18 | GPS_DAT-Block 48bit + 4bit-PCK_ID 0x0..0x8 (letztes nibble): 19 | PCK_ID bitpos 20 | 21 | 0x0 24..31 Counter: 8 bit 22 | 23 | 0x1 32..47 UTC-msec : 16bit (sec*1e3), GPS = UTC + 17s (ab 1. Juli 2015) 24 | 25 | 0x2 0..31 GPS-lat : 32bit, Faktor 1e7 26 | 32..47 hor-V m/s: 16bit, Faktor 1e2 27 | 28 | 0x3 0..31 GPS-lon : 32bit, Faktor 1e7 29 | 32..47 direction: 16bit, Faktor 1e2 (unsigned, 0-36000) 30 | 31 | 0x4 0..31 GPS-alt m: 32bit, Faktor 1e2 32 | 32..47 ver-V m/s: 16bit, Faktor 1e2 (signed) 33 | 34 | 0x8 0..11 Jahr : 12bit 35 | 12..15 Monat: 4bit 36 | 16..20 Tag : 5bit 37 | 21..25 Std : 5bit 38 | 26..31 Min : 6bit 39 | 40 | 0xF 0..47 000000000000 41 | 42 | 43 | CFG/MEAS-Block beginnt mit nibble 44 | 0,1,2, 0,1,3, 0,1,4, 0,1,5, 0,1,6, 45 | 0,1,2, ... 46 | 47 | DFM-06 (341110), NXP 48 | Seriennummer/ID: 49 | CFG-Block 6xxxxxx (Kanal 6) 50 | e.g. 51 | 6324695 -> 324695 52 | (fehlt bei aelteren DFMs) 53 | 54 | DFM-09 (543410), STM32 55 | Seriennummer/ID: 56 | CFG-Block Acaaaab (Kanal A) 57 | b=0: high 16bit aaaa 58 | b=1: low 16bit aaaa 59 | e.g. 60 | AC00070 : 0x0007 61 | ACDB131 : 0xDB13 62 | -> 0x0007DB13 = 514835 63 | 64 | 65 | measure_sensor, Kanaele n=0,..,4: 66 | 67 | nexxxxx: float24 (4bit_exp + 20bit_mantisse) 68 | fn = xxxxx/2^e 69 | bei DFM-NXP(8bit) ist letztes nibble=0, 70 | d.h. nexxxx0 (somit 4+16=20 bit) 71 | 72 | n=3: Rs (dfm6-pcb 10k, dfm9-pcb 20k) 73 | n=4: 220k 74 | 75 | n=0: Thermistor + Rs 76 | 77 | Temperatur-Sensor, DFM-06: 78 | NTC-Thermistor EPCOS B57540G0502 ? 79 | R/T No 8402, R25=5k 80 | 81 | f0 = a*(R+Rs) , R: thermistor 82 | f3 = a*Rs , Rs = 10k,20k 83 | f4 = a*220k , a: A/D-faktor 84 | 85 | (f0-f3)/f4 = R/220k 86 | 87 | ntc thermistor approx: 88 | R/Ro = exp(B(1/T-1/To)) 89 | 1/T = 1/To + 1/B * log(R/Ro) 90 | 91 | To = 25C = 273.15+25 Kelvin 92 | Ro = 5k 93 | B(0C..100C) = 3450 Kelvin 94 | B(-55C..30C) etwas niedriger (z.B. 3260 Kelvin) 95 | 96 | R/Ro = (f0-f3)/f4 * 220k/5k 97 | 98 | EEPROM: GRAW-Kalibrierdaten -80C..+40C ? 99 | 100 | 101 | DFM-06 und DFM-09 haben unterschiedliche Polaritaet bzw. Manchester-Varianten 102 | DFM-06 hat Kanaele 0..6 (anfangs nur 0..5) 103 | DFM-09 hat Kanaele 0..A 104 | Ausnahme: erste DFM-09-Versionen senden wie DFM-06 105 | 106 | 107 | 108 | alter Quellcode (Codierung, Telemetrie): 109 | https://www.amateurfunk.uni-kl.de/projekte-aktivitaeten/decoder-wettersonden/ 110 | Sensor-Info: 111 | https://www.imk-tro.kit.edu/download/Diplomarbeit_Fuetterer.pdf 112 | 113 | -------------------------------------------------------------------------------- /auto_rx/autorx/static/js/utils.js: -------------------------------------------------------------------------------- 1 | // Utility Functions 2 | // Mark Jessop 2018-06-30 3 | 4 | 5 | // Color cycling for balloon traces and icons - Hopefully 4 colors should be enough for now! 6 | var colour_values = ['blue','red','green','purple']; 7 | var colour_idx = 0; 8 | 9 | var los_color = '#00FF00'; 10 | var los_opacity = 0.6; 11 | 12 | // Create a set of icons for the different colour values. 13 | var sondeAscentIcons = {}; 14 | var sondeDescentIcons = {}; 15 | 16 | // TODO: Make these /static URLS be filled in with templates (or does it not matter?) 17 | for (_col in colour_values){ 18 | sondeAscentIcons[colour_values[_col]] = L.icon({ 19 | iconUrl: "/static/img/balloon-" + colour_values[_col] + '.png', 20 | iconSize: [46, 85], 21 | iconAnchor: [23, 76] 22 | }); 23 | sondeDescentIcons[colour_values[_col]] = L.icon({ 24 | iconUrl: "/static/img/parachute-" + colour_values[_col] + '.png', 25 | iconSize: [46, 84], 26 | iconAnchor: [23, 76] 27 | }); 28 | } 29 | 30 | 31 | // calculates look angles between two points 32 | // format of a and b should be {lon: 0, lat: 0, alt: 0} 33 | // returns {elevention: 0, azimut: 0, bearing: "", range: 0} 34 | // 35 | // based on earthmath.py 36 | // Copyright 2012 (C) Daniel Richman; GNU GPL 3 37 | 38 | var DEG_TO_RAD = Math.PI / 180.0; 39 | var EARTH_RADIUS = 6371000.0; 40 | 41 | function calculate_lookangles(a, b) { 42 | // degrees to radii 43 | a.lat = a.lat * DEG_TO_RAD; 44 | a.lon = a.lon * DEG_TO_RAD; 45 | b.lat = b.lat * DEG_TO_RAD; 46 | b.lon = b.lon * DEG_TO_RAD; 47 | 48 | var d_lon = b.lon - a.lon; 49 | var sa = Math.cos(b.lat) * Math.sin(d_lon); 50 | var sb = (Math.cos(a.lat) * Math.sin(b.lat)) - (Math.sin(a.lat) * Math.cos(b.lat) * Math.cos(d_lon)); 51 | var bearing = Math.atan2(sa, sb); 52 | var aa = Math.sqrt(Math.pow(sa, 2) + Math.pow(sb, 2)); 53 | var ab = (Math.sin(a.lat) * Math.sin(b.lat)) + (Math.cos(a.lat) * Math.cos(b.lat) * Math.cos(d_lon)); 54 | var angle_at_centre = Math.atan2(aa, ab); 55 | var great_circle_distance = angle_at_centre * EARTH_RADIUS; 56 | 57 | ta = EARTH_RADIUS + a.alt; 58 | tb = EARTH_RADIUS + b.alt; 59 | ea = (Math.cos(angle_at_centre) * tb) - ta; 60 | eb = Math.sin(angle_at_centre) * tb; 61 | var elevation = Math.atan2(ea, eb) / DEG_TO_RAD; 62 | 63 | // Use Math.coMath.sine rule to find unknown side. 64 | var distance = Math.sqrt(Math.pow(ta, 2) + Math.pow(tb, 2) - 2 * tb * ta * Math.cos(angle_at_centre)); 65 | 66 | // Give a bearing in range 0 <= b < 2pi 67 | bearing += (bearing < 0) ? 2 * Math.PI : 0; 68 | bearing /= DEG_TO_RAD; 69 | 70 | var value = Math.round(bearing % 90); 71 | value = ((bearing > 90 && bearing < 180) || (bearing > 270 && bearing < 360)) ? 90 - value : value; 72 | 73 | var str_bearing = "" + ((bearing < 90 || bearing > 270) ? 'N' : 'S')+ " " + value + '° ' + ((bearing < 180) ? 'E' : 'W'); 74 | 75 | return { 76 | 'elevation': elevation, 77 | 'azimuth': bearing, 78 | 'range': distance, 79 | 'bearing': str_bearing 80 | }; 81 | } -------------------------------------------------------------------------------- /meisei/meisei-rs.txt: -------------------------------------------------------------------------------- 1 | 2 | Meisei-Radiosonden 3 | RS-06G, RS-11G, iMS-100 4 | 5 | 6 | PCM-FM, 1200 baud biphase-S 7 | 8 | Die 1200 bit pro Sekunde bestehen aus zwei Frames, die wiederum in zwei Subframes unterteilt werden koennen, d.h. 4 mal 300 bit. 9 | Einige Wetterdaten werden zweimal pro Sekunde gesendet, Telemetrie einmal pro Sekunde. 10 | Es gibt zwei 600=300+300 bit Frames pro Sekunde mit einen 23+1 bit Header 0x049DCE gefolgt von einem Frame-Counter. 11 | Die zweiten 300 bit werden wiederum mit dem Header 0xFB6230 eingeleitet, der bis auf das letzte bit das Komplement des anderen Headers ist, 12 | d.h. 0x049DCE xor 0xFB6230 = 0xFFFFFE. 13 | Nach jedem Header folgen 6 Bloecke zu je 46 bit, also 24+6*46=300. 14 | 15 | Die 46bit-Bloecke sind BCH-Codewoerter. Es handelt sich um einen (63,51)-Code mit Generatorpolynom 16 | g(x)=x^12+x^10+x^8+x^5+x^4+x^3+1=(x^6+x^4+x^2+x+1)(x^6+x+1). 17 | gekuerzt auf (46,34), die letzten 12 bit sind die BCH-Kontrollbits. 18 | 19 | Die 34 Nachrichtenbits sind aufgeteilt in 16+1+16+1, d.h. nach einem 16 bit Block kommt ein Paritaetsbit, 20 | das 1 ist, wenn die Anzahl 1en in den 16 bit davor gerade ist, und sonst 0. 21 | 22 | Fuer Datenanordnung und Inhalt gibt es mindestens zwei Version je nach Sondentyp. 23 | GPS z.B. ist bei dem einen Typ jeweils 32bit Integer mit Faktor 1e7 bzw. Hoehe Faktor 1e2; 24 | beim zweiten Typ hat die Hoehe nur 24 bit auch mit 1e2, und Lon und Lat mit Faktor 1e6, wobei die Nachkommastellen Minuten sind, 25 | also wie bei NMEA mit Faktor 1e4. 26 | 27 | 28 | 29 | 30 | Variante 1 (RS-11G ?) 31 | 32 | 049DCE1C667FDD8F537C8100004F20764630A20000000010040436 FB623080801F395FFE08A76540000FE01D0C2C1E75025006DE0A07 33 | 049DCE1C67008C73D7168200004F0F764B31A2FFFF000010270B14 FB6230000000000000000000000000000000000000000000001D59 34 | 35 | 0x00..0x02 HEADER 0x049DCE 36 | 0x03..0x04 16 bit 0.5s-counter, count%2=0: 37 | 0x1B..0x1D HEADER 0xFB6230 38 | 0x20..0x23 32 bit GPS-lat * 1e7 (DD.dddddd) 39 | 0x24..0x27 32 bit GPS-lon * 1e7 (DD.dddddd) 40 | 0x28..0x2B 32 bit GPS-alt * 1e2 (m) 41 | 0x32..0x35 32 bit date jjJJMMTT 42 | 43 | 0x00..0x02 HEADER 0x049DCE 44 | 0x03..0x04 16 bit 0.5s-counter, count%2=1: 45 | 0x17..0x18 16 bit time ms xxyy, 00.000-59.000 46 | 0x19..0x1A 16 bit time hh:mm 47 | 0x1B..0x1D HEADER 0xFB6230 48 | 49 | 50 | 51 | Variante 2 (iMS-100 ?) 52 | 53 | 049DCE3E228023DBF53FA700003C74628430C100000000ABE00B3B FB62302390031EECCC00E656E42327562B2436C4C01CDB0F18B09A 54 | 049DCE3E23516AF62B3FC700003C7390D131C100000000AB090000 FB62300000000000032423222422202014211B13220000000067C4 55 | 56 | 0x00..0x02 HEADER 0x049DCE 57 | 0x03..0x04 16 bit 0.5s-counter, count%2=0: 58 | 0x17..0x18 16 bit time ms yyxx, 00.000-59.000 59 | 0x19..0x1A 16 bit time hh:mm 60 | 0x1B..0x1D HEADER 0xFB6230 61 | 0x1E..0x1F 16 bit ? date (TT,MM,JJ)=(date/1000,(date/10)%100,(date%10)+10) 62 | 0x20..0x23 32 bit GPS-lat * 1e4 (NMEA DDMM.mmmm) 63 | 0x24..0x27 32 bit GPS-lon * 1e4 (NMEA DDMM.mmmm) 64 | 0x28..0x2A 24 bit GPS-alt * 1e2 (m) 65 | 66 | 0x00..0x02 HEADER 0x049DCE 67 | 0x03..0x04 16 bit 0.5s-counter, count%2=1: 68 | 0x17..0x18 16 bit 1024-counter yyxx, +0x400=1024; rollover synchron zu ms-counter, nach rollover auch +0x300=768 69 | 0x1B..0x1D HEADER 0xFB6230 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /rs92/pos2nmea.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | use strict; 3 | use warnings; 4 | 5 | 6 | my $filename = undef; 7 | my $date = undef; 8 | 9 | while (@ARGV) { 10 | if ($ARGV[0] eq "--date") { 11 | shift @ARGV; 12 | if (defined $ARGV[0]) { 13 | $date = shift @ARGV; 14 | } 15 | } 16 | else { 17 | $filename = shift @ARGV; 18 | } 19 | } 20 | 21 | my $fpi; 22 | 23 | if (defined $filename) { 24 | open($fpi, "<", $filename) or die "Could not open $filename: $!"; 25 | } 26 | else { 27 | $fpi = *STDIN; 28 | } 29 | 30 | my $fpo = *STDOUT; 31 | 32 | 33 | my $line; 34 | 35 | my $hms; 36 | my $lat; my $lon; my $alt; 37 | my $sign; 38 | my $NS; my $EW; 39 | my $cs; 40 | my $str; 41 | 42 | my $speed = 0.00; 43 | my $course = 0.00; 44 | 45 | if (defined $date && $date =~ /(\d?\d\d\d\d\d)/) { 46 | $date = $1; 47 | } 48 | else { 49 | $date = 21116; ## (d)dmmyy ohne fuehrende 0 (wenn pos-log kein Datum enthaelt) 50 | } 51 | 52 | my $geoid = 50.0; ## GPS ueber Ellipsoid; Geoid-Hoehe in Europa ca 40-50m 53 | 54 | while ($line = <$fpi>) { 55 | 56 | print STDERR $line; ## entweder: alle Zeilen ausgeben 57 | 58 | if ($line =~ /(\d\d):(\d\d):(\d\d\.?\d?\d?\d?).*\ +lat:\ *(-?\d*)(\.\d*)\ +lon:\ *(-?\d*)(\.\d*)\ +alt:\ *(-?\d*\.\d*).*/) { 59 | 60 | #print STDERR $line; ## oder: nur Zeile mit Koordinaten ausgeben 61 | 62 | $hms = $1*10000+$2*100+$3; 63 | 64 | if ($4 < 0) { $NS="S"; $sign *= -1; } 65 | else { $NS="N"; $sign = 1} 66 | $lat = $sign*$4*100+$5*60; 67 | 68 | if ($6 < 0) { $EW="W"; $sign = -1; } 69 | else { $EW="E"; $sign = 1; } 70 | $lon = $sign*$6*100+$7*60; 71 | 72 | $alt = $8; 73 | 74 | if ($line =~ /(\d\d\d\d)-(\d\d)-(\d\d).*/) { 75 | $date = $3*10000+$2*100+($1%100); 76 | } 77 | 78 | if ($line =~ /vH:\ *(\d+\.\d+)\ +D:\ *(\d+\.\d+).*/) { 79 | $speed = $1*3.6/1.852; ## m/s -> knots 80 | $course = $2; 81 | } 82 | 83 | $str = sprintf("GPRMC,%010.3f,A,%08.3f,$NS,%09.3f,$EW,%.2f,%.2f,%06d,,", $hms, $lat, $lon, $speed, $course, $date); 84 | $cs = 0; 85 | $cs ^= $_ for unpack 'C*', $str; 86 | printf $fpo "\$$str*%02X\n", $cs; 87 | 88 | $str = sprintf("GPGGA,%010.3f,%08.3f,$NS,%09.3f,$EW,1,04,0.0,%.3f,M,%.1f,M,,", $hms, $lat, $lon, $alt-$geoid, $geoid); 89 | $cs = 0; 90 | $cs ^= $_ for unpack 'C*', $str; 91 | printf $fpo "\$$str*%02X\n", $cs; 92 | 93 | } 94 | #elsif ($line =~ / # xdata = (.*)/) { ## nicht, wenn (oben) alle Zeilen ausgeben werden 95 | # if ($1) { 96 | # print STDERR $line; 97 | # } 98 | #} 99 | 100 | } 101 | 102 | close $fpi; 103 | close $fpo; 104 | 105 | 106 | ############################################################################################################################# 107 | ## 108 | ## term-1$ socat -d -d pty,raw,echo=0 pty,raw,b4800,echo=0 #[default baudrate 38400] 109 | ## N PTY is /dev/pts/12 110 | ## N PTY is /dev/pts/15 111 | ## 112 | ## term-2$ sox -t oss /dev/dsp -t wav - lowpass 3600 2>/dev/null | ./rs92ecc --ecc --crc --vel2 -e brdc3010.16n | \ 113 | ## ./pos2nmea.pl --date 31116 > /dev/pts/12 114 | ## 115 | ## term-3$ gpsd -D2 -b -n -N /dev/pts/15 116 | ## 117 | ## term-4$ gpspipe -R localhost:2947 118 | ## 119 | ## Viking: GPS-layer, Realtime Tracking Mode, gpsd-port: 2947 120 | ## 121 | ############################################################################################################################# 122 | 123 | 124 | -------------------------------------------------------------------------------- /auto_rx/utils/snr_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Demodulator Performance Testing 4 | # Really simple testing of how the demodulators cope with added white noise. 5 | # 6 | # Copy in the relevant demod binaries to this directory. 7 | # Run with: python snr_test.py -f test_file.bin -d RS92 8 | # 9 | # The input test file should be the FM demodulated signal, with the sox post-processing. 10 | # For example: 11 | # RS92: rtl_fm -p 0 -M fm -F9 -s 12k -f 400500000 | sox -t raw -r 12k -e s -b 16 -c 1 - -r 48000 -b 8 -t wav - highpass 20 lowpass 2500 2>/dev/null > test_file.bin 12 | # RS41: rtl_fm -p 0 -M fm -F9 -s 15k -f 405500000 | sox -t raw -r 15k -e s -b 16 -c 1 - -r 48000 -b 8 -t wav - lowpass 2600 2>/dev/null > test_file.bin 13 | # 14 | # I'm unsure how comparable the RS92 and RS41 results are. Take the results with a healthy lump of salt. 15 | # At the very least, this should be useful to compare performance of different demod revisions. 16 | # 17 | 18 | import sys 19 | import os 20 | import numpy as np 21 | import argparse 22 | import subprocess 23 | 24 | # Demodulator calls. Replace as appropriate. 25 | RS92_DEMOD = "./rs92ecc --crc --ecc --vel" 26 | RS41_DEMOD = "./rs41ecc --crc --ecc --ptu" 27 | 28 | # Noise level range (gaussian distribution, standard deviation referred to full scale), in dB. 29 | # Both demods seem to fall over by -15 dB of added noise. 30 | NOISE_LEVELS = np.arange(-30.0, -15, 1.0) 31 | 32 | TEMP_FILENAME = 'temp.bin' 33 | 34 | def read_file(filename): 35 | ''' Read in file and convert to floating point ''' 36 | data = np.fromfile(filename,dtype='u1') 37 | header = data[:44] # This is a bit of a hack. The RS demods want a wave header, so we store this for later writeout. 38 | data = (data[44:].astype('float') - 128.0) / 128.0 39 | 40 | return (data,header) 41 | 42 | 43 | def write_file(filename, data, header): 44 | ''' Convert an array of floats to uint8 and write to a file ''' 45 | 46 | data = (data*128.0)+128.0 47 | data = data.astype('u1') 48 | f = open(filename,'wb') 49 | f.write(header.tobytes()) 50 | f.write(data.tobytes()) 51 | f.close() 52 | 53 | def add_noise(data, noise_level): 54 | ''' Add white noise to a file ''' 55 | noise_level_linear = 10**(noise_level/20.0) 56 | noise = np.random.normal(scale=noise_level_linear, size=data.shape) 57 | 58 | return data + noise 59 | 60 | def run_demod(filename, demod='RS92'): 61 | if demod == 'RS92': 62 | demod_bin = RS92_DEMOD 63 | else: 64 | demod_bin = RS41_DEMOD 65 | 66 | demod_command = "cat %s | %s" % (filename, demod_bin) 67 | 68 | # Run demod. 69 | with open(os.devnull, 'w') as devnull: 70 | output = subprocess.check_output(demod_command, shell=True, stderr=devnull) 71 | 72 | 73 | if demod == 'RS92': 74 | # RS92 demod just gives us one line per frame. 75 | return len(output.split('\n')) 76 | else: 77 | # RS41 demod gives us a lot more... 78 | frames = 0 79 | for _line in output.split('\n'): 80 | if _line != '': 81 | if _line[0] == '[': 82 | frames += 1 83 | return frames 84 | 85 | 86 | if __name__ == '__main__': 87 | # Command line arguments. 88 | parser = argparse.ArgumentParser() 89 | parser.add_argument("-f", "--filename", type=str, help="Input file. Assumed to be unsigned 8-bit, 48 kHz file.") 90 | parser.add_argument("-d", "--demod", type=str, help="Demodulator to test, either RS92 or RS41.") 91 | args = parser.parse_args() 92 | 93 | print("Reading: %s" % args.filename) 94 | # Read in input file. 95 | (data,header) = read_file(args.filename) 96 | print("Samples: %d" % len(data)) 97 | 98 | for noise_lvl in NOISE_LEVELS: 99 | temp_data = add_noise(data, noise_lvl) 100 | write_file(TEMP_FILENAME, temp_data, header) 101 | 102 | frame_count = run_demod(TEMP_FILENAME, args.demod) 103 | print("%f dB: Frames Recovered: %d" % (noise_lvl,frame_count)) 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /rs92/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Radiosonde RS92 3 | 4 | Tools for decoding RS92-SGP and RS92-AGP radiosonde signals. 5 | 6 | ### Files 7 | 8 | * `nav_gps_vel.c` - include-file for `rs92gps.c`, `rs92gps_2dfix.c`, `rs92ecc.c`, `rs92agp.c`; 9 | `RS/ecc/bch_ecc.c` 10 | 11 | * `rs92gps.c` - RS92-SGP decoder (includes `nav_gps_vel.c`) 12 | 13 | #### Compile 14 | `gcc rs92gps.c -lm -o rs92gps` 15 | 16 | #### Usage 17 | `./rs92gps [options] `
18 | * `file`:
19 | 1.1 ``: FM-demodulated signal, recorded as wav audio file
20 | 2.1 `--rawin1 `: raw data file created with option `-r`
21 | 2.2 `--rawin2 `: SM raw data 22 | * `options`:
23 | `-i`: invert signal/polarity
24 | `-r`: output raw data
25 | `-a `: use SEM almanac (GPS satellites, orbital data) 26 | (cf. https://celestrak.com/GPS/almanac/SEM/)
27 | `-e `: use RINEX ephemerides (GPS satellites, orbital data) 28 | (e.g. YYYY=2017: ftp://cddis.gsfc.nasa.gov/gnss/data/daily/2017/brdc/, 29 | brdcDDD0.YYn.Z, YY - year, DDD - day)
30 | `-v`: additional data/info
31 | `--vel`: output velocity (vH: horizontal, D: direction/heading, vV: vertical)
32 | `--crc`: output only frames with valid GPS-CRC
33 | 34 | `./rs92gps -h`: list more options 35 | 36 | #### Examples 37 | The rs92-radiosonde transmits pseudorange GPS data and GPS time. 38 | To calculate its position, orbital data of the GPS satellites is needed. 39 | You can use either almanac data which is less accurate (but can be used +/- 3 days), 40 | or rinex ephemerides data (recommended) which is more accurate but should not be 41 | older than 2 hours. 42 | The recommended sample rate of the FM-demodulated signal is 48 kHz. 43 | The GPS-altitude is above ellipsoid (in europe, subtract 40-50m geoid height). 44 | * `./rs92gps -r 2015101_14Z.wav > raw.txt` (raw output into `raw.txt`)
45 | `./rs92gps -v -e brdc3050.15n --rawin1 raw.txt` 46 | * `./rs92gps -v -e brdc3050.15n 2015101_14Z.wav` 47 | * `./rs92gps -v --vel2 -e brdc3050.15n 2015101_14Z.wav | tee log.txt` (console output and into file `log.txt`) 48 | 49 | The FSK-demodulation is kept very simple. If the signal quality is low, a lowpass filter is recommended, e.g. 50 | (using `sox`) 51 | * `sox 2015101_14Z.wav -t wav - lowpass 2600 2>/dev/null | ./rs92gps -v -e brdc3050.15n` 52 | 53 | You can redirect live audio stream to the decoder via `sox`, e.g. 54 | * `sox -t oss /dev/dsp -t wav - lowpass 2600 2>/dev/null | ./rs92gps -v --vel -e brdc3050.15n` 55 | * `sox -t oss /dev/dsp -t wav - lowpass 2600 2>/dev/null | stdbuf -oL ./rs92gps -v --vel2 -e brdc3050.15n | tee log.txt`
56 | 57 | If the signal is inverted 58 | (depends on sdr-software and/or audio-card/settings), try option `-i`. 59 | 60 | * `rs92gps_2dfix.c` - test decoder for 2d-fix (if only 3 satellites are available). If the position altitude is known/given, 61 | a position can be calculated with 3 satellites. 62 | 63 | #### Compile 64 | `gcc rs92gps_2dfix.c -lm -o rs92gps_2dfix` 65 | 66 | #### Usage 67 | Same as `rs92gps`. 68 | Additional option `--2dalt `:
69 | `` is the (estimated) altitude of the radiosonde in meters above ellipsoid. 70 | Default (without `--2dalt `) is 0m. 71 | 72 | 80 | * `rs92ecc.c` - RS92-SGP decoder with Reed-Solomon error correction (includes `nav_gps_vel.c`, `bch_ecc.c`) 81 | 82 | #### Compile 83 | * `gcc rs92ecc.c -lm -o rs92ecc`    (copy `RS/ecc/bch_ecc.c`) 84 | 85 | #### Usage 86 | Same as `rs92gps`. 87 | Additional option `--ecc`. 88 | 89 | * `rs92agp.c` - RS92-AGP, RS92-BGP decoder 90 | 91 | * `rs92.txt` - infos 92 | 93 | * `pos2kml.pl`, `pos2gpx.pl`, `pos2nmea.pl`
94 | perl scripts for kml-, gpx-, or nmea-output, resp. 95 | #### Usage/Example 96 | `./rs92ecc --ecc --crc -v --vel2 -e brdc3050.15n 2015101_14Z.wav | ./pos2nmea.pl`
97 | `stderr`: frame output
98 | `stdout`: NMEA output
99 | Only NMEA: 100 | `./rs92ecc --ecc --crc -v --vel2 -e brdc3050.15n 2015101_14Z.wav | ./pos2nmea.pl 2>/dev/null` 101 | 102 | 103 | * `gps_navdata.c` - test tool/example, compares orbital data. Includes `nav_gps.c` and 104 | compares `almanac.sem.week0843.061440.txt`, `brdc2910.15n`, `nga18670.Z`. 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /m10/m10c_part.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * radiosonde M10 (Trimble GPS) 4 | * author: zilog80 5 | * 6 | * big endian forest 7 | */ 8 | 9 | 10 | typedef struct { 11 | int week; int gpssec; 12 | int jahr; int monat; int tag; 13 | int wday; 14 | int std; int min; int sek; 15 | double lat; double lon; double h; 16 | } datum_t; 17 | 18 | datum_t datum; 19 | 20 | 21 | #define BAUD_RATE 4800 22 | 23 | 24 | unsigned char header_bytes[] = { 0x64, 0x9F, 0x20 }; 25 | char header[] = "011001001001111100100000"; 26 | 27 | #define FRAME_LEN 102 28 | int framebytes[FRAME_LEN]; 29 | 30 | 31 | int bits2bytes(char *bits) { 32 | // big endian 33 | // framebytes[pos] = byteval; 34 | } 35 | 36 | 37 | /* -------------------------------------------------------------------------- */ 38 | 39 | #define pos_GPSTOW 0x0A // 4 byte 40 | #define pos_GPSlat 0x0E // 4 byte 41 | #define pos_GPSlon 0x12 // 4 byte 42 | #define pos_GPSheight 0x16 // 4 byte 43 | #define pos_GPSweek 0x20 // 2 byte 44 | 45 | char weekday[7][3] = { "So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"}; 46 | 47 | double B60B60 = 0xB60B60; // 2^32/360 = 0xB60B60.xxx 48 | 49 | void Gps2Date(long GpsWeek, long GpsSeconds, int *Year, int *Month, int *Day) { 50 | // 51 | } 52 | 53 | int get_GPSweek() { 54 | // 55 | for (i = 0; i < 2; i++) { 56 | gpsweek_bytes[i] = framebytes[pos_GPSweek + i]; 57 | } 58 | 59 | datum.week = (gpsweek_bytes[0] << 8) + gpsweek_bytes[1]; 60 | 61 | return 0; 62 | } 63 | 64 | int get_GPStime() { 65 | // 66 | for (i = 0; i < 4; i++) { 67 | gpstime_bytes[i] = framebytes[pos_GPSTOW + i]; 68 | } 69 | 70 | gpstime = 0; 71 | for (i = 0; i < 4; i++) { 72 | gpstime |= gpstime_bytes[i] << (8*(3-i)); 73 | } 74 | 75 | //ms = gpstime % 1000; 76 | gpstime /= 1000; 77 | datum.gpssec = gpstime; 78 | 79 | day = gpstime / (24 * 3600); 80 | gpstime %= (24*3600); 81 | 82 | datum.wday = day; 83 | datum.std = gpstime/3600; 84 | datum.min = (gpstime%3600)/60; 85 | datum.sek = gpstime%60; 86 | 87 | return 0; 88 | } 89 | 90 | int get_GPSlat() { 91 | // 92 | for (i = 0; i < 4; i++) { 93 | gpslat_bytes[i] = framebytes[pos_GPSlat + i]; 94 | } 95 | 96 | gpslat = 0; 97 | for (i = 0; i < 4; i++) { 98 | gpslat |= gpslat_bytes[i] << (8*(3-i)); 99 | } 100 | datum.lat = gpslat / B60B60; 101 | 102 | return 0; 103 | } 104 | 105 | int get_GPSlon() { 106 | // 107 | for (i = 0; i < 4; i++) { 108 | gpslon_bytes[i] = framebytes[pos_GPSlon + i]; 109 | } 110 | 111 | gpslon = 0; 112 | for (i = 0; i < 4; i++) { 113 | gpslon |= gpslon_bytes[i] << (8*(3-i)); 114 | } 115 | datum.lon = gpslon / B60B60; 116 | 117 | return 0; 118 | } 119 | 120 | int get_GPSheight() { 121 | // 122 | for (i = 0; i < 4; i++) { 123 | gpsheight_bytes[i] = framebytes[pos_GPSheight + i]; 124 | } 125 | 126 | gpsheight = 0; 127 | for (i = 0; i < 4; i++) { 128 | gpsheight |= gpsheight_bytes[i] << (8*(3-i)); 129 | } 130 | datum.h = gpsheight / 1000.0; 131 | 132 | return 0; 133 | } 134 | 135 | /* -------------------------------------------------------------------------- */ 136 | 137 | int print_pos() { 138 | int err; 139 | 140 | err = 0; 141 | err |= get_GPSweek(); 142 | err |= get_GPStime(); 143 | err |= get_GPSlat(); 144 | err |= get_GPSlon(); 145 | err |= get_GPSheight(); 146 | 147 | if (!err) { 148 | 149 | Gps2Date(datum.week, datum.gpssec, &datum.jahr, &datum.monat, &datum.tag); 150 | 151 | printf(" (W %d) ", datum.week); 152 | printf("%s ", weekday[datum.wday]); 153 | printf("%04d-%02d-%02d (%02d:%02d:%02d) ", 154 | datum.jahr, datum.monat, datum.tag, datum.std, datum.min, datum.sek); 155 | printf(" lat: %.6f ", datum.lat); 156 | printf(" lon: %.6f ", datum.lon); 157 | printf(" h: %.2f ", datum.h); 158 | printf("\n"); 159 | 160 | } 161 | 162 | return err; 163 | } 164 | 165 | void print_frame() { 166 | int i; 167 | ui8_t byte; 168 | 169 | for (i = 0; i < FRAME_LEN; i++) { 170 | byte = framebytes[i]; 171 | fprintf(stdout, "%02x", byte); 172 | } 173 | fprintf(stdout, "\n"); 174 | 175 | } 176 | 177 | 178 | int main(int argc, char **argv) { 179 | 180 | 181 | while ( !read_bits_psk() ) { 182 | // 183 | // read_bit_PSK: 184 | /* 185 | synch: ...,du,ud,du oder ...,ud,du,ud 186 | bit 0: ud,du oder du,ud 187 | bit 1: du,du oder ud,ud (phase shift) 188 | Header 189 | _uuddu udududduududduud udduudududududud duududduudduuddu (oder:) 190 | _dduud dududuudduduuddu duuddudududududu udduduudduudduud (invers) 191 | 0 0 0 1 1 0 0 1 0 0 1 0 0 1 1 1 1 1 0 0 1 0 0 0 0 0 192 | */ 193 | // header_found 194 | // bits2byte 195 | // framebytes 196 | // 197 | if (option_raw) print_frame(); 198 | else print_pos(); 199 | 200 | } 201 | 202 | 203 | return 0; 204 | } 205 | 206 | -------------------------------------------------------------------------------- /rs_module/rs_demod.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #include "rs_data.h" 6 | #include "rs_demod.h" 7 | 8 | 9 | static int sample_rate = 0, 10 | bits_sample = 0, 11 | channels = 0; 12 | static float samples_per_bit = 0.0; 13 | 14 | static int findstr(char *buff, char *str, int pos) { 15 | int i; 16 | for (i = 0; i < 4; i++) { 17 | if (buff[(pos+i)%4] != str[i]) break; 18 | } 19 | return i; 20 | } 21 | 22 | int read_wav_header(FILE *fp, rs_data_t *rs_data) { 23 | char txt[4+1] = "\0\0\0\0"; 24 | unsigned char dat[4]; 25 | int byte, p=0; 26 | 27 | if (fread(txt, 1, 4, fp) < 4) return -1; 28 | if (strncmp(txt, "RIFF", 4)) return -1; 29 | if (fread(txt, 1, 4, fp) < 4) return -1; 30 | // pos_WAVE = 8L 31 | if (fread(txt, 1, 4, fp) < 4) return -1; 32 | if (strncmp(txt, "WAVE", 4)) return -1; 33 | // pos_fmt = 12L 34 | for ( ; ; ) { 35 | if ( (byte=fgetc(fp)) == EOF ) return -1; 36 | txt[p % 4] = byte; 37 | p++; if (p==4) p=0; 38 | if (findstr(txt, "fmt ", p) == 4) break; 39 | } 40 | if (fread(dat, 1, 4, fp) < 4) return -1; 41 | if (fread(dat, 1, 2, fp) < 2) return -1; 42 | 43 | if (fread(dat, 1, 2, fp) < 2) return -1; 44 | channels = dat[0] + (dat[1] << 8); 45 | 46 | if (fread(dat, 1, 4, fp) < 4) return -1; 47 | // memcpy(&sample_rate, dat, 4); // string.h 48 | sample_rate = dat[0]|(dat[1]<<8)|(dat[2]<<16)|(dat[3]<<24); 49 | 50 | if (fread(dat, 1, 4, fp) < 4) return -1; 51 | if (fread(dat, 1, 2, fp) < 2) return -1; 52 | //byte = dat[0] + (dat[1] << 8); 53 | 54 | if (fread(dat, 1, 2, fp) < 2) return -1; 55 | bits_sample = dat[0] + (dat[1] << 8); 56 | 57 | // pos_dat = 36L + info 58 | for ( ; ; ) { 59 | if ( (byte=fgetc(fp)) == EOF ) return -1; 60 | txt[p % 4] = byte; 61 | p++; if (p==4) p=0; 62 | if (findstr(txt, "data", p) == 4) break; 63 | } 64 | if (fread(dat, 1, 4, fp) < 4) return -1; 65 | 66 | 67 | fprintf(stderr, "sample_rate: %d\n", sample_rate); 68 | fprintf(stderr, "bits : %d\n", bits_sample); 69 | fprintf(stderr, "channels : %d\n", channels); 70 | 71 | if ((bits_sample != 8) && (bits_sample != 16)) return -1; 72 | 73 | samples_per_bit = sample_rate/rs_data->baud; 74 | fprintf(stderr, "samples/bit: %.2f\n", samples_per_bit); 75 | rs_data->samples_per_bit = samples_per_bit; 76 | 77 | return 0; 78 | } 79 | 80 | 81 | #define EOF_INT 0x1000000 82 | 83 | static unsigned long sample_count = 0; 84 | 85 | static int read_signed_sample(FILE *fp) { // int = i32_t 86 | int byte, i, sample, s=0; // EOF -> 0x1000000 87 | 88 | for (i = 0; i < channels; i++) { 89 | // i = 0: left/mono 90 | byte = fgetc(fp); 91 | if (byte == EOF) return EOF_INT; 92 | if (i == 0) sample = byte; 93 | 94 | if (bits_sample == 16) { 95 | byte = fgetc(fp); 96 | if (byte == EOF) return EOF_INT; 97 | if (i == 0) sample += byte << 8; 98 | } 99 | 100 | } 101 | 102 | if (bits_sample == 8) s = sample-128; // 8bit: 00..FF, centerpoint 0x80=128 103 | if (bits_sample == 16) s = (short)sample; 104 | 105 | sample_count++; 106 | 107 | return s; 108 | } 109 | 110 | static int par=1, par_alt=1; 111 | 112 | int read_bits_fsk(FILE *fp, int *bit, int *len, int inv) { 113 | 114 | int option_res = 0; 115 | 116 | static int sample; 117 | int n, y0; 118 | float l, x1; 119 | static float x0; 120 | 121 | n = 0; 122 | do{ 123 | y0 = sample; 124 | sample = read_signed_sample(fp); 125 | if (sample == EOF_INT) return EOF; 126 | //sample_count++; // in read_signed_sample() 127 | par_alt = par; 128 | par = (sample >= 0) ? 1 : -1; // 8bit: 0..127,128..255 (-128..-1,0..127) 129 | n++; 130 | } while (par*par_alt > 0); 131 | 132 | if (!option_res) l = (float)n / samples_per_bit; 133 | else { // genauere Bitlaengen-Messung 134 | x1 = sample/(float)(sample-y0); // hilft bei niedriger sample rate 135 | l = (n+x0-x1) / samples_per_bit; // meist mehr frames (nicht immer) 136 | x0 = x1; 137 | } 138 | 139 | *len = (int)(l+0.5); 140 | 141 | if (!inv) *bit = (1+par_alt)/2; // oben 1, unten -1 142 | else *bit = (1-par_alt)/2; // sdr#bufpos = (rs_data->bufpos+1) % rs_data->header_len; 154 | } 155 | 156 | int compare(rs_data_t *rs_data) { 157 | int i=0, j = rs_data->bufpos; 158 | 159 | while (i < rs_data->header_len) { 160 | if (j < 0) j = rs_data->header_len-1; 161 | if (rs_data->buf[j] != rs_data->header[rs_data->header_ofs+rs_data->header_len-1-i]) break; 162 | j--; 163 | i++; 164 | } 165 | return i; 166 | } 167 | 168 | 169 | 170 | /* ------------------------------------------------------------------------------------ */ 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /auto_rx/autorx/email_notification.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # radiosonde_auto_rx - Email Notification 4 | # 5 | # Copyright (C) 2018 Philip Heron 6 | # Released under GNU GPL v3 or later 7 | 8 | import logging 9 | import time 10 | import smtplib 11 | from email.mime.text import MIMEText 12 | from email.utils import formatdate 13 | from threading import Thread 14 | 15 | try: 16 | # Python 2 17 | from Queue import Queue 18 | 19 | except ImportError: 20 | # Python 3 21 | from queue import Queue 22 | 23 | 24 | class EmailNotification(object): 25 | """ Radiosonde Email Notification Class. 26 | 27 | Accepts telemetry dictionaries from a decoder, and sends an email on newly detected sondes. 28 | Incoming telemetry is processed via a queue, so this object should be thread safe. 29 | 30 | """ 31 | 32 | # We require the following fields to be present in the input telemetry dict. 33 | REQUIRED_FIELDS = [ 'id', 'lat', 'lon', 'alt', 'type', 'freq'] 34 | 35 | def __init__(self, smtp_server = 'localhost', mail_from = None, mail_to = None): 36 | """ Init a new E-Mail Notification Thread """ 37 | self.smtp_server = smtp_server 38 | self.mail_from = mail_from 39 | self.mail_to = mail_to 40 | 41 | # Dictionary to track sonde IDs 42 | self.sondes = {} 43 | 44 | # Input Queue. 45 | self.input_queue = Queue() 46 | 47 | # Start queue processing thread. 48 | self.input_processing_running = True 49 | self.input_thread = Thread(target = self.process_queue) 50 | self.input_thread.start() 51 | 52 | self.log_info("Started E-Mail Notifier Thread") 53 | 54 | 55 | def add(self, telemetry): 56 | """ Add a telemetery dictionary to the input queue. """ 57 | # Check the telemetry dictionary contains the required fields. 58 | for _field in self.REQUIRED_FIELDS: 59 | if _field not in telemetry: 60 | self.log_error("JSON object missing required field %s" % _field) 61 | return 62 | 63 | # Add it to the queue if we are running. 64 | if self.input_processing_running: 65 | self.input_queue.put(telemetry) 66 | else: 67 | self.log_error("Processing not running, discarding.") 68 | 69 | 70 | def process_queue(self): 71 | """ Process packets from the input queue. """ 72 | while self.input_processing_running: 73 | 74 | # Process everything in the queue. 75 | while self.input_queue.qsize() > 0: 76 | try: 77 | _telem = self.input_queue.get_nowait() 78 | self.process_telemetry(_telem) 79 | 80 | except Exception as e: 81 | self.log_error("Error processing telemetry dict - %s" % str(e)) 82 | 83 | # Sleep while waiting for some new data. 84 | time.sleep(0.5) 85 | 86 | 87 | def process_telemetry(self, telemetry): 88 | """ Process a new telemmetry dict, and send an e-mail if it is a new sonde. """ 89 | _id = telemetry['id'] 90 | 91 | if _id not in self.sondes: 92 | try: 93 | # This is a new sonde. Send the email. 94 | msg = 'Sonde launch detected:\n' 95 | msg += '\n' 96 | msg += 'Callsign: %s\n' % _id 97 | msg += 'Type: %s\n' % telemetry['type'] 98 | msg += 'Frequency: %s MHz\n' % telemetry['freq'] 99 | msg += 'Position: %.5f,%.5f\n' % (telemetry['lat'], telemetry['lon']) 100 | msg += 'Altitude: %dm\n' % round(telemetry['alt']) 101 | msg += '\n' 102 | msg += 'https://tracker.habhub.org/#!qm=All&q=RS_%s\n' % _id 103 | 104 | msg = MIMEText(msg, 'plain', 'UTF-8') 105 | msg['Subject'] = 'Sonde launch detected: ' + _id 106 | msg['From'] = self.mail_from 107 | msg['To'] = self.mail_to 108 | msg["Date"] = formatdate() 109 | 110 | s = smtplib.SMTP(self.smtp_server) 111 | s.sendmail(msg['From'], msg['To'], msg.as_string()) 112 | s.quit() 113 | 114 | self.log_info("E-mail sent.") 115 | except Exception as e: 116 | self.log_error("Error sending E-mail - %s" % str(e)) 117 | 118 | self.sondes[_id] = { 'last_time': time.time() } 119 | 120 | 121 | def close(self): 122 | """ Close input processing thread. """ 123 | self.log_debug("Waiting for processing thread to close...") 124 | self.input_processing_running = False 125 | 126 | if self.input_thread is not None: 127 | self.input_thread.join() 128 | 129 | 130 | def running(self): 131 | """ Check if the logging thread is running. 132 | 133 | Returns: 134 | bool: True if the logging thread is running. 135 | """ 136 | return self.input_processing_running 137 | 138 | 139 | def log_debug(self, line): 140 | """ Helper function to log a debug message with a descriptive heading. 141 | Args: 142 | line (str): Message to be logged. 143 | """ 144 | logging.debug("E-Mail - %s" % line) 145 | 146 | 147 | def log_info(self, line): 148 | """ Helper function to log an informational message with a descriptive heading. 149 | Args: 150 | line (str): Message to be logged. 151 | """ 152 | logging.info("E-Mail - %s" % line) 153 | 154 | 155 | def log_error(self, line): 156 | """ Helper function to log an error message with a descriptive heading. 157 | Args: 158 | line (str): Message to be logged. 159 | """ 160 | logging.error("E-Mail - %s" % line) 161 | 162 | 163 | if __name__ == "__main__": 164 | # Test Script 165 | pass 166 | 167 | -------------------------------------------------------------------------------- /m10/m10_msp430.txt: -------------------------------------------------------------------------------- 1 | 2 | M10v05: 3 | 16-bit Microcontroller TI MSP430F233 4 | 5 | 6 | Dokumente 7 | slas5471: MSP430F233 data sheet 8 | slau144 : MSP430x2xx User's Guide 9 | slau278 : MSP430 Hardware Tools 10 | slaa089 : Features of the MSP430 Bootstrap Loader 11 | slau319 : MSP430 Programming With the Bootloader (BSL) 12 | slau320 : MSP430 Programming With the JTAG Interface 13 | 14 | 15 | slaa089: 16 | 5.1 17 | Unprotected Commands 18 | • Receive password 19 | • Mass erase 20 | • Transmit BSL version (V1.50 or higher or in loadables BL_150S_14x.txt, BL_150S_44x.txt) 21 | • Change baud rate (V1.60 or higher or in loadables BL_150S_14x.txt, BL_150S_44x.txt) 22 | 5.2 23 | Password Protected Commands 24 | • Receive data block to program flash memory, RAM, or peripherals 25 | • Transmit data block 26 | • Erase segment 27 | • Erase check (V1.50 or higher or in loadables BL_150S_14x.txt, BL_150S_44x.txt) 28 | • Load program counter and start user program 29 | 30 | slau319: 31 | 2.7 32 | Password Protection 33 | The password protection prohibits every command that potentially allows direct or indirect data access. 34 | Only the unprotected commands like mass erase and RX password (optionally, TX BSL version and 35 | change baud rate) can be performed without prior receipt of the correct password after BSL entry. 36 | Applying the RX password command for receiving the correct password unlocks the remaining 37 | commands. 38 | After it is unlocked, it remains unlocked until initiating another BSL entry. 39 | The password itself consists of the 16 interrupt vectors located at addresses FFE0h to FFFFh (256 bits), 40 | starting with the first byte at address FFE0h. After mass erase and with unprogrammed devices, all 41 | password bits are logical high (1). 42 | BSL versions 2.00 and higher have enhanced security features. These features are controlled by the flash 43 | data word located beneath the interrupt vector table addresses (for example, for the MSP430F2131, 44 | address 0xFFDE). If this word contains: 45 | • 0x0000: The flash memory is not erased if an incorrect BSL password has been received by the target. 46 | • 0xAA55: The BSL is disabled. This means that the BSL is not started with the default initialization 47 | sequence shown in Section 1.3. 48 | • All other values: If an incorrect password is transmitted, the entire flash memory address space is 49 | erased automatically. 50 | 51 | 52 | 53 | 54 | M10v05 mit MSP430F233, MCU_ID 0xF249, BSL_VER 2.02. 55 | 56 | olimex-JTAG: 57 | $ mspdebug -j olimex "hexout 0x0200 0xFE00 m10v05.hex" 58 | $ msp430-objdump -m msp430 -D m10v05.hex > m10v05.asm 59 | USB-TTL CP2102: 60 | $ ./msp430-bsl.py -c /dev/ttyUSB0 --invert-reset --invert-test -P pwd_m10v05.txt --upload=0x0200 --size=0xFE00 > f233hex.out 61 | 62 | 63 | In den letzten 32 Bytes (0xFFE0-0xFFFF) stehen 16 Interrupt-Vektoren, die auch als Passwort fuer die "Protected Commands" dienen. 64 | Will man den Speicher lesen (Protected Command), muss man die richtigen 32 Bytes des IVT uebergeben. (Durch "Mass Erase" wird der 65 | Speicher mit 0xFF beschrieben. Somit haben auch die 32 (pwd-)Bytes des IVT den (default) Wert 0xFF. Danach koennen auch die 66 | "Protected Commands" genutzt und ein neues Programm geschrieben werden.) 67 | 68 | In Version 2.02 des BSL steht in Adresse 0xFFDE, ob bei falschem Passwort der Speicher geloescht wird. Wenn man den Speicher mit 69 | JTAG ausliest, kann man in die Adresse 0xFFDE den Wert 0x0000 schreiben, um leichter mit BSL zu arbeiten. 70 | 71 | Abfrage von TX_BSL in Version 2.02 gibt DATA_NAK=A0h zurueck. 72 | 73 | 74 | 75 | 76 | 0x0000-0x000F special function registers 77 | 0x0010-0x01FF peripheral modules 78 | 0x0200 RAM 79 | 80 | 0x0C00-0x0FFF boot strap loader (BSL) 81 | BSLSKEY-cmp: 82 | c0c: b2 90 55 aa cmp #-21931,&0xffde ;#0xaa55, BSLSKEY==0xAA55? 83 | c10: de ff 84 | c12: ff 27 jz $+0 ;abs 0xc12 85 | ... 86 | d30: 36 40 e0 ff mov #-32, r6 ;#0xffe0 &0xffe0: IVT 87 | d34: 37 40 20 00 mov #32, r7 ;#0x0020 32 bytes 88 | d38: 2b c2 bic #4, r11 ;r2 As==10 89 | BSL-pwd_cmp: 90 | d3a: b0 12 54 0f call #0x0f54 ; read byte 91 | d3e: 7c 96 cmp.b @r6+, r12 92 | d40: 03 24 jz $+8 ;abs 0xd48 93 | d42: 3b d0 40 00 bis #64, r11 ;#0x0040 set pwd-bit 94 | d46: 02 3c jmp $+6 ;abs 0xd4c 95 | d48: 00 3c jmp $+2 ;abs 0xd4a 96 | d4a: 00 3c jmp $+2 ;abs 0xd4c 97 | d4c: 17 83 dec r7 98 | d4e: f5 23 jnz $-20 ;abs 0xd3a loop 32 bytes 99 | d50: b0 12 58 0e call #0x0e58 100 | d54: 3b b0 40 00 bit #64, r11 ;#0x0040 pwd-bit set? 101 | d58: b0 27 jz $-158 ;abs 0xcba pwd correct 102 | d5a: 82 93 de ff tst &0xffde ; BSLSKEY==0x0000? 103 | d5e: 07 24 jz $+16 ;abs 0xd6e 104 | MCU_ID/BSL_VER: 105 | ff0: f2 49 ; MCU_ID F249 106 | ff2: 04 60 107 | ff4: 00 00 108 | ff6: 00 00 109 | ff8: 00 00 110 | ffa: 02 02 ; BSL 2.02 111 | ffc: 01 00 112 | ffe: f5 2b 113 | 114 | 0x1000-0x10FF info memory 115 | 1080: 31 25 ; SN 116 | 1082: 02 08 ; SN 117 | 1084: 3a 08 ; SN 310 2 11329 118 | 1086: 00 0d 119 | 1088: 00 00 120 | 108a: e8 03 121 | 108c: e8 03 122 | 108e: 00 01 123 | 1090: df 07 ; Freq [kHz/200]: 403000 kHz 124 | 1092: 00 00 125 | 126 | 0xE000-0xFFBF code memory 127 | 128 | 0xFFC0-0xFFDF interrupt vector table (IVT) 129 | BSLSKEY: 130 | ffde: ff ff ; BSLSKEY 131 | 0xFFE0-0xFFFF interrupt vector table (IVT) 132 | IVT: 133 | ffe0: ff ff 134 | ffe2: ff ff 135 | ffe4: ff ff 136 | ffe6: ff ff 137 | ffe8: ff ff 138 | ffea: ff ff 139 | ffec: ff ff 140 | ffee: e6 f4 141 | fff0: b4 ed 142 | fff2: 48 f3 143 | fff4: ff ff 144 | fff6: ff ff 145 | fff8: b0 fd 146 | fffa: d0 f8 147 | fffc: ff ff 148 | fffe: 00 e0 ; RESET 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /auto_rx/utils/receiver_stats.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Receiver Statistics Calculator 4 | # 5 | # 2018-04 Mark Jessop 6 | # 7 | # Usage: 8 | # python receiver_stats.py --lat=yourlat --lon=yourlon --alt=youralt ../log/ 9 | # lat and lon are in decimal degrees. 10 | # 11 | import argparse 12 | import glob 13 | import numpy as np 14 | import sys 15 | from math import radians, degrees, sin, cos, atan2, sqrt, pi 16 | 17 | 18 | # Earthmaths code by Daniel Richman (thanks!) 19 | # Copyright 2012 (C) Daniel Richman; GNU GPL 3 20 | def position_info(listener, balloon): 21 | """ 22 | Calculate and return information from 2 (lat, lon, alt) tuples 23 | 24 | Returns a dict with: 25 | 26 | - angle at centre 27 | - great circle distance 28 | - distance in a straight line 29 | - bearing (azimuth or initial course) 30 | - elevation (altitude) 31 | 32 | Input and output latitudes, longitudes, angles, bearings and elevations are 33 | in degrees, and input altitudes and output distances are in meters. 34 | """ 35 | 36 | # Earth: 37 | #radius = 6371000.0 38 | radius = 6364963.0 # Optimized for Australia :-) 39 | 40 | (lat1, lon1, alt1) = listener 41 | (lat2, lon2, alt2) = balloon 42 | 43 | lat1 = radians(lat1) 44 | lat2 = radians(lat2) 45 | lon1 = radians(lon1) 46 | lon2 = radians(lon2) 47 | 48 | # Calculate the bearing, the angle at the centre, and the great circle 49 | # distance using Vincenty's_formulae with f = 0 (a sphere). See 50 | # http://en.wikipedia.org/wiki/Great_circle_distance#Formulas and 51 | # http://en.wikipedia.org/wiki/Great-circle_navigation and 52 | # http://en.wikipedia.org/wiki/Vincenty%27s_formulae 53 | d_lon = lon2 - lon1 54 | sa = cos(lat2) * sin(d_lon) 55 | sb = (cos(lat1) * sin(lat2)) - (sin(lat1) * cos(lat2) * cos(d_lon)) 56 | bearing = atan2(sa, sb) 57 | aa = sqrt((sa ** 2) + (sb ** 2)) 58 | ab = (sin(lat1) * sin(lat2)) + (cos(lat1) * cos(lat2) * cos(d_lon)) 59 | angle_at_centre = atan2(aa, ab) 60 | great_circle_distance = angle_at_centre * radius 61 | 62 | # Armed with the angle at the centre, calculating the remaining items 63 | # is a simple 2D triangley circley problem: 64 | 65 | # Use the triangle with sides (r + alt1), (r + alt2), distance in a 66 | # straight line. The angle between (r + alt1) and (r + alt2) is the 67 | # angle at the centre. The angle between distance in a straight line and 68 | # (r + alt1) is the elevation plus pi/2. 69 | 70 | # Use sum of angle in a triangle to express the third angle in terms 71 | # of the other two. Use sine rule on sides (r + alt1) and (r + alt2), 72 | # expand with compound angle formulae and solve for tan elevation by 73 | # dividing both sides by cos elevation 74 | ta = radius + alt1 75 | tb = radius + alt2 76 | ea = (cos(angle_at_centre) * tb) - ta 77 | eb = sin(angle_at_centre) * tb 78 | elevation = atan2(ea, eb) 79 | 80 | # Use cosine rule to find unknown side. 81 | distance = sqrt((ta ** 2) + (tb ** 2) - 2 * tb * ta * cos(angle_at_centre)) 82 | 83 | # Give a bearing in range 0 <= b < 2pi 84 | if bearing < 0: 85 | bearing += 2 * pi 86 | 87 | return { 88 | "listener": listener, "balloon": balloon, 89 | "listener_radians": (lat1, lon1, alt1), 90 | "balloon_radians": (lat2, lon2, alt2), 91 | "angle_at_centre": degrees(angle_at_centre), 92 | "angle_at_centre_radians": angle_at_centre, 93 | "bearing": degrees(bearing), 94 | "bearing_radians": bearing, 95 | "great_circle_distance": great_circle_distance, 96 | "straight_distance": distance, 97 | "elevation": degrees(elevation), 98 | "elevation_radians": elevation 99 | } 100 | 101 | 102 | 103 | def read_last_position(filename): 104 | ''' Read in a a sonde log file, and return the last lat/lon/alt ''' 105 | 106 | _data = np.genfromtxt(filename,delimiter=',', dtype=None) 107 | _last = _data[-1] 108 | 109 | return _last 110 | 111 | 112 | if __name__ == '__main__': 113 | 114 | parser = argparse.ArgumentParser() 115 | parser.add_argument("--lat", default=-34.0, type=float, help="Receiver Latitude") 116 | parser.add_argument("--lon", default=138.0, type=float, help="Receiver Longitude") 117 | parser.add_argument("--alt", default=0.0, type=float, help="Receiver Altitude") 118 | parser.add_argument("folder", default="../log/", help="Folder containing log files.") 119 | args = parser.parse_args() 120 | 121 | 122 | _file_list = glob.glob(args.folder + "*_sonde.log") 123 | print(_file_list) 124 | 125 | 126 | statistics = { 127 | 'furthest': {'range':0}, 128 | 'lowest': {'elev':90} 129 | } 130 | 131 | 132 | for _file in _file_list: 133 | try: 134 | _last_entry = read_last_position(_file) 135 | except: 136 | continue 137 | print(_last_entry) 138 | _last_pos = (_last_entry[3],_last_entry[4],_last_entry[5]) 139 | 140 | _stats = position_info((args.lat, args.lon, args.alt), _last_pos) 141 | 142 | _range = _stats['straight_distance']/1000.0 143 | _elev = _stats['elevation'] 144 | 145 | if _range > statistics['furthest']['range']: 146 | # Update the furthest distance 147 | statistics['furthest']['range'] = _range 148 | statistics['furthest']['elev'] = _stats['elevation'] 149 | statistics['furthest']['bearing'] = _stats['bearing'] 150 | statistics['furthest']['telem'] = _last_entry 151 | 152 | if (_elev < statistics['lowest']['elev']) and (_range > 20) : 153 | statistics['lowest']['range'] = _range 154 | statistics['lowest']['elev'] = _stats['elevation'] 155 | statistics['lowest']['bearing'] = _stats['bearing'] 156 | statistics['lowest']['telem'] = _last_entry 157 | 158 | print("Longest Distance: %.1f km" % statistics['furthest']['range']) 159 | print(" Detail: %.1f deg elev, %.1f deg bearing" % ( 160 | statistics['furthest']['elev'], 161 | statistics['furthest']['bearing'])) 162 | print(" Last Telemetry: %s" % str(statistics['furthest']['telem'])) 163 | 164 | 165 | print("Lowest Elevation: %.1f degrees" % statistics['lowest']['elev']) 166 | print(" Detail: %.1f km range, %.1f deg bearing" % ( 167 | statistics['lowest']['range'], 168 | statistics['lowest']['bearing'])) 169 | print(" Last Telemetry: %s" % str(statistics['lowest']['telem'])) 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | -------------------------------------------------------------------------------- /rs92/rs92.txt: -------------------------------------------------------------------------------- 1 | 2 | RS92-SGP 3 | 4 | Modulation: GFSK 5 | raw: 4800 bit/s 6 | SGP: 1 frame/s 7 | raw-frame: 4800 bit 8 | 9 | RS92-SGP: manchester -> 2400 bit 10 | 8N1, startbit, 8 bit, stopbit (-> 10bit/byte) 11 | 12 | Frame: 240 byte, little endian 13 | error correction: (CRC,) Reed-Solomon 14 | 15 | 16 | 0x00: 17 | Header (SGP): 2A 2A 2A 2A 2A 10 18 | dann: 65 10 [FrameNb] [SondeID] 19 | 20 | 21 | 0x06: 22 | 65 10 23 | 0x08: 24 | FrameNb(2byte), 20 20 25 | 0x0C: 26 | SondeID(8byte), 00 61 00 27 | 0x17: 28 | Cal_Nb(1byte) 29 | Wenn Cal_Nb=00, dann 30 | ?? ?? 12 02 FF FF 01 00 31 | | freq: 0x0212 = 530 -> (400000 + freq*10) kHz 32 | Frequenzen: 33 | D7 00 -> 402.15 MHz (Zadar) 34 | E0 00 -> 402.24 MHz (Udine) 35 | E6 00 -> 402.30 MHz (Udine/Muenchen/Meiningen) 36 | 0E 01 -> 402.70 MHz (Idar-Oberstein/Kuemmersbruck) 37 | 22 01 -> 402.90 MHz (Hohenspeissenberg) 38 | 5E 01 -> 403.50 MHz (Uccle) 39 | 7C 01 -> 403.80 MHz (Praha) 40 | 86 01 -> 403.90 MHz (De Bilt) 41 | 90 01 -> 404.00 MHz (Udine) 42 | 9A 01 -> 404.10 MHz (Norderney) 43 | AE 01 -> 404.30 MHz (Schleswig2) 44 | C2 01 -> 404.50 MHz (Meppen 6,12 Uhr) 45 | FE 01 -> 405.10 MHz (Meppen 9 Uhr/Lindenberg) 46 | 12 02 -> 405.30 MHz (Essen) 47 | 3A 02 -> 405.70 MHz (Bergen) 48 | 49 | 50 | 0x46: 51 | 67 3D 52 | 0x48: 53 | GPS TOW (gps time of week) 54 | (Receiver: Common Transmission Time vs Common Reception Time) 55 | 56 | 0x4E..0x55: GPS PRN 57 | 12*5 bit = 60 bit, auf 8 byte verteilt: 58 | little endian 59 | 00000111 1122222x 60 | 33333444 4455555x 61 | 66666777 7788888x 62 | 99999AAA AABBBBBx 63 | PRN-32, 6.bit: 64 | Entweder es ist die 3.PRN in dem Block und dann steht es im sonst nicht genutzten 16.bit x, 65 | oder wenn im 16-bit-Block noch eine weitere PRN folgt, 66 | dann ist diese entweder PRN-1 oder eine gerade PRN, 67 | so dass man im letzteren Fall das unterste Bit wieder loescht. 68 | Das Status-Byte sollte anzeigen, dass es sich davor tatsaechlich um PRN-32 handelt 69 | und nicht um einen Satelliten, der noch nicht gelockt ist. 70 | 71 | 0x56..0x61: 12 byte GPS STATUS 72 | 73 | 0x62: 12*8 byte GPS DATA 74 | 4 byte pseudochips; 3 byte delta_chips/385; 1 byte 75 | pseudochip-counter 32bit-fract, Faktor 2^(-10)=1/1024 76 | df = c/(chips/sec) / 2^10 = 299792.458/1023.0/1024.0 = 0.286183844 // c=299792458m/s, 1023000chips/s 77 | pseudorange[m] = - df[m/chips] * pseudochips[chips] 78 | dl = L1/(chips/sec)/4 = 1575.42/1.023/4.0; //385.0 // GPS L1 1575.42MHz=154*10.23MHz, dl=154*10/4 79 | delta(pseudochips, 1s) = delta_chips/dl 80 | 81 | delta(pseudochips) = ca 25800 +/- d, 82 | + sat kommt naeher 83 | - sat entfernt sich 84 | d.h. chip-counter zaehlt, wieviele chips im Intervall schon empfangen wurden; 85 | wenn sat sich entfernt, weniger chips. 86 | nach ca 14 min reset 87 | 88 | Geschwindigkeit kann aus delta_chips/385 errechnet werden: 89 | a) 1sec-ECEF-Differenz von pseudochips und delta_chips (braucht nur Sat-Positionen); 90 | b) delta_chips/Doppler als Relativgeschwindigkeiten mit gleicher "Design Matrix" wie fuer 91 | Positions-Approximation, wobei V-Startwert (0,0,0) (braucht auch Sat-Geschwindigkeiten). 92 | 93 | Bei GPS-Loesung ist DOP-Wert der Konstellation wichtig fuer die Genauigkeit. 94 | Bei almanac-Daten 500-1000m Fehler moeglich, rinex-ephemeris deutlich genauer. 95 | 96 | 97 | 0xC8: 98 | 8 byte aux-data (z.B. O3) 99 | 100 | 101 | Error Correction Code 102 | CRC16 / Reed-Solomon 103 | 104 | HEADER 6 105 | DATA 210 106 | RS-PARITY 24 107 | 108 | 2a2a2a2a2a10 (header) 109 | 6510 (L=2*0x10=32) 110 | e81820204b34393533393334006100083d3d07b342bb3e9809d3bc3f754c963b ebfb 111 | 690c (L=2*0x0c=24) 112 | ca2b0fd9670f00670f8c0f11a458111e8810da410d30430d dc84 113 | 673d (L=2*0x3d=122) 114 | b01c852167ab07298b3cb05a536e0fffcf4faf7f4f3fff8fcfffffffff7fe5918aef20e8110175879900d0e0e900061f8c048aa393009edea1fe2557dc0019549f04b5160d00ae998b00e12a4200370b8608cacc1900b3e08905bd60040126da9303e1d4b4006d04a00573469700d6a98b00699f120195828f04 6ed7 115 | 6805 (L=2*0x05=10) 116 | 03030000000000000000 b27d 117 | ff02 (L=2*0x02=4) 118 | 02000200 119 | f0be2a40a7cd69b9ed0668ec12182e8560ea6dd0733612a1 (Reed-Solomon parity) 120 | 121 | ecc/ecc-rs_vaisala.c: 122 | 1) 123 | ./ecc-rs_vaisala 2a2a2a2a2a106510e81820204b34393533393334006100083d3d07b342bb3e9809d3bc3f754c963bebfb690cca2b0fd9670f00670f8c0f11a458111e8810da410d30430ddc84673db01c852167ab07298b3cb05a536e0fffcf4faf7f4f3fff8fcfffffffff7fe5918aef20e8110175879900d0e0e900061f8c048aa393009edea1fe2557dc0019549f04b5160d00ae998b00e12a4200370b8608cacc1900b3e08905bd60040126da9303e1d4b4006d04a00573469700d6a98b00699f120195828f046ed7680503030000000000000000b27dff0202000200f0be2a40a7cd69b9ed0668ec12182e8560ea6dd0733612a1 124 | RS92 125 | codeword 126 | errors: 0 127 | 0000000000000000000000000000000000000000000002000202FF7DB2000000000000000003030568D76E048F829501129F69008BA9D60097467305A0046D00B4D4E10393DA26010460BD0589E0B30019CCCA08860B3700422AE1008B99AE000D16B5049F541900DC5725FEA1DE9E0093A38A048C1F0600E9E0D0009987750111E820EF8A91E57FFFFFFFFFCF8FFF3F4F7FAF4FCFFF0F6E535AB03C8B2907AB6721851CB03D6784DC0D43300D41DA10881E1158A4110F8C0F67000F67D90F2BCA0C69FBEB3B964C753FBCD309983EBB42B3073D3D08006100343339333539344B202018E81065A1123673D06DEA60852E1812EC6806EDB969CDA7402ABEF0 128 | frame: 129 | 2a2a2a2a2a106510e81820204b34393533393334006100083d3d07b342bb3e9809d3bc3f754c963bebfb690cca2b0fd9670f00670f8c0f11a458111e8810da410d30430ddc84673db01c852167ab07298b3cb05a536e0fffcf4faf7f4f3fff8fcfffffffff7fe5918aef20e8110175879900d0e0e900061f8c048aa393009edea1fe2557dc0019549f04b5160d00ae998b00e12a4200370b8608cacc1900b3e08905bd60040126da9303e1d4b4006d04a00573469700d6a98b00699f120195828f046ed7680503030000000000000000b27dff0202000200f0be2a40a7cd69b9ed0668ec12182e8560ea6dd0733612a1 130 | 131 | 2a) 132 | ./rs92gps -r rs92.wav > rs92raw.txt 133 | cat rs92raw.txt | ./ecc-rs_vaisala > rs92raw_ecc.txt 134 | ./rs92gps --rawin1 -a almanac.txt rs92raw_ecc.txt 135 | 2b) 136 | cat rs92raw.txt | ./ecc-rs_vaisala | ./rs92gps --rawin1 -a almanac.txt 137 | 2c) 138 | ./rs92gps -r rs92.wav | ./ecc-rs_vaisala | ./rs92gps --rawin1 -a almanac.txt 139 | 140 | 3) 141 | ./rs92ecc -b --ecc -e rinex.txt rs92.wav 142 | ./rs92ecc -b --ecc --crc -e rinex.txt rs92.wav 143 | Fehler werden nur in raw-output angezeigt: 144 | ./rs92ecc -b --ecc -r rs92.wav 145 | 146 | 147 | 148 | Bahndaten 149 | SEM Almanac: 150 | https://celestrak.com/GPS/almanac/SEM/ 151 | Rinex Ephemeris: 152 | http://cddis.gsfc.nasa.gov/Data_and_Derived_Products/GNSS/broadcast_ephemeris_data.html 153 | ftp://cddis.gsfc.nasa.gov/gnss/data/daily/YYYY/DDD/YYn/brdcDDD0.YYn.Z (updated) 154 | ftp://cddis.gsfc.nasa.gov/gnss/data/daily/YYYY/brdc/brdcDDD0.YYn.Z (final) 155 | 156 | GPS calendar: 157 | http://adn.agi.com/GNSSWeb/ 158 | 159 | Geoid: 160 | http://geographiclib.sourceforge.net/cgi-bin/GeoidEval 161 | 162 | 163 | weitere Infos: 164 | http://www.vaisala.com/Vaisala%20Documents/Brochures%20and%20Datasheets/RS92SGP-Datasheet-B210358EN-F-LOW.pdf 165 | http://www.vaisala.com/Vaisala%20Documents/Vaisala%20News%20Articles/VN164/VN164_State-of-the-Art_Radiosonde_Telemetry.pdf 166 | http://brmlab.cz/project/weathersonde/ 167 | http://prohu.altervista.org/convert.pdf (PTU, Haeberli 2001) 168 | 169 | Reed-Solomon-Decoder: 170 | http://www.ka9q.net/code/fec/ 171 | https://en.wikiversity.org/wiki/Reed%E2%80%93Solomon_codes_for_coders 172 | 173 | -------------------------------------------------------------------------------- /scan/rtlsdr_scan.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use POSIX qw(strftime); 7 | 8 | 9 | ## rs_detect.c return: 10 | ## #define DFM 2 11 | ## #define RS41 3 12 | ## #define RS92 4 13 | ## #define M10 5 14 | ## #define iMet 6 15 | 16 | my $log_dir = "log"; 17 | 18 | my $utc = strftime('%Y%m%d_%H%M%S', gmtime); 19 | print $utc, "UTC", "\n"; 20 | 21 | my $powfile = sprintf "log_power\.csv"; 22 | 23 | 24 | my $ppm = 54; 25 | my $fos = 403200000 * ( 1 + $ppm/1000000 ); # 403.2MHz-Linie 26 | 27 | my $scan_f1 = "400.6M"; 28 | my $scan_f2 = "405.9M"; 29 | 30 | my $line; 31 | my @fields; 32 | 33 | my $date; 34 | my $time; 35 | my $f1; 36 | my $f2; 37 | my $f; 38 | my $step; 39 | my @db; 40 | 41 | my $i; 42 | my $j; 43 | 44 | my $peak = 0; 45 | my $peak_start; 46 | my $peak_end = 0; 47 | 48 | my $freq; 49 | my $ret; 50 | my $inv; 51 | my $dec; 52 | my $wavfile; 53 | my $filter; 54 | my $breite = ""; 55 | my $rs; 56 | my $WFM; 57 | 58 | my $squelch; 59 | my $snr = 6; 60 | 61 | my @peakarray; 62 | my $num_peaks; 63 | 64 | print "[rtl_power: scan $scan_f1:$scan_f2]\n"; 65 | system("timeout 30 rtl_power -p $ppm -f $scan_f1:$scan_f2:800 -i20 -1 $powfile 2>/dev/null"); 66 | if ( $? == -1 ) { 67 | print "Fehler: $!\n"; 68 | } 69 | 70 | 71 | my $fh; 72 | open ($fh, '<', "$powfile") or die "Kann '$powfile' nicht oeffnen: $!\n"; 73 | 74 | 75 | @peakarray = (); 76 | my $num_lines = 0; 77 | 78 | while ($line = <$fh>) { 79 | $num_lines += 1; 80 | chomp $line; 81 | @db = split(",", $line); 82 | $date = $db[0]; 83 | shift @db; 84 | $time = $db[0]; 85 | shift @db; 86 | $f1 = $db[0]; 87 | shift @db; 88 | $f2 = $db[0]; 89 | shift @db; 90 | $step = $db[0]; 91 | shift @db; 92 | shift @db; 93 | 94 | my $sum = eval join '+', @db; 95 | my $mean = $sum / scalar(@db); 96 | #printf "level: %.1f\n", $mean; 97 | $squelch = $mean + $snr; 98 | 99 | $peak = 0; 100 | $peak_start = $peak_end = 0; 101 | 102 | for ($j = 0; $j < scalar(@db)-1; $j++) { 103 | 104 | if ($db[$j] > $squelch) { 105 | if ($peak == 0) { 106 | $peak_start = $f1 + $j*$step; 107 | } 108 | $peak = 1; 109 | } 110 | elsif ($peak > 0) { 111 | if ($db[$j+1] <= $squelch) { 112 | $peak_end = $f1 + ($j-1)*$step; 113 | $peak = 0; 114 | } 115 | if ($peak_start < $peak_end) { 116 | $freq = $peak_start + ($peak_end-$peak_start)/2; 117 | if ( !($freq > $fos-1000 && $freq < $fos+1000) ) { ## 403.2MHz-Linie auslassen 118 | push @peakarray, $freq; 119 | } 120 | } 121 | } 122 | 123 | } 124 | } 125 | close $fh; 126 | 127 | if ($num_lines == 0) { 128 | print "[reset dvb-t ...]\n"; 129 | reset_dvbt(); 130 | } 131 | else { 132 | $num_peaks = scalar(@peakarray); 133 | for ($j = 0; $j < $num_peaks-1; $j++) { 134 | if ($peakarray[$j+1]-$peakarray[$j] < 10e3) { # DFM peak-to-peak: 6kHz 135 | push @peakarray, $peakarray[$j]+($peakarray[$j+1]-$peakarray[$j])/2; 136 | } 137 | elsif ($peakarray[$j+1]-$peakarray[$j] < 34e3) { # iMet peak-to-peak: 24kHz 138 | push @peakarray, $peakarray[$j]+($peakarray[$j+1]-$peakarray[$j])/2; 139 | } 140 | } 141 | 142 | print "[rtl_fm: detect/decode]\n"; 143 | 144 | for ($j = 0; $j < $num_peaks; $j++) { 145 | 146 | $freq = sprintf "%.0f", $peakarray[$j]; 147 | 148 | eval { 149 | local $SIG{ALRM} = sub {die "alarm\n"}; 150 | alarm 30; # beide Bandbreiten 151 | print "\n$freq Hz: "; # detect ohne highpass 20 152 | system("timeout 12s rtl_fm -p $ppm -M fm -s 15k -f $freq 2>/dev/null |\ 153 | sox -t raw -r 15k -e s -b 16 -c 1 - -r 48000 -t wav - 2>/dev/null |\ 154 | ./rs_detect -s -z -t 8 2>/dev/null"); 155 | $ret = $? >> 8; 156 | if (!$ret) { # mehr Bandbreite bei iMet 157 | system("timeout 12s rtl_fm -p $ppm -M fm -s 36k -o 4 -f $freq 2>/dev/null |\ 158 | sox -t raw -r 36k -e s -b 16 -c 1 - -r 48000 -t wav - 2>/dev/null |\ 159 | ./rs_detect -s -z -t 8 2>/dev/null"); 160 | $ret = $? >> 8; 161 | } 162 | alarm 0; 163 | }; 164 | if ($@) { 165 | die unless $@ =~ /alarm/; 166 | reset_dvbt(); 167 | print "[reset dvb-t ...]\n"; 168 | } 169 | 170 | if ($ret) { 171 | if ($ret & 0x80) { 172 | $inv = '-i'; 173 | $ret = - (0x100 - $ret); # obwohl ($ret & 0x80) = core dump 174 | } 175 | else { $inv = ''; } 176 | $rs = ""; 177 | $WFM = ""; 178 | if (abs($ret) == 2) { $rs = "dfm"; $breite = "15k"; $dec = './dfm06 -vv --ecc'; $filter = "lowpass 2000 highpass 20"; } 179 | if (abs($ret) == 3) { $rs = "rs41"; $breite = "12k"; $dec = './rs41ecc --ecc -v'; $filter = "lowpass 2600"; } 180 | if (abs($ret) == 4) { $rs = "rs92"; $breite = "12k"; $dec = './rs92gps --vel2 -a almanac.txt'; $filter = "lowpass 2500 highpass 20"; } 181 | if (abs($ret) == 5) { $rs = "m10"; $breite = "24k"; $dec = './m10x -vv'; $filter = "highpass 20"; } 182 | if (abs($ret) == 6) { $rs = "imet"; $breite = "40k"; $dec = './imet1ab -v'; $filter = "highpass 20"; $WFM = "-o 4"; } 183 | if ($inv) { print "-";} print uc($rs)," ($utc)\n"; 184 | $utc = strftime('%Y%m%d_%H%M%S', gmtime); 185 | $wavfile = $rs."-".$utc."Z-".$freq."Hz.wav"; 186 | if ($rs) { 187 | system("timeout 30s rtl_fm -p $ppm -M fm $WFM -s $breite -f $freq 2>/dev/null |\ 188 | sox -t raw -r $breite -e s -b 16 -c 1 - -r 48000 -b 8 -t wav - $filter 2>/dev/null |\ 189 | tee $log_dir/$wavfile | $dec $inv 2>/dev/null"); 190 | } 191 | } 192 | 193 | } 194 | print "\n"; 195 | } 196 | 197 | print "\n=\n"; 198 | 199 | 200 | ## $snr: peak sensitivity: wenn zu niedrig, viele Kandidaten; 201 | ## wenn zu hoch, zu wenige (rs41 sendet nur 1/2 sek. 202 | ## andere Methoden: 203 | ## Fenster/Bandbreite aufsummieren/integrieren; Schwerpunkt; 204 | ## rtl-sdr IQ-Band: 1 Minute aufnehmen und dann analysieren/scannen. 205 | ## 206 | ## rs92: aktuellen (SEM) almanac.txt 207 | ## wenn brdc-emphemerides (-e) benutzt werden, darauf achten, dass diese 208 | ## aktuell sind, wenn ueber laengeren Zeitraum gescannt wird 209 | ## 210 | ## rs_detect.c und decoder: gleiche Polaritaet in read_bits_fsk(), 211 | ## am besten als gemeinsame include-Datei wav_audio.c 212 | ################################################################################################### 213 | 214 | 215 | ## http://askubuntu.com/questions/645/how-do-you-reset-a-usb-device-from-the-command-line 216 | ## usbreset.c -> reset_usb 217 | sub reset_dvbt { 218 | my @devices = split("\n",`lsusb`); 219 | foreach my $line (@devices) { 220 | if ($line =~ /\w+\s(\d+)\s\w+\s(\d+):\sID\s([0-9a-f]+):([0-9a-f]+).+Realtek Semiconductor Corp\./) { 221 | if ($4 eq "2832" || $4 eq "2838") { 222 | system("./reset_usb /dev/bus/usb/$1/$2"); 223 | } 224 | } 225 | } 226 | } 227 | 228 | -------------------------------------------------------------------------------- /rs92/almanac.sem.week0843.061440.txt: -------------------------------------------------------------------------------- 1 | 31 CURRENT.ALM 2 | 843 61440 3 | 4 | 1 5 | 63 6 | 0 7 | 4.76169586181641E-03 6.42013549804688E-03 -2.50292941927910E-09 8 | 5.15360498046875E+03 5.49559831619263E-01 1.56480073928833E-01 9 | 9.42617654800415E-01 2.86102294921875E-06 0.00000000000000E+00 10 | 0 11 | 11 12 | 13 | 2 14 | 61 15 | 0 16 | 1.48696899414062E-02 -3.68118286132812E-04 -2.56841303780675E-09 17 | 5.15358935546875E+03 5.36597847938538E-01 -7.07825660705566E-01 18 | -8.72436165809631E-01 5.93185424804688E-04 0.00000000000000E+00 19 | 0 20 | 9 21 | 22 | 3 23 | 69 24 | 0 25 | 4.80175018310547E-04 5.43022155761719E-03 -2.52475729212165E-09 26 | 5.15352343750000E+03 8.80910396575928E-01 -8.89197349548340E-01 27 | -3.48153710365295E-01 2.00271606445312E-05 0.00000000000000E+00 28 | 0 29 | 11 30 | 31 | 4 32 | 34 33 | 0 34 | 1.19705200195312E-02 -6.56127929687500E-04 -2.57932697422802E-09 35 | 5.15358593750000E+03 5.41403293609619E-01 3.54043602943420E-01 36 | 8.81271481513977E-01 -3.43322753906250E-05 -3.63797880709171E-12 37 | 0 38 | 9 39 | 40 | 5 41 | 50 42 | 0 43 | 4.47034835815430E-03 1.34086608886719E-03 -2.57205101661384E-09 44 | 5.15360156250000E+03 8.78067612648010E-01 1.41729474067688E-01 45 | -9.47973728179932E-02 -1.84059143066406E-04 3.63797880709171E-12 46 | 0 47 | 10 48 | 49 | 6 50 | 67 51 | 0 52 | 2.11715698242188E-04 6.37626647949219E-03 -2.50292941927910E-09 53 | 5.15350830078125E+03 5.46863794326782E-01 8.81587386131287E-01 54 | -2.91297674179077E-01 7.91549682617188E-05 7.27595761418343E-12 55 | 0 56 | 11 57 | 58 | 7 59 | 48 60 | 0 61 | 8.99744033813477E-03 8.29315185546875E-03 -2.53567122854292E-09 62 | 5.15364599609375E+03 -4.44634079933167E-01 -8.56053233146667E-01 63 | 7.37231254577637E-01 4.82559204101562E-04 0.00000000000000E+00 64 | 0 65 | 10 66 | 67 | 8 68 | 72 69 | 0 70 | 1.43575668334961E-03 5.85174560546875E-03 -2.50292941927910E-09 71 | 5.15354345703125E+03 2.12597131729126E-01 -5.56113839149475E-01 72 | 1.06339812278748E-01 -9.53674316406250E-06 0.00000000000000E+00 73 | 0 74 | 11 75 | 76 | 9 77 | 68 78 | 0 79 | 3.50475311279297E-04 4.50325012207031E-03 -2.49929144047201E-09 80 | 5.15366406250000E+03 -7.87639379501343E-01 7.68764019012451E-01 81 | -5.35195589065552E-01 -9.53674316406250E-07 3.63797880709171E-12 82 | 0 83 | 11 84 | 85 | 11 86 | 46 87 | 0 88 | 1.63025856018066E-02 -1.53999328613281E-02 -2.70301825366914E-09 89 | 5.15356542968750E+03 4.34556365013123E-01 4.67948317527771E-01 90 | 7.64931321144104E-01 -6.11305236816406E-04 -3.63797880709171E-12 91 | 0 92 | 9 93 | 94 | 12 95 | 58 96 | 0 97 | 5.51271438598633E-03 1.52435302734375E-02 -2.49565346166492E-09 98 | 5.15354687500000E+03 -1.03542566299438E-01 2.05610990524292E-01 99 | 5.91576457023621E-01 3.37600708007812E-04 3.63797880709171E-12 100 | 0 101 | 10 102 | 103 | 13 104 | 43 105 | 0 106 | 4.65059280395508E-03 9.75990295410156E-03 -2.44472175836563E-09 107 | 5.15364160156250E+03 -7.50291109085083E-01 6.54097199440002E-01 108 | -9.83810424804688E-01 -1.57356262207031E-04 -3.63797880709171E-12 109 | 0 110 | 9 111 | 112 | 14 113 | 41 114 | 0 115 | 8.43811035156250E-03 7.37762451171875E-03 -2.47018761001527E-09 116 | 5.15362451171875E+03 -7.61617422103882E-01 -6.20580315589905E-01 117 | -3.76976013183594E-01 2.76565551757812E-05 -3.63797880709171E-12 118 | 0 119 | 9 120 | 121 | 15 122 | 55 123 | 0 124 | 7.60126113891602E-03 -3.18527221679688E-03 -2.56477505899966E-09 125 | 5.15367968750000E+03 -8.02642464637756E-01 1.32260441780090E-01 126 | -5.98965883255005E-01 -2.77519226074219E-04 -3.63797880709171E-12 127 | 0 128 | 10 129 | 130 | 16 131 | 56 132 | 0 133 | 8.12625885009766E-03 1.54151916503906E-02 -2.49565346166492E-09 134 | 5.15359667968750E+03 -9.75867509841919E-02 1.01557016372681E-01 135 | -5.11003732681274E-02 -7.53402709960938E-05 3.63797880709171E-12 136 | 0 137 | 9 138 | 139 | 17 140 | 53 141 | 0 142 | 1.05104446411133E-02 1.00307464599609E-02 -2.45563569478691E-09 143 | 5.15353906250000E+03 2.28366613388062E-01 -6.38240098953247E-01 144 | -3.62008810043335E-01 -1.91688537597656E-04 0.00000000000000E+00 145 | 0 146 | 10 147 | 148 | 18 149 | 54 150 | 0 151 | 1.63583755493164E-02 -5.47409057617188E-03 -2.64117261394858E-09 152 | 5.15357177734375E+03 8.72890233993530E-01 -6.15308403968811E-01 153 | 1.28568768501282E-01 4.44412231445312E-04 3.63797880709171E-12 154 | 0 155 | 9 156 | 157 | 19 158 | 59 159 | 0 160 | 1.13387107849121E-02 9.08660888671875E-03 -2.45927367359400E-09 161 | 5.15510888671875E+03 2.44028687477112E-01 2.08910703659058E-01 162 | -8.89738202095032E-01 -5.21659851074219E-04 0.00000000000000E+00 163 | 0 164 | 9 165 | 166 | 20 167 | 51 168 | 0 169 | 4.97055053710938E-03 -5.09643554687500E-03 -2.63389665633440E-09 170 | 5.15358886718750E+03 8.56302618980408E-01 3.97144556045532E-01 171 | -5.79112887382507E-01 3.64303588867188E-04 3.63797880709171E-12 172 | 0 173 | 9 174 | 175 | 21 176 | 45 177 | 0 178 | 2.26144790649414E-02 -2.55584716796875E-03 -2.58660293184221E-09 179 | 5.15367919921875E+03 5.40921092033386E-01 -5.95261573791504E-01 180 | 4.29614663124084E-01 -4.87327575683594E-04 -3.63797880709171E-12 181 | 0 182 | 9 183 | 184 | 22 185 | 47 186 | 0 187 | 7.99179077148438E-03 -6.16455078125000E-03 -2.65208655036986E-09 188 | 5.15360986328125E+03 8.73178720474243E-01 -6.50720953941345E-01 189 | -2.59807109832764E-02 4.02450561523438E-04 3.63797880709171E-12 190 | 0 191 | 9 192 | 193 | 23 194 | 60 195 | 0 196 | 1.03850364685059E-02 1.63459777832031E-03 -2.52111931331456E-09 197 | 5.15373535156250E+03 -7.85323858261108E-01 -8.35123538970947E-01 198 | -7.43304252624512E-01 -1.39236450195312E-04 -3.63797880709171E-12 199 | 0 200 | 9 201 | 202 | 24 203 | 65 204 | 0 205 | 3.56006622314453E-03 2.92778015136719E-03 -2.59387888945639E-09 206 | 5.15360156250000E+03 -4.58439588546753E-01 9.36031341552734E-02 207 | -9.51664447784424E-01 -6.67572021484375E-06 0.00000000000000E+00 208 | 0 209 | 11 210 | 211 | 25 212 | 62 213 | 0 214 | 4.66060638427734E-03 1.15451812744141E-02 -2.53567122854292E-09 215 | 5.15356640625000E+03 -1.18910312652588E-01 2.33322262763977E-01 216 | 3.87429952621460E-01 -5.43594360351562E-05 -3.63797880709171E-12 217 | 0 218 | 11 219 | 220 | 26 221 | 71 222 | 0 223 | 2.22206115722656E-04 5.83267211914062E-03 -2.59387888945639E-09 224 | 5.15360009765625E+03 -1.20532512664795E-01 -1.81727409362793E-02 225 | 2.22779273986816E-01 -8.86917114257812E-05 -1.45519152283669E-11 226 | 0 227 | 11 228 | 229 | 27 230 | 66 231 | 0 232 | 2.68411636352539E-03 8.42475891113281E-03 -2.47382558882236E-09 233 | 5.15366015625000E+03 2.12892651557922E-01 9.78533029556274E-02 234 | -3.79243373870850E-01 2.86102294921875E-05 3.63797880709171E-12 235 | 0 236 | 11 237 | 238 | 28 239 | 44 240 | 0 241 | 1.98087692260742E-02 1.49860382080078E-02 -2.48473952524364E-09 242 | 5.15365576171875E+03 -9.59317684173584E-02 -5.26890754699707E-01 243 | -1.09564542770386E-01 4.74929809570312E-04 3.63797880709171E-12 244 | 0 245 | 9 246 | 247 | 29 248 | 57 249 | 0 250 | 8.44955444335938E-04 1.03130340576172E-02 -2.45563569478691E-09 251 | 5.15363476562500E+03 2.31390595436096E-01 -1.60193920135498E-01 252 | 4.28920388221741E-01 6.39915466308594E-04 3.63797880709171E-12 253 | 0 254 | 10 255 | 256 | 30 257 | 64 258 | 0 259 | 1.69801712036133E-03 3.76701354980469E-03 -2.58660293184221E-09 260 | 5.15363330078125E+03 -4.29573178291321E-01 -9.96210932731628E-01 261 | 7.12476134300232E-01 4.29153442382812E-05 7.27595761418343E-12 262 | 0 263 | 11 264 | 265 | 31 266 | 52 267 | 0 268 | 8.19396972656250E-03 9.84001159667969E-03 -2.52111931331456E-09 269 | 5.15356835937500E+03 -4.41966295242310E-01 -1.63536071777344E-01 270 | 7.38614082336426E-01 2.98500061035156E-04 0.00000000000000E+00 271 | 0 272 | 10 273 | 274 | 32 275 | 23 276 | 0 277 | 1.13606452941895E-02 1.49917602539062E-03 -2.55386112257838E-09 278 | 5.15361035156250E+03 9.06374692916870E-01 4.80757951736450E-02 279 | 8.73380422592163E-01 -1.04904174804688E-05 1.09139364212751E-11 280 | 0 281 | 9 282 | 283 | -------------------------------------------------------------------------------- /rs_module/rs_main41.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "rs_data.h" 9 | #include "rs_demod.h" 10 | #include "rs_datum.h" 11 | #include "rs_rs41.h" 12 | 13 | 14 | int option_verbose = 0, // ausfuehrliche Anzeige 15 | option_raw = 0, // rohe Frames 16 | option_inv = 0, // invertiert Signal 17 | option_crc = 0, // check CRC 18 | option_sat = 0, // GPS sat data 19 | wavloaded = 0; 20 | option_csv = 0; 21 | 22 | 23 | ui8_t *frame = NULL; 24 | 25 | int print_position(rs_data_t *rs_data) { 26 | 27 | // option_crc: check block-crc 28 | 29 | fprintf(stdout, "[%5d] ", rs_data->frnr); 30 | fprintf(stdout, "(%s) ", rs_data->SN); 31 | 32 | fprintf(stdout, "%s ", weekday[rs_data->wday]); 33 | fprintf(stdout, "%04d-%02d-%02d %02d:%02d:%06.3f", 34 | rs_data->year, rs_data->month, rs_data->day, 35 | rs_data->hr, rs_data->min, rs_data->sec); 36 | if (option_verbose == 3) fprintf(stdout, " (W %d)", (rs_data->GPS).week); 37 | 38 | fprintf(stdout, " "); 39 | fprintf(stdout, " lat: %.5f ", (rs_data->GPS).lat); 40 | fprintf(stdout, " lon: %.5f ", (rs_data->GPS).lon); 41 | fprintf(stdout, " alt: %.2f ", (rs_data->GPS).alt); 42 | fprintf(stdout," vH: %4.1f D: %5.1f° vV: %3.1f ", (rs_data->GPS).vH, (rs_data->GPS).vD, (rs_data->GPS).vU); 43 | 44 | int i; 45 | fprintf(stdout, " # ["); 46 | for (i=0; i<5; i++) fprintf(stdout, "%d", (rs_data->crc>>i)&1); 47 | fprintf(stdout, "]"); 48 | 49 | if (rs_data->ecc >= 0) fprintf(stdout, " [OK]"); else fprintf(stdout, " [NO]"); 50 | if (rs_data->ecc > 0) fprintf(stdout, " (%d)", rs_data->ecc); 51 | 52 | fprintf(stdout, "\n"); 53 | 54 | return 0; 55 | } 56 | 57 | // VK5QI Addition - Print data as easily parseable CSV. 58 | int print_position_csv(rs_data_t *rs_data) { 59 | 60 | // option_crc: check block-crc 61 | 62 | if ( option_crc==0 || ( option_crc && (rs_data->crc & 0x7)==0 ) ) 63 | { 64 | fprintf(stdout, "%5d,", rs_data->frnr); 65 | fprintf(stdout, "%s,", rs_data->SN); 66 | 67 | //fprintf(stdout, "%s,", weekday[rs_data->wday]); 68 | fprintf(stdout, "%04d-%02d-%02d,%02d:%02d:%06.3f,", 69 | rs_data->year, rs_data->month, rs_data->day, 70 | rs_data->hr, rs_data->min, rs_data->sec); 71 | //if (option_verbose) fprintf(stdout, " (W %d)", (rs_data->GPS).week); 72 | fprintf(stdout, "%.5f,", (rs_data->GPS).lat); 73 | fprintf(stdout, "%.5f,", (rs_data->GPS).lon); 74 | fprintf(stdout, "%.2f,", (rs_data->GPS).alt); 75 | fprintf(stdout,"%4.1f,%5.1f,%3.1f,", (rs_data->GPS).vH, (rs_data->GPS).vD, (rs_data->GPS).vU); 76 | if (rs_data->ecc >= 0) fprintf(stdout, "OK"); else fprintf(stdout, "FAIL"); 77 | //if (rs_data->ecc > 0) fprintf(stdout, " (%d)", rs_data->ecc); 78 | 79 | fprintf(stdout, "\n"); 80 | } 81 | return 0; 82 | } 83 | 84 | void print_frame(rs_data_t *rs_data) { 85 | int i; 86 | 87 | if (!option_raw && option_verbose) fprintf(stdout, "\n"); // fflush(stdout); 88 | 89 | (rs_data->rs_process)(rs_data, option_raw, option_verbose); 90 | 91 | if (option_raw) { 92 | for (i = 0; i < rs_data->pos; i++) { 93 | fprintf(stdout, "%02x", rs_data->frame_bytes[i]); 94 | } 95 | 96 | if (rs_data->ecc >= 0) fprintf(stdout, " [OK]"); else fprintf(stdout, " [NO]"); 97 | if (rs_data->ecc > 0) fprintf(stdout, " (%d)", rs_data->ecc); 98 | 99 | fprintf(stdout, "\n"); 100 | } 101 | else if (option_csv){ 102 | print_position_csv(rs_data); 103 | }else{ 104 | print_position(rs_data); 105 | } 106 | } 107 | 108 | int main(int argc, char *argv[]) { 109 | 110 | FILE *fp = NULL; 111 | char *fpname = NULL; 112 | char *bitbuf = NULL; 113 | int bit_count = 0, 114 | header_found = 0, 115 | frmlen = 0; 116 | int i, bit, len; 117 | int err = 0; 118 | 119 | 120 | setbuf(stdout, NULL); 121 | 122 | fpname = argv[0]; 123 | ++argv; 124 | while ((*argv) && (!wavloaded)) { 125 | if ( (strcmp(*argv, "-h") == 0) || (strcmp(*argv, "--help") == 0) ) { 126 | fprintf(stderr, "%s [options] audio.wav\n", fpname); 127 | fprintf(stderr, " options:\n"); 128 | fprintf(stderr, " -v, -vx, -vv (info, aux, info/conf)\n"); 129 | fprintf(stderr, " -r, --raw\n"); 130 | fprintf(stderr, " -i, --invert\n"); 131 | fprintf(stderr, " --crc (check CRC)\n"); 132 | fprintf(stderr, " --std (std framelen)\n"); 133 | fprintf(stderr, " --csv (output as CSV)\n"); 134 | return 0; 135 | } 136 | else if ( (strcmp(*argv, "-v") == 0) || (strcmp(*argv, "--verbose") == 0) ) { 137 | option_verbose |= 0x1; 138 | } 139 | else if (strcmp(*argv, "-vx") == 0) { option_verbose |= 0x2; } 140 | else if (strcmp(*argv, "-vv") == 0) { option_verbose |= 0x3; } 141 | else if (strcmp(*argv, "--crc") == 0) { option_crc = 1; } 142 | else if (strcmp(*argv, "--csv") == 0) { option_csv = 1; } 143 | else if ( (strcmp(*argv, "-r") == 0) || (strcmp(*argv, "--raw") == 0) ) { 144 | option_raw = 1; 145 | } 146 | else if ( (strcmp(*argv, "-i") == 0) || (strcmp(*argv, "--invert") == 0) ) { 147 | option_inv = 1; 148 | } 149 | else if (strcmp(*argv, "--std" ) == 0) { frmlen = 320; } // NDATA_LEN 150 | else if (strcmp(*argv, "--std2") == 0) { frmlen = 518; } // FRAME_LEN 151 | else if (strcmp(*argv, "--sat") == 0) { option_sat = 1; option_verbose |= 0x100; } 152 | else { 153 | fp = fopen(*argv, "rb"); 154 | if (fp == NULL) { 155 | fprintf(stderr, "%s konnte nicht geoeffnet werden\n", *argv); 156 | return -1; 157 | } 158 | wavloaded = 1; 159 | } 160 | ++argv; 161 | } 162 | if (!wavloaded) fp = stdin; 163 | 164 | 165 | rs_data_t rs41data = {{0}}; 166 | rs_data_t *rs_data = &rs41data; 167 | rs_data->input = 8; 168 | err = init_rs41data(rs_data); 169 | if (err) goto error_tag; 170 | frame = rs_data->frame_bytes; 171 | if (frmlen == 0) frmlen = rs_data->frame_len; 172 | 173 | err = read_wav_header(fp, rs_data); 174 | if (err) goto error_tag; 175 | 176 | bitbuf = calloc(rs_data->bits, 1); 177 | if (bitbuf == NULL) { 178 | err = -1; 179 | goto error_tag; 180 | } 181 | 182 | rs_data->pos = rs_data->frame_start; 183 | 184 | while (!read_bits_fsk(fp, &bit, &len, option_inv)) { 185 | 186 | if (len == 0) { // reset_frame(); 187 | if (rs_data->pos > rs_data->pos_min) { 188 | print_frame(rs_data); 189 | bit_count = 0; 190 | rs_data->pos = rs_data->frame_start; 191 | header_found = 0; 192 | } 193 | //inc_bufpos(); 194 | //buf[bufpos] = 'x'; 195 | continue; // ... 196 | } 197 | 198 | for (i = 0; i < len; i++) { 199 | 200 | inc_bufpos(rs_data); 201 | rs_data->buf[rs_data->bufpos] = 0x30 + bit; // Ascii 202 | 203 | if (!header_found) { 204 | if (compare(rs_data) >= rs_data->header_len) header_found = 1; 205 | } 206 | else { 207 | 208 | if (rs_data->input < 8) { 209 | rs_data->frame_rawbits[rs_data->bits*rs_data->pos + bit_count] = 0x30 + bit; 210 | } 211 | 212 | bitbuf[bit_count] = bit; 213 | bit_count++; 214 | 215 | if (bit_count == rs_data->bits) { 216 | bit_count = 0; 217 | if (rs_data->input == 8) { 218 | frame[rs_data->pos] = rs_data->bits2byte(rs_data, bitbuf); 219 | } 220 | rs_data->pos++; 221 | if (rs_data->pos == frmlen) { 222 | print_frame(rs_data); 223 | rs_data->pos = rs_data->frame_start; 224 | header_found = 0; 225 | } 226 | } 227 | } 228 | 229 | } 230 | } 231 | 232 | free(bitbuf); 233 | bitbuf = NULL; 234 | 235 | error_tag: 236 | fclose(fp); 237 | free_rs41data(rs_data); 238 | 239 | return err; 240 | } 241 | 242 | -------------------------------------------------------------------------------- /auto_rx/autorx/ozimux.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # radiosonde_auto_rx - OziMux Output 4 | # 5 | # Copyright (C) 2018 Mark Jessop 6 | # Released under GNU GPL v3 or later 7 | # 8 | 9 | import json 10 | import logging 11 | import socket 12 | import time 13 | from threading import Thread 14 | try: 15 | # Python 2 16 | from Queue import Queue 17 | except ImportError: 18 | # Python 3 19 | from queue import Queue 20 | 21 | 22 | class OziUploader(object): 23 | """ Push radiosonde telemetry out via UDP broadcast to the Horus Chase-Car Utilities 24 | 25 | Uploads to: 26 | - OziMux / OziPlotter (UDP Broadcast, port 8942 by default) - Refer here for information on the data format: 27 | https://github.com/projecthorus/oziplotter/wiki/3---Data-Sources 28 | - "Payload Summary" (UDP Broadcast, on port 55672 by default) 29 | Refer here for information: https://github.com/projecthorus/horus_utils/wiki/5.-UDP-Broadcast-Messages#payload-summary-payload_summary 30 | 31 | NOTE: This class is designed to only handle telemetry from a single radiosonde at a time. It should only be operated in a 32 | single-SDR configuration, where only one sonde is tracked at a time. 33 | 34 | """ 35 | 36 | # We require the following fields to be present in the incoming telemetry dictionary data 37 | REQUIRED_FIELDS = ['frame', 'id', 'datetime', 'lat', 'lon', 'alt', 'temp', 'type', 'freq', 'freq_float', 'datetime_dt'] 38 | 39 | 40 | def __init__(self, 41 | ozimux_port = None, 42 | payload_summary_port = None, 43 | update_rate = 5 44 | ): 45 | """ Initialise an OziUploader Object. 46 | 47 | Args: 48 | ozimux_port (int): UDP port to push ozimux/oziplotter messages to. Set to None to disable. 49 | payload_summary_port (int): UDP port to push payload summary messages to. Set to None to disable. 50 | update_rate (int): Time in seconds between updates. 51 | """ 52 | 53 | self.ozimux_port = ozimux_port 54 | self.payload_summary_port = payload_summary_port 55 | self.update_rate = update_rate 56 | 57 | # Input Queue. 58 | self.input_queue = Queue() 59 | 60 | # Start the input queue processing thread. 61 | self.input_processing_running = True 62 | self.input_thread = Thread(target=self.process_queue) 63 | self.input_thread.start() 64 | 65 | self.log_info("Started OziMux / Payload Summary Exporter") 66 | 67 | 68 | def send_ozimux_telemetry(self, telemetry): 69 | """ Send a packet of telemetry into the network in OziMux/OziPlotter-compatible format. 70 | 71 | Args: 72 | telemetry (dict): Telemetry dictionary to send. 73 | 74 | """ 75 | 76 | _short_time = telemetry['datetime_dt'].strftime("%H:%M:%S") 77 | _sentence = "TELEMETRY,%s,%.5f,%.5f,%d\n" % (_short_time, telemetry['lat'], telemetry['lon'], telemetry['alt']) 78 | 79 | try: 80 | _ozisock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) 81 | 82 | # Set up socket for broadcast, and allow re-use of the address 83 | _ozisock.setsockopt(socket.SOL_SOCKET,socket.SO_BROADCAST,1) 84 | _ozisock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 85 | # Under OSX we also need to set SO_REUSEPORT to 1 86 | try: 87 | _ozisock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) 88 | except: 89 | pass 90 | _ozisock.sendto(_sentence.encode('ascii'),('',self.ozimux_port)) 91 | _ozisock.close() 92 | 93 | except Exception as e: 94 | self.log_error("Failed to send OziMux packet: %s" % str(e)) 95 | 96 | 97 | def send_payload_summary(self, telemetry): 98 | """ Send a payload summary message into the network via UDP broadcast. 99 | 100 | Args: 101 | telemetry (dict): Telemetry dictionary to send. 102 | 103 | """ 104 | 105 | try: 106 | # Prepare heading & speed fields, if they are provided in the incoming telemetry blob. 107 | if 'heading' in telemetry.keys(): 108 | _heading = telemetry['heading'] 109 | else: 110 | _heading = -1 111 | 112 | if 'vel_h' in telemetry.keys(): 113 | _speed = telemetry['vel_h']*3.6 114 | else: 115 | _speed = -1 116 | 117 | # Generate 'short' time field. 118 | _short_time = telemetry['datetime_dt'].strftime("%H:%M:%S") 119 | 120 | 121 | packet = { 122 | 'type' : 'PAYLOAD_SUMMARY', 123 | 'callsign' : telemetry['id'], 124 | 'latitude' : telemetry['lat'], 125 | 'longitude' : telemetry['lon'], 126 | 'altitude' : telemetry['alt'], 127 | 'speed' : _speed, 128 | 'heading': _heading, 129 | 'time' : _short_time, 130 | 'comment' : 'Radiosonde', 131 | # Additional fields specifically for radiosondes 132 | 'model': telemetry['type'], 133 | 'freq': telemetry['freq'], 134 | 'temp': telemetry['temp'] 135 | } 136 | 137 | # Set up our UDP socket 138 | _s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) 139 | _s.settimeout(1) 140 | # Set up socket for broadcast, and allow re-use of the address 141 | _s.setsockopt(socket.SOL_SOCKET,socket.SO_BROADCAST,1) 142 | _s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 143 | # Under OSX we also need to set SO_REUSEPORT to 1 144 | try: 145 | _s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) 146 | except: 147 | pass 148 | 149 | _s.sendto(json.dumps(packet).encode('ascii'), ('', self.payload_summary_port)) 150 | except Exception as e: 151 | self.log_error("Error sending Payload Summary: %s" % str(e)) 152 | 153 | 154 | def process_queue(self): 155 | """ Process packets from the input queue. 156 | 157 | This thread handles packets from the input queue (provided by the decoders) 158 | Packets are sorted by ID, and a dictionary entry is created. 159 | 160 | """ 161 | 162 | while self.input_processing_running: 163 | 164 | if self.input_queue.qsize() > 0: 165 | # Dump the queue, keeping the most recent element. 166 | while not self.input_queue.empty(): 167 | _telem = self.input_queue.get() 168 | 169 | # Send! 170 | if self.ozimux_port != None: 171 | self.send_ozimux_telemetry(_telem) 172 | 173 | if self.payload_summary_port != None: 174 | self.send_payload_summary(_telem) 175 | 176 | time.sleep(self.update_rate) 177 | 178 | 179 | 180 | 181 | def add(self, telemetry): 182 | """ Add a dictionary of telemetry to the input queue. 183 | 184 | Args: 185 | telemetry (dict): Telemetry dictionary to add to the input queue. 186 | 187 | """ 188 | 189 | # Check the telemetry dictionary contains the required fields. 190 | for _field in self.REQUIRED_FIELDS: 191 | if _field not in telemetry: 192 | self.log_error("JSON object missing required field %s" % _field) 193 | return 194 | 195 | # Add it to the queue if we are running. 196 | if self.input_processing_running: 197 | self.input_queue.put(telemetry) 198 | else: 199 | self.log_error("Processing not running, discarding.") 200 | 201 | 202 | def close(self): 203 | """ Shutdown processing thread. """ 204 | self.log_debug("Waiting for processing thread to close...") 205 | self.input_processing_running = False 206 | 207 | if self.input_thread is not None: 208 | self.input_thread.join() 209 | 210 | 211 | def log_debug(self, line): 212 | """ Helper function to log a debug message with a descriptive heading. 213 | Args: 214 | line (str): Message to be logged. 215 | """ 216 | logging.debug("OziMux - %s" % line) 217 | 218 | 219 | def log_info(self, line): 220 | """ Helper function to log an informational message with a descriptive heading. 221 | Args: 222 | line (str): Message to be logged. 223 | """ 224 | logging.info("OziMux - %s" % line) 225 | 226 | 227 | def log_error(self, line): 228 | """ Helper function to log an error message with a descriptive heading. 229 | Args: 230 | line (str): Message to be logged. 231 | """ 232 | logging.error("OziMux - %s" % line) 233 | -------------------------------------------------------------------------------- /auto_rx/autorx/logger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # radiosonde_auto_rx - Sonde Telemetry Logger 4 | # 5 | # Copyright (C) 2018 Mark Jessop 6 | # Released under GNU GPL v3 or later 7 | # 8 | import datetime 9 | import glob 10 | import logging 11 | import os 12 | import time 13 | from threading import Thread 14 | try: 15 | # Python 2 16 | from Queue import Queue 17 | except ImportError: 18 | # Python 3 19 | from queue import Queue 20 | 21 | 22 | 23 | class TelemetryLogger(object): 24 | """ Radiosonde Telemetry Logger Class. 25 | 26 | Accepts telemetry dictionaries from a decoder, and writes out to log files, one per sonde ID. 27 | Incoming telemetry is processed via a queue, so this object should be thread safe. 28 | 29 | Log files are written to filenames of the form YYYYMMDD-HHMMSS____sonde.log 30 | 31 | """ 32 | 33 | # Close any open file handles after X seconds of no activity. 34 | # This will help avoid having lots of file handles open for a long period of time if we are handling telemetry 35 | # from multiple sondes. 36 | FILE_ACTIVITY_TIMEOUT = 30 37 | 38 | # We require the following fields to be present in the input telemetry dict. 39 | REQUIRED_FIELDS = ['frame', 'id', 'datetime', 'lat', 'lon', 'alt', 'temp', 'type', 'freq', 'datetime_dt'] 40 | 41 | def __init__(self, 42 | log_directory = "./log"): 43 | """ Initialise and start a sonde logger. 44 | 45 | Args: 46 | log_directory (str): Directory in which to save log files. 47 | 48 | """ 49 | 50 | self.log_directory = log_directory 51 | 52 | # Dictionary to contain file handles. 53 | # Each sonde id is added as a unique key. Under each key are the contents: 54 | # 'log' (file): Open file object. 55 | # 'last_time' (float): Timestamp of the last time telemetry was added to the open log. 56 | self.open_logs = {} 57 | 58 | # Input Queue. 59 | self.input_queue = Queue() 60 | 61 | 62 | # Start queue processing thread. 63 | self.input_processing_running = True 64 | self.log_process_thread = Thread(target=self.process_queue) 65 | self.log_process_thread.start() 66 | 67 | 68 | def add(self, telemetry): 69 | """ Add a dictionary of telemetry to the input queue. 70 | 71 | Args: 72 | telemetry (dict): Telemetry dictionary to add to the input queue. 73 | 74 | """ 75 | 76 | # Check the telemetry dictionary contains the required fields. 77 | for _field in self.REQUIRED_FIELDS: 78 | if _field not in telemetry: 79 | self.log_error("JSON object missing required field %s" % _field) 80 | return 81 | 82 | # Add it to the queue if we are running. 83 | if self.input_processing_running: 84 | self.input_queue.put(telemetry) 85 | else: 86 | self.log_error("Processing not running, discarding.") 87 | 88 | 89 | def process_queue(self): 90 | """ Process data from the input queue, and write telemetry to log files. 91 | """ 92 | self.log_info("Started Telemetry Logger Thread.") 93 | 94 | while self.input_processing_running: 95 | 96 | # Process everything in the queue. 97 | while self.input_queue.qsize() > 0: 98 | try: 99 | _telem = self.input_queue.get_nowait() 100 | self.write_telemetry(_telem) 101 | except Exception as e: 102 | self.log_error("Error processing telemetry dict - %s" % str(e)) 103 | 104 | # Close any un-needed log handlers. 105 | self.cleanup_logs() 106 | 107 | # Sleep while waiting for some new data. 108 | time.sleep(0.5) 109 | 110 | self.log_info("Stopped Telemetry Logger Thread.") 111 | 112 | 113 | def telemetry_to_string(self, telemetry): 114 | """ Convert a telemetry dictionary to a CSV string. 115 | 116 | Args: 117 | telemetry (dict): Telemetry dictionary to process. 118 | """ 119 | _log_line = "%s,%s,%d,%.5f,%.5f,%.1f,%.1f,%s,%.3f\n" % ( 120 | telemetry['datetime'], 121 | telemetry['id'], 122 | telemetry['frame'], 123 | telemetry['lat'], 124 | telemetry['lon'], 125 | telemetry['alt'], 126 | telemetry['temp'], 127 | telemetry['type'], 128 | telemetry['freq_float']) 129 | 130 | # TODO: Add Aux data, if it exists. 131 | 132 | return _log_line 133 | 134 | def write_telemetry(self, telemetry): 135 | """ Write a packet of telemetry to a log file. 136 | 137 | Args: 138 | telemetry (dict): Telemetry dictionary to process. 139 | """ 140 | 141 | _id = telemetry['id'] 142 | _type = telemetry['type'] 143 | 144 | # If there is no log open for the current ID check to see if there is an existing (closed) log file, and open it. 145 | if _id not in self.open_logs: 146 | _search_string = os.path.join(self.log_directory, "*%s_%s*_sonde.log" % (_id, _type)) 147 | _existing_files = glob.glob(_search_string) 148 | if len(_existing_files) != 0: 149 | # Open the existing log file. 150 | _log_file_name = _existing_files[0] 151 | self.log_info("Using existing log file: %s" % _log_file_name) 152 | # Create entry in open logs dictionary 153 | self.open_logs[_id] = {'log':open(_log_file_name,'a'), 'last_time':time.time()} 154 | else: 155 | # Create a new log file. 156 | _log_suffix = "%s_%s_%s_%d_sonde.log" % ( 157 | datetime.datetime.utcnow().strftime("%Y%m%d-%H%M%S"), 158 | _id, 159 | _type, 160 | int(telemetry['freq_float']*1e3) # Convert frequency to kHz 161 | ) 162 | _log_file_name = os.path.join(self.log_directory, _log_suffix) 163 | self.log_info("Opening new log file: %s" % _log_file_name) 164 | # Create entry in open logs dictionary 165 | self.open_logs[_id] = {'log':open(_log_file_name,'a'), 'last_time':time.time()} 166 | 167 | 168 | # Produce log file sentence. 169 | _log_line = self.telemetry_to_string(telemetry) 170 | 171 | # Write out to log. 172 | self.open_logs[_id]['log'].write(_log_line) 173 | self.open_logs[_id]['log'].flush() 174 | # Update the last_time field. 175 | self.open_logs[_id]['last_time'] = time.time() 176 | self.log_debug("Wrote line: %s" % _log_line.strip()) 177 | 178 | 179 | def cleanup_logs(self): 180 | """ Close any open logs that have not had telemetry added in X seconds. """ 181 | 182 | _now = time.time() 183 | 184 | for _id in self.open_logs.keys(): 185 | try: 186 | if _now > (self.open_logs[_id]['last_time'] + self.FILE_ACTIVITY_TIMEOUT): 187 | # Flush and close the log file, and pop this element from the dictionary. 188 | self.open_logs[_id]['log'].flush() 189 | self.open_logs[_id]['log'].close() 190 | self.open_logs.pop(_id, None) 191 | self.log_info("Closed log file for %s" % _id) 192 | except Exception as e: 193 | self.log_error("Error closing log for %s - %s" % (_id, str(e))) 194 | 195 | 196 | 197 | def close(self): 198 | """ Close input processing thread. """ 199 | self.input_processing_running = False 200 | 201 | 202 | def running(self): 203 | """ Check if the logging thread is running. 204 | 205 | Returns: 206 | bool: True if the logging thread is running. 207 | """ 208 | return self.input_processing_running 209 | 210 | 211 | def log_debug(self, line): 212 | """ Helper function to log a debug message with a descriptive heading. 213 | Args: 214 | line (str): Message to be logged. 215 | """ 216 | logging.debug("Telemetry Logger - %s" % line) 217 | 218 | 219 | def log_info(self, line): 220 | """ Helper function to log an informational message with a descriptive heading. 221 | Args: 222 | line (str): Message to be logged. 223 | """ 224 | logging.info("Telemetry Logger - %s" % line) 225 | 226 | 227 | def log_error(self, line): 228 | """ Helper function to log an error message with a descriptive heading. 229 | Args: 230 | line (str): Message to be logged. 231 | """ 232 | logging.error("Telemetry Logger - %s" % line) 233 | 234 | 235 | if __name__ == "__main__": 236 | # Test Script 237 | pass 238 | -------------------------------------------------------------------------------- /mk2a/Mk2a_9600.bytes: -------------------------------------------------------------------------------- 1 | 24 52 4D 32 EC 64 05 D7 00 38 0D 03 B9 B0 19 FA 67 11 D5 EC 19 FB 71 1F F8 57 0F FF FD 1D FD 75 11 F6 4D 03 B9 C2 03 B9 CD 03 B9 C6 03 B9 C8 03 B9 BF 29 E2 0F 0F 0F 0F 0F 0F 0F 0F 0F 0F 44 FF A7 7B 2E 20 CA CA CA CA CA CA CA CA CA CA 2 | 24 52 54 32 EC 64 05 D7 00 A2 19 94 37 BB FF EE 84 92 21 41 60 82 B8 57 DA EF 00 1B FD 3E 00 07 BD FF F9 A0 00 0E E8 00 30 0A 10 28 0F 14 00 84 35 5F 47 1C FB 32 8E 07 2E 0F 14 00 83 07 E0 E2 1C EC F4 4D 0A 2B 0F 14 00 8B EE 8C 09 1C F6 1B C8 06 26 0F 14 00 83 AF 54 27 1C F3 08 C3 08 29 0F 14 00 91 E7 40 26 1C EA F7 A2 0D 2B 0F 14 00 79 F5 EB D4 1C F4 CE C8 13 24 0F 14 00 89 DB 4B F1 1C EA 78 64 17 2B 0F 14 00 7E A0 15 A5 1C F9 BA C4 03 27 0F 14 00 80 6F 4F 86 1C EE A7 8A 05 26 0F 14 00 98 60 25 99 1C ED EE 47 B4 B5 CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA DA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA DA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA 3 | 24 52 4D 31 EC 64 05 D8 00 38 0D 03 B9 B2 19 FA 6C 11 D6 32 19 FC 86 1F F8 56 0F FF FD 1D FD 74 11 F6 4E 03 B9 BD 03 B9 D4 03 B9 C5 03 B9 BA 03 B9 BE EC 64 00 6B 00 0D 6D 07 60 13 71 56 9A CB 42 38 E4 15 CA CA CA CA CA CA CA CA CA CA 4 | 24 52 54 31 EC 64 05 D8 00 A2 19 94 3B A2 FF E5 18 43 21 41 5F 68 B8 57 D9 C6 00 1C 09 67 FF F9 0F FF F5 E4 00 0D 38 00 30 0A 10 28 0F 14 00 85 50 A5 99 1C FB 3D FF 07 2D 0F 14 00 84 21 B9 EA 1C EC EC AA 0A 2A 0F 14 00 8D 09 4E 33 1C F6 0F D2 06 27 0F 14 00 84 C9 C8 A1 1C F3 15 08 08 29 0F 14 00 93 00 E4 B4 1C EA EA 63 0D 2B 0F 14 00 7B 10 8D A8 1C F4 CE 7D 13 26 0F 14 00 8A F4 E7 A7 1C EA 7E 9A 17 2C 0F 14 00 7F BB 36 0E 1C F9 BC 21 03 28 0F 14 00 81 89 57 67 1C EE AF B3 05 24 0F 14 00 99 7A 17 46 1C ED E9 A9 34 AF CA CA CA CA CA CA CA CA CA CA 5 | 24 52 4D 32 EC 64 05 D8 00 38 0D 03 B9 B2 19 FA 6C 11 D6 32 19 FC 86 1F F8 56 0F FF FD 1D FD 74 11 F6 4E 03 B9 BD 03 B9 D4 03 B9 C5 03 B9 BA 03 B9 BE EC 64 00 6B 00 0D 6D 07 60 13 71 56 9A CB 42 38 D0 11 CA CA CA CA CA CA CA CA CA CA 6 | 24 52 54 32 EC 64 05 D8 00 A2 19 94 3B A2 FF E5 18 43 21 41 5F 68 B8 57 D9 C6 00 1C 09 67 FF F9 0F FF F5 E4 00 0D 38 00 30 0A 10 28 0F 14 00 85 50 A5 99 1C FB 3D FF 07 2D 0F 14 00 84 21 B9 EA 1C EC EC AA 0A 2A 0F 14 00 8D 09 4E 33 1C F6 0F D2 06 27 0F 14 00 84 C9 C8 A1 1C F3 15 08 08 29 0F 14 00 93 00 E4 B4 1C EA EA 63 0D 2B 0F 14 00 7B 10 8D A8 1C F4 CE 7D 13 26 0F 14 00 8A F4 E7 A7 1C EA 7E 9A 17 2C 0F 14 00 7F BB 36 0E 1C F9 BC 21 03 28 0F 14 00 81 89 57 67 1C EE AF B3 05 24 0F 14 00 99 7A 17 46 1C ED E9 A9 AE 60 CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA CA 8A 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 14 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 94 7 | 24 52 4D 31 EC 64 05 D9 00 38 0D 03 B9 B0 19 FA 70 11 D6 17 19 FF 8D 1F F8 55 0F FF FD 1D FD 75 11 F6 4F 03 B9 C9 03 B9 C6 03 B9 C4 03 B9 CF 03 B9 BE 4F FF 0C 24 30 39 39 39 C3 58 AE 7E C5 B6 2D DC 6E 68 CA CA CA CA CA CA CA CA CA CA 8 | 24 52 54 31 EC 64 05 D9 00 A2 19 94 3F 8D FF EA CE 9F 21 41 5C D7 B8 57 D8 CA 00 1C 1D 62 FF F9 ED FF E8 62 00 13 2E 00 30 0A 10 29 0F 14 00 84 A6 2E EA 1C FB 3E E4 07 2B 0F 14 00 83 75 D3 6A 1C EC EB F0 0A 26 0F 14 00 8C 5E 52 C9 1C F6 10 E3 06 29 0F 14 00 84 1E 80 83 1C F3 11 35 08 24 0F 14 00 92 54 CB 48 1C EA EA C6 0D 2B 0F 14 00 7A 65 6F A7 1C F4 C6 F1 13 2A 0F 14 00 8A 48 C1 72 1C EA 72 8C 17 2E 0F 14 00 7F 10 97 19 1C F9 AF 82 03 2B 0F 14 00 80 DD 9A FD 1C EE A7 0D 05 21 0F 14 00 98 CE 4B EC 1C ED F6 B9 20 31 CA CA CA CA CA CA CA CA CA CA 9 | 24 52 4D 32 EC 64 05 D9 00 38 0D 03 B9 B0 19 FA 70 11 D6 17 19 FF 8D 1F F8 55 0F FF FD 1D FD 75 11 F6 4F 03 B9 C9 03 B9 C6 03 B9 C4 03 B9 CF 03 B9 BE 4F FF 0C 24 30 39 39 39 C3 58 AE 7E C5 B6 2D DC 5A 6C CA CA CA CA CA CA CA CA CA CA 10 | 24 52 54 32 EC 64 05 D9 00 A2 19 94 3F 8D FF EA CE 9F 21 41 5C D7 B8 57 D8 CA 00 1C 1D 62 FF F9 ED FF E8 62 00 13 2E 00 30 0A 10 29 87 8A 80 C2 D3 97 F5 8E FD 9F F2 83 95 87 8A 80 C1 BA E9 B5 8E F6 F5 F8 85 93 87 8A 80 C6 AF A9 E4 8E FB 88 F1 83 94 87 8A 80 C2 8F C0 C1 8E F9 88 9A 84 92 87 8A 80 C9 AA E5 A4 8E F5 F5 E3 86 95 87 8A 80 BD B2 B7 D3 8E FA E3 F8 89 95 87 8A 80 C5 A4 E0 B9 8E F5 B9 C6 8B 97 87 8A 80 BF 88 CB 8C 8E FC D7 C1 81 95 87 45 40 60 77 66 7F 47 7B 69 43 41 48 43 45 40 66 73 52 7B 47 7B 7D 6E 6E 7F 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 73 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 72 11 | 24 52 4D 31 EC 64 05 DA 00 38 0D 03 B9 AC 19 FA 77 11 D6 26 17 D7 F0 1F F8 53 0F FF FC 1D FD 76 11 F6 4C 03 B9 C7 03 B9 C7 03 B9 C0 03 B9 C4 03 B9 C1 46 37 75 53 C5 81 77 A5 44 BD 52 E9 C0 C8 8D CE 9A 60 CA CA CA CA CA CA CA CA CA CA 12 | 24 52 54 31 EC 64 05 DA 00 A2 19 94 43 74 FF E1 62 52 21 41 59 18 B8 57 DA 6B 00 1C 32 15 00 0A 22 FF DD 15 00 13 1B 00 30 0A 10 2B 0F 14 00 85 C1 74 F1 1C FB 3B 6E 07 28 0F 14 00 84 8F AD 45 1C EC F9 A6 0A 23 0F 14 00 8D 79 16 39 1C F6 22 3B 06 2B 0F 14 00 85 38 F4 D8 1C F3 06 35 08 22 0F 14 00 93 6E 71 B9 1C EA FF 9A 86 95 87 8A 80 BD C0 88 AE 8E FA 73 45 44 4A 43 45 40 62 58 5E 58 47 7A 58 52 45 4B 43 45 40 60 4A 6D 5B 47 7E 69 79 40 4B 43 45 40 60 7D 67 69 47 7B 66 4A 41 48 43 45 40 66 7A 50 52 47 7B 42 62 4C 4E 72 72 72 72 72 72 72 72 72 13 | 24 52 4D 32 EC 64 05 DA 00 38 0D 03 B9 AC 19 FA 77 11 D6 26 17 D7 F0 1F F8 53 0F FF FC 1D FD 76 11 F6 4C 03 B9 C7 03 B9 C7 03 B9 C0 03 B9 C4 03 B9 C1 46 37 75 53 C5 81 77 A5 44 BD 52 E9 C0 C8 8D CE AE 64 CA CA CA CA CA CA CA CA CA CA 14 | 24 52 54 32 EC 64 05 DA 00 A2 19 94 43 74 FF E1 62 52 21 41 59 18 B8 57 DA 6B 00 1C 32 15 00 0A 22 FF DD 15 00 13 1B 00 30 0A 10 2B 0F 14 00 85 C1 74 F1 1C FB 3B 6E 07 28 0F 14 00 84 8F AD 45 1C EC F9 A6 0A 23 0F 14 00 8D 79 16 39 1C F6 22 3B 06 2B 0F 14 00 85 38 F4 D8 1C F3 06 35 08 22 0F 14 00 93 6E 71 B9 1C EA FD 35 0D 2B 0F 14 00 7B 80 11 5C 1C F4 C7 14 13 2B 0F 14 00 8B 62 59 62 1C EA 62 4B 17 2E 0F 15 | -------------------------------------------------------------------------------- /rs_module/rs_main92.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "rs_data.h" 9 | #include "rs_demod.h" 10 | #include "rs_datum.h" 11 | #include "rs_rs92.h" 12 | 13 | 14 | int option_verbose = 0, // ausfuehrliche Anzeige 15 | option_raw = 0, // rohe Frames 16 | option_inv = 0, // invertiert Signal 17 | option_crc = 0, // check CRC 18 | option_sat = 0, // GPS sat data 19 | wavloaded = 0; 20 | option_csv = 0; // Print output as CSV 21 | 22 | 23 | ui8_t *frame = NULL; 24 | 25 | int print_position(rs_data_t *rs_data) { 26 | 27 | // option_crc: check block-crc 28 | 29 | fprintf(stdout, "[%5d] ", rs_data->frnr); 30 | 31 | if ( option_crc==0 || ( option_crc && (rs_data->crc & 0x7)==0 ) ) 32 | { 33 | fprintf(stdout, "(%s) ", rs_data->SN); 34 | 35 | fprintf(stdout, "%s ", weekday[rs_data->wday]); 36 | fprintf(stdout, "%04d-%02d-%02d %02d:%02d:%06.3f", 37 | rs_data->year, rs_data->month, rs_data->day, 38 | rs_data->hr, rs_data->min, rs_data->sec); 39 | if (option_verbose) fprintf(stdout, " (W %d)", (rs_data->GPS).week); 40 | 41 | fprintf(stdout, " "); 42 | fprintf(stdout, " lat: %.5f ", (rs_data->GPS).lat); 43 | fprintf(stdout, " lon: %.5f ", (rs_data->GPS).lon); 44 | fprintf(stdout, " alt: %.2f ", (rs_data->GPS).alt); 45 | fprintf(stdout," vH: %4.1f D: %5.1f° vV: %3.1f ", (rs_data->GPS).vH, (rs_data->GPS).vD, (rs_data->GPS).vU); 46 | } 47 | int i; 48 | fprintf(stdout, " # ["); 49 | for (i=0; i<4; i++) fprintf(stdout, "%d", (rs_data->crc>>i)&1); 50 | fprintf(stdout, "]"); 51 | 52 | if (rs_data->ecc >= 0) fprintf(stdout, " [OK]"); else fprintf(stdout, " [NO]"); 53 | if (rs_data->ecc > 0) fprintf(stdout, " (%d)", rs_data->ecc); 54 | 55 | fprintf(stdout, "\n"); 56 | 57 | return 0; 58 | } 59 | 60 | // VK5QI Addition - Print data as easily parseable CSV. 61 | int print_position_csv(rs_data_t *rs_data) { 62 | 63 | // option_crc: check block-crc 64 | 65 | if ( option_crc==0 || ( option_crc && (rs_data->crc & 0x7)==0 ) ) 66 | { 67 | fprintf(stdout, "%5d,", rs_data->frnr); 68 | fprintf(stdout, "%s,", rs_data->SN); 69 | 70 | //fprintf(stdout, "%s,", weekday[rs_data->wday]); 71 | fprintf(stdout, "%04d-%02d-%02d,%02d:%02d:%06.3f,", 72 | rs_data->year, rs_data->month, rs_data->day, 73 | rs_data->hr, rs_data->min, rs_data->sec); 74 | //if (option_verbose) fprintf(stdout, " (W %d)", (rs_data->GPS).week); 75 | fprintf(stdout, "%.5f,", (rs_data->GPS).lat); 76 | fprintf(stdout, "%.5f,", (rs_data->GPS).lon); 77 | fprintf(stdout, "%.2f,", (rs_data->GPS).alt); 78 | fprintf(stdout,"%4.1f,%5.1f,%3.1f,", (rs_data->GPS).vH, (rs_data->GPS).vD, (rs_data->GPS).vU); 79 | if (rs_data->ecc >= 0) fprintf(stdout, "OK"); else fprintf(stdout, "FAIL"); 80 | //if (rs_data->ecc > 0) fprintf(stdout, " (%d)", rs_data->ecc); 81 | 82 | fprintf(stdout, "\n"); 83 | } 84 | return 0; 85 | } 86 | 87 | void print_frame(rs_data_t *rs_data) { 88 | int i; 89 | 90 | if (!option_raw && option_verbose) fprintf(stdout, "\n"); // fflush(stdout); 91 | 92 | (rs_data->rs_process)(rs_data, option_raw, option_verbose); 93 | 94 | if (option_raw) { 95 | for (i = 0; i < rs_data->pos; i++) { 96 | fprintf(stdout, "%02x", rs_data->frame_bytes[i]); 97 | } 98 | 99 | if (rs_data->ecc >= 0) fprintf(stdout, " [OK]"); else fprintf(stdout, " [NO]"); 100 | if (rs_data->ecc > 0) fprintf(stdout, " (%d)", rs_data->ecc); 101 | 102 | fprintf(stdout, "\n"); 103 | } 104 | else if (option_csv){ 105 | print_position_csv(rs_data); 106 | }else{ 107 | print_position(rs_data); 108 | } 109 | } 110 | 111 | int main(int argc, char *argv[]) { 112 | 113 | FILE *fp = NULL; 114 | char *fpname = NULL; 115 | char *bitbuf = NULL; 116 | int bit_count = 0, 117 | header_found = 0, 118 | frmlen = 0; 119 | int i, bit, len; 120 | int err = 0; 121 | 122 | int orbdata = 0; 123 | char *eph_file = NULL; 124 | 125 | setbuf(stdout, NULL); 126 | 127 | fpname = argv[0]; 128 | ++argv; 129 | while ((*argv) && (!wavloaded)) { 130 | if ( (strcmp(*argv, "-h") == 0) || (strcmp(*argv, "--help") == 0) ) { 131 | fprintf(stderr, "%s [options] audio.wav\n", fpname); 132 | fprintf(stderr, " options:\n"); 133 | fprintf(stderr, " -v, -vx, -vv (info, aux, info/conf)\n"); 134 | fprintf(stderr, " -r, --raw\n"); 135 | fprintf(stderr, " -i, --invert\n"); 136 | fprintf(stderr, " --crc (check CRC)\n"); 137 | fprintf(stderr, " --std (std framelen)\n"); 138 | fprintf(stderr, " --csv (output as CSV)\n"); 139 | return 0; 140 | } 141 | else if ( (strcmp(*argv, "-v") == 0) || (strcmp(*argv, "--verbose") == 0) ) { 142 | option_verbose |= 0x1; 143 | } 144 | else if (strcmp(*argv, "-vx") == 0) { option_verbose |= 0x2; } 145 | else if (strcmp(*argv, "-vv") == 0) { option_verbose |= 0x3; } 146 | else if (strcmp(*argv, "--crc") == 0) { option_crc = 1; } 147 | else if (strcmp(*argv, "--csv") == 0) { option_csv = 1; } 148 | else if ( (strcmp(*argv, "-r") == 0) || (strcmp(*argv, "--raw") == 0) ) { 149 | option_raw = 1; 150 | } 151 | else if ( (strcmp(*argv, "-i") == 0) || (strcmp(*argv, "--invert") == 0) ) { 152 | option_inv = 1; 153 | } 154 | else if (strcmp(*argv, "--sat") == 0) { option_sat = 1; option_verbose |= 0x100; } 155 | else if ( (strcmp(*argv, "-a") == 0) || (strcmp(*argv, "--almanac") == 0) ) { 156 | ++argv; 157 | eph_file = *argv; 158 | if (eph_file) orbdata = 1; 159 | else return -1; 160 | } 161 | else if ( (strcmp(*argv, "-e") == 0) || (strncmp(*argv, "--ephem", 7) == 0) ) { 162 | ++argv; 163 | eph_file = *argv; 164 | if (eph_file) orbdata = 2; 165 | else return -1; 166 | } 167 | else { 168 | fp = fopen(*argv, "rb"); 169 | if (fp == NULL) { 170 | fprintf(stderr, "%s konnte nicht geoeffnet werden\n", *argv); 171 | return -1; 172 | } 173 | wavloaded = 1; 174 | } 175 | ++argv; 176 | } 177 | if (!wavloaded) fp = stdin; 178 | 179 | 180 | rs_data_t rs92data = {{0}}; 181 | rs_data_t *rs_data = &rs92data; 182 | rs_data->input = 8; 183 | err = init_rs92data(rs_data, orbdata, eph_file); 184 | if (err) goto error_tag; 185 | frame = rs_data->frame_bytes; 186 | if (frmlen == 0) frmlen = rs_data->frame_len; 187 | 188 | err = read_wav_header(fp, rs_data); 189 | if (err) goto error_tag; 190 | 191 | bitbuf = calloc(rs_data->bits, 1); 192 | if (bitbuf == NULL) { 193 | err = -1; 194 | goto error_tag; 195 | } 196 | 197 | rs_data->pos = rs_data->frame_start; 198 | 199 | while (!read_bits_fsk(fp, &bit, &len, option_inv)) { 200 | 201 | if (len == 0) { // reset_frame(); 202 | if (rs_data->pos > rs_data->pos_min) { 203 | print_frame(rs_data); 204 | /* 205 | bit_count = 0; 206 | rs_data->pos = rs_data->frame_start; 207 | header_found = 0; 208 | */ 209 | } 210 | bit_count = 0; 211 | rs_data->pos = rs_data->frame_start; 212 | header_found = 0; 213 | //inc_bufpos(rs_data); 214 | //buf[bufpos] = 'x'; 215 | continue; // ... 216 | } 217 | 218 | for (i = 0; i < len; i++) { 219 | 220 | inc_bufpos(rs_data); 221 | rs_data->buf[rs_data->bufpos] = 0x30 + bit; // Ascii 222 | 223 | if (!header_found) { 224 | if (compare(rs_data) >= rs_data->header_len) header_found = 1; 225 | } 226 | else { 227 | 228 | if (rs_data->input < 8) { 229 | rs_data->frame_rawbits[rs_data->bits*rs_data->pos + bit_count] = 0x30 + bit; 230 | } 231 | 232 | bitbuf[bit_count] = bit; 233 | bit_count++; 234 | 235 | if (bit_count == rs_data->bits) { 236 | bit_count = 0; 237 | if (rs_data->input == 8) { 238 | frame[rs_data->pos] = rs_data->bits2byte(rs_data, bitbuf); 239 | } 240 | rs_data->pos++; 241 | if (rs_data->pos == frmlen) { 242 | print_frame(rs_data); 243 | rs_data->pos = rs_data->frame_start; 244 | header_found = 0; 245 | } 246 | } 247 | } 248 | 249 | } 250 | } 251 | 252 | free(bitbuf); 253 | bitbuf = NULL; 254 | 255 | error_tag: 256 | fclose(fp); 257 | free_rs92data(rs_data); 258 | 259 | return err; 260 | } 261 | 262 | -------------------------------------------------------------------------------- /auto_rx/utils/log_to_kml.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Radiosonde Auto RX Tools 4 | # Log-to-KML Utilities 5 | # 6 | # 2018-02 Mark Jessop 7 | # 8 | # Note: This utility requires the fastkml and shapely libraries, which can be installed using: 9 | # sudo pip install fastkml shapely 10 | # 11 | 12 | import sys 13 | import time 14 | import datetime 15 | import traceback 16 | import argparse 17 | import glob 18 | import os 19 | import fastkml 20 | from dateutil.parser import * 21 | from shapely.geometry import Point, LineString 22 | 23 | def read_telemetry_csv(filename, 24 | datetime_field = 0, 25 | latitude_field = 3, 26 | longitude_field = 4, 27 | altitude_field = 5, 28 | delimiter=','): 29 | ''' 30 | Read in a radiosonde_auto_rx generated telemetry CSV file. 31 | Fields to use can be set as arguments to this function. 32 | These have output like the following: 33 | 2017-12-27T23:21:59.560,M2913374,982,-34.95143,138.52471,719.9,-273.0,RS92,401.520 34 | ,,,,,,,, 35 | 36 | Note that the datetime field must be parsable by dateutil.parsers.parse. 37 | 38 | If any fields are missing, or invalid, this function will return None. 39 | 40 | The output data structure is in the form: 41 | [ 42 | [datetime (as a datetime object), latitude, longitude, altitude, raw_line], 43 | [datetime (as a datetime object), latitude, longitude, altitude, raw_line], 44 | ... 45 | ] 46 | ''' 47 | 48 | output = [] 49 | 50 | f = open(filename,'r') 51 | 52 | for line in f: 53 | try: 54 | # Split line by comma delimiters. 55 | _fields = line.split(delimiter) 56 | 57 | # Attempt to parse fields. 58 | _datetime = parse(_fields[datetime_field]) 59 | _latitude = float(_fields[latitude_field]) 60 | _longitude = float(_fields[longitude_field]) 61 | _altitude = float(_fields[altitude_field]) 62 | 63 | output.append([_datetime, _latitude, _longitude, _altitude, line]) 64 | except: 65 | traceback.print_exc() 66 | return None 67 | 68 | f.close() 69 | 70 | return output 71 | 72 | 73 | def flight_burst_position(flight_path): 74 | ''' Search through flight data for the burst position and return it. ''' 75 | 76 | # Read through array and hunt for max altitude point. 77 | current_alt = 0.0 78 | current_index = 0 79 | for i in range(len(flight_path)): 80 | if flight_path[i][3] > current_alt: 81 | current_alt = flight_path[i][3] 82 | current_index = i 83 | 84 | return flight_path[current_index] 85 | 86 | 87 | ns = '{http://www.opengis.net/kml/2.2}' 88 | 89 | def new_placemark(lat, lon, alt, 90 | placemark_id="Placemark ID", 91 | name="Placemark Name", 92 | absolute = False, 93 | icon = "http://maps.google.com/mapfiles/kml/shapes/placemark_circle.png", 94 | scale = 1.0): 95 | """ Generate a generic placemark object """ 96 | 97 | if absolute: 98 | _alt_mode = 'absolute' 99 | else: 100 | _alt_mode = 'clampToGround' 101 | 102 | flight_icon_style = fastkml.styles.IconStyle( 103 | ns=ns, 104 | icon_href=icon, 105 | scale=scale) 106 | 107 | flight_style = fastkml.styles.Style( 108 | ns=ns, 109 | styles=[flight_icon_style]) 110 | 111 | flight_placemark = fastkml.kml.Placemark( 112 | ns=ns, 113 | id=placemark_id, 114 | name=name, 115 | description="", 116 | styles=[flight_style]) 117 | 118 | flight_placemark.geometry = fastkml.geometry.Geometry( 119 | ns=ns, 120 | geometry=Point(lon, lat, alt), 121 | altitude_mode=_alt_mode) 122 | 123 | return flight_placemark 124 | 125 | 126 | 127 | def flight_path_to_geometry(flight_path, 128 | placemark_id="Flight Path ID", 129 | name="Flight Path Name", 130 | track_color="aaffffff", 131 | poly_color="20000000", 132 | track_width=2.0, 133 | absolute = True, 134 | extrude = True, 135 | tessellate = True): 136 | ''' Produce a fastkml geometry object from a flight path array ''' 137 | 138 | # Handle selection of absolute altitude mode 139 | if absolute: 140 | _alt_mode = 'absolute' 141 | else: 142 | _alt_mode = 'clampToGround' 143 | 144 | # Convert the flight path array [time, lat, lon, alt, comment] into a LineString object. 145 | track_points = [] 146 | for _point in flight_path: 147 | # Flight path array is in lat,lon,alt order, needs to be in lon,lat,alt 148 | track_points.append([_point[2],_point[1],_point[3]]) 149 | 150 | _flight_geom = LineString(track_points) 151 | 152 | # Define the Line and Polygon styles, which are used for the flight path, and the extrusions (if enabled) 153 | flight_track_line_style = fastkml.styles.LineStyle( 154 | ns=ns, 155 | color=track_color, 156 | width=track_width) 157 | 158 | flight_extrusion_style = fastkml.styles.PolyStyle( 159 | ns=ns, 160 | color=poly_color) 161 | 162 | flight_track_style = fastkml.styles.Style( 163 | ns=ns, 164 | styles=[flight_track_line_style, flight_extrusion_style]) 165 | 166 | # Generate the Placemark which will contain the track data. 167 | flight_line = fastkml.kml.Placemark( 168 | ns=ns, 169 | id=placemark_id, 170 | name=name, 171 | styles=[flight_track_style]) 172 | 173 | # Add the track data to the Placemark 174 | flight_line.geometry = fastkml.geometry.Geometry( 175 | ns=ns, 176 | geometry=_flight_geom, 177 | altitude_mode=_alt_mode, 178 | extrude=extrude, 179 | tessellate=tessellate) 180 | 181 | return flight_line 182 | 183 | 184 | def write_kml(geom_objects, 185 | filename="output.kml", 186 | comment=""): 187 | """ Write out flight path geometry objects to a kml file. """ 188 | 189 | kml_root = fastkml.kml.KML() 190 | kml_doc = fastkml.kml.Document( 191 | ns=ns, 192 | name=comment) 193 | 194 | if type(geom_objects) is not list: 195 | geom_objects = [geom_objects] 196 | 197 | for _flight in geom_objects: 198 | kml_doc.append(_flight) 199 | 200 | with open(filename,'w') as kml_file: 201 | kml_file.write(kml_doc.to_string()) 202 | kml_file.close() 203 | 204 | 205 | def convert_single_file(filename, absolute=True, tessellate=True, last_only=False): 206 | ''' Convert a single sonde log file to a fastkml KML Folder object ''' 207 | 208 | # Read file. 209 | _flight_data = read_telemetry_csv(filename) 210 | 211 | # Extract the flight's serial number and launch time from the first line in the file. 212 | _first_line = _flight_data[0][4] 213 | _flight_serial = _first_line.split(',')[1] # Serial number is the second field in the line. 214 | _launch_time = _flight_data[0][0].strftime("%Y%m%d-%H%M%SZ") 215 | # Generate a comment line to use in the folder and placemark descriptions 216 | _track_comment = "%s %s" % (_launch_time, _flight_serial) 217 | _landing_comment = "%s Last Position" % (_flight_serial) 218 | 219 | # Grab burst and last-seen positions 220 | _burst_pos = flight_burst_position(_flight_data) 221 | _landing_pos = _flight_data[-1] 222 | 223 | # Generate the placemark & flight track. 224 | _flight_geom = flight_path_to_geometry(_flight_data, name=_track_comment, absolute=absolute, tessellate=tessellate, extrude=tessellate) 225 | _landing_geom = new_placemark(_landing_pos[1], _landing_pos[2], _landing_pos[3], name=_landing_comment, absolute=absolute) 226 | 227 | _folder = fastkml.kml.Folder(ns, _flight_serial, _track_comment, 'Radiosonde Flight Path') 228 | if last_only == False: 229 | _folder.append(_flight_geom) 230 | _folder.append(_landing_geom) 231 | 232 | return _folder 233 | 234 | 235 | if __name__ == "__main__": 236 | parser = argparse.ArgumentParser() 237 | parser.add_argument("-i", "--input", type=str, default="../log/*_sonde.log", 238 | help="Path to log file. May include wildcards, though the path must be wrapped in quotes. Default=../log/*_sonde.log") 239 | parser.add_argument("-o", "--output", type=str, default="sondes.kml", help="KML output file name. Default=sondes.kml") 240 | parser.add_argument('--clamp', action="store_false", default=True, help="Clamp tracks to ground instead of showing absolute altitudes.") 241 | parser.add_argument('--noextrude', action="store_false", default=True, help="Disable Extrusions for absolute flight paths.") 242 | parser.add_argument('--lastonly', action="store_true", default=False, help="Only plot last-seen sonde positions, not the flight paths.") 243 | args = parser.parse_args() 244 | 245 | _file_list = glob.glob(args.input) 246 | 247 | _placemarks = [] 248 | 249 | for _file in _file_list: 250 | print("Processing: %s" % _file) 251 | try: 252 | _placemarks.append(convert_single_file(_file, absolute=args.clamp, tessellate=args.noextrude, last_only=args.lastonly)) 253 | except: 254 | print("Failed to process: %s" % _file) 255 | 256 | write_kml(_placemarks, filename=args.output) 257 | 258 | print("Output saved to: %s" % args.output) 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | -------------------------------------------------------------------------------- /auto_rx/station.cfg.example: -------------------------------------------------------------------------------- 1 | # 2 | # Radiosonde Auto RX v2 Station Configuration File 3 | # 4 | # Copy this file to station.cfg and modify as required. 5 | # 6 | 7 | # Logging Settings 8 | [logging] 9 | # If enabled, a log file will be written to ./log/ for each detected radiosonde. 10 | per_sonde_log = True 11 | 12 | # RTLSDR Receiver Settings 13 | [sdr] 14 | 15 | # Number of RTLSDRs to use. 16 | # If more than one RTLSDR is in use, multiple [sdr_X] sections must be populated below 17 | sdr_quantity = 1 18 | 19 | # Individual SDR Settings. 20 | [sdr_1] 21 | # Device Index / Serial 22 | # If using a single RTLSDR, set this value to 0 23 | # If using multiple SDRs, you MUST allocate each SDR a unique serial number using rtl_eeprom 24 | # i.e. to set the serial number of a (single) connected RTLSDR: rtl_eeprom -s 00000002 25 | # Then set the device_idx below to 00000002, and repeat for the other [sdr_n] sections below 26 | device_idx = 0 27 | 28 | # Frequency Correction (ppm offset) 29 | # Refer here for a method of determining this correction: https://gist.github.com/darksidelemm/b517e6a9b821c50c170f1b9b7d65b824 30 | ppm = 0 31 | 32 | # SDR Gain Setting 33 | # Gain settings can generally range between 0dB and 40dB depending on the tuner in use. 34 | # Run rtl_test to confirm what gain settings are available, or use a value of -1 to use automatic gain control. 35 | # Note that this is an overall gain value, not an individual mixer/tuner gain. This is a limitation of the rtl_power/rtl_fm utils. 36 | gain = -1 37 | 38 | # Bias Tee - Enable the bias tee in the RTLSDR v3 Dongles. 39 | bias = False 40 | 41 | [sdr_2] 42 | # As above, for the next SDR, if used. Note the warning about serial numbers. 43 | device_idx = 00000002 44 | ppm = 0 45 | gain = -1 46 | bias = False 47 | 48 | # Add more SDR definitions here if needed. 49 | 50 | 51 | # Radiosonde Search Parameters 52 | [search_params] 53 | # Minimum and maximum search frequencies, in MHz. 54 | # Australia: Use 400.05 - 403 MHz 55 | # Europe: Use 400.05 - 406 MHz 56 | min_freq = 400.05 57 | max_freq = 403.0 58 | # Have the decoder timeout after X seconds of no valid data. 59 | rx_timeout = 180 60 | 61 | # Frequency Lists - These must be provided as JSON-compatible lists of floats (in MHz), i.e. [400.50, 401.520, 403.200] 62 | 63 | # White-List - Add values to this list to *only* scan on these frequencies. 64 | # This is for when you only want to monitor a small set of launch frequencies. 65 | whitelist = [] 66 | 67 | # Black-List - Any values added to this list will be removed from the list of detected peaks. 68 | # This is used to remove known spurs or other interferers from the scan list, potentially speeding up detection of a sonde. 69 | blacklist = [] 70 | 71 | # Grey-List - Any values in this list will be added to the start every scan run. 72 | # This is useful when you know the regular frequency of a local sonde, but still want to allow detections on other frequencies. 73 | greylist = [] 74 | 75 | 76 | 77 | # Settings for uploading to the Habitat HAB tracking database ( https://tracker.habhub.org/ ) 78 | # Note that the habitat upload will use a fixed string format of: 79 | # `$$,,