Update docs and fix bugs (#149)

Improved the docs for the 3 main Tactility projects. I also fixed some inaccuracies and bugs in certain APIs as I went through the code.
This commit is contained in:
Ken Van Hoeylandt 2025-01-07 20:45:23 +01:00 committed by GitHub
parent ff4287e2ce
commit 415096c3b2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
62 changed files with 503 additions and 517 deletions

View File

@ -18,9 +18,7 @@ void app_main() {
.hardware = TT_BOARD_HARDWARE, .hardware = TT_BOARD_HARDWARE,
.apps = { .apps = {
&hello_world_app, &hello_world_app,
}, }
.services = {},
.autoStartAppId = nullptr
}; };
#ifdef ESP_PLATFORM #ifdef ESP_PLATFORM

View File

@ -41,8 +41,8 @@ bool lvgl_task_is_running() {
return result; return result;
} }
static bool lvgl_lock(uint32_t timeout_ticks) { static bool lvgl_lock(uint32_t timeoutMillis) {
return lvgl_mutex.acquire(timeout_ticks) == tt::TtStatusOk; return lvgl_mutex.acquire(pdMS_TO_TICKS(timeoutMillis)) == tt::TtStatusOk;
} }
static void lvgl_unlock() { static void lvgl_unlock() {

View File

@ -1,4 +1,4 @@
# Bugs # Issues
- WiFi bug: when pressing disconnect while between `WIFI_EVENT_STA_START` and `IP_EVENT_STA_GOT_IP`, then auto-connect becomes active again. - WiFi bug: when pressing disconnect while between `WIFI_EVENT_STA_START` and `IP_EVENT_STA_GOT_IP`, then auto-connect becomes active again.
- ESP32 (CYD) memory issues (or any device without PSRAM): - ESP32 (CYD) memory issues (or any device without PSRAM):
- Boot app doesn't show logo - Boot app doesn't show logo
@ -11,8 +11,17 @@
- Clean up static_cast when casting to base class. - Clean up static_cast when casting to base class.
- M5Stack CoreS3 SD card mounts, but cannot be read. There is currently a notice about it [here](https://github.com/espressif/esp-bsp/blob/master/bsp/m5stack_core_s3/README.md). - M5Stack CoreS3 SD card mounts, but cannot be read. There is currently a notice about it [here](https://github.com/espressif/esp-bsp/blob/master/bsp/m5stack_core_s3/README.md).
- SD card statusbar icon shows error when there's a read timeout on the SD card status. Don't show the error icon in this scenario. - SD card statusbar icon shows error when there's a read timeout on the SD card status. Don't show the error icon in this scenario.
- EventFlag: Fix return value of set/get/wait (the errors are weirdly mixed in)
- getConfiguration() in TT headless is a reference, while in TT main project it's a pointer. Make it a pointer for headless too.
# TODOs # TODOs
- Tactility.h config: List of apps and services can be a std::vector (get rid of TT_CONFIG_SERVICES_LIMIT and TT_CONFIG_APPS_LIMIT)
- Boot hooks instead of a single boot method in config. Define different boot phases/levels in enum.
- Rename "Desktop" to "Launcher" because it more clearly communicates its purpose
- Add toggle to Display app for sysmon overlay: https://docs.lvgl.io/master/API/others/sysmon/index.html
- Mutex: Cleanup deprecated methods
- CrashHandler: use "corrupted" flag
- CrashHandler: process other types of crashes (WDT?)
- Call tt::lvgl::isSyncSet after HAL init and show error (and crash?) when it is not set. - Call tt::lvgl::isSyncSet after HAL init and show error (and crash?) when it is not set.
- Create different partitions files for different ESP flash size targets (N4, N8, N16, N32) - Create different partitions files for different ESP flash size targets (N4, N8, N16, N32)
- Attach ELF data to wrapper app (as app data) (check that app state is "running"!) so you can run more than 1 external apps at a time. - Attach ELF data to wrapper app (as app data) (check that app state is "running"!) so you can run more than 1 external apps at a time.
@ -37,6 +46,8 @@
- Support hot-plugging SD card - Support hot-plugging SD card
# Nice-to-haves # Nice-to-haves
- OTA updates
- Web flasher
- T-Deck Plus: Create separate board config? - T-Deck Plus: Create separate board config?
- Support for displays with different DPI. Consider the layer-based system like on Android. - Support for displays with different DPI. Consider the layer-based system like on Android.
- Make firmwares available via web serial website - Make firmwares available via web serial website

View File

@ -7,17 +7,23 @@
namespace tt { namespace tt {
typedef struct { /** @brief The configuration for the operating system
* It contains the hardware configuration, apps and services
*/
struct Configuration {
/** HAL configuration (drivers) */
const hal::Configuration* hardware; const hal::Configuration* hardware;
// List of user applications /** List of user applications */
const app::AppManifest* const apps[TT_CONFIG_APPS_LIMIT]; const app::AppManifest* const apps[TT_CONFIG_APPS_LIMIT] = {};
const service::ServiceManifest* const services[TT_CONFIG_SERVICES_LIMIT]; /** List of user services */
const char* autoStartAppId; const service::ServiceManifest* const services[TT_CONFIG_SERVICES_LIMIT] = {};
} Configuration; /** Optional app to start automatically after the splash screen. */
const char* _Nullable autoStartAppId = nullptr;
};
/** /**
* Attempts to initialize Tactility and all configured hardware. * Attempts to initialize Tactility and all configured hardware.
* @param config * @param[in] config
*/ */
void run(const Configuration& config); void run(const Configuration& config);

View File

@ -16,8 +16,8 @@ typedef union {
} Flags; } Flags;
/** /**
* A limited representation of the application instance. * The public representation of an application instance.
* Do not store references or pointers to these! * @warning Do not store references or pointers to these! You can retrieve them via the service registry.
*/ */
class AppContext { class AppContext {

View File

@ -11,7 +11,8 @@ namespace tt::app {
class AppContext; class AppContext;
typedef enum { /** Application types */
enum Type {
/** Boot screen, shown before desktop is launched. */ /** Boot screen, shown before desktop is launched. */
TypeBoot, TypeBoot,
/** A desktop app sits at the root of the app stack managed by the Loader service */ /** A desktop app sits at the root of the app stack managed by the Loader service */
@ -24,8 +25,9 @@ typedef enum {
TypeSettings, TypeSettings,
/** User-provided apps. */ /** User-provided apps. */
TypeUser TypeUser
} Type; };
/** Result status code for application result callback. */
typedef enum { typedef enum {
ResultOk, ResultOk,
ResultCancelled, ResultCancelled,
@ -39,49 +41,31 @@ typedef void (*AppOnHide)(AppContext& app);
typedef void (*AppOnResult)(AppContext& app, Result result, const Bundle& resultData); typedef void (*AppOnResult)(AppContext& app, Result result, const Bundle& resultData);
struct AppManifest { struct AppManifest {
/** /** The identifier by which the app is launched by the system and other apps. */
* The identifier by which the app is launched by the system and other apps.
*/
std::string id; std::string id;
/** /** The user-readable name of the app. Used in UI. */
* The user-readable name of the app. Used in UI.
*/
std::string name; std::string name;
/** /** Optional icon. */
* Optional icon.
*/
std::string icon = {}; std::string icon = {};
/** /** App type affects launch behaviour. */
* App type affects launch behaviour.
*/
Type type = TypeUser; Type type = TypeUser;
/** /** Non-blocking method to call when app is started. */
* Non-blocking method to call when app is started.
*/
AppOnStart onStart = nullptr; AppOnStart onStart = nullptr;
/** /** Non-blocking method to call when app is stopped. */
* Non-blocking method to call when app is stopped.
*/
AppOnStop _Nullable onStop = nullptr; AppOnStop _Nullable onStop = nullptr;
/** /** Non-blocking method to create the GUI. */
* Non-blocking method to create the GUI
*/
AppOnShow _Nullable onShow = nullptr; AppOnShow _Nullable onShow = nullptr;
/** /** Non-blocking method, called before gui is destroyed. */
* Non-blocking method, called before gui is destroyed
*/
AppOnHide _Nullable onHide = nullptr; AppOnHide _Nullable onHide = nullptr;
/** /** Handle the result for apps that are launched. */
* Handle the result for apps that are launched
*/
AppOnResult _Nullable onResult = nullptr; AppOnResult _Nullable onResult = nullptr;
}; };

View File

@ -22,7 +22,7 @@ bool startElfApp(const std::string& filePath) {
assert(elfFileData == nullptr); assert(elfFileData == nullptr);
size_t size = 0; size_t size = 0;
elfFileData = file::readBinary(filePath.c_str(), size); elfFileData = file::readBinary(filePath, size);
if (elfFileData == nullptr) { if (elfFileData == nullptr) {
return false; return false;
} }

View File

@ -6,8 +6,16 @@
namespace tt::app { namespace tt::app {
/** Register an application with its manifest */
void addApp(const AppManifest* manifest); void addApp(const AppManifest* manifest);
/** Find an application manifest by its id
* @param[in] id the manifest id
* @return the application manifest if it was found
*/
const AppManifest _Nullable* findAppById(const std::string& id); const AppManifest _Nullable* findAppById(const std::string& id);
/** @return a list of all registered apps. This includes user and system apps. */
std::vector<const AppManifest*> getApps(); std::vector<const AppManifest*> getApps();
} // namespace } // namespace

View File

@ -9,10 +9,10 @@
#include <esp_cpu_utils.h> #include <esp_cpu_utils.h>
std::string getUrlFromCrashData() { std::string getUrlFromCrashData() {
auto* crash_data = getRtcCrashData(); auto crash_data = getRtcCrashData();
auto* stack_buffer = (uint32_t*) malloc(crash_data->callstackLength * 2 * sizeof(uint32_t)); auto* stack_buffer = (uint32_t*) malloc(crash_data.callstackLength * 2 * sizeof(uint32_t));
for (int i = 0; i < crash_data->callstackLength; ++i) { for (int i = 0; i < crash_data.callstackLength; ++i) {
const CallstackFrame&frame = crash_data->callstack[i]; const CallstackFrame&frame = crash_data.callstack[i];
uint32_t pc = esp_cpu_process_stack_pc(frame.pc); uint32_t pc = esp_cpu_process_stack_pc(frame.pc);
#if CRASH_DATA_INCLUDES_SP #if CRASH_DATA_INCLUDES_SP
uint32_t sp = frame.sp; uint32_t sp = frame.sp;
@ -30,7 +30,7 @@ std::string getUrlFromCrashData() {
stream << "&a=" << CONFIG_IDF_TARGET; // Architecture stream << "&a=" << CONFIG_IDF_TARGET; // Architecture
stream << "&s="; // Stacktrace stream << "&s="; // Stacktrace
for (int i = crash_data->callstackLength - 1; i >= 0; --i) { for (int i = crash_data.callstackLength - 1; i >= 0; --i) {
uint32_t pc = stack_buffer[(i * 2)]; uint32_t pc = stack_buffer[(i * 2)];
stream << std::hex << pc; stream << std::hex << pc;
#if CRASH_DATA_INCLUDES_SP #if CRASH_DATA_INCLUDES_SP

View File

@ -3,10 +3,10 @@
namespace tt::lvgl { namespace tt::lvgl {
Mutex lockMutex; static Mutex lockMutex;
static bool defaultLock(uint32_t timeoutTicks) { static bool defaultLock(uint32_t timeoutMillis) {
return lockMutex.acquire(timeoutTicks) == TtStatusOk; return lockMutex.acquire(timeoutMillis) == TtStatusOk;
} }
static void defaultUnlock() { static void defaultUnlock() {
@ -21,8 +21,8 @@ void syncSet(LvglLock lock, LvglUnlock unlock) {
unlock_singleton = unlock; unlock_singleton = unlock;
} }
bool lock(uint32_t timeout_ticks) { bool lock(TickType_t timeout) {
return lock_singleton(timeout_ticks); return lock_singleton(pdMS_TO_TICKS(timeout == 0 ? portMAX_DELAY : timeout));
} }
void unlock() { void unlock() {
@ -33,7 +33,7 @@ class LvglSync : public Lockable {
public: public:
~LvglSync() override = default; ~LvglSync() override = default;
bool lock(uint32_t timeoutTicks) const override { bool lock(TickType_t timeoutTicks) const override {
return tt::lvgl::lock(timeoutTicks); return tt::lvgl::lock(timeoutTicks);
} }

View File

@ -6,12 +6,23 @@
namespace tt::lvgl { namespace tt::lvgl {
typedef bool (*LvglLock)(uint32_t timeout_ticks); /**
* LVGL locking function
* @param[in] timeoutMillis timeout in milliseconds. waits forever when 0 is passed.
* @warning this works with milliseconds, as opposed to every other FreeRTOS function that works in ticks!
* @warning when passing zero, we wait forever, as this is the default behaviour for esp_lvgl_port, and we want it to remain consistent
*/
typedef bool (*LvglLock)(uint32_t timeoutMillis);
typedef void (*LvglUnlock)(); typedef void (*LvglUnlock)();
void syncSet(LvglLock lock, LvglUnlock unlock); void syncSet(LvglLock lock, LvglUnlock unlock);
bool isSyncSet();
bool lock(uint32_t timeout_ticks); /**
* LVGL locking function
* @param[in] timeout as ticks
* @warning when passing zero, we wait forever, as this is the default behaviour for esp_lvgl_port, and we want it to remain consistent
*/
bool lock(TickType_t timeout);
void unlock(); void unlock();
std::shared_ptr<Lockable> getLvglSyncLockable(); std::shared_ptr<Lockable> getLvglSyncLockable();

View File

@ -9,12 +9,11 @@ typedef struct Gui Gui;
/** /**
* Set the app viewport in the gui state and request the gui to draw it. * Set the app viewport in the gui state and request the gui to draw it.
* * @param[in] app
* @param app * @param[in] onShow
* @param on_show * @param[in] onHide
* @param on_hide
*/ */
void showApp(app::AppContext& app, ViewPortShowCallback on_show, ViewPortHideCallback on_hide); void showApp(app::AppContext& app, ViewPortShowCallback onShow, ViewPortHideCallback onHide);
/** /**
* Hide the current app's viewport. * Hide the current app's viewport.
@ -25,7 +24,7 @@ void hideApp();
/** /**
* Show the on-screen keyboard. * Show the on-screen keyboard.
* @param textarea the textarea to focus the input for * @param[in] textarea the textarea to focus the input for
*/ */
void keyboardShow(lv_obj_t* textarea); void keyboardShow(lv_obj_t* textarea);
@ -47,7 +46,7 @@ bool keyboardIsEnabled();
* Glue code for the on-screen keyboard and the hardware keyboard: * Glue code for the on-screen keyboard and the hardware keyboard:
* - Attach automatic hide/show parameters for the on-screen keyboard. * - Attach automatic hide/show parameters for the on-screen keyboard.
* - Registers the textarea to the default lv_group_t for hardware keyboards. * - Registers the textarea to the default lv_group_t for hardware keyboards.
* @param textarea * @param[in] textarea
*/ */
void keyboardAddTextArea(lv_obj_t* textarea); void keyboardAddTextArea(lv_obj_t* textarea);

View File

@ -26,26 +26,22 @@ typedef struct ViewPort {
} ViewPort; } ViewPort;
/** ViewPort allocator /** ViewPort allocator
*
* always returns view_port or stops system if not enough memory. * always returns view_port or stops system if not enough memory.
* @param app * @param app
* @param on_show Called to create LVGL widgets * @param onShow Called to create LVGL widgets
* @param on_hide Called before clearing the LVGL widget parent * @param onHide Called before clearing the LVGL widget parent
* * @return ViewPort instance
* @return ViewPort instance
*/ */
ViewPort* view_port_alloc( ViewPort* view_port_alloc(
app::AppContext& app, app::AppContext& app,
ViewPortShowCallback on_show, ViewPortShowCallback onShow,
ViewPortHideCallback on_hide ViewPortHideCallback onHide
); );
/** ViewPort deallocator /** ViewPort destruction
*
* Ensure that view_port was unregistered in GUI system before use. * Ensure that view_port was unregistered in GUI system before use.
* * @param viewPort ViewPort instance
* @param view_port ViewPort instance
*/ */
void view_port_free(ViewPort* view_port); void view_port_free(ViewPort* viewPort);
} // namespace } // namespace

View File

@ -26,11 +26,10 @@ typedef enum {
*/ */
void startApp(const std::string& id, bool blocking = false, std::shared_ptr<const Bundle> _Nullable parameters = nullptr); void 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. */
* @brief Stop the currently showing app. Show the previous app if any app was still running.
*/
void stopApp(); void stopApp();
/** @return the currently running app (it is only ever null before the splash screen is shown) */
app::AppContext* _Nullable getCurrentApp(); app::AppContext* _Nullable getCurrentApp();
/** /**

View File

@ -39,7 +39,7 @@ void ScreenshotService::startApps(const char* path) {
} }
} }
void ScreenshotService::startTimed(const char* path, uint8_t delay_in_seconds, uint8_t amount) { void ScreenshotService::startTimed(const char* path, uint8_t delayInSeconds, uint8_t amount) {
auto scoped_lockable = mutex.scoped(); auto scoped_lockable = mutex.scoped();
if (!scoped_lockable->lock(50 / portTICK_PERIOD_MS)) { if (!scoped_lockable->lock(50 / portTICK_PERIOD_MS)) {
TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED); TT_LOG_W(TAG, LOG_MESSAGE_MUTEX_LOCK_FAILED);
@ -49,7 +49,7 @@ void ScreenshotService::startTimed(const char* path, uint8_t delay_in_seconds, u
if (task == nullptr || task->isFinished()) { if (task == nullptr || task->isFinished()) {
task = std::make_unique<ScreenshotTask>(); task = std::make_unique<ScreenshotTask>();
mode = ScreenshotModeTimed; mode = ScreenshotModeTimed;
task->startTimed(path, delay_in_seconds, amount); task->startTimed(path, delayInSeconds, amount);
} else { } else {
TT_LOG_W(TAG, "Screenshot task already running"); TT_LOG_W(TAG, "Screenshot task already running");
} }

View File

@ -16,8 +16,10 @@ typedef enum {
ScreenshotModeApps ScreenshotModeApps
} Mode; } Mode;
class ScreenshotService { class ScreenshotService {
private:
Mutex mutex; Mutex mutex;
std::unique_ptr<ScreenshotTask> task; std::unique_ptr<ScreenshotTask> task;
Mode mode = ScreenshotModeNone; Mode mode = ScreenshotModeNone;
@ -25,9 +27,23 @@ class ScreenshotService {
public: public:
bool isTaskStarted(); bool isTaskStarted();
/** The state of the service. */
Mode getMode(); Mode getMode();
/** @brief Start taking screenshot whenever an app is started
* @param[in] path the path to store the screenshots at
*/
void startApps(const char* path); void startApps(const char* path);
void startTimed(const char* path, uint8_t delay_in_seconds, uint8_t amount);
/** @brief Start taking screenshots after a certain delay
* @param[in] path the path to store the screenshots at
* @param[in] delayInSeconds the delay before starting (and between successive screenshots)
* @param[in] amount 0 = indefinite, >0 for a specific
*/
void startTimed(const char* path, uint8_t delayInSeconds, uint8_t amount);
/** @brief Stop taking screenshots */
void stop(); void stop();
}; };

View File

@ -35,22 +35,18 @@ public:
~ScreenshotTask(); ~ScreenshotTask();
/** @brief Start taking screenshots after a certain delay /** @brief Start taking screenshots after a certain delay
* @param task the screenshot task * @param[in] path the path to store the screenshots at
* @param path the path to store the screenshots at * @param[in] delayInSeconds the delay before starting (and between successive screenshots)
* @param delay_in_seconds the delay before starting (and between successive screenshots) * @param[in] amount 0 = indefinite, >0 for a specific
* @param amount 0 = indefinite, >0 for a specific
*/ */
void startTimed(const char* path, uint8_t delay_in_seconds, uint8_t amount); void startTimed(const char* path, uint8_t delayInSeconds, uint8_t amount);
/** @brief Start taking screenshot whenever an app is started /** @brief Start taking screenshot whenever an app is started
* @param task the screenshot task * @param[in] path the path to store the screenshots at
* @param path the path to store the screenshots at
*/ */
void startApps(const char* path); void startApps(const char* path);
/** @brief Stop taking screenshots /** @brief Stop taking screenshots */
* @param task the screenshot task
*/
void stop(); void stop();
void taskMain(); void taskMain();

View File

@ -13,11 +13,11 @@ void tt_message_queue_free(MessageQueueHandle handle) {
delete HANDLE_TO_MESSAGE_QUEUE(handle); delete HANDLE_TO_MESSAGE_QUEUE(handle);
} }
bool tt_message_queue_put(MessageQueueHandle handle, const void* message, uint32_t timeout) { bool tt_message_queue_put(MessageQueueHandle handle, const void* message, TickType_t timeout) {
return HANDLE_TO_MESSAGE_QUEUE(handle)->put(message, timeout); return HANDLE_TO_MESSAGE_QUEUE(handle)->put(message, timeout);
} }
bool tt_message_queue_get(MessageQueueHandle handle, void* message, uint32_t timeout) { bool tt_message_queue_get(MessageQueueHandle handle, void* message, TickType_t timeout) {
return HANDLE_TO_MESSAGE_QUEUE(handle)->get(message, timeout); return HANDLE_TO_MESSAGE_QUEUE(handle)->get(message, timeout);
} }

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include <freertos/FreeRTOS.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
@ -11,8 +13,8 @@ typedef void* MessageQueueHandle;
MessageQueueHandle tt_message_queue_alloc(uint32_t capacity, uint32_t messageSize); MessageQueueHandle tt_message_queue_alloc(uint32_t capacity, uint32_t messageSize);
void tt_message_queue_free(MessageQueueHandle handle); void tt_message_queue_free(MessageQueueHandle handle);
bool tt_message_queue_put(MessageQueueHandle handle, const void* message, uint32_t timeout); bool tt_message_queue_put(MessageQueueHandle handle, const void* message, TickType_t timeout);
bool tt_message_queue_get(MessageQueueHandle handle, void* message, uint32_t timeout); bool tt_message_queue_get(MessageQueueHandle handle, void* message, TickType_t timeout);
uint32_t tt_message_queue_get_capacity(MessageQueueHandle handle); uint32_t tt_message_queue_get_capacity(MessageQueueHandle handle);
uint32_t tt_message_queue_get_message_size(MessageQueueHandle handle); uint32_t tt_message_queue_get_message_size(MessageQueueHandle handle);
uint32_t tt_message_queue_get_count(MessageQueueHandle handle); uint32_t tt_message_queue_get_count(MessageQueueHandle handle);

View File

@ -20,8 +20,8 @@ void tt_mutex_free(MutexHandle handle) {
delete HANDLE_AS_MUTEX(handle); delete HANDLE_AS_MUTEX(handle);
} }
bool tt_mutex_lock(MutexHandle handle, uint32_t timeoutTicks) { bool tt_mutex_lock(MutexHandle handle, TickType_t timeout) {
return HANDLE_AS_MUTEX(handle)->lock(timeoutTicks); return HANDLE_AS_MUTEX(handle)->lock((TickType_t)timeout);
} }
bool tt_mutex_unlock(MutexHandle handle) { bool tt_mutex_unlock(MutexHandle handle) {

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include <freertos/FreeRTOS.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
@ -16,7 +18,7 @@ enum TtMutexType {
MutexHandle tt_mutex_alloc(enum TtMutexType); MutexHandle tt_mutex_alloc(enum TtMutexType);
void tt_mutex_free(MutexHandle handle); void tt_mutex_free(MutexHandle handle);
bool tt_mutex_lock(MutexHandle handle, uint32_t timeoutTicks); bool tt_mutex_lock(MutexHandle handle, TickType_t timeoutTicks);
bool tt_mutex_unlock(MutexHandle handle); bool tt_mutex_unlock(MutexHandle handle);
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -5,7 +5,7 @@ extern "C" {
#define HANDLE_AS_SEMAPHORE(handle) ((tt::Semaphore*)(handle)) #define HANDLE_AS_SEMAPHORE(handle) ((tt::Semaphore*)(handle))
SemaphoreHandle tt_semaphore_alloc(uint32_t maxCount, uint32_t initialCount) { SemaphoreHandle tt_semaphore_alloc(uint32_t maxCount, TickType_t initialCount) {
return new tt::Semaphore(maxCount, initialCount); return new tt::Semaphore(maxCount, initialCount);
} }
@ -13,7 +13,7 @@ void tt_semaphore_free(SemaphoreHandle handle) {
delete HANDLE_AS_SEMAPHORE(handle); delete HANDLE_AS_SEMAPHORE(handle);
} }
bool tt_semaphore_acquire(SemaphoreHandle handle, uint32_t timeoutTicks) { bool tt_semaphore_acquire(SemaphoreHandle handle, TickType_t timeoutTicks) {
return HANDLE_AS_SEMAPHORE(handle)->acquire(timeoutTicks); return HANDLE_AS_SEMAPHORE(handle)->acquire(timeoutTicks);
} }

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include <freertos/FreeRTOS.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
@ -9,9 +11,9 @@ extern "C" {
typedef void* SemaphoreHandle; typedef void* SemaphoreHandle;
SemaphoreHandle tt_semaphore_alloc(uint32_t maxCount, uint32_t initialCount); SemaphoreHandle tt_semaphore_alloc(uint32_t maxCount, TickType_t initialCount);
void tt_semaphore_free(SemaphoreHandle handle); void tt_semaphore_free(SemaphoreHandle handle);
bool tt_semaphore_acquire(SemaphoreHandle handle, uint32_t timeoutTicks); bool tt_semaphore_acquire(SemaphoreHandle handle, TickType_t timeoutTicks);
bool tt_semaphore_release(SemaphoreHandle handle); bool tt_semaphore_release(SemaphoreHandle handle);
uint32_t tt_semaphore_get_count(SemaphoreHandle handle); uint32_t tt_semaphore_get_count(SemaphoreHandle handle);

View File

@ -29,11 +29,11 @@ void tt_timer_free(TimerHandle handle) {
delete wrapper; delete wrapper;
} }
bool tt_timer_start(TimerHandle handle, uint32_t intervalTicks) { bool tt_timer_start(TimerHandle handle, TickType_t intervalTicks) {
return ((TimerWrapper*)handle)->timer->start(intervalTicks); return ((TimerWrapper*)handle)->timer->start(intervalTicks);
} }
bool tt_timer_restart(TimerHandle handle, uint32_t intervalTicks) { bool tt_timer_restart(TimerHandle handle, TickType_t intervalTicks) {
return ((TimerWrapper*)handle)->timer->restart(intervalTicks); return ((TimerWrapper*)handle)->timer->restart(intervalTicks);
} }
@ -49,11 +49,12 @@ uint32_t tt_timer_get_expire_time(TimerHandle handle) {
return ((TimerWrapper*)handle)->timer->getExpireTime(); return ((TimerWrapper*)handle)->timer->getExpireTime();
} }
bool tt_timer_set_pending_callback(TimerHandle handle, TimerPendingCallback callback, void* callbackContext, uint32_t arg) { bool tt_timer_set_pending_callback(TimerHandle handle, TimerPendingCallback callback, void* callbackContext, uint32_t callbackArg, TickType_t timeoutTicks) {
return ((TimerWrapper*)handle)->timer->setPendingCallback( return ((TimerWrapper*)handle)->timer->setPendingCallback(
callback, callback,
callbackContext, callbackContext,
arg callbackArg,
(TickType_t)timeoutTicks
); );
} }

View File

@ -1,5 +1,7 @@
#pragma once #pragma once
#include <freertos/FreeRTOS.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
@ -24,12 +26,12 @@ typedef void (*TimerPendingCallback)(void* context, uint32_t arg);
TimerHandle tt_timer_alloc(TimerType type, TimerCallback callback, void* callbackContext); TimerHandle tt_timer_alloc(TimerType type, TimerCallback callback, void* callbackContext);
void tt_timer_free(TimerHandle handle); void tt_timer_free(TimerHandle handle);
bool tt_timer_start(TimerHandle handle, uint32_t intervalTicks); bool tt_timer_start(TimerHandle handle, TickType_t intervalTicks);
bool tt_timer_restart(TimerHandle handle, uint32_t intervalTicks); bool tt_timer_restart(TimerHandle handle, TickType_t intervalTicks);
bool tt_timer_stop(TimerHandle handle); bool tt_timer_stop(TimerHandle handle);
bool tt_timer_is_running(TimerHandle handle); bool tt_timer_is_running(TimerHandle handle);
uint32_t tt_timer_get_expire_time(TimerHandle handle); uint32_t tt_timer_get_expire_time(TimerHandle handle);
bool tt_timer_set_pending_callback(TimerHandle handle, TimerPendingCallback callback, void* callbackContext, uint32_t arg); bool tt_timer_set_pending_callback(TimerHandle handle, TimerPendingCallback callback, void* callbackContext, uint32_t callbackArg, TickType_t timeoutTicks);
void tt_timer_set_thread_priority(TimerHandle handle, TimerThreadPriority priority); void tt_timer_set_thread_priority(TimerHandle handle, TimerThreadPriority priority);
#ifdef __cplusplus #ifdef __cplusplus

View File

@ -10,6 +10,9 @@
namespace tt { namespace tt {
/**
* A dictionary that maps keys (strings) onto several atomary types.
*/
class Bundle { class Bundle {
private: private:

View File

@ -40,9 +40,8 @@ namespace tt {
tt::_crash(); \ tt::_crash(); \
} while (0) } while (0)
/** Halt system /** Halt the system
* * @param[in] optional message (const char*)
* @param optional message (const char*)
*/ */
#define tt_halt(...) M_APPLY(__tt_halt, M_IF_EMPTY(__VA_ARGS__)((NULL), (__VA_ARGS__))) #define tt_halt(...) M_APPLY(__tt_halt, M_IF_EMPTY(__VA_ARGS__)((NULL), (__VA_ARGS__)))
@ -61,8 +60,8 @@ namespace tt {
/** Check condition and crash if failed /** Check condition and crash if failed
* *
* @param condition to check * @param[in] condition to check
* @param optional message (const char*) * @param[in] optional message (const char*)
*/ */
#define tt_check(x, ...) if (!(x)) { TT_LOG_E("check", "Failed: %s", #x); tt::_crash(); } #define tt_check(x, ...) if (!(x)) { TT_LOG_E("check", "Failed: %s", #x); tt::_crash(); }
@ -89,10 +88,7 @@ namespace tt {
#endif #endif
/** Assert condition and crash if failed /** Assert condition and crash if failed
* * @warning only will do check if firmware compiled in debug mode
* @warning only will do check if firmware compiled in debug mode * @param[in] condition to check
*
* @param condition to check
* @param optional message (const char*)
*/ */
#define tt_assert(expression) assert(expression) #define tt_assert(expression) assert(expression)

View File

@ -45,5 +45,3 @@
#define _TT_ARGCOUNT4(X,...) _TT_ARGCOUNT ## __VA_OPT__(5(__VA_ARGS__) TT_ARG_IGNORE) (4) #define _TT_ARGCOUNT4(X,...) _TT_ARGCOUNT ## __VA_OPT__(5(__VA_ARGS__) TT_ARG_IGNORE) (4)
#define _TT_ARGCOUNT5(X,...) 5 #define _TT_ARGCOUNT5(X,...) 5
#define _TT_ARGCOUNT(X) X #define _TT_ARGCOUNT(X) X
// endregion

View File

@ -1,5 +1,10 @@
#pragma once #pragma once
/** Find the largest value
* @param[in] a first value to compare
* @param[in] b second value to compare
* @return the largest value of a and b
*/
#define TT_MAX(a, b) \ #define TT_MAX(a, b) \
({ \ ({ \
__typeof__(a) _a = (a); \ __typeof__(a) _a = (a); \
@ -7,6 +12,11 @@
_a > _b ? _a : _b; \ _a > _b ? _a : _b; \
}) })
/** Find the smallest value
* @param[in] a first value to compare
* @param[in] b second value to compare
* @return the smallest value of a and b
*/
#define TT_MIN(a, b) \ #define TT_MIN(a, b) \
({ \ ({ \
__typeof__(a) _a = (a); \ __typeof__(a) _a = (a); \
@ -14,49 +24,12 @@
_a < _b ? _a : _b; \ _a < _b ? _a : _b; \
}) })
/** @return the absolute value of the input */
#define TT_ABS(a) ({ (a) < 0 ? -(a) : (a); }) #define TT_ABS(a) ({ (a) < 0 ? -(a) : (a); })
#define TT_ROUND_UP_TO(a, b) \ /** Clamp a value between a min and a max.
({ \ * @param[in] x value to clamp
__typeof__(a) _a = (a); \ * @param[in] upper upper bounds for x
__typeof__(b) _b = (b); \ * @param[in] lower lower bounds for x
_a / _b + !!(_a % _b); \ */
})
#define TT_CLAMP(x, upper, lower) (TT_MIN(upper, TT_MAX(x, lower))) #define TT_CLAMP(x, upper, lower) (TT_MIN(upper, TT_MAX(x, lower)))
#define TT_COUNT_OF(x) (sizeof(x) / sizeof(x[0]))
#define TT_SWAP(x, y) \
do { \
typeof(x) SWAP = x; \
x = y; \
y = SWAP; \
} while (0)
#define TT_STRINGIFY(x) #x
#define TT_TOSTRING(x) TT_STRINGIFY(x)
#define TT_CONCATENATE(a, b) CONCATENATE_(a, b)
#define TT_CONCATENATE_(a, b) a##b
#define TT_REVERSE_BYTES_U32(x) \
((((x) & 0x000000FF) << 24) | (((x) & 0x0000FF00) << 8) | (((x) & 0x00FF0000) >> 8) | \
(((x) & 0xFF000000) >> 24))
#define TT_BIT(x, n) (((x) >> (n)) & 1)
#define TT_BIT_SET(x, n) \
({ \
__typeof__(x) _x = (1); \
(x) |= (_x << (n)); \
})
#define TT_BIT_CLEAR(x, n) \
({ \
__typeof__(x) _x = (1); \
(x) &= ~(_x << (n)); \
})
#define TT_SW_MEMBARRIER() asm volatile("" : : : "memory")

View File

@ -18,8 +18,8 @@ Dispatcher::~Dispatcher() {
mutex.release(); mutex.release();
} }
void Dispatcher::dispatch(Callback callback, std::shared_ptr<void> context) { void Dispatcher::dispatch(Function function, std::shared_ptr<void> context) {
auto message = std::make_shared<DispatcherMessage>(callback, std::move(context)); auto message = std::make_shared<DispatcherMessage>(function, std::move(context));
// Mutate // Mutate
if (mutex.lock(1000 / portTICK_PERIOD_MS)) { if (mutex.lock(1000 / portTICK_PERIOD_MS)) {
queue.push(std::move(message)); queue.push(std::move(message));
@ -34,16 +34,16 @@ void Dispatcher::dispatch(Callback callback, std::shared_ptr<void> context) {
} }
} }
uint32_t Dispatcher::consume(uint32_t timeout_ticks) { uint32_t Dispatcher::consume(TickType_t timeout) {
// Wait for signal and clear // Wait for signal and clear
TickType_t start_ticks = kernel::getTicks(); TickType_t start_ticks = kernel::getTicks();
if (eventFlag.wait(WAIT_FLAG, TtFlagWaitAny, timeout_ticks) == WAIT_FLAG) { if (eventFlag.wait(WAIT_FLAG, TtFlagWaitAny, timeout) == WAIT_FLAG) {
eventFlag.clear(WAIT_FLAG); eventFlag.clear(WAIT_FLAG);
} else { } else {
return 0; return 0;
} }
TickType_t ticks_remaining = TT_MAX(timeout_ticks - (kernel::getTicks() - start_ticks), 0); TickType_t ticks_remaining = TT_MAX(timeout - (kernel::getTicks() - start_ticks), 0);
TT_LOG_I(TAG, "Dispatcher continuing (%d ticks)", (int)ticks_remaining); TT_LOG_I(TAG, "Dispatcher continuing (%d ticks)", (int)ticks_remaining);
@ -59,7 +59,7 @@ uint32_t Dispatcher::consume(uint32_t timeout_ticks) {
processing = !queue.empty(); processing = !queue.empty();
// Don't keep lock as callback might be slow // Don't keep lock as callback might be slow
tt_check(mutex.unlock()); tt_check(mutex.unlock());
item->callback(item->context); item->function(item->context);
} else { } else {
processing = false; processing = false;
tt_check(mutex.unlock()); tt_check(mutex.unlock());

View File

@ -13,16 +13,24 @@
namespace tt { namespace tt {
typedef void (*Callback)(std::shared_ptr<void> data);
/**
* A thread-safe way to defer code execution.
* Generally, one task would dispatch the execution,
* while the other thread consumes and executes the work.
*/
class Dispatcher { class Dispatcher {
public:
typedef void (*Function)(std::shared_ptr<void> data);
private: private:
struct DispatcherMessage { struct DispatcherMessage {
Callback callback; Function function;
std::shared_ptr<void> context; // Can't use unique_ptr with void, so we use shared_ptr std::shared_ptr<void> context; // Can't use unique_ptr with void, so we use shared_ptr
DispatcherMessage(Callback callback, std::shared_ptr<void> context) : DispatcherMessage(Function function, std::shared_ptr<void> context) :
callback(callback), function(function),
context(std::move(context)) context(std::move(context))
{} {}
@ -38,8 +46,19 @@ public:
explicit Dispatcher(); explicit Dispatcher();
~Dispatcher(); ~Dispatcher();
void dispatch(Callback callback, std::shared_ptr<void> context); /**
uint32_t consume(uint32_t timeout_ticks); * Queue a function to be consumed elsewhere.
* @param[in] function the function to execute elsewhere
* @param[in] context the data to pass onto the function
*/
void dispatch(Function function, std::shared_ptr<void> context);
/**
* Consume a dispatched function (if any)
* @param[in] timeout the ticks to wait for a message
* @return the amount of messages that were consumed
*/
uint32_t consume(TickType_t timeout);
}; };
} // namespace } // namespace

View File

@ -29,8 +29,8 @@ void DispatcherThread::_threadMain() {
} while (!interruptThread); } while (!interruptThread);
} }
void DispatcherThread::dispatch(Callback callback, std::shared_ptr<void> context) { void DispatcherThread::dispatch(Dispatcher::Function function, std::shared_ptr<void> context) {
dispatcher.dispatch(callback, std::move(context)); dispatcher.dispatch(function, std::move(context));
} }
void DispatcherThread::start() { void DispatcherThread::start() {

View File

@ -19,7 +19,7 @@ public:
/** /**
* Dispatch a message. * Dispatch a message.
*/ */
void dispatch(Callback callback, std::shared_ptr<void> context); void dispatch(Dispatcher::Function function, std::shared_ptr<void> context);
/** Start the thread (blocking). */ /** Start the thread (blocking). */
void start(); void start();

View File

@ -1,22 +1,32 @@
#pragma once #pragma once
#include "Check.h" #include "Check.h"
#include "RtosCompat.h"
#include <memory> #include <memory>
namespace tt { namespace tt {
class ScopedLockableUsage; class ScopedLockableUsage;
/** Represents a lock/mutex */
class Lockable { class Lockable {
public: public:
virtual ~Lockable() = default; virtual ~Lockable() = default;
virtual bool lock(uint32_t timeoutTicks) const = 0; virtual bool lock(TickType_t timeoutTicks) const = 0;
virtual bool unlock() const = 0; virtual bool unlock() const = 0;
std::unique_ptr<ScopedLockableUsage> scoped() const; std::unique_ptr<ScopedLockableUsage> scoped() const;
}; };
/**
* Represents a lockable instance that is scoped to a specific lifecycle.
* Once the ScopedLockableUsage is destroyed, unlock() is called automatically.
*
* In other words:
* You have to lock() this object manually, but unlock() happens automatically on destruction.
*/
class ScopedLockableUsage final : public Lockable { class ScopedLockableUsage final : public Lockable {
const Lockable& lockable; const Lockable& lockable;
@ -29,7 +39,7 @@ public:
lockable.unlock(); // We don't care whether it succeeded or not lockable.unlock(); // We don't care whether it succeeded or not
} }
bool lock(uint32_t timeout) const override { bool lock(TickType_t timeout) const override {
return lockable.lock(timeout); return lockable.lock(timeout);
} }

View File

@ -12,6 +12,7 @@
namespace tt { namespace tt {
/** Used for log output filtering */
enum LogLevel { enum LogLevel {
LogLevelNone, /*!< No log output */ LogLevelNone, /*!< No log output */
LogLevelError, /*!< Critical errors, software module can not recover on its own */ LogLevelError, /*!< Critical errors, software module can not recover on its own */
@ -26,6 +27,10 @@ struct LogEntry {
char message[TT_LOG_MESSAGE_SIZE] = { 0 }; char message[TT_LOG_MESSAGE_SIZE] = { 0 };
}; };
/** Make a copy of the currently stored entries.
* The array size is TT_LOG_ENTRY_COUNT
* @param[out] outIndex the write index for the next log entry.
*/
LogEntry* copyLogEntries(unsigned int& outIndex); LogEntry* copyLogEntries(unsigned int& outIndex);
} // namespace tt } // namespace tt

View File

@ -5,13 +5,13 @@
namespace tt { namespace tt {
MessageQueue::MessageQueue(uint32_t capacity, uint32_t msg_size) { MessageQueue::MessageQueue(uint32_t capacity, uint32_t msg_size) {
tt_assert((kernel::isIrq() == 0U) && (capacity > 0U) && (msg_size > 0U)); tt_assert(!TT_IS_ISR() && (capacity > 0U) && (msg_size > 0U));
queue_handle = xQueueCreate(capacity, msg_size); queue_handle = xQueueCreate(capacity, msg_size);
tt_check(queue_handle); tt_check(queue_handle);
} }
MessageQueue::~MessageQueue() { MessageQueue::~MessageQueue() {
tt_assert(kernel::isIrq() == 0U); tt_assert(!TT_IS_ISR());
vQueueDelete(queue_handle); vQueueDelete(queue_handle);
} }
@ -19,7 +19,7 @@ bool MessageQueue::put(const void* message, uint32_t timeout) {
bool result = true; bool result = true;
BaseType_t yield; BaseType_t yield;
if (kernel::isIrq() != 0U) { if (TT_IS_ISR()) {
if ((queue_handle == nullptr) || (message == nullptr) || (timeout != 0U)) { if ((queue_handle == nullptr) || (message == nullptr) || (timeout != 0U)) {
result = false; result = false;
} else { } else {
@ -45,7 +45,7 @@ bool MessageQueue::get(void* msg_ptr, uint32_t timeout_ticks) {
BaseType_t yield; BaseType_t yield;
if (kernel::isIrq()) { if (TT_IS_ISR()) {
if ((queue_handle == nullptr) || (msg_ptr == nullptr) || (timeout_ticks != 0U)) { if ((queue_handle == nullptr) || (msg_ptr == nullptr) || (timeout_ticks != 0U)) {
result = false; result = false;
} else { } else {
@ -91,7 +91,7 @@ uint32_t MessageQueue::getCount() const {
if (queue_handle == nullptr) { if (queue_handle == nullptr) {
count = 0U; count = 0U;
} else if (kernel::isIrq() != 0U) { } else if (TT_IS_ISR()) {
count = uxQueueMessagesWaitingFromISR(queue_handle); count = uxQueueMessagesWaitingFromISR(queue_handle);
} else { } else {
count = uxQueueMessagesWaiting(queue_handle); count = uxQueueMessagesWaiting(queue_handle);
@ -108,7 +108,7 @@ uint32_t MessageQueue::getSpace() const {
if (mq == nullptr) { if (mq == nullptr) {
space = 0U; space = 0U;
} else if (kernel::isIrq() != 0U) { } else if (TT_IS_ISR()) {
isrm = taskENTER_CRITICAL_FROM_ISR(); isrm = taskENTER_CRITICAL_FROM_ISR();
/* space = pxQueue->uxLength - pxQueue->uxMessagesWaiting; */ /* space = pxQueue->uxLength - pxQueue->uxMessagesWaiting; */
@ -123,7 +123,7 @@ uint32_t MessageQueue::getSpace() const {
} }
bool MessageQueue::reset() { bool MessageQueue::reset() {
tt_check(!kernel::isIrq()); tt_check(!TT_IS_ISR());
if (queue_handle == nullptr) { if (queue_handle == nullptr) {
return false; return false;
} else { } else {

View File

@ -36,7 +36,8 @@ public:
~MessageQueue(); ~MessageQueue();
/** Put message into queue /** Post a message to the queue.
* The message is queued by copy, not by reference.
* @param[in] message A pointer to a message. The message will be copied into a buffer. * @param[in] message A pointer to a message. The message will be copied into a buffer.
* @param[in] timeoutTicks * @param[in] timeoutTicks
* @return success result * @return success result
@ -44,7 +45,7 @@ public:
bool put(const void* message, uint32_t timeoutTicks); bool put(const void* message, uint32_t timeoutTicks);
/** Get message from queue /** Get message from queue
* @param message A pointer to an already allocated message object * @param[out] message A pointer to an already allocated message object
* @param[in] timeoutTicks * @param[in] timeoutTicks
* @return success result * @return success result
*/ */

View File

@ -44,7 +44,7 @@ Mutex::~Mutex() {
semaphore = nullptr; // If the mutex is used after release, this might help debugging semaphore = nullptr; // If the mutex is used after release, this might help debugging
} }
TtStatus Mutex::acquire(uint32_t timeout) const { TtStatus Mutex::acquire(TickType_t timeout) const {
tt_assert(!TT_IS_IRQ_MODE()); tt_assert(!TT_IS_IRQ_MODE());
tt_assert(semaphore); tt_assert(semaphore);
@ -114,7 +114,7 @@ void tt_mutex_free(Mutex* mutex) {
delete mutex; delete mutex;
} }
TtStatus tt_mutex_acquire(Mutex* mutex, uint32_t timeout) { TtStatus tt_mutex_acquire(Mutex* mutex, TickType_t timeout) {
return mutex-> acquire(timeout); return mutex-> acquire(timeout);
} }

View File

@ -13,8 +13,6 @@
namespace tt { namespace tt {
class ScopedMutexUsage;
/** /**
* Wrapper for FreeRTOS xSemaphoreCreateMutex and xSemaphoreCreateRecursiveMutex * Wrapper for FreeRTOS xSemaphoreCreateMutex and xSemaphoreCreateRecursiveMutex
* Can be used in IRQ mode (within ISR context) * Can be used in IRQ mode (within ISR context)
@ -38,56 +36,64 @@ public:
explicit Mutex(Type type = TypeNormal); explicit Mutex(Type type = TypeNormal);
~Mutex() override; ~Mutex() override;
TtStatus acquire(uint32_t timeoutTicks) const; /** Attempt to lock the mutex. Blocks until timeout passes or lock is acquired.
* @param[in] timeout
* @return status result
*/
TtStatus acquire(TickType_t timeout) const;
/** Attempt to unlock the mutex.
* @return status result
*/
TtStatus release() const; TtStatus release() const;
bool lock(uint32_t timeoutTicks) const override { return acquire(timeoutTicks) == TtStatusOk; } /** Attempt to lock the mutex. Blocks until timeout passes or lock is acquired.
* @param[in] timeout
* @return success result
*/
bool lock(TickType_t timeout) const override { return acquire(timeout) == TtStatusOk; }
/** Attempt to unlock the mutex.
* @return success result
*/
bool unlock() const override { return release() == TtStatusOk; } bool unlock() const override { return release() == TtStatusOk; }
/** @return the owner of the thread */
ThreadId getOwner() const; ThreadId getOwner() const;
}; };
/** Allocate Mutex /** Allocate Mutex
* * @param[in] type The mutex type
* @param[in] type The mutex type * @return pointer to Mutex instance
*
* @return pointer to Mutex instance
*/ */
[[deprecated("use class")]] [[deprecated("use class")]]
Mutex* tt_mutex_alloc(Mutex::Type type); Mutex* tt_mutex_alloc(Mutex::Type type);
/** Free Mutex /** Free Mutex
* * @param[in] mutex The Mutex instance
* @param mutex The Mutex instance
*/ */
[[deprecated("use class")]] [[deprecated("use class")]]
void tt_mutex_free(Mutex* mutex); void tt_mutex_free(Mutex* mutex);
/** Acquire mutex /** Acquire mutex
* * @param[in] mutex
* @param mutex The Mutex instance * @param[in] timeout
* @param[in] timeout The timeout * @return the status result
*
* @return The status.
*/ */
[[deprecated("use class")]] [[deprecated("use class")]]
TtStatus tt_mutex_acquire(Mutex* mutex, uint32_t timeout); TtStatus tt_mutex_acquire(Mutex* mutex, TickType_t timeout);
/** Release mutex /** Release mutex
* * @param[in] mutex The Mutex instance
* @param mutex The Mutex instance * @return the status result
*
* @return The status.
*/ */
[[deprecated("use class")]] [[deprecated("use class")]]
TtStatus tt_mutex_release(Mutex* mutex); TtStatus tt_mutex_release(Mutex* mutex);
/** Get mutex owner thread id /** Get mutex owner thread id
* * @param[in] mutex The Mutex instance
* @param mutex The Mutex instance * @return The thread identifier.
*
* @return The thread identifier.
*/ */
[[deprecated("use class")]] [[deprecated("use class")]]
ThreadId tt_mutex_get_owner(Mutex* mutex); ThreadId tt_mutex_get_owner(Mutex* mutex);

View File

@ -3,12 +3,12 @@
namespace tt { namespace tt {
PubSubSubscription* tt_pubsub_subscribe(std::shared_ptr<PubSub> pubsub, PubSubCallback callback, void* callback_context) { PubSubSubscription* tt_pubsub_subscribe(std::shared_ptr<PubSub> pubsub, PubSubCallback callback, void* callbackContext) {
tt_check(pubsub->mutex.acquire(TtWaitForever) == TtStatusOk); tt_check(pubsub->mutex.acquire(TtWaitForever) == TtStatusOk);
PubSubSubscription subscription = { PubSubSubscription subscription = {
.id = (++pubsub->last_id), .id = (++pubsub->last_id),
.callback = callback, .callback = callback,
.callback_context = callback_context .callback_context = callbackContext
}; };
pubsub->items.push_back( pubsub->items.push_back(
subscription subscription

View File

@ -30,34 +30,27 @@ struct PubSub {
}; };
/** Subscribe to PubSub /** Subscribe to PubSub
*
* Threadsafe, Reentrable * Threadsafe, Reentrable
* * @param[in] pubsub pointer to PubSub instance
* @param pubsub pointer to PubSub instance * @param[in] callback
* @param[in] callback The callback * @param[in] callbackContext the data to pass to the callback
* @param callback_context The callback context * @return subscription instance
*
* @return pointer to PubSubSubscription instance
*/ */
PubSubSubscription* PubSubSubscription*
tt_pubsub_subscribe(std::shared_ptr<PubSub> pubsub, PubSubCallback callback, void* callback_context); tt_pubsub_subscribe(std::shared_ptr<PubSub> pubsub, PubSubCallback callback, void* callbackContext);
/** Unsubscribe from PubSub /** Unsubscribe from PubSub
* * No use of `tt_pubsub_subscription` allowed after call of this method
* No use of `pubsub_subscription` allowed after call of this method * Threadsafe, Re-entrable.
* Threadsafe, Reentrable. * @param[in] pubsub
* * @param[in] subscription
* @param pubsub pointer to PubSub instance
* @param pubsub_subscription pointer to PubSubSubscription instance
*/ */
void tt_pubsub_unsubscribe(std::shared_ptr<PubSub> pubsub, PubSubSubscription* pubsub_subscription); void tt_pubsub_unsubscribe(std::shared_ptr<PubSub> pubsub, PubSubSubscription* subscription);
/** Publish message to PubSub /** Publish message to PubSub
*
* Threadsafe, Reentrable. * Threadsafe, Reentrable.
* * @param[in] pubsub
* @param pubsub pointer to PubSub instance * @param[in] message message pointer to publish - it is passed as-is to the callback
* @param message message pointer to publish
*/ */
void tt_pubsub_publish(std::shared_ptr<PubSub> pubsub, void* message); void tt_pubsub_publish(std::shared_ptr<PubSub> pubsub, void* message);

View File

@ -35,8 +35,8 @@ public:
* interrupt that will write to the buffer (the writer), and only one task or * interrupt that will write to the buffer (the writer), and only one task or
* interrupt that will read from the buffer (the reader). * interrupt that will read from the buffer (the reader).
* *
* @param size The total number of bytes the stream buffer will be able to hold at any one time. * @param[in] size The total number of bytes the stream buffer will be able to hold at any one time.
* @param triggerLevel The number of bytes that must be in the stream buffer * @param[in] triggerLevel The number of bytes that must be in the stream buffer
* before a task that is blocked on the stream buffer to wait for data is moved out of the blocked state. * before a task that is blocked on the stream buffer to wait for data is moved out of the blocked state.
* @return The stream buffer instance. * @return The stream buffer instance.
*/ */
@ -50,7 +50,7 @@ public:
* stream buffer before a task that is blocked on the stream buffer to * stream buffer before a task that is blocked on the stream buffer to
* wait for data is moved out of the blocked state. * wait for data is moved out of the blocked state.
* *
* @param triggerLevel The new trigger level for the stream buffer. * @param[in] triggerLevel The new trigger level for the stream buffer.
* @return true if trigger level can be be updated (new trigger level was less than or equal to the stream buffer's length). * @return true if trigger level can be be updated (new trigger level was less than or equal to the stream buffer's length).
* @return false if trigger level can't be be updated (new trigger level was greater than the stream buffer's length). * @return false if trigger level can't be be updated (new trigger level was greater than the stream buffer's length).
*/ */
@ -60,9 +60,9 @@ public:
* @brief Sends bytes to a stream buffer. The bytes are copied into the stream buffer. * @brief Sends bytes to a stream buffer. The bytes are copied into the stream buffer.
* Wakes up task waiting for data to become available if called from ISR. * Wakes up task waiting for data to become available if called from ISR.
* *
* @param data A pointer to the data that is to be copied into the stream buffer. * @param[in] data A pointer to the data that is to be copied into the stream buffer.
* @param length The maximum number of bytes to copy from data into the stream buffer. * @param[in] length The maximum number of bytes to copy from data into the stream buffer.
* @param timeout The maximum amount of time the task should remain in the * @param[in] timeout The maximum amount of time the task should remain in the
* Blocked state to wait for space to become available if the stream buffer is full. * Blocked state to wait for space to become available if the stream buffer is full.
* Will return immediately if timeout is zero. * Will return immediately if timeout is zero.
* Setting timeout to TtWaitForever will cause the task to wait indefinitely. * Setting timeout to TtWaitForever will cause the task to wait indefinitely.
@ -79,10 +79,10 @@ public:
* @brief Receives bytes from a stream buffer. * @brief Receives bytes from a stream buffer.
* Wakes up task waiting for space to become available if called from ISR. * Wakes up task waiting for space to become available if called from ISR.
* *
* @param data A pointer to the buffer into which the received bytes will be * @param[in] data A pointer to the buffer into which the received bytes will be
* copied. * copied.
* @param length The length of the buffer pointed to by the data parameter. * @param[in] length The length of the buffer pointed to by the data parameter.
* @param timeout The maximum amount of time the task should remain in the * @param[in] timeout The maximum amount of time the task should remain in the
* Blocked state to wait for data to become available if the stream buffer is empty. * Blocked state to wait for data to become available if the stream buffer is empty.
* Will return immediately if timeout is zero. * Will return immediately if timeout is zero.
* Setting timeout to TtWaitForever will cause the task to wait indefinitely. * Setting timeout to TtWaitForever will cause the task to wait indefinitely.

View File

@ -7,13 +7,7 @@
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#ifdef ESP_PLATFORM #include "RtosCompatTask.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#else
#include "FreeRTOS.h"
#include "task.h"
#endif
namespace tt { namespace tt {
@ -47,14 +41,14 @@ public:
typedef int32_t (*Callback)(void* context); typedef int32_t (*Callback)(void* context);
/** Write to stdout callback /** Write to stdout callback
* @param data pointer to data * @param[in] data pointer to data
* @param size data size @warning your handler must consume everything * @param[in] size data size @warning your handler must consume everything
*/ */
typedef void (*StdoutWriteCallback)(const char* data, size_t size); typedef void (*StdoutWriteCallback)(const char* data, size_t size);
/** Thread state change callback called upon thread state change /** Thread state change callback called upon thread state change
* @param state new thread state * @param[in] state new thread state
* @param context callback context * @param[in] context callback context
*/ */
typedef void (*StateCallback)(State state, void* context); typedef void (*StateCallback)(State state, void* context);
@ -85,11 +79,10 @@ public:
Thread(); Thread();
/** Allocate Thread, shortcut version /** Allocate Thread, shortcut version
* @param[in] name
* @param name * @param[in] stack_size
* @param stack_size * @param[in] callback
* @param callback * @param[in] callbackContext
* @param context
* @return Thread* * @return Thread*
*/ */
Thread( Thread(
@ -102,8 +95,7 @@ public:
~Thread(); ~Thread();
/** Set Thread name /** Set Thread name
* * @param[in] name string
* @param name string
*/ */
void setName(const std::string& name); void setName(const std::string& name);
@ -118,75 +110,49 @@ public:
bool isMarkedAsStatic() const; bool isMarkedAsStatic() const;
/** Set Thread stack size /** Set Thread stack size
* * @param[in] stackSize stack size in bytes
* @param thread Thread instance
* @param stackSize stack size in bytes
*/ */
void setStackSize(size_t stackSize); void setStackSize(size_t stackSize);
/** Set Thread callback /** Set Thread callback
* * @param[in] callback ThreadCallback, called upon thread run
* @param thread Thread instance * @param[in] callbackContext what to pass to the callback
* @param callback ThreadCallback, called upon thread run
* @param callbackContext what to pass to the callback
*/ */
void setCallback(Callback callback, _Nullable void* callbackContext = nullptr); void setCallback(Callback callback, _Nullable void* callbackContext = nullptr);
/** Set Thread priority /** Set Thread priority
* * @param[in] priority ThreadPriority value
* @param thread Thread instance
* @param priority ThreadPriority value
*/ */
void setPriority(Priority priority); void setPriority(Priority priority);
/** Set Thread state change callback /** Set Thread state change callback
* * @param[in] callback state change callback
* @param thread Thread instance * @param[in] callbackContext pointer to context
* @param callback state change callback
* @param context pointer to context
*/ */
void setStateCallback(StateCallback callback, _Nullable void* callbackContext = nullptr); void setStateCallback(StateCallback callback, _Nullable void* callbackContext = nullptr);
/** Get Thread state /** Get Thread state
*
* @param thread Thread instance
*
* @return thread state from ThreadState * @return thread state from ThreadState
*/ */
State getState() const; State getState() const;
/** Start Thread /** Start Thread
*
* @param thread Thread instance
*/ */
void start(); void start();
/** Join Thread /** Join Thread
* * @warning Use this method only when CPU is not busy (Idle task receives control), otherwise it will wait forever.
* @warning Use this method only when CPU is not busy(Idle task receives
* control), otherwise it will wait forever.
*
* @param thread Thread instance
*
* @return success result * @return success result
*/ */
bool join(); bool join();
/** Get FreeRTOS ThreadId for Thread instance /** Get FreeRTOS ThreadId for Thread instance
*
* @param thread Thread instance
*
* @return ThreadId or nullptr * @return ThreadId or nullptr
*/ */
ThreadId getId(); ThreadId getId();
/** Get thread return code /** @return thread return code */
*
* @param thread Thread instance
*
* @return return code
*/
int32_t getReturnCode(); int32_t getReturnCode();
private: private:
@ -200,31 +166,17 @@ private:
#define THREAD_PRIORITY_ISR (TT_CONFIG_THREAD_MAX_PRIORITIES - 1) #define THREAD_PRIORITY_ISR (TT_CONFIG_THREAD_MAX_PRIORITIES - 1)
/** Set current thread priority /** Set current thread priority
* * @param[in] priority ThreadPriority value
* @param priority ThreadPriority value
*/ */
void thread_set_current_priority(Thread::Priority priority); void thread_set_current_priority(Thread::Priority priority);
/** Get current thread priority /** @return ThreadPriority value */
*
* @return ThreadPriority value
*/
Thread::Priority thread_get_current_priority(); Thread::Priority thread_get_current_priority();
/** Thread related methods that doesn't involve Thread directly */ /** @return FreeRTOS ThreadId or NULL */
/** Get FreeRTOS ThreadId for current thread
*
* @param thread Thread instance
*
* @return ThreadId or NULL
*/
ThreadId thread_get_current_id(); ThreadId thread_get_current_id();
/** Get Thread instance for current thread /** @return pointer to Thread instance or NULL if this thread doesn't belongs to Tactility */
*
* @return pointer to Thread or NULL if this thread doesn't belongs to Tactility
*/
Thread* thread_get_current(); Thread* thread_get_current();
/** Return control to scheduler */ /** Return control to scheduler */
@ -240,44 +192,38 @@ uint32_t thread_flags_wait(uint32_t flags, uint32_t options, uint32_t timeout);
/** /**
* @brief Get thread name * @brief Get thread name
* * @param[in] threadId
* @param thread_id
* @return const char* name or NULL * @return const char* name or NULL
*/ */
const char* thread_get_name(ThreadId thread_id); const char* thread_get_name(ThreadId threadId);
/** /**
* @brief Get thread stack watermark * @brief Get thread stack watermark
* * @param[in] threadId
* @param thread_id
* @return uint32_t * @return uint32_t
*/ */
uint32_t thread_get_stack_space(ThreadId thread_id); uint32_t thread_get_stack_space(ThreadId threadId);
/** Suspend thread /** Suspend thread
* * @param[in] threadId thread id
* @param thread_id thread id
*/ */
void thread_suspend(ThreadId thread_id); void thread_suspend(ThreadId threadId);
/** Resume thread /** Resume thread
* * @param[in] threadId thread id
* @param thread_id thread id
*/ */
void thread_resume(ThreadId thread_id); void thread_resume(ThreadId threadId);
/** Get thread suspended state /** Get thread suspended state
* * @param[in] threadId thread id
* @param thread_id thread id
* @return true if thread is suspended * @return true if thread is suspended
*/ */
bool thread_is_suspended(ThreadId thread_id); bool thread_is_suspended(ThreadId threadId);
/** Check if the thread was created with static memory /** Check if the thread was created with static memory
* * @param[in] threadId thread id
* @param thread_id thread id
* @return true if thread memory is static * @return true if thread memory is static
*/ */
bool thread_mark_is_static(ThreadId thread_id); bool thread_mark_is_static(ThreadId threadId);
} // namespace } // namespace

View File

@ -16,7 +16,7 @@ static void timer_callback(TimerHandle_t hTimer) {
} }
Timer::Timer(Type type, Callback callback, std::shared_ptr<void> callbackContext) { Timer::Timer(Type type, Callback callback, std::shared_ptr<void> callbackContext) {
tt_assert((kernel::isIrq() == 0U) && (callback != nullptr)); tt_assert((!TT_IS_ISR()) && (callback != nullptr));
this->callback = callback; this->callback = callback;
this->callbackContext = std::move(callbackContext); this->callbackContext = std::move(callbackContext);
@ -33,48 +33,49 @@ Timer::Timer(Type type, Callback callback, std::shared_ptr<void> callbackContext
} }
Timer::~Timer() { Timer::~Timer() {
tt_assert(!kernel::isIrq()); tt_assert(!TT_IS_ISR());
tt_check(xTimerDelete(timerHandle, portMAX_DELAY) == pdPASS); tt_check(xTimerDelete(timerHandle, portMAX_DELAY) == pdPASS);
} }
bool Timer::start(uint32_t intervalTicks) { bool Timer::start(uint32_t intervalTicks) {
tt_assert(!kernel::isIrq()); tt_assert(!TT_IS_ISR());
tt_assert(intervalTicks < portMAX_DELAY); tt_assert(intervalTicks < portMAX_DELAY);
return xTimerChangePeriod(timerHandle, intervalTicks, portMAX_DELAY) == pdPASS; return xTimerChangePeriod(timerHandle, intervalTicks, portMAX_DELAY) == pdPASS;
} }
bool Timer::restart(uint32_t intervalTicks) { bool Timer::restart(uint32_t intervalTicks) {
tt_assert(!kernel::isIrq()); tt_assert(!TT_IS_ISR());
tt_assert(intervalTicks < portMAX_DELAY); tt_assert(intervalTicks < portMAX_DELAY);
return xTimerChangePeriod(timerHandle, intervalTicks, portMAX_DELAY) == pdPASS && return xTimerChangePeriod(timerHandle, intervalTicks, portMAX_DELAY) == pdPASS &&
xTimerReset(timerHandle, portMAX_DELAY) == pdPASS; xTimerReset(timerHandle, portMAX_DELAY) == pdPASS;
} }
bool Timer::stop() { bool Timer::stop() {
tt_assert(!kernel::isIrq()); tt_assert(!TT_IS_ISR());
return xTimerStop(timerHandle, portMAX_DELAY) == pdPASS; return xTimerStop(timerHandle, portMAX_DELAY) == pdPASS;
} }
bool Timer::isRunning() { bool Timer::isRunning() {
tt_assert(!kernel::isIrq()); tt_assert(!TT_IS_ISR());
return xTimerIsTimerActive(timerHandle) == pdTRUE; return xTimerIsTimerActive(timerHandle) == pdTRUE;
} }
uint32_t Timer::getExpireTime() { uint32_t Timer::getExpireTime() {
tt_assert(!kernel::isIrq()); tt_assert(!TT_IS_ISR());
return (uint32_t)xTimerGetExpiryTime(timerHandle); return (uint32_t)xTimerGetExpiryTime(timerHandle);
} }
bool Timer::setPendingCallback(PendingCallback callback, void* callbackContext, uint32_t arg) { bool Timer::setPendingCallback(PendingCallback callback, void* callbackContext, uint32_t callbackArg, TickType_t timeout) {
if (kernel::isIrq()) { if (TT_IS_ISR()) {
return xTimerPendFunctionCallFromISR(callback, callbackContext, arg, nullptr) == pdPASS; assert(timeout == 0);
return xTimerPendFunctionCallFromISR(callback, callbackContext, callbackArg, nullptr) == pdPASS;
} else { } else {
return xTimerPendFunctionCall(callback, callbackContext, arg, TtWaitForever) == pdPASS; return xTimerPendFunctionCall(callback, callbackContext, callbackArg, timeout) == pdPASS;
} }
} }
void Timer::setThreadPriority(ThreadPriority priority) { void Timer::setThreadPriority(ThreadPriority priority) {
tt_assert(!kernel::isIrq()); tt_assert(!TT_IS_ISR());
TaskHandle_t task_handle = xTimerGetTimerDaemonTaskHandle(); TaskHandle_t task_handle = xTimerGetTimerDaemonTaskHandle();
tt_assert(task_handle); // Don't call this method before timer task start tt_assert(task_handle); // Don't call this method before timer task start

View File

@ -33,55 +33,44 @@ public:
~Timer(); ~Timer();
/** Start timer /** Start timer
* * @warning This is asynchronous call, real operation will happen as soon as timer service process this request.
* @warning This is asynchronous call, real operation will happen as soon as
* timer service process this request.
*
* @param[in] ticks The interval in ticks * @param[in] ticks The interval in ticks
* @return success result * @return success result
*/ */
bool start(uint32_t intervalTicks); bool start(uint32_t intervalTicks);
/** Restart timer with previous timeout value /** Restart timer with previous timeout value
* * @warning This is asynchronous call, real operation will happen as soon as timer service process this request.
* @warning This is asynchronous call, real operation will happen as soon as
* timer service process this request.
*
* @param[in] ticks The interval in ticks * @param[in] ticks The interval in ticks
*
* @return success result * @return success result
*/ */
bool restart(uint32_t intervalTicks); bool restart(uint32_t intervalTicks);
/** Stop timer /** Stop timer
* * @warning This is asynchronous call, real operation will happen as soon as timer service process this request.
* @warning This is asynchronous call, real operation will happen as soon as
* timer service process this request.
*
* @return success result * @return success result
*/ */
bool stop(); bool stop();
/** Is timer running /** Is timer running
* * @warning This cal may and will return obsolete timer state if timer commands are still in the queue. Please read FreeRTOS timer documentation first.
* @warning This cal may and will return obsolete timer state if timer
* commands are still in the queue. Please read FreeRTOS timer
* documentation first.
*
* @return true when running * @return true when running
*/ */
bool isRunning(); bool isRunning();
/** Get timer expire time /** Get timer expire time
*
* @param instance The Timer instance
*
* @return expire tick * @return expire tick
*/ */
uint32_t getExpireTime(); uint32_t getExpireTime();
bool setPendingCallback(PendingCallback callback, void* callbackContext, uint32_t arg); /**
* Calls xTimerPendFunctionCall internally.
* @param[in] callback the function to call
* @param[in] callbackContext the first function argument
* @param[in] callbackArg the second function argument
* @param[in] timeout the function timeout (must set to 0 in ISR mode)
*/
bool setPendingCallback(PendingCallback callback, void* callbackContext, uint32_t callbackArg, TickType_t timeout);
typedef enum { typedef enum {
TimerThreadPriorityNormal, /**< Lower then other threads */ TimerThreadPriorityNormal, /**< Lower then other threads */
@ -89,7 +78,6 @@ public:
} ThreadPriority; } ThreadPriority;
/** Set Timer thread priority /** Set Timer thread priority
*
* @param[in] priority The priority * @param[in] priority The priority
*/ */
void setThreadPriority(ThreadPriority priority); void setThreadPriority(ThreadPriority priority);

View File

@ -91,14 +91,14 @@ static void get_nvs_key(uint8_t key[32]) {
/** /**
* Performs XOR on 2 memory regions and stores it in a third * Performs XOR on 2 memory regions and stores it in a third
* @param[in] in_left input buffer for XOR * @param[in] inLeft input buffer for XOR
* @param[in] in_right second input buffer for XOR * @param[in] inRight second input buffer for XOR
* @param[out] out output buffer for result of XOR * @param[out] out output buffer for result of XOR
* @param[in] length data length (all buffers must be at least this size) * @param[in] length data length (all buffers must be at least this size)
*/ */
static void xorKey(const uint8_t* in_left, const uint8_t* in_right, uint8_t* out, size_t length) { static void xorKey(const uint8_t* inLeft, const uint8_t* inRight, uint8_t* out, size_t length) {
for (int i = 0; i < length; ++i) { for (int i = 0; i < length; ++i) {
out[i] = in_left[i] ^ in_right[i]; out[i] = inLeft[i] ^ inRight[i];
} }
} }
@ -112,10 +112,10 @@ static void getKey(uint8_t key[32]) {
TT_LOG_W(TAG, "An attacker with physical access to your ESP32 can decrypt your secure data."); TT_LOG_W(TAG, "An attacker with physical access to your ESP32 can decrypt your secure data.");
#endif #endif
#ifdef ESP_PLATFORM
uint8_t hardware_key[32]; uint8_t hardware_key[32];
uint8_t nvs_key[32]; uint8_t nvs_key[32];
#ifdef ESP_PLATFORM
get_hardware_key(hardware_key); get_hardware_key(hardware_key);
get_nvs_key(nvs_key); get_nvs_key(nvs_key);
xorKey(hardware_key, nvs_key, key, 32); xorKey(hardware_key, nvs_key, key, 32);
@ -125,10 +125,10 @@ static void getKey(uint8_t key[32]) {
#endif #endif
} }
void getIv(const void* data, size_t data_length, uint8_t iv[16]) { void getIv(const void* data, size_t dataLength, uint8_t iv[16]) {
memset((void*)iv, 0, 16); memset((void*)iv, 0, 16);
uint8_t* data_bytes = (uint8_t*)data; auto* data_bytes = (uint8_t*)data;
for (int i = 0; i < data_length; ++i) { for (int i = 0; i < dataLength; ++i) {
size_t safe_index = i % 16; size_t safe_index = i % 16;
iv[safe_index] %= data_bytes[i]; iv[safe_index] %= data_bytes[i];
} }
@ -160,8 +160,8 @@ static int aes256CryptCbc(
return result; return result;
} }
int encrypt(const uint8_t iv[16], uint8_t* in_data, uint8_t* out_data, size_t length) { int encrypt(const uint8_t iv[16], uint8_t* inData, uint8_t* outData, size_t dataLength) {
tt_check(length % 16 == 0, "Length is not a multiple of 16 bytes (for AES 256"); tt_check(dataLength % 16 == 0, "Length is not a multiple of 16 bytes (for AES 256");
uint8_t key[32]; uint8_t key[32];
getKey(key); getKey(key);
@ -169,11 +169,11 @@ int encrypt(const uint8_t iv[16], uint8_t* in_data, uint8_t* out_data, size_t le
uint8_t iv_copy[16]; uint8_t iv_copy[16];
memcpy(iv_copy, iv, sizeof(iv_copy)); memcpy(iv_copy, iv, sizeof(iv_copy));
return aes256CryptCbc(key, MBEDTLS_AES_ENCRYPT, length, iv_copy, in_data, out_data); return aes256CryptCbc(key, MBEDTLS_AES_ENCRYPT, dataLength, iv_copy, inData, outData);
} }
int decrypt(const uint8_t iv[16], uint8_t* in_data, uint8_t* out_data, size_t length) { int decrypt(const uint8_t iv[16], uint8_t* inData, uint8_t* outData, size_t dataLength) {
tt_check(length % 16 == 0, "Length is not a multiple of 16 bytes (for AES 256"); tt_check(dataLength % 16 == 0, "Length is not a multiple of 16 bytes (for AES 256");
uint8_t key[32]; uint8_t key[32];
getKey(key); getKey(key);
@ -181,7 +181,7 @@ int decrypt(const uint8_t iv[16], uint8_t* in_data, uint8_t* out_data, size_t le
uint8_t iv_copy[16]; uint8_t iv_copy[16];
memcpy(iv_copy, iv, sizeof(iv_copy)); memcpy(iv_copy, iv, sizeof(iv_copy));
return aes256CryptCbc(key, MBEDTLS_AES_DECRYPT, length, iv_copy, in_data, out_data); return aes256CryptCbc(key, MBEDTLS_AES_DECRYPT, dataLength, iv_copy, inData, outData);
} }
} // namespace } // namespace

View File

@ -26,11 +26,11 @@ namespace tt::crypt {
/** /**
* @brief Fills the IV with zeros and then creates an IV based on the input data. * @brief Fills the IV with zeros and then creates an IV based on the input data.
* @param data input data * @param[in] data input data
* @param data_length input data length * @param[in] dataLength input data length
* @param iv output IV * @param[out] iv output IV
*/ */
void getIv(const void* data, size_t data_length, uint8_t iv[16]); void getIv(const void* data, size_t dataLength, uint8_t iv[16]);
/** /**
* @brief Encrypt data. * @brief Encrypt data.
@ -38,13 +38,13 @@ void getIv(const void* data, size_t data_length, uint8_t iv[16]);
* Important: Use flash encryption to increase security. * Important: Use flash encryption to increase security.
* Important: input and output data must be aligned to 16 bytes. * Important: input and output data must be aligned to 16 bytes.
* *
* @param iv the AES IV * @param[in] iv the AES IV
* @param data_in input data * @param[in] inData input data
* @param data_out output data * @param[out] outData output data
* @param length data length, a multiple of 16 * @param[in] dataLength data length, a multiple of 16 (for both inData and outData)
* @return the result of esp_aes_crypt_cbc() (MBEDTLS_ERR_*) * @return the result of esp_aes_crypt_cbc() (MBEDTLS_ERR_*)
*/ */
int encrypt(const uint8_t iv[16], uint8_t* in_data, uint8_t* out_data, size_t length); int encrypt(const uint8_t iv[16], uint8_t* inData, uint8_t* outData, size_t dataLength);
/** /**
* @brief Decrypt data. * @brief Decrypt data.
@ -52,12 +52,12 @@ int encrypt(const uint8_t iv[16], uint8_t* in_data, uint8_t* out_data, size_t le
* Important: Use flash encryption to increase security. * Important: Use flash encryption to increase security.
* Important: input and output data must be aligned to 16 bytes. * Important: input and output data must be aligned to 16 bytes.
* *
* @param iv AES IV * @param[in] iv AES IV
* @param data_in input data * @param[in] inData input data
* @param data_out output data * @param[out] outData output data
* @param length data length, a multiple of 16 * @param[in] dataLength data length, a multiple of 16 (for both inData and outData)
* @return the result of esp_aes_crypt_cbc() (MBEDTLS_ERR_*) * @return the result of esp_aes_crypt_cbc() (MBEDTLS_ERR_*)
*/ */
int decrypt(const uint8_t iv[16], uint8_t* in_data, uint8_t* out_data, size_t length); int decrypt(const uint8_t iv[16], uint8_t* inData, uint8_t* outData, size_t dataLength);
} // namespace } // namespace

View File

@ -15,6 +15,7 @@ uint32_t djb2(const char* str);
/** /**
* Implementation of DJB2 hashing algorithm. * Implementation of DJB2 hashing algorithm.
* @param[in] data the bytes to calculate the hash for * @param[in] data the bytes to calculate the hash for
* @param[in] length the size of data
* @return the hash * @return the hash
*/ */
uint32_t djb2(const void* data, size_t length); uint32_t djb2(const void* data, size_t length);

View File

@ -26,6 +26,11 @@ long getSize(FILE* file) {
return file_size; return file_size;
} }
/** Read a file.
* @param[in] filepath
* @param[out] outSize the amount of bytes that were read, excluding the sizePadding
* @param[in] sizePadding optional padding to add at the end of the output data (the values are not set)
*/
static std::unique_ptr<uint8_t[]> readBinaryInternal(const std::string& filepath, size_t& outSize, size_t sizePadding = 0) { static std::unique_ptr<uint8_t[]> readBinaryInternal(const std::string& filepath, size_t& outSize, size_t sizePadding = 0) {
FILE* file = fopen(filepath.c_str(), "rb"); FILE* file = fopen(filepath.c_str(), "rb");
@ -53,15 +58,19 @@ static std::unique_ptr<uint8_t[]> readBinaryInternal(const std::string& filepath
if (bytes_read > 0) { if (bytes_read > 0) {
buffer_offset += bytes_read; buffer_offset += bytes_read;
} else { // Something went wrong? } else { // Something went wrong?
data = nullptr;
break; break;
} }
} }
outSize = buffer_offset;
fclose(file); fclose(file);
return data;
if (buffer_offset == content_length) {
outSize = buffer_offset;
return data;
} else {
outSize = 0;
return nullptr;
}
} }
std::unique_ptr<uint8_t[]> readBinary(const std::string& filepath, size_t& outSize) { std::unique_ptr<uint8_t[]> readBinary(const std::string& filepath, size_t& outSize) {
@ -71,15 +80,11 @@ std::unique_ptr<uint8_t[]> readBinary(const std::string& filepath, size_t& outSi
std::unique_ptr<uint8_t[]> readString(const std::string& filepath) { std::unique_ptr<uint8_t[]> readString(const std::string& filepath) {
size_t size = 0; size_t size = 0;
auto data = readBinaryInternal(filepath, size, 1); auto data = readBinaryInternal(filepath, size, 1);
if (data == nullptr) { if (data != nullptr) {
return nullptr;
} else if (size > 0) {
data.get()[size] = 0; // Append null terminator data.get()[size] = 0; // Append null terminator
return data; return data;
} else { // Empty file: return empty string } else {
auto value = std::make_unique<uint8_t[]>(1); return nullptr;
value[0] = 0;
return value;
} }
} }

View File

@ -7,7 +7,17 @@ namespace tt::file {
long getSize(FILE* file); long getSize(FILE* file);
/** Read a file and return its data.
* @param[in] filepath the path of the file
* @param[out] size the amount of bytes that were read
* @return null on error, or an array of bytes in case of success
*/
std::unique_ptr<uint8_t[]> readBinary(const std::string& filepath, size_t& outSize); std::unique_ptr<uint8_t[]> readBinary(const std::string& filepath, size_t& outSize);
/** Read a file and return a null-terminated string that represents its content.
* @param[in] filepath the path of the file
* @return null on error, or an array of bytes in case of success. Empty string returns as a single 0 character.
*/
std::unique_ptr<uint8_t[]> readString(const std::string& filepath); std::unique_ptr<uint8_t[]> readString(const std::string& filepath);
} }

View File

@ -12,16 +12,12 @@
namespace tt::kernel { namespace tt::kernel {
bool isIrq() {
return TT_IS_IRQ_MODE();
}
bool isRunning() { bool isRunning() {
return xTaskGetSchedulerState() != taskSCHEDULER_RUNNING; return xTaskGetSchedulerState() != taskSCHEDULER_RUNNING;
} }
int32_t lock() { int32_t lock() {
tt_assert(!isIrq()); tt_assert(!TT_IS_ISR());
int32_t lock; int32_t lock;
@ -46,7 +42,7 @@ int32_t lock() {
} }
int32_t unlock() { int32_t unlock() {
tt_assert(!isIrq()); tt_assert(!TT_IS_ISR());
int32_t lock; int32_t lock;
@ -76,7 +72,7 @@ int32_t unlock() {
} }
int32_t restoreLock(int32_t lock) { int32_t restoreLock(int32_t lock) {
tt_assert(!isIrq()); tt_assert(!TT_IS_ISR());
switch (xTaskGetSchedulerState()) { switch (xTaskGetSchedulerState()) {
case taskSCHEDULER_SUSPENDED: case taskSCHEDULER_SUSPENDED:
@ -112,7 +108,7 @@ uint32_t getTickFrequency() {
} }
void delayTicks(TickType_t ticks) { void delayTicks(TickType_t ticks) {
tt_assert(!isIrq()); tt_assert(!TT_IS_ISR());
if (ticks == 0U) { if (ticks == 0U) {
taskYIELD(); taskYIELD();
} else { } else {
@ -121,7 +117,7 @@ void delayTicks(TickType_t ticks) {
} }
TtStatus delayUntilTick(TickType_t tick) { TtStatus delayUntilTick(TickType_t tick) {
tt_assert(!isIrq()); tt_assert(!TT_IS_ISR());
TickType_t tcnt, delay; TickType_t tcnt, delay;
TtStatus stat; TtStatus stat;
@ -150,7 +146,7 @@ TtStatus delayUntilTick(TickType_t tick) {
TickType_t getTicks() { TickType_t getTicks() {
TickType_t ticks; TickType_t ticks;
if (isIrq() != 0U) { if (TT_IS_ISR() != 0U) {
ticks = xTaskGetTickCountFromISR(); ticks = xTaskGetTickCountFromISR();
} else { } else {
ticks = xTaskGetTickCount(); ticks = xTaskGetTickCount();

View File

@ -10,87 +10,56 @@
namespace tt::kernel { namespace tt::kernel {
/** Recognized platform types */
typedef enum { typedef enum {
PlatformEsp, PlatformEsp,
PlatformSimulator PlatformSimulator
} Platform; } Platform;
/** Check if CPU is in IRQ or kernel running and IRQ is masked
*
* Originally this primitive was born as a workaround for FreeRTOS kernel primitives shenanigans with PRIMASK.
*
* Meaningful use cases are:
*
* - When kernel is started and you want to ensure that you are not in IRQ or IRQ is not masked(like in critical section)
* - When kernel is not started and you want to make sure that you are not in IRQ mode, ignoring PRIMASK.
*
* As you can see there will be edge case when kernel is not started and PRIMASK is not 0 that may cause some funky behavior.
* Most likely it will happen after kernel primitives being used, but control not yet passed to kernel.
* It's up to you to figure out if it is safe for your code or not.
*
* @return true if CPU is in IRQ or kernel running and IRQ is masked
*/
bool isIrq();
/** Check if kernel is running /** Check if kernel is running
* * @return true if the FreeRTOS kernel is running, false otherwise
* @return true if running, false otherwise
*/ */
bool isRunning(); bool isRunning();
/** Lock kernel, pause process scheduling /** Lock kernel, pause process scheduling
* * @warning don't call from ISR context
* @warning This should never be called in interrupt request context. * @return previous lock state(0 - unlocked, 1 - locked)
*
* @return previous lock state(0 - unlocked, 1 - locked)
*/ */
int32_t lock(); int32_t lock();
/** Unlock kernel, resume process scheduling /** Unlock kernel, resume process scheduling
* * @warning don't call from ISR context
* @warning This should never be called in interrupt request context. * @return previous lock state(0 - unlocked, 1 - locked)
*
* @return previous lock state(0 - unlocked, 1 - locked)
*/ */
int32_t unlock(); int32_t unlock();
/** Restore kernel lock state /** Restore kernel lock state
* * @warning don't call from ISR context
* @warning This should never be called in interrupt request context. * @param[in] lock The lock state
* * @return new lock state or error
* @param[in] lock The lock state
*
* @return new lock state or error
*/ */
int32_t restoreLock(int32_t lock); int32_t restoreLock(int32_t lock);
/** Get kernel systick frequency /** Get kernel systick frequency
* * @return systick counts per second
* @return systick counts per second
*/ */
uint32_t getTickFrequency(); uint32_t getTickFrequency();
TickType_t getTicks(); TickType_t getTicks();
/** Delay execution /** Delay execution
* * @warning don't call from ISR context
* @warning This should never be called in interrupt request context.
*
* Also keep in mind delay is aliased to scheduler timer intervals. * Also keep in mind delay is aliased to scheduler timer intervals.
* * @param[in] ticks The ticks count to pause
* @param[in] ticks The ticks count to pause
*/ */
void delayTicks(TickType_t ticks); void delayTicks(TickType_t ticks);
/** Delay until tick /** Delay until tick
* * @warning don't call from ISR context
* @warning This should never be called in interrupt request context. * @param[in] ticks The tick until which kerel should delay task execution
* * @return the status
* @param[in] ticks The tick until which kerel should delay task execution
*
* @return The status.
*/ */
TtStatus delayUntilTick(uint32_t tick); TtStatus delayUntilTick(TickType_t tick);
/** Convert milliseconds to ticks /** Convert milliseconds to ticks
* *
@ -100,26 +69,22 @@ TtStatus delayUntilTick(uint32_t tick);
TickType_t millisToTicks(uint32_t milliSeconds); TickType_t millisToTicks(uint32_t milliSeconds);
/** Delay in milliseconds /** Delay in milliseconds
*
* This method uses kernel ticks on the inside, which causes delay to be aliased to scheduler timer intervals. * This method uses kernel ticks on the inside, which causes delay to be aliased to scheduler timer intervals.
* Real wait time will be between X+ milliseconds. * Real wait time will be between X+ milliseconds.
* Special value: 0, will cause task yield. * Special value: 0, will cause task yield.
* Also if used when kernel is not running will fall back to `tt_delay_us`. * Also if used when kernel is not running will fall back to delayMicros()
* * @warning don't call from ISR context
* @warning Cannot be used from ISR * @param[in] milliSeconds milliseconds to wait
*
* @param[in] milliSeconds milliseconds to wait
*/ */
void delayMillis(uint32_t milliSeconds); void delayMillis(uint32_t milliSeconds);
/** Delay in microseconds /** Delay in microseconds
*
* Implemented using Cortex DWT counter. Blocking and non aliased. * Implemented using Cortex DWT counter. Blocking and non aliased.
* * @param[in] microSeconds microseconds to wait
* @param[in] microSeconds microseconds to wait
*/ */
void delayMicros(uint32_t microSeconds); void delayMicros(uint32_t microSeconds);
/** @return the platform that Tactility currently is running on. */
Platform getPlatform(); Platform getPlatform();
} // namespace } // namespace

View File

@ -2,7 +2,6 @@
#include "PanicHandler.h" #include "PanicHandler.h"
#include <esp_rom_sys.h>
#include <esp_debug_helpers.h> #include <esp_debug_helpers.h>
#include <esp_attr.h> #include <esp_attr.h>
#include <esp_memory_utils.h> #include <esp_memory_utils.h>
@ -10,6 +9,10 @@
extern "C" { extern "C" {
/**
* This static variable survives a crash reboot.
* It is reset by the Boot app.
*/
static RTC_NOINIT_ATTR CrashData crashData; static RTC_NOINIT_ATTR CrashData crashData;
void __real_esp_panic_handler(void* info); void __real_esp_panic_handler(void* info);
@ -61,6 +64,6 @@ void __wrap_esp_panic_handler(void* info) {
} }
const CrashData* getRtcCrashData() { return &crashData; } const CrashData& getRtcCrashData() { return crashData; }
#endif #endif

View File

@ -7,6 +7,7 @@
#define CRASH_DATA_CALLSTACK_LIMIT 64 #define CRASH_DATA_CALLSTACK_LIMIT 64
#define CRASH_DATA_INCLUDES_SP false #define CRASH_DATA_INCLUDES_SP false
/** Represents a single frame on the callstack. */
struct CallstackFrame { struct CallstackFrame {
uint32_t pc = 0; uint32_t pc = 0;
#if CRASH_DATA_INCLUDES_SP #if CRASH_DATA_INCLUDES_SP
@ -14,12 +15,14 @@ struct CallstackFrame {
#endif #endif
}; };
/** Callstack-related crash data. */
struct CrashData { struct CrashData {
bool callstackCorrupted = false; bool callstackCorrupted = false;
uint8_t callstackLength = 0; uint8_t callstackLength = 0;
CallstackFrame callstack[CRASH_DATA_CALLSTACK_LIMIT]; CallstackFrame callstack[CRASH_DATA_CALLSTACK_LIMIT];
}; };
const CrashData* getRtcCrashData(); /** @return the crash data */
const CrashData& getRtcCrashData();
#endif #endif

View File

@ -10,8 +10,15 @@ typedef struct {
bool kernelRunning; bool kernelRunning;
} TtCriticalInfo; } TtCriticalInfo;
/** Enter a critical section
* @return info on the status
*/
TtCriticalInfo enter(); TtCriticalInfo enter();
/**
* Exit a critical section
* @param[in] info the info from when the critical section was started
*/
void exit(TtCriticalInfo info); void exit(TtCriticalInfo info);
} // namespace } // namespace

View File

@ -5,6 +5,10 @@
namespace tt { namespace tt {
/**
* Settings that persist on NVS flash for ESP32.
* On simulator, the settings are only in-memory.
*/
class Preferences { class Preferences {
private: private:
const char* namespace_; const char* namespace_;

View File

@ -7,14 +7,20 @@
namespace tt { namespace tt {
/** Initialize the hardware and started the internal services. */
void initHeadless(const hal::Configuration& config); void initHeadless(const hal::Configuration& config);
/** Provides access to the dispatcher that runs on the main task.
* @warning This dispatcher is used for WiFi and might block for some time during WiFi connection.
* @return the dispatcher
*/
Dispatcher& getMainDispatcher(); Dispatcher& getMainDispatcher();
} // namespace } // namespace
namespace tt::hal { namespace tt::hal {
/** Can be called after initHeadless() is called. Will crash otherwise. */
const Configuration& getConfiguration(); const Configuration& getConfiguration();
} // namespace } // namespace

View File

@ -8,6 +8,10 @@ namespace tt::service {
class Paths; class Paths;
/**
* The public representation of a service instance.
* @warning Do not store references or pointers to these! You can retrieve them via the Loader service.
*/
class ServiceContext { class ServiceContext {
protected: protected:
@ -16,9 +20,13 @@ protected:
public: public:
/** @return a reference ot the service's manifest */
virtual const service::ServiceManifest& getManifest() const = 0; virtual const service::ServiceManifest& getManifest() const = 0;
virtual std::shared_ptr<void> getData() const = 0; /** @return a shared pointer to the data that is attached to the service */
virtual std::shared_ptr<void> _Nullable getData() const = 0;
/** Set the data for a service. */
virtual void setData(std::shared_ptr<void> newData) = 0; virtual void setData(std::shared_ptr<void> newData) = 0;
/** Retrieve the paths that are relevant to this service */
virtual std::unique_ptr<Paths> getPaths() const = 0; virtual std::unique_ptr<Paths> getPaths() const = 0;
}; };

View File

@ -9,22 +9,16 @@ class ServiceContext;
typedef void (*ServiceOnStart)(ServiceContext& service); typedef void (*ServiceOnStart)(ServiceContext& service);
typedef void (*ServiceOnStop)(ServiceContext& service); typedef void (*ServiceOnStop)(ServiceContext& service);
/** A ledger that describes the main parts of a service. */
struct ServiceManifest { struct ServiceManifest {
/** /** The identifier by which the app is launched by the system and other apps. */
* The identifier by which the app is launched by the system and other apps.
*/
std::string id {}; std::string id {};
/** /** Non-blocking method to call when service is started. */
* Non-blocking method to call when service is started.
*/
const ServiceOnStart onStart = nullptr; const ServiceOnStart onStart = nullptr;
/** /** Non-blocking method to call when service is stopped. */
* Non-blocking method to call when service is stopped.
*/
const ServiceOnStop onStop = nullptr; const ServiceOnStop onStop = nullptr;
}; };
} // namespace } // namespace

View File

@ -4,17 +4,40 @@
namespace tt::service { namespace tt::service {
typedef void (*ManifestCallback)(const ServiceManifest*, void* context);
void initRegistry(); void initRegistry();
/** Register a service.
* @param[in] the service manifest
*/
void addService(const ServiceManifest* manifest); void addService(const ServiceManifest* manifest);
/** Unregister a service.
* @param[in] the service manifest
*/
void removeService(const ServiceManifest* manifest); void removeService(const ServiceManifest* manifest);
/** Start a service.
* @param[in] the service id as defined in its manifest
* @return true on success
*/
bool startService(const std::string& id); bool startService(const std::string& id);
/** Stop a service.
* @param[in] the service id as defined in its manifest
* @return true on success or false when service wasn't running.
*/
bool stopService(const std::string& id); bool stopService(const std::string& id);
/** Find a service manifest by its id.
* @param[in] id the id as defined in the manifest
* @return the matching manifest or nullptr when it wasn't found
*/
const ServiceManifest* _Nullable findManifestId(const std::string& id); const ServiceManifest* _Nullable findManifestId(const std::string& id);
/** Find a service by its manifest id.
* @param[in] id the id as defined in the manifest
* @return the service context or nullptr when it wasn't found
*/
ServiceContext* _Nullable findServiceById(const std::string& id); ServiceContext* _Nullable findServiceById(const std::string& id);
} // namespace } // namespace

View File

@ -84,53 +84,42 @@ WifiRadioState getRadioState();
*/ */
void scan(); void scan();
/** /** @return true if wifi is actively scanning */
* @return true if wifi is actively scanning
*/
bool isScanning(); bool isScanning();
/** /** @return true the ssid name or empty string */
* @return true the ssid name or empty string
*/
std::string getConnectionTarget(); std::string getConnectionTarget();
/** /** @return the access points from the last scan (if any). It only contains public APs. */
* @brief Returns the access points from the last scan (if any). It only contains public APs.
*/
std::vector<WifiApRecord> getScanResults(); std::vector<WifiApRecord> getScanResults();
/** /**
* @brief Overrides the default scan result size of 16. * @brief Overrides the default scan result size of 16.
* @param records the record limit for the scan result (84 bytes per record!) * @param[in] records the record limit for the scan result (84 bytes per record!)
*/ */
void setScanRecords(uint16_t records); void setScanRecords(uint16_t records);
/** /**
* @brief Enable/disable the radio. Ignores input if desired state matches current state. * @brief Enable/disable the radio. Ignores input if desired state matches current state.
* @param enabled * @param[in] enabled
*/ */
void setEnabled(bool enabled); void setEnabled(bool enabled);
/** /**
* @brief Connect to a network. Disconnects any existing connection. * @brief Connect to a network. Disconnects any existing connection.
* Returns immediately but runs in the background. Results are through pubsub. * Returns immediately but runs in the background. Results are through pubsub.
* @param ap * @param[in] ap
* @param[in] remember whether to save the ap data to the settings upon successful connection
*/ */
void connect(const settings::WifiApSettings* ap, bool remember); void connect(const settings::WifiApSettings* ap, bool remember);
/** /** @brief Disconnect from the access point. Doesn't have any effect when not connected. */
* @brief Disconnect from the access point. Doesn't have any effect when not connected.
*/
void disconnect(); void disconnect();
/** /** @return true if the connection isn't unencrypted. */
* Return true if the connection isn't unencrypted.
*/
bool isConnectionSecure(); bool isConnectionSecure();
/** /** @return the RSSI value (negative number) or return 1 when not connected. */
* Returns the RSSI value (negative number) or return 1 when not connected
*/
int getRssi(); int getRssi();
} // namespace } // namespace

View File

@ -780,7 +780,7 @@ static void dispatchConnect(std::shared_ptr<void> context) {
TT_LOG_I(TAG, "Waiting for EventFlag by event_handler()"); TT_LOG_I(TAG, "Waiting for EventFlag by event_handler()");
if (bits & WIFI_CONNECTED_BIT) { if (bits & WIFI_CONNECTED_BIT) {
wifi->setSecureConnection(wifi_config.sta.password[0] != 0x00); wifi->setSecureConnection(wifi_config.sta.password[0] != 0x00U);
wifi->setRadioState(WIFI_RADIO_CONNECTION_ACTIVE); wifi->setRadioState(WIFI_RADIO_CONNECTION_ACTIVE);
publish_event_simple(wifi, WifiEventTypeConnectionSuccess); publish_event_simple(wifi, WifiEventTypeConnectionSuccess);
TT_LOG_I(TAG, "Connected to %s", wifi->connection_target.ssid); TT_LOG_I(TAG, "Connected to %s", wifi->connection_target.ssid);