//=============================================================================
// Meet Smartware, your smart tupperware! Food freshness with confidence.
//=============================================================================
// HOW IT WORKS //
// The user switches the device on and neopixel breathes white light.
// User sets timer through text messaging and the lights blink blue
// (int times based on # of days set).
// To start the timer, just put the tupperware in the fridge. It will start
// once the temperature drops below 40F.
// The next time you open the fridge, you'll be sure not to miss your tupperware
// at the back of the fridge and the countdown will show how much time you have
// to eat it.
// Once time is up, the neopixel blinks yellow. If at any point, the VOC gas
// reading goes above a 600, the neopixel will flash red until the dish is
// removed from the fridge.
// The device automatically turns off when the tupperware temperature sensor
// is >50F for more than 30 mins.
// DEMO ADJUSTMENTS //
// In the demo version, "Days" are converted to minutes
// Counter starts when temperature is <60F
// Shut off triggered if temp >75F, no time delay
// Depending on exterior encasement, red light may need to change to white
//=============================================================================
// Libraries Used
//=============================================================================
#include "OneWire.h"
#include "spark-dallas-temperature.h"
#include "neopixel.h"
#include "math.h"
#include "string"
//=============================================================================
// State Change
//=============================================================================
# define STATE_WELCOME 0 // Breath white until button is pushed
# define STATE_TURN_ON 1 // Blinks blue twice: Push of button triggers blink, changes state, and starts timer
# define STATE_TIMER 2 // Turn on green LEDs based on preset timer
# define STATE_THROW_OUT 3 // 5 blinks yellow: Time passed set threshold
# define STATE_TURN_OFF 4 // Blinks blue twice (same as turn on)
# define STATE_WAIT 5 // LEDs off. Click button again to turn on
# define STATE_SMS_SENT 6 // State to avoid duplicate SMS messages
# define STATE_GAS_HIGH 7 // Wipe red: Gas sensor >600
//=============================================================================
// Neopixel Controls
//=============================================================================
// IMPORTANT: Set pixel COUNT, PIN and TYPE
#define PIXEL_COUNT 16
#define PIXEL_PIN D3
#define PIXEL_TYPE WS2812
Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXEL_COUNT, PIXEL_PIN, PIXEL_TYPE);
//=============================================================================
// Temperature Sensor Controls
//=============================================================================
OneWire oneWire(A0); // Pass oneWire reference to Dallas Temperature.
DallasTemperature dallas(&oneWire);
double temperature = 0.0; // Temperature varibale in Degrees Celsius
double temperatureF = 0.0; // Temperature variable in Degrees Farenheight
double tempFSmoothedValue = 0.0; // Smoothed Temperature variable
// Low Pass Filter (temp)
#define filterSamples 5
int tempFSampleArray [filterSamples];
//=============================================================================
// Variable Identification
//=============================================================================
//Initialize and store a value in the state to get things started
int state = 0; // Start things off with breathing white & initial sms
int buttonPin = D1; // Button for interrupt
int buttonValue = HIGH; // Button for interrupt initial value
int set_smartware = 7; // Initial timer set to 7 days
String set_smartware_str = ""; // Global variable for user input coming from app.rb
int INTERVAL_TIME = 0;
int timeElapsed = 0;
bool hasElapsed2 = 0; // Use as trigger to send SMS after 2 days
bool hasSentElapsed2 = 0; // Stops duplicates from being sent
bool has3daysLeft = 0; // Use as trigger to send SMS 3 days before complete
bool hasSent3daysLeft = 0; // Stops duplicates from being sent
bool has1dayLeft = 0; // Use as trigger to send SMS 1 day before complete
bool hasSent1dayLeft = 0; // Stops duplicates from being sent
bool hasSentStinkySMS = 0;
bool hasSentColdSMS = 0;
// Low Pass Filter (gas)
#define filterGasReading 12
int gasReadingArray [filterGasReading]; // array for holding raw sensor values for sensor2
int gasReadingRawValue = 0;
int gasReadingSmoothedValue = 0;
// Neopixel colors
int red = 0xff0000;
int blue = 0x0000ff;
int green = 0x00ff00;
int yellow = 0xFFFF00;
//=============================================================================
// SMS / MMS handler for app.rb responses
//=============================================================================
void handleSMSEvent(const char *event, const char *data) {
Serial.println("handler started");
Serial.println(data); // data is coming from linked app.rb
set_smartware_str = (data);
}
//=============================================================================
// Setup
//=============================================================================
void setup(){
Serial.begin(9600); // Start the serial monitor
// Low pass filter smoothening array to smooth temp readings
for(int i = 0; i < filterSamples; i++){
tempFSampleArray[i]= 0;
}
// Initiate the temperature sensor library
dallas.begin();
// Cloud variables
Particle.variable("temperatureF", &temperatureF, DOUBLE);
Particle.variable("melsGasVar", &gasReadingSmoothedValue, INT);
// Initiate the Neopixels. Set them all to off
strip.begin();
strip.show();
strip.setBrightness(3);
// Set time to EST and track once started
Time.zone(-4);
// Attach an interrupt using the button (turns device on)
pinMode( buttonPin, INPUT_PULLUP );
attachInterrupt( buttonPin, getButtonValueOn, RISING);
// Initiate the array
for(int i=0; i<filterGasReading; i++)
{
gasReadingArray[i]= 0;
}
// Subscribe to incoming messages coming through app.rb
Particle.subscribe("smart_food/sms/incoming", handleSMSEvent);
}
//=============================================================================
// Run this loop Continuously
//=============================================================================
void loop(){
readTemperature(); // Continuously check temp reading
doMonitorGas(); // Continuously monitor gas sensor
switch( state ){
case STATE_WELCOME: // Breathe white + welcome SMS goes out
doWelcomeState();
break;
case STATE_SMS_SENT: // Breathe white, stop sending SMS
doWelcomeState();
break;
case STATE_TURN_ON: // Blink blue
doTurnOn();
break;
case STATE_TIMER: // Green count down
doCountDown();
break;
case STATE_GAS_HIGH: // Blink red: Gas sensor >600
blinkRed(1);
break;
case STATE_THROW_OUT: // Blink yellow, time elapsed
doThrowOutReminder();
break;
case STATE_TURN_OFF: // Blinks blue twice then shuts off
doTurnOff();
break;
}
delay(50);
}
//=============================================================================
// Webhooks
//=============================================================================
void doWebhookSMS(){
if( state == 0 ){
Particle.publish("smart_food_webhook_sms", "Hey! Smartware here. Put me in the fridge to start my 7 day timer or reply SET to set the timer manually.", PRIVATE);
delay(50);
state = STATE_SMS_SENT;
}else if( state == STATE_TURN_ON && hasSentColdSMS == 0){
Particle.publish("smart_food_webhook_sms", "Aaa, nice and cool. Your smartware timer has started.", PRIVATE);
hasSentColdSMS = 1;
}else if( state == 1 ){
Particle.publish("smart_food_webhook_sms", "Aaa, nice and cool. Your smartware timer has started.", PRIVATE);
}else if( state == 2 && hasElapsed2 == 1 && hasSentElapsed2 == 0 ){ // Send SMS after 2 days
Particle.publish("smart_food_webhook_sms", "Food waste costs the average American family up to $2,000/year, but youre not average are you? ;)", PRIVATE);
}else if( state == 2 && has3daysLeft == 1 && hasSent3daysLeft == 0 ){ // Send SMS 3 days before complete
Particle.publish("smart_food_webhook_sms", "30-40 percent of food produced in the US gets wasted. 43 percent of that happens at the hands of the consumer. Eat leftovers. Check. Save the world. Check.", PRIVATE);
}else if( state == 2 && has1dayLeft == 1 && hasSent1dayLeft == 0 ){ // Send SMS 1 day before complete
Particle.publish("smart_food_webhook_sms", "Remember to eat your leftovers! The smartware timer ends tomorrow.", PRIVATE);
}else if( state == STATE_TURN_OFF ){
Particle.publish("smart_food_webhook_sms", "I hope you enjoyed your leftovers! Shutting down now.", PRIVATE);
}else if( state == STATE_GAS_HIGH && hasSentStinkySMS == 0){
Particle.publish("smart_food_webhook_sms", "Whewwwww, thats getting ripe. Check your leftovers, my ditigal sniffer detected something funky.", PRIVATE);
hasSentStinkySMS = 1; // only send one message
}
}
//=============================================================================
// Welcome state
//=============================================================================
void doWelcomeState(){
breatheWhite();
doWebhookSMS();
}
//=============================================================================
// Blink twice when on button is clicked
//=============================================================================
void doTurnOn(){
if( state == STATE_TURN_ON ){ // Once the button is pushed, the device turns on
blinkBlue(5);
delay(50);
doWebhookSMS(); // Send "timer turned on" SMS triggered by temp
state = STATE_TIMER;
}
}
//=============================================================================
// Light transition for duration of the time interval
//=============================================================================
long startedIntervalAt = -1;
// c++ for includes: if(str1.find(str2) != std::string::npos){
/*set_smartware = set_smartware_str.toInt(); // turns user input (string) to int*/
void doCountDown(){
String nan_trick = ":"; // All non integer inputs should include ":"
char * checking; // set up for string function to check if one string contains another substring
checking = strstr(set_smartware_str, nan_trick);
if( checking != NULL || set_smartware_str == "" ){ // this function checks to see if a response like "bye" or "set" was entered
set_smartware = 7; // if NAN or the user hasn't input a value, leave default timer.
}else{ // set smartware based on user input
set_smartware = set_smartware_str.toInt(); // turn user input (string) to int
if ( set_smartware > 30 ) { // if user sets something too large
set_smartware = 30; // default max is 30 days
}
}
Serial.println( set_smartware ); // check to make sure we're not getting any NANs
INTERVAL_TIME = (4000 * set_smartware); // SMS incoming messages used to set duration
if( startedIntervalAt == -1 ) // if start, keep record of time elapsed
startedIntervalAt = millis();
timeElapsed = millis() - startedIntervalAt; // check time passed
// light up a certain number of pixels based on time elapsed.
int numPixelsToLight = map( timeElapsed, 0, INTERVAL_TIME, 0, strip.numPixels() );
for( int i = 0; i < strip.numPixels(); i++ ){
if( i <= numPixelsToLight ){
strip.setPixelColor(i, strip.Color( 127,255,0 )); //green chartreuse
}else{
strip.setPixelColor(i, 0); // turn all pixels off
}
}
strip.show();
if( timeElapsed > INTERVAL_TIME){ // if beyond time, let's move on
state = STATE_THROW_OUT;
hasElapsed2 = 0; // Use as trigger to send SMS after 2 days
has3daysLeft = 0; // Use as trigger to send SMS 3 days before complete
has1dayLeft = 0; // Use as trigger to send SMS 1 day before complete
doWebhookSMS();
}else if(timeElapsed > INTERVAL_TIME - 4000 ){ //&& timeElapsed > INTERVAL_TIME - 2300
has1dayLeft = 1;
hasElapsed2 = 0;
has3daysLeft = 0;
doWebhookSMS();
delay(10);
hasSent1dayLeft = 1; // Stops duplicates from being sent
}else if(timeElapsed > INTERVAL_TIME - 7000 ){ //&& timeElapsed > INTERVAL_TIME - 7000
has3daysLeft = 1;
hasElapsed2 = 0;
has1dayLeft = 0;
doWebhookSMS();
delay(10);
hasSent3daysLeft = 1; // Stops duplicates from being sent
}else if(timeElapsed > 4000 ){ //&& timeElapsed < 4000
hasElapsed2 = 1;
has3daysLeft = 0;
has1dayLeft = 0;
doWebhookSMS();
delay(10);
hasSentElapsed2 = 1; // Stops duplicates from being sent
}
delay( 100 );
}
//=============================================================================
// Timer done, time to toss food
//=============================================================================
void doThrowOutReminder(){
blinkYellow( 2 );
}
//=============================================================================
// Bring temp sensor to >80, blink Red as warning and turn all lights off
//=============================================================================
void doTurnOff(){
blinkBlue( 2 );
wipeDownNeoPixels();
blinkBlue( 5 );
TurnOffLEDs();
doWebhookSMS(); // send "device turned off" message triggered by temp
state = STATE_WAIT;
}
//=============================================================================
// Button methods
//=============================================================================
void getButtonValueOn(){
if(state == STATE_WELCOME){ //If STATE_WELCOME, button turns countdown on
state = STATE_TURN_ON;
}else if(state == STATE_THROW_OUT || state == STATE_TIMER ){
state = STATE_TURN_OFF;
}else{
state = STATE_WELCOME; //Otherwise reset to STATE_WELCOME
doTurnOn();
startedIntervalAt = -1;
}
}
//=============================================================================
// Breathing light pattern
//=============================================================================
float breatheWhite(){
// Calc the sin wave for the breathing white led
float val = (exp(sin(millis()/2000.0*M_PI)) - 0.36787944)*108.0;
uint16_t i;
uint32_t c = strip.Color(val, val, val);
for(i=0; i< strip.numPixels(); i++) {
strip.setPixelColor(i, c );
}
strip.show();
}
//=============================================================================
// Blink Helpers
//=============================================================================
void wipeDownNeoPixels(){ // Wipe down red
for (int i=strip.numPixels() - 1; i >= 0; i--) {
strip.setPixelColor(i, 255, 0, 0 );//red
strip.show();
delay( 200 );
}
}
void blinkBlue( int times ){ // Blink blue int number of times
for( int i = 0 ; i < times; i++ ){
TurnOnBlue();
delay(150);
TurnOffLEDs();
delay(150);
}
}
void blinkRed( int times ){ // Blink red int number of times
for( int i = 0 ; i < times; i++ ){
TurnOnRed();
delay(150);
TurnOffLEDs();
delay(150);
}
}
void blinkYellow( int times ){ // Blink yellow int number of times
for( int i = 0 ; i < times; i++ ){
TurnOnYellow();
delay( 150);
allNeopixelsOff();
delay( 150);
}
}
void TurnOnRed(){ // Turn all LEDs on (red)
int i;
for(i=0; i < 16; i++)
{
strip.setPixelColor(i, red);
strip.show();
}
}
void TurnOnBlue(){ // Turn all LEDs on (blue)
int i;
for(i=0; i < 16; i++)
{
strip.setPixelColor(i, blue);
strip.show();
}
}
void TurnOnYellow(){ // Turn all LEDs on (yellow)
int i;
for(i=0; i < 16; i++)
{
strip.setPixelColor(i, yellow);
strip.show();
}
}
void TurnOffLEDs(){ // Turn all LEDs off
int i;
for(i=0; i < 16; i++)
{
strip.setPixelColor(i, 0x000000);
strip.show();
}
}
//=============================================================================
// Additional Neopixel Helpers
//=============================================================================
void allNeopixelsOn(){
for (int i=0; i < strip.numPixels(); i++) {
strip.setPixelColor(i, strip.Color( 10,10,10 )); // turn all pixels on
}
strip.show();
}
void allNeopixelsOff(){
for (int i=0; i < strip.numPixels(); i++) {
strip.setPixelColor(i, 0); // turn all pixels off
}
strip.show();
}
//=============================================================================
// VOC Sensor Helper
//=============================================================================
void doMonitorGas(){
time_t time = Time.now();
String tStr = Time.format(time, TIME_FORMAT_ISO8601_FULL); // 2017-04-05T07:08:47-05:00
Serial.print( tStr ); //track the time and date the data was sent
Serial.print( ", "); // In order to use data in CSV file, separate w/ comma
gasReadingRawValue = analogRead(A3); // grab gas sensor reading from pin A0
if ( gasReadingRawValue <= 0){ // remove extreme values
delay(1);
}else if( gasReadingRawValue >= 290 && state == STATE_TIMER ){
state = STATE_GAS_HIGH;
// Flashes red if gas reading goes above normal & still in count down.
// Normal varies based on food. Current solution doesn't adjust for food type
doWebhookSMS();
}else if( gasReadingRawValue >= 290 && state == STATE_THROW_OUT ){
state = STATE_GAS_HIGH;
// Flashes red if gas reading goes above normal & count down just ended.
// Normal varies based on food. Current solution doesn't adjust for food type
doWebhookSMS();
}else{
gasReadingRawValue = analogRead(A3);
gasReadingSmoothedValue = digitalSmooth( gasReadingRawValue , gasReadingArray );
}
Serial.print("\tRaw gas reading:,");
Serial.print(gasReadingRawValue);
Serial.print( ",\tSmoothed gas reading:,");
Serial.print(gasReadingSmoothedValue);
Serial.print( ",");
}
//=============================================================================
// Low Pass Filter to smooth gas readings
//=============================================================================
int digitalSmoothGas(int gasDataValue, int * gasSensorSampleArray ){
int j, k, temp, top, bottom;
long total;
static int i;
static int sorted[filterGasReading];
boolean done;
// increment counter and roll over if necc. - % (modulo operator) rolls over variable
i = (i + 1) % filterGasReading;
gasSensorSampleArray[i] = gasDataValue; // input new data into the oldest slot
for (j=0; j<filterGasReading; j++){ // transfer data array into anther array for sorting and averaging
sorted[j] = gasSensorSampleArray[j];
}
done = 0; // flag to know when we're done sorting
while(done != 1){ // simple swap sort, sorts numbers from lowest to highest
done = 1;
for (j = 0; j < (filterGasReading - 1); j++){
if (sorted[j] > sorted[j + 1]){ // numbers are out of order - swap
temp = sorted[j + 1];
sorted [j+1] = sorted[j] ;
sorted [j] = temp;
done = 0;
}
}
}
// throw out top and bottom 15% of samples - limit to throw out at least one from top and bottom
// +1 makes up for asymmetry caused by integer rounding
bottom = max(((filterGasReading * 15) / 100), 1);
top = min((((filterGasReading * 85) / 100) + 1 ), (filterGasReading - 1));
k = 0;
total = 0;
for ( j = bottom; j< top; j++){
total += sorted[j]; // total remaining indices
k++;
}
return total / k; // divide by number of samples
}
//=============================================================================
// Temperature Control Functions
//=============================================================================
void readTemperature(){ // Function gets temp reading from Dallas temp sensor
dallas.requestTemperatures(); // Request temperature
sin( 23423 );
float tempC = dallas.getTempCByIndex(0); // Get the temperature in Celcius
if ( tempC <= -100 || tempC >= 50 ){ // Eemove extreme values
delay(2);
}else{
temperature = (double)tempC; // Convert to double
float tempF = DallasTemperature::toFahrenheit( tempC ); // Convert to Fahrenheit
temperatureF = (double)tempF; // Convert to double
}
tempFSmoothedValue = digitalSmooth( temperatureF , tempFSampleArray ); // Smooth temp reading
Serial.print( "\tRaw Temp = ");
Serial.print( temperatureF );
Serial.println( ", ");
Serial.print( "\tSmoothed Temp = ");
Serial.print( tempFSmoothedValue );
Serial.println( ", ");
checkTemperature(); // Adjust heater output based on temp reading
}
void checkTemperature(){ // Start timer when put in fridge
if(tempFSmoothedValue > 61 && tempFSmoothedValue < 72 && state == STATE_SMS_SENT){
state = STATE_TURN_ON; // Triggers doTurnOn(); Blink, send SMS message, start counter
}else if (tempFSmoothedValue > 72 && state == STATE_TIMER ){ // or after countdown passes threshold
state = STATE_TURN_OFF; // Triggers doTurnOff (); Blink, send shutdown SMS, turn off
}else if (tempFSmoothedValue > 72 && state == STATE_GAS_HIGH){ // or after countdown passes threshold
state = STATE_TURN_OFF; // Triggers doTurnOff (); Blink, send shutdown SMS, turn off
}
}
//=============================================================================
// Low Pass Filter to smooth Temperature readings
//=============================================================================
//Low pass filter to smooth the temerature readings
int digitalSmooth(int dataValue, int * sensorSampleArray ){
int j, k, temp, top, bottom;
long total;
static int i;
static int sorted[filterSamples];
boolean done;
// increment counter and roll over if necc. - % (modulo operator) rolls over variable
i = (i + 1) % filterSamples;
sensorSampleArray[i] = dataValue; // input new data into the oldest slot
for (j=0; j<filterSamples; j++){ // transfer data array into anther array for sorting and averaging
sorted[j] = sensorSampleArray[j];
}
done = 0; // flag to know when we're done sorting
while(done != 1){ // simple swap sort, sorts numbers from lowest to highest
done = 1;
for (j = 0; j < (filterSamples - 1); j++){
if (sorted[j] > sorted[j + 1]){ // numbers are out of order - swap
temp = sorted[j + 1];
sorted [j+1] = sorted[j] ;
sorted [j] = temp;
done = 0;
}
}
}
// throw out top and bottom 15% of samples - limit to throw out at least one from top and bottom
bottom = max(((filterSamples * 15) / 100), 1);
top = min((((filterSamples * 85) / 100) + 1 ), (filterSamples - 1));
// the + 1 is to make up for asymmetry caused by integer rounding
k = 0;
total = 0;
for ( j = bottom; j< top; j++){
total += sorted[j]; // total remaining indices
k++;
}
return total / k; // divide by number of samples
}
Click to Expand
Content Rating
Is this a good/useful/informative piece of content to include in the project? Have your say!
You must login before you can post a comment. .