Compare commits
12 Commits
06e81301b2
...
setup
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41516374f0 | ||
|
|
4cda7d2de3 | ||
|
|
a801caaae6 | ||
|
|
89e8014798 | ||
|
|
1d12dc5160 | ||
|
|
0b26e0c7c9 | ||
|
|
89daff2267 | ||
|
|
18ac21e257 | ||
|
|
821fb0d9d7 | ||
|
|
01c36669cf | ||
|
|
d339a1f4c3 | ||
|
|
e458256193 |
1
.clang-tidy
Normal file
1
.clang-tidy
Normal file
@@ -0,0 +1 @@
|
||||
Checks: '-clang-diagnostic-builtin-macro-redefined'
|
||||
13
.devcontainer/Dockerfile
Normal file
13
.devcontainer/Dockerfile
Normal file
@@ -0,0 +1,13 @@
|
||||
ARG DOCKER_TAG=latest
|
||||
FROM espressif/idf:${DOCKER_TAG}
|
||||
|
||||
ENV LC_ALL=C.UTF-8
|
||||
ENV LANG=C.UTF-8
|
||||
|
||||
RUN apt-get update -y && apt-get install udev -y
|
||||
|
||||
RUN echo "source /opt/esp/idf/export.sh > /dev/null 2>&1" >> ~/.bashrc
|
||||
|
||||
ENTRYPOINT [ "/opt/esp/entrypoint.sh" ]
|
||||
|
||||
CMD ["/bin/bash", "-c"]
|
||||
30
.devcontainer/devcontainer.json
Normal file
30
.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "ESP-IDF QEMU",
|
||||
"build": {
|
||||
"dockerfile": "Dockerfile"
|
||||
},
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {
|
||||
"terminal.integrated.defaultProfile.linux": "bash",
|
||||
"idf.espIdfPath": "/opt/esp/idf",
|
||||
"idf.toolsPath": "/opt/esp",
|
||||
"idf.gitPath": "/usr/bin/git"
|
||||
},
|
||||
"extensions": [
|
||||
"espressif.esp-idf-extension",
|
||||
"espressif.esp-idf-web",
|
||||
"ms-vscode.cpptools",
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
"mhutchie.git-graph",
|
||||
"oderwat.indent-rainbow",
|
||||
"SirTori.indenticator",
|
||||
"christian-kohler.path-intellisense",
|
||||
"esbenp.prettier-vscode",
|
||||
"redhat.vscode-yaml"
|
||||
|
||||
]
|
||||
}
|
||||
},
|
||||
"runArgs": ["--privileged"]
|
||||
}
|
||||
84
.gitignore
vendored
Normal file
84
.gitignore
vendored
Normal file
@@ -0,0 +1,84 @@
|
||||
# macOS
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Directory metadata
|
||||
.directory
|
||||
|
||||
# Temporary files
|
||||
*~
|
||||
*.swp
|
||||
*.swo
|
||||
*.bak
|
||||
*.tmp
|
||||
|
||||
# Log files
|
||||
*.log
|
||||
|
||||
# Build artifacts and directories
|
||||
**/build/
|
||||
build/
|
||||
*.o
|
||||
*.a
|
||||
*.out
|
||||
*.exe # For any host-side utilities compiled on Windows
|
||||
|
||||
# ESP-IDF specific build outputs
|
||||
*.bin
|
||||
*.elf
|
||||
*.map
|
||||
flasher_args.json # Generated in build directory
|
||||
sdkconfig.old
|
||||
sdkconfig
|
||||
|
||||
# ESP-IDF dependencies
|
||||
# For older versions or manual component management
|
||||
/components/.idf/
|
||||
**/components/.idf/
|
||||
# For modern ESP-IDF component manager
|
||||
managed_components/
|
||||
# If ESP-IDF tools are installed/referenced locally to the project
|
||||
.espressif/
|
||||
|
||||
# CMake generated files
|
||||
CMakeCache.txt
|
||||
CMakeFiles/
|
||||
cmake_install.cmake
|
||||
install_manifest.txt
|
||||
CTestTestfile.cmake
|
||||
|
||||
# Python environment files
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
__pycache__/
|
||||
*.egg-info/
|
||||
dist/
|
||||
|
||||
# Virtual environment folders
|
||||
venv/
|
||||
.venv/
|
||||
env/
|
||||
|
||||
# Language Servers
|
||||
.clangd/
|
||||
.ccls-cache/
|
||||
compile_commands.json
|
||||
|
||||
# Windows specific
|
||||
Thumbs.db
|
||||
ehthumbs.db
|
||||
Desktop.ini
|
||||
|
||||
# User-specific configuration files
|
||||
*.user
|
||||
*.workspace # General workspace files, can be from various tools
|
||||
*.suo # Visual Studio Solution User Options
|
||||
*.sln.docstates # Visual Studio
|
||||
|
||||
# cache files
|
||||
.cache/
|
||||
|
||||
# vscode settings
|
||||
.vscode/
|
||||
9
CMakeLists.txt
Normal file
9
CMakeLists.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
# 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)
|
||||
# target_compile_options(${COMPONENT_LIB} PRIVATE -std=c++23)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(ink-board)
|
||||
58
dependencies.lock
Normal file
58
dependencies.lock
Normal file
@@ -0,0 +1,58 @@
|
||||
dependencies:
|
||||
espressif/esp_lcd_touch:
|
||||
component_hash: 3f85a7d95af876f1a6ecca8eb90a81614890d0f03a038390804e5a77e2caf862
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=4.4.2'
|
||||
source:
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 1.2.1
|
||||
espressif/esp_lcd_touch_gt911:
|
||||
component_hash: be02e243d18b9a661bc13b0d22c0a5cfa3f708cf04d6eb059772276c8c8a4d76
|
||||
dependencies:
|
||||
- name: espressif/esp_lcd_touch
|
||||
registry_url: https://components.espressif.com
|
||||
require: public
|
||||
version: ^1.2.0
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=4.4.2'
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 1.2.0~1
|
||||
espressif/esp_lvgl_port:
|
||||
component_hash: f872401524cb645ee6ff1c9242d44fb4ddcfd4d37d7be8b9ed3f4e85a404efcd
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.1'
|
||||
- name: lvgl/lvgl
|
||||
registry_url: https://components.espressif.com
|
||||
require: public
|
||||
version: '>=8,<10'
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 2.7.0
|
||||
idf:
|
||||
source:
|
||||
type: idf
|
||||
version: 5.5.2
|
||||
lvgl/lvgl:
|
||||
component_hash: 17e68bfd21f0edf4c3ee838e2273da840bf3930e5dbc3bfa6c1190c3aed41f9f
|
||||
dependencies: []
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 9.4.0
|
||||
direct_dependencies:
|
||||
- espressif/esp_lcd_touch_gt911
|
||||
- espressif/esp_lvgl_port
|
||||
- idf
|
||||
- lvgl/lvgl
|
||||
manifest_hash: fef450d0c399587685f90aba8ae661965ef507d04a5fcf17633db86d5d0fbcff
|
||||
target: esp32
|
||||
version: 2.0.0
|
||||
29
diagram.json
Normal file
29
diagram.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"version": 1,
|
||||
"author": "GW_ MC",
|
||||
"editor": "wokwi",
|
||||
"parts": [
|
||||
{
|
||||
"type": "board-esp32-devkit-c-v4",
|
||||
"id": "esp",
|
||||
"top": 0,
|
||||
"left": 0,
|
||||
"attrs": {}
|
||||
}
|
||||
],
|
||||
"connections": [
|
||||
[
|
||||
"esp:TX",
|
||||
"$serialMonitor:RX",
|
||||
"",
|
||||
[]
|
||||
],
|
||||
[
|
||||
"esp:RX",
|
||||
"$serialMonitor:TX",
|
||||
"",
|
||||
[]
|
||||
]
|
||||
],
|
||||
"dependencies": {}
|
||||
}
|
||||
6
main/CMakeLists.txt
Normal file
6
main/CMakeLists.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
set(requires esp-tls spi_flash nvs_flash esp_event esp_netif esp_http_client esp_wifi)
|
||||
file(GLOB SRCS "main.cpp" "*.cpp" "*.c" "**/*.cpp" "**/*.c")
|
||||
|
||||
idf_component_register(SRCS ${SRCS}
|
||||
PRIV_REQUIRES ${requires}
|
||||
INCLUDE_DIRS "." "display" "touch" "network" "ui" "io" "common")
|
||||
9
main/Kconfig.projbuild
Normal file
9
main/Kconfig.projbuild
Normal file
@@ -0,0 +1,9 @@
|
||||
menu "ink-board Configuration"
|
||||
|
||||
config PARTIAL_REFRESH_LIMIT
|
||||
int "Partial Refresh Limit"
|
||||
default 20
|
||||
range 5 100
|
||||
help "Number of partial updates before full refresh"
|
||||
|
||||
endmenu
|
||||
17
main/common/constants.h
Normal file
17
main/common/constants.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
// 800x480 = 384,000 pixels
|
||||
|
||||
#define EINK_WIDTH 800
|
||||
#define EINK_HEIGHT 480
|
||||
|
||||
#define CORE_0 0
|
||||
#define CORE_1 1
|
||||
|
||||
#define SYSTEM_SHUTDOWN_BIT (1 << 0)
|
||||
#define SYSTEM_RESTART_BIT (1 << 1)
|
||||
#define SYSTEM_START_BIT (1 << 2)
|
||||
//
|
||||
#define DISPLAY_READY_BIT (1 << 1)
|
||||
#define TOUCH_CALIBRATED_BIT (1 << 2)
|
||||
#define STORAGE_READY_BIT (1 << 3)
|
||||
#define NETWORK_READY_BIT (1 << 4)
|
||||
31
main/common/queue_defs.h
Normal file
31
main/common/queue_defs.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#include <indev/lv_indev.h>
|
||||
|
||||
typedef enum {
|
||||
CMD_DISPLAY_UPDATE,
|
||||
CMD_SAVE_DATA,
|
||||
CMD_LOAD_DATA,
|
||||
CMD_REFRESH_DISPLAY,
|
||||
CMD_SYSTEM_STATUS,
|
||||
} cmd_type_t;
|
||||
|
||||
typedef struct {
|
||||
cmd_type_t type;
|
||||
uint32_t id;
|
||||
void* data;
|
||||
size_t len;
|
||||
QueueHandle_t reply_to; // NULL if one-way
|
||||
} async_cmd_t;
|
||||
|
||||
extern QueueHandle_t command_queue;
|
||||
|
||||
typedef struct {
|
||||
uint16_t x, y;
|
||||
lv_indev_state_t state; // LV_INDEV_STATE_PR/REL
|
||||
uint32_t timestamp;
|
||||
uint8_t gesture; // TAP, SWIPE, LONG_PRESS
|
||||
} touch_event_t;
|
||||
|
||||
extern QueueHandle_t touch_queue;
|
||||
|
||||
extern EventGroupHandle_t system_event_group;
|
||||
28
main/common/semaphore_guard.h
Normal file
28
main/common/semaphore_guard.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/portmacro.h"
|
||||
|
||||
struct SemaphoreGuard {
|
||||
public:
|
||||
const SemaphoreHandle_t semaphore;
|
||||
|
||||
SemaphoreGuard(SemaphoreHandle_t semaphore) : semaphore(semaphore) { }
|
||||
|
||||
portBASE_TYPE take(TickType_t ticks_to_wait = portMAX_DELAY) {
|
||||
portBASE_TYPE result = xSemaphoreTake(this->semaphore, ticks_to_wait);
|
||||
taken = (result == pdTRUE);
|
||||
return result;
|
||||
}
|
||||
|
||||
~SemaphoreGuard() {
|
||||
if (taken) {
|
||||
xSemaphoreGive(this->semaphore);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
// prevent copying
|
||||
SemaphoreGuard(const SemaphoreGuard&) = delete;
|
||||
SemaphoreGuard& operator=(const SemaphoreGuard&) = delete;
|
||||
bool taken = false;
|
||||
};
|
||||
62
main/display/display.cpp
Normal file
62
main/display/display.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include "display.h"
|
||||
#include "common/constants.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
// TODO: implement actual display functionality
|
||||
|
||||
DisplayHandler::DisplayHandler(QueueHandle_t touch_queue, SemaphoreHandle_t lvgl_mutex) {
|
||||
(void)touch_queue;
|
||||
(void)lvgl_mutex;
|
||||
}
|
||||
|
||||
DisplayHandler::~DisplayHandler() { }
|
||||
|
||||
EInkDisplayHandler::EInkDisplayHandler(QueueHandle_t touch_queue, SemaphoreHandle_t lvgl_mutex)
|
||||
: DisplayHandler(touch_queue, lvgl_mutex) { }
|
||||
|
||||
EInkDisplayHandler::~EInkDisplayHandler() { }
|
||||
|
||||
void EInkDisplayHandler::init(EventGroupHandle_t system_event_group) {
|
||||
if (system_event_group != NULL) {
|
||||
xEventGroupSetBits(system_event_group, DISPLAY_READY_BIT);
|
||||
}
|
||||
}
|
||||
void EInkDisplayHandler::start_event_loop() {
|
||||
// Minimal background task to represent display processing
|
||||
xTaskCreate(
|
||||
// use the static adapter and pass `this` as the task parameter
|
||||
EInkDisplayHandler::task_adapter,
|
||||
"display_task",
|
||||
2048,
|
||||
this,
|
||||
tskIDLE_PRIORITY + 1,
|
||||
nullptr
|
||||
);
|
||||
}
|
||||
|
||||
// static
|
||||
void EInkDisplayHandler::task_adapter(void* arg) {
|
||||
EInkDisplayHandler* self = static_cast<EInkDisplayHandler*>(arg);
|
||||
if (self) {
|
||||
self->run_event_loop();
|
||||
} else {
|
||||
printf("EInkDisplayHandler::task_adapter received null pointer\n");
|
||||
}
|
||||
// If run_event_loop ever returns, delete the task.
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void EInkDisplayHandler::run_event_loop() {
|
||||
for (;;) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
}
|
||||
|
||||
shutdown_display_handlerFunc EInkDisplayHandler::get_shutdown_display_handler() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
restart_display_handlerFunc EInkDisplayHandler::get_restart_display_handler() {
|
||||
return nullptr;
|
||||
}
|
||||
42
main/display/display.h
Normal file
42
main/display/display.h
Normal file
@@ -0,0 +1,42 @@
|
||||
#include "info/info.h"
|
||||
|
||||
typedef void (*shutdown_display_handlerFunc)(void);
|
||||
typedef void (*restart_display_handlerFunc)(void);
|
||||
|
||||
class DisplayHandler {
|
||||
public:
|
||||
DisplayHandler(QueueHandle_t touch_queue, SemaphoreHandle_t lvgl_mutex);
|
||||
// the system_event_group is used to set display-ready bit
|
||||
virtual void init(EventGroupHandle_t system_event_group) = 0;
|
||||
virtual void start_event_loop() = 0;
|
||||
// get a handler to perform display shutdown cleanup, this is called after event loop ends and DisplayHandler is deleted
|
||||
virtual shutdown_display_handlerFunc get_shutdown_display_handler() = 0;
|
||||
virtual restart_display_handlerFunc get_restart_display_handler() = 0;
|
||||
virtual ~DisplayHandler() = 0;
|
||||
|
||||
private:
|
||||
DisplayHandler(const DisplayHandler&) = delete;
|
||||
DisplayHandler& operator=(const DisplayHandler&) = delete;
|
||||
};
|
||||
|
||||
class EInkDisplayHandler : public DisplayHandler {
|
||||
public:
|
||||
EInkDisplayHandler(QueueHandle_t touch_queue, SemaphoreHandle_t lvgl_mutex);
|
||||
void init(EventGroupHandle_t system_event_group) override;
|
||||
void start_event_loop() override;
|
||||
shutdown_display_handlerFunc get_shutdown_display_handler() override;
|
||||
restart_display_handlerFunc get_restart_display_handler() override;
|
||||
~EInkDisplayHandler() override;
|
||||
|
||||
private:
|
||||
// Task adapter used for FreeRTOS task creation. It forwards to the
|
||||
// instance `run_event_loop()` method using the `this` pointer passed
|
||||
// as the task parameter.
|
||||
static void task_adapter(void* arg);
|
||||
|
||||
// Instance method that implements the display task loop.
|
||||
void run_event_loop();
|
||||
// prevent copying
|
||||
EInkDisplayHandler(const EInkDisplayHandler&) = delete;
|
||||
EInkDisplayHandler& operator=(const EInkDisplayHandler&) = delete;
|
||||
};
|
||||
19
main/idf_component.yml
Normal file
19
main/idf_component.yml
Normal file
@@ -0,0 +1,19 @@
|
||||
## IDF Component Manager Manifest File
|
||||
dependencies:
|
||||
## Required IDF version
|
||||
idf:
|
||||
version: '>=4.1.0'
|
||||
# # Put list of dependencies here
|
||||
# # For components maintained by Espressif:
|
||||
# component: "~1.0.0"
|
||||
# # For 3rd party components:
|
||||
# username/component: ">=1.0.0,<2.0.0"
|
||||
# username2/component2:
|
||||
# version: "~1.0.0"
|
||||
# # For transient dependencies `public` flag can be set.
|
||||
# # `public` flag doesn't have an effect dependencies of the `main` component.
|
||||
# # All dependencies of `main` are public by default.
|
||||
# public: true
|
||||
lvgl/lvgl: ^9.4.0
|
||||
espressif/esp_lcd_touch_gt911: ^1.2.0~1
|
||||
espressif/esp_lvgl_port: ^2.7.0
|
||||
30
main/info/info.cpp
Normal file
30
main/info/info.cpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#include "info.h"
|
||||
|
||||
void display_chip_info() {
|
||||
|
||||
/* Print chip information */
|
||||
esp_chip_info_t chip_info;
|
||||
uint32_t flash_size;
|
||||
esp_chip_info(&chip_info);
|
||||
printf("This is %s chip with %d CPU core(s), %s%s%s%s, ",
|
||||
CONFIG_IDF_TARGET,
|
||||
chip_info.cores,
|
||||
(chip_info.features & CHIP_FEATURE_WIFI_BGN) ? "WiFi/" : "",
|
||||
(chip_info.features & CHIP_FEATURE_BT) ? "BT" : "",
|
||||
(chip_info.features & CHIP_FEATURE_BLE) ? "BLE" : "",
|
||||
(chip_info.features & CHIP_FEATURE_IEEE802154) ? ", 802.15.4 (Zigbee/Thread)" : "");
|
||||
|
||||
unsigned major_rev = chip_info.revision / 100;
|
||||
unsigned minor_rev = chip_info.revision % 100;
|
||||
printf("silicon revision v%d.%d, ", major_rev, minor_rev);
|
||||
if (esp_flash_get_size(NULL, &flash_size) != ESP_OK) {
|
||||
printf("Get flash size failed");
|
||||
return;
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
}
|
||||
10
main/info/info.h
Normal file
10
main/info/info.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#include <stdio.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"
|
||||
|
||||
void display_chip_info();
|
||||
27
main/io/io.h
Normal file
27
main/io/io.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include <memory>
|
||||
|
||||
typedef bool(*FilterFunc)(const char* const& key);
|
||||
typedef void (*KeyValueProcessor)(void* arg, const char* const& key, const char* const& value);
|
||||
|
||||
class KVStorageHandler {
|
||||
public:
|
||||
virtual ~KVStorageHandler() = default;
|
||||
|
||||
virtual void init(const EventGroupHandle_t& system_event_group) = 0;
|
||||
|
||||
// Store a key-value pair
|
||||
virtual void put(const char* const& key, const char* const& value) = 0;
|
||||
|
||||
// Retrieve a value by key, returns nullptr if key not found
|
||||
// The caller is responsible for freeing the returned memory
|
||||
virtual std::unique_ptr<char[]> get(const char* const& key) const = 0;
|
||||
virtual esp_err_t process_all(KeyValueProcessor processor, void* arg) const = 0;
|
||||
virtual esp_err_t process_filtered(const char* const& key_prefix, KeyValueProcessor processor, void* arg) const = 0;
|
||||
virtual esp_err_t process_filtered(FilterFunc filter_func, KeyValueProcessor processor, void* arg) const = 0;
|
||||
|
||||
// Delete a key-value pair
|
||||
virtual void remove(const char* const& key) = 0;
|
||||
};
|
||||
183
main/io/nvs_handler.cpp
Normal file
183
main/io/nvs_handler.cpp
Normal file
@@ -0,0 +1,183 @@
|
||||
#include "common/constants.h"
|
||||
#include "io/nvs_handler.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "string.h"
|
||||
|
||||
NVSStorageHandler::NVSStorageHandler(
|
||||
const char* name_space
|
||||
) : name_space(name_space) { }
|
||||
|
||||
NVSStorageHandler::~NVSStorageHandler() {
|
||||
if (this->nvsHandle != 0) {
|
||||
nvs_close(this->nvsHandle);
|
||||
this->nvsHandle = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void NVSStorageHandler::init(const EventGroupHandle_t& system_event_group) {
|
||||
esp_err_t err = nvs_flash_init();
|
||||
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
nvs_flash_erase();
|
||||
err = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(err);
|
||||
|
||||
err = nvs_open(this->name_space, NVS_READWRITE, &this->nvsHandle);
|
||||
if (err != ESP_OK) {
|
||||
printf("Error (%s) opening NVS handle!\n", esp_err_to_name(err));
|
||||
} else {
|
||||
xEventGroupSetBits(system_event_group, STORAGE_READY_BIT);
|
||||
printf("NVS Storage initialized.\n");
|
||||
}
|
||||
}
|
||||
|
||||
void NVSStorageHandler::put(const char* const& key, const char* const& value) {
|
||||
if (this->nvsHandle == 0) {
|
||||
printf("NVS handle is not initialized.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
esp_err_t err = nvs_set_str(this->nvsHandle, key, value);
|
||||
if (err != ESP_OK) {
|
||||
printf("Error (%s) setting key-value pair in NVS!\n", esp_err_to_name(err));
|
||||
} else {
|
||||
nvs_commit(this->nvsHandle);
|
||||
printf("Key-value pair (%s, %s) stored in NVS.\n", key, value);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<char[]> NVSStorageHandler::get(const char* const& key) const {
|
||||
if (this->nvsHandle == 0) {
|
||||
printf("NVS handle is not initialized.\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t required_size = 0;
|
||||
esp_err_t err = nvs_get_str(this->nvsHandle, key, nullptr, &required_size);
|
||||
if (err == ESP_ERR_NVS_NOT_FOUND) {
|
||||
printf("Key %s not found in NVS.\n", key);
|
||||
return nullptr;
|
||||
} else if (err != ESP_OK) {
|
||||
printf("Error (%s) getting size for key %s from NVS!\n", esp_err_to_name(err), key);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<char[]> value(new char[required_size]);
|
||||
err = nvs_get_str(this->nvsHandle, key, value.get(), &required_size);
|
||||
if (err != ESP_OK) {
|
||||
printf("Error (%s) getting value for key %s from NVS!\n", esp_err_to_name(err), key);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
NVSIteratorGuard NVSStorageHandler::create_iterator() const {
|
||||
nvs_iterator_t it = nullptr;
|
||||
esp_err_t err = nvs_entry_find(NVS_DEFAULT_PART_NAME, this->name_space, NVS_TYPE_ANY, &it);
|
||||
if (err != ESP_OK) {
|
||||
printf("Error (%s) creating NVS iterator!\n", esp_err_to_name(err));
|
||||
return NVSIteratorGuard(nullptr, err);
|
||||
}
|
||||
|
||||
return NVSIteratorGuard(it, ESP_OK);
|
||||
}
|
||||
|
||||
esp_err_t NVSStorageHandler::process_all(KeyValueProcessor processor, void* arg) const {
|
||||
NVSIteratorGuard iterator_guard = this->create_iterator();
|
||||
if (!iterator_guard.is_valid()) {
|
||||
return iterator_guard.get_error();
|
||||
}
|
||||
const nvs_iterator_t& it = iterator_guard.get_iterator();
|
||||
|
||||
for (; it != NULL; iterator_guard.advance_iter()) {
|
||||
nvs_entry_info_t info;
|
||||
esp_err_t err = nvs_entry_info(it, &info);
|
||||
if (err != ESP_OK) {
|
||||
printf("Error (%s) getting NVS entry info!\n", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
nvs_handle_t temp_handle;
|
||||
err = nvs_open(this->name_space, NVS_READONLY, &temp_handle);
|
||||
if (err != ESP_OK) {
|
||||
printf("Error (%s) opening NVS handle for reading!\n", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
// call the processor with the key and value
|
||||
processor(arg, info.key, this->get(info.key).get());
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
esp_err_t NVSStorageHandler::process_filtered(const char* const& key_prefix, KeyValueProcessor processor, void* arg) const {
|
||||
NVSIteratorGuard iterator_guard = this->create_iterator();
|
||||
if (!iterator_guard.is_valid()) {
|
||||
return iterator_guard.get_error();
|
||||
}
|
||||
const nvs_iterator_t& it = iterator_guard.get_iterator();
|
||||
|
||||
for (; it != NULL; iterator_guard.advance_iter()) {
|
||||
nvs_entry_info_t info;
|
||||
esp_err_t err = nvs_entry_info(it, &info);
|
||||
if (err != ESP_OK) {
|
||||
printf("Error (%s) getting NVS entry info!\n", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
// check if the key matches the prefix
|
||||
if (strncmp(info.key, key_prefix, strlen(key_prefix)) == 0) {
|
||||
nvs_handle_t temp_handle;
|
||||
err = nvs_open(this->name_space, NVS_READONLY, &temp_handle);
|
||||
if (err != ESP_OK) {
|
||||
printf("Error (%s) opening NVS handle for reading!\n", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
// call the processor with the key and value
|
||||
processor(arg, info.key, this->get(info.key).get());
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t NVSStorageHandler::process_filtered(FilterFunc filter_func, KeyValueProcessor processor, void* arg) const {
|
||||
NVSIteratorGuard iterator_guard = this->create_iterator();
|
||||
if (!iterator_guard.is_valid()) {
|
||||
return iterator_guard.get_error();
|
||||
}
|
||||
const nvs_iterator_t& it = iterator_guard.get_iterator();
|
||||
|
||||
for (; it != NULL; iterator_guard.advance_iter()) {
|
||||
nvs_entry_info_t info;
|
||||
esp_err_t err = nvs_entry_info(it, &info);
|
||||
if (err != ESP_OK) {
|
||||
printf("Error (%s) getting NVS entry info!\n", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
// check if the key matches the filter function
|
||||
if (filter_func(info.key)) {
|
||||
nvs_handle_t temp_handle;
|
||||
err = nvs_open(this->name_space, NVS_READONLY, &temp_handle);
|
||||
if (err != ESP_OK) {
|
||||
printf("Error (%s) opening NVS handle for reading!\n", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
// call the processor with the key and value
|
||||
processor(arg, info.key, this->get(info.key).get());
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void NVSStorageHandler::remove(const char* const& key) {
|
||||
if (this->nvsHandle == 0) {
|
||||
printf("NVS handle is not initialized.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
esp_err_t err = nvs_erase_key(this->nvsHandle, key);
|
||||
if (err != ESP_OK) {
|
||||
printf("Error (%s) deleting key %s from NVS!\n", esp_err_to_name(err), key);
|
||||
} else {
|
||||
nvs_commit(this->nvsHandle);
|
||||
printf("Key %s deleted from NVS.\n", key);
|
||||
}
|
||||
}
|
||||
70
main/io/nvs_handler.h
Normal file
70
main/io/nvs_handler.h
Normal file
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
#include "io/io.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "nvs.h"
|
||||
|
||||
struct NVSIteratorGuard {
|
||||
public:
|
||||
~NVSIteratorGuard() {
|
||||
if (iterator) {
|
||||
nvs_release_iterator(iterator);
|
||||
}
|
||||
}
|
||||
|
||||
// accessors to the iterator, the internal state should not be modified directly
|
||||
// The iterator is advanced using advance_iter(), and is changed to nullptr on error or end
|
||||
// Caller MUST NOT release the iterator manually nor call get_iterator after advance_iter
|
||||
const nvs_iterator_t& get_iterator() const {
|
||||
return iterator;
|
||||
}
|
||||
|
||||
void advance_iter() {
|
||||
if (iterator) {
|
||||
// advance the iterator and update the internal state
|
||||
esp_err_t err = nvs_entry_next(&iterator);
|
||||
if (err != ESP_OK) {
|
||||
error = err;
|
||||
iterator = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t get_error() const {
|
||||
return error;
|
||||
}
|
||||
bool is_valid() const {
|
||||
return iterator != nullptr && error == ESP_OK;
|
||||
}
|
||||
friend class NVSStorageHandler;
|
||||
private:
|
||||
NVSIteratorGuard(nvs_iterator_t it
|
||||
, esp_err_t err
|
||||
) : iterator(it), error(err) { }
|
||||
nvs_iterator_t iterator;
|
||||
esp_err_t error;
|
||||
};
|
||||
|
||||
class NVSStorageHandler : public KVStorageHandler {
|
||||
public:
|
||||
NVSStorageHandler(
|
||||
const char* name_space
|
||||
);
|
||||
~NVSStorageHandler() override;
|
||||
|
||||
void init(const EventGroupHandle_t& system_event_group) override;
|
||||
|
||||
void put(const char* const& key, const char* const& value) override;
|
||||
|
||||
std::unique_ptr<char[]> get(const char* const& key) const override;
|
||||
esp_err_t process_all(KeyValueProcessor processor, void* arg) const override;
|
||||
esp_err_t process_filtered(const char* const& key_prefix, KeyValueProcessor processor, void* arg) const override;
|
||||
esp_err_t process_filtered(FilterFunc filter_func, KeyValueProcessor processor, void* arg) const override;
|
||||
|
||||
void remove(const char* const& key) override;
|
||||
|
||||
private:
|
||||
NVSIteratorGuard create_iterator() const;
|
||||
|
||||
nvs_handle_t nvsHandle = 0;
|
||||
const char* name_space;
|
||||
};
|
||||
1513
main/lv_conf.h
Normal file
1513
main/lv_conf.h
Normal file
File diff suppressed because it is too large
Load Diff
197
main/main.cpp
Normal file
197
main/main.cpp
Normal file
@@ -0,0 +1,197 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2010-2022 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <inttypes.h>
|
||||
#include <stdexcept>
|
||||
#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 "common/constants.h"
|
||||
#include "common/queue_defs.h"
|
||||
#include "io/nvs_handler.h"
|
||||
#include "info/info.h"
|
||||
#include "display/display.h"
|
||||
#include "touch/touch.h"
|
||||
#include <tick/lv_tick.h>
|
||||
#include "network.h"
|
||||
|
||||
#define DEFAULT_STORAGE_NAMESPACE "storage"
|
||||
#define WIFI_CREDENTIALS_STORAGE_NAMESPACE "wifi_credentials"
|
||||
|
||||
extern "C" void app_main(void);
|
||||
|
||||
void init_queues(
|
||||
QueueHandle_t& touch_queue,
|
||||
EventGroupHandle_t& system_event_group,
|
||||
EventGroupHandle_t& system_lifecycle_event_group
|
||||
);
|
||||
|
||||
|
||||
void app_main(void) {
|
||||
display_chip_info();
|
||||
|
||||
try {
|
||||
QueueHandle_t touch_event_queue = NULL;
|
||||
EventGroupHandle_t system_event_group = NULL, system_lifecycle_event_group = NULL;
|
||||
|
||||
init_queues(touch_event_queue, system_event_group, system_lifecycle_event_group);
|
||||
if (touch_event_queue == NULL || system_event_group == NULL || system_lifecycle_event_group == NULL) {
|
||||
throw std::runtime_error("Failed to create one or more queues/event groups");
|
||||
}
|
||||
printf("Queues initialized.\n");
|
||||
SemaphoreHandle_t lvgl_mutex = xSemaphoreCreateMutex();
|
||||
if (lvgl_mutex == NULL) {
|
||||
throw std::runtime_error("Failed to create LVGL mutex");
|
||||
}
|
||||
//
|
||||
WifiHandler wifi_handler(
|
||||
new NVSStorageHandler(WIFI_CREDENTIALS_STORAGE_NAMESPACE)
|
||||
);
|
||||
NetworkHandler* network_handler = new NetworkHandler(std::move(wifi_handler));
|
||||
KVStorageHandler* kv_storage_handler = new NVSStorageHandler(
|
||||
DEFAULT_STORAGE_NAMESPACE
|
||||
);
|
||||
DisplayHandler* display_handler = new EInkDisplayHandler(touch_event_queue, lvgl_mutex);
|
||||
TouchHandler* touch_handler = new EInkTouchHandler(touch_event_queue);
|
||||
//
|
||||
network_handler->init(system_event_group);
|
||||
kv_storage_handler->init(system_event_group);
|
||||
display_handler->init(system_event_group);
|
||||
touch_handler->init(system_event_group);
|
||||
//
|
||||
// LVGL tick timer
|
||||
auto lvgl_tick_timer_callback = [](TimerHandle_t xTimer) {
|
||||
lv_tick_inc(5);
|
||||
};
|
||||
TimerHandle_t lvgl_tick_timer = xTimerCreate(
|
||||
"lvgl_tick_timer",
|
||||
pdMS_TO_TICKS(5),
|
||||
pdTRUE,
|
||||
NULL,
|
||||
lvgl_tick_timer_callback
|
||||
);
|
||||
if (lvgl_tick_timer == NULL) {
|
||||
throw std::runtime_error("Failed to create LVGL tick timer");
|
||||
}
|
||||
xTimerStart(lvgl_tick_timer, 0);
|
||||
|
||||
//
|
||||
printf("Waiting for system to be ready...\n");
|
||||
xEventGroupWaitBits(
|
||||
system_event_group,
|
||||
DISPLAY_READY_BIT | TOUCH_CALIBRATED_BIT | STORAGE_READY_BIT | NETWORK_READY_BIT,
|
||||
// do not clear on exit, require explicit reset
|
||||
pdFALSE,
|
||||
pdTRUE,
|
||||
portMAX_DELAY
|
||||
);
|
||||
printf("System is ready. Starting main application...\n");
|
||||
// starting event loops
|
||||
display_handler->start_event_loop();
|
||||
touch_handler->start_event_loop();
|
||||
// wait for shutdown signal
|
||||
EventBits_t bits = xEventGroupWaitBits(
|
||||
system_lifecycle_event_group,
|
||||
SYSTEM_SHUTDOWN_BIT | SYSTEM_RESTART_BIT,
|
||||
// do not clear on exit, require explicit reset
|
||||
pdFALSE,
|
||||
pdFALSE,
|
||||
portMAX_DELAY
|
||||
);
|
||||
printf("Shutdown signal received. Cleaning up...\n");
|
||||
|
||||
// cleanup
|
||||
shutdown_display_handlerFunc shutdown_display_handler = display_handler->get_shutdown_display_handler();
|
||||
restart_display_handlerFunc restart_display_handler = display_handler->get_restart_display_handler();
|
||||
delete display_handler;
|
||||
delete touch_handler;
|
||||
vSemaphoreDelete(lvgl_mutex);
|
||||
vEventGroupDelete(system_event_group);
|
||||
vQueueDelete(touch_event_queue);
|
||||
printf("Cleanup complete.\n");
|
||||
|
||||
// handle shutdown or restart
|
||||
if (bits & SYSTEM_SHUTDOWN_BIT) {
|
||||
if (shutdown_display_handler != nullptr) {
|
||||
printf("Calling display shutdown handler...\n");
|
||||
shutdown_display_handler();
|
||||
} else {
|
||||
printf("No display shutdown handler to call.\n");
|
||||
}
|
||||
printf("System is shutting down.\n");
|
||||
fflush(stdout);
|
||||
// wait for start bit to be set again if future restart is desired, else expect manual power cycle
|
||||
EventBits_t bits = xEventGroupWaitBits(
|
||||
system_lifecycle_event_group,
|
||||
SYSTEM_START_BIT,
|
||||
pdFALSE,
|
||||
pdFALSE,
|
||||
portMAX_DELAY
|
||||
);
|
||||
if (bits & SYSTEM_START_BIT) {
|
||||
printf("SYSTEM_START_BIT received, restarting system.\n");
|
||||
} else {
|
||||
printf("No restart signal received, waiting for manual power cycle.\n");
|
||||
while (true) {
|
||||
vTaskDelay(portMAX_DELAY);
|
||||
}
|
||||
}
|
||||
} else if (bits & SYSTEM_RESTART_BIT) {
|
||||
if (restart_display_handler != nullptr) {
|
||||
printf("Calling display restart handler...\n");
|
||||
restart_display_handler();
|
||||
} else {
|
||||
printf("No display restart handler to call.\n");
|
||||
}
|
||||
printf("System is restarting.\n");
|
||||
fflush(stdout);
|
||||
} else {
|
||||
printf("Unknown shutdown signal received. Restarting by default.\n");
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
return esp_restart();
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
printf("Exception occurred during initialization: %s\n", e.what());
|
||||
printf("System will restart due to the error.\n");
|
||||
for (int i = 5; i >= 0; --i) {
|
||||
printf("Restarting in %d seconds...\n", i);
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
printf("Restarting now.\n");
|
||||
fflush(stdout);
|
||||
return esp_restart();
|
||||
}
|
||||
|
||||
printf("Reached end of app_main unexpectedly.\n");
|
||||
printf("System will restart in 10 seconds...\n");
|
||||
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);
|
||||
return esp_restart();
|
||||
}
|
||||
|
||||
void init_queues(
|
||||
QueueHandle_t& touch_queue,
|
||||
EventGroupHandle_t& system_event_group,
|
||||
EventGroupHandle_t& system_lifecycle_event_group
|
||||
) {
|
||||
// Implementation of queue initialization
|
||||
touch_queue = xQueueCreate(10, sizeof(touch_event_t));
|
||||
system_event_group = xEventGroupCreate();
|
||||
system_lifecycle_event_group = xEventGroupCreate();
|
||||
}
|
||||
51
main/network/http_handler.cpp
Normal file
51
main/network/http_handler.cpp
Normal file
@@ -0,0 +1,51 @@
|
||||
#include "network/http_handler.h"
|
||||
#include "esp_http_client.h"
|
||||
#include "esp_log.h"
|
||||
#include "string.h"
|
||||
|
||||
HttpHandler::HttpHandler(const esp_http_client_config_t&& config, WifiHandler* wifiHandler)
|
||||
: wifiHandler(wifiHandler) {
|
||||
this->client = esp_http_client_init(&config);
|
||||
}
|
||||
|
||||
HttpHandler::~HttpHandler() {
|
||||
if (this->client) {
|
||||
esp_http_client_cleanup(this->client);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t HttpHandler::set_method(esp_http_client_method_t method) {
|
||||
return esp_http_client_set_method(this->client, method);
|
||||
}
|
||||
|
||||
esp_err_t HttpHandler::set_header(const char* header, const char* value) {
|
||||
return esp_http_client_set_header(this->client, header, value);
|
||||
}
|
||||
|
||||
esp_err_t HttpHandler::set_post_field(const char* field, size_t len) {
|
||||
return esp_http_client_set_post_field(this->client, field, len);
|
||||
}
|
||||
|
||||
esp_err_t HttpHandler::perform_request() {
|
||||
return esp_http_client_perform(this->client);
|
||||
}
|
||||
|
||||
void HttpHandler::get_body(
|
||||
char*& buffer,
|
||||
int& total_len
|
||||
) {
|
||||
total_len = esp_http_client_get_content_length(this->client);
|
||||
buffer = new char[total_len + 1]; // +1 for null-terminator
|
||||
if (buffer) {
|
||||
int read_len = esp_http_client_read(this->client, buffer, total_len);
|
||||
if (read_len >= 0) {
|
||||
buffer[read_len] = '\0'; // null-terminate
|
||||
} else {
|
||||
delete[] buffer;
|
||||
buffer = nullptr;
|
||||
total_len = 0;
|
||||
}
|
||||
} else {
|
||||
total_len = 0;
|
||||
}
|
||||
}
|
||||
55
main/network/http_handler.h
Normal file
55
main/network/http_handler.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include "esp_http_client.h"
|
||||
#include "network/wifi_handler.h"
|
||||
|
||||
// forward declare NetworkHandler to avoid circular include with network.h
|
||||
class NetworkHandler;
|
||||
|
||||
// default config values for esp_http_client_config_t
|
||||
// disable Wmissing-field-initializers warning for these structs
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
||||
static const inline esp_http_client_config_t DEFAULT_HTTP_CLIENT_CONFIG = {
|
||||
.timeout_ms = 10000,
|
||||
};
|
||||
|
||||
static const inline esp_http_client_config_t DEFAULT_HTTP_CLIENT_CONFIG_HTTPS = {
|
||||
.transport_type = HTTP_TRANSPORT_OVER_SSL,
|
||||
//
|
||||
.use_global_ca_store = true,
|
||||
.skip_cert_common_name_check = false,
|
||||
};
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
// esp http client wrapper with automatic initialization and cleanup
|
||||
class HttpHandler {
|
||||
public:
|
||||
~HttpHandler();
|
||||
|
||||
esp_err_t set_method(esp_http_client_method_t method);
|
||||
esp_err_t set_header(const char* header, const char* value);
|
||||
esp_err_t set_post_field(const char* field, size_t len);
|
||||
//
|
||||
esp_err_t perform_request();
|
||||
// buffer is allocated inside the method, caller must free it
|
||||
void get_body(
|
||||
char*& buffer,
|
||||
int& total_len
|
||||
);
|
||||
|
||||
|
||||
// only NetworkHandler can create HttpHandler instances
|
||||
friend class NetworkHandler;
|
||||
// disable copy constructor and assignment operator
|
||||
HttpHandler(const HttpHandler&) = delete;
|
||||
HttpHandler& operator=(const HttpHandler&) = delete;
|
||||
|
||||
private:
|
||||
// private constructor, only NetworkHandler can create instances
|
||||
HttpHandler(const esp_http_client_config_t&& config, WifiHandler* wifiHandler);
|
||||
|
||||
esp_http_client_handle_t client;
|
||||
// backreference to WifiHandler to ensure WiFi is connected, DO NOT DELETE
|
||||
WifiHandler* wifiHandler;
|
||||
};
|
||||
32
main/network/network.cpp
Normal file
32
main/network/network.cpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#include "esp_log.h"
|
||||
#include "network/network.h"
|
||||
#include "network/http_handler.h"
|
||||
#include "common/constants.h"
|
||||
|
||||
NetworkHandler::NetworkHandler(
|
||||
WifiHandler&& wifiHandler
|
||||
) : wifiHandler(std::move(wifiHandler)) { }
|
||||
|
||||
NetworkHandler::~NetworkHandler() { }
|
||||
|
||||
void NetworkHandler::init(EventGroupHandle_t system_event_group) {
|
||||
if (this->initialized) {
|
||||
ESP_LOGW("NetworkHandler", "Already initialized, skipping");
|
||||
return;
|
||||
}
|
||||
this->wifiHandler.init();
|
||||
this->initialized = true;
|
||||
xEventGroupSetBits(
|
||||
system_event_group,
|
||||
NETWORK_READY_BIT
|
||||
);
|
||||
}
|
||||
|
||||
WifiHandler& NetworkHandler::get_wifi_handler() {
|
||||
return this->wifiHandler;
|
||||
}
|
||||
|
||||
std::unique_ptr<HttpHandler> NetworkHandler::get_http_handler(const esp_http_client_config_t&& config) {
|
||||
return std::unique_ptr<HttpHandler>(new HttpHandler(std::move(config), &this->wifiHandler));
|
||||
}
|
||||
|
||||
27
main/network/network.h
Normal file
27
main/network/network.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_system.h"
|
||||
#include "network/wifi_handler.h"
|
||||
#include "esp_http_client.h"
|
||||
|
||||
// forward declare HttpHandler to avoid circular include with http_handler.h
|
||||
class HttpHandler;
|
||||
|
||||
class NetworkHandler {
|
||||
public:
|
||||
NetworkHandler(
|
||||
WifiHandler&& wifiHandler
|
||||
);
|
||||
~NetworkHandler();
|
||||
|
||||
void init(EventGroupHandle_t system_event_group);
|
||||
WifiHandler& get_wifi_handler();
|
||||
// factory method to create HttpHandler instances
|
||||
std::unique_ptr<HttpHandler> get_http_handler(const esp_http_client_config_t&& config);
|
||||
|
||||
|
||||
private:
|
||||
WifiHandler wifiHandler;
|
||||
bool initialized = false;
|
||||
};
|
||||
301
main/network/wifi_handler.cpp
Normal file
301
main/network/wifi_handler.cpp
Normal file
@@ -0,0 +1,301 @@
|
||||
#include "wifi_handler.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_event.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "common/semaphore_guard.h"
|
||||
|
||||
static const char* TAG = "WifiHandler";
|
||||
static const char* WIFI_SSID_KEY = "wifi_ssid";
|
||||
static const char* WIFI_PASSWORD_KEY = "wifi_password";
|
||||
|
||||
WifiHandler::WifiHandler(
|
||||
// this handler is used to store/retrieve WiFi credentials
|
||||
// should have a unique namespace for WiFi credentials
|
||||
// it will be owned by WifiHandler and deleted in its destructor
|
||||
KVStorageHandler* kvs
|
||||
) : kvs(kvs) {
|
||||
this->s_wifi_event_group = xEventGroupCreate();
|
||||
this->scan_mutex = xSemaphoreCreateMutex();
|
||||
this->connection_mutex = xSemaphoreCreateMutex();
|
||||
}
|
||||
|
||||
// Move constructor: transfer ownership of resources
|
||||
WifiHandler::WifiHandler(WifiHandler&& other) noexcept
|
||||
: initialized(other.initialized),
|
||||
kvs(other.kvs),
|
||||
s_wifi_event_group(other.s_wifi_event_group),
|
||||
scan_mutex(other.scan_mutex),
|
||||
connection_mutex(other.connection_mutex),
|
||||
current_ssid(other.current_ssid),
|
||||
expect_disconnected(other.expect_disconnected) {
|
||||
other.kvs = nullptr;
|
||||
other.initialized = false;
|
||||
other.s_wifi_event_group = 0;
|
||||
other.scan_mutex = nullptr;
|
||||
other.connection_mutex = nullptr;
|
||||
other.current_ssid = nullptr;
|
||||
other.expect_disconnected = false;
|
||||
}
|
||||
|
||||
WifiHandler::~WifiHandler() {
|
||||
if (this->initialized) {
|
||||
esp_wifi_stop();
|
||||
// Check if it should be called
|
||||
esp_wifi_deinit();
|
||||
vEventGroupDelete(this->s_wifi_event_group);
|
||||
if (this->current_ssid) {
|
||||
delete[] this->current_ssid;
|
||||
}
|
||||
vSemaphoreDelete(this->scan_mutex);
|
||||
vSemaphoreDelete(this->connection_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
void WifiHandler::init() {
|
||||
if (this->initialized) {
|
||||
ESP_LOGW(TAG, "Already initialized, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// get WiFi credentials from KV storage if available
|
||||
char* ssid = nullptr;
|
||||
char* password = nullptr;
|
||||
this->get_wifi_credentials(ssid, password);
|
||||
|
||||
if (ssid && password) {
|
||||
ESP_LOGI(TAG, "Found stored WiFi credentials, connecting to SSID: %s", ssid);
|
||||
esp_err_t err = this->connect(ssid, password);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to connect to stored WiFi credentials: %s", esp_err_to_name(err));
|
||||
}
|
||||
} else {
|
||||
ESP_LOGI(TAG, "No stored WiFi credentials found, not connecting");
|
||||
}
|
||||
|
||||
delete[] ssid;
|
||||
delete[] password;
|
||||
// TODO: setup WiFi event handlers
|
||||
// TODO: add auto-reconnect logic
|
||||
//
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
esp_err_t WifiHandler::connect(const char* ssid, const char* password) {
|
||||
SemaphoreGuard guard(this->connection_mutex);
|
||||
// wait up to 5 seconds to take the mutex
|
||||
if (!guard.take(5000 / portTICK_PERIOD_MS)) {
|
||||
ESP_LOGE(TAG, "Failed to take connection mutex");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
expect_disconnected = false;
|
||||
if (this->current_ssid) {
|
||||
delete[] this->current_ssid;
|
||||
}
|
||||
size_t ssid_len = strlen(ssid);
|
||||
this->current_ssid = new char[ssid_len + 1];
|
||||
strncpy(this->current_ssid, ssid, ssid_len + 1);
|
||||
this->current_ssid[ssid_len] = '\0';
|
||||
|
||||
//
|
||||
wifi_config_t wifi_config = {};
|
||||
strncpy((char*)wifi_config.sta.ssid, this->current_ssid, sizeof(wifi_config.sta.ssid));
|
||||
wifi_config.sta.ssid[sizeof(wifi_config.sta.ssid) - 1] = '\0';
|
||||
strncpy((char*)wifi_config.sta.password, password, sizeof(wifi_config.sta.password));
|
||||
wifi_config.sta.password[sizeof(wifi_config.sta.password) - 1] = '\0';
|
||||
// set auth mode to WPA2_PSK minimum
|
||||
wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
|
||||
|
||||
ESP_LOGI(TAG, "Connecting to SSID: %s", this->current_ssid);
|
||||
esp_err_t err = esp_wifi_set_config(wifi_interface_t::WIFI_IF_STA, &wifi_config);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to set WiFi config: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
err = esp_wifi_connect();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initiate WiFi connection: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
// store credentials
|
||||
this->kvs->put(WIFI_SSID_KEY, this->current_ssid);
|
||||
// store password under key derived from SSID
|
||||
char* password_key = this->build_password_key(this->current_ssid);
|
||||
this->kvs->put(password_key, password);
|
||||
delete[] password_key;
|
||||
|
||||
// set connected bit on successful connection
|
||||
xEventGroupSetBits(
|
||||
this->s_wifi_event_group,
|
||||
WIFI_CONNECTED_BIT
|
||||
);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t WifiHandler::connect(const char* ssid) {
|
||||
char* stored_ssid = nullptr;
|
||||
char* stored_password = nullptr;
|
||||
this->get_wifi_credentials(stored_ssid, stored_password);
|
||||
if (!stored_ssid || strcmp(stored_ssid, ssid) != 0) {
|
||||
ESP_LOGE(TAG, "No stored credentials for SSID: %s", ssid);
|
||||
delete[] stored_ssid;
|
||||
delete[] stored_password;
|
||||
return ESP_FAIL;
|
||||
}
|
||||
esp_err_t err = this->connect(stored_ssid, stored_password ? stored_password : "");
|
||||
delete[] stored_ssid;
|
||||
delete[] stored_password;
|
||||
return err;
|
||||
}
|
||||
|
||||
esp_err_t WifiHandler::reconnect() {
|
||||
if (!this->current_ssid) {
|
||||
ESP_LOGE(TAG, "No current SSID set, cannot reconnect");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return this->connect(this->current_ssid);
|
||||
}
|
||||
|
||||
void WifiHandler::disconnect() {
|
||||
SemaphoreGuard guard(this->connection_mutex);
|
||||
// wait up to 5 seconds to take the mutex
|
||||
if (!guard.take(5000 / portTICK_PERIOD_MS)) {
|
||||
ESP_LOGE(TAG, "Failed to take connection mutex");
|
||||
return;
|
||||
}
|
||||
|
||||
expect_disconnected = true;
|
||||
esp_wifi_disconnect();
|
||||
xEventGroupClearBits(
|
||||
this->s_wifi_event_group,
|
||||
WIFI_CONNECTED_BIT
|
||||
);
|
||||
}
|
||||
|
||||
esp_err_t WifiHandler::scan_networks(
|
||||
wifi_ap_record_t*& ap_records,
|
||||
uint16_t& ap_count
|
||||
) {
|
||||
SemaphoreGuard guard(this->scan_mutex);
|
||||
// wait up to 5 seconds to take the mutex
|
||||
if (!guard.take(5000 / portTICK_PERIOD_MS)) {
|
||||
ESP_LOGE(TAG, "Failed to take scan mutex");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
ap_records = nullptr;
|
||||
ap_count = 0;
|
||||
// start scan
|
||||
|
||||
esp_err_t err = esp_wifi_scan_start(nullptr, true); // block until done
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to start WiFi scan: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
// get number of APs found
|
||||
uint16_t ap_count_local = 0;
|
||||
err = esp_wifi_scan_get_ap_num(&ap_count_local);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to get number of APs found: %s", esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
wifi_ap_record_t* ap_records_local = new wifi_ap_record_t[ap_count_local];
|
||||
err = esp_wifi_scan_get_ap_records(&ap_count_local, ap_records_local);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to get AP records: %s", esp_err_to_name(err));
|
||||
delete[] ap_records_local;
|
||||
return err;
|
||||
}
|
||||
|
||||
ap_records = ap_records_local;
|
||||
ap_count = ap_count_local;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void WifiHandler::wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
|
||||
WifiHandler* self = static_cast<WifiHandler*>(arg);
|
||||
if (self == nullptr) {
|
||||
ESP_LOGE(TAG, "wifi_event_handler received null WifiHandler pointer");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event_id) {
|
||||
case WIFI_EVENT_STA_START:
|
||||
// When the station starts, attempt to connect
|
||||
ESP_LOGI(TAG, "WIFI_EVENT_STA_START");
|
||||
if (!self->expect_disconnected && self->current_ssid) {
|
||||
ESP_LOGI(TAG, "Station started, attempting to connect to SSID: %s", self->current_ssid);
|
||||
self->reconnect();
|
||||
}
|
||||
break;
|
||||
case WIFI_EVENT_STA_DISCONNECTED:
|
||||
ESP_LOGI(TAG, "WIFI_EVENT_STA_DISCONNECTED");
|
||||
if (!self->expect_disconnected) {
|
||||
ESP_LOGI(TAG, "Unexpected disconnection, attempting to reconnect");
|
||||
self->reconnect();
|
||||
}
|
||||
xEventGroupClearBits(
|
||||
self->s_wifi_event_group,
|
||||
WIFI_CONNECTED_BIT
|
||||
);
|
||||
break;
|
||||
case IP_EVENT_STA_GOT_IP:
|
||||
{
|
||||
ip_event_got_ip_t* event = static_cast<ip_event_got_ip_t*>(event_data);
|
||||
ESP_LOGI(TAG, "WIFI_EVENT_STA_GOT_IP: " IPSTR, IP2STR(&event->ip_info.ip));
|
||||
xEventGroupSetBits(
|
||||
self->s_wifi_event_group,
|
||||
WIFI_CONNECTED_BIT
|
||||
);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unhandled WiFi event: %d", event_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// private methods
|
||||
//
|
||||
|
||||
char* WifiHandler::build_password_key(const char* ssid) {
|
||||
// `{WIFI_PASSWORD_KEY}_{ssid}`
|
||||
size_t password_key_len = strlen(WIFI_PASSWORD_KEY) + 1 + strlen(ssid) + 1;
|
||||
char* password_key_buff = new char[password_key_len];
|
||||
snprintf(password_key_buff, password_key_len, "%s_%s", WIFI_PASSWORD_KEY, ssid);
|
||||
return password_key_buff;
|
||||
}
|
||||
|
||||
void WifiHandler::get_wifi_credentials(char*& ssid, char*& password) {
|
||||
if (!kvs) {
|
||||
ESP_LOGW(TAG, "KVStorageHandler not set, cannot get WiFi credentials");
|
||||
return;
|
||||
}
|
||||
ssid = kvs->get(WIFI_SSID_KEY).get();
|
||||
if (!ssid) {
|
||||
ssid = nullptr;
|
||||
password = nullptr;
|
||||
return;
|
||||
}
|
||||
// password is from KV storage, may be nullptr
|
||||
char* password_key = this->build_password_key(ssid);
|
||||
password = kvs->get(password_key).get();
|
||||
delete[] password_key;
|
||||
}
|
||||
|
||||
EventBits_t WifiHandler::wait_for_connection(TickType_t ticks_to_wait) {
|
||||
return xEventGroupWaitBits(
|
||||
s_wifi_event_group,
|
||||
WIFI_CONNECTED_BIT,
|
||||
pdFALSE,
|
||||
pdTRUE,
|
||||
ticks_to_wait
|
||||
);
|
||||
}
|
||||
54
main/network/wifi_handler.h
Normal file
54
main/network/wifi_handler.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
#include "io/io.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "freertos/event_groups.h"
|
||||
|
||||
#define WIFI_CONNECTED_BIT (1 << 0)
|
||||
|
||||
class WifiHandler {
|
||||
public:
|
||||
WifiHandler(
|
||||
// this handler is used to store/retrieve WiFi credentials
|
||||
// should have a unique namespace for WiFi credentials
|
||||
// it will be owned by WifiHandler and deleted in its destructor
|
||||
KVStorageHandler* kvs
|
||||
);
|
||||
~WifiHandler();
|
||||
|
||||
// move semantics
|
||||
WifiHandler(WifiHandler&& other) noexcept;
|
||||
|
||||
void init();
|
||||
esp_err_t connect(const char* ssid, const char* password);
|
||||
esp_err_t connect(const char* ssid); // connect using stored password
|
||||
esp_err_t reconnect(); // reconnect to current SSID
|
||||
void disconnect();
|
||||
EventBits_t wait_for_connection(TickType_t ticks_to_wait);
|
||||
// returns list of available networks, caller is responsible for freeing the returned memory
|
||||
// returns nullptr if scan failed
|
||||
esp_err_t scan_networks(
|
||||
wifi_ap_record_t*& ap_records,
|
||||
uint16_t& ap_count
|
||||
);
|
||||
|
||||
static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data);
|
||||
|
||||
private:
|
||||
// prevent copying
|
||||
WifiHandler(const WifiHandler&) = delete;
|
||||
WifiHandler& operator=(const WifiHandler&) = delete;
|
||||
|
||||
char* build_password_key(const char* ssid);
|
||||
void get_wifi_credentials(char*& ssid, char*& password);
|
||||
|
||||
bool initialized = false;
|
||||
KVStorageHandler* kvs = nullptr;
|
||||
EventGroupHandle_t s_wifi_event_group = 0;
|
||||
SemaphoreHandle_t scan_mutex = nullptr;
|
||||
SemaphoreHandle_t connection_mutex = nullptr;
|
||||
// current connected / preferred SSID
|
||||
char* current_ssid = nullptr;
|
||||
// prevent auto-reconnect on expected disconnection, e.g. when user calls disconnect()
|
||||
// should be reset to false after connect()
|
||||
bool expect_disconnected = false;
|
||||
};
|
||||
53
main/touch/touch.cpp
Normal file
53
main/touch/touch.cpp
Normal file
@@ -0,0 +1,53 @@
|
||||
#include "touch.h"
|
||||
#include "common/constants.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
// TODO: implement actual touch functionality
|
||||
|
||||
TouchHandler::TouchHandler(QueueHandle_t touch_queue) {
|
||||
(void)touch_queue;
|
||||
}
|
||||
|
||||
TouchHandler::~TouchHandler() { }
|
||||
|
||||
EInkTouchHandler::EInkTouchHandler(QueueHandle_t touch_queue)
|
||||
: TouchHandler(touch_queue) { }
|
||||
|
||||
EInkTouchHandler::~EInkTouchHandler() { }
|
||||
|
||||
void EInkTouchHandler::init(EventGroupHandle_t system_event_group) {
|
||||
if (system_event_group != NULL) {
|
||||
xEventGroupSetBits(system_event_group, TOUCH_CALIBRATED_BIT);
|
||||
}
|
||||
}
|
||||
|
||||
void EInkTouchHandler::start_event_loop() {
|
||||
// Minimal background task to represent touch processing
|
||||
xTaskCreate(
|
||||
// use static adapter and pass `this` as task parameter
|
||||
EInkTouchHandler::task_adapter,
|
||||
"touch_task",
|
||||
2048,
|
||||
this,
|
||||
tskIDLE_PRIORITY + 1,
|
||||
nullptr
|
||||
);
|
||||
}
|
||||
|
||||
// static
|
||||
void EInkTouchHandler::task_adapter(void* arg) {
|
||||
EInkTouchHandler* self = static_cast<EInkTouchHandler*>(arg);
|
||||
if (self) {
|
||||
self->run_event_loop();
|
||||
} else {
|
||||
printf("EInkTouchHandler::task_adapter received null pointer\n");
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void EInkTouchHandler::run_event_loop() {
|
||||
for (;;) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
}
|
||||
32
main/touch/touch.h
Normal file
32
main/touch/touch.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#include "info/info.h"
|
||||
|
||||
class TouchHandler {
|
||||
public:
|
||||
TouchHandler(QueueHandle_t touch_queue);
|
||||
// the system_event_group is used to set touch-calibrated bit
|
||||
virtual void init(EventGroupHandle_t system_event_group) = 0;
|
||||
virtual void start_event_loop() = 0;
|
||||
virtual ~TouchHandler() = 0;
|
||||
private:
|
||||
TouchHandler(const TouchHandler&) = delete;
|
||||
TouchHandler& operator=(const TouchHandler&) = delete;
|
||||
};
|
||||
|
||||
class EInkTouchHandler : public TouchHandler {
|
||||
public:
|
||||
EInkTouchHandler(QueueHandle_t touch_queue);
|
||||
void init(EventGroupHandle_t system_event_group) override;
|
||||
void start_event_loop() override;
|
||||
~EInkTouchHandler() override;
|
||||
|
||||
private:
|
||||
// Task adapter used for FreeRTOS task creation. Forwards to
|
||||
// `run_event_loop()` using the `this` pointer passed as the task param.
|
||||
static void task_adapter(void* arg);
|
||||
|
||||
// Instance method implementing the touch event loop.
|
||||
void run_event_loop();
|
||||
// prevent copying
|
||||
EInkTouchHandler(const EInkTouchHandler&) = delete;
|
||||
EInkTouchHandler& operator=(const EInkTouchHandler&) = delete;
|
||||
};
|
||||
0
main/ui/ui.h
Normal file
0
main/ui/ui.h
Normal file
55
pytest_hello_world.py
Normal file
55
pytest_hello_world.py
Normal file
@@ -0,0 +1,55 @@
|
||||
# SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
import hashlib
|
||||
import logging
|
||||
from typing import Callable
|
||||
|
||||
import pytest
|
||||
from pytest_embedded_idf.dut import IdfDut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
from pytest_embedded_qemu.app import QemuApp
|
||||
from pytest_embedded_qemu.dut import QemuDut
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@idf_parametrize('target', ['supported_targets', 'preview_targets'], indirect=['target'])
|
||||
def test_hello_world(dut: IdfDut, log_minimum_free_heap_size: Callable[..., None]) -> None:
|
||||
dut.expect('Hello world!')
|
||||
log_minimum_free_heap_size()
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@idf_parametrize('target', ['linux'], indirect=['target'])
|
||||
def test_hello_world_linux(dut: IdfDut) -> None:
|
||||
dut.expect('Hello world!')
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@pytest.mark.macos_shell
|
||||
@idf_parametrize('target', ['linux'], indirect=['target'])
|
||||
def test_hello_world_macos(dut: IdfDut) -> None:
|
||||
dut.expect('Hello world!')
|
||||
|
||||
|
||||
def verify_elf_sha256_embedding(app: QemuApp, sha256_reported: str) -> None:
|
||||
sha256 = hashlib.sha256()
|
||||
with open(app.elf_file, 'rb') as f:
|
||||
sha256.update(f.read())
|
||||
sha256_expected = sha256.hexdigest()
|
||||
|
||||
logging.info(f'ELF file SHA256: {sha256_expected}')
|
||||
logging.info(f'ELF file SHA256 (reported by the app): {sha256_reported}')
|
||||
|
||||
# the app reports only the first several hex characters of the SHA256, check that they match
|
||||
if not sha256_expected.startswith(sha256_reported):
|
||||
raise ValueError('ELF file SHA256 mismatch')
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@pytest.mark.qemu
|
||||
@idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target'])
|
||||
def test_hello_world_host(app: QemuApp, dut: QemuDut) -> None:
|
||||
sha256_reported = dut.expect(r'ELF file SHA256:\s+([a-f0-9]+)').group(1).decode('utf-8')
|
||||
verify_elf_sha256_embedding(app, sha256_reported)
|
||||
|
||||
dut.expect('Hello world!')
|
||||
0
sdkconfig.ci
Normal file
0
sdkconfig.ci
Normal file
4
wokwi.toml
Normal file
4
wokwi.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[wokwi]
|
||||
version = 1
|
||||
firmware = 'build/flasher_args.json'
|
||||
elf = "build/ink-board.elf"
|
||||
Reference in New Issue
Block a user