├── .gitmodules ├── .gitignore ├── Cargo.toml ├── LICENSE ├── src ├── lib.rs ├── complex.c ├── dsp.rs ├── main.rs └── usage.rs ├── README.md └── Cargo.lock /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "doppler" 3 | version = "1.1.10" 4 | authors = ["Andres Vahter "] 5 | build = "build.rs" 6 | 7 | [dependencies] 8 | clap = "*" 9 | libc = "*" 10 | time = "*" 11 | num = "*" 12 | log = "*" 13 | 14 | [dependencies.gpredict] 15 | git = "https://github.com/cubehub/rust-gpredict.git" 16 | 17 | [dependencies.liquid_dsp] 18 | git = "https://github.com/cubehub/rust-liquid-dsp.git" 19 | 20 | [dependencies.fern] 21 | git = "https://github.com/marjakm/fern-rs.git" 22 | 23 | [build-dependencies] 24 | gcc = "*" 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Andres Vahter (andres.vahter@gmail.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2015 Andres Vahter (andres.vahter@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | extern crate libc; 26 | extern crate time; 27 | extern crate num; 28 | 29 | #[macro_use] extern crate log; 30 | #[macro_use] extern crate clap; 31 | extern crate gpredict; 32 | extern crate liquid_dsp; 33 | 34 | pub mod usage; 35 | pub mod dsp; 36 | -------------------------------------------------------------------------------- /src/complex.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2015 Andres Vahter (andres.vahter@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | #include 26 | #include 27 | 28 | typedef struct { 29 | float real; 30 | float imag; 31 | } RustComplex; 32 | 33 | void ccexpf(RustComplex* a) { 34 | float complex input = a->real + a->imag * I; 35 | float complex cout = cexpf(input); 36 | RustComplex rout = {.real=creal(cout), .imag=cimag(cout)}; 37 | a->real = creal(cout); 38 | a->imag = cimag(cout); 39 | } 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # doppler 2 | Command line utility that takes IQ data stream as input and produces doppler corrected output stream based on TLE. 3 | Firstly it was written in C ([last commit to C version](https://github.com/cubehub/doppler/commit/e6df4d271ece09a88b8dba9b054bb10bdcb996ce)), however now it is rewritten in [rust](http://www.rust-lang.org). 4 | 5 | ## dependencies 6 | #### mac os x 7 | xcode-select --install 8 | brew install autoconf 9 | brew install automake 10 | 11 | #### linux 12 | sudo apt-get install autoconf 13 | 14 | #### libgpredict 15 | Follow install instructions from here: https://github.com/cubehub/libgpredict 16 | 17 | #### [liquid-dsp](https://github.com/jgaeddert/liquid-dsp) 18 | git clone git://github.com/jgaeddert/liquid-dsp.git 19 | cd liquid-dsp 20 | ./bootstrap.sh 21 | ./configure 22 | make 23 | sudo make install 24 | 25 | #### rust 26 | http://www.rust-lang.org/install.html 27 | 28 | curl -sSf https://static.rust-lang.org/rustup.sh | sh 29 | 30 | ## build 31 | git clone https://github.com/cubehub/doppler.git 32 | cd doppler 33 | cargo build --release 34 | 35 | ## install 36 | #### mac os x 37 | cp target/release/doppler /usr/local/bin/ 38 | 39 | #### linux 40 | sudo cp target/release/doppler /usr/local/bin/ 41 | 42 | ## usage 43 | #### help 44 | doppler -h 45 | doppler track -h 46 | doppler const -h 47 | 48 | #### realtime 49 | Do realtime doppler correction to ESTCube-1 satellite that transmits on 437.505 MHz and write output to a file. 50 | Notice that `rtl_fm` is tuned to 437.500 MHz, but ESTCube-1 transmits on 437.505 MHz, therefore 5000 Hz constant offset correction is also added with `--offset` parameter. It can be omitted if there is no offset. 51 | 52 | 53 | rtl_fm -f 437.5M -s 1024000 -g 20 -M raw - | doppler track -s 1024000 -i i16 --tlefile cubesat.txt --tlename 'ESTCUBE 1' --location lat=58.26541,lon=26.46667,alt=76 --frequency 437505000 --offset 5000 > zero.iq 54 | 55 | #### recording 56 | Do doppler correction to a file that is recorded before. For example someone has recorded an overpass and you would like to convert it to another file where doppler compensation has been made. 57 | If parameter `--time` is specified it does doppler correction based on this time instead of real time. It denotes start time of the recording in UTC. 58 | 59 | cat last_overpass_256000sps_i16.iq | doppler track -s 256000 -i i16 --tlefile cubesat.txt --tlename 'ESTCUBE 1' --location lat=58.26541,lon=26.46667,alt=76 --frequency 437505000 --offset -2500 --time 2015-01-22T09:07:16 > zero_overpass.iq 60 | 61 | sox -t wav last_overpass.wav -esigned-integer -b16 -r 300000 -t raw - | doppler track -s 300000 -i i16 --tlefile cubesat.txt --tlename 'ESTCUBE 1' --location lat=58.26541,lon=26.46667,alt=76 --frequency 437505000 --offset -2500 --time 2015-01-22T09:07:16 > zero_overpass.iq 62 | 63 | Notice that if dealing with old files you also have to use TLEs from that day, otherwise doppler correction result might be off. Here offset compensation of -2500 Hz is used only for example purposes. 64 | 65 | #### baseband shifting 66 | It is also possible to just shift baseband signal "left" or "right" using `const` mode. In this example input signal has float IQ data format therefore `-i f32` is used. However output is converted to int16 IQ data format using `-o i16`. 67 | 68 | cat baseband_256000sps_f32.iq | doppler const -s 256000 -i f32 --shift -15000 -o i16 > shifted_baseband_256000sps_i16.iq 69 | -------------------------------------------------------------------------------- /src/dsp.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2015 Andres Vahter (andres.vahter@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | use liquid_dsp::LiquidComplex32; 26 | 27 | use num::complex::Complex; 28 | use std::mem; 29 | 30 | // Rust does not support C complex numbers in the same way on 32 and 64 bit platforms: 31 | // https://github.com/rust-lang/rfcs/issues/793 32 | // Therefore this workaround is needed. 33 | // 34 | // Type `LiquidComplex32` is used here because standard `num::complex::Complex` does not have #[repr(C)]. 35 | // Function `ccexpf` is implemented in complex.c 36 | // 37 | // Notice that `ccexpf` does not return value, it just changes `z` value in place. 38 | // For some reson it did not work correctly on ARM (BeagleBoneBlack) if `ccexpf` returned 39 | // calculated `LiquidComplex32` struct. Although it worked on Mac OS X and 64/32 bit X86 Ubuntu. 40 | extern { 41 | pub fn ccexpf(z: *mut LiquidComplex32); 42 | } 43 | 44 | use std::f32::consts::PI; 45 | 46 | #[cfg(test)] 47 | use std; 48 | 49 | #[cfg(test)] 50 | fn assert_eq_delta(a: f32, b: f32, delta: f32) { 51 | let relative_error = ((a - b) / b).abs(); 52 | if relative_error >= delta { 53 | panic!("`(left == right)` (left: `{}`, right: `{}`)'", a, b); 54 | } 55 | } 56 | 57 | #[test] 58 | fn test_cexpf() { 59 | // cargo test -- --nocapture 60 | // to see prints 61 | 62 | let mut a = Complex::::new(0.0, 0.0); 63 | unsafe {ccexpf(mem::transmute(&mut a))}; 64 | assert_eq_delta(a.re, 1.0, 0.000001); 65 | assert_eq_delta(a.im, 0.0, 0.000001); 66 | 67 | let mut a = Complex::::new(1.0, 1.0); 68 | unsafe {ccexpf(mem::transmute(&mut a))}; 69 | assert_eq_delta(a.re, 1.468694, 0.000001); 70 | assert_eq_delta(a.im, 2.2873552, 0.000001); 71 | 72 | let mut a = Complex::::new(70.0, 70.0); 73 | unsafe {ccexpf(mem::transmute(&mut a))}; 74 | assert_eq_delta(a.re, 1593075600000000000000000000000f32, 0.000001); 75 | assert_eq_delta(a.im, 1946674600000000000000000000000f32, 0.000001); 76 | 77 | let mut a = Complex::::new(1_000_000.0, 1_000_000.0); 78 | unsafe {ccexpf(mem::transmute(&mut a))}; 79 | assert_eq!(a.re, std::f32::INFINITY); 80 | assert_eq!(a.im, -std::f32::INFINITY); 81 | 82 | //println!("a={:?}", a); 83 | } 84 | 85 | pub fn convert_iqi16_to_complex(inbuf: &[u8]) -> Vec> { 86 | // inbuf consists of i16 IQ pairs that are represented as bytes here 87 | assert!(inbuf.len() % 4 == 0); 88 | 89 | let mut output = Vec::>::with_capacity(inbuf.len()/8); 90 | 91 | for b in inbuf.chunks(4) { 92 | let i: f32 = ((b[1] as i16) << 8 | b[0] as i16) as f32 / 32768.; 93 | let q: f32 = ((b[3] as i16) << 8 | b[2] as i16) as f32 / 32768.; 94 | 95 | output.push(Complex::::new(i, q)); 96 | } 97 | 98 | output 99 | } 100 | 101 | pub fn convert_iqf32_to_complex(inbuf: &[u8]) -> Vec> { 102 | // inbuf consists of f32 IQ pairs that are represented as bytes here 103 | assert!(inbuf.len() % 8 == 0); 104 | 105 | let mut output = Vec::>::with_capacity(inbuf.len()/8); 106 | 107 | for b in inbuf.chunks(8) { 108 | let i: f32 = unsafe {mem::transmute::(((b[3] as u32) << 24) | ((b[2] as u32) << 16) | ((b[1] as u32) << 8) | b[0] as u32)}; 109 | let q: f32 = unsafe {mem::transmute::(((b[7] as u32) << 24) | ((b[6] as u32) << 16) | ((b[5] as u32) << 8) | b[4] as u32)}; 110 | 111 | output.push(Complex::::new(i, q)); 112 | } 113 | 114 | output 115 | } 116 | 117 | pub fn shift_frequency(inbuf: &[Complex], samplenum: &mut u32, shift_hz: f32, samplerate: u32) -> Vec> { 118 | let mut output = Vec::>::with_capacity(inbuf.len()); 119 | 120 | for sample in inbuf { 121 | let mut corrector = Complex::::new(0.0, -2. * PI * (shift_hz / samplerate as f32 * (*samplenum) as f32) as f32); 122 | unsafe { ccexpf(mem::transmute(&mut corrector))}; 123 | output.push(sample * corrector); 124 | 125 | if (shift_hz / samplerate as f32 * *samplenum as f32).fract() == 0.0 { 126 | *samplenum = 1; 127 | } 128 | else { 129 | *samplenum += 1; 130 | } 131 | } 132 | 133 | output 134 | } 135 | 136 | #[test] 137 | fn test_bench_shift_frequency() { 138 | // use as: 139 | // cargo test test_bench_shift_frequency -- release 140 | 141 | let mut samplenr: u32 = 0; 142 | let shift_hz: f32 = 815000.0; 143 | let samplerate: u32 = 2400000; 144 | 145 | let input: [u8; 1_000_000] = [0xAA; 1_000_000]; 146 | let complex_input = convert_iqf32_to_complex(&input); 147 | 148 | let mut iterator = 0; 149 | loop { 150 | shift_frequency(&complex_input, &mut samplenr, shift_hz, samplerate); 151 | 152 | iterator += 1; 153 | if iterator > 300 { 154 | break; 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2015 Andres Vahter (andres.vahter@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | 26 | // import local modules 27 | extern crate doppler; 28 | use doppler::usage; 29 | use doppler::usage::Mode::{ConstMode, TrackMode}; 30 | use doppler::usage::DataType::{I16, F32}; 31 | use doppler::dsp; 32 | 33 | // import external modules 34 | #[macro_use] 35 | extern crate log; 36 | extern crate fern; 37 | use std::process::exit; 38 | use std::io; 39 | use std::io::prelude::*; 40 | use std::io::BufReader; 41 | use std::io::BufWriter; 42 | use std::slice; 43 | 44 | extern crate time; 45 | extern crate gpredict; 46 | use gpredict::{Predict, Tle, Location}; 47 | 48 | const SPEED_OF_LIGHT_M_S: f64 = 299792458.; 49 | const BUFFER_SIZE: usize = 8192; 50 | 51 | fn main() { 52 | setup_logger(); 53 | let args = usage::args(); 54 | 55 | info!("doppler {} andres.vahter@gmail.com\n\n", env!("CARGO_PKG_VERSION")); 56 | 57 | let mut stdin = BufReader::with_capacity(BUFFER_SIZE*2, io::stdin()); 58 | let mut stdout = BufWriter::new(io::stdout()); 59 | 60 | let mut samplenr: u32 = 0; 61 | 62 | let mut shift = |intype: doppler::usage::DataType, shift_hz: f32, samplerate: u32| { 63 | let invec = stdin.by_ref().bytes().take(BUFFER_SIZE).collect::,_>>().ok().expect("doppler collect error"); 64 | 65 | let input = match intype { 66 | I16 => dsp::convert_iqi16_to_complex(&invec), 67 | F32 => dsp::convert_iqf32_to_complex(&invec), 68 | }; 69 | 70 | let output = dsp::shift_frequency(&input, &mut samplenr, shift_hz, samplerate); 71 | 72 | match *args.outputtype.as_ref().unwrap() { 73 | doppler::usage::DataType::I16 => { 74 | let mut outputi16 = Vec::::with_capacity(output.len() * 4); 75 | 76 | for sample in &output[..] { 77 | let i = (sample.re * 32767.0) as i16; 78 | let q = (sample.im * 32767.0) as i16; 79 | 80 | outputi16.push((i & 0xFF) as u8); 81 | outputi16.push(((i >> 8) & 0xFF) as u8); 82 | outputi16.push((q & 0xFF) as u8); 83 | outputi16.push(((q >> 8) & 0xFF) as u8); 84 | } 85 | 86 | stdout.write(&outputi16[..]).map_err(|e|{info!("doppler stdout.write error: {:?}", e)}).unwrap(); 87 | }, 88 | 89 | doppler::usage::DataType::F32 => { 90 | // * 8 because Complex is 8 bytes long 91 | let slice = unsafe {slice::from_raw_parts(output.as_ptr() as *const _, output.len() * 8)}; 92 | stdout.write(&slice).map_err(|e|{info!("doppler stdout.write error: {:?}", e)}).unwrap(); 93 | }, 94 | }; 95 | 96 | 97 | stdout.flush().map_err(|e|{info!("doppler stdout.flush error: {:?}", e)}).unwrap(); 98 | (invec.len() != BUFFER_SIZE, output.len()) 99 | }; 100 | 101 | match *args.mode.as_ref().unwrap() { 102 | ConstMode => { 103 | info!("constant shift mode"); 104 | info!("\tIQ samplerate : {}", args.samplerate.as_ref().unwrap()); 105 | info!("\tIQ input type : {}", args.inputtype.as_ref().unwrap()); 106 | info!("\tIQ output type : {}\n", args.outputtype.as_ref().unwrap()); 107 | info!("\tfrequency shift : {} Hz", args.constargs.shift.as_ref().unwrap()); 108 | 109 | let intype = args.inputtype.unwrap(); 110 | let shift_hz = args.constargs.shift.unwrap() as f32; 111 | let samplerate = args.samplerate.unwrap(); 112 | 113 | loop { 114 | let stop_and_count: (bool, usize) = shift(intype, shift_hz, samplerate); 115 | if stop_and_count.0 { 116 | break; 117 | } 118 | } 119 | } 120 | 121 | 122 | TrackMode => { 123 | info!("tracking mode"); 124 | info!("\tIQ samplerate : {}", args.samplerate.as_ref().unwrap()); 125 | info!("\tIQ input type : {}", args.inputtype.as_ref().unwrap()); 126 | info!("\tIQ output type : {}\n", args.outputtype.as_ref().unwrap()); 127 | info!("\tTLE file : {}", args.trackargs.tlefile.as_ref().unwrap()); 128 | info!("\tTLE name : {}", args.trackargs.tlename.as_ref().unwrap()); 129 | info!("\tlocation : {:?}", args.trackargs.location.as_ref().unwrap()); 130 | if args.trackargs.time.is_some() { 131 | info!("\ttime : {:.3}", args.trackargs.time.unwrap().to_utc().rfc3339()); 132 | } 133 | info!("\tfrequency : {} Hz", args.trackargs.frequency.as_ref().unwrap()); 134 | info!("\toffset : {} Hz\n\n\n", args.trackargs.offset.unwrap_or(0)); 135 | 136 | let l = args.trackargs.location.unwrap(); 137 | let location: Location = Location{lat_deg: l.lat, lon_deg: l.lon, alt_m: l.alt}; 138 | let tlename = args.trackargs.tlename.as_ref().unwrap(); 139 | let tlefile = args.trackargs.tlefile.as_ref().unwrap(); 140 | 141 | let tle = match Tle::from_file(&tlename, &tlefile) { 142 | Ok(t) => {t}, 143 | Err(e) => { 144 | info!("{}", e); 145 | exit(1); 146 | } 147 | }; 148 | 149 | let mut predict: Predict = Predict::new(&tle, &location); 150 | let intype = args.inputtype.unwrap(); 151 | 152 | let samplerate = args.samplerate.unwrap(); 153 | let mut last_time: time::Tm = time::now_utc(); 154 | 155 | match args.trackargs.time { 156 | Some(start_time) => { 157 | let mut sample_count = 0; 158 | let mut dt = time::Duration::seconds(0); 159 | last_time = start_time; 160 | 161 | loop { 162 | predict.update(Some(start_time + dt)); 163 | let doppler_hz = (predict.sat.range_rate_km_sec * 1000_f64 / SPEED_OF_LIGHT_M_S) * args.trackargs.frequency.unwrap() as f64 * (-1.0); 164 | 165 | // advance time based on how many samples are read in 166 | dt = time::Duration::seconds((sample_count as f32 / samplerate as f32) as i64); 167 | if start_time + dt - last_time >= time::Duration::seconds(5) { 168 | last_time = start_time + dt; 169 | info!("time : {:}", (start_time + dt).to_utc().rfc3339()); 170 | info!("az : {:.2}°", predict.sat.az_deg); 171 | info!("el : {:.2}°", predict.sat.el_deg); 172 | info!("range : {:.0} km", predict.sat.range_km); 173 | info!("range rate : {:.3} km/sec", predict.sat.range_rate_km_sec); 174 | info!("doppler@{:.3} MHz : {:.2} Hz\n", args.trackargs.frequency.unwrap() as f32 / 1000_000_f32, doppler_hz); 175 | } 176 | 177 | let (stop, count): (bool, usize) = shift(intype, doppler_hz as f32 + args.trackargs.offset.unwrap_or(0) as f32, samplerate); 178 | if stop { 179 | break; 180 | } 181 | 182 | sample_count += count; 183 | } 184 | } 185 | 186 | None => { 187 | loop { 188 | predict.update(None); 189 | let doppler_hz = (predict.sat.range_rate_km_sec * 1000_f64 / SPEED_OF_LIGHT_M_S) * args.trackargs.frequency.unwrap() as f64 * (-1.0); 190 | 191 | if time::now_utc() - last_time >= time::Duration::seconds(1) { 192 | last_time = time::now_utc(); 193 | info!("time : {:}", time::now_utc().to_utc().rfc3339()); 194 | info!("az : {:.2}°", predict.sat.az_deg); 195 | info!("el : {:.2}°", predict.sat.el_deg); 196 | info!("range : {:.0} km", predict.sat.range_km); 197 | info!("range rate : {:.3} km/sec", predict.sat.range_rate_km_sec); 198 | info!("doppler@{:.3} MHz : {:.2} Hz\n", args.trackargs.frequency.unwrap() as f32 / 1000_000_f32, doppler_hz); 199 | } 200 | 201 | let (stop, _): (bool, usize) = shift(intype, doppler_hz as f32 + args.trackargs.offset.unwrap_or(0) as f32, samplerate); 202 | if stop { 203 | break; 204 | } 205 | } 206 | } 207 | }; 208 | } 209 | } 210 | } 211 | 212 | fn setup_logger() { 213 | let logger_config = fern::DispatchConfig { 214 | format: Box::new(|msg: &str, level: &log::LogLevel, _location: &log::LogLocation| { 215 | let t = time::now(); 216 | let ms = t.tm_nsec/1000_000; 217 | let path = _location.__module_path; 218 | let line = _location.__line; 219 | 220 | format!("{}.{:3} [{:<6} {:<30} {:>3}] {}", 221 | t.strftime("%Y-%m-%dT%H:%M:%S") 222 | .unwrap_or_else(|err| panic!("strftime format error: {}", err)), 223 | ms, level, path, line, msg) 224 | }), 225 | output: vec![fern::OutputConfig::stderr()], 226 | level: log::LogLevelFilter::Debug, 227 | directives: vec!() 228 | }; 229 | 230 | if let Err(e) = fern::init_global_logger(logger_config, log::LogLevelFilter::Trace) { 231 | panic!("Failed to initialize global logger: {}", e); 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "ansi_term" 3 | version = "0.9.0" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | 6 | [[package]] 7 | name = "bitflags" 8 | version = "0.7.0" 9 | source = "registry+https://github.com/rust-lang/crates.io-index" 10 | 11 | [[package]] 12 | name = "clap" 13 | version = "2.19.0" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | dependencies = [ 16 | "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 17 | "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 18 | "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", 19 | "strsim 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 20 | "term_size 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 21 | "unicode-segmentation 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 22 | "unicode-width 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 23 | "vec_map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 24 | ] 25 | 26 | [[package]] 27 | name = "coordinates" 28 | version = "0.1.0" 29 | source = "git+https://github.com/cubehub/rust-coordinates.git#e8548c16dbb8a72cce19d3e3d395543aacac8b7f" 30 | dependencies = [ 31 | "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 32 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 33 | ] 34 | 35 | [[package]] 36 | name = "doppler" 37 | version = "1.1.10" 38 | dependencies = [ 39 | "clap 2.19.0 (registry+https://github.com/rust-lang/crates.io-index)", 40 | "fern 0.4.0 (git+https://github.com/marjakm/fern-rs.git)", 41 | "gcc 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)", 42 | "gpredict 0.2.6 (git+https://github.com/cubehub/rust-gpredict.git)", 43 | "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", 44 | "liquid_dsp 0.3.0 (git+https://github.com/cubehub/rust-liquid-dsp.git)", 45 | "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 46 | "num 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", 47 | "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 48 | ] 49 | 50 | [[package]] 51 | name = "fern" 52 | version = "0.4.0" 53 | source = "git+https://github.com/marjakm/fern-rs.git#9b1b821ac9f8cf28b426e0adafa881a7fcfa5631" 54 | dependencies = [ 55 | "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 56 | ] 57 | 58 | [[package]] 59 | name = "gcc" 60 | version = "0.3.38" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | 63 | [[package]] 64 | name = "gpredict" 65 | version = "0.2.6" 66 | source = "git+https://github.com/cubehub/rust-gpredict.git#b223e69b998fa5efa0e8e5f51a6318503ca036de" 67 | dependencies = [ 68 | "coordinates 0.1.0 (git+https://github.com/cubehub/rust-coordinates.git)", 69 | "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", 70 | "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 71 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 72 | "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 73 | ] 74 | 75 | [[package]] 76 | name = "kernel32-sys" 77 | version = "0.2.2" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | dependencies = [ 80 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 81 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 82 | ] 83 | 84 | [[package]] 85 | name = "lazy_static" 86 | version = "0.2.2" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | 89 | [[package]] 90 | name = "libc" 91 | version = "0.2.17" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | 94 | [[package]] 95 | name = "liquid_dsp" 96 | version = "0.3.0" 97 | source = "git+https://github.com/cubehub/rust-liquid-dsp.git#343bf9bd960c5450e1bdb02533316bd414ca4b1f" 98 | dependencies = [ 99 | "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", 100 | "num 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", 101 | ] 102 | 103 | [[package]] 104 | name = "log" 105 | version = "0.3.6" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | 108 | [[package]] 109 | name = "num" 110 | version = "0.1.36" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | dependencies = [ 113 | "num-bigint 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 114 | "num-complex 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 115 | "num-integer 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 116 | "num-iter 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 117 | "num-rational 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 118 | "num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", 119 | ] 120 | 121 | [[package]] 122 | name = "num-bigint" 123 | version = "0.1.35" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | dependencies = [ 126 | "num-integer 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 127 | "num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", 128 | "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 129 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 130 | ] 131 | 132 | [[package]] 133 | name = "num-complex" 134 | version = "0.1.35" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | dependencies = [ 137 | "num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", 138 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 139 | ] 140 | 141 | [[package]] 142 | name = "num-integer" 143 | version = "0.1.32" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | dependencies = [ 146 | "num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", 147 | ] 148 | 149 | [[package]] 150 | name = "num-iter" 151 | version = "0.1.32" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | dependencies = [ 154 | "num-integer 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 155 | "num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", 156 | ] 157 | 158 | [[package]] 159 | name = "num-rational" 160 | version = "0.1.35" 161 | source = "registry+https://github.com/rust-lang/crates.io-index" 162 | dependencies = [ 163 | "num-bigint 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 164 | "num-integer 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)", 165 | "num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)", 166 | "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", 167 | ] 168 | 169 | [[package]] 170 | name = "num-traits" 171 | version = "0.1.36" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | 174 | [[package]] 175 | name = "rand" 176 | version = "0.3.15" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | dependencies = [ 179 | "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", 180 | ] 181 | 182 | [[package]] 183 | name = "rustc-serialize" 184 | version = "0.3.24" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | 187 | [[package]] 188 | name = "strsim" 189 | version = "0.5.2" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | 192 | [[package]] 193 | name = "term_size" 194 | version = "0.2.1" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | dependencies = [ 197 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 198 | "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", 199 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 200 | ] 201 | 202 | [[package]] 203 | name = "time" 204 | version = "0.1.35" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | dependencies = [ 207 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 208 | "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)", 209 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 210 | ] 211 | 212 | [[package]] 213 | name = "unicode-segmentation" 214 | version = "0.1.2" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | 217 | [[package]] 218 | name = "unicode-width" 219 | version = "0.1.3" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | 222 | [[package]] 223 | name = "vec_map" 224 | version = "0.6.0" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | 227 | [[package]] 228 | name = "winapi" 229 | version = "0.2.8" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | 232 | [[package]] 233 | name = "winapi-build" 234 | version = "0.1.1" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | 237 | [metadata] 238 | "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" 239 | "checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" 240 | "checksum clap 2.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef87e92396a3d29bf7e611c8a595be35ae90d9cb844a3571425900eaca4f51c8" 241 | "checksum coordinates 0.1.0 (git+https://github.com/cubehub/rust-coordinates.git)" = "" 242 | "checksum fern 0.4.0 (git+https://github.com/marjakm/fern-rs.git)" = "" 243 | "checksum gcc 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)" = "553f11439bdefe755bf366b264820f1da70f3aaf3924e594b886beb9c831bcf5" 244 | "checksum gpredict 0.2.6 (git+https://github.com/cubehub/rust-gpredict.git)" = "" 245 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 246 | "checksum lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6abe0ee2e758cd6bc8a2cd56726359007748fbf4128da998b65d0b70f881e19b" 247 | "checksum libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "044d1360593a78f5c8e5e710beccdc24ab71d1f01bc19a29bcacdba22e8475d8" 248 | "checksum liquid_dsp 0.3.0 (git+https://github.com/cubehub/rust-liquid-dsp.git)" = "" 249 | "checksum log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ab83497bf8bf4ed2a74259c1c802351fcd67a65baa86394b6ba73c36f4838054" 250 | "checksum num 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "bde7c03b09e7c6a301ee81f6ddf66d7a28ec305699e3d3b056d2fc56470e3120" 251 | "checksum num-bigint 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "88b14378471f7c2adc5262f05b4701ef53e8da376453a8d8fee48e51db745e49" 252 | "checksum num-complex 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c78e054dd19c3fd03419ade63fa661e9c49bb890ce3beb4eee5b7baf93f92f" 253 | "checksum num-integer 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "fb24d9bfb3f222010df27995441ded1e954f8f69cd35021f6bef02ca9552fb92" 254 | "checksum num-iter 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "287a1c9969a847055e1122ec0ea7a5c5d6f72aad97934e131c83d5c08ab4e45c" 255 | "checksum num-rational 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "54ff603b8334a72fbb27fe66948aac0abaaa40231b3cecd189e76162f6f38aaf" 256 | "checksum num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)" = "a16a42856a256b39c6d3484f097f6713e14feacd9bfb02290917904fae46c81c" 257 | "checksum rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "022e0636ec2519ddae48154b028864bdce4eaf7d35226ab8e65c611be97b189d" 258 | "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" 259 | "checksum strsim 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "67f84c44fbb2f91db7fef94554e6b2ac05909c9c0b0bc23bb98d3a1aebfe7f7c" 260 | "checksum term_size 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f7f5f3f71b0040cecc71af239414c23fd3c73570f5ff54cf50e03cef637f2a0" 261 | "checksum time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "3c7ec6d62a20df54e07ab3b78b9a3932972f4b7981de295563686849eb3989af" 262 | "checksum unicode-segmentation 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b905d0fc2a1f0befd86b0e72e31d1787944efef9d38b9358a9e92a69757f7e3b" 263 | "checksum unicode-width 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2d6722facc10989f63ee0e20a83cd4e1714a9ae11529403ac7e0afd069abc39e" 264 | "checksum vec_map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cac5efe5cb0fa14ec2f84f83c701c562ee63f6dcc680861b21d65c682adfb05f" 265 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 266 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 267 | -------------------------------------------------------------------------------- /src/usage.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2015 Andres Vahter (andres.vahter@gmail.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | */ 24 | 25 | use clap::{App, AppSettings, Arg, SubCommand}; 26 | use time; 27 | use self::DataType::{F32, I16}; 28 | use self::Mode::{ConstMode, TrackMode}; 29 | 30 | use std::fmt; 31 | use std::process::exit; 32 | 33 | pub enum Mode { 34 | ConstMode, 35 | TrackMode, 36 | } 37 | 38 | #[derive(Clone, Copy)] 39 | pub enum DataType { 40 | F32, 41 | I16, 42 | } 43 | 44 | impl fmt::Display for DataType { 45 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 46 | match *self { 47 | DataType::F32 => {write!(f, "f32")}, 48 | DataType::I16 => {write!(f, "i16")}, 49 | } 50 | } 51 | } 52 | 53 | #[derive(Debug)] 54 | #[derive(Clone, Copy)] 55 | pub struct Location { 56 | pub lat: f64, 57 | pub lon: f64, 58 | pub alt: f64, 59 | } 60 | 61 | pub struct ConstModeArgs { 62 | pub shift: Option, 63 | } 64 | 65 | pub struct TrackModeArgs { 66 | pub tlefile: Option, 67 | pub tlename: Option, 68 | pub location: Option, 69 | pub time: Option, 70 | pub frequency: Option, 71 | pub offset: Option, 72 | } 73 | 74 | pub struct CommandArgs { 75 | pub mode: Option, 76 | 77 | pub samplerate: Option, 78 | pub inputtype: Option, 79 | pub outputtype: Option, 80 | 81 | pub constargs: ConstModeArgs, 82 | pub trackargs: TrackModeArgs, 83 | } 84 | 85 | fn parse_location(location: &str) -> Result { 86 | if location.contains("lat") && location.contains("lon") && location.contains("alt"){ 87 | let split = location.split(","); 88 | 89 | let mut lat: Option = None; 90 | let mut lon: Option = None; 91 | let mut alt: Option = None; 92 | 93 | for s in split { 94 | if s.contains("lat") && s.contains("=") { 95 | lat = s.split("=").nth(1).unwrap().parse::().ok(); 96 | } 97 | else if s.contains("lon") && s.contains("=") { 98 | lon = s.split("=").nth(1).unwrap().parse::().ok(); 99 | } 100 | else if s.contains("alt") && s.contains("=") { 101 | alt = s.split("=").nth(1).unwrap().parse::().ok(); 102 | } 103 | } 104 | 105 | if lat.is_some() && lon.is_some() && alt.is_some() { 106 | Ok(Location{lat: lat.unwrap(), lon: lon.unwrap(), alt: alt.unwrap()}) 107 | } 108 | else { 109 | Err(format!("{} isn't a valid value for --location\n\t[use as: lat=58.64560,lon=23.15163,alt=8]", location).to_string()) 110 | } 111 | } 112 | else { 113 | Err("--location should be defined as: lat=58.64560,lon=23.15163,alt=8".to_string()) 114 | } 115 | } 116 | 117 | pub fn args() -> CommandArgs { 118 | let datatypes = ["i16", "f32"]; 119 | 120 | let matches = App::new("doppler") 121 | .author("Andres Vahter ") 122 | .version(env!("CARGO_PKG_VERSION")) 123 | .about("Compensates IQ data stream doppler shift based on TLE information, also can be used for doing constant baseband shifting") 124 | 125 | 126 | .subcommand(SubCommand::with_name("const") 127 | .setting(AppSettings::AllowLeadingHyphen) // allow negative values like --shift -5000 128 | .about("Constant shift mode") 129 | 130 | .arg(Arg::with_name("SAMPLERATE") 131 | .long("samplerate") 132 | .short("s") 133 | .help("IQ data samplerate") 134 | .required(true) 135 | .takes_value(true)) 136 | 137 | .arg(Arg::with_name("INTYPE") 138 | .long("intype") 139 | .short("i") 140 | .help("IQ data input type") 141 | .required(true) 142 | .possible_values(&datatypes) 143 | .takes_value(true)) 144 | 145 | .arg(Arg::with_name("OUTTYPE") 146 | .long("outtype") 147 | .short("o") 148 | .help("IQ data output type") 149 | .required(false) 150 | .possible_values(&datatypes) 151 | .takes_value(true)) 152 | 153 | .arg(Arg::with_name("SHIFT") 154 | .long("shift") 155 | .help("frequency shift in Hz") 156 | .required(true) 157 | .takes_value(true))) 158 | 159 | 160 | .subcommand(SubCommand::with_name("track") 161 | .setting(AppSettings::AllowLeadingHyphen) // allow negative values like --offset -5000 162 | .about("Doppler tracking mode") 163 | 164 | .arg(Arg::with_name("SAMPLERATE") 165 | .long("samplerate") 166 | .short("s") 167 | .help("IQ data samplerate") 168 | .required(true) 169 | .takes_value(true)) 170 | 171 | .arg(Arg::with_name("INTYPE") 172 | .long("intype") 173 | .short("i") 174 | .help("IQ data type") 175 | .required(true) 176 | .possible_values(&datatypes) 177 | .takes_value(true)) 178 | 179 | .arg(Arg::with_name("OUTTYPE") 180 | .long("outtype") 181 | .short("o") 182 | .help("IQ data output type") 183 | .required(false) 184 | .possible_values(&datatypes) 185 | .takes_value(true)) 186 | 187 | .arg(Arg::with_name("TLEFILE") 188 | .long("tlefile") 189 | .help("TLE file: eg. http://www.celestrak.com/NORAD/elements/cubesat.txt") 190 | .required(true) 191 | .takes_value(true)) 192 | 193 | .arg(Arg::with_name("TLENAME") 194 | .long("tlename") 195 | .help("TLE name in TLE file: eg. ESTCUBE 1") 196 | .required(true) 197 | .takes_value(true)) 198 | 199 | .arg(Arg::with_name("LOCATION") 200 | .long("location") 201 | .help("Observer location (lat=,lon=,alt=): eg. lat=58.64560,lon=23.15163,alt=8") 202 | .required(true) 203 | .use_delimiter(false) 204 | .takes_value(true)) 205 | 206 | .arg(Arg::with_name("TIME") 207 | .long("time") 208 | .help("Observation start time in UTC Y-m-dTH:M:S: eg. 2015-05-13T14:28:48. If not specified current time is used") 209 | .required(false) 210 | .takes_value(true)) 211 | 212 | .arg(Arg::with_name("FREQUENCY") 213 | .long("frequency") 214 | .help("Satellite transmitter frequency in Hz") 215 | .required(true) 216 | .takes_value(true)) 217 | 218 | .arg(Arg::with_name("OFFSET") 219 | .long("offset") 220 | .help("Constant frequency shift in Hz. Can be used to compensate constant offset") 221 | .required(false) 222 | .takes_value(true))) 223 | 224 | .get_matches(); 225 | 226 | 227 | let mut args = CommandArgs { 228 | mode : None, 229 | 230 | samplerate : None, 231 | inputtype : None, 232 | outputtype: None, 233 | 234 | constargs : ConstModeArgs { 235 | shift: None, 236 | }, 237 | 238 | trackargs : TrackModeArgs { 239 | tlefile : None, 240 | tlename : None, 241 | location: None, 242 | time : None, 243 | frequency : None, 244 | offset : None, 245 | }, 246 | }; 247 | 248 | 249 | match matches.subcommand_name() { 250 | Some("const") => { 251 | args.mode = Some(ConstMode); 252 | let submatches = matches.subcommand_matches("const").unwrap(); 253 | args.samplerate = Some(value_t_or_exit!(submatches.value_of("SAMPLERATE"), u32)); 254 | 255 | match submatches.value_of("INTYPE").unwrap() { 256 | "f32" => {args.inputtype = Some(F32);}, 257 | "i16" => {args.inputtype = Some(I16);}, 258 | _ => unreachable!() 259 | } 260 | 261 | if submatches.is_present("OUTTYPE") { 262 | match submatches.value_of("OUTTYPE").unwrap() { 263 | "f32" => {args.outputtype = Some(F32);}, 264 | "i16" => {args.outputtype = Some(I16);}, 265 | _ => unreachable!() 266 | } 267 | } 268 | else { 269 | args.outputtype = args.inputtype; 270 | } 271 | 272 | args.constargs.shift = Some(value_t_or_exit!(submatches.value_of("SHIFT"), i32)); 273 | }, 274 | 275 | 276 | Some("track") => { 277 | args.mode = Some(TrackMode); 278 | let submatches = matches.subcommand_matches("track").unwrap(); 279 | args.samplerate = Some(value_t_or_exit!(submatches.value_of("SAMPLERATE"), u32)); 280 | 281 | match submatches.value_of("INTYPE").unwrap() { 282 | "f32" => {args.inputtype = Some(F32);}, 283 | "i16" => {args.inputtype = Some(I16);}, 284 | _ => unreachable!() 285 | } 286 | 287 | if submatches.is_present("OUTTYPE") { 288 | match submatches.value_of("OUTTYPE").unwrap() { 289 | "f32" => {args.outputtype = Some(F32);}, 290 | "i16" => {args.outputtype = Some(I16);}, 291 | _ => unreachable!() 292 | } 293 | } 294 | else { 295 | args.outputtype = args.inputtype; 296 | } 297 | 298 | if submatches.is_present("OFFSET") { 299 | args.trackargs.offset = Some(value_t_or_exit!(submatches.value_of("OFFSET"), i32)); 300 | } 301 | 302 | if submatches.is_present("TIME") { 303 | let tm = time::strptime(submatches.value_of("TIME").unwrap(), "%Y-%m-%dT%H:%M:%S"); 304 | match tm { 305 | Ok(_) => {}, 306 | Err(e) => { 307 | error!("{}.", e); 308 | error!("--time should be defined in Y-m-dTH:M:S format: eg. 2015-05-13T14:28:48"); 309 | exit(1); 310 | }, 311 | }; 312 | 313 | args.trackargs.time = Some(tm.unwrap()); 314 | } 315 | 316 | args.trackargs.tlefile = Some(submatches.value_of("TLEFILE").unwrap().to_string()); 317 | args.trackargs.tlename = Some(submatches.value_of("TLENAME").unwrap().to_string()); 318 | args.trackargs.frequency = Some(value_t_or_exit!(submatches.value_of("FREQUENCY"), u32)); 319 | 320 | let location = parse_location(&submatches.value_of("LOCATION").unwrap().to_string()); 321 | match location { 322 | Ok(loc) => { args.trackargs.location = Some(loc);}, 323 | Err(e) => { 324 | error!("{}.", e); 325 | exit(1); 326 | } 327 | } 328 | }, 329 | 330 | _ => { 331 | info!("no arguments provided, try with doppler -h"); 332 | exit(1); 333 | } 334 | } 335 | 336 | args 337 | } 338 | --------------------------------------------------------------------------------