Merge branch 'speed'
Conflicts: src/python/uhd_modes.py
This commit is contained in:
commit
462b7fbed3
@ -28,6 +28,9 @@
|
||||
#include <gr_io_signature.h>
|
||||
#include <air_modes_types.h>
|
||||
#include <modes_energy.h>
|
||||
#include <gr_tag_info.h>
|
||||
#include <iostream>
|
||||
#include <string.h>
|
||||
|
||||
air_modes_framer_sptr air_make_modes_framer(int channel_rate)
|
||||
{
|
||||
@ -36,16 +39,21 @@ air_modes_framer_sptr air_make_modes_framer(int channel_rate)
|
||||
|
||||
air_modes_framer::air_modes_framer(int channel_rate) :
|
||||
gr_sync_block ("modes_framer",
|
||||
gr_make_io_signature2 (2, 2, sizeof(float), sizeof(unsigned char)), //stream 0 is received data, stream 1 is binary preamble detector output
|
||||
gr_make_io_signature (1, 1, sizeof(unsigned char))) //output is [0, 1, 2]: [no frame, short frame, long frame]
|
||||
gr_make_io_signature (1, 1, sizeof(float)), //stream 0 is received data
|
||||
gr_make_io_signature (1, 1, sizeof(float))) //raw samples passed back out
|
||||
{
|
||||
//initialize private data here
|
||||
d_chip_rate = 2000000; //2Mchips per second
|
||||
d_samples_per_chip = channel_rate / d_chip_rate; //must be integer number of samples per chip to work
|
||||
d_samples_per_symbol = d_samples_per_chip * 2;
|
||||
d_check_width = 120 * d_samples_per_symbol; //gotta be able to look at two long frame lengths at a time in the event that FRUIT occurs near the end of the first frame
|
||||
|
||||
set_output_multiple(1+d_check_width*2);
|
||||
d_check_width = 120 * d_samples_per_symbol; //gotta be able to look at two long frame lengths at a time
|
||||
//in the event that FRUIT occurs near the end of the first frame
|
||||
//set_history(d_check_width*2);
|
||||
|
||||
std::stringstream str;
|
||||
str << name() << unique_id();
|
||||
d_me = pmt::pmt_string_to_symbol(str.str());
|
||||
d_key = pmt::pmt_string_to_symbol("frame_info");
|
||||
}
|
||||
|
||||
int air_modes_framer::work(int noutput_items,
|
||||
@ -54,60 +62,62 @@ int air_modes_framer::work(int noutput_items,
|
||||
{
|
||||
//do things!
|
||||
const float *inraw = (const float *) input_items[0];
|
||||
const unsigned char *inattrib = (const unsigned char *) input_items[1];
|
||||
|
||||
//float *outraw = (float *) output_items[0];
|
||||
unsigned char *outattrib = (unsigned char *) output_items[0];
|
||||
|
||||
int size = noutput_items - d_check_width; //need to be able to look ahead a full frame
|
||||
|
||||
float reference_level = 0;
|
||||
float *outraw = (float *) output_items[0];
|
||||
//unsigned char *outattrib = (unsigned char *) output_items[0];
|
||||
int size = noutput_items - d_check_width*2;
|
||||
float reference_level;
|
||||
framer_packet_type packet_attrib;
|
||||
|
||||
for(int i = 0; i < size; i++) {
|
||||
|
||||
packet_attrib = No_Packet;
|
||||
|
||||
if(!inattrib[i]) {
|
||||
outattrib[i] = packet_attrib;
|
||||
continue; //if there's no preamble marker, forget it, move on
|
||||
}
|
||||
|
||||
std::vector<pmt::pmt_t> tags;
|
||||
|
||||
uint64_t abs_sample_cnt = nitems_read(0);
|
||||
get_tags_in_range(tags, 0, abs_sample_cnt, abs_sample_cnt + size, pmt::pmt_string_to_symbol("preamble_found"));
|
||||
std::vector<pmt::pmt_t>::iterator tag_iter;
|
||||
|
||||
memcpy(outraw, inraw, size * sizeof(float));
|
||||
|
||||
for(tag_iter = tags.begin(); tag_iter != tags.end(); tag_iter++) {
|
||||
uint64_t i = gr_tags::get_nitems(*tag_iter) - abs_sample_cnt;
|
||||
//first, assume we have a long packet
|
||||
packet_attrib = Long_Packet;
|
||||
|
||||
//let's use the preamble marker to get a reference level for the packet
|
||||
reference_level = (bit_energy(&inraw[i], d_samples_per_chip)
|
||||
+ bit_energy(&inraw[i+int(1.0*d_samples_per_symbol)], d_samples_per_chip)
|
||||
+ bit_energy(&inraw[i+int(3.5*d_samples_per_symbol)], d_samples_per_chip)
|
||||
+ bit_energy(&inraw[i+int(4.5*d_samples_per_symbol)], d_samples_per_chip)) / 4;
|
||||
|
||||
+ bit_energy(&inraw[i+int(1.0*d_samples_per_symbol)], d_samples_per_chip)
|
||||
+ bit_energy(&inraw[i+int(3.5*d_samples_per_symbol)], d_samples_per_chip)
|
||||
+ bit_energy(&inraw[i+int(4.5*d_samples_per_symbol)], d_samples_per_chip)) / 4;
|
||||
|
||||
//armed with our reference level, let's look for marks within 3dB of the reference level in bits 57-62 (65-70, see above)
|
||||
//if bits 57-62 have marks in either chip, we've got a long packet
|
||||
//otherwise we have a short packet
|
||||
|
||||
//NOTE: you can change the default here to be short packet, and then check for a long packet. don't know which way is better.
|
||||
|
||||
for(int j = (65 * d_samples_per_symbol); j < (70 * d_samples_per_symbol); j += d_samples_per_symbol) {
|
||||
float t_max = (bit_energy(&inraw[i+j], d_samples_per_chip) > bit_energy(&inraw[i+j+d_samples_per_chip], d_samples_per_chip)) ? bit_energy(&inraw[i+j], d_samples_per_chip) : bit_energy(&inraw[i+j+d_samples_per_chip], d_samples_per_chip);
|
||||
if(t_max < (reference_level / 2)) packet_attrib = Short_Packet;
|
||||
float t_max = std::max(bit_energy(&inraw[i+j], d_samples_per_chip),
|
||||
bit_energy(&inraw[i+j+d_samples_per_chip], d_samples_per_chip)
|
||||
);
|
||||
if(t_max < (reference_level / 2.0)) packet_attrib = Short_Packet;
|
||||
}
|
||||
|
||||
//BUT: we must also loop through the entire packet to make sure it is clear of additional preamble markers! if it has another preamble marker, it's been FRUITed, and we must only
|
||||
|
||||
//BUT: we must also loop through the entire packet to make sure it is clear of additional preamble markers!
|
||||
//if it has another preamble marker, it's been FRUITed, and we must only
|
||||
//mark the new packet (i.e., just continue).
|
||||
|
||||
int lookahead;
|
||||
if(packet_attrib == Long_Packet) lookahead = 112;
|
||||
else lookahead = 56;
|
||||
|
||||
for(int j = i+1; j < i+(lookahead * d_samples_per_symbol); j++) {
|
||||
if(inattrib[j]) packet_attrib = Fruited_Packet; //FRUITed by mode S! in this case, we drop this first packet
|
||||
//if(inraw[j] > (reference_level * 2)) packet_attrib = Fruited_Packet; //catches strong Mode A/C fruit inside the packet
|
||||
//but good error correction should cope with that
|
||||
}
|
||||
|
||||
outattrib[i] = packet_attrib;
|
||||
if(packet_attrib == Long_Packet) lookahead = 112 * d_samples_per_symbol;
|
||||
else lookahead = 56 * d_samples_per_symbol;
|
||||
|
||||
//ok we have to re-do lookahead for this tagged version.
|
||||
//we can do this by looking for tags that fall within that window
|
||||
std::vector<pmt::pmt_t> fruit_tags;
|
||||
get_tags_in_range(fruit_tags, 0, abs_sample_cnt+i+1, abs_sample_cnt+i+lookahead, pmt::pmt_string_to_symbol("preamble_found"));
|
||||
if(fruit_tags.size() > 0) packet_attrib = Fruited_Packet;
|
||||
|
||||
//insert tag here
|
||||
add_item_tag(0, //stream ID
|
||||
nitems_written(0)+i, //sample
|
||||
d_key, //preamble_found
|
||||
pmt::pmt_from_long((long)packet_attrib),
|
||||
d_me //block src id
|
||||
);
|
||||
}
|
||||
|
||||
return size;
|
||||
|
@ -44,6 +44,7 @@ private:
|
||||
int d_chip_rate;
|
||||
int d_samples_per_chip;
|
||||
int d_samples_per_symbol;
|
||||
pmt::pmt_t d_me, d_key;
|
||||
|
||||
public:
|
||||
int work (int noutput_items,
|
||||
|
@ -27,6 +27,8 @@
|
||||
#include <air_modes_preamble.h>
|
||||
#include <gr_io_signature.h>
|
||||
#include <modes_energy.h>
|
||||
#include <string.h>
|
||||
#include <iostream>
|
||||
|
||||
air_modes_preamble_sptr air_make_modes_preamble(int channel_rate, float threshold_db)
|
||||
{
|
||||
@ -36,9 +38,8 @@ air_modes_preamble_sptr air_make_modes_preamble(int channel_rate, float threshol
|
||||
air_modes_preamble::air_modes_preamble(int channel_rate, float threshold_db) :
|
||||
gr_sync_block ("modes_preamble",
|
||||
gr_make_io_signature2 (2, 2, sizeof(float), sizeof(float)), //stream 0 is received data, stream 1 is moving average for reference
|
||||
gr_make_io_signature (1, 1, sizeof(unsigned char)))
|
||||
gr_make_io_signature (1, 1, sizeof(float))) //the original data. we pass it out in order to use tags.
|
||||
{
|
||||
//initialize private data here
|
||||
d_chip_rate = 2000000; //2Mchips per second
|
||||
d_samples_per_chip = channel_rate / d_chip_rate; //must be integer number of samples per chip to work
|
||||
d_samples_per_symbol = d_samples_per_chip * 2;
|
||||
@ -46,6 +47,11 @@ air_modes_preamble::air_modes_preamble(int channel_rate, float threshold_db) :
|
||||
d_threshold_db = threshold_db;
|
||||
d_threshold = powf(10., threshold_db/10.); //the level that the sample must be above the moving average in order to qualify as a pulse
|
||||
set_output_multiple(1+d_check_width*2);
|
||||
std::stringstream str;
|
||||
str << name() << unique_id();
|
||||
d_me = pmt::pmt_string_to_symbol(str.str());
|
||||
d_key = pmt::pmt_string_to_symbol("preamble_found");
|
||||
set_history(d_check_width);
|
||||
}
|
||||
|
||||
int air_modes_preamble::work(int noutput_items,
|
||||
@ -56,12 +62,15 @@ int air_modes_preamble::work(int noutput_items,
|
||||
const float *inraw = (const float *) input_items[0];
|
||||
const float *inavg = (const float *) input_items[1];
|
||||
|
||||
//float *outraw = (float *) output_items[0];
|
||||
unsigned char *outattrib = (unsigned char *) output_items[0];
|
||||
float *outraw = (float *) output_items[0];
|
||||
|
||||
int size = noutput_items - d_check_width;
|
||||
int size = noutput_items;// - d_check_width;
|
||||
int pulse_offsets[4];
|
||||
float bit_energies[4];
|
||||
|
||||
memcpy(outraw, inraw, size * sizeof(float));
|
||||
|
||||
uint64_t abs_out_sample_cnt = nitems_written(0);
|
||||
|
||||
for(int i = d_samples_per_chip; i < size; i++) {
|
||||
float pulse_threshold = bit_energy(&inavg[i], d_samples_per_chip) * d_threshold;
|
||||
@ -69,31 +78,19 @@ int air_modes_preamble::work(int noutput_items,
|
||||
float gate_sum_now = 0, gate_sum_early = 0, gate_sum_late = 0;
|
||||
|
||||
if(bit_energy(&inraw[i], d_samples_per_chip) > pulse_threshold) { //if the sample is greater than the reference level by the specified amount
|
||||
//while(inraw[i+1] > inraw[i]) i++;
|
||||
|
||||
//if(inraw[i+1] > inraw[i]) continue; //we're still coming up on the pulse peak, so let's just fall out and look at it next time around
|
||||
//if(inraw[i-1] > inraw[i]) continue; //we're past the peak, so it's no longer a valid pulse
|
||||
|
||||
//a note on the above. this simple early/late gate system works for decim = 16, but doesn't work so great for decim = 8. the extra samples, subject to noise,
|
||||
//mean the peak is not necessarily the center of the bit, and you get fooled into sampling at strange bit edges. the solution is an area integral, a real early/late gate
|
||||
//system, computing the area of a bit and maximizing so you sample at the center of the bit shape for any decimation. for decim = 16 it won't really matter since you only have
|
||||
//one possible bit center.
|
||||
|
||||
int gate_sum = early_late(&inraw[i], d_samples_per_chip); //see modes_energy.cc
|
||||
if(gate_sum != 0) continue; //if either the early gate or the late gate had greater energy, keep moving.
|
||||
// if(gate_sum_late > gate_sum_now) continue;
|
||||
//the packets are so short we choose not to do any sort of closed-loop synchronization after this simple gating.
|
||||
//if we get a good center sample, the drift should be negligible.
|
||||
pulse_offsets[0] = 0;
|
||||
pulse_offsets[1] = int(1.0 * d_samples_per_symbol);
|
||||
pulse_offsets[2] = int(3.5 * d_samples_per_symbol);
|
||||
pulse_offsets[3] = int(4.5 * d_samples_per_symbol);
|
||||
|
||||
//the packets are so short we choose not to do any sort of closed-loop synchronization after this simple gating. if we get a good center sample, the drift should be negligible.
|
||||
|
||||
pulse_offsets[0] = i;
|
||||
pulse_offsets[1] = i+int(1.0 * d_samples_per_symbol);
|
||||
pulse_offsets[2] = i+int(3.5 * d_samples_per_symbol);
|
||||
pulse_offsets[3] = i+int(4.5 * d_samples_per_symbol);
|
||||
|
||||
bit_energies[0] = bit_energy(&inraw[pulse_offsets[0]], d_samples_per_chip);
|
||||
bit_energies[1] = bit_energy(&inraw[pulse_offsets[1]], d_samples_per_chip);
|
||||
bit_energies[2] = bit_energy(&inraw[pulse_offsets[2]], d_samples_per_chip);
|
||||
bit_energies[3] = bit_energy(&inraw[pulse_offsets[3]], d_samples_per_chip);
|
||||
bit_energies[0] = bit_energy(&inraw[i+pulse_offsets[0]], d_samples_per_chip);
|
||||
bit_energies[1] = bit_energy(&inraw[i+pulse_offsets[1]], d_samples_per_chip);
|
||||
bit_energies[2] = bit_energy(&inraw[i+pulse_offsets[2]], d_samples_per_chip);
|
||||
bit_energies[3] = bit_energy(&inraw[i+pulse_offsets[3]], d_samples_per_chip);
|
||||
|
||||
//search for the rest of the pulses at their expected positions
|
||||
if( bit_energies[1] < pulse_threshold) continue;
|
||||
@ -112,58 +109,52 @@ int air_modes_preamble::work(int noutput_items,
|
||||
for(int j = 5 * d_samples_per_symbol; j <= 7.5 * d_samples_per_symbol; j+=d_samples_per_chip)
|
||||
if(bit_energy(&inraw[i+j], d_samples_per_chip) > space_threshold) valid_preamble = false;
|
||||
|
||||
//make sure all four peaks are within 2dB of each other
|
||||
|
||||
|
||||
float minpeak = avgpeak * 0.631; //-2db
|
||||
float maxpeak = avgpeak * 1.585; //2db
|
||||
//make sure all four peaks are within 3dB of each other
|
||||
float minpeak = avgpeak * 0.5;//-3db, was 0.631; //-2db
|
||||
float maxpeak = avgpeak * 2.0;//3db, was 1.585; //2db
|
||||
|
||||
if(bit_energies[0] < minpeak || bit_energies[0] > maxpeak) continue;
|
||||
if(bit_energies[1] < minpeak || bit_energies[1] > maxpeak) continue;
|
||||
if(bit_energies[2] < minpeak || bit_energies[2] > maxpeak) continue;
|
||||
if(bit_energies[3] < minpeak || bit_energies[3] > maxpeak) continue;
|
||||
|
||||
}
|
||||
|
||||
//just for kicks, after validating a preamble, you might want to use all four peaks to form a more accurate "average" center sample time, so that if noise corrupts the first leading edge
|
||||
//sample, you don't mis-sample the entire packet.
|
||||
|
||||
//this could also be done in a separate packet, although it probably saves CPU to do it here
|
||||
//for the 2 samples per chip case, you can just add up the peaks at the expected peak times, then do the same for +1, and -1.
|
||||
if(valid_preamble) {
|
||||
//get a more accurate chip center by finding the energy peak across all four preamble peaks
|
||||
//there's some weirdness in the early part, so i ripped it out.
|
||||
bool early, late;
|
||||
do {
|
||||
early = late = false;
|
||||
//gate_sum_early= bit_energy(&inraw[i+pulse_offsets[0]-1], d_samples_per_chip)
|
||||
// + bit_energy(&inraw[i+pulse_offsets[1]-1], d_samples_per_chip)
|
||||
// + bit_energy(&inraw[i+pulse_offsets[2]-1], d_samples_per_chip)
|
||||
// + bit_energy(&inraw[i+pulse_offsets[3]-1], d_samples_per_chip);
|
||||
|
||||
gate_sum_now = bit_energy(&inraw[i+pulse_offsets[0]], d_samples_per_chip)
|
||||
+ bit_energy(&inraw[i+pulse_offsets[1]], d_samples_per_chip)
|
||||
+ bit_energy(&inraw[i+pulse_offsets[2]], d_samples_per_chip)
|
||||
+ bit_energy(&inraw[i+pulse_offsets[3]], d_samples_per_chip);
|
||||
|
||||
gate_sum_late = bit_energy(&inraw[i+pulse_offsets[0]+1], d_samples_per_chip)
|
||||
+ bit_energy(&inraw[i+pulse_offsets[1]+1], d_samples_per_chip)
|
||||
+ bit_energy(&inraw[i+pulse_offsets[2]+1], d_samples_per_chip)
|
||||
+ bit_energy(&inraw[i+pulse_offsets[3]+1], d_samples_per_chip);
|
||||
|
||||
gate_sum_now = bit_energies[0] + bit_energies[1] + bit_energies[2] + bit_energies[3];
|
||||
// gate_sum_early = bit_energy(&inraw[pulse_offsets[0]-1], d_samples_per_chip)
|
||||
// + bit_energy(&inraw[pulse_offsets[1]-1], d_samples_per_chip)
|
||||
// + bit_energy(&inraw[pulse_offsets[2]-1], d_samples_per_chip)
|
||||
// + bit_energy(&inraw[pulse_offsets[3]-1], d_samples_per_chip);
|
||||
gate_sum_late = bit_energy(&inraw[pulse_offsets[0]+1], d_samples_per_chip)
|
||||
+ bit_energy(&inraw[pulse_offsets[1]+1], d_samples_per_chip)
|
||||
+ bit_energy(&inraw[pulse_offsets[2]+1], d_samples_per_chip)
|
||||
+ bit_energy(&inraw[pulse_offsets[3]+1], d_samples_per_chip);
|
||||
/*
|
||||
if(d_samples_per_chip <= 2) {
|
||||
gate_sum_now = inraw[pulse_offsets[0]+0] + inraw[pulse_offsets[1]+0] + inraw[pulse_offsets[2]+0] + inraw[pulse_offsets[3]+0];
|
||||
gate_sum_early = inraw[pulse_offsets[0]-1] + inraw[pulse_offsets[1]-1] + inraw[pulse_offsets[2]-1] + inraw[pulse_offsets[3]-1];
|
||||
gate_sum_late = inraw[pulse_offsets[0]+1] + inraw[pulse_offsets[1]+1] + inraw[pulse_offsets[2]+1] + inraw[pulse_offsets[3]+1];
|
||||
} else {
|
||||
for(int j = 1-d_samples_per_chip/2; j < d_samples_per_chip/2; j++) {
|
||||
gate_sum_now += inraw[j+pulse_offsets[0]+0] + inraw[j+pulse_offsets[1]+0] + inraw[j+pulse_offsets[2]+0] + inraw[j+pulse_offsets[3]+0];
|
||||
gate_sum_early += inraw[j+pulse_offsets[0]-1] + inraw[j+pulse_offsets[1]-1] + inraw[j+pulse_offsets[2]-1] + inraw[j+pulse_offsets[3]-1];
|
||||
gate_sum_late += inraw[j+pulse_offsets[0]+1] + inraw[j+pulse_offsets[1]+1] + inraw[j+pulse_offsets[2]+1] + inraw[j+pulse_offsets[3]+1];
|
||||
}
|
||||
}
|
||||
*/
|
||||
// if(gate_sum_early > gate_sum_now) { //i think this is redundant
|
||||
// outattrib[i-1] = 1;
|
||||
// }
|
||||
/*else*/ if(gate_sum_late > gate_sum_now) {
|
||||
outattrib[i+1] = 1;
|
||||
i+=1; //so we skip the next one and don't overwrite it
|
||||
}
|
||||
else outattrib[i] = 1;
|
||||
early = (gate_sum_early > gate_sum_now);
|
||||
late = (gate_sum_late > gate_sum_now);
|
||||
if(late) i++;
|
||||
//else if(early) i--;
|
||||
//if(early && late) early = late = false;
|
||||
} while(late);
|
||||
|
||||
} else outattrib[i] = 0;
|
||||
//finally after all this, let's post the preamble!
|
||||
add_item_tag(0, //stream ID
|
||||
nitems_written(0)+i, //sample
|
||||
d_key, //preamble_found
|
||||
pmt::PMT_T, //meaningless for us
|
||||
d_me //block src id
|
||||
);
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ private:
|
||||
int d_samples_per_symbol;
|
||||
float d_threshold_db;
|
||||
float d_threshold;
|
||||
pmt::pmt_t d_me, d_key;
|
||||
|
||||
public:
|
||||
int work (int noutput_items,
|
||||
|
@ -31,6 +31,7 @@
|
||||
#include <iomanip>
|
||||
#include <modes_parity.h>
|
||||
#include <modes_energy.h>
|
||||
#include <gr_tag_info.h>
|
||||
|
||||
extern "C"
|
||||
{
|
||||
@ -45,7 +46,7 @@ air_modes_slicer_sptr air_make_modes_slicer(int channel_rate, gr_msg_queue_sptr
|
||||
|
||||
air_modes_slicer::air_modes_slicer(int channel_rate, gr_msg_queue_sptr queue) :
|
||||
gr_sync_block ("modes_slicer",
|
||||
gr_make_io_signature2 (2, 2, sizeof(float), sizeof(unsigned char)), //stream 0 is received data, stream 1 is binary preamble detector output
|
||||
gr_make_io_signature (1, 1, sizeof(float)), //stream 0 is received data, stream 1 is binary preamble detector output
|
||||
gr_make_io_signature (0, 0, 0) )
|
||||
{
|
||||
//initialize private data here
|
||||
@ -54,195 +55,228 @@ air_modes_slicer::air_modes_slicer(int channel_rate, gr_msg_queue_sptr queue) :
|
||||
d_samples_per_symbol = d_samples_per_chip * 2;
|
||||
d_check_width = 120 * d_samples_per_symbol; //how far you will have to look ahead
|
||||
d_queue = queue;
|
||||
d_secs_per_sample = 1.0 / channel_rate;
|
||||
|
||||
set_output_multiple(1+d_check_width * 2); //how do you specify buffer size for sinks?
|
||||
}
|
||||
|
||||
static bool pmtcompare(pmt::pmt_t x, pmt::pmt_t y)
|
||||
{
|
||||
uint64_t t_x, t_y;
|
||||
t_x = pmt::pmt_to_uint64(pmt::pmt_tuple_ref(x, 0));
|
||||
t_y = pmt::pmt_to_uint64(pmt::pmt_tuple_ref(y, 0));
|
||||
return t_x < t_y;
|
||||
}
|
||||
|
||||
int air_modes_slicer::work(int noutput_items,
|
||||
gr_vector_const_void_star &input_items,
|
||||
gr_vector_void_star &output_items)
|
||||
{
|
||||
//do things!
|
||||
const float *inraw = (const float *) input_items[0];
|
||||
const unsigned char *inattrib = (const unsigned char *) input_items[1];
|
||||
int size = noutput_items - d_check_width; //since it's a sync block, i assume that it runs with ninput_items = noutput_items
|
||||
|
||||
int i;
|
||||
|
||||
std::vector<pmt::pmt_t> tags;
|
||||
uint64_t abs_sample_cnt = nitems_read(0);
|
||||
get_tags_in_range(tags, 0, abs_sample_cnt, abs_sample_cnt + size, pmt::pmt_string_to_symbol("frame_info"));
|
||||
std::vector<pmt::pmt_t>::iterator tag_iter;
|
||||
|
||||
for(tag_iter = tags.begin(); tag_iter != tags.end(); tag_iter++) {
|
||||
uint64_t i = gr_tags::get_nitems(*tag_iter) - abs_sample_cnt;
|
||||
modes_packet rx_packet;
|
||||
framer_packet_type packet_type = framer_packet_type(pmt::pmt_to_long(gr_tags::get_value(*tag_iter)));
|
||||
|
||||
for(i = 0; i < size; i++) {
|
||||
if(inattrib[i] == framer_packet_type(Short_Packet) || inattrib[i] == framer_packet_type(Long_Packet)) { //if there's a packet starting here....
|
||||
modes_packet rx_packet;
|
||||
int packet_length;
|
||||
packet_length = packet_type == framer_packet_type(Short_Packet) ? 56 : 112;
|
||||
|
||||
int packet_length = 112;
|
||||
if(inattrib[i] == framer_packet_type(Short_Packet)) packet_length = 56;
|
||||
|
||||
//printf("Packet received from framer w/length %i\n", packet_length);
|
||||
|
||||
rx_packet.type = framer_packet_type(inattrib[i]);
|
||||
memset(&rx_packet.data, 0x00, 14 * sizeof(unsigned char));
|
||||
memset(&rx_packet.lowconfbits, 0x00, 24 * sizeof(unsigned char));
|
||||
rx_packet.numlowconf = 0;
|
||||
rx_packet.type = packet_type;
|
||||
memset(&rx_packet.data, 0x00, 14 * sizeof(unsigned char));
|
||||
memset(&rx_packet.lowconfbits, 0x00, 24 * sizeof(unsigned char));
|
||||
rx_packet.numlowconf = 0;
|
||||
|
||||
//let's use the preamble marker to get a reference level for the packet
|
||||
rx_packet.reference_level = (bit_energy(&inraw[i], d_samples_per_chip)
|
||||
+ bit_energy(&inraw[i+int(1.0*d_samples_per_symbol)], d_samples_per_chip)
|
||||
+ bit_energy(&inraw[i+int(3.5*d_samples_per_symbol)], d_samples_per_chip)
|
||||
+ bit_energy(&inraw[i+int(4.5*d_samples_per_symbol)], d_samples_per_chip)) / 4;
|
||||
rx_packet.reference_level = (bit_energy(&inraw[i], d_samples_per_chip)
|
||||
+ bit_energy(&inraw[i+int(1.0*d_samples_per_symbol)], d_samples_per_chip)
|
||||
+ bit_energy(&inraw[i+int(3.5*d_samples_per_symbol)], d_samples_per_chip)
|
||||
+ bit_energy(&inraw[i+int(4.5*d_samples_per_symbol)], d_samples_per_chip)) / 4;
|
||||
|
||||
i += 8 * d_samples_per_symbol; //move to the center of the first bit of the data
|
||||
i += 8 * d_samples_per_symbol; //move to the center of the first bit of the data
|
||||
|
||||
//here we calculate the total energy contained in each chip of the symbol
|
||||
for(int j = 0; j < packet_length; j++) {
|
||||
int firstchip = i+j*d_samples_per_symbol;
|
||||
int secondchip = firstchip + d_samples_per_chip;
|
||||
bool slice, confidence;
|
||||
float firstchip_energy=0, secondchip_energy=0;
|
||||
//here we calculate the total energy contained in each chip of the symbol
|
||||
for(int j = 0; j < packet_length; j++) {
|
||||
int firstchip = i+j*d_samples_per_symbol;
|
||||
int secondchip = firstchip + d_samples_per_chip;
|
||||
bool slice, confidence;
|
||||
float firstchip_energy=0, secondchip_energy=0;
|
||||
|
||||
firstchip_energy = bit_energy(&inraw[firstchip], d_samples_per_chip);
|
||||
secondchip_energy = bit_energy(&inraw[secondchip], d_samples_per_chip);
|
||||
firstchip_energy = bit_energy(&inraw[firstchip], d_samples_per_chip);
|
||||
secondchip_energy = bit_energy(&inraw[secondchip], d_samples_per_chip);
|
||||
|
||||
//3dB limits for bit slicing and confidence measurement
|
||||
float highlimit=rx_packet.reference_level*2;
|
||||
float lowlimit=rx_packet.reference_level*0.5;
|
||||
bool firstchip_inref = ((firstchip_energy > lowlimit) && (firstchip_energy < highlimit));
|
||||
bool secondchip_inref = ((secondchip_energy > lowlimit) && (secondchip_energy < highlimit));
|
||||
//3dB limits for bit slicing and confidence measurement
|
||||
float highlimit=rx_packet.reference_level*2;
|
||||
float lowlimit=rx_packet.reference_level*0.5;
|
||||
bool firstchip_inref = ((firstchip_energy > lowlimit) && (firstchip_energy < highlimit));
|
||||
bool secondchip_inref = ((secondchip_energy > lowlimit) && (secondchip_energy < highlimit));
|
||||
|
||||
//these two lines for a super simple naive slicer.
|
||||
// slice = firstchip_energy > secondchip_energy;
|
||||
// confidence = bool(int(firstchip_inref) + int(secondchip_inref)); //one and only one chip in the reference zone
|
||||
//these two lines for a super simple naive slicer.
|
||||
// slice = firstchip_energy > secondchip_energy;
|
||||
// confidence = bool(int(firstchip_inref) + int(secondchip_inref)); //one and only one chip in the reference zone
|
||||
|
||||
//below is the Lincoln Labs slicer. it may produce greater bit errors. supposedly it is more resistant to mode A/C FRUIT.
|
||||
//below is the Lincoln Labs slicer. it may produce greater bit errors. supposedly it is more resistant to mode A/C FRUIT.
|
||||
//see http://adsb.tc.faa.gov/WG3_Meetings/Meeting8/Squitter-Lon.pdf
|
||||
if(firstchip_inref && !secondchip_inref) {
|
||||
slice = 1;
|
||||
confidence = 1;
|
||||
}
|
||||
else if(secondchip_inref && !firstchip_inref) {
|
||||
slice = 0;
|
||||
confidence = 1;
|
||||
}
|
||||
else if(firstchip_inref && secondchip_inref) {
|
||||
slice = firstchip_energy > secondchip_energy;
|
||||
confidence = 0;
|
||||
}
|
||||
else if(!firstchip_inref && !secondchip_inref) { //in this case, we determine the bit by whichever is larger, and we determine high confidence if the low chip is 6dB below reference.
|
||||
slice = firstchip_energy > secondchip_energy;
|
||||
if(slice) {
|
||||
if(secondchip_energy < lowlimit * 0.5) confidence = 1;
|
||||
else confidence = 0;
|
||||
} else {
|
||||
if(firstchip_energy < lowlimit * 0.5) confidence = 1;
|
||||
else confidence = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//put the data into the packet
|
||||
if(firstchip_inref && !secondchip_inref) {
|
||||
slice = 1;
|
||||
confidence = 1;
|
||||
}
|
||||
else if(secondchip_inref && !firstchip_inref) {
|
||||
slice = 0;
|
||||
confidence = 1;
|
||||
}
|
||||
else if(firstchip_inref && secondchip_inref) {
|
||||
slice = firstchip_energy > secondchip_energy;
|
||||
confidence = 0;
|
||||
}
|
||||
else if(!firstchip_inref && !secondchip_inref) { //in this case, we determine the bit by whichever is larger, and we determine high confidence if the low chip is 6dB below reference.
|
||||
slice = firstchip_energy > secondchip_energy;
|
||||
if(slice) {
|
||||
rx_packet.data[j/8] += 1 << (7-(j%8));
|
||||
}
|
||||
//put the confidence decision into the packet
|
||||
if(confidence) {
|
||||
//rx_packet.confidence[j/8] += 1 << (7-(j%8));
|
||||
if(secondchip_energy < lowlimit * 0.5) confidence = 1;
|
||||
else confidence = 0;
|
||||
} else {
|
||||
if(rx_packet.numlowconf < 24) rx_packet.lowconfbits[rx_packet.numlowconf++] = j;
|
||||
if(firstchip_energy < lowlimit * 0.5) confidence = 1;
|
||||
else confidence = 0;
|
||||
}
|
||||
|
||||
}
|
||||
i += packet_length * d_samples_per_symbol;
|
||||
|
||||
//here you might want to traverse the whole packet and if you find all 0's, just toss it. don't know why these packets turn up, but they pass ECC.
|
||||
bool zeroes = 1;
|
||||
for(int m = 0; m < 14; m++) {
|
||||
if(rx_packet.data[m]) zeroes = 0;
|
||||
//put the data into the packet
|
||||
if(slice) {
|
||||
rx_packet.data[j/8] += 1 << (7-(j%8));
|
||||
}
|
||||
if(zeroes) continue; //toss it
|
||||
|
||||
rx_packet.message_type = (rx_packet.data[0] >> 3) & 0x1F; //get the message type for the parser to conveniently use, and to make decisions on ECC methods
|
||||
|
||||
//we note that short packets other than type 11 CANNOT be reliably decoded, since the a/c address is encoded with the parity bits.
|
||||
//mode S in production ATC use relies on the fact that these short packets are reply squitters to transponder requests,
|
||||
//and so the radar should already know the expected a/c reply address. so, error-correction makes no sense on short packets (other than type 11)
|
||||
//this means two things: first, we will DROP short packets (other than type 11) with ANY low-confidence bits, since we can't be confident that we're seeing real data
|
||||
//second, we will only perform error correction on LONG type S packets.
|
||||
|
||||
//the limitation on short packets means in practice a short packet has to be at least 6dB above the noise floor in order to be output. long packets can theoretically
|
||||
//be decoded at the 3dB SNR point. below that and the preamble detector won't fire.
|
||||
|
||||
//in practice, this limitation causes you to see a HUGE number of type 11 packets which pass CRC through random luck.
|
||||
//these packets necessarily have large numbers of low-confidence bits, so we toss them with an arbitrary limit of 10.
|
||||
//that's a pretty dang low threshold so i don't think we'll drop many legit packets
|
||||
|
||||
if(rx_packet.type == Short_Packet && rx_packet.message_type != 11 && rx_packet.numlowconf != 0) continue;
|
||||
if(rx_packet.type == Short_Packet && rx_packet.message_type == 11 && rx_packet.numlowconf >= 10) continue;
|
||||
|
||||
|
||||
//if(rx_packet.numlowconf >= 24) continue; //don't even try, this is the maximum number of errors ECC could possibly correct
|
||||
//the above line should be part of ECC, and only checked if the message has parity errors
|
||||
|
||||
rx_packet.parity = modes_check_parity(rx_packet.data, packet_length);
|
||||
|
||||
if(rx_packet.parity && rx_packet.type == Long_Packet) {
|
||||
// long before = rx_packet.parity;
|
||||
bruteResultTypeDef bruteResult = modes_ec_brute(rx_packet);
|
||||
|
||||
if(bruteResult == No_Solution) {
|
||||
//printf("No solution!\n");
|
||||
continue;
|
||||
} else if(bruteResult == Multiple_Solutions) {
|
||||
// printf("Multiple solutions!\n");
|
||||
continue;
|
||||
} else if(bruteResult == Too_Many_LCBs) {
|
||||
//printf("Too many LCBs (%i)!\n", rx_packet.numlowconf);
|
||||
continue;
|
||||
} else if(bruteResult == No_Error) {
|
||||
// printf("No error!\n");
|
||||
} else if(bruteResult == Solution_Found) {
|
||||
// printf("Solution found for %i LCBs!\n", rx_packet.numlowconf);
|
||||
}
|
||||
// rx_packet.parity = modes_check_parity(rx_packet.data, packet_length);
|
||||
// if(rx_packet.parity) printf("Error: packet fails parity check after correction, was %x, now %x\n", before, rx_packet.parity);
|
||||
//put the confidence decision into the packet
|
||||
if(confidence) {
|
||||
//rx_packet.confidence[j/8] += 1 << (7-(j%8));
|
||||
} else {
|
||||
if(rx_packet.numlowconf < 24) rx_packet.lowconfbits[rx_packet.numlowconf++] = j;
|
||||
}
|
||||
}
|
||||
|
||||
/******************** BEGIN TIMESTAMP BS ******************/
|
||||
rx_packet.timestamp_secs = 0;
|
||||
rx_packet.timestamp_frac = 0;
|
||||
|
||||
uint64_t abs_sample_cnt = nitems_read(0);
|
||||
std::vector<pmt::pmt_t> tags;
|
||||
uint64_t timestamp_secs, timestamp_sample, timestamp_delta;
|
||||
double timestamp_frac;
|
||||
|
||||
pmt::pmt_t timestamp;
|
||||
|
||||
get_tags_in_range(tags, 0, abs_sample_cnt, abs_sample_cnt + i, pmt::pmt_string_to_symbol("packet_time_stamp"));
|
||||
//tags.back() is the most recent timestamp, then.
|
||||
if(tags.size() > 0) {
|
||||
//if nobody but the USRP is producing timestamps this isn't necessary
|
||||
//std::sort(tags.begin(), tags.end(), pmtcompare);
|
||||
timestamp = tags.back();
|
||||
|
||||
timestamp_secs = pmt_to_uint64(pmt_tuple_ref(gr_tags::get_value(timestamp), 0));
|
||||
timestamp_frac = pmt_to_double(pmt_tuple_ref(gr_tags::get_value(timestamp), 1));
|
||||
timestamp_sample = gr_tags::get_nitems(timestamp);
|
||||
//now we have to offset the timestamp based on the current sample number
|
||||
timestamp_delta = (abs_sample_cnt + i) - timestamp_sample;
|
||||
|
||||
timestamp_frac += timestamp_delta * d_secs_per_sample;
|
||||
if(timestamp_frac > 1.0) {
|
||||
timestamp_frac -= 1.0;
|
||||
timestamp_secs++;
|
||||
}
|
||||
|
||||
rx_packet.timestamp_secs = timestamp_secs;
|
||||
rx_packet.timestamp_frac = timestamp_frac;
|
||||
}
|
||||
|
||||
// if(rx_packet.parity && rx_packet.type == Long_Packet) printf("Error! Bad packet forwarded to the queue.\n");
|
||||
//now we have a complete packet with confidence data, let's print it to the message queue
|
||||
//here, rather than send the entire packet, since we've already done parity checking and ECC in C++, we'll
|
||||
//send just the data (no confidence bits), separated into fields for easier parsing.
|
||||
/******************* END TIMESTAMP BS *********************/
|
||||
|
||||
//increment for the next round
|
||||
|
||||
//we'll replicate some data by sending the message type as the first field, followed by the first 8+24=32 bits of the packet, followed by
|
||||
//56 long packet data bits if applicable (zero-padded if not), followed by parity
|
||||
//here you might want to traverse the whole packet and if you find all 0's, just toss it. don't know why these packets turn up, but they pass ECC.
|
||||
bool zeroes = 1;
|
||||
for(int m = 0; m < 14; m++) {
|
||||
if(rx_packet.data[m]) zeroes = 0;
|
||||
}
|
||||
if(zeroes) continue; //toss it
|
||||
|
||||
d_payload.str("");
|
||||
rx_packet.message_type = (rx_packet.data[0] >> 3) & 0x1F; //get the message type for the parser to conveniently use, and to make decisions on ECC methods
|
||||
|
||||
d_payload << std::dec << std::setw(2) << std::setfill('0') << rx_packet.message_type << std::hex << " ";
|
||||
//we note that short packets other than type 11 CANNOT be reliably decoded, since the a/c address is encoded with the parity bits.
|
||||
//mode S in production ATC use relies on the fact that these short packets are reply squitters to transponder requests,
|
||||
//and so the radar should already know the expected a/c reply address. so, error-correction makes no sense on short packets (other than type 11)
|
||||
//this means two things: first, we will DROP short packets (other than type 11) with ANY low-confidence bits, since we can't be confident that we're seeing real data
|
||||
//second, we will only perform error correction on LONG type S packets.
|
||||
|
||||
for(int m = 0; m < 4; m++) {
|
||||
//the limitation on short packets means in practice a short packet has to be at least 6dB above the noise floor in order to be output. long packets can theoretically
|
||||
//be decoded at the 3dB SNR point. below that and the preamble detector won't fire.
|
||||
|
||||
//in practice, this limitation causes you to see a HUGE number of type 11 packets which pass CRC through random luck.
|
||||
//these packets necessarily have large numbers of low-confidence bits, so we toss them with an arbitrary limit of 10.
|
||||
//that's a pretty dang low threshold so i don't think we'll drop many legit packets
|
||||
|
||||
if(rx_packet.type == Short_Packet && rx_packet.message_type != 11 && rx_packet.numlowconf != 0) continue;
|
||||
if(rx_packet.type == Short_Packet && rx_packet.message_type == 11 && rx_packet.numlowconf >= 10) continue;
|
||||
|
||||
|
||||
//if(rx_packet.numlowconf >= 24) continue; //don't even try, this is the maximum number of errors ECC could possibly correct
|
||||
//the above line should be part of ECC, and only checked if the message has parity errors
|
||||
|
||||
rx_packet.parity = modes_check_parity(rx_packet.data, packet_length);
|
||||
|
||||
if(rx_packet.parity && rx_packet.type == Long_Packet) {
|
||||
bruteResultTypeDef bruteResult = modes_ec_brute(rx_packet);
|
||||
|
||||
if(bruteResult == No_Solution) {
|
||||
continue;
|
||||
} else if(bruteResult == Multiple_Solutions) {
|
||||
continue;
|
||||
} else if(bruteResult == Too_Many_LCBs) {
|
||||
continue;
|
||||
} else if(bruteResult == No_Error) {
|
||||
} else if(bruteResult == Solution_Found) {
|
||||
// printf("Solution found for %i LCBs!\n", rx_packet.numlowconf);
|
||||
}
|
||||
}
|
||||
|
||||
//we'll replicate some data by sending the message type as the first field, followed by the first 8+24=32 bits of the packet, followed by
|
||||
//56 long packet data bits if applicable (zero-padded if not), followed by parity
|
||||
|
||||
d_payload.str("");
|
||||
d_payload << std::dec << std::setw(2) << std::setfill('0') << rx_packet.message_type << std::hex << " ";
|
||||
for(int m = 0; m < 4; m++) {
|
||||
d_payload << std::setw(2) << std::setfill('0') << unsigned(rx_packet.data[m]);
|
||||
}
|
||||
d_payload << " ";
|
||||
if(packet_length == 112) {
|
||||
for(int m = 4; m < 11; m++) {
|
||||
d_payload << std::setw(2) << std::setfill('0') << unsigned(rx_packet.data[m]);
|
||||
}
|
||||
d_payload << " ";
|
||||
if(packet_length == 112) {
|
||||
for(int m = 4; m < 11; m++) {
|
||||
d_payload << std::setw(2) << std::setfill('0') << unsigned(rx_packet.data[m]);
|
||||
}
|
||||
d_payload << " ";
|
||||
for(int m = 11; m < 14; m++) {
|
||||
d_payload << std::setw(2) << std::setfill('0') << unsigned(rx_packet.data[m]);
|
||||
}
|
||||
} else {
|
||||
for(int m = 4; m < 11; m++) {
|
||||
d_payload << std::setw(2) << std::setfill('0') << unsigned(0);
|
||||
}
|
||||
d_payload << " ";
|
||||
for(int m = 4; m < 7; m++) {
|
||||
d_payload << std::setw(2) << std::setfill('0') << unsigned(rx_packet.data[m]);
|
||||
}
|
||||
for(int m = 11; m < 14; m++) {
|
||||
d_payload << std::setw(2) << std::setfill('0') << unsigned(rx_packet.data[m]);
|
||||
}
|
||||
} else {
|
||||
for(int m = 4; m < 11; m++) {
|
||||
d_payload << std::setw(2) << std::setfill('0') << unsigned(0);
|
||||
}
|
||||
d_payload << " ";
|
||||
for(int m = 4; m < 7; m++) {
|
||||
d_payload << std::setw(2) << std::setfill('0') << unsigned(rx_packet.data[m]);
|
||||
}
|
||||
|
||||
d_payload << " " << std::setw(6) << rx_packet.parity << " " << std::dec << rx_packet.reference_level;
|
||||
|
||||
gr_message_sptr msg = gr_make_message_from_string(std::string(d_payload.str()));
|
||||
d_queue->handle(msg);
|
||||
|
||||
}
|
||||
|
||||
d_payload << " " << std::setw(6) << rx_packet.parity << " " << std::dec << rx_packet.reference_level
|
||||
<< " " << rx_packet.timestamp_secs
|
||||
<< " " << std::setprecision(10) << std::setw(10) << rx_packet.timestamp_frac;
|
||||
gr_message_sptr msg = gr_make_message_from_string(std::string(d_payload.str()));
|
||||
d_queue->handle(msg);
|
||||
|
||||
}
|
||||
|
||||
return size;
|
||||
|
@ -45,6 +45,7 @@ private:
|
||||
int d_chip_rate;
|
||||
int d_samples_per_chip;
|
||||
int d_samples_per_symbol;
|
||||
double d_secs_per_sample;
|
||||
gr_msg_queue_sptr d_queue;
|
||||
std::ostringstream d_payload;
|
||||
|
||||
|
@ -36,6 +36,8 @@ struct modes_packet {
|
||||
framer_packet_type type; //what length packet are we
|
||||
unsigned int message_type;
|
||||
float reference_level;
|
||||
unsigned long timestamp_secs; //timestamp from tags
|
||||
double timestamp_frac;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
13
src/python/mlat-test.py
Executable file
13
src/python/mlat-test.py
Executable file
@ -0,0 +1,13 @@
|
||||
#!/usr/bin/python
|
||||
import mlat
|
||||
import numpy
|
||||
|
||||
replies = []
|
||||
for i in range(0, len(mlat.teststations)):
|
||||
replies.append((mlat.teststations[i], mlat.teststamps[i]))
|
||||
|
||||
ans = mlat.mlat(replies, mlat.testalt)
|
||||
error = numpy.linalg.norm(numpy.array(mlat.llh2ecef(ans))-numpy.array(mlat.testplane))
|
||||
range = numpy.linalg.norm(mlat.llh2geoid(ans)-numpy.array(mlat.llh2geoid(mlat.teststations[0])))
|
||||
print "Error: %.2fm" % (error)
|
||||
print "Range: %.2fkm (from first station in list)" % (range/1000)
|
235
src/python/mlat.py
Executable file
235
src/python/mlat.py
Executable file
@ -0,0 +1,235 @@
|
||||
#!/usr/bin/python
|
||||
import math
|
||||
import numpy
|
||||
from scipy.ndimage import map_coordinates
|
||||
|
||||
#functions for multilateration.
|
||||
|
||||
#NB: because of the way this solver works, at least 3 stations and timestamps
|
||||
#are required. this function will not return hyperbolae for underconstrained systems.
|
||||
#TODO: get HDOP out of this so we can draw circles of likely position and indicate constraint
|
||||
|
||||
##########################NOTES#########################################
|
||||
#you should test your solver with a reference dataset that you've calculated. let's put one together.
|
||||
#let's say we have you here in SF, ER in MV, and some place in Fremont. the plane can be at SFO at 24000'.
|
||||
#your pos: 37.76225, -122.44254, 300'
|
||||
#ettus: 37.409044, -122.077748, 300'
|
||||
#fremont: 37.585085, -121.986395, 300'
|
||||
#airplane: 37.617175,-122.380843, 24000'
|
||||
|
||||
#calculated using a third-party tool and verified against the algorithms in this file:
|
||||
#you: -2708399, -4260759, 3884677
|
||||
#ettus: -2693916, -4298177, 3853611
|
||||
#fremont: -2680759, -4292379, 3869113
|
||||
#airplane: -2712433, -4277271, 3876757
|
||||
|
||||
#here's how I did it in Octave...
|
||||
#prange_est = ((stations(:, 1) - xguess(1)).^2 + (stations(:, 2) - xguess(2)).^2 + (stations(:,3) - xguess(3)).^2).^0.5;
|
||||
#dphat = prange_obs - prange_est;
|
||||
#H = [[-(stations(:,1)-xguess(1)) ./ prange_est, -(stations(:,2)-xguess(2)) ./ prange_est, -(stations(:,3)-xguess(3)) ./ prange_est]];
|
||||
#xerr = (H'*H\H'*dphat)';
|
||||
#xguess = xguess + xerr
|
||||
#remember the last line of stations is the 0 vector. remember the last line of prange_obs is the height of the a/c above geoid.
|
||||
|
||||
#use one of the station positions as a starting guess. calculate alt-above-geoid once as though it were located directly above the starting guess; 300 miles
|
||||
#is like 4 degrees of latitude and i don't think it'll introduce more than a few meters of error.
|
||||
|
||||
#well, there are some places where 4 degrees of latitude would count for 20 meters of error. but since our accuracy is really +/- 250m anyway it's probably not
|
||||
#a big deal. saves a lot of CPU to only do the lookup once.
|
||||
|
||||
#so, the above solver works for pseudorange, but what if you only have time-of-flight info?
|
||||
#let's modify the prange eq to deal with time difference of arrival instead of pseudorange
|
||||
#knowns: time of arrival at each station, positions of each station, altitude of a/c
|
||||
#from this, you can say "it arrived x ns sooner at each of the other stations"
|
||||
#thus you can say "it's this much closer/farther to station y than to me"
|
||||
|
||||
#the stations vector is RELATIVE TO YOU -- so it's [ettus-me; fremont-me; [0,0,0]-me]
|
||||
#prange_obs is a vector of TDOAs; the first value (earliest) is truncated since the difference is zero (just like stations)
|
||||
#this implies the TDOAs should arrive sorted, which is probably a good idea, or at the very least the closest station should be first
|
||||
|
||||
#prange_est = [norm(stations(1,:)-xguess);
|
||||
# norm(stations(2,:)-xguess);
|
||||
# norm(stations(3,:)-xguess)]; #only valid for the three-station case we're testing Octave with
|
||||
#dphat = prange_obs - prange_est;
|
||||
#H = [[-(stations(:,1)-xguess(1)) ./ prange_est, -(stations(:,2)-xguess(2)) ./ prange_est, -(stations(:,3)-xguess(3)) ./ prange_est]];
|
||||
#xguess += (H'*H\H'*dphat)';
|
||||
#err=norm(airplane-(xguess+me)) #just for calculating convergence
|
||||
|
||||
#it converges for 500km position error in the initial guess, so it seems pretty good.
|
||||
#seems to converge quickly in the terminal phase. 250m timing offset gives 450m position error
|
||||
#250m time offset in the local receiver (830ns) gives 325m position error
|
||||
|
||||
#the last question is how to use the altitude data in prange_obs; calculate height above geoid for YOUR position at a/c alt and use that to get height above [0,0,0]
|
||||
#this will be close enough. you could iterate this along with the rest of it but it won't change more than the variation in geoid height between you and a/c.
|
||||
#maybe after convergence you can iterate a few more times with the a/c geoid height if you're worried about it.
|
||||
#there's probably a way to use YOU as the alt. station and just use height above YOU instead. but this works.
|
||||
|
||||
########################END NOTES#######################################
|
||||
|
||||
|
||||
#this is a 10x10-degree WGS84 geoid datum, in meters relative to the WGS84 reference ellipsoid. given the maximum slope, you should probably interpolate.
|
||||
#NIMA suggests a 2x2 interpolation using four neighbors. we'll go cubic spline JUST BECAUSE WE CAN
|
||||
wgs84_geoid = numpy.array([[13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13], #90N
|
||||
[3,1,-2,-3,-3,-3,-1,3,1,5,9,11,19,27,31,34,33,34,33,34,28,23,17,13,9,4,4,1,-2,-2,0,2,3,2,1,1], #80N
|
||||
[2,2,1,-1,-3,-7,-14,-24,-27,-25,-19,3,24,37,47,60,61,58,51,43,29,20,12,5,-2,-10,-14,-12,-10,-14,-12,-6,-2,3,6,4], #70N
|
||||
[2,9,17,10,13,1,-14,-30,-39,-46,-42,-21,6,29,49,65,60,57,47,41,21,18,14,7,-3,-22,-29,-32,-32,-26,-15,-2,13,17,19,6], #60N
|
||||
[-8,8,8,1,-11,-19,-16,-18,-22,-35,-40,-26,-12,24,45,63,62,59,47,48,42,28,12,-10,-19,-33,-43,-42,-43,-29,-2,17,23,22,6,2], #50N
|
||||
[-12,-10,-13,-20,-31,-34,-21,-16,-26,-34,-33,-35,-26,2,33,59,52,51,52,48,35,40,33,-9,-28,-39,-48,-59,-50,-28,3,23,37,18,-1,-11], #40N
|
||||
[-7,-5,-8,-15,-28,-40,-42,-29,-22,-26,-32,-51,-40,-17,17,31,34,44,36,28,29,17,12,-20,-15,-40,-33,-34,-34,-28,7,29,43,20,4,-6], #30N
|
||||
[5,10,7,-7,-23,-39,-47,-34,-9,-10,-20,-45,-48,-32,-9,17,25,31,31,26,15,6,1,-29,-44,-61,-67,-59,-36,-11,21,39,49,39,22,10], #20N
|
||||
[13,12,11,2,-11,-28,-38,-29,-10,3,1,-11,-41,-42,-16,3,17,33,22,23,2,-3,-7,-36,-59,-90,-95,-63,-24,12,53,60,58,46,36,26], #10N
|
||||
[22,16,17,13,1,-12,-23,-20,-14,-3,14,10,-15,-27,-18,3,12,20,18,12,-13,-9,-28,-49,-62,-89,-102,-63,-9,33,58,73,74,63,50,32], #0
|
||||
[36,22,11,6,-1,-8,-10,-8,-11,-9,1,32,4,-18,-13,-9,4,14,12,13,-2,-14,-25,-32,-38,-60,-75,-63,-26,0,35,52,68,76,64,52], #10S
|
||||
[51,27,10,0,-9,-11,-5,-2,-3,-1,9,35,20,-5,-6,-5,0,13,17,23,21,8,-9,-10,-11,-20,-40,-47,-45,-25,5,23,45,58,57,63], #20S
|
||||
[46,22,5,-2,-8,-13,-10,-7,-4,1,9,32,16,4,-8,4,12,15,22,27,34,29,14,15,15,7,-9,-25,-37,-39,-23,-14,15,33,34,45], #30S
|
||||
[21,6,1,-7,-12,-12,-12,-10,-7,-1,8,23,15,-2,-6,6,21,24,18,26,31,33,39,41,30,24,13,-2,-20,-32,-33,-27,-14,-2,5,20], #40S
|
||||
[-15,-18,-18,-16,-17,-15,-10,-10,-8,-2,6,14,13,3,3,10,20,27,25,26,34,39,45,45,38,39,28,13,-1,-15,-22,-22,-18,-15,-14,-10], #50S
|
||||
[-45,-43,-37,-32,-30,-26,-23,-22,-16,-10,-2,10,20,20,21,24,22,17,16,19,25,30,35,35,33,30,27,10,-2,-14,-23,-30,-33,-29,-35,-43], #60S
|
||||
[-61,-60,-61,-55,-49,-44,-38,-31,-25,-16,-6,1,4,5,4,2,6,12,16,16,17,21,20,26,26,22,16,10,-1,-16,-29,-36,-46,-55,-54,-59], #70S
|
||||
[-53,-54,-55,-52,-48,-42,-38,-38,-29,-26,-26,-24,-23,-21,-19,-16,-12,-8,-4,-1,1,4,4,6,5,4,2,-6,-15,-24,-33,-40,-48,-50,-53,-52], #80S
|
||||
[-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30,-30]], #90S
|
||||
dtype=numpy.float)
|
||||
|
||||
#ok this calculates the geoid offset from the reference ellipsoid
|
||||
#combined with LLH->ECEF this gets you XYZ for a ground-referenced point
|
||||
def wgs84_height(lat, lon):
|
||||
yi = numpy.array([9-lat/10.0])
|
||||
xi = numpy.array([18+lon/10.0])
|
||||
return float(map_coordinates(wgs84_geoid, [yi, xi]))
|
||||
|
||||
#WGS84 reference ellipsoid constants
|
||||
wgs84_a = 6378137.0
|
||||
wgs84_b = 6356752.314245
|
||||
wgs84_e2 = 0.0066943799901975848
|
||||
wgs84_a2 = wgs84_a**2 #to speed things up a bit
|
||||
wgs84_b2 = wgs84_b**2
|
||||
|
||||
#convert ECEF to lat/lon/alt without geoid correction
|
||||
#returns alt in meters
|
||||
def ecef2llh((x,y,z)):
|
||||
ep = math.sqrt((wgs84_a2 - wgs84_b2) / wgs84_b2)
|
||||
p = math.sqrt(x**2+y**2)
|
||||
th = math.atan2(wgs84_a*z, wgs84_b*p)
|
||||
lon = math.atan2(y, x)
|
||||
lat = math.atan2(z+ep**2*wgs84_b*math.sin(th)**3, p-wgs84_e2*wgs84_a*math.cos(th)**3)
|
||||
N = wgs84_a / math.sqrt(1-wgs84_e2*math.sin(lat)**2)
|
||||
alt = p / math.cos(lat) - N
|
||||
|
||||
lon *= (180. / math.pi)
|
||||
lat *= (180. / math.pi)
|
||||
|
||||
return [lat, lon, alt]
|
||||
|
||||
#convert lat/lon/alt coords to ECEF without geoid correction, WGS84 model
|
||||
#remember that alt is in meters
|
||||
def llh2ecef((lat, lon, alt)):
|
||||
lat *= (math.pi / 180.0)
|
||||
lon *= (math.pi / 180.0)
|
||||
|
||||
n = lambda x: wgs84_a / math.sqrt(1 - wgs84_e2*(math.sin(x)**2))
|
||||
|
||||
x = (n(lat) + alt)*math.cos(lat)*math.cos(lon)
|
||||
y = (n(lat) + alt)*math.cos(lat)*math.sin(lon)
|
||||
z = (n(lat)*(1-wgs84_e2)+alt)*math.sin(lat)
|
||||
|
||||
return [x,y,z]
|
||||
|
||||
#do both of the above to get a geoid-corrected x,y,z position
|
||||
def llh2geoid((lat, lon, alt)):
|
||||
(x,y,z) = llh2ecef((lat, lon, alt + wgs84_height(lat, lon)))
|
||||
return [x,y,z]
|
||||
|
||||
|
||||
c = 299792458 / 1.0003 #modified for refractive index of air, why not
|
||||
|
||||
#here's some test data to validate the algorithm
|
||||
teststations = [[37.76225, -122.44254, 100], [37.409044, -122.077748, 100], [37.585085, -121.986395, 100]]
|
||||
testalt = 8000
|
||||
testplane = numpy.array(llh2ecef([37.617175,-122.380843, testalt]))
|
||||
testme = llh2geoid(teststations[0])
|
||||
teststamps = [10,
|
||||
10 + numpy.linalg.norm(testplane-numpy.array(llh2geoid(teststations[1]))) / c,
|
||||
10 + numpy.linalg.norm(testplane-numpy.array(llh2geoid(teststations[2]))) / c,
|
||||
]
|
||||
|
||||
#this function is the iterative solver core of the mlat function below
|
||||
#we use limit as a goal to stop solving when we get "close enough" (error magnitude in meters for that iteration)
|
||||
#basically 20 meters is way less than the anticipated error of the system so it doesn't make sense to continue
|
||||
#it's possible this could fail in situations where the solution converges slowly
|
||||
#because the change in ERROR is not necessarily the error itself
|
||||
#still, it should converge quickly when close, so it SHOULDN'T give more than 2-3x the precision in total error
|
||||
def mlat_iter(rel_stations, prange_obs, xguess = [0,0,0], limit = 20, maxrounds = 50):
|
||||
xerr = [1e9, 1e9, 1e9]
|
||||
rounds = 0
|
||||
while numpy.linalg.norm(xerr) > limit:
|
||||
prange_est = []
|
||||
for station in rel_stations:
|
||||
prange_est.append([numpy.linalg.norm(station - xguess)])
|
||||
dphat = prange_obs - prange_est
|
||||
H = []
|
||||
for row in range(0,len(rel_stations)):
|
||||
H.append((numpy.array(-rel_stations[row,:])-xguess) / prange_est[row])
|
||||
H = numpy.array(H)
|
||||
#now we have H, the Jacobian, and can solve for residual error
|
||||
xerr = numpy.dot(numpy.linalg.solve(numpy.dot(H.T,H), H.T), dphat).flatten()
|
||||
xguess += xerr
|
||||
rounds += 1
|
||||
if rounds > maxrounds:
|
||||
raise Exception("Failed to converge!")
|
||||
break
|
||||
return xguess
|
||||
|
||||
#func mlat:
|
||||
#uses a modified GPS pseudorange solver to locate aircraft by multilateration.
|
||||
#replies is a list of reports, in ([lat, lon, alt], timestamp) format
|
||||
#altitude is the barometric altitude of the aircraft as returned by the aircraft
|
||||
#returns the estimated position of the aircraft in (lat, lon, alt) geoid-corrected WGS84.
|
||||
#let's make it take a list of tuples so we can sort by them
|
||||
def mlat(replies, altitude):
|
||||
sorted_replies = sorted(replies, key=lambda time: time[1])
|
||||
|
||||
stations = []
|
||||
timestamps = []
|
||||
#i really couldn't figure out how to unpack this, sorry
|
||||
for i in range(0, len(replies)):
|
||||
stations.append( sorted_replies[i][0] )
|
||||
timestamps.append( sorted_replies[i][1] )
|
||||
|
||||
me_llh = stations[0]
|
||||
me = llh2geoid(stations[0])
|
||||
|
||||
rel_stations = [] #list of stations in XYZ relative to me
|
||||
for station in stations[1:]:
|
||||
rel_stations.append(numpy.array(llh2geoid(station)) - numpy.array(me))
|
||||
rel_stations.append([0,0,0]-numpy.array(me)) #arne saknussemm, reporting in
|
||||
rel_stations = numpy.array(rel_stations) #convert list of arrays to 2d array
|
||||
|
||||
tdoa = []
|
||||
for stamp in timestamps[1:]:
|
||||
tdoa.append(stamp - timestamps[0])
|
||||
|
||||
prange_obs = []
|
||||
for stamp in tdoa:
|
||||
prange_obs.append([c * stamp])
|
||||
|
||||
#so here we calc the estimated pseudorange to the center of the earth, using station[0] as a reference point for the geoid
|
||||
#this is a necessary approximation since we don't know the location of the aircraft yet
|
||||
#if the dang earth were actually round this wouldn't be an issue
|
||||
prange_obs.append( [numpy.linalg.norm(llh2ecef((me_llh[0], me_llh[1], altitude)))] ) #use ECEF not geoid since alt is MSL not GPS
|
||||
#prange_obs.append( [numpy.linalg.norm(testplane)]) #test for error
|
||||
prange_obs = numpy.array(prange_obs)
|
||||
|
||||
xyzpos = mlat_iter(rel_stations, prange_obs)
|
||||
llhpos = ecef2llh(xyzpos+me)
|
||||
|
||||
#now, we could return llhpos right now and be done with it.
|
||||
#but the assumption we made above, namely that the aircraft is directly above the
|
||||
#nearest station, results in significant error due to the oblateness of the Earth's geometry.
|
||||
#so now we solve AGAIN, but this time with the corrected pseudorange of the aircraft altitude
|
||||
#this might not be really useful in practice but the sim shows >50m errors without it
|
||||
prange_obs[-1] = [numpy.linalg.norm(llh2ecef((llhpos[0], llhpos[1], altitude)))]
|
||||
|
||||
xyzpos_corr = mlat_iter(rel_stations, prange_obs, xyzpos) #start off with a really close guess
|
||||
llhpos = ecef2llh(xyzpos_corr+me)
|
||||
|
||||
return llhpos
|
@ -30,13 +30,14 @@ class modes_output_print(modes_parse.modes_parse):
|
||||
modes_parse.modes_parse.__init__(self, mypos)
|
||||
|
||||
def parse(self, message):
|
||||
[msgtype, shortdata, longdata, parity, ecc, reference] = message.split()
|
||||
|
||||
[msgtype, shortdata, longdata, parity, ecc, reference, time_secs, time_frac] = message.split()
|
||||
shortdata = long(shortdata, 16)
|
||||
longdata = long(longdata, 16)
|
||||
parity = long(parity, 16)
|
||||
ecc = long(ecc, 16)
|
||||
reference = float(reference)
|
||||
time_secs = long(time_secs)
|
||||
time_frac = float(time_frac)
|
||||
|
||||
msgtype = int(msgtype)
|
||||
|
||||
@ -53,15 +54,15 @@ class modes_output_print(modes_parse.modes_parse):
|
||||
elif msgtype == 17:
|
||||
output = self.print17(shortdata, longdata, parity, ecc)
|
||||
else:
|
||||
output = "No handler for message type " + str(msgtype) + " from " + str(ecc)
|
||||
output = "No handler for message type " + str(msgtype) + " from %x" % ecc
|
||||
|
||||
if reference == 0.0:
|
||||
refdb = 0.0
|
||||
refdb = -150.0
|
||||
else:
|
||||
refdb = 10.0*math.log10(reference)
|
||||
|
||||
if output is not None:
|
||||
output = "(%.0f) " % (refdb) + output
|
||||
output = "(%.0f %u %f) " % (refdb, time_secs, time_frac) + output
|
||||
print output
|
||||
|
||||
def print0(self, shortdata, parity, ecc):
|
||||
|
@ -59,7 +59,7 @@ class modes_output_sbs1(modes_parse.modes_parse):
|
||||
#assembles a SBS-1-style output string from the received message
|
||||
#this version ignores anything that isn't Type 17 for now, because we just don't care
|
||||
|
||||
[msgtype, shortdata, longdata, parity, ecc, reference] = message.split()
|
||||
[msgtype, shortdata, longdata, parity, ecc, reference, time_secs, time_frac] = message.split()
|
||||
|
||||
shortdata = long(shortdata, 16)
|
||||
longdata = long(longdata, 16)
|
||||
|
@ -68,7 +68,7 @@ class modes_output_sql(modes_parse.modes_parse):
|
||||
def make_insert_query(self, message):
|
||||
#assembles a SQL query tailored to our database
|
||||
#this version ignores anything that isn't Type 17 for now, because we just don't care
|
||||
[msgtype, shortdata, longdata, parity, ecc, reference] = message.split()
|
||||
[msgtype, shortdata, longdata, parity, ecc, reference, time_secs, time_frac] = message.split()
|
||||
|
||||
shortdata = long(shortdata, 16)
|
||||
longdata = long(longdata, 16)
|
||||
|
@ -19,7 +19,8 @@
|
||||
# Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
my_position = [37.76225, -122.44254]
|
||||
#my_position = [37.76225, -122.44254]
|
||||
my_position = [37.409066,-122.077836]
|
||||
|
||||
from gnuradio import gr, gru, optfir, eng_notation, blks2, air
|
||||
from gnuradio import uhd
|
||||
@ -56,9 +57,9 @@ class adsb_rx_block (gr.top_block):
|
||||
if options.filename is None:
|
||||
self.u = uhd.single_usrp_source("", uhd.io_type_t.COMPLEX_FLOAT32, 1)
|
||||
|
||||
if(options.rx_subdev_spec is None):
|
||||
options.rx_subdev_spec = ""
|
||||
self.u.set_subdev_spec(options.rx_subdev_spec)
|
||||
#if(options.rx_subdev_spec is None):
|
||||
# options.rx_subdev_spec = ""
|
||||
#self.u.set_subdev_spec(options.rx_subdev_spec)
|
||||
|
||||
rate = options.rate
|
||||
self.u.set_samp_rate(rate)
|
||||
@ -66,20 +67,21 @@ class adsb_rx_block (gr.top_block):
|
||||
|
||||
if options.gain is None: #set to halfway
|
||||
g = self.u.get_gain_range()
|
||||
options.gain = (g.min+g.max) / 2.0
|
||||
options.gain = (g.start()+g.stop()) / 2.0
|
||||
|
||||
if not(self.tune(options.freq)):
|
||||
print "Failed to set initial frequency"
|
||||
|
||||
print "Setting gain to %i" % (options.gain,)
|
||||
self.u.set_gain(options.gain)
|
||||
print "Gain is %i" % (self.u.get_gain(),)
|
||||
|
||||
else:
|
||||
rate = options.rate
|
||||
self.u = gr.file_source(gr.sizeof_gr_complex, options.filename)
|
||||
|
||||
print "Rate is %i" % (rate,)
|
||||
|
||||
|
||||
pass_all = 0
|
||||
if options.output_all :
|
||||
pass_all = 1
|
||||
@ -89,31 +91,19 @@ class adsb_rx_block (gr.top_block):
|
||||
|
||||
#the DBSRX especially tends to be spur-prone; the LPF keeps out the
|
||||
#spur multiple that shows up at 2MHz
|
||||
self.filtcoeffs = gr.firdes.low_pass(1, rate, 1.8e6, 200e3)
|
||||
self.filter = gr.fir_filter_fff(1, self.filtcoeffs)
|
||||
# self.filtcoeffs = gr.firdes.low_pass(1, rate, 1.8e6, 200e3)
|
||||
# self.filter = gr.fir_filter_fff(1, self.filtcoeffs)
|
||||
|
||||
self.preamble = air.modes_preamble(rate, options.threshold)
|
||||
self.framer = air.modes_framer(rate)
|
||||
self.slicer = air.modes_slicer(rate, queue)
|
||||
|
||||
self.connect(self.u, self.demod, self.filter)
|
||||
self.connect(self.filter, self.avg)
|
||||
self.connect(self.filter, (self.preamble, 0))
|
||||
self.connect(self.avg, (self.preamble, 1))
|
||||
self.connect(self.filter, (self.framer, 0))
|
||||
self.connect(self.preamble, (self.framer, 1))
|
||||
self.connect(self.filter, (self.slicer, 0))
|
||||
self.connect(self.framer, (self.slicer, 1))
|
||||
|
||||
#use this flowgraph instead to omit the filter
|
||||
# self.connect(self.u, self.demod)
|
||||
# self.connect(self.demod, self.avg)
|
||||
# self.connect(self.demod, (self.preamble, 0))
|
||||
# self.connect(self.avg, (self.preamble, 1))
|
||||
# self.connect(self.demod, (self.framer, 0))
|
||||
# self.connect(self.preamble, (self.framer, 1))
|
||||
# self.connect(self.demod, (self.slicer, 0))
|
||||
# self.connect(self.framer, (self.slicer, 1))
|
||||
self.connect(self.u, self.demod)
|
||||
self.connect(self.demod, self.avg)
|
||||
self.connect(self.demod, (self.preamble, 0))
|
||||
self.connect(self.avg, (self.preamble, 1))
|
||||
self.connect((self.preamble, 0), (self.framer, 0))
|
||||
self.connect(self.framer, self.slicer)
|
||||
|
||||
def tune(self, freq):
|
||||
result = self.u.set_center_freq(freq, 0)
|
||||
|
Loading…
Reference in New Issue
Block a user