PET V1 (The Ultimate Gadget for Pets)

March 23, 2025
Description
PET V1 (The Ultimate Gadget for Pets)
Description:
I built a robot with laser pointer controlled by two servo motors and a food dispenser for pets which also contains a servo, and it moves on mecanum wheels for omnidirectional motion which will make the pet more Amaze and get interacted with it. The laser, which is especially the favourite of cats grabs the attention of the pets, and it can point the laser here and there with the help of two servers which will keep the pets engaged with. At last, this is the most amazing thing you present to your pet.
Functions:
1. It has omnidirectional wheels which allows it to move is any direction and is more fun.
2. It also has servo motor for dispenser which dispense treat for pets.
3. It has pan and tilt servo for laser pointer so that you can sprint your pets.
4. At the end its brain is Arduino uno r4 Wi-Fi so that we can control all its functionality through webpage.
Hardware and Software used:
Hardware:
1. Arduino uno r4 Wi-Fi (For control)
2. Adafruit motor shield v-2.3 (For controlling motors)
3. Motors (For motion)
4. Servos (They act as actuators)
5. Laser (For fun)
6. Mecanum Wheels (For omnidirectional movement)
7. 3D printer (For printing parts)
Software:
1. Fusion 360 (3D modelling)
2. Arduino IDE (For programming Arduino)
3. Chrome (For running webpage)
4. Fritzing (For circuit)
Wiring Diagram:
Adafruit Motor Shield V2 (stacked on Arduino)
M1 → Front-Left Motor
M2 → Front-Right Motor
M3 → Rear-Left Motor
M4 → Rear-Right Motor
Pan Servo → Pin 9
Tilt Servo → Pin 10
Dispenser Servo → Pin 11
Laser:
2-Wire → VCC → Pin 8 + 220Ω, GND → GND
3-Wire → SIG → Pin 8, VCC → 5V, GND → GND
Li-Po Battery → Motor Shield Vin+/GND
IMP Note:
- There are two type of lasers one with 2 wire and one with 3 wires so please consider wiring seriously.
- Also, it is important to use right battery as wrong voltage one can damage electronics. Please consider battery with 7.4 to 12 volts output. Connect battery to Motor shield not Arduino.
Connection Diagram
Code:
What does the code do;
1. Creates a Wi-Fi access point (AP) called "PetBot" with a password "12345678"
2. Sets up a web server that allows remote control of a robot
3. Controls four DC motors through an Adafruit Motor Shield for omnidirectional movement
4. Manages three servos: for pan, tilt and dispensing
5. Provides a user-friendly HTML interface accessible through a web browser
6. Includes movement controls functions
7. Features sliders for precise pan and tilt control of the laser pointer
8. Includes a button to toggle the laser on/off
9. Has a button to dispense pet food
10. Implements smooth servo movement with gradual position changes
11. Handles HTTP requests to control all robot functions
12. Displays current servo positions in the web interface
Well here is the code but you can find it in files also:
#include <WiFiS3.h>
#include <Adafruit_MotorShield.h>
#include <Servo.h>
// WiFi Credentials (AP Mode)
const char* ssid = "PetBot";
const char* password = "12345678";
// Web Server
WiFiServer server(80);
String header;
// Motors
Adafruit_MotorShield AFMS = Adafruit_MotorShield();
Adafruit_DCMotor *LeftFrontWheel = AFMS.getMotor(1);
Adafruit_DCMotor *RightFrontWheel = AFMS.getMotor(2);
Adafruit_DCMotor *LeftBackWheel = AFMS.getMotor(3);
Adafruit_DCMotor *RightBackWheel = AFMS.getMotor(4);
// Servos & Laser
Servo panServo, tiltServo, dispenserServo;
const int laserPin = 8;
bool laserState = false;
unsigned long laserStartTime = 0;
// Servo positions
int currentPanPosition = 90;
int currentTiltPosition = 90;
int targetPanPosition = 90;
int targetTiltPosition = 90;
// Wheel speed
int wheelSpeed = 200; // Adjust as needed
// Command rate limiting
unsigned long lastCommandTime = 0;
const unsigned long commandThrottle = 50; // Minimum time between servo commands (ms)
void setup() {
Serial.begin(9600);
AFMS.begin(); // Initialize MotorShield
// Attach servos and laser
panServo.attach(9);
tiltServo.attach(10);
dispenserServo.attach(11);
panServo.write(currentPanPosition); // Center position
tiltServo.write(currentTiltPosition);
pinMode(laserPin, OUTPUT);
digitalWrite(laserPin, LOW);
// Start WiFi AP
WiFi.beginAP(ssid, password);
Serial.print("AP IP: ");
Serial.println(WiFi.localIP());
server.begin();
}
void loop() {
// Handle smooth servo movement
updateServoPositions();
// Handle client connections
WiFiClient client = server.available();
if (client) {
Serial.println("New client connected");
String currentLine = "";
unsigned long currentTime = millis();
unsigned long previousTime = currentTime;
const unsigned long timeoutTime = 2000; // Define timeout time in milliseconds
bool requestComplete = false;
// Loop while the client is connected
while (client.connected() && currentTime - previousTime <= timeoutTime && !requestComplete) {
currentTime = millis();
if (client.available()) {
char c = client.read();
header += c;
if (c == '\n') {
// If the current line is blank, you got two newline characters in a row.
// that's the end of the client HTTP request, so send a response:
if (currentLine.length() == 0) {
handleRequest(header);
sendHTML(client);
requestComplete = true;
} else {
currentLine = "";
}
} else if (c != '\r') {
// Add character to currentLine
currentLine += c;
}
}
}
// Clear the header variable
header = "";
// Close the connection
client.stop();
Serial.println("Client disconnected");
}
// Auto-turn off laser after 30 seconds
if (laserState && (millis() - laserStartTime > 30000)) {
digitalWrite(laserPin, LOW);
laserState = false;
}
}
// Handle incoming HTTP requests
void handleRequest(String request) {
// Movement Commands
if (request.indexOf("GET /?move=forward") != -1) moveForward();
else if (request.indexOf("GET /?move=backward") != -1) moveBackward();
else if (request.indexOf("GET /?move=left") != -1) moveSidewaysLeft();
else if (request.indexOf("GET /?move=right") != -1) moveSidewaysRight();
else if (request.indexOf("GET /?move=rotateLeft") != -1) rotateLeft();
else if (request.indexOf("GET /?move=rotateRight") != -1) rotateRight();
else if (request.indexOf("GET /?move=rightForward") != -1) moveRightForward();
else if (request.indexOf("GET /?move=rightBackward") != -1) moveRightBackward();
else if (request.indexOf("GET /?move=leftForward") != -1) moveLeftForward();
else if (request.indexOf("GET /?move=leftBackward") != -1) moveLeftBackward();
else if (request.indexOf("GET /?move=stop") != -1) stopMoving();
// Servo Control
if (request.indexOf("GET /?pan=") != -1) {
int startPos = request.indexOf("pan=") + 4;
int endPos = request.indexOf("&", startPos);
if (endPos == -1) endPos = request.indexOf(" ", startPos);
if (endPos != -1) {
targetPanPosition = request.substring(startPos, endPos).toInt();
targetPanPosition = constrain(targetPanPosition, 0, 180);
}
}
if (request.indexOf("GET /?tilt=") != -1) {
int startPos = request.indexOf("tilt=") + 5;
int endPos = request.indexOf("&", startPos);
if (endPos == -1) endPos = request.indexOf(" ", startPos);
if (endPos != -1) {
targetTiltPosition = request.substring(startPos, endPos).toInt();
targetTiltPosition = constrain(targetTiltPosition, 0, 180);
}
}
// Laser & Dispenser
if (request.indexOf("GET /?laser=on") != -1) {
digitalWrite(laserPin, HIGH);
laserState = true;
laserStartTime = millis();
} else if (request.indexOf("GET /?laser=off") != -1) {
digitalWrite(laserPin, LOW);
laserState = false;
}
if (request.indexOf("GET /?dispense") != -1) dispenseFood();
}
// Handle smooth servo movement
void updateServoPositions() {
unsigned long currentTime = millis();
// Only update servos at the throttled rate
if (currentTime - lastCommandTime >= commandThrottle) {
// Update pan servo if needed
if (currentPanPosition != targetPanPosition) {
// Move one degree at a time for smoother motion
if (currentPanPosition < targetPanPosition) {
currentPanPosition++;
} else {
currentPanPosition--;
}
panServo.write(currentPanPosition);
}
// Update tilt servo if needed
if (currentTiltPosition != targetTiltPosition) {
// Move one degree at a time for smoother motion
if (currentTiltPosition < targetTiltPosition) {
currentTiltPosition++;
} else {
currentTiltPosition--;
}
tiltServo.write(currentTiltPosition);
}
lastCommandTime = currentTime;
}
}
// Movement Functions
void moveForward() {
LeftFrontWheel->setSpeed(wheelSpeed);
LeftBackWheel->setSpeed(wheelSpeed);
RightFrontWheel->setSpeed(wheelSpeed);
RightBackWheel->setSpeed(wheelSpeed);
LeftFrontWheel->run(FORWARD);
LeftBackWheel->run(FORWARD);
RightFrontWheel->run(FORWARD);
RightBackWheel->run(FORWARD);
}
void moveBackward() {
LeftFrontWheel->setSpeed(wheelSpeed);
LeftBackWheel->setSpeed(wheelSpeed);
RightFrontWheel->setSpeed(wheelSpeed);
RightBackWheel->setSpeed(wheelSpeed);
LeftFrontWheel->run(BACKWARD);
LeftBackWheel->run(BACKWARD);
RightFrontWheel->run(BACKWARD);
RightBackWheel->run(BACKWARD);
}
void moveSidewaysRight() {
LeftFrontWheel->setSpeed(wheelSpeed);
LeftBackWheel->setSpeed(wheelSpeed);
RightFrontWheel->setSpeed(wheelSpeed);
RightBackWheel->setSpeed(wheelSpeed);
LeftFrontWheel->run(FORWARD);
LeftBackWheel->run(BACKWARD);
RightFrontWheel->run(BACKWARD);
RightBackWheel->run(FORWARD);
}
void moveSidewaysLeft() {
LeftFrontWheel->setSpeed(wheelSpeed);
LeftBackWheel->setSpeed(wheelSpeed);
RightFrontWheel->setSpeed(wheelSpeed);
RightBackWheel->setSpeed(wheelSpeed);
LeftFrontWheel->run(BACKWARD);
LeftBackWheel->run(FORWARD);
RightFrontWheel->run(FORWARD);
RightBackWheel->run(BACKWARD);
}
void rotateLeft() {
LeftFrontWheel->setSpeed(wheelSpeed);
LeftBackWheel->setSpeed(wheelSpeed);
RightFrontWheel->setSpeed(wheelSpeed);
RightBackWheel->setSpeed(wheelSpeed);
LeftFrontWheel->run(BACKWARD);
LeftBackWheel->run(BACKWARD);
RightFrontWheel->run(FORWARD);
RightBackWheel->run(FORWARD);
}
void rotateRight() {
LeftFrontWheel->setSpeed(wheelSpeed);
LeftBackWheel->setSpeed(wheelSpeed);
RightFrontWheel->setSpeed(wheelSpeed);
RightBackWheel->setSpeed(wheelSpeed);
LeftFrontWheel->run(FORWARD);
LeftBackWheel->run(FORWARD);
RightFrontWheel->run(BACKWARD);
RightBackWheel->run(BACKWARD);
}
void moveRightForward() {
LeftFrontWheel->setSpeed(wheelSpeed);
LeftBackWheel->setSpeed(0);
RightFrontWheel->setSpeed(0);
RightBackWheel->setSpeed(wheelSpeed);
LeftFrontWheel->run(FORWARD);
RightBackWheel->run(FORWARD);
}
void moveRightBackward() {
LeftFrontWheel->setSpeed(0);
LeftBackWheel->setSpeed(wheelSpeed);
RightFrontWheel->setSpeed(wheelSpeed);
RightBackWheel->setSpeed(0);
LeftBackWheel->run(BACKWARD);
RightFrontWheel->run(BACKWARD);
}
void moveLeftForward() {
LeftFrontWheel->setSpeed(0);
LeftBackWheel->setSpeed(wheelSpeed);
RightFrontWheel->setSpeed(wheelSpeed);
RightBackWheel->setSpeed(0);
LeftBackWheel->run(FORWARD);
RightFrontWheel->run(FORWARD);
}
void moveLeftBackward() {
LeftFrontWheel->setSpeed(wheelSpeed);
LeftBackWheel->setSpeed(0);
RightFrontWheel->setSpeed(0);
RightBackWheel->setSpeed(wheelSpeed);
LeftFrontWheel->run(BACKWARD);
RightBackWheel->run(BACKWARD);
}
void stopMoving() {
LeftFrontWheel->setSpeed(0);
LeftBackWheel->setSpeed(0);
RightFrontWheel->setSpeed(0);
RightBackWheel->setSpeed(0);
}
void dispenseFood() {
dispenserServo.write(90);
delay(500);
dispenserServo.write(0);
}
// HTML Page with Enhanced Controls
void sendHTML(WiFiClient &client) {
// HTTP headers
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println("Connection: close");
client.println(); // Empty line between headers and content
// HTML content
client.println("<!DOCTYPE html>");
client.println("<html>");
client.println("<head>");
client.println("<title>PetBot Control</title>");
client.println("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
client.println("<style>");
client.println("body { text-align: center; font-family: Arial; background-color: #f0f0f0; margin: 0; padding: 20px; }");
client.println(".container { max-width: 800px; margin: 0 auto; background-color: white; padding: 20px; border-radius: 10px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); }");
client.println(".grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; max-width: 600px; margin: 0 auto; }");
client.println("button { padding: 15px; margin: 5px; background-color: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer; transition: background-color 0.3s; }");
client.println("button:hover { background-color: #45a049; }");
client.println(".slider-container { margin: 15px 0; }");
client.println(".slider { width: 80%; margin: 10px; }");
client.println("h1, h3 { color: #333; }");
client.println(".value-display { display: inline-block; width: 40px; text-align: center; font-weight: bold; }");
client.println(".control-section { margin-bottom: 30px; border-bottom: 1px solid #eee; padding-bottom: 20px; }");
client.println("</style>");
client.println("</head>");
client.println("<body>");
client.println("<div class=\"container\">");
client.println("<h1>PetBot Controller</h1>");
client.println("<div class=\"control-section\">");
client.println("<h3>Movement Controls</h3>");
client.println("<div class=\"grid\">");
client.println("<button onmousedown=\"sendCommand('move=leftForward')\" onmouseup=\"sendCommand('move=stop')\" ontouchstart=\"sendCommand('move=leftForward')\" ontouchend=\"sendCommand('move=stop')\">Left Forward</button>");
client.println("<button onmousedown=\"sendCommand('move=forward')\" onmouseup=\"sendCommand('move=stop')\" ontouchstart=\"sendCommand('move=forward')\" ontouchend=\"sendCommand('move=stop')\">Forward</button>");
client.println("<button onmousedown=\"sendCommand('move=rightForward')\" onmouseup=\"sendCommand('move=stop')\" ontouchstart=\"sendCommand('move=rightForward')\" ontouchend=\"sendCommand('move=stop')\">Right Forward</button>");
client.println("<button onmousedown=\"sendCommand('move=left')\" onmouseup=\"sendCommand('move=stop')\" ontouchstart=\"sendCommand('move=left')\" ontouchend=\"sendCommand('move=stop')\">Left</button>");
client.println("<button onmousedown=\"sendCommand('move=stop')\" ontouchstart=\"sendCommand('move=stop')\">Stop</button>");
client.println("<button onmousedown=\"sendCommand('move=right')\" onmouseup=\"sendCommand('move=stop')\" ontouchstart=\"sendCommand('move=right')\" ontouchend=\"sendCommand('move=stop')\">Right</button>");
client.println("<button onmousedown=\"sendCommand('move=leftBackward')\" onmouseup=\"sendCommand('move=stop')\" ontouchstart=\"sendCommand('move=leftBackward')\" ontouchend=\"sendCommand('move=stop')\">Left Backward</button>");
client.println("<button onmousedown=\"sendCommand('move=backward')\" onmouseup=\"sendCommand('move=stop')\" ontouchstart=\"sendCommand('move=backward')\" ontouchend=\"sendCommand('move=stop')\">Backward</button>");
client.println("<button onmousedown=\"sendCommand('move=rightBackward')\" onmouseup=\"sendCommand('move=stop')\" ontouchstart=\"sendCommand('move=rightBackward')\" ontouchend=\"sendCommand('move=stop')\">Right Backward</button>");
client.println("</div>");
client.println("<div style=\"margin-top: 15px;\">");
client.println("<button onmousedown=\"sendCommand('move=rotateLeft')\" onmouseup=\"sendCommand('move=stop')\" ontouchstart=\"sendCommand('move=rotateLeft')\" ontouchend=\"sendCommand('move=stop')\">Rotate Left</button>");
client.println("<button onmousedown=\"sendCommand('move=rotateRight')\" onmouseup=\"sendCommand('move=stop')\" ontouchstart=\"sendCommand('move=rotateRight')\" ontouchend=\"sendCommand('move=stop')\">Rotate Right</button>");
client.println("</div>");
client.println("</div>");
client.println("<div class=\"control-section\">");
client.println("<h3>Laser Aiming</h3>");
client.println("<div class=\"slider-container\">");
client.println("Pan: <input type=\"range\" min=\"0\" max=\"180\" value=\"" + String(currentPanPosition) + "\" class=\"slider\" id=\"pan\" oninput=\"updateSliderValue('pan', this.value)\" onchange=\"sendPanTilt()\">");
client.println("<span id=\"panValue\" class=\"value-display\">" + String(currentPanPosition) + "</span>");
client.println("</div>");
client.println("<div class=\"slider-container\">");
client.println("Tilt: <input type=\"range\" min=\"0\" max=\"180\" value=\"" + String(currentTiltPosition) + "\" class=\"slider\" id=\"tilt\" oninput=\"updateSliderValue('tilt', this.value)\" onchange=\"sendPanTilt()\">");
client.println("<span id=\"tiltValue\" class=\"value-display\">" + String(currentTiltPosition) + "</span>");
client.println("</div>");
client.println("<button id=\"laserButton\" onclick=\"toggleLaser()\">Laser ON</button>");
client.println("</div>");
client.println("<div class=\"control-section\" style=\"border-bottom: none;\">");
client.println("<h3>Feeding System</h3>");
client.println("<button onclick=\"sendCommand('dispense')\">Dispense Food</button>");
client.println("</div>");
client.println("<script>");
// Improved JavaScript for better control
client.println("let laserOn = false;");
client.println("let panValue = " + String(currentPanPosition) + ";");
client.println("let tiltValue = " + String(currentTiltPosition) + ";");
client.println("let panTimer, tiltTimer;");
client.println("function updateSliderValue(id, value) {");
client.println(" document.getElementById(id + 'Value').innerHTML = value;");
client.println(" if (id === 'pan') {");
client.println(" panValue = value;");
client.println(" clearTimeout(panTimer);");
client.println(" panTimer = setTimeout(() => sendPanTilt(), 100);");
client.println(" } else if (id === 'tilt') {");
client.println(" tiltValue = value;");
client.println(" clearTimeout(tiltTimer);");
client.println(" tiltTimer = setTimeout(() => sendPanTilt(), 100);");
client.println(" }");
client.println("}");
client.println("function sendPanTilt() {");
client.println(" fetch('/?pan=' + panValue + '&tilt=' + tiltValue);");
client.println("}");
client.println("function toggleLaser() {");
client.println(" laserOn = !laserOn;");
client.println(" const cmd = laserOn ? 'laser=on' : 'laser=off';");
client.println(" fetch('/?' + cmd);");
client.println(" document.getElementById('laserButton').innerHTML = laserOn ? 'Laser OFF' : 'Laser ON';");
client.println("}");
client.println("function sendCommand(cmd) {");
client.println(" fetch('/?' + cmd);");
client.println("}");
client.println("</script>");
client.println("</div>");
client.println("</body>");
client.println("</html>");
}
Printing Instructions:
· Filament Material: PLA (recommended)
· Layer Height: 0.2mm
· Infill: 15-25%
· Supports: Yes supports will be required but not that significantly.
Assembly Instructions:
1. Attach motors to body frame by super glue or hot glue.
2. Attach the roof of the robot to the body with screws.
3. You can change the model according to your parts cause mine and yours cannot be same.
4. The dispensing door should be attached to servo by screws.
5. The wiring should be soldered to motor shield precisely or you can use headers but they have loose connection so I wouldn’t recommend it.
6. The pan and tilt servo should be fitted without any wire coming in a way or you will end up with strangled wires.
7. Motor shield should be stacked over Arduino gently.
8. Upload the code to your arduino using arduino IDE by selecting correct Board and COM port.
Demo and Usage instructions:
1. After uploading code to Arduino unplug it and connect it to battery.
2. All Arduino uno R4 Wi-Fi have same asses point IP. The IP is http://192.168.4.1 .
3. To access the webpage, connect your device to the Wi-Fi credentials: SSID: PetBot, Password:12345678 you can change this credentials in code.
4. You need to modify and calibrate servo in code cause mine and yours cannot be same.
5. You need to check motor direction and swap wires if needed.
6. After calibrating and modifying code yout PET V1 is ready to start working.
7. Interact with the webpage and enjoy 😊.
Additional Notes or Tips:
1. Read the note given in this document very carefully this are really important.
2. If your servo doesn’t work check the wiring and power supply.
3. If motors doesn’t work make sure you tightened the screws of the terminals
4. In the end if anything goes wrong share it with forums or community also you can use A.I for troubleshooting.
Photos:
I don’t have access/own any 3d printer so I couldn’t make this project but I have put my heart and soul in designing and coding. I really admire to make this project when I have access to 3d printer. Well you will find all files, photos in the page. But here is image of the webpage.