Don’t you wanna watch me spill? Alerting when a container is nearly full of liquid.

Categories
Arduino C++ Contrivances The Diary of Lupin Pooter
Slow-pulsing the RGB LED in blue.
Faster-pulsing the RGB orange after the first water-level sensor has been tripped. Blinking it in red and making an irritating screechy noise using the piezo buzzer when the second sensor trips.

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.

Everything is soldered to a breadboard-like protoboard, except the Nano, which I snapped into a socket which I'd soldered to the board.
Everything is soldered to a breadboard-like protoboard, except the Nano, which I snapped into a socket which I’d soldered to the board.

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.

Everything is soldered to a breadboard-like protoboard, except the Nano, which I snapped into a socket which I'd soldered to the board.
The reverse of the soldered protoboard.

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.

Fritzing diagram of a version of the breadboard prototype. There are some differences between this circuit and the eventual soldered protoboard version.
Fritzing diagram of a version of the breadboard prototype. There are some differences between this circuit and the eventual soldered protoboard version.

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.

Shot showing one sensor with its cover removed, along with the tools I used to pry it open.
Removing the cover of one of the sensors didn’t tell much, aside from the model of relay used and the max-resistance of the variable resistor.
Shot showing one sensor with its cover removed, along with the tools I used to pry it open.
Close-up of the two partially visible components. Alas, the relay’s identifying markings are disadvantageously lit.

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 sensor itself.
Clear-ish photos of the exterior of one sensor (part number XKC-Y28). I attempted to make the markings (molded into the white plastic of the case) more readable with a blue wax pencil but ended up messing with filters in Photoshop to make it legible (hence the black-and-white cutting mat). The silver-gloss sticker on the container-facing side specifies, in Simplified Chinese, that this is the 5V model, that it uses a relay, and that it’s “normally open”.

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;
 }


}