wspr-beacon/src/wspr.rs

143 lines
4.0 KiB
Rust
Raw Permalink Normal View History

2021-04-25 17:33:35 +02:00
// Implemented by following http://www.g4jnt.com/Coding/WSPR_Coding_Process.pdf
const WSPR_LENGTH: usize = 162;
const WSPR_POWER_LOC_BITS: usize = 22;
const WSPR_SYNC: [u8; WSPR_LENGTH] = [
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,
];
fn encode_call_char(call_sign: &arrayvec::ArrayString<6>, idx: usize) -> u32 {
let c = call_sign.chars().nth(idx).unwrap().to_ascii_uppercase();
if c >= '0' && c <= '9' {
return (c as u32) - ('0' as u32);
} else if c >= 'A' && c <= 'Z' {
return 10 + (c as u32) - ('A' as u32);
}
// Turn everything else to space
else {
return 36;
}
}
fn encode_loc_char(locator: &arrayvec::ArrayString<6>, idx: usize) -> u32 {
let c = locator.chars().nth(idx).unwrap().to_ascii_uppercase();
if c >= '0' && c <= '9' {
return (c as u32) - ('0' as u32);
} else if c >= 'A' && c <= 'R' {
return (c as u32) - ('A' as u32);
}
// Everthing else becomes 9/I
else {
return 9;
}
}
fn encode_power(power: u8) -> u32 {
if power > 60 {
return 60 as u32;
}
return power as u32;
}
fn message_to_bits(
call_sign: &arrayvec::ArrayString<6>,
locator: &arrayvec::ArrayString<6>,
power: u8,
) -> u64 {
let mut n = encode_call_char(call_sign, 0);
n = n * 36 + encode_call_char(call_sign, 1);
n = n * 10 + encode_call_char(call_sign, 2);
n = n * 27 + encode_call_char(call_sign, 3) - 10;
n = n * 27 + encode_call_char(call_sign, 4) - 10;
n = n * 27 + encode_call_char(call_sign, 5) - 10;
// lowest 28 bits of n contain the encoded callsign
let mut m = 179 - 10 * encode_loc_char(locator, 0) - encode_loc_char(locator, 2);
m = m * 180 + 10 * encode_loc_char(locator, 1) + encode_loc_char(locator, 3);
m = m * 128 + encode_power(power) + 64;
// lowest 22 bits of m contain the encoded locator (15bit) and the encoded power (7 bits)
// Concactenate to 50bit wspr message
return (n as u64) << WSPR_POWER_LOC_BITS | (m as u64);
}
const FEC_TAPS1: u32 = 0xF2D05351;
const FEC_TAPS2: u32 = 0xE4613C47;
fn parity32(mut x: u32) -> u8 {
x ^= x >> 16;
x ^= x >> 8;
x ^= x >> 4;
x ^= x >> 2;
x ^= x >> 1;
return (x & 1) as u8;
}
fn generate_fec(msg: u64) -> [u8; WSPR_LENGTH] {
let mut output: [u8; WSPR_LENGTH] = [0; WSPR_LENGTH];
let mut reg: u32 = 0;
for i in 0..81 {
let bit = if i < 50 {
((msg >> (50 - i - 1)) & 1) as u32
} else {
0
};
reg = (reg << 1) | bit;
let tabs1 = reg & FEC_TAPS1;
let tabs2 = reg & FEC_TAPS2;
output[i * 2] = parity32(tabs1);
output[i * 2 + 1] = parity32(tabs2);
}
return output;
}
fn interleave_message(input: [u8; WSPR_LENGTH]) -> [u8; WSPR_LENGTH] {
let mut output: [u8; WSPR_LENGTH] = [0; WSPR_LENGTH];
let mut p: u8 = 0;
let mut i: u8 = 0;
while p < WSPR_LENGTH as u8 {
let rev_i = i.reverse_bits();
if rev_i < WSPR_LENGTH as u8 {
output[rev_i as usize] = input[p as usize];
p += 1;
}
i += 1;
}
return output;
}
fn add_sync(symbols: &mut [u8; WSPR_LENGTH]) {
for i in 0..WSPR_LENGTH {
symbols[i] = WSPR_SYNC[i] | symbols[i] << 1;
}
}
pub fn encode_message(
2021-04-25 17:33:35 +02:00
call_sign: &arrayvec::ArrayString<6>,
locator: &arrayvec::ArrayString<6>,
power: u8,
) -> [u8; WSPR_LENGTH] {
let bits = message_to_bits(call_sign, locator, power);
let msg_fec = generate_fec(bits);
let mut msg = interleave_message(msg_fec);
add_sync(&mut msg);
return msg;
}