From 418e057377185a275f343fa7e514b780d92a8f4f Mon Sep 17 00:00:00 2001 From: Andreas Tsouchlos Date: Sat, 2 Mar 2024 15:27:26 +0100 Subject: [PATCH] Add wifi, nvs and inplace_function implementations --- components/nvs/CMakeLists.txt | 2 + components/nvs/include/nvs/nvs.h | 10 + components/nvs/src/nvs.cpp | 26 ++ components/util/CMakeLists.txt | 1 + .../util/include/util/inplace_function.h | 122 +++++++++ components/wifi/CMakeLists.txt | 10 + components/wifi/include/wifi/wifi.h | 57 ++++ components/wifi/src/wifi.cpp | 251 ++++++++++++++++++ main/CMakeLists.txt | 2 +- main/src/main.cpp | 86 +++--- 10 files changed, 519 insertions(+), 48 deletions(-) create mode 100644 components/nvs/CMakeLists.txt create mode 100644 components/nvs/include/nvs/nvs.h create mode 100644 components/nvs/src/nvs.cpp create mode 100644 components/util/CMakeLists.txt create mode 100644 components/util/include/util/inplace_function.h create mode 100644 components/wifi/include/wifi/wifi.h create mode 100644 components/wifi/src/wifi.cpp diff --git a/components/nvs/CMakeLists.txt b/components/nvs/CMakeLists.txt new file mode 100644 index 0000000..06f393d --- /dev/null +++ b/components/nvs/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "src/nvs.cpp" INCLUDE_DIRS include REQUIRES + nvs_flash) diff --git a/components/nvs/include/nvs/nvs.h b/components/nvs/include/nvs/nvs.h new file mode 100644 index 0000000..b1eede3 --- /dev/null +++ b/components/nvs/include/nvs/nvs.h @@ -0,0 +1,10 @@ +#pragma once + + +namespace nvs { + + +void init(); + + +} // namespace nvs diff --git a/components/nvs/src/nvs.cpp b/components/nvs/src/nvs.cpp new file mode 100644 index 0000000..25d13f4 --- /dev/null +++ b/components/nvs/src/nvs.cpp @@ -0,0 +1,26 @@ +#include + +#include "esp_err.h" +#include "nvs_flash.h" + + +namespace nvs { + + +void init() { + + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || + ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + + if (nvs_flash_erase() != ESP_OK) + throw std::runtime_error("Failed to erase NVS"); + + ret = nvs_flash_init(); + } + + if (ret != ESP_OK) throw std::runtime_error("Failed to initialize NVS"); +} + + +} // namespace nvs diff --git a/components/util/CMakeLists.txt b/components/util/CMakeLists.txt new file mode 100644 index 0000000..eafb7ac --- /dev/null +++ b/components/util/CMakeLists.txt @@ -0,0 +1 @@ +idf_component_register(INCLUDE_DIRS include) diff --git a/components/util/include/util/inplace_function.h b/components/util/include/util/inplace_function.h new file mode 100644 index 0000000..93cc254 --- /dev/null +++ b/components/util/include/util/inplace_function.h @@ -0,0 +1,122 @@ +#pragma once + + +#include +#include +#include +#include + + +template +class inplace_function; + +template +class inplace_function { + + using storage_t = std::byte[storage_size]; + using wrapper_func_t = return_t (*)(storage_t, args_t...); + +public: + inplace_function() = default; + + /// + /// @brief Constructor for function objects. + /// + template + inplace_function(F f) { + bind(f); + } + + /// + /// @brief Construct from free function pointer. + /// + template + static inplace_function construct() { + inplace_function func; + func.template bind(); + return func; + } + + /// + /// @brief Construct form member function pointer. + /// + template + static inplace_function construct(class_t& c) { + inplace_function func; + func.template bind(c); + return func; + } + + /// + /// @brief Construct from function object. + /// + template + static inplace_function construct(F f) { + inplace_function func; + func.bind(f); + return func; + } + + return_t operator()(args_t... args) { + if (mWrapper) + mWrapper(mStorage, args...); + else + throw std::bad_function_call(); + } + + operator bool() const { + return !(mWrapper == nullptr); + } + + /// + /// @brief Bind a free function. + /// + /// @tparam F Pointer to free function + /// + template + requires std::is_invocable_r_v + void bind() { + mWrapper = [](storage_t, args_t... args) { + std::invoke(F, std::forward(args)...); + }; + } + + /// + /// @brief Bind a member function. + /// + /// @tparam class_t Class the member belongs to. + /// @param c Reference to object the member function should be called on. + /// @tparam F Member function pointer. + /// + template + requires std::is_invocable_r_v && + (sizeof(class_t*) <= storage_size) + void bind(class_t& c) { + new (mStorage)(class_t*){&c}; + + mWrapper = [](storage_t storage, args_t... args) { + std::invoke(F, reinterpret_cast(storage), + std::forward(args)...); + }; + } + + /// + /// @brief Bind a function object. + /// + template + requires std::is_invocable_r_v && + (sizeof(F) <= storage_size) && + std::is_trivially_destructible_v + void bind(F f) { + new (mStorage) F{std::move(f)}; + mWrapper = [](storage_t storage, args_t... args) { + std::invoke(*reinterpret_cast(storage), + std::forward(args)...); + }; + } + +private: + storage_t mStorage; + wrapper_func_t mWrapper = nullptr; +}; diff --git a/components/wifi/CMakeLists.txt b/components/wifi/CMakeLists.txt index e69de29..c2f7688 100644 --- a/components/wifi/CMakeLists.txt +++ b/components/wifi/CMakeLists.txt @@ -0,0 +1,10 @@ +idf_component_register( + SRCS + "src/wifi.cpp" + INCLUDE_DIRS + include + REQUIRES + esp_wifi + util + nvs + nanofmt) diff --git a/components/wifi/include/wifi/wifi.h b/components/wifi/include/wifi/wifi.h new file mode 100644 index 0000000..511ac3d --- /dev/null +++ b/components/wifi/include/wifi/wifi.h @@ -0,0 +1,57 @@ +#pragma once + + +#include +#include + +#include + + +namespace wifi { + + +enum class Mode { station, soft_ap, /* station_and_soft_ap */ }; + + +// TODO: Authmode +struct soft_ap_settings_t { + uint8_t channel = 11; + uint8_t max_connections = 5; + + inplace_function, uint8_t)> connected; + inplace_function, uint8_t)> disconnected; +}; + +struct station_settings_t { + inplace_function stationStarted = [] {}; + inplace_function disconnected = [] {}; + inplace_function)> gotIp = [](auto...) {}; +}; + +struct scanned_ap_t { + std::array ssid = {0}; + int rssi; +}; + + +void init(); +void deinit(); + +void set_mode(Mode mode); + +void configure_soft_ap(std::string_view ssid, std::string_view password, + soft_ap_settings_t settings = {}); +void configure_station(std::string_view ssid, std::string_view password, + station_settings_t settings = {}); +void clear_callbacks(); + +void station_connect(); +void station_disconnect(); + +void start(); +void stop(); + +std::span scan(std::span memory); + + +} // namespace wifi diff --git a/components/wifi/src/wifi.cpp b/components/wifi/src/wifi.cpp new file mode 100644 index 0000000..ed10b6d --- /dev/null +++ b/components/wifi/src/wifi.cpp @@ -0,0 +1,251 @@ +#include "wifi/wifi.h" +#include +#include + +#include "esp_event_base.h" +#include "esp_netif.h" +#include "esp_netif_ip_addr.h" +#include "esp_netif_types.h" +#include "esp_wifi.h" +#include "esp_wifi_types.h" + +#include + + +namespace { + + +esp_netif_t* gSoftApNetif; +esp_netif_t* gStationNetif; + +inplace_function, uint8_t)> gAPConnectCallback = + [](auto...) {}; +inplace_function, uint8_t)> gAPDisconnectCallback = + [](auto...) {}; + +inplace_function gStationStartCallback = [] {}; +inplace_function gStationDisconnectCallback = [] {}; +inplace_function)> gStationGotIpCallback = + [](auto...) {}; + + +void wifi_event_handler(void*, esp_event_base_t event_base, int32_t event_id, + void* event_data) { + if (event_base == WIFI_EVENT) { + if (event_id == WIFI_EVENT_STA_START) { + gStationStartCallback(); + } else if (event_id == WIFI_EVENT_STA_DISCONNECTED) { + gStationDisconnectCallback(); + } else if (event_id == WIFI_EVENT_AP_STACONNECTED) { + auto disconnectEvent = + reinterpret_cast(event_data); + + std::array macStr = {0}; + std::snprintf(macStr.data(), macStr.size(), + "%02x:%02x:%02x:%02x:%02x:%02x", + disconnectEvent->mac[0], disconnectEvent->mac[1], + disconnectEvent->mac[2], disconnectEvent->mac[3], + disconnectEvent->mac[4], disconnectEvent->mac[5]); + + gAPConnectCallback(macStr, disconnectEvent->aid); + } else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) { + auto disconnectEvent = + reinterpret_cast(event_data); + + std::array macStr = {0}; + std::snprintf(macStr.data(), macStr.size(), + "%02x:%02x:%02x:%02x:%02x:%02x", + disconnectEvent->mac[0], disconnectEvent->mac[1], + disconnectEvent->mac[2], disconnectEvent->mac[3], + disconnectEvent->mac[4], disconnectEvent->mac[5]); + + gAPDisconnectCallback(macStr, disconnectEvent->aid); + } + } else if (event_base == IP_EVENT) { + if (event_id == IP_EVENT_STA_GOT_IP) { + auto event = reinterpret_cast(event_data); + + auto ipAddr = &event->ip_info.ip; + + std::array ipStr = {0}; + std::snprintf(ipStr.data(), ipStr.size(), "%d.%d.%d.%d", + esp_ip4_addr1_16(ipAddr), esp_ip4_addr2_16(ipAddr), + esp_ip4_addr3_16(ipAddr), esp_ip4_addr4_16(ipAddr)); + + gStationGotIpCallback(ipStr); + } + } +} + + +} // namespace + + +namespace wifi { + + +void init() { + if (esp_netif_init() != ESP_OK) + throw std::runtime_error("Failed to initialize netif"); + + if (esp_event_loop_create_default() != ESP_OK) + throw std::runtime_error("Failed to create event loop"); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + if (esp_wifi_init(&cfg) != ESP_OK) + throw std::runtime_error("Failed to initialize wifi"); + + if (esp_event_handler_instance_register( + ESP_EVENT_ANY_BASE, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, + NULL) != ESP_OK) + throw std::runtime_error("Failed to register wifi event handler"); +} + +void deinit() { + if (esp_wifi_deinit() != ESP_OK) + throw std::runtime_error("Failed to deinitialize wifi"); + + if (esp_event_loop_delete_default() != ESP_OK) + throw std::runtime_error("Failed to delete event loop"); + + // Apparently, this is not implemented in the esp idf yet + // esp_netif_deinit(); +} + +void set_mode(Mode mode) { + wifi_mode_t esp_mode = WIFI_MODE_NULL; + + switch (mode) { + case Mode::station: + esp_mode = WIFI_MODE_STA; + break; + case Mode::soft_ap: + esp_mode = WIFI_MODE_AP; + break; + // case Mode::station_and_soft_ap: + // esp_mode = WIFI_MODE_APSTA; + // break; + } + + if (esp_wifi_set_mode(esp_mode) != ESP_OK) + throw std::runtime_error("Failed to set wifi mode"); +} + +void configure_soft_ap(std::string_view ssid, std::string_view password, + soft_ap_settings_t settings) { + + // Create network interface + + if (gSoftApNetif != nullptr) esp_netif_destroy_default_wifi(gSoftApNetif); + gSoftApNetif = esp_netif_create_default_wifi_ap(); + + // Configure wifi + + wifi_config_t wifi_config = { + .ap = + { + .ssid_len = static_cast(ssid.size()), + .channel = settings.channel, + .authmode = + password.size() != 0 ? WIFI_AUTH_WPA3_PSK : WIFI_AUTH_OPEN, + .max_connection = settings.max_connections, + .pmf_cfg = + { + .required = true, + }, + .sae_pwe_h2e = WPA3_SAE_PWE_BOTH, + }, + }; + std::copy(ssid.begin(), ssid.end(), wifi_config.ap.ssid); + std::copy(password.begin(), password.end(), wifi_config.ap.password); + + if (esp_wifi_set_config(WIFI_IF_AP, &wifi_config) != ESP_OK) + throw std::runtime_error("Failed to set wifi config"); + + // Register event handlers + + gAPConnectCallback = settings.connected; + gAPDisconnectCallback = settings.disconnected; +} + +void configure_station(std::string_view ssid, std::string_view password, + station_settings_t settings) { + + // Create network interface + + if (gStationNetif != nullptr) esp_netif_destroy_default_wifi(gStationNetif); + gStationNetif = esp_netif_create_default_wifi_sta(); + + + // Configure wifi + + // TODO: Are there really no other settings necessary? + wifi_config_t wifi_config = {}; + std::copy(ssid.begin(), ssid.end(), wifi_config.sta.ssid); + std::copy(password.begin(), password.end(), wifi_config.sta.password); + + if (esp_wifi_set_config(WIFI_IF_STA, &wifi_config) != ESP_OK) + throw std::runtime_error("Failed to configure wifi station"); + + // Set event handlers + + gStationStartCallback = settings.stationStarted; + gStationDisconnectCallback = settings.disconnected; + gStationGotIpCallback = settings.gotIp; +} + +void clear_callbacks() { + gAPConnectCallback = [](auto...) {}; + gAPDisconnectCallback = [](auto...) {}; + gStationStartCallback = [] {}; + gStationGotIpCallback = [](auto...) {}; + gStationDisconnectCallback = [] {}; +} + +void station_connect() { + if (esp_wifi_connect() != ESP_OK) + throw std::runtime_error("Failed to connect to wifi"); +} + +void station_disconnect() { + if (esp_wifi_disconnect() != ESP_OK) + throw std::runtime_error("Failed to disconnect from wifi"); +} + +void start() { + if (esp_wifi_start() != ESP_OK) + throw std::runtime_error("Failed to start wifi"); +} + +void stop() { + if (esp_wifi_stop() != ESP_OK) + throw std::runtime_error("Failed to stop wifi"); +} + +std::span scan(std::span memory) { + uint16_t number = memory.size(); + wifi_ap_record_t ap_info[number]; + + esp_err_t err; + + if ((err = esp_wifi_scan_start(NULL, true)) != ESP_OK) { + throw std::runtime_error("Failed to start scan"); + } + + if (esp_wifi_scan_get_ap_records(&number, ap_info) != ESP_OK) + throw std::runtime_error("Failed to get wifi ap records"); + + std::transform(&ap_info[0], &ap_info[0] + number, memory.begin(), + [](const wifi_ap_record_t& record) { + scanned_ap_t result; + std::copy(&record.ssid[0], &record.ssid[0] + 32, + result.ssid.begin()); + result.rssi = record.rssi; + return result; + }); + + return std::span{memory.begin(), number}; +} + + +} // namespace wifi diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 91aef2d..7bd4829 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,4 +1,4 @@ set(SOURCES src/main.cpp) set(INCLUDES include) -idf_component_register(SRCS ${SOURCES} INCLUDE_DIRS ${INCLUDES}) +idf_component_register(SRCS ${SOURCES} INCLUDE_DIRS ${INCLUDES} REQUIRES wifi nvs nanofmt) diff --git a/main/src/main.cpp b/main/src/main.cpp index 42fa37f..f2efe21 100644 --- a/main/src/main.cpp +++ b/main/src/main.cpp @@ -1,54 +1,46 @@ -/* - * SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: CC0-1.0 - */ +#include +#include -#include -#include -#include "sdkconfig.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "esp_chip_info.h" -#include "esp_flash.h" -#include "esp_system.h" +#include "esp_log.h" -#include "nanofmt/format.h" -extern "C" void app_main(void) -{ - printf("Hello world!\n"); +extern "C" void app_main(void) { + try { + nvs::init(); + wifi::init(); - /* Print chip information */ - esp_chip_info_t chip_info; - uint32_t flash_size; - esp_chip_info(&chip_info); - printf("This is %s chip with %d CPU core(s), %s%s%s%s, ", - CONFIG_IDF_TARGET, - chip_info.cores, - (chip_info.features & CHIP_FEATURE_WIFI_BGN) ? "WiFi/" : "", - (chip_info.features & CHIP_FEATURE_BT) ? "BT" : "", - (chip_info.features & CHIP_FEATURE_BLE) ? "BLE" : "", - (chip_info.features & CHIP_FEATURE_IEEE802154) ? ", 802.15.4 (Zigbee/Thread)" : ""); + // wifi::set_mode(wifi::Mode::station); + // wifi::configure_station( + // "Vodafone-180C", "giraffen!", + // {.stationStarted = []() { wifi::station_connect(); }, + // .disconnected = [] { wifi::station_connect(); }, + // .gotIp = + // [](auto ip) { + // ESP_LOGI("DEBUG - ip callback ", "Got ip: %s", + // ip.data()); + // }}); + // wifi::start(); - unsigned major_rev = chip_info.revision / 100; - unsigned minor_rev = chip_info.revision % 100; - printf("silicon revision v%d.%d, ", major_rev, minor_rev); - if(esp_flash_get_size(NULL, &flash_size) != ESP_OK) { - printf("Get flash size failed"); - return; + // wifi::stop(); + // wifi::clear_callbacks(); + // wifi::stop(); + // wifi::deinit(); + + wifi::set_mode(wifi::Mode::soft_ap); + wifi::configure_soft_ap( + "HyperLink", "1234567890", + {.connected = + [](auto mac, auto aid) { + ESP_LOGI("DEBUG - ap callback", "Connected to mac %s", + mac.data()); + }, + .disconnected = + [](auto mac, auto aid) { + ESP_LOGI("DEBUG - ap callback", "Disconnected from mac %s", + mac.data()); + }}); + wifi::start(); + } catch (const std::exception& e) { + ESP_LOGE("main", "Exception: %s", e.what()); } - - printf("%" PRIu32 "MB %s flash\n", flash_size / (uint32_t)(1024 * 1024), - (chip_info.features & CHIP_FEATURE_EMB_FLASH) ? "embedded" : "external"); - - printf("Minimum free heap size: %" PRIu32 " bytes\n", esp_get_minimum_free_heap_size()); - - for (int i = 10; i >= 0; i--) { - printf("Restarting in %d seconds...\n", i); - vTaskDelay(1000 / portTICK_PERIOD_MS); - } - printf("Restarting now.\n"); - fflush(stdout); - esp_restart(); }