Final Project: A Work Accompaniment

In summary, my final project was a series of failed experiments.

My original goal was to use the p5.speech library to create something for Zoom classes. The user would be alerted every few minutes if they ended up falling asleep on their pillow using a capacitative touch sensor. I also hoped to record the transcript of the meeting/class in session and play some sort of alarm if the user’s name was said, urging them to get up.

I ended up playing around with this idea, using the p5.serialcontrol program to communicate between p5.js and Arduino. While it is cool that this exists, it was a bit clunky in terms of workflow in terms of connecting ports, etc. Additionally, I wasn’t fully comfortable using the p5.speech library to record transcripts. Other than the technical problem of pipes breaking, I didn’t think it was okay to record the transcripts due to user privacy. Additionally, the speech recognition was a bit better than expected but probably still only a 65% recognition of the names of friends I tried, so I thought it would still make microaggressions in its failure to understand certain friends’ names, etc.

p5.serialcontrol gui app screenshot
Screenshot of p5.serialcontrol app

Thus, I needed to repivot my idea. I really wanted to keep the capacitative touch element of the project because this was something I had never been exposed to. I decided to maintain the original spirit of the project as well…something that could help me personally. I have lots of low energy periods due to insomnia and perpetual migraines, but I still have to do my best to complete my work. However, amidst the low energy, I need to make sure I don’t completely fall asleep. It’s not very predictable so setting an alarm ahead of time is not something I would know to do. Thus, I thought the capacitative touch sensor could be useful to see if I do end up falling asleep–having a sensor in my pillow could detect this and set into motion a way to wake me up.

From there I imagined having a combination of vibrations and sound to wake me up. I originally considered using an LED to show whether the sensor was working, but thought bright lights and migraines aren’t the greatest of combinations. In our class check-in, Aaron suggested using motors to create the vibrations.

sketch of program
Sketch of Program

I was also trying to brainstorm what the Processing program could look like.  Having structured work time and making sure I take breaks is important to make sure I don’t get too fatigued. Thus, I thought it could be interesting to make my own version of the Pomodoro method. Additionally, doing work tends to bring out a lot of negative thought patterns, so I wanted to integrate some positive energy into the program. I found this affirmation API that I could pull a random message from.

Creating the scrollbars was a bit manual, but I had help from the Scrollbar Processing documentation example. I modified it to make more simple and more to the aesthetic of the program and used a map function instead of calculating the ratio of the slider’s position to the slider’s width. I also added text to know what the current value of the slider is.

I designed a color palette using Coolors and tested it for accessibility for various visual abilities using their tool.

Creating the stopwatch display was a bit tricky because I wanted the new time to appear any time the scrollbar was moved. I ended up creating a helper function to calculate the new timer value. This would be used in the calculateTime function. Another issue I had was displaying the time in a way easy for the reader to understand. For instance, simply using the numerical value of minutes and seconds wouldn’t work for the 60 second mark (the user would expect to see 00 instead of 60) and for the second ticks less than 10 (the user would expect to see :01 instead of :1). I ended up just using an if statement to check for these edge cases, but I’m sure there is a smarter way to do this.

The final aspect of the front page of the program was the affirmations. Using the http.requests library was pretty straightforward, but the response was not in a form conducive for using JSON. I ended up just creating a substring of what was returned to include only the actual message part.

prototype of home screen of program with stopwatch, scrollbars, and affirmation at the bottom
I kind of don’t vibe with some of the affirmations in the API but here’s a home screen of the program

I also wanted to include another element of interaction with the Arduino. Thus, I thought I could create something for the break period of the program that involved using a hardware component. I considered doing a dance break–playing music with dancing LEDs, but had done something similar for one of the weekly projects and wanted to learn something new. I also thought it could be interesting to do a small mini game using the buttons on Arduino as controls, but I’m a bit tired of making games in all honesty. Thus, I decided to do something involving drawing. From the moment we starting working with potentiometers, they reminded me of the little knobs on those toys Etch-a-Sketch–I decided to build a mini Etch-a-sketch for the break part of the program!

prototype of drawing game
Screen to etch a sketch for break part of program

In terms of my hardware, I used the capacitative touch sensor. I was inspired by the Love You Pillow project on the Arduino Hub to use foil for the sensor. I had to experiment a bit with finding the right threshold, but overall, it worked pretty smoothly and consistently.  I used the motor shield to connect two motors to create the vibrations…the code and wiring was heavily remixed from the class we spent on motors. I also used a piezo buzzer and two potentiometers.

Component Setup: breadboards with foil in pillow as sensor
Overview of Component Setup
Closeup of Wiring
Closeup of Wiring

I also experimented a bit with the LED display–I was thinking of randomly sending messages like “Hydrate” and “Stretch,” but I ran out of pins to include it in the final setup, already using some of the analog pins as digital pins.

LCD Display setup
LCD Display Setup

Overall, I’m not very content with the project. I know I’m capable of better work, but for where I’m at in terms of energy, I think I did my best. There’s definitely a lot of room for improvement. It could be cool to include the LCD Display with user choice of messages on the Processing screen (hydrate, eye break, stretch, breathe deeply). It could be cool to have more options for what to do on the break. I realize I don’t really like drawing with the etch a sketch, so it could be more useful to explore other potential activities like making a mini piano or game, etc. I think the user interface of the etch a sketch could be made more interesting by having more user input in choosing a color etc.

sketch of potential choice of break
sketch of potential choice of break

Here’s a walkthrough of my project:

CODE:

Arduino:

#include <CapacitiveSensor.h>
#include <SparkFun_TB6612.h>
//#include <LiquidCrystal.h>      

#define AIN1 2
#define BIN1 7
#define AIN2 4
#define BIN2 8
#define PWMA 5
#define PWMB 6
#define STBY 9

 
//LiquidCrystal lcd(19, 18, 17, 16,11, 3);   
// these constants are used to allow you to make your motor configuration 
// line up with function names like forward.  Value can be 1 or -1
//from motor library example code
const int offsetA = 1;
const int offsetB = 1;
int duration = 2000;
Motor motor1 = Motor(AIN1, AIN2, PWMA, offsetA, STBY);
Motor motor2 = Motor(BIN1, BIN2, PWMB, offsetB, STBY);
//pressed boolean indicates whether the pillow sensor senses touch
int pressed = 0;
long timer;
//threshold for pillow sensor; if greater than threshold, there is touch
int threshold = 30;
//timeLength variable from first scrollbar in Processing program
int timeLength;

CapacitiveSensor   cs_4_2 = CapacitiveSensor(10,12);        //12 is sensor pin
//count indicates what level of intensity to be woken up
int count = 0;
int piezoPin = 3;

int pot1 = A4;
int pot2 = A5;

void setup()                    
{
   cs_4_2.set_CS_AutocaL_Millis(0xFFFFFFFF);     //from capacitive sensor library
   Serial.begin(9600);
   Serial.println(0,0);

//   lcd.begin(16, 2);                 //tell the lcd library that we are using a display that is 16 characters wide and 2 characters high
//  lcd.clear();                      //clear the display
}

void loop()  {
 while (Serial.available()) {
  //read from Processing
  timeLength = Serial.parseInt();
  //turns into minutes, change this value if you want the interval in seconds
  timeLength *= 60000;

   if (Serial.read() == '\n') {
    //sends potentiometer values to processing
  int val1 = analogRead(pot1);
  Serial.print(val1);
  Serial.print(",");
  int val2 = analogRead(pot2);
 
  Serial.println(val2);
   }  
  }

//   lcd.setCursor(0, 1);              
//  lcd.print(timeLength);
//  
    long start = millis();
    long cap =  cs_4_2.capacitiveSensor(30);
    //if the sensor value is greater than the threshold and has not yet been activated
    //starts sensor and level count
    if (cap > threshold && pressed != 1){
      pressed = 1;
      timer = millis();
      count = 0;

    } else if (cap > threshold && pressed == 1) {
      pressed = 1;
      //if sensor value is less than threshold, turn pressed off
    } else if (cap < threshold && pressed == 1) {
      pressed = 0;
    }
  if (pressed == 1) {
//   lcd.setCursor(0, 0);              
//  lcd.print("sensor on");
//  lcd.setCursor(0, 1);              
//  lcd.print(timeLength);

//after every X minutes
  if (millis() - timer >= timeLength) {
    timer = millis();
   level(count);
   count += 1;
  }
  }  
}
//function creates intensity of response to wake up depending on level
void level(int count) {
  switch(count){
  case 0:
    vibrate(0);
    break;
  case 1:
    vibrate(1);
    break;
  case 2:
    vibrate(2);
    break;
  case 3:
    vibrate(2);
    tone(piezoPin, 200, duration);
    break;
  default:
    vibrate(3);
    tone(piezoPin, 500, duration);
    break;
  }
}
//vibrate function to determine speed based on level
void vibrate(int level) {
  int sp = 0;
  if (level == 0) {
    sp = 75;
  } else if (level == 1) {
    sp = 120;
  } else if (level == 2) {
    sp = 180;
  } else {
  
    sp = 255;
  }
  //moves the motors
  motor1.drive(sp,duration);
  motor2.drive(sp,duration);
  //stops motors
  motor1.brake();
  motor2.brake();
}

Processing:

import http.requests.*;
import processing.serial.*;

Serial myPort;

Scrollbar bar1;
Scrollbar bar2;

int first = 0;
int second = 0;
float lastX = 0;
float lastY = 0;
//Button play;
//Button dance;
//Button draw;

int seconds;
int minutes;

int timer;
int startTime;

PFont lucida;
PFont avenir;
int state = 0;
String response;
void setup() {
    size(1200, 800);
    lucida = createFont("Lucida Bright Italic", 36);
    avenir = createFont("Avenir", 12);
    textAlign(CENTER);
    background(#D08C60);
    //requests affirmation from api
    GetRequest affirm = new GetRequest("https://dulce-affirmations-api.herokuapp.com/affirmation");
    affirm.send();
    response = affirm.getContent();
    //finds string of affirmation
    response = response.substring(12, response.length() - 3);
    
    //Scrollbar(x, y, width, height, min val, max val)
    bar1 = new Scrollbar(20, height/10, 300, 16, 1, 60);
    bar2 = new Scrollbar(20, height/10 + 40, 300, 16, 1, 60);
    //finds initial time of scrollbars
    adjustTime();
    
     //play = new Button("PLAY", width/2 - 150, height/3, 300, 80, 2);
     //dance = new Button("DANCE", width/2 - 150, height/3 + 120, 300, 80, 3);
     //draw = new Button("DRAW", width/2 - 150, height/3 + 240, 300, 80, 4);
     
     //sets up serial communication
     String portname=Serial.list()[2];
      myPort = new Serial(this,portname,9600);
      myPort.clear();
      myPort.bufferUntil('\n');
      delay(1000);

}

void draw(){
//if home screen
  if (state == 0) {
    background(#D08C60);
    //scrollbars - draw and update if user input
    bar1.update();
    bar2.update();
    bar1.display();
    bar2.display();
    fill(#F9F1DC);
    //displays affirmation
    textFont(lucida);
      text("A Reminder...", width/2, height - 140);
      text(response, width/2, height - 100);
    textAlign(LEFT);  
    textFont(avenir);
    text("intervals to be woken up in", 340, height/10 + 5);
    text("chunk of time to work for", 340, height/10 + 45);
    //calculates and displays time
    calculateTime();
  //} else if (state == 1) {
  //    background(#F1DCA7);
  //    textFont(avenir);
  //    textSize(72);
  //    fill(#997B66);
  //    text("Time for a break!", width/2, height/8);
  //    play.display();
  //    dance.display();
  //    draw.display();
  //    play.update();
  //    dance.update();
  //    draw.update();
  } else if (state == 2) {
    //drawing screen game
      textFont(avenir);
      textSize(42);
      fill(#997B66);
      text("Time for a break. Use the potentiometers to etch a sketch", width/2, height/8);
      textSize(36);
      text("Click ENTER to clear and SPACE to get back to work.", width/2, height/8 + 65);
      //maps potentiometer value to some coordinate on the screen
      float x = map(first, 0, 1023, 0, width);
      float y = map(second, 0, 1023, height/4, height);
      //finds random color for the line
      stroke(random(0,255), random(0,255), random(0,255));
      //idea for saving last coordinate from this project: http://graysonearle.com/edu/physcom/etch-a-sketch-arduino-processing/
      line(x, y, lastX, lastY);
      lastX = x;
      lastY = y;
  } 
}
//scrollbar class for user input on first screen
class Scrollbar {
  //slider width and height
  int sw;
  int sh;
  float xpos;
  float ypos;
  //the slider position (ellipse)
  float slider_pos;
  float newpos;
  float sliderMin;
  float sliderMax;
  boolean mouseOnTop;
  boolean moved;
  boolean posChanged;
  //min and max values of slider
  int sValMin;
  int sValMax;
  
  Scrollbar(float x, float y, int w, int h, int minVal, int maxVal) {
    sw = w;
    sh = h;
    xpos = x;
    ypos = y - sh/2;
    slider_pos = xpos + sw/2 + sh/2;
    newpos = slider_pos;
    sliderMin = xpos;
    sliderMax = xpos + sw;
    sValMin = minVal;
    sValMax = maxVal;
    posChanged = false;
  }
  
  void update() {
    //if user mouseX, mouseY is on top of the scrollbar
    if (overEvent()) {
      mouseOnTop = true;
    } else {
      mouseOnTop = false;
    }
    //if mouse held and on top, then the scrollbar is being moved
    if (mousePressed && mouseOnTop) {
      moved = true;
    }
    
    if (!mousePressed) {
      moved = false;
    }
    //keep within the range of the slider
    //code remixed from https://processing.org/examples/scrollbar.html
    if (moved) {
      newpos = constrain(mouseX - sh/2, sliderMin, sliderMax);
    }
    //if slider value has changed
    if (abs(newpos - slider_pos) > 1) {
      slider_pos += (newpos -slider_pos);  
      adjustTime();
    }
  }
  
  float constrain(float val, float smin, float smax) {
    return min(max(val, smin), smax);
  }
  boolean overEvent() {
    if (mouseX > xpos && mouseX < xpos + sw && mouseY > ypos && mouseY < ypos + sh) {
      return true;
    } else {
      return false;
    }
  }
  //creates the scrollbar
  void display() {
    noStroke();
    fill(200);
    rect(xpos, ypos, sw,sh);
    fill(#9B9B7A);
    //ellipse is the slider part of the bar
    ellipse(slider_pos, ypos + sh/2, 1.5 *sh, 1.5*sh);
    fill(255);
   textAlign(CENTER);
    textFont(avenir);
    //label of the actual value
    text(getVal(), slider_pos, ypos + sh/1.5);
  }
  
  int getVal() {
    float mapVal = map(slider_pos, xpos, xpos + sw, sValMin, sValMax);
    return int(mapVal);
  }
 
}
//get value from scrollbar and create a new start time
void adjustTime() {
  timer = bar2.getVal();
  startTime = millis();
}

void calculateTime() {
  seconds = 60 - ((millis() - startTime)/1000) % 60;
  minutes = timer - 1 - ((millis() - startTime) / 60000);
  //displays time in middle of screen
  textFont(avenir);
  textSize(240);
  textAlign(CENTER, BOTTOM);
  if (seconds == 60) {
    text(minutes + 1 + ":00", width/2, 2*height /3);
  } else if (seconds < 10) {
  text(minutes + ":0" + seconds, width/2, 2*height /3);
  } else {
    text(minutes + ":" + seconds, width/2, 2*height /3);
  }
  //when timer runs out, move to next screen
  if (seconds == 60 && minutes <= 0) {
    state = 2;
    background(#F1DCA7);
  }
}
//class Button {
//  String msg;
//  int xpos;
//  int ypos;
//  int w;
//  int h;
//  int cntl;
  
//  Button(String words, int x, int y, int w_, int h_, int st) {
//    msg = words;
//    xpos = x;
//    ypos = y;
//    w = w_;
//    h = h_;
//    cntl = st;
//  }
  
//  void display() {
//    noStroke();
//    if (mouseX > xpos && mouseX < xpos + w && mouseY > ypos && mouseY < ypos + h) {
//      fill(#7A6352);
//    } else {
//      fill(#997B66);
//    }
//    rect(xpos, ypos, w, h);
//    textAlign(CENTER, CENTER);
//    fill(#F1DCA7);
//    text(msg, xpos + w/2, ypos + h/3);
//  }
  
//  void update() {
//    //if button is clicked
//    if (mousePressed && mouseX > xpos && mouseX < xpos + w && mouseY > ypos && mouseY < ypos + h) {
//      state = cntl;
//      background(#F1DCA7);
//    }
//  }
//}

void keyPressed() {
  if (state == 2) {
    //returns to home screen if SPACE is clicked
    if (key == ' ') {
      state = 0;
    }
    //refreshes background if ENTER clicked to allow for new drawing
    else if (key == ENTER) {
    background(#F1DCA7); 
    }
  }
}
void serialEvent(Serial myPort){
 String s=myPort.readStringUntil('\n');
 println(s);
  s=trim(s);
  if (s!=null){ 
    //gets value of potentiometer
    int values[]=int(split(s,','));
    first = values[0];
    second = values[1];
  }
  //writes to arduino the scrollbar value
  myPort.write(bar1.getVal() + "\n");
}

 

Group Project – Music Box

Our task this week was to work with one other person from class and make a musical instrument. Sara and I decided to make a music box.

The materials we used on our Arduino are as follows:

  • two servo motors (one for the rotating figurine, one for the lid opener)
  • one buzzer (for tone)
  • a potentiometer (to switch the music box on)
  • a wooden box
  • a small figurine

First, we stuck the servo boxes using velcro into the box, and then we added the buzzer. One servo was to just open the box and the other one was to turn the figurine around.

The building process: 

Our Code:

#include <Servo.h>
 
Servo servo;
Servo servo2;

//PINS
int speakerPin = 8;
int servoPin = 10;
int servo2Pin = 9;
int potentioPin = A2;
int length = 15; // the number of notes
char notes[] = "ccggaagffeeddc "; // a space represents a rest
int beats[] = { 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 4 };
int tempo = 300;



//FOR SERVO
const int MAX_DISTANCE = 160;
//const int MED_DISTANCE = 90;
const int MIN_DISTANCE = 20;
int degree = 10;
int pos = 50;
int pos2;

//FOR POTENTIOMETER
int potentioValue = 0;

int i = 0;

void setup() {
  Serial.begin(9600);
  servo.attach(servoPin);
  servo2.attach(servo2Pin);
  pinMode(speakerPin, OUTPUT);
  servo2.write(20);
  servo.write(20);
}

void playTone(int tone, int duration) {
  for (long i = 0; i < duration * 1000L; i += tone * 2) {
    digitalWrite(speakerPin, HIGH);
    delayMicroseconds(tone);
    digitalWrite(speakerPin, LOW);
    delayMicroseconds(tone);
  }
}

void playNote(char note, int duration) {
  char names[] = { 'c', 'd', 'e', 'f', 'g', 'a', 'b', 'C' };
  int tones[] = { 1915, 1700, 1519, 1432, 1275, 1136, 1014, 956 };

  // play the tone corresponding to the note name
  for (int i = 0; i < 8; i++) {
    if (names[i] == note) {
      playTone(tones[i], duration);
    }
  } 

   pinMode(speakerPin, OUTPUT);
}
 
void loop() {

  potentioValue = analogRead(potentioPin);
  Serial.println(potentioValue);
  delay(1000);

  pos2 = map(potentioValue, 0, 1023, 50, 180);
  servo.write(pos2);
  //delay(1000);

  if (pos2 < 90)
  {
 
    pos = pos + degree;
  
    if (pos < MIN_DISTANCE || pos > MAX_DISTANCE)
    {
      degree = -degree;
    }
  
    servo2.write(pos);

    if (notes[i] == ' ') {
      delay(beats[i] * tempo); // rest
    } else {
      playNote(notes[i], beats[i] * tempo);
    }

    i++;555;

    if (i > length)
    {
      i = 0;
    }
    // pause between notes
    delay(tempo / 2); 
  }
  else
  {
    pos = 50;
    servo2.write(pos);
    //no tone
  }
}