App and Service improvements (#106)

This commit is contained in:
Ken Van Hoeylandt 2024-12-05 22:46:27 +01:00 committed by GitHub
parent e86a11b7e2
commit 50ee77d572
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
69 changed files with 692 additions and 596 deletions

View File

@ -1,8 +1,8 @@
#include "lvgl.h"
#include "lvgl/Toolbar.h"
static void onShow(tt::app::App& app, lv_obj_t* parent) {
lv_obj_t* toolbar = tt::lvgl::toolbar_create(parent, app);
static void onShow(tt::app::AppContext& context, lv_obj_t* parent) {
lv_obj_t* toolbar = tt::lvgl::toolbar_create(parent, context);
lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);
lv_obj_t* label = lv_label_create(parent);
@ -10,7 +10,7 @@ static void onShow(tt::app::App& app, lv_obj_t* parent) {
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
}
extern const tt::app::Manifest hello_world_app = {
extern const tt::app::AppManifest hello_world_app = {
.id = "HelloWorld",
.name = "Hello World",
.onShow = onShow,

View File

@ -7,7 +7,7 @@ namespace tt::service::wifi {
extern void wifi_main(void*);
}
extern const tt::app::Manifest hello_world_app;
extern const tt::app::AppManifest hello_world_app;
extern "C" {

View File

@ -38,9 +38,9 @@ UI is created with [lvgl](https://github.com/lvgl/lvgl) which has lots of [widge
Creating a touch-capable UI is [easy](https://docs.lvgl.io/9.0/get-started/quick-overview.html) and doesn't require your own render loop!
```C++
static void onShow(tt::app::App app, lv_obj_t* parent) {
static void onShow(tt::app::AppContext context, lv_obj_t* parent) {
// Default toolbar with app name and close button
lv_obj_t* toolbar = tt::lvgl::toolbar_create(parent, app);
lv_obj_t* toolbar = tt::lvgl::toolbar_create(parent, context);
lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);
// Label widget
@ -50,7 +50,7 @@ static void onShow(tt::app::App app, lv_obj_t* parent) {
// Widgets are auto-removed from the parent when the app is closed
}
extern const tt::app::Manifest manifest = {
extern const tt::app::AppManifest manifest = {
.id = "HelloWorld", // Used to identify and start an app
.name = "Hello World", // Shown on the desktop and app's toolbar
.onShow = onShow // A minimal setup sets the onShow() function

View File

@ -1,10 +1,11 @@
#pragma once
#include "app/App.h"
#include "app/Manifest.h"
#include "app/AppContext.h"
#include "app/AppManifest.h"
#include "Bundle.h"
#include "Mutex.h"
#include <memory>
#include <utility>
namespace tt::app {
@ -17,70 +18,70 @@ typedef enum {
} State;
struct ResultHolder {
ResultHolder(Result result, const Bundle& resultData) {
this->result = result;
this->resultData = new Bundle(resultData);
}
~ResultHolder() {
delete resultData;
}
Result result;
Bundle* _Nullable resultData;
std::shared_ptr<const Bundle> resultData;
explicit ResultHolder(Result result) : result(result), resultData(nullptr) {}
ResultHolder(Result result, std::shared_ptr<const Bundle> resultData) :
result(result),
resultData(std::move(resultData))
{}
};
/**
* Thread-safe app instance.
*/
class AppInstance : public App {
class AppInstance : public AppContext {
private:
Mutex mutex = Mutex(MutexTypeNormal);
const Manifest& manifest;
const AppManifest& manifest;
State state = StateInitial;
Flags flags = { .showStatusbar = true };
/** @brief Optional parameters to start the app with
* When these are stored in the app struct, the struct takes ownership.
* Do not mutate after app creation.
*/
tt::Bundle parameters;
std::shared_ptr<const tt::Bundle> _Nullable parameters;
/** @brief @brief Contextual data related to the running app's instance
* The app can attach its data to this.
* The lifecycle is determined by the on_start and on_stop methods in the AppManifest.
* These manifest methods can optionally allocate/free data that is attached here.
*/
void* _Nullable data = nullptr;
std::unique_ptr<ResultHolder> resultHolder;
std::shared_ptr<void> _Nullable data;
std::unique_ptr<ResultHolder> _Nullable resultHolder;
public:
AppInstance(const Manifest& manifest) :
explicit AppInstance(const AppManifest& manifest) :
manifest(manifest) {}
AppInstance(const Manifest& manifest, const Bundle& parameters) :
AppInstance(const AppManifest& manifest, std::shared_ptr<const Bundle> parameters) :
manifest(manifest),
parameters(parameters) {}
parameters(std::move(parameters)) {}
~AppInstance() {}
~AppInstance() override = default;
void setState(State state);
State getState() const;
[[nodiscard]] State getState() const;
const Manifest& getManifest() const;
[[nodiscard]] const AppManifest& getManifest() const override;
Flags getFlags() const;
[[nodiscard]] Flags getFlags() const override;
void setFlags(Flags flags);
Flags& mutableFlags() { return flags; }
Flags& mutableFlags() { return flags; } // TODO: locking mechanism
_Nullable void* getData() const;
void setData(void* data);
[[nodiscard]] std::shared_ptr<void> _Nullable getData() const override;
void setData(std::shared_ptr<void> data) override;
const Bundle& getParameters() const;
[[nodiscard]] std::shared_ptr<const Bundle> getParameters() const override;
void setResult(Result result, const Bundle& bundle);
bool hasResult() const;
void setResult(Result result) override;
void setResult(Result result, std::shared_ptr<const Bundle> bundle) override;
[[nodiscard]] bool hasResult() const override;
std::unique_ptr<ResultHolder>& getResult() { return resultHolder; }
};

View File

@ -1,6 +1,6 @@
#pragma once
#include "app/Manifest.h"
#include "app/AppManifest.h"
#include "app/AppInstance.h"
#include "MessageQueue.h"
#include "Pubsub.h"
@ -9,6 +9,7 @@
#include "service/loader/Loader.h"
#include "RtosCompatSemaphore.h"
#include <stack>
#include <utility>
namespace tt::service::loader {
@ -35,7 +36,7 @@ typedef struct {
} LoaderEventAppHiding;
typedef struct {
const app::Manifest& manifest;
const app::AppManifest& manifest;
} LoaderEventAppStopped;
typedef struct {
@ -62,14 +63,14 @@ typedef enum {
class LoaderMessageAppStart {
public:
std::string id;
Bundle bundle;
std::shared_ptr<const Bundle> _Nullable parameters;
LoaderMessageAppStart() = default;
LoaderMessageAppStart(const std::string& id, const Bundle& bundle) {
this->id = id;
this->bundle = bundle;
}
LoaderMessageAppStart(const std::string& id, std::shared_ptr<const Bundle> parameters) :
id(id),
parameters(std::move(parameters))
{}
};
typedef struct {

View File

@ -15,13 +15,13 @@ static const Configuration* config_instance = nullptr;
// region Default services
namespace service {
namespace gui { extern const Manifest manifest; }
namespace loader { extern const Manifest manifest; }
namespace screenshot { extern const Manifest manifest; }
namespace statusbar { extern const Manifest manifest; }
namespace gui { extern const ServiceManifest manifest; }
namespace loader { extern const ServiceManifest manifest; }
namespace screenshot { extern const ServiceManifest manifest; }
namespace statusbar { extern const ServiceManifest manifest; }
}
static const std::vector<const service::Manifest*> system_services = {
static const std::vector<const service::ServiceManifest*> system_services = {
&service::loader::manifest,
&service::gui::manifest, // depends on loader service
#ifndef ESP_PLATFORM // Screenshots don't work yet on ESP32
@ -35,30 +35,30 @@ static const std::vector<const service::Manifest*> system_services = {
// region Default apps
namespace app {
namespace boot { extern const Manifest manifest; }
namespace desktop { extern const Manifest manifest; }
namespace files { extern const Manifest manifest; }
namespace gpio { extern const Manifest manifest; }
namespace imageviewer { extern const Manifest manifest; }
namespace screenshot { extern const Manifest manifest; }
namespace settings { extern const Manifest manifest; }
namespace display { extern const Manifest manifest; }
namespace i2cscanner { extern const Manifest manifest; }
namespace i2csettings { extern const Manifest manifest; }
namespace power { extern const Manifest manifest; }
namespace selectiondialog { extern const Manifest manifest; }
namespace systeminfo { extern const Manifest manifest; }
namespace textviewer { extern const Manifest manifest; }
namespace wifiapsettings { extern const Manifest manifest; }
namespace wificonnect { extern const Manifest manifest; }
namespace wifimanage { extern const Manifest manifest; }
namespace boot { extern const AppManifest manifest; }
namespace desktop { extern const AppManifest manifest; }
namespace files { extern const AppManifest manifest; }
namespace gpio { extern const AppManifest manifest; }
namespace imageviewer { extern const AppManifest manifest; }
namespace screenshot { extern const AppManifest manifest; }
namespace settings { extern const AppManifest manifest; }
namespace display { extern const AppManifest manifest; }
namespace i2cscanner { extern const AppManifest manifest; }
namespace i2csettings { extern const AppManifest manifest; }
namespace power { extern const AppManifest manifest; }
namespace selectiondialog { extern const AppManifest manifest; }
namespace systeminfo { extern const AppManifest manifest; }
namespace textviewer { extern const AppManifest manifest; }
namespace wifiapsettings { extern const AppManifest manifest; }
namespace wificonnect { extern const AppManifest manifest; }
namespace wifimanage { extern const AppManifest manifest; }
}
#ifndef ESP_PLATFORM
extern const app::Manifest screenshot_app;
extern const app::AppManifest screenshot_app;
#endif
static const std::vector<const app::Manifest*> system_apps = {
static const std::vector<const app::AppManifest*> system_apps = {
&app::boot::manifest,
&app::desktop::manifest,
&app::display::manifest,
@ -92,10 +92,10 @@ static void register_system_apps() {
}
}
static void register_user_apps(const app::Manifest* const apps[TT_CONFIG_APPS_LIMIT]) {
static void register_user_apps(const app::AppManifest* const apps[TT_CONFIG_APPS_LIMIT]) {
TT_LOG_I(TAG, "Registering user apps");
for (size_t i = 0; i < TT_CONFIG_APPS_LIMIT; i++) {
const app::Manifest* manifest = apps[i];
const app::AppManifest* manifest = apps[i];
if (manifest != nullptr) {
addApp(manifest);
} else {
@ -113,10 +113,10 @@ static void register_and_start_system_services() {
}
}
static void register_and_start_user_services(const service::Manifest* const services[TT_CONFIG_SERVICES_LIMIT]) {
static void register_and_start_user_services(const service::ServiceManifest* const services[TT_CONFIG_SERVICES_LIMIT]) {
TT_LOG_I(TAG, "Registering and starting user services");
for (size_t i = 0; i < TT_CONFIG_SERVICES_LIMIT; i++) {
const service::Manifest* manifest = services[i];
const service::ServiceManifest* manifest = services[i];
if (manifest != nullptr) {
addService(manifest);
tt_check(service::startService(manifest->id));
@ -152,11 +152,11 @@ void init(const Configuration& config) {
register_user_apps(config.apps);
TT_LOG_I(TAG, "init starting desktop app");
service::loader::startApp(app::boot::manifest.id, true, Bundle());
service::loader::startApp(app::boot::manifest.id, true);
if (config.auto_start_app_id) {
TT_LOG_I(TAG, "init auto-starting %s", config.auto_start_app_id);
service::loader::startApp(config.auto_start_app_id, true, Bundle());
service::loader::startApp(config.auto_start_app_id, true);
}
TT_LOG_I(TAG, "init complete");

View File

@ -1,8 +1,8 @@
#pragma once
#include "app/Manifest.h"
#include "app/AppManifest.h"
#include "hal/Configuration.h"
#include "service/Manifest.h"
#include "service/ServiceManifest.h"
#include "TactilityConfig.h"
namespace tt {
@ -10,8 +10,8 @@ namespace tt {
typedef struct {
const hal::Configuration* hardware;
// List of user applications
const app::Manifest* const apps[TT_CONFIG_APPS_LIMIT];
const service::Manifest* const services[TT_CONFIG_SERVICES_LIMIT];
const app::AppManifest* const apps[TT_CONFIG_APPS_LIMIT];
const service::ServiceManifest* const services[TT_CONFIG_SERVICES_LIMIT];
const char* auto_start_app_id;
} Configuration;

View File

@ -1,30 +0,0 @@
#pragma once
#include "Manifest.h"
#include "Bundle.h"
namespace tt::app {
typedef union {
struct {
bool showStatusbar : 1;
};
unsigned char flags;
} Flags;
/**
* A limited representation of the application instance.
* Do not store references or pointers to these!
*/
class App {
public:
virtual ~App() {};
virtual const Manifest& getManifest() const = 0;
virtual _Nullable void* getData() const = 0;
virtual void setData(void* data) = 0;
virtual const Bundle& getParameters() const = 0;
virtual Flags getFlags() const = 0;
virtual void setResult(Result result, const Bundle& bundle = Bundle()) = 0;
};
}

View File

@ -0,0 +1,38 @@
#pragma once
#include "AppManifest.h"
#include "Bundle.h"
#include <memory>
namespace tt::app {
typedef union {
struct {
bool showStatusbar : 1;
};
unsigned char flags;
} Flags;
/**
* A limited representation of the application instance.
* Do not store references or pointers to these!
*/
class AppContext {
protected:
virtual ~AppContext() = default;
public:
[[nodiscard]] virtual const AppManifest& getManifest() const = 0;
[[nodiscard]] virtual std::shared_ptr<void> _Nullable getData() const = 0;
virtual void setData(std::shared_ptr<void> data) = 0;
[[nodiscard]] virtual std::shared_ptr<const Bundle> getParameters() const = 0;
[[nodiscard]] virtual Flags getFlags() const = 0;
virtual void setResult(Result result) = 0;
virtual void setResult(Result result, std::shared_ptr<const Bundle> bundle)= 0;
[[nodiscard]] virtual bool hasResult() const = 0;
};
}

View File

@ -23,7 +23,7 @@ State AppInstance::getState() const {
* Consider creating MutableBundle vs Bundle.
* Consider not exposing bundle, but expose `app_get_bundle_int(key)` methods with locking in it.
*/
const Manifest& AppInstance::getManifest() const {
const AppManifest& AppInstance::getManifest() const {
return manifest;
}
@ -40,26 +40,45 @@ void AppInstance::setFlags(Flags newFlags) {
mutex.release();
}
_Nullable void* AppInstance::getData() const {
std::shared_ptr<void> _Nullable AppInstance::getData() const {
mutex.acquire(TtWaitForever);
auto result = data;
mutex.release();
return result;
}
void AppInstance::setData(void* newData) {
void AppInstance::setData(std::shared_ptr<void> newData) {
mutex.acquire(TtWaitForever);
data = newData;
mutex.release();
}
const Bundle& AppInstance::getParameters() const {
return parameters;
std::shared_ptr<const Bundle> AppInstance::getParameters() const {
mutex.acquire(TtWaitForever);
std::shared_ptr<const Bundle> result = parameters;
mutex.release();
return result;
}
void AppInstance::setResult(Result result, const Bundle& bundle) {
std::unique_ptr<ResultHolder> new_holder(new ResultHolder(result, bundle));
void AppInstance::setResult(Result result) {
std::unique_ptr<ResultHolder> new_holder(new ResultHolder(result));
mutex.acquire(TtWaitForever);
resultHolder = std::move(new_holder);
mutex.release();
}
void AppInstance::setResult(Result result, std::shared_ptr<const Bundle> bundle) {
std::unique_ptr<ResultHolder> new_holder(new ResultHolder(result, std::move(bundle)));
mutex.acquire(TtWaitForever);
resultHolder = std::move(new_holder);
mutex.release();
}
bool AppInstance::hasResult() const {
mutex.acquire(TtWaitForever);
bool has_result = resultHolder != nullptr;
mutex.release();
return has_result;
}
} // namespace

View File

@ -9,7 +9,7 @@ typedef struct _lv_obj_t lv_obj_t;
namespace tt::app {
class App;
class AppContext;
typedef enum {
/** Boot screen, shown before desktop is launched. */
@ -32,13 +32,13 @@ typedef enum {
ResultError
} Result;
typedef void (*AppOnStart)(App& app);
typedef void (*AppOnStop)(App& app);
typedef void (*AppOnShow)(App& app, lv_obj_t* parent);
typedef void (*AppOnHide)(App& app);
typedef void (*AppOnResult)(App& app, Result result, const Bundle& resultData);
typedef void (*AppOnStart)(AppContext& app);
typedef void (*AppOnStop)(AppContext& app);
typedef void (*AppOnShow)(AppContext& app, lv_obj_t* parent);
typedef void (*AppOnHide)(AppContext& app);
typedef void (*AppOnResult)(AppContext& app, Result result, const Bundle& resultData);
struct Manifest {
struct AppManifest {
/**
* The identifier by which the app is launched by the system and other apps.
*/
@ -86,7 +86,7 @@ struct Manifest {
};
struct {
bool operator()(const Manifest* left, const Manifest* right) const { return left->name < right->name; }
bool operator()(const AppManifest* left, const AppManifest* right) const { return left->name < right->name; }
} SortAppManifestByName;
} // namespace

View File

@ -7,12 +7,12 @@
namespace tt::app {
typedef std::unordered_map<std::string, const Manifest*> AppManifestMap;
typedef std::unordered_map<std::string, const AppManifest*> AppManifestMap;
static AppManifestMap app_manifest_map;
static Mutex hash_mutex(MutexTypeNormal);
void addApp(const Manifest* manifest) {
void addApp(const AppManifest* manifest) {
TT_LOG_I(TAG, "Registering manifest %s", manifest->id.c_str());
hash_mutex.acquire(TtWaitForever);
@ -20,16 +20,16 @@ void addApp(const Manifest* manifest) {
hash_mutex.release();
}
_Nullable const Manifest * findAppById(const std::string& id) {
_Nullable const AppManifest * findAppById(const std::string& id) {
hash_mutex.acquire(TtWaitForever);
auto iterator = app_manifest_map.find(id);
_Nullable const Manifest* result = iterator != app_manifest_map.end() ? iterator->second : nullptr;
_Nullable const AppManifest* result = iterator != app_manifest_map.end() ? iterator->second : nullptr;
hash_mutex.release();
return result;
}
std::vector<const Manifest*> getApps() {
std::vector<const Manifest*> manifests;
std::vector<const AppManifest*> getApps() {
std::vector<const AppManifest*> manifests;
hash_mutex.acquire(TtWaitForever);
for (const auto& item: app_manifest_map) {
manifests.push_back(item.second);

View File

@ -1,13 +1,13 @@
#pragma once
#include "Manifest.h"
#include "AppManifest.h"
#include <string>
#include <vector>
namespace tt::app {
void addApp(const Manifest* manifest);
const Manifest _Nullable* findAppById(const std::string& id);
std::vector<const Manifest*> getApps();
void addApp(const AppManifest* manifest);
const AppManifest _Nullable* findAppById(const std::string& id);
std::vector<const AppManifest*> getApps();
} // namespace

View File

@ -2,7 +2,7 @@
#include <Thread.h>
#include <Kernel.h>
#include "Assets.h"
#include "app/App.h"
#include "app/AppContext.h"
#include "lvgl.h"
#include "hal/Display.h"
#include "service/loader/Loader.h"
@ -48,8 +48,8 @@ static int32_t threadCallback(TT_UNUSED void* context) {
return 0;
}
static void onShow(TT_UNUSED App& app, lv_obj_t* parent) {
Data* data = (Data*)app.getData();
static void onShow(TT_UNUSED AppContext& app, lv_obj_t* parent) {
auto data = std::static_pointer_cast<Data>(app.getData());
lv_obj_t* image = lv_image_create(parent);
lv_obj_set_size(image, LV_PCT(100), LV_PCT(100));
@ -59,19 +59,17 @@ static void onShow(TT_UNUSED App& app, lv_obj_t* parent) {
data->thread.start();
}
static void onStart(App& app) {
Data* data = new Data();
static void onStart(AppContext& app) {
auto data = std::shared_ptr<Data>(new Data());
app.setData(data);
}
static void onStop(App& app) {
Data* data = (Data*)app.getData();
static void onStop(AppContext& app) {
auto data = std::static_pointer_cast<Data>(app.getData());
data->thread.join();
tt_assert(data);
delete data;
}
extern const Manifest manifest = {
extern const AppManifest manifest = {
.id = "Boot",
.name = "Boot",
.type = TypeBoot,

View File

@ -10,12 +10,12 @@ namespace tt::app::desktop {
static void onAppPressed(lv_event_t* e) {
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_CLICKED) {
const auto* manifest = static_cast<const Manifest*>(lv_event_get_user_data(e));
service::loader::startApp(manifest->id, false, Bundle());
const auto* manifest = static_cast<const AppManifest*>(lv_event_get_user_data(e));
service::loader::startApp(manifest->id, false);
}
}
static void createAppWidget(const Manifest* manifest, void* parent) {
static void createAppWidget(const AppManifest* manifest, void* parent) {
tt_check(parent);
auto* list = static_cast<lv_obj_t*>(parent);
const void* icon = !manifest->icon.empty() ? manifest->icon.c_str() : TT_ASSETS_APP_ICON_FALLBACK;
@ -23,7 +23,7 @@ static void createAppWidget(const Manifest* manifest, void* parent) {
lv_obj_add_event_cb(btn, &onAppPressed, LV_EVENT_CLICKED, (void*)manifest);
}
static void onShow(TT_UNUSED App& app, lv_obj_t* parent) {
static void onShow(TT_UNUSED AppContext& app, lv_obj_t* parent) {
lv_obj_t* list = lv_list_create(parent);
lv_obj_set_size(list, LV_PCT(100), LV_PCT(100));
lv_obj_center(list);
@ -46,7 +46,7 @@ static void onShow(TT_UNUSED App& app, lv_obj_t* parent) {
}
}
extern const Manifest manifest = {
extern const AppManifest manifest = {
.id = "Desktop",
.name = "Desktop",
.type = TypeDesktop,

View File

@ -1,4 +1,4 @@
#include "app/App.h"
#include "app/AppContext.h"
#include "Assets.h"
#include "DisplaySettings.h"
#include "Tactility.h"
@ -70,7 +70,7 @@ static void onOrientationSet(lv_event_t* event) {
}
}
static void onShow(App& app, lv_obj_t* parent) {
static void onShow(AppContext& app, lv_obj_t* parent) {
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lvgl::toolbar_create(parent, app);
@ -121,13 +121,13 @@ static void onShow(App& app, lv_obj_t* parent) {
lv_dropdown_set_selected(orientation_dropdown, orientation_selected);
}
static void onHide(TT_UNUSED App& app) {
static void onHide(TT_UNUSED AppContext& app) {
if (backlight_duty_set) {
setBacklightDuty(backlight_duty);
}
}
extern const Manifest manifest = {
extern const AppManifest manifest = {
.id = "Display",
.name = "Display",
.icon = TT_ASSETS_APP_ICON_DISPLAY_SETTINGS,

View File

@ -1,6 +1,6 @@
#include "FilesData.h"
#include "Tactility.h"
#include "app/App.h"
#include "app/AppContext.h"
#include "Assets.h"
#include "Check.h"
#include "FileUtils.h"
@ -17,6 +17,18 @@ namespace tt::app::files {
#define TAG "files_app"
extern const AppManifest manifest;
/** Returns the app data if the app is active. Note that this could clash if the same app is started twice and a background thread is slow. */
std::shared_ptr<Data> _Nullable optData() {
app::AppContext* app = service::loader::getCurrentApp();
if (app->getManifest().id == manifest.id) {
return std::static_pointer_cast<Data>(app->getData());
} else {
return nullptr;
}
}
/**
* Case-insensitive check to see if the given file matches the provided file extension.
* @param path the full path to the file
@ -57,10 +69,14 @@ static bool isSupportedTextFile(const char* filename) {
// region Views
static void updateViews(Data* data);
static void updateViews(std::shared_ptr<Data> data);
static void onNavigateUpPressed(TT_UNUSED lv_event_t* event) {
auto files_data = optData();
if (files_data == nullptr) {
return;
}
static void onNavigateUpPressed(lv_event_t* event) {
auto* files_data = (Data*)lv_event_get_user_data(event);
if (strcmp(files_data->current_path, "/") != 0) {
TT_LOG_I(TAG, "Navigating upwards");
char new_absolute_path[MAX_PATH_LENGTH];
@ -68,6 +84,7 @@ static void onNavigateUpPressed(lv_event_t* event) {
data_set_entries_for_path(files_data, new_absolute_path);
}
}
updateViews(files_data);
}
@ -103,16 +120,16 @@ static void viewFile(const char* path, const char* filename) {
TT_LOG_I(TAG, "Clicked %s", filepath);
if (isSupportedImageFile(filename)) {
Bundle bundle;
bundle.putString(IMAGE_VIEWER_FILE_ARGUMENT, processed_filepath);
auto bundle = std::shared_ptr<Bundle>(new Bundle());
bundle->putString(IMAGE_VIEWER_FILE_ARGUMENT, processed_filepath);
service::loader::startApp("ImageViewer", false, bundle);
} else if (isSupportedTextFile(filename)) {
Bundle bundle;
auto bundle = std::shared_ptr<Bundle>(new Bundle());
if (get_platform() == PlatformEsp) {
bundle.putString(TEXT_VIEWER_FILE_ARGUMENT, processed_filepath);
bundle->putString(TEXT_VIEWER_FILE_ARGUMENT, processed_filepath);
} else {
// Remove forward slash, because we need a relative path
bundle.putString(TEXT_VIEWER_FILE_ARGUMENT, processed_filepath + 1);
bundle->putString(TEXT_VIEWER_FILE_ARGUMENT, processed_filepath + 1);
}
service::loader::startApp("TextViewer", false, bundle);
} else {
@ -123,11 +140,13 @@ static void viewFile(const char* path, const char* filename) {
}
static void onFilePressed(lv_event_t* event) {
auto files_data = optData();
if (files_data == nullptr) {
return;
}
lv_event_code_t code = lv_event_get_code(event);
if (code == LV_EVENT_CLICKED) {
lv_obj_t* button = lv_event_get_current_target_obj(event);
auto* files_data = static_cast<Data*>(lv_obj_get_user_data(button));
auto* dir_entry = static_cast<dirent*>(lv_event_get_user_data(event));
TT_LOG_I(TAG, "Pressed %s %d", dir_entry->d_name, dir_entry->d_type);
@ -152,7 +171,7 @@ static void onFilePressed(lv_event_t* event) {
}
}
static void createFileWidget(Data* files_data, lv_obj_t* parent, struct dirent* dir_entry) {
static void createFileWidget(lv_obj_t* parent, struct dirent* dir_entry) {
tt_check(parent);
auto* list = (lv_obj_t*)parent;
const char* symbol;
@ -166,15 +185,14 @@ static void createFileWidget(Data* files_data, lv_obj_t* parent, struct dirent*
symbol = LV_SYMBOL_FILE;
}
lv_obj_t* button = lv_list_add_button(list, symbol, dir_entry->d_name);
lv_obj_set_user_data(button, files_data);
lv_obj_add_event_cb(button, &onFilePressed, LV_EVENT_CLICKED, (void*)dir_entry);
}
static void updateViews(Data* data) {
static void updateViews(std::shared_ptr<Data> data) {
lv_obj_clean(data->list);
for (int i = 0; i < data->dir_entries_count; ++i) {
TT_LOG_I(TAG, "Entry: %s %d", data->dir_entries[i]->d_name, data->dir_entries[i]->d_type);
createFileWidget(data, data->list, data->dir_entries[i]);
createFileWidget(data->list, data->dir_entries[i]);
}
}
@ -182,14 +200,14 @@ static void updateViews(Data* data) {
// region Lifecycle
static void onShow(App& app, lv_obj_t* parent) {
auto* data = static_cast<Data*>(app.getData());
static void onShow(AppContext& app, lv_obj_t* parent) {
auto data = std::static_pointer_cast<Data>(app.getData());
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lv_obj_t* toolbar = lvgl::toolbar_create(parent, "Files");
lvgl::toolbar_set_nav_action(toolbar, LV_SYMBOL_CLOSE, &onExitAppPressed, nullptr);
lvgl::toolbar_add_action(toolbar, LV_SYMBOL_UP, &onNavigateUpPressed, data);
lvgl::toolbar_add_action(toolbar, LV_SYMBOL_UP, &onNavigateUpPressed, nullptr);
data->list = lv_list_create(parent);
lv_obj_set_width(data->list, LV_PCT(100));
@ -198,8 +216,8 @@ static void onShow(App& app, lv_obj_t* parent) {
updateViews(data);
}
static void onStart(App& app) {
auto* data = data_alloc();
static void onStart(AppContext& app) {
auto data = std::shared_ptr<Data>(new Data());
// PC platform is bound to current work directory because of the LVGL file system mapping
if (get_platform() == PlatformSimulator) {
char cwd[PATH_MAX];
@ -216,20 +234,14 @@ static void onStart(App& app) {
app.setData(data);
}
static void onStop(App& app) {
auto* data = static_cast<Data*>(app.getData());
data_free(data);
}
// endregion Lifecycle
extern const Manifest manifest = {
extern const AppManifest manifest = {
.id = "Files",
.name = "Files",
.icon = TT_ASSETS_APP_ICON_FILES,
.type = TypeSystem,
.onStart = onStart,
.onStop = onStop,
.onShow = onShow,
};

View File

@ -26,40 +26,16 @@ static bool get_child_path(char* base_path, const char* child_path, char* out_pa
}
}
Data* data_alloc() {
auto* data = static_cast<Data*>(malloc(sizeof(Data)));
*data = (Data) {
.current_path = { 0x00 },
.dir_entries = nullptr,
.dir_entries_count = 0
};
return data;
}
void data_free(Data* data) {
data_free_entries(data);
free(data);
}
void data_free_entries(Data* data) {
for (int i = 0; i < data->dir_entries_count; ++i) {
free(data->dir_entries[i]);
}
free(data->dir_entries);
data->dir_entries = nullptr;
data->dir_entries_count = 0;
}
static void data_set_entries(Data* data, struct dirent** entries, int count) {
static void data_set_entries(std::shared_ptr<Data> data, struct dirent** entries, int count) {
if (data->dir_entries != nullptr) {
data_free_entries(data);
data->freeEntries();
}
data->dir_entries = entries;
data->dir_entries_count = count;
}
bool data_set_entries_for_path(Data* data, const char* path) {
bool data_set_entries_for_path(std::shared_ptr<Data> data, const char* path) {
TT_LOG_I(TAG, "Changing path: %s -> %s", data->current_path, path);
/**
@ -100,7 +76,7 @@ bool data_set_entries_for_path(Data* data, const char* path) {
}
}
bool data_set_entries_for_child_path(Data* data, const char* child_path) {
bool data_set_entries_for_child_path(std::shared_ptr<Data> data, const char* child_path) {
char new_absolute_path[MAX_PATH_LENGTH + 1];
if (get_child_path(data->current_path, child_path, new_absolute_path, MAX_PATH_LENGTH)) {
TT_LOG_I(TAG, "Navigating from %s to %s", data->current_path, new_absolute_path);

View File

@ -1,23 +1,36 @@
#pragma once
#include <dirent.h>
#include "lvgl.h"
#include <dirent.h>
#include <memory>
namespace tt::app::files {
#define MAX_PATH_LENGTH 256
typedef struct {
char current_path[MAX_PATH_LENGTH];
struct dirent** dir_entries;
int dir_entries_count;
lv_obj_t* list;
} Data;
struct Data {
char current_path[MAX_PATH_LENGTH] = { 0 };
struct dirent** dir_entries = nullptr;
int dir_entries_count = 0;
lv_obj_t* list = nullptr;
Data* data_alloc();
void data_free(Data* data);
void data_free_entries(Data* data);
bool data_set_entries_for_child_path(Data* data, const char* child_path);
bool data_set_entries_for_path(Data* data, const char* path);
void freeEntries() {
for (int i = 0; i < dir_entries_count; ++i) {
free(dir_entries[i]);
}
free(dir_entries);
dir_entries = nullptr;
dir_entries_count = 0;
}
~Data() {
freeEntries();
}
};
void data_free(std::shared_ptr<Data> data);
void data_free_entries(std::shared_ptr<Data> data);
bool data_set_entries_for_child_path(std::shared_ptr<Data> data, const char* child_path);
bool data_set_entries_for_path(std::shared_ptr<Data> data, const char* path);
} // namespace

View File

@ -29,8 +29,8 @@ public:
tt_check(mutex.release() == TtStatusOk);
}
void onShow(App& app, lv_obj_t* parent);
void onHide(App& app);
void onShow(AppContext& app, lv_obj_t* parent);
void onHide(AppContext& app);
void startTask();
void stopTask();
@ -135,8 +135,8 @@ void Gpio::stopTask() {
// endregion Task
void Gpio::onShow(App& app, lv_obj_t* parent) {
auto* gpio = (Gpio*)app.getData();
void Gpio::onShow(AppContext& app, lv_obj_t* parent) {
auto gpio = std::static_pointer_cast<Gpio>(app.getData());
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lv_obj_t* toolbar = lvgl::toolbar_create(parent, app);
@ -191,42 +191,36 @@ void Gpio::onShow(App& app, lv_obj_t* parent) {
gpio->startTask();
}
void Gpio::onHide(App& app) {
auto* gpio = (Gpio*)app.getData();
void Gpio::onHide(AppContext& app) {
auto gpio = std::static_pointer_cast<Gpio>(app.getData());
gpio->stopTask();
}
// region App lifecycle
static void onShow(App& app, lv_obj_t* parent) {
auto* gpio = (Gpio*)app.getData();
static void onShow(AppContext& app, lv_obj_t* parent) {
auto gpio = std::static_pointer_cast<Gpio>(app.getData());
gpio->onShow(app, parent);
}
static void onHide(App& app) {
auto* gpio = (Gpio*)app.getData();
static void onHide(AppContext& app) {
auto gpio = std::static_pointer_cast<Gpio>(app.getData());
gpio->onHide(app);
}
static void onStart(App& app) {
auto* gpio = new Gpio();
static void onStart(AppContext& app) {
auto gpio = std::shared_ptr<Gpio>(new Gpio());
app.setData(gpio);
}
static void onStop(App& app) {
auto* gpio = (Gpio*)app.getData();
delete gpio;
}
// endregion App lifecycle
extern const Manifest manifest = {
extern const AppManifest manifest = {
.id = "Gpio",
.name = "GPIO",
.type = TypeSystem,
.onStart = onStart,
.onStop = onStop,
.onShow = onShow,
.onHide = onHide
};

View File

@ -14,7 +14,6 @@ std::string getAddressText(uint8_t address) {
return stream.str();
}
std::string getPortNamesForDropdown() {
std::vector<std::string> config_names;
size_t port_index = 0;

View File

@ -4,21 +4,37 @@
#include "Assets.h"
#include "Tactility.h"
#include "app/App.h"
#include "app/AppContext.h"
#include "lvgl/LvglSync.h"
#include "lvgl/Toolbar.h"
#include "service/loader/Loader.h"
#define START_SCAN_TEXT "Scan"
#define STOP_SCAN_TEXT "Stop scan"
namespace tt::app::i2cscanner {
static void updateViews(Data* data);
static void updateViews(std::shared_ptr<Data> data);
extern const AppManifest manifest;
/** Returns the app data if the app is active. Note that this could clash if the same app is started twice and a background thread is slow. */
std::shared_ptr<Data> _Nullable optData() {
app::AppContext* app = service::loader::getCurrentApp();
if (app->getManifest().id == manifest.id) {
return std::static_pointer_cast<Data>(app->getData());
} else {
return nullptr;
}
}
static void onSelectBus(lv_event_t* event) {
auto data = optData();
if (data == nullptr) {
return;
}
auto* dropdown = static_cast<lv_obj_t*>(lv_event_get_target(event));
uint32_t selected = lv_dropdown_get_selected(dropdown);
Data* data = (Data*) lv_event_get_user_data(event);
auto i2c_devices = tt::getConfiguration()->hardware->i2c;
assert(selected < i2c_devices.size());
@ -35,16 +51,18 @@ static void onSelectBus(lv_event_t* event) {
}
static void onPressScan(lv_event_t* event) {
auto* data = (Data*)lv_event_get_user_data(event);
if (data->scanState == ScanStateScanning) {
stopScanning(data);
} else {
startScanning(data);
auto data = optData();
if (data != nullptr) {
if (data->scanState == ScanStateScanning) {
stopScanning(data);
} else {
startScanning(data);
}
updateViews(data);
}
updateViews(data);
}
static void updateViews(Data* data) {
static void updateViews(std::shared_ptr<Data> data) {
if (data->mutex.acquire(100 / portTICK_PERIOD_MS) == TtStatusOk) {
if (data->scanState == ScanStateScanning) {
lv_label_set_text(data->scanButtonLabelWidget, STOP_SCAN_TEXT);
@ -75,7 +93,7 @@ static void updateViews(Data* data) {
}
}
static void updateViewsSafely(Data* data) {
static void updateViewsSafely(std::shared_ptr<Data> data) {
if (lvgl::lock(100 / portTICK_PERIOD_MS)) {
updateViews(data);
lvgl::unlock();
@ -84,7 +102,7 @@ static void updateViewsSafely(Data* data) {
}
}
void onThreadFinished(Data* data) {
void onThreadFinished(std::shared_ptr<Data> data) {
if (data->mutex.acquire(100 / portTICK_PERIOD_MS) == TtStatusOk) {
if (data->scanState == ScanStateScanning) {
data->scanState = ScanStateStopped;
@ -96,8 +114,8 @@ void onThreadFinished(Data* data) {
}
}
static void onShow(App& app, lv_obj_t* parent) {
auto* data = (Data*)app.getData();
static void onShow(AppContext& app, lv_obj_t* parent) {
auto data = std::static_pointer_cast<Data>(app.getData());
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lvgl::toolbar_create(parent, app);
@ -116,7 +134,7 @@ static void onShow(App& app, lv_obj_t* parent) {
lv_obj_t* scan_button = lv_button_create(wrapper);
lv_obj_set_width(scan_button, LV_PCT(48));
lv_obj_align(scan_button, LV_ALIGN_TOP_LEFT, 0, 1); // Shift 1 pixel to align with selection box
lv_obj_add_event_cb(scan_button, &onPressScan, LV_EVENT_CLICKED, data);
lv_obj_add_event_cb(scan_button, &onPressScan, LV_EVENT_CLICKED, nullptr);
lv_obj_t* scan_button_label = lv_label_create(scan_button);
lv_obj_align(scan_button_label, LV_ALIGN_CENTER, 0, 0);
lv_label_set_text(scan_button_label, START_SCAN_TEXT);
@ -127,7 +145,7 @@ static void onShow(App& app, lv_obj_t* parent) {
lv_dropdown_set_options(port_dropdown, dropdown_items.c_str());
lv_obj_set_width(port_dropdown, LV_PCT(48));
lv_obj_align(port_dropdown, LV_ALIGN_TOP_RIGHT, 0, 0);
lv_obj_add_event_cb(port_dropdown, onSelectBus, LV_EVENT_VALUE_CHANGED, data);
lv_obj_add_event_cb(port_dropdown, onSelectBus, LV_EVENT_VALUE_CHANGED, nullptr);
lv_dropdown_set_selected(port_dropdown, 0);
data->portDropdownWidget = port_dropdown;
@ -139,30 +157,24 @@ static void onShow(App& app, lv_obj_t* parent) {
data->scanListWidget = scan_list;
}
static void onHide(App& app) {
auto* data = (Data*)app.getData();
static void onHide(AppContext& app) {
auto data = std::static_pointer_cast<Data>(app.getData());
if (hasScanThread(data)) {
stopScanning(data);
}
}
static void onStart(App& app) {
Data* data = new Data();
static void onStart(AppContext& app) {
auto data = std::shared_ptr<Data>(new Data());
app.setData(data);
}
static void onStop(App& app) {
Data* data = (Data*)app.getData();
delete data;
}
extern const Manifest manifest = {
extern const AppManifest manifest = {
.id = "I2cScanner",
.name = "I2C Scanner",
.icon = TT_ASSETS_APP_ICON_I2C_SETTINGS,
.type = TypeSystem,
.onStart = onStart,
.onStop = onStop,
.onShow = onShow,
.onHide = onHide
};

View File

@ -5,6 +5,7 @@
#include <Thread.h>
#include "lvgl.h"
#include "hal/i2c/I2c.h"
#include <memory>
namespace tt::app::i2cscanner {
@ -30,6 +31,6 @@ struct Data {
lv_obj_t* scanListWidget = nullptr;
};
void onThreadFinished(Data* data);
void onThreadFinished(std::shared_ptr<Data> data);
}

View File

@ -1,11 +1,12 @@
#include "I2cScannerThread.h"
#include "lvgl.h"
#include "service/gui/Gui.h"
#include <iomanip>
#include "service/loader/Loader.h"
namespace tt::app::i2cscanner {
static bool shouldStopScanThread(Data* data) {
std::shared_ptr<Data> _Nullable optData();
static bool shouldStopScanThread(std::shared_ptr<Data> data) {
if (data->mutex.acquire(100 / portTICK_PERIOD_MS) == TtStatusOk) {
bool is_scanning = data->scanState == ScanStateScanning;
tt_check(data->mutex.release() == TtStatusOk);
@ -15,7 +16,7 @@ static bool shouldStopScanThread(Data* data) {
}
}
static bool getPort(Data* data, i2c_port_t* port) {
static bool getPort(std::shared_ptr<Data> data, i2c_port_t* port) {
if (data->mutex.acquire(100 / portTICK_PERIOD_MS) == TtStatusOk) {
*port = data->port;
tt_assert(data->mutex.release() == TtStatusOk);
@ -26,7 +27,7 @@ static bool getPort(Data* data, i2c_port_t* port) {
}
}
static bool addAddressToList(Data* data, uint8_t address) {
static bool addAddressToList(std::shared_ptr<Data> data, uint8_t address) {
if (data->mutex.acquire(100 / portTICK_PERIOD_MS) == TtStatusOk) {
data->scannedAddresses.push_back(address);
tt_assert(data->mutex.release() == TtStatusOk);
@ -37,8 +38,12 @@ static bool addAddressToList(Data* data, uint8_t address) {
}
}
static int32_t scanThread(void* context) {
Data* data = (Data*) context;
static int32_t scanThread(TT_UNUSED void* context) {
auto data = optData();
if (data == nullptr) {
return -1;
}
TT_LOG_I(TAG, "Scan thread started");
for (uint8_t address = 0; address < 128; ++address) {
@ -69,7 +74,7 @@ static int32_t scanThread(void* context) {
return 0;
}
bool hasScanThread(Data* data) {
bool hasScanThread(std::shared_ptr<Data> data) {
bool has_thread;
if (data->mutex.acquire(100 / portTICK_PERIOD_MS) == TtStatusOk) {
has_thread = data->scanThread != nullptr;
@ -82,7 +87,7 @@ bool hasScanThread(Data* data) {
}
}
void startScanning(Data* data) {
void startScanning(std::shared_ptr<Data> data) {
if (hasScanThread(data)) {
stopScanning(data);
}
@ -99,7 +104,7 @@ void startScanning(Data* data) {
"i2c scanner",
4096,
scanThread,
data
nullptr
);
data->scanThread->start();
tt_check(data->mutex.release() == TtStatusOk);
@ -108,7 +113,7 @@ void startScanning(Data* data) {
}
}
void stopScanning(Data* data) {
void stopScanning(std::shared_ptr<Data> data) {
bool sent_halt;
if (data->mutex.acquire(250 / portTICK_PERIOD_MS) == TtStatusOk) {
tt_assert(data->scanThread != nullptr);

View File

@ -1,11 +1,12 @@
#pragma once
#include "I2cScanner.h"
#include <memory>
namespace tt::app::i2cscanner {
bool hasScanThread(Data* data);
void startScanning(Data* data);
void stopScanning(Data* data);
bool hasScanThread(std::shared_ptr<Data> data);
void startScanning(std::shared_ptr<Data> data);
void stopScanning(std::shared_ptr<Data> data);
}

View File

@ -69,7 +69,7 @@ static void show(lv_obj_t* parent, const hal::i2c::Configuration& configuration)
}
}
static void onShow(App& app, lv_obj_t* parent) {
static void onShow(AppContext& app, lv_obj_t* parent) {
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lvgl::toolbar_create(parent, app);
@ -85,7 +85,7 @@ static void onShow(App& app, lv_obj_t* parent) {
}
}
extern const Manifest manifest = {
extern const AppManifest manifest = {
.id = "I2cSettings",
.name = "I2C",
.icon = TT_ASSETS_APP_ICON_I2C_SETTINGS,

View File

@ -1,5 +1,5 @@
#include <TactilityCore.h>
#include "ImageViewer.h"
#include "Log.h"
#include "lvgl.h"
#include "lvgl/Style.h"
#include "lvgl/Toolbar.h"
@ -8,7 +8,7 @@ namespace tt::app::imageviewer {
#define TAG "image_viewer"
static void onShow(App& app, lv_obj_t* parent) {
static void onShow(AppContext& app, lv_obj_t* parent) {
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lvgl::toolbar_create(parent, app);
@ -22,16 +22,17 @@ static void onShow(App& app, lv_obj_t* parent) {
lv_obj_t* image = lv_img_create(wrapper);
lv_obj_align(image, LV_ALIGN_CENTER, 0, 0);
const Bundle& bundle = app.getParameters();
std::shared_ptr<const Bundle> bundle = app.getParameters();
tt_check(bundle != nullptr, "Parameters not set");
std::string file_argument;
if (bundle.optString(IMAGE_VIEWER_FILE_ARGUMENT, file_argument)) {
if (bundle->optString(IMAGE_VIEWER_FILE_ARGUMENT, file_argument)) {
std::string prefixed_path = "A:" + file_argument;
TT_LOG_I(TAG, "Opening %s", prefixed_path.c_str());
lv_img_set_src(image, prefixed_path.c_str());
}
}
extern const Manifest manifest = {
extern const AppManifest manifest = {
.id = "ImageViewer",
.name = "Image Viewer",
.type = TypeHidden,

View File

@ -1,4 +1,4 @@
#include "app/App.h"
#include "app/AppContext.h"
#include "Assets.h"
#include "lvgl.h"
#include "Tactility.h"
@ -6,22 +6,35 @@
#include "lvgl/LvglSync.h"
#include "lvgl/Style.h"
#include "lvgl/Toolbar.h"
#include "service/loader/Loader.h"
namespace tt::app::power {
#define TAG "power"
typedef struct {
Timer* update_timer;
extern const AppManifest manifest;
static void on_timer(TT_UNUSED void* context);
struct Data {
std::unique_ptr<Timer> update_timer = std::unique_ptr<Timer>(new Timer(Timer::TypePeriodic, &on_timer, nullptr));
const hal::Power* power;
lv_obj_t* enable_switch;
lv_obj_t* charge_state;
lv_obj_t* charge_level;
lv_obj_t* current;
} Data;
};
static void updateUi(void* callbackContext) {
auto* data = (Data*)callbackContext;
/** Returns the app data if the app is active. Note that this could clash if the same app is started twice and a background thread is slow. */
std::shared_ptr<Data> _Nullable optData() {
app::AppContext* app = service::loader::getCurrentApp();
if (app->getManifest().id == manifest.id) {
return std::static_pointer_cast<Data>(app->getData());
} else {
return nullptr;
}
}
static void updateUi(std::shared_ptr<Data> data) {
bool charging_enabled = data->power->isChargingEnabled();
const char* charge_state = data->power->isCharging() ? "yes" : "no";
uint8_t charge_level = data->power->getChargeLevel();
@ -40,20 +53,30 @@ static void updateUi(void* callbackContext) {
lvgl::unlock();
}
static void on_timer(TT_UNUSED void* context) {
auto data = optData();
if (data != nullptr) {
updateUi(data);
}
}
static void onPowerEnabledChanged(lv_event_t* event) {
lv_event_code_t code = lv_event_get_code(event);
auto* enable_switch = static_cast<lv_obj_t*>(lv_event_get_target(event));
if (code == LV_EVENT_VALUE_CHANGED) {
bool is_on = lv_obj_has_state(enable_switch, LV_STATE_CHECKED);
auto* data = static_cast<Data*>(lv_event_get_user_data(event));
if (data->power->isChargingEnabled() != is_on) {
data->power->setChargingEnabled(is_on);
updateUi(data);
auto data = optData();
if (data != nullptr) {
if (data->power->isChargingEnabled() != is_on) {
data->power->setChargingEnabled(is_on);
updateUi(data);
}
}
}
}
static void onShow(App& app, lv_obj_t* parent) {
static void onShow(AppContext& app, lv_obj_t* parent) {
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lvgl::toolbar_create(parent, app);
@ -64,7 +87,7 @@ static void onShow(App& app, lv_obj_t* parent) {
lv_obj_set_flex_grow(wrapper, 1);
lv_obj_set_flex_flow(wrapper, LV_FLEX_FLOW_COLUMN);
auto* data = static_cast<Data*>(app.getData());
auto data = std::static_pointer_cast<Data>(app.getData());
// Top row: enable/disable
lv_obj_t* switch_container = lv_obj_create(wrapper);
@ -78,7 +101,7 @@ static void onShow(App& app, lv_obj_t* parent) {
lv_obj_set_align(enable_label, LV_ALIGN_LEFT_MID);
lv_obj_t* enable_switch = lv_switch_create(switch_container);
lv_obj_add_event_cb(enable_switch, onPowerEnabledChanged, LV_EVENT_VALUE_CHANGED, data);
lv_obj_add_event_cb(enable_switch, onPowerEnabledChanged, LV_EVENT_VALUE_CHANGED, nullptr);
lv_obj_set_align(enable_switch, LV_ALIGN_RIGHT_MID);
data->enable_switch = enable_switch;
@ -90,32 +113,24 @@ static void onShow(App& app, lv_obj_t* parent) {
data->update_timer->start(ms_to_ticks(1000));
}
static void onHide(TT_UNUSED App& app) {
auto* data = static_cast<Data*>(app.getData());
static void onHide(TT_UNUSED AppContext& app) {
auto data = std::static_pointer_cast<Data>(app.getData());
data->update_timer->stop();
}
static void onStart(App& app) {
auto* data = new Data();
data->update_timer = new Timer(Timer::TypePeriodic, &updateUi, data);
static void onStart(AppContext& app) {
auto data = std::shared_ptr<Data>(new Data());
app.setData(data);
data->power = getConfiguration()->hardware->power;
assert(data->power != nullptr); // The Power app only shows up on supported devices
app.setData(data);
}
static void onStop(App& app) {
auto* data = static_cast<Data*>(app.getData());
delete data->update_timer;
delete data;
}
extern const Manifest manifest = {
extern const AppManifest manifest = {
.id = "Power",
.name = "Power",
.icon = TT_ASSETS_APP_ICON_POWER_SETTINGS,
.type = TypeSettings,
.onStart = onStart,
.onStop = onStop,
.onShow = onShow,
.onHide = onHide
};

View File

@ -1,29 +1,24 @@
#include "ScreenshotUi.h"
#include <memory>
namespace tt::app::screenshot {
static void onShow(App& app, lv_obj_t* parent) {
auto* ui = static_cast<ScreenshotUi*>(app.getData());
static void onShow(AppContext& app, lv_obj_t* parent) {
auto ui = std::static_pointer_cast<ScreenshotUi>(app.getData());
create_ui(app, ui, parent);
}
static void onStart(App& app) {
auto* ui = static_cast<ScreenshotUi*>(malloc(sizeof(ScreenshotUi)));
app.setData(ui);
static void onStart(AppContext& app) {
auto ui = std::shared_ptr<ScreenshotUi>(new ScreenshotUi());
app.setData(ui); // Ensure data gets deleted when no more in use
}
static void onStop(App& app) {
auto* ui = static_cast<ScreenshotUi*>(app.getData());
free(ui);
}
extern const Manifest manifest = {
extern const AppManifest manifest = {
.id = "Screenshot",
.name = "_Screenshot", // So it gets put at the bottom of the desktop and becomes less visible on small screen devices
.icon = LV_SYMBOL_IMAGE,
.type = TypeSystem,
.onStart = onStart,
.onStop = onStop,
.onShow = onShow,
};

View File

@ -3,6 +3,7 @@
#include "TactilityCore.h"
#include "hal/sdcard/Sdcard.h"
#include "service/gui/Gui.h"
#include "service/loader/Loader.h"
#include "service/screenshot/Screenshot.h"
#include "lvgl/Toolbar.h"
@ -10,29 +11,24 @@ namespace tt::app::screenshot {
#define TAG "screenshot_ui"
static void update_mode(ScreenshotUi* ui) {
lv_obj_t* label = ui->start_stop_button_label;
if (service::screenshot::isStarted()) {
lv_label_set_text(label, "Stop");
} else {
lv_label_set_text(label, "Start");
}
extern AppManifest manifest;
static void update_mode(std::shared_ptr<ScreenshotUi> ui);
uint32_t selected = lv_dropdown_get_selected(ui->mode_dropdown);
if (selected == 0) { // Timer
lv_obj_remove_flag(ui->timer_wrapper, LV_OBJ_FLAG_HIDDEN);
/** Returns the app data if the app is active. Note that this could clash if the same app is started twice and a background thread is slow. */
std::shared_ptr<ScreenshotUi> _Nullable optScreenshotUi() {
app::AppContext* app = service::loader::getCurrentApp();
if (app->getManifest().id == manifest.id) {
return std::static_pointer_cast<ScreenshotUi>(app->getData());
} else {
lv_obj_add_flag(ui->timer_wrapper, LV_OBJ_FLAG_HIDDEN);
return nullptr;
}
}
static void on_mode_set(lv_event_t* event) {
auto* ui = (ScreenshotUi*)lv_event_get_user_data(event);
update_mode(ui);
}
static void on_start_pressed(lv_event_t* event) {
auto* ui = static_cast<ScreenshotUi*>(lv_event_get_user_data(event));
auto ui = optScreenshotUi();
if (ui == nullptr) {
return;
}
if (service::screenshot::isStarted()) {
TT_LOG_I(TAG, "Stop screenshot");
@ -58,7 +54,30 @@ static void on_start_pressed(lv_event_t* event) {
update_mode(ui);
}
static void create_mode_setting_ui(ScreenshotUi* ui, lv_obj_t* parent) {
static void update_mode(std::shared_ptr<ScreenshotUi> ui) {
lv_obj_t* label = ui->start_stop_button_label;
if (service::screenshot::isStarted()) {
lv_label_set_text(label, "Stop");
} else {
lv_label_set_text(label, "Start");
}
uint32_t selected = lv_dropdown_get_selected(ui->mode_dropdown);
if (selected == 0) { // Timer
lv_obj_remove_flag(ui->timer_wrapper, LV_OBJ_FLAG_HIDDEN);
} else {
lv_obj_add_flag(ui->timer_wrapper, LV_OBJ_FLAG_HIDDEN);
}
}
static void on_mode_set(lv_event_t* event) {
auto ui = optScreenshotUi();
if (ui != nullptr) {
update_mode(ui);
}
}
static void create_mode_setting_ui(std::shared_ptr<ScreenshotUi> ui, lv_obj_t* parent) {
lv_obj_t* mode_wrapper = lv_obj_create(parent);
lv_obj_set_size(mode_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_pad_all(mode_wrapper, 0, 0);
@ -71,7 +90,7 @@ static void create_mode_setting_ui(ScreenshotUi* ui, lv_obj_t* parent) {
lv_obj_t* mode_dropdown = lv_dropdown_create(mode_wrapper);
lv_dropdown_set_options(mode_dropdown, "Timer\nApp start");
lv_obj_align_to(mode_dropdown, mode_label, LV_ALIGN_OUT_RIGHT_MID, 8, 0);
lv_obj_add_event_cb(mode_dropdown, on_mode_set, LV_EVENT_VALUE_CHANGED, ui);
lv_obj_add_event_cb(mode_dropdown, on_mode_set, LV_EVENT_VALUE_CHANGED, nullptr);
ui->mode_dropdown = mode_dropdown;
service::screenshot::Mode mode = service::screenshot::getMode();
if (mode == service::screenshot::ScreenshotModeApps) {
@ -83,10 +102,10 @@ static void create_mode_setting_ui(ScreenshotUi* ui, lv_obj_t* parent) {
lv_obj_t* button_label = lv_label_create(button);
lv_obj_align(button_label, LV_ALIGN_CENTER, 0, 0);
ui->start_stop_button_label = button_label;
lv_obj_add_event_cb(button, &on_start_pressed, LV_EVENT_CLICKED, ui);
lv_obj_add_event_cb(button, &on_start_pressed, LV_EVENT_CLICKED, nullptr);
}
static void create_path_ui(ScreenshotUi* ui, lv_obj_t* parent) {
static void create_path_ui(std::shared_ptr<ScreenshotUi> ui, lv_obj_t* parent) {
lv_obj_t* path_wrapper = lv_obj_create(parent);
lv_obj_set_size(path_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_pad_all(path_wrapper, 0, 0);
@ -116,7 +135,7 @@ static void create_path_ui(ScreenshotUi* ui, lv_obj_t* parent) {
}
}
static void create_timer_settings_ui(ScreenshotUi* ui, lv_obj_t* parent) {
static void create_timer_settings_ui(std::shared_ptr<ScreenshotUi> ui, lv_obj_t* parent) {
lv_obj_t* timer_wrapper = lv_obj_create(parent);
lv_obj_set_size(timer_wrapper, LV_PCT(100), LV_SIZE_CONTENT);
lv_obj_set_style_pad_all(timer_wrapper, 0, 0);
@ -153,7 +172,7 @@ static void create_timer_settings_ui(ScreenshotUi* ui, lv_obj_t* parent) {
lv_label_set_text(delay_unit_label, "seconds");
}
void create_ui(const App& app, ScreenshotUi* ui, lv_obj_t* parent) {
void create_ui(const AppContext& app, std::shared_ptr<ScreenshotUi> ui, lv_obj_t* parent) {
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lv_obj_t* toolbar = lvgl::toolbar_create(parent, app);
lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);

View File

@ -1,6 +1,6 @@
#pragma once
#include "app/App.h"
#include "app/AppContext.h"
#include "lvgl.h"
namespace tt::app::screenshot {
@ -13,6 +13,6 @@ typedef struct {
lv_obj_t* delay_textarea;
} ScreenshotUi;
void create_ui(const App& app, ScreenshotUi* ui, lv_obj_t* parent);
void create_ui(const AppContext& app, std::shared_ptr<ScreenshotUi> ui, lv_obj_t* parent);
} // namespace

View File

@ -1,9 +1,9 @@
#include "SelectionDialog.h"
#include "Log.h"
#include "lvgl.h"
#include "lvgl/Toolbar.h"
#include "service/loader/Loader.h"
#include <StringUtils.h>
#include <TactilityCore.h>
namespace tt::app::selectiondialog {
@ -27,17 +27,17 @@ int32_t getResultIndex(const Bundle& bundle) {
return index;
}
void setResultIndex(Bundle& bundle, int32_t index) {
bundle.putInt32(RESULT_BUNDLE_KEY_INDEX, index);
void setResultIndex(std::shared_ptr<Bundle> bundle, int32_t index) {
bundle->putInt32(RESULT_BUNDLE_KEY_INDEX, index);
}
void setTitleParameter(Bundle& bundle, const std::string& title) {
bundle.putString(PARAMETER_BUNDLE_KEY_TITLE, title);
void setTitleParameter(std::shared_ptr<Bundle> bundle, const std::string& title) {
bundle->putString(PARAMETER_BUNDLE_KEY_TITLE, title);
}
static std::string getTitleParameter(const Bundle& bundle) {
static std::string getTitleParameter(std::shared_ptr<const Bundle> bundle) {
std::string result;
if (bundle.optString(PARAMETER_BUNDLE_KEY_TITLE, result)) {
if (bundle->optString(PARAMETER_BUNDLE_KEY_TITLE, result)) {
return result;
} else {
return DEFAULT_TITLE;
@ -49,8 +49,8 @@ static void onListItemSelected(lv_event_t* e) {
if (code == LV_EVENT_CLICKED) {
size_t index = (size_t)(e->user_data);
TT_LOG_I(TAG, "Selected item at index %d", index);
tt::app::App* app = service::loader::getCurrentApp();
Bundle bundle;
tt::app::AppContext* app = service::loader::getCurrentApp();
auto bundle = std::shared_ptr<Bundle>(new Bundle());
setResultIndex(bundle, (int32_t)index);
app->setResult(app::ResultOk, bundle);
service::loader::stopApp();
@ -63,7 +63,7 @@ static void createChoiceItem(void* parent, const std::string& title, size_t inde
lv_obj_add_event_cb(btn, &onListItemSelected, LV_EVENT_CLICKED, (void*)index);
}
static void onShow(App& app, lv_obj_t* parent) {
static void onShow(AppContext& app, lv_obj_t* parent) {
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
std::string title = getTitleParameter(app.getParameters());
lvgl::toolbar_create(parent, title);
@ -72,16 +72,17 @@ static void onShow(App& app, lv_obj_t* parent) {
lv_obj_set_width(list, LV_PCT(100));
lv_obj_set_flex_grow(list, 1);
const Bundle& parameters = app.getParameters();
auto parameters = app.getParameters();
tt_check(parameters != nullptr, "No parameters");
std::string items_concatenated;
if (parameters.optString(PARAMETER_BUNDLE_KEY_ITEMS, items_concatenated)) {
if (parameters->optString(PARAMETER_BUNDLE_KEY_ITEMS, items_concatenated)) {
std::vector<std::string> items = string_split(items_concatenated, PARAMETER_ITEM_CONCATENATION_TOKEN);
if (items.empty() || items.front().empty()) {
TT_LOG_E(TAG, "No items provided");
app.setResult(ResultError);
service::loader::stopApp();
} else if (items.size() == 1) {
Bundle result_bundle;
auto result_bundle = std::shared_ptr<Bundle>(new Bundle());
setResultIndex(result_bundle, 0);
app.setResult(ResultOk, result_bundle);
service::loader::stopApp();
@ -99,7 +100,7 @@ static void onShow(App& app, lv_obj_t* parent) {
}
}
extern const Manifest manifest = {
extern const AppManifest manifest = {
.id = "SelectionDialog",
.name = "Selection Dialog",
.type = TypeHidden,

View File

@ -11,12 +11,12 @@ namespace tt::app::settings {
static void onAppPressed(lv_event_t* e) {
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_CLICKED) {
const auto* manifest = static_cast<const Manifest*>(lv_event_get_user_data(e));
const auto* manifest = static_cast<const AppManifest*>(lv_event_get_user_data(e));
service::loader::startApp(manifest->id);
}
}
static void createWidget(const Manifest* manifest, void* parent) {
static void createWidget(const AppManifest* manifest, void* parent) {
tt_check(parent);
auto* list = (lv_obj_t*)parent;
const void* icon = !manifest->icon.empty() ? manifest->icon.c_str() : TT_ASSETS_APP_ICON_FALLBACK;
@ -24,7 +24,7 @@ static void createWidget(const Manifest* manifest, void* parent) {
lv_obj_add_event_cb(btn, &onAppPressed, LV_EVENT_CLICKED, (void*)manifest);
}
static void onShow(App& app, lv_obj_t* parent) {
static void onShow(AppContext& app, lv_obj_t* parent) {
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lvgl::toolbar_create(parent, app);
@ -42,7 +42,7 @@ static void onShow(App& app, lv_obj_t* parent) {
}
}
extern const Manifest manifest = {
extern const AppManifest manifest = {
.id = "Settings",
.name = "Settings",
.icon = TT_ASSETS_APP_ICON_SETTINGS,

View File

@ -108,7 +108,7 @@ static void addRtosTasks(lv_obj_t* parent) {
#endif
static void onShow(App& app, lv_obj_t* parent) {
static void onShow(AppContext& app, lv_obj_t* parent) {
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lvgl::toolbar_create(parent, app);
@ -151,7 +151,7 @@ static void onShow(App& app, lv_obj_t* parent) {
#endif
}
extern const Manifest manifest = {
extern const AppManifest manifest = {
.id = "SystemInfo",
.name = "System Info",
.icon = TT_ASSETS_APP_ICON_SYSTEM_INFO,

View File

@ -1,4 +1,4 @@
#include "Log.h"
#include <TactilityCore.h>
#include "TextViewer.h"
#include "lvgl.h"
#include "lvgl/LabelUtils.h"
@ -9,7 +9,7 @@
namespace tt::app::textviewer {
static void onShow(App& app, lv_obj_t* parent) {
static void onShow(AppContext& app, lv_obj_t* parent) {
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lvgl::toolbar_create(parent, app);
@ -22,9 +22,10 @@ static void onShow(App& app, lv_obj_t* parent) {
lv_obj_t* label = lv_label_create(wrapper);
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
const Bundle& bundle = app.getParameters();
auto parameters = app.getParameters();
tt_check(parameters != nullptr, "No parameters");
std::string file_argument;
if (bundle.optString(TEXT_VIEWER_FILE_ARGUMENT, file_argument)) {
if (parameters->optString(TEXT_VIEWER_FILE_ARGUMENT, file_argument)) {
TT_LOG_I(TAG, "Opening %s", file_argument.c_str());
lvgl::label_set_text_file(label, file_argument.c_str());
} else {
@ -32,7 +33,7 @@ static void onShow(App& app, lv_obj_t* parent) {
}
}
extern const Manifest manifest = {
extern const AppManifest manifest = {
.id = "TextViewer",
.name = "Text Viewer",
.type = TypeHidden,

View File

@ -1,6 +1,6 @@
#include "WifiApSettings.h"
#include "TactilityCore.h"
#include "app/App.h"
#include "app/AppContext.h"
#include "lvgl.h"
#include "lvgl/Style.h"
#include "lvgl/Toolbar.h"
@ -11,11 +11,11 @@ namespace tt::app::wifiapsettings {
#define TAG "wifi_ap_settings"
extern const Manifest manifest;
extern const AppManifest manifest;
void start(const std::string& ssid) {
Bundle bundle;
bundle.putString("ssid", ssid);
auto bundle = std::shared_ptr<Bundle>(new Bundle());
bundle->putString("ssid", ssid);
service::loader::startApp(manifest.id, false, bundle);
}
@ -39,9 +39,10 @@ static void onToggleAutoConnect(lv_event_t* event) {
}
}
static void onShow(App& app, lv_obj_t* parent) {
const Bundle& bundle = app.getParameters();
std::string ssid = bundle.getString("ssid");
static void onShow(AppContext& app, lv_obj_t* parent) {
auto paremeters = app.getParameters();
tt_check(paremeters != nullptr, "No parameters");
std::string ssid = paremeters->getString("ssid");
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lvgl::toolbar_create(parent, ssid);
@ -69,7 +70,7 @@ static void onShow(App& app, lv_obj_t* parent) {
lv_obj_align(auto_connect_label, LV_ALIGN_TOP_LEFT, 0, 6);
lv_obj_t* auto_connect_switch = lv_switch_create(wrapper);
lv_obj_add_event_cb(auto_connect_switch, onToggleAutoConnect, LV_EVENT_VALUE_CHANGED, (void*)&bundle);
lv_obj_add_event_cb(auto_connect_switch, onToggleAutoConnect, LV_EVENT_VALUE_CHANGED, (void*)&paremeters);
lv_obj_align(auto_connect_switch, LV_ALIGN_TOP_RIGHT, 0, 0);
service::wifi::settings::WifiApSettings settings {};
@ -85,7 +86,7 @@ static void onShow(App& app, lv_obj_t* parent) {
}
}
extern const Manifest manifest = {
extern const AppManifest manifest = {
.id = "WifiApSettings",
.name = "Wi-Fi AP Settings",
.icon = LV_SYMBOL_WIFI,

View File

@ -3,12 +3,12 @@
#include "Parameters.h"
#include "WifiConnect.h"
#include "Log.h"
#include "lvgl.h"
#include "service/gui/Gui.h"
#include "service/wifi/WifiSettings.h"
#include "lvgl/Style.h"
#include "lvgl/Toolbar.h"
#include <TactilityCore.h>
namespace tt::app::wificonnect {
@ -107,7 +107,7 @@ void View::createBottomButtons(WifiConnect* wifi, lv_obj_t* parent) {
}
// TODO: Standardize dialogs
void View::init(App& app, WifiConnect* wifiConnect, lv_obj_t* parent) {
void View::init(AppContext& app, WifiConnect* wifiConnect, lv_obj_t* parent) {
lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
lvgl::toolbar_create(parent, app);
@ -187,15 +187,17 @@ void View::init(App& app, WifiConnect* wifiConnect, lv_obj_t* parent) {
service::gui::keyboardAddTextArea(password_textarea);
// Init from app parameters
const Bundle& bundle = app.getParameters();
std::string ssid;
if (bundle.optString(WIFI_CONNECT_PARAM_SSID, ssid)) {
lv_textarea_set_text(ssid_textarea, ssid.c_str());
}
auto bundle = app.getParameters();
if (bundle != nullptr) {
std::string ssid;
if (bundle->optString(WIFI_CONNECT_PARAM_SSID, ssid)) {
lv_textarea_set_text(ssid_textarea, ssid.c_str());
}
std::string password;
if (bundle.optString(WIFI_CONNECT_PARAM_PASSWORD, password)) {
lv_textarea_set_text(password_textarea, password.c_str());
std::string password;
if (bundle->optString(WIFI_CONNECT_PARAM_PASSWORD, password)) {
lv_textarea_set_text(password_textarea, password.c_str());
}
}
}

View File

@ -3,7 +3,7 @@
#include "Bindings.h"
#include "State.h"
#include "app/App.h"
#include "app/AppContext.h"
#include "lvgl.h"
namespace tt::app::wificonnect {
@ -25,7 +25,7 @@ public:
lv_obj_t* connection_error = nullptr;
lv_group_t* group = nullptr;
void init(App& app, WifiConnect* wifiConnect, lv_obj_t* parent);
void init(AppContext& app, WifiConnect* wifiConnect, lv_obj_t* parent);
void update(Bindings* bindings, State* state);
void createBottomButtons(WifiConnect* wifi, lv_obj_t* parent);

View File

@ -1,6 +1,6 @@
#include "WifiConnect.h"
#include "app/App.h"
#include "app/AppContext.h"
#include "TactilityCore.h"
#include "service/loader/Loader.h"
#include "service/wifi/Wifi.h"
@ -10,6 +10,18 @@ namespace tt::app::wificonnect {
#define TAG "wifi_connect"
extern const AppManifest manifest;
/** Returns the app data if the app is active. Note that this could clash if the same app is started twice and a background thread is slow. */
std::shared_ptr<WifiConnect> _Nullable optWifiConnect() {
app::AppContext* app = service::loader::getCurrentApp();
if (app->getManifest().id == manifest.id) {
return std::static_pointer_cast<WifiConnect>(app->getData());
} else {
return nullptr;
}
}
static void eventCallback(const void* message, void* context) {
auto* event = static_cast<const service::wifi::WifiEvent*>(message);
auto* wifi = static_cast<WifiConnect*>(context);
@ -76,7 +88,7 @@ void WifiConnect::requestViewUpdate() {
unlock();
}
void WifiConnect::onShow(App& app, lv_obj_t* parent) {
void WifiConnect::onShow(AppContext& app, lv_obj_t* parent) {
lock();
view_enabled = true;
view.init(app, this, parent);
@ -84,41 +96,35 @@ void WifiConnect::onShow(App& app, lv_obj_t* parent) {
unlock();
}
void WifiConnect::onHide(App& app) {
void WifiConnect::onHide(AppContext& app) {
// No need to lock view, as this is called from within Gui's LVGL context
lock();
view_enabled = false;
unlock();
}
static void onShow(App& app, lv_obj_t* parent) {
auto* wifi = static_cast<WifiConnect*>(app.getData());
static void onShow(AppContext& app, lv_obj_t* parent) {
auto wifi = std::static_pointer_cast<WifiConnect>(app.getData());
wifi->onShow(app, parent);
}
static void onHide(App& app) {
auto* wifi = static_cast<WifiConnect*>(app.getData());
static void onHide(AppContext& app) {
auto wifi = std::static_pointer_cast<WifiConnect>(app.getData());
wifi->onHide(app);
}
static void onStart(App& app) {
auto* wifi_connect = new WifiConnect();
app.setData(wifi_connect);
static void onStart(AppContext& app) {
auto wifi = std::shared_ptr<WifiConnect>(new WifiConnect());
app.setData(wifi);
}
static void onStop(App& app) {
auto* wifi_connect = static_cast<WifiConnect*>(app.getData());
tt_assert(wifi_connect != nullptr);
delete wifi_connect;
}
extern const Manifest manifest = {
extern const AppManifest manifest = {
.id = "WifiConnect",
.name = "Wi-Fi Connect",
.icon = LV_SYMBOL_WIFI,
.type = TypeSettings,
.onStart = &onStart,
.onStop = &onStop,
.onShow = &onShow,
.onHide = &onHide
};

View File

@ -28,8 +28,8 @@ public:
void lock();
void unlock();
void onShow(App& app, lv_obj_t* parent);
void onHide(App& app);
void onShow(AppContext& app, lv_obj_t* parent);
void onHide(AppContext& app);
State& getState() { return state; }
Bindings& getBindings() { return bindings; }

View File

@ -214,7 +214,7 @@ void View::updateEnableOnBootToggle() {
// region Main
void View::init(const App& app, Bindings* bindings, lv_obj_t* parent) {
void View::init(const AppContext& app, Bindings* bindings, lv_obj_t* parent) {
root = parent;
// Toolbar

View File

@ -1,6 +1,6 @@
#pragma once
#include "app/App.h"
#include "app/AppContext.h"
#include "Bindings.h"
#include "State.h"
#include "lvgl.h"
@ -16,7 +16,7 @@ private:
lv_obj_t* networks_list = nullptr;
public:
View() {}
void init(const App& app, Bindings* bindings, lv_obj_t* parent);
void init(const AppContext& app, Bindings* bindings, lv_obj_t* parent);
void update(Bindings* bindings, State* state);
private:

View File

@ -2,7 +2,7 @@
#include "View.h"
#include "State.h"
#include "app/App.h"
#include "app/AppContext.h"
#include "app/wificonnect/Parameters.h"
#include "app/wifiapsettings/WifiApSettings.h"
#include "TactilityCore.h"
@ -21,9 +21,9 @@ static void onConnect(const char* ssid) {
service::wifi::connect(&settings, false);
} else {
TT_LOG_I(TAG, "Starting connection dialog");
Bundle bundle;
bundle.putString(WIFI_CONNECT_PARAM_SSID, ssid);
bundle.putString(WIFI_CONNECT_PARAM_PASSWORD, "");
auto bundle = std::shared_ptr<Bundle>(new Bundle());
bundle->putString(WIFI_CONNECT_PARAM_SSID, ssid);
bundle->putString(WIFI_CONNECT_PARAM_PASSWORD, "");
service::loader::startApp("WifiConnect", false, bundle);
}
}
@ -95,7 +95,7 @@ static void wifiManageEventCallback(const void* message, void* context) {
wifi->requestViewUpdate();
}
void WifiManage::onShow(App& app, lv_obj_t* parent) {
void WifiManage::onShow(AppContext& app, lv_obj_t* parent) {
PubSub* wifi_pubsub = service::wifi::getPubsub();
wifiSubscription = tt_pubsub_subscribe(wifi_pubsub, &wifiManageEventCallback, this);
@ -121,7 +121,7 @@ void WifiManage::onShow(App& app, lv_obj_t* parent) {
}
}
void WifiManage::onHide(TT_UNUSED App& app) {
void WifiManage::onHide(TT_UNUSED AppContext& app) {
lock();
PubSub* wifi_pubsub = service::wifi::getPubsub();
tt_pubsub_unsubscribe(wifi_pubsub, wifiSubscription);
@ -132,37 +132,29 @@ void WifiManage::onHide(TT_UNUSED App& app) {
// region Manifest methods
static void onStart(App& app) {
auto* wifi = new WifiManage();
static void onStart(AppContext& app) {
auto wifi = std::shared_ptr<WifiManage>(new WifiManage());
app.setData(wifi);
}
static void onStop(App& app) {
auto* wifi = (WifiManage*)app.getData();
tt_assert(wifi != nullptr);
delete wifi;
}
static void onShow(App& app, lv_obj_t* parent) {
auto* wifi = (WifiManage*)app.getData();
static void onShow(AppContext& app, lv_obj_t* parent) {
auto wifi = std::static_pointer_cast<WifiManage>(app.getData());
wifi->onShow(app, parent);
}
static void onHide(App& app) {
auto* wifi = (WifiManage*)app.getData();
static void onHide(AppContext& app) {
auto wifi = std::static_pointer_cast<WifiManage>(app.getData());
wifi->onHide(app);
}
// endregion
extern const Manifest manifest = {
extern const AppManifest manifest = {
.id = "WifiManage",
.name = "Wi-Fi",
.icon = LV_SYMBOL_WIFI,
.type = TypeSettings,
.onStart = onStart,
.onStop = onStop,
.onShow = onShow,
.onHide = onHide
};

View File

@ -23,8 +23,8 @@ public:
void lock();
void unlock();
void onShow(App& app, lv_obj_t* parent);
void onHide(App& app);
void onShow(AppContext& app, lv_obj_t* parent);
void onHide(AppContext& app);
State& getState() { return state; }

View File

@ -1,7 +1,7 @@
#pragma once
#include "lvgl.h"
#include "app/App.h"
#include "app/AppContext.h"
namespace tt::lvgl {

View File

@ -91,7 +91,7 @@ lv_obj_t* toolbar_create(lv_obj_t* parent, const std::string& title) {
return obj;
}
lv_obj_t* toolbar_create(lv_obj_t* parent, const app::App& app) {
lv_obj_t* toolbar_create(lv_obj_t* parent, const app::AppContext& app) {
return toolbar_create(parent, app.getManifest().name);
}

View File

@ -1,7 +1,7 @@
#pragma once
#include "lvgl.h"
#include "app/App.h"
#include "app/AppContext.h"
namespace tt::lvgl {
@ -19,7 +19,7 @@ typedef struct {
} ToolbarAction;
lv_obj_t* toolbar_create(lv_obj_t* parent, const std::string& title);
lv_obj_t* toolbar_create(lv_obj_t* parent, const app::App& app);
lv_obj_t* toolbar_create(lv_obj_t* parent, const app::AppContext& app);
void toolbar_set_title(lv_obj_t* obj, const std::string& title);
void toolbar_set_nav_action(lv_obj_t* obj, const char* icon, lv_event_cb_t callback, void* user_data);
uint8_t toolbar_add_action(lv_obj_t* obj, const char* icon, lv_event_cb_t callback, void* user_data);

View File

@ -18,8 +18,8 @@ Gui* gui = nullptr;
void loader_callback(const void* message, TT_UNUSED void* context) {
auto* event = static_cast<const loader::LoaderEvent*>(message);
if (event->type == loader::LoaderEventTypeApplicationShowing) {
app::App& app = event->app_showing.app;
const app::Manifest& app_manifest = app.getManifest();
app::AppContext& app = event->app_showing.app;
const app::AppManifest& app_manifest = app.getManifest();
showApp(app, app_manifest.onShow, app_manifest.onHide);
} else if (event->type == loader::LoaderEventTypeApplicationHiding) {
hideApp();
@ -77,7 +77,7 @@ void requestDraw() {
thread_flags_set(thread_id, GUI_THREAD_FLAG_DRAW);
}
void showApp(app::App& app, ViewPortShowCallback on_show, ViewPortHideCallback on_hide) {
void showApp(app::AppContext& app, ViewPortShowCallback on_show, ViewPortHideCallback on_hide) {
lock();
tt_check(gui->app_view_port == nullptr);
gui->app_view_port = view_port_alloc(app, on_show, on_hide);
@ -128,14 +128,14 @@ static int32_t gui_main(TT_UNUSED void* p) {
// region AppManifest
static void start(TT_UNUSED Service& service) {
static void start(TT_UNUSED ServiceContext& service) {
gui = gui_alloc();
gui->thread->setPriority(THREAD_PRIORITY_SERVICE);
gui->thread->start();
}
static void stop(TT_UNUSED Service& service) {
static void stop(TT_UNUSED ServiceContext& service) {
lock();
ThreadId thread_id = gui->thread->getId();
@ -148,7 +148,7 @@ static void stop(TT_UNUSED Service& service) {
gui_free(gui);
}
extern const Manifest manifest = {
extern const ServiceManifest manifest = {
.id = "Gui",
.onStart = &start,
.onStop = &stop

View File

@ -1,6 +1,6 @@
#pragma once
#include "app/App.h"
#include "app/AppContext.h"
#include "ViewPort.h"
namespace tt::service::gui {
@ -14,7 +14,7 @@ typedef struct Gui Gui;
* @param on_show
* @param on_hide
*/
void showApp(app::App& app, ViewPortShowCallback on_show, ViewPortHideCallback on_hide);
void showApp(app::AppContext& app, ViewPortShowCallback on_show, ViewPortHideCallback on_hide);
/**
* Hide the current app's viewport.

View File

@ -9,7 +9,7 @@ namespace tt::service::gui {
#define TAG "gui"
static lv_obj_t* create_app_views(Gui* gui, lv_obj_t* parent, app::App& app) {
static lv_obj_t* create_app_views(Gui* gui, lv_obj_t* parent, app::AppContext& app) {
lvgl::obj_set_style_bg_blacken(parent);
lv_obj_t* vertical_container = lv_obj_create(parent);
@ -49,7 +49,7 @@ void redraw(Gui* gui) {
ViewPort* view_port = gui->app_view_port;
if (view_port != nullptr) {
app::App& app = view_port->app;
app::AppContext& app = view_port->app;
lv_obj_t* container = create_app_views(gui, gui->lvgl_parent, app);
view_port_show(view_port, container);
} else {

View File

@ -9,7 +9,7 @@ namespace tt::service::gui {
#define TAG "viewport"
ViewPort* view_port_alloc(
app::App& app,
app::AppContext& app,
ViewPortShowCallback on_show,
ViewPortHideCallback on_hide
) {

View File

@ -1,6 +1,6 @@
#pragma once
#include "app/App.h"
#include "app/AppContext.h"
#include "lvgl.h"
namespace tt::service::gui {
@ -8,18 +8,18 @@ namespace tt::service::gui {
/** ViewPort Draw callback
* @warning called from GUI thread
*/
typedef void (*ViewPortShowCallback)(app::App& app, lv_obj_t* parent);
typedef void (*ViewPortHideCallback)(app::App& app);
typedef void (*ViewPortShowCallback)(app::AppContext& app, lv_obj_t* parent);
typedef void (*ViewPortHideCallback)(app::AppContext& app);
// TODO: Move internally, use handle publicly
typedef struct ViewPort {
app::App& app;
app::AppContext& app;
ViewPortShowCallback onShow;
ViewPortHideCallback _Nullable onHide;
ViewPort(
app::App& app,
app::AppContext& app,
ViewPortShowCallback on_show,
ViewPortHideCallback _Nullable on_hide
) : app(app), onShow(on_show), onHide(on_hide) {}
@ -35,7 +35,7 @@ typedef struct ViewPort {
* @return ViewPort instance
*/
ViewPort* view_port_alloc(
app::App& app,
app::AppContext& app,
ViewPortShowCallback on_show,
ViewPortHideCallback on_hide
);

View File

@ -1,8 +1,8 @@
#include "Tactility.h"
#include <Mutex.h>
#include "app/Manifest.h"
#include "app/AppManifest.h"
#include "app/ManifestRegistry.h"
#include "service/Manifest.h"
#include "service/ServiceManifest.h"
#include "service/gui/Gui.h"
#include "service/loader/Loader_i.h"
#include "RtosCompat.h"
@ -63,7 +63,7 @@ static void loader_unlock() {
tt_check(tt_mutex_release(loader_singleton->mutex) == TtStatusOk);
}
LoaderStatus startApp(const std::string& id, bool blocking, const Bundle& arguments) {
LoaderStatus startApp(const std::string& id, bool blocking, std::shared_ptr<const Bundle> parameters) {
TT_LOG_I(TAG, "Start app %s", id.c_str());
tt_assert(loader_singleton);
@ -71,7 +71,7 @@ LoaderStatus startApp(const std::string& id, bool blocking, const Bundle& argume
.value = LoaderStatusOk
};
auto* start_message = new LoaderMessageAppStart(id, arguments);
auto* start_message = new LoaderMessageAppStart(id, parameters);
LoaderMessage message(start_message, result);
EventFlag* event_flag = blocking ? new EventFlag() : nullptr;
@ -99,12 +99,12 @@ void stopApp() {
loader_singleton->queue.put(&message, TtWaitForever);
}
app::App* _Nullable getCurrentApp() {
app::AppContext* _Nullable getCurrentApp() {
tt_assert(loader_singleton);
loader_lock();
app::AppInstance* app = loader_singleton->app_stack.top();
loader_unlock();
return dynamic_cast<app::App*>(app);
return dynamic_cast<app::AppContext*>(app);
}
PubSub* getPubsub() {
@ -133,7 +133,7 @@ static const char* app_state_to_string(app::State state) {
}
static void app_transition_to_state(app::AppInstance& app, app::State state) {
const app::Manifest& manifest = app.getManifest();
const app::AppManifest& manifest = app.getManifest();
const app::State old_state = app.getState();
TT_LOG_I(
@ -187,15 +187,15 @@ static void app_transition_to_state(app::AppInstance& app, app::State state) {
}
static LoaderStatus loader_do_start_app_with_manifest(
const app::Manifest* manifest,
const Bundle& bundle
const app::AppManifest* manifest,
std::shared_ptr<const Bundle> _Nullable parameters
) {
TT_LOG_I(TAG, "start with manifest %s", manifest->id.c_str());
loader_lock();
auto previous_app = !loader_singleton->app_stack.empty() ? loader_singleton->app_stack.top() : nullptr;
auto new_app = new app::AppInstance(*manifest, bundle);
auto new_app = new app::AppInstance(*manifest, parameters);
new_app->mutableFlags().showStatusbar = (manifest->type != app::TypeBoot);
loader_singleton->app_stack.push(new_app);
@ -227,16 +227,16 @@ static LoaderStatus loader_do_start_app_with_manifest(
static LoaderStatus do_start_by_id(
const std::string& id,
const Bundle& bundle
std::shared_ptr<const Bundle> _Nullable parameters
) {
TT_LOG_I(TAG, "Start by id %s", id.c_str());
const app::Manifest* manifest = app::findAppById(id);
const app::AppManifest* manifest = app::findAppById(id);
if (manifest == nullptr) {
TT_LOG_E(TAG, "App not found: %s", id.c_str());
return LoaderStatusErrorUnknownApp;
} else {
return loader_do_start_app_with_manifest(manifest, bundle);
return loader_do_start_app_with_manifest(manifest, parameters);
}
}
@ -263,7 +263,7 @@ static void do_stop_app() {
std::unique_ptr<app::ResultHolder> result_holder = std::move(app_to_stop->getResult());
const app::Manifest& manifest = app_to_stop->getManifest();
const app::AppManifest& manifest = app_to_stop->getManifest();
app_transition_to_state(*app_to_stop, app::StateHiding);
app_transition_to_state(*app_to_stop, app::StateStopped);
@ -283,7 +283,7 @@ static void do_stop_app() {
auto on_result = app_to_resume->getManifest().onResult;
if (on_result != nullptr) {
if (result_holder != nullptr) {
Bundle* result_bundle = result_holder->resultData;
auto result_bundle = result_holder->resultData.get();
if (result_bundle != nullptr) {
on_result(
*app_to_resume,
@ -335,7 +335,7 @@ static int32_t loader_main(TT_UNUSED void* parameter) {
case LoaderMessageTypeAppStart:
message.result.status_value.value = do_start_by_id(
message.payload.start->id,
message.payload.start->bundle
message.payload.start->parameters
);
if (message.api_lock != nullptr) {
message.api_lock->set(TT_API_LOCK_EVENT);
@ -359,7 +359,7 @@ static int32_t loader_main(TT_UNUSED void* parameter) {
// region AppManifest
static void loader_start(TT_UNUSED Service& service) {
static void loader_start(TT_UNUSED ServiceContext& service) {
tt_check(loader_singleton == nullptr);
loader_singleton = loader_alloc();
@ -367,7 +367,7 @@ static void loader_start(TT_UNUSED Service& service) {
loader_singleton->thread->start();
}
static void loader_stop(TT_UNUSED Service& service) {
static void loader_stop(TT_UNUSED ServiceContext& service) {
tt_check(loader_singleton != nullptr);
// Send stop signal to thread and wait for thread to finish
@ -383,7 +383,7 @@ static void loader_stop(TT_UNUSED Service& service) {
loader_singleton = nullptr;
}
extern const Manifest manifest = {
extern const ServiceManifest manifest = {
.id = "Loader",
.onStart = &loader_start,
.onStop = &loader_stop

View File

@ -1,9 +1,10 @@
#pragma once
#include "app/Manifest.h"
#include "app/AppManifest.h"
#include "Bundle.h"
#include "Pubsub.h"
#include "service/Manifest.h"
#include "service/ServiceManifest.h"
#include <memory>
namespace tt::service::loader {
@ -21,17 +22,17 @@ typedef enum {
* @brief Start an app
* @param[in] id application name or id
* @param[in] blocking whether this call is blocking or not. You cannot call this from an LVGL thread.
* @param[in] arguments optional parameters to pass onto the application
* @param[in] parameters optional parameters to pass onto the application
* @return LoaderStatus
*/
LoaderStatus startApp(const std::string& id, bool blocking = false, const Bundle& arguments = Bundle());
LoaderStatus startApp(const std::string& id, bool blocking = false, std::shared_ptr<const Bundle> _Nullable parameters = nullptr);
/**
* @brief Stop the currently showing app. Show the previous app if any app was still running.
*/
void stopApp();
app::App* _Nullable getCurrentApp();
app::AppContext* _Nullable getCurrentApp();
/**
* @brief PubSub for LoaderEvent

View File

@ -3,7 +3,7 @@
#include "Mutex.h"
#include "ScreenshotTask.h"
#include "service/Service.h"
#include "service/ServiceContext.h"
#include "service/ServiceRegistry.h"
#include "TactilityCore.h"
@ -11,7 +11,7 @@ namespace tt::service::screenshot {
#define TAG "screenshot_service"
extern const Manifest manifest;
extern const ServiceManifest manifest;
typedef struct {
Mutex* mutex;
@ -41,12 +41,12 @@ static void service_data_unlock(ServiceData* data) {
tt_check(tt_mutex_release(data->mutex) == TtStatusOk);
}
static void on_start(Service& service) {
static void on_start(ServiceContext& service) {
ServiceData* data = service_data_alloc();
service.setData(data);
}
static void on_stop(Service& service) {
static void on_stop(ServiceContext& service) {
auto* data = static_cast<ServiceData*>(service.getData());
if (data->task) {
task::free(data->task);
@ -95,7 +95,7 @@ void startTimed(const char* path, uint8_t delay_in_seconds, uint8_t amount) {
}
void stop() {
_Nullable Service* service = findServiceById(manifest.id);
_Nullable ServiceContext* service = findServiceById(manifest.id);
if (service == nullptr) {
TT_LOG_E(TAG, "Service not found");
return;
@ -132,7 +132,7 @@ bool isStarted() {
return getMode() != ScreenshotModeNone;
}
extern const Manifest manifest = {
extern const ServiceManifest manifest = {
.id = "Screenshot",
.onStart = &on_start,
.onStop = &on_stop

View File

@ -1,7 +1,7 @@
#include "ScreenshotTask.h"
#include "lv_screenshot.h"
#include "app/App.h"
#include "app/AppContext.h"
#include "Mutex.h"
#include "TactilityCore.h"
#include "Thread.h"
@ -98,9 +98,9 @@ static int32_t screenshot_task(void* context) {
break; // Interrupted loop
}
} else if (data->work.type == TASK_WORK_TYPE_APPS) {
app::App* _Nullable app = loader::getCurrentApp();
app::AppContext* _Nullable app = loader::getCurrentApp();
if (app) {
const app::Manifest& manifest = app->getManifest();
const app::AppManifest& manifest = app->getManifest();
if (manifest.id != last_app_id) {
delay_ms(100);
last_app_id = manifest.id;

View File

@ -2,7 +2,7 @@
#include "hal/Power.h"
#include "hal/sdcard/Sdcard.h"
#include "Mutex.h"
#include "service/Service.h"
#include "service/ServiceContext.h"
#include "service/wifi/Wifi.h"
#include "Tactility.h"
#include "lvgl/Statusbar.h"
@ -184,7 +184,7 @@ int32_t service_main(TT_UNUSED void* parameter) {
return 0;
}
static void on_start(Service& service) {
static void on_start(ServiceContext& service) {
ServiceData* data = service_data_alloc();
service.setData(data);
@ -195,7 +195,7 @@ static void on_start(Service& service) {
data->thread->start();
}
static void on_stop(Service& service) {
static void on_stop(ServiceContext& service) {
auto* data = static_cast<ServiceData*>(service.getData());
// Stop thread
@ -208,7 +208,7 @@ static void on_stop(Service& service) {
service_data_free(data);
}
extern const Manifest manifest = {
extern const ServiceManifest manifest = {
.id = "Statusbar",
.onStart = &on_start,
.onStop = &on_stop

View File

@ -0,0 +1,25 @@
#pragma once
#include "service/ServiceContext.h"
namespace tt::service {
class ServiceInstance : public ServiceContext {
private:
Mutex mutex = Mutex(MutexTypeNormal);
const service::ServiceManifest& manifest;
void* data = nullptr;
public:
explicit ServiceInstance(const service::ServiceManifest& manifest);
~ServiceInstance() override = default;
[[nodiscard]] const service::ServiceManifest& getManifest() const override;
[[nodiscard]] void* getData() const override;
void setData(void* newData) override;
};
}

View File

@ -1,7 +1,7 @@
#include "TactilityHeadless.h"
#include "hal/Configuration.h"
#include "hal/Hal_i.h"
#include "service/Manifest.h"
#include "service/ServiceManifest.h"
#include "service/ServiceRegistry.h"
#ifdef ESP_PLATFORM
@ -12,10 +12,10 @@ namespace tt {
#define TAG "tactility"
namespace service::wifi { extern const Manifest manifest; }
namespace service::sdcard { extern const Manifest manifest; }
namespace service::wifi { extern const ServiceManifest manifest; }
namespace service::sdcard { extern const ServiceManifest manifest; }
static const service::Manifest* const system_services[] = {
static const service::ServiceManifest* const system_services[] = {
&service::sdcard::manifest,
&service::wifi::manifest
};
@ -24,7 +24,7 @@ static const hal::Configuration* hardwareConfig = nullptr;
static void register_and_start_system_services() {
TT_LOG_I(TAG, "Registering and starting system services");
int app_count = sizeof(system_services) / sizeof(service::Manifest*);
int app_count = sizeof(system_services) / sizeof(service::ServiceManifest*);
for (int i = 0; i < app_count; ++i) {
addService(system_services[i]);
tt_check(service::startService(system_services[i]->id));

View File

@ -1,23 +0,0 @@
#include "Service.h"
#include "Manifest.h"
namespace tt::service {
Service::Service(const service::Manifest& manifest) : manifest(manifest) {}
const service::Manifest& Service::getManifest() const { return manifest; }
void* Service::getData() const {
mutex.acquire(TtWaitForever);
void* data_copy = data;
mutex.release();
return data_copy;
}
void Service::setData(void* newData) {
mutex.acquire(TtWaitForever);
data = newData;
mutex.release();
}
} // namespace

View File

@ -1,22 +0,0 @@
#pragma once
#include "Mutex.h"
#include "Manifest.h"
namespace tt::service {
class Service {
private:
Mutex mutex = Mutex(MutexTypeNormal);
const service::Manifest& manifest;
void* data = nullptr;
public:
Service(const service::Manifest& manifest);
[[nodiscard]] const service::Manifest& getManifest() const;
[[nodiscard]] void* getData() const;
void setData(void* newData);
};
} // namespace

View File

@ -0,0 +1,21 @@
#pragma once
#include "Mutex.h"
#include "ServiceManifest.h"
namespace tt::service {
class ServiceContext {
protected:
virtual ~ServiceContext() = default;
public:
[[nodiscard]] virtual const service::ServiceManifest& getManifest() const = 0;
[[nodiscard]] virtual void* getData() const = 0;
virtual void setData(void* newData) = 0;
};
} // namespace

View File

@ -0,0 +1,22 @@
#include "service/ServiceInstance.h"
namespace tt::service {
ServiceInstance::ServiceInstance(const service::ServiceManifest&manifest) : manifest(manifest) {}
const service::ServiceManifest& ServiceInstance::getManifest() const { return manifest; }
void* ServiceInstance::getData() const {
mutex.acquire(TtWaitForever);
void* data_copy = data;
mutex.release();
return data_copy;
}
void ServiceInstance::setData(void* newData) {
mutex.acquire(TtWaitForever);
data = newData;
mutex.release();
}
}

View File

@ -4,12 +4,12 @@
namespace tt::service {
class Service;
class ServiceContext;
typedef void (*ServiceOnStart)(Service& service);
typedef void (*ServiceOnStop)(Service& service);
typedef void (*ServiceOnStart)(ServiceContext& service);
typedef void (*ServiceOnStop)(ServiceContext& service);
typedef struct Manifest {
struct ServiceManifest {
/**
* The identifier by which the app is launched by the system and other apps.
*/
@ -25,6 +25,6 @@ typedef struct Manifest {
*/
const ServiceOnStop onStop = nullptr;
} Manifest;
};
} // namespace

View File

@ -1,8 +1,8 @@
#include "ServiceRegistry.h"
#include "Mutex.h"
#include "Service.h"
#include "Manifest.h"
#include "service/ServiceInstance.h"
#include "service/ServiceManifest.h"
#include "TactilityCore.h"
#include <string>
#include <unordered_map>
@ -11,8 +11,8 @@ namespace tt::service {
#define TAG "service_registry"
typedef std::unordered_map<std::string, const Manifest*> ManifestMap;
typedef std::unordered_map<std::string, Service*> ServiceInstanceMap;
typedef std::unordered_map<std::string, const ServiceManifest*> ManifestMap;
typedef std::unordered_map<std::string, ServiceInstance*> ServiceInstanceMap;
static ManifestMap service_manifest_map;
static ServiceInstanceMap service_instance_map;
@ -20,57 +20,57 @@ static ServiceInstanceMap service_instance_map;
static Mutex manifest_mutex(MutexTypeNormal);
static Mutex instance_mutex(MutexTypeNormal);
void addService(const Manifest* manifest) {
TT_LOG_I(TAG, "adding %s", manifest->id.c_str());
void addService(const ServiceManifest* manifest) {
TT_LOG_I(TAG, "Adding %s", manifest->id.c_str());
manifest_mutex.acquire(TtWaitForever);
service_manifest_map[manifest->id] = manifest;
manifest_mutex.release();
}
const Manifest* _Nullable findManifestId(const std::string& id) {
const ServiceManifest* _Nullable findManifestId(const std::string& id) {
manifest_mutex.acquire(TtWaitForever);
auto iterator = service_manifest_map.find(id);
_Nullable const Manifest * manifest = iterator != service_manifest_map.end() ? iterator->second : nullptr;
_Nullable const ServiceManifest * manifest = iterator != service_manifest_map.end() ? iterator->second : nullptr;
manifest_mutex.release();
return manifest;
}
static Service* _Nullable service_registry_find_instance_by_id(const std::string& id) {
static ServiceInstance* _Nullable service_registry_find_instance_by_id(const std::string& id) {
manifest_mutex.acquire(TtWaitForever);
auto iterator = service_instance_map.find(id);
_Nullable Service* service = iterator != service_instance_map.end() ? iterator->second : nullptr;
_Nullable ServiceInstance* service = iterator != service_instance_map.end() ? iterator->second : nullptr;
manifest_mutex.release();
return service;
}
// TODO: return proper error/status instead of BOOL
// TODO: Return proper error/status instead of BOOL?
bool startService(const std::string& id) {
TT_LOG_I(TAG, "starting %s", id.c_str());
const Manifest* manifest = findManifestId(id);
TT_LOG_I(TAG, "Starting %s", id.c_str());
const ServiceManifest* manifest = findManifestId(id);
if (manifest == nullptr) {
TT_LOG_E(TAG, "manifest not found for service %s", id.c_str());
return false;
}
auto* service = new Service(*manifest);
auto* service = new ServiceInstance(*manifest);
manifest->onStart(*service);
instance_mutex.acquire(TtWaitForever);
service_instance_map[manifest->id] = service;
instance_mutex.release();
TT_LOG_I(TAG, "started %s", id.c_str());
TT_LOG_I(TAG, "Started %s", id.c_str());
return true;
}
_Nullable Service* findServiceById(const std::string& service_id) {
return (Service*)service_registry_find_instance_by_id(service_id);
_Nullable ServiceContext* findServiceById(const std::string& service_id) {
return static_cast<ServiceInstance*>(service_registry_find_instance_by_id(service_id));
}
bool stopService(const std::string& id) {
TT_LOG_I(TAG, "stopping %s", id.c_str());
Service* service = service_registry_find_instance_by_id(id);
TT_LOG_I(TAG, "Stopping %s", id.c_str());
ServiceInstance* service = service_registry_find_instance_by_id(id);
if (service == nullptr) {
TT_LOG_W(TAG, "service not running: %s", id.c_str());
return false;
@ -83,7 +83,7 @@ bool stopService(const std::string& id) {
service_instance_map.erase(id);
instance_mutex.release();
TT_LOG_I(TAG, "stopped %s", id.c_str());
TT_LOG_I(TAG, "Stopped %s", id.c_str());
return true;
}

View File

@ -1,20 +1,20 @@
#pragma once
#include "Manifest.h"
#include "service/ServiceManifest.h"
namespace tt::service {
typedef void (*ManifestCallback)(const Manifest*, void* context);
typedef void (*ManifestCallback)(const ServiceManifest*, void* context);
void initRegistry();
void addService(const Manifest* manifest);
void removeService(const Manifest* manifest);
void addService(const ServiceManifest* manifest);
void removeService(const ServiceManifest* manifest);
bool startService(const std::string& id);
bool stopService(const std::string& id);
const Manifest* _Nullable findManifestId(const std::string& id);
Service* _Nullable findServiceById(const std::string& id);
const ServiceManifest* _Nullable findManifestId(const std::string& id);
ServiceContext* _Nullable findServiceById(const std::string& id);
} // namespace

View File

@ -1,7 +1,7 @@
#include <cstdlib>
#include "Mutex.h"
#include "service/Service.h"
#include "service/ServiceContext.h"
#include "TactilityCore.h"
#include "TactilityHeadless.h"
@ -75,7 +75,7 @@ static int32_t sdcard_task(void* context) {
return 0;
}
static void on_start(Service& service) {
static void on_start(ServiceContext& service) {
if (hal::getConfiguration().sdcard != nullptr) {
ServiceData* data = service_data_alloc();
service.setData(data);
@ -85,7 +85,7 @@ static void on_start(Service& service) {
}
}
static void on_stop(Service& service) {
static void on_stop(ServiceContext& service) {
auto* data = static_cast<ServiceData*>(service.getData());
if (data != nullptr) {
service_data_lock(data);
@ -98,7 +98,7 @@ static void on_stop(Service& service) {
}
}
extern const Manifest manifest = {
extern const ServiceManifest manifest = {
.id = "sdcard",
.onStart = &on_start,
.onStop = &on_stop

View File

@ -6,10 +6,9 @@
#include "Mutex.h"
#include "Check.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "Log.h"
#include "Pubsub.h"
#include "service/Service.h"
#include "service/ServiceContext.h"
#include "WifiSettings.h"
#include <atomic>
#include <cstring>
@ -790,13 +789,13 @@ _Noreturn int32_t wifi_main(TT_UNUSED void* parameter) {
}
}
static void service_start(Service& service) {
static void service_start(ServiceContext& service) {
tt_assert(wifi_singleton == nullptr);
wifi_singleton = new Wifi();
service.setData(wifi_singleton);
}
static void service_stop(Service& service) {
static void service_stop(ServiceContext& service) {
tt_assert(wifi_singleton != nullptr);
WifiRadioState state = wifi_singleton->radio_state;
@ -812,7 +811,7 @@ static void service_stop(Service& service) {
tt_crash("not fully implemented");
}
extern const Manifest manifest = {
extern const ServiceManifest manifest = {
.id = "Wifi",
.onStart = &service_start,
.onStop = &service_stop

View File

@ -7,7 +7,7 @@
#include "MessageQueue.h"
#include "Mutex.h"
#include "Pubsub.h"
#include "service/Service.h"
#include "service/ServiceContext.h"
#include <cstdlib>
#include <cstring>
@ -175,12 +175,12 @@ static void unlock(Wifi* wifi) {
}
static void service_start(TT_UNUSED Service& service) {
static void service_start(TT_UNUSED ServiceContext& service) {
tt_check(wifi == nullptr);
wifi = wifi_alloc();
}
static void service_stop(TT_UNUSED Service& service) {
static void service_stop(TT_UNUSED ServiceContext& service) {
tt_check(wifi != nullptr);
wifi_free(wifi);
@ -191,7 +191,7 @@ void wifi_main(void*) {
// NO-OP
}
extern const Manifest manifest = {
extern const ServiceManifest manifest = {
.id = "Wifi",
.onStart = &service_start,
.onStop = &service_stop