Week 14 – FINAL PROJECT

A Walk Through Time: Final Project Documentation

Concept

The idea behind A Walk Through Time is to let the viewer control the flow of time with simple hand gestures. When the viewer waves their hand on one side, time moves forward. When they wave on the other side, time reverses. When no one interacts, time pauses. The system changes both the physical world and the digital world at the same time.

The physical clock hand moves using a stepper motor. A growing plant moves up and down using a DC motor and a telescoping cylinder system. On the screen, a surreal p5.js world shows time moving with colors, waves, particles, and a glowing abstract clock. Everything stays in sync and reacts at the same moment. The goal was to create one experience where movement, gesture, and time feel connected.

Project Interaction 

Interaction description:

  • The viewer stands in front of the clock and plant
  • Two ultrasonic sensors wait for hand gestures
  • Waving on the right makes the clock tick forward and the plant rise
  • Waving on the left makes the clock tick backward and the plant collapse
  • When the viewer steps away, both the clock and plant pause
  • The p5.js visuals shift to match the state: forward, backward, or paused

How the Implementation Works

The system uses two Arduinos, two motors, two sensors, and a p5.js sketch.

Main Arduino

  • Reads the left and right ultrasonic sensors
  • Decides the time state: FORWARD, BACKWARD, or PAUSED
  • Moves the stepper motor to tick the physical clock
  • Sends the state through serial as a single character (F, B, or P)
  • Sends the same data to the second Arduino

Second Arduino

  • Receives F, B, P
  • Moves the DC motor to pull or release fishing wire
  • This grows or collapses a three-layer telescoping plant

p5.js

  • Reads the same serial data from the main Arduino
  • Updates the surreal background
  • Moves particles, waves, arrows, and an abstract glowing clock
  • Lets the viewer see time flowing

Interaction Design

The interaction is very simple. The viewer uses hand gestures to control time.

Right sensor → Time Forward
Left sensor → Time Backward
Both or none → Pause

All outputs reinforce this state:

    • The physical clock hand moves
    • The plant grows or collapses
    • The digital world changes color and motion

Arduino Code

Below are the Arduino codes:

#include <Stepper.h>

const int stepsPerRevolution = 2048;
const int ticksPerRevolution = 12;
const int stepsPerTick = stepsPerRevolution / ticksPerRevolution;

Stepper clockStepper(stepsPerRevolution, 8, 10, 9, 11);

enum TimeState { PAUSED, FORWARD, BACKWARD };
TimeState timeState = PAUSED;
TimeState lastSentState = PAUSED;

const int TRIG_RIGHT = 4;
const int ECHO_RIGHT = 5;
const int TRIG_LEFT  = 6;
const int ECHO_LEFT  = 7;

const int DETECT_THRESHOLD_CM = 40;

unsigned long lastTickTime = 0;
const unsigned long tickInterval = 1000;

long readDistanceCM(int trigPin, int echoPin) {
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  long duration = pulseIn(echoPin, HIGH, 20000);
  if (duration == 0) return -1;
  return duration / 29 / 2;
}

void sendStateIfChanged() {
  if (timeState == lastSentState) return;
  lastSentState = timeState;
  char c = 'P';
  if (timeState == FORWARD) c = 'F';
  else if (timeState == BACKWARD) c = 'B';
  Serial.write(c);
}

void setup() {
  clockStepper.setSpeed(10);
  pinMode(TRIG_LEFT, OUTPUT);
  pinMode(ECHO_LEFT, INPUT);
  pinMode(TRIG_RIGHT, OUTPUT);
  pinMode(ECHO_RIGHT, INPUT);
  Serial.begin(9600);
}

void loop() {
  unsigned long now = millis();

  long distLeft  = readDistanceCM(TRIG_LEFT,  ECHO_LEFT);
  long distRight = readDistanceCM(TRIG_RIGHT, ECHO_RIGHT);

  bool leftDetected  = (distLeft  > 0 && distLeft  < DETECT_THRESHOLD_CM);
  bool rightDetected = (distRight > 0 && distRight < DETECT_THRESHOLD_CM);

  if (leftDetected && !rightDetected) timeState = BACKWARD;
  else if (!leftDetected && rightDetected) timeState = FORWARD;
  else timeState = PAUSED;

  if (now - lastTickTime >= tickInterval) {
    lastTickTime += tickInterval;
    if (timeState == FORWARD) clockStepper.step(-stepsPerTick);
    else if (timeState == BACKWARD) clockStepper.step(stepsPerTick);
  }

  sendStateIfChanged();
}

const int ENA = 6;
const int IN1 = 5;
const int IN2 = 4;

enum TimeState { PAUSED, FORWARD, BACKWARD };
TimeState state = PAUSED;

byte motorSpeed = 80;

unsigned long lastChangeTime = 0;
const unsigned long maxRunTime = 10000; // 10 seconds

void setup() {
  pinMode(ENA, OUTPUT);
  pinMode(IN1, OUTPUT);
  pinMode(IN2, OUTPUT);
  Serial.begin(9600);
  lastChangeTime = millis();
}

void applyMotorState(TimeState s, byte speed) {
  if (s == PAUSED) {
    digitalWrite(IN1, LOW);
    digitalWrite(IN2, LOW);
    analogWrite(ENA, 0);
  } else if (s == FORWARD) {
    digitalWrite(IN1, HIGH);
    digitalWrite(IN2, LOW);
    analogWrite(ENA, speed);
  } else if (s == BACKWARD) {
    digitalWrite(IN1, LOW);
    digitalWrite(IN2, HIGH);
    analogWrite(ENA, speed);
  }
}

void setState(TimeState newState) {
  if (newState != state) {
    state = newState;
    lastChangeTime = millis();
  }
}

void loop() {
  if (Serial.available() > 0) {
    char c = Serial.read();
    if (c == 'F') setState(FORWARD);
    else if (c == 'B') setState(BACKWARD);
    else if (c == 'P') setState(PAUSED);
  }

  unsigned long now = millis();

  if (state != PAUSED && (now - lastChangeTime >= maxRunTime)) {
    setState(PAUSED);
  }

  applyMotorState(state, motorSpeed);
}

Circuit Schematic

(Diagram made with https://www.circuit-diagram.org/)

breakdown of schematic:

Main Arduino

  • Ultrasonic Sensor Left
    • TRIG to pin 6
    • ECHO to pin 7
    • VCC to 5V
    • GND to GND
  • Ultrasonic Sensor Right
    • TRIG to pin 4
    • ECHO to pin 5
    • VCC to 5V
    • GND to GND
  • Stepper Motor (with driver)
    • IN1 → pin 8
    • IN2 → pin 9
    • IN3 → pin 10
    • IN4 → pin 11
    • VCC → 5V
    • GND → GND
  • Serial Out
    • TX (pin 1) → RX of second Arduino

Second Arduino (DC Motor Controller)

  • DC Motor Driver
    • IN1 → pin 5
    • IN2 → pin 4
    • ENA (PWM) → pin 6
    • Motor output → DC motor
    • Vmotor → 5V
    • GND → common ground with Arduin
  • Serial In
    • RX (pin 0) → TX of main Arduino

p5.js Code

  • Reads the serial state from Arduino
  • Updates the scene
  • Changes background colors
  • Moves particles and waves
  • Animates the digital clock
  • Shows arrows for direction

 

Communication Between Arduino and p5.js

Arduino → p5.js

Sends one character:

  • 'F' — forward
  • 'B' — backward
  • 'P' — paused

p5.js reads this using the Web Serial API.
When p5.js sees the character, it updates the digital world.

What I Am Proud Of

I am proud of how everything stays in sync.
The telescoping plant mechanism was hard to build, but it works well and gives life to the piece.
The gesture-based control also feels natural, and most users understand the idea at once.

How This Was Made

The clock was laser cut and screwed into a cardboard cylinder. I used an ice cream stick for the hand, which was connected using a skewer to the stepper motor. The boxes for the ultrasonic sensors were laser cut, and I got the design from boxes.py. For the fast forward and rewind icons, I designed them in Illustrator and then laser cut them. I got the idea for the telescoping cylinder from a YouTube short (https://www.youtube.com/shorts/99a4RUlUTm0), and I made a much simpler version that I 3D printed. I used another cardboard cylinder that I cut open to place the plant in and attach the DC motor and wheel at the top. I used acrylic paint, with help from an art major friend, to paint a background scene with the plant and sky.

The p5.js code was written through many tests and changes to connect smoothly with the Arduino using Web Serial. The designs for the scene, the clock visuals, and the interaction layout were made in small steps until everything felt right. The writeup was also done in simple clear language to explain the full system. All media was either created by me, painted by a friend, laser cut from my designs, or made using free online tools.

Areas for Future Improvement

The clock could be painted with a wooden-style finish to look more complete. I also still want to explore the original rotary sensor idea. The plan was to let the user manually rewind the clock by hand, and the system would detect this and move backward. I tested this with gears connecting the rotary sensor to the stepper motor, but the motor was too weak or the gears did not line up. I want to try again with stronger parts.

Finally, while the p5.js visuals look good and support the project, I feel there may be more ways to integrate the digital space with the physical movement. This is something I want to improve in the future.

Week 13 – user testing

User Testing: A Walk Through Time

I have conducted the user testing for the project, and the reactions were very positive. Most users understood the main idea right away. When the clock hand moved forward, they saw it as time moving forward. When it moved backward, they understood that time was reversing. This was a good sign that the core interaction is intuitive and does not need much explanation.

The gesture control also made sense to most people, but a few were unsure at first about which sensor to wave their hand over. To make this clearer, I decided to laser cut simple icons for fast forward and rewind and attach them to the ultrasonic sensors. This small change makes the mapping between gesture and action much more obvious.

One interesting issue that came up during testing was the behavior of the plant mechanism. The DC motor pulls fishing wire that extends a telescoping plant, and it collapses when the motor goes in reverse. Some users kept reversing time for too long, which caused the wire to unwind so far that it started rolling in the opposite direction. This made the plant rise again by mistake. Another related problem was users sending the plant up too high until it almost reached the motor.

To address this, I am adding a failsafe to the DC motor logic. The system will now prevent the motor from spinning in the same direction for too long. This will keep the fishing wire from fully unspooling and will protect the telescoping structure from being pulled too far up. This fix makes the physical system more reliable and safer for open interaction

Week 12 – Final Proposal

A Walk Through Time

A Walk Through Time is an interactive artwork that combines a physical clock, motion sensors, a DC motor, a stepper motor, and a digital surreal time-scape made in p5.js. The goal is to let the viewer control the flow of time with simple gestures, and watch both the physical world and the digital world respond in sync. When the viewer waves a hand on one side, time moves forward. When the viewer waves a hand on the other side, time moves backward. When no one is interacting, time pauses.

Arduino: Design and Behavior
The hardware side uses:

Two ultrasonic sensors

A stepper motor that drives a physical clock hand

A DC motor that rotates forward or backward depending on the state

A communication link to p5.js through Web Serial

A second Arduino that receives the state and drives the DC motor

Inputs (Arduino)

Left ultrasonic sensor

    • Detects a hand or body close to it
    • If only the left sensor sees something, time moves backward

Right ultrasonic sensor

    • Detects a hand or body on the right side
    • If only the right sensor sees something, time moves forward

Both sensors together or none

    • Time enters a paused state

The Arduino reads both sensors and decides one of three states: FORWARD, BACKWARD, PAUSED

Outputs (Arduino)

Stepper motor movement

    • Moves a physical clock hand
    • A full rotation is broken into 12 “ticks”
    • In FORWARD state the stepper ticks clockwise
    • In BACKWARD state the stepper ticks counterclockwise
    • In PAUSED state it holds still

Serial output sent to p5.js

    • The Arduino sends a single character representing the state:
    • ‘F’ for forward
    • ‘B’ for backward
    • ‘P’ for paused

Serial output to the second Arduino (DC motor controller)

    • The same state characters (F, B, P) are sent out
DC Motor Arduino

The second Arduino receives the state from the first one:

    • ‘F’ → DC motor spins forward
    • ‘B’ → DC motor spins backward
    • ‘P’ → DC motor stops
p5.js: Design and Behavior

The digital part is a surreal, dreamlike time-space. It reacts in real time to the state coming from Arduino. The design uses motion, color shifts, particles, waves, ripples, and a glowing abstract clock.

Inputs (p5.js)

Serial data from the Arduino

    • Reads incoming characters: F, B, or P
    • Updates timeState
    • Applies visual changes based on the state

Keyboard fallback for testing

    • F B P keys switch states if Arduino is not connected

Behavior of the digital scene

The scene changes in several ways depending on the state, reflecting time going forward, backwards, or stopped.

 

Week 11 – Gravity, Bounce, and Wind

For this assignment, I worked with Youssab to connect p5.js and Arduino. We completed three exercises to practice sending data between the physical and digital worlds.

Part 1: One Sensor to p5.js

In this first exercise, we used a potentiometer on the Arduino. This controls the horizontal position of a circle on the computer screen. The Arduino reads the sensor value and sends it to p5.js. Then, p5.js moves the ball left or right based on that number.

Schematic

Arduino Code

void setup() {
  Serial.begin(9600);
}

void loop() {
  // Read analog value and send it as a line of text
  int sensorValue = analogRead(A0);
  Serial.println(sensorValue);
  delay(20); 
}

p5.js Code

let port;
let connectBtn;
let ballX = 0;
let sensorVal = 0;

function setup() {
  createCanvas(600, 400);
  background(50);

  port = createSerial();

  // Open the port automatically if used before
  let usedPorts = usedSerialPorts();
  if (usedPorts.length > 0) {
    port.open(usedPorts[0], 9600);
  }

  connectBtn = createButton('Connect to Arduino');
  connectBtn.position(10, 10);
  connectBtn.mousePressed(connectBtnClick);
}

function draw() {
  // Check if port is open
  if (port.available() > 0) {
    let data = port.readUntil("\n");
    
    if (data.length > 0) {
      // Update value
      sensorVal = Number(data.trim()); 
    }
  }

  background(256);
  
  // Map sensor val to canvas width
  ballX = map(sensorVal, 0, 1023, 25, width - 25);
  
  // Draw ball
  fill(0, 255, 100);
  noStroke();
  ellipse(ballX, height / 2, 50, 50);
}

function connectBtnClick() {
  if (!port.opened()) {
    port.open('Arduino', 9600);
  } else {
    port.close();
  }
}

Part 2: p5.js to LED Brightness

For the second part, we reversed the flow of data. We send information from p5.js to the Arduino. The ball on the screen represents a light bulb. If you drag the ball higher, the LED gets brighter. If you drag it lower, the LED dims.

Schematic

Arduino Code

C++

void setup() {
  Serial.begin(9600);
  pinMode(9, OUTPUT); //pmw pin
}

void loop() {
  if (Serial.available() > 0) {
    String input = Serial.readStringUntil('\n');
    int brightness = input.toInt();    // convert str to int
    brightness = constrain(brightness, 0, 255);    // just in case data is weird
    analogWrite(9, brightness);
  }
}

p5.js Code

JavaScript

let port;
let connectBtn;

// Ball variables
let ballX = 300;
let ballY = 200;
let ballSize = 50;
let isDragging = false; 

// Data variables
let brightness = 0;
let lastSent = -1; 

function setup() {
  createCanvas(600, 400);
  
  port = createSerial();
  
  let usedPorts = usedSerialPorts();
  if (usedPorts.length > 0) {
    port.open(usedPorts[0], 9600);
  }

  connectBtn = createButton('Connect to Arduino');
  connectBtn.position(10, 10);
  connectBtn.mousePressed(connectBtnClick);
}

function draw() {
  background(50);

  // ball logic
  if (isDragging) {
    ballX = mouseX;
    ballY = mouseY;
    
    // Keep ball inside canvas
    ballY = constrain(ballY, 0, height);
    ballX = constrain(ballX, 0, width);
  }

  // map brightness to y pos
  brightness = floor(map(ballY, 0, height, 255, 0));

  // send data
  if (port.opened() && brightness !== lastSent) {
    port.write(String(brightness) + "\n");
    lastSent = brightness;
  }
  //draw ball
  noStroke();
  fill(brightness, brightness, 0); 
  ellipse(ballX, ballY, ballSize);
  stroke(255);
  line(ballX, 0, ballX, ballY);

}

// --- MOUSE INTERACTION FUNCTIONS ---
function mousePressed() {
  // check if mouse is inside the ball
  let d = dist(mouseX, mouseY, ballX, ballY);
  if (d < ballSize / 2) {
    isDragging = true;
  }
}

function mouseReleased() {
  // stop dragging when mouse is let go
  isDragging = false;
}

function connectBtnClick() {
  if (!port.opened()) {
    port.open('Arduino', 9600);
  } else {
    port.close();
  }
}

Part 3: Gravity Wind and Bi-directional Communication

This final exercise combined everything. We used the gravity wind example code. We modified it to do two things.

First, we use a potentiometer to control the wind force. Second, when the ball bounces on the floor, the LED lights up.

This required some problem solving. I had to ignore very small bounces. Without that check, the LED would blink constantly when the ball rolled on the floor. I also added a visual indicator arrow to show the direction of the wind.

Schematic

Arduino Code

void setup() {
  Serial.begin(9600);
  pinMode(9, OUTPUT); // LED on Pin 9
}

void loop() {
  // read & send pot value
  int potValue = analogRead(A0);
  Serial.println(potValue);


  if (Serial.available() > 0) {
    char inChar = Serial.read();
    
    // check for blink command
    if (inChar == 'B') {
      digitalWrite(9, HIGH);
      delay(50); 
      digitalWrite(9, LOW);
    }
  }
  
  delay(15);
}

p5.js Code

let port;
let connectBtn;

let velocity;
let gravity;
let position;
let acceleration;
let wind;
let drag = 0.99;
let mass = 50;
let sensorVal = 512; 

function setup() {
  createCanvas(640, 360);
  noFill();
  
  // Physics Setup
  position = createVector(width/2, 0);
  velocity = createVector(0,0);
  acceleration = createVector(0,0);
  gravity = createVector(0, 0.5*mass);
  wind = createVector(0,0);

  // Serial Setup
  port = createSerial();
  let usedPorts = usedSerialPorts();
  if (usedPorts.length > 0) {
    port.open(usedPorts[0], 9600);
  }
  
  connectBtn = createButton('Connect to Arduino');
  connectBtn.position(10, 10);
  connectBtn.mousePressed(connectBtnClick);
}

function draw() {
  background(255);
  
  // read for wind
  if (port.available() > 0) {
    let data = port.readUntil("\n");
    if (data.length > 0) {
      sensorVal = Number(data.trim());
    }
  }
  
  // map wind
  let windX = map(sensorVal, 0, 1023, -0.8, 0.8);
  wind.set(windX, 0);

  // apply physics
  applyForce(wind);
  applyForce(gravity);
  velocity.add(acceleration);
  velocity.mult(drag);
  position.add(velocity);
  acceleration.mult(0);
  
  // draw
  fill(0);
  ellipse(position.x, position.y, mass, mass);
  drawWindIndicator(windX);

  // detect bounce
  if (position.y > height - mass/2) {
      velocity.y *= -0.9; 
      position.y = height - mass/2;
      
      // send blink command
      if (abs(velocity.y) > 1 && port.opened()) {
        port.write('B');
      }
  }
  
  // collision detection
  if (position.x > width - mass/2) {
    position.x = width - mass/2;
    velocity.x *= -0.9;
  } else if (position.x < mass/2) {
    position.x = mass/2;
    velocity.x *= -0.9;
  }
}

function applyForce(force){
  let f = p5.Vector.div(force, mass);
  acceleration.add(f);
}

function connectBtnClick() {
  if (!port.opened()) {
    port.open('Arduino', 9600);
  } else {
    port.close();
  }
}

// helper to visualize the wind
function drawWindIndicator(w) {
  push();
  translate(width/2, 50);
  fill(150);
  noStroke();
  text("Wind Force", -30, -20);
  stroke(0);
  strokeWeight(3);
  line(0, 0, w * 100, 0); 
  fill(255, 0, 0);
  noStroke();
  if (w > 0.05) triangle(w*100, 0, w*100-10, -5, w*100-10, 5); // Right Arrow
  if (w < -0.05) triangle(w*100, 0, w*100+10, -5, w*100+10, 5); // Left Arrow
  pop();
}

function keyPressed(){
  // reset ball
  if (key==' '){
    mass=random(15,80);
    position.y=-mass;
    position.x = width/2;
    velocity.mult(0);
  }
}

Video Demonstration

Week 11 – Post Response

This week, I read Design Meets Disability by Graham Pullin, and it really struck a chord with me. It made me think about a conversation I have with my parents constantly.

For years, my parents have tried to talk me into getting LASIK eye surgery. They see it as fixing a problem. But I have always refused. I don’t see my poor eyesight as a disability that needs to be erased. I see my glasses as a feature. They are part of who I am. They are part of my identity.

Pullin talks about this in the book. He uses eyewear as the perfect example of a medical device becoming a fashion statement. Nobody looks at glasses and thinks “medical equipment” anymore. We just think “style.” This is exactly how I feel. If I fixed my eyes, I would lose a part of my personality.

However, I did not agree with everything in the text. There was a point where the “Swiss Army Knife” approach—making one tool do many things—was critiqued as ugly or cluttered. I disagree. I think there is beauty in something that is highly functional and straightforward. Design does not always have to be minimal to be good. If a device solves a problem efficiently, that is good design.

The biggest takeaway for me is that we need to stop hiding. Too often, medical design tries to mimic skin tone or conceal the device. This is a mistake. When you try to hide a hearing aid or a wheelchair, you are telling the user that their condition is something to be ashamed of. It also limits creativity.

If we stop trying to hide, we can start designing.

Imagine if Nike designed a wheelchair. It wouldn’t look like a hospital chair. It would look fast, sporty, and bold. Imagine if Apple designed a hearing aid. It wouldn’t be “flesh-colored” plastic. It would be sleek, white, or metallic, and people might actually want to wear it.

We need to move toward a world where assistive technology does not apologize for existing. It should serve its function while prioritizing great design. Just like my glasses.

Week 10 – DJ

Project: The Arduino DJ Console

Assignment Description
For this assignment, we had to create a musical instrument. The requirements were to use at least one digital sensor (switch) and one analog sensor.

What We Made
We created a DJ console using an Arduino. This instrument allows for infinite possibilities based on the user. We used the following components:

  • Two Potentiometers (Analog Sensors): One knob adjusts the tone (pitch) of the note. The other knob adjusts the speed (duration) of the note.

  • One Button (Digital Sensor): This button acts as a mute switch to stop the sound.

  • Piezo Buzzer: This plays the sound.

Schematic

Here is the circuit diagram for our project. We connected the knobs to the analog pins (A0 and A5) and the button to a digital pin (13).

Code

We wrote code to read the sensors and play notes from a C Major scale. Here is the source code for the project:

C++

// Control a buzzer with two knobs and a button

// Define hardware pins
const int piezoPin = 9;
const int pitchPotPin = A0;
const int durationPotPin = A5;
const int buttonPin = 13;

// List of frequencies for C Major scale
int notes[] = {262, 294, 330, 349, 392, 440, 494, 523};

void setup() {
  // Start data connection to computer
  Serial.begin(9600);
  Serial.println("Instrument Ready! Note Stepping Enabled.");

  // Set pin modes
  pinMode(piezoPin, OUTPUT);
  pinMode(buttonPin, INPUT_PULLUP);
}

void loop() {
  // Check if the button is pressed
  int buttonState = digitalRead(buttonPin);

  // Mute sound if button is held down
  if (buttonState == LOW) {
    noTone(piezoPin);
  } else {
    // Read values from both knobs
    int pitchValue = analogRead(pitchPotPin);
    int durationValue = analogRead(durationPotPin);

    // Convert pitch knob value to a note index from 0 to 7
    int noteIndex = map(pitchValue, 0, 1023, 0, 7);

    // Select the frequency from the list
    int frequency = notes[noteIndex];

    // Convert duration knob value to time in milliseconds
    int noteDuration = map(durationValue, 0, 1023, 50, 500);

    // Play the sound
    tone(piezoPin, frequency, noteDuration);

    // Show information on the screen
    Serial.print("Note Index: ");
    Serial.print(noteIndex);
    Serial.print(" | Frequency: ");
    Serial.print(frequency);
    Serial.print(" Hz | Duration: ");
    Serial.print(noteDuration);
    Serial.println(" ms");

    // Wait for the note to finish
    delay(noteDuration + 50);
  }
}

Video Demonstration

Check out the video below to see the instrument in action.

 

Here is my attempting Happy Birthday (Badly)

Week 10 – Post Response

Reading Bret Victor’s “A Brief Rant on the Future of Interaction Design” feels like a wake-up call. His critique of modern interfaces—what he calls “Pictures Under Glass”—is undeniable. We have taken the incredible dexterity of the human hand, capable of thousands of distinct movements, and reduced it to a single, numb motion: the swipe.

Victor argues that the future should be about “Dynamic Media”—screens that can physically morph, allowing us to feel buttons and textures. While I agree with his diagnosis of the problem (we are disconnected from the physical world), I disagree with his solution. I don’t think the future is a morphing screen.

In my opinion, Smart Glasses are the next big thing.

The End of the Swipe

Victor’s main gripe is that touchscreens blind our hands. We have to look at the screen to know where the buttons are because we can’t feel them.

Smart glasses solve this, but not by adding texture. They solve it by freeing our hands entirely. With the advanced hand-tracking and depth sensors we are seeing in emerging tech, the “swipe” becomes obsolete. Instead, we can return to the real-life gestures Victor mourns the loss of.

If I want to turn a dial, I can mime turning a dial in the air. If I want to grab an object, I just grab it. The interface isn’t trapped under a sheet of glass anymore; it is overlaid onto the real world. We can use our full range of motor skills to manipulate digital objects as if they were physical ones.

24/7 Access, Zero Friction

The other massive advantage of the Smart Glasses form factor is integration. Victor worries that interfaces are separating us from our environment. But if the interface is a pair of glasses that look like normal eyewear, the digital world becomes a seamless layer on top of the physical one.

We could have access to the digital world 24/7, completely unnoticeable to the people around us. No more looking down at a phone, hunching over, or disengaging from a conversation to check a notification. The technology recedes into the background, becoming a true extension of the senses rather than a “tool” you have to pick up and hold.

The Futility of Prediction

However, reading through Victor’s “Responses” page reminds me that we should be humble with our predictions.

Victor wrote his rant in 2011, convinced that the iPad was just a transitional phase like black-and-white photography. Yet, over a decade later, we are still swiping on glass.

When we look back at how people in the 1900s predicted the year 2000, they got almost everything wrong. They imagined flying firefighters and personal blimps, but they completely missed the internet, microchips, and AI. We tend to predict the future by exaggerating the present—Victor predicted better physical buttons because he liked physical tools. I am predicting smart glasses because I like visual overlays.

Ultimately, nobody knows what the “next big thing” actually is until it arrives. We can analyze and debate, but in the end, we just have to wait and see.

Week 9 – Simon Says

Description

For this assignment, I created a “Simon Says” style memory game. The goal of the game is to memorize a sequence of colors displayed on the main RGB LED and repeat the pattern back using four push buttons.

To fulfill the assignment requirements, I incorporated both digital and analog controls to affect the game’s behavior:

  1. Digital Inputs (Switches): Four push buttons act as the game controller. These are used to start the game and input the color sequence.

  2. Analog Input (Sensor): A Potentiometer is used as a difficulty selector. Before the game starts, reading the analog value of the potentiometer determines the speed of the flashes and the length of the sequence.

  3. Outputs:

    • RGB LED: Displays the randomized game sequence.

    • Feedback LEDs: Two separate LEDs (Green and Red) indicate if the player won or lost the round.

Schematic

Here is the hand-drawn wiring diagram for the circuit. It details the connections for the RGB LED (Pins 11-13), the Feedback LEDs (Pins 9-10), the Buttons (Pins 2-5), and the Potentiometer (Pin A1).

Logic & Interaction

The system waits for the Yellow button to be pressed to start. Once triggered, the Arduino reads the Potentiometer.

  • If the potentiometer is turned one way, the game is “Easy” (slower flashes, shorter sequence).

  • If turned the other way, the game becomes “Hard” (rapid flashes, longer sequence).

The Arduino then generates a random pattern displayed on the RGB LED. The player must press the buttons in the correct order. If successful, the distinct Green feedback LED flashes; if incorrect, the Red feedback LED flashes.

Gameplay Video

Code

Below is the Arduino code used for this project. 

// RGB LED Pins
const int RGB_RED_PIN = 13;
const int RGB_GREEN_PIN = 12;
const int RGB_BLUE_PIN = 11;

// Feedback LED Pins
const int FEEDBACK_RED_PIN = 9;
const int FEEDBACK_GREEN_PIN = 10;

// Button Pins
const int BUTTON_YELLOW_PIN = 2;
const int BUTTON_BLUE_PIN = 3;
const int BUTTON_GREEN_PIN = 4;
const int BUTTON_RED_PIN = 5;

// Potentiometer Pin
const int POT_PIN = A1;

// Game Settings
const int BASE_SEQUENCE_LENGTH = 3;
const int MAX_SEQUENCE_LENGTH = 12;
const int BASE_LIGHT_DISPLAY_TIME = 1000;
const int FAST_LIGHT_DISPLAY_TIME = 100;
const int PAUSE_BETWEEN_LIGHTS = 50;
const int FEEDBACK_BLINK_TIME = 200;
const int FEEDBACK_BLINK_COUNT = 3;

// Game State
int gameSequence[MAX_SEQUENCE_LENGTH];
int currentSequenceLength = BASE_SEQUENCE_LENGTH;
int currentDisplayTime = BASE_LIGHT_DISPLAY_TIME;

void setup() {
  // Initialize serial communication
  Serial.begin(9600);
  Serial.println("Simon Game Started!");

  // Configure LED pins
  pinMode(RGB_RED_PIN, OUTPUT);
  pinMode(RGB_GREEN_PIN, OUTPUT);
  pinMode(RGB_BLUE_PIN, OUTPUT);
  pinMode(FEEDBACK_RED_PIN, OUTPUT);
  pinMode(FEEDBACK_GREEN_PIN, OUTPUT);

  // Configure button pins
  pinMode(BUTTON_YELLOW_PIN, INPUT_PULLUP);
  pinMode(BUTTON_BLUE_PIN, INPUT_PULLUP);
  pinMode(BUTTON_GREEN_PIN, INPUT_PULLUP);
  pinMode(BUTTON_RED_PIN, INPUT_PULLUP);

  // Reset LEDs
  turnAllRGBOff();
  digitalWrite(FEEDBACK_RED_PIN, LOW);
  digitalWrite(FEEDBACK_GREEN_PIN, LOW);

  // Seed random generator
  randomSeed(analogRead(A0));
  
  Serial.println("Waiting for start...");
}

void loop() {
  // Wait for start button 
  while (digitalRead(BUTTON_YELLOW_PIN) == HIGH) {
    digitalWrite(FEEDBACK_RED_PIN, HIGH);
    digitalWrite(FEEDBACK_GREEN_PIN, LOW);
    delay(150);
    digitalWrite(FEEDBACK_RED_PIN, LOW);
    digitalWrite(FEEDBACK_GREEN_PIN, HIGH);
    delay(150);
    digitalWrite(FEEDBACK_GREEN_PIN, LOW);
    delay(100);
  }
  
  digitalWrite(FEEDBACK_RED_PIN, LOW);
  digitalWrite(FEEDBACK_GREEN_PIN, LOW);

  Serial.println("Game Starting...");
  delay(200);
  
  // Update difficulty based on potentiometer
  updateGamePace();
  
  // Generate and display sequence
  generateSequence();
  printSequence();
  displaySequence();

  // Process player input
  bool correct = getUserInput();

  // Provide result feedback
  if (correct) {
    Serial.println("Correct!");
    feedbackBlink(FEEDBACK_GREEN_PIN, FEEDBACK_BLINK_COUNT, FEEDBACK_BLINK_TIME);
  } else {
    Serial.println("Incorrect!");
    feedbackBlink(FEEDBACK_RED_PIN, FEEDBACK_BLINK_COUNT, FEEDBACK_BLINK_TIME);
  }
}

// Adjust sequence length and speed based on potentiometer value
void updateGamePace() {
  int potValue = analogRead(POT_PIN);

  currentSequenceLength = map(potValue, 0, 1023, BASE_SEQUENCE_LENGTH, MAX_SEQUENCE_LENGTH);
  currentSequenceLength = constrain(currentSequenceLength, BASE_SEQUENCE_LENGTH, MAX_SEQUENCE_LENGTH);

  currentDisplayTime = map(potValue, 0, 1023, BASE_LIGHT_DISPLAY_TIME, FAST_LIGHT_DISPLAY_TIME);
  currentDisplayTime = constrain(currentDisplayTime, FAST_LIGHT_DISPLAY_TIME, BASE_LIGHT_DISPLAY_TIME);
}

// Fill sequence array with random colors
void generateSequence() {
  for (int i = 0; i < currentSequenceLength; i++) {
    gameSequence[i] = random(4);
  }
}

// Output current sequence to serial monitor for debugging
void printSequence() {
  Serial.print("Sequence: [");
  for (int i = 0; i < currentSequenceLength; i++) {
    Serial.print(gameSequence[i]);
    if (i < currentSequenceLength - 1) Serial.print(", ");
  }
  Serial.println("]");
}

// Play back the sequence on the RGB LED
void displaySequence() {
  for (int i = 0; i < currentSequenceLength; i++) {
    switch (gameSequence[i]) {
      case 0: turnOnRGBRed(); break;
      case 1: turnOnRGBGreen(); break;
      case 2: turnOnRGBBlue(); break;
      case 3: turnOnRGBYellow(); break;
    }
    delay(currentDisplayTime);
    turnAllRGBOff();
    delay(PAUSE_BETWEEN_LIGHTS);
  }
}

// Capture player input and verify against sequence
bool getUserInput() {
  int inputCount = 0;

  while (inputCount < currentSequenceLength) {
    int pressedButton = readButtons();
    
    if (pressedButton != -1) {
      // Visual feedback for button press
      switch (pressedButton) {
        case 0: turnOnRGBRed(); break;
        case 1: turnOnRGBGreen(); break;
        case 2: turnOnRGBBlue(); break;
        case 3: turnOnRGBYellow(); break;
      }
      delay(100);
      turnAllRGBOff();
      delay(50);

      // Check against expected sequence
      if (pressedButton != gameSequence[inputCount]) {
        return false;
      }
      
      inputCount++;
      
      // Wait for button release
      while(digitalRead(BUTTON_RED_PIN) == LOW || digitalRead(BUTTON_GREEN_PIN) == LOW ||
            digitalRead(BUTTON_BLUE_PIN) == LOW || digitalRead(BUTTON_YELLOW_PIN) == LOW);
    }
  }
  return true;
}

// Return index of pressed button or -1 if none
int readButtons() {
  if (digitalRead(BUTTON_RED_PIN) == LOW) return 0;
  if (digitalRead(BUTTON_GREEN_PIN) == LOW) return 1;
  if (digitalRead(BUTTON_BLUE_PIN) == LOW) return 2;
  if (digitalRead(BUTTON_YELLOW_PIN) == LOW) return 3;
  return -1;
}

// Blink specified LED for feedback
void feedbackBlink(int pin, int count, int blinkTime) {
  for (int i = 0; i < count; i++) {
    digitalWrite(pin, HIGH);
    delay(blinkTime);
    digitalWrite(pin, LOW);
    delay(blinkTime);
  }
}

// --- RGB Control Helpers ---

void turnAllRGBOff() {
  digitalWrite(RGB_RED_PIN, LOW);
  digitalWrite(RGB_GREEN_PIN, LOW);
  digitalWrite(RGB_BLUE_PIN, LOW);
}

void turnOnRGBRed() {
  turnAllRGBOff();
  digitalWrite(RGB_RED_PIN, HIGH);
}

void turnOnRGBGreen() {
  turnAllRGBOff();
  digitalWrite(RGB_GREEN_PIN, HIGH);
}

void turnOnRGBBlue() {
  turnAllRGBOff();
  digitalWrite(RGB_BLUE_PIN, HIGH);
}

void turnOnRGBYellow() {
  turnAllRGBOff();
  digitalWrite(RGB_RED_PIN, HIGH);
  digitalWrite(RGB_GREEN_PIN, HIGH);
}

 

Week 9 – Post Response

Good Design Needs No Explanation

This week we read “Physical Computing’s Greatest Hits and Misses” and “Making Interactive Art: Set the Stage, Then Shut Up and Listen.” These readings made me think about how we interact with objects.

In my opinion, the main factor for a brilliant design is intuition. It should be unnoticeable. It should not require an instruction manual. I am a self-proclaimed kinesthetic learner. This means I learn by doing. I despise manuals. When I get something new, I like to jump in headfirst and discover how it works.

The reading on “Greatest Hits and Misses” talks about reliability and clarity. However, modern tech companies often ignore this. They prioritize simple, sleek designs to look futuristic. This often sacrifices intuitivity.

For example, you might have to double tap the back of a phone to take a screenshot. Or you have to triple tap to turn on a flash. These are hidden secrets, not clear controls.

I have a personal example of this. I used to go into the settings menu on my phone every time I wanted to turn on noise cancellation on my AirPods. Eventually, someone pointed out that I can just hold the stem of the AirPod to change the mode. I felt silly, but I shouldn’t have. The design gave me no clue that the feature existed.

Of course, there are exceptions. An airplane cockpit is very complex. It has hundreds of buttons. This affords complexity because knowledge is a prerequisite. You have to go to flight school to fly a plane.

But for everyday items or art, we should not need flight school.

This connects to the second reading, “Set the Stage, Then Shut Up and Listen.” This text argues that an artist should not stand next to their work and explain it.

If you let a user interact with your art without context, and they do it wrong, that is important data. It does not mean the user is stupid. It means the design is flawed or unintuitive.

As a designer, you have to set the stage. You have to give clues through the shape and feel of the object. If you have to write a manual for your art installation, you have failed to make a connection. Good interaction is about letting the user figure it out with their own hands.

Week 8 – Post Response

Design is More Than Function: Why Feeling Right Matters

When we think about technology, it’s easy to focus only on what it does. But good design isn’t just about function, it’s about how it feels. The readings “Emotion & Design: Attractive Things Work Better” and Her Code Got Humans on the Moon reminded me that emotion, perception, and clever design tricks shape how we interact with technology every day.

Take something as simple as a volume bar on your phone. On paper, 50% should be half the sound. But our ears don’t perceive sound linearly, they perceive it logarithmically. This means that a volume slider isn’t just a slider, it’s an emotional experience. Apple and Google realized this and adjusted the curves so that the volume feels smooth and natural to our ears. YouTube, by contrast, leaves most of the adjustment in the last 10%, making it feel unpredictable and frustrating.

Some designs don’t actually change the outcome but give the illusion of control. Elevators are a perfect example. About 80% of “close door” buttons don’t do anything, the door closes automatically, but the button makes users feel empowered. That illusion of control creates a better experience, even if it’s technically unnecessary. This is exactly the kind of trick designers use to shape our perception and emotions.

Another example from the transcript is the Nintendo Switch joycon mirrors. The solution was low-tech and simple: a small mirror to reflect the LED lights. It’s deceptively simple but brilliantly effective. It shows that design isn’t always about complexity, it’s about solving the human problem in a way that feels right.

Even everyday software hides these design decisions. Progress bars, icons, and animations are not just decorative, they are carefully crafted to keep users calm, informed, and engaged. Google tested 50 shades of blue to see which one users clicked most, and Apple uses subtle motion blur to make screens feel smoother. These small touches are invisible if done well, but they make a huge difference in the user experience.