Improvements & fixes (#589)

This commit is contained in:
Ken Van Hoeylandt 2026-07-26 21:26:52 +02:00 committed by GitHub
parent b98a813f3c
commit f21c0df6fe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 143 additions and 29 deletions

View File

@ -4,6 +4,7 @@ idf_component_register(
"Libraries/TactilityKernel/include" "Libraries/TactilityKernel/include"
"Libraries/TactilityFreeRtos/include" "Libraries/TactilityFreeRtos/include"
"Libraries/lvgl/include" "Libraries/lvgl/include"
"Libraries/minmea/include"
"Modules/lvgl-module/include" "Modules/lvgl-module/include"
# DRIVER_INCLUDE_DIRS_PLACEHOLDER # DRIVER_INCLUDE_DIRS_PLACEHOLDER
REQUIRES esp_timer REQUIRES esp_timer
@ -13,7 +14,9 @@ idf_component_register(
add_prebuilt_library(TactilityC Libraries/TactilityC/binary/libTactilityC.a) add_prebuilt_library(TactilityC Libraries/TactilityC/binary/libTactilityC.a)
add_prebuilt_library(TactilityKernel Libraries/TactilityKernel/binary/libTactilityKernel.a) add_prebuilt_library(TactilityKernel Libraries/TactilityKernel/binary/libTactilityKernel.a)
add_prebuilt_library(lvgl Libraries/lvgl/binary/liblvgl.a) add_prebuilt_library(lvgl Libraries/lvgl/binary/liblvgl.a)
add_prebuilt_library(minmea Libraries/minmea/binary/libminmea.a)
target_link_libraries(${COMPONENT_LIB} INTERFACE TactilityC) target_link_libraries(${COMPONENT_LIB} INTERFACE TactilityC)
target_link_libraries(${COMPONENT_LIB} INTERFACE TactilityKernel) target_link_libraries(${COMPONENT_LIB} INTERFACE TactilityKernel)
target_link_libraries(${COMPONENT_LIB} INTERFACE lvgl) target_link_libraries(${COMPONENT_LIB} INTERFACE lvgl)
target_link_libraries(${COMPONENT_LIB} INTERFACE minmea)

View File

@ -185,6 +185,13 @@ def main():
# elf_loader # elf_loader
{'src': 'Libraries/elf_loader/elf_loader.cmake', 'dst': 'Libraries/elf_loader/'}, {'src': 'Libraries/elf_loader/elf_loader.cmake', 'dst': 'Libraries/elf_loader/'},
{'src': 'Libraries/elf_loader/license.txt', 'dst': 'Libraries/elf_loader/'}, {'src': 'Libraries/elf_loader/license.txt', 'dst': 'Libraries/elf_loader/'},
# minmea
{'src': 'build/esp-idf/minmea/libminmea.a', 'dst': 'Libraries/minmea/binary/'},
{'src': 'Libraries/minmea/Include/**', 'dst': 'Libraries/minmea/include/'},
{'src': 'Libraries/minmea/CMakeLists.txt', 'dst': 'Libraries/minmea/'},
{'src': 'Libraries/minmea/README.md', 'dst': 'Libraries/minmea/'},
{'src': 'Libraries/minmea/LICENSE*.*', 'dst': 'Libraries/minmea/'},
{'src': 'Libraries/minmea/COPYING', 'dst': 'Libraries/minmea/'},
] ]
map_copy(mappings, target_path) map_copy(mappings, target_path)
@ -192,6 +199,7 @@ def main():
# Modules # Modules
add_module(target_path, "lvgl-module") add_module(target_path, "lvgl-module")
add_module(target_path, "crypt-module") add_module(target_path, "crypt-module")
add_module(target_path, "gps-module")
# Drivers - only ones actually built for this target (chip-restricted drivers like # Drivers - only ones actually built for this target (chip-restricted drivers like
# sc2356-module won't have a .a outside ESP32-P4) # sc2356-module won't have a .a outside ESP32-P4)

View File

@ -111,7 +111,7 @@ void lvgl_devices_detach() {
lv_disp_t* display = lv_disp_get_next(NULL); lv_disp_t* display = lv_disp_get_next(NULL);
while (display != NULL) { while (display != NULL) {
lv_display_delete(display); lvgl_display_remove(display);
display = lv_disp_get_next(NULL); display = lv_disp_get_next(NULL);
} }

View File

@ -6,26 +6,33 @@
#include <Tactility/Tactility.h> #include <Tactility/Tactility.h>
#include <Tactility/TactilityConfig.h> #include <Tactility/TactilityConfig.h>
#include <Tactility/bluetooth/Bluetooth.h>
#include <Tactility/LogMessages.h>
#include <Tactility/CpuAffinity.h> #include <Tactility/CpuAffinity.h>
#include <Tactility/MountPoints.h> #include <Tactility/MountPoints.h>
#include <Tactility/app/AppManifestParsing.h> #include <Tactility/app/AppManifestParsing.h>
#include <Tactility/app/AppRegistration.h> #include <Tactility/app/AppRegistration.h>
#include <Tactility/file/File.h> #include <Tactility/file/File.h>
#include <Tactility/file/FileLock.h> #include <Tactility/file/FileLock.h>
#include <Tactility/LogMessages.h>
#include <Tactility/hal/SdCard.h>
#include <Tactility/network/NtpPrivate.h> #include <Tactility/network/NtpPrivate.h>
#include <Tactility/Paths.h>
#include <Tactility/service/ServiceManifest.h> #include <Tactility/service/ServiceManifest.h>
#include <Tactility/service/ServiceRegistration.h> #include <Tactility/service/ServiceRegistration.h>
#include <Tactility/service/audio/Audio.h> #include <Tactility/service/audio/Audio.h>
#include <Tactility/settings/TimePrivate.h> #include <Tactility/settings/TimePrivate.h>
#ifdef ESP_PLATFORM
#include <Tactility/InitEsp.h>
#endif
#include <gps/module.h> #include <gps/module.h>
#include <gps_generic/gps_generic_module.h> #include <gps_generic/gps_generic_module.h>
#include <crypt/module.h> #include <crypt/module.h>
#include <lvgl/module.h> #include <lvgl/module.h>
#include <lvgl/widgets/toolbar.h> #include <lvgl/widgets/toolbar.h>
#include <tactility/concurrent/thread.h> #include <tactility/concurrent/thread.h>
#include <tactility/drivers/audio_stream.h> #include <tactility/drivers/audio_stream.h>
#include <tactility/drivers/display.h> #include <tactility/drivers/display.h>
@ -36,15 +43,7 @@
#include <tactility/filesystem/file_system.h> #include <tactility/filesystem/file_system.h>
#include <tactility/kernel_init.h> #include <tactility/kernel_init.h>
#include <tactility/log.h> #include <tactility/log.h>
#include <tactility/memory.h>
#ifdef ESP_PLATFORM
#include <Tactility/InitEsp.h>
#endif
#include "Tactility/Paths.h"
#include "Tactility/hal/SdCard.h"
#include <Tactility/bluetooth/Bluetooth.h>
namespace tt { namespace tt {
@ -371,6 +370,8 @@ static void onLvglStarted() {
#if TT_FEATURE_SCREENSHOT_ENABLED #if TT_FEATURE_SCREENSHOT_ENABLED
addService(service::screenshot::manifest); addService(service::screenshot::manifest);
#endif #endif
memory_trace();
} }
static void onLvglStopped() { static void onLvglStopped() {
@ -386,6 +387,8 @@ static void onLvglStopped() {
check(service::removeService(service::memorychecker::manifest.id)); check(service::removeService(service::memorychecker::manifest.id));
check(service::removeService(service::statusbar::manifest.id)); check(service::removeService(service::statusbar::manifest.id));
check(service::removeService(service::gui::manifest.id)); check(service::removeService(service::gui::manifest.id));
memory_trace();
} }
void run(Module* dtsModules[], DtsDevice dtsDevices[]) { void run(Module* dtsModules[], DtsDevice dtsDevices[]) {

View File

@ -95,6 +95,7 @@ private:
// Note: the result code maps to values from cstdlib's errno.h // Note: the result code maps to values from cstdlib's errno.h
lastError = getErrorCodeString(-relocate_result); lastError = getErrorCodeString(-relocate_result);
LOG_E(TAG, "Application failed to load: %s", lastError.c_str()); LOG_E(TAG, "Application failed to load: %s", lastError.c_str());
esp_elf_deinit(&elf);
elfFileData = nullptr; elfFileData = nullptr;
return false; return false;
} }

View File

@ -200,10 +200,10 @@ size_t receiveFile(httpd_req_t* request, size_t length, const std::string& fileP
auto expected_chunk_size = std::min<size_t>(BUFFER_SIZE, length - bytes_received); auto expected_chunk_size = std::min<size_t>(BUFFER_SIZE, length - bytes_received);
size_t receive_chunk_size = httpd_req_recv(request, buffer, expected_chunk_size); size_t receive_chunk_size = httpd_req_recv(request, buffer, expected_chunk_size);
if (receive_chunk_size <= 0) { if (receive_chunk_size <= 0) {
LOG_E(TAG, "Receive failed"); LOG_E(TAG, "Receive failed, got 0 bytes but expected %zu more", length - bytes_received);
break; break;
} }
if (fwrite(buffer, 1, receive_chunk_size, file) != (size_t)receive_chunk_size) { if (fwrite(buffer, 1, receive_chunk_size, file) != receive_chunk_size) {
LOG_E(TAG, "Failed to write all bytes"); LOG_E(TAG, "Failed to write all bytes");
break; break;
} }

View File

@ -1,5 +1,6 @@
#include <Tactility/service/ServiceRegistration.h> #include <Tactility/service/ServiceRegistration.h>
#include <Tactility/Mutex.h>
#include <Tactility/service/ServiceContext.h> #include <Tactility/service/ServiceContext.h>
#include <Tactility/service/ServiceManifest.h> #include <Tactility/service/ServiceManifest.h>
@ -9,11 +10,35 @@
#include <cassert> #include <cassert>
#include <memory> #include <memory>
#include <unordered_map>
namespace tt::service { namespace tt::service {
constexpr auto* TAG = "ServiceRegistration"; constexpr auto* TAG = "ServiceRegistration";
namespace {
// Tracks the heap allocations addService() makes per registered id, so removeService()
// can free them once the kernel confirms the manifest is unregistered. The kernel only
// ever stores the raw pointer it's handed (see service_manager_add/_remove) - it never
// takes ownership - so the registering side (us) is responsible for the lifetime.
struct AllocatedManifest {
std::shared_ptr<const ServiceManifest>* persistentManifest;
::ServiceManifest* cManifest;
};
Mutex& allocatedManifestsMutex() {
static Mutex mutex;
return mutex;
}
std::unordered_map<std::string, AllocatedManifest>& allocatedManifests() {
static std::unordered_map<std::string, AllocatedManifest> map;
return map;
}
} // namespace
// Bridges the kernel's C ServiceManifest/Service callbacks to the C++ Service // Bridges the kernel's C ServiceManifest/Service callbacks to the C++ Service
// instances they wrap. Declared extern "C" to match the linkage of the C // instances they wrap. Declared extern "C" to match the linkage of the C
// function-pointer types they're assigned to (see e.g. gpio_controller.cpp). // function-pointer types they're assigned to (see e.g. gpio_controller.cpp).
@ -48,9 +73,8 @@ void addService(std::shared_ptr<const ServiceManifest> manifest, bool autoStart)
return; return;
} }
// Intentionally never freed: removeService() only unregisters the manifest // Freed by removeService() once the kernel confirms the manifest is unregistered.
// from the kernel, it doesn't own this allocation. Keeps id's backing // Keeps id's backing string alive for cManifest.id below in the meantime.
// string alive for cManifest.id below.
auto* persistentManifest = new std::shared_ptr(manifest); auto* persistentManifest = new std::shared_ptr(manifest);
auto* cManifest = new ::ServiceManifest { auto* cManifest = new ::ServiceManifest {
.id = (*persistentManifest)->id.c_str(), .id = (*persistentManifest)->id.c_str(),
@ -60,6 +84,12 @@ void addService(std::shared_ptr<const ServiceManifest> manifest, bool autoStart)
.on_stop = cppOnStopTrampoline, .on_stop = cppOnStopTrampoline,
}; };
{
auto lock = allocatedManifestsMutex().asScopedLock();
lock.lock();
allocatedManifests()[id] = AllocatedManifest { persistentManifest, cManifest };
}
error_t error = service_manager_add(cManifest, autoStart); error_t error = service_manager_add(cManifest, autoStart);
if (error != ERROR_NONE) { if (error != ERROR_NONE) {
LOG_E(TAG, "Failed to add service %s: %s", id.c_str(), error_to_string(error)); LOG_E(TAG, "Failed to add service %s: %s", id.c_str(), error_to_string(error));
@ -85,6 +115,20 @@ bool removeService(const std::string& id) {
LOG_E(TAG, "Failed to remove service %s: %s", id.c_str(), error_to_string(error)); LOG_E(TAG, "Failed to remove service %s: %s", id.c_str(), error_to_string(error));
return false; return false;
} }
// The kernel has confirmed the manifest is unregistered, so id (which points into
// persistentManifest's string) is no longer needed by anything - safe to free now.
{
auto lock = allocatedManifestsMutex().asScopedLock();
lock.lock();
auto iterator = allocatedManifests().find(id);
if (iterator != allocatedManifests().end()) {
delete iterator->second.cManifest;
delete iterator->second.persistentManifest;
allocatedManifests().erase(iterator);
}
}
LOG_I(TAG, "Removed %s", id.c_str()); LOG_I(TAG, "Removed %s", id.c_str());
return true; return true;
} }

View File

@ -157,13 +157,20 @@ esp_err_t DevelopmentService::handleAppInstall(httpd_req_t* request) {
// Create tmp directory // Create tmp directory
const std::string tmp_path = getTempPath(); const std::string tmp_path = getTempPath();
if (!file::findOrCreateDirectory(tmp_path, 0777)) { if (!file::findOrCreateDirectory(tmp_path, 0777)) {
httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to save file"); httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to create temp path");
return ESP_FAIL; return ESP_FAIL;
} }
auto file_path = std::format("{}/{}", tmp_path, filename_entry->second); std::string safe_name = file::getLastPathSegment(filename_entry->second);
if (safe_name.empty() || safe_name.find("..") != std::string::npos ||
safe_name.find('/') != std::string::npos || safe_name.find('\\') != std::string::npos) {
httpd_resp_send_err(request, HTTPD_400_BAD_REQUEST, "invalid filename");
return ESP_FAIL;
}
auto file_path = std::format("{}/{}", tmp_path, safe_name);
if (network::receiveFile(request, file_size, file_path) != file_size) { if (network::receiveFile(request, file_size, file_path) != file_size) {
httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to save file"); file::deleteFile(file_path);
httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to receive file");
return ESP_FAIL; return ESP_FAIL;
} }

View File

@ -200,6 +200,13 @@ void GuiService::redraw() {
// Create a default group which adds all objects automatically, // Create a default group which adds all objects automatically,
// and assign all indevs to it. // and assign all indevs to it.
// This enables navigation with limited input, such as encoder wheels. // This enables navigation with limited input, such as encoder wheels.
// The previous default group (if any) is no longer referenced by anything
// after lv_obj_clean() above, so it must be freed here or it leaks.
auto* previous_group = lv_group_get_default();
if (previous_group != nullptr) {
lv_group_delete(previous_group);
}
lv_group_t* group = lv_group_create(); lv_group_t* group = lv_group_create();
auto* indev = lv_indev_get_next(nullptr); auto* indev = lv_indev_get_next(nullptr);
while (indev) { while (indev) {
@ -279,6 +286,17 @@ void GuiService::onStop(ServiceContext& service) {
lv_group_delete(keyboardGroup); lv_group_delete(keyboardGroup);
keyboardGroup = nullptr; keyboardGroup = nullptr;
} }
auto* default_group = lv_group_get_default();
if (default_group != nullptr) {
lv_group_delete(default_group);
lv_group_set_default(nullptr);
}
auto* screen_root = lv_screen_active();
if (screen_root != nullptr) {
lv_obj_clean(screen_root);
}
lvgl_unlock(); lvgl_unlock();
delete thread; delete thread;

View File

@ -10,12 +10,8 @@
#include <vector> #include <vector>
#ifdef ESP_PLATFORM
#include <esp_heap_caps.h>
#include <utility>
#endif
#include <tactility/log.h> #include <tactility/log.h>
#include <tactility/memory.h>
namespace tt::service::loader { namespace tt::service::loader {
@ -72,6 +68,8 @@ void LoaderService::onStartAppMessage(const std::string& id, app::LaunchId launc
appStack.push_back(new_app); appStack.push_back(new_app);
transitionAppToState(new_app, app::State::Created); transitionAppToState(new_app, app::State::Created);
transitionAppToState(new_app, app::State::Showing); transitionAppToState(new_app, app::State::Showing);
memory_trace();
} }
void LoaderService::onStopTopAppMessage(const std::string& id) { void LoaderService::onStopTopAppMessage(const std::string& id) {
@ -125,10 +123,6 @@ void LoaderService::onStopTopAppMessage(const std::string& id) {
LOG_W(TAG, "Memory leak: Stopped %s, but use count is %d", app_to_stop->getManifest().appId.c_str(), (int)(app_to_stop->getApp().use_count() - 2)); LOG_W(TAG, "Memory leak: Stopped %s, but use count is %d", app_to_stop->getManifest().appId.c_str(), (int)(app_to_stop->getApp().use_count() - 2));
} }
#ifdef ESP_PLATFORM
LOG_I(TAG, "Free heap: %d", (int)heap_caps_get_free_size(MALLOC_CAP_INTERNAL));
#endif
std::shared_ptr<app::AppInstance> instance_to_resume; std::shared_ptr<app::AppInstance> instance_to_resume;
// If there's a previous app, resume it // If there's a previous app, resume it
if (!appStack.empty()) { if (!appStack.empty()) {
@ -167,6 +161,8 @@ void LoaderService::onStopTopAppMessage(const std::string& id) {
); );
} }
} }
memory_trace();
} }
int LoaderService::findAppInStack(const std::string& id) const { int LoaderService::findAppInStack(const std::string& id) const {

View File

@ -0,0 +1,11 @@
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
void memory_trace();
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,23 @@
#include <tactility/log.h>
#include <tactility/memory.h>
#ifdef ESP_PLATFORM
#include <esp_heap_caps.h>
#endif
constexpr auto* TAG = "memory";
extern "C" {
void memory_trace() {
#ifdef ESP_PLATFORM
size_t heap_free = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
size_t heap_total = heap_caps_get_total_size(MALLOC_CAP_INTERNAL);
LOG_I(TAG, "Heap: %zu / %zu available", heap_free, heap_total);
size_t ext_free = heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
size_t ext_total = heap_caps_get_total_size(MALLOC_CAP_SPIRAM);
LOG_I(TAG, "External: %zu / %zu available", ext_free, ext_total);
#endif
}
}