6 Commits

10 changed files with 2105 additions and 75 deletions

26
.clang-format Normal file
View File

@@ -0,0 +1,26 @@
BasedOnStyle: LLVM
Language: Cpp
IndentWidth: 4
UseTab: Never
PointerAlignment: Left
AccessModifierOffset: -4
AlwaysBreakTemplateDeclarations: true
MaxEmptyLinesToKeep: 2
CompactNamespaces: true
FixNamespaceComments: true
LambdaBodyIndentation: Signature
AllowShortFunctionsOnASingleLine: false
AllowShortLambdasOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: true
AlignConsecutiveAssignments: true
AlignConsecutiveBitFields: true
AlignConsecutiveDeclarations: true
AlignConsecutiveMacros: true

1
.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

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
$ 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 $ ./idf.sh flash monitor
``` ```
## 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 @@
idf_component_register(INCLUDE_DIRS include REQUIRES util esp_http_server)

View File

@@ -0,0 +1,147 @@
#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

View File

@@ -59,7 +59,7 @@ public:
return_t operator()(args_t... args) { return_t operator()(args_t... args) {
if (mWrapper) if (mWrapper)
mWrapper(mStorage, args...); return mWrapper(mStorage, args...);
else else
throw std::bad_function_call(); throw std::bad_function_call();
} }
@@ -77,7 +77,7 @@ public:
requires std::is_invocable_r_v<return_t, decltype(F), args_t...> requires std::is_invocable_r_v<return_t, decltype(F), args_t...>
void bind() { void bind() {
mWrapper = [](storage_t, args_t... args) { mWrapper = [](storage_t, args_t... args) {
std::invoke(F, std::forward<args_t>(args)...); return std::invoke(F, std::forward<args_t>(args)...);
}; };
} }
@@ -96,7 +96,7 @@ public:
new (mStorage)(class_t*){&c}; new (mStorage)(class_t*){&c};
mWrapper = [](storage_t storage, args_t... args) { mWrapper = [](storage_t storage, args_t... args) {
std::invoke(F, reinterpret_cast<class_t*>(storage), return std::invoke(F, reinterpret_cast<class_t*>(storage),
std::forward<args_t>(args)...); std::forward<args_t>(args)...);
}; };
} }
@@ -111,7 +111,7 @@ public:
void bind(F f) { void bind(F f) {
new (mStorage) F{std::move(f)}; new (mStorage) F{std::move(f)};
mWrapper = [](storage_t storage, args_t... args) { mWrapper = [](storage_t storage, args_t... args) {
std::invoke(*reinterpret_cast<F*>(storage), return std::invoke(*reinterpret_cast<F*>(storage),
std::forward<args_t>(args)...); std::forward<args_t>(args)...);
}; };
} }

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} REQUIRES wifi nvs nanofmt) idf_component_register(SRCS ${SOURCES} INCLUDE_DIRS ${INCLUDES} REQUIRES wifi nvs nanofmt http_server)

View File

@@ -1,31 +1,14 @@
#include "esp_log.h"
#include <http/server.h>
#include <nvs/nvs.h> #include <nvs/nvs.h>
#include <wifi/wifi.h> #include <wifi/wifi.h>
#include "esp_log.h"
void start_ap() {
extern "C" void app_main(void) {
try {
nvs::init(); nvs::init();
wifi::init(); wifi::init();
// 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();
// wifi::stop();
// wifi::clear_callbacks();
// wifi::stop();
// wifi::deinit();
wifi::set_mode(wifi::Mode::soft_ap); wifi::set_mode(wifi::Mode::soft_ap);
wifi::configure_soft_ap( wifi::configure_soft_ap(
"HyperLink", "1234567890", "HyperLink", "1234567890",
@@ -40,6 +23,38 @@ extern "C" void app_main(void) {
mac.data()); mac.data());
}}); }});
wifi::start(); wifi::start();
}
void start_http_server() {
http::start_server({.port = 80,
.on_error =
[]() {
// TODO
},
.on_not_found =
[]() {
// TODO
}});
http::set_handlers({{http::Method::get, "/",
[]() {
return 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"};
}}});
}
extern "C" void app_main(void) {
try {
start_ap();
start_http_server();
} catch (const std::exception& e) { } catch (const std::exception& e) {
ESP_LOGE("main", "Exception: %s", e.what()); ESP_LOGE("main", "Exception: %s", e.what());
} }

1857
sdkconfig Normal file

File diff suppressed because it is too large Load Diff