Merge develop into main (#167)

- WiFi Connect app is now hidden by default, but accessible at the bottom of the WiFi Manage app when WiFi is turned on.
- WiFi service now turns on WiFi when calling connect() and WiFi is not on.
- Removed `blocking` option for `service::loader::startApp()`. This feature was unused and complex.
- Various apps: Moved private headers into Private/ folder.
- Various apps: created start() function for easy starting.
- Added documentation to all TactilityC APIs
- Refactored various `enum` into `class enum`
- Refactor M5Stack `initBoot()` (but VBus is still 0V for some reason)
This commit is contained in:
Ken Van Hoeylandt
2025-01-17 19:37:42 +01:00
committed by GitHub
parent 3ca0f8cf97
commit 3ea02d912f
147 changed files with 1538 additions and 739 deletions
+1 -1
View File
@@ -37,7 +37,7 @@ class AppInstance : public AppContext {
private:
Mutex mutex = Mutex(Mutex::TypeNormal);
Mutex mutex = Mutex(Mutex::Type::Normal);
const AppManifest& manifest;
State state = StateInitial;
Flags flags = { .showStatusbar = true };
@@ -0,0 +1,5 @@
#pragma once
#include <cstdio>
bool getQrVersionForBinaryDataLength(size_t inLength, int& outVersion);
@@ -0,0 +1,5 @@
#pragma once
#include <string>
std::string getUrlFromCrashData();
+64
View File
@@ -0,0 +1,64 @@
#pragma once
#include <dirent.h>
#include <string>
#include <vector>
namespace tt::app::files {
/** File types for `dirent`'s `d_type`. */
enum {
TT_DT_UNKNOWN = 0,
#define TT_DT_UNKNOWN TT_DT_UNKNOWN // Unknown type
TT_DT_FIFO = 1,
#define TT_DT_FIFO TT_DT_FIFO // Named pipe or FIFO
TT_DT_CHR = 2,
#define TT_DT_CHR TT_DT_CHR // Character device
TT_DT_DIR = 4,
#define TT_DT_DIR TT_DT_DIR // Directory
TT_DT_BLK = 6,
#define TT_DT_BLK TT_DT_BLK // Block device
TT_DT_REG = 8,
#define TT_DT_REG TT_DT_REG // Regular file
TT_DT_LNK = 10,
#define TT_DT_LNK TT_DT_LNK // Symbolic link
TT_DT_SOCK = 12,
#define TT_DT_SOCK TT_DT_SOCK // Local-domain socket
TT_DT_WHT = 14
#define TT_DT_WHT TT_DT_WHT // Whiteout inodes
};
std::string getChildPath(const std::string& basePath, const std::string& childPath);
typedef int (*ScandirFilter)(const struct dirent*);
typedef bool (*ScandirSort)(const struct dirent&, const struct dirent&);
bool dirent_sort_alpha_and_type(const struct dirent& left, const struct dirent& right);
int dirent_filter_dot_entries(const struct dirent* entry);
/**
* A scandir()-like implementation that works on ESP32.
* It does not return "." and ".." items but otherwise functions the same.
* It returns an allocated output array with allocated dirent instances.
* The caller is responsible for free-ing the memory of these.
*
* @param[in] path path the scan for files and directories
* @param[out] outList a pointer to vector of dirent
* @param[in] filter an optional filter to filter out specific items
* @param[in] sort an optional sorting function
* @return the amount of items that were stored in "output" or -1 when an error occurred
*/
int scandir(
const std::string& path,
std::vector<dirent>& outList,
ScandirFilter _Nullable filter,
ScandirSort _Nullable sort
);
bool isSupportedExecutableFile(const std::string& filename);
bool isSupportedImageFile(const std::string& filename);
bool isSupportedTextFile(const std::string& filename);
} // namespace
@@ -0,0 +1,34 @@
#pragma once
#include "./View.h"
#include "./State.h"
#include "app/AppManifest.h"
#include <lvgl.h>
#include <dirent.h>
#include <memory>
namespace tt::app::files {
class Files {
std::unique_ptr<View> view;
std::shared_ptr<State> state;
public:
Files() {
state = std::make_shared<State>();
view = std::make_unique<View>(state);
}
void onShow(lv_obj_t* parent) {
view->init(parent);
}
void onResult(Result result, const Bundle& bundle) {
view->onResult(result, bundle);
}
};
} // namespace
+74
View File
@@ -0,0 +1,74 @@
#pragma once
#include <string>
#include <vector>
#include <dirent.h>
#include "Mutex.h"
namespace tt::app::files {
class State {
public:
enum PendingAction {
ActionNone,
ActionDelete,
ActionRename
};
private:
Mutex mutex = Mutex(Mutex::Type::Recursive);
std::vector<dirent> dir_entries;
std::string current_path;
std::string selected_child_entry;
PendingAction action = ActionNone;
public:
State();
void freeEntries() {
dir_entries.clear();
}
~State() {
freeEntries();
}
bool setEntriesForChildPath(const std::string& child_path);
bool setEntriesForPath(const std::string& path);
const std::vector<dirent>& lockEntries() const {
mutex.lock(TtWaitForever);
return dir_entries;
}
void unlockEntries() {
mutex.unlock();
}
bool getDirent(uint32_t index, dirent& dirent);
void setSelectedChildEntry(const std::string& newFile) {
selected_child_entry = newFile;
action = ActionNone;
}
std::string getSelectedChildEntry() const { return selected_child_entry; }
std::string getCurrentPath() const { return current_path; }
std::string getSelectedChildPath() const;
PendingAction getPendingAction() const {
return action;
}
void setPendingAction(PendingAction newAction) {
action = newAction;
}
};
}
+41
View File
@@ -0,0 +1,41 @@
#pragma once
#include "./State.h"
#include "app/AppManifest.h"
#include <lvgl.h>
#include <memory>
namespace tt::app::files {
class View {
std::shared_ptr<State> state;
lv_obj_t* dir_entry_list = nullptr;
lv_obj_t* action_list = nullptr;
lv_obj_t* navigate_up_button = nullptr;
void showActionsForDirectory();
void showActionsForFile();
void viewFile(const std::string&path, const std::string&filename);
void createDirEntryWidget(lv_obj_t* parent, struct dirent& dir_entry);
void onNavigate();
public:
explicit View(const std::shared_ptr<State>& state) : state(state) {}
void init(lv_obj_t* parent);
void update();
void onNavigateUpPressed();
void onDirEntryPressed(uint32_t index);
void onDirEntryLongPressed(int32_t index);
void onRenamePressed();
void onDeletePressed();
void onDirEntryListScrollBegin();
void onResult(Result result, const Bundle& bundle);
};
}
@@ -0,0 +1,12 @@
#pragma once
#include <string>
#include <cstdint>
namespace tt::app::i2cscanner {
std::string getAddressText(uint8_t address);
std::string getPortNamesForDropdown();
}
@@ -0,0 +1,37 @@
#pragma once
#include <TactilityCore.h>
#include <Mutex.h>
#include <Thread.h>
#include "lvgl.h"
#include "hal/i2c/I2c.h"
#include "Timer.h"
#include <memory>
namespace tt::app::i2cscanner {
#define TAG "i2cscanner"
enum ScanState {
ScanStateInitial,
ScanStateScanning,
ScanStateStopped
};
struct Data {
// Core
Mutex mutex = Mutex(Mutex::Type::Recursive);
std::unique_ptr<Timer> scanTimer = nullptr;
// State
ScanState scanState;
i2c_port_t port = I2C_NUM_0;
std::vector<uint8_t> scannedAddresses;
// Widgets
lv_obj_t* scanButtonLabelWidget = nullptr;
lv_obj_t* portDropdownWidget = nullptr;
lv_obj_t* scanListWidget = nullptr;
};
void onScanTimerFinished(std::shared_ptr<Data> data);
}
@@ -0,0 +1,12 @@
#pragma once
#include "./I2cScannerPrivate.h"
#include <memory>
namespace tt::app::i2cscanner {
bool hasScanTimer(std::shared_ptr<Data> data);
void startScanning(std::shared_ptr<Data> data);
void stopScanning(std::shared_ptr<Data> data);
}
@@ -0,0 +1,42 @@
#include "Timer.h"
#include "TactilityConfig.h"
#if TT_FEATURE_SCREENSHOT_ENABLED
#pragma once
#include "app/AppContext.h"
#include "lvgl.h"
namespace tt::app::screenshot {
class ScreenshotUi {
lv_obj_t* modeDropdown = nullptr;
lv_obj_t* pathTextArea = nullptr;
lv_obj_t* startStopButtonLabel = nullptr;
lv_obj_t* timerWrapper = nullptr;
lv_obj_t* delayTextArea = nullptr;
std::unique_ptr<Timer> updateTimer;
void createTimerSettingsWidgets(lv_obj_t* parent);
void createModeSettingWidgets(lv_obj_t* parent);
void createFilePathWidgets(lv_obj_t* parent);
void updateScreenshotMode();
public:
ScreenshotUi();
~ScreenshotUi();
void createWidgets(const AppContext& app, lv_obj_t* parent);
void onStartPressed();
void onModeSet();
void onTimerTick();
};
} // namespace
#endif
@@ -0,0 +1,14 @@
#pragma once
#include "service/wifi/WifiSettings.h"
namespace tt::app::wificonnect {
typedef void (*OnConnectSsid)(const service::wifi::settings::WifiApSettings* settings, bool store, void* context);
typedef struct {
OnConnectSsid onConnectSsid;
void* onConnectSsidContext;
} Bindings;
} // namespace
+32
View File
@@ -0,0 +1,32 @@
#pragma once
#include "Mutex.h"
#include "service/wifi/Wifi.h"
#include "service/wifi/WifiSettings.h"
namespace tt::app::wificonnect {
class State {
Mutex lock;
service::wifi::settings::WifiApSettings apSettings = {
.ssid = { 0 },
.password = { 0 },
.auto_connect = false
};
bool connectionError = false;
bool connecting = false;
public:
void setConnectionError(bool error);
bool hasConnectionError() const;
const service::wifi::settings::WifiApSettings& lockApSettings();
void unlockApSettings();
void setApSettings(const service::wifi::settings::WifiApSettings* newSettings);
void setConnecting(bool isConnecting);
bool isConnecting() const;
};
} // namespace
+46
View File
@@ -0,0 +1,46 @@
#pragma once
#include "Bindings.h"
#include "State.h"
#include "app/AppContext.h"
#include "lvgl.h"
namespace tt::app::wificonnect {
class WifiConnect;
class View {
private:
Bindings* bindings;
State* state;
public:
lv_obj_t* ssid_textarea = nullptr;
lv_obj_t* ssid_error = nullptr;
lv_obj_t* password_textarea = nullptr;
lv_obj_t* password_error = nullptr;
lv_obj_t* connect_button = nullptr;
lv_obj_t* remember_switch = nullptr;
lv_obj_t* connecting_spinner = nullptr;
lv_obj_t* connection_error = nullptr;
lv_group_t* group = nullptr;
View(Bindings* bindings, State* state) :
bindings(bindings),
state(state)
{}
void init(AppContext& app, lv_obj_t* parent);
void update();
void createBottomButtons(lv_obj_t* parent);
void setLoading(bool loading);
void resetErrors();
};
} // namespace
@@ -0,0 +1,45 @@
#pragma once
#include "app/wificonnect/Bindings.h"
#include "app/wificonnect/State.h"
#include "app/wificonnect/View.h"
#include "Mutex.h"
#include "service/wifi/Wifi.h"
namespace tt::app::wificonnect {
class WifiConnect {
Mutex mutex;
State state;
Bindings bindings = {
.onConnectSsid = nullptr,
.onConnectSsidContext = nullptr
};
View view = View(&bindings, &state);
PubSubSubscription* wifiSubscription;
bool view_enabled = false;
public:
WifiConnect();
~WifiConnect();
void lock();
void unlock();
void onShow(AppContext& app, lv_obj_t* parent);
void onHide(AppContext& app);
State& getState() { return state; }
Bindings& getBindings() { return bindings; }
View& getView() { return view; }
void requestViewUpdate();
};
bool optSsidParameter(const std::shared_ptr<const Bundle>& bundle, std::string& ssid);
bool optPasswordParameter(const std::shared_ptr<const Bundle>& bundle, std::string& password);
} // namespace
@@ -0,0 +1,19 @@
#pragma once
namespace tt::app::wifimanage {
typedef void (*OnWifiToggled)(bool enable);
typedef void (*OnConnectSsid)(const char* ssid);
typedef void (*OnDisconnect)();
typedef void (*OnShowApSettings)(const char* ssid);
typedef void (*OnConnectToHidden)();
struct Bindings{
OnWifiToggled onWifiToggled;
OnConnectSsid onConnectSsid;
OnDisconnect onDisconnect;
OnShowApSettings onShowApSettings;
OnConnectToHidden onConnectToHidden;
};
} // namespace
+40
View File
@@ -0,0 +1,40 @@
#pragma once
#include "service/wifi/Wifi.h"
#include "Mutex.h"
namespace tt::app::wifimanage {
/**
* View's state
*/
class State {
Mutex mutex = Mutex(Mutex::Type::Recursive);
bool scanning = false;
bool scannedAfterRadioOn = false;
service::wifi::RadioState radioState;
std::vector<service::wifi::ApRecord> apRecords;
std::string connectSsid;
public:
State() = default;
void setScanning(bool isScanning);
bool isScanning() const;
bool hasScannedAfterRadioOn() const { return scannedAfterRadioOn; }
void setRadioState(service::wifi::RadioState state);
service::wifi::RadioState getRadioState() const;
void updateApRecords();
const std::vector<service::wifi::ApRecord>& lockApRecords() const;
void unlockApRecords() const;
void setConnectSsid(const std::string& ssid);
std::string getConnectSsid() const;
};
} // namespace
+40
View File
@@ -0,0 +1,40 @@
#pragma once
#include "app/AppContext.h"
#include "./Bindings.h"
#include "./State.h"
#include "lvgl.h"
namespace tt::app::wifimanage {
class View {
private:
Bindings* bindings;
State* state;
std::unique_ptr<app::Paths> paths;
lv_obj_t* root = nullptr;
lv_obj_t* enable_switch = nullptr;
lv_obj_t* enable_on_boot_switch = nullptr;
lv_obj_t* scanning_spinner = nullptr;
lv_obj_t* networks_list = nullptr;
lv_obj_t* connect_to_hidden = nullptr;
void updateWifiToggle();
void updateEnableOnBootToggle();
void updateScanning();
void updateNetworkList();
void updateConnectToHidden();
void createSsidListItem(const service::wifi::ApRecord& record, bool isConnecting);
public:
View(Bindings* bindings, State* state) : bindings(bindings), state(state) {}
void init(const AppContext& app, lv_obj_t* parent);
void update();
};
} // namespace
@@ -0,0 +1,35 @@
#pragma once
#include "Mutex.h"
#include "./View.h"
#include "./State.h"
#include "service/wifi/Wifi.h"
namespace tt::app::wifimanage {
class WifiManage {
PubSubSubscription* wifiSubscription = nullptr;
Mutex mutex;
Bindings bindings = { };
State state;
View view = View(&bindings, &state);
bool isViewEnabled = false;
public:
WifiManage();
void lock();
void unlock();
void onShow(AppContext& app, lv_obj_t* parent);
void onHide(AppContext& app);
Bindings& getBindings() { return bindings; }
State& getState() { return state; }
void requestViewUpdate();
};
} // namespace
+1 -1
View File
@@ -19,7 +19,7 @@ namespace tt::service::gui {
struct Gui {
// Thread and lock
Thread* thread = nullptr;
Mutex mutex = Mutex(Mutex::TypeRecursive);
Mutex mutex = Mutex(Mutex::Type::Recursive);
PubSubSubscription* loader_pubsub_subscription = nullptr;
// Layers and Canvas
+1 -14
View File
@@ -57,17 +57,12 @@ typedef struct {
class LoaderMessageAppStart {
public:
// This lock blocks anyone from starting an app as long
// as an app is already running via loader_start()
// This lock's lifecycle is not owned by this class.
std::shared_ptr<EventFlag> api_lock = std::make_shared<EventFlag>();
std::string id;
std::shared_ptr<const Bundle> _Nullable parameters;
LoaderMessageAppStart() = default;
LoaderMessageAppStart(LoaderMessageAppStart& other) :
api_lock(other.api_lock),
id(other.id),
parameters(other.parameters) {}
@@ -77,14 +72,6 @@ public:
{}
~LoaderMessageAppStart() = default;
std::shared_ptr<EventFlag> getApiLockEventFlag() { return api_lock; }
uint32_t getApiLockEventFlagValue() { return 1; }
void onProcessed() {
api_lock->set(1);
}
};
// endregion LoaderMessage
@@ -92,7 +79,7 @@ public:
struct Loader {
std::shared_ptr<PubSub> pubsubInternal = std::make_shared<PubSub>();
std::shared_ptr<PubSub> pubsubExternal = std::make_shared<PubSub>();
Mutex mutex = Mutex(Mutex::TypeRecursive);
Mutex mutex = Mutex(Mutex::Type::Recursive);
std::stack<app::AppInstance*> appStack;
/** The dispatcher thread needs a callstack large enough to accommodate all the dispatched methods.
* This includes full LVGL redraw via Gui::redraw()