Compare commits
9 Commits
feature/in
...
feature/ci
| Author | SHA1 | Date | |
|---|---|---|---|
| 2bc3e68119 | |||
| d254049421 | |||
| a84ad5b430 | |||
| 8fc5c0ead6 | |||
| 6f09edbdfd | |||
| 9b41d1d399 | |||
| 1bcb7a7981 | |||
| c8dc085cc7 | |||
| f91d7b5335 |
@@ -3,20 +3,20 @@ Language: Cpp
|
|||||||
|
|
||||||
IndentWidth: 4
|
IndentWidth: 4
|
||||||
UseTab: Never
|
UseTab: Never
|
||||||
|
#NamespaceIndentation: All
|
||||||
|
|
||||||
PointerAlignment: Left
|
PointerAlignment: Left
|
||||||
AccessModifierOffset: -4
|
AccessModifierOffset: -4
|
||||||
AlwaysBreakTemplateDeclarations: true
|
AlwaysBreakTemplateDeclarations: true
|
||||||
|
LambdaBodyIndentation: Signature
|
||||||
|
|
||||||
MaxEmptyLinesToKeep: 2
|
MaxEmptyLinesToKeep: 3
|
||||||
|
#ColumnLimit: 128
|
||||||
|
|
||||||
CompactNamespaces: true
|
CompactNamespaces: true
|
||||||
FixNamespaceComments: true
|
FixNamespaceComments: true
|
||||||
|
|
||||||
LambdaBodyIndentation: Signature
|
|
||||||
|
|
||||||
AllowShortFunctionsOnASingleLine: false
|
AllowShortFunctionsOnASingleLine: false
|
||||||
AllowShortLambdasOnASingleLine: Empty
|
|
||||||
AllowShortIfStatementsOnASingleLine: true
|
AllowShortIfStatementsOnASingleLine: true
|
||||||
|
|
||||||
AlignConsecutiveAssignments: true
|
AlignConsecutiveAssignments: true
|
||||||
@@ -24,3 +24,5 @@ AlignConsecutiveBitFields: true
|
|||||||
AlignConsecutiveDeclarations: true
|
AlignConsecutiveDeclarations: true
|
||||||
AlignConsecutiveMacros: true
|
AlignConsecutiveMacros: true
|
||||||
|
|
||||||
|
#BraceWrapping:
|
||||||
|
# BeforeElse: true
|
||||||
|
|||||||
4
.clangd
4
.clangd
@@ -1,4 +0,0 @@
|
|||||||
CompileFlags:
|
|
||||||
Add: [-std=c++20]
|
|
||||||
Remove: ['-fno-shrink-wrap', '-fstrict-volatile-bitfields', '-fno-tree-switch-conversion']
|
|
||||||
|
|
||||||
16
.gitea/workflows/build.yaml
Normal file
16
.gitea/workflows/build.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repo
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: 'recursive'
|
||||||
|
- name: esp-idf build
|
||||||
|
uses: espressif/esp-idf-ci-action@v1
|
||||||
|
with:
|
||||||
|
esp_idf_version: v4.4
|
||||||
|
target: esp32s2
|
||||||
|
path: 'esp32-s2-hmi-devkit-1/examples/smart-panel'
|
||||||
20
.gitea/workflows/clang-format.yaml
Normal file
20
.gitea/workflows/clang-format.yaml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
name: clang-format Check
|
||||||
|
on: [push, pull_request]
|
||||||
|
jobs:
|
||||||
|
formatting-check:
|
||||||
|
name: Formatting Check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
path:
|
||||||
|
- check: 'main/src'
|
||||||
|
exclude: '' # Nothing to exclude
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Run clang-format style check for C/C++/Protobuf programs.
|
||||||
|
uses: jidicula/clang-format-action@v4.11.0
|
||||||
|
with:
|
||||||
|
clang-format-version: '13'
|
||||||
|
check-path: ${{ matrix.path['check'] }}
|
||||||
|
exclude-regex: ${{ matrix.path['exclude'] }}
|
||||||
|
fallback-style: 'Mozilla' # optional
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,6 +3,7 @@
|
|||||||
# https://github.com/espressif/esp-idf
|
# https://github.com/espressif/esp-idf
|
||||||
|
|
||||||
build/
|
build/
|
||||||
|
sdkconfig
|
||||||
sdkconfig.old
|
sdkconfig.old
|
||||||
|
|
||||||
.idea
|
.idea
|
||||||
@@ -12,5 +13,3 @@ 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/
|
|
||||||
|
|||||||
@@ -2,7 +2,5 @@ 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)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM espressif/idf:release-v5.2
|
FROM espressif/idf:release-v5.1
|
||||||
|
|
||||||
LABEL maintainer="andreas.tsouchlos@gmail.com"
|
LABEL maintainer="andreas.tsouchlos@gmail.com"
|
||||||
|
|
||||||
|
|||||||
37
README.md
37
README.md
@@ -1,23 +1,38 @@
|
|||||||
# HyperLink_SW
|
# HyperLink_SW
|
||||||
|
|
||||||
Firmware for the HyperLink project.
|
Firmware for the HyperLink board.
|
||||||
|
|
||||||
## Build and run
|
## Build and run
|
||||||
|
|
||||||
This project is built using the
|
This project is built using the [ESP IDF](https://docs.espressif.com/projects/esp-idf/en/release-v5.1/esp32/index.html) toolchain.
|
||||||
[ESP IDF](https://docs.espressif.com/projects/esp-idf/en/release-v5.1/esp32/index.html)
|
In order to ease the build process, a custom `Dockerfile` is provided.
|
||||||
toolchain. In order to ease the build process, a custom `Dockerfile` is
|
Any ESP IDF toolchain command can be run by replacing `idf.py` by `./idf.sh`, e.g., `./idf.sh build`,
|
||||||
provided. Any ESP IDF toolchain command can be run by replacing `idf.py` by
|
after having built the docker image.
|
||||||
`./idf.sh`, e.g., `./idf.sh build`.
|
|
||||||
|
|
||||||
Before the executing the following commands, make sure the docker daemon is
|
As the docker container has to be able to access the serial device for flashing,
|
||||||
running:
|
some configuration may have to take place.
|
||||||
|
Make sure the `USB_GUID` and `USB_DEV` in `idf.py` have the correct values.
|
||||||
|
|
||||||
1. Build the project
|
1. Build the docker image
|
||||||
|
```bash
|
||||||
|
$ sudo docker build . --tag hyperlink
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Compile the project
|
||||||
```bash
|
```bash
|
||||||
$ ./idf.sh build
|
$ ./idf.sh build
|
||||||
```
|
```
|
||||||
1. Flash the executable
|
|
||||||
|
3. Flash the executable
|
||||||
```bash
|
```bash
|
||||||
$ ./idf.sh flash monitor
|
$ ./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:
|
||||||
|
|
||||||
|

|
||||||
@@ -1 +0,0 @@
|
|||||||
idf_component_register(INCLUDE_DIRS include REQUIRES util esp_http_server)
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <stdexcept>
|
|
||||||
|
|
||||||
#include <esp_http_server.h>
|
|
||||||
|
|
||||||
#include <util/inplace_function.h>
|
|
||||||
|
|
||||||
|
|
||||||
#define RESPONSE_BUFFER_LEN 512
|
|
||||||
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Types
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
|
|
||||||
namespace http {
|
|
||||||
|
|
||||||
|
|
||||||
enum class Method { get = HTTP_GET, post = HTTP_POST };
|
|
||||||
|
|
||||||
struct response_t {
|
|
||||||
response_t() = default;
|
|
||||||
response_t(const char* str) {
|
|
||||||
std::size_t respLen =
|
|
||||||
std::min(strlen(str), static_cast<unsigned>(RESPONSE_BUFFER_LEN));
|
|
||||||
std::copy(str, str + respLen, text.begin());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::array<char, RESPONSE_BUFFER_LEN> text = {0};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct handler_t {
|
|
||||||
Method method;
|
|
||||||
const char* path;
|
|
||||||
inplace_function<response_t()> handler;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct server_settings_t {
|
|
||||||
int port = 80;
|
|
||||||
inplace_function<void()> on_error = [] {};
|
|
||||||
inplace_function<void()> on_not_found = [] {};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace http
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Implementation details
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
|
|
||||||
namespace http::detail {
|
|
||||||
|
|
||||||
|
|
||||||
inline auto& get_server() {
|
|
||||||
static httpd_handle_t server = nullptr;
|
|
||||||
return server;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <std::size_t N>
|
|
||||||
inline auto& get_handler_storage() {
|
|
||||||
static std::array<inplace_function<response_t()>, N> handlers;
|
|
||||||
return handlers;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <std::size_t N>
|
|
||||||
inline auto& get_esp_uri_storage() {
|
|
||||||
static std::array<httpd_uri_t, N> uris;
|
|
||||||
return uris;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// TODO: Currently, the set handler is only valid for GET requests
|
|
||||||
template <std::size_t N, std::size_t I>
|
|
||||||
inline void set_handlers_impl(const handler_t (&handlers)[N]) {
|
|
||||||
auto& uriStorage = get_esp_uri_storage<N>();
|
|
||||||
|
|
||||||
uriStorage[I] = httpd_uri_t{
|
|
||||||
.uri = handlers[I].path,
|
|
||||||
.method = static_cast<httpd_method_t>(handlers[I].method),
|
|
||||||
.handler =
|
|
||||||
[](httpd_req_t* req) {
|
|
||||||
auto resp = get_handler_storage<N>()[I]();
|
|
||||||
httpd_resp_send(req, resp.text.data(), HTTPD_RESP_USE_STRLEN);
|
|
||||||
return ESP_OK;
|
|
||||||
},
|
|
||||||
.user_ctx = nullptr,
|
|
||||||
};
|
|
||||||
|
|
||||||
httpd_register_uri_handler(get_server(), &(uriStorage[I]));
|
|
||||||
|
|
||||||
if constexpr (I < N - 1) set_handlers_impl<N, I + 1>(handlers);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace http::detail
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Public API
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
|
|
||||||
namespace http {
|
|
||||||
|
|
||||||
|
|
||||||
// TODO: Give request information to handlers
|
|
||||||
template <std::size_t N>
|
|
||||||
inline void set_handlers(const handler_t (&handlers)[N]) {
|
|
||||||
auto& handlerStorage = detail::get_handler_storage<N>();
|
|
||||||
std::transform(handlers, handlers + N, handlerStorage.begin(),
|
|
||||||
[](const auto& handler) {
|
|
||||||
return handler.handler;
|
|
||||||
});
|
|
||||||
|
|
||||||
detail::set_handlers_impl<N, 0>(handlers);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline void start_server(server_settings_t settings) {
|
|
||||||
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
|
||||||
config.server_port = settings.port;
|
|
||||||
|
|
||||||
if (httpd_start(&detail::get_server(), &config) != ESP_OK) {
|
|
||||||
throw std::runtime_error("Failed to start server");
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Set error and not found handlers
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
inline void stop_server() {
|
|
||||||
if (httpd_stop(detail::get_server()) != ESP_OK) {
|
|
||||||
throw std::runtime_error("Failed to stop server");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace http
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
idf_component_register(SRCS "src/nvs.cpp" INCLUDE_DIRS include REQUIRES
|
|
||||||
nvs_flash)
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
|
|
||||||
namespace nvs {
|
|
||||||
|
|
||||||
|
|
||||||
void init();
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace nvs
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
#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
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
idf_component_register(INCLUDE_DIRS include)
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
#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)
|
|
||||||
return 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) {
|
|
||||||
return 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) {
|
|
||||||
return 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) {
|
|
||||||
return std::invoke(*reinterpret_cast<F*>(storage),
|
|
||||||
std::forward<args_t>(args)...);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
storage_t mStorage;
|
|
||||||
wrapper_func_t mWrapper = nullptr;
|
|
||||||
};
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
idf_component_register(
|
|
||||||
SRCS
|
|
||||||
"src/wifi.cpp"
|
|
||||||
INCLUDE_DIRS
|
|
||||||
include
|
|
||||||
REQUIRES
|
|
||||||
esp_wifi
|
|
||||||
util
|
|
||||||
nvs
|
|
||||||
nanofmt)
|
|
||||||
|
|||||||
@@ -1,57 +0,0 @@
|
|||||||
#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
|
|
||||||
@@ -1,251 +0,0 @@
|
|||||||
#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
2
idf.sh
@@ -35,4 +35,4 @@ done
|
|||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
docker build . && docker run ${DEVICE_FLAGS} --rm --user $(id -u):${USB_GID} -v $PWD:/project -w /project -it $(docker build . -q) idf.py $@
|
docker run ${DEVICE_FLAGS} --rm --user $(id -u):${USB_GID} -v $PWD:/project -w /project -it hyperlink idf.py $@
|
||||||
|
|||||||
@@ -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} REQUIRES wifi nvs nanofmt http_server)
|
idf_component_register(SRCS ${SOURCES} INCLUDE_DIRS ${INCLUDES})
|
||||||
|
|||||||
@@ -1,65 +1,54 @@
|
|||||||
#include "esp_log.h"
|
/*
|
||||||
#include <http/server.h>
|
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
|
||||||
#include <nvs/nvs.h>
|
*
|
||||||
#include <wifi/wifi.h>
|
* SPDX-License-Identifier: CC0-1.0
|
||||||
|
*/
|
||||||
|
|
||||||
const http::response_t text_html = {
|
#include <stdio.h>
|
||||||
#include "res/test.html"
|
#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"
|
||||||
|
|
||||||
void start_ap() {
|
#include "nanofmt/format.h"
|
||||||
nvs::init();
|
|
||||||
wifi::init();
|
|
||||||
|
|
||||||
wifi::set_mode(wifi::Mode::soft_ap);
|
extern "C" void app_main(void)
|
||||||
wifi::configure_soft_ap(
|
{
|
||||||
"HyperLink", "1234567890",
|
printf("Hello world!\n");
|
||||||
{.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();
|
|
||||||
}
|
|
||||||
|
|
||||||
void start_http_server() {
|
/* Print chip information */
|
||||||
http::start_server({.port = 80,
|
esp_chip_info_t chip_info;
|
||||||
.on_error =
|
uint32_t flash_size;
|
||||||
[]() {
|
esp_chip_info(&chip_info);
|
||||||
// TODO
|
printf("This is %s chip with %d CPU core(s), %s%s%s%s, ",
|
||||||
},
|
CONFIG_IDF_TARGET,
|
||||||
.on_not_found =
|
chip_info.cores,
|
||||||
[]() {
|
(chip_info.features & CHIP_FEATURE_WIFI_BGN) ? "WiFi/" : "",
|
||||||
// TODO
|
(chip_info.features & CHIP_FEATURE_BT) ? "BT" : "",
|
||||||
}});
|
(chip_info.features & CHIP_FEATURE_BLE) ? "BLE" : "",
|
||||||
http::set_handlers({{http::Method::get, "/",
|
(chip_info.features & CHIP_FEATURE_IEEE802154) ? ", 802.15.4 (Zigbee/Thread)" : "");
|
||||||
[]() {
|
|
||||||
return text_html;
|
|
||||||
//http::response_t("res/test.html")
|
|
||||||
//http::response_t{
|
|
||||||
// "<html><body style=\"background:black; color: "
|
|
||||||
// "white\">Home</body></html>"};
|
|
||||||
}},
|
|
||||||
{http::Method::get, "/hello1",
|
|
||||||
[]() {
|
|
||||||
return http::response_t{"Hello 1"};
|
|
||||||
}},
|
|
||||||
{http::Method::get, "/hello2", []() {
|
|
||||||
return http::response_t{"Hello 2"};
|
|
||||||
}}});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
unsigned major_rev = chip_info.revision / 100;
|
||||||
extern "C" void app_main(void) {
|
unsigned minor_rev = chip_info.revision % 100;
|
||||||
try {
|
printf("silicon revision v%d.%d, ", major_rev, minor_rev);
|
||||||
start_ap();
|
if(esp_flash_get_size(NULL, &flash_size) != ESP_OK) {
|
||||||
start_http_server();
|
printf("Get flash size failed");
|
||||||
} catch (const std::exception& e) {
|
return;
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
"<html>"
|
|
||||||
"<body style=\"background:black; color: white\">"
|
|
||||||
"Home"
|
|
||||||
"</body>"
|
|
||||||
"</html>"
|
|
||||||
Reference in New Issue
Block a user