Description
A box that acts as a to-do list, with a user interface comprised of buttons and an LCD screen.

Crave the satisfaction of hitting a button when you finish your homework? Do you find written planners too boring? This is the device for you!

Demo Video

Project Photos

Overall photo for proportion/scale

Welcome screen, prompting user to press taskbutton. All the changes in the system occur onscreen.

Input task function of box, controlled by bottom right button (rotary encoder). (Not sure why text is fuzzy in this post; looks fine on my computer)

Process Images and Review

My first decision point occurred when I was deciding on what the main goal of this device would be. Initially, I was considering using the device to schedule the tasks that I input, but I soon realized that using the input to schedule work time around my calendar was a deeper software task that I would have spent an inordinate amount of time on compared to the hardware portion of the project. So the device became a simpler to-do list, allowing me to focus more on things like task input.

A second decision point was made when deciding on the appearance of the device, after I had most of the electronics working. I had lasercut a clock for another class, and was considering modifying this to give the device a nicer appearance and use the hands of the clock to set a timer on each task. However, it proved tricky and time-consuming to configure a servo to receive input by manually repositioning the clock hand, so I decided to lasercut an acrylic box instead. Perhaps with more time the clock idea could have worked out, but I thought focusing on the design enough to do it justice would have detracted from the function and purpose of the device.

Idea of lasercut clock with associated parts

Process Images

Taking measurements, deciding how to design the top of the box as simply as possible

After laser cutting, realizing that some holes were not the right size and drilling them to the correct diameter

Creating an electronic prototype, working on making user input through the rotary encoder possible

Got stuck on converting the characters for task input to an array that I could index through; kept getting this strange error and couldn’t figure out what I was doing wrong. I solved this by realizing I could just cast the string to a char, so I stored the string in a char type variable. I spent way too long trying to fix this before realizing the much simpler solution.

I also spent an inordinate amount of time making sure the tasks, if they were too long for the screen, would scroll across the display. As a result, I wrote a lot of redundant code so that this would work in all situations. I eventually solved this by restructuring my code so that there were two states: one when there was a new task to be input, and the other when there was not. This way less code had to be repeated.

Discussion

On the whole, I’m satisfied with how the text and interface with the LCD screen and buttons worked out. The device met the goal of having a functioning task entry and display system. Although the device did reach its “minimum viable product” stage, I’m a little disappointed with how it came out in terms of capability and performance (it got somewhat glitchy from wires disconnecting from the Arduino) and wish I spent more time on making it robust.

At the same time, though, there would always be more I could do to make the device more useful or look nicer. I would have liked if I made the user be able to change factors such as the number of tasks (essentially the length of the task list) through the interface, or implement sound effects so that finishing a task is more rewarding. As some of my peers said, “having some sort of feedback when you complete a task would be nice”; “It would be nice to have a reward at the end!” I do agree, as this would make the device more enticing (and fun) to use. I would have also preferred the original clock idea to make it more aesthetically pleasing and implement task scheduling. If I keep working on this, I will likely implement all these smaller ideas and return to the clock design.

Through working on this project, I found that I spent a lot of time focusing on minor details and features in code, like making the tasks scroll consistently. However, I spent extra time doing this instead of considering the aesthetic aspect of the device, and the aesthetics of this project are much more lacking. I realized that I take more time to work out the kinks in my code than I need to, and should instead allocate more time to the construction of my project, as that took more time and was more troublesome than I expected. The scale was off on the laser cutter application and I printed out the box significantly larger than expected, and didn’t have enough material to retry, so I was less than satisfied with how the appearance of the box turned out. My peers also suggested that it be smaller: “It would be pretty useful as a compact handheld tool”;  “The smaller display would help keep things together.” I agree with these statements, as a task device such as this would be most useful when more desk-friendly and usable, or to carry around in person. One person said “I wish the words were lasercutted on the box so that there’s an indication of what the buttons do”; this is a solution I never thought about to make the buttons more intuitive, so the user wouldn’t need to simply figure it out.

Schematic

Code 

/*
 * Whack-a-Task: Project 2
 * 
 * Tanvi Bhargava
 * 
 * This code allows the user to press a button to display a new task 
 * on the screen, and the task scrolls across the screen if it is too 
 * long for the LCD display. On pressing another button, that which is
 * attached to the rotary encoder, it switches to "New Task" mode and 
 * allows the user to input a task by rotating the rotary encoder and
 * pressing the button on desired letters (displayed on the LCD). Then
 * a third button can be pressed to enter the task into the device,
 * and the user can select another task to be displayed on the screen.
 * 
 * For use, one must download the libraries included below.
 */


#include <Entropy.h>  // used external random library, gives different numbers even upon reset
#include <Wire.h>
#include <LiquidCrystal_I2C.h> // Library for LCD screen use
#include <RotaryEncoder.h> // Library for rotary encoder position

// Declare pins
const int BUT_PIN = 2; // pin of display random task button
const int encoderA = 6; // CLK pin of Rotary Enocoder
const int encoderB = 5; // DT pin of Rotary Enocoder
const int BUT_ENTER = 7; // button pin of Rotary Enocoder
const int BUT_EXIT = 4; // third button - exit new task/mark as complete

// Declare variables used to store data
int buttonNew = 0;
bool longVal = false;
int taskIndex = 0;
int taskNumber = 7;

// variables for rotary encoder
int aState = 0;
int aLastState = 0;
int task_prev = 0;
int task_cur = 0;
bool newTask = false;
int t_prev = 0;
int t_cur = 0;
int encoder_pos = 0;
int state = 0; // when 1, is in encoder/task input state

// letters, numbers, space, characters
const int str_len = 44;
char alphabet[str_len] = "abcdefghijklmnopqrstuvwxyz0123456789 !?,.:-"; 
String message = "";
int nTasks = 10;

// create LCD display object
LiquidCrystal_I2C lcd(0x27, 16, 2);

// Declare encoder (from library)
RotaryEncoder encoder(encoderA, encoderB);

// TASKS SETUP
// use a data structure to let each task have properties
struct task {
  String task_name;
  String due_date;
};

// Initialize tasks list - couldn't figure out how to make a dynamic array,
// not sure if that's possible, so starts off with these pre-set tasks
// and pre-set length of 10 tasks.
task my_tasks[] = {
  {"IDeATe Project", "YES"},
  {"Finish 122 programming", "Feb 28"},
  {"Eat bagels", "today"},
  {"Make buggies", "forever and ever and ever"},
  {"Prepare for interview", "March 1"},
  {"Study for midterm", "Feb 27"},
  {"SLEEP MORE", "always"},
  {"nothing", "DONE"},
  {"nothing2", "DONE"},
  {"nothing3", "DONE"}
};



void setup() {
  // pin modes
  pinMode(BUT_PIN, INPUT);
  pinMode(BUT_ENTER, INPUT);
  pinMode(BUT_EXIT, INPUT);
  
  // initialize serial, was used when debugging
  Serial.begin(9600);
  
  // initialize screen, turn on backlight, set cursor position
  lcd.init();
  lcd.backlight();
  lcd.home();
  
  // print initial messages to LCD screen
  lcd.print("Hello, Tanvi!");
  delay(1000);
  lcd.clear();

  lcd.print("Press the button");
  lcd.setCursor(0, 1);
  lcd.print("for tasks to do.");

  Entropy.initialize(); // initialize Random library

  aLastState = digitalRead(encoderA); //initialize variable for rotary encoder
}



void encoder_input() // helper function for enter task state
{
  int exit_task = digitalRead(BUT_EXIT); // read exit button's state
  int lim = 0;
  
  encoder.tick(); // line used from RotaryEncoder example
  int newPos = encoder.getPosition();
  if (encoder_pos != newPos) { // if encoder is being rotated
    lim = newPos % str_len;
    // lim is the index of the characters in the variable "alphabet"
    if (lim < 0) {
      lim += str_len; // loop around to the first index of the characters, 
      // so you don't have to move all the way back from z to a
    }
    lcd.setCursor(0, 1);
    lcd.print(message);
    lcd.print(alphabet[lim]);
    // print message and letter you're scrolling through but haven't selected
    encoder_pos = newPos;
  }

  if (digitalRead(BUT_ENTER) == 0) { // if encoder button is pressed, i.e. to select a char
    delay(500); // so you don't accidentally select the letter many times
    int lim2 = newPos % str_len;
    if (lim2 < 0) {
      lim2 += str_len;
    } // get the index of the letter being selected
    message += alphabet[lim2]; // add the selected letter to the message
    lcd.print(message);
  }
  
  if (exit_task == 1) {
    // replace the first task with a due date of "DONE" with the task just created
    // if no tasks are done, replace last task in array
    taskIndex = 0;
    while (my_tasks[taskIndex].due_date != "DONE" || taskIndex < nTasks - 1) {
      taskIndex++;
    }
    my_tasks[taskIndex] = {message, "soon"}; // didn't get around to allowing input of 
    // due date, but the process would be much the same as for a new task.
    message = "";
    newTask = !newTask;
    state = 0;
    lcd.clear(); // reset all variables used for storing message, exit enter task mode
    delay(500);
    lcd.print("Push Task Button"); // the big blue button
  }
}



void loop() {
  if (state == 0) {
    delay(200);  // need delay so doesn't change too quickly
  
    task_cur = digitalRead(BUT_ENTER);
    if (task_prev == 1 && task_cur == 0) { // when rotary encoder button is clicked
      newTask = !newTask; // change newTask state
      lcd.clear();
    }
    
    if (newTask) {
      lcd.clear();
      lcd.print("Enter a new task!");
      lcd.setCursor(0, 1);
      state = 1;
      // switch to rotary encoder mode, get input from user
    }
    task_prev = task_cur; // update button state
    
    buttonNew = digitalRead(BUT_PIN); // display different task button 
  
    if (longVal) { // if message is too long for screen
      for (int i = 0; i < 40; i++) {
        buttonNew = digitalRead(BUT_PIN); // still read button values so can exit scrolling
        if (buttonNew == 1) {
          longVal = false;
          break;
        }
        
        t_cur = digitalRead(BUT_ENTER); // scrolls until a button is hit
        if (t_prev == 1 && t_cur == 0) { // same functionality as outside the longVal loop
          // included here so you don't have to wait until it's done scrolling
          newTask = !newTask;
          longVal = false;
          lcd.clear();
          break;
        }
        t_prev = t_cur;
        
        lcd.scrollDisplayLeft();
        delay(750); // speed of scrolling
      }
    }
    
    if (buttonNew == 1) {
      taskIndex = Entropy.random(0,taskNumber); // get random task from list
      lcd.clear();
      
      lcd.print(my_tasks[taskIndex].task_name); // display the new task
      lcd.setCursor(0, 1);
      lcd.print("Due: ");
      lcd.print(my_tasks[taskIndex].due_date); // display due date
    }
    
    if (!newTask && ((my_tasks[taskIndex].task_name).length() > 16 || 
      (my_tasks[taskIndex].due_date).length() > 16)) longVal = true; 
    // if either task or due date is longer than screen of length 16, it's too long.
    else longVal = false;
  
    if (digitalRead(BUT_EXIT) == 1) { 
      // in this mode, exit button is used as "mark as complete" button
      my_tasks[taskIndex].due_date = "DONE"; // mark as done; deleting elements is too memory intensive
      lcd.clear();
      lcd.print("Good Job!");
      lcd.setCursor(0,1);
      lcd.print("Task Completed");
      delay(1000);
      // print next task on the list
      lcd.clear();
      taskIndex += 1;
      lcd.print(my_tasks[taskIndex].task_name); // display the task
      lcd.setCursor(0, 1);
      lcd.print("Due: ");
      lcd.print(my_tasks[taskIndex].due_date);
    }
  }
  else if (state == 1) {
    encoder_input(); // get user input in the "insert new task" state
  }
}