Skip to content

Commit

Permalink
Second round of refactoring. Isolate touch array logic in its own file,
Browse files Browse the repository at this point in the history
make everything else more agnostic, and address PR comments.
  • Loading branch information
vberthiaume committed Jan 16, 2025
1 parent 98a82c0 commit ecc7782
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 68 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
78 changes: 42 additions & 36 deletions exampleProjects/touch/src/TinyTouch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,24 @@

#include <puara/gestures.h>

#include <Adafruit_FT6206.h>
#include <Adafruit_FT6206.h> // Touchscreen library
#include <Adafruit_GFX.h> // Core graphics library
#include <Adafruit_ILI9341.h> // Hardware-specific library for ST7789
#include <Adafruit_ILI9341.h> // Display library
#include <SPI.h>

/**
* @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
{
Expand All @@ -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)
Expand All @@ -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);

Expand All @@ -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);
}
}

Expand Down Expand Up @@ -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};
Expand Down
2 changes: 1 addition & 1 deletion exampleProjects/touch/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ void setup(void)

void loop()
{
tinyTouch.updateRectangles();
tinyTouch.update();
tinyTouch.printUpdate();
}
17 changes: 4 additions & 13 deletions include/puara/descriptors/brushRub.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -198,7 +189,7 @@ class Rub : public ValueIntegrator

//======================================================================

class RubAndBrushDetector
class BrushRubDetector
{
public:
Brush brush;
Expand Down
43 changes: 29 additions & 14 deletions include/puara/descriptors/touchArrayGestureDetector.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <int maxNumBlobs, int touchSizeEdge>
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{};
Expand All @@ -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<maxNumBlobs> 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);
Expand Down
3 changes: 2 additions & 1 deletion include/puara/utils/blobDetector.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <int maxNumBlobs>
Expand Down
4 changes: 2 additions & 2 deletions tests/testing_touch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
}

0 comments on commit ecc7782

Please sign in to comment.