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:
GW_MC
2026-01-17 20:09:33 +08:00
parent 64fe528abc
commit e458256193
27 changed files with 2279 additions and 0 deletions

5
main/CMakeLists.txt Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

1513
main/lv_conf.h Normal file

File diff suppressed because it is too large Load Diff

175
main/main.cpp Normal file
View 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
View File

53
main/touch/touch.cpp Normal file
View 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
View 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
View File