Final Project

For the final project, I installed light sensors and ultrasonic sensors in and around a lasercut chipboard topography model to achieve several different objectives.

Landscape Topography Model

My final project consists of 4 parts:

  1. Visualizing Light Sensor Data on a Digital Model

This exercise builds on previous work in the visual crit, in which data coming from light sensors influenced the visual patterns of the topography model in Rhino. In this version, light sensor data is used to create a gradient from white to black in Rhino/Grasshopper, which can either be viewed as a plan in 2d, or projected over the digital topography surface to be viewed in 3d. The data has been smoothed and remapped over a cellular grid, with data points mapped onto the digital topography model to correspond with the locations of the sensors on the physical model.

Views of 2d and 3d visualizations of the light gradient below:

2d Visualization of Light Sensor Gradient
3d Visualization of Light Sensor Gradient

 

Part 1: GH Code

Demonstration of 2d visualization of light sensor data below:

2. Turning LED Lights on Below a Given Light Level

When one of the light sensors falls below a certain threshold of light, the code shown below in Grasshopper will turn on the LED light corresponding to that particular sensor. When the light sensor value rises above this threshold, the LED light turns off. Depending on the way light is cast across the physical topography, one light may be turned on due to the surrounding low light levels, while the others are off. In this way, additional light is only provided when it is deemed necessary, based on the surrounding light conditions.

Model with mounted sensors and corresponding LED lights beyond
Part 2: GH Code

3. Digital Drawing with Ultrasonic Sensors

Using ultrasonic sensors mounted above the topography model, a pointer (such as a long dowel rod) can be moved across the physical terrain of the model. As the ultrasonic sensors pick up the location of the dowel, these locations are read in Grasshopper, logged, and construct points across the digital Rhino model, which can then be used to generate linework. One of the challenges with this project is the limited peripheral detection abilities of the ultrasonic sensors. Two were used in this demonstration, and when the dowel rod moved out of the range of one of the sensors, the code would interpret the coordinates in this area as “0” and the points and associated linework will “jump” to the edges of the model. One possible remedy would be to remove coordinates with “0” from the log, but this would not resolve the sensor range issue. Another possible solution could be to add more sensors. This would likely require multiple Arduinos, unless more complicated Arduino code could be developed to handle multiple ultrasonic sensors at a time (my preliminary attempts to combine 2 ultrasonic sensors on one Arduino were unsuccessful).

Part 3: GH Code
Ultrasonic Sketch: 3d Projection, Drawing in Green
Ultrasonic Sensors Sketch: 2d Drawing in Green
Ultrasonic Sensors Sketch: 3d Projection, Drawing in Green

4. Digital Drawing with Potentiometers

To explore a second technique for digital drawing, I used potentiometers to guide the drawing across the physical model. This method allowed for greater range and control over the digital drawing (provided one is accustomed to the controls), but it is less intuitive than gesturing with a dowel rod.

Part 4: GH Code
Drawing with Potentiometers, 2d Plan
Drawing with Potentiometers, 3d Projection

The Arduino set up for the light sensors and the potentiometer uses Firmata, written by Andrew Payne and Jason Kelly Johnson to facilitate communication between the Arduino and Rhino/Grasshopper/Firefly. A modification is required for 2022 Arduino:

/* 
 Created by Andrew Payne and Jason Kelly Johnson
 Latest Update March 25th, 2015 
 Copyright 2015 | All Rights Reserved
 version below modified for 2022 Arduino
 
 This Firmata allows you to control an Arduino board from Rhino/Grasshopper/Firefly.
 Updates, Questions, Suggestions visit: http://www.fireflyexperiments.com
 
 1. Plug Arduino boards into your USB port; confirm that your Arduino's green power LED in on
 2. Select your specific Arduino Board and Serial Port (Tools > Board; Tools > Serial Port) *Take note of your Serial Port COM #
 3. Verify (play button) and Upload (upload button) this program to your Arduino, close the Arduino program
 4. then open ... Rhino/Grasshopper/Firefly
 
 Note: The Firefly Firmata sets the following pins to perform these functions:
 
 *****ON STANDARD BOARDS (ie. Uno, Diecimila, Duemilanove, Lillypad, Mini, etc.)*****
 ANALOG IN pins 0-5 are set to return values (from 0 to 1023) for analog sensors
 DIGITAL IN pins 2,4,7 will return 0's or 1's; for 3 potential digital sensors (buttons, switches, on/off, true/false, etc.)
 DIGITAL/ANALOG OUT pins 3,5,6,11 (marked with a ~) can be used to digitalWrite, analogWrite, or Servo.write depending on the input status of that Firefly pin
 DIGITAL OUT pins 8,9,10,12,13 can be used to digitalWrite, Servo.write, or analogWrite depending on the input status of that Firefly pin
 
 *****ON MEGA BOARDS (ie. ATMEGA1280, ATMEGA2560)*****
 ANALOG IN pins 0-15 will return values (from 0 to 1023) for 16 analog sensors 
 DIGITAL IN pins 22-31 will return 0's or 1's; for digital sensors (buttons, switches, on/off, true/false, etc.) 
 DIGITAL/ANALOG OUT pins 2-13 can be used to digitalWrite, analogWrite, or Servo.write depending on the input status of that Firefly pin
 DIGITAL OUT pins 32-53 can be used to digitalWrite, Servo.write, or analogWrite depending on the input status of that Firefly pin
 
 *****ON LEONARDO BOARDS*****
 ANALOG IN pins 0-5 are set to return values (from 0 to 1023) for analog sensors
 DIGITAL IN pins 2,4,7 will return 0's or 1's; for 3 potential digital sensors (buttons, switches, on/off, true/false, etc.)
 DIGITAL/ANALOG OUT pins 3,5,6,11 (marked with a ~) can be used to digitalWrite, analogWrite, or Servo.write depending on the input status of that Firefly pin
 DIGITAL OUT pins 8,9,10,12,13 can be used to digitalWrite, Servo.write, or analogWrite depending on the input status of that Firefly pin
 
  *****ON DUE BOARDS (ie. SAM3X8E)*****
 ANALOG IN pins 0-11 will return values (from 0 to 4095) for 12 analog sensors 
 DIGITAL IN pins 22-31 will return 0's or 1's; for digital sensors (buttons, switches, on/off, true/false, etc.) 
 DIGITAL/ANALOG OUT pins 2-13 can be used to digitalWrite, analogWrite, or Servo.write depending on the input status of that Firefly pin
 DIGITAL OUT pins 32-53 can be used to digitalWrite, Servo.write, or analogWrite depending on the input status of that Firefly pin
 DAC0 and DAC1 can be used to output an analog voltage on those pins (only available on DUE boards)
 */

#include <Servo.h>            // attach Servo library (http://www.arduino.cc/playground/ComponentLib/Servo)
#include <pins_arduino.h>     // attach arduino pins header file to determine which board type is being used

#define BAUDRATE 115200       // Set the Baud Rate to an appropriate speed
#define BUFFSIZE 512          // buffer one command at a time

/*==============================================================================
 * GLOBAL VARIABLES
 *============================================================================*/

char buffer[BUFFSIZE];        // declare buffer
uint8_t bufferidx = 0;        // a type of unsigned integer of length 8 bits
char *parseptr;
char buffidx;

int counter = 0;
int numcycles = 1000;

#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)                                                // declare variables for STANDARD boards
  Servo Servo13, Servo12, Servo11, Servo10, Servo9, Servo8, Servo6, Servo5, Servo3;
  Servo SERVO_CONFIG[] = {Servo13, Servo12, Servo11, Servo10, Servo9, Servo8, Servo6, Servo5, Servo3};       // declare array of Servo objects
  int WRITE_PIN_CONFIG[] = {13,12,11,10,9,8,6,5,3}; 
  int READ_APIN_CONFIG[] = {0,1,2,3,4,5};
  int READ_DPIN_CONFIG[] = {2,4,7}; 
#endif

#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega16U4__)                                               // declare variables for LEONARDO board
  Servo Servo13, Servo12, Servo11, Servo10, Servo9, Servo8, Servo6, Servo5, Servo3;       
  Servo SERVO_CONFIG[] = {Servo13, Servo12, Servo11, Servo10, Servo9, Servo8, Servo6, Servo5, Servo3};       // declare array of Servo objects
  int WRITE_PIN_CONFIG[] = {13,12,11,10,9,8,6,5,3}; 
  int READ_APIN_CONFIG[] = {0,1,2,3,4,5};
  int READ_DPIN_CONFIG[] = {2,4,7};
#endif

#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)                        // declare variables for MEGA boards
  Servo Servo2, Servo3, Servo4, Servo5, Servo6, Servo7, Servo8, Servo9, Servo10, Servo11, Servo12, Servo13, Servo32, Servo33, Servo34, Servo35, Servo36, Servo37, Servo38, Servo39, Servo40, Servo41, Servo42, Servo43, Servo44, Servo45, Servo46, Servo47, Servo48, Servo49, Servo50, Servo51, Servo52, Servo53;
  Servo SERVO_CONFIG[] = {Servo2, Servo3, Servo4, Servo5, Servo6, Servo7, Servo8, Servo9, Servo10, Servo11, Servo12, Servo13, Servo32, Servo33, Servo34, Servo35, Servo36, Servo37, Servo38, Servo39, Servo40, Servo41, Servo42, Servo43, Servo44, Servo45, Servo46, Servo47, Servo48, Servo49, Servo50, Servo51, Servo52, Servo53};  // declare array of Servo objects
  int WRITE_PIN_CONFIG[] = {2,3,4,5,6,7,8,9,10,11,12,13,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53}; 
  int READ_APIN_CONFIG[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
  int READ_DPIN_CONFIG[] = {22,23,24,25,26,27,28,29,30,31};
#endif

#if defined(__SAM3X8E__)                 // declare variables for DUE boards
  Servo FDAC0, FDAC1, Servo2, Servo3, Servo4, Servo5, Servo6, Servo7, Servo8, Servo9, Servo10, Servo11, Servo12, Servo13, Servo32, Servo33, Servo34, Servo35, Servo36, Servo37, Servo38, Servo39, Servo40, Servo41, Servo42, Servo43, Servo44, Servo45, Servo46, Servo47, Servo48, Servo49, Servo50, Servo51, Servo52, Servo53;  
  Servo SERVO_CONFIG[] = {FDAC0, FDAC1, Servo2, Servo3, Servo4, Servo5, Servo6, Servo7, Servo8, Servo9, Servo10, Servo11, Servo12, Servo13, Servo32, Servo33, Servo34, Servo35, Servo36, Servo37, Servo38, Servo39, Servo40, Servo41, Servo42, Servo43, Servo44, Servo45, Servo46, Servo47, Servo48, Servo49, Servo50, Servo51, Servo52, Servo53};  // declare array of Servo objects
  int WRITE_PIN_CONFIG[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53}; //Note: first two values correspond to the DAC pins
  int READ_APIN_CONFIG[] = {0,1,2,3,4,5,6,7,8,9,10,11};
  int READ_DPIN_CONFIG[] = {22,23,24,25,26,27,28,29,30,31};
#endif

/*==============================================================================
 * SETUP() This code runs once
 *============================================================================*/
void setup()
{ 
  Init();                       //set initial pinmodes
  Serial.begin(BAUDRATE);       // Start Serial communication
  #if defined(__SAM3X8E__)      //if the connected board is an Arduino DUE
    analogReadResolution(12);   //Set the analog read resolution to 12 bits (acceptable values between 1-32 bits).  This is only for DUE boards
    analogWriteResolution(12);  // Set the analog write resolution to 12 bits (acceptable values between 1-32 bits).  This is only for DUE boards
  #endif
}

/*==============================================================================
 * LOOP() This code loops
 *============================================================================*/
void loop()
{
  if(Serial){
    ReadSerial();                       // read and parse string from serial port and write to pins
    if (counter >= numcycles){          // Wait every nth loop 
      ReadInputs();                     // get input data and print data to the serial port
      counter = 0;                      // reset the counter
    }
    counter ++;                         // increment the writecounter
  }
}

/*==============================================================================
 * FUNCTIONS()
 *============================================================================*/

/*
* Initializes the digital pins which will be used as inputs
*/
void Init(){
  int len = sizeof(READ_DPIN_CONFIG)/sizeof(READ_DPIN_CONFIG[0]); //get the size of the array
  for(int i = 0; i < len; i++){
    pinMode(READ_DPIN_CONFIG[i], INPUT);
  }
}

/* 
* Reads the incoming ADC or digital values from the corresponding analog and digital input  
* pins and prints the value to the serial port as a formatted commma separated string
*/
void ReadInputs(){ 
  int len = sizeof(READ_APIN_CONFIG)/sizeof(READ_APIN_CONFIG[0]); //get the size of the array
  for(int i = 0; i < len; i++){
    int val = analogRead(READ_APIN_CONFIG[i]);  //read value from analog pins
    Serial.print(val); Serial.print(",");   
  }
  len = sizeof(READ_DPIN_CONFIG)/sizeof(READ_DPIN_CONFIG[0]); //get the size of the array
  for(int i = 0; i < len; i++){
    int val = digitalRead(READ_DPIN_CONFIG[i]); //read value from digital pins
    Serial.print(val); Serial.print(",");   
  }
  Serial.println("eol");  //end of line marker
}

/*
* Retrieve the latest incoming serial value and split the string at the comma delimeter.
* When a comma is found, the value is offloaded to a temporary variable and written
* to the corresponding digital pin.
*/
void ReadSerial(){
  char c;    // holds one character from the serial port
  if (Serial.available()) {
    c = Serial.read();         // read one character
    buffer[bufferidx] = c;     // add to buffer
    if (c == '\n') {  
      buffer[bufferidx+1] = 0; // terminate it
      parseptr = buffer;       // offload the buffer into temp variable
      int len = sizeof(WRITE_PIN_CONFIG)/sizeof(WRITE_PIN_CONFIG[0]); //get the size of the array
      for(int i = 0; i < len; i++){
        //parse all incoming values and assign them to the appropriate variable
        int val = parsedecimal(parseptr);       // parse the incoming number
        if(i != len - 1) parseptr = strchr(parseptr, ',')+1;   // move past the ","
        WriteToPin(WRITE_PIN_CONFIG[i], val, SERVO_CONFIG[i]);         //send value out to pin on arduino board
      }    
      bufferidx = 0;                             // reset the buffer for the next read
      return;                                    // return so that we don't trigger the index increment below
    }                                            // didn't get newline, need to read more from the buffer
    bufferidx++;                                 // increment the index for the next character
    if (bufferidx == BUFFSIZE-1) bufferidx = 0;  // if we get to the end of the buffer reset for safety
  }
}

/*
* Send the incoming value to the appropriate pin using pre-defined logic (ie. digital, analog, or servo)
*/
void WriteToPin(int _pin, int _value, Servo _servo){
  if (_value >= 10000 && _value < 20000)            // check if value should be used for Digital Write (HIGH/LOW)
  {      
    if (_servo.attached()) _servo.detach();         // detach servo is one is attached to pin
    pinMode(_pin, OUTPUT);                       
    _value -= 10000;                                // subtract 10,000 from the value sent from Grasshopper 
    if (_value == 1) digitalWrite(_pin, HIGH);     
    else digitalWrite(_pin, LOW);   
  }   
  else if (_value >= 20000 && _value < 30000)       // check if value should be used for Analog Write (0-255)
  {
    if (_servo.attached()) _servo.detach();         // detach servo is one is attached to pin
    pinMode(_pin, OUTPUT);               
    _value -= 20000;                                // subtract 20,000 from the value sent from Grasshopper
    analogWrite(_pin, _value);                     
  }
  else if (_value >= 30000 && _value < 40000)       // check if value should be used for Servo Write (0-180)
  {
    _value -= 30000;                                // subtract 30,000 from the value sent from Grasshopper
    if (!_servo.attached())_servo.attach(_pin);     // attaches a Servo to the PWM pin (180 degree standard servos)                                    
    _servo.write(_value);                          
  }
  else if (_value >= 40000 && _value < 50000)       // check if value should be used for Analog Write (0-4096) for DACs
  {
    if (_servo.attached()) _servo.detach();         // detach servo is one is attached to pin
    pinMode(_pin, OUTPUT);               
    _value -= 40000;                                // subtract 40,000 from the value sent from Grasshopper
    WriteToDAC(_pin, _value);                     
  }
}

/*
* Parse a string value as a decimal
*/
uint32_t parsedecimal(char *str){
  uint32_t d = 0;
  while (str[0] != 0) {
    if ((str[0] > '50') || (str[0] < '0'))
      return d;
    d *= 10;
    d += str[0] - '0';
    str++;
  }
  return d;
}

/*
* Send the incoming value to the appropriate DAC for DUE boards. 
* Note: analogWrite resolution (default is 12 bits) is defined in the Setup function.
*/
//modification to original sketch to work with 2022 firefly and arduino
  void WriteToDAC(int _pin, int _value){
    #if defined(__SAM3X8E__) 
    if(_pin == 0) analogWrite(DAC0, _value);
    else if (_pin == 1) analogWrite(DAC1, _value);
    #endif
  }

 

To use the ultrasonic sensors with Rhino/Grasshopper/Firefly, the NewPing library was used:

// ---------------------------------------------------------------------------
// Example NewPing library sketch that does a ping about 20 times per second.
// ---------------------------------------------------------------------------

#include <NewPing.h>

#define TRIGGER_PIN  12  // Arduino pin tied to trigger pin on the ultrasonic sensor.
#define ECHO_PIN     11  // Arduino pin tied to echo pin on the ultrasonic sensor.
#define MAX_DISTANCE 30 // Maximum distance we want to ping for (in centimeters). Maximum sensor distance is rated at 400-500cm.

NewPing sonar(TRIGGER_PIN, ECHO_PIN, MAX_DISTANCE); // NewPing setup of pins and maximum distance.

void setup() {
  Serial.begin(115200); // Open serial monitor at 115200 baud to see ping results.
}

void loop() {
  delay(50);                     // Wait 50ms between pings (about 20 pings/sec). 29ms should be the shortest delay between pings.
  Serial.print("Ping: ");
  Serial.print(sonar.ping_cm()); // Send ping, get distance in cm and print result (0 = outside set distance range)
  Serial.println("cm");
}

 

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

 

Crit 3: Visual

For the Visual Crit, I used Arduino, Rhino, Grasshopper, and GH plug-in Firefly to visualize sensor data over a topographical landscape. The topography model was initially generated digitally with Rhino and Grasshopper, so it could be used later in the model visualization. Contours were exported to 2d CAD to cut the physical model.

The physical model was constructed of lasercut chipboard, with enough variation in height to imitate a landscape that could plausibly have multiple microclimates, which can form due to differences in light, drainage, and airflow. Changes in topography can drive these shifts in environmental conditions. Light sensors will installed in different areas of the model.

Uploaded to the arduino is this Firmata code (which has been edited since the 2015 version to account for some differences in Arduino):

/* 
 Created by Andrew Payne and Jason Kelly Johnson
 Latest Update March 25th, 2015 
 Copyright 2015 | All Rights Reserved
 
 This Firmata allows you to control an Arduino board from Rhino/Grasshopper/Firefly.
 Updates, Questions, Suggestions visit: http://www.fireflyexperiments.com
 
 1. Plug Arduino boards into your USB port; confirm that your Arduino's green power LED in on
 2. Select your specific Arduino Board and Serial Port (Tools > Board; Tools > Serial Port) *Take note of your Serial Port COM #
 3. Verify (play button) and Upload (upload button) this program to your Arduino, close the Arduino program
 4. then open ... Rhino/Grasshopper/Firefly
 
 Note: The Firefly Firmata sets the following pins to perform these functions:
 
 *****ON STANDARD BOARDS (ie. Uno, Diecimila, Duemilanove, Lillypad, Mini, etc.)*****
 ANALOG IN pins 0-5 are set to return values (from 0 to 1023) for analog sensors
 DIGITAL IN pins 2,4,7 will return 0's or 1's; for 3 potential digital sensors (buttons, switches, on/off, true/false, etc.)
 DIGITAL/ANALOG OUT pins 3,5,6,11 (marked with a ~) can be used to digitalWrite, analogWrite, or Servo.write depending on the input status of that Firefly pin
 DIGITAL OUT pins 8,9,10,12,13 can be used to digitalWrite, Servo.write, or analogWrite depending on the input status of that Firefly pin
 
 *****ON MEGA BOARDS (ie. ATMEGA1280, ATMEGA2560)*****
 ANALOG IN pins 0-15 will return values (from 0 to 1023) for 16 analog sensors 
 DIGITAL IN pins 22-31 will return 0's or 1's; for digital sensors (buttons, switches, on/off, true/false, etc.) 
 DIGITAL/ANALOG OUT pins 2-13 can be used to digitalWrite, analogWrite, or Servo.write depending on the input status of that Firefly pin
 DIGITAL OUT pins 32-53 can be used to digitalWrite, Servo.write, or analogWrite depending on the input status of that Firefly pin
 
 *****ON LEONARDO BOARDS*****
 ANALOG IN pins 0-5 are set to return values (from 0 to 1023) for analog sensors
 DIGITAL IN pins 2,4,7 will return 0's or 1's; for 3 potential digital sensors (buttons, switches, on/off, true/false, etc.)
 DIGITAL/ANALOG OUT pins 3,5,6,11 (marked with a ~) can be used to digitalWrite, analogWrite, or Servo.write depending on the input status of that Firefly pin
 DIGITAL OUT pins 8,9,10,12,13 can be used to digitalWrite, Servo.write, or analogWrite depending on the input status of that Firefly pin
 
  *****ON DUE BOARDS (ie. SAM3X8E)*****
 ANALOG IN pins 0-11 will return values (from 0 to 4095) for 12 analog sensors 
 DIGITAL IN pins 22-31 will return 0's or 1's; for digital sensors (buttons, switches, on/off, true/false, etc.) 
 DIGITAL/ANALOG OUT pins 2-13 can be used to digitalWrite, analogWrite, or Servo.write depending on the input status of that Firefly pin
 DIGITAL OUT pins 32-53 can be used to digitalWrite, Servo.write, or analogWrite depending on the input status of that Firefly pin
 DAC0 and DAC1 can be used to output an analog voltage on those pins (only available on DUE boards)
 */

#include <Servo.h>            // attach Servo library (http://www.arduino.cc/playground/ComponentLib/Servo)
#include <pins_arduino.h>     // attach arduino pins header file to determine which board type is being used

#define BAUDRATE 115200       // Set the Baud Rate to an appropriate speed
#define BUFFSIZE 512          // buffer one command at a time

/*==============================================================================
 * GLOBAL VARIABLES
 *============================================================================*/

char buffer[BUFFSIZE];        // declare buffer
uint8_t bufferidx = 0;        // a type of unsigned integer of length 8 bits
char *parseptr;
char buffidx;

int counter = 0;
int numcycles = 1000;

#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)                                                // declare variables for STANDARD boards
  Servo Servo13, Servo12, Servo11, Servo10, Servo9, Servo8, Servo6, Servo5, Servo3;
  Servo SERVO_CONFIG[] = {Servo13, Servo12, Servo11, Servo10, Servo9, Servo8, Servo6, Servo5, Servo3};       // declare array of Servo objects
  int WRITE_PIN_CONFIG[] = {13,12,11,10,9,8,6,5,3}; 
  int READ_APIN_CONFIG[] = {0,1,2,3,4,5};
  int READ_DPIN_CONFIG[] = {2,4,7}; 
#endif

#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega16U4__)                                               // declare variables for LEONARDO board
  Servo Servo13, Servo12, Servo11, Servo10, Servo9, Servo8, Servo6, Servo5, Servo3;       
  Servo SERVO_CONFIG[] = {Servo13, Servo12, Servo11, Servo10, Servo9, Servo8, Servo6, Servo5, Servo3};       // declare array of Servo objects
  int WRITE_PIN_CONFIG[] = {13,12,11,10,9,8,6,5,3}; 
  int READ_APIN_CONFIG[] = {0,1,2,3,4,5};
  int READ_DPIN_CONFIG[] = {2,4,7};
#endif

#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)                        // declare variables for MEGA boards
  Servo Servo2, Servo3, Servo4, Servo5, Servo6, Servo7, Servo8, Servo9, Servo10, Servo11, Servo12, Servo13, Servo32, Servo33, Servo34, Servo35, Servo36, Servo37, Servo38, Servo39, Servo40, Servo41, Servo42, Servo43, Servo44, Servo45, Servo46, Servo47, Servo48, Servo49, Servo50, Servo51, Servo52, Servo53;
  Servo SERVO_CONFIG[] = {Servo2, Servo3, Servo4, Servo5, Servo6, Servo7, Servo8, Servo9, Servo10, Servo11, Servo12, Servo13, Servo32, Servo33, Servo34, Servo35, Servo36, Servo37, Servo38, Servo39, Servo40, Servo41, Servo42, Servo43, Servo44, Servo45, Servo46, Servo47, Servo48, Servo49, Servo50, Servo51, Servo52, Servo53};  // declare array of Servo objects
  int WRITE_PIN_CONFIG[] = {2,3,4,5,6,7,8,9,10,11,12,13,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53}; 
  int READ_APIN_CONFIG[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
  int READ_DPIN_CONFIG[] = {22,23,24,25,26,27,28,29,30,31};
#endif

#if defined(__SAM3X8E__)                 // declare variables for DUE boards
  Servo FDAC0, FDAC1, Servo2, Servo3, Servo4, Servo5, Servo6, Servo7, Servo8, Servo9, Servo10, Servo11, Servo12, Servo13, Servo32, Servo33, Servo34, Servo35, Servo36, Servo37, Servo38, Servo39, Servo40, Servo41, Servo42, Servo43, Servo44, Servo45, Servo46, Servo47, Servo48, Servo49, Servo50, Servo51, Servo52, Servo53;  
  Servo SERVO_CONFIG[] = {FDAC0, FDAC1, Servo2, Servo3, Servo4, Servo5, Servo6, Servo7, Servo8, Servo9, Servo10, Servo11, Servo12, Servo13, Servo32, Servo33, Servo34, Servo35, Servo36, Servo37, Servo38, Servo39, Servo40, Servo41, Servo42, Servo43, Servo44, Servo45, Servo46, Servo47, Servo48, Servo49, Servo50, Servo51, Servo52, Servo53};  // declare array of Servo objects
  int WRITE_PIN_CONFIG[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53}; //Note: first two values correspond to the DAC pins
  int READ_APIN_CONFIG[] = {0,1,2,3,4,5,6,7,8,9,10,11};
  int READ_DPIN_CONFIG[] = {22,23,24,25,26,27,28,29,30,31};
#endif

/*==============================================================================
 * SETUP() This code runs once
 *============================================================================*/
void setup()
{ 
  Init();                       //set initial pinmodes
  Serial.begin(BAUDRATE);       // Start Serial communication
  #if defined(__SAM3X8E__)      //if the connected board is an Arduino DUE
    analogReadResolution(12);   //Set the analog read resolution to 12 bits (acceptable values between 1-32 bits).  This is only for DUE boards
    analogWriteResolution(12);  // Set the analog write resolution to 12 bits (acceptable values between 1-32 bits).  This is only for DUE boards
  #endif
}

/*==============================================================================
 * LOOP() This code loops
 *============================================================================*/
void loop()
{
  if(Serial){
    ReadSerial();                       // read and parse string from serial port and write to pins
    if (counter >= numcycles){          // Wait every nth loop 
      ReadInputs();                     // get input data and print data to the serial port
      counter = 0;                      // reset the counter
    }
    counter ++;                         // increment the writecounter
  }
}

/*==============================================================================
 * FUNCTIONS()
 *============================================================================*/

/*
* Initializes the digital pins which will be used as inputs
*/
void Init(){
  int len = sizeof(READ_DPIN_CONFIG)/sizeof(READ_DPIN_CONFIG[0]); //get the size of the array
  for(int i = 0; i < len; i++){
    pinMode(READ_DPIN_CONFIG[i], INPUT);
  }
}

/* 
* Reads the incoming ADC or digital values from the corresponding analog and digital input  
* pins and prints the value to the serial port as a formatted commma separated string
*/
void ReadInputs(){ 
  int len = sizeof(READ_APIN_CONFIG)/sizeof(READ_APIN_CONFIG[0]); //get the size of the array
  for(int i = 0; i < len; i++){
    int val = analogRead(READ_APIN_CONFIG[i]);  //read value from analog pins
    Serial.print(val); Serial.print(",");   
  }
  len = sizeof(READ_DPIN_CONFIG)/sizeof(READ_DPIN_CONFIG[0]); //get the size of the array
  for(int i = 0; i < len; i++){
    int val = digitalRead(READ_DPIN_CONFIG[i]); //read value from digital pins
    Serial.print(val); Serial.print(",");   
  }
  Serial.println("eol");  //end of line marker
}

/*
* Retrieve the latest incoming serial value and split the string at the comma delimeter.
* When a comma is found, the value is offloaded to a temporary variable and written
* to the corresponding digital pin.
*/
void ReadSerial(){
  char c;    // holds one character from the serial port
  if (Serial.available()) {
    c = Serial.read();         // read one character
    buffer[bufferidx] = c;     // add to buffer
    if (c == '\n') {  
      buffer[bufferidx+1] = 0; // terminate it
      parseptr = buffer;       // offload the buffer into temp variable
      int len = sizeof(WRITE_PIN_CONFIG)/sizeof(WRITE_PIN_CONFIG[0]); //get the size of the array
      for(int i = 0; i < len; i++){
        //parse all incoming values and assign them to the appropriate variable
        int val = parsedecimal(parseptr);       // parse the incoming number
        if(i != len - 1) parseptr = strchr(parseptr, ',')+1;   // move past the ","
        WriteToPin(WRITE_PIN_CONFIG[i], val, SERVO_CONFIG[i]);         //send value out to pin on arduino board
      }    
      bufferidx = 0;                             // reset the buffer for the next read
      return;                                    // return so that we don't trigger the index increment below
    }                                            // didn't get newline, need to read more from the buffer
    bufferidx++;                                 // increment the index for the next character
    if (bufferidx == BUFFSIZE-1) bufferidx = 0;  // if we get to the end of the buffer reset for safety
  }
}

/*
* Send the incoming value to the appropriate pin using pre-defined logic (ie. digital, analog, or servo)
*/
void WriteToPin(int _pin, int _value, Servo _servo){
  if (_value >= 10000 && _value < 20000)            // check if value should be used for Digital Write (HIGH/LOW)
  {      
    if (_servo.attached()) _servo.detach();         // detach servo is one is attached to pin
    pinMode(_pin, OUTPUT);                       
    _value -= 10000;                                // subtract 10,000 from the value sent from Grasshopper 
    if (_value == 1) digitalWrite(_pin, HIGH);     
    else digitalWrite(_pin, LOW);   
  }   
  else if (_value >= 20000 && _value < 30000)       // check if value should be used for Analog Write (0-255)
  {
    if (_servo.attached()) _servo.detach();         // detach servo is one is attached to pin
    pinMode(_pin, OUTPUT);               
    _value -= 20000;                                // subtract 20,000 from the value sent from Grasshopper
    analogWrite(_pin, _value);                     
  }
  else if (_value >= 30000 && _value < 40000)       // check if value should be used for Servo Write (0-180)
  {
    _value -= 30000;                                // subtract 30,000 from the value sent from Grasshopper
    if (!_servo.attached())_servo.attach(_pin);     // attaches a Servo to the PWM pin (180 degree standard servos)                                    
    _servo.write(_value);                          
  }
  else if (_value >= 40000 && _value < 50000)       // check if value should be used for Analog Write (0-4096) for DACs
  {
    if (_servo.attached()) _servo.detach();         // detach servo is one is attached to pin
    pinMode(_pin, OUTPUT);               
    _value -= 40000;                                // subtract 40,000 from the value sent from Grasshopper
    WriteToDAC(_pin, _value);                     
  }
}

/*
* Parse a string value as a decimal
*/
uint32_t parsedecimal(char *str){
  uint32_t d = 0;
  while (str[0] != 0) {
    if ((str[0] > '50') || (str[0] < '0'))
      return d;
    d *= 10;
    d += str[0] - '0';
    str++;
  }
  return d;
}

/*
* Send the incoming value to the appropriate DAC for DUE boards. 
* Note: analogWrite resolution (default is 12 bits) is defined in the Setup function.
*/
//modification to original sketch to work with 2022 firefly and arduino
  void WriteToDAC(int _pin, int _value){
    #if defined(__SAM3X8E__) 
    if(_pin == 0) analogWrite(DAC0, _value);
    else if (_pin == 1) analogWrite(DAC1, _value);
    #endif
  }

Next, with Grasshopper/Firefly, the serial data from the light sensors is read. This data is used to generate points, which act as attractors in a domain field. The brighter the light, the smaller the circle illustrated in the domain field.

The final results can be viewed as a plan, or project onto the digital model surface to be viewed in 3d.

Light Readings, plan view, v1

Light Readings, 3d, v1

Light Readings, plan view, v2

Light Readings, plan view, v2

 

 

FootprintID and Mapping Frequencies in Matlab from Audio Files

In one of my other courses (Foundations of Intelligent Infrastructure), we went through an exercise today where we recorded sound and then used Matlab to map frequencies.

My group measured the sound of different shoes stomping on the concrete hall in Porter. Graphs showing frequency and sound below.

Our experiment reminded the professor of a project at CMU where floor sensors are used to measure movement across spaces. The specific application discussed is in elder care, but also may be a way to provide less invasive monitoring of spaces.

https://www.cmu.edu/piper/news/archives/2017/february/noh-wins-NSF-award.html

https://www.andrew.cmu.edu/user/shijiapa/documentations/Pan_Ubicomp_2017.pdf

 

Matlab code (note: need to drop sound file into the same folder as the Matlab file):

%%
clear all; clc;

% Load signal in the time domain, x (fs is the sample rate in hertz)
[x,fs] = audioread('takeone.m4a');
tb = (0:1/fs:(length(x)-1)/fs); % Convert samples, k, to time base, t
plot(tb,x)
xlabel('Time (seconds)')
ylabel('Amplitude')
title('Sound')

%% 
% Play the sound
sound(x,fs)

%%
% Use the fft command to compute the DFT of the signal. 
m = length(x);         % Window length, N
y = fft(x,m);          % DFT of signal
f = (0:m-1)*(fs/m);    % Frequency range
p = y.*conj(y)/m;      % Power of the DFT

% Plot up to the Nyquist frequency:
plot(f(1:floor(m/2)),p(1:floor(m/2)))
xlabel('Frequency (Hz)')
ylabel('Power')
title('Five Shoe Stomps on Concrete Floor')
grid on;
xlim([0 1000])        % Zoom in on relevant range

 

 

Visual Crit – Jud

Problem:

Many rooms in the basements of buildings are very depressing and hard to work in because of the lack of windows and access to nature. Looking out the window is a very comforting and calming action that one can take for granted when working on floors where there is plenty of access to windows. Not only being able to look out the window and see what the weather is currently, but also being able to see nature outside is very helpful to productivity. In order to solve this problem, this project attempts to make a virtual window that allows you to see the outside environment and also interact with it. The p5.js script reads the weather of the desired location and inputs that into the script which then decides what to do based on what the weather is. For example, if it’s raining outside, the visual feedback will be a rain simulation with the rain droplets accumulating at the bottom to show how much it has actually rained. The same thing happens with snow and other weather events that have a physical element falling to the ground. In addition to the simulation of the weather, the device will have a gyroscope inside reading the current tilt angle and sending that back into the p5 script. With this tilt information, the accumulated physical weather element will then move around the screen to be level with the ground.

The following images and videos show part of the system working with light rain, heavy rain, and snow. The current script is able to read the weather and report back to the user what it is outside. The simulation of the physical weather elements are also able to move around corresponding to the tilt angle accumulating at the bottom until the weather type changes.

Images:

Image of snow weather
Physical build of gyroscope sensor and teensy

Videos:

Video of Snow

Video of Light Rain

Video of Heavy Rain

Code:

Arduino

/*
    MPU6050 Triple Axis Gyroscope & Accelerometer. Simple Gyroscope Example.
    Read more: http://www.jarzebski.pl/arduino/czujniki-i-sensory/3-osiowy-zyroskop-i-akcelerometr-mpu6050.html
    GIT: https://github.com/jarzebski/Arduino-MPU6050
    Web: http://www.jarzebski.pl
    (c) 2014 by Korneliusz Jarzebski
*/

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

MPU6050 mpu;

double zPrev = 0;

void setup()
{
  Serial.begin(115200);

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

void loop()
{
  Vector rawGyro = mpu.readRawAccel();
  Vector normGyro = mpu.readNormalizeAccel();

  //  Serial.print(" Xraw = ");
  //  Serial.print(rawGyro.XAxis);
  //  Serial.print(" Yraw = ");
  //  Serial.print(rawGyro.YAxis);
  //  Serial.print(" Zraw = ");
  //  Serial.println(rawGyro.ZAxis);

  //  Serial.print(" Xnorm = ");
  double x = rawGyro.XAxis;
  double z = rawGyro.ZAxis;
  
  if (z < 20000) {
    z = z + 65000;
  }
  z = z*0.8 + 0.2*zPrev;
//  Serial.print(78000 - z);
//  Serial.print("\t");
  
  z = (78000 - z)/(78000 - 30000)*180;
  z = z - 130;
  Serial.print(z);
//  Serial.print("\t");

  zPrev = z;

  Serial.println();

  delay(10);
}

P5.JS

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;

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

// A wind direction vector
let wind;
// Circle position
let position;
var weather = 'sn';
var weather_String = 'Snow';
var startHeight = 50;
var currColor;

var currWidth = 0;
var currHeight = 0;

let angle = 0;

let windAngle = 0.0;
let windMag = 0.0;

var startTime = 0;
var waitTime = 1000;
var rainAccumulated = 0;
var numDrops = 0;

let numBalls = 0;
let spring = 0.01;
let gravity = 0.03;
let friction = -0.2;
let balls = [];

function setup() {
  if (window.innerWidth > minWidth){
  width = window.innerWidth;
  } else {
    width = minWidth;
  }
  if (window.innerHeight > minHeight) {
    height = window.innerHeight;
  } else {
    height = minHeight;
  }
  //set up canvas
  createCanvas(width, height);
  noStroke();
  // Request the data from metaweather.com
  // let url = 'https://cors-anywhere.herokuapp.com/https://www.metaweather.com/api/location/2357536/';
  // loadJSON(url,gotWeather);
  // // Circle starts in the middle
  // position = createVector(width/2, height/2);
  // // wind starts as (0,0)
  // wind = createVector();
  // noStroke();
  // fill(255, 204);

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

  angleMode(DEGREES);

}

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);
   angle = latestData;
 }

function draw() {
  background(220);
  
  //Draw Text
  // translate(width/2, height/2);
  // rotate(angle);
  fill('black')
  textAlign(CENTER);
  textSize(50);
  text(weather_String, width/2, 60);
  textSize(20);
  var windText = 'Wind Speed: ' + windMag + '\t Wind Angle: ' + windAngle;
  text(windText, width/2, 100);
  text(latestData, width/2, 130);
  
  updateWeather();
  
  // angle = cos(millis()/1000);
  
  if (millis() - startTime > waitTime) {
    balls[numBalls] = new Ball(random(width), 0, random(20, 30), random(30, 50), numBalls, balls);
    numBalls +=1;
    startTime = millis();
  }
  
  balls.forEach(ball => {
    ball.collide();
    ball.move();
    ball.display();
  });
  
  
  // // This section draws an arrow pointing in the direction of wind
  // push();
  // translate(32, height - 32);
  // // Rotate by the wind's angle
  // rotate(wind.heading() + PI/2);
  // noStroke();
  // fill(255);
  // ellipse(0, 0, 48, 48);

  // stroke(45, 123, 182);
  // strokeWeight(3);
  // line(0, -16, 0, 16);

  // noStroke();
  // fill(45, 123, 182);
  // triangle(0, -18, -6, -10, 6, -10);
  // pop();
  
  // // Move in the wind's direction
  // position.add(wind);
  
  // stroke(0);
  // fill(51);
  // ellipse(position.x, position.y, 16, 16);

  // if (position.x > width)  position.x = 0;
  // if (position.x < 0)      position.x = width;
  // if (position.y > height) position.y = 0;
  // if (position.y < 0)      position.y = height;
}

function gotWeather(weather) {
  print(weather.title);
  let weather_today = weather.consolidated_weather[0]
  weather = weather_today.weather_state_abbr;
  weather_String = weather_today.weather_state_name;
  // print(weather);
  // print(weather_String);
  // Get the angle (convert to radians)
  windAngle = radians(Number(weather_today.wind_direction));
  // Get the wind speed
  windMag = Number(weather_today.wind_speed)/100;
  
  // Display as HTML elements
  // let temperatureDiv = createDiv(floor(weather_today.the_temp) + '&deg;C');
  // let windDiv = createDiv("WIND " + windmag + " <small>MPH</small>");
  
  // Make a vector
  wind = p5.Vector.fromAngle(angle);
}

function updateWeather() {
  switch(weather){
    case 'sn':
      waitTime = 300;
      fill('white')
      currWidth = 30;
      currHeight = 30;
      weather_String = 'Snow';
      break;

    case 'sl':
      weather_String = 'Sleet';
      break;
    case 'h':
      weather_String = 'Hail';
      break;
    case 't':
      weather_String = 'Thunderstorms';
      break;

    case 'hr':
      weather_String = 'Heavy Rain';
      waitTime = 100;
      fill(138, 183, 255)
      currWidth = 30;
      currHeight = 30;
      accumulate = true;
      break;

    case 'lr':
      weather_String = 'Light Rain';
      waitTime = 2000;
      fill(138, 183, 255)
      currWidth = 30;
      currHeight = 30;
      accumulate = true;
      break;

    case 's':
      weather_String = 'Showers';
      break;

    case 'hc':
      weather_String = 'Heavy Clouds';

      break;

    case 'lc':
      weather_String = 'Light Clouds';

      break;

    case 'c':
      weather_String = 'Clear';

      break;

    default:
      break;
  }
}

class Ball {
  constructor(xin, yin, win, hin, idin, oin) {
    this.x = xin;
    this.y = yin;
    this.vx = 0;
    this.vy = 0;
    this.w = win;
    this.h = hin
    this.id = idin;
    this.others = oin;
    // this.vxAdd = windMag*cos(windAngle);
    // this.vyAdd = windMag*sin(windAngle);
    this.vxAdd = 0;
    this.vyAdd = 0;
  }

  collide() {
    for (let i = this.id + 1; i < numBalls; i++) {
      // console.log(others[i]);
      let dx = this.others[i].x - this.x;
      let dy = this.others[i].y - this.y;
      let distance = sqrt(dx * dx + dy * dy);
      let minDist = this.others[i].w / 2 + this.w / 2;
      //   console.log(distance);
      //console.log(minDist);
      if (distance < minDist) {
        //console.log("2");
        let angle = atan2(dy, dx);
        let targetX = this.x + cos(angle) * minDist;
        let targetY = this.y + sin(angle) * minDist;
        let ax = (targetX - this.others[i].x) * spring;
        let ay = (targetY - this.others[i].y) * spring;
        this.vx -= ax;
        this.vy -= ay;
        this.others[i].vx += ax;
        this.others[i].vy += ay;
      }
    }
  }

  move() {
    var ygravity = gravity*cos(angle);
    var xgravity = gravity*sin(angle);
    this.vy += ygravity + this.vyAdd;
    this.vx += xgravity + this.vxAdd;
    this.x += this.vx;
    this.y += this.vy;
    if (this.x + this.w / 2 > width) {
      this.vxAdd = 0;
      // print("Hello");
      this.x = width - this.w / 2;
      this.vx *= friction;
    } else if (this.x - this.w / 2 < 0) {
      this.vxAdd = 0;
      this.x = this.w / 2;
      this.vx *= friction;
    }
    if (this.y + this.w / 2 > height) {
      this.vyAdd = 0;
      this.y = height - this.w / 2;
      this.vy *= friction;
    } else if (this.y - this.w / 2 < 0) {
      this.y = this.w / 2;
      this.vy *= friction;
    }
  }

  display() {
    ellipse(this.x, this.y, this.w, this.h);
  }
}

 

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

 

Virtual Window for Windowless Rooms Ideation

The idea for this project comes from an earlier project in this class from someone else involving sensing the weather outside and reporting it to the user. Where this project differs, however, is it will incorporate a visual representation of the weather inside a screen. The general functionality will include sensing the weather either through sensors or getting data from online and then interpreting that into visual representation that can either be displayed through p5.js or an LED screen controlled by a teensy. The two different styles will work the same way just with slightly different implementations.

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

Gestures to control Holograms (within limits)

We talked about how tiring gestural interfaces can be in class. I came across this project – the Holographic Theater- by Simmetrico while looking for some interactive design examples. I think it’s interesting that they choose to limit the gestures to a ‘portal’ area- maybe this kind of ‘area limit’ could support a type of ergonomics for gestural interfaces: https://www.leva.io/projects/holographic-theater

RUSSIAN COPPER COMPANY’S PAVILION 2019