Skip to content

Commit 6cbce8e

Browse files
committed
Add WSPR Type 2 and 3 support
1 parent f5e1aa9 commit 6cbce8e

File tree

1 file changed

+252
-51
lines changed

1 file changed

+252
-51
lines changed

src/JTEncode.cpp

Lines changed: 252 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
* JTEncode.cpp - JT65/JT9/WSPR/FSQ encoder library for Arduino
33
*
4-
* Copyright (C) 2015-2018 Jason Milldrum <[email protected]>
4+
* Copyright (C) 2015-2021 Jason Milldrum <[email protected]>
55
*
66
* Based on the algorithms presented in the WSJT software suite.
77
* Thanks to Andy Talbot G4JNT for the whitepaper on the WSPR encoding
@@ -24,6 +24,7 @@
2424
#include <JTEncode.h>
2525
#include <crc14.h>
2626
#include <generator.h>
27+
#include <nhash.h>
2728

2829
#include <string.h>
2930
#include <ctype.h>
@@ -188,16 +189,17 @@ void JTEncode::jt4_encode(const char * msg, uint8_t * symbols)
188189
/*
189190
* wspr_encode(const char * call, const char * loc, const uint8_t dbm, uint8_t * symbols)
190191
*
191-
* Takes an arbitrary message of up to 13 allowable characters and returns
192+
* Takes a callsign, grid locator, and power level and returns a WSPR symbol
193+
* table for a Type 1, 2, or 3 message.
192194
*
193-
* call - Callsign (6 characters maximum).
194-
* loc - Maidenhead grid locator (4 characters maximum).
195+
* call - Callsign (11 characters maximum).
196+
* loc - Maidenhead grid locator (6 characters maximum).
195197
* dbm - Output power in dBm.
196198
* symbols - Array of channel symbols to transmit returned by the method.
197199
* Ensure that you pass a uint8_t array of at least size WSPR_SYMBOL_COUNT to the method.
198200
*
199201
*/
200-
void JTEncode::wspr_encode(const char * call, const char * loc, const uint8_t dbm, uint8_t * symbols)
202+
void JTEncode::wspr_encode(const char * call, const char * loc, const int8_t dbm, uint8_t * symbols)
201203
{
202204
char call_[7];
203205
char loc_[5];
@@ -533,7 +535,7 @@ uint8_t JTEncode::ft_code(char c)
533535
uint8_t JTEncode::wspr_code(char c)
534536
{
535537
// Validate the input then return the proper integer code.
536-
// Return 255 as an error code if the char is not allowed.
538+
// Change character to a space if the char is not allowed.
537539

538540
if(isdigit(c))
539541
{
@@ -549,7 +551,7 @@ uint8_t JTEncode::wspr_code(char c)
549551
}
550552
else
551553
{
552-
return 255;
554+
return 36;
553555
}
554556
}
555557

@@ -612,72 +614,85 @@ void JTEncode::ft_message_prep(char * message)
612614
strcpy(message, temp_msg);
613615
}
614616

615-
void JTEncode::wspr_message_prep(char * call, char * loc, uint8_t dbm)
617+
void JTEncode::wspr_message_prep(char * call, char * loc, int8_t dbm)
616618
{
617619
// Callsign validation and padding
618620
// -------------------------------
619-
620-
// If only the 2nd character is a digit, then pad with a space.
621-
// If this happens, then the callsign will be truncated if it is
622-
// longer than 5 characters.
623-
if((call[1] >= '0' && call[1] <= '9') && (call[2] < '0' || call[2] > '9'))
621+
622+
// Ensure that the only allowed characters are digits, uppercase letters, slash, and angle brackets
623+
uint8_t i;
624+
for(i = 0; i < 12; i++)
624625
{
625-
memmove(call + 1, call, 5);
626-
call[0] = ' ';
626+
if(callsign[i] != '/' && callsign[i] != '<' && callsign[i] != '>')
627+
{
628+
callsign[i] = toupper(callsign[i]);
629+
if(!(isdigit(callsign[i]) || isupper(callsign[i])))
630+
{
631+
callsign[i] = ' ';
632+
}
633+
}
627634
}
628635

629-
// Now the 3rd charcter in the callsign must be a digit
630-
if(call[2] < '0' || call[2] > '9')
631-
{
632-
// TODO: need a better way to handle this
633-
call[2] = '0';
634-
}
636+
strncpy(callsign, call, 12);
635637

636-
// Ensure that the only allowed characters are digits and
637-
// uppercase letters
638-
uint8_t i;
639-
for(i = 0; i < 6; i++)
638+
// Grid locator validation
639+
if(strlen(loc) == 4 || strlen(loc) == 6)
640640
{
641-
call[i] = toupper(call[i]);
642-
if(!(isdigit(call[i]) || isupper(call[i])))
641+
for(i = 0; i <= 1; i++)
643642
{
644-
call[i] = ' ';
643+
loc[i] = toupper(loc[i]);
644+
if((loc[i] < 'A' || loc[i] > 'R'))
645+
{
646+
strncpy(loc, "AA00AA", 7);
647+
}
648+
}
649+
for(i = 2; i <= 3; i++)
650+
{
651+
if(!(isdigit(loc[i])))
652+
{
653+
strncpy(loc, "AA00AA", 7);
654+
}
645655
}
646656
}
657+
else
658+
{
659+
strncpy(loc, "AA00AA", 7);
660+
}
647661

648-
memcpy(callsign, call, 6);
649-
650-
// Grid locator validation
651-
for(i = 0; i < 4; i++)
662+
if(strlen(loc) == 6)
652663
{
653-
loc[i] = toupper(loc[i]);
654-
if(!(isdigit(loc[i]) || (loc[i] >= 'A' && loc[i] <= 'R')))
664+
for(i = 4; i <= 5; i++)
655665
{
656-
memcpy(loc, "AA00", 5);
657-
//loc = "AA00";
666+
loc[i] = toupper(loc[i]);
667+
if((loc[i] < 'A' || loc[i] > 'X'))
668+
{
669+
strncpy(loc, "AA00AA", 7);
670+
}
658671
}
659672
}
660673

661-
memcpy(locator, loc, 4);
674+
strncpy(locator, loc, 7);
662675

663676
// Power level validation
664677
// Only certain increments are allowed
665678
if(dbm > 60)
666679
{
667680
dbm = 60;
668681
}
669-
const uint8_t valid_dbm[19] =
670-
{0, 3, 7, 10, 13, 17, 20, 23, 27, 30, 33, 37, 40,
682+
const uint8_t VALID_DBM_SIZE = 28;
683+
const int8_t valid_dbm[VALID_DBM_SIZE] =
684+
{-30, -27, -23, -20, -17, -13, -10, -7, -3,
685+
0, 3, 7, 10, 13, 17, 20, 23, 27, 30, 33, 37, 40,
671686
43, 47, 50, 53, 57, 60};
672-
for(i = 0; i < 19; i++)
687+
for(i = 0; i < VALID_DBM_SIZE; i++)
673688
{
674689
if(dbm == valid_dbm[i])
675690
{
676691
power = dbm;
677692
}
678693
}
679694
// If we got this far, we have an invalid power level, so we'll round down
680-
for(i = 1; i < 19; i++)
695+
for(i = 1; i < VALID_DBM_SIZE; i++)
681696
{
682697
if(dbm < valid_dbm[i] && dbm >= valid_dbm[i - 1])
683698
{
@@ -794,18 +809,186 @@ void JTEncode::wspr_bit_packing(uint8_t * c)
794809
{
795810
uint32_t n, m;
796811

797-
n = wspr_code(callsign[0]);
798-
n = n * 36 + wspr_code(callsign[1]);
799-
n = n * 10 + wspr_code(callsign[2]);
800-
n = n * 27 + (wspr_code(callsign[3]) - 10);
801-
n = n * 27 + (wspr_code(callsign[4]) - 10);
802-
n = n * 27 + (wspr_code(callsign[5]) - 10);
812+
// Determine if type 1, 2 or 3 message
813+
char* slash_avail = strchr(callsign, (int)'/');
814+
if(callsign[0] == '<')
815+
{
816+
// Type 3 message
817+
char base_call[7];
818+
memset(base_call, 0, 7);
819+
uint32_t init_val = 146;
820+
char* bracket_avail = strchr(callsign, (int)'>');
821+
int call_len = bracket_avail - callsign - 1;
822+
strncpy(base_call, callsign + 1, call_len);
823+
uint32_t hash = nhash_(base_call, &call_len, &init_val);
824+
uint16_t call_hash = hash & 32767;
825+
826+
// Convert 6 char grid square to "callsign" format for transmission
827+
// by putting the first character at the end
828+
char temp_loc = locator[0];
829+
locator[0] = locator[1];
830+
locator[1] = locator[2];
831+
locator[2] = locator[3];
832+
locator[3] = locator[4];
833+
locator[4] = locator[5];
834+
locator[5] = temp_loc;
835+
836+
n = wspr_code(locator[0]);
837+
n = n * 36 + wspr_code(locator[1]);
838+
n = n * 10 + wspr_code(locator[2]);
839+
n = n * 27 + (wspr_code(locator[3]) - 10);
840+
n = n * 27 + (wspr_code(locator[4]) - 10);
841+
n = n * 27 + (wspr_code(locator[5]) - 10);
842+
843+
m = (call_hash * 128) - (power + 1) + 64;
844+
}
845+
else if(slash_avail == (void *)0)
846+
{
847+
// Type 1 message
848+
pad_callsign(callsign);
849+
n = wspr_code(callsign[0]);
850+
n = n * 36 + wspr_code(callsign[1]);
851+
n = n * 10 + wspr_code(callsign[2]);
852+
n = n * 27 + (wspr_code(callsign[3]) - 10);
853+
n = n * 27 + (wspr_code(callsign[4]) - 10);
854+
n = n * 27 + (wspr_code(callsign[5]) - 10);
855+
856+
m = ((179 - 10 * (locator[0] - 'A') - (locator[2] - '0')) * 180) +
857+
(10 * (locator[1] - 'A')) + (locator[3] - '0');
858+
m = (m * 128) + power + 64;
859+
}
860+
else if(slash_avail)
861+
{
862+
// Type 2 message
863+
int slash_pos = slash_avail - callsign;
864+
uint8_t i;
803865

804-
m = ((179 - 10 * (locator[0] - 'A') - (locator[2] - '0')) * 180) +
805-
(10 * (locator[1] - 'A')) + (locator[3] - '0');
806-
m = (m * 128) + power + 64;
866+
// Determine prefix or suffix
867+
if(callsign[slash_pos + 2] == ' ' || callsign[slash_pos + 2] == 0)
868+
{
869+
// Single character suffix
870+
char base_call[7];
871+
memset(base_call, 0, 7);
872+
strncpy(base_call, callsign, slash_pos);
873+
for(i = 0; i < 6; i++)
874+
{
875+
base_call[i] = toupper(base_call[i]);
876+
if(!(isdigit(base_call[i]) || isupper(base_call[i])))
877+
{
878+
base_call[i] = ' ';
879+
}
880+
}
881+
pad_callsign(base_call);
882+
883+
n = wspr_code(base_call[0]);
884+
n = n * 36 + wspr_code(base_call[1]);
885+
n = n * 10 + wspr_code(base_call[2]);
886+
n = n * 27 + (wspr_code(base_call[3]) - 10);
887+
n = n * 27 + (wspr_code(base_call[4]) - 10);
888+
n = n * 27 + (wspr_code(base_call[5]) - 10);
807889

808-
// Callsign is 28 bits, locator/power is 22 bits.
890+
char x = callsign[slash_pos + 1];
891+
if(x >= 48 && x <= 57)
892+
{
893+
x -= 48;
894+
}
895+
else if(x >= 65 && x <= 90)
896+
{
897+
x -= 55;
898+
}
899+
else
900+
{
901+
x = 38;
902+
}
903+
904+
m = 60000 - 32768 + x;
905+
906+
m = (m * 128) + power + 2 + 64;
907+
908+
}
909+
else if(callsign[slash_pos + 3] == ' ' || callsign[slash_pos + 3] == 0)
910+
{
911+
// Two-digit numerical suffix
912+
char base_call[7];
913+
memset(base_call, 0, 7);
914+
strncpy(base_call, callsign, slash_pos);
915+
for(i = 0; i < 6; i++)
916+
{
917+
base_call[i] = toupper(base_call[i]);
918+
if(!(isdigit(base_call[i]) || isupper(base_call[i])))
919+
{
920+
base_call[i] = ' ';
921+
}
922+
}
923+
pad_callsign(base_call);
924+
925+
n = wspr_code(base_call[0]);
926+
n = n * 36 + wspr_code(base_call[1]);
927+
n = n * 10 + wspr_code(base_call[2]);
928+
n = n * 27 + (wspr_code(base_call[3]) - 10);
929+
n = n * 27 + (wspr_code(base_call[4]) - 10);
930+
n = n * 27 + (wspr_code(base_call[5]) - 10);
931+
932+
// TODO: needs validation of digit
933+
m = 10 * (callsign[slash_pos + 1] - 48) + callsign[slash_pos + 2] - 48;
934+
m = 60000 + 26 + m;
935+
m = (m * 128) + power + 2 + 64;
936+
}
937+
else
938+
{
939+
// Prefix
940+
char prefix[4];
941+
char base_call[7];
942+
memset(prefix, 0, 4);
943+
memset(base_call, 0, 7);
944+
strncpy(prefix, callsign, slash_pos);
945+
strncpy(base_call, callsign + slash_pos + 1, 7);
946+
947+
if(prefix[2] == ' ' || prefix[2] == 0)
948+
{
949+
// Right align prefix
950+
prefix[3] = 0;
951+
prefix[2] = prefix[1];
952+
prefix[1] = prefix[0];
953+
prefix[0] = ' ';
954+
}
955+
956+
for(uint8_t i = 0; i < 6; i++)
957+
{
958+
base_call[i] = toupper(base_call[i]);
959+
if(!(isdigit(base_call[i]) || isupper(base_call[i])))
960+
{
961+
base_call[i] = ' ';
962+
}
963+
}
964+
pad_callsign(base_call);
965+
966+
n = wspr_code(base_call[0]);
967+
n = n * 36 + wspr_code(base_call[1]);
968+
n = n * 10 + wspr_code(base_call[2]);
969+
n = n * 27 + (wspr_code(base_call[3]) - 10);
970+
n = n * 27 + (wspr_code(base_call[4]) - 10);
971+
n = n * 27 + (wspr_code(base_call[5]) - 10);
972+
973+
m = 0;
974+
for(uint8_t i = 0; i < 3; ++i)
975+
{
976+
m = 37 * m + wspr_code(prefix[i]);
977+
}
978+
979+
if(m >= 32768)
980+
{
981+
m -= 32768;
982+
m = (m * 128) + power + 2 + 64;
983+
}
984+
else
985+
{
986+
m = (m * 128) + power + 1 + 64;
987+
}
988+
}
989+
}
990+
991+
// Callsign is 28 bits, locator/power is 22 bits.
809992
// A little less work to start with the least-significant bits
810993
c[3] = (uint8_t)((n & 0x0f) << 4);
811994
n = n >> 4;
@@ -1387,3 +1570,21 @@ uint8_t JTEncode::crc8(const char * text)
13871570

13881571
return crc;
13891572
}
1573+
1574+
void JTEncode::pad_callsign(char * call)
1575+
{
1576+
// If only the 2nd character is a digit, then pad with a space.
1577+
// If this happens, then the callsign will be truncated if it is
1578+
// longer than 5 characters.
1579+
if(isdigit(call[1]) && isupper(call[2]))
1580+
{
1581+
memmove(call + 1, call, 5);
1582+
call[0] = ' ';
1583+
}
1584+
1585+
// Now the 3rd charcter in the callsign must be a digit
1586+
// if(call[2] < '0' || call[2] > '9')
1587+
// {
1588+
// // return 1;
1589+
// }
1590+
}

0 commit comments

Comments
 (0)