kopia lustrzana https://github.com/kgoba/ft8_lib
Some code cleanup
rodzic
7d534db0db
commit
72754d02f0
|
@ -2,6 +2,7 @@ BasedOnStyle: WebKit
|
|||
# Cpp11BracedListStyle: false
|
||||
# ColumnLimit: 120
|
||||
IndentCaseLabels: false
|
||||
IndentExternBlock: false
|
||||
IndentWidth: 4
|
||||
TabWidth: 8
|
||||
UseTab: Never
|
||||
|
|
42
decode_ft8.c
42
decode_ft8.c
|
@ -69,7 +69,7 @@ void waterfall_init(waterfall_t* me, int max_blocks, int num_bins, int time_osr,
|
|||
me->time_osr = time_osr;
|
||||
me->freq_osr = freq_osr;
|
||||
me->block_stride = (time_osr * freq_osr * num_bins);
|
||||
me->mag = (uint8_t *)malloc(mag_size);
|
||||
me->mag = (uint8_t*)malloc(mag_size);
|
||||
LOG(LOG_DEBUG, "Waterfall size = %zu\n", mag_size);
|
||||
}
|
||||
|
||||
|
@ -94,14 +94,16 @@ typedef struct
|
|||
typedef struct
|
||||
{
|
||||
float symbol_period; ///< FT4/FT8 symbol period in seconds
|
||||
int block_size; ///< Number of samples per symbol (block)
|
||||
int subblock_size; ///< Analysis shift size (number of samples)
|
||||
int nfft; ///< FFT size
|
||||
float fft_norm; ///< FFT normalization factor
|
||||
float* window; ///< Window function for STFT analysis (nfft samples)
|
||||
float* last_frame; ///< Current STFT analysis frame (nfft samples)
|
||||
waterfall_t wf; ///< Waterfall object
|
||||
float max_mag; ///< Maximum detected magnitude (debug stats)
|
||||
int min_bin;
|
||||
int max_bin;
|
||||
int block_size; ///< Number of samples per symbol (block)
|
||||
int subblock_size; ///< Analysis shift size (number of samples)
|
||||
int nfft; ///< FFT size
|
||||
float fft_norm; ///< FFT normalization factor
|
||||
float* window; ///< Window function for STFT analysis (nfft samples)
|
||||
float* last_frame; ///< Current STFT analysis frame (nfft samples)
|
||||
waterfall_t wf; ///< Waterfall object
|
||||
float max_mag; ///< Maximum detected magnitude (debug stats)
|
||||
|
||||
// KISS FFT housekeeping variables
|
||||
void* fft_work; ///< Work area required by Kiss FFT
|
||||
|
@ -119,7 +121,7 @@ void monitor_init(monitor_t* me, const monitor_config_t* cfg)
|
|||
me->fft_norm = 2.0f / me->nfft;
|
||||
// const int len_window = 1.8f * me->block_size; // hand-picked and optimized
|
||||
|
||||
me->window = (float *)malloc(me->nfft * sizeof(me->window[0]));
|
||||
me->window = (float*)malloc(me->nfft * sizeof(me->window[0]));
|
||||
for (int i = 0; i < me->nfft; ++i)
|
||||
{
|
||||
// window[i] = 1;
|
||||
|
@ -128,7 +130,7 @@ void monitor_init(monitor_t* me, const monitor_config_t* cfg)
|
|||
// me->window[i] = hamming_i(i, me->nfft);
|
||||
// me->window[i] = (i < len_window) ? hann_i(i, len_window) : 0;
|
||||
}
|
||||
me->last_frame = (float *)malloc(me->nfft * sizeof(me->last_frame[0]));
|
||||
me->last_frame = (float*)malloc(me->nfft * sizeof(me->last_frame[0]));
|
||||
|
||||
size_t fft_work_size;
|
||||
kiss_fftr_alloc(me->nfft, 0, 0, &fft_work_size);
|
||||
|
@ -141,10 +143,16 @@ void monitor_init(monitor_t* me, const monitor_config_t* cfg)
|
|||
me->fft_work = malloc(fft_work_size);
|
||||
me->fft_cfg = kiss_fftr_alloc(me->nfft, 0, me->fft_work, &fft_work_size);
|
||||
|
||||
// Allocate enough blocks to fit the entire FT8/FT4 slot in memory
|
||||
const int max_blocks = (int)(slot_time / symbol_period);
|
||||
const int num_bins = (int)(cfg->sample_rate * symbol_period / 2);
|
||||
// Keep only FFT bins in the specified frequency range (f_min/f_max)
|
||||
me->min_bin = (int)(cfg->f_min * symbol_period);
|
||||
me->max_bin = (int)(cfg->f_max * symbol_period) + 1;
|
||||
const int num_bins = me->max_bin - me->min_bin;
|
||||
|
||||
waterfall_init(&me->wf, max_blocks, num_bins, cfg->time_osr, cfg->freq_osr);
|
||||
me->wf.protocol = cfg->protocol;
|
||||
|
||||
me->symbol_period = symbol_period;
|
||||
|
||||
me->max_mag = -120.0f;
|
||||
|
@ -196,7 +204,7 @@ void monitor_process(monitor_t* me, const float* frame)
|
|||
// Loop over two possible frequency bin offsets (for averaging)
|
||||
for (int freq_sub = 0; freq_sub < me->wf.freq_osr; ++freq_sub)
|
||||
{
|
||||
for (int bin = 0; bin < me->wf.num_bins; ++bin)
|
||||
for (int bin = me->min_bin; bin < me->max_bin; ++bin)
|
||||
{
|
||||
int src_bin = (bin * me->wf.freq_osr) + freq_sub;
|
||||
float mag2 = (freqdata[src_bin].i * freqdata[src_bin].i) + (freqdata[src_bin].r * freqdata[src_bin].r);
|
||||
|
@ -322,12 +330,12 @@ int main(int argc, char** argv)
|
|||
if (cand->score < kMin_score)
|
||||
continue;
|
||||
|
||||
float freq_hz = (cand->freq_offset + (float)cand->freq_sub / mon.wf.freq_osr) / mon.symbol_period;
|
||||
float freq_hz = (mon.min_bin + cand->freq_offset + (float)cand->freq_sub / mon.wf.freq_osr) / mon.symbol_period;
|
||||
float time_sec = (cand->time_offset + (float)cand->time_sub / mon.wf.time_osr) * mon.symbol_period;
|
||||
|
||||
message_t message;
|
||||
decode_status_t status;
|
||||
if (!ft8_decode(&mon.wf, cand, &message, kLDPC_iterations, &status))
|
||||
if (!ft8_decode(&mon.wf, cand, &message, kLDPC_iterations, NULL, &status))
|
||||
{
|
||||
// printf("000000 %3d %+4.2f %4.0f ~ ---\n", cand->score, time_sec, freq_hz);
|
||||
if (status.ldpc_errors > 0)
|
||||
|
@ -377,8 +385,8 @@ int main(int argc, char** argv)
|
|||
++num_decoded;
|
||||
|
||||
// Fake WSJT-X-like output for now
|
||||
int snr = 0; // TODO: compute SNR
|
||||
printf("000000 %3d %+4.2f %4.0f ~ %s\n", cand->score, time_sec, freq_hz, message.text);
|
||||
float snr = cand->score * 0.5f; // TODO: compute better approximation of SNR
|
||||
printf("000000 %2.1f %+4.2f %4.0f ~ %s\n", snr, time_sec, freq_hz, message.text);
|
||||
}
|
||||
}
|
||||
LOG(LOG_INFO, "Decoded %d messages\n", num_decoded);
|
||||
|
|
|
@ -49,39 +49,39 @@ extern "C"
|
|||
#define FT8_CRC_POLYNOMIAL ((uint16_t)0x2757u) ///< CRC-14 polynomial without the leading (MSB) 1
|
||||
#define FT8_CRC_WIDTH (14)
|
||||
|
||||
typedef enum
|
||||
{
|
||||
PROTO_FT4,
|
||||
PROTO_FT8
|
||||
} ftx_protocol_t;
|
||||
typedef enum
|
||||
{
|
||||
PROTO_FT4,
|
||||
PROTO_FT8
|
||||
} ftx_protocol_t;
|
||||
|
||||
/// Costas 7x7 tone pattern for synchronization
|
||||
extern const uint8_t kFT8_Costas_pattern[7];
|
||||
extern const uint8_t kFT4_Costas_pattern[4][4];
|
||||
/// Costas 7x7 tone pattern for synchronization
|
||||
extern const uint8_t kFT8_Costas_pattern[7];
|
||||
extern const uint8_t kFT4_Costas_pattern[4][4];
|
||||
|
||||
/// Gray code map to encode 8 symbols (tones)
|
||||
extern const uint8_t kFT8_Gray_map[8];
|
||||
extern const uint8_t kFT4_Gray_map[4];
|
||||
/// Gray code map to encode 8 symbols (tones)
|
||||
extern const uint8_t kFT8_Gray_map[8];
|
||||
extern const uint8_t kFT4_Gray_map[4];
|
||||
|
||||
extern const uint8_t kFT4_XOR_sequence[10];
|
||||
extern const uint8_t kFT4_XOR_sequence[10];
|
||||
|
||||
/// Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first)
|
||||
extern const uint8_t kFTX_LDPC_generator[FTX_LDPC_M][FTX_LDPC_K_BYTES];
|
||||
/// Parity generator matrix for (174,91) LDPC code, stored in bitpacked format (MSB first)
|
||||
extern const uint8_t kFTX_LDPC_generator[FTX_LDPC_M][FTX_LDPC_K_BYTES];
|
||||
|
||||
/// LDPC(174,91) parity check matrix, containing 83 rows,
|
||||
/// each row describes one parity check,
|
||||
/// each number is an index into the codeword (1-origin).
|
||||
/// The codeword bits mentioned in each row must xor to zero.
|
||||
/// From WSJT-X's ldpc_174_91_c_reordered_parity.f90.
|
||||
extern const uint8_t kFTX_LDPC_Nm[FTX_LDPC_M][7];
|
||||
/// LDPC(174,91) parity check matrix, containing 83 rows,
|
||||
/// each row describes one parity check,
|
||||
/// each number is an index into the codeword (1-origin).
|
||||
/// The codeword bits mentioned in each row must xor to zero.
|
||||
/// From WSJT-X's ldpc_174_91_c_reordered_parity.f90.
|
||||
extern const uint8_t kFTX_LDPC_Nm[FTX_LDPC_M][7];
|
||||
|
||||
/// Mn from WSJT-X's bpdecode174.f90. Each row corresponds to a codeword bit.
|
||||
/// The numbers indicate which three parity checks (rows in Nm) refer to the codeword bit.
|
||||
/// The numbers use 1 as the origin (first entry).
|
||||
extern const uint8_t kFTX_LDPC_Mn[FTX_LDPC_N][3];
|
||||
/// Mn from WSJT-X's bpdecode174.f90. Each row corresponds to a codeword bit.
|
||||
/// The numbers indicate which three parity checks (rows in Nm) refer to the codeword bit.
|
||||
/// The numbers use 1 as the origin (first entry).
|
||||
extern const uint8_t kFTX_LDPC_Mn[FTX_LDPC_N][3];
|
||||
|
||||
/// Number of rows (columns in C/C++) in the array Nm.
|
||||
extern const uint8_t kFTX_LDPC_Num_rows[FTX_LDPC_M];
|
||||
/// Number of rows (columns in C/C++) in the array Nm.
|
||||
extern const uint8_t kFTX_LDPC_Num_rows[FTX_LDPC_M];
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
24
ft8/crc.h
24
ft8/crc.h
|
@ -9,20 +9,20 @@ extern "C"
|
|||
{
|
||||
#endif
|
||||
|
||||
// Compute 14-bit CRC for a sequence of given number of bits using FT8/FT4 CRC polynomial
|
||||
// [IN] message - byte sequence (MSB first)
|
||||
// [IN] num_bits - number of bits in the sequence
|
||||
uint16_t ftx_compute_crc(const uint8_t message[], int num_bits);
|
||||
// Compute 14-bit CRC for a sequence of given number of bits using FT8/FT4 CRC polynomial
|
||||
// [IN] message - byte sequence (MSB first)
|
||||
// [IN] num_bits - number of bits in the sequence
|
||||
uint16_t ftx_compute_crc(const uint8_t message[], int num_bits);
|
||||
|
||||
/// Extract the FT8/FT4 CRC of a packed message (during decoding)
|
||||
/// @param[in] a91 77 bits of payload data + CRC
|
||||
/// @return Extracted CRC
|
||||
uint16_t ftx_extract_crc(const uint8_t a91[]);
|
||||
/// Extract the FT8/FT4 CRC of a packed message (during decoding)
|
||||
/// @param[in] a91 77 bits of payload data + CRC
|
||||
/// @return Extracted CRC
|
||||
uint16_t ftx_extract_crc(const uint8_t a91[]);
|
||||
|
||||
/// Add FT8/FT4 CRC to a packed message (during encoding)
|
||||
/// @param[in] payload 77 bits of payload data
|
||||
/// @param[out] a91 91 bits of payload data + CRC
|
||||
void ftx_add_crc(const uint8_t payload[], uint8_t a91[]);
|
||||
/// Add FT8/FT4 CRC to a packed message (during encoding)
|
||||
/// @param[in] payload 77 bits of payload data
|
||||
/// @param[out] a91 91 bits of payload data + CRC
|
||||
void ftx_add_crc(const uint8_t payload[], uint8_t a91[]);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
74
ft8/decode.c
74
ft8/decode.c
|
@ -32,13 +32,70 @@ static void ft4_extract_symbol(const uint8_t* wf, float* logl);
|
|||
static void ft8_extract_symbol(const uint8_t* wf, float* logl);
|
||||
static void ft8_decode_multi_symbols(const uint8_t* wf, int num_bins, int n_syms, int bit_idx, float* log174);
|
||||
|
||||
static int get_index(const waterfall_t* wf, const candidate_t* candidate)
|
||||
static const uint8_t* get_cand_mag(const waterfall_t* wf, const candidate_t* candidate)
|
||||
{
|
||||
int offset = candidate->time_offset;
|
||||
offset = (offset * wf->time_osr) + candidate->time_sub;
|
||||
offset = (offset * wf->freq_osr) + candidate->freq_sub;
|
||||
offset = (offset * wf->num_bins) + candidate->freq_offset;
|
||||
return offset;
|
||||
return wf->mag + offset;
|
||||
}
|
||||
|
||||
int ft8_snr(const waterfall_t* wf, const candidate_t* candidate)
|
||||
{
|
||||
int sum_signal = 0;
|
||||
int sum_noise = 0;
|
||||
int num_average = 0;
|
||||
|
||||
// Get the pointer to symbol 0 of the candidate
|
||||
const uint8_t* mag_cand = get_cand_mag(wf, candidate);
|
||||
|
||||
if (wf->protocol == PROTO_FT4)
|
||||
{
|
||||
}
|
||||
|
||||
// Compute average score over sync symbols (m+k = 0-7, 36-43, 72-79)
|
||||
for (int block = 0; block < FT8_NN; ++block)
|
||||
{
|
||||
int block_abs = candidate->time_offset + block; // relative to the captured signal
|
||||
// Check for time boundaries
|
||||
if (block_abs < 0)
|
||||
continue;
|
||||
if (block_abs >= wf->num_blocks)
|
||||
break;
|
||||
|
||||
// Get the pointer to symbol 'block' of the candidate
|
||||
const uint8_t* p8 = mag_cand + (block * wf->block_stride);
|
||||
|
||||
int k = block % FT8_SYNC_OFFSET;
|
||||
int sm = -1;
|
||||
if (k < 7)
|
||||
{
|
||||
// Check only the neighbors of the expected symbol frequency- and time-wise
|
||||
sm = kFT8_Costas_pattern[k]; // Index of the expected bin
|
||||
}
|
||||
else
|
||||
{
|
||||
int max;
|
||||
for (int m = 0; m < 8; ++m)
|
||||
{
|
||||
if ((sm == -1) || (p8[m] > max))
|
||||
{
|
||||
sm = m;
|
||||
max = p8[m];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sm != -1)
|
||||
{
|
||||
sum_signal += p8[sm];
|
||||
sum_noise += (3 + (int)p8[0] + (int)p8[1] + (int)p8[2] + (int)p8[3] + (int)p8[4] + (int)p8[5] + (int)p8[6] + (int)p8[7] - (int)p8[sm]) / 7;
|
||||
++num_average;
|
||||
}
|
||||
}
|
||||
// return num_average;
|
||||
return (sum_signal - sum_noise) / num_average;
|
||||
}
|
||||
|
||||
static int ft8_sync_score(const waterfall_t* wf, const candidate_t* candidate)
|
||||
|
@ -47,7 +104,7 @@ static int ft8_sync_score(const waterfall_t* wf, const candidate_t* candidate)
|
|||
int num_average = 0;
|
||||
|
||||
// Get the pointer to symbol 0 of the candidate
|
||||
const uint8_t* mag_cand = wf->mag + get_index(wf, candidate);
|
||||
const uint8_t* mag_cand = get_cand_mag(wf, candidate);
|
||||
|
||||
// Compute average score over sync symbols (m+k = 0-7, 36-43, 72-79)
|
||||
for (int m = 0; m < FT8_NUM_SYNC; ++m)
|
||||
|
@ -113,7 +170,7 @@ static int ft4_sync_score(const waterfall_t* wf, const candidate_t* candidate)
|
|||
int num_average = 0;
|
||||
|
||||
// Get the pointer to symbol 0 of the candidate
|
||||
const uint8_t* mag_cand = wf->mag + get_index(wf, candidate);
|
||||
const uint8_t* mag_cand = get_cand_mag(wf, candidate);
|
||||
|
||||
// Compute average score over sync symbols (block = 1-4, 34-37, 67-70, 100-103)
|
||||
for (int m = 0; m < FT4_NUM_SYNC; ++m)
|
||||
|
@ -193,6 +250,7 @@ int ft8_find_sync(const waterfall_t* wf, int num_candidates, candidate_t heap[],
|
|||
else
|
||||
{
|
||||
candidate.score = ft8_sync_score(wf, &candidate);
|
||||
// candidate.score = ft8_snr(wf, &candidate);
|
||||
}
|
||||
|
||||
if (candidate.score < min_score)
|
||||
|
@ -235,7 +293,7 @@ int ft8_find_sync(const waterfall_t* wf, int num_candidates, candidate_t heap[],
|
|||
|
||||
static void ft4_extract_likelihood(const waterfall_t* wf, const candidate_t* cand, float* log174)
|
||||
{
|
||||
const uint8_t* mag_cand = wf->mag + get_index(wf, cand);
|
||||
const uint8_t* mag_cand = get_cand_mag(wf, cand);
|
||||
|
||||
// Go over FSK tones and skip Costas sync symbols
|
||||
for (int k = 0; k < FT4_ND; ++k)
|
||||
|
@ -264,7 +322,7 @@ static void ft4_extract_likelihood(const waterfall_t* wf, const candidate_t* can
|
|||
|
||||
static void ft8_extract_likelihood(const waterfall_t* wf, const candidate_t* cand, float* log174)
|
||||
{
|
||||
const uint8_t* mag_cand = wf->mag + get_index(wf, cand);
|
||||
const uint8_t* mag_cand = get_cand_mag(wf, cand);
|
||||
|
||||
// Go over FSK tones and skip Costas sync symbols
|
||||
for (int k = 0; k < FT8_ND; ++k)
|
||||
|
@ -313,7 +371,7 @@ static void ftx_normalize_logl(float* log174)
|
|||
}
|
||||
}
|
||||
|
||||
bool ft8_decode(const waterfall_t* wf, const candidate_t* cand, message_t* message, int max_iterations, decode_status_t* status)
|
||||
bool ft8_decode(const waterfall_t* wf, const candidate_t* cand, message_t* message, int max_iterations, const unpack_hash_interface_t* hash_if, decode_status_t* status)
|
||||
{
|
||||
float log174[FTX_LDPC_N]; // message bits encoded as likelihood
|
||||
if (wf->protocol == PROTO_FT4)
|
||||
|
@ -362,7 +420,7 @@ bool ft8_decode(const waterfall_t* wf, const candidate_t* cand, message_t* messa
|
|||
}
|
||||
}
|
||||
|
||||
status->unpack_status = unpack77(a91, message->text);
|
||||
status->unpack_status = unpack77(a91, message->text, hash_if);
|
||||
|
||||
if (status->unpack_status < 0)
|
||||
{
|
||||
|
|
120
ft8/decode.h
120
ft8/decode.h
|
@ -5,76 +5,78 @@
|
|||
#include <stdbool.h>
|
||||
|
||||
#include "constants.h"
|
||||
#include "unpack.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
/// Input structure to ft8_find_sync() function. This structure describes stored waterfall data over the whole message slot.
|
||||
/// Fields time_osr and freq_osr specify additional oversampling rate for time and frequency resolution.
|
||||
/// If time_osr=1, FFT magnitude data is collected once for every symbol transmitted, i.e. every 1/6.25 = 0.16 seconds.
|
||||
/// Values time_osr > 1 mean each symbol is further subdivided in time.
|
||||
/// If freq_osr=1, each bin in the FFT magnitude data corresponds to 6.25 Hz, which is the tone spacing.
|
||||
/// Values freq_osr > 1 mean the tone spacing is further subdivided by FFT analysis.
|
||||
typedef struct
|
||||
{
|
||||
int max_blocks; ///< number of blocks (symbols) allocated in the mag array
|
||||
int num_blocks; ///< number of blocks (symbols) stored in the mag array
|
||||
int num_bins; ///< number of FFT bins in terms of 6.25 Hz
|
||||
int time_osr; ///< number of time subdivisions
|
||||
int freq_osr; ///< number of frequency subdivisions
|
||||
uint8_t* mag; ///< FFT magnitudes stored as uint8_t[blocks][time_osr][freq_osr][num_bins]
|
||||
int block_stride; ///< Helper value = time_osr * freq_osr * num_bins
|
||||
ftx_protocol_t protocol; ///< Indicate if using FT4 or FT8
|
||||
} waterfall_t;
|
||||
/// Input structure to ft8_find_sync() function. This structure describes stored waterfall data over the whole message slot.
|
||||
/// Fields time_osr and freq_osr specify additional oversampling rate for time and frequency resolution.
|
||||
/// If time_osr=1, FFT magnitude data is collected once for every symbol transmitted, i.e. every 1/6.25 = 0.16 seconds.
|
||||
/// Values time_osr > 1 mean each symbol is further subdivided in time.
|
||||
/// If freq_osr=1, each bin in the FFT magnitude data corresponds to 6.25 Hz, which is the tone spacing.
|
||||
/// Values freq_osr > 1 mean the tone spacing is further subdivided by FFT analysis.
|
||||
typedef struct
|
||||
{
|
||||
int max_blocks; ///< number of blocks (symbols) allocated in the mag array
|
||||
int num_blocks; ///< number of blocks (symbols) stored in the mag array
|
||||
int num_bins; ///< number of FFT bins in terms of 6.25 Hz
|
||||
int time_osr; ///< number of time subdivisions
|
||||
int freq_osr; ///< number of frequency subdivisions
|
||||
uint8_t* mag; ///< FFT magnitudes stored as uint8_t[blocks][time_osr][freq_osr][num_bins]
|
||||
int block_stride; ///< Helper value = time_osr * freq_osr * num_bins
|
||||
ftx_protocol_t protocol; ///< Indicate if using FT4 or FT8
|
||||
} waterfall_t;
|
||||
|
||||
/// Output structure of ft8_find_sync() and input structure of ft8_decode().
|
||||
/// Holds the position of potential start of a message in time and frequency.
|
||||
typedef struct
|
||||
{
|
||||
int16_t score; ///< Candidate score (non-negative number; higher score means higher likelihood)
|
||||
int16_t time_offset; ///< Index of the time block
|
||||
int16_t freq_offset; ///< Index of the frequency bin
|
||||
uint8_t time_sub; ///< Index of the time subdivision used
|
||||
uint8_t freq_sub; ///< Index of the frequency subdivision used
|
||||
} candidate_t;
|
||||
/// Output structure of ft8_find_sync() and input structure of ft8_decode().
|
||||
/// Holds the position of potential start of a message in time and frequency.
|
||||
typedef struct
|
||||
{
|
||||
int16_t score; ///< Candidate score (non-negative number; higher score means higher likelihood)
|
||||
int16_t time_offset; ///< Index of the time block
|
||||
int16_t freq_offset; ///< Index of the frequency bin
|
||||
uint8_t time_sub; ///< Index of the time subdivision used
|
||||
uint8_t freq_sub; ///< Index of the frequency subdivision used
|
||||
int16_t snr;
|
||||
} candidate_t;
|
||||
|
||||
/// Structure that holds the decoded message
|
||||
typedef struct
|
||||
{
|
||||
// TODO: check again that this size is enough
|
||||
char text[25]; ///< Plain text
|
||||
uint16_t hash; ///< Hash value to be used in hash table and quick checking for duplicates
|
||||
} message_t;
|
||||
/// Structure that holds the decoded message
|
||||
typedef struct
|
||||
{
|
||||
// TODO: check again that this size is enough
|
||||
char text[25]; ///< Plain text
|
||||
uint16_t hash; ///< Hash value to be used in hash table and quick checking for duplicates
|
||||
} message_t;
|
||||
|
||||
/// Structure that contains the status of various steps during decoding of a message
|
||||
typedef struct
|
||||
{
|
||||
int ldpc_errors; ///< Number of LDPC errors during decoding
|
||||
uint16_t crc_extracted; ///< CRC value recovered from the message
|
||||
uint16_t crc_calculated; ///< CRC value calculated over the payload
|
||||
int unpack_status; ///< Return value of the unpack routine
|
||||
} decode_status_t;
|
||||
/// Structure that contains the status of various steps during decoding of a message
|
||||
typedef struct
|
||||
{
|
||||
int ldpc_errors; ///< Number of LDPC errors during decoding
|
||||
uint16_t crc_extracted; ///< CRC value recovered from the message
|
||||
uint16_t crc_calculated; ///< CRC value calculated over the payload
|
||||
int unpack_status; ///< Return value of the unpack routine
|
||||
} decode_status_t;
|
||||
|
||||
/// Localize top N candidates in frequency and time according to their sync strength (looking at Costas symbols)
|
||||
/// We treat and organize the candidate list as a min-heap (empty initially).
|
||||
/// @param[in] power Waterfall data collected during message slot
|
||||
/// @param[in] sync_pattern Synchronization pattern
|
||||
/// @param[in] num_candidates Number of maximum candidates (size of heap array)
|
||||
/// @param[in,out] heap Array of candidate_t type entries (with num_candidates allocated entries)
|
||||
/// @param[in] min_score Minimal score allowed for pruning unlikely candidates (can be zero for no effect)
|
||||
/// @return Number of candidates filled in the heap
|
||||
int ft8_find_sync(const waterfall_t* power, int num_candidates, candidate_t heap[], int min_score);
|
||||
/// Localize top N candidates in frequency and time according to their sync strength (looking at Costas symbols)
|
||||
/// We treat and organize the candidate list as a min-heap (empty initially).
|
||||
/// @param[in] power Waterfall data collected during message slot
|
||||
/// @param[in] sync_pattern Synchronization pattern
|
||||
/// @param[in] num_candidates Number of maximum candidates (size of heap array)
|
||||
/// @param[in,out] heap Array of candidate_t type entries (with num_candidates allocated entries)
|
||||
/// @param[in] min_score Minimal score allowed for pruning unlikely candidates (can be zero for no effect)
|
||||
/// @return Number of candidates filled in the heap
|
||||
int ft8_find_sync(const waterfall_t* power, int num_candidates, candidate_t heap[], int min_score);
|
||||
|
||||
/// Attempt to decode a message candidate. Extracts the bit probabilities, runs LDPC decoder, checks CRC and unpacks the message in plain text.
|
||||
/// @param[in] power Waterfall data collected during message slot
|
||||
/// @param[in] cand Candidate to decode
|
||||
/// @param[out] message message_t structure that will receive the decoded message
|
||||
/// @param[in] max_iterations Maximum allowed LDPC iterations (lower number means faster decode, but less precise)
|
||||
/// @param[out] status decode_status_t structure that will be filled with the status of various decoding steps
|
||||
/// @return True if the decoding was successful, false otherwise (check status for details)
|
||||
bool ft8_decode(const waterfall_t* power, const candidate_t* cand, message_t* message, int max_iterations, decode_status_t* status);
|
||||
/// Attempt to decode a message candidate. Extracts the bit probabilities, runs LDPC decoder, checks CRC and unpacks the message in plain text.
|
||||
/// @param[in] power Waterfall data collected during message slot
|
||||
/// @param[in] cand Candidate to decode
|
||||
/// @param[out] message message_t structure that will receive the decoded message
|
||||
/// @param[in] max_iterations Maximum allowed LDPC iterations (lower number means faster decode, but less precise)
|
||||
/// @param[out] status decode_status_t structure that will be filled with the status of various decoding steps
|
||||
/// @return True if the decoding was successful, false otherwise (check status for details)
|
||||
bool ft8_decode(const waterfall_t* power, const candidate_t* cand, message_t* message, int max_iterations, const unpack_hash_interface_t* hash_if, decode_status_t* status);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
44
ft8/encode.h
44
ft8/encode.h
|
@ -8,31 +8,31 @@ extern "C"
|
|||
{
|
||||
#endif
|
||||
|
||||
// typedef struct
|
||||
// {
|
||||
// uint8_t tones[FT8_NN];
|
||||
// // for waveform readout:
|
||||
// int n_spsym; // Number of waveform samples per symbol
|
||||
// float *pulse; // [3 * n_spsym]
|
||||
// int idx_symbol; // Index of the current symbol
|
||||
// float f0; // Base frequency, Hertz
|
||||
// float signal_rate; // Waveform sample rate, Hertz
|
||||
// } encoder_t;
|
||||
// typedef struct
|
||||
// {
|
||||
// uint8_t tones[FT8_NN];
|
||||
// // for waveform readout:
|
||||
// int n_spsym; // Number of waveform samples per symbol
|
||||
// float *pulse; // [3 * n_spsym]
|
||||
// int idx_symbol; // Index of the current symbol
|
||||
// float f0; // Base frequency, Hertz
|
||||
// float signal_rate; // Waveform sample rate, Hertz
|
||||
// } encoder_t;
|
||||
|
||||
// void encoder_init(float signal_rate, float *pulse_buffer);
|
||||
// void encoder_set_f0(float f0);
|
||||
// void encoder_process(const message_t *message); // in: message
|
||||
// void encoder_generate(float *block); // out: block of waveforms
|
||||
// void encoder_init(float signal_rate, float *pulse_buffer);
|
||||
// void encoder_set_f0(float f0);
|
||||
// void encoder_process(const message_t *message); // in: message
|
||||
// void encoder_generate(float *block); // out: block of waveforms
|
||||
|
||||
/// Generate FT8 tone sequence from payload data
|
||||
/// @param[in] payload - 10 byte array consisting of 77 bit payload
|
||||
/// @param[out] tones - array of FT8_NN (79) bytes to store the generated tones (encoded as 0..7)
|
||||
void ft8_encode(const uint8_t* payload, uint8_t* tones);
|
||||
/// Generate FT8 tone sequence from payload data
|
||||
/// @param[in] payload - 10 byte array consisting of 77 bit payload
|
||||
/// @param[out] tones - array of FT8_NN (79) bytes to store the generated tones (encoded as 0..7)
|
||||
void ft8_encode(const uint8_t* payload, uint8_t* tones);
|
||||
|
||||
/// Generate FT4 tone sequence from payload data
|
||||
/// @param[in] payload - 10 byte array consisting of 77 bit payload
|
||||
/// @param[out] tones - array of FT4_NN (105) bytes to store the generated tones (encoded as 0..3)
|
||||
void ft4_encode(const uint8_t* payload, uint8_t* tones);
|
||||
/// Generate FT4 tone sequence from payload data
|
||||
/// @param[in] payload - 10 byte array consisting of 77 bit payload
|
||||
/// @param[out] tones - array of FT4_NN (105) bytes to store the generated tones (encoded as 0..3)
|
||||
void ft4_encode(const uint8_t* payload, uint8_t* tones);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
12
ft8/ldpc.h
12
ft8/ldpc.h
|
@ -8,13 +8,13 @@ extern "C"
|
|||
{
|
||||
#endif
|
||||
|
||||
// codeword is 174 log-likelihoods.
|
||||
// plain is a return value, 174 ints, to be 0 or 1.
|
||||
// iters is how hard to try.
|
||||
// ok == 87 means success.
|
||||
void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int* ok);
|
||||
// codeword is 174 log-likelihoods.
|
||||
// plain is a return value, 174 ints, to be 0 or 1.
|
||||
// iters is how hard to try.
|
||||
// ok == 87 means success.
|
||||
void ldpc_decode(float codeword[], int max_iters, uint8_t plain[], int* ok);
|
||||
|
||||
void bp_decode(float codeword[], int max_iters, uint8_t plain[], int* ok);
|
||||
void bp_decode(float codeword[], int max_iters, uint8_t plain[], int* ok);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
34
ft8/pack.c
34
ft8/pack.c
|
@ -10,16 +10,9 @@
|
|||
#define MAX22 ((uint32_t)4194304L)
|
||||
#define MAXGRID4 ((uint16_t)32400)
|
||||
|
||||
// TODO: This is wasteful, should figure out something more elegant
|
||||
const char A0[] = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-./?";
|
||||
const char A1[] = " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
const char A2[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
const char A3[] = "0123456789";
|
||||
const char A4[] = " ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
|
||||
// Pack a special token, a 22-bit hash code, or a valid base call
|
||||
// into a 28-bit integer.
|
||||
int32_t pack28(const char* callsign)
|
||||
static int32_t pack28(const char* callsign)
|
||||
{
|
||||
// Check for special tokens first
|
||||
if (starts_with(callsign, "DE "))
|
||||
|
@ -74,8 +67,13 @@ int32_t pack28(const char* callsign)
|
|||
}
|
||||
|
||||
// Check for standard callsign
|
||||
int i0, i1, i2, i3, i4, i5;
|
||||
if ((i0 = char_index(A1, c6[0])) >= 0 && (i1 = char_index(A2, c6[1])) >= 0 && (i2 = char_index(A3, c6[2])) >= 0 && (i3 = char_index(A4, c6[3])) >= 0 && (i4 = char_index(A4, c6[4])) >= 0 && (i5 = char_index(A4, c6[5])) >= 0)
|
||||
int i0 = nchar(c6[0], FT8_CHAR_TABLE_ALPHANUM_SPACE);
|
||||
int i1 = nchar(c6[1], FT8_CHAR_TABLE_ALPHANUM);
|
||||
int i2 = nchar(c6[2], FT8_CHAR_TABLE_NUMERIC);
|
||||
int i3 = nchar(c6[3], FT8_CHAR_TABLE_LETTERS_SPACE);
|
||||
int i4 = nchar(c6[4], FT8_CHAR_TABLE_LETTERS_SPACE);
|
||||
int i5 = nchar(c6[5], FT8_CHAR_TABLE_LETTERS_SPACE);
|
||||
if ((i0 >= 0) && (i1 >= 0) && (i2 >= 0) && (i3 >= 0) && (i4 >= 0) && (i5 >= 0))
|
||||
{
|
||||
// This is a standard callsign
|
||||
int32_t n28 = i0;
|
||||
|
@ -87,8 +85,8 @@ int32_t pack28(const char* callsign)
|
|||
return NTOKENS + MAX22 + n28;
|
||||
}
|
||||
|
||||
//char text[13];
|
||||
//if (length > 13) return -1;
|
||||
// char text[13];
|
||||
// if (length > 13) return -1;
|
||||
|
||||
// TODO:
|
||||
// Treat this as a nonstandard callsign: compute its 22-bit hash
|
||||
|
@ -98,7 +96,7 @@ int32_t pack28(const char* callsign)
|
|||
// Check if a string could be a valid standard callsign or a valid
|
||||
// compound callsign.
|
||||
// Return base call "bc" and a logical "cok" indicator.
|
||||
bool chkcall(const char* call, char* bc)
|
||||
static bool chkcall(const char* call, char* bc)
|
||||
{
|
||||
int length = strlen(call); // n1=len_trim(w)
|
||||
if (length > 11)
|
||||
|
@ -119,7 +117,7 @@ bool chkcall(const char* call, char* bc)
|
|||
return true;
|
||||
}
|
||||
|
||||
uint16_t packgrid(const char* grid4)
|
||||
static uint16_t packgrid(const char* grid4)
|
||||
{
|
||||
if (grid4 == 0)
|
||||
{
|
||||
|
@ -164,14 +162,14 @@ uint16_t packgrid(const char* grid4)
|
|||
}
|
||||
|
||||
// Pack Type 1 (Standard 77-bit message) and Type 2 (ditto, with a "/P" call)
|
||||
int pack77_1(const char* msg, uint8_t* b77)
|
||||
static int pack77_1(const char* msg, uint8_t* b77)
|
||||
{
|
||||
// Locate the first delimiter
|
||||
const char* s1 = strchr(msg, ' ');
|
||||
if (s1 == 0)
|
||||
return -1;
|
||||
|
||||
const char* call1 = msg; // 1st call
|
||||
const char* call1 = msg; // 1st call
|
||||
const char* call2 = s1 + 1; // 2nd call
|
||||
|
||||
int32_t n28a = pack28(call1);
|
||||
|
@ -217,7 +215,7 @@ int pack77_1(const char* msg, uint8_t* b77)
|
|||
return 0;
|
||||
}
|
||||
|
||||
void packtext77(const char* text, uint8_t* b77)
|
||||
static void packtext77(const char* text, uint8_t* b77)
|
||||
{
|
||||
int length = strlen(text);
|
||||
|
||||
|
@ -254,7 +252,7 @@ void packtext77(const char* text, uint8_t* b77)
|
|||
// Get the index of the current char
|
||||
if (j < length)
|
||||
{
|
||||
int q = char_index(A0, text[j]);
|
||||
int q = nchar(text[j], FT8_CHAR_TABLE_FULL);
|
||||
x = (q > 0) ? q : 0;
|
||||
}
|
||||
else
|
||||
|
|
|
@ -8,10 +8,11 @@ extern "C"
|
|||
{
|
||||
#endif
|
||||
|
||||
// Pack FT8 text message into 72 bits
|
||||
// [IN] msg - FT8 message (e.g. "CQ TE5T KN01")
|
||||
// [OUT] c77 - 10 byte array to store the 77 bit payload (MSB first)
|
||||
int pack77(const char* msg, uint8_t* c77);
|
||||
/// Parse and pack FT8/FT4 text message into 77 bit binary payload
|
||||
/// @param[in] msg FT8/FT4 message (e.g. "CQ TE5T KN01")
|
||||
/// @param[out] c77 10 byte array to store the 77 bit payload (MSB first)
|
||||
/// @return Parsing result (0 - success, otherwise error)
|
||||
int pack77(const char* msg, uint8_t* c77);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
43
ft8/text.c
43
ft8/text.c
|
@ -67,18 +67,6 @@ bool equals(const char* string1, const char* string2)
|
|||
return 0 == strcmp(string1, string2);
|
||||
}
|
||||
|
||||
int char_index(const char* string, char c)
|
||||
{
|
||||
for (int i = 0; *string; ++i, ++string)
|
||||
{
|
||||
if (c == *string)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1; // Not found
|
||||
}
|
||||
|
||||
// Text message formatting:
|
||||
// - replaces lowercase letters with uppercase
|
||||
// - merges consecutive spaces into single space
|
||||
|
@ -164,40 +152,33 @@ void int_to_dd(char* str, int value, int width, bool full_sign)
|
|||
*str = 0; // Add zero terminator
|
||||
}
|
||||
|
||||
// convert integer index to ASCII character according to one of 6 tables:
|
||||
// table 0: " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-./?"
|
||||
// table 1: " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
// table 2: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
// table 3: "0123456789"
|
||||
// table 4: " ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
// table 5: " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ/"
|
||||
char charn(int c, int table_idx)
|
||||
char charn(int c, ft8_char_table_e table)
|
||||
{
|
||||
if (table_idx != 2 && table_idx != 3)
|
||||
if ((table != FT8_CHAR_TABLE_ALPHANUM) && (table != FT8_CHAR_TABLE_NUMERIC))
|
||||
{
|
||||
if (c == 0)
|
||||
return ' ';
|
||||
c -= 1;
|
||||
}
|
||||
if (table_idx != 4)
|
||||
if (table != FT8_CHAR_TABLE_LETTERS_SPACE)
|
||||
{
|
||||
if (c < 10)
|
||||
return '0' + c;
|
||||
c -= 10;
|
||||
}
|
||||
if (table_idx != 3)
|
||||
if (table != FT8_CHAR_TABLE_NUMERIC)
|
||||
{
|
||||
if (c < 26)
|
||||
return 'A' + c;
|
||||
c -= 26;
|
||||
}
|
||||
|
||||
if (table_idx == 0)
|
||||
if (table == FT8_CHAR_TABLE_FULL)
|
||||
{
|
||||
if (c < 5)
|
||||
return "+-./?"[c];
|
||||
}
|
||||
else if (table_idx == 5)
|
||||
else if (table == FT8_CHAR_TABLE_ALPHANUM_SPACE_SLASH)
|
||||
{
|
||||
if (c == 0)
|
||||
return '/';
|
||||
|
@ -207,29 +188,29 @@ char charn(int c, int table_idx)
|
|||
}
|
||||
|
||||
// Convert character to its index (charn in reverse) according to a table
|
||||
int nchar(char c, int table_idx)
|
||||
int nchar(char c, ft8_char_table_e table)
|
||||
{
|
||||
int n = 0;
|
||||
if (table_idx != 2 && table_idx != 3)
|
||||
if ((table != FT8_CHAR_TABLE_ALPHANUM) && (table != FT8_CHAR_TABLE_NUMERIC))
|
||||
{
|
||||
if (c == ' ')
|
||||
return n + 0;
|
||||
n += 1;
|
||||
}
|
||||
if (table_idx != 4)
|
||||
if (table != FT8_CHAR_TABLE_LETTERS_SPACE)
|
||||
{
|
||||
if (c >= '0' && c <= '9')
|
||||
return n + (c - '0');
|
||||
n += 10;
|
||||
}
|
||||
if (table_idx != 3)
|
||||
if (table != FT8_CHAR_TABLE_NUMERIC)
|
||||
{
|
||||
if (c >= 'A' && c <= 'Z')
|
||||
return n + (c - 'A');
|
||||
n += 26;
|
||||
}
|
||||
|
||||
if (table_idx == 0)
|
||||
if (table == FT8_CHAR_TABLE_FULL)
|
||||
{
|
||||
if (c == '+')
|
||||
return n + 0;
|
||||
|
@ -242,7 +223,7 @@ int nchar(char c, int table_idx)
|
|||
if (c == '?')
|
||||
return n + 4;
|
||||
}
|
||||
else if (table_idx == 5)
|
||||
else if (table == FT8_CHAR_TABLE_ALPHANUM_SPACE_SLASH)
|
||||
{
|
||||
if (c == '/')
|
||||
return n + 0;
|
||||
|
|
55
ft8/text.h
55
ft8/text.h
|
@ -9,35 +9,46 @@ extern "C"
|
|||
{
|
||||
#endif
|
||||
|
||||
// Utility functions for characters and strings
|
||||
// Utility functions for characters and strings
|
||||
|
||||
const char* trim_front(const char* str);
|
||||
void trim_back(char* str);
|
||||
char* trim(char* str);
|
||||
const char* trim_front(const char* str);
|
||||
void trim_back(char* str);
|
||||
char* trim(char* str);
|
||||
|
||||
char to_upper(char c);
|
||||
bool is_digit(char c);
|
||||
bool is_letter(char c);
|
||||
bool is_space(char c);
|
||||
bool in_range(char c, char min, char max);
|
||||
bool starts_with(const char* string, const char* prefix);
|
||||
bool equals(const char* string1, const char* string2);
|
||||
char to_upper(char c);
|
||||
bool is_digit(char c);
|
||||
bool is_letter(char c);
|
||||
bool is_space(char c);
|
||||
bool in_range(char c, char min, char max);
|
||||
bool starts_with(const char* string, const char* prefix);
|
||||
bool equals(const char* string1, const char* string2);
|
||||
|
||||
int char_index(const char* string, char c);
|
||||
// Text message formatting:
|
||||
// - replaces lowercase letters with uppercase
|
||||
// - merges consecutive spaces into single space
|
||||
void fmtmsg(char* msg_out, const char* msg_in);
|
||||
|
||||
// Text message formatting:
|
||||
// - replaces lowercase letters with uppercase
|
||||
// - merges consecutive spaces into single space
|
||||
void fmtmsg(char* msg_out, const char* msg_in);
|
||||
// Parse a 2 digit integer from string
|
||||
int dd_to_int(const char* str, int length);
|
||||
|
||||
// Parse a 2 digit integer from string
|
||||
int dd_to_int(const char* str, int length);
|
||||
// Convert a 2 digit integer to string
|
||||
void int_to_dd(char* str, int value, int width, bool full_sign);
|
||||
|
||||
// Convert a 2 digit integer to string
|
||||
void int_to_dd(char* str, int value, int width, bool full_sign);
|
||||
typedef enum
|
||||
{
|
||||
FT8_CHAR_TABLE_FULL, // table[42] " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ+-./?"
|
||||
FT8_CHAR_TABLE_ALPHANUM_SPACE_SLASH, // table[38] " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ/"
|
||||
FT8_CHAR_TABLE_ALPHANUM_SPACE, // table[37] " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
FT8_CHAR_TABLE_LETTERS_SPACE, // table[27] " ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
FT8_CHAR_TABLE_ALPHANUM, // table[36] "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
FT8_CHAR_TABLE_NUMERIC, // table[10] "0123456789"
|
||||
} ft8_char_table_e;
|
||||
|
||||
char charn(int c, int table_idx);
|
||||
int nchar(char c, int table_idx);
|
||||
/// Convert integer index to ASCII character according to one of character tables
|
||||
char charn(int c, ft8_char_table_e table);
|
||||
|
||||
/// Look up the index of an ASCII character in one of character tables
|
||||
int nchar(char c, ft8_char_table_e table);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
125
ft8/unpack.c
125
ft8/unpack.c
|
@ -15,7 +15,7 @@
|
|||
|
||||
// n28 is a 28-bit integer, e.g. n28a or n28b, containing all the
|
||||
// call sign bits from a packed message.
|
||||
int unpack_callsign(uint32_t n28, uint8_t ip, uint8_t i3, char* result)
|
||||
static int unpack_callsign(uint32_t n28, uint8_t ip, uint8_t i3, char* result, const unpack_hash_interface_t* hash_if)
|
||||
{
|
||||
// Check for special tokens DE, QRZ, CQ, CQ_nnn, CQ_aaaa
|
||||
if (n28 < NTOKENS)
|
||||
|
@ -46,7 +46,7 @@ int unpack_callsign(uint32_t n28, uint8_t ip, uint8_t i3, char* result)
|
|||
aaaa[4] = '\0';
|
||||
for (int i = 3; /* */; --i)
|
||||
{
|
||||
aaaa[i] = charn(n % 27, 4);
|
||||
aaaa[i] = charn(n % 27, FT8_CHAR_TABLE_LETTERS_SPACE);
|
||||
if (i == 0)
|
||||
break;
|
||||
n /= 27;
|
||||
|
@ -64,12 +64,14 @@ int unpack_callsign(uint32_t n28, uint8_t ip, uint8_t i3, char* result)
|
|||
if (n28 < MAX22)
|
||||
{
|
||||
// This is a 22-bit hash of a result
|
||||
// TODO: implement
|
||||
strcpy(result, "<...>");
|
||||
// result[0] = '<';
|
||||
// int_to_dd(result + 1, n28, 7, false);
|
||||
// result[8] = '>';
|
||||
// result[9] = '\0';
|
||||
if (hash_if != NULL)
|
||||
{
|
||||
hash_if->hash22(n28, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
strcpy(result, "<...>");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -78,17 +80,17 @@ int unpack_callsign(uint32_t n28, uint8_t ip, uint8_t i3, char* result)
|
|||
|
||||
char callsign[7];
|
||||
callsign[6] = '\0';
|
||||
callsign[5] = charn(n % 27, 4);
|
||||
callsign[5] = charn(n % 27, FT8_CHAR_TABLE_LETTERS_SPACE);
|
||||
n /= 27;
|
||||
callsign[4] = charn(n % 27, 4);
|
||||
callsign[4] = charn(n % 27, FT8_CHAR_TABLE_LETTERS_SPACE);
|
||||
n /= 27;
|
||||
callsign[3] = charn(n % 27, 4);
|
||||
callsign[3] = charn(n % 27, FT8_CHAR_TABLE_LETTERS_SPACE);
|
||||
n /= 27;
|
||||
callsign[2] = charn(n % 10, 3);
|
||||
callsign[2] = charn(n % 10, FT8_CHAR_TABLE_NUMERIC);
|
||||
n /= 10;
|
||||
callsign[1] = charn(n % 36, 2);
|
||||
callsign[1] = charn(n % 36, FT8_CHAR_TABLE_ALPHANUM);
|
||||
n /= 36;
|
||||
callsign[0] = charn(n % 37, 1);
|
||||
callsign[0] = charn(n % 37, FT8_CHAR_TABLE_ALPHANUM_SPACE);
|
||||
|
||||
// Skip trailing and leading whitespace in case of a short callsign
|
||||
strcpy(result, trim(callsign));
|
||||
|
@ -111,7 +113,7 @@ int unpack_callsign(uint32_t n28, uint8_t ip, uint8_t i3, char* result)
|
|||
return 0; // Success
|
||||
}
|
||||
|
||||
int unpack_type1(const uint8_t* a77, uint8_t i3, char* call_to, char* call_de, char* extra)
|
||||
static int unpack_type1(const uint8_t* a77, uint8_t i3, char* call_to, char* call_de, char* extra, const unpack_hash_interface_t* hash_if)
|
||||
{
|
||||
uint32_t n28a, n28b;
|
||||
uint16_t igrid4;
|
||||
|
@ -133,23 +135,25 @@ int unpack_type1(const uint8_t* a77, uint8_t i3, char* call_to, char* call_de, c
|
|||
igrid4 |= (a77[9] >> 6);
|
||||
|
||||
// Unpack both callsigns
|
||||
if (unpack_callsign(n28a >> 1, n28a & 0x01, i3, call_to) < 0)
|
||||
if (unpack_callsign(n28a >> 1, n28a & 0x01, i3, call_to, hash_if) < 0)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
if (unpack_callsign(n28b >> 1, n28b & 0x01, i3, call_de) < 0)
|
||||
if (unpack_callsign(n28b >> 1, n28b & 0x01, i3, call_de, hash_if) < 0)
|
||||
{
|
||||
return -2;
|
||||
}
|
||||
// Fix "CQ_" to "CQ " -> already done in unpack_callsign()
|
||||
|
||||
// TODO: add to recent calls
|
||||
// if (call_to[0] != '<' && strlen(call_to) >= 4) {
|
||||
// save_hash_call(call_to)
|
||||
// }
|
||||
// if (call_de[0] != '<' && strlen(call_de) >= 4) {
|
||||
// save_hash_call(call_de)
|
||||
// }
|
||||
if ((call_to[0] != '<') && (strlen(call_to) >= 4) && (hash_if != NULL))
|
||||
{
|
||||
hash_if->save_hash(call_to);
|
||||
}
|
||||
if ((call_de[0] != '<') && (strlen(call_de) >= 4) && (hash_if != NULL))
|
||||
{
|
||||
hash_if->save_hash(call_de);
|
||||
}
|
||||
|
||||
char* dst = extra;
|
||||
|
||||
|
@ -208,7 +212,7 @@ int unpack_type1(const uint8_t* a77, uint8_t i3, char* call_to, char* call_de, c
|
|||
return 0; // Success
|
||||
}
|
||||
|
||||
int unpack_text(const uint8_t* a71, char* text)
|
||||
static int unpack_text(const uint8_t* a71, char* text)
|
||||
{
|
||||
// TODO: test
|
||||
uint8_t b71[9];
|
||||
|
@ -233,14 +237,14 @@ int unpack_text(const uint8_t* a71, char* text)
|
|||
b71[i] = rem / 42;
|
||||
rem = rem % 42;
|
||||
}
|
||||
c14[idx] = charn(rem, 0);
|
||||
c14[idx] = charn(rem, FT8_CHAR_TABLE_FULL);
|
||||
}
|
||||
|
||||
strcpy(text, trim(c14));
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
int unpack_telemetry(const uint8_t* a71, char* telemetry)
|
||||
static int unpack_telemetry(const uint8_t* a71, char* telemetry)
|
||||
{
|
||||
uint8_t b71[9];
|
||||
|
||||
|
@ -267,27 +271,27 @@ int unpack_telemetry(const uint8_t* a71, char* telemetry)
|
|||
return 0;
|
||||
}
|
||||
|
||||
//none standard for wsjt-x 2.0
|
||||
//by KD8CEC
|
||||
int unpack_nonstandard(const uint8_t* a77, char* call_to, char* call_de, char* extra)
|
||||
// none standard for wsjt-x 2.0
|
||||
// by KD8CEC
|
||||
static int unpack_nonstandard(const uint8_t* a77, char* call_to, char* call_de, char* extra, const unpack_hash_interface_t* hash_if)
|
||||
{
|
||||
uint32_t n12, iflip, nrpt, icq;
|
||||
uint64_t n58;
|
||||
n12 = (a77[0] << 4); //11 ~4 : 8
|
||||
n12 |= (a77[1] >> 4); //3~0 : 12
|
||||
n12 = (a77[0] << 4); // 11 ~4 : 8
|
||||
n12 |= (a77[1] >> 4); // 3~0 : 12
|
||||
|
||||
n58 = ((uint64_t)(a77[1] & 0x0F) << 54); //57 ~ 54 : 4
|
||||
n58 |= ((uint64_t)a77[2] << 46); //53 ~ 46 : 12
|
||||
n58 |= ((uint64_t)a77[3] << 38); //45 ~ 38 : 12
|
||||
n58 |= ((uint64_t)a77[4] << 30); //37 ~ 30 : 12
|
||||
n58 |= ((uint64_t)a77[5] << 22); //29 ~ 22 : 12
|
||||
n58 |= ((uint64_t)a77[6] << 14); //21 ~ 14 : 12
|
||||
n58 |= ((uint64_t)a77[7] << 6); //13 ~ 6 : 12
|
||||
n58 |= ((uint64_t)a77[8] >> 2); //5 ~ 0 : 765432 10
|
||||
n58 = ((uint64_t)(a77[1] & 0x0F) << 54); // 57 ~ 54 : 4
|
||||
n58 |= ((uint64_t)a77[2] << 46); // 53 ~ 46 : 12
|
||||
n58 |= ((uint64_t)a77[3] << 38); // 45 ~ 38 : 12
|
||||
n58 |= ((uint64_t)a77[4] << 30); // 37 ~ 30 : 12
|
||||
n58 |= ((uint64_t)a77[5] << 22); // 29 ~ 22 : 12
|
||||
n58 |= ((uint64_t)a77[6] << 14); // 21 ~ 14 : 12
|
||||
n58 |= ((uint64_t)a77[7] << 6); // 13 ~ 6 : 12
|
||||
n58 |= ((uint64_t)a77[8] >> 2); // 5 ~ 0 : 765432 10
|
||||
|
||||
iflip = (a77[8] >> 1) & 0x01; //76543210
|
||||
iflip = (a77[8] >> 1) & 0x01; // 76543210
|
||||
nrpt = ((a77[8] & 0x01) << 1);
|
||||
nrpt |= (a77[9] >> 7); //76543210
|
||||
nrpt |= (a77[9] >> 7); // 76543210
|
||||
icq = ((a77[9] >> 6) & 0x01);
|
||||
|
||||
char c11[12];
|
||||
|
@ -295,27 +299,32 @@ int unpack_nonstandard(const uint8_t* a77, char* call_to, char* call_de, char* e
|
|||
|
||||
for (int i = 10; /* no condition */; --i)
|
||||
{
|
||||
c11[i] = charn(n58 % 38, 5);
|
||||
c11[i] = charn(n58 % 38, FT8_CHAR_TABLE_ALPHANUM_SPACE_SLASH);
|
||||
if (i == 0)
|
||||
break;
|
||||
n58 /= 38;
|
||||
}
|
||||
|
||||
char call_3[15];
|
||||
// should replace with hash12(n12, call_3);
|
||||
strcpy(call_3, "<...>");
|
||||
// call_3[0] = '<';
|
||||
// int_to_dd(call_3 + 1, n12, 4, false);
|
||||
// call_3[5] = '>';
|
||||
// call_3[6] = '\0';
|
||||
if (hash_if != NULL)
|
||||
{
|
||||
hash_if->hash12(n12, call_3);
|
||||
}
|
||||
else
|
||||
{
|
||||
strcpy(call_3, "<...>");
|
||||
}
|
||||
|
||||
char* call_1 = (iflip) ? c11 : call_3;
|
||||
char* call_2 = (iflip) ? call_3 : c11;
|
||||
//save_hash_call(c11_trimmed);
|
||||
char* call_1 = trim((iflip) ? c11 : call_3);
|
||||
char* call_2 = trim((iflip) ? call_3 : c11);
|
||||
if (hash_if != NULL)
|
||||
{
|
||||
hash_if->save_hash(c11);
|
||||
}
|
||||
|
||||
if (icq == 0)
|
||||
{
|
||||
strcpy(call_to, trim(call_1));
|
||||
strcpy(call_to, call_1);
|
||||
if (nrpt == 1)
|
||||
strcpy(extra, "RRR");
|
||||
else if (nrpt == 2)
|
||||
|
@ -332,12 +341,12 @@ int unpack_nonstandard(const uint8_t* a77, char* call_to, char* call_de, char* e
|
|||
strcpy(call_to, "CQ");
|
||||
extra[0] = '\0';
|
||||
}
|
||||
strcpy(call_de, trim(call_2));
|
||||
strcpy(call_de, call_2);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int unpack77_fields(const uint8_t* a77, char* call_to, char* call_de, char* extra)
|
||||
int unpack77_fields(const uint8_t* a77, char* call_to, char* call_de, char* extra, const unpack_hash_interface_t* hash_if)
|
||||
{
|
||||
call_to[0] = call_de[0] = extra[0] = '\0';
|
||||
|
||||
|
@ -373,7 +382,7 @@ int unpack77_fields(const uint8_t* a77, char* call_to, char* call_de, char* extr
|
|||
else if (i3 == 1 || i3 == 2)
|
||||
{
|
||||
// Type 1 (standard message) or Type 2 ("/P" form for EU VHF contest)
|
||||
return unpack_type1(a77, i3, call_to, call_de, extra);
|
||||
return unpack_type1(a77, i3, call_to, call_de, extra, hash_if);
|
||||
}
|
||||
// else if (i3 == 3) {
|
||||
// // Type 3: ARRL RTTY Contest
|
||||
|
@ -383,7 +392,7 @@ int unpack77_fields(const uint8_t* a77, char* call_to, char* call_de, char* extr
|
|||
// // Type 4: Nonstandard calls, e.g. <WA9XYZ> PJ4/KA1ABC RR73
|
||||
// // One hashed call or "CQ"; one compound or nonstandard call with up
|
||||
// // to 11 characters; and (if not "CQ") an optional RRR, RR73, or 73.
|
||||
return unpack_nonstandard(a77, call_to, call_de, extra);
|
||||
return unpack_nonstandard(a77, call_to, call_de, extra, hash_if);
|
||||
}
|
||||
// else if (i3 == 5) {
|
||||
// // Type 5: TU; W9XYZ K1ABC R-09 FN 1 28 28 1 7 9 74 WWROF contest
|
||||
|
@ -393,13 +402,13 @@ int unpack77_fields(const uint8_t* a77, char* call_to, char* call_de, char* extr
|
|||
return -1;
|
||||
}
|
||||
|
||||
int unpack77(const uint8_t* a77, char* message)
|
||||
int unpack77(const uint8_t* a77, char* message, const unpack_hash_interface_t* hash_if)
|
||||
{
|
||||
char call_to[14];
|
||||
char call_de[14];
|
||||
char extra[19];
|
||||
|
||||
int rc = unpack77_fields(a77, call_to, call_de, extra);
|
||||
int rc = unpack77_fields(a77, call_to, call_de, extra, hash_if);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
|
||||
|
|
28
ft8/unpack.h
28
ft8/unpack.h
|
@ -8,13 +8,29 @@ extern "C"
|
|||
{
|
||||
#endif
|
||||
|
||||
// field1 - at least 14 bytes
|
||||
// field2 - at least 14 bytes
|
||||
// field3 - at least 7 bytes
|
||||
int unpack77_fields(const uint8_t* a77, char* field1, char* field2, char* field3);
|
||||
typedef struct
|
||||
{
|
||||
/// Called when a callsign is looked up by its 22 bit hash code
|
||||
void (*hash22)(uint32_t n22, char* callsign);
|
||||
/// Called when a callsign is looked up by its 12 bit hash code
|
||||
void (*hash12)(uint32_t n12, char* callsign);
|
||||
/// Called when a callsign should hashed and stored (both by its 22 and 12 bit hash code)
|
||||
void (*save_hash)(const char* callsign);
|
||||
} unpack_hash_interface_t;
|
||||
|
||||
// message should have at least 35 bytes allocated (34 characters + zero terminator)
|
||||
int unpack77(const uint8_t* a77, char* message);
|
||||
/// Unpack a 77 bit message payload into three fields (typically call_to, call_de and grid/report/other)
|
||||
/// @param[in] a77 message payload in binary form (77 bits, MSB first)
|
||||
/// @param[out] field1 at least 14 bytes (typically call_to)
|
||||
/// @param[out] field2 at least 14 bytes (typically call_de)
|
||||
/// @param[out] field3 at least 7 bytes (typically grid/report/other)
|
||||
/// @param[in] hash_if hashing interface (can be NULL)
|
||||
int unpack77_fields(const uint8_t* a77, char* field1, char* field2, char* field3, const unpack_hash_interface_t* hash_if);
|
||||
|
||||
/// Unpack a 77 bit message payload into text message
|
||||
/// @param[in] a77 message payload in binary form (77 bits, MSB first)
|
||||
/// @param[out] message should have at least 35 bytes allocated (34 characters + zero terminator)
|
||||
/// @param[in] hash_if hashing interface (can be NULL)
|
||||
int unpack77(const uint8_t* a77, char* message, const unpack_hash_interface_t* hash_if);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue