Week 14. Interface and Application Programming
Before the Start
Introduction 🖊
This week belongs to programming. Bas and Bente will introduce different ways of integrating interfaces with Arduino and our Hello World boards.
Fab Assignments 📚
-
Group assignment:
- Compare as many tool options as possible.
-
Individual assignment:
- Write an application that interfaces a user with an input and/or output device that you made.
My Goals 🎯
-
What I think I already know:
- I have experience designing interfaces as graphic designer, not ad developer.
- I had a taste of interfacing due to the first week of the FabAcademy working with Hugo and developing our own websites.
-
What I want to learn:
- Basics about developing interfaces.
- I am very interested in seeing what’s behind the interface integration process, and how the data collected by the input of our projects can be shown in a dynamic way.
Project Management
How I’m going to organize my work during the week.
Task | Time | Day |
---|---|---|
Research | 3h | 3, may |
Group Assignment | 6h | 4, May |
Individual Assignment | 4h | 10, May |
Documentation | 1h | 10, May |
Files 📂
Name | Description | Link |
---|---|---|
File 0 | Saliva = In this code the LED blinks every time the sensor reads the threshold on 530 | Link |
File 1 | Saliva = Processing Interfacing | Link |
Research
I am working on my final project with a Heart Beat sensor. I did research the following websites in order to make the assignment and test the input for my final project:
- What is Pulse Sensor : Types & Its Applications
- PulseSensor Playground Tools
- Pulse Sensor Developers Website
And I watched the following Youtube Video tutorials:
During the Process
Results 🖖
Group Assignment Process 🏊♀️ 🏊🏾 🏊🏽♀️
Bas
Attached are the tips and codes we used together with Bas during his introduction to the topic. More info can be found on his website.
He learned us how to integrate Processing with Arduino.
Drawing a line and a Circle
line(0,0,100,100);
circle(20,40,20);
If you code this, everything is black because first processing draws the circule and secondly the back on top of it:
void setup(){
size(800,600);
}
void draw(){
circle(300,500,300);
background(0,0,0);
}
If you code this, you get the circle because it first makes the background and then the circle:
void setup(){
size(800,600);
}
void draw(){
background(0,0,0);
circle(300,500,300);
}
-
add fill using this command:
void setup(){ size(800,600); } void draw(){ background(255,0,0); circle(250,250,250); fill(500,200,0); }
-
add new circle, interesting background for each circle:
void setup(){ size(800,600); } void draw(){ background(255,0,0); circle(250,250,250); fill(500,200,0); circle(250,250,100); fill(0,0,0); }
-
add dynamic circle:
void setup(){ size(800,600); } void draw(){ background(255,0,0); circle(mouseX,mouseY,250); fill(500,300,0); }
-
add dynamic circle in the screen, independent from the mouse movement:
int green = 0xFF00FF00; float xPosition = width/2; float yPosition = height/2; void setup(){ size(1000,1000); xPosition = width/2; yPosition = height/2; } void draw(){ background(255,0,0); fill(255,0,0); circle(xPosition,yPosition,200); xPosition++; }
-
adding velocity as a parameter:
int green = 0xFF00FF00; float xPosition = width/2; float yPosition = height/2; float xVelocity = 1; float yVelocity = 1; void setup(){ size(1000,1000); //fullScreen(); xPosition = width/2; yPosition = height/2; } void draw(){ background(255,0,0); fill(255,0,0); circle(xPosition,yPosition,200); xPosition += xVelocity; yPosition += yVelocity; }
-
adding a limit on the movement in the balls in the corners of the screen:
int green = 0xFF00FF00; float xPosition = width/2; float yPosition = height/2; float xVelocity = 3; float yVelocity = 3; void setup(){ size(700,700); //fullScreen(); xPosition = width/2; yPosition = height/2; } void draw(){ background(100); fill(255,0,0); circle(xPosition,yPosition,50); xPosition += xVelocity; yPosition += yVelocity; if(xPosition > width || xPosition < 0) xVelocity = -xVelocity; if(yPosition > height || yPosition < 0) yVelocity = -yVelocity; }
-
adding gravity to the Y axes:
int green = 0xFF00FF00; float xPosition = width/2; float yPosition = height/2; float xVelocity = 3; float yVelocity = 3; void setup(){ size(700,700); //fullScreen(); xPosition = width/2; yPosition = height/2; } void draw(){ background(100); fill(255,0,0); circle(xPosition,yPosition,50); xPosition += xVelocity; yPosition += yVelocity; if(xPosition > width || xPosition < 0) xVelocity = -xVelocity; if(yPosition > height || yPosition < 0) yVelocity = -yVelocity; yVelocity += 0.5; }
-
adding gravity to the x axes:
int green = 0xFF00FF00; float xPosition = width/2; float yPosition = height/2; float xVelocity = 3; float yVelocity = 3; void setup(){ size(700,700); //fullScreen(); xPosition = width/2; yPosition = height/2; } void draw(){ background(100); fill(255,0,0); circle(xPosition,yPosition,50); xPosition += xVelocity; yPosition += yVelocity; if(xPosition > width || xPosition < 0) xVelocity = -xVelocity; if(yPosition > height || yPosition < 0) yVelocity = -yVelocity; xVelocity +=0.3; }
-
add text to the screen, locating it and adding as color white:
int green = 0xFF00FF00; float xPosition = width/2; float yPosition = height/2; float xVelocity = 3; float yVelocity = 3; void setup(){ size(700,700); //fullScreen(); xPosition = width/2; yPosition = height/2; textSize(30); } void draw(){ background(100); fill(255,0,0); circle(xPosition,yPosition,50); xPosition += xVelocity; yPosition += yVelocity; if(xPosition > width || xPosition < 0) xVelocity = -xVelocity; if(yPosition > height || yPosition < 0) yVelocity = -yVelocity; xVelocity +=0.3; fill(255); text(xPosition,20,50); }
-
add multiple balls:
ArrayList<Ball> balls = new ArrayList<Ball>(); void setup(){ size(800, 600, P2D); // Add 100 balls to the list for (int iBall=0; iBall<100; iBall++) balls.add(new Ball()); } void draw(){ // Traverse the list and update each ball for (int iBall=0; iBall<100; iBall++){ balls.get(iBall).update(); } // Use a for-each loop to draw each ball for (Ball ball : balls){ ball.draw(); } }
Individual Assignment Process 🏊♀️
Thanks to the lessons given by Bas and Bente, I found interest in learning more about processing and its design possibilities.
I started the individual assignment having fun with this software, playing around with the tutorials available on its website.
Then, I moved to research how I could use this new learning for my final project. I tested the heartbeat sensor using Processing to show in the screen the heartbeats.
Processing + Final Project 🧘🏼✨🌸
The development was easy because I could follow a few tutorials online which explain ways to integrate processing with the sensor. These are a few videos that I checked for their development:
The code used with Arduino is the following. I just needed to change the const int OUTPUT_TYPE from SERIAL_PLOTTER to PROCESSING_VISUALIZER:
// "Saliva" code by Paola Zanchetta - 29/05/2022
// Interactive system mind-plants
// In this code the LED blinks every time the sensor reads the threshold on 530
const int OUTPUT_TYPE = PROCESSING_VISUALIZER;
// Define PINs, change for PCB
int PulseSensor = 0; // INPUT = A0, Heart Sensor
int LED = 13; // OUTPUT = 13, LED to check if it is working
// Define Global Variables
int Signal; // Holds the incoming raw data. Signal value can range from 0-1024
int Threshold = 530; // Determine which Signal to "count as a beat", and which to ingore.
// The SetUp Function:
void setup() {
pinMode(LED,OUTPUT); // LED that will blink to my heartbeat
Serial.begin(9600); // Serial Communication at certain speed
}
// The Main Loop Function
void loop() {
Signal = analogRead(PulseSensor); // Reads the PulseSensor's value and assigns this value to the "Signal" variable
Serial.println(Signal); // Send the Signal value to Serial Plotter
if(Signal > Threshold){ // If the signal is above "530", then "turn-on" Arduino's on-Board LED
digitalWrite(LED,HIGH);
} else {
digitalWrite(LED,LOW); // Else, the sigal must be below "530", so "turn-off" this LED
}
delay(5);
}
I could find the code to work with Processing here:
import processing.serial.*; // serial library lets us talk to Arduino
PFont font;
PFont portsFont;
Scrollbar scaleBar;
Serial port;
int Sensor; // HOLDS PULSE SENSOR DATA FROM ARDUINO
int IBI; // HOLDS TIME BETWEN HEARTBEATS FROM ARDUINO
int BPM; // HOLDS HEART RATE VALUE FROM ARDUINO
int[] RawY; // HOLDS HEARTBEAT WAVEFORM DATA BEFORE SCALING
int[] ScaledY; // USED TO POSITION SCALED HEARTBEAT WAVEFORM
int[] rate; // USED TO POSITION BPM DATA WAVEFORM
float zoom; // USED WHEN SCALING PULSE WAVEFORM TO PULSE WINDOW
float offset; // USED WHEN SCALING PULSE WAVEFORM TO PULSE WINDOW
color eggshell = color(255, 253, 248);
int heart = 0; // This variable times the heart image 'pulse' on screen
// THESE VARIABLES DETERMINE THE SIZE OF THE DATA WINDOWS
int PulseWindowWidth = 490;
int PulseWindowHeight = 512;
int BPMWindowWidth = 180;
int BPMWindowHeight = 340;
boolean beat = false; // set when a heart beat is detected, then cleared when the BPM graph is advanced
// SERIAL PORT STUFF TO HELP YOU FIND THE CORRECT SERIAL PORT
String serialPort;
String[] serialPorts = new String[Serial.list().length];
boolean serialPortFound = false;
Radio[] button = new Radio[Serial.list().length*2];
int numPorts = serialPorts.length;
boolean refreshPorts = false;
void setup() {
size(700, 600); // Stage size
frameRate(100);
font = loadFont("Arial-BoldMT-24.vlw");
textFont(font);
textAlign(CENTER);
rectMode(CENTER);
ellipseMode(CENTER);
// Scrollbar constructor inputs: x,y,width,height,minVal,maxVal
scaleBar = new Scrollbar (400, 575, 180, 12, 0.5, 1.0); // set parameters for the scale bar
RawY = new int[PulseWindowWidth]; // initialize raw pulse waveform array
ScaledY = new int[PulseWindowWidth]; // initialize scaled pulse waveform array
rate = new int [BPMWindowWidth]; // initialize BPM waveform array
zoom = 0.75; // initialize scale of heartbeat window
// set the visualizer lines to 0
resetDataTraces();
background(0);
// DRAW OUT THE PULSE WINDOW AND BPM WINDOW RECTANGLES
drawDataWindows();
drawHeart();
// GO FIND THE ARDUINO
fill(eggshell);
text("Select Your Serial Port",245,30);
listAvailablePorts();
}
void draw() {
if(serialPortFound){
// ONLY RUN THE VISUALIZER AFTER THE PORT IS CONNECTED
background(0);
noStroke();
drawDataWindows();
drawPulseWaveform();
drawBPMwaveform();
drawHeart();
// PRINT THE DATA AND VARIABLE VALUES
fill(eggshell); // get ready to print text
text("Pulse Sensor Amped Visualizer v1.5",245,30); // tell them what you are
text("IBI " + IBI + "mS",600,585); // print the time between heartbeats in mS
text(BPM + " BPM",600,200); // print the Beats Per Minute
text("Pulse Window Scale " + nf(zoom,1,2), 150, 585); // show the current scale of Pulse Window
// DO THE SCROLLBAR THINGS
scaleBar.update (mouseX, mouseY);
scaleBar.display();
} else { // SCAN BUTTONS TO FIND THE SERIAL PORT
autoScanPorts();
if(refreshPorts){
refreshPorts = false;
drawDataWindows();
drawHeart();
listAvailablePorts();
}
for(int i=0; i<numPorts+1; i++){
button[i].overRadio(mouseX,mouseY);
button[i].displayRadio();
}
}
} //end of draw loop
void drawDataWindows(){
// DRAW OUT THE PULSE WINDOW AND BPM WINDOW RECTANGLES
noStroke();
fill(eggshell); // color for the window background
rect(255,height/2,PulseWindowWidth,PulseWindowHeight);
rect(600,385,BPMWindowWidth,BPMWindowHeight);
}
void drawPulseWaveform(){
// DRAW THE PULSE WAVEFORM
// prepare pulse data points
RawY[RawY.length-1] = (1023 - Sensor) - 212; // place the new raw datapoint at the end of the array
zoom = scaleBar.getPos(); // get current waveform scale value
offset = map(zoom,0.5,1,150,0); // calculate the offset needed at this scale
for (int i = 0; i < RawY.length-1; i++) { // move the pulse waveform by
RawY[i] = RawY[i+1]; // shifting all raw datapoints one pixel left
float dummy = RawY[i] * zoom + offset; // adjust the raw data to the selected scale
ScaledY[i] = constrain(int(dummy),44,556); // transfer the raw data array to the scaled array
}
stroke(250,0,0); // red is a good color for the pulse waveform
noFill();
beginShape(); // using beginShape() renders fast
for (int x = 1; x < ScaledY.length-1; x++) {
vertex(x+10, ScaledY[x]); //draw a line connecting the data points
}
endShape();
}
void drawBPMwaveform(){
// DRAW THE BPM WAVE FORM
// first, shift the BPM waveform over to fit then next data point only when a beat is found
if (beat == true){ // move the heart rate line over one pixel every time the heart beats
beat = false; // clear beat flag (beat flag waset in serialEvent tab)
for (int i=0; i<rate.length-1; i++){
rate[i] = rate[i+1]; // shift the bpm Y coordinates over one pixel to the left
}
// then limit and scale the BPM value
BPM = min(BPM,200); // limit the highest BPM value to 200
float dummy = map(BPM,0,200,555,215); // map it to the heart rate window Y
rate[rate.length-1] = int(dummy); // set the rightmost pixel to the new data point value
}
// GRAPH THE HEART RATE WAVEFORM
stroke(250,0,0); // color of heart rate graph
strokeWeight(2); // thicker line is easier to read
noFill();
beginShape();
for (int i=0; i < rate.length-1; i++){ // variable 'i' will take the place of pixel x position
vertex(i+510, rate[i]); // display history of heart rate datapoints
}
endShape();
}
void drawHeart(){
// DRAW THE HEART AND MAYBE MAKE IT BEAT
fill(250,0,0);
stroke(250,0,0);
// the 'heart' variable is set in serialEvent when arduino sees a beat happen
heart--; // heart is used to time how long the heart graphic swells when your heart beats
heart = max(heart,0); // don't let the heart variable go into negative numbers
if (heart > 0){ // if a beat happened recently,
strokeWeight(8); // make the heart big
}
smooth(); // draw the heart with two bezier curves
bezier(width-100,50, width-20,-20, width,140, width-100,150);
bezier(width-100,50, width-190,-20, width-200,140, width-100,150);
strokeWeight(1); // reset the strokeWeight for next time
}
void listAvailablePorts(){
println(Serial.list()); // print a list of available serial ports to the console
serialPorts = Serial.list();
fill(0);
textFont(font,16);
textAlign(LEFT);
// set a counter to list the ports backwards
int yPos = 0;
int xPos = 35;
for(int i=serialPorts.length-1; i>=0; i--){
button[i] = new Radio(xPos, 95+(yPos*20),12,color(180),color(80),color(255),i,button);
text(serialPorts[i],xPos+15, 100+(yPos*20));
yPos++;
if(yPos > height-30){
yPos = 0; xPos+=200;
}
}
int p = numPorts;
fill(233,0,0);
button[p] = new Radio(35, 95+(yPos*20),12,color(180),color(80),color(255),p,button);
text("Refresh Serial Ports List",50, 100+(yPos*20));
textFont(font);
textAlign(CENTER);
}
void autoScanPorts(){
if(Serial.list().length != numPorts){
if(Serial.list().length > numPorts){
println("New Ports Opened!");
int diff = Serial.list().length - numPorts; // was serialPorts.length
serialPorts = expand(serialPorts,diff);
numPorts = Serial.list().length;
}else if(Serial.list().length < numPorts){
println("Some Ports Closed!");
numPorts = Serial.list().length;
}
refreshPorts = true;
return;
}
}
void resetDataTraces(){
for (int i=0; i<rate.length; i++){
rate[i] = 555; // Place BPM graph line at bottom of BPM Window
}
for (int i=0; i<RawY.length; i++){
RawY[i] = height/2; // initialize the pulse window data line to V/2
}
}
There are a couple of cool things that can be made while the sketch is running:
- Press ’s’ or ‘S’ to take a screenshot of the program window.
- The image will be saved in the sketch folder as a .jpg Press ‘r’ or ‘R’ to reset the data windows to zero.
Attached the screen of what could be seen in the processing monitor + the Arduino set up:
Retrospective 🤔
This week resulted on being lots of fun!. Probably the nicest part of learning about coding is being able to translate the data into something tangible: both by using outputs like we learned during the previous weeks, or by integrating the data into graphic visualizations.
I did test an example of how to integrate Processing and Arduino. Learning the basics: how to connect ports, and create a simple integration of the data. The outcome was very exciting and motivating.
I would like to work more on my future projects with the knowledge. I think it is a very powerful tool that can be used to transform complicated data into colorful and dynamic graphics which engage better with people.