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
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)
533535uint8_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