Add main application logic and touch handling functionality
- Implemented main application entry point in main.cpp, initializing queues and event groups. - Created TouchHandler and EInkTouchHandler classes for handling touch events. - Added a minimal event loop for touch processing in touch.cpp. - Introduced unit tests for the hello world application in pytest_hello_world.py. - Added configuration files for CI and Wokwi support. - Created empty header files for network and UI modules.
This commit is contained in:
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": {}
|
||||||
|
}
|
||||||
5
main/CMakeLists.txt
Normal file
5
main/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
file(GLOB SRCS "main.cpp" "*.cpp" "*.c" "**/*.cpp" "**/*.c")
|
||||||
|
idf_component_register(SRCS ${SRCS}
|
||||||
|
PRIV_REQUIRES
|
||||||
|
spi_flash
|
||||||
|
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
|
||||||
14
main/common/constants.h
Normal file
14
main/common/constants.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// 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)
|
||||||
30
main/common/queue_defs.h
Normal file
30
main/common/queue_defs.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#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;
|
||||||
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();
|
||||||
0
main/io/io.h
Normal file
0
main/io/io.h
Normal file
1513
main/lv_conf.h
Normal file
1513
main/lv_conf.h
Normal file
File diff suppressed because it is too large
Load Diff
175
main/main.cpp
Normal file
175
main/main.cpp
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
/*
|
||||||
|
* 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 "info/info.h"
|
||||||
|
#include "display/display.h"
|
||||||
|
#include "touch/touch.h"
|
||||||
|
#include <tick/lv_tick.h>
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
//
|
||||||
|
DisplayHandler* display_handler = new EInkDisplayHandler(touch_event_queue, lvgl_mutex);
|
||||||
|
TouchHandler* touch_handler = new EInkTouchHandler(touch_event_queue);
|
||||||
|
//
|
||||||
|
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,
|
||||||
|
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_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
|
||||||
|
);
|
||||||
|
} 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();
|
||||||
|
}
|
||||||
0
main/network/network.h
Normal file
0
main/network/network.h
Normal file
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