diff --git a/CMakeLists.txt b/CMakeLists.txt index ba652e7..b4c4701 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,7 +29,7 @@ add_library(puara_gestures include/puara/descriptors/roll.h include/puara/descriptors/shake.h include/puara/descriptors/tilt.h - include/puara/descriptors/touch.h + include/puara/descriptors/touchArrayGestureDetector.h include/puara/utils/blobDetector.h include/puara/utils/calibration.h diff --git a/exampleProjects/touch/src/TinyTouch.hpp b/exampleProjects/touch/src/TinyTouch.hpp index ec1332c..a3a33a2 100644 --- a/exampleProjects/touch/src/TinyTouch.hpp +++ b/exampleProjects/touch/src/TinyTouch.hpp @@ -2,18 +2,24 @@ #include -#include +#include // Touchscreen library #include // Core graphics library -#include // Hardware-specific library for ST7789 +#include // Display library #include /** * @class TinyTouch - * @brief A class to handle touchscreen interactions using the Adafruit ILI9341 and FT6206 libraries. + * @brief A class to demonstrate how to detect gestures on a touch-array like device (here using + * a capacitive touchscreen available in Wokwi) ran on a TinyPICO board. * - * This class is designed to test the Puara gesture framework by providing a simple interface - * for initializing the screen, displaying introductory messages, detecting touch events, and - * drawing rectangles on the screen. It uses the Puara Gesture library to recognise gestures. + * This class is designed to demonstrate how to use the Puara gesture framework by providing a + * simple interface for displaying an introductory message, drawing rectangles on the screen to + * simulate a touch array, and detecting touch events on this array. It uses the + * TouchArrayGestureDetector to recognise gestures. + * + * The associated wokwi diagram has one `board-ili9341-cap-touch` touch screen device, which is + * split into its display and touch components in class members `Adafruit_ILI9341 display` + * and `Adafruit_FT6206 touchScreen`. */ class TinyTouch { @@ -29,13 +35,15 @@ class TinyTouch */ void initScreen() { - tft.begin(); - screenHeight = tft.height(); - screenWidth = tft.width(); + //initialize the display + display.begin(); + screenHeight = display.height(); + screenWidth = display.width(); rectangleHeight = screenHeight / RECT_COUNT; Serial.println(F("Touchscreen is initialized")); - if(!ctp.begin()) + //initialize the touchscreen + if(!touchScreen.begin()) { Serial.println("Couldn't start touchscreen controller"); while(1) @@ -49,56 +57,54 @@ class TinyTouch */ void printIntro() { - tft.fillScreen(ILI9341_BLACK); - tft.setTextColor(ILI9341_WHITE); - tft.setTextSize(2); - tft.setCursor(10, 100); - tft.println("Puara Gesture Test!"); - tft.setTextSize(1); - tft.setCursor(10, 130); - tft.println("Touch the screen to begin."); + display.fillScreen(ILI9341_BLACK); + display.setTextColor(ILI9341_WHITE); + display.setTextSize(2); + display.setCursor(10, 100); + display.println("Puara Gesture Test!"); + display.setTextSize(1); + display.setCursor(10, 130); + display.println("Touch the screen to begin."); } /** * @brief Checks if the touchscreen is currently being touched. * @return True if the touchscreen is being touched, false otherwise. */ - bool touched() { return ctp.touched(); } + bool touched() { return touchScreen.touched(); } /** * @brief Draws rectangles on the screen. * * This function divides the screen into a series of rectangles and draws them. These - * discrete rectangles will be used by puara to calculate various touch gestures. + * discrete rectangles will simulate stripes in a touch array, which will be used + * by the TouchArrayGestureDetector to calculate various gestures. */ void drawRectangles() { // Draw rectangles - tft.fillScreen(ILI9341_BLACK); + display.fillScreen(ILI9341_BLACK); for(int i = 0; i < RECT_COUNT; i++) - tft.drawRect(0, i * rectangleHeight, screenWidth, rectangleHeight, ILI9341_WHITE); + display.drawRect(0, i * rectangleHeight, screenWidth, rectangleHeight, ILI9341_WHITE); } /** - * @brief Updates the touch state of rectangles and redraws them on the screen. + * @brief Updates the touch state of rectangles and the gestures in the TouchArrayGestureDetector. * - * This function resets the current touch states, checks for new touch inputs, + * This function resets the current touched rectangles, checks for new touch inputs, * and updates the screen and internal state to reflect changes. Touched * rectangles are filled with white, while untapped ones are filled with black * and outlined in white. - * - * @pre `ctp` must be initialized, and rectangle dimensions must match screen setup. - * @post The screen reflects the updated touch states. */ - void updateRectangles() + void update() { // Reset the current touch array std::fill_n(touchedRectangles, RECT_COUNT, 0); // Check for touches - if(ctp.touched()) + if(touchScreen.touched()) { - TS_Point p{ctp.getPoint()}; + TS_Point p{touchScreen.getPoint()}; p.x = map(p.x, 0, 240, 240, 0); p.y = map(p.y, 0, 320, 320, 0); @@ -113,14 +119,14 @@ class TinyTouch { if(touchedRectangles[i] == 1 && previouslyTouchedRectangles[i] == 0) { - // Rectangle is touched - make it white - tft.fillRect(0, i * rectangleHeight, screenWidth, rectangleHeight, ILI9341_WHITE); + // Rectangle was just touched - make it white + display.fillRect(0, i * rectangleHeight, screenWidth, rectangleHeight, ILI9341_WHITE); } else if(touchedRectangles[i] == 0 && previouslyTouchedRectangles[i] == 1) { // Rectangle no longer touched - make it black - tft.fillRect(0, i * rectangleHeight, screenWidth, rectangleHeight, ILI9341_BLACK); - tft.drawRect(0, i * rectangleHeight, screenWidth, rectangleHeight, ILI9341_WHITE); + display.fillRect(0, i * rectangleHeight, screenWidth, rectangleHeight, ILI9341_BLACK); + display.drawRect(0, i * rectangleHeight, screenWidth, rectangleHeight, ILI9341_WHITE); } } @@ -149,8 +155,8 @@ class TinyTouch int screenWidth{-1}; int rectangleHeight{-1}; - Adafruit_ILI9341 tft{TinyTouch::PIN_CS, TinyTouch::PIN_DC}; - Adafruit_FT6206 ctp; + Adafruit_ILI9341 display{TinyTouch::PIN_CS, TinyTouch::PIN_DC}; + Adafruit_FT6206 touchScreen; //======================== setup puara ======================= static constexpr int RECT_COUNT{16}; diff --git a/exampleProjects/touch/src/main.cpp b/exampleProjects/touch/src/main.cpp index 4c04749..1b7e37a 100644 --- a/exampleProjects/touch/src/main.cpp +++ b/exampleProjects/touch/src/main.cpp @@ -26,6 +26,6 @@ void setup(void) void loop() { - tinyTouch.updateRectangles(); + tinyTouch.update(); tinyTouch.printUpdate(); } diff --git a/include/puara/descriptors/brushRub.h b/include/puara/descriptors/brushRub.h index 91b402e..da60403 100644 --- a/include/puara/descriptors/brushRub.h +++ b/include/puara/descriptors/brushRub.h @@ -15,7 +15,6 @@ namespace puara_gestures class ValueIntegrator { public: - //TODO VB: we need at least this default constructor because of the non-default constructor for the tied value ValueIntegrator() = default; ValueIntegrator(const ValueIntegrator&) noexcept = default; ValueIntegrator(ValueIntegrator&&) noexcept = default; @@ -44,7 +43,7 @@ class ValueIntegrator { const auto delta = newValue - prevValue; prevValue = newValue; - //TODO VB: floating point comparison + // No delta since the last update -> potentially reset the value if(delta == 0.0) { @@ -91,18 +90,10 @@ class ValueIntegrator /** * @brief Ties the feature to an external value. * @param new_tie Pointer to the external value to tie the feature to. - * @return True if the tie was successful; false if `new_tie` is null. */ - bool tie(double* new_tie) + void tie(double& new_tie) { - if(new_tie == nullptr) - { - assert(false && "tied_value cannot be null!"); - return false; - } - - tied_data = new_tie; - return true; + tied_data = &new_tie; } protected: @@ -198,7 +189,7 @@ class Rub : public ValueIntegrator //====================================================================== -class RubAndBrushDetector +class BrushRubDetector { public: Brush brush; diff --git a/include/puara/descriptors/touchArrayGestureDetector.h b/include/puara/descriptors/touchArrayGestureDetector.h index a4935ff..bee2e7d 100644 --- a/include/puara/descriptors/touchArrayGestureDetector.h +++ b/include/puara/descriptors/touchArrayGestureDetector.h @@ -9,14 +9,27 @@ namespace puara_gestures // touchSizeEdge: number of stripes for top and bottom portions (arbitrary) //maxNumBlobs: maximum number of "blobs" that the blob detector can detect at once on the touch array + +/** + * @class TouchArrayGestureDetector + * @brief A class to detect gestures on a touch-array device. + * + * This class is designed to detect various gestures on a touch-array device by analyzing touch data. + * It detects blobs (contiguous touched stripes in the touch array), and for each blob, it calculates + * the amount of "brushing" and "rubbing" gestures using a BrushRubDetector. + * + * @tparam maxNumBlobs The maximum number of blobs that the blob detector can detect at once on the touch array. + * @tparam touchSizeEdge The number of stripes for the top and bottom portions of the touch array. + */ template class TouchArrayGestureDetector { public: - float touchAll{}; // f, 0--1 - float touchTop{}; // f, 0--1 - float touchMiddle{}; // f, 0--1 - float touchBottom{}; // f, 0--1 + //these four values are the average amount of touch for the entire touch array, as well as the top, middle and bottom parts. + float totalTouchAverage{}; // f, 0--1 + float topTouchAverage{}; // f, 0--1 + float middleTouchAverage{}; // f, 0--1 + float bottomTouchAverage{}; // f, 0--1 /** total amount of "brushing" gesture for all detected blobs on the touch array, in ~cm/s (distance between stripes = ~1.5cm) */ float totalBrush{}; @@ -31,33 +44,35 @@ class TouchArrayGestureDetector { // Update the "amount of touch" for the entire touch sensor, as well as the top, middle and bottom parts. // All normalized between 0 and 1. - touchAll = utils::arrayAverage(touchArray, 0, touchSize); - touchTop = utils::arrayAverage(touchArray, 0, touchSizeEdge); - touchMiddle = utils::arrayAverage(touchArray, (0 + touchSizeEdge), (touchSize - touchSizeEdge)); - touchBottom = utils::arrayAverage(touchArray, (touchSize - touchSizeEdge), touchSize); + totalTouchAverage = utils::arrayAverage(touchArray, 0, touchSize); + topTouchAverage = utils::arrayAverage(touchArray, 0, touchSizeEdge); + middleTouchAverage = utils::arrayAverage(touchArray, (0 + touchSizeEdge), (touchSize - touchSizeEdge)); + bottomTouchAverage = utils::arrayAverage(touchArray, (touchSize - touchSizeEdge), touchSize); //detect blobs on the touch array blobDetector.detect1D(touchArray, touchSize); + //based on the start position of detected blobs, update the brush and rub detector for(int i = 0; i < maxNumBlobs; ++i) - rubAndBrushDetector[i].update(blobDetector.blobStartPos[i]); + brushRubDetector[i].update(blobDetector.blobStartPos[i]); - updateBrushAndRub(); + //and finally update the total brush and rub values + updateTotalBrushAndRub(); } private: BlobDetector blobDetector; - RubAndBrushDetector rubAndBrushDetector[maxNumBlobs]; + BrushRubDetector brushRubDetector[maxNumBlobs]; - void updateBrushAndRub() + void updateTotalBrushAndRub() { // Calculate total brush and rub values double brushes[maxNumBlobs]; double rubs[maxNumBlobs]; for(int i = 0; i < maxNumBlobs; ++i) { - brushes[i] = rubAndBrushDetector[i].brush.value; - rubs[i] = rubAndBrushDetector[i].rub.value; + brushes[i] = brushRubDetector[i].brush.value; + rubs[i] = brushRubDetector[i].rub.value; } totalBrush = utils::arrayAverageZero(brushes, maxNumBlobs); diff --git a/include/puara/utils/blobDetector.h b/include/puara/utils/blobDetector.h index 7ce87ac..da29560 100644 --- a/include/puara/utils/blobDetector.h +++ b/include/puara/utils/blobDetector.h @@ -16,13 +16,14 @@ namespace puara_gestures * - The size in terms of consecutive `1`s (`blobSize`) * - The center index (`blobCenter`) * + * @tparam maxNumBlobs The maximum number of blobs that the blob detector can detect at once. + * * @warning Most of these values are reset when a detection function, e.g., detect1D(), is called, * and calculated during the detection funtion calls -- so their value is only really valid right * after such a call. There is no locking mechanism on any of these values so it is on the client * to ensure there is no race conditions. * * @note - * - The number of blobs processed is set by the template parameter `maxNumBlobs`. * - If the input contains more blobs than `maxNumBlobs`, the additional blobs are ignored. */ template diff --git a/tests/testing_touch.cpp b/tests/testing_touch.cpp index 1f04716..3ed691f 100644 --- a/tests/testing_touch.cpp +++ b/tests/testing_touch.cpp @@ -33,7 +33,7 @@ int main() // Update the touch data and print the computed values touchArrayGD.update(touchArray, touchSize); - std::cout << "touchAll: " << touchArrayGD.touchAll << std::endl; + std::cout << "totalTouchAverage: " << touchArrayGD.totalTouchAverage << std::endl; std::cout << "brush: " << touchArrayGD.totalBrush << std::endl; std::cout << "rub: " << touchArrayGD.totalRub << std::endl; @@ -61,7 +61,7 @@ int main() // Update the touch data and print the computed values touchArrayGD.update(touchArray, touchSize); - std::cout << "touchAll: " << touchArrayGD.touchAll << std::endl; + std::cout << "totalTouchAverage: " << touchArrayGD.totalTouchAverage << std::endl; std::cout << "brush: " << touchArrayGD.totalBrush << std::endl; std::cout << "rub: " << touchArrayGD.totalRub << std::endl; }