use std::default::Default;
use arraydeque::ArrayDeque;
#[cfg(not(test))]
use log::debug;
#[cfg(test)]
use std::println as debug;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum SquelchState {
NoCarrier,
DroppedCarrier,
Reading,
Ready(bool, SquelchOut),
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct SquelchOut {
pub samples: [f32; CodeAndPowerSquelch::OUTPUT_LENGTH],
pub symbol_counter: u64,
pub power: f32,
}
#[derive(Clone, Debug)]
pub struct CodeAndPowerSquelch {
max_errors: u32,
power_open: f32,
power_close: f32,
correlator: CodeCorrelator,
power_track: PowerTracker,
sample_history: ArrayDeque<f32, 64, arraydeque::Wrapping>,
power_history: ArrayDeque<bool, 32, arraydeque::Wrapping>,
symbol_counter: u64,
sample_clock: Option<u8>,
sync_lock: bool,
}
impl CodeAndPowerSquelch {
pub const INPUT_LENGTH: usize = 2;
pub const OUTPUT_LENGTH: usize = 16;
pub fn new(
sync_to: u32,
max_errors: u32,
power_open: f32,
power_close: f32,
power_track_bandwidth: f32,
) -> Self {
let mut out = Self {
max_errors,
power_open,
power_close: f32::min(power_close, power_open),
correlator: CodeCorrelator::new(sync_to),
power_track: PowerTracker::new(power_track_bandwidth),
sample_history: ArrayDeque::default(),
power_history: ArrayDeque::default(),
symbol_counter: 0,
sample_clock: None,
sync_lock: false,
};
out.reset();
out
}
pub fn input(&mut self, input: &[f32]) -> SquelchState {
assert_eq!(Self::INPUT_LENGTH, input.len());
self.sample_history.push_back(input[0]);
self.sample_history.push_back(input[1]);
let err = self.correlator.search(input[1]);
let pwr = self.power_track.track(input[1]);
self.power_history.push_back(pwr >= self.power_close);
self.symbol_counter += 1;
if !self.sample_history.is_full() {
return SquelchState::NoCarrier;
}
let mut adjusted = false;
if !self.sync_lock && err <= self.max_errors && pwr >= self.power_open {
self.sample_clock = match self.sample_clock {
None => {
debug!(
"squelch: acquired byte sync: {} errors, power {:.3}",
err, pwr
);
adjusted = true;
Some(0)
}
Some(0) => {
Some(0)
}
Some(n) => {
debug!(
"squelch: adjust byte sync by +{} symbols with {} errors, power {:.3}",
8 - n,
err,
pwr
);
adjusted = true;
Some(0)
}
};
} else if self.is_sync() && !self.power_history.front().expect("bad power history") {
debug!(
"squelch: lost sync: symbol power {:.3} below threshold",
pwr
);
self.end();
return SquelchState::DroppedCarrier;
}
match self.sample_clock {
None => SquelchState::NoCarrier,
Some(0) => {
self.sample_clock = Some(1);
let mut out = SquelchOut::default();
for (o, h) in out
.samples
.iter_mut()
.zip(self.sample_history.iter().take(Self::OUTPUT_LENGTH))
{
*o = *h;
}
out.symbol_counter = self.symbol_counter;
out.power = pwr;
SquelchState::Ready(adjusted, out)
}
Some(ref mut clk) => {
*clk = (*clk + 1) % 8;
SquelchState::Reading
}
}
}
pub fn lock(&mut self, lock: bool) {
self.sync_lock = lock;
}
pub fn reset(&mut self) {
self.end();
self.correlator.reset();
self.sample_history.clear();
self.power_track.reset();
self.power_history.clear();
self.symbol_counter = 0;
}
pub fn end(&mut self) {
self.sync_lock = false;
self.sample_clock = None;
}
pub fn symbol_count(&self) -> u64 {
self.symbol_counter
}
pub fn power(&self) -> f32 {
self.power_track.power
}
pub fn is_sync(&self) -> bool {
self.sample_clock.is_some()
}
#[allow(dead_code)]
pub fn is_locked(&self) -> bool {
self.sync_lock
}
}
impl Default for SquelchState {
fn default() -> SquelchState {
SquelchState::Reading
}
}
#[derive(Clone, Debug)]
pub struct CodeCorrelator {
sync_to: u32,
data: u32,
}
impl CodeCorrelator {
pub fn new(sync_to: u32) -> Self {
Self { sync_to, data: 0 }
}
pub fn search(&mut self, sym: f32) -> u32 {
let bit = (sym >= 0.0f32) as u32;
self.data = self.data >> 1;
self.data |= bit << 31;
num_bit_errors(self.sync_to, self.data)
}
pub fn reset(&mut self) {
self.data = 0;
}
}
#[inline]
fn num_bit_errors(sync_to: u32, data: u32) -> u32 {
let err = sync_to ^ data;
err.count_ones()
}
#[derive(Clone, Debug)]
struct PowerTracker {
bandwidth: f32,
power: f32,
}
impl PowerTracker {
pub fn new(bandwidth: f32) -> Self {
Self {
bandwidth: f32::clamp(bandwidth, 0.0f32, 1.0f32),
power: 0.0f32,
}
}
pub fn reset(&mut self) {
self.power = 0.0f32;
}
#[inline]
pub fn track(&mut self, sym: f32) -> f32 {
let pwr = sym * sym;
self.power += (pwr - self.power) * self.bandwidth;
self.power = self.power.max(0.0f32);
self.power
}
}
#[cfg(test)]
mod tests {
use super::super::waveform;
use super::*;
use assert_approx_eq::assert_approx_eq;
#[test]
fn test_num_bit_errors() {
let sync_to = waveform::PREAMBLE_SYNC_WORD;
let one_error = sync_to | 0x40u32;
let another_error = 0xa9ababab;
assert_eq!(0, num_bit_errors(sync_to, sync_to));
assert_eq!(1, num_bit_errors(sync_to, one_error));
assert_eq!(1, num_bit_errors(sync_to, another_error));
}
#[test]
fn test_codecorr() {
const BYTES: &[u8] = &[0xAB, 0xAB, 0xAB, 0xAB, 0x21];
let mut syms = waveform::bytes_to_symbols(BYTES);
let mut uut = CodeCorrelator::new(waveform::PREAMBLE_SYNC_WORD);
let out: Vec<u32> = syms.iter().map(|s| uut.search(*s)).collect();
for (i, err) in out.iter().enumerate() {
match i {
31 => assert_eq!(*err, 0),
_ => assert!(*err > 0),
}
}
syms[19] = -syms[19];
let out: Vec<u32> = syms.iter().map(|s| uut.search(*s)).collect();
for (i, err) in out.iter().enumerate() {
match i {
31 => assert_eq!(*err, 1),
_ => assert!(*err >= 1),
}
}
}
#[test]
fn test_power_tracker() {
let mut ptrack = PowerTracker::new(1.0f32);
ptrack.track(1.0f32);
ptrack.bandwidth = 0.5f32;
assert_approx_eq!(0.62500f32, ptrack.track(-0.5f32));
ptrack.power = 1.0f32;
for _i in 0..16 {
ptrack.track(1.0f32);
}
assert_approx_eq!(1.0f32, ptrack.power);
assert_approx_eq!(1.0f32, ptrack.track(1.0f32));
}
#[test]
fn test_simple_sync() {
const BYTES: &[u8] = &[0xAB, 0xAB, 0xAB, 0xAB, 0x21];
let insamp = waveform::bytes_to_samples(BYTES, 2);
let mut squelch = CodeAndPowerSquelch::new(waveform::PREAMBLE_SYNC_WORD, 0, 0.0, 0.0, 0.1);
assert!(!squelch.is_sync());
let mut align_idx = 0; for (chunk, inp) in insamp.chunks(2).enumerate() {
match squelch.input(inp) {
SquelchState::Ready(resync, out) => {
assert!(!resync || chunk == 31);
if chunk == 31 {
assert_eq!(squelch.correlator.data, 0xabababab);
}
assert_eq!(&out.samples, &insamp[align_idx..align_idx + 16]);
align_idx += 16;
assert_eq!(out.symbol_counter - 1, chunk as u64);
}
_ => {}
}
}
assert!(squelch.is_sync());
assert_eq!(align_idx, 32);
squelch.end();
assert!(!squelch.is_sync());
}
#[test]
fn test_sync_with_error() {
const BYTES: &[u8] = &[0xF0, 0x0B, 0xA9, 0xAB, 0xAB, 0xAB, 0x21];
let insamp = waveform::bytes_to_samples(BYTES, 2);
let mut squelch = CodeAndPowerSquelch::new(waveform::PREAMBLE_SYNC_WORD, 1, 0.0, 0.0, 0.1);
assert!(!squelch.is_sync());
let mut align_idx = 32; for (chunk, inp) in insamp.chunks(2).enumerate() {
match squelch.input(inp) {
SquelchState::Ready(resync, out) => {
assert!(!resync || 47 == chunk);
assert_eq!(&out.samples, &insamp[align_idx..align_idx + 16]);
align_idx += 16;
}
_ => {}
}
}
assert!(squelch.is_sync());
}
#[test]
fn test_sync_with_lots_of_errors() {
const BYTES: &[u8] = &[0xAB, 0x0B, 0xA9, 0xAB, 0xAB, 0xAA, 0x21];
let insamp = waveform::bytes_to_samples(BYTES, 2);
let mut found_early = false;
let mut found_later = false;
let mut squelch = CodeAndPowerSquelch::new(waveform::PREAMBLE_SYNC_WORD, 3, 0.8, 0.1, 0.1);
assert!(!squelch.is_sync());
let mut align_idx = 32; for (chunk, inp) in insamp.chunks(2).enumerate() {
match squelch.input(inp) {
SquelchState::Ready(_, out) => {
if chunk == 47 {
assert_eq!(&out.samples, &insamp[align_idx..align_idx + 16]);
align_idx += 16;
found_later = true;
} else {
found_early = true;
}
}
_ => {}
}
}
assert!(squelch.is_sync());
assert!(found_later);
assert!(found_early);
}
#[test]
fn test_power_detection() {
const BYTES: &[u8] = &[0xF0, 0x0B, 0xA9, 0xAB, 0xAB, 0xAB, 0x21];
let insamp = waveform::bytes_to_samples(BYTES, 2);
let mut squelch = CodeAndPowerSquelch::new(waveform::PREAMBLE_SYNC_WORD, 1, 0.9, 0.5, 0.1);
for inp in insamp.chunks(2) {
let _ = squelch.input(inp);
}
assert!(squelch.is_sync());
let mut found_reading = false;
let mut found_drop = false;
let mut found_none = false;
for (samps, _itr) in std::iter::repeat([0.0f32; 2]).zip(0..40) {
match squelch.input(&samps) {
SquelchState::Reading => found_reading = true,
SquelchState::DroppedCarrier => found_drop = true,
SquelchState::NoCarrier => found_none = true,
_ => {}
}
}
assert!(found_reading);
assert!(found_drop);
assert!(found_none);
assert!(!squelch.is_sync());
}
}