diff --git a/README.md b/README.md index a7b5f8e..978c31c 100644 --- a/README.md +++ b/README.md @@ -67,4 +67,5 @@ This folder contains the following documents: - [logger.md](docs/logger.md): Logging and Debugging Messages - [mkllfs.md](docs/mkllfs.md): Make Linked List File System - [style_guide.md](docs/style_guide.md): Style Guide -- [tftp.md](docs/tftp.md): Trivial File Transfer Protocol \ No newline at end of file +- [tftp.md](docs/tftp.md): Trivial File Transfer Protocol +- [udp_broadcast.md](docs/udp_broadcast.md): UDP Broadcast \ No newline at end of file diff --git a/docs/lcd_api.md b/docs/lcd_api.md index a5f82da..28f9b8a 100644 --- a/docs/lcd_api.md +++ b/docs/lcd_api.md @@ -104,7 +104,7 @@ void main(void) { #### Drawing text on the screen ```c -void lcd_display_text(const char* text, uint16_t x_pos, uint16_t y_pos, uint32_t color, sFONT *font); +void lcd_display_text(const char* text, uint16_t x_pos, uint16_t y_pos, uint32_t color, uint32_t bg_color, sFONT *font); ``` ```c @@ -116,7 +116,7 @@ void main(void) { ... lcd_init(true); ... - lcd_display_text("This is a text string.", 10, 10, LCD_GREEN, LCD_FONT16); + lcd_display_text("This is a text string.", 10, 10, LCD_GREEN, LCD_BLACK, LCD_FONT16); } ``` Display text on the LCD screen in a certain color. When text width exceeds BSP_LCD_GetXSize(), a text wrap will be performed. If the text wrap is between two will be injected. @@ -279,3 +279,20 @@ void main(void) { ``` Clears all text strings on the LCD screen. + +#### Clearing (background/images) layer 0 to color +```c +void lcd_set_bg_color_layer0(uint32_t color); +``` + +```c +#include "lcd_api.h" + +void main(void) { + ... + lcd_init(true); + lcd_draw_img_from_fs("st.bmp", 300, 100); + ... + lcd_set_bg_color_layer0(LCD_BLACK); +} +``` diff --git a/docs/tftp.md b/docs/tftp.md index db3a6c4..c8d67be 100644 --- a/docs/tftp.md +++ b/docs/tftp.md @@ -1,5 +1,14 @@ # TFTP This is the documentation of the TFTP task + +## Table of contents +- [Table of contents](#table-of-contents) +- [Initialization](#initialization) +- [Deinitialization](#deinitialization) +- [Usage](#usage) +- [Receive a file](#receive-a-file) +- [Send a file](#send-a-file) + ## Initialization The TFTP task is initialized in the main function. ```c 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/lcd_api.h b/project/Core/Inc/lcd_api.h index c3ee770..cdc2f7d 100644 --- a/project/Core/Inc/lcd_api.h +++ b/project/Core/Inc/lcd_api.h @@ -96,13 +96,16 @@ void lcd_task(void); * a text wrap will be performed. If the text wrap is between two letters in a word, the '-' character * will be injected. * + * @note When bg_color 0 is passed as a parameter, the background for the text will be transparent + * * @param[in] text C-style text string to display on the LCD screen * @param[in] x_pos X-position * @param[in] y_pos Y-position * @param[in] color Color in which the text will be displayed, see preset colors in defines above + * @param[in] bg_color Text background color * @param[in] font Font size, see defines above in file */ -void lcd_display_text(const char* text, uint16_t x_pos, uint16_t y_pos, uint32_t color, sFONT *font); +void lcd_display_text(const char* text, uint16_t x_pos, uint16_t y_pos, uint32_t color, uint32_t bg_color, sFONT *font); /** * @brief Draw BMP image on screen @@ -156,6 +159,14 @@ void lcd_clear_text(void); */ void lcd_clear_images(void); +/** + * @brief Set background color of layer 0 + * Sets the layer 0 color + * + * @param[in] color Color of layer 0 (images layer) + */ +void lcd_set_bg_color_layer0(uint32_t color); + /** * @brief LCD stop all GIFs * Stops all playing GIFs on lcd screen 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/lcd_api.c b/project/Core/Src/lcd_api.c index 2c0738e..59a8a5e 100644 --- a/project/Core/Src/lcd_api.c +++ b/project/Core/Src/lcd_api.c @@ -84,7 +84,7 @@ void lcd_task(void) { } } -void lcd_display_text(const char* text, uint16_t x_pos, uint16_t y_pos, uint32_t color, sFONT* font) { +void lcd_display_text(const char* text, uint16_t x_pos, uint16_t y_pos, uint32_t color, uint32_t bg_color, sFONT* font) { BSP_LCD_SelectLayer(1); LOG_INFO(TAG, "Display text: %s @x=%d,y=%d", text, x_pos, y_pos); @@ -94,7 +94,7 @@ void lcd_display_text(const char* text, uint16_t x_pos, uint16_t y_pos, uint32_t } BSP_LCD_SetTextColor(color); - BSP_LCD_SetBackColor(0); + BSP_LCD_SetBackColor(bg_color); BSP_LCD_SetFont(font); if (tot_length > BSP_LCD_GetXSize()) { @@ -186,6 +186,11 @@ void lcd_clear_images(void) { BSP_LCD_Clear(0); } +void lcd_set_bg_color_layer0(uint32_t color){ + BSP_LCD_SelectLayer(0); + BSP_LCD_Clear(color); +} + void lcd_stop_all_gifs(void) { for (uint8_t i = 0; i < LCD_MAX_GIFS; i++) { if (gifs[i].src != NULL) { diff --git a/project/Core/Src/main.c b/project/Core/Src/main.c index e2665af..f564c3d 100644 --- a/project/Core/Src/main.c +++ b/project/Core/Src/main.c @@ -28,7 +28,9 @@ #include "log.h" #include "llfs.h" #include "lcd_api.h" +#include "mqtt_application.h" #include "tftp.h" +#include "UDP_broadcast.h" /* USER CODE END Includes */ @@ -132,16 +134,30 @@ int main(void) /* Initialize the tftp server */ tftp_server_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 */ /* Infinite loop */ /* 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/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); +} diff --git a/project/Core/Src/tftp.c b/project/Core/Src/tftp.c index ac04312..87c2d1b 100644 --- a/project/Core/Src/tftp.c +++ b/project/Core/Src/tftp.c @@ -167,7 +167,7 @@ void tftp_close(void* handle) { if (handle == &virt_file[VIRT_TEXT_TXT]) { lcd_clear_images(); lcd_clear_text(); - lcd_display_text((uint8_t*)virt_file[VIRT_TEXT_TXT].data, 0, 0, LCD_COLOR_WHITE, LCD_FONT16); + lcd_display_text((uint8_t*)virt_file[VIRT_TEXT_TXT].data, 0, 0, LCD_COLOR_WHITE, LCD_TRANSPARENT, LCD_FONT16); } if (handle == &virt_file[VIRT_INDEX_TXT] || handle == &virt_file[VIRT_IMAGE_BMP] @@ -299,6 +299,7 @@ void tftp_server_init(void) { // Init the tftp server if (tftp_init(&tftpContext_s) != ERR_OK) { LOG_FATAL(TAG, "Could not initialize tftp server"); + tftp_server_deinit(); return; } LOG_INFO(TAG, "tftp server initialized successfully"); @@ -321,4 +322,4 @@ void tftp_server_deinit(void) { virt_file[VIRT_TEXT_TXT].data = NULL; virt_file[VIRT_TEXT_TXT].len = 0; -} \ No newline at end of file +}