Corner view of the product detector, with the power (9V)
This project detects when a bottle of conditioner is running low. It uses a small weight sensor to measure the bottle’s weight and displays how much is left using colored LEDs. Before starting, the light glows purple, and when you press the button, the system records that weight as “full.” From then on, the LEDs update in real time as the conditioner gets used, and when the bottle is almost empty, the light blinks red as a warning. A small switch lets me reset or recalibrate the system when needed. This project shows how combining sensors, buttons, and simple feedback can make everyday objects like a conditioner bottle more useful and interactive.
Project Overview
The front view with the full weight of the product, hence full green lights.
The inside of the detector where you would put your product on top of the pink surface.
The yellow button to set the initial weight is shown and the switch which turns off/on the detector
Showcases the front view with no product and with the purple light, which tells us there had not been a weight set.
I am lifting the bottle up slowly, which is decreasing the weight, which makes the green lights turn red one by one.
The bottle is now completely off the detector so that people know the product is done, and it tells you by blinking full red lights.
Video
In this video, I start with plugging in 9V. I then flip the switch on and wait for the lights to turn purple. I placed my product inside and then set the full weight by pressing the yellow button. I then lift the bottle up slowly to showcase the progress bar with the red and green lights.
Process
Laying the part out for the load cell base
Assembling the outer box and checking to see if everything is still working after the transfer of putting all components inside
More information:
At first, the load weight base was really unstable because the screw heads were sticking out from the bottom. To fix that, I added small rubber grippers to the actual load cell base and then hot glued it onto the wooden board. The screws are placed in the middle of all sides and measured carefully so they’re the same height all around. I used slightly longer screws so I could secure the load cell base with the wooden board on top without messing with the wiring underneath. Once everything was tightened and leveled, the whole structure became much more stable. Before that, the bottle would wobble a bit when placed on top, but after adding the grippers and adjusting the screws, it felt solid and gave me way more accurate readings. Getting this part right made the rest of the project work a lot smoother.
Putting the load cell base all together (with load cell)
Had to put to screws on the side to create separation from the wires to put the load cell weight on top, which is now connected to a wooden base to keep it stable
Discussion
During the in-class critique, I got a lot of helpful feedback that made me think more about both the look and function of my project. Yutong said, “It doesn’t seem like the figure joints on the top part of the box is necessary if it remains open.” My take on that is that the finger joints on top aren’t really necessary, but it was mainly a happy accident. I realized I needed more height for the device so the bottle wouldn’t fall over, so I laser cut the box again and flipped it over. Even though it doesn’t really serve a purpose, I think the design of the top looks cool and adds a fun detail. Zar said, “Having it constantly on might not be very power-efficient, maybe you could have a movement sensor that detects if you walk in front of it.” I thought that was a really interesting idea and something I’d love to try in the future. My concern is that it would mean the Arduino has to stay on all the time to track movement. Manually turning it on and off makes more sense since it saves power by shutting everything down. If I used sensors to do that automatically, it would have to be battery powered, which isn’t ideal in the long run. It would be amazing if the Arduino could turn itself on and off with a sensor, but I don’t think that’s possible.
I’m really happy with how my project turned out. It went beyond what I thought it would be. My goal was to make a stable device that detects weight quickly and shows a progress bar that goes down as the product is being used. I wanted it to show fullness visually, like for a bottle of conditioner or shampoo. I do wish I had made the load base a little larger so the full surface could be used, but functionally it worked great. The last-minute idea of flipping the extra box on top ended up being one of my favorite parts because it made the project sturdier and look more finished.
I learned a lot about myself through this process. Coding is definitely not my strong suit, and I needed help figuring that part out. I also didn’t plan how to keep the product stable on the platform until later, which caused some frustration, but it was worth it once it worked. I made one big mistake when I powered the Arduino 5V pin directly with a 9V battery, which completely stopped it from working. I learned it should go into the VIN pin instead. I had to get a new Arduino and resolder everything, and I also learned how fragile the load cell wires are.If I made another version, I’d add sound and make the base completely flat. I’d also add a small holder to keep bottles secure and try to make it smaller overall. I’m really proud of how it came out and how much I learned.
Technical Information
/*
Product Running Out Detector for Intro to Physical Computing Project 2
This project uses a load cell to measure the weight of a product
such as a bottle of conditioner to detect when it’s running low.
The LED strip shows the current fill level by color, starting as
light purple before setup and gradually shifting from green
when full to red when almost empty. A button is used to set the
full reference weight.
This code was entirely written by ChatGPT. I did do revisions, so this is just a summary of the prompts put together.
Prompt: I want you to write me a complete Arduino sketch for a project
that uses a load cell and a Pololu LED strip to detect how much of something
is left, like a bottle of conditioner. When it first turns on, the LEDs
should be light purple until I press a button on pin 7, which sets whatever
Weight is on the scale as the full amount. After that, the LEDs should turn
green when the product is full, red when it is empty, and blink red when it
is almost empty. The serial monitor should show the live readings, including
the current weight, the full weight, and the percentage level. The goal is to
make a simple and clear product running out detector.
Pin Mapping:
Arduino pin | Role | Description
------------------------------------------------------------
4 | Input | HX711 DOUT
5 | Input | HX711 SCK
7 | Input | Button
12 | Output | LED strip DIN
Code released to the public domain by the author, 11/12/2025
Danely Rodriguez, danelyr@andrew.cmu.edu
*/
#include <HX711_ADC.h>
#include <PololuLedStrip.h>
// ---- Pins ----
const uint8_t HX711_DOUT = 4;
const uint8_t HX711_SCK = 5;
const uint8_t BUTTON_PIN = 7; // button to GND
PololuLedStrip<12> ledStrip; // DIN on pin 12
// ---- LED strip ----
#define LED_COUNT 10
rgb_color colors[LED_COUNT];
// ---- Scale ----
HX711_ADC LoadCell(HX711_DOUT, HX711_SCK);
float calibrationValue = 696.0f; // your calibration value
// ---- State ----
float FULL_GRAMS = 0.0f;
bool fullSet = false;
// ---- Button debounce ----
bool lastBtn = HIGH;
unsigned long lastChange = 0;
const unsigned long DEBOUNCE_MS = 40;
// ---- Blink state ----
bool blinkState = false;
unsigned long lastBlink = 0;
const unsigned long BLINK_INTERVAL = 500; // 0.5s
// ---- Helpers ----
void showSolid(uint8_t r, uint8_t g, uint8_t b) {
for (int i = 0; i < LED_COUNT; i++) colors[i] = rgb_color(r, g, b);
ledStrip.write(colors, LED_COUNT);
}
void showLevelBar(float frac) {
if (frac < 0) frac = 0;
if (frac > 1) frac = 1;
int greens = (int)floor(frac * LED_COUNT + 1e-6f);
for (int i = 0; i < LED_COUNT; i++)
colors[i] = (i < greens) ? rgb_color(0, 180, 0) : rgb_color(180, 0, 0);
ledStrip.write(colors, LED_COUNT);
}
// ---- Setup ----
void setup() {
Serial.begin(57600);
pinMode(BUTTON_PIN, INPUT_PULLUP);
// Quick LED test
showSolid(0, 180, 0); delay(300);
showSolid(180, 0, 0); delay(300);
showSolid(0, 0, 0);
// Load cell setup
LoadCell.begin();
LoadCell.setReverseOutput();
LoadCell.start(2000, true);
if (LoadCell.getTareTimeoutFlag()) {
Serial.println("HX711 timeout. Check wiring.");
showSolid(180, 0, 0);
while (1) {}
}
LoadCell.setCalFactor(calibrationValue);
Serial.println("Press button (D7→GND) to set current weight as FULL.");
// Standby: solid light purple until FULL is set
showSolid(120, 0, 120);
}
// ---- Loop ----
void loop() {
static bool newDataReady = false;
if (LoadCell.update()) newDataReady = true;
// ----- BUTTON -----
bool nowBtn = digitalRead(BUTTON_PIN);
unsigned long now = millis();
if (nowBtn != lastBtn && (now - lastChange) > DEBOUNCE_MS) {
lastChange = now;
if (nowBtn == LOW && lastBtn == HIGH) {
float grams = LoadCell.getData();
Serial.print("Button press. Current weight = ");
Serial.print(grams, 1); Serial.println(" g");
if (isfinite(grams) && grams > 20.0f) {
FULL_GRAMS = grams;
fullSet = true;
Serial.print("FULL set to "); Serial.print(FULL_GRAMS, 1); Serial.println(" g");
// confirm with a quick blue blink
showSolid(0, 0, 180); delay(200);
} else {
Serial.println("Not setting FULL (weight too small).");
}
}
lastBtn = nowBtn;
}
// ---- Standby purple until FULL is set ----
if (!fullSet) {
showSolid(120, 0, 120); // solid light purple idle
return; // skip rest until FULL is captured
}
// ----- LED BAR -----
if (newDataReady) {
float grams = LoadCell.getData();
if (!isfinite(grams) || grams < 0) grams = 0;
float frac = 0.0f;
if (FULL_GRAMS > 0.0f) {
frac = grams / FULL_GRAMS;
if (frac < 0) frac = 0;
if (frac > 1) frac = 1;
}
// Blink if empty (<= 5%)
if (frac <= 0.05) {
if (now - lastBlink > BLINK_INTERVAL) {
lastBlink = now;
blinkState = !blinkState;
showSolid(blinkState ? 180 : 0, 0, 0); // red on/off
}
} else {
showLevelBar(frac);
}
// Debug print to Serial
static unsigned long lastPrint = 0;
if (now - lastPrint > 400) {
Serial.print("Weight: "); Serial.print(grams, 1); Serial.print(" g | ");
Serial.print("Full: "); Serial.print(FULL_GRAMS, 1); Serial.print(" g | ");
Serial.print("Level: "); Serial.print(frac * 100.0f, 1); Serial.println("%");
lastPrint = now;
}
newDataReady = false;
}
}