Sims…but its you
Description:
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); } }