diff --git a/docs/udp_broadcast.md b/docs/udp_broadcast.md new file mode 100644 index 0000000..8e8cf46 --- /dev/null +++ b/docs/udp_broadcast.md @@ -0,0 +1,116 @@ +# UDP broadcast + +## Introduction + +The UDP broadcast code is used to handle incoming UDP datagrams. +there are currently 2 types of datagrams it processes: +- broadcasts: "Where are you?v1.0", replies with current owner details. +- change of details: "func1:name: ..., surname: ...", replies with changed owner details. + +It also writes the current owner's name on the screen and updates it everytime it's changed. + +## Table of contents +- [Introduction](#introduction) +- [Table of contents](#table-of-contents) +- [Usage of UDP broadcast](#usage-of-udp-broadcast) + - [Initialization of UDP broadcast](#initialization-of-udp-broadcast) + - [Initialization of UDP connection](#initialization-of-udp-connection) + - [Owner details interface](#owner-details-interface) + - [Setting owner details](#setting-owner-details) + - [Getting owner details](#getting-owner-details) + +## Usage of UDP broadcast +### Initialization of UDP broadcast +The 'udp_broadcast_init(uint16_t x_pos, uint16_t y_pos)' function does 4 things: +1. It initializes the coördinates of where the owner's name has to be written on the LCD. +2. [It initializes the UDP connection](#initialization-of-udp-connection) +3. It initializes the owner's details with a default name. +4. Returns the error value of [udp_broadcast_connection_init()](#initialization-of-udp-connection). This way the user can use some code to check whether the "connection" was initialized correctly. +```c +#include "UDP_broadcast.h' + +... + +void main(void){ + ... + if (udp_broadcast_init(270,255) != ERR_OK){ + ... + } + ... +} +``` + +### Initialization of UDP connection +The 'udp_broadcast_connection_init()' funciton does 2 things: +1. Initializes the UDP "connection" so that incoming datagrams can be processed and replied to. It binds to port 64000 and listens to every IP-address in the local network. +2. returns the LWIP error code so that [err_t udp_broadcast_init(uint16_t x_pos, uint16_t y_pos)](#initialization-of-udp-broadcast) knows the "connection" is initializes correctly + +This function can be used seperately from [err_t udp_broadcast_init(uint16_t x_pos, uint16_t y_pos)](#initialization-of-udp-broadcast), this gives the possibility to try "connecting" again after the first time failed. +```c +#include "UDP_broadcast.h' + +... + +void main(void){ + ... + if (udp_broadcast_init(10,255) == ERR_OK){ + goto connected; + } + LOG_WARN(TAG,"error initializing udp connection, trying again in 500ms"); + HAL_Delay(500); + if(udp_broadcast_connection_init() != ERR_OK){ + LOG_WARN(TAG,"error initializing udp connection, check warnings from udp_broadcast_init() or udp_broadcast_connection_init()"); + } + +connected: + ... +} + +``` +### Owner details interface +The interface to ask for the owner's details and change them is a modified version of the [Qt application](https://github.com/wimdams/Device_finder) Wim Dams build. His only has the functionality to ask for the owner's details. + +Just because the owner's details might want to be used in other code, some functions have been written for obtaining these in the STM32 code aswell. +#### Setting owner details +THe 'udp_broadcast_set_owner_details(const char* , const char*)' function does 2 things: +1. Set the owner details, the order of the parameters is: name, surname +2. Return 1 if the owner's details have been set correctly and 0 if not. +```c +#include "UDP_broadcast.h' + +... + +void main(void){ + ... + if (udp_broadcast_set_owner_details("Joran", "Van Nieuwenhoven") != ERR_OK){ + ... + } + ... +} +``` +#### Getting owner details +There are 3 functions: +- udp_broadcast_get_owner_details_name(): returns the owner's name. +- udp_broadcast_get_owner_details_surname(): returns the owner's surname. +- udp_broadcast_get_owner_details_reply(): returns what would be replied to a UDP broadcast with datagram "Where are you?v1.0". + +```c +#include +#include "UDP_broadcast.h' + +... + +void main(void){ + ... + char name[20]; + char surname[20]; + char reply[120]; + + strncp(name, udp_broadcast_get_owner_details_name(), sizeof(name) - 1); + + strncp(surname, udp_broadcast_get_owner_details_surname(), sizeof(surname) - 1); + + strncp(reply, udp_broadcast_get_owner_details_reply(), sizeof(reply) - 1); + ... +} +``` \ No newline at end of file diff --git a/project/Core/Inc/UDP_broadcast.h b/project/Core/Inc/UDP_broadcast.h new file mode 100644 index 0000000..ff26d10 --- /dev/null +++ b/project/Core/Inc/UDP_broadcast.h @@ -0,0 +1,127 @@ +/** + * @file UDP_broadcast.h + * + * @brief UDP broadcast handler + * Created on: Nov 6, 2023 + * Author: joran + */ + +#ifndef INC_UDP_BROADCAST_H_ +#define INC_UDP_BROADCAST_H_ + +// includes +#include +#include +#include +#include "lwip.h" +#include "lwip/netif.h" +#include "udp.h" + +#include "lcd_api.h" + +// Defines used by UDP callback +#define UDP_BROADCAST_MAX_DATA_SIZE ((UDP_BROADCAST_MAX_NAME_SIZE * 2) - 2 + 25) // Define the maximum expected data size +#define UDP_BROADCAST_UDP_QUESTION1 "Where are you?v1.0" // Expected question from UDP client +#define UDP_BROADCAST_MAX_FUNC_LEN 7 +#define UDP_BROADCAST_MAX_COLON_COMMA_COUNT 4 + +// Defines used by owner details +#define UDP_BROADCAST_MAX_REPLY_SIZE (UDP_BROADCAST_MAX_MAC_ADDR_LEN + sizeof(UDP_BROADCAST_REPLY_MIDDLE_TEXT) + (UDP_BROADCAST_MAX_NAME_SIZE * 2) + UDP_BROADCAST_MAX_REPLY_SIZE_EXTRA) +#define UDP_BROADCAST_REPLY_MIDDLE_TEXT "is present and my owner is" + +#define UDP_BROADCAST_MAX_MAC_ADDR_LEN 19 // Format is: "xx:xx:xx:xx:xx:xx" +#define UDP_BROADCAST_MAX_NAME_SIZE 21 // Code automatically leaves 1 char for '\0' (actual length = length - 1) +#define UDP_BROADCAST_MAX_REPLY_SIZE_EXTRA 20 // Just a bit extra + +#define UDP_BROADCAST_LCD_NAME_PRE_TEXT "New owner: " +#define UDP_BROADCAST_LCD_TEXT_SIZE (strlen(UDP_BROADCAST_LCD_NAME_PRE_TEXT) + UDP_BROADCAST_MAX_NAME_SIZE) + + +/** + * @struct owner_details_t + * @brief contains information about the owner + * + */ + +typedef struct { + char name[UDP_BROADCAST_MAX_NAME_SIZE]; + char surname[UDP_BROADCAST_MAX_NAME_SIZE]; + uint8_t mac_address[6]; + char reply[UDP_BROADCAST_MAX_REPLY_SIZE]; +} owner_details_t; + +// The following functions are used for owner details (those that must be available in main) + +/** + * @fn err_t udp_broadcast_set_owner_details(const char*, const char*) + * @brief udp_broadcast_set_owner_details() is the interface that can be used in other files + * to set the owner's details + * + * @param[in] name string containing the new owner's name + * @param[in] surname string containing the new owner's surname + * @return lwIP error code. + * - ERR_OK. Successful. No error occurred. + * - ERR_ARG. one or both arguments are NULL pointers + */ +err_t udp_broadcast_set_owner_details(const char*, const char*); + +/** + * @fn char udp_broadcast_get_owner_details_name*(void) + * @brief udp_broadcast_get_owner_details_name() can be used to get the current owner's name + * + * @return name of owner + * this name is set by @see udp_broadcast_set_owner_details_name() + */ +char* udp_broadcast_get_owner_details_name(void); + +/** + * @fn char udp_broadcast_get_owner_details_surname*(void) + * @brief udp_broadcast_get_owner_details_surname() can be used to get the current owner's surname + * + * @return surname of owner + * this name is set by @see udp_broadcast_set_owner_details_surname() + */ +char* udp_broadcast_get_owner_details_surname(void); + +/** + * @fn char udp_broadcast_get_owner_details_reply*(void) + * @brief udp_broadcast_get_owner_details_reply() can be used to get the current UDP reply + * + * @return reply for UDP broadcast + * this reply is formatted by @see format_reply() + */ +char* udp_broadcast_get_owner_details_reply(void); + +// Initialization functions + +/** + * @fn err_t udp_broadcast_init(uint16_t x_pos, uint16_t y_pos) + * @brief udp_broadcast_init() initializes the owner's variables and calls upon @see udp_broadcast_connection_init() + * + * @param[in] x_pos : uint16_t that sets the x coordinate the owner's name will be written on the LCD + * @param[in] y_pos : uint16_t that sets the y coordinate the owner's name will be written on the LCD + * @return lwIP error code. + * - ERR_OK. Successful. No error occurred. + * - ERR_USE. The specified ipaddr and port are already bound to by another UDP PCB. + * - ERR_MEM. udp pcb couldn't be created + * + * - ERR_ARG. one or both arguments of udp_broadcast_set_owner_details() are NULL pointers + */ + +err_t udp_broadcast_init(uint16_t x_pos, uint16_t y_pos); + +/** + * @fn err_t udp_broadcast_connection_init() + * @brief udp_broadcast_connection_init() initializes the UDP connection so that it listens for all traffic on + * port 6400 + * it is called by @see udp_broadcast_init() aswell but can be used separately if it failed before + * + * @return lwIP error code. + * - ERR_OK. Successful. No error occurred. + * - ERR_USE. The specified ipaddr and port are already bound to by another UDP PCB. + * - ERR_MEM. udp pcb couldn't be created + */ + +err_t udp_broadcast_connection_init(void); + +#endif /* INC_UDP_BROADCAST_H_ */ diff --git a/project/Core/Inc/modbus_tcp.h b/project/Core/Inc/modbus_tcp.h index 10c993f..2812d7c 100644 --- a/project/Core/Inc/modbus_tcp.h +++ b/project/Core/Inc/modbus_tcp.h @@ -8,7 +8,8 @@ #ifndef INC_MODBUS_H_ #define INC_MODBUS_H_ -#define MODBUSPORT 502 //is the default + +#define MODBUSPORT 502 // 502 is the default #include @@ -21,9 +22,8 @@ /** * @fn void modbus_init - * @brief Initiallises the modbus tcp + * @brief Initializes the modbus tcp */ - void modbus_init(void); #endif /* INC_MODBUS_H_ */ diff --git a/project/Core/Inc/mqtt_application.h b/project/Core/Inc/mqtt_application.h new file mode 100644 index 0000000..389f3b4 --- /dev/null +++ b/project/Core/Inc/mqtt_application.h @@ -0,0 +1,19 @@ +/** + * @file mqtt_application.h + * @brief header for mosquitto application of the groups assignment + * @author RobinVdB + */ + +#ifndef INC_MQTTA_H_ +#define INC_MQTTA_H_ + +#include + +/** + * @brief Initialize MQTT application + * + * @output returns 1 if the init failed to create a client and start an MQTT connection + */ +uint8_t mqtt_application_init(void); + +#endif /* INC_MQTTA_H_ */ diff --git a/project/Core/Src/UDP_broadcast.c b/project/Core/Src/UDP_broadcast.c new file mode 100644 index 0000000..d7261be --- /dev/null +++ b/project/Core/Src/UDP_broadcast.c @@ -0,0 +1,399 @@ +/** + * @file UDP_broadcast.c + * + * @brief UDP broadcast handler + * Created on: Nov 6, 2023 + * Author: joran + */ + +// Includes +#include "UDP_broadcast.h" +#define LOGGER_LEVEL_INFO +#include "log.h" + +// Global variables + +static const char* TAG = "UDP_broadcast"; // Tag used in logs +static owner_details_t udp_owner; +static uint16_t owner_name_x_pos; +static uint16_t owner_name_y_pos; + +// Functions +static void udp_broadcast_set_owner_details_mac(void); +static void udp_broadcast_name_to_lcd(void); +static uint8_t udp_broadcast_set_owner_details_name(const char* name); +static uint8_t udp_broadcast_set_owner_details_surname(const char* surname); +static uint8_t udp_broadcast_set_owner_details_reply(const char* reply); +static void udp_broadcast_format_reply(void); +static void udp_receive_callback(void* arg, + struct udp_pcb* connection, + struct pbuf* p, + const ip_addr_t* addr, + u16_t port); + +/** + * @fn uint8_t udp_broadcast_set_owner_details_mac(owner_details_t*) + * @brief set_owner_details_mac() gets the MAC address from the default netif + * and sets it in the owner_details_t struct + */ + +static void udp_broadcast_set_owner_details_mac(void) { + for (uint8_t i = 0; i < 6; i++) { + udp_owner.mac_address[i] = netif_default->hwaddr[i]; // Access the MAC address + } +} + +/** + * @fn void udp_broadcast_name_to_lcd(void) + * @brief prints the owner's name with + * @see UDP_BROADCAST_LCD_NAME_PRE_TEXT in front of it + */ + +static void udp_broadcast_name_to_lcd(void){ + char text[UDP_BROADCAST_LCD_TEXT_SIZE]; + + memset(text,' ',UDP_BROADCAST_LCD_TEXT_SIZE); // Fill with spaces + text[UDP_BROADCAST_LCD_TEXT_SIZE - 1] = '\0'; // Make the last a NULL byte + lcd_display_text(text, owner_name_x_pos, owner_name_y_pos, LCD_BLACK, LCD_WHITE, LCD_FONT12); + + snprintf(text, UDP_BROADCAST_LCD_TEXT_SIZE, "%s%s",UDP_BROADCAST_LCD_NAME_PRE_TEXT, + udp_owner.name); + + lcd_display_text(text, owner_name_x_pos, owner_name_y_pos, LCD_BLACK, LCD_WHITE, LCD_FONT12); +} + + +/** + * @fn uint8_t udp_broadcast_set_owner_details_name(owner_details_t*, const char*) + * @brief set_owner_details_name() sets the owner's name in the owner_details_t struct + * strncpy is used to copy the function paremeter safely to the owner_details_t's name + * it also uses the lcd api to display the latest owner's name + * + * @param[in] name string containing the owner's name + * @return setting owner name error + * - 0: no error occured, name was set + * - 1: an error occured, name pointer is NULL + */ + +static uint8_t udp_broadcast_set_owner_details_name(const char* name) { + + if (name == NULL) { + LOG_WARN(TAG, "%s: string given is a NULL pointer", __func__); + return 1; + } + LOG_DEBUG(TAG, "set: %s", name); + strncpy(udp_owner.name, name, sizeof(udp_owner.name) - 1); // -1: compensate for '\0' + + udp_broadcast_name_to_lcd(); + return 0; +} + +/** + * @fn uint8_t udp_broadcast_set_owner_details_surname(owner_details_t*, const char*) + * @brief set_owner_details_surname() sets the owner's surname in the owner_details_t struct + * strncpy is used to copy the function paremeter safely to the owner_details_t's surname + * + * @param[in] surname string containing the owner's surname + * @return setting owner surname error + * - 0: no error occured, surname was set + * - 1: an error occured, surname pointer is NULL + */ +static uint8_t udp_broadcast_set_owner_details_surname(const char* surname) { + if (surname == NULL) { + LOG_WARN(TAG, "%s: string given is a NULL pointer", __func__); + return 1; + } + LOG_DEBUG(TAG, "set: %s", surname); + strncpy(udp_owner.surname, surname, sizeof(udp_owner.surname)-1); // -1: compensate for '\0' + return 0; +} + +/** + * @fn uint8_t udp_broadcast_set_owner_details_reply(const char*) + * @brief set_owner_details_reply() sets the UDP reply in the owner_details_t struct + * strncpy is used to copy the function paremeter safely to the owner_details_t's reply + * + * @param[in] reply string used to reply to the UDP broadcast + * @return setting owner reply error + * - 0: no error occured, reply was set + * - 1: an error occured, reply pointer is null + */ + +static uint8_t udp_broadcast_set_owner_details_reply(const char* reply) { + if (reply == NULL) { + LOG_WARN(TAG, "%s: string given is a NULL pointer", __func__); + return 1; + } + LOG_DEBUG(TAG, "set: %s", reply); + strncpy(udp_owner.reply, reply, sizeof(udp_owner.reply) - 1); // -1: compensate for '\0' + return 0; +} + +/** + * @fn uint8_t udp_broadcast_format_reply(const owner_details_t*) + * @brief format_reply() formats all the owner's details into a string + * it formats a string using the owner's details using snprintf + * it sets this reply with @see udp_broadcast_set_owner_details_reply() + */ + +static void udp_broadcast_format_reply(void) { + char mac_addr_str[UDP_BROADCAST_MAX_MAC_ADDR_LEN]; + char reply_buf[UDP_BROADCAST_MAX_REPLY_SIZE]; + + snprintf(mac_addr_str, sizeof(mac_addr_str), "%02X:%02X:%02X:%02X:%02X:%02X", udp_owner.mac_address[0], + udp_owner.mac_address[1], udp_owner.mac_address[2], udp_owner.mac_address[3], udp_owner.mac_address[4], + udp_owner.mac_address[5]); + + snprintf(reply_buf, UDP_BROADCAST_MAX_REPLY_SIZE, "%s %s %s %s", mac_addr_str, UDP_BROADCAST_REPLY_MIDDLE_TEXT, udp_owner.surname, + udp_owner.name); + udp_broadcast_set_owner_details_reply(reply_buf); +} + +/** + * @fn err_t udp_broadcast_set_owner_details(owner_details_t*, const char*, const char*) + * @brief set_owner_details() is the interface that can be used in other files + * to set the owner's details + * the pointers get checked by the functions that are called in this function + * + * @param[in] name string containing the new owner's name + * @param[in] surname string containing the new owner's surname + * @return lwIP error code. + * - ERR_OK. Successful. No error occurred. + * - ERR_ARG. one or both arguments are NULL pointers + */ +err_t udp_broadcast_set_owner_details(const char* name, const char* surname) { + if (!udp_broadcast_set_owner_details_name(name) && !udp_broadcast_set_owner_details_surname(surname)) { + + // If both return 0 it's okay + udp_broadcast_set_owner_details_mac(); + udp_broadcast_format_reply(); + return ERR_OK; + } + return ERR_ARG; +} + +/** + * @fn char udp_broadcast_get_owner_details_name*(void) + * @brief get_owner_details_name() can be used to get the current owner's name + * + * @return name of owner + * this name is set by @see udp_broadcast_set_owner_details_name() + */ + +char* udp_broadcast_get_owner_details_name(void) { + return udp_owner.name; +} + +/** + * @fn char udp_broadcast_get_owner_details_surname*(void) + * @brief get_owner_details_surname() can be used to get the current owner's surname + * + * @return surname of owner + * this name is set by @see udp_broadcast_set_owner_details_surname() + */ + +char* udp_broadcast_get_owner_details_surname(void) { + return udp_owner.surname; +} + +/** + * @fn char udp_broadcast_get_owner_details_reply*(void) + * @brief get_owner_details_reply() can be used to get the current UDP reply + * + * @return reply for UDP broadcast + * this reply is formatted by @see format_reply() + */ + +char* udp_broadcast_get_owner_details_reply(void) { + return udp_owner.reply; +} + +/** + * @fn void udp_broadcast_check_function(const char[]) + * @brief checks what the UDP datagram asked to do + * and processes the datagram if it was not @see UDP_QUESTION1 + * + * @param[in] data the datagram received on port 64000 + * + * @return checked + * - 0: a function was found and processed if necessary + * - 1: datagram didn't have a known function + */ + +static uint8_t udp_broadcast_check_function(const char data[UDP_BROADCAST_MAX_DATA_SIZE]) { + char func[UDP_BROADCAST_MAX_FUNC_LEN]; + char buffer[UDP_BROADCAST_MAX_NAME_SIZE]; + uint8_t enders[UDP_BROADCAST_MAX_COLON_COMMA_COUNT]; + uint8_t counter = 0; + uint8_t data_len = strlen(data); + + if (strcmp(data, UDP_BROADCAST_UDP_QUESTION1) == 0) { + return 0; + } + + memset(func, 0, sizeof(func)); + memset(buffer, 0, sizeof(buffer)); + + memcpy(func,data,UDP_BROADCAST_MAX_FUNC_LEN - 1); + if (strcmp(func, "func1:") != 0) { + LOG_WARN(TAG, "%s: datagram does not contain function that's currently available", __func__); + return 1; + } + for (uint8_t i = 0; i < data_len && counter < UDP_BROADCAST_MAX_COLON_COMMA_COUNT; i++) { + if ((data[i] == ',' || data[i] == ':')) { + enders[counter] = i; + counter++; + } + } + if (enders[2] - enders[1] < UDP_BROADCAST_MAX_NAME_SIZE + 2 && data_len - enders[3] < UDP_BROADCAST_MAX_NAME_SIZE + 2 + && strncmp(data + enders[0], ":name", 5) == 0 && strncmp(data + enders[2], ", surname", 9) == 0) { + + counter = 0; + for (uint8_t i = enders[1] + 2; i < enders[2]; i++) { + buffer[counter] = data[i]; + counter++; + } + if (buffer[0]=='\0') { + strncpy(buffer, "name", sizeof(buffer) - 1); // -1: compensate for '\0' + } + LOG_INFO(TAG, "new owner name:%s", buffer); + udp_broadcast_set_owner_details_name(buffer); + memset(buffer, 0, sizeof(buffer)); + counter = 0; + for (uint8_t i = enders[3] + 2; i < data_len; i++) { + buffer[counter] = data[i]; + counter++; + } + if (buffer[0]=='\0') { + strncpy(buffer, "default", sizeof(buffer) - 1); // -1: compensate for '\0' + } + LOG_INFO(TAG, "new owner surname:%s", buffer); + udp_broadcast_set_owner_details_surname(buffer); + udp_broadcast_format_reply(); + return 0; + } + LOG_WARN(TAG,"%s: function didn't receive the right formatting", __func__); + return 1; +} + +/** + * @fn void udp_receive_callback(void*, struct udp_pcb*, struct pbuf*, const ip_addr_t*, u16_t) + * @brief udp_receive_callback() callback function for when a UDP packet has been received. + * it compares the data to a set string @see UDP_QUESTION1, if it's the same it sends the reply string, + * @see reply_str, back to the client + * if it was not @see UDP_QUESTION1, it checks what function was called with @see udp_broadcast_check_function() + * + * @param[in] arg a pointer to some user-defined data or context + * @param[in] connection UDP PCB to be bound with a local address ipaddr and port. + * @param[in] p packet buffer it holds the incoming UDP packet data, including its payload and length + * @param[in] addr ip_addr_t structure that contains the IP address of the sender of the UDP packet + * @param[in] port the source port number of the sender's UDP packet + */ + +static void udp_receive_callback(void* arg, + struct udp_pcb* connection, + struct pbuf* p, + const ip_addr_t* addr, + u16_t port) { + struct pbuf* p_data; + size_t len; + char* pc; + char data[UDP_BROADCAST_MAX_DATA_SIZE]; + char source_ip_str[16]; + + memset(data, 0, sizeof(data)); + + ipaddr_ntoa_r(addr, source_ip_str, sizeof(source_ip_str)); // Convert the source IP address to a string + + if (p == NULL) { + LOG_WARN(TAG, "%s: input buffer was a NULL pointer", __func__); + return; + } + pc = (char*)p->payload; + len = p->tot_len; + if (len >= UDP_BROADCAST_MAX_DATA_SIZE) { // >= : only if it's smaller to compensate for '\0' + LOG_WARN(TAG, "%s: input buffer was bigger than or was max size %d", __func__, UDP_BROADCAST_MAX_DATA_SIZE); + return; + } + + p_data = pbuf_alloc(PBUF_TRANSPORT, sizeof(udp_owner.reply), PBUF_RAM); + if (p_data == NULL) { + LOG_WARN(TAG, "%s: unable to allocate data buffer for reply", __func__); + goto defer; + } + for (size_t i = 0; i < len; i++) { + data[i] = pc[i]; + } + + LOG_INFO(TAG, "%s: received data from %s at port: %d: %s", __func__, source_ip_str, port, data); + LOG_INFO(TAG, "%s: checking which function was called", __func__); + + if(!udp_broadcast_check_function(data)){ // Should return 0 to reply + p_data->payload = udp_owner.reply; + p_data->len = strlen(udp_owner.reply); + p_data->tot_len = strlen(udp_owner.reply); + udp_sendto(connection, p_data, addr, 64000); // QT app listens on port 64000 + LOG_INFO(TAG, "%s: tried to reply to %s at port: %d: %s", __func__, source_ip_str, 64000, udp_owner.reply); + } + + +defer: + pbuf_free(p); + pbuf_free(p_data); +} + +/** + * @fn err_t udp_broadcast_init(void) + * @brief init_UDP_server() initialises the UDP connection so that it listens for all traffic on + * port 6400 + * it makes a udp_pcb, binds it to port 64000 and initializes the callback function for when data is received + * + * @return lwIP error code. + * - ERR_OK. Successful. No error occurred. + * - ERR_USE. The specified ipaddr and port are already bound to by another UDP PCB. + * - ERR_MEM. udp pcb couldn't be created + */ + +err_t udp_broadcast_connection_init(void) { + struct udp_pcb* connection; + err_t err; + + LOG_INFO(TAG, "%s: initializing UDP server", __func__); + connection = udp_new(); + if (connection == NULL) { + LOG_WARN(TAG, "%s: Initializing UDP server failed, connection is null", __func__); + return ERR_MEM; + } + err = udp_bind(connection, IP_ANY_TYPE, 64000); + if (err != ERR_OK) { + LOG_WARN(TAG, "%s: Initializing UDP server failed, err not ok", __func__); + udp_remove(connection); + return err; + } + udp_recv(connection, udp_receive_callback, NULL); + LOG_INFO(TAG, "%s: Initializing UDP server successful, callback running", __func__); + return err; +} + + + +/** + * @fn err_t udp_broadcast_init() + * @brief udp_broadcast_init() initializes the owner's variables and calls upon @see udp_broadcast_connection_init() + * + * @return lwIP error code. + * - ERR_OK. Successful. No error occurred. + * - ERR_USE. The specified ipaddr and port are already bound to by another UDP PCB. + * - ERR_MEM. udp pcb couldn't be created + * + * - ERR_ARG. one or both arguments of udp_broadcast_set_owner_details() are NULL pointers + */ +err_t udp_broadcast_init(uint16_t x_pos, uint16_t y_pos) { + owner_name_x_pos = x_pos; + owner_name_y_pos = y_pos; + if(udp_broadcast_set_owner_details("name", "default") != ERR_OK){ + LOG_WARN(TAG, "%s: don't give NULL pointers as arguments for the owner's details", __func__); + return ERR_ARG; + } + return udp_broadcast_connection_init(); +} diff --git a/project/Core/Src/main.c b/project/Core/Src/main.c index 56d1184..b5f6500 100644 --- a/project/Core/Src/main.c +++ b/project/Core/Src/main.c @@ -28,8 +28,10 @@ #include "log.h" #include "llfs.h" #include "lcd_api.h" +#include "mqtt_application.h" #include "tftp.h" #include "modbus_tcp.h" +#include "UDP_broadcast.h" /* USER CODE END Includes */ @@ -133,9 +135,21 @@ int main(void) /* Initialize the tftp server */ tftp_server_init(); - + /* Initialize Modbus*/ modbus_init(); + + /* Initialize the MQTT application */ + mqtt_application_init(); + + // Initialize the UDP broadcast service + + if (udp_broadcast_init(10,255) != ERR_OK){ + LOG_WARN(TAG,"error initializing udp connection, check warnings from udp_broadcast_init() or udp_broadcast_connection_init()"); + } + if (udp_broadcast_set_owner_details("Joran", "Van Nieuwenhoven") != ERR_OK){ + LOG_WARN(TAG,"error setting owner's details"); + } /* USER CODE END 2 */ @@ -143,10 +157,10 @@ int main(void) /* USER CODE BEGIN WHILE */ while (1) { - /* USER CODE END WHILE */ + /* USER CODE END WHILE */ - /* USER CODE BEGIN 3 */ - MX_LWIP_Process(); + /* USER CODE BEGIN 3 */ + MX_LWIP_Process(); lcd_task(); } /* USER CODE END 3 */ diff --git a/project/Core/Src/modbus_tcp.c b/project/Core/Src/modbus_tcp.c index 42eea58..7e2410b 100644 --- a/project/Core/Src/modbus_tcp.c +++ b/project/Core/Src/modbus_tcp.c @@ -11,7 +11,7 @@ #include "log.h" // Defines -#define MAX_REG 250 +#define MAX_REG 250 #define EXTENSION_LENGHT 4 #define TEXT_LENGHT 200 #define MULTIPLE_REG 0x10 @@ -26,24 +26,23 @@ #define REG_06 24 #define REG_07 26 + // Global variables char registers[MAX_REG]; -//const char* TAG = "Modbus_TCP"; // Tag used in logs +// const char* TAG = "Modbus_TCP"; // Tag used in logs // Functions -static err_t modbus_incomming_data(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err); -static err_t modbus_accept(void *arg, struct tcp_pcb *pcb, err_t err); - +static err_t modbus_incoming_data(void* arg, struct tcp_pcb* pcb, struct pbuf* p, err_t err); +static err_t modbus_accept(void* arg, struct tcp_pcb* pcb, err_t err); /** - * @fn static err_t modbus_incomming_data(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) - * @brief Function thats called when theris a new request on port 502. - * It handles the incomming data from Qmodmaster + * @fn static err_t modbus_incoming_data(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) + * @brief Function that's called when there is a new request on port 502. + * It handles the incoming data from Qmodmaster */ - -static err_t modbus_incomming_data(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err){ +static err_t modbus_incoming_data(void* arg, struct tcp_pcb* pcb, struct pbuf* p, err_t err) { uint8_t counter; - char *pc; + char* pc; char text[TEXT_LENGHT]; uint8_t background_red = 0; uint8_t background_green = 0; @@ -55,13 +54,15 @@ static err_t modbus_incomming_data(void *arg, struct tcp_pcb *pcb, struct pbuf * uint32_t result_background = 0; uint32_t text_foreground_color = 0; + memset(text,'_',TEXT_LENGHT); // Putting underscores in the whole array text[TEXT_LENGHT-1] = '\0'; - if (p != NULL){ - //LOG_INFO(TAG, "data not null\n"); - // here im going to procces the modbusdata - tcp_recved( pcb, p->tot_len ); + + if (p != NULL) { + // LOG_INFO(TAG, "data not null\n"); + // Procces the modbusdata + tcp_recved(pcb, p->tot_len); pc = (char*)p->payload; for(uint16_t i = 0; i < MAX_REG; i++) { // putting the bufer in the register array @@ -78,11 +79,12 @@ static err_t modbus_incomming_data(void *arg, struct tcp_pcb *pcb, struct pbuf * text_color_blue = (uint8_t)(registers[REG_06]); // 06 nr_img = (uint8_t)(registers[REG_07]); // 07 - //LOG_INFO(TAG, "%d %d %d %d %d %d %d ",background_red,background_green,background_blue,text_color_red,text_color_green,text_color_blue,nr_img); + // LOG_INFO(TAG, "%d %d %d %d %d %d %d + // ",background_red,background_green,background_blue,text_color_red,text_color_green,text_color_blue,nr_img); counter = 0; - for(int i = START_DATA; i < REG_LENGTH; i++){ - if(i % 2 == 0){ + for (int i = START_DATA; i < REG_LENGTH; i++) { + if (i % 2 == 0) { text[counter] = registers[i]; counter++; } @@ -100,26 +102,29 @@ static err_t modbus_incomming_data(void *arg, struct tcp_pcb *pcb, struct pbuf * // proccesing the image index size_t number_of_files = llfs_file_count(); // How many files that there are - llfs_file_t file_list[number_of_files]; // reserving memory for the list - number_of_files = llfs_file_list(file_list, number_of_files, NULL); // freeed memory filled with the list + llfs_file_t file_list[number_of_files]; // Reserving memory for the list - if(number_of_files < nr_img){ + number_of_files = llfs_file_list(file_list, number_of_files, NULL); // Freed memory filled with the list + + if (number_of_files < nr_img) { lcd_clear_text(); lcd_clear_images(); lcd_stop_all_gifs(); + lcd_display_text(text, 10, 10, text_foreground_color, result_background, LCD_FONT24); //When no image + lcd_display_text("FILE NOT IN FILESYSTEM", 10, 75, LCD_RED, LCD_BLACK, LCD_FONT24); - } - else{ - const char * ext = strrchr(file_list[nr_img - 1].name, '.'); + } else { + const char* ext = strrchr(file_list[nr_img - 1].name, '.'); if (ext == NULL) { - // No valid extension found + // No valid extension found } - if (strcmp(ext,".gif") == 0) { + if (strcmp(ext, ".gif") == 0) { lcd_clear_text(); lcd_clear_images(); lcd_stop_all_gifs(); + lcd_display_text(text, 10, 10, text_foreground_color, result_background, LCD_FONT24); lcd_gif_t* gif = lcd_draw_gif_from_fs(file_list[nr_img - 1].name, 0, 75); //GIF on screen } else if (strcmp(ext,".bmp") == 0) { @@ -128,49 +133,40 @@ static err_t modbus_incomming_data(void *arg, struct tcp_pcb *pcb, struct pbuf * lcd_stop_all_gifs(); lcd_display_text(text, 10, 10, text_foreground_color, result_background, LCD_FONT24); lcd_draw_img_from_fs(file_list[nr_img - 1].name, 0, 75); //BMP on screen + } } + } else { + // LOG_INFO(TAG, "not in writing multiple register mode!!!\n"); } - else{ - //LOG_INFO(TAG, "not in writing multiple register mode!!!\n"); - } - } else if (err == ERR_OK){ - tcp_close(pcb); // when everithing was ok close the tcpconnection + } else if (err == ERR_OK) { + tcp_close(pcb); // When everything was ok close the TCP connection } return ERR_OK; } - /** * @fn static err_t modbus_accept(void *arg, struct tcp_pcb *pcb, err_t err) - * @brief Sets the function thats being called when theirs incoming data + * @brief Sets the function that's being called when theirs incoming data */ - -static err_t modbus_accept(void *arg, struct tcp_pcb *pcb, err_t err) -{ - LWIP_UNUSED_ARG(arg); // Eliminates compiler warning about unused arguments - LWIP_UNUSED_ARG(err); // Eliminates compiler warning about unused arguments - tcp_setprio(pcb, TCP_PRIO_MIN); // Sets the priority of a connection. - tcp_recv(pcb, modbus_incomming_data); // sets which function is being called when new data arives +static err_t modbus_accept(void* arg, struct tcp_pcb* pcb, err_t err) { + LWIP_UNUSED_ARG(arg); // Eliminates compiler warning about unused arguments + LWIP_UNUSED_ARG(err); // Eliminates compiler warning about unused arguments + tcp_setprio(pcb, TCP_PRIO_MIN); // Sets the priority of a connection. + tcp_recv(pcb, modbus_incoming_data); // sets which function is being called when new data arrives return ERR_OK; } - /** * @fn void modbus_init - * @brief Initiallises the modbus tcp + * @brief Initializes the modbus tcp */ +void modbus_init(void) { + struct tcp_pcb* modbus_pcb; + modbus_pcb = tcp_new(); // Creating a new tcp pcb + tcp_bind(modbus_pcb, IP_ADDR_ANY, MODBUSPORT); // Bind the modbus_pcb to port 502 -void modbus_init(void) -{ - struct tcp_pcb *modbus_pcb; - modbus_pcb = tcp_new(); // creating a new tcp pcb - tcp_bind(modbus_pcb, IP_ADDR_ANY, MODBUSPORT); // bind the modbus_pcb to port 502 - - modbus_pcb = tcp_listen(modbus_pcb); // listen - tcp_accept(modbus_pcb, modbus_accept); // set callback function + modbus_pcb = tcp_listen(modbus_pcb); // Listen + tcp_accept(modbus_pcb, modbus_accept); // Set callback function } - - - diff --git a/project/Core/Src/mqtt_application.c b/project/Core/Src/mqtt_application.c new file mode 100644 index 0000000..53f72e4 --- /dev/null +++ b/project/Core/Src/mqtt_application.c @@ -0,0 +1,414 @@ +/** + * @file mqtt_application.c + * @brief mosquitto application for group assignment + * @author RobinVdB + */ + +#include +#include "httpd.h" +#include "lcd_api.h" +#include "lwip/apps/fs.h" +#include "lwip/ip_addr.h" +#include "mdns.h" +#include "mqtt.h" +#include "mqtt_application.h" + +#define LOGGER_LEVEL_INFO +#include "log.h" + +#define ATTEMPT_RECONNECT_AMOUNT 50 +#define PUBLISH_QOS 2 +#define PUBLISH_RETAIN 1 +#define MQTT_SERVER_PORT 1883 +#define PRINT_XPOS 50 +#define PRINT_YPOS 50 +#define MAX_FILES 20 +#define SERVER_IP4_A 192 +#define SERVER_IP4_B 168 +#define SERVER_IP4_C 69 +#define SERVER_IP4_D 11 +#define SERVER_PORT 1883 + +typedef enum input_topic { + set_text, + set_text_color, + set_color, + set_image, + other_topic +} input_topic_t; + +// Function prototypes +static void mqtt_pub_request_cb(void*, err_t); +static void publish_data(mqtt_client_t*, void*); +static void mqtt_incoming_publish_cb(void*, const char*, u32_t); +static void mqtt_incoming_data_cb(void*, const uint8_t*, u16_t, u8_t); +static void mqtt_sub_request_cb(void*, err_t); +static void mqtt_connection_cb(mqtt_client_t*, void*, mqtt_connection_status_t); +static void mosquitto_connect(mqtt_client_t*); +static uint32_t color_picker(char*); +static void create_publish_string(char*, char*, size_t); + +// Global variables used in mqtt_incoming_publish_cb and mqtt_incoming_data_cb to give an easy to use ID to the subscribed topics +static sFONT* font; +static uint16_t xpos; +static uint16_t ypos; +static uint16_t connection_attempt_counter; +static uint32_t color; +static uint32_t bgcolor; +static input_topic_t inpub_id; +static const char* TAG = "MQTT"; + +/** + * @brief callback function for publishing data + * + * @param[in] arg User supplied argument to connection callback + * @param[in] result Whether the publish was successful or not + */ +static void mqtt_pub_request_cb(void* arg, err_t result) { + LOG_DEBUG(TAG, "Publish result: %d", result); +} + +/** + * @brief Publishes data + * Publishes the names of all the .bmp and .gif files on the topic getImageList + * + * @param[in] client Pointer to the MQTT client + * @param[in] arg Additional argument to pass to the callback function + */ +static void publish_data(mqtt_client_t* client, void* arg) { + char pub_payload[200] = {0}; + err_t err; + + LOG_DEBUG(TAG, "Entering publish"); + + create_publish_string("*.bmp", pub_payload,sizeof(pub_payload)); + + err = mqtt_publish(client, "getImageList", pub_payload, strlen(pub_payload), PUBLISH_QOS, PUBLISH_RETAIN, mqtt_pub_request_cb, arg); + if (err != ERR_OK) { + LOG_DEBUG(TAG, "Publish err: %d", err); + } + + pub_payload[0] = '\0'; + create_publish_string("*.gif", pub_payload, sizeof(pub_payload)); + + err = mqtt_publish(client, "getGifList", pub_payload, strlen(pub_payload), PUBLISH_QOS, PUBLISH_RETAIN, mqtt_pub_request_cb, arg); + if (err != ERR_OK) { + LOG_DEBUG(TAG, "Publish err: %d", err); + } +} + +/** + * @brief Handles incoming publish + * Callback function for when data was published to a subscribed topic + * + * @param[in] arg User supplied argument to connection callback + * @param[in] topic The topic on which an incoming publish was received + * @param[in] tot_len Length of the incoming data + */ +static void mqtt_incoming_publish_cb(void* arg, const char* topic, uint32_t tot_len) { + LOG_DEBUG(TAG, "Incoming publish at topic %s with total length %lu", topic, tot_len); + // Check for which topic a publish was received + if (strcmp(topic, "input/setText") == 0) { + inpub_id = set_text; + return; + } + if (strcmp(topic, "input/setImage") == 0) { + inpub_id = set_image; + return; + } + if (strcmp(topic, "input/setTextColor") == 0) { + inpub_id = set_text_color; + return; + } + if (strcmp(topic, "input/setColor") == 0) { + inpub_id = set_color; + return; + } + // In case of wrong topic + inpub_id = other_topic; +} + +/** + * @brief Handles incoming publish data + * Handles the received data from a publish to a subscribed topic + * + * @param[in] arg User supplied argument to connection callback + * @param[in] data The incoming data + * @param[in] len Length of the data + * @param[in] flags Whether this is the last fragment of the incoming data + */ +static void mqtt_incoming_data_cb(void* arg, const uint8_t* data, uint16_t len, uint8_t flags) { + char data_buffer[len + 1]; + lcd_gif_t* gif; + + LOG_INFO(TAG, "Incoming publish payload with length %d, flags %d", len, flags); + if (!(flags & MQTT_DATA_FLAG_LAST)) { + LOG_WARN(TAG, "incoming data too big to fit in buffer."); + return; + } + memcpy(data_buffer, data, len); + data_buffer[len] = '\0'; + switch (inpub_id) { + case set_text: + // Places text on the lcd + LOG_INFO(TAG, "incoming data on input/setText: %s.", data_buffer); + lcd_clear_text(); + lcd_display_text((const char*)data_buffer, xpos, ypos, color, bgcolor, font); + break; + case set_image: + // Places an image on the lcd + LOG_INFO(TAG, "incoming data on input/setImage: %s.", data_buffer); + lcd_clear_images(); + lcd_set_bg_color_layer0(bgcolor); + if (len >= 3) { + if (data_buffer[len - 3] == 'b') { + lcd_draw_img_from_fs((const char*)data_buffer, xpos, ypos); + } + if (data_buffer[len - 3] == 'g') { + gif = lcd_draw_gif_from_fs((const char*)data_buffer, xpos, ypos); + if (gif == NULL) { + LOG_INFO(TAG, "GIF could not be drawn"); + } + } + } + break; + case set_text_color: + // Changes the text color for the next time text is written + LOG_INFO(TAG, "incoming data on input/setTextColor: %s.", data_buffer); + color = color_picker(data_buffer); + break; + case set_color: + // Changes the background color for the next time text is written + LOG_INFO(TAG, "incoming data on input/setColor: %s.", data_buffer); + bgcolor = color_picker(data_buffer); + break; + default: + LOG_INFO(TAG, "Publish received on wrong topic, incoming data ignored."); + } +} + +/** + * @brief Callback function for outgoing subscribe request + * + * @param[in] arg User supplied argument to connection callback + * @param[in] result Result code for the subscribe request + */ +static void mqtt_sub_request_cb(void* arg, err_t result) { + LOG_DEBUG(TAG, "Subscribe result: %d", result); +} + +/** + * @brief Callback function for attempting a connection + * If a connection was made setup a callback function for incoming publishes. + * subscribes to the input topics and calls the publish_data function. + * + * @param[in] client Pointer to the MQTT client + * @param[in] arg User supplied argument to connection callback + * @param[in] status Connect result code or disconnection notification + */ +static void mqtt_connection_cb(mqtt_client_t* client, void* arg, mqtt_connection_status_t status) { + err_t err; + + if (status != MQTT_CONNECT_ACCEPTED) { + LOG_INFO(TAG, "mqtt_connection_cb: Disconnected, reason: %d", status); + + if (connection_attempt_counter < ATTEMPT_RECONNECT_AMOUNT) { + connection_attempt_counter++; + // Try to reconnect + mosquitto_connect(client); + } + return; + } + LOG_INFO(TAG, "Successfully connected"); + + connection_attempt_counter = 0; + // Set up callback function for input + mqtt_set_inpub_callback(client, mqtt_incoming_publish_cb, mqtt_incoming_data_cb, arg); + + // Subscribe to the topics setText, setImage, setColor and setTextcolor + err = mqtt_subscribe(client, "input/#", 1, mqtt_sub_request_cb, arg); + if (err != ERR_OK) { + LOG_DEBUG(TAG, "mqtt_subscribe return: %d", err); + } + + // Publish list of images here + publish_data(client, NULL); +} + +/** + * @brief Attempts to create a connection to the mosquitto broker + * Creates a mqtt client and sets up a connection callback function + * + * @param[in] client Pointer to the MQTT client + */ +static void mosquitto_connect(mqtt_client_t* client) { + struct mqtt_connect_client_info_t ci; + err_t err; + + LOG_INFO(TAG, "Attempting MQTT Connection"); + + memset(&ci, 0, sizeof(ci)); + + ci.client_id = "STM32"; + ip_addr_t server_ip; + IP4_ADDR(&server_ip, SERVER_IP4_A, SERVER_IP4_B, SERVER_IP4_C, SERVER_IP4_D); + uint16_t server_port = SERVER_PORT; + err = mqtt_client_connect(client, &server_ip, server_port, mqtt_connection_cb, 0, &ci); + if (err != ERR_OK) { + LOG_DEBUG(TAG, "mqtt_connect return %d", err); + return; + } + LOG_DEBUG(TAG, "Went into mqtt_client_connect; mqtt_connect return %d", err); +} + +/** + * @brief Init function for the mosquitto application of the assignment + * Gives the global variables a value and calls the mosquitto_connect function + */ +uint8_t mqtt_application_init(void) { + color = LCD_BLACK; + bgcolor = LCD_WHITE; + font = LCD_FONT16; + xpos = PRINT_XPOS; + ypos = PRINT_YPOS; + connection_attempt_counter = 0; + + mqtt_client_t* client = mqtt_client_new(); + if (client == NULL) { + LOG_CRIT(TAG, "%s: client == NULL", __func__); + return 1; + } + LOG_DEBUG(TAG, "Starting connection test"); + mosquitto_connect(client); + + return 0; +} + +/** + * @brief Reads the color input string and outputs it to a usable value for LCD_APi + * + * @param[in] color Input string to select a color + * @return color Define to use with the LCD_API + */ +uint32_t color_picker(char* color_in) { + for (int i = 0; i < strlen(color_in); i++) { + color_in[i] = tolower(color_in[i]); + } + if (strcmp((const char*)color_in, "blue") == 0) { + return LCD_BLUE; + } + if (strcmp((const char*)color_in, "green") == 0) { + return LCD_GREEN; + } + if (strcmp((const char*)color_in, "red") == 0) { + return LCD_RED; + } + if (strcmp((const char*)color_in, "cyan") == 0) { + return LCD_CYAN; + } + if (strcmp((const char*)color_in, "magenta") == 0) { + return LCD_MAGENTA; + } + if (strcmp((const char*)color_in, "yellow") == 0) { + return LCD_YELLOW; + } + if (strcmp((const char*)color_in, "light blue") == 0) { + return LCD_LIGHTBLUE; + } + if (strcmp((const char*)color_in, "light green") == 0) { + return LCD_LIGHTGREEN; + } + if (strcmp((const char*)color_in, "light red") == 0) { + return LCD_LIGHTRED; + } + if (strcmp((const char*)color_in, "light cyan") == 0) { + return LCD_LIGHTCYAN; + } + if (strcmp((const char*)color_in, "light magenta") == 0) { + return LCD_LIGHTMAGENTA; + } + if (strcmp((const char*)color_in, "light yellow") == 0) { + return LCD_LIGHTYELLOW; + } + if (strcmp((const char*)color_in, "dark blue") == 0) { + return LCD_DARKBLUE; + } + if (strcmp((const char*)color_in, "dark green") == 0) { + return LCD_DARKGREEN; + } + if (strcmp((const char*)color_in, "dark red") == 0) { + return LCD_DARKRED; + } + if (strcmp((const char*)color_in, "dark cyan") == 0) { + return LCD_DARKCYAN; + } + if (strcmp((const char*)color_in, "dark magenta") == 0) { + return LCD_DARKMAGENTA; + } + if (strcmp((const char*)color_in, "dark yellow") == 0) { + return LCD_DARKYELLOW; + } + if (strcmp((const char*)color_in, "white") == 0) { + return LCD_WHITE; + } + if (strcmp((const char*)color_in, "light gray") == 0) { + return LCD_LIGHTGRAY; + } + if (strcmp((const char*)color_in, "gray") == 0) { + return LCD_GRAY; + } + if (strcmp((const char*)color_in, "dark gray") == 0) { + return LCD_DARKGRAY; + } + if (strcmp((const char*)color_in, "black") == 0) { + return LCD_BLACK; + } + if (strcmp((const char*)color_in, "brown") == 0) { + return LCD_BROWN; + } + if (strcmp((const char*)color_in, "orange") == 0) { + return LCD_ORANGE; + } + if (strcmp((const char*)color_in, "transparent") == 0) { + return LCD_TRANSPARENT; + } + + return LCD_BLACK; +} + +/** + * @brief creates a string to publish on the getImageList topic + * + * @param[in] file_type The file extension asked to be published + * @param[in] payload_buffer The string to be published + * @param[in] buffer_size Size of payload_buffer + */ +static void create_publish_string(char* file_type, char* payload_buffer, size_t buffer_size) { + size_t num_files; + llfs_file_t file_list[MAX_FILES]; + + num_files = llfs_file_list(file_list, MAX_FILES, file_type); + + if (num_files == 0) { + strncat(payload_buffer, "No files found of type: ", buffer_size - strlen(payload_buffer) - 1); + strncat(payload_buffer, file_type, buffer_size - strlen(payload_buffer) - 1); + LOG_INFO(TAG, "%s: No files found of type %s", __func__, file_type); + return; + } + + if (strcmp(file_type, "*.bmp") == 0) { + strncat(payload_buffer, "Available images: ", buffer_size - strlen(payload_buffer) - 1); + } else if (strcmp(file_type, "*.gif") == 0) { + strncat(payload_buffer, "Available gifs: ", buffer_size - strlen(payload_buffer) - 1); + } else { + LOG_WARN(TAG, "No application for given file type: %s", file_type); + return; + } + for (size_t i = 0; i < num_files; i++) { + // Concatenate file names into the payload string + strncat(payload_buffer, file_list[i].name, buffer_size - strlen(payload_buffer) - 1); + strncat(payload_buffer, ", ", buffer_size - strlen(payload_buffer) - 1); // Add a comma between file names + } + strncat(payload_buffer, "\0", buffer_size - strlen(payload_buffer) - 1); + LOG_DEBUG(TAG, "String: %s", payload_buffer); +}