diff --git a/firmware/.clang-format b/firmware/.clang-format new file mode 100644 index 0000000..db25e5a --- /dev/null +++ b/firmware/.clang-format @@ -0,0 +1,67 @@ +# Generated from CLion C/C++ Code Style settings +BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +#AlignConsecutiveAssignments: false +AlignOperands: true +AllowAllArgumentsOnNextLine: false +AllowAllConstructorInitializersOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Always +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Always +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterReturnType: None +AlwaysBreakTemplateDeclarations: Yes +BreakBeforeBraces: Custom +BraceWrapping: + AfterCaseLabel: true + AfterClass: true + AfterControlStatement: Always + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterUnion: true + AfterStruct: true + BeforeCatch: true + BeforeElse: true + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: true +BreakBeforeBinaryOperators: None +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeComma +ColumnLimit: 0 +CompactNamespaces: false +ContinuationIndentWidth: 8 +IndentCaseLabels: true +IndentPPDirectives: None +IndentWidth: 2 +KeepEmptyLinesAtTheStartOfBlocks: true +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: None +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PointerAlignment: Left +ReflowComments: false +SpaceAfterCStyleCast: true +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 0 +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInContainerLiterals: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +TabWidth: 2 +UseTab: Never diff --git a/firmware/.gitignore b/firmware/.gitignore new file mode 100644 index 0000000..a7110bd --- /dev/null +++ b/firmware/.gitignore @@ -0,0 +1,10 @@ +build/ +sdkconfig +sdkconfig.old + +managed_components/ + +.vscode/ +.idea/ + +cmake-build-*/ diff --git a/firmware/CMakeLists.txt b/firmware/CMakeLists.txt new file mode 100644 index 0000000..0a454d0 --- /dev/null +++ b/firmware/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(hello_world) diff --git a/firmware/main/CMakeLists.txt b/firmware/main/CMakeLists.txt new file mode 100644 index 0000000..6cd1b77 --- /dev/null +++ b/firmware/main/CMakeLists.txt @@ -0,0 +1,6 @@ +idf_component_register( + SRCS "main.cpp" "wifi_provisioner.cpp" + INCLUDE_DIRS "" + REQUIRES nvs_flash esp_wifi esp_http_server + EMBED_FILES "${CMAKE_CURRENT_LIST_DIR}/static/index.html" +) diff --git a/firmware/main/main.cpp b/firmware/main/main.cpp new file mode 100644 index 0000000..8eaa3e0 --- /dev/null +++ b/firmware/main/main.cpp @@ -0,0 +1,38 @@ +#include "nvs_flash.h" +#include "nvs.h" +#include "nvs_handle.hpp" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#include "sdkconfig.h" + +#include +#include +#include + +#include "wifi_provisioner.hpp" + +extern "C" void app_main(void) +{ + esp_err_t err = nvs_flash_init(); + if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) + { + // NVS partition was truncated and needs to be erased + // Retry nvs_flash_init + ESP_ERROR_CHECK(nvs_flash_erase()); + err = nvs_flash_init(); + } + + ESP_ERROR_CHECK(err); + + WifiProvisioner provisioner; + + if (!provisioner.wifiIsConfigured()) + { + auto result = provisioner.startProvisioning(); + if (!result.has_value()) + printf("Error: %s\n", esp_err_to_name(result.error())); + else + printf("Provisioning started...\n"); + } +} diff --git a/firmware/main/static/index.html b/firmware/main/static/index.html new file mode 100644 index 0000000..7a1f4b1 --- /dev/null +++ b/firmware/main/static/index.html @@ -0,0 +1,24 @@ + + + + + ESP Captive Portal + + +

ESP Captive Portal

+

Hello World, this is ESP32!

+ +
+
+
+
+
+ +
+ + + diff --git a/firmware/main/wifi_provisioner.cpp b/firmware/main/wifi_provisioner.cpp new file mode 100644 index 0000000..2dfe1a9 --- /dev/null +++ b/firmware/main/wifi_provisioner.cpp @@ -0,0 +1,182 @@ +// +// Created by erki on 07/01/24. +// + +#include "wifi_provisioner.hpp" + +#include +#include "esp_wifi.h" +#include "esp_mac.h" +#include "esp_log.h" + +#define TRY(x) ({ \ + const auto& _x = (x); \ + if (!_x) { \ + return std::unexpected(_x.error()); \ + } \ + _x.value(); \ +}) + +#define TRY_ESP(x) ({ \ + const esp_err_t& _x = (x); \ + if (_x != ESP_OK) \ + return std::unexpected(_x); \ + _x; \ +}) + +#define EXAMPLE_ESP_WIFI_SSID "ESP_TEST" +#define EXAMPLE_ESP_WIFI_PASS "1234567891" +#define EXAMPLE_ESP_WIFI_CHANNEL 5 +#define EXAMPLE_MAX_STA_CONN 5 + +extern const char index_start[] asm("_binary_index_html_start"); +extern const char index_end[] asm("_binary_index_html_end"); + +namespace +{ + +const char* NVS_WIFI_NAMESPACE = "wifi_prov"; +const char* NVS_WIFI_IS_INITIALIZED = "is_inited"; + +const char *TAG = "wifi softAP"; + +ssize_t indexHtmlLength() +{ + return std::distance(index_start, index_end); +} + +void wifiEventHandler_(void* arg, esp_event_base_t event_base, + int32_t event_id, void* event_data) +{ + if (event_id == WIFI_EVENT_AP_STACONNECTED) { + wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data; + ESP_LOGI(TAG, "station join, AID=%d", + event->aid); + } else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) { + wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data; + ESP_LOGI(TAG, "station leave, AID=%d", + event->aid); + } +} + +esp_err_t rootGetHandler_(httpd_req_t* req) +{ + httpd_resp_set_type(req, "text/html"); + httpd_resp_send(req, index_start, indexHtmlLength()); + return ESP_OK; +} + +esp_err_t rootPostHandler_(httpd_req_t* req) +{ + + //httpd_query_key_value + return ESP_OK; +} + +} + +WifiProvisioner::WifiProvisioner() +{ + esp_err_t err = ESP_OK; + file_handle_ = nvs::open_nvs_handle(NVS_WIFI_NAMESPACE, NVS_READWRITE, &err); + + ESP_ERROR_CHECK(err); + + err = file_handle_->get_item(NVS_WIFI_IS_INITIALIZED, wifi_initialized_); + + if (err != ESP_OK) + { + ESP_ERROR_CHECK(initializeNvsNamespace_()); + } +} + +std::expected WifiProvisioner::startProvisioning() +{ + TRY(initializeWifiAp_()); + + http_server_ = TRY(initializeCaptivePortal_()); + + return {}; +} + +esp_err_t WifiProvisioner::initializeNvsNamespace_() +{ + esp_err_t err = file_handle_->set_item(NVS_WIFI_IS_INITIALIZED, false); + + if (err != ESP_OK) + return err; + + err = file_handle_->commit(); + + return err; +} + +std::expected WifiProvisioner::initializeWifiAp_() +{ + TRY_ESP(esp_netif_init()); + TRY_ESP(esp_event_loop_create_default()); + esp_netif_create_default_wifi_ap(); + + wifi_init_config_t config = WIFI_INIT_CONFIG_DEFAULT(); + TRY_ESP(esp_wifi_init(&config)); + + TRY_ESP(esp_event_handler_instance_register( + WIFI_EVENT, ESP_EVENT_ANY_ID, &wifiEventHandler_, this, nullptr + )); + + //https://github.com/espressif/esp-idf/blob/5524b692ee5d04d7a1000eb0c41640746fc67f3c/examples/wifi/getting_started/softAP/main/softap_example_main.c + wifi_config_t wifi_config = { + .ap = { + .ssid = EXAMPLE_ESP_WIFI_SSID, + .password = EXAMPLE_ESP_WIFI_PASS, + .ssid_len = std::uint8_t(strlen(EXAMPLE_ESP_WIFI_SSID)), + .channel = EXAMPLE_ESP_WIFI_CHANNEL, +#ifdef CONFIG_ESP_WIFI_SOFTAP_SAE_SUPPORT + .authmode = WIFI_AUTH_WPA3_PSK, +#else /* CONFIG_ESP_WIFI_SOFTAP_SAE_SUPPORT */ + .authmode = WIFI_AUTH_WPA2_PSK, +#endif + .ssid_hidden = 0, + .max_connection = EXAMPLE_MAX_STA_CONN, + .pmf_cfg = { + .required = true, + }, +#ifdef CONFIG_ESP_WIFI_SOFTAP_SAE_SUPPORT + .sae_pwe_h2e = WPA3_SAE_PWE_BOTH, +#endif + } + }; + + if (strlen(EXAMPLE_ESP_WIFI_PASS) == 0) + wifi_config.ap.authmode = WIFI_AUTH_OPEN; + + TRY_ESP(esp_wifi_set_mode(WIFI_MODE_AP)); + TRY_ESP(esp_wifi_set_config(WIFI_IF_AP, &wifi_config)); + TRY_ESP(esp_wifi_start()); + + return {}; +} + +std::expected WifiProvisioner::initializeCaptivePortal_() +{ + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + httpd_handle_t server = nullptr; + + TRY_ESP(httpd_start(&server, &config)); + + static httpd_uri_t uri_main = { + .uri = "/", + .method = HTTP_GET, + .handler = rootGetHandler_, + .user_ctx = this + }; + + if (const auto err = httpd_register_uri_handler(server, &uri_main); + err != ESP_OK) + { + httpd_stop(server); + return std::unexpected(err); + } + + return server; +} diff --git a/firmware/main/wifi_provisioner.hpp b/firmware/main/wifi_provisioner.hpp new file mode 100644 index 0000000..a12c341 --- /dev/null +++ b/firmware/main/wifi_provisioner.hpp @@ -0,0 +1,31 @@ +// +// Created by erki on 07/01/24. +// + +#pragma once + +#include "nvs_handle.hpp" +#include +#include + +class WifiProvisioner +{ +public: + WifiProvisioner(); + + bool wifiIsConfigured() const + { + return wifi_initialized_; + } + + std::expected startProvisioning(); + +private: + std::unique_ptr file_handle_; + bool wifi_initialized_ = false; + httpd_handle_t http_server_ = nullptr; + + esp_err_t initializeNvsNamespace_(); + std::expected initializeWifiAp_(); + std::expected initializeCaptivePortal_(); +};