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,
.apps = {
&hello_world_app,
},
.services = {},
.autoStartAppId = nullptr
}
};
#ifdef ESP_PLATFORM

View File

@ -41,8 +41,8 @@ bool lvgl_task_is_running() {
return result;
}
static bool lvgl_lock(uint32_t timeout_ticks) {
return lvgl_mutex.acquire(timeout_ticks) == tt::TtStatusOk;
static bool lvgl_lock(uint32_t timeoutMillis) {
return lvgl_mutex.acquire(pdMS_TO_TICKS(timeoutMillis)) == tt::TtStatusOk;
}
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.
- ESP32 (CYD) memory issues (or any device without PSRAM):
- Boot app doesn't show logo
@ -11,8 +11,17 @@
- 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).
- 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
- 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.
- 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.
@ -37,6 +46,8 @@
- Support hot-plugging SD card
# Nice-to-haves
- OTA updates
- Web flasher
- T-Deck Plus: Create separate board config?
- Support for displays with different DPI. Consider the layer-based system like on Android.
- Make firmwares available via web serial website

View File

@ -7,17 +7,23 @@
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;
// List of user applications
const app::AppManifest* const apps[TT_CONFIG_APPS_LIMIT];
const service::ServiceManifest* const services[TT_CONFIG_SERVICES_LIMIT];
const char* autoStartAppId;
} Configuration;
/** List of user applications */
const app::AppManifest* const apps[TT_CONFIG_APPS_LIMIT] = {};
/** List of user services */
const service::ServiceManifest* const services[TT_CONFIG_SERVICES_LIMIT] = {};
/** Optional app to start automatically after the splash screen. */
const char* _Nullable autoStartAppId = nullptr;
};
/**
* Attempts to initialize Tactility and all configured hardware.
* @param config
* @param[in] config
*/
void run(const Configuration& config);

View File

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

View File

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

View File

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

View File

@ -6,8 +6,16 @@
namespace tt::app {
/** Register an application with its 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);
/** @return a list of all registered apps. This includes user and system apps. */
std::vector<const AppManifest*> getApps();
} // namespace

View File

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

View File

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

View File

@ -6,12 +6,23 @@
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)();
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();
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.
*
* @param app
* @param on_show
* @param on_hide
* @param[in] app
* @param[in] onShow
* @param[in] onHide
*/
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.
@ -25,7 +24,7 @@ void hideApp();
/**
* 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);
@ -47,7 +46,7 @@ bool keyboardIsEnabled();
* Glue code for the on-screen keyboard and the hardware keyboard:
* - Attach automatic hide/show parameters for the on-screen keyboard.
* - Registers the textarea to the default lv_group_t for hardware keyboards.
* @param textarea
* @param[in] textarea
*/
void keyboardAddTextArea(lv_obj_t* textarea);

View File

@ -26,26 +26,22 @@ typedef struct ViewPort {
} ViewPort;
/** ViewPort allocator
*
* always returns view_port or stops system if not enough memory.
* @param app
* @param on_show Called to create LVGL widgets
* @param on_hide Called before clearing the LVGL widget parent
*
* @return ViewPort instance
* @param onShow Called to create LVGL widgets
* @param onHide Called before clearing the LVGL widget parent
* @return ViewPort instance
*/
ViewPort* view_port_alloc(
app::AppContext& app,
ViewPortShowCallback on_show,
ViewPortHideCallback on_hide
ViewPortShowCallback onShow,
ViewPortHideCallback onHide
);
/** ViewPort deallocator
*
/** ViewPort destruction
* Ensure that view_port was unregistered in GUI system before use.
*
* @param view_port ViewPort instance
* @param viewPort ViewPort instance
*/
void view_port_free(ViewPort* view_port);
void view_port_free(ViewPort* viewPort);
} // 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);
/**
* @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();
/** @return the currently running app (it is only ever null before the splash screen is shown) */
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();
if (!scoped_lockable->lock(50 / portTICK_PERIOD_MS)) {
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()) {
task = std::make_unique<ScreenshotTask>();
mode = ScreenshotModeTimed;
task->startTimed(path, delay_in_seconds, amount);
task->startTimed(path, delayInSeconds, amount);
} else {
TT_LOG_W(TAG, "Screenshot task already running");
}

View File

@ -16,8 +16,10 @@ typedef enum {
ScreenshotModeApps
} Mode;
class ScreenshotService {
private:
Mutex mutex;
std::unique_ptr<ScreenshotTask> task;
Mode mode = ScreenshotModeNone;
@ -25,9 +27,23 @@ class ScreenshotService {
public:
bool isTaskStarted();
/** The state of the service. */
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 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();
};

View File

@ -35,22 +35,18 @@ public:
~ScreenshotTask();
/** @brief Start taking screenshots after a certain delay
* @param task the screenshot task
* @param path the path to store the screenshots at
* @param delay_in_seconds the delay before starting (and between successive screenshots)
* @param amount 0 = indefinite, >0 for a specific
* @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 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
* @param task the screenshot task
* @param path the path to store the screenshots at
* @param[in] path the path to store the screenshots at
*/
void startApps(const char* path);
/** @brief Stop taking screenshots
* @param task the screenshot task
*/
/** @brief Stop taking screenshots */
void stop();
void taskMain();

View File

@ -13,11 +13,11 @@ void tt_message_queue_free(MessageQueueHandle 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);
}
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);
}

View File

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

View File

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

View File

@ -1,5 +1,7 @@
#pragma once
#include <freertos/FreeRTOS.h>
#ifdef __cplusplus
extern "C" {
#endif
@ -16,7 +18,7 @@ enum TtMutexType {
MutexHandle tt_mutex_alloc(enum TtMutexType);
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);
#ifdef __cplusplus

View File

@ -5,7 +5,7 @@ extern "C" {
#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);
}
@ -13,7 +13,7 @@ void tt_semaphore_free(SemaphoreHandle 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);
}

View File

@ -1,5 +1,7 @@
#pragma once
#include <freertos/FreeRTOS.h>
#ifdef __cplusplus
extern "C" {
#endif
@ -9,9 +11,9 @@ extern "C" {
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);
bool tt_semaphore_acquire(SemaphoreHandle handle, uint32_t timeoutTicks);
bool tt_semaphore_acquire(SemaphoreHandle handle, TickType_t timeoutTicks);
bool tt_semaphore_release(SemaphoreHandle handle);
uint32_t tt_semaphore_get_count(SemaphoreHandle handle);

View File

@ -29,11 +29,11 @@ void tt_timer_free(TimerHandle handle) {
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);
}
bool tt_timer_restart(TimerHandle handle, uint32_t intervalTicks) {
bool tt_timer_restart(TimerHandle handle, TickType_t 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();
}
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(
callback,
callbackContext,
arg
callbackArg,
(TickType_t)timeoutTicks
);
}

View File

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

View File

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

View File

@ -40,9 +40,8 @@ namespace tt {
tt::_crash(); \
} while (0)
/** Halt system
*
* @param optional message (const char*)
/** Halt the system
* @param[in] optional message (const char*)
*/
#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
*
* @param condition to check
* @param optional message (const char*)
* @param[in] condition to check
* @param[in] optional message (const char*)
*/
#define tt_check(x, ...) if (!(x)) { TT_LOG_E("check", "Failed: %s", #x); tt::_crash(); }
@ -89,10 +88,7 @@ namespace tt {
#endif
/** Assert condition and crash if failed
*
* @warning only will do check if firmware compiled in debug mode
*
* @param condition to check
* @param optional message (const char*)
* @warning only will do check if firmware compiled in debug mode
* @param[in] condition to check
*/
#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_ARGCOUNT5(X,...) 5
#define _TT_ARGCOUNT(X) X
// endregion

View File

@ -1,5 +1,10 @@
#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) \
({ \
__typeof__(a) _a = (a); \
@ -7,6 +12,11 @@
_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) \
({ \
__typeof__(a) _a = (a); \
@ -14,49 +24,12 @@
_a < _b ? _a : _b; \
})
/** @return the absolute value of the input */
#define TT_ABS(a) ({ (a) < 0 ? -(a) : (a); })
#define TT_ROUND_UP_TO(a, b) \
({ \
__typeof__(a) _a = (a); \
__typeof__(b) _b = (b); \
_a / _b + !!(_a % _b); \
})
/** Clamp a value between a min and a max.
* @param[in] x value to clamp
* @param[in] upper upper bounds for x
* @param[in] lower lower bounds for x
*/
#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();
}
void Dispatcher::dispatch(Callback callback, std::shared_ptr<void> context) {
auto message = std::make_shared<DispatcherMessage>(callback, std::move(context));
void Dispatcher::dispatch(Function function, std::shared_ptr<void> context) {
auto message = std::make_shared<DispatcherMessage>(function, std::move(context));
// Mutate
if (mutex.lock(1000 / portTICK_PERIOD_MS)) {
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
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);
} else {
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);
@ -59,7 +59,7 @@ uint32_t Dispatcher::consume(uint32_t timeout_ticks) {
processing = !queue.empty();
// Don't keep lock as callback might be slow
tt_check(mutex.unlock());
item->callback(item->context);
item->function(item->context);
} else {
processing = false;
tt_check(mutex.unlock());

View File

@ -13,16 +13,24 @@
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 {
public:
typedef void (*Function)(std::shared_ptr<void> data);
private:
struct DispatcherMessage {
Callback callback;
Function function;
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) :
callback(callback),
DispatcherMessage(Function function, std::shared_ptr<void> context) :
function(function),
context(std::move(context))
{}
@ -38,8 +46,19 @@ public:
explicit 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

View File

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

View File

@ -19,7 +19,7 @@ public:
/**
* 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). */
void start();

View File

@ -1,22 +1,32 @@
#pragma once
#include "Check.h"
#include "RtosCompat.h"
#include <memory>
namespace tt {
class ScopedLockableUsage;
/** Represents a lock/mutex */
class Lockable {
public:
virtual ~Lockable() = default;
virtual bool lock(uint32_t timeoutTicks) const = 0;
virtual bool lock(TickType_t timeoutTicks) const = 0;
virtual bool unlock() const = 0;
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 {
const Lockable& lockable;
@ -29,7 +39,7 @@ public:
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);
}

View File

@ -12,6 +12,7 @@
namespace tt {
/** Used for log output filtering */
enum LogLevel {
LogLevelNone, /*!< No log output */
LogLevelError, /*!< Critical errors, software module can not recover on its own */
@ -26,6 +27,10 @@ struct LogEntry {
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);
} // namespace tt

View File

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

View File

@ -36,7 +36,8 @@ public:
~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] timeoutTicks
* @return success result
@ -44,7 +45,7 @@ public:
bool put(const void* message, uint32_t timeoutTicks);
/** 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
* @return success result
*/

View File

@ -44,7 +44,7 @@ Mutex::~Mutex() {
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(semaphore);
@ -114,7 +114,7 @@ void tt_mutex_free(Mutex* 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);
}

View File

@ -13,8 +13,6 @@
namespace tt {
class ScopedMutexUsage;
/**
* Wrapper for FreeRTOS xSemaphoreCreateMutex and xSemaphoreCreateRecursiveMutex
* Can be used in IRQ mode (within ISR context)
@ -38,56 +36,64 @@ public:
explicit Mutex(Type type = TypeNormal);
~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;
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; }
/** @return the owner of the thread */
ThreadId getOwner() const;
};
/** Allocate Mutex
*
* @param[in] type The mutex type
*
* @return pointer to Mutex instance
* @param[in] type The mutex type
* @return pointer to Mutex instance
*/
[[deprecated("use class")]]
Mutex* tt_mutex_alloc(Mutex::Type type);
/** Free Mutex
*
* @param mutex The Mutex instance
* @param[in] mutex The Mutex instance
*/
[[deprecated("use class")]]
void tt_mutex_free(Mutex* mutex);
/** Acquire mutex
*
* @param mutex The Mutex instance
* @param[in] timeout The timeout
*
* @return The status.
* @param[in] mutex
* @param[in] timeout
* @return the status result
*/
[[deprecated("use class")]]
TtStatus tt_mutex_acquire(Mutex* mutex, uint32_t timeout);
TtStatus tt_mutex_acquire(Mutex* mutex, TickType_t timeout);
/** Release mutex
*
* @param mutex The Mutex instance
*
* @return The status.
* @param[in] mutex The Mutex instance
* @return the status result
*/
[[deprecated("use class")]]
TtStatus tt_mutex_release(Mutex* mutex);
/** Get mutex owner thread id
*
* @param mutex The Mutex instance
*
* @return The thread identifier.
* @param[in] mutex The Mutex instance
* @return The thread identifier.
*/
[[deprecated("use class")]]
ThreadId tt_mutex_get_owner(Mutex* mutex);

View File

@ -3,12 +3,12 @@
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);
PubSubSubscription subscription = {
.id = (++pubsub->last_id),
.callback = callback,
.callback_context = callback_context
.callback_context = callbackContext
};
pubsub->items.push_back(
subscription

View File

@ -30,34 +30,27 @@ struct PubSub {
};
/** Subscribe to PubSub
*
* Threadsafe, Reentrable
*
* @param pubsub pointer to PubSub instance
* @param[in] callback The callback
* @param callback_context The callback context
*
* @return pointer to PubSubSubscription instance
* @param[in] pubsub pointer to PubSub instance
* @param[in] callback
* @param[in] callbackContext the data to pass to the callback
* @return subscription instance
*/
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
*
* No use of `pubsub_subscription` allowed after call of this method
* Threadsafe, Reentrable.
*
* @param pubsub pointer to PubSub instance
* @param pubsub_subscription pointer to PubSubSubscription instance
* No use of `tt_pubsub_subscription` allowed after call of this method
* Threadsafe, Re-entrable.
* @param[in] pubsub
* @param[in] subscription
*/
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
*
* Threadsafe, Reentrable.
*
* @param pubsub pointer to PubSub instance
* @param message message pointer to publish
* @param[in] pubsub
* @param[in] message message pointer to publish - it is passed as-is to the callback
*/
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 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 triggerLevel The number of bytes that must be in the stream buffer
* @param[in] size The total number of bytes the stream buffer will be able to hold at any one time.
* @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.
* @return The stream buffer instance.
*/
@ -50,7 +50,7 @@ public:
* stream buffer before a task that is blocked on the stream buffer to
* 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 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.
* 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 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] data A pointer to the data that is to be copied into the stream buffer.
* @param[in] length The maximum number of bytes to copy from data into the stream buffer.
* @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.
* Will return immediately if timeout is zero.
* Setting timeout to TtWaitForever will cause the task to wait indefinitely.
@ -79,10 +79,10 @@ public:
* @brief Receives bytes from a stream buffer.
* 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.
* @param 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] length The length of the buffer pointed to by the data parameter.
* @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.
* Will return immediately if timeout is zero.
* Setting timeout to TtWaitForever will cause the task to wait indefinitely.

View File

@ -7,13 +7,7 @@
#include <cstdint>
#include <string>
#ifdef ESP_PLATFORM
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#else
#include "FreeRTOS.h"
#include "task.h"
#endif
#include "RtosCompatTask.h"
namespace tt {
@ -47,14 +41,14 @@ public:
typedef int32_t (*Callback)(void* context);
/** Write to stdout callback
* @param data pointer to data
* @param size data size @warning your handler must consume everything
* @param[in] data pointer to data
* @param[in] size data size @warning your handler must consume everything
*/
typedef void (*StdoutWriteCallback)(const char* data, size_t size);
/** Thread state change callback called upon thread state change
* @param state new thread state
* @param context callback context
* @param[in] state new thread state
* @param[in] context callback context
*/
typedef void (*StateCallback)(State state, void* context);
@ -85,11 +79,10 @@ public:
Thread();
/** Allocate Thread, shortcut version
* @param name
* @param stack_size
* @param callback
* @param context
* @param[in] name
* @param[in] stack_size
* @param[in] callback
* @param[in] callbackContext
* @return Thread*
*/
Thread(
@ -102,8 +95,7 @@ public:
~Thread();
/** Set Thread name
*
* @param name string
* @param[in] name string
*/
void setName(const std::string& name);
@ -118,75 +110,49 @@ public:
bool isMarkedAsStatic() const;
/** Set Thread stack size
*
* @param thread Thread instance
* @param stackSize stack size in bytes
* @param[in] stackSize stack size in bytes
*/
void setStackSize(size_t stackSize);
/** Set Thread callback
*
* @param thread Thread instance
* @param callback ThreadCallback, called upon thread run
* @param callbackContext what to pass to the callback
* @param[in] callback ThreadCallback, called upon thread run
* @param[in] callbackContext what to pass to the callback
*/
void setCallback(Callback callback, _Nullable void* callbackContext = nullptr);
/** Set Thread priority
*
* @param thread Thread instance
* @param priority ThreadPriority value
* @param[in] priority ThreadPriority value
*/
void setPriority(Priority priority);
/** Set Thread state change callback
*
* @param thread Thread instance
* @param callback state change callback
* @param context pointer to context
* @param[in] callback state change callback
* @param[in] callbackContext pointer to context
*/
void setStateCallback(StateCallback callback, _Nullable void* callbackContext = nullptr);
/** Get Thread state
*
* @param thread Thread instance
*
* @return thread state from ThreadState
*/
State getState() const;
/** Start Thread
*
* @param thread Thread instance
*/
void start();
/** Join Thread
*
* @warning Use this method only when CPU is not busy(Idle task receives
* control), otherwise it will wait forever.
*
* @param thread Thread instance
*
* @warning Use this method only when CPU is not busy (Idle task receives control), otherwise it will wait forever.
* @return success result
*/
bool join();
/** Get FreeRTOS ThreadId for Thread instance
*
* @param thread Thread instance
*
* @return ThreadId or nullptr
*/
ThreadId getId();
/** Get thread return code
*
* @param thread Thread instance
*
* @return return code
*/
/** @return thread return code */
int32_t getReturnCode();
private:
@ -200,31 +166,17 @@ private:
#define THREAD_PRIORITY_ISR (TT_CONFIG_THREAD_MAX_PRIORITIES - 1)
/** Set current thread priority
*
* @param priority ThreadPriority value
* @param[in] priority ThreadPriority value
*/
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 related methods that doesn't involve Thread directly */
/** Get FreeRTOS ThreadId for current thread
*
* @param thread Thread instance
*
* @return ThreadId or NULL
*/
/** @return FreeRTOS ThreadId or NULL */
ThreadId thread_get_current_id();
/** Get Thread instance for current thread
*
* @return pointer to Thread or NULL if this thread doesn't belongs to Tactility
*/
/** @return pointer to Thread instance or NULL if this thread doesn't belongs to Tactility */
Thread* thread_get_current();
/** 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
*
* @param thread_id
* @param[in] threadId
* @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
*
* @param thread_id
* @param[in] threadId
* @return uint32_t
*/
uint32_t thread_get_stack_space(ThreadId thread_id);
uint32_t thread_get_stack_space(ThreadId threadId);
/** Suspend thread
*
* @param thread_id thread id
* @param[in] threadId thread id
*/
void thread_suspend(ThreadId thread_id);
void thread_suspend(ThreadId threadId);
/** Resume thread
*
* @param thread_id thread id
* @param[in] threadId thread id
*/
void thread_resume(ThreadId thread_id);
void thread_resume(ThreadId threadId);
/** Get thread suspended state
*
* @param thread_id thread id
* @param[in] threadId thread id
* @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
*
* @param thread_id thread id
* @param[in] threadId thread id
* @return true if thread memory is static
*/
bool thread_mark_is_static(ThreadId thread_id);
bool thread_mark_is_static(ThreadId threadId);
} // 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) {
tt_assert((kernel::isIrq() == 0U) && (callback != nullptr));
tt_assert((!TT_IS_ISR()) && (callback != nullptr));
this->callback = callback;
this->callbackContext = std::move(callbackContext);
@ -33,48 +33,49 @@ Timer::Timer(Type type, Callback callback, std::shared_ptr<void> callbackContext
}
Timer::~Timer() {
tt_assert(!kernel::isIrq());
tt_assert(!TT_IS_ISR());
tt_check(xTimerDelete(timerHandle, portMAX_DELAY) == pdPASS);
}
bool Timer::start(uint32_t intervalTicks) {
tt_assert(!kernel::isIrq());
tt_assert(!TT_IS_ISR());
tt_assert(intervalTicks < portMAX_DELAY);
return xTimerChangePeriod(timerHandle, intervalTicks, portMAX_DELAY) == pdPASS;
}
bool Timer::restart(uint32_t intervalTicks) {
tt_assert(!kernel::isIrq());
tt_assert(!TT_IS_ISR());
tt_assert(intervalTicks < portMAX_DELAY);
return xTimerChangePeriod(timerHandle, intervalTicks, portMAX_DELAY) == pdPASS &&
xTimerReset(timerHandle, portMAX_DELAY) == pdPASS;
}
bool Timer::stop() {
tt_assert(!kernel::isIrq());
tt_assert(!TT_IS_ISR());
return xTimerStop(timerHandle, portMAX_DELAY) == pdPASS;
}
bool Timer::isRunning() {
tt_assert(!kernel::isIrq());
tt_assert(!TT_IS_ISR());
return xTimerIsTimerActive(timerHandle) == pdTRUE;
}
uint32_t Timer::getExpireTime() {
tt_assert(!kernel::isIrq());
tt_assert(!TT_IS_ISR());
return (uint32_t)xTimerGetExpiryTime(timerHandle);
}
bool Timer::setPendingCallback(PendingCallback callback, void* callbackContext, uint32_t arg) {
if (kernel::isIrq()) {
return xTimerPendFunctionCallFromISR(callback, callbackContext, arg, nullptr) == pdPASS;
bool Timer::setPendingCallback(PendingCallback callback, void* callbackContext, uint32_t callbackArg, TickType_t timeout) {
if (TT_IS_ISR()) {
assert(timeout == 0);
return xTimerPendFunctionCallFromISR(callback, callbackContext, callbackArg, nullptr) == pdPASS;
} else {
return xTimerPendFunctionCall(callback, callbackContext, arg, TtWaitForever) == pdPASS;
return xTimerPendFunctionCall(callback, callbackContext, callbackArg, timeout) == pdPASS;
}
}
void Timer::setThreadPriority(ThreadPriority priority) {
tt_assert(!kernel::isIrq());
tt_assert(!TT_IS_ISR());
TaskHandle_t task_handle = xTimerGetTimerDaemonTaskHandle();
tt_assert(task_handle); // Don't call this method before timer task start

View File

@ -33,55 +33,44 @@ public:
~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
* @return success result
*/
bool start(uint32_t intervalTicks);
/** 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
*
* @return success result
*/
bool restart(uint32_t intervalTicks);
/** 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
*/
bool stop();
/** 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
*/
bool isRunning();
/** Get timer expire time
*
* @param instance The Timer instance
*
* @return expire tick
*/
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 {
TimerThreadPriorityNormal, /**< Lower then other threads */
@ -89,7 +78,6 @@ public:
} ThreadPriority;
/** Set Timer thread priority
*
* @param[in] priority The 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
* @param[in] in_left input buffer for XOR
* @param[in] in_right second input buffer for XOR
* @param[in] inLeft input buffer for XOR
* @param[in] inRight second input buffer for XOR
* @param[out] out output buffer for result of XOR
* @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) {
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.");
#endif
#ifdef ESP_PLATFORM
uint8_t hardware_key[32];
uint8_t nvs_key[32];
#ifdef ESP_PLATFORM
get_hardware_key(hardware_key);
get_nvs_key(nvs_key);
xorKey(hardware_key, nvs_key, key, 32);
@ -125,10 +125,10 @@ static void getKey(uint8_t key[32]) {
#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);
uint8_t* data_bytes = (uint8_t*)data;
for (int i = 0; i < data_length; ++i) {
auto* data_bytes = (uint8_t*)data;
for (int i = 0; i < dataLength; ++i) {
size_t safe_index = i % 16;
iv[safe_index] %= data_bytes[i];
}
@ -160,8 +160,8 @@ static int aes256CryptCbc(
return result;
}
int encrypt(const uint8_t iv[16], uint8_t* in_data, uint8_t* out_data, size_t length) {
tt_check(length % 16 == 0, "Length is not a multiple of 16 bytes (for AES 256");
int encrypt(const uint8_t iv[16], uint8_t* inData, uint8_t* outData, size_t dataLength) {
tt_check(dataLength % 16 == 0, "Length is not a multiple of 16 bytes (for AES 256");
uint8_t key[32];
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];
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) {
tt_check(length % 16 == 0, "Length is not a multiple of 16 bytes (for AES 256");
int decrypt(const uint8_t iv[16], uint8_t* inData, uint8_t* outData, size_t dataLength) {
tt_check(dataLength % 16 == 0, "Length is not a multiple of 16 bytes (for AES 256");
uint8_t key[32];
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];
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

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.
* @param data input data
* @param data_length input data length
* @param iv output IV
* @param[in] data input data
* @param[in] dataLength input data length
* @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.
@ -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: input and output data must be aligned to 16 bytes.
*
* @param iv the AES IV
* @param data_in input data
* @param data_out output data
* @param length data length, a multiple of 16
* @param[in] iv the AES IV
* @param[in] inData input data
* @param[out] outData output data
* @param[in] dataLength data length, a multiple of 16 (for both inData and outData)
* @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.
@ -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: input and output data must be aligned to 16 bytes.
*
* @param iv AES IV
* @param data_in input data
* @param data_out output data
* @param length data length, a multiple of 16
* @param[in] iv AES IV
* @param[in] inData input data
* @param[out] outData output data
* @param[in] dataLength data length, a multiple of 16 (for both inData and outData)
* @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

View File

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

View File

@ -26,6 +26,11 @@ long getSize(FILE* file) {
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) {
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) {
buffer_offset += bytes_read;
} else { // Something went wrong?
data = nullptr;
break;
}
}
outSize = buffer_offset;
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) {
@ -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) {
size_t size = 0;
auto data = readBinaryInternal(filepath, size, 1);
if (data == nullptr) {
return nullptr;
} else if (size > 0) {
if (data != nullptr) {
data.get()[size] = 0; // Append null terminator
return data;
} else { // Empty file: return empty string
auto value = std::make_unique<uint8_t[]>(1);
value[0] = 0;
return value;
} else {
return nullptr;
}
}

View File

@ -7,7 +7,17 @@ namespace tt::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);
/** 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);
}

View File

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

View File

@ -10,87 +10,56 @@
namespace tt::kernel {
/** Recognized platform types */
typedef enum {
PlatformEsp,
PlatformSimulator
} 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
*
* @return true if running, false otherwise
* @return true if the FreeRTOS kernel is running, false otherwise
*/
bool isRunning();
/** Lock kernel, pause process scheduling
*
* @warning This should never be called in interrupt request context.
*
* @return previous lock state(0 - unlocked, 1 - locked)
* @warning don't call from ISR context
* @return previous lock state(0 - unlocked, 1 - locked)
*/
int32_t lock();
/** Unlock kernel, resume process scheduling
*
* @warning This should never be called in interrupt request context.
*
* @return previous lock state(0 - unlocked, 1 - locked)
* @warning don't call from ISR context
* @return previous lock state(0 - unlocked, 1 - locked)
*/
int32_t unlock();
/** Restore kernel lock state
*
* @warning This should never be called in interrupt request context.
*
* @param[in] lock The lock state
*
* @return new lock state or error
* @warning don't call from ISR context
* @param[in] lock The lock state
* @return new lock state or error
*/
int32_t restoreLock(int32_t lock);
/** Get kernel systick frequency
*
* @return systick counts per second
* @return systick counts per second
*/
uint32_t getTickFrequency();
TickType_t getTicks();
/** Delay execution
*
* @warning This should never be called in interrupt request context.
*
* @warning don't call from ISR context
* 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);
/** Delay until tick
*
* @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.
* @warning don't call from ISR context
* @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
*
@ -100,26 +69,22 @@ TtStatus delayUntilTick(uint32_t tick);
TickType_t millisToTicks(uint32_t milliSeconds);
/** Delay in milliseconds
*
* 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.
* Special value: 0, will cause task yield.
* Also if used when kernel is not running will fall back to `tt_delay_us`.
*
* @warning Cannot be used from ISR
*
* @param[in] milliSeconds milliseconds to wait
* Also if used when kernel is not running will fall back to delayMicros()
* @warning don't call from ISR context
* @param[in] milliSeconds milliseconds to wait
*/
void delayMillis(uint32_t milliSeconds);
/** Delay in microseconds
*
* 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);
/** @return the platform that Tactility currently is running on. */
Platform getPlatform();
} // namespace

View File

@ -2,7 +2,6 @@
#include "PanicHandler.h"
#include <esp_rom_sys.h>
#include <esp_debug_helpers.h>
#include <esp_attr.h>
#include <esp_memory_utils.h>
@ -10,6 +9,10 @@
extern "C" {
/**
* This static variable survives a crash reboot.
* It is reset by the Boot app.
*/
static RTC_NOINIT_ATTR CrashData crashData;
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

View File

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

View File

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

View File

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

View File

@ -7,14 +7,20 @@
namespace tt {
/** Initialize the hardware and started the internal services. */
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();
} // namespace
namespace tt::hal {
/** Can be called after initHeadless() is called. Will crash otherwise. */
const Configuration& getConfiguration();
} // namespace

View File

@ -8,6 +8,10 @@ namespace tt::service {
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 {
protected:
@ -16,9 +20,13 @@ protected:
public:
/** @return a reference ot the service's manifest */
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;
/** Retrieve the paths that are relevant to this service */
virtual std::unique_ptr<Paths> getPaths() const = 0;
};

View File

@ -9,22 +9,16 @@ class ServiceContext;
typedef void (*ServiceOnStart)(ServiceContext& service);
typedef void (*ServiceOnStop)(ServiceContext& service);
/** A ledger that describes the main parts of a service. */
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 {};
/**
* Non-blocking method to call when service is started.
*/
/** Non-blocking method to call when service is started. */
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;
};
} // namespace

View File

@ -4,17 +4,40 @@
namespace tt::service {
typedef void (*ManifestCallback)(const ServiceManifest*, void* context);
void initRegistry();
/** Register a service.
* @param[in] the service manifest
*/
void addService(const ServiceManifest* manifest);
/** Unregister a service.
* @param[in] the service 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);
/** 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);
/** 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);
/** 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);
} // namespace

View File

@ -84,53 +84,42 @@ WifiRadioState getRadioState();
*/
void scan();
/**
* @return true if wifi is actively scanning
*/
/** @return true if wifi is actively scanning */
bool isScanning();
/**
* @return true the ssid name or empty string
*/
/** @return true the ssid name or empty string */
std::string getConnectionTarget();
/**
* @brief Returns the access points from the last scan (if any). It only contains public APs.
*/
/** @return the access points from the last scan (if any). It only contains public APs. */
std::vector<WifiApRecord> getScanResults();
/**
* @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);
/**
* @brief Enable/disable the radio. Ignores input if desired state matches current state.
* @param enabled
* @param[in] enabled
*/
void setEnabled(bool enabled);
/**
* @brief Connect to a network. Disconnects any existing connection.
* 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);
/**
* @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();
/**
* Return true if the connection isn't unencrypted.
*/
/** @return true if the connection isn't unencrypted. */
bool isConnectionSecure();
/**
* Returns the RSSI value (negative number) or return 1 when not connected
*/
/** @return the RSSI value (negative number) or return 1 when not connected. */
int getRssi();
} // 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()");
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);
publish_event_simple(wifi, WifiEventTypeConnectionSuccess);
TT_LOG_I(TAG, "Connected to %s", wifi->connection_target.ssid);