Compare commits

...

5 Commits

Author SHA1 Message Date
Erki
57d068ffad Firmware: enable syncing to NTP 2024-02-10 20:45:54 +02:00
Erki
b42bb7efa9 Firmware: implement connecting to configured wifi 2024-02-10 20:01:36 +02:00
Erki
d012620386 Firmware: fix provisioner not updating its initialization state properly 2024-02-10 20:00:55 +02:00
Erki
2dd46ee686 Firmware: clean-up 2024-01-23 14:42:48 +02:00
Erki
0ed3d69906 Firmware: Final work for the provisioner 2024-01-23 10:47:33 +02:00
8 changed files with 358 additions and 19 deletions

View File

@ -3,4 +3,4 @@
cmake_minimum_required(VERSION 3.16) cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake) include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(hello_world) project(esp-vfd-clock)

View File

@ -1,5 +1,5 @@
idf_component_register( idf_component_register(
SRCS "main.cpp" "wifi_provisioner.cpp" SRCS "main.cpp" "wifi_provisioner.cpp" "clock_core.cpp"
INCLUDE_DIRS "" INCLUDE_DIRS ""
REQUIRES nvs_flash esp_wifi esp_http_server etlcpp json REQUIRES nvs_flash esp_wifi esp_http_server etlcpp json
EMBED_FILES "${CMAKE_CURRENT_LIST_DIR}/static/index.html" EMBED_FILES "${CMAKE_CURRENT_LIST_DIR}/static/index.html"

View File

@ -0,0 +1,166 @@
//
// Created by erki on 25/01/24.
//
#include "clock_core.hpp"
#include "esp_expected.hpp"
#include <cstring>
#include <esp_event.h>
#include <esp_log.h>
#include <esp_netif.h>
#include <esp_netif_sntp.h>
#include <esp_wifi.h>
namespace
{
const char* TAG = "clock_core";
constexpr EventBits_t WIFI_FAIL_BIT = BIT0;
constexpr EventBits_t WIFI_CONNECTED_BIT = BIT1;
constexpr EventBits_t SNTP_SYNCED = BIT2;
EventGroupHandle_t clock_core_event_group_;
void wifiEventHandler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
static int retry_num = 0;
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
{
esp_wifi_connect();
}
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
{
if (retry_num < 10)
{
esp_wifi_connect();
retry_num++;
ESP_LOGI(TAG, "Retrying to connect to AP.");
}
else
{
xEventGroupSetBits(clock_core_event_group_, WIFI_FAIL_BIT);
}
ESP_LOGW(TAG, "Connection to AP failed.");
}
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
{
auto* event = static_cast<ip_event_got_ip_t*>(event_data);
ESP_LOGI(TAG, "Connected to AP. Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
retry_num = 0;
xEventGroupSetBits(clock_core_event_group_, WIFI_CONNECTED_BIT);
}
}
void sntpEventHandler(struct timeval*)
{
xEventGroupSetBits(clock_core_event_group_, SNTP_SYNCED);
}
esp_err_t sntpInitialize()
{
esp_sntp_config_t config = ESP_NETIF_SNTP_DEFAULT_CONFIG("pool.ntp.org");
config.smooth_sync = true;
config.server_from_dhcp = true;
config.renew_servers_after_new_IP = true;
config.sync_cb = sntpEventHandler;
config.start = false;
config.ip_event_to_renew = IP_EVENT_STA_GOT_IP;
return esp_netif_sntp_init(&config);
}
std::expected<void, esp_err_t> wifiInitialize(const char* wifi_ssid, const char* wifi_password)
{
TRY_ESP(esp_netif_init());
TRY_ESP(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t config = WIFI_INIT_CONFIG_DEFAULT();
TRY_ESP(esp_wifi_init(&config));
TRY_ESP(sntpInitialize());
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
TRY_ESP(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&wifiEventHandler,
nullptr,
&instance_any_id));
TRY_ESP(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&wifiEventHandler,
nullptr,
&instance_got_ip));
wifi_sta_config_t sta_config;
std::memset(&sta_config, 0, sizeof(sta_config));
std::strncpy((char*) sta_config.ssid, wifi_ssid, 32);
std::strncpy((char*) sta_config.password, wifi_password, 64);
sta_config.threshold.authmode = WIFI_AUTH_WPA2_PSK;
wifi_config_t wifi_config = { .sta = sta_config };
TRY_ESP(esp_wifi_set_mode(WIFI_MODE_STA));
TRY_ESP(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
TRY_ESP(esp_wifi_start());
return {};
}
}
namespace clock_core
{
void run(const char* wifi_ssid, const char* wifi_password)
{
clock_core_event_group_ = xEventGroupCreate();
if (const auto res = wifiInitialize(wifi_ssid, wifi_password);
!res.has_value())
{
ESP_LOGI(TAG, "WiFi setup errored in clock_core::run. Error: %s", esp_err_to_name(res.error()));
abort();
}
while (true)
{
const EventBits_t bits = xEventGroupWaitBits(clock_core_event_group_,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT | SNTP_SYNCED,
pdTRUE,
pdFALSE,
portMAX_DELAY);
if (bits & WIFI_CONNECTED_BIT)
{
ESP_LOGI(TAG, "WiFi setup in clock_core::run successful.");
if (const auto res = esp_netif_sntp_start();
res != ESP_OK)
{
ESP_LOGE(TAG, "SNTP setup failed. Error: %s", esp_err_to_name(res));
abort();
}
}
else if (bits & WIFI_FAIL_BIT)
{
ESP_LOGE(TAG, "WiFi setup in clock_core::run failed.");
}
else if (bits & SNTP_SYNCED)
{
ESP_LOGI(TAG, "SNTP sync successful.");
}
else
{
ESP_LOGE(TAG, "WiFi setup encountered an unexpected set of bits. %lu", bits);
}
}
}
}

View File

@ -0,0 +1,12 @@
//
// Created by erki on 25/01/24.
//
#pragma once
namespace clock_core
{
void run(const char* wifi_ssid, const char* wifi_password);
}

View File

@ -5,7 +5,13 @@
#pragma once #pragma once
#include <expected> #include <expected>
#include <source_location>
#include <string_view>
#include <functional>
#include <utility>
#include <esp_err.h> #include <esp_err.h>
#include <esp_rom_sys.h>
#define TRY(x) ({ \ #define TRY(x) ({ \
const auto& _x = (x); \ const auto& _x = (x); \
@ -21,3 +27,33 @@
return std::unexpected(_x); \ return std::unexpected(_x); \
_x; \ _x; \
}) })
[[noreturn]] inline void abortWithError(const char* error, const std::source_location location = std::source_location::current())
{
esp_rom_printf("abortWithError called, reason: %s\n", error);
esp_rom_printf("file: \"%s\" line %d\n", location.file_name(), location.line());
if (location.function_name() != nullptr)
esp_rom_printf("func: %s\n", location.function_name());
abort();
}
template<typename Expected>
decltype(auto) unwrapOrAbort(Expected&& expected, const std::source_location location = std::source_location::current())
{
if (!expected.has_value())
abortWithError("Unwrapped an errored expected.", location);
else
return std::forward<Expected>(expected).value();
}
template<typename Expected, typename F>
decltype(auto) unwrapOr(Expected&& expected, F&& f)
{
if (!expected.has_value())
std::invoke(std::forward<F>(f), expected.error());
else
return std::forward<Expected>(expected).value();
std::unreachable();
}

View File

@ -1,16 +1,17 @@
#include "nvs_flash.h" #include "nvs_flash.h"
#include "nvs.h" #include "nvs.h"
#include "nvs_handle.hpp"
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
#include "sdkconfig.h" #include "sdkconfig.h"
#include <inttypes.h> #include <cinttypes>
#include <stdio.h> #include <cstdio>
#include <expected> #include <expected>
#include "esp_expected.hpp"
#include "wifi_provisioner.hpp" #include "wifi_provisioner.hpp"
#include "clock_core.hpp"
extern "C" void app_main(void) extern "C" void app_main(void)
{ {
@ -25,9 +26,10 @@ extern "C" void app_main(void)
ESP_ERROR_CHECK(err); ESP_ERROR_CHECK(err);
auto* provisioner = new WifiProvisioner("wifi_settings", [](const auto& params) auto* provisioner = new WifiProvisioner("wifi_settings", [task_id = xTaskGetCurrentTaskHandle()](const auto& params)
{ {
printf("Settings successfully done."); printf("Settings successfully done.");
xTaskNotify(task_id, 0, eNoAction);
}); });
if (!provisioner->addParameter("SSID", "ssid", WifiProvisioner::Parameter::Type::STRING).has_value()) if (!provisioner->addParameter("SSID", "ssid", WifiProvisioner::Parameter::Type::STRING).has_value())
{ {
@ -41,12 +43,45 @@ extern "C" void app_main(void)
return; return;
} }
// @todo: Remove later.
// provisioner->clearSettings();
if (!provisioner->parametersAreConfigured()) if (!provisioner->parametersAreConfigured())
{ {
auto result = provisioner->startProvisioning(); auto result = provisioner->startProvisioning();
if (!result.has_value()) if (!result.has_value())
printf("Error: %s\n", esp_err_to_name(result.error())); {
else ESP_ERROR_CHECK(result.error());
}
printf("Provisioning started...\n"); printf("Provisioning started...\n");
const auto notification_received = xTaskNotifyWait(pdFALSE, ULONG_MAX, nullptr, portMAX_DELAY);
if (notification_received == pdPASS)
{
printf("Notification happened.");
}
else
{
abortWithError("Main task notification timed out.");
} }
} }
const auto password = unwrapOrAbort(provisioner->getParameter("password")
.and_then([] (const auto& value)
{
return value.template getValue<etl::string<100>>();
}));
const auto ssid = unwrapOrAbort(provisioner->getParameter("ssid")
.and_then([] (const auto& value)
{
return value.template getValue<etl::string<100>>();
}));
// Pack up the provisioner.
delete provisioner;
printf("We now have a wifi with SSID: %s, password: %s\n", ssid.c_str(), password.c_str());
clock_core::run(ssid.c_str(), password.c_str());
}

View File

@ -25,7 +25,7 @@ extern const char index_end[] asm("_binary_index_html_end");
namespace namespace
{ {
const char* TAG = "wifi softAP"; const char* TAG = "ParamProvisioner";
const char* NVS_IS_INITED = "_is_inited"; const char* NVS_IS_INITED = "_is_inited";
ssize_t indexHtmlLength() ssize_t indexHtmlLength()
@ -156,20 +156,23 @@ esp_err_t parametersGetHandler_(httpd_req_t* req)
} }
std::expected<WifiProvisioner::Parameter::Value, esp_err_t> WifiProvisioner::Parameter::getValue(nvs::NVSHandle* file_handle) const std::expected<WifiProvisioner::Parameter::Value, esp_err_t> WifiProvisioner::Parameter::tryReadAndAssignValue(nvs::NVSHandle* file_handle)
{ {
if (type == Type::INT) if (type == Type::INT)
{ {
std::int32_t value = 0; std::int32_t value = 0;
TRY_ESP(file_handle->get_item(nvs_name.c_str(), value)); TRY_ESP(file_handle->get_item(nvs_name.c_str(), value));
data = value;
return value; return data;
} }
else if (type == Type::FLOAT) else if (type == Type::FLOAT)
{ {
std::int32_t value_raw = 0; std::int32_t value_raw = 0;
TRY_ESP(file_handle->get_item(nvs_name.c_str(), value_raw)); TRY_ESP(file_handle->get_item(nvs_name.c_str(), value_raw));
return std::bit_cast<float>(value_raw); data = std::bit_cast<float>(value_raw);
return data;
} }
else if (type == Type::STRING) else if (type == Type::STRING)
{ {
@ -179,7 +182,9 @@ std::expected<WifiProvisioner::Parameter::Value, esp_err_t> WifiProvisioner::Par
TRY_ESP(file_handle->get_string(nvs_name.c_str(), value.data(), value.size() - 1)); TRY_ESP(file_handle->get_string(nvs_name.c_str(), value.data(), value.size() - 1));
value.trim_to_terminator(); value.trim_to_terminator();
return value; data = value;
return data;
} }
return ESP_FAIL; return ESP_FAIL;
@ -241,18 +246,34 @@ WifiProvisioner::~WifiProvisioner()
{ {
httpd_stop(http_server_); httpd_stop(http_server_);
} }
if (esp_netif_)
{
esp_wifi_stop();
esp_wifi_deinit();
esp_netif_destroy_default_wifi(esp_netif_);
esp_event_loop_delete_default();
}
} }
bool WifiProvisioner::parametersAreConfigured() bool WifiProvisioner::parametersAreConfigured()
{ {
if (!settings_initialized_)
return false;
if (params_.empty()) if (params_.empty())
return false; return false;
for (const auto& param : params_) for (auto& param : params_)
{ {
if (const auto value = param.getValue(file_handle_.get()); if (const auto value = param.tryReadAndAssignValue(file_handle_.get());
!value.has_value()) !value.has_value())
{ {
settings_initialized_ = false;
file_handle_->set_item(NVS_IS_INITED, settings_initialized_);
file_handle_->commit();
return false; return false;
} }
} }
@ -260,6 +281,16 @@ bool WifiProvisioner::parametersAreConfigured()
return true; return true;
} }
void WifiProvisioner::clearSettings()
{
if (!settings_initialized_)
return;
settings_initialized_ = false;
ESP_ERROR_CHECK(file_handle_->set_item(NVS_IS_INITED, settings_initialized_));
ESP_ERROR_CHECK(file_handle_->commit());
}
std::expected<void, esp_err_t> WifiProvisioner::startProvisioning() std::expected<void, esp_err_t> WifiProvisioner::startProvisioning()
{ {
TRY(initializeWifiAp_()); TRY(initializeWifiAp_());
@ -269,6 +300,28 @@ std::expected<void, esp_err_t> WifiProvisioner::startProvisioning()
return {}; return {};
} }
const etl::vector<WifiProvisioner::Parameter, 10>& WifiProvisioner::getParameters()
{
if (!settings_initialized_)
abortWithError("WifiProvisioner read parameters without initialization.");
return params_;
}
std::expected<WifiProvisioner::Parameter, std::error_code> WifiProvisioner::getParameter(const etl::string<15>& name)
{
if (!settings_initialized_)
return std::unexpected{std::make_error_code(std::errc::not_connected)};
for (const auto& param : params_)
{
if (param.nvs_name == name)
return param;
}
return std::unexpected{std::make_error_code(std::errc::invalid_argument)};
}
std::expected<void, std::error_code> WifiProvisioner::addParameter(const char* name, const char* nvs_name, const Parameter::Type type) std::expected<void, std::error_code> WifiProvisioner::addParameter(const char* name, const char* nvs_name, const Parameter::Type type)
{ {
if (params_.full()) if (params_.full())
@ -338,7 +391,7 @@ std::expected<void, esp_err_t> WifiProvisioner::initializeWifiAp_()
{ {
TRY_ESP(esp_netif_init()); TRY_ESP(esp_netif_init());
TRY_ESP(esp_event_loop_create_default()); TRY_ESP(esp_event_loop_create_default());
esp_netif_create_default_wifi_ap(); esp_netif_ = esp_netif_create_default_wifi_ap();
wifi_init_config_t config = WIFI_INIT_CONFIG_DEFAULT(); wifi_init_config_t config = WIFI_INIT_CONFIG_DEFAULT();
TRY_ESP(esp_wifi_init(&config)); TRY_ESP(esp_wifi_init(&config));

View File

@ -9,6 +9,7 @@
#include <etl/vector.h> #include <etl/vector.h>
#include <etl/string.h> #include <etl/string.h>
#include <etl/delegate.h> #include <etl/delegate.h>
#include <cstdint>
#include <expected> #include <expected>
#include <variant> #include <variant>
#include <memory> #include <memory>
@ -53,7 +54,7 @@ public:
Type type; Type type;
using Value = std::variant<std::int32_t, float, etl::string<100>>; using Value = std::variant<std::monostate, std::int32_t, float, etl::string<100>>;
Value data; Value data;
Parameter() = default; Parameter() = default;
@ -61,10 +62,43 @@ public:
: name(name) : name(name)
, nvs_name(nvs_name) , nvs_name(nvs_name)
, type(type) , type(type)
, data(std::monostate())
{ } { }
std::expected<Value, esp_err_t> getValue(nvs::NVSHandle* file_handle) const; std::expected<Value, esp_err_t> tryReadAndAssignValue(nvs::NVSHandle* file_handle);
std::expected<Value, const char*> tryValidateAndAssign(cJSON* object); std::expected<Value, const char*> tryValidateAndAssign(cJSON* object);
template<typename T>
std::expected<T, std::error_code> getValue() const
{
static_assert(std::is_same_v<T, std::int32_t> || std::is_same_v<T, float> || std::is_same_v<T, etl::string<100>>,
"T must be in the Value variant.");
if (holds_alternative<std::monostate>(data))
return std::unexpected(std::make_error_code(std::errc::io_error));
if constexpr (std::is_same_v<T, std::int32_t>)
{
if (type != Type::INT)
return std::unexpected(std::make_error_code(std::errc::invalid_argument));
return std::get<std::int32_t>(data);
}
else if constexpr (std::is_same_v<T, float>)
{
if (type != Type::FLOAT)
return std::unexpected(std::make_error_code(std::errc::invalid_argument));
return std::get<float>(data);
}
else
{
if (type != Type::STRING)
return std::unexpected(std::make_error_code(std::errc::invalid_argument));
return std::get<etl::string<100>>(data);
}
}
}; };
explicit WifiProvisioner(const char* settings_namespace, etl::delegate<void (const etl::vector<Parameter, 10>&)> success_cb); explicit WifiProvisioner(const char* settings_namespace, etl::delegate<void (const etl::vector<Parameter, 10>&)> success_cb);
@ -77,8 +111,10 @@ public:
WifiProvisioner& operator=(WifiProvisioner&&) = delete; WifiProvisioner& operator=(WifiProvisioner&&) = delete;
bool parametersAreConfigured(); bool parametersAreConfigured();
void clearSettings();
std::expected<void, esp_err_t> startProvisioning(); std::expected<void, esp_err_t> startProvisioning();
const etl::vector<Parameter, 10>& getParameters();
std::expected<Parameter, std::error_code> getParameter(const etl::string<15>& name);
std::expected<void, std::error_code> addParameter(const char* name, const char* nvs_name, const Parameter::Type type); std::expected<void, std::error_code> addParameter(const char* name, const char* nvs_name, const Parameter::Type type);
CJsonPtr getParameterObjects() const; CJsonPtr getParameterObjects() const;
@ -88,6 +124,7 @@ public:
private: private:
std::unique_ptr<nvs::NVSHandle> file_handle_; std::unique_ptr<nvs::NVSHandle> file_handle_;
bool settings_initialized_ = false; bool settings_initialized_ = false;
void* esp_netif_ = nullptr;
httpd_handle_t http_server_ = nullptr; httpd_handle_t http_server_ = nullptr;
etl::vector<Parameter, 10> params_; etl::vector<Parameter, 10> params_;
etl::delegate<void (const etl::vector<Parameter, 10>& params)> success_cb_; etl::delegate<void (const etl::vector<Parameter, 10>& params)> success_cb_;