Logic help with pattern recognition but dealing with wrap around issue

Hello All!

I have a trackpad used as a mouse in relative mode. I don't have absolute positioning, so I'm using x, y relative data to make a detectable pattern to start scrolling.

I am able to get direction using x,y and form a recognizable pattern over elapsed time, the data repeats in this pattern but not necessary starts at 1 or ends at 4.

CW gesture on trackpad: Data: 1234... and repeats
CCW gesture on trackpad: Data: 4321...and repeats

I've seemed to have solved the issue using arrays and 4 inputs but cant seem to be able to compare arrays to known CW and CCW list in arrays. Thanks for all your advice.

      currentMillis = millis();                   //get the current "time" (actually the number of milliseconds since the program started)
      if (currentMillis - startMillis <= period)  //test whether the period has elapsed
      {
        if (data.y < 0) {
          if (data.x < 0) {
            //Serial.println(" NE ");
            direction = 1;
          } else if (data.x > 0) {
            //Serial.println(" NW ");
            direction = 4;
          }
        } else if (data.y > 0) {
          if (data.x < 0) {
            //Serial.println(" SE ");
            direction = 2;
          } else if (data.x > 0) {
            //Serial.println(" SW ")
            direction = 3;
          }
        }

        if (direction != oldDirection){
        //Serial.print(direction);
        input[i] = direction;
        //Serial.print(input[i]);
        i++;
        if (i > 3) 
        i = 0;
        }
        oldDirection = direction;
      }  ///END OF PERIOD
      else {
        Serial.println(" END ");
        startMillis = currentMillis;  //IMPORTANT to save the restart start time.
        ///LOGIC FOR SCROLLING  GREYCODE?

        //CW INC Scroll

        int* final[4] = {&input[1], &input[2], &input[3], &input[4]};
        int CW[4] = {1234,4123,2341,3412};
        int CCW[4] = {4321,1432,3214,2143};
        for (int outer = 0; outer < 3; outer++){
            for (int inner = 0; inner < 3; inner++){
              if (!memcmp(final[outer], CW[inner], 4))  
                   Mouse.move(0,0,1);
              else if (!memcmp(final[outer], CCW[inner], 4))
                   Mouse.move(0,0,-1);
          }
        }

       ///CLEAR THE ARRAY
        for (int x = 0; x < 4; x++)
        input[x];
      }

I can solve it using this bit of code but not seems clunky. Is there a way to pool the data to greycode?

///CW INC SCROLL
         if (input[0] == 1 && input[1] == 2 && input[2] == 3 && input[3] == 4)
         Mouse.move(0,0,1);
         else if (input[0] == 4 && input[1] == 1 && input[2] == 2 && input[3] == 3)
         Mouse.move(0,0,1);
         else if (input[0] == 2 && input[1] == 3 && input[2] == 4  && input[3] == 1)
         Mouse.move(0,0,1);
         else if (input[0] == 3 && input[1] == 4 && input[2] == 1  && input[3] == 2)
         Mouse.move(0,0,1);

         //CCW DEC Scroll
         else if (input[0] == 4 && input[1] == 3 && input[2] == 2  && input[3] == 1)
         Mouse.move(0,0,-1);
         else if (input[0] == 1 && input[1] == 4 && input[2] == 3  && input[3] == 2)
         Mouse.move(0,0,-1);
         else if (input[0] == 3 && input[1] == 2 && input[2] == 1  && input[3] == 4)
         Mouse.move(0,0,-1);
         else if (input[0] == 2 && input[1] == 1 && input[2] == 4  && input[3] == 3)
         Mouse.move(0,0,-1);

Could something simpler like this work?

  currentMillis = millis();                   //get the current "time" (actually the number of milliseconds since the program started)
  if (currentMillis - startMillis <= period) { //test whether the period has elapsed

    if (data.y < 0) {
     if (data.x < 0) {
        //Serial.println(" NE ");
        sector = 1;
      } else if (data.x > 0) {
        //Serial.println(" NW ");
        sector = 4;
      }
    } else if (data.y > 0) {
      if (data.x < 0) {
        //Serial.println(" SE ");
        sector = 2;
      } else if (data.x > 0) {
        //Serial.println(" SW ")
        sector = 3;
      }
    }

    if (sector == oldSector+1 or (sector == 1 and oldSector == 4)) {
     direction = 1;
    }
    else if (sector == oldSector-1 or (sector == 4 and oldSector == 1)) {
      direction = -1;
    }
    else {
      direction = 0;
    }
    oldSector = sector;

    if (direction == oldDirection) {
      dirCount++;
      if (dirCount == 3) {
        Mouse.move(0,0,direction);
        dirCount = 0;
      }
    }
    else {
      dirCount = 0;
      oldDirection = direction;
    }
    startMillis = currentMillis;
  }

Thank you PaulRB for your time!

I get the intended results in serial print of -1 and 1 but no mouse scrolling. So, I modified the code as such adding mouse codes earlier and works but get scrolls with regular mouse movement.


    if (sector == (oldSector + 1) or (sector == 1 and oldSector == 4)) {
     direction = 1;
     Mouse.move(0,0,direction);   ///MODIFED BY ME
     //Serial.println(direction);
    }
    else if (sector == (oldSector - 1) or (sector == 4 and oldSector == 1)) {
      direction = -1;
      Mouse.move(0,0,direction);      ///MODIFED BY ME
      //Serial.println(direction);
    }
    else {
      direction = 0;
    }
    oldSector = sector;

I figure I will need this statement to work as it gets 3 directions before moving the scroll to correct the unintended mouse movements that may trigger a scroll? Added a println for debugging but not getting results.


    if (direction == oldDirection) {
      dirCount++;
      if (dirCount == 3) {
        Serial.println(direction);   ///MODIFIED BY ME 
        //Mouse.move(0,0,direction);
        dirCount = 0;
      }
    }
    else {
      dirCount = 0;
      oldDirection = direction;
    }
    startMillis = currentMillis;
    }
  }

I have a feeling its with oldDirection and direction not working as intended.

Hi PaulRB!

I figured it out! I added if (!direction == 0) to your logic so that it will not use 0 and only 1 and -1 to count prior to scroll command. Thank you!

 if (!direction == 0)
    if (direction == oldDirection){
      dirCount++;
      if (dirCount == 3) {
        Serial.println(dirCount);
        Mouse.move(0,0,direction);
        dirCount = 0;
      }
    } 
    else {
      dirCount = 0;
      oldDirection = direction;
      }
    startMillis = currentMillis;

Glad you got it working and well done finding my error (did not think about sending zero scroll direction).

Your code is very smooth compared to what I had running and got rid of some extraneous sends since using arrays. Good example of how to improve speed!

I had one last request and having issues understanding the logic.
Since I have four directions, NE, SE,SW, and NW (think of a compass.) I would like to add, N, S, E, W to see if I can scroll a bit faster with less directional change. So I modified the above.

        if (data.y < 0) {
     if (data.x < 0) {
        //Serial.println(" NE ");
        sector = 8;
      } else if (data.x > 0) {
        //Serial.println(" NW ");
        sector = 6;
      }
    } else if (data.y > 0) {
      if (data.x < 0) {
        //Serial.println(" SE ");
        sector = 2;
      } else if (data.x > 0) { 
        //Serial.println(" SW ");
        sector = 3;
      }

      if (data.x > 0 and data.y == 0) {   // EAST
        sector = 1;
      }
      else if (data.x < 0 and data.y == 0) { // WEST
        sector = 5;
      }
       else if (data.y > 0 and data.x == 0) {  // NORTH
        sector = 7;
      }
       else if (data.y < 0 and data.x == 0) {  // SOUTH
        sector = 3;
      }

I wasn't sure how to add it to your existing magic. Forgive me, still trying to understand it.

    if (sector == (oldSector + 1) or (sector == 1 and oldSector == 4)) {
     direction = 1;
     //Mouse.move(0,0,direction);
     //Serial.println(sector);
    }
    else if (sector == (oldSector - 1) or (sector == 4 and oldSector == 1)) {
      direction = -1;
      //Mouse.move(0,0,direction);
      //Serial.println(sector);
    }
    else {
      direction = 0;
      //Serial.println(direction);
    }
    oldSector = sector;

    if (!direction == 0)
    if (direction == oldDirection){
      dirCount++;
      if (dirCount == 3) {
        //Serial.println(dirCount);
        Serial.println(sector);
        Mouse.move(0,0,direction);
        dirCount = 0;
      }
    } 
    else {
      dirCount = 0;
      oldDirection = direction;
      }
    startMillis = currentMillis;
    //Serial.println("time...");
    }

Not magic, just simple code. You should not be having trouble understanding it.

If the sector number is going up, then the direction is positive. If the sector number is going down, direction is negative. The exception is when the sector number completes a 360 circle and goes from 1 to 4 (negative direction) or 4 to 1 (positive direction).

With your 8 sectors, completing the circle will result in sector going from 1 to 8 or 8 to 1.

Not sure this will work very well. Catching the y value when it is exactly zero (or x being exactly exactly zero) may easily be missed. That would result in the sector changing by 2 instead of 1 and direction getting set to 0.

Rather than compass directions, think of your sectors as ranges of angles:
0 to 45 degrees = 1
45 to 90 degrees = 2
90 to 135 degrees = 3
...
315 to 360 degrees = 8

You can determine which sector the x and y coordinates are in easily.

For example if x >= 0 and y >= 0 then the sector is either 1 or 2. But which is it? Well, at exactly 45 degrees, x == y. So if x >= y then the angle is 45 degrees or more, so the sector is 2. Otherwise (x < y), sector is 1.

Exactly the same trick will work in the other parts of the circle, but you may need to flip the sign of x or y.

As another example, if x < 0 and y >= 0 then sector is either 7 or 8. At exactly 315 degrees, (-x) == y. So if (-x) > y, sector must be 7.

Thank you for explaining and making it easier to understand.

My X is positive from left to right. My y is negative going from top to bottom (flipped.)

Also, I see you have x >= 0 and y >= 0 from sector 1 to 2

but x < 0 and y >= 0 from 7 to 8.

Can you please explain?

So I adjusted the code to your example and built sector 3 to 4 and 5 to 6 .

The resolution is much better now that its 8! If I were to use your concept that at the edge x == y could I compare to last sector and know its position? This way I will get a total of 16 sectors?


//SECTOR 1 or 2
if ( data.x > 0 and data.y >= 0)  ///@ 45 degrees x == y
  if ( data.x > data.y ) //// 45 degrees or more
  sector = 2;
  else if ( data.x < data.y )
  sector = 1;
  
    //SECTOR 3 or 4
if ( (data.x) > 0 and ( -data.y) >= 0)  ///@ 225 degrees x == y
  if  ( (data.x ) > ( -data.y ) ) //// 225 degrees or more
  sector = 3;
  else if ( (data.x) < (-data.y) )
  sector = 4;

  //SECTOR 5 or 6
if ( ( -data.x ) > 0 and (-data.y) >= 0)  ///@ 90 degrees x == y
  if ( ( -data.x ) > ( -data.y ) )//// 90 degrees or more
  sector = 6;
  else if ( ( -data.x )  < ( -data.y ) )
  sector = 5;
  
/////SECTOR 7 or 8
if ( data.x < 0 and data.y >= 0 ) ///SECTOR EITHER 7 or 8  @ 315 degrees -x == y
  if ( ( -data.x ) > data.y ) //// 315 degrees or more
  sector = 7;
  else if ( ( -data.x ) < data.y )
  sector = 8;

  ///////////////////////////////////TESTING END///////////////////////////

   ///THIS SECTION PREVENTS THE WRAPAROUND ISSUE
    if (sector == (oldSector + 1) or (sector == 1 and oldSector == 8)) {
     direction = 1;
    }
    else if (sector == (oldSector - 1) or (sector == 8 and oldSector == 1)) {
      direction = -1;
    }
    else 
      direction = 0;
    oldSector = sector;

I would not consider that to be flipped, that is what I was expecting. I guess you are thinking about LCD & OLED screens on Arduino or PC where the "origin" (x=0, y=0) is at top left corner and y increases down the screen?

Knowing that I expected y to be negative at the bottom, does it make sense now?

As I think I said before, I would not use that exact condition because it could easily be missed as x and y change. If you want 16 sectors, you need each sector to be 22.5 degrees wide. I think that could be done with test like 2.x < y or 2.y < x etc.

Sorry for late response. Had covid over the interval and finally feel I can look at a screen!

Yes you are correct in stating before that might not capture x==y, I figure I give it a shot but the more I think of it, the more it will be a futile exercise so going to take your word for it!

Im getting errors with 2.data.x or 2.(data.x) Is there a different way of writing such? Is the "." multiplication? I wasnt able to find it on the arduino forum docs. Some form of increment?

Sorry for confusing you. In algebra, "." means multiply. In C/C++ you would use "*".