Wikipedia UI

Spring 2019

The questions below are due on Sunday March 10, 2019; 11:59:00 PM.
 
You are not logged in.

If you are a current student, please Log In for full access to the web site.
Note that this link will take you to an external site (https://oidc.mit.edu) to authenticate, and then you will be redirected back to this page.

Back to Exercise 05

Music for this Page



The starter code for this exercise IS FOUND HERE

We're going to finish up that Wikipedia Project from last week by creating the user-interface on our device. When we're done with this, we can put everything together and have all of human knowledge in our hands...so no big deal.

Our Overall System and the particular piece we'll be working on today.

In the last exercise you got the Wikipedia system's back-end working. Using a very simple query string in the format of query=pigeon, we can look up what the word "pigeon" means through the web, for example. If we were only based in a computer-centric web-browser, this would seem like a silly functionality to have developed, but this will be very convenient if we wanted to figure out what "pigeon" meant from our mobile labkit since we now have an online resource we can go to that will give us text from Wikipedia in small snippets free of html formatting...in other words, perfect for display on our OLED.

1) Wikipedia UI

Your goal for this last major exercise (of this mini-project) is to integrate:

  • The Button input (complete with the short and long-press capabilities from last week EX04)
  • The IMU accelerometer
  • The WiFi library

The goal is to create a state machine that will enable a user to generate a query-string by tilting your device sideways and using short button presses to select letters. Once your message is built, a long-button press will send it to the url of your functioning Python script from this week's Wikipedia Getter to look up the meaning of the message. The returned response should be displayed on your LCD until the user applies another long-press, at which point the system will start building query-strings again.

A video of the operation is shown below. Look at me looking up words. So cool. Also note I'm on battery power, which makes this even more cool.

The starter code for this week IS FOUND HERE. It is has gaps for the Button class that you previously wrote. Make sure you post in your working versions for these code chunks, and compile just to make sure you're starting with stuff that is working!

There is also an incomplete class definition for WikipediaGetter which you will finish in this exercise. You can also see how we'll integrate it in with everything else in the setup and loop of the code.

2) WikipediaGetter

The state machine will be based in a class called WikipediaGetter. The skeleton of it is shown below (duplicated from starter file):

class WikipediaGetter {
    char alphabet[50] = " ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    char message[400] = {0}; //contains previous query response
    char query_string[50] = {0};
    int char_index;
    int state;
    unsigned long scrolling_timer;
    const int scrolling_threshold = 150;
    const float angle_threshold = 0.3;
  public:

    WikipediaGetter() {
      state = 0;
      memset(message, 0, sizeof(message));
      strcat(message, "Long Press to Start!");
      char_index = 0;
      scrolling_timer = millis();
    }
    void update(float angle, int button, char* output) {
      //you'll be coding this today...and tomorrow!!
      }
    }
};

2.1) The update Member Function

The state-machine logic should be implemented in the update member function. This function takes in three arguments:

  • float angle: (INPUT) float value of our device's "X" orientation angle (note this may be different than the "Y" orientation as marked on the IMU)
  • int button: (INPUT) The output from Button object (see EX04 for Button class details), also see the loop body of code for how it is interfaced with the WikipediaGetter class.
  • char* output: (OUTPUT) A char array used to accept text outputs to display on the screen!

Every time update is called, it must update its state, and it must also update the value assigned to char* output which is used for displaying on that particular iteration through the loop. Investigating the loop function will give more clues to how it should operate:

void loop() {
  float x, y;
  get_angle(&x, &y); //get angle values
  int bv = button.update(); //get button value
  wg.update(-y, bv, response); //input: angle and button, output String to display on this timestep
  if (strcmp(response, old_response) != 0) {//only draw if changed!
    tft.fillScreen(TFT_BLACK);
    tft.setCursor(0, 0, 1);
    tft.println(response);
  }
  memset(old_response, 0, sizeof(old_response));
  strcat(old_response, response);
  while (millis() - primary_timer < LOOP_PERIOD); //wait for primary timer to increment
  primary_timer = millis();
}

2.2) System Operation

Watch the video for the device above to get an idea of operation. You should implement this behavior with a state machine that has four states

2.2.1) State 0 - Message Display

In this state, simply return the most recent query result, which you want to store in the message variable. Initially, before any requests have occurred, this state should return the String "Long Press to Start!" (conveniently, message is initialized to that String in the constructor). In steady-state, though, this will usually contain the result of the previous Wikipedia lookup/response. When a long button-press occurs, transition to the "Text Entry" state. On the outgoing state transition, you should still return the most recent query result.

2.2.2) State 1 - Text Entry

In this state, the user builds up a query_string by tilting left or right to cycle through the characters in alphabet. It is helpful to use char_index to keep track of the currently selected character (i.e. it's location of the character in alphabet). In our lingo, "currently selected character" means the character that we're currently choosing, which has not yet been "locked into" the query string.

  • When this state is entered, the query_string should be empty. char_index should be 0 (i.e. the initial selection for the first character is " ")
  • When the board is tilted to the right, increment the currently selected chacracter by one (' ' -> 'A', 'A' -> 'B', ..., '9' -> ' '). When the board is tiled to the left, decrement the currently selected character by one (' ' -> '9', ..., 'B' -> 'A', 'A' -> ' '). 1
    • The input angle is the current angle of the board (in the relevant axis) in radians. From Exercise 03, you know which direction is positive and which is negative, and the simulation/testing below will make use of that specific orientation, so be aware in case you mounted your IMU differently..
    • You have access to (and should make use of) a global unsigned long variable called scrolling_timer.
    • Only scroll if the duration since the last scroll is at least scrolling_threshold. This is to control the rate at which you scroll. Additionally, the system needs to be present in state 1 for at least scrolling_threshold before scrolling (so when you transition from state 0 to state 1, the system cannot scroll immediately even if the input is a tilt.)
    • The board is considered "tilted" if the magnitude of angle is at least angle_threshold.
  • A short button-press should add the currently selected character to the query string, and should set char_index=0 (so the initial selection for the next character is " ").
  • A long button-press triggers a transition to the "Send Query" state. Do not add the currently selected character to the query.

Edge case: If there is both a) a short button-press and b) the necessary conditions to scroll, then the button press should take precedence.

In this state, you should load query_string into output concatenated with the currently selected character. On the outgoing state transition, however, you should place the empty string ("") into output.

2.2.3) State 2 - Send Query

This is a very short state, what we'd call a "pass-through." The system should only be in it for one call to update before moving automatically to state 3, and should return the String "Sending Query". It exists as a way to notify the user prior to the potential lag that comes about with the upcoming HTTP request.

2.2.4) State 3 - Send/Receive Result

In this state, the system will build the correct HTTP argument using the query_string char array and hand that as well as a reference to the message array (as an output) to the function lookup function which will perform the appropriate GET request (this is taken care of for you). The format of the HTTP argument string should be "query=VALUE&len=200", that is two HTTP query arguments where query is the word your system just built up (query_string), and a hardcoded request for a len of 200. **In the full test-code you download make sure lookup points to your functioning Wikipedia server-side script earlier in this week's exercises). After lookup is finished, reset the query_string variable back to "". The system should then transition to the "Message Display" state (state 0) and update message with the response.

2.2.5) Overview

So again, just to review. There are four states in this. You need to make sure to do all the required actions as specified above in each state in response to appropriate inputs:

  • State 0 (Message Display): Return whatever the current message is. This will start with a prompt message "Long Press to Start!"; to help the user realize they need to do do a long-button press to get into the query-building state.
  • State 1 (Text Entry): Use the IMU and Button object signals to build up a query string.
  • State 2 (Announce Send): A quick state that is used to announce "Sending Query"
  • State 3 (Send/Receive Results): Send the appropriate request using the lookup function. Process the return, store the response in the message variable, and return message

The checker below will test your update function in the context of a WikipediaGetter. Use the test cases responses as feedback. They have meaning.

Design your code to be non-blocking (no while loops waiting on timed events)! This is to ensure it works well on the actual device (blocking code will make listening to button presses become unreliable). Also Make sure your function updates char * output appropriately in all cases! Failure here could potentially result in crashes and other sad things.

Seriously consider developing this on your ESP32! It will make the debugging more intuitive. The test cases are mostly annotated clearly, but can be tricky otherwise!

Good luck! We believe in you! :)

void update(float angle, int button, char* output) { //your code here }

When it is all done you should be able to get some sweet Wikipedia results!

Back to Exercise 05


 
Footnotes

1If you want to use the modulo operator ("%"), be careful about negative numbers. Google around to learn more, it's a bit counterintuitive in C++. (click to return to text)



This page was last updated on Sunday March 10, 2019 at 06:37:58 PM (revision 42054d3).
 
Course Site powered by CAT-SOOP 14.0.4.dev5.
CAT-SOOP is free/libre software, available under the terms
of the GNU Affero General Public License, version 3.
(Download Source Code)
CSS/stryling from the Outboxcraft library Beauter, licensed under MIT
Copyright 2017