network/uart_tcp_client/main.c

/*******************************************************************************
* # License
* Copyright 2019 Silicon Laboratories Inc. www.silabs.com
*******************************************************************************
*
* The licensor of this software is Silicon Laboratories Inc. Your use of this
* software is governed by the terms of Silicon Labs Master Software License
* Agreement (MSLA) available at
* www.silabs.com/about-us/legal/master-software-license-agreement. This
* software is distributed to you in Source Code format and is governed by the
* sections of the MSLA applicable to Source Code.
*
******************************************************************************/
/* Documentation for this app is available online.
* See https://docs.silabs.com/gecko-os/4/standard/latest/sdk/examples/network/tcp-client
*/
#include "gos.h"
#include "example_app_util.h"
#include "nvm_settings.h"
#define MAX_RX_IRQ_EVENTS 8
#define NETWORK_UP_PERIOD_MS 7000 // ms
#define ATTEMPT_CONNECT_PERIOD_MS 5000 // ms
#define TCP_RX_BUFFER_SIZE 1460
#define TCP_WRITE_MAX_RETRIES 3
#define MAX_TCP_MSG_SIZE 50 //bytes
#define APPLICATION_START_LINE "\r\nUART TCP Client example starting ..."
extern uart_tcp_client_settings_t *uart_tcp_client_settings;
static struct
{
gos_uart_t uart;
uint8_t *rx_buffer;
uint8_t *tcp_rx_buffer;
gos_handle_t tcp_handle;
} context;
/*************************************************************************************************/
void gos_app_init(void)
{
gos_result_t result;
GOS_LOG(APPLICATION_START_LINE);
if (GOS_FAILED(result, gos_load_app_settings_once("settings.ini", SETTINGS_VERSION)))
{
GOS_LOG("Failed to load settings, err:%d", result);
return;
}
GOS_NVM_GET(UART_TCP_CLIENT, SETTINGS, uart_tcp_client_settings);
if (GOS_FAILED(result, gos_event_enable_irq_events(MAX_RX_IRQ_EVENTS)))
{
GOS_LOG("Failed to enable IRQ event, err:%d", result);
}
if (GOS_FAILED(result, example_app_util_network_up(uart_tcp_client_settings->tcp.interface, false, network_event_handler)))
{
}
}
/*************************************************************************************************/
static void network_event_handler(bool is_up)
{
if (is_up == true)
{
GOS_LOG("Network up");
gos_event_issue(tcp_attempt_connect_handler, NULL, 0);
}
else
{
GOS_LOG("Network down");
gos_event_issue(network_up_handler, NULL, 0);
}
}
/*************************************************************************************************/
static void network_up_handler(void *arg)
{
gos_result_t result;
GOS_LOG("Attempting to bring network up ...");
gos_event_unregister(network_up_handler, NULL);
if (GOS_FAILED(result, gos_network_up(uart_tcp_client_settings->tcp.interface, true)))
{
GOS_LOG("Failed to bring up up, err:%d", result);
GOS_LOG("Trying again in %d seconds", NETWORK_UP_PERIOD_MS/1000);
gos_event_register_timed(network_up_handler, NULL, NETWORK_UP_PERIOD_MS, 0);
}
}
/*************************************************************************************************/
static void tcp_attempt_connect_handler(void *arg)
{
gos_result_t result;
gos_handle_t stream_handle;
context.tcp_handle = GOS_NULL_HANDLE;
if (gos_network_is_up(uart_tcp_client_settings->tcp.interface) == false)
{
network_event_handler(false);
return;
}
GOS_LOG("Attempting to connect to %s:%u", uart_tcp_client_settings->tcp.host, uart_tcp_client_settings->tcp.port);
if (GOS_FAILED(result, gos_tcp_connect(uart_tcp_client_settings->tcp.interface,
uart_tcp_client_settings->tcp.host,
uart_tcp_client_settings->tcp.port, &stream_handle)))
{
GOS_LOG("Failed to connect. Trying again in %d seconds", ATTEMPT_CONNECT_PERIOD_MS/1000);
gos_event_register_timed(tcp_attempt_connect_handler, NULL, ATTEMPT_CONNECT_PERIOD_MS, 0);
return;
}
GOS_LOG("Connected. Stream handle: %d", stream_handle);
context.tcp_handle = stream_handle;
if (GOS_FAILED(result, configure_uart()))
{
GOS_LOG("Failed to configure UART, err:%d", result);
}
else
{
set_uart_data_forwarding_enabled(true);
GOS_LOG("UART TCP App Ready. Type something ...");
GOS_LOG("NOTE: The command console is disabled, type: '$' to re-enable the command console");
gos_tcp_register_client_event_handlers(stream_handle, tcp_disconnect_handler, tcp_receive_handler);
}
}
/*************************************************************************************************/
static void tcp_disconnect_handler(gos_handle_t handle)
{
context.tcp_handle = GOS_NULL_HANDLE;
GOS_LOG("Server disconnected");
gos_uart_clear_rx_callback(context.uart, uart_rx_callback);
gos_event_unregister(uart_receive_rx_packet_handler, NULL);
GOS_LOG("Closing TCP handle: %d", handle);
GOS_LOG("Restarting connection timer");
gos_event_issue(tcp_attempt_connect_handler, NULL, 0);
}
/*************************************************************************************************/
static void tcp_receive_handler(gos_handle_t handle)
{
gos_result_t result;
gos_buffer_t buffer = {.size = 0};
// Poll the TCP stream to see if any data is available
if (GOS_FAILED(result, gos_tcp_poll(handle, &buffer.size, NULL)))
{
GOS_LOG("Failed to poll TCP stream, err:%d", result);
}
// Just return if no data is available
else if (buffer.size == 0)
{
}
// Attempt to read the stream
// NOTE: This API simply obtains a pointer to the buffer
// This is useful as no memcpy is required,
// this allows for much more efficient data transfers
else if (GOS_FAILED(result, gos_tcp_read_with_buffer(handle, &buffer)))
{
GOS_LOG("Failed to read from TCP stream, err:%d", result);
}
// Write the TCP data directly to the UART
else if(GOS_FAILED(result, gos_uart_transmit_bytes(context.uart, buffer.data, buffer.size, NULL, GOS_WAIT_FOREVER)))
{
GOS_LOG("Failed to write to UART, err:%d", result);
}
else
{
// Finally, invoke this handler again
// This is needed to account for race conditions when TCP data is received between the poll and read operations.
// If more TCP data is pending then this handler will execute again
// If no more data is pending then this handler will immediately return
// If this handler is already invoked and waiting to execute then this API call will return GOS_DUPLICATE
gos_event_issue((gos_handler_t)tcp_receive_handler, (void*)handle, 0);
}
}
/*************************************************************************************************
* This is called every time the UART receives a character.
* It executes in the UART RX IRQ context.
*/
static gos_result_t uart_rx_callback(void *unused)
{
// If the TCP connection is open
if (context.tcp_handle != GOS_NULL_HANDLE)
{
gos_event_issue(uart_receive_rx_packet_handler, NULL, GOS_EVENT_FLAG_NONE);
}
return GOS_SUCCESS;
}
/*************************************************************************************************/
static void uart_receive_rx_packet_handler(void *unused)
{
gos_result_t result;
uint32_t bytes_available = 0;
if (context.tcp_handle == GOS_NULL_HANDLE)
{
return;
}
gos_uart_peek_bytes(context.uart, NULL, &bytes_available);
if (bytes_available == 0)
{
return;
}
const uint32_t size_size = MIN(TCP_RX_BUFFER_SIZE, bytes_available);
if (GOS_FAILED(result, gos_uart_receive_bytes(context.uart, context.tcp_rx_buffer, size_size,
&bytes_available, GOS_WAIT_FOREVER)))
{
GOS_LOG("Failed to receive data from UART, err:%d", result);
}
else if (bytes_available == 0)
{
}
else if(memchr((char*)context.tcp_rx_buffer, '$', size_size) != NULL)
{
set_uart_data_forwarding_enabled(false);
}
else if (GOS_FAILED(result, gos_tcp_write(context.tcp_handle, context.tcp_rx_buffer, bytes_available, false)))
{
GOS_LOG("Failed to write TCP stream, err:%d", result);
}
}
/*************************************************************************************************/
static gos_result_t configure_uart(void)
{
gos_result_t result;
const gos_uart_config_t config =
{
.baud_rate = uart_tcp_client_settings->uart.baud,
.data_width = GOS_UART_WIDTH_8BIT,
.parity = GOS_UART_NO_PARITY,
.stop_bits = GOS_UART_STOP_BITS_1,
.flow_control = uart_tcp_client_settings->uart.flow_control_enabled ? GOS_UART_FLOW_CONTROL_CTSRTS : GOS_UART_FLOW_CONTROL_DISABLED
};
gos_buffer_t rx_buffer =
{
.size = uart_tcp_client_settings->uart.rx_buffer_size,
.data = NULL
};
GOS_LOG("Configuring UART\r\n");
if (GOS_FAILED(result, gos_malloc("uart_rx", &rx_buffer.data, rx_buffer.size + TCP_RX_BUFFER_SIZE)))
{
}
else if (GOS_FAILED(result, gos_uart_configure(uart_tcp_client_settings->uart.id, &config, &rx_buffer)))
{
gos_free(rx_buffer.data);
}
else
{
gos_uart_set_rx_callback(uart_tcp_client_settings->uart.id, uart_rx_callback);
gos_free(context.rx_buffer);
context.rx_buffer = rx_buffer.data;
context.tcp_rx_buffer = rx_buffer.data + rx_buffer.size;
context.uart = uart_tcp_client_settings->uart.id;
}
return result;
}
/*************************************************************************************************/
static void set_uart_data_forwarding_enabled(bool enabled)
{
if(enabled)
{
GOS_LOG("Disabling command console");
gos_uart_set_rx_callback(PLATFORM_STD_UART, uart_rx_callback);
}
else
{
gos_uart_clear_rx_callback(PLATFORM_STD_UART, uart_rx_callback);
GOS_LOG("\r\nEnabling command console");
}
}