#![no_std]
#[cfg(feature = "defmt-03")]
use defmt;
#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
#[derive(Debug, PartialEq)]
pub enum Error {
InvalidPower,
InvalidGrid,
InvalidCallsign,
}
struct ShiftRegister {
value: u32,
and_const: u32,
}
impl ShiftRegister {
fn new(and_const: u32) -> Self {
Self {
value: 0,
and_const,
}
}
fn shift(&mut self, bit: u32) -> u8 {
self.value = (self.value << 1) | bit;
let ones = (self.value & self.and_const).count_ones();
let parity = ones & 0x01; parity as u8
}
}
struct Buffer {
buffer: [u8; 162],
index: usize,
}
impl Buffer {
fn new() -> Self {
Self {
buffer: [0u8; 162],
index: 0,
}
}
fn push(&mut self, bit: u8) {
self.buffer[self.index] = bit;
self.index += 1;
}
fn interleave(&mut self) {
let mut interleaved = [0u8; 162];
let mut p = 0;
for i in 0u8..255 {
let j = i.reverse_bits() as usize;
if j < 162 {
interleaved[j] = self.buffer[p];
p += 1;
if p == 162 {
break;
}
}
}
self.buffer = interleaved;
}
fn sync(&mut self) {
const SYNC: [u8; 162] = [
1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1,
0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0,
0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0,
0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0,
0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0,
1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1,
0, 0, 0, 1, 1, 0, 0, 0,
];
for i in 0..162 {
self.buffer[i] = SYNC[i] + 2 * self.buffer[i];
}
}
fn release(self) -> [u8; 162] {
self.buffer
}
}
fn encode_callsign_char(c: u8) -> Result<u32, Error> {
let c = c as char;
if c == ' ' {
Ok(36)
} else {
match c.to_digit(36) {
Some(d) => Ok(d),
None => return Err(Error::InvalidCallsign),
}
}
}
fn encode_callsign(callsign: &str) -> Result<u32, Error> {
let callsign = callsign.as_bytes();
let length = callsign.len();
if !(3..=6).contains(&length) {
return Err(Error::InvalidCallsign);
}
let start = match length {
0..=4 => {
if (callsign[2] as char).is_digit(10) {
0
} else {
1
}
}
5 => {
if (callsign[1] as char).is_digit(10) {
1
} else {
0
}
}
_ => 0,
};
let stop = start + length;
let mut padded = [b' '; 6];
padded[start..stop].copy_from_slice(&callsign);
let callsign = padded;
if !(callsign[2] as char).is_digit(10) {
return Err(Error::InvalidCallsign);
}
let scalars = [0u32, 36, 10, 27, 27, 27];
let subtracts = [0u32, 0, 0, 10, 10, 10];
let mut n = 0;
for (index, &c) in callsign.iter().enumerate() {
n = n * scalars[index] + encode_callsign_char(c)? as u32
- subtracts[index];
}
Ok(n)
}
fn encode_grid_char(c: u8) -> Result<u8, Error> {
return match (c as char).to_ascii_uppercase() {
'A'..='R' => Ok((c as u8) - b'A'),
'0'..='9' => Ok((c as u8) - b'0'),
_ => Err(Error::InvalidGrid),
};
}
fn encode_grid(grid: &str) -> Result<u16, Error> {
let grid = grid.as_bytes();
if grid.len() != 4 {
return Err(Error::InvalidGrid);
}
let first = encode_grid_char(grid[0])? as u16;
let second = encode_grid_char(grid[1])? as u16;
let third = encode_grid_char(grid[2])? as u16;
let fourth = encode_grid_char(grid[3])? as u16;
let result = (179 - 10 * first - third) * 180 + 10 * second + fourth;
Ok(result)
}
fn encode_power(power: u8) -> Result<u8, Error> {
let rem = power % 10;
if (0..=60).contains(&power) && (rem == 0 || rem == 3 || rem == 7) {
Ok(power + 64)
} else {
Err(Error::InvalidPower)
}
}
pub fn encode(
callsign: &str,
grid: &str,
power: u8,
) -> Result<[u8; 162], Error> {
let callsign = encode_callsign(callsign)?;
let grid = encode_grid(grid)?;
let power = encode_power(power)?;
let mut reg0 = ShiftRegister::new(0xF2D05351);
let mut reg1 = ShiftRegister::new(0xE4613C47);
let mut buffer = Buffer::new();
for i in (0..28).rev() {
let bit = (callsign >> i) & 0x01;
buffer.push(reg0.shift(bit));
buffer.push(reg1.shift(bit));
}
for i in (0..15).rev() {
let bit = (grid as u32 >> i) & 0x01;
buffer.push(reg0.shift(bit));
buffer.push(reg1.shift(bit));
}
for i in (0..7).rev() {
let bit = (power as u32 >> i) & 0x01;
buffer.push(reg0.shift(bit));
buffer.push(reg1.shift(bit));
}
for _ in (0..31).rev() {
let bit = 0;
buffer.push(reg0.shift(bit));
buffer.push(reg1.shift(bit));
}
buffer.interleave();
buffer.sync();
Ok(buffer.release())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_callsign() {
assert_eq!(encode_callsign(" 9 "), Ok(262374389));
assert_eq!(encode_callsign("KA1BCD"), Ok(143706369));
}
#[test]
fn test_encode_grid() {
assert_eq!(encode_grid("AA00"), Ok(32220));
assert_eq!(encode_grid("RR99"), Ok(179));
assert_eq!(encode_grid("Z"), Err(Error::InvalidGrid));
assert_eq!(encode_grid("ZZ"), Err(Error::InvalidGrid));
assert_eq!(encode_grid("ZZ11"), Err(Error::InvalidGrid));
}
#[test]
fn test_encode_power() {
assert_eq!(encode_power(61), Err(Error::InvalidPower));
for power in 0..=60 {
let rem = power % 10;
let result = encode_power(power);
if rem == 0 || rem == 3 || rem == 7 {
assert_eq!(result, Ok(power + 64));
} else {
assert_eq!(result, Err(Error::InvalidPower));
}
}
}
#[test]
fn test_encode_wspr() {
assert_eq!(
encode("K1A", "FN34", 33),
Ok([
3, 3, 0, 0, 2, 2, 0, 0, 1, 2, 0, 0, 1, 1, 1, 0, 2, 0, 3, 0, 0,
3, 0, 1, 1, 3, 3, 0, 0, 0, 0, 2, 0, 2, 1, 0, 0, 3, 2, 1, 2, 0,
2, 0, 0, 0, 3, 0, 1, 3, 0, 2, 3, 1, 2, 3, 0, 2, 0, 3, 3, 0, 1,
2, 2, 0, 0, 1, 3, 2, 1, 0, 3, 2, 3, 2, 1, 0, 0, 3, 2, 2, 1, 2,
1, 1, 0, 0, 0, 1, 1, 0, 3, 2, 3, 2, 2, 2, 3, 0, 2, 2, 0, 0, 1,
2, 2, 1, 2, 0, 1, 3, 1, 2, 3, 3, 0, 0, 1, 1, 2, 3, 2, 2, 0, 3,
1, 3, 2, 2, 0, 2, 0, 3, 0, 3, 2, 0, 1, 1, 2, 2, 0, 0, 2, 2, 2,
1, 3, 2, 3, 2, 3, 1, 2, 0, 0, 3, 1, 2, 2, 2
])
);
assert_eq!(
encode("N6AB", "CM87", 0),
Ok([
3, 1, 0, 0, 2, 2, 0, 2, 1, 0, 2, 0, 1, 3, 3, 0, 2, 0, 3, 0, 0,
1, 2, 1, 3, 1, 1, 2, 0, 2, 0, 0, 0, 2, 3, 2, 0, 1, 0, 3, 2, 0,
0, 0, 0, 2, 1, 0, 1, 3, 2, 0, 3, 1, 2, 1, 2, 0, 2, 3, 3, 0, 3,
0, 2, 2, 2, 3, 3, 0, 3, 2, 3, 2, 3, 2, 3, 0, 2, 1, 2, 2, 1, 0,
1, 3, 2, 2, 0, 1, 1, 0, 1, 2, 1, 0, 2, 2, 1, 0, 0, 2, 2, 0, 1,
0, 2, 3, 0, 2, 1, 1, 1, 0, 3, 3, 2, 0, 3, 1, 0, 3, 2, 0, 0, 3,
3, 1, 2, 2, 2, 2, 2, 3, 0, 1, 2, 0, 1, 1, 0, 2, 2, 0, 2, 2, 2,
3, 3, 2, 1, 2, 1, 3, 0, 0, 0, 3, 1, 0, 2, 2
])
);
assert_eq!(
encode("G1ABC", "IO83", 37),
Ok([
3, 3, 0, 0, 0, 2, 0, 0, 1, 0, 2, 0, 1, 1, 3, 2, 2, 2, 3, 2, 2,
1, 0, 1, 1, 3, 1, 2, 2, 2, 0, 0, 0, 0, 3, 0, 0, 1, 0, 3, 0, 2,
2, 2, 0, 2, 3, 2, 1, 3, 2, 2, 3, 3, 0, 1, 0, 0, 0, 1, 3, 2, 3,
2, 2, 2, 0, 1, 1, 2, 3, 0, 3, 0, 1, 0, 3, 0, 0, 1, 2, 2, 3, 2,
3, 3, 0, 0, 2, 3, 1, 2, 1, 0, 1, 2, 2, 2, 1, 0, 2, 0, 2, 2, 3,
2, 0, 1, 0, 0, 3, 1, 1, 2, 3, 3, 2, 2, 1, 1, 2, 1, 2, 0, 0, 1,
3, 3, 2, 0, 0, 2, 2, 1, 2, 3, 2, 0, 1, 1, 2, 2, 2, 2, 2, 0, 2,
3, 3, 2, 1, 2, 1, 3, 0, 2, 2, 3, 3, 2, 2, 0
])
);
assert_eq!(
encode("KA1BCD", "AA00", 33),
Ok([
3, 3, 2, 2, 0, 2, 0, 2, 3, 2, 0, 2, 1, 1, 1, 0, 0, 2, 1, 0, 2,
3, 2, 1, 1, 1, 1, 0, 0, 2, 0, 2, 2, 0, 3, 2, 2, 3, 2, 3, 2, 2,
2, 0, 2, 0, 3, 0, 3, 1, 0, 2, 3, 1, 0, 3, 2, 2, 0, 1, 3, 2, 1,
2, 0, 2, 0, 3, 3, 0, 3, 2, 1, 2, 1, 0, 3, 0, 2, 3, 0, 0, 3, 0,
3, 3, 2, 0, 2, 1, 1, 0, 3, 0, 3, 2, 2, 0, 3, 2, 0, 0, 2, 0, 3,
2, 0, 1, 2, 2, 1, 3, 1, 2, 1, 3, 2, 0, 1, 1, 2, 3, 0, 0, 2, 1,
3, 3, 2, 0, 2, 2, 2, 3, 0, 1, 2, 2, 1, 1, 0, 2, 0, 0, 0, 0, 2,
3, 1, 2, 1, 2, 3, 3, 2, 2, 2, 3, 1, 2, 0, 2
])
);
}
}