Compare commits

...

6 Commits

17 changed files with 2402 additions and 82 deletions

4
.clangd Normal file
View File

@ -0,0 +1,4 @@
CompileFlags:
Add: [-std=c++20]
Remove: ['-fno-shrink-wrap', '-fstrict-volatile-bitfields', '-fno-tree-switch-conversion']

3
.gitignore vendored
View File

@ -3,7 +3,6 @@
# https://github.com/espressif/esp-idf # https://github.com/espressif/esp-idf
build/ build/
sdkconfig
sdkconfig.old sdkconfig.old
.idea .idea
@ -13,3 +12,5 @@ cmake-build-relwithdebinfo
cmake-build-debug-docker/ cmake-build-debug-docker/
cmake-build-release-docker/ cmake-build-release-docker/
cmake-build-relwithdebinfo-docker/ cmake-build-relwithdebinfo-docker/
.cache/

View File

@ -2,5 +2,7 @@ cmake_minimum_required(VERSION 3.16)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
include($ENV{IDF_PATH}/tools/cmake/project.cmake) include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(HyperLink) project(HyperLink)

View File

@ -1,4 +1,4 @@
FROM espressif/idf:release-v5.1 FROM espressif/idf:release-v5.2
LABEL maintainer="andreas.tsouchlos@gmail.com" LABEL maintainer="andreas.tsouchlos@gmail.com"

View File

@ -1,38 +1,23 @@
# HyperLink_SW # HyperLink_SW
Firmware for the HyperLink board. Firmware for the HyperLink project.
## Build and run ## Build and run
This project is built using the [ESP IDF](https://docs.espressif.com/projects/esp-idf/en/release-v5.1/esp32/index.html) toolchain. This project is built using the
In order to ease the build process, a custom `Dockerfile` is provided. [ESP IDF](https://docs.espressif.com/projects/esp-idf/en/release-v5.1/esp32/index.html)
Any ESP IDF toolchain command can be run by replacing `idf.py` by `./idf.sh`, e.g., `./idf.sh build`, toolchain. In order to ease the build process, a custom `Dockerfile` is
after having built the docker image. provided. Any ESP IDF toolchain command can be run by replacing `idf.py` by
`./idf.sh`, e.g., `./idf.sh build`.
As the docker container has to be able to access the serial device for flashing, Before the executing the following commands, make sure the docker daemon is
some configuration may have to take place. running:
Make sure the `USB_GUID` and `USB_DEV` in `idf.py` have the correct values.
1. Build the docker image 1. Build the project
```bash ```bash
$ sudo docker build . --tag hyperlink $ ./idf.sh build
``` ```
1. Flash the executable
2. Compile the project ```bash
```bash $ ./idf.sh flash monitor
$ ./idf.sh build ```
```
3. Flash the executable
```bash
$ ./idf.sh flash
```
## Misc
### CLion Docker Setup
To configure the project to be built with docker in CLion, create a new docker
toolchain with the following settings:
![image](doc/CLion_docker_settings_screenshot.png)

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "src/nvs.cpp" INCLUDE_DIRS include REQUIRES
nvs_flash)

View File

@ -0,0 +1,10 @@
#pragma once
namespace nvs {
void init();
} // namespace nvs

View File

@ -0,0 +1,26 @@
#include <stdexcept>
#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

View File

@ -0,0 +1 @@
idf_component_register(INCLUDE_DIRS include)

View File

@ -0,0 +1,122 @@
#pragma once
#include <cstddef>
#include <functional>
#include <type_traits>
#include <utility>
template <typename func_sig_t, std::size_t storage_size = 8>
class inplace_function;
template <typename return_t, typename... args_t, std::size_t storage_size>
class inplace_function<return_t(args_t...), storage_size> {
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 <typename F>
inplace_function(F f) {
bind(f);
}
///
/// @brief Construct from free function pointer.
///
template <auto F>
static inplace_function construct() {
inplace_function<return_t(args_t...)> func;
func.template bind<F>();
return func;
}
///
/// @brief Construct form member function pointer.
///
template <auto F, typename class_t>
static inplace_function construct(class_t& c) {
inplace_function<return_t(args_t...)> func;
func.template bind<F>(c);
return func;
}
///
/// @brief Construct from function object.
///
template <typename F>
static inplace_function construct(F f) {
inplace_function<return_t(args_t...)> 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 <auto F>
requires std::is_invocable_r_v<return_t, decltype(F), args_t...>
void bind() {
mWrapper = [](storage_t, args_t... args) {
std::invoke(F, std::forward<args_t>(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 <auto F, typename class_t>
requires std::is_invocable_r_v<return_t, decltype(F), class_t&,
args_t...> &&
(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<class_t*>(storage),
std::forward<args_t>(args)...);
};
}
///
/// @brief Bind a function object.
///
template <typename F>
requires std::is_invocable_r_v<return_t, F, args_t...> &&
(sizeof(F) <= storage_size) &&
std::is_trivially_destructible_v<F>
void bind(F f) {
new (mStorage) F{std::move(f)};
mWrapper = [](storage_t storage, args_t... args) {
std::invoke(*reinterpret_cast<F*>(storage),
std::forward<args_t>(args)...);
};
}
private:
storage_t mStorage;
wrapper_func_t mWrapper = nullptr;
};

View File

@ -0,0 +1,10 @@
idf_component_register(
SRCS
"src/wifi.cpp"
INCLUDE_DIRS
include
REQUIRES
esp_wifi
util
nvs
nanofmt)

View File

@ -0,0 +1,57 @@
#pragma once
#include <span>
#include <string_view>
#include <util/inplace_function.h>
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<void(std::array<char, 18>, uint8_t)> connected;
inplace_function<void(std::array<char, 18>, uint8_t)> disconnected;
};
struct station_settings_t {
inplace_function<void()> stationStarted = [] {};
inplace_function<void()> disconnected = [] {};
inplace_function<void(std::array<char, 17>)> gotIp = [](auto...) {};
};
struct scanned_ap_t {
std::array<char, 33> 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<scanned_ap_t> scan(std::span<scanned_ap_t> memory);
} // namespace wifi

View File

@ -0,0 +1,251 @@
#include "wifi/wifi.h"
#include <cstring>
#include <stdexcept>
#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 <util/inplace_function.h>
namespace {
esp_netif_t* gSoftApNetif;
esp_netif_t* gStationNetif;
inplace_function<void(std::array<char, 18>, uint8_t)> gAPConnectCallback =
[](auto...) {};
inplace_function<void(std::array<char, 18>, uint8_t)> gAPDisconnectCallback =
[](auto...) {};
inplace_function<void()> gStationStartCallback = [] {};
inplace_function<void()> gStationDisconnectCallback = [] {};
inplace_function<void(std::array<char, 17>)> 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<wifi_event_ap_stadisconnected_t*>(event_data);
std::array<char, 18> 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<wifi_event_ap_stadisconnected_t*>(event_data);
std::array<char, 18> 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<ip_event_got_ip_t*>(event_data);
auto ipAddr = &event->ip_info.ip;
std::array<char, 17> 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<uint8_t>(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<scanned_ap_t> scan(std::span<scanned_ap_t> 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

2
idf.sh
View File

@ -35,4 +35,4 @@ done
# #
docker run ${DEVICE_FLAGS} --rm --user $(id -u):${USB_GID} -v $PWD:/project -w /project -it hyperlink idf.py $@ docker build . && docker run ${DEVICE_FLAGS} --rm --user $(id -u):${USB_GID} -v $PWD:/project -w /project -it $(docker build . -q) idf.py $@

View File

@ -1,4 +1,4 @@
set(SOURCES src/main.cpp) set(SOURCES src/main.cpp)
set(INCLUDES include) set(INCLUDES include)
idf_component_register(SRCS ${SOURCES} INCLUDE_DIRS ${INCLUDES}) idf_component_register(SRCS ${SOURCES} INCLUDE_DIRS ${INCLUDES} REQUIRES wifi nvs nanofmt)

View File

@ -1,54 +1,46 @@
/* #include <nvs/nvs.h>
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD #include <wifi/wifi.h>
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdio.h> #include "esp_log.h"
#include <inttypes.h>
#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 "nanofmt/format.h"
extern "C" void app_main(void) extern "C" void app_main(void) {
{ try {
printf("Hello world!\n"); nvs::init();
wifi::init();
/* Print chip information */ // wifi::set_mode(wifi::Mode::station);
esp_chip_info_t chip_info; // wifi::configure_station(
uint32_t flash_size; // "Vodafone-180C", "giraffen!",
esp_chip_info(&chip_info); // {.stationStarted = []() { wifi::station_connect(); },
printf("This is %s chip with %d CPU core(s), %s%s%s%s, ", // .disconnected = [] { wifi::station_connect(); },
CONFIG_IDF_TARGET, // .gotIp =
chip_info.cores, // [](auto ip) {
(chip_info.features & CHIP_FEATURE_WIFI_BGN) ? "WiFi/" : "", // ESP_LOGI("DEBUG - ip callback ", "Got ip: %s",
(chip_info.features & CHIP_FEATURE_BT) ? "BT" : "", // ip.data());
(chip_info.features & CHIP_FEATURE_BLE) ? "BLE" : "", // }});
(chip_info.features & CHIP_FEATURE_IEEE802154) ? ", 802.15.4 (Zigbee/Thread)" : ""); // wifi::start();
unsigned major_rev = chip_info.revision / 100; // wifi::stop();
unsigned minor_rev = chip_info.revision % 100; // wifi::clear_callbacks();
printf("silicon revision v%d.%d, ", major_rev, minor_rev); // wifi::stop();
if(esp_flash_get_size(NULL, &flash_size) != ESP_OK) { // wifi::deinit();
printf("Get flash size failed");
return; 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();
} }

1857
sdkconfig Normal file

File diff suppressed because it is too large Load Diff