Innovative Kibble Dispenser

by EvWorks in Living > Pets

203 Views, 3 Favorites, 0 Comments

Innovative Kibble Dispenser

P1180125.JPG

Do you have an adorable dog or cute cat at home? If so, you've come to the right place!


Going away for the weekend, coming home late, or simply wanting to get a bit more sleep... These are commonplace situations for us, but for our pets, they can quickly rhyme with empty bowls: the worst imaginable catastrophe in their eyes.

And even when someone is there to feed them, there's no guarantee that the quantity is right. Too little, and they clamor. Too much, and they're gluttons for food.


So to ensure that my cats always receive the right amount of kibble, and above all to avoid being woken up at 6am by a big, desperate meow, I designed an automatic kibble dispenser.

You don't even have to be at home any more: thanks to an Internet connection, you can remotely control not only the dispensing times, but also the exact quantity of kibble to be given. The system warns you immediately when the tank is empty, and you can check the remaining level in real time. All feedings are recorded, so you can keep track of the quantities fed day after day. With a capacity that can last up to two weeks, you can be sure that your pet will be fed even if you're away for a long time.

As a bonus, the dispenser can be connected to a 3D-printed button so your pet can help himself to his favorite kibble. A simple gesture, but a real headache to teach your cat.


In this Instructables, I'll show you how to make this dispenser step by step. A simple, low-cost, accessible and customizable project that's very close to my heart.

Supplies

Mechanical fabrication:

  1. 3D printer
  2. Plexiglas tube (inner diameter: 113 mm, thickness: 3 mm, to be purchased or printed with transparent filament)
  3. Pet bowls

Electronic components and sensors:

  1. Converter 220V AC → 12V DC
  2. ESP32
  3. Weighing cell and HX711
  4. Ultrasonic sensor HC-SR04
  5. DC motor 12V
  6. Transistor TIP120

Electronic assembly:

  1. Soldering iron and wire
  2. Cables and basic components
  3. PCB Prototyping Board Hardware

3D Printing the System

WhatsApp Image 2025-07-22 at 13.08.13.jpeg
P1180104.JPG

First, you've got to print out the different parts of the dispenser. This can be a lengthy process, due to the large size of some of the parts to be printed, but don't worry, during this time we'll be able to turn our attention to the electronics of the system.

Most parts can be printed in PLA with a layer height of 0.2 mm and a 20% gyroid fill. You can, of course, adapt these parameters to your own preferences and printer settings. I advise you to use tree supports when necessary: they're easier to remove, especially around complex areas.

ATTENTION:

- Print the 'Plexiglas Storage Cylinder' only if you don't want to buy a real Plexiglas cylinder. In this case, use a transparent filament.

- Print the ‘Cable Cover 2’ part twice, and the ‘Foot’ part four times, preferably in TPU for a better grip on the ground, but you can also print it in PLA if you don't have any.

Create a PCB With All the Electronic Components

WhatsApp Image 2025-07-13 at 18.29.25(1).jpeg
schéma.png
Capture d'écran 2025-08-01 144939.png

To avoid false contacts and improve system organization, it's best to group all components together on a printed circuit board (PCB). The main components are soldered to it, and connectors are added for the motor and scale cables, which are a little further away from the rest of the electronics.

NB: Refer to the diagram for exact routing and placement.

Let's Get Coding!

Capture d'écran 2025-08-01 150337.png
FNYZURYLVWEH4S8.png
FX5UQFDLVWEH5UI.png

To carry out this project, we'll need Visual Studio Code. For those of you who don't already have it, you can download and install it here.

In the extensions tab install PlatformIO and configure it for your ESP32-WROOM

Then, the first thing to do is to calibrate your scales. To do this, you need to install the 'HX711_ADC' library and run the 'Calibration' example.

Finally, you can copy and paste the code below. Don't forget to install all the necessary libraries from PlatformIO and to replace information such as the ssid and password for your wifi.

// Import libraries
#include <Arduino.h>
#include "HX711.h"
#include <WiFi.h>
#include <PubSubClient.h>

// ESP32 pin definitions
const int trigPin = 17;
const int echoPin = 5;
const int LOADCELL_DOUT_PIN = 22;
const int LOADCELL_SCK_PIN = 23;
const int MOTOR_PIN = 16; // PWM pin on ESP32

// ====== WEIGHT SENSOR CONFIGURATION ======
HX711 scale;
float calibration_factor = 1078.6; // Adjust this after calibration

// ====== WiFi CONFIGURATION ======
const char* WIFI_SSID = "XXX"; //replace with your own ssid wifi
const char* WIFI_PASS = "XXX"; //replace with your own wifi password

// ====== MQTT CONFIGURATION ======
const char* MQTT_SERVER = "192.168.XXX.XXX";
const int MQTT_PORT = 1883;
const char* MQTT_USERNAME = "node-red";
const char* MQTT_PASSWORD = "123456789";
const char* MQTT_CLIENT_ID = "ESP32_Croquettes";

// MQTT Topics
const char* TOPIC_CMD_DISTRIBUER = "croquettes/distrib/cmd/distribuer";
const char* TOPIC_NIVEAU = "croquettes/distrib/niveau";
const char* TOPIC_POIDS_GAMELLE = "croquettes/distrib/poids_gamelle";

unsigned long lastPing = 0;
const unsigned long pingInterval = 40000; // 40 seconds interval

// ========== WiFi and MQTT ==========
WiFiClient wifiClient;
PubSubClient mqtt(wifiClient);

// PWM configuration for ESP32
const int pwmFreq = 5000; // PWM frequency
const int pwmChannel = 0; // PWM channel
const int pwmResolution = 8; // 8-bit resolution (0–255)

// ========= Functions =========

void connectToWiFi() {
Serial.print("Connecting to WiFi...");
WiFi.begin(WIFI_SSID, WIFI_PASS);

while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(1000);
}

Serial.println(" Connected!");
Serial.print("Local IP: ");
Serial.println(WiFi.localIP());
}

void sendWeight(float weight) {
String payload = String(weight, 2); // 2 decimal places
Serial.print("Sending weight: ");
Serial.println(payload);

if (!mqtt.publish(TOPIC_POIDS_GAMELLE, payload.c_str())) {
Serial.println("Error sending weight!");
}
}

float readWeight() {
if (scale.is_ready()) {
float weight = scale.get_units(10); // average over 10 readings
if (weight < 0) weight = 0; // avoid negative values
return weight;
} else {
Serial.println("Error: load cell not ready.");
return -1;
}
}

// Initialize PWM motor on ESP32
void initMotor() {
// Setup PWM channel
ledcSetup(pwmChannel, pwmFreq, pwmResolution);
ledcAttachPin(MOTOR_PIN, pwmChannel);
ledcWrite(pwmChannel, 0); // Make sure the motor is stopped at startup
}

// Control motor speed in % (0 to 100)
void motor(int speed) {
// Convert percentage to PWM value (0 to 255)
int pwmValue = map(speed, 0, 100, 0, 255);
ledcWrite(pwmChannel, pwmValue);
}

// Get distance in centimeters
float getDistance() {
// Clean trigger signal
digitalWrite(trigPin, LOW);
delayMicroseconds(2);

// Send 10 µs pulse
digitalWrite(trigPin, HIGH);
delayMicroseconds(10);
digitalWrite(trigPin, LOW);

// Read echo duration (in microseconds)
long duration = pulseIn(echoPin, HIGH);

// Convert to distance (cm)
float distance = duration * 0.0343 / 2;

return distance;
}

void sendLevel(float distance) {
String payload = String(distance, 2);
Serial.print("Sending level: ");
Serial.println(payload);

if (!mqtt.publish(TOPIC_NIVEAU, payload.c_str())) {
Serial.println("Error sending level!");
}
}

// Callback to process incoming MQTT messages
void mqttCallback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message received on topic: ");
Serial.print(topic);
Serial.print(". Message: ");

String message;
for (int i = 0; i < length; i++) {
message += (char)payload[i];
}
Serial.println(message);

// Check if it's the distribution command topic
if(String(topic) == TOPIC_CMD_DISTRIBUER){
float amount_cmd = message.toFloat();

Serial.println("Command received: ");
Serial.println(amount_cmd);
Serial.println(" g");

// Start motor
motor(100);
delay(30 * amount_cmd);
motor(0);

delay(500); // Stabilize system

// Read weight after distribution
float weight_after = readWeight();
Serial.println("Weight after distribution:");
Serial.println(weight_after);
Serial.println(" g");
sendWeight(weight_after);

// Read reservoir level
float distance = getDistance();
Serial.println("Current distance:");
Serial.println(distance);
Serial.println(" cm");
sendLevel(distance);
}
}

void connectToMQTT() {
while (!mqtt.connected()) {
Serial.print("Attempting MQTT connection...");

if (mqtt.connect(MQTT_CLIENT_ID, MQTT_USERNAME, MQTT_PASSWORD)) {
Serial.println(" connected!");

// Subscribe to command topic
if (mqtt.subscribe(TOPIC_CMD_DISTRIBUER)) {
Serial.print("Subscribed to topic: ");
Serial.println(TOPIC_CMD_DISTRIBUER);
} else {
Serial.println("Subscription failed!");
}

} else {
Serial.print(" failed, rc=");
Serial.print(mqtt.state());
Serial.println(" retrying in 5 seconds");
delay(5000);
}
}
}

void initScale() {
Serial.println("Initializing load cell...");
scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
scale.set_scale(calibration_factor);
scale.tare(); // Set current reading to zero

Serial.println("Calibration complete. Example readings:");

Serial.print("Raw reading: ");
Serial.println(scale.read());

Serial.print("Average reading: ");
Serial.println(scale.read_average(20));

Serial.print("Value (g): ");
Serial.println(scale.get_value(5));

Serial.print("Weight (units): ");
Serial.println(scale.get_units(5), 1);
}

void setup() {
Serial.begin(115200);

// Pin setup
pinMode(trigPin, OUTPUT);
pinMode(echoPin, INPUT);

initScale(); // Initialize scale
initMotor(); // Initialize motor PWM

connectToWiFi();

// MQTT setup
mqtt.setServer(MQTT_SERVER, MQTT_PORT);
mqtt.setCallback(mqttCallback);


Once the code has been uploaded, open the serial monitor to obtain the ESP32's local IP address. Enter this address in your browser (from a device connected to the same Wi-Fi network). This will take you to the web interface for remote control of the dispenser.

Assemble the Dispenser

P1180098.JPG
P1180100.JPG
P1180097.JPG
P1180107.JPG

Assembly of the dispenser may seem complex, but it has been designed to be as simple as possible. Follow these step-by-step instructions for a flawless finished product :

  1. Fasten the mechanical part using M2 screws:
  2. the Endless Screw
  3. Base Funnel
  4. Croquettes Cylinder
  5. Place the electronics in the dispenser base.
  6. Fasten under the base :
  7. Bottom Cap
  8. feet (Foot) with M2 screws
  9. Cleanly pull out the cables :
  10. scale
  11. ultrasonic sensor
  12. power supply
  13. Move on to the top section:
  14. Attach the Plexiglas Storage Cylinder
  15. Assemble the parts making up the dispenser cover
  16. Install the ultrasonic sensor in the cover to measure the level of remaining kibble.
  17. Use the Cable Cover parts to hide the cables and keep the unit clean.
  18. Insert the Croquettes Output piece at the kibble outlet (press firmly if necessary).
  19. Place the scale under your pet's bowl to monitor the amount of kibble dispensed.

And there you have it, ready for use.

Create the Button

P1180093.JPG

If you'd like to go a step further and design a button that will allow your pet to help himself to his favorite kibble, I invite you to follow steps 5 and 6. Otherwise, go on to step 7 to conclude this exciting project!

To make this Wifi-connected button for the kibble dispenser, you'll need an ESP 32, a PCB measuring around 30 mm by 40 mm, a switch and other basic components, such as cables, etc.

First of all, you need to print the button structure in PLA using the same parameters as those used for printing the kibble dispenser parts... The 'Button' part directly incorporates a spring, to ensure good user comfort. To print it correctly, remember to tilt the button so that the springs can be printed parallel to the plate.

Once the button is printed and the pcb made (it's simply a matter of connecting a button to the pin of your choice, in this case 5), you can assemble the whole thing using 3 M2 screws.

Code the Button

You can copy and paste this code into a new project on PlatformIO and upload it to t.

#include <WiFi.h>
#include <PubSubClient.h>

#define BUTTON_PIN 5

const char* ssid = "XXX";
const char* password = "XXX";

const char* mqtt_server = "192.168.1.14";
const int mqtt_port = 1883;
const char* mqtt_topic = "esp32c3/bouton";
const char* mqtt_user = "node-red";
const char* mqtt_password = "123456789";

WiFiClient espClient;
PubSubClient client(espClient);

void connectToWiFi() {
Serial.print("WiFi connection to: ");
Serial.println(ssid);
WiFi.begin(ssid, password);

unsigned long startAttemptTime = millis();
while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 10000) {
delay(500);
Serial.print(".");
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nWiFi connected. IP: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("\nWiFi connection failed");
}
}

void reconnectMQTT() {
Serial.print("MQTT connection...");
if (client.connect("ESP32C3Client", mqtt_user, mqtt_password)) {
Serial.println("Connected!");
} else {
Serial.print("Failed. rc=");
Serial.println(client.state());
}
}

void setup() {
Serial.begin(115200);
delay(1000); // Small delay to stabilize serial port

pinMode(BUTTON_PIN, INPUT_PULLDOWN); // Set button to pull-down

// Configure wake-up by GPIO interrupt on rising edge (LOW->HIGH)
esp_deep_sleep_enable_gpio_wakeup(1ULL << BUTTON_PIN, ESP_GPIO_WAKEUP_GPIO_HIGH);

// Check the cause of wake-up
if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_GPIO) {
Serial.println("Woken up by button!");
connectToWiFi();
client.setServer(mqtt_server, mqtt_port);
reconnectMQTT();

if (client.connected()) {
String message = "Button pressed on ESP32-C3 in deep sleep";
client.publish(mqtt_topic, message.c_str());
client.disconnect();
}

Serial.println("Going back to deep sleep in 2s...");
delay(2000);
} else {
Serial.println("Normal startup");
}

// Enter deep sleep
Serial.println("Entering deep sleep...");
esp_deep_sleep_start();
}

void loop() {
// Should never be called because everything happens in setup and deep sleep
}

Give It a Try With Your Pets!

My Innovative Kibble Dispenser in action !!
P1180126.JPG
P1180137.JPG
Capture d'&eacute;cran 2025-07-29 192413.png
Capture d'&eacute;cran 2025-07-29 192403.png

Now your dispenser works perfectly! You can use the web interface to control and monitor kibble dispensing, and even teach your cat or dog to serve itself.

I hope you've enjoyed this tutorial. Feel free to leave a comment and a heart if you did. If you've followed my Instructables, please let me know, I'd really appreciate it! And if you get stuck at any stage, leave a message in the comments, I'll do my best to help.