mirror of
https://github.com/ByteWelder/Tactility.git
synced 2026-02-21 16:05:05 +00:00
Add low memory warning (#417)
This commit is contained in:
parent
dddca1ea76
commit
a4f4784ed9
BIN
Data/system/service/MemoryChecker/assets/memory_alert.png
Normal file
BIN
Data/system/service/MemoryChecker/assets/memory_alert.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 273 B |
@ -10,11 +10,22 @@ constexpr auto STATUSBAR_ICON_LIMIT = 8;
|
|||||||
constexpr auto STATUSBAR_ICON_SIZE = 20;
|
constexpr auto STATUSBAR_ICON_SIZE = 20;
|
||||||
constexpr auto STATUSBAR_HEIGHT = STATUSBAR_ICON_SIZE + 2;
|
constexpr auto STATUSBAR_HEIGHT = STATUSBAR_ICON_SIZE + 2;
|
||||||
|
|
||||||
|
/** Create a statusbar widget. Needs to be called with LVGL lock. */
|
||||||
lv_obj_t* statusbar_create(lv_obj_t* parent);
|
lv_obj_t* statusbar_create(lv_obj_t* parent);
|
||||||
int8_t statusbar_icon_add(const std::string& image);
|
|
||||||
|
/** Add an icon to the statusbar. Does not need to be called with LVGL lock. */
|
||||||
|
int8_t statusbar_icon_add(const std::string& image, bool visible);
|
||||||
|
|
||||||
|
/** Add an icon to the statusbar. Does not need to be called with LVGL lock. */
|
||||||
int8_t statusbar_icon_add();
|
int8_t statusbar_icon_add();
|
||||||
|
|
||||||
|
/** Remove an icon from the statusbar. Does not need to be called with LVGL lock. */
|
||||||
void statusbar_icon_remove(int8_t id);
|
void statusbar_icon_remove(int8_t id);
|
||||||
|
|
||||||
|
/** Update an icon's image from the statusbar. Does not need to be called with LVGL lock. */
|
||||||
void statusbar_icon_set_image(int8_t id, const std::string& image);
|
void statusbar_icon_set_image(int8_t id, const std::string& image);
|
||||||
|
|
||||||
|
/** Update the visibility for an icon on the statusbar. Does not need to be called with LVGL lock. */
|
||||||
void statusbar_icon_set_visibility(int8_t id, bool visible);
|
void statusbar_icon_set_visibility(int8_t id, bool visible);
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Tactility/service/Service.h"
|
||||||
|
|
||||||
|
#include <Tactility/Mutex.h>
|
||||||
|
#include <Tactility/Timer.h>
|
||||||
|
|
||||||
|
namespace tt::service::memorychecker {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs a background timer that validates if there's sufficient memory available.
|
||||||
|
* It shows a statusbar icon when memory is low. It also outputs warning to the log.
|
||||||
|
*/
|
||||||
|
class MemoryCheckerService final : public Service {
|
||||||
|
|
||||||
|
Mutex mutex = Mutex(Mutex::Type::Recursive);
|
||||||
|
Timer timer = Timer(Timer::Type::Periodic, [this] { onTimerUpdate(); });
|
||||||
|
|
||||||
|
// LVGL Statusbar icon
|
||||||
|
int8_t statusbarIconId = -1;
|
||||||
|
// Keep track of state to minimize UI updates
|
||||||
|
bool memoryLow = false;
|
||||||
|
|
||||||
|
void onTimerUpdate();
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
bool onStart(ServiceContext& service) override;
|
||||||
|
|
||||||
|
void onStop(ServiceContext& service) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@ -43,6 +43,7 @@ namespace service {
|
|||||||
// Secondary (UI)
|
// Secondary (UI)
|
||||||
namespace gui { extern const ServiceManifest manifest; }
|
namespace gui { extern const ServiceManifest manifest; }
|
||||||
namespace loader { extern const ServiceManifest manifest; }
|
namespace loader { extern const ServiceManifest manifest; }
|
||||||
|
namespace memorychecker { extern const ServiceManifest manifest; }
|
||||||
namespace statusbar { extern const ServiceManifest manifest; }
|
namespace statusbar { extern const ServiceManifest manifest; }
|
||||||
#if TT_FEATURE_SCREENSHOT_ENABLED
|
#if TT_FEATURE_SCREENSHOT_ENABLED
|
||||||
namespace screenshot { extern const ServiceManifest manifest; }
|
namespace screenshot { extern const ServiceManifest manifest; }
|
||||||
@ -215,6 +216,7 @@ static void registerAndStartSecondaryServices() {
|
|||||||
addService(service::loader::manifest);
|
addService(service::loader::manifest);
|
||||||
addService(service::gui::manifest);
|
addService(service::gui::manifest);
|
||||||
addService(service::statusbar::manifest);
|
addService(service::statusbar::manifest);
|
||||||
|
addService(service::memorychecker::manifest);
|
||||||
#if TT_FEATURE_SCREENSHOT_ENABLED
|
#if TT_FEATURE_SCREENSHOT_ENABLED
|
||||||
addService(service::screenshot::manifest);
|
addService(service::screenshot::manifest);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -229,13 +229,13 @@ static void statusbar_event(TT_UNUSED const lv_obj_class_t* class_p, lv_event_t*
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int8_t statusbar_icon_add(const std::string& image) {
|
int8_t statusbar_icon_add(const std::string& image, bool visible) {
|
||||||
statusbar_data.mutex.lock();
|
statusbar_data.mutex.lock();
|
||||||
int8_t result = -1;
|
int8_t result = -1;
|
||||||
for (int8_t i = 0; i < STATUSBAR_ICON_LIMIT; ++i) {
|
for (int8_t i = 0; i < STATUSBAR_ICON_LIMIT; ++i) {
|
||||||
if (!statusbar_data.icons[i].claimed) {
|
if (!statusbar_data.icons[i].claimed) {
|
||||||
statusbar_data.icons[i].claimed = true;
|
statusbar_data.icons[i].claimed = true;
|
||||||
statusbar_data.icons[i].visible = !image.empty();
|
statusbar_data.icons[i].visible = visible;
|
||||||
statusbar_data.icons[i].image = image;
|
statusbar_data.icons[i].image = image;
|
||||||
result = i;
|
result = i;
|
||||||
TT_LOG_D(TAG, "id %d: added", i);
|
TT_LOG_D(TAG, "id %d: added", i);
|
||||||
@ -248,7 +248,7 @@ int8_t statusbar_icon_add(const std::string& image) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int8_t statusbar_icon_add() {
|
int8_t statusbar_icon_add() {
|
||||||
return statusbar_icon_add("");
|
return statusbar_icon_add("", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void statusbar_icon_remove(int8_t id) {
|
void statusbar_icon_remove(int8_t id) {
|
||||||
|
|||||||
@ -0,0 +1,93 @@
|
|||||||
|
#include <Tactility/Tactility.h>
|
||||||
|
#include <Tactility/lvgl/Statusbar.h>
|
||||||
|
#include <Tactility/service/ServiceContext.h>
|
||||||
|
#include <Tactility/service/ServiceManifest.h>
|
||||||
|
#include <Tactility/service/ServicePaths.h>
|
||||||
|
#include <Tactility/service/memorychecker/MemoryCheckerService.h>
|
||||||
|
|
||||||
|
namespace tt::service::memorychecker {
|
||||||
|
|
||||||
|
constexpr const char* TAG = "MemoryChecker";
|
||||||
|
constexpr TickType_t TIMER_UPDATE_INTERVAL = 1000U / portTICK_PERIOD_MS;
|
||||||
|
|
||||||
|
// Total memory (in bytes) that should be free before warnings occur
|
||||||
|
constexpr auto TOTAL_FREE_THRESHOLD = 10'000;
|
||||||
|
// Smallest memory block size (in bytes) that should be available before warnings occur
|
||||||
|
constexpr auto LARGEST_FREE_BLOCK_THRESHOLD = 2'000;
|
||||||
|
|
||||||
|
static size_t getInternalFree() {
|
||||||
|
#ifdef ESP_PLATFORM
|
||||||
|
return heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
|
||||||
|
#else
|
||||||
|
// PC mock data
|
||||||
|
return 1024 * 1024;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t getInternalLargestFreeBlock() {
|
||||||
|
#ifdef ESP_PLATFORM
|
||||||
|
return heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL);
|
||||||
|
#else
|
||||||
|
// PC mock data
|
||||||
|
return 1024 * 1024;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isMemoryLow() {
|
||||||
|
bool memory_low = false;
|
||||||
|
const auto total_free = getInternalFree();
|
||||||
|
if (total_free < TOTAL_FREE_THRESHOLD) {
|
||||||
|
TT_LOG_W(TAG, "Internal memory low: %zu bytes", total_free);
|
||||||
|
memory_low = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto largest_block = getInternalLargestFreeBlock();
|
||||||
|
if (largest_block < LARGEST_FREE_BLOCK_THRESHOLD) {
|
||||||
|
TT_LOG_W(TAG, "Largest free internal memory block is %zu bytes", largest_block);
|
||||||
|
memory_low = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return memory_low;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MemoryCheckerService::onStart(ServiceContext& service) {
|
||||||
|
auto lock = mutex.asScopedLock();
|
||||||
|
lock.lock();
|
||||||
|
|
||||||
|
auto icon_path = std::string("A:") + service.getPaths()->getAssetsPath("memory_alert.png");
|
||||||
|
statusbarIconId = lvgl::statusbar_icon_add(icon_path, false);
|
||||||
|
lvgl::statusbar_icon_set_visibility(statusbarIconId, false);
|
||||||
|
|
||||||
|
timer.setThreadPriority(Thread::Priority::Lower);
|
||||||
|
timer.start(TIMER_UPDATE_INTERVAL);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemoryCheckerService::onStop(ServiceContext& service) {
|
||||||
|
timer.stop();
|
||||||
|
|
||||||
|
// Lock after timer stop, because the timer task might still be busy and this way we await it
|
||||||
|
auto lock = mutex.asScopedLock();
|
||||||
|
lock.lock();
|
||||||
|
|
||||||
|
lvgl::statusbar_icon_remove(statusbarIconId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemoryCheckerService::onTimerUpdate() {
|
||||||
|
auto lock = mutex.asScopedLock();
|
||||||
|
lock.lock();
|
||||||
|
|
||||||
|
bool memory_low = isMemoryLow();
|
||||||
|
if (memory_low != memoryLow) {
|
||||||
|
memoryLow = memory_low;
|
||||||
|
lvgl::statusbar_icon_set_visibility(statusbarIconId, memory_low);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern const ServiceManifest manifest = {
|
||||||
|
.id = "MemoryChecker",
|
||||||
|
.createService = create<MemoryCheckerService>
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@ -269,6 +269,8 @@ public:
|
|||||||
service->update();
|
service->update();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
updateTimer->setThreadPriority(Thread::Priority::Lower);
|
||||||
|
|
||||||
// We want to try and scan more often in case of startup or scan lock failure
|
// We want to try and scan more often in case of startup or scan lock failure
|
||||||
updateTimer->start(1000);
|
updateTimer->start(1000);
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user