Skip to content

Conversation

@KenVanHoeylandt
Copy link
Member

No description provided.

@coderabbitai
Copy link

coderabbitai bot commented Jan 2, 2026

📝 Walkthrough

Walkthrough

Adds a new TactilityFreeRtos component providing FreeRTOS wrappers (Thread, Timer, Mutex, RecursiveMutex, Semaphore, MessageQueue, Dispatcher, EventGroup, and freertoscompat headers), new CMake targets and tests. Removes many TactilityCore FreeRTOS wrappers and compatibility headers and deletes core implementations (Thread, Timer, MessageQueue, Semaphore, StreamBuffer, Dispatcher, EventFlag, kernel wrappers). Propagates API changes across the codebase: TAG macros → constexpr, several unlock/release functions change return type to void, default timeouts use kernel::MAX_TICKS, timer and thread APIs/signatures adjusted, and Thread ID types renamed to TaskHandle.

Pre-merge checks

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.75% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description check ❓ Inconclusive No description was provided by the author; the pull request description is empty. Consider adding a description explaining the purpose of TactilityFreertos, its role in the system, and key changes made.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: creating a new TactilityFreertos subproject with associated components, headers, and integrations across the codebase.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
Tactility/Source/service/ServiceRegistration.cpp (1)

56-59: Critical: Wrong mutex protects service_instance_map.

The function uses manifest_mutex to protect access to service_instance_map, but based on the naming convention and usage elsewhere (lines 75-77, 85-87, 116-118), it should use instance_mutex instead. This creates a race condition when concurrent access occurs to both maps.

🔎 Proposed fix
 static std::shared_ptr<ServiceInstance> _Nullable findServiceInstanceById(const std::string& id) {
-    manifest_mutex.lock();
+    instance_mutex.lock();
     auto iterator = service_instance_map.find(id);
     auto service = iterator != service_instance_map.end() ? iterator->second : nullptr;
-    manifest_mutex.unlock();
+    instance_mutex.unlock();
     return service;
 }

Note: This issue exists in the unchanged code and is not introduced by this PR, but it's critical enough to address.

TactilityCore/Source/kernel/critical/Critical.cpp (1)

33-41: Critical bug: exit() function uses ENTER_CRITICAL instead of EXIT_CRITICAL.

Line 37 incorrectly uses TT_ENTER_CRITICAL() when it should use the corresponding exit macro. This causes critical section mismanagement, potentially leading to deadlocks or corrupted state. While this is a pre-existing bug (not introduced by this PR), it must be fixed.

🔎 Proposed fix

Add the missing macro definition and fix the exit function:

 #ifdef ESP_PLATFORM
 static portMUX_TYPE critical_mutex;
 #define TT_ENTER_CRITICAL() taskENTER_CRITICAL(&critical_mutex)
+#define TT_EXIT_CRITICAL() taskEXIT_CRITICAL(&critical_mutex)
 #else
 #define TT_ENTER_CRITICAL() taskENTER_CRITICAL()
+#define TT_EXIT_CRITICAL() taskEXIT_CRITICAL()
 #endif
 void exit(CriticalInfo info) {
     if (info.fromIsr) {
         taskEXIT_CRITICAL_FROM_ISR(info.isrm);
     } else if (info.kernelRunning) {
-        TT_ENTER_CRITICAL();
+        TT_EXIT_CRITICAL();
     } else {
         portENABLE_INTERRUPTS();
     }
 }
Tests/TactilityFreeRtos/ThreadTest.cpp (1)

88-99: Fix duplicate test case name.

This test case has the same name as the test at Line 44 ("thread id should only be set at when thread is started"), which will cause issues with test frameworks. This appears to be testing the return code functionality.

🔎 Proposed fix
-TEST_CASE("thread id should only be set at when thread is started") {
+TEST_CASE("thread should return the correct return code") {
TactilityFreeRtos/Include/Tactility/PubSub.h (2)

96-105: Holding mutex during callback invocation creates deadlock risk.

The publish() method holds the lock while invoking callbacks (lines 100-102). If any callback attempts to call subscribe(), unsubscribe(), or publish() on the same PubSub instance, it will deadlock since Mutex is non-recursive.

This contradicts the "Re-entrable" documentation on line 93. Consider copying the callback list under lock, then invoking callbacks without the lock:

🔎 Proposed fix
     void publish(DataType data) {
-        mutex.lock();
-
-        // Iterate over subscribers
-        for (auto& it : items) {
-            it.callback(data);
-        }
-
-        mutex.unlock();
+        mutex.lock();
+        // Copy callbacks to allow re-entrancy
+        auto callbacks = items;
+        mutex.unlock();
+
+        // Invoke callbacks without holding the lock
+        for (auto& it : callbacks) {
+            it.callback(data);
+        }
     }

51-67: subscribe() documentation claims "Re-entrable" but uses non-recursive mutex.

If a callback (invoked during publish()) tries to call subscribe(), it will deadlock. Either update the documentation to clarify the limitation, or use a RecursiveMutex.

Tactility/Source/hal/gps/GpsInit.cpp (1)

106-106: Off-by-one error in buffer position check.

The condition bufferPos == sizeof(buffer) - 1 checks for 13 bytes when CAS_ACK_NACK_MSG_SIZE (14 bytes as per the comment) is expected. This should be bufferPos == sizeof(buffer) or bufferPos >= CAS_ACK_NACK_MSG_SIZE.

🔎 Proposed fix
-        if (bufferPos == sizeof(buffer) - 1) {
+        if (bufferPos == sizeof(buffer)) {
🟡 Minor comments (10)
TactilityC/Source/tt_timer.cpp-28-34 (1)

28-34: API split is well-designed and migration is complete.

The tt_timer_reset and tt_timer_reset_with_interval functions are properly implemented and exported. No remaining references to the removed tt_timer_restart function exist in the codebase, confirming successful migration.

Minor fix needed: Update the documentation comments in the header file (TactilityC/Include/tt_timer.h, lines 43-46 and 51-53). The comments still say "Restart an already started timer" but should say "Reset" to match the new function names.

Tactility/Source/app/gpssettings/GpsSettings.cpp-65-65 (1)

65-65: Timer API pattern is correctly implemented in GpsSettings.cpp, but test code needs updating.

The timer->start() call at line 65 correctly omits the interval parameter since the interval is provided at Timer construction (line 271 with kernel::secondsToTicks(1)). This pattern is consistent across the production codebase.

However, test code in Tests/TactilityFreeRtos/TimerTest.cpp still uses the old API pattern, calling timer->start(interval_value) with interval parameters. These tests should be updated to call timer->start() without parameters to align with the new API.

Tactility/Source/service/gui/GuiService.cpp-183-184 (1)

183-184: Unused variable task_handle.

The task_handle is assigned but never used. This appears to be leftover code from a previous implementation.

🔎 Proposed fix
-    auto task_handle = thread->getTaskHandle();
     threadFlags.set(GUI_THREAD_FLAG_EXIT);
Tactility/Source/service/gui/GuiService.cpp-196-197 (1)

196-197: Unused variable task_handle.

Same issue as in onStop() - the variable is assigned but never used.

🔎 Proposed fix
 void GuiService::requestDraw() {
-    auto task_handle = thread->getTaskHandle();
     threadFlags.set(GUI_THREAD_FLAG_DRAW);
 }
Tactility/Source/service/gui/GuiService.cpp-40-46 (1)

40-46: Variable shadowing: redundant service lookup.

Line 42 declares a new service variable that shadows the service from line 28. Since both refer to the same service, the inner lookup is redundant and potentially confusing.

🔎 Proposed fix
             // Process and dispatch draw call
             if (flags & GUI_THREAD_FLAG_DRAW) {
                 service->threadFlags.clear(GUI_THREAD_FLAG_DRAW);
-                auto service = findService();
-                if (service != nullptr) {
-                    service->redraw();
-                }
+                service->redraw();
             }
TactilityFreeRtos/Include/Tactility/Thread.h-114-118 (1)

114-118: Typo in documentation comment.

Minor typo: "mjust" should be "must".

🔎 Proposed fix
-    /** @warning If thread is running, you mjust call join() first */
+    /** @warning If thread is running, you must call join() first */
TactilityFreeRtos/README.md-5-7 (1)

5-7: Update the license file path from LICENSE.md to ../LICENSE.md.

The file does not exist in the TactilityFreeRtos/ directory. The actual license is in the repository root.

TactilityFreeRtos/Include/Tactility/Semaphore.h-118-119 (1)

118-119: unlock() silently discards failure from release().

The Lock interface requires void unlock(), but release() returns a boolean indicating success/failure. In ISR context, release() can fail (e.g., semaphore already at max count). Silently ignoring this could hide bugs.

Consider adding an assertion or logging on failure, consistent with how other Lock implementations handle this.

🔎 Proposed fix
     /** Calls release() */
-    void unlock() const override { release(); }
+    void unlock() const override {
+        bool success = release();
+        assert(success);
+        (void)success; // Suppress unused warning in release builds
+    }
Tests/TactilityFreeRtos/RecursiveMutexTest.cpp-33-38 (1)

33-38: Missing second unlock() call leaves mutex in locked state.

The test locks the mutex twice but only unlocks once. A RecursiveMutex requires balanced lock/unlock calls; this leaves the mutex partially locked when the test ends, which could affect other tests or leak resources.

🔎 Proposed fix
 TEST_CASE("a RecursiveMutex can be locked more than once from the same context") {
     RecursiveMutex mutex;
     CHECK_EQ(mutex.lock(0), true);
     CHECK_EQ(mutex.lock(0), true);
     mutex.unlock();
+    mutex.unlock();
 }
TactilityFreeRtos/Include/Tactility/MessageQueue.h-119-128 (1)

119-128: Documentation mismatch: reset() returns void but doc says "@return success result".

The documentation states @return success result but the function returns void. Either update the return type to bool (like the TactilityCore version) or fix the documentation.

🔎 Proposed fix (option 1: fix documentation)
     /**
      * Reset queue
      * @warning Don't call this from ISR context
-     * @return success result
      */
     void reset() const {
🧹 Nitpick comments (35)
Tactility/Source/service/screenshot/Screenshot.cpp (1)

15-15: Consider making the TAG type explicit.

While constexpr auto* TAG = "ScreenshotService"; works correctly (auto deduces to const char*), using an explicit type would improve clarity:

-constexpr auto* TAG = "ScreenshotService";
+constexpr const char* TAG = "ScreenshotService";

Alternatively, constexpr std::string_view TAG = "ScreenshotService"; would also be suitable if used consistently across the codebase.

Tactility/Include/Tactility/service/loader/Loader.h (1)

3-11: Include reordering looks good.

The reordered includes maintain all necessary dependencies for the class definition. No headers were removed or added, and all type usage (PubSub, RecursiveMutex, DispatcherThread, Bundle, AppInstance, AppContext, Service) is covered.

One minor note: the system include <memory> is placed after local includes. If your project follows a strict include ordering convention (system headers before local headers), this could be moved to line 3 for consistency.

TactilityC/Source/tt_message_queue.cpp (1)

28-30: Simplify the unnecessary void return statement.

Line 29 returns a void expression, which is valid C++ but unnecessary. Simplify for clarity:

Proposed change
 void tt_message_queue_reset(MessageQueueHandle handle) {
-    return HANDLE_TO_MESSAGE_QUEUE(handle)->reset();
+    HANDLE_TO_MESSAGE_QUEUE(handle)->reset();
 }

The underlying MessageQueue::reset() method returns void, making the return statement redundant.

Tactility/Source/service/ServiceRegistration.cpp (1)

30-36: Consider using RAII for mutex management.

The code uses manual lock() and unlock() calls throughout. Consider using RAII-based lock guards (e.g., std::lock_guard or a custom wrapper) to ensure mutexes are always released, even if an exception is thrown.

Note: This applies to unchanged code and can be addressed in a future refactoring effort.

Also applies to: 48-51, 75-77, 85-87, 116-118

Tactility/Source/kernel/SystemEvents.cpp (2)

59-71: Consider logging lock acquisition failure.

If mutex.lock(kernel::MAX_TICKS) fails, events are silently dropped without any indication. While lock failures should be rare with an indefinite timeout, adding error logging would aid debugging if synchronization issues occur.

🔎 Optional: Add error logging on lock failure
 void publishSystemEvent(SystemEvent event) {
     TT_LOG_I(TAG, "%s", getEventName(event));
 
     if (mutex.lock(kernel::MAX_TICKS)) {
         for (auto& subscription : subscriptions) {
             if (subscription.event == event) {
                 subscription.handler(event);
             }
         }
 
         mutex.unlock();
+    } else {
+        TT_LOG_E(TAG, "Failed to acquire mutex for event %s", getEventName(event));
     }
 }

90-97: Consider logging lock acquisition failure.

If mutex.lock(kernel::MAX_TICKS) fails, the subscription is silently left in place, potentially causing resource leaks or continued event handling. Adding error logging would help diagnose synchronization issues.

🔎 Optional: Add error logging on lock failure
 void unsubscribeSystemEvent(SystemEventSubscription subscription) {
     if (mutex.lock(kernel::MAX_TICKS)) {
         std::erase_if(subscriptions, [subscription](auto& item) {
             return (item.id == subscription);
         });
         mutex.unlock();
+    } else {
+        TT_LOG_E(TAG, "Failed to acquire mutex for unsubscribing subscription %u", subscription);
     }
 }
TactilityC/Source/tt_timer.cpp (1)

57-59: Consider renaming tt_timer_set_thread_priority to align with underlying C++ API.

The wrapper calls setCallbackPriority(), but the C wrapper function retains the name tt_timer_set_thread_priority. Renaming to tt_timer_set_callback_priority would improve clarity and consistency with the underlying implementation. Since the function is not used internally in the codebase, this change would not break internal code, though external users of this C API (if any) would need to update their calls.

Tactility/Source/service/statusbar/Statusbar.cpp (2)

268-270: Refactor lambda capture to avoid circular reference.

The lambda captures a shared_ptr<StatusbarService>, creating a circular reference: StatusbarService owns updateTimer (via unique_ptr), and the timer's callback holds a shared_ptr back to the service. While onStop() breaks this cycle, it artificially inflates the reference count during normal operation and could leak if onStop() is skipped due to an error path.

Since the Timer is owned by the service and destroyed in onStop(), capturing raw this is safe and cleaner.

🔎 Recommended fix
-        updateTimer = std::make_unique<Timer>(Timer::Type::Periodic, pdMS_TO_TICKS(1000), [service] {
-            service->update();
+        updateTimer = std::make_unique<Timer>(Timer::Type::Periodic, pdMS_TO_TICKS(1000), [this] {
+            this->update();
         });

140-140: Consider removing unused mutex.

The mutex member and its lock()/unlock() methods appear unused—no calls are visible in the file. The update() method uses lvgl::lock() directly instead. If this mutex is not needed for future functionality, consider removing it to reduce clutter and memory footprint.

🔎 Proposed cleanup
-    Mutex mutex;
     std::unique_ptr<Timer> updateTimer;
     int8_t gps_icon_id;
-    void lock() const {
-        mutex.lock();
-    }
-
-    void unlock() const {
-        mutex.unlock();
-    }
-
     void updateGpsIcon() {

Also applies to: 153-159

Tactility/Source/network/Http.cpp (1)

34-34: Track the TODO comment.

The TODO comment indicates that there are missing initializer warnings for the esp_http_client_config_t struct. This is a common issue with ESP-IDF structs that have many fields.

Do you want me to open a new issue to track this task?

TactilityFreeRtos/Include/Tactility/freertoscompat/PortCompat.h (1)

6-6: Consider suppressing unused parameter warning.

The macro parameter x is unused, which may trigger compiler warnings. Consider casting to void if the parameter must match the ESP platform signature.

🔎 Proposed change to suppress unused parameter warning
-#define xPortInIsrContext(x) (false)
+#define xPortInIsrContext(x) ((void)(x), false)
LICENSE.md (1)

10-10: Minor grammar improvement: hyphenate "Third party".

Consider changing "Third party" to "Third-party" for proper compound adjective usage.

🔎 Proposed fix
-Third party projects that were included in Tactility retain their licenses.
+Third-party projects that were included in Tactility retain their licenses.
TactilityCore/Include/Tactility/TactilityCore.h (1)

5-10: Inconsistent include styles for internal headers.

Line 5 uses quoted includes ("Tactility/Thread.h") while line 10 uses angle brackets (<Tactility/kernel/Kernel.h>). Consider using a consistent style for internal project headers.

TactilityFreeRtos/Include/Tactility/Semaphore.h (1)

61-61: Non-standard move constructor signature.

This constructor takes a non-const lvalue reference and moves from it, which is the behavior of a move constructor but with a non-standard signature. This can cause confusion and won't work with rvalue temporaries.

Consider using the standard move constructor signature.

🔎 Proposed fix
-    Semaphore(Semaphore& other) : handle(std::move(other.handle)) {}
+    Semaphore(Semaphore&& other) noexcept : handle(std::move(other.handle)) {}
TactilityFreeRtos/Include/Tactility/Thread.h (1)

164-168: Scoped lock acquired but not utilized correctly.

asScopedLock() returns an unlocked ScopedLock. The manual lock.lock() call works, but returning state after the function exits means the lock is still held during the return. Consider simplifying with a direct mutex.lock()/unlock() pattern or ensuring the scoped lock is used idiomatically.

🔎 Proposed simplification
     State getState() const {
-        auto lock = mutex.asScopedLock();
-        lock.lock();
-        return state;
+        mutex.lock();
+        State result = state;
+        mutex.unlock();
+        return result;
     }
Tests/TactilityFreeRtos/ThreadTest.cpp (1)

44-63: Update the test case name to reflect the API change.

The test case name still references "thread id" but the implementation now tests getTaskHandle(). Consider renaming to accurately describe what is being tested.

🔎 Proposed fix
-TEST_CASE("thread id should only be set at when thread is started") {
+TEST_CASE("thread task handle should only be set when thread is started") {
TactilityCore/Include/Tactility/kernel/Platform.h (1)

6-9: Consider using enum class for better type safety.

The current typedef enum is valid but enum class provides stronger type safety and scoped enumerators in modern C++.

🔎 Proposed refactor
-/** Recognized platform types */
-typedef enum {
-    PlatformEsp,
-    PlatformSimulator
-} Platform;
+/** Recognized platform types */
+enum class Platform {
+    Esp,
+    Simulator
+};

Note: This would require updating call sites from PlatformEsp to Platform::Esp and PlatformSimulator to Platform::Simulator.

Tests/TactilityFreeRtos/RecursiveMutexTest.cpp (1)

22-28: Consider: Timing-based assertions may be flaky under load.

The 5ms delays assume the scheduler will have run by then. In heavily loaded CI environments, this could occasionally fail. Consider using longer delays or event-based synchronization for more deterministic behavior.

TactilityFreeRtos/Include/Tactility/Mutex.h (1)

39-43: Changed unlock() to void, ignoring xSemaphoreGive return value.

The change aligns with the interface update. Note that xSemaphoreGive can return pdFAIL if called on a mutex not owned by the current task. Since the mutex enforces non-ISR context via the assertion and is designed for proper lock/unlock pairing, this should be safe in correct usage.

If defensive error detection is desired, consider adding an assertion on the return value:

[[maybe_unused]] auto result = xSemaphoreGive(handle.get());
assert(result == pdPASS);
Tests/TactilityFreeRtos/Main.cpp (1)

26-30: Consider returning early after vTaskEndScheduler() to avoid redundant vTaskDelete.

When shouldExit() is true (e.g., --help or --exit flags), the scheduler ends but the task still calls vTaskDelete(nullptr). Depending on the FreeRTOS port, this may be harmless or cause undefined behavior after the scheduler has stopped.

🔎 Proposed fix
     if (context.shouldExit()) { // important - query flags (and --exit) rely on the user doing this
         vTaskEndScheduler();
+        return;
     }
 
     vTaskDelete(nullptr);
TactilityFreeRtos/Include/Tactility/Dispatcher.h (1)

28-29: Type mismatch: BACKPRESSURE_WARNING_COUNT should be size_t or uint32_t.

EventBits_t is semantically incorrect here since this value is compared against queue.size() (which returns size_t), not used as event bits.

🔎 Proposed fix
-    static constexpr EventBits_t BACKPRESSURE_WARNING_COUNT = 100U;
+    static constexpr size_t BACKPRESSURE_WARNING_COUNT = 100U;
Tests/TactilityFreeRtos/CMakeLists.txt (3)

5-5: Hardcoding g++ reduces portability.

This forces g++ regardless of the toolchain configured by the parent project or CI environment. Consider removing this line to inherit the compiler from the parent CMake configuration.

🔎 Proposed fix
-set(CMAKE_CXX_COMPILER g++)

10-11: Prefer target_compile_definitions over add_definitions.

add_definitions affects all targets in the directory scope. Using target_compile_definitions is the modern CMake approach and limits the scope to the specific target.

🔎 Proposed fix
-add_definitions(-D_Nullable=)
-add_definitions(-D_Nonnull=)
+target_compile_definitions(TactilityFreeRtosTests PRIVATE
+    _Nullable=
+    _Nonnull=
+)

Note: Move this after the add_executable call.


21-24: Use PRIVATE linkage for test executable.

Test executables typically don't export their linked libraries. PRIVATE is more appropriate here.

🔎 Proposed fix
-target_link_libraries(TactilityFreeRtosTests PUBLIC
+target_link_libraries(TactilityFreeRtosTests PRIVATE
     TactilityFreeRtos
     freertos_kernel
 )
TactilityFreeRtos/Include/Tactility/Lock.h (1)

46-46: Inconsistent indentation: tabs mixed with spaces.

Lines 46, 79-80 use tabs while the rest of the file uses spaces.

Also applies to: 79-81

Tactility/Source/hal/gps/GpsInit.cpp (2)

46-74: Buffer size validation missing in makeCASPacket.

The function writes to buffer without validating that the caller-provided buffer is large enough. The function writes to offset 9 + payload_size, requiring a buffer of at least payload_size + 10 bytes. Consider adding a buffer_size parameter and validation.

🔎 Suggested defensive improvement
-static uint8_t makeCASPacket(uint8_t* buffer, uint8_t class_id, uint8_t msg_id, uint8_t payload_size, const uint8_t *msg)
+static uint8_t makeCASPacket(uint8_t* buffer, size_t buffer_size, uint8_t class_id, uint8_t msg_id, uint8_t payload_size, const uint8_t *msg)
 {
+    size_t required_size = static_cast<size_t>(payload_size) + 10;
+    assert(buffer_size >= required_size);
+
     // ... rest of function

139-168: Unreachable code after exhaustive switch.

The TT_LOG_I and return false at lines 166-167 are unreachable since the switch covers all GpsModel enum values. If GpsModel::Unknown causes a crash via tt_crash(), and all other cases return, this code path cannot be reached. Consider removing this dead code or adding a default case if future enum values are expected.

TactilityC/Include/tt_timer.h (2)

48-55: Inconsistent indentation in doc comment.

The doc comment for tt_timer_reset has extra indentation that doesn't match the rest of the file's formatting style.

🔎 Proposed fix
 bool tt_timer_reset_with_interval(TimerHandle handle, TickType interval);

-    /**
+/**
  * Restart an already started timer
  * @param[in] handle the timer instance handle
  * @return true when the timer was successfully restarted
  */
 bool tt_timer_reset(TimerHandle handle);

86-86: Inconsistent type usage: TickType_t vs TickType.

This function uses TickType_t while other functions in this header use TickType. For API consistency, consider using TickType throughout.

🔎 Proposed fix
-bool tt_timer_set_pending_callback(TimerHandle handle, TimerPendingCallback callback, void* callbackContext, uint32_t callbackArg, TickType_t timeout);
+bool tt_timer_set_pending_callback(TimerHandle handle, TimerPendingCallback callback, void* callbackContext, uint32_t callbackArg, TickType timeout);
TactilityFreeRtos/Include/Tactility/DispatcherThread.h (1)

64-65: isStarted() logic may not accurately reflect running state.

The condition thread != nullptr && !interruptThread doesn't account for the thread being in the process of stopping (after stop() is called but before join() completes). Consider checking thread->getState() for a more accurate status.

TactilityFreeRtos/Include/Tactility/kernel/Kernel.h (2)

27-33: constexpr is incorrect for runtime function calls.

Functions like getTicks() call xTaskGetTickCount() which are runtime FreeRTOS calls. These cannot be constexpr in the traditional sense. While some compilers may allow this as an extension, it's misleading and non-portable.

🔎 Proposed fix
 /** @return the amount of ticks that has passed in the main kernel task */
-constexpr TickType_t getTicks() {
+inline TickType_t getTicks() {
     if (xPortInIsrContext() == pdTRUE) {
         return xTaskGetTickCountFromISR();
     } else {
         return xTaskGetTickCount();
     }
 }

Similar changes should be applied to getMillis(), getMicrosSinceBoot(), delayTicks(), delayMillis(), and delayMicros().


52-55: Incorrect seconds-to-ticks conversion formula.

The formula seconds * 1000U / portTICK_PERIOD_MS is correct only if portTICK_PERIOD_MS == 1. The standard formula should be seconds * configTICK_RATE_HZ or seconds * 1000 / portTICK_PERIOD_MS (which is what you have, but the intermediate calculation may overflow for large values).

Consider using configTICK_RATE_HZ directly for clarity:

🔎 Proposed fix for clarity and overflow safety
 /** Convert seconds to ticks */
 constexpr TickType_t secondsToTicks(uint32_t seconds) {
-    return static_cast<TickType_t>(seconds) * 1000U / portTICK_PERIOD_MS;
+    return static_cast<TickType_t>(seconds) * configTICK_RATE_HZ;
 }
TactilityFreeRtos/Include/Tactility/Timer.h (2)

129-136: ISR path ignores pxHigherPriorityTaskWoken result.

When calling xTimerPendFunctionCallFromISR, the pxHigherPriorityTaskWoken parameter is passed as nullptr, meaning no context switch is triggered even if a higher-priority task becomes ready. This could delay callback execution.

🔎 Proposed fix
     bool setPendingCallback(PendingCallback newCallback, void* callbackContext, uint32_t callbackArg, TickType_t timeout) const {
         if (xPortInIsrContext() == pdTRUE) {
             assert(timeout == 0);
-            return xTimerPendFunctionCallFromISR(newCallback, callbackContext, callbackArg, nullptr) == pdPASS;
+            BaseType_t xHigherPriorityTaskWoken = pdFALSE;
+            BaseType_t result = xTimerPendFunctionCallFromISR(newCallback, callbackContext, callbackArg, &xHigherPriorityTaskWoken);
+            portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
+            return result == pdPASS;
         } else {
             return xTimerPendFunctionCall(newCallback, callbackContext, callbackArg, timeout) == pdPASS;
         }
     }

138-148: setCallbackPriority affects ALL timers globally.

This method modifies the priority of the timer daemon task, which affects the execution priority of all timer callbacks in the system, not just this timer instance. The documentation should clarify this global side effect.

🔎 Suggested documentation improvement
-    /** Set callback priority
+    /** Set callback priority for the timer daemon task.
+     * @warning This affects ALL timer callbacks in the system, not just this timer.
      * @param[in] priority The priority
      */
     void setCallbackPriority(Thread::Priority priority) const {
TactilityFreeRtos/Include/Tactility/EventGroup.h (1)

49-73: Unused variable and ISR semantics clarification.

Line 52 declares uint32_t result but never uses it. Also, in the ISR path, outFlags is set to the input flags rather than actual resulting flags (line 61), which is unavoidable since xEventGroupSetBitsFromISR defers to the timer daemon. Consider clarifying this in the documentation or removing outFlags for ISR context.

🔎 Proposed fix
     bool set(uint32_t flags, uint32_t* outFlags = nullptr, Error* outError = nullptr) const {
         assert(handle);
         if (xPortInIsrContext() == pdTRUE) {
-            uint32_t result;
             BaseType_t yield = pdFALSE;
             if (xEventGroupSetBitsFromISR(handle.get(), flags, &yield) == pdFAIL) {
                 if (outError != nullptr) {
                     *outError = Error::Resource;
                 }
                 return false;
             } else {
-                if (outFlags != nullptr) {
-                    *outFlags = flags;
-                }
+                // Note: outFlags not set in ISR context as bits are set deferred by timer daemon
                 portYIELD_FROM_ISR(yield);
                 return true;
             }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a4dc633 and 2d867ac.

📒 Files selected for processing (148)
  • .github/workflows/tests.yml
  • CMakeLists.txt
  • Devices/simulator/Source/LvglTask.cpp
  • Devices/simulator/Source/Main.cpp
  • Firmware/CMakeLists.txt
  • LICENSE.md
  • Tactility/CMakeLists.txt
  • Tactility/Include/Tactility/Tactility.h
  • Tactility/Include/Tactility/hal/gps/GpsDevice.h
  • Tactility/Include/Tactility/hal/gps/Satellites.h
  • Tactility/Include/Tactility/hal/i2c/I2c.h
  • Tactility/Include/Tactility/hal/sdcard/SdCardDevice.h
  • Tactility/Include/Tactility/hal/spi/Spi.h
  • Tactility/Include/Tactility/hal/uart/Uart.h
  • Tactility/Include/Tactility/service/gps/GpsService.h
  • Tactility/Include/Tactility/service/loader/Loader.h
  • Tactility/Include/Tactility/service/wifi/Wifi.h
  • Tactility/Private/Tactility/app/AppInstance.h
  • Tactility/Private/Tactility/app/i2cscanner/I2cScannerPrivate.h
  • Tactility/Private/Tactility/service/gui/GuiService.h
  • Tactility/Private/Tactility/service/memorychecker/MemoryCheckerService.h
  • Tactility/Private/Tactility/service/screenshot/ScreenshotTask.h
  • Tactility/Source/Tactility.cpp
  • Tactility/Source/app/AppManifestParsing.cpp
  • Tactility/Source/app/development/Development.cpp
  • Tactility/Source/app/files/State.cpp
  • Tactility/Source/app/files/View.cpp
  • Tactility/Source/app/fileselection/State.cpp
  • Tactility/Source/app/fileselection/View.cpp
  • Tactility/Source/app/gpssettings/GpsSettings.cpp
  • Tactility/Source/app/i2cscanner/I2cScanner.cpp
  • Tactility/Source/app/power/Power.cpp
  • Tactility/Source/app/screenshot/Screenshot.cpp
  • Tactility/Source/app/systeminfo/SystemInfo.cpp
  • Tactility/Source/app/timezone/TimeZone.cpp
  • Tactility/Source/hal/Device.cpp
  • Tactility/Source/hal/gps/GpsDevice.cpp
  • Tactility/Source/hal/gps/GpsInit.cpp
  • Tactility/Source/hal/gps/Satellites.cpp
  • Tactility/Source/hal/gps/Ublox.cpp
  • Tactility/Source/hal/i2c/I2c.cpp
  • Tactility/Source/hal/spi/Spi.cpp
  • Tactility/Source/hal/uart/UartEsp.cpp
  • Tactility/Source/hal/uart/UartPosix.cpp
  • Tactility/Source/kernel/SystemEvents.cpp
  • Tactility/Source/lvgl/EspLvglPort.cpp
  • Tactility/Source/lvgl/LvglSync.cpp
  • Tactility/Source/lvgl/Statusbar.cpp
  • Tactility/Source/network/Http.cpp
  • Tactility/Source/network/HttpServer.cpp
  • Tactility/Source/service/ServiceRegistration.cpp
  • Tactility/Source/service/displayidle/DisplayIdle.cpp
  • Tactility/Source/service/espnow/EspNowWifi.cpp
  • Tactility/Source/service/gui/GuiService.cpp
  • Tactility/Source/service/keyboardidle/KeyboardIdle.cpp
  • Tactility/Source/service/memorychecker/MemoryCheckerService.cpp
  • Tactility/Source/service/screenshot/Screenshot.cpp
  • Tactility/Source/service/screenshot/ScreenshotTask.cpp
  • Tactility/Source/service/sdcard/Sdcard.cpp
  • Tactility/Source/service/statusbar/Statusbar.cpp
  • Tactility/Source/service/wifi/Wifi.cpp
  • Tactility/Source/service/wifi/WifiEsp.cpp
  • Tactility/Source/service/wifi/WifiMock.cpp
  • TactilityC/Include/tt_hal_i2c.h
  • TactilityC/Include/tt_kernel.h
  • TactilityC/Include/tt_lock.h
  • TactilityC/Include/tt_message_queue.h
  • TactilityC/Include/tt_thread.h
  • TactilityC/Include/tt_timer.h
  • TactilityC/Source/symbols/freertos.cpp
  • TactilityC/Source/tt_app.cpp
  • TactilityC/Source/tt_hal_i2c.cpp
  • TactilityC/Source/tt_init.cpp
  • TactilityC/Source/tt_kernel.cpp
  • TactilityC/Source/tt_lock.cpp
  • TactilityC/Source/tt_message_queue.cpp
  • TactilityC/Source/tt_thread.cpp
  • TactilityC/Source/tt_timer.cpp
  • TactilityCore/CMakeLists.txt
  • TactilityCore/Include/Tactility/CpuAffinity.h
  • TactilityCore/Include/Tactility/Dispatcher.h
  • TactilityCore/Include/Tactility/DispatcherThread.h
  • TactilityCore/Include/Tactility/EventFlag.h
  • TactilityCore/Include/Tactility/MessageQueue.h
  • TactilityCore/Include/Tactility/RtosCompat.h
  • TactilityCore/Include/Tactility/RtosCompatEventGroups.h
  • TactilityCore/Include/Tactility/RtosCompatSemaphore.h
  • TactilityCore/Include/Tactility/RtosCompatTask.h
  • TactilityCore/Include/Tactility/RtosCompatTimers.h
  • TactilityCore/Include/Tactility/Semaphore.h
  • TactilityCore/Include/Tactility/StreamBuffer.h
  • TactilityCore/Include/Tactility/TactilityCore.h
  • TactilityCore/Include/Tactility/TactilityCoreConfig.h
  • TactilityCore/Include/Tactility/Thread.h
  • TactilityCore/Include/Tactility/Timer.h
  • TactilityCore/Include/Tactility/file/File.h
  • TactilityCore/Include/Tactility/kernel/Kernel.h
  • TactilityCore/Include/Tactility/kernel/Platform.h
  • TactilityCore/Source/Check.cpp
  • TactilityCore/Source/Dispatcher.cpp
  • TactilityCore/Source/DispatcherThread.cpp
  • TactilityCore/Source/EventFlag.cpp
  • TactilityCore/Source/Lock.cpp
  • TactilityCore/Source/MessageQueue.cpp
  • TactilityCore/Source/Semaphore.cpp
  • TactilityCore/Source/StreamBuffer.cpp
  • TactilityCore/Source/Thread.cpp
  • TactilityCore/Source/Timer.cpp
  • TactilityCore/Source/file/File.cpp
  • TactilityCore/Source/kernel/Kernel.cpp
  • TactilityCore/Source/kernel/Platform.cpp
  • TactilityCore/Source/kernel/critical/Critical.cpp
  • TactilityFreeRtos/CMakeLists.txt
  • TactilityFreeRtos/Include/Tactility/Dispatcher.h
  • TactilityFreeRtos/Include/Tactility/DispatcherThread.h
  • TactilityFreeRtos/Include/Tactility/EventGroup.h
  • TactilityFreeRtos/Include/Tactility/Lock.h
  • TactilityFreeRtos/Include/Tactility/MessageQueue.h
  • TactilityFreeRtos/Include/Tactility/Mutex.h
  • TactilityFreeRtos/Include/Tactility/PubSub.h
  • TactilityFreeRtos/Include/Tactility/RecursiveMutex.h
  • TactilityFreeRtos/Include/Tactility/Semaphore.h
  • TactilityFreeRtos/Include/Tactility/Thread.h
  • TactilityFreeRtos/Include/Tactility/Timer.h
  • TactilityFreeRtos/Include/Tactility/freertoscompat/EventGroups.h
  • TactilityFreeRtos/Include/Tactility/freertoscompat/PortCompat.h
  • TactilityFreeRtos/Include/Tactility/freertoscompat/Queue.h
  • TactilityFreeRtos/Include/Tactility/freertoscompat/README.md
  • TactilityFreeRtos/Include/Tactility/freertoscompat/RTOS.h
  • TactilityFreeRtos/Include/Tactility/freertoscompat/Semaphore.h
  • TactilityFreeRtos/Include/Tactility/freertoscompat/Task.h
  • TactilityFreeRtos/Include/Tactility/freertoscompat/Timers.h
  • TactilityFreeRtos/Include/Tactility/kernel/Kernel.h
  • TactilityFreeRtos/README.md
  • Tests/CMakeLists.txt
  • Tests/TactilityCore/CMakeLists.txt
  • Tests/TactilityFreeRtos/CMakeLists.txt
  • Tests/TactilityFreeRtos/DispatcherTest.cpp
  • Tests/TactilityFreeRtos/DispatcherThreadTest.cpp
  • Tests/TactilityFreeRtos/LockTest.cpp
  • Tests/TactilityFreeRtos/Main.cpp
  • Tests/TactilityFreeRtos/MessageQueueTest.cpp
  • Tests/TactilityFreeRtos/MutexTest.cpp
  • Tests/TactilityFreeRtos/PubSubTest.cpp
  • Tests/TactilityFreeRtos/RecursiveMutexTest.cpp
  • Tests/TactilityFreeRtos/SemaphoreTest.cpp
  • Tests/TactilityFreeRtos/ThreadTest.cpp
  • Tests/TactilityFreeRtos/TimerTest.cpp
💤 Files with no reviewable changes (30)
  • TactilityCore/Include/Tactility/TactilityCoreConfig.h
  • TactilityCore/Source/Lock.cpp
  • Tests/TactilityFreeRtos/DispatcherThreadTest.cpp
  • TactilityCore/Include/Tactility/Dispatcher.h
  • Tests/TactilityFreeRtos/MessageQueueTest.cpp
  • TactilityCore/Source/Dispatcher.cpp
  • TactilityCore/Include/Tactility/Thread.h
  • TactilityCore/Include/Tactility/Semaphore.h
  • TactilityCore/Source/kernel/Kernel.cpp
  • TactilityCore/Include/Tactility/RtosCompatEventGroups.h
  • TactilityCore/Include/Tactility/kernel/Kernel.h
  • TactilityC/Include/tt_kernel.h
  • TactilityCore/Include/Tactility/StreamBuffer.h
  • TactilityCore/Include/Tactility/DispatcherThread.h
  • TactilityCore/Source/StreamBuffer.cpp
  • TactilityCore/Source/DispatcherThread.cpp
  • TactilityCore/Include/Tactility/MessageQueue.h
  • TactilityCore/Include/Tactility/RtosCompatTask.h
  • TactilityCore/Source/MessageQueue.cpp
  • TactilityCore/Source/Semaphore.cpp
  • TactilityCore/Include/Tactility/Timer.h
  • TactilityCore/Include/Tactility/RtosCompatSemaphore.h
  • TactilityCore/Include/Tactility/RtosCompatTimers.h
  • TactilityCore/Include/Tactility/RtosCompat.h
  • TactilityCore/Include/Tactility/EventFlag.h
  • TactilityC/Source/tt_kernel.cpp
  • Tests/TactilityFreeRtos/TimerTest.cpp
  • TactilityCore/Source/Timer.cpp
  • TactilityCore/Source/Thread.cpp
  • TactilityCore/Source/EventFlag.cpp
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2025-11-01T19:57:58.233Z
Learnt from: NellowTCS
Repo: ByteWelder/Tactility PR: 405
File: Tactility/Source/hal/usb/UsbTusb.cpp:12-12
Timestamp: 2025-11-01T19:57:58.233Z
Learning: In ESP-IDF projects like Tactility, the wear levelling header can be included directly as `#include <wear_levelling.h>` without requiring a directory prefix like `wear_levelling/wear_levelling.h`.

Applied to files:

  • Tactility/Source/hal/gps/GpsDevice.cpp
  • TactilityCore/CMakeLists.txt
  • TactilityFreeRtos/Include/Tactility/freertoscompat/Task.h
  • Tactility/Include/Tactility/service/wifi/Wifi.h
  • Tactility/Include/Tactility/hal/i2c/I2c.h
  • Tactility/CMakeLists.txt
  • Tactility/Source/service/wifi/Wifi.cpp
  • TactilityCore/Source/kernel/critical/Critical.cpp
  • TactilityFreeRtos/Include/Tactility/freertoscompat/Queue.h
  • TactilityCore/Include/Tactility/CpuAffinity.h
  • TactilityFreeRtos/CMakeLists.txt
  • Tactility/Source/hal/spi/Spi.cpp
  • TactilityFreeRtos/Include/Tactility/freertoscompat/README.md
  • Tactility/Source/hal/gps/Ublox.cpp
  • TactilityCore/Include/Tactility/file/File.h
  • TactilityC/Source/symbols/freertos.cpp
  • Tactility/Source/lvgl/EspLvglPort.cpp
  • TactilityCore/Source/Check.cpp
  • TactilityCore/Include/Tactility/TactilityCore.h
  • Tactility/Include/Tactility/hal/gps/Satellites.h
  • Tactility/Source/service/ServiceRegistration.cpp
  • Tactility/Include/Tactility/hal/spi/Spi.h
  • Tactility/Source/service/espnow/EspNowWifi.cpp
  • Tactility/Source/Tactility.cpp
  • TactilityFreeRtos/Include/Tactility/freertoscompat/RTOS.h
  • Tactility/Source/app/development/Development.cpp
  • CMakeLists.txt
  • TactilityC/Source/tt_init.cpp
📚 Learning: 2025-10-26T12:44:21.858Z
Learnt from: KenVanHoeylandt
Repo: ByteWelder/Tactility PR: 391
File: Boards/CYD-2432S028RV3/Source/devices/Display.h:11-19
Timestamp: 2025-10-26T12:44:21.858Z
Learning: In the Tactility codebase, prefer `constexpr auto` for constant declarations (such as LCD pin configurations, resolutions, and buffer sizes) rather than explicit type annotations like `gpio_num_t`, `spi_host_device_t`, `uint16_t`, or `size_t`.

Applied to files:

  • Tactility/Source/hal/gps/GpsDevice.cpp
  • Tactility/Include/Tactility/hal/i2c/I2c.h
  • Tactility/Source/hal/Device.cpp
  • Tactility/Source/hal/gps/Ublox.cpp
  • TactilityCore/Source/Check.cpp
  • Devices/simulator/Source/LvglTask.cpp
  • Tactility/Source/hal/uart/UartEsp.cpp
  • Tactility/Source/hal/gps/GpsInit.cpp
  • Tactility/Source/hal/i2c/I2c.cpp
  • Tactility/Private/Tactility/app/i2cscanner/I2cScannerPrivate.h
  • Tactility/Source/app/development/Development.cpp
  • Tactility/Source/app/gpssettings/GpsSettings.cpp
  • Tactility/Source/hal/uart/UartPosix.cpp
📚 Learning: 2025-10-27T22:33:23.840Z
Learnt from: KenVanHoeylandt
Repo: ByteWelder/Tactility PR: 394
File: Boards/WaveshareEsp32C6Lcd147/Source/WaveshareEsp32C6Lcd147.cpp:49-50
Timestamp: 2025-10-27T22:33:23.840Z
Learning: In the Tactility project, when configuring SPI for displays that use esp_lvgl_port, the `.lock` field must be set to `tt::lvgl::getSyncLock()` rather than `tt::hal::spi::getLock()`, because esp_lvgl_port creates and manages its own synchronization lock for display operations.

Applied to files:

  • TactilityC/Include/tt_hal_i2c.h
  • Tactility/Include/Tactility/hal/i2c/I2c.h
  • TactilityC/Source/tt_hal_i2c.cpp
  • Tactility/Source/app/files/View.cpp
  • Tactility/Source/service/keyboardidle/KeyboardIdle.cpp
  • Tactility/Source/app/timezone/TimeZone.cpp
  • Tactility/Source/hal/spi/Spi.cpp
  • Tactility/Source/service/displayidle/DisplayIdle.cpp
  • Tactility/Source/lvgl/EspLvglPort.cpp
  • Tactility/Include/Tactility/hal/sdcard/SdCardDevice.h
  • Devices/simulator/Source/LvglTask.cpp
  • Tactility/Source/app/power/Power.cpp
  • Tactility/Include/Tactility/hal/spi/Spi.h
  • Tactility/Source/hal/i2c/I2c.cpp
  • Tactility/Source/lvgl/Statusbar.cpp
  • Tactility/Source/app/development/Development.cpp
  • CMakeLists.txt
  • TactilityC/Source/tt_init.cpp
  • Tactility/Source/lvgl/LvglSync.cpp
📚 Learning: 2025-11-11T17:22:20.238Z
Learnt from: KenVanHoeylandt
Repo: ByteWelder/Tactility PR: 411
File: sdkconfig.board.waveshare-s3-touch-lcd-43:39-39
Timestamp: 2025-11-11T17:22:20.238Z
Learning: In Tactility sdkconfig.board.* files, board names (CONFIG_TT_BOARD_NAME) that include screen size measurements intentionally use escaped quotes (e.g., "WaveShare S3 Touch LCD 4.3\"") where the \" represents the inch symbol (") in the final board name string. This is not a syntax error but deliberate formatting.

Applied to files:

  • Tactility/Private/Tactility/service/screenshot/ScreenshotTask.h
📚 Learning: 2025-11-11T17:22:17.736Z
Learnt from: KenVanHoeylandt
Repo: ByteWelder/Tactility PR: 411
File: sdkconfig.board.waveshare-s3-lcd-13:39-39
Timestamp: 2025-11-11T17:22:17.736Z
Learning: In sdkconfig.board.* files for Tactility boards, CONFIG_TT_BOARD_NAME values that include display sizes in inches intentionally use a trailing escaped quote (e.g., "WaveShare S3 LCD 1.3\"") to display the literal inch symbol (") in the board name. This pattern is correct and should not be flagged as a string escaping error.

Applied to files:

  • Tactility/Private/Tactility/service/screenshot/ScreenshotTask.h
📚 Learning: 2025-10-27T22:52:15.539Z
Learnt from: KenVanHoeylandt
Repo: ByteWelder/Tactility PR: 395
File: Boards/M5stackCardputerAdv/Source/devices/CardputerKeyboard.cpp:145-153
Timestamp: 2025-10-27T22:52:15.539Z
Learning: In the Tactility codebase, for LVGL device lifecycle methods (e.g., stopLvgl(), startLvgl()), the maintainer prefers using assert() to catch double-calls and programming errors rather than adding defensive null-checks. The intent is to crash on misuse during development rather than silently handle it, avoiding the overhead of defensive checks in every driver.

Applied to files:

  • Tactility/Source/app/timezone/TimeZone.cpp
  • Tactility/Source/lvgl/EspLvglPort.cpp
  • Devices/simulator/Source/LvglTask.cpp
  • Tactility/Source/app/power/Power.cpp
  • Tactility/Source/lvgl/Statusbar.cpp
📚 Learning: 2025-10-28T19:44:35.263Z
Learnt from: NellowTCS
Repo: ByteWelder/Tactility PR: 398
File: Drivers/ST7789-i8080/Source/St7789i8080Display.cpp:326-329
Timestamp: 2025-10-28T19:44:35.263Z
Learning: In the LilyGo T-Display S3 ST7789 i8080 display driver (Drivers/ST7789-i8080/Source/St7789i8080Display.cpp), both callback registrations are required: setting on_color_trans_done during panel IO creation in configurePanelIO() and calling esp_lcd_panel_io_register_event_callbacks() in initializeLvgl() after the LVGL display is created. This dual registration is intentional and necessary for proper operation on this hardware.

Applied to files:

  • Tactility/Source/service/displayidle/DisplayIdle.cpp
📚 Learning: 2025-10-28T05:42:03.394Z
Learnt from: NellowTCS
Repo: ByteWelder/Tactility PR: 398
File: Boards/LilygoTdisplayS3/Source/devices/Display.cpp:183-199
Timestamp: 2025-10-28T05:42:03.394Z
Learning: The LilyGo T-Display S3 board (Boards/LilygoTdisplayS3) requires a non-standard LVGL flush pattern in its ST7789 i8080 display driver. In st7789_send_color_cb, lv_display_flush_ready() must be called immediately after esp_lcd_panel_io_tx_color() with a vTaskDelay(1), rather than using standard DMA completion callbacks via esp_lcd_panel_io_register_event_callbacks. Standard DMA callback approaches cause display corruption on this specific board. This pattern was tested against multiple alternatives (callback registration, buffer tuning, clock reduction) and is the only stable solution for T-Display S3 hardware.

Applied to files:

  • Tactility/Source/lvgl/EspLvglPort.cpp
🧬 Code graph analysis (23)
TactilityC/Include/tt_lock.h (1)
TactilityC/Source/tt_lock.cpp (2)
  • tt_lock_release (35-37)
  • tt_lock_release (35-35)
Tactility/Private/Tactility/service/gui/GuiService.h (3)
Tactility/Source/app/timezone/TimeZone.cpp (1)
  • mutex (79-89)
Tactility/Source/service/sdcard/Sdcard.cpp (1)
  • mutex (25-27)
Tactility/Source/service/statusbar/Statusbar.cpp (2)
  • mutex (153-155)
  • mutex (157-159)
Tactility/Source/service/keyboardidle/KeyboardIdle.cpp (1)
TactilityFreeRtos/Include/Tactility/kernel/Kernel.h (1)
  • millisToTicks (58-64)
TactilityC/Include/tt_thread.h (1)
TactilityC/Source/tt_thread.cpp (4)
  • tt_thread_join (65-67)
  • tt_thread_join (65-65)
  • tt_thread_get_task_handle (69-71)
  • tt_thread_get_task_handle (69-69)
Tactility/Source/service/sdcard/Sdcard.cpp (2)
Tactility/Source/service/statusbar/Statusbar.cpp (2)
  • service (280-283)
  • service (280-280)
Tactility/Source/service/wifi/WifiEsp.cpp (4)
  • service (917-936)
  • service (917-917)
  • service (938-962)
  • service (938-938)
Tests/TactilityFreeRtos/MutexTest.cpp (2)
TactilityFreeRtos/Include/Tactility/Mutex.h (1)
  • Mutex (24-26)
TactilityFreeRtos/Include/Tactility/Thread.h (1)
  • Thread (18-162)
Tactility/Source/kernel/SystemEvents.cpp (1)
Tactility/Source/app/timezone/TimeZone.cpp (1)
  • mutex (79-89)
TactilityFreeRtos/Include/Tactility/MessageQueue.h (2)
TactilityFreeRtos/Include/Tactility/Timer.h (1)
  • reset (104-113)
TactilityCore/Include/Tactility/MessageQueue.h (2)
  • MessageQueue (26-84)
  • tt (20-86)
Tactility/Source/service/displayidle/DisplayIdle.cpp (1)
TactilityFreeRtos/Include/Tactility/kernel/Kernel.h (1)
  • millisToTicks (58-64)
Tactility/Source/service/screenshot/ScreenshotTask.cpp (3)
Tactility/Source/app/timezone/TimeZone.cpp (1)
  • mutex (79-89)
Tactility/Source/service/sdcard/Sdcard.cpp (1)
  • mutex (25-27)
Tactility/Source/service/statusbar/Statusbar.cpp (2)
  • mutex (153-155)
  • mutex (157-159)
TactilityC/Include/tt_timer.h (1)
TactilityC/Source/tt_timer.cpp (8)
  • tt_timer_alloc (12-16)
  • tt_timer_alloc (12-12)
  • tt_timer_reset_with_interval (32-34)
  • tt_timer_reset_with_interval (32-32)
  • tt_timer_reset (28-30)
  • tt_timer_reset (28-28)
  • tt_timer_get_expiry_time (44-46)
  • tt_timer_get_expiry_time (44-44)
Tactility/Include/Tactility/hal/sdcard/SdCardDevice.h (2)
Tactility/Source/hal/sdcard/SdmmcDevice.cpp (2)
  • getState (98-119)
  • getState (98-98)
Tactility/Source/hal/sdcard/SpiSdCardDevice.cpp (2)
  • getState (126-147)
  • getState (126-126)
TactilityFreeRtos/Include/Tactility/Semaphore.h (1)
TactilityFreeRtos/Include/Tactility/Lock.h (1)
  • Lock (14-47)
Tactility/Source/app/power/Power.cpp (2)
TactilityFreeRtos/Include/Tactility/Timer.h (1)
  • Timer (15-65)
TactilityFreeRtos/Include/Tactility/kernel/Kernel.h (1)
  • millisToTicks (58-64)
TactilityFreeRtos/Include/Tactility/Mutex.h (1)
TactilityFreeRtos/Include/Tactility/MessageQueue.h (1)
  • get (88-107)
TactilityFreeRtos/Include/Tactility/RecursiveMutex.h (1)
TactilityFreeRtos/Include/Tactility/MessageQueue.h (1)
  • get (88-107)
Tactility/Source/app/systeminfo/SystemInfo.cpp (2)
TactilityFreeRtos/Include/Tactility/Timer.h (1)
  • Timer (15-65)
TactilityFreeRtos/Include/Tactility/kernel/Kernel.h (1)
  • millisToTicks (58-64)
TactilityFreeRtos/Include/Tactility/Thread.h (1)
TactilityFreeRtos/Include/Tactility/kernel/Kernel.h (2)
  • getTicks (27-33)
  • delayTicks (70-77)
TactilityFreeRtos/Include/Tactility/Timer.h (2)
TactilityFreeRtos/Include/Tactility/MessageQueue.h (1)
  • get (88-107)
TactilityFreeRtos/Include/Tactility/Thread.h (1)
  • Priority (31-118)
Tactility/Source/app/development/Development.cpp (2)
TactilityFreeRtos/Include/Tactility/Timer.h (1)
  • Timer (15-65)
Tactility/Source/app/gpssettings/GpsSettings.cpp (2)
  • timer (64-67)
  • timer (69-72)
Tactility/Source/app/gpssettings/GpsSettings.cpp (1)
TactilityFreeRtos/Include/Tactility/kernel/Kernel.h (1)
  • secondsToTicks (53-55)
TactilityFreeRtos/Include/Tactility/EventGroup.h (2)
Tactility/Include/Tactility/app/App.h (1)
  • result (25-75)
TactilityFreeRtos/Include/Tactility/MessageQueue.h (1)
  • get (88-107)
Tactility/Source/lvgl/LvglSync.cpp (1)
TactilityCore/Source/file/File.cpp (2)
  • timeout (17-17)
  • timeout (17-17)
🪛 Clang (14.0.6)
TactilityCore/Include/Tactility/kernel/Platform.h

[error] 3-3: unknown type name 'namespace'

(clang-diagnostic-error)


[error] 3-3: expected ';' after top level declarator

(clang-diagnostic-error)


[error] 3-3: expected identifier or '('

(clang-diagnostic-error)

🪛 LanguageTool
LICENSE.md

[grammar] ~10-~10: Use a hyphen to join words.
Context: ...me](https://kenvanhoeylandt.net). Third party projects that were included in Tac...

(QB_NEW_EN_HYPHEN)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: BuildSdk (generic-esp32, esp32)
  • GitHub Check: BuildSdk (generic-esp32c6, esp32c6)
  • GitHub Check: BuildSdk (generic-esp32p4, esp32p4)
  • GitHub Check: BuildSdk (generic-esp32s3, esp32s3)
  • GitHub Check: Linux
  • GitHub Check: run
  • GitHub Check: macOS

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
Tests/TactilityFreeRtos/TimerTest.cpp (1)

6-18: Consider a more precise assertion to better validate periodic behavior.

The test checks CHECK_GE(counter, 2) after two 10-tick runs with a 1-tick period timer. With approximately 20 expected callbacks, checking for only >= 2 is very conservative and doesn't validate that the timer is actually firing periodically as expected. Consider tightening this to something like CHECK_GE(counter, 16) to catch timing issues while still allowing for FreeRTOS scheduling variance.

🔎 Suggested improvement
-    CHECK_GE(counter, 2);
+    CHECK_GE(counter, 16); // Expect ~20 callbacks (2 runs × ~10 each), allow margin for timing
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2d867ac and 10cc269.

📒 Files selected for processing (2)
  • TactilityFreeRtos/Include/Tactility/Semaphore.h
  • Tests/TactilityFreeRtos/TimerTest.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • TactilityFreeRtos/Include/Tactility/Semaphore.h
🧰 Additional context used
🧬 Code graph analysis (1)
Tests/TactilityFreeRtos/TimerTest.cpp (1)
TactilityFreeRtos/Include/Tactility/kernel/Kernel.h (1)
  • delayTicks (70-77)
🔇 Additional comments (3)
Tests/TactilityFreeRtos/TimerTest.cpp (3)

20-30: LGTM! Precise validation of periodic timer behavior.

The test correctly validates that a periodic timer with a 1-tick interval fires exactly 10 times over 10 ticks. The precise assertion properly tests the timer's periodic behavior.


32-44: LGTM! Correctly validates once-timer restart behavior.

The test properly verifies that a Timer::Type::Once timer can be restarted and fires exactly once per start cycle. The assertion correctly expects 2 total callbacks.


8-9: LGTM! Tests correctly use the new Timer API.

The tests properly reflect the new Timer API changes:

  • Constructor specifies interval at construction time: Timer(Type, interval, callback)
  • start() is called without parameters and returns a boolean
  • stop() returns a boolean

All tests correctly verify the return values from start() and stop().

Also applies to: 11-12, 14-14, 23-24, 26-26, 34-35, 37-38, 40-40

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

♻️ Duplicate comments (3)
TactilityFreeRtos/Include/Tactility/Dispatcher.h (2)

46-51: Destructor race condition remains unresolved.

The destructor's synchronization is insufficient. A consumer thread could be executing a function (line 115) with the mutex released, allowing the destructor to acquire the lock, complete destruction, and invalidate object state while the consumer still references member variables or the function object itself.


93-129: Potential busy-wait loop remains unresolved.

The hardcoded 10-tick timeout on line 107 can lead to a busy-wait if the mutex is held for extended periods. While the shutdown flag (line 126) will eventually terminate the loop, the consumer may spin many times, consuming CPU unnecessarily.

TactilityFreeRtos/Include/Tactility/Thread.h (1)

171-182: Race condition: state transition not protected by mutex.

The mutex is released at line 176 before setState(State::Starting) at line 178. This leaves a window where state can be observed or modified inconsistently. The state transition and task creation should be atomic.

🧹 Nitpick comments (5)
TactilityFreeRtos/Include/Tactility/kernel/Kernel.h (1)

57-64: Prefer integer arithmetic in millisToTicks.

Line 62 uses floating-point arithmetic for tick conversion when configTICK_RATE_HZ != 1000. Integer arithmetic is generally preferred in embedded systems for better performance and determinism.

🔎 Proposed integer-based implementation
 /** Convert milliseconds to ticks */
 constexpr TickType_t millisToTicks(uint32_t milliSeconds) {
 #if configTICK_RATE_HZ == 1000
     return static_cast<TickType_t>(milliSeconds);
 #else
-    return static_cast<TickType_t>(((float)configTICK_RATE_HZ) / 1000.0f * (float)milliSeconds);
+    return static_cast<TickType_t>((static_cast<uint64_t>(milliSeconds) * configTICK_RATE_HZ) / 1000U);
 #endif
 }

Note: Using uint64_t intermediate to avoid overflow for large milliSeconds values.

TactilityFreeRtos/Include/Tactility/EventGroup.h (2)

49-73: Remove unused variable.

Line 52 declares uint32_t result but never uses it in the ISR path. This should be removed.

🔎 Proposed fix
     bool set(uint32_t flags, uint32_t* outFlags = nullptr, Error* outError = nullptr) const {
         assert(handle);
         if (xPortInIsrContext() == pdTRUE) {
-            uint32_t result;
             BaseType_t yield = pdFALSE;
             if (xEventGroupSetBitsFromISR(handle.get(), flags, &yield) == pdFAIL) {

82-83: Add handle assertion for consistency.

The clear() method should include assert(handle) at the beginning, similar to the set() method (line 50), to maintain consistent defensive checks across the API.

🔎 Proposed fix
     bool clear(uint32_t flags, uint32_t* outFlags = nullptr, Error* outError = nullptr) const {
+        assert(handle);
         if (xPortInIsrContext() == pdTRUE) {
TactilityFreeRtos/Include/Tactility/Thread.h (2)

90-97: Consider invoking callback outside the mutex to avoid potential deadlock.

The stateCallback is invoked while the mutex is held. If the callback attempts to call any Thread method that acquires the same mutex, it will deadlock.

🔎 Proposed fix
     void setState(State newState) {
+        StateCallback cb = nullptr;
+        void* ctx = nullptr;
         mutex.lock();
         state = newState;
-        if (stateCallback) {
-            stateCallback(state, stateCallbackContext);
-        }
+        cb = stateCallback;
+        ctx = stateCallbackContext;
         mutex.unlock();
+        if (cb) {
+            cb(newState, ctx);
+        }
     }

239-239: Consider adding synchronization for getTaskHandle().

taskHandle is modified in mainBody (line 73) without holding the mutex, while getTaskHandle() reads it without synchronization. While pointer reads are typically atomic on most platforms, this is technically a data race. The join() method relies on this for its polling loop.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 10cc269 and 94d9c1d.

📒 Files selected for processing (10)
  • Drivers/BQ25896/Source/Bq25896.cpp
  • Drivers/DRV2605/Source/Drv2605.h
  • TactilityC/Source/tt_lock.cpp
  • TactilityCore/Source/kernel/critical/Critical.cpp
  • TactilityFreeRtos/Include/Tactility/Dispatcher.h
  • TactilityFreeRtos/Include/Tactility/EventGroup.h
  • TactilityFreeRtos/Include/Tactility/Thread.h
  • TactilityFreeRtos/Include/Tactility/kernel/Kernel.h
  • Tests/TactilityCore/Main.cpp
  • Tests/TactilityFreeRtos/Main.cpp
✅ Files skipped from review due to trivial changes (1)
  • Drivers/DRV2605/Source/Drv2605.h
🚧 Files skipped from review as they are similar to previous changes (1)
  • Tests/TactilityFreeRtos/Main.cpp
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-10-26T12:44:21.858Z
Learnt from: KenVanHoeylandt
Repo: ByteWelder/Tactility PR: 391
File: Boards/CYD-2432S028RV3/Source/devices/Display.h:11-19
Timestamp: 2025-10-26T12:44:21.858Z
Learning: In the Tactility codebase, prefer `constexpr auto` for constant declarations (such as LCD pin configurations, resolutions, and buffer sizes) rather than explicit type annotations like `gpio_num_t`, `spi_host_device_t`, `uint16_t`, or `size_t`.

Applied to files:

  • Drivers/BQ25896/Source/Bq25896.cpp
🧬 Code graph analysis (2)
TactilityFreeRtos/Include/Tactility/EventGroup.h (3)
TactilityFreeRtos/Include/Tactility/Dispatcher.h (1)
  • tt (18-85)
TactilityFreeRtos/Include/Tactility/kernel/Kernel.h (1)
  • tt (17-99)
TactilityCore/Source/EventFlag.cpp (1)
  • EventFlag (9-135)
TactilityFreeRtos/Include/Tactility/Thread.h (1)
TactilityFreeRtos/Include/Tactility/kernel/Kernel.h (3)
  • tt (17-99)
  • getTicks (27-33)
  • delayTicks (70-77)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: macOS
  • GitHub Check: Linux
  • GitHub Check: BuildSdk (generic-esp32s3, esp32s3)
  • GitHub Check: run
  • GitHub Check: BuildSdk (generic-esp32p4, esp32p4)
  • GitHub Check: BuildSdk (generic-esp32, esp32)
  • GitHub Check: BuildSdk (generic-esp32c6, esp32c6)
🔇 Additional comments (18)
TactilityC/Source/tt_lock.cpp (1)

35-37: LGTM! Previous issue resolved.

The critical issue identified in the previous review has been fixed. The function now correctly calls unlock() without the return keyword, matching the void return type.

TactilityCore/Source/kernel/critical/Critical.cpp (4)

1-4: LGTM! Header includes updated correctly.

The include paths have been properly updated to use the new freertoscompat/Task.h header from the TactilityFreeRtos subproject, which provides the necessary FreeRTOS API declarations.


6-13: LGTM! Critical bug fix: TT_EXIT_CRITICAL() macro now defined.

The addition of the TT_EXIT_CRITICAL() macro for both ESP and non-ESP platforms addresses the critical bug identified in the previous review. The macros are correctly defined and symmetric with their TT_ENTER_CRITICAL() counterparts.


17-33: LGTM! ISR detection updated to use FreeRTOS API directly.

The change from kernel::isIsr() to xPortInIsrContext() == pdTRUE at line 20 is consistent with the PR's goal of using FreeRTOS compatibility APIs directly. The logic remains correct and well-structured.


35-43: LGTM! Critical bug fixed: Line 39 now correctly calls TT_EXIT_CRITICAL().

The critical bug identified in the previous review has been resolved. Line 39 now correctly calls TT_EXIT_CRITICAL() instead of TT_ENTER_CRITICAL(), ensuring that critical sections are properly exited when the kernel is running. The enter/exit logic is now symmetric and correct across all three code paths (ISR, kernel running, and fallback).

Drivers/BQ25896/Source/Bq25896.cpp (1)

3-3: LGTM!

The include addition is necessary to support the TT_LOG_I logging calls used in this file.

TactilityFreeRtos/Include/Tactility/kernel/Kernel.h (1)

36-39: Previous issues have been addressed.

The tick-to-milliseconds conversion now correctly multiplies (line 38), and the microseconds-since-boot function no longer truncates the 64-bit value (line 44).

Also applies to: 42-50

Tests/TactilityCore/Main.cpp (1)

51-52: LGTM: Correct handling of scheduler exit.

The explicit return ensures that if vTaskStartScheduler() unexpectedly returns (indicating a scheduler error), the test result is properly propagated instead of implicitly returning 0.

TactilityFreeRtos/Include/Tactility/EventGroup.h (2)

108-114: LGTM: Correct ISR-aware implementation.

The get() method correctly handles both ISR and non-ISR contexts to retrieve current event flags.


125-162: LGTM: Well-structured wait implementation.

The wait logic correctly handles both "wait for all" and "wait for any" semantics, with appropriate error handling for timeout and resource failures.

TactilityFreeRtos/Include/Tactility/Dispatcher.h (1)

59-85: LGTM: Correct dispatch implementation.

The dispatch method properly synchronizes queue access, checks the shutdown flag, and signals consumers. The use of std::move on line 72 efficiently transfers the function object.

TactilityFreeRtos/Include/Tactility/Thread.h (7)

1-16: LGTM!

The includes are well-organized with proper platform-conditional compilation for ESP-specific logging.


52-76: LGTM!

The task body correctly manages TLS for thread-local self-pointer, handles state transitions with appropriate assertions, and properly cleans up before task deletion.


128-134: LGTM!

The assertion now correctly validates newStackSize instead of stackSize.


222-237: LGTM!

The polling-based join with configurable timeout and poll interval is a practical approach for FreeRTOS. The self-join assertion prevents deadlock.


259-261: LGTM!

Named priority constants provide clear semantics for common use cases.


157-157: The _Nullable portability concern is already addressed.

The codebase defines _Nullable as an empty macro across all build configurations via CMake (add_definitions(-D_Nullable=) in multiple CMakeLists.txt files including TactilityFreeRtos, TactilityCore, Tactility, TactilityC, Firmware, and Devices/simulator). This standard pattern ensures the annotation is safely removed on non-Clang compilers (GCC, MSVC) while remaining intact for Clang builds. No changes are needed.

Likely an incorrect or invalid review comment.


165-169: The code pattern in getState() is correct. ScopedLock is designed as a manual-lock, automatic-unlock RAII wrapper: you must explicitly call lock() after construction, and unlock() is called automatically on destruction (documented in Lock.h). Removing the lock.lock() call would leave the mutex unlocked, causing a data race. No changes needed.

Likely an incorrect or invalid review comment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
TactilityFreeRtos/Include/Tactility/Thread.h (2)

115-115: Typo in documentation comment.

"you just call" should be "you must call". The warning doesn't make grammatical sense as written.

🔎 Proposed fix
-    /** @warning If thread is running, you just call join() first */
+    /** @warning If thread is running, you must call join() first */

171-182: Race condition: state transition not atomic with precondition checks.

The mutex is released at line 176 before setState(State::Starting) at line 178. This creates a window where:

  1. Another thread could call start() and pass the state == State::Stopped assertion
  2. State could be observed as Stopped when it's conceptually transitioning

The lock-unlock-lock pattern (lines 172-176, then 180-182) is also inefficient.

🔎 Proposed fix: keep state transition under single lock
     void start() {
         mutex.lock();
         assert(mainFunction);
         assert(state == State::Stopped);
         assert(stackSize > 0 && stackSize < (UINT16_MAX * sizeof(StackType_t)));
-        mutex.unlock();
-
-        setState(State::Starting);
-
-        mutex.lock();
         uint32_t stack_depth = stackSize / sizeof(StackType_t);
+        state = State::Starting;
+        StateCallback cb = stateCallback;
+        void* ctx = stateCallbackContext;
         mutex.unlock();
+
+        if (cb) {
+            cb(State::Starting, ctx);
+        }

         BaseType_t result;
🧹 Nitpick comments (2)
TactilityFreeRtos/Include/Tactility/Thread.h (2)

72-73: Use LOCAL_STORAGE_SELF_POINTER_INDEX constant and protect taskHandle write.

Line 72 uses hardcoded 0 instead of the LOCAL_STORAGE_SELF_POINTER_INDEX constant defined at line 20.

Additionally, taskHandle = nullptr at line 73 is written without mutex protection, which can race with getTaskHandle() calls from join() on another thread.

🔎 Proposed fix
-        vTaskSetThreadLocalStoragePointer(nullptr, 0, nullptr);
-        thread->taskHandle = nullptr;
+        vTaskSetThreadLocalStoragePointer(nullptr, LOCAL_STORAGE_SELF_POINTER_INDEX, nullptr);
+        thread->mutex.lock();
+        thread->taskHandle = nullptr;
+        thread->mutex.unlock();

90-97: Consider deadlock risk when invoking callback under mutex.

The stateCallback is invoked while the mutex is held. If the callback attempts to call any Thread method that also acquires the mutex, this will deadlock (if Mutex is non-recursive) or cause unexpected reentrant behavior.

Consider either documenting this constraint clearly for users, or invoking the callback outside the lock.

🔎 Alternative: invoke callback outside lock
     void setState(State newState) {
+        StateCallback cb = nullptr;
+        void* ctx = nullptr;
         mutex.lock();
         state = newState;
-        if (stateCallback) {
-            stateCallback(state, stateCallbackContext);
-        }
+        cb = stateCallback;
+        ctx = stateCallbackContext;
         mutex.unlock();
+        if (cb) {
+            cb(newState, ctx);
+        }
     }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 94d9c1d and fd45aa6.

📒 Files selected for processing (7)
  • .github/workflows/tests.yml
  • Devices/m5stack-cardputer-adv/Source/devices/CardputerKeyboard.cpp
  • Drivers/BQ25896/Source/Bq25896.cpp
  • LICENSE.md
  • TactilityFreeRtos/Include/Tactility/EventGroup.h
  • TactilityFreeRtos/Include/Tactility/Thread.h
  • TactilityFreeRtos/Include/Tactility/kernel/Kernel.h
🚧 Files skipped from review as they are similar to previous changes (3)
  • TactilityFreeRtos/Include/Tactility/EventGroup.h
  • .github/workflows/tests.yml
  • TactilityFreeRtos/Include/Tactility/kernel/Kernel.h
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-10-26T12:44:21.858Z
Learnt from: KenVanHoeylandt
Repo: ByteWelder/Tactility PR: 391
File: Boards/CYD-2432S028RV3/Source/devices/Display.h:11-19
Timestamp: 2025-10-26T12:44:21.858Z
Learning: In the Tactility codebase, prefer `constexpr auto` for constant declarations (such as LCD pin configurations, resolutions, and buffer sizes) rather than explicit type annotations like `gpio_num_t`, `spi_host_device_t`, `uint16_t`, or `size_t`.

Applied to files:

  • Drivers/BQ25896/Source/Bq25896.cpp
📚 Learning: 2025-11-02T15:49:07.895Z
Learnt from: KenVanHoeylandt
Repo: ByteWelder/Tactility PR: 406
File: Buildscripts/CDN/devices.properties:0-0
Timestamp: 2025-11-02T15:49:07.895Z
Learning: In the ByteWelder/Tactility repository, alphabetical ordering issues (such as entries in Buildscripts/CDN/devices.properties) and similar organizational/formatting concerns should be marked as "minor" severity rather than "major" severity.

Applied to files:

  • LICENSE.md
🧬 Code graph analysis (1)
TactilityFreeRtos/Include/Tactility/Thread.h (1)
TactilityFreeRtos/Include/Tactility/kernel/Kernel.h (3)
  • tt (17-99)
  • getTicks (27-33)
  • delayTicks (70-77)
🪛 LanguageTool
LICENSE.md

[grammar] ~10-~10: Use a hyphen to join words.
Context: ...me](https://kenvanhoeylandt.net). Third party projects that were included in Tac...

(QB_NEW_EN_HYPHEN)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: BuildSdk (generic-esp32, esp32)
  • GitHub Check: BuildSdk (generic-esp32c6, esp32c6)
  • GitHub Check: BuildSdk (generic-esp32p4, esp32p4)
  • GitHub Check: BuildSdk (generic-esp32s3, esp32s3)
  • GitHub Check: Linux
  • GitHub Check: macOS
  • GitHub Check: run
🔇 Additional comments (9)
LICENSE.md (1)

1-26: Licensing structure appropriately updated for new TactilityFreeRtos component.

The reorganization cleanly separates GPL v3 (main Tactility code) and Apache 2.0 (TactilityC, TactilityFreeRtos, TactilitySDK) licensed components. The FAQ clarifies the build policy for external vs. internal apps. The changes align well with the PR objective to introduce the TactilityFreeRtos subproject.

Drivers/BQ25896/Source/Bq25896.cpp (2)

3-4: LGTM: Logging header added correctly.

The include for Tactility/Log.h is necessary for the logging statements used in the power control methods.


5-5: LGTM: TAG correctly set and follows preferred pattern.

The TAG is now correctly set to "BQ25896", matching the component name. This resolves the issue from the previous review where it was incorrectly set to "BQ27220". The use of constexpr auto* follows the codebase preference.

Based on learnings, constexpr auto is preferred for constant declarations in the Tactility codebase.

Devices/m5stack-cardputer-adv/Source/devices/CardputerKeyboard.cpp (2)

128-130: LGTM! Timer construction follows the new API correctly.

The timer is now constructed with an explicit tick-based delay using pdMS_TO_TICKS(20), which properly converts 20ms to FreeRTOS ticks. This aligns with the broader refactoring to the new FreeRTOS timer integration.


138-138: LGTM! Timer start correctly uses the new parameterless API.

The start() call now relies on the timer's internal delay configured during construction, which is the correct usage pattern for the updated timer API.

TactilityFreeRtos/Include/Tactility/Thread.h (4)

121-163: LGTM!

Setter methods consistently acquire the mutex, validate preconditions, and release the mutex. The setStackSize assertion now correctly validates newStackSize.


246-252: LGTM!

The getStackSpace() method correctly guards against ISR context and null task handle before calling uxTaskGetStackHighWaterMark().


259-262: LGTM!

Clearly named priority constants improve code readability and consistency across the codebase.


165-169: Remove scoped lock concern—pattern is correct by design.

The code pattern auto lock = mutex.asScopedLock(); lock.lock(); is intentional and correct. According to the Lock.h documentation and ScopedLock implementation, asScopedLock() returns an unlocked scoped lock guard that requires manual lock() call. Automatic unlock() is handled by the destructor (RAII). This pattern is consistently used throughout the codebase and is the intended design.

Likely an incorrect or invalid review comment.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
Devices/unphone/Source/UnPhoneFeatures.cpp (1)

40-59: Consider using std::atomic<bool> for thread-safe interruption.

The buttonHandlingThreadInterruptRequest flag is written in the destructor (line 63) and read in the thread loop (line 42) without synchronization, creating a data race. While this pattern often works for simple shutdown flags, it's technically undefined behavior.

For proper thread safety, consider changing buttonHandlingThreadInterruptRequest to std::atomic<bool>.

🔎 Proposed refactor to use atomic flag

In the header file (UnPhoneFeatures.h), change the member declaration to:

std::atomic<bool> buttonHandlingThreadInterruptRequest{false};

The usage in this file remains the same, but reads and writes will now be atomic operations.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fd45aa6 and c734081.

📒 Files selected for processing (3)
  • Devices/unphone/Source/UnPhoneFeatures.cpp
  • LICENSE.md
  • TactilityC/Source/symbols/freertos.cpp
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-02T15:49:07.895Z
Learnt from: KenVanHoeylandt
Repo: ByteWelder/Tactility PR: 406
File: Buildscripts/CDN/devices.properties:0-0
Timestamp: 2025-11-02T15:49:07.895Z
Learning: In the ByteWelder/Tactility repository, alphabetical ordering issues (such as entries in Buildscripts/CDN/devices.properties) and similar organizational/formatting concerns should be marked as "minor" severity rather than "major" severity.

Applied to files:

  • LICENSE.md
📚 Learning: 2025-10-26T12:44:21.858Z
Learnt from: KenVanHoeylandt
Repo: ByteWelder/Tactility PR: 391
File: Boards/CYD-2432S028RV3/Source/devices/Display.h:11-19
Timestamp: 2025-10-26T12:44:21.858Z
Learning: In the Tactility codebase, prefer `constexpr auto` for constant declarations (such as LCD pin configurations, resolutions, and buffer sizes) rather than explicit type annotations like `gpio_num_t`, `spi_host_device_t`, `uint16_t`, or `size_t`.

Applied to files:

  • Devices/unphone/Source/UnPhoneFeatures.cpp
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: BuildSdk (generic-esp32, esp32)
  • GitHub Check: BuildSdk (generic-esp32c6, esp32c6)
  • GitHub Check: BuildSdk (generic-esp32s3, esp32s3)
  • GitHub Check: BuildSdk (generic-esp32p4, esp32p4)
  • GitHub Check: macOS
  • GitHub Check: Linux
  • GitHub Check: run
🔇 Additional comments (4)
LICENSE.md (1)

1-3: Licensing structure clarifications look good.

The changes effectively restructure the licensing documentation to clearly distinguish the main GPL v3 license for the core Tactility project from the secondary Apache License 2.0 covering TactilityC, TactilityFreeRtos, and TactilitySDK. The FAQ addition helpfully clarifies the build policy for developers. The past grammatical issue ("Third party" → "Third-party") has been resolved.

Also applies to: 10-10, 12-17, 23-26

Devices/unphone/Source/UnPhoneFeatures.cpp (2)

4-4: LGTM: Include changes improve clarity.

The include reordering and explicit addition of <esp_io_expander.h> are good improvements, making the dependencies clear since this file uses the IO expander APIs extensively.

Also applies to: 9-9


102-106: LGTM: Type-safe thread setup.

The refactoring to use setMainFunction with a lambda that wraps buttonHandlingThreadMain is a clean, type-safe improvement over the previous callback approach. The lambda capture is safe since the thread is properly joined in the destructor.

TactilityC/Source/symbols/freertos.cpp (1)

6-6: Remove the unused <Tactility/freertoscompat/RTOS.h> include.

The file already includes <freertos/task.h> and <freertos/event_groups.h> directly, which transitively provide all necessary FreeRTOS declarations. The compatibility header wrapper is not used and can be removed.

⛔ Skipped due to learnings
Learnt from: NellowTCS
Repo: ByteWelder/Tactility PR: 405
File: Tactility/Source/hal/usb/UsbTusb.cpp:12-12
Timestamp: 2025-11-01T19:57:58.233Z
Learning: In ESP-IDF projects like Tactility, the wear levelling header can be included directly as `#include <wear_levelling.h>` without requiring a directory prefix like `wear_levelling/wear_levelling.h`.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
Devices/lilygo-tlora-pager/Source/devices/TpagerKeyboard.cpp (1)

93-93: Use tt::kernel::millisToTicks() for consistency.

Line 93 still uses the old FreeRTOS tick conversion 50 / portTICK_PERIOD_MS, while the timer code (lines 123, 128) now uses tt::kernel::millisToTicks(). For consistency with the new API pattern introduced in this PR, consider updating this line as well.

🔎 Proposed refactor
-            if (chr != '\0') xQueueSend(queue, &chr, 50 / portTICK_PERIOD_MS);
+            if (chr != '\0') xQueueSend(queue, &chr, tt::kernel::millisToTicks(50));
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c734081 and bf80b7d.

📒 Files selected for processing (2)
  • Devices/lilygo-tlora-pager/Source/devices/TpagerKeyboard.cpp
  • TactilityC/Source/symbols/freertos.cpp
🧰 Additional context used
🧬 Code graph analysis (2)
Devices/lilygo-tlora-pager/Source/devices/TpagerKeyboard.cpp (1)
TactilityFreeRtos/Include/Tactility/kernel/Kernel.h (1)
  • millisToTicks (58-64)
TactilityC/Source/symbols/freertos.cpp (1)
TactilityC/Source/tt_init.cpp (16)
  • ESP_ELFSYM_EXPORT (69-69)
  • ESP_ELFSYM_EXPORT (70-70)
  • ESP_ELFSYM_EXPORT (71-71)
  • ESP_ELFSYM_EXPORT (72-72)
  • ESP_ELFSYM_EXPORT (73-73)
  • ESP_ELFSYM_EXPORT (74-74)
  • ESP_ELFSYM_EXPORT (75-75)
  • ESP_ELFSYM_EXPORT (76-76)
  • ESP_ELFSYM_EXPORT (78-78)
  • ESP_ELFSYM_EXPORT (79-79)
  • ESP_ELFSYM_EXPORT (81-81)
  • ESP_ELFSYM_EXPORT (83-83)
  • ESP_ELFSYM_EXPORT (84-84)
  • ESP_ELFSYM_EXPORT (85-85)
  • ESP_ELFSYM_EXPORT (86-86)
  • ESP_ELFSYM_EXPORT (87-87)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: Linux
  • GitHub Check: BuildSdk (generic-esp32c6, esp32c6)
  • GitHub Check: BuildSdk (generic-esp32s3, esp32s3)
  • GitHub Check: BuildSdk (generic-esp32, esp32)
  • GitHub Check: BuildSdk (generic-esp32p4, esp32p4)
  • GitHub Check: macOS
  • GitHub Check: run
🔇 Additional comments (6)
Devices/lilygo-tlora-pager/Source/devices/TpagerKeyboard.cpp (4)

2-6: LGTM: Include reordering.

The include reorganization improves code organization by grouping related headers together.


123-125: LGTM: Timer construction updated to new API.

The timer construction correctly uses the new API with tt::kernel::millisToTicks(20) for portable tick conversion and explicit Type::Periodic specification.


128-130: LGTM: Timer construction follows the new API pattern.

Consistent with the inputTimer changes, using tt::kernel::millisToTicks(50) for portable tick conversion.


138-139: LGTM: Timer start calls updated correctly.

The start() calls correctly omit the period parameter, which is now specified during timer construction.

TactilityC/Source/symbols/freertos.cpp (2)

6-6: LGTM: Header path updated correctly.

The header path change from <Tactility/RtosCompat.h> to <Tactility/freertoscompat/RTOS.h> aligns with the PR's introduction of the TactilityFreeRtos component and freertoscompat headers.


11-79: LGTM: Duplicate exports resolved, comprehensive symbol coverage added.

The duplicate symbol exports flagged in the previous review have been successfully resolved. Each symbol now appears exactly once in the export table. The additions provide comprehensive coverage of FreeRTOS APIs across Task, EventGroup, Queue, Timer, and portmacro categories, which should properly support the ELF loader/bridge functionality.

@KenVanHoeylandt KenVanHoeylandt merged commit 7283920 into main Jan 2, 2026
47 checks passed
@KenVanHoeylandt KenVanHoeylandt deleted the develop branch January 2, 2026 23:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants