This device is meant to help remind the user how much water they have in their water bottle and give them helpful suggestions to drink water throughout the day. The main functionality of the system is a water sensor made of various wire endpoints on the inside of the water bottle that conduct electricity when they come in contact with the water. This signal is then read by the microcontroller which then calculates water level based on the number of sensor readings that are high. While the resolution of this sensor is only 7, this is all that is necessary for the 7 pixel display showing the water level. In addition to this main sensor, there is also an IMU on board sensing the current acceleration of the water bottle, a buzzer, and the LED light bar. Starting with the LED light bar, this is made of 7 LED pixels that indicate various things to the user including water level and when the user needs to drink water. The IMU on board is used for detecting when the bottle is at rest and thus the water level reading will be accurate. Finally, the buzzer acts in tandem with the light bar for indicating to the user when they need to drink water. All of these components add up to a device that is effectively able to let the user know how much water is in their water bottle and when they get dehydrated.
I decided to take my previous visual crit further for this final crit by adding more components from a typical house into the control scheme/visualization method. The idea is to create a little virtual world where the user can visualize and interact with things going on in their home related to electronics. The user has the ability to turn lamps on and off and the states of each lamp will brighten the virtual room accordingly.
The user can also see whether someone is at the door and let them inside if they so choose. I did not know how to connect a camera to the Arduino so the system is not able to distinguish who is at the door but that is a future addition to theoretical implementation of this concept. Assuming the user knows the person present at the door, they can click the lock next to the door and unlock the door for the user.
The user also has visual indications of the temperature and thermostat setting through a heater/ac unit. The color of the unit dynamically changes based on the difference between the goal temperature and current temperature in the room. If the room is hotter than it should be, the unit appears more blue to indicate that the ac is running. The same is true for the opposite scenario as well where the room is colder than desired.
Circuit:
Demo Video:
Code:
p5.js
/*
- RGB to HSV Conversion Function from http://www.javascripter.net/faq/rgb2hsv.htm
*/
let serial;
let latestData = "waiting for data";
var outMessage;
var prevOutMessage;
var message;
var currentMessage;
let goalTemp = 0;
let currTemp = 0;
let newFont;
let lightsEnable = true;
let x = 100;
let y = 100;
let width = 1700;
let height = 900;
let color = 300;
let baseSaturation = 40;
let baseBrightness = 30;
let table1X = 970;
let table2X = 630;
let table1Y = 200;
//Lightsource vector [on/off, x, y, Lamp Radius, Shine Radius]
let lightSources = [];
let updateLightSource = true;
//States
let heaterState = 0;
let acState = 1;
//Colors
let doorStepColor = [50, 50, 50];
let lockColor = [255, 0, 0];
let heaterColor = [255, 150, 173];
//Positions
let heaterX = 375;
let heaterY = 250;
let livingRoomWidth = width - 600;
let livingRoomHeight = height - 100;
let doorStepHeight = 200;
let doorStepWidth = 200;
let lockX = width/2 + livingRoomWidth/2 + 10;
let lockY = height/2 + livingRoomHeight/2 - doorStepHeight/2 - 60;
let lockState = 0;
let minDistIndex = 0;
function setup() {
createCanvas(width, height);
//Lamps initiation
lightSources[0] = [0, table1X, table1Y, 40, 200];
lightSources[1] = [0, table2X, table1Y, 40, 200];
serial = new p5.SerialPort();
serial.list();
serial.open('/dev/tty.usbmodem114697101');
serial.on('connected', serverConnected);
serial.on('list', gotList);
serial.on('data', gotData);
serial.on('error', gotError);
serial.on('open', gotOpen);
serial.on('close', gotClose);
}
function serverConnected() {
print("Connected to Server");
}
function gotList(thelist) {
print("List of Serial Ports:");
for (let i = 0; i < thelist.length; i++) {
print(i + " " + thelist[i]);
}
}
function gotOpen() {
print("Serial Port is Open");
}
function gotClose(){
print("Serial Port is Closed");
latestData = "Serial Port is Closed";
}
function gotError(theerror) {
print(theerror);
}
function gotData() {
let currentString = serial.readLine();
trim(currentString);
if (!currentString) return;
console.log(currentString);
latestData = currentString;
if (latestData != currentMessage) {
currentMessage = latestData;
handleInputData(latestData);
}
}
function handleInputData(latestData) {
//Current temperature
if (latestData/10 >= 70) {
currTemp = latestData - 700;
}
//Goal temperature
else if (latestData/10 >= 60) {
goalTemp = latestData - 600;
}
//Presence at door
else if (latestData/10 >= 4) {
if (latestData == 41) {
doorStepColor = [255, 255, 255];
} else if (latestData == 40) {
doorStepColor = [50, 50, 50];
}
}
//Lock
else if (latestData/10 >= 3) {
if (latestData == 30){
lockState = 0;
toggleLockColor(lockState);
} else if (latestData == 31) {
lockState = 1;
toggleLockColor(lockState);
}
}
//Lamp 2
else if (latestData/10 >= 2) {
if (latestData == 20){
lightSources[1][0] = 0;
} else if (latestData == 21) {
lightSources[1][0] = 1;
}
}
//Lamp 1
else if (latestData/10 >= 1) {
if (latestData == 10){
lightSources[0][0] = 0;
} else if (latestData == 11) {
lightSources[0][0] = 1;
}
}
}
function draw() {
colorMode(HSB);
background(128, 20, 70);
//House top view
//================================
push();
colorMode(RGB);
rectMode(CENTER);
stroke(0);
strokeWeight(10);
fill(50);
rect(width/2, height/2,livingRoomWidth,livingRoomHeight);
//Room
fill(50);
stroke(0);
strokeWeight(0);
rect(width/2 + livingRoomWidth/2, height/2 + livingRoomHeight/2 - doorStepHeight/2,100,75);
//Door step
fill(doorStepColor[0],doorStepColor[1],doorStepColor[2]);
stroke(0);
strokeWeight(0);
rect(width/2 + livingRoomWidth/2 + doorStepWidth/2 + 5, height/2 + livingRoomHeight/2 - doorStepHeight/2,doorStepWidth,doorStepHeight);
//Lock
fill(lockColor[0], lockColor[1],lockColor[2]);
rect(lockX, lockY,10,30);
pop();
//============================
//Heater
drawHeater(heaterX, heaterY, 200, 80, PI/2);
//Couch
drawCouch(800, 200, 80, 200, 0, 50, 40, 80);
//Chairs
drawCouch(980, 300, 80, 80, PI/2, 50, 40, 80);
drawCouch(620, 300, 80, 80, -PI/2, 50, 40, 80);
//Coffee table
drawTable(800, 300, 80, 200, PI, 30, 40, 30);
//Lamp tables
drawTable(table1X, table1Y, 80, 80, 0, 30, 40, 30);
drawTable(table2X, table1Y, 80, 80, 0, 30, 40, 30);
for (k = 0; k < lightSources.length; k++) {
fill(200, 40, 60);
drawLamp(lightSources[k][1], lightSources[k][2], lightSources[k][3]);
}
for (n = 0; n < lightSources.length; n++) {
if (lightSources[n][0] == 1) {
turnLightOn(lightSources[n][1], lightSources[n][2], lightSources[n][3], lightSources[n][4]);
}
}
push();
fill(0);
textAlign(CENTER);
textSize(20);
text("House Setting: " + goalTemp, 50, 100, 150, 100);
text("Room Temperature: " + currTemp, 30, 180, 190, 100);
pop();
}
function updateLightState(lightIndex) {
lightIndex = Number(lightIndex);
if (lightSources[lightIndex][0] == 1) {
lightSources[lightIndex][0] = 0;
if (lightIndex == 0) {
currentMessage = 10;
sendMessage(currentMessage);
} else if (lightIndex == 1){
currentMessage = 20;
sendMessage(currentMessage);
}
}
else if (lightSources[lightIndex][0] == 0) {
lightSources[lightIndex][0] = 1;
if (lightIndex == 0) {
currentMessage = 11;
sendMessage(currentMessage);
} else if (lightIndex == 1){
currentMessage = 21;
sendMessage(currentMessage);
}
}
}
function mousePressed() {
let currX = mouseX;
let currY = mouseY;
dist2Lamp1 = sqrt(pow((currX-lightSources[0][1]),2) + pow((currY-lightSources[0][2]),2));
dist2Lamp2 = sqrt(pow((currX-lightSources[1][1]),2) + pow((currY-lightSources[1][2]),2));
dist2Heater = sqrt(pow((currX-heaterX),2) + pow((currY-heaterY),2));
dist2Lock = sqrt(pow((currX-lockX),2) + pow((currY-lockY),2));
let minDistIndex = 0;
let distVector = [dist2Lamp1,dist2Lamp2,dist2Heater,dist2Lock];
for (i = 1; i < 4; i++) {
if (distVector[minDistIndex] > distVector[i]) {
minDistIndex = i;
}
}
if (minDistIndex == 0) {
if (dist2Lamp1 < lightSources[0][3]*2) {
updateLightState(0);
}
}
if (minDistIndex == 1) {
if (dist2Lamp2 < lightSources[1][3]*2) {
updateLightState(1);
}
}
if (minDistIndex == 3) {
if (currX < lockX + 5 && currX > lockX - 5) {
if (currY < lockY + 15 && currY > lockY - 15) {
lockState = !lockState;
toggleLockColor();
}
}
}
}
function turnLightOn(x, y, lampRadius, shineRadius) {
var centerX = x;
var centerY = y;
//Iterating through each point in square to brighten color if inside lamp shine radius
for (i = 0; i < 2*shineRadius; i = i+5) {
for (j = 0; j < 2*shineRadius; j = j+5) {
var currX = centerX - shineRadius + i;
var currY = centerY - shineRadius + j;
var dist2Center = sqrt(pow((centerX - currX),2) + pow((centerY - currY),2));
if (dist2Center <= shineRadius) {
var currColor = get(currX, currY);
hsvConversion = rgb2hsv(currColor[0], currColor[1], currColor[2]);
colorMode(HSB);
stroke(hsvConversion[0], hsvConversion[1]+40, hsvConversion[2]+20);
strokeWeight(7);
point(currX, currY);
}
}
}
strokeWeight(0);
}
function drawCouch(x, y, width, height, theta, couchH, couchS, couchB) {
let couchHeight = width;
let couchWidth = height;
let armWidth = couchWidth*1/5;
let armHeight = couchHeight*4/5;
let backWidth = couchWidth;
let backHeight = couchHeight*2/5;
//Base Couch
push();
angleMode(RADIANS);
rectMode(CENTER);
translate(x, y);
rotate(theta);
// translate(transRadius - transRadius*cos(theta), -transRadius*sin(theta));
strokeWeight(2);
stroke(0);
fill(couchH, couchS, couchB);
rect(0, 0, couchWidth, couchHeight, 0, 0, 10, 10);
//Arm rests
fill(couchH, couchS, couchB - 20);
rect(-couchWidth/2, armHeight - couchHeight, armWidth, armHeight, 10, 10, 10, 10);
rect(couchWidth/2, armHeight - couchHeight, armWidth, armHeight, 10, 10, 10, 10);
//Backing
fill(couchH, couchS, couchB - 25);
rect(0, (armHeight/2 - couchHeight/2) + (backHeight/2 - couchHeight/2), backWidth, backHeight, 0, 0, 30, 30);
strokeWeight(0);
pop();
}
function drawTable(x, y, width, height, theta, couchH, couchS, couchB) {
let tableHeight = width;
let tableWidth = height;
//Base Couch
push();
angleMode(RADIANS);
rectMode(CENTER);
translate(x, y);
rotate(theta);
// translate(transRadius - transRadius*cos(theta), -transRadius*sin(theta));
//Table top
strokeWeight(2);
stroke(0);
fill(couchH, couchS, couchB);
rect(0, 0, tableWidth, tableHeight);
//Top pattern
strokeWeight(2);
stroke(0);
fill(couchH, couchS, couchB + 40);
rect(0, 0, tableWidth - 10, tableHeight - 10);
pop();
}
function drawLamp(x, y, radius, couchH, couchS, couchB) {
let tableHeight = width;
let tableWidth = height;
//Base Couch
push();
//Lamp
fill(couchH, couchS, couchB);
ellipse(x, y, radius);
pop();
}
function drawHeater(x, y, width, height, theta) {
let heaterWidth = width;
let heaterHeight = height;
let numOfFins = 20;
let finWidth = heaterWidth/numOfFins - 4 - 8/numOfFins;
let finHeight = height/2 - 10;
let tempDiff = goalTemp - currTemp;
heaterColor = [220+2*tempDiff, 180, 200-tempDiff];
//Base Couch
push();
colorMode(RGB);
angleMode(RADIANS);
rectMode(CENTER);
translate(x, y);
rotate(theta);
// translate(transRadius - transRadius*cos(theta), -transRadius*sin(theta));
//Heater Base
strokeWeight(0);
fill(heaterColor[0], heaterColor[1], heaterColor[2]);
rect(0, 0, heaterWidth, heaterHeight, 5, 5, 5, 5);
//Top pattern
strokeWeight(2);
stroke(0);
fill(117, 109, 94);
for (i = 0; i < numOfFins; i++) {
let currX = 0 - heaterWidth/2 + (finWidth+12)/2 + (finWidth+4)*(i);
rect(currX, finHeight/2+5, finWidth, finHeight, finHeight/2, finHeight/2, finHeight/2);
rect(currX, -finHeight/2-5, finWidth, finHeight, finHeight/2, finHeight/2, finHeight/2);
}
pop();
}
function toggleLockColor() {
if (lockState) {
lockColor = [0, 255, 0];
currentMessage = 31;
sendMessage(currentMessage);
} else {
lockColor = [255, 0, 0];
currentMessage = 30;
sendMessage(currentMessage);
}
}
function rgb2hsv (r,g,b) {
var computedH = 0;
var computedS = 0;
var computedV = 0;
//remove spaces from input RGB values, convert to int
var r = parseInt( (''+r).replace(/\s/g,''),10 );
var g = parseInt( (''+g).replace(/\s/g,''),10 );
var b = parseInt( (''+b).replace(/\s/g,''),10 );
if ( r==null || g==null || b==null ||
isNaN(r) || isNaN(g)|| isNaN(b) ) {
alert ('Please enter numeric RGB values!');
return;
}
if (r<0 || g<0 || b<0 || r>255 || g>255 || b>255) {
alert ('RGB values must be in the range 0 to 255.');
return;
}
r=r/255; g=g/255; b=b/255;
var minRGB = Math.min(r,Math.min(g,b));
var maxRGB = Math.max(r,Math.max(g,b));
// Black-gray-white
if (minRGB==maxRGB) {
computedV = minRGB;
return [0,0,computedV];
}
// Colors other than black-gray-white:
var d = (r==minRGB) ? g-b : ((b==minRGB) ? r-g : b-r);
var h = (r==minRGB) ? 3 : ((b==minRGB) ? 1 : 5);
computedH = 60*(h - d/(maxRGB - minRGB));
computedS = (maxRGB - minRGB)/maxRGB;
computedV = maxRGB;
return [computedH,computedS*100,computedV*100];
}
function sendMessage(message) {
for (i = 0; i < 1; i++) {
//Output message to serial
serial.write(int(message));
}
}
Arduino
/*
References:
- Temperature Reading Code: https://www.circuitbasics.com/arduino-thermistor-temperature-sensor-tutorial/
Goal: bring control of home to digital interface on p5.js
1. Lights
- ON/OFF through p5.js (100%)
- State control based on presence in room (0%)
2. Temperature
- Holding temperature (70%)
- Indication of heater and ac states through p5.js (0%)
3. Doorbell
- Represent door presence through p5.js (30%)
- Unlock door through p5.js (80%) --> motor (CHECK), lights (CHECK), and sound to indicate to user that door is unlocked
4. TV
- Control ON/OF based on presence on couch (0%)
5. Notifications
- Notify user of house happenings through p5.js (0%)
- Take input through p5.js (0%)
Communication Protocol:
1 - lamp 1
2 - lamp 2
3 - lock state --> 1=unlocked; 0=locked
4 - presence state
5 - heater on? --> 1=yes; 0=no
6 - current temp
*/
#include <Servo.h>
#define lamp1pin 17
#define lamp2pin 16
#define button1 14
#define button2 40
#define coldPin 10
#define hotPin 11
#define tempSensor1 20
#define sliderPin 21
#define pressurePlate 19
#define presenceIndicator 8
#define lockButton 28
#define lockLight 38
#define unlockLight 35
//================//
//==Temp Control==//
//================//
bool heaterON = true;
bool acON = true;
long double prevTempTime = 0;
int tempTimerDuration = 2; // [s]
//========//
//==Lock==//
//========//
unsigned long debounceTimerLock = 0;
int debounceTimeLock = 300;
bool DEBOUNCE_LOCK = false;
bool firstCountLock = true;
volatile int lockState = 0; // 0 => locked; 1 => unlocked
int unlockTimer = 3000;
long double prevUnlockTime = 0;
Servo lockServo;
//=========//
//==Lamps==//
//=========//
unsigned long debounceTimer1 = 0;
int debounceTime1 = 300;
bool DEBOUNCE1 = false;
bool firstCount1 = true;
unsigned long debounceTimer2 = 0;
int debounceTime2 = 300;
bool DEBOUNCE2 = false;
bool firstCount2 = true;
volatile int lamp1state = 0;
volatile int lamp2state = 0;
//=================//
//==Communication==//
//=================//
int currentMessage;
int prevMessage;
int prevInMessage;
int inMessage;
bool receivedMessage = false;
void setup() {
// put your setup code here, to run once:
pinMode(lamp1pin, OUTPUT);
pinMode(lamp2pin, OUTPUT);
pinMode(coldPin, OUTPUT);
pinMode(hotPin, OUTPUT);
pinMode(button1, INPUT_PULLUP);
pinMode(button2, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(button1), button1Pressed, CHANGE);
attachInterrupt(digitalPinToInterrupt(button2), button2Pressed, CHANGE);
pinMode(tempSensor1, INPUT);
pinMode(sliderPin, INPUT);
pinMode(pressurePlate, INPUT);
pinMode(lockButton, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(lockButton), handleLockButton, CHANGE);
Serial.begin(9600);
lockServo.attach(29);
lockServo.write(0);
delay(15);
}
void loop() {
//if main computer sent a command
if ( Serial.available() > 0) {
int c = Serial.read();
parseInput(c);
}
doPlatePresenceCheck();
//Handling heater check
if (millis() / 1000.0 - prevTempTime > tempTimerDuration) {
prevTempTime = millis() / 1000.0;
checkTemp();
}
//Debounce for lock
if (DEBOUNCE_LOCK && firstCountLock) {
debounceTimerLock = millis();
firstCountLock = false;
toggleLockState(lockState);
}
if (millis() - debounceTimerLock > debounceTimeLock) {
DEBOUNCE_LOCK = false;
firstCountLock = true;
}
if (millis() - prevUnlockTime > unlockTimer && lockState) {
lockState = 0;
toggleLockState(lockState);
}
//Debounce for lamp 1
if (DEBOUNCE1 && firstCount1) {
debounceTimer1 = millis();
firstCount1 = false;
currentMessage = 10 + lamp1state;
sendMessage(currentMessage);
}
if (millis() - debounceTimer1 > debounceTime1) {
DEBOUNCE1 = false;
firstCount1 = true;
}
//Debounce for lamp 2
if (DEBOUNCE2 && firstCount2) {
debounceTimer2 = millis();
firstCount2 = false;
currentMessage = 20 + lamp2state;
sendMessage(currentMessage);
}
if (millis() - debounceTimer2 > debounceTime2) {
DEBOUNCE2 = false;
firstCount2 = true;
}
}
void checkTemp() {
int Vo;
float R1 = 10000;
float logR2, R2, T;
float c1 = 1.009249522e-03, c2 = 2.378405444e-04, c3 = 2.019202697e-07;
float minTemp = 60;
float maxTemp = 90;
float goalTemp;
Vo = analogRead(tempSensor1);
R2 = R1 * (1023.0 / (float)Vo - 1.0);
logR2 = log(R2);
T = (1.0 / (c1 + c2 * logR2 + c3 * logR2 * logR2 * logR2));
T = T - 273.15;
T = (T * 9.0) / 5.0 + 32.0;
goalTemp = map(analogRead(sliderPin), 0, 1023, minTemp, maxTemp);
currentMessage = 600 + goalTemp;
sendMessage(currentMessage);
currentMessage = 700 + T;
sendMessage(currentMessage);
if (goalTemp > T && acON) {
digitalWrite(hotPin, HIGH);
digitalWrite(coldPin, LOW);
heaterON = true;
acON = false;
currentMessage = 51; //Tell p5.js heater is on / AC is off
sendMessage(currentMessage);
} else if (goalTemp < T && heaterON) {
digitalWrite(hotPin, LOW);
digitalWrite(coldPin, HIGH);
heaterON = false;
acON = true;
currentMessage = 50; //Tell p5.js heater is off / AC is on
sendMessage(currentMessage);
}
}
void doPlatePresenceCheck() {
int standingThreshold = 600;
int standingTimeThreshold = 2000; // [ms]
static bool timingStand = false;
static bool someoneOnPressurePlate = false;
static unsigned long standingTime = 0;
int plateReading = analogRead(pressurePlate);
if (plateReading > standingThreshold) {
if (!timingStand) {
timingStand = true;
standingTime = millis();
}
} else if (plateReading < standingThreshold && someoneOnPressurePlate) {
timingStand = false;
someoneOnPressurePlate = false;
digitalWrite(presenceIndicator, LOW);
currentMessage = 40;
sendMessage(currentMessage);
}
if (timingStand && millis() - standingTime > standingTimeThreshold && !someoneOnPressurePlate) {
timingStand = false;
someoneOnPressurePlate = true;
digitalWrite(presenceIndicator, HIGH);
currentMessage = 41;
sendMessage(currentMessage);
}
}
void handleLockButton() {
if (!DEBOUNCE_LOCK) {
DEBOUNCE_LOCK = true;
lockState = !lockState;
receivedMessage = false;
}
}
void toggleLockState(int lockState) {
int numOfBlinks = 5;
if (lockState) {
//Unlock door
lockServo.write(179);
delay(15);
//Turn light on
digitalWrite(lockLight, LOW);
for (int i = 0; i < numOfBlinks; i++) {
digitalWrite(unlockLight, HIGH);
delay(100);
digitalWrite(unlockLight, LOW);
delay(50);
}
digitalWrite(unlockLight, HIGH);
//Start timing unlock
prevUnlockTime = millis();
//Indicate locking state to p5
currentMessage = 31;
sendMessage(currentMessage);
} else {
//Lock door
lockServo.write(0);
delay(15);
//Turn lock light on and unlock light off
digitalWrite(unlockLight, LOW);
for (int i = 0; i < numOfBlinks; i++) {
digitalWrite(lockLight, HIGH);
delay(100);
digitalWrite(lockLight, LOW);
delay(50);
}
digitalWrite(lockLight, HIGH);
//Indicate locking state to p5
currentMessage = 30;
sendMessage(currentMessage);
}
}
void button2Pressed() {
if (!DEBOUNCE2) {
DEBOUNCE2 = true;
lamp2state = !lamp2state;
digitalWrite(lamp2pin, lamp2state);
}
}
void button1Pressed() {
if (!DEBOUNCE1) {
DEBOUNCE1 = true;
lamp1state = !lamp1state;
digitalWrite(lamp1pin, lamp1state);
}
}
void parseInput(int inputData) {
int y = inputData;
if (y == 10 && prevInMessage != 10) { //1
lamp1state = 0;
digitalWrite(lamp1pin, LOW);
} else if (y == 11 && prevInMessage != 11) { //1
lamp1state = 1;
digitalWrite(lamp1pin, HIGH);
} if (y == 20 && prevInMessage != 20) { //1
lamp2state = 0;
digitalWrite(lamp2pin, LOW);
} else if (y == 21 && prevInMessage != 21) { //1
lamp2state = 1;
digitalWrite(lamp2pin, HIGH);
} else if (y == 30 && prevInMessage != 30) { //2
lockState = 0;
toggleLockState(lockState);
receivedMessage = true;
} else if (y == 31 && prevInMessage != 31) { //3
lockState = 1;
toggleLockState(lockState);
receivedMessage = true;
}
}
void sendMessage(int message) {
for (int i = 0; i < 10; i++) {
Serial.println(message);
delay(40);
}
}
For this project, I aimed to create an interactive 8-bit type representation of a house that the user could interact with and change stuff about their environment with the interface. I decided to go with lights and represent their states in the virtual p5.js environment. The user can click the lamps in the virtual world and turn them on/off in the real world. The states of the lamps are reflected in the p5.js environment as well brightening up the surrounding environment when they are on. The user can also turn the lights on and off in the physical world and the light states would be again reflected in the virtual environment so the information goes both ways from p5.js to arduino and back.
In thinking about projects that I could do for the visual crit, I thought about two ways of representing information visually. The first idea I had was a different version of the ripe fruit detector from last crit. I was thinking it would be cool if you cold take color data from one spectrum and manipulate it/represent it in another spectrum to make colors “easier” to discern.
Another idea I had was somehow representing data about utility usage visually. This would probably require a combination of p5.js and maybe other sensing from utility meters around the house to give you a more continuous representation but I think it could be cool to take this data and map it to a tank of water or a thunderstorm for electricity (more electricity usage translating to a larger storm). I also think this could be used to represent usage compared to household averages or goals based on how green or efficient you would want your house to be (This idea may be outside the scope of a crit but would be really fun to think about).
I thought the conversation about virtual projections was especially interesting given the state of virtual reality in the modern tech world. I think it is interesting to think about real world applications of virtual projections and I think things like the Metaverse are the closest thing we will get in the near future. I think it’s also interesting to think about the application of virtual reality type equipment not only in virtual environments but also in exploring real world places and things. With haptic feedback and camera technology nowadays, I think it is realistic to think that the next step of virtual projection type technology is not only visual but also touch based.
The conversation about color was also interesting to me because giving meaning to colors as an engineer is not something I think about everyday but affects the way I interact with my projects. For example, the color of wires in electrical diagrams plays a big part in how easy they are to understand. Using red wires for everything connecting to power and black wires for everything connecting to ground is something that is second nature to me having worked with electrical components for a while but it seems like an arbitrary decision. There are lots of other color standards for indicating signal types flowing through specific wires but it’s kind of interesting to think about the origin of those color schemes similar to the reasons why blue displays signify futuristic tech.
The concept for this device was to create a doorbell that could be used navigated and used with little to no eyesight required. My goal was to eliminate the component of doorbell interactions where the user has to navigate on the door to find the button and ring the doorbell. I ended up settling on a doormat that senses when a person is present and then greets the user and prompts them to stomp a specified amount of times for desired interactions. The actions were made to resemble those of a traditional answering machine: stomp once to hear the address, stomp twice to ring the doorbell, stomp three times to hear the options again. Going further into the design, I think it would be helpful to add a message component so the user could leave a message to the resident if they want. I think it would also be helpful for security sake to incorporate a notification system to the resident is notified of any users on their doorstep and a picture is captured of whoever is there.
Electrical Schematic:
Demo Video:
Code:
/*
Warning Machine for Something Approaching
*/
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>
AudioPlaySdWav playWav;
AudioOutputMQS mqs1; //xy=625,279
AudioConnection patchCord1(playWav, 0, mqs1, 0);
AudioConnection patchCord2(playWav, 1, mqs1, 1);
// Use these with the Teensy 3.5 & 3.6 SD card
#define SDCARD_CS_PIN BUILTIN_SDCARD
#define SDCARD_MOSI_PIN 11 // not actually used
#define SDCARD_SCK_PIN 13 // not actually used
#define pressurePlate 14
String message = "";
int stompCounter = 0;
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
// Audio connections require memory to work. For more
// detailed information, see the MemoryAndCpuUsage example
AudioMemory(8);
// Comment these out if not using the audio adaptor board.
// This may wait forever if the SDA & SCL pins lack
// pullup resistors
SPI.setMOSI(SDCARD_MOSI_PIN);
SPI.setSCK(SDCARD_SCK_PIN);
if (!(SD.begin(SDCARD_CS_PIN))) {
// stop here, but print a message repetitively
while (1) {
Serial.println("Unable to access the SD card");
delay(500);
}
}
pinMode(pressurePlate, INPUT);
}
void loop() {
// put your main code here, to run repeatedly
bool someoneOnPressurePlate = doPlatePresenceCheck();
if (someoneOnPressurePlate) {
doPressurePlateCheck();
}
Serial.print("Pressure Plate Reading: ");
Serial.print(analogRead(pressurePlate));
Serial.print("\t");
Serial.print("Stomp Counter: ");
Serial.print(stompCounter);
Serial.print("\t");
// Serial.print("Pressure Plate Pressence: ");
// Serial.print(someoneOnPressurePlate);
// Serial.print("\t");
Serial.print("Message: ");
Serial.print(message);
Serial.print("\t");
Serial.println("");
delay(5);
}
void playSound(const char *filename) {
playWav.play(filename);
delay(25);
while (playWav.isPlaying()) {
}
}
bool doPlatePresenceCheck() {
int standingThreshold = 600;
int standingTimeThreshold = 2000; // [ms]
static bool timingStand = false;
static bool someoneOnPressurePlate = false;
static unsigned long standingTime = 0;
int plateReading = analogRead(pressurePlate);
if (plateReading > standingThreshold) {
if (!timingStand) {
timingStand = true;
standingTime = millis();
}
} else if (plateReading < standingThreshold) {
timingStand = false;
someoneOnPressurePlate = false;
}
if (timingStand && millis() - standingTime > standingTimeThreshold) {
if (!someoneOnPressurePlate) {
playSound("Initial Greeting.WAV");
playSound("Options Sound.WAV");
}
timingStand = false;
someoneOnPressurePlate = true;
}
return someoneOnPressurePlate;
}
void doPressurePlateCheck() {
int debounceTime = 300;
int stompThreshold = 900;
int countTimerThreshold = 3000; // [ms]
static bool DEBOUNCE = false;
static bool timeDebounce = false;
static bool countStart = false;
static unsigned long debounceTimer = 0;
static unsigned long countTimer = 0;
int pressureReading = analogRead(pressurePlate);
if (!DEBOUNCE && pressureReading > stompThreshold) {
if (!countStart) {
countStart = true;
countTimer = millis();
}
DEBOUNCE = true;
stompCounter += 1;
}
if (millis() - countTimer > countTimerThreshold || stompCounter > 3) {
actionBasedOnStompCount(stompCounter);
countStart = false;
stompCounter = 0;
}
if (DEBOUNCE && timeDebounce) {
timeDebounce = false;
debounceTimer = millis();
}
if (millis() - debounceTimer > debounceTime) {
DEBOUNCE = false;
timeDebounce = true;
}
}
void actionBasedOnStompCount(int stompCount) {
if (stompCount == 1) {
//user wants to read address
message = "Read Address";
playSound("Address Sound.WAV");
} else if (stompCount == 2) {
//user wants to ring doorbell
message = "Ring Doorbell";
playSound("Doorbell Sound.WAV");
} else if (stompCount == 3) {
//user wants to leave message
message = "Reading Options Again";
playSound("Options Sound.WAV");
}
}
Throughout the chapter, there were three things that were portrayed as critical to an interface’s functionality and perception. The first of these categories was font and its use to convey information through a screen. Font is super important because it conveys information to the user through it’s appearance. Fonts that are more block-like and all uppercase tend to be harder to read and mimic early style computers making the user seem more intelligent or knowledgeable. In addition, fonts that have more character like serif fonts are more readable and can be used to convey very important information that needs to be read. The size of the font is also important providing insight into the importance of the information. All of these characteristics add up to convey information about the importance and urgency of the words being displayed without the user even having to read them.
In addition to font, color is also stressed as a very important visual aspect when it comes to displays. One aspect mentioned throughout chapter 3 is the idea of the blue and glow phenomena that conveys futuristic text. This is interesting because whenever I hear blue and glow I think of hologram which is very futuristic highlighting the cultural implications of this color. In addition, colors like red on a display signify more urgent or error-like messages. While this isn’t always the case, red is described as having a sense of danger. This is an interesting interpretation because red in other cultures also signifies good luck or happiness which is generally thought of to be the opposite of danger. This difference in color perception is very interesting because it brings up the idea that it is pretty much impossible to create a perfectly perceptible user interface. While we can come to a general consensus on what means what, it is very hard to create a display that means the same thing regardless of culture especially when it comes to color schemes.
Finally, in addition to color and font size, one large aspect of displays is the user interaction through buttons or cursors. One example brought up in chapter 3 is the Star Trek LCARS interface that uses shadows to give the digital buttons on the screen a sort of depth and animation. This not only makes them feel more life-like but also gives feedback as to whether the button was pressed or not. These types of visual cues that are missed when translating from physical to digital interfaces are key in user interaction and is something that can contribute to a device being amazing or completely unusable.
Chapter 4:
Volumetric projections are very good at portraying information in such a way that humans are comfortable with. Since we see the world in 3D, VP’s allow the user to interact with the information in such a way that mimics their natural perspective. Where this can become an issue, however, is in the realness of the projection. As discussed in chapter 4, VP’s can be very tricky if they are made too realistic because the user might actually think there is something there when in reality there isn’t. While this isn’t all bad for most applications, when something that has to be physical for the safety of the user is made virtual (like a step or floor) this can create circumstances ready for injury. In addition to safety and trickery, VP’s also have to portray the 3D information in such a way that looks natural and mimics the proportions of regular space. Otherwise the information might seem very abnormal and not as informative.
Chapter 5:
Gestural interfaces are very intuitive for the most part but also have the caveat of moving the entire body to do one single action. While there are seven distinct motions set by physical intuition but also movies and TV, these motions require sustained use of the arms at heights around heart level requiring lots of strength and endurance to keep up. This results in the user getting very tired after a small amount of use. So while gestural interfaces might seem cool, they are mostly just used for futuristic looking interfaces.
The purpose of this device is to assist people in locating themselves when they enter a new room in their home or other location. The basic idea is that when someone enters a room, a speaker will relay what room they are entering. This is accomplished by tracking the user’s position within a house through IR break beam sensors. One set of IR break beams is installed at each doorway or opening where the user can transition from room to room. When a break is detected in one of the beams, the system checks the current sensor being tripped against the previous room the user was in and adjusts the current room accordingly. Additionally, each room has its own sound that plays indicating to the user what room they are entering upon walking in. Finally, the system sends the current room number to a p5.js script that shows a visual layout of the floor plan with the current room highlighted in green.
This project was meant to convey emotions through sound by playing a song corresponding to the user’s emotions when a button is pressed. Currently there are only 3 buttons for 3 different sounds/songs but the idea is to be able to hook up more and more as songs stick out as conveying your emotions. The main components for this build are buttons, resistors, a speaker and amplifier, and the Teensy 4.1. When each button is pressed, a certain song corresponding to a different emotion will start to play through. When the song is over, it waits for the next button to be pressed and does not repeat the current sound. When each button is pressed, the current song being played will stop playing and switch to the next song corresponding to the button press. As of now, button 0 plays the Jaws intense sound that signifies a stress and anxiety. Button 1 plays an African Safari game sound that signifies being happy and adventurous. Button 2 plays a relaxing song to convey peace and relaxation.