/////////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2020 Jon Beniston, M7RCE // // // // This program 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 as version 3 of the License, or // // (at your option) any later version. // // // // This program 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 V3 for more details. // // // // You should have received a copy of the GNU General Public License // // along with this program. If not, see . // /////////////////////////////////////////////////////////////////////////////////// #define BOOST_CHRONO_HEADER_ONLY #include #include #include "util/db.h" #include "adsbdemodreport.h" #include "adsbdemodsink.h" #include "adsbdemodsinkworker.h" #include "adsbdemodsettings.h" #include "adsb.h" MESSAGE_CLASS_DEFINITION(ADSBDemodSinkWorker::MsgConfigureADSBDemodSinkWorker, Message) void ADSBDemodSinkWorker::run() { int readBuffer = 0; // Acquire first buffer m_sink->m_bufferRead[readBuffer].acquire(); // Start recording how much time is spent processing in this method boost::chrono::steady_clock::time_point startPoint = boost::chrono::steady_clock::now(); // Check for updated settings handleInputMessages(); // samplesPerBit is only changed when the thread is stopped int samplesPerBit = m_settings.m_samplesPerBit; int samplesPerFrame = samplesPerBit*(ADS_B_PREAMBLE_BITS+ADS_B_ES_BITS); int samplesPerChip = samplesPerBit/ADS_B_CHIPS_PER_BIT; qDebug() << "ADSBDemodSinkWorker:: running with" << " samplesPerFrame: " << samplesPerFrame << " samplesPerChip: " << samplesPerChip << " samplesPerBit: " << samplesPerBit << " correlateFullPreamble: " << m_settings.m_correlateFullPreamble << " correlationScale: " << m_correlationScale << " correlationThreshold: " << m_settings.m_correlationThreshold; int readIdx = m_sink->m_samplesPerFrame - 1; while (true) { int startIdx = readIdx; // Correlate received signal with expected preamble // chip+ indexes are 0, 2, 7, 9 // correlating over first 6 bits gives a reduction in per-sample // processing, but more than doubles the number of false matches Real preambleCorrelationOnes = 0.0; Real preambleCorrelationZeros = 0.0; if (m_settings.m_correlateFullPreamble) { for (int i = 0; i < samplesPerChip; i++) { preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 0*samplesPerChip + i]; preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 1*samplesPerChip + i]; preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 2*samplesPerChip + i]; preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 3*samplesPerChip + i]; preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 4*samplesPerChip + i]; preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 5*samplesPerChip + i]; preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 6*samplesPerChip + i]; preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 7*samplesPerChip + i]; preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 8*samplesPerChip + i]; preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 9*samplesPerChip + i]; preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 10*samplesPerChip + i]; preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 11*samplesPerChip + i]; preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 12*samplesPerChip + i]; preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 13*samplesPerChip + i]; preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 14*samplesPerChip + i]; preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 15*samplesPerChip + i]; } } else { for (int i = 0; i < samplesPerChip; i++) { preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 0*samplesPerChip + i]; preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 1*samplesPerChip + i]; preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 2*samplesPerChip + i]; preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 3*samplesPerChip + i]; preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 4*samplesPerChip + i]; preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 5*samplesPerChip + i]; preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 6*samplesPerChip + i]; preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 7*samplesPerChip + i]; preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 8*samplesPerChip + i]; preambleCorrelationOnes += m_sink->m_sampleBuffer[readBuffer][startIdx + 9*samplesPerChip + i]; preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 10*samplesPerChip + i]; preambleCorrelationZeros += m_sink->m_sampleBuffer[readBuffer][startIdx + 11*samplesPerChip + i]; } } // Use the ratio of ones power over zeros power, as we don't care how powerful the signal // is, just whether there is a good correlation with the preamble. The absolute value varies // too much with different radios, AGC settings and and the noise floor is not constant // (E.g: it's quite possible to receive multiple frames simultaneously, so we don't // want a maximum threshold for the zeros, as a weaker signal may transmit 1s in // a stronger signals 0 chip position. Similarly a strong signal in an adjacent // channel may cause AGC to reduce gain, reducing the ampltiude of an otherwise // strong signal, as well as the noise floor) // The threshold accounts for the different number of zeros and ones in the preamble // If the sum of ones is exactly 0, it's probably no signal Real preambleCorrelation = preambleCorrelationOnes/preambleCorrelationZeros; // without one/zero ratio correction if ((preambleCorrelation > m_correlationThresholdLinear) && (preambleCorrelationOnes != 0.0f)) { int firstIdx = startIdx; m_demodStats.m_correlatorMatches++; // Skip over preamble startIdx += samplesPerBit*ADS_B_PREAMBLE_BITS; // Demodulate waveform to bytes unsigned char data[ADS_B_ES_BYTES]; int byteIdx = 0; int currentBit; unsigned char currentByte = 0; int df; for (int bit = 0; bit < ADS_B_ES_BITS; bit++) { // PPM (Pulse position modulation) - Each bit spreads to two chips, 1->10, 0->01 // Determine if bit is 1 or 0, by seeing which chip has largest combined energy over the sampling period Real oneSum = 0.0f; Real zeroSum = 0.0f; for (int i = 0; i < samplesPerChip; i++) { oneSum += m_sink->m_sampleBuffer[readBuffer][startIdx+i]; zeroSum += m_sink->m_sampleBuffer[readBuffer][startIdx+samplesPerChip+i]; } currentBit = oneSum > zeroSum; startIdx += samplesPerBit; // Convert bit to bytes - MSB first currentByte |= currentBit << (7-(bit & 0x7)); if ((bit & 0x7) == 0x7) { data[byteIdx++] = currentByte; currentByte = 0; // Don't try to demodulate any further, if this isn't an ADS-B frame // to help reduce processing overhead if (!m_settings.m_demodModeS && (bit == 7)) { df = ((data[0] >> 3) & ADS_B_DF_MASK); if ((df != 17) && (df != 18)) break; } } } // Is ADS-B? df = ((data[0] >> 3) & ADS_B_DF_MASK); if ((df == 17) || (df == 18)) { m_crc.init(); unsigned int parity = (data[11] << 16) | (data[12] << 8) | data[13]; // Parity / CRC m_crc.calculate(data, ADS_B_ES_BYTES-3); if (parity == m_crc.get()) { // Got a valid frame m_demodStats.m_adsbFrames++; // Get 24-bit ICAO and save in hash of ICAOs that have been seen unsigned icao = ((data[1] & 0xff) << 16) | ((data[2] & 0xff) << 8) | (data[3] & 0xff); m_icaos.insert(icao, true); // Don't try to re-demodulate the same frame // We could possibly allow a partial overlap here readIdx += (ADS_B_ES_BITS+ADS_B_PREAMBLE_BITS)*ADS_B_CHIPS_PER_BIT*samplesPerChip - 1; // Pass to GUI if (m_sink->getMessageQueueToGUI()) { ADSBDemodReport::MsgReportADSB *msg = ADSBDemodReport::MsgReportADSB::create( QByteArray((char*)data, sizeof(data)), preambleCorrelation * m_correlationScale, preambleCorrelationOnes / samplesPerChip, rxDateTime(firstIdx, readBuffer), m_crc.get()); m_sink->getMessageQueueToGUI()->push(msg); } // Pass to worker to feed to other servers if (m_sink->getMessageQueueToWorker()) { ADSBDemodReport::MsgReportADSB *msg = ADSBDemodReport::MsgReportADSB::create( QByteArray((char*)data, sizeof(data)), preambleCorrelation * m_correlationScale, preambleCorrelationOnes / samplesPerChip, rxDateTime(firstIdx, readBuffer), m_crc.get()); m_sink->getMessageQueueToWorker()->push(msg); } } else m_demodStats.m_crcFails++; } else if (m_settings.m_demodModeS) { int bytes; // Determine number of bytes in frame depending on downlink format if ((df == 0) || (df == 4) || (df == 5) || (df == 11)) { bytes = 56/8; } else if ((df == 16) || (df == 20) || (df == 21) || (df >= 24)) { bytes = 112/8; } else { bytes = 0; } if (bytes > 0) { // Extract received parity int parity = (data[bytes-3] << 16) | (data[bytes-2] << 8) | data[bytes-1]; // Calculate CRC on received frame m_crc.init(); m_crc.calculate(data, bytes-3); int crc = m_crc.get(); // DF4 / DF5 / DF20 / DF21 have ICAO address XORed in to parity. // Extract ICAO from parity and see if it matches an aircraft we've already // received an ADS-B frame from if ((df == 4) || (df == 5) || (df == 20) || (df == 21)) { unsigned icao = (parity ^ crc) & 0xffffff; if (m_icaos.contains(icao)) { crc ^= icao; } } // For DF11, the last 7 bits may have an address/interogration identifier (II) // XORed in, so we ignore those bits if ((parity == crc) || ((df == 11) && ((parity & 0xffff80) == (crc & 0xffff80)))) { m_demodStats.m_modesFrames++; // Pass to GUI (only pass formats it can decode) if (m_sink->getMessageQueueToGUI() && ((df == 4) || (df == 5) || (df == 20) || (df == 21))) { ADSBDemodReport::MsgReportADSB *msg = ADSBDemodReport::MsgReportADSB::create( QByteArray((char*)data, bytes), preambleCorrelation * m_correlationScale, preambleCorrelationOnes / samplesPerChip, rxDateTime(firstIdx, readBuffer), m_crc.get()); m_sink->getMessageQueueToGUI()->push(msg); } // Pass to worker to feed to other servers if (m_sink->getMessageQueueToWorker()) { ADSBDemodReport::MsgReportADSB *msg = ADSBDemodReport::MsgReportADSB::create( QByteArray((char*)data, bytes), preambleCorrelation * m_correlationScale, preambleCorrelationOnes / samplesPerChip, rxDateTime(firstIdx, readBuffer), m_crc.get()); m_sink->getMessageQueueToWorker()->push(msg); } } else { m_demodStats.m_crcFails++; } } else m_demodStats.m_typeFails++; } else m_demodStats.m_typeFails++; } readIdx++; if (readIdx > m_sink->m_bufferSize - samplesPerFrame) { int nextBuffer = readBuffer+1; if (nextBuffer >= m_sink->m_buffers) nextBuffer = 0; // Update amount of time spent processing (don't include time spend in acquire) boost::chrono::duration sec = boost::chrono::steady_clock::now() - startPoint; m_demodStats.m_demodTime += sec.count(); m_demodStats.m_feedTime = m_sink->m_feedTime; // Send stats to GUI if (m_sink->getMessageQueueToGUI()) { ADSBDemodReport::MsgReportDemodStats *msg = ADSBDemodReport::MsgReportDemodStats::create(m_demodStats); m_sink->getMessageQueueToGUI()->push(msg); } if (!isInterruptionRequested()) { // Get next buffer m_sink->m_bufferRead[nextBuffer].acquire(); // Check for updated settings handleInputMessages(); // Resume timing how long we are processing startPoint = boost::chrono::steady_clock::now(); int samplesRemaining = m_sink->m_bufferSize - readIdx; if (samplesRemaining > 0) { // Copy remaining samples, to start of next buffer memcpy(&m_sink->m_sampleBuffer[nextBuffer][samplesPerFrame - 1 - samplesRemaining], &m_sink->m_sampleBuffer[readBuffer][readIdx], samplesRemaining*sizeof(Real)); readIdx = samplesPerFrame - 1 - samplesRemaining; } else { readIdx = samplesPerFrame - 1; } m_sink->m_bufferWrite[readBuffer].release(); readBuffer = nextBuffer; } else { // Use a break to avoid testing a condition in the main loop break; } } } } void ADSBDemodSinkWorker::handleInputMessages() { Message* message; while ((message = m_inputMessageQueue.pop()) != nullptr) { if (MsgConfigureADSBDemodSinkWorker::match(*message)) { MsgConfigureADSBDemodSinkWorker* cfg = (MsgConfigureADSBDemodSinkWorker*)message; ADSBDemodSettings settings = cfg->getSettings(); QStringList settingsKeys = cfg->getSettingsKeys(); bool force = cfg->getForce(); if (settingsKeys.contains("correlateFullPreamble") || force) { if (settings.m_correlateFullPreamble) { m_correlationScale = 3.0; } else { m_correlationScale = 2.0; } } if ((settingsKeys.contains("correlationThreshold") && (m_settings.m_correlationThreshold != settings.m_correlationThreshold)) || force) { m_correlationThresholdLinear = CalcDb::powerFromdB(settings.m_correlationThreshold); m_correlationThresholdLinear /= m_correlationScale; qDebug() << "m_correlationThresholdLinear: " << m_correlationThresholdLinear; } if (force) { m_settings = settings; } else { m_settings.applySettings(settingsKeys, settings); } delete message; } } } QDateTime ADSBDemodSinkWorker::rxDateTime(int firstIdx, int readBuffer) const { const qint64 samplesPerSecondMSec = ADS_B_BITS_PER_SECOND * m_settings.m_samplesPerBit / 1000; const qint64 offsetMSec = (firstIdx - m_sink->m_samplesPerFrame - 1) / samplesPerSecondMSec; return m_sink->m_bufferFirstSampleDateTime[readBuffer].addMSecs(offsetMSec); }