Are you prepared for the next couple hours weather?

Problem

With quarantine, I have not been leaving the house all that much, so I seem to have lost my intuition about temperature and my habit of always checking the weather in the morning. This has led me to be surprised by how hot/cold it gets if I’ve gone on a long walk or gone grocery shopping as well as when it starts raining before I’ve gotten home. I’ve found myself really cold in just a sweatshirt in the evening, hot when I wore thick sweatpants on a day that became unusually warm, and caught in the rain unprepared in the infrequent times I’ve been outside for an hour or two this semester.  This led me to the idea of a system that when I leave the house, would let me know about weather changes in the 2 following hours without bothering me.

Proposed Solution

To solve this problem, I propose a small device that can be placed on the wall near the front door that will detect when you leave the house and give notifications appropriate to the current weather and the changes. The opening of the door is detected with a magnetic reed switch.  Whether you are entering or exiting the house is decided based on if the PIR motion sensor detects motion right before the door opens. If you are entering the house, motion will be detected. Notifications will only be given when you are leaving the house, so when no motion is detected. This device uses an esp32 board (I chose the Adafruit Huzzah32) to get the current and hourly weather from the OpenWeather API over WiFi. The current temperature and weather condition as well as that of the next 2 hours are taken from the fetched information. If it will rain or snow, and it isn’t already raining or snowing, the buzzer will play a specific tone sequence 3 times as the door is opening. I chose a sequence that sounds to me a bit like “oh hooray” when it will snow because I love snow. If it will rain, I chose a more warning sound alert because it’s usually not fun to get caught in the rain. The main reason I chose to not give an alert if it will snow/rain when it already is is because you can clearly see what’s happening and should be prepared for it to keep raining/snowing. The buzzer was chosen rather than a speaker because it was loud enough to catch my attention easily, but unlike the speaker it isn’t loud enough to be annoying. I chose to have the LED strip light up to let you know it will be colder or hotter by 5 or more degrees Fahrenheit as that’s when the temperature change bothers me. Red corresponds to hotter and blue to colder. The LED strip will flash for 2 seconds once the door closes to get your attention and then for another 5 seconds it will stay that color so that you can still see the alert but not get annoyed by it as you are leaving. In the next section, you can see demo videos of the cases for this device.

Proof of Concept

Outside videos showing operation when set up:

Demo while leaving the house showing the snow and colder notifications.

Demo while entering the house, showing how there’s no notifications even though the temperature gets colder enough and it will snow.

Videos taken inside to show the different cases:

Demo showing how there are no notifications when temperature doesn’t change enough and it won’t snow or rain.

Demo showing when it just gets colder.

Demo showing when it just gets hotter.

Demo showing when it just will rain.

Demo showing when it just will snow.

Demo showing when it just will rain and is currently raining.

Changes for real implementation

Currently, my device is easily seen and looks bad especially because I had to use so much duct tape for things to stick. To fix this, I would use command strips instead of duct tape to attach everything to the door and wall. The wires for the magnetic reed switch would be all white to blend more. The main board part would be soldered together and put into a box then place under the decorative wreath.

In the final version, a rechargeable 9V battery would be stepped down to 5V would power the LED strip and PIR motion sensor rather than my uno plugged into my laptop which I used because my 9V battery was drained. Using a usb wall plug, I would power the esp32 board rather than my laptop’s port. The outlet my laptop is plugged into would be used for this.

I would run the LED strip wires across the roof of the porch and put the LED strip where the green circle is. This way, it would be where you face while leaving the house and is in the line of sight for going down the porch stairs.

Schematic:

Code:

Note: You will have to enter in your own WiFi information as well as API key- I don’t want to just broadcast that.

//used this tutorial for getting weather: https://techtutorialsx.com/2018/03/17/esp32-arduino-getting-weather-data-from-api/
#include <WiFi.h>
#include <HTTPClient.h>
const char* ssid = "YOUR WIFI NAME";
const char* password =  "YOUR WIFI PASSWORD";

const String endpoint = "https://api.openweathermap.org/data/2.5/onecall?lat=40.445227&lon=-79.934243&exclude=minutely,daily,alerts&units=imperial&appid=";//CHANGE TO LOCATION YOU WANT DATA FROM
const String key = "YOUR UNIQUE KEY";

const int doorSwitchPin = 14;
int justOpened = 0;
int foundWeather = 0;
//const int LEDpin=12;
#include <NeoPixelBus.h>
const int LEDstripPin = 12;
const int numLEDs = 10;
NeoPixelBus<NeoGrbFeature, Neo800KbpsMethod> strip(numLEDs, LEDstripPin);
long startTime;

int hotter = 0;
int colder = 0;
int currentlyRaining = 0;
int currentlySnowing = 0;
int willRain = 0;
int willSnow = 0;
int rainWarning = 0;
int snowWarning = 0;
int maskWarning = 0;
int haveMask = 0;
String content;
#include <Tone32.h>

#define BUZZER_PIN A0
#define BUZZER_CHANNEL 0


const int PIRpin = 27;
int pirVal;

void pirISR() {
  pirVal = digitalRead(PIRpin);
}

//RFID stuff mainly from this tutorial https://randomnerdtutorials.com/security-access-using-mfrc522-rfid-reader-with-arduino/
//#include <SPI.h>
//#include <MFRC522.h>
//
//#define SS_PIN 23
//#define RST_PIN 15
//MFRC522 mfrc522(SS_PIN, RST_PIN);   // Create MFRC522 instance.

void setup() {

  Serial.begin(115200);
  //  SPI.begin();      // Initiate  SPI bus
  //  mfrc522.PCD_Init();   // Initiate MFRC522
  pinMode(doorSwitchPin, INPUT);
  pinMode(PIRpin, INPUT);
  attachInterrupt(digitalPinToInterrupt(PIRpin), pirISR, CHANGE);
  //pinMode(LEDpin, OUTPUT);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi..");
  }

  Serial.println("Connected to the WiFi network");
  // this resets all the neopixels to an off state
  strip.Begin();
  strip.Show();
}

void loop() {
  if ((WiFi.status() == WL_CONNECTED)) { //Check the current connection status
    HTTPClient http;
    if (digitalRead(doorSwitchPin) == 0) {//door opened
      //added measure so lights off when the door is opened, will immediately turn of lights if you open it while they are on
      for (int i = 0; i < numLEDs; i = i + 1) {
        strip.SetPixelColor(i, RgbColor(0, 0, 0));
      }
      strip.Show();
      if (pirVal == 0) { //no motion detected at door near knob outside, so ur opening from inside
        //        if (haveMask == 0) { // Look for new cards
        //          if ( ! mfrc522.PICC_IsNewCardPresent())
        //          {
        //            //return;
        //          }
        //          // Select one of the cards
        //          if ( ! mfrc522.PICC_ReadCardSerial())
        //          {
        //            //return;
        //          }
        //        }
        //        //Show UID on serial monitor
        //        // Serial.print("UID tag :");
        //        content = "";
        //        byte letter;
        //        for (byte i = 0; i < mfrc522.uid.size; i++)
        //        {
        //          //          Serial.print(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " ");
        //          //          Serial.print(mfrc522.uid.uidByte[i], HEX);
        //          content.concat(String(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : " "));
        //          content.concat(String(mfrc522.uid.uidByte[i], HEX));
        //        }
        //        content.toUpperCase();
        //
        //        if (content.substring(1) == "ED E4 86 B9" || content.substring(1) ==  "9D 65 7D B9" ) {//change here the UID of the card/cards that you want to give access
        //          int haveMask = 1;
        //        }
        //        else { //dont have mask
        //          if (maskWarning == 0) {
        //            for (int count = 0; count < 4; count++) {
        //              tone(BUZZER_PIN, NOTE_D4, 500, BUZZER_CHANNEL);
        //  noTone(BUZZER_PIN, BUZZER_CHANNEL);
        //  tone(BUZZER_PIN, NOTE_C4, 500, BUZZER_CHANNEL);
        //  noTone(BUZZER_PIN, BUZZER_CHANNEL);
        //            }
        //            maskWarning = 1; //been warned
        //          }
        //        }
        if (foundWeather == 0) {//havent calculated the weather for this opening yet
          // Serial.println("calculating");
          http.begin(endpoint + key); //Specify the URL
          int httpCode = http.GET();  //Make the request

          if (httpCode > 0) { //Check for the returning code

            String payload = http.getString();
            int first1 = payload.indexOf("temp");//gives index of the t
            int start1 = first1 + 6;//add indexso that I'm at the number
            int end1 = payload.indexOf(',', start1);
            Serial.println(payload);
            //Serial.println(start1);
            float currentTemp = payload.substring(start1, end1).toFloat();//making string into float so that I can do math with it

            int first2 = payload.indexOf("temp", end1);
            int start2 = first2 + 6;
            int end2 = payload.indexOf(',', start2);
            float hrOneTemp = payload.substring(start2, end2).toFloat();

            int first3 = payload.indexOf("temp", end2);
            int start3 = first3 + 6;
            int end3 = payload.indexOf(',', start3);
            float hrTwoTemp = payload.substring(start3, end3).toFloat();

            Serial.print("Current temp: ");
            Serial.print(currentTemp);
            Serial.print("\t");
            Serial.print("Temp in 1 hr: ");
            Serial.print(hrOneTemp);
            Serial.print("\t");
            Serial.print("Temp in 2 hrs: ");
            Serial.println(hrTwoTemp);
            if (((hrOneTemp - currentTemp) > 5) || ((hrTwoTemp - currentTemp) > 5)) { //would've been 5 but not fluctuating much at all recently
              hotter = 1;
            }
            else {
              hotter = 0;
            }
            if (((currentTemp - hrOneTemp) > 5) || ((currentTemp - hrTwoTemp) > 5)) { //would've been 5 but not fluctuating much at all recently
              colder = 1;
            }
            else {
              colder = 0;
            }
            int first4 = payload.indexOf("temp", end3);//just using to know around where hour 3 is
            int checkRain = payload.indexOf("Rain");
            if (checkRain != -1) { //means doesn't rain at all in huge data set
              if (checkRain < first2) {
                currentlyRaining = 1;
                willRain = 0; //dont care if it rains later as can already see it's raining
              }//don't bother finding index of another rain bc if it's already raining don't need to see if it'll rain in next 2 hours
              else if (checkRain < first3) {
                willRain = 1; //will rain in an hr
              }
              else if (checkRain < first4) {
                willRain = 1; //will rain in 2 hours
              }
            }
            else {
              willRain = 0;
            }
            int checkSnow = payload.indexOf("Snow");
            if (checkSnow != -1) {
              if (checkSnow < first2) {
                currentlySnowing = 1;
                willSnow = 0;
              }
              else if (checkSnow < first3) {
                willSnow = 1;
              }
              else if (checkSnow < first4) {
                willSnow = 1;
              }
            }
            else {
              willSnow = 0;
            }
            willRain = 1;
            Serial.print("Currently raining: ");
            Serial.print(currentlyRaining);
            Serial.print("\t");
            Serial.print("Will rain: ");
            Serial.println(willRain);
            Serial.print("Currently snowing: ");
            Serial.print(currentlySnowing);
            Serial.print("\t");
            Serial.print("Will snow: ");
            Serial.println(willSnow);
            if (willRain) {
              if (rainWarning == 0) {
                for (int count = 0; count < 4; count++) {//play sequence 3x while door is being closed/your just stepping outside
                  tone(BUZZER_PIN, NOTE_C4, 100, BUZZER_CHANNEL);
                  noTone(BUZZER_PIN, BUZZER_CHANNEL);
                  tone(BUZZER_PIN, NOTE_A4, 200, BUZZER_CHANNEL);
                  noTone(BUZZER_PIN, BUZZER_CHANNEL);
                  tone(BUZZER_PIN, NOTE_B4, 200, BUZZER_CHANNEL);
                  noTone(BUZZER_PIN, BUZZER_CHANNEL);
                }
                rainWarning = 1;//so warning doesn't just keep playing if door is open
              }
            }
            if (willSnow) {
              if (snowWarning == 0) {
                for (int count = 0; count < 4; count++) {
                  tone(BUZZER_PIN, NOTE_D4, 100, BUZZER_CHANNEL);
                  noTone(BUZZER_PIN, BUZZER_CHANNEL);
                  tone(BUZZER_PIN, NOTE_E4, 500, BUZZER_CHANNEL);
                  noTone(BUZZER_PIN, BUZZER_CHANNEL);
                  tone(BUZZER_PIN, NOTE_C4, 300, BUZZER_CHANNEL);
                  noTone(BUZZER_PIN, BUZZER_CHANNEL);
                }
                snowWarning = 1;
              }
            }
          }

          else {
            Serial.println("Error on HTTP request");
          }

          http.end(); //Free the resources
          foundWeather = 1;
        }
      }
    }
    //}
    else {//door closed again
      if (foundWeather == 1) {
        startTime = millis();
        //resetting the variables above
        rainWarning = 0;
        snowWarning = 0;
        maskWarning = 0;
        haveMask = 0;
        content = "";
      }
      foundWeather = 0;


      if (hotter) {
        if (millis() - startTime < 2000) { //flash to get attention
          static int blinkStart = millis();
          for (uint16_t i = 0; i < numLEDs; i++) {//this rotates through each LED on the strip so that each one will be red
            byte x = millis() - 20 * i;
            strip.SetPixelColor(i, RgbColor(255 - x, 0, 0));
          }
          strip.Show();
          if (millis() - blinkStart >= 10) {
            for (uint16_t i = 0; i < numLEDs; i++) {
              strip.SetPixelColor(i, RgbColor(0, 0, 0));
            }
            strip.Show();
            blinkStart = millis();
          }
        }
        else if (millis() - startTime < 7000) {//stays on just lights as attention probably already caught and dont want to annoy
          for (uint16_t i = 0; i < numLEDs; i++) {
            byte x = millis() - 20 * i;
            strip.SetPixelColor(i, RgbColor(255 - x, 0, 0));
          }
        }
        else {
          for (int i = 0; i < numLEDs; i = i + 1) {
            strip.SetPixelColor(i, RgbColor(0, 0, 0));
          }
          strip.Show();
        }
      }
      else if (colder) {
        if (millis() - startTime < 2000) { 
          static int blinkStart = millis();
          for (uint16_t i = 0; i < numLEDs; i++) {
            byte x = millis() - 20 * i;
            strip.SetPixelColor(i, RgbColor(0, 0, 255 - x));
          }
          strip.Show();
          if (millis() - blinkStart >= 10) {
            for (uint16_t i = 0; i < numLEDs; i++) {
              strip.SetPixelColor(i, RgbColor(0, 0, 0));
            }
            strip.Show();
            blinkStart = millis();
          }
        }
        else if (millis() - startTime < 7000) {
          for (uint16_t i = 0; i < numLEDs; i++) {
            byte x = millis() - 20 * i;
            strip.SetPixelColor(i, RgbColor(0, 0, 255 - x));
          }
        }
        else {
          for (int i = 0; i < numLEDs; i = i + 1) {
            strip.SetPixelColor(i, RgbColor(0, 0, 0));
          }
          strip.Show();
        }
      }
    }
  }
}

Notes on taking project further and functioning that was decided against

  • The idea of having an RFID scanner so that notifications are only given when you aren’t prepared is really interesting and would add great functionality, but doesn’t work well with this project. The RFID scanner I have took too long to read the card and held up the entire system; it took long enough that you could already make it to the stairs before a notification was given. Additionally, the card/tag had to be touching the scanner which isn’t realistic for someone leaving the house to do. Finally, it can’t read multiple cards right next to each other. The first of these problems could be fixed with better equipment, but I don’t think the other two would get solved. If someone fixed these issues and wanted to make my project, especially more commercial, the add-ons would be that the clothes and items that pertain to certain type of weather (ie sweatshirt, raincoat, boots, gloves, etc) could be scanned on the way out. Multiple tags can be used for the same category and the tags could be small and machine washable. Similar to this, in the age of covid, masks could be checked for and if you don’t have one, be warned with an alarm sound. Moreover, our school cards are RFID cards, so I could have set it to react according to who is leaving the house or even only just for me. To really expand, a To-do list could be linked via the internet and if you leave close to the time of grocery shopping, you could get a notification if you don’t have reusable bags.
  • Instead of specific sound sequences, I tried to use the TTS esp library with a speaker and amplifier circuit. This is loud enough with the amplifier circuit, however, it isn’t very understandable. I wanted it to say, rain, snow, and mask (with the RFID implemented but again it didn’t work fast enough). Even changing the text given to try and make it sound better by spelling more like pronunciation didn’t help much.
  • Something relatively easy to do with IFTTT is to send a text message when certain criteria are met. I could have had a text message to me if it was going to be colder, hotter, rain, or snow. I decided against this because it would only really make sense if who was leaving was detected and free accounts only have 3 applets included so I didn’t want to use another on this especially since for me I probably wouldn’t even look at or notice that notification until I’m pretty far away.

Notes about using the esp32 board

In general, it takes much longer to compile and upload to the board than it did for the uno.

Certain libraries that work with the uno don’t work with this and you need to download different ones in general or the same ones modified to work with esp32s.

  • I needed to use the Tone32 library instead of regular one.
  • The PololuLedStrip library is incompatible and so I instead used NeoPixelBus.
  • Not for this project, but for the other one I’m working on, I needed to use download an esp servo library and still included Servo.h like usual.

Interactive paper menu interactions

As technology advances, many interactions are drastically changing. When I came back to China, I was shocked by how mobile ordering in restaurants is widely adopted. It went from ordering with a server, to ordering using a tablet/ipad, to a table of people ordering from the same phone, to now where everyone can contribute to the same order using their own phone. Just like many people who prefer reading a paper book, I really enjoyed flipping through menus at restaurants.

However, we cannot ignore the benefit of having electronic orders: the order goes directly to the kitchen and all orders are automatically  stored digitally. To combine the paper feel of the menu and the benefit of having a digital menu, this project explores 3 ways of interactions that can be used in a interactive paper menu using paper circuits.

The initial goal was to have a full menu, but I realized it was too ambitious so I scaled down the project, but I did make these designs for a imaginary make-your-own burger restaurant. I will discuss more about the original design later.

So for this project, what I ended up doing was 3 different interactions:

    • presses (equivalent to a button)
    • slider (equivalent to a linear potentiometer)
    • wheel (equivalent to a circular potentiometer)

I won’t include a schematic here because paper circuits are quite different from using wires and parts. And in fact, the only materials I used here is:

    • an arduino uno

      Conductive tape, resistors, and SMD LEDs
    • wires to connect the paper circuit to arduino
    • solder & soldering iron to connect wires&resistors&LEDs to paper circuit
    • conductive tape (conductive on both sides, which made the fabrication much easier!)
    • resistors
    • SMD LEDs
    • paper
    • tapes

Here is what the paper circuit initially looked like for testing. On the top we have the wheel, then the slider,and those taped cardboard squares are presses.

First, let’s look at the wheel. 3 resistors(for 4 segments) are used, and they are connected in sequence with one end to 5v and one end to ground. We use an analog pin(connection to the analog pin not included here) and use the read value to determine which segment of the wheel is in contact with pin.

Then, we have the slider. Similar to the wheel, the wirings of it is exactly the same, except for they are placed linearly on paper. We used 4 resistors here because  we have 5 segments.

Presses are simply buttons. On the paper, we have one tape connected to the ground, and one tape connected to a digital pin (with internal pullup resistor). We use non-conductive tape to tape these “buttons” on top gap between the ‘ground’ tape and ‘digital pin’ tape. When the ‘button’ is pressed, the conductive part of the button connects the ‘ground’ tape and ‘digital pin’ tape, and thus makes the value change for the digital pin read.

Working with conductive tape was definitely more tedious and involves more planning compared with using wires, but one thing I found convenient was that to make  2 pieces of overlapping conductive tape non-conductive with each other, simply place a piece of non-conductive tape between the two, which is why in some of my photos, you see traces with different purposes “overlap” with each other.

In the image below, we added a block of conductive tape for the wheel, and a line for the slider, they are connected to the analog pins that read the values.  The 2 pieces on the right are used to create connections between these newly added circuits and the ones in the previous image.

So that was the first layer of the circuit, I call them input layer. Now let’s look a the second layer, the output(LEDs) layer. Each LED is wired to a digital pin with 220 ohms resistors in between and the other side to ground.

Then I connected the two layers by taping them together.

And finally, I put the graphic layer on the top. Notice the two pieces coming out of the graphic layer, they are our means of interactions. And they connect the layers simply by going through the papers.

So before this final version of the project, I tried a variety of things.

Here are some initial playing with the paper circuit. On the top left, I tried using graphite pencil marks as resistors, while it works, I thought it was too much trouble to make it as robust as I would like.

Below was my attempt to use a single analog pin to read 15 button/presses inputs. While it worked when I tested with tactile buttons, the readings again was not robust enough for the effect I needed.

The interaction I originally envisioned was that we have 2 wheels, a slider, and 16 presses on the right. And as things are being selected, the corresponding part on the burger gets lighted up. And once all the parts are lighted up, the order is ready to be completed. I gave up doing this when I realized that I don’t have enough digital pins, nor does the analog reading method works.

 

 

 

 

The main thing I struggled with was robust connections and readings, so I got lots of fuzzy readings that can’t just be fixed from the software side. For future works, getting an arduino mega with more pins would open up a lot more possibilities, and also some explorations in how to have robust connections using paper circuits.

#include <Bounce2.h>

const int WHEELPIN = A2;
const int WHEELLENGTH =4;
const int WHEELTHRESHOLD[] = {170, 512, 853, 1023};
const char *WHEELNAME[] = {"ciabatta", "lettuce wrap", "whole wheat", "brioche"};
char *bunselection = "none";

const int SLIDERPIN = A3;
const int SLIDERLENGTH = 5;
const int SLIDERTHRESHOLD[] = {1023, 897, 644, 340, 130};
const char *SLIDERNAME[] = {"well done", "medium well", "medium", "medium rare", "rare"};
char *cookselection = "none";

const int RESETBUTTON = 3;

const int TOPPINGSNUM = 4;
const int TOPPINGSPIN[] = {7, 6, 5, 4};

const int LEDNUM = 5;
const int LEDPINS[] = {13, 12, 11, 10, 9};
Bounce buttons[] = {Bounce(), Bounce(), Bounce(), Bounce(), Bounce()};
Bounce b = Bounce();

const char *TOPPINGNAME[] = {"red onion", "tomato",
                     "lettuce", "pickels"};
bool toppingsSeletion[] = {false, false, false, false};

void reset() {
  for (int i=0; i< TOPPINGSNUM; i++) {
    toppingsSeletion[i] = false;
  }
  bunselection = "none";
  cookselection = "none";
}
void setup() {
  Serial.begin(9600);
  for (int i=0; i< TOPPINGSNUM; i++) {
    buttons[i].attach (TOPPINGSPIN[i] , INPUT_PULLUP);
    buttons[i].interval(100);
  }
  buttons[TOPPINGSNUM].attach (RESETBUTTON , INPUT_PULLUP);
}

void loop() {
  // check for reset 
  buttons[TOPPINGSNUM].update();
  if (buttons[TOPPINGSNUM].fell()) {
    Serial.println("reset");
    reset();
  }

  // check for toppings selection
  for (int i=0; i< TOPPINGSNUM; i++) {
    buttons[i].update();
    if (buttons[i].fell()) {
      toppingsSeletion[i] = (!toppingsSeletion[i]);
      Serial.print(TOPPINGNAME[i]);
      if (toppingsSeletion[i]) {
        Serial.println(" is selected");
      }
      else {
        Serial.println(" is no longer selected");
      }
    }
  }

  // check for wheel
  int wheelVal = analogRead(WHEELPIN);
  for (int i=0; i< WHEELLENGTH; i++) {
    if (wheelVal <= WHEELTHRESHOLD[i]) {
      if (WHEELNAME[i] != bunselection) {
        bunselection = WHEELNAME[i];
        Serial.print("bun selection changed to: ");
        Serial.println(bunselection);
      }
      break;
    }
  }

  int sliderVal = analogRead(SLIDERPIN);
  for (int i=SLIDERLENGTH-1;  i>= 0; i--) {
    if (sliderVal <= SLIDERTHRESHOLD[i]) {
      if (SLIDERNAME[i] != cookselection) {
        cookselection = SLIDERNAME[i];
        Serial.print("cook changed to: ");
        Serial.println(cookselection);
        for (int j=0; j<LEDNUM; j++) {
          digitalWrite(LEDPINS[j], LOW);
        }
        for (int j=0; j<=i; j++) {
          digitalWrite(LEDPINS[j], HIGH);
        }
      }
      break;
    }
  }
}

 

 

Should I stay or should I go?

Problem:

There are many factors that have to be considered in managing risk of contagion during the pandemic. These are not straightforward, and can vary wildly depending on the context and combination of variables. Constantly trying to analyze these can cause fatigue, anxiety and unnecessary worry about perceived risks.

One particularly difficult element to get a good gauge on whether it is safe to remain indoors in a particular space. While maintaining 6ft of distance is still a good rule of thumb, it doesn’t always guarantee low risk.

This can be counterbalanced by other risk factors, such as ventilation and speaking volume. Risky activity such as singing generates significantly more droplets and aerosols that increases infection risks significantly, even when keeping a distance of 6ft.

Often, in an enclosed space, even when in individuals are in close proximity, a powerful HVAC system is circulating the air can prevent significant spreading. For example, when looking at transmission patterns in airplanes, only a few individuals sitting directly next to, in front of and behind Patient Zero were infected. Others remained safe, even though they remained in the same (relatively small) enclosed area for a significant amount of time.

This further complicated by having to consider the volume of indoor space – how risky these factors are varies considerably based on that as well.

Without ventilation, aerosols remain suspended in the air, becoming increasingly concentrated as time goes by. (Source)

Solution:

Lots of these factors, such as ventilation and speaking volume, are invisible and hard to keep track to begin with. Compared to distance from the next person, it’s really difficult for the average person to estimate what’s safe and what’s not.

It is even more challenging and stressful to calibrate these against dynamic changes from people coming and leaving, and the type of activity or behavior they choose when in said space.

By developing a system that balances all these factors and aggregates an overall risk grade that is displayed in a public space, it’s much easier for incoming and outgoing individuals to gauge their risk and exposure, and take the necessary precautions immediately.

As the number of people increase, the tradeoff between ventilation and noise level changes. We monitor these different axes with a wind sensor, a sound sensor and an ultrasonic sensor.

Components:

  • 1x HC-SR04 ultrasonic sensor
  • 1x RGB Diffused Common Cathode
  • 3x LED (Red, Green, Yellow)
  • 6x Resistor 220 ohm
  • Wind Sensor Rev. C
  • 1x LCD1602 Module
  • Sound sensor (replaced by potentiometer)

Wind sensor

The wind sensor gives us an approximation of the rate of airflow, and thus some insight into the amount of ventilation that a space has.

 

Ultrasonic sensor

Using two ultrasonic sensors, we can keep count of how many people are in the room at any one time.

 

Sound sensor

The decibel level can give us an approximation of how loudly people in the room are speaking (and thus how likely they are to expel aerosols in the air). A library is ~40dB, and a cafe is ~80dB. I will be substituting this value with a potentiometer for demo purposes, with a range of 1-100 to represent dB levels.

Safety rating

Keeping track of all this and displaying this information to the individuals in a space is an overall safety rating that indicates the recommended course of action to remain safe.

 

What should you do, and when?

When the number of people in a room is small, the safety rating remains green regardless of how well ventilated and how loud people are.

But, if enough people gather, the safety rating will turn red.

In this case, you might choose not to enter the room, and keep walking. If you do need to be present, there are a few things you can do to improve the situation.

Reducing speaking levels and ventilation levels can shift the safety ratings. Depending on the extent of how much each of these fluctuate and in what direction, the overall effect could be either an increase or decrease in risk levels.

Another way to improve the safety rating is to just decrease the total number of people in a space.

If the safety rating does not change after making these adjustments, it’s a sign that you should leave in order to be safe.

Schematic:

Code:

/*************************************************
* Ultrasonic sensor + LCD screen
*************************************************/
#include <Wire.h>
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
#define trigPin 10
#define echoPin 9

int counter = 0;
int currentState1 = 0;
int previousState1 = 0;
int currentState2 = 0;
int previousState2 = 0;
int inside = 0;
int outside = 0;

/*************************************************
* Wind Sensor
*************************************************/
#define analogPinForRV    1   // blue jumper wire
#define analogPinForTMP   0   // yellow jumper wire

// to calibrate your sensor, put a glass over it, but the sensor should not be
// touching the desktop surface however.
// adjust the zeroWindAdjustment until your sensor reads about zero with the glass over it. 

const float zeroWindAdjustment =  .2; // negative numbers yield smaller wind speeds and vice versa.

int TMP_Therm_ADunits;  //temp termistor value from wind sensor
float RV_Wind_ADunits;    //RV output from wind sensor 
float RV_Wind_Volts;
unsigned long lastMillis;
int TempCtimes100;
float zeroWind_ADunits;
float zeroWind_volts;
float WindSpeed_MPH;

//LED Feedback
int redPin = A4; //Pin for the red RGB led pin
int greenPin = A3; //Pin for the green RGB led pin
int bluePin = A2; //Pin for the blue RGB led pin 

int writeValue_red; //declare variable to send to the red LED
int writeValue_green; //declare variable to send to the green LED
int writeValue_blue; //declare variable to send to the blue LED



/*************************************************
* Sound sensor
*************************************************/
#define soundSensorPin  5
int soundSensor = 0;
bool soundRisk = false;

/*************************************************
* Aggregated risk
*************************************************/
int riskLevel = 0;
int greenLED = 6;
int yellowLED = 7;
int redLED = 8;

/*************************************************
 * 
 * SETUP
 * 
*************************************************/

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

  //Ultrasonic sensor + LCD
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
  lcd.begin(16,2);

  //wind sensor
  Serial.println("start");
  //   Uncomment the three lines below to reset the analog pins A2 & A3
  //   This is code from the Modern Device temp sensor (not required)
  pinMode(A2, INPUT);        // GND pin      
  pinMode(A3, INPUT);        // VCC pin
  digitalWrite(A3, LOW);     // turn off pullups

  //Risk feedback state
  int greenLED = 6;
  int yellowLED = 7;
  int redLED = 8;
  pinMode(greenLED, OUTPUT);
  pinMode(yellowLED, OUTPUT);
  pinMode(redLED, OUTPUT);
  
}

void loop(){

/*************************************************
* Ultrasonic sensor + LCD screen
*************************************************/
//setting up LCD screen display
lcd.setCursor(0, 0);
lcd.print("IN: ");
lcd.setCursor(7, 0);
lcd.print("Wind: ");
lcd.setCursor(0, 1);
lcd.print("OUT: ");
lcd.setCursor(7, 1);
lcd.print("Total: ");
  
//sending ping + calculating distance using ultrasonic sensor
long duration;
float distance;
digitalWrite(trigPin, LOW);
delayMicroseconds(2);
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);
duration = pulseIn(echoPin, HIGH);
distance = (duration/2) / 29.1;

//going inside
if (distance<= 9){
  currentState1 = 1;
}
else {
  currentState1 = 0;
}

delay(100);

if(currentState1 != previousState1){
  if(currentState1 == 1){
    counter = counter + 1;
    inside = inside + 1;
    //update LCD screen
    lcd.setCursor(14, 1);
    lcd.print(counter);
    lcd.setCursor(4, 0);
    lcd.print(inside);
  }
  }

//returning outside
  if (distance > 14 && distance <=30){
    currentState2 = 1;
  }
  else {
    currentState2 = 0;
  }
  delay(100);
  
  if(currentState2 != previousState2){
    if(currentState2 == 1){
      counter = counter - 1;
      outside = outside + 1;
      //update LCD screen
      lcd.setCursor(14, 1);
      lcd.print(counter);
      lcd.setCursor(5, 1);
      lcd.print(outside);
    }
  }

//keeping tally based on   
    Serial.print("   IN:");
    Serial.println((float)inside);

    Serial.print("   OUT:");
    Serial.println((float)outside);

    Serial.print("   TOTAL INSIDE:");
    Serial.println((float)counter);
    
//debugging
//    Serial.println(duration);
//    Serial.println(duration/2);
//    Serial.println((duration/2) / 29.1);


/*************************************************
* Wind Sensor
*************************************************/
  if (millis() - lastMillis > 200){      // read every 200 ms - printing slows this down further
    
    TMP_Therm_ADunits = analogRead(analogPinForTMP);
    RV_Wind_ADunits = analogRead(analogPinForRV);
    RV_Wind_Volts = (RV_Wind_ADunits *  0.0048828125);

    // these are all derived from regressions from raw data as such they depend on a lot of experimental factors
    // such as accuracy of temp sensors, and voltage at the actual wind sensor, (wire losses) which were unaccouted for.
    TempCtimes100 = (0.005 *((float)TMP_Therm_ADunits * (float)TMP_Therm_ADunits)) - (16.862 * (float)TMP_Therm_ADunits) + 9075.4;  

    zeroWind_ADunits = -0.0006*((float)TMP_Therm_ADunits * (float)TMP_Therm_ADunits) + 1.0727 * (float)TMP_Therm_ADunits + 47.172;  //  13.0C  553  482.39

    zeroWind_volts = (zeroWind_ADunits * 0.0048828125) - zeroWindAdjustment;  

    // This from a regression from data in the form of 
    // Vraw = V0 + b * WindSpeed ^ c
    // V0 is zero wind at a particular temperature
    // The constants b and c were determined by some Excel wrangling with the solver.
    
   WindSpeed_MPH =  pow(((RV_Wind_Volts - zeroWind_volts) /.2300) , 2.7265);   

    
//debugging   
//    Serial.print("  TMP volts ");
//    Serial.print(TMP_Therm_ADunits * 0.0048828125);
//    
//    Serial.print(" RV volts ");
//    Serial.print((float)RV_Wind_Volts);
//
//    Serial.print("\t  TempC*100 ");
//    Serial.print(TempCtimes100 );
//
//    Serial.print("   ZeroWind volts ");
//    Serial.print(zeroWind_volts);

    Serial.print("   WindSpeed MPH ");
    Serial.println((float)WindSpeed_MPH);

    lastMillis = millis();

    lcd.setCursor(13, 0);
    lcd.print(WindSpeed_MPH);
  }

/*************************************************
* Wind sensor LED feedback
*************************************************/

  writeValue_red = (255./10.)*WindSpeed_MPH; //Calculate the value to write on the red LED (add point to change to float point)
  writeValue_green = (255./10.)*WindSpeed_MPH; //Calculate the value to write on the green LED
  writeValue_blue = (255./10.)*WindSpeed_MPH; ///Calculate the value to write on the blue LED
  
  analogWrite(redPin,writeValue_red); //write value to set the brightness of the red LED
  analogWrite(greenPin,writeValue_green); //write value to set the brightness of the green LED
  analogWrite(bluePin,writeValue_blue); //write value to set the brightness of the blue LED



/*************************************************
* Sound sensor
*************************************************/
int soundSensor = analogRead(soundSensorPin);

//assuming that there are two main buckets of sound levels - one is low, which conversational and not risky, and the other is high, such as singing, which is risky)
if (soundSensor >= 100){
  soundRisk = true;
}
  else {
    soundRisk = false;
  }

Serial.println(soundSensor);
Serial.println(soundRisk);

/*************************************************
* States + risk level display
*************************************************/

//small group
if (counter <= 3) {
  //sound and ventilation doesn't matter
  riskLevel = 0;  
}

//medium group
else if (counter > 3 && counter <=5){
  //sound risk is high, ventilation needs to be high
  if (soundRisk == true){
    if (WindSpeed_MPH >= 10){
      riskLevel = 0;
    } else if (WindSpeed_MPH < 10 && WindSpeed_MPH >= 7){
      riskLevel = 1;
    } else if (WindSpeed_MPH < 7){
      riskLevel = 2;
    }
    
  //sound risk is low, ventilation can be lower
  } else if (soundRisk == false){
      if (WindSpeed_MPH >= 7){
        riskLevel = 0;
      } else if (WindSpeed_MPH < 7 && WindSpeed_MPH >=2){
        riskLevel = 1;
      } else if (WindSpeed_MPH < 2){
        riskLevel = 2;
      }
  }
  
  //
}

//big group
else if (counter > 5){
  //sound risk is high, ventilation needs to be high
  if (soundRisk == true){
    if (WindSpeed_MPH >= 20){
      riskLevel = 0;
    }
    if (WindSpeed_MPH >= 10 && WindSpeed_MPH < 20){
      riskLevel = 1;
    } else if (WindSpeed_MPH <10){
      riskLevel = 2;
    }
  //sound risk is low, ventilation can be lower
  } else if (soundRisk == false){
    if (WindSpeed_MPH >= 12){
      riskLevel = 0;
    } else if (WindSpeed_MPH >= 8 && WindSpeed_MPH > 5){
      riskLevel = 1;
    } else if (WindSpeed_MPH <= 5){
      riskLevel = 2;
    }
  }
}

/*************************************************
* Overall risk level display
*************************************************/

if (riskLevel == 0){
  //Low risk
  //turn green LED on
  digitalWrite(greenLED, HIGH);
  //turn the rest off
  digitalWrite(redLED, LOW);
  digitalWrite(yellowLED, LOW);
}

else if (riskLevel == 1){
  //Medium risk
  //turn yellow LED on
  digitalWrite(yellowLED, HIGH);
  //turn the rest off
  digitalWrite(redLED, LOW);
  digitalWrite(greenLED, LOW);
}

else if (riskLevel == 2){
  //High risk
  //turn red LED on
  digitalWrite(redLED, HIGH);
  //turn the rest off
  digitalWrite(yellowLED, LOW);
  digitalWrite(greenLED, LOW);
}

}

Reflections on implementation

There are a few things which I tried and did not turn out so well for the prototype. For starters, I had initially attempted to operationalize the graph diagram I had drawn earlier indicating the relationship between sound volumes and ventilations. Not only were the resultant numbers difficult to predict and thus challenging to show in a demo, sound generally clusters around two median values – loud, or not too loud. Instead of a continuous range, I went ahead and made the sound value a boolean instead. Actual implementation of this would also require a much more nuanced look at the relationship between these variables as well as the risk tradeoffs along a continuum, unlike the buckets that I had created to assist accuracy in the demo.

There were also some unpredictability in the ultrasonic sensor’s readings and counting of people – on occasion, it would double or even triple count, while on others it missed it totally. This challenged the reproducibility of the demo. If this were to be implemented in real life, much more robust software would be needed in order to provide an accurate estimate of the number of people in the room.

I’m glad that in my second iteration of the circuits and code I was able to fit everything onto a single breadboard, as it made it much easier to see what was happening or changing altogether at once.

Growing this project further

If I had had more time, I would have liked to delve more into strategies in responsive architecture to help individuals respond more appropriately to the risk level. For example, if this was a public area such as an indoor mall, perhaps  public seating can transform to become more hostile to discourage loitering, or eliminate it by disappearing altogether.

One can imagine the benches shape-shifting into something uncomfortable. The example below illustrates this in action:

Or, they could disappear altogether into the ground until it is safe again, like the night time urinals in London.

Other ways we could encourage individuals to cut their time in a space short is to switch off alternate lights, and/or cut off background music to give people the message to conclude their affairs quickly and leave as it feels like things are closing.

If it is able to be actuated, the building could also enforce a base-level of ventilation by automatically switching on the fans and opening the windows when the safety indicator goes yellow or red. Throwing open the windows when necessary would also also inevitably expose individuals to the elements of nature, encouraging them to disperse and head home, particularly in wintertime.

 

Final Project: Chill buddy

 

 

How to wear it: red pad on right wrist, green on left, yellow on your foot, and an accelerometer attached to your shirt.

When it comes to exercises like pilates and yoga that requires constant attention to breathing and posture, which can be hard for people to follow while exercising.

The other key part of these types of exercises is that the user should have a good balance of the reflection of their state and maintaining the focus on their body. To assist them, they should be able to know how they are breathing and how they are holding up the posture, yet also they should not be interfered from being zen.

Therefore, I came up with a device that assists people with both breathing and posture.

The main problem with designing a device for this was that it has to be light and intimate to the body. If it is too heavy, it is uneasy for the user to focus on inner peace. If the user has to carry/hold on to the device, that would hinder the user from holding up a posture.

Component diagram:

The device consists of three parts: an ECG sensor, a LED neopixel, and an accelerometer. The neopixel lights up in an ideal breathing tempo, and the ECG sensor monitors your heartbeat and your breathing.

The ECG sensor solved the problem of the lightness and closeness to the body. The ECG sensor comes with three attachable pads. They are light and thin, which made it easier for me as a user to focus on myself.

Explanation video: https://drive.google.com/file/d/1ynT0niDqBbBioUs-JuklcV1FvNpeHiIn/view?usp=sharing

Demo video: https://drive.google.com/file/d/18wtBSU3APmGJdT1yotgQdTB6aySNxs1L/view?usp=sharing

default state; inhale according to the tempo of LED neopixel

default state; exhale according to the tempo of LED neopixel

when the heartbeat rises, the light changes to pink and tells you to calm down.

when the back is not straight, the LCD tells you to straighten your back.

Schematics:

Code:

#include <Wire.h>
#include <LiquidCrystal.h>


LiquidCrystal lcd(13, 12, 5, 4, 3, 2);

#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
#include <avr/power.h>
#endif

#define PIN 8
#define NUMPIXELS 8

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

int delayval = 500; // delay for half a second
int zpin = A1;

void setup() {
// initialize the serial communication:
Serial.begin(9600);
lcd.begin(16,2);
pixels.begin();pixels.begin();

pinMode(10, INPUT); // Setup for leads off detection LO +
pinMode(11, INPUT); // Setup for leads off detection LO -
pinMode(zpin, INPUT);
}

void loop() {

for(int i=0;i<NUMPIXELS;i++){

// pixels.Color takes RGB values, from 0,0,0 up to 255,255,255
pixels.setPixelColor(i, pixels.Color(0,150,0)); //green

pixels.show(); // This sends the updated pixel color to the hardware.

delay(delayval); // Delay for a period of time (in milliseconds).

lcd.setCursor(0,0);
lcd.print("INHALE ");
lcd.setCursor(0,2);
// lcd.print(" ");

}

for(int k=NUMPIXELS;k>0;k--){


pixels.setPixelColor(k, pixels.Color(0,0,0));

pixels.show();

delay(delayval);

lcd.setCursor(0,0);
lcd.print("EXHALE ");
}

if (analogRead(A0)>700){
for(int i=0;i<NUMPIXELS;i++){


pixels.setPixelColor(i, pixels.Color(235,64,52));

pixels.show();

delay(delayval);


lcd.setCursor(0,2);
lcd.print("CALM DOWN ");

}

for(int k=NUMPIXELS;k>0;k--){


pixels.setPixelColor(k, pixels.Color(235,64,52));

pixels.show();

delay(delayval);


lcd.setCursor(0,2);
lcd.print("CALM DOWN ");

}
}

if(analogRead(zpin)>300){
lcd.setCursor(0,2);
lcd.print("BACK STRAIGHT");
}

if((digitalRead(10) == 1)||(digitalRead(11) == 1)){
Serial.println('!');
}
else{
// send the value of analog input 0:
Serial.println(analogRead(A0));
}
//Wait for a bit to keep serial data from saturating
delay(10);

}

Vibrotactile Sensations: Exploring their impact on human body

Problem: Many people today experience an enormous amount of stress due to the fast-paced and competitive rhythms of a society that tries to survive under the uncertainty that Covid-19 brought. Stress constitutes a psycho-emotional condition that most of the times breaks out through the human body using various forms: headaches, stomach pains, fast pulses and breathing, sometimes even inertia. Many people turn to medicine drugs in an effort to find a way out, neglecting the therapeutic effects that a simple touch may have on their body. Alternative ways of medicine and well being such as pain and stress and management through meditation, therapeutic massage and reflexology are being neglected by the variety of people, especially in West societies.  Sometimes, a simple touch and push between your eyes, may prove to be enough to stop a strong headache or force yourself to release significant stress and feel asleep.

Solution: For my final project, I propose the design and computation of an e-bandage that can be wrapped around different areas of the human body (head, stomach, arm) and offer different vibrotactile sensations, that can either calm stress, treat pain or even induce an alert. In this project, I am about to explore different effects of vibrations and their impact on the human body, by playing with factors such as the spatial configuration and distance among the vibrators, their intensity, frequency and location upon the body. I intent to use two arrays of LRA vibration motors, where different combinations of motors are going to be activated for different haptic patterns. A touchpad matrix is going to be used for choosing which effect should be executed.

Components:

  • 8 x LRA vibrating motors
  • 8 x Transistors to increase power output of the motor
  • 8 x Adafruit haptic motor drivers or 8 x SparkFun Haptic Motor Driver (?)
  • conductive thread
  • optional: Flora microcontroller or LilyPad arduino

Fight Alzheimer’s Disease with Karaoke

Problem

Alzheimer’s Disease is an irreversible, progressive brain disorder that slowly destroys memory and thinking skills, and, eventually, the ability to carry out the simplest tasks. Experts suggest that more than 5.5 million Americans, most of them age 65 or older, may have dementia caused by Alzheimer’s. Recent estimates indicate that the disorder is ranked third in the leading cause of death in U.S. With longer life expectancy and larger population, prevention of Alzheimer’s Disease is no doubt challenging but also necessary.

So far little is known about the cause of Alzheimer’s Disease, and no evidence has proven any treatment that works for sure. However, the National Academies of Sciences, Engineering, and Medicine (NASEM),  found “encouraging but inconclusive” evidence for three types of interventions:

  •  Increased physical activity
  • Blood Pressure Control
  • Cognitive training.

Further study shows that informal cognitively stimulating activities, such as reading or playing games, may lower risk of Alzheimer’s-related cognitive impairment and dementia. In addition, social activity and physical activities also have positive impact for prevention.

Solution

I was trying to come up with a device that can encourage the person to engage in social activity, cognitively stimulating activity, and physical activity. Then I thought about the Pokemon Go which you get rewarded by going to different places; I also thought about video games where items can be shared across different devices/accounts when they are connected, which encourages players to play together.

The device I made is basically a karaoke device that can display lyrics to the screen. An optional microphone is connected with the device through Bluetooth.

The layout of the device

The optional Microphone gives the vibe of Karaoke.

Learning the song and memorizing lyrics is also an informal cognitively stimulating activity. When the reader has learnt the song and does not need the lyrics anymore, the lyrics can be switched off by the top button.

However, there is no song to play initially. The user can select a music genre and time period once per day to obtain a random song that is in the selected category; to earn more songs, the user has to do some physical exercise such as walking and running for a period . The physical activity is detected by an accelerometer placed in the users’ shoes.

To encourage social activity, songs can be “shared” between two users. For example, if I find out that my friend has a song that I would like to have on my device, he can share the song with me. However, it is limited to one shared song per day. There is also a PK feature that allows the user to challenge each other; the two users will sing the same song, and whoever look at the lyrics for the least amount of time wins. The purpose of these mechanism is to make users meet more with their families and friends.

Proof of Logic

Layout

The layout of the components of the prototype

For this prototype, two Arduinos are used; The one on the left in the photo above is the prototype of the device, and the one on the right is a “dummy device” that only sends information through the transceiver. The “dummy device” is needed to show the interaction between two device; it is not made into another full prototype because of the lack of materials.

The Arduino of the full prototype connects to a accelerometer, a potentiometer, a mini-mp3 player, 4 buttons, a LCD screen, and a transceiver. The accelerometer detects motion for physical exercise. The potentiometer is for volume control. The mini-mp3 player works with the transducer speaker to play song. The buttons are for the user interface. The LCD screen is to show the user interface and display lyrics when songs are playing. The Transceiver allows communication between the prototype and the “dummy” Arduino.

The “dummy” Arduino is connected to a button and a transceiver; pressing the button triggers the Arduino to send information through the transceiver. The “dummy” Arduino can be powered by an external  of a 9V battery. However, it is plugged into PC during testing for convenience of uploading codes.

Interaction

Interface

The user interface of the prototype includes a LCD screen and four buttons. The first button from the top represents rolling the rotary encoder up, and the second button represents rolling the rotary encoder down. The rotary encoder was labeled “rotator” in the sketch of the layout of the device. The third button is the “Confirm” button of the rotary encoder, and the fourth button is the “Back/Lyrics” button.

In the following video, the functions of the four buttons are shown by going through the menu. In menu, there are 3 modules to choose: “Unlocked Songs”, “Be Connected”, and “Jackpot”.  The first and second button switches the current option.  The “Confirm” button select the selects option. And the “Back” button goes back to the previous stage. Notice that when option “Unlocked Songs” was selected in the video, it said empty. It was because there was no unlocked songs yet.

Jackpot

The next video shows the option “Jackpot”. The user has to select the desired genre and times of the song, then a random song from the selected category will be unlocked. For demonstration purpose, there are only 3 genres (Movie soundtrack, Pop, Jazz) and 2 period (50s, 2010s). Again, the user can only unlock one song per day through jackpot.

Physical Exercise

If the user wants to obtain more songs in a day, he/she can do so by doing physical exercise. An accelerometer can be placed in the user’s shoe or on user’s wrist as a fit-band; the physical activity will be detected by the accelerometer. I shook the accelerometer in the video to stimulate the physical activity. After exercising, the user obtains a chance to unlock a song again.

Playing Unlocked Song

With songs unlocked from the “Jackpot”, the user can play the unlocked songs and sing along them. The potentiometer represents the volume control of the device. When the song is being played, the “Confirm” button pauses the song and the “Back/ Lyrics” button toggles the lyrics. The lyrics are coded into the prototype because there are better applications to do that.  In addition, the potentiometer used in the demonstration does not always have a stable contact with the breadboard, which overwhelms the Arduino with data and makes the buttons less responsive.

Receive Song

If the user stays in the module of “Be Connected” and switches to “Share Song” as the current option (but not selecting it), the device can receive a new song from a different device. However, each device can only receive a song from another device once per day. After a receipt, no new song will be added anymore.

Share Song

In the “Be Connected” module, if the user selects the option of “Share Song” as shown in the video, the user can then choose a n unlocked song to share to a friend. There is no limits on how many songs a device can share per day.

Compete

In the module of “Be Connected”, the user can select an unlocked song to challenge his/her friend. When the song is being played, the device keeps track of how long the lyrics is been looked at. When the song finished, the devices will compare the time of showing the lyrics and decide a winner. In the video, the dummy Arduino sends its time of displaying the lyrics after the button was pressed.

Final Notes

  • When I started to work on the transceiver, I found out that I did not have enough female-to-male jumper wires. I bought some immediately but they only came the night before the crit, which makes it more stressful than it should be. Next time when I am preparing the components I have to not only think about major devices but all the materials including wires.
  • There are definitely more rooms for improvement. First of all, the device is designed as a device that downloads music from the internet instead of storing music in a micro-SD card. Some wifi or bluetooth parts should be able to connect the device to the internet. Secondly, the code can be futher optimized. It is cumbersome and inefficient at the point. Thirdly, more games such as guessing the name of the song can be added in addition to the competition of sing without looking at the lyrics. Fourthly, a vibration motor can be added to the device to notify the user when the opponent started to look at lyrics in competition. In addition, the interface can be better with lively sound effects added. For example, before a new song is unlocked, the device can play a little bit of drum rolling. Finally, the device can only became a real karaoke device when a microphone is incorporated.

Schematics

Schematics for the full prototype

Schematics for the dummy Arduino

Code

Code for the full prototype:

//Library
#include "Arduino.h"
#include "SoftwareSerial.h"
#include "DFRobotDFPlayerMini.h"
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>

//Set up of hardwares
//Mp3 player
SoftwareSerial mySoftwareSerial(6, 7); // RX, TX
DFRobotDFPlayerMini myDFPlayer;
void printDetail(uint8_t type, int value);
//LCD
LiquidCrystal_I2C screen(0x27, 16, 2);
//Transceiver
RF24 radio(8, 9); // CE, CSN



//Global Variables
//Constant variables
const int ROTARY_UP = 5;
const int ROTARY_DOWN = 4;
const int ROTARY_CONFRIM = 3;
const int BACK_LYRICS = 2;
const int VOLUME_CONTROL = A0;
const int XPIN = A1;
const int YPIN = A2;
const int ZPIN = A3;

//Chaning Variables
//Button
int ButtonState_rotaryUp;
int ButtonState_rotaryDown;
int ButtonState_Confirm;
int ButtonState_Back_Lyrics;

int lastButtonState_rotaryUp = HIGH;
int lastButtonState_rotaryDown = HIGH;
int lastButtonState_Confirm = HIGH;
int lastButtonState_Back_Lyrics = HIGH;

//Mp3
int lastVolume = 15;

//Debounce Varibale
unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled
unsigned long debounceDelay = 50;    // the debounce time; increase if the output flickers

//The variables related to display the lyrics
bool lyrics = false;
//This is for counting the time when lyrics are displayed throughout the song.
unsigned long lyrics_timer_Start;
unsigned long lyrics_time;
unsigned long lyrics_time_oponent;
bool playing = false;

//Accelerometer
bool timer = false;
unsigned long accelerometer_timer_Start;
unsigned long accelerometer_time;
//Exercise for 10 seconds to get a chance for jackpot
const unsigned long accelerometer_time_jackpot = 10000;

//The variables used in building the interface
String interFace = "Menu";
String interFace_Secondary;
int interFace_Option = 1;

String selected_Genre;
String selected_Period;

int Jackpot_Chance = 1;
unsigned long Jackpot_Clock;
const int OneDay = 60000;         //For demo purpose 1_Day is 1 minute.

unsigned long Receive_Clock;
bool song_received = false;

//Can't read the number of file yet in the global scope so make the array as big as the capacity of the SD card. Or assume the information is already given when the songs are downloaded from internet.
//The sizes of the array are set for the demo purpose
//Arrary that includes all the unlocked songs
int unlocked_Song[5];
int input_indice_unlocked;
//Array taht includes the unlocked 2010s movie soundtrack
int Unlocked_2010s_Movie[1];
int input_indice_movie;
int Unlocked_2010s_Pop[1];
int input_indice_pop;
int Unlocked_50s_Jazz[3];
int input_indice_jazz;

//Transceiver's addresses
//One for writing and one for reading
const byte addresses[][6] = {"00001", "00002"};


//Function
//Functions related to the MP3 player
void read_information() {
  Serial.println(myDFPlayer.readState()); //read mp3 state
  Serial.println(myDFPlayer.readVolume()); //read current volume
  Serial.println(myDFPlayer.readEQ()); //read EQ setting
  Serial.println(myDFPlayer.readFileCounts()); //read all file counts in SD card
  Serial.println(myDFPlayer.readCurrentFileNumber()); //read current play file number
  //Serial.println(myDFPlayer.readFileCountsInFolder(3)); //read fill counts in folder SD:/03
}

//Function related to the transveriver
void share_song(int shared_song) {
  radio.stopListening();
  radio.write(&shared_song, sizeof(shared_song));
}

//Function that receives song from  "Be connected"
void receive_song() {
  radio.startListening();
  if (radio.available() && song_received == false ) {
    int shared_song;
    radio.read(&shared_song, sizeof(shared_song));
    //Write the number of the number into the array for unlocked_songs
    unlocked_Song[input_indice_unlocked] = shared_song;
    input_indice_unlocked += 1;
    song_received = true;
    screen.clear();
    screen.setCursor(4, 0);
    screen.print("New Song");
    screen.setCursor(5, 1);
    screen.print("Added!");
    delay(3000);
    screen.clear();
    screen.setCursor(6, 0);
    screen.print("Menu");
    screen.setCursor(1, 1);
    screen.print("Unlocked Songs");
    interFace = "Menu";
    interFace_Option = 1;
    radio.stopListening();
  }
}



//Function related to the accelerometer
void read_accelerometer() {
  if (timer == false) {
    if (analogRead(ZPIN) > 321) {
      timer = true;
      accelerometer_timer_Start = millis();
    }
  }
  else {
    if (analogRead(ZPIN) <= 321 && analogRead(ZPIN) >= 317) {
      accelerometer_time += millis() - accelerometer_timer_Start;
      timer = false;
    }
  }
}

//Functions related to the interface of the device
//Function that checks whether there is an zero in the array, which means there is still song for that category to unlock. (supplemental to function jackpot)
bool CheckforZero(String Genre, String Period) {
  if (Genre == "Movie" && Period == "2010s") {
    for (int i = 0; i < (sizeof(Unlocked_2010s_Movie) / sizeof(Unlocked_2010s_Movie[0])); i++) {
      if (Unlocked_2010s_Movie[i] == 0) {
        return true;
      }
    }

  }


  else if (Genre == "Pop" && Period == "2010s") {
    for (int i = 0; i < (sizeof(Unlocked_2010s_Pop) / sizeof(Unlocked_2010s_Pop[0])); i++) {
      if (Unlocked_2010s_Pop[i] == 0) {
        return true;
      }
    }
  }

  else if (Genre == "Jazz" && Period == "50s") {
    for (int i = 0; i < (sizeof(Unlocked_50s_Jazz) / sizeof(Unlocked_50s_Jazz[0])); i++) {
      if (Unlocked_50s_Jazz[i] == 0) {
        return true;
      }
    }
  }

  return false;
  //return false when there is no more 0 in the array


}


//Function that check whether a number is already in an array. (supplemental to function jackpot)
bool checkNumberinList(int numb, String Genre, String Period) {
  if (Genre == "Movie" && Period == "2010s") {
    //Serial.println((sizeof(Unlocked_2010s_Movie) / sizeof(Unlocked_2010s_Movie[0])));
    //Serial.println(Unlocked_2010s_Movie[0]);
    for (int i = 0; i < (sizeof(Unlocked_2010s_Movie) / sizeof(Unlocked_2010s_Movie[0])); i++) {
      if (Unlocked_2010s_Movie[i] == numb) {
        //Serial.println("Here");
        return true;
      }
    }
  }

  else if (Genre == "Pop" && Period == "2010s") {
    for (int i = 0; i < (sizeof(Unlocked_2010s_Pop) / sizeof(Unlocked_2010s_Pop[0])); i++) {
      if (numb == Unlocked_2010s_Pop[i]) {
        return true;
      }
    }
  }

  else if (Genre == "Jazz" && Period == "50s") {
    for (int i = 0; i < (sizeof(Unlocked_50s_Jazz) / sizeof(Unlocked_50s_Jazz[0])); i++) {
      if (numb == Unlocked_50s_Jazz[i]) {
        return true;
      }
    }
  }
}



//Function that select a random song to unlock in Jackpot. (supplemental to function button pressed)
int jackpot(String Genre, String Period) {
  //Check if the selected category still has songs in the songbank to unlock
  if (CheckforZero(Genre, Period) != true) {
    //Print the information on the lcd screen
    screen.clear();
    screen.setCursor(1, 0);
    screen.print(String("No more " + Genre));
    screen.setCursor(3, 1);
    screen.print(String("in " + Period));
    delay(5000);
    screen.setCursor(1, 0);
    screen.print("Select another");
    screen.setCursor(2, 1);
    screen.print("genre/period");
    delay(5000);
    screen.clear();

    return 0;
  }

  else {

    if (Genre == "Movie" && Period == "2010s") {
      //Number 1 to number 1 represents Movie Soundtracks in 2010s
      int randomPick = random(1, 2);

      while (checkNumberinList(randomPick, Genre, Period) == true) {
        randomPick = random(1, 2);
        //Serial.println(randomPick);
      }
      //Add the song into the array of unlocked song
      unlocked_Song[input_indice_unlocked] = randomPick;
      input_indice_unlocked += 1;

      //Add the song into the array of unlcoked movie soundtrack array
      Unlocked_2010s_Movie[input_indice_movie] = randomPick;
      input_indice_movie += 1;


      return randomPick;
    }


    else if (Genre == "Pop" && Period == "2010s") {
      //Number 1 to number 1 represents Movie Soundtracks in 2010s
      int randomPick = random(2, 3);
      while (checkNumberinList(randomPick, Genre, Period) == true) {
        randomPick = random(2, 3);
      }
      //Add the song into the array of unlocked song
      unlocked_Song[input_indice_unlocked] = randomPick;
      input_indice_unlocked += 1;

      //Add the song into the array of unlcoked movie soundtrack array
      Unlocked_2010s_Pop[input_indice_pop] = randomPick;
      input_indice_pop += 1;

      return randomPick;
    }

    else if (Genre == "Jazz" && Period == "50s") {
      //Number 1 to number 1 represents Movie Soundtracks in 2010s
      int randomPick = random(3, 6);
      while (checkNumberinList(randomPick, Genre, Period) == true) {
        randomPick = random(3, 6);
      }
      //Add the song into the array of unlocked song
      unlocked_Song[input_indice_unlocked] = randomPick;
      input_indice_unlocked += 1;

      //Add the song into the array of unlcoked movie soundtrack array
      Unlocked_50s_Jazz[input_indice_jazz] = randomPick;
      input_indice_jazz += 1;

      return randomPick;
    }
  }
}


//Function thats returns the name of the song given the interface_option. (supplemental to function button pressed)
String SongName(int song) {
  if (song == 1) {
    return "City of Star";
  }
  else if (song == 2) {
    return "Stay";
  }
  else if (song == 3) {
    return "Fly Me to the Moon";
  }
  else if (song == 4) {
    return "All of You";
  }
  else if (song == 5) {
    return "The Best is yet to Come";
  }
}

//Function that determines what happens when the rotary enocoder is rolled up or down. (supplemental to function button pressed)
void Rotary(int input, String pressed_button, int min, int max) {
  if (pressed_button == "ROTARY_UP") {
    if (input == 1) {
      interFace_Option = max;
    }

    else {
      interFace_Option = input - 1;
    }
  }

  else if (pressed_button == "ROTARY_DOWN") {
    if (input == max) {
      interFace_Option = 1;
    }

    else {
      interFace_Option = input + 1;
    }
  }
}


//Function that decide what to do when buttons are pressed. (supplemental to function Read Button)
void button_pressed(String pressed_button) {
  //When buttons are pressed at the menu
  if (interFace == "Menu") {
    Rotary(interFace_Option, pressed_button, 1, 3);

    if (pressed_button == "ROTARY_CONFIRM") {
      if (interFace_Option == 1) {
        interFace = "Unlocked Songs";
        interFace_Secondary = "Select_Song";
        interFace_Option = 1;

      }

      else if (interFace_Option == 2) {
        interFace = "Jackpot";
        //Check if the user has earned a chance for jackpot through exercising
        if (accelerometer_time >= accelerometer_time_jackpot) {
          Serial.println(accelerometer_time);
          Jackpot_Chance += 1;
          accelerometer_time = 0;
        }
        if (Jackpot_Chance == 0) {
          //No more chance to get a new song for the day through Jackpot
          interFace_Secondary = "NoMore";
        }
        else {
          interFace_Secondary = "Genre";
        }
        interFace_Option = 1;

      }

      else if (interFace_Option == 3) {
        interFace = "Be Connected";
        interFace_Option = 1;
        interFace_Secondary = "Receive";
      }
    }

    //Display the options in Menu to the LCD screen
    //Clear the screen first
    screen.clear();
    if (interFace == "Menu") {
      screen.setCursor(6, 0);
      screen.print("Menu");
      if (interFace_Option == 1) {
        screen.setCursor(1, 1);
        screen.print("Unlocked Songs");
      }
      else if (interFace_Option == 2) {
        screen.setCursor(4, 1);
        screen.print("Jackpot");
      }
      else if (interFace_Option == 3) {
        screen.setCursor(2, 1);
        screen.print("Be Connected");
      }
    }
    else if (interFace == "Unlocked Songs") {
      screen.setCursor(1, 0);
      screen.print("Unlocked Songs");
      if (unlocked_Song[interFace_Option - 1] == 0) {
        screen.setCursor(5, 1);
        screen.print("Empty");
        delay(3000);
        screen.clear();
        screen.setCursor(6, 0);
        screen.print("Menu");
        screen.setCursor(1, 1);
        screen.print("Unlocked Songs");
        interFace = "Menu";
        interFace_Option = 1;
      }

      else {
        int song = unlocked_Song[interFace_Option - 1];
        screen.setCursor((16 - SongName(song).length()) / 2, 1);
        screen.print(SongName(song));
      }
    }
    else if (interFace == "Jackpot") {
      if (interFace_Secondary == "NoMore") {
        screen.setCursor(2, 0);
        screen.print("Unavailable");
        screen.setCursor(3, 1);
        screen.print("Right now");
        delay(3000);
        screen.clear();
        screen.setCursor(1, 0);
        screen.print("Earn More Song");
        screen.setCursor(1, 1);
        screen.print("by exercising");
        delay(3000);
        screen.clear();
        screen.setCursor(2, 0);
        screen.print("and meeting");
        screen.setCursor(4, 1);
        screen.print("friends!");
        delay(3000);
        //Goes back the menu page
        screen.clear();
        interFace = "Menu";
        interFace_Option = 1;
        screen.setCursor(6, 0);
        screen.print("Menu");
        screen.setCursor(1, 1);
        screen.print("Unlocked Songs");

      }


      else  {
        screen.setCursor(5, 0);
        screen.print("Genre");
        screen.setCursor(5, 1);
        screen.print("Movie");
      }
    }

    else if (interFace == "Be Connected") {
      screen.setCursor(2, 0);
      screen.print("Be Connected");
      screen.setCursor(3, 1);
      screen.print("Share Song");
    }
  }

  //When buttons are pressed at Unlocked Songs
  else if (interFace == "Unlocked Songs") {
    if (interFace_Secondary == "Select_Song") {
      Rotary(interFace_Option, pressed_button, 1, input_indice_unlocked);


      if (pressed_button == "ROTARY_CONFIRM") {
        myDFPlayer.play(unlocked_Song[interFace_Option - 1]);

        interFace_Secondary = "Playing_song";
        playing = true;
        screen.clear();
      }

      else if (pressed_button == "BACK_LYRICS") {
        interFace = "Menu";
        interFace_Option = 1;
        screen.clear();
        screen.setCursor(6, 0);
        screen.print("Menu");
        screen.setCursor(1, 1);
        screen.print("Unlocked Songs");

      }
    }

    else if (interFace_Secondary == "Playing_song") {
      if (pressed_button == "ROTARY_CONFIRM") {
        playing = !playing;
        if (playing == false) {
          Serial.println("pause");
          myDFPlayer.pause();
        }
        else {
          myDFPlayer.start();
        }
      }

      else if (pressed_button == "BACK_LYRICS") {
        //Serial.println(myDFPlayer.readState());
        //This means the song is finished
        if (myDFPlayer.readState() == 512) {
          if (lyrics == true) {
            lyrics = false;
          }

          interFace = "Menu";
          interFace_Option = 1;
          screen.clear();
          interFace = "Menu";
          interFace_Option = 1;
          screen.setCursor(6, 0);
          screen.print("Menu");
          screen.setCursor(1, 1);
          screen.print("Unlocked Songs");
        }
        else {
          lyrics = !lyrics;
          //Serial.println(lyrics);
          if (lyrics == true) {

            screen.setCursor(5, 0);
            screen.print("Lyrics");
            screen.setCursor(5, 1);
            screen.print("Lyrics");
          }
          else {
            screen.clear();
            //Add the lyrics-displaying time to the sum

          }
        }
      }
    }

    //Display the options in Menu to the LCD screen
    //Clear the screen first

    if (interFace == "Unlocked Songs") {
      if (unlocked_Song[0] == 0) {
        screen.clear();
        screen.setCursor(1, 0);
        screen.print("Unlocked Songs");
        screen.setCursor(5, 1);
        screen.print("Empty");
      }

      else if (interFace_Secondary == "Select_Song") {
        int song = unlocked_Song[interFace_Option - 1];
        //Serial.println("Hello");
        screen.clear();
        screen.setCursor(1, 0);
        screen.print("Unlocked Songs");
        screen.setCursor((16 - SongName(song).length()) / 2, 1);
        screen.print(SongName(song));
      }
    }
    else if (interFace == "Menu") {
      screen.clear();
      screen.setCursor(6, 0);
      screen.print("Menu");
      if (interFace_Option == 1) {
        screen.setCursor(1, 1);
        screen.print("Unlocked Songs");
      }
    }
  }

  //When buttons are pressed at Jackpot
  else if (interFace == "Jackpot") {
    //Selecting the Genre of the music of jackpot
    if (interFace_Secondary == "Genre") {
      Rotary(interFace_Option, pressed_button, 1, 3);


      if (pressed_button == "ROTARY_CONFIRM") {
        if (interFace_Option == 1) {
          selected_Genre = "Movie";
        }

        else if (interFace_Option == 2) {
          selected_Genre = "Jazz";
        }

        else if (interFace_Option == 3) {
          selected_Genre = "Pop";
        }

        interFace_Secondary = "Period";
        interFace_Option = 1;
        //Serial.println(selected_Genre);

      }

      else if (pressed_button == "BACK_LYRICS") {
        interFace = "Menu";
        interFace_Option = 1;
      }

      //Display the options in Menu to the LCD screen
      //Clear the screen first
      screen.clear();
      if (interFace == "Menu") {
        screen.setCursor(6, 0);
        screen.print("Menu");
        screen.setCursor(1, 1);
        screen.print("Unlocked Songs");
      }
      else if (interFace_Secondary == "Period") {
        screen.setCursor(5, 0);
        screen.print("Period");
        screen.setCursor(5, 1);
        screen.print("2010s");
      }

      else {
        screen.setCursor(5, 0);
        screen.print("Genre");
        if (interFace_Option == 1) {
          screen.setCursor(5, 1);
          screen.print("Movie");
        }

        else if (interFace_Option == 2) {
          screen.setCursor(6, 1);
          screen.print("Jazz");
        }

        else if (interFace_Option == 3) {
          screen.setCursor(6, 1);
          screen.print("Pop");
        }
      }
    }

    //Selecting the Period
    else if (interFace_Secondary == "Period") {
      Rotary(interFace_Option, pressed_button, 1, 2);


      if (pressed_button == "ROTARY_CONFIRM") {
        if (interFace_Option == 1) {
          selected_Period = "2010s";
          //When there is no more songs in the selected category to unlock
        }
        else if (interFace_Option == 2) {
          selected_Period = "50s";
        }

        int song = jackpot(selected_Genre, selected_Period);
        if (song == 0) {
          //Go back to choose another genre
          interFace_Secondary = "Genre";
          interFace_Option = 1;
          screen.setCursor(5, 0);
          screen.print("Genre");
          screen.setCursor(5, 1);
          screen.print("Movie");
          return;
        }
        else {
          //Serial.println(jackpot(selected_Genre, selected_Period));
          Jackpot_Chance -= 1;
          screen.clear();
          screen.setCursor(4, 0);
          screen.print("Jackpot!");
          String Acquired_Song = SongName(song);
          screen.setCursor((16 - Acquired_Song.length()) / 2, 1);
          screen.print(Acquired_Song);
          delay(2000);
          //Returns to the Menu page
          interFace = "Menu";
          interFace_Option = 1;
          screen.clear();
          screen.setCursor(6, 0);
          screen.print("Menu");
          screen.setCursor(1, 1);
          screen.print("Unlocked Songs");
          return;
        }
      }

      else if (pressed_button == "BACK_LYRICS") {
        interFace_Secondary = "Genre";
        interFace_Option = 1;
      }

      //Display the options in Menu to the LCD screen
      //Clear the screen first
      screen.clear();
      if (interFace_Secondary == "Genre") {
        screen.setCursor(5, 0);
        screen.print("Genre");
        screen.setCursor(5, 1);
        screen.print("Movie");
      }
      else {
        screen.setCursor(5, 0);
        screen.print("Period");
        if (interFace_Option == 1) {
          screen.setCursor(5, 1);
          screen.print("2010s");
        }

        else if (interFace_Option == 2) {
          screen.setCursor(6, 1);
          screen.print("50s");
        }
      }
    }
  }

  //When the interface is Be Connected
  else if (interFace == "Be Connected") {
    if (interFace_Secondary == "Receive") {
      Rotary(interFace_Option, pressed_button, 1, 2);

      if (pressed_button == "ROTARY_CONFIRM") {
        if (interFace_Option == 1) {
          interFace_Secondary = "share_song";
          interFace_Option = 1;
        }

        else if (interFace_Option == 2) {
          interFace_Secondary = "compete";
          interFace_Option = 1;
        }
      }


      else if (pressed_button == "BACK_LYRICS") {
        interFace = "Menu";
        interFace_Option = 1;
      }

      //lcd display
      screen.clear();
      if (interFace == "Menu") {
        screen.clear();
        screen.setCursor(6, 0);
        screen.print("Menu");
        screen.setCursor(1, 1);
        screen.print("Unlocked Songs");
      }
      else if (interFace_Secondary == "Receive") {
        screen.setCursor(2, 0);
        screen.print("Be Connected");
        if (interFace_Option == 1) {
          screen.setCursor(3, 1);
          screen.print("Share Song");
        }
        else if (interFace_Option == 2) {
          screen.setCursor(4, 1);
          screen.print("Compete");
        }
      }

      else if (interFace_Secondary == "share_song") {
        screen.setCursor(3, 0);
        screen.print("Share Song");
        if (unlocked_Song[0] == 0) {
          screen.setCursor(5, 1);
          screen.print("Empty");
          delay(2000);
          interFace_Secondary = "Receive";
          interFace_Option = 1;
          screen.setCursor(2, 0);
          screen.print("Be Connected");
          screen.setCursor(3, 1);
          screen.print("Share Song");
        }

        else {
          int song = unlocked_Song[interFace_Option - 1];
          //Serial.println("Hello");
          screen.setCursor((16 - SongName(song).length()) / 2, 1);
          screen.print(SongName(song));
        }
      }

      else if (interFace_Secondary == "compete") {
        screen.setCursor(4, 0);
        screen.print("Compete");
        if (unlocked_Song[0] == 0) {
          screen.setCursor(5, 1);
          screen.print("Empty");
          delay(2000);
          interFace_Secondary = "Receive";
          interFace_Option = 1;
          screen.setCursor(2, 0);
          screen.print("Be Connected");
          screen.setCursor(3, 1);
          screen.print("Share Song");
        }

        else {
          int song = unlocked_Song[interFace_Option - 1];
          //Serial.println("Hello");
          screen.setCursor((16 - SongName(song).length()) / 2, 1);
          screen.print(SongName(song));
        }
      }







    }

    else if (interFace_Secondary == "Playing_song") {
      if (pressed_button == "ROTARY_CONFIRM") {
        playing = !playing;
        if (playing == false) {
          Serial.println("pause");
          myDFPlayer.pause();
        }
        else {
          myDFPlayer.start();
        }
      }

      else if (pressed_button == "BACK_LYRICS") {
        //Serial.println(myDFPlayer.readState());
        //This means the song is finished
        if (myDFPlayer.readState() == 512) {
          if (lyrics == true) {
            lyrics_time += millis() - lyrics_timer_Start;
            lyrics = false;
          }

          //Send and receive the lyrics_time, then determines who wins
          radio.stopListening();
          radio.write(&lyrics_time, sizeof(lyrics_time));
          radio.startListening();
          while (!radio.available()); //wait until the lyrics_time of the opponenet was received
          radio.read(&lyrics_time_oponent , sizeof(lyrics_time_oponent));

          if (lyrics_time <= lyrics_time_oponent) {
            screen.setCursor(4, 0);
            screen.print("You Win!");
            screen.setCursor(2, 1);
            screen.print("Great Work!");
          }
          else {
            screen.setCursor(4, 0);
            screen.print("You Lose");
          }
          delay(5000);
          lyrics_time = 0;
          lyrics_time_oponent = 0;

          //Goes back to the menu
          interFace = "Menu";
          interFace_Option = 1;
          screen.clear();
          interFace = "Menu";
          interFace_Option = 1;
          screen.setCursor(6, 0);
          screen.print("Menu");
          screen.setCursor(1, 1);
          screen.print("Unlocked Songs");
        }
        else {
          Serial.println("Lyrics");
          lyrics = !lyrics;
          //Serial.println(lyrics);
          if (lyrics == true) {
            lyrics_timer_Start = millis();
            screen.setCursor(5, 0);
            screen.print("Lyrics");
            screen.setCursor(5, 1);
            screen.print("Lyrics");
          }
          else {
            screen.clear();
            //Add the lyrics-displaying time to the sum
            lyrics_time += millis() - lyrics_timer_Start;
          }
        }
      }
    }
    //Instead of receiving information, either sharing song or competing
    else {
      Rotary(interFace_Option, pressed_button, 1, input_indice_unlocked);


      if (pressed_button == "ROTARY_CONFIRM") {
        //Sending the number of the song through transceiver
        if (interFace_Secondary == "share_song") {
          int song = unlocked_Song[interFace_Option - 1];
          share_song(song);
          //lcd displays the song shared confirmation
          //Serial.println("Here");
          screen.clear();
          screen.setCursor(6, 0);
          screen.print("Song");
          screen.setCursor(4, 1);
          screen.print("Shared!");
          delay(2000);
          interFace_Secondary = "Receive";
          interFace_Option = 1;
        }

        //For competing
        else if (interFace_Secondary == "compete") {
          int song = unlocked_Song[interFace_Option - 1];
          share_song(song);
          myDFPlayer.play(unlocked_Song[interFace_Option - 1]);

          interFace_Secondary = "Playing_song";
          playing = true;

          screen.clear();
        }
      }

      else if (pressed_button == "BACK_LYRICS") {
        interFace_Secondary = "Receive";
        interFace_Option = 1;
      }

      //lcd display
      screen.clear();
      if (interFace_Secondary == "Receive") {
        screen.setCursor(2, 0);
        screen.print("Be Connected");
        screen.setCursor(3, 1);
        screen.print("Share Song");
      }

      else if (interFace_Secondary != "Playing_song") {
        if (interFace_Secondary == "share_song") {
          screen.setCursor(3, 0);
          screen.print("Share Song");
        }
        else if (interFace_Secondary == "compete") {
          screen.setCursor(4, 0);
          screen.print("Compete");
        }
        int song = unlocked_Song[interFace_Option - 1];
        screen.setCursor((16 - SongName(song).length()) / 2, 1);
        screen.print(SongName(song));
      }
    }
  }
}


//Function that read the pressing of the buttons
void read_buttons() {
  //Read Potentiometer and adjust the volume
  int potVal = analogRead(VOLUME_CONTROL);
  int currentVolume = map(potVal, 0, 1023, 0, 30);

  if (currentVolume != lastVolume) {
    //Serial.println(currentVolume);
    myDFPlayer.volume(currentVolume);
    lastVolume = currentVolume;
  }

  //Reading the buttons (with debounce)
  int reading_Up = digitalRead(ROTARY_UP);
  int reading_Down = digitalRead(ROTARY_DOWN);
  int reading_Confirm = digitalRead(ROTARY_CONFRIM);
  int reading_back_Lyrics = digitalRead(BACK_LYRICS);

  if (reading_Up != lastButtonState_rotaryUp || reading_Down != lastButtonState_rotaryDown || reading_Confirm != lastButtonState_Confirm || reading_back_Lyrics != lastButtonState_Back_Lyrics) {
    // reset the debouncing timer
    lastDebounceTime = millis();
  }

  if ((millis() - lastDebounceTime) > debounceDelay) {
    // whatever the reading is at, it's been there for longer than the debounce
    // delay, so take it as the actual current state:

    //Rotary up button
    // if the button state has changed:
    if (reading_Up != ButtonState_rotaryUp) {
      ButtonState_rotaryUp = reading_Up;

      if (ButtonState_rotaryUp == LOW) {
        //Serial.println(interFace_Option);
        Serial.println("Rotary Up");
        button_pressed("ROTARY_UP");
        Serial.println(interFace_Option);
      }
    }

    //Rotary Down Button
    if (reading_Down != ButtonState_rotaryDown) {
      ButtonState_rotaryDown = reading_Down;

      if (ButtonState_rotaryDown == LOW) {
        Serial.println("Rotary Down");
        button_pressed("ROTARY_DOWN");
        Serial.println(interFace_Option);
      }
    }

    //Rotary Confirm Button
    if (reading_Confirm != ButtonState_Confirm) {
      ButtonState_Confirm = reading_Confirm;

      if (ButtonState_Confirm == LOW) {
        Serial.println("Confirm");
        button_pressed("ROTARY_CONFIRM");
        Serial.println(interFace);
      }
    }

    //Back or show lyric Button
    if (reading_back_Lyrics != ButtonState_Back_Lyrics) {
      ButtonState_Back_Lyrics = reading_back_Lyrics;

      if (ButtonState_Back_Lyrics == LOW) {
        Serial.println("Back or show lyrics");
        button_pressed("BACK_LYRICS");
        Serial.println(interFace);
      }
    }
  }


  // save the readinga. Next time through the loop, they'll be the lastButtonState:
  lastButtonState_rotaryUp = reading_Up;
  lastButtonState_rotaryDown = reading_Down;
  lastButtonState_Confirm = reading_Confirm;
  lastButtonState_Back_Lyrics = reading_back_Lyrics;
}


//Setup Function
void setup()
{
  //For the mp3 player
  mySoftwareSerial.begin(9600);
  Serial.begin(115200);


  if (!myDFPlayer.begin(mySoftwareSerial)) {  //Use softwareSerial to communicate with mp3.
    Serial.println(F("Unable to begin:"));
    Serial.println(F("1.Please recheck the connection!"));
    Serial.println(F("2.Please insert the SD card!"));
    while (true);
  }
  Serial.println(F("DFPlayer Mini online."));

  //For the LCD screen
  screen.init();
  screen.backlight();


  //Set serial communictaion time out 500ms
  myDFPlayer.setTimeOut(500);
  //----Set volume----
  myDFPlayer.volume(15);  //Set volume value (0~30).
  //----Set different EQ----
  myDFPlayer.EQ(DFPLAYER_EQ_NORMAL);
  //----Set device we use SD as default----
  myDFPlayer.outputDevice(DFPLAYER_DEVICE_SD);


  //Setting up the pins (buttons)
  pinMode(ROTARY_UP, INPUT_PULLUP);
  pinMode(ROTARY_DOWN, INPUT_PULLUP);
  pinMode(ROTARY_CONFRIM, INPUT_PULLUP);
  pinMode(BACK_LYRICS, INPUT_PULLUP);
  pinMode(XPIN, INPUT);
  pinMode(YPIN, INPUT);
  pinMode(ZPIN, INPUT);

  //Initial Display on the lcd for the menu
  screen.setCursor(4, 0);
  screen.print("Welcome");
  delay(1500);
  screen.clear();
  screen.setCursor(6, 0);
  screen.print("Menu");
  screen.setCursor(1, 1);
  screen.print("Unlocked Songs");

  //Set up the transceiver
  radio.begin();
  radio.openWritingPipe(addresses[1]); // 00002
  radio.openReadingPipe(1, addresses[0]); // 00001
  radio.setPALevel(RF24_PA_MIN);



  //Test ground
  //Serial.println(Unlocked_2010s_Movie[0]);
  //myDFPlayer.play(1);
  //read_information();
}

void loop()
{
  //Check if it is already 1 day after the last Jackpot. If so, add 1 chance to jackpot.
  if (millis() - Jackpot_Clock > OneDay) {
    accelerometer_time = 0;
    Jackpot_Chance += 1;
    Jackpot_Clock = millis();
  }

  //Check if it is already 1 day after the last reception of song. If so, allow reception again.
  if (millis() - Receive_Clock > OneDay) {
    song_received = false;
    Receive_Clock = millis();
  }


  //Update the interface according to the inputs
  read_accelerometer();
  read_buttons();

  //Check if there is song being shared to receive
  if (interFace == "Be Connected") {
    //Song_received is the bool variable that check whether a reception has already occured 
    if (interFace_Secondary == "Receive" && song_received == false) {
      if (interFace_Option == 1) {
        receive_song();
      }
    }
  }
}

Code for the “Dummy Arduino”:

//Library
#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
RF24 radio(8, 9); // CE, CSN

//Addresses for transceriver
const byte addresses[][6] = {"00001", "00002"};

//Variables for the button 
const int BUTTONPIN = 7;
int buttonState;             
int lastButtonState = HIGH; 

//For debouncing
unsigned long lastDebounceTime = 0;  
unsigned long debounceDelay = 50; 

//Change this to 1-5 for sharing songs. Any number for stimulating the competition.
int shared_song = 100000;

//Function to send a integer representing a song through transceiver. It can be used to send int for any purpose. 
void share_song() {
  radio.stopListening();
  radio.write(&shared_song, sizeof(shared_song));
  shared_song += 1;
  Serial.println("Shared");
}

void setup() {
  radio.begin();
  radio.openWritingPipe(addresses[0]); // 00002
  radio.openReadingPipe(1,addresses[1]); // 00001
  radio.setPALevel(RF24_PA_MIN);
  
  pinMode(BUTTONPIN, INPUT_PULLUP);
}


void loop() {
  /*
  int song = 1;
  radio.write(&song, sizeof(song));
  delay(1000);
  */

   int reading = digitalRead(BUTTONPIN);


  // If the switch changed, due to noise or pressing:
  if (reading != lastButtonState) {
    // reset the debouncing timer
    lastDebounceTime = millis();
  }

  if ((millis() - lastDebounceTime) > debounceDelay) {
    // whatever the reading is at, it's been there for longer than the debounce
    // delay, so take it as the actual current state:

    // if the button state has changed:
    if (reading != buttonState) {
      buttonState = reading;

      
      if (buttonState == LOW) {
        share_song();
      }
    }
  }
  lastButtonState = reading;
}

 

Invisibility in covid

I’d like to expand on my sound crit idea to incorporate the invisible factors around covid prevention. Three sensors I’d use – wind sensor, ultrasonic sensor, volume sensor. Together, they can provide the count on the number of people in a room, ventilation speed and how loud people are talking. This would feed information into a p5.js sketch that shows a visualization of what’s in a green, orange or red zone. This can also be accompanied by a sound when the red zone is reached, and potentially windows can be set to open automatically.
Alternatively, I could also work on a shopping mall entrance checker with a temperature sensor and a ml5.js sketch that recognizes whether the person in front of it is wearing a mask. If they’re not wearing a mask, the door doesn’t open to allow them to enter (probably use a servo to simulate this), even if their temperature is within range.

Are you set for the next couple hours?

Problem

With quarantine, I have not been leaving the house all that much, so I seem to have lost my intuition about temperature and my habit of always checking the weather in the morning. This has led me to be surprised by how hot/cold it gets if I’ve gone on a long walk or gone grocery shopping as well as when it starts raining before I’ve gotten home. This led me to the idea of a system that when I leave the house, would let me know about weather changes in the 2 following hours without bothering me. During initial discussion, Jet brought up the suggestion of having reminders only if the person isn’t prepared for the weather change as well as a notification if the person forgot a mask.

Solution

To realize my idea, I plan to make a system that would notify you depending on the weather change when you are leaving the house. It would be known if you are leaving the house rather than entering house because the motion sensor would not be triggered. When you open the door, the current weather would be compared to the weather in the next 2 hours and if it will rain, snow, and/or the temperature will change by 5°F or more you’ll be notified. This data would be collected by a python web scraper. For rain or snow, a pitter-patter type sound would be played by the speaker and temperature changes would be signified by the LED strip’s colors. To attract the person’s attention, this would flash the appropriate color of red-orange or blue three times before staying on for about 5 seconds more. These reminders would be noticeable but not a bother if you are already prepared. This is what I want to first accomplish and then will move on to implementing the RFID part as this is most important to me and it is easily usable by everyone and tags would not have to be put on clothes. Additionally, this would be my first time using WiFi on a project, connecting the Arduino to Python, and creating a web scraper, so I want to focus first on doing that well.

To create the system that would only notify the person if they aren’t prepared, I’d put an RFID scanner near the door frame beside the magnetic door switch so that the the tags on clothes could be scanned while you walk by. The same barcodes would be on things that fit the same purpose. For example, all masks would have one code, rain jacket and umbrella would have another, sweatshirts another, and light jackets another. This can be customized per user in the household code so that what they’d wear for certain conditions and the matching codes are associated with just that person. Each person would be differentiated by the RFID card that they carry on them whenever they leave the house, here the CMU ID card. For a full product using this, I’ve found laundry safe RFID tags that were pretty cheap and small so could work really well in this application.

Components I plan to use:

  • magnetic door switch
  • esp8266 module or board
  • speaker
  • led strip
  • motion sensor

Add ons

  • RFID sensor and cards

 

Pilates buddy

As a person who always sits in front of the monitor 24/7, my colleagues and I started to realize that we are beginning to establish another commonality other than our majors — back pain. Then I started to brainstorm around the problems of keeping up a good posture, and I got an idea of what other exercises require one to be in a straight back position.

Then I thought of pilates. Pilates, an exercise that requires one to not only have a straight back, but also a regulated breathing technique, and I thought making a pilates tracker would fit the agenda of the final crit.

There are two main interactions I am trying to tackle: the back posture and the breathing tempo.

For the back posture algorithm, I thought of manipulating the accelerometer’s XYZ values on each. If each value shifts in an inclined position, then the vibration motor 1 would buzz.

For breathing, two variations would be needed for the vibration: a short breath and a long breath. By having a button, one can change the tempo of the vibration motor 2. The vibration motor will pulsate in a standard breathing tempo.

Then the additional feature would be background music. It will be peaceful, zen music to corporate with one’s respiration as well. This will be done by integrating the mp3 function in the code.

 

The image on the left would be a more realistic appearance compared to the one on the right. The band will have the accelerometer in the middle, and the two vibration motors on each side.

 

This is what it would look like as a sketch.

Ambition:

  • Developing a UI
  • Visualizing the band with real scale components in them
  • Other components?