I had a need to monitor the depth of water in my 2600 gallon water tank that supplies my home and outside irrigation needs. I initially tried the “easy” solution, using an HC-SR04 ultrasonic sensor. This worked well for a day or so before condensation on the sensor caused it to report bad values. @bko suggested heating the sensor above the due point (see discussion of water tank monitoring here), but for several reasons, I decided not to go that route. For one, I thought it wold take a lot of testing under different temperature conditions (summer and winter) to determine if the solution was robust. Another more serious concern was that my tank has a rather vigorous ozone bubbler in it which causes a lot of aerosol formation. Given the high mineral content of my well water, I thought there would be a significant chance that the sensor would become coated with mineral deposits, rendering it useless.
So, I decided to use multiple hall effect switches that would be activated by a magnet on a float. All the electronic components would be inside a PVC pipe, with a donut shaped float riding up and down on this pipe; none of the sensors would be exposed to the internal atmosphere of the tank this way. I think this will be a more reliable way to measure the water level in my particular case, with the trade-off being discrete data points, rather than continuous ones, and a considerably more complicated hardware setup.
I used 48 hall effect switches (Allegro MicroSystems A3212) that were attached to a flat(ish) PVC garage door weather seal. After cutting the seal to about 1-1/2 inches wide, it was flat on one side, and had two grooves on the other that I could use to confine bare copper wires used for the ground and power connections to the switches.
Front and back of the PVC strip holding the Hall Effect switches. The first (highest in the tank) 25 sensors are 3/4” apart, the next 15 are 1” apart, and the remaining 8 are 2” apart. The sensors were secured to the PVC with a drop of cyanoacrylate glue.
After soldering all the sensors to the ground and power buses, and all the data pins to thermostat wire (8 conductors per cable), the strip was put into a 2” PVC pipe with styrofoam pieces on the wired face to keep the sensor side pressed against the inside wall of the pipe. I ripped a 1/2” piece of PVC pipe, taking off about a quarter of it to create a “U” shaped piece that I glued to the 2” pipe on the side opposite of the sensors. This would act as a key to keep the float from rotating as it moved up and down on the pipe.
The electronic part of the project was simpler than the mechanical part. The circuit board only contains a Photon and 3 CD4067 16 channel CMOS multiplexers. The outputs of the multiplexers were tied together and tied to one I/O port on the Photon. The code cycles through the chips (through their inhibit lines) and the addresses to read each sensor in turn. The code keeps track of which sensors are currently activated (it can be one or two), and averages the values if two are activated concurrently. Those values are sent to Ubidots for storage and graphing. This allows me to monitor the amount of water I use each day, and alerts me (via an SMS) if the tank fails to fill overnight; my well pump has a bad habit of losing its prime as soon as I go on vacation. The project has been running for over a month now with no problems.
Here is the Photon’s code,
// This #include statement was automatically added by the Particle IDE.
#include "HttpClient/HttpClient.h"
int dataPin = D2; // data pin to read the A3212 Hall Effect Switches (connected to the common in/out pin on the CD4067)
int chipInh0 = A2; // chip inhibit pins for the 3 CD4067 1x16 multiplexers. The chip is selected when the chip inhibit pin is LOW (inhibited when HIGH)
int chipInh1 = A3;
int chipInh2 = A4;
int onesBit = D1; // address pin A on a CD4067
int twosBit = D0; // address pin B
int foursBit = A1; // address pin C
int eightsBit = A0; // address pin D
unsigned long startTime;
float lastLevel = 100; // will be LOW for the selected sensor in the presense of a magnet
int counter;
float level;
bool shouldSend6AMData = true;
// The first 25 Hall effect switches are 3/4" apart. The next 15 are 1" apart. The last 8 (lowest down in the tank) are 2" apart
// There are two places in the array below where the values are out of order (11&12 and 27&28) -- this is intentional to correct for wiring mistakes
float inchesDown[48] = {0.032, 0.75, 1.5, 2.25, 3, 3.75, 4.5, 5.25, 6, 6.75, 7.5, 9, 8.25, 9.75, 10.5, 11.25, 12, 12.75, 13.5, 14.25, 15, 15.75, 16.5, 17.25, 18, 19, 20, 22, 21, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 35, 37, 39, 41, 43, 45, 47, 49};
http_request_t request;
http_response_t response;
HttpClient http;
http_header_t headers[] = {
{ "Content-Type", "application/json" },
{ NULL, NULL }
};
STARTUP(WiFi.selectAntenna(ANT_EXTERNAL));
SYSTEM_THREAD(ENABLED);
void setup() {
Time.zone(-7);
request.hostname = "things.ubidots.com";
request.port = 80;
pinMode(dataPin, INPUT_PULLUP);
pinMode(onesBit, OUTPUT);
pinMode(twosBit, OUTPUT);
pinMode(foursBit, OUTPUT);
pinMode(eightsBit, OUTPUT);
pinMode(chipInh0,OUTPUT);
pinMode(chipInh1,OUTPUT);
pinMode(chipInh2,OUTPUT);
startTime = millis();
Particle.function("getRSSI", getRSSI);
}
void loop() {
if (Time.hour() == 6 && Time.minute() == 0 && shouldSend6AMData == true) {
shouldSend6AMData = false;
request.path = "/api/v1.6/variables/<variable ID here>/values?token=<token here>";
request.body = "{\"value\":" + String(level * -31.33) + "}"; // this value is sent to Ubidots variable "levelAt6AM" once a day at 6 AM, and is tied to a Ubidots event that sends an SMS if the value is < -2 (i.e. the tank did not fill overnight)
http.post(request, response, headers);
}
if (Time.hour() == 6 && Time.minute() == 1 && shouldSend6AMData == false) {
shouldSend6AMData = true;
}
unsigned long now = millis();
if (now - startTime > 5000) {
cycleAddressPins();
startTime = now;
}
}
void cycleAddressPins() {
float total = 0;
counter = 0;
for (int i=0; i<47; i++) { // 47 instead of 48 because the last sensor (lowest down in the tank) is non-functional and always reads LOW (as if a magnet was next to it)
digitalWrite(chipInh0, (i < 16)? LOW:HIGH);
digitalWrite(chipInh1, (i > 15 && i < 32)? LOW:HIGH);
digitalWrite(chipInh2, (i > 31)? LOW:HIGH);
delay(50);
digitalWrite(onesBit, (i & 1));
digitalWrite(twosBit, (i & 2) >> 1);
digitalWrite(foursBit, (i & 4) >> 2);
digitalWrite(eightsBit, (i & 8) >> 3);
delay(50);
int reading = digitalRead(dataPin);
// interpolate between sensors if 2 are activated at the same time
if (reading == 0) {
total += inchesDown[i];
counter ++;
}
}
level = (counter >0)? total/counter : lastLevel;
if (level != lastLevel) {
lastLevel = level;
request.path = "/api/v1.6/variables/<variable ID here>/values?token=<token here>";
request.body = "{\"value\":" + String(level * -31.33) + "}"; // there are 31.33 gallons of water per inch of height in my 8' diameter tank
http.post(request, response, headers);
}
}
int getRSSI(String cmd) {
return WiFi.RSSI();
}