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:
How are you doing today?
How strong would you like your coffee?
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
Lever was not properly finalized. We opted out for a doorstop that looked like a lever. It only controlled the “on” function.
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.
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.
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.
Two different pieces of wood (pine and bamboo) provided a stark contrast for the machine, but did make it look a bit off.
Scale was inserted into the machine, but could have looked more like a part of it.
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.
I will never work with 128×32 OLED displays. They break so easily. Do not recommend.
Successes
Dark stain made it all look cozy and warm.
Light bulbs in series provided the perfect mood and aesthetic. The pulley to turn them on/off was extremely satisfying.
Having different components come together has its strengths. Fixing and troubleshooting did not affect the work of other members of the project.
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);
}