From 428f6a2360077d893334ca5265388e6ba4a923d6 Mon Sep 17 00:00:00 2001 From: Mateusz Lubecki Date: Mon, 22 Apr 2024 05:34:41 +0200 Subject: [PATCH] aprs message decoding --- STM32L476_ParaMETEO/system/src/aprs/subdir.mk | 3 + include/aprsis.h | 2 +- include/etc/ax25_config.h | 6 - include/etc/pwr_save_configuration.h | 2 +- src/aprsis.c | 96 +++-- system/include/aprs/ax25.h | 61 +-- system/include/aprs/ax25_t.h | 61 +++ system/include/aprs/message.h | 58 +++ system/src/aprs/message.c | 361 ++++++++++++++++++ 9 files changed, 565 insertions(+), 85 deletions(-) create mode 100644 system/include/aprs/ax25_t.h create mode 100644 system/include/aprs/message.h create mode 100644 system/src/aprs/message.c diff --git a/STM32L476_ParaMETEO/system/src/aprs/subdir.mk b/STM32L476_ParaMETEO/system/src/aprs/subdir.mk index beebc03..c595821 100644 --- a/STM32L476_ParaMETEO/system/src/aprs/subdir.mk +++ b/STM32L476_ParaMETEO/system/src/aprs/subdir.mk @@ -12,6 +12,7 @@ C_SRCS += \ ../system/src/aprs/crc.c \ ../system/src/aprs/dac.c \ ../system/src/aprs/digi.c \ +../system/src/aprs/message.c \ ../system/src/aprs/status.c \ ../system/src/aprs/telemetry.c \ ../system/src/aprs/wx.c @@ -25,6 +26,7 @@ OBJS += \ ./system/src/aprs/crc.o \ ./system/src/aprs/dac.o \ ./system/src/aprs/digi.o \ +./system/src/aprs/message.o \ ./system/src/aprs/status.o \ ./system/src/aprs/telemetry.o \ ./system/src/aprs/wx.o @@ -38,6 +40,7 @@ C_DEPS += \ ./system/src/aprs/crc.d \ ./system/src/aprs/dac.d \ ./system/src/aprs/digi.d \ +./system/src/aprs/message.d \ ./system/src/aprs/status.d \ ./system/src/aprs/telemetry.d \ ./system/src/aprs/wx.d diff --git a/include/aprsis.h b/include/aprsis.h index 96785e4..e483bf9 100644 --- a/include/aprsis.h +++ b/include/aprsis.h @@ -50,7 +50,7 @@ void aprsis_init( aprsis_return_t aprsis_connect_and_login(const char * address, uint8_t address_ln, uint16_t port, uint8_t auto_send_beacon); aprsis_return_t aprsis_connect_and_login_default(uint8_t auto_send_beacon); sim800_return_t aprsis_disconnect(void); -void aprsis_receive_callback(srl_context_t* srl_context); +//void aprsis_receive_callback(srl_context_t* srl_context); void aprsis_check_alive(void); int aprsis_check_connection_attempt_alive(void); diff --git a/include/etc/ax25_config.h b/include/etc/ax25_config.h index 44002cf..72ffa53 100644 --- a/include/etc/ax25_config.h +++ b/include/etc/ax25_config.h @@ -25,10 +25,6 @@ * $WIZ$ type = "int" * $WIZ$ min = 1 */ -#include - -//extern uint8_t kiss_txdelay; -//#define CONFIG_AFSK_PREAMBLE_LEN (kiss_txdelay*10UL) #define CONFIG_AFSK_PREAMBLE_LEN 400UL /// 300 /** @@ -36,8 +32,6 @@ * $WIZ$ type = "int" * $WIZ$ min = 1 */ -//extern uint8_t kiss_txtail; -//#define CONFIG_AFSK_TRAILER_LEN (kiss_txtail*10UL) #define CONFIG_AFSK_TRAILER_LEN 50UL diff --git a/include/etc/pwr_save_configuration.h b/include/etc/pwr_save_configuration.h index ed8f54c..56dc8d6 100644 --- a/include/etc/pwr_save_configuration.h +++ b/include/etc/pwr_save_configuration.h @@ -45,7 +45,7 @@ /** * Do not uncomment this on production devices */ -//#define INHIBIT_CUTOFF +#define INHIBIT_CUTOFF /** * Intermediate STOP2 cycle lenght within L7 or L6 mode. diff --git a/src/aprsis.c b/src/aprsis.c index 49c6d93..52cfccc 100644 --- a/src/aprsis.c +++ b/src/aprsis.c @@ -9,6 +9,7 @@ #include "etc/aprsis_config.h" #include "text.h" #include "aprs/status.h" +#include "aprs/message.h" #include "gsm/sim800c.h" #include "gsm/sim800c_poolers.h" @@ -19,6 +20,12 @@ #include #include +#ifdef UNIT_TEST +#define STATIC +#else +#define STATIC static +#endif + srl_context_t * aprsis_serial_port; /** @@ -180,6 +187,72 @@ char aprsis_login_string_reveived[APRSIS_LOGIN_STRING_RECEIVED_LN]; */ #define MAXIMUM_CALL_SSID_DASH_LN 10 +/** + * Checks if data in a buffer contains APRS message + * @param message + * @param message_ln + * @return position at which content of message starts + */ +STATIC int aprsis_check_is_message(const uint8_t * const message, const uint16_t message_ln) { + // example message + // Details:"SP8EBC>APX216,TCPIP*,qAC,NINTH::SR9WXZ :tedt{0s}\r\n", '\0' + + // go through a buffer and look for double ':' + + int message_start_position = 0; + + for (int i = 0; i < message_ln; i++) { + const uint8_t * this_character = message + i; + + const uint8_t * next_character = message + i + 1; + + if (*this_character == 0x00) { + break; + } + + if ((*this_character == ':') && (*next_character == ':')) { + message_start_position = i + 2; + break; + } + } + + return message_start_position; +} + +/** + * + * @param srl_context + */ +STATIC void aprsis_receive_callback(srl_context_t* srl_context) { + + const uint8_t * buffer = srl_get_rx_buffer(srl_context); + + const uint16_t message_ln = srl_context->srl_rx_bytes_counter; + + // if something was actually received + if (srl_context->srl_rx_state == SRL_RX_DONE) { + // check if this is keepalive message + if (*buffer == '#') { + aprsis_last_keepalive_ts = main_get_master_time(); + + aprsis_last_keepalive_long_ts = main_get_master_time(); + + aprsis_keepalive_received_counter++; + + gsm_sim800_tcpip_async_receive(aprsis_serial_port, aprsis_gsm_modem_state, 0, 61000, aprsis_receive_callback); + } + else if (aprsis_check_is_message(buffer, message_ln) == 1) { + + } + else { + aprsis_another_received_counter++; + + gsm_sim800_tcpip_async_receive(aprsis_serial_port, aprsis_gsm_modem_state, 0, 61000, aprsis_receive_callback); + + } + } +} + void aprsis_init( srl_context_t * context, gsm_sim800_state_t * gsm_modem_state, @@ -394,29 +467,6 @@ sim800_return_t aprsis_disconnect(void) { return out; } -void aprsis_receive_callback(srl_context_t* srl_context) { - - // if something was actually received - if (srl_context->srl_rx_state == SRL_RX_DONE) { - // check if this is keepalive message - if (*(srl_get_rx_buffer(srl_context)) == '#') { - aprsis_last_keepalive_ts = main_get_master_time(); - - aprsis_last_keepalive_long_ts = main_get_master_time(); - - aprsis_keepalive_received_counter++; - - gsm_sim800_tcpip_async_receive(aprsis_serial_port, aprsis_gsm_modem_state, 0, 61000, aprsis_receive_callback); - } - else { - aprsis_another_received_counter++; - - gsm_sim800_tcpip_async_receive(aprsis_serial_port, aprsis_gsm_modem_state, 0, 61000, aprsis_receive_callback); - - } - } -} - /** * Pooler function which check periodically if APRS-IS connection is alive. */ diff --git a/system/include/aprs/ax25.h b/system/include/aprs/ax25.h index 1757261..9bec5b9 100644 --- a/system/include/aprs/ax25.h +++ b/system/include/aprs/ax25.h @@ -6,6 +6,7 @@ #include "main_master_time.h" +#include "ax25_t.h" #include "cfifo.h" #include "afsk.h" @@ -50,15 +51,15 @@ struct AX25Msg; // fwd declaration -/** - * Type for AX25 messages callback. - */ -typedef void (*ax25_callback_t)(struct AX25Msg *ax25_rxed_frame); /** - * Type for channel free wait timeout callback + * Create an AX25Call structure on the fly. + * \param str callsign, can be 6 characters or shorter. + * \param id ssid associated with the callsign. */ -typedef void (*ax25_ch_free_timeout_callback_t)(void); +#define AX25_CALL(str, id) {.call = (str), .ssid = (id) } +#define AX25_PATH(dst, src, ...) { dst, src, ## __VA_ARGS__ } + typedef struct AX25Ctx { @@ -81,54 +82,6 @@ typedef struct AX25Ctx } AX25Ctx; - -/** - * AX25 Call sign. - */ -typedef struct AX25Call -{ - char call[6]; ///< Call string, max 6 character - uint8_t ssid; ///< SSID (secondary station ID) for the call -} AX25Call; - - -/** - * Create an AX25Call structure on the fly. - * \param str callsign, can be 6 characters or shorter. - * \param id ssid associated with the callsign. - */ -#define AX25_CALL(str, id) {.call = (str), .ssid = (id) } -#define AX25_PATH(dst, src, ...) { dst, src, ## __VA_ARGS__ } - - -/** - * Maximum number of Repeaters in a AX25 message. - */ -#define AX25_MAX_RPT 8 - - -/** - * AX25 Message. - * Used to handle AX25 sent/received messages. - */ -typedef struct AX25Msg -{ - - AX25Call src; ///< Source adress - AX25Call dst; ///< Destination address - AX25Call rpt_lst[AX25_MAX_RPT]; ///< List of repeaters - uint8_t rpt_cnt; ///< Number of repeaters in this message - uint8_t rpt_flags; ///< Has-been-repeated flags for each repeater (bit-mapped) - #define AX25_REPEATED(msg, idx) ((msg)->rpt_flags & BV(idx)) - uint16_t ctrl; ///< AX25 control field - uint8_t pid; ///< AX25 PID field - const uint8_t *info; ///< Pointer to the info field (payload) of the message - uint16_t len; ///< Payload length - uint8_t raw_data[CONFIG_AX25_FRAME_BUF_LEN]; /// Surowa zawarto�� ramki przekopiowana z Ctx->buff - short int raw_msg_len; // wielkosc surowej ramki - -} AX25Msg; - extern AX25Msg ax25_rxed_frame; extern char ax25_new_msg_rx_flag; diff --git a/system/include/aprs/ax25_t.h b/system/include/aprs/ax25_t.h new file mode 100644 index 0000000..3fb34ea --- /dev/null +++ b/system/include/aprs/ax25_t.h @@ -0,0 +1,61 @@ +/* + * ax25_t.h + * + * Created on: Apr 20, 2024 + * Author: mateusz + */ + +#ifndef INCLUDE_APRS_AX25_T_H_ +#define INCLUDE_APRS_AX25_T_H_ + +#include "stdint.h" +#include "ax25_config.h" + +/** + * Maximum number of Repeaters in a AX25 message. + */ +#define AX25_MAX_RPT 8 + +/** + * AX25 Call sign. + */ +typedef struct AX25Call +{ + char call[6]; ///< Call string, max 6 character + uint8_t ssid; ///< SSID (secondary station ID) for the call +} AX25Call; + +/** + * AX25 Message. + * Used to handle AX25 sent/received messages. + */ +typedef struct AX25Msg +{ + + AX25Call src; ///< Source adress + AX25Call dst; ///< Destination address + AX25Call rpt_lst[AX25_MAX_RPT]; ///< List of repeaters + uint8_t rpt_cnt; ///< Number of repeaters in this message + uint8_t rpt_flags; ///< Has-been-repeated flags for each repeater (bit-mapped) + #define AX25_REPEATED(msg, idx) ((msg)->rpt_flags & BV(idx)) + uint16_t ctrl; ///< AX25 control field + uint8_t pid; ///< AX25 PID field + const uint8_t *info; ///< Pointer to the info field (payload) of the message + uint16_t len; ///< Payload length + uint8_t raw_data[CONFIG_AX25_FRAME_BUF_LEN]; /// Surowa zawarto�� ramki przekopiowana z Ctx->buff + short int raw_msg_len; // wielkosc surowej ramki + +} AX25Msg; + +/** + * Type for AX25 messages callback. + */ +typedef void (*ax25_callback_t)(struct AX25Msg *ax25_rxed_frame); + +/** + * Type for channel free wait timeout callback + */ +typedef void (*ax25_ch_free_timeout_callback_t)(void); + + +#endif /* INCLUDE_APRS_AX25_T_H_ */ diff --git a/system/include/aprs/message.h b/system/include/aprs/message.h new file mode 100644 index 0000000..e6e84de --- /dev/null +++ b/system/include/aprs/message.h @@ -0,0 +1,58 @@ +/* + * message.h + * + * Created on: Apr 20, 2024 + * Author: mateusz + */ + +#ifndef INCLUDE_APRS_MESSAGE_H_ +#define INCLUDE_APRS_MESSAGE_H_ + +#include "ax25_t.h" +#include "stdint.h" +#include "./stored_configuration_nvm/config_data.h" + +#define MESSAGE_MAX_LENGHT 67 + +typedef struct message_t { + AX25Call from; + AX25Call to; + uint8_t content[MESSAGE_MAX_LENGHT]; + uint8_t number; +}message_t; + +typedef enum message_source_t { + MESSAGE_SOURCE_APRSIS, + MESSAGE_SOURCE_RADIO + +}message_source_t; + +/** + * Decode received data to look for an APRS message and put it into a structure + * @param message pointer to data received from APRS-IS + * @param message_ln lenght of a buffer content + * @param content_position optional position of an APRS message content (recipient callsign). if set to zero function will look for it + * @param src + * @param output parsed APRS message content + * @return zero if message has been found and decoded, non zero if parsing failed + */ +uint8_t message_decode_from_aprsis(const uint8_t * const message, const uint16_t message_ln, uint16_t content_position, message_source_t src, message_t * output); + +/** + * + * @param config_data + * @param message + * @return zero if this is a message to us, non zero otherwise + */ +uint8_t message_is_for_me(config_data_basic_t * config_data, const message_t * const message); + +/** + * + * @param out_buffer + * @param out_buffer_ln + * @param message + * @param src how this message has been received + */ +void message_create_ack_for(uint8_t * out_buffer, const uint16_t out_buffer_ln, const message_t * const message, const message_source_t src); + +#endif /* INCLUDE_APRS_MESSAGE_H_ */ diff --git a/system/src/aprs/message.c b/system/src/aprs/message.c new file mode 100644 index 0000000..8d31fb2 --- /dev/null +++ b/system/src/aprs/message.c @@ -0,0 +1,361 @@ +/* + * message.c + * + * Created on: Apr 20, 2024 + * Author: mateusz + */ + +#include "message.h" +#include "string.h" +#include "stdio.h" + +#define MESSAGE_RECIPIENT_FIELD_SIZE 9 + +#define MESSAGE_SSID_CHARS_LN 2 + +#define MESSAGE_ATOI_BUFFER 5 ///!< include room of null terminator + +#define MESSAGE_SENDER_CALL_SSID_MAXLEN 9 + +#define MESSAGE_CURRENT_CHAR *(message + content_position + i) + +#define MESSAGE_CURRENT_SENDER_CHAR *(message + i) + +#define MESSAGE_IS_DIGIT(c) (c >= '0' && c <= '9') + +#define MESSAGE_ACK_REMAINING_BUF (out_buffer_ln - current_pos) + +#define MESSAGE_ACK_CURRENT_POS (char*)(out_buffer + current_pos) + +static char message_atoi_buffer[MESSAGE_ATOI_BUFFER]; + +/** + * Decode received data to look for an APRS message and put it into a structure + * @param message pointer to data received from APRS-IS + * @param message_ln lenght of a buffer content + * @param content_position optional position of an APRS message content (recipient callsign). if set to zero function will look for it + * @param output parsed APRS message content + * @return zero if message has been found and decoded, non zero if parsing failed + */ +uint8_t message_decode_from_aprsis(const uint8_t * const message, const uint16_t message_ln, uint16_t content_position, message_source_t src, message_t * output) { + + // example message:: SP8EBC>APX216,TCPIP*,qAC,NINTH::SR9WXZ :tedt{0s}\r\n + + // result returned by the function, although shamesly it is also used as local aux variable + // in few places of this function :( this is what You sometimes do to save some stack. + uint8_t result = 0xFF; + + uint16_t i = 0; + + if (output == 0x00) { + return result; + } + + if (message_ln < (MESSAGE_SENDER_CALL_SSID_MAXLEN + MESSAGE_RECIPIENT_FIELD_SIZE)) { + return result; + } + + memset(message_atoi_buffer, 0x00, MESSAGE_ATOI_BUFFER); + + // if start position of APRS message (position of recipient callsign) has not been provided + if ((src == MESSAGE_SOURCE_APRSIS) && (content_position == 0)) { + + // look for it + for (i = 0; i < message_ln; i++) { + const uint8_t * this_character = message + i; + + const uint8_t * next_character = message + i + 1; + + // break on an end of input string + if (*this_character == 0x00) { + break; + } + + // check if double semicolon has been found + if ((*this_character == ':') && (*next_character == ':')) { + // APRS message starts after second semicolong + content_position = i + 2; + break; + } + } + } + + // clear the iterator, it will be used across this function + i = 0; + + // check content position one more time to verify + // if input data contains APRS message at all + if ( + ((src == MESSAGE_SOURCE_APRSIS) && (content_position != 0)) || + (src == MESSAGE_SOURCE_RADIO) + ) { + + // set this variable to zero as now it will be used as a local + result = 0; + + // clear output structure to make room for new data + memset(output, 0x00, sizeof(message_t)); + + //extract sender call and SSID only if this message has been received from APRS-IS in pure text form + if (src == MESSAGE_SOURCE_APRSIS) { + // fast forward any potential whitespace at the begining + while (MESSAGE_CURRENT_SENDER_CHAR == ' ') { + i++; + + if (i >= message_ln) { + break; + } + } + + // extract sender callsign + for (; i < MESSAGE_SENDER_CALL_SSID_MAXLEN; i++) { + + // if SSID separator or sender end character ('>') has been reached + if (MESSAGE_CURRENT_SENDER_CHAR == '-' || MESSAGE_CURRENT_SENDER_CHAR == '>') { + break; + } + + output->from.call[result++] = MESSAGE_CURRENT_SENDER_CHAR; + } + + // check if sender has SSID + if (MESSAGE_CURRENT_SENDER_CHAR == '-') { + // jumps to next character. otherwise separating '-' + // will be interpreted as a minus/negitive sign + i++; + + result = 0; // here used as a local iterator + + // extract SSID + for (; i < MESSAGE_RECIPIENT_FIELD_SIZE; i++) { + // copy characters to aux buffer + message_atoi_buffer[result++] = MESSAGE_CURRENT_SENDER_CHAR; + + // check if there isn't enough characters + if (result == MESSAGE_ATOI_BUFFER) { + break; + } + } + + // convert SSID to int + output->from.ssid = atoi(message_atoi_buffer); + } + + // clear the iterator, it will be used across this function + i = 0; + } + else { + // clear content_position iterator + content_position = 0; + + // find a begining of the message in data from radio channel + while (MESSAGE_CURRENT_CHAR != ':') { + content_position++; + + if (content_position >= message_ln) { + break; + } + } + // jump over ':' + content_position++; + } + + // extract recipient + for (; i < MESSAGE_RECIPIENT_FIELD_SIZE; i++) { + + // look if end of callsign or separating '-' has been found + if (MESSAGE_CURRENT_CHAR == ' ' || MESSAGE_CURRENT_CHAR == '-') { + break; // and go for SSID reading + } + + // copy recipient callsign character per character + output->to.call[i] = MESSAGE_CURRENT_CHAR; + } + + // check if callsign has SSID set + if (MESSAGE_CURRENT_CHAR == '-') { + // jumps to next character. otherwise separating '-' + // will be interpreted as a minus/negitive sign + i++; + + result = 0; // here used as a local iterator + + // extract SSID + for (; i < MESSAGE_RECIPIENT_FIELD_SIZE; i++) { + // copy characters to aux buffer + message_atoi_buffer[result++] = MESSAGE_CURRENT_CHAR; + + // check if there isn't enough characters + if (result == MESSAGE_ATOI_BUFFER) { + break; + } + } + + // convert SSID to int + output->to.ssid = atoi(message_atoi_buffer); + } + + if (result != MESSAGE_ATOI_BUFFER && /* if SSID extraction was OK and end of a buffer hasn't been reached */ + ( + (i < MESSAGE_RECIPIENT_FIELD_SIZE) || /* if recipient and callsign has been read before ':' separating from message content */ + ((i == MESSAGE_RECIPIENT_FIELD_SIZE) && (MESSAGE_CURRENT_CHAR == ':')) /* if a position of separating ':' was reached and ':' is there */ + ) + ) { + + // reinitialize buffer before next usage + memset(message_atoi_buffer, 0x00, MESSAGE_ATOI_BUFFER); + + // check if the iterator is set now to position of ':' separating + // recipient an the message itself + while(MESSAGE_CURRENT_CHAR != ':' && i <= MESSAGE_RECIPIENT_FIELD_SIZE) { + i++; + } + + // one more incrementation to jump over ':' and land on the first character of the message + i++; + + result = 0; + + // then copy message, which ends on a counter, something like '{1' + while(MESSAGE_CURRENT_CHAR != ':' && i + content_position < message_ln) { + output->content[result++] = MESSAGE_CURRENT_CHAR; + i++; + + // break on message counter separator + if (MESSAGE_CURRENT_CHAR == '{') { + i++; // move to first digit of a counter + break; + } + } + + // check which condition has ended previous 'while' loop and if an end of the buffer has been reached + if (i + content_position < message_ln) { + result = 0; + + // now iterator is set (should be set) on a first digit of message counter + // copy everything until first non digit character is found + while (MESSAGE_IS_DIGIT(MESSAGE_CURRENT_CHAR)) { + message_atoi_buffer[result++] = MESSAGE_CURRENT_CHAR; + + i++; + + // check if there isn't enough characters + if (result == MESSAGE_ATOI_BUFFER) { + break; + } + } + + // convert message counter from string to int + output->number = atoi(message_atoi_buffer); + + if (result < MESSAGE_ATOI_BUFFER) { + // new we are done (??) + result = 0; + } + } + else { + result = 0xFF; + } + } + else { + result = 0xFF; + } + } + + return result; +} + +/** + * + * @param config_data + * @param message + * @return zero if this is a message to us, non zero otherwise + */ +uint8_t message_is_for_me(config_data_basic_t * config_data, const message_t * const message) +{ + const int callsign = strncmp(config_data->callsign, message->to.call, 6); + + if (callsign == 0 && (config_data->ssid == message->to.ssid)) { + return 0; + } + else { + return 1; + } +} + +/** + * + * @param out_buffer + * @param out_buffer_ln + * @param message + * @param src how this message has been received + */ +void message_create_ack_for(uint8_t * out_buffer, const uint16_t out_buffer_ln, const message_t * const message, const message_source_t src) +{ + int current_pos = 0; + + uint8_t call_position = 0; + + // clear output buffer + memset(out_buffer, 0x00, out_buffer_ln); + + if (src == MESSAGE_SOURCE_APRSIS) { + + // put my callsign + for (; call_position < 6; call_position++) { + // break on null character + if (message->to.call[call_position] == 0x00) { + break; + } + + // copy callsign data + out_buffer[current_pos + call_position] = message->to.call[call_position]; + } + + current_pos += call_position; + + call_position = 0; + + // check if I have a SSID + if (message->to.ssid != 0) { + current_pos += snprintf(MESSAGE_ACK_CURRENT_POS, MESSAGE_ACK_REMAINING_BUF, "-%d", message->to.ssid); + } + + // constant part + current_pos += snprintf(MESSAGE_ACK_CURRENT_POS, MESSAGE_ACK_REMAINING_BUF, ">AKLPRZ::"); + + // put sender callsign, station I received this message from + for (; call_position < 6; call_position++) { + // break on null character + if (message->from.call[call_position] == 0x00) { + break; + } + + // copy callsign data + out_buffer[current_pos + call_position] = message->from.call[call_position]; + } + + // put sender SSID, station I received this message from + if (message->from.ssid != 0) { + call_position += snprintf(MESSAGE_ACK_CURRENT_POS + call_position, MESSAGE_ACK_REMAINING_BUF, "-%d", message->from.ssid); + } + + // check if callsign was shorter than 6 characters + while (call_position < 9) { + // copy callsign data + out_buffer[current_pos + call_position] = ' '; + + call_position++; + } + + // callsign + ssid + padding must be exactly 9 characters long + current_pos += 9; + + // then put 'ackXX' where X is message number + current_pos += snprintf(MESSAGE_ACK_CURRENT_POS, MESSAGE_ACK_REMAINING_BUF, ":ack%d", message->number); + + } + else { + + } +}