Merge develop into main (#327)
## New features - Implemented support for app packaging in firmware and `tactility.py`: load `.app` files instead of `.elf` files. Install apps remotely or via `FileBrowser`. - Ensure headless mode works: all services that require LVGL can deal with the absence of a display - Service `onStart()` is now allowed to fail (return `bool` result) - Added and improved various file-related helper functions ## Improvements - Completely revamped the SystemInfo app UI - Improved Calculator UI of internal and external variant - Fix Chat UI and removed the emoji buttons for now - Fix for toolbar bottom padding issue in all apps ## Fixes - Fix for allowing recursive locking for certain SPI SD cards & more
This commit is contained in:
committed by
GitHub
parent
068600f98c
commit
84049658db
@@ -1,10 +1,11 @@
|
||||
#include <Tactility/Tactility.h>
|
||||
#include <Tactility/TactilityConfig.h>
|
||||
|
||||
#include <Tactility/app/AppRegistration.h>
|
||||
#include <Tactility/DispatcherThread.h>
|
||||
#include <Tactility/MountPoints.h>
|
||||
#include <Tactility/file/File.h>
|
||||
#include <Tactility/file/PropertiesFile.h>
|
||||
#include <Tactility/hal/HalPrivate.h>
|
||||
#include <Tactility/hal/sdcard/SdCardMounting.h>
|
||||
#include <Tactility/lvgl/LvglPrivate.h>
|
||||
#include <Tactility/network/NtpPrivate.h>
|
||||
#include <Tactility/service/ServiceManifest.h>
|
||||
@@ -12,6 +13,9 @@
|
||||
#include <Tactility/service/loader/Loader.h>
|
||||
#include <Tactility/settings/TimePrivate.h>
|
||||
|
||||
#include <map>
|
||||
#include <format>
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
#include <Tactility/InitEsp.h>
|
||||
#endif
|
||||
@@ -137,6 +141,67 @@ static void registerSystemApps() {
|
||||
}
|
||||
}
|
||||
|
||||
static void registerInstalledApp(std::string path) {
|
||||
TT_LOG_I(TAG, "Registering app at %s", path.c_str());
|
||||
std::string manifest_path = path + "/manifest.properties";
|
||||
if (!file::isFile(manifest_path)) {
|
||||
TT_LOG_E(TAG, "Manifest not found at %s", manifest_path.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
std::map<std::string, std::string> manifest;
|
||||
if (!file::loadPropertiesFile(manifest_path, manifest)) {
|
||||
TT_LOG_E(TAG, "Failed to load manifest at %s", manifest_path.c_str());
|
||||
}
|
||||
|
||||
auto app_id_entry = manifest.find("[app]id");
|
||||
if (app_id_entry == manifest.end()) {
|
||||
TT_LOG_E(TAG, "Failed to find app id in manifest");
|
||||
return;
|
||||
}
|
||||
|
||||
auto app_name_entry = manifest.find("[app]name");
|
||||
if (app_name_entry == manifest.end()) {
|
||||
TT_LOG_E(TAG, "Failed to find app name in manifest");
|
||||
return;
|
||||
}
|
||||
|
||||
app::addApp({
|
||||
.id = app_id_entry->second,
|
||||
.name = app_name_entry->second,
|
||||
.type = app::Type::User,
|
||||
.location = app::Location::external(path)
|
||||
});
|
||||
}
|
||||
|
||||
static void registerInstalledApps(const std::string& path) {
|
||||
file::listDirectory(path, [&path](const auto& entry) {
|
||||
auto absolute_path = std::format("{}/{}", path, entry.d_name);
|
||||
if (file::isDirectory(absolute_path)) {
|
||||
registerInstalledApp(absolute_path);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static void registerInstalledAppsFromSdCard(const std::shared_ptr<hal::sdcard::SdCardDevice>& sdcard) {
|
||||
auto sdcard_root_path = sdcard->getMountPath();
|
||||
auto app_path = std::format("{}/apps", sdcard_root_path);
|
||||
sdcard->getLock()->lock();
|
||||
if (file::isDirectory(app_path)) {
|
||||
registerInstalledApps(app_path);
|
||||
}
|
||||
sdcard->getLock()->unlock();
|
||||
}
|
||||
|
||||
static void registerInstalledAppsFromSdCards() {
|
||||
auto sdcard_devices = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard);
|
||||
for (const auto& sdcard : sdcard_devices) {
|
||||
if (sdcard->isMounted()) {
|
||||
registerInstalledAppsFromSdCard(sdcard);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void registerUserApps(const std::vector<const app::AppManifest*>& apps) {
|
||||
TT_LOG_I(TAG, "Registering user apps");
|
||||
for (auto* manifest : apps) {
|
||||
@@ -176,8 +241,13 @@ static void registerAndStartUserServices(const std::vector<const service::Servic
|
||||
|
||||
void initFromBootApp() {
|
||||
auto configuration = getConfiguration();
|
||||
// Then we register system apps. They are not used/started yet.
|
||||
// Then we register apps. They are not used/started yet.
|
||||
registerSystemApps();
|
||||
auto data_apps_path = std::format("{}/apps", file::MOUNT_POINT_DATA);
|
||||
if (file::isDirectory(data_apps_path)) {
|
||||
registerInstalledApps(data_apps_path);
|
||||
}
|
||||
registerInstalledAppsFromSdCards();
|
||||
// Then we register and start user services. They are started after system app
|
||||
// registration just in case they want to figure out which system apps are installed.
|
||||
registerAndStartUserServices(configuration->services);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "Tactility/app/App.h"
|
||||
|
||||
#include <Tactility/app/App.h>
|
||||
#include <Tactility/service/loader/Loader.h>
|
||||
|
||||
namespace tt::app {
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
#include <Tactility/app/App.h>
|
||||
|
||||
#include <Tactility/MountPoints.h>
|
||||
#include <Tactility/app/AppManifest.h>
|
||||
#include <Tactility/app/AppRegistration.h>
|
||||
#include <Tactility/file/File.h>
|
||||
#include <Tactility/file/FileLock.h>
|
||||
#include <Tactility/file/PropertiesFile.h>
|
||||
#include <Tactility/hal/Device.h>
|
||||
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <format>
|
||||
#include <libgen.h>
|
||||
#include <map>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <minitar.h>
|
||||
|
||||
constexpr auto* TAG = "App";
|
||||
|
||||
namespace tt::app {
|
||||
|
||||
static int untarFile(const minitar_entry* entry, const void* buf, const std::string& destinationPath) {
|
||||
auto absolute_path = destinationPath + "/" + entry->metadata.path;
|
||||
if (!file::findOrCreateDirectory(destinationPath, 0777)) return 1;
|
||||
|
||||
int fd = open(absolute_path.c_str(), O_WRONLY | O_CREAT | O_EXCL | O_CLOEXEC, 0644);
|
||||
if (fd < 0) return 1;
|
||||
|
||||
if (write(fd, buf, entry->metadata.size) < 0) return 1;
|
||||
|
||||
// Note: fchmod() doesn't exist on ESP-IDF and chmod() does nothing on that platform
|
||||
if (chmod(absolute_path.c_str(), entry->metadata.mode) < 0) return 1;
|
||||
|
||||
return close(fd);
|
||||
}
|
||||
|
||||
static bool untar_directory(const minitar_entry* entry, const std::string& destinationPath) {
|
||||
auto absolute_path = destinationPath + "/" + entry->metadata.path;
|
||||
if (!file::findOrCreateDirectory(absolute_path, 0777)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool untar(const std::string& tarPath, const std::string& destinationPath) {
|
||||
minitar mp;
|
||||
if (minitar_open(tarPath.c_str(), &mp) != 0) {
|
||||
perror(tarPath.c_str());
|
||||
return 1;
|
||||
}
|
||||
bool success = true;
|
||||
minitar_entry entry;
|
||||
|
||||
do {
|
||||
if (minitar_read_entry(&mp, &entry) == 0) {
|
||||
TT_LOG_I(TAG, "Extracting %s", entry.metadata.path);
|
||||
if (entry.metadata.type == MTAR_DIRECTORY) {
|
||||
if (!strcmp(entry.metadata.name, ".") || !strcmp(entry.metadata.name, "..") || !strcmp(entry.metadata.name, "/")) continue;
|
||||
if (!untar_directory(&entry, destinationPath)) {
|
||||
TT_LOG_E(TAG, "Failed to create directory %s/%s: %s", destinationPath.c_str(), entry.metadata.name, strerror(errno));
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
} else if (entry.metadata.type == MTAR_REGULAR) {
|
||||
auto file_buffer = static_cast<char*>(malloc(entry.metadata.size));
|
||||
if (!file_buffer) {
|
||||
TT_LOG_E(TAG, "Failed to allocate %d bytes for file %s", entry.metadata.size, entry.metadata.path);;
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
|
||||
minitar_read_contents(&mp, &entry, file_buffer, entry.metadata.size);
|
||||
int status = untarFile(&entry, file_buffer, destinationPath);
|
||||
free(file_buffer);
|
||||
if (status != 0) {
|
||||
TT_LOG_E(TAG, "Failed to extract file %s: %s", entry.metadata.path, strerror(errno));
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
} else if (entry.metadata.type == MTAR_SYMLINK) {
|
||||
TT_LOG_E(TAG, "SYMLINK not supported");
|
||||
} else if (entry.metadata.type == MTAR_HARDLINK) {
|
||||
TT_LOG_E(TAG, "HARDLINK not supported");
|
||||
} else if (entry.metadata.type == MTAR_FIFO) {
|
||||
TT_LOG_E(TAG, "FIFO not supported");
|
||||
} else if (entry.metadata.type == MTAR_BLKDEV) {
|
||||
TT_LOG_E(TAG, "BLKDEV not supported");
|
||||
} else if (entry.metadata.type == MTAR_CHRDEV) {
|
||||
TT_LOG_E(TAG, "CHRDEV not supported");
|
||||
} else {
|
||||
TT_LOG_E(TAG, "Unknown entry type: %d", entry.metadata.type);
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
} else break;
|
||||
} while (true);
|
||||
minitar_close(&mp);
|
||||
return success;
|
||||
}
|
||||
|
||||
bool findFirstMountedSdCardPath(std::string& path) {
|
||||
// const auto sdcards = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard);
|
||||
bool is_set = false;
|
||||
hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard, [&is_set, &path](const auto& device) {
|
||||
if (device->isMounted()) {
|
||||
path = device->getMountPath();
|
||||
is_set = true;
|
||||
return false; // stop iterating
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return is_set;
|
||||
}
|
||||
|
||||
std::string getTempPath() {
|
||||
std::string root_path;
|
||||
if (!findFirstMountedSdCardPath(root_path)) {
|
||||
root_path = file::MOUNT_POINT_DATA;
|
||||
}
|
||||
return root_path + "/tmp";
|
||||
}
|
||||
|
||||
std::string getInstallPath() {
|
||||
std::string root_path;
|
||||
if (!findFirstMountedSdCardPath(root_path)) {
|
||||
root_path = file::MOUNT_POINT_DATA;
|
||||
}
|
||||
return root_path + "/apps";
|
||||
}
|
||||
|
||||
bool install(const std::string& path) {
|
||||
// TODO: Make better: lock for each path type properly (source vs target)
|
||||
|
||||
// We lock and unlock frequently because SPI SD card devices share
|
||||
// the lock with the display. We don't want to lock the display for very long.
|
||||
|
||||
auto app_parent_path = getInstallPath();
|
||||
TT_LOG_I(TAG, "Installing app %s to %s", path.c_str(), app_parent_path.c_str());
|
||||
|
||||
auto lock = file::getLock(app_parent_path)->asScopedLock();
|
||||
|
||||
lock.lock();
|
||||
auto filename = file::getLastPathSegment(path);
|
||||
const std::string app_target_path = std::format("{}/{}", app_parent_path, filename);
|
||||
if (file::isDirectory(app_target_path) && !file::deleteRecursively(app_target_path)) {
|
||||
TT_LOG_W(TAG, "Failed to delete %s", app_target_path.c_str());
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
lock.lock();
|
||||
if (!file::findOrCreateDirectory(app_target_path, 0777)) {
|
||||
TT_LOG_I(TAG, "Failed to create directory %s", app_target_path.c_str());
|
||||
return false;
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
lock.lock();
|
||||
TT_LOG_I(TAG, "Extracting app from %s to %s", path.c_str(), app_target_path.c_str());
|
||||
if (!untar(path, app_target_path)) {
|
||||
TT_LOG_E(TAG, "Failed to extract");
|
||||
return false;
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
lock.lock();
|
||||
auto manifest_path = app_target_path + "/manifest.properties";
|
||||
if (!file::isFile(manifest_path)) {
|
||||
TT_LOG_E(TAG, "Manifest not found at %s", manifest_path.c_str());
|
||||
return false;
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
lock.lock();
|
||||
std::map<std::string, std::string> properties;
|
||||
if (!file::loadPropertiesFile(manifest_path, properties)) {
|
||||
TT_LOG_E(TAG, "Failed to load manifest at %s", manifest_path.c_str());
|
||||
return false;
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
auto app_id_iterator = properties.find("[app]id");
|
||||
if (app_id_iterator == properties.end()) {
|
||||
TT_LOG_E(TAG, "Failed to find app id in manifest");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto app_name_entry = properties.find("[app]name");
|
||||
if (app_name_entry == properties.end()) {
|
||||
TT_LOG_E(TAG, "Failed to find app name in manifest");
|
||||
return false;
|
||||
}
|
||||
|
||||
lock.lock();
|
||||
const std::string renamed_target_path = std::format("{}/{}", app_parent_path, app_id_iterator->second);
|
||||
if (file::isDirectory(renamed_target_path)) {
|
||||
if (!file::deleteRecursively(renamed_target_path)) {
|
||||
TT_LOG_W(TAG, "Failed to delete existing installation at %s", renamed_target_path.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
lock.lock();
|
||||
if (rename(app_target_path.c_str(), renamed_target_path.c_str()) != 0) {
|
||||
TT_LOG_E(TAG, "Failed to rename %s to %s", app_target_path.c_str(), app_id_iterator->second.c_str());
|
||||
return false;
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
addApp({
|
||||
.id = app_id_iterator->second,
|
||||
.name = app_name_entry->second,
|
||||
.type = Type::User,
|
||||
.location = Location::external(renamed_target_path)
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -20,12 +20,12 @@ void addApp(const AppManifest& manifest) {
|
||||
|
||||
hash_mutex.lock();
|
||||
|
||||
if (!app_manifest_map.contains(manifest.id)) {
|
||||
app_manifest_map[manifest.id] = std::make_shared<AppManifest>(manifest);
|
||||
} else {
|
||||
TT_LOG_E(TAG, "App id in use: %s", manifest.id.c_str());
|
||||
if (app_manifest_map.contains(manifest.id)) {
|
||||
TT_LOG_W(TAG, "Overwriting existing manifest for %s", manifest.id.c_str());
|
||||
}
|
||||
|
||||
app_manifest_map[manifest.id] = std::make_shared<AppManifest>(manifest);
|
||||
|
||||
hash_mutex.unlock();
|
||||
}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ static std::shared_ptr<Lock> elfManifestLock = std::make_shared<Mutex>();
|
||||
|
||||
class ElfApp : public App {
|
||||
|
||||
const std::string filePath;
|
||||
const std::string appPath;
|
||||
std::unique_ptr<uint8_t[]> elfFileData;
|
||||
esp_elf_t elf {
|
||||
.psegment = nullptr,
|
||||
@@ -54,12 +54,13 @@ class ElfApp : public App {
|
||||
std::string lastError = "";
|
||||
|
||||
bool startElf() {
|
||||
TT_LOG_I(TAG, "Starting ELF %s", filePath.c_str());
|
||||
const std::string elf_path = std::format("{}/elf/{}.elf", appPath, CONFIG_IDF_TARGET);
|
||||
TT_LOG_I(TAG, "Starting ELF %s", elf_path.c_str());
|
||||
assert(elfFileData == nullptr);
|
||||
|
||||
size_t size = 0;
|
||||
file::withLock<void>(filePath, [this, &size]{
|
||||
elfFileData = file::readBinary(filePath, size);
|
||||
file::withLock<void>(elf_path, [this, &elf_path, &size]{
|
||||
elfFileData = file::readBinary(elf_path, size);
|
||||
});
|
||||
|
||||
if (elfFileData == nullptr) {
|
||||
@@ -111,7 +112,7 @@ class ElfApp : public App {
|
||||
|
||||
public:
|
||||
|
||||
explicit ElfApp(std::string filePath) : filePath(std::move(filePath)) {}
|
||||
explicit ElfApp(std::string appPath) : appPath(std::move(appPath)) {}
|
||||
|
||||
void onCreate(AppContext& appContext) override {
|
||||
// Because we use global variables, we have to ensure that we are not starting 2 apps in parallel
|
||||
@@ -205,22 +206,6 @@ void setElfAppManifest(
|
||||
elfManifestSetCount++;
|
||||
}
|
||||
|
||||
std::string getElfAppId(const std::string& filePath) {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
void registerElfApp(const std::string& filePath) {
|
||||
if (findAppById(filePath) == nullptr) {
|
||||
auto manifest = AppManifest {
|
||||
.id = getElfAppId(filePath),
|
||||
.name = string::removeFileExtension(string::getLastPathSegment(filePath)),
|
||||
.type = Type::User,
|
||||
.location = Location::external(filePath)
|
||||
};
|
||||
addApp(manifest);
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<App> createElfApp(const std::shared_ptr<AppManifest>& manifest) {
|
||||
TT_LOG_I(TAG, "createElfApp");
|
||||
assert(manifest != nullptr);
|
||||
|
||||
@@ -74,6 +74,7 @@ public:
|
||||
|
||||
void onShow(AppContext& app, lv_obj_t* parent) final {
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
|
||||
lvgl::toolbar_create(parent, app);
|
||||
|
||||
|
||||
@@ -22,21 +22,21 @@ namespace tt::app::alertdialog {
|
||||
|
||||
extern const AppManifest manifest;
|
||||
|
||||
void start(const std::string& title, const std::string& message, const std::vector<std::string>& buttonLabels) {
|
||||
LaunchId start(const std::string& title, const std::string& message, const std::vector<std::string>& buttonLabels) {
|
||||
std::string items_joined = string::join(buttonLabels, PARAMETER_ITEM_CONCATENATION_TOKEN);
|
||||
auto bundle = std::make_shared<Bundle>();
|
||||
bundle->putString(PARAMETER_BUNDLE_KEY_TITLE, title);
|
||||
bundle->putString(PARAMETER_BUNDLE_KEY_MESSAGE, message);
|
||||
bundle->putString(PARAMETER_BUNDLE_KEY_BUTTON_LABELS, items_joined);
|
||||
service::loader::startApp(manifest.id, bundle);
|
||||
return service::loader::startApp(manifest.id, bundle);
|
||||
}
|
||||
|
||||
void start(const std::string& title, const std::string& message) {
|
||||
LaunchId start(const std::string& title, const std::string& message) {
|
||||
auto bundle = std::make_shared<Bundle>();
|
||||
bundle->putString(PARAMETER_BUNDLE_KEY_TITLE, title);
|
||||
bundle->putString(PARAMETER_BUNDLE_KEY_MESSAGE, message);
|
||||
bundle->putString(PARAMETER_BUNDLE_KEY_BUTTON_LABELS, "OK");
|
||||
service::loader::startApp(manifest.id, bundle);
|
||||
return service::loader::startApp(manifest.id, bundle);
|
||||
}
|
||||
|
||||
int32_t getResultIndex(const Bundle& bundle) {
|
||||
|
||||
@@ -39,7 +39,9 @@ class BootApp : public App {
|
||||
|
||||
static void setupDisplay() {
|
||||
const auto hal_display = getHalDisplay();
|
||||
assert(hal_display != nullptr);
|
||||
if (hal_display == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
settings::display::DisplaySettings settings;
|
||||
if (settings::display::load(settings)) {
|
||||
@@ -81,13 +83,27 @@ class BootApp : public App {
|
||||
}
|
||||
|
||||
static int32_t bootThreadCallback() {
|
||||
TT_LOG_I(TAG, "Starting boot thread");
|
||||
const auto start_time = kernel::getTicks();
|
||||
|
||||
kernel::publishSystemEvent(kernel::SystemEvent::BootSplash);
|
||||
// Give the UI some time to redraw
|
||||
// If we don't do this, various init calls will read files and block SPI IO for the display
|
||||
// This would result in a blank/black screen being shown during this phase of the boot process
|
||||
// This works with 5 ms on a T-Lora Pager, so we give it 10 ms to be safe
|
||||
TT_LOG_I(TAG, "Delay");
|
||||
kernel::delayMillis(10);
|
||||
|
||||
// TODO: Support for multiple displays
|
||||
TT_LOG_I(TAG, "Setup display");
|
||||
setupDisplay(); // Set backlight
|
||||
|
||||
// This event will likely block as other systems are initialized
|
||||
// e.g. Wi-Fi reads AP configs from SD card
|
||||
TT_LOG_I(TAG, "Publish event");
|
||||
kernel::publishSystemEvent(kernel::SystemEvent::BootSplash);
|
||||
|
||||
if (!setupUsbBootMode()) {
|
||||
TT_LOG_I(TAG, "initFromBootApp");
|
||||
initFromBootApp();
|
||||
waitForMinimalSplashDuration(start_time);
|
||||
service::loader::stopApp();
|
||||
@@ -117,6 +133,17 @@ class BootApp : public App {
|
||||
|
||||
public:
|
||||
|
||||
void onCreate(AppContext& app) override {
|
||||
// Just in case this app is somehow resumed
|
||||
if (thread.getState() == Thread::State::Stopped) {
|
||||
thread.start();
|
||||
}
|
||||
}
|
||||
|
||||
void onDestroy(AppContext& app) override {
|
||||
thread.join();
|
||||
}
|
||||
|
||||
void onShow(TT_UNUSED AppContext& app, lv_obj_t* parent) override {
|
||||
auto* image = lv_image_create(parent);
|
||||
lv_obj_set_size(image, LV_PCT(100), LV_PCT(100));
|
||||
@@ -128,15 +155,6 @@ public:
|
||||
lv_image_set_src(image, logo_path.c_str());
|
||||
|
||||
lvgl::obj_set_style_bg_blacken(parent);
|
||||
|
||||
// Just in case this app is somehow resumed
|
||||
if (thread.getState() == Thread::State::Stopped) {
|
||||
thread.start();
|
||||
}
|
||||
}
|
||||
|
||||
void onDestroy(AppContext& app) override {
|
||||
thread.join();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -166,8 +166,9 @@ class CalculatorApp : public App {
|
||||
void onShow(AppContext& context, lv_obj_t* parent) override {
|
||||
lv_obj_remove_flag(parent, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
|
||||
lv_obj_t* toolbar = tt::lvgl::toolbar_create(parent, context);
|
||||
lv_obj_t* toolbar = lvgl::toolbar_create(parent, context);
|
||||
lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);
|
||||
|
||||
lv_obj_t* wrapper = lv_obj_create(parent);
|
||||
@@ -208,10 +209,9 @@ class CalculatorApp : public App {
|
||||
lv_obj_set_style_pad_all(btnm, 5, LV_PART_MAIN);
|
||||
lv_obj_set_style_pad_row(btnm, 10, LV_PART_MAIN);
|
||||
lv_obj_set_style_pad_column(btnm, 5, LV_PART_MAIN);
|
||||
lv_obj_set_style_border_width(btnm, 0, LV_PART_MAIN);
|
||||
lv_obj_set_style_border_width(btnm, 2, LV_PART_MAIN);
|
||||
lv_obj_set_style_bg_color(btnm, lv_palette_main(LV_PALETTE_BLUE), LV_PART_ITEMS);
|
||||
|
||||
lv_obj_set_style_border_width(btnm, 0, LV_PART_MAIN);
|
||||
if (lv_display_get_horizontal_resolution(nullptr) <= 240 || lv_display_get_vertical_resolution(nullptr) <= 240) { //small screens
|
||||
lv_obj_set_size(btnm, lv_pct(100), lv_pct(60));
|
||||
} else { //large screens
|
||||
|
||||
@@ -33,28 +33,15 @@ class ChatApp : public App {
|
||||
lv_obj_scroll_to_y(msg_list, lv_obj_get_scroll_y(msg_list) + 20, LV_ANIM_ON);
|
||||
}
|
||||
|
||||
static void onQuickSendClicked(lv_event_t* e) {
|
||||
auto* self = static_cast<ChatApp*>(lv_event_get_user_data(e));
|
||||
auto* btn = static_cast<lv_obj_t*>(lv_event_get_target(e));
|
||||
const char* message = lv_label_get_text(lv_obj_get_child(btn, 0));
|
||||
|
||||
if (message) {
|
||||
self->addMessage(message);
|
||||
if (!service::espnow::send(BROADCAST_ADDRESS, (const uint8_t*)message, strlen(message))) {
|
||||
TT_LOG_E(TAG, "Failed to send message");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void onSendClicked(lv_event_t* e) {
|
||||
auto* self = static_cast<ChatApp*>(lv_event_get_user_data(e));
|
||||
auto* msg = lv_textarea_get_text(self->input_field);
|
||||
auto msg_len = strlen(msg);
|
||||
const auto msg_len = strlen(msg);
|
||||
|
||||
if (self->msg_list && msg && msg_len) {
|
||||
self->addMessage(msg);
|
||||
|
||||
if (!service::espnow::send(BROADCAST_ADDRESS, (const uint8_t*)msg, msg_len)) {
|
||||
if (!service::espnow::send(BROADCAST_ADDRESS, reinterpret_cast<const uint8_t*>(msg), msg_len)) {
|
||||
TT_LOG_E(TAG, "Failed to send message");
|
||||
}
|
||||
|
||||
@@ -64,14 +51,14 @@ class ChatApp : public App {
|
||||
|
||||
void onReceive(const esp_now_recv_info_t* receiveInfo, const uint8_t* data, int length) {
|
||||
// Append \0 to make it a string
|
||||
char* buffer = (char*)malloc(length + 1);
|
||||
auto buffer = static_cast<char*>(malloc(length + 1));
|
||||
memcpy(buffer, data, length);
|
||||
buffer[length] = 0x00;
|
||||
std::string message_prefixed = std::string("Received: ") + buffer;
|
||||
const std::string message_prefixed = std::string("Received: ") + buffer;
|
||||
|
||||
tt::lvgl::getSyncLock()->lock();
|
||||
lvgl::getSyncLock()->lock();
|
||||
addMessage(message_prefixed.c_str());
|
||||
tt::lvgl::getSyncLock()->unlock();
|
||||
lvgl::getSyncLock()->unlock();
|
||||
|
||||
free(buffer);
|
||||
}
|
||||
@@ -82,7 +69,7 @@ public:
|
||||
// TODO: Move this to a configuration screen/app
|
||||
static const uint8_t key[ESP_NOW_KEY_LEN] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
auto config = service::espnow::EspNowConfig(
|
||||
(uint8_t*)key,
|
||||
const_cast<uint8_t*>(key),
|
||||
service::espnow::Mode::Station,
|
||||
1,
|
||||
false,
|
||||
@@ -105,58 +92,39 @@ public:
|
||||
}
|
||||
|
||||
void onShow(AppContext& context, lv_obj_t* parent) override {
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
|
||||
// Create toolbar
|
||||
auto* toolbar = tt::lvgl::toolbar_create(parent, context);
|
||||
lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);
|
||||
const int toolbar_height = lv_obj_get_height(toolbar);
|
||||
lvgl::toolbar_create(parent, context);
|
||||
|
||||
// Message list
|
||||
msg_list = lv_list_create(parent);
|
||||
lv_obj_set_size(msg_list, lv_pct(75), lv_pct(43));
|
||||
lv_obj_align(msg_list, LV_ALIGN_TOP_LEFT, 5, toolbar_height + 45);
|
||||
lv_obj_set_flex_grow(msg_list, 1);
|
||||
lv_obj_set_width(msg_list, LV_PCT(100));
|
||||
lv_obj_set_flex_grow(msg_list, 1);
|
||||
lv_obj_set_style_bg_color(msg_list, lv_color_hex(0x262626), 0);
|
||||
lv_obj_set_style_border_width(msg_list, 1, 0);
|
||||
lv_obj_set_style_pad_all(msg_list, 5, 0);
|
||||
|
||||
// Quick message panel
|
||||
auto* quick_panel = lv_obj_create(parent);
|
||||
lv_obj_set_size(quick_panel, lv_pct(20), lv_pct(58));
|
||||
lv_obj_align(quick_panel, LV_ALIGN_TOP_RIGHT, -5, toolbar_height + 15); // Adjusted to match
|
||||
lv_obj_set_flex_flow(quick_panel, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_flex_align(quick_panel, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_set_style_pad_all(quick_panel, 5, 0);
|
||||
|
||||
// Quick message buttons
|
||||
const char* quick_msgs[] = {":-)", "+1", ":-("};
|
||||
for (size_t i = 0; i < sizeof(quick_msgs)/sizeof(quick_msgs[0]); i++) {
|
||||
lv_obj_t* btn = lv_btn_create(quick_panel);
|
||||
lv_obj_set_size(btn, lv_pct(75), 25);
|
||||
lv_obj_add_event_cb(btn, onQuickSendClicked, LV_EVENT_CLICKED, this);
|
||||
|
||||
lv_obj_t* label = lv_label_create(btn);
|
||||
lv_label_set_text(label, quick_msgs[i]);
|
||||
lv_obj_center(label);
|
||||
}
|
||||
lv_obj_set_style_pad_ver(msg_list, 0, 0);
|
||||
lv_obj_set_style_pad_hor(msg_list, 4, 0);
|
||||
|
||||
// Input panel
|
||||
auto* input_panel = lv_obj_create(parent);
|
||||
lv_obj_set_size(input_panel, lv_pct(95), 60);
|
||||
lv_obj_align(input_panel, LV_ALIGN_BOTTOM_MID, 0, -5);
|
||||
lv_obj_set_flex_flow(input_panel, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_flex_align(input_panel, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
lv_obj_set_style_pad_all(input_panel, 5, 0);
|
||||
auto* bottom_wrapper = lv_obj_create(parent);
|
||||
lv_obj_set_flex_flow(bottom_wrapper, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_size(bottom_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
|
||||
lv_obj_set_style_pad_all(bottom_wrapper, 0, 0);
|
||||
lv_obj_set_style_pad_column(bottom_wrapper, 4, 0);
|
||||
lv_obj_set_style_border_opa(bottom_wrapper, 0, LV_STATE_DEFAULT);
|
||||
|
||||
// Input field
|
||||
input_field = lv_textarea_create(input_panel);
|
||||
input_field = lv_textarea_create(bottom_wrapper);
|
||||
lv_obj_set_flex_grow(input_field, 1);
|
||||
lv_obj_set_height(input_field, LV_PCT(100));
|
||||
lv_textarea_set_placeholder_text(input_field, "Type a message...");
|
||||
lv_textarea_set_one_line(input_field, true);
|
||||
|
||||
// Send button
|
||||
auto* send_btn = lv_btn_create(input_panel);
|
||||
lv_obj_set_size(send_btn, 80, LV_PCT(100));
|
||||
auto* send_btn = lv_button_create(bottom_wrapper);
|
||||
lv_obj_set_style_margin_all(send_btn, 0, LV_STATE_DEFAULT);
|
||||
lv_obj_set_style_margin_top(send_btn, 2, LV_STATE_DEFAULT); // Hack to fix alignment
|
||||
lv_obj_add_event_cb(send_btn, onSendClicked, LV_EVENT_CLICKED, this);
|
||||
|
||||
auto* btn_label = lv_label_create(send_btn);
|
||||
|
||||
@@ -91,6 +91,7 @@ public:
|
||||
|
||||
void onShow(AppContext& app, lv_obj_t* parent) override {
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
|
||||
// Toolbar
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
|
||||
@@ -67,6 +67,7 @@ public:
|
||||
displaySettings = settings::display::loadOrGetDefault();
|
||||
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
|
||||
auto hal_display = getHalDisplay();
|
||||
assert(hal_display != nullptr);
|
||||
|
||||
@@ -28,7 +28,7 @@ public:
|
||||
}
|
||||
|
||||
void onResult(AppContext& appContext, TT_UNUSED LaunchId launchId, Result result, std::unique_ptr<Bundle> bundle) override {
|
||||
view->onResult(result, std::move(bundle));
|
||||
view->onResult(launchId, result, std::move(bundle));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -5,13 +5,8 @@ namespace tt::app::filebrowser {
|
||||
|
||||
constexpr auto* TAG = "FileBrowser";
|
||||
|
||||
bool isSupportedExecutableFile(const std::string& filename) {
|
||||
#ifdef ESP_PLATFORM
|
||||
// Currently only the PNG library is built into Tactility
|
||||
return filename.ends_with(".elf");
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
bool isSupportedAppFile(const std::string& filename) {
|
||||
return filename.ends_with(".app");
|
||||
}
|
||||
|
||||
bool isSupportedImageFile(const std::string& filename) {
|
||||
|
||||
@@ -86,11 +86,12 @@ void View::viewFile(const std::string& path, const std::string& filename) {
|
||||
|
||||
TT_LOG_I(TAG, "Clicked %s", file_path.c_str());
|
||||
|
||||
if (isSupportedExecutableFile(filename)) {
|
||||
if (isSupportedAppFile(filename)) {
|
||||
#ifdef ESP_PLATFORM
|
||||
registerElfApp(processed_filepath);
|
||||
auto app_id = getElfAppId(processed_filepath);
|
||||
service::loader::startApp(app_id);
|
||||
// install(filename);
|
||||
auto message = std::format("Do you want to install {}?", filename);
|
||||
installAppPath = processed_filepath;
|
||||
installAppLaunchId = alertdialog::start("Install?", message, { "Yes", "No" });
|
||||
#endif
|
||||
} else if (isSupportedImageFile(filename)) {
|
||||
imageviewer::start(processed_filepath);
|
||||
@@ -253,6 +254,7 @@ void View::update() {
|
||||
|
||||
void View::init(lv_obj_t* parent) {
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
|
||||
auto* toolbar = lvgl::toolbar_create(parent, "Files");
|
||||
navigate_up_button = lvgl::toolbar_add_button_action(toolbar, LV_SYMBOL_UP, &onNavigateUpPressedCallback, this);
|
||||
@@ -292,11 +294,20 @@ void View::onNavigate() {
|
||||
}
|
||||
}
|
||||
|
||||
void View::onResult(Result result, std::unique_ptr<Bundle> bundle) {
|
||||
void View::onResult(LaunchId launchId, Result result, std::unique_ptr<Bundle> bundle) {
|
||||
if (result != Result::Ok || bundle == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
launchId == installAppLaunchId &&
|
||||
result == Result::Ok &&
|
||||
alertdialog::getResultIndex(*bundle) == 0
|
||||
) {
|
||||
install(installAppPath);
|
||||
return;
|
||||
}
|
||||
|
||||
std::string filepath = state->getSelectedChildPath();
|
||||
TT_LOG_I(TAG, "Result for %s", filepath.c_str());
|
||||
|
||||
|
||||
@@ -175,6 +175,7 @@ void View::update() {
|
||||
|
||||
void View::init(lv_obj_t* parent, Mode mode) {
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
|
||||
auto* toolbar = lvgl::toolbar_create(parent, "Select File");
|
||||
navigate_up_button = lvgl::toolbar_add_button_action(toolbar, LV_SYMBOL_UP, &onNavigateUpPressedCallback, this);
|
||||
|
||||
@@ -104,6 +104,7 @@ void GpioApp::stopTask() {
|
||||
|
||||
void GpioApp::onShow(AppContext& app, lv_obj_t* parent) {
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
auto* toolbar = lvgl::toolbar_create(parent, app);
|
||||
lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);
|
||||
|
||||
|
||||
@@ -276,6 +276,7 @@ public:
|
||||
|
||||
void onShow(AppContext& app, lv_obj_t* parent) final {
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
|
||||
auto* toolbar = lvgl::toolbar_create(parent, app);
|
||||
|
||||
|
||||
@@ -95,6 +95,7 @@ int32_t I2cScannerApp::getLastBusIndex() {
|
||||
|
||||
void I2cScannerApp::onShow(AppContext& app, lv_obj_t* parent) {
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
|
||||
lvgl::toolbar_create(parent, app);
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ class I2cSettingsApp : public App {
|
||||
|
||||
void onShow(AppContext& app, lv_obj_t* parent) override {
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
lvgl::toolbar_create(parent, app);
|
||||
|
||||
auto* wrapper = lv_obj_create(parent);
|
||||
|
||||
@@ -85,6 +85,7 @@ public:
|
||||
textResources.load();
|
||||
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
|
||||
lvgl::toolbar_create(parent, app);
|
||||
|
||||
|
||||
@@ -74,6 +74,8 @@ public:
|
||||
|
||||
void onShow(AppContext& app, lv_obj_t* parent) override {
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
|
||||
auto* toolbar = lvgl::toolbar_create(parent, app);
|
||||
lvgl::toolbar_add_button_action(toolbar, LV_SYMBOL_EDIT, onLevelFilterPressedCallback, this);
|
||||
|
||||
|
||||
@@ -123,6 +123,7 @@ class NotesApp : public App {
|
||||
void onShow(AppContext& context, lv_obj_t* parent) override {
|
||||
lv_obj_remove_flag(parent, LV_OBJ_FLAG_SCROLLABLE);
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
|
||||
lv_obj_t* toolbar = lvgl::toolbar_create(parent, context);
|
||||
lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);
|
||||
|
||||
@@ -144,6 +144,7 @@ public:
|
||||
|
||||
void onShow(AppContext& app, lv_obj_t* parent) override {
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
|
||||
lvgl::toolbar_create(parent, app);
|
||||
|
||||
|
||||
@@ -255,6 +255,8 @@ void ScreenshotApp::onShow(AppContext& appContext, lv_obj_t* parent) {
|
||||
}
|
||||
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
|
||||
auto* toolbar = lvgl::toolbar_create(parent, appContext);
|
||||
lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);
|
||||
|
||||
|
||||
@@ -73,6 +73,8 @@ public:
|
||||
|
||||
void onShow(AppContext& app, lv_obj_t* parent) override {
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
|
||||
std::string title = getTitleParameter(app.getParameters());
|
||||
lvgl::toolbar_create(parent, title);
|
||||
|
||||
|
||||
@@ -61,6 +61,8 @@ public:
|
||||
|
||||
void onShow(AppContext& app, lv_obj_t* parent) final {
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
|
||||
auto* toolbar = lvgl::toolbar_create(parent, app);
|
||||
|
||||
disconnectButton = lvgl::toolbar_add_button_action(toolbar, LV_SYMBOL_POWER, onDisconnectPressed, this);
|
||||
|
||||
@@ -27,6 +27,7 @@ class SettingsApp : public App {
|
||||
|
||||
void onShow(AppContext& app, lv_obj_t* parent) override {
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
|
||||
lvgl::toolbar_create(parent, app);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <Tactility/hal/Device.h>
|
||||
#include <Tactility/Tactility.h>
|
||||
|
||||
#include <format>
|
||||
#include <lvgl.h>
|
||||
#include <utility>
|
||||
|
||||
@@ -84,17 +85,17 @@ static std::string getStorageUnitString(StorageUnit unit) {
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t getStorageValue(StorageUnit unit, uint64_t bytes) {
|
||||
static std::string getStorageValue(StorageUnit unit, uint64_t bytes) {
|
||||
using enum StorageUnit;
|
||||
switch (unit) {
|
||||
case Bytes:
|
||||
return bytes;
|
||||
return std::to_string(bytes);
|
||||
case Kilobytes:
|
||||
return bytes / 1024;
|
||||
return std::to_string(bytes / 1024);
|
||||
case Megabytes:
|
||||
return bytes / 1024 / 1024;
|
||||
return std::format("{:.1f}", static_cast<float>(bytes) / 1024.f / 1024.f);
|
||||
case Gigabytes:
|
||||
return bytes / 1024 / 1024 / 1024;
|
||||
return std::format("{:.1f}", static_cast<float>(bytes) / 1024.f / 1024.f / 1024.f);
|
||||
default:
|
||||
std::unreachable();
|
||||
}
|
||||
@@ -107,6 +108,7 @@ static void addMemoryBar(lv_obj_t* parent, const char* label, uint64_t free, uin
|
||||
lv_obj_set_style_pad_all(container, 0, 0);
|
||||
lv_obj_set_style_border_width(container, 0, 0);
|
||||
lv_obj_set_flex_flow(container, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_style_bg_opa(container, 0, LV_STATE_DEFAULT);
|
||||
|
||||
auto* left_label = lv_label_create(container);
|
||||
lv_label_set_text(left_label, label);
|
||||
@@ -115,22 +117,31 @@ static void addMemoryBar(lv_obj_t* parent, const char* label, uint64_t free, uin
|
||||
auto* bar = lv_bar_create(container);
|
||||
lv_obj_set_flex_grow(bar, 1);
|
||||
|
||||
// Scale down the uint64_t until it fits int32_t for the lv_bar
|
||||
uint64_t free_scaled = free;
|
||||
uint64_t total_scaled = total;
|
||||
while (total_scaled > static_cast<uint64_t>(INT32_MAX)) {
|
||||
free_scaled /= 1024;
|
||||
total_scaled /= 1024;
|
||||
}
|
||||
|
||||
if (total > 0) {
|
||||
lv_bar_set_range(bar, 0, (int32_t)total);
|
||||
lv_bar_set_range(bar, 0, total_scaled);
|
||||
} else {
|
||||
lv_bar_set_range(bar, 0, 1);
|
||||
}
|
||||
|
||||
lv_bar_set_value(bar, (int32_t)used, LV_ANIM_OFF);
|
||||
lv_bar_set_value(bar, (total_scaled - free_scaled), LV_ANIM_OFF);
|
||||
|
||||
auto* bottom_label = lv_label_create(parent);
|
||||
const auto unit = getStorageUnit(total);
|
||||
const auto unit_label = getStorageUnitString(unit);
|
||||
const auto used_converted = getStorageValue(unit, used);
|
||||
const auto total_converted = getStorageValue(unit, total);
|
||||
lv_label_set_text_fmt(bottom_label, "%llu / %llu %s", used_converted, total_converted, unit_label.c_str());
|
||||
lv_label_set_text_fmt(bottom_label, "%s / %s %s used", used_converted.c_str(), total_converted.c_str(), unit_label.c_str());
|
||||
lv_obj_set_width(bottom_label, LV_PCT(100));
|
||||
lv_obj_set_style_text_align(bottom_label, LV_TEXT_ALIGN_RIGHT, 0);
|
||||
lv_obj_set_style_pad_bottom(bottom_label, 12, LV_STATE_DEFAULT);
|
||||
}
|
||||
|
||||
#if configUSE_TRACE_FACILITY
|
||||
@@ -164,6 +175,7 @@ static void addRtosTasks(lv_obj_t* parent) {
|
||||
auto* tasks = (TaskStatus_t*)malloc(sizeof(TaskStatus_t) * count);
|
||||
uint32_t totalRuntime = 0;
|
||||
UBaseType_t actual = uxTaskGetSystemState(tasks, count, &totalRuntime);
|
||||
|
||||
for (int i = 0; i < actual; ++i) {
|
||||
const TaskStatus_t& task = tasks[i];
|
||||
addRtosTask(parent, task);
|
||||
@@ -189,6 +201,7 @@ class SystemInfoApp : public App {
|
||||
|
||||
void onShow(AppContext& app, lv_obj_t* parent) override {
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
lvgl::toolbar_create(parent, app);
|
||||
|
||||
// This wrapper automatically has its children added vertically underneath eachother
|
||||
@@ -197,43 +210,54 @@ class SystemInfoApp : public App {
|
||||
lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_width(wrapper, LV_PCT(100));
|
||||
lv_obj_set_flex_grow(wrapper, 1);
|
||||
lv_obj_set_style_pad_all(wrapper, 0, LV_STATE_DEFAULT);
|
||||
|
||||
// Wrapper for the memory usage bars
|
||||
auto* memory_label = lv_label_create(wrapper);
|
||||
lv_label_set_text(memory_label, "Memory usage");
|
||||
auto* memory_wrapper = lv_obj_create(wrapper);
|
||||
lv_obj_set_flex_flow(memory_wrapper, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_size(memory_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
|
||||
auto* tabview = lv_tabview_create(wrapper);
|
||||
lv_tabview_set_tab_bar_position(tabview, LV_DIR_LEFT);
|
||||
lv_tabview_set_tab_bar_size(tabview, 80);
|
||||
|
||||
addMemoryBar(memory_wrapper, "Internal", getHeapFree(), getHeapTotal());
|
||||
// Tabs
|
||||
|
||||
auto* memory_tab = lv_tabview_add_tab(tabview, "Memory");
|
||||
lv_obj_set_flex_flow(memory_tab, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(memory_tab, 0, LV_STATE_DEFAULT);
|
||||
auto* storage_tab = lv_tabview_add_tab(tabview, "Storage");
|
||||
lv_obj_set_flex_flow(storage_tab, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(storage_tab, 0, LV_STATE_DEFAULT);
|
||||
auto* tasks_tab = lv_tabview_add_tab(tabview, "Tasks");
|
||||
lv_obj_set_flex_flow(tasks_tab, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(tasks_tab, 4, LV_STATE_DEFAULT);
|
||||
auto* devices_tab = lv_tabview_add_tab(tabview, "Devices");
|
||||
lv_obj_set_flex_flow(devices_tab, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(devices_tab, 4, LV_STATE_DEFAULT);
|
||||
auto* about_tab = lv_tabview_add_tab(tabview, "About");
|
||||
lv_obj_set_flex_flow(about_tab, LV_FLEX_FLOW_COLUMN);
|
||||
|
||||
// Memory tab content
|
||||
|
||||
addMemoryBar(memory_tab, "Internal", getHeapFree(), getHeapTotal());
|
||||
if (getSpiTotal() > 0) {
|
||||
addMemoryBar(memory_wrapper, "External", getSpiFree(), getSpiTotal());
|
||||
addMemoryBar(memory_tab, "External", getSpiFree(), getSpiTotal());
|
||||
}
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
// Wrapper for the memory usage bars
|
||||
auto* storage_label = lv_label_create(wrapper);
|
||||
lv_label_set_text(storage_label, "Storage usage");
|
||||
auto* storage_wrapper = lv_obj_create(wrapper);
|
||||
lv_obj_set_flex_flow(storage_wrapper, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_size(storage_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
|
||||
|
||||
uint64_t storage_total = 0;
|
||||
uint64_t storage_free = 0;
|
||||
|
||||
if (esp_vfs_fat_info(file::MOUNT_POINT_SYSTEM, &storage_total, &storage_free) == ESP_OK) {
|
||||
addMemoryBar(storage_wrapper, file::MOUNT_POINT_SYSTEM, storage_free, storage_total);
|
||||
addMemoryBar(storage_tab, file::MOUNT_POINT_SYSTEM, storage_free, storage_total);
|
||||
}
|
||||
|
||||
if (esp_vfs_fat_info(file::MOUNT_POINT_DATA, &storage_total, &storage_free) == ESP_OK) {
|
||||
addMemoryBar(storage_wrapper, file::MOUNT_POINT_DATA, storage_free, storage_total);
|
||||
addMemoryBar(storage_tab, file::MOUNT_POINT_DATA, storage_free, storage_total);
|
||||
}
|
||||
|
||||
const auto sdcard_devices = hal::findDevices<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard);
|
||||
for (const auto& sdcard : sdcard_devices) {
|
||||
if (sdcard->isMounted() && esp_vfs_fat_info(sdcard->getMountPath().c_str(), &storage_total, &storage_free) == ESP_OK) {
|
||||
addMemoryBar(
|
||||
storage_wrapper,
|
||||
storage_tab,
|
||||
sdcard->getMountPath().c_str(),
|
||||
storage_free,
|
||||
storage_total
|
||||
@@ -243,30 +267,17 @@ class SystemInfoApp : public App {
|
||||
#endif
|
||||
|
||||
#if configUSE_TRACE_FACILITY
|
||||
auto* tasks_label = lv_label_create(wrapper);
|
||||
lv_label_set_text(tasks_label, "Tasks");
|
||||
auto* tasks_wrapper = lv_obj_create(wrapper);
|
||||
lv_obj_set_flex_flow(tasks_wrapper, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_size(tasks_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
|
||||
addRtosTasks(tasks_wrapper);
|
||||
addRtosTasks(tasks_tab);
|
||||
#endif
|
||||
auto* devices_label = lv_label_create(wrapper);
|
||||
lv_label_set_text(devices_label, "Devices");
|
||||
auto* devices_wrapper = lv_obj_create(wrapper);
|
||||
lv_obj_set_flex_flow(devices_wrapper, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_size(devices_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
|
||||
addDevices(devices_wrapper);
|
||||
|
||||
#ifdef ESP_PLATFORM
|
||||
addDevices(devices_tab);
|
||||
|
||||
// Build info
|
||||
auto* build_info_label = lv_label_create(wrapper);
|
||||
lv_label_set_text(build_info_label, "Build info");
|
||||
auto* build_info_wrapper = lv_obj_create(wrapper);
|
||||
lv_obj_set_flex_flow(build_info_wrapper, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_size(build_info_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
|
||||
|
||||
auto* esp_idf_version = lv_label_create(build_info_wrapper);
|
||||
lv_label_set_text_fmt(esp_idf_version, "IDF version: %d.%d.%d", ESP_IDF_VERSION_MAJOR, ESP_IDF_VERSION_MINOR, ESP_IDF_VERSION_PATCH);
|
||||
auto* tactility_version = lv_label_create(about_tab);
|
||||
lv_label_set_text_fmt(tactility_version, "Tactility v%s", TT_VERSION);
|
||||
#ifdef ESP_PLATFORM
|
||||
auto* esp_idf_version = lv_label_create(about_tab);
|
||||
lv_label_set_text_fmt(esp_idf_version, "ESP-IDF v%d.%d.%d", ESP_IDF_VERSION_MAJOR, ESP_IDF_VERSION_MINOR, ESP_IDF_VERSION_PATCH);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
@@ -26,6 +26,7 @@ public:
|
||||
|
||||
void onShow(AppContext& app, lv_obj_t* parent) override {
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
|
||||
lvgl::toolbar_create(parent, app);
|
||||
|
||||
|
||||
@@ -193,6 +193,8 @@ public:
|
||||
|
||||
void onShow(AppContext& app, lv_obj_t* parent) override {
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
|
||||
lvgl::toolbar_create(parent, app);
|
||||
|
||||
auto* search_wrapper = lv_obj_create(parent);
|
||||
|
||||
@@ -64,6 +64,8 @@ class WifiApSettings : public App {
|
||||
std::string ssid = paremeters->getString("ssid");
|
||||
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
|
||||
lvgl::toolbar_create(parent, ssid);
|
||||
|
||||
// Wrappers
|
||||
|
||||
@@ -109,8 +109,9 @@ void View::createBottomButtons(lv_obj_t* parent) {
|
||||
|
||||
// TODO: Standardize dialogs
|
||||
void View::init(AppContext& app, lv_obj_t* parent) {
|
||||
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
|
||||
lvgl::toolbar_create(parent, app);
|
||||
|
||||
auto* wrapper = lv_obj_create(parent);
|
||||
|
||||
@@ -272,12 +272,15 @@ void View::updateEnableOnBootToggle() {
|
||||
// region Main
|
||||
|
||||
void View::init(const AppContext& app, lv_obj_t* parent) {
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_style_pad_row(parent, 0, LV_STATE_DEFAULT);
|
||||
|
||||
root = parent;
|
||||
|
||||
paths = app.getPaths();
|
||||
|
||||
// Toolbar
|
||||
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
|
||||
|
||||
lv_obj_t* toolbar = lvgl::toolbar_create(parent, app);
|
||||
|
||||
scanning_spinner = lvgl::toolbar_add_spinner_action(toolbar);
|
||||
|
||||
@@ -22,17 +22,22 @@ bool loadPropertiesFile(const std::string& filePath, std::function<void(const st
|
||||
return file::withLock<bool>(filePath, [&filePath, &callback] {
|
||||
TT_LOG_I(TAG, "Reading properties file %s", filePath.c_str());
|
||||
uint16_t line_count = 0;
|
||||
return readLines(filePath, true, [&line_count, &filePath, &callback](const std::string& line) {
|
||||
std::string key_prefix = "";
|
||||
return readLines(filePath, true, [&key_prefix, &line_count, &filePath, &callback](const std::string& line) {
|
||||
line_count++;
|
||||
std::string key, value;
|
||||
auto trimmed_line = string::trim(line, " \t");
|
||||
if (!trimmed_line.starts_with("#")) {
|
||||
if (getKeyValuePair(trimmed_line, key, value)) {
|
||||
std::string trimmed_key = string::trim(key, " \t");
|
||||
std::string trimmed_value = string::trim(value, " \t");
|
||||
callback(trimmed_key, trimmed_value);
|
||||
if (trimmed_line.starts_with("[")) {
|
||||
key_prefix = trimmed_line;
|
||||
} else {
|
||||
TT_LOG_E(TAG, "Failed to parse line %d of %s", line_count, filePath.c_str());
|
||||
if (getKeyValuePair(trimmed_line, key, value)) {
|
||||
std::string trimmed_key = key_prefix + string::trim(key, " \t");
|
||||
std::string trimmed_value = string::trim(value, " \t");
|
||||
callback(trimmed_key, trimmed_value);
|
||||
} else {
|
||||
TT_LOG_E(TAG, "Failed to parse line %d of %s", line_count, filePath.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -77,8 +77,15 @@ bool startService(const std::string& id) {
|
||||
instance_mutex.unlock();
|
||||
|
||||
service_instance->setState(State::Starting);
|
||||
service_instance->getService()->onStart(*service_instance);
|
||||
service_instance->setState(State::Started);
|
||||
if (service_instance->getService()->onStart(*service_instance)) {
|
||||
service_instance->setState(State::Started);
|
||||
} else {
|
||||
TT_LOG_E(TAG, "Starting %s failed", id.c_str());
|
||||
service_instance->setState(State::Stopped);
|
||||
instance_mutex.lock();
|
||||
service_instance_map.erase(manifest->id);
|
||||
instance_mutex.unlock();
|
||||
}
|
||||
|
||||
TT_LOG_I(TAG, "Started %s", id.c_str());
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
#include <esp_wifi.h>
|
||||
#include <ranges>
|
||||
#include <sstream>
|
||||
#include <Tactility/Tactility.h>
|
||||
#include <Tactility/file/FileLock.h>
|
||||
|
||||
namespace tt::service::development {
|
||||
|
||||
@@ -25,7 +27,7 @@ extern const ServiceManifest manifest;
|
||||
|
||||
constexpr const char* TAG = "DevService";
|
||||
|
||||
void DevelopmentService::onStart(ServiceContext& service) {
|
||||
bool DevelopmentService::onStart(ServiceContext& service) {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
|
||||
@@ -39,6 +41,8 @@ void DevelopmentService::onStart(ServiceContext& service) {
|
||||
);
|
||||
|
||||
setEnabled(shouldEnableOnBoot());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DevelopmentService::onStop(ServiceContext& service) {
|
||||
@@ -97,6 +101,7 @@ void DevelopmentService::startServer() {
|
||||
deviceResponse = stream.str();
|
||||
|
||||
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||
config.stack_size = 5120;
|
||||
|
||||
config.server_port = 6666;
|
||||
config.uri_match_fn = httpd_uri_match_wildcard;
|
||||
@@ -154,6 +159,8 @@ void DevelopmentService::onNetworkDisconnected() {
|
||||
// region endpoints
|
||||
|
||||
esp_err_t DevelopmentService::handleGetInfo(httpd_req_t* request) {
|
||||
TT_LOG_I(TAG, "GET /device");
|
||||
|
||||
if (httpd_resp_set_type(request, "application/json") != ESP_OK) {
|
||||
TT_LOG_W(TAG, "Failed to send header");
|
||||
return ESP_FAIL;
|
||||
@@ -171,6 +178,8 @@ esp_err_t DevelopmentService::handleGetInfo(httpd_req_t* request) {
|
||||
}
|
||||
|
||||
esp_err_t DevelopmentService::handleAppRun(httpd_req_t* request) {
|
||||
TT_LOG_I(TAG, "POST /app/run");
|
||||
|
||||
std::string query;
|
||||
if (!network::getQueryOrSendError(request, query)) {
|
||||
return ESP_FAIL;
|
||||
@@ -184,28 +193,15 @@ esp_err_t DevelopmentService::handleAppRun(httpd_req_t* request) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
auto app_id = id_key_pos->second;
|
||||
if (app_id.ends_with(".app.elf")) {
|
||||
if (!file::isFile(app_id)) {
|
||||
TT_LOG_W(TAG, "[400] /app/run cannot find app %s", app_id.c_str());
|
||||
httpd_resp_send_err(request, HTTPD_400_BAD_REQUEST, "app not found");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
app::registerElfApp(app_id);
|
||||
app_id = app::getElfAppId(app_id);
|
||||
} else if (!app::findAppById(app_id.c_str())) {
|
||||
TT_LOG_W(TAG, "[400] /app/run app not found");
|
||||
httpd_resp_send_err(request, HTTPD_400_BAD_REQUEST, "app not found");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
app::start(app_id);
|
||||
TT_LOG_I(TAG, "[200] /app/run %s", app_id.c_str());
|
||||
app::start(id_key_pos->second);
|
||||
TT_LOG_I(TAG, "[200] /app/run %s", id_key_pos->second.c_str());
|
||||
httpd_resp_send(request, nullptr, 0);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t DevelopmentService::handleAppInstall(httpd_req_t* request) {
|
||||
TT_LOG_I(TAG, "PUT /app/install");
|
||||
|
||||
std::string boundary;
|
||||
if (!network::getMultiPartBoundaryOrSendError(request, boundary)) {
|
||||
return false;
|
||||
@@ -250,8 +246,19 @@ esp_err_t DevelopmentService::handleAppInstall(httpd_req_t* request) {
|
||||
}
|
||||
content_left -= content_read;
|
||||
|
||||
const std::string tmp_path = app::getTempPath();
|
||||
auto lock = file::getLock(tmp_path)->asScopedLock();
|
||||
|
||||
lock.lock();
|
||||
if (!file::findOrCreateDirectory(tmp_path, 0777)) {
|
||||
httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to save file");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
// Write file
|
||||
auto file_path = std::format("/data/{}", filename_entry->second);
|
||||
lock.lock();
|
||||
auto file_path = std::format("{}/{}", tmp_path, filename_entry->second);
|
||||
auto* file = fopen(file_path.c_str(), "wb");
|
||||
auto file_bytes_written = fwrite(buffer.get(), 1, file_size, file);
|
||||
fclose(file);
|
||||
@@ -259,6 +266,8 @@ esp_err_t DevelopmentService::handleAppInstall(httpd_req_t* request) {
|
||||
httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to save file");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
|
||||
// Read and verify part
|
||||
if (!network::readAndDiscardOrSendError(request, part_after_file)) {
|
||||
@@ -270,9 +279,21 @@ esp_err_t DevelopmentService::handleAppInstall(httpd_req_t* request) {
|
||||
TT_LOG_W(TAG, "We have more bytes at the end of the request parsing?!");
|
||||
}
|
||||
|
||||
if (!app::install(file_path)) {
|
||||
httpd_resp_send_err(request, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to install");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
lock.lock();
|
||||
if (remove(file_path.c_str()) != 0) {
|
||||
TT_LOG_W(TAG, "Failed to delete %s", file_path.c_str());
|
||||
}
|
||||
lock.unlock();
|
||||
|
||||
TT_LOG_I(TAG, "[200] /app/install -> %s", file_path.c_str());
|
||||
|
||||
httpd_resp_send(request, nullptr, 0);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -20,11 +20,13 @@ static uint8_t BROADCAST_MAC[ESP_NOW_ETH_ALEN];
|
||||
|
||||
constexpr bool isBroadcastAddress(uint8_t address[ESP_NOW_ETH_ALEN]) { return memcmp(address, BROADCAST_MAC, ESP_NOW_ETH_ALEN) == 0; }
|
||||
|
||||
void EspNowService::onStart(ServiceContext& service) {
|
||||
bool EspNowService::onStart(ServiceContext& service) {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
|
||||
memset(BROADCAST_MAC, 0xFF, sizeof(BROADCAST_MAC));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void EspNowService::onStop(ServiceContext& service) {
|
||||
|
||||
@@ -12,8 +12,8 @@ namespace tt::service::gps {
|
||||
constexpr const char* TAG = "GpsService";
|
||||
extern const ServiceManifest manifest;
|
||||
|
||||
constexpr inline bool hasTimeElapsed(TickType_t now, TickType_t timeInThePast, TickType_t expireTimeInTicks) {
|
||||
return (TickType_t)(now - timeInThePast) >= expireTimeInTicks;
|
||||
constexpr bool hasTimeElapsed(TickType_t now, TickType_t timeInThePast, TickType_t expireTimeInTicks) {
|
||||
return (now - timeInThePast) >= expireTimeInTicks;
|
||||
}
|
||||
|
||||
GpsService::GpsDeviceRecord* _Nullable GpsService::findGpsRecord(const std::shared_ptr<GpsDevice>& device) {
|
||||
@@ -58,14 +58,14 @@ void GpsService::removeGpsDevice(const std::shared_ptr<GpsDevice>& device) {
|
||||
});
|
||||
}
|
||||
|
||||
void GpsService::onStart(tt::service::ServiceContext& serviceContext) {
|
||||
bool GpsService::onStart(ServiceContext& serviceContext) {
|
||||
auto lock = mutex.asScopedLock();
|
||||
lock.lock();
|
||||
|
||||
paths = serviceContext.getPaths();
|
||||
return true;
|
||||
}
|
||||
|
||||
void GpsService::onStop(tt::service::ServiceContext& serviceContext) {
|
||||
void GpsService::onStop(ServiceContext& serviceContext) {
|
||||
if (getState() == State::On) {
|
||||
stopReceiving();
|
||||
}
|
||||
|
||||
@@ -112,7 +112,13 @@ void GuiService::redraw() {
|
||||
unlock();
|
||||
}
|
||||
|
||||
void GuiService::onStart(TT_UNUSED ServiceContext& service) {
|
||||
bool GuiService::onStart(TT_UNUSED ServiceContext& service) {
|
||||
auto* screen_root = lv_screen_active();
|
||||
if (screen_root == nullptr) {
|
||||
TT_LOG_E(TAG, "No display found");
|
||||
return false;
|
||||
}
|
||||
|
||||
thread = new Thread(
|
||||
"gui",
|
||||
4096, // Last known minimum was 2800 for launching desktop
|
||||
@@ -123,12 +129,9 @@ void GuiService::onStart(TT_UNUSED ServiceContext& service) {
|
||||
onLoaderEvent(event);
|
||||
});
|
||||
|
||||
lvgl::lock(portMAX_DELAY);
|
||||
|
||||
tt_check(lvgl::lock(1000 / portTICK_PERIOD_MS));
|
||||
keyboardGroup = lv_group_create();
|
||||
auto* screen_root = lv_screen_active();
|
||||
assert(screen_root != nullptr);
|
||||
|
||||
lvgl::obj_set_style_bg_blacken(screen_root);
|
||||
|
||||
lv_obj_t* vertical_container = lv_obj_create(screen_root);
|
||||
@@ -156,6 +159,8 @@ void GuiService::onStart(TT_UNUSED ServiceContext& service) {
|
||||
|
||||
thread->setPriority(THREAD_PRIORITY_SERVICE);
|
||||
thread->start();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GuiService::onStop(TT_UNUSED ServiceContext& service) {
|
||||
|
||||
@@ -62,8 +62,9 @@ class LoaderService final : public Service {
|
||||
|
||||
public:
|
||||
|
||||
void onStart(TT_UNUSED ServiceContext& service) override {
|
||||
bool onStart(TT_UNUSED ServiceContext& service) override {
|
||||
dispatcherThread->start();
|
||||
return true;
|
||||
}
|
||||
|
||||
void onStop(TT_UNUSED ServiceContext& service) override {
|
||||
|
||||
@@ -2,16 +2,15 @@
|
||||
|
||||
#if TT_FEATURE_SCREENSHOT_ENABLED
|
||||
|
||||
#include "Tactility/service/screenshot/Screenshot.h"
|
||||
|
||||
#include <Tactility/service/ServiceContext.h>
|
||||
#include <Tactility/service/screenshot/Screenshot.h>
|
||||
#include <Tactility/service/ServiceRegistration.h>
|
||||
|
||||
#include <memory>
|
||||
#include <lvgl.h>
|
||||
|
||||
namespace tt::service::screenshot {
|
||||
|
||||
#define TAG "screenshot_service"
|
||||
constexpr auto* TAG = "ScreenshotService";
|
||||
|
||||
extern const ServiceManifest manifest;
|
||||
|
||||
@@ -51,6 +50,15 @@ void ScreenshotService::startTimed(const std::string& path, uint8_t delayInSecon
|
||||
}
|
||||
}
|
||||
|
||||
bool ScreenshotService::onStart(ServiceContext& serviceContext) {
|
||||
if (lv_screen_active() == nullptr) {
|
||||
TT_LOG_E(TAG, "No display found");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScreenshotService::stop() {
|
||||
auto lock = mutex.asScopedLock();
|
||||
if (!lock.lock(50 / portTICK_PERIOD_MS)) {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include <Tactility/service/ServiceRegistration.h>
|
||||
|
||||
#include <Tactility/Mutex.h>
|
||||
#include <Tactility/Tactility.h>
|
||||
#include <Tactility/Timer.h>
|
||||
#include <Tactility/hal/sdcard/SdCardDevice.h>
|
||||
|
||||
@@ -52,13 +53,21 @@ class SdCardService final : public Service {
|
||||
|
||||
public:
|
||||
|
||||
void onStart(ServiceContext& serviceContext) override {
|
||||
bool onStart(ServiceContext& serviceContext) override {
|
||||
if (hal::findFirstDevice<hal::sdcard::SdCardDevice>(hal::Device::Type::SdCard) == nullptr) {
|
||||
TT_LOG_W(TAG, "No SD card device found - not starting Service");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto service = findServiceById<SdCardService>(manifest.id);
|
||||
updateTimer = std::make_unique<Timer>(Timer::Type::Periodic, [service]() {
|
||||
service->update();
|
||||
});
|
||||
|
||||
// We want to try and scan more often in case of startup or scan lock failure
|
||||
updateTimer->start(1000);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void onStop(ServiceContext& serviceContext) override {
|
||||
|
||||
@@ -249,7 +249,12 @@ public:
|
||||
lvgl::statusbar_icon_remove(gps_icon_id);
|
||||
}
|
||||
|
||||
void onStart(ServiceContext& serviceContext) override {
|
||||
bool onStart(ServiceContext& serviceContext) override {
|
||||
if (lv_screen_active() == nullptr) {
|
||||
TT_LOG_E(TAG, "No display found");
|
||||
return false;
|
||||
}
|
||||
|
||||
paths = serviceContext.getPaths();
|
||||
|
||||
// TODO: Make thread-safe for LVGL
|
||||
@@ -265,6 +270,8 @@ public:
|
||||
|
||||
// We want to try and scan more often in case of startup or scan lock failure
|
||||
updateTimer->start(1000);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void onStop(ServiceContext& service) override{
|
||||
|
||||
@@ -414,10 +414,9 @@ static bool copy_scan_list(std::shared_ptr<Wifi> wifi) {
|
||||
}
|
||||
|
||||
static bool find_auto_connect_ap(std::shared_ptr<Wifi> wifi, settings::WifiApSettings& settings) {
|
||||
TT_LOG_I(TAG, "find_auto_connect_ap()");
|
||||
auto lock = wifi->dataMutex.asScopedLock();
|
||||
|
||||
if (lock.lock(10 / portTICK_PERIOD_MS)) {
|
||||
TT_LOG_I(TAG, "auto_connect()");
|
||||
for (int i = 0; i < wifi->scan_list_count; ++i) {
|
||||
auto ssid = reinterpret_cast<const char*>(wifi->scan_list[i].ssid);
|
||||
if (settings::contains(ssid)) {
|
||||
@@ -897,7 +896,7 @@ class WifiService final : public Service {
|
||||
|
||||
public:
|
||||
|
||||
void onStart(ServiceContext& service) override {
|
||||
bool onStart(ServiceContext& service) override {
|
||||
assert(wifi_singleton == nullptr);
|
||||
wifi_singleton = std::make_shared<Wifi>();
|
||||
|
||||
@@ -913,6 +912,8 @@ public:
|
||||
TT_LOG_I(TAG, "Auto-enabling due to setting");
|
||||
getMainDispatcher().dispatch([] { dispatchEnable(wifi_singleton); });
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void onStop(ServiceContext& service) override {
|
||||
|
||||
@@ -144,9 +144,10 @@ class WifiService final : public Service {
|
||||
|
||||
public:
|
||||
|
||||
void onStart(TT_UNUSED ServiceContext& service) final {
|
||||
bool onStart(TT_UNUSED ServiceContext& service) final {
|
||||
tt_check(wifi == nullptr);
|
||||
wifi = new Wifi();
|
||||
return true;
|
||||
}
|
||||
|
||||
void onStop(TT_UNUSED ServiceContext& service) final {
|
||||
|
||||
Reference in New Issue
Block a user