Physical Computing Erkin Salmorbekov Physical Computing Erkin Salmorbekov

Steampunk Coffee Machine

Assignment

A final project for the Physical Computing class.

Final Result

A Steampunk Coffee Machine made at ITP (NYU) by Abigail Faelnar, Samuel Sords, and Erkin Salmorbekov.

  • Steampunk Coffee Machine

  • Steampunk Coffee Machine

  • Incandescent light bulbs

  • Question Sliders, Displays, and Scale

  • Cup with brass filter on top

System Diagram

What’s happening here?

Essentially there are two main parts that compose the Steampunk Coffee Machine. Left side is the physical computing side that lets the users input their choices, and the right side produces the output: coffee.

Sidenote: code will be at the very bottom of this post.

Left side

There are two Arduino Nano 33 IoTs present: one behind the machine and one along with the scale inside the machine.

Right side

A heating plate taken from a Mr. Coffee product is carefully placed and concealed within the machine. There are two tubes connected to it: one for the intake of water and for the output of water (heated by that point since it passes through the hot plate).

Top

At the top we have 4 incandescent light bulbs in series that are connected through a plug into AC outlet. They are controlled separately from everything else with a simple chain pulley that turns it on and off.

Interaction Process

User answers three questions on the left:

  1. How are you doing today?

  2. How strong would you like your coffee?

  3. Which flavor profile do you prefer?

Question one and two determine the amount of coffee that the user will receive. When answering question one: if a person is doing well, then they would get less coffee, and if they are doing bad then vice-versa. When answering question two: it’s quite straightforward – it goes from less to more.Question three determines the flavor profile of coffee that the user might want ranging from light to medium to dark.

When all the questions are answered OLED display #1 will show the amount of coffee and the type of coffee that the user should receive. User proceeds by measuring out the coffee on the scale. OLED display #2 shows the amount. A button situated just below the scale lets the user to zero out the scale in order to not count the weight of the measuring cup.

Next and final step involves the user inserting the coffee into the filter and pushing the lever down to turn the machine. After 2-3 minutes, the user can enjoy their personalized cup of joe.

Shortcomings and Challenges

  1. Lever was not properly finalized. We opted out for a doorstop that looked like a lever. It only controlled the “on” function.

  2. We used two Arduinos for both main functions. We probably could have put it all together on the same Arduino. The reason why we split them up was due to the same I2C address of the OLED displays. It is possible to use both displays with a multiplex chip, which we did not have time to purchase.

  3. Light bulbs looked amazing, but we could have probably used them for more than their aesthetic. For example, they could work as timers or as progress bars.

  4. Mr. Coffee heating plate was necessary, but in the future we would like to figure out if it’s possible (it most likely is) to heat up water and make coffee with an Arduino.

  5. Two different pieces of wood (pine and bamboo) provided a stark contrast for the machine, but did make it look a bit off.

  6. Scale was inserted into the machine, but could have looked more like a part of it.

  7. Piping system and the bucket could be more sanitary and clean. Having a proper enclosed water system is extremely important for the safety of the public. Part of the reason why we did not try to fix the piping issue. Health and sanitation is above and beyond when serving coffee.

  8. I will never work with 128×32 OLED displays. They break so easily. Do not recommend.

Successes

  1. Dark stain made it all look cozy and warm.

  2. Light bulbs in series provided the perfect mood and aesthetic. The pulley to turn them on/off was extremely satisfying.

  3. Having different components come together has its strengths. Fixing and troubleshooting did not affect the work of other members of the project.

  4. Overall Steampunk look was accomplished.

At the Winter Show 2019

Winter Show is an annual ITP tradition that allows ITPers to present their work to the members of the public. There is also a Spring Show.

Six months ago I did not even dream that I could ever present.

It was our first Winter Show and surprisingly (to me at least), it went quite well. We ran into an issue with one of the pipes not being stable, so we opted out to disconnect it and made pour-overs for our customers.

Their Observations

Something that really melts my heart: people noticed the “steampunk” in our Steampunk Coffee Machine. We knew we wanted to do something with coffee and we knew we wanted to make it Steampunk. In my personal opinion – we did not achieve the look, we simply have a vintage looking machine. The people, however, knew that it was steampunk. We have never made anything steampunk or steampunk-ish before. Having not only one, but dozens of people say that it looked steampunk really meant a lot.

Their Opinions

Honestly, and this is just my thought, I did not anticipate for this project to be a big success at the show. I don’t know if it’s the free coffee (an extremely valid bias to assume), or if it’s the looks of the machine, but people gave us overwhelmingly positive reactions. The only couple negative major remarks we got were about our pour-over style and not the machine.

We were flattered and appreciative of people’s feedback. It was so refreshing to hear opinions of people who did not know that it was an Arduino running things behind the scenes. This is not meant as disrespect to our classmates or professors, but rather as an observation of how not knowing the components and the process alters people’s opinion about the machine.

Kids loved the look of the machine. Shiny bulbs, vintage look, and overall physicality attracted them it seems. One kid wanted his father to purchase the machine, the father replied with, “We cannot, it’s one of a kind”.

Overall, I learned at this Winter Show that no matter what, presenting the product to the outside world of ITP is absolutely invaluable.

Thank you to everyone who has taken the time to use our machine and enjoy our cups of coffee.

Performance

Unexpectedly, our project became a sort of performance piece. I have strong passion for coffee. I dream of retiring one day and opening up my own coffee shop. Serving people coffee was an honour. Yes, honor with an ‘u’, because it’s more fancy that way. British.

Conclusion

What’s next?

We talked about this and we think that we can probably make a more unified experience in the future. Despite many problems, we enjoyed making this machine. We stayed up many nights laboring over it and giving it our all. So many cups of coffee were drank. So many. This machine can look and work better in the future. Instead of having two AC plugs and two USBs we can have one AC or one USB perhaps.

Thank you

First and foremost, thank you to our Professor Tom Igoe. His input, guidance, and teachings is what made it possible for us to make something like this. I will be the first one to admit that it’s not a perfect machine, but eight months ago I was teaching English in Japan and had no idea about Arduinos and had no idea how to make a scale. Beyond this particular product I learned how to read code in Arduino and became familiar with wiring actual physical components beyond the theory. To be able to troubleshoot and approach problems is a valuable skill not just in physical computing, but in life. It teaches you patience. I also learned how to talk about product design, something that I have never done before, and how to discuss interactivity – the core of ITP. I thought about these concepts before, but never really had the chance to explore them in an intellectual capacity. Professor Igoe not only answered any question that I had, but also directed me to the correct path. He created such an environment for all students of our Fall 2019 Physical Computing class. I am sure I will look back on this period and seek guidance from my past for my future.

Thank you to Abigail Faelnar and Sammy Sords. Best partners for a project ever. They were patient with me and our project. They taught me not to overreact in times of trouble and calmed me down whenever I felt nervous or anxious. They appreciate coffee as much as I do and that really means a lot to me. They saw my vision of a lever and made it all happen. I think we learned a lot along the way together. During the project both partners were resourceful, quick-witted, funny, extremely pleasant, charming, and just all around jolly. Working with them is a great time. Not once have I felt that I’d rather be somewhere else.

Finally, thank you to our classmates who helped me with the code and members of the public at the Winter Show for their feedback.

Code for the scale

// Load Cell part
#include "HX711.h"

#define calibration_factor 867 //This value is obtained using the SparkFun_HX711_Calibration sketch

#define DOUT  3
#define CLK  2
const int buttonPin = 8;
int buttonState = 0;
HX711 scale;

// Adafruit Display Part
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);


void setup() {
  pinMode(buttonPin, INPUT);
  Serial.begin(9600);
  // Scale part
  //
  //
  scale.begin(DOUT, CLK);
  scale.set_scale(calibration_factor); //This value is obtained by using the SparkFun_HX711_Calibration sketch
  scale.tare(); //Assuming there is no weight on the scale at start up, reset the scale to 0

  //OLED part
  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3C for 128x32
    Serial.println(F("SSD1306 allocation failed"));
    for (;;); // Don't proceed, loop forever
  }

}

void loop() {
  //button to ZERO it out
  if(digitalRead(buttonPin)){
    scale.tare();
    delay(1000);
  }
  // put your main code here, to run repeatedly:
  Serial.println(digitalRead(buttonPin));
  Serial.print("Reading: ");
  Serial.print(scale.get_units(), 1); //scale.get_units() returns a float
  Serial.print(" grams"); //You can change this to kg but you'll need to refactor the calibration_factor
  Serial.println();
  // the oled part

  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(14,12);

  float currentWeight = scale.get_units();
  if (currentWeight < 0.0 && currentWeight > -1.0)
    currentWeight = 0; 
    
  display.print(currentWeight, 0);
  display.print(" grams");
  display.display();
  
}

Code for the questions

const int Q1 = A0, Q2 = A2, Q3 = A3;

int SensorVal1 = 0, SensorVal2 = 0, SensorVal3 = 0, onDelay = 500;

float q1Level = 0, q2Level = 0, q3Level = 0;

#include <Wire.h>

#include <Adafruit_GFX.h>

#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels

#define SCREEN_HEIGHT 32 // OLED display height, in pixels

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

void setup() {

Serial.begin(115200);

if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // Address 0x3D for 128x64

Serial.println(F("SSD1306 allocation failed"));

for(;;);

}

pinMode(Q1, INPUT);

pinMode(Q2, INPUT);

pinMode(Q3, INPUT);

}

void loop() {

SensorVal1 = analogRead(Q1);

SensorVal2 = analogRead(Q2);

SensorVal3 = analogRead(Q3);

q1Level = map(SensorVal1, 0, 800, 1, 3);

q2Level = map(SensorVal2, 0, 100, 1, 3);

q3Level = map(SensorVal3, 0, 850, 1, 3);

Serial.print("Value 1 = ");

Serial.print(SensorVal1);

Serial.print("\t Q1 = ");

Serial.print(q1Level);

Serial.print("\t Value 2 = ");

Serial.print(SensorVal2);

Serial.print("\t Q2 = ");

Serial.print(q2Level);

Serial.print("\t Value 3 = ");

Serial.print(SensorVal3);

Serial.print("\t Q3 = ");

Serial.println(q3Level);

// Output #1

if (q1Level == 1 && q2Level == 1 && q3Level == 1) {

display.clearDisplay();

display.setTextSize(2);

display.setTextColor(WHITE);

display.setCursor(0, 10);

display.println("Rwanda/12g"); // Answers #1: (1) :), Answer #2: (1) Mexico; Answer #3: (1) Light

display.display();

}

// Output #2

if (q1Level == 1 && q2Level == 1 && q3Level == 2) {

display.clearDisplay();

display.setTextSize(2);

display.setTextColor(WHITE);

display.setCursor(0, 10);

display.println("Gtmala/12g"); // Answers #1: (1) :), Answer #2: (1) Mexico; Answer #3: (2) Dark

display.display();

}

// Output #3

if (q1Level == 1 && q2Level == 1 && q3Level == 3) {

display.clearDisplay();

display.setTextSize(2);

display.setTextColor(WHITE);

display.setCursor(0, 10);

display.println("Sumatra/12g"); // Answers #1: (1) :), Answer #2: (1) Mexico; Answer #3: (3) Medium

display.display();

}

// Output #4

if (q1Level == 1 && q2Level == 2 && q3Level == 1) {

display.clearDisplay();

display.setTextSize(2);

display.setTextColor(WHITE);

display.setCursor(0, 10);

display.println("Rwanda/14g"); // Answers #1: (1) :), Answer #2: (2) Balanced; Answer #3: (1) Light

display.display();

}

// Output #5

if (q1Level == 1 && q2Level == 2 && q3Level == 2) {

display.clearDisplay();

display.setTextSize(2);

display.setTextColor(WHITE);

display.setCursor(0, 10);

display.println("Gtmala/14g"); // Answers #1: (1) :), Answer #2: (2) Balanced; Answer #3: (2) Dark

display.display();

}

// Output #6

if (q1Level == 1 && q2Level == 2 && q3Level == 3) {

display.clearDisplay();

display.setTextSize(2);

display.setTextColor(WHITE);

display.setCursor(0, 10);

display.println("Sumatra/14g"); // Answers #1: (1) :), Answer #2: (2) Balanced; Answer #3: (3) Medium

display.display();

}

// Output #7

if (q1Level == 1 && q2Level == 3 && q3Level == 1) {

display.clearDisplay();

display.setTextSize(2);

display.setTextColor(WHITE);

display.setCursor(0, 10);

display.println("Rwanda/15g"); // Answers #1: (1) :), Answer #2: (3) Bold; Answer #3: (1) Light

display.display();

}

// Output #8

if (q1Level == 1 && q2Level == 3 && q3Level == 2) {

display.clearDisplay();

display.setTextSize(2);

display.setTextColor(WHITE);

display.setCursor(0, 10);

display.println("Gtmala/15g"); // Answers #1: (1) :), Answer #2: (3) Bold; Answer #3: (2) Dark

display.display();

}

// Output #9

if (q1Level == 1 && q2Level == 3 && q3Level == 3) {

display.clearDisplay();

display.setTextSize(2);

display.setTextColor(WHITE);

display.setCursor(0, 10);

display.setCursor(0, 10);

display.println("Sumatra/15g"); // Answers #1: (1) :), Answer #2: (3) Bold; Answer #3: (3) Medium

display.display();

}

// Output #10

if (q1Level == 2 && q2Level == 1 && q3Level == 1) {

display.clearDisplay();

display.setTextSize(2);

display.setTextColor(WHITE);

display.setCursor(0, 10);

display.println("Rwanda/14g"); // Answers #1: (2) :|, Answer #2: (1) Mexico; Answer #3: (1) Light

display.display();

}

// Output #11

if (q1Level == 2 && q2Level == 1 && q3Level == 2) {

display.clearDisplay();

display.setTextSize(2);

display.setTextColor(WHITE);

display.setCursor(0, 10);

display.println("Gtmala/14g"); // Answers #1: (2) :|, Answer #2: (1) Mexico; Answer #3: (2) Dark

display.display();

}

// Output #12

if (q1Level == 2 && q2Level == 1 && q3Level == 3) {

display.clearDisplay();

display.setTextSize(2);

display.setTextColor(WHITE);

display.setCursor(0, 10);

display.println("Sumatra/14g"); // Answers #1: (2) :|, Answer #2: (1) Mexico; Answer #3: (3) Medium

display.display();

}

// Output #13

if (q1Level == 2 && q2Level == 2 && q3Level == 1) {

display.clearDisplay();

display.setTextSize(2);

display.setTextColor(WHITE);

display.setCursor(0, 10);

display.println("Rwanda/15g"); // Answers #1: (2) :|, Answer #2: (2) Balanced; Answer #3: (1) Light

display.display();

}

// Output #14

if (q1Level == 2 && q2Level == 2 && q3Level == 2) {

display.clearDisplay();

display.setTextSize(2);

display.setTextColor(WHITE);

display.setCursor(0, 10);

display.println("Gtmala/15g"); // Answers #1: (2) :|, Answer #2: (2) Balanced; Answer #3: (2) Dark

display.display();

}

// Output #15

if (q1Level == 2 && q2Level == 2 && q3Level == 3) {

display.clearDisplay();

display.setTextSize(2);

display.setTextColor(WHITE);

display.setCursor(0, 10);

display.println("Sumatra/15g"); // Answers #1: (2) :|, Answer #2: (2) Balanced; Answer #3: (3) Medium

display.display();

}

// Output #16

if (q1Level == 2 && q2Level == 3 && q3Level == 1) {

display.clearDisplay();

display.setTextSize(2);

display.setTextColor(WHITE);

display.setCursor(0, 10);

display.println("Rwanda/16g"); // Answers #1: (2) :|, Answer #2: (3) Bold; Answer #3: (1) Light

display.display();

}

// Output #17

if (q1Level == 2 && q2Level == 3 && q3Level == 2) {

display.clearDisplay();

display.setTextSize(2);

display.setTextColor(WHITE);

display.setCursor(0, 10);

display.println("Gtmala/16g"); // Answers #1: (2) :|, Answer #2: (3) Bold; Answer #3: (2) Dark

display.display();

}

// Output #18

if (q1Level == 2 && q2Level == 3 && q3Level == 3) {

display.clearDisplay();

display.setTextSize(2);

display.setTextColor(WHITE);

display.setCursor(0, 10);

display.println("Sumatra/16g"); // Answers #1: (2) :|, Answer #2: (3) Bold; Answer #3: (3) Medium

display.display();

}

// Output #19

if (q1Level == 3 && q2Level == 1 && q3Level == 1) {

display.clearDisplay();

display.setTextSize(2);

display.setTextColor(WHITE);

display.setCursor(0, 10);

display.println("Rwanda/15g"); // Answers #1: (3) :(, Answer #2: (1) Mexico; Answer #3: (1) Light

display.display();

}

// Output #20

if (q1Level == 3 && q2Level == 1 && q3Level == 2) {

display.clearDisplay();

display.setTextSize(2);

display.setTextColor(WHITE);

display.setCursor(0, 10);

display.println("Gtmala/15g"); // Answers #1: (3) :(, Answer #2: (1) Mexico; Answer #3: (2) Dark

display.display();

}

// Output #21

if (q1Level == 3 && q2Level == 1 && q3Level == 3) {

display.clearDisplay();

display.setTextSize(2);

display.setTextColor(WHITE);

display.setCursor(0, 10);

display.println("Sumatra/15g"); // Answers #1: (3) :(, Answer #2: (1) Mexico; Answer #3: (3) Medium

display.display();

}

// Output #22

if (q1Level == 3 && q2Level == 2 && q3Level == 1) {

display.clearDisplay();

display.setTextSize(2);

display.setTextColor(WHITE);

display.setCursor(0, 10);

display.println("Rwanda/16g"); // Answers #1: (3) :(, Answer #2: (2) Balanced; Answer #3: (1) Light

display.display();

}

// Output #23

if (q1Level == 3 && q2Level == 2 && q3Level == 2) {

display.clearDisplay();

display.setTextSize(2);

display.setTextColor(WHITE);

display.setCursor(0, 10);

display.println("Gtmala/16g"); // Answers #1: (3) :(, Answer #2: (2) Balanced; Answer #3: (2) Dark

display.display();

}

// Output #24

if (q1Level == 3 && q2Level == 2 && q3Level == 3) {

display.clearDisplay();

display.setTextSize(2);

display.setTextColor(WHITE);

display.setCursor(0, 10);

display.println("Sumatra/16g"); // Answers #1: (3) :(, Answer #2: (2) Balanced; Answer #3: (3) Medium

display.display();

}

// Output #25

if (q1Level == 3 && q2Level == 3 && q3Level == 1) {

display.clearDisplay();

display.setTextSize(2);

display.setTextColor(WHITE);

display.setCursor(0, 10);

display.println("Rwanda/18g"); // Answers #1: (3) :(, Answer #2: (3) Bold; Answer #3: (1) Light

display.display();

}

// Output #26

if (q1Level == 3 && q2Level == 3 && q3Level == 2) {

display.clearDisplay();

display.setTextSize(2);

display.setTextColor(WHITE);

display.setCursor(0, 10);

display.println("Gtmala/18g"); // Answers #1: (3) :(, Answer #2: (3) Bold; Answer #3: (2) Dark

display.display();

}

// Output #27

if (q1Level == 3 && q2Level == 3 && q3Level == 3) {

display.clearDisplay();

display.setTextSize(2);

display.setTextColor(WHITE);

display.setCursor(0, 10);

display.println("Sumatra/18g"); // Answers #1: (3) :(, Answer #2: (3) Bold; Answer #3: (3) Monaco

display.display();

}

delay(onDelay);

}



Read More
Animations Erkin Salmorbekov Animations Erkin Salmorbekov

Maze-CRAZE

Assignment

To make a final project with Unity: an AR experience or a game.

Short demo of the Maze

Brainstorming

My partner Vanessa and I initially wanted to make a ping pong ball that would have someone’s face on it. We ran into many problems with Vuforia, an extension/plug-in component for Unity that makes AR possible, that we ended up giving up on our idea. One of the problems was importing a 3D scan of a model into Vuforia and then Unity. Just a lot of issues from the shape of the model to its coloring and lighting.

We tried to rethink our strategy and we knew that we didn’t want to simply make something appear on a metro card. We thought of maybe making a portal of some sort, but did not have any solid idea. We asked ourselves “ok, clearly this is not working out for us, so what can we accomplish that has nothing to do with AR?” We stared at the example scene that we had on hand and then it clicked, “Let’s make a game!”

We have never made a game before and since we are at ITP – why not? Due to our lack of experience and a short amount of time, we have decided to make conditions to win “physical”, meaning directions simply written in words. A hunt for an object akin to a real world scavenger hunt came to mind, so we made a maze game in which you have to look for a cat. We looked for an ugly picture of a cat, because Vanessa has a wild sense of humor and I like Vanessa’s antics.

Process

I have prior experience with Valve’s Hammer Tool, so Unity’s interface was not too wild for me. I knew how to work the cameras and how to position objects in 3D space.

Vanessa took care of the scripts and did research along the way for textures, assets, music, and memes.

I was pleasantly delighted with our teamwork. We never ran into any idea issues and naturally split up the work.

Challenges

Main challenge for me personally was figuring out the terrain and making sure that the player does not get motion sickness or dizziness of any kind.

Apparently the size of the terrain needs to be adjusted in the settings of the terrain and not manually, which in hindsight makes a lot of sense. At the time, however, it took quite a while understanding the terrain system.

As for figuring the problem with motion sickness, adding different textures and making the terrain non-linear helped a lot.

Conclusion

I think that I finally found something that I really enjoy making: video games. I hope that this is a start of something greater to come. I love video games and complex map layouts. Making it myself filled me with pride and a sense of accomplishment.

Read More