Project tutorial
Kroebe Lights

Kroebe Lights

Have it your way.

  • 1,763 views
  • 0 comments
  • 4 respects

Components and supplies

12V Battery
×1
BJT
×1
Bluetooth Module
×1
Acrylic Glass
×6
Generic Underwater RGB Spotlight
×1
Liquid Pump (350 gph)
×1
A000066 iso both
Arduino UNO & Genuino UNO
×1
Tubing + Nozzle
×1
Android Tablet
×1
IR sensor
×1
IR receiver
×1

About this project

Brainstorming Process

After selecting Kroeber Fountain as our location of interest, we began to notice that it was a place at which people would congregate without interacting with one another. The fountain’s shape - most notably, its tiered steps - affords it the ability to serve as a seat where multiple people can sit in close proximity to one another and still manage to treat each other like strangers. 

We wanted to blur the boundaries between strangers’ personal space regions by providing them with an incentive for making contact with one another. Our first idea revolved around the idea of “twin” outlets - if a person wanted to charge their laptop, for instance, the outlet would not supply power unless its twin was also in use. We had discussed the idea of orienting the outlet in such a manner that it would force strangers to sit close to one another, and by restricting power to solitary individuals, we would also be forcing individuals to communicate with one another to reach their shared goal of charging their respective devices. Other ideas at making strangers work collaboratively took on an element of “gamification.” Strangers could work together to race boats, play music, or change the configuration of the fountain’s water pumps.

Once the idea of allowing people to alter the fountain’s state entered our minds, we began to view the concept of personal space in a different light.We began to view personal space as a space in which a person feels a sense of ownership. People spend the vast majority of their time in public spaces in which everything has been predetermined for us. We wanted to give users some agency, a way to make a public space a bit more personal. From there we set out to build Kroebe, the fountain that recognizes and remembers you.

Project Description

35,000 students roam the Berkeley campus and the Kroeber Fountain Beautification Project revolves around the idea of allowing these individuals to subtly personalize what has traditionally been a very public space. We accomplished this feat by creating a mobile application that gives people the ability to create a fountain show that reflects their personal preferences; a person can select their color or effect (for example, fade), height, and frequency (i.e., number of bursts per short interval of time) and register these settings to their device’s bluetooth signal. Each time thereafter that this bluetooth signal comes in range of the fountain, the previously defined fountain show will become activated. We are enabling individuals to exercise control over a public space for at least a brief moment, and to feel a sense of belonging. “Kroebe" is the fountain that recognizes and remembers you.

Observational Documentation

As we spent time observing our site, we observed a wide range of activities taking place in the vicinity of the fountain. The fountain is often used as a meeting point for people and clubs, most notably the Cal Running Club and Triathlon Club. While this particular usage of the fountain is social in nature, we also observed many people sitting on the fountain alone. This solitary time is meant to be relaxing, a claim reinforced by a Daily Cal article we discovered. This alone time could also be spent smoking; the fountain is often littered with used cigarette butts. The fountain is in close proximity to the intersection of Bancroft Way and College Avenue, a place where food trucks park daily, turning the fountain into a place to sit and eat food at. The fountain's role as a place to sit and enjoy food is further reinforced by the fact that it is situated across the street from Cafe Strada; people will buy drinks at Cafe Strada, and then walk across the street and enjoy them at Kroeber Fountain where they can sit in the warmth of the sun. 

Because Kroeber Plaza is a high traffic area in walking distance of Greek Row, many fraternities and sororities will hold fundraisers here. When we conducted our intentional observation of the site, TKE, Sigma Nu, and Tri Delta had organized a fundraiser benefiting St. Jude Children's Research Hospital. Other individuals endeavoring to take advantage of the high volume of traffic Kroeber experiences are protestors. One such example is a gentleman identified only by the lettering on his shirt - "Yoshua" - who was passing out Bibles and flyers, alerting students to the end of days. 

Kroeber Fountain is also the subject of a great deal of artwork. Kroeber Hall and Wurster Hall are both within walking distance of the fountain, and the art students and architecture students who study in these buildings often sketch the fountain. They also use the fountain as a place to sit as they sketch the objects, people, buildings, etc. surrounding the fountain.

Our interviews revealed that even though some people may not interact with the fountain directly, they still recognize and acknowledge its presence. Interviewees frequently talked about the fountain in terms of a landmark they pass by on their way to class. This piece of information helped shape the future outcome of our project as being something that people could interact with without having to stop and linger at the fountain; a person could experience a transient interaction with the fountain as he walked past it on the way to his final destination. 

Laser Cut Files

Main Arduino File (see Github for full header/cpp files)

kroeber.ino
Main Arduino File (see Github for full header/cpp files)

Warning: Embedding code files within the project story has been deprecated. To edit this file or add more files, go to the "Software" tab. To remove this file from the story, click on it to trigger the context menu, then click the trash can button (this won't delete it from the "Software" tab).

//Author: Felix Li
//Description: All the necessary Arduino code to run operation Kroeber. 

#include "Constants.h"
#include "IRCommands.h"
#include <SoftwareSerial.h>

//initialize Bluetooth
SoftwareSerial Bluetooth(RX, TX);

//State Machine for Bluetooth protocol
int state = LISTENING;
//Pointer to current configuration 
CurrentConfig configuration;
//for burst/frequency calculation 
long lastBurst = 0;
long burstRate = 0;
long burstDelay = 0;

long currentTime = 0;

//fp = function pointer
void repeatCommand(void (*fp)(void), int numTimes) {
  for (int i = 0; i< numTimes; i++) {
    (*fp)();
  }  
}

//Initialize the configuration 
void initializeConfig() {
  configuration.color = DEFAULT_COLOR;
  configuration.height = DEFAULT_HEIGHT;
  configuration.burst = DEFAULT_BURST;  
}


void printConfiguration() {
  Serial.println("Color: \t" + String(configuration.color));  
  Serial.println("Height: \t" + String(configuration.height));  
  Serial.println("Frequency: \t" + String(configuration.burst));  
}

void updateLight(int color) {
  switch (color) {
    
    case RED: {
      repeatCommand(&red, REPEAT);
      break; 
    } 
    
    case GREEN: {
      repeatCommand(&green, REPEAT);
      break;     
    }
    
    
    case BLUE: {
      repeatCommand(&blue, REPEAT);
      break;          
    }
    
    case WHITE: {
      repeatCommand(&white, REPEAT);
      break;          
    }    
    
     
    case RED2: {
      repeatCommand(&red2, REPEAT);
      break; 
    } 
    
    case GREEN2: {
      repeatCommand(&green2, REPEAT);
      break;     
    }
    
    
    case BLUE2: {
      repeatCommand(&blue2, REPEAT);
      break;          
    }
    
    case FLASH: {
      repeatCommand(&flash, REPEAT);
      break;          
    }    
    
     
    case RED3: {
      repeatCommand(&red3, REPEAT);
      break; 
    } 
    
    case GREEN3: {
      repeatCommand(&green3, REPEAT);
      break;     
    }
    
    
    case BLUE3: {
      repeatCommand(&blue3, REPEAT);
      break;          
    }
    
    case STROBE: {
      repeatCommand(&strobe, REPEAT);
      break;          
    }    
    
     
    case RED4: {
      repeatCommand(&red4, REPEAT);
      break; 
    } 
    
    case GREEN4: {
      repeatCommand(&green4, REPEAT);
      break;     
    }
    
    
    case BLUE4: {
      repeatCommand(&blue4, REPEAT);
      break;          
    }
    
    case FADE: {
      repeatCommand(&fade, REPEAT);
      break;          
    }    
    
     
    case RED5: {
      repeatCommand(&red5, REPEAT);
      break; 
    } 
    
    case GREEN5: {
      repeatCommand(&green5, REPEAT);
      break;     
    }
    
    
    case BLUE5: {
      repeatCommand(&blue5, REPEAT);
      break;          
    }
    
    case SMOOTH: {
      repeatCommand(&smooth, REPEAT);
      break;          
    }    
    
  } //end switch color
}

void updateHeight(int height) {
  analogWrite(HeightPWM, height);
}

void updateBurst(int burst) {
 if (burst == 0) {
   burstRate = 0;  
 } else {
   burstRate = MAX_BURSTS/burst + 1000;
 }
}

void run() {
  long diff = millis() - lastBurst;  
  if (lastBurst + burstRate > millis()) {
    analogWrite(HeightPWM, configuration.height);
    lastBurst = millis();      
    delay(100);
  } else {
    analogWrite(HeightPWM, 0);  
  }
}

void change() {
  int height = random(100, 255);
  configuration.height = height;
  int color = random(0, 20);
  configuration.color = color;
  int burst = random(0, 5);
  configuration.burst = 0;
  updateHardware();
 }
 
//update the hardware according to the current configuration 
void updateHardware() {
  updateLight(configuration.color);
  updateHeight(configuration.height);
  updateBurst(configuration.burst);  
}

// The setup() method runs once, when the sketch starts 
void setup()   {                
  // initialize the IR digital pin as an output:
  Bluetooth.begin(115200);
  pinMode(IRledPin, OUTPUT);      
  Serial.begin(9600);
  initializeConfig();
   updateHardware();
}
 
void loop()                     
{
  //Serial.println("main");
  //listen to bluetooth
    /*
    //Serial.println("Received!");
    int integerValue = 0;
    while(1) {
      char incomingByte = (char) Bluetooth.read();
      if (incomingByte == 'n') break;   // exit the while(1), we're done receiving
      if (incomingByte == -1) continue;  // if no characters are in the buffer read() returns -1
      Serial.println(incomingByte - 48);    
      integerValue *= 10;  // shift left 1 decimal place
      // convert ASCII to integer, add, and shift left 1 decimal place
      integerValue = ((incomingByte - 48) + integerValue);
    }
    Serial.println(integerValue);   // Do something with the value
  
  */
 
 //For random configurations  
 /*
  if (millis() > currentTime + 5000) {
    change();
    currentTime = millis();
    printConfiguration();
  }
  */
  
    
  if (Bluetooth.available() > 0) {
    char incomingByte = (char) Bluetooth.read();
    int intRead = incomingByte;
  
    //Serial.println("State" + String(state));
    switch(state) {
      case LISTENING: {
          if (intRead == START_COMMAND) {
            state = COLOR;
          }
        break;  
      } 
      
      case COLOR: {
        configuration.color = intRead;
        state =  HEIGHT;
        break;
      }
      
      case HEIGHT: {
        int adjusted = map(intRead, 0, 255, 100, 255);
        configuration.height = adjusted;
        state = BURST;
        break;
      }
      
      case BURST: {
        configuration.burst = intRead;
        //Bluetooth.print(ACK); //send acknowledgement 
        state = LISTENING;
        updateHardware(); // updates the Hardware
        printConfiguration();
        break;
      }
    } //end switch
  }
    
  
 // printConfiguration(); 
 //run();
}

Step by Step Guide (Hardware)

Step 1: Gather

Buy and collect the materials needed.

Step 2: Test Equipment

For the liquid pump, submerge it in water and feed it 12V to see if water is pushed out from the barbed outlet.

For the light, also attach 12V and see if the light turns on. Also, play with the remote controller to toggle the color.

Step 3: Create Encasing for Pump

Refer to the Illustrator files in this Hackster.io page. We measured the dimensions of the pump and created the boxes with boxmaker (boxmaker.rahulbotics.com). We then drew designs of "Critical Making," our Kroeber logo, and our names onto the design. After cutting our our pieces using the laser cutter, we glued the pieces together using acrylic adhesive. We found that it was extremely helpful to use the clamps to keep the pieces in place when waiting for them to dry. Essentially, the casing was used to prevent the liquid pump from moving around wherever you want to place it.

Step 4: Tubing and Nozzle for Pump

Go to Home Deport and pray that you get a water fountain savant like we did (Shoutout to Haroon)! Specifically, the tubing should be 3/4'' in its inner diameter to fit snugly onto the barbed outlet of the liquid pump, then get a 3/4'' adapter for the tubing, and finally a nozzle you can attach at the end. This step is maximize the height generated by the fountain.

Step 5: Use PWM to Vary Intensity Fed Into the Pump

For this step, I used a PWM pin from an Arduino Uno and fed that into the B terminal of a BJT transistor. Basically, this transistor serves as a switch with the load connected at the Collector junction, and the battery ground connected at the Emitter junction.


All you need to know to understand this is that you're varying voltage fed into the BJT, which in turn varies how much power eventually does get fed into the liquid pump.

Step 6: Reverse Engineering the Light

If you are buying the exact same materials we used for this project, you will notice that the underwater spotlight LED came with an IR remote controller. However, we obviously can't interface the tablet's app with this so we must create our own remote controlled by the Arduino.


The progress can be broken down as follows:

1. Use IR receiver and see what IR command sequences are being sent.

2. Use your own IR sensor to send the exact same bits to the LED light.


See https://learn.adafruit.com/ir-sensor for more details.

Step 7: Bluetooth Module

Add the bluetooth module to the Arduino by connecting two digital IO pins to it and feeding it power.

Step 8: Put Everything Together

Now that you got Kroeber (the mounted liquid pump) and Pretty Lights (the led light) both up and running individually, it's time to combine them! Just wire the appropriate pins to the Arduino and fit all electronics into an encasing that shields them from splashing water.


For example,

1 PWM pin for BJT.

2 Digital IO pin for RX/TX for Bluetooth

1 Digital IO pin for IR sensor

Step 9: Stop.... and Appreciate

Take this moment to realize that you're done with hardware and appreciate your work of art.

Warning: embedding parts within the project story has been deprecated. To edit, remove or add more parts, go to the "Hardware" tab. To remove this list from the story, click on it to trigger the context menu, then click the trash can button (this won't delete it from the "Hardware" tab).
12V Battery
BJT
Bluetooth Module
Acrylic Glass
Generic Underwater RGB Spotlight
Liquid Pump (350 gph)
A000066 iso both
Arduino UNO & Genuino UNO
Tubing + Nozzle
Android Tablet
IR sensor
IR receiver

Video: Demo Video

Video: Live Demo

Video: Testing out light angles

Process Photos

Sketches

Our Beautiful Code

Step by Step Guide (Software)

Step 1: Pair Tablet with Bluetooth on Arduino

On the tablet, navigate to the settings menu and begin scanning for Bluetooth devices. Find the Arduino's Bluetooth name and pair with it.

Step 2: Connect Table with Arduino

In the first activity of the application, an activity that does not have a UI but links to the UI of the first page, connect to the already paired Arduino's Bluetooth. This is necessary in order to send the eventual configurations.

Step 3: Scan for Bluetooth

Create a button that upon click scans for bluetooth discoverable phones. From there, populate a list from which that the user can select his or her device.

Step 4: Store Bluetooth Device

Upon selection of a device, create an object that will store that devices name/address and eventually the fountain configurations that correspond with it. Pass this object to the next activity once the user clicks "Next".

Step 5: Store Configuration Details

As the user selects specific configurations, store these configurations in the object created at the previous step. The configurations will each be stored as numbers that will be sent to the Arduino.

Step 6: Add Object to Hash Table

Once the user has finalized his or her selections and clicked "Submit," the object must be stored into our hash table to be found later. The object will be stored with its name/address as the key and the object itself as the value.

Step 7: Continuously Scan for Saved Devices/Configurations in a Background Thread

Create a background thread that scans for Bluetooth devices every 10 seconds. When a list of devices is found, check the name/address of that specific device to see if it is stored in the hash table. If the device is in the hash table, proceed to the next step.

Step 8: Send Configuration to Arduino

If the device is in the hash table, it is time to communicate with the Arduino! Prompt the Arduino by sending a predefined number to inform the Arduino that it should expect configuration settings. Then, send the numbers corresponding to the specific configuration settings.

Step 9: Watch the Fountain Soar!

Upon receiving the numbers, the Arduino will play the specific configuration corresponding to the numbers. Voila!

Observational Images

Code

kroeber.inoC/C++
kroeber.ino
//Author: Felix Li
//Description: All the necessary Arduino code to run operation Kroeber. 

#include "Constants.h"
#include "IRCommands.h"
#include <SoftwareSerial.h>

//initialize Bluetooth
SoftwareSerial Bluetooth(RX, TX);

//State Machine for Bluetooth protocol
int state = LISTENING;
//Pointer to current configuration 
CurrentConfig configuration;
//for burst/frequency calculation 
long lastBurst = 0;
long burstRate = 0;
long burstDelay = 0;

long currentTime = 0;

//fp = function pointer
void repeatCommand(void (*fp)(void), int numTimes) {
  for (int i = 0; i< numTimes; i++) {
    (*fp)();
  }  
}

//Initialize the configuration 
void initializeConfig() {
  configuration.color = DEFAULT_COLOR;
  configuration.height = DEFAULT_HEIGHT;
  configuration.burst = DEFAULT_BURST;  
}


void printConfiguration() {
  Serial.println("Color: \t" + String(configuration.color));  
  Serial.println("Height: \t" + String(configuration.height));  
  Serial.println("Frequency: \t" + String(configuration.burst));  
}

void updateLight(int color) {
  switch (color) {
    
    case RED: {
      repeatCommand(&red, REPEAT);
      break; 
    } 
    
    case GREEN: {
      repeatCommand(&green, REPEAT);
      break;     
    }
    
    
    case BLUE: {
      repeatCommand(&blue, REPEAT);
      break;          
    }
    
    case WHITE: {
      repeatCommand(&white, REPEAT);
      break;          
    }    
    
     
    case RED2: {
      repeatCommand(&red2, REPEAT);
      break; 
    } 
    
    case GREEN2: {
      repeatCommand(&green2, REPEAT);
      break;     
    }
    
    
    case BLUE2: {
      repeatCommand(&blue2, REPEAT);
      break;          
    }
    
    case FLASH: {
      repeatCommand(&flash, REPEAT);
      break;          
    }    
    
     
    case RED3: {
      repeatCommand(&red3, REPEAT);
      break; 
    } 
    
    case GREEN3: {
      repeatCommand(&green3, REPEAT);
      break;     
    }
    
    
    case BLUE3: {
      repeatCommand(&blue3, REPEAT);
      break;          
    }
    
    case STROBE: {
      repeatCommand(&strobe, REPEAT);
      break;          
    }    
    
     
    case RED4: {
      repeatCommand(&red4, REPEAT);
      break; 
    } 
    
    case GREEN4: {
      repeatCommand(&green4, REPEAT);
      break;     
    }
    
    
    case BLUE4: {
      repeatCommand(&blue4, REPEAT);
      break;          
    }
    
    case FADE: {
      repeatCommand(&fade, REPEAT);
      break;          
    }    
    
     
    case RED5: {
      repeatCommand(&red5, REPEAT);
      break; 
    } 
    
    case GREEN5: {
      repeatCommand(&green5, REPEAT);
      break;     
    }
    
    
    case BLUE5: {
      repeatCommand(&blue5, REPEAT);
      break;          
    }
    
    case SMOOTH: {
      repeatCommand(&smooth, REPEAT);
      break;          
    }    
    
  } //end switch color
}

void updateHeight(int height) {
  analogWrite(HeightPWM, height);
}

void updateBurst(int burst) {
 if (burst == 0) {
   burstRate = 0;  
 } else {
   burstRate = MAX_BURSTS/burst + 1000;
 }
}

void run() {
  long diff = millis() - lastBurst;  
  if (lastBurst + burstRate > millis()) {
    analogWrite(HeightPWM, configuration.height);
    lastBurst = millis();      
    delay(100);
  } else {
    analogWrite(HeightPWM, 0);  
  }
}

void change() {
  int height = random(100, 255);
  configuration.height = height;
  int color = random(0, 20);
  configuration.color = color;
  int burst = random(0, 5);
  configuration.burst = 0;
  updateHardware();
 }
 
//update the hardware according to the current configuration 
void updateHardware() {
  updateLight(configuration.color);
  updateHeight(configuration.height);
  updateBurst(configuration.burst);  
}

// The setup() method runs once, when the sketch starts 
void setup()   {                
  // initialize the IR digital pin as an output:
  Bluetooth.begin(115200);
  pinMode(IRledPin, OUTPUT);      
  Serial.begin(9600);
  initializeConfig();
   updateHardware();
}
 
void loop()                     
{
  //Serial.println("main");
  //listen to bluetooth
    /*
    //Serial.println("Received!");
    int integerValue = 0;
    while(1) {
      char incomingByte = (char) Bluetooth.read();
      if (incomingByte == 'n') break;   // exit the while(1), we're done receiving
      if (incomingByte == -1) continue;  // if no characters are in the buffer read() returns -1
      Serial.println(incomingByte - 48);    
      integerValue *= 10;  // shift left 1 decimal place
      // convert ASCII to integer, add, and shift left 1 decimal place
      integerValue = ((incomingByte - 48) + integerValue);
    }
    Serial.println(integerValue);   // Do something with the value
  
  */
 
 //For random configurations  
 /*
  if (millis() > currentTime + 5000) {
    change();
    currentTime = millis();
    printConfiguration();
  }
  */
  
    
  if (Bluetooth.available() > 0) {
    char incomingByte = (char) Bluetooth.read();
    int intRead = incomingByte;
  
    //Serial.println("State" + String(state));
    switch(state) {
      case LISTENING: {
          if (intRead == START_COMMAND) {
            state = COLOR;
          }
        break;  
      } 
      
      case COLOR: {
        configuration.color = intRead;
        state =  HEIGHT;
        break;
      }
      
      case HEIGHT: {
        int adjusted = map(intRead, 0, 255, 100, 255);
        configuration.height = adjusted;
        state = BURST;
        break;
      }
      
      case BURST: {
        configuration.burst = intRead;
        //Bluetooth.print(ACK); //send acknowledgement 
        state = LISTENING;
        updateHardware(); // updates the Hardware
        printConfiguration();
        break;
      }
    } //end switch
  }
    
  
 // printConfiguration(); 
 //run();
}

Comments

Similar projects you might like

LED Emergency Lights using WS2812 RGB LED Module

Project tutorial by bigboystoys13

  • 10,004 views
  • 5 comments
  • 20 respects

Arduino and AC Devices - Automatic Lights

Project tutorial by Ahmed Hamdy

  • 21,981 views
  • 7 comments
  • 51 respects

Sparkly Arduino Christmas Lights

Project tutorial by Donald Doerres

  • 9,703 views
  • 0 comments
  • 7 respects

Happy Birthday: Lights and Sounds

Project tutorial by jsheng

  • 3,083 views
  • 1 comment
  • 17 respects

Programmable Holiday Lights

Project tutorial by Arduino_Scuola

  • 2,193 views
  • 0 comments
  • 5 respects

Christmas and More Lights

Project tutorial by Team FunguyPro

  • 1,739 views
  • 0 comments
  • 4 respects
Add projectSign up / Login