kopia lustrzana https://github.com/kgoba/ft8_lib
non-standard callsigns; special CQ; field type annotation (#47)
* Fix CLOCK_REALTIME * Get Makefile working with both C and C++ compilers We need HAVE_STPCPY defined to avoid duplicating it. We shouldn't depend on CPPFLAGS if building with gcc. It may be necessary to build with a C++ compiler when using portaudio or integrating into a C++ project. Now it builds either way. * Build static lib by default; FT8_DEBUG for ASAN and debug symbols kiss_fft is omitted for now: not every program needs it, and the object file can still be linked separately, as before. `FT8_DEBUG=1 make` builds with `-fsanitize=address -ggdb3` and without `-O3` for debugging purposes. * copy_token(): save space for the null terminator If the given string is longer than the given length (available space), copy one less than the length and then append a null terminator. This guarantees that token will always be null-terminated, to avoid buffer overflow. For example in ftx_message_encode(), if message_text is excessively-long free text with no spaces, we attempt to copy it to call_to, which is 12 bytes long. We must copy only 11 bytes, so that it can be null-terminated. (In this case call_to is not really used anyway: it's not a callsign, and after failing the other encodings, we will call ftx_message_encode_free(msg, message_text), and then fail because it's too long. But don't crash on the way, either.) Discovered with ASAN. * Fix ftx_message_print() * Add support of non-standard callsigns with prefixes Closes kgoba/ft8_lib#44 * ftx_message_encode_free(): set all payload bytes; debug parsed words If a ftx_message_t struct is reused for all outgoing messages, it may have leftover i3.n3 values. Make sure to overwrite those. (Although, "telemetry" should really be type 0.5; but we are reusing ftx_message_encode_telemetry() in ftx_message_encode_free(), so I guess the caller who calls ftx_message_encode_telemetry() should set that byte itself.) Also output the "parsed '...' ..." debug log message before checking for errors, to help clarify why the error occurred. * Decode message field types and offsets The user of this library should not need to re-tokenize the plain-text message and guess again which field is a callsign, special-token, grid etc.: it's redundant and relatively failure-prone, whereas libft8 already has all the information. It already needed to decode individual tokens out of the bitstream and construct plain text from that. But some users (such as sbitx) color-code the message, and IMO it's best not to invent some ad-hoc markup language. So the message is kept as plain text, and its "metadata" is kept separate, in the form of a start position and a type enum for each recognizable span found in the message text. It could be argued that this could go further, such as splitting "CQ POTA" into two fields, FTX_FIELD_TOKEN and FTX_FIELD_TOKEN_ARG; however, the internal model so far that a std or nonstd message can contain only 3 fields, so we treat the whole thing as a "callsign", then pack28() detects CQ and calls parse_cq_modifier(). And FTX_MAX_MESSAGE_FIELDS is 3 because of this. Likewise, we could separate FTX_FIELD_CALL_TO and FTX_FIELD_CALL_DE: but they have positional meaning anyway. This could be reexamined later. * Use type 4 message for nonstandard CQ; get tests passing We need trim_brackets() for the tests, and it may be useful in user programs too. When a CHECK(this == that) test fails, it's helpful to see the values that were not equal; so add and use the CHECK_EQ_VAL(this, that) macro. A failed test needs to end with an extra newline to separate the output from the next test. And FAIL! is subtly better than FAIL: for searching in the terminal (also, I have muscle memory for it, from Qt work). * Fall back to free text to encode more than 3 tokens "TNX BOB 73 GL" (an example from WSJT-X tests) contains 4 tokens (which are not callsigns), but ft8_lib would have previously tried to compose a standard type 1 message. If a program that uses ft8_lib has paid attention to the field types and offsets from previous messages, and has UI for the user to select (for example) a FTX_FIELD_CALL field and start a new QSO, then it has enough information to call a type-specific function. pack_basecall() can be used to disambiguate whether the callsign is standard or not: then the program can call ftx_message_encode_std(), or ftx_message_encode_nonstd() if it detects a non-standard callsign or if ftx_message_encode_std() fails. Likewise, it can call ftx_message_encode_free() if it already knows the message must be sent as free text, or as a fallback if the other encode functions fail. ftx_message_encode() does this fallback sequence already, but it is not given field types to indicate what the message contains, so it could potentially make a mistake (e.g. with "THX BOB 73"). But it is retained for backwards compatibility, and also because it's easier to use; and now it gets more cases right than it did before. * Add pack_basecall() to public API A program could use it to pre-check whether a callsign is standard or not. (But if it's not standard, it may not be a callsign at all.) * Fix CQ_nnn and CQ_a[bcd] encoding Figured out how by reading subroutine pack28(c13,n28) in WSJT-X. The decoding already works, it seems. We now use space as the delimiter between CQ and the modifier, rather than underscore. An end user composing a message by hand would use space, so it must be detectable that way; and BTW CQ may also be a callsign prefix (Portugal, Azores, Madeira), so we cannot simply detect CQ without checking the following delimiter, and assume that it's a CQ message. nnn and a[bcd] are unlike callsigns (a CQ modifier can have letters OR numbers, but not both, whereas a callsign ALWAYS has both); so we can reliably detect modifiers without needing underscore as a delimiter. --------- Co-authored-by: Georgy Dyuldin <g.dyuldin@gmail.com>master
rodzic
50ee0c0636
commit
9fec6ca398
37
Makefile
37
Makefile
|
|
@ -9,15 +9,20 @@ COMMON_OBJ = $(patsubst %.c,$(BUILD_DIR)/%.o,$(COMMON_SRC))
|
|||
FFT_SRC = $(wildcard fft/*.c)
|
||||
FFT_OBJ = $(patsubst %.c,$(BUILD_DIR)/%.o,$(FFT_SRC))
|
||||
|
||||
TARGETS = gen_ft8 decode_ft8 test_ft8
|
||||
TARGETS = libft8.a gen_ft8 decode_ft8 test_ft8
|
||||
|
||||
CFLAGS = -fsanitize=address -O3 -ggdb3
|
||||
CPPFLAGS = -std=c11 -I.
|
||||
ifdef FT8_DEBUG
|
||||
CFLAGS = -fsanitize=address -ggdb3 -DHAVE_STPCPY -I. -DFTX_DEBUG_PRINT
|
||||
LDFLAGS = -fsanitize=address -lm
|
||||
else
|
||||
CFLAGS = -O3 -DHAVE_STPCPY -I.
|
||||
LDFLAGS = -lm
|
||||
endif
|
||||
|
||||
# Optionally, use Portaudio for live audio input
|
||||
# Portaudio is a C++ library, so then you need to set CC=clang++ or CC=g++
|
||||
ifdef PORTAUDIO_PREFIX
|
||||
CPPFLAGS += -DUSE_PORTAUDIO -I$(PORTAUDIO_PREFIX)/include
|
||||
CFLAGS += -DUSE_PORTAUDIO -I$(PORTAUDIO_PREFIX)/include
|
||||
LDFLAGS += -lportaudio -L$(PORTAUDIO_PREFIX)/lib
|
||||
endif
|
||||
|
||||
|
|
@ -26,24 +31,28 @@ endif
|
|||
all: $(TARGETS)
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILD_DIR) $(TARGETS)
|
||||
rm -rf $(BUILD_DIR) $(TARGETS)
|
||||
|
||||
run_tests: test_ft8
|
||||
@./test_ft8
|
||||
|
||||
install:
|
||||
$(AR) rc libft8.a $(FT8_OBJ) $(COMMON_OBJ)
|
||||
install: libft8.a
|
||||
install libft8.a /usr/lib/libft8.a
|
||||
|
||||
gen_ft8: $(BUILD_DIR)/demo/gen_ft8.o $(FT8_OBJ) $(COMMON_OBJ) $(FFT_OBJ)
|
||||
$(CC) $(LDFLAGS) -o $@ $^
|
||||
gen_ft8: $(BUILD_DIR)/demo/gen_ft8.o libft8.a
|
||||
$(CC) $(CFLAGS) -o $@ .build/demo/gen_ft8.o -lft8 -L. -lm
|
||||
|
||||
decode_ft8: $(BUILD_DIR)/demo/decode_ft8.o $(FT8_OBJ) $(COMMON_OBJ) $(FFT_OBJ)
|
||||
$(CC) $(LDFLAGS) -o $@ $^
|
||||
decode_ft8: $(BUILD_DIR)/demo/decode_ft8.o libft8.a $(FFT_OBJ)
|
||||
$(CC) $(CFLAGS) -o $@ $(BUILD_DIR)/demo/decode_ft8.o $(FFT_OBJ) -lft8 -L. -lm
|
||||
|
||||
test_ft8: $(BUILD_DIR)/test/test.o $(FT8_OBJ)
|
||||
$(CC) $(LDFLAGS) -o $@ $^
|
||||
test_ft8: $(BUILD_DIR)/test/test.o libft8.a
|
||||
$(CC) $(CFLAGS) -o $@ .build/test/test.o -lft8 -L. -lm
|
||||
|
||||
$(BUILD_DIR)/%.o: %.c
|
||||
@mkdir -p $(dir $@)
|
||||
$(CC) $(CFLAGS) $(CPPFLAGS) -o $@ -c $^
|
||||
$(CC) $(CFLAGS) -o $@ -c $^
|
||||
|
||||
lib: libft8.a
|
||||
|
||||
libft8.a: $(FT8_OBJ) $(COMMON_OBJ)
|
||||
$(AR) rc libft8.a $(FT8_OBJ) $(COMMON_OBJ)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#define _POSIX_C_SOURCE 199309L
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
|
@ -212,7 +213,8 @@ void decode(const monitor_t* mon, struct tm* tm_slot_start)
|
|||
++num_decoded;
|
||||
|
||||
char text[FTX_MAX_MESSAGE_LENGTH];
|
||||
ftx_message_rc_t unpack_status = ftx_message_decode(&message, &hash_if, text);
|
||||
ftx_message_offsets_t offsets;
|
||||
ftx_message_rc_t unpack_status = ftx_message_decode(&message, &hash_if, text, &offsets);
|
||||
if (unpack_status != FTX_MESSAGE_RC_OK)
|
||||
{
|
||||
snprintf(text, sizeof(text), "Error [%d] while unpacking!", (int)unpack_status);
|
||||
|
|
|
|||
323
ft8/message.c
323
ft8/message.c
|
|
@ -12,7 +12,6 @@
|
|||
|
||||
////////////////////////////////////////////////////// Static function prototypes //////////////////////////////////////////////////////////////
|
||||
|
||||
static bool trim_brackets(char* result, const char* original, int length);
|
||||
static void add_brackets(char* result, const char* original, int length);
|
||||
|
||||
/// Compute hash value for a callsign and save it in a hash table via the provided callsign hash interface.
|
||||
|
|
@ -25,7 +24,8 @@ static void add_brackets(char* result, const char* original, int length);
|
|||
static bool save_callsign(const ftx_callsign_hash_interface_t* hash_if, const char* callsign, uint32_t* n22_out, uint16_t* n12_out, uint16_t* n10_out);
|
||||
static bool lookup_callsign(const ftx_callsign_hash_interface_t* hash_if, ftx_callsign_hash_type_t hash_type, uint32_t hash, char* callsign);
|
||||
|
||||
static int32_t pack_basecall(const char* callsign, int length);
|
||||
/// returns the numeric value if it matches "CQ nnn" or "CQ a[bcd]", otherwise -1
|
||||
static int parse_cq_modifier(const char* string);
|
||||
|
||||
/// Pack a special token, a 22-bit hash code, or a valid base call into a 29-bit integer.
|
||||
static int32_t pack28(const char* callsign, const ftx_callsign_hash_interface_t* hash_if, uint8_t* ip);
|
||||
|
|
@ -35,7 +35,7 @@ static int32_t pack28(const char* callsign, const ftx_callsign_hash_interface_t*
|
|||
/// @param[in] i3 Payload type (3 bits), 1 or 2
|
||||
/// @param[in] hash_if Callsign hash table interface (can be NULL)
|
||||
/// @param[out] result Unpacked callsign (max size: 13 characters including the terminating \0)
|
||||
static int unpack28(uint32_t n28, uint8_t ip, uint8_t i3, const ftx_callsign_hash_interface_t* hash_if, char* result);
|
||||
static int unpack28(uint32_t n28, uint8_t ip, uint8_t i3, const ftx_callsign_hash_interface_t* hash_if, char* result, ftx_field_t* field_type);
|
||||
|
||||
/// Pack a non-standard base call into a 28-bit integer.
|
||||
static bool pack58(const ftx_callsign_hash_interface_t* hash_if, const char* callsign, uint64_t* n58);
|
||||
|
|
@ -44,7 +44,7 @@ static bool pack58(const ftx_callsign_hash_interface_t* hash_if, const char* cal
|
|||
static bool unpack58(uint64_t n58, const ftx_callsign_hash_interface_t* hash_if, char* callsign);
|
||||
|
||||
static uint16_t packgrid(const char* grid4);
|
||||
static int unpackgrid(uint16_t igrid4, uint8_t ir, char* extra);
|
||||
static int unpackgrid(uint16_t igrid4, uint8_t ir, char* extra, ftx_field_t* extra_field_type);
|
||||
|
||||
/////////////////////////////////////////////////////////// Exported functions /////////////////////////////////////////////////////////////////
|
||||
|
||||
|
|
@ -120,35 +120,70 @@ ftx_message_rc_t ftx_message_encode(ftx_message_t* msg, ftx_callsign_hash_interf
|
|||
char extra[20];
|
||||
|
||||
const char* parse_position = message_text;
|
||||
parse_position = copy_token(call_to, 12, parse_position);
|
||||
parse_position = copy_token(call_de, 12, parse_position);
|
||||
parse_position = copy_token(extra, 20, parse_position);
|
||||
const bool is_cq = starts_with(message_text, "CQ ");
|
||||
if (is_cq) {
|
||||
parse_position += 3;
|
||||
memset(call_to, 0, sizeof(call_to));
|
||||
|
||||
if (call_to[11] != '\0')
|
||||
// copy the next token temporarily (for the debug message)
|
||||
copy_token(call_de, 12, parse_position);
|
||||
LOG(LOG_DEBUG, "next token after CQ: '%s' in '%s'\n", call_de, message_text);
|
||||
|
||||
// see if the word after CQ matches the a[bcd] or nnn pattern, and append to call_to
|
||||
int cq_modifier_v = parse_cq_modifier(message_text);
|
||||
if (cq_modifier_v >= 0) {
|
||||
// treat "CQ nnn" or "CQ a[bcd]" as a single token:
|
||||
// copy the CQ and then the next token to call_to
|
||||
memcpy(call_to, "CQ \0", 4);
|
||||
parse_position = copy_token(call_to + 3, sizeof(call_to) - 3, parse_position);
|
||||
LOG(LOG_DEBUG, "CQ modifier encoding %d; parse_pos after CQ: %s in %s\n", cq_modifier_v, parse_position, message_text);
|
||||
} else {
|
||||
memcpy(call_to, "CQ\0", 3);
|
||||
// the next token should be call_de, which we will get below
|
||||
}
|
||||
} else {
|
||||
// else it's not a CQ: expect first token to be the "to" callsign
|
||||
parse_position = copy_token(call_to, sizeof(call_to), parse_position);
|
||||
}
|
||||
// now we are fairly sure the next word should be the "de" callsign
|
||||
parse_position = copy_token(call_de, sizeof(call_de), parse_position);
|
||||
// and the word after that may be a grid or signal report
|
||||
parse_position = copy_token(extra, sizeof(extra), parse_position);
|
||||
|
||||
LOG(LOG_DEBUG, "ftx_message_encode: parsed '%s' '%s' '%s'; remaining chars '%s'\n", call_to, call_de, extra, parse_position);
|
||||
|
||||
if (call_to[sizeof(call_to) - 1] != '\0')
|
||||
{
|
||||
// token too long
|
||||
return FTX_MESSAGE_RC_ERROR_CALLSIGN1;
|
||||
}
|
||||
if (call_de[11] != '\0')
|
||||
if (call_de[sizeof(call_de) - 1] != '\0')
|
||||
{
|
||||
// token too long
|
||||
return FTX_MESSAGE_RC_ERROR_CALLSIGN2;
|
||||
}
|
||||
if (extra[19] != '\0')
|
||||
if (extra[sizeof(extra) - 1] != '\0')
|
||||
{
|
||||
// token too long
|
||||
return FTX_MESSAGE_RC_ERROR_GRID;
|
||||
}
|
||||
|
||||
ftx_message_rc_t rc;
|
||||
rc = ftx_message_encode_std(msg, hash_if, call_to, call_de, extra);
|
||||
if (!parse_position[0]) {
|
||||
// up to 3 tokens with no leftovers
|
||||
rc = ftx_message_encode_std(msg, hash_if, call_to, call_de, extra);
|
||||
if (rc == FTX_MESSAGE_RC_OK)
|
||||
return rc;
|
||||
LOG(LOG_DEBUG, " ftx_message_encode_std failed: %d\n", rc);
|
||||
rc = ftx_message_encode_nonstd(msg, hash_if, call_to, call_de, extra);
|
||||
if (rc == FTX_MESSAGE_RC_OK)
|
||||
return rc;
|
||||
LOG(LOG_DEBUG, " ftx_message_encode_nonstd failed: %d\n", rc);
|
||||
}
|
||||
rc = ftx_message_encode_free(msg, message_text);
|
||||
if (rc == FTX_MESSAGE_RC_OK)
|
||||
return rc;
|
||||
rc = ftx_message_encode_nonstd(msg, hash_if, call_to, call_de, extra);
|
||||
if (rc == FTX_MESSAGE_RC_OK)
|
||||
return rc;
|
||||
|
||||
// rc = ftx_message_encode_telemetry_hex(msg, hash_if, call_to, call_de, extra);
|
||||
LOG(LOG_DEBUG, " ftx_message_encode_free failed: %d\n", rc);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
|
@ -157,9 +192,11 @@ ftx_message_rc_t ftx_message_encode_std(ftx_message_t* msg, ftx_callsign_hash_in
|
|||
{
|
||||
uint8_t ipa, ipb;
|
||||
|
||||
LOG(LOG_DEBUG, "ftx_message_encode_std '%s' '%s' '%s'\n", call_to, call_de, extra);
|
||||
|
||||
int32_t n28a = pack28(call_to, hash_if, &ipa);
|
||||
int32_t n28b = pack28(call_de, hash_if, &ipb);
|
||||
LOG(LOG_DEBUG, "n29a = %d, n29b = %d\n", n28a, n28b);
|
||||
LOG(LOG_DEBUG, " n29a = %d, n29b = %d\n", n28a, n28b);
|
||||
|
||||
if (n28a < 0)
|
||||
return FTX_MESSAGE_RC_ERROR_CALLSIGN1;
|
||||
|
|
@ -176,6 +213,13 @@ ftx_message_rc_t ftx_message_encode_std(ftx_message_t* msg, ftx_callsign_hash_in
|
|||
}
|
||||
}
|
||||
|
||||
char* slash_de = strchr(call_de, '/');
|
||||
uint8_t icq = (uint8_t)equals(call_to, "CQ") || starts_with(call_to, "CQ ");
|
||||
if (slash_de && (slash_de - call_de >= 2) && icq && !(equals(slash_de, "/P") || equals(slash_de, "/R")))
|
||||
{
|
||||
return FTX_MESSAGE_RC_ERROR_CALLSIGN2; // nonstandard call: need a type 4 message
|
||||
}
|
||||
|
||||
uint16_t igrid4 = packgrid(extra);
|
||||
LOG(LOG_DEBUG, "igrid4 = %d\n", igrid4);
|
||||
|
||||
|
|
@ -211,19 +255,12 @@ ftx_message_rc_t ftx_message_encode_nonstd(ftx_message_t* msg, ftx_callsign_hash
|
|||
{
|
||||
uint8_t i3 = 4;
|
||||
|
||||
uint8_t icq = (uint8_t)equals(call_to, "CQ");
|
||||
LOG(LOG_DEBUG, "ftx_message_encode_nonstd '%s' '%s' '%s'\n", call_to, call_de, extra);
|
||||
|
||||
uint8_t icq = (uint8_t)equals(call_to, "CQ") || starts_with(call_to, "CQ ");
|
||||
int len_call_to = strlen(call_to);
|
||||
int len_call_de = strlen(call_de);
|
||||
|
||||
// if ((icq != 0) || (pack_basecall(call_to, len_call_to) >= 0))
|
||||
// {
|
||||
// if (pack_basecall(call_de, len_call_de) >= 0)
|
||||
// {
|
||||
// // no need for encode_nonstd, should use encode_std
|
||||
// return FTX_MESSAGE_RC_ERROR_CALLSIGN2;
|
||||
// }
|
||||
// }
|
||||
|
||||
if ((icq == 0) && ((len_call_to < 3)))
|
||||
return FTX_MESSAGE_RC_ERROR_CALLSIGN1;
|
||||
if ((len_call_de < 3))
|
||||
|
|
@ -258,6 +295,7 @@ ftx_message_rc_t ftx_message_encode_nonstd(ftx_message_t* msg, ftx_callsign_hash
|
|||
iflip = 0;
|
||||
n12 = 0;
|
||||
call58 = call_de;
|
||||
LOG(LOG_DEBUG, "CQ: 58-bit '%s'; omitting grid '%s'\n", call58, extra);
|
||||
}
|
||||
|
||||
if (!pack58(hash_if, call58, &n58))
|
||||
|
|
@ -293,7 +331,62 @@ ftx_message_rc_t ftx_message_encode_nonstd(ftx_message_t* msg, ftx_callsign_hash
|
|||
return FTX_MESSAGE_RC_OK;
|
||||
}
|
||||
|
||||
ftx_message_rc_t ftx_message_decode(const ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, char* message)
|
||||
ftx_message_rc_t ftx_message_encode_free(ftx_message_t* msg, const char* text)
|
||||
{
|
||||
uint8_t str_len = strlen(text);
|
||||
if (str_len > 13)
|
||||
{
|
||||
// Too long text
|
||||
return FTX_MESSAGE_RC_ERROR_TYPE;
|
||||
}
|
||||
|
||||
uint8_t b71[9];
|
||||
memset(b71, 0, 9);
|
||||
int8_t cid;
|
||||
char c;
|
||||
|
||||
for (int idx = 0; idx < 13; idx++)
|
||||
{
|
||||
if (idx < str_len)
|
||||
{
|
||||
c = text[idx];
|
||||
}
|
||||
else
|
||||
{
|
||||
c = ' ';
|
||||
}
|
||||
cid = nchar(c, FT8_CHAR_TABLE_FULL);
|
||||
if (cid == -1)
|
||||
{
|
||||
return FTX_MESSAGE_RC_ERROR_TYPE;
|
||||
}
|
||||
uint16_t rem = cid;
|
||||
for (int i = 8; i >= 0; i--)
|
||||
{
|
||||
rem += b71[i] * 42;
|
||||
b71[i] = rem & 0xff;
|
||||
rem = rem >> 8;
|
||||
}
|
||||
}
|
||||
ftx_message_rc_t ret = ftx_message_encode_telemetry(msg, b71);
|
||||
msg->payload[9] = 0; // i3.n3 = 0.0; etc.
|
||||
return ret;
|
||||
}
|
||||
|
||||
// TODO set byte 9? or must the caller do it?
|
||||
ftx_message_rc_t ftx_message_encode_telemetry(ftx_message_t* msg, const uint8_t* telemetry)
|
||||
{
|
||||
// Shift bits in telemetry left by 1 bit to right-align the data
|
||||
uint8_t carry = 0;
|
||||
for (int i = 8; i >= 0; --i)
|
||||
{
|
||||
msg->payload[i] = (telemetry[i] << 1) | (carry >> 7);
|
||||
carry = telemetry[i] & 0x80;
|
||||
}
|
||||
return FTX_MESSAGE_RC_OK;
|
||||
}
|
||||
|
||||
ftx_message_rc_t ftx_message_decode(const ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, char* message, ftx_message_offsets_t* offsets)
|
||||
{
|
||||
ftx_message_rc_t rc;
|
||||
|
||||
|
|
@ -303,15 +396,20 @@ ftx_message_rc_t ftx_message_decode(const ftx_message_t* msg, ftx_callsign_hash_
|
|||
char* field3 = buf + 14 + 14;
|
||||
|
||||
message[0] = '\0';
|
||||
for (int i = 0; i < FTX_MAX_MESSAGE_FIELDS; ++i)
|
||||
{
|
||||
offsets->types[i] = FTX_FIELD_UNKNOWN;
|
||||
offsets->offsets[i] = -1;
|
||||
}
|
||||
|
||||
ftx_message_type_t msg_type = ftx_message_get_type(msg);
|
||||
switch (msg_type)
|
||||
{
|
||||
case FTX_MESSAGE_TYPE_STANDARD:
|
||||
rc = ftx_message_decode_std(msg, hash_if, field1, field2, field3);
|
||||
rc = ftx_message_decode_std(msg, hash_if, field1, field2, field3, offsets->types);
|
||||
break;
|
||||
case FTX_MESSAGE_TYPE_NONSTD_CALL:
|
||||
rc = ftx_message_decode_nonstd(msg, hash_if, field1, field2, field3);
|
||||
rc = ftx_message_decode_nonstd(msg, hash_if, field1, field2, field3, offsets->types);
|
||||
break;
|
||||
case FTX_MESSAGE_TYPE_FREE_TEXT:
|
||||
ftx_message_decode_free(msg, field1);
|
||||
|
|
@ -335,14 +433,18 @@ ftx_message_rc_t ftx_message_decode(const ftx_message_t* msg, ftx_callsign_hash_
|
|||
if (field1 != NULL)
|
||||
{
|
||||
// TODO join fields via whitespace
|
||||
const char* message_start = message;
|
||||
message = append_string(message, field1);
|
||||
offsets->offsets[0] = 0;
|
||||
if (field2 != NULL)
|
||||
{
|
||||
message = append_string(message, " ");
|
||||
offsets->offsets[1] = message - message_start;
|
||||
message = append_string(message, field2);
|
||||
if (field3 != NULL)
|
||||
if ((field3 != NULL) && (field3[0] != 0))
|
||||
{
|
||||
message = append_string(message, " ");
|
||||
offsets->offsets[2] = message - message_start;
|
||||
message = append_string(message, field3);
|
||||
}
|
||||
}
|
||||
|
|
@ -351,7 +453,8 @@ ftx_message_rc_t ftx_message_decode(const ftx_message_t* msg, ftx_callsign_hash_
|
|||
return rc;
|
||||
}
|
||||
|
||||
ftx_message_rc_t ftx_message_decode_std(const ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, char* call_to, char* call_de, char* extra)
|
||||
ftx_message_rc_t ftx_message_decode_std(const ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if,
|
||||
char* call_to, char* call_de, char* extra, ftx_field_t field_types[FTX_MAX_MESSAGE_FIELDS])
|
||||
{
|
||||
uint32_t n29a, n29b;
|
||||
uint16_t igrid4;
|
||||
|
|
@ -379,15 +482,15 @@ ftx_message_rc_t ftx_message_decode_std(const ftx_message_t* msg, ftx_callsign_h
|
|||
call_to[0] = call_de[0] = extra[0] = '\0';
|
||||
|
||||
// Unpack both callsigns
|
||||
if (unpack28(n29a >> 1, n29a & 1u, i3, hash_if, call_to) < 0)
|
||||
if (unpack28(n29a >> 1, n29a & 1u, i3, hash_if, call_to, &field_types[0]) < 0)
|
||||
{
|
||||
return FTX_MESSAGE_RC_ERROR_CALLSIGN1;
|
||||
}
|
||||
if (unpack28(n29b >> 1, n29b & 1u, i3, hash_if, call_de) < 0)
|
||||
if (unpack28(n29b >> 1, n29b & 1u, i3, hash_if, call_de, &field_types[1]) < 0)
|
||||
{
|
||||
return FTX_MESSAGE_RC_ERROR_CALLSIGN2;
|
||||
}
|
||||
if (unpackgrid(igrid4, ir, extra) < 0)
|
||||
if (unpackgrid(igrid4, ir, extra, &field_types[2]) < 0)
|
||||
{
|
||||
return FTX_MESSAGE_RC_ERROR_GRID;
|
||||
}
|
||||
|
|
@ -397,7 +500,8 @@ ftx_message_rc_t ftx_message_decode_std(const ftx_message_t* msg, ftx_callsign_h
|
|||
}
|
||||
|
||||
// non-standard messages, code originally by KD8CEC
|
||||
ftx_message_rc_t ftx_message_decode_nonstd(const ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, char* call_to, char* call_de, char* extra)
|
||||
ftx_message_rc_t ftx_message_decode_nonstd(const ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if,
|
||||
char* call_to, char* call_de, char* extra, ftx_field_t field_types[FTX_MAX_MESSAGE_FIELDS])
|
||||
{
|
||||
uint16_t n12, iflip, nrpt, icq;
|
||||
uint64_t n58;
|
||||
|
|
@ -437,22 +541,37 @@ ftx_message_rc_t ftx_message_decode_nonstd(const ftx_message_t* msg, ftx_callsig
|
|||
if (icq == 0)
|
||||
{
|
||||
strcpy(call_to, call_1);
|
||||
field_types[0] = FTX_FIELD_CALL;
|
||||
if (nrpt == 1)
|
||||
{
|
||||
strcpy(extra, "RRR");
|
||||
field_types[2] = FTX_FIELD_TOKEN;
|
||||
}
|
||||
else if (nrpt == 2)
|
||||
{
|
||||
strcpy(extra, "RR73");
|
||||
field_types[2] = FTX_FIELD_TOKEN;
|
||||
}
|
||||
else if (nrpt == 3)
|
||||
{
|
||||
strcpy(extra, "73");
|
||||
field_types[2] = FTX_FIELD_TOKEN;
|
||||
}
|
||||
else
|
||||
{
|
||||
extra[0] = '\0';
|
||||
field_types[2] = FTX_FIELD_NONE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
strcpy(call_to, "CQ");
|
||||
extra[0] = '\0';
|
||||
field_types[0] = FTX_FIELD_TOKEN;
|
||||
field_types[2] = FTX_FIELD_NONE;
|
||||
}
|
||||
strcpy(call_de, call_2);
|
||||
|
||||
field_types[1] = FTX_FIELD_CALL;
|
||||
LOG(LOG_INFO, "Decoded non-standard (type %d) message [%s] [%s] [%s]\n", i3, call_to, call_de, extra);
|
||||
return FTX_MESSAGE_RC_OK;
|
||||
}
|
||||
|
|
@ -518,11 +637,11 @@ void ftx_message_decode_telemetry(const ftx_message_t* msg, uint8_t* telemetry)
|
|||
void ftx_message_print(ftx_message_t* msg)
|
||||
{
|
||||
printf("[");
|
||||
for (int i = 0; i < PAYLOAD_LENGTH_BYTES; ++i)
|
||||
for (int i = 0; i < FTX_PAYLOAD_LENGTH_BYTES; ++i)
|
||||
{
|
||||
if (i > 0)
|
||||
printf(" ");
|
||||
printf("%02x", msg->payload[i]));
|
||||
printf("%02x", msg->payload[i]);
|
||||
}
|
||||
printf("]");
|
||||
}
|
||||
|
|
@ -530,22 +649,6 @@ void ftx_message_print(ftx_message_t* msg)
|
|||
|
||||
/////////////////////////////////////////////////////////// Static functions /////////////////////////////////////////////////////////////////
|
||||
|
||||
static bool trim_brackets(char* result, const char* original, int length)
|
||||
{
|
||||
if (original[0] == '<' && original[length - 1] == '>')
|
||||
{
|
||||
memcpy(result, original + 1, length - 2);
|
||||
result[length - 2] = '\0';
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy(result, original, length);
|
||||
result[length] = '\0';
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void add_brackets(char* result, const char* original, int length)
|
||||
{
|
||||
result[0] = '<';
|
||||
|
|
@ -613,7 +716,7 @@ static bool lookup_callsign(const ftx_callsign_hash_interface_t* hash_if, ftx_ca
|
|||
return found;
|
||||
}
|
||||
|
||||
static int32_t pack_basecall(const char* callsign, int length)
|
||||
int32_t pack_basecall(const char* callsign, int length)
|
||||
{
|
||||
if (length > 2)
|
||||
{
|
||||
|
|
@ -672,6 +775,38 @@ static int32_t pack_basecall(const char* callsign, int length)
|
|||
return -1;
|
||||
}
|
||||
|
||||
// returns the numeric value if it matches CQ_nnn or CQ_a[bcd], otherwise -1
|
||||
static int parse_cq_modifier(const char* string)
|
||||
{
|
||||
int nnum = 0, nlet = 0;
|
||||
|
||||
// encode CQ_nnn or CQ_a[bcd]
|
||||
int m = 0;
|
||||
for (int i = 3; i < 8; ++i) {
|
||||
if (!string[i] || is_space(string[i]))
|
||||
break;
|
||||
else if (is_digit(string[i]))
|
||||
++nnum;
|
||||
else if (is_letter(string[i])) {
|
||||
++nlet;
|
||||
m = 27 * m + (string[i] - 'A' + 1);
|
||||
} else {
|
||||
// non-digit non-letter characters (such as '/') are not allowed
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
LOG(LOG_DEBUG, "CQ_nnn/CQ_a[bcd] '%s' %d/%d\n", string, nnum, nlet);
|
||||
if (nnum == 3 && nlet == 0) {
|
||||
LOG(LOG_DEBUG, "CQ_nnn detected: %d\n", atoi(string + 3));
|
||||
return atoi(string + 3);
|
||||
}
|
||||
else if (nnum == 0 && nlet <= 4) {
|
||||
LOG(LOG_DEBUG, "CQ_a[bcd] detected: m %d\n", m);
|
||||
return 1000 + m;
|
||||
}
|
||||
return -1; // not a special CQ
|
||||
}
|
||||
|
||||
static int32_t pack28(const char* callsign, const ftx_callsign_hash_interface_t* hash_if, uint8_t* ip)
|
||||
{
|
||||
LOG(LOG_DEBUG, "pack28() callsign [%s]\n", callsign);
|
||||
|
|
@ -688,13 +823,14 @@ static int32_t pack28(const char* callsign, const ftx_callsign_hash_interface_t*
|
|||
int length = strlen(callsign);
|
||||
LOG(LOG_DEBUG, "Callsign length = %d\n", length);
|
||||
|
||||
if (starts_with(callsign, "CQ_") && length < 8)
|
||||
if (starts_with(callsign, "CQ ") && length < 8)
|
||||
{
|
||||
int nnum = 0, nlet = 0;
|
||||
|
||||
// TODO: decode CQ_nnn or CQ_abcd
|
||||
LOG(LOG_WARN, "CQ_nnn/CQ_abcd detected, not implemented\n");
|
||||
return -1;
|
||||
int v = parse_cq_modifier(callsign);
|
||||
if (v < 0) {
|
||||
LOG(LOG_WARN, "CQ_nnn/CQ_a[bcd] '%s' not allowed\n", callsign);
|
||||
return -1;
|
||||
}
|
||||
return 3 + v;
|
||||
}
|
||||
|
||||
// Detect /R and /P suffix for basecall check
|
||||
|
|
@ -718,10 +854,12 @@ static int32_t pack28(const char* callsign, const ftx_callsign_hash_interface_t*
|
|||
if ((length >= 3) && (length <= 11))
|
||||
{
|
||||
// Treat this as a nonstandard callsign: compute its 22-bit hash
|
||||
LOG(LOG_DEBUG, "Encoding as non-standard callsign\n");
|
||||
LOG(LOG_DEBUG, "Encoding '%s' as non-standard callsign\n", callsign);
|
||||
uint32_t n22;
|
||||
if (!save_callsign(hash_if, callsign, &n22, NULL, NULL))
|
||||
if (!save_callsign(hash_if, callsign, &n22, NULL, NULL)) {
|
||||
LOG(LOG_DEBUG, " Failed to save '%s'\n", callsign);
|
||||
return -1; // Error (some problem with callsign contents)
|
||||
}
|
||||
*ip = 0;
|
||||
return NTOKENS + n22; // 22-bit hashed callsign
|
||||
}
|
||||
|
|
@ -729,20 +867,29 @@ static int32_t pack28(const char* callsign, const ftx_callsign_hash_interface_t*
|
|||
return -1; // Error
|
||||
}
|
||||
|
||||
static int unpack28(uint32_t n28, uint8_t ip, uint8_t i3, const ftx_callsign_hash_interface_t* hash_if, char* result)
|
||||
static int unpack28(uint32_t n28, uint8_t ip, uint8_t i3, const ftx_callsign_hash_interface_t* hash_if, char* result, ftx_field_t* field_type)
|
||||
{
|
||||
LOG(LOG_DEBUG, "unpack28() n28=%d i3=%d\n", n28, i3);
|
||||
// Check for special tokens DE, QRZ, CQ, CQ_nnn, CQ_aaaa
|
||||
// Check for special tokens DE, QRZ, CQ, CQ nnn, CQ a[bcd]
|
||||
if (n28 < NTOKENS)
|
||||
{
|
||||
if (n28 <= 2u)
|
||||
{
|
||||
if (n28 == 0)
|
||||
{
|
||||
strcpy(result, "DE");
|
||||
*field_type = FTX_FIELD_TOKEN;
|
||||
}
|
||||
else if (n28 == 1)
|
||||
{
|
||||
strcpy(result, "QRZ");
|
||||
else /* if (n28 == 2) */
|
||||
*field_type = FTX_FIELD_TOKEN;
|
||||
}
|
||||
else
|
||||
{ /* if (n28 == 2) */
|
||||
strcpy(result, "CQ");
|
||||
*field_type = FTX_FIELD_TOKEN;
|
||||
}
|
||||
return 0; // Success
|
||||
}
|
||||
if (n28 <= 1002u)
|
||||
|
|
@ -750,6 +897,7 @@ static int unpack28(uint32_t n28, uint8_t ip, uint8_t i3, const ftx_callsign_has
|
|||
// CQ nnn with 3 digits
|
||||
strcpy(result, "CQ ");
|
||||
int_to_dd(result + 3, n28 - 3, 3, false);
|
||||
*field_type = FTX_FIELD_TOKEN_WITH_ARG;
|
||||
return 0; // Success
|
||||
}
|
||||
if (n28 <= 532443ul)
|
||||
|
|
@ -768,7 +916,8 @@ static int unpack28(uint32_t n28, uint8_t ip, uint8_t i3, const ftx_callsign_has
|
|||
}
|
||||
|
||||
strcpy(result, "CQ ");
|
||||
strcat(result, trim_front(aaaa));
|
||||
strcat(result, trim_front(aaaa, ' '));
|
||||
*field_type = FTX_FIELD_TOKEN_WITH_ARG;
|
||||
return 0; // Success
|
||||
}
|
||||
// unspecified
|
||||
|
|
@ -780,6 +929,7 @@ static int unpack28(uint32_t n28, uint8_t ip, uint8_t i3, const ftx_callsign_has
|
|||
{
|
||||
// This is a 22-bit hash of a result
|
||||
lookup_callsign(hash_if, FTX_CALLSIGN_HASH_22_BITS, n28, result);
|
||||
*field_type = FTX_FIELD_CALL;
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
|
|
@ -837,6 +987,7 @@ static int unpack28(uint32_t n28, uint8_t ip, uint8_t i3, const ftx_callsign_has
|
|||
// Save the result to hash table
|
||||
save_callsign(hash_if, result, NULL, NULL, NULL);
|
||||
|
||||
*field_type = FTX_FIELD_CALL;
|
||||
return 0; // Success
|
||||
}
|
||||
|
||||
|
|
@ -940,7 +1091,7 @@ static uint16_t packgrid(const char* grid4)
|
|||
return MAXGRID4 + 1;
|
||||
}
|
||||
|
||||
static int unpackgrid(uint16_t igrid4, uint8_t ir, char* extra)
|
||||
static int unpackgrid(uint16_t igrid4, uint8_t ir, char* extra, ftx_field_t* extra_field_type)
|
||||
{
|
||||
char* dst = extra;
|
||||
|
||||
|
|
@ -962,7 +1113,8 @@ static int unpackgrid(uint16_t igrid4, uint8_t ir, char* extra)
|
|||
dst[1] = 'A' + (n % 18); // A..R
|
||||
n /= 18;
|
||||
dst[0] = 'A' + (n % 18); // A..R
|
||||
// if (ir > 0 && strncmp(call_to, "CQ", 2) == 0) return -1;
|
||||
// if (ir > 0 && strncmp(call_to, "CQ", 2) == 0) return -1;
|
||||
*extra_field_type = FTX_FIELD_GRID;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -970,22 +1122,31 @@ static int unpackgrid(uint16_t igrid4, uint8_t ir, char* extra)
|
|||
int irpt = igrid4 - MAXGRID4;
|
||||
|
||||
// Check special cases first (irpt > 0 always)
|
||||
if (irpt == 1)
|
||||
dst[0] = '\0';
|
||||
else if (irpt == 2)
|
||||
strcpy(dst, "RRR");
|
||||
else if (irpt == 3)
|
||||
strcpy(dst, "RR73");
|
||||
else if (irpt == 4)
|
||||
strcpy(dst, "73");
|
||||
else
|
||||
switch (irpt)
|
||||
{
|
||||
case 1:
|
||||
dst[0] = '\0';
|
||||
*extra_field_type = FTX_FIELD_NONE;
|
||||
break;
|
||||
case 2:
|
||||
strcpy(dst, "RRR");
|
||||
*extra_field_type = FTX_FIELD_TOKEN;
|
||||
break;
|
||||
case 3:
|
||||
strcpy(dst, "RR73");
|
||||
*extra_field_type = FTX_FIELD_TOKEN;
|
||||
break;
|
||||
case 4:
|
||||
strcpy(dst, "73");
|
||||
*extra_field_type = FTX_FIELD_TOKEN;
|
||||
break;
|
||||
default:
|
||||
// Extract signal report as a two digit number with a + or - sign
|
||||
if (ir > 0)
|
||||
{
|
||||
*dst++ = 'R'; // Add "R" before report
|
||||
}
|
||||
int_to_dd(dst, irpt - 35, 2, true);
|
||||
*extra_field_type = FTX_FIELD_RST;
|
||||
break;
|
||||
}
|
||||
// if (irpt >= 2 && strncmp(call_to, "CQ", 2) == 0) return -1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ extern "C"
|
|||
|
||||
#define FTX_PAYLOAD_LENGTH_BYTES 10 ///< number of bytes to hold 77 bits of FTx payload data
|
||||
#define FTX_MAX_MESSAGE_LENGTH 35 ///< max message length = callsign[13] + space + callsign[13] + space + report[6] + terminator
|
||||
#define FTX_MAX_MESSAGE_FIELDS 3 // may need to get longer for multi-part messages (DXpedition, contest etc.)
|
||||
|
||||
/// Structure that holds the decoded message
|
||||
typedef struct
|
||||
|
|
@ -78,6 +79,28 @@ typedef enum
|
|||
FTX_MESSAGE_RC_ERROR_TYPE
|
||||
} ftx_message_rc_t;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
FTX_FIELD_UNKNOWN,
|
||||
FTX_FIELD_NONE,
|
||||
FTX_FIELD_TOKEN, // RRR, RR73, 73, DE, QRZ, CQ, ...
|
||||
FTX_FIELD_TOKEN_WITH_ARG, // CQ nnn, CQ abcd
|
||||
FTX_FIELD_CALL,
|
||||
FTX_FIELD_GRID,
|
||||
FTX_FIELD_RST
|
||||
} ftx_field_t;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
// parallel arrays:
|
||||
// e.g. "CQ POTA W9XYZ AB12" generates
|
||||
// types { FTX_FIELD_TOKEN_WITH_ARG, FTX_FIELD_CALL, FTX_FIELD_CALL_GRID" }
|
||||
// offsets { 0, 8, 14 }
|
||||
// Both arrays end where offsets[i] < 0
|
||||
ftx_field_t types[FTX_MAX_MESSAGE_FIELDS];
|
||||
int16_t offsets[FTX_MAX_MESSAGE_FIELDS];
|
||||
} ftx_message_offsets_t;
|
||||
|
||||
// Callsign types and sizes:
|
||||
// * Std. call (basecall) - 1-2 letter/digit prefix (at least one letter), 1 digit area code, 1-3 letter suffix,
|
||||
// total 3-6 chars (exception: 7 character calls with prefixes 3DA0- and 3XA..3XZ-)
|
||||
|
|
@ -93,7 +116,17 @@ ftx_message_type_t ftx_message_get_type(const ftx_message_t* msg);
|
|||
|
||||
// bool ftx_message_check_recipient(const ftx_message_t* msg, const char* callsign);
|
||||
|
||||
/// Pack (encode) a text message
|
||||
/// Pack (encode) a callsign in the standard way, and return the numeric representation.
|
||||
/// Returns -1 if \a callsign cannot be encoded in the standard way.
|
||||
/// This function can be used to decide whether to call ftx_message_encode_std() or ftx_message_decode_nonstd().
|
||||
/// Alternatively, ftx_message_encode_std() itself fails when one of the callsigns cannot be packed this way.
|
||||
int32_t pack_basecall(const char* callsign, int length);
|
||||
|
||||
/// Pack (encode) a text message, guessing which message type to use and falling back on failure:
|
||||
/// if there are 3 or fewer tokens, try ftx_message_encode_std first,
|
||||
/// then ftx_message_encode_nonstd if that fails because of a non-standard callsign;
|
||||
/// otherwise fall back to ftx_message_encode_free.
|
||||
/// If you already know which type to use, you can call one of those functions directly.
|
||||
ftx_message_rc_t ftx_message_encode(ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, const char* message_text);
|
||||
|
||||
/// Pack Type 1 (Standard 77-bit message) or Type 2 (ditto, with a "/P" call) message
|
||||
|
|
@ -105,13 +138,13 @@ ftx_message_rc_t ftx_message_encode_std(ftx_message_t* msg, ftx_callsign_hash_in
|
|||
/// Pack Type 4 (One nonstandard call and one hashed call) message
|
||||
ftx_message_rc_t ftx_message_encode_nonstd(ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, const char* call_to, const char* call_de, const char* extra);
|
||||
|
||||
void ftx_message_encode_free(const char* text);
|
||||
void ftx_message_encode_telemetry_hex(const char* telemetry_hex);
|
||||
void ftx_message_encode_telemetry(const uint8_t* telemetry);
|
||||
/// Pack plain text, up to 13 characters
|
||||
ftx_message_rc_t ftx_message_encode_free(ftx_message_t* msg, const char* text);
|
||||
ftx_message_rc_t ftx_message_encode_telemetry(ftx_message_t* msg, const uint8_t* telemetry);
|
||||
|
||||
ftx_message_rc_t ftx_message_decode(const ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, char* message);
|
||||
ftx_message_rc_t ftx_message_decode_std(const ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, char* call_to, char* call_de, char* extra);
|
||||
ftx_message_rc_t ftx_message_decode_nonstd(const ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, char* call_to, char* call_de, char* extra);
|
||||
ftx_message_rc_t ftx_message_decode(const ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, char* message, ftx_message_offsets_t* offsets);
|
||||
ftx_message_rc_t ftx_message_decode_std(const ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, char* call_to, char* call_de, char* extra, ftx_field_t field_types[FTX_MAX_MESSAGE_FIELDS]);
|
||||
ftx_message_rc_t ftx_message_decode_nonstd(const ftx_message_t* msg, ftx_callsign_hash_interface_t* hash_if, char* call_to, char* call_de, char* extra, ftx_field_t field_types[FTX_MAX_MESSAGE_FIELDS]);
|
||||
void ftx_message_decode_free(const ftx_message_t* msg, char* text);
|
||||
void ftx_message_decode_telemetry_hex(const ftx_message_t* msg, char* telemetry_hex);
|
||||
void ftx_message_decode_telemetry(const ftx_message_t* msg, uint8_t* telemetry);
|
||||
|
|
|
|||
29
ft8/text.c
29
ft8/text.c
|
|
@ -2,21 +2,21 @@
|
|||
|
||||
#include <string.h>
|
||||
|
||||
const char* trim_front(const char* str)
|
||||
const char* trim_front(const char* str, char to_trim)
|
||||
{
|
||||
// Skip leading whitespace
|
||||
while (*str == ' ')
|
||||
// Skip leading to_trim characters
|
||||
while (*str == to_trim)
|
||||
{
|
||||
str++;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
void trim_back(char* str)
|
||||
void trim_back(char* str, char to_trim)
|
||||
{
|
||||
// Skip trailing whitespace by replacing it with '\0' characters
|
||||
// Skip trailing to_trim characters by replacing them with '\0' characters
|
||||
int idx = strlen(str) - 1;
|
||||
while (idx >= 0 && str[idx] == ' ')
|
||||
while (idx >= 0 && str[idx] == to_trim)
|
||||
{
|
||||
str[idx--] = '\0';
|
||||
}
|
||||
|
|
@ -24,15 +24,23 @@ void trim_back(char* str)
|
|||
|
||||
char* trim(char* str)
|
||||
{
|
||||
str = (char*)trim_front(str);
|
||||
trim_back(str);
|
||||
str = (char*)trim_front(str, ' ');
|
||||
trim_back(str, ' ');
|
||||
// return a pointer to the first non-whitespace character
|
||||
return str;
|
||||
}
|
||||
|
||||
char* trim_brackets(char* str)
|
||||
{
|
||||
str = (char*)trim_front(str, '<');
|
||||
trim_back(str, '>');
|
||||
// return a pointer to the first non-whitespace character
|
||||
return str;
|
||||
}
|
||||
|
||||
void trim_copy(char* trimmed, const char* str)
|
||||
{
|
||||
str = (char*)trim_front(str);
|
||||
str = (char*)trim_front(str, ' ');
|
||||
int len = strlen(str) - 1;
|
||||
while (len >= 0 && str[len] == ' ')
|
||||
{
|
||||
|
|
@ -107,6 +115,7 @@ void fmtmsg(char* msg_out, const char* msg_in)
|
|||
*msg_out = 0; // Add zero termination
|
||||
}
|
||||
|
||||
// Returns pointer to the null terminator within the given string (destination)
|
||||
char* append_string(char* string, const char* token)
|
||||
{
|
||||
while (*token != '\0')
|
||||
|
|
@ -124,7 +133,7 @@ const char* copy_token(char* token, int length, const char* string)
|
|||
// Copy characters until a whitespace character or the end of string
|
||||
while (*string != ' ' && *string != '\0')
|
||||
{
|
||||
if (length > 0)
|
||||
if (length > 1)
|
||||
{
|
||||
*token = *string;
|
||||
token++;
|
||||
|
|
|
|||
10
ft8/text.h
10
ft8/text.h
|
|
@ -11,8 +11,8 @@ extern "C"
|
|||
|
||||
// Utility functions for characters and strings
|
||||
|
||||
const char* trim_front(const char* str);
|
||||
void trim_back(char* str);
|
||||
const char* trim_front(const char* str, char to_trim);
|
||||
void trim_back(char* str, char to_trim);
|
||||
|
||||
/// In-place whitespace trim from front and back:
|
||||
/// 1) trims the back by changing whitespaces to '\0'
|
||||
|
|
@ -23,6 +23,12 @@ char* trim(char* str);
|
|||
/// Trim whitespace from start and end of string
|
||||
void trim_copy(char* trimmed, const char* str);
|
||||
|
||||
/// In-place trim of <> characters from front and back:
|
||||
/// 1) trims the back by changing > to '\0'
|
||||
/// 2) trims the front by skipping <
|
||||
/// @return trimmed string (pointer to first non-whitespace character)
|
||||
char* trim_brackets(char* str);
|
||||
|
||||
char to_upper(char c);
|
||||
bool is_digit(char c);
|
||||
bool is_letter(char c);
|
||||
|
|
|
|||
112
test/test.c
112
test/test.c
|
|
@ -1,3 +1,4 @@
|
|||
#define _POSIX_C_SOURCE 200809L
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
|
@ -101,11 +102,19 @@ void test3() {
|
|||
}
|
||||
*/
|
||||
|
||||
#define CHECK(condition) \
|
||||
if (!(condition)) \
|
||||
{ \
|
||||
printf("FAIL: Condition \'" #condition "' failed!\n"); \
|
||||
return; \
|
||||
#define CHECK(condition) \
|
||||
if (!(condition)) \
|
||||
{ \
|
||||
printf("FAIL! Condition \'" #condition "' failed\n\n"); \
|
||||
return; \
|
||||
}
|
||||
|
||||
#define CHECK_EQ_VAL(this, that) \
|
||||
if ((this) != (that)) \
|
||||
{ \
|
||||
printf("FAIL! Expected " #this " (%d) == " #that " (%d)\n\n", \
|
||||
(this), (that)); \
|
||||
return; \
|
||||
}
|
||||
|
||||
#define TEST_END printf("Test OK\n\n")
|
||||
|
|
@ -172,57 +181,54 @@ ftx_callsign_hash_interface_t hash_if = {
|
|||
.save_hash = hashtable_add
|
||||
};
|
||||
|
||||
void test_std_msg(const char* call_to_tx, const char* call_de_tx, const char* extra_tx)
|
||||
void test_std_msg(const char* call_to_tx, ftx_field_t to_field, const char* call_de_tx, ftx_field_t de_field, const char* extra_tx, ftx_field_t extra_field)
|
||||
{
|
||||
ftx_message_t msg;
|
||||
ftx_message_init(&msg);
|
||||
|
||||
ftx_message_rc_t rc_encode = ftx_message_encode_std(&msg, &hash_if, call_to_tx, call_de_tx, extra_tx);
|
||||
CHECK(rc_encode == FTX_MESSAGE_RC_OK);
|
||||
printf("Encoded [%s] [%s] [%s]\n", call_to_tx, call_de_tx, extra_tx);
|
||||
CHECK_EQ_VAL(rc_encode, FTX_MESSAGE_RC_OK);
|
||||
|
||||
char call_to[14];
|
||||
char call_de[14];
|
||||
char call_to_arr[14];
|
||||
char call_de_arr[14];
|
||||
char extra[14];
|
||||
ftx_message_rc_t rc_decode = ftx_message_decode_std(&msg, &hash_if, call_to, call_de, extra);
|
||||
CHECK(rc_decode == FTX_MESSAGE_RC_OK);
|
||||
char *call_to = call_to_arr;
|
||||
char *call_de = call_de_arr;
|
||||
ftx_field_t types[FTX_MAX_MESSAGE_FIELDS];
|
||||
ftx_message_rc_t rc_decode = ftx_message_decode_std(&msg, &hash_if, call_to, call_de, extra, types);
|
||||
CHECK_EQ_VAL(rc_decode, FTX_MESSAGE_RC_OK);
|
||||
printf("Decoded [%s] [%s] [%s]\n", call_to, call_de, extra);
|
||||
CHECK(0 == strcmp(call_to, call_to_tx));
|
||||
CHECK(0 == strcmp(call_de, call_de_tx));
|
||||
CHECK(0 == strcmp(extra, extra_tx));
|
||||
// CHECK(1 == 2);
|
||||
call_to = trim_brackets(call_to);
|
||||
call_de = trim_brackets(call_de);
|
||||
CHECK_EQ_VAL(0, strcmp(call_to, call_to_tx));
|
||||
CHECK_EQ_VAL(0, strcmp(call_de, call_de_tx));
|
||||
CHECK_EQ_VAL(0, strcmp(extra, extra_tx));
|
||||
CHECK_EQ_VAL(to_field, types[0]);
|
||||
CHECK_EQ_VAL(de_field, types[1]);
|
||||
CHECK_EQ_VAL(extra_field, types[2]);
|
||||
TEST_END;
|
||||
}
|
||||
|
||||
void test_msg(const char* call_to_tx, const char* call_de_tx, const char* extra_tx)
|
||||
void test_msg(const char* message_text, ftx_message_type_t expected_type, const char* expected, ftx_callsign_hash_interface_t* hash_if)
|
||||
{
|
||||
char message_text[12 + 12 + 20];
|
||||
char* copy_ptr = message_text;
|
||||
copy_ptr = stpcpy(copy_ptr, call_to_tx);
|
||||
copy_ptr = stpcpy(copy_ptr, " ");
|
||||
copy_ptr = stpcpy(copy_ptr, call_de_tx);
|
||||
if (strlen(extra_tx) > 0)
|
||||
{
|
||||
copy_ptr = stpcpy(copy_ptr, " ");
|
||||
copy_ptr = stpcpy(copy_ptr, extra_tx);
|
||||
}
|
||||
printf("Testing [%s]\n", message_text);
|
||||
|
||||
ftx_message_t msg;
|
||||
ftx_message_init(&msg);
|
||||
|
||||
ftx_message_rc_t rc_encode = ftx_message_encode(&msg, NULL, message_text);
|
||||
CHECK(rc_encode == FTX_MESSAGE_RC_OK);
|
||||
ftx_message_rc_t rc_encode = ftx_message_encode(&msg, hash_if, message_text);
|
||||
CHECK_EQ_VAL(rc_encode, FTX_MESSAGE_RC_OK);
|
||||
CHECK_EQ_VAL(expected_type, ftx_message_get_type(&msg));
|
||||
|
||||
char call_to[14];
|
||||
char call_de[14];
|
||||
char extra[14];
|
||||
ftx_message_rc_t rc_decode = ftx_message_decode_std(&msg, NULL, call_to, call_de, extra);
|
||||
CHECK(rc_decode == FTX_MESSAGE_RC_OK);
|
||||
CHECK(0 == strcmp(call_to, call_to_tx));
|
||||
CHECK(0 == strcmp(call_de, call_de_tx));
|
||||
CHECK(0 == strcmp(extra, extra_tx));
|
||||
// CHECK(1 == 2);
|
||||
char message_decoded[12 + 12 + 20];
|
||||
ftx_message_offsets_t offsets;
|
||||
ftx_message_rc_t rc_decode = ftx_message_decode(&msg, hash_if, message_decoded, &offsets);
|
||||
CHECK_EQ_VAL(rc_decode, FTX_MESSAGE_RC_OK);
|
||||
printf("Decoded [%s]; offsets %d:%d %d:%d %d:%d\n", message_decoded,
|
||||
offsets.offsets[0], offsets.types[0], offsets.offsets[1], offsets.types[1], offsets.offsets[2], offsets.types[2]);
|
||||
CHECK_EQ_VAL(0, strcmp(expected, message_decoded));
|
||||
// TODO check offsets
|
||||
TEST_END;
|
||||
}
|
||||
|
||||
|
|
@ -232,9 +238,11 @@ int main()
|
|||
{
|
||||
// test1();
|
||||
// test4();
|
||||
const char* callsigns[] = { "YL3JG", "W1A", "W1A/R", "W5AB", "W8ABC", "DE6ABC", "DE6ABC/R", "DE7AB", "DE9A", "3DA0X", "3DA0XYZ", "3DA0XYZ/R", "3XZ0AB", "3XZ0A" };
|
||||
const char* tokens[] = { "CQ", "QRZ" };
|
||||
const char* callsigns[] = { "YL3JG", "W1A", "W1A/R", "W5AB", "W8ABC", "DE6ABC", "DE6ABC/R", "DE7AB", "DE9A", "3DA0X", "3DA0XYZ", "3DA0XYZ/R", "3XZ0AB", "3XZ0A", "CQ1CQ" };
|
||||
const char* tokens[] = { "CQ", "QRZ", "CQ 123", "CQ 000", "CQ POTA", "CQ SA", "CQ O", "CQ ASD" };
|
||||
const ftx_field_t token_types[] = { FTX_FIELD_TOKEN, FTX_FIELD_TOKEN, FTX_FIELD_TOKEN_WITH_ARG, FTX_FIELD_TOKEN_WITH_ARG, FTX_FIELD_TOKEN_WITH_ARG, FTX_FIELD_TOKEN_WITH_ARG, FTX_FIELD_TOKEN_WITH_ARG, FTX_FIELD_TOKEN_WITH_ARG };
|
||||
const char* grids[] = { "KO26", "RR99", "AA00", "RR09", "AA01", "RRR", "RR73", "73", "R+10", "R+05", "R-12", "R-02", "+10", "+05", "-02", "-02", "" };
|
||||
const ftx_field_t grid_types[] = { FTX_FIELD_GRID, FTX_FIELD_GRID, FTX_FIELD_GRID, FTX_FIELD_GRID, FTX_FIELD_GRID, FTX_FIELD_TOKEN, FTX_FIELD_TOKEN, FTX_FIELD_TOKEN, FTX_FIELD_RST, FTX_FIELD_RST, FTX_FIELD_RST, FTX_FIELD_RST, FTX_FIELD_RST, FTX_FIELD_RST, FTX_FIELD_RST, FTX_FIELD_RST, FTX_FIELD_NONE };
|
||||
|
||||
for (int idx_grid = 0; idx_grid < SIZEOF_ARRAY(grids); ++idx_grid)
|
||||
{
|
||||
|
|
@ -242,19 +250,37 @@ int main()
|
|||
{
|
||||
for (int idx_callsign2 = 0; idx_callsign2 < SIZEOF_ARRAY(callsigns); ++idx_callsign2)
|
||||
{
|
||||
test_std_msg(callsigns[idx_callsign], callsigns[idx_callsign2], grids[idx_grid]);
|
||||
test_std_msg(callsigns[idx_callsign], FTX_FIELD_CALL, callsigns[idx_callsign2], FTX_FIELD_CALL, grids[idx_grid], grid_types[idx_grid]);
|
||||
}
|
||||
}
|
||||
for (int idx_token = 0; idx_token < SIZEOF_ARRAY(tokens); ++idx_token)
|
||||
{
|
||||
for (int idx_callsign2 = 0; idx_callsign2 < SIZEOF_ARRAY(callsigns); ++idx_callsign2)
|
||||
{
|
||||
test_std_msg(tokens[idx_token], callsigns[idx_callsign2], grids[idx_grid]);
|
||||
test_std_msg(tokens[idx_token], token_types[idx_token], callsigns[idx_callsign2], FTX_FIELD_CALL, grids[idx_grid], grid_types[idx_grid]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// test_std_msg("YOMAMA", "MYMAMA/QRP", "73");
|
||||
test_msg("CQ K7IHZ DM43", FTX_MESSAGE_TYPE_STANDARD,
|
||||
"CQ K7IHZ DM43", &hash_if);
|
||||
test_msg("CQ EA8/G5LSI", FTX_MESSAGE_TYPE_NONSTD_CALL,
|
||||
"CQ EA8/G5LSI", &hash_if);
|
||||
test_msg("EA8/G5LSI R2RFE RR73", FTX_MESSAGE_TYPE_STANDARD,
|
||||
"<EA8/G5LSI> R2RFE RR73", &hash_if);
|
||||
test_msg("R2RFE/P EA8/G5LSI R+12", FTX_MESSAGE_TYPE_STANDARD,
|
||||
"R2RFE/P <EA8/G5LSI> R+12", &hash_if);
|
||||
test_msg("TNX BOB 73 GL", FTX_MESSAGE_TYPE_FREE_TEXT,
|
||||
"TNX BOB 73 GL", &hash_if); // message with 4 tokens must be free text
|
||||
test_msg("TNX BOB 73", FTX_MESSAGE_TYPE_STANDARD,
|
||||
"<TNX> <BOB> 73", &hash_if); // can't distinguish special callsigns from other tokens
|
||||
test_msg("CQ YL/LB2JK KO16sw", FTX_MESSAGE_TYPE_NONSTD_CALL,
|
||||
"CQ YL/LB2JK", &hash_if); // grid not allowed with nonstandard call
|
||||
test_msg("CQ POTA YL/LB2JK KO16sw", FTX_MESSAGE_TYPE_NONSTD_CALL,
|
||||
"CQ YL/LB2JK", &hash_if); // CQ modifier not allowed with nonstandard call
|
||||
test_msg("CQ JA LB2JK JO59", FTX_MESSAGE_TYPE_STANDARD,
|
||||
"CQ JA LB2JK JO59", &hash_if);
|
||||
test_msg("CQ 123 LB2JK JO59", FTX_MESSAGE_TYPE_STANDARD,
|
||||
"CQ 123 LB2JK JO59", &hash_if);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
Ładowanie…
Reference in New Issue