Smart Water Bottle (with helpful reminders)

Description:

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.

Videos:

Digital Water Level Sensor Testing

Analog Water Level Sensor Testing

Buzzer Testing

Final Demo

Images:

Side view of new digital water level sensor
Side view of LED light bar indicator mounted to the side of the water bottle
Image of final electronics

Process:

LED light bar used for indication of water level
Image of original water sensor that was too small for the whole bottle
Overall image of first prototype with original analog water sensor
LED panel backside

Code:

#include <Adafruit_NeoPixel.h>
#include <Wire.h>
#include <MPU6050.h>

#define NEOPIXEL_PIN  22
#define WATER_PIN     19
#define NUM_PIXELS    7
#define SENSOR_PIN0   38
#define SENSOR_PIN1   39
#define SENSOR_PIN2   40
#define SENSOR_PIN3   41
#define SENSOR_PIN4   14
#define SENSOR_PIN5   15
#define SENSOR_PIN6   16

int currWaterLevel = 0;
int prevWaterLevel = 0;

int currLightLevel = 0;
int prevLightLevel = 0;

int mod = (int)1024.0 / NUM_PIXELS;
Adafruit_NeoPixel pixels(NUM_PIXELS, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);

int waterSensors[7] = {SENSOR_PIN0, SENSOR_PIN1, SENSOR_PIN2, SENSOR_PIN3, SENSOR_PIN4, SENSOR_PIN5, SENSOR_PIN6};

//Timer Variables
unsigned long waitCheckWaterLevel = 500;          //Check water level every 0.5sec
unsigned long waitCheckDehydration = 10000; //Alert dehydration every 30 minutes
unsigned long waitCheckAccel = 1000;              //Update MOVING veriable after 1sec of sustained action

unsigned long currTime = 0;

//Accelermoeter variables
double accelThresh = 100000;
double absAccel = 0;
double prevAbsAccel = 0;

int numDataPoints = 100;

bool MOVING = false; //Tracks the motion of the bottle
bool PREV_MOVING = false;
bool CHECK_WATER = false;
bool DEHYDRATED = false;
bool ACCEL_WAIT_CHECK = false;
//--> false if bottle is stationary
//--> true if bottle is moving

int toneFreq = 349;

MPU6050 mpu;


void setup() {

  Serial.begin(115200);

  pixels.begin();

  // Initialize MPU6050
  Serial.println("Initialize MPU6050");
  while (!mpu.begin(MPU6050_SCALE_2000DPS, MPU6050_RANGE_2G))
  {
    Serial.println("Could not find a valid MPU6050 sensor, check wiring!");
    delay(500);
  }

  // If you want, you can set gyroscope offsets
  mpu.setAccelOffsetX(40);
  mpu.setAccelOffsetY(40);
  mpu.setAccelOffsetZ(40);

  // Calibrate gyroscope. The calibration must be at rest.
  // If you don't want calibrate, comment this line.
  mpu.calibrateGyro();

  // Set threshold sensivty. Default 3.
  // If you don't want use threshold, comment this line or set 0.
  mpu.setThreshold(3);

  // Check settings
  checkSettings();

}

void loop() {
  //Read sensors
  Vector rawGyro = mpu.readRawGyro();
  currTime = millis();

  double x = rawGyro.XAxis;
  double y = rawGyro.YAxis;
  double z = rawGyro.ZAxis;

  absAccel = sqrt(x * x + y * y + z * z);
  absAccel = updateAccel(absAccel);

  currWaterLevel = readWaterLevel();

  checkMoving(absAccel);
  checkDehydrated();

  if (!MOVING && !DEHYDRATED) {
    showWaterLevel(currWaterLevel);
  }
  

  if (DEHYDRATED) {
    static unsigned long tStart = 0;
    static bool FLASH = true;
    if ((currTime - tStart) > 500) {
      pixels.clear();
      if (FLASH) {
        for (int i = 0; i < NUM_PIXELS; i++) {
          pixels.setPixelColor(i, pixels.Color(100, 0, 0));
        }
        FLASH = false;
        tone(12, toneFreq);
        
      }
      else {
        Serial.print("Hi");
        for (int i = 0; i < NUM_PIXELS; i++) {
          pixels.setPixelColor(i, pixels.Color(0, 0, 0));
        }
        FLASH = true;
        tone(12, 0);
      }
      pixels.show();
      tStart = currTime;
      Serial.print(FLASH);
    }
    
  }

  prevWaterLevel = currWaterLevel;
  prevAbsAccel = absAccel;
  PREV_MOVING = MOVING;

  Serial.println();
}

int readWaterLevel() {
  int waterLevel = 0;

  for (int i = 0; i < sizeof(waterSensors) / sizeof(waterSensors[0]); i++) {
    waterLevel += digitalRead(waterSensors[i]);
  }

  return waterLevel;
}

void showWaterLevel(int level) {
  pixels.clear();
  for (int i = 0; i < level; i++) {
    pixels.setPixelColor(i, pixels.Color(0, 0, 50));
  }
  pixels.show();
}

void checkMoving(double accel) {
  static unsigned long prevCheckTime = 0;

  bool ACCEL_WAIT_CHECK = false;
  bool ABOVE_THRESH = absAccel > accelThresh;

  //Start timer for passing absAccel thresh
  if (MOVING && ABOVE_THRESH || !MOVING && !ABOVE_THRESH) {
    
    ACCEL_WAIT_CHECK = abs(currTime - prevCheckTime) < waitCheckAccel;
  }
  else {
    prevCheckTime = currTime;
    ACCEL_WAIT_CHECK = false;
  }

  //Only update MOVING if timer is up
  if (ACCEL_WAIT_CHECK) {
    MOVING = !ABOVE_THRESH;
  }

}

void checkDehydrated() {
  static unsigned long prevCheckTime = 0;

  if (!DEHYDRATED) {
    DEHYDRATED = abs(currTime - prevCheckTime) > waitCheckDehydration;
  }
  else if (prevWaterLevel < currWaterLevel || PREV_MOVING != MOVING) {
    DEHYDRATED = false;
  }
  else {
    prevCheckTime = currTime;
  }
}

double updateAccel(double newReading) {
  static double dataArray[100];

  double sum = 0;

  for (int i = numDataPoints - 1; i >= 0; i--) {
    if (i == 0) {
      dataArray[i] = newReading;
    }
    else {
      dataArray[i] = dataArray[i - 1];
    }
    sum += dataArray[i];
  }
  sum = sum / numDataPoints;
  return sum;
}

void checkSettings()
{
  //  Serial.println();

  Serial.print(" * Sleep Mode:        ");
  Serial.println(mpu.getSleepEnabled() ? "Enabled" : "Disabled");

  Serial.print(" * Clock Source:      ");
  switch (mpu.getClockSource())
  {
    case MPU6050_CLOCK_KEEP_RESET:     Serial.println("Stops the clock and keeps the timing generator in reset"); break;
    case MPU6050_CLOCK_EXTERNAL_19MHZ: Serial.println("PLL with external 19.2MHz reference"); break;
    case MPU6050_CLOCK_EXTERNAL_32KHZ: Serial.println("PLL with external 32.768kHz reference"); break;
    case MPU6050_CLOCK_PLL_ZGYRO:      Serial.println("PLL with Z axis gyroscope reference"); break;
    case MPU6050_CLOCK_PLL_YGYRO:      Serial.println("PLL with Y axis gyroscope reference"); break;
    case MPU6050_CLOCK_PLL_XGYRO:      Serial.println("PLL with X axis gyroscope reference"); break;
    case MPU6050_CLOCK_INTERNAL_8MHZ:  Serial.println("Internal 8MHz oscillator"); break;
  }

  //  Serial.print(" * Gyroscope:         ");
  switch (mpu.getScale())
  {
    case MPU6050_SCALE_2000DPS:        Serial.println("2000 dps"); break;
    case MPU6050_SCALE_1000DPS:        Serial.println("1000 dps"); break;
    case MPU6050_SCALE_500DPS:         Serial.println("500 dps"); break;
    case MPU6050_SCALE_250DPS:         Serial.println("250 dps"); break;
  }

  Serial.print(" * Gyroscope offsets: ");
  Serial.print(mpu.getGyroOffsetX());
  Serial.print(" / ");
  Serial.print(mpu.getGyroOffsetY());
  Serial.print(" / ");
  Serial.println(mpu.getGyroOffsetZ());

  Serial.println();
}

Electrical Schematic:

 

 

Final Crit – James Kyle

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.

Lamps both off
One lamp on and one lamp off

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.

Process for unlocking door

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.

Temperature settings and their color representation
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);
  }
}

 

Visual Crit – James Kyle

Description:

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.

No lights on
One lamp on
Both lamps on

 

Demo video:

https://youtu.be/KQ2YrnX2HL4http://

 

Circuit:

 

Arduino Code:

#include <string.h>

#define lamp1pin 10
#define lamp2pin 9
#define button1 3
#define button2 2


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;


int inputData;

char buf[25];
String command;

int c;


void setup() {
  // put your setup code here, to run once:

  Serial.begin(9600);

  pinMode(lamp1pin, OUTPUT);
  pinMode(lamp2pin, OUTPUT);
  pinMode(button1, INPUT_PULLUP);
  pinMode(button2, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(button1), button1Pressed, CHANGE);
  attachInterrupt(digitalPinToInterrupt(button2), button2Pressed, CHANGE);

}


void button2Pressed() {
  if (!DEBOUNCE2) {
    DEBOUNCE2 = true;
    lamp2state = !lamp2state;
    digitalWrite(lamp2pin, lamp2state);
  }
}

void button1Pressed() {
  if (!DEBOUNCE1) {
    DEBOUNCE1 = true;
    lamp1state = !lamp1state;
    digitalWrite(lamp1pin, lamp1state);
  }
}

void loop() {

  int message1 = 10;
  int message2 = 20;

  //if main computer sent a command
  if ( Serial.available() > 0) {

    c = Serial.read();
    
//    if (c == 1) {
//      
//      digitalWrite(lamp1pin, HIGH);
//    }
    Serial.println(c);
    parseInput(c);
  }

  if (DEBOUNCE1 && firstCount1) {
    debounceTimer1 = millis();
    firstCount1 = false;
    message1 = 1 + lamp1state;
//    Serial.println(message1);
  }
  
  if (millis() - debounceTimer1 > debounceTime1) {
    DEBOUNCE1 = false;
    firstCount1 = true;
  }

  if (DEBOUNCE2 && firstCount2) {
    debounceTimer2 = millis();
    firstCount2 = false;
    message2 = 3 + lamp2state;
//    Serial.println(message2);
  }
  if (millis() - debounceTimer2 > debounceTime2) {
    DEBOUNCE2 = false;
    firstCount2 = true;
  }
  
  
}


void parseInput(int inputData) {


  int y = inputData;

    if (y == 1) { //1
      lamp1state = 1;
      digitalWrite(lamp1pin, HIGH);
    } else if (y == 2) { //2
      lamp1state = 0;
      digitalWrite(lamp1pin, LOW);
    }
    else if (y == 3) { //3
      lamp2state = 1;
      digitalWrite(lamp2pin, HIGH);
    } else if (y == 4) { //4
      lamp2state = 0;
      digitalWrite(lamp2pin, LOW);
    }

  
}

 

p5.js Code:

/*


- RGB to HSV Conversion Function from http://www.javascripter.net/faq/rgb2hsv.htm


*/




let serial;
let latestData = "waiting for data";
var outData;
var message;

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;


function setup() {
  createCanvas(width, height);

  //Lamps initiation
  lightSources[0] = [0, table1X, table1Y, 40, 200];
  lightSources[1] = [0, table2X, table1Y, 40, 200];
  lightSources[2] = [0, table1X, table1Y, 40, 200];
  
  
  serial = new p5.SerialPort();

  serial.list();
  serial.open('/dev/tty.usbmodem14301');

  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/10 >= 2) {
     lightSources[1][0] = latestData - 20;
   } else {
    lightSources[0][0] = latestData - 10; 
   }
 }
 
function draw() {

  colorMode(HSB);
  background(128, 20, 70);

  push();
  rectMode(CENTER);
  fill(50);
  rect(width/2, height/2,width - 100, height - 100);
  pop();

  //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);
  textSize(14);
  text(latestData, 10, 20);
  pop();

  push();
  fill(0);
  textSize(14);
  text(message, 10, 40);
  pop();

  var d = 3;
  d = int(d);

  // serial.write(int(2));
 
}


function updateLightState(lightIndex) {
  lightIndex = Number(lightIndex);
  if (lightSources[lightIndex][0] == 1) {
      lightSources[lightIndex][0] = 0;
      if (lightIndex == 2) { 
        outData = 2;
      } else if (lightIndex == 1){
        outData = 4;
      }
      // print(message);
      serial.write(int(outData));
  }

  else if (lightSources[lightIndex][0] == 0) {
      lightSources[lightIndex][0] = 1;
      if (lightIndex == 2) { 
        outData = 1;
      } else if (lightIndex == 1){
        outData = 3;
      }
      // print(message);
      serial.write(int(outData));
  }
}

function checkIfLampClicked(x, y) {

  for (n = 0; n < lightSources.length; n++) {
    let currX = lightSources[n][1];
    let currY = lightSources[n][2];
    let currR = lightSources[n][3];

    let distance = sqrt(pow((currX-x),2) - pow((currY-y),2));

    if (distance <= currR) {
      //Toggle lamp state
      updateLightState(n);
      return;
    }
  }
}

function keyPressed() {

  updateLightState(key);

}



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 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];
}

 

Ideas for Visual Crit – James Kyle

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).

Make it So Ch 3, 4, 5 – James Kyle

Thoughts:

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.

 

 

Doorbell for the Blind

Concept:

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");

  }
}

 

Make It So Chapters 3, 4, & 5

Chapter 3:

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.

Room Indicator for the Blind

Description:

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.

Images:

Videos:

Demo of physical build

Code:

 

/*
   Crit 2: Location Feedback w/Sound
   Judson Kyle
   judsonk

   Description:
*/

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

AudioPlaySdWav           playWav1;
AudioMixer4              mixer1;         //xy=647,408
AudioOutputMQS           mqs1;           //xy=625,279
AudioConnection          patchCord1(playWav1, 0, mqs1, 0);
AudioConnection          patchCord2(playWav1, 1, mqs1, 1);

#define SDCARD_CS_PIN    BUILTIN_SDCARD
#define SDCARD_MOSI_PIN  11  // not actually used
#define SDCARD_SCK_PIN   13  // not actually used

#define TRIGGER0   17
#define TRIGGER1   18
#define TRIGGER2   19
#define TRIGGER3   20
#define TRIGGER4   21
#define TRIGGER5   33
#define TRIGGER6   33
#define TRIGGER7   33
#define TRIGGER8   33
#define TRIGGER9   33
#define TRIGGER10  33
#define TRIGGER11  33
#define TRIGGER12  33

const char* outside = "Outside.WAV";
const char* sunRoom = "Sun Room.WAV";
const char* livingRoom = "Living Room.WAV";
const char* bed1 = "Bedroom 1.WAV";
const char* bed2 = "Bedroom 2.WAV";
const char* bed3 = "Bedroom 3.WAV";
const char* kitchen = "Kitchen.WAV";
const char* laundryRoom = "Laundry Room.WAV";
const char* diningRoom = "Dining Room.WAV";
const char* bath1 = "Bathroom 1.WAV";
const char* bath2 = "Bathroom 2.WAV";
const char* hall1 = "Hallway 1.WAV";
const char* hall2 = "Hallway 2.WAV";

//char *roomNames[13] = { outside,    sunRoom,      livingRoom,
//                        bed1,       bed2,         bed3,
//                        kitchen,    laundryRoom,  diningRoom,
//                        bath1,      bath2,        hall1,
//                        hall2};

int triggerPins[13] = { TRIGGER0,   TRIGGER2,   TRIGGER3,   TRIGGER4,
                        TRIGGER4,   TRIGGER5,   TRIGGER6,   TRIGGER7,
                        TRIGGER8,   TRIGGER9,   TRIGGER10,  TRIGGER11,
                        TRIGGER12
                      };
int numTriggers = 13;
int currTriggerOn = 0;
int prevTriggerOn = -1;
int prevRoom = 0;
volatile int currRoom = 0;

unsigned long currTime = 0;
unsigned long debounceTime = 100;
volatile unsigned long startTime = 0;
volatile bool DEBOUNCE = false;

const char* currFileName = outside;

void setup(void)
{
  // Wait for at least 3 seconds for the USB serial connection
  Serial.begin (9600);

  AudioMemory(8);

  pinMode(TRIGGER0, INPUT);
  pinMode(TRIGGER1, INPUT);
  pinMode(TRIGGER2, INPUT);
  pinMode(TRIGGER3, INPUT);
  pinMode(TRIGGER4, INPUT);

  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);
    }
  }

}

void loop (void)
{
  currTime = millis();

  if (!DEBOUNCE && !playWav1.isPlaying()) {
    updateCurrRoom();
    DEBOUNCE = true;
    startTime = currTime;
  }

  if (currTriggerOn != prevTriggerOn) {
    updateCurrRoom();
  }

  if (prevRoom != currRoom) {
    if (playWav1.isPlaying()) {
      playWav1.togglePlayPause();
    }
    playWav1.play(currFileName);
    Serial.println(currRoom);
  }

  prevRoom = currRoom;
  prevTriggerOn = currTriggerOn;

  //Update debounce state
  DEBOUNCE = abs(currTime - startTime) < debounceTime;
}

void updateCurrRoom() {
  switch (currRoom) {
    case 0:
      if (digitalRead(TRIGGER0) > 0) {
        currRoom = 1;
        currFileName = sunRoom;
      }
      break;
    case 1:
      if (digitalRead(TRIGGER0) > 0) {
        currRoom = 0;
        currFileName = outside;
      }
      else if (digitalRead(TRIGGER1) > 0) {
        currRoom = 2;
        currFileName = livingRoom;
      }
      break;
    case 2:
      if (digitalRead(TRIGGER2) > 0) {
        currRoom = 8;
        currFileName = diningRoom;
      }
      else if (digitalRead(TRIGGER1) > 0) {
        currRoom = 1;
        currFileName = sunRoom;
      }
      break;
    case 3:
      if (digitalRead(TRIGGER3) > 0) {
        currRoom = 8;
        currFileName = diningRoom;
      }
      break;
    case 8:
      if (digitalRead(TRIGGER2) > 0) {
        currRoom = 2;
        currFileName = livingRoom;
      }
      else if (digitalRead(TRIGGER4) > 0) {
        currRoom = 9;
        currFileName = bath1;
      }
      else if (digitalRead(TRIGGER3) > 0) {
        currRoom = 3;
        currFileName = bed1;
      }
      break;
    case 9:
      if (digitalRead(TRIGGER4) > 0) {
        currRoom = 8;
        currFileName = diningRoom;
      }
      break;
    default:
      break;
  }
}

 

let serial;
let latestData = "waiting for data";
var outData;

var minWidth = 600;   //set min width and height for canvas
var minHeight = 400;
var width, height;    // actual width and height for the sketch
var squareWidth = 100;

var greenColor = '#64d65d';
var redColor = '#d6220c';

var currRoom = 0;

class Room {
  constructor(length, height, xOffset, yOffset, color, name) {
    this.scale = 40;
    this.length = length*this.scale;
    this.height = height*this.scale;
    this.xOffset = xOffset*this.scale;
    this.yOffset = yOffset*this.scale;
    this.color = color; 
    this.name = name;
  }
  getX(canvasWidth) {
    return (canvasWidth - 12*this.scale)/2 + this.xOffset;
  }
  getY(canvasHeight) {
    return (canvasHeight + 17*this.scale)/2 - this.yOffset - this.height;
  }
}

let sunRoom = new Room(6, 2, 0, 0, redColor, "Sun Room");
let livingRoom = new Room(6, 5, 0, 2, redColor, "Living Room");
let dinningRoom = new Room(6, 4, 0, 7, redColor, "Dinning Room");
let bed1 = new Room(6, 7, 6, 0, redColor, "Bedroom 1");
let bed2 = new Room(6, 5, 6, 11, redColor, "Bedroom 2");
let bed3 = new Room(5, 3, 0, 11, redColor, "Bedroom 3");
let kitchen = new Room(4, 3, 2, 14, redColor, "Kitchen");
let laundryRoom = new Room(2, 1, 0, 16, redColor, "Laundry Room");
let bath1 = new Room(5, 4, 7, 7, redColor, "Bathroom 1");
let bath2 = new Room(2, 2, 0, 14, redColor, "Bathroom 2");
let hall1 = new Room(1, 4, 6, 7, redColor, "Hall 1");
let hall2 = new Room(1, 3, 5, 11, redColor, "Hall 2");
let outside = new Room(0, 0, 0, 0, redColor, "Outside");


var rooms = [ outside, sunRoom,  livingRoom, 
              bed1,     bed2,        bed3, 
              kitchen,  laundryRoom, dinningRoom,
              bath1,    bath2, 
              hall1,    hall2];

var scale = 10;


function setup() {
 // set the canvas to match the window size
 if (window.innerWidth > minWidth){
  width = window.innerWidth;
  } else {
    width = minWidth;
  }
  if (window.innerHeight > minHeight) {
    height = window.innerHeight;
  } else {
    height = minHeight;
  }

  originX = width/2 - 5.5*scale;
  originY = height/2 - 8.5*scale;

  //set up canvas
  createCanvas(width, height);
  noStroke();

 serial = new p5.SerialPort();

 serial.list();
 serial.open('/dev/tty.usbmodem114760901');

 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 = Number(currentString);
  currRoom = latestData;
  for (i = 0; i < rooms.length; i++) {
    rooms[i].color = redColor;
  }
  rooms[currRoom].color = greenColor;
}

function draw() {  
  background(0);

  //Draw canvas
  strokeWeight(4);
  stroke('white');
  fill('black');
  noStroke();

  fill(255);
  textSize(16);
  text("House Map", 30, 30);
  text("Current Room: " + rooms[currRoom].name, 30, 50);    // displaying the input

  //Draw Rooms
  strokeWeight(4);
  stroke('black');
  // fill(redColor);
  for (i = 0; i < rooms.length; i++) {
    fill(rooms[i].color);
    rect(rooms[i].getX(width), rooms[i].getY(height), rooms[i].length, rooms[i].height);
  }

}

function mousePressed() {
  var currX = mouseX;
  var currY = mouseY;

  for (i = 0; i < rooms.length; i++) {
    var roomX = rooms[i].getX(width);
    var roomY = rooms[i].getY(height);

    var check1 = (currX > roomX) && (currX < roomX + rooms[i].length);
    var check2 = (currY > roomY) && (currY < roomY + rooms[i].height);
    
    if (check1 && check2) {
      currRoom = i;
      rooms[i].color = greenColor;
    }
    else {
      rooms[i].color = redColor;
    }
  }
  
}

function mouseDragged() {

}

Electrical Schematic:

 

Assignment 7: Sound with Meaning

To develop ‘sound with meaning,’ I wrote a sketch using the tone() function to replicate the ‘wah wah wah’ trombone sound. Sketch and video below.

 

void setup() {

  tone(7, 293.66, 1000);
  delay(1000);
  noTone(7);
  delay(500);
  tone(7,277.18,1000);
  delay(1000);
  noTone(7);
  delay(500);
  tone(7,261.63,1000);
  delay(1000);
  noTone(7);
  delay(500);
  tone(7,246.94,3000);
  delay(1500);
  noTone(7);
  delay(3000);
}

void loop() {


}

 

I had a 60 hertz hum with the original power supply I used the first time around. Switching to another power reduced the issue, but it’s still there.

Resources

More background on the Teensy audio library and MQS:

https://melp242.blogspot.com/2020/10/audio-output-on-teensy-4x-boards.html

Note frequencies:

https://pages.mtu.edu/~suits/notefreqs.html

Assignment 7: Sound that has meaning

Description:

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.

Video:

Demo Video

Images:

Code:

/*
 * Assignment 7: Sound with Meaning
 * Judson Kyle
 * judsonk
 * 
 * Description: This sketch plays a sound corresponding to the users mood at the
 *              time. Depending on the button pressed, a sound corresponding to
 *              one of three emotions will play. These emotions are intense/stressed,
 *              happy/upbeat, and peaceful/relaxed corresponding to buttons 0, 1,
 *              and 2 respectively. Each sound will only play once through to the
 *              end and not repeat after that.
 */

#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

AudioPlaySdWav           playWav1;
AudioMixer4              mixer1;         //xy=647,408
AudioOutputMQS           mqs1;           //xy=625,279
AudioConnection          patchCord1(playWav1, 0, mqs1, 0);
AudioConnection          patchCord2(playWav1, 1, mqs1, 1);

#define SDCARD_CS_PIN    BUILTIN_SDCARD
#define SDCARD_MOSI_PIN  11  // not actually used
#define SDCARD_SCK_PIN   13  // not actually used

#define SWITCH0   33
#define SWITCH1   34
#define SWITCH2   35
#define SWITCH3   37
#define SWITCH4   38
#define SWITCH5   39

#define BUTTON0   16
#define BUTTON1   17
#define BUTTON2   18

unsigned long currTime = 0;
unsigned long debounceTime = 0;
volatile unsigned long startTime = 0;

volatile bool DEBOUNCE = false;

int numButtons = 3;

volatile int state = 0;

const char* currFileName = "Shark Attack Shortened.WAV";

void setup(void)
{
  // Wait for at least 3 seconds for the USB serial connection
  Serial.begin (9600);

  AudioMemory(8);

  pinMode(SWITCH0, INPUT);
  pinMode(SWITCH1, INPUT);
  pinMode(SWITCH2, INPUT);
  pinMode(SWITCH3, INPUT);
  pinMode(SWITCH4, INPUT);
  pinMode(SWITCH5, INPUT);

  attachInterrupt(digitalPinToInterrupt(BUTTON0), button0Pressed, CHANGE);
  attachInterrupt(digitalPinToInterrupt(BUTTON1), button1Pressed, CHANGE);
  attachInterrupt(digitalPinToInterrupt(BUTTON2), button2Pressed, CHANGE);

  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);
    }
  }

}

void loop (void)
{
  currTime = millis();
  static int prevState = 0;

  if (prevState != state) {
    if (playWav1.isPlaying()) {
      playWav1.stop();
    }
    switch (state) {
      case 0: //Play intense sound
        currFileName = "Shark Attack Shortened.WAV";
        playWav1.play(currFileName);
        printPlayState(currFileName);
        delay(5);
        break;
      case 1: //Play fun/upbeat sound
        currFileName = "African_fun_long.WAV";
        playWav1.play(currFileName);
        printPlayState(currFileName);
        delay(5);
        break;
      case 2: //Play peaceful sound
        currFileName = "With-You-in-My-Arms-SSJ011001.WAV";
        playWav1.play(currFileName);
        printPlayState(currFileName);
        delay(5);
        break;
    }
  }
  prevState = state;
  
  //Update debounce state
  DEBOUNCE = abs(currTime - startTime) < debounceTime;
}

//Button 0 interrupt function
void button0Pressed() {
  if ((digitalRead(BUTTON0) < 1) && !DEBOUNCE) {
    state = 0;
    startTime = currTime;
    DEBOUNCE = true;
  }
}

//Button 1 interrupt function
void button1Pressed() {
  if ((digitalRead(BUTTON1) < 1) && !DEBOUNCE) {
    state = 1;
    startTime = currTime;
    DEBOUNCE = true;
  }
}

//Button 2 interrupt function
void button2Pressed() {
  if ((digitalRead(BUTTON2) < 1) && !DEBOUNCE) {
    state = 2;
    startTime = currTime;
    DEBOUNCE = true;
  }
}

//Print out the current cong being played or return an error message if the song isn't being played
void printPlayState(const char* fileName) {
  if (!playWav1.isPlaying()) {
    Serial.print("Error playing: ");
    Serial.println(fileName);
    delay(5);
  }
  else {
    Serial.print("Playing: ");
    Serial.println(fileName);
    delay(5);
  }
}

Electrical Schematic: