Sarah HanGrace HanKevin VoDathan WongChia-Chun Chen
Created April 30, 2019 © GPL3+

SolBox 向日盒

UV and temperature monitor with Bluetooth speaker that prevents exposure to dangerous levels of UV rays through notifications and reminders.

IntermediateFull instructions provided3.5 hours700
SolBox 向日盒

Things used in this project

Hardware components

ELEGOO UNO R3 Project Complete Starter Kit
ELEGOO UNO R3 Project Complete Starter Kit
Specific components used from the starter kit were the Arduino Uno, LCD panel, Rotary Encoder, Temperature/Humidity sensor, resistors, wires, and 10k potentiometer.
×1
WaveShare UV Sensor
×1
HiLetgo Micro SD TF Card Adapter
×1
LM386 Audio Amplifier Module
×1
Bluetooth speaker
To enable the Bluetooth functionality we hacked a cheap Bluetooth speaker we received from an event. We opened up the Bluetooth speaker and cut the wires from the printed circuit board to the speaker which left us with a speaker component and a Bluetooth enabled printed circuit board.
×1
ELEGOO Neptune FDM 3D Printer
ELEGOO Neptune FDM 3D Printer
×1
Aideepen ESP8266 Serial Wi-Fi Wireless ESP-01 Adapter Module
×1
Relay Module (Generic)
×2
Micro SD Card
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free

Story

Read more

Custom parts and enclosures

SolBox - top lid

Cover for the box, placement for LCD screen, UV sensor, rotary knob

SolBox - bottom container

Holds all of the electronics (sensors, Arduino, wires, breadboards)

Schematics

SolBox Circuit Diagram

Connect the different components to the Arduino Uno as described in the document. This is not a traditional circuit diagram but hopefully is easier to follow for anyone new to electronics.

Sound Files

Unzip the sound files folder and copy the files to your SD card.

Code

SolBox Code

Arduino
The following code will run on the Arduino Uno. The wav files must be formatted with 8 bit resolution, 16000 Hz sampling rate, mono audio channel, and PCM unsigned 8-bit. You can use an online audio converter to get the right format https://audio.online-convert.com/convert-to-wav. The current version collects data every second but can be changed by editing the readInterval variable in milliseconds.
#include "DHT.h" 
#include "LiquidCrystal.h" 
#include "SD.h" 
#include "SPI.h" 
#include "TMRpcm.h" 
#include "SoftwareSerial.h" 
 
//Calculations 
unsigned long readInterval = 1000; 
unsigned long elapsedTime = 0; 
unsigned long newTime; 
unsigned long oldTime = 0; 
unsigned int skinTone = 999; 
unsigned int skinType[] = {166, 200, 266, 333, 533, 1000}; 
double averageUV = 0; 
double timeAllowed; 
boolean sunscreenAlert = false; 
boolean insideAlert = false; 
 
//Wifi 
SoftwareSerial mySerial(0,1); 
 
//SD card 
#define SD_ChipSelectPin 4  
TMRpcm speaker; 
 
//Temperature Sensor 
#define DHTPIN 6 
#define DHTTYPE DHT11 
DHT dht(DHTPIN, DHTTYPE); 
 
//UV Sensor 
const int pinUV = A0; 
float uv; 
int uvIndex; 
 
//LCD Display 
LiquidCrystal lcd(A1, A2, A3, A4, A5, 5); 
 
//Rotary Encoder 
enum PinAssignments{ 
  encoderPinA = 2, encoderPinB = 3, clearButton = 10 
}; 
volatile unsigned int encoderPos = 1; 
unsigned int lastReportedPos=1; 
static boolean rotating=false; 
boolean A_set = false; 
boolean B_set = false; 
unsigned int maxSkinTone = 6; 
 
//Speaker 
#define RELAYPIN 7 
#define SPKPIN 9 
 
void setup() { 
  Serial.begin(9600); 
   
  //Temperature/Humidity Sensor 
  dht.begin(); 
   
  //LCD Display 
  lcd.begin(16,2); 
  lcd.print("Set skin tone"); 
  lcd.setCursor(0,1); 
  lcd.print(encoderPos); 
 
 
  //WiFi 
  mySerial.begin(9600); 
  SendCommand("AT+RST", "Ready"); 
  delay(5000); 
  SendCommand("AT+CWMODE=1","OK"); 
  SendCommand("AT+CIFSR", "OK"); 
  SendCommand("AT+CIPMUX=1","OK"); 
  SendCommand("AT+CIPSERVER=1,80","OK"); 

   
  //Rotary Encoder 
  pinMode(encoderPinA, INPUT_PULLUP); 
  pinMode(encoderPinB, INPUT_PULLUP); 
  pinMode(clearButton, INPUT_PULLUP); 
  attachInterrupt(digitalPinToInterrupt(2), doEncoderA, CHANGE); 
  attachInterrupt(digitalPinToInterrupt(3), doEncoderB, CHANGE); 
   
 
  //Speaker 
  pinMode(RELAYPIN, OUTPUT); 
  digitalWrite(RELAYPIN, LOW); 
  speaker.speakerPin=SPKPIN; 
  if(!SD.begin(SD_ChipSelectPin)){ 
    Serial.print("Failed to initiate SD card"); 
    return; 
  } 
 
  //SD Card 
  if(!SD.begin(SD_ChipSelectPin)){ 
    Serial.println("SD fail"); 
    return; 
  } 
   
 
 
} 
 
void loop() { 
 
    //read sensor values every interval to avoid collecting too much data 
    newTime = millis(); 
    if((newTime - oldTime) > readInterval){ 
      //Read UV sensor input and conversion to UV index 
      //UV index conversion from http://arduinolearning.com/code/arduino-guva-s12sd-uv-sensor.php 
      uv = analogRead(pinUV); 
      if(uv <= 40){ 
        uvIndex = 0; 
      } else{ 
        uvIndex = floor(uv/20)-1; 
      } 
       
      //Read temperature data 
      float temp = dht.readTemperature(); 
 
      //Send data over Wifi 
      mySerial.println("AT+CIPSEND=0,23"); 
      mySerial.println(uvIndex); 
      SendCommand("AT+CIPCLOSE=0","OK"); 
       
      //Calculate average UV exposure 
      elapsedTime++; 
      oldTime = newTime; 
      averageUV = (((elapsedTime - 1) * averageUV) + uvIndex)/elapsedTime; 
 
       
       
      //Calculate times 
      if(skinTone <= maxSkinTone){ 
        timeAllowed = (skinType[skinTone-1] / averageUV) * 60000; 
        //Time to apply sunscreen 
        if (((timeAllowed/2) < (elapsedTime * readInterval)) && !sunscreenAlert){ 
          lcd.clear(); 
          lcd.print("Sunscreen time"); 
          sunscreenAlert = true; 
          //Play from SD card 
          digitalWrite(RELAYPIN, HIGH); 
          speaker.play("4.wav"); 
          delay(3000); 
          speaker.play("2.wav"); 
          delay(3000); 
          digitalWrite(RELAYPIN, LOW); 
        } 
        //Time to go inside 
        if ((timeAllowed < (elapsedTime * readInterval)) && !insideAlert){ 
          lcd.clear(); 
          lcd.print("Time to go inside"); 
          insideAlert = true; 
          //Play from SD card 
          digitalWrite(RELAYPIN, HIGH); 
          speaker.play("3.wav"); 
          delay(3000); 
          speaker.play("1.wav"); 
          delay(3000); 
          digitalWrite(RELAYPIN, LOW); 
        } 
      } 
       
      //Output for debugging 
      Serial.print("UV Index: "); 
      Serial.print(uvIndex); 
      Serial.print(" UV Value: "); 
      Serial.print(uv); 
      Serial.print(" Temp: "); 
      Serial.print(temp); 
      Serial.print(" Average UV: "); 
      Serial.print(averageUV); 
      Serial.print(" Elapsed Time: "); 
      Serial.print(elapsedTime); 
      Serial.print(" Time Allowed: "); 
      Serial.print(timeAllowed); 
      Serial.println(); 
    } 
     
    //Rotary Encoder 
    if(skinTone > 6){ 
      rotating = true; 
      //Print skin tone selection 
      lcd.setCursor(0,1); 
      if(lastReportedPos != encoderPos){ 
        if(lastReportedPos >= 10){ 
          lcd.clear(); 
          lcd.setCursor(0,0); 
          lcd.print("Set skin tone"); 
          lcd.setCursor(0,1); 
        } 
      lcd.print(encoderPos); 
      lastReportedPos = encoderPos; 
      } 
      //Select skin tone 
      if(digitalRead(clearButton) == LOW){ 
        lcd.clear(); 
        skinTone = encoderPos; 
        elapsedTime = 0; 
        lcd.print("Monitoring Skin"); 
      } 
    } 
     
} 
 
 
void doEncoderA(){ 
  if(rotating) delay(1); 
  if(digitalRead(encoderPinA)!=A_set){ 
    A_set=!A_set; 
    if(A_set && !B_set){ 
      encoderPos++; 
      if (encoderPos > maxSkinTone) encoderPos = 1; 
      rotating = false; 
    } 
  } 
} 
 
void doEncoderB(){ 
  if(rotating) delay(1); 
  if(digitalRead(encoderPinB) != B_set){ 
    B_set = !B_set; 
    if(B_set && !A_set){ 
      encoderPos--; 
      if (encoderPos == 0) encoderPos = maxSkinTone; 
      rotating = false; 
    } 
  } 
} 
 

boolean SendCommand(String cmd, String ack){ 
  mySerial.println(cmd); 
  if (!echoFind(ack)) return true; 
} 
 
boolean echoFind(String keyword){ 
 byte current_char = 0; 
 byte keyword_length = keyword.length(); 
 long deadline = millis() + 5000; 
 while(millis() < deadline){ 
  if (mySerial.available()){ 
    char ch = mySerial.read(); 
    Serial.write(ch); 
    if (ch == keyword[current_char]) 
      if (++current_char == keyword_length){ 
       Serial.println(); 
       return true; 
    } 
   } 
  } 
 return false; // Timed out 
} 

Credits

Sarah Han

Sarah Han

13 projects • 79 followers
Software Engineer, Design, 3D
Grace Han

Grace Han

2 projects • 3 followers
Kevin Vo

Kevin Vo

4 projects • 3 followers
Dathan Wong

Dathan Wong

2 projects • 1 follower
Chia-Chun Chen

Chia-Chun Chen

2 projects • 0 followers

Comments