wspr/
lib.rs

1// References: [1] http://g4jnt.com/WSPR_Coding_Process.pdf
2
3#![no_std]
4
5#[cfg(feature = "defmt-03")]
6use defmt;
7
8#[cfg_attr(feature = "defmt-03", derive(defmt::Format))]
9#[derive(Debug, PartialEq)]
10pub enum Error {
11    InvalidPower,
12    InvalidGrid,
13    InvalidCallsign,
14}
15
16// A 32-bit shift register that shifts bits into the least significant bit,
17// performs a bitwise AND with a constant, counts the number of set bits
18// returning a 0 if even, 1 if odd.
19struct ShiftRegister {
20    value: u32,
21    and_const: u32,
22}
23
24impl ShiftRegister {
25    fn new(and_const: u32) -> Self {
26        Self {
27            value: 0,
28            and_const,
29        }
30    }
31
32    fn shift(&mut self, bit: u32) -> u8 {
33        self.value = (self.value << 1) | bit;
34        let ones = (self.value & self.and_const).count_ones();
35        let parity = ones & 0x01; // is parity odd
36        parity as u8
37    }
38}
39
40// Typically we'd just use a Vec here, but this crate is designed for [no_std]
41// use, we'll implement this by hand.
42struct Buffer {
43    buffer: [u8; 162],
44    index: usize,
45}
46
47impl Buffer {
48    fn new() -> Self {
49        Self {
50            buffer: [0u8; 162],
51            index: 0,
52        }
53    }
54
55    fn push(&mut self, bit: u8) {
56        self.buffer[self.index] = bit;
57        self.index += 1;
58    }
59
60    fn interleave(&mut self) {
61        let mut interleaved = [0u8; 162];
62        let mut p = 0;
63        for i in 0u8..255 {
64            let j = i.reverse_bits() as usize;
65            if j < 162 {
66                interleaved[j] = self.buffer[p];
67                p += 1;
68                if p == 162 {
69                    break;
70                }
71            }
72        }
73
74        self.buffer = interleaved;
75    }
76
77    fn sync(&mut self) {
78        const SYNC: [u8; 162] = [
79            1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1,
80            0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0,
81            0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0,
82            0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0,
83            0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0,
84            1, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0,
85            0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1,
86            0, 0, 0, 1, 1, 0, 0, 0,
87        ];
88
89        for i in 0..162 {
90            self.buffer[i] = SYNC[i] + 2 * self.buffer[i];
91        }
92    }
93
94    fn release(self) -> [u8; 162] {
95        self.buffer
96    }
97}
98
99// Return the base-36 value (0-35) for a single character '0-9A-Z', 36 for
100// spaces, or an error if any other characters are encountered.
101fn encode_callsign_char(c: u8) -> Result<u32, Error> {
102    let c = c as char;
103    if c == ' ' {
104        Ok(36)
105    } else {
106        match c.to_digit(36) {
107            Some(d) => Ok(d),
108            None => return Err(Error::InvalidCallsign),
109        }
110    }
111}
112
113fn encode_callsign(callsign: &str) -> Result<u32, Error> {
114    let callsign = callsign.as_bytes();
115
116    // Verify callsign is the appropriate length
117    let length = callsign.len();
118    if !(3..=6).contains(&length) {
119        return Err(Error::InvalidCallsign);
120    }
121
122    // Pad the callsign with spaces to make it a total of 6 ASCII characters,
123    // using the following scheme:
124    //
125    // "K1A"    => " K1A  "  (len 3)
126    // "K1AB"   => " K1AB "  (len 4)
127    // "KA1B"   => "KA1B  "  (len 4)
128    // "K1ABC"  => " K1ABC"  (len 5)
129    // "KA1BC"  => "KA1BC "  (len 5)
130    // "KA1BCD" => "KA1BCD"  (len 6)
131
132    // Determine the starting index of the first non-space character.
133    let start = match length {
134        0..=4 => {
135            if (callsign[2] as char).is_digit(10) {
136                0
137            } else {
138                1
139            }
140        }
141        5 => {
142            if (callsign[1] as char).is_digit(10) {
143                1
144            } else {
145                0
146            }
147        }
148        _ => 0,
149    };
150
151    // Fill an array with spaces and copying the callsign into the appropriate
152    // offset.
153    let stop = start + length;
154    let mut padded = [b' '; 6];
155    padded[start..stop].copy_from_slice(&callsign);
156    let callsign = padded;
157
158    // Ensure the 3rd character in the padded callsign is a digit.
159    if !(callsign[2] as char).is_digit(10) {
160        return Err(Error::InvalidCallsign);
161    }
162
163    // The code below is an looping version of the algorithm described
164    // in `The WSPR Coding Process`[1] by G4JNT.
165    //
166    // N0 = 0
167    // N1 = N0 * 0  + [Ch 1]
168    // N2 = N1 * 36 + [Ch 2]
169    // N3 = N2 * 10 + [Ch 3]
170    // N4 = N3 * 27 + [Ch 4] – 10
171    // N5 = N4 * 27 + [Ch 5] – 10
172    // N6 = N5 * 27 + [Ch 6] – 10
173
174    let scalars = [0u32, 36, 10, 27, 27, 27];
175    let subtracts = [0u32, 0, 0, 10, 10, 10];
176
177    let mut n = 0;
178    for (index, &c) in callsign.iter().enumerate() {
179        n = n * scalars[index] + encode_callsign_char(c)? as u32
180            - subtracts[index];
181    }
182
183    Ok(n)
184}
185
186fn encode_grid_char(c: u8) -> Result<u8, Error> {
187    return match (c as char).to_ascii_uppercase() {
188        'A'..='R' => Ok((c as u8) - b'A'),
189        '0'..='9' => Ok((c as u8) - b'0'),
190        _ => Err(Error::InvalidGrid),
191    };
192}
193
194fn encode_grid(grid: &str) -> Result<u16, Error> {
195    let grid = grid.as_bytes();
196
197    if grid.len() != 4 {
198        return Err(Error::InvalidGrid);
199    }
200
201    let first = encode_grid_char(grid[0])? as u16;
202    let second = encode_grid_char(grid[1])? as u16;
203    let third = encode_grid_char(grid[2])? as u16;
204    let fourth = encode_grid_char(grid[3])? as u16;
205
206    let result = (179 - 10 * first - third) * 180 + 10 * second + fourth;
207    Ok(result)
208}
209
210fn encode_power(power: u8) -> Result<u8, Error> {
211    // Power is between 0 and 60 dBm, and can only end in 0, 3, or 7 (otherwise
212    // it's invalid).
213    //
214    // Power in milliwatts can be calculated as 10 ^ (power / 10). For example:
215    // 37dBm = 10 ^ 3.7 = 5011.87 mW.
216    let rem = power % 10;
217
218    if (0..=60).contains(&power) && (rem == 0 || rem == 3 || rem == 7) {
219        Ok(power + 64)
220    } else {
221        Err(Error::InvalidPower)
222    }
223}
224
225/// Encodes a callsign, a four character Maidenhead grid square, and a power
226/// level (in dBm) into 162 symbols each with a range of 0-3. These symbols
227/// may then be transmitting using 4 tone frequency shift keying. Each tone
228/// is separated by 1.46Hz and is transmitted for 0.683s at a time, for a total
229/// transmission time of 110.64s.
230pub fn encode(
231    callsign: &str,
232    grid: &str,
233    power: u8,
234) -> Result<[u8; 162], Error> {
235    let callsign = encode_callsign(callsign)?;
236    let grid = encode_grid(grid)?;
237    let power = encode_power(power)?;
238
239    let mut reg0 = ShiftRegister::new(0xF2D05351);
240    let mut reg1 = ShiftRegister::new(0xE4613C47);
241
242    let mut buffer = Buffer::new();
243
244    for i in (0..28).rev() {
245        let bit = (callsign >> i) & 0x01;
246        buffer.push(reg0.shift(bit));
247        buffer.push(reg1.shift(bit));
248    }
249
250    for i in (0..15).rev() {
251        let bit = (grid as u32 >> i) & 0x01;
252        buffer.push(reg0.shift(bit));
253        buffer.push(reg1.shift(bit));
254    }
255
256    for i in (0..7).rev() {
257        let bit = (power as u32 >> i) & 0x01;
258        buffer.push(reg0.shift(bit));
259        buffer.push(reg1.shift(bit));
260    }
261
262    for _ in (0..31).rev() {
263        let bit = 0;
264        buffer.push(reg0.shift(bit));
265        buffer.push(reg1.shift(bit));
266    }
267
268    buffer.interleave();
269    buffer.sync();
270    Ok(buffer.release())
271}
272
273#[cfg(test)]
274mod tests {
275    use super::*;
276
277    #[test]
278    fn test_encode_callsign() {
279        assert_eq!(encode_callsign("  9   "), Ok(262374389));
280        assert_eq!(encode_callsign("KA1BCD"), Ok(143706369));
281    }
282
283    #[test]
284    fn test_encode_grid() {
285        assert_eq!(encode_grid("AA00"), Ok(32220));
286        assert_eq!(encode_grid("RR99"), Ok(179));
287
288        assert_eq!(encode_grid("Z"), Err(Error::InvalidGrid));
289        assert_eq!(encode_grid("ZZ"), Err(Error::InvalidGrid));
290        assert_eq!(encode_grid("ZZ11"), Err(Error::InvalidGrid));
291    }
292
293    #[test]
294    fn test_encode_power() {
295        // Too much power!
296        assert_eq!(encode_power(61), Err(Error::InvalidPower));
297
298        for power in 0..=60 {
299            let rem = power % 10;
300            let result = encode_power(power);
301            if rem == 0 || rem == 3 || rem == 7 {
302                assert_eq!(result, Ok(power + 64));
303            } else {
304                // doesn't end in 0, 3 or 7
305                assert_eq!(result, Err(Error::InvalidPower));
306            }
307        }
308    }
309
310    #[test]
311    fn test_encode_wspr() {
312        assert_eq!(
313            encode("K1A", "FN34", 33),
314            Ok([
315                3, 3, 0, 0, 2, 2, 0, 0, 1, 2, 0, 0, 1, 1, 1, 0, 2, 0, 3, 0, 0,
316                3, 0, 1, 1, 3, 3, 0, 0, 0, 0, 2, 0, 2, 1, 0, 0, 3, 2, 1, 2, 0,
317                2, 0, 0, 0, 3, 0, 1, 3, 0, 2, 3, 1, 2, 3, 0, 2, 0, 3, 3, 0, 1,
318                2, 2, 0, 0, 1, 3, 2, 1, 0, 3, 2, 3, 2, 1, 0, 0, 3, 2, 2, 1, 2,
319                1, 1, 0, 0, 0, 1, 1, 0, 3, 2, 3, 2, 2, 2, 3, 0, 2, 2, 0, 0, 1,
320                2, 2, 1, 2, 0, 1, 3, 1, 2, 3, 3, 0, 0, 1, 1, 2, 3, 2, 2, 0, 3,
321                1, 3, 2, 2, 0, 2, 0, 3, 0, 3, 2, 0, 1, 1, 2, 2, 0, 0, 2, 2, 2,
322                1, 3, 2, 3, 2, 3, 1, 2, 0, 0, 3, 1, 2, 2, 2
323            ])
324        );
325
326        assert_eq!(
327            encode("N6AB", "CM87", 0),
328            Ok([
329                3, 1, 0, 0, 2, 2, 0, 2, 1, 0, 2, 0, 1, 3, 3, 0, 2, 0, 3, 0, 0,
330                1, 2, 1, 3, 1, 1, 2, 0, 2, 0, 0, 0, 2, 3, 2, 0, 1, 0, 3, 2, 0,
331                0, 0, 0, 2, 1, 0, 1, 3, 2, 0, 3, 1, 2, 1, 2, 0, 2, 3, 3, 0, 3,
332                0, 2, 2, 2, 3, 3, 0, 3, 2, 3, 2, 3, 2, 3, 0, 2, 1, 2, 2, 1, 0,
333                1, 3, 2, 2, 0, 1, 1, 0, 1, 2, 1, 0, 2, 2, 1, 0, 0, 2, 2, 0, 1,
334                0, 2, 3, 0, 2, 1, 1, 1, 0, 3, 3, 2, 0, 3, 1, 0, 3, 2, 0, 0, 3,
335                3, 1, 2, 2, 2, 2, 2, 3, 0, 1, 2, 0, 1, 1, 0, 2, 2, 0, 2, 2, 2,
336                3, 3, 2, 1, 2, 1, 3, 0, 0, 0, 3, 1, 0, 2, 2
337            ])
338        );
339
340        assert_eq!(
341            encode("G1ABC", "IO83", 37),
342            Ok([
343                3, 3, 0, 0, 0, 2, 0, 0, 1, 0, 2, 0, 1, 1, 3, 2, 2, 2, 3, 2, 2,
344                1, 0, 1, 1, 3, 1, 2, 2, 2, 0, 0, 0, 0, 3, 0, 0, 1, 0, 3, 0, 2,
345                2, 2, 0, 2, 3, 2, 1, 3, 2, 2, 3, 3, 0, 1, 0, 0, 0, 1, 3, 2, 3,
346                2, 2, 2, 0, 1, 1, 2, 3, 0, 3, 0, 1, 0, 3, 0, 0, 1, 2, 2, 3, 2,
347                3, 3, 0, 0, 2, 3, 1, 2, 1, 0, 1, 2, 2, 2, 1, 0, 2, 0, 2, 2, 3,
348                2, 0, 1, 0, 0, 3, 1, 1, 2, 3, 3, 2, 2, 1, 1, 2, 1, 2, 0, 0, 1,
349                3, 3, 2, 0, 0, 2, 2, 1, 2, 3, 2, 0, 1, 1, 2, 2, 2, 2, 2, 0, 2,
350                3, 3, 2, 1, 2, 1, 3, 0, 2, 2, 3, 3, 2, 2, 0
351            ])
352        );
353
354        assert_eq!(
355            encode("KA1BCD", "AA00", 33),
356            Ok([
357                3, 3, 2, 2, 0, 2, 0, 2, 3, 2, 0, 2, 1, 1, 1, 0, 0, 2, 1, 0, 2,
358                3, 2, 1, 1, 1, 1, 0, 0, 2, 0, 2, 2, 0, 3, 2, 2, 3, 2, 3, 2, 2,
359                2, 0, 2, 0, 3, 0, 3, 1, 0, 2, 3, 1, 0, 3, 2, 2, 0, 1, 3, 2, 1,
360                2, 0, 2, 0, 3, 3, 0, 3, 2, 1, 2, 1, 0, 3, 0, 2, 3, 0, 0, 3, 0,
361                3, 3, 2, 0, 2, 1, 1, 0, 3, 0, 3, 2, 2, 0, 3, 2, 0, 0, 2, 0, 3,
362                2, 0, 1, 2, 2, 1, 3, 1, 2, 1, 3, 2, 0, 1, 1, 2, 3, 0, 0, 2, 1,
363                3, 3, 2, 0, 2, 2, 2, 3, 0, 1, 2, 2, 1, 1, 0, 2, 0, 0, 0, 0, 2,
364                3, 1, 2, 1, 2, 3, 3, 2, 2, 2, 3, 1, 2, 0, 2
365            ])
366        );
367    }
368}