Skip to content

Add calibration methods and app#520

Open
NellowTCS wants to merge 5 commits intoTactilityProject:mainfrom
NellowTCS:calibrator
Open

Add calibration methods and app#520
NellowTCS wants to merge 5 commits intoTactilityProject:mainfrom
NellowTCS:calibrator

Conversation

@NellowTCS
Copy link
Copy Markdown
Contributor

@NellowTCS NellowTCS commented Apr 6, 2026

This pull request introduces a unified and extensible touch calibration system for touch drivers, integrating persistent calibration settings, a new settings API, and a user interface for calibration. The changes remove legacy per-driver calibration logic and replace it with a centralized approach, making calibration consistent across drivers and accessible from the display settings UI.

Summary by CodeRabbit

  • New Features

    • Touch Calibration app — four-point guided workflow with on-screen targets.
    • “Calibrate” button appears in Display when a calibratable touch device is present.
    • Persistent touch calibration settings with runtime enable/disable.
  • Improvements

    • Touch drivers now report calibration capability and apply active calibration for more accurate input.
    • Simplified touch sampling and mapping for more consistent touch detection.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 6, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a full touch calibration flow: a new Touch Calibration app and manifest; a touch calibration settings API and implementation that persist, validate, cache, and apply calibration data; a new TouchDevice::supportsCalibration() capability and drivers overriding it; XPT2046 drivers updated to expose raw readings and apply centralized calibration (including an esp_lcd touch process_coordinates callback that uses driver user_data); XPT2046SoftSPI refactored to remove onboard NVS calibration and use raw sampling with centralized calibration; Display app UI now shows a “Calibrate” button when a calibratable device is present.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary change: introducing a centralized touch calibration system with both methods and a user-facing app, which aligns with the substantial additions of calibration settings API, calibration app, and integration across multiple drivers.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Copy Markdown

@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

🧹 Nitpick comments (3)
Tactility/Source/app/display/Display.cpp (1)

134-137: Consider removing unused user_data parameter.

The callback receives this as user_data (line 311) but doesn't use it. This is harmless but could be cleaner.

♻️ Optional cleanup
     static void onCalibrateTouchClicked(lv_event_t* event) {
         (void)event;
         app::start("TouchCalibration");
     }

And on line 311:

-            lv_obj_add_event_cb(calibrate_button, onCalibrateTouchClicked, LV_EVENT_SHORT_CLICKED, this);
+            lv_obj_add_event_cb(calibrate_button, onCalibrateTouchClicked, LV_EVENT_SHORT_CLICKED, nullptr);
Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.cpp (1)

147-173: Sampling introduces ~8ms latency per touch read.

The readRawPoint function takes 8 samples with 1ms delay between each (line 163), resulting in ~8ms minimum latency per touch poll. Combined with LVGL's input polling rate, this could affect touch responsiveness.

This may be intentional for noise filtering, but worth noting for touch-heavy UI interactions.

Tactility/Source/settings/TouchCalibrationSettings.cpp (1)

82-89: Reject invalid enabled calibration settings at save-time.

Consider enforcing isValid(settings) in save() when settings.enabled is true, so invalid calibration never gets persisted.

♻️ Proposed tweak
 bool save(const TouchCalibrationSettings& settings) {
+    if (settings.enabled && !isValid(settings)) {
+        return false;
+    }
     std::map<std::string, std::string> map;

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ce2bbcf6-1437-4005-8516-64815f0cd904

📥 Commits

Reviewing files that changed from the base of the PR and between e64f4ff and 316e159.

📒 Files selected for processing (11)
  • Drivers/XPT2046/Source/Xpt2046Touch.cpp
  • Drivers/XPT2046/Source/Xpt2046Touch.h
  • Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.cpp
  • Drivers/XPT2046SoftSPI/Source/Xpt2046SoftSpi.h
  • Tactility/Include/Tactility/app/touchcalibration/TouchCalibration.h
  • Tactility/Include/Tactility/hal/touch/TouchDevice.h
  • Tactility/Include/Tactility/settings/TouchCalibrationSettings.h
  • Tactility/Source/Tactility.cpp
  • Tactility/Source/app/display/Display.cpp
  • Tactility/Source/app/touchcalibration/TouchCalibration.cpp
  • Tactility/Source/settings/TouchCalibrationSettings.cpp

Copy link
Copy Markdown

@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

♻️ Duplicate comments (2)
Tactility/Source/settings/TouchCalibrationSettings.cpp (2)

61-64: ⚠️ Potential issue | 🟠 Major

std::strtol parsing is currently lossy and can silently accept bad values.

Line 61-Line 64 parse with nullptr endptr and no errno/range checks, so malformed or out-of-range values can be accepted unexpectedly.

🧮 Proposed fix
 `#include` <algorithm>
+#include <cerrno>
 `#include` <cstdlib>
+#include <limits>
 `#include` <map>
 `#include` <string>
@@
 static bool toBool(const std::string& value) {
     return value == "1" || value == "true" || value == "True";
 }
+
+static bool parseInt32(const std::string& value, int32_t& out) {
+    errno = 0;
+    char* end = nullptr;
+    const long parsed = std::strtol(value.c_str(), &end, 10);
+    if (errno != 0 || end == value.c_str() || *end != '\0') {
+        return false;
+    }
+    if (parsed < std::numeric_limits<int32_t>::min() || parsed > std::numeric_limits<int32_t>::max()) {
+        return false;
+    }
+    out = static_cast<int32_t>(parsed);
+    return true;
+}
@@
-    loaded.xMin = static_cast<int32_t>(std::strtol(x_min_it->second.c_str(), nullptr, 10));
-    loaded.xMax = static_cast<int32_t>(std::strtol(x_max_it->second.c_str(), nullptr, 10));
-    loaded.yMin = static_cast<int32_t>(std::strtol(y_min_it->second.c_str(), nullptr, 10));
-    loaded.yMax = static_cast<int32_t>(std::strtol(y_max_it->second.c_str(), nullptr, 10));
+    if (!parseInt32(x_min_it->second, loaded.xMin) ||
+        !parseInt32(x_max_it->second, loaded.xMax) ||
+        !parseInt32(y_min_it->second, loaded.yMin) ||
+        !parseInt32(y_max_it->second, loaded.yMax)) {
+        return false;
+    }

94-105: ⚠️ Potential issue | 🔴 Critical

Cache state is mutated/read without synchronization.

Line 94-Line 105 accesses shared mutable globals from save() and getActive() without locking; concurrent callers can race and observe torn/stale state.

🔒 Proposed fix
 `#include` <algorithm>
 `#include` <cstdlib>
 `#include` <map>
+#include <mutex>
 `#include` <string>
@@
 static bool cacheInitialized = false;
 static TouchCalibrationSettings cachedSettings;
+static std::mutex cacheMutex;
@@
 bool save(const TouchCalibrationSettings& settings) {
+    std::lock_guard<std::mutex> lock(cacheMutex);
     std::map<std::string, std::string> map;
@@
 TouchCalibrationSettings getActive() {
+    std::lock_guard<std::mutex> lock(cacheMutex);
     if (!cacheInitialized) {
         cachedSettings = loadOrGetDefault();
         cacheInitialized = true;
     }
     return cachedSettings;
 }
🧹 Nitpick comments (1)
Tactility/Source/settings/TouchCalibrationSettings.cpp (1)

82-97: Validate calibration invariants before persisting enabled settings.

save() writes and caches any payload. Rejecting invalid enabled settings here prevents storing unusable calibration state.

✅ Proposed guard in save path
 bool save(const TouchCalibrationSettings& settings) {
+    if (settings.enabled && !isValid(settings)) {
+        return false;
+    }
+
     std::map<std::string, std::string> map;
     map[SETTINGS_KEY_ENABLED] = settings.enabled ? "1" : "0";

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f982477e-f476-4543-bc1c-52955f4ecb74

📥 Commits

Reviewing files that changed from the base of the PR and between 316e159 and 7005818.

📒 Files selected for processing (1)
  • Tactility/Source/settings/TouchCalibrationSettings.cpp

Copy link
Copy Markdown

@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.

🧹 Nitpick comments (1)
Tactility/Source/app/touchcalibration/TouchCalibration.cpp (1)

117-124: Consider clamping negative touch coordinates.

lv_point_t coordinates could theoretically be negative if a touch is reported outside the display bounds. The direct cast to uint16_t on line 122-123 would wrap around to large positive values, corrupting calibration data.

🛡️ Proposed defensive clamping
         lv_point_t point = {0, 0};
         lv_indev_get_point(indev, &point);
 
         if (sampleCount < 4) {
             samples[sampleCount] = {
-                .x = static_cast<uint16_t>(point.x),
-                .y = static_cast<uint16_t>(point.y),
+                .x = static_cast<uint16_t>(std::max(static_cast<lv_coord_t>(0), point.x)),
+                .y = static_cast<uint16_t>(std::max(static_cast<lv_coord_t>(0), point.y)),
             };

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f92f30be-b36a-4d6a-9b35-11457ce612aa

📥 Commits

Reviewing files that changed from the base of the PR and between 7005818 and 2f8d7f0.

📒 Files selected for processing (5)
  • Drivers/XPT2046/Source/Xpt2046Touch.h
  • Tactility/Include/Tactility/settings/TouchCalibrationSettings.h
  • Tactility/Source/app/display/Display.cpp
  • Tactility/Source/app/touchcalibration/TouchCalibration.cpp
  • Tactility/Source/settings/TouchCalibrationSettings.cpp
✅ Files skipped from review due to trivial changes (1)
  • Tactility/Source/app/display/Display.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • Drivers/XPT2046/Source/Xpt2046Touch.h

@NellowTCS
Copy link
Copy Markdown
Contributor Author

The UI is not fully tested. I will test it properly soon!

Copy link
Copy Markdown

@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)
Tactility/Source/app/touchcalibration/TouchCalibration.cpp (1)

75-90: Prefer a single source of truth for range validation.

MIN_RANGE at Line 75 duplicates constraints already enforced by settings::touch::isValid(settings) at Line 89. Keeping both risks drift if rules change.

Refactor suggestion
-        constexpr int32_t MIN_RANGE = 20;
-
         const int32_t xMin = (static_cast<int32_t>(samples[0].x) + static_cast<int32_t>(samples[3].x)) / 2;
         const int32_t xMax = (static_cast<int32_t>(samples[1].x) + static_cast<int32_t>(samples[2].x)) / 2;
         const int32_t yMin = (static_cast<int32_t>(samples[0].y) + static_cast<int32_t>(samples[1].y)) / 2;
         const int32_t yMax = (static_cast<int32_t>(samples[2].y) + static_cast<int32_t>(samples[3].y)) / 2;
@@
-        if ((xMax - xMin) < MIN_RANGE || (yMax - yMin) < MIN_RANGE || !settings::touch::isValid(settings)) {
+        if (!settings::touch::isValid(settings)) {

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 485043d2-21d0-45fe-a604-e82efba3ed29

📥 Commits

Reviewing files that changed from the base of the PR and between 2f8d7f0 and ec54133.

📒 Files selected for processing (1)
  • Tactility/Source/app/touchcalibration/TouchCalibration.cpp

Copy link
Copy Markdown

@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.

🧹 Nitpick comments (2)
Tactility/Source/app/touchcalibration/TouchCalibration.cpp (2)

147-150: Avoid evicting the freshly-saved calibration on teardown.

Line 150 throws away the cache that settings::touch::save() just refreshed. That makes the first post-calibration read hit settings I/O again for no real gain. Re-enabling runtime calibration is enough here.

♻️ Proposed fix
     void onDestroy(AppContext& app) override {
         (void)app;
         settings::touch::setRuntimeCalibrationEnabled(true);
-        settings::touch::invalidateCache();
     }

75-89: Keep calibration validity rules in one place.

settings::touch::isValid() already owns the minimum-range policy. Duplicating it here makes the UI and persistence path drift-prone if that rule changes later.

♻️ Proposed fix
-        constexpr int32_t MIN_RANGE = 20;
-
         const int32_t xMin = (static_cast<int32_t>(samples[0].x) + static_cast<int32_t>(samples[3].x)) / 2;
         const int32_t xMax = (static_cast<int32_t>(samples[1].x) + static_cast<int32_t>(samples[2].x)) / 2;
         const int32_t yMin = (static_cast<int32_t>(samples[0].y) + static_cast<int32_t>(samples[1].y)) / 2;
         const int32_t yMax = (static_cast<int32_t>(samples[2].y) + static_cast<int32_t>(samples[3].y)) / 2;
@@
-        if ((xMax - xMin) < MIN_RANGE || (yMax - yMin) < MIN_RANGE || !settings::touch::isValid(settings)) {
+        if (!settings::touch::isValid(settings)) {
             lv_label_set_text(titleLabel, "Calibration Failed");
             lv_label_set_text(hintLabel, "Range invalid. Tap to close.");
             lv_obj_add_flag(target, LV_OBJ_FLAG_HIDDEN);
             setResult(Result::Error);
             return;

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 38763d23-dd94-431f-bccb-968b704b73eb

📥 Commits

Reviewing files that changed from the base of the PR and between ec54133 and 9bc8d1e.

📒 Files selected for processing (1)
  • Tactility/Source/app/touchcalibration/TouchCalibration.cpp

@NellowTCS
Copy link
Copy Markdown
Contributor Author

The UI and calibration saving (and runtime calibration) all work!

@NellowTCS
Copy link
Copy Markdown
Contributor Author

@KenVanHoeylandt Ready for review!

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.

1 participant