[feat](trx-rs): add FT2 decoder protocol support
Implement a distinct FT2 protocol path in the decoder stack and align\nits timing with the confirmed FT2 framing used by Decodium.\n\nCo-authored-by: OpenAI Codex <codex@openai.com> Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
This commit is contained in:
Vendored
+2
-2
@@ -54,8 +54,8 @@ static void waterfall_free(ftx_waterfall_t* me)
|
|||||||
|
|
||||||
void monitor_init(monitor_t* me, const monitor_config_t* cfg)
|
void monitor_init(monitor_t* me, const monitor_config_t* cfg)
|
||||||
{
|
{
|
||||||
float slot_time = (cfg->protocol == FTX_PROTOCOL_FT4) ? FT4_SLOT_TIME : FT8_SLOT_TIME;
|
float slot_time = ftx_protocol_slot_time(cfg->protocol);
|
||||||
float symbol_period = (cfg->protocol == FTX_PROTOCOL_FT4) ? FT4_SYMBOL_PERIOD : FT8_SYMBOL_PERIOD;
|
float symbol_period = ftx_protocol_symbol_period(cfg->protocol);
|
||||||
// Compute DSP parameters that depend on the sample rate
|
// Compute DSP parameters that depend on the sample rate
|
||||||
me->block_size = (int)(cfg->sample_rate * symbol_period); // samples corresponding to one FSK symbol
|
me->block_size = (int)(cfg->sample_rate * symbol_period); // samples corresponding to one FSK symbol
|
||||||
me->subblock_size = me->block_size / cfg->time_osr;
|
me->subblock_size = me->block_size / cfg->time_osr;
|
||||||
|
|||||||
Vendored
+32
-1
@@ -14,6 +14,9 @@ extern "C"
|
|||||||
#define FT4_SYMBOL_PERIOD (0.048f) ///< FT4 symbol duration, defines tone deviation in Hz and symbol rate
|
#define FT4_SYMBOL_PERIOD (0.048f) ///< FT4 symbol duration, defines tone deviation in Hz and symbol rate
|
||||||
#define FT4_SLOT_TIME (7.5f) ///< FT4 slot period
|
#define FT4_SLOT_TIME (7.5f) ///< FT4 slot period
|
||||||
|
|
||||||
|
#define FT2_SYMBOL_PERIOD (0.024f) ///< FT2 symbol duration (288 samples @ 12 kHz)
|
||||||
|
#define FT2_SLOT_TIME (3.75f) ///< FT2 slot period
|
||||||
|
|
||||||
// Define FT8 symbol counts
|
// Define FT8 symbol counts
|
||||||
// FT8 message structure:
|
// FT8 message structure:
|
||||||
// S D1 S D2 S
|
// S D1 S D2 S
|
||||||
@@ -38,6 +41,14 @@ extern "C"
|
|||||||
#define FT4_NUM_SYNC (4) ///< Number of sync groups
|
#define FT4_NUM_SYNC (4) ///< Number of sync groups
|
||||||
#define FT4_SYNC_OFFSET (33) ///< Offset between sync groups
|
#define FT4_SYNC_OFFSET (33) ///< Offset between sync groups
|
||||||
|
|
||||||
|
// FT2 reuses the FT4 channel structure with a shorter slot and symbol period.
|
||||||
|
#define FT2_ND FT4_ND
|
||||||
|
#define FT2_NR FT4_NR
|
||||||
|
#define FT2_NN FT4_NN
|
||||||
|
#define FT2_LENGTH_SYNC FT4_LENGTH_SYNC
|
||||||
|
#define FT2_NUM_SYNC FT4_NUM_SYNC
|
||||||
|
#define FT2_SYNC_OFFSET FT4_SYNC_OFFSET
|
||||||
|
|
||||||
// Define LDPC parameters
|
// Define LDPC parameters
|
||||||
#define FTX_LDPC_N (174) ///< Number of bits in the encoded message (payload with LDPC checksum bits)
|
#define FTX_LDPC_N (174) ///< Number of bits in the encoded message (payload with LDPC checksum bits)
|
||||||
#define FTX_LDPC_K (91) ///< Number of payload bits (including CRC)
|
#define FTX_LDPC_K (91) ///< Number of payload bits (including CRC)
|
||||||
@@ -52,9 +63,29 @@ extern "C"
|
|||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
FTX_PROTOCOL_FT4,
|
FTX_PROTOCOL_FT4,
|
||||||
FTX_PROTOCOL_FT8
|
FTX_PROTOCOL_FT8,
|
||||||
|
FTX_PROTOCOL_FT2
|
||||||
} ftx_protocol_t;
|
} ftx_protocol_t;
|
||||||
|
|
||||||
|
static inline float ftx_protocol_symbol_period(ftx_protocol_t protocol)
|
||||||
|
{
|
||||||
|
return (protocol == FTX_PROTOCOL_FT8)
|
||||||
|
? FT8_SYMBOL_PERIOD
|
||||||
|
: ((protocol == FTX_PROTOCOL_FT2) ? FT2_SYMBOL_PERIOD : FT4_SYMBOL_PERIOD);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline float ftx_protocol_slot_time(ftx_protocol_t protocol)
|
||||||
|
{
|
||||||
|
return (protocol == FTX_PROTOCOL_FT8)
|
||||||
|
? FT8_SLOT_TIME
|
||||||
|
: ((protocol == FTX_PROTOCOL_FT2) ? FT2_SLOT_TIME : FT4_SLOT_TIME);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int ftx_protocol_uses_ft4_layout(ftx_protocol_t protocol)
|
||||||
|
{
|
||||||
|
return (protocol == FTX_PROTOCOL_FT4) || (protocol == FTX_PROTOCOL_FT2);
|
||||||
|
}
|
||||||
|
|
||||||
/// Costas 7x7 tone pattern for synchronization
|
/// Costas 7x7 tone pattern for synchronization
|
||||||
extern const uint8_t kFT8_Costas_pattern[7];
|
extern const uint8_t kFT8_Costas_pattern[7];
|
||||||
extern const uint8_t kFT4_Costas_pattern[4][4];
|
extern const uint8_t kFT4_Costas_pattern[4][4];
|
||||||
|
|||||||
Vendored
+6
-5
@@ -189,8 +189,9 @@ static int ft4_sync_score(const ftx_waterfall_t* wf, const ftx_candidate_t* cand
|
|||||||
|
|
||||||
int ftx_find_candidates(const ftx_waterfall_t* wf, int num_candidates, ftx_candidate_t heap[], int min_score)
|
int ftx_find_candidates(const ftx_waterfall_t* wf, int num_candidates, ftx_candidate_t heap[], int min_score)
|
||||||
{
|
{
|
||||||
int (*sync_fun)(const ftx_waterfall_t*, const ftx_candidate_t*) = (wf->protocol == FTX_PROTOCOL_FT4) ? ft4_sync_score : ft8_sync_score;
|
int (*sync_fun)(const ftx_waterfall_t*, const ftx_candidate_t*) =
|
||||||
int num_tones = (wf->protocol == FTX_PROTOCOL_FT4) ? 4 : 8;
|
ftx_protocol_uses_ft4_layout(wf->protocol) ? ft4_sync_score : ft8_sync_score;
|
||||||
|
int num_tones = ftx_protocol_uses_ft4_layout(wf->protocol) ? 4 : 8;
|
||||||
|
|
||||||
int heap_size = 0;
|
int heap_size = 0;
|
||||||
ftx_candidate_t candidate;
|
ftx_candidate_t candidate;
|
||||||
@@ -327,7 +328,7 @@ static void ftx_normalize_logl(float* log174)
|
|||||||
bool ftx_decode_candidate(const ftx_waterfall_t* wf, const ftx_candidate_t* cand, int max_iterations, ftx_message_t* message, ftx_decode_status_t* status)
|
bool ftx_decode_candidate(const ftx_waterfall_t* wf, const ftx_candidate_t* cand, int max_iterations, ftx_message_t* message, ftx_decode_status_t* status)
|
||||||
{
|
{
|
||||||
float log174[FTX_LDPC_N]; // message bits encoded as likelihood
|
float log174[FTX_LDPC_N]; // message bits encoded as likelihood
|
||||||
if (wf->protocol == FTX_PROTOCOL_FT4)
|
if (ftx_protocol_uses_ft4_layout(wf->protocol))
|
||||||
{
|
{
|
||||||
ft4_extract_likelihood(wf, cand, log174);
|
ft4_extract_likelihood(wf, cand, log174);
|
||||||
}
|
}
|
||||||
@@ -366,7 +367,7 @@ bool ftx_decode_candidate(const ftx_waterfall_t* wf, const ftx_candidate_t* cand
|
|||||||
// Reuse CRC value as a hash for the message (TODO: 14 bits only, should perhaps use full 16 or 32 bits?)
|
// Reuse CRC value as a hash for the message (TODO: 14 bits only, should perhaps use full 16 or 32 bits?)
|
||||||
message->hash = status->crc_calculated;
|
message->hash = status->crc_calculated;
|
||||||
|
|
||||||
if (wf->protocol == FTX_PROTOCOL_FT4)
|
if (ftx_protocol_uses_ft4_layout(wf->protocol))
|
||||||
{
|
{
|
||||||
// '[..] for FT4 only, in order to avoid transmitting a long string of zeros when sending CQ messages,
|
// '[..] for FT4 only, in order to avoid transmitting a long string of zeros when sending CQ messages,
|
||||||
// the assembled 77-bit message is bitwise exclusive-OR’ed with [a] pseudorandom sequence before computing the CRC and FEC parity bits'
|
// the assembled 77-bit message is bitwise exclusive-OR’ed with [a] pseudorandom sequence before computing the CRC and FEC parity bits'
|
||||||
@@ -589,4 +590,4 @@ static void pack_bits(const uint8_t bit_array[], int num_bits, uint8_t packed[])
|
|||||||
++byte_idx;
|
++byte_idx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+5
@@ -193,3 +193,8 @@ void ft4_encode(const uint8_t* payload, uint8_t* tones)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ft2_encode(const uint8_t* payload, uint8_t* tones)
|
||||||
|
{
|
||||||
|
ft4_encode(payload, tones);
|
||||||
|
}
|
||||||
|
|||||||
Vendored
+6
@@ -34,6 +34,12 @@ void ft8_encode(const uint8_t* payload, uint8_t* tones);
|
|||||||
/// @param[out] tones - array of FT4_NN (105) bytes to store the generated tones (encoded as 0..3)
|
/// @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);
|
void ft4_encode(const uint8_t* payload, uint8_t* tones);
|
||||||
|
|
||||||
|
/// Generate FT2 tone sequence from payload data.
|
||||||
|
/// FT2 uses the FT4 framing with a doubled symbol rate.
|
||||||
|
/// @param[in] payload - 10 byte array consisting of 77 bit payload
|
||||||
|
/// @param[out] tones - array of FT2_NN (105) bytes to store the generated tones (encoded as 0..3)
|
||||||
|
void ft2_encode(const uint8_t* payload, uint8_t* tones);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -31,12 +31,21 @@ fn main() {
|
|||||||
|
|
||||||
println!("cargo:rerun-if-changed=src/ft8_wrapper.c");
|
println!("cargo:rerun-if-changed=src/ft8_wrapper.c");
|
||||||
println!("cargo:rerun-if-changed={base}/common/monitor.c");
|
println!("cargo:rerun-if-changed={base}/common/monitor.c");
|
||||||
|
println!("cargo:rerun-if-changed={base}/common/monitor.h");
|
||||||
println!("cargo:rerun-if-changed={base}/fft/kiss_fft.c");
|
println!("cargo:rerun-if-changed={base}/fft/kiss_fft.c");
|
||||||
|
println!("cargo:rerun-if-changed={base}/fft/kiss_fft.h");
|
||||||
println!("cargo:rerun-if-changed={base}/fft/kiss_fftr.c");
|
println!("cargo:rerun-if-changed={base}/fft/kiss_fftr.c");
|
||||||
|
println!("cargo:rerun-if-changed={base}/fft/kiss_fftr.h");
|
||||||
println!("cargo:rerun-if-changed={base}/ft8/constants.c");
|
println!("cargo:rerun-if-changed={base}/ft8/constants.c");
|
||||||
|
println!("cargo:rerun-if-changed={base}/ft8/constants.h");
|
||||||
println!("cargo:rerun-if-changed={base}/ft8/crc.c");
|
println!("cargo:rerun-if-changed={base}/ft8/crc.c");
|
||||||
|
println!("cargo:rerun-if-changed={base}/ft8/crc.h");
|
||||||
println!("cargo:rerun-if-changed={base}/ft8/decode.c");
|
println!("cargo:rerun-if-changed={base}/ft8/decode.c");
|
||||||
|
println!("cargo:rerun-if-changed={base}/ft8/decode.h");
|
||||||
println!("cargo:rerun-if-changed={base}/ft8/ldpc.c");
|
println!("cargo:rerun-if-changed={base}/ft8/ldpc.c");
|
||||||
|
println!("cargo:rerun-if-changed={base}/ft8/ldpc.h");
|
||||||
println!("cargo:rerun-if-changed={base}/ft8/message.c");
|
println!("cargo:rerun-if-changed={base}/ft8/message.c");
|
||||||
|
println!("cargo:rerun-if-changed={base}/ft8/message.h");
|
||||||
println!("cargo:rerun-if-changed={base}/ft8/text.c");
|
println!("cargo:rerun-if-changed={base}/ft8/text.c");
|
||||||
|
println!("cargo:rerun-if-changed={base}/ft8/text.h");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,13 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
TRX_FTX_PROTOCOL_FT4 = 0,
|
||||||
|
TRX_FTX_PROTOCOL_FT8 = 1,
|
||||||
|
TRX_FTX_PROTOCOL_FT2 = 2,
|
||||||
|
};
|
||||||
|
|
||||||
// Callsign hash table (from demo/decode_ft8.c)
|
// Callsign hash table (from demo/decode_ft8.c)
|
||||||
#define CALLSIGN_HASHTABLE_SIZE 256
|
#define CALLSIGN_HASHTABLE_SIZE 256
|
||||||
|
|
||||||
@@ -120,7 +127,19 @@ ft8_decoder_t* ft8_decoder_create(int sample_rate, float f_min, float f_max, int
|
|||||||
dec->cfg.sample_rate = sample_rate;
|
dec->cfg.sample_rate = sample_rate;
|
||||||
dec->cfg.time_osr = time_osr;
|
dec->cfg.time_osr = time_osr;
|
||||||
dec->cfg.freq_osr = freq_osr;
|
dec->cfg.freq_osr = freq_osr;
|
||||||
dec->cfg.protocol = (protocol == 0) ? FTX_PROTOCOL_FT4 : FTX_PROTOCOL_FT8;
|
switch (protocol)
|
||||||
|
{
|
||||||
|
case TRX_FTX_PROTOCOL_FT4:
|
||||||
|
dec->cfg.protocol = FTX_PROTOCOL_FT4;
|
||||||
|
break;
|
||||||
|
case TRX_FTX_PROTOCOL_FT2:
|
||||||
|
dec->cfg.protocol = FTX_PROTOCOL_FT2;
|
||||||
|
break;
|
||||||
|
case TRX_FTX_PROTOCOL_FT8:
|
||||||
|
default:
|
||||||
|
dec->cfg.protocol = FTX_PROTOCOL_FT8;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
hashtable_init();
|
hashtable_init();
|
||||||
monitor_init(&dec->mon, &dec->cfg);
|
monitor_init(&dec->mon, &dec->cfg);
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ const TIME_OSR: i32 = 2;
|
|||||||
const FREQ_OSR: i32 = 2;
|
const FREQ_OSR: i32 = 2;
|
||||||
|
|
||||||
const FTX_MAX_MESSAGE_LENGTH: usize = 35;
|
const FTX_MAX_MESSAGE_LENGTH: usize = 35;
|
||||||
|
const PROTOCOL_FT4: c_int = 0;
|
||||||
|
const PROTOCOL_FT8: c_int = 1;
|
||||||
|
const PROTOCOL_FT2: c_int = 2;
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
@@ -63,30 +66,18 @@ unsafe impl Send for Ft8Decoder {}
|
|||||||
|
|
||||||
impl Ft8Decoder {
|
impl Ft8Decoder {
|
||||||
pub fn new(sample_rate: u32) -> Result<Self, String> {
|
pub fn new(sample_rate: u32) -> Result<Self, String> {
|
||||||
unsafe {
|
Self::new_with_protocol(sample_rate, PROTOCOL_FT8, "FT8")
|
||||||
let ptr = ft8_decoder_create(
|
|
||||||
sample_rate as c_int,
|
|
||||||
F_MIN_HZ,
|
|
||||||
F_MAX_HZ,
|
|
||||||
TIME_OSR as c_int,
|
|
||||||
FREQ_OSR as c_int,
|
|
||||||
1, // FTX_PROTOCOL_FT8
|
|
||||||
);
|
|
||||||
let inner = NonNull::new(ptr).ok_or_else(|| "ft8_decoder_create failed".to_string())?;
|
|
||||||
let block_size = ft8_decoder_block_size(inner.as_ptr()) as usize;
|
|
||||||
if block_size == 0 {
|
|
||||||
ft8_decoder_free(inner.as_ptr());
|
|
||||||
return Err("invalid FT8 block size".to_string());
|
|
||||||
}
|
|
||||||
Ok(Self {
|
|
||||||
inner,
|
|
||||||
block_size,
|
|
||||||
sample_rate,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_ft4(sample_rate: u32) -> Result<Self, String> {
|
pub fn new_ft4(sample_rate: u32) -> Result<Self, String> {
|
||||||
|
Self::new_with_protocol(sample_rate, PROTOCOL_FT4, "FT4")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_ft2(sample_rate: u32) -> Result<Self, String> {
|
||||||
|
Self::new_with_protocol(sample_rate, PROTOCOL_FT2, "FT2")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_with_protocol(sample_rate: u32, protocol: c_int, label: &str) -> Result<Self, String> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr = ft8_decoder_create(
|
let ptr = ft8_decoder_create(
|
||||||
sample_rate as c_int,
|
sample_rate as c_int,
|
||||||
@@ -94,13 +85,13 @@ impl Ft8Decoder {
|
|||||||
F_MAX_HZ,
|
F_MAX_HZ,
|
||||||
TIME_OSR as c_int,
|
TIME_OSR as c_int,
|
||||||
FREQ_OSR as c_int,
|
FREQ_OSR as c_int,
|
||||||
0, // FTX_PROTOCOL_FT4
|
protocol,
|
||||||
);
|
);
|
||||||
let inner = NonNull::new(ptr).ok_or_else(|| "ft8_decoder_create failed".to_string())?;
|
let inner = NonNull::new(ptr).ok_or_else(|| "ft8_decoder_create failed".to_string())?;
|
||||||
let block_size = ft8_decoder_block_size(inner.as_ptr()) as usize;
|
let block_size = ft8_decoder_block_size(inner.as_ptr()) as usize;
|
||||||
if block_size == 0 {
|
if block_size == 0 {
|
||||||
ft8_decoder_free(inner.as_ptr());
|
ft8_decoder_free(inner.as_ptr());
|
||||||
return Err("invalid FT4 block size".to_string());
|
return Err(format!("invalid {label} block size"));
|
||||||
}
|
}
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
inner,
|
inner,
|
||||||
@@ -110,11 +101,6 @@ impl Ft8Decoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_ft2(sample_rate: u32) -> Result<Self, String> {
|
|
||||||
// Wired to FT4 protocol pending a dedicated FT2 implementation.
|
|
||||||
Self::new_ft4(sample_rate)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn block_size(&self) -> usize {
|
pub fn block_size(&self) -> usize {
|
||||||
self.block_size
|
self.block_size
|
||||||
}
|
}
|
||||||
@@ -179,3 +165,18 @@ impl Drop for Ft8Decoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Ft8Decoder;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn ft2_uses_distinct_block_size() {
|
||||||
|
let ft4 = Ft8Decoder::new_ft4(12_000).expect("ft4 decoder");
|
||||||
|
let ft2 = Ft8Decoder::new_ft2(12_000).expect("ft2 decoder");
|
||||||
|
|
||||||
|
assert!(ft2.block_size() < ft4.block_size());
|
||||||
|
assert_eq!(ft4.block_size(), 576);
|
||||||
|
assert_eq!(ft2.block_size(), 288);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1853,7 +1853,7 @@ pub async fn run_ft4_decoder(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run the FT2 decoder task. Mirrors FT4 but uses FT2 protocol (7.5s slots for now).
|
/// Run the FT2 decoder task. Mirrors FT4 but uses FT2 protocol timing.
|
||||||
pub async fn run_ft2_decoder(
|
pub async fn run_ft2_decoder(
|
||||||
sample_rate: u32,
|
sample_rate: u32,
|
||||||
channels: u16,
|
channels: u16,
|
||||||
@@ -1902,12 +1902,11 @@ pub async fn run_ft2_decoder(
|
|||||||
recv = pcm_rx.recv() => {
|
recv = pcm_rx.recv() => {
|
||||||
match recv {
|
match recv {
|
||||||
Ok(frame) => {
|
Ok(frame) => {
|
||||||
let now = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
|
let now_ms = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
|
||||||
Ok(dur) => dur.as_secs() as i64,
|
Ok(dur) => dur.as_millis() as i64,
|
||||||
Err(_) => 0,
|
Err(_) => 0,
|
||||||
};
|
};
|
||||||
// FT2 slot period is 7.5s (same as FT4 for now); use now * 2 / 15
|
let slot = now_ms / 3_750;
|
||||||
let slot = now * 2 / 15;
|
|
||||||
if slot != last_slot {
|
if slot != last_slot {
|
||||||
last_slot = slot;
|
last_slot = slot;
|
||||||
decoder.reset();
|
decoder.reset();
|
||||||
@@ -2449,13 +2448,12 @@ async fn run_background_ft2_decoder(
|
|||||||
loop {
|
loop {
|
||||||
match pcm_rx.recv().await {
|
match pcm_rx.recv().await {
|
||||||
Ok(frame) => {
|
Ok(frame) => {
|
||||||
let now = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)
|
let now_ms = match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH)
|
||||||
{
|
{
|
||||||
Ok(dur) => dur.as_secs() as i64,
|
Ok(dur) => dur.as_millis() as i64,
|
||||||
Err(_) => 0,
|
Err(_) => 0,
|
||||||
};
|
};
|
||||||
// FT2 slot period is 7.5s (same as FT4 for now); use now * 2 / 15
|
let slot = now_ms / 3_750;
|
||||||
let slot = now * 2 / 15;
|
|
||||||
if slot != last_slot {
|
if slot != last_slot {
|
||||||
last_slot = slot;
|
last_slot = slot;
|
||||||
decoder.reset();
|
decoder.reset();
|
||||||
|
|||||||
Reference in New Issue
Block a user