* Rewrote preamble detector. Uses integrate-and-dump along with correlator to perform optimal receive. * No longer using framer. Determining packet length via header. Missing some anti-FRUIT stuff. * Pulled out the slicer logic into its own function. Lots of todos and fixmes.
290 lines
11 KiB
C++
290 lines
11 KiB
C++
/*
|
|
# Copyright 2010 Nick Foster
|
|
#
|
|
# This file is part of gr-air-modes
|
|
#
|
|
# gr-air-modes is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 3, or (at your option)
|
|
# any later version.
|
|
#
|
|
# gr-air-modes is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with gr-air-modes; see the file COPYING. If not, write to
|
|
# the Free Software Foundation, Inc., 51 Franklin Street,
|
|
# Boston, MA 02110-1301, USA.
|
|
#
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <air_modes_slicer.h>
|
|
#include <gr_io_signature.h>
|
|
#include <air_modes_types.h>
|
|
#include <sstream>
|
|
#include <iomanip>
|
|
#include <modes_parity.h>
|
|
#include <gr_tag_info.h>
|
|
#include <iostream>
|
|
|
|
extern "C"
|
|
{
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
}
|
|
|
|
air_modes_slicer_sptr air_make_modes_slicer(int channel_rate, gr_msg_queue_sptr queue)
|
|
{
|
|
return air_modes_slicer_sptr (new air_modes_slicer(channel_rate, queue));
|
|
}
|
|
|
|
air_modes_slicer::air_modes_slicer(int channel_rate, gr_msg_queue_sptr queue) :
|
|
gr_sync_block ("modes_slicer",
|
|
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
|
|
d_chip_rate = 2000000; //2Mchips per second
|
|
d_samples_per_chip = 2;//FIXME this is constant now channel_rate / d_chip_rate;
|
|
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;
|
|
}
|
|
|
|
//this slicer is courtesy of Lincoln Labs. supposedly it is more resistant to mode A/C FRUIT.
|
|
//see http://adsb.tc.faa.gov/WG3_Meetings/Meeting8/Squitter-Lon.pdf
|
|
static bool slicer(const float bit0, const float bit1, const float ref) {
|
|
bool slice, confidence;
|
|
|
|
//3dB limits for bit slicing and confidence measurement
|
|
float highlimit=ref*2;
|
|
float lowlimit=ref*0.5;
|
|
|
|
bool firstchip_inref = ((bit0 > lowlimit) && (bit0 < highlimit));
|
|
bool secondchip_inref = ((bit1 > lowlimit) && (bit1 < highlimit));
|
|
|
|
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 = bit0 > bit1;
|
|
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 = bit0 > bit1;
|
|
if(slice) {
|
|
if(bit1 < lowlimit * 0.5) confidence = 1;
|
|
else confidence = 0;
|
|
} else {
|
|
if(bit0 < lowlimit * 0.5) confidence = 1;
|
|
else confidence = 0;
|
|
}
|
|
}
|
|
return slice;
|
|
}
|
|
|
|
/*
|
|
static double pmt_to_timestamp(pmt::pmt_t tstamp, uint64_t sample_cnt, double secs_per_sample) {
|
|
double frac;
|
|
uint64_t secs, sample, sample_age;
|
|
|
|
if(gr_tags::get_name(tstamp) != "time") return 0;
|
|
|
|
secs = pmt_to_uint64(pmt_tuple_ref(gr_tags::get_value(tstamp), 0));
|
|
frac = pmt_to_double(pmt_tuple_ref(gr_tags::get_value(tstamp), 1));
|
|
sample = gr_tags::get_nitems(d_timestamp);
|
|
//now we have to offset the timestamp based on the current sample number
|
|
sample_age = (sample_cnt + i) - sample;
|
|
return sample_age * secs_per_sample + frac + secs;
|
|
}
|
|
*/
|
|
int air_modes_slicer::work(int noutput_items,
|
|
gr_vector_const_void_star &input_items,
|
|
gr_vector_void_star &output_items)
|
|
{
|
|
const float *in = (const float *) input_items[0];
|
|
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("preamble_found"));
|
|
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;
|
|
|
|
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 to get a reference level for the packet
|
|
//fixme: a better thing to do is create a bi-level avg 1 and avg 0
|
|
//through simple statistics, then take the median for your slice level
|
|
//this won't improve decoding but will improve confidence
|
|
rx_packet.reference_level = (in[i]
|
|
+ in[i+2]
|
|
+ in[i+7]
|
|
+ in[i+9]) / 4.0;
|
|
|
|
i += 16; //move on up to the first bit of the packet data
|
|
//now let's slice the header so we can determine if it's a short pkt or a long pkt
|
|
unsigned char pkt_hdr = 0;
|
|
for(int j=0; j < 5; j++) {
|
|
bool slice = slicer(in[i+j*2], in[i+j*2+1], rx_packet.reference_level);
|
|
if(slice) pkt_hdr += 1 << (4-j);
|
|
}
|
|
//std::cout << "SLICER: TYPE " << int(pkt_hdr) << std::endl;
|
|
|
|
if(pkt_hdr == 17) rx_packet.type = Long_Packet;
|
|
else rx_packet.type = Short_Packet;
|
|
|
|
int packet_length;
|
|
packet_length = (rx_packet.type == framer_packet_type(Short_Packet)) ? 56 : 112;
|
|
|
|
//it's slice time!
|
|
//TODO: don't repeat your work here, you already have the first 5 bits
|
|
for(int j = 0; j < packet_length; j++) {
|
|
bool slice = slicer(in[i+j*2], in[i+j*2+1], rx_packet.reference_level);
|
|
|
|
//put the data into the packet
|
|
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));
|
|
// } else {
|
|
// if(rx_packet.numlowconf < 24) rx_packet.lowconfbits[rx_packet.numlowconf++] = j;
|
|
// }
|
|
}
|
|
|
|
/******************** BEGIN TIMESTAMP BS ******************/
|
|
rx_packet.timestamp = 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;
|
|
|
|
get_tags_in_range(tags, 0, abs_sample_cnt, abs_sample_cnt + i, pmt::pmt_string_to_symbol("time"));
|
|
//tags.back() is the most recent timestamp, then.
|
|
if(tags.size() > 0) {
|
|
d_timestamp = tags.back();
|
|
}
|
|
|
|
if(d_timestamp) {
|
|
rx_packet.timestamp = pmt_to_timestamp(d_timestamp, abs_sample_cnt + i, d_secs_per_sample);
|
|
}
|
|
*/
|
|
/******************* END TIMESTAMP BS *********************/
|
|
|
|
//increment for the next round
|
|
|
|
//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
|
|
|
|
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) {
|
|
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 << " ";
|
|
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
|
|
<< " " << std::setprecision(10) << std::setw(10) << rx_packet.timestamp;
|
|
gr_message_sptr msg = gr_make_message_from_string(std::string(d_payload.str()));
|
|
d_queue->handle(msg);
|
|
|
|
}
|
|
|
|
return size;
|
|
}
|