Michael Wilson
Published © GPL3+

Smart Sump Pump Monitor

Sump pump monitor has live dashboard for usage statistics, and data logging. Email notifications through Particle Cloud or IFTTT.

IntermediateFull instructions provided8 hours4,501
Smart Sump Pump Monitor

Things used in this project

Hardware components

Sparkfun Photon Redboard
Using this version of Photon because of Arduino form factor and fits in Arduino cases! (You can use any particle photon form factor)
×1
Ultrasonic Sensor - HC-SR04 (Generic)
Ultrasonic Sensor - HC-SR04 (Generic)
Suggested link (need 1 sensor), but cheaper to buy a 3 or 5 pack from your favorite online source.
×1
Arduino Compatible "Mega" Large Enclosure
Suggested Link (need Mega compatible case to fit both Photon and Sensor). This one is easy to modify!
×1
Female to Male Jumper Connectors
Suggested Link (need any female connectors to make jumpers).
×1
5V Power
Suggested Link
×1

Software apps and online services

Ubidots
Ubidots
Maker service
IFTTT Maker service

Hand tools and fabrication machines

Tool to modify plastic enclosure
Use metal filer or 3/8'' drill bit

Story

Read more

Schematics

Sump Monitor V2

Schematic changes Pinout to trigger D2, echo D6

Code

SumpMonitorV2

C/C++
Copy and Paste into Particle.IO IDE
// This #include statement was automatically added by the Particle IDE.
#include <HttpClient.h>
#include "application.h"

//#define TOKEN "paste_ubidots_token_here"

// GET TOKEN ID AND VARIABLE ID FROM YOUR UBIDOTS ACCOUNT

//String SUMP_WATER_LEVEL   = "paste_ubidots_variable_here";  // Sump Water Level Variable
//String SUMP_FREQUENCY     = "paste_ubidots_variable_here";  // Sump Frequency of discharge in hours
//String SUMP_CYCLES        = "paste_ubidots_variable_here";  // Sump Cycles
//String SUMP_GALLONS       = "paste_ubidots_variable_here";  // Sump Gallons Discharged
//String SUMP_HEARTBEAT     = "paste_ubidots_variable_here";  // Sump Monitor Status
//String ADC_POSITION       = "paste_ubidots_variable_here";  // Analog Position if you have one


// HTTP Client Variables
String resultstrdata;
char resultstr[64];    // used to buffer sprintf(x) function
HttpClient http;

http_header_t headers[] = {
    { "Content-Type", "application/json" },
    { "X-Auth-Token" , TOKEN },
    { NULL, NULL } // NOTE: Always terminate headers will NULL
};

http_request_t request;
http_response_t response;

/*
 ******************************************************************************
 *  Copyright (c) 2015 Particle Industries, Inc.  All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation, either
 * version 3 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
 *
 * -----------------------------------
 * HC-SR04 Ping / Range finder wiring:
 * -----------------------------------
 * Particle - HC-SR04
 *      GND - GND
 *      VIN - VCC
 *       D2 - TRIG
 *       D6 - ECHO
 */

//int buffer[4]; // buffer needed for sprintf() function

unsigned int data_samples, temp_val;       // used for buffering sonic sensor and getting average of 60 samples
unsigned int analogvalue, adc_voltage;     // if using position sensor then buffering not needed

unsigned int heartbeat_counter;            // used to count every sample sent to ubidots
unsigned int dischargefreq_counter;        // used to count time between cycles
unsigned int cycle_counter;                // used to count number of cycles
unsigned int cycle_latch;                  // used to flag a complete discharge
unsigned int current_value;                // water level taken
unsigned int last_hour_value;              // water level taken last hour

unsigned int seconds_counter, minutes_counter, hours_counter; // used to count time for main loop
unsigned int inches, centimeters, seconds, minutes;
unsigned int flag_1sec, flag_1min, flag_60min;

#define SERIAL_BAUDRATE 115200     // 115200 kbit/s
#define HEARTBEAT_COUNT_LIMIT 15   // 15 min
#define MONITOR_TIMEOUT_LIMIT 1439 // 1439 min (24 hours)
#define SUMP_BASIN_VOLUME 14       // 14 gal. per cycle (based on volume of water level)
#define SUMP_BASIN_HEIGHT 54       // 54 cm
#define LATCH_LOWER_LIMIT 12       // 12 cm
#define LATCH_UPPER_LIMIT 30       // 30 cm

Timer Time_millisecs( 1000, CountSeconds); // 1 second timer interrupt to run the main loop

void setup() {
    init_system();             // initialize variables and pin IO
    Time_millisecs.start();    // start timer
}

void loop() {
    
    if ( flag_1sec == TRUE ) { // 1 second loop
        flag_1sec = FALSE;
        get_signals();
    }
    
    if ( flag_1min == TRUE ) { // 1 minute loop
        flag_1min = FALSE;
        SoftwareInterrupt_1min();
        calc_sump_stats();
    }
    
    if ( flag_60min == TRUE ) { // 1 hour loop
        flag_60min = FALSE;
        SoftwareInterrupt_60min();
        check_sensor_operation();
    }
}

void CountSeconds() {
    
    seconds_counter++;
    flag_1sec = TRUE;
    
    if ( seconds_counter > 59 ) {
        seconds_counter=0;
        flag_1min = TRUE;
        minutes_counter++;
        dischargefreq_counter++;
        
        if ( minutes_counter > 59 ) {
            minutes_counter = 0;
            flag_60min = TRUE;
            hours_counter++;
        }
    }
    
    return;
}

void SoftwareInterrupt_1min(void) {
    
    centimeters = data_samples/60; // simple average filter to deal with noisy sonic sensor
    data_samples = 0;              // get 60 samples and average them, then clear buffer
    //get_ADC();                   // this is a position sensor, no filtering required, just read ADC
    // leave commented if not using position sensor
    send_to_ubidots();
    
    return;
}

void SoftwareInterrupt_60min(void) {
    
    last_hour_value = centimeters;
    //sprintf(resultstr, " %d", last_hour_value);
    //resultstrdata = resultstr;
    //Particle.publish("Sump Monitor", "Latest Hour Water Level was" + resultstrdata + " cm", 60, PRIVATE);
    
    return;
}

void get_signals(void) {
    
    temp_val = ping(D2, D6, 10, false);    // get sonic sensor sample
    
    if ( temp_val > SUMP_BASIN_HEIGHT ) temp_val = SUMP_BASIN_HEIGHT;  // use max limit
    
    data_samples += SUMP_BASIN_HEIGHT-temp_val;    // subtract sensor reading to calc actual water level
    
    return;
}

void calc_sump_stats(void) {
    
    if ( centimeters > LATCH_LOWER_LIMIT ) {
        
        if ( centimeters > LATCH_UPPER_LIMIT ) cycle_latch = TRUE;
        return;
    }
    
    else {
        if (cycle_latch) {
            cycle_counter++;
            
            sprintf(resultstr, "{\"value\":%d}", dischargefreq_counter );
            send_http_post(SUMP_FREQUENCY, resultstr); //USE THIS TO SEND DISCHARGE TIME
            
            sprintf(resultstr, "{\"value\":%d}", cycle_counter);
            send_http_post(SUMP_CYCLES, resultstr); //USE THIS TO SEND CYCLES
            
            sprintf(resultstr, "{\"value\":%d}", cycle_counter*SUMP_BASIN_VOLUME);
            send_http_post(SUMP_GALLONS, resultstr); //USE THIS TO SEND GALLONS
            
            //Particle.publish("Sump Monitor", "Pump has discharged", 60, PRIVATE);
            
            dischargefreq_counter = 0;
            cycle_latch = FALSE;
            return;
        }
        
        return;
    }
}

void check_sensor_operation(void) {
    
    if ( (centimeters == 60) && (last_hour_value == 60) ) {
        
        // compare sensor reading to check if sonic sensor is stuck or faulted
        sprintf(resultstr, "{\"value\":%d}", 0);
        send_http_post(SUMP_WATER_LEVEL, resultstr);
        Particle.publish("Sump Monitor", "System Reset Required", 60, PRIVATE);
        System.reset();
    }
    
    if ( minutes_counter > MONITOR_TIMEOUT_LIMIT ) reset_ubidots_stats();
    
    return;
}

void send_http_post(String VARIABLE_ID, char* result_str) {
    request.port = 80;
    request.hostname = "things.ubidots.com";
    request.path = "/api/v1.6/variables/" + VARIABLE_ID + "/values";
    request.body = result_str;//Sending presence to Ubidots
    http.post(request, response, headers);
}

void send_to_ubidots(void) {
    //sprintf(resultstr, "{\"value\":%d}", adc_voltage);  // format water level to string
    //send_http_post(ADC_POSITION, resultstr);            // send string result to ubidots
    
    sprintf(resultstr, "{\"value\":%d}", centimeters);  // format water level to string
    send_http_post(SUMP_WATER_LEVEL, resultstr);        // send string result to ubidots
    
    if( heartbeat_counter++ > HEARTBEAT_COUNT_LIMIT ) heartbeat_counter = 0;
    
    sprintf(resultstr, "{\"value\":%d}", heartbeat_counter );
    resultstrdata = resultstr;
    send_http_post(SUMP_HEARTBEAT, resultstr);             // send string result to ubidots
}

void init_system(void) {
    
    Serial.begin(SERIAL_BAUDRATE);
    pinMode(A5, INPUT);
    
    analogvalue = 0;
    adc_voltage = 0;
    
    flag_1sec = seconds    = 0;
    flag_1min = minutes    = 0;
    flag_60min             = 0;
    seconds_counter        = 0;
    minutes_counter        = 0;
    hours_counter          = 0;
    heartbeat_counter      = 0;
    dischargefreq_counter  = 0;
    last_hour_value        = 0;
    cycle_latch            = 0;
    cycle_counter          = 0;
    centimeters            = 0;
    temp_val               = 0;
    
    Particle.publish("Sump Monitor V2", "Online", 60, PRIVATE);
    reset_ubidots_stats();
}

void reset_ubidots_stats(void) {
    
    cycle_counter = 0;
    dischargefreq_counter = 0;
    hours_counter = 0;             // don't clear this timer
    
    sprintf(resultstr, "{\"value\":%d}", 0);
    send_http_post(SUMP_CYCLES, resultstr);
    send_http_post(SUMP_HEARTBEAT, resultstr);
    send_http_post(SUMP_FREQUENCY, resultstr);
    send_http_post(SUMP_WATER_LEVEL, resultstr);
    send_http_post(SUMP_GALLONS, resultstr);
    
    //Particle.publish("Sump Monitor", "No Activity for 24 Hours", 60, PRIVATE);
    
    return;
}

void get_ADC(void) {
    
    analogvalue = analogRead(A5);
    adc_voltage = (analogvalue*330)/4095;
    
    return;
    
}

unsigned int ping(pin_t trig_pin, pin_t echo_pin, uint32_t wait, bool info) {
    uint32_t duration, inches, cm;
    static bool init = false;
    if (!init) {
        pinMode(trig_pin, OUTPUT);
        digitalWriteFast(trig_pin, LOW);
        pinMode(echo_pin, INPUT);
        delay(50);
        init = true;
    }
    
    /* Trigger the sensor by sending a HIGH pulse of 10 or more microseconds */
    digitalWriteFast(trig_pin, HIGH);
    delayMicroseconds(10);
    digitalWriteFast(trig_pin, LOW);
    
    duration = pulseIn(echo_pin, HIGH);
    
    /* Convert the time into a distance */
    // Sound travels at 1130 ft/s (73.746 us/inch)
    // or 340 m/s (29 us/cm), out and back so divide by 2
    // Ref: http://www.parallax.com/dl/docs/prod/acc/28015-PING-v1.3.pdf
    inches = duration / 74 / 2;
    cm = duration / 29 / 2;
    
    if (info) { /* Visual Output */
        Serial.printf("%2d:", inches);
        for(int x=0;x<inches;x++) Serial.print("#");
        Serial.println();
    } else { /* Informational Output */
        Serial.printlnf("%6d in / %6d cm / %6d us", inches, cm, duration);
    }
    delay(wait); // slow down the output
    return cm;
}

Credits

Michael Wilson

Michael Wilson

1 project • 2 followers
Hobby electronics enthusiast

Comments