Blynk Board Washer/Dryer Alarm

Pages
Contributors: jimblom
Favorited Favorite 9

Program the Blynk Board

If you haven’t set your computer up to program the Blynk Board using Arduino yet, read through our Blynk Board Arduino Development Guide first. Following that, you should have the Blynk Board hardware definitions and most of the libraries you’ll need added to your Arduino IDE.

Set your Arduino IDE up for Blynk Board development!

Install the SparkFun MMA8452Q Arduino Library

The latest version of our MMA8452Q Arduino library can be downloaded from the SparkFun_MMA8452Q_Arduino_Library GitHub repository (click the "Download ZIP" button there). Or you can skip the detour, by clicking the button below.

The library will be downloaded as a ZIP file. To install it in Arduino, navigate to Sketch > Include Library > Add .ZIP Library.... Then select the MMA8452Q ZIP folder you just downloaded.

Add library from zip folder

Copy/Paste the Code

Our "Blynk Board Laundry Monitor" source code is also available on GitHub. Download it from there, or copy/paste from the box below:

language:c
#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>
#include <Wire.h> // Must include Wire library for I2C
#include <SparkFun_MMA8452Q.h> // Includes the SFE_MMA8452Q library
#include <Adafruit_NeoPixel.h>

//////////
// WiFi //
////////// // Enter your WiFi credentials here:
const char WiFiSSID[] = "WiFiNetworkName";
const char WiFiPSWD[] = "WiFiPassword";

///////////
// Blynk //
///////////             // Your Blynk auth token here
const char BlynkAuth[] = "0a1b2c3d4e5f";
bool notifyFlag = false;
#define VIRTUAL_ENABLE_PUSH     V0
#define VIRTUAL_SHAKE_THRESHOLD V1
#define VIRTUAL_START_TIME      V2
#define VIRTUAL_STOP_TIME       V3
#define VIRTUAL_LCD             V4
#define VIRTUAL_SHAKE_VALUE     V5
WidgetLCD lcd(VIRTUAL_LCD);
bool pushEnabled = false;
void printLaundryTime(void);

/////////////////////
// Shake Detection //
/////////////////////
unsigned int shakeThreshold = 50;
unsigned int shakeStartTimeHysteresis = 1000;
unsigned int shakeStopTimeHysteresis = 10;
unsigned long shakeStateChangeTime = 0;
unsigned long shakeStartTime = 0;
bool loadTimer = true;

enum {
  NO_SHAKING_LONG, // Haven't been shaking for a long time
  NO_SHAKING,  // Hasn't been any shaking
  PRE_SHAKING, // Started shaking, pre-hysteresis
  SHAKING,     // Currently shaking
  POST_SHAKING // Stopped shaking, pre-hysteresis
} shakingState = NO_SHAKING;

// Possible return values from the shake sensor
enum sensorShakeReturn {
  SENSOR_SHAKING,
  SENSOR_NOT_SHAKING,
  SENSOR_NOT_READY
};
sensorShakeReturn checkShake(void);
void shakeLoop(void);

////////////////////////////
// MMA8452Q Accelerometer //
////////////////////////////
MMA8452Q accel;
int16_t lastX, lastY, lastZ;
void initAccel(void);

//////////////////////////
// Hardware Definitions //
//////////////////////////
const int LED_PIN = 5;
const int RGB_PIN = 4;
Adafruit_NeoPixel rgb = Adafruit_NeoPixel(1, RGB_PIN, NEO_GRB + NEO_KHZ800);
void setLED(uint8_t red, uint8_t green, uint8_t blue);

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

  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW); // Turn off blue LED
  rgb.begin(); // Set up WS2812
  initAccel(); // Set up accelerometer
  setLED(0, 0, 32); // LED blue

  // Initialize Blynk, and wait for a connection before doing anything else
  Serial.println("Connecting to WiFi and Blynk");
  Blynk.begin(BlynkAuth, WiFiSSID, WiFiPSWD);
  while (!Blynk.connected())
    Blynk.run();
  Serial.println("Blynk connected! Laundry monitor starting.");
  setLED(0, 32, 0); // LED green
}

void loop()
{
  shakeLoop(); // Check if we're shaking, and update variables accordingly
  Blynk.run(); // Blynk run as much as possible
}

bool firstConnect = true;
BLYNK_CONNECTED()
{
  if (firstConnect) // When we first connect to Blynk
  {
    // Two options here. Either sync values from phone to Blynk Board:
    //Blynk.syncAll(); // Uncomment to enable.
    // Or set phone variables to default values of the globals:
    Blynk.virtualWrite(VIRTUAL_SHAKE_THRESHOLD, shakeThreshold);
    Blynk.virtualWrite(VIRTUAL_STOP_TIME, shakeStopTimeHysteresis);
    Blynk.virtualWrite(VIRTUAL_START_TIME, shakeStartTimeHysteresis);
    Blynk.virtualWrite(VIRTUAL_ENABLE_PUSH, pushEnabled);

    // Print a splash screen:
    lcd.clear();
    lcd.print(0, 0, "Laundry Monitor ");
    lcd.print(0, 1, "     Ready      ");
  }
}

BLYNK_WRITE(VIRTUAL_ENABLE_PUSH)
{
  int enable = param.asInt();
  if (enable > 0)
  {
    pushEnabled = true;
    Serial.println("Push notification enabled");
  }
  else
  {
    pushEnabled = false;
    Serial.println("Push notification disabled");
  }
}

BLYNK_WRITE(VIRTUAL_SHAKE_THRESHOLD)
{
  int inputThreshold = param.asInt();

  shakeThreshold = constrain(inputThreshold, 1, 2048);

  Serial.println("Shake threshold set to: " + String(shakeThreshold));
}

BLYNK_WRITE(VIRTUAL_START_TIME)
{
  int inputStartTime = param.asInt();

  if (inputStartTime <= 0) inputStartTime = 1;
  shakeStartTimeHysteresis = inputStartTime;

  Serial.println("Shake start time set to: " + String(shakeStartTimeHysteresis) + " ms");  
}

BLYNK_WRITE(VIRTUAL_STOP_TIME)
{
  int inputStopTime = param.asInt();

  if (inputStopTime <= 0) inputStopTime = 1;
  shakeStopTimeHysteresis = inputStopTime;

  Serial.println("Shake stop time set to: " + String(shakeStopTimeHysteresis) + " seconds");
}

void printLaundryTime(void)
{
  unsigned long runTime = millis() - shakeStartTime;
  int runSeconds = (runTime / 1000) % 60;
  int runMinutes = ((runTime / 1000) / 60) % 60;
  int runHours = ((runTime / 1000) / 60 ) / 60;

  // Create a string like HHHH:MM:SS
  String runTimeString = "   " + String(runHours) + ":";
  if (runMinutes < 10) runTimeString += "0"; // Leading 0 minutes
  runTimeString += String(runMinutes) + ":";
  if (runSeconds < 10) runTimeString += "0"; // Leading 0 seconds
  runTimeString += String(runSeconds);

  // Fill out the rest of the string to 16 chars
  int lineLength = runTimeString.length();
  for (int i=lineLength; i<16; i++)
  {
    runTimeString += " ";
  }

  if (shakingState == PRE_SHAKING)
    lcd.print(0, 0, "Laundry starting");
  else if (shakingState == SHAKING)
    lcd.print(0, 0, "Laundry running ");
  else if (shakingState == NO_SHAKING)
    lcd.print(0, 0, "Laundry stopping");
  else if (shakingState == NO_SHAKING_LONG)
    lcd.print(0, 0, "Laundry done!   ");
  lcd.print(0, 1, runTimeString);
}

void shakeLoop(void)
{
  sensorShakeReturn sensorState = checkShake();
  if (sensorState == SENSOR_SHAKING) // If the sensor is shaking
  {
    switch (shakingState)
    {
    case NO_SHAKING_LONG:
    case NO_SHAKING: // If we haven't been shaking
      setLED(32, 0, 32); // LED purple
      shakingState = PRE_SHAKING; // Set mode to pre-shaking
      shakeStateChangeTime = millis(); // Log state change time
      if (loadTimer)
      {
        loadTimer = false;
        shakeStartTime = millis(); // Log time we started shaking
      }
      printLaundryTime();
      break;
    case PRE_SHAKING: // If we're pre-hysteresis shaking
      if (millis() - shakeStateChangeTime >= shakeStartTimeHysteresis)
      { // If we've passed hysteresis time
        shakingState = SHAKING; // Set mode to shaking
        digitalWrite(LED_PIN, HIGH); // Turn blue LED on
        Serial.println("Shaking!");
        notifyFlag = true; // Flag that we need to notify when shaking stops
        setLED(32, 0, 0); // LED red
      }
      break;
    case SHAKING: // If we're already shaking
      printLaundryTime(); // Update laundry timer
      break; // Do nothing
    case POST_SHAKING: // If we didn't stop shaking before hysteresis
      shakingState = SHAKING; // Go back to shaking
      break;
    }
  }
  else if (sensorState == SENSOR_NOT_SHAKING) // If the sensor is not shaking
  {
    switch (shakingState)
    {
    case NO_SHAKING_LONG: // If we haven't been shaking for a long time
      break; // Do nothing
    case NO_SHAKING: // If we haven't been shaking
      if (millis() - shakeStateChangeTime >= (shakeStopTimeHysteresis * 1000))
      { // Check if it's been a long time
        setLED(0, 32, 0); // Turn LED green
        shakingState = NO_SHAKING_LONG;
        if (notifyFlag == true) // If notify flag was set during shaking
        {
          loadTimer = true;
          printLaundryTime(); // Update LCD
          notifyFlag = false; // Clear notify flag
          if (pushEnabled) // If push is enabled
            Blynk.notify("Washer/dryer is done!"); // Notify!
        }
      }
      break;
    case PRE_SHAKING: // If we're pre-hysteresis shaking
      shakingState = NO_SHAKING; // Go back to no shaking
      setLED(0, 32, 0);
      break;
    case SHAKING: // If we're already shaking
      shakingState = POST_SHAKING; // Go to hysteresis cooldown
      shakeStateChangeTime = millis();
      break; // Do nothing
    case POST_SHAKING: // If we're in the shake cooldown state
      if (millis() - shakeStateChangeTime >= shakeStartTimeHysteresis)
      {
        digitalWrite(5, LOW); // LED off
        shakingState = NO_SHAKING;
        printLaundryTime();
        setLED(32, 16, 0);
        Serial.println("Stopped.");
      }
      break;      
    }
  }
}

sensorShakeReturn checkShake(void)
{
  static unsigned long lastShakeCheck = 0;
  float shake = 0;
  if (accel.available()) // If new accel data is available
  {
    int16_t x, y, z;

    accel.read(); // read the data in
    x = accel.x;
    y = accel.y;
    z = accel.z;

    // To determine if we're shaking, compare the sum of
    // x,y,z accels to the sum of the previous accels.
    shake = abs(x + y + z - lastX - lastY - lastZ);

    // Write the value to Blynk:
    Blynk.virtualWrite(VIRTUAL_SHAKE_VALUE, shake);

    // Update previous values:
    lastX = x;
    lastY = y;
    lastZ = z;
  }
  else // If sensore didn't have new data
  { // Return not ready
    return SENSOR_NOT_READY;
  }
  // If shake value exceeded threshold
  if (shake >= shakeThreshold)
    return SENSOR_SHAKING; // Return "shaking"
  else 
    return SENSOR_NOT_SHAKING; // Or return "not shaking"
}

void initAccel(void)
{
  // Use a slow update rate to throttle the shake sensor.
  // ODR_6 will set the accelerometer's update rate to 6Hz
  // Use +/-2g scale -- the lowest -- to get most sensitivity
  accel.init(SCALE_2G, ODR_6); // Initialize accelerometer

  while (!accel.available()) // Wait for data to be available
    yield(); // Let the system do other things
  accel.read(); // Read data from accel
  lastX = accel.x; // Initialize last values
  lastY = accel.y;
  lastZ = accel.z;
}

void setLED(uint8_t red, uint8_t green, uint8_t blue)
{
  rgb.setPixelColor(0, rgb.Color(red, green, blue));
  rgb.show();
}

Before uploading, however, you'll need to adjust a few strings.

Configure Your WiFi Network

Near the top of the sketch, you'll find a couple string constants named WiFiSSID and WiFiPSWD. Change the WiFiNetworkName and WiFiPassword values to match your WiFi network name (SSID) and password.

language:c
const char WiFiSSID[] = "WiFiNetworkName";
const char WiFiPSWD[] = "WiFiPassword";

Configure Your Blynk Token

Next, you'll need to configure the BlynkAuth string to match your Blynk auth token. You can find this in the settings section of your Blynk project -- click the hexagon-shaped "nut" icon in the upper-right.

Use the E-Mail feature to send yourself a copy of the auth token, which you can copy and paste into the code:

language:c
const char BlynkAuth[] = "0a1b2c3d4e5f..."

Upload the Code

Finally, upload the code -- making sure your serial port is correctly selected and the board type is set to SparkFun Blynk Board.

Select the Blynk Board board