Double Transducer: Transparency to Water Level

by Josh and Emma

Overall images of our project:

Overhead view of the final orientation of Emma's transducer. There are the main wiring transducer in the top, and water pump outputs on the bottom.
Overall final orientation of Emma's transducer
LCD display of Emma's transducer water pump. The LCD display reads the Target water level of 15.68 on the left, and current water level of 14.84 on the right.
LCD Display of Emma's transducer - writes target / current water level of the pump
The orientation of servo motor and fill / drain buttons. The servo motor is positioned directly above the fill / drain buttons on the default state. It looks like if servo motor is directed towards left or right, then it's going to physically press the buttons with its wings.
The orientation of servo motor and fill / drain buttons - shows how the servo motor can physically press the button 
Overhead view of Josh's transducer. There are inputs coming from the bottom, and outputs releasing from the top.
Overall final orientation of Josh's transducer
A closeup / detailed shot of wired L923DNE, which is our dual H-bridge motor driver IC. It is connected to our pumps to regulate them.
The wiring of L923DNE - motor driver of the pump
A close-up detailed shot of our photoresistor and LED, facing each other in one board.
Where the input of transparency is analyzed - LED and photoresistor to read the light level that goes through a surface

Functioning videos of our project:

Narrative:

In Project 1, we're creating a device that can turn light into a way to control the water level in a container. To make this happen, we're using two important parts: a light sensor and a mechanical system. First, the light sensor, which is like an electronic eye, will measure how transparent a surface is by measuring the light that goes through the surfaces. We'll use this information to figure out how much water needs to be added or removed in our container. Next, we have a mechanical system that can move a lever up and down. This lever will physically press buttons for us by positioning its wings at a certain angle. One button will make the water level go down (like draining water from a sink), and the other button will make it go up (like filling a glass with water). Now, here's how it all works together: When the light sensor sees more light, it will make the lever move in a certain way. This lever, in turn, will press one of the buttons. The number of times this button gets pressed will tell us how much water we should add or remove. Finally, we have a pipe connected to our container. Depending on the number of times the button is pressed, the pipe will either let water out or add more water in, just like turning a tap on and off. This way, we can control the water level in our container with the help of the light and the mechanical system. In conclusion, our device uses light to tell us when to add or remove water, and a mechanical system does the actual work by pressing buttons and controlling a pipe.

Progress photos (Emma):

The beginning model of the transducer. Everything looks similar with the final version of the model, except is very unorganized and messy
The second to final wiring; connected pumps and everything - all done except soldering
Initial orientation of the fill / drain button before soldering, which was placed exactly on the breadboard inefficiently
Initial orientation of fill / drain button, before solder
The process photo of transferring the buttons on to the soldering board
Soldering the buttons on to the seprate soldering board

Progress photos (Josh):

The wired model of solenoids, which was the initial step of what we were trying to do
Initial usage of solenoids - did not work as intended :(
Josh's process photo of how the two servo motors and buttons were going to work after failure of solenoids. It looks like the servo motor is in place with a hand clamp, and we're controlling the photoresistor with our fingers
Ideation of using servo motors and two buttons after failing the usage of solenoids
Ideation of how to keep servo motor in place - initially using hand clamps which was functional but not pleasantly looking

Discussion:

Initially, our team had a list of creative ideas on how to implement transparency to the water level measurement project, so the planning phase felt relatively straightforward. We brainstormed various approaches, considering different technologies and methodologies. However, as we delved deeper into the project, we realized that we lacked in focus in details. A moment came when we decided to incorporate buttons into our design. At this juncture, we were faced with the task of selecting the most appropriate materials, equipment, and input methods to make our idea a reality.

The real challenge emerged when we attempted to choose the right equipment for the job. We received a recommendation from Zach suggesting the use of solenoids, but this led to unforeseen issues. The solenoids heated up quickly, rendering them impractical for our purposes. This tweak forced us to regroup during the middle of the project, making the tough decision to unplug everything and shift our focus to servo motors. Despite the setbacks and adjustments, our determination to create a transparent water level measurement system remained unwavering, demonstrating the inherent complexity and problem-solving nature of engineering projects.  

Thus, in the future, we decided to promptly plan more carefully in advance, giving us seamless procedure with our project.

Functional block diagram / schematic / sketch:

Block diagram of the transducer

Electronic Schematic Diagram for our transducer

Code Submission

// Double transducer : changing trasparency to water level 

// by Josh and Emma


// The following code executes a way to change transparency to water level, using servo motors and buttons.

// Pins configuration, LCD display initialization, and servo motor initialization are executed in the 

// setup() function. After proper declaration of variable, the code calculates target volume by mapping 

// light value to some value, and difference between current volume and target volume. This is done so that 

// we can set a target range for the servo motor to properly orientate itself. The orientation of servo 

// motor is calculated so that it physically presses one of two buttons (fill / drain) pump, or press neither

//  at all at a default angle. Then we make the pump to do fill / drain / do nothing in those situations.


// Pin mapping:

// pin 13: backLight 

// pin 9: pumpFill (pump for filling)

// pin 10: pumpDrain (pump for Draining)

// pin 6: btnFill (button for filling)

// pin 7: btnDrain (button for draining)

// pin 3: servo motor

// pin A0: photoresistor input for analogWrite


#include <Servo.h>

#include <LiquidCrystal_I2C.h>


// pins

#define backLight 13

#define pumpFill 9

#define pumpDrain 10

#define btnFill 6

#define btnDrain 7

#define SERVO 3

#define photoRes A0 //0-1023, actual 720- 980 with 5V power supplies


//consts

const bool debug = false;

const float volError = 1.5;

const float photoHigh = 1023;

const float photoLow = 650;

const float waterHigh = 30;

const float waterLow = 0;


Servo servo;

LiquidCrystal_I2C lcd(0x27,16,2);  // set the LCD address to 0x3F for a 16 chars and 2 line display


int photoVal = 0;

float currentVol = 0;

int state = 0;

int prevState = 0;

long fillTime = 0;

long drainTime = 0;


long lastLoopTime = 0;

long lastLCDtime = 0;


void setup() {

  servo.attach(SERVO);

  pinMode(backLight, OUTPUT);

  pinMode(photoRes, INPUT);

  pinMode(btnFill, INPUT);

  pinMode(btnDrain, INPUT);

  pinMode(pumpFill, OUTPUT);

  pinMode(pumpDrain, OUTPUT);


  lcd.init();

  lcd.clear();         

  lcd.backlight();      // Make sure backlight is on


  Serial.begin(9600);

  servo.write(50);

  delay(500); // wait for servo to reset

}


void loop() {

  delay(100);

  photoVal = analogRead(photoRes);


  // calcualate target volume by mapping light value to some volume

  float targetVol = mapfloat(photoVal, photoLow, photoHigh, waterLow, waterHigh);


  // calcualte the difference between current volume and target volume

  float volDiff = targetVol - currentVol;


  if (abs(volDiff) < volError) { // volumes are close enough, no pumping

    // do nothing

    if (debug) {

      Serial.print(photoVal);

      Serial.print(" stop at ");

      Serial.println(targetVol);

    }

    servo.write(50);

    

  } else if (volDiff > 0) { // current below target, fill

    // fill

    if (debug) {

      Serial.print(photoVal);

      Serial.print(" fill to ");

      Serial.println(targetVol);

    }


    servo.write(78);

    

  } else if (volDiff < 0) { // current above target, drain

    // drain

    if (debug) {

      Serial.print(photoVal);

      Serial.print(" drain to ");

      Serial.println(targetVol);

    }


    servo.write(22);

  } else {

    Serial.println("Unexpected Error");

  }


  // set which state the pump should be in based on buttons 

  if (digitalRead(btnFill)) {

    state = 1; // fill

  } else if (digitalRead(btnDrain)) {

    state = 2; // drain

  } else {

    state = 0; // do nothing

  }


  // based on which state the pump is in, get total time pumped in each state

  switch (state) {

    case 1: // fill

      // TODO make pump fill

      digitalWrite(pumpFill, HIGH);

      digitalWrite(pumpDrain, LOW);

      fillTime += millis() - lastLoopTime; // essentially add up time each time loop() happens

      break;

    case 2: //drain

      // TODO make pump drain

      digitalWrite(pumpFill, LOW);

      digitalWrite(pumpDrain, HIGH);

      drainTime += millis() - lastLoopTime;

      break;

    default:

      digitalWrite(pumpFill, LOW);

      digitalWrite(pumpDrain, LOW);

      break; // nothing happens

  }


  // calculate current volume as sum of filled and drained volume, assume begin at 0

  currentVol = (float)(fillTime - drainTime) / 1000;

  displayVolume(targetVol, currentVol);


  if (debug || true) {

    Serial.print(targetVol);

    Serial.print(" : ");

    Serial.println(currentVol);

  }


  // record the time when this loop ends so we can get duration between this loop and last loop

  lastLoopTime = millis();

}


void displayVolume(float targetVol, float currentVol) {

  if (millis() > lastLCDtime + 300) {

    lcd.setCursor(0,0);   //Set cursor to character 2 on line 0

    lcd.print("Target : Current");

    lcd.setCursor(0,1);   //Move cursor to character 2 on line 1

    lcd.print(String(targetVol) + "  : " + String(currentVol));

    lastLCDtime = millis();

  }

  

}


// same map function except this one returns floats

float mapfloat(float x, float in_min, float in_max, float out_min, float out_max)

{

  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;

}