Skip to content

Instantly share code, notes, and snippets.

@bd1es
Last active September 18, 2021 06:43
Show Gist options
  • Save bd1es/a782e2529b8289288fadd35e407f6440 to your computer and use it in GitHub Desktop.
Save bd1es/a782e2529b8289288fadd35e407f6440 to your computer and use it in GitHub Desktop.
WSPR encoder AVR porting and testing
/* wspr_enc.c
*
* This file is part of the AVR WSPR beacon.
*
* Copyright (C) 2010-2020 BD1ES.
* License: GNU GPL v3+
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/*
* Function "wspr_enc" is designed for AVR MCUs and is used to encode the input
* callsign, Maidenhead locator, and transmission power into different types of
* WSPR messages. The composition of the callsign and the length of the locator
* will determine the type of encoded message. Since this function supports the
* type 3 message, the K1JT NHASH algorithm is therefore required to complete a
* callsign digest. And the source code "nhash.c" and "nhash.h" should be added
* to the project while building the target.
*
* wspr_enc has been tested on some AVR development boards and "Proteus VSM for
* AVR" by compiling the code using Atmel Studio 6 with its built-in toolchain.
* This function also works on Raspberry Pi OS, Armbian, Ubuntu, and Windows.
*
* wspr_enc takes advantage of the following open-source software:
* Joe Taylor, K1JT: WSPR, WSJT-X, and the NHASH code nhash.c, nhash.h
* https://sourceforge.net/p/wsjt/wsjtx/ci/master/tree/lib/wsprd
*
* James Peroulas: wspr.cpp
* https://github.com/JamesP6000/WsprryPi
*
* Reference:
* Joe Taylor, K1JT: WSPR 2.0 User's Guide
* https://www.physics.princeton.edu/pulsar/K1JT/WSPR_2.0_User.pdf
*
* Andy Talbot, G4JNT: The WSPR Coding Process
* http://www.g4jnt.com/wspr_coding_process.pdf
*
* Nick Massey, VA7NRM: Inside WSPR, JT65 and JT9 Weak-signal HF Modes
* http://archive.nsarc.ca/hf/jt_modes.pdf
*
* History:
* 21 FEB 2020:
* Released to the Gist
* https://gist.github.com/bd1es/a782e2529b8289288fadd35e407f6440
* 31 MAR 2020:
* Replaced the parity table with an algorithm introduced by Sean Anderson
* http://graphics.stanford.edu/~seander/bithacks.html#ParityParallel
* 06 FEB 2021:
* Function "char_enc" has been added to refine the scope of "wspr_cenc."
* 16 SEP 2021:
* Minor changes (mostly typing.)
*/
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#if !defined(__AVR_ARCH__)
#include <stdio.h>
#endif
#include "nhash.h"
#include "wspr_enc.h"
// These macros are defined for sharing AVR GCC code with PCs.
#if defined(__GNUC__) && defined(__AVR_ARCH__)
#include <avr/pgmspace.h>
#define PGM_ROM_SPACE const PROGMEM
#else
#define PGM_ROM_SPACE const
#define pgm_read_byte *
#define pgm_read_word *
#define pgm_read_dword *
#endif
static uint8_t char_enc(uint8_t c)
{
/* Character encoding table, based on "The WSPR Coding Process, G4JNT."
* The 37 allowed characters are allocated values from 0 to 36 such that
* '0' - '9' give 0 - 9, 'A' to 'Z' give 10 to 35, and [space] is given
* the value 36.
*/
static PGM_ROM_SPACE uint8_t wspr_cenc[128] = { /* in ASCII order */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 36, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5,
6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
35, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 0, 0, 0,
0, 0,
};
if(c < 128){
return pgm_read_byte(&wspr_cenc[c]);
}else{
return 0;
}
}
// This function was ported from James Peroulas's wspr().
static void pack_call(const char *call, uint8_t clen, uint32_t *n1,
uint32_t *ng, uint8_t *nadd)
{
const uint8_t *s = (uint8_t *)strchr(call, '/');
const uint8_t *c = (uint8_t *)call;
uint8_t n, i;
uint32_t m = 0;
if(s){
*nadd = 2;
// stroke position
i = s - c;
// suffix len, prefix-call len
n = clen - i - 1;
// 1-digit suffix /A to /Z, /0 to /9
if(n == 1) m = 60000 - 32768 + char_enc(c[i+1]);
// 2-digit suffix /10 to /99
if(n == 2) m = 60000 + 26 + 10*char_enc(c[i+1]) + char_enc(c[i+2]);
// prefix EA8/, right align
if(n > 2){
m = i < 3 ? 36 : char_enc(c[i-3]);
m = 37 * m + (i < 2 ? 36 : char_enc(c[i-2]));
m = 37 * m + (i < 1 ? 36 : char_enc(c[i-1]));
if(m < 32768){
*nadd = 1;
}else{
m -= 32768;
}
c += i + 1;
clen -= i + 1;
}else{
clen -= n + 1;
}
// for the type 2 message, ng contains either a call prefix or suffix
*ng = m;
}else{
*nadd = 0;
}
// n1 contains an ordinary call
i = isdigit(c[2]) ? 2 : isdigit(c[1]) ? 1 : 0;
n = clen - i - 1;
m = i < 2 ? 36 : char_enc(c[i-2]);
m = 36 * m + (i < 1 ? 36 : char_enc(c[i-1]));
m = 10 * m + char_enc(c[i]);
m = 27 * m + (n < 1 ? 26 : char_enc(c[i+1])-10);
m = 27 * m + (n < 2 ? 26 : char_enc(c[i+2])-10);
m = 27 * m + (n < 3 ? 26 : char_enc(c[i+3])-10);
*n1 = m;
}
// Pack the given parameters into a single WSPR code block.
static uint8_t pack_message(const char *call, const char *grid,
const char *dBm, uint8_t *packed)
{
uint32_t n1, ng;
uint8_t nadd, mtype;
uint8_t gridlen = strlen(grid);
if(gridlen == 4){
pack_call(call, strlen(call), &n1, &ng, &nadd);
// for the type 1 message, ng contains a 4-character grid locator
if(nadd == 0){
const uint8_t *g = (uint8_t *)grid;
ng = 180 * (179 - 10*(char_enc(g[0])-10) - char_enc(g[2]))
+ 10*(char_enc(g[1])-10) + char_enc(g[3]);
mtype = 1;
}else{
mtype = 2;
}
}else if(gridlen == 6){
char tmp[WSPR_MAX_CALLLEN_TYPE3];
uint8_t i, clen = 0;
// for the type 3 message, ng contains a 15-bit callsign digest
for(i = 0; i < WSPR_MAX_CALLLEN_TYPE3; i++){
if(call[i]){
tmp[i] = toupper(call[i]);
clen++;
}else{
break;
}
}
ng = nhash(tmp, clen, (uint32_t)146);
// n1 has a left-rotated 6-character locator that acts as a call
for(i = 0; i < 5; i++) {
tmp[i] = grid[i+1];
}
tmp[5] = grid[0];
tmp[6]=0;
pack_call(tmp, 6, &n1, &ng, &nadd);
mtype = 3;
}else{
return 0;
}
// EIRP in dBm = {0,3,7,10,13,17,20,23,27,30,33,37,40,43,47,50,53,57,60}
static PGM_ROM_SPACE int8_t corr[] = {0, -1, 1, 0, -1, 2, 1, 0, -1, 1};
int pwr = atoi(dBm);
pwr = pwr>60 ? 60 : pwr<0 ? 0 : pwr + (int8_t)pgm_read_byte(&corr[pwr%10]);
// add the converted power level to ng according to the message type
int8_t ntype = gridlen!=6 ? pwr+nadd : -(pwr+1);
ng = 128*ng + ntype + 64;
// pack n1, ng, and zero-tail to 50 bits
packed[0] = (uint8_t)(n1>>20);
packed[1] = (uint8_t)(n1>>12),
packed[2] = (uint8_t)(n1>>4),
packed[3] = (uint8_t)(((n1&0x0f)<<4) | ((ng>>18)&0x0f)),
packed[4] = (uint8_t)(ng>>10),
packed[5] = (uint8_t)(ng>>2),
packed[6] = (uint8_t)((ng&0x03)<<6),
packed[7] = packed[8] = packed[9] = packed[10] = 0;
return mtype;
}
/* Calculate the parity of 32-bit given "v" and double the returning result.
* Such a value can be added directly to the channel sync.
*/
static uint8_t sparity(uint32_t v)
{
v ^= v >> 16;
v ^= v >> 8;
v ^= v >> 4;
v &= 0xf;
return ((0x6996u*2) >> v) & 2;
}
// Encode the WSPR code block into a set of channel symbols, a WSPR message.
static void encode_symbols(const uint8_t *packed, uint8_t *symbols)
{
/* symbol interleaving table */
static PGM_ROM_SPACE uint8_t interleave[162] = {
0, 128, 64, 32, 160, 96, 16, 144, 80, 48, 112, 8, 136, 72,
40, 104, 24, 152, 88, 56, 120, 4, 132, 68, 36, 100, 20, 148,
84, 52, 116, 12, 140, 76, 44, 108, 28, 156, 92, 60, 124, 2,
130, 66, 34, 98, 18, 146, 82, 50, 114, 10, 138, 74, 42, 106,
26, 154, 90, 58, 122, 6, 134, 70, 38, 102, 22, 150, 86, 54,
118, 14, 142, 78, 46, 110, 30, 158, 94, 62, 126, 1, 129, 65,
33, 161, 97, 17, 145, 81, 49, 113, 9, 137, 73, 41, 105, 25,
153, 89, 57, 121, 5, 133, 69, 37, 101, 21, 149, 85, 53, 117,
13, 141, 77, 45, 109, 29, 157, 93, 61, 125, 3, 131, 67, 35,
99, 19, 147, 83, 51, 115, 11, 139, 75, 43, 107, 27, 155, 91,
59, 123, 7, 135, 71, 39, 103, 23, 151, 87, 55, 119, 15, 143,
79, 47, 111, 31, 159, 95, 63, 127,
};
// channel syncs
static PGM_ROM_SPACE uint8_t sync[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,
};
/* The data will be expanded to add FEC with a rate ½, constraint length
* 32, convolutional encoder. (G4JNT)
*/
uint8_t i, j, k, conv_ptr = 0;
uint32_t reg = 0;
for(i = 0; i < 11; i++){
for(j = 7; j < 255; j--){
if(packed[i] & (1<<j)) reg |= 1;
k = pgm_read_byte(&interleave[conv_ptr++]);
symbols[k] = sparity(reg & 0xF2D05351) | pgm_read_byte(&sync[k]);
k = pgm_read_byte(&interleave[conv_ptr++]);
symbols[k] = sparity(reg & 0xE4613C47) | pgm_read_byte(&sync[k]);
if(conv_ptr == 162){
break;
}else{
reg <<= 1;
}
}
}
}
// The WSPR encoder.
uint8_t wspr_enc(const char *call, const char *grid, const char *dBm,
uint8_t *symbols)
{
uint8_t packed[11];
uint8_t msgtype = pack_message(call, grid, dBm, packed);
if(msgtype != 0){
encode_symbols(packed, symbols);
}
return msgtype;
}
/* wspr_enc.h
*
* This file is part of the AVR WSPR beacon.
*
* Copyright (C) 2010-2020 BD1ES.
*/
#ifndef WSPR_ENC_H
#define WSPR_ENC_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* These macros are defined for WSPR specific message types. Developers can use
* them to constrain the length of the callsign input.
*/
#define WSPR_MAX_CALLLEN_TYPE1 6
#define WSPR_MAX_CALLLEN_TYPE2 10
#define WSPR_MAX_CALLLEN_TYPE3 10
/* WSPR encoder
*
* Parameters
* call: Pointer to a callsign.
* A compound callsign will produce a type 2 message. (And 3DA0xx
* will always look forward to its alternative form of 3D0xx.)
* grid: Pointer to a Maidenhead grid locator.
* A 6-character locator will produce a type 3 message.
* dBm: Pointer to TX power level, ranging from "0" to "60."
* symbols: Pointer to an 8-bit array holding 162 channel symbols.
*
* Return value:
* 0: Error occurred. (The length of locator is neither 4 nor 6.)
* 1 - 3: The type of generated message.
*
* Comments:
* This function is mainly designed for AVRs, with fewer input checks.
* For improved stability, developers might need to prepare additional
* validations in their application.
*/
uint8_t wspr_enc(const char *call, const char *grid, const char *dBm,
uint8_t *symbols);
#ifdef __cplusplus
}
#endif
#endif // WSPR_ENC_H
/* wspr_enc_test.c
*
* This file is part of the AVR WSPR beacon.
*
* Copyright (C) 2010-2020 BD1ES.
*/
/*
* This program is designed for developers to test the WSPR encoding function
* "wspr_enc," which I have used to generate WSPR messages in my project.
*
* On top of the test, K1JT's WSPR message simulation tool "WSPRcode" needs to
* be installed to provide original reference symbols. You can build this tool
* from the source by typing "./go.sh" on any platform equipped with a Fortran
* 90 compiler. To download the source code, please visit
* https://sourceforge.net/p/wsjt/wsjtx/ci/master/tree/lib/wsprcode
*
* Reference:
* Joe Taylor, K1JT: WSPR 2.0 User's Guide
* https://www.physics.princeton.edu/pulsar/K1JT/WSPR_2.0_User.pdf
*
* Andy Talbot, G4JNT: The WSPR Coding Process
* http://www.g4jnt.com/wspr_coding_process.pdf
*
* History:
* 21 FEB 2020:
* Released to the Gist:
* https://gist.github.com/bd1es/a782e2529b8289288fadd35e407f6440
* 31 MAR 2020:
* The parity table has been removed due to the optimization of the encoder.
* 01 APR 2020:
* Encoder-generated symbols can be auto-compared by invoking "WSPRcode."
* 06 FEB 2021:
* Minor changes.
* 16 SEP 2021:
* Minor changes (mostly typing.)
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <getopt.h>
#include <stdarg.h>
#include "wspr_enc.h"
// This function generates a WSPR character encoding table.
static void gen_char_enc_table()
{
puts("/* Character encoding table, based on \"The WSPR Coding Process, G4JNT.\"\n"
" * The 37 allowed characters are allocated values from 0 to 36 such that\n"
" * '0' - '9' give 0 - 9, 'A' to 'Z' give 10 to 35, and [space] is given\n"
" * the value 36.\n"
" */"
);
printf("static PGM_ROM_SPACE uint8_t wspr_cenc[128] = { /* in ASCII order */");
int i;
for(i = 0; i < 128; i++){
int k;
if(i == ' '){
k = 36;
}else if(i >= '0' && i <= '9'){
k = i - '0';
}else if(i >= 'A' && i <= 'Z'){
k = i - 'A' + 10;
}else if(i >= 'a' && i <= 'z'){
k = i - 'a' + 10;
}else{
k = 0;
}
if((i%18) == 0){
printf("\n ");
}
printf("%2d, ", k);
}
puts("\n};");
}
// This function generates a WSPR specific symbol interleaving table.
static void gen_interleave_table()
{
puts("/* symbol interleaving table. */");
printf("static PGM_ROM_SPACE uint8_t interleave[162] = {");
int i, j, k;
for(i = 0, k = 0; i < 256; i++){
uint8_t reversed_byte = 0;
for(j = 0; j < 8; j++){
uint8_t temp = (i & (1 << j));
if(temp){
reversed_byte |= (1 << ((8 - 1) - j));
}
}
if(reversed_byte < 162){
if((k++)%14 == 0){
printf("\n ");
}
printf("%3d, ", reversed_byte);
if(k == 162){
break;
}
}
}
puts("\n};");
}
// ----------------------------------------------------------------------------
static void str_to_upper(char *dist, const char *str)
{
while(*str) *dist++ = toupper(*str++);
*dist = 0;
}
static void print_help(const char *pname)
{
printf("This program is designed to test the WSPR encoding function \"wspr_enc.\"\n");
printf("Usage: %s [options] <callsign> <grid> <dBm>\n", pname);
printf("Examples: %s BD1XYZ OM89 10 (message type 1)\n", pname);
printf(" %s BD1XYZ/M xxxx 20 (message type 2)\n", pname);
printf(" %s BD1XYZ OM89dw 30 (message type 3)\n", pname);
printf("Options:\n");
printf(" -h --help\n");
printf(" Display this help.\n");
printf(" --gen_tables\n");
printf(" Generate encoder lookup tables.\n");
printf("Arguments:\n");
printf(" callsign: BD1XYZ BD1XYZ/9 EA8/BD1XYZ ...\n");
printf(" in which a compound callsign will produce a type 2 message.\n");
printf(" grid: 4- or 6-character grid locator\n");
printf(" in which a 6-character locator will produce a type 3 message.\n");
printf(" dBm: The TX power (EIRP) in dBm, ranging from 0 to 60.\n");
printf("\n");
printf("%s requires K1JT's WSPRcode to check any generated messages.\n", pname);
}
struct strings_t{
int size;
char line[256][256];
};
static int run_pipe(struct strings_t *strs, const char *command)
{
FILE *fpipe;
if(!(fpipe = (FILE*)popen(command, "r"))){
perror("");
return -1;
}
strs->size = 0;
while(fgets(strs->line[strs->size], 256, fpipe)){
strs->size++;
if(strs->size == 256) break;
}
pclose(fpipe);
return 0;
}
static int check_channel_symbols(uint8_t *symbols, struct strings_t *strs)
{
int i, symbol_pos = 0;
// find the starting position of channel symbols given by WSPRcode
for(i = 0; i < strs->size; i++){
if(strncmp(strs->line[i], "Channel symbols:", 16) == 0){
symbol_pos = i+1;
break;
}
}
if(symbol_pos == 0){
printf("No channel symbols were found in WSPRcode output.\n"
"Symbol verification cannot continue.\n");
return -1;
}
char line[256];
int verified = 0;
for(i = 0; i < 6; i++){
int j;
char *line_p = line;
for(j = 0; (j < 30) && ((i*30 + j) < 162); j++){
*line_p++ = '0' + symbols[i*30 + j];
*line_p++ = ' ';
}
*line_p = 0;
if(strncmp(strs->line[symbol_pos+i]+6, line, line_p-line-1) != 0){
printf("Inconsistent channel symbols were found in line %d:\n"
"%s--> %s\n",
i+1, strs->line[symbol_pos+i], line);
verified = -1;
}
}
return verified;
}
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
int main(int argc, char *argv[])
{
static const char *PROG_NAME = "wspr_enc_test";
char *call = NULL, *grid = NULL, *dBm = NULL;
char call_u[256], grid_u[256];
static const struct option long_options[] = {
{"help", no_argument, 0, 'h'},
{"gen_tables", no_argument, 0, 1},
{0, 0, 0, 0}
};
// get options
while(1){
int option_index;
int c = getopt_long(argc, argv, "h", long_options, &option_index);
if(c == -1){
break;
}
switch(c){
case 'h':
print_help(PROG_NAME);
return 0;
case 1:
gen_char_enc_table();
gen_interleave_table();
return 0;
default:
return -1;
}
}
// get arguments
size_t nargs = 0;
char *endp;
while(optind < argc){
switch(nargs){
case 0:
call = argv[optind++];
if(strlen(call) > (sizeof(call_u)-1)) call[sizeof(call_u)-1] = 0;
if((strlen(call) < 2) || (strlen(call) > 10)){
printf("Warning, invalid length of the given callsign %s.\n", call);
}
str_to_upper(call_u, call);
// for Swaziland 3DA0YZ, 3D0YZ will be used for proper decoding
if(strncmp(call_u, "3DA0", 4) == 0){
size_t i;
for(i = 3; i < strlen(call); i++){
call[i-1] = call[i];
}
call[i-1] = 0;
}
str_to_upper(call_u, call);
break;
case 1:
grid = argv[optind++];
if(strlen(grid) > (sizeof(grid_u)-1)) grid[sizeof(grid_u)-1] = 0;
if((strlen(grid)!=4) && (strlen(grid)!=6)){
printf("Warning, invalid length of the given locator %s.\n", grid);
}
str_to_upper(grid_u, grid);
break;
case 2:
dBm = argv[optind++];
strtol(dBm, &endp, 10);
if((dBm==endp) || (*endp!='\0')){
printf("Invalid dBm value %s.\n", dBm);
return -1;
}
break;
default:
puts("Too many parameters.\n");
print_help(PROG_NAME);
return -1;
}
nargs++;
}
if(nargs < 3){
puts("Too few parameters.\n");
print_help(PROG_NAME);
return -1;
}
// invoke "wspr_enc" to generate WSPR channel symbols
printf("Converting given parameters:\n"
"callsign = %s, "
"grid = %s, "
"power = %s dBm\n", call, grid, dBm);
uint8_t symbols[162];
uint8_t msgtype = wspr_enc(call, grid, dBm, symbols);
printf("into the following channel symbols (with message type %d:)\n", msgtype);
int i;
for(i = 0; i < 162; i++){
if(i%30 == 0){
printf("\n %d", symbols[i]);
}else{
printf(" %d", symbols[i]);
}
}
printf("\n");
// build command for invoking WSPRcode according to the "msgtype"
char cmd[1024], msg[1024];
if(msgtype == 1){
sprintf(cmd, "WSPRcode \"%s %s %s\"", call_u, grid_u, dBm);
sprintf(msg, "Message: %s %s %s", call_u, grid_u, dBm);
}else if(msgtype == 2){
sprintf(cmd, "WSPRcode \"%s %s\"", call_u, dBm);
sprintf(msg, "Message: %s %s", call_u, dBm);
}else if(msgtype == 3){
sprintf(cmd, "WSPRcode \"<%s> %s %s\"", call_u, grid_u, dBm);
sprintf(msg, "Message: <%s> %s %s", call_u, grid_u, dBm);
}else{
printf("Message type %d is unknown. Check the given locator.\n", msgtype);
return -1;
}
// run WSPRcode and collect its output from the pipe.
printf("\nChecking symbols by invoking %s ...\n", cmd);
struct strings_t strs;
int res = run_pipe(&strs, cmd);
if(res || (strncmp(strs.line[0], msg, strlen(msg))!=0)){
printf("Command WSPRcode returned unexpected \"%s\".\n", strs.line[0]);
return -1;
}else{
int i;
for(i = 0; i < strs.size; i++){
printf("%s", strs.line[i]);
}
}
printf("\n");
// perform the verification
res = check_channel_symbols(symbols, &strs);
if(res == 0){
printf("Symbols have been verified.\n");
}else{
printf("Symbol verification failed.\n");
}
return res;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment