Week 11 Final Project Brainstorm

For my final project, I want to create a physically interactive AI Tamagotchi. A mini robot creature that the user can pat, touch, hold, and communicate with, but which responds in unexpectedly pessimistic ways. The project combines physical interaction through Arduino sensors with a character AI conversational system and visual interface built in p5.

I recently got obsessed with Tamagotchi, which is a small handheld digital pets that require constant attention: need users feed them, clean up after them, and respond to their needs. Tamagotchis reward human care with affection or happiness, but with the growing cultural anxieties around automation and AI as well as environmental destruction caused by human behavior, I want to  imagines this project in a future in which artificial no longer need, or even want, human caretaking.

The final project would be a mini physical pet built with neopixels and sensors(e.g., force sensor, knob) to react (e.g., twitch, turn away, glow, flash) when touched. Users interact with it through force sensors, knobs, and touch inputs connected to an Arduino. The p5 interface displays a larger animated avatar of the creature and handles generative dialogue and visual responses.

However, unlike typical virtual pets, this creature is intentionally negative and pessimistic toward the user.

  1. When patted, it snaps: “Go away, you silly little human.”
  2. When fed (via knob input), it complains: “Ugh, you call this food?”
  3. When left alone too long, it becomes sarcastic: “Finally. Peace without humans.”

Using an AI API, the creature can answer user questions and hold brief conversations, but it always maintains a consistent disrespectful personality, reflecting a world where robots might question human motives, or judge the damage humans have done to the environment.

Week 11 Exercises Documentation

Group Member: Yiyang

(Arduino codes are commented in the bottom of p5 links)

1. make something that uses only one sensor  on Arduino and makes the ellipse in p5 move on the horizontal axis, in the middle of the screen, and nothing on arduino is controlled by p5

p5 & arduino: https://editor.p5js.org/joyzheng/sketches/63Yg60k8D

Video Documentation: IMG_9638

 

2. make something that controls the LED brightness from p5

p5 & arduino: https://editor.p5js.org/yiyang/sketches/dtftbIzaK

 

3. take the gravity wind example (https://editor.p5js.org/aaronsherwood/sketches/I7iQrNCul) and make it so every time the ball bounces one led lights up and then turns off, and you can control the wind from one analog sensor

p5 & arduino: https://editor.p5js.org/joyzheng/sketches/v77Sd41K4

Video Documentation: IMG_9640

Week 11 Reading

This article reminds me of an idea I heard from my robots class: are we all augmented human or “half robots” if there are so many designs eliminate the disability of us. When we relate this concept to real life, we see that we are constantly relying on external design to bridge our own biological limitations, whether that involves wearing glasses to correct what is considered a “mild visual impairment” (a product that has evolved from a medical appliance to a core fashion accessory), or using an advanced hearing aid. The increasing sophistication of products designed to solve specific physical or cognitive problems suggests that human capability itself is often defined by the sophisticated tools we seamlessly integrate into our lives.

The readings’ idea of how specialized, constraint-driven design eventually becomes universal is also visible all around us. This “trickle-down effect” is not just a theory, it’s the logic behind some of the most successful products today. Many of the most intuitive interfaces we use, like large buttons on smartphone screens or voice assistants like Siri and Alexa, were originally developed for users who struggled with fine motor control or had visual impairments. Now they’ve become standard because they make life easier for everyone. The dementia-friendly radio mentioned in the reading is a perfect example: its extreme simplicity wasn’t a limitation, but a breakthrough. The need to create something gentle, forgiving, and easy to navigate forced designers to rethink what “good design” actually means, and the resulting object ended up being loved far beyond its intended audience. We see this again in curb cuts designed for wheelchair users, which now help parents with strollers, travelers with luggage, and delivery workers with carts. These real-world cases show that when designers begin with the most constrained user, they often uncover solutions that improve daily life for the entire population.

Week 11 – 3 Exercises (group work)

For week 11, the task was to work in groups to finish 3 exercises. Our group was Me (Asma), Hajar, and Mukhlisa 🙂

Exercise 1: ball moving horizontally with potentiometer

Group Member: Asma (Me)

Video:


Schematic:

Arduino Code:

const int potPin = A0;

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

void loop() {
  int val = analogRead(potPin);   // 0..1023
  Serial.println(val);            
  delay(10);                     
}

 

P5js Code:

// === Arduino + p5.js WebSerial (directional movement, fixed) ===
// Pot controls direction/speed by how its value changes. p5 does NOT control Arduino.

let port;
let reader;
let connectButton;
let isConnected = false;

let latestData = null; // last parsed int from serial (0..1023)
let prevData   = null; // previous sample to compute delta
let lineBuffer = '';   // accumulate serial chunks until '\n'

let posX = 0;          // ellipse position
let speed = 0;         // horizontal velocity

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(240);
  textFont('monospace');

  connectButton = createButton('Connect to Arduino');
  connectButton.position(20, 20);
  connectButton.mousePressed(connectToSerial);

  // start centered; we'll keep it centered until first data arrives
  posX = width / 2;
}

async function connectToSerial() {
  try {
    // Request and open port
    port = await navigator.serial.requestPort();
    await port.open({ baudRate: 9600 });
    isConnected = true;
    console.log(' Port opened');

    // Create a text decoder stream and reader for clean line-by-line reads
    const textDecoder = new TextDecoderStream();
    const readableClosed = port.readable.pipeTo(textDecoder.writable);
    reader = textDecoder.readable.getReader();

    // Kick off read loop
    readSerialLines();
  } catch (err) {
    console.error(' Connection failed:', err);
    isConnected = false;
  }
}

async function readSerialLines() {
  try {
    while (true) {
      const { value, done } = await reader.read();
      if (done) break; // reader released
      if (!value) continue;

      // Accumulate and split by newline
      lineBuffer += value;
      let lines = lineBuffer.split(/\r?\n/);
      lineBuffer = lines.pop(); // save incomplete tail

      for (let line of lines) {
        line = line.trim();
        if (!line) continue;
        const v = parseInt(line, 10);
        if (!Number.isNaN(v)) {
          // Clamp to expected 10-bit range
          latestData = Math.min(Math.max(v, 0), 1023);
          // Initialize prevData on first valid sample
          if (prevData === null) prevData = latestData;
        }
      }
    }
  } catch (err) {
    console.error(' Read error:', err);
  } finally {
    try { reader && reader.releaseLock(); } catch {}
  }
}

function draw() {
  background(240);

  if (!isConnected) {
    fill(200, 0, 0);
    noStroke();
    textAlign(CENTER, CENTER);
    textSize(20);
    text("Click 'Connect to Arduino' to begin", width / 2, height / 2);
    return;
  }

  // If we haven't received any valid data yet, show waiting status
  if (latestData === null || prevData === null) {
    fill(0);
    textSize(16);
    textAlign(LEFT, TOP);
    text('Waiting for data...', 20, 60);
    // Keep ellipse centered until first data arrives
  } else {
    // Change in pot reading determines direction and speed bump
    const delta = latestData - prevData;

    // Deadband to ignore small noise
    const deadband = 4;
    if (delta > deadband) {
      speed = constrain(speed + 0.6, -12, 12); // turn right -> move right
    } else if (delta < -deadband) {
      speed = constrain(speed - 0.6, -12, 12); // turn left -> move left
    } else {
      // friction when knob still
      speed *= 0.90;
    }

    // Integrate position and clamp
    posX += speed;
    posX = constrain(posX, 0, width);

    // Update prev for next frame
    prevData = latestData;
  }

  // Draw ellipse at vertical center
  noStroke();
  fill(50, 100, 255);
  ellipse(posX, height / 2, 80, 80);

  // HUD
  fill(0);
  textSize(14);
  textAlign(LEFT, TOP);
  const shown = latestData === null ? '—' : latestData;
  text(`Sensor: ${shown}`, 20, 60);
  text(`Speed:  ${nf(speed, 1, 2)}`, 20, 80);
}

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  // Keep position on-screen if you resize smaller
  posX = constrain(posX, 0, width);
}

Reflection:

I built a simple circuit using the Arduino and a 10kΩ potentiometer to control an on-screen ellipse in p5.js. I connected the potentiometer’s outer legs to 5V and GND and the middle leg  to the analog pin A0, allowing it to act as a variable voltage divider. After uploading the Arduino code and checking the serial monitor, I could see how turning the knob changed the analog readings from 0 to 1023. This helped me understand how analog sensors translate physical movement into numerical data that can be visualized digitally. It was satisfying to see the system work after troubleshooting my wiring and realizing how the order of connections affects the readings.

Exercise 2: Controlling the LED

Group Member: Hajar

Video:

Arduino Code:

//  Arduino: LED brightness from p5.js 

const int ledPin = 10;  // LED connected to pin 10

void setup() {
  Serial.begin(9600);   // must match p5.js baud rate
  pinMode(ledPin, OUTPUT);
}

void loop() {
  if (Serial.available() > 0) {
    int brightness = Serial.read();      // read 0–255
    brightness = constrain(brightness, 0, 255);
    analogWrite(ledPin, brightness);     // control LED brightness
  }
}

P5js code:

let port;
let writer;

async function setup() {
  createCanvas(512, 512);
  background(0);
  textSize(16);
  textAlign(CENTER, CENTER);
  text('Click to connect to Arduino', width/2, height/2);
}

async function mousePressed() {
  if (!port) {
    port = await navigator.serial.requestPort();
    await port.open({ baudRate: 9600 });
    writer = port.writable.getWriter();
    console.log('Connected!');
  }
}

function mouseDragged() {
  if (writer) {
    let brightness = floor(map(mouseY, 0, height, 255, 0));
    brightness = constrain(brightness, 0, 255);
    writer.write(new Uint8Array([brightness]));
  }
}

Schematic: 

Reflection:

(Hajar) For this project, I created both the schematic and the circuit, and I kept them very simple. My setup only included one LED and one resistor connected to the Arduino and grounded. The main purpose of the assignment was to use serial communication with p5.js to control the brightness of the LED, so I focused more on the coding rather than making the hardware complex. Before starting the p5 part, I tested my circuit using a simple Arduino code just to make sure that the LED was lighting up correctly and everything was connected properly. Once I confirmed that it worked, I added the schematic and moved on to the serial communication part. The schematic itself was very basic something I’ve done before so it wasn’t hard to figure out. I liked that I could keep the circuit minimal but still meet the goal of the exercise, which was to control the LED’s brightness through p5. It showed me how even a simple circuit can become interactive and meaningful when combined with code.

The coding part was definitely the hardest and most time-consuming part of this project. I’ve never connected p5.js and Arduino together before, so figuring out how to make them communicate took a lot of trial and error. At first, I kept trying to make it work on Safari without realizing that the serial connection doesn’t actually work there, it only works on Google Chrome. So, I kept rewriting and rechecking my code, thinking there was something wrong with it, even though the logic itself was fine. My professor had already shown us the structure for serial communication, so I kept following it, creating and recreating the same code over and over again, but it just wouldn’t connect.

It got really frustrating at one point because I had everything wired correctly, and my code looked right, but the Arduino and p5 still weren’t talking to each other. I spent a lot of time trying to figure out what was wrong. Once I finally switched to the right browser and saw the serial connection actually working, it was such a relief. The LED started responding, and it felt like everything finally came together. After so many attempts, seeing both the Arduino and p5.js working together perfectly was honestly so rewarding. I was really proud of how it turned out in the end it looked simple but worked exactly how I wanted it to. All that frustration was worth it because the final design turned out really good, and it felt amazing to watch it finally come to life.

Exercise 3: Make the LED light up when the ball bounces

Group Member: Mukhlisa

Video:

b52d0a23-cf7d-4cf2-81e1-e2255dc62cad

Ardunio Code:

int potPin = A5;   // Potentiometer
int ledPin = 3;    // LED on PWM pin

void setup() {
  Serial.begin(9600);
  pinMode(ledPin, OUTPUT);

  // Blink LED to confirm setup
  analogWrite(ledPin, 255);
  delay(200);
  analogWrite(ledPin, 0);
}

void loop() {
  // Read potentiometer (0–1023)
  int raw = analogRead(potPin);

  // Map to PWM range (0–255)
  int brightness = map(raw, 0, 1023, 0, 255);

  // Continuously send brightness value to p5 if you need it
  Serial.println(brightness);

  // Check for serial commands from p5
  if (Serial.available()) {
    String data = Serial.readStringUntil('\n');

    // When ball hits ground → p5 sends "1,0"
    if (data == "1,0") {
      analogWrite(ledPin, brightness);  // flash with pot brightness
      delay(100);
      analogWrite(ledPin, 0);           // turn off after flash
    }
  }

  delay(30); // loop stability
}

P5js Code:

let velocity;
let gravity;
let position;
let acceleration;
let drag = 0.99;
let mass = 50;

let brightnessValue = 0; // Potentiometer value from Arduino (0–5)
let ballDropped = false;
let ledOn = false;

function setup() {
  createCanvas(640, 360);
  noFill();
  textSize(18);

  position = createVector(width / 2, 0);
  velocity = createVector(0, 0);
  acceleration = createVector(0, 0);
  gravity = createVector(0, 0.5 * mass);
}

function draw() {
  background(255);

  fill(0);
  if (!ballDropped) {
    text("Press D to drop the ball", 20, 30);
    text("Press Space Bar to select Serial Port", 20, 50);
    return;
  }

  if (serialActive) {
    text("Connected", 20, 30);
    text(`Potentiometer: ${brightnessValue}`, 20, 50);
  } else {
    text("Serial Port Not Connected", 20, 30);
  }

  // Gravity only (no wind)
  applyForce(gravity);

  // Update ball
  velocity.add(acceleration);
  velocity.mult(drag);
  position.add(velocity);
  acceleration.mult(0);

  // Draw ball
  ellipse(position.x, position.y, mass, mass);

  // Bounce
  if (position.y >= height - mass / 2) {
    velocity.y *= -0.9;
    position.y = height - mass / 2;

    // Tell Arduino: turn LED on briefly
    if (serialActive && !ledOn) {
      writeSerial("1,0\n");
      ledOn = true;
    }
  } else if (ledOn) {
    // Tell Arduino: turn LED off
    writeSerial("0,0\n");
    ledOn = false;
  }
}

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

// Serial setup and drop ball
function keyPressed() {
  if (key == " ") setUpSerial();
  if (key == "D" || key == "d") dropBall();
}

function dropBall() {
  position.set(width / 2, 0);
  velocity.set(0, 0);
  mass = 50;
  gravity = createVector(0, 0.5 * mass);
  ballDropped = true;
}

// Read data from Arduino
function readSerial(data) {
  if (data != null) {
    let fromArduino = split(trim(data), ",");
    if (fromArduino.length === 1) {
      brightnessValue = int(fromArduino[0]); // Potentiometer value
    }
  }
}

Schematic:

Reflection:

(Mukhlisa) For this project, I combined both physical computing and digital simulation by connecting an Arduino circuit to a p5.js sketch. My setup included a potentiometer to control the wind force in the animation and an LED that lit up every time the falling ball hit the ground. I built a simple circuit using one LED, a resistor, and a 10kΩ potentiometer, and then connected it to my computer through serial communication. Even though the hardware was straightforward, the real challenge came from getting the Arduino and p5.js to communicate properly. I spent a lot of time testing the potentiometer readings, debugging the serial connection, and making sure the LED responded at the right moment in the animation.

Week 11 – Production

Task 1: make something that uses only one sensor on Arduino and makes the ellipse in p5 move on the horizontal axis, in the middle of the screen, and nothing on arduino is controlled by p5.

Asma built a simple circuit using an Arduino and a 10kΩ potentiometer to control an on-screen ellipse in p5.js. She connected the potentiometer’s outer legs to 5V and GND and the middle leg to the analog pin A0, allowing it to act as a variable voltage divider. After uploading the Arduino code and checking the serial monitor, she could see how turning the knob changed the analog readings from 0 to 1023. This helped her understand how analog sensors translate physical movement into numerical data that can be visualized digitally.

Arduino code:

const int potPin = A0;

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

void loop() {
  int val = analogRead(potPin);   // 0..1023
  Serial.println(val);            
  delay(10);                     
}

P5.js codę:

// === Arduino + p5.js WebSerial (directional movement, fixed) ===
// Pot controls direction/speed by how its value changes. p5 does NOT control Arduino.

let port;
let reader;
let connectButton;
let isConnected = false;

let latestData = null; // last parsed int from serial (0..1023)
let prevData   = null; // previous sample to compute delta
let lineBuffer = '';   // accumulate serial chunks until '\n'

let posX = 0;          // ellipse position
let speed = 0;         // horizontal velocity

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(240);
  textFont('monospace');

  connectButton = createButton('Connect to Arduino');
  connectButton.position(20, 20);
  connectButton.mousePressed(connectToSerial);

  // start centered; we'll keep it centered until first data arrives
  posX = width / 2;
}

async function connectToSerial() {
  try {
    // Request and open port
    port = await navigator.serial.requestPort();
    await port.open({ baudRate: 9600 });
    isConnected = true;
    console.log(' Port opened');

    // Create a text decoder stream and reader for clean line-by-line reads
    const textDecoder = new TextDecoderStream();
    const readableClosed = port.readable.pipeTo(textDecoder.writable);
    reader = textDecoder.readable.getReader();

    // Kick off read loop
    readSerialLines();
  } catch (err) {
    console.error(' Connection failed:', err);
    isConnected = false;
  }
}

async function readSerialLines() {
  try {
    while (true) {
      const { value, done } = await reader.read();
      if (done) break; // reader released
      if (!value) continue;

      // Accumulate and split by newline
      lineBuffer += value;
      let lines = lineBuffer.split(/\r?\n/);
      lineBuffer = lines.pop(); // save incomplete tail

      for (let line of lines) {
        line = line.trim();
        if (!line) continue;
        const v = parseInt(line, 10);
        if (!Number.isNaN(v)) {
          // Clamp to expected 10-bit range
          latestData = Math.min(Math.max(v, 0), 1023);
          // Initialize prevData on first valid sample
          if (prevData === null) prevData = latestData;
        }
      }
    }
  } catch (err) {
    console.error(' Read error:', err);
  } finally {
    try { reader && reader.releaseLock(); } catch {}
  }
}

function draw() {
  background(240);

  if (!isConnected) {
    fill(200, 0, 0);
    noStroke();
    textAlign(CENTER, CENTER);
    textSize(20);
    text("Click 'Connect to Arduino' to begin", width / 2, height / 2);
    return;
  }

  // If we haven't received any valid data yet, show waiting status
  if (latestData === null || prevData === null) {
    fill(0);
    textSize(16);
    textAlign(LEFT, TOP);
    text('Waiting for data...', 20, 60);
    // Keep ellipse centered until first data arrives
  } else {
    // Change in pot reading determines direction and speed bump
    const delta = latestData - prevData;

    // Deadband to ignore small noise
    const deadband = 4;
    if (delta > deadband) {
      speed = constrain(speed + 0.6, -12, 12); // turn right -> move right
    } else if (delta < -deadband) {
      speed = constrain(speed - 0.6, -12, 12); // turn left -> move left
    } else {
      // friction when knob still
      speed *= 0.90;
    }

    // Integrate position and clamp
    posX += speed;
    posX = constrain(posX, 0, width);

    // Update prev for next frame
    prevData = latestData;
  }

  // Draw ellipse at vertical center
  noStroke();
  fill(50, 100, 255);
  ellipse(posX, height / 2, 80, 80);

  // HUD
  fill(0);
  textSize(14);
  textAlign(LEFT, TOP);
  const shown = latestData === null ? '—' : latestData;
  text(⁠ Sensor: ${shown} ⁠, 20, 60);
  text(⁠ Speed:  ${nf(speed, 1, 2)} ⁠, 20, 80);
}

function windowResized() {
  resizeCanvas(windowWidth, windowHeight);
  // Keep position on-screen if you resize smaller
  posX = constrain(posX, 0, width);
}

Schematic:

 

Task 2: make something that controls the LED brightness from p5

Hajar created a simple schematic and circuit using one LED and one resistor connected to the Arduino. Since the main goal was to control the LED’s brightness through serial communication with p5.js, she kept the hardware minimal and focused more on the coding. She first tested the LED with a basic Arduino sketch to make sure everything worked, then created the schematic. Although the circuit was simple and familiar, it still showed her how even a basic setup can become interactive when combined with p5.js.

Arduino code:

const int ledPin = 10;  // LED connected to pin 10

void setup() {
  Serial.begin(9600);   // must match p5.js baud rate
  pinMode(ledPin, OUTPUT);
}

void loop() {
  if (Serial.available() > 0) {
    int brightness = Serial.read();      // read 0–255
    brightness = constrain(brightness, 0, 255);
    analogWrite(ledPin, brightness);     // control LED brightness
  }
}

P5.js codę:

Sketch:

Task 3: take the gravity wind example and make it so every time the ball bounces one led lights up and then turns off, and you can control the wind from one analog sensor

For this project, I combined both physical computing and digital simulation by connecting an Arduino circuit to a p5.js sketch. My setup included a potentiometer to control the wind force in the animation and an LED that lit up every time the falling ball hit the ground. I built a simple circuit using one LED, a resistor, and a 10kΩ potentiometer, and then connected it to my computer through serial communication. Even though the hardware was straightforward, the real challenge came from getting the Arduino and p5.js to communicate properly. I spent a lot of time testing the potentiometer readings, debugging the serial connection, and making sure the LED responded at the right moment in the animation.

P5.js code:

let velocity;
let gravity;
let position;
let acceleration;
let drag = 0.99;
let mass = 50;

let brightnessValue = 0; // Potentiometer value from Arduino (0–5)
let ballDropped = false;
let ledOn = false;

function setup() {
  createCanvas(640, 360);
  noFill();
  textSize(18);

  position = createVector(width / 2, 0);
  velocity = createVector(0, 0);
  acceleration = createVector(0, 0);
  gravity = createVector(0, 0.5 * mass);
}

function draw() {
  background(255);

  fill(0);
  if (!ballDropped) {
    text("Press D to drop the ball", 20, 30);
    text("Press Space Bar to select Serial Port", 20, 50);
    return;
  }

  if (serialActive) {
    text("Connected", 20, 30);
    text(`Potentiometer: ${brightnessValue}`, 20, 50);
  } else {
    text("Serial Port Not Connected", 20, 30);
  }

  // Gravity only (no wind)
  applyForce(gravity);

  // Update ball
  velocity.add(acceleration);
  velocity.mult(drag);
  position.add(velocity);
  acceleration.mult(0);

  // Draw ball
  ellipse(position.x, position.y, mass, mass);

  // Bounce
  if (position.y >= height - mass / 2) {
    velocity.y *= -0.9;
    position.y = height - mass / 2;

    // Tell Arduino: turn LED on briefly
    if (serialActive && !ledOn) {
      writeSerial("1,0\n");
      ledOn = true;
    }
  } else if (ledOn) {
    // Tell Arduino: turn LED off
    writeSerial("0,0\n");
    ledOn = false;
  }
}

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

// Serial setup and drop ball
function keyPressed() {
  if (key == " ") setUpSerial();
  if (key == "D" || key == "d") dropBall();
}

function dropBall() {
  position.set(width / 2, 0);
  velocity.set(0, 0);
  mass = 50;
  gravity = createVector(0, 0.5 * mass);
  ballDropped = true;
}

// Read data from Arduino
function readSerial(data) {
  if (data != null) {
    let fromArduino = split(trim(data), ",");
    if (fromArduino.length === 1) {
      brightnessValue = int(fromArduino[0]); // Potentiometer value
    }
  }
}

Arduino code:

int potPin = A5;   
int ledPin = 3;    

void setup() {
  Serial.begin(9600);
  pinMode(ledPin, OUTPUT);

  analogWrite(ledPin, 255);
  delay(200);
  analogWrite(ledPin, 0);
}

void loop() {
  int raw = analogRead(potPin);

  int brightness = map(raw, 0, 1023, 0, 255);

  Serial.println(brightness);

  // Check for serial commands from p5
  if (Serial.available()) {
    String data = Serial.readStringUntil('\n');

    if (data == "1,0") {
      analogWrite(ledPin, brightness);  // flash with pot brightness
      delay(100);
      analogWrite(ledPin, 0);           // turn off after flash
    }
  }

  delay(30); 
}

Sketch:

Video:

IMG_9469

Week 11: Production

Group Member: Aditi

Task 1: make something that uses only one sensor on Arduino and makes the ellipse in p5 move on the horizontal axis, in the middle of the screen, and nothing on arduino is controlled by p5.

We used a potentiometer as the analog sensor on Arduino to move the ellipse on the horizontal axis.

p5js code:

// Serial port object
let port;

// Latest sensor value
let sensorVal = 0;

function setup() {
  createCanvas(400, 300);
  background(220);

  // Create the serial connection object
  port = createSerial();

  // If a port was used before, auto-reconnect
  let used = usedSerialPorts();
  if (used.length > 0) {
    port.open(used[0], 9600);
  }
}

function draw() {
  background(220);

  // Read one line of text until newline "\n"
  let str = port.readUntil("\n");

  // Make sure we actually received something
  if (str.length > 0) {

    // Convert the string into an integer
    let val = int(str.trim());

    // Map sensor value (0–1023) to screen X position (0–400)
    let x = map(val, 0, 1023, 0, width);

    // Draw a circle at mapped position
    ellipse(x, height / 2, 40, 40);

  } else {

    // If empty data is received, print it for debugging
    console.log("Empty:", str);
  }
}

Arduino code:

const int sensor=A2;
int sensorValue;
void setup() {
  // put your setup code here, to run once:
  pinMode(sensor,INPUT);
  Serial.begin(9600);
  Serial.println(0); // send a starting message
}

void loop() {
  // put your main code here, to run repeatedly:
  sensorValue=analogRead(sensor);
  delay(60);
  Serial.println(sensorValue);  
}

Task 2: make something that controls the LED brightness from p5.

We decided to control the LED’s brightness through four buttons on p5js.

p5js code:

let port;
let brightnessToSend = 0;
const baudrate = 9600;

function setup() {
  createCanvas(400, 400);

  // Create the serial port
  port = createSerial();

  // connect automatically if used before
  let usedPorts = usedSerialPorts();
  if (usedPorts.length > 0) {
    port.open(usedPorts[0], baudrate);
  } else {
    connectBtn = createButton("Connect to Serial");
    connectBtn.position(10, 10);
    connectBtn.mousePressed(() => port.open(baudrate));
  }
}

function draw() {
  background(30);

  drawStarButton(80, 150, 40, 20, 5, 64);
  drawStarButton(160, 150, 40, 20, 5, 128);
  drawStarButton(240, 150, 40, 20, 5, 192);
  drawStarButton(320, 150, 40, 20, 5, 255);

  fill(255);
  textAlign(CENTER);
  text("Current brightness: " + brightnessToSend, width / 2, 280);
}

function mousePressed() {
  if (!port.opened()) return; // p5.webserial function to check if open

  let stars = [
    { x: 80,  brightness: 64  },
    { x: 160, brightness: 128 },
    { x: 240, brightness: 192 },
    { x: 320, brightness: 255 }
  ];

  for (let s of stars) {
    if (dist(mouseX, mouseY, s.x, 150) < 30) {
      brightnessToSend = s.brightness;

      // Send brightness (0–255)
      port.write(brightnessToSend);
    }
  }
}

function drawStarButton(x, y, radius1, radius2, points, brightness) {
  let angle = TWO_PI / points;
  let halfAngle = angle / 2;

  if (brightnessToSend === brightness) fill(255, 200, 0);
  else fill(255);

  push();
  translate(x, y);
  beginShape();
  for (let a = 0; a < TWO_PI; a += angle) {
    vertex(cos(a) * radius1, sin(a) * radius1);
    vertex(cos(a + halfAngle) * radius2, sin(a + halfAngle) * radius2);
  }
  
  endShape(CLOSE);
  pop();
}

Arduino code:

int ledPin = 9;

void setup() {
  Serial.begin(9600);
  pinMode(ledPin, OUTPUT);
}

void loop() {
  if (Serial.available() > 0) {
    int brightness = Serial.read();  // 0–255
    analogWrite(ledPin, brightness); // PWM LED brightness
  }
}

Video:

Task 3: take the gravity wind example and make it so every time the ball bounces one led lights up and then turns off, and you can control the wind from one analog sensor.

p5js code:

let velocity;        // velocity vector of the ball
let gravity;         // gravity force vector
let position;        // position of the ball
let acceleration;    // accumulated acceleration each frame
let wind;            // wind force vector
let drag = 0.99;     // drag factor to slow down velocity a bit
let mass = 50;       // "mass" of the ball, also used as its size
let port;            // serial port object for Arduino ↔ p5 communication
let button;          // button to trigger serial connection popup
let open=false;      // flag to track if we've tried to open the port

function setup() {
  createCanvas(640, 360);
  noFill();
  position = createVector(width/2, 0);   // start ball at top center
  velocity = createVector(0,0);          // start with no movement
  acceleration = createVector(0,0);      // acceleration accumulates forces
  gravity = createVector(0, 0.5*mass);   // gravity pulls ball downward
  wind = createVector(0,0);              // wind starts with no force
  port = createSerial(); //port is an object that will be used to send message and receeieve message from arudnio
 
  //createbutton creates button in p5js with quote
  button=createButton("Connect to Arduino");
  button.position(width/2,height/2); //where you want your button to be
  button.mousePressed(openArduino); 
}

function openArduino(){
  if(!port.opened()){ //to check if the port is opened or not we use function opened
    port.open(9600) // we open the port for communication with 9600 frequency
   
    button.remove() // once arudino is opended we removed the button to connect to ardunio
   
    open=true; //just a variable to check if we opended the port or not
  } 
}

function draw() {
  if(port.opened()){             // run main logic only if serial port is open
  background(255);               // clear canvas every frame
  applyForce(wind);              // apply wind force first
  applyForce(gravity);           // then apply gravity force
  velocity.add(acceleration);    // a → v
  velocity.mult(drag);           // apply drag to slowly reduce velocity
  position.add(velocity);        // v → position
  acceleration.mult(0);          // reset acceleration so next frame starts fresh
  ellipse(position.x,position.y,mass,mass);  // draw the ball
 
  }

  // collision + LED control are outside the port.opened() block,
  // so they always run based on current position
  if (position.y > height-mass/2) {     
      velocity.y *= -0.9;         // A little dampening when hitting the bottom (bounce)
      position.y = height-mass/2; // keep ball from sinking below the "floor"
      port.write("1\n")           // send "1" to Arduino → turn LED ON
    }
    else {
      port.write("0\n")    // otherwise send "0" → turn LED OFF
    }
    potvalue=port.readUntil("\n");  // read a line from Arduino (pot value as text)
    // console.log(potvalue);
 
    if(potvalue>514){      // simple threshold: above midpoint = wind to the right
      wind.x=1
    }
    else{                  // below midpoint = wind to the left
      wind.x=-1
    } 
}

function applyForce(force){
  // Newton's 2nd law: F = M * A
  // or A = F / M
  let f = p5.Vector.div(force, mass);  // scale force by mass → acceleration
  acceleration.add(f);                 // accumulate acceleration for this frame
}

function keyPressed(){
  if (keyCode==LEFT_ARROW){
    wind.x=-1;              // keyboard override: push ball left
  }
  if (keyCode==RIGHT_ARROW){
    wind.x=1;               // keyboard override: push ball right
  }
  if (key==' '){
    mass=random(15,80);     // randomize mass (and drawn size)
    position.y=-mass;       // reset ball above the top so it falls back down
    velocity.mult(0);       // clear velocity so it restarts clean
  }
}

Arduino code:

int greenLight=7;
int sensor=A0;
void setup() {
  // put your setup code here, to run once:
  pinMode(greenLight,OUTPUT);
  pinMode(sensor,INPUT);
  Serial.begin(9600);
  digitalWrite(greenLight,0);
}

void loop() {
  // put our main code here, to run repeatedly:
  // Serial.print("0");
 
  int value= analogRead(sensor);
  Serial.print(value);
  Serial.print("\n");

  while(Serial.available()>0){ //avaiable checks if there is any message in inbox of ardino
    String a=Serial.readStringUntil('\n');
    a.trim();
    if(a=="1"){
       digitalWrite(greenLight,1);
    }
    else{
      digitalWrite(greenLight,0);
    }   
  }
}

Video:

Week 11 reading response

This text suffers from a fundamental contradiction that undermines its own arguments: while championing simplicity and challenging universal design, it romanticises elite consumer products that are themselves inaccessible to many disabled users. The iPod worship is weird, celebrating a device with no tactile feedback beyond a smooth wheel and minimal physical controls as some paragon of inclusive thinking. The author gestures at Peter and Steve’s differing preferences but never interrogates why someone like Tyler would need to feel “self-confident” using a phone in public, treating internalised ableism as personal taste rather than a design failure. The golden prosthetic hand and Aimee Mullins’s carved wooden legs are presented as liberating alternatives to discretion, yet these bespoke art objects are available only to those with money, barely a decent vision of disability design.

The tension between “fashion meets discretion” reveals the author’s shallow engagement with power dynamics in disability. Dismissing pink plastic hearing aids as shameful while celebrating expensive designer eyewear ignores that frames cost hundreds of pounds, discretion through flesh-coloured plastic may be the only affordable option for many users. The suggestion that “fashion designers should be involved as a matter of course” treats aesthetics as a universal good without acknowledging that high design often increases cost and decreases repairability.

The “flying submarine” metaphor encapsulates the text’s elitism, better to have multiple specialised and beautifully designed products than one versatile compromise. This definitely works if you can afford a wardrobe of prosthetics or a collection of single purpose devices, but it’s design thinking for the wealthy masquerading as liberation. The text never acknowledges that universal design emerged precisely because disabled people couldn’t access or afford specialised alternatives.

Shahram Chaudhry – Week 11 – Production

Task 1

I used a flex sensor to control the horizontal movement of the circle on the screen. When the sensor is bent, the circle moves horizontally. 

P5JS Code:

let port;
let sensorValue = 0;

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

function draw() {
  background(240);
  let str = port.readUntil("\n");
  if (str.length > 0) {
    sensorValue = int(str);
  }
  let x = map(sensorValue, 430, 500, 0, width);
  fill(100, 150, 255);
  ellipse(x, height / 2, 50, 50);
  fill(0);
  text("Sensor: " + sensorValue, 10, height - 20);
}

function mousePressed() {
  if (!port.opened()) {
    port.open("Arduino", 9600);
  }
}

Arduino Code:

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

void loop() {
  int sensorValue = analogRead(A0);  
  Serial.println(sensorValue);       
  delay(20);                         
}

Schematic:

Video

 

Task 2:

Moving the mouse left or right on the screen changes how bright the LED connected to the Arduino glows. As we move the mouse toward the left, the LED gets dimmer, and as we move it to the right, it gets brighter.

P5js Code:

let port;

function setup() {
  createCanvas(600, 600);
  port = createSerial();

  let usedPorts = usedSerialPorts();
  if (usedPorts.length > 0) {
    port.open(usedPorts[0], 9600);
  }
}

function draw() {
  background(220);
  if (port.opened()) {
    let brightness = map(mouseX, 0, width, 0, 255);
    port.write(int(brightness) + "\n");
    textSize(50);
    text("Brightness: " + int(brightness), 50 , height - 20);
  } else {
    text("Click to connect", 10, height - 20);
  }
} 
function mousePressed() {
  if (!port.opened()) {
    port.open("Arduino", 9600);  
  }
}

Arduino Code:

int brightness = 0; 
int input = 0; 
void setup() { 
  Serial.begin(9600); 
  pinMode(9, OUTPUT); 
} 
void loop() { 
  if (Serial.available() > 0) { 
    input = Serial.parseInt(); 
    brightness = map(input, 0, 1023, 0, 255); 
    analogWrite(9, brightness); 
  } 
}
Schematic

Video

 

 

Task 3:

I modified the gravity wind example so that an LED lights up briefly each time the ball hits the ground and bounces back. The wind that pushes the ball left or right is controlled by the potentiometer.

P5js Code: 

let port;
let sensorValue = 0;
let velocity;
let gravity;
let position;
let acceleration;
let wind;
let drag = 0.99;
let mass = 50;
let onGroundLastFrame = false;

function setup() {
  createCanvas(640, 360);
  noFill();
  port = createSerial();
  let usedPorts = usedSerialPorts();
  if (usedPorts.length > 0){
    port.open(usedPorts[0], 9600);
  }
  position = createVector(width/2, 0);
  velocity = createVector(0,0); 
  acceleration = createVector(0,0);
  gravity = createVector(0, 0.5*mass);
  wind = createVector(0,0);
}

function draw() {
  background(255);
  let incoming = port.readUntil("\n");
  if (incoming.length > 0) {
    sensorValue = int(incoming);  
  }
  wind.x = map(sensorValue, 0, 1023, -1, 1);

  applyForce(wind);
  applyForce(gravity);
  velocity.add(acceleration);
  velocity.mult(drag);
  position.add(velocity);
  acceleration.mult(0);
  ellipse(position.x,position.y,mass,mass);
  
  let onGroundNow = (position.y > height - mass / 2);

  if (onGroundNow && !onGroundLastFrame) {
    port.write("1\n"); // bounce, flash led
  } else {
    port.write("0\n");
  }
  if (onGroundNow) {
    velocity.y *= -0.9;
    position.y = height - mass / 2;
  }
  onGroundLastFrame = onGroundNow;
}

function applyForce(force){
  // Newton's 2nd law: F = M * A
  // or A = F / M
  let f = p5.Vector.div(force, mass);
  acceleration.add(f);
}

function mousePressed() {
  if (!port.opened()) {
    port.open("Arduino", 9600);
  }
}

Arduino Code:

int ledPin = 2;

void setup() {
  Serial.begin(9600);
  pinMode(ledPin, OUTPUT);
}

void loop() {
  int sensor = analogRead(A0);
  Serial.println(sensor);
  if (Serial.available() > 0) {
    int bouncesig = Serial.parseInt();
    if (bouncesig == 1) {
      digitalWrite(ledPin, HIGH);
      delay(50);
      digitalWrite(ledPin, LOW);
    }
  }
  delay(10); 
}

Schematic

 

Video

 

 

Week 11 – Preliminary for Final Project

Muiz is an interactive 3D artwork where the user can manipulate Kazakh ornament shapes (such as mүйіз, қосмүйіз, and қыңырмүйіз) using a sensor glove and head movement.

The project uses:
1. Arduino + flex sensors on a glove to read finger bends and hand gestures
2. A webcam to track the user’s head movement
3. p5.js with WEBGL to display the ornaments on a 3D canvas
Basically, the user controls and deforms existing ornament patterns in real time, like sculpting them in the air.

How It Works
Glove with Flex Sensors (Arduino)
– Several flex sensors are placed on the glove fingers.
– When the user bends a finger, the sensor value changes.
– Arduino reads these values and sends them to the computer (to p5.js) via serial communication.

These finger bends and hand gestures are used to control the ornament:
– change size
– twist the shape
– bend or “curl” the pattern
– blend between different ornament types

Head Movement: Camera Tracking
A webcam is used to track the user’s head position and movement (some libraries shown in class will be used)

Head movement can control the view in 3D space:
– turn head left/right → rotate around the ornament
– move head up/down → change vertical angle (look from above or below)
– lean forward/back → zoom in and out
It feels like walking around a floating 3D sculpture.

Initial finger mapping:
1. Index finger bend → scale (make ornament bigger/smaller)
2. Middle finger bend → twist/rotate the pattern
3. Ring finger bend → bend the pattern, make it more “curvy”
4. Small finger bend → thickness of the lines or 3D extrusion
5. Thumb + index pinch → switch ornament type (mүйіз → қосмүйіз → қыңырмүйіз)
6. Fist → reset shape to original form
7. Open hand → start a smooth animation (ornament slowly breathing, pulsing)

Week 11: Final Project Idea

For my final project, I am designing a physically interactive dual-player dance survival game that integrates both Arduino and p5.js.

At the core of the interaction is a physical wheel connected to an Arduino. As the player rotates the wheel, the Arduino continuously reads the wheel’s angular direction using potentiometer (not sure). This sensing becomes the “listening” component of the system. The Arduino then sends this data to p5.js, where it controls the movement of a circle containing two characters.

In the p5 sketch, objects fall randomly from the top of the screen, and the player must rotate the wheel to shift the characters left or right, dodging the falling obstacles. The challenge increases because the wheel controls both characters simultaneously: if either one is hit, the game ends. This creates a dynamic where the player must keep track of two positions at once.

Furthermore, to communicate from p5 to Arduino I plan on lighting a green LED light everytime the player successfully dodges obstacle and red light for when game is over.