diff --git a/components/config/CMakeLists.txt b/components/config/CMakeLists.txt new file mode 100644 index 0000000..8cb522d --- /dev/null +++ b/components/config/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "ConfigManager.cpp" + PRIV_REQUIRES constants nvs_flash + INCLUDE_DIRS "include") diff --git a/components/config/ConfigManager.cpp b/components/config/ConfigManager.cpp new file mode 100644 index 0000000..3079f80 --- /dev/null +++ b/components/config/ConfigManager.cpp @@ -0,0 +1,59 @@ +// +// Created by Johnathon Slightham on 2025-06-12. +// + +#include "nvs_flash.h" +#include "constants/config.h" + +#include "ConfigManager.h" + +void ConfigManager::init_config() { + nvs_flash_init(); + + nvs_handle config_handle; + nvs_open(NVS_FLASH_NAMESPACE, NVS_READWRITE, &config_handle); + + uint16_t id; + if (ESP_ERR_NVS_NOT_FOUND == nvs_get_u16(config_handle, MODULE_ID_KEY, &id)) { + nvs_set_u16(config_handle, MODULE_ID_KEY, DEFAULT_MODULE_ID); + } + + uint16_t type; + if (ESP_ERR_NVS_NOT_FOUND == nvs_get_u16(config_handle, MODULE_TYPE_KEY, &type)) { + nvs_set_u16(config_handle, MODULE_TYPE_KEY, DEFAULT_MODULE_TYPE); + } + + nvs_close(config_handle); +} + +uint16_t ConfigManager::get_module_id() { + nvs_handle config_handle; + nvs_open(NVS_FLASH_NAMESPACE, NVS_READONLY, &config_handle); + uint16_t id; + nvs_get_u16(config_handle, MODULE_ID_KEY, &id); + nvs_close(config_handle); + return id; +} + +ModuleType ConfigManager::get_module_type() { + nvs_handle config_handle; + nvs_open(NVS_FLASH_NAMESPACE, NVS_READONLY, &config_handle); + uint16_t type; + nvs_get_u16(config_handle, MODULE_TYPE_KEY, &type); + nvs_close(config_handle); + return static_cast(type); +} + +void ConfigManager::set_module_id(const uint16_t id) { + nvs_handle config_handle; + nvs_open(NVS_FLASH_NAMESPACE, NVS_READWRITE, &config_handle); + nvs_set_u16(config_handle, MODULE_ID_KEY, id); + nvs_close(config_handle); +} + +void ConfigManager::set_module_type(const ModuleType type) { + nvs_handle config_handle; + nvs_open(NVS_FLASH_NAMESPACE, NVS_READWRITE, &config_handle); + nvs_set_u16(config_handle, MODULE_TYPE_KEY, type); + nvs_close(config_handle); +} diff --git a/components/config/include/ConfigManager.h b/components/config/include/ConfigManager.h new file mode 100644 index 0000000..536b6a8 --- /dev/null +++ b/components/config/include/ConfigManager.h @@ -0,0 +1,21 @@ +// +// Created by Johnathon Slightham on 2025-06-12. +// + +#ifndef CONFIGMANAGER_H +#define CONFIGMANAGER_H + +#include "constants/config.h" + +class ConfigManager { +public: + static void init_config(); + + static uint16_t get_module_id(); + static ModuleType get_module_type(); + + static void set_module_id(uint16_t id); + static void set_module_type(ModuleType type); +}; + +#endif //CONFIGMANAGER_H diff --git a/components/constants/include/constants/config.h b/components/constants/include/constants/config.h new file mode 100644 index 0000000..32807fe --- /dev/null +++ b/components/constants/include/constants/config.h @@ -0,0 +1,20 @@ +// +// Created by Johnathon Slightham on 2025-06-12. +// + +#ifndef CONFIG_H +#define CONFIG_H + +#define NVS_FLASH_NAMESPACE "app" +#define DEFAULT_MODULE_ID 0 +#define DEFAULT_MODULE_TYPE 0 + +#define MODULE_ID_KEY "module_id" +#define MODULE_TYPE_KEY "module_type" + + +enum ModuleType { splitter }; // todo: maybe this should be flatbuffer + + + +#endif //CONFIG_H diff --git a/components/constants/include/constants/tcp.h b/components/constants/include/constants/tcp.h new file mode 100644 index 0000000..bd4ec8f --- /dev/null +++ b/components/constants/include/constants/tcp.h @@ -0,0 +1,18 @@ +// +// Created by Johnathon Slightham on 2025-06-01. +// + +#ifndef TCP_H +#define TCP_H + +#define TCP_DEFAULT_LISTEN_BACKLOG 1 + +#define KEEPALIVE_IDLE 7200 +#define KEEPALIVE_INTERVAL 75 +#define KEEPALIVE_COUNT 9 + +#define SLEEP_AFTER_FAIL_MS 5000 + +#define TCP_PORT 3001 + +#endif //TCP_H diff --git a/components/constants/include/constants/wifi.h b/components/constants/include/constants/wifi.h index c710911..5267c4b 100644 --- a/components/constants/include/constants/wifi.h +++ b/components/constants/include/constants/wifi.h @@ -5,8 +5,9 @@ #ifndef WIFI_H #define WIFI_H -#define WIFI_SSID "botchain" -#define SOFTAP_CHANNEL 1 -#define SOFTAP_MAX_CONNECTIONS 10 +// SoftAP Configuration +#define WIFI_SSID "botchain" +#define SOFTAP_CHANNEL 1 +#define SOFTAP_MAX_CONNECTIONS 10 #endif //WIFI_H diff --git a/components/logger/CMakeLists.txt b/components/logger/CMakeLists.txt deleted file mode 100644 index 90e2e28..0000000 --- a/components/logger/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -idf_component_register(SRCS "logger.cpp" - PRIV_REQUIRES - INCLUDE_DIRS "include") diff --git a/components/logger/include/logger.h b/components/logger/include/logger.h deleted file mode 100644 index c2d9c95..0000000 --- a/components/logger/include/logger.h +++ /dev/null @@ -1,20 +0,0 @@ -// -// Created by Johnathon Slightham on 2025-05-26. -// - -#ifndef LOGGER_H -#define LOGGER_H - -class Logger { -public: - static Logger &getInstance() { - static Logger instance; - return instance; - } - -private: - virtual Logger(); - -} - -#endif //LOGGER_H diff --git a/components/logger/logger.cpp b/components/logger/logger.cpp deleted file mode 100644 index 32d6587..0000000 --- a/components/logger/logger.cpp +++ /dev/null @@ -1,3 +0,0 @@ -// -// Created by Johnathon Slightham on 2025-05-26. -// diff --git a/components/rpc/CMakeLists.txt b/components/rpc/CMakeLists.txt index fd1ad7e..deece47 100644 --- a/components/rpc/CMakeLists.txt +++ b/components/rpc/CMakeLists.txt @@ -1,3 +1,3 @@ -idf_component_register(SRCS "rpc.cpp" "WifiManager.cpp" "mDNSDiscoveryService.cpp" - PRIV_REQUIRES driver esp_event nvs_flash esp_netif esp_wifi espressif__mdns constants +idf_component_register(SRCS "rpc.cpp" "WifiManager.cpp" "mDNSDiscoveryService.cpp" "TCPServer.cpp" + PRIV_REQUIRES driver esp_event nvs_flash esp_netif esp_wifi espressif__mdns constants config INCLUDE_DIRS "include") diff --git a/components/rpc/TCPServer.cpp b/components/rpc/TCPServer.cpp index e96ef99..cf06472 100644 --- a/components/rpc/TCPServer.cpp +++ b/components/rpc/TCPServer.cpp @@ -1,54 +1,199 @@ -#include - -#include "esp_system.h" -#include "esp_mac.h" +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_event.h" #include "esp_netif.h" -#include "esp_log.h" -#define HOST_IP_ADDR "192.168.0.196" -#define PORT 3001 -#define TAG "SOCKET" +#include "lwip/err.h" +#include "lwip/sockets.h" +#include "lwip/sys.h" +#include -static const char *payload = "Message from board"; +#include "TCPServer.h" -int tcp_client() { - char rx_buffer[128]; - char host_ip[] = HOST_IP_ADDR; - int addr_family = AF_INET; - int ip_protocol = IPPROTO_IP; +#include - struct sockaddr_in dest_addr; - inet_pton(AF_INET, host_ip, &dest_addr.sin_addr); // Convert ipv4 address to binary - dest_addr.sin_family = AF_INET; - dest_addr.sin_port = htons(PORT); +#include "constants/tcp.h" - int sock = socket(addr_family, SOCK_STREAM, ip_protocol); - if (sock < 0) { - ESP_LOGE(TAG, "Failed to create socket: %d", errno); - return sock; - } +// todo: - add message routing to correct client +// - authenticate (don't just return true from the auth function) +// - tx from board - if (0 != connect(sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr))) { - ESP_LOGE(TAG, "Failed to connect: %d", errno); - } +TCPServer::TCPServer(const int port) { + this->m_port = port; + this->m_mutex = xSemaphoreCreateMutex(); + this->m_clients = std::unordered_set(); + this->m_task = nullptr; + this->m_rx_task = nullptr; + this->m_tx_task = nullptr; - ESP_LOGI(TAG, "Connected"); + xTaskCreate(tcp_server_task, "tcp_accept_server", 4096, this, 5, &this->m_task); + xTaskCreate(socket_monitor_thread, "tcp_rx", 4096, this, 5, &this->m_rx_task); +} - while(1) { - int err = send(sock, payload, strlen(payload), 0); - if (err < 0) { - ESP_LOGE(TAG, "Error occurred during sending: %d", errno); +TCPServer::~TCPServer() { + vTaskDelete(this->m_task); + vTaskDelete(this->m_rx_task); + vTaskDelete(this->m_tx_task); + vSemaphoreDelete(this->m_mutex); +} + +[[noreturn]] void TCPServer::tcp_server_task(void *args) { + constexpr int keepAlive = 1; + constexpr int keepIdle = KEEPALIVE_IDLE; + constexpr int keepInterval = KEEPALIVE_INTERVAL; + constexpr int keepCount = KEEPALIVE_COUNT; + + auto that = static_cast(args); + + while (true) { + printf("Attempting to start TCP Server on port %d", that->m_port); + + that->m_server_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + if (that->m_server_sock < 0) { + printf("Unable to create TCP socket: errno %d\n", errno); + vTaskDelay(SLEEP_AFTER_FAIL_MS / portTICK_PERIOD_MS); + continue; } - int len = recv(sock, rx_buffer, sizeof(rx_buffer) -1, 0); // blocking call - if (len < 0) { - ESP_LOGE(TAG, "Failed to receive: %d", errno); - } else { - rx_buffer[len] = 0; // temp: Null terminate to treat as a string - ESP_LOGI(TAG, "Received %d bytes", len); - ESP_LOGI(TAG, "%s", rx_buffer); + constexpr int opt = 1; + setsockopt(that->m_server_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); + printf("Socket created\n"); + + sockaddr_in server_addr = { + .sin_family = AF_INET, + .sin_port = htons(that->m_port), + .sin_addr = { + .s_addr = htonl(INADDR_ANY), + }, + }; + + int err = bind(that->m_server_sock, reinterpret_cast(&server_addr), sizeof(server_addr)); + if (0 != err) { + printf("Socket unable to bind: errno %d\n", errno); + close(that->m_server_sock); + that->m_server_sock = -1; + vTaskDelay(SLEEP_AFTER_FAIL_MS / portTICK_PERIOD_MS); + continue; } - vTaskDelay(1000 / portTICK_PERIOD_MS); + printf("Socket bound to port %d\n", that->m_port); + + err = listen(that->m_server_sock, TCP_DEFAULT_LISTEN_BACKLOG); + if (0 != err) { + printf("Error occurred during TCP listen: errno %d\n", errno); + close(that->m_server_sock); + that->m_server_sock = -1; + vTaskDelay(SLEEP_AFTER_FAIL_MS / portTICK_PERIOD_MS); + continue; + } + + while (is_network_connected()) { + sockaddr_in client_addr{}; + socklen_t addr_len = sizeof(client_addr); + int client_sock = accept(that->m_server_sock, reinterpret_cast(&client_addr), &addr_len); + if (client_sock < 0) { + printf("Unable to accept TCP connection: errno %d\n", errno); + continue; + } + + err = that->authenticate_client(client_sock); + if (0 != err) { + printf("Client failed authentication\n"); + } + + setsockopt(client_sock, SOL_SOCKET, SO_KEEPALIVE, &keepAlive, sizeof(int)); + setsockopt(client_sock, IPPROTO_TCP, TCP_KEEPIDLE, &keepIdle, sizeof(int)); + setsockopt(client_sock, IPPROTO_TCP, TCP_KEEPINTVL, &keepInterval, sizeof(int)); + setsockopt(client_sock, IPPROTO_TCP, TCP_KEEPCNT, &keepCount, sizeof(int)); + + xSemaphoreTake(that->m_mutex, portMAX_DELAY); + that->m_clients.emplace(client_sock); + xSemaphoreGive(that->m_mutex); + } + + close(that->m_server_sock); + vTaskDelay(SLEEP_AFTER_FAIL_MS / portTICK_PERIOD_MS); } -} \ No newline at end of file +} + +[[noreturn]] void TCPServer::socket_monitor_thread(void *args) { + auto that = static_cast(args); + + while (true) { + fd_set readfds; + FD_ZERO(&readfds); + int max_fd = -1; + + xSemaphoreTake(that->m_mutex, portMAX_DELAY); + for (const auto sock : that->m_clients) { + FD_SET(sock, &readfds); + if (sock > max_fd) max_fd = sock; + } + xSemaphoreGive(that->m_mutex); + + timeval timeout = {1, 0}; // 1 second timeout + int ret = select(max_fd + 1, &readfds, nullptr, nullptr, &timeout); + + if (ret > 0) { + xSemaphoreTake(that->m_mutex, portMAX_DELAY); + std::vector to_remove; + for (int sock : that->m_clients) { + if (FD_ISSET(sock, &readfds)) { + // Handle socket + char buffer[512]; + int len = recv(sock, buffer, sizeof(buffer) - 1, 0); // temp: for the null terminator + + if (len < 0) { + printf("Error occurred during receiving: errno %d\n", errno); + } else if (0 == len) { + printf("Connection closed\n"); + close(sock); + to_remove.emplace_back(sock); + } else { + // todo: send to rx queue instead of printing + buffer[len] = 0; // temp: Null-terminate whatever is received and treat it like a string + printf("TCP Server Received %d bytes: %s\n", len, buffer); + } + } + } + + for (const auto r : to_remove) { + that->m_clients.erase(r); + } + + xSemaphoreGive(that->m_mutex); + } + } +} + +bool TCPServer::is_network_connected() { + esp_netif_ip_info_t ip_info; + esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"); + + if (netif != nullptr && esp_netif_get_ip_info(netif, &ip_info) == ESP_OK) { + return true; + } + + if (0 != ip_info.ip.addr) { + return true; + } + + netif = esp_netif_get_handle_from_ifkey("WIFI_AP_DEF"); + + if (netif != nullptr && esp_netif_get_ip_info(netif, &ip_info) == ESP_OK) { + return true; + } + + if (0 != ip_info.ip.addr) { + return true; + } + + return false; +} + +bool TCPServer::authenticate_client(int sock) { + // todo: authentication (wait for a passphrase from the client) + return 0; +} diff --git a/components/rpc/WifiManager.cpp b/components/rpc/WifiManager.cpp index f1ce63d..b0a0fa3 100644 --- a/components/rpc/WifiManager.cpp +++ b/components/rpc/WifiManager.cpp @@ -12,6 +12,7 @@ WifiManager::WifiManager() { esp_netif_init(); + esp_wifi_set_storage(WIFI_STORAGE_RAM); esp_event_loop_create_default(); this->m_mutex = xSemaphoreCreateMutex(); @@ -98,15 +99,15 @@ int WifiManager::init_connection() { wifi_config_t wifi_configuration; wifi_configuration = { .sta = { - .ssid = "dlink-C32D", + .ssid = "", .password = "", } }; + esp_wifi_set_mode(WIFI_MODE_STA); esp_wifi_set_config(static_cast(ESP_IF_WIFI_STA), &wifi_configuration); esp_wifi_start(); - esp_wifi_set_mode(WIFI_MODE_STA); esp_wifi_connect(); return 0; diff --git a/components/rpc/include/IRPCServer.h b/components/rpc/include/IRPCServer.h index 6a9db7a..9de70a1 100644 --- a/components/rpc/include/IRPCServer.h +++ b/components/rpc/include/IRPCServer.h @@ -7,6 +7,6 @@ class IRPCServer { -} +}; #endif //IRPCSERVER_H diff --git a/components/rpc/include/TCPServer.h b/components/rpc/include/TCPServer.h index 631ff3a..2945f25 100644 --- a/components/rpc/include/TCPServer.h +++ b/components/rpc/include/TCPServer.h @@ -5,15 +5,34 @@ #ifndef TCPSERVER_H #define TCPSERVER_H +#include +#include + +#include "freertos/FreeRTOS.h" + #include "IRPCServer.h" class TCPServer : IRPCServer { public: -TCPServer(); -~TCPServer(); + explicit TCPServer(int port); + ~TCPServer(); private: + bool authenticate_client(int client_sock); -} + static bool is_network_connected(); + [[noreturn]] static void tcp_server_task(void *args); + [[noreturn]] static void socket_monitor_thread(void *args); + + int m_port; + int m_server_sock; + + TaskHandle_t m_task; + TaskHandle_t m_rx_task; + TaskHandle_t m_tx_task; + + SemaphoreHandle_t m_mutex; + std::unordered_set m_clients; +}; #endif //TCPSERVER_H diff --git a/components/rpc/include/mDNSDiscoveryService.h b/components/rpc/include/mDNSDiscoveryService.h index 1cccdd1..1071818 100644 --- a/components/rpc/include/mDNSDiscoveryService.h +++ b/components/rpc/include/mDNSDiscoveryService.h @@ -5,9 +5,12 @@ #ifndef DISCOVERYSERVICE_H #define DISCOVERYSERVICE_H -class mDNSDiscoveryService { +class mDNSDiscoveryService final { public: - mDNSDiscoveryService(); + mDNSDiscoveryService() = delete; + ~mDNSDiscoveryService() = delete; + + static void setup(); }; #endif //DISCOVERYSERVICE_H diff --git a/components/rpc/mDNSDiscoveryService.cpp b/components/rpc/mDNSDiscoveryService.cpp index bba0829..2d2c9a6 100644 --- a/components/rpc/mDNSDiscoveryService.cpp +++ b/components/rpc/mDNSDiscoveryService.cpp @@ -4,13 +4,29 @@ #include "mdns.h" +#include "ConfigManager.h" + #include "mDNSDiscoveryService.h" -mDNSDiscoveryService::mDNSDiscoveryService() { - mdns_init(); - mdns_hostname_set("botchain-0000"); - mdns_instance_name_set("BotChain Robot Module 0000"); +#include "constants/tcp.h" - mdns_service_add(NULL, "_robotcontrol", "_udp", 3000, NULL, 0); - mdns_service_instance_name_set("_robotcontrol", "_udp", "Robot Control UDP Server"); +#include +#include + +// todo: clean this up (strange to be a constructor) also need to add more details, need to add to routing table +void mDNSDiscoveryService::setup() { + mdns_init(); + mdns_hostname_set(std::format("botchain-{}", ConfigManager::get_module_id()).c_str()); + mdns_instance_name_set(std::format("BotChain Robot Module {}", ConfigManager::get_module_id()).c_str()); + + mdns_service_add(nullptr, "_robotcontrol", "_tcp", TCP_PORT, nullptr, 0); + mdns_service_instance_name_set("_robotcontrol", "_tcp", "Robot Control TCP Server"); + + mdns_txt_item_t service_txt_data[3] = { + {"module_id",std::to_string(ConfigManager::get_module_id()).c_str()}, + {"module_type",std::to_string(ConfigManager::get_module_type()).c_str()}, + {"connected_modules","2,3,4,5,6,7,8,9"}, + }; + + mdns_service_txt_set("_robotcontrol", "_tcp", service_txt_data, 3); } diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index b550b8b..ff92540 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,3 +1,3 @@ idf_component_register(SRCS "main.cpp" - PRIV_REQUIRES spi_flash nvs_flash esp_event rpc + PRIV_REQUIRES spi_flash nvs_flash esp_event rpc constants config INCLUDE_DIRS "") diff --git a/main/main.cpp b/main/main.cpp index b06f086..b8decc2 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -4,19 +4,22 @@ #include "sdkconfig.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#include "esp_flash.h" -#include "nvs_flash.h" #include "WifiManager.h" #include "mDNSDiscoveryService.h" +#include "TCPServer.h" +#include "ConfigManager.h" +#include "constants/tcp.h" extern "C" [[noreturn]] void app_main(void) { - nvs_flash_init(); + ConfigManager::init_config(); const auto manager = std::make_unique(); manager->connect(); - const auto discovery = std::make_unique(); + mDNSDiscoveryService::setup(); + + const auto tcp_server = std::make_unique(TCP_PORT); printf("Hello world!\n");