Don’t you wanna watch me spill? Alerting when a container is nearly full of liquid.
Categories
There may come a time when you need to know if a container being filled with liquid that flows at a slow, variable rate is nearly full so that you can empty the contents or do something else with the stuff inside. Such a need recently arose here and I put something together using an Arduino (Cloneduino) Nano, a couple of non-contact capacitive water level sensors, a WS2812 LED breakout board, and a piezo buzzer.
One sensor would’ve been enough, but I wanted the possibility of a bit of early warning. When the lower sensor trips, the LED blinks orange and at a faster rate. When the second sensor also trips, the LED changes to red and the buzzer cranks out an irritating noise.
As you can see from the second of the two videos embedded side-by-side above, the ripples from liquid falling into the container or caused by someone bumping into it or by vibrations in the floor when, for example, someone walks by can cause an on-the-verge-of-tripping sensor to jitter on and off. The sensors incorporate relays and, during the period in which one is beginning to trip, they emit an audible clicking noise. The Arduino sketch running on the Nano includes a mechanism that waits until one of the sensors seems to have been permanently tripped before taking the corresponding action.
The contraption itself (sensors out of view), in all its vainglory is shown above. The lines of red and black marker are to remind me instantly which traces I’d used as the positive and negative power rails (via wires run from the wide-DIP socket holes corresponding to the Nano’s 5V-out pin and one of its GND pins.
Power is from a 6V (4xAA) battery pack that terminates in a male mini-USB connector which plugs directly into the Nano. When the water level is below what’s needed to trip the lower sensor, I pulse the LED blue. At the time that I added that behavior to the Arduino sketch, my thinking (mistaken in retrospect) was that it would help me notice when the batteries were running down. In practice, however, when the batteries get low on juice, even if the relay in the lower sensor trips, the LED will continue pulsing blue when it should be blinking orange.
Any future version probably needs current sensing and low-current warning and power ought to be going to the board directly rather than to and then through the microcontroller.
Here’s the back of the protoboard. The positive and negative signs indicate the polarity of the pins of the piezo buzzer, since I always snip off protruding lengths of component pins post-soldering and wouldn’t be able to compare their lengths in some hypothetical future where I might need to reuse the buzzer in some other project. Visible browning is from flux residue.
Connections-wise, I think the Fritzing diagram above is equivalent to the protoboard prototype. There are differences, like my use of a WS2812B LED ring rather than a single WS2812 LED on a breakout. In my defense, I used a WS2812 ring during the breadboard phase and replaced it with a single LED for the soldered version to reduce current consumption. Just now, while I had Fritzing open to screen-cap that image, I checked for a single-WS2812 part that looked like (Chinese generic clone of) Sparkfun’s WS2812B LED breakout that I used. My Fritzing setup has a lone-LED WS2812 breakout, but the pin labels are different and so on and so forth and I can’t find a Fritzing part for the Sparkfun breakout, even in Sparkfun’s official trove of Fritzing parts. But it would still be a WS2812B LED when mine is a WS2812. So, anyway.
I non-destructively popped open the case of one of the sensors. Everything below the head of the relay and the top of the adjustable resistor. Yours truly hasn’t needed to make any adjustments, but the latter is intended to allow one to tune the sensor to account for varying container thicknesses. Everything else was potted in white silicone. I only had two of these doohickeys at the time that I opened this one up and didn’t want to chance messing one up by trying to excavate goop to reveal more detail).
The sensors I’m using are XKC-Y28 5v, normally-open, and relay-based capacitive water sensors (XKC-Y28 Capacitive Non-Contact Liquid Level Sensor Pipeline Water Tank Detection Dry Node Output Water Level Sensing Built-In Relay DC 5V Normally Open Output).
Here’s the sketch I’m using at the moment:
#include <Tone.h> #include <Adafruit_NeoPixel.h> #define LED_PIN 5 #define LED_COUNT 1 int intMainLoopRuns = 0; // variable to store the read value int intMainLoopRunsResetAt = 10; // variable to store the read value int intLoopRunsLowerSensorTripped = 0; int intLoopRunsUpperSensorTripped = 0; int intThresholdNumLoopRunsLowerSensorTripped = 200; int intThresholdNumLoopRunsUpperSensorTripped = 200; int intTrippedLowerSensor = 0; // variable to store the read value int intTrippedUpperSensor = 0; // variable to store the read value #define WATER_LEVEL_SENSE_PIN_1_BELOW 8 #define WATER_LEVEL_SENSE_PIN_2_ABOVE 9 #define BUZZER_PIN 3 Tone alarm; Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800); int intStripBrightness = 0; int intStripBrightnessGettingFull = 25; int intStripBrightnessAlmostFull = 50; uint32_t ledColorGettingFull = strip.Color(255,125,0); uint32_t ledColorAlmostFull = strip.Color(255, 0, 0); void setStripColorGettingFull(){ strip.setBrightness(intStripBrightnessGettingFull); strip.setPixelColor(0, strip.Color(255, 125, 0)); strip.show(); delay(100); strip.setPixelColor(0, strip.Color(0, 0, 0)); strip.show(); delay(100); } void setStripColorAlmostFull(){ strip.setBrightness(intStripBrightnessAlmostFull); strip.setPixelColor(0, strip.Color(255, 0, 0)); strip.show(); delay(80); strip.setPixelColor(0, strip.Color(0, 0, 0)); strip.show(); delay(80); } void setStripNormal(){ for (int i=0; i<LED_COUNT; i++) { strip.setPixelColor(i, strip.Color(255, 255, 255)); } strip.setBrightness(0); strip.show(); } void pulseStripNormal(){ // One LED, so no need to iterate through strip LEDs strip.setPixelColor(0, strip.Color(0, 0, 0)); strip.setBrightness(35); strip.show(); for (int i=0; i<255; i++) { strip.setPixelColor(0, strip.Color(0, 0, i)); strip.show(); delay(10); } for (int j=255; j>0; j--) { strip.setPixelColor(0, strip.Color(0, 0, j)); strip.show(); delay(10); } } void soundTheAlarm() { if ( !alarm.isPlaying() ){ alarm.play(NOTE_A2, 400); delay(100); alarm.play(NOTE_D2, 500); delay(100); } } void alarmOff() { if (alarm.isPlaying()) { alarm.stop(); } } void setup() { Serial.begin(9600); pinMode(WATER_LEVEL_SENSE_PIN_1_BELOW, INPUT_PULLUP); pinMode(WATER_LEVEL_SENSE_PIN_2_ABOVE, INPUT_PULLUP); pinMode(BUZZER_PIN, OUTPUT); alarm.begin(BUZZER_PIN); strip.begin(); strip.clear(); setStripNormal(); } void loop() { intMainLoopRuns++; intTrippedLowerSensor = digitalRead(WATER_LEVEL_SENSE_PIN_1_BELOW); intTrippedUpperSensor = digitalRead(WATER_LEVEL_SENSE_PIN_2_ABOVE); int intStripBrightness_now = strip.getBrightness(); /* Serial.print("strip brightness: "); Serial.print(intStripBrightness_now); Serial.print("\n"); */ if ( (intTrippedLowerSensor == LOW) && (intTrippedUpperSensor == LOW) ){ if ( (intLoopRunsLowerSensorTripped >= intThresholdNumLoopRunsLowerSensorTripped) && (intLoopRunsUpperSensorTripped >= intThresholdNumLoopRunsUpperSensorTripped) ){ soundTheAlarm(); setStripColorAlmostFull(); } else { intLoopRunsLowerSensorTripped++; intLoopRunsUpperSensorTripped++; } Serial.print(" 2 ["); Serial.print(intLoopRunsLowerSensorTripped); Serial.print(","); Serial.print(intLoopRunsUpperSensorTripped); Serial.print("] "); } else if ( (intTrippedLowerSensor == LOW) && (intTrippedUpperSensor == HIGH) ){ if ( intLoopRunsLowerSensorTripped >= intThresholdNumLoopRunsLowerSensorTripped ){ intLoopRunsUpperSensorTripped=0; alarmOff(); setStripColorGettingFull(); } else { intLoopRunsLowerSensorTripped++; intLoopRunsUpperSensorTripped=0; } Serial.print(" 1 ["); Serial.print(intLoopRunsLowerSensorTripped); Serial.print("] "); } else { alarmOff(); pulseStripNormal(); intLoopRunsLowerSensorTripped=0; intLoopRunsUpperSensorTripped=0; Serial.print(" . "); } if(intMainLoopRuns == intMainLoopRunsResetAt) { Serial.print("\n"); intMainLoopRuns = 0; } }