Sump pump water level monitor: ultrasonic sensor HC-SR04, wifi module ESP8266, and Gmail sender

A few months ago, my sump pump float switch failed. It was second time since we moved in this house a few years ago. There are a few different types of float switches in the store and mine was one of those popular ones: a pig-tailed plastic float with a metal ball inside, like the one in below picture.


I asked how long they can guarantee this type of float switch to a salesclerk at the store when I bought a new one, and he said it’s only 1 year. It has very weak point where the electric wire bends as the float moving up and down so many times. So 1 year guarantee seems to be understandable. But it’s not cheap for its simple and weak design.

I tried to find a better switch, but unfortunately, there was no other option on that day. It was in February and rained a lot for unusual warm weather this year. At that time, the sump pump runs almost every 10-20 minutes, so I couldn’t look around the stores to find a better switch and had to buy whatever available and replace it immediately, which I did.

But I started worrying about its failure when nobody’s at home and doing some research on the backup plans. There are existing products and ideas you can find through internet search including a second pump with a second switch and backup power supply like a emergency generator or a UPS battery. There are also a venturi pump using city water line which doesn’t need electricity for the pump. I think this venturi pump might be a good solution and maybe I will investigate this option further later.

Anyway, since I replaced the float switch, I knew it would work at least a year. But I needed to have some backup system and monitoring system.

For a backup system, I assumed the pump itself can work much longer than the switch which is true, and decided to add another float switch with different type that has a floater with a rod that triggers the switch (see below picture for example). A good thing about this switch is the electric wire is not the part of the mechanical movement, so its lifetime is much longer than the ball float type.


I have arranged this second backup switch a little higher than the ball float so that it can trigger the pump when the ball float switch fails.


This prevents overflow by the ball float switch failure, but that’s not enough. I need to know when it happens. So I decided to put a sensor to monitor the water level and send me notification by either emails or text messages to my cell phone. There are many ways to sense the water level, using like resistance or inductance based immersed electrodes, a series of mechanical float switches, optical sensors, or ultrasonic sensor.

I had heard about the ultrasonic sensor, HC-SR04 with Arduino before, but had never bought or used it. One attractive thing about this option is that it’s a non-contact measurement which means there is no mechanical moving parts involved so it has relatively longer lifetime. My younger son, who used to be a programmer for the FIRST robotics robots and is now studying Mechatronics in university, had experience with an ultrasonic sensors to measure the distance from the robot body to an object, and he recommended to use it for water level monitoring. I wasn’t sure about the accuracy, but my son said it was quite accurate for cm ranges. So I ordered one of those HC-SR04 and tried with my Jeonlab mini and it worked perfectly. I don’t need mm scale but cm scale is just good enough for this type of measurements.

Now, I needed a solution to drive the sensor and interface to send the messages to my email or phone. ESP8266-01 seemed to me a good choice because it has even more memory (not that I need bigger memory for this project) than Jeonlab mini (ATmega328p based Arduino variant) and two GPIO pins as well as wifi interface. The sensor has two pins, trigger and echo. So, that’s perfect combination.

The entire circuit diagram including voltage regulator is as below.


One thing you need to consider with this combination is the voltages for those two devices. The HC-SR04 need 5V while the ESP8266 needs 3.3V. So the trigger line is fine from ESP8266 because 3.3V is high enough to trigger HIGH on HC-SR04, but the echo signal from the HC-SR04 is 5V and need to be converted to 3.3V using either voltage divider or a transistor. In my case, I used a NPN transistor because the GPIO0 needs to be pulled up for normal booting anyway.

Here is some pictures of assembled sump pump water level monitoring system.

Now the programming part.  I searched and found this link and followed his instructable and it worked perfectly with Gmail. He explained well how to set up the wifi account in his instructable and all the core program is his work, so I put only the setup() and loop() part and distance conversion function for the HC-SR04 below.

void setup()
int cm;
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);
connection_state = WiFiConnect();
if (!connection_state) // if not connected to WIFI
Awaits(); // constantly trying to connect

Gsender *gsender = Gsender::Instance(); // Getting pointer to class instance
String subject;
subject = "Sump pump monitor started";if (gsender->Subject(subject)->Send("", "")) {
Serial.println("Message send.");
} else {
Serial.print("Error sending message: ");

do {
cm = measure_cm();
if (count == 0) {
level_high = cm;
level_low = cm;
else {
if (cm < level_high) level_high = cm;
if (cm > level_low) level_low = cm;

} while (count < 40);

subject = "High: " + String(level_high) + " Low: " + String(level_low);

if (gsender->Subject(subject)->Send("", "")) {
Serial.println("Message send.");
} else {
Serial.print("Error sending message: ");
count = 0;

void loop() {
int cm = measure_cm();
Gsender *gsender = Gsender::Instance();
String subject, interval;

if (cm < level_high - 5) {
subject = "Sump water level HIGH: " + String(cm);
if (gsender->Subject(subject)->Send("your_at&t", "")) {
Serial.println("Message send.");
} else {
Serial.print("Error sending message: ");
if (cm - previous_cm > 10 && previous_cm != 0) {
subject = "Sump pump ON: " + String(previous_cm) + " to " + String(cm);
interval = String((millis() - mil_sec) / milsec_to_min) + " minutes";
mil_sec = millis();
if (gsender->Subject(subject)->Send("", interval)) {
Serial.println("Message send.");
} else {
Serial.print("Error sending message: ");
count = 0;
if (count > 29) {
subject = "Current water level: " + String(cm);
if (gsender->Subject(subject)->Send("", "")) {
Serial.println("Message send.");
} else {
Serial.print("Error sending message: ");
count = 0;
previous_cm = cm;

int measure_cm() {
long duration;
int cm, cm_sum = 0, cm_min = 0, cm_max = 0, count = 0;

do {
// trigger 10us pulse
digitalWrite(trigPin, 0);
digitalWrite(trigPin, 1);
digitalWrite(trigPin, 0);

duration = pulseIn(echoPin, 0);

// convert the time into a distance
cm = duration / 58;
if (count == 0) {
cm_min = cm;
cm_max = cm;
else {
if (cm < cm_min) cm_min = cm;
if (cm > cm_max) cm_max = cm;
cm_sum = cm_sum + cm;

} while (count < 10);

cm_sum = cm_sum - cm_min - cm_max;
cm = cm_sum / 8;

return cm;

It keeps monitoring water level and when the pump runs (sudden change in level), it sends an email to me. If it doesn’t run after 30 minutes, it also send me an email with current level to ensure the monitoring system is working and the power is on. If the water level is higher than a threshold, it sends a text message to my phone so that it notify me there is a either pump or switch issue.

Posted in Electronics

Binary golf shot counter

I have never liked to either play or watch any sports. A few years ago, there was a golf outing at work when I have ever tried golf for the first time. Golfing became one of my favorite things to do since. Well, I’m still playing over 100 and my non-athletic body does not cooperate very well. But I have no plan to take any class or get a coach for better play. I just enjoy to think about how to improve my swing, angle, balance, and steadiness. I have no intention or any possibility to become a professional golfer. All I want is to go out on a nice and sunny day with my wife and enjoy the time.

So, as you can imagine, I often forget how many shots I played during the game. If you are really good and can play no more than bogey, you may be easy to count how many shots you played. But that’s not the case for me. Many times, I asked myself, “was that the third or fourth?”

I have searched for golf shot counter, and for sure, found a lot of different type of counters, from beads on a string, plastic dial, to a fancy digital one with LCD displays. But none of them attracts me and thought I would design one with simple portable electronics.

At first, I thought about using one of those ATtiny chips with a small LCD. But all I need is a button switch to add 1 to the total number and show the number when I want to see it. So the ATtiny chip with the LCD sounded like overkill. And then I thought about binary display with a few LEDs. If I have 4 LEDs, it can show up to 15 which is more than I need (I’m not THAT bad). 3 LEDs might be too small because it can count only up to 7. Considering when I play triple bogey or worse for par 5 hole, I will need at least 8 or more. So, 4 LEDs it is.

Now, how to make a binary counting circuit was the question. I immediately thought about a flip-flop circuit which is a basic calculator and memory element. If I have 4 of those and connect the output to the next one, that can make a binary counter. Wait a minute… there is a binary counter chips already. I don’t need to make one myself. So I ordered a part from Digi-key and that part is SN74HC393. This one has dual 4 digit binary counters, but I would use only one of them.

As soon as I got the part, I connected switches and LEDs and checked if it works fine and found I need a debouncer for the SHOT button switch. That was fixed easily with a Schmitt trigger with a resistor and a capacitor.

Below schematic is the final circuit diagram.


I ordered the PCB and will post the result as soon as it’s ready to show.


Posted in Electronics, Gadgets

Updated conky.conf

I have updated my conky as shown below.


The conky.conf looks like this.

-- vim: ts=4 sw=4 noet ai cindent syntax=lua
Conky, a system monitor, based on torsmo

Any original torsmo code is licensed under the BSD license

All code written since the fork of torsmo is licensed under the GPL

Please see COPYING for details

Copyright (c) 2004, Hannu Saransaari and Lauri Hakkarainen
Copyright (c) 2005-2012 Brenden Matthews, Philip Kovacs, et. al. (see AUTHORS)
All rights reserved.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program.  If not, see <>.

conky.config = {
    alignment = 'top_right',
    background = false,
    border_width = 1,
    cpu_avg_samples = 2,
    default_color = 'white',
    default_outline_color = 'white',
    default_shade_color = 'white',
    draw_borders = false,
    draw_graph_borders = true,
    draw_outline = false,
    draw_shades = false,
    use_xft = true,
    font = 'Monospace:size=10',
    gap_x = 5,
    gap_y = 5,
    minimum_height = 5,
    minimum_width = 5,
    net_avg_samples = 2,
    no_buffers = true,
    out_to_console = false,
    out_to_stderr = false,
    extra_newline = false,
    own_window = true,
    own_window_class = 'Conky',
    own_window_type = 'desktop',
    own_window_transparent = true,
    stippled_borders = 0,
    update_interval = 1.0,
    uppercase = false,
    use_spacer = 'none',
    show_graph_scale = false,
    show_graph_range = false,
    double_buffer = true

conky.text = [[
#${scroll 24 $nodename - $sysname $kernel on $machine | }
${color cyan}Date and time: $color $time
${color cyan}Uptime:$color $uptime
${color grey}Frequency (in MHz):$color $freq
#${color grey}Frequency (in GHz):$color $freq_g
${color grey}CPU Usage:$color $cpu% ${cpubar 4}
${color grey}RAM Usage:$color $mem/$memmax - $memperc% ${membar 4}
${color cyan}Name              PID   CPU%   MEM%
${color lightgrey} ${top name 1} ${top pid 1} ${top cpu 1} ${top mem 1}
${color lightgrey} ${top name 2} ${top pid 2} ${top cpu 2} ${top mem 2}
${color lightgrey} ${top name 3} ${top pid 3} ${top cpu 3} ${top mem 3}
${color lightgrey} ${top name 4} ${top pid 4} ${top cpu 4} ${top mem 4}
#${color grey}Swap Usage:$color $swap/$swapmax - $swapperc% ${swapbar 4}
${color grey}Processes:$color $processes  ${color grey}Running:$color $running_processes
${color orange}Temperature:$alignr${acpitemp} degC
${color green}Battery status:$alignr $battery$color
${color cyan}File systems:$color
 / ${fs_used /}/${fs_size /} ${fs_bar 6 /}
 /mnt/DATA ${fs_used /mnt/DATA}/${fs_size /mnt/DATA} ${fs_bar 6 /mnt/DATA}
${color cyan}Wireless Networking:
${color red}Up:$color ${upspeed wlp2s0} 
${upspeedgraph wlp2s0 18,300 000000 ff0000}
${color yellow}Down:$color ${downspeed wlp2s0}
${downspeedgraph wlp2s0 18,300 000000 00ffff}



Posted in Electronics

2014 in review

The stats helper monkeys prepared a 2014 annual report for this blog.

Here’s an excerpt:

The concert hall at the Sydney Opera House holds 2,700 people. This blog was viewed about 13,000 times in 2014. If it were a concert at Sydney Opera House, it would take about 5 sold-out performances for that many people to see it.

Click here to see the complete report.

Posted in Electronics

my Conky configuration on Linux Mint


conky.conf in /etc/conky

use_xft yes
xftfont 123:size=8
xftalpha 0.1
update_interval 1
total_run_times 0

own_window yes
own_window_type normal
own_window_transparent yes
own_window_hints undecorated,below,sticky,skip_taskbar,skip_pager
own_window_colour 000000
own_window_argb_visual yes
own_window_argb_value 0

double_buffer yes
draw_shades no
draw_outline no
draw_borders no
draw_graph_borders no
default_color white
default_shade_color red
default_outline_color green
alignment top_right
gap_x 0
gap_y 10
no_buffers yes
uppercase no
cpu_avg_samples 2
net_avg_samples 1
override_utf8_locale yes
use_spacer yes
minimum_size 0 0
maximum_width 130

#${color cyan}${time %a, } ${color }${time %e %B %G}
${font DejaVu Sans Mono:size=8}${execpi 60 DJS=`date +%_d`; ncal -C -h | sed s/”\(^\|[^0-9]\)$DJS”‘\b’/’\1${color cyan}'”$DJS”‘$color’/}${font}
${color cyan}${time %Z,    }${color }${time %H:%M:%S}
${color cyan}UpTime: ${color }$uptime
#${color cyan}Kern:${color }$kernel
${color cyan}CPU:${color } $cpu%         ${color red} ${acpitemp}C${color }
${cpugraph 20,130 000000 ffff00}
#${color cyan}Load: ${color }$loadavg
${color cyan}Processes: ${color }$processes
${color cyan}Running:   ${color }$running_processes

${color cyan}Highest CPU:
${color #ddaa00} ${top name 1}${top_mem cpu 1}
${color lightgrey} ${top name 2}${top cpu 2}
${color lightgrey} ${top name 3}${top cpu 3}
${color lightgrey} ${top name 4}${top cpu 4}

${color cyan}Highest MEM:
${color #ddaa00} ${top_mem name 1}${top_mem mem 1}
${color lightgrey} ${top_mem name 2}${top_mem mem 2}
${color lightgrey} ${top_mem name 3}${top_mem mem 3}
${color lightgrey} ${top_mem name 4}${top_mem mem 4}

${color cyan}MEM:  ${color } $memperc%
${membar 3,100}
${color cyan}SWAP: ${color }$swapperc%
${swapbar 3,100}

${color cyan}ROOT:    ${color }${fs_free /}/${fs_size /}
${fs_bar 3,100 /}
${color cyan}HOME:  ${color }${fs_free /home}/${fs_size /home}
${fs_bar 3,100 /home}
${color cyan}NET:
${color}Up: ${color }${upspeed wlan0} k/s
${upspeedgraph wlan0 20,130 000000 ff0000}
${color}Down: ${color }${downspeed wlan0}k/s${color}
${downspeedgraph wlan0 20,130 000000 00ffff}

#install acpi first to get Battery info: sudo apt-get install acpi
${color green}Battery:${color }
capacity:$alignr${execi 60 acpi | grep -Eo ‘[0-9]+%’}
status:$alignr${execi 60 acpi | grep -Eo ‘\w+,’ | grep -Eo ‘\w+’}
remaining:$alignr${execi 60 acpi | grep -Eo ‘(:?[0-9]+){3}’}

Tagged with:
Posted in Electronics

GPS distance measurement between two coordinates using Arduino

I knew the dimension of the LCD (Nokia 5110) as 43mm x 43mm but it looked smaller than I thought.  That’s good because I want to put it on top of the GPS (Holux M-1000).

In my previous post, I explained how to get coordinates, date & time, speed, and bearing data from GPS, Holux M-1000.  Now that I have the LCD, it’s time to add two parts to the Arduino sketch: 1) LCD driver/display, 2) distance calculation between two locations.

1. LCD Driver

First of all, I searched for a simple and small library for the LCD, Nokia 5110. There were a few different libraries for this LCD: Adafruit’s, Sparkfun’s, and Henning Karlsen’s.  Among these libraries, I chose Henning Karlsen’s because I needed only simple text display with a couple of different font sizes.  Henning Karlsen has separate library for graphics as well.  I would like to thank Henning for his sharing his nice work on the library.  Henning’s library supports 3 different font sizes: SmallFont (text and number, 6×8), MediumNumber (number only, 12×16), and BigNumber (number only, 14x 24).  Only downside of this library is that the Medium and Big fonts do not support texts but only numbers. However I would need only numbers to display with bigger fonts, this limitation was no problem with me.

2. Distance calculation between two locations

There are number of websites showing how to calculate distance between two locations from latitudes and longitudes. Movable Type Scripts shows various  calculations of distance, bearing and other useful conversions using Haversine formula and BlueMM posted the Excel formula to calculate distance which is basically the same way as Haversine.

The calculation is quite straightforward but I found there was a problem: Arduino (Atmega328p) cannot handle over 6-7th decimal digits which is very important in trigonometric calculation for short distance.

Arduino reference page says “Floats have only 6-7 decimal digits of precision. That means the total number of digits, not the number to the right of the decimal point. Unlike other platforms, where you can get more precision by using a double (e.g. up to 15 digits), on the Arduino, double is the same size as float.”

Let me give you an example.  Suppose we started from a position A (lat: 40.00, long: 80) to a position B (lat: 40.01, long: 80.00). That is, we moved 0.01 degrees in latitude only. If you calculate the distance using Haversine formula on your PC, you will get about 1,111.9m. However, Arduino calculates it as 3,110.8m. Big error!  More interesting thing is that even if you reduce the latitude difference to 0.001 or 0.0001 degrees, you get the same results, 3,110.8m. So I investigate further what exactly cause this error. Of course I know the culprit is the float precision limitation as said above. But I wanted to know which part of the calculation by Arduino cause this big error. In the Haversine formula, there are COS, SIN and ACOS functions used.  I tested a few different calculations using these functions and found the calculation of COS and SIN functions affect minimal but the problem was the ACOS.  If you calculate the formula on your PC only inside of ACOS bracket, you will get 0.9999999848. See my point? The decimal places below 6th in ACOS function is actually important to calculate the angular difference for small distance, but unfortunately Arduino cannot handle this.  Not only for small distance but for even relatively long distance (say over 1 degree for instance) there is error between the results on the PC and Arduino.

Well, so I started thinking about how to avoid trigonometric function calculation when over 6th decimal places are important. And I found a solution! Instead of calculating angular difference between two positions and THEN calculating the distance by multiplying the mean earth radius, calculating a ratio of angle between two positions (latitude and longitude separately) over 360 degrees and divide the circumference of the earth by this ratio. In other words, keep the numbers big while calculation. Arduino’s float type has a limitation on the small decimal places, but can handle relatively big numbers!

Here is my formula:

The mean circumference of the earth is 2 x 6,371,000m x π = 40,030,170m

Δd (lat) = 40,030,170 x ΔΘ (lat) / 360 (assuming ΔΘ is small)

Δd(long) = 40,030,170 x ΔΘ(long) x cosΘm / 360 (Θm: mean latitude between two positions)

Now, the distance is √[Δd (lat)^2 + Δd (long)^2]

Below is the test Arduino sketch to test my formula.  The result is 11.029m while Haversine formula for the same coordinates gives 11.119m.  This is close enough considering the accuracy of the most GPS is bigger than one meter.

float gpsLat0 = 40.0;
float gpsLat = 40.0001;
float gpsLong0 = 80.0;
float gpsLong = 80.0;

void setup()
float delLat = abs(gpsLat0-gpsLat)*111194.9;
float delLong = 111194.9*abs(gpsLong0-gpsLong)*cos(radians((gpsLat0+gpsLat)/2));
float distance = sqrt(pow(delLat,2)+pow(delLong,2));

void loop()

To be continued….

Posted in Arduino, Electronics, Gadgets, JeonLab mini, Modification

GPS (M-1000) with LCD (Nokia 5110)

Finally, I got the Nokia 5110 LCD that was ordered on eBay a few weeks ago. It took me a couple of days to find the best library for the LCD and quickly updated my Arduino program to display current location (latitude and longitude), date/time, speed, and bearing. There is 2.8V regulated power in the GPS that powers the JeonLab mini and LCD. I will upload my sketch and full detail later.

 photo GPSwithLCD5110_currentlocation.jpg

Posted in Arduino, Electronics, Gadgets, JeonLab mini
June 2017
« Jan    

Enter your email address to subscribe to this blog and receive notifications of new posts by email.

Join 10 other followers